Allow pages to trigger programmatic paste from script on iOS
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 12 Feb 2019 22:37:48 +0000 (22:37 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 12 Feb 2019 22:37:48 +0000 (22:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=194271
<rdar://problem/47808810>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Add support for allowing script to trigger programmatic paste commands. Currently on macOS and iOS, the ability
to trigger programmatic paste (i.e. `document.execCommand('Paste');`) is disabled by default, such that
execCommand is simply a no-op that returns false. This policy is a privacy measure (common among other major
browsers) that prevents untrusted web content from sniffing content from the system pasteboard (even on user
interaction, since unintended user interaction occasionally happens as well!).

In order to make it possible for web pages to programmatically paste without opening the door to privacy and
security issues, we make paste commands triggered from bindings present platform UI on iOS, in the form of a
callout bar with the single option to paste. This UI is dismissed upon any user interaction; furthermore, any
user interaction short of explicitly triggering the "Paste" action subsequently prevents the page from executing
the paste (and causes execCommand to return false). However, if the paste action is chosen by the user, we
instead follow through with the programmatic paste command.

New tests to come in a followup patch.

* WebCore.xcodeproj/project.pbxproj:
* dom/DOMPasteAccessPolicy.h: Added.
* dom/UserGestureIndicator.h:
(WebCore::UserGestureToken::domPasteAccessPolicy const):
(WebCore::UserGestureToken::didRequestDOMPasteAccess):

Add helpers on UserGestureToken to update and query the current DOM paste access policy. The access policies are
"NotRequestedYet" (i.e. pending a response from the user), "Granted" (the user has granted DOM paste access to
the page), or "Denied" (the user has prevented the page from reading the contents of the clipboard). When DOM
paste access is granted or rejected, make this decision sticky until the end of the current user gesture.

* editing/EditorCommand.cpp:
(WebCore::executePaste):
(WebCore::executePasteAndMatchStyle):
(WebCore::executePasteAsPlainText):
(WebCore::executePasteAsQuotation):

When executing a paste command where the source is DOM bindings, request DOM paste if needed before proceeding
with the paste.

(WebCore::supportedPaste):
* loader/EmptyClients.cpp:
* page/EditorClient.h:
* page/Frame.cpp:
(WebCore::Frame::requestDOMPasteAccess):

Add a helper method that requests access to the clipboard on behalf of script when pasting.

* page/Frame.h:
* page/Settings.yaml:

Introduce a new WebCore setting, used to gate DOM paste access requests.

Source/WebKit:

* Shared/WebPreferences.yaml:
* Shared/WebPreferencesDefaultValues.h:

Add an internal setting to enable or disable DOM paste access requests. This is on by default in iOS only
(excluding watchOS and Apple TV), and is additionally disabled on macOS.

* UIProcess/API/gtk/PageClientImpl.cpp:
(WebKit::PageClientImpl::requestDOMPasteAccess):
* UIProcess/API/gtk/PageClientImpl.h:
* UIProcess/API/wpe/PageClientImpl.cpp:
(WebKit::PageClientImpl::requestDOMPasteAccess):

Plumb DOM paste access requests from the web process (WebEditorClient) to the view (WKContentView). As per the
usual, this involves WebEditorClient, WebPage, WebPageProxy, PageClient and finally WKContentView.

* UIProcess/API/wpe/PageClientImpl.h:
* UIProcess/PageClient.h:
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::requestDOMPasteAccess):
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* UIProcess/ios/PageClientImplIOS.h:
* UIProcess/ios/PageClientImplIOS.mm:
(WebKit::PageClientImpl::requestDOMPasteAccess):
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView setupInteraction]):
(-[WKContentView cleanupInteraction]):
(-[WKContentView resignFirstResponderForWebView]):
(-[WKContentView _webTouchEventsRecognized:]):

Bail from any pending DOM paste access handler the moment we start handling touches on the web view, or if the
web view resigns first responder, or if the web process crashes.

(-[WKContentView textInteractionGesture:shouldBeginAtPoint:]):

Reject text selection gestures while waiting for DOM paste access.

(-[WKContentView canPerformAction:withSender:]):
(-[WKContentView canPerformActionForWebView:withSender:]):

If we're handling a DOM paste, always return YES to allow the callout bar to show the "Paste" option.

(-[WKContentView _didHideMenu:]):

If the menu is programmatically hidden by the app while handling a DOM paste request, immediately reject the DOM
paste request.

(-[WKContentView pasteForWebView:]):

Adjust -pasteForWebView: on WKContentView to first check whether there's an outstanding DOM paste completion
handler to invoke, instead of telling the page to execute a paste command.

(-[WKContentView _handleDOMPasteRequestWithResult:]):

Add a helper to take and invoke the current DOM paste completion handler (if it exists) with the given result,
and then dismiss the shared callout bar. Returns whether or not the paste completion handler exists. Invoked
from various sources of user interaction or significant state changes (e.g. following a web process crash in
-cleanupInteraction).

(-[WKContentView _willPerformAction:sender:]):
(-[WKContentView _didPerformAction:sender:]):

Add hooks to detect when WKContentView is executing an editing action. This is to ensure that the page doesn't
get stuck in a bad state in the case where WKWebView has been subclassed, overrides `-paste:`, and does not
invoke the superclass method (which calls back into `-[WKContentView pasteForWebView:]`). There are a few
possibilities here:
1. WKWebView's `-paste:` action is not overridden. In this case, we will call back into `-pasteForWebView:`,
   which will notice that we have a pending paste completion handler and invoke it.
2. WKWebView's `-paste:` action is overridden and does not call back into the content view. In this case, we
   will invoke the paste completion handler in `-_didPerformAction:sender:`.
3. WKWebView's `-canPerformAction:withSender:` is overridden to include additional actions. In this case, we may
   get a call to invoke a different action selector while waiting for a potential paste action. If this happens,
   prevent the DOM paste in `-_willPerformAction:sender:` prior to handling the other action.

(-[WKContentView handleKeyWebEvent:withCompletionHandler:]):

Dismiss DOM paste UI upon handling any key event.

(-[WKContentView showGlobalMenuControllerInRect:]):
(-[WKContentView hideGlobalMenuController]):

Helper methods to present and dismiss the global UIMenuController, that accounts for available platform APIs for
presenting or dismissing the menu controller on iOS.

(-[WKContentView _requestDOMPasteAccessWithElementRect:completionHandler:]):

