[iOS] Paste is missing from callout bar when pasteboard only contains custom data
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 18 Aug 2018 22:59:20 +0000 (22:59 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 18 Aug 2018 22:59:20 +0000 (22:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=184271
<rdar://problem/39256708>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Export a couple of WebCore functions for use in WebKit.

* dom/Document.h:
* platform/Pasteboard.h:

Source/WebKit:

Currently, the "paste:" selector action cannot be performed during editing if the pasteboard only contains
custom pasteboard data. This is because logic in -[WKContentView canPerformActionForWebView:withSender:] only
checks against a list of pasteboard types which does not include the type identifier for custom pasteboard data.
To fix this, we allow pasting only in the case where none of the other type identifiers exist in the pasteboard,
as long as the custom pasteboard data type identifier is present, and the custom pasteboard data's origin
matches the origin of the focused frame's document.

Test: PasteMixedContent.CopyAndPasteWithCustomPasteboardDataOnly

* Shared/EditorState.cpp:
(WebKit::EditorState::encode const):
(WebKit::EditorState::decode):
* Shared/EditorState.h:

Add a originIdentifierForPasteboard field, and add support for encoding it when propagating EditorState via IPC.

* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView canPerformActionForWebView:withSender:]):

If none of the conventional pasteboard type identifiers for rich or plain text editing are present, check to see
if we have custom pasteboard data; if so, only allow pasting if the custom pasteboard data's origin matches that
of the focused frame's document origin.

Additionally refactor a bit of logic by pulling out `_page->editorState()` into a separate local variable, used
throughout the rest of the method.

* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::editorState const):

Send the focused frame's document origin to the UI process via EditorState.

Tools:

Add a new API test to verify that on both iOS and macOS, copied custom pasteboard data can only be pasted in a
matching origin. Additionally verify that on iOS, the web view is capable of performing the "paste:" selector.

* TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm:
(TestWebKitAPI::TEST):
(imagePath): Deleted.
(writeTypesAndDataToPasteboard): Deleted.
(setUpWebView): Deleted.
(markupString): Deleted.

Make this test file no longer exclusive to macOS.

* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[TestWKWebView synchronouslyLoadHTMLString:baseURL:]):

Tweak this helper to also take in a `baseURL`. Defaults to using the TestWebKitAPI bundle resource URL.

(-[TestWKWebView synchronouslyLoadHTMLString:]):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@235011 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.h
Source/WebCore/platform/Pasteboard.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/EditorState.cpp
Source/WebKit/Shared/EditorState.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm
Tools/TestWebKitAPI/cocoa/TestWKWebView.h
Tools/TestWebKitAPI/cocoa/TestWKWebView.mm

index 881286f..a518c8f 100644 (file)
@@ -1,3 +1,16 @@
+2018-08-18  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] Paste is missing from callout bar when pasteboard only contains custom data
+        https://bugs.webkit.org/show_bug.cgi?id=184271
+        <rdar://problem/39256708>
+
+        Reviewed by Ryosuke Niwa.
+
+        Export a couple of WebCore functions for use in WebKit.
+
+        * dom/Document.h:
+        * platform/Pasteboard.h:
+
 2018-08-17  Ryosuke Niwa  <rniwa@webkit.org>
 
         Pack booleans in Event into a bitfield
index 2e3afc2..d79a44e 100644 (file)
@@ -1006,7 +1006,7 @@ public:
     void incDOMTreeVersion() { m_domTreeVersion = ++s_globalTreeVersion; }
     uint64_t domTreeVersion() const { return m_domTreeVersion; }
 
-    String originIdentifierForPasteboard();
+    WEBCORE_EXPORT String originIdentifierForPasteboard();
 
     // XPathEvaluator methods
     WEBCORE_EXPORT ExceptionOr<Ref<XPathExpression>> createExpression(const String& expression, RefPtr<XPathNSResolver>&&);
