blob: 70f0b711a9d35b962058afb11ba68ffafb55fb17 [file] [log] [blame]
/*
* Copyright (C) 2006-2019 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. AND ITS CONTRIBUTORS ``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 ITS 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"
#if PLATFORM(IOS_FAMILY)
#import "CSSComputedStyleDeclaration.h"
#import "CSSPrimitiveValueMappings.h"
#import "CachedImage.h"
#import "DataTransfer.h"
#import "DictationCommandIOS.h"
#import "DocumentFragment.h"
#import "DocumentMarkerController.h"
#import "Editing.h"
#import "EditorClient.h"
#import "Frame.h"
#import "HTMLInputElement.h"
#import "HTMLNames.h"
#import "HTMLParserIdioms.h"
#import "HTMLTextAreaElement.h"
#import "Pasteboard.h"
#import "RenderBlock.h"
#import "RenderImage.h"
#import "SharedBuffer.h"
#import "StyleProperties.h"
#import "Text.h"
#import "TypingCommand.h"
#import "WAKAppKitStubs.h"
#import "WebContentReader.h"
#import "markup.h"
#import <wtf/text/StringBuilder.h>
namespace WebCore {
using namespace HTMLNames;
void Editor::showFontPanel()
{
}
void Editor::showStylesPanel()
{
}
void Editor::showColorPanel()
{
}
void Editor::setTextAlignmentForChangedBaseWritingDirection(WritingDirection direction)
{
// Note that the passed-in argument is the direction that has been changed to by
// some code or user interaction outside the scope of this function. The former
// direction is not known, nor is it required for the kind of text alignment
// changes done by this function.
//
// Rules:
// When text has no explicit alignment, set to alignment to match the writing direction.
// If the text has left or right alignment, flip left->right and right->left.
// Otherwise, do nothing.
auto selectionStyle = EditingStyle::styleAtSelectionStart(m_document.selection().selection());
if (!selectionStyle || !selectionStyle->style())
return;
RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(selectionStyle->style()->getPropertyCSSValue(CSSPropertyTextAlign));
if (!value)
return;
const char *newValue = nullptr;
TextAlignMode textAlign = *value;
switch (textAlign) {
case TextAlignMode::Start:
case TextAlignMode::End: {
switch (direction) {
case WritingDirection::Natural:
// no-op
break;
case WritingDirection::LeftToRight:
newValue = "left";
break;
case WritingDirection::RightToLeft:
newValue = "right";
break;
}
break;
}
case TextAlignMode::Left:
case TextAlignMode::WebKitLeft:
newValue = "right";
break;
case TextAlignMode::Right:
case TextAlignMode::WebKitRight:
newValue = "left";
break;
case TextAlignMode::Center:
case TextAlignMode::WebKitCenter:
case TextAlignMode::Justify:
// no-op
break;
}
if (!newValue)
return;
Element* focusedElement = m_document.focusedElement();
if (focusedElement && (is<HTMLTextAreaElement>(*focusedElement) || (is<HTMLInputElement>(*focusedElement)
&& (downcast<HTMLInputElement>(*focusedElement).isTextField()
|| downcast<HTMLInputElement>(*focusedElement).isSearchField())))) {
if (direction == WritingDirection::Natural)
return;
downcast<HTMLElement>(*focusedElement).setAttributeWithoutSynchronization(alignAttr, newValue);
m_document.updateStyleIfNeeded();
return;
}
auto style = MutableStyleProperties::create();
style->setProperty(CSSPropertyTextAlign, newValue);
applyParagraphStyle(style.ptr());
}
void Editor::removeUnchangeableStyles()
{
// This function removes styles that the user cannot modify by applying their default values.
auto editingStyle = EditingStyle::create(m_document.bodyOrFrameset());
auto defaultStyle = editingStyle->style()->mutableCopy();
// Text widgets implement background color via the UIView property. Their body element will not have one.
defaultStyle->setProperty(CSSPropertyBackgroundColor, "rgba(255, 255, 255, 0.0)");
// Remove properties that the user can modify, like font-weight.
// Also remove font-family, per HI spec.
// FIXME: it'd be nice if knowledge about which styles were unchangeable was not hard-coded here.
defaultStyle->removeProperty(CSSPropertyFontWeight);
defaultStyle->removeProperty(CSSPropertyFontStyle);
defaultStyle->removeProperty(CSSPropertyFontVariantCaps);
// FIXME: we should handle also pasted quoted text, strikethrough, etc. <rdar://problem/9255115>
defaultStyle->removeProperty(CSSPropertyTextDecoration);
defaultStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect); // implements underline
// FIXME add EditAction::MatchStlye <rdar://problem/9156507> Undo rich text's paste & match style should say "Undo Match Style"
applyStyleToSelection(defaultStyle.ptr(), EditAction::ChangeAttributes);
}
static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
{
auto* renderer = imageElement.renderer();
if (!is<RenderImage>(renderer))
return;
CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
return;
image = tentativeCachedImage->imageForRenderer(renderer);
if (!image)
return;
cachedImage = tentativeCachedImage;
}
void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL& url, const String& title)
{
PasteboardImage pasteboardImage;
RefPtr<Image> image;
CachedImage* cachedImage = nullptr;
getImage(imageElement, image, cachedImage);
if (!image)
return;
ASSERT(cachedImage);
auto imageSourceURL = imageElement.document().completeURL(stripLeadingAndTrailingHTMLSpaces(imageElement.imageSourceURL()));
auto pasteboardImageURL = url.isEmpty() ? imageSourceURL : url;
if (!pasteboardImageURL.isLocalFile()) {
pasteboardImage.url.url = pasteboardImageURL;
pasteboardImage.url.title = title;
}
pasteboardImage.suggestedName = imageSourceURL.lastPathComponent().toString();
pasteboardImage.imageSize = image->size();
pasteboardImage.resourceMIMEType = pasteboard.resourceMIMEType(cachedImage->response().mimeType());
pasteboardImage.resourceData = cachedImage->resourceBuffer();
if (!pasteboard.isStatic())
client()->getClientPasteboardData(makeRangeSelectingNode(imageElement), pasteboardImage.clientTypes, pasteboardImage.clientData);
pasteboard.write(pasteboardImage);
}
void Editor::pasteWithPasteboard(Pasteboard* pasteboard, OptionSet<PasteOption> options)
{
auto range = selectedRange();
bool allowPlainText = options.contains(PasteOption::AllowPlainText);
WebContentReader reader(*m_document.frame(), *range, allowPlainText);
int numberOfPasteboardItems = client()->getPasteboardItemsCount();
for (int i = 0; i < numberOfPasteboardItems; ++i) {
auto fragment = client()->documentFragmentFromDelegate(i);
if (!fragment)
continue;
reader.addFragment(fragment.releaseNonNull());
}
auto fragment = WTFMove(reader.fragment);
if (!fragment) {
bool chosePlainTextIgnored;
fragment = webContentFromPasteboard(*pasteboard, *range, allowPlainText, chosePlainTextIgnored);
}
if (fragment && options.contains(PasteOption::AsQuotation))
quoteFragmentForPasting(*fragment);
if (fragment && shouldInsertFragment(*fragment, range, EditorInsertAction::Pasted))
pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(*pasteboard), false, options.contains(PasteOption::IgnoreMailBlockquote) ? MailBlockquoteHandling::IgnoreBlockquote : MailBlockquoteHandling::RespectBlockquote);
}
void Editor::insertDictationPhrases(Vector<Vector<String>>&& dictationPhrases, id metadata)
{
if (m_document.selection().isNone())
return;
if (dictationPhrases.isEmpty())
return;
DictationCommandIOS::create(document(), WTFMove(dictationPhrases), metadata)->apply();
}
void Editor::setDictationPhrasesAsChildOfElement(const Vector<Vector<String>>& dictationPhrases, id metadata, Element& element)
{
// Clear the composition.
clear();
// Clear the Undo stack, since the operations that follow are not Undoable, and will corrupt the stack.
// Some day we could make them Undoable, and let callers clear the Undo stack explicitly if they wish.
clearUndoRedoOperations();
m_document.selection().clear();
element.removeChildren();
if (dictationPhrases.isEmpty()) {
client()->respondToChangedContents();
return;
}
auto context = makeRangeSelectingNodeContents(element);
StringBuilder dictationPhrasesBuilder;
for (auto& interpretations : dictationPhrases)
dictationPhrasesBuilder.append(interpretations[0]);
element.appendChild(createFragmentFromText(context, dictationPhrasesBuilder.toString()));
WeakPtr weakElement { element };
// We need a layout in order to add markers below.
document().updateLayout();
if (!weakElement)
return;
if (!element.firstChild()->isTextNode()) {
// Shouldn't happen.
ASSERT(element.firstChild()->isTextNode());
return;
}
auto& textNode = downcast<Text>(*element.firstChild());
unsigned previousDictationPhraseStart = 0;
for (auto& interpretations : dictationPhrases) {
auto dictationPhraseLength = interpretations[0].length();
if (interpretations.size() > 1) {
auto alternatives = interpretations;
alternatives.remove(0);
addMarker(textNode, previousDictationPhraseStart, dictationPhraseLength, DocumentMarker::DictationPhraseWithAlternatives, WTFMove(alternatives));
}
previousDictationPhraseStart += dictationPhraseLength;
}
addMarker(textNode, 0, textNode.length(), DocumentMarker::DictationResult, retainPtr(metadata));
client()->respondToChangedContents();
}
void Editor::confirmMarkedText()
{
// FIXME: This is a hacky workaround for the keyboard calling this method too late -
// after the selection and focus have already changed. See <rdar://problem/5975559>.
Element* focused = document().focusedElement();
Node* composition = compositionNode();
if (composition && focused && focused != composition && !composition->isDescendantOrShadowDescendantOf(focused)) {
cancelComposition();
document().setFocusedElement(focused);
} else
confirmComposition();
}
void Editor::setTextAsChildOfElement(const String& text, Element& element)
{
// Clear the composition
clear();
// Clear the Undo stack, since the operations that follow are not Undoable, and will corrupt the stack.
// Some day we could make them Undoable, and let callers clear the Undo stack explicitly if they wish.
clearUndoRedoOperations();
// If the element is empty already and we're not adding text, we can early return and avoid clearing/setting
// a selection at [0, 0] and the expense involved in creating VisiblePositions.
if (!element.firstChild() && text.isEmpty())
return;
// As a side effect this function sets a caret selection after the inserted content.
// What follows is more expensive if there is a selection, so clear it since it's going to change anyway.
m_document.selection().clear();
element.stringReplaceAll(text);
VisiblePosition afterContents = makeContainerOffsetPosition(&element, element.countChildNodes());
if (afterContents.isNull())
return;
m_document.selection().setSelection(afterContents);
client()->respondToChangedContents();
}
// If the selection is adjusted from UIKit without closing the typing, the typing command may
// have a stale selection.
void Editor::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping()
{
TypingCommand::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(m_document, m_document.selection().selection());
}
} // namespace WebCore
#endif // PLATFORM(IOS_FAMILY)