Attempt to find a good target presentation rect when showing the callout menu. First, we will try to use the
rect of the element the user has interacted with when triggering the paste. If such an element is too large or
does not exist, we fall back to presenting the callout menu near the user's last touch location (with a small
amount of margin, such that the action doesn't overlap with the user's finger, stylus, etc.).

(-[WKContentView _resetShowingTextStyle:]): Deleted.

Rename this to `-_didHideMenu:`.

* UIProcess/mac/PageClientImplMac.h:
* UIProcess/win/PageClientImpl.cpp:
(WebKit::PageClientImpl::requestDOMPasteAccess):
* UIProcess/win/PageClientImpl.h:
* WebProcess/WebCoreSupport/WebEditorClient.cpp:
(WebKit::WebEditorClient::requestDOMPasteAccess):
* WebProcess/WebCoreSupport/WebEditorClient.h:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::requestDOMPasteAccess):

Add more plumbing and method stubs.

(WebKit::WebPage::updateCurrentModifierState):
(WebKit::WebPage::rectForElementAtInteractionLocation const):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::rectForElementAtInteractionLocation const):
(WebKit::WebPage::rectForElementAtInteractionLocation): Deleted.

Mark this method as const, add a platform-agnostic stub, and adopt it for the purposes of determining where to
position the callout bar when pasting.

Source/WebKitLegacy/mac:

See WebCore and WebKit ChangeLogs for more details.

* WebCoreSupport/WebEditorClient.h:

Source/WebKitLegacy/win:

* WebCoreSupport/WebEditorClient.h:

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

37 files changed:
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/DOMPasteAccessPolicy.h [new file with mode: 0644]
Source/WebCore/dom/UserGestureIndicator.h
Source/WebCore/editing/EditorCommand.cpp
Source/WebCore/loader/EmptyClients.cpp
Source/WebCore/page/EditorClient.h
Source/WebCore/page/Frame.cpp
Source/WebCore/page/Frame.h
Source/WebCore/page/Settings.yaml
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebPreferences.yaml
Source/WebKit/Shared/WebPreferencesDefaultValues.h
Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp
Source/WebKit/UIProcess/API/gtk/PageClientImpl.h
Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp
Source/WebKit/UIProcess/API/wpe/PageClientImpl.h
Source/WebKit/UIProcess/PageClient.h
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebPageProxy.messages.in
Source/WebKit/UIProcess/ios/PageClientImplIOS.h
Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/UIProcess/mac/PageClientImplMac.h
Source/WebKit/UIProcess/win/PageClientImpl.cpp
Source/WebKit/UIProcess/win/PageClientImpl.h
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h
Source/WebKitLegacy/win/ChangeLog
Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h

index b3f96ee..4f32b97 100644 (file)
@@ -1,3 +1,59 @@
+2019-02-12  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Allow pages to trigger programmatic paste from script on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=194271
+        <rdar://problem/47808810>
+
+        Reviewed by Ryosuke Niwa.
+
+        Add support for allowing script to trigger programmatic paste commands. Currently on macOS and iOS, the ability
+        to trigger programmatic paste (i.e. `document.execCommand('Paste');`) is disabled by default, such that
+        execCommand is simply a no-op that returns false. This policy is a privacy measure (common among other major
+        browsers) that prevents untrusted web content from sniffing content from the system pasteboard (even on user
+        interaction, since unintended user interaction occasionally happens as well!).
+
+        In order to make it possible for web pages to programmatically paste without opening the door to privacy and
+        security issues, we make paste commands triggered from bindings present platform UI on iOS, in the form of a
+        callout bar with the single option to paste. This UI is dismissed upon any user interaction; furthermore, any
+        user interaction short of explicitly triggering the "Paste" action subsequently prevents the page from executing
+        the paste (and causes execCommand to return false). However, if the paste action is chosen by the user, we
+        instead follow through with the programmatic paste command.
+
+        New tests to come in a followup patch.
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/DOMPasteAccessPolicy.h: Added.
+        * dom/UserGestureIndicator.h:
+        (WebCore::UserGestureToken::domPasteAccessPolicy const):
+        (WebCore::UserGestureToken::didRequestDOMPasteAccess):
+
+        Add helpers on UserGestureToken to update and query the current DOM paste access policy. The access policies are
+        "NotRequestedYet" (i.e. pending a response from the user), "Granted" (the user has granted DOM paste access to
+        the page), or "Denied" (the user has prevented the page from reading the contents of the clipboard). When DOM
+        paste access is granted or rejected, make this decision sticky until the end of the current user gesture.
+
+        * editing/EditorCommand.cpp:
+        (WebCore::executePaste):
+        (WebCore::executePasteAndMatchStyle):
+        (WebCore::executePasteAsPlainText):
+        (WebCore::executePasteAsQuotation):
+
+        When executing a paste command where the source is DOM bindings, request DOM paste if needed before proceeding
+        with the paste.
+
+        (WebCore::supportedPaste):
+        * loader/EmptyClients.cpp:
+        * page/EditorClient.h:
+        * page/Frame.cpp:
+        (WebCore::Frame::requestDOMPasteAccess):
+
+        Add a helper method that requests access to the clipboard on behalf of script when pasting.
+
+        * page/Frame.h:
+        * page/Settings.yaml:
+
+        Introduce a new WebCore setting, used to gate DOM paste access requests.
+
 2019-02-12  Alex Christensen  <achristensen@webkit.org>
 
         Remove setDefersLoading infrastructure from WebKit2
