Changeset 241320 in webkit


Ignore:
Timestamp:
Feb 12, 2019 2:37:48 PM (5 years ago)
Author:
Wenson Hsieh
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:
Location:
trunk/Source
Files:
1 added
36 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r241319 r241320  
     12019-02-12  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Allow pages to trigger programmatic paste from script on iOS
     4        https://bugs.webkit.org/show_bug.cgi?id=194271
     5        <rdar://problem/47808810>
     6
     7        Reviewed by Ryosuke Niwa.
     8
     9        Add support for allowing script to trigger programmatic paste commands. Currently on macOS and iOS, the ability
     10        to trigger programmatic paste (i.e. `document.execCommand('Paste');`) is disabled by default, such that
     11        execCommand is simply a no-op that returns false. This policy is a privacy measure (common among other major
     12        browsers) that prevents untrusted web content from sniffing content from the system pasteboard (even on user
     13        interaction, since unintended user interaction occasionally happens as well!).
     14
     15        In order to make it possible for web pages to programmatically paste without opening the door to privacy and
     16        security issues, we make paste commands triggered from bindings present platform UI on iOS, in the form of a
     17        callout bar with the single option to paste. This UI is dismissed upon any user interaction; furthermore, any
     18        user interaction short of explicitly triggering the "Paste" action subsequently prevents the page from executing
     19        the paste (and causes execCommand to return false). However, if the paste action is chosen by the user, we
     20        instead follow through with the programmatic paste command.
     21
     22        New tests to come in a followup patch.
     23
     24        * WebCore.xcodeproj/project.pbxproj:
     25        * dom/DOMPasteAccessPolicy.h: Added.
     26        * dom/UserGestureIndicator.h:
     27        (WebCore::UserGestureToken::domPasteAccessPolicy const):
     28        (WebCore::UserGestureToken::didRequestDOMPasteAccess):
     29
     30        Add helpers on UserGestureToken to update and query the current DOM paste access policy. The access policies are
     31        "NotRequestedYet" (i.e. pending a response from the user), "Granted" (the user has granted DOM paste access to
     32        the page), or "Denied" (the user has prevented the page from reading the contents of the clipboard). When DOM
     33        paste access is granted or rejected, make this decision sticky until the end of the current user gesture.
     34
     35        * editing/EditorCommand.cpp:
     36        (WebCore::executePaste):
     37        (WebCore::executePasteAndMatchStyle):
     38        (WebCore::executePasteAsPlainText):
     39        (WebCore::executePasteAsQuotation):
     40
     41        When executing a paste command where the source is DOM bindings, request DOM paste if needed before proceeding
     42        with the paste.
     43
     44        (WebCore::supportedPaste):
     45        * loader/EmptyClients.cpp:
     46        * page/EditorClient.h:
     47        * page/Frame.cpp:
     48        (WebCore::Frame::requestDOMPasteAccess):
     49
     50        Add a helper method that requests access to the clipboard on behalf of script when pasting.
     51
     52        * page/Frame.h:
     53        * page/Settings.yaml:
     54
     55        Introduce a new WebCore setting, used to gate DOM paste access requests.
     56
    1572019-02-12  Alex Christensen  <achristensen@webkit.org>
    258
  • trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj

    r241310 r241320  
    49534953                F48D2AA52159740D00C6752B /* ColorCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = F48D2AA32159740D00C6752B /* ColorCocoa.h */; };
    49544954                F49786881FF45FA500E060AB /* PasteboardItemInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = F49786871FF45FA500E060AB /* PasteboardItemInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };
     4955                F4B422C4220C0568009E1E7D /* DOMPasteAccessPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = F4B422C2220C0000009E1E7D /* DOMPasteAccessPolicy.h */; settings = {ATTRIBUTES = (Private, ); }; };
    49554956                F4BFB9851E1DDF9B00862C24 /* DumpEditingHistory.js in Copy Scripts */ = {isa = PBXBuildFile; fileRef = F48389831E1DDF2B0076B7EA /* DumpEditingHistory.js */; };
    49564957                F4BFB9861E1DDF9B00862C24 /* EditingHistoryUtil.js in Copy Scripts */ = {isa = PBXBuildFile; fileRef = F48389841E1DDF2B0076B7EA /* EditingHistoryUtil.js */; };
     
    1518715188                F49786871FF45FA500E060AB /* PasteboardItemInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PasteboardItemInfo.h; sourceTree = "<group>"; };
    1518815189                F49E98E421DEE6C1009AE55E /* EditAction.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EditAction.cpp; sourceTree = "<group>"; };
     15190                F4B422C2220C0000009E1E7D /* DOMPasteAccessPolicy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DOMPasteAccessPolicy.h; sourceTree = "<group>"; };
    1518915191                F4D43D64218802E600ECECAC /* SerializedAttachmentData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SerializedAttachmentData.h; sourceTree = "<group>"; };
    1519015192                F4D9817D2195FBF6008230FC /* ChangeListTypeCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ChangeListTypeCommand.h; sourceTree = "<group>"; };
     
    2751627518                                A8185F3309765765005826D9 /* DOMImplementation.h */,
    2751727519                                93EEC1E909C2877700C515D1 /* DOMImplementation.idl */,
     27520                                F4B422C2220C0000009E1E7D /* DOMPasteAccessPolicy.h */,
    2751827521                                0F4966991DB408C100A274BB /* DOMPoint.h */,
    2751927522                                0F49669A1DB408C100A274BB /* DOMPoint.idl */,
     
    2909029093                                A9C6E4E80D745E18006442E9 /* DOMMimeTypeArray.h in Headers */,
    2909129094                                1ACE53E80A8D18E70022947D /* DOMParser.h in Headers */,
     29095                                F4B422C4220C0568009E1E7D /* DOMPasteAccessPolicy.h in Headers */,
    2909229096                                7A54881714E432A1006AE05A /* DOMPatchSupport.h in Headers */,
    2909329097                                A9C6E4EC0D745E2B006442E9 /* DOMPlugin.h in Headers */,
  • trunk/Source/WebCore/dom/UserGestureIndicator.h

    r241183 r241320  
    2626#pragma once
    2727
     28#include "DOMPasteAccessPolicy.h"
    2829#include <wtf/Function.h>
    2930#include <wtf/Noncopyable.h>
     
    6465    }
    6566
     67    DOMPasteAccessPolicy domPasteAccessPolicy() const { return m_domPasteAccessPolicy; }
     68    void didRequestDOMPasteAccess(bool granted) { m_domPasteAccessPolicy = granted ? DOMPasteAccessPolicy::Granted : DOMPasteAccessPolicy::Denied; }
     69
    6670private:
    6771    UserGestureToken(ProcessingUserGestureState state, UserGestureType gestureType)
     
    7478    Vector<WTF::Function<void (UserGestureToken&)>> m_destructionObservers;
    7579    UserGestureType m_gestureType;
     80    DOMPasteAccessPolicy m_domPasteAccessPolicy { DOMPasteAccessPolicy::NotRequestedYet };
    7681};
    7782
  • trunk/Source/WebCore/editing/EditorCommand.cpp

    r239584 r241320  
    909909        UserTypingGestureIndicator typingGestureIndicator(frame);
    910910        frame.editor().paste();
    911     } else
    912         frame.editor().paste();
     911        return true;
     912    }
     913
     914    if (!frame.requestDOMPasteAccess())
     915        return false;
     916
     917    frame.editor().paste();
    913918    return true;
    914919}
     
    935940        UserTypingGestureIndicator typingGestureIndicator(frame);
    936941        frame.editor().pasteAsPlainText();
    937     } else
    938         frame.editor().pasteAsPlainText();
     942        return true;
     943    }
     944
     945    if (!frame.requestDOMPasteAccess())
     946        return false;
     947
     948    frame.editor().pasteAsPlainText();
    939949    return true;
    940950}
     
    945955        UserTypingGestureIndicator typingGestureIndicator(frame);
    946956        frame.editor().pasteAsPlainText();
    947     } else
    948         frame.editor().pasteAsPlainText();
     957        return true;
     958    }
     959
     960    if (!frame.requestDOMPasteAccess())
     961        return false;
     962
     963    frame.editor().pasteAsPlainText();
    949964    return true;
    950965}
     
    955970        UserTypingGestureIndicator typingGestureIndicator(frame);
    956971        frame.editor().pasteAsQuotation();
    957     } else
    958         frame.editor().pasteAsQuotation();
     972        return true;
     973    }
     974
     975    if (!frame.requestDOMPasteAccess())
     976        return false;
     977
     978    frame.editor().pasteAsQuotation();
    959979    return true;
    960980}
     
    12211241        return false;
    12221242
    1223     bool defaultValue = frame->settings().javaScriptCanAccessClipboard() && frame->settings().DOMPasteAllowed();
     1243    auto& settings = frame->settings();
     1244    bool defaultValue = (settings.javaScriptCanAccessClipboard() && settings.DOMPasteAllowed()) || settings.domPasteAccessRequestsEnabled();
    12241245
    12251246    EditorClient* client = frame->editor().client();
  • trunk/Source/WebCore/loader/EmptyClients.cpp

    r241105 r241320  
    191191    void clearUndoRedoOperations() final { }
    192192
     193    bool requestDOMPasteAccess() final { return false; }
     194
    193195    bool canCopyCut(Frame*, bool defaultValue) const final { return defaultValue; }
    194196    bool canPaste(Frame*, bool defaultValue) const final { return defaultValue; }
  • trunk/Source/WebCore/page/EditorClient.h

    r239584 r241320  
    9999    virtual void handleAcceptedCandidateWithSoftSpaces(TextCheckingResult) { }
    100100
     101    virtual bool requestDOMPasteAccess() = 0;
     102
    101103    // Notify an input method that a composition was voluntarily discarded by WebCore, so that it could clean up too.
    102104    // This function is not called when a composition is closed per a request from an input method.
  • trunk/Source/WebCore/page/Frame.cpp

    r240237 r241320  
    9696#include "UserContentController.h"
    9797#include "UserContentURLPattern.h"
     98#include "UserGestureIndicator.h"
    9899#include "UserScript.h"
    99100#include "UserTypingGestureIndicator.h"
     
    659660#endif // PLATFORM(IOS_FAMILY)
    660661
     662bool Frame::requestDOMPasteAccess()
     663{
     664    if (m_settings->javaScriptCanAccessClipboard() && m_settings->DOMPasteAllowed())
     665        return true;
     666
     667    if (!m_settings->domPasteAccessRequestsEnabled() || !m_doc)
     668        return false;
     669
     670    auto gestureToken = UserGestureIndicator::currentUserGesture();
     671    if (!gestureToken || !gestureToken->processingUserGesture())
     672        return false;
     673
     674    switch (gestureToken->domPasteAccessPolicy()) {
     675    case DOMPasteAccessPolicy::Granted:
     676        return true;
     677    case DOMPasteAccessPolicy::Denied:
     678        return false;
     679    case DOMPasteAccessPolicy::NotRequestedYet: {
     680        auto* client = m_editor->client();
     681        if (!client)
     682            return false;
     683
     684        bool granted = client->requestDOMPasteAccess();
     685        gestureToken->didRequestDOMPasteAccess(granted);
     686        return granted;
     687    }
     688    }
     689}
     690
    661691void Frame::setPrinting(bool printing, const FloatSize& pageSize, const FloatSize& originalPageSize, float maximumShrinkRatio, AdjustViewSizeOrNot shouldAdjustViewSize)
    662692{
  • trunk/Source/WebCore/page/Frame.h

    r239742 r241320  
    176176    void setHasHadUserInteraction() { m_hasHadUserInteraction = true; }
    177177
     178    bool requestDOMPasteAccess();
     179
    178180// ======== All public functions below this point are candidates to move out of Frame into another class. ========
    179181
  • trunk/Source/WebCore/page/Settings.yaml

    r240644 r241320  
    345345  initial: false
    346346
     347domPasteAccessRequestsEnabled:
     348  initial: false
     349
    347350# When enabled, window.blur() does not change focus, and
    348351# window.focus() only changes focus when invoked from the context that
  • trunk/Source/WebKit/ChangeLog

    r241317 r241320  
     12019-02-12  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Allow pages to trigger programmatic paste from script on iOS
     4        https://bugs.webkit.org/show_bug.cgi?id=194271
     5        <rdar://problem/47808810>
     6
     7        Reviewed by Ryosuke Niwa.
     8
     9        * Shared/WebPreferences.yaml:
     10        * Shared/WebPreferencesDefaultValues.h:
     11
     12        Add an internal setting to enable or disable DOM paste access requests. This is on by default in iOS only
     13        (excluding watchOS and Apple TV), and is additionally disabled on macOS.
     14
     15        * UIProcess/API/gtk/PageClientImpl.cpp:
     16        (WebKit::PageClientImpl::requestDOMPasteAccess):
     17        * UIProcess/API/gtk/PageClientImpl.h:
     18        * UIProcess/API/wpe/PageClientImpl.cpp:
     19        (WebKit::PageClientImpl::requestDOMPasteAccess):
     20
     21        Plumb DOM paste access requests from the web process (WebEditorClient) to the view (WKContentView). As per the
     22        usual, this involves WebEditorClient, WebPage, WebPageProxy, PageClient and finally WKContentView.
     23
     24        * UIProcess/API/wpe/PageClientImpl.h:
     25        * UIProcess/PageClient.h:
     26        * UIProcess/WebPageProxy.cpp:
     27        (WebKit::WebPageProxy::requestDOMPasteAccess):
     28        * UIProcess/WebPageProxy.h:
     29        * UIProcess/WebPageProxy.messages.in:
     30        * UIProcess/ios/PageClientImplIOS.h:
     31        * UIProcess/ios/PageClientImplIOS.mm:
     32        (WebKit::PageClientImpl::requestDOMPasteAccess):
     33        * UIProcess/ios/WKContentViewInteraction.h:
     34        * UIProcess/ios/WKContentViewInteraction.mm:
     35        (-[WKContentView setupInteraction]):
     36        (-[WKContentView cleanupInteraction]):
     37        (-[WKContentView resignFirstResponderForWebView]):
     38        (-[WKContentView _webTouchEventsRecognized:]):
     39
     40        Bail from any pending DOM paste access handler the moment we start handling touches on the web view, or if the
     41        web view resigns first responder, or if the web process crashes.
     42
     43        (-[WKContentView textInteractionGesture:shouldBeginAtPoint:]):
     44
     45        Reject text selection gestures while waiting for DOM paste access.
     46
     47        (-[WKContentView canPerformAction:withSender:]):
     48        (-[WKContentView canPerformActionForWebView:withSender:]):
     49
     50        If we're handling a DOM paste, always return YES to allow the callout bar to show the "Paste" option.
     51
     52        (-[WKContentView _didHideMenu:]):
     53
     54        If the menu is programmatically hidden by the app while handling a DOM paste request, immediately reject the DOM
     55        paste request.
     56
     57        (-[WKContentView pasteForWebView:]):
     58
     59        Adjust -pasteForWebView: on WKContentView to first check whether there's an outstanding DOM paste completion
     60        handler to invoke, instead of telling the page to execute a paste command.
     61
     62        (-[WKContentView _handleDOMPasteRequestWithResult:]):
     63
     64        Add a helper to take and invoke the current DOM paste completion handler (if it exists) with the given result,
     65        and then dismiss the shared callout bar. Returns whether or not the paste completion handler exists. Invoked
     66        from various sources of user interaction or significant state changes (e.g. following a web process crash in
     67        -cleanupInteraction).
     68
     69        (-[WKContentView _willPerformAction:sender:]):
     70        (-[WKContentView _didPerformAction:sender:]):
     71
     72        Add hooks to detect when WKContentView is executing an editing action. This is to ensure that the page doesn't
     73        get stuck in a bad state in the case where WKWebView has been subclassed, overrides `-paste:`, and does not
     74        invoke the superclass method (which calls back into `-[WKContentView pasteForWebView:]`). There are a few
     75        possibilities here:
     76        1. WKWebView's `-paste:` action is not overridden. In this case, we will call back into `-pasteForWebView:`,
     77           which will notice that we have a pending paste completion handler and invoke it.
     78        2. WKWebView's `-paste:` action is overridden and does not call back into the content view. In this case, we
     79           will invoke the paste completion handler in `-_didPerformAction:sender:`.
     80        3. WKWebView's `-canPerformAction:withSender:` is overridden to include additional actions. In this case, we may
     81           get a call to invoke a different action selector while waiting for a potential paste action. If this happens,
     82           prevent the DOM paste in `-_willPerformAction:sender:` prior to handling the other action.
     83
     84        (-[WKContentView handleKeyWebEvent:withCompletionHandler:]):
     85
     86        Dismiss DOM paste UI upon handling any key event.
     87
     88        (-[WKContentView showGlobalMenuControllerInRect:]):
     89        (-[WKContentView hideGlobalMenuController]):
     90
     91        Helper methods to present and dismiss the global UIMenuController, that accounts for available platform APIs for
     92        presenting or dismissing the menu controller on iOS.
     93
     94        (-[WKContentView _requestDOMPasteAccessWithElementRect:completionHandler:]):
     95
     96        Attempt to find a good target presentation rect when showing the callout menu. First, we will try to use the
     97        rect of the element the user has interacted with when triggering the paste. If such an element is too large or
     98        does not exist, we fall back to presenting the callout menu near the user's last touch location (with a small
     99        amount of margin, such that the action doesn't overlap with the user's finger, stylus, etc.).
     100
     101        (-[WKContentView _resetShowingTextStyle:]): Deleted.
     102
     103        Rename this to `-_didHideMenu:`.
     104
     105        * UIProcess/mac/PageClientImplMac.h:
     106        * UIProcess/win/PageClientImpl.cpp:
     107        (WebKit::PageClientImpl::requestDOMPasteAccess):
     108        * UIProcess/win/PageClientImpl.h:
     109        * WebProcess/WebCoreSupport/WebEditorClient.cpp:
     110        (WebKit::WebEditorClient::requestDOMPasteAccess):
     111        * WebProcess/WebCoreSupport/WebEditorClient.h:
     112        * WebProcess/WebPage/WebPage.cpp:
     113        (WebKit::WebPage::requestDOMPasteAccess):
     114
     115        Add more plumbing and method stubs.
     116
     117        (WebKit::WebPage::updateCurrentModifierState):
     118        (WebKit::WebPage::rectForElementAtInteractionLocation const):
     119        * WebProcess/WebPage/WebPage.h:
     120        * WebProcess/WebPage/ios/WebPageIOS.mm:
     121        (WebKit::WebPage::rectForElementAtInteractionLocation const):
     122        (WebKit::WebPage::rectForElementAtInteractionLocation): Deleted.
     123
     124        Mark this method as const, add a platform-agnostic stub, and adopt it for the purposes of determining where to
     125        position the callout bar when pasting.
     126
    11272019-02-12  Alex Christensen  <achristensen@webkit.org>
    2128
  • trunk/Source/WebKit/Shared/WebPreferences.yaml

    r240874 r241320  
    15351535  category: internal
    15361536
     1537DOMPasteAccessRequestsEnabled:
     1538  type: bool
     1539  defaultValue: DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED
     1540  humanReadableName: "DOM Paste Access Requests"
     1541  humanReadableDescription: "Enable DOM Paste Access Requests"
     1542  category: internal
     1543
    15371544# Deprecated
    15381545
  • trunk/Source/WebKit/Shared/WebPreferencesDefaultValues.h

    r239704 r241320  
    243243#define DEFAULT_DATALIST_ELEMENT_ENABLED true
    244244#endif
     245
     246#if PLATFORM(IOS)
     247#define DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED true
     248#else
     249#define DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED false
     250#endif
  • trunk/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp

    r241224 r241320  
    515515#endif
    516516
     517void PageClientImpl::requestDOMPasteAccess(const IntRect&, CompletionHandler<void(bool)>&& completionHandler)
     518{
     519    completionHandler(false);
     520}
     521
    517522} // namespace WebKit
  • trunk/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h

    r241224 r241320  
    149149    void didFinishProcessingAllPendingMouseEvents() final { }
    150150
     151    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
     152
    151153#if ENABLE(VIDEO) && USE(GSTREAMER)
    152154    bool decidePolicyForInstallMissingMediaPluginsPermissionRequest(InstallMissingMediaPluginsPermissionRequest&) override;
  • trunk/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp

    r241260 r241320  
    390390#endif // ENABLE(FULLSCREEN_API)
    391391
     392void PageClientImpl::requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&& completionHandler)
     393{
     394    completionHandler(false);
     395}
     396
    392397} // namespace WebKit
  • trunk/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h

    r241260 r241320  
    145145
    146146    IPC::Attachment hostFileDescriptor() final;
     147    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
    147148
    148149    WebCore::UserInterfaceLayoutDirection userInterfaceLayoutDirection() override;
  • trunk/Source/WebKit/UIProcess/PageClient.h

    r241260 r241320  
    466466#endif
    467467
     468    virtual void requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&&) = 0;
     469
    468470#if ENABLE(ATTACHMENT_ELEMENT)
    469471    virtual void didInsertAttachment(API::Attachment&, const String& source) { }
  • trunk/Source/WebKit/UIProcess/WebPageProxy.cpp

    r241270 r241320  
    54405440}
    54415441
     5442void WebPageProxy::requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&& completionHandler)
     5443{
     5444    m_pageClient->requestDOMPasteAccess(elementRect, WTFMove(completionHandler));
     5445}
     5446
    54425447// BackForwardList
    54435448
  • trunk/Source/WebKit/UIProcess/WebPageProxy.h

    r241282 r241320  
    16601660    void setNeedsPlainTextQuirk(bool);
    16611661
     1662    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&);
     1663
    16621664    // Back/Forward list management
    16631665    void backForwardAddItem(BackForwardListItemState&&);
  • trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in

    r241146 r241320  
    255255    SetNeedsHiddenContentEditableQuirk(bool needsHiddenContentEditableQuirk)
    256256    SetNeedsPlainTextQuirk(bool needsPlainTextQuirk)
     257    RequestDOMPasteAccess(WebCore::IntRect elementRect) -> (bool granted) Delayed
    257258
    258259    # Find messages
  • trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.h

    r240875 r241320  
    225225#endif
    226226
     227    void requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&&) final;
     228
    227229#if ENABLE(DATA_INTERACTION)
    228230    void didPerformDragOperation(bool handled) override;
  • trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm

    r240875 r241320  
    838838#endif
    839839
     840void PageClientImpl::requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&& completionHandler)
     841{
     842    [m_contentView _requestDOMPasteAccessWithElementRect:elementRect completionHandler:WTFMove(completionHandler)];
     843}
     844
    840845#if HAVE(PENCILKIT)
    841846RetainPtr<WKDrawingView> PageClientImpl::createDrawingView(WebCore::GraphicsLayer::EmbeddedViewID embeddedViewID)
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h

    r241282 r241320  
    5050#import <WebCore/FloatQuad.h>
    5151#import <wtf/BlockPtr.h>
     52#import <wtf/CompletionHandler.h>
    5253#import <wtf/Forward.h>
    5354#import <wtf/OptionSet.h>
     
    314315
    315316    BOOL _hasSetUpInteractions;
     317    CompletionHandler<void(bool)> _domPasteRequestHandler;
    316318
    317319#if ENABLE(DATA_INTERACTION)
     
    435437- (WKFormInputSession *)_formInputSession;
    436438
     439- (void)_requestDOMPasteAccessWithElementRect:(const WebCore::IntRect&)elementRect completionHandler:(CompletionHandler<void(bool)>&&)completionHandler;
     440
    437441@property (nonatomic, readonly) WebKit::InteractionInformationAtPosition currentPositionInformation;
    438442- (void)doAfterPositionInformationUpdate:(void (^)(WebKit::InteractionInformationAtPosition))action forRequest:(WebKit::InteractionInformationRequest)request;
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

    r241311 r241320  
    741741
    742742    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    743     [center addObserver:self selector:@selector(_resetShowingTextStyle:) name:UIMenuControllerDidHideMenuNotification object:nil];
     743    [center addObserver:self selector:@selector(_didHideMenu:) name:UIMenuControllerDidHideMenuNotification object:nil];
    744744    [center addObserver:self selector:@selector(_keyboardDidRequestDismissal:) name:UIKeyboardPrivateDidRequestDismissalNotification object:nil];
    745745
     
    887887    [self _resetPanningPreventionFlags];
    888888#endif
     889    [self _handleDOMPasteRequestWithResult:NO];
    889890}
    890891
     
    11441145    bool superDidResign = [super resignFirstResponder];
    11451146
    1146     if (superDidResign)
     1147    if (superDidResign) {
     1148        [self _handleDOMPasteRequestWithResult:NO];
    11471149        _page->activityStateDidChange(WebCore::ActivityState::IsFocused);
     1150    }
    11481151
    11491152    return superDidResign;
     
    11791182
    11801183    _lastInteractionLocation = lastTouchEvent->locationInDocumentCoordinates;
    1181     if (lastTouchEvent->type == UIWebTouchEventTouchBegin)
     1184    if (lastTouchEvent->type == UIWebTouchEventTouchBegin) {
     1185        [self _handleDOMPasteRequestWithResult:NO];
    11821186        _layerTreeTransactionIdAtLastTouchStart = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).lastCommittedLayerTreeTransactionID();
     1187    }
    11831188
    11841189#if ENABLE(TOUCH_EVENTS)
     
    19791984{
    19801985    if (!_webView.configuration._textInteractionGesturesEnabled)
     1986        return NO;
     1987
     1988    if (_domPasteRequestHandler)
    19811989        return NO;
    19821990
     
    23982406    - (void)_action:(id)sender \
    23992407    { \
     2408        SEL action = @selector(_action:);\
     2409        [self _willPerformAction:action sender:sender];\
    24002410        [_webView _action:sender]; \
     2411        [self _didPerformAction:action sender:sender];\
    24012412    }
    24022413
     
    26432654- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
    26442655{
     2656    if (_domPasteRequestHandler)
     2657        return action == @selector(paste:);
     2658
    26452659    return [_webView canPerformAction:action withSender:sender];
    26462660}
     
    26482662- (BOOL)canPerformActionForWebView:(SEL)action withSender:(id)sender
    26492663{
     2664    if (_domPasteRequestHandler)
     2665        return action == @selector(paste:);
     2666
    26502667    if (action == @selector(_nextAccessoryTab:))
    26512668        return hasFocusedElement(_focusedElementInformation) && _focusedElementInformation.hasNextNode;
     
    27942811}
    27952812
    2796 - (void)_resetShowingTextStyle:(NSNotification *)notification
     2813- (void)_didHideMenu:(NSNotification *)notification
    27972814{
    27982815    _showingTextStyleOptions = NO;
    27992816    [_textSelectionAssistant hideTextStyleOptions];
     2817    [self _handleDOMPasteRequestWithResult:NO];
    28002818}
    28012819
     
    28192837- (void)pasteForWebView:(id)sender
    28202838{
     2839    if (sender == UIMenuController.sharedMenuController && [self _handleDOMPasteRequestWithResult:YES])
     2840        return;
     2841
    28212842    _page->executeEditCommand("paste"_s);
    28222843}
     
    29462967{
    29472968    _page->storeSelectionForAccessibility(false);
     2969}
     2970
     2971- (BOOL)_handleDOMPasteRequestWithResult:(BOOL)allowPaste
     2972{
     2973    if (auto pasteHandler = WTFMove(_domPasteRequestHandler)) {
     2974        [self hideGlobalMenuController];
     2975        pasteHandler(allowPaste);
     2976        return YES;
     2977    }
     2978    return NO;
     2979}
     2980
     2981- (void)_willPerformAction:(SEL)action sender:(id)sender
     2982{
     2983    if (action != @selector(paste:))
     2984        [self _handleDOMPasteRequestWithResult:NO];
     2985}
     2986
     2987- (void)_didPerformAction:(SEL)action sender:(id)sender
     2988{
     2989    if (action == @selector(paste:))
     2990        [self _handleDOMPasteRequestWithResult:NO];
    29482991}
    29492992
     
    40954138- (void)handleKeyWebEvent:(::WebEvent *)theEvent withCompletionHandler:(void (^)(::WebEvent *theEvent, BOOL wasHandled))completionHandler
    40964139{
     4140    [self _handleDOMPasteRequestWithResult:NO];
     4141
    40974142    _keyWebEventHandler = makeBlockPtr(completionHandler);
    40984143    _page->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(theEvent));
     
    47924837    [self reloadInputViews];
    47934838#endif
     4839}
     4840
     4841- (void)showGlobalMenuControllerInRect:(CGRect)rect
     4842{
     4843    UIMenuController *controller = UIMenuController.sharedMenuController;
     4844#if HAVE(MENU_CONTROLLER_SHOW_HIDE_API)
     4845    [controller showMenuFromView:self rect:rect];
     4846#else
     4847    [controller setTargetRect:rect inView:self];
     4848    [controller setMenuVisible:YES animated:YES];
     4849#endif
     4850}
     4851
     4852- (void)hideGlobalMenuController
     4853{
     4854    UIMenuController *controller = UIMenuController.sharedMenuController;
     4855#if HAVE(MENU_CONTROLLER_SHOW_HIDE_API)
     4856    [controller hideMenuFromView:self];
     4857#else
     4858    [controller setMenuVisible:NO animated:YES];
     4859#endif
     4860}
     4861
     4862- (void)_requestDOMPasteAccessWithElementRect:(const WebCore::IntRect&)elementRect completionHandler:(CompletionHandler<void(bool)>&&)completionHandler
     4863{
     4864    if (auto existingCompletionHandler = std::exchange(_domPasteRequestHandler, WTFMove(completionHandler))) {
     4865        ASSERT_NOT_REACHED();
     4866        existingCompletionHandler(false);
     4867    }
     4868
     4869    WebCore::IntRect menuControllerRect = elementRect;
     4870
     4871    const CGFloat maximumElementWidth = 300;
     4872    const CGFloat maximumElementHeight = 120;
     4873    if (elementRect.isEmpty() || elementRect.width() > maximumElementWidth || elementRect.height() > maximumElementHeight) {
     4874        const CGFloat interactionLocationMargin = 10;
     4875        menuControllerRect = { WebCore::IntPoint(_lastInteractionLocation), { } };
     4876        menuControllerRect.inflate(interactionLocationMargin);
     4877    }
     4878
     4879    [self showGlobalMenuControllerInRect:menuControllerRect];
    47944880}
    47954881
  • trunk/Source/WebKit/UIProcess/mac/PageClientImplMac.h

    r240490 r241320  
    3131#include "PageClientImplCocoa.h"
    3232#include "WebFullScreenManagerProxy.h"
     33#include <wtf/CompletionHandler.h>
    3334#include <wtf/RetainPtr.h>
    3435
     
    216217    void didRemoveNavigationGestureSnapshot() override;
    217218
     219    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&& completion) final { completion(false); }
     220
    218221    NSView *activeView() const;
    219222    NSWindow *activeWindow() const;
  • trunk/Source/WebKit/UIProcess/win/PageClientImpl.cpp

    r240046 r241320  
    358358}
    359359
     360void PageClientImpl::requestDOMPasteAccess(const IntRect&, CompletionHandler<void(bool)>&& completionHandler)
     361{
     362    completionHandler(false);
     363}
     364
    360365} // namespace WebKit
  • trunk/Source/WebKit/UIProcess/win/PageClientImpl.h

    r240046 r241320  
    143143    void didFinishProcessingAllPendingMouseEvents() final { }
    144144
     145    void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final;
     146
    145147    // Members of PageClientImpl class
    146148    DefaultUndoController m_undoController;
  • trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp

    r240342 r241320  
    360360}
    361361
     362bool WebEditorClient::requestDOMPasteAccess()
     363{
     364    return m_page->requestDOMPasteAccess();
     365}
     366
    362367#if PLATFORM(WIN)
    363368void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event)
  • trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h

    r239584 r241320  
    8989    void registerRedoStep(WebCore::UndoStep&) final;
    9090    void clearUndoRedoOperations() final;
     91
     92    bool requestDOMPasteAccess() final;
    9193
    9294    bool canCopyCut(WebCore::Frame*, bool defaultValue) const final;
  • trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp

    r241306 r241320  
    64256425}
    64266426
     6427bool WebPage::requestDOMPasteAccess()
     6428{
     6429    bool granted = false;
     6430    if (!sendSyncWithDelayedReply(Messages::WebPageProxy::RequestDOMPasteAccess(rectForElementAtInteractionLocation()), Messages::WebPageProxy::RequestDOMPasteAccess::Reply(granted)))
     6431        return false;
     6432
     6433    return granted;
     6434}
     6435
    64276436void WebPage::simulateDeviceOrientationChange(double alpha, double beta, double gamma)
    64286437{
     
    64986507{
    64996508    PlatformKeyboardEvent::setCurrentModifierState(modifiers);
    6500 }   
     6509}
     6510
     6511#if !PLATFORM(IOS_FAMILY)
     6512
     6513WebCore::IntRect WebPage::rectForElementAtInteractionLocation() const
     6514{
     6515    return { };
     6516}
     6517
     6518#endif // !PLATFORM(IOS_FAMILY)
    65016519
    65026520} // namespace WebKit
  • trunk/Source/WebKit/WebProcess/WebPage/WebPage.h

    r241306 r241320  
    659659    void setFocusedElementValueAsNumber(double);
    660660    void setFocusedElementSelectedIndex(uint32_t index, bool allowMultipleSelection);
    661     WebCore::IntRect rectForElementAtInteractionLocation();
    662661    void updateSelectionAppearance();
    663662    void getSelectionContext(CallbackID);
     
    11491148        return sendSync(WTFMove(message), WTFMove(reply), m_pageID, Seconds::infinity(), IPC::SendSyncOption::InformPlatformProcessWillSuspend);
    11501149    }
     1150
     1151    bool requestDOMPasteAccess();
     1152    WebCore::IntRect rectForElementAtInteractionLocation() const;
    11511153
    11521154private:
  • trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm

    r241282 r241320  
    512512}
    513513
    514 IntRect WebPage::rectForElementAtInteractionLocation()
     514IntRect WebPage::rectForElementAtInteractionLocation() const
    515515{
    516516    HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint(m_lastInteractionLocation, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AllowChildFrameContent);
  • trunk/Source/WebKitLegacy/mac/ChangeLog

    r241299 r241320  
     12019-02-12  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Allow pages to trigger programmatic paste from script on iOS
     4        https://bugs.webkit.org/show_bug.cgi?id=194271
     5        <rdar://problem/47808810>
     6
     7        Reviewed by Ryosuke Niwa.
     8
     9        See WebCore and WebKit ChangeLogs for more details.
     10
     11        * WebCoreSupport/WebEditorClient.h:
     12
    1132019-02-12  Andy Estes  <aestes@apple.com>
    214
  • trunk/Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h

    r239584 r241320  
    8282
    8383    void setInsertionPasteboard(const String&) final;
     84    bool requestDOMPasteAccess() final { return false; }
    8485
    8586#if USE(APPKIT)
  • trunk/Source/WebKitLegacy/win/ChangeLog

    r241044 r241320  
     12019-02-12  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Allow pages to trigger programmatic paste from script on iOS
     4        https://bugs.webkit.org/show_bug.cgi?id=194271
     5        <rdar://problem/47808810>
     6
     7        Reviewed by Ryosuke Niwa.
     8
     9        * WebCoreSupport/WebEditorClient.h:
     10
    1112019-02-06  Daniel Bates  <dabates@apple.com>
    212
  • trunk/Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h

    r227068 r241320  
    116116    bool performTwoStepDrop(WebCore::DocumentFragment&, WebCore::Range&, bool) final { return false; }
    117117
     118    bool requestDOMPasteAccess() final { return false; }
     119
    118120    WebCore::TextCheckerClient* textChecker() final { return this; }
    119121
Note: See TracChangeset for help on using the changeset viewer.