blob: 6e2a0d652542663fa7b2f448c95f3096a4cb042c [file] [log] [blame]
/*
* 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 "FontCascade.h"
#import "Frame.h"
#import "FrameLoader.h"
#import "FrameSelection.h"
#import "HTMLAttachmentElement.h"
#import "HTMLConverter.h"
#import "HTMLImageElement.h"
#import "HTMLSpanElement.h"
#import "LegacyWebArchive.h"
#import "Pasteboard.h"
#import "RenderElement.h"
#import "RenderStyle.h"
#import "Text.h"
#import "WebContentReader.h"
#import "WebCoreNSURLExtras.h"
#import "markup.h"
#import <pal/spi/cocoa/NSAttributedStringSPI.h>
#import <pal/spi/cocoa/NSKeyedArchiverSPI.h>
#import <wtf/BlockObjCExceptions.h>
namespace WebCore {
void Editor::getTextDecorationAttributesRespectingTypingStyle(const RenderStyle& style, NSMutableDictionary* result) const
{
RefPtr<EditingStyle> typingStyle = m_frame.selection().typingStyle();
if (typingStyle && typingStyle->style()) {
RefPtr<CSSValue> value = typingStyle->style()->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
if (value && value->isValueList()) {
CSSValueList& valueList = downcast<CSSValueList>(*value);
if (valueList.hasValue(CSSValuePool::singleton().createIdentifierValue(CSSValueLineThrough).ptr()))
[result setObject:@(NSUnderlineStyleSingle) forKey:NSStrikethroughStyleAttributeName];
if (valueList.hasValue(CSSValuePool::singleton().createIdentifierValue(CSSValueUnderline).ptr()))
[result setObject:@(NSUnderlineStyleSingle) forKey:NSUnderlineStyleAttributeName];
}
} else {
int decoration = style.textDecorationsInEffect();
if (decoration & TextDecorationLineThrough)
[result setObject:@(NSUnderlineStyleSingle) forKey:NSStrikethroughStyleAttributeName];
if (decoration & TextDecorationUnderline)
[result setObject:@(NSUnderlineStyleSingle) forKey:NSUnderlineStyleAttributeName];
}
}
RetainPtr<NSDictionary> Editor::fontAttributesForSelectionStart() const
{
Node* nodeToRemove;
auto* style = styleForSelectionStart(&m_frame, nodeToRemove);
if (!style)
return nil;
RetainPtr<NSMutableDictionary> attributes = adoptNS([[NSMutableDictionary alloc] init]);
if (auto ctFont = style->fontCascade().primaryFont().getCTFont())
[attributes setObject:(id)ctFont forKey:NSFontAttributeName];
// FIXME: Why would we not want to retrieve these attributes on iOS?
#if PLATFORM(MAC)
if (style->visitedDependentColor(CSSPropertyBackgroundColor).isVisible())
[attributes setObject:nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName];
if (style->visitedDependentColor(CSSPropertyColor).isValid() && !Color::isBlackColor(style->visitedDependentColor(CSSPropertyColor)))
[attributes setObject:nsColor(style->visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName];
const ShadowData* shadowData = style->textShadow();
if (shadowData) {
RetainPtr<NSShadow> platformShadow = adoptNS([[NSShadow alloc] init]);
[platformShadow setShadowOffset:NSMakeSize(shadowData->x(), shadowData->y())];
[platformShadow setShadowBlurRadius:shadowData->radius()];
[platformShadow setShadowColor:nsColor(shadowData->color())];
[attributes setObject:platformShadow.get() forKey:NSShadowAttributeName];
}
int superscriptInt = 0;
switch (style->verticalAlign()) {
case BASELINE:
case BOTTOM:
case BASELINE_MIDDLE:
case LENGTH:
case MIDDLE:
case TEXT_BOTTOM:
case TEXT_TOP:
case TOP:
break;
case SUB:
superscriptInt = -1;
break;
case SUPER:
superscriptInt = 1;
break;
}
if (superscriptInt)
[attributes setObject:@(superscriptInt) forKey:NSSuperscriptAttributeName];
#endif
getTextDecorationAttributesRespectingTypingStyle(*style, attributes.get());
if (nodeToRemove)
nodeToRemove->remove();
return attributes;
}
static RefPtr<SharedBuffer> archivedDataForAttributedString(NSAttributedString *attributedString)
{
if (!attributedString.length)
return nullptr;
return SharedBuffer::create(securelyArchivedDataWithRootObject(attributedString));
}
String Editor::selectionInHTMLFormat()
{
if (auto range = selectedRange())
return createMarkup(*range, nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
return { };
}
#if ENABLE(ATTACHMENT_ELEMENT)
void Editor::getPasteboardTypesAndDataForAttachment(HTMLAttachmentElement& attachment, Vector<String>& outTypes, Vector<RefPtr<SharedBuffer>>& outData)
{
auto attachmentRange = Range::create(attachment.document(), { &attachment, Position::PositionIsBeforeAnchor }, { &attachment, Position::PositionIsAfterAnchor });
client()->getClientPasteboardDataForRange(attachmentRange.ptr(), outTypes, outData);
// FIXME: We should additionally write the attachment as a web archive here, such that drag and drop within the
// same page doesn't destroy and recreate attachments unnecessarily. This is also needed to preserve the attachment
// display mode when dragging and dropping or cutting and pasting. For the time being, this is disabled because
// inserting attachment elements from web archive data sometimes causes attachment data to be lost; this requires
// further investigation.
}
#endif
void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
{
NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
PasteboardWebContent content;
content.contentOrigin = m_frame.document()->originIdentifierForPasteboard();
content.canSmartCopyOrDelete = canSmartCopyOrDelete();
content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
content.dataInRTFDFormat = attributedString.containsAttachments ? dataInRTFDFormat(attributedString) : nullptr;
content.dataInRTFFormat = dataInRTFFormat(attributedString);
content.dataInAttributedStringFormat = archivedDataForAttributedString(attributedString);
content.dataInHTMLFormat = selectionInHTMLFormat();
content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
pasteboard.write(content);
}
void Editor::writeSelection(PasteboardWriterData& pasteboardWriterData)
{
NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
PasteboardWriterData::WebContent webContent;
webContent.contentOrigin = m_frame.document()->originIdentifierForPasteboard();
webContent.canSmartCopyOrDelete = canSmartCopyOrDelete();
webContent.dataInWebArchiveFormat = selectionInWebArchiveFormat();
webContent.dataInRTFDFormat = attributedString.containsAttachments ? dataInRTFDFormat(attributedString) : nullptr;
webContent.dataInRTFFormat = dataInRTFFormat(attributedString);
webContent.dataInAttributedStringFormat = archivedDataForAttributedString(attributedString);
webContent.dataInHTMLFormat = selectionInHTMLFormat();
webContent.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
client()->getClientPasteboardDataForRange(selectedRange().get(), webContent.clientTypes, webContent.clientData);
pasteboardWriterData.setWebContent(WTFMove(webContent));
}
RefPtr<SharedBuffer> Editor::selectionInWebArchiveFormat()
{
RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(&m_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();
String text = selectedText();
text.replace(noBreakSpace, ' ');
return text;
}
String Editor::stringSelectionForPasteboardWithImageAltText()
{
if (!canCopy())
return emptyString();
String text = selectedTextForDataTransfer();
text.replace(noBreakSpace, ' ');
return text;
}
void Editor::replaceSelectionWithAttributedString(NSAttributedString *attributedString, MailBlockquoteHandling mailBlockquoteHandling)
{
if (m_frame.selection().isNone())
return;
if (m_frame.selection().selection().isContentRichlyEditable()) {
if (auto fragment = createFragmentAndAddResources(m_frame, attributedString)) {
if (shouldInsertFragment(*fragment, selectedRange().get(), EditorInsertAction::Pasted))
pasteAsFragment(fragment.releaseNonNull(), false, false, mailBlockquoteHandling);
}
} else {
String text = attributedString.string;
if (shouldInsertText(text, selectedRange().get(), EditorInsertAction::Pasted))
pasteAsPlainText(text, false);
}
}
String Editor::userVisibleString(const URL& url)
{
return WebCore::userVisibleString(url);
}
RefPtr<SharedBuffer> 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<SharedBuffer> 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;
}
}