index 9162abc..571fc37 100644 (file)
                F48D2A7E2157182600C6752B /* FontAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = F48D2A712156DC0A00C6752B /* FontAttributes.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F48D2AA52159740D00C6752B /* ColorCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = F48D2AA32159740D00C6752B /* ColorCocoa.h */; };
                F49786881FF45FA500E060AB /* PasteboardItemInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = F49786871FF45FA500E060AB /* PasteboardItemInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               F4B422C4220C0568009E1E7D /* DOMPasteAccessPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = F4B422C2220C0000009E1E7D /* DOMPasteAccessPolicy.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F4BFB9851E1DDF9B00862C24 /* DumpEditingHistory.js in Copy Scripts */ = {isa = PBXBuildFile; fileRef = F48389831E1DDF2B0076B7EA /* DumpEditingHistory.js */; };
                F4BFB9861E1DDF9B00862C24 /* EditingHistoryUtil.js in Copy Scripts */ = {isa = PBXBuildFile; fileRef = F48389841E1DDF2B0076B7EA /* EditingHistoryUtil.js */; };
                F4D43D662188038B00ECECAC /* SerializedAttachmentData.h in Headers */ = {isa = PBXBuildFile; fileRef = F4D43D64218802E600ECECAC /* SerializedAttachmentData.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F48D2AA42159740D00C6752B /* ColorCocoa.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ColorCocoa.mm; sourceTree = "<group>"; };
                F49786871FF45FA500E060AB /* PasteboardItemInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PasteboardItemInfo.h; sourceTree = "<group>"; };
                F49E98E421DEE6C1009AE55E /* EditAction.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EditAction.cpp; sourceTree = "<group>"; };
+               F4B422C2220C0000009E1E7D /* DOMPasteAccessPolicy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DOMPasteAccessPolicy.h; sourceTree = "<group>"; };
                F4D43D64218802E600ECECAC /* SerializedAttachmentData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SerializedAttachmentData.h; sourceTree = "<group>"; };
                F4D9817D2195FBF6008230FC /* ChangeListTypeCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ChangeListTypeCommand.h; sourceTree = "<group>"; };
                F4D9817E2195FBF6008230FC /* ChangeListTypeCommand.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ChangeListTypeCommand.cpp; sourceTree = "<group>"; };
                                A8185F3609765765005826D9 /* DOMImplementation.cpp */,
                                A8185F3309765765005826D9 /* DOMImplementation.h */,
                                93EEC1E909C2877700C515D1 /* DOMImplementation.idl */,
+                               F4B422C2220C0000009E1E7D /* DOMPasteAccessPolicy.h */,
                                0F4966991DB408C100A274BB /* DOMPoint.h */,
                                0F49669A1DB408C100A274BB /* DOMPoint.idl */,
                                0F4966A21DB4091000A274BB /* DOMPointInit.h */,
                                A9C6E4E40D745E05006442E9 /* DOMMimeType.h in Headers */,
                                A9C6E4E80D745E18006442E9 /* DOMMimeTypeArray.h in Headers */,
                                1ACE53E80A8D18E70022947D /* DOMParser.h in Headers */,
+                               F4B422C4220C0568009E1E7D /* DOMPasteAccessPolicy.h in Headers */,
                                7A54881714E432A1006AE05A /* DOMPatchSupport.h in Headers */,
                                A9C6E4EC0D745E2B006442E9 /* DOMPlugin.h in Headers */,
                                A9C6E4F00D745E38006442E9 /* DOMPluginArray.h in Headers */,
diff --git a/Source/WebCore/dom/DOMPasteAccessPolicy.h b/Source/WebCore/dom/DOMPasteAccessPolicy.h
new file mode 100644 (file)
index 0000000..80018b9
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#pragma once
+
+namespace WebCore {
+
+enum class DOMPasteAccessPolicy : uint8_t {
+    NotRequestedYet,
+    Denied,
+    Granted
+};
+
+}
index c18cdc0..3e5ea43 100644 (file)
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "DOMPasteAccessPolicy.h"
 #include <wtf/Function.h>
 #include <wtf/Noncopyable.h>
 #include <wtf/Optional.h>
@@ -63,6 +64,9 @@ public:
         m_destructionObservers.append(WTFMove(observer));
     }
 
+    DOMPasteAccessPolicy domPasteAccessPolicy() const { return m_domPasteAccessPolicy; }
+    void didRequestDOMPasteAccess(bool granted) { m_domPasteAccessPolicy = granted ? DOMPasteAccessPolicy::Granted : DOMPasteAccessPolicy::Denied; }
+
 private:
     UserGestureToken(ProcessingUserGestureState state, UserGestureType gestureType)
         : m_state(state)
@@ -73,6 +77,7 @@ private:
     ProcessingUserGestureState m_state = NotProcessingUserGesture;
     Vector<WTF::Function<void (UserGestureToken&)>> m_destructionObservers;
     UserGestureType m_gestureType;
+    DOMPasteAccessPolicy m_domPasteAccessPolicy { DOMPasteAccessPolicy::NotRequestedYet };
 };
 
 class UserGestureIndicator {
index 0b3bbb9..9494709 100644 (file)
@@ -908,8 +908,13 @@ static bool executePaste(Frame& frame, Event*, EditorCommandSource source, const
     if (source == CommandFromMenuOrKeyBinding) {
         UserTypingGestureIndicator typingGestureIndicator(frame);
         frame.editor().paste();
-    } else
-        frame.editor().paste();
+        return true;
+    }
+
+    if (!frame.requestDOMPasteAccess())
+        return false;
+
+    frame.editor().paste();
     return true;
 }
 
@@ -934,8 +939,13 @@ static bool executePasteAndMatchStyle(Frame& frame, Event*, EditorCommandSource
     if (source == CommandFromMenuOrKeyBinding) {
         UserTypingGestureIndicator typingGestureIndicator(frame);
         frame.editor().pasteAsPlainText();
-    } else
-        frame.editor().pasteAsPlainText();
+        return true;
+    }
+
+    if (!frame.requestDOMPasteAccess())
+        return false;
+
+    frame.editor().pasteAsPlainText();
     return true;
 }
 
@@ -944,8 +954,13 @@ static bool executePasteAsPlainText(Frame& frame, Event*, EditorCommandSource so
     if (source == CommandFromMenuOrKeyBinding) {
         UserTypingGestureIndicator typingGestureIndicator(frame);
         frame.editor().pasteAsPlainText();
-    } else
-        frame.editor().pasteAsPlainText();
+        return true;
+    }
+
+    if (!frame.requestDOMPasteAccess())
+        return false;
+
+    frame.editor().pasteAsPlainText();
     return true;
 }
 
@@ -954,8 +969,13 @@ static bool executePasteAsQuotation(Frame& frame, Event*, EditorCommandSource so
     if (source == CommandFromMenuOrKeyBinding) {
         UserTypingGestureIndicator typingGestureIndicator(frame);
         frame.editor().pasteAsQuotation();
-    } else
-        frame.editor().pasteAsQuotation();
+        return true;
+    }
+
+    if (!frame.requestDOMPasteAccess())
+        return false;
+
+    frame.editor().pasteAsQuotation();
     return true;
 }
 
@@ -1220,7 +1240,8 @@ static bool supportedPaste(Frame* frame)
     if (!frame)
         return false;
 