index 111f3af..d5433dd 100644 (file)
@@ -170,7 +170,7 @@ struct PasteboardCustomData {
     WEBCORE_EXPORT static PasteboardCustomData fromSharedBuffer(const SharedBuffer&);
 
 #if PLATFORM(COCOA)
-    static const char* cocoaType();
+    WEBCORE_EXPORT static const char* cocoaType();
 #endif
 };
 
index 86ce120..f09c645 100644 (file)
@@ -1,3 +1,42 @@
+2018-08-18  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] Paste is missing from callout bar when pasteboard only contains custom data
+        https://bugs.webkit.org/show_bug.cgi?id=184271
+        <rdar://problem/39256708>
+
+        Reviewed by Ryosuke Niwa.
+
+        Currently, the "paste:" selector action cannot be performed during editing if the pasteboard only contains
+        custom pasteboard data. This is because logic in -[WKContentView canPerformActionForWebView:withSender:] only
+        checks against a list of pasteboard types which does not include the type identifier for custom pasteboard data.
+        To fix this, we allow pasting only in the case where none of the other type identifiers exist in the pasteboard,
+        as long as the custom pasteboard data type identifier is present, and the custom pasteboard data's origin
+        matches the origin of the focused frame's document.
+
+        Test: PasteMixedContent.CopyAndPasteWithCustomPasteboardDataOnly
+
+        * Shared/EditorState.cpp:
+        (WebKit::EditorState::encode const):
+        (WebKit::EditorState::decode):
+        * Shared/EditorState.h:
+
+        Add a originIdentifierForPasteboard field, and add support for encoding it when propagating EditorState via IPC.
+
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView canPerformActionForWebView:withSender:]):
+
+        If none of the conventional pasteboard type identifiers for rich or plain text editing are present, check to see
+        if we have custom pasteboard data; if so, only allow pasting if the custom pasteboard data's origin matches that
+        of the focused frame's document origin.
+
+        Additionally refactor a bit of logic by pulling out `_page->editorState()` into a separate local variable, used
+        throughout the rest of the method.
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::editorState const):
+
+        Send the focused frame's document origin to the UI process via EditorState.
+
 2018-08-17  Tim Horton  <timothy_horton@apple.com>
 
         Start bringing up Unified Sources in WebKit2
index 02ca367..2dfa518 100644 (file)
@@ -53,6 +53,8 @@ void EditorState::encode(IPC::Encoder& encoder) const
     encoder << lastMarkedRect;
     encoder << markedText;
 #endif
+
+    encoder << originIdentifierForPasteboard;
 }
 
 bool EditorState::decode(IPC::Decoder& decoder, EditorState& result)
@@ -98,6 +100,9 @@ bool EditorState::decode(IPC::Decoder& decoder, EditorState& result)
         return false;
 #endif
 
+    if (!decoder.decode(result.originIdentifierForPasteboard))
+        return false;
+
     return true;
 }
 
index a2173a4..2085c15 100644 (file)
@@ -80,6 +80,8 @@ struct EditorState {
     String markedText;
 #endif
 
+    String originIdentifierForPasteboard;
+
     struct PostLayoutData {
         uint32_t typingAttributes { AttributeNone };
 #if PLATFORM(IOS) || PLATFORM(GTK)
index 470741f..e829d45 100644 (file)
@@ -2276,35 +2276,49 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
     if (action == @selector(_arrowKey:))
         return [self isFirstResponder];
         
+    auto editorState = _page->editorState();
     if (action == @selector(_showTextStyleOptions:))
-        return _page->editorState().isContentRichlyEditable && _page->editorState().selectionIsRange && !_showingTextStyleOptions;
+        return editorState.isContentRichlyEditable && editorState.selectionIsRange && !_showingTextStyleOptions;
     if (_showingTextStyleOptions)
         return (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:));
     if (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:))
-        return _page->editorState().isContentRichlyEditable;
+        return editorState.isContentRichlyEditable;
     if (action == @selector(cut:))
