/* * Copyright (C) 2006-2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "Editor.h" #import "ArchiveResource.h" #import "CSSValueList.h" #import "CSSValuePool.h" #import "CachedResourceLoader.h" #import "ColorMac.h" #import "DocumentFragment.h" #import "DocumentLoader.h" #import "Editing.h" #import "EditingStyle.h" #import "EditorClient.h" #import "ElementInlines.h" #import "FontAttributes.h" #import "FontCascade.h" #import "Frame.h" #import "FrameLoader.h" #import "FrameSelection.h" #import "HTMLAttachmentElement.h" #import "HTMLConverter.h" #import "HTMLImageElement.h" #import "HTMLSpanElement.h" #import "ImageOverlay.h" #import "LegacyNSPasteboardTypes.h" #import "LegacyWebArchive.h" #import "Page.h" #import "PagePasteboardContext.h" #import "Pasteboard.h" #import "PasteboardStrategy.h" #import "PlatformStrategies.h" #import "RenderElement.h" #import "RenderStyle.h" #import "Settings.h" #import "SystemSoundManager.h" #import "Text.h" #import "UTIUtilities.h" #import "WebContentReader.h" #import "markup.h" #import #import #import namespace WebCore { static RefPtr archivedDataForAttributedString(NSAttributedString *attributedString) { if (!attributedString.length) return nullptr; return SharedBuffer::create([NSKeyedArchiver archivedDataWithRootObject:attributedString requiringSecureCoding:YES error:nullptr]); } String Editor::selectionInHTMLFormat() { if (ImageOverlay::isInsideOverlay(m_document.selection().selection())) return { }; return serializePreservingVisualAppearance(m_document.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy, SerializeComposedTree::Yes); } #if ENABLE(ATTACHMENT_ELEMENT) void Editor::getPasteboardTypesAndDataForAttachment(Element& element, Vector& outTypes, Vector>& outData) { auto elementRange = makeRangeSelectingNode(element); client()->getClientPasteboardData(elementRange, outTypes, outData); outTypes.append(PasteboardCustomData::cocoaType()); outData.append(PasteboardCustomData { element.document().originIdentifierForPasteboard(), { } }.createSharedBuffer()); if (elementRange) { if (auto archive = LegacyWebArchive::create(*elementRange)) { if (auto data = archive->rawDataRepresentation()) { outTypes.append(WebArchivePboardType); outData.append(SharedBuffer::create(data.get())); } } } } #endif static RetainPtr selectionInImageOverlayAsAttributedString(const VisibleSelection& selection) { #if ENABLE(IMAGE_ANALYSIS_ENHANCEMENTS) auto* page = selection.document()->page(); if (!page) return nil; RefPtr hostElement = dynamicDowncast(selection.start().containerNode()->shadowHost()); if (!hostElement) { ASSERT_NOT_REACHED(); return nil; } auto cachedResult = page->cachedTextRecognitionResult(*hostElement); if (!cachedResult) return nil; auto characterRange = valueOrDefault(ImageOverlay::characterRange(selection)); if (!characterRange.length) return nil; auto string = stringForRange(*cachedResult, characterRange); __block bool hasAnyAttributes = false; [string enumerateAttributesInRange:NSMakeRange(0, [string length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange, BOOL *stop) { if (attributes.count) { hasAnyAttributes = true; *stop = YES; } }]; if (!hasAnyAttributes) return nil; return string; #else UNUSED_PARAM(selection); return nil; #endif } static RetainPtr selectionAsAttributedString(const Document& document) { auto selection = document.selection().selection(); if (ImageOverlay::isInsideOverlay(selection)) return selectionInImageOverlayAsAttributedString(selection); auto range = selection.firstRange(); return range ? attributedString(*range).string : adoptNS([[NSAttributedString alloc] init]); } void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard) { auto string = selectionAsAttributedString(m_document); PasteboardWebContent content; content.contentOrigin = m_document.originIdentifierForPasteboard(); content.canSmartCopyOrDelete = canSmartCopyOrDelete(); if (!pasteboard.isStatic()) { content.dataInWebArchiveFormat = selectionInWebArchiveFormat(); content.dataInRTFDFormat = [string containsAttachments] ? dataInRTFDFormat(string.get()) : nullptr; content.dataInRTFFormat = dataInRTFFormat(string.get()); content.dataInAttributedStringFormat = archivedDataForAttributedString(string.get()); client()->getClientPasteboardData(selectedRange(), content.clientTypes, content.clientData); } content.dataInHTMLFormat = selectionInHTMLFormat(); content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText(); pasteboard.write(content); } void Editor::writeSelection(PasteboardWriterData& pasteboardWriterData) { auto string = selectionAsAttributedString(m_document); PasteboardWriterData::WebContent webContent; webContent.contentOrigin = m_document.originIdentifierForPasteboard(); webContent.canSmartCopyOrDelete = canSmartCopyOrDelete(); webContent.dataInWebArchiveFormat = selectionInWebArchiveFormat(); webContent.dataInRTFDFormat = [string containsAttachments] ? dataInRTFDFormat(string.get()) : nullptr; webContent.dataInRTFFormat = dataInRTFFormat(string.get()); webContent.dataInAttributedStringFormat = archivedDataForAttributedString(string.get()); webContent.dataInHTMLFormat = selectionInHTMLFormat(); webContent.dataInStringFormat = stringSelectionForPasteboardWithImageAltText(); client()->getClientPasteboardData(selectedRange(), webContent.clientTypes, webContent.clientData); pasteboardWriterData.setWebContent(WTFMove(webContent)); } RefPtr Editor::selectionInWebArchiveFormat() { if (ImageOverlay::isInsideOverlay(m_document.selection().selection())) return nullptr; auto archive = LegacyWebArchive::createFromSelection(m_document.frame()); if (!archive) return nullptr; return SharedBuffer::create(archive->rawDataRepresentation().get()); } // FIXME: Makes no sense that selectedTextForDataTransfer always includes alt text, but stringSelectionForPasteboard does not. // This was left in a bad state when selectedTextForDataTransfer was added. Need to look over clients and fix this. String Editor::stringSelectionForPasteboard() { if (!canCopy()) return emptyString(); return makeStringByReplacingAll(selectedText(), noBreakSpace, ' '); } String Editor::stringSelectionForPasteboardWithImageAltText() { if (!canCopy()) return emptyString(); return makeStringByReplacingAll(selectedTextForDataTransfer(), noBreakSpace, ' '); } void Editor::replaceSelectionWithAttributedString(NSAttributedString *attributedString, MailBlockquoteHandling mailBlockquoteHandling) { if (m_document.selection().isNone()) return; if (m_document.selection().selection().isContentRichlyEditable()) { if (auto fragment = createFragmentAndAddResources(*m_document.frame(), attributedString)) { if (shouldInsertFragment(*fragment, selectedRange(), EditorInsertAction::Pasted)) pasteAsFragment(fragment.releaseNonNull(), false, false, mailBlockquoteHandling); } } else { String text = attributedString.string; if (shouldInsertText(text, selectedRange(), EditorInsertAction::Pasted)) pasteAsPlainText(text, false); } } String Editor::userVisibleString(const URL& url) { return WTF::userVisibleString(url); } RefPtr Editor::dataInRTFDFormat(NSAttributedString *string) { NSUInteger length = string.length; if (!length) return nullptr; BEGIN_BLOCK_OBJC_EXCEPTIONS return SharedBuffer::create([string RTFDFromRange:NSMakeRange(0, length) documentAttributes:@{ }]); END_BLOCK_OBJC_EXCEPTIONS return nullptr; } RefPtr Editor::dataInRTFFormat(NSAttributedString *string) { NSUInteger length = string.length; if (!length) return nullptr; BEGIN_BLOCK_OBJC_EXCEPTIONS return SharedBuffer::create([string RTFFromRange:NSMakeRange(0, length) documentAttributes:@{ }]); END_BLOCK_OBJC_EXCEPTIONS return nullptr; } // FIXME: Should give this function a name that makes it clear it adds resources to the document loader as a side effect. // Or refactor so it does not do that. RefPtr Editor::webContentFromPasteboard(Pasteboard& pasteboard, const SimpleRange& context, bool allowPlainText, bool& chosePlainText) { WebContentReader reader(*m_document.frame(), context, allowPlainText); pasteboard.read(reader); chosePlainText = reader.madeFragmentFromPlainText; return WTFMove(reader.fragment); } void Editor::takeFindStringFromSelection() { if (!canCopyExcludingStandaloneImages()) { SystemSoundManager::singleton().systemBeep(); return; } auto stringFromSelection = m_document.frame()->displayStringModifiedByEncoding(selectedTextForDataTransfer()); #if PLATFORM(MAC) Vector types; types.append(String(legacyStringPasteboardType())); auto context = PagePasteboardContext::create(m_document.pageID()); ALLOW_DEPRECATED_DECLARATIONS_BEGIN platformStrategies()->pasteboardStrategy()->setTypes(types, NSFindPboard, context.get()); platformStrategies()->pasteboardStrategy()->setStringForType(WTFMove(stringFromSelection), legacyStringPasteboardType(), NSFindPboard, context.get()); ALLOW_DEPRECATED_DECLARATIONS_END #else if (auto* client = this->client()) { // Since the find pasteboard doesn't exist on iOS, WebKit maintains its own notion of the latest find string, // which SPI clients may respect when presenting find-in-page UI. client->updateStringForFind(stringFromSelection); } #endif } String Editor::platformContentTypeForBlobType(const String& type) const { auto utiType = UTIFromMIMEType(type); if (!utiType.isEmpty()) return utiType; return type; } void Editor::readSelectionFromPasteboard(const String& pasteboardName) { Pasteboard pasteboard(PagePasteboardContext::create(m_document.pageID()), pasteboardName); if (m_document.selection().selection().isContentRichlyEditable()) pasteWithPasteboard(&pasteboard, { PasteOption::AllowPlainText }); else pasteAsPlainTextWithPasteboard(pasteboard); } static void maybeCopyNodeAttributesToFragment(const Node& node, DocumentFragment& fragment) { // This is only supported for single-Node fragments. RefPtr firstChild = fragment.firstChild(); if (!firstChild || firstChild != fragment.lastChild()) return; // And only supported for HTML elements. if (!node.isHTMLElement() || !firstChild->isHTMLElement()) return; // And only if the source Element and destination Element have the same HTML tag name. Ref oldElement = downcast(node); Ref newElement = downcast(*firstChild); if (oldElement->localName() != newElement->localName()) return; for (auto& attribute : oldElement->attributesIterator()) { if (newElement->hasAttribute(attribute.name())) continue; newElement->setAttribute(attribute.name(), attribute.value()); } } void Editor::replaceNodeFromPasteboard(Node& node, const String& pasteboardName, EditAction action) { if (node.document() != m_document) return; auto range = makeRangeSelectingNode(node); if (!range) return; Ref protectedDocument = m_document; m_document.selection().setSelection({ *range }, FrameSelection::DoNotSetFocus); Pasteboard pasteboard(PagePasteboardContext::create(m_document.pageID()), pasteboardName); if (!m_document.selection().selection().isContentRichlyEditable()) { pasteAsPlainTextWithPasteboard(pasteboard); return; } #if PLATFORM(MAC) ALLOW_DEPRECATED_DECLARATIONS_BEGIN // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name? // FIXME: We can also remove `setInsertionPasteboard` altogether once Mail compose on macOS no longer uses WebKitLegacy, // since it's only implemented for WebKitLegacy on macOS, and the only known client is Mail compose. client()->setInsertionPasteboard(NSGeneralPboard); ALLOW_DEPRECATED_DECLARATIONS_END #endif bool chosePlainText; if (auto fragment = webContentFromPasteboard(pasteboard, *range, true, chosePlainText)) { maybeCopyNodeAttributesToFragment(node, *fragment); if (shouldInsertFragment(*fragment, *range, EditorInsertAction::Pasted)) pasteAsFragment(fragment.releaseNonNull(), false, false, MailBlockquoteHandling::IgnoreBlockquote, action); } #if PLATFORM(MAC) client()->setInsertionPasteboard({ }); #endif } }