-    bool defaultValue = frame->settings().javaScriptCanAccessClipboard() && frame->settings().DOMPasteAllowed();
+    auto& settings = frame->settings();
+    bool defaultValue = (settings.javaScriptCanAccessClipboard() && settings.DOMPasteAllowed()) || settings.domPasteAccessRequestsEnabled();
 
     EditorClient* client = frame->editor().client();
     return client ? client->canPaste(frame, defaultValue) : defaultValue;
index fb02baf..6c2dbdf 100644 (file)
@@ -190,6 +190,8 @@ private:
     void registerRedoStep(UndoStep&) final;
     void clearUndoRedoOperations() final { }
 
+    bool requestDOMPasteAccess() final { return false; }
+
     bool canCopyCut(Frame*, bool defaultValue) const final { return defaultValue; }
     bool canPaste(Frame*, bool defaultValue) const final { return defaultValue; }
     bool canUndo() const final { return false; }
index 37bf182..6e0d0be 100644 (file)
@@ -98,6 +98,8 @@ public:
     virtual void requestCandidatesForSelection(const VisibleSelection&) { }
     virtual void handleAcceptedCandidateWithSoftSpaces(TextCheckingResult) { }
 
+    virtual bool requestDOMPasteAccess() = 0;
+
     // Notify an input method that a composition was voluntarily discarded by WebCore, so that it could clean up too.
     // This function is not called when a composition is closed per a request from an input method.
     virtual void discardedComposition(Frame*) = 0;
index a4f83b2..b51e444 100644 (file)
@@ -95,6 +95,7 @@
 #include "TextResourceDecoder.h"
 #include "UserContentController.h"
 #include "UserContentURLPattern.h"
+#include "UserGestureIndicator.h"
 #include "UserScript.h"
 #include "UserTypingGestureIndicator.h"
 #include "VisibleUnits.h"
@@ -658,6 +659,35 @@ bool Frame::selectionChangeCallbacksDisabled() const
 }
 #endif // PLATFORM(IOS_FAMILY)
 
+bool Frame::requestDOMPasteAccess()
+{
+    if (m_settings->javaScriptCanAccessClipboard() && m_settings->DOMPasteAllowed())
+        return true;
+
+    if (!m_settings->domPasteAccessRequestsEnabled() || !m_doc)
+        return false;
+
+    auto gestureToken = UserGestureIndicator::currentUserGesture();
+    if (!gestureToken || !gestureToken->processingUserGesture())
+        return false;
+
+    switch (gestureToken->domPasteAccessPolicy()) {
+    case DOMPasteAccessPolicy::Granted:
+        return true;
+    case DOMPasteAccessPolicy::Denied:
+        return false;
+    case DOMPasteAccessPolicy::NotRequestedYet: {
+        auto* client = m_editor->client();
+        if (!client)
+            return false;
+
+        bool granted = client->requestDOMPasteAccess();
+        gestureToken->didRequestDOMPasteAccess(granted);
+        return granted;
+    }
+    }
+}
+
 void Frame::setPrinting(bool printing, const FloatSize& pageSize, const FloatSize& originalPageSize, float maximumShrinkRatio, AdjustViewSizeOrNot shouldAdjustViewSize)
 {
     if (!view())
index 8ad13d6..74f6f4f 100644 (file)
@@ -175,6 +175,8 @@ public:
     bool hasHadUserInteraction() const { return m_hasHadUserInteraction; }
     void setHasHadUserInteraction() { m_hasHadUserInteraction = true; }
 
+    bool requestDOMPasteAccess();
+
 // ======== All public functions below this point are candidates to move out of Frame into another class. ========
 
     WEBCORE_EXPORT void injectUserScripts(UserScriptInjectionTime);
index 976c84c..54585be 100644 (file)
@@ -344,6 +344,9 @@ mediaEnabled:
 DOMPasteAllowed:
   initial: false
 
+domPasteAccessRequestsEnabled:
+  initial: false
+
 # When enabled, window.blur() does not change focus, and
 # window.focus() only changes focus when invoked from the context that
 # created the window.
index 6a3f7ca..a4b597e 100644 (file)
@@ -1,3 +1,129 @@
+2019-02-12  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Allow pages to trigger programmatic paste from script on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=194271
+        <rdar://problem/47808810>
+
+        Reviewed by Ryosuke Niwa.
+
+        * Shared/WebPreferences.yaml:
+        * Shared/WebPreferencesDefaultValues.h:
+
+        Add an internal setting to enable or disable DOM paste access requests. This is on by default in iOS only
+        (excluding watchOS and Apple TV), and is additionally disabled on macOS.
+
+        * UIProcess/API/gtk/PageClientImpl.cpp:
+        (WebKit::PageClientImpl::requestDOMPasteAccess):
+        * UIProcess/API/gtk/PageClientImpl.h:
+        * UIProcess/API/wpe/PageClientImpl.cpp:
+        (WebKit::PageClientImpl::requestDOMPasteAccess):
+
+        Plumb DOM paste access requests from the web process (WebEditorClient) to the view (WKContentView). As per the
+        usual, this involves WebEditorClient, WebPage, WebPageProxy, PageClient and finally WKContentView.
+
+        * UIProcess/API/wpe/PageClientImpl.h:
+        * UIProcess/PageClient.h:
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::requestDOMPasteAccess):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * UIProcess/ios/PageClientImplIOS.h:
+        * UIProcess/ios/PageClientImplIOS.mm:
+        (WebKit::PageClientImpl::requestDOMPasteAccess):
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView setupInteraction]):
+        (-[WKContentView cleanupInteraction]):
+        (-[WKContentView resignFirstResponderForWebView]):
+        (-[WKContentView _webTouchEventsRecognized:]):
+
+        Bail from any pending DOM paste access handler the moment we start handling touches on the web view, or if the
+        web view resigns first responder, or if the web process crashes.
+
+        (-[WKContentView textInteractionGesture:shouldBeginAtPoint:]):
+
+        Reject text selection gestures while waiting for DOM paste access.
+
+        (-[WKContentView canPerformAction:withSender:]):
+        (-[WKContentView canPerformActionForWebView:withSender:]):
+
+        If we're handling a DOM paste, always return YES to allow the callout bar to show the "Paste" option.
+
+        (-[WKContentView _didHideMenu:]):
+
+        If the menu is programmatically hidden by the app while handling a DOM paste request, immediately reject the DOM
+        paste request.
+
+        (-[WKContentView pasteForWebView:]):
+
+        Adjust -pasteForWebView: on WKContentView to first check whether there's an outstanding DOM paste completion
+        handler to invoke, instead of telling the page to execute a paste command.
+
+        (-[WKContentView _handleDOMPasteRequestWithResult:]):
+
+        Add a helper to take and invoke the current DOM paste completion handler (if it exists) with the given result,
+        and then dismiss the shared callout bar. Returns whether or not the paste completion handler exists. Invoked
+        from various sources of user interaction or significant state changes (e.g. following a web process crash in
+        -cleanupInteraction).
+
+        (-[WKContentView _willPerformAction:sender:]):
+        (-[WKContentView _didPerformAction:sender:]):
+
+        Add hooks to detect when WKContentView is executing an editing action. This is to ensure that the page doesn't
+        get stuck in a bad state in the case where WKWebView has been subclassed, overrides `-paste:`, and does not
+        invoke the superclass method (which calls back into `-[WKContentView pasteForWebView:]`). There are a few
+        possibilities here:
+        1. WKWebView's `-paste:` action is not overridden. In this case, we will call back into `-pasteForWebView:`,
+           which will notice that we have a pending paste completion handler and invoke it.
+        2. WKWebView's `-paste:` action is overridden and does not call back into the content view. In this case, we
+           will invoke the paste completion handler in `-_didPerformAction:sender:`.
+        3. WKWebView's `-canPerformAction:withSender:` is overridden to include additional actions. In this case, we may
+           get a call to invoke a different action selector while waiting for a potential paste action. If this happens,
+           prevent the DOM paste in `-_willPerformAction:sender:` prior to handling the other action.
+
+        (-[WKContentView handleKeyWebEvent:withCompletionHandler:]):
+
+        Dismiss DOM paste UI upon handling any key event.
+
+        (-[WKContentView showGlobalMenuControllerInRect:]):
+        (-[WKContentView hideGlobalMenuController]):
+
+        Helper methods to present and dismiss the global UIMenuController, that accounts for available platform APIs for
+        presenting or dismissing the menu controller on iOS.
+
+        (-[WKContentView _requestDOMPasteAccessWithElementRect:completionHandler:]):
+
+        Attempt to find a good target presentation rect when showing the callout menu. First, we will try to use the
+        rect of the element the user has interacted with when triggering the paste. If such an element is too large or
+        does not exist, we fall back to presenting the callout menu near the user's last touch location (with a small
+        amount of margin, such that the action doesn't overlap with the user's finger, stylus, etc.).
+
+        (-[WKContentView _resetShowingTextStyle:]): Deleted.
+
+        Rename this to `-_didHideMenu:`.
+
+        * UIProcess/mac/PageClientImplMac.h:
+        * UIProcess/win/PageClientImpl.cpp:
+        (WebKit::PageClientImpl::requestDOMPasteAccess):
+        * UIProcess/win/PageClientImpl.h:
+        * WebProcess/WebCoreSupport/WebEditorClient.cpp:
+        (WebKit::WebEditorClient::requestDOMPasteAccess):
+        * WebProcess/WebCoreSupport/WebEditorClient.h:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::requestDOMPasteAccess):
+
+        Add more plumbing and method stubs.
+
+        (WebKit::WebPage::updateCurrentModifierState):
+        (WebKit::WebPage::rectForElementAtInteractionLocation const):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::rectForElementAtInteractionLocation const):
+        (WebKit::WebPage::rectForElementAtInteractionLocation): Deleted.
+
+        Mark this method as const, add a platform-agnostic stub, and adopt it for the purposes of determining where to
+        position the callout bar when pasting.
+
 2019-02-12  Alex Christensen  <achristensen@webkit.org>
 
         Remove setDefersLoading infrastructure from WebKit2
