Title: [241320] trunk/Source
Revision
241320
Author
[email protected]
Date
2019-02-12 14:37:48 -0800 (Tue, 12 Feb 2019)

Log Message

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.

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:

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (241319 => 241320)


--- trunk/Source/WebCore/ChangeLog	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/ChangeLog	2019-02-12 22:37:48 UTC (rev 241320)
@@ -1,3 +1,59 @@
+2019-02-12  Wenson Hsieh  <[email protected]>
+
+        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  <[email protected]>
 
         Remove setDefersLoading infrastructure from WebKit2

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (241319 => 241320)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2019-02-12 22:37:48 UTC (rev 241320)
@@ -4952,6 +4952,7 @@
 		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, ); }; };
@@ -15186,6 +15187,7 @@
 		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>"; };
@@ -27515,6 +27517,7 @@
 				A8185F3609765765005826D9 /* DOMImplementation.cpp */,
 				A8185F3309765765005826D9 /* DOMImplementation.h */,
 				93EEC1E909C2877700C515D1 /* DOMImplementation.idl */,
+				F4B422C2220C0000009E1E7D /* DOMPasteAccessPolicy.h */,
 				0F4966991DB408C100A274BB /* DOMPoint.h */,
 				0F49669A1DB408C100A274BB /* DOMPoint.idl */,
 				0F4966A21DB4091000A274BB /* DOMPointInit.h */,
@@ -29089,6 +29092,7 @@
 				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 */,

Added: trunk/Source/WebCore/dom/DOMPasteAccessPolicy.h (0 => 241320)


--- trunk/Source/WebCore/dom/DOMPasteAccessPolicy.h	                        (rev 0)
+++ trunk/Source/WebCore/dom/DOMPasteAccessPolicy.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -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
+};
+
+}

Modified: trunk/Source/WebCore/dom/UserGestureIndicator.h (241319 => 241320)


--- trunk/Source/WebCore/dom/UserGestureIndicator.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/dom/UserGestureIndicator.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -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 @@
         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 @@
     ProcessingUserGestureState m_state = NotProcessingUserGesture;
     Vector<WTF::Function<void (UserGestureToken&)>> m_destructionObservers;
     UserGestureType m_gestureType;
+    DOMPasteAccessPolicy m_domPasteAccessPolicy { DOMPasteAccessPolicy::NotRequestedYet };
 };
 
 class UserGestureIndicator {

Modified: trunk/Source/WebCore/editing/EditorCommand.cpp (241319 => 241320)


--- trunk/Source/WebCore/editing/EditorCommand.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/editing/EditorCommand.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -908,8 +908,13 @@
     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 @@
     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 @@
     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 @@
     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 @@
     if (!frame)
         return false;
 
-    bool defaultValue = frame->settings()._javascript_CanAccessClipboard() && frame->settings().DOMPasteAllowed();
+    auto& settings = frame->settings();
+    bool defaultValue = (settings._javascript_CanAccessClipboard() && settings.DOMPasteAllowed()) || settings.domPasteAccessRequestsEnabled();
 
     EditorClient* client = frame->editor().client();
     return client ? client->canPaste(frame, defaultValue) : defaultValue;

Modified: trunk/Source/WebCore/loader/EmptyClients.cpp (241319 => 241320)


--- trunk/Source/WebCore/loader/EmptyClients.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/loader/EmptyClients.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -190,6 +190,8 @@
     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; }

Modified: trunk/Source/WebCore/page/EditorClient.h (241319 => 241320)


--- trunk/Source/WebCore/page/EditorClient.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/page/EditorClient.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -98,6 +98,8 @@
     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;

Modified: trunk/Source/WebCore/page/Frame.cpp (241319 => 241320)


--- trunk/Source/WebCore/page/Frame.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/page/Frame.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -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 @@
 }
 #endif // PLATFORM(IOS_FAMILY)
 
+bool Frame::requestDOMPasteAccess()
+{
+    if (m_settings->_javascript_CanAccessClipboard() && 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())

Modified: trunk/Source/WebCore/page/Frame.h (241319 => 241320)


--- trunk/Source/WebCore/page/Frame.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/page/Frame.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -175,6 +175,8 @@
     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);

Modified: trunk/Source/WebCore/page/Settings.yaml (241319 => 241320)


