2 * Copyright (C) 2006, 2007, 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 INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
29 #include "BlockExceptions.h"
30 #include "CachedImage.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSPrimitiveValueMappings.h"
33 #include "DOMRangeInternal.h"
34 #include "DataTransfer.h"
35 #include "DocumentFragment.h"
36 #include "DocumentLoader.h"
37 #include "EditorClient.h"
38 #include "FontCascade.h"
40 #include "FrameLoaderClient.h"
41 #include "HTMLConverter.h"
42 #include "HTMLInputElement.h"
43 #include "HTMLNames.h"
44 #include "HTMLParserIdioms.h"
45 #include "HTMLTextAreaElement.h"
46 #include "LegacyWebArchive.h"
47 #include "NSAttributedStringSPI.h"
48 #include "NodeTraversal.h"
50 #include "Pasteboard.h"
51 #include "RenderBlock.h"
52 #include "RenderImage.h"
53 #include "SharedBuffer.h"
54 #include "SoftLinking.h"
55 #include "StyleProperties.h"
57 #include "TypingCommand.h"
58 #include "WAKAppKitStubs.h"
59 #include "htmlediting.h"
62 SOFT_LINK_FRAMEWORK(AppSupport)
63 SOFT_LINK(AppSupport, CPSharedResourcesDirectory, CFStringRef, (void), ())
65 SOFT_LINK_FRAMEWORK(MobileCoreServices)
67 SOFT_LINK(MobileCoreServices, UTTypeConformsTo, Boolean, (CFStringRef inUTI, CFStringRef inConformsToUTI), (inUTI, inConformsToUTI))
68 SOFT_LINK(MobileCoreServices, UTTypeCreatePreferredIdentifierForTag, CFStringRef, (CFStringRef inTagClass, CFStringRef inTag, CFStringRef inConformingToUTI), (inTagClass, inTag, inConformingToUTI))
69 SOFT_LINK(MobileCoreServices, UTTypeCopyPreferredTagWithClass, CFStringRef, (CFStringRef inUTI, CFStringRef inTagClass), (inUTI, inTagClass))
71 SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypePNG, CFStringRef)
72 SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeJPEG, CFStringRef)
73 SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassFilenameExtension, CFStringRef)
74 SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassMIMEType, CFStringRef)
76 #define kUTTypePNG getkUTTypePNG()
77 #define kUTTypeJPEG getkUTTypeJPEG()
78 #define kUTTagClassFilenameExtension getkUTTagClassFilenameExtension()
79 #define kUTTagClassMIMEType getkUTTagClassMIMEType()
81 @interface NSAttributedString (NSAttributedStringKitAdditions)
82 - (id)initWithRTF:(NSData *)data documentAttributes:(NSDictionary **)dict;
83 - (id)initWithRTFD:(NSData *)data documentAttributes:(NSDictionary **)dict;
84 - (NSData *)RTFFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
85 - (NSData *)RTFDFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
86 - (BOOL)containsAttachments;
91 using namespace HTMLNames;
93 void Editor::showFontPanel()
97 void Editor::showStylesPanel()
101 void Editor::showColorPanel()
105 void Editor::setTextAlignmentForChangedBaseWritingDirection(WritingDirection direction)
107 // Note that the passed-in argument is the direction that has been changed to by
108 // some code or user interaction outside the scope of this function. The former
109 // direction is not known, nor is it required for the kind of text alignment
110 // changes done by this function.
113 // When text has no explicit alignment, set to alignment to match the writing direction.
114 // If the text has left or right alignment, flip left->right and right->left.
115 // Otherwise, do nothing.
117 RefPtr<EditingStyle> selectionStyle = EditingStyle::styleAtSelectionStart(m_frame.selection().selection());
118 if (!selectionStyle || !selectionStyle->style())
121 RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(selectionStyle->style()->getPropertyCSSValue(CSSPropertyTextAlign));
125 const char *newValue = nullptr;
126 ETextAlign textAlign = *value;
132 case NaturalWritingDirection:
135 case LeftToRightWritingDirection:
138 case RightToLeftWritingDirection:
162 Element* focusedElement = m_frame.document()->focusedElement();
163 if (focusedElement && (is<HTMLTextAreaElement>(*focusedElement) || (is<HTMLInputElement>(*focusedElement)
164 && (downcast<HTMLInputElement>(*focusedElement).isTextField()
165 || downcast<HTMLInputElement>(*focusedElement).isSearchField())))) {
166 if (direction == NaturalWritingDirection)
168 downcast<HTMLElement>(*focusedElement).setAttribute(alignAttr, newValue);
169 m_frame.document()->updateStyleIfNeeded();
173 RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
174 style->setProperty(CSSPropertyTextAlign, newValue);
175 applyParagraphStyle(style.get());
178 bool Editor::insertParagraphSeparatorInQuotedContent()
180 // FIXME: Why is this missing calls to canEdit, canEditRichly, etc...
181 TypingCommand::insertParagraphSeparatorInQuotedContent(*m_frame.document());
182 revealSelectionAfterEditingOperation();
186 const Font* Editor::fontForSelection(bool& hasMultipleFonts) const
188 hasMultipleFonts = false;
190 if (!m_frame.selection().isRange()) {
192 RenderStyle* style = styleForSelectionStart(&m_frame, nodeToRemove); // sets nodeToRemove
194 const Font* result = nullptr;
196 result = &style->fontCascade().primaryFont();
198 nodeToRemove->remove(ASSERT_NO_EXCEPTION);
204 const Font* font = nullptr;
205 RefPtr<Range> range = m_frame.selection().toNormalizedRange();
206 if (Node* startNode = adjustedSelectionStartForStyleComputation(m_frame.selection().selection()).deprecatedNode()) {
207 Node* pastEnd = range->pastLastNode();
208 // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
209 // unreproducible case where this didn't happen, so check for null also.
210 for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(*node)) {
211 auto renderer = node->renderer();
214 // FIXME: Are there any node types that have renderers, but that we should be skipping?
215 const Font& primaryFont = renderer->style().fontCascade().primaryFont();
218 else if (font != &primaryFont) {
219 hasMultipleFonts = true;
228 NSDictionary* Editor::fontAttributesForSelectionStart() const
231 RenderStyle* style = styleForSelectionStart(&m_frame, nodeToRemove);
235 NSMutableDictionary* result = [NSMutableDictionary dictionary];
237 CTFontRef font = style->fontCascade().primaryFont().getCTFont();
239 [result setObject:(id)font forKey:NSFontAttributeName];
241 getTextDecorationAttributesRespectingTypingStyle(*style, result);
244 nodeToRemove->remove(ASSERT_NO_EXCEPTION);
249 void Editor::removeUnchangeableStyles()
251 // This function removes styles that the user cannot modify by applying their default values.
253 RefPtr<EditingStyle> editingStyle = EditingStyle::create(m_frame.document()->bodyOrFrameset());
254 RefPtr<MutableStyleProperties> defaultStyle = editingStyle.get()->style()->mutableCopy();
256 // Text widgets implement background color via the UIView property. Their body element will not have one.
257 defaultStyle->setProperty(CSSPropertyBackgroundColor, "rgba(255, 255, 255, 0.0)");
259 // Remove properties that the user can modify, like font-weight.
260 // Also remove font-family, per HI spec.
261 // FIXME: it'd be nice if knowledge about which styles were unchangeable was not hard-coded here.
262 defaultStyle->removeProperty(CSSPropertyFontWeight);
263 defaultStyle->removeProperty(CSSPropertyFontStyle);
264 defaultStyle->removeProperty(CSSPropertyFontVariantCaps);
265 // FIXME: we should handle also pasted quoted text, strikethrough, etc. <rdar://problem/9255115>
266 defaultStyle->removeProperty(CSSPropertyTextDecoration);
267 defaultStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect); // implements underline
269 // FIXME add EditActionMatchStlye <rdar://problem/9156507> Undo rich text's paste & match style should say "Undo Match Style"
270 applyStyleToSelection(defaultStyle.get(), EditActionChangeAttributes);
273 static PassRefPtr<SharedBuffer> dataInRTFDFormat(NSAttributedString *string)
275 NSUInteger length = string.length;
279 BEGIN_BLOCK_OBJC_EXCEPTIONS;
280 return SharedBuffer::wrapNSData([string RTFDFromRange:NSMakeRange(0, length) documentAttributes:nil]);
281 END_BLOCK_OBJC_EXCEPTIONS;
286 static PassRefPtr<SharedBuffer> dataInRTFFormat(NSAttributedString *string)
288 NSUInteger length = string.length;
292 BEGIN_BLOCK_OBJC_EXCEPTIONS;
293 return SharedBuffer::wrapNSData([string RTFFromRange:NSMakeRange(0, length) documentAttributes:nil]);
294 END_BLOCK_OBJC_EXCEPTIONS;
299 String Editor::stringSelectionForPasteboardWithImageAltText()
301 String text = selectedTextForDataTransfer();
302 text.replace(noBreakSpace, ' ');
306 PassRefPtr<SharedBuffer> Editor::selectionInWebArchiveFormat()
308 RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(&m_frame);
309 return archive ? SharedBuffer::wrapCFData(archive->rawDataRepresentation().get()) : nullptr;
312 void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
314 NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
316 PasteboardWebContent content;
317 content.canSmartCopyOrDelete = canSmartCopyOrDelete();
318 content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
319 content.dataInRTFDFormat = [attributedString containsAttachments] ? dataInRTFDFormat(attributedString) : 0;
320 content.dataInRTFFormat = dataInRTFFormat(attributedString);
321 content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
322 client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
324 pasteboard.write(content);
327 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
329 auto* renderer = imageElement.renderer();
330 if (!is<RenderImage>(renderer))
333 CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
334 if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
337 image = tentativeCachedImage->imageForRenderer(renderer);
341 cachedImage = tentativeCachedImage;
344 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL&, const String& title)
346 PasteboardImage pasteboardImage;
348 CachedImage* cachedImage;
349 getImage(imageElement, pasteboardImage.image, cachedImage);
350 if (!pasteboardImage.image)
354 pasteboardImage.url.url = imageElement.document().completeURL(stripLeadingAndTrailingHTMLSpaces(imageElement.imageSourceURL()));
355 pasteboardImage.url.title = title;
356 pasteboardImage.resourceMIMEType = pasteboard.resourceMIMEType(cachedImage->response().mimeType());
357 pasteboardImage.resourceData = cachedImage->resourceBuffer();
359 pasteboard.write(pasteboardImage);
362 class Editor::WebContentReader final : public PasteboardWebContentReader {
364 WebContentReader(Frame& frame, Range& context, bool allowPlainText)
367 , allowPlainText(allowPlainText)
368 , madeFragmentFromPlainText(false)
372 void addFragment(PassRefPtr<DocumentFragment>);
376 const bool allowPlainText;
378 RefPtr<DocumentFragment> fragment;
379 bool madeFragmentFromPlainText;
382 virtual bool readWebArchive(PassRefPtr<SharedBuffer>) override;
383 virtual bool readFilenames(const Vector<String>&) override;
384 virtual bool readHTML(const String&) override;
385 virtual bool readRTFD(PassRefPtr<SharedBuffer>) override;
386 virtual bool readRTF(PassRefPtr<SharedBuffer>) override;
387 virtual bool readImage(PassRefPtr<SharedBuffer>, const String& type) override;
388 virtual bool readURL(const URL&, const String& title) override;
389 virtual bool readPlainText(const String&) override;
392 void Editor::WebContentReader::addFragment(PassRefPtr<DocumentFragment> newFragment)
395 if (newFragment && newFragment->firstChild()) {
397 fragment->appendChild(*newFragment->firstChild(), ec);
400 fragment = newFragment;
403 bool Editor::WebContentReader::readWebArchive(PassRefPtr<SharedBuffer> buffer)
405 if (!frame.document())
408 RefPtr<LegacyWebArchive> archive = LegacyWebArchive::create(URL(), buffer.get());
412 RefPtr<ArchiveResource> mainResource = archive->mainResource();
416 const String& type = mainResource->mimeType();
418 if (frame.loader().client().canShowMIMETypeAsHTML(type)) {
419 // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
420 if (DocumentLoader* loader = frame.loader().documentLoader())
421 loader->addAllArchiveResources(archive.get());
423 String markupString = String::fromUTF8(mainResource->data()->data(), mainResource->data()->size());
424 addFragment(createFragmentFromMarkup(*frame.document(), markupString, mainResource->url(), DisallowScriptingAndPluginContent));
431 bool Editor::WebContentReader::readFilenames(const Vector<String>&)
436 bool Editor::WebContentReader::readHTML(const String& string)
438 if (!frame.document())
441 addFragment(createFragmentFromMarkup(*frame.document(), string, emptyString(), DisallowScriptingAndPluginContent));
445 bool Editor::WebContentReader::readRTFD(PassRefPtr<SharedBuffer> buffer)
447 addFragment(frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTFD:buffer->createNSData().get() documentAttributes:nullptr]).get()));
451 bool Editor::WebContentReader::readRTF(PassRefPtr<SharedBuffer> buffer)
453 addFragment(frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTF:buffer->createNSData().get() documentAttributes:nullptr]).get()));
457 bool Editor::WebContentReader::readImage(PassRefPtr<SharedBuffer> buffer, const String& type)
459 RetainPtr<CFStringRef> stringType = type.createCFString();
460 RetainPtr<NSString> filenameExtension = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass(stringType.get(), kUTTagClassFilenameExtension));
461 NSString *relativeURLPart = [@"image" stringByAppendingString:filenameExtension.get()];
462 RetainPtr<NSString> mimeType = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass(stringType.get(), kUTTagClassMIMEType));
464 addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(buffer, URL::fakeURLWithRelativePart(relativeURLPart), mimeType.get(), emptyString(), emptyString())));
468 bool Editor::WebContentReader::readURL(const URL& url, const String&)
473 if (!frame.editor().client()->hasRichlyEditableSelection()) {
474 if (readPlainText([(NSURL *)url absoluteString]))
478 if ([(NSURL *)url isFileURL]) {
479 NSString *localPath = [(NSURL *)url relativePath];
480 // Only allow url attachments from ~/Media for now.
481 if (![localPath hasPrefix:[(NSString *)CPSharedResourcesDirectory() stringByAppendingString:@"/Media/DCIM/"]])
484 RetainPtr<NSString> fileType = adoptNS((NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[localPath pathExtension], NULL));
485 NSData *data = [NSData dataWithContentsOfFile:localPath];
486 if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypePNG)) {
487 addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), URL::fakeURLWithRelativePart("image.png"), @"image/png", emptyString(), emptyString())));
489 } else if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypeJPEG)) {
490 addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), URL::fakeURLWithRelativePart("image.jpg"), @"image/jpg", emptyString(), emptyString())));
494 Ref<Element> anchor = frame.document()->createElement(HTMLNames::aTag, false);
495 anchor->setAttribute(HTMLNames::hrefAttr, url.string());
496 anchor->appendChild(frame.document()->createTextNode([[(NSURL *)url absoluteString] precomposedStringWithCanonicalMapping]));
498 RefPtr<DocumentFragment> newFragment = frame.document()->createDocumentFragment();
499 newFragment->appendChild(WTFMove(anchor));
500 addFragment(newFragment);
506 bool Editor::WebContentReader::readPlainText(const String& text)
511 addFragment(createFragmentFromText(context, [text precomposedStringWithCanonicalMapping]));
515 madeFragmentFromPlainText = true;
519 // FIXME: Should give this function a name that makes it clear it adds resources to the document loader as a side effect.
520 // Or refactor so it does not do that.
521 PassRefPtr<DocumentFragment> Editor::webContentFromPasteboard(Pasteboard& pasteboard, Range& context, bool allowPlainText, bool& chosePlainText)
523 WebContentReader reader(m_frame, context, allowPlainText);
524 pasteboard.read(reader);
525 chosePlainText = reader.madeFragmentFromPlainText;
526 return reader.fragment.release();
529 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText, MailBlockquoteHandling mailBlockquoteHandling)
531 RefPtr<Range> range = selectedRange();
532 WebContentReader reader(m_frame, *range, allowPlainText);
533 int numberOfPasteboardItems = client()->getPasteboardItemsCount();
534 for (int i = 0; i < numberOfPasteboardItems; ++i) {
535 RefPtr<DocumentFragment> fragment = client()->documentFragmentFromDelegate(i);
539 reader.addFragment(fragment);
542 RefPtr<DocumentFragment> fragment = reader.fragment;
544 bool chosePlainTextIgnored;
545 fragment = webContentFromPasteboard(*pasteboard, *range, allowPlainText, chosePlainTextIgnored);
548 if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
549 pasteAsFragment(fragment, canSmartReplaceWithPasteboard(*pasteboard), false, mailBlockquoteHandling);
552 PassRefPtr<DocumentFragment> Editor::createFragmentAndAddResources(NSAttributedString *string)
554 if (!m_frame.page() || !m_frame.document() || !m_frame.document()->isHTMLDocument())
560 bool wasDeferringCallbacks = m_frame.page()->defersLoading();
561 if (!wasDeferringCallbacks)
562 m_frame.page()->setDefersLoading(true);
564 Vector<RefPtr<ArchiveResource>> resources;
565 RefPtr<DocumentFragment> fragment = client()->documentFragmentFromAttributedString(string, resources);
567 if (DocumentLoader* loader = m_frame.loader().documentLoader()) {
568 for (auto& resource : resources)
569 loader->addArchiveResource(resource);
572 if (!wasDeferringCallbacks)
573 m_frame.page()->setDefersLoading(false);
575 return fragment.release();
578 PassRefPtr<DocumentFragment> Editor::createFragmentForImageResourceAndAddResource(PassRefPtr<ArchiveResource> resource)
583 Ref<Element> imageElement = m_frame.document()->createElement(HTMLNames::imgTag, false);
584 // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
585 if (DocumentLoader* loader = m_frame.loader().documentLoader())
586 loader->addArchiveResource(resource.get());
588 NSURL *URL = resource->url();
589 imageElement->setAttribute(HTMLNames::srcAttr, [URL isFileURL] ? [URL absoluteString] : resource->url());
591 RefPtr<DocumentFragment> fragment = m_frame.document()->createDocumentFragment();
592 fragment->appendChild(WTFMove(imageElement));
594 return fragment.release();
597 void Editor::replaceSelectionWithAttributedString(NSAttributedString *attributedString, MailBlockquoteHandling mailBlockquoteHandling)
599 if (m_frame.selection().isNone())
602 if (m_frame.selection().selection().isContentRichlyEditable()) {
603 RefPtr<DocumentFragment> fragment = createFragmentAndAddResources(attributedString);
604 if (fragment && shouldInsertFragment(fragment, selectedRange(), EditorInsertActionPasted))
605 pasteAsFragment(fragment, false, false, mailBlockquoteHandling);
607 String text = [attributedString string];
608 if (shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted))
609 pasteAsPlainText(text, false);
613 } // namespace WebCore