index cce4ace..405b7a1 100644 (file)
@@ -1534,6 +1534,13 @@ AdClickAttributionEnabled:
   webcoreBinding: RuntimeEnabledFeatures
   category: internal
 
+DOMPasteAccessRequestsEnabled:
+  type: bool
+  defaultValue: DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED
+  humanReadableName: "DOM Paste Access Requests"
+  humanReadableDescription: "Enable DOM Paste Access Requests"
+  category: internal
+
 # Deprecated
 
 ICECandidateFilteringEnabled:
index 0a52167..6e82173 100644 (file)
@@ -242,3 +242,9 @@ bool defaultCustomPasteboardDataEnabled();
 #define DEFAULT_INPUT_TYPE_COLOR_ENABLED true
 #define DEFAULT_DATALIST_ELEMENT_ENABLED true
 #endif
+
+#if PLATFORM(IOS)
+#define DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED true
+#else
+#define DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED false
+#endif
index 4c734e3..0fb8802 100644 (file)
@@ -514,4 +514,9 @@ bool PageClientImpl::decidePolicyForInstallMissingMediaPluginsPermissionRequest(
 }
 #endif
 
+void PageClientImpl::requestDOMPasteAccess(const IntRect&, CompletionHandler<void(bool)>&& completionHandler)
+{
+    completionHandler(false);
+}
+
 } // namespace WebKit
index 93ca18f..c7dd6f2 100644 (file)
@@ -148,6 +148,8 @@ private:
 
     void didFinishProcessingAllPendingMouseEvents() final { }
 
+    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
+
 #if ENABLE(VIDEO) && USE(GSTREAMER)
     bool decidePolicyForInstallMissingMediaPluginsPermissionRequest(InstallMissingMediaPluginsPermissionRequest&) override;
 #endif