-        return !_page->editorState().isInPasswordField && _page->editorState().isContentEditable && _page->editorState().selectionIsRange;
+        return !editorState.isInPasswordField && editorState.isContentEditable && editorState.selectionIsRange;
     
     if (action == @selector(paste:)) {
-        if (_page->editorState().selectionIsNone || !_page->editorState().isContentEditable)
+        if (editorState.selectionIsNone || !editorState.isContentEditable)
             return NO;
         UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
         NSArray *types = [self supportedPasteboardTypesForCurrentSelection];
         NSIndexSet *indices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pasteboard numberOfItems])];
-        return [pasteboard containsPasteboardTypes:types inItemSet:indices];
+        if ([pasteboard containsPasteboardTypes:types inItemSet:indices])
+            return YES;
+
+        auto focusedDocumentOrigin = editorState.originIdentifierForPasteboard;
+        if (focusedDocumentOrigin.isEmpty())
+            return NO;
+
+        NSArray *allCustomPasteboardData = [pasteboard dataForPasteboardType:@(PasteboardCustomData::cocoaType()) inItemSet:indices];
+        for (NSData *data in allCustomPasteboardData) {
+            auto buffer = SharedBuffer::create(data);
+            if (PasteboardCustomData::fromSharedBuffer(buffer.get()).origin == focusedDocumentOrigin)
+                return YES;
+        }
+        return NO;
     }
 
     if (action == @selector(copy:)) {
-        if (_page->editorState().isInPasswordField)
+        if (editorState.isInPasswordField)
             return NO;
-        return _page->editorState().selectionIsRange;
+        return editorState.selectionIsRange;
     }
 
     if (action == @selector(_define:)) {
-        if (_page->editorState().isInPasswordField || !_page->editorState().selectionIsRange)
+        if (editorState.isInPasswordField || !editorState.selectionIsRange)
             return NO;
 
-        NSUInteger textLength = _page->editorState().postLayoutData().selectedTextLength;
+        NSUInteger textLength = editorState.postLayoutData().selectedTextLength;
         // FIXME: We should be calling UIReferenceLibraryViewController to check if the length is
         // acceptable, but the interface takes a string.
         // <rdar://problem/15254406>
@@ -2320,7 +2334,7 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
     }
 
     if (action == @selector(_lookup:)) {
-        if (_page->editorState().isInPasswordField)
+        if (editorState.isInPasswordField)
             return NO;
 
 #if !PLATFORM(IOSMAC)
@@ -2328,18 +2342,18 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
             return NO;
 #endif
 
-        return _page->editorState().selectionIsRange;
+        return editorState.selectionIsRange;
     }
 
     if (action == @selector(_share:)) {
-        if (_page->editorState().isInPasswordField || !_page->editorState().selectionIsRange)
+        if (editorState.isInPasswordField || !editorState.selectionIsRange)
             return NO;
 
-        return _page->editorState().postLayoutData().selectedTextLength > 0;
+        return editorState.postLayoutData().selectedTextLength > 0;
     }
 
     if (action == @selector(_addShortcut:)) {
-        if (_page->editorState().isInPasswordField || !_page->editorState().selectionIsRange)
+        if (editorState.isInPasswordField || !editorState.selectionIsRange)
             return NO;
 
         NSString *selectedText = [self selectedText];
@@ -2354,7 +2368,7 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
     }
 
     if (action == @selector(_promptForReplace:)) {
-        if (!_page->editorState().selectionIsRange || !_page->editorState().postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
+        if (!editorState.selectionIsRange || !editorState.postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
             return NO;
         if ([[self selectedText] _containsCJScriptsOnly])
             return NO;
@@ -2362,24 +2376,24 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
     }
 
     if (action == @selector(_transliterateChinese:)) {
-        if (!_page->editorState().selectionIsRange || !_page->editorState().postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
+        if (!editorState.selectionIsRange || !editorState.postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
             return NO;
         return UIKeyboardEnabledInputModesAllowChineseTransliterationForText([self selectedText]);
     }
 
     if (action == @selector(select:)) {
         // Disable select in password fields so that you can't see word boundaries.
-        return !_page->editorState().isInPasswordField && [self hasContent] && !_page->editorState().selectionIsNone && !_page->editorState().selectionIsRange;
+        return !editorState.isInPasswordField && [self hasContent] && !editorState.selectionIsNone && !editorState.selectionIsRange;
     }
 
     if (action == @selector(selectAll:)) {
-        if (!_page->editorState().selectionIsNone && !_page->editorState().selectionIsRange)
+        if (!editorState.selectionIsNone && !editorState.selectionIsRange)
             return YES;
         return NO;
     }
 
     if (action == @selector(replace:))
-        return _page->editorState().isContentEditable && !_page->editorState().isInPasswordField;
+        return editorState.isContentEditable && !editorState.isInPasswordField;
 
     return [super canPerformAction:action withSender:sender];
 }
index dff88dc..82fff49 100644 (file)
@@ -921,6 +921,9 @@ EditorState WebPage::editorState(IncludePostLayoutDataHint shouldIncludePostLayo
     result.hasComposition = editor.hasComposition();
     result.shouldIgnoreSelectionChanges = editor.ignoreSelectionChanges();
 
+    if (auto* document = frame.document())
+        result.originIdentifierForPasteboard = document->originIdentifierForPasteboard();
+
     bool canIncludePostLayoutData = frame.view() && !frame.view()->needsLayout();
     if (shouldIncludePostLayoutData == IncludePostLayoutDataHint::Yes && canIncludePostLayoutData) {
         auto& postLayoutData = result.postLayoutData();
index 823737e..773c9ac 100644 (file)
@@ -1,3 +1,31 @@
+2018-08-18  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] Paste is missing from callout bar when pasteboard only contains custom data
+        https://bugs.webkit.org/show_bug.cgi?id=184271
+        <rdar://problem/39256708>
+
+        Reviewed by Ryosuke Niwa.
+
+        Add a new API test to verify that on both iOS and macOS, copied custom pasteboard data can only be pasted in a
+        matching origin. Additionally verify that on iOS, the web view is capable of performing the "paste:" selector.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm:
+        (TestWebKitAPI::TEST):
+        (imagePath): Deleted.
+        (writeTypesAndDataToPasteboard): Deleted.
+        (setUpWebView): Deleted.
+        (markupString): Deleted.
+
+        Make this test file no longer exclusive to macOS.
+
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[TestWKWebView synchronouslyLoadHTMLString:baseURL:]):
+
+        Tweak this helper to also take in a `baseURL`. Defaults to using the TestWebKitAPI bundle resource URL.
+
+        (-[TestWKWebView synchronouslyLoadHTMLString:]):
+
 2018-08-17  Jonathan Bedard  <jbedard@apple.com>
 
         Add back --wtf-only to run-api-tests
index 9f4ae5f..b5fb481 100644 (file)
 
 #include "config.h"
 
-#if PLATFORM(MAC) && WK_API_ENABLED
+#if WK_API_ENABLED
 
 #import "PlatformUtilities.h"
+#import "TestURLSchemeHandler.h"
 #import "TestWKWebView.h"
 #import <WebKit/WKPreferencesRef.h>
 #import <WebKit/WKPreferencesRefPrivate.h>
+#import <WebKit/WKWebViewPrivate.h>
 #import <wtf/RetainPtr.h>
 #import <wtf/text/WTFString.h>
 
+namespace TestWebKitAPI {
+
+#if PLATFORM(MAC)
+
 static NSString *imagePath()
 {
     return [[NSBundle mainBundle] pathForResource:@"icon" ofType:@"png" inDirectory:@"TestWebKitAPI.resources"];
@@ -96,8 +102,6 @@ static NSString *markupString()
     return @"<script>foo()</script><strong onmouseover='javascript:void(0)'>HELLO WORLD</strong>";
 }
 
-namespace TestWebKitAPI {
-
 TEST(PasteMixedContent, ImageFileAndPlainText)
 {
     auto webView = setUpWebView();
@@ -251,6 +255,39 @@ TEST(PasteMixedContent, PasteURLWrittenToPasteboardUsingWriteObjects)
     EXPECT_WK_STREQ([webView stringByEvaluatingJavaScript:@"document.querySelector('a').textContent"], urlToCopy);
 }
 
+#endif // PLATFORM(MAC)
+
+TEST(PasteMixedContent, CopyAndPasteWithCustomPasteboardDataOnly)
+{
+    NSString *markupForSource = @"<body oncopy=\"event.preventDefault(); event.clipboardData.setData('foo', 'bar')\">hello</body>";
+    NSString *markupForDestination = @"<input autofocus onpaste=\"event.preventDefault(); this.value = event.clipboardData.getData('foo')\">";
+
+    auto schemeHandler = adoptNS([[TestURLSchemeHandler alloc] init]);
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [configuration setURLSchemeHandler:schemeHandler.get() forURLScheme:@"same"];
+    [configuration setURLSchemeHandler:schemeHandler.get() forURLScheme:@"different"];
+
+    auto source = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400) configuration:configuration.get()]);
+    [source synchronouslyLoadHTMLString:markupForSource baseURL:[NSURL URLWithString:@"same://"]];
+    [source selectAll:nil];
+    [source _executeEditCommand:@"copy" argument:nil completion:nil];
+
+    auto destination = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400) configuration:configuration.get()]);
+    [destination synchronouslyLoadHTMLString:markupForDestination baseURL:[NSURL URLWithString:@"same://"]];
+    [destination paste:nil];
+    EXPECT_WK_STREQ("bar", [destination stringByEvaluatingJavaScript:@"document.querySelector('input').value"]);
+#if PLATFORM(IOS)
+    EXPECT_TRUE([destination canPerformAction:@selector(paste:) withSender:nil]);
+#endif
+
+    [destination synchronouslyLoadHTMLString:markupForDestination baseURL:[NSURL URLWithString:@"different://"]];
+    [destination paste:nil];
+    EXPECT_WK_STREQ("", [destination stringByEvaluatingJavaScript:@"document.querySelector('input').value"]);
+#if PLATFORM(IOS)
+    EXPECT_FALSE([destination canPerformAction:@selector(paste:) withSender:nil]);
+#endif
+}
+
 } // namespace TestWebKitAPI
 
