WebKit Bugzilla
Attachment 361582 Details for
Bug 194271
: Allow pages to trigger programmatic paste from script on iOS
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Combined patch (v2)
bug-194271-20190208191213.patch (text/plain), 104.68 KB, created by
Wenson Hsieh
on 2019-02-08 19:12:14 PST
(
hide
)
Description:
Combined patch (v2)
Filename:
MIME Type:
Creator:
Wenson Hsieh
Created:
2019-02-08 19:12:14 PST
Size:
104.68 KB
patch
obsolete
>Subversion Revision: 241183 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index a04c1dfde31711154bc87260a81b59071b566c83..b70456a80ce4a7e72c4eef59c05f1527c6878576 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,81 @@ >+2019-02-08 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Allow pages to trigger programmatic paste from script on iOS >+ https://bugs.webkit.org/show_bug.cgi?id=194271 >+ <rdar://problem/47808810> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Tests: editing/pasteboard/ios/dom-paste-confirmation.html >+ editing/pasteboard/ios/dom-paste-consecutive-confirmations.html >+ editing/pasteboard/ios/dom-paste-rejection.html >+ editing/pasteboard/ios/dom-paste-requires-user-gesture.html >+ >+ * dom/UserGestureIndicator.cpp: >+ (WebCore::UserGestureIndicator::~UserGestureIndicator): >+ >+ Reset a gesture token's DOM paste access when exiting the scope of a user gesture. This prevents DOM paste >+ access permissions from leaking into `setTimeout()` callbacks when we forward user gesture tokens. >+ >+ * dom/UserGestureIndicator.h: >+ (WebCore::UserGestureToken::resetDOMPasteAccess): >+ >+2019-02-07 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Allow pages to trigger programmatic paste from script on iOS >+ https://bugs.webkit.org/show_bug.cgi?id=194271 >+ <rdar://problem/47808810> >+ >+ Reviewed by Ryosuke Niwa. >+ >+ Add support for allowing script to trigger programmatic paste commands. Currently on macOS and iOS, the ability >+ to trigger programmatic paste (i.e. `document.execCommand('Paste');`) is disabled by default, such that >+ execCommand is simply a no-op that returns false. This policy is a privacy measure (common among other major >+ browsers) that prevents untrusted web content from sniffing content from the system pasteboard (even on user >+ interaction, since unintended user interaction occasionally happens as well!). >+ >+ In order to make it possible for web pages to programmatically paste without opening the door to privacy and >+ security issues, we make paste commands triggered from bindings present platform UI on iOS, in the form of a >+ callout bar with the single option to paste. This UI is dismissed upon any user interaction; furthermore, any >+ user interaction short of explicitly triggering the "Paste" action subsequently prevents the page from executing >+ the paste (and causes execCommand to return false). However, if the paste action is chosen by the user, we >+ instead follow through with the programmatic paste command. >+ >+ New tests to come in a followup patch. >+ >+ * WebCore.xcodeproj/project.pbxproj: >+ * dom/DOMPasteAccessPolicy.h: Added. >+ * dom/UserGestureIndicator.h: >+ (WebCore::UserGestureToken::domPasteAccessPolicy const): >+ (WebCore::UserGestureToken::didRequestDOMPasteAccess): >+ >+ Add helpers on UserGestureToken to update and query the current DOM paste access policy. The access policies are >+ "NotRequestedYet" (i.e. pending a response from the user), "Granted" (the user has granted DOM paste access to >+ the page), or "Denied" (the user has prevented the page from reading the contents of the clipboard). When DOM >+ paste access is granted or rejected, make this decision sticky until the end of the current user gesture. >+ >+ * editing/EditorCommand.cpp: >+ (WebCore::executePaste): >+ (WebCore::executePasteAndMatchStyle): >+ (WebCore::executePasteAsPlainText): >+ (WebCore::executePasteAsQuotation): >+ >+ When executing a paste command where the source is DOM bindings, request DOM paste if needed before proceeding >+ with the paste. >+ >+ (WebCore::supportedPaste): >+ * loader/EmptyClients.cpp: >+ * page/EditorClient.h: >+ * page/Frame.cpp: >+ (WebCore::Frame::requestDOMPasteAccess): >+ >+ Add a helper method that requests access to the clipboard on behalf of script when pasting. >+ >+ * page/Frame.h: >+ * page/Settings.yaml: >+ >+ Introduce a new WebCore setting, used to gate DOM paste access requests. >+ > 2019-02-07 Chris Dumez <cdumez@apple.com> > > Mark more heap-allocated classes as fast allocated >diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog >index a8280858f28ae3c63a1ae987f8b53c2a08231b5a..0704f1d3a6f6e5462d95a7bc5235ae5a0a0d62f5 100644 >--- a/Source/WebKit/ChangeLog >+++ b/Source/WebKit/ChangeLog >@@ -1,3 +1,150 @@ >+2019-02-08 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Allow pages to trigger programmatic paste from script on iOS >+ https://bugs.webkit.org/show_bug.cgi?id=194271 >+ <rdar://problem/47808810> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Cancel the pending DOM paste access handler when the menu is about to hide, rather than when the hiding >+ animation has completed. This ensures that if the page (on behalf of the user) requests DOM paste again during >+ user interaction before the callout bar has finished fading after the previous DOM paste, we won't automatically >+ cancel the incoming DOM paste access request because the callout bar animation finished. >+ >+ This scenario is exercised in the layout test editing/pasteboard/ios/dom-paste-consecutive-confirmations.html. >+ >+ * Platform/spi/ios/UIKitSPI.h: >+ * UIProcess/ios/WKContentViewInteraction.mm: >+ (-[WKContentView setupInteraction]): >+ (-[WKContentView _willHideMenu:]): >+ (-[WKContentView _didHideMenu:]): >+ >+2019-02-07 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Allow pages to trigger programmatic paste from script on iOS >+ https://bugs.webkit.org/show_bug.cgi?id=194271 >+ <rdar://problem/47808810> >+ >+ Reviewed by Ryosuke Niwa. >+ >+ * Shared/WebPreferences.yaml: >+ * Shared/WebPreferencesDefaultValues.h: >+ >+ Add an internal setting to enable or disable DOM paste access requests. This is on by default in iOS only >+ (excluding watchOS and Apple TV), and is additionally disabled on macOS. >+ >+ * UIProcess/API/gtk/PageClientImpl.cpp: >+ (WebKit::PageClientImpl::requestDOMPasteAccess): >+ * UIProcess/API/gtk/PageClientImpl.h: >+ * UIProcess/API/wpe/PageClientImpl.cpp: >+ (WebKit::PageClientImpl::requestDOMPasteAccess): >+ >+ Plumb DOM paste access requests from the web process (WebEditorClient) to the view (WKContentView). As per the >+ usual, this involves WebEditorClient, WebPage, WebPageProxy, PageClient and finally WKContentView. >+ >+ * UIProcess/API/wpe/PageClientImpl.h: >+ * UIProcess/PageClient.h: >+ * UIProcess/WebPageProxy.cpp: >+ (WebKit::WebPageProxy::requestDOMPasteAccess): >+ * UIProcess/WebPageProxy.h: >+ * UIProcess/WebPageProxy.messages.in: >+ * UIProcess/ios/PageClientImplIOS.h: >+ * UIProcess/ios/PageClientImplIOS.mm: >+ (WebKit::PageClientImpl::requestDOMPasteAccess): >+ * UIProcess/ios/WKContentViewInteraction.h: >+ * UIProcess/ios/WKContentViewInteraction.mm: >+ (-[WKContentView setupInteraction]): >+ (-[WKContentView cleanupInteraction]): >+ (-[WKContentView resignFirstResponderForWebView]): >+ (-[WKContentView _webTouchEventsRecognized:]): >+ >+ Bail from any pending DOM paste access handler the moment we start handling touches on the web view, or if the >+ web view resigns first responder, or if the web process crashes. >+ >+ (-[WKContentView textInteractionGesture:shouldBeginAtPoint:]): >+ >+ Reject text selection gestures while waiting for DOM paste access. >+ >+ (-[WKContentView canPerformAction:withSender:]): >+ (-[WKContentView canPerformActionForWebView:withSender:]): >+ >+ If we're handling a DOM paste, always return YES to allow the callout bar to show the "Paste" option. >+ >+ (-[WKContentView _didHideMenu:]): >+ >+ If the menu is programmatically hidden by the app while handling a DOM paste request, immediately reject the DOM >+ paste request. >+ >+ (-[WKContentView pasteForWebView:]): >+ >+ Adjust -pasteForWebView: on WKContentView to first check whether there's an outstanding DOM paste completion >+ handler to invoke, instead of telling the page to execute a paste command. >+ >+ (-[WKContentView _handleDOMPasteRequestWithResult:]): >+ >+ Add a helper to take and invoke the current DOM paste completion handler (if it exists) with the given result, >+ and then dismiss the shared callout bar. Returns whether or not the paste completion handler exists. Invoked >+ from various sources of user interaction or significant state changes (e.g. following a web process crash in >+ -cleanupInteraction). >+ >+ (-[WKContentView _willPerformAction:sender:]): >+ (-[WKContentView _didPerformAction:sender:]): >+ >+ Add hooks to detect when WKContentView is executing an editing action. This is to ensure that the page doesn't >+ get stuck in a bad state in the case where WKWebView has been subclassed, overrides `-paste:`, and does not >+ invoke the superclass method (which calls back into `-[WKContentView pasteForWebView:]`). There are a few >+ possibilities here: >+ 1. WKWebView's `-paste:` action is not overridden. In this case, we will call back into `-pasteForWebView:`, >+ which will notice that we have a pending paste completion handler and invoke it. >+ 2. WKWebView's `-paste:` action is overridden and does not call back into the content view. In this case, we >+ will invoke the paste completion handler in `-_didPerformAction:sender:`. >+ 3. WKWebView's `-canPerformAction:withSender:` is overridden to include additional actions. In this case, we may >+ get a call to invoke a different action selector while waiting for a potential paste action. If this happens, >+ prevent the DOM paste in `-_willPerformAction:sender:` prior to handling the other action. >+ >+ (-[WKContentView handleKeyWebEvent:withCompletionHandler:]): >+ >+ Dismiss DOM paste UI upon handling any key event. >+ >+ (-[WKContentView showGlobalMenuControllerInRect:]): >+ (-[WKContentView hideGlobalMenuController]): >+ >+ Helper methods to present and dismiss the global UIMenuController, that accounts for available platform APIs for >+ presenting or dismissing the menu controller on iOS. >+ >+ (-[WKContentView _requestDOMPasteAccessWithElementRect:completionHandler:]): >+ >+ Attempt to find a good target presentation rect when showing the callout menu. First, we will try to use the >+ rect of the element the user has interacted with when triggering the paste. If such an element is too large or >+ does not exist, we fall back to presenting the callout menu near the user's last touch location (with a small >+ amount of margin, such that the action doesn't overlap with the user's finger, stylus, etc.). >+ >+ (-[WKContentView _resetShowingTextStyle:]): Deleted. >+ >+ Rename this to `-_didHideMenu:`. >+ >+ * UIProcess/mac/PageClientImplMac.h: >+ * UIProcess/win/PageClientImpl.cpp: >+ (WebKit::PageClientImpl::requestDOMPasteAccess): >+ * UIProcess/win/PageClientImpl.h: >+ * WebProcess/WebCoreSupport/WebEditorClient.cpp: >+ (WebKit::WebEditorClient::requestDOMPasteAccess): >+ * WebProcess/WebCoreSupport/WebEditorClient.h: >+ * WebProcess/WebPage/WebPage.cpp: >+ (WebKit::WebPage::requestDOMPasteAccess): >+ >+ Add more plumbing and method stubs. >+ >+ (WebKit::WebPage::updateCurrentModifierState): >+ (WebKit::WebPage::rectForElementAtInteractionLocation const): >+ * WebProcess/WebPage/WebPage.h: >+ * WebProcess/WebPage/ios/WebPageIOS.mm: >+ (WebKit::WebPage::rectForElementAtInteractionLocation const): >+ (WebKit::WebPage::rectForElementAtInteractionLocation): Deleted. >+ >+ Mark this method as const, add a platform-agnostic stub, and adopt it for the purposes of determining where to >+ position the callout bar when pasting. >+ > 2019-02-07 Chris Dumez <cdumez@apple.com> > > Mark more heap-allocated classes as fast allocated >diff --git a/Source/WebKitLegacy/mac/ChangeLog b/Source/WebKitLegacy/mac/ChangeLog >index 9a36586501c2dd39f6780b66302a81163da64397..9cca2abd6c0aac43608dc5bc0dfc502f3d33b6e4 100644 >--- a/Source/WebKitLegacy/mac/ChangeLog >+++ b/Source/WebKitLegacy/mac/ChangeLog >@@ -1,3 +1,15 @@ >+2019-02-07 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Allow pages to trigger programmatic paste from script on iOS >+ https://bugs.webkit.org/show_bug.cgi?id=194271 >+ <rdar://problem/47808810> >+ >+ Reviewed by Ryosuke Niwa. >+ >+ See WebCore and WebKit ChangeLogs for more details. >+ >+ * WebCoreSupport/WebEditorClient.h: >+ > 2019-02-06 Andy Estes <aestes@apple.com> > > [Payment Request] It should be possible to require a phonetic name for shipping contacts >diff --git a/Source/WebKitLegacy/win/ChangeLog b/Source/WebKitLegacy/win/ChangeLog >index e90cf1ac20f5f5080875b617bc64b8d4bda63d01..a74dafaf58938a85d879a372485cff6f65889aab 100644 >--- a/Source/WebKitLegacy/win/ChangeLog >+++ b/Source/WebKitLegacy/win/ChangeLog >@@ -1,3 +1,13 @@ >+2019-02-07 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Allow pages to trigger programmatic paste from script on iOS >+ https://bugs.webkit.org/show_bug.cgi?id=194271 >+ <rdar://problem/47808810> >+ >+ Reviewed by Ryosuke Niwa. >+ >+ * WebCoreSupport/WebEditorClient.h: >+ > 2019-02-06 Daniel Bates <dabates@apple.com> > > Standardize on ControlKey instead of CtrlKey >diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj >index 1e8b88343aaecfe2682d199c893d1735cedb8595..cb2b10f8ff81176dfe7e2e493155e368eaca16da 100644 >--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj >+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj >@@ -4950,6 +4950,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, ); }; }; >@@ -15182,6 +15183,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>"; }; >@@ -27509,6 +27511,7 @@ > A8185F3609765765005826D9 /* DOMImplementation.cpp */, > A8185F3309765765005826D9 /* DOMImplementation.h */, > 93EEC1E909C2877700C515D1 /* DOMImplementation.idl */, >+ F4B422C2220C0000009E1E7D /* DOMPasteAccessPolicy.h */, > 0F4966991DB408C100A274BB /* DOMPoint.h */, > 0F49669A1DB408C100A274BB /* DOMPoint.idl */, > 0F4966A21DB4091000A274BB /* DOMPointInit.h */, >@@ -29083,6 +29086,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 */, >diff --git a/Source/WebCore/dom/DOMPasteAccessPolicy.h b/Source/WebCore/dom/DOMPasteAccessPolicy.h >new file mode 100644 >index 0000000000000000000000000000000000000000..80018b921ca27dd76c2ca9293cb00432bb582463 >--- /dev/null >+++ b/Source/WebCore/dom/DOMPasteAccessPolicy.h >@@ -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 >+}; >+ >+} >diff --git a/Source/WebCore/dom/UserGestureIndicator.cpp b/Source/WebCore/dom/UserGestureIndicator.cpp >index f691d386d6bad1ae989d489cf095dc3e47380267..35665dd1c31d1d208274da8a938d62ade521a95d 100644 >--- a/Source/WebCore/dom/UserGestureIndicator.cpp >+++ b/Source/WebCore/dom/UserGestureIndicator.cpp >@@ -87,6 +87,9 @@ UserGestureIndicator::~UserGestureIndicator() > if (!isMainThread()) > return; > >+ if (auto token = currentToken()) >+ token->resetDOMPasteAccess(); >+ > currentToken() = m_previousToken; > } > >diff --git a/Source/WebCore/dom/UserGestureIndicator.h b/Source/WebCore/dom/UserGestureIndicator.h >index c18cdc0c6cedc1b8281a7b0d742105065381f4fc..ddcbdd6490066875de58dd4006b57534c63e3089 100644 >--- a/Source/WebCore/dom/UserGestureIndicator.h >+++ b/Source/WebCore/dom/UserGestureIndicator.h >@@ -25,6 +25,7 @@ > > #pragma once > >+#include "DOMPasteAccessPolicy.h" > #include <wtf/Function.h> > #include <wtf/Noncopyable.h> > #include <wtf/Optional.h> >@@ -63,6 +64,10 @@ public: > m_destructionObservers.append(WTFMove(observer)); > } > >+ DOMPasteAccessPolicy domPasteAccessPolicy() const { return m_domPasteAccessPolicy; } >+ void didRequestDOMPasteAccess(bool granted) { m_domPasteAccessPolicy = granted ? DOMPasteAccessPolicy::Granted : DOMPasteAccessPolicy::Denied; } >+ void resetDOMPasteAccess() { m_domPasteAccessPolicy = DOMPasteAccessPolicy::NotRequestedYet; } >+ > private: > UserGestureToken(ProcessingUserGestureState state, UserGestureType gestureType) > : m_state(state) >@@ -73,6 +78,7 @@ private: > ProcessingUserGestureState m_state = NotProcessingUserGesture; > Vector<WTF::Function<void (UserGestureToken&)>> m_destructionObservers; > UserGestureType m_gestureType; >+ DOMPasteAccessPolicy m_domPasteAccessPolicy { DOMPasteAccessPolicy::NotRequestedYet }; > }; > > class UserGestureIndicator { >diff --git a/Source/WebCore/editing/EditorCommand.cpp b/Source/WebCore/editing/EditorCommand.cpp >index 0b3bbb9be69569eb82cf054dc24c715e7e4ad438..9494709dc6f159c08172b6bfcb22873165d46525 100644 >--- a/Source/WebCore/editing/EditorCommand.cpp >+++ b/Source/WebCore/editing/EditorCommand.cpp >@@ -908,8 +908,13 @@ static bool executePaste(Frame& frame, Event*, EditorCommandSource source, const > if (source == CommandFromMenuOrKeyBinding) { > UserTypingGestureIndicator typingGestureIndicator(frame); > frame.editor().paste(); >- } else >- frame.editor().paste(); >+ return true; >+ } >+ >+ if (!frame.requestDOMPasteAccess()) >+ return false; >+ >+ frame.editor().paste(); > return true; > } > >@@ -934,8 +939,13 @@ static bool executePasteAndMatchStyle(Frame& frame, Event*, EditorCommandSource > if (source == CommandFromMenuOrKeyBinding) { > UserTypingGestureIndicator typingGestureIndicator(frame); > frame.editor().pasteAsPlainText(); >- } else >- frame.editor().pasteAsPlainText(); >+ return true; >+ } >+ >+ if (!frame.requestDOMPasteAccess()) >+ return false; >+ >+ frame.editor().pasteAsPlainText(); > return true; > } > >@@ -944,8 +954,13 @@ static bool executePasteAsPlainText(Frame& frame, Event*, EditorCommandSource so > if (source == CommandFromMenuOrKeyBinding) { > UserTypingGestureIndicator typingGestureIndicator(frame); > frame.editor().pasteAsPlainText(); >- } else >- frame.editor().pasteAsPlainText(); >+ return true; >+ } >+ >+ if (!frame.requestDOMPasteAccess()) >+ return false; >+ >+ frame.editor().pasteAsPlainText(); > return true; > } > >@@ -954,8 +969,13 @@ static bool executePasteAsQuotation(Frame& frame, Event*, EditorCommandSource so > if (source == CommandFromMenuOrKeyBinding) { > UserTypingGestureIndicator typingGestureIndicator(frame); > frame.editor().pasteAsQuotation(); >- } else >- frame.editor().pasteAsQuotation(); >+ return true; >+ } >+ >+ if (!frame.requestDOMPasteAccess()) >+ return false; >+ >+ frame.editor().pasteAsQuotation(); > return true; > } > >@@ -1220,7 +1240,8 @@ static bool supportedPaste(Frame* frame) > if (!frame) > return false; > >- bool defaultValue = frame->settings().javaScriptCanAccessClipboard() && frame->settings().DOMPasteAllowed(); >+ auto& settings = frame->settings(); >+ bool defaultValue = (settings.javaScriptCanAccessClipboard() && settings.DOMPasteAllowed()) || settings.domPasteAccessRequestsEnabled(); > > EditorClient* client = frame->editor().client(); > return client ? client->canPaste(frame, defaultValue) : defaultValue; >diff --git a/Source/WebCore/loader/EmptyClients.cpp b/Source/WebCore/loader/EmptyClients.cpp >index fb02baf6755590846fc0279a333aca3c1fd07093..6c2dbdf67211a0b8b15283bf0b74cb863b0a6d2c 100644 >--- a/Source/WebCore/loader/EmptyClients.cpp >+++ b/Source/WebCore/loader/EmptyClients.cpp >@@ -190,6 +190,8 @@ private: > void registerRedoStep(UndoStep&) final; > void clearUndoRedoOperations() final { } > >+ bool requestDOMPasteAccess() final { return false; } >+ > bool canCopyCut(Frame*, bool defaultValue) const final { return defaultValue; } > bool canPaste(Frame*, bool defaultValue) const final { return defaultValue; } > bool canUndo() const final { return false; } >diff --git a/Source/WebCore/page/EditorClient.h b/Source/WebCore/page/EditorClient.h >index 37bf1823a98c2d9e52f3d02cbf5bcffaee358c44..6e0d0bee67a4d4590423b17e92e201847ab84a5c 100644 >--- a/Source/WebCore/page/EditorClient.h >+++ b/Source/WebCore/page/EditorClient.h >@@ -98,6 +98,8 @@ public: > virtual void requestCandidatesForSelection(const VisibleSelection&) { } > virtual void handleAcceptedCandidateWithSoftSpaces(TextCheckingResult) { } > >+ virtual bool requestDOMPasteAccess() = 0; >+ > // Notify an input method that a composition was voluntarily discarded by WebCore, so that it could clean up too. > // This function is not called when a composition is closed per a request from an input method. > virtual void discardedComposition(Frame*) = 0; >diff --git a/Source/WebCore/page/Frame.cpp b/Source/WebCore/page/Frame.cpp >index a4f83b27f0b6ecaf653321d6a554457ece669daa..b51e44442563962bedf3d7b7da9c6ebaeae71438 100644 >--- a/Source/WebCore/page/Frame.cpp >+++ b/Source/WebCore/page/Frame.cpp >@@ -95,6 +95,7 @@ > #include "TextResourceDecoder.h" > #include "UserContentController.h" > #include "UserContentURLPattern.h" >+#include "UserGestureIndicator.h" > #include "UserScript.h" > #include "UserTypingGestureIndicator.h" > #include "VisibleUnits.h" >@@ -658,6 +659,35 @@ bool Frame::selectionChangeCallbacksDisabled() const > } > #endif // PLATFORM(IOS_FAMILY) > >+bool Frame::requestDOMPasteAccess() >+{ >+ if (m_settings->javaScriptCanAccessClipboard() && m_settings->DOMPasteAllowed()) >+ return true; >+ >+ if (!m_settings->domPasteAccessRequestsEnabled() || !m_doc) >+ return false; >+ >+ auto gestureToken = UserGestureIndicator::currentUserGesture(); >+ if (!gestureToken || !gestureToken->processingUserGesture()) >+ return false; >+ >+ switch (gestureToken->domPasteAccessPolicy()) { >+ case DOMPasteAccessPolicy::Granted: >+ return true; >+ case DOMPasteAccessPolicy::Denied: >+ return false; >+ case DOMPasteAccessPolicy::NotRequestedYet: { >+ auto* client = m_editor->client(); >+ if (!client) >+ return false; >+ >+ bool granted = client->requestDOMPasteAccess(); >+ gestureToken->didRequestDOMPasteAccess(granted); >+ return granted; >+ } >+ } >+} >+ > void Frame::setPrinting(bool printing, const FloatSize& pageSize, const FloatSize& originalPageSize, float maximumShrinkRatio, AdjustViewSizeOrNot shouldAdjustViewSize) > { > if (!view()) >diff --git a/Source/WebCore/page/Frame.h b/Source/WebCore/page/Frame.h >index 8ad13d6e1ccc27a638157d811ab1cf48ce2e2069..74f6f4f03638c6b2a4736119153e5f34dbfa46a0 100644 >--- a/Source/WebCore/page/Frame.h >+++ b/Source/WebCore/page/Frame.h >@@ -175,6 +175,8 @@ public: > bool hasHadUserInteraction() const { return m_hasHadUserInteraction; } > void setHasHadUserInteraction() { m_hasHadUserInteraction = true; } > >+ bool requestDOMPasteAccess(); >+ > // ======== All public functions below this point are candidates to move out of Frame into another class. ======== > > WEBCORE_EXPORT void injectUserScripts(UserScriptInjectionTime); >diff --git a/Source/WebCore/page/Settings.yaml b/Source/WebCore/page/Settings.yaml >index 976c84c4a0f1873754de891c4f678a14e9192a00..54585beb664dd11980692e95195a0c203fc2f173 100644 >--- a/Source/WebCore/page/Settings.yaml >+++ b/Source/WebCore/page/Settings.yaml >@@ -344,6 +344,9 @@ mediaEnabled: > DOMPasteAllowed: > initial: false > >+domPasteAccessRequestsEnabled: >+ initial: false >+ > # When enabled, window.blur() does not change focus, and > # window.focus() only changes focus when invoked from the context that > # created the window. >diff --git a/Source/WebKit/Platform/spi/ios/UIKitSPI.h b/Source/WebKit/Platform/spi/ios/UIKitSPI.h >index 1190812e718eaaf4d2aebdbf589a3320dbc89814..aac966f0c5f6810da6726be4a8704e0b9f2f5ea9 100644 >--- a/Source/WebKit/Platform/spi/ios/UIKitSPI.h >+++ b/Source/WebKit/Platform/spi/ios/UIKitSPI.h >@@ -978,6 +978,12 @@ typedef NS_OPTIONS(NSUInteger, UIDragOperation) > -(void)remove; > @end > >+@interface UIURLDragPreviewView : UIView >++ (instancetype)viewWithTitle:(NSString *)title URL:(NSURL *)url; >+@end >+ >+#endif >+ > @interface UICalloutBar : UIView > + (void)fadeSharedCalloutBar; > @end >@@ -989,12 +995,6 @@ typedef NS_OPTIONS(NSUInteger, UIDragOperation) > + (UITextEffectsWindow *)sharedTextEffectsWindow; > @end > >-@interface UIURLDragPreviewView : UIView >-+ (instancetype)viewWithTitle:(NSString *)title URL:(NSURL *)url; >-@end >- >-#endif >- > @interface _UIVisualEffectLayerConfig : NSObject > + (instancetype)layerWithFillColor:(UIColor *)fillColor opacity:(CGFloat)opacity filterType:(NSString *)filterType; > - (void)configureLayerView:(UIView *)view; >diff --git a/Source/WebKit/Shared/WebPreferences.yaml b/Source/WebKit/Shared/WebPreferences.yaml >index cce4ace8356325e7a0a00e523f9e4c992f9152ff..405b7a18b138c4bb5bc97aeea5e1c7790730d60b 100644 >--- a/Source/WebKit/Shared/WebPreferences.yaml >+++ b/Source/WebKit/Shared/WebPreferences.yaml >@@ -1534,6 +1534,13 @@ AdClickAttributionEnabled: > webcoreBinding: RuntimeEnabledFeatures > category: internal > >+DOMPasteAccessRequestsEnabled: >+ type: bool >+ defaultValue: DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED >+ humanReadableName: "DOM Paste Access Requests" >+ humanReadableDescription: "Enable DOM Paste Access Requests" >+ category: internal >+ > # Deprecated > > ICECandidateFilteringEnabled: >diff --git a/Source/WebKit/Shared/WebPreferencesDefaultValues.h b/Source/WebKit/Shared/WebPreferencesDefaultValues.h >index 0a52167c20fa5ae0404734784a49ec91c9106ff7..6e82173b0cb2cbe91531041dec2708c50dfbfe83 100644 >--- a/Source/WebKit/Shared/WebPreferencesDefaultValues.h >+++ b/Source/WebKit/Shared/WebPreferencesDefaultValues.h >@@ -242,3 +242,9 @@ bool defaultCustomPasteboardDataEnabled(); > #define DEFAULT_INPUT_TYPE_COLOR_ENABLED true > #define DEFAULT_DATALIST_ELEMENT_ENABLED true > #endif >+ >+#if PLATFORM(IOS) >+#define DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED true >+#else >+#define DEFAULT_DOM_PASTE_ACCESS_REQUESTS_ENABLED false >+#endif >diff --git a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp >index 317859d75335e7ddca0307204dcce5e4de1e012e..15e379370ed0b1712074ca24574bb916e9f85a6a 100644 >--- a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp >+++ b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp >@@ -473,4 +473,9 @@ bool PageClientImpl::decidePolicyForInstallMissingMediaPluginsPermissionRequest( > } > #endif > >+void PageClientImpl::requestDOMPasteAccess(const IntRect&, CompletionHandler<void(bool)>&& completionHandler) >+{ >+ completionHandler(false); >+} >+ > } // namespace WebKit >diff --git a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h >index d27842e734329b4dea7c8d71e397d32b721b4686..d945e099746ae163a35abef589b1463ffb10474a 100644 >--- a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h >+++ b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.h >@@ -146,6 +146,8 @@ private: > > void didFinishProcessingAllPendingMouseEvents() final { } > >+ void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final; >+ > #if ENABLE(VIDEO) && USE(GSTREAMER) > bool decidePolicyForInstallMissingMediaPluginsPermissionRequest(InstallMissingMediaPluginsPermissionRequest&) override; > #endif >diff --git a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp >index 22336150feabf8c40638ffa905cc4217b84014fd..ad159d765e7e3d71b532584b96edc1450c5a8326 100644 >--- a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp >+++ b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp >@@ -384,4 +384,9 @@ void PageClientImpl::beganExitFullScreen(const WebCore::IntRect& /* initialFrame > > #endif // ENABLE(FULLSCREEN_API) > >+void PageClientImpl::requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&& completionHandler) >+{ >+ completionHandler(false); >+} >+ > } // namespace WebKit >diff --git a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h >index b699f29f9b2870a6be0b3ffb44a7896e23f85c46..304ccd78feb45a51952245af1c502a029f4f45b6 100644 >--- a/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h >+++ b/Source/WebKit/UIProcess/API/wpe/PageClientImpl.h >@@ -143,6 +143,8 @@ private: > > void didFinishProcessingAllPendingMouseEvents() final { } > >+ void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final; >+ > WebCore::UserInterfaceLayoutDirection userInterfaceLayoutDirection() override; > > WKWPE::View& m_view; >diff --git a/Source/WebKit/UIProcess/PageClient.h b/Source/WebKit/UIProcess/PageClient.h >index 38c6552bb1e8a95d02a1822387a0228300f0eb80..3bdd1fff89fdc8e27b32c3c3a8baf6809b494599 100644 >--- a/Source/WebKit/UIProcess/PageClient.h >+++ b/Source/WebKit/UIProcess/PageClient.h >@@ -465,6 +465,8 @@ public: > virtual void didChangeDragCaretRect(const WebCore::IntRect& previousCaretRect, const WebCore::IntRect& caretRect) = 0; > #endif > >+ virtual void requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&&) = 0; >+ > #if ENABLE(ATTACHMENT_ELEMENT) > virtual void didInsertAttachment(API::Attachment&, const String& source) { } > virtual void didRemoveAttachment(API::Attachment&) { } >diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp >index 70a0970e8f987b8168744e2ac64e35ac1715278f..44bf83c0e0ab17a862c4e6009412952fe122d99d 100644 >--- a/Source/WebKit/UIProcess/WebPageProxy.cpp >+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp >@@ -5436,6 +5436,11 @@ void WebPageProxy::setNeedsPlainTextQuirk(bool needsPlainTextQuirk) > m_needsPlainTextQuirk = needsPlainTextQuirk; > } > >+void WebPageProxy::requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&& completionHandler) >+{ >+ m_pageClient->requestDOMPasteAccess(elementRect, WTFMove(completionHandler)); >+} >+ > // BackForwardList > > void WebPageProxy::backForwardAddItem(BackForwardListItemState&& itemState) >diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h >index 96cf862606c0f25dcce198b563ff3087b384d959..45700941d019dd9bdbc05ff4f33595d4c1f00661 100644 >--- a/Source/WebKit/UIProcess/WebPageProxy.h >+++ b/Source/WebKit/UIProcess/WebPageProxy.h >@@ -1658,6 +1658,8 @@ private: > void setNeedsHiddenContentEditableQuirk(bool); > void setNeedsPlainTextQuirk(bool); > >+ void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&); >+ > // Back/Forward list management > void backForwardAddItem(BackForwardListItemState&&); > void backForwardGoToItem(const WebCore::BackForwardItemIdentifier&, SandboxExtension::Handle&); >diff --git a/Source/WebKit/UIProcess/WebPageProxy.messages.in b/Source/WebKit/UIProcess/WebPageProxy.messages.in >index 302e7df67b44fcbdfb2a308b74dbe9da5f4df3e4..b984ea3b7ab7d618265f83432fbe01a074d64145 100644 >--- a/Source/WebKit/UIProcess/WebPageProxy.messages.in >+++ b/Source/WebKit/UIProcess/WebPageProxy.messages.in >@@ -254,6 +254,7 @@ messages -> WebPageProxy { > SetHasHadSelectionChangesFromUserInteraction(bool hasHadUserSelectionChanges) > SetNeedsHiddenContentEditableQuirk(bool needsHiddenContentEditableQuirk) > SetNeedsPlainTextQuirk(bool needsPlainTextQuirk) >+ RequestDOMPasteAccess(WebCore::IntRect elementRect) -> (bool granted) Delayed > > # Find messages > DidCountStringMatches(String string, uint32_t matchCount) >diff --git a/Source/WebKit/UIProcess/ios/PageClientImplIOS.h b/Source/WebKit/UIProcess/ios/PageClientImplIOS.h >index a3c6d3af89d506677958704dd901667db41e96e1..d0d03847d0e2e3b57c75338267aaa204a02a3366 100644 >--- a/Source/WebKit/UIProcess/ios/PageClientImplIOS.h >+++ b/Source/WebKit/UIProcess/ios/PageClientImplIOS.h >@@ -224,6 +224,8 @@ private: > void requestPasswordForQuickLookDocument(const String& fileName, WTF::Function<void(const String&)>&&) override; > #endif > >+ void requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&&) final; >+ > #if ENABLE(DATA_INTERACTION) > void didPerformDragOperation(bool handled) override; > void didHandleDragStartRequest(bool started) override; >diff --git a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm >index 1509cc27429943acbdc35a68f255f9eebbad6272..5e0a6907b80336778858fb050ec010a0fc98b25c 100644 >--- a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm >+++ b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm >@@ -837,6 +837,11 @@ void PageClientImpl::requestPasswordForQuickLookDocument(const String& fileName, > } > #endif > >+void PageClientImpl::requestDOMPasteAccess(const WebCore::IntRect& elementRect, CompletionHandler<void(bool)>&& completionHandler) >+{ >+ [m_contentView _requestDOMPasteAccessWithElementRect:elementRect completionHandler:WTFMove(completionHandler)]; >+} >+ > #if HAVE(PENCILKIT) > RetainPtr<WKDrawingView> PageClientImpl::createDrawingView(WebCore::GraphicsLayer::EmbeddedViewID embeddedViewID) > { >diff --git a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h >index 44c6bf7d7e009ec69a0cd6b6f94907aa2f7f89aa..587194d7c1a453b4e314065fa9a4569c3a4c38a4 100644 >--- a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h >+++ b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h >@@ -49,6 +49,7 @@ > #import <WebCore/Color.h> > #import <WebCore/FloatQuad.h> > #import <wtf/BlockPtr.h> >+#import <wtf/CompletionHandler.h> > #import <wtf/Forward.h> > #import <wtf/OptionSet.h> > #import <wtf/Vector.h> >@@ -313,6 +314,7 @@ struct WKAutoCorrectionData { > BOOL _focusRequiresStrongPasswordAssistance; > > BOOL _hasSetUpInteractions; >+ CompletionHandler<void(bool)> _domPasteRequestHandler; > > #if ENABLE(DATA_INTERACTION) > WebKit::DragDropInteractionState _dragDropInteractionState; >@@ -434,6 +436,8 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW) > - (void)_accessibilityClearSelection; > - (WKFormInputSession *)_formInputSession; > >+- (void)_requestDOMPasteAccessWithElementRect:(const WebCore::IntRect&)elementRect completionHandler:(CompletionHandler<void(bool)>&&)completionHandler; >+ > @property (nonatomic, readonly) WebKit::InteractionInformationAtPosition currentPositionInformation; > - (void)doAfterPositionInformationUpdate:(void (^)(WebKit::InteractionInformationAtPosition))action forRequest:(WebKit::InteractionInformationRequest)request; > - (BOOL)ensurePositionInformationIsUpToDate:(WebKit::InteractionInformationRequest)request; >diff --git a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm >index 680366457607e402d4fdcd47964f84ed72d54465..61abea0fbe7057ce3f268645db1e2908eb1ba94e 100644 >--- a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm >+++ b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm >@@ -740,7 +740,8 @@ - (void)setupInteraction > #endif > > NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; >- [center addObserver:self selector:@selector(_resetShowingTextStyle:) name:UIMenuControllerDidHideMenuNotification object:nil]; >+ [center addObserver:self selector:@selector(_willHideMenu:) name:UIMenuControllerWillHideMenuNotification 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 +887,7 @@ - (void)cleanupInteraction > #if ENABLE(POINTER_EVENTS) > [self _resetPanningPreventionFlags]; > #endif >+ [self _handleDOMPasteRequestWithResult:NO]; > } > > - (void)_removeDefaultGestureRecognizers >@@ -1138,8 +1140,10 @@ - (BOOL)resignFirstResponderForWebView > > bool superDidResign = [super resignFirstResponder]; > >- if (superDidResign) >+ if (superDidResign) { >+ [self _handleDOMPasteRequestWithResult:NO]; > _page->activityStateDidChange(WebCore::ActivityState::IsFocused); >+ } > > return superDidResign; > } >@@ -1168,8 +1172,10 @@ - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRe > 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); >@@ -1970,6 +1976,9 @@ - (BOOL)textInteractionGesture:(UIWKGestureType)gesture shouldBeginAtPoint:(CGPo > if (!_webView.configuration._textInteractionGesturesEnabled) > return NO; > >+ if (_domPasteRequestHandler) >+ return NO; >+ > if (_suppressSelectionAssistantReasons) > return NO; > >@@ -2393,7 +2402,10 @@ - (NSArray *)supportedPasteboardTypesForCurrentSelection > #define FORWARD_ACTION_TO_WKWEBVIEW(_action) \ > - (void)_action:(id)sender \ > { \ >+ SEL action = @selector(_action:);\ >+ [self _willPerformAction:action sender:sender];\ > [_webView _action:sender]; \ >+ [self _didPerformAction:action sender:sender];\ > } > > FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW) >@@ -2638,11 +2650,17 @@ - (void)tintColorDidChange > > - (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:)) >@@ -2789,7 +2807,12 @@ - (id)targetForActionForWebView:(SEL)action withSender:(id)sender > return [super targetForAction:action withSender:sender]; > } > >-- (void)_resetShowingTextStyle:(NSNotification *)notification >+- (void)_willHideMenu:(NSNotification *)notification >+{ >+ [self _handleDOMPasteRequestWithResult:NO]; >+} >+ >+- (void)_didHideMenu:(NSNotification *)notification > { > _showingTextStyleOptions = NO; > [_textSelectionAssistant hideTextStyleOptions]; >@@ -2814,6 +2837,9 @@ - (void)cutForWebView:(id)sender > > - (void)pasteForWebView:(id)sender > { >+ if (sender == UIMenuController.sharedMenuController && [self _handleDOMPasteRequestWithResult:YES]) >+ return; >+ > _page->executeEditCommand("paste"_s); > } > >@@ -2943,6 +2969,28 @@ - (void)_accessibilityClearSelection > _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) >@@ -4090,6 +4138,8 @@ - (void)handleKeyWebEvent:(::WebEvent *)theEvent > > - (void)handleKeyWebEvent:(::WebEvent *)theEvent withCompletionHandler:(void (^)(::WebEvent *theEvent, BOOL wasHandled))completionHandler > { >+ [self _handleDOMPasteRequestWithResult:NO]; >+ > _keyWebEventHandler = makeBlockPtr(completionHandler); > _page->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(theEvent)); > } >@@ -4779,6 +4829,47 @@ - (void)_didUpdateInputMode:(WebCore::InputMode)mode > #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]; >diff --git a/Source/WebKit/UIProcess/mac/PageClientImplMac.h b/Source/WebKit/UIProcess/mac/PageClientImplMac.h >index 4707a0202e42a828d5a50ed58be4e30f3a88a0b9..d242c05c1d26c4380a27d044d54dc6cbf4eb22cb 100644 >--- a/Source/WebKit/UIProcess/mac/PageClientImplMac.h >+++ b/Source/WebKit/UIProcess/mac/PageClientImplMac.h >@@ -30,6 +30,7 @@ > #include "CorrectionPanel.h" > #include "PageClientImplCocoa.h" > #include "WebFullScreenManagerProxy.h" >+#include <wtf/CompletionHandler.h> > #include <wtf/RetainPtr.h> > > @class WKEditorUndoTarget; >@@ -215,6 +216,8 @@ private: > void willRecordNavigationSnapshot(WebBackForwardListItem&) override; > void didRemoveNavigationGestureSnapshot() override; > >+ void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&& completion) final { completion(false); } >+ > NSView *activeView() const; > NSWindow *activeWindow() const; > >diff --git a/Source/WebKit/UIProcess/win/PageClientImpl.cpp b/Source/WebKit/UIProcess/win/PageClientImpl.cpp >index 170c2d7763962af02f32b4f8b5ed029b679f6ff1..912b4a99c724d62d3a547ba5b4367b1bb31abcb8 100644 >--- a/Source/WebKit/UIProcess/win/PageClientImpl.cpp >+++ b/Source/WebKit/UIProcess/win/PageClientImpl.cpp >@@ -357,4 +357,9 @@ HWND PageClientImpl::viewWidget() > return m_view.window(); > } > >+void PageClientImpl::requestDOMPasteAccess(const IntRect&, CompletionHandler<void(bool)>&& completionHandler) >+{ >+ completionHandler(false); >+} >+ > } // namespace WebKit >diff --git a/Source/WebKit/UIProcess/win/PageClientImpl.h b/Source/WebKit/UIProcess/win/PageClientImpl.h >index 66572bb14058aacff5fac3fdd75106c8b075ef5a..e8426c963ecb6117c40d2a2482a3aefe2c08ff03 100644 >--- a/Source/WebKit/UIProcess/win/PageClientImpl.h >+++ b/Source/WebKit/UIProcess/win/PageClientImpl.h >@@ -142,6 +142,8 @@ private: > > void didFinishProcessingAllPendingMouseEvents() final { } > >+ void requestDOMPasteAccess(const WebCore::IntRect&, CompletionHandler<void(bool)>&&) final; >+ > // Members of PageClientImpl class > DefaultUndoController m_undoController; > >diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp >index 8f2181bebf181c3dcd787fb4b9fbd620c9d00d7f..96a4dd18f214b594328c5078d7e22555ed7e7f3e 100644 >--- a/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp >+++ b/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp >@@ -359,6 +359,11 @@ void WebEditorClient::redo() > m_page->sendSync(Messages::WebPageProxy::ExecuteUndoRedo(UndoOrRedo::Redo), Messages::WebPageProxy::ExecuteUndoRedo::Reply()); > } > >+bool WebEditorClient::requestDOMPasteAccess() >+{ >+ return m_page->requestDOMPasteAccess(); >+} >+ > #if PLATFORM(WIN) > void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event) > { >diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h b/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h >index 4720b71175412a59a229bc47106dab2da3c8756e..7be8ddfca3f6d0ca897b7e685ce1993fb80023d8 100644 >--- a/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h >+++ b/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h >@@ -89,6 +89,8 @@ private: > void registerRedoStep(WebCore::UndoStep&) final; > void clearUndoRedoOperations() final; > >+ bool requestDOMPasteAccess() final; >+ > bool canCopyCut(WebCore::Frame*, bool defaultValue) const final; > bool canPaste(WebCore::Frame*, bool defaultValue) const final; > bool canUndo() const final; >diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp >index de34617e40fc26758000fc4ec9b590d2c9acf449..96cd4422c39080aedf2158a0dc986f7e0829bc24 100644 >--- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp >+++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp >@@ -6425,6 +6425,15 @@ void WebPage::didCompleteShareSheet(bool wasGranted, ShareSheetCallbackID callba > callback(wasGranted); > } > >+bool WebPage::requestDOMPasteAccess() >+{ >+ bool granted = false; >+ if (!sendSyncWithDelayedReply(Messages::WebPageProxy::RequestDOMPasteAccess(rectForElementAtInteractionLocation()), Messages::WebPageProxy::RequestDOMPasteAccess::Reply(granted))) >+ return false; >+ >+ return granted; >+} >+ > void WebPage::simulateDeviceOrientationChange(double alpha, double beta, double gamma) > { > #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS_FAMILY) >@@ -6498,7 +6507,16 @@ void WebPage::didFinishLoadingApplicationManifest(uint64_t coreCallbackID, const > void WebPage::updateCurrentModifierState(OptionSet<PlatformEvent::Modifier> modifiers) > { > PlatformKeyboardEvent::setCurrentModifierState(modifiers); >-} >+} >+ >+#if !PLATFORM(IOS_FAMILY) >+ >+WebCore::IntRect WebPage::rectForElementAtInteractionLocation() const >+{ >+ return { }; >+} >+ >+#endif // !PLATFORM(IOS_FAMILY) > > } // namespace WebKit > >diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h >index 6062ad88bc9c13cc1fcc60bd7225452bafd5e222..5b8aa49a258cf528873bb8cdaaacca3d7488cb8b 100644 >--- a/Source/WebKit/WebProcess/WebPage/WebPage.h >+++ b/Source/WebKit/WebProcess/WebPage/WebPage.h >@@ -659,7 +659,6 @@ public: > void setFocusedElementValue(const String&); > void setFocusedElementValueAsNumber(double); > void setFocusedElementSelectedIndex(uint32_t index, bool allowMultipleSelection); >- WebCore::IntRect rectForElementAtInteractionLocation(); > void updateSelectionAppearance(); > void getSelectionContext(CallbackID); > void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, uint64_t requestID); >@@ -1146,6 +1145,9 @@ public: > return sendSync(WTFMove(message), WTFMove(reply), m_pageID, Seconds::infinity(), IPC::SendSyncOption::InformPlatformProcessWillSuspend); > } > >+ bool requestDOMPasteAccess(); >+ WebCore::IntRect rectForElementAtInteractionLocation() const; >+ > private: > WebPage(uint64_t pageID, WebPageCreationParameters&&); > >diff --git a/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm b/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm >index 7eb71ae912a93500b35893830b6e24a0636cd7ef..32c5f3d576cc5ea308e726be30005e9e84e43d40 100644 >--- a/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm >+++ b/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm >@@ -518,7 +518,7 @@ void WebPage::advanceToNextMisspelling(bool) > notImplemented(); > } > >-IntRect WebPage::rectForElementAtInteractionLocation() >+IntRect WebPage::rectForElementAtInteractionLocation() const > { > HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint(m_lastInteractionLocation, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AllowChildFrameContent); > Node* hitNode = result.innerNode(); >diff --git a/Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h b/Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h >index f0f968b7e2b66c891dc14b6c1faaf0b8bbe1d19e..55f40e0b53b1266b68fce4e3898e883cce9a72a7 100644 >--- a/Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h >+++ b/Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h >@@ -81,6 +81,7 @@ private: > String replacementURLForResource(Ref<WebCore::SharedBuffer>&& resourceData, const String& mimeType) final; > > void setInsertionPasteboard(const String&) final; >+ bool requestDOMPasteAccess() final { return false; } > > #if USE(APPKIT) > void uppercaseWord() final; >diff --git a/Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h b/Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h >index 7a6edde8193613859a31d2f0d2ebb35f822c5d2b..cc5e4d645be56704a092d11fc006c32313995adb 100644 >--- a/Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h >+++ b/Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h >@@ -115,6 +115,8 @@ private: > void requestCheckingOfString(WebCore::TextCheckingRequest&, const WebCore::VisibleSelection&) final { } > bool performTwoStepDrop(WebCore::DocumentFragment&, WebCore::Range&, bool) final { return false; } > >+ bool requestDOMPasteAccess() final { return false; } >+ > WebCore::TextCheckerClient* textChecker() final { return this; } > > WebView* m_webView; >diff --git a/Tools/ChangeLog b/Tools/ChangeLog >index 3014a68edbb701749eb1fb62c5b65052245760c3..169776aaa42b8c4ded5e652f7f9bbaff5b5b8517 100644 >--- a/Tools/ChangeLog >+++ b/Tools/ChangeLog >@@ -1,3 +1,96 @@ >+2019-02-08 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Allow pages to trigger programmatic paste from script on iOS >+ https://bugs.webkit.org/show_bug.cgi?id=194271 >+ <rdar://problem/47808810> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Add support for interacting with the callout bar on iOS during layout tests. See below for more detail. >+ >+ * DumpRenderTree/ios/UIScriptControllerIOS.mm: >+ (WTR::UIScriptController::platformSetDidShowMenuCallback): >+ (WTR::UIScriptController::platformSetDidHideMenuCallback): >+ (WTR::UIScriptController::rectForMenuAction const): >+ >+ Add new mechanisms to make it possible to interact with and query the state of the callout menu on iOS. This >+ includes determining the rect (in content view coordinates) of the menu's controls, and callbacks to register >+ for when the menu is shown or hidden. >+ >+ * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl: >+ * TestRunnerShared/UIScriptContext/UIScriptContext.h: >+ * TestRunnerShared/UIScriptContext/UIScriptController.cpp: >+ (WTR::UIScriptController::setDidShowMenuCallback): >+ (WTR::UIScriptController::didShowMenuCallback const): >+ (WTR::UIScriptController::setDidHideMenuCallback): >+ (WTR::UIScriptController::didHideMenuCallback const): >+ (WTR::UIScriptController::platformSetDidShowMenuCallback): >+ (WTR::UIScriptController::platformSetDidHideMenuCallback): >+ (WTR::UIScriptController::rectForMenuAction const): >+ * TestRunnerShared/UIScriptContext/UIScriptController.h: >+ * WebKitTestRunner/TestController.cpp: >+ (WTR::TestController::resetPreferencesToConsistentValues): >+ (WTR::updateTestOptionsFromTestHeader): >+ * WebKitTestRunner/TestOptions.h: >+ >+ Add a new test option to determine whether DOM paste is enabled. DOM paste is currently enabled everywhere by >+ default, but these new programmatic paste tests require it to be disabled in order for confirmation UI to show. >+ >+ (WTR::TestOptions::hasSameInitializationOptions const): >+ * WebKitTestRunner/UIScriptControllerCocoa.mm: >+ (WTR::UIScriptController::calendarType const): >+ (WTR::UIScriptController::platformUndoManager const): >+ * WebKitTestRunner/cocoa/TestRunnerWKWebView.h: >+ * WebKitTestRunner/cocoa/TestRunnerWKWebView.mm: >+ (-[TestRunnerWKWebView initWithFrame:configuration:]): >+ (-[TestRunnerWKWebView dealloc]): >+ (-[TestRunnerWKWebView _didShowMenu]): >+ (-[TestRunnerWKWebView _didHideMenu]): >+ >+ Listen to when the callout bar is presented and dismissed, and invoke testing callbacks as needed. >+ >+ * WebKitTestRunner/ios/TestControllerIOS.mm: >+ (WTR::handleMenuWillHideNotification): >+ (WTR::handleMenuDidHideNotification): >+ (WTR::TestController::platformInitialize): >+ (WTR::TestController::platformDestroy): >+ (WTR::TestController::platformResetStateToConsistentValues): >+ >+ Additionally ensure that any callout menu presented by a previous layout test is dismissed before running the >+ next test by hiding the callout bar if necessary, and then waiting for the "DidHide" notification. >+ >+ * WebKitTestRunner/ios/UIScriptControllerIOS.mm: >+ (WTR::forEachViewInHierarchy): >+ (WTR::findViewInHierarchyOfType): >+ >+ Move `forEachViewInHierarchy` so that we can use it throughout the file, and then add some additional helper >+ functions that dig through a given view's hierarchy in search of a view of a given class. >+ >+ (WTR::UIScriptController::selectionStartGrabberViewRect const): >+ (WTR::UIScriptController::selectionEndGrabberViewRect const): >+ (WTR::UIScriptController::selectionCaretViewRect const): >+ (WTR::UIScriptController::selectionRangeViewRects const): >+ (WTR::UIScriptController::platformSetDidShowMenuCallback): >+ (WTR::UIScriptController::platformSetDidHideMenuCallback): >+ >+ Tweak these to use `platformContentView` instead of grabbing the content view from WKWebView directly. >+ >+ (WTR::UIScriptController::rectForMenuAction const): >+ >+ Add a new UIScriptController method to get the rect of the action in the contextual menu (on iOS, this is the >+ callout bar) whose label matches the given string. >+ >+ (WTR::UIScriptController::platformContentView const): >+ >+ Add a `platformContentView()` helper on UIScriptController so that we can stop grabbing the value for key >+ "_currentContentView" from various places in this file. Additionally, rewrite `platformUndoManager()` in terms >+ of this new helper, and move the code out from iOS/macOS-specific files into UIScriptControllerCocoa. >+ >+ (WTR::UIScriptController::platformUndoManager const): Deleted. >+ * WebKitTestRunner/mac/UIScriptControllerMac.mm: >+ (WTR::UIScriptController::platformContentView const): >+ (WTR::UIScriptController::platformUndoManager const): Deleted. >+ > 2019-02-07 Jonathan Bedard <jbedard@apple.com> > > webkitpy: Respect --dedicated-simulators flag >diff --git a/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm b/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm >index db11ad641562d1d3a3164294b65446df8f53a39c..10cb0e9ec20053f307f2d410e4cd37719d5d1f1c 100644 >--- a/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm >+++ b/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm >@@ -311,6 +311,19 @@ void UIScriptController::platformSetDidHideKeyboardCallback() > { > } > >+void UIScriptController::platformSetDidShowMenuCallback() >+{ >+} >+ >+void UIScriptController::platformSetDidHideMenuCallback() >+{ >+} >+ >+JSObjectRef UIScriptController::rectForMenuAction(JSStringRef) const >+{ >+ return nullptr; >+} >+ > void UIScriptController::platformSetDidEndScrollingCallback() > { > } >diff --git a/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl b/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl >index e54467ff448a0a7c37070e9b7d0f18825c943143..ccfb98dc2566ff1cbf17889e7b8b6eb2c49de934 100644 >--- a/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl >+++ b/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl >@@ -221,6 +221,10 @@ interface UIScriptController { > attribute object didHideKeyboardCallback; > readonly attribute boolean isShowingKeyboard; > >+ attribute object didShowMenuCallback; >+ attribute object didHideMenuCallback; >+ object rectForMenuAction(DOMString action); >+ > attribute object willBeginZoomingCallback; > attribute object didEndZoomingCallback; > >diff --git a/Tools/TestRunnerShared/UIScriptContext/UIScriptContext.h b/Tools/TestRunnerShared/UIScriptContext/UIScriptContext.h >index 4ec18293a0331155ff9cb2354ac555ba1408dbbc..8040647a23f237618a3ebd65d76227e8c4a2b6df 100644 >--- a/Tools/TestRunnerShared/UIScriptContext/UIScriptContext.h >+++ b/Tools/TestRunnerShared/UIScriptContext/UIScriptContext.h >@@ -54,6 +54,8 @@ typedef enum { > CallbackTypeDidEndZooming, > CallbackTypeDidShowKeyboard, > CallbackTypeDidHideKeyboard, >+ CallbackTypeDidShowMenu, >+ CallbackTypeDidHideMenu, > CallbackTypeDidEndScrolling, > CallbackTypeDidStartFormControlInteraction, > CallbackTypeDidEndFormControlInteraction, >diff --git a/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp b/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp >index 4b45d56fdc2cb2ca4dae0f66dd1f527eefe7c39e..05c4727ffddad1e456e52828c9c9e359cd0949ac 100644 >--- a/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp >+++ b/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp >@@ -205,6 +205,28 @@ JSValueRef UIScriptController::didHideKeyboardCallback() const > return m_context->callbackWithID(CallbackTypeDidHideKeyboard); > } > >+void UIScriptController::setDidShowMenuCallback(JSValueRef callback) >+{ >+ m_context->registerCallback(callback, CallbackTypeDidShowMenu); >+ platformSetDidShowMenuCallback(); >+} >+ >+JSValueRef UIScriptController::didShowMenuCallback() const >+{ >+ return m_context->callbackWithID(CallbackTypeDidShowMenu); >+} >+ >+void UIScriptController::setDidHideMenuCallback(JSValueRef callback) >+{ >+ m_context->registerCallback(callback, CallbackTypeDidHideMenu); >+ platformSetDidHideMenuCallback(); >+} >+ >+JSValueRef UIScriptController::didHideMenuCallback() const >+{ >+ return m_context->callbackWithID(CallbackTypeDidHideMenu); >+} >+ > #if !PLATFORM(COCOA) > > void UIScriptController::zoomToScale(double, JSValueRef) >@@ -489,6 +511,19 @@ void UIScriptController::platformSetDidHideKeyboardCallback() > { > } > >+void UIScriptController::platformSetDidShowMenuCallback() >+{ >+} >+ >+void UIScriptController::platformSetDidHideMenuCallback() >+{ >+} >+ >+JSObjectRef UIScriptController::rectForMenuAction(JSStringRef) const >+{ >+ return nullptr; >+} >+ > void UIScriptController::platformClearAllCallbacks() > { > } >diff --git a/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h b/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h >index 956c345c32f61351cfede1143f4a61c00e66c740..c52ba7b5a6a1bca725d434e8633fa8ddaea83553 100644 >--- a/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h >+++ b/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h >@@ -32,6 +32,8 @@ > #include <wtf/Ref.h> > > OBJC_CLASS NSUndoManager; >+OBJC_CLASS NSView; >+OBJC_CLASS UIView; > > namespace WebCore { > class FloatRect; >@@ -152,6 +154,14 @@ public: > > bool isShowingKeyboard() const; > >+ void setDidHideMenuCallback(JSValueRef); >+ JSValueRef didHideMenuCallback() const; >+ >+ void setDidShowMenuCallback(JSValueRef); >+ JSValueRef didShowMenuCallback() const; >+ >+ JSObjectRef rectForMenuAction(JSStringRef action) const; >+ > void setDidEndScrollingCallback(JSValueRef); > JSValueRef didEndScrollingCallback() const; > >@@ -226,6 +236,8 @@ private: > void platformSetDidEndZoomingCallback(); > void platformSetDidShowKeyboardCallback(); > void platformSetDidHideKeyboardCallback(); >+ void platformSetDidShowMenuCallback(); >+ void platformSetDidHideMenuCallback(); > void platformSetDidEndScrollingCallback(); > void platformClearAllCallbacks(); > void platformPlayBackEventStream(JSStringRef, JSValueRef); >@@ -234,6 +246,13 @@ private: > NSUndoManager *platformUndoManager() const; > #endif > >+#if PLATFORM(IOS_FAMILY) >+ UIView *platformContentView() const; >+#endif >+#if PLATFORM(MAC) >+ NSView *platformContentView() const; >+#endif >+ > JSClassRef wrapperClass() final; > > JSObjectRef objectFromRect(const WebCore::FloatRect&) const; >diff --git a/Tools/WebKitTestRunner/TestController.cpp b/Tools/WebKitTestRunner/TestController.cpp >index 4cc4cccc0f023876de4a00dab367ec8c681ac29b..5673322af6fe5f197c3c2b84d99f61dc935ac237 100644 >--- a/Tools/WebKitTestRunner/TestController.cpp >+++ b/Tools/WebKitTestRunner/TestController.cpp >@@ -745,7 +745,7 @@ void TestController::resetPreferencesToConsistentValues(const TestOptions& optio > WKPreferencesSetJavaScriptRuntimeFlags(preferences, kWKJavaScriptRuntimeFlagsAllEnabled); > WKPreferencesSetJavaScriptCanOpenWindowsAutomatically(preferences, true); > WKPreferencesSetJavaScriptCanAccessClipboard(preferences, true); >- WKPreferencesSetDOMPasteAllowed(preferences, true); >+ WKPreferencesSetDOMPasteAllowed(preferences, options.domPasteAllowed); > WKPreferencesSetUniversalAccessFromFileURLsAllowed(preferences, true); > WKPreferencesSetFileAccessFromFileURLsAllowed(preferences, true); > #if ENABLE(FULLSCREEN_API) >@@ -1262,6 +1262,8 @@ static void updateTestOptionsFromTestHeader(TestOptions& testOptions, const std: > testOptions.applicationManifest = parseStringTestHeaderValueAsRelativePath(value, pathOrURL); > else if (key == "allowCrossOriginSubresourcesToAskForCredentials") > testOptions.allowCrossOriginSubresourcesToAskForCredentials = parseBooleanTestHeaderValue(value); >+ else if (key == "domPasteAllowed") >+ testOptions.domPasteAllowed = parseBooleanTestHeaderValue(value); > else if (key == "enableProcessSwapOnNavigation") > testOptions.enableProcessSwapOnNavigation = parseBooleanTestHeaderValue(value); > else if (key == "enableProcessSwapOnWindowOpen") >diff --git a/Tools/WebKitTestRunner/TestOptions.h b/Tools/WebKitTestRunner/TestOptions.h >index 0ac586eb1eabe5e1901cad638b648328a74ea882..392d8e70b409e88e5924e3fddc0de77628dd91d4 100644 >--- a/Tools/WebKitTestRunner/TestOptions.h >+++ b/Tools/WebKitTestRunner/TestOptions.h >@@ -57,6 +57,7 @@ struct TestOptions { > bool shouldShowTouches { false }; > bool dumpJSConsoleLogInStdErr { false }; > bool allowCrossOriginSubresourcesToAskForCredentials { false }; >+ bool domPasteAllowed { true }; > bool enableProcessSwapOnNavigation { true }; > bool enableProcessSwapOnWindowOpen { false }; > bool enableColorFilter { false }; >@@ -105,6 +106,7 @@ struct TestOptions { > || dumpJSConsoleLogInStdErr != options.dumpJSConsoleLogInStdErr > || applicationManifest != options.applicationManifest > || allowCrossOriginSubresourcesToAskForCredentials != options.allowCrossOriginSubresourcesToAskForCredentials >+ || domPasteAllowed != options.domPasteAllowed > || enableProcessSwapOnNavigation != options.enableProcessSwapOnNavigation > || enableProcessSwapOnWindowOpen != options.enableProcessSwapOnWindowOpen > || enableColorFilter != options.enableColorFilter >diff --git a/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm b/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm >index 702e5185523314be8d975edcea194b841f0fa48e..648aa7f3ab3e5f2812e9272aec04d09a29aa00c4 100644 >--- a/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm >+++ b/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm >@@ -183,9 +183,7 @@ void UIScriptController::setDefaultCalendarType(JSStringRef calendarIdentifier) > JSObjectRef UIScriptController::calendarType() const > { > #if WK_API_ENABLED >- WKWebView *webView = TestController::singleton().mainWebView()->platformView(); >- UIView *contentView = [webView valueForKeyPath:@"_currentContentView"]; >- NSString *calendarTypeString = [contentView valueForKeyPath:@"formInputControl.dateTimePickerCalendarType"]; >+ NSString *calendarTypeString = [platformContentView() valueForKeyPath:@"formInputControl.dateTimePickerCalendarType"]; > auto jsContext = m_context->jsContext(); > return JSValueToObject(jsContext, [JSValue valueWithObject:calendarTypeString inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr); > #else >@@ -203,4 +201,9 @@ JSRetainPtr<JSStringRef> UIScriptController::firstRedoLabel() const > return JSStringCreateWithCFString((__bridge CFStringRef)platformUndoManager().redoActionName); > } > >+NSUndoManager *UIScriptController::platformUndoManager() const >+{ >+ return platformContentView().undoManager; >+} >+ > } // namespace WTR >diff --git a/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h b/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h >index 5bc2cb7288bd075b31cf7dc08b03360707d369d7..28d57ac2a66ceb10e6150e11b68da0c2c9ae7688 100644 >--- a/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h >+++ b/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h >@@ -43,6 +43,8 @@ > @property (nonatomic, copy) void (^didEndZoomingCallback)(void); > @property (nonatomic, copy) void (^didShowKeyboardCallback)(void); > @property (nonatomic, copy) void (^didHideKeyboardCallback)(void); >+@property (nonatomic, copy) void (^didShowMenuCallback)(void); >+@property (nonatomic, copy) void (^didHideMenuCallback)(void); > @property (nonatomic, copy) void (^didEndScrollingCallback)(void); > @property (nonatomic, copy) void (^rotationDidEndCallback)(void); > @property (nonatomic, copy) NSString *accessibilitySpeakSelectionContent; >diff --git a/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm b/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm >index fbfca09d8d351e9417bef8340d2f13a03cd550d0..155fd69a7ecec245af9175e88b0157411f008d43 100644 >--- a/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm >+++ b/Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm >@@ -55,6 +55,7 @@ @interface TestRunnerWKWebView () <WKUIDelegatePrivate> { > @property (nonatomic, copy) void (^zoomToScaleCompletionHandler)(void); > @property (nonatomic, copy) void (^retrieveSpeakSelectionContentCompletionHandler)(void); > @property (nonatomic, getter=isShowingKeyboard, setter=setIsShowingKeyboard:) BOOL showingKeyboard; >+@property (nonatomic, getter=isShowingMenu, setter=setIsShowingMenu:) BOOL showingMenu; > > @end > >@@ -77,7 +78,8 @@ - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguratio > NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; > [center addObserver:self selector:@selector(_invokeShowKeyboardCallbackIfNecessary) name:UIKeyboardDidShowNotification object:nil]; > [center addObserver:self selector:@selector(_invokeHideKeyboardCallbackIfNecessary) name:UIKeyboardDidHideNotification object:nil]; >- >+ [center addObserver:self selector:@selector(_didShowMenu) name:UIMenuControllerDidShowMenuNotification object:nil]; >+ [center addObserver:self selector:@selector(_didHideMenu) name:UIMenuControllerDidHideMenuNotification object:nil]; > self.UIDelegate = self; > } > return self; >@@ -95,6 +97,8 @@ - (void)dealloc > self.didEndZoomingCallback = nil; > self.didShowKeyboardCallback = nil; > self.didHideKeyboardCallback = nil; >+ self.didShowMenuCallback = nil; >+ self.didHideMenuCallback = nil; > self.didEndScrollingCallback = nil; > self.rotationDidEndCallback = nil; > >@@ -172,6 +176,26 @@ - (void)_invokeHideKeyboardCallbackIfNecessary > self.didHideKeyboardCallback(); > } > >+- (void)_didShowMenu >+{ >+ if (self.showingMenu) >+ return; >+ >+ self.showingMenu = YES; >+ if (self.didShowMenuCallback) >+ self.didShowMenuCallback(); >+} >+ >+- (void)_didHideMenu >+{ >+ if (!self.showingMenu) >+ return; >+ >+ self.showingMenu = NO; >+ if (self.didHideMenuCallback) >+ self.didHideMenuCallback(); >+} >+ > - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view > { > [super scrollViewWillBeginZooming:scrollView withView:view]; >diff --git a/Tools/WebKitTestRunner/ios/TestControllerIOS.mm b/Tools/WebKitTestRunner/ios/TestControllerIOS.mm >index 04011a6b3544837c559851730f699d76987214c3..18ffa5f6ae504955911f6be62c32d951191f6441 100644 >--- a/Tools/WebKitTestRunner/ios/TestControllerIOS.mm >+++ b/Tools/WebKitTestRunner/ios/TestControllerIOS.mm >@@ -53,6 +53,7 @@ static BOOL overrideIsInHardwareKeyboardMode() > namespace WTR { > > static bool isDoneWaitingForKeyboardToDismiss = true; >+static bool isDoneWaitingForMenuToDismiss = true; > > static void handleKeyboardWillHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef) > { >@@ -64,6 +65,16 @@ static void handleKeyboardDidHideNotification(CFNotificationCenterRef, void*, CF > isDoneWaitingForKeyboardToDismiss = true; > } > >+static void handleMenuWillHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef) >+{ >+ isDoneWaitingForMenuToDismiss = false; >+} >+ >+static void handleMenuDidHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef) >+{ >+ isDoneWaitingForMenuToDismiss = true; >+} >+ > void TestController::notifyDone() > { > } >@@ -79,6 +90,8 @@ void TestController::platformInitialize() > auto center = CFNotificationCenterGetLocalCenter(); > CFNotificationCenterAddObserver(center, this, handleKeyboardWillHideNotification, (CFStringRef)UIKeyboardWillHideNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); > CFNotificationCenterAddObserver(center, this, handleKeyboardDidHideNotification, (CFStringRef)UIKeyboardDidHideNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); >+ CFNotificationCenterAddObserver(center, this, handleMenuWillHideNotification, (CFStringRef)UIMenuControllerWillHideMenuNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); >+ CFNotificationCenterAddObserver(center, this, handleMenuDidHideNotification, (CFStringRef)UIMenuControllerDidHideMenuNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); > > // Override the implementation of +[UIKeyboard isInHardwareKeyboardMode] to ensure that test runs are deterministic > // regardless of whether a hardware keyboard is attached. We intentionally never restore the original implementation. >@@ -92,6 +105,8 @@ void TestController::platformDestroy() > auto center = CFNotificationCenterGetLocalCenter(); > CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIKeyboardWillHideNotification, nullptr); > CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIKeyboardDidHideNotification, nullptr); >+ CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIMenuControllerWillHideMenuNotification, nullptr); >+ CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIMenuControllerDidHideMenuNotification, nullptr); > } > > void TestController::initializeInjectedBundlePath() >@@ -115,6 +130,7 @@ void TestController::platformResetStateToConsistentValues(const TestOptions& opt > { > cocoaResetStateToConsistentValues(options); > >+ UIMenuController.sharedMenuController.menuVisible = NO; > [[UIApplication sharedApplication] _cancelAllTouches]; > [[UIDevice currentDevice] setOrientation:UIDeviceOrientationPortrait animated:NO]; > >@@ -141,6 +157,7 @@ void TestController::platformResetStateToConsistentValues(const TestOptions& opt > } > > runUntil(isDoneWaitingForKeyboardToDismiss, m_currentInvocation->shortTimeout()); >+ runUntil(isDoneWaitingForMenuToDismiss, m_currentInvocation->shortTimeout()); > > if (shouldRestoreFirstResponder) > [mainWebView()->platformView() becomeFirstResponder]; >diff --git a/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm b/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm >index 4952765a0acfd3de3f182be28e2fe8e19395683b..ffc3bcde0c0ed6b729d76eb2dcc764d0b9ab2e73 100644 >--- a/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm >+++ b/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm >@@ -63,7 +63,45 @@ static NSDictionary *toNSDictionary(CGRect rect) > @"height": @(rect.size.height) > }; > } >+ >+static BOOL forEachViewInHierarchy(UIView *view, void(^mapFunction)(UIView *subview, BOOL *stop)) >+{ >+ BOOL stop = NO; >+ mapFunction(view, &stop); >+ if (stop) >+ return YES; > >+ for (UIView *subview in view.subviews) { >+ stop = forEachViewInHierarchy(subview, mapFunction); >+ if (stop) >+ break; >+ } >+ return stop; >+} >+ >+static UIView *findViewInHierarchyOfType(UIView *view, Class viewClass) >+{ >+ __block RetainPtr<UIView> foundView; >+ forEachViewInHierarchy(view, ^(UIView *subview, BOOL *stop) { >+ if (![subview isKindOfClass:viewClass]) >+ return; >+ >+ foundView = subview; >+ *stop = YES; >+ }); >+ return foundView.autorelease(); >+} >+ >+static NSArray<UIView *> *findAllViewsInHierarchyOfType(UIView *view, Class viewClass) >+{ >+ __block RetainPtr<NSMutableArray> views = adoptNS([[NSMutableArray alloc] init]); >+ forEachViewInHierarchy(view, ^(UIView *subview, BOOL *stop) { >+ if ([subview isKindOfClass:viewClass]) >+ [views addObject:subview]; >+ }); >+ return views.autorelease(); >+} >+ > void UIScriptController::checkForOutstandingCallbacks() > { > if (![[HIDEventGenerator sharedHIDEventGenerator] checkForOutstandingCallbacks]) >@@ -589,8 +627,7 @@ JSObjectRef UIScriptController::textSelectionCaretRect() const > > JSObjectRef UIScriptController::selectionStartGrabberViewRect() const > { >- WKWebView *webView = TestController::singleton().mainWebView()->platformView(); >- UIView *contentView = [webView valueForKeyPath:@"_currentContentView"]; >+ UIView *contentView = platformContentView(); > UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"]; > auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"startGrabber"] frame] toView:contentView]; > frameInContentCoordinates = CGRectIntersection(contentView.bounds, frameInContentCoordinates); >@@ -600,8 +637,7 @@ JSObjectRef UIScriptController::selectionStartGrabberViewRect() const > > JSObjectRef UIScriptController::selectionEndGrabberViewRect() const > { >- WKWebView *webView = TestController::singleton().mainWebView()->platformView(); >- UIView *contentView = [webView valueForKeyPath:@"_currentContentView"]; >+ UIView *contentView = platformContentView(); > UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"]; > auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"endGrabber"] frame] toView:contentView]; > frameInContentCoordinates = CGRectIntersection(contentView.bounds, frameInContentCoordinates); >@@ -611,8 +647,7 @@ JSObjectRef UIScriptController::selectionEndGrabberViewRect() const > > JSObjectRef UIScriptController::selectionCaretViewRect() const > { >- WKWebView *webView = TestController::singleton().mainWebView()->platformView(); >- UIView *contentView = [webView valueForKeyPath:@"_currentContentView"]; >+ UIView *contentView = platformContentView(); > UIView *caretView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.caretView"]; > auto rectInContentViewCoordinates = CGRectIntersection([caretView convertRect:caretView.bounds toView:contentView], contentView.bounds); > return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(rectInContentViewCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr); >@@ -620,8 +655,7 @@ JSObjectRef UIScriptController::selectionCaretViewRect() const > > JSObjectRef UIScriptController::selectionRangeViewRects() const > { >- WKWebView *webView = TestController::singleton().mainWebView()->platformView(); >- UIView *contentView = [webView valueForKeyPath:@"_currentContentView"]; >+ UIView *contentView = platformContentView(); > UIView *rangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"]; > auto rectsAsDictionaries = adoptNS([[NSMutableArray alloc] init]); > NSArray *textRectInfoArray = [rangeView valueForKeyPath:@"rects"]; >@@ -785,6 +819,58 @@ void UIScriptController::platformSetDidHideKeyboardCallback() > }; > } > >+void UIScriptController::platformSetDidShowMenuCallback() >+{ >+ TestController::singleton().mainWebView()->platformView().didShowMenuCallback = ^{ >+ if (!m_context) >+ return; >+ m_context->fireCallback(CallbackTypeDidShowMenu); >+ }; >+} >+ >+void UIScriptController::platformSetDidHideMenuCallback() >+{ >+ TestController::singleton().mainWebView()->platformView().didHideMenuCallback = ^{ >+ if (!m_context) >+ return; >+ m_context->fireCallback(CallbackTypeDidHideMenu); >+ }; >+} >+ >+JSObjectRef UIScriptController::rectForMenuAction(JSStringRef jsAction) const >+{ >+ auto action = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, jsAction)); >+ >+ UIWindow *windowForButton = nil; >+ UIButton *buttonForAction = nil; >+ for (UIWindow *window in UIApplication.sharedApplication.windows) { >+ if (![window isKindOfClass:UITextEffectsWindow.class]) >+ continue; >+ >+ UIView *calloutBar = findViewInHierarchyOfType(window, UICalloutBar.class); >+ if (!calloutBar) >+ continue; >+ >+ for (UIButton *button in findAllViewsInHierarchyOfType(calloutBar, UIButton.class)) { >+ NSString *buttonTitle = [button titleForState:UIControlStateNormal]; >+ if (!buttonTitle.length) >+ continue; >+ >+ if (CFStringCompare((__bridge CFStringRef)buttonTitle, action.get(), 0) != kCFCompareEqualTo) >+ continue; >+ >+ buttonForAction = button; >+ windowForButton = window; >+ } >+ } >+ >+ if (!buttonForAction) >+ return nullptr; >+ >+ CGRect rectInRootViewCoordinates = [buttonForAction convertRect:buttonForAction.bounds toView:platformContentView()]; >+ return m_context->objectFromRect(WebCore::FloatRect(rectInRootViewCoordinates.origin.x, rectInRootViewCoordinates.origin.y, rectInRootViewCoordinates.size.width, rectInRootViewCoordinates.size.height)); >+} >+ > void UIScriptController::platformSetDidEndScrollingCallback() > { > TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView(); >@@ -830,21 +916,6 @@ void UIScriptController::completeBackSwipe(JSValueRef callback) > [webView _completeBackSwipeForTesting]; > } > >-static BOOL forEachViewInHierarchy(UIView *view, void(^mapFunction)(UIView *subview, BOOL *stop)) >-{ >- BOOL stop = NO; >- mapFunction(view, &stop); >- if (stop) >- return YES; >- >- for (UIView *subview in view.subviews) { >- stop = forEachViewInHierarchy(subview, mapFunction); >- if (stop) >- break; >- } >- return stop; >-} >- > bool UIScriptController::isShowingDataListSuggestions() const > { > Class remoteKeyboardWindowClass = NSClassFromString(@"UIRemoteKeyboardWindow"); >@@ -949,9 +1020,9 @@ JSObjectRef UIScriptController::attachmentInfo(JSStringRef jsAttachmentIdentifie > return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:attachmentInfoDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr); > } > >-NSUndoManager *UIScriptController::platformUndoManager() const >+UIView *UIScriptController::platformContentView() const > { >- return [(UIView *)[TestController::singleton().mainWebView()->platformView() valueForKeyPath:@"_currentContentView"] undoManager]; >+ return [TestController::singleton().mainWebView()->platformView() valueForKeyPath:@"_currentContentView"]; > } > > } >diff --git a/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm b/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm >index 757fa06bd78f1833f13b38cbc9b030f93419763f..09a26e110061bd9fd0488ee4b12c83ea25fda67c 100644 >--- a/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm >+++ b/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm >@@ -209,9 +209,9 @@ void UIScriptController::toggleCapsLock(JSValueRef callback) > doAsyncTask(callback); > } > >-NSUndoManager *UIScriptController::platformUndoManager() const >+NSView *UIScriptController::platformContentView() const > { >- return TestController::singleton().mainWebView()->platformView().undoManager; >+ return TestController::singleton().mainWebView()->platformView(); > } > > } // namespace WTR >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index cabed6b27dc78adb8cee1ab4461bbd74ed0b4d5e..502439a5adb2bac5dc4f311130e3e40c7a22d4e8 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,47 @@ >+2019-02-08 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Allow pages to trigger programmatic paste from script on iOS >+ https://bugs.webkit.org/show_bug.cgi?id=194271 >+ <rdar://problem/47808810> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Add new tests to exercise programmatic pasting. >+ >+ * TestExpectations: >+ * editing/pasteboard/ios/dom-paste-confirmation-expected.txt: Added. >+ * editing/pasteboard/ios/dom-paste-confirmation.html: Added. >+ >+ Verify that the user can tap "Paste" to allow programmatic pasting. >+ >+ * editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt: Added. >+ * editing/pasteboard/ios/dom-paste-consecutive-confirmations.html: Added. >+ >+ Verify that DOM paste access isn't carried over when using `setTimeout` in a user gesture event handler. >+ >+ * editing/pasteboard/ios/dom-paste-rejection-expected.txt: Added. >+ * editing/pasteboard/ios/dom-paste-rejection.html: Added. >+ >+ Verify that resigning first responder dismisses the callout bar and does not allow programmatic pasting. >+ >+ * editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt: Added. >+ * editing/pasteboard/ios/dom-paste-requires-user-gesture.html: Added. >+ >+ Verify that user gesture is required to present the callout menu for a programmatic paste request. >+ >+ * editing/pasteboard/ios/resources/dom-paste-helper.js: Added. >+ (return.new.Promise.): >+ (async._waitForOrTriggerPasteMenu): >+ (async.triggerPasteMenuAfterTapAt): >+ (async.waitForPasteMenu): >+ >+ Add helpers to summon, wait for, and interact with the callout bar when the page attempts to trigger a paste. >+ >+ * platform/ios-wk2/TestExpectations: >+ * platform/win/TestExpectations: >+ >+ Skip editing/pasteboard/ios by default, and enable it only in the modern WebKit port of iOS. >+ > 2019-02-07 Justin Fan <justin_fan@apple.com> > > [Web GPU] GPUDevice::createTexture implementation prototype >diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations >index fe88082b85ecc0610bcfc844c34f6c2d3d8aaac0..e5f294a499d2baed35cf71523fbbd9d8890b64ac 100644 >--- a/LayoutTests/TestExpectations >+++ b/LayoutTests/TestExpectations >@@ -52,6 +52,7 @@ http/tests/cookies/same-site [ Skip ] > system-preview [ Skip ] > editing/images [ Skip ] > pointerevents/ios [ Skip ] >+editing/pasteboard/ios [ Skip ] > editing/pasteboard/mac [ Skip ] > > # window.showModalDialog is only tested in DumpRenderTree on Mac. >diff --git a/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation-expected.txt b/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..1e522898bf671c3ddb50052a6a0dc78e0cc2c967 >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation-expected.txt >@@ -0,0 +1,18 @@ >+Click here to copy >+Click here to copy >+Click here to copy >+Verifies that a callout is shown when the page programmatically triggers paste, and that tapping the callout allows the paste to happen. To manually test, tap the text on the bottom, tap the editable area above, and then select 'Paste' in the resulting callout menu. The text 'Click here to copy' should be pasted twice in the editable area. >+ >+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". >+ >+PASS document.queryCommandSupported('Paste') is true >+PASS document.queryCommandEnabled('Paste') is true >+PASS event.clipboardData.getData('text/plain') is "Click here to copy" >+PASS document.execCommand('Paste') is true >+PASS event.clipboardData.getData('text/plain') is "Click here to copy" >+PASS document.execCommand('Paste') is true >+PASS editor.textContent is "Click here to copyClick here to copy" >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation.html b/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation.html >new file mode 100644 >index 0000000000000000000000000000000000000000..74c589a1b978bc866f26a2181ce5c092a9c4f864 >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/dom-paste-confirmation.html >@@ -0,0 +1,73 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true ] --> >+<html> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+<head> >+<script src="./resources/dom-paste-helper.js"></script> >+<script src="../../../resources/js-test.js"></script> >+<script src="../../../resources/ui-helper.js"></script> >+<style> >+body { >+ margin: 0; >+} >+ >+#copy, #editor { >+ text-align: center; >+} >+ >+#copy { >+ font-size: 40px; >+ width: 100%; >+ height: 50px; >+ border: 1px dashed black; >+} >+ >+#editor { >+ width: 100%; >+ height: 100px; >+ border: 1px dashed silver; >+} >+</style> >+</head> >+<body> >+<div id="editor" contenteditable></div> >+<div id="copy">Click here to copy</div> >+<div id="description"></div> >+<div id="console"></div> >+<script> >+jsTestIsAsync = true; >+ >+const copy = document.getElementById("copy"); >+const editor = document.getElementById("editor"); >+ >+description("Verifies that a callout is shown when the page programmatically triggers paste, and that tapping the callout allows the paste to happen. To manually test, tap the text on the bottom, tap the editable area above, and then select 'Paste' in the resulting callout menu. The text 'Click here to copy' should be pasted <strong><em>twice</em></strong> in the editable area."); >+ >+copy.addEventListener("click", () => { >+ getSelection().selectAllChildren(copy); >+ document.execCommand("Copy"); >+ getSelection().removeAllRanges(); >+}); >+ >+editor.addEventListener("paste", event => shouldBeEqualToString("event.clipboardData.getData('text/plain')", "Click here to copy")); >+editor.addEventListener("click", event => { >+ getSelection().setPosition(editor); >+ shouldBe("document.queryCommandSupported('Paste')", "true"); >+ shouldBe("document.queryCommandEnabled('Paste')", "true"); >+ shouldBe("document.execCommand('Paste')", "true"); >+ document.execCommand('InsertParagraph'); >+ shouldBe("document.execCommand('Paste')", "true"); >+ shouldBeEqualToString("editor.textContent", "Click here to copyClick here to copy"); >+ event.preventDefault(); >+ editor.blur(); >+}); >+ >+(async () => { >+ if (!window.testRunner) >+ return; >+ >+ await UIHelper.activateAt(160, 125); >+ await triggerPasteMenuAfterTapAt(160, 50); >+ finishJSTest(); >+})(); >+</script> >+</body> >+</html> >diff --git a/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt b/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..ad36273ebc50464ad2af114ba325088443c9fc39 >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations-expected.txt >@@ -0,0 +1,13 @@ >+Click here to copy >+Verifies that no callout is shown when the page programmatically triggers paste on a timer after user interaction. To test manually, click the text on the bottom to copy, and then click the editable area above to trigger two programmatic pastes with the callout bar. Check that permissions for the first programmatic paste do not affect the second programmatic paste, since it is performed on a zero-delay timer. >+ >+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". >+ >+PASS document.execCommand('Paste') is true >+PASS editor.textContent is "Click here to copy" >+PASS document.execCommand('Paste') is true >+PASS editor.textContent is "Click here to copy" >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations.html b/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations.html >new file mode 100644 >index 0000000000000000000000000000000000000000..7971a586f032f6e9098d92b361c6a94f5e897fa2 >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/dom-paste-consecutive-confirmations.html >@@ -0,0 +1,93 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true ] --> >+<html> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+<head> >+<script src="./resources/dom-paste-helper.js"></script> >+<script src="../../../resources/js-test.js"></script> >+<script src="../../../resources/ui-helper.js"></script> >+<style> >+body { >+ margin: 0; >+} >+ >+#copy, #editor { >+ text-align: center; >+} >+ >+#copy { >+ font-size: 40px; >+ width: 100%; >+ height: 50px; >+ border: 1px dashed black; >+} >+ >+#editor { >+ width: 100%; >+ height: 100px; >+ border: 1px dashed silver; >+} >+</style> >+</head> >+<body> >+<div id="editor" contenteditable></div> >+<div id="copy">Click here to copy</div> >+<div id="description"></div> >+<div id="console"></div> >+<script> >+jsTestIsAsync = true; >+ >+const copy = document.getElementById("copy"); >+const editor = document.getElementById("editor"); >+ >+description("Verifies that no callout is shown when the page programmatically triggers paste on a timer after user interaction. To test manually, click the text on the bottom to copy, and then click the editable area above to trigger two programmatic pastes with the callout bar. Check that permissions for the first programmatic paste do not affect the second programmatic paste, since it is performed on a zero-delay timer."); >+ >+async function waitForAndTapPasteMenuTwice() { >+ return new Promise(resolve => { >+ testRunner.runUIScript(` >+ (() => { >+ doneCount = 0; >+ function incrementProgress() { >+ if (++doneCount === 4) >+ uiController.uiScriptComplete(); >+ } >+ >+ uiController.didHideMenuCallback = incrementProgress; >+ uiController.didShowMenuCallback = () => { >+ const rect = uiController.rectForMenuAction("Paste"); >+ uiController.singleTapAtPoint(rect.left + rect.width / 2, rect.top + rect.height / 2, incrementProgress); >+ }; >+ })()`, resolve); >+ }); >+} >+ >+copy.addEventListener("click", () => { >+ getSelection().selectAllChildren(copy); >+ document.execCommand("Copy"); >+ getSelection().removeAllRanges(); >+}); >+ >+function paste() { >+ getSelection().setPosition(editor); >+ shouldBe("document.execCommand('Paste')", "true"); >+ shouldBeEqualToString("editor.textContent", "Click here to copy"); >+ editor.textContent = ""; >+ getSelection().removeAllRanges(editor); >+} >+ >+editor.addEventListener("click", event => { >+ event.preventDefault(); >+ paste(); >+ setTimeout(paste, 0); >+}); >+ >+(async () => { >+ if (!window.testRunner || !window.internals) >+ return; >+ >+ waitForAndTapPasteMenuTwice().then(finishJSTest); >+ await UIHelper.activateAt(160, 125); >+ await UIHelper.activateAt(160, 50); >+})(); >+</script> >+</body> >+</html> >diff --git a/LayoutTests/editing/pasteboard/ios/dom-paste-rejection-expected.txt b/LayoutTests/editing/pasteboard/ios/dom-paste-rejection-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..bccad1732b52aa3840657af38c7ff69c82320fa2 >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/dom-paste-rejection-expected.txt >@@ -0,0 +1,14 @@ >+Click here to copy >+Verifies that a callout is shown when the page programmatically triggers paste, and that dismissing the callout prevents the paste from happening. To manually test, tap the text on the bottom, tap the editable area above, and then dismiss the resulting callout menu by scrolling or tapping elsewhere. The text 'Click here to copy' should not be pasted, and the callout bar should disappear. >+ >+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". >+ >+PASS document.queryCommandSupported('Paste') is true >+PASS document.queryCommandEnabled('Paste') is true >+PASS document.execCommand('Paste') is false >+PASS document.execCommand('Paste') is false >+PASS editor.textContent is "" >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/editing/pasteboard/ios/dom-paste-rejection.html b/LayoutTests/editing/pasteboard/ios/dom-paste-rejection.html >new file mode 100644 >index 0000000000000000000000000000000000000000..7515baa817e57a8412f694c6bb79bbec0dffcd97 >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/dom-paste-rejection.html >@@ -0,0 +1,72 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true ] --> >+<html> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+<head> >+<script src="./resources/dom-paste-helper.js"></script> >+<script src="../../../resources/js-test.js"></script> >+<script src="../../../resources/ui-helper.js"></script> >+<style> >+body { >+ margin: 0; >+} >+ >+#copy, #editor { >+ text-align: center; >+} >+ >+#copy { >+ font-size: 40px; >+ width: 100%; >+ height: 50px; >+ border: 1px dashed black; >+} >+ >+#editor { >+ width: 100%; >+ height: 100px; >+ border: 1px dashed silver; >+} >+</style> >+</head> >+<body> >+<div id="editor" contenteditable></div> >+<div id="copy">Click here to copy</div> >+<div id="description"></div> >+<div id="console"></div> >+<script> >+jsTestIsAsync = true; >+ >+const copy = document.getElementById("copy"); >+const editor = document.getElementById("editor"); >+ >+description("Verifies that a callout is shown when the page programmatically triggers paste, and that dismissing the callout prevents the paste from happening. To manually test, tap the text on the bottom, tap the editable area above, and then dismiss the resulting callout menu by scrolling or tapping elsewhere. The text 'Click here to copy' should <strong>not</strong> be pasted, and the callout bar should disappear."); >+ >+copy.addEventListener("click", () => { >+ getSelection().selectAllChildren(copy); >+ document.execCommand("Copy"); >+ getSelection().removeAllRanges(); >+}); >+ >+editor.addEventListener("paste", event => shouldBeEqualToString("event.clipboardData.getData('text/plain')", "Click here to copy")); >+editor.addEventListener("click", event => { >+ getSelection().setPosition(editor); >+ shouldBe("document.queryCommandSupported('Paste')", "true"); >+ shouldBe("document.queryCommandEnabled('Paste')", "true"); >+ shouldBe("document.execCommand('Paste')", "false"); >+ shouldBe("document.execCommand('Paste')", "false"); >+ shouldBeEqualToString("editor.textContent", ""); >+ event.preventDefault(); >+ editor.blur(); >+}); >+ >+(async () => { >+ if (!window.testRunner) >+ return; >+ >+ await UIHelper.activateAt(160, 125); >+ await triggerPasteMenuAfterTapAt(160, 50, false); >+ finishJSTest(); >+})(); >+</script> >+</body> >+</html> >diff --git a/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt b/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..67aaeca32ae25f8662d14c940df6139f7eebebed >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture-expected.txt >@@ -0,0 +1,13 @@ >+Click here to copy >+Click here to copy >+Verifies that no callout is shown when the page programmatically triggers paste outside the scope of user interaction. This test requires WebKitTestRunner. >+ >+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". >+ >+PASS document.execCommand('Paste') is true >+PASS document.execCommand('Paste') is false >+PASS editor.textContent is "Click here to copy" >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture.html b/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture.html >new file mode 100644 >index 0000000000000000000000000000000000000000..5223f63e75fa7a8f65e4499191bedb56d9dec8a2 >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/dom-paste-requires-user-gesture.html >@@ -0,0 +1,62 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true ] --> >+<html> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+<head> >+<script src="./resources/dom-paste-helper.js"></script> >+<script src="../../../resources/js-test.js"></script> >+<script src="../../../resources/ui-helper.js"></script> >+<style> >+body { >+ margin: 0; >+} >+ >+#copy, #editor { >+ text-align: center; >+} >+ >+#copy { >+ font-size: 40px; >+ width: 100%; >+ height: 50px; >+ border: 1px dashed black; >+} >+ >+#editor { >+ width: 100%; >+ height: 100px; >+ border: 1px dashed silver; >+} >+</style> >+</head> >+<body> >+<div id="editor" contenteditable></div> >+<div id="copy">Click here to copy</div> >+<div id="description"></div> >+<div id="console"></div> >+<script> >+jsTestIsAsync = true; >+ >+const copy = document.getElementById("copy"); >+const editor = document.getElementById("editor"); >+ >+description("Verifies that no callout is shown when the page programmatically triggers paste outside the scope of user interaction. This test requires WebKitTestRunner."); >+ >+copy.addEventListener("click", () => { >+ getSelection().selectAllChildren(copy); >+ document.execCommand("Copy"); >+ getSelection().removeAllRanges(); >+}); >+ >+UIHelper.activateAt(160, 125).then(() => { >+ editor.focus(); >+ waitForPasteMenu().then(finishJSTest); >+ >+ UIHelper.ensurePresentationUpdate().then(() => { >+ internals.withUserGesture(() => shouldBe("document.execCommand('Paste')", "true")); >+ shouldBe("document.execCommand('Paste')", "false"); >+ shouldBeEqualToString("editor.textContent", "Click here to copy"); >+ }); >+}); >+</script> >+</body> >+</html> >diff --git a/LayoutTests/editing/pasteboard/ios/resources/dom-paste-helper.js b/LayoutTests/editing/pasteboard/ios/resources/dom-paste-helper.js >new file mode 100644 >index 0000000000000000000000000000000000000000..bbcdb4fae56da0bc5b60fbdcb716291d372b0223 >--- /dev/null >+++ b/LayoutTests/editing/pasteboard/ios/resources/dom-paste-helper.js >@@ -0,0 +1,35 @@ >+ >+async function _waitForOrTriggerPasteMenu(x, y, proceedWithPaste, shouldTap) { >+ return new Promise(resolve => { >+ testRunner.runUIScript(` >+ (() => { >+ doneCount = 0; >+ function checkDone() { >+ if (++doneCount === (${shouldTap} ? 3 : 2)) >+ uiController.uiScriptComplete(); >+ } >+ >+ uiController.didHideMenuCallback = checkDone; >+ uiController.didShowMenuCallback = () => { >+ if (${proceedWithPaste}) { >+ const rect = uiController.rectForMenuAction("Paste"); >+ uiController.singleTapAtPoint(rect.left + rect.width / 2, rect.top + rect.height / 2, checkDone); >+ } else { >+ uiController.resignFirstResponder(); >+ checkDone(); >+ } >+ }; >+ >+ if (${shouldTap}) >+ uiController.singleTapAtPoint(${x}, ${y}, checkDone); >+ })()`, resolve); >+ }); >+} >+ >+async function triggerPasteMenuAfterTapAt(x, y, proceedWithPaste = true) { >+ return _waitForOrTriggerPasteMenu(x, y, proceedWithPaste, true); >+} >+ >+async function waitForPasteMenu(proceedWithPaste = true) { >+ return _waitForOrTriggerPasteMenu(null, null, proceedWithPaste, false); >+} >diff --git a/LayoutTests/platform/ios-wk2/TestExpectations b/LayoutTests/platform/ios-wk2/TestExpectations >index 8dc1fb3fe08ddb2d20856bd78e92089b352f3e05..59a5103fbc2c1378670a4f7d0a1117a9f4bdc957 100644 >--- a/LayoutTests/platform/ios-wk2/TestExpectations >+++ b/LayoutTests/platform/ios-wk2/TestExpectations >@@ -15,6 +15,7 @@ scrollingcoordinator [ Pass ] > fast/web-share [ Pass ] > editing/find [ Pass ] > editing/input/ios [ Pass ] >+editing/pasteboard/ios [ Pass ] > editing/undo-manager [ Pass ] > > editing/selection/character-granularity-rect.html [ Failure ] >diff --git a/LayoutTests/platform/win/TestExpectations b/LayoutTests/platform/win/TestExpectations >index 3230bf0469a649e9fad7767ce2b6152db4f528ee..ffec15ae231e10adac03b3db2c8987d629a3736f 100644 >--- a/LayoutTests/platform/win/TestExpectations >+++ b/LayoutTests/platform/win/TestExpectations >@@ -1157,6 +1157,7 @@ media/track/track-in-band-cues-added-once.html [ Skip ] # Timeout > ###### Pasteboard > ###### These tests are very flaky. > editing/pasteboard/ [ Pass Failure ] >+editing/pasteboard/ios [ Skip ] > [ Debug ] editing/pasteboard/copy-crash.html [ Skip ] # Debug Assertion > [ Debug ] editing/pasteboard/copy-crash-with-extraneous-attribute.html [ Skip ] # Debug Assertion > [ Debug ] editing/pasteboard/testcase-9507.html [ Skip ] # Debug Assertion
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 194271
:
361202
|
361212
|
361252
|
361305
|
361306
|
361316
|
361350
|
361351
|
361397
|
361398
|
361537
|
361547
| 361582 |
361702
|
361837
|
361847