Bug 741598 - Enable finding elements of child nodes, r=jgriffin

This commit is contained in:
Malini Das 2012-04-03 11:08:49 -07:00
parent 3d76ad5939
commit bb96b17e38
3 changed files with 118 additions and 66 deletions

View File

@ -112,7 +112,6 @@ function MarionetteDriverActor(aConnection)
this.curBrowser = null; // points to current browser this.curBrowser = null; // points to current browser
this.context = "content"; this.context = "content";
this.scriptTimeout = null; this.scriptTimeout = null;
this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
this.timer = null; this.timer = null;
this.marionetteLog = new MarionetteLogObj(); this.marionetteLog = new MarionetteLogObj();
this.command_id = null; this.command_id = null;
@ -141,7 +140,7 @@ MarionetteDriverActor.prototype = {
* Object to send to the listener * Object to send to the listener
*/ */
sendAsync: function MDA_sendAsync(name, values) { sendAsync: function MDA_sendAsync(name, values) {
this.messageManager.sendAsyncMessage("Marionette:" + name + this.browsers[this.curBrowser].curFrameId, values); this.messageManager.sendAsyncMessage("Marionette:" + name + this.curBrowser.curFrameId, values);
}, },
/** /**
@ -249,9 +248,11 @@ MarionetteDriverActor.prototype = {
let winId = win.QueryInterface(Ci.nsIInterfaceRequestor). let winId = win.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils).outerWindowID; getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
winId = winId + ((appName == "B2G") ? '-b2g' : ''); winId = winId + ((appName == "B2G") ? '-b2g' : '');
if (this.elementManager.seenItems[winId] == undefined) { this.browsers[winId] = browser;
this.curBrowser = this.browsers[winId];
if (this.curBrowser.elementManager.seenItems[winId] == undefined) {
//add this to seenItems so we can guarantee the user will get winId as this window's id //add this to seenItems so we can guarantee the user will get winId as this window's id
this.elementManager.seenItems[winId] = win; this.curBrowser.elementManager.seenItems[winId] = win;
} }
this.browsers[winId] = browser; this.browsers[winId] = browser;
return winId; return winId;
@ -270,11 +271,10 @@ MarionetteDriverActor.prototype = {
* True if this is the first time we're talking to this browser * True if this is the first time we're talking to this browser
*/ */
startBrowser: function MDA_startBrowser(win, newSession) { startBrowser: function MDA_startBrowser(win, newSession) {
let winId = this.addBrowser(win); this.addBrowser(win);
this.curBrowser = winId; this.curBrowser.newSession = newSession;
this.browsers[this.curBrowser].newSession = newSession; this.curBrowser.startSession(newSession);
this.browsers[this.curBrowser].startSession(newSession); this.curBrowser.loadFrameScript("chrome://marionette/content/marionette-listener.js", win);
this.browsers[this.curBrowser].loadFrameScript("chrome://marionette/content/marionette-listener.js", win);
}, },
/** /**
@ -292,11 +292,10 @@ MarionetteDriverActor.prototype = {
if (!prefs.getBoolPref("marionette.contentListener")) { if (!prefs.getBoolPref("marionette.contentListener")) {
this.startBrowser(this.getCurrentWindow(), true); this.startBrowser(this.getCurrentWindow(), true);
} }
else if ((appName == "B2G")&& (this.curBrowser == null)) { else if ((appName == "B2G") && (this.curBrowser == null)) {
//if there is a content listener, then we just wake it up //if there is a content listener, then we just wake it up
let winId = this.addBrowser(this.getCurrentWindow()); this.addBrowser(this.getCurrentWindow());
this.curBrowser = winId; this.curBrowser.startSession(false);
this.browsers[this.curBrowser].startSession(false);
this.messageManager.sendAsyncMessage("Marionette:restart", {}); this.messageManager.sendAsyncMessage("Marionette:restart", {});
} }
else { else {
@ -354,7 +353,7 @@ MarionetteDriverActor.prototype = {
*/ */
createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args) { createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args) {
try { try {
args = this.elementManager.convertWrappedArguments(args, aWindow); args = this.curBrowser.elementManager.convertWrappedArguments(args, aWindow);
} }
catch(e) { catch(e) {
this.sendError(e.message, e.num, e.stack); this.sendError(e.message, e.num, e.stack);
@ -363,7 +362,7 @@ MarionetteDriverActor.prototype = {
let _chromeSandbox = new Cu.Sandbox(aWindow, let _chromeSandbox = new Cu.Sandbox(aWindow,
{ sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''}); { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''});
_chromeSandbox.__namedArgs = this.elementManager.applyNamedArgs(args); _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args);
_chromeSandbox.__marionetteParams = args; _chromeSandbox.__marionetteParams = args;
marionette.exports.forEach(function(fn) { marionette.exports.forEach(function(fn) {
@ -405,7 +404,7 @@ MarionetteDriverActor.prototype = {
} }
if (!async) { if (!async) {
this.sendResponse(this.elementManager.wrapValue(res)); this.sendResponse(this.curBrowser.elementManager.wrapValue(res));
} }
} }
catch (e) { catch (e) {
@ -542,7 +541,7 @@ MarionetteDriverActor.prototype = {
curWindow.onerror = original_onerror; curWindow.onerror = original_onerror;
if (status == 0 || status == undefined) { if (status == 0 || status == undefined) {
that.sendToClient({from: that.actorID, value: that.elementManager.wrapValue(value), status: status}, that.sendToClient({from: that.actorID, value: that.curBrowser.elementManager.wrapValue(value), status: status},
marionette.command_id); marionette.command_id);
} }
else { else {
@ -642,7 +641,11 @@ MarionetteDriverActor.prototype = {
* Get the current window's server-assigned ID * Get the current window's server-assigned ID
*/ */
getWindow: function MDA_getWindow() { getWindow: function MDA_getWindow() {
this.sendResponse(this.curBrowser); for (let i in this.browsers) {
if (this.curBrowser == this.browsers[i]) {
this.sendResponse(i);
}
}
}, },
/** /**
@ -679,14 +682,14 @@ MarionetteDriverActor.prototype = {
this.startBrowser(foundWin, false); this.startBrowser(foundWin, false);
} }
foundWin.focus(); foundWin.focus();
this.curBrowser = winId; this.curBrowser = this.browsers[winId];
this.sendOk(); this.sendOk();
return; return;
} }
} }
this.sendError("Unable to locate window " + aRequest.value, 23, null); this.sendError("Unable to locate window " + aRequest.value, 23, null);
}, },
/** /**
* Switch to a given frame within the current window * Switch to a given frame within the current window
* *
@ -706,7 +709,7 @@ MarionetteDriverActor.prototype = {
setSearchTimeout: function MDA_setSearchTimeout(aRequest) { setSearchTimeout: function MDA_setSearchTimeout(aRequest) {
if (this.context == "chrome") { if (this.context == "chrome") {
try { try {
this.elementManager.setSearchTimeout(aRequest.value); this.curBrowser.elementManager.setSearchTimeout(aRequest.value);
this.sendOk(); this.sendOk();
} }
catch (e) { catch (e) {
@ -730,7 +733,7 @@ MarionetteDriverActor.prototype = {
let id; let id;
try { try {
let notify = this.sendResponse.bind(this); let notify = this.sendResponse.bind(this);
id = this.elementManager.find(aRequest, this.getCurrentWindow().document, notify, false); id = this.curBrowser.elementManager.find(this.getCurrentWindow(),aRequest, notify, false);
} }
catch (e) { catch (e) {
this.sendError(e.message, e.num, e.stack); this.sendError(e.message, e.num, e.stack);
@ -754,7 +757,7 @@ MarionetteDriverActor.prototype = {
let id; let id;
try { try {
let notify = this.sendResponse.bind(this); let notify = this.sendResponse.bind(this);
id = this.elementManager.find(aRequest, this.getCurrentWindow().document, notify, true); id = this.curBrowser.elementManager.find(this.getCurrentWindow(), aRequest, notify, true);
} }
catch (e) { catch (e) {
this.sendError(e.message, e.num, e.stack); this.sendError(e.message, e.num, e.stack);
@ -787,16 +790,16 @@ MarionetteDriverActor.prototype = {
* and can safely be reused. * and can safely be reused.
*/ */
deleteSession: function MDA_deleteSession() { deleteSession: function MDA_deleteSession() {
if (this.browsers[this.curBrowser] != null) { if (this.curBrowser != null) {
if (appName == "B2G") { if (appName == "B2G") {
this.messageManager.sendAsyncMessage("Marionette:sleepSession" + this.browsers[this.curBrowser].mainContentId, {}); this.messageManager.sendAsyncMessage("Marionette:sleepSession" + this.curBrowser.mainContentId, {});
this.browsers[this.curBrowser].knownFrames.splice(this.browsers[this.curBrowser].knownFrames.indexOf(this.browsers[this.curBrowser].mainContentId), 1); this.curBrowser.knownFrames.splice(this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1);
} }
else { else {
//don't set this pref for B2G since the framescript can be safely reused //don't set this pref for B2G since the framescript can be safely reused
prefs.setBoolPref("marionette.contentListener", false); prefs.setBoolPref("marionette.contentListener", false);
} }
this.browsers[this.curBrowser].closeTab(); this.curBrowser.closeTab();
//delete session in each frame in each browser //delete session in each frame in each browser
for (let win in this.browsers) { for (let win in this.browsers) {
for (let i in this.browsers[win].knownFrames) { for (let i in this.browsers[win].knownFrames) {
@ -817,7 +820,6 @@ MarionetteDriverActor.prototype = {
this.messageManager.removeMessageListener("Marionette:register", this); this.messageManager.removeMessageListener("Marionette:register", this);
this.messageManager.removeMessageListener("Marionette:goUrl", this); this.messageManager.removeMessageListener("Marionette:goUrl", this);
this.curBrowser = null; this.curBrowser = null;
this.elementManager.reset();
}, },
/** /**
@ -849,15 +851,15 @@ MarionetteDriverActor.prototype = {
case "Marionette:register": case "Marionette:register":
// This code processes the content listener's registration information // This code processes the content listener's registration information
// and either accepts the listener, or ignores it // and either accepts the listener, or ignores it
let nullPrevious= (this.browsers[this.curBrowser].curFrameId == null); let nullPrevious = (this.curBrowser.curFrameId == null);
let curWin = this.getCurrentWindow(); let curWin = this.getCurrentWindow();
let frameObject = curWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils).getOuterWindowWithId(message.json.value); let frameObject = curWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils).getOuterWindowWithId(message.json.value);
let reg = this.browsers[this.curBrowser].register(message.json.value, message.json.href); let reg = this.curBrowser.register(message.json.value, message.json.href);
if (reg) { if (reg) {
this.elementManager.seenItems[reg] = frameObject; //add to seenItems this.curBrowser.elementManager.seenItems[reg] = frameObject; //add to seenItems
if (nullPrevious && (this.browsers[this.curBrowser].curFrameId != null)) { if (nullPrevious && (this.curBrowser.curFrameId != null)) {
this.sendAsync("newSession", {B2G: (appName == "B2G")}); this.sendAsync("newSession", {B2G: (appName == "B2G")});
if (this.browsers[this.curBrowser].newSession) { if (this.curBrowser.newSession) {
this.sendResponse(reg); this.sendResponse(reg);
} }
} }
@ -866,7 +868,7 @@ MarionetteDriverActor.prototype = {
case "Marionette:goUrl": case "Marionette:goUrl":
// if content determines that the goUrl call is directed at a top level window (not an iframe) // if content determines that the goUrl call is directed at a top level window (not an iframe)
// it calls back into chrome to load the uri. // it calls back into chrome to load the uri.
this.browsers[this.curBrowser].loadURI(message.json.value, this); this.curBrowser.loadURI(message.json.value, this);
break; break;
} }
}, },
@ -876,7 +878,7 @@ MarionetteDriverActor.prototype = {
handleEvent: function MDA_handleEvent(evt) { handleEvent: function MDA_handleEvent(evt) {
if (evt.type == "DOMContentLoaded") { if (evt.type == "DOMContentLoaded") {
this.sendOk(); this.sendOk();
this.browsers[this.curBrowser].browser.removeEventListener("DOMContentLoaded", this, false); this.curBrowser.browser.removeEventListener("DOMContentLoaded", this, false);
} }
}, },
}; };
@ -927,6 +929,7 @@ function BrowserObj(win) {
this.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]. this.messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
getService(Ci.nsIChromeFrameMessageManager); getService(Ci.nsIChromeFrameMessageManager);
this.newSession = true; //used to set curFrameId upon new session this.newSession = true; //used to set curFrameId upon new session
this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
this.setBrowser(win); this.setBrowser(win);
} }

View File

@ -125,7 +125,6 @@ ElementManager.prototype = {
else if (val == null) { else if (val == null) {
result = null; result = null;
} }
// nodeType 1 == 'element'
else if (val.nodeType == 1) { else if (val.nodeType == 1) {
for(let i in this.seenItems) { for(let i in this.seenItems) {
if (this.seenItems[i] == val) { if (this.seenItems[i] == val) {
@ -222,18 +221,20 @@ ElementManager.prototype = {
}, },
/** /**
* Find an element or elements starting at the document root * Find an element or elements starting at the document root or
* using the given search strategy. Search * given node, using the given search strategy. Search
* will continue until the search timelimit has been reached. * will continue until the search timelimit has been reached.
* *
* @param nsIDOMWindow win
* The window to search in
* @param object values * @param object values
* The 'using' member of values will tell us which search * The 'using' member of values will tell us which search
* method to use. The 'value' member tells us the value we * method to use. The 'value' member tells us the value we
* are looking for. * are looking for.
* If this object has an 'element' member, this will be used
* as the start node instead of the document root
* If this object has a 'time' member, this number will be * If this object has a 'time' member, this number will be
* used to see if we have hit the search timelimit. * used to see if we have hit the search timelimit.
* @param nsIDOMElement rootNode
* The document root
* @param function notify * @param function notify
* The notification callback used when we are returning * The notification callback used when we are returning
* @param boolean all * @param boolean all
@ -243,12 +244,13 @@ ElementManager.prototype = {
* @return nsIDOMElement or list of nsIDOMElements * @return nsIDOMElement or list of nsIDOMElements
* Returns the element(s) by calling the notify function. * Returns the element(s) by calling the notify function.
*/ */
find: function EM_find(values, rootNode, notify, all) { find: function EM_find(win, values, notify, all) {
let startTime = values.time ? values.time : new Date().getTime(); let startTime = values.time ? values.time : new Date().getTime();
let startNode = (values.element != undefined) ? this.getKnownElement(values.element, win) : win.document;
if (this.elementStrategies.indexOf(values.using) < 0) { if (this.elementStrategies.indexOf(values.using) < 0) {
throw new ElementException("No such strategy.", 17, null); throw new ElementException("No such strategy.", 17, null);
} }
let found = all ? this.findElements(values.using, values.value, rootNode) : this.findElement(values.using, values.value, rootNode); let found = all ? this.findElements(values.using, values.value, win.document, startNode) : this.findElement(values.using, values.value, win.document, startNode);
if (found) { if (found) {
let type = Object.prototype.toString.call(found); let type = Object.prototype.toString.call(found);
if ((type == '[object Array]') || (type == '[object HTMLCollection]')) { if ((type == '[object Array]') || (type == '[object HTMLCollection]')) {
@ -268,10 +270,53 @@ ElementManager.prototype = {
throw new ElementException("Unable to locate element: " + values.value, 7, null); throw new ElementException("Unable to locate element: " + values.value, 7, null);
} else { } else {
values.time = startTime; values.time = startTime;
this.timer.initWithCallback(this.find.bind(this, values, rootNode, notify, all), 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT); this.timer.initWithCallback(this.find.bind(this, win, values, notify, all), 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
} }
} }
}, },
/**
* 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,
Components.interfaces.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,
Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
let elements = [];
let element = values.iterateNext();
while (element) {
elements.push(element);
element = values.iterateNext();
}
return elements;
},
/** /**
* Helper method to find. Finds one element using find's criteria * Helper method to find. Finds one element using find's criteria
@ -282,33 +327,37 @@ ElementManager.prototype = {
* Value to look for * Value to look for
* @param nsIDOMElement rootNode * @param nsIDOMElement rootNode
* Document root * Document root
* @param nsIDOMElement startNode
* Node from which we start searching
* *
* @return nsIDOMElement * @return nsIDOMElement
* Returns found element or throws Exception if not found * Returns found element or throws Exception if not found
*/ */
findElement: function EM_findElement(using, value, rootNode) { findElement: function EM_findElement(using, value, rootNode, startNode) {
let element; let element;
switch (using) { switch (using) {
case ID: case ID:
element = rootNode.getElementById(value); element = startNode.getElementById ?
startNode.getElementById(value) :
this.findByXPath(rootNode, './/*[@id="' + value + '"]', startNode);
break; break;
case NAME: case NAME:
element = rootNode.getElementsByName(value)[0]; element = startNode.getElementsByName ?
startNode.getElementsByName(value)[0] :
this.findByXPath(rootNode, './/*[@name="' + value + '"]', startNode);
break; break;
case CLASS_NAME: case CLASS_NAME:
element = rootNode.getElementsByClassName(value)[0]; element = startNode.getElementsByClassName(value)[0]; //works for >=FF3
break; break;
case TAG: case TAG:
element = rootNode.getElementsByTagName(value)[0]; element = startNode.getElementsByTagName(value)[0]; //works for all elements
break; break;
case XPATH: case XPATH:
element = rootNode.evaluate(value, rootNode, null, element = this.findByXPath(rootNode, value, startNode);
Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).
singleNodeValue;
break; break;
case LINK_TEXT: case LINK_TEXT:
case PARTIAL_LINK_TEXT: case PARTIAL_LINK_TEXT:
let allLinks = rootNode.getElementsByTagName('A'); let allLinks = startNode.getElementsByTagName('A');
for (let i = 0; i < allLinks.length && !element; i++) { for (let i = 0; i < allLinks.length && !element; i++) {
let text = allLinks[i].text; let text = allLinks[i].text;
if (PARTIAL_LINK_TEXT == using) { if (PARTIAL_LINK_TEXT == using) {
@ -321,7 +370,7 @@ ElementManager.prototype = {
} }
break; break;
case SELECTOR: case SELECTOR:
element = rootNode.querySelector(value); element = startNode.querySelector(value);
break; break;
default: default:
throw new ElementException("No such strategy", 500, null); throw new ElementException("No such strategy", 500, null);
@ -338,32 +387,30 @@ ElementManager.prototype = {
* Value to look for * Value to look for
* @param nsIDOMElement rootNode * @param nsIDOMElement rootNode
* Document root * Document root
* @param nsIDOMElement startNode
* Node from which we start searching
* *
* @return nsIDOMElement * @return nsIDOMElement
* Returns found elements or throws Exception if not found * Returns found elements or throws Exception if not found
*/ */
findElements: function EM_findElements(using, value, rootNode) { findElements: function EM_findElements(using, value, rootNode, startNode) {
let elements = []; let elements = [];
switch (using) { switch (using) {
case ID: case ID:
value = './/*[@id="' + value + '"]'; value = './/*[@id="' + value + '"]';
case XPATH: case XPATH:
values = rootNode.evaluate(value, rootNode, null, elements = this.findByXPathAll(rootNode, value, startNode);
Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null)
let element = values.iterateNext();
while (element) {
elements.push(element);
element = values.iterateNext();
}
break; break;
case NAME: case NAME:
elements = rootNode.getElementsByName(value); element = startNode.getElementsByName ?
startNode.getElementsByName(value)[0] :
this.findByXPathAll(rootNode, './/*[@name="' + value + '"]', startNode);
break; break;
case CLASS_NAME: case CLASS_NAME:
elements = rootNode.getElementsByClassName(value); elements = startNode.getElementsByClassName(value);
break; break;
case TAG: case TAG:
elements = rootNode.getElementsByTagName(value); elements = startNode.getElementsByTagName(value);
break; break;
case LINK_TEXT: case LINK_TEXT:
case PARTIAL_LINK_TEXT: case PARTIAL_LINK_TEXT:

View File

@ -425,7 +425,8 @@ function findElementContent(msg) {
let id; let id;
try { try {
let notify = function(id) { sendResponse({value:id});}; let notify = function(id) { sendResponse({value:id});};
id = elementManager.find(msg.json, win.document, notify, false); let curWin = activeFrame ? win.frames[activeFrame] : win;
id = elementManager.find(curWin, msg.json, notify, false);
} }
catch (e) { catch (e) {
sendError(e.message, e.num, e.stack); sendError(e.message, e.num, e.stack);
@ -441,7 +442,8 @@ function findElementsContent(msg) {
let id; let id;
try { try {
let notify = function(id) { sendResponse({value:id});}; let notify = function(id) { sendResponse({value:id});};
id = elementManager.find(msg.json, win.document, notify, true); let curWin = activeFrame ? win.frames[activeFrame] : win;
id = elementManager.find(curWin, msg.json, notify, true);
} }
catch (e) { catch (e) {
sendError(e.message, e.num, e.stack); sendError(e.message, e.num, e.stack);