<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[176959] trunk/Tools</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/176959">176959</a></dd>
<dt>Author</dt> <dd>timothy_horton@apple.com</dd>
<dt>Date</dt> <dd>2014-12-08 11:40:27 -0800 (Mon, 08 Dec 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add action menu tests
https://bugs.webkit.org/show_bug.cgi?id=139156

Reviewed by Dean Jackson.

* TestWebKitAPI/PlatformWebView.h:
* TestWebKitAPI/mac/PlatformWebViewMac.mm:
(TestWebKitAPI::PlatformWebView::PlatformWebView):
Add a mechanism allowing tests to provide their own WKView subclass.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKit2/action-menu-targets.html: Added.
Add a file with lots of menu targets.

* TestWebKitAPI/Tests/WebKit2ObjC/ActionMenus.mm: Added.
(-[ActionMenusTestWKView _actionMenuItemsForHitTestResult:withType:defaultActionMenuItems:userData:]):
(-[ActionMenusTestWKView runMenuSequenceAtPoint:preMenuNeedsUpdateHandler:preWillOpenMenuHandler:preDidCloseMenuHandler:]):
(-[ActionMenusTestWKView runMenuSequenceAtPoint:preDidCloseMenuHandler:]):
Run through the normal sequence of callbacks, recording what items and type were returned, and give clients a chance to do work at various points in the process.

(-[ActionMenusTestWKView _setOverrideActionMenuItems:]):

(TestWebKitAPI::didFinishLoadForFrameCallback):
(TestWebKitAPI::didFinishDownloadCallback):
(TestWebKitAPI::didCreateDownloadDestinationCallback):
Watch downloads and ensure that they match the content we expect.

(TestWebKitAPI::watchPasteboardForString):
(TestWebKitAPI::watchPasteboardForImage):
(TestWebKitAPI::JavaScriptStringCallbackContext::JavaScriptStringCallbackContext):
(TestWebKitAPI::JavaScriptBoolCallbackContext::JavaScriptBoolCallbackContext):
(TestWebKitAPI::javaScriptStringCallback):
(TestWebKitAPI::javaScriptBoolCallback):
(TestWebKitAPI::callJavaScriptReturningString):
(TestWebKitAPI::callJavaScriptReturningBool):
(TestWebKitAPI::watchEditableAreaForString):
(TestWebKitAPI::waitForVideoReady):
(TestWebKitAPI::retrieveSelection):
(TestWebKitAPI::retrieveSelectionInElement):
(TestWebKitAPI::performMenuItemAtIndexOfTypeAsync):
(TestWebKitAPI::ensureMenuItemAtIndexOfTypeIsDisabled):
Add many helpers for testing, especially to make asynchronous things synchronous.

(TestWebKitAPI::windowPointForTarget):
(TestWebKitAPI::inset8):
Hard-code points in action-menu-targets.html for hit testing.

(TestWebKitAPI::TEST):
Add a suite of tests for WebKit2 action menus.

* TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundle.mm: Added.
(TestWebKitAPI::createActionContextForPhoneNumber):
(TestWebKitAPI::ActionMenuTest::ActionMenuTest):
(TestWebKitAPI::ActionMenuTest::prepareForActionMenu):
(TestWebKitAPI::ActionMenuTest::actionContextForResultAtPoint):
(TestWebKitAPI::ActionMenuTest::drawRect):
(TestWebKitAPI::ActionMenuTest::didCreatePage):
Add a bundle with a PageOverlay and a actionContextForResultAtPoint override,
so that we can test that infrastructure.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsTestWebKitAPIPlatformWebViewh">trunk/Tools/TestWebKitAPI/PlatformWebView.h</a></li>
<li><a href="#trunkToolsTestWebKitAPITestWebKitAPIxcodeprojprojectpbxproj">trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj</a></li>
<li><a href="#trunkToolsTestWebKitAPImacPlatformWebViewMacmm">trunk/Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkToolsTestWebKitAPITestsWebKit2actionmenutargetshtml">trunk/Tools/TestWebKitAPI/Tests/WebKit2/action-menu-targets.html</a></li>
<li><a href="#trunkToolsTestWebKitAPITestsWebKit2ObjCActionMenusmm">trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenus.mm</a></li>
<li><a href="#trunkToolsTestWebKitAPITestsWebKit2ObjCActionMenusBundlemm">trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundle.mm</a></li>
<li><a href="#trunkToolsTestWebKitAPITestsWebKit2ObjCActionMenusBundleSPIh">trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundleSPI.h</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (176958 => 176959)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog        2014-12-08 19:30:19 UTC (rev 176958)
+++ trunk/Tools/ChangeLog        2014-12-08 19:40:27 UTC (rev 176959)
</span><span class="lines">@@ -1,3 +1,65 @@
</span><ins>+2014-12-08  Tim Horton  &lt;timothy_horton@apple.com&gt;
+
+        Add action menu tests
+        https://bugs.webkit.org/show_bug.cgi?id=139156
+
+        Reviewed by Dean Jackson.
+
+        * TestWebKitAPI/PlatformWebView.h:
+        * TestWebKitAPI/mac/PlatformWebViewMac.mm:
+        (TestWebKitAPI::PlatformWebView::PlatformWebView):
+        Add a mechanism allowing tests to provide their own WKView subclass.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKit2/action-menu-targets.html: Added.
+        Add a file with lots of menu targets.
+
+        * TestWebKitAPI/Tests/WebKit2ObjC/ActionMenus.mm: Added.
+        (-[ActionMenusTestWKView _actionMenuItemsForHitTestResult:withType:defaultActionMenuItems:userData:]):
+        (-[ActionMenusTestWKView runMenuSequenceAtPoint:preMenuNeedsUpdateHandler:preWillOpenMenuHandler:preDidCloseMenuHandler:]):
+        (-[ActionMenusTestWKView runMenuSequenceAtPoint:preDidCloseMenuHandler:]):
+        Run through the normal sequence of callbacks, recording what items and type were returned, and give clients a chance to do work at various points in the process.
+
+        (-[ActionMenusTestWKView _setOverrideActionMenuItems:]):
+
+        (TestWebKitAPI::didFinishLoadForFrameCallback):
+        (TestWebKitAPI::didFinishDownloadCallback):
+        (TestWebKitAPI::didCreateDownloadDestinationCallback):
+        Watch downloads and ensure that they match the content we expect.
+
+        (TestWebKitAPI::watchPasteboardForString):
+        (TestWebKitAPI::watchPasteboardForImage):
+        (TestWebKitAPI::JavaScriptStringCallbackContext::JavaScriptStringCallbackContext):
+        (TestWebKitAPI::JavaScriptBoolCallbackContext::JavaScriptBoolCallbackContext):
+        (TestWebKitAPI::javaScriptStringCallback):
+        (TestWebKitAPI::javaScriptBoolCallback):
+        (TestWebKitAPI::callJavaScriptReturningString):
+        (TestWebKitAPI::callJavaScriptReturningBool):
+        (TestWebKitAPI::watchEditableAreaForString):
+        (TestWebKitAPI::waitForVideoReady):
+        (TestWebKitAPI::retrieveSelection):
+        (TestWebKitAPI::retrieveSelectionInElement):
+        (TestWebKitAPI::performMenuItemAtIndexOfTypeAsync):
+        (TestWebKitAPI::ensureMenuItemAtIndexOfTypeIsDisabled):
+        Add many helpers for testing, especially to make asynchronous things synchronous.
+
+        (TestWebKitAPI::windowPointForTarget):
+        (TestWebKitAPI::inset8):
+        Hard-code points in action-menu-targets.html for hit testing.
+
+        (TestWebKitAPI::TEST):
+        Add a suite of tests for WebKit2 action menus.
+
+        * TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundle.mm: Added.
+        (TestWebKitAPI::createActionContextForPhoneNumber):
+        (TestWebKitAPI::ActionMenuTest::ActionMenuTest):
+        (TestWebKitAPI::ActionMenuTest::prepareForActionMenu):
+        (TestWebKitAPI::ActionMenuTest::actionContextForResultAtPoint):
+        (TestWebKitAPI::ActionMenuTest::drawRect):
+        (TestWebKitAPI::ActionMenuTest::didCreatePage):
+        Add a bundle with a PageOverlay and a actionContextForResultAtPoint override,
+        so that we can test that infrastructure.
+
</ins><span class="cx"> 2014-12-08  Philippe Normand  &lt;pnormand@igalia.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [GTK] UserMedia Permission Request API
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPIPlatformWebViewh"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/PlatformWebView.h (176958 => 176959)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/PlatformWebView.h        2014-12-08 19:30:19 UTC (rev 176958)
+++ trunk/Tools/TestWebKitAPI/PlatformWebView.h        2014-12-08 19:40:27 UTC (rev 176959)
</span><span class="lines">@@ -30,6 +30,10 @@
</span><span class="cx"> #include &lt;CoreGraphics/CGGeometry.h&gt;
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+#if PLATFORM(MAC)
+#include &lt;objc/objc.h&gt;
+#endif
+
</ins><span class="cx"> #ifdef __APPLE__
</span><span class="cx"> #ifdef __OBJC__
</span><span class="cx"> @class WKView;
</span><span class="lines">@@ -54,6 +58,9 @@
</span><span class="cx"> public:
</span><span class="cx">     explicit PlatformWebView(WKContextRef, WKPageGroupRef = 0);
</span><span class="cx">     explicit PlatformWebView(WKPageRef relatedPage);
</span><ins>+#if PLATFORM(MAC)
+    explicit PlatformWebView(WKContextRef, WKPageGroupRef, Class wkViewSubclass);
+#endif
</ins><span class="cx">     ~PlatformWebView();
</span><span class="cx"> 
</span><span class="cx">     WKPageRef page() const;
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPITestWebKitAPIxcodeprojprojectpbxproj"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (176958 => 176959)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj        2014-12-08 19:30:19 UTC (rev 176958)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj        2014-12-08 19:40:27 UTC (rev 176959)
</span><span class="lines">@@ -62,6 +62,9 @@
</span><span class="cx">                 29AB8AA1164C735800D49BEC /* CustomProtocolsTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 29AB8A9F164C735800D49BEC /* CustomProtocolsTest.mm */; };
</span><span class="cx">                 29AB8AA4164C7A9300D49BEC /* TestBrowsingContextLoadDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 29AB8AA2164C7A9300D49BEC /* TestBrowsingContextLoadDelegate.mm */; };
</span><span class="cx">                 2D640B5517875DFF00BFAF99 /* ScrollPinningBehaviors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2D640B5417875DFF00BFAF99 /* ScrollPinningBehaviors.cpp */; };
</span><ins>+                2D950FBC1A2217D000012434 /* ActionMenus.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D950FBA1A2217D000012434 /* ActionMenus.mm */; };
+                2D950FBE1A2217D300012434 /* ActionMenusBundle.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D950FBB1A2217D000012434 /* ActionMenusBundle.mm */; };
+                2D950FC01A230C3A00012434 /* action-menu-targets.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2D950FBF1A230C1E00012434 /* action-menu-targets.html */; };
</ins><span class="cx">                 2DD7D3AA178205D00026E1E3 /* ResizeReversePaginatedWebView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7D3A9178205D00026E1E3 /* ResizeReversePaginatedWebView.cpp */; };
</span><span class="cx">                 2DD7D3AF178227B30026E1E3 /* lots-of-text-vertical-lr.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2DD7D3AE178227AC0026E1E3 /* lots-of-text-vertical-lr.html */; };
</span><span class="cx">                 2E7765CD16C4D80A00BA2BB1 /* mainIOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2E7765CC16C4D80A00BA2BB1 /* mainIOS.mm */; };
</span><span class="lines">@@ -340,6 +343,7 @@
</span><span class="cx">                                 BCBD3737125ABBEB00D2C29F /* icon.png in Copy Resources */,
</span><span class="cx">                                 378E64791632707400B6C676 /* link-with-title.html in Copy Resources */,
</span><span class="cx">                                 9361002914DC95A70061379D /* lots-of-iframes.html in Copy Resources */,
</span><ins>+                                2D950FC01A230C3A00012434 /* action-menu-targets.html in Copy Resources */,
</ins><span class="cx">                                 93AF4ED11506F130007FD57E /* lots-of-images.html in Copy Resources */,
</span><span class="cx">                                 930AD402150698D00067970F /* lots-of-text.html in Copy Resources */,
</span><span class="cx">                                 2DD7D3AF178227B30026E1E3 /* lots-of-text-vertical-lr.html in Copy Resources */,
</span><span class="lines">@@ -422,8 +426,12 @@
</span><span class="cx">                 29AB8AA2164C7A9300D49BEC /* TestBrowsingContextLoadDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestBrowsingContextLoadDelegate.mm; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 29AB8AA3164C7A9300D49BEC /* TestBrowsingContextLoadDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestBrowsingContextLoadDelegate.h; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 2D640B5417875DFF00BFAF99 /* ScrollPinningBehaviors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScrollPinningBehaviors.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><ins>+                2D950FBA1A2217D000012434 /* ActionMenus.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ActionMenus.mm; path = WebKit2ObjC/ActionMenus.mm; sourceTree = &quot;&lt;group&gt;&quot;; };
+                2D950FBB1A2217D000012434 /* ActionMenusBundle.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ActionMenusBundle.mm; path = WebKit2ObjC/ActionMenusBundle.mm; sourceTree = &quot;&lt;group&gt;&quot;; };
+                2D950FBF1A230C1E00012434 /* action-menu-targets.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = &quot;action-menu-targets.html&quot;; sourceTree = &quot;&lt;group&gt;&quot;; };
</ins><span class="cx">                 2DD7D3A9178205D00026E1E3 /* ResizeReversePaginatedWebView.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ResizeReversePaginatedWebView.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 2DD7D3AE178227AC0026E1E3 /* lots-of-text-vertical-lr.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = &quot;lots-of-text-vertical-lr.html&quot;; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><ins>+                2DDE58961A36342B00AA80DE /* ActionMenusBundleSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ActionMenusBundleSPI.h; path = WebKit2ObjC/ActionMenusBundleSPI.h; sourceTree = &quot;&lt;group&gt;&quot;; };
</ins><span class="cx">                 2E7765CC16C4D80A00BA2BB1 /* mainIOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = mainIOS.mm; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 2E7765CE16C4D81100BA2BB1 /* mainMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = mainMac.mm; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 333B9CE11277F23100FEFCE3 /* PreventEmptyUserAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PreventEmptyUserAgent.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="lines">@@ -807,6 +815,9 @@
</span><span class="cx">                 BC3C4C6F14575B1D0025FB62 /* WebKit2 Objective-C */ = {
</span><span class="cx">                         isa = PBXGroup;
</span><span class="cx">                         children = (
</span><ins>+                                2D950FBA1A2217D000012434 /* ActionMenus.mm */,
+                                2D950FBB1A2217D000012434 /* ActionMenusBundle.mm */,
+                                2DDE58961A36342B00AA80DE /* ActionMenusBundleSPI.h */,
</ins><span class="cx">                                 297234B2173AD04800983601 /* CustomProtocolsInvalidScheme.mm */,
</span><span class="cx">                                 297234B5173AFAC700983601 /* CustomProtocolsInvalidScheme_Bundle.cpp */,
</span><span class="cx">                                 290F4276172A232C00939FF0 /* CustomProtocolsSyncXHRTest.mm */,
</span><span class="lines">@@ -985,6 +996,7 @@
</span><span class="cx">                         children = (
</span><span class="cx">                                 4A410F4D19AF7BEF002EBAB5 /* getUserMedia.html */,
</span><span class="cx">                                 C045F9461385C2F800C0F3CD /* 18-characters.html */,
</span><ins>+                                2D950FBF1A230C1E00012434 /* action-menu-targets.html */,
</ins><span class="cx">                                 F6B7BE9617469B7E008A3445 /* associate-form-controls.html */,
</span><span class="cx">                                 76E182DE15475A8300F1FADD /* auto-submitting-form.html */,
</span><span class="cx">                                 1A50AA1F1A2A4EA500F4C345 /* close-from-within-create-page.html */,
</span><span class="lines">@@ -1334,6 +1346,7 @@
</span><span class="cx">                                 14464013167A8305000BD218 /* LayoutUnit.cpp in Sources */,
</span><span class="cx">                                 2D640B5517875DFF00BFAF99 /* ScrollPinningBehaviors.cpp in Sources */,
</span><span class="cx">                                 26300B1816755CD90066886D /* ListHashSet.cpp in Sources */,
</span><ins>+                                2D950FBC1A2217D000012434 /* ActionMenus.mm in Sources */,
</ins><span class="cx">                                 A1A4FE5F18DD3DB700B5EA8A /* Download.mm in Sources */,
</span><span class="cx">                                 C95501BF19AD2FAF0049BE3E /* Preferences.mm in Sources */,
</span><span class="cx">                                 52CB47411448FB9300873995 /* LoadAlternateHTMLStringWithNonDirectoryURL.cpp in Sources */,
</span><span class="lines">@@ -1476,6 +1489,7 @@
</span><span class="cx">                                 F6B7BE9517469212008A3445 /* DidAssociateFormControls_Bundle.cpp in Sources */,
</span><span class="cx">                                 C0BD669F131D3CFF00E18F2A /* ResponsivenessTimerDoesntFireEarly_Bundle.cpp in Sources */,
</span><span class="cx">                                 51FCF7A11534B2A000104491 /* ShouldGoToBackForwardListItem_Bundle.cpp in Sources */,
</span><ins>+                                2D950FBE1A2217D300012434 /* ActionMenusBundle.mm in Sources */,
</ins><span class="cx">                                 BC22D31914DC68B900FFB1DD /* UserMessage_Bundle.cpp in Sources */,
</span><span class="cx">                                 520BCF4C141EB09E00937EA8 /* WebArchive_Bundle.cpp in Sources */,
</span><span class="cx">                                 76E182DD1547569100F1FADD /* WillSendSubmitEvent_Bundle.cpp in Sources */,
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWebKit2actionmenutargetshtml"></a>
<div class="addfile"><h4>Added: trunk/Tools/TestWebKitAPI/Tests/WebKit2/action-menu-targets.html (0 => 176959)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WebKit2/action-menu-targets.html                                (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKit2/action-menu-targets.html        2014-12-08 19:40:27 UTC (rev 176959)
</span><span class="lines">@@ -0,0 +1,114 @@
</span><ins>+&lt;style&gt;
+div {
+    position: absolute;
+    width: 195px; height: 45px;
+    overflow: hidden;
+    border: 1px solid black;
+}
+&lt;/style&gt;
+
+&lt;script&gt;
+function editableAreaString(name) {
+    if (name.indexOf(&quot;textarea&quot;) === 0 || name.indexOf(&quot;input&quot;) === 0)
+        return document.getElementById(name).value;
+    return document.getElementById(name).innerText;
+}
+
+function stringifySelection() {
+    var sel = window.getSelection();
+    if (sel.rangeCount == 0 || sel.getRangeAt(0).collapsed)
+        return &quot;&lt;no selection&gt;&quot;;
+    return sel.toString();
+}
+
+function stringifySelectionInElement(name) {
+    var el = document.getElementById(name);
+    console.log(el);
+    if (el.selectionStart == undefined)
+        return &quot;&lt;no selection&gt;&quot;;
+    return el.value.substring(el.selectionStart, el.selectionEnd);
+}
+
+var failWasCalled = false;
+
+function fail() {
+    failWasCalled = true;
+}
+
+function wasFailCalled() {
+    return failWasCalled;
+}
+
+// MSE video:
+var source;
+var request;
+var videoIsReady = false;
+
+window.onload = function () {
+    playVideo();
+}
+
+function playVideo()
+{
+    request = new XMLHttpRequest();
+    request.responseType = 'arraybuffer';
+    request.open('GET', 'test-mse.mp4', true);
+    request.addEventListener('load', load);
+    request.send();
+}
+
+function load(event)
+{
+    source = new MediaSource();
+    source.addEventListener('sourceopen', sourceOpen);
+    var video = document.getElementById('mse-video');
+    video.src = URL.createObjectURL(source);
+}
+
+function sourceOpen(event)
+{
+    var sourceBuffer = source.addSourceBuffer('video/mp4;codecs=&quot;avc1.4D4001,mp4a.40.2&quot;');
+    sourceBuffer.appendBuffer(request.response);
+    sourceBuffer.addEventListener('updateend', updateEnd);
+}
+
+function updateEnd(event)
+{
+    videoIsReady = true;
+}
+
+function isVideoReady()
+{
+    return videoIsReady;
+}
+
+&lt;/script&gt;
+
+&lt;div style=&quot;top: 0px; left: 0px;&quot;&gt;word&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 0px; left: 200px;&quot;&gt;New York&lt;/div&gt;&lt;br/&gt;
+
+&lt;div style=&quot;top: 50px; left: 0px;&quot;&gt;1 Infinite Loop, Cupertino, CA 95014&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 50px; left: 200px;&quot;&gt;May 17th, 2012&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 50px; left: 400px;&quot;&gt;(408) 996-1010&lt;/div&gt;&lt;br/&gt;
+
+&lt;div id=&quot;editable1&quot; style=&quot;top: 150px; left: 0px;&quot; contenteditable&gt;editable editable editable editable&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 150px; left: 200px;&quot;&gt;&lt;input id=&quot;input1&quot; type=&quot;text&quot; value=&quot;editable editable editable editable&quot;&gt;&lt;/input&gt;&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 150px; left: 400px;&quot;&gt;&lt;textarea id=&quot;textarea1&quot;&gt;editable editable editable editable&lt;/textarea&gt;&lt;/div&gt;&lt;br/&gt;
+
+&lt;div id=&quot;editable2&quot; style=&quot;top: 200px; left: 0px;&quot; contenteditable&gt;New York some words&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 200px; left: 200px;&quot;&gt;&lt;input id=&quot;input2&quot; type=&quot;text&quot; value=&quot;New York some words&quot;&gt;&lt;/input&gt;&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 200px; left: 400px;&quot;&gt;&lt;textarea id=&quot;textarea2&quot;&gt;New York some words&lt;/textarea&gt;&lt;/div&gt;&lt;br/&gt;
+
+&lt;div style=&quot;top: 250px; left: 0px;&quot;&gt;&lt;img src=&quot;icon.png&quot; height=&quot;100%&quot;&gt;&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 250px; left: 200px;&quot;&gt;&lt;a href=&quot;http://example.org/&quot;&gt;&lt;img src=&quot;icon.png&quot; height=&quot;100%&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;br/&gt;
+
+&lt;div style=&quot;top: 300px; left: 0px; width: 95px;&quot;&gt;&lt;a href=&quot;http://example.org/&quot;&gt;http&lt;/a&gt;&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 300px; left: 100px; width: 95px;&quot;&gt;&lt;a href=&quot;ftp://example.org/&quot;&gt;ftp&lt;/a&gt;&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 300px; left: 200px; width: 95px;&quot;&gt;&lt;a href=&quot;mailto:example@example.org&quot;&gt;mailto link&lt;/a&gt;&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 300px; left: 300px; width: 95px;&quot;&gt;&lt;a href=&quot;javascript:fail()&quot;&gt;javascript link&lt;/a&gt;&lt;/div&gt;&lt;br/&gt;
+
+&lt;div style=&quot;top: 350px; left: 0px;&quot;&gt;&lt;video src=&quot;test.mp4&quot;&gt;&lt;/video&gt;&lt;/div&gt;&lt;br/&gt;
+&lt;div style=&quot;top: 350px; left: 200px;&quot;&gt;&lt;video id=&quot;mse-video&quot;&gt;&lt;/video&gt;&lt;/div&gt;&lt;br/&gt;
+
+
+&lt;div style=&quot;top: 0px; left: 750px; height: 600px;&quot;&gt;data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text data detectors menu text &lt;/div&gt;&lt;br/&gt;
</ins></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWebKit2ObjCActionMenusmm"></a>
<div class="addfile"><h4>Added: trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenus.mm (0 => 176959)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenus.mm                                (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenus.mm        2014-12-08 19:40:27 UTC (rev 176959)
</span><span class="lines">@@ -0,0 +1,739 @@
</span><ins>+/*
+ * Copyright (C) 2014 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.
+ */
+
+#import &quot;config.h&quot;
+#import &quot;Test.h&quot;
+
+#import &quot;PlatformUtilities.h&quot;
+#import &quot;PlatformWebView.h&quot;
+#import &quot;TestBrowsingContextLoadDelegate.h&quot;
+#import &lt;JavaScriptCore/JSContextRef.h&gt;
+#import &lt;JavaScriptCore/JSRetainPtr.h&gt;
+#import &lt;WebKit/WebKit2.h&gt;
+#import &lt;WebKit/WKActionMenuItemTypes.h&gt;
+#import &lt;WebKit/WKActionMenuTypes.h&gt;
+#import &lt;WebKit/WKPreferencesPrivate.h&gt;
+#import &lt;WebKit/WKSerializedScriptValue.h&gt;
+#import &lt;WebKit/WKViewPrivate.h&gt;
+#import &lt;wtf/RetainPtr.h&gt;
+
+static bool didFinishLoad = false;
+static bool didFinishDownload = false;
+
+@interface WKView (Details)
+
+- (void)prepareForMenu:(NSMenu *)menu withEvent:(NSEvent *)event;
+- (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event;
+- (void)didCloseMenu:(NSMenu *)menu withEvent:(NSEvent *)event;
+
+- (NSMenu *)actionMenu;
+- (void)copy:(id)sender;
+
+@end
+
+struct ActionMenuResult {
+    WKRetainPtr&lt;WKHitTestResultRef&gt; hitTestResult;
+    _WKActionMenuType type;
+    RetainPtr&lt;NSArray&gt; defaultMenuItems;
+    WKRetainPtr&lt;WKTypeRef&gt; userData;
+};
+
+@interface ActionMenusTestWKView : WKView {
+    ActionMenuResult _actionMenuResult;
+    RetainPtr&lt;NSArray&gt; _overrideItems;
+    BOOL _shouldHaveUserData;
+}
+
+@property (nonatomic, assign, setter=_setActionMenuResult:) ActionMenuResult _actionMenuResult;
+
+@end
+
+@implementation ActionMenusTestWKView
+
+@synthesize _actionMenuResult=_actionMenuResult;
+
+- (NSArray *)_actionMenuItemsForHitTestResult:(WKHitTestResultRef)hitTestResult withType:(_WKActionMenuType)type defaultActionMenuItems:(NSArray *)defaultMenuItems userData:(WKTypeRef)userData
+{
+    if (type != kWKActionMenuNone)
+        EXPECT_GT(defaultMenuItems.count, (NSUInteger)0);
+
+    // Clients should be able to pass userData from the Web to UI process, between
+    // the WKBundlePageContextMenuClient's prepareForActionMenu, and here.
+    // http://trac.webkit.org/changeset/175444
+    if (_shouldHaveUserData) {
+        EXPECT_NOT_NULL(userData);
+        EXPECT_EQ(WKDictionaryGetTypeID(), WKGetTypeID(userData));
+        WKRetainPtr&lt;WKStringRef&gt; hasLinkKey = adoptWK(WKStringCreateWithUTF8CString(&quot;hasLinkURL&quot;));
+        WKTypeRef hasLinkValue = WKDictionaryGetItemForKey((WKDictionaryRef)userData, hasLinkKey.get());
+        EXPECT_NOT_NULL(hasLinkValue);
+        EXPECT_EQ(WKBooleanGetTypeID(), WKGetTypeID(hasLinkValue));
+        WKRetainPtr&lt;WKURLRef&gt; absoluteLinkURL = adoptWK(WKHitTestResultCopyAbsoluteLinkURL(hitTestResult));
+        EXPECT_EQ(!!absoluteLinkURL, WKBooleanGetValue((WKBooleanRef)hasLinkValue));
+    } else
+        EXPECT_NULL(userData);
+
+    _actionMenuResult.hitTestResult = hitTestResult;
+    _actionMenuResult.type = type;
+    _actionMenuResult.defaultMenuItems = defaultMenuItems;
+    _actionMenuResult.userData = userData;
+    return _overrideItems ? _overrideItems.get() : defaultMenuItems;
+}
+
+- (void)runMenuSequenceAtPoint:(NSPoint)point preDidCloseMenuHandler:(void(^)(void))preDidCloseMenuHandler
+{
+    __block bool didFinishSequence = false;
+
+    NSMenu *actionMenu = self.actionMenu;
+    RetainPtr&lt;NSEvent&gt; event = [NSEvent mouseEventWithType:NSLeftMouseDown location:point modifierFlags:0 timestamp:0 windowNumber:self.window.windowNumber context:0 eventNumber:0 clickCount:0 pressure:0];
+
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self prepareForMenu:actionMenu withEvent:event.get()];
+    });
+
+    dispatch_async(dispatch_get_main_queue(), ^{
+        _shouldHaveUserData = YES;
+        [[actionMenu delegate] menuNeedsUpdate:actionMenu];
+        _shouldHaveUserData = NO;
+    });
+
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self willOpenMenu:actionMenu withEvent:event.get()];
+    });
+
+    void (^copiedPreDidCloseMenuHandler)() = Block_copy(preDidCloseMenuHandler);
+    dispatch_async(dispatch_get_main_queue(), ^{
+        copiedPreDidCloseMenuHandler();
+        Block_release(copiedPreDidCloseMenuHandler);
+        [self didCloseMenu:actionMenu withEvent:event.get()];
+        [self mouseDown:event.get()];
+        didFinishSequence = true;
+    });
+
+    TestWebKitAPI::Util::run(&amp;didFinishSequence);
+}
+
+- (void)_setOverrideActionMenuItems:(NSArray *)overrideItems
+{
+    _overrideItems = overrideItems;
+}
+
+@end
+
+namespace TestWebKitAPI {
+
+struct ActiveDownloadContext {
+    WKRetainPtr&lt;WKStringRef&gt; path;
+    bool shouldCheckForImage = 0;
+};
+
+static void didFinishLoadForFrameCallback(WKPageRef page, WKFrameRef frame, WKTypeRef userData, const void* clientInfo)
+{
+    didFinishLoad = true;
+}
+
+static void didFinishDownloadCallback(WKContextRef context, WKDownloadRef download, const void *clientInfo)
+{
+    WKStringRef wkActiveDownloadPath = ((ActiveDownloadContext*)clientInfo)-&gt;path.get();
+    size_t length = WKStringGetLength(wkActiveDownloadPath) + 1;
+    char *activeDownloadPath = (char *)calloc(1, length);
+    WKStringGetUTF8CString(wkActiveDownloadPath, activeDownloadPath, length);
+
+    if (((ActiveDownloadContext*)clientInfo)-&gt;shouldCheckForImage) {
+        RetainPtr&lt;NSImage&gt; image = adoptNS([[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:activeDownloadPath]]);
+
+        EXPECT_EQ(215, [image size].width);
+        EXPECT_EQ(174, [image size].height);
+    }
+
+    didFinishDownload = true;
+}
+
+static void didCreateDownloadDestinationCallback(WKContextRef context, WKDownloadRef download, WKStringRef path, const void *clientInfo)
+{
+    ((ActiveDownloadContext*)clientInfo)-&gt;path = path;
+}
+
+static NSString *watchPasteboardForString()
+{
+    [[NSPasteboard generalPasteboard] clearContents];
+
+    while (true) {
+        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
+        NSString *pasteboardString = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString];
+        if (pasteboardString)
+            return pasteboardString;
+    }
+}
+
+static NSImage *watchPasteboardForImage()
+{
+    [[NSPasteboard generalPasteboard] clearContents];
+
+    while (true) {
+        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
+        NSArray *pasteboardItems = [[NSPasteboard generalPasteboard] readObjectsForClasses:@[ [NSImage class] ] options:nil];
+        if (pasteboardItems &amp;&amp; pasteboardItems.count)
+            return pasteboardItems.lastObject;
+    }
+}
+
+struct JavaScriptStringCallbackContext {
+    JavaScriptStringCallbackContext()
+        : didFinish(false)
+    {
+    }
+
+    bool didFinish;
+    JSRetainPtr&lt;JSStringRef&gt; actualString;
+};
+
+struct JavaScriptBoolCallbackContext {
+    JavaScriptBoolCallbackContext()
+        : didFinish(false)
+    {
+    }
+
+    bool didFinish;
+    bool value;
+};
+
+static void javaScriptStringCallback(WKSerializedScriptValueRef resultSerializedScriptValue, WKErrorRef error, void* ctx)
+{
+    EXPECT_NOT_NULL(resultSerializedScriptValue);
+
+    JavaScriptStringCallbackContext* context = static_cast&lt;JavaScriptStringCallbackContext*&gt;(ctx);
+
+    JSGlobalContextRef scriptContext = JSGlobalContextCreate(0);
+    EXPECT_NOT_NULL(scriptContext);
+
+    JSValueRef scriptValue = WKSerializedScriptValueDeserialize(resultSerializedScriptValue, scriptContext, 0);
+    EXPECT_NOT_NULL(scriptValue);
+
+    context-&gt;actualString.adopt(JSValueToStringCopy(scriptContext, scriptValue, 0));
+    EXPECT_NOT_NULL(context-&gt;actualString.get());
+
+    context-&gt;didFinish = true;
+    
+    JSGlobalContextRelease(scriptContext);
+    
+    EXPECT_NULL(error);
+}
+
+static void javaScriptBoolCallback(WKSerializedScriptValueRef resultSerializedScriptValue, WKErrorRef error, void* ctx)
+{
+    EXPECT_NOT_NULL(resultSerializedScriptValue);
+
+    JavaScriptBoolCallbackContext* context = static_cast&lt;JavaScriptBoolCallbackContext*&gt;(ctx);
+
+    JSGlobalContextRef scriptContext = JSGlobalContextCreate(0);
+    EXPECT_NOT_NULL(scriptContext);
+
+    JSValueRef scriptValue = WKSerializedScriptValueDeserialize(resultSerializedScriptValue, scriptContext, 0);
+    EXPECT_NOT_NULL(scriptValue);
+
+    EXPECT_TRUE(JSValueIsBoolean(scriptContext, scriptValue));
+
+    context-&gt;value = JSValueToBoolean(scriptContext, scriptValue);
+    context-&gt;didFinish = true;
+    
+    JSGlobalContextRelease(scriptContext);
+    
+    EXPECT_NULL(error);
+}
+
+static std::unique_ptr&lt;char[]&gt; callJavaScriptReturningString(WKPageRef page, const char* js)
+{
+    JavaScriptStringCallbackContext context;
+    WKPageRunJavaScriptInMainFrame(page, Util::toWK(js).get(), &amp;context, javaScriptStringCallback);
+    Util::run(&amp;context.didFinish);
+
+    size_t bufferSize = JSStringGetMaximumUTF8CStringSize(context.actualString.get());
+    auto buffer = std::make_unique&lt;char[]&gt;(bufferSize);
+    JSStringGetUTF8CString(context.actualString.get(), buffer.get(), bufferSize);
+    return buffer;
+}
+
+static bool callJavaScriptReturningBool(WKPageRef page, const char* js)
+{
+    JavaScriptBoolCallbackContext context;
+    WKPageRunJavaScriptInMainFrame(page, Util::toWK(js).get(), &amp;context, javaScriptBoolCallback);
+    Util::run(&amp;context.didFinish);
+    return context.value;
+}
+
+static void watchEditableAreaForString(WKPageRef page, const char *areaName, const char *watchString)
+{
+    while (true) {
+        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
+        auto buffer = callJavaScriptReturningString(page, [[NSString stringWithFormat:@&quot;editableAreaString('%s')&quot;, areaName] UTF8String]);
+
+        if (!strcmp(buffer.get(), watchString))
+            return;
+    }
+}
+
+static void waitForVideoReady(WKPageRef page)
+{
+    while (true) {
+        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
+        if (callJavaScriptReturningBool(page, &quot;isVideoReady()&quot;))
+            return;
+    }
+}
+
+static NSString *retrieveSelection(WKPageRef page)
+{
+    auto buffer = callJavaScriptReturningString(page, &quot;stringifySelection()&quot;);
+    return [NSString stringWithUTF8String:buffer.get()];
+}
+
+static NSString *retrieveSelectionInElement(WKPageRef page, const char *areaName)
+{
+    auto buffer = callJavaScriptReturningString(page, [[NSString stringWithFormat:@&quot;stringifySelectionInElement('%s')&quot;, areaName] UTF8String]);
+    return [NSString stringWithUTF8String:buffer.get()];
+}
+
+static void performMenuItemAtIndexOfTypeAsync(NSMenu *menu, NSInteger index, int type)
+{
+    EXPECT_LT(index, menu.numberOfItems);
+    if (index &gt;= menu.numberOfItems)
+        return;
+    NSMenuItem *menuItem = [menu itemAtIndex:index];
+    EXPECT_NOT_NULL(menuItem);
+    EXPECT_EQ(type, [menuItem tag]);
+    EXPECT_TRUE([menuItem isEnabled]);
+    [menuItem.target performSelector:menuItem.action withObject:menuItem afterDelay:0];
+}
+
+static void ensureMenuItemAtIndexOfTypeIsDisabled(NSMenu *menu, NSInteger index, int type)
+{
+    EXPECT_LT(index, menu.numberOfItems);
+    if (index &gt;= menu.numberOfItems)
+        return;
+    NSMenuItem *menuItem = [menu itemAtIndex:index];
+    EXPECT_NOT_NULL(menuItem);
+    EXPECT_EQ(type, [menuItem tag]);
+    EXPECT_FALSE([menuItem isEnabled]);
+}
+
+enum class TargetType {
+    Word,
+    Phrase,
+    Address,
+    Date,
+    PhoneNumber,
+    ContentEditableWords,
+    ContentEditablePhrase,
+    TextInputWords,
+    TextInputPhrase,
+    TextAreaWords,
+    TextAreaPhrase,
+    Image,
+    ImageAsLink,
+    HTTPLink,
+    FTPLink,
+    MailtoLink,
+    JavaScriptLink,
+    PageOverlay,
+    PageWhitespace,
+    Video,
+    MSEVideo,
+};
+
+static NSPoint windowPointForTarget(TargetType target)
+{
+    NSPoint contentPoint;
+    switch (target) {
+    case TargetType::Word:
+        contentPoint = NSMakePoint(0, 0);
+        break;
+    case TargetType::Phrase:
+        contentPoint = NSMakePoint(200, 0);
+        break;
+    case TargetType::Address:
+        contentPoint = NSMakePoint(0, 50);
+        break;
+    case TargetType::Date:
+        contentPoint = NSMakePoint(200, 50);
+        break;
+    case TargetType::PhoneNumber:
+        contentPoint = NSMakePoint(400, 50);
+        break;
+    case TargetType::ContentEditableWords:
+        contentPoint = NSMakePoint(0, 150);
+        break;
+    case TargetType::ContentEditablePhrase:
+        contentPoint = NSMakePoint(0, 200);
+        break;
+    case TargetType::TextInputWords:
+        contentPoint = NSMakePoint(200, 150);
+        break;
+    case TargetType::TextInputPhrase:
+        contentPoint = NSMakePoint(200, 200);
+        break;
+    case TargetType::TextAreaWords:
+        contentPoint = NSMakePoint(400, 150);
+        break;
+    case TargetType::TextAreaPhrase:
+        contentPoint = NSMakePoint(400, 200);
+        break;
+    case TargetType::Image:
+        contentPoint = NSMakePoint(0, 250);
+        break;
+    case TargetType::ImageAsLink:
+        contentPoint = NSMakePoint(200, 250);
+        break;
+    case TargetType::HTTPLink:
+        contentPoint = NSMakePoint(0, 300);
+        break;
+    case TargetType::FTPLink:
+        contentPoint = NSMakePoint(100, 300);
+        break;
+    case TargetType::MailtoLink:
+        contentPoint = NSMakePoint(200, 300);
+        break;
+    case TargetType::JavaScriptLink:
+        contentPoint = NSMakePoint(300, 300);
+        break;
+    case TargetType::PageOverlay:
+        contentPoint = NSMakePoint(750, 100);
+        break;
+    case TargetType::PageWhitespace:
+        contentPoint = NSMakePoint(650, 0);
+        break;
+    case TargetType::Video:
+        contentPoint = NSMakePoint(0, 350);
+        break;
+    case TargetType::MSEVideo:
+        contentPoint = NSMakePoint(200, 350);
+        break;
+    }
+
+    return NSMakePoint(contentPoint.x + 8, 600 - contentPoint.y - 8);
+}
+
+// FIXME: Ideally, each of these would be able to run as its own subtest in a suite, sharing a WKView (for performance reasons),
+// but we cannot because run-api-tests explicitly runs each test in a separate process. So, we use a single test for many tests instead.
+TEST(WebKit2, ActionMenusTest)
+{
+    WKRetainPtr&lt;WKContextRef&gt; context = adoptWK(Util::createContextForInjectedBundleTest(&quot;ActionMenusTest&quot;));
+
+    WKRetainPtr&lt;WKPageGroupRef&gt; pageGroup = adoptWK(WKPageGroupCreateWithIdentifier(Util::toWK(&quot;ActionMenusTestGroup&quot;).get()));
+    WKPreferencesRef preferences = WKPageGroupGetPreferences(pageGroup.get());
+    WKPreferencesSetMediaSourceEnabled(preferences, true);
+    WKPreferencesSetFileAccessFromFileURLsAllowed(preferences, true);
+
+    PlatformWebView platformWebView(context.get(), pageGroup.get(), [ActionMenusTestWKView class]);
+    RetainPtr&lt;ActionMenusTestWKView&gt; wkView = (ActionMenusTestWKView *)platformWebView.platformView();
+
+    if (![wkView respondsToSelector:@selector(setActionMenu:)])
+        return;
+
+    WKPageLoaderClientV0 loaderClient;
+    memset(&amp;loaderClient, 0, sizeof(loaderClient));
+    loaderClient.base.version = 0;
+    loaderClient.didFinishLoadForFrame = didFinishLoadForFrameCallback;
+    WKPageSetPageLoaderClient([wkView pageRef], &amp;loaderClient.base);
+
+    ActiveDownloadContext activeDownloadContext;
+
+    WKContextDownloadClientV0 downloadClient;
+    memset(&amp;downloadClient, 0, sizeof(downloadClient));
+    downloadClient.base.version = 0;
+    downloadClient.base.clientInfo = &amp;activeDownloadContext;
+    downloadClient.didFinish = didFinishDownloadCallback;
+    downloadClient.didCreateDestination = didCreateDownloadDestinationCallback;
+    WKContextSetDownloadClient(context.get(), &amp;downloadClient.base);
+
+    WKRetainPtr&lt;WKURLRef&gt; url(AdoptWK, Util::createURLForResource(&quot;action-menu-targets&quot;, &quot;html&quot;));
+    WKPageLoadURL([wkView pageRef], url.get());
+
+    Util::run(&amp;didFinishLoad);
+
+    // Read-only text.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Word) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuReadOnlyText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;word&quot;, retrieveSelection([wkView pageRef]));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyText);
+        EXPECT_WK_STREQ(@&quot;word&quot;, watchPasteboardForString());
+    }];
+
+    // Read-only text, on a phrase.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Phrase) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuReadOnlyText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;New York&quot;, retrieveSelection([wkView pageRef]));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyText);
+        EXPECT_WK_STREQ(@&quot;New York&quot;, watchPasteboardForString());
+    }];
+
+    // Read-only text, on an address.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Address) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuDataDetectedItem, [wkView _actionMenuResult].type);
+
+        // Addresses don't get selected, because they immediately show a TextIndicator.
+        EXPECT_WK_STREQ(@&quot;&lt;no selection&gt;&quot;, retrieveSelection([wkView pageRef]));
+    }];
+
+    // Read-only text, on a date.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Date) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuDataDetectedItem, [wkView _actionMenuResult].type);
+
+        // Dates don't get selected, because they immediately show a TextIndicator.
+        EXPECT_WK_STREQ(@&quot;&lt;no selection&gt;&quot;, retrieveSelection([wkView pageRef]));
+    }];
+
+    // Read-only text, on a phone number.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::PhoneNumber) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuDataDetectedItem, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;(408) 996-1010&quot;, retrieveSelection([wkView pageRef]));
+    }];
+
+    // Copy from a contentEditable div.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ContentEditableWords) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;editable&quot;, retrieveSelection([wkView pageRef]));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyText);
+        EXPECT_WK_STREQ(@&quot;editable&quot;, watchPasteboardForString());
+    }];
+
+    // Copy a phrase from a contentEditable div.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ContentEditablePhrase) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;New York&quot;, retrieveSelection([wkView pageRef]));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyText);
+        EXPECT_WK_STREQ(@&quot;New York&quot;, watchPasteboardForString());
+    }];
+
+    // Paste on top of the text in the contentEditable div.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ContentEditableWords) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        [[NSPasteboard generalPasteboard] clearContents];
+        [[NSPasteboard generalPasteboard] setString:@&quot;pasted string&quot; forType:NSPasteboardTypeString];
+        EXPECT_WK_STREQ(@&quot;editable&quot;, retrieveSelection([wkView pageRef]));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 2, kWKContextActionItemTagPaste);
+
+        // Now check and see if our paste succeeded. It should only replace one 'editable'.
+        watchEditableAreaForString([wkView pageRef], &quot;editable1&quot;, &quot;pasted string editable editable editable&quot;);
+    }];
+
+    // Paste on top of a phrase in the contentEditable div.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ContentEditablePhrase) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        [[NSPasteboard generalPasteboard] clearContents];
+        [[NSPasteboard generalPasteboard] setString:@&quot;pasted over phrase&quot; forType:NSPasteboardTypeString];
+        EXPECT_WK_STREQ(@&quot;New York&quot;, retrieveSelection([wkView pageRef]));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 2, kWKContextActionItemTagPaste);
+
+        // Now check and see if our paste succeeded, and replaced the whole phrase.
+        watchEditableAreaForString([wkView pageRef], &quot;editable2&quot;, &quot;pasted over phrase some words&quot;);
+    }];
+
+    // Copy from an &lt;input&gt;.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextInputWords) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;editable&quot;, retrieveSelectionInElement([wkView pageRef], &quot;input1&quot;));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyText);
+        EXPECT_WK_STREQ(@&quot;editable&quot;, watchPasteboardForString());
+    }];
+
+    // Copy a phrase from an &lt;input&gt;.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextInputPhrase) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;New York&quot;, retrieveSelectionInElement([wkView pageRef], &quot;input2&quot;));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyText);
+        EXPECT_WK_STREQ(@&quot;New York&quot;, watchPasteboardForString());
+    }];
+
+    // Paste on top of the editable text in an &lt;input&gt;.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextInputWords) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        [[NSPasteboard generalPasteboard] clearContents];
+        [[NSPasteboard generalPasteboard] setString:@&quot;pasted string&quot; forType:NSPasteboardTypeString];
+        EXPECT_WK_STREQ(@&quot;editable&quot;, retrieveSelectionInElement([wkView pageRef], &quot;input1&quot;));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 2, kWKContextActionItemTagPaste);
+
+        // Now check and see if our paste succeeded. It should only replace one 'editable'.
+        watchEditableAreaForString([wkView pageRef], &quot;input1&quot;, &quot;pasted string editable editable editable&quot;);
+    }];
+
+    // Paste on top of the editable text, on a phrase in an &lt;input&gt;.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextInputPhrase) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        [[NSPasteboard generalPasteboard] clearContents];
+        [[NSPasteboard generalPasteboard] setString:@&quot;pasted over phrase&quot; forType:NSPasteboardTypeString];
+        EXPECT_WK_STREQ(@&quot;New York&quot;, retrieveSelectionInElement([wkView pageRef], &quot;input2&quot;));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 2, kWKContextActionItemTagPaste);
+
+        // Now check and see if our paste succeeded, and replaced the whole phrase.
+        watchEditableAreaForString([wkView pageRef], &quot;input2&quot;, &quot;pasted over phrase some words&quot;);
+    }];
+
+    // Copy from a &lt;textarea&gt;.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextAreaWords) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;editable&quot;, retrieveSelectionInElement([wkView pageRef], &quot;textarea1&quot;));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyText);
+        EXPECT_WK_STREQ(@&quot;editable&quot;, watchPasteboardForString());
+    }];
+
+    // Copy a phrase from a &lt;textarea&gt;.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextAreaPhrase) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;New York&quot;, retrieveSelectionInElement([wkView pageRef], &quot;textarea2&quot;));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyText);
+        EXPECT_WK_STREQ(@&quot;New York&quot;, watchPasteboardForString());
+    }];
+
+    // Paste on top of the editable text in a &lt;textarea&gt;.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextAreaWords) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        [[NSPasteboard generalPasteboard] clearContents];
+        [[NSPasteboard generalPasteboard] setString:@&quot;pasted&quot; forType:NSPasteboardTypeString];
+        EXPECT_WK_STREQ(@&quot;editable&quot;, retrieveSelectionInElement([wkView pageRef], &quot;textarea1&quot;));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 2, kWKContextActionItemTagPaste);
+
+        // Now check and see if our paste succeeded. It should only replace one 'editable'.
+        watchEditableAreaForString([wkView pageRef], &quot;textarea1&quot;, &quot;pasted editable editable editable&quot;);
+    }];
+
+    // Paste on top of the editable text, on a phrase in a &lt;textarea&gt;.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::TextAreaPhrase) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuEditableText, [wkView _actionMenuResult].type);
+        [[NSPasteboard generalPasteboard] clearContents];
+        [[NSPasteboard generalPasteboard] setString:@&quot;pasted over phrase&quot; forType:NSPasteboardTypeString];
+        EXPECT_WK_STREQ(@&quot;New York&quot;, retrieveSelectionInElement([wkView pageRef], &quot;textarea2&quot;));
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 2, kWKContextActionItemTagPaste);
+
+        // Now check and see if our paste succeeded, and replaced the whole phrase.
+        watchEditableAreaForString([wkView pageRef], &quot;textarea2&quot;, &quot;pasted over phrase some words&quot;);
+    }];
+
+    // Copy an image.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Image) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuImage, [wkView _actionMenuResult].type);
+
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyImage);
+        NSImage *image = watchPasteboardForImage();
+
+        EXPECT_EQ(215, image.size.width);
+        EXPECT_EQ(174, image.size.height);
+    }];
+
+    // Download an image.
+    activeDownloadContext.shouldCheckForImage = true;
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Image) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuImage, [wkView _actionMenuResult].type);
+
+        didFinishDownload = false;
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 2, kWKContextActionItemTagSaveImageToDownloads);
+        Util::run(&amp;didFinishDownload);
+    }];
+    activeDownloadContext.shouldCheckForImage = false;
+
+    // Images that are also links should be treated as links.
+    // http://trac.webkit.org/changeset/175701
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::ImageAsLink) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuLink, [wkView _actionMenuResult].type);
+    }];
+
+    waitForVideoReady([wkView pageRef]);
+
+    // Copy a video URL.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Video) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuVideo, [wkView _actionMenuResult].type);
+
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyVideoURL);
+        NSString *videoURL = watchPasteboardForString();
+        EXPECT_WK_STREQ(@&quot;test.mp4&quot;, [videoURL lastPathComponent]);
+    }];
+
+    // Copying a video URL for a non-downloadable video should result in copying the page URL instead.
+    // http://trac.webkit.org/changeset/176131
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::MSEVideo) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuVideo, [wkView _actionMenuResult].type);
+
+        performMenuItemAtIndexOfTypeAsync([wkView actionMenu], 0, kWKContextActionItemTagCopyVideoURL);
+        NSString *videoURL = watchPasteboardForString();
+        EXPECT_WK_STREQ(@&quot;action-menu-targets.html&quot;, [videoURL lastPathComponent]);
+
+        // Also, the download menu item should be disabled for non-downloadable video.
+        ensureMenuItemAtIndexOfTypeIsDisabled([wkView actionMenu], 2, kWKContextActionItemTagSaveVideoToDownloads);
+    }];
+
+    // HTTP link.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::HTTPLink) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuLink, [wkView _actionMenuResult].type);
+
+        // Invoking an action menu should dismiss any existing selection.
+        // http://trac.webkit.org/changeset/175753
+        EXPECT_WK_STREQ(@&quot;&lt;no selection&gt;&quot;, retrieveSelection([wkView pageRef]));
+    }];
+
+    // Mailto link.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::MailtoLink) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuMailtoLink, [wkView _actionMenuResult].type);
+
+        // Data detected links don't get a selection nor special indication, for consistency with HTTP links.
+        EXPECT_WK_STREQ(@&quot;&lt;no selection&gt;&quot;, retrieveSelection([wkView pageRef]));
+    }];
+
+    // Non-HTTP(S), non-mailto links should fall back to the text menu.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::FTPLink) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuReadOnlyText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;ftp&quot;, retrieveSelection([wkView pageRef]));
+    }];
+
+    // JavaScript links should not be executed, and should fall back to the text menu.
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::JavaScriptLink) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuReadOnlyText, [wkView _actionMenuResult].type);
+        EXPECT_WK_STREQ(@&quot;javascript&quot;, retrieveSelection([wkView pageRef]));
+        EXPECT_FALSE(callJavaScriptReturningBool([wkView pageRef], &quot;wasFailCalled()&quot;));
+    }];
+
+    // Clients should be able to customize the menu by overriding WKView's _actionMenuItemsForHitTestResult.
+    // http://trac.webkit.org/changeset/174908
+    RetainPtr&lt;NSMenuItem&gt; item = adoptNS([[NSMenuItem alloc] initWithTitle:@&quot;Some Action&quot; action:@selector(copy:) keyEquivalent:@&quot;&quot;]);
+    [wkView _setOverrideActionMenuItems:@[ item.get() ]];
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::Image) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(1, [wkView actionMenu].numberOfItems);
+        EXPECT_WK_STREQ(@&quot;Some Action&quot;, [[wkView actionMenu] itemAtIndex:0].title);
+    }];
+    [wkView _setOverrideActionMenuItems:nil];
+
+    // Clients should be able to customize the DataDetectors actions by implementing
+    // WKBundlePageOverlayClient's prepareForActionMenu callback.
+    // http://trac.webkit.org/changeset/176086
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::PageOverlay) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuDataDetectedItem, [wkView _actionMenuResult].type);
+    }];
+
+    // No menu should be built for whitespace (except in editable areas).
+    [wkView runMenuSequenceAtPoint:windowPointForTarget(TargetType::PageWhitespace) preDidCloseMenuHandler:^() {
+        EXPECT_EQ(kWKActionMenuNone, [wkView _actionMenuResult].type);
+        EXPECT_EQ(0, [wkView actionMenu].numberOfItems);
+    }];
+}
+
+} // namespace TestWebKitAPI
</ins></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWebKit2ObjCActionMenusBundlemm"></a>
<div class="addfile"><h4>Added: trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundle.mm (0 => 176959)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundle.mm                                (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundle.mm        2014-12-08 19:40:27 UTC (rev 176959)
</span><span class="lines">@@ -0,0 +1,140 @@
</span><ins>+/*
+ * Copyright (C) 2014 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.
+ */
+
+#import &quot;config.h&quot;
+#import &quot;ActionMenusBundleSPI.h&quot;
+#import &quot;InjectedBundleTest.h&quot;
+#import &quot;PlatformUtilities.h&quot;
+#import &quot;Test.h&quot;
+#import &lt;Foundation/Foundation.h&gt;
+#import &lt;WebKit/WKBundleFramePrivate.h&gt;
+#import &lt;WebKit/WKBundleHitTestResult.h&gt;
+#import &lt;WebKit/WKBundleNodeHandlePrivate.h&gt;
+#import &lt;WebKit/WKBundlePage.h&gt;
+#import &lt;WebKit/WKBundlePageOverlay.h&gt;
+#import &lt;WebKit/WKBundleRangeHandlePrivate.h&gt;
+#import &lt;WebKit/WKDictionary.h&gt;
+#import &lt;WebKit/WKNumber.h&gt;
+#import &lt;wtf/RetainPtr.h&gt;
+
+namespace TestWebKitAPI {
+
+RetainPtr&lt;DDActionContext&gt; createActionContextForPhoneNumber()
+{
+    RetainPtr&lt;CFStringRef&gt; plainText = CFSTR(&quot;(408) 996-1010&quot;);
+    RetainPtr&lt;DDScannerRef&gt; scanner = adoptCF(DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
+    RetainPtr&lt;DDScanQueryRef&gt; scanQuery = adoptCF(DDScanQueryCreateFromString(kCFAllocatorDefault, plainText.get(), CFRangeMake(0, CFStringGetLength(plainText.get()))));
+
+    if (!DDScannerScanQuery(scanner.get(), scanQuery.get()))
+        return nullptr;
+
+    RetainPtr&lt;CFArrayRef&gt; results = adoptCF(DDScannerCopyResultsWithOptions(scanner.get(), DDScannerCopyResultsOptionsNoOverlap));
+
+    CFIndex resultCount = CFArrayGetCount(results.get());
+    if (resultCount != 1)
+        return nullptr;
+
+    DDResultRef mainResult = (DDResultRef)CFArrayGetValueAtIndex(results.get(), 0);
+
+    if (!mainResult)
+        return nullptr;
+
+    RetainPtr&lt;DDActionContext&gt; actionContext = adoptNS([[getDDActionContextClass() alloc] init]);
+    [actionContext setAllResults:@[ (id)mainResult ]];
+    [actionContext setMainResult:mainResult];
+
+    return actionContext;
+}
+    
+class ActionMenuTest : public InjectedBundleTest {
+public:
+    ActionMenuTest(const std::string&amp; identifier)
+        : InjectedBundleTest(identifier)
+    {
+    }
+
+    static void prepareForActionMenu(WKBundlePageRef page, WKBundleHitTestResultRef hitTestResult, WKTypeRef* userData, const void* clientInfo)
+    {
+        WKStringRef keys[1];
+        WKTypeRef values[1];
+
+        WKRetainPtr&lt;WKStringRef&gt; hasLinkKey = adoptWK(WKStringCreateWithUTF8CString(&quot;hasLinkURL&quot;));
+        WKRetainPtr&lt;WKURLRef&gt; absoluteLinkURL = adoptWK(WKBundleHitTestResultCopyAbsoluteLinkURL(hitTestResult));
+        WKRetainPtr&lt;WKBooleanRef&gt; hasLinkValue = adoptWK(WKBooleanCreate(!!absoluteLinkURL));
+        keys[0] = hasLinkKey.get();
+        values[0] = hasLinkValue.get();
+
+        *userData = WKDictionaryCreate(keys, values, 1);
+    }
+
+    static void* actionContextForResultAtPoint(WKBundlePageOverlayRef pageOverlay, WKPoint position, WKBundleRangeHandleRef* rangeHandle, const void* clientInfo)
+    {
+        if (position.x &gt; 700) {
+            RetainPtr&lt;DDActionContext&gt; actionContext = createActionContextForPhoneNumber();
+            *rangeHandle = (WKBundleRangeHandleRef)clientInfo;
+            return (void*)actionContext.autorelease();
+        }
+        return nullptr;
+    }
+
+    static void drawRect(WKBundlePageOverlayRef pageOverlay, void* graphicsContext, WKRect dirtyRect, const void* clientInfo)
+    {
+        CGContextRef ctx = static_cast&lt;CGContextRef&gt;(graphicsContext);
+        CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);
+        CGContextFillRect(ctx, CGRectMake(700, 0, 100, 600));
+    }
+
+    virtual void didCreatePage(WKBundleRef bundle, WKBundlePageRef page)
+    {
+        WKBundlePageContextMenuClientV1 contextMenuClient;
+        memset(&amp;contextMenuClient, 0, sizeof(contextMenuClient));
+        contextMenuClient.base.version = 1;
+        contextMenuClient.prepareForActionMenu = prepareForActionMenu;
+        WKBundlePageSetContextMenuClient(page, &amp;contextMenuClient.base);
+
+        WKRetainPtr&lt;WKBundleHitTestResultRef&gt; hitTestResult = adoptWK(WKBundleFrameCreateHitTestResult(WKBundlePageGetMainFrame(page), WKPointMake(708, 8)));
+        WKRetainPtr&lt;WKBundleNodeHandleRef&gt; nodeHandle = adoptWK(WKBundleHitTestResultCopyNodeHandle(hitTestResult.get()));
+        _rangeHandle = adoptWK(WKBundleNodeHandleCopyVisibleRange(nodeHandle.get()));
+
+        WKBundlePageOverlayClientV1 pageOverlayClient;
+        memset(&amp;pageOverlayClient, 0, sizeof(pageOverlayClient));
+        pageOverlayClient.base.version = 1;
+        pageOverlayClient.base.clientInfo = _rangeHandle.get();
+
+        pageOverlayClient.drawRect = drawRect;
+        pageOverlayClient.actionContextForResultAtPoint = actionContextForResultAtPoint;
+
+        _bundlePageOverlay = adoptWK(WKBundlePageOverlayCreate(&amp;pageOverlayClient.base));
+        WKBundlePageInstallPageOverlay(page, _bundlePageOverlay.get());
+        WKBundlePageOverlaySetNeedsDisplay(_bundlePageOverlay.get(), WKRectMake(0, 0, 800, 600));
+    }
+
+    WKRetainPtr&lt;WKBundlePageOverlayRef&gt; _bundlePageOverlay;
+    WKRetainPtr&lt;WKBundleRangeHandleRef&gt; _rangeHandle;
+};
+
+static InjectedBundleTest::Register&lt;ActionMenuTest&gt; registrar(&quot;ActionMenusTest&quot;);
+
+} // namespace TestWebKitAPI
</ins></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWebKit2ObjCActionMenusBundleSPIh"></a>
<div class="addfile"><h4>Added: trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundleSPI.h (0 => 176959)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundleSPI.h                                (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKit2ObjC/ActionMenusBundleSPI.h        2014-12-08 19:40:27 UTC (rev 176959)
</span><span class="lines">@@ -0,0 +1,112 @@
</span><ins>+/*
+ * Copyright (C) 2014 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.
+ */
+
+#import &quot;config.h&quot;
+#import &lt;Foundation/Foundation.h&gt;
+#import &lt;dlfcn.h&gt;
+#import &lt;objc/runtime.h&gt;
+#import &lt;wtf/Assertions.h&gt;
+
+#define SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(framework) \
+    static void* framework##Library() \
+    { \
+        static void* frameworkLibrary = dlopen(&quot;/System/Library/PrivateFrameworks/&quot; #framework &quot;.framework/&quot; #framework, RTLD_NOW); \
+        return frameworkLibrary; \
+    }
+
+#define SOFT_LINK(framework, functionName, resultType, parameterDeclarations, parameterNames) \
+    static resultType init##functionName parameterDeclarations; \
+    static resultType (*softLink##functionName) parameterDeclarations = init##functionName; \
+    \
+    static resultType init##functionName parameterDeclarations \
+    { \
+        softLink##functionName = (resultType (*) parameterDeclarations) dlsym(framework##Library(), #functionName); \
+        ASSERT_WITH_MESSAGE(softLink##functionName, &quot;%s&quot;, dlerror()); \
+        return softLink##functionName parameterNames; \
+    }\
+    \
+    inline resultType functionName parameterDeclarations \
+    {\
+        return softLink##functionName parameterNames; \
+    }
+
+#define SOFT_LINK_CLASS(framework, className) \
+    static Class init##className(); \
+    static Class (*get##className##Class)() = init##className; \
+    static Class class##className; \
+    \
+    static Class className##Function() \
+    { \
+        return class##className; \
+    }\
+    \
+    static Class init##className() \
+    { \
+        framework##Library(); \
+        class##className = objc_getClass(#className); \
+        ASSERT(class##className); \
+        get##className##Class = className##Function; \
+        return class##className; \
+    }
+
+typedef struct __DDScanner DDScanner, *DDScannerRef;
+typedef struct __DDScanQuery *DDScanQueryRef;
+typedef struct __DDResult *DDResultRef;
+
+typedef enum {
+    DDScannerTypeStandard = 0,
+} DDScannerType;
+
+enum {
+    DDScannerOptionStopAtFirstMatch = 1,
+};
+typedef CFIndex DDScannerOptions;
+
+enum {
+    DDScannerCopyResultsOptionsNone = 0,
+    DDScannerCopyResultsOptionsNoOverlap = 1 &lt;&lt; 0,
+};
+typedef CFIndex DDScannerCopyResultsOptions;
+
+SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(DataDetectors)
+SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(DataDetectorsCore)
+
+extern &quot;C&quot; {
+
+SOFT_LINK(DataDetectorsCore, DDScannerCreate, DDScannerRef, (DDScannerType type, DDScannerOptions options, CFErrorRef* errorRef), (type, options, errorRef))
+SOFT_LINK(DataDetectorsCore, DDScanQueryCreateFromString, DDScanQueryRef, (CFAllocatorRef allocator, CFStringRef string, CFRange range), (allocator, string, range))
+SOFT_LINK(DataDetectorsCore, DDScannerScanQuery, DDScanQueryRef, (DDScannerRef scanner, DDScanQueryRef query), (scanner, query))
+SOFT_LINK(DataDetectorsCore, DDScannerCopyResultsWithOptions, CFArrayRef, (DDScannerRef scanner, DDScannerCopyResultsOptions options), (scanner, options))
+
+}
+
+SOFT_LINK_CLASS(DataDetectors, DDActionContext)
+
+@interface DDActionContext : NSObject &lt;NSCopying, NSSecureCoding&gt;
+
+@property (retain) NSArray *allResults;
+@property (retain) __attribute__((NSObject)) DDResultRef mainResult;
+
+@end
</ins></span></pre></div>
<a id="trunkToolsTestWebKitAPImacPlatformWebViewMacmm"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm (176958 => 176959)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm        2014-12-08 19:30:19 UTC (rev 176958)
+++ trunk/Tools/TestWebKitAPI/mac/PlatformWebViewMac.mm        2014-12-08 19:40:27 UTC (rev 176959)
</span><span class="lines">@@ -46,9 +46,14 @@
</span><span class="cx"> namespace TestWebKitAPI {
</span><span class="cx"> 
</span><span class="cx"> PlatformWebView::PlatformWebView(WKContextRef contextRef, WKPageGroupRef pageGroupRef)
</span><ins>+    : PlatformWebView(contextRef, pageGroupRef, [WKView class])
</ins><span class="cx"> {
</span><ins>+}
+
+PlatformWebView::PlatformWebView(WKPageRef relatedPage)
+{
</ins><span class="cx">     NSRect rect = NSMakeRect(0, 0, 800, 600);
</span><del>-    m_view = [[WKView alloc] initWithFrame:rect contextRef:contextRef pageGroupRef:pageGroupRef];
</del><ins>+    m_view = [[WKView alloc] initWithFrame:rect contextRef:WKPageGetContext(relatedPage) pageGroupRef:WKPageGetPageGroup(relatedPage) relatedToPage:relatedPage];
</ins><span class="cx">     [m_view setWindowOcclusionDetectionEnabled:NO];
</span><span class="cx"> 
</span><span class="cx">     NSRect windowRect = NSOffsetRect(rect, -10000, [(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
</span><span class="lines">@@ -60,10 +65,10 @@
</span><span class="cx">     [m_window setReleasedWhenClosed:NO];
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-PlatformWebView::PlatformWebView(WKPageRef relatedPage)
</del><ins>+PlatformWebView::PlatformWebView(WKContextRef contextRef, WKPageGroupRef pageGroupRef, Class wkViewSubclass)
</ins><span class="cx"> {
</span><span class="cx">     NSRect rect = NSMakeRect(0, 0, 800, 600);
</span><del>-    m_view = [[WKView alloc] initWithFrame:rect contextRef:WKPageGetContext(relatedPage) pageGroupRef:WKPageGetPageGroup(relatedPage) relatedToPage:relatedPage];
</del><ins>+    m_view = [[wkViewSubclass alloc] initWithFrame:rect contextRef:contextRef pageGroupRef:pageGroupRef];
</ins><span class="cx">     [m_view setWindowOcclusionDetectionEnabled:NO];
</span><span class="cx"> 
</span><span class="cx">     NSRect windowRect = NSOffsetRect(rect, -10000, [(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
</span></span></pre>
</div>
</div>

</body>
</html>