[Cocoa] First pass at implementing alternative presentation button element
authordbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 28 Nov 2017 18:31:31 +0000 (18:31 +0000)
committerdbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 28 Nov 2017 18:31:31 +0000 (18:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179785
Part of <rdar://problem/34917108>

Reviewed by Brent Fulgham.

Source/WebCore:

Implement support for substituting a button for one or more elements in a page.
This is a first pass. We will refine the logic and the API/SPI in subsequent
commits.

Tests: accessibility/alternative-presentation-button-input-type.html
       accessibility/alternative-presentation-button.html
       fast/forms/alternative-presentation-button/replace-and-remove.html
       fast/forms/alternative-presentation-button/replacement.html

* English.lproj/Localizable.strings: Add placeholder strings for localization.
* SourcesCocoa.txt: Add some files.
* WebCore.xcodeproj/project.pbxproj: Ditto.
* dom/Element.h:
* editing/Editor.cpp:
(WebCore::Editor::clear): Clear out all substitutions. This is called whenever
we are navigating between pages.
(WebCore::Editor::substituteWithAlternativePresentationButton): Added.
(WebCore::Editor::removeAlternativePresentationButton): Added.
(WebCore::Editor::didInsertAlternativePresentationButtonElement): Added.
(WebCore::Editor::didRemoveAlternativePresentationButtonElement): Added.
* editing/Editor.h:
* editing/cocoa/AlternativePresentationButtonSubstitution.cpp: Added.
(WebCore::AlternativePresentationButtonSubstitution::AlternativePresentationButtonSubstitution):
(WebCore::AlternativePresentationButtonSubstitution::initializeSavedDisplayStyles):
(WebCore::AlternativePresentationButtonSubstitution::apply):
(WebCore::AlternativePresentationButtonSubstitution::unapply):
* editing/cocoa/AlternativePresentationButtonSubstitution.h:
* html/HTMLInputElement.cpp:
(WebCore::HTMLInputElement::alternativePresentationButtonElement const): Added.
(WebCore::HTMLInputElement::setTypeWithoutUpdatingAttribute): Added.
(WebCore::HTMLInputElement::createInputType): Extracted the logic to create the InputType from
HTMLInputElement::updateType() to here and added logic to create the input type for the
alternative presentation button. This input type is not web exposed.
(WebCore::HTMLInputElement::updateType): Modified to take the name of the InputType object to
create as an argument and pass it through to HTMLInputElement::createInputType() to actually
create it. Reordered the logic for destroying the shadow tree of the old InputType, deallocating
the old InputType, and assigning the new InputType such that we assign the new InputType,
destroy the shadow tree of the old InputType, and deallocate the old InputType. This ordering
allows AlternativePresentationButtonSubstitution::substitute() to avoid restoring the input
type saved before the substitution when the input type is changed by the page as opposed to
by SPI.
(WebCore::HTMLInputElement::parseAttribute): Pass the parsed type.
(WebCore::HTMLInputElement::willAttachRenderers): Ditto.
* html/HTMLInputElement.h: Change visibility of removeShadowRoot() from private to public so
that it can be called from AlternativePresentationButtonSubstitution.
* html/InputType.h:
(WebCore::InputType::alternativePresentationButtonElement const): Added.
* html/InputTypeNames.cpp:
(WebCore::InputTypeNames::alternativePresentationButton): Added.
* html/InputTypeNames.h:
* html/shadow/cocoa/AlternativePresentationButtonElement.cpp: Added.
(WebCore::AlternativePresentationButtonElement::create):
(WebCore::AlternativePresentationButtonElement::AlternativePresentationButtonElement):
(WebCore::AlternativePresentationButtonElement::insertedIntoAncestor):
(WebCore::AlternativePresentationButtonElement::removedFromAncestor):
(WebCore::AlternativePresentationButtonElement::didFinishInsertingNode):
(WebCore::AlternativePresentationButtonElement::defaultEventHandler):
* html/shadow/cocoa/AlternativePresentationButtonElement.h:
* html/shadow/cocoa/AlternativePresentationButtonInputType.cpp: Added.
(WebCore::AlternativePresentationButtonInputType::AlternativePresentationButtonInputType):
(WebCore::AlternativePresentationButtonInputType::formControlType const):
(WebCore::AlternativePresentationButtonInputType::appendFormData const):
(WebCore::AlternativePresentationButtonInputType::supportsValidation const):
(WebCore::AlternativePresentationButtonInputType::isTextButton const):
(WebCore::AlternativePresentationButtonInputType::alternativePresentationButtonElement const):
(WebCore::AlternativePresentationButtonInputType::createShadowSubtree):
(WebCore::AlternativePresentationButtonInputType::destroyShadowSubtree):
* html/shadow/cocoa/AlternativePresentationButtonInputType.h:
* page/ChromeClient.h:
* platform/LocalizedStrings.cpp:
(WebCore::AXAlternativePresentationButtonLabel):
(WebCore::alternativePresentationButtonTitle):
(WebCore::alternativePresentationButtonSubtitle):
* platform/LocalizedStrings.h:
* testing/Internals.cpp:
(WebCore::Internals::substituteWithAlternativePresentationButton): Added.
(WebCore::Internals::removeAlternativePresentationButton): Added.
* testing/Internals.h:
* testing/Internals.idl:

Source/WebKit:

Expose SPI to substitute the alternative presentation button for one or more elements
and remove the alternative presentation button. Add a private delegate callback when
the alternative presentation button is clicked.

* UIProcess/API/APIUIClient.h:
(API::UIClient::didClickAlternativePresentationButton): Added.
* UIProcess/API/C/WKPageUIClient.h:
* UIProcess/API/Cocoa/WKUIDelegatePrivate.h:
* UIProcess/Cocoa/UIDelegate.h:
* UIProcess/Cocoa/UIDelegate.mm:
(WebKit::UIDelegate::setDelegate): Wired up delegate callback.
(WebKit::UIDelegate::UIClient::didClickAlternativePresentationButton): Added.
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::handleAlternativePresentationButtonClick): Added.
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* WebProcess/InjectedBundle/API/APIInjectedBundlePageUIClient.h:
(API::InjectedBundle::PageUIClient::didClickAlternativePresentationButton): Added.
* WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFrame.h:
* WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFrame.mm:
(-[WKWebProcessPlugInFrame substituteElements:withAlternativePresentationButtonWithIdentifier:]): Added.
(-[WKWebProcessPlugInFrame removeAlternativePresentationButton:]): Added.
* WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFramePrivate.h:
* WebProcess/InjectedBundle/API/c/WKBundleFrame.cpp:
(WKBundleSubstituteWithAlternativePresentationButton): Added.
(WKBundleRemoveAlternativePresentationButton): Added.
* WebProcess/InjectedBundle/API/c/WKBundleFramePrivate.h:
* WebProcess/InjectedBundle/API/c/WKBundlePageUIClient.h:
* WebProcess/InjectedBundle/InjectedBundlePageUIClient.cpp:
(WebKit::InjectedBundlePageUIClient::didClickAlternativePresentationButton): Added.
* WebProcess/InjectedBundle/InjectedBundlePageUIClient.h:
* WebProcess/WebCoreSupport/WebChromeClient.cpp:
(WebKit::WebChromeClient::handleAlternativePresentationButtonClick): Added.
* WebProcess/WebCoreSupport/WebChromeClient.h:

Tools:

Add a test that substitutes the alternative presentation button for an element in
the page and clicks it.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: Add test.
* TestWebKitAPI/Tests/WebKitCocoa/ClickAlternativePresentationButton.mm: Added.
(didClickAlternativePresentationButton):
(-[ClickAlternativePresentationButton webProcessPlugIn:didCreateBrowserContextController:]):
* TestWebKitAPI/Tests/WebKitCocoa/UIDelegate.mm:
(TEST):
(-[AlternativePresentationButtonDelegate _webView:didClickAlternativePresentationButtonWithUserInfo:]):
(-[AlternativePresentationButtonDelegate webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:]):

LayoutTests:

Adds tests to ensure that we can apply and remove the substitution of one or more
elements with the alternative presentation button.

Also added some accessibility tests to ensure that the alternative presentation button
can be seen and hit tested by the accessibility machinery. When the alternative presentation
button is substituted for an <input> it masquerades as a text button and when it is
substituted for an arbitrary HTML element it masquerades as the original element. As a
result the accessibility machinery shows an empty role description in the former case
because it does find the ARIA label for the button and the accessibility element hierarchy
may be incorrect in the latter case. We will fix these issues in a subsequent commit(s).

* TestExpectations: Skip the test on all platforms. We will selectively enable
tests on Cocoa platforms (below).
* accessibility/alternative-presentation-button-expected.txt: Added.
* accessibility/alternative-presentation-button-input-type-expected.txt: Added.
* accessibility/alternative-presentation-button-input-type.html: Added.
* accessibility/alternative-presentation-button.html: Added.
* fast/forms/alternative-presentation-button/replace-and-remove-expected.html: Added.
* fast/forms/alternative-presentation-button/replace-and-remove.html: Added.
* fast/forms/alternative-presentation-button/replacement-expected.txt: Added.
* fast/forms/alternative-presentation-button/replacement.html: Added.
* platform/ios/TestExpectations: Mark tests as PASS so that we run them.
* platform/ios/fast/forms/alternative-presentation-button/replacement-expected.txt: Added.
* platform/mac/TestExpectations: Mark tests as PASS so that we run them.

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

61 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/accessibility/alternative-presentation-button-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/alternative-presentation-button-input-type-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/alternative-presentation-button-input-type.html [new file with mode: 0644]
LayoutTests/accessibility/alternative-presentation-button.html [new file with mode: 0644]
LayoutTests/fast/forms/alternative-presentation-button/replace-and-remove-expected.html [new file with mode: 0644]
LayoutTests/fast/forms/alternative-presentation-button/replace-and-remove.html [new file with mode: 0644]
LayoutTests/fast/forms/alternative-presentation-button/replacement-expected.txt [new file with mode: 0644]
LayoutTests/fast/forms/alternative-presentation-button/replacement.html [new file with mode: 0644]
LayoutTests/platform/ios/TestExpectations
LayoutTests/platform/ios/fast/forms/alternative-presentation-button/replacement-expected.txt [new file with mode: 0644]
LayoutTests/platform/mac/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/English.lproj/Localizable.strings
Source/WebCore/SourcesCocoa.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/Element.h
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/Editor.h
Source/WebCore/editing/cocoa/AlternativePresentationButtonSubstitution.cpp [new file with mode: 0644]
Source/WebCore/editing/cocoa/AlternativePresentationButtonSubstitution.h [new file with mode: 0644]
Source/WebCore/html/HTMLInputElement.cpp
Source/WebCore/html/HTMLInputElement.h
Source/WebCore/html/InputType.h
Source/WebCore/html/InputTypeNames.cpp
Source/WebCore/html/InputTypeNames.h
Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonElement.cpp [new file with mode: 0644]
Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonElement.h [new file with mode: 0644]
Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonInputType.cpp [new file with mode: 0644]
Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonInputType.h [new file with mode: 0644]
Source/WebCore/page/ChromeClient.h
Source/WebCore/platform/LocalizedStrings.cpp
Source/WebCore/platform/LocalizedStrings.h
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/APIUIClient.h
Source/WebKit/UIProcess/API/C/WKPageUIClient.h
Source/WebKit/UIProcess/API/Cocoa/WKUIDelegatePrivate.h
Source/WebKit/UIProcess/Cocoa/UIDelegate.h
Source/WebKit/UIProcess/Cocoa/UIDelegate.mm
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebPageProxy.messages.in
Source/WebKit/WebProcess/InjectedBundle/API/APIInjectedBundlePageUIClient.h
Source/WebKit/WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFrame.h
Source/WebKit/WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFrame.mm
Source/WebKit/WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFramePrivate.h
Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundleFrame.cpp
Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundleFramePrivate.h
Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePageUIClient.h
Source/WebKit/WebProcess/InjectedBundle/InjectedBundlePageUIClient.cpp
Source/WebKit/WebProcess/InjectedBundle/InjectedBundlePageUIClient.h
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/ClickAlternativePresentationButton.mm [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitCocoa/UIDelegate.mm

index 023326a..946c1b1 100644 (file)
@@ -1,3 +1,36 @@
+2017-11-28  Daniel Bates  <dabates@apple.com>
+
+        [Cocoa] First pass at implementing alternative presentation button element
+        https://bugs.webkit.org/show_bug.cgi?id=179785
+        Part of <rdar://problem/34917108>
+
+        Reviewed by Brent Fulgham.
+
+        Adds tests to ensure that we can apply and remove the substitution of one or more
+        elements with the alternative presentation button.
+
+        Also added some accessibility tests to ensure that the alternative presentation button
+        can be seen and hit tested by the accessibility machinery. When the alternative presentation
+        button is substituted for an <input> it masquerades as a text button and when it is
+        substituted for an arbitrary HTML element it masquerades as the original element. As a
+        result the accessibility machinery shows an empty role description in the former case
+        because it does find the ARIA label for the button and the accessibility element hierarchy
+        may be incorrect in the latter case. We will fix these issues in a subsequent commit(s).
+
+        * TestExpectations: Skip the test on all platforms. We will selectively enable
+        tests on Cocoa platforms (below).
+        * accessibility/alternative-presentation-button-expected.txt: Added.
+        * accessibility/alternative-presentation-button-input-type-expected.txt: Added.
+        * accessibility/alternative-presentation-button-input-type.html: Added.
+        * accessibility/alternative-presentation-button.html: Added.
+        * fast/forms/alternative-presentation-button/replace-and-remove-expected.html: Added.
+        * fast/forms/alternative-presentation-button/replace-and-remove.html: Added.
+        * fast/forms/alternative-presentation-button/replacement-expected.txt: Added.
+        * fast/forms/alternative-presentation-button/replacement.html: Added.
+        * platform/ios/TestExpectations: Mark tests as PASS so that we run them.
+        * platform/ios/fast/forms/alternative-presentation-button/replacement-expected.txt: Added.
+        * platform/mac/TestExpectations: Mark tests as PASS so that we run them.
+
 2017-11-28  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r225209.
index c56fa1d..aea8b06 100644 (file)
@@ -22,6 +22,7 @@ fast/visual-viewport/tiled-drawing [ Skip ]
 swipe [ Skip ]
 fast/zooming/ios [ Skip ]
 fast/forms/ios [ Skip ]
+fast/forms/alternative-presentation-button [ Skip ]
 fast/viewport/ios [ Skip ]
 fast/visual-viewport/ios/ [ Skip ]
 fast/events/ios [ Skip ]
diff --git a/LayoutTests/accessibility/alternative-presentation-button-expected.txt b/LayoutTests/accessibility/alternative-presentation-button-expected.txt
new file mode 100644 (file)
index 0000000..059cd1a
--- /dev/null
@@ -0,0 +1,14 @@
+This tests that the alternative presentation button is an accessible element and can be hit test.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Role: AXRole: AXButton
+Description: AXDescription: alternative presentation button
+
+Hit test alternative presentation button:
+PASS axAlternativePresentationButton.elementAtPoint(x, y).isEqual(axAlternativePresentationButton) is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/alternative-presentation-button-input-type-expected.txt b/LayoutTests/accessibility/alternative-presentation-button-input-type-expected.txt
new file mode 100644 (file)
index 0000000..4a98acd
--- /dev/null
@@ -0,0 +1,21 @@
+
+This tests that the alternative presentation button is an accessible element and can be hit test.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Before substitution:
+Role: AXRole: AXTextField
+Description: AXDescription: 
+
+After substitution:
+Role: AXRole: AXButton
+Description: AXDescription: 
+
+Hit test alternative presentation button:
+PASS axTextField.elementAtPoint(x, y).isEqual(axTextField) is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/alternative-presentation-button-input-type.html b/LayoutTests/accessibility/alternative-presentation-button-input-type.html
new file mode 100644 (file)
index 0000000..746f389
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+<input type="text" id="textfield">
+<p id="description"></p>
+<div id="console"></div>
+<script>
+description("This tests that the alternative presentation button is an accessible element and can be hit test.");
+
+if (window.accessibilityController) {
+    debug("<br>Before substitution:");
+    var axTextField = accessibilityController.accessibleElementById("textfield");
+    dumpRoleAndDescription(axTextField);
+
+    debug("<br>After substitution:");
+    window.internals.substituteWithAlternativePresentationButton([document.getElementById("textfield")], 1);
+    axTextField = accessibilityController.accessibleElementById("textfield");
+    dumpRoleAndDescription(axTextField);
+
+    debug("<br>Hit test alternative presentation button:");
+    var x = axTextField.clickPointX;
+    var y = axTextField.clickPointY;
+    shouldBeTrue("axTextField.elementAtPoint(x, y).isEqual(axTextField)");
+}
+
+function dumpRoleAndDescription(accessibleElement)
+{
+    debug("Role: " + accessibleElement.role);
+    debug("Description: " + accessibleElement.description);
+}
+</script>
+</body>
+</html>
diff --git a/LayoutTests/accessibility/alternative-presentation-button.html b/LayoutTests/accessibility/alternative-presentation-button.html
new file mode 100644 (file)
index 0000000..8a095bd
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../resources/js-test.js"></script>
+</head>
+<body>
+<div id="test">
+    <h2>First name</h2>
+    <input type="text" id="first-name">
+    <h2>Last name</h2>
+    <input type="text" id="last-name">
+</div>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+description("This tests that the alternative presentation button is an accessible element and can be hit test.");
+
+if (window.accessibilityController) {
+    window.internals.substituteWithAlternativePresentationButton(document.getElementById("test").children, 1);
+    var axAlternativePresentationButton = accessibilityController.focusedElement.childAtIndex(0).childAtIndex(0);
+    dumpRoleAndDescription(axAlternativePresentationButton);
+
+    debug("<br>Hit test alternative presentation button:");
+    var x = axAlternativePresentationButton.clickPointX;
+    var y = axAlternativePresentationButton.clickPointY;
+    shouldBeTrue("axAlternativePresentationButton.elementAtPoint(x, y).isEqual(axAlternativePresentationButton)");
+}
+
+function dumpRoleAndDescription(accessibleElement)
+{
+    debug("Role: " + accessibleElement.role);
+    debug("Description: " + accessibleElement.description);
+}
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/forms/alternative-presentation-button/replace-and-remove-expected.html b/LayoutTests/fast/forms/alternative-presentation-button/replace-and-remove-expected.html
new file mode 100644 (file)
index 0000000..84dc48e
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="test-container">
+    <div class="test">
+        <input type="text">
+    </div>
+    <div class="test">
+        <label for="first-name">First name</label>
+        <input type="text" id="first-name">
+        <label for="last-name">Last name</label>
+        <input type="text" id="last-name">
+    </div>
+    <div class="test">
+        <p>First name</p>
+        <input type="text" id="first-name">
+        <p>Last name</p>
+        <input type="text" id="last-name">
+    </div>
+    <div class="test">
+        <label>Name <input type="text"></label>
+        <p>Some more text.</p>
+    </div>
+    <div>
+        <table>
+                <tr class="test">
+                    <td>First name</td>
+                    <td><input type="text"></td>
+                    <td>Last name</td>
+                    <td><input type="text"></td>
+                </tr>
+        </table>
+    </div>
+    <div>
+        <table>
+                <tr class="test">
+                    <td>First name <input type="text"></td>
+                    <td>Last name <input type="text"></td>
+                </tr>
+        </table>
+    </div>
+    <p>Name</p>
+</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/forms/alternative-presentation-button/replace-and-remove.html b/LayoutTests/fast/forms/alternative-presentation-button/replace-and-remove.html
new file mode 100644 (file)
index 0000000..3d41b42
--- /dev/null
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="test-container">
+    <div class="test">
+        <input type="text">
+    </div>
+    <div class="test">
+        <label for="first-name">First name</label>
+        <input type="text" id="first-name">
+        <label for="last-name">Last name</label>
+        <input type="text" id="last-name">
+    </div>
+    <div class="test">
+        <p>First name</p>
+        <input type="text" id="first-name">
+        <p>Last name</p>
+        <input type="text" id="last-name">
+    </div>
+    <div class="test">
+        <label>Name <input type="text"></label>
+        <p>Some more text.</p>
+    </div>
+    <div>
+        <table>
+                <tr class="test">
+                    <td>First name</td>
+                    <td><input type="text"></td>
+                    <td>Last name</td>
+                    <td><input type="text"></td>
+                </tr>
+        </table>
+    </div>
+    <div>
+        <table>
+                <tr class="test">
+                    <td>First name <input type="text"></td>
+                    <td>Last name <input type="text"></td>
+                </tr>
+        </table>
+    </div>
+</div>
+<script>
+var id = 0;
+var testContainer = document.getElementById("test-container");
+var tests = testContainer.getElementsByClassName("test");
+for (let test of tests) {
+    if (window.internals)
+        internals.substituteWithAlternativePresentationButton(test.children, ++id);
+}
+
+// Programmatically add element
+var pElement = document.createElement("p");
+pElement.textContent = "Name";
+testContainer.appendChild(pElement);
+if (window.internals)
+    internals.substituteWithAlternativePresentationButton([pElement], ++id);
+
+for (let i = id; i; ) {
+    if (window.internals)
+        internals.removeAlternativePresentationButton(i);
+    --i;
+}
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/forms/alternative-presentation-button/replacement-expected.txt b/LayoutTests/fast/forms/alternative-presentation-button/replacement-expected.txt
new file mode 100644 (file)
index 0000000..f3bf422
--- /dev/null
@@ -0,0 +1,118 @@
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x336
+  RenderBlock {HTML} at (0,0) size 800x336
+    RenderBody {BODY} at (8,8) size 784x312
+      RenderBlock {DIV} at (0,0) size 784x312
+        RenderBlock {DIV} at (0,0) size 784x36
+          RenderButton {INPUT} at (2,2) size 211x32 [bgcolor=#FFFFFF] [border: (2px inset #000000)]
+            RenderBlock (anonymous) at (3,3) size 205x26
+              RenderBlock {DIV} at (0,0) size 205x26
+                RenderBlock {DIV} at (0,0) size 205x0
+                  RenderBlock {DIV} at (0,0) size 205x0
+                RenderBlock {DIV} at (0,0) size 205x26
+                  RenderBlock (anonymous) at (0,0) size 205x13
+                    RenderInline {SPAN} at (0,0) size 185x13
+                      RenderText {#text} at (0,0) size 185x13
+                        text run at (0,0) width 185: "alternative presentation button title"
+                  RenderBlock {DIV} at (0,13) size 205x13
+                    RenderText {#text} at (0,0) size 205x13
+                      text run at (0,0) width 205: "alternative presentation button subtitle"
+          RenderText {#text} at (0,0) size 0x0
+        RenderBlock {DIV} at (0,36) size 784x36
+          RenderBlock (anonymous) at (0,0) size 784x0
+            RenderInline {LABEL} at (0,0) size 0x0
+          RenderBlock (anonymous) at (0,0) size 784x36
+            RenderBlock {DIV} at (0,0) size 784x36
+              RenderBlock {DIV} at (0,0) size 784x0
+                RenderBlock {DIV} at (0,0) size 784x0
+              RenderBlock {DIV} at (0,0) size 784x36
+                RenderBlock (anonymous) at (0,0) size 784x18
+                  RenderInline {SPAN} at (0,0) size 224x18
+                    RenderText {#text} at (0,0) size 224x18
+                      text run at (0,0) width 224: "alternative presentation button title"
+                RenderBlock {DIV} at (0,18) size 784x18
+                  RenderText {#text} at (0,0) size 246x18
+                    text run at (0,0) width 246: "alternative presentation button subtitle"
+          RenderBlock (anonymous) at (0,36) size 784x0
+            RenderInline {LABEL} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+        RenderBlock {DIV} at (0,88) size 784x36
+          RenderBlock {P} at (0,0) size 784x36
+            RenderBlock {DIV} at (0,0) size 784x36
+              RenderBlock {DIV} at (0,0) size 784x0
+                RenderBlock {DIV} at (0,0) size 784x0
+              RenderBlock {DIV} at (0,0) size 784x36
+                RenderBlock (anonymous) at (0,0) size 784x18
+                  RenderInline {SPAN} at (0,0) size 224x18
+                    RenderText {#text} at (0,0) size 224x18
+                      text run at (0,0) width 224: "alternative presentation button title"
+                RenderBlock {DIV} at (0,18) size 784x18
+                  RenderText {#text} at (0,0) size 246x18
+                    text run at (0,0) width 246: "alternative presentation button subtitle"
+        RenderBlock {DIV} at (0,140) size 784x36
+          RenderBlock (anonymous) at (0,0) size 784x0
+            RenderInline {LABEL} at (0,0) size 0x0
+          RenderBlock (anonymous) at (0,0) size 784x36
+            RenderBlock {DIV} at (0,0) size 784x36
+              RenderBlock {DIV} at (0,0) size 784x0
+                RenderBlock {DIV} at (0,0) size 784x0
+              RenderBlock {DIV} at (0,0) size 784x36
+                RenderBlock (anonymous) at (0,0) size 784x18
+                  RenderInline {SPAN} at (0,0) size 224x18
+                    RenderText {#text} at (0,0) size 224x18
+                      text run at (0,0) width 224: "alternative presentation button title"
+                RenderBlock {DIV} at (0,18) size 784x18
+                  RenderText {#text} at (0,0) size 246x18
+                    text run at (0,0) width 246: "alternative presentation button subtitle"
+          RenderBlock (anonymous) at (0,36) size 784x0
+            RenderInline {LABEL} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+        RenderBlock {DIV} at (0,176) size 784x42
+          RenderTable {TABLE} at (0,0) size 252x42
+            RenderTableSection {TBODY} at (0,0) size 252x42
+              RenderTableRow {TR} at (0,2) size 252x38
+                RenderTableCell {TD} at (2,2) size 248x38 [r=0 c=0 rs=1 cs=1]
+                  RenderBlock {DIV} at (1,1) size 246x36
+                    RenderBlock {DIV} at (0,0) size 246x0
+                      RenderBlock {DIV} at (0,0) size 246x0
+                    RenderBlock {DIV} at (0,0) size 246x36
+                      RenderBlock (anonymous) at (0,0) size 246x18
+                        RenderInline {SPAN} at (0,0) size 224x18
+                          RenderText {#text} at (0,0) size 224x18
+                            text run at (0,0) width 224: "alternative presentation button title"
+                      RenderBlock {DIV} at (0,18) size 246x18
+                        RenderText {#text} at (0,0) size 246x18
+                          text run at (0,0) width 246: "alternative presentation button subtitle"
+        RenderBlock {DIV} at (0,218) size 784x42
+          RenderTable {TABLE} at (0,0) size 252x42
+            RenderTableSection {TBODY} at (0,0) size 252x42
+              RenderTableRow {TR} at (0,2) size 252x38
+                RenderTableCell {TD} at (2,2) size 248x38 [r=0 c=0 rs=1 cs=1]
+                  RenderBlock {DIV} at (1,1) size 246x36
+                    RenderBlock {DIV} at (0,0) size 246x0
+                      RenderBlock {DIV} at (0,0) size 246x0
+                    RenderBlock {DIV} at (0,0) size 246x36
+                      RenderBlock (anonymous) at (0,0) size 246x18
+                        RenderInline {SPAN} at (0,0) size 224x18
+                          RenderText {#text} at (0,0) size 224x18
+                            text run at (0,0) width 224: "alternative presentation button title"
+                      RenderBlock {DIV} at (0,18) size 246x18
+                        RenderText {#text} at (0,0) size 246x18
+                          text run at (0,0) width 246: "alternative presentation button subtitle"
+        RenderBlock {P} at (0,276) size 784x36
+          RenderBlock {DIV} at (0,0) size 784x36
+            RenderBlock {DIV} at (0,0) size 784x0
+              RenderBlock {DIV} at (0,0) size 784x0
+            RenderBlock {DIV} at (0,0) size 784x36
+              RenderBlock (anonymous) at (0,0) size 784x18
+                RenderInline {SPAN} at (0,0) size 224x18
+                  RenderText {#text} at (0,0) size 224x18
+                    text run at (0,0) width 224: "alternative presentation button title"
+              RenderBlock {DIV} at (0,18) size 784x18
+                RenderText {#text} at (0,0) size 246x18
+                  text run at (0,0) width 246: "alternative presentation button subtitle"
diff --git a/LayoutTests/fast/forms/alternative-presentation-button/replacement.html b/LayoutTests/fast/forms/alternative-presentation-button/replacement.html
new file mode 100644 (file)
index 0000000..ed78e5c
--- /dev/null
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="test-container">
+    <div class="test">
+        <input type="text">
+    </div>
+    <div class="test">
+        <label for="first-name">First name</label>
+        <input type="text" id="first-name">
+        <label for="last-name">Last name</label>
+        <input type="text" id="last-name">
+    </div>
+    <div class="test">
+        <p>First name</p>
+        <input type="text" id="first-name">
+        <p>Last name</p>
+        <input type="text" id="last-name">
+    </div>
+    <div class="test">
+        <label>Name <input type="text"></label>
+        <p>Some more text.</p>
+    </div>
+    <div>
+        <table>
+                <tr class="test">
+                    <td>First name</td>
+                    <td><input type="text"></td>
+                    <td>Last name</td>
+                    <td><input type="text"></td>
+                </tr>
+        </table>
+    </div>
+    <div>
+        <table>
+                <tr class="test">
+                    <td>First name <input type="text"></td>
+                    <td>Last name <input type="text"></td>
+                </tr>
+        </table>
+    </div>
+</div>
+<script>
+var id = 0;
+var testContainer = document.getElementById("test-container");
+var tests = testContainer.getElementsByClassName("test");
+for (let test of tests) {
+    if (window.internals)
+        internals.substituteWithAlternativePresentationButton(test.children, ++id);
+}
+
+// Programmatically add element
+var pElement = testContainer.appendChild(document.createElement("p"));
+if (window.internals)
+    internals.substituteWithAlternativePresentationButton([pElement], ++id);
+</script>
+</body>
+</html>
index b3fad48..701885f 100644 (file)
@@ -13,6 +13,7 @@ media/controls/ipad [ Pass ]
 media/ios [ Pass ]
 quicklook [ Pass ]
 
+fast/forms/alternative-presentation-button [ Pass ]
 fast/text-autosizing/ios [ Pass ]
 fast/zooming/ios [ Pass ]
 http/tests/preload/viewport [ Pass ]
diff --git a/LayoutTests/platform/ios/fast/forms/alternative-presentation-button/replacement-expected.txt b/LayoutTests/platform/ios/fast/forms/alternative-presentation-button/replacement-expected.txt
new file mode 100644 (file)
index 0000000..66f441f
--- /dev/null
@@ -0,0 +1,118 @@
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x363
+  RenderBlock {HTML} at (0,0) size 800x363
+    RenderBody {BODY} at (8,8) size 784x339
+      RenderBlock {DIV} at (0,0) size 784x339
+        RenderBlock {DIV} at (0,0) size 784x39
+          RenderButton {INPUT} at (2,2) size 199x36 [bgcolor=#FFFFFF] [border: (1px solid #4C4C4C)]
+            RenderBlock (anonymous) at (6,3) size 187x29
+              RenderBlock {DIV} at (0,0) size 186x28
+                RenderBlock {DIV} at (0,0) size 186x0
+                  RenderBlock {DIV} at (0,0) size 186x0
+                RenderBlock {DIV} at (0,0) size 186x28
+                  RenderBlock (anonymous) at (0,0) size 186x14
+                    RenderInline {SPAN} at (0,0) size 169x14
+                      RenderText {#text} at (0,0) size 169x14
+                        text run at (0,0) width 169: "alternative presentation button title"
+                  RenderBlock {DIV} at (0,14) size 186x14
+                    RenderText {#text} at (0,0) size 186x14
+                      text run at (0,0) width 186: "alternative presentation button subtitle"
+          RenderText {#text} at (0,0) size 0x0
+        RenderBlock {DIV} at (0,39) size 784x40
+          RenderBlock (anonymous) at (0,0) size 784x0
+            RenderInline {LABEL} at (0,0) size 0x0
+          RenderBlock (anonymous) at (0,0) size 784x40
+            RenderBlock {DIV} at (0,0) size 784x40
+              RenderBlock {DIV} at (0,0) size 784x0
+                RenderBlock {DIV} at (0,0) size 784x0
+              RenderBlock {DIV} at (0,0) size 784x40
+                RenderBlock (anonymous) at (0,0) size 784x20
+                  RenderInline {SPAN} at (0,0) size 224x19
+                    RenderText {#text} at (0,0) size 224x19
+                      text run at (0,0) width 224: "alternative presentation button title"
+                RenderBlock {DIV} at (0,20) size 784x20
+                  RenderText {#text} at (0,0) size 246x19
+                    text run at (0,0) width 246: "alternative presentation button subtitle"
+          RenderBlock (anonymous) at (0,40) size 784x0
+            RenderInline {LABEL} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+        RenderBlock {DIV} at (0,95) size 784x40
+          RenderBlock {P} at (0,0) size 784x40
+            RenderBlock {DIV} at (0,0) size 784x40
+              RenderBlock {DIV} at (0,0) size 784x0
+                RenderBlock {DIV} at (0,0) size 784x0
+              RenderBlock {DIV} at (0,0) size 784x40
+                RenderBlock (anonymous) at (0,0) size 784x20
+                  RenderInline {SPAN} at (0,0) size 224x19
+                    RenderText {#text} at (0,0) size 224x19
+                      text run at (0,0) width 224: "alternative presentation button title"
+                RenderBlock {DIV} at (0,20) size 784x20
+                  RenderText {#text} at (0,0) size 246x19
+                    text run at (0,0) width 246: "alternative presentation button subtitle"
+        RenderBlock {DIV} at (0,151) size 784x40
+          RenderBlock (anonymous) at (0,0) size 784x0
+            RenderInline {LABEL} at (0,0) size 0x0
+          RenderBlock (anonymous) at (0,0) size 784x40
+            RenderBlock {DIV} at (0,0) size 784x40
+              RenderBlock {DIV} at (0,0) size 784x0
+                RenderBlock {DIV} at (0,0) size 784x0
+              RenderBlock {DIV} at (0,0) size 784x40
+                RenderBlock (anonymous) at (0,0) size 784x20
+                  RenderInline {SPAN} at (0,0) size 224x19
+                    RenderText {#text} at (0,0) size 224x19
+                      text run at (0,0) width 224: "alternative presentation button title"
+                RenderBlock {DIV} at (0,20) size 784x20
+                  RenderText {#text} at (0,0) size 246x19
+                    text run at (0,0) width 246: "alternative presentation button subtitle"
+          RenderBlock (anonymous) at (0,40) size 784x0
+            RenderInline {LABEL} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+            RenderText {#text} at (0,0) size 0x0
+        RenderBlock {DIV} at (0,191) size 784x46
+          RenderTable {TABLE} at (0,0) size 252x46
+            RenderTableSection {TBODY} at (0,0) size 252x46
+              RenderTableRow {TR} at (0,2) size 252x42
+                RenderTableCell {TD} at (2,2) size 248x42 [r=0 c=0 rs=1 cs=1]
+                  RenderBlock {DIV} at (1,1) size 246x40
+                    RenderBlock {DIV} at (0,0) size 246x0
+                      RenderBlock {DIV} at (0,0) size 246x0
+                    RenderBlock {DIV} at (0,0) size 246x40
+                      RenderBlock (anonymous) at (0,0) size 246x20
+                        RenderInline {SPAN} at (0,0) size 224x19
+                          RenderText {#text} at (0,0) size 224x19
+                            text run at (0,0) width 224: "alternative presentation button title"
+                      RenderBlock {DIV} at (0,20) size 246x20
+                        RenderText {#text} at (0,0) size 246x19
+                          text run at (0,0) width 246: "alternative presentation button subtitle"
+        RenderBlock {DIV} at (0,237) size 784x46
+          RenderTable {TABLE} at (0,0) size 252x46
+            RenderTableSection {TBODY} at (0,0) size 252x46
+              RenderTableRow {TR} at (0,2) size 252x42
+                RenderTableCell {TD} at (2,2) size 248x42 [r=0 c=0 rs=1 cs=1]
+                  RenderBlock {DIV} at (1,1) size 246x40
+                    RenderBlock {DIV} at (0,0) size 246x0
+                      RenderBlock {DIV} at (0,0) size 246x0
+                    RenderBlock {DIV} at (0,0) size 246x40
+                      RenderBlock (anonymous) at (0,0) size 246x20
+                        RenderInline {SPAN} at (0,0) size 224x19
+                          RenderText {#text} at (0,0) size 224x19
+                            text run at (0,0) width 224: "alternative presentation button title"
+                      RenderBlock {DIV} at (0,20) size 246x20
+                        RenderText {#text} at (0,0) size 246x19
+                          text run at (0,0) width 246: "alternative presentation button subtitle"
+        RenderBlock {P} at (0,299) size 784x40
+          RenderBlock {DIV} at (0,0) size 784x40
+            RenderBlock {DIV} at (0,0) size 784x0
+              RenderBlock {DIV} at (0,0) size 784x0
+            RenderBlock {DIV} at (0,0) size 784x40
+              RenderBlock (anonymous) at (0,0) size 784x20
+                RenderInline {SPAN} at (0,0) size 224x19
+                  RenderText {#text} at (0,0) size 224x19
+                    text run at (0,0) width 224: "alternative presentation button title"
+              RenderBlock {DIV} at (0,20) size 784x20
+                RenderText {#text} at (0,0) size 246x19
+                  text run at (0,0) width 246: "alternative presentation button subtitle"
index 90572d1..89890e2 100644 (file)
@@ -12,6 +12,7 @@ editing/mac [ Pass ]
 fast/scrolling/latching [ Pass ]
 media/mac [ Pass ]
 
+fast/forms/alternative-presentation-button [ Pass ]
 fast/forms/search/search-padding-cancel-results-buttons.html [ Pass ]
 fast/forms/search/search-results-hidden-crash.html [ Pass ]
 
index 22fd0d6..cac94f9 100644 (file)
@@ -1,3 +1,91 @@
+2017-11-28  Daniel Bates  <dabates@apple.com>
+
+        [Cocoa] First pass at implementing alternative presentation button element
+        https://bugs.webkit.org/show_bug.cgi?id=179785
+        Part of <rdar://problem/34917108>
+
+        Reviewed by Brent Fulgham.
+
+        Implement support for substituting a button for one or more elements in a page.
+        This is a first pass. We will refine the logic and the API/SPI in subsequent
+        commits.
+
+        Tests: accessibility/alternative-presentation-button-input-type.html
+               accessibility/alternative-presentation-button.html
+               fast/forms/alternative-presentation-button/replace-and-remove.html
+               fast/forms/alternative-presentation-button/replacement.html
+
+        * English.lproj/Localizable.strings: Add placeholder strings for localization.
+        * SourcesCocoa.txt: Add some files.
+        * WebCore.xcodeproj/project.pbxproj: Ditto.
+        * dom/Element.h:
+        * editing/Editor.cpp:
+        (WebCore::Editor::clear): Clear out all substitutions. This is called whenever
+        we are navigating between pages.
+        (WebCore::Editor::substituteWithAlternativePresentationButton): Added.
+        (WebCore::Editor::removeAlternativePresentationButton): Added.
+        (WebCore::Editor::didInsertAlternativePresentationButtonElement): Added.
+        (WebCore::Editor::didRemoveAlternativePresentationButtonElement): Added.
+        * editing/Editor.h:
+        * editing/cocoa/AlternativePresentationButtonSubstitution.cpp: Added.
+        (WebCore::AlternativePresentationButtonSubstitution::AlternativePresentationButtonSubstitution):
+        (WebCore::AlternativePresentationButtonSubstitution::initializeSavedDisplayStyles):
+        (WebCore::AlternativePresentationButtonSubstitution::apply):
+        (WebCore::AlternativePresentationButtonSubstitution::unapply):
+        * editing/cocoa/AlternativePresentationButtonSubstitution.h:
+        * html/HTMLInputElement.cpp:
+        (WebCore::HTMLInputElement::alternativePresentationButtonElement const): Added.
+        (WebCore::HTMLInputElement::setTypeWithoutUpdatingAttribute): Added.
+        (WebCore::HTMLInputElement::createInputType): Extracted the logic to create the InputType from
+        HTMLInputElement::updateType() to here and added logic to create the input type for the
+        alternative presentation button. This input type is not web exposed.
+        (WebCore::HTMLInputElement::updateType): Modified to take the name of the InputType object to
+        create as an argument and pass it through to HTMLInputElement::createInputType() to actually
+        create it. Reordered the logic for destroying the shadow tree of the old InputType, deallocating
+        the old InputType, and assigning the new InputType such that we assign the new InputType,
+        destroy the shadow tree of the old InputType, and deallocate the old InputType. This ordering
+        allows AlternativePresentationButtonSubstitution::substitute() to avoid restoring the input
+        type saved before the substitution when the input type is changed by the page as opposed to
+        by SPI.
+        (WebCore::HTMLInputElement::parseAttribute): Pass the parsed type.
+        (WebCore::HTMLInputElement::willAttachRenderers): Ditto.
+        * html/HTMLInputElement.h: Change visibility of removeShadowRoot() from private to public so
+        that it can be called from AlternativePresentationButtonSubstitution.
+        * html/InputType.h:
+        (WebCore::InputType::alternativePresentationButtonElement const): Added.
+        * html/InputTypeNames.cpp:
+        (WebCore::InputTypeNames::alternativePresentationButton): Added.
+        * html/InputTypeNames.h:
+        * html/shadow/cocoa/AlternativePresentationButtonElement.cpp: Added.
+        (WebCore::AlternativePresentationButtonElement::create):
+        (WebCore::AlternativePresentationButtonElement::AlternativePresentationButtonElement):
+        (WebCore::AlternativePresentationButtonElement::insertedIntoAncestor):
+        (WebCore::AlternativePresentationButtonElement::removedFromAncestor):
+        (WebCore::AlternativePresentationButtonElement::didFinishInsertingNode):
+        (WebCore::AlternativePresentationButtonElement::defaultEventHandler):
+        * html/shadow/cocoa/AlternativePresentationButtonElement.h:
+        * html/shadow/cocoa/AlternativePresentationButtonInputType.cpp: Added.
+        (WebCore::AlternativePresentationButtonInputType::AlternativePresentationButtonInputType):
+        (WebCore::AlternativePresentationButtonInputType::formControlType const):
+        (WebCore::AlternativePresentationButtonInputType::appendFormData const):
+        (WebCore::AlternativePresentationButtonInputType::supportsValidation const):
+        (WebCore::AlternativePresentationButtonInputType::isTextButton const):
+        (WebCore::AlternativePresentationButtonInputType::alternativePresentationButtonElement const):
+        (WebCore::AlternativePresentationButtonInputType::createShadowSubtree):
+        (WebCore::AlternativePresentationButtonInputType::destroyShadowSubtree):
+        * html/shadow/cocoa/AlternativePresentationButtonInputType.h:
+        * page/ChromeClient.h:
+        * platform/LocalizedStrings.cpp:
+        (WebCore::AXAlternativePresentationButtonLabel):
+        (WebCore::alternativePresentationButtonTitle):
+        (WebCore::alternativePresentationButtonSubtitle):
+        * platform/LocalizedStrings.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::substituteWithAlternativePresentationButton): Added.
+        (WebCore::Internals::removeAlternativePresentationButton): Added.
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2017-11-28  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r225209.
index b58eb5c..36f12d1 100644 (file)
 /* Option in segmented control for choosing list type in text editing */
 "•" = "•";
 
+/* Label for the alternative presentation button. */
+"alternative presentation button" = "alternative presentation button";
+
+/* Alternative presentation button title. */
+"alternative presentation button title" = "alternative presentation button title";
+
+/* Alternative presentation button subtitle. */
+"alternative presentation button subtitle" = "alternative presentation button subtitle";
index d28deba..16d1f61 100644 (file)
@@ -367,6 +367,14 @@ platform/mediastream/mac/WebAudioSourceProviderAVFObjC.mm
 
 platform/mediastream/libwebrtc/LibWebRTCProviderCocoa.cpp
 
+#if ENABLE_ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT
+
+    editing/cocoa/AlternativePresentationButtonSubstitution.cpp
+    html/shadow/cocoa/AlternativePresentationButtonElement.cpp
+    html/shadow/cocoa/AlternativePresentationButtonInputType.cpp
+
+#endif
+
 #if ENABLE_APPLE_PAY
 
     Modules/applepay/ApplePayContactField.cpp
index 2d338fc..4c66aa8 100644 (file)
                CE7B2DB11586ABAD0098B3FA /* TextAlternativeWithRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextAlternativeWithRange.h; sourceTree = "<group>"; };
                CE7B2DB21586ABAD0098B3FA /* TextAlternativeWithRange.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TextAlternativeWithRange.mm; sourceTree = "<group>"; };
                CE7E17821C83A49100AD06AF /* ContentSecurityPolicyHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContentSecurityPolicyHash.h; path = csp/ContentSecurityPolicyHash.h; sourceTree = "<group>"; };
+               CEB828571FBBA0CE005CBEA1 /* AlternativePresentationButtonElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlternativePresentationButtonElement.h; sourceTree = "<group>"; };
+               CEB828581FBBA0CE005CBEA1 /* AlternativePresentationButtonElement.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AlternativePresentationButtonElement.cpp; sourceTree = "<group>"; };
+               CEB8285A1FBBB273005CBEA1 /* AlternativePresentationButtonInputType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlternativePresentationButtonInputType.h; sourceTree = "<group>"; };
+               CEB8285B1FBBB273005CBEA1 /* AlternativePresentationButtonInputType.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AlternativePresentationButtonInputType.cpp; sourceTree = "<group>"; };
+               CEB828621FBCC142005CBEA1 /* AlternativePresentationButtonSubstitution.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlternativePresentationButtonSubstitution.h; sourceTree = "<group>"; };
+               CEB828641FBCE0D3005CBEA1 /* AlternativePresentationButtonSubstitution.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AlternativePresentationButtonSubstitution.cpp; sourceTree = "<group>"; };
                CECADFC2153778FF00E37068 /* DictationAlternative.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DictationAlternative.cpp; sourceTree = "<group>"; };
                CECADFC3153778FF00E37068 /* DictationAlternative.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DictationAlternative.h; sourceTree = "<group>"; };
                CECADFC4153778FF00E37068 /* DictationCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DictationCommand.cpp; sourceTree = "<group>"; };
                4150F9ED12B6E0990008C860 /* shadow */ = {
                        isa = PBXGroup;
                        children = (
+                               CE0B30531FACE93500E3FA79 /* cocoa */,
                                51C4AA5118B28357007BFE9B /* mac */,
                                7C1E97251A9F9834007BF0FB /* AutoFillButtonElement.cpp */,
                                7C1E97261A9F9834007BF0FB /* AutoFillButtonElement.h */,
                7C3E510718DF8F1200C112F7 /* cocoa */ = {
                        isa = PBXGroup;
                        children = (
+                               CEB828641FBCE0D3005CBEA1 /* AlternativePresentationButtonSubstitution.cpp */,
+                               CEB828621FBCC142005CBEA1 /* AlternativePresentationButtonSubstitution.h */,
                                C5227DEF1C3C6DD700F5ED54 /* DataDetection.h */,
                                C5227DF01C3C6DD700F5ED54 /* DataDetection.mm */,
                                9B55EEE81B3E8898005342BC /* EditorCocoa.mm */,
                        path = mediasource;
                        sourceTree = "<group>";
                };
+               CE0B30531FACE93500E3FA79 /* cocoa */ = {
+                       isa = PBXGroup;
+                       children = (
+                               CEB828581FBBA0CE005CBEA1 /* AlternativePresentationButtonElement.cpp */,
+                               CEB828571FBBA0CE005CBEA1 /* AlternativePresentationButtonElement.h */,
+                               CEB8285B1FBBB273005CBEA1 /* AlternativePresentationButtonInputType.cpp */,
+                               CEB8285A1FBBB273005CBEA1 /* AlternativePresentationButtonInputType.h */,
+                       );
+                       path = cocoa;
+                       sourceTree = "<group>";
+               };
                CE17AD141C58522F005F4799 /* csp */ = {
                        isa = PBXGroup;
                        children = (
index 60e9f73..38001cb 100644 (file)
@@ -280,6 +280,7 @@ public:
 
     RefPtr<ShadowRoot> userAgentShadowRoot() const;
     WEBCORE_EXPORT ShadowRoot& ensureUserAgentShadowRoot();
+    void removeShadowRoot();
 
     void setIsDefinedCustomElement(JSCustomElementInterface&);
     void setIsFailedCustomElement(JSCustomElementInterface&);
@@ -636,8 +637,6 @@ private:
     Ref<Node> cloneNodeInternal(Document&, CloningOperation) override;
     virtual Ref<Element> cloneElementWithoutAttributesAndChildren(Document&);
 
-    void removeShadowRoot();
-
     const RenderStyle& resolveComputedStyle();
     const RenderStyle& resolvePseudoElementStyle(PseudoId);
 
index 8be0411..2159226 100644 (file)
 #include "ServicesOverlayController.h"
 #endif
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+#include "AlternativePresentationButtonElement.h"
+#include "AlternativePresentationButtonSubstitution.h"
+#endif
+
 namespace WebCore {
 
 static bool dispatchBeforeInputEvent(Element& element, const AtomicString& inputType, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }, bool cancelable = true)
@@ -1174,6 +1179,11 @@ void Editor::clear()
     m_customCompositionUnderlines.clear();
     m_shouldStyleWithCSS = false;
     m_defaultParagraphSeparator = EditorParagraphSeparatorIsDiv;
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    m_alternativePresentationButtonElementToSubstitutionMap.clear();
+    m_alternativePresentationButtonIdentifierToElementMap.clear();
+    m_lastAlternativePresentationButtonSubstitution = nullptr;
+#endif
 }
 
 bool Editor::insertText(const String& text, Event* triggeringEvent, TextEventInputType inputType)
@@ -3812,6 +3822,56 @@ void Editor::insertAttachmentFromFile(const String& identifier, const String& fi
 
 #endif // ENABLE(ATTACHMENT_ELEMENT)
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+
+void Editor::substituteWithAlternativePresentationButton(Vector<Ref<Element>>&& elements, const String& identifier)
+{
+    if (elements.isEmpty())
+        return;
+
+    // The implementation of the first element is exchanged for the alternative presentation button.
+    // All other elements are hidden.
+    Ref<Element> elementForAlternativePresentation = WTFMove(elements[0]);
+    elements.remove(0);
+
+    m_lastAlternativePresentationButtonIdentifier = identifier;
+    if (is<HTMLInputElement>(elementForAlternativePresentation))
+        m_lastAlternativePresentationButtonSubstitution = std::make_unique<AlternativePresentationButtonSubstitution>(downcast<HTMLInputElement>(elementForAlternativePresentation.get()), WTFMove(elements));
+    else {
+        // FIXME: This substitution is only safe to do if and only if elementForAlternativePresentation can support a user-
+        // agent shadow root and does not support an author shadow root. Not all elements meet this criterion (e.g. <details>).
+        // See <https://bugs.webkit.org/show_bug.cgi?id=180086> for more details.
+        m_lastAlternativePresentationButtonSubstitution = std::make_unique<AlternativePresentationButtonSubstitution>(elementForAlternativePresentation.get(), WTFMove(elements));
+    }
+    m_lastAlternativePresentationButtonSubstitution->apply();
+}
+
+void Editor::removeAlternativePresentationButton(const String& identifier)
+{
+    if (!m_alternativePresentationButtonIdentifierToElementMap.contains(identifier))
+        return;
+    auto* button = m_alternativePresentationButtonIdentifierToElementMap.take(identifier);
+    ASSERT(m_alternativePresentationButtonElementToSubstitutionMap.contains(button));
+    auto substitution = m_alternativePresentationButtonElementToSubstitutionMap.take(button);
+    substitution->unapply();
+}
+
+void Editor::didInsertAlternativePresentationButtonElement(AlternativePresentationButtonElement& button)
+{
+    ASSERT(!m_alternativePresentationButtonElementToSubstitutionMap.contains(&button));
+    ASSERT(!m_alternativePresentationButtonIdentifierToElementMap.contains(m_lastAlternativePresentationButtonIdentifier));
+    m_alternativePresentationButtonElementToSubstitutionMap.set(&button, WTFMove(m_lastAlternativePresentationButtonSubstitution));
+    m_alternativePresentationButtonIdentifierToElementMap.set(m_lastAlternativePresentationButtonIdentifier, &button);
+    button.setUniqueIdentifier(m_lastAlternativePresentationButtonIdentifier);
+}
+
+void Editor::didRemoveAlternativePresentationButtonElement(AlternativePresentationButtonElement& button)
+{
+    removeAlternativePresentationButton(button.uniqueIdentifier());
+}
+
+#endif // ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+
 void Editor::handleAcceptedCandidate(TextCheckingResult acceptedCandidate)
 {
     const VisibleSelection& selection = m_frame.selection().selection();
index c2ed745..ead6664 100644 (file)
@@ -54,6 +54,8 @@ class KillRing;
 
 namespace WebCore {
 
+class AlternativePresentationButtonElement;
+class AlternativePresentationButtonSubstitution;
 class AlternativeTextController;
 class ArchiveResource;
 class DataTransfer;
@@ -507,6 +509,17 @@ public:
     void didRemoveAttachmentElement(HTMLAttachmentElement&);
 #endif
 
+    // FIXME: Find a better place for this functionality.
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    // FIXME: Remove the need to pass an identifier for the alternative presentation button.
+    WEBCORE_EXPORT void substituteWithAlternativePresentationButton(Vector<Ref<Element>>&&, const String&);
+    // FIXME: Have this take an AlternativePresentationButtonElement& instead of an identifier.
+    WEBCORE_EXPORT void removeAlternativePresentationButton(const String&);
+
+    void didInsertAlternativePresentationButtonElement(AlternativePresentationButtonElement&);
+    void didRemoveAlternativePresentationButtonElement(AlternativePresentationButtonElement&);
+#endif
+
 private:
     Document& document() const;
 
@@ -582,6 +595,13 @@ private:
     HashSet<String> m_removedAttachmentIdentifiers;
 #endif
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    HashMap<AlternativePresentationButtonElement*, std::unique_ptr<AlternativePresentationButtonSubstitution>> m_alternativePresentationButtonElementToSubstitutionMap;
+    HashMap<String, AlternativePresentationButtonElement*> m_alternativePresentationButtonIdentifierToElementMap;
+    std::unique_ptr<AlternativePresentationButtonSubstitution> m_lastAlternativePresentationButtonSubstitution;
+    String m_lastAlternativePresentationButtonIdentifier;
+#endif
+
     VisibleSelection m_oldSelectionForEditorUIUpdate;
     Timer m_editorUIUpdateTimer;
     bool m_editorUIUpdateTimerShouldCheckSpellingAndGrammar { false };
diff --git a/Source/WebCore/editing/cocoa/AlternativePresentationButtonSubstitution.cpp b/Source/WebCore/editing/cocoa/AlternativePresentationButtonSubstitution.cpp
new file mode 100644 (file)
index 0000000..b465872
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "config.h"
+#include "AlternativePresentationButtonSubstitution.h"
+
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+
+#include "AlternativePresentationButtonElement.h"
+#include "HTMLInputElement.h"
+#include "InputTypeNames.h"
+#include "ShadowRoot.h"
+#include "StyleProperties.h"
+
+namespace WebCore {
+
+AlternativePresentationButtonSubstitution::AlternativePresentationButtonSubstitution(HTMLInputElement& element, Vector<Ref<Element>>&& elementsToHide)
+    : m_shadowHost { makeRef(element) }
+{
+    initializeSavedDisplayStyles(WTFMove(elementsToHide));
+}
+
+AlternativePresentationButtonSubstitution::AlternativePresentationButtonSubstitution(Element& element, Vector<Ref<Element>>&& elementsToHide)
+    : m_shadowHost { makeRef(element) }
+    , m_alternativePresentationButtonElement { AlternativePresentationButtonElement::create(element.document()) }
+{
+    initializeSavedDisplayStyles(WTFMove(elementsToHide));
+}
+
+void AlternativePresentationButtonSubstitution::initializeSavedDisplayStyles(Vector<Ref<Element>>&& elements)
+{
+    m_savedDisplayStyles.reserveInitialCapacity(elements.size());
+    for (auto& element : elements) {
+        if (is<StyledElement>(element))
+            m_savedDisplayStyles.uncheckedAppend({ adoptRef(downcast<StyledElement>(element.leakRef())) });
+    }
+}
+
+void AlternativePresentationButtonSubstitution::apply()
+{
+    auto saveStyles = [&] {
+        for (auto& savedDisplayStyle : m_savedDisplayStyles) {
+            auto* styleProperties = savedDisplayStyle.element->inlineStyle();
+            if (!styleProperties)
+                continue;
+            savedDisplayStyle.value = styleProperties->getPropertyValue(CSSPropertyDisplay);
+            savedDisplayStyle.important = styleProperties->propertyIsImportant(CSSPropertyDisplay);
+            savedDisplayStyle.wasSpecified = true;
+        }
+    };
+    auto attachShadowRoot = [&] {
+        if (is<HTMLInputElement>(m_shadowHost)) {
+            m_savedShadowHostInputType = m_shadowHost->attributeWithoutSynchronization(HTMLNames::typeAttr);
+            auto& inputElement = downcast<HTMLInputElement>(m_shadowHost.get());
+            ASSERT(inputElement.type() != InputTypeNames::alternativePresentationButton());
+            inputElement.setTypeWithoutUpdatingAttribute(InputTypeNames::alternativePresentationButton());
+            return;
+        }
+        ASSERT(m_alternativePresentationButtonElement);
+        ASSERT(!m_shadowHost->shadowRoot());
+        ASSERT(!m_shadowHost->userAgentShadowRoot());
+        m_shadowHost->ensureUserAgentShadowRoot().appendChild(*m_alternativePresentationButtonElement);
+    };
+    auto hideElements = [&] {
+        for (auto& savedDisplayStyle : m_savedDisplayStyles)
+            savedDisplayStyle.element->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true); // important
+    };
+
+    ASSERT(!m_isApplied);
+    m_isApplied = true;
+    saveStyles();
+    attachShadowRoot();
+    hideElements();
+}
+
+void AlternativePresentationButtonSubstitution::unapply()
+{
+    auto detachShadowRoot = [&] {
+        if (is<HTMLInputElement>(m_shadowHost)) {
+            auto& inputElement = downcast<HTMLInputElement>(m_shadowHost.get());
+            if (inputElement.type() == InputTypeNames::alternativePresentationButton())
+                inputElement.setTypeWithoutUpdatingAttribute(m_savedShadowHostInputType);
+            return;
+        }
+        ASSERT(m_alternativePresentationButtonElement);
+        ASSERT(m_shadowHost->userAgentShadowRoot());
+        ASSERT(m_shadowHost->userAgentShadowRoot()->countChildNodes() == 1);
+        m_shadowHost->userAgentShadowRoot()->removeChild(*m_alternativePresentationButtonElement);
+        ASSERT(!m_shadowHost->userAgentShadowRoot()->countChildNodes());
+        m_shadowHost->removeShadowRoot();
+    };
+    auto restoreStyles = [&] {
+        for (auto& savedDisplayStyle : m_savedDisplayStyles) {
+            if (savedDisplayStyle.wasSpecified)
+                savedDisplayStyle.element->setInlineStyleProperty(CSSPropertyDisplay, savedDisplayStyle.value, savedDisplayStyle.important);
+            else
+                savedDisplayStyle.element->removeInlineStyleProperty(CSSPropertyDisplay);
+        }
+    };
+
+    ASSERT(m_isApplied);
+    m_isApplied = false;
+    restoreStyles();
+    detachShadowRoot();
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
diff --git a/Source/WebCore/editing/cocoa/AlternativePresentationButtonSubstitution.h b/Source/WebCore/editing/cocoa/AlternativePresentationButtonSubstitution.h
new file mode 100644 (file)
index 0000000..32bc2b0
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 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
+
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+
+#include <wtf/RefPtr.h>
+#include <wtf/Vector.h>
+#include <wtf/text/AtomicString.h>
+
+namespace WebCore {
+
+class AlternativePresentationButtonElement;
+class Element;
+class HTMLInputElement;
+class StyledElement;
+
+class AlternativePresentationButtonSubstitution {
+public:
+    AlternativePresentationButtonSubstitution(HTMLInputElement&, Vector<Ref<Element>>&& elementsToHide);
+    AlternativePresentationButtonSubstitution(Element&, Vector<Ref<Element>>&& elementsToHide);
+
+    void apply();
+    void unapply();
+
+private:
+    void initializeSavedDisplayStyles(Vector<Ref<Element>>&&);
+
+    Ref<Element> m_shadowHost;
+    AtomicString m_savedShadowHostInputType;
+    RefPtr<AlternativePresentationButtonElement> m_alternativePresentationButtonElement;
+
+    struct InlineDisplayStyle {
+        Ref<StyledElement> element;
+        String value { };
+        bool important { false };
+        bool wasSpecified { false };
+    };
+    Vector<InlineDisplayStyle> m_savedDisplayStyles;
+
+    bool m_isApplied { false };
+};
+
+} // namespace WebCore
+
+#endif
index 7c833c9..e5b92b4 100644 (file)
 #include "TouchEvent.h"
 #endif
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+#include "AlternativePresentationButtonInputType.h"
+#include "InputTypeNames.h"
+#endif
+
 namespace WebCore {
 
 using namespace HTMLNames;
@@ -230,6 +235,13 @@ HTMLElement* HTMLInputElement::placeholderElement() const
     return m_inputType->placeholderElement();
 }
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+HTMLElement* HTMLInputElement::alternativePresentationButtonElement() const
+{
+    return m_inputType->alternativePresentationButtonElement();
+}
+#endif
+
 bool HTMLInputElement::shouldAutocomplete() const
 {
     if (m_autocomplete != Uninitialized)
@@ -479,12 +491,28 @@ void HTMLInputElement::setType(const AtomicString& type)
     setAttributeWithoutSynchronization(typeAttr, type);
 }
 
-void HTMLInputElement::updateType()
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+void HTMLInputElement::setTypeWithoutUpdatingAttribute(const AtomicString& type)
+{
+    updateType(type);
+}
+#endif
+
+inline std::unique_ptr<InputType> HTMLInputElement::createInputType(const AtomicString& type)
+{
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    if (type == InputTypeNames::alternativePresentationButton())
+        return std::make_unique<AlternativePresentationButtonInputType>(*this);
+#endif
+    return InputType::create(*this, type);
+}
+
+void HTMLInputElement::updateType(const AtomicString& newType)
 {
     ASSERT(m_inputType);
-    auto newType = InputType::create(*this, attributeWithoutSynchronization(typeAttr));
+    auto newInputType = createInputType(newType);
     m_hasType = true;
-    if (m_inputType->formControlType() == newType->formControlType())
+    if (m_inputType->formControlType() == newInputType->formControlType())
         return;
 
     removeFromRadioButtonGroup();
@@ -494,9 +522,9 @@ void HTMLInputElement::updateType()
     bool didRespectHeightAndWidth = m_inputType->shouldRespectHeightAndWidthAttributes();
     bool wasSuccessfulSubmitButtonCandidate = m_inputType->canBeSuccessfulSubmitButton();
 
-    m_inputType->destroyShadowSubtree();
-
-    m_inputType = WTFMove(newType);
+    std::unique_ptr<InputType> oldInputType = WTFMove(m_inputType);
+    m_inputType = WTFMove(newInputType);
+    oldInputType->destroyShadowSubtree();
     m_inputType->createShadowSubtree();
     updateInnerTextElementEditability();
 
@@ -690,7 +718,7 @@ void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomicStr
                 unregisterForSuspensionCallbackIfNeeded();
         }
     } else if (name == typeAttr)
-        updateType();
+        updateType(value);
     else if (name == valueAttr) {
         // Changes to the value attribute may change whether or not this element has a default value.
         // If this field is autocomplete=off that might affect the return value of needsSuspensionCallback.
@@ -810,7 +838,7 @@ RenderPtr<RenderElement> HTMLInputElement::createElementRenderer(RenderStyle&& s
 void HTMLInputElement::willAttachRenderers()
 {
     if (!m_hasType)
-        updateType();
+        updateType(attributeWithoutSynchronization(typeAttr));
 }
 
 void HTMLInputElement::didAttachRenderers()
index a856a9b..4ec4946 100644 (file)
@@ -147,6 +147,10 @@ public:
     HTMLElement* placeholderElement() const final;
     WEBCORE_EXPORT HTMLElement* autoFillButtonElement() const;
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    WEBCORE_EXPORT HTMLElement* alternativePresentationButtonElement() const;
+#endif
+
     bool checked() const { return m_isChecked; }
     WEBCORE_EXPORT void setChecked(bool, TextFieldEventBehavior = DispatchNoEvent);
 
@@ -164,6 +168,10 @@ public:
 
     WEBCORE_EXPORT void setType(const AtomicString&);
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    void setTypeWithoutUpdatingAttribute(const AtomicString&);
+#endif
+
     WEBCORE_EXPORT String value() const final;
     WEBCORE_EXPORT ExceptionOr<void> setValue(const String&, TextFieldEventBehavior = DispatchNoEvent);
     WEBCORE_EXPORT void setValueForUser(const String&);
@@ -425,7 +433,8 @@ private:
     void requiredAttributeChanged() final;
 
     void initializeInputType();
-    void updateType();
+    std::unique_ptr<InputType> createInputType(const AtomicString&);
+    void updateType(const AtomicString&);
     void runPostTypeUpdateTasks();
     
     void subtreeHasChanged() final;
index c2d857f..c739a8f 100644 (file)
@@ -226,6 +226,10 @@ public:
     virtual HTMLElement* sliderTrackElement() const { return nullptr; }
     virtual HTMLElement* placeholderElement() const;
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    virtual HTMLElement* alternativePresentationButtonElement() const { return nullptr; }
+#endif
+
     // Miscellaneous functions.
 
     virtual bool rendererIsNeeded();
index 4387618..07a7d1d 100644 (file)
@@ -169,6 +169,14 @@ const AtomicString& week()
     return name;
 }
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+const AtomicString& alternativePresentationButton()
+{
+    static NeverDestroyed<AtomicString> name("alternative-presentation-button", AtomicString::ConstructFromLiteral);
+    return name;
+}
+#endif
+
 } // namespace WebCore::InputTypeNames
 
 } // namespace WebCore
index 4700306..7f05092 100644 (file)
@@ -50,6 +50,10 @@ const AtomicString& time();
 const AtomicString& url();
 const AtomicString& week();
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+const AtomicString& alternativePresentationButton();
+#endif
+
 }
 
 } // namespace WebCore
diff --git a/Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonElement.cpp b/Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonElement.cpp
new file mode 100644 (file)
index 0000000..af06b6d
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "config.h"
+#include "AlternativePresentationButtonElement.h"
+
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+
+#include "Chrome.h"
+#include "ChromeClient.h"
+#include "DocumentFragment.h"
+#include "Editor.h"
+#include "EventNames.h"
+#include "Frame.h"
+#include "HTMLInputElement.h"
+#include "HTMLSpanElement.h"
+#include "HTMLStyleElement.h"
+#include "LocalizedStrings.h"
+#include "MouseEvent.h"
+#include "Text.h"
+#include "UserAgentStyleSheets.h"
+#include <wtf/NeverDestroyed.h>
+#include <wtf/Ref.h>
+
+namespace WebCore {
+
+Ref<AlternativePresentationButtonElement> AlternativePresentationButtonElement::create(Document& document)
+{
+    return adoptRef(*new AlternativePresentationButtonElement { document });
+}
+
+AlternativePresentationButtonElement::AlternativePresentationButtonElement(Document& document)
+    : HTMLDivElement { HTMLNames::divTag, document }
+{
+    setPseudo(AtomicString { "-webkit-alternative-presentation-button", AtomicString::ConstructFromLiteral });
+    setAttributeWithoutSynchronization(HTMLNames::aria_labelAttr, AXAlternativePresentationButtonLabel());
+    setAttributeWithoutSynchronization(HTMLNames::roleAttr, AtomicString { "button", AtomicString::ConstructFromLiteral });
+}
+
+auto AlternativePresentationButtonElement::insertedIntoAncestor(InsertionType type, ContainerNode& parentOfInsertedTree) -> InsertedIntoAncestorResult
+{
+    HTMLElement::insertedIntoAncestor(type, parentOfInsertedTree);
+    if (type.connectedToDocument) {
+        if (auto* frame = document().frame())
+            frame->editor().didInsertAlternativePresentationButtonElement(*this);
+        return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
+    }
+    return InsertedIntoAncestorResult::Done;
+}
+
+void AlternativePresentationButtonElement::removedFromAncestor(RemovalType type, ContainerNode& ancestor)
+{
+    HTMLDivElement::removedFromAncestor(type, ancestor);
+    if (type.disconnectedFromDocument) {
+        if (auto* frame = document().frame())
+            frame->editor().didRemoveAlternativePresentationButtonElement(*this);
+    }
+}
+
+void AlternativePresentationButtonElement::didFinishInsertingNode()
+{
+    auto fragment = document().createDocumentFragment();
+
+#if __has_include(<WebKitAdditions/alternativePresentationButtonElementShadow.css>)
+    static NeverDestroyed<String> styleSheet { alternativePresentationButtonElementShadowUserAgentStyleSheet, String::ConstructFromLiteral };
+    auto style = HTMLStyleElement::create(document());
+    style->setTextContent(styleSheet);
+    fragment->appendChild(style);
+#endif
+
+    auto iconContainer = HTMLDivElement::create(document());
+    iconContainer->setPseudo(AtomicString { "-webkit-alternative-presentation-button-icon-container" , AtomicString::ConstructFromLiteral });
+    fragment->appendChild(iconContainer);
+
+    auto icon = HTMLDivElement::create(document());
+    icon->setPseudo(AtomicString { "-webkit-alternative-presentation-button-icon", AtomicString::ConstructFromLiteral });
+    icon->setAttributeWithoutSynchronization(HTMLNames::classAttr, AtomicString { "center-img", AtomicString::ConstructFromLiteral });
+    iconContainer->appendChild(icon);
+
+    auto textContainer = HTMLDivElement::create(document());
+    textContainer->setPseudo(AtomicString { "-webkit-alternative-presentation-button-text-container", AtomicString::ConstructFromLiteral });
+    textContainer->setAttributeWithoutSynchronization(HTMLNames::classAttr, AtomicString { "center", AtomicString::ConstructFromLiteral });
+    fragment->appendChild(textContainer);
+
+    auto text = HTMLSpanElement::create(document());
+    text->setPseudo(AtomicString { "-webkit-alternative-presentation-button-title", AtomicString::ConstructFromLiteral });
+    text->appendChild(document().createTextNode(alternativePresentationButtonTitle()));
+    textContainer->appendChild(text);
+
+    auto subtext = HTMLDivElement::create(document());
+    subtext->setPseudo(AtomicString { "-webkit-alternative-presentation-button-subtitle", AtomicString::ConstructFromLiteral });
+    subtext->appendChild(document().createTextNode(alternativePresentationButtonSubtitle()));
+    textContainer->appendChild(subtext);
+
+    appendChild(fragment);
+}
+
+void AlternativePresentationButtonElement::defaultEventHandler(Event& event)
+{
+    if (!is<MouseEvent>(event)) {
+        if (!event.defaultHandled())
+            HTMLDivElement::defaultEventHandler(event);
+        return;
+    }
+
+    MouseEvent& mouseEvent = downcast<MouseEvent>(event);
+
+    if (mouseEvent.type() == eventNames().clickEvent) {
+        document().page()->chrome().client().handleAlternativePresentationButtonClick(*this);
+        event.setDefaultHandled();
+    }
+
+    if (!event.defaultHandled())
+        HTMLDivElement::defaultEventHandler(event);
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
diff --git a/Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonElement.h b/Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonElement.h
new file mode 100644 (file)
index 0000000..de80d0a
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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
+
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+
+#include "HTMLDivElement.h"
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+class HTMLInputElement;
+
+class AlternativePresentationButtonElement final : public HTMLDivElement {
+public:
+    static Ref<AlternativePresentationButtonElement> create(Document&);
+
+    void setUniqueIdentifier(const String& uniqueIdentifier) { m_uniqueIdentifier = uniqueIdentifier; }
+    const String& uniqueIdentifier() const { return m_uniqueIdentifier; }
+
+private:
+    explicit AlternativePresentationButtonElement(Document&);
+
+    // Node
+    InsertedIntoAncestorResult insertedIntoAncestor(InsertionType, ContainerNode&) final;
+    void removedFromAncestor(RemovalType, ContainerNode&) final;
+    void didFinishInsertingNode() final;
+    void defaultEventHandler(Event&) final;
+
+    String m_uniqueIdentifier;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
diff --git a/Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonInputType.cpp b/Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonInputType.cpp
new file mode 100644 (file)
index 0000000..fb14993
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "config.h"
+#include "AlternativePresentationButtonInputType.h"
+
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+
+#include "AlternativePresentationButtonElement.h"
+#include "DOMFormData.h"
+#include "HTMLNames.h"
+#include "InputTypeNames.h"
+#include "ShadowRoot.h"
+
+namespace WebCore {
+
+AlternativePresentationButtonInputType::AlternativePresentationButtonInputType(HTMLInputElement& element)
+    : BaseButtonInputType { element }
+{
+}
+
+AlternativePresentationButtonInputType::~AlternativePresentationButtonInputType() = default;
+
+const AtomicString& AlternativePresentationButtonInputType::formControlType() const
+{
+    return InputTypeNames::alternativePresentationButton();
+}
+
+bool AlternativePresentationButtonInputType::appendFormData(DOMFormData& formData, bool multipart) const
+{
+    InputType::appendFormData(formData, multipart);
+    auto& dirnameAttrValue = element().attributeWithoutSynchronization(HTMLNames::dirnameAttr);
+    if (!dirnameAttrValue.isNull())
+        formData.append(dirnameAttrValue, element().directionForFormData());
+    return true;
+}
+
+bool AlternativePresentationButtonInputType::supportsValidation() const
+{
+    return false;
+}
+
+bool AlternativePresentationButtonInputType::isTextButton() const
+{
+    return true;
+}
+
+HTMLElement* AlternativePresentationButtonInputType::alternativePresentationButtonElement() const
+{
+    return m_alternativePresentationButtonElement.get();
+}
+
+void AlternativePresentationButtonInputType::createShadowSubtree()
+{
+    ASSERT(element().shadowRoot());
+    m_alternativePresentationButtonElement = AlternativePresentationButtonElement::create(element().document());
+    element().shadowRoot()->appendChild(*m_alternativePresentationButtonElement);
+}
+
+void AlternativePresentationButtonInputType::destroyShadowSubtree()
+{
+    BaseButtonInputType::destroyShadowSubtree();
+    m_alternativePresentationButtonElement = nullptr;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
diff --git a/Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonInputType.h b/Source/WebCore/html/shadow/cocoa/AlternativePresentationButtonInputType.h
new file mode 100644 (file)
index 0000000..bee6875
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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
+
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+
+#include "BaseButtonInputType.h"
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+class AlternativePresentationButtonElement;
+
+class AlternativePresentationButtonInputType final : public BaseButtonInputType {
+public:
+    explicit AlternativePresentationButtonInputType(HTMLInputElement&);
+    ~AlternativePresentationButtonInputType();
+
+private:
+    // InputType
+    const AtomicString& formControlType() const final;
+    HTMLElement* alternativePresentationButtonElement() const final;
+    bool supportsValidation() const final;
+    bool isTextButton() const final;
+    void createShadowSubtree() final;
+    void destroyShadowSubtree() final;
+    bool appendFormData(DOMFormData&, bool) const final;
+
+    RefPtr<AlternativePresentationButtonElement> m_alternativePresentationButtonElement;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
index d65875f..b181e09 100644 (file)
@@ -444,6 +444,8 @@ public:
 
     virtual void handleAutoFillButtonClick(HTMLInputElement&) { }
 
+    virtual void handleAlternativePresentationButtonClick(Node&) { }
+
 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
     virtual void addPlaybackTargetPickerClient(uint64_t /*contextId*/) { }
     virtual void removePlaybackTargetPickerClient(uint64_t /*contextId*/) { }
index cc42c79..109b09c 100644 (file)
@@ -625,6 +625,21 @@ String AXAutoFillContactsLabel()
     return WEB_UI_STRING("contact info auto fill", "Label for the auto fill contacts button inside a text field.");
 }
 
+String AXAlternativePresentationButtonLabel()
+{
+    return WEB_UI_STRING("alternative presentation button", "Label for the alternative presentation button.");
+}
+
+String alternativePresentationButtonTitle()
+{
+    return WEB_UI_STRING("alternative presentation button title", "Title text for alternative presentation button");
+}
+
+String alternativePresentationButtonSubtitle()
+{
+    return WEB_UI_STRING("alternative presentation button subtitle", "Subtitle text for alternative presentation button");
+}
+
 String missingPluginText()
 {
     return WEB_UI_STRING("Missing Plug-in", "Label text to be used when a plugin is missing");
index 541ae10..08bd805 100644 (file)
@@ -312,6 +312,12 @@ namespace WebCore {
     WEBCORE_EXPORT String exitFullScreenButtonAccessibilityTitle();
 #endif
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    String AXAlternativePresentationButtonLabel();
+    String alternativePresentationButtonTitle();
+    String alternativePresentationButtonSubtitle();
+#endif
+
 #if USE(GLIB) && defined(GETTEXT_PACKAGE)
 #define WEB_UI_STRING(string, description) WebCore::localizedString(_(string))
 #define WEB_UI_STRING_KEY(string, key, description) WebCore::localizedString(C_(key, string))
index 0c945e6..22ca65a 100644 (file)
@@ -4294,4 +4294,30 @@ MockPaymentCoordinator& Internals::mockPaymentCoordinator() const
 }
 #endif
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+ExceptionOr<void> Internals::substituteWithAlternativePresentationButton(Vector<RefPtr<Element>>&& elementsFromBindings, const String& identifier)
+{
+    if (!frame())
+        return Exception { InvalidAccessError };
+    if (elementsFromBindings.isEmpty())
+        return Exception { TypeError, ASCIILiteral { "Must specify at least one element to substitute." } };
+    Vector<Ref<Element>> elements;
+    elements.reserveInitialCapacity(elementsFromBindings.size());
+    for (auto& element : elementsFromBindings) {
+        if (element)
+            elements.uncheckedAppend(element.releaseNonNull());
+    }
+    frame()->editor().substituteWithAlternativePresentationButton(WTFMove(elements), identifier);
+    return { };
+}
+
+ExceptionOr<void> Internals::removeAlternativePresentationButton(const String& identifier)
+{
+    if (!frame())
+        return Exception { InvalidAccessError };
+    frame()->editor().removeAlternativePresentationButton(identifier);
+    return { };
+}
+#endif
+
 } // namespace WebCore
index 3f959ef..74b047c 100644 (file)
@@ -90,7 +90,7 @@ class VoidCallback;
 class WebGLRenderingContext;
 class XMLHttpRequest;
 
-class Internals final : public RefCounted<Internals>,  private ContextDestructionObserver
+class Internals final : public RefCounted<Internals>, private ContextDestructionObserver
 #if ENABLE(MEDIA_STREAM)
     , private RealtimeMediaSource::Observer
 #endif
@@ -629,6 +629,11 @@ public:
     MockPaymentCoordinator& mockPaymentCoordinator() const;
 #endif
 
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    ExceptionOr<void> substituteWithAlternativePresentationButton(Vector<RefPtr<Element>>&&, const String&);
+    ExceptionOr<void> removeAlternativePresentationButton(const String&);
+#endif
+
     String timelineDescription(AnimationTimeline&);
     void pauseTimeline(AnimationTimeline&);
     void setTimelineCurrentTime(AnimationTimeline&, double);
index 41a34dc..99a1c91 100644 (file)
@@ -572,4 +572,7 @@ enum EventThrottlingBehavior {
     [EnabledAtRuntime=WebAnimations] void pauseTimeline(AnimationTimeline timeline);
     [EnabledAtRuntime=WebAnimations] void setTimelineCurrentTime(AnimationTimeline timeline, double currentTime);
     [Conditional=APPLE_PAY] readonly attribute MockPaymentCoordinator mockPaymentCoordinator;
+
+    [Conditional=ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT, MayThrowException] void substituteWithAlternativePresentationButton(sequence<Element> elements, DOMString identifier);
+    [Conditional=ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT, MayThrowException] void removeAlternativePresentationButton(DOMString identifier);
 };
index a517ec0..b273cf2 100644 (file)
@@ -1,3 +1,46 @@
+2017-11-28  Daniel Bates  <dabates@apple.com>
+
+        [Cocoa] First pass at implementing alternative presentation button element
+        https://bugs.webkit.org/show_bug.cgi?id=179785
+        Part of <rdar://problem/34917108>
+
+        Reviewed by Brent Fulgham.
+
+        Expose SPI to substitute the alternative presentation button for one or more elements
+        and remove the alternative presentation button. Add a private delegate callback when
+        the alternative presentation button is clicked.
+
+        * UIProcess/API/APIUIClient.h:
+        (API::UIClient::didClickAlternativePresentationButton): Added.
+        * UIProcess/API/C/WKPageUIClient.h:
+        * UIProcess/API/Cocoa/WKUIDelegatePrivate.h:
+        * UIProcess/Cocoa/UIDelegate.h:
+        * UIProcess/Cocoa/UIDelegate.mm:
+        (WebKit::UIDelegate::setDelegate): Wired up delegate callback.
+        (WebKit::UIDelegate::UIClient::didClickAlternativePresentationButton): Added.
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::handleAlternativePresentationButtonClick): Added.
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * WebProcess/InjectedBundle/API/APIInjectedBundlePageUIClient.h:
+        (API::InjectedBundle::PageUIClient::didClickAlternativePresentationButton): Added.
+        * WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFrame.h:
+        * WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFrame.mm:
+        (-[WKWebProcessPlugInFrame substituteElements:withAlternativePresentationButtonWithIdentifier:]): Added.
+        (-[WKWebProcessPlugInFrame removeAlternativePresentationButton:]): Added.
+        * WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInFramePrivate.h:
+        * WebProcess/InjectedBundle/API/c/WKBundleFrame.cpp:
+        (WKBundleSubstituteWithAlternativePresentationButton): Added.
+        (WKBundleRemoveAlternativePresentationButton): Added.
+        * WebProcess/InjectedBundle/API/c/WKBundleFramePrivate.h:
+        * WebProcess/InjectedBundle/API/c/WKBundlePageUIClient.h:
+        * WebProcess/InjectedBundle/InjectedBundlePageUIClient.cpp:
+        (WebKit::InjectedBundlePageUIClient::didClickAlternativePresentationButton): Added.
+        * WebProcess/InjectedBundle/InjectedBundlePageUIClient.h:
+        * WebProcess/WebCoreSupport/WebChromeClient.cpp:
+        (WebKit::WebChromeClient::handleAlternativePresentationButtonClick): Added.
+        * WebProcess/WebCoreSupport/WebChromeClient.h:
+
 2017-11-27  Chris Dumez  <cdumez@apple.com>
 
         ASSERTION FAILED: addResult.isNewEntry WebCore::SWServerRegistration::addClientUsingRegistration(WebCore::ServiceWorkerClientIdentifier const&) + 141
index bebe57b..e783693 100644 (file)
@@ -169,6 +169,8 @@ public:
 
     virtual void didClickAutoFillButton(WebKit::WebPageProxy&, Object*) { }
 
+    virtual void didClickAlternativePresentationButton(WebKit::WebPageProxy&, Object*) { }
+
     virtual void imageOrMediaDocumentSizeChanged(const WebCore::IntSize&) { }
 
     virtual void didExceedBackgroundResourceLimitWhileInForeground(WebKit::WebPageProxy&, WKResourceLimit) { }
index 0880a0d..4bbd4ad 100644 (file)
@@ -23,8 +23,7 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef WKPageUIClient_h
-#define WKPageUIClient_h
+#pragma once
 
 #include <WebKit/WKBase.h>
 #include <WebKit/WKEvent.h>
@@ -121,6 +120,7 @@ typedef void (*WKPageIsPlayingAudioDidChangeCallback)(WKPageRef page, const void
 typedef void (*WKPageDecidePolicyForUserMediaPermissionRequestCallback)(WKPageRef page, WKFrameRef frame, WKSecurityOriginRef userMediaDocumentOrigin, WKSecurityOriginRef topLevelDocumentOrigin, WKUserMediaPermissionRequestRef permissionRequest, const void* clientInfo);
 typedef void (*WKCheckUserMediaPermissionCallback)(WKPageRef page, WKFrameRef frame, WKSecurityOriginRef userMediaDocumentOrigin, WKSecurityOriginRef topLevelDocumentOrigin, WKUserMediaPermissionCheckRef devicesRequest, const void *clientInfo);
 typedef void (*WKPageDidClickAutoFillButtonCallback)(WKPageRef page, WKTypeRef userData, const void *clientInfo);
+typedef void (*WKPageDidClickAlternativePresentationButtonCallback)(WKPageRef page, WKTypeRef userData, const void *clientInfo);
 typedef void (*WKPageMediaSessionMetadataDidChangeCallback)(WKPageRef page, WKMediaSessionMetadataRef metadata, const void* clientInfo);
 typedef void (*WKHandleAutoplayEventCallback)(WKPageRef page, WKAutoplayEvent event, WKAutoplayEventFlags flags, const void* clientInfo);
 typedef void (*WKFullscreenMayReturnToInlineCallback)(WKPageRef page, const void* clientInfo);
@@ -128,7 +128,7 @@ typedef void (*WKRequestPointerLockCallback)(WKPageRef page, const void* clientI
 typedef void (*WKDidLosePointerLockCallback)(WKPageRef page, const void* clientInfo);
 typedef void (*WKHasVideoInPictureInPictureDidChangeCallback)(WKPageRef page, bool hasVideoInPictureInPicture, const void* clientInfo);
 typedef void (*WKDidExceedBackgroundResourceLimitWhileInForegroundCallback)(WKPageRef page, WKResourceLimit limit, const void* clientInfo);
-    
+
 // Deprecated
 typedef WKPageRef (*WKPageCreateNewPageCallback_deprecatedForUseWithV0)(WKPageRef page, WKDictionaryRef features, WKEventModifiers modifiers, WKEventMouseButton mouseButton, const void *clientInfo);
 typedef void      (*WKPageMouseDidMoveOverElementCallback_deprecatedForUseWithV0)(WKPageRef page, WKEventModifiers modifiers, WKTypeRef userData, const void *clientInfo);
@@ -931,12 +931,108 @@ typedef struct WKPageUIClientV10 {
     WKHandleAutoplayEventCallback                                       handleAutoplayEvent;
     
     // Version 10.
-    WKHasVideoInPictureInPictureDidChangeCallback                             hasVideoInPictureInPictureDidChange;
+    WKHasVideoInPictureInPictureDidChangeCallback                       hasVideoInPictureInPictureDidChange;
     WKDidExceedBackgroundResourceLimitWhileInForegroundCallback         didExceedBackgroundResourceLimitWhileInForeground;
 } WKPageUIClientV10;
-    
+
+typedef struct WKPageUIClientV11 {
+    WKPageUIClientBase                                                  base;
+
+    // Version 0.
+    WKPageCreateNewPageCallback_deprecatedForUseWithV0                  createNewPage_deprecatedForUseWithV0;
+    WKPageUIClientCallback                                              showPage;
+    WKPageUIClientCallback                                              close;
+    WKPageTakeFocusCallback                                             takeFocus;
+    WKPageFocusCallback                                                 focus;
+    WKPageUnfocusCallback                                               unfocus;
+    WKPageRunJavaScriptAlertCallback_deprecatedForUseWithV0             runJavaScriptAlert_deprecatedForUseWithV0;
+    WKPageRunJavaScriptConfirmCallback_deprecatedForUseWithV0           runJavaScriptConfirm_deprecatedForUseWithV0;
+    WKPageRunJavaScriptPromptCallback_deprecatedForUseWithV0            runJavaScriptPrompt_deprecatedForUseWithV0;
+    WKPageSetStatusTextCallback                                         setStatusText;
+    WKPageMouseDidMoveOverElementCallback_deprecatedForUseWithV0        mouseDidMoveOverElement_deprecatedForUseWithV0;
+    WKPageMissingPluginButtonClickedCallback_deprecatedForUseWithV0     missingPluginButtonClicked_deprecatedForUseWithV0;
+    WKPageDidNotHandleKeyEventCallback                                  didNotHandleKeyEvent;
+    WKPageDidNotHandleWheelEventCallback                                didNotHandleWheelEvent;
+    WKPageGetToolbarsAreVisibleCallback                                 toolbarsAreVisible;
+    WKPageSetToolbarsAreVisibleCallback                                 setToolbarsAreVisible;
+    WKPageGetMenuBarIsVisibleCallback                                   menuBarIsVisible;
+    WKPageSetMenuBarIsVisibleCallback                                   setMenuBarIsVisible;
+    WKPageGetStatusBarIsVisibleCallback                                 statusBarIsVisible;
+    WKPageSetStatusBarIsVisibleCallback                                 setStatusBarIsVisible;
+    WKPageGetIsResizableCallback                                        isResizable;
+    WKPageSetIsResizableCallback                                        setIsResizable;
+    WKPageGetWindowFrameCallback                                        getWindowFrame;
+    WKPageSetWindowFrameCallback                                        setWindowFrame;
+    WKPageRunBeforeUnloadConfirmPanelCallback_deprecatedForUseWithV6    runBeforeUnloadConfirmPanel_deprecatedForUseWithV6;
+    WKPageUIClientCallback                                              didDraw;
+    WKPageUIClientCallback                                              pageDidScroll;
+    WKPageExceededDatabaseQuotaCallback                                 exceededDatabaseQuota;
+    WKPageRunOpenPanelCallback                                          runOpenPanel;
+    WKPageDecidePolicyForGeolocationPermissionRequestCallback           decidePolicyForGeolocationPermissionRequest;
+    WKPageHeaderHeightCallback                                          headerHeight;
+    WKPageFooterHeightCallback                                          footerHeight;
+    WKPageDrawHeaderCallback                                            drawHeader;
+    WKPageDrawFooterCallback                                            drawFooter;
+    WKPagePrintFrameCallback                                            printFrame;
+    WKPageUIClientCallback                                              runModal;
+    void*                                                               unused1; // Used to be didCompleteRubberBandForMainFrame
+    WKPageSaveDataToFileInDownloadsFolderCallback                       saveDataToFileInDownloadsFolder;
+    void*                                                               shouldInterruptJavaScript_unavailable;
+
+    // Version 1.
+    WKPageCreateNewPageCallback_deprecatedForUseWithV1                  createNewPage_deprecatedForUseWithV1;
+    WKPageMouseDidMoveOverElementCallback                               mouseDidMoveOverElement;
+    WKPageDecidePolicyForNotificationPermissionRequestCallback          decidePolicyForNotificationPermissionRequest;
+    WKPageUnavailablePluginButtonClickedCallback_deprecatedForUseWithV1 unavailablePluginButtonClicked_deprecatedForUseWithV1;
+
+    // Version 2.
+    WKPageShowColorPickerCallback                                       showColorPicker;
+    WKPageHideColorPickerCallback                                       hideColorPicker;
+    WKPageUnavailablePluginButtonClickedCallback                        unavailablePluginButtonClicked;
+
+    // Version 3.
+    WKPagePinnedStateDidChangeCallback                                  pinnedStateDidChange;
+
+    // Version 4.
+    void*                                                               unused2; // Used to be didBeginTrackingPotentialLongMousePress.
+    void*                                                               unused3; // Used to be didRecognizeLongMousePress.
+    void*                                                               unused4; // Used to be didCancelTrackingPotentialLongMousePress.
+    WKPageIsPlayingAudioDidChangeCallback                               isPlayingAudioDidChange;
+
+    // Version 5.
+    WKPageDecidePolicyForUserMediaPermissionRequestCallback             decidePolicyForUserMediaPermissionRequest;
+    WKPageDidClickAutoFillButtonCallback                                didClickAutoFillButton;
+    WKPageRunJavaScriptAlertCallback_deprecatedForUseWithV5             runJavaScriptAlert_deprecatedForUseWithV5;
+    WKPageRunJavaScriptConfirmCallback_deprecatedForUseWithV5           runJavaScriptConfirm_deprecatedForUseWithV5;
+    WKPageRunJavaScriptPromptCallback_deprecatedForUseWithV5            runJavaScriptPrompt_deprecatedForUseWithV5;
+    WKPageMediaSessionMetadataDidChangeCallback                         mediaSessionMetadataDidChange;
+
+    // Version 6.
+    WKPageCreateNewPageCallback                                         createNewPage;
+    WKPageRunJavaScriptAlertCallback                                    runJavaScriptAlert;
+    WKPageRunJavaScriptConfirmCallback                                  runJavaScriptConfirm;
+    WKPageRunJavaScriptPromptCallback                                   runJavaScriptPrompt;
+    WKCheckUserMediaPermissionCallback                                  checkUserMediaPermissionForOrigin;
+
+    // Version 7.
+    WKPageRunBeforeUnloadConfirmPanelCallback                           runBeforeUnloadConfirmPanel;
+    WKFullscreenMayReturnToInlineCallback                               fullscreenMayReturnToInline;
+
+    // Version 8.
+    WKRequestPointerLockCallback                                        requestPointerLock;
+    WKDidLosePointerLockCallback                                        didLosePointerLock;
+
+    // Version 9.
+    WKHandleAutoplayEventCallback                                       handleAutoplayEvent;
+
+    // Version 10.
+    WKHasVideoInPictureInPictureDidChangeCallback                       hasVideoInPictureInPictureDidChange;
+    WKDidExceedBackgroundResourceLimitWhileInForegroundCallback         didExceedBackgroundResourceLimitWhileInForeground;
+
+    // Version 11.
+    WKPageDidClickAlternativePresentationButtonCallback                 didClickAlternativePresentationButton;
+} WKPageUIClientV11;
+
 #ifdef __cplusplus
 }
 #endif
-
-#endif // WKPageUIClient_h
index 3b2e8cb..31a34e8 100644 (file)
@@ -165,6 +165,7 @@ struct UIEdgeInsets;
 - (void)_webView:(WKWebView *)webView didNotHandleWheelEvent:(NSEvent *)event WK_API_AVAILABLE(macosx(WK_MAC_TBA));
 - (void)_webView:(WKWebView *)webView handleAutoplayEvent:(_WKAutoplayEvent)event withFlags:(_WKAutoplayEventFlags)flags WK_API_AVAILABLE(macosx(WK_MAC_TBA));
 - (void)_webView:(WKWebView *)webView didClickAutoFillButtonWithUserInfo:(id <NSSecureCoding>)userInfo WK_API_AVAILABLE(macosx(WK_MAC_TBA));
+- (void)_webView:(WKWebView *)webView didClickAlternativePresentationButtonWithUserInfo:(id <NSSecureCoding>)userInfo WK_API_AVAILABLE(macosx(WK_MAC_TBA));
 - (void)_webView:(WKWebView *)webView getToolbarsAreVisibleWithCompletionHandler:(void(^)(BOOL))completionHandler WK_API_AVAILABLE(macosx(WK_MAC_TBA));
 - (void)_webView:(WKWebView *)webView saveDataToFile:(NSData *)data suggestedFilename:(NSString *)suggestedFilename mimeType:(NSString *)mimeType originatingURL:(NSURL *)url WK_API_AVAILABLE(macosx(WK_MAC_TBA));
 - (CGFloat)_webViewHeaderHeight:(WKWebView *)webView WK_API_AVAILABLE(macosx(WK_MAC_TBA));
index 5fbca66..0e09b88 100644 (file)
@@ -114,6 +114,7 @@ private:
         void unavailablePluginButtonClicked(WebPageProxy&, WKPluginUnavailabilityReason, API::Dictionary&) final;
         void mouseDidMoveOverElement(WebPageProxy&, const WebHitTestResultData&, WebEvent::Modifiers, API::Object*);
         void didClickAutoFillButton(WebPageProxy&, API::Object*) final;
+        void didClickAlternativePresentationButton(WebPageProxy&, API::Object*) final;
         void toolbarsAreVisible(WebPageProxy&, Function<void(bool)>&&) final;
         bool runOpenPanel(WebPageProxy*, WebFrameProxy*, const WebCore::SecurityOriginData&, API::OpenPanelParameters*, WebOpenPanelResultListenerProxy*) final;
         void didExceedBackgroundResourceLimitWhileInForeground(WebPageProxy&, WKResourceLimit) final;
@@ -172,6 +173,7 @@ private:
         bool webViewHandleAutoplayEventWithFlags : 1;
         bool webViewUnavailablePlugInButtonClicked : 1;
         bool webViewDidClickAutoFillButtonWithUserInfo : 1;
+        bool webViewDidClickAlternativePresentationButtonWithUserInfo : 1;
         bool webViewDrawHeaderInRectForPageWithTitleURL : 1;
         bool webViewDrawFooterInRectForPageWithTitleURL : 1;
         bool webViewGetWindowFrameWithCompletionHandler : 1;
index 4c584f9..c8d3279 100644 (file)
@@ -120,6 +120,7 @@ void UIDelegate::setDelegate(id <WKUIDelegate> delegate)
     m_delegateMethods.webViewUnavailablePlugInButtonClicked = [delegate respondsToSelector:@selector(_webView:unavailablePlugInButtonClickedWithReason:plugInInfo:)];
     m_delegateMethods.webViewHandleAutoplayEventWithFlags = [delegate respondsToSelector:@selector(_webView:handleAutoplayEvent:withFlags:)];
     m_delegateMethods.webViewDidClickAutoFillButtonWithUserInfo = [delegate respondsToSelector:@selector(_webView:didClickAutoFillButtonWithUserInfo:)];
+    m_delegateMethods.webViewDidClickAlternativePresentationButtonWithUserInfo = [delegate respondsToSelector:@selector(_webView:didClickAlternativePresentationButtonWithUserInfo:)];
     m_delegateMethods.webViewDrawHeaderInRectForPageWithTitleURL = [delegate respondsToSelector:@selector(_webView:drawHeaderInRect:forPageWithTitle:URL:)];
     m_delegateMethods.webViewDrawFooterInRectForPageWithTitleURL = [delegate respondsToSelector:@selector(_webView:drawFooterInRect:forPageWithTitle:URL:)];
     m_delegateMethods.webViewHeaderHeight = [delegate respondsToSelector:@selector(_webViewHeaderHeight:)];
@@ -696,7 +697,19 @@ void UIDelegate::UIClient::didClickAutoFillButton(WebPageProxy&, API::Object* us
     
     [(id <WKUIDelegatePrivate>)delegate _webView:m_uiDelegate.m_webView didClickAutoFillButtonWithUserInfo:userInfo ? static_cast<id <NSSecureCoding>>(userInfo->wrapper()) : nil];
 }
-    
+
+void UIDelegate::UIClient::didClickAlternativePresentationButton(WebPageProxy&, API::Object* userInfo)
+{
+    if (!m_uiDelegate.m_delegateMethods.webViewDidClickAlternativePresentationButtonWithUserInfo)
+        return;
+
+    auto delegate = m_uiDelegate.m_delegate.get();
+    if (!delegate)
+        return;
+
+    [(id <WKUIDelegatePrivate>)delegate _webView:m_uiDelegate.m_webView didClickAlternativePresentationButtonWithUserInfo:userInfo ? static_cast<id <NSSecureCoding>>(userInfo->wrapper()) : nil];
+}
+
 void UIDelegate::UIClient::handleAutoplayEvent(WebPageProxy&, WebCore::AutoplayEvent event, OptionSet<WebCore::AutoplayEventFlags> flags)
 {
     if (!m_uiDelegate.m_delegateMethods.webViewHandleAutoplayEventWithFlags)
index 7590d51..1c36cb8 100644 (file)
@@ -6879,6 +6879,11 @@ void WebPageProxy::handleAutoFillButtonClick(const UserData& userData)
     m_uiClient->didClickAutoFillButton(*this, m_process->transformHandlesToObjects(userData.object()).get());
 }
 
+void WebPageProxy::handleAlternativePresentationButtonClick(const UserData& userData)
+{
+    m_uiClient->didClickAlternativePresentationButton(*this, m_process->transformHandlesToObjects(userData.object()).get());
+}
+
 #if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
 void WebPageProxy::addPlaybackTargetPickerClient(uint64_t contextId)
 {
index 1ce15f0..3f96fdf 100644 (file)
@@ -1634,6 +1634,8 @@ private:
 
     void handleAutoFillButtonClick(const UserData&);
 
+    void handleAlternativePresentationButtonClick(const UserData&);
+
     void finishInitializingWebPageAfterProcessLaunch();
 
     void handleMessage(IPC::Connection&, const String& messageName, const UserData& messageBody);
index 15aff31..a1c9648 100644 (file)
@@ -459,6 +459,9 @@ messages -> WebPageProxy {
     HandleSynchronousMessage(String messageName, WebKit::UserData messageBody) -> (WebKit::UserData returnData) WantsConnection
 
     HandleAutoFillButtonClick(WebKit::UserData userData);
+
+    HandleAlternativePresentationButtonClick(WebKit::UserData userData)
+
     ContentRuleListNotification(WebCore::URL url, Vector<String> identifiers, Vector<String> notifications)
 
 #if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
index c8ea05a..3168355 100644 (file)
@@ -23,8 +23,7 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef APIInjectedBundlePageUIClient_h
-#define APIInjectedBundlePageUIClient_h
+#pragma once
 
 #include "WebEvent.h"
 #include <runtime/ConsoleTypes.h>
@@ -89,10 +88,10 @@ public:
     virtual WTF::String plugInExtraScript() const { return emptyString(); }
 
     virtual void didClickAutoFillButton(WebKit::WebPage&, WebKit::InjectedBundleNodeHandle&, RefPtr<API::Object>&) { }
+
+    virtual void didClickAlternativePresentationButton(WebKit::WebPage&, WebKit::InjectedBundleNodeHandle&, RefPtr<API::Object>&) { }
 };
 
 } // namespace InjectedBundle
 
 } // namespace API
-
-#endif // APIInjectedBundlePageUIClient_h
index 715e291..c056eb6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
index 543f68c..b4e1231 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #import "_WKFrameHandleInternal.h"
 #import <JavaScriptCore/JSValue.h>
 #import <WebCore/CertificateInfo.h>
+#import <WebCore/Editor.h>
+#import <WebCore/Element.h>
 #import <WebCore/Frame.h>
 #import <WebCore/IntPoint.h>
 #import <WebCore/LinkIconCollector.h>
 #import <WebCore/LinkIconType.h>
+#import <wtf/Vector.h>
 
 using namespace WebKit;
 
@@ -88,6 +91,31 @@ using namespace WebKit;
     return [JSValue valueWithJSValueRef:valueRef inContext:[self jsContextForWorld:world]];
 }
 
+- (void)substituteElements:(NSArray<WKWebProcessPlugInNodeHandle *> *)elements withAlternativePresentationButtonWithIdentifier:(NSString *)identifier
+{
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    size_t size = static_cast<size_t>(elements.count);
+    Vector<Ref<WebCore::Element>> coreElements;
+    coreElements.reserveInitialCapacity(size);
+    for (size_t i = 0; i < size; ++i) {
+        auto* plugInNodeHandle = [elements objectAtIndex:static_cast<NSUInteger>(i)];
+        if (!plugInNodeHandle)
+            continue;
+        auto& coreNode = *plugInNodeHandle._nodeHandle.coreNode();
+        if (is<WebCore::Element>(coreNode))
+            coreElements.uncheckedAppend(downcast<WebCore::Element>(coreNode));
+    }
+    _frame->coreFrame()->editor().substituteWithAlternativePresentationButton(WTFMove(coreElements), identifier);
+#endif
+}
+
+- (void)removeAlternativePresentationButton:(NSString *)identifier
+{
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    _frame->coreFrame()->editor().removeAlternativePresentationButton(identifier);
+#endif
+}
+
 - (WKWebProcessPlugInBrowserContextController *)_browserContextController
 {
     return wrapper(*_frame->page());
index 8310ac5..a84cc45 100644 (file)
@@ -42,6 +42,9 @@
 
 @property (nonatomic, readonly) WKWebProcessPlugInFrame *_parentFrame;
 
+- (void)substituteElements:(NSArray<WKWebProcessPlugInNodeHandle *> *)elements withAlternativePresentationButtonWithIdentifier:(NSString *)identifier;
+- (void)removeAlternativePresentationButton:(NSString *)identifier;
+
 @end
 
 #endif // WK_API_ENABLED
index 4c0c6ae..a5ca4a9 100644 (file)
@@ -40,6 +40,7 @@
 #include "WebFrame.h"
 #include "WebPage.h"
 #include <WebCore/Document.h>
+#include <WebCore/Editor.h>
 #include <WebCore/FocusController.h>
 #include <WebCore/Frame.h>
 #include <WebCore/FrameLoader.h>
@@ -296,3 +297,39 @@ void WKBundleFrameFocus(WKBundleFrameRef frameRef)
 
     coreFrame->page()->focusController().setFocusedFrame(coreFrame);
 }
+
+void WKBundleSubstituteWithAlternativePresentationButton(WKBundleFrameRef frame, WKArrayRef elements, WKStringRef identifier)
+{
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    auto* coreFrame = toImpl(frame)->coreFrame();
+    if (!coreFrame)
+        return;
+    API::Array* apiAssociatedElements = toImpl(elements);
+    Vector<Ref<Element>> coreElements;
+    coreElements.reserveInitialCapacity(apiAssociatedElements->size());
+    // FIXME: Add a non-constant iterator for API::Array.
+    for (const auto* nodeHandle : apiAssociatedElements->elementsOfType<InjectedBundleNodeHandle>()) {
+        if (!nodeHandle)
+            continue;
+        Node& coreNode = *const_cast<InjectedBundleNodeHandle*>(nodeHandle)->coreNode();
+        if (is<Element>(coreNode))
+            coreElements.uncheckedAppend(downcast<Element>(coreNode));
+    }
+    coreFrame->editor().substituteWithAlternativePresentationButton(WTFMove(coreElements), toWTFString(identifier));
+#else
+    UNUSED_PARAM(frame);
+    UNUSED_PARAM(elements);
+    UNUSED_PARAM(identifier);
+#endif
+}
+
+void WKBundleRemoveAlternativePresentationButton(WKBundleFrameRef frame, WKStringRef identifier)
+{
+#if ENABLE(ALTERNATIVE_PRESENTATION_BUTTON_ELEMENT)
+    if (auto* coreFrame = toImpl(frame)->coreFrame())
+        coreFrame->editor().removeAlternativePresentationButton(toWTFString(identifier));
+#else
+    UNUSED_PARAM(frame);
+    UNUSED_PARAM(identifier);
+#endif
+}
index 0e83d35..434ab5d 100644 (file)
@@ -56,6 +56,9 @@ WK_EXPORT bool WKBundleFrameHandlesPageScaleGesture(WKBundleFrameRef frame);
 
 WK_EXPORT void WKBundleFrameFocus(WKBundleFrameRef frame);
 
+WK_EXPORT void WKBundleSubstituteWithAlternativePresentationButton(WKBundleFrameRef frame, WKArrayRef elements, WKStringRef identifier);
+WK_EXPORT void WKBundleRemoveAlternativePresentationButton(WKBundleFrameRef frame, WKStringRef identifier);
+
 #ifdef __cplusplus
 }
 #endif
index e07a6b2..1e9e237 100644 (file)
@@ -23,8 +23,7 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef WKBundlePageUIClient_h
-#define WKBundlePageUIClient_h
+#pragma once
 
 #include <WebKit/WKBase.h>
 #include <WebKit/WKEvent.h>
@@ -55,6 +54,7 @@ typedef WKStringRef (*WKBundlePagePlugInCreateStartLabelSubtitleCallback)(WKStri
 typedef WKStringRef (*WKBundlePagePlugInCreateExtraStyleSheetCallback)(const void *clientInfo);
 typedef WKStringRef (*WKBundlePagePlugInCreateExtraScriptCallback)(const void *clientInfo);
 typedef void (*WKBundlePageDidClickAutoFillButtonCallback)(WKBundlePageRef page, WKBundleNodeHandleRef inputElement, WKTypeRef* userData, const void *clientInfo);
+typedef void (*WKBundlePageDidClickAlternativePresentationButtonCallback)(WKBundlePageRef page, WKBundleNodeHandleRef inputElement, WKTypeRef* userData, const void *clientInfo);
 
 typedef struct WKBundlePageUIClientBase {
     int                                                                 version;
@@ -171,4 +171,42 @@ typedef struct WKBundlePageUIClientV3 {
     WKBundlePageDidClickAutoFillButtonCallback                          didClickAutoFillButton;
 } WKBundlePageUIClientV3;
 
-#endif // WKBundlePageUIClient_h
+typedef struct WKBundlePageUIClientV4 {
+    WKBundlePageUIClientBase                                            base;
+
+    // Version 0.
+    WKBundlePageWillAddMessageToConsoleCallback                         willAddMessageToConsole;
+    WKBundlePageWillSetStatusbarTextCallback                            willSetStatusbarText;
+    WKBundlePageWillRunJavaScriptAlertCallback                          willRunJavaScriptAlert;
+    WKBundlePageWillRunJavaScriptConfirmCallback                        willRunJavaScriptConfirm;
+    WKBundlePageWillRunJavaScriptPromptCallback                         willRunJavaScriptPrompt;
+    WKBundlePageMouseDidMoveOverElementCallback                         mouseDidMoveOverElement;
+    WKBundlePageDidScrollCallback                                       pageDidScroll;
+    void*                                                               unused1;
+    WKBundlePageGenerateFileForUploadCallback                           shouldGenerateFileForUpload;
+    WKBundlePageGenerateFileForUploadCallback                           generateFileForUpload;
+    void*                                                               unused2;
+    WKBundlePageStatusBarIsVisibleCallback                              statusBarIsVisible;
+    WKBundlePageMenuBarIsVisibleCallback                                menuBarIsVisible;
+    WKBundlePageToolbarsAreVisibleCallback                              toolbarsAreVisible;
+
+    // Version 1.
+    WKBundlePageReachedAppCacheOriginQuotaCallback                      didReachApplicationCacheOriginQuota;
+
+    // Version 2.
+    WKBundlePageExceededDatabaseQuotaCallback                           didExceedDatabaseQuota;
+    WKBundlePagePlugInCreateStartLabelTitleCallback                     createPlugInStartLabelTitle;
+    WKBundlePagePlugInCreateStartLabelSubtitleCallback                  createPlugInStartLabelSubtitle;
+    WKBundlePagePlugInCreateExtraStyleSheetCallback                     createPlugInExtraStyleSheet;
+    WKBundlePagePlugInCreateExtraScriptCallback                         createPlugInExtraScript;
+
+    // Version 3.
+    void*                                                               unused3;
+    void*                                                               unused4;
+    void*                                                               unused5;
+
+    WKBundlePageDidClickAutoFillButtonCallback                          didClickAutoFillButton;
+
+    // Version 4.
+    WKBundlePageDidClickAlternativePresentationButtonCallback           didClickAlternativePresentationButton;
+} WKBundlePageUIClientV4;
index 38e6c23..55c960e 100644 (file)
@@ -212,4 +212,14 @@ void InjectedBundlePageUIClient::didClickAutoFillButton(WebPage& page, InjectedB
     userData = adoptRef(toImpl(userDataToPass));
 }
 
+void InjectedBundlePageUIClient::didClickAlternativePresentationButton(WebPage& page, InjectedBundleNodeHandle& nodeHandle, RefPtr<API::Object>& userData)
+{
+    if (!m_client.didClickAlternativePresentationButton)
+        return;
+
+    WKTypeRef userDataToPass = nullptr;
+    m_client.didClickAlternativePresentationButton(toAPI(&page), toAPI(&nodeHandle), &userDataToPass, m_client.base.clientInfo);
+    userData = adoptRef(toImpl(userDataToPass));
+}
+
 } // namespace WebKit
index bc246ec..3f8c514 100644 (file)
@@ -23,8 +23,7 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef InjectedBundlePageUIClient_h
-#define InjectedBundlePageUIClient_h
+#pragma once
 
 #include "APIClient.h"
 #include "APIInjectedBundlePageUIClient.h"
@@ -37,7 +36,7 @@ namespace API {
 class Object;
 
 template<> struct ClientTraits<WKBundlePageUIClientBase> {
-    typedef std::tuple<WKBundlePageUIClientV0, WKBundlePageUIClientV1, WKBundlePageUIClientV2, WKBundlePageUIClientV3> Versions;
+    typedef std::tuple<WKBundlePageUIClientV0, WKBundlePageUIClientV1, WKBundlePageUIClientV2, WKBundlePageUIClientV3, WKBundlePageUIClientV4> Versions;
 };
 }
 
@@ -71,8 +70,8 @@ public:
     String plugInExtraScript() const override;
 
     void didClickAutoFillButton(WebPage&, InjectedBundleNodeHandle&, RefPtr<API::Object>& userData) override;
+
+    void didClickAlternativePresentationButton(WebPage&, InjectedBundleNodeHandle&, RefPtr<API::Object>& userData) override;
 };
 
 } // namespace WebKit
-
-#endif // InjectedBundlePageUIClient_h
index 2daa819..5678495 100644 (file)
@@ -1169,6 +1169,18 @@ void WebChromeClient::handleAutoFillButtonClick(HTMLInputElement& inputElement)
     m_page.send(Messages::WebPageProxy::HandleAutoFillButtonClick(UserData(WebProcess::singleton().transformObjectsToHandles(userData.get()).get())));
 }
 
+void WebChromeClient::handleAlternativePresentationButtonClick(Node& node)
+{
+    RefPtr<API::Object> userData;
+
+    // Notify the bundle client.
+    auto nodeHandle = InjectedBundleNodeHandle::getOrCreate(node);
+    m_page.injectedBundleUIClient().didClickAlternativePresentationButton(m_page, nodeHandle.get(), userData);
+
+    // Notify the UIProcess.
+    m_page.send(Messages::WebPageProxy::HandleAlternativePresentationButtonClick(UserData(WebProcess::singleton().transformObjectsToHandles(userData.get()).get())));
+}
+
 #if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
 
 void WebChromeClient::addPlaybackTargetPickerClient(uint64_t contextId)
index b498830..9ba99be 100644 (file)
@@ -323,6 +323,8 @@ private:
 
     void handleAutoFillButtonClick(WebCore::HTMLInputElement&) final;
 
+    void handleAlternativePresentationButtonClick(WebCore::Node&) final;
+
 #if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
     void addPlaybackTargetPickerClient(uint64_t /*contextId*/) final;
     void removePlaybackTargetPickerClient(uint64_t /*contextId*/) final;
index da152f6..d848fe0 100644 (file)
@@ -1,3 +1,23 @@
+2017-11-28  Daniel Bates  <dabates@apple.com>
+
+        [Cocoa] First pass at implementing alternative presentation button element
+        https://bugs.webkit.org/show_bug.cgi?id=179785
+        Part of <rdar://problem/34917108>
+
+        Reviewed by Brent Fulgham.
+
+        Add a test that substitutes the alternative presentation button for an element in
+        the page and clicks it.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: Add test.
+        * TestWebKitAPI/Tests/WebKitCocoa/ClickAlternativePresentationButton.mm: Added.
+        (didClickAlternativePresentationButton):
+        (-[ClickAlternativePresentationButton webProcessPlugIn:didCreateBrowserContextController:]):
+        * TestWebKitAPI/Tests/WebKitCocoa/UIDelegate.mm:
+        (TEST):
+        (-[AlternativePresentationButtonDelegate _webView:didClickAlternativePresentationButtonWithUserInfo:]):
+        (-[AlternativePresentationButtonDelegate webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:]):
+
 2017-11-28  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         REGRESSION(r225166): [GTK] Skipped unit tests are considered failures after glib upgrade
index b04ce51..ced90a1 100644 (file)
                CE06DF9B1E1851F200E570C9 /* SecurityOrigin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE06DF9A1E1851F200E570C9 /* SecurityOrigin.cpp */; };
                CE14F1A4181873B0001C2705 /* WillPerformClientRedirectToURLCrash.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CE14F1A2181873B0001C2705 /* WillPerformClientRedirectToURLCrash.html */; };
                CE1866491F72E8F100A0CAB6 /* MarkerSubrange.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE1866471F72E8F100A0CAB6 /* MarkerSubrange.cpp */; };
+               CE2C13881FBDF98D0032DD6C /* ClickAlternativePresentationButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = CE2C13861FBD125C0032DD6C /* ClickAlternativePresentationButton.mm */; };
                CE3524F81B1431F60028A7C5 /* TextFieldDidBeginAndEndEditing_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE3524F21B142B8D0028A7C5 /* TextFieldDidBeginAndEndEditing_Bundle.cpp */; };
                CE3524F91B1441C40028A7C5 /* TextFieldDidBeginAndEndEditing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE3524F11B142B8D0028A7C5 /* TextFieldDidBeginAndEndEditing.cpp */; };
                CE3524FA1B1443890028A7C5 /* input-focus-blur.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CE3524F51B142BBB0028A7C5 /* input-focus-blur.html */; };
                CE06DF9A1E1851F200E570C9 /* SecurityOrigin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SecurityOrigin.cpp; sourceTree = "<group>"; };
                CE14F1A2181873B0001C2705 /* WillPerformClientRedirectToURLCrash.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = WillPerformClientRedirectToURLCrash.html; sourceTree = "<group>"; };
                CE1866471F72E8F100A0CAB6 /* MarkerSubrange.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MarkerSubrange.cpp; sourceTree = "<group>"; };
+               CE2C13861FBD125C0032DD6C /* ClickAlternativePresentationButton.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ClickAlternativePresentationButton.mm; sourceTree = "<group>"; };
                CE32C7C718184C4900CD8C28 /* WillPerformClientRedirectToURLCrash.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WillPerformClientRedirectToURLCrash.mm; sourceTree = "<group>"; };
                CE3524F11B142B8D0028A7C5 /* TextFieldDidBeginAndEndEditing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextFieldDidBeginAndEndEditing.cpp; sourceTree = "<group>"; };
                CE3524F21B142B8D0028A7C5 /* TextFieldDidBeginAndEndEditing_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextFieldDidBeginAndEndEditing_Bundle.cpp; sourceTree = "<group>"; };
                                37A709AC1E3EA7E800CA5969 /* BundleRangeHandleProtocol.h */,
                                1C2B817E1C891E4200A5529F /* CancelFontSubresource.mm */,
                                1C2B81811C891EFA00A5529F /* CancelFontSubresourcePlugIn.mm */,
+                               CE2C13861FBD125C0032DD6C /* ClickAlternativePresentationButton.mm */,
                                5CB18BA71F5645B200EE23C4 /* ClickAutoFillButton.mm */,
                                1AAD19F51C7CE20300831E47 /* Coding.mm */,
                                7C3DB8E21D12129B00AE8CC3 /* CommandBackForward.mm */,
                                A13EBBB01B87436F00097110 /* BundleParametersPlugIn.mm in Sources */,
                                37A709AF1E3EA97E00CA5969 /* BundleRangeHandlePlugIn.mm in Sources */,
                                1C2B81831C891F0900A5529F /* CancelFontSubresourcePlugIn.mm in Sources */,
+                               CE2C13881FBDF98D0032DD6C /* ClickAlternativePresentationButton.mm in Sources */,
                                5CB18BA81F5645E300EE23C4 /* ClickAutoFillButton.mm in Sources */,
                                A14FC58B1B89927100D107EB /* ContentFilteringPlugIn.mm in Sources */,
                                A13EBBAB1B87434600097110 /* PlatformUtilitiesCocoa.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ClickAlternativePresentationButton.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ClickAlternativePresentationButton.mm
new file mode 100644 (file)
index 0000000..821829e
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 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 "config.h"
+
+#if WK_API_ENABLED
+
+#import <WebKit/WKBundleNodeHandlePrivate.h>
+#import <WebKit/WKBundlePage.h>
+#import <WebKit/WKBundlePageUIClient.h>
+#import <WebKit/WKDOMDocument.h>
+#import <WebKit/WKDOMElement.h>
+#import <WebKit/WKDOMNodePrivate.h>
+#import <WebKit/WKDOMText.h>
+#import <WebKit/WKString.h>
+#import <WebKit/WKWebProcessPlugIn.h>
+#import <WebKit/WKWebProcessPlugInBrowserContextControllerPrivate.h>
+#import <WebKit/WKWebProcessPlugInFrame.h>
+#import <WebKit/WKWebProcessPlugInFramePrivate.h>
+#import <WebKit/WKWebProcessPlugInNodeHandle.h>
+#import <WebKit/WKWebProcessPlugInScriptWorld.h>
+#import <wtf/RetainPtr.h>
+
+void didClickAlternativePresentationButton(WKBundlePageRef, WKBundleNodeHandleRef, WKTypeRef* userData, const void *)
+{
+    *userData = WKStringCreateWithUTF8CString("user data string");
+}
+
+@interface ClickAlternativePresentationButton : NSObject <WKWebProcessPlugIn>
+@end
+
+@implementation ClickAlternativePresentationButton
+
+- (void)webProcessPlugIn:(WKWebProcessPlugInController *)plugInController didCreateBrowserContextController:(WKWebProcessPlugInBrowserContextController *)browserContextController
+{
+    WKBundlePageUIClientV4 client;
+    memset(&client, 0, sizeof(client));
+    client.base.version = 4;
+    client.didClickAlternativePresentationButton = didClickAlternativePresentationButton;
+    WKBundlePageSetUIClient([browserContextController _bundlePageRef], &client.base);
+
+    WKDOMDocument *document = [browserContextController mainFrameDocument];
+    WKDOMElement *inputElement = [document createElement:@"input"];
+    [[document body] appendChild:inputElement];
+
+    auto *jsContext = [[browserContextController mainFrame] jsContextForWorld:[WKWebProcessPlugInScriptWorld normalWorld]];
+    auto *jsValue = [jsContext evaluateScript:@"document.querySelector('input')"];
+
+    RetainPtr<NSArray<WKWebProcessPlugInNodeHandle *>> elements = @[[WKWebProcessPlugInNodeHandle nodeHandleWithJSValue:jsValue inContext:jsContext]];
+    [[browserContextController mainFrame] substituteElements:elements.get() withAlternativePresentationButtonWithIdentifier:@"1"];
+
+    [jsContext evaluateScript:@"alert('ready for click!')"];
+}
+
+@end
+
+#endif // WK_API_ENABLED
index 5a67d3f..8ea1a4a 100644 (file)
@@ -523,6 +523,43 @@ TEST(WebKit, ClickAutoFillButton)
     [webView mouseDownAtPoint:buttonLocation simulatePressure:NO];
     [webView mouseUpAtPoint:buttonLocation];
     TestWebKitAPI::Util::run(&done);
+    readyForClick = false;
+}
+
+@interface AlternativePresentationButtonDelegate : NSObject <WKUIDelegatePrivate>
+@end
+
+@implementation AlternativePresentationButtonDelegate
+
+- (void)_webView:(WKWebView *)webView didClickAlternativePresentationButtonWithUserInfo:(id <NSSecureCoding>)userInfo
+{
+    ASSERT_TRUE([(id<NSObject>)userInfo isKindOfClass:[NSString class]]);
+    ASSERT_STREQ([(NSString*)userInfo UTF8String], "user data string");
+    done = true;
+}
+
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
+{
+    completionHandler();
+    ASSERT_STREQ(message.UTF8String, "ready for click!");
+    readyForClick = true;
+}
+
+@end
+
+TEST(WebKit, ClickAlternativePresentationButton)
+{
+    WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"ClickAlternativePresentationButton"];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration]);
+    auto delegate = adoptNS([[AlternativePresentationButtonDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+    TestWebKitAPI::Util::run(&readyForClick);
+    NSPoint buttonLocation = NSMakePoint(130, 575);
+    [webView mouseDownAtPoint:buttonLocation simulatePressure:NO];
+    [webView mouseUpAtPoint:buttonLocation];
+    TestWebKitAPI::Util::run(&done);
+    readyForClick = false;
 }
 
 @interface AutoFillAvailableDelegate : NSObject <WKUIDelegatePrivate>