-#endif // PLATFORM(MAC) && WK_API_ENABLED
+#endif // WK_API_ENABLED
index 051fa29..613dfae 100644 (file)
@@ -51,6 +51,7 @@
 - (void)performAfterReceivingMessage:(NSString *)message action:(dispatch_block_t)action;
 - (void)loadTestPageNamed:(NSString *)pageName;
 - (void)synchronouslyLoadHTMLString:(NSString *)html;
+- (void)synchronouslyLoadHTMLString:(NSString *)html baseURL:(NSURL *)url;
 - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName;
 - (id)objectByEvaluatingJavaScript:(NSString *)script;
 - (NSString *)stringByEvaluatingJavaScript:(NSString *)script;
index d096cf0..5f2ca53 100644 (file)
@@ -246,13 +246,17 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
     [self loadRequest:request];
 }
 
-- (void)synchronouslyLoadHTMLString:(NSString *)html
+- (void)synchronouslyLoadHTMLString:(NSString *)html baseURL:(NSURL *)url
 {
-    NSURL *testResourceURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"TestWebKitAPI.resources"];
-    [self loadHTMLString:html baseURL:testResourceURL];
+    [self loadHTMLString:html baseURL:url];
     [self _test_waitForDidFinishNavigation];
 }
 
+- (void)synchronouslyLoadHTMLString:(NSString *)html
+{
+    [self synchronouslyLoadHTMLString:html baseURL:[[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"TestWebKitAPI.resources"]];
+}
+
 - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName
 {
     [self loadTestPageNamed:pageName];