index c34810e..74548d9 100644 (file)
@@ -389,4 +389,9 @@ void PageClientImpl::beganExitFullScreen(const WebCore::IntRect& /* initialFrame
 
 #endif // ENABLE(FULLSCREEN_API)
 
+void PageClientImpl::requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&& completionHandler)
+{
+    completionHandler(false);
+}
+
 } // namespace WebKit
index f9e25b7..469d015 100644 (file)
@@ -144,6 +144,7 @@ private:
     void didFinishProcessingAllPendingMouseEvents() final { }
 
     IPC::Attachment hostFileDescriptor() final;
+    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
 
     WebCore::UserInterfaceLayoutDirection userInterfaceLayoutDirection() override;
 
index 3c2dc01..4135184 100644 (file)
@@ -465,6 +465,8 @@ public:
     virtual void didChangeDragCaretRect(const WebCore::IntRect& previousCaretRect, const WebCore::IntRect& caretRect) = 0;
 #endif
 
+    virtual void requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&&) = 0;
+
 #if ENABLE(ATTACHMENT_ELEMENT)
     virtual void didInsertAttachment(API::Attachment&, const String& source) { }
     virtual void didRemoveAttachment(API::Attachment&) { }
index 6a17745..7a3f352 100644 (file)
@@ -5439,6 +5439,11 @@ void WebPageProxy::setNeedsPlainTextQuirk(bool needsPlainTextQuirk)
     m_needsPlainTextQuirk = needsPlainTextQuirk;
 }
 
+void WebPageProxy::requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&& completionHandler)
+{
+    m_pageClient->requestDOMPasteAccess(elementRect, WTFMove(completionHandler));
+}
+
 // BackForwardList
 
 void WebPageProxy::backForwardAddItem(BackForwardListItemState&& itemState)
index 0ba2936..9740e5d 100644 (file)
@@ -1659,6 +1659,8 @@ private:
     void setNeedsHiddenContentEditableQuirk(bool);
     void setNeedsPlainTextQuirk(bool);
 
+    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&);
+
     // Back/Forward list management
     void backForwardAddItem(BackForwardListItemState&&);
     void backForwardGoToItem(const WebCore::BackForwardItemIdentifier&, SandboxExtension::Handle&);
index 302e7df..b984ea3 100644 (file)
@@ -254,6 +254,7 @@ messages -> WebPageProxy {
     SetHasHadSelectionChangesFromUserInteraction(bool hasHadUserSelectionChanges)
     SetNeedsHiddenContentEditableQuirk(bool needsHiddenContentEditableQuirk)
     SetNeedsPlainTextQuirk(bool needsPlainTextQuirk)
+    RequestDOMPasteAccess(WebCore::IntRect elementRect) -> (bool granted) Delayed
 
     # Find messages
     DidCountStringMatches(String string, uint32_t matchCount)
index a3c6d3a..d0d0384 100644 (file)
@@ -224,6 +224,8 @@ private:
     void requestPasswordForQuickLookDocument(const String& fileName, WTF::Function<void(const String&)>&&) override;
 #endif
 
+    void requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&&) final;
+
 #if ENABLE(DATA_INTERACTION)
     void didPerformDragOperation(bool handled) override;
     void didHandleDragStartRequest(bool started) override;
index 1509cc2..5e0a690 100644 (file)
@@ -837,6 +837,11 @@ void PageClientImpl::requestPasswordForQuickLookDocument(const String& fileName,
 }
 #endif
 
+void PageClientImpl::requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&& completionHandler)
+{
+    [m_contentView _requestDOMPasteAccessWithElementRect:elementRect completionHandler:WTFMove(completionHandler)];
+}
+
 #if HAVE(PENCILKIT)
 RetainPtr<WKDrawingView> PageClientImpl::createDrawingView(WebCore::GraphicsLayer::EmbeddedViewID embeddedViewID)
 {
index 9189ebd..2cc7d00 100644 (file)
@@ -49,6 +49,7 @@
 #import <WebCore/Color.h>
 #import <WebCore/FloatQuad.h>
 #import <wtf/BlockPtr.h>
+#import <wtf/CompletionHandler.h>
 #import <wtf/Forward.h>
 #import <wtf/OptionSet.h>
 #import <wtf/Vector.h>
@@ -313,6 +314,7 @@ struct WKAutoCorrectionData {
     BOOL _focusRequiresStrongPasswordAssistance;
 
     BOOL _hasSetUpInteractions;
+    CompletionHandler<void(bool)> _domPasteRequestHandler;
 
 #if ENABLE(DATA_INTERACTION)
     WebKit::DragDropInteractionState _dragDropInteractionState;
@@ -434,6 +436,8 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW)
 - (void)_accessibilityClearSelection;
 - (WKFormInputSession *)_formInputSession;
 
+- (void)_requestDOMPasteAccessWithElementRect:(const WebCore::IntRect&)elementRect completionHandler:(CompletionHandler<void(bool)>&&)completionHandler;
+
 @property (nonatomic, readonly) WebKit::InteractionInformationAtPosition currentPositionInformation;
 - (void)doAfterPositionInformationUpdate:(void (^)(WebKit::InteractionInformationAtPosition))action forRequest:(WebKit::InteractionInformationRequest)request;
 - (BOOL)ensurePositionInformationIsUpToDate:(WebKit::InteractionInformationRequest)request;
index c4804b2..a0a77c8 100644 (file)
@@ -740,7 +740,7 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
 #endif
 
     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
-    [center addObserver:self selector:@selector(_resetShowingTextStyle:) name:UIMenuControllerDidHideMenuNotification object:nil];
+    [center addObserver:self selector:@selector(_didHideMenu:) name:UIMenuControllerDidHideMenuNotification object:nil];
     [center addObserver:self selector:@selector(_keyboardDidRequestDismissal:) name:UIKeyboardPrivateDidRequestDismissalNotification object:nil];
 
     _showingTextStyleOptions = NO;
@@ -886,6 +886,7 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
 #if ENABLE(POINTER_EVENTS)
     [self _resetPanningPreventionFlags];
 #endif
+    [self _handleDOMPasteRequestWithResult:NO];
 }
 
 - (void)_removeDefaultGestureRecognizers
@@ -1143,8 +1144,10 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
 
     bool superDidResign = [super resignFirstResponder];
 
-    if (superDidResign)
+    if (superDidResign) {
+        [self _handleDOMPasteRequestWithResult:NO];
         _page->activityStateDidChange(WebCore::ActivityState::IsFocused);
+    }
 
     return superDidResign;
 }
@@ -1178,8 +1181,10 @@ inline static UIKeyModifierFlags gestureRecognizerModifierFlags(UIGestureRecogni
     const _UIWebTouchEvent* lastTouchEvent = gestureRecognizer.lastTouchEvent;
 
     _lastInteractionLocation = lastTouchEvent->locationInDocumentCoordinates;
-    if (lastTouchEvent->type == UIWebTouchEventTouchBegin)
+    if (lastTouchEvent->type == UIWebTouchEventTouchBegin) {
+        [self _handleDOMPasteRequestWithResult:NO];
         _layerTreeTransactionIdAtLastTouchStart = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).lastCommittedLayerTreeTransactionID();
+    }
 
 #if ENABLE(TOUCH_EVENTS)
     WebKit::NativeWebTouchEvent nativeWebTouchEvent { lastTouchEvent, gestureRecognizerModifierFlags(gestureRecognizer) };
@@ -1980,6 +1985,9 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
     if (!_webView.configuration._textInteractionGesturesEnabled)
         return NO;
 
+    if (_domPasteRequestHandler)
+        return NO;
+
     if (_suppressSelectionAssistantReasons)
         return NO;
 
@@ -2397,7 +2405,10 @@ static void cancelPotentialTapIfNecessary(WKContentView* contentView)
 #define FORWARD_ACTION_TO_WKWEBVIEW(_action) \
     - (void)_action:(id)sender \
     { \
+        SEL action = @selector(_action:);\
+        [self _willPerformAction:action sender:sender];\
         [_webView _action:sender]; \
+        [self _didPerformAction:action sender:sender];\
     }
 
 FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
@@ -2642,11 +2653,17 @@ WEBCORE_COMMAND_FOR_WEBVIEW(pasteAndMatchStyle);
 
 - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
 {
+    if (_domPasteRequestHandler)
+        return action == @selector(paste:);
+
     return [_webView canPerformAction:action withSender:sender];
 }
 
 - (BOOL)canPerformActionForWebView:(SEL)action withSender:(id)sender
 {
+    if (_domPasteRequestHandler)
+        return action == @selector(paste:);
+
     if (action == @selector(_nextAccessoryTab:))
         return hasFocusedElement(_focusedElementInformation) && _focusedElementInformation.hasNextNode;
     if (action == @selector(_previousAccessoryTab:))
@@ -2793,10 +2810,11 @@ WEBCORE_COMMAND_FOR_WEBVIEW(pasteAndMatchStyle);
     return [super targetForAction:action withSender:sender];
 }
 
-- (void)_resetShowingTextStyle:(NSNotification *)notification
+- (void)_didHideMenu:(NSNotification *)notification
 {
     _showingTextStyleOptions = NO;
     [_textSelectionAssistant hideTextStyleOptions];
+    [self _handleDOMPasteRequestWithResult:NO];
 }
 
 - (void)_keyboardDidRequestDismissal:(NSNotification *)notification
@@ -2818,6 +2836,9 @@ WEBCORE_COMMAND_FOR_WEBVIEW(pasteAndMatchStyle);
 
 - (void)pasteForWebView:(id)sender
 {
+    if (sender == UIMenuController.sharedMenuController && [self _handleDOMPasteRequestWithResult:YES])
+        return;
+
     _page->executeEditCommand("paste"_s);
 }
 
@@ -2947,6 +2968,28 @@ WEBCORE_COMMAND_FOR_WEBVIEW(pasteAndMatchStyle);
     _page->storeSelectionForAccessibility(false);
 }
 
+- (BOOL)_handleDOMPasteRequestWithResult:(BOOL)allowPaste
+{
+    if (auto pasteHandler = WTFMove(_domPasteRequestHandler)) {
+        [self hideGlobalMenuController];
+        pasteHandler(allowPaste);
+        return YES;
+    }
+    return NO;
+}
+
+- (void)_willPerformAction:(SEL)action sender:(id)sender
+{
+    if (action != @selector(paste:))
+        [self _handleDOMPasteRequestWithResult:NO];
+}
+
+- (void)_didPerformAction:(SEL)action sender:(id)sender
+{
+    if (action == @selector(paste:))
+        [self _handleDOMPasteRequestWithResult:NO];
+}
+
 // UIWKInteractionViewProtocol
 
 static inline WebKit::GestureType toGestureType(UIWKGestureType gestureType)
@@ -4094,6 +4137,8 @@ static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
 
 - (void)handleKeyWebEvent:(::WebEvent *)theEvent withCompletionHandler:(void (^)(::WebEvent *theEvent, BOOL wasHandled))completionHandler
 {
+    [self _handleDOMPasteRequestWithResult:NO];
+
     _keyWebEventHandler = makeBlockPtr(completionHandler);
     _page->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(theEvent));
 }
@@ -4793,6 +4838,47 @@ static const double minimumFocusedElementAreaForSuppressingSelectionAssistant =
 #endif
 }
 
+- (void)showGlobalMenuControllerInRect:(CGRect)rect
+{
+    UIMenuController *controller = UIMenuController.sharedMenuController;
+#if HAVE(MENU_CONTROLLER_SHOW_HIDE_API)
+    [controller showMenuFromView:self rect:rect];
+#else
+    [controller setTargetRect:rect inView:self];
+    [controller setMenuVisible:YES animated:YES];
+#endif
+}
+
+- (void)hideGlobalMenuController
+{
+    UIMenuController *controller = UIMenuController.sharedMenuController;
+#if HAVE(MENU_CONTROLLER_SHOW_HIDE_API)
+    [controller hideMenuFromView:self];
+#else
+    [controller setMenuVisible:NO animated:YES];
+#endif
+}
+
+- (void)_requestDOMPasteAccessWithElementRect:(const WebCore::IntRect&)elementRect completionHandler:(CompletionHandler<void(bool)>&&)completionHandler
+{
+    if (auto existingCompletionHandler = std::exchange(_domPasteRequestHandler, WTFMove(completionHandler))) {
+        ASSERT_NOT_REACHED();
+        existingCompletionHandler(false);
+    }
+
+    WebCore::IntRect menuControllerRect = elementRect;
+
+    const CGFloat maximumElementWidth = 300;
+    const CGFloat maximumElementHeight = 120;
+    if (elementRect.isEmpty() || elementRect.width() > maximumElementWidth || elementRect.height() > maximumElementHeight) {
+        const CGFloat interactionLocationMargin = 10;
+        menuControllerRect = { WebCore::IntPoint(_lastInteractionLocation), { } };
+        menuControllerRect.inflate(interactionLocationMargin);
+    }
+
+    [self showGlobalMenuControllerInRect:menuControllerRect];
+}
+
 - (void)_didReceiveEditorStateUpdateAfterFocus
 {
     [self _updateInitialWritingDirectionIfNecessary];
index 4707a02..d242c05 100644 (file)
@@ -30,6 +30,7 @@
 #include "CorrectionPanel.h"
 #include "PageClientImplCocoa.h"
 #include "WebFullScreenManagerProxy.h"
+#include <wtf/CompletionHandler.h>
 #include <wtf/RetainPtr.h>
 
 @class WKEditorUndoTarget;
@@ -215,6 +216,8 @@ private:
     void willRecordNavigationSnapshot(WebBackForwardListItem&) override;
     void didRemoveNavigationGestureSnapshot() override;
 
+    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&& completion) final { completion(false); }
+
     NSView *activeView() const;
     NSWindow *activeWindow() const;
 
index 170c2d7..912b4a9 100644 (file)
@@ -357,4 +357,9 @@ HWND PageClientImpl::viewWidget()
     return m_view.window();
 }
 
+void PageClientImpl::requestDOMPasteAccess(const IntRect&, CompletionHandler<void(bool)>&& completionHandler)
+{
+    completionHandler(false);
+}
+
 } // namespace WebKit
index 66572bb..e8426c9 100644 (file)
@@ -142,6 +142,8 @@ private:
 
     void didFinishProcessingAllPendingMouseEvents() final { }
 
+    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
+
     // Members of PageClientImpl class
     DefaultUndoController m_undoController;
 
index 8f2181b..96a4dd1 100644 (file)
@@ -359,6 +359,11 @@ void WebEditorClient::redo()
     m_page->sendSync(Messages::WebPageProxy::ExecuteUndoRedo(UndoOrRedo::Redo), Messages::WebPageProxy::ExecuteUndoRedo::Reply());
 }
 
+bool WebEditorClient::requestDOMPasteAccess()
+{
+    return m_page->requestDOMPasteAccess();
+}
+
 #if PLATFORM(WIN)
 void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event)
 {
index 4720b71..7be8ddf 100644 (file)
@@ -89,6 +89,8 @@ private:
     void registerRedoStep(WebCore::UndoStep&) final;
     void clearUndoRedoOperations() final;
 
+    bool requestDOMPasteAccess() final;
+
     bool canCopyCut(WebCore::Frame*, bool defaultValue) const final;
     bool canPaste(WebCore::Frame*, bool defaultValue) const final;
     bool canUndo() const final;
index a611b26..dfaab22 100644 (file)
@@ -6424,6 +6424,15 @@ void WebPage::didCompleteShareSheet(bool wasGranted, ShareSheetCallbackID callba
     callback(wasGranted);
 }
 
+bool WebPage::requestDOMPasteAccess()
+{
+    bool granted = false;
+    if (!sendSyncWithDelayedReply(Messages::WebPageProxy::RequestDOMPasteAccess(rectForElementAtInteractionLocation()), Messages::WebPageProxy::RequestDOMPasteAccess::Reply(granted)))
+        return false;
+
+    return granted;
+}
+
 void WebPage::simulateDeviceOrientationChange(double alpha, double beta, double gamma)
 {
 #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS_FAMILY)
@@ -6497,7 +6506,16 @@ void WebPage::didFinishLoadingApplicationManifest(uint64_t coreCallbackID, const
 void WebPage::updateCurrentModifierState(OptionSet<PlatformEvent::Modifier> modifiers)
 {
     PlatformKeyboardEvent::setCurrentModifierState(modifiers);
-}    
+}
+
+#if !PLATFORM(IOS_FAMILY)
+
+WebCore::IntRect WebPage::rectForElementAtInteractionLocation() const
+{
+    return { };
+}
+
+#endif // !PLATFORM(IOS_FAMILY)
 
 } // namespace WebKit
 
index 03b4d3e..4a99b91 100644 (file)
@@ -658,7 +658,6 @@ public:
     void setFocusedElementValue(const String&);
     void setFocusedElementValueAsNumber(double);
     void setFocusedElementSelectedIndex(uint32_t index, bool allowMultipleSelection);
-    WebCore::IntRect rectForElementAtInteractionLocation();
     void updateSelectionAppearance();
     void getSelectionContext(CallbackID);
     void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, OptionSet<WebKit::WebEvent::Modifier>, uint64_t requestID);
@@ -1149,6 +1148,9 @@ public:
         return sendSync(WTFMove(message), WTFMove(reply), m_pageID, Seconds::infinity(), IPC::SendSyncOption::InformPlatformProcessWillSuspend);
     }
 
+    bool requestDOMPasteAccess();
+    WebCore::IntRect rectForElementAtInteractionLocation() const;
+
 private:
     WebPage(uint64_t pageID, WebPageCreationParameters&&);
 
index 9d1f2de..f18f19a 100644 (file)
@@ -511,7 +511,7 @@ void WebPage::advanceToNextMisspelling(bool)
     notImplemented();
 }
 
-IntRect WebPage::rectForElementAtInteractionLocation()
+IntRect WebPage::rectForElementAtInteractionLocation() const
 {
     HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint(m_lastInteractionLocation, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AllowChildFrameContent);
     Node* hitNode = result.innerNode();
index b91e946..a0f8691 100644 (file)
@@ -1,3 +1,15 @@
+2019-02-12  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Allow pages to trigger programmatic paste from script on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=194271
+        <rdar://problem/47808810>
+
+        Reviewed by Ryosuke Niwa.
+
+        See WebCore and WebKit ChangeLogs for more details.
+
+        * WebCoreSupport/WebEditorClient.h:
+
 2019-02-12  Andy Estes  <aestes@apple.com>
 
         [iOSMac] Enable Parental Controls Content Filtering
index f0f968b..55f40e0 100644 (file)
@@ -81,6 +81,7 @@ private:
     String replacementURLForResource(Ref<WebCore::SharedBuffer>&& resourceData, const String& mimeType) final;
 
     void setInsertionPasteboard(const String&) final;
+    bool requestDOMPasteAccess() final { return false; }
 
 #if USE(APPKIT)
     void uppercaseWord() final;
index e90cf1a..cc72d9b 100644 (file)
@@ -1,3 +1,13 @@
+2019-02-12  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Allow pages to trigger programmatic paste from script on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=194271
+        <rdar://problem/47808810>
+
+        Reviewed by Ryosuke Niwa.
+
+        * WebCoreSupport/WebEditorClient.h:
+
 2019-02-06  Daniel Bates  <dabates@apple.com>
 
         Standardize on ControlKey instead of CtrlKey
index 7a6edde..cc5e4d6 100644 (file)
@@ -115,6 +115,8 @@ private:
     void requestCheckingOfString(WebCore::TextCheckingRequest&, const WebCore::VisibleSelection&) final { }
     bool performTwoStepDrop(WebCore::DocumentFragment&, WebCore::Range&, bool) final { return false; }
 
+    bool requestDOMPasteAccess() final { return false; }
+
     WebCore::TextCheckerClient* textChecker() final { return this; }
 
     WebView* m_webView;