2 * Copyright (C) 2006-2016 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #import "CSSPrimitiveValueMappings.h"
31 #import "CSSValuePool.h"
33 #import "DataTransfer.h"
34 #import "DocumentFragment.h"
35 #import "DocumentLoader.h"
38 #import "EditorClient.h"
40 #import "FontCascade.h"
42 #import "FrameLoaderClient.h"
44 #import "HTMLAnchorElement.h"
45 #import "HTMLAttachmentElement.h"
46 #import "HTMLConverter.h"
47 #import "HTMLElement.h"
48 #import "HTMLImageElement.h"
50 #import "LegacyWebArchive.h"
51 #import "MIMETypeRegistry.h"
52 #import "NodeTraversal.h"
54 #import "Pasteboard.h"
55 #import "PasteboardStrategy.h"
56 #import "PlatformStrategies.h"
58 #import "RenderBlock.h"
59 #import "RenderImage.h"
60 #import "RuntimeApplicationChecks.h"
63 #import "StyleProperties.h"
65 #import "TypingCommand.h"
67 #import "WebNSAttributedStringExtras.h"
72 using namespace HTMLNames;
74 void Editor::showFontPanel()
76 [[NSFontManager sharedFontManager] orderFrontFontPanel:nil];
79 void Editor::showStylesPanel()
81 [[NSFontManager sharedFontManager] orderFrontStylesPanel:nil];
84 void Editor::showColorPanel()
86 [[NSApplication sharedApplication] orderFrontColorPanel:nil];
89 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText, MailBlockquoteHandling mailBlockquoteHandling)
91 RefPtr<Range> range = selectedRange();
93 #pragma clang diagnostic push
94 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
95 // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
96 client()->setInsertionPasteboard(NSGeneralPboard);
97 #pragma clang diagnostic pop
100 RefPtr<DocumentFragment> fragment = webContentFromPasteboard(*pasteboard, *range, allowPlainText, chosePlainText);
102 if (fragment && shouldInsertFragment(fragment, range, EditorInsertAction::Pasted))
103 pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(*pasteboard), false, mailBlockquoteHandling);
105 client()->setInsertionPasteboard(String());
108 bool Editor::canCopyExcludingStandaloneImages()
110 const VisibleSelection& selection = m_frame.selection().selection();
111 return selection.isRange() && !selection.isInPasswordField();
114 void Editor::takeFindStringFromSelection()
116 if (!canCopyExcludingStandaloneImages()) {
121 Vector<String> types;
122 types.append(String(NSStringPboardType));
123 #pragma clang diagnostic push
124 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
125 platformStrategies()->pasteboardStrategy()->setTypes(types, NSFindPboard);
126 platformStrategies()->pasteboardStrategy()->setStringForType(m_frame.displayStringModifiedByEncoding(selectedTextForDataTransfer()), NSStringPboardType, NSFindPboard);
127 #pragma clang diagnostic pop
130 void Editor::readSelectionFromPasteboard(const String& pasteboardName, MailBlockquoteHandling mailBlockquoteHandling)
132 Pasteboard pasteboard(pasteboardName);
133 if (m_frame.selection().selection().isContentRichlyEditable())
134 pasteWithPasteboard(&pasteboard, true, mailBlockquoteHandling);
136 pasteAsPlainTextWithPasteboard(pasteboard);
139 static void maybeCopyNodeAttributesToFragment(const Node& node, DocumentFragment& fragment)
141 // This is only supported for single-Node fragments.
142 Node* firstChild = fragment.firstChild();
143 if (!firstChild || firstChild != fragment.lastChild())
146 // And only supported for HTML elements.
147 if (!node.isHTMLElement() || !firstChild->isHTMLElement())
150 // And only if the source Element and destination Element have the same HTML tag name.
151 const HTMLElement& oldElement = downcast<HTMLElement>(node);
152 HTMLElement& newElement = downcast<HTMLElement>(*firstChild);
153 if (oldElement.localName() != newElement.localName())
156 for (const Attribute& attribute : oldElement.attributesIterator()) {
157 if (newElement.hasAttribute(attribute.name()))
159 newElement.setAttribute(attribute.name(), attribute.value());
163 void Editor::replaceNodeFromPasteboard(Node* node, const String& pasteboardName)
167 if (&node->document() != m_frame.document())
170 Ref<Frame> protector(m_frame);
171 RefPtr<Range> range = Range::create(node->document(), Position(node, Position::PositionIsBeforeAnchor), Position(node, Position::PositionIsAfterAnchor));
172 m_frame.selection().setSelection(VisibleSelection(*range), FrameSelection::DoNotSetFocus);
174 Pasteboard pasteboard(pasteboardName);
176 if (!m_frame.selection().selection().isContentRichlyEditable()) {
177 pasteAsPlainTextWithPasteboard(pasteboard);
181 #pragma clang diagnostic push
182 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
183 // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
184 client()->setInsertionPasteboard(NSGeneralPboard);
185 #pragma clang diagnostic pop
188 if (RefPtr<DocumentFragment> fragment = webContentFromPasteboard(pasteboard, *range, true, chosePlainText)) {
189 maybeCopyNodeAttributesToFragment(*node, *fragment);
190 if (shouldInsertFragment(fragment, range, EditorInsertAction::Pasted))
191 pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(pasteboard), false, MailBlockquoteHandling::IgnoreBlockquote);
194 client()->setInsertionPasteboard(String());
197 String Editor::selectionInHTMLFormat()
199 return createMarkup(*selectedRange(), nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
202 RefPtr<SharedBuffer> Editor::imageInWebArchiveFormat(Element& imageElement)
204 RefPtr<LegacyWebArchive> archive = LegacyWebArchive::create(imageElement);
207 return SharedBuffer::wrapCFData(archive->rawDataRepresentation().get());
210 RefPtr<SharedBuffer> Editor::dataSelectionForPasteboard(const String& pasteboardType)
212 // FIXME: The interface to this function is awkward. We'd probably be better off with three separate functions.
213 // As of this writing, this is only used in WebKit2 to implement the method -[WKView writeSelectionToPasteboard:types:],
214 // which is only used to support OS X services.
216 // FIXME: Does this function really need to use adjustedSelectionRange()? Because writeSelectionToPasteboard() just uses selectedRange().
220 if (pasteboardType == WebArchivePboardType)
221 return selectionInWebArchiveFormat();
223 if (pasteboardType == String(NSRTFDPboardType))
224 return dataInRTFDFormat(attributedStringFromRange(*adjustedSelectionRange()));
226 if (pasteboardType == String(NSRTFPboardType)) {
227 NSAttributedString* attributedString = attributedStringFromRange(*adjustedSelectionRange());
228 // FIXME: Why is this attachment character stripping needed here, but not needed in writeSelectionToPasteboard?
229 if ([attributedString containsAttachments])
230 attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
231 return dataInRTFFormat(attributedString);
237 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
239 auto* renderer = imageElement.renderer();
240 if (!is<RenderImage>(renderer))
243 CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
244 if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
247 image = tentativeCachedImage->imageForRenderer(renderer);
251 cachedImage = tentativeCachedImage;
254 String Editor::userVisibleString(const URL& url)
256 return client()->userVisibleString(url);
259 void Editor::selectionWillChange()
261 if (!hasComposition() || ignoreSelectionChanges() || m_frame.selection().isNone())
265 client()->canceledComposition();
268 String Editor::plainTextFromPasteboard(const PasteboardPlainText& text)
270 String string = text.text;
272 // FIXME: It's not clear this is 100% correct since we know -[NSURL URLWithString:] does not handle
273 // all the same cases we handle well in the URL code for creating an NSURL.
275 string = client()->userVisibleString([NSURL URLWithString:string]);
277 // FIXME: WTF should offer a non-Mac-specific way to convert string to precomposed form so we can do it for all platforms.
278 return [(NSString *)string precomposedStringWithCanonicalMapping];
281 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL& url, const String& title)
283 PasteboardImage pasteboardImage;
285 CachedImage* cachedImage;
286 getImage(imageElement, pasteboardImage.image, cachedImage);
287 if (!pasteboardImage.image)
291 pasteboardImage.dataInWebArchiveFormat = imageInWebArchiveFormat(imageElement);
292 pasteboardImage.url.url = url;
293 pasteboardImage.url.title = title;
294 pasteboardImage.url.userVisibleForm = client()->userVisibleString(pasteboardImage.url.url);
295 pasteboardImage.resourceData = cachedImage->resourceBuffer();
296 pasteboardImage.resourceMIMEType = cachedImage->response().mimeType();
298 pasteboard.write(pasteboardImage);
301 class Editor::WebContentReader final : public PasteboardWebContentReader {
305 const bool allowPlainText;
307 RefPtr<DocumentFragment> fragment;
308 bool madeFragmentFromPlainText;
310 WebContentReader(Frame& frame, Range& context, bool allowPlainText)
313 , allowPlainText(allowPlainText)
314 , madeFragmentFromPlainText(false)
319 bool readWebArchive(SharedBuffer*) override;
320 bool readFilenames(const Vector<String>&) override;
321 bool readHTML(const String&) override;
322 bool readRTFD(SharedBuffer&) override;
323 bool readRTF(SharedBuffer&) override;
324 bool readImage(Ref<SharedBuffer>&&, const String& type) override;
325 bool readURL(const URL&, const String& title) override;
326 bool readPlainText(const String&) override;
329 bool Editor::WebContentReader::readWebArchive(SharedBuffer* buffer)
331 if (frame.settings().preferMIMETypeForImages())
334 if (!frame.document())
340 auto archive = LegacyWebArchive::create(URL(), *buffer);
344 RefPtr<ArchiveResource> mainResource = archive->mainResource();
348 const String& type = mainResource->mimeType();
350 if (frame.loader().client().canShowMIMETypeAsHTML(type)) {
351 // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
352 if (DocumentLoader* loader = frame.loader().documentLoader())
353 loader->addAllArchiveResources(*archive);
355 String markupString = String::fromUTF8(mainResource->data().data(), mainResource->data().size());
356 fragment = createFragmentFromMarkup(*frame.document(), markupString, mainResource->url(), DisallowScriptingAndPluginContent);
360 if (MIMETypeRegistry::isSupportedImageMIMEType(type)) {
361 fragment = frame.editor().createFragmentForImageResourceAndAddResource(WTFMove(mainResource));
368 bool Editor::WebContentReader::readFilenames(const Vector<String>& paths)
373 if (!frame.document())
375 Document& document = *frame.document();
377 fragment = document.createDocumentFragment();
379 for (auto& text : paths) {
380 #if ENABLE(ATTACHMENT_ELEMENT)
381 auto attachment = HTMLAttachmentElement::create(attachmentTag, document);
382 attachment->setFile(File::create([[NSURL fileURLWithPath:text] path]).ptr());
383 fragment->appendChild(attachment);
385 auto paragraph = createDefaultParagraphElement(document);
386 paragraph->appendChild(document.createTextNode(frame.editor().client()->userVisibleString([NSURL fileURLWithPath:text])));
387 fragment->appendChild(paragraph);
394 bool Editor::WebContentReader::readHTML(const String& string)
396 String stringOmittingMicrosoftPrefix = string;
398 // This code was added to make HTML paste from Microsoft Word on Mac work, back in 2004.
399 // It's a simple-minded way to ignore the CF_HTML clipboard format, just skipping over the
400 // description part and parsing the entire context plus fragment.
401 if (string.startsWith("Version:")) {
402 size_t location = string.findIgnoringCase("<html");
403 if (location != notFound)
404 stringOmittingMicrosoftPrefix = string.substring(location);
407 if (stringOmittingMicrosoftPrefix.isEmpty())
410 if (!frame.document())
412 Document& document = *frame.document();
414 fragment = createFragmentFromMarkup(document, stringOmittingMicrosoftPrefix, emptyString(), DisallowScriptingAndPluginContent);
418 bool Editor::WebContentReader::readRTFD(SharedBuffer& buffer)
420 if (frame.settings().preferMIMETypeForImages())
423 fragment = frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTFD:buffer.createNSData().get() documentAttributes:nullptr]).get());
427 bool Editor::WebContentReader::readRTF(SharedBuffer& buffer)
429 if (frame.settings().preferMIMETypeForImages())
432 fragment = frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTF:buffer.createNSData().get() documentAttributes:nullptr]).get());
436 bool Editor::WebContentReader::readImage(Ref<SharedBuffer>&& buffer, const String& type)
438 ASSERT(type.contains('/'));
439 String typeAsFilenameWithExtension = type;
440 typeAsFilenameWithExtension.replace('/', '.');
442 Vector<uint8_t> data;
443 data.append(buffer->data(), buffer->size());
444 auto blob = Blob::create(WTFMove(data), type);
445 ASSERT(frame.document());
446 String blobURL = DOMURL::createObjectURL(*frame.document(), blob);
448 fragment = frame.editor().createFragmentForImageAndURL(blobURL);
452 bool Editor::WebContentReader::readURL(const URL& url, const String& title)
454 if (url.string().isEmpty())
457 auto anchor = HTMLAnchorElement::create(*frame.document());
458 anchor->setAttributeWithoutSynchronization(HTMLNames::hrefAttr, url.string());
459 anchor->appendChild(frame.document()->createTextNode([title precomposedStringWithCanonicalMapping]));
461 fragment = frame.document()->createDocumentFragment();
462 fragment->appendChild(anchor);
466 bool Editor::WebContentReader::readPlainText(const String& text)
471 fragment = createFragmentFromText(context, [text precomposedStringWithCanonicalMapping]);
475 madeFragmentFromPlainText = true;
479 // FIXME: Should give this function a name that makes it clear it adds resources to the document loader as a side effect.
480 // Or refactor so it does not do that.
481 RefPtr<DocumentFragment> Editor::webContentFromPasteboard(Pasteboard& pasteboard, Range& context, bool allowPlainText, bool& chosePlainText)
483 WebContentReader reader(m_frame, context, allowPlainText);
484 pasteboard.read(reader);
485 chosePlainText = reader.madeFragmentFromPlainText;
486 return WTFMove(reader.fragment);
489 void Editor::applyFontStyles(const String& fontFamily, double fontSize, unsigned fontTraits)
491 auto& cssValuePool = CSSValuePool::singleton();
492 Ref<MutableStyleProperties> style = MutableStyleProperties::create();
493 style->setProperty(CSSPropertyFontFamily, cssValuePool.createFontFamilyValue(fontFamily));
494 style->setProperty(CSSPropertyFontStyle, (fontTraits & NSFontItalicTrait) ? CSSValueItalic : CSSValueNormal);
495 style->setProperty(CSSPropertyFontWeight, (fontTraits & NSFontBoldTrait) ? CSSValueBold : CSSValueNormal);
496 style->setProperty(CSSPropertyFontSize, cssValuePool.createValue(fontSize, CSSPrimitiveValue::CSS_PX));
497 applyStyleToSelection(style.ptr(), EditActionSetFont);
500 } // namespace WebCore