--- trunk/Source/WebCore/page/Settings.yaml	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebCore/page/Settings.yaml	2019-02-12 22:37:48 UTC (rev 241320)
@@ -344,6 +344,9 @@
 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.

Modified: trunk/Source/WebKit/ChangeLog (241319 => 241320)


--- trunk/Source/WebKit/ChangeLog	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/ChangeLog	2019-02-12 22:37:48 UTC (rev 241320)
@@ -1,3 +1,129 @@
+2019-02-12  Wenson Hsieh  <[email protected]>
+
+        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  <[email protected]>
 
         Remove setDefersLoading infrastructure from WebKit2

Modified: trunk/Source/WebKit/Shared/WebPreferences.yaml (241319 => 241320)


--- trunk/Source/WebKit/Shared/WebPreferences.yaml	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/Shared/WebPreferences.yaml	2019-02-12 22:37:48 UTC (rev 241320)
@@ -1534,6 +1534,13 @@
   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:

Modified: trunk/Source/WebKit/Shared/WebPreferencesDefaultValues.h (241319 => 241320)


--- trunk/Source/WebKit/Shared/WebPreferencesDefaultValues.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/Shared/WebPreferencesDefaultValues.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -242,3 +242,9 @@
 #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

Modified: trunk/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -514,4 +514,9 @@
 }
 #endif
 
+void PageClientImpl::requestDOMPasteAccess(const IntRect&, CompletionHandler<void(bool)>&& completionHandler)
+{
+    completionHandler(false);
+}
+
 } // namespace WebKit

Modified: trunk/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -148,6 +148,8 @@
 
     void didFinishProcessingAllPendingMouseEvents() final { }
 
+    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
+
 #if ENABLE(VIDEO) && USE(GSTREAMER)
     bool decidePolicyForInstallMissingMediaPluginsPermissionRequest(InstallMissingMediaPluginsPermissionRequest&) override;
 #endif

Modified: trunk/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -389,4 +389,9 @@
 
 #endif // ENABLE(FULLSCREEN_API)
 
+void PageClientImpl::requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&& completionHandler)
+{
+    completionHandler(false);
+}
+
 } // namespace WebKit

Modified: trunk/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -144,6 +144,7 @@
     void didFinishProcessingAllPendingMouseEvents() final { }
 
     IPC::Attachment hostFileDescriptor() final;
+    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
 
     WebCore::UserInterfaceLayoutDirection userInterfaceLayoutDirection() override;
 

Modified: trunk/Source/WebKit/UIProcess/PageClient.h (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/PageClient.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/PageClient.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -465,6 +465,8 @@
     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&) { }

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -5439,6 +5439,11 @@
     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)

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -1659,6 +1659,8 @@
     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&);

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in	2019-02-12 22:37:48 UTC (rev 241320)
@@ -254,6 +254,7 @@
     SetHasHadSelectionChangesFromUserInteraction(bool hasHadUserSelectionChanges)
     SetNeedsHiddenContentEditableQuirk(bool needsHiddenContentEditableQuirk)
     SetNeedsPlainTextQuirk(bool needsPlainTextQuirk)
+    RequestDOMPasteAccess(WebCore::IntRect elementRect) -> (bool granted) Delayed
 
     # Find messages
     DidCountStringMatches(String string, uint32_t matchCount)

Modified: trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.h (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -224,6 +224,8 @@
     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;

Modified: trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm	2019-02-12 22:37:48 UTC (rev 241320)
@@ -837,6 +837,11 @@
 }
 #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)
 {

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -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 @@
     BOOL _focusRequiresStrongPasswordAssistance;
 
     BOOL _hasSetUpInteractions;
+    CompletionHandler<void(bool)> _domPasteRequestHandler;
 
 #if ENABLE(DATA_INTERACTION)
     WebKit::DragDropInteractionState _dragDropInteractionState;
@@ -434,6 +436,8 @@
 - (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;

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-02-12 22:37:48 UTC (rev 241320)
@@ -740,7 +740,7 @@
 #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 @@
 #if ENABLE(POINTER_EVENTS)
     [self _resetPanningPreventionFlags];
 #endif
+    [self _handleDOMPasteRequestWithResult:NO];
 }
 
 - (void)_removeDefaultGestureRecognizers
@@ -1143,8 +1144,10 @@
 
     bool superDidResign = [super resignFirstResponder];
 
-    if (superDidResign)
+    if (superDidResign) {
+        [self _handleDOMPasteRequestWithResult:NO];
         _page->activityStateDidChange(WebCore::ActivityState::IsFocused);
+    }
 
     return superDidResign;
 }
