1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 18:37:58 +01:00
Files
pentadactyl-pm/common/tests/functional/shared-modules/dom-utils.js
Kris Maglione d16c0b0d06 Back out most of the changes accidentally merged from key-processing.
--HG--
extra : rebase_source : a00510584f7e13917f8496e15b7dd36852d98ea7
2011-01-27 15:12:33 -05:00

686 lines
23 KiB
JavaScript

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <hskupin@mozilla.com>
* Adrian Kalla <akalla@aviary.pl>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Include required modules
var modalDialog = require("modal-dialog");
var utils = require("utils");
/**
* Unwraps a node which is wrapped into a XPCNativeWrapper or XrayWrapper
*
* @param {DOMnode} Wrapped DOM node
* @returns {DOMNode} Unwrapped DOM node
*/
function unwrapNode(aNode) {
var node = aNode;
if (node) {
// unwrap is not available on older branches (3.5 and 3.6) - Bug 533596
if ("unwrap" in XPCNativeWrapper) {
node = XPCNativeWrapper.unwrap(node);
}
else if ("wrappedJSObject" in node) {
node = node.wrappedJSObject;
}
}
return node;
}
/**
* DOMWalker Constructor
*
* @param {MozMillController} controller
* MozMill controller of the window to operate on.
* @param {Function} callbackFilter
* callback-method to filter nodes
* @param {Function} callbackNodeTest
* callback-method to test accepted nodes
* @param {Function} callbackResults
* callback-method to process the results
* [optional - default: undefined]
*/
function DOMWalker(controller, callbackFilter, callbackNodeTest,
callbackResults) {
this._controller = controller;
this._callbackFilter = callbackFilter;
this._callbackNodeTest = callbackNodeTest;
this._callbackResults = callbackResults;
}
DOMWalker.FILTER_ACCEPT = 1;
DOMWalker.FILTER_REJECT = 2;
DOMWalker.FILTER_SKIP = 3;
DOMWalker.GET_BY_ID = "id";
DOMWalker.GET_BY_SELECTOR = "selector";
DOMWalker.WINDOW_CURRENT = 1;
DOMWalker.WINDOW_MODAL = 2;
DOMWalker.WINDOW_NEW = 4;
DOMWalker.prototype = {
/**
* Returns the filter-callback
*
* @returns Function
*/
get callbackFilter() {
return this._callbackFilter;
},
/**
* Returns the node-testing-callback
*
* @returns Function
*/
get callbackNodeTest() {
return this._callbackNodeTest;
},
/**
* Returns the results-callback
*
* @returns Function
*/
get callbackResults() {
return this._callbackResults;
},
/**
* Returns the MozMill controller
*
* @returns Mozmill controller
* @type {MozMillController}
*/
get controller() {
return this._controller;
},
/**
* The main DOMWalker function.
*
* It start's the _walk-method for a given window or other dialog, runs
* a callback to process the results for that window/dialog.
* After that switches to provided new windows/dialogs.
*
* @param {array of objects} ids
* Contains informations on the elements to open while
* Object-elements: getBy - attribute-name of the attribute
* containing the identification
* information for the opener-element
* subContent - array of ids of the opener-elements
* in the window with the value of
* the above getBy-attribute
* target - information, where the new
* elements will be opened
* [1|2|4]
* title - title of the opened dialog/window
* waitFunction - The function used as an argument
* for MozmillController.waitFor to
* wait before starting the walk.
* [optional - default: no waiting]
* windowHandler - Window instance
* [only needed for some tests]
*
* @param {Node} root
* Node to start testing from
* [optional - default: this._controller.window.document.documentElement]
* @param {Function} waitFunction
* The function used as an argument for MozmillController.waitFor to
* wait before starting the walk.
* [optional - default: no waiting]
*/
walk : function DOMWalker_walk(ids, root, waitFunction) {
if (typeof waitFunction == 'function')
this._controller.waitFor(waitFunction());
if (!root)
root = this._controller.window.document.documentElement;
var resultsArray = this._walk(root);
if (typeof this._callbackResults == 'function')
this._callbackResults(this._controller, resultsArray);
if (ids)
this._prepareTargetWindows(ids);
},
/**
* DOMWalker_filter filters a given node by submitting it to the
* this._callbackFilter method to decide, if it should be submitted to
* a provided this._callbackNodeTest method for testing (that hapens in case
* of FILTER_ACCEPT).
* In case of FILTER_ACCEPT and FILTER_SKIP, the children of such a node
* will be filtered recursively.
* Nodes with the nodeStatus "FILTER_REJECT" and their descendants will be
* completetly ignored.
*
* @param {Node} node
* Node to filter
* @param {array of elements} collectedResults
* An array with gathered all results from testing a given element
* @returns An array with gathered all results from testing a given element
* @type {array of elements}
*/
_filter : function DOMWalker_filter(node, collectedResults) {
var nodeStatus = this._callbackFilter(node);
var nodeTestResults = [];
switch (nodeStatus) {
case DOMWalker.FILTER_ACCEPT:
nodeTestResults = this._callbackNodeTest(node);
collectedResults = collectedResults.concat(nodeTestResults);
// no break here as we have to perform the _walk below too
case DOMWalker.FILTER_SKIP:
nodeTestResults = this._walk(node);
break;
default:
break;
}
collectedResults = collectedResults.concat(nodeTestResults);
return collectedResults;
},
/**
* Retrieves and returns a wanted node based on the provided identification
* set.
*
* @param {array of objects} idSet
* Contains informations on the elements to open while
* Object-elements: getBy - attribute-name of the attribute
* containing the identification
* information for the opener-element
* subContent - array of ids of the opener-elements
* in the window with the value of
* the above getBy-attribute
* target - information, where the new
* elements will be opened
* [1|2|4]
* title - title of the opened dialog/window
* waitFunction - The function used as an argument
* for MozmillController.waitFor to
* wait before starting the walk.
* [optional - default: no waiting]
* windowHandler - Window instance
* [only needed for some tests]
*
* @returns Node
* @type {Node}
*/
_getNode : function DOMWalker_getNode(idSet) {
var doc = this._controller.window.document;
// QuerySelector seems to be unusuale for id's in this case:
// https://developer.mozilla.org/En/Code_snippets/QuerySelector
switch (idSet.getBy) {
case DOMWalker.GET_BY_ID:
return doc.getElementById(idSet[idSet.getBy]);
case DOMWalker.GET_BY_SELECTOR:
return doc.querySelector(idSet[idSet.getBy]);
default:
throw new Error("Not supported getBy-attribute: " + idSet.getBy);
}
},
/**
* Main entry point to open new elements like windows, tabpanels, prefpanes,
* dialogs
*
* @param {array of objects} ids
* Contains informations on the elements to open while
* Object-elements: getBy - attribute-name of the attribute
* containing the identification
* information for the opener-element
* subContent - array of ids of the opener-elements
* in the window with the value of
* the above getBy-attribute
* target - information, where the new
* elements will be opened
* [1|2|4]
* title - title of the opened dialog/window
* waitFunction - The function used as an argument
* for MozmillController.waitFor to
* wait before starting the walk.
* [optional - default: no waiting]
* windowHandler - Window instance
* [only needed for some tests]
*/
_prepareTargetWindows : function DOMWalker_prepareTargetWindows(ids) {
var doc = this._controller.window.document;
// Go through all the provided ids
for (var i = 0; i < ids.length; i++) {
var node = this._getNode(ids[i]);
// Go further only, if the needed element exists
if (node) {
var idSet = ids[i];
// Decide if what we want to open is a new normal/modal window or if it
// will be opened in the current window.
switch (idSet.target) {
case DOMWalker.WINDOW_CURRENT:
this._processNode(node, idSet);
break;
case DOMWalker.WINDOW_MODAL:
// Modal windows have to be able to access that informations
var modalInfos = {ids : idSet.subContent,
callbackFilter : this._callbackFilter,
callbackNodeTest : this._callbackNodeTest,
callbackResults : this._callbackResults,
waitFunction : idSet.waitFunction}
persisted.modalInfos = modalInfos;
var md = new modalDialog.modalDialog(this._controller.window);
md.start(this._modalWindowHelper);
this._processNode(node, idSet);
md.waitForDialog();
break;
case DOMWalker.WINDOW_NEW:
this._processNode(node, idSet);
// Get the new non-modal window controller
var controller = utils.handleWindow('title', idSet.title,
undefined, false);
// Start a new DOMWalker instance
let domWalker = new DOMWalker(controller, this._callbackFilter,
this._callbackNodeTest,
this._callbackResults);
domWalker.walk(idSet.subContent,
controller.window.document.documentElement,
idSet.waitFunction);
// Close the window
controller.window.close();
break;
default:
throw new Error("Node does not exist: " + ids[i][ids[i].getBy]);
}
}
}
},
/**
* Opens new windows/dialog and starts the DOMWalker.walk() in case of dialogs
* in existing windows.
*
* @param {Node} activeNode
* Node that holds the information which way
* to open the new window/dialog
* @param {object} idSet
* ID set for the element to open
*/
_processNode: function DOMWalker_processNode(activeNode, idSet) {
var doc = this._controller.window.document;
var nodeToProcess = this._getNode(idSet);
// Opens a new window/dialog through a menulist and runs DOMWalker.walk()
// for it.
// If the wanted window/dialog is already selected, just run this function
// recursively for it's descendants.
if (activeNode.localName == "menulist") {
if (nodeToProcess.value != idSet.value) {
var dropDown = new elementslib.Elem(nodeToProcess);
this._controller.waitForElement(dropDown);
this._controller.select(dropDown, null, null, idSet.value);
this._controller.waitFor(function() {
return nodeToProcess.value == idSet.value;
}, "The menu item did not load in time: " + idSet.value);
// If the target is a new modal/non-modal window, this.walk() has to be
// started by the method opening that window. If not, we do it here.
if (idSet.target == DOMWalker.WINDOW_CURRENT)
this.walk(idSet.subContent, null, idSet.waitFunction);
} else if (nodeToProcess.selected && idSet.subContent &&
idSet.subContent.length > 0) {
this._prepareTargetWindows(idSet.subContent);
}
}
// Opens a new prefpane using a provided windowHandler object
// and runs DOMWalker.walk() for it.
// If the wanted prefpane is already selected, just run this function
// recursively for it's descendants.
else if (activeNode.localName == "prefpane") {
var windowHandler = idSet.windowHandler;
if (windowHandler.paneId != idSet.id) {
windowHandler.paneId = idSet.id;
// Wait for the pane's content to load and to be fully displayed
this._controller.waitFor(function() {
return (nodeToProcess.loaded &&
(!mozmill.isMac ||
nodeToProcess.style.opacity == 1 ||
nodeToProcess.style.opacity == null));
}, "The pane did not load in time: " + idSet.id);
// If the target is a new modal/non-modal window, this.walk() has to be
// started by the method opening that window. If not, we do it here.
if (idSet.target == DOMWalker.WINDOW_CURRENT)
this.walk(idSet.subContent, null, idSet.waitFunction);
} else if (windowHandler.paneId == idSet.id && idSet.subContent &&
idSet.subContent.length > 0) {
this._prepareTargetWindows(idSet.subContent);
}
}
// Switches to another tab and runs DOMWalker.walk() for it.
// If the wanted tabpanel is already selected, just run this function
// recursively for it's descendants.
else if (activeNode.localName == "tab") {
if (nodeToProcess.selected != true) {
this._controller.click(new elementslib.Elem(nodeToProcess));
// If the target is a new modal/non-modal window, this.walk() has to be
// started by the method opening that window. If not, we do it here.
if (idSet.target == DOMWalker.WINDOW_CURRENT)
this.walk(idSet.subContent, null, idSet.waitFunction);
} else if (nodeToProcess.selected && idSet.subContent
&& idSet.subContent.length > 0) {
this._prepareTargetWindows(idSet.subContent);
}
}
// Opens a new dialog/window by clicking on an object and runs
// DOMWalker.walk() for it.
else {
this._controller.click(new elementslib.Elem(nodeToProcess));
// If the target is a new modal/non-modal window, this.walk() has to be
// started by the method opening that window. If not, we do it here.
if (idSet.target == DOMWalker.WINDOW_CURRENT)
this.walk(idSet.subContent, null, idSet.waitFunction);
}
},
/**
* DOMWalker_walk goes recursively through the DOM, starting with a provided
* root-node and filters the nodes using the this._filter method.
*
* @param {Node} root
* Node to start testing from
* [optional - default: this._controller.window.document.documentElement]
* @returns An array with gathered all results from testing a given element
* @type {array of elements}
*/
_walk : function DOMWalker__walk(root) {
if (!root.childNodes)
throw new Error("root.childNodes does not exist");
var collectedResults = [];
// There seems to be no other way to get to the nodes hidden in the
// "_buttons" object (see Bug 614949)
if (root._buttons) {
for each (button in root._buttons) {
collectedResults = this._filter(button, collectedResults);
}
}
for (var i = 0; i < root.childNodes.length; i++) {
collectedResults = this._filter(root.childNodes[i], collectedResults);
}
return collectedResults;
},
/**
* Callback function to handle new windows
*
* @param {MozMillController} controller
* MozMill controller of the new window to operate on.
*/
_modalWindowHelper: function DOMWalker_modalWindowHelper(controller) {
let domWalker = new DOMWalker(controller,
persisted.modalInfos.callbackFilter,
persisted.modalInfos.callbackNodeTest,
persisted.modalInfos.callbackResults);
domWalker.walk(persisted.modalInfos.ids,
controller.window.document.documentElement,
persisted.modalInfos.waitFunction);
delete persisted.modalInfos;
controller.window.close();
}
}
/**
* Default constructor
*
* @param {object} aRoot
* Root node in the DOM to use as parent
*/
function nodeCollector(aRoot) {
this._root = aRoot.wrappedJSObject ? aRoot.wrappedJSObject : aRoot;
this._document = this._root.ownerDocument ? this._root.ownerDocument : this._root;
this._nodes = [ ];
}
/**
* Node collector class
*/
nodeCollector.prototype = {
/**
* Converts current nodes to elements
*
* @returns List of elements
* @type {array of ElemBase}
*/
get elements() {
var elements = [ ];
Array.forEach(this._nodes, function(element) {
elements.push(new elementslib.Elem(element));
});
return elements;
},
/**
* Get the current list of DOM nodes
*
* @returns List of nodes
* @type {array of object}
*/
get nodes() {
return this._nodes;
},
/**
* Sets current nodes to entries from the node list
*
* @param {array of objects} aNodeList
* List of DOM nodes to set
*/
set nodes(aNodeList) {
if (aNodeList) {
this._nodes = [ ];
Array.forEach(aNodeList, function(node) {
this._nodes.push(node);
}, this);
}
},
/**
* Get the root node used as parent for a node collection
*
* @returns Current root node
* @type {object}
*/
get root() {
return this._root;
},
/**
* Sets root node to the specified DOM node
*
* @param {object} aRoot
* DOM node to use as root for node collection
*/
set root(aRoot) {
if (aRoot) {
this._root = aRoot;
this._nodes = [ ];
}
},
/**
* Filter nodes given by the specified callback function
*
* @param {function} aCallback
* Function to test each element of the array.
* Elements: node, index (optional) , array (optional)
* @param {object} aThisObject
* Object to use as 'this' when executing callback.
* [optional - default: function scope]
*
* @returns The class instance
* @type {object}
*/
filter : function nodeCollector_filter(aCallback, aThisObject) {
if (!aCallback)
throw new Error(arguments.callee.name + ": No callback specified");
this.nodes = Array.filter(this.nodes, aCallback, aThisObject);
return this;
},
/**
* Filter nodes by DOM property and its value
*
* @param {string} aProperty
* Property to filter for
* @param {string} aValue
* Expected value of the DOM property
* [optional - default: n/a]
*
* @returns The class instance
* @type {object}
*/
filterByDOMProperty : function nodeCollector_filterByDOMProperty(aProperty, aValue) {
return this.filter(function(node) {
if (aProperty && aValue)
return node.getAttribute(aProperty) == aValue;
else if (aProperty)
return node.hasAttribute(aProperty);
else
return true;
});
},
/**
* Filter nodes by JS property and its value
*
* @param {string} aProperty
* Property to filter for
* @param {string} aValue
* Expected value of the JS property
* [optional - default: n/a]
*
* @returns The class instance
* @type {object}
*/
filterByJSProperty : function nodeCollector_filterByJSProperty(aProperty, aValue) {
return this.filter(function(node) {
if (aProperty && aValue)
return node.aProperty == aValue;
else if (aProperty)
return node.aProperty !== undefined;
else
return true;
});
},
/**
* Find anonymouse nodes with the specified attribute and value
*
* @param {string} aAttribute
* DOM attribute of the wanted node
* @param {string} aValue
* Value of the DOM attribute
*
* @returns The class instance
* @type {object}
*/
queryAnonymousNodes : function nodeCollector_queryAnonymousNodes(aAttribute, aValue) {
var node = this._document.getAnonymousElementByAttribute(this._root,
aAttribute,
aValue);
this.nodes = node ? [node] : [ ];
return this;
},
/**
* Find nodes with the specified selector
*
* @param {string} aSelector
* jQuery like element selector string
*
* @returns The class instance
* @type {object}
*/
queryNodes : function nodeCollector_queryNodes(aSelector) {
this.nodes = this._root.querySelectorAll(aSelector);
return this;
}
}
// Exports of functions
exports.unwrapNode = unwrapNode;
// Exports of classes
exports.DOMWalker = DOMWalker;
exports.nodeCollector = nodeCollector;