2 * Copyright (C) 2006, 2007, 2008, 2013 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 COMPUTER, 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 COMPUTER, 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.
29 #import "CachedResourceLoader.h"
32 #import "DOMRangeInternal.h"
33 #import "DocumentFragment.h"
34 #import "DocumentLoader.h"
36 #import "EditorClient.h"
39 #import "FrameLoaderClient.h"
41 #import "HTMLConverter.h"
42 #import "HTMLElement.h"
44 #import "LegacyWebArchive.h"
45 #import "MIMETypeRegistry.h"
46 #import "NodeTraversal.h"
48 #import "Pasteboard.h"
49 #import "PasteboardStrategy.h"
50 #import "PlatformStrategies.h"
52 #import "RenderBlock.h"
53 #import "RenderImage.h"
54 #import "ResourceBuffer.h"
55 #import "RuntimeApplicationChecks.h"
57 #import "StyleProperties.h"
59 #import "TypingCommand.h"
61 #import "WebNSAttributedStringExtras.h"
62 #import "htmlediting.h"
67 using namespace HTMLNames;
69 void Editor::showFontPanel()
71 [[NSFontManager sharedFontManager] orderFrontFontPanel:nil];
74 void Editor::showStylesPanel()
76 [[NSFontManager sharedFontManager] orderFrontStylesPanel:nil];
79 void Editor::showColorPanel()
81 [[NSApplication sharedApplication] orderFrontColorPanel:nil];
84 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText)
86 RefPtr<Range> range = selectedRange();
88 // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
89 client()->setInsertionPasteboard(NSGeneralPboard);
92 RefPtr<DocumentFragment> fragment = webContentFromPasteboard(*pasteboard, *range, allowPlainText, chosePlainText);
94 if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
95 pasteAsFragment(fragment, canSmartReplaceWithPasteboard(*pasteboard), false);
97 client()->setInsertionPasteboard(String());
100 bool Editor::insertParagraphSeparatorInQuotedContent()
102 // FIXME: Why is this missing calls to canEdit, canEditRichly, etc.?
103 TypingCommand::insertParagraphSeparatorInQuotedContent(document());
104 revealSelectionAfterEditingOperation();
108 static RenderStyle* styleForSelectionStart(Frame* frame, Node *&nodeToRemove)
112 if (frame->selection().isNone())
115 Position position = frame->selection().selection().visibleStart().deepEquivalent();
116 if (!position.isCandidate() || position.isNull())
119 RefPtr<EditingStyle> typingStyle = frame->selection().typingStyle();
120 if (!typingStyle || !typingStyle->style())
121 return &position.deprecatedNode()->renderer()->style();
123 RefPtr<Element> styleElement = frame->document()->createElement(spanTag, false);
125 String styleText = typingStyle->style()->asText() + " display: inline";
126 styleElement->setAttribute(styleAttr, styleText);
128 styleElement->appendChild(frame->document()->createEditingTextNode(""), ASSERT_NO_EXCEPTION);
130 position.deprecatedNode()->parentNode()->appendChild(styleElement, ASSERT_NO_EXCEPTION);
132 nodeToRemove = styleElement.get();
133 return styleElement->renderer() ? &styleElement->renderer()->style() : 0;
136 const SimpleFontData* Editor::fontForSelection(bool& hasMultipleFonts) const
138 hasMultipleFonts = false;
140 if (!m_frame.selection().isRange()) {
142 RenderStyle* style = styleForSelectionStart(&m_frame, nodeToRemove); // sets nodeToRemove
144 const SimpleFontData* result = 0;
146 result = style->font().primaryFont();
149 nodeToRemove->remove(ASSERT_NO_EXCEPTION);
154 const SimpleFontData* font = 0;
155 RefPtr<Range> range = m_frame.selection().toNormalizedRange();
156 Node* startNode = adjustedSelectionStartForStyleComputation(m_frame.selection().selection()).deprecatedNode();
157 if (range && startNode) {
158 Node* pastEnd = range->pastLastNode();
159 // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
160 // unreproducible case where this didn't happen, so check for null also.
161 for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(node)) {
162 auto renderer = node->renderer();
165 // FIXME: Are there any node types that have renderers, but that we should be skipping?
166 const SimpleFontData* primaryFont = renderer->style().font().primaryFont();
169 else if (font != primaryFont) {
170 hasMultipleFonts = true;
179 NSDictionary* Editor::fontAttributesForSelectionStart() const
182 RenderStyle* style = styleForSelectionStart(&m_frame, nodeToRemove);
186 NSMutableDictionary* result = [NSMutableDictionary dictionary];
188 if (style->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && style->visitedDependentColor(CSSPropertyBackgroundColor).alpha() != 0)
189 [result setObject:nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName];
191 if (style->font().primaryFont()->getNSFont())
192 [result setObject:style->font().primaryFont()->getNSFont() forKey:NSFontAttributeName];
194 if (style->visitedDependentColor(CSSPropertyColor).isValid() && style->visitedDependentColor(CSSPropertyColor) != Color::black)
195 [result setObject:nsColor(style->visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName];
197 const ShadowData* shadow = style->textShadow();
199 RetainPtr<NSShadow> s = adoptNS([[NSShadow alloc] init]);
200 [s.get() setShadowOffset:NSMakeSize(shadow->x(), shadow->y())];
201 [s.get() setShadowBlurRadius:shadow->radius()];
202 [s.get() setShadowColor:nsColor(shadow->color())];
203 [result setObject:s.get() forKey:NSShadowAttributeName];
206 int decoration = style->textDecorationsInEffect();
207 if (decoration & TextDecorationLineThrough)
208 [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
210 int superscriptInt = 0;
211 switch (style->verticalAlign()) {
214 case BASELINE_MIDDLE:
229 [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName];
231 if (decoration & TextDecorationUnderline)
232 [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
235 nodeToRemove->remove(ASSERT_NO_EXCEPTION);
240 bool Editor::canCopyExcludingStandaloneImages()
242 FrameSelection& selection = m_frame.selection();
243 return selection.isRange() && !selection.isInPasswordField();
246 void Editor::takeFindStringFromSelection()
248 if (!canCopyExcludingStandaloneImages()) {
253 Vector<String> types;
254 types.append(String(NSStringPboardType));
255 platformStrategies()->pasteboardStrategy()->setTypes(types, NSFindPboard);
256 platformStrategies()->pasteboardStrategy()->setStringForType(m_frame.displayStringModifiedByEncoding(selectedTextForClipboard()), NSStringPboardType, NSFindPboard);
259 void Editor::readSelectionFromPasteboard(const String& pasteboardName)
261 Pasteboard pasteboard(pasteboardName);
262 if (m_frame.selection().isContentRichlyEditable())
263 pasteWithPasteboard(&pasteboard, true);
265 pasteAsPlainTextWithPasteboard(pasteboard);
268 // FIXME: Makes no sense that selectedTextForClipboard always includes alt text, but stringSelectionForPasteboard does not.
269 // This was left in a bad state when selectedTextForClipboard was added. Need to look over clients and fix this.
270 String Editor::stringSelectionForPasteboard()
274 String text = selectedText();
275 text.replace(noBreakSpace, ' ');
279 String Editor::stringSelectionForPasteboardWithImageAltText()
283 String text = selectedTextForClipboard();
284 text.replace(noBreakSpace, ' ');
288 PassRefPtr<SharedBuffer> Editor::selectionInWebArchiveFormat()
290 RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(&m_frame);
291 return archive ? SharedBuffer::wrapCFData(archive->rawDataRepresentation().get()) : 0;
294 PassRefPtr<Range> Editor::adjustedSelectionRange()
296 // FIXME: Why do we need to adjust the selection to include the anchor tag it's in?
297 // Whoever wrote this code originally forgot to leave us a comment explaining the rationale.
298 RefPtr<Range> range = selectedRange();
299 Node* commonAncestor = range->commonAncestorContainer(IGNORE_EXCEPTION);
300 ASSERT(commonAncestor);
301 Node* enclosingAnchor = enclosingNodeWithTag(firstPositionInNode(commonAncestor), HTMLNames::aTag);
302 if (enclosingAnchor && comparePositions(firstPositionInOrBeforeNode(range->startPosition().anchorNode()), range->startPosition()) >= 0)
303 range->setStart(enclosingAnchor, 0, IGNORE_EXCEPTION);
307 static NSAttributedString *attributedStringForRange(Range& range)
309 return [adoptNS([[WebHTMLConverter alloc] initWithDOMRange:kit(&range)]) attributedString];
312 static PassRefPtr<SharedBuffer> dataInRTFDFormat(NSAttributedString *string)
314 NSUInteger length = [string length];
315 return length ? SharedBuffer::wrapNSData([string RTFDFromRange:NSMakeRange(0, length) documentAttributes:nil]) : 0;
318 static PassRefPtr<SharedBuffer> dataInRTFFormat(NSAttributedString *string)
320 NSUInteger length = [string length];
321 return length ? SharedBuffer::wrapNSData([string RTFFromRange:NSMakeRange(0, length) documentAttributes:nil]) : 0;
324 PassRefPtr<SharedBuffer> Editor::dataSelectionForPasteboard(const String& pasteboardType)
326 // FIXME: The interface to this function is awkward. We'd probably be better off with three separate functions.
327 // As of this writing, this is only used in WebKit2 to implement the method -[WKView writeSelectionToPasteboard:types:],
328 // which is only used to support OS X services.
330 // FIXME: Does this function really need to use adjustedSelectionRange()? Because writeSelectionToPasteboard() just uses selectedRange().
334 if (pasteboardType == WebArchivePboardType)
335 return selectionInWebArchiveFormat();
337 if (pasteboardType == String(NSRTFDPboardType))
338 return dataInRTFDFormat(attributedStringForRange(*adjustedSelectionRange()));
340 if (pasteboardType == String(NSRTFPboardType)) {
341 NSAttributedString* attributedString = attributedStringForRange(*adjustedSelectionRange());
342 // FIXME: Why is this attachment character stripping needed here, but not needed in writeSelectionToPasteboard?
343 if ([attributedString containsAttachments])
344 attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
345 return dataInRTFFormat(attributedString);
351 void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
353 NSAttributedString *attributedString = attributedStringForRange(*selectedRange());
355 PasteboardWebContent content;
356 content.canSmartCopyOrDelete = canSmartCopyOrDelete();
357 content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
358 content.dataInRTFDFormat = [attributedString containsAttachments] ? dataInRTFDFormat(attributedString) : 0;
359 content.dataInRTFFormat = dataInRTFFormat(attributedString);
360 content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
361 client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
363 pasteboard.write(content);
366 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
368 auto renderer = imageElement.renderer();
369 if (!renderer || !renderer->isImage())
372 CachedImage* tentativeCachedImage = toRenderImage(renderer)->cachedImage();
373 if (!tentativeCachedImage || tentativeCachedImage->errorOccurred()) {
374 tentativeCachedImage = 0;
378 image = tentativeCachedImage->imageForRenderer(renderer);
382 cachedImage = tentativeCachedImage;
385 void Editor::fillInUserVisibleForm(PasteboardURL& pasteboardURL)
387 pasteboardURL.userVisibleForm = client()->userVisibleString(pasteboardURL.url);
390 String Editor::plainTextFromPasteboard(const PasteboardPlainText& text)
392 String string = text.text;
394 // FIXME: It's not clear this is 100% correct since we know -[NSURL URLWithString:] does not handle
395 // all the same cases we handle well in the URL code for creating an NSURL.
397 string = client()->userVisibleString([NSURL URLWithString:string]);
399 // FIXME: WTF should offer a non-Mac-specific way to convert string to precomposed form so we can do it for all platforms.
400 return [(NSString *)string precomposedStringWithCanonicalMapping];
403 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL& url, const String& title)
405 PasteboardImage pasteboardImage;
407 CachedImage* cachedImage;
408 getImage(imageElement, pasteboardImage.image, cachedImage);
409 if (!pasteboardImage.image)
413 pasteboardImage.url.url = url;
414 pasteboardImage.url.title = title;
415 pasteboardImage.url.userVisibleForm = client()->userVisibleString(pasteboardImage.url.url);
416 pasteboardImage.resourceData = cachedImage->resourceBuffer()->sharedBuffer();
417 pasteboardImage.resourceMIMEType = cachedImage->response().mimeType();
419 pasteboard.write(pasteboardImage);
422 class Editor::WebContentReader FINAL : public PasteboardWebContentReader {
426 const bool allowPlainText;
428 RefPtr<DocumentFragment> fragment;
429 bool madeFragmentFromPlainText;
431 WebContentReader(Frame& frame, Range& context, bool allowPlainText)
434 , allowPlainText(allowPlainText)
435 , madeFragmentFromPlainText(false)
440 virtual bool readWebArchive(PassRefPtr<SharedBuffer>) override;
441 virtual bool readFilenames(const Vector<String>&) override;
442 virtual bool readHTML(const String&) override;
443 virtual bool readRTFD(PassRefPtr<SharedBuffer>) override;
444 virtual bool readRTF(PassRefPtr<SharedBuffer>) override;
445 virtual bool readImage(PassRefPtr<SharedBuffer>, const String& type) override;
446 virtual bool readURL(const URL&, const String& title) override;
447 virtual bool readPlainText(const String&) override;
450 bool Editor::WebContentReader::readWebArchive(PassRefPtr<SharedBuffer> buffer)
452 if (!frame.document())
455 RefPtr<LegacyWebArchive> archive = LegacyWebArchive::create(URL(), buffer.get());
459 RefPtr<ArchiveResource> mainResource = archive->mainResource();
463 const String& type = mainResource->mimeType();
465 if (frame.loader().client().canShowMIMETypeAsHTML(type)) {
466 // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
467 if (DocumentLoader* loader = frame.loader().documentLoader())
468 loader->addAllArchiveResources(archive.get());
470 String markupString = String::fromUTF8(mainResource->data()->data(), mainResource->data()->size());
471 fragment = createFragmentFromMarkup(*frame.document(), markupString, mainResource->url(), DisallowScriptingAndPluginContent);
475 if (MIMETypeRegistry::isSupportedImageMIMEType(type)) {
476 fragment = frame.editor().createFragmentForImageResourceAndAddResource(mainResource.release());
483 bool Editor::WebContentReader::readFilenames(const Vector<String>& paths)
485 size_t size = paths.size();
489 if (!frame.document())
491 Document& document = *frame.document();
493 fragment = document.createDocumentFragment();
495 for (size_t i = 0; i < size; i++) {
496 String text = paths[i];
497 text = frame.editor().client()->userVisibleString([NSURL fileURLWithPath:text]);
499 RefPtr<HTMLElement> paragraph = createDefaultParagraphElement(document);
500 paragraph->appendChild(document.createTextNode(text));
501 fragment->appendChild(paragraph.release());
507 bool Editor::WebContentReader::readHTML(const String& string)
509 String stringOmittingMicrosoftPrefix = string;
511 // This code was added to make HTML paste from Microsoft Word on Mac work, back in 2004.
512 // It's a simple-minded way to ignore the CF_HTML clipboard format, just skipping over the
513 // description part and parsing the entire context plus fragment.
514 if (string.startsWith("Version:")) {
515 size_t location = string.findIgnoringCase("<html");
516 if (location != notFound)
517 stringOmittingMicrosoftPrefix = string.substring(location);
520 if (stringOmittingMicrosoftPrefix.isEmpty())
523 if (!frame.document())
525 Document& document = *frame.document();
527 fragment = createFragmentFromMarkup(document, stringOmittingMicrosoftPrefix, emptyString(), DisallowScriptingAndPluginContent);
531 bool Editor::WebContentReader::readRTFD(PassRefPtr<SharedBuffer> buffer)
533 fragment = frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTFD:buffer->createNSData().get() documentAttributes:nullptr]).get());
537 bool Editor::WebContentReader::readRTF(PassRefPtr<SharedBuffer> buffer)
539 fragment = frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTF:buffer->createNSData().get() documentAttributes:nullptr]).get());
543 bool Editor::WebContentReader::readImage(PassRefPtr<SharedBuffer> buffer, const String& type)
545 ASSERT(type.contains('/'));
546 String typeAsFilenameWithExtension = type;
547 typeAsFilenameWithExtension.replace('/', '.');
548 URL imageURL = URL(URL(), "webkit-fake-url://" + createCanonicalUUIDString() + '/' + typeAsFilenameWithExtension);
550 fragment = frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(buffer, imageURL, type, emptyString(), emptyString()));
554 bool Editor::WebContentReader::readURL(const URL& url, const String& title)
556 if (url.string().isEmpty())
559 RefPtr<Element> anchor = frame.document()->createElement(HTMLNames::aTag, false);
560 anchor->setAttribute(HTMLNames::hrefAttr, url.string());
561 anchor->appendChild(frame.document()->createTextNode([title precomposedStringWithCanonicalMapping]));
563 fragment = frame.document()->createDocumentFragment();
564 fragment->appendChild(anchor.release());
568 bool Editor::WebContentReader::readPlainText(const String& text)
573 fragment = createFragmentFromText(context, [text precomposedStringWithCanonicalMapping]);
577 madeFragmentFromPlainText = true;
581 // FIXME: Should give this function a name that makes it clear it adds resources to the document loader as a side effect.
582 // Or refactor so it does not do that.
583 PassRefPtr<DocumentFragment> Editor::webContentFromPasteboard(Pasteboard& pasteboard, Range& context, bool allowPlainText, bool& chosePlainText)
585 WebContentReader reader(m_frame, context, allowPlainText);
586 pasteboard.read(reader);
587 chosePlainText = reader.madeFragmentFromPlainText;
588 return reader.fragment.release();
591 PassRefPtr<DocumentFragment> Editor::createFragmentForImageResourceAndAddResource(PassRefPtr<ArchiveResource> resource)
596 RefPtr<Element> imageElement = document().createElement(HTMLNames::imgTag, false);
597 imageElement->setAttribute(HTMLNames::srcAttr, resource->url().string());
599 RefPtr<DocumentFragment> fragment = document().createDocumentFragment();
600 fragment->appendChild(imageElement.release());
602 // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
603 if (DocumentLoader* loader = m_frame.loader().documentLoader())
604 loader->addArchiveResource(resource.get());
606 return fragment.release();
609 PassRefPtr<DocumentFragment> Editor::createFragmentAndAddResources(NSAttributedString *string)
611 if (!m_frame.page() || !document().isHTMLDocument())
617 bool wasDeferringCallbacks = m_frame.page()->defersLoading();
618 if (!wasDeferringCallbacks)
619 m_frame.page()->setDefersLoading(true);
621 Vector<RefPtr<ArchiveResource>> resources;
622 RefPtr<DocumentFragment> fragment = client()->documentFragmentFromAttributedString(string, resources);
624 if (DocumentLoader* loader = m_frame.loader().documentLoader()) {
625 for (size_t i = 0, size = resources.size(); i < size; ++i)
626 loader->addArchiveResource(resources[i]);
629 if (!wasDeferringCallbacks)
630 m_frame.page()->setDefersLoading(false);
632 return fragment.release();
635 } // namespace WebCore