@@ -1178,8 +1181,10 @@
     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 @@
     if (!_webView.configuration._textInteractionGesturesEnabled)
         return NO;
 
+    if (_domPasteRequestHandler)
+        return NO;
+
     if (_suppressSelectionAssistantReasons)
         return NO;
 
@@ -2397,7 +2405,10 @@
 #define FORWARD_ACTION_TO_WKWEBVIEW(_action) \
     - (void)_action:(id)sender \
     { \
+        SEL 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 @@
 
 - (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 @@
     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 @@
 
 - (void)pasteForWebView:(id)sender
 {
+    if (sender == UIMenuController.sharedMenuController && [self _handleDOMPasteRequestWithResult:YES])
+        return;
+
     _page->executeEditCommand("paste"_s);
 }
 
@@ -2947,6 +2968,28 @@
     _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 @@
 
 - (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 @@
 #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];

Modified: trunk/Source/WebKit/UIProcess/mac/PageClientImplMac.h (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/mac/PageClientImplMac.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/mac/PageClientImplMac.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -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 @@
     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;
 

Modified: trunk/Source/WebKit/UIProcess/win/PageClientImpl.cpp (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/win/PageClientImpl.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/win/PageClientImpl.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -357,4 +357,9 @@
     return m_view.window();
 }
 
+void PageClientImpl::requestDOMPasteAccess(const IntRect&, CompletionHandler<void(bool)>&& completionHandler)
+{
+    completionHandler(false);
+}
+
 } // namespace WebKit

Modified: trunk/Source/WebKit/UIProcess/win/PageClientImpl.h (241319 => 241320)


--- trunk/Source/WebKit/UIProcess/win/PageClientImpl.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/UIProcess/win/PageClientImpl.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -142,6 +142,8 @@
 
     void didFinishProcessingAllPendingMouseEvents() final { }
 
+    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
+
     // Members of PageClientImpl class
     DefaultUndoController m_undoController;
 

Modified: trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp (241319 => 241320)


--- trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -359,6 +359,11 @@
     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)
 {

Modified: trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h (241319 => 241320)


--- trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -89,6 +89,8 @@
     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;

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (241319 => 241320)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2019-02-12 22:37:48 UTC (rev 241320)
@@ -6424,6 +6424,15 @@
     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,8 +6506,17 @@
 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
 
 #undef RELEASE_LOG_IF_ALLOWED

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (241319 => 241320)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -658,7 +658,6 @@
     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 @@
         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&&);
 

Modified: trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (241319 => 241320)


--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-02-12 22:37:48 UTC (rev 241320)
@@ -511,7 +511,7 @@
     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();

Modified: trunk/Source/WebKitLegacy/mac/ChangeLog (241319 => 241320)


--- trunk/Source/WebKitLegacy/mac/ChangeLog	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKitLegacy/mac/ChangeLog	2019-02-12 22:37:48 UTC (rev 241320)
@@ -1,3 +1,15 @@
+2019-02-12  Wenson Hsieh  <[email protected]>
+
+        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  <[email protected]>
 
         [iOSMac] Enable Parental Controls Content Filtering

Modified: trunk/Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h (241319 => 241320)


--- trunk/Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -81,6 +81,7 @@
     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;

Modified: trunk/Source/WebKitLegacy/win/ChangeLog (241319 => 241320)


--- trunk/Source/WebKitLegacy/win/ChangeLog	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKitLegacy/win/ChangeLog	2019-02-12 22:37:48 UTC (rev 241320)
@@ -1,3 +1,13 @@
+2019-02-12  Wenson Hsieh  <[email protected]>
+
+        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  <[email protected]>
 
         Standardize on ControlKey instead of CtrlKey

Modified: trunk/Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h (241319 => 241320)


--- trunk/Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h	2019-02-12 21:57:48 UTC (rev 241319)
+++ trunk/Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h	2019-02-12 22:37:48 UTC (rev 241320)
@@ -115,6 +115,8 @@
     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;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to