blob: e36a14fb47f2c476b3ec2ac78fb630dcf83c9ec5 [file] [log] [blame]
/*
* 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;
}
switch (strategy) {
case "css selector":
case "link text":
case "partial link text":
case "tag name":
case "class name":
case "xpath":
break;
default:
// §12.2 Find Element and §12.3 Find Elements, step 4: If location strategy is not present as a keyword
// in the table of location strategies, return error with error code invalid argument.
// https://www.w3.org/TR/webdriver/#find-element
throw { name: "InvalidParameter", message: ("Unsupported locator strategy: " + strategy + ".") };
}
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 "link text":
let linkTextResult = [];
for (let link of ancestorElement.getElementsByTagName("a")) {
if (link.text.trim() == query) {
linkTextResult.push(link);
if (firstResultOnly)
break;
}
}
if (firstResultOnly)
return linkTextResult[0] || null;
return linkTextResult;
case "partial link text":
let partialLinkResult = [];
for (let link of ancestorElement.getElementsByTagName("a")) {
if (link.text.includes(query)) {
partialLinkResult.push(link);
if (firstResultOnly)
break;
}
}
if (firstResultOnly)
return partialLinkResult[0] || null;
return partialLinkResult;
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) {
// §12. Element Retrieval. Step 6: If a DOMException, SyntaxError, or other error occurs during
// the execution of the element location strategy, return error invalid selector.
// https://www.w3.org/TR/webdriver/#dfn-find
throw { name: "InvalidSelector", message: error.message };
}
}
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();
}