Diff
Modified: trunk/Source/WebKit2/ChangeLog (219387 => 219388)
--- trunk/Source/WebKit2/ChangeLog 2017-07-12 10:12:39 UTC (rev 219387)
+++ trunk/Source/WebKit2/ChangeLog 2017-07-12 10:56:22 UTC (rev 219388)
@@ -1,3 +1,17 @@
+2017-07-12 Carlos Garcia Campos <[email protected]>
+
+ Web Automation: upstream safaridriver's _javascript_ atom implementations
+ https://bugs.webkit.org/show_bug.cgi?id=172060
+ <rdar://problem/32168187>
+
+ Reviewed by Brian Burg.
+
+ * UIProcess/Automation/atoms/ElementAttribute.js: Added.
+ * UIProcess/Automation/atoms/ElementDisplayed.js: Added.
+ * UIProcess/Automation/atoms/FindNodes.js: Added.
+ * UIProcess/Automation/atoms/FormElementClear.js: Added.
+ * UIProcess/Automation/atoms/FormSubmit.js: Added.
+
2017-07-11 Dean Jackson <[email protected]>
Remove NAVIGATOR_HWCONCURRENCY
Added: trunk/Source/WebKit2/UIProcess/Automation/atoms/ElementAttribute.js (0 => 219388)
--- trunk/Source/WebKit2/UIProcess/Automation/atoms/ElementAttribute.js (rev 0)
+++ trunk/Source/WebKit2/UIProcess/Automation/atoms/ElementAttribute.js 2017-07-12 10:56:22 UTC (rev 219388)
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+function(element, attributeName) {
+ "use strict";
+
+ let lowercaseAttributeName = attributeName.toLowerCase();
+ let tagName = element.tagName.toUpperCase();
+
+ // Special Case: "selected" / "checked" work analogously
+ // on options and checkboxes.
+ if (lowercaseAttributeName === "selected" || lowercaseAttributeName === "checked") {
+ switch (tagName) {
+ case "OPTION":
+ return element.selected ? "true" : null;
+ case "INPUT":
+ if (element.type === "checkbox" || element.type === "radio")
+ return element.checked ? "true" : null;
+ break;
+ }
+ }
+
+ // Special Case: For images and links, require the existence of
+ // the attribute before accessing the property.
+ if ((tagName === "IMG" && lowercaseAttributeName === "src") || (tagName === "A" && lowercaseAttributeName === "href")) {
+ let value = element.getAttribute(lowercaseAttributeName);
+ return value ? element[lowercaseAttributeName] : value;
+ }
+
+ // Prefer the element's property over the attribute if the
+ // property value is not null and not an object. For boolean
+ // properties return null if false.
+ try {
+ const propertyNameAliases = {"class": "className", "readonly": "readOnly"};
+ let propertyName = propertyNameAliases[attributeName] || attributeName;
+ let value = element[propertyName];
+ if (value != null && typeof value !== "object") {
+ if (typeof value === "boolean")
+ return value ? "true" : null;
+ return value.toString();
+ }
+ } catch (e) { }
+
+ return element.getAttribute(attributeName);
+}
Added: trunk/Source/WebKit2/UIProcess/Automation/atoms/ElementDisplayed.js (0 => 219388)
--- trunk/Source/WebKit2/UIProcess/Automation/atoms/ElementDisplayed.js (rev 0)
+++ trunk/Source/WebKit2/UIProcess/Automation/atoms/ElementDisplayed.js 2017-07-12 10:56:22 UTC (rev 219388)
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ */
+
+function isShown(element) {
+ "use strict";
+
+ function nodeIsElement(node) {
+ if (!node)
+ return false;
+
+ switch (node.nodeType) {
+ case Node.ELEMENT_NODE:
+ case Node.DOCUMENT_NODE:
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ function parentElementForElement(element) {
+ if (!element)
+ return null;
+
+ return enclosingNodeOrSelfMatchingPredicate(element.parentNode, nodeIsElement);
+ }
+
+ function enclosingNodeOrSelfMatchingPredicate(targetNode, predicate) {
+ for (let node = targetNode; node && node !== targetNode.ownerDocument; node = node.parentNode)
+ if (predicate(node))
+ return node;
+
+ return null;
+ }
+
+ function enclosingElementOrSelfMatchingPredicate(targetElement, predicate) {
+ for (let element = targetElement; element && element !== targetElement.ownerDocument; element = parentElementForElement(element))
+ if (predicate(element))
+ return element;
+
+ return null;
+ }
+
+ function cascadedStylePropertyForElement(element, property) {
+ if (!element || !property)
+ return null;
+
+ let computedStyle = window.getComputedStyle(element);
+ let computedStyleProperty = computedStyle.getPropertyValue(property);
+ if (computedStyleProperty && computedStyleProperty !== "inherit")
+ return computedStyleProperty;
+
+ // Ideally getPropertyValue would return the 'used' or 'actual' value, but
+ // it doesn't for legacy reasons. So we need to do our own poor man's cascade.
+ // Fall back to the first non-'inherit' value found in an ancestor.
+ // In any case, getPropertyValue will not return 'initial'.
+
+ // FIXME: will this incorrectly inherit non-inheritable CSS properties?
+ // I think all important non-inheritable properties (width, height, etc.)
+ // for our purposes here are specially resolved, so this may not be an issue.
+ // Specification is here: https://drafts.csswg.org/cssom/#resolved-values
+ let parentElement = parentElementForElement(element);
+ return cascadedStylePropertyForElement(parentElement, property);
+ }
+
+ function elementSubtreeHasNonZeroDimensions(element) {
+ let boundingBox = element.getBoundingClientRect();
+ if (boundingBox.width > 0 && boundingBox.height > 0)
+ return true;
+
+ // Paths can have a zero width or height. Treat them as shown if the stroke width is positive.
+ if (element.tagName.toUpperCase() === "PATH" && boundingBox.width + boundingBox.height > 0) {
+ let strokeWidth = cascadedStylePropertyForElement(element, "stroke-width");
+ return !!strokeWidth && (parseInt(strokeWidth, 10) > 0);
+ }
+
+ let cascadedOverflow = cascadedStylePropertyForElement(element, "overflow");
+ if (cascadedOverflow === "hidden")
+ return false;
+
+ // If the container's overflow is not hidden and it has zero size, consider the
+ // container to have non-zero dimensions if a child node has non-zero dimensions.
+ return Array.from(element.childNodes).some((childNode) => {
+ if (childNode.nodeType === Node.TEXT_NODE)
+ return true;
+
+ if (nodeIsElement(childNode))
+ return elementSubtreeHasNonZeroDimensions(childNode);
+
+ return false;
+ });
+ }
+
+ function elementOverflowsContainer(element) {
+ let cascadedOverflow = cascadedStylePropertyForElement(element, "overflow");
+ if (cascadedOverflow !== "hidden")
+ return false;
+
+ // FIXME: this needs to take into account the scroll position of the element,
+ // the display modes of it and its ancestors, and the container it overflows.
+ // See Selenium's bot.dom.getOverflowState atom for an exhaustive list of edge cases.
+ return true;
+ }
+
+ function isElementSubtreeHiddenByOverflow(element) {
+ if (!element)
+ return false;
+
+ if (!elementOverflowsContainer(element))
+ return false;
+
+ // This element's subtree is hidden by overflow if all child subtrees are as well.
+ return Array.from(element.childNodes).every((childNode) => {
+ // Returns true if the child node is overflowed or otherwise hidden.
+ // Base case: not an element, has zero size, scrolled out, or doesn't overflow container.
+ if (!nodeIsElement(childNode))
+ return true;
+
+ if (!elementSubtreeHasNonZeroDimensions(childNode))
+ return true;
+
+ // Recurse.
+ return isElementSubtreeHiddenByOverflow(childNode);
+ });
+ }
+
+ // This is a partial reimplementation of Selenium's "element is displayed" algorithm.
+ // When the W3C specification's algorithm stabilizes, we should implement that.
+
+ if (!(element instanceof Element))
+ throw new Error("Cannot check the displayedness of a non-Element argument.");
+
+ // If this command is misdirected to the wrong document, treat it as not shown.
+ if (!document.contains(element))
+ return false;
+
+ // Special cases for specific tag names.
+ switch (element.tagName.toUpperCase()) {
+ case "BODY":
+ return true;
+
+ case "SCRIPT":
+ case "NOSCRIPT":
+ return false;
+
+ case "OPTGROUP":
+ case "OPTION":
+ // Option/optgroup are considered shown if the containing <select> is shown.
+ let enclosingSelectElement = enclosingNodeOrSelfMatchingPredicate(element, (e) => e.tagName.toUpperCase() === "SELECT");
+ return isShown(enclosingSelectElement);
+
+ case "INPUT":
+ // <input type="hidden"> is considered not shown.
+ if (element.type === "hidden")
+ return false;
+ break;
+
+ case "MAP":
+ // FIXME: Selenium has special handling for <map> elements. We don't do anything now.
+
+ default:
+ break;
+ }
+
+ if (cascadedStylePropertyForElement(element, "visibility") !== "visible")
+ return false;
+
+ let hasAncestorWithZeroOpacity = !!enclosingElementOrSelfMatchingPredicate(element, (e) => {
+ return Number(cascadedStylePropertyForElement(e, "opacity")) === 0;
+ });
+ let hasAncestorWithDisplayNone = !!enclosingElementOrSelfMatchingPredicate(element, (e) => {
+ return cascadedStylePropertyForElement(e, "display") === "none";
+ });
+ if (hasAncestorWithZeroOpacity || hasAncestorWithDisplayNone)
+ return false;
+
+ if (!elementSubtreeHasNonZeroDimensions(element))
+ return false;
+
+ if (isElementSubtreeHiddenByOverflow(element))
+ return false;
+
+ return true;
+}
Added: trunk/Source/WebKit2/UIProcess/Automation/atoms/FindNodes.js (0 => 219388)
--- trunk/Source/WebKit2/UIProcess/Automation/atoms/FindNodes.js (rev 0)
+++ trunk/Source/WebKit2/UIProcess/Automation/atoms/FindNodes.js 2017-07-12 10:56:22 UTC (rev 219388)
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+function(strategy, ancestorElement, query, firstResultOnly, timeoutDuration, callback) {
+ ancestorElement = ancestorElement || document;
+
+ switch (strategy) {
+ case "id":
+ strategy = "css selector";
+ query = "[id=\"" + escape(query) + "\"]";
+ break;
+ case "name":
+ strategy = "css selector";
+ query = "[name=\"" + escape(query) + "\"]";
+ break;
+ case "link text":
+ strategy = "xpath";
+ query = ".//a[@href][text() = \"" + escape(query) + "\"]";
+ break;
+ case "partial link text":
+ strategy = "xpath";
+ query = ".//a[@href][contains(text(), \"" + escape(query) + "\")]";
+ break;
+ }
+
+ switch (strategy) {
+ case "css selector":
+ case "tag name":
+ case "class name":
+ case "xpath":
+ break;
+ default:
+ // Unknown strategy.
+ callback(firstResultOnly ? null : []);
+ return;
+ }
+
+ function escape(string) {
+ return string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
+ }
+
+ function tryToFindNode() {
+ try {
+ switch (strategy) {
+ case "css selector":
+ if (firstResultOnly)
+ return ancestorElement.querySelector(query) || null;
+ return Array.from(ancestorElement.querySelectorAll(query));
+
+ case "tag name":
+ let tagNameResult = ancestorElement.getElementsByTagName(query);
+ if (firstResultOnly)
+ return tagNameResult[0] || null;
+ return Array.from(tagNameResult);
+
+ case "class name":
+ let classNameResult = ancestorElement.getElementsByClassName(query);
+ if (firstResultOnly)
+ return classNameResult[0] || null;
+ return Array.from(classNameResult);
+
+ case "xpath":
+ if (firstResultOnly) {
+ let xpathResult = document.evaluate(query, ancestorElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
+ if (!xpathResult)
+ return null;
+ return xpathResult.singleNodeValue;
+ }
+
+ let xpathResult = document.evaluate(query, ancestorElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ if (!xpathResult || !xpathResult.snapshotLength)
+ return [];
+
+ let arrayResult = [];
+ for (let i = 0; i < xpathResult.snapshotLength; ++i)
+ arrayResult.push(xpathResult.snapshotItem(i));
+ return arrayResult;
+ }
+ } catch (error) {
+ if (error instanceof XPathException && error.code === XPathException.INVALID_EXPRESSION_ERR)
+ return "InvalidXPathExpression";
+ // FIXME: Bad CSS can throw an error that we should report back to the endpoint. There is no
+ // special error code for that though, so we just return an empty match.
+ return firstResultOnly ? null : [];
+ }
+ }
+
+ const pollInterval = 50;
+ let pollUntil = performance.now() + timeoutDuration;
+
+ function pollForNode() {
+ let result = tryToFindNode();
+
+ // Report any valid results.
+ if (typeof result === "string" || result instanceof Node || (result instanceof Array && result.length)) {
+ callback(result);
+ return;
+ }
+
+ // Schedule another attempt if we have time remaining.
+ let durationRemaining = pollUntil - performance.now();
+ if (durationRemaining < pollInterval) {
+ callback(firstResultOnly ? null : []);
+ return;
+ }
+
+ setTimeout(pollForNode, pollInterval);
+ }
+
+ pollForNode();
+}
Added: trunk/Source/WebKit2/UIProcess/Automation/atoms/FormElementClear.js (0 => 219388)
--- trunk/Source/WebKit2/UIProcess/Automation/atoms/FormElementClear.js (rev 0)
+++ trunk/Source/WebKit2/UIProcess/Automation/atoms/FormElementClear.js 2017-07-12 10:56:22 UTC (rev 219388)
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+function(element) {
+ "use strict";
+
+ if (element.disabled || element.readOnly)
+ throw {name: "InvalidElementState", message: "Element must be user-editable in order to clear."};
+
+ if (element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
+ element.value = "";
+ return;
+ }
+
+ if (element instanceof HTMLInputElement) {
+ switch (element.type) {
+ case "button":
+ case "submit":
+ case "reset":
+ return;
+
+ case "radio":
+ case "checkbox":
+ element.checked = false;
+ return;
+
+ default:
+ element.value = "";
+ return;
+ }
+ }
+
+ if (element.isContentEditable) {
+ element.innerHTML = "";
+ return;
+ }
+}
Added: trunk/Source/WebKit2/UIProcess/Automation/atoms/FormSubmit.js (0 => 219388)
--- trunk/Source/WebKit2/UIProcess/Automation/atoms/FormSubmit.js (rev 0)
+++ trunk/Source/WebKit2/UIProcess/Automation/atoms/FormSubmit.js 2017-07-12 10:56:22 UTC (rev 219388)
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+function(element) {
+ for (let currentElement = element; currentElement && currentElement !== document; currentElement = currentElement.parentNode) {
+ if (!(currentElement instanceof HTMLFormElement))
+ continue;
+ currentElement.submit();
+ break;
+ }
+}