Bug 1274274 - Decouple element retrieval methods; r?automatedtester
Moves element retrieval methods from ElementManager to the
testing/marionette/element.js module itself. This means some more
work needs to be done by the caller, but avoids bloat by ensuring
ElementManager does not end up as a super-object.
MozReview-Commit-ID: 5LGe0vpSWwS
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -33,16 +33,26 @@ Cu.import("chrome://marionette/content/s
this.EXPORTED_SYMBOLS = ["GeckoDriver", "Context"];
var FRAME_SCRIPT = "chrome://marionette/content/listener.js";
const BROWSER_STARTUP_FINISHED = "browser-delayed-startup-finished";
const CLICK_TO_START_PREF = "marionette.debugging.clicktostart";
const CONTENT_LISTENER_PREF = "marionette.contentListener";
+const SUPPORTED_STRATEGIES = new Set([
+ element.Strategy.ClassName,
+ element.Strategy.Selector,
+ element.Strategy.ID,
+ element.Strategy.TagName,
+ element.Strategy.XPath,
+ element.Strategy.Anon,
+ element.Strategy.AnonAttribute,
+]);
+
const logger = Log.repository.getLogger("Marionette");
const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
// This is used to prevent newSession from returning before the telephony
// API's are ready; see bug 792647. This assumes that marionette-server.js
// will be loaded before the 'system-message-listener-ready' message
// is fired. If this stops being true, this approach will have to change.
@@ -1349,17 +1359,17 @@ GeckoDriver.prototype.switchToWindow = f
GeckoDriver.prototype.getActiveFrame = function(cmd, resp) {
switch (this.context) {
case Context.CHROME:
// no frame means top-level
resp.body.value = null;
if (this.curFrame) {
resp.body.value = this.curBrowser.elementManager
- .addToKnownElements(this.curFrame.frameElement);
+ .add(this.curFrame.frameElement);
}
break;
case Context.CONTENT:
resp.body.value = this.currentFrameElement;
break;
}
};
@@ -1650,64 +1660,82 @@ GeckoDriver.prototype.multiAction = func
* Find an element using the indicated search strategy.
*
* @param {string} using
* Indicates which search method to use.
* @param {string} value
* Value the client is looking for.
*/
GeckoDriver.prototype.findElement = function*(cmd, resp) {
+ let strategy = cmd.parameters.using;
+ let expr = cmd.parameters.value;
let opts = {
startNode: cmd.parameters.element,
timeout: this.searchTimeout,
all: false,
};
switch (this.context) {
case Context.CHROME:
+ if (!SUPPORTED_STRATEGIES.has(strategy)) {
+ throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
+ }
+
let container = {frame: this.getCurrentWindow()};
- resp.body.value = yield this.curBrowser.elementManager.find(
- container,
- cmd.parameters.using,
- cmd.parameters.value,
- opts);
+ if (opts.startNode) {
+ opts.startNode = this.curBrowser.elementManager.getKnownElement(opts.startNode, container);
+ }
+ let el = yield element.find(container, strategy, expr, opts);
+ let elRef = this.curBrowser.elementManager.add(el);
+ let webEl = element.makeWebElement(elRef);
+
+ resp.body.value = webEl;
break;
case Context.CONTENT:
resp.body.value = yield this.listener.findElementContent(
- cmd.parameters.using,
- cmd.parameters.value,
+ strategy,
+ expr,
opts);
break;
}
};
/**
* Find elements using the indicated search strategy.
*
* @param {string} using
* Indicates which search method to use.
* @param {string} value
* Value the client is looking for.
*/
GeckoDriver.prototype.findElements = function*(cmd, resp) {
+ let strategy = cmd.parameters.using;
+ let expr = cmd.parameters.value;
let opts = {
startNode: cmd.parameters.element,
timeout: this.searchTimeout,
all: true,
};
switch (this.context) {
case Context.CHROME:
+ if (!SUPPORTED_STRATEGIES.has(strategy)) {
+ throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
+ }
+
let container = {frame: this.getCurrentWindow()};
- resp.body = yield this.curBrowser.elementManager.find(
- container,
- cmd.parameters.using,
- cmd.parameters.value,
- opts);
+ if (opts.startNode) {
+ opts.startNode = this.curBrowser.elementManager.getKnownElement(opts.startNode, container);
+ }
+ let els = yield element.find(container, strategy, expr, opts);
+
+ let elRefs = this.curBrowser.elementManager.addAll(els);
+ let webEls = elRefs.map(element.makeWebElement);
+ resp.body = webEls;
break;
case Context.CONTENT:
resp.body = yield this.listener.findElementsContent(
cmd.parameters.using,
cmd.parameters.value,
opts);
break;
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -53,60 +53,74 @@ element.Strategy = {
Name: "name",
LinkText: "link text",
PartialLinkText: "partial link text",
TagName: "tag name",
XPath: "xpath",
Anon: "anon",
AnonAttribute: "anon attribute",
};
-element.Strategies = new Set(Object.values(element.Strategy));
-this.ElementManager = function ElementManager(unsupportedStrategies = []) {
+this.ElementManager = function ElementManager() {
this.seenItems = {};
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
- this.supportedStrategies = new Set(element.Strategies);
- for (let s of unsupportedStrategies) {
- this.supportedStrategies.delete(s);
- }
};
ElementManager.prototype = {
/**
* Reset values
*/
reset: function EM_clear() {
this.seenItems = {};
},
/**
- * Add element to list of seen elements
- *
- * @param nsIDOMElement element
- * The element to add
+ * Make a collection of elements seen.
+ *
+ * The oder of the returned web element references is guaranteed to
+ * match that of the collection passed in.
+ *
+ * @param {NodeList} els
+ * Sequence of elements to add to set of seen elements.
+ *
+ * @return {Array.<WebElement>}
+ * List of the web element references associated with each element
+ * from |els|.
+ */
+ addAll: function(els) {
+ let add = this.add.bind(this);
+ return [...els].map(add);
+ },
+
+ /**
+ * Make an element seen.
*
- * @return string
- * Returns the server-assigned reference ID
+ * @param {nsIDOMElement} el
+ * Element to add to set of seen elements.
+ *
+ * @return {string}
+ * Web element reference associated with element.
*/
- addToKnownElements: function EM_addToKnownElements(el) {
+ add: function(el) {
for (let i in this.seenItems) {
- let foundEl = null;
+ let foundEl;
try {
foundEl = this.seenItems[i].get();
} catch (e) {}
+
if (foundEl) {
if (XPCNativeWrapper(foundEl) == XPCNativeWrapper(el)) {
return i;
}
} else {
// cleanup reference to GC'd element
delete this.seenItems[i];
}
}
+
let id = element.generateUUID();
this.seenItems[id] = Cu.getWeakReference(el);
return id;
},
/**
* Retrieve element from its unique ID
*
@@ -216,17 +230,17 @@ ElementManager.prototype = {
result.push(this.wrapValue(val[i]));
}
}
else if (val == null) {
result = null;
}
else if (val.nodeType == 1) {
- let elementId = this.addToKnownElements(val);
+ let elementId = this.add(val);
result = {[element.LegacyKey]: elementId, [element.Key]: elementId};
}
else {
result = {};
for (let prop in val) {
try {
result[prop] = this.wrapValue(val[prop]);
} catch (e if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED)) {
@@ -284,342 +298,325 @@ ElementManager.prototype = {
for (let prop in args) {
converted[prop] = this.convertWrappedArguments(args[prop], container);
}
}
break;
}
return converted;
},
+};
- /**
- * Find a single element or a collection of elements starting at the
- * document root or a given node.
- *
- * If |timeout| is above 0, an implicit search technique is used.
- * This will wait for the duration of |timeout| for the element
- * to appear in the DOM.
- *
- * See the |element.Strategy| enum for a full list of supported
- * search strategies that can be passed to |strategy|.
- *
- * Available flags for |opts|:
- *
- * |all|
- * If true, a multi-element search selector is used and a sequence
- * of elements will be returned. Otherwise a single element.
- *
- * |timeout|
- * Duration to wait before timing out the search. If |all| is
- * false, a NoSuchElementError is thrown if unable to find
- * the element within the timeout duration.
- *
- * |startNode|
- * Element to use as the root of the search.
- *
- * @param {Object.<string, Window>} container
- * Window object and an optional shadow root that contains the
- * root shadow DOM element.
- * @param {string} strategy
- * Search strategy whereby to locate the element(s).
- * @param {string} selector
- * Selector search pattern. The selector must be compatible with
- * the chosen search |strategy|.
- * @param {Object.<string, ?>} opts
- * Options.
- *
- * @return {Promise: (WebElement|Array<WebElement>)}
- * Single element or a sequence of elements.
- *
- * @throws InvalidSelectorError
- * If |strategy| is unknown.
- * @throws InvalidSelectorError
- * If |selector| is malformed.
- * @throws NoSuchElementError
- * If a single element is requested, this error will throw if the
- * element is not found.
- */
- find: function(container, strategy, selector, opts = {}) {
- opts.all = !!opts.all;
- opts.timeout = opts.timeout || 0;
+/**
+ * Find a single element or a collection of elements starting at the
+ * document root or a given node.
+ *
+ * If |timeout| is above 0, an implicit search technique is used.
+ * This will wait for the duration of |timeout| for the element
+ * to appear in the DOM.
+ *
+ * See the |element.Strategy| enum for a full list of supported
+ * search strategies that can be passed to |strategy|.
+ *
+ * Available flags for |opts|:
+ *
+ * |all|
+ * If true, a multi-element search selector is used and a sequence
+ * of elements will be returned. Otherwise a single element.
+ *
+ * |timeout|
+ * Duration to wait before timing out the search. If |all| is
+ * false, a NoSuchElementError is thrown if unable to find
+ * the element within the timeout duration.
+ *
+ * |startNode|
+ * Element to use as the root of the search.
+ *
+ * @param {Object.<string, Window>} container
+ * Window object and an optional shadow root that contains the
+ * root shadow DOM element.
+ * @param {string} strategy
+ * Search strategy whereby to locate the element(s).
+ * @param {string} selector
+ * Selector search pattern. The selector must be compatible with
+ * the chosen search |strategy|.
+ * @param {Object.<string, ?>} opts
+ * Options.
+ *
+ * @return {Promise: (nsIDOMElement|Array<nsIDOMElement>)}
+ * Single element or a sequence of elements.
+ *
+ * @throws InvalidSelectorError
+ * If |strategy| is unknown.
+ * @throws InvalidSelectorError
+ * If |selector| is malformed.
+ * @throws NoSuchElementError
+ * If a single element is requested, this error will throw if the
+ * element is not found.
+ */
+element.find = function(container, strategy, selector, opts = {}) {
+ opts.all = !!opts.all;
+ opts.timeout = opts.timeout || 0;
- let searchFn;
- if (opts.all) {
- searchFn = this.findElements.bind(this);
- } else {
- searchFn = this.findElement.bind(this);
- }
+ let searchFn;
+ if (opts.all) {
+ searchFn = findElements.bind(this);
+ } else {
+ searchFn = findElement.bind(this);
+ }
- return new Promise((resolve, reject) => {
- let findElements = implicitlyWaitFor(
- () => this.find_(container, strategy, selector, searchFn, opts),
- opts.timeout);
+ return new Promise((resolve, reject) => {
+ let findElements = implicitlyWaitFor(
+ () => find_(container, strategy, selector, searchFn, opts),
+ opts.timeout);
- findElements.then(foundEls => {
- // the following code ought to be moved into findElement
- // and findElements when bug 1254486 is addressed
- if (!opts.all && (!foundEls || foundEls.length == 0)) {
- let msg;
- switch (strategy) {
- case element.Strategy.AnonAttribute:
- msg = "Unable to locate anonymous element: " + JSON.stringify(selector);
- break;
+ findElements.then(foundEls => {
+ // the following code ought to be moved into findElement
+ // and findElements when bug 1254486 is addressed
+ if (!opts.all && (!foundEls || foundEls.length == 0)) {
+ let msg;
+ switch (strategy) {
+ case element.Strategy.AnonAttribute:
+ msg = "Unable to locate anonymous element: " + JSON.stringify(selector);
+ break;
- default:
- msg = "Unable to locate element: " + selector;
- }
-
- reject(new NoSuchElementError(msg));
- }
-
- // serialise elements for return
- let rv = [];
- for (let el of foundEls) {
- let ref = this.addToKnownElements(el);
- let we = element.makeWebElement(ref);
- rv.push(we);
+ default:
+ msg = "Unable to locate element: " + selector;
}
- if (opts.all) {
- resolve(rv);
- }
- resolve(rv[0]);
- }, reject);
- });
- },
+ reject(new NoSuchElementError(msg));
+ }
+
+ if (opts.all) {
+ resolve(foundEls);
+ }
+ resolve(foundEls[0]);
+ }, reject);
+ });
+};
- find_: function(container, strategy, selector, searchFn, opts) {
- let rootNode = container.shadowRoot || container.frame.document;
- let startNode;
- if (opts.startNode) {
- startNode = this.getKnownElement(opts.startNode, container);
- } else {
- startNode = rootNode;
- }
+function find_(container, strategy, selector, searchFn, opts) {
+ let rootNode = container.shadowRoot || container.frame.document;
+ let startNode = opts.startNode || rootNode;
- if (!this.supportedStrategies.has(strategy)) {
- throw new InvalidSelectorError("Strategy not supported: " + strategy);
- }
+ let res;
+ try {
+ res = searchFn(strategy, selector, rootNode, startNode);
+ } catch (e) {
+ throw new InvalidSelectorError(
+ `Given ${strategy} expression "${selector}" is invalid: ${e}`);
+ }
- let res;
- try {
- res = searchFn(strategy, selector, rootNode, startNode);
- } catch (e) {
- throw new InvalidSelectorError(
- `Given ${strategy} expression "${selector}" is invalid`);
- }
-
- if (element.isElementCollection(res)) {
- return res;
- } else if (res) {
- return [res];
- }
- return [];
- },
+ if (element.isElementCollection(res)) {
+ return res;
+ } else if (res) {
+ return [res];
+ }
+ return [];
+}
- /**
- * Find a value by XPATH
- *
- * @param nsIDOMElement root
- * Document root
- * @param string value
- * XPATH search string
- * @param nsIDOMElement node
- * start node
- *
- * @return nsIDOMElement
- * returns the found element
- */
- findByXPath: function EM_findByXPath(root, value, node) {
- return root.evaluate(value, node, null,
- Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
- },
+/**
+ * Find a value by XPATH
+ *
+ * @param nsIDOMElement root
+ * Document root
+ * @param string value
+ * XPATH search string
+ * @param nsIDOMElement node
+ * start node
+ *
+ * @return nsIDOMElement
+ * returns the found element
+ */
+function findByXPath(root, value, node) {
+ return root.evaluate(value, node, null,
+ Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+}
- /**
- * Find values by XPATH
- *
- * @param nsIDOMElement root
- * Document root
- * @param string value
- * XPATH search string
- * @param nsIDOMElement node
- * start node
- *
- * @return object
- * returns a list of found nsIDOMElements
- */
- findByXPathAll: function EM_findByXPathAll(root, value, node) {
- let values = root.evaluate(value, node, null,
- Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
- let elements = [];
- let element = values.iterateNext();
- while (element) {
- elements.push(element);
- element = values.iterateNext();
- }
- return elements;
- },
+/**
+ * Find values by XPATH
+ *
+ * @param nsIDOMElement root
+ * Document root
+ * @param string value
+ * XPATH search string
+ * @param nsIDOMElement node
+ * start node
+ *
+ * @return object
+ * returns a list of found nsIDOMElements
+ */
+function findByXPathAll(root, value, node) {
+ let values = root.evaluate(value, node, null,
+ Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
+ let elements = [];
+ let element = values.iterateNext();
+ while (element) {
+ elements.push(element);
+ element = values.iterateNext();
+ }
+ return elements;
+}
- /**
- * Finds a single element.
- *
- * @param {element.Strategy} using
- * Selector strategy to use.
- * @param {string} value
- * Selector expression.
- * @param {DOMElement} rootNode
- * Document root.
- * @param {DOMElement=} startNode
- * Optional node from which to start searching.
- *
- * @return {DOMElement}
- * Found elements.
- *
- * @throws {InvalidSelectorError}
- * If strategy |using| is not recognised.
- * @throws {Error}
- * If selector expression |value| is malformed.
- */
- findElement: function(using, value, rootNode, startNode) {
- switch (using) {
- case element.Strategy.ID:
- if (startNode.getElementById) {
- return startNode.getElementById(value);
- }
- return this.findByXPath(rootNode, `.//*[@id="${value}"]`, startNode);
+/**
+ * Finds a single element.
+ *
+ * @param {element.Strategy} using
+ * Selector strategy to use.
+ * @param {string} value
+ * Selector expression.
+ * @param {DOMElement} rootNode
+ * Document root.
+ * @param {DOMElement=} startNode
+ * Optional node from which to start searching.
+ *
+ * @return {DOMElement}
+ * Found elements.
+ *
+ * @throws {InvalidSelectorError}
+ * If strategy |using| is not recognised.
+ * @throws {Error}
+ * If selector expression |value| is malformed.
+ */
+function findElement(using, value, rootNode, startNode) {
+ switch (using) {
+ case element.Strategy.ID:
+ if (startNode.getElementById) {
+ return startNode.getElementById(value);
+ }
+ return findByXPath(rootNode, `.//*[@id="${value}"]`, startNode);
- case element.Strategy.Name:
- if (startNode.getElementsByName) {
- return startNode.getElementsByName(value)[0];
- }
- return this.findByXPath(rootNode, `.//*[@name="${value}"]`, startNode);
+ case element.Strategy.Name:
+ if (startNode.getElementsByName) {
+ return startNode.getElementsByName(value)[0];
+ }
+ return findByXPath(rootNode, `.//*[@name="${value}"]`, startNode);
- case element.Strategy.ClassName:
- // works for >= Firefox 3
- return startNode.getElementsByClassName(value)[0];
-
- case element.Strategy.TagName:
- // works for all elements
- return startNode.getElementsByTagName(value)[0];
+ case element.Strategy.ClassName:
+ // works for >= Firefox 3
+ return startNode.getElementsByClassName(value)[0];
- case element.Strategy.XPath:
- return this.findByXPath(rootNode, value, startNode);
+ case element.Strategy.TagName:
+ // works for all elements
+ return startNode.getElementsByTagName(value)[0];
+
+ case element.Strategy.XPath:
+ return findByXPath(rootNode, value, startNode);
- // TODO(ato): Rewrite this, it's hairy:
- case element.Strategy.LinkText:
- case element.Strategy.PartialLinkText:
- let el;
- let allLinks = startNode.getElementsByTagName("A");
- for (let i = 0; i < allLinks.length && !el; i++) {
- let text = allLinks[i].text;
- if (using == element.Strategy.PartialLinkText) {
- if (text.indexOf(value) != -1) {
- el = allLinks[i];
- }
- } else if (text == value) {
+ // TODO(ato): Rewrite this, it's hairy:
+ case element.Strategy.LinkText:
+ case element.Strategy.PartialLinkText:
+ let el;
+ let allLinks = startNode.getElementsByTagName("A");
+ for (let i = 0; i < allLinks.length && !el; i++) {
+ let text = allLinks[i].text;
+ if (using == element.Strategy.PartialLinkText) {
+ if (text.indexOf(value) != -1) {
el = allLinks[i];
}
+ } else if (text == value) {
+ el = allLinks[i];
}
- return el;
-
- case element.Strategy.Selector:
- try {
- return startNode.querySelector(value);
- } catch (e) {
- throw new InvalidSelectorError(`${e.message}: "${value}"`);
- }
+ }
+ return el;
- case element.Strategy.Anon:
- return rootNode.getAnonymousNodes(startNode);
+ case element.Strategy.Selector:
+ try {
+ return startNode.querySelector(value);
+ } catch (e) {
+ throw new InvalidSelectorError(`${e.message}: "${value}"`);
+ }
- case element.Strategy.AnonAttribute:
- let attr = Object.keys(value)[0];
- return rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
+ case element.Strategy.Anon:
+ return rootNode.getAnonymousNodes(startNode);
- default:
- throw new InvalidSelectorError(`No such strategy: ${using}`);
- }
-},
+ case element.Strategy.AnonAttribute:
+ let attr = Object.keys(value)[0];
+ return rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
+
+ default:
+ throw new InvalidSelectorError(`No such strategy: ${using}`);
+ }
+};
- /**
- * Find multiple elements.
- *
- * @param {element.Strategy} using
- * Selector strategy to use.
- * @param {string} value
- * Selector expression.
- * @param {DOMElement} rootNode
- * Document root.
- * @param {DOMElement=} startNode
- * Optional node from which to start searching.
- *
- * @return {DOMElement}
- * Found elements.
- *
- * @throws {InvalidSelectorError}
- * If strategy |using| is not recognised.
- * @throws {Error}
- * If selector expression |value| is malformed.
- */
- findElements: function(using, value, rootNode, startNode) {
- switch (using) {
- case element.Strategy.ID:
- value = `.//*[@id="${value}"]`;
+/**
+ * Find multiple elements.
+ *
+ * @param {element.Strategy} using
+ * Selector strategy to use.
+ * @param {string} value
+ * Selector expression.
+ * @param {DOMElement} rootNode
+ * Document root.
+ * @param {DOMElement=} startNode
+ * Optional node from which to start searching.
+ *
+ * @return {DOMElement}
+ * Found elements.
+ *
+ * @throws {InvalidSelectorError}
+ * If strategy |using| is not recognised.
+ * @throws {Error}
+ * If selector expression |value| is malformed.
+ */
+function findElements(using, value, rootNode, startNode) {
+ switch (using) {
+ case element.Strategy.ID:
+ value = `.//*[@id="${value}"]`;
- // fall through
- case element.Strategy.XPath:
- return this.findByXPathAll(rootNode, value, startNode);
+ // fall through
+ case element.Strategy.XPath:
+ return findByXPathAll(rootNode, value, startNode);
- case element.Strategy.Name:
- if (startNode.getElementsByName) {
- return startNode.getElementsByName(value);
- }
- return this.findByXPathAll(rootNode, `.//*[@name="${value}"]`, startNode);
-
- case element.Strategy.ClassName:
- return startNode.getElementsByClassName(value);
+ case element.Strategy.Name:
+ if (startNode.getElementsByName) {
+ return startNode.getElementsByName(value);
+ }
+ return findByXPathAll(rootNode, `.//*[@name="${value}"]`, startNode);
- case element.Strategy.TagName:
- return startNode.getElementsByTagName(value);
+ case element.Strategy.ClassName:
+ return startNode.getElementsByClassName(value);
+
+ case element.Strategy.TagName:
+ return startNode.getElementsByTagName(value);
- case element.Strategy.LinkText:
- case element.Strategy.PartialLinkText:
- let els = [];
- let allLinks = startNode.getElementsByTagName("A");
- for (let i = 0; i < allLinks.length; i++) {
- let text = allLinks[i].text;
- if (using == element.Strategy.PartialLinkText) {
- if (text.indexOf(value) != -1) {
- els.push(allLinks[i]);
- }
- } else if (text == value) {
+ case element.Strategy.LinkText:
+ case element.Strategy.PartialLinkText:
+ let els = [];
+ let allLinks = startNode.getElementsByTagName("A");
+ for (let i = 0; i < allLinks.length; i++) {
+ let text = allLinks[i].text;
+ if (using == element.Strategy.PartialLinkText) {
+ if (text.indexOf(value) != -1) {
els.push(allLinks[i]);
}
+ } else if (text == value) {
+ els.push(allLinks[i]);
}
- return els;
+ }
+ return els;
- case element.Strategy.Selector:
- return Array.slice(startNode.querySelectorAll(value));
+ case element.Strategy.Selector:
+ return startNode.querySelectorAll(value);
- case element.Strategy.Anon:
- return rootNode.getAnonymousNodes(startNode);
+ case element.Strategy.Anon:
+ return rootNode.getAnonymousNodes(startNode);
- case element.Strategy.AnonAttribute:
- let attr = Object.keys(value)[0];
- let el = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
- if (el) {
- return [el];
- }
- return [];
+ case element.Strategy.AnonAttribute:
+ let attr = Object.keys(value)[0];
+ let el = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
+ if (el) {
+ return [el];
+ }
+ return [];
- default:
- throw new InvalidSelectorError(`No such strategy: ${using}`);
- }
- },
-};
+ default:
+ throw new InvalidSelectorError(`No such strategy: ${using}`);
+ }
+}
/**
* Runs function off the main thread until its return value is truthy
* or the provided timeout is reached. The function is guaranteed to be
* run at least once, irregardless of the timeout.
*
* A truthy return value constitutes a truthful boolean, positive number,
* object, or non-empty array.
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -37,17 +37,29 @@ var isB2G = false;
var marionetteTestName;
var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
var listenerId = null; // unique ID of this listener
var curContainer = { frame: content, shadowRoot: null };
var isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
var previousContainer = null;
+
var elementManager = new ElementManager();
+var SUPPORTED_STRATEGIES = new Set([
+ element.Strategy.ClassName,
+ element.Strategy.Selector,
+ element.Strategy.ID,
+ element.Strategy.Name,
+ element.Strategy.LinkText,
+ element.Strategy.PartialLinkText,
+ element.Strategy.TagName,
+ element.Strategy.XPath,
+]);
+
var capabilities = {};
var actions = new action.Chain(checkForInterrupted);
// Contains the last file input element that was the target of
// sendKeysToElement.
var fileInputElement;
@@ -1007,47 +1019,65 @@ function refresh(msg) {
};
addEventListener("DOMContentLoaded", listen, false);
}
/**
* Find an element in the current browsing context's document using the
* given search strategy.
*/
-function findElementContent(strategy, selector, opts = {}) {
+function* findElementContent(strategy, selector, opts = {}) {
+ if (!SUPPORTED_STRATEGIES.has(strategy)) {
+ throw new InvalidSelectorError("Strategy not supported: " + strategy);
+ }
+
opts.all = false;
- return elementManager.find(
- curContainer,
- strategy,
- selector,
- opts);
+ if (opts.startNode) {
+ opts.startNode = elementManager.getKnownElement(opts.startNode, curContainer);
+ }
+
+ let el = yield element.find(curContainer, strategy, selector, opts);
+ let elRef = elementManager.add(el);
+ let webEl = element.makeWebElement(elRef);
+ return webEl;
}
/**
* Find elements in the current browsing context's document using the
* given search strategy.
*/
-function findElementsContent(strategy, selector, opts = {}) {
+function* findElementsContent(strategy, selector, opts = {}) {
+ if (!SUPPORTED_STRATEGIES.has(strategy)) {
+ throw new InvalidSelectorError("Strategy not supported: " + strategy);
+ }
+
opts.all = true;
- return elementManager.find(
- curContainer,
- strategy,
- selector,
- opts);
+ if (opts.startNode) {
+ opts.startNode = elementManager.getKnownElement(opts.startNode, curContainer);
+ }
+
+ let els = yield element.find(curContainer, strategy, selector, opts);
+ let elRefs = elementManager.addAll(els);
+ let webEls = elRefs.map(element.makeWebElement);
+ return webEls;
}
/**
* Find and return the active element on the page.
*
* @return {WebElement}
* Reference to web element.
*/
function getActiveElement() {
let el = curContainer.frame.document.activeElement;
- return elementManager.addToKnownElements(el);
+ let elRef = elementManager.add(el);
+ // TODO(ato): This incorrectly returns
+ // the element's associated UUID as a string
+ // instead of a web element.
+ return elRef;
}
/**
* Send click event to element.
*
* @param {WebElement} id
* Reference to the web element to click.
*/
@@ -1267,17 +1297,17 @@ function switchToShadowRoot(id) {
/**
* Switch to the parent frame of the current Frame. If the frame is the top most
* is the current frame then no action will happen.
*/
function switchToParentFrame(msg) {
let command_id = msg.json.command_id;
curContainer.frame = curContainer.frame.parent;
- let parentElement = elementManager.addToKnownElements(curContainer.frame);
+ let parentElement = elementManager.add(curContainer.frame);
sendSyncMessage("Marionette:switchedToFrame", { frameValue: parentElement });
sendOk(msg.json.command_id);
}
/**
* Switch to frame given either the server-assigned element id,
@@ -1360,17 +1390,17 @@ function switchToFrame(msg) {
}
}
if (foundFrame === null) {
if (typeof(msg.json.id) === 'number') {
try {
foundFrame = frames[msg.json.id].frameElement;
if (foundFrame !== null) {
curContainer.frame = foundFrame;
- foundFrame = elementManager.addToKnownElements(curContainer.frame);
+ foundFrame = elementManager.add(curContainer.frame);
}
else {
// If foundFrame is null at this point then we have the top level browsing
// context so should treat it accordingly.
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null});
curContainer.frame = content;
if(msg.json.focus == true) {
curContainer.frame.focus();