diff --git a/testing/marionette/marionette-actors.js b/testing/marionette/marionette-actors.js index 34316f74c4e8..f397a0872a75 100644 --- a/testing/marionette/marionette-actors.js +++ b/testing/marionette/marionette-actors.js @@ -112,7 +112,6 @@ function MarionetteDriverActor(aConnection) this.curBrowser = null; // points to current browser this.context = "content"; this.scriptTimeout = null; - this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]); this.timer = null; this.marionetteLog = new MarionetteLogObj(); this.command_id = null; @@ -141,7 +140,7 @@ MarionetteDriverActor.prototype = { * Object to send to the listener */ 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). getInterface(Ci.nsIDOMWindowUtils).outerWindowID; 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 - this.elementManager.seenItems[winId] = win; + this.curBrowser.elementManager.seenItems[winId] = win; } this.browsers[winId] = browser; return winId; @@ -270,11 +271,10 @@ MarionetteDriverActor.prototype = { * True if this is the first time we're talking to this browser */ startBrowser: function MDA_startBrowser(win, newSession) { - let winId = this.addBrowser(win); - this.curBrowser = winId; - this.browsers[this.curBrowser].newSession = newSession; - this.browsers[this.curBrowser].startSession(newSession); - this.browsers[this.curBrowser].loadFrameScript("chrome://marionette/content/marionette-listener.js", win); + this.addBrowser(win); + this.curBrowser.newSession = newSession; + this.curBrowser.startSession(newSession); + this.curBrowser.loadFrameScript("chrome://marionette/content/marionette-listener.js", win); }, /** @@ -292,11 +292,10 @@ MarionetteDriverActor.prototype = { if (!prefs.getBoolPref("marionette.contentListener")) { 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 - let winId = this.addBrowser(this.getCurrentWindow()); - this.curBrowser = winId; - this.browsers[this.curBrowser].startSession(false); + this.addBrowser(this.getCurrentWindow()); + this.curBrowser.startSession(false); this.messageManager.sendAsyncMessage("Marionette:restart", {}); } else { @@ -354,7 +353,7 @@ MarionetteDriverActor.prototype = { */ createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args) { try { - args = this.elementManager.convertWrappedArguments(args, aWindow); + args = this.curBrowser.elementManager.convertWrappedArguments(args, aWindow); } catch(e) { this.sendError(e.message, e.num, e.stack); @@ -363,7 +362,7 @@ MarionetteDriverActor.prototype = { let _chromeSandbox = new Cu.Sandbox(aWindow, { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''}); - _chromeSandbox.__namedArgs = this.elementManager.applyNamedArgs(args); + _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args); _chromeSandbox.__marionetteParams = args; marionette.exports.forEach(function(fn) { @@ -405,7 +404,7 @@ MarionetteDriverActor.prototype = { } if (!async) { - this.sendResponse(this.elementManager.wrapValue(res)); + this.sendResponse(this.curBrowser.elementManager.wrapValue(res)); } } catch (e) { @@ -542,7 +541,7 @@ MarionetteDriverActor.prototype = { curWindow.onerror = original_onerror; 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); } else { @@ -642,7 +641,11 @@ MarionetteDriverActor.prototype = { * Get the current window's server-assigned ID */ 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); } foundWin.focus(); - this.curBrowser = winId; + this.curBrowser = this.browsers[winId]; this.sendOk(); return; } } this.sendError("Unable to locate window " + aRequest.value, 23, null); }, - + /** * Switch to a given frame within the current window * @@ -706,7 +709,7 @@ MarionetteDriverActor.prototype = { setSearchTimeout: function MDA_setSearchTimeout(aRequest) { if (this.context == "chrome") { try { - this.elementManager.setSearchTimeout(aRequest.value); + this.curBrowser.elementManager.setSearchTimeout(aRequest.value); this.sendOk(); } catch (e) { @@ -730,7 +733,7 @@ MarionetteDriverActor.prototype = { let id; try { 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) { this.sendError(e.message, e.num, e.stack); @@ -754,7 +757,7 @@ MarionetteDriverActor.prototype = { let id; try { 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) { this.sendError(e.message, e.num, e.stack); @@ -787,16 +790,16 @@ MarionetteDriverActor.prototype = { * and can safely be reused. */ deleteSession: function MDA_deleteSession() { - if (this.browsers[this.curBrowser] != null) { + if (this.curBrowser != null) { if (appName == "B2G") { - this.messageManager.sendAsyncMessage("Marionette:sleepSession" + this.browsers[this.curBrowser].mainContentId, {}); - this.browsers[this.curBrowser].knownFrames.splice(this.browsers[this.curBrowser].knownFrames.indexOf(this.browsers[this.curBrowser].mainContentId), 1); + this.messageManager.sendAsyncMessage("Marionette:sleepSession" + this.curBrowser.mainContentId, {}); + this.curBrowser.knownFrames.splice(this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1); } else { //don't set this pref for B2G since the framescript can be safely reused prefs.setBoolPref("marionette.contentListener", false); } - this.browsers[this.curBrowser].closeTab(); + this.curBrowser.closeTab(); //delete session in each frame in each browser for (let win in this.browsers) { for (let i in this.browsers[win].knownFrames) { @@ -817,7 +820,6 @@ MarionetteDriverActor.prototype = { this.messageManager.removeMessageListener("Marionette:register", this); this.messageManager.removeMessageListener("Marionette:goUrl", this); this.curBrowser = null; - this.elementManager.reset(); }, /** @@ -849,15 +851,15 @@ MarionetteDriverActor.prototype = { case "Marionette:register": // This code processes the content listener's registration information // 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 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) { - this.elementManager.seenItems[reg] = frameObject; //add to seenItems - if (nullPrevious && (this.browsers[this.curBrowser].curFrameId != null)) { + this.curBrowser.elementManager.seenItems[reg] = frameObject; //add to seenItems + if (nullPrevious && (this.curBrowser.curFrameId != null)) { this.sendAsync("newSession", {B2G: (appName == "B2G")}); - if (this.browsers[this.curBrowser].newSession) { + if (this.curBrowser.newSession) { this.sendResponse(reg); } } @@ -866,7 +868,7 @@ MarionetteDriverActor.prototype = { case "Marionette:goUrl": // 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. - this.browsers[this.curBrowser].loadURI(message.json.value, this); + this.curBrowser.loadURI(message.json.value, this); break; } }, @@ -876,7 +878,7 @@ MarionetteDriverActor.prototype = { handleEvent: function MDA_handleEvent(evt) { if (evt.type == "DOMContentLoaded") { 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"]. getService(Ci.nsIChromeFrameMessageManager); this.newSession = true; //used to set curFrameId upon new session + this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]); this.setBrowser(win); } diff --git a/testing/marionette/marionette-elements.js b/testing/marionette/marionette-elements.js index 49447fb0e7c3..dad0b66e9cdc 100644 --- a/testing/marionette/marionette-elements.js +++ b/testing/marionette/marionette-elements.js @@ -125,7 +125,6 @@ ElementManager.prototype = { else if (val == null) { result = null; } - // nodeType 1 == 'element' else if (val.nodeType == 1) { for(let i in this.seenItems) { if (this.seenItems[i] == val) { @@ -222,18 +221,20 @@ ElementManager.prototype = { }, /** - * Find an element or elements starting at the document root - * using the given search strategy. Search + * Find an element or elements starting at the document root or + * given node, using the given search strategy. Search * will continue until the search timelimit has been reached. * + * @param nsIDOMWindow win + * The window to search in * @param object values * The 'using' member of values will tell us which search * method to use. The 'value' member tells us the value we * 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 * used to see if we have hit the search timelimit. - * @param nsIDOMElement rootNode - * The document root * @param function notify * The notification callback used when we are returning * @param boolean all @@ -243,12 +244,13 @@ ElementManager.prototype = { * @return nsIDOMElement or list of nsIDOMElements * 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 startNode = (values.element != undefined) ? this.getKnownElement(values.element, win) : win.document; if (this.elementStrategies.indexOf(values.using) < 0) { 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) { let type = Object.prototype.toString.call(found); if ((type == '[object Array]') || (type == '[object HTMLCollection]')) { @@ -268,10 +270,53 @@ ElementManager.prototype = { throw new ElementException("Unable to locate element: " + values.value, 7, null); } else { 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 @@ -282,33 +327,37 @@ ElementManager.prototype = { * Value to look for * @param nsIDOMElement rootNode * Document root + * @param nsIDOMElement startNode + * Node from which we start searching * * @return nsIDOMElement * 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; switch (using) { case ID: - element = rootNode.getElementById(value); + element = startNode.getElementById ? + startNode.getElementById(value) : + this.findByXPath(rootNode, './/*[@id="' + value + '"]', startNode); break; case NAME: - element = rootNode.getElementsByName(value)[0]; + element = startNode.getElementsByName ? + startNode.getElementsByName(value)[0] : + this.findByXPath(rootNode, './/*[@name="' + value + '"]', startNode); break; case CLASS_NAME: - element = rootNode.getElementsByClassName(value)[0]; + element = startNode.getElementsByClassName(value)[0]; //works for >=FF3 break; case TAG: - element = rootNode.getElementsByTagName(value)[0]; + element = startNode.getElementsByTagName(value)[0]; //works for all elements break; case XPATH: - element = rootNode.evaluate(value, rootNode, null, - Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null). - singleNodeValue; + element = this.findByXPath(rootNode, value, startNode); break; case 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++) { let text = allLinks[i].text; if (PARTIAL_LINK_TEXT == using) { @@ -321,7 +370,7 @@ ElementManager.prototype = { } break; case SELECTOR: - element = rootNode.querySelector(value); + element = startNode.querySelector(value); break; default: throw new ElementException("No such strategy", 500, null); @@ -338,32 +387,30 @@ ElementManager.prototype = { * Value to look for * @param nsIDOMElement rootNode * Document root + * @param nsIDOMElement startNode + * Node from which we start searching * * @return nsIDOMElement * 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 = []; switch (using) { case ID: value = './/*[@id="' + value + '"]'; case XPATH: - values = rootNode.evaluate(value, rootNode, null, - Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null) - let element = values.iterateNext(); - while (element) { - elements.push(element); - element = values.iterateNext(); - } + elements = this.findByXPathAll(rootNode, value, startNode); break; case NAME: - elements = rootNode.getElementsByName(value); + element = startNode.getElementsByName ? + startNode.getElementsByName(value)[0] : + this.findByXPathAll(rootNode, './/*[@name="' + value + '"]', startNode); break; case CLASS_NAME: - elements = rootNode.getElementsByClassName(value); + elements = startNode.getElementsByClassName(value); break; case TAG: - elements = rootNode.getElementsByTagName(value); + elements = startNode.getElementsByTagName(value); break; case LINK_TEXT: case PARTIAL_LINK_TEXT: diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js index 5292e5101a0b..7f3c81e07d9f 100644 --- a/testing/marionette/marionette-listener.js +++ b/testing/marionette/marionette-listener.js @@ -425,7 +425,8 @@ function findElementContent(msg) { let id; try { 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) { sendError(e.message, e.num, e.stack); @@ -441,7 +442,8 @@ function findElementsContent(msg) { let id; try { 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) { sendError(e.message, e.num, e.stack);