https://bugs.webkit.org/show_bug.cgi?id=175116
<rdar://problem/
28279301>
Reviewed by Darin Adler and Ryosuke Niwa.
Source/WebCore:
WebCore support for WebPage::editorState refactoring. See WebKit ChangeLogs for more detail.
Tests: EditorStateTests.TypingAttributesBold
EditorStateTests.TypingAttributesItalic
EditorStateTests.TypingAttributesUnderline
EditorStateTests.TypingAttributesTextAlignmentAbsoluteAlignmentOptions
EditorStateTests.TypingAttributesTextAlignmentStartEnd
EditorStateTests.TypingAttributesTextAlignmentDirectionalText
EditorStateTests.TypingAttributesTextColor
EditorStateTests.TypingAttributesMixedStyles
EditorStateTests.TypingAttributesLinkColor
* css/StyleProperties.cpp:
(WebCore::StyleProperties::propertyAsColor const):
(WebCore::StyleProperties::propertyAsValueID const):
Introduces some helper functions in StyleProperties to convert CSS property values to Color or a CSSValueID.
* css/StyleProperties.h:
* editing/EditingStyle.cpp:
(WebCore::EditingStyle::hasStyle):
Pull out logic in selectionStartHasStyle that asks for a style TriState into EditingStyle::hasStyle. This is
because WebPage::editorState will now query for multiple styles at the selection start, but
selectionStartHasStyle currently recomputes styleAtSelectionStart every time it is called. To prevent extra work
from being done, we can just call selectionStartHasStyle once and use ask for EditingStyle::hasStyle on the
computed EditingStyle at selection start.
* editing/EditingStyle.h:
* editing/Editor.cpp:
(WebCore::Editor::selectionStartHasStyle const):
Source/WebKit:
Refactors WebPage::editorState to only use the StyleProperties derived from EditingStyle, instead of inserting
and removing a temporary node to figure out the style. Also adds hooks to notify the UI delegate of EditorState
changes.
* UIProcess/API/Cocoa/WKUIDelegatePrivate.h:
* UIProcess/API/Cocoa/WKWebView.mm:
(nsTextAlignment):
(dictionaryRepresentationForEditorState):
(-[WKWebView _didChangeEditorState]):
Alerts the private UI delegate of UI-side EditorState updates.
(-[WKWebView _web_editorStateDidChange]):
(-[WKWebView _executeEditCommand:argument:completion:]):
* UIProcess/API/Cocoa/WKWebViewInternal.h:
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/API/mac/WKView.mm:
(-[WKView _web_editorStateDidChange]):
* UIProcess/Cocoa/WebViewImpl.h:
* UIProcess/Cocoa/WebViewImpl.mm:
(WebKit::WebViewImpl::selectionDidChange):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::executeEditCommand):
Change executeEditCommand(name, callback) to executeEditCommand(name, argument, callback) and lift out of iOS
platform code and into WebPage.cpp.
* UIProcess/WebPageProxy.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView executeEditCommandWithCallback:]):
(-[WKContentView _selectionChanged]):
* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::executeEditCommand): Deleted.
Move the iOS-specific implementation of executeEditCommand that invokes a callback when the web process responds
out of WebPageProxyIOS, and into cross-platform WebPageProxy code. Additionally, add a parameter for the edit
command's argument.
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::editorState const):
Use EditingStyle::styleAtSelectionStart instead of Editor::styleForSelectionStart when computing an EditorState.
Tweak bold, italic and underline to use EditingStyle TriStates.
(WebKit::WebPage::executeEditCommandWithCallback):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::executeEditCommandWithCallback): Deleted.
Tools:
Introduces new testing infrastructure and API tests to test EditorState updates in the UI process. The new
EditorStateTests run on both iOS and Mac.
* TestWebKitAPI/EditingTestHarness.h: Added.
* TestWebKitAPI/EditingTestHarness.mm: Added.
EditingTestHarness is a helper object that API tests may use to apply editing commands and store EditorState
history. This test harness adds sugaring around various editing commands, and simplifies the process of checking
the state of the latest observed EditorState.
(-[EditingTestHarness initWithWebView:]):
(-[EditingTestHarness dealloc]):
(-[EditingTestHarness webView]):
(-[EditingTestHarness latestEditorState]):
(-[EditingTestHarness editorStateHistory]):
(-[EditingTestHarness insertText:andExpectEditorStateWith:]):
(-[EditingTestHarness insertHTML:andExpectEditorStateWith:]):
(-[EditingTestHarness selectAllAndExpectEditorStateWith:]):
(-[EditingTestHarness moveBackwardAndExpectEditorStateWith:]):
(-[EditingTestHarness moveWordBackwardAndExpectEditorStateWith:]):
(-[EditingTestHarness toggleBold]):
(-[EditingTestHarness toggleItalic]):
(-[EditingTestHarness toggleUnderline]):
(-[EditingTestHarness setForegroundColor:]):
(-[EditingTestHarness alignJustifiedAndExpectEditorStateWith:]):
(-[EditingTestHarness alignLeftAndExpectEditorStateWith:]):
(-[EditingTestHarness alignCenterAndExpectEditorStateWith:]):
(-[EditingTestHarness alignRightAndExpectEditorStateWith:]):
(-[EditingTestHarness insertParagraphAndExpectEditorStateWith:]):
(-[EditingTestHarness deleteBackwardAndExpectEditorStateWith:]):
(-[EditingTestHarness _execCommand:argument:expectEntries:]):
Dispatches an editing command to the web process, and blocks until a response is received. If an expected
entries dictionary is given, this will additionally verify that the latest EditorState contains all the expected
keys and values.
(-[EditingTestHarness latestEditorStateContains:]):
(-[EditingTestHarness _webView:editorStateDidChange:]):
* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKit2Cocoa/EditorStateTests.mm: Added.
(TestWebKitAPI::setUpEditorStateTestHarness):
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKit2Cocoa/editor-state-test-harness.html: Added.
LayoutTests:
* platform/ios-wk2/editing/style/unbold-in-bold-expected.txt:
* platform/mac-wk2/editing/style/unbold-in-bold-expected.txt:
Rebaseline a WK2 LayoutTest expectations. This test currently expects an empty anonymous RenderBlock to be
inserted into the render tree, but this is only a result of us adding and removing a temporary <span> when
computing a RenderStyle in WebPage::editorState -- this patch removes these empty RenderBlocks, making these
expectations' RenderTrees consistent with WebKit1.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@221065
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2017-08-22 Wenson Hsieh <wenson_hsieh@apple.com>
+
+ [iOS WK2] WKWebView schedules nonstop layout after pressing cmb+b,i,u inside a contenteditable div
+ https://bugs.webkit.org/show_bug.cgi?id=175116
+ <rdar://problem/28279301>
+
+ Reviewed by Darin Adler and Ryosuke Niwa.
+
+ * platform/ios-wk2/editing/style/unbold-in-bold-expected.txt:
+ * platform/mac-wk2/editing/style/unbold-in-bold-expected.txt:
+
+ Rebaseline a WK2 LayoutTest expectations. This test currently expects an empty anonymous RenderBlock to be
+ inserted into the render tree, but this is only a result of us adding and removing a temporary <span> when
+ computing a RenderStyle in WebPage::editorState -- this patch removes these empty RenderBlocks, making these
+ expectations' RenderTrees consistent with WebKit1.
+
2017-08-22 Wenson Hsieh <wenson_hsieh@apple.com>
[WK2] EditorState updates should be rolled into the layer update lifecycle when possible
RenderText {#text} at (170,15) size 72x28
text run at (170,15) width 72: "xxxxxx"
RenderInline {SPAN} at (0,0) size 0x28
- RenderBlock (anonymous) at (0,58) size 784x0
selection start: position 0 of child 1 {#text} of child 1 {DIV} of body
selection end: position 7 of child 1 {#text} of child 1 {DIV} of body
RenderText {#text} at (164,14) size 78x28
text run at (164,14) width 78: " xxxxxx"
RenderInline {SPAN} at (0,0) size 0x28
- RenderBlock (anonymous) at (0,56) size 784x0
selection start: position 0 of child 1 {#text} of child 1 {DIV} of body
selection end: position 6 of child 1 {#text} of child 1 {DIV} of body
+2017-08-20 Wenson Hsieh <wenson_hsieh@apple.com>
+
+ [iOS WK2] WKWebView schedules nonstop layout after pressing cmb+b,i,u inside a contenteditable div
+ https://bugs.webkit.org/show_bug.cgi?id=175116
+ <rdar://problem/28279301>
+
+ Reviewed by Darin Adler and Ryosuke Niwa.
+
+ WebCore support for WebPage::editorState refactoring. See WebKit ChangeLogs for more detail.
+
+ Tests: EditorStateTests.TypingAttributesBold
+ EditorStateTests.TypingAttributesItalic
+ EditorStateTests.TypingAttributesUnderline
+ EditorStateTests.TypingAttributesTextAlignmentAbsoluteAlignmentOptions
+ EditorStateTests.TypingAttributesTextAlignmentStartEnd
+ EditorStateTests.TypingAttributesTextAlignmentDirectionalText
+ EditorStateTests.TypingAttributesTextColor
+ EditorStateTests.TypingAttributesMixedStyles
+ EditorStateTests.TypingAttributesLinkColor
+
+ * css/StyleProperties.cpp:
+ (WebCore::StyleProperties::propertyAsColor const):
+ (WebCore::StyleProperties::propertyAsValueID const):
+
+ Introduces some helper functions in StyleProperties to convert CSS property values to Color or a CSSValueID.
+
+ * css/StyleProperties.h:
+ * editing/EditingStyle.cpp:
+ (WebCore::EditingStyle::hasStyle):
+
+ Pull out logic in selectionStartHasStyle that asks for a style TriState into EditingStyle::hasStyle. This is
+ because WebPage::editorState will now query for multiple styles at the selection start, but
+ selectionStartHasStyle currently recomputes styleAtSelectionStart every time it is called. To prevent extra work
+ from being done, we can just call selectionStartHasStyle once and use ask for EditingStyle::hasStyle on the
+ computed EditingStyle at selection start.
+
+ * editing/EditingStyle.h:
+ * editing/Editor.cpp:
+ (WebCore::Editor::selectionStartHasStyle const):
+
2017-08-22 Wenson Hsieh <wenson_hsieh@apple.com>
[WK2] EditorState updates should be rolled into the layer update lifecycle when possible
#include "CSSValueKeywords.h"
#include "CSSValueList.h"
#include "CSSValuePool.h"
+#include "Color.h"
#include "Document.h"
#include "PropertySetCSSStyleDeclaration.h"
#include "StylePropertyShorthand.h"
}
}
+std::optional<Color> StyleProperties::propertyAsColor(CSSPropertyID property) const
+{
+ auto colorValue = getPropertyCSSValue(property);
+ if (!is<CSSPrimitiveValue>(colorValue.get()))
+ return std::nullopt;
+
+ auto& primitiveColor = downcast<CSSPrimitiveValue>(*colorValue);
+ return primitiveColor.isRGBColor() ? primitiveColor.color() : CSSParser::parseColor(colorValue->cssText());
+}
+
+CSSValueID StyleProperties::propertyAsValueID(CSSPropertyID property) const
+{
+ auto cssValue = getPropertyCSSValue(property);
+ return is<CSSPrimitiveValue>(cssValue.get()) ? downcast<CSSPrimitiveValue>(*cssValue).valueID() : CSSValueInvalid;
+}
+
String StyleProperties::getCustomPropertyValue(const String& propertyName) const
{
RefPtr<CSSValue> value = getCustomPropertyCSSValue(propertyName);
class CSSDeferredParser;
class CSSStyleDeclaration;
class CachedResource;
+class Color;
class ImmutableStyleProperties;
class URL;
class MutableStyleProperties;
WEBCORE_EXPORT RefPtr<CSSValue> getPropertyCSSValue(CSSPropertyID) const;
WEBCORE_EXPORT String getPropertyValue(CSSPropertyID) const;
+
+ WEBCORE_EXPORT std::optional<Color> propertyAsColor(CSSPropertyID) const;
+ WEBCORE_EXPORT CSSValueID propertyAsValueID(CSSPropertyID) const;
+
bool propertyIsImportant(CSSPropertyID) const;
String getPropertyShorthand(CSSPropertyID) const;
bool isPropertyImplicit(CSSPropertyID) const;
m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize);
}
+bool EditingStyle::hasStyle(CSSPropertyID propertyID, const String& value)
+{
+ return EditingStyle::create(propertyID, value)->triStateOfStyle(this) != FalseTriState;
+}
+
RefPtr<EditingStyle> EditingStyle::styleAtSelectionStart(const VisibleSelection& selection, bool shouldUseBackgroundColorInEffect)
{
if (selection.isNone())
void setStrikeThroughChange(TextDecorationChange change) { m_strikeThroughChange = static_cast<unsigned>(change); }
TextDecorationChange strikeThroughChange() const { return static_cast<TextDecorationChange>(m_strikeThroughChange); }
+ WEBCORE_EXPORT bool hasStyle(CSSPropertyID, const String& value);
WEBCORE_EXPORT static RefPtr<EditingStyle> styleAtSelectionStart(const VisibleSelection&, bool shouldUseBackgroundColorInEffect = false);
static WritingDirection textDirectionForSelection(const VisibleSelection&, EditingStyle* typingStyle, bool& hasNestedOrMultipleEmbeddings);
bool Editor::selectionStartHasStyle(CSSPropertyID propertyID, const String& value) const
{
- return EditingStyle::create(propertyID, value)->triStateOfStyle(
- EditingStyle::styleAtSelectionStart(m_frame.selection().selection(), propertyID == CSSPropertyBackgroundColor).get());
+ if (auto editingStyle = EditingStyle::styleAtSelectionStart(m_frame.selection().selection(), propertyID == CSSPropertyBackgroundColor))
+ return editingStyle->hasStyle(propertyID, value);
+ return false;
}
TriState Editor::selectionHasStyle(CSSPropertyID propertyID, const String& value) const
+2017-08-20 Wenson Hsieh <wenson_hsieh@apple.com>
+
+ [iOS WK2] WKWebView schedules nonstop layout after pressing cmb+b,i,u inside a contenteditable div
+ https://bugs.webkit.org/show_bug.cgi?id=175116
+ <rdar://problem/28279301>
+
+ Reviewed by Darin Adler and Ryosuke Niwa.
+
+ Refactors WebPage::editorState to only use the StyleProperties derived from EditingStyle, instead of inserting
+ and removing a temporary node to figure out the style. Also adds hooks to notify the UI delegate of EditorState
+ changes.
+
+ * UIProcess/API/Cocoa/WKUIDelegatePrivate.h:
+ * UIProcess/API/Cocoa/WKWebView.mm:
+ (nsTextAlignment):
+ (dictionaryRepresentationForEditorState):
+ (-[WKWebView _didChangeEditorState]):
+
+ Alerts the private UI delegate of UI-side EditorState updates.
+
+ (-[WKWebView _web_editorStateDidChange]):
+ (-[WKWebView _executeEditCommand:argument:completion:]):
+ * UIProcess/API/Cocoa/WKWebViewInternal.h:
+ * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+ * UIProcess/API/mac/WKView.mm:
+ (-[WKView _web_editorStateDidChange]):
+ * UIProcess/Cocoa/WebViewImpl.h:
+ * UIProcess/Cocoa/WebViewImpl.mm:
+ (WebKit::WebViewImpl::selectionDidChange):
+ * UIProcess/WebPageProxy.cpp:
+ (WebKit::WebPageProxy::executeEditCommand):
+
+ Change executeEditCommand(name, callback) to executeEditCommand(name, argument, callback) and lift out of iOS
+ platform code and into WebPage.cpp.
+
+ * UIProcess/WebPageProxy.h:
+ * UIProcess/ios/WKContentViewInteraction.mm:
+ (-[WKContentView executeEditCommandWithCallback:]):
+ (-[WKContentView _selectionChanged]):
+ * UIProcess/ios/WebPageProxyIOS.mm:
+ (WebKit::WebPageProxy::executeEditCommand): Deleted.
+
+ Move the iOS-specific implementation of executeEditCommand that invokes a callback when the web process responds
+ out of WebPageProxyIOS, and into cross-platform WebPageProxy code. Additionally, add a parameter for the edit
+ command's argument.
+
+ * WebProcess/WebPage/WebPage.cpp:
+ (WebKit::WebPage::editorState const):
+
+ Use EditingStyle::styleAtSelectionStart instead of Editor::styleForSelectionStart when computing an EditorState.
+ Tweak bold, italic and underline to use EditingStyle TriStates.
+
+ (WebKit::WebPage::executeEditCommandWithCallback):
+ * WebProcess/WebPage/WebPage.h:
+ * WebProcess/WebPage/WebPage.messages.in:
+ * WebProcess/WebPage/ios/WebPageIOS.mm:
+ (WebKit::WebPage::executeEditCommandWithCallback): Deleted.
+
2017-08-22 Wenson Hsieh <wenson_hsieh@apple.com>
[WK2] EditorState updates should be rolled into the layer update lifecycle when possible
- (void)_webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures completionHandler:(void (^)(WKWebView *webView))completionHandler WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
- (void)_webView:(WKWebView *)webView runBeforeUnloadConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)_webView:(WKWebView *)webView editorStateDidChange:(NSDictionary *)editorState WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
#if TARGET_OS_IPHONE
- (BOOL)_webView:(WKWebView *)webView shouldIncludeAppLinkActionsForElement:(_WKActivatedElementInfo *)element WK_API_AVAILABLE(ios(9.0));
_page->setViewportSizeForCSSViewportUnits(viewportSizeForViewportUnits);
}
+static NSTextAlignment nsTextAlignment(WebKit::TextAlignment alignment)
+{
+ switch (alignment) {
+ case WebKit::NoAlignment:
+ return NSTextAlignmentNatural;
+ case WebKit::LeftAlignment:
+ return NSTextAlignmentLeft;
+ case WebKit::RightAlignment:
+ return NSTextAlignmentRight;
+ case WebKit::CenterAlignment:
+ return NSTextAlignmentCenter;
+ case WebKit::JustifiedAlignment:
+ return NSTextAlignmentJustified;
+ }
+ ASSERT_NOT_REACHED();
+ return NSTextAlignmentNatural;
+}
+
+static NSDictionary *dictionaryRepresentationForEditorState(const WebKit::EditorState& state)
+{
+ if (state.isMissingPostLayoutData)
+ return @{ @"post-layout-data" : @NO };
+
+ auto& postLayoutData = state.postLayoutData();
+ return @{
+ @"post-layout-data" : @YES,
+ @"bold": postLayoutData.typingAttributes & WebKit::AttributeBold ? @YES : @NO,
+ @"italic": postLayoutData.typingAttributes & WebKit::AttributeItalics ? @YES : @NO,
+ @"underline": postLayoutData.typingAttributes & WebKit::AttributeUnderline ? @YES : @NO,
+ @"text-alignment": @(nsTextAlignment(static_cast<WebKit::TextAlignment>(postLayoutData.textAlignment))),
+ @"text-color": (NSString *)postLayoutData.textColor.cssText()
+ };
+}
+
+- (void)_didChangeEditorState
+{
+ id <WKUIDelegatePrivate> uiDelegate = (id <WKUIDelegatePrivate>)self.UIDelegate;
+ if ([uiDelegate respondsToSelector:@selector(_webView:editorStateDidChange:)])
+ [uiDelegate _webView:self editorStateDidChange:dictionaryRepresentationForEditorState(_page->editorState())];
+}
+
#pragma mark iOS-specific methods
#if PLATFORM(IOS)
_impl->dismissContentRelativeChildWindowsWithAnimationFromViewOnly(withAnimation);
}
+- (void)_web_editorStateDidChange
+{
+ [self _didChangeEditorState];
+}
+
- (void)_web_gestureEventWasNotHandledByWebCore:(NSEvent *)event
{
_impl->gestureEventWasNotHandledByWebCoreFromViewOnly(event);
WebKit::ViewSnapshotStore::singleton().setDisableSnapshotVolatilityForTesting(true);
}
+- (void)_executeEditCommand:(NSString *)command argument:(NSString *)argument completion:(void (^)(BOOL))completion
+{
+ _page->executeEditCommand(command, argument, [capturedCompletionBlock = makeBlockPtr(completion)](WebKit::CallbackBase::Error error) {
+ capturedCompletionBlock(error == WebKit::CallbackBase::Error::None);
+ });
+}
+
#if PLATFORM(IOS)
- (void)_simulateDataInteractionEntered:(id)info
- (void)_showPasswordViewWithDocumentName:(NSString *)documentName passwordHandler:(void (^)(NSString *))passwordHandler;
- (void)_hidePasswordView;
+- (void)_didChangeEditorState;
+
- (void)_addShortcut:(id)sender;
- (void)_arrowKey:(id)sender;
- (void)_define:(id)sender;
- (void)_doAfterNextVisibleContentRectUpdate:(void (^)(void))updateBlock WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
- (void)_disableBackForwardSnapshotVolatilityForTesting WK_API_AVAILABLE(macosx(10.12.3), ios(10.3));
+- (void)_executeEditCommand:(NSString *)command argument:(NSString *)argument completion:(void (^)(BOOL))completion WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
@end
[self _dismissContentRelativeChildWindowsWithAnimation:withAnimation];
}
+- (void)_web_editorStateDidChange
+{
+}
+
- (void)_web_gestureEventWasNotHandledByWebCore:(NSEvent *)event
{
[self _gestureEventWasNotHandledByWebCore:event];
- (void)_web_dismissContentRelativeChildWindows;
- (void)_web_dismissContentRelativeChildWindowsWithAnimation:(BOOL)animate;
+- (void)_web_editorStateDidChange;
- (void)_web_gestureEventWasNotHandledByWebCore:(NSEvent *)event;
if (!m_page->editorState().isMissingPostLayoutData)
requestCandidatesForSelectionIfNeeded();
#endif
+
+ [m_view _web_editorStateDidChange];
}
void WebViewImpl::didBecomeEditable()
{
m_maintainsInactiveSelection = newValue;
}
+
+void WebPageProxy::executeEditCommand(const String& commandName, const String& argument, WTF::Function<void(CallbackBase::Error)>&& callbackFunction)
+{
+ if (!isValid()) {
+ callbackFunction(CallbackBase::Error::Unknown);
+ return;
+ }
+
+ auto callbackID = m_callbacks.put(WTFMove(callbackFunction), m_process->throttler().backgroundActivityToken());
+ m_process->send(Messages::WebPage::ExecuteEditCommandWithCallback(commandName, argument, callbackID), m_pageID);
+}
void WebPageProxy::executeEditCommand(const String& commandName, const String& argument)
{
void activateMediaStreamCaptureInPage();
bool isMediaStreamCaptureMuted() const { return m_mutedState & WebCore::MediaProducer::CaptureDevicesAreMuted; }
void setMediaStreamCaptureMuted(bool);
+ void executeEditCommand(const String& commandName, const String& argument, WTF::Function<void(CallbackBase::Error)>&&);
#if PLATFORM(IOS)
- void executeEditCommand(const String& commandName, WTF::Function<void (CallbackBase::Error)>&&);
double displayedContentScale() const { return m_lastVisibleContentRectUpdate.scale(); }
const WebCore::FloatRect& exposedContentRect() const { return m_lastVisibleContentRectUpdate.exposedContentRect(); }
const WebCore::FloatRect& unobscuredContentRect() const { return m_lastVisibleContentRectUpdate.unobscuredContentRect(); }
{
[self beginSelectionChange];
RetainPtr<WKContentView> view = self;
- _page->executeEditCommand(commandName, [view](WebKit::CallbackBase::Error) {
+ _page->executeEditCommand(commandName, { }, [view](WebKit::CallbackBase::Error) {
[view endSelectionChange];
});
}
// to wait to paint the selection.
if (_usingGestureForSelection)
[self _updateChangedSelection];
+
+ [_webView _didChangeEditorState];
}
- (void)selectWordForReplacement
m_process->send(Messages::WebPage::ApplyAutocorrection(correction, originalText, callbackID), m_pageID);
}
-void WebPageProxy::executeEditCommand(const String& commandName, WTF::Function<void (CallbackBase::Error)>&& callbackFunction)
-{
- if (!isValid()) {
- callbackFunction(CallbackBase::Error::Unknown);
- return;
- }
-
- auto callbackID = m_callbacks.put(WTFMove(callbackFunction), m_process->throttler().backgroundActivityToken());
- m_process->send(Messages::WebPage::ExecuteEditCommandWithCallback(commandName, callbackID), m_pageID);
-}
-
bool WebPageProxy::applyAutocorrection(const String& correction, const String& originalText)
{
bool autocorrectionApplied = false;
if (shouldIncludePostLayoutData == IncludePostLayoutDataHint::Yes && canIncludePostLayoutData && result.isContentEditable) {
auto& postLayoutData = result.postLayoutData();
if (!selection.isNone()) {
- Node* nodeToRemove;
- if (auto* style = Editor::styleForSelectionStart(&frame, nodeToRemove)) {
- if (isFontWeightBold(style->fontCascade().weight()))
+ if (auto editingStyle = EditingStyle::styleAtSelectionStart(selection)) {
+ if (editingStyle->hasStyle(CSSPropertyFontWeight, "bold"))
postLayoutData.typingAttributes |= AttributeBold;
- if (isItalic(style->fontCascade().italic()))
+
+ if (editingStyle->hasStyle(CSSPropertyFontStyle, "italic") || editingStyle->hasStyle(CSSPropertyFontStyle, "oblique"))
postLayoutData.typingAttributes |= AttributeItalics;
- RefPtr<EditingStyle> typingStyle = frame.selection().typingStyle();
- if (typingStyle && typingStyle->style()) {
- String value = typingStyle->style()->getPropertyValue(CSSPropertyWebkitTextDecorationsInEffect);
- if (value.contains("underline"))
+ if (editingStyle->hasStyle(CSSPropertyWebkitTextDecorationsInEffect, "underline"))
postLayoutData.typingAttributes |= AttributeUnderline;
- } else {
- if (style->textDecorationsInEffect() & TextDecorationUnderline)
- postLayoutData.typingAttributes |= AttributeUnderline;
- }
- if (style->visitedDependentColor(CSSPropertyColor).isValid())
- postLayoutData.textColor = style->visitedDependentColor(CSSPropertyColor);
-
- switch (style->textAlign()) {
- case RIGHT:
- case WEBKIT_RIGHT:
- postLayoutData.textAlignment = RightAlignment;
- break;
- case LEFT:
- case WEBKIT_LEFT:
- postLayoutData.textAlignment = LeftAlignment;
- break;
- case CENTER:
- case WEBKIT_CENTER:
- postLayoutData.textAlignment = CenterAlignment;
- break;
- case JUSTIFY:
- postLayoutData.textAlignment = JustifiedAlignment;
- break;
- case TASTART:
- postLayoutData.textAlignment = style->isLeftToRightDirection() ? LeftAlignment : RightAlignment;
- break;
- case TAEND:
- postLayoutData.textAlignment = style->isLeftToRightDirection() ? RightAlignment : LeftAlignment;
- break;
+ if (auto* styleProperties = editingStyle->style()) {
+ bool isLeftToRight = styleProperties->propertyAsValueID(CSSPropertyDirection) == CSSValueLtr;
+ switch (styleProperties->propertyAsValueID(CSSPropertyTextAlign)) {
+ case CSSValueRight:
+ case CSSValueWebkitRight:
+ postLayoutData.textAlignment = RightAlignment;
+ break;
+ case CSSValueLeft:
+ case CSSValueWebkitLeft:
+ postLayoutData.textAlignment = LeftAlignment;
+ break;
+ case CSSValueCenter:
+ case CSSValueWebkitCenter:
+ postLayoutData.textAlignment = CenterAlignment;
+ break;
+ case CSSValueJustify:
+ postLayoutData.textAlignment = JustifiedAlignment;
+ break;
+ case CSSValueStart:
+ postLayoutData.textAlignment = isLeftToRight ? LeftAlignment : RightAlignment;
+ break;
+ case CSSValueEnd:
+ postLayoutData.textAlignment = isLeftToRight ? RightAlignment : LeftAlignment;
+ break;
+ default:
+ break;
+ }
+ if (auto textColor = styleProperties->propertyAsColor(CSSPropertyColor))
+ postLayoutData.textColor = *textColor;
}
-
- HTMLElement* enclosingListElement = enclosingList(selection.start().deprecatedNode());
- if (enclosingListElement) {
- if (is<HTMLUListElement>(*enclosingListElement))
- postLayoutData.enclosingListType = UnorderedList;
- else if (is<HTMLOListElement>(*enclosingListElement))
- postLayoutData.enclosingListType = OrderedList;
- else
- ASSERT_NOT_REACHED();
- } else
- postLayoutData.enclosingListType = NoList;
-
- if (nodeToRemove)
- nodeToRemove->remove();
+ }
+
+ if (auto* enclosingListElement = enclosingList(selection.start().containerNode())) {
+ if (is<HTMLUListElement>(*enclosingListElement))
+ postLayoutData.enclosingListType = UnorderedList;
+ else if (is<HTMLOListElement>(*enclosingListElement))
+ postLayoutData.enclosingListType = OrderedList;
+ else
+ ASSERT_NOT_REACHED();
}
}
}
return result;
}
+void WebPage::executeEditCommandWithCallback(const String& commandName, const String& argument, CallbackID callbackID)
+{
+ executeEditCommand(commandName, argument);
+ send(Messages::WebPageProxy::VoidCallback(callbackID));
+}
+
void WebPage::updateEditorStateAfterLayoutIfEditabilityChanged()
{
// FIXME: We should update EditorStateIsContentEditable to track whether the state is richly
void resetAssistedNodeForFrame(WebFrame*);
void viewportPropertiesDidChange(const WebCore::ViewportArguments&);
+ void executeEditCommandWithCallback(const String&, const String& argument, CallbackID);
#if PLATFORM(IOS)
WebCore::FloatSize screenSize() const;
#endif
void contentSizeCategoryDidChange(const String&);
- void executeEditCommandWithCallback(const String&, CallbackID);
Seconds eventThrottlingDelay() const;
ViewWillStartLiveResize()
ViewWillEndLiveResize()
+ ExecuteEditCommandWithCallback(String name, String argument, WebKit::CallbackID callbackID)
KeyEvent(WebKit::WebKeyboardEvent event)
MouseEvent(WebKit::WebMouseEvent event)
#if PLATFORM(IOS)
ApplicationWillEnterForeground(bool isSuspendedUnderLock)
ApplicationDidBecomeActive()
ContentSizeCategoryDidChange(String contentSizeCategory)
- ExecuteEditCommandWithCallback(String name, WebKit::CallbackID callbackID)
GetSelectionContext(WebKit::CallbackID callbackID)
SetAllowsMediaDocumentInlinePlayback(bool allows)
HandleTwoFingerTapAtPoint(WebCore::IntPoint point, uint64_t requestID)
send(Messages::WebPageProxy::StringCallback(correctionApplied ? correction : String(), callbackID));
}
-void WebPage::executeEditCommandWithCallback(const String& commandName, CallbackID callbackID)
-{
- executeEditCommand(commandName, String());
- send(Messages::WebPageProxy::VoidCallback(callbackID));
-}
-
Seconds WebPage::eventThrottlingDelay() const
{
auto behaviorOverride = m_page->eventThrottlingBehaviorOverride();
+2017-08-20 Wenson Hsieh <wenson_hsieh@apple.com>
+
+ [iOS WK2] WKWebView schedules nonstop layout after pressing cmb+b,i,u inside a contenteditable div
+ https://bugs.webkit.org/show_bug.cgi?id=175116
+ <rdar://problem/28279301>
+
+ Reviewed by Darin Adler and Ryosuke Niwa.
+
+ Introduces new testing infrastructure and API tests to test EditorState updates in the UI process. The new
+ EditorStateTests run on both iOS and Mac.
+
+ * TestWebKitAPI/EditingTestHarness.h: Added.
+ * TestWebKitAPI/EditingTestHarness.mm: Added.
+
+ EditingTestHarness is a helper object that API tests may use to apply editing commands and store EditorState
+ history. This test harness adds sugaring around various editing commands, and simplifies the process of checking
+ the state of the latest observed EditorState.
+
+ (-[EditingTestHarness initWithWebView:]):
+ (-[EditingTestHarness dealloc]):
+ (-[EditingTestHarness webView]):
+ (-[EditingTestHarness latestEditorState]):
+ (-[EditingTestHarness editorStateHistory]):
+ (-[EditingTestHarness insertText:andExpectEditorStateWith:]):
+ (-[EditingTestHarness insertHTML:andExpectEditorStateWith:]):
+ (-[EditingTestHarness selectAllAndExpectEditorStateWith:]):
+ (-[EditingTestHarness moveBackwardAndExpectEditorStateWith:]):
+ (-[EditingTestHarness moveWordBackwardAndExpectEditorStateWith:]):
+ (-[EditingTestHarness toggleBold]):
+ (-[EditingTestHarness toggleItalic]):
+ (-[EditingTestHarness toggleUnderline]):
+ (-[EditingTestHarness setForegroundColor:]):
+ (-[EditingTestHarness alignJustifiedAndExpectEditorStateWith:]):
+ (-[EditingTestHarness alignLeftAndExpectEditorStateWith:]):
+ (-[EditingTestHarness alignCenterAndExpectEditorStateWith:]):
+ (-[EditingTestHarness alignRightAndExpectEditorStateWith:]):
+ (-[EditingTestHarness insertParagraphAndExpectEditorStateWith:]):
+ (-[EditingTestHarness deleteBackwardAndExpectEditorStateWith:]):
+ (-[EditingTestHarness _execCommand:argument:expectEntries:]):
+
+ Dispatches an editing command to the web process, and blocks until a response is received. If an expected
+ entries dictionary is given, this will additionally verify that the latest EditorState contains all the expected
+ keys and values.
+
+ (-[EditingTestHarness latestEditorStateContains:]):
+ (-[EditingTestHarness _webView:editorStateDidChange:]):
+ * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+ * TestWebKitAPI/Tests/WebKit2Cocoa/EditorStateTests.mm: Added.
+ (TestWebKitAPI::setUpEditorStateTestHarness):
+ (TestWebKitAPI::TEST):
+ * TestWebKitAPI/Tests/WebKit2Cocoa/editor-state-test-harness.html: Added.
+
2017-08-22 Wenson Hsieh <wenson_hsieh@apple.com>
[WK2] EditorState updates should be rolled into the layer update lifecycle when possible
--- /dev/null
+/*
+ * Copyright (C) 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. 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.
+ */
+
+#pragma once
+
+#if WK_API_ENABLED
+
+#import "TestWKWebView.h"
+#import <WebKit/WKUIDelegatePrivate.h>
+
+@interface EditingTestHarness : NSObject<WKUIDelegatePrivate> {
+ RetainPtr<NSMutableArray<NSDictionary *> *> _editorStateHistory;
+ RetainPtr<TestWKWebView *> _webView;
+}
+
+- (instancetype)initWithWebView:(TestWKWebView *)webView;
+
+@property (nonatomic, readonly) TestWKWebView *webView;
+@property (nonatomic, readonly) NSDictionary *latestEditorState;
+@property (nonatomic, readonly) NSArray<NSDictionary *> *editorStateHistory;
+
+- (void)insertParagraphAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)insertText:(NSString *)text andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)insertHTML:(NSString *)html andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)selectAllAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)moveBackwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)moveWordBackwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)deleteBackwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)toggleBold;
+- (void)toggleItalic;
+- (void)toggleUnderline;
+- (void)setForegroundColor:(NSString *)colorAsString;
+- (void)alignJustifiedAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)alignLeftAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)alignCenterAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+- (void)alignRightAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
+
+- (BOOL)latestEditorStateContains:(NSDictionary<NSString *, id> *)entries;
+
+@end
+
+#endif // WK_API_ENABLED
--- /dev/null
+/*
+ * Copyright (C) 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. 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.
+ */
+
+
+#include "config.h"
+#include "EditingTestHarness.h"
+
+#if WK_API_ENABLED
+
+#import "PlatformUtilities.h"
+#import <WebKit/WKWebViewPrivate.h>
+
+@implementation EditingTestHarness
+
+- (instancetype)initWithWebView:(TestWKWebView *)webView
+{
+ if (self = [super init]) {
+ _webView = webView;
+ [_webView setUIDelegate:self];
+ _editorStateHistory = adoptNS([[NSMutableArray alloc] init]);
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if ([_webView UIDelegate] == self)
+ [_webView setUIDelegate:nil];
+
+ [super dealloc];
+}
+
+- (TestWKWebView *)webView
+{
+ return _webView.get();
+}
+
+- (NSDictionary *)latestEditorState
+{
+ return self.editorStateHistory.lastObject;
+}
+
+- (NSArray<NSDictionary *> *)editorStateHistory
+{
+ return _editorStateHistory.get();
+}
+
+- (void)insertText:(NSString *)text andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"InsertText" argument:text expectEntries:entries];
+}
+
+- (void)insertHTML:(NSString *)html andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"InsertHTML" argument:html expectEntries:entries];
+}
+
+- (void)selectAllAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"SelectAll" argument:nil expectEntries:entries];
+}
+
+- (void)moveBackwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"MoveBackward" argument:nil expectEntries:entries];
+}
+
+- (void)moveWordBackwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"MoveWordBackward" argument:nil expectEntries:entries];
+}
+
+- (void)toggleBold
+{
+ [self _execCommand:@"ToggleBold" argument:nil expectEntries:nil];
+}
+
+- (void)toggleItalic
+{
+ [self _execCommand:@"ToggleItalic" argument:nil expectEntries:nil];
+}
+
+- (void)toggleUnderline
+{
+ [self _execCommand:@"ToggleUnderline" argument:nil expectEntries:nil];
+}
+
+- (void)setForegroundColor:(NSString *)colorAsString
+{
+ [self _execCommand:@"ForeColor" argument:colorAsString expectEntries:nil];
+}
+
+- (void)alignJustifiedAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"AlignJustified" argument:nil expectEntries:entries];
+}
+
+- (void)alignLeftAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"AlignLeft" argument:nil expectEntries:entries];
+}
+
+- (void)alignCenterAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"AlignCenter" argument:nil expectEntries:entries];
+}
+
+- (void)alignRightAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"AlignRight" argument:nil expectEntries:entries];
+}
+
+- (void)insertParagraphAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"InsertParagraph" argument:nil expectEntries:entries];
+}
+
+- (void)deleteBackwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+ [self _execCommand:@"DeleteBackward" argument:nil expectEntries:entries];
+}
+
+- (void)_execCommand:(NSString *)command argument:(NSString *)argument expectEntries:(NSDictionary<NSString *, id> *)entries
+{
+ __block BOOL result = false;
+ __block bool done = false;
+ [_webView _executeEditCommand:command argument:argument completion:^(BOOL success) {
+ result = success;
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+
+ [_webView waitForNextPresentationUpdate];
+
+ EXPECT_TRUE(result);
+ if (!result)
+ NSLog(@"Failed to execute editing command: ('%@', '%@')", command, argument ?: @"");
+
+ BOOL containsEntries = [self latestEditorStateContains:entries];
+ EXPECT_TRUE(containsEntries);
+ if (!containsEntries)
+ NSLog(@"Expected %@ to contain %@", self.latestEditorState, entries);
+}
+
+- (BOOL)latestEditorStateContains:(NSDictionary<NSString *, id> *)entries
+{
+ NSDictionary *latestEditorState = self.latestEditorState;
+ for (NSString *key in entries) {
+ if (![latestEditorState[key] isEqual:entries[key]])
+ return NO;
+ }
+ return latestEditorState.count || !entries.count;
+}
+
+#pragma mark - WKUIDelegatePrivate
+
+- (void)_webView:(WKWebView *)webView editorStateDidChange:(NSDictionary *)editorState
+{
+ if (![editorState[@"post-layout-data"] boolValue])
+ return;
+
+ if (![self.latestEditorState isEqualToDictionary:editorState])
+ [_editorStateHistory addObject:editorState];
+}
+
+@end
+
+#endif // WK_API_ENABLED
F41AB9AA1EF4696B0083FA08 /* textarea-to-input.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F41AB9951EF4692C0083FA08 /* textarea-to-input.html */; };
F42DA5161D8CEFE400336F40 /* large-input-field-focus-onload.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F42DA5151D8CEFDB00336F40 /* large-input-field-focus-onload.html */; };
F4451C761EB8FD890020C5DA /* two-paragraph-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */; };
+ F44D06451F395C26001A0E29 /* editor-state-test-harness.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F44D06441F395C0D001A0E29 /* editor-state-test-harness.html */; };
+ F44D06471F39627A001A0E29 /* EditorStateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F44D06461F395C4D001A0E29 /* EditorStateTests.mm */; };
+ F44D064A1F3962F2001A0E29 /* EditingTestHarness.mm in Sources */ = {isa = PBXBuildFile; fileRef = F44D06491F3962E3001A0E29 /* EditingTestHarness.mm */; };
F4538EF71E8473E600B5C953 /* large-red-square.png in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4538EF01E846B4100B5C953 /* large-red-square.png */; };
F45B63FB1F197F4A009D38B9 /* image-map.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F45B63FA1F197F33009D38B9 /* image-map.html */; };
F45B63FE1F19D410009D38B9 /* ActionSheetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F45B63FC1F19D410009D38B9 /* ActionSheetTests.mm */; };
F4D5E4E81F0C5D38008C1A49 /* dragstart-clear-selection.html in Copy Resources */,
A155022C1E050D0300A24C57 /* duplicate-completion-handler-calls.html in Copy Resources */,
9984FACE1CFFB090008D198C /* editable-body.html in Copy Resources */,
+ F44D06451F395C26001A0E29 /* editor-state-test-harness.html in Copy Resources */,
51C8E1A91F27F49600BF731B /* EmptyGrandfatheredResourceLoadStatistics.plist in Copy Resources */,
A14AAB651E78DC5400C1ADC2 /* encrypted.pdf in Copy Resources */,
F4C2AB221DD6D95E00E06D5B /* enormous-video-with-sound.html in Copy Resources */,
F41AB99E1EF4692C0083FA08 /* div-and-large-image.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "div-and-large-image.html"; sourceTree = "<group>"; };
F42DA5151D8CEFDB00336F40 /* large-input-field-focus-onload.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = "large-input-field-focus-onload.html"; path = "Tests/WebKit2Cocoa/large-input-field-focus-onload.html"; sourceTree = SOURCE_ROOT; };
F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "two-paragraph-contenteditable.html"; sourceTree = "<group>"; };
+ F44D06441F395C0D001A0E29 /* editor-state-test-harness.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "editor-state-test-harness.html"; sourceTree = "<group>"; };
+ F44D06461F395C4D001A0E29 /* EditorStateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EditorStateTests.mm; sourceTree = "<group>"; };
+ F44D06481F3962E3001A0E29 /* EditingTestHarness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditingTestHarness.h; sourceTree = "<group>"; };
+ F44D06491F3962E3001A0E29 /* EditingTestHarness.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EditingTestHarness.mm; sourceTree = "<group>"; };
F4538EF01E846B4100B5C953 /* large-red-square.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "large-red-square.png"; sourceTree = "<group>"; };
F45B63FA1F197F33009D38B9 /* image-map.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "image-map.html"; sourceTree = "<group>"; };
F45B63FC1F19D410009D38B9 /* ActionSheetTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ActionSheetTests.mm; sourceTree = "<group>"; };
isa = PBXGroup;
children = (
A13EBB441B87332B00097110 /* WebProcessPlugIn */,
+ F44D06481F3962E3001A0E29 /* EditingTestHarness.h */,
+ F44D06491F3962E3001A0E29 /* EditingTestHarness.mm */,
5C726D6D1D3EE06800C5E1A1 /* InstanceMethodSwizzler.h */,
5C726D6E1D3EE06800C5E1A1 /* InstanceMethodSwizzler.mm */,
0F139E721A423A2B00F590F5 /* PlatformUtilitiesCocoa.mm */,
2DC60E221E79F88C00FA6C7D /* DoAfterNextPresentationUpdateAfterCrash.mm */,
A1A4FE5D18DD3DB700B5EA8A /* Download.mm */,
A15502281E05020B00A24C57 /* DuplicateCompletionHandlerCalls.mm */,
+ F44D06461F395C4D001A0E29 /* EditorStateTests.mm */,
2D8104CB1BEC13E70020DA46 /* FindInPage.mm */,
2D1FE0AF1AD465C1006CD9E6 /* FixedLayoutSize.mm */,
CD78E11A1DB7EA360014A2DE /* FullscreenDelegate.mm */,
F4D5E4E71F0C5D27008C1A49 /* dragstart-clear-selection.html */,
A155022B1E050BC500A24C57 /* duplicate-completion-handler-calls.html */,
9984FACD1CFFB038008D198C /* editable-body.html */,
+ F44D06441F395C0D001A0E29 /* editor-state-test-harness.html */,
51C8E1A81F27F47300BF731B /* EmptyGrandfatheredResourceLoadStatistics.plist */,
F4C2AB211DD6D94100E06D5B /* enormous-video-with-sound.html */,
F407FE381F1D0DE60017CF25 /* enormous.svg */,
A155022A1E05020B00A24C57 /* DuplicateCompletionHandlerCalls.mm in Sources */,
7CCE7EBE1A411A7E00447C4C /* DynamicDeviceScaleFactor.mm in Sources */,
5C0BF8921DD599B600B00328 /* EarlyKVOCrash.mm in Sources */,
+ F44D064A1F3962F2001A0E29 /* EditingTestHarness.mm in Sources */,
7CCE7EE01A411A9A00447C4C /* EditorCommands.mm in Sources */,
+ F44D06471F39627A001A0E29 /* EditorStateTests.mm in Sources */,
7CCE7EBF1A411A7E00447C4C /* ElementAtPointInWebFrame.mm in Sources */,
07492B3B1DF8B14C00633DE1 /* EnumerateMediaDevices.cpp in Sources */,
448D7E471EA6C55500ECC756 /* EnvironmentUtilitiesTest.cpp in Sources */,
--- /dev/null
+/*
+ * Copyright (C) 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. 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.
+ */
+
+#include "config.h"
+
+#if WK_API_ENABLED
+
+#import "EditingTestHarness.h"
+#import "PlatformUtilities.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKWebViewPrivate.h>
+
+namespace TestWebKitAPI {
+
+static RetainPtr<EditingTestHarness> setUpEditorStateTestHarness()
+{
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+ auto testHarness = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
+ [webView synchronouslyLoadTestPageNamed:@"editor-state-test-harness"];
+ return testHarness;
+}
+
+TEST(EditorStateTests, TypingAttributesBold)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+
+ [testHarness insertHTML:@"<b>first</b>" andExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness toggleBold];
+ [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"bold": @NO }];
+ [testHarness insertHTML:@"<span style='font-weight: 700'> third</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness insertHTML:@"<span style='font-weight: 300'> fourth</span>" andExpectEditorStateWith:@{ @"bold": @NO }];
+ [testHarness insertHTML:@"<span style='font-weight: 800'> fifth</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness insertHTML:@"<span style='font-weight: 400'> sixth</span>" andExpectEditorStateWith:@{ @"bold": @NO }];
+ [testHarness insertHTML:@"<span style='font-weight: 900'> seventh</span>" andExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness toggleBold];
+ [testHarness insertText:@" eighth" andExpectEditorStateWith:@{ @"bold": @NO }];
+ [testHarness insertHTML:@"<strong> ninth</strong>" andExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @NO }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"bold": @NO }];
+ [testHarness selectAllAndExpectEditorStateWith:@{ @"bold": @YES }];
+ EXPECT_WK_STREQ("first second third fourth fifth sixth seventh eighth ninth", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
+}
+
+TEST(EditorStateTests, TypingAttributesItalic)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+
+ [testHarness insertHTML:@"<i>first</i>" andExpectEditorStateWith:@{ @"italic": @YES }];
+ [testHarness toggleItalic];
+ [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"italic": @NO }];
+ [testHarness insertHTML:@"<span style='font-style: italic'> third</span>" andExpectEditorStateWith:@{ @"italic": @YES }];
+ [testHarness toggleItalic];
+ [testHarness insertText:@" fourth" andExpectEditorStateWith:@{ @"italic": @NO }];
+ [testHarness toggleItalic];
+ [testHarness insertText:@" fifth" andExpectEditorStateWith:@{ @"italic": @YES }];
+ [testHarness insertHTML:@"<span style='font-style: normal'> sixth</span>" andExpectEditorStateWith:@{ @"italic": @NO }];
+ [testHarness insertHTML:@"<span style='font-style: oblique'> seventh</span>" andExpectEditorStateWith:@{ @"italic": @YES }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"italic": @YES }];
+ [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @NO }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"italic": @NO }];
+
+ [testHarness selectAllAndExpectEditorStateWith:@{ @"italic": @YES }];
+ EXPECT_WK_STREQ("first second third fourth fifth sixth seventh", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
+}
+
+TEST(EditorStateTests, TypingAttributesUnderline)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+
+ [testHarness insertHTML:@"<u>first</u>" andExpectEditorStateWith:@{ @"underline": @YES }];
+ [testHarness toggleUnderline];
+ [testHarness insertText:@" second" andExpectEditorStateWith:@{ @"underline": @NO }];
+ [testHarness insertHTML:@"<span style='text-decoration: underline'> third</span>" andExpectEditorStateWith:@{ @"underline": @YES }];
+ [testHarness insertHTML:@"<span style='text-decoration: line-through'> fourth</span>" andExpectEditorStateWith:@{ @"underline": @NO }];
+ [testHarness insertHTML:@"<span style='text-decoration: underline overline line-through'> fifth</span>" andExpectEditorStateWith:@{ @"underline": @YES }];
+ [testHarness insertHTML:@"<span style='text-decoration: none'> sixth</span>" andExpectEditorStateWith:@{ @"underline": @NO }];
+ [testHarness toggleUnderline];
+ [testHarness insertText:@" seventh" andExpectEditorStateWith:@{ @"underline": @YES }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"underline": @YES }];
+ [testHarness deleteBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @NO }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @YES }];
+ [testHarness moveWordBackwardAndExpectEditorStateWith:@{ @"underline": @NO }];
+
+ [testHarness selectAllAndExpectEditorStateWith:@{ @"underline": @YES }];
+ EXPECT_WK_STREQ("first second third fourth fifth sixth seventh", [[testHarness webView] stringByEvaluatingJavaScript:@"getSelection().toString()"]);
+}
+
+TEST(EditorStateTests, TypingAttributesTextAlignmentAbsoluteAlignmentOptions)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+ TestWKWebView *webView = [testHarness webView];
+
+ [webView stringByEvaluatingJavaScript:@"document.body.style.direction = 'ltr'"];
+
+ [testHarness insertHTML:@"<div style='text-align: right;'>right</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+
+ [testHarness insertText:@"justified" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness alignJustifiedAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentJustified) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentJustified) }];
+
+ [testHarness alignCenterAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
+ [testHarness insertText:@"center" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
+
+ [testHarness insertHTML:@"<span id='left'>left</span>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
+ [webView stringByEvaluatingJavaScript:@"getSelection().setBaseAndExtent(left.childNodes[0], 0, left.childNodes[0], 6)"];
+ [testHarness alignLeftAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+
+ [testHarness selectAllAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ EXPECT_WK_STREQ("right\njustified\ncenter\nleft", [webView stringByEvaluatingJavaScript:@"getSelection().toString()"]);
+}
+
+TEST(EditorStateTests, TypingAttributesTextAlignmentStartEnd)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+ TestWKWebView *webView = [testHarness webView];
+
+ [webView stringByEvaluatingJavaScript:@"document.styleSheets[0].insertRule('.start { text-align: start; }')"];
+ [webView stringByEvaluatingJavaScript:@"document.styleSheets[0].insertRule('.end { text-align: end; }')"];
+ [webView stringByEvaluatingJavaScript:@"document.body.style.direction = 'rtl'"];
+
+ [testHarness insertHTML:@"<div class='start'>rtl start</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+
+ [testHarness insertHTML:@"<div class='end'>rtl end</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+
+ [[testHarness webView] stringByEvaluatingJavaScript:@"document.body.style.direction = 'ltr'"];
+ [testHarness insertHTML:@"<div class='start'>ltr start</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+
+ [testHarness insertHTML:@"<div class='end'>ltr end</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+}
+
+TEST(EditorStateTests, TypingAttributesTextAlignmentDirectionalText)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+ [[testHarness webView] stringByEvaluatingJavaScript:@"document.body.setAttribute('dir', 'auto')"];
+
+ [testHarness insertHTML:@"<div>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness insertHTML:@"<div dir='ltr'>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+ [testHarness insertHTML:@"<div dir='rtl'>מקור השם עברית</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+
+ [testHarness insertHTML:@"<div dir='auto'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+ [testHarness insertHTML:@"<div dir='rtl'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentRight) }];
+ [testHarness insertHTML:@"<div dir='ltr'>This is English text</div>" andExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentLeft) }];
+}
+
+TEST(EditorStateTests, TypingAttributesTextColor)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+
+ [testHarness setForegroundColor:@"rgb(255, 0, 0)"];
+ [testHarness insertText:@"red" andExpectEditorStateWith:@{ @"text-color": @"rgb(255, 0, 0)" }];
+
+ [testHarness insertHTML:@"<span style='color: rgb(0, 255, 0)'>green</span>" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 255, 0)" }];
+ [testHarness insertParagraphAndExpectEditorStateWith:@{ @"text-color": @"rgb(0, 255, 0)" }];
+
+ [testHarness setForegroundColor:@"rgb(0, 0, 255)"];
+ [testHarness insertText:@"blue" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 255)" }];
+}
+
+TEST(EditorStateTests, TypingAttributesMixedStyles)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+
+ [testHarness alignCenterAndExpectEditorStateWith:@{ @"text-alignment": @(NSTextAlignmentCenter) }];
+ [testHarness setForegroundColor:@"rgb(128, 128, 128)"];
+ [testHarness toggleBold];
+ [testHarness toggleItalic];
+ [testHarness toggleUnderline];
+ NSDictionary *expectedAttributes = @{
+ @"bold": @YES,
+ @"italic": @YES,
+ @"underline": @YES,
+ @"text-color": @"rgb(128, 128, 128)",
+ @"text-alignment": @(NSTextAlignmentCenter)
+ };
+ BOOL containsProperties = [testHarness latestEditorStateContains:expectedAttributes];
+ EXPECT_TRUE(containsProperties);
+ if (!containsProperties)
+ NSLog(@"Expected %@ to contain %@", [testHarness latestEditorState], expectedAttributes);
+}
+
+TEST(EditorStateTests, TypingAttributeLinkColor)
+{
+ auto testHarness = setUpEditorStateTestHarness();
+ [testHarness insertHTML:@"<a href='https://www.apple.com/'>This is a link</a>" andExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 238)" }];
+ [testHarness selectAllAndExpectEditorStateWith:@{ @"text-color": @"rgb(0, 0, 238)" }];
+ EXPECT_WK_STREQ("https://www.apple.com/", [[testHarness webView] stringByEvaluatingJavaScript:@"document.querySelector('a').href"]);
+}
+
+} // namespace TestWebKitAPI
+
+#endif // WK_API_ENABLED
--- /dev/null
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<style>
+body, html {
+ font-family: -apple-system;
+ font-size: 1em;
+ width: 100%;
+ height: 100%;
+}
+</style>
+<body contenteditable></body>
+<script>
+document.body.focus();
+</script>