mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-11 16:32:59 +00:00
Bug 982610 - Update TPS to use latest Mozmill 2.0.6. r=hskupin DONTBUILD
--HG-- extra : rebase_source : 6e967421250dd6093c0fcc89dcbd078c0812fcb6
This commit is contained in:
parent
dfc8cc59ba
commit
e2b5275fc3
@ -2,10 +2,10 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import('resource://tps/mozmill/sync.jsm');
|
||||
Components.utils.import('resource://tps/tps.jsm');
|
||||
|
||||
var setupModule = function(module) {
|
||||
controller = mozmill.getBrowserController();
|
||||
module.controller = mozmill.getBrowserController();
|
||||
assert.ok(true, "SetupModule passes");
|
||||
}
|
||||
|
||||
@ -16,8 +16,9 @@ var setupTest = function(module) {
|
||||
var testTestStep = function() {
|
||||
assert.ok(true, "test Passes");
|
||||
controller.open("http://www.mozilla.org");
|
||||
TPS.SetupSyncAccount();
|
||||
assert.equal(TPS.Sync(SYNC_WIPE_SERVER), 0, "sync succeeded");
|
||||
|
||||
TPS.Login();
|
||||
TPS.Sync(ACTIONS.ACTION_SYNC_WIPE_CLIENT);
|
||||
}
|
||||
|
||||
var teardownTest = function () {
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var jum = {}; Components.utils.import('resource://mozmill/modules/jum.js', jum);
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var setupModule = function(module) {
|
||||
module.controller = mozmill.getBrowserController();
|
||||
@ -11,43 +10,6 @@ var testGetNode = function() {
|
||||
controller.open("about:support");
|
||||
controller.waitForPageLoad();
|
||||
|
||||
var appbox = new elementslib.ID(controller.tabs.activeTab, "application-box");
|
||||
jum.assert(appbox.getNode().innerHTML == 'Firefox', 'correct app name');
|
||||
};
|
||||
|
||||
const NAV_BAR = '/id("main-window")/id("tab-view-deck")/{"flex":"1"}' +
|
||||
'/id("navigator-toolbox")/id("nav-bar")';
|
||||
const SEARCH_BAR = NAV_BAR + '/id("search-container")/id("searchbar")';
|
||||
const SEARCH_TEXTBOX = SEARCH_BAR + '/anon({"anonid":"searchbar-textbox"})';
|
||||
const SEARCH_DROPDOWN = SEARCH_TEXTBOX + '/[0]/anon({"anonid":"searchbar-engine-button"})';
|
||||
const SEARCH_POPUP = SEARCH_DROPDOWN + '/anon({"anonid":"searchbar-popup"})';
|
||||
const SEARCH_INPUT = SEARCH_TEXTBOX + '/anon({"class":"autocomplete-textbox-container"})' +
|
||||
'/anon({"anonid":"textbox-input-box"})' +
|
||||
'/anon({"anonid":"input"})';
|
||||
const SEARCH_CONTEXT = SEARCH_TEXTBOX + '/anon({"anonid":"textbox-input-box"})' +
|
||||
'/anon({"anonid":"input-box-contextmenu"})';
|
||||
const SEARCH_GO_BUTTON = SEARCH_TEXTBOX + '/anon({"class":"search-go-container"})' +
|
||||
'/anon({"class":"search-go-button"})';
|
||||
const SEARCH_AUTOCOMPLETE = '/id("main-window")/id("mainPopupSet")/id("PopupAutoComplete")';
|
||||
|
||||
var testLookupExpressions = function() {
|
||||
var item;
|
||||
item = new elementslib.Lookup(controller.window.document, NAV_BAR);
|
||||
controller.click(item);
|
||||
item = new elementslib.Lookup(controller.window.document, SEARCH_BAR);
|
||||
controller.click(item);
|
||||
item = new elementslib.Lookup(controller.window.document, SEARCH_TEXTBOX);
|
||||
controller.click(item);
|
||||
item = new elementslib.Lookup(controller.window.document, SEARCH_DROPDOWN);
|
||||
controller.click(item);
|
||||
item = new elementslib.Lookup(controller.window.document, SEARCH_POPUP);
|
||||
controller.click(item);
|
||||
item = new elementslib.Lookup(controller.window.document, SEARCH_INPUT);
|
||||
controller.click(item);
|
||||
item = new elementslib.Lookup(controller.window.document, SEARCH_CONTEXT);
|
||||
controller.click(item);
|
||||
item = new elementslib.Lookup(controller.window.document, SEARCH_GO_BUTTON);
|
||||
controller.click(item);
|
||||
item = new elementslib.Lookup(controller.window.document, SEARCH_AUTOCOMPLETE);
|
||||
controller.click(item);
|
||||
var appbox = findElement.ID(controller.tabs.activeTab, "application-box");
|
||||
assert.waitFor(() => appbox.getNode().textContent == 'Firefox', 'correct app name');
|
||||
};
|
||||
|
0
services/sync/tps/extensions/mozmill/chrome.manifest
Normal file → Executable file
0
services/sync/tps/extensions/mozmill/chrome.manifest
Normal file → Executable file
@ -1,7 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* debugging prefs */
|
||||
pref("browser.dom.window.dump.enabled", true);
|
||||
pref("javascript.options.showInConsole", true);
|
59
services/sync/tps/extensions/mozmill/install.rdf
Normal file → Executable file
59
services/sync/tps/extensions/mozmill/install.rdf
Normal file → Executable file
@ -5,59 +5,24 @@
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>mozmill@mozilla.com</em:id>
|
||||
<em:name>MozMill</em:name>
|
||||
<em:version>2.0b1</em:version>
|
||||
<em:creator>Adam Christian</em:creator>
|
||||
<em:description>A testing extension based on the Windmill Testing Framework client source</em:description>
|
||||
<em:name>Mozmill</em:name>
|
||||
<em:version>2.0.6</em:version>
|
||||
<em:description>UI Automation tool for Mozilla applications</em:description>
|
||||
<em:unpack>true</em:unpack>
|
||||
|
||||
<em:creator>Mozilla Automation and Testing Team</em:creator>
|
||||
<em:contributor>Adam Christian</em:contributor>
|
||||
<em:contributor>Mikeal Rogers</em:contributor>
|
||||
|
||||
<em:targetApplication>
|
||||
<!-- Firefox -->
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>3.5</em:minVersion>
|
||||
<em:maxVersion>12.*</em:maxVersion>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>10.0</em:minVersion>
|
||||
<em:maxVersion>31.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:targetApplication>
|
||||
<!-- Thunderbird -->
|
||||
<Description>
|
||||
<em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
|
||||
<em:minVersion>3.0a1pre</em:minVersion>
|
||||
<em:maxVersion>9.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:targetApplication>
|
||||
<!-- Sunbird -->
|
||||
<Description>
|
||||
<em:id>{718e30fb-e89b-41dd-9da7-e25a45638b28}</em:id>
|
||||
<em:minVersion>0.6a1</em:minVersion>
|
||||
<em:maxVersion>1.0pre</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:targetApplication>
|
||||
<!-- SeaMonkey -->
|
||||
<Description>
|
||||
<em:id>{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}</em:id>
|
||||
<em:minVersion>2.0a1</em:minVersion>
|
||||
<em:maxVersion>9.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:targetApplication>
|
||||
<!-- Songbird -->
|
||||
<Description>
|
||||
<em:id>songbird@songbirdnest.com</em:id>
|
||||
<em:minVersion>0.3pre</em:minVersion>
|
||||
<em:maxVersion>1.3.0a</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>1.9.1</em:minVersion>
|
||||
<em:maxVersion>9.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
</Description>
|
||||
</RDF>
|
||||
|
1150
services/sync/tps/extensions/mozmill/resource/driver/controller.js
Normal file
1150
services/sync/tps/extensions/mozmill/resource/driver/controller.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,30 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["Elem", "ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
|
||||
var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
|
||||
"Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
|
||||
];
|
||||
|
||||
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
|
||||
var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
|
||||
var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
|
||||
var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
|
||||
var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
|
||||
var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
|
||||
var objects = {}; Components.utils.import('resource://mozmill/stdlib/objects.js', objects);
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
var countQuotes = function(str){
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
|
||||
var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
|
||||
var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2);
|
||||
var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
|
||||
var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom);
|
||||
var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects);
|
||||
|
||||
var countQuotes = function (str) {
|
||||
var count = 0;
|
||||
var i = 0;
|
||||
while(i < str.length) {
|
||||
|
||||
while (i < str.length) {
|
||||
i = str.indexOf('"', i);
|
||||
if (i != -1) {
|
||||
count++;
|
||||
@ -26,6 +33,7 @@ var countQuotes = function(str){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
@ -53,10 +61,12 @@ var smartSplit = function (str) {
|
||||
var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
|
||||
var ret = []
|
||||
var match = re.exec(str);
|
||||
|
||||
while (match != null) {
|
||||
ret.push(match[0].replace(/^\//, ""));
|
||||
match = re.exec(str);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
@ -67,9 +77,12 @@ var smartSplit = function (str) {
|
||||
* if no document is provided
|
||||
*/
|
||||
function defaultDocuments() {
|
||||
var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);
|
||||
win = windowManager.getMostRecentWindow("navigator:browser");
|
||||
return [win.gBrowser.selectedBrowser.contentDocument, win.document];
|
||||
var win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
return [
|
||||
win.document,
|
||||
utils.getBrowserObject(win).selectedBrowser.contentWindow.document
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
@ -84,30 +97,37 @@ function nodeSearch(doc, func, string) {
|
||||
} else {
|
||||
var documents = defaultDocuments();
|
||||
}
|
||||
|
||||
var e = null;
|
||||
var element = null;
|
||||
|
||||
//inline function to recursively find the element in the DOM, cross frame.
|
||||
var search = function(win, func, string) {
|
||||
if (win == null)
|
||||
var search = function (win, func, string) {
|
||||
if (win == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//do the lookup in the current window
|
||||
element = func.call(win, string);
|
||||
|
||||
if (!element || (element.length == 0)) {
|
||||
var frames = win.frames;
|
||||
for (var i=0; i < frames.length; i++) {
|
||||
for (var i = 0; i < frames.length; i++) {
|
||||
search(frames[i], func, string);
|
||||
}
|
||||
} else {
|
||||
e = element;
|
||||
}
|
||||
else { e = element; }
|
||||
};
|
||||
|
||||
for (var i = 0; i < documents.length; ++i) {
|
||||
var win = documents[i].defaultView;
|
||||
search(win, func, string);
|
||||
if (e) break;
|
||||
if (e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
};
|
||||
|
||||
@ -120,11 +140,15 @@ function Selector(_document, selector, index) {
|
||||
if (selector == undefined) {
|
||||
throw new Error('Selector constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
this.selector = selector;
|
||||
|
||||
this.getNodeForDocument = function (s) {
|
||||
return this.document.querySelectorAll(s);
|
||||
};
|
||||
|
||||
var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
|
||||
|
||||
return nodes ? nodes[index || 0] : null;
|
||||
};
|
||||
|
||||
@ -137,9 +161,11 @@ function ID(_document, nodeID) {
|
||||
if (nodeID == undefined) {
|
||||
throw new Error('ID constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
this.getNodeForDocument = function (nodeID) {
|
||||
return this.document.getElementById(nodeID);
|
||||
};
|
||||
|
||||
return nodeSearch(_document, this.getNodeForDocument, nodeID);
|
||||
};
|
||||
|
||||
@ -154,40 +180,48 @@ function Link(_document, linkName) {
|
||||
}
|
||||
|
||||
this.getNodeForDocument = function (linkName) {
|
||||
var getText = function(el){
|
||||
var getText = function (el) {
|
||||
var text = "";
|
||||
if (el.nodeType == 3){ //textNode
|
||||
if (el.data != undefined){
|
||||
|
||||
if (el.nodeType == 3) { //textNode
|
||||
if (el.data != undefined) {
|
||||
text = el.data;
|
||||
} else {
|
||||
text = el.innerHTML;
|
||||
}
|
||||
text = text.replace(/n|r|t/g, " ");
|
||||
|
||||
text = text.replace(/n|r|t/g, " ");
|
||||
}
|
||||
if (el.nodeType == 1){ //elementNode
|
||||
else if (el.nodeType == 1) { //elementNode
|
||||
for (var i = 0; i < el.childNodes.length; i++) {
|
||||
var child = el.childNodes.item(i);
|
||||
text += getText(child);
|
||||
}
|
||||
if (el.tagName == "P" || el.tagName == "BR" || el.tagName == "HR" || el.tagName == "DIV") {
|
||||
text += "n";
|
||||
|
||||
if (el.tagName == "P" || el.tagName == "BR" ||
|
||||
el.tagName == "HR" || el.tagName == "DIV") {
|
||||
text += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
//sometimes the windows won't have this function
|
||||
try {
|
||||
var links = this.document.getElementsByTagName('a'); }
|
||||
catch(err){ // ADD LOG LINE mresults.write('Error: '+ err, 'lightred');
|
||||
var links = this.document.getElementsByTagName('a');
|
||||
} catch (e) {
|
||||
// ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
|
||||
}
|
||||
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var el = links[i];
|
||||
//if (getText(el).indexOf(this.linkName) != -1) {
|
||||
if (el.innerHTML.indexOf(linkName) != -1){
|
||||
if (el.innerHTML.indexOf(linkName) != -1) {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -214,14 +248,20 @@ function XPath(_document, expr) {
|
||||
} else {
|
||||
xpe = new this.document.defaultView.XPathEvaluator();
|
||||
}
|
||||
var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement : aNode.ownerDocument.documentElement);
|
||||
|
||||
var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement
|
||||
: aNode.ownerDocument.documentElement);
|
||||
var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
|
||||
var found = [];
|
||||
var res;
|
||||
while (res = result.iterateNext())
|
||||
|
||||
while (res = result.iterateNext()) {
|
||||
found.push(res);
|
||||
}
|
||||
|
||||
return found[0];
|
||||
};
|
||||
|
||||
return nodeSearch(_document, this.getNodeForDocument, expr);
|
||||
};
|
||||
|
||||
@ -234,14 +274,19 @@ function Name(_document, nName) {
|
||||
if (nName == undefined) {
|
||||
throw new Error('Name constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
this.getNodeForDocument = function (s) {
|
||||
try{
|
||||
var els = this.document.getElementsByName(s);
|
||||
if (els.length > 0) { return els[0]; }
|
||||
if (els.length > 0) {
|
||||
return els[0];
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
catch(err){};
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return nodeSearch(_document, this.getNodeForDocument, nName);
|
||||
};
|
||||
|
||||
@ -249,110 +294,138 @@ function Name(_document, nName) {
|
||||
var _returnResult = function (results) {
|
||||
if (results.length == 0) {
|
||||
return null
|
||||
} else if (results.length == 1) {
|
||||
}
|
||||
else if (results.length == 1) {
|
||||
return results[0];
|
||||
} else {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
var _forChildren = function (element, name, value) {
|
||||
var results = [];
|
||||
var nodes = [e for each (e in element.childNodes) if (e)]
|
||||
|
||||
for (var i in nodes) {
|
||||
var n = nodes[i];
|
||||
if (n[name] == value) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
var _forAnonChildren = function (_document, element, name, value) {
|
||||
var results = [];
|
||||
var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
|
||||
|
||||
for (var i in nodes ) {
|
||||
var n = nodes[i];
|
||||
if (n[name] == value) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
var _byID = function (_document, parent, value) {
|
||||
return _returnResult(_forChildren(parent, 'id', value));
|
||||
}
|
||||
|
||||
var _byName = function (_document, parent, value) {
|
||||
return _returnResult(_forChildren(parent, 'tagName', value));
|
||||
}
|
||||
|
||||
var _byAttrib = function (parent, attributes) {
|
||||
var results = [];
|
||||
|
||||
var nodes = parent.childNodes;
|
||||
|
||||
for (var i in nodes) {
|
||||
var n = nodes[i];
|
||||
requirementPass = 0;
|
||||
requirementLength = 0;
|
||||
|
||||
for (var a in attributes) {
|
||||
requirementLength++;
|
||||
try {
|
||||
if (n.getAttribute(a) == attributes[a]) {
|
||||
requirementPass++;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (e) {
|
||||
// Workaround any bugs in custom attribute crap in XUL elements
|
||||
}
|
||||
}
|
||||
|
||||
if (requirementPass == requirementLength) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
|
||||
return _returnResult(results)
|
||||
}
|
||||
|
||||
var _byAnonAttrib = function (_document, parent, attributes) {
|
||||
var results = [];
|
||||
|
||||
if (objects.getLength(attributes) == 1) {
|
||||
for (var i in attributes) {var k = i; var v = attributes[i]; }
|
||||
var result = _document.getAnonymousElementByAttribute(parent, k, v)
|
||||
for (var i in attributes) {
|
||||
var k = i;
|
||||
var v = attributes[i];
|
||||
}
|
||||
|
||||
var result = _document.getAnonymousElementByAttribute(parent, k, v);
|
||||
if (result) {
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
|
||||
|
||||
function resultsForNodes (nodes) {
|
||||
for (var i in nodes) {
|
||||
var n = nodes[i];
|
||||
requirementPass = 0;
|
||||
requirementLength = 0;
|
||||
|
||||
for (var a in attributes) {
|
||||
requirementLength++;
|
||||
if (n.getAttribute(a) == attributes[a]) {
|
||||
requirementPass++;
|
||||
}
|
||||
}
|
||||
|
||||
if (requirementPass == requirementLength) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
resultsForNodes(nodes)
|
||||
|
||||
resultsForNodes(nodes);
|
||||
if (results.length == 0) {
|
||||
resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
|
||||
}
|
||||
|
||||
return _returnResult(results)
|
||||
}
|
||||
|
||||
var _byIndex = function (_document, parent, i) {
|
||||
if (parent instanceof Array) {
|
||||
return parent[i];
|
||||
}
|
||||
|
||||
return parent.childNodes[i];
|
||||
}
|
||||
|
||||
var _anonByName = function (_document, parent, value) {
|
||||
return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
|
||||
}
|
||||
|
||||
var _anonByAttrib = function (_document, parent, value) {
|
||||
return _byAnonAttrib(_document, parent, value);
|
||||
}
|
||||
|
||||
var _anonByIndex = function (_document, parent, i) {
|
||||
return _document.getAnonymousNodes(parent)[i];
|
||||
}
|
||||
@ -362,18 +435,32 @@ var _anonByIndex = function (_document, parent, i) {
|
||||
*
|
||||
* Finds an element by Lookup expression
|
||||
*/
|
||||
function Lookup (_document, expression) {
|
||||
function Lookup(_document, expression) {
|
||||
if (expression == undefined) {
|
||||
throw new Error('Lookup constructor did not recieve enough arguments.');
|
||||
}
|
||||
|
||||
var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
|
||||
expSplit.unshift(_document)
|
||||
expSplit.unshift(_document);
|
||||
|
||||
var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
|
||||
var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
|
||||
|
||||
/**
|
||||
* Reduces the lookup expression
|
||||
* @param {Object} parentNode
|
||||
* Parent node (previousValue of the formerly executed reduce callback)
|
||||
* @param {String} exp
|
||||
* Lookup expression for the parents child node
|
||||
*
|
||||
* @returns {Object} Node found by the given expression
|
||||
*/
|
||||
var reduceLookup = function (parentNode, exp) {
|
||||
// Abort in case the parent node was not found
|
||||
if (!parentNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var reduceLookup = function (parent, exp) {
|
||||
// Handle case where only index is provided
|
||||
var cases = nCases;
|
||||
|
||||
@ -381,21 +468,27 @@ function Lookup (_document, expression) {
|
||||
if (withs.endsWith(exp, ']')) {
|
||||
var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
|
||||
}
|
||||
|
||||
// Handle anon
|
||||
if (withs.startsWith(exp, 'anon')) {
|
||||
var exp = strings.vslice(exp, '(', ')');
|
||||
var cases = aCases;
|
||||
exp = strings.vslice(exp, '(', ')');
|
||||
cases = aCases;
|
||||
}
|
||||
|
||||
if (withs.startsWith(exp, '[')) {
|
||||
try {
|
||||
var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
|
||||
} catch (err) {
|
||||
throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '[', ']')+' ||');
|
||||
} catch (e) {
|
||||
throw new SyntaxError(e + '. String to be parsed was || ' +
|
||||
strings.vslice(exp, '[', ']') + ' ||');
|
||||
}
|
||||
var r = cases['index'](_document, parent, obj);
|
||||
|
||||
var r = cases['index'](_document, parentNode, obj);
|
||||
if (r == null) {
|
||||
throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
|
||||
throw new SyntaxError('Expression "' + exp +
|
||||
'" returned null. Anonymous == ' + (cases == aCases));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -403,30 +496,28 @@ function Lookup (_document, expression) {
|
||||
if (withs.startsWith(exp, c)) {
|
||||
try {
|
||||
var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
|
||||
} catch(err) {
|
||||
throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '(', ')')+' ||');
|
||||
} catch (e) {
|
||||
throw new SyntaxError(e + '. String to be parsed was || ' +
|
||||
strings.vslice(exp, '(', ')') + ' ||');
|
||||
}
|
||||
var result = cases[c](_document, parent, obj);
|
||||
var result = cases[c](_document, parentNode, obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
if ( withs.startsWith(exp, '{') ) {
|
||||
if (withs.startsWith(exp, '{')) {
|
||||
try {
|
||||
var obj = json2.JSON.parse(exp)
|
||||
} catch(err) {
|
||||
throw new Error(err+'. String to be parsed was || '+exp+' ||');
|
||||
var obj = json2.JSON.parse(exp);
|
||||
} catch (e) {
|
||||
throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
|
||||
}
|
||||
|
||||
if (cases == aCases) {
|
||||
var result = _anonByAttrib(_document, parent, obj)
|
||||
var result = _anonByAttrib(_document, parentNode, obj);
|
||||
} else {
|
||||
var result = _byAttrib(parent, obj)
|
||||
var result = _byAttrib(parentNode, obj);
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
|
||||
}
|
||||
}
|
||||
|
||||
// Final return
|
||||
@ -437,8 +528,10 @@ function Lookup (_document, expression) {
|
||||
// TODO: Check length and raise error
|
||||
return result;
|
||||
}
|
||||
|
||||
// Maybe we should cause an exception here
|
||||
return false;
|
||||
};
|
||||
|
||||
return expSplit.reduce(reduceLookup);
|
||||
};
|
1163
services/sync/tps/extensions/mozmill/resource/driver/mozelement.js
Normal file
1163
services/sync/tps/extensions/mozmill/resource/driver/mozelement.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
|
||||
"getBrowserController", "newBrowserController",
|
||||
@ -8,119 +8,153 @@ var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
|
||||
"newMail3PaneController", "getMail3PaneController",
|
||||
"wm", "platform", "getAddrbkController",
|
||||
"getMsgComposeController", "getDownloadsController",
|
||||
"Application", "cleanQuit",
|
||||
"Application", "findElement",
|
||||
"getPlacesController", 'isMac', 'isLinux', 'isWindows',
|
||||
"firePythonCallback"
|
||||
"firePythonCallback", "getAddons"
|
||||
];
|
||||
|
||||
// imports
|
||||
var controller = {}; Components.utils.import('resource://mozmill/modules/controller.js', controller);
|
||||
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
|
||||
var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
|
||||
var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
|
||||
var os = {}; Components.utils.import('resource://mozmill/stdlib/os.js', os);
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
try {
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
} catch(e) { /* Firefox 4 only */ }
|
||||
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// imports
|
||||
var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
|
||||
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
|
||||
var controller = {}; Cu.import('resource://mozmill/driver/controller.js', controller);
|
||||
var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
|
||||
var findElement = {}; Cu.import('resource://mozmill/driver/mozelement.js', findElement);
|
||||
var os = {}; Cu.import('resource://mozmill/stdlib/os.js', os);
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
var windows = {}; Cu.import('resource://mozmill/modules/windows.js', windows);
|
||||
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
// This is a useful "check" timer. See utils.js, good for debugging
|
||||
if (DEBUG) {
|
||||
utils.startTimer();
|
||||
}
|
||||
|
||||
var assert = new assertions.Assert();
|
||||
|
||||
// platform information
|
||||
var platform = os.getPlatform();
|
||||
var isMac = false;
|
||||
var isWindows = false;
|
||||
var isLinux = false;
|
||||
|
||||
if (platform == "darwin"){
|
||||
isMac = true;
|
||||
}
|
||||
|
||||
if (platform == "winnt"){
|
||||
isWindows = true;
|
||||
}
|
||||
|
||||
if (platform == "linux"){
|
||||
isLinux = true;
|
||||
}
|
||||
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var wm = Services.wm;
|
||||
|
||||
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Components.interfaces.nsIXULAppInfo);
|
||||
|
||||
var locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
|
||||
.getService(Components.interfaces.nsIXULChromeRegistry)
|
||||
.getSelectedLocale("global");
|
||||
|
||||
var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
|
||||
getService(Components.interfaces.nsIConsoleService);
|
||||
var appInfo = Services.appinfo;
|
||||
var Application = utils.applicationName;
|
||||
|
||||
|
||||
applicationDictionary = {
|
||||
"{718e30fb-e89b-41dd-9da7-e25a45638b28}": "Sunbird",
|
||||
"{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "SeaMonkey",
|
||||
"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "Firefox",
|
||||
"{3550f703-e582-4d05-9a08-453d09bdfdc6}": 'Thunderbird',
|
||||
/**
|
||||
* Retrieves the list with information about installed add-ons.
|
||||
*
|
||||
* @returns {String} JSON data of installed add-ons
|
||||
*/
|
||||
function getAddons() {
|
||||
var addons = null;
|
||||
|
||||
AddonManager.getAllAddons(function (addonList) {
|
||||
var tmp_list = [ ];
|
||||
|
||||
addonList.forEach(function (addon) {
|
||||
var tmp = { };
|
||||
|
||||
// We have to filter out properties of type 'function' of the addon
|
||||
// object, which will break JSON.stringify() and result in incomplete
|
||||
// addon information.
|
||||
for (var key in addon) {
|
||||
if (typeof(addon[key]) !== "function") {
|
||||
tmp[key] = addon[key];
|
||||
}
|
||||
}
|
||||
|
||||
tmp_list.push(tmp);
|
||||
});
|
||||
|
||||
addons = tmp_list;
|
||||
});
|
||||
|
||||
try {
|
||||
// Sychronize with getAllAddons so we do not return too early
|
||||
assert.waitFor(function () {
|
||||
return !!addons;
|
||||
})
|
||||
|
||||
return addons;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var Application = applicationDictionary[appInfo.ID];
|
||||
/**
|
||||
* Retrieves application details for the Mozmill report
|
||||
*
|
||||
* @return {String} JSON data of application details
|
||||
*/
|
||||
function getApplicationDetails() {
|
||||
var locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
|
||||
.getService(Ci.nsIXULChromeRegistry)
|
||||
.getSelectedLocale("global");
|
||||
|
||||
if (Application == undefined) {
|
||||
// Default to Firefox
|
||||
var Application = 'Firefox';
|
||||
// Put all our necessary information into JSON and return it:
|
||||
// appinfo, startupinfo, and addons
|
||||
var details = {
|
||||
application_id: appInfo.ID,
|
||||
application_name: Application,
|
||||
application_version: appInfo.version,
|
||||
application_locale: locale,
|
||||
platform_buildid: appInfo.platformBuildID,
|
||||
platform_version: appInfo.platformVersion,
|
||||
addons: getAddons(),
|
||||
startupinfo: getStartupInfo(),
|
||||
paths: {
|
||||
appdata: Services.dirsvc.get('UAppData', Ci.nsIFile).path,
|
||||
profile: Services.dirsvc.get('ProfD', Ci.nsIFile).path
|
||||
}
|
||||
};
|
||||
|
||||
return JSON.stringify(details);
|
||||
}
|
||||
|
||||
// get startup time if available
|
||||
// see http://blog.mozilla.com/tglek/2011/04/26/measuring-startup-speed-correctly/
|
||||
var startupInfo = {};
|
||||
try {
|
||||
var _startupInfo = Components.classes["@mozilla.org/toolkit/app-startup;1"]
|
||||
.getService(Components.interfaces.nsIAppStartup).getStartupInfo();
|
||||
for (var i in _startupInfo) {
|
||||
startupInfo[i] = _startupInfo[i].getTime(); // convert from Date object to ms since epoch
|
||||
function getStartupInfo() {
|
||||
var startupInfo = {};
|
||||
|
||||
try {
|
||||
var _startupInfo = Services.startup.getStartupInfo();
|
||||
for (var time in _startupInfo) {
|
||||
// convert from Date object to ms since epoch
|
||||
startupInfo[time] = _startupInfo[time].getTime();
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
startupInfo = null;
|
||||
}
|
||||
|
||||
return startupInfo;
|
||||
}
|
||||
|
||||
|
||||
// keep list of installed addons to send to jsbridge for test run report
|
||||
var addons = "null"; // this will be JSON parsed
|
||||
if(typeof AddonManager != "undefined") {
|
||||
AddonManager.getAllAddons(function(addonList) {
|
||||
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
|
||||
converter.charset = 'utf-8';
|
||||
|
||||
function replacer(key, value) {
|
||||
if (typeof(value) == "string") {
|
||||
try {
|
||||
return converter.ConvertToUnicode(value);
|
||||
} catch(e) {
|
||||
var newstring = '';
|
||||
for (var i=0; i < value.length; i++) {
|
||||
replacement = '';
|
||||
if ((32 <= value.charCodeAt(i)) && (value.charCodeAt(i) < 127)) {
|
||||
// eliminate non-convertable characters;
|
||||
newstring += value.charAt(i);
|
||||
} else {
|
||||
newstring += replacement;
|
||||
}
|
||||
}
|
||||
return newstring;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
addons = converter.ConvertToUnicode(JSON.stringify(addonList, replacer))
|
||||
});
|
||||
}
|
||||
|
||||
function cleanQuit () {
|
||||
utils.getMethodInWindows('goQuitApplication')();
|
||||
}
|
||||
|
||||
function addHttpResource (directory, namespace) {
|
||||
return 'http://localhost:4545/'+namespace;
|
||||
}
|
||||
|
||||
function newBrowserController () {
|
||||
return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
|
||||
@ -128,34 +162,39 @@ function newBrowserController () {
|
||||
|
||||
function getBrowserController () {
|
||||
var browserWindow = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
if (browserWindow == null) {
|
||||
return newBrowserController();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return new controller.MozMillController(browserWindow);
|
||||
}
|
||||
}
|
||||
|
||||
function getPlacesController () {
|
||||
utils.getMethodInWindows('PlacesCommandHook').showPlacesOrganizer('AllBookmarks');
|
||||
|
||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||
}
|
||||
|
||||
function getAddonsController () {
|
||||
if (Application == 'SeaMonkey') {
|
||||
utils.getMethodInWindows('toEM')();
|
||||
} else if (Application == 'Thunderbird') {
|
||||
}
|
||||
else if (Application == 'Thunderbird') {
|
||||
utils.getMethodInWindows('openAddonsMgr')();
|
||||
} else if (Application == 'Sunbird') {
|
||||
}
|
||||
else if (Application == 'Sunbird') {
|
||||
utils.getMethodInWindows('goOpenAddons')();
|
||||
} else {
|
||||
utils.getMethodInWindows('BrowserOpenAddonsMgr')();
|
||||
}
|
||||
|
||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||
}
|
||||
|
||||
function getDownloadsController() {
|
||||
utils.getMethodInWindows('BrowserDownloadsUI')();
|
||||
|
||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||
}
|
||||
|
||||
@ -165,6 +204,7 @@ function getPreferencesController() {
|
||||
} else {
|
||||
utils.getMethodInWindows('openPreferences')();
|
||||
}
|
||||
|
||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||
}
|
||||
|
||||
@ -175,10 +215,10 @@ function newMail3PaneController () {
|
||||
|
||||
function getMail3PaneController () {
|
||||
var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
|
||||
|
||||
if (mail3PaneWindow == null) {
|
||||
return newMail3PaneController();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return new controller.MozMillController(mail3PaneWindow);
|
||||
}
|
||||
}
|
||||
@ -188,6 +228,7 @@ function newAddrbkController () {
|
||||
utils.getMethodInWindows("toAddressBook")();
|
||||
utils.sleep(2000);
|
||||
var addyWin = wm.getMostRecentWindow("mail:addressbook");
|
||||
|
||||
return new controller.MozMillController(addyWin);
|
||||
}
|
||||
|
||||
@ -195,35 +236,50 @@ function getAddrbkController () {
|
||||
var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
|
||||
if (addrbkWindow == null) {
|
||||
return newAddrbkController();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return new controller.MozMillController(addrbkWindow);
|
||||
}
|
||||
}
|
||||
|
||||
function firePythonCallback (filename, method, args, kwargs) {
|
||||
obj = {'filename': filename, 'method': method};
|
||||
obj['test'] = frame.events.currentModule.__file__;
|
||||
obj['args'] = args || [];
|
||||
obj['kwargs'] = kwargs || {};
|
||||
frame.events.fireEvent("firePythonCallback", obj);
|
||||
|
||||
broker.sendMessage("firePythonCallback", obj);
|
||||
}
|
||||
|
||||
function timer (name) {
|
||||
this.name = name;
|
||||
this.timers = {};
|
||||
frame.timers.push(this);
|
||||
this.actions = [];
|
||||
|
||||
frame.timers.push(this);
|
||||
}
|
||||
|
||||
timer.prototype.start = function (name) {
|
||||
this.timers[name].startTime = (new Date).getTime();
|
||||
}
|
||||
|
||||
timer.prototype.stop = function (name) {
|
||||
var t = this.timers[name];
|
||||
|
||||
t.endTime = (new Date).getTime();
|
||||
t.totalTime = (t.endTime - t.startTime);
|
||||
}
|
||||
|
||||
timer.prototype.end = function () {
|
||||
frame.events.fireEvent("timer", this);
|
||||
frame.timers.remove(this);
|
||||
}
|
||||
|
||||
// Initialization
|
||||
|
||||
/**
|
||||
* Initialize Mozmill
|
||||
*/
|
||||
function initialize() {
|
||||
windows.init();
|
||||
}
|
||||
|
||||
initialize();
|
@ -0,0 +1,58 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['addListener', 'addObject',
|
||||
'removeListener',
|
||||
'sendMessage', 'log', 'pass', 'fail'];
|
||||
|
||||
var listeners = {};
|
||||
|
||||
// add a listener for a specific message type
|
||||
function addListener(msgType, listener) {
|
||||
if (listeners[msgType] === undefined) {
|
||||
listeners[msgType] = [];
|
||||
}
|
||||
|
||||
listeners[msgType].push(listener);
|
||||
}
|
||||
|
||||
// add each method in an object as a message listener
|
||||
function addObject(object) {
|
||||
for (var msgType in object) {
|
||||
addListener(msgType, object[msgType]);
|
||||
}
|
||||
}
|
||||
|
||||
// remove a listener for all message types
|
||||
function removeListener(listener) {
|
||||
for (var msgType in listeners) {
|
||||
for (let i = 0; i < listeners.length; ++i) {
|
||||
if (listeners[msgType][i] == listener) {
|
||||
listeners[msgType].splice(i, 1); // remove listener from array
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage(msgType, obj) {
|
||||
if (listeners[msgType] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < listeners[msgType].length; ++i) {
|
||||
listeners[msgType][i](obj);
|
||||
}
|
||||
}
|
||||
|
||||
function log(obj) {
|
||||
sendMessage('log', obj);
|
||||
}
|
||||
|
||||
function pass(obj) {
|
||||
sendMessage('pass', obj);
|
||||
}
|
||||
|
||||
function fail(obj) {
|
||||
sendMessage('fail', obj);
|
||||
}
|
@ -1,42 +1,174 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Use the frame module of Mozmill to raise non-fatal failures
|
||||
var mozmillFrame = {};
|
||||
Cu.import('resource://mozmill/modules/frame.js', mozmillFrame);
|
||||
var EXPORTED_SYMBOLS = ['Assert', 'Expect'];
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
|
||||
var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
|
||||
var stack = {}; Cu.import('resource://mozmill/modules/stack.js', stack);
|
||||
|
||||
/**
|
||||
* @name assertions
|
||||
* @namespace Defines expect and assert methods to be used for assertions.
|
||||
*/
|
||||
var assertions = exports;
|
||||
|
||||
/**
|
||||
* The Assert class implements fatal assertions, and can be used in cases
|
||||
* when a failing test has to directly abort the current test function. All
|
||||
* remaining tasks will not be performed.
|
||||
*
|
||||
*/
|
||||
var Assert = function () {}
|
||||
|
||||
/* non-fatal assertions */
|
||||
var Expect = function() {}
|
||||
Assert.prototype = {
|
||||
|
||||
Expect.prototype = {
|
||||
// The following deepEquals implementation is from Narwhal under this license:
|
||||
|
||||
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
|
||||
//
|
||||
// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
|
||||
//
|
||||
// Originally from narwhal.js (http://narwhaljs.org)
|
||||
// Copyright (c) 2009 Thomas Robinson <280north.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the 'Software'), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
_deepEqual: function (actual, expected) {
|
||||
// 7.1. All identical values are equivalent, as determined by ===.
|
||||
if (actual === expected) {
|
||||
return true;
|
||||
|
||||
// 7.2. If the expected value is a Date object, the actual value is
|
||||
// equivalent if it is also a Date object that refers to the same time.
|
||||
} else if (actual instanceof Date && expected instanceof Date) {
|
||||
return actual.getTime() === expected.getTime();
|
||||
|
||||
// 7.3. Other pairs that do not both pass typeof value == 'object',
|
||||
// equivalence is determined by ==.
|
||||
} else if (typeof actual != 'object' && typeof expected != 'object') {
|
||||
return actual == expected;
|
||||
|
||||
// 7.4. For all other Object pairs, including Array objects, equivalence is
|
||||
// determined by having the same number of owned properties (as verified
|
||||
// with Object.prototype.hasOwnProperty.call), the same set of keys
|
||||
// (although not necessarily the same order), equivalent values for every
|
||||
// corresponding key, and an identical 'prototype' property. Note: this
|
||||
// accounts for both named and indexed properties on Arrays.
|
||||
} else {
|
||||
return this._objEquiv(actual, expected);
|
||||
}
|
||||
},
|
||||
|
||||
_objEquiv: function (a, b) {
|
||||
if (a == null || a == undefined || b == null || b == undefined)
|
||||
return false;
|
||||
// an identical 'prototype' property.
|
||||
if (a.prototype !== b.prototype) return false;
|
||||
|
||||
function isArguments(object) {
|
||||
return Object.prototype.toString.call(object) == '[object Arguments]';
|
||||
}
|
||||
|
||||
//~~~I've managed to break Object.keys through screwy arguments passing.
|
||||
// Converting to array solves the problem.
|
||||
if (isArguments(a)) {
|
||||
if (!isArguments(b)) {
|
||||
return false;
|
||||
}
|
||||
a = pSlice.call(a);
|
||||
b = pSlice.call(b);
|
||||
return _deepEqual(a, b);
|
||||
}
|
||||
try {
|
||||
var ka = Object.keys(a),
|
||||
kb = Object.keys(b),
|
||||
key, i;
|
||||
} catch (e) {//happens when one is a string literal and the other isn't
|
||||
return false;
|
||||
}
|
||||
// having the same number of owned properties (keys incorporates
|
||||
// hasOwnProperty)
|
||||
if (ka.length != kb.length)
|
||||
return false;
|
||||
//the same set of keys (although not necessarily the same order),
|
||||
ka.sort();
|
||||
kb.sort();
|
||||
//~~~cheap key test
|
||||
for (i = ka.length - 1; i >= 0; i--) {
|
||||
if (ka[i] != kb[i])
|
||||
return false;
|
||||
}
|
||||
//equivalent values for every corresponding key, and
|
||||
//~~~possibly expensive deep test
|
||||
for (i = ka.length - 1; i >= 0; i--) {
|
||||
key = ka[i];
|
||||
if (!this._deepEqual(a[key], b[key])) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_expectedException : function Assert__expectedException(actual, expected) {
|
||||
if (!actual || !expected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected instanceof RegExp) {
|
||||
return expected.test(actual);
|
||||
} else if (actual instanceof expected) {
|
||||
return true;
|
||||
} else if (expected.call({}, actual) === true) {
|
||||
return true;
|
||||
} else if (actual.name === expected.name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Log a test as failing by adding a fail frame.
|
||||
* Log a test as failing by throwing an AssertionException.
|
||||
*
|
||||
* @param {object} aResult
|
||||
* Test result details used for reporting.
|
||||
* <dl>
|
||||
* <dd>fileName</dd>
|
||||
* <dt>Name of the file in which the assertion failed.</dt>
|
||||
* <dd>function</dd>
|
||||
* <dd>functionName</dd>
|
||||
* <dt>Function in which the assertion failed.</dt>
|
||||
* <dd>lineNumber</dd>
|
||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
||||
* <dd>message</dd>
|
||||
* <dt>Message why the assertion failed.</dt>
|
||||
* </dl>
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
*/
|
||||
_logFail: function Expect__logFail(aResult) {
|
||||
mozmillFrame.events.fail({fail: aResult});
|
||||
_logFail: function Assert__logFail(aResult) {
|
||||
throw new errors.AssertionError(aResult.message,
|
||||
aResult.fileName,
|
||||
aResult.lineNumber,
|
||||
aResult.functionName,
|
||||
aResult.name);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -47,7 +179,7 @@ Expect.prototype = {
|
||||
* <dl>
|
||||
* <dd>fileName</dd>
|
||||
* <dt>Name of the file in which the assertion failed.</dt>
|
||||
* <dd>function</dd>
|
||||
* <dd>functionName</dd>
|
||||
* <dt>Function in which the assertion failed.</dt>
|
||||
* <dd>lineNumber</dd>
|
||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
||||
@ -55,8 +187,8 @@ Expect.prototype = {
|
||||
* <dt>Message why the assertion failed.</dt>
|
||||
* </dl>
|
||||
*/
|
||||
_logPass: function Expect__logPass(aResult) {
|
||||
mozmillFrame.events.pass({pass: aResult});
|
||||
_logPass: function Assert__logPass(aResult) {
|
||||
broker.pass({pass: aResult});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -68,9 +200,11 @@ Expect.prototype = {
|
||||
* Message to show for the test result
|
||||
* @param {string} aDiagnosis
|
||||
* Diagnose message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
_test: function Expect__test(aCondition, aMessage, aDiagnosis) {
|
||||
_test: function Assert__test(aCondition, aMessage, aDiagnosis) {
|
||||
let diagnosis = aDiagnosis || "";
|
||||
let message = aMessage || "";
|
||||
|
||||
@ -78,19 +212,23 @@ Expect.prototype = {
|
||||
message = aMessage ? message + " - " + diagnosis : diagnosis;
|
||||
|
||||
// Build result data
|
||||
let frame = Components.stack;
|
||||
let frame = stack.findCallerFrame(Components.stack);
|
||||
|
||||
let result = {
|
||||
'fileName' : frame.filename.replace(/(.*)-> /, ""),
|
||||
'function' : frame.name,
|
||||
'lineNumber' : frame.lineNumber,
|
||||
'message' : message
|
||||
'fileName' : frame.filename.replace(/(.*)-> /, ""),
|
||||
'functionName' : frame.name,
|
||||
'lineNumber' : frame.lineNumber,
|
||||
'message' : message
|
||||
};
|
||||
|
||||
// Log test result
|
||||
if (aCondition)
|
||||
if (aCondition) {
|
||||
this._logPass(result);
|
||||
else
|
||||
}
|
||||
else {
|
||||
result.stack = Components.stack;
|
||||
this._logFail(result);
|
||||
}
|
||||
|
||||
return aCondition;
|
||||
},
|
||||
@ -102,7 +240,7 @@ Expect.prototype = {
|
||||
* Message to show for the test result.
|
||||
* @returns {boolean} Always returns true.
|
||||
*/
|
||||
pass: function Expect_pass(aMessage) {
|
||||
pass: function Assert_pass(aMessage) {
|
||||
return this._test(true, aMessage, undefined);
|
||||
},
|
||||
|
||||
@ -111,9 +249,11 @@ Expect.prototype = {
|
||||
*
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result.
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Always returns false.
|
||||
*/
|
||||
fail: function Expect_fail(aMessage) {
|
||||
fail: function Assert_fail(aMessage) {
|
||||
return this._test(false, aMessage, undefined);
|
||||
},
|
||||
|
||||
@ -124,16 +264,18 @@ Expect.prototype = {
|
||||
* Value to test.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result.
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
ok: function Expect_ok(aValue, aMessage) {
|
||||
ok: function Assert_ok(aValue, aMessage) {
|
||||
let condition = !!aValue;
|
||||
let diagnosis = "got '" + aValue + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Test if both specified values are identical.
|
||||
*
|
||||
* @param {boolean|string|number|object} aValue
|
||||
@ -142,16 +284,18 @@ Expect.prototype = {
|
||||
* Value to strictly compare with.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
equal: function Expect_equal(aValue, aExpected, aMessage) {
|
||||
equal: function Assert_equal(aValue, aExpected, aMessage) {
|
||||
let condition = (aValue === aExpected);
|
||||
let diagnosis = "got '" + aValue + "', expected '" + aExpected + "'";
|
||||
let diagnosis = "'" + aValue + "' should equal '" + aExpected + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Test if both specified values are not identical.
|
||||
*
|
||||
* @param {boolean|string|number|object} aValue
|
||||
@ -160,15 +304,81 @@ Expect.prototype = {
|
||||
* Value to strictly compare with.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
notEqual: function Expect_notEqual(aValue, aExpected, aMessage) {
|
||||
notEqual: function Assert_notEqual(aValue, aExpected, aMessage) {
|
||||
let condition = (aValue !== aExpected);
|
||||
let diagnosis = "got '" + aValue + "', not expected '" + aExpected + "'";
|
||||
let diagnosis = "'" + aValue + "' should not equal '" + aExpected + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if an object equals another object
|
||||
*
|
||||
* @param {object} aValue
|
||||
* The object to test.
|
||||
* @param {object} aExpected
|
||||
* The object to strictly compare with.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
deepEqual: function equal(aValue, aExpected, aMessage) {
|
||||
let condition = this._deepEqual(aValue, aExpected);
|
||||
try {
|
||||
var aValueString = JSON.stringify(aValue);
|
||||
} catch (e) {
|
||||
var aValueString = String(aValue);
|
||||
}
|
||||
try {
|
||||
var aExpectedString = JSON.stringify(aExpected);
|
||||
} catch (e) {
|
||||
var aExpectedString = String(aExpected);
|
||||
}
|
||||
|
||||
let diagnosis = "'" + aValueString + "' should equal '" +
|
||||
aExpectedString + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if an object does not equal another object
|
||||
*
|
||||
* @param {object} aValue
|
||||
* The object to test.
|
||||
* @param {object} aExpected
|
||||
* The object to strictly compare with.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
notDeepEqual: function notEqual(aValue, aExpected, aMessage) {
|
||||
let condition = !this._deepEqual(aValue, aExpected);
|
||||
try {
|
||||
var aValueString = JSON.stringify(aValue);
|
||||
} catch (e) {
|
||||
var aValueString = String(aValue);
|
||||
}
|
||||
try {
|
||||
var aExpectedString = JSON.stringify(aExpected);
|
||||
} catch (e) {
|
||||
var aExpectedString = String(aExpected);
|
||||
}
|
||||
|
||||
let diagnosis = "'" + aValueString + "' should not equal '" +
|
||||
aExpectedString + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if the regular expression matches the string.
|
||||
*
|
||||
@ -178,9 +388,11 @@ Expect.prototype = {
|
||||
* Regular expression to use for testing that a match exists.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
match: function Expect_match(aString, aRegex, aMessage) {
|
||||
match: function Assert_match(aString, aRegex, aMessage) {
|
||||
// XXX Bug 634948
|
||||
// Regex objects are transformed to strings when evaluated in a sandbox
|
||||
// For now lets re-create the regex from its string representation
|
||||
@ -190,8 +402,7 @@ Expect.prototype = {
|
||||
|
||||
pattern = matches[1];
|
||||
flags = matches[2];
|
||||
}
|
||||
catch (ex) {
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
let regex = new RegExp(pattern, flags);
|
||||
@ -210,9 +421,11 @@ Expect.prototype = {
|
||||
* Regular expression to use for testing that a match does not exist.
|
||||
* @param {string} aMessage
|
||||
* Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
notMatch: function Expect_notMatch(aString, aRegex, aMessage) {
|
||||
notMatch: function Assert_notMatch(aString, aRegex, aMessage) {
|
||||
// XXX Bug 634948
|
||||
// Regex objects are transformed to strings when evaluated in a sandbox
|
||||
// For now lets re-create the regex from its string representation
|
||||
@ -222,8 +435,7 @@ Expect.prototype = {
|
||||
|
||||
pattern = matches[1];
|
||||
flags = matches[2];
|
||||
}
|
||||
catch (ex) {
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
let regex = new RegExp(pattern, flags);
|
||||
@ -243,9 +455,11 @@ Expect.prototype = {
|
||||
* the expected error class
|
||||
* @param {string} message
|
||||
* message to present if assertion fails
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
throws : function Expect_throws(block, /*optional*/error, /*optional*/message) {
|
||||
throws : function Assert_throws(block, /*optional*/error, /*optional*/message) {
|
||||
return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
|
||||
},
|
||||
|
||||
@ -258,9 +472,11 @@ Expect.prototype = {
|
||||
* the expected error class
|
||||
* @param {string} message
|
||||
* message to present if assertion fails
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {boolean} Result of the test.
|
||||
*/
|
||||
doesNotThrow : function Expect_doesNotThrow(block, /*optional*/error, /*optional*/message) {
|
||||
doesNotThrow : function Assert_doesNotThrow(block, /*optional*/error, /*optional*/message) {
|
||||
return this._throws.apply(this, [false].concat(Array.prototype.slice.call(arguments)));
|
||||
},
|
||||
|
||||
@ -270,7 +486,7 @@ Expect.prototype = {
|
||||
adapted from node.js's assert._throws()
|
||||
https://github.com/joyent/node/blob/master/lib/assert.js
|
||||
*/
|
||||
_throws : function Expect__throws(shouldThrow, block, expected, message) {
|
||||
_throws : function Assert__throws(shouldThrow, block, expected, message) {
|
||||
var actual;
|
||||
|
||||
if (typeof expected === 'string') {
|
||||
@ -299,80 +515,153 @@ Expect.prototype = {
|
||||
!this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
|
||||
throw actual;
|
||||
}
|
||||
|
||||
return this._test(true, message);
|
||||
},
|
||||
|
||||
_expectedException : function Expect__expectedException(actual, expected) {
|
||||
if (!actual || !expected) {
|
||||
return false;
|
||||
/**
|
||||
* Test if the string contains the pattern.
|
||||
*
|
||||
* @param {String} aString String to test.
|
||||
* @param {String} aPattern Pattern to look for in the string
|
||||
* @param {String} aMessage Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {Boolean} Result of the test.
|
||||
*/
|
||||
contain: function Assert_contain(aString, aPattern, aMessage) {
|
||||
let condition = (aString.indexOf(aPattern) !== -1);
|
||||
let diagnosis = "'" + aString + "' should contain '" + aPattern + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if the string does not contain the pattern.
|
||||
*
|
||||
* @param {String} aString String to test.
|
||||
* @param {String} aPattern Pattern to look for in the string
|
||||
* @param {String} aMessage Message to show for the test result
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {Boolean} Result of the test.
|
||||
*/
|
||||
notContain: function Assert_notContain(aString, aPattern, aMessage) {
|
||||
let condition = (aString.indexOf(aPattern) === -1);
|
||||
let diagnosis = "'" + aString + "' should not contain '" + aPattern + "'";
|
||||
|
||||
return this._test(condition, aMessage, diagnosis);
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for the callback evaluates to true
|
||||
*
|
||||
* @param {Function} aCallback
|
||||
* Callback for evaluation
|
||||
* @param {String} aMessage
|
||||
* Message to show for result
|
||||
* @param {Number} aTimeout
|
||||
* Timeout in waiting for evaluation
|
||||
* @param {Number} aInterval
|
||||
* Interval between evaluation attempts
|
||||
* @param {Object} aThisObject
|
||||
* this object
|
||||
* @throws {errors.AssertionError}
|
||||
*
|
||||
* @returns {Boolean} Result of the test.
|
||||
*/
|
||||
waitFor: function Assert_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
|
||||
var timeout = aTimeout || 5000;
|
||||
var interval = aInterval || 100;
|
||||
|
||||
var self = {
|
||||
timeIsUp: false,
|
||||
result: aCallback.call(aThisObject)
|
||||
};
|
||||
var deadline = Date.now() + timeout;
|
||||
|
||||
function wait() {
|
||||
if (self.result !== true) {
|
||||
self.result = aCallback.call(aThisObject);
|
||||
self.timeIsUp = Date.now() > deadline;
|
||||
}
|
||||
}
|
||||
|
||||
if (expected instanceof RegExp) {
|
||||
return expected.test(actual);
|
||||
} else if (actual instanceof expected) {
|
||||
return true;
|
||||
} else if (expected.call({}, actual) === true) {
|
||||
return true;
|
||||
var hwindow = Services.appShell.hiddenDOMWindow;
|
||||
var timeoutInterval = hwindow.setInterval(wait, interval);
|
||||
var thread = Services.tm.currentThread;
|
||||
|
||||
while (self.result !== true && !self.timeIsUp) {
|
||||
thread.processNextEvent(true);
|
||||
|
||||
let type = typeof(self.result);
|
||||
if (type !== 'boolean')
|
||||
throw TypeError("waitFor() callback has to return a boolean" +
|
||||
" instead of '" + type + "'");
|
||||
}
|
||||
|
||||
return false;
|
||||
hwindow.clearInterval(timeoutInterval);
|
||||
|
||||
if (self.result !== true && self.timeIsUp) {
|
||||
aMessage = aMessage || arguments.callee.name + ": Timeout exceeded for '" + aCallback + "'";
|
||||
throw new errors.TimeoutError(aMessage);
|
||||
}
|
||||
|
||||
broker.pass({'function':'assert.waitFor()'});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AssertionError
|
||||
*
|
||||
* Error object thrown by failing assertions
|
||||
*/
|
||||
function AssertionError(message, fileName, lineNumber) {
|
||||
var err = new Error();
|
||||
if (err.stack) {
|
||||
this.stack = err.stack;
|
||||
}
|
||||
this.message = message === undefined ? err.message : message;
|
||||
this.fileName = fileName === undefined ? err.fileName : fileName;
|
||||
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
|
||||
};
|
||||
AssertionError.prototype = new Error();
|
||||
AssertionError.prototype.constructor = AssertionError;
|
||||
AssertionError.prototype.name = 'AssertionError';
|
||||
/* non-fatal assertions */
|
||||
var Expect = function () {}
|
||||
|
||||
|
||||
var Assert = function() {}
|
||||
|
||||
Assert.prototype = new Expect();
|
||||
|
||||
Assert.prototype.AssertionError = AssertionError;
|
||||
Expect.prototype = new Assert();
|
||||
|
||||
/**
|
||||
* The Assert class implements fatal assertions, and can be used in cases
|
||||
* when a failing test has to directly abort the current test function. All
|
||||
* remaining tasks will not be performed.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Log a test as failing by throwing an AssertionException.
|
||||
* Log a test as failing by adding a fail frame.
|
||||
*
|
||||
* @param {object} aResult
|
||||
* Test result details used for reporting.
|
||||
* <dl>
|
||||
* <dd>fileName</dd>
|
||||
* <dt>Name of the file in which the assertion failed.</dt>
|
||||
* <dd>function</dd>
|
||||
* <dd>functionName</dd>
|
||||
* <dt>Function in which the assertion failed.</dt>
|
||||
* <dd>lineNumber</dd>
|
||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
||||
* <dd>message</dd>
|
||||
* <dt>Message why the assertion failed.</dt>
|
||||
* </dl>
|
||||
* @throws {AssertionError }
|
||||
*/
|
||||
Assert.prototype._logFail = function Assert__logFail(aResult) {
|
||||
throw new AssertionError(aResult);
|
||||
Expect.prototype._logFail = function Expect__logFail(aResult) {
|
||||
broker.fail({fail: aResult});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the callback evaluates to true
|
||||
*
|
||||
* @param {Function} aCallback
|
||||
* Callback for evaluation
|
||||
* @param {String} aMessage
|
||||
* Message to show for result
|
||||
* @param {Number} aTimeout
|
||||
* Timeout in waiting for evaluation
|
||||
* @param {Number} aInterval
|
||||
* Interval between evaluation attempts
|
||||
* @param {Object} aThisObject
|
||||
* this object
|
||||
*/
|
||||
Expect.prototype.waitFor = function Expect_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
|
||||
let condition = true;
|
||||
let message = aMessage;
|
||||
|
||||
// Export of variables
|
||||
assertions.Expect = Expect;
|
||||
assertions.Assert = Assert;
|
||||
try {
|
||||
Assert.prototype.waitFor.apply(this, arguments);
|
||||
}
|
||||
catch (ex if ex instanceof errors.AssertionError) {
|
||||
message = ex.message;
|
||||
condition = false;
|
||||
}
|
||||
|
||||
return this._test(condition, message);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
290
services/sync/tps/extensions/mozmill/resource/modules/driver.js
Normal file
290
services/sync/tps/extensions/mozmill/resource/modules/driver.js
Normal file
@ -0,0 +1,290 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* @namespace Defines the Mozmill driver for global actions
|
||||
*/
|
||||
var driver = exports;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Temporarily include utils module to re-use sleep
|
||||
var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
|
||||
var mozmill = {}; Cu.import("resource://mozmill/driver/mozmill.js", mozmill);
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
|
||||
/**
|
||||
* Gets the topmost browser window. If there are none at that time, optionally
|
||||
* opens one. Otherwise will raise an exception if none are found.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Boolean] [aOpenIfNone=true] Open a new browser window if none are found.
|
||||
* @returns {DOMWindow}
|
||||
*/
|
||||
function getBrowserWindow(aOpenIfNone) {
|
||||
// Set default
|
||||
if (typeof aOpenIfNone === 'undefined') {
|
||||
aOpenIfNone = true;
|
||||
}
|
||||
|
||||
// If implicit open is off, turn on strict checking, and vice versa.
|
||||
let win = getTopmostWindowByType("navigator:browser", !aOpenIfNone);
|
||||
|
||||
// Can just assume automatic open here. If we didn't want it and nothing found,
|
||||
// we already raised above when getTopmostWindow was called.
|
||||
if (!win)
|
||||
win = openBrowserWindow();
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the hidden window on OS X
|
||||
*
|
||||
* @memberOf driver
|
||||
* @returns {DOMWindow} The hidden window
|
||||
*/
|
||||
function getHiddenWindow() {
|
||||
return Services.appShell.hiddenDOMWindow;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a new browser window
|
||||
*
|
||||
* @memberOf driver
|
||||
* @returns {DOMWindow}
|
||||
*/
|
||||
function openBrowserWindow() {
|
||||
// On OS X we have to be able to create a new browser window even with no other
|
||||
// window open. Therefore we have to use the hidden window. On other platforms
|
||||
// at least one remaining browser window has to exist.
|
||||
var win = mozmill.isMac ? getHiddenWindow() :
|
||||
getTopmostWindowByType("navigator:browser", true);
|
||||
return win.OpenBrowserWindow();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pause the test execution for the given amount of time
|
||||
*
|
||||
* @type utils.sleep
|
||||
* @memberOf driver
|
||||
*/
|
||||
var sleep = utils.sleep;
|
||||
|
||||
/**
|
||||
* Wait until the given condition via the callback returns true.
|
||||
*
|
||||
* @type utils.waitFor
|
||||
* @memberOf driver
|
||||
*/
|
||||
var waitFor = assertions.Assert.waitFor;
|
||||
|
||||
//
|
||||
// INTERNAL WINDOW ENUMERATIONS
|
||||
//
|
||||
|
||||
/**
|
||||
* Internal function to build a list of DOM windows using a given enumerator
|
||||
* and filter.
|
||||
*
|
||||
* @private
|
||||
* @memberOf driver
|
||||
* @param {nsISimpleEnumerator} aEnumerator Window enumerator to use.
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] Throw an error if no windows found
|
||||
*
|
||||
* @returns {DOMWindow[]} The windows found, in the same order as the enumerator.
|
||||
*/
|
||||
function _getWindows(aEnumerator, aFilterCallback, aStrict) {
|
||||
// Set default
|
||||
if (typeof aStrict === 'undefined')
|
||||
aStrict = true;
|
||||
|
||||
let windows = [];
|
||||
|
||||
while (aEnumerator.hasMoreElements()) {
|
||||
let window = aEnumerator.getNext();
|
||||
|
||||
if (!aFilterCallback || aFilterCallback(window)) {
|
||||
windows.push(window);
|
||||
}
|
||||
}
|
||||
|
||||
// If this list is empty and we're strict, throw an error
|
||||
if (windows.length === 0 && aStrict) {
|
||||
var message = 'No windows were found';
|
||||
|
||||
// We'll throw a more detailed error if a filter was used.
|
||||
if (aFilterCallback && aFilterCallback.name)
|
||||
message += ' using filter "' + aFilterCallback.name + '"';
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return windows;
|
||||
}
|
||||
|
||||
//
|
||||
// FILTER CALLBACKS
|
||||
//
|
||||
|
||||
/**
|
||||
* Generator of a closure to filter a window based by a method
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {String} aName Name of the method in the window object.
|
||||
* @returns {Boolean} True if the condition is met.
|
||||
*/
|
||||
function windowFilterByMethod(aName) {
|
||||
return function byMethod(aWindow) { return (aName in aWindow); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generator of a closure to filter a window based by the its title
|
||||
*
|
||||
* @param {String} aTitle Title of the window.
|
||||
* @returns {Boolean} True if the condition is met.
|
||||
*/
|
||||
function windowFilterByTitle(aTitle) {
|
||||
return function byTitle(aWindow) { return (aWindow.document.title === aTitle); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generator of a closure to filter a window based by the its type
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {String} aType Type of the window.
|
||||
* @returns {Boolean} True if the condition is met.
|
||||
*/
|
||||
function windowFilterByType(aType) {
|
||||
return function byType(aWindow) {
|
||||
var type = aWindow.document.documentElement.getAttribute("windowtype");
|
||||
return (type === aType);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// WINDOW LIST RETRIEVAL FUNCTIONS
|
||||
//
|
||||
|
||||
/**
|
||||
* Retrieves a sorted list of open windows based on their age (newest to oldest),
|
||||
* optionally matching filter criteria.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] Throw an error if no windows found
|
||||
*
|
||||
* @returns {DOMWindow[]} List of windows.
|
||||
*/
|
||||
function getWindowsByAge(aFilterCallback, aStrict) {
|
||||
var windows = _getWindows(Services.wm.getEnumerator(""),
|
||||
aFilterCallback, aStrict);
|
||||
|
||||
// Reverse the list, since naturally comes back old->new
|
||||
return windows.reverse();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a sorted list of open windows based on their z order (topmost first),
|
||||
* optionally matching filter criteria.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] Throw an error if no windows found
|
||||
*
|
||||
* @returns {DOMWindow[]} List of windows.
|
||||
*/
|
||||
function getWindowsByZOrder(aFilterCallback, aStrict) {
|
||||
return _getWindows(Services.wm.getZOrderDOMWindowEnumerator("", true),
|
||||
aFilterCallback, aStrict);
|
||||
}
|
||||
|
||||
//
|
||||
// SINGLE WINDOW RETRIEVAL FUNCTIONS
|
||||
//
|
||||
|
||||
/**
|
||||
* Retrieves the last opened window, optionally matching filter criteria.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] If true, throws error if no window found.
|
||||
*
|
||||
* @returns {DOMWindow} The window, or null if none found and aStrict == false
|
||||
*/
|
||||
function getNewestWindow(aFilterCallback, aStrict) {
|
||||
var windows = getWindowsByAge(aFilterCallback, aStrict);
|
||||
return windows.length ? windows[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the topmost window, optionally matching filter criteria.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {Function} [aFilterCallback] Function which is used to filter windows.
|
||||
* @param {Boolean} [aStrict=true] If true, throws error if no window found.
|
||||
*
|
||||
* @returns {DOMWindow} The window, or null if none found and aStrict == false
|
||||
*/
|
||||
function getTopmostWindow(aFilterCallback, aStrict) {
|
||||
var windows = getWindowsByZOrder(aFilterCallback, aStrict);
|
||||
return windows.length ? windows[0] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the topmost window given by the window type
|
||||
*
|
||||
* XXX: Bug 462222
|
||||
* This function has to be used instead of getTopmostWindow until the
|
||||
* underlying platform bug has been fixed.
|
||||
*
|
||||
* @memberOf driver
|
||||
* @param {String} [aWindowType=null] Window type to query for
|
||||
* @param {Boolean} [aStrict=true] Throw an error if no windows found
|
||||
*
|
||||
* @returns {DOMWindow} The window, or null if none found and aStrict == false
|
||||
*/
|
||||
function getTopmostWindowByType(aWindowType, aStrict) {
|
||||
if (typeof aStrict === 'undefined')
|
||||
aStrict = true;
|
||||
|
||||
var win = Services.wm.getMostRecentWindow(aWindowType);
|
||||
|
||||
if (win === null && aStrict) {
|
||||
var message = 'No windows of type "' + aWindowType + '" were found';
|
||||
throw new errors.UnexpectedError(message);
|
||||
}
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
|
||||
// Export of functions
|
||||
driver.getBrowserWindow = getBrowserWindow;
|
||||
driver.getHiddenWindow = getHiddenWindow;
|
||||
driver.openBrowserWindow = openBrowserWindow;
|
||||
driver.sleep = sleep;
|
||||
driver.waitFor = waitFor;
|
||||
|
||||
driver.windowFilterByMethod = windowFilterByMethod;
|
||||
driver.windowFilterByTitle = windowFilterByTitle;
|
||||
driver.windowFilterByType = windowFilterByType;
|
||||
|
||||
driver.getWindowsByAge = getWindowsByAge;
|
||||
driver.getNewestWindow = getNewestWindow;
|
||||
driver.getTopmostWindowByType = getTopmostWindowByType;
|
||||
|
||||
|
||||
// XXX Bug: 462222
|
||||
// Currently those functions cannot be used. So they shouldn't be exported.
|
||||
//driver.getWindowsByZOrder = getWindowsByZOrder;
|
||||
//driver.getTopmostWindow = getTopmostWindow;
|
102
services/sync/tps/extensions/mozmill/resource/modules/errors.js
Normal file
102
services/sync/tps/extensions/mozmill/resource/modules/errors.js
Normal file
@ -0,0 +1,102 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['BaseError',
|
||||
'ApplicationQuitError',
|
||||
'AssertionError',
|
||||
'TimeoutError'];
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of a base error
|
||||
*
|
||||
* @class Represents the base for custom errors
|
||||
* @param {string} [aMessage=Error().message]
|
||||
* The error message to show
|
||||
* @param {string} [aFileName=Error().fileName]
|
||||
* The file name where the error has been raised
|
||||
* @param {string} [aLineNumber=Error().lineNumber]
|
||||
* The line number of the file where the error has been raised
|
||||
* @param {string} [aFunctionName=undefined]
|
||||
* The function name in which the error has been raised
|
||||
*/
|
||||
function BaseError(aMessage, aFileName, aLineNumber, aFunctionName) {
|
||||
this.name = this.constructor.name;
|
||||
|
||||
var err = new Error();
|
||||
if (err.stack) {
|
||||
this.stack = err.stack;
|
||||
}
|
||||
|
||||
this.message = aMessage || err.message;
|
||||
this.fileName = aFileName || err.fileName;
|
||||
this.lineNumber = aLineNumber || err.lineNumber;
|
||||
this.functionName = aFunctionName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of an application quit error used by Mozmill to
|
||||
* indicate that the application is going to shutdown
|
||||
*
|
||||
* @class Represents an error object thrown when the application is going to shutdown
|
||||
* @param {string} [aMessage=Error().message]
|
||||
* The error message to show
|
||||
* @param {string} [aFileName=Error().fileName]
|
||||
* The file name where the error has been raised
|
||||
* @param {string} [aLineNumber=Error().lineNumber]
|
||||
* The line number of the file where the error has been raised
|
||||
* @param {string} [aFunctionName=undefined]
|
||||
* The function name in which the error has been raised
|
||||
*/
|
||||
function ApplicationQuitError(aMessage, aFileName, aLineNumber, aFunctionName) {
|
||||
BaseError.apply(this, arguments);
|
||||
}
|
||||
|
||||
ApplicationQuitError.prototype = Object.create(BaseError.prototype, {
|
||||
constructor : { value : ApplicationQuitError }
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of an assertion error
|
||||
*
|
||||
* @class Represents an error object thrown by failing assertions
|
||||
* @param {string} [aMessage=Error().message]
|
||||
* The error message to show
|
||||
* @param {string} [aFileName=Error().fileName]
|
||||
* The file name where the error has been raised
|
||||
* @param {string} [aLineNumber=Error().lineNumber]
|
||||
* The line number of the file where the error has been raised
|
||||
* @param {string} [aFunctionName=undefined]
|
||||
* The function name in which the error has been raised
|
||||
*/
|
||||
function AssertionError(aMessage, aFileName, aLineNumber, aFunctionName) {
|
||||
BaseError.apply(this, arguments);
|
||||
}
|
||||
|
||||
AssertionError.prototype = Object.create(BaseError.prototype, {
|
||||
constructor : { value : AssertionError }
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new instance of a timeout error
|
||||
*
|
||||
* @class Represents an error object thrown by failing assertions
|
||||
* @param {string} [aMessage=Error().message]
|
||||
* The error message to show
|
||||
* @param {string} [aFileName=Error().fileName]
|
||||
* The file name where the error has been raised
|
||||
* @param {string} [aLineNumber=Error().lineNumber]
|
||||
* The line number of the file where the error has been raised
|
||||
* @param {string} [aFunctionName=undefined]
|
||||
* The function name in which the error has been raised
|
||||
*/
|
||||
function TimeoutError(aMessage, aFileName, aLineNumber, aFunctionName) {
|
||||
AssertionError.apply(this, arguments);
|
||||
}
|
||||
|
||||
TimeoutError.prototype = Object.create(AssertionError.prototype, {
|
||||
constructor : { value : TimeoutError }
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -1,177 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
|
||||
|
||||
/**
|
||||
* Console listener which listens for error messages in the console and forwards
|
||||
* them to the Mozmill reporting system for output.
|
||||
*/
|
||||
function ConsoleListener() {
|
||||
this.register();
|
||||
}
|
||||
ConsoleListener.prototype = {
|
||||
observe: function(aMessage) {
|
||||
var msg = aMessage.message;
|
||||
var re = /^\[.*Error:.*(chrome|resource):\/\/.*/i;
|
||||
if (msg.match(re)) {
|
||||
frame.events.fail(aMessage);
|
||||
}
|
||||
},
|
||||
QueryInterface: function (iid) {
|
||||
if (!iid.equals(Components.interfaces.nsIConsoleListener) && !iid.equals(Components.interfaces.nsISupports)) {
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
register: function() {
|
||||
var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
|
||||
.getService(Components.interfaces.nsIConsoleService);
|
||||
aConsoleService.registerListener(this);
|
||||
},
|
||||
unregister: function() {
|
||||
var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
|
||||
.getService(Components.interfaces.nsIConsoleService);
|
||||
aConsoleService.unregisterListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
// start listening
|
||||
var consoleListener = new ConsoleListener();
|
||||
|
||||
var EXPORTED_SYMBOLS = ["mozmill"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
var mozmill = Cu.import('resource://mozmill/modules/mozmill.js');
|
||||
|
||||
// Observer for new top level windows
|
||||
var windowObserver = {
|
||||
observe: function(subject, topic, data) {
|
||||
attachEventListeners(subject);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach event listeners
|
||||
*/
|
||||
function attachEventListeners(window) {
|
||||
// These are the event handlers
|
||||
function pageShowHandler(event) {
|
||||
var doc = event.originalTarget;
|
||||
var tab = window.gBrowser.getBrowserForDocument(doc);
|
||||
|
||||
if (tab) {
|
||||
//log("*** Loaded tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
|
||||
tab.mozmillDocumentLoaded = true;
|
||||
} else {
|
||||
//log("*** Loaded HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
|
||||
doc.defaultView.mozmillDocumentLoaded = true;
|
||||
}
|
||||
|
||||
// We need to add/remove the unload/pagehide event listeners to preserve caching.
|
||||
window.gBrowser.addEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
window.gBrowser.addEventListener("pagehide", pageHideHandler, true);
|
||||
};
|
||||
|
||||
var DOMContentLoadedHandler = function(event) {
|
||||
var errorRegex = /about:.+(error)|(blocked)\?/;
|
||||
if (errorRegex.exec(event.target.baseURI)) {
|
||||
// Wait about 1s to be sure the DOM is ready
|
||||
mozmill.utils.sleep(1000);
|
||||
|
||||
var tab = window.gBrowser.getBrowserForDocument(event.target);
|
||||
if (tab)
|
||||
tab.mozmillDocumentLoaded = true;
|
||||
|
||||
// We need to add/remove the unload event listener to preserve caching.
|
||||
window.gBrowser.addEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
}
|
||||
};
|
||||
|
||||
// beforeunload is still needed because pagehide doesn't fire before the page is unloaded.
|
||||
// still use pagehide for cases when beforeunload doesn't get fired
|
||||
function beforeUnloadHandler(event) {
|
||||
var doc = event.originalTarget;
|
||||
var tab = window.gBrowser.getBrowserForDocument(event.target);
|
||||
|
||||
if (tab) {
|
||||
//log("*** Unload tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
|
||||
tab.mozmillDocumentLoaded = false;
|
||||
} else {
|
||||
//log("*** Unload HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
|
||||
doc.defaultView.mozmillDocumentLoaded = false;
|
||||
}
|
||||
|
||||
window.gBrowser.removeEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
};
|
||||
|
||||
var pageHideHandler = function(event) {
|
||||
// If event.persisted is false, the beforeUnloadHandler should fire
|
||||
// and there is no need for this event handler.
|
||||
if (event.persisted) {
|
||||
var doc = event.originalTarget;
|
||||
var tab = window.gBrowser.getBrowserForDocument(event.target);
|
||||
|
||||
if (tab) {
|
||||
//log("*** Unload tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
|
||||
tab.mozmillDocumentLoaded = false;
|
||||
} else {
|
||||
//log("*** Unload HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
|
||||
doc.defaultView.mozmillDocumentLoaded = false;
|
||||
}
|
||||
|
||||
window.gBrowser.removeEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Add the event handlers to the tabbedbrowser once its window has loaded
|
||||
window.addEventListener("load", function(event) {
|
||||
window.mozmillDocumentLoaded = true;
|
||||
|
||||
|
||||
if (window.gBrowser) {
|
||||
// Page is ready
|
||||
window.gBrowser.addEventListener("pageshow", pageShowHandler, true);
|
||||
|
||||
// Note: Error pages will never fire a "load" event. For those we
|
||||
// have to wait for the "DOMContentLoaded" event. That's the final state.
|
||||
// Error pages will always have a baseURI starting with
|
||||
// "about:" followed by "error" or "blocked".
|
||||
window.gBrowser.addEventListener("DOMContentLoaded", DOMContentLoadedHandler, true);
|
||||
|
||||
// Leave page (use caching)
|
||||
window.gBrowser.addEventListener("pagehide", pageHideHandler, true);
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Mozmill
|
||||
*/
|
||||
function initialize() {
|
||||
// Activate observer for new top level windows
|
||||
var observerService = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
observerService.addObserver(windowObserver, "toplevel-window-ready", false);
|
||||
|
||||
// Attach event listeners to all open windows
|
||||
var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
|
||||
getService(Ci.nsIWindowMediator).getEnumerator("");
|
||||
while (enumerator.hasMoreElements()) {
|
||||
var win = enumerator.getNext();
|
||||
attachEventListeners(win);
|
||||
|
||||
// For windows or dialogs already open we have to explicitly set the property
|
||||
// otherwise windows which load really quick never gets the property set and
|
||||
// we fail to create the controller
|
||||
win.mozmillDocumentLoaded = true;
|
||||
};
|
||||
}
|
||||
|
||||
initialize();
|
||||
|
@ -1,363 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["inspectElement"]
|
||||
|
||||
var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
|
||||
var mozmill = {}; Components.utils.import('resource://mozmill/modules/mozmill.js', mozmill);
|
||||
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
|
||||
|
||||
var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
|
||||
var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
|
||||
var objects = {}; Components.utils.import('resource://mozmill/stdlib/objects.js', objects);
|
||||
var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
|
||||
var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
|
||||
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
|
||||
var isNotAnonymous = function (elem, result) {
|
||||
if (result == undefined) {
|
||||
var result = true;
|
||||
}
|
||||
if ( elem.parentNode ) {
|
||||
var p = elem.parentNode;
|
||||
return isNotAnonymous(p, result == arrays.inArray(p.childNodes, elem) == true);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
var elemIsAnonymous = function (elem) {
|
||||
if (elem.getAttribute('anonid') || !arrays.inArray(elem.parentNode.childNodes, elem)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var getXPath = function (node, path) {
|
||||
path = path || [];
|
||||
|
||||
if(node.parentNode) {
|
||||
path = getXPath(node.parentNode, path);
|
||||
}
|
||||
|
||||
if(node.previousSibling) {
|
||||
var count = 1;
|
||||
var sibling = node.previousSibling
|
||||
do {
|
||||
if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {count++;}
|
||||
sibling = sibling.previousSibling;
|
||||
} while(sibling);
|
||||
if(count == 1) {count = null;}
|
||||
} else if(node.nextSibling) {
|
||||
var sibling = node.nextSibling;
|
||||
do {
|
||||
if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {
|
||||
var count = 1;
|
||||
sibling = null;
|
||||
} else {
|
||||
var count = null;
|
||||
sibling = sibling.previousSibling;
|
||||
}
|
||||
} while(sibling);
|
||||
}
|
||||
|
||||
if(node.nodeType == 1) {
|
||||
// if ($('absXpaths').checked){
|
||||
path.push(node.nodeName.toLowerCase() + (node.id ? "[@id='"+node.id+"']" : count > 0 ? "["+count+"]" : ''));
|
||||
// }
|
||||
// else{
|
||||
// path.push(node.nodeName.toLowerCase() + (node.id ? "" : count > 0 ? "["+count+"]" : ''));
|
||||
// }
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
function getXSPath(node){
|
||||
var xpArray = getXPath(node);
|
||||
var stringXpath = xpArray.join('/');
|
||||
stringXpath = '/'+stringXpath;
|
||||
stringXpath = stringXpath.replace('//','/');
|
||||
return stringXpath;
|
||||
}
|
||||
function getXULXpath (el, xml) {
|
||||
var xpath = '';
|
||||
var pos, tempitem2;
|
||||
|
||||
while(el !== xml.documentElement) {
|
||||
pos = 0;
|
||||
tempitem2 = el;
|
||||
while(tempitem2) {
|
||||
if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) {
|
||||
// If it is ELEMENT_NODE of the same name
|
||||
pos += 1;
|
||||
}
|
||||
tempitem2 = tempitem2.previousSibling;
|
||||
}
|
||||
|
||||
xpath = "*[name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
|
||||
|
||||
el = el.parentNode;
|
||||
}
|
||||
xpath = '/*'+"[name()='"+xml.documentElement.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']"+'/'+xpath;
|
||||
xpath = xpath.replace(/\/$/, '');
|
||||
return xpath;
|
||||
}
|
||||
|
||||
var getDocument = function (elem) {
|
||||
while (elem.parentNode) {
|
||||
var elem = elem.parentNode;
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
var getTopWindow = function(doc) {
|
||||
return utils.getChromeWindow(doc.defaultView);
|
||||
}
|
||||
|
||||
var attributeToIgnore = ['focus', 'focused', 'selected', 'select', 'flex', // General Omissions
|
||||
'linkedpanel', 'last-tab', 'afterselected', // From Tabs UI, thanks Farhad
|
||||
'style', // Gets set dynamically all the time, also effected by dx display code
|
||||
];
|
||||
|
||||
var getUniqueAttributesReduction = function (attributes, node) {
|
||||
for (var i in attributes) {
|
||||
if ( node.getAttribute(i) == attributes[i] || arrays.inArray(attributeToIgnore, i) || arrays.inArray(attributeToIgnore, attributes[i]) || i == 'id') {
|
||||
delete attributes[i];
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
var getLookupExpression = function (_document, elem) {
|
||||
expArray = [];
|
||||
while ( elem.parentNode ) {
|
||||
var exp = getLookupForElem(_document, elem);
|
||||
expArray.push(exp);
|
||||
var elem = elem.parentNode;
|
||||
}
|
||||
expArray.reverse();
|
||||
return '/' + expArray.join('/');
|
||||
}
|
||||
|
||||
var getLookupForElem = function (_document, elem) {
|
||||
if ( !elemIsAnonymous(elem) ) {
|
||||
if (elem.id != "" && !withs.startsWith(elem.id, 'panel')) {
|
||||
identifier = {'name':'id', 'value':elem.id};
|
||||
} else if ((elem.name != "") && (typeof(elem.name) != "undefined")) {
|
||||
identifier = {'name':'name', 'value':elem.name};
|
||||
} else {
|
||||
identifier = null;
|
||||
}
|
||||
|
||||
if (identifier) {
|
||||
var result = {'id':elementslib._byID, 'name':elementslib._byName}[identifier.name](_document, elem.parentNode, identifier.value);
|
||||
if ( typeof(result != 'array') ) {
|
||||
return identifier.name+'('+json2.JSON.stringify(identifier.value)+')';
|
||||
}
|
||||
}
|
||||
|
||||
// At this point there is either no identifier or it returns multiple
|
||||
var parse = [n for each (n in elem.parentNode.childNodes) if
|
||||
(n.getAttribute && n != elem)
|
||||
];
|
||||
parse.unshift(dom.getAttributes(elem));
|
||||
var uniqueAttributes = parse.reduce(getUniqueAttributesReduction);
|
||||
|
||||
if (!result) {
|
||||
var result = elementslib._byAttrib(elem.parentNode, uniqueAttributes);
|
||||
}
|
||||
|
||||
if (!identifier && typeof(result) == 'array' ) {
|
||||
return json2.JSON.stringify(uniqueAttributes) + '['+arrays.indexOf(result, elem)+']'
|
||||
} else {
|
||||
var aresult = elementslib._byAttrib(elem.parentNode, uniqueAttributes);
|
||||
if ( typeof(aresult != 'array') ) {
|
||||
if (objects.getLength(uniqueAttributes) == 0) {
|
||||
return '['+arrays.indexOf(elem.parentNode.childNodes, elem)+']'
|
||||
}
|
||||
return json2.JSON.stringify(uniqueAttributes)
|
||||
} else if ( result.length > aresult.length ) {
|
||||
return json2.JSON.stringify(uniqueAttributes) + '['+arrays.indexOf(aresult, elem)+']'
|
||||
} else {
|
||||
return identifier.name+'('+json2.JSON.stringify(identifier.value)+')' + '['+arrays.indexOf(result, elem)+']'
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Handle Anonymous Nodes
|
||||
var parse = [n for each (n in _document.getAnonymousNodes(elem.parentNode)) if
|
||||
(n.getAttribute && n != elem)
|
||||
];
|
||||
parse.unshift(dom.getAttributes(elem));
|
||||
var uniqueAttributes = parse.reduce(getUniqueAttributesReduction);
|
||||
if (uniqueAttributes.anonid && typeof(elementslib._byAnonAttrib(_document,
|
||||
elem.parentNode, {'anonid':uniqueAttributes.anonid})) != 'array') {
|
||||
uniqueAttributes = {'anonid':uniqueAttributes.anonid};
|
||||
}
|
||||
|
||||
if (objects.getLength(uniqueAttributes) == 0) {
|
||||
return 'anon(['+arrays.indexOf(_document.getAnonymousNodes(elem.parentNode), elem)+'])';
|
||||
} else if (arrays.inArray(uniqueAttributes, 'anonid')) {
|
||||
return 'anon({"anonid":"'+uniqueAttributes['anonid']+'"})';
|
||||
} else {
|
||||
return 'anon('+json2.JSON.stringify(uniqueAttributes)+')';
|
||||
}
|
||||
|
||||
}
|
||||
return 'broken '+elemIsAnonymous(elem)
|
||||
}
|
||||
|
||||
var removeHTMLTags = function(str){
|
||||
str = str.replace(/&(lt|gt);/g, function (strMatch, p1){
|
||||
return (p1 == "lt")? "<" : ">";
|
||||
});
|
||||
var strTagStrippedText = str.replace(/<\/?[^>]+(>|$)/g, "");
|
||||
strTagStrippedText = strTagStrippedText.replace(/ /g,"");
|
||||
return strTagStrippedText;
|
||||
}
|
||||
|
||||
var isMagicAnonymousDiv = function (_document, node) {
|
||||
if (node.getAttribute && node.getAttribute('class') == 'anonymous-div') {
|
||||
if (!arrays.inArray(node.parentNode.childNodes, node) && (_document.getAnonymousNodes(node) == null ||
|
||||
!arrays.inArray(_document.getAnonymousNodes(node), node) ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var copyToClipboard = function(str){
|
||||
const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"] .getService(Components.interfaces.nsIClipboardHelper);
|
||||
gClipboardHelper.copyString(str, _window.document);
|
||||
}
|
||||
|
||||
var getControllerAndDocument = function (_document, _window) {
|
||||
var windowtype = _window.document.documentElement.getAttribute('windowtype');
|
||||
var controllerString, documentString, activeTab;
|
||||
|
||||
// TODO replace with object based cases
|
||||
switch(windowtype) {
|
||||
case 'navigator:browser':
|
||||
controllerString = 'mozmill.getBrowserController()';
|
||||
activeTab = mozmill.getBrowserController().tabs.activeTab;
|
||||
break;
|
||||
case 'Browser:Preferences':
|
||||
controllerString = 'mozmill.getPreferencesController()';
|
||||
break;
|
||||
case 'Extension:Manager':
|
||||
controllerString = 'mozmill.getAddonsController()';
|
||||
break;
|
||||
default:
|
||||
if(windowtype)
|
||||
controllerString = 'new mozmill.controller.MozMillController(mozmill.utils.getWindowByType("' + windowtype + '"))';
|
||||
else if(_window.document.title)
|
||||
controllerString = 'new mozmill.controller.MozMillController(mozmill.utils.getWindowByTitle("'+_window.document.title+'"))';
|
||||
else
|
||||
controllerString = 'Cannot find window';
|
||||
break;
|
||||
}
|
||||
|
||||
if(activeTab == _document) {
|
||||
documentString = 'controller.tabs.activeTab';
|
||||
} else if(activeTab == _document.defaultView.top.document) {
|
||||
// if this document is from an iframe in the active tab
|
||||
var stub = getDocumentStub(_document, activeTab.defaultView);
|
||||
documentString = 'controller.tabs.activeTab.defaultView' + stub;
|
||||
} else {
|
||||
var stub = getDocumentStub(_document, _window);
|
||||
if(stub)
|
||||
documentString = 'controller.window' + stub;
|
||||
else
|
||||
documentString = 'Cannot find document';
|
||||
}
|
||||
return {'controllerString':controllerString, 'documentString':documentString}
|
||||
}
|
||||
|
||||
getDocumentStub = function( _document, _window) {
|
||||
if(_window.document == _document)
|
||||
return '.document';
|
||||
for(var i = 0; i < _window.frames.length; i++) {
|
||||
var stub = getDocumentStub(_document, _window.frames[i]);
|
||||
if (stub)
|
||||
return '.frames['+i+']' + stub;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
var inspectElement = function(e){
|
||||
if (e.originalTarget != undefined) {
|
||||
target = e.originalTarget;
|
||||
} else {
|
||||
target = e.target;
|
||||
}
|
||||
|
||||
//Element highlighting
|
||||
try {
|
||||
if (this.lastEvent)
|
||||
this.lastEvent.target.style.outline = "";
|
||||
} catch(err) {}
|
||||
|
||||
this.lastEvent = e;
|
||||
|
||||
try {
|
||||
e.target.style.outline = "1px solid darkblue";
|
||||
} catch(err){}
|
||||
|
||||
var _document = getDocument(target);
|
||||
|
||||
|
||||
if (isMagicAnonymousDiv(_document, target)) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
var windowtype = _document.documentElement.getAttribute('windowtype');
|
||||
var _window = getTopWindow(_document);
|
||||
r = getControllerAndDocument(_document, _window);
|
||||
|
||||
// displayText = "Controller: " + r.controllerString + '\n\n';
|
||||
if ( isNotAnonymous(target) ) {
|
||||
// Logic for which identifier to use is duplicated above
|
||||
if (target.id != "" && !withs.startsWith(target.id, 'panel')) {
|
||||
elemText = "new elementslib.ID("+ r.documentString + ', "' + target.id + '")';
|
||||
var telem = new elementslib.ID(_document, target.id);
|
||||
} else if ((target.name != "") && (typeof(target.name) != "undefined")) {
|
||||
elemText = "new elementslib.Name("+ r.documentString + ', "' + target.name + '")';
|
||||
var telem = new elementslib.Name(_document, target.name);
|
||||
} else if (target.nodeName == "A") {
|
||||
var linkText = removeHTMLTags(target.innerHTML);
|
||||
elemText = "new elementslib.Link("+ r.documentString + ', "' + linkText + '")';
|
||||
var telem = new elementslib.Link(_document, linkText);
|
||||
}
|
||||
}
|
||||
// Fallback on XPath
|
||||
if (telem == undefined || telem.getNode() != target) {
|
||||
if (windowtype == null) {
|
||||
var stringXpath = getXSPath(target);
|
||||
} else {
|
||||
var stringXpath = getXULXpath(target, _document);
|
||||
}
|
||||
var telem = new elementslib.XPath(_document, stringXpath);
|
||||
if ( telem.getNode() == target ) {
|
||||
elemText = "new elementslib.XPath("+ r.documentString + ', "' + stringXpath + '")';
|
||||
}
|
||||
}
|
||||
// Fallback to Lookup
|
||||
if (telem == undefined || telem.getNode() != target) {
|
||||
var exp = getLookupExpression(_document, target);
|
||||
elemText = "new elementslib.Lookup("+ r.documentString + ", '" + exp + "')";
|
||||
var telem = new elementslib.Lookup(_document, exp);
|
||||
}
|
||||
|
||||
return {'validation':( target == telem.getNode() ),
|
||||
'elementText':elemText,
|
||||
'elementType':telem.constructor.name,
|
||||
'controllerText':r.controllerString,
|
||||
'documentString':r.documentString,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,231 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["assert", "assertTrue", "assertFalse", "assertEquals", "assertNotEquals",
|
||||
"assertNull", "assertNotNull", "assertUndefined", "assertNotUndefined",
|
||||
"assertNaN", "assertNotNaN", "assertArrayContains", "fail", "pass"];
|
||||
|
||||
|
||||
// Array.isArray comes with JavaScript 1.8.5 (Firefox 4)
|
||||
// cf. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
|
||||
Array.isArray = Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]'; };
|
||||
|
||||
var frame = {}; Components.utils.import("resource://mozmill/modules/frame.js", frame);
|
||||
|
||||
var ifJSONable = function (v) {
|
||||
if (typeof(v) == 'function') {
|
||||
return undefined;
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
var assert = function (booleanValue, comment) {
|
||||
if (booleanValue) {
|
||||
frame.events.pass({'function':'jum.assert', 'value':ifJSONable(booleanValue), 'comment':comment});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assert', 'value':ifJSONable(booleanValue), 'comment':comment});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertTrue = function (booleanValue, comment) {
|
||||
if (typeof(booleanValue) != 'boolean') {
|
||||
frame.events.fail({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
|
||||
'message':'Bad argument, value type '+typeof(booleanValue)+' != "boolean"',
|
||||
'comment':comment});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (booleanValue) {
|
||||
frame.events.pass({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
|
||||
'comment':comment});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
|
||||
'comment':comment});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertFalse = function (booleanValue, comment) {
|
||||
if (typeof(booleanValue) != 'boolean') {
|
||||
frame.events.fail({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
|
||||
'message':'Bad argument, value type '+typeof(booleanValue)+' != "boolean"',
|
||||
'comment':comment});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!booleanValue) {
|
||||
frame.events.pass({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
|
||||
'comment':comment});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
|
||||
'comment':comment});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertEquals = function (value1, value2, comment) {
|
||||
// Case where value1 is an array
|
||||
if (Array.isArray(value1)) {
|
||||
|
||||
if (!Array.isArray(value2)) {
|
||||
frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
|
||||
'message':'Bad argument, value1 is an array and value2 type ' +
|
||||
typeof(value2)+' != "array"',
|
||||
'value2':ifJSONable(value2)});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value1.length != value2.length) {
|
||||
frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
|
||||
'message':"The arrays do not have the same length",
|
||||
'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < value1.length; i++) {
|
||||
if (value1[i] !== value2[i]) {
|
||||
frame.events.fail(
|
||||
{'function':'jum.assertEquals', 'comment':comment,
|
||||
'message':"The element of the arrays are different at index " + i,
|
||||
'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
frame.events.pass({'function':'jum.assertEquals', 'comment':comment,
|
||||
'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Case where value1 is not an array
|
||||
if (value1 == value2) {
|
||||
frame.events.pass({'function':'jum.assertEquals', 'comment':comment,
|
||||
'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
|
||||
'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertNotEquals = function (value1, value2, comment) {
|
||||
if (value1 != value2) {
|
||||
frame.events.pass({'function':'jum.assertNotEquals', 'comment':comment,
|
||||
'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertNotEquals', 'comment':comment,
|
||||
'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertNull = function (value, comment) {
|
||||
if (value == null) {
|
||||
frame.events.pass({'function':'jum.assertNull', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertNull', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertNotNull = function (value, comment) {
|
||||
if (value != null) {
|
||||
frame.events.pass({'function':'jum.assertNotNull', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertNotNull', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertUndefined = function (value, comment) {
|
||||
if (value == undefined) {
|
||||
frame.events.pass({'function':'jum.assertUndefined', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertUndefined', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertNotUndefined = function (value, comment) {
|
||||
if (value != undefined) {
|
||||
frame.events.pass({'function':'jum.assertNotUndefined', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertNotUndefined', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertNaN = function (value, comment) {
|
||||
if (isNaN(value)) {
|
||||
frame.events.pass({'function':'jum.assertNaN', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertNaN', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertNotNaN = function (value, comment) {
|
||||
if (!isNaN(value)) {
|
||||
frame.events.pass({'function':'jum.assertNotNaN', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return true;
|
||||
} else {
|
||||
frame.events.fail({'function':'jum.assertNotNaN', 'comment':comment,
|
||||
'value':ifJSONable(value)});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var assertArrayContains = function(array, value, comment) {
|
||||
if (!Array.isArray(array)) {
|
||||
frame.events.fail({'function':'jum.assertArrayContains', 'comment':comment,
|
||||
'message':'Bad argument, value type '+typeof(array)+' != "array"',
|
||||
'value':ifJSONable(array)});
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
if (array[i] === value) {
|
||||
frame.events.pass({'function':'jum.assertArrayContains', 'comment':comment,
|
||||
'value1':ifJSONable(array), 'value2':ifJSONable(value)});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
frame.events.fail({'function':'jum.assertArrayContains', 'comment':comment,
|
||||
'value1':ifJSONable(array), 'value2':ifJSONable(value)});
|
||||
return false;
|
||||
}
|
||||
|
||||
var fail = function (comment) {
|
||||
frame.events.fail({'function':'jum.fail', 'comment':comment});
|
||||
return false;
|
||||
}
|
||||
|
||||
var pass = function (comment) {
|
||||
frame.events.pass({'function':'jum.pass', 'comment':comment});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* @namespace Defines useful methods to work with localized content
|
||||
*/
|
||||
var l10n = exports;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
/**
|
||||
* Retrieve the localized content for a given DTD entity
|
||||
*
|
||||
@ -54,14 +56,11 @@ function getEntity(aDTDs, aEntityId) {
|
||||
* @returns {String} Value of the requested property
|
||||
*/
|
||||
function getProperty(aURL, aProperty) {
|
||||
var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
|
||||
getService(Ci.nsIStringBundleService);
|
||||
var bundle = sbs.createBundle(aURL);
|
||||
var bundle = Services.strings.createBundle(aURL);
|
||||
|
||||
try {
|
||||
return bundle.GetStringFromName(aProperty);
|
||||
}
|
||||
catch (ex) {
|
||||
} catch (ex) {
|
||||
throw new Error("Unkown property '" + aProperty + "'");
|
||||
}
|
||||
}
|
||||
|
@ -1,668 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
|
||||
"MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
|
||||
"MozMillTextBox", "subclasses",
|
||||
];
|
||||
|
||||
var EventUtils = {}; Components.utils.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
|
||||
var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
|
||||
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
|
||||
var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
|
||||
|
||||
// A list of all the subclasses available. Shared modules can push their own subclasses onto this list
|
||||
var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
|
||||
|
||||
/**
|
||||
* createInstance()
|
||||
*
|
||||
* Returns an new instance of a MozMillElement
|
||||
* The type of the element is automatically determined
|
||||
*/
|
||||
function createInstance(locatorType, locator, elem) {
|
||||
if (elem) {
|
||||
var args = {"element":elem};
|
||||
for (var i = 0; i < subclasses.length; ++i) {
|
||||
if (subclasses[i].isType(elem)) {
|
||||
return new subclasses[i](locatorType, locator, args);
|
||||
}
|
||||
}
|
||||
if (MozMillElement.isType(elem)) return new MozMillElement(locatorType, locator, args);
|
||||
}
|
||||
throw new Error("could not find element " + locatorType + ": " + locator);
|
||||
};
|
||||
|
||||
var Elem = function(node) {
|
||||
return createInstance("Elem", node, node);
|
||||
};
|
||||
|
||||
var Selector = function(_document, selector, index) {
|
||||
return createInstance("Selector", selector, elementslib.Selector(_document, selector, index));
|
||||
};
|
||||
|
||||
var ID = function(_document, nodeID) {
|
||||
return createInstance("ID", nodeID, elementslib.ID(_document, nodeID));
|
||||
};
|
||||
|
||||
var Link = function(_document, linkName) {
|
||||
return createInstance("Link", linkName, elementslib.Link(_document, linkName));
|
||||
};
|
||||
|
||||
var XPath = function(_document, expr) {
|
||||
return createInstance("XPath", expr, elementslib.XPath(_document, expr));
|
||||
};
|
||||
|
||||
var Name = function(_document, nName) {
|
||||
return createInstance("Name", nName, elementslib.Name(_document, nName));
|
||||
};
|
||||
|
||||
var Lookup = function(_document, expression) {
|
||||
return createInstance("Lookup", expression, elementslib.Lookup(_document, expression));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* MozMillElement
|
||||
* The base class for all mozmill elements
|
||||
*/
|
||||
function MozMillElement(locatorType, locator, args) {
|
||||
args = args || {};
|
||||
this._locatorType = locatorType;
|
||||
this._locator = locator;
|
||||
this._element = args["element"];
|
||||
this._document = args["document"];
|
||||
this._owner = args["owner"];
|
||||
// Used to maintain backwards compatibility with controller.js
|
||||
this.isElement = true;
|
||||
}
|
||||
|
||||
// Static method that returns true if node is of this element type
|
||||
MozMillElement.isType = function(node) {
|
||||
return true;
|
||||
};
|
||||
|
||||
// This getter is the magic behind lazy loading (note distinction between _element and element)
|
||||
MozMillElement.prototype.__defineGetter__("element", function() {
|
||||
if (this._element == undefined) {
|
||||
if (elementslib[this._locatorType]) {
|
||||
this._element = elementslib[this._locatorType](this._document, this._locator);
|
||||
} else if (this._locatorType == "Elem") {
|
||||
this._element = this._locator;
|
||||
} else {
|
||||
throw new Error("Unknown locator type: " + this._locatorType);
|
||||
}
|
||||
}
|
||||
return this._element;
|
||||
});
|
||||
|
||||
// Returns the actual wrapped DOM node
|
||||
MozMillElement.prototype.getNode = function() {
|
||||
return this.element;
|
||||
};
|
||||
|
||||
MozMillElement.prototype.getInfo = function() {
|
||||
return this._locatorType + ": " + this._locator;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sometimes an element which once existed will no longer exist in the DOM
|
||||
* This function re-searches for the element
|
||||
*/
|
||||
MozMillElement.prototype.exists = function() {
|
||||
this._element = undefined;
|
||||
if (this.element) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a keypress event on the given element
|
||||
*
|
||||
* @param {string} aKey
|
||||
* Key to use for synthesizing the keypress event. It can be a simple
|
||||
* character like "k" or a string like "VK_ESCAPE" for command keys
|
||||
* @param {object} aModifiers
|
||||
* Information about the modifier keys to send
|
||||
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
|
||||
* [optional - default: false]
|
||||
* altKey - Hold down the alt key
|
||||
* [optional - default: false]
|
||||
* ctrlKey - Hold down the ctrl key
|
||||
* [optional - default: false]
|
||||
* metaKey - Hold down the meta key (command key on Mac)
|
||||
* [optional - default: false]
|
||||
* shiftKey - Hold down the shift key
|
||||
* [optional - default: false]
|
||||
* @param {object} aExpectedEvent
|
||||
* Information about the expected event to occur
|
||||
* Elements: target - Element which should receive the event
|
||||
* [optional - default: current element]
|
||||
* type - Type of the expected key event
|
||||
*/
|
||||
MozMillElement.prototype.keypress = function(aKey, aModifiers, aExpectedEvent) {
|
||||
if (!this.element) {
|
||||
throw new Error("Could not find element " + this.getInfo());
|
||||
}
|
||||
|
||||
var win = this.element.ownerDocument? this.element.ownerDocument.defaultView : this.element;
|
||||
this.element.focus();
|
||||
|
||||
if (aExpectedEvent) {
|
||||
var target = aExpectedEvent.target? aExpectedEvent.target.getNode() : this.element;
|
||||
EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
|
||||
"MozMillElement.keypress()", win);
|
||||
} else {
|
||||
EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
|
||||
}
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.keypress()'});
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Synthesize a general mouse event on the given element
|
||||
*
|
||||
* @param {ElemBase} aTarget
|
||||
* Element which will receive the mouse event
|
||||
* @param {number} aOffsetX
|
||||
* Relative x offset in the elements bounds to click on
|
||||
* @param {number} aOffsetY
|
||||
* Relative y offset in the elements bounds to click on
|
||||
* @param {object} aEvent
|
||||
* Information about the event to send
|
||||
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
|
||||
* [optional - default: false]
|
||||
* altKey - Hold down the alt key
|
||||
* [optional - default: false]
|
||||
* button - Mouse button to use
|
||||
* [optional - default: 0]
|
||||
* clickCount - Number of counts to click
|
||||
* [optional - default: 1]
|
||||
* ctrlKey - Hold down the ctrl key
|
||||
* [optional - default: false]
|
||||
* metaKey - Hold down the meta key (command key on Mac)
|
||||
* [optional - default: false]
|
||||
* shiftKey - Hold down the shift key
|
||||
* [optional - default: false]
|
||||
* type - Type of the mouse event ('click', 'mousedown',
|
||||
* 'mouseup', 'mouseover', 'mouseout')
|
||||
* [optional - default: 'mousedown' + 'mouseup']
|
||||
* @param {object} aExpectedEvent
|
||||
* Information about the expected event to occur
|
||||
* Elements: target - Element which should receive the event
|
||||
* [optional - default: current element]
|
||||
* type - Type of the expected mouse event
|
||||
*/
|
||||
MozMillElement.prototype.mouseEvent = function(aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
|
||||
if (!this.element) {
|
||||
throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
|
||||
}
|
||||
|
||||
// If no offset is given we will use the center of the element to click on.
|
||||
var rect = this.element.getBoundingClientRect();
|
||||
if (isNaN(aOffsetX)) {
|
||||
aOffsetX = rect.width / 2;
|
||||
}
|
||||
if (isNaN(aOffsetY)) {
|
||||
aOffsetY = rect.height / 2;
|
||||
}
|
||||
|
||||
// Scroll element into view otherwise the click will fail
|
||||
if (this.element.scrollIntoView) {
|
||||
this.element.scrollIntoView();
|
||||
}
|
||||
|
||||
if (aExpectedEvent) {
|
||||
// The expected event type has to be set
|
||||
if (!aExpectedEvent.type)
|
||||
throw new Error(arguments.callee.name + ": Expected event type not specified");
|
||||
|
||||
// If no target has been specified use the specified element
|
||||
var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : this.element;
|
||||
if (!target) {
|
||||
throw new Error(arguments.callee.name + ": could not find element " + aExpectedEvent.target.getInfo());
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
|
||||
target, aExpectedEvent.event,
|
||||
"MozMillElement.mouseEvent()",
|
||||
this.element.ownerDocument.defaultView);
|
||||
} else {
|
||||
EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
|
||||
this.element.ownerDocument.defaultView);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a mouse click event on the given element
|
||||
*/
|
||||
MozMillElement.prototype.click = function(left, top, expectedEvent) {
|
||||
// Handle menu items differently
|
||||
if (this.element && this.element.tagName == "menuitem") {
|
||||
this.element.click();
|
||||
} else {
|
||||
this.mouseEvent(left, top, {}, expectedEvent);
|
||||
}
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.click()'});
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a double click on the given element
|
||||
*/
|
||||
MozMillElement.prototype.doubleClick = function(left, top, expectedEvent) {
|
||||
this.mouseEvent(left, top, {clickCount: 2}, expectedEvent);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.doubleClick()'});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a mouse down event on the given element
|
||||
*/
|
||||
MozMillElement.prototype.mouseDown = function (button, left, top, expectedEvent) {
|
||||
this.mouseEvent(left, top, {button: button, type: "mousedown"}, expectedEvent);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.mouseDown()'});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a mouse out event on the given element
|
||||
*/
|
||||
MozMillElement.prototype.mouseOut = function (button, left, top, expectedEvent) {
|
||||
this.mouseEvent(left, top, {button: button, type: "mouseout"}, expectedEvent);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.mouseOut()'});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a mouse over event on the given element
|
||||
*/
|
||||
MozMillElement.prototype.mouseOver = function (button, left, top, expectedEvent) {
|
||||
this.mouseEvent(left, top, {button: button, type: "mouseover"}, expectedEvent);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.mouseOver()'});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a mouse up event on the given element
|
||||
*/
|
||||
MozMillElement.prototype.mouseUp = function (button, left, top, expectedEvent) {
|
||||
this.mouseEvent(left, top, {button: button, type: "mouseup"}, expectedEvent);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.mouseUp()'});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a mouse middle click event on the given element
|
||||
*/
|
||||
MozMillElement.prototype.middleClick = function(left, top, expectedEvent) {
|
||||
this.mouseEvent(left, top, {button: 1}, expectedEvent);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.middleClick()'});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a mouse right click event on the given element
|
||||
*/
|
||||
MozMillElement.prototype.rightClick = function(left, top, expectedEvent) {
|
||||
this.mouseEvent(left, top, {type : "contextmenu", button: 2 }, expectedEvent);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.rightClick()'});
|
||||
return true;
|
||||
};
|
||||
|
||||
MozMillElement.prototype.waitForElement = function(timeout, interval) {
|
||||
var elem = this;
|
||||
utils.waitFor(function() {
|
||||
return elem.exists();
|
||||
}, "Timeout exceeded for waitForElement " + this.getInfo(), timeout, interval);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.waitForElement()'});
|
||||
};
|
||||
|
||||
MozMillElement.prototype.waitForElementNotPresent = function(timeout, interval) {
|
||||
var elem = this;
|
||||
utils.waitFor(function() {
|
||||
return !elem.exists();
|
||||
}, "Timeout exceeded for waitForElementNotPresent " + this.getInfo(), timeout, interval);
|
||||
|
||||
frame.events.pass({'function':'MozMillElement.waitForElementNotPresent()'});
|
||||
};
|
||||
|
||||
MozMillElement.prototype.waitThenClick = function (timeout, interval, left, top, expectedEvent) {
|
||||
this.waitForElement(timeout, interval);
|
||||
this.click(left, top, expectedEvent);
|
||||
};
|
||||
|
||||
// Dispatches an HTMLEvent
|
||||
MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
|
||||
canBubble = canBubble || true;
|
||||
var evt = this.element.ownerDocument.createEvent('HTMLEvents');
|
||||
evt.shiftKey = modifiers["shift"];
|
||||
evt.metaKey = modifiers["meta"];
|
||||
evt.altKey = modifiers["alt"];
|
||||
evt.ctrlKey = modifiers["ctrl"];
|
||||
evt.initEvent(eventType, canBubble, true);
|
||||
this.element.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* MozMillCheckBox
|
||||
* Checkbox element, inherits from MozMillElement
|
||||
*/
|
||||
MozMillCheckBox.prototype = new MozMillElement();
|
||||
MozMillCheckBox.prototype.parent = MozMillElement.prototype;
|
||||
MozMillCheckBox.prototype.constructor = MozMillCheckBox;
|
||||
function MozMillCheckBox(locatorType, locator, args) {
|
||||
this.parent.constructor.call(this, locatorType, locator, args);
|
||||
}
|
||||
|
||||
// Static method returns true if node is this type of element
|
||||
MozMillCheckBox.isType = function(node) {
|
||||
if ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
|
||||
(node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
|
||||
(node.localName.toLowerCase() == 'checkbox')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable/Disable a checkbox depending on the target state
|
||||
*/
|
||||
MozMillCheckBox.prototype.check = function(state) {
|
||||
var result = false;
|
||||
|
||||
if (!this.element) {
|
||||
throw new Error("could not find element " + this.getInfo());
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have a XUL element, unwrap its XPCNativeWrapper
|
||||
if (this.element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
|
||||
this.element = utils.unwrapNode(this.element);
|
||||
}
|
||||
|
||||
state = (typeof(state) == "boolean") ? state : false;
|
||||
if (state != this.element.checked) {
|
||||
this.click();
|
||||
var element = this.element;
|
||||
utils.waitFor(function() {
|
||||
return element.checked == state;
|
||||
}, "Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
|
||||
|
||||
result = true;
|
||||
}
|
||||
|
||||
frame.events.pass({'function':'MozMillCheckBox.check(' + this.getInfo() + ', state: ' + state + ')'});
|
||||
return result;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* MozMillRadio
|
||||
* Radio button inherits from MozMillElement
|
||||
*/
|
||||
MozMillRadio.prototype = new MozMillElement();
|
||||
MozMillRadio.prototype.parent = MozMillElement.prototype;
|
||||
MozMillRadio.prototype.constructor = MozMillRadio;
|
||||
function MozMillRadio(locatorType, locator, args) {
|
||||
this.parent.constructor.call(this, locatorType, locator, args);
|
||||
}
|
||||
|
||||
// Static method returns true if node is this type of element
|
||||
MozMillRadio.isType = function(node) {
|
||||
if ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
|
||||
(node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
|
||||
(node.localName.toLowerCase() == 'radio') ||
|
||||
(node.localName.toLowerCase() == 'radiogroup')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the given radio button
|
||||
*
|
||||
* index - Specifies which radio button in the group to select (only applicable to radiogroup elements)
|
||||
* Defaults to the first radio button in the group
|
||||
*/
|
||||
MozMillRadio.prototype.select = function(index) {
|
||||
if (!this.element) {
|
||||
throw new Error("could not find element " + this.getInfo());
|
||||
}
|
||||
|
||||
if (this.element.localName.toLowerCase() == "radiogroup") {
|
||||
var element = this.element.getElementsByTagName("radio")[index || 0];
|
||||
new MozMillRadio("Elem", element).click();
|
||||
} else {
|
||||
var element = this.element;
|
||||
this.click();
|
||||
}
|
||||
|
||||
utils.waitFor(function() {
|
||||
// If we have a XUL element, unwrap its XPCNativeWrapper
|
||||
if (element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
|
||||
element = utils.unwrapNode(element);
|
||||
return element.selected == true;
|
||||
}
|
||||
return element.checked == true;
|
||||
}, "Radio button " + this.getInfo() + " could not be selected", 500);
|
||||
|
||||
frame.events.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
|
||||
return true;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* MozMillDropList
|
||||
* DropList inherits from MozMillElement
|
||||
*/
|
||||
MozMillDropList.prototype = new MozMillElement();
|
||||
MozMillDropList.prototype.parent = MozMillElement.prototype;
|
||||
MozMillDropList.prototype.constructor = MozMillDropList;
|
||||
function MozMillDropList(locatorType, locator, args) {
|
||||
this.parent.constructor.call(this, locatorType, locator, args);
|
||||
};
|
||||
|
||||
// Static method returns true if node is this type of element
|
||||
MozMillDropList.isType = function(node) {
|
||||
if ((node.localName.toLowerCase() == 'toolbarbutton' && (node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
|
||||
(node.localName.toLowerCase() == 'menu') ||
|
||||
(node.localName.toLowerCase() == 'menulist') ||
|
||||
(node.localName.toLowerCase() == 'select' )) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/* Select the specified option and trigger the relevant events of the element */
|
||||
MozMillDropList.prototype.select = function (indx, option, value) {
|
||||
if (!this.element){
|
||||
throw new Error("Could not find element " + this.getInfo());
|
||||
}
|
||||
|
||||
//if we have a select drop down
|
||||
if (this.element.localName.toLowerCase() == "select"){
|
||||
var item = null;
|
||||
|
||||
// The selected item should be set via its index
|
||||
if (indx != undefined) {
|
||||
// Resetting a menulist has to be handled separately
|
||||
if (indx == -1) {
|
||||
this.dispatchEvent('focus', false);
|
||||
this.element.selectedIndex = indx;
|
||||
this.dispatchEvent('change', true);
|
||||
|
||||
frame.events.pass({'function':'MozMillDropList.select()'});
|
||||
return true;
|
||||
} else {
|
||||
item = this.element.options.item(indx);
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < this.element.options.length; i++) {
|
||||
var entry = this.element.options.item(i);
|
||||
if (option != undefined && entry.innerHTML == option ||
|
||||
value != undefined && entry.value == value) {
|
||||
item = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Click the item
|
||||
try {
|
||||
// EventUtils.synthesizeMouse doesn't work.
|
||||
this.dispatchEvent('focus', false);
|
||||
item.selected = true;
|
||||
this.dispatchEvent('change', true);
|
||||
|
||||
frame.events.pass({'function':'MozMillDropList.select()'});
|
||||
return true;
|
||||
} catch (ex) {
|
||||
throw new Error("No item selected for element " + this.getInfo());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//if we have a xul menupopup select accordingly
|
||||
else if (this.element.namespaceURI.toLowerCase() == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
|
||||
var ownerDoc = this.element.ownerDocument;
|
||||
// Unwrap the XUL element's XPCNativeWrapper
|
||||
this.element = utils.unwrapNode(this.element);
|
||||
// Get the list of menuitems
|
||||
menuitems = this.element.getElementsByTagName("menupopup")[0].getElementsByTagName("menuitem");
|
||||
|
||||
var item = null;
|
||||
|
||||
if (indx != undefined) {
|
||||
if (indx == -1) {
|
||||
this.dispatchEvent('focus', false);
|
||||
this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild = null;
|
||||
this.dispatchEvent('change', true);
|
||||
|
||||
frame.events.pass({'function':'MozMillDropList.select()'});
|
||||
return true;
|
||||
} else {
|
||||
item = menuitems[indx];
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < menuitems.length; i++) {
|
||||
var entry = menuitems[i];
|
||||
if (option != undefined && entry.label == option ||
|
||||
value != undefined && entry.value == value) {
|
||||
item = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Click the item
|
||||
try {
|
||||
EventUtils.synthesizeMouse(this.element, 1, 1, {}, ownerDoc.defaultView);
|
||||
|
||||
// Scroll down until item is visible
|
||||
for (var i = 0; i <= menuitems.length; ++i) {
|
||||
var selected = this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild;
|
||||
if (item == selected) {
|
||||
break;
|
||||
}
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, ownerDoc.defaultView);
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(item, 1, 1, {}, ownerDoc.defaultView);
|
||||
|
||||
frame.events.pass({'function':'MozMillDropList.select()'});
|
||||
return true;
|
||||
} catch (ex) {
|
||||
throw new Error('No item selected for element ' + this.getInfo());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* MozMillTextBox
|
||||
* TextBox inherits from MozMillElement
|
||||
*/
|
||||
MozMillTextBox.prototype = new MozMillElement();
|
||||
MozMillTextBox.prototype.parent = MozMillElement.prototype;
|
||||
MozMillTextBox.prototype.constructor = MozMillTextBox;
|
||||
function MozMillTextBox(locatorType, locator, args) {
|
||||
this.parent.constructor.call(this, locatorType, locator, args);
|
||||
};
|
||||
|
||||
// Static method returns true if node is this type of element
|
||||
MozMillTextBox.isType = function(node) {
|
||||
if ((node.localName.toLowerCase() == 'input' && (node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
|
||||
(node.localName.toLowerCase() == 'textarea') ||
|
||||
(node.localName.toLowerCase() == 'textbox')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize keypress events for each character on the given element
|
||||
*
|
||||
* @param {string} aText
|
||||
* The text to send as single keypress events
|
||||
* @param {object} aModifiers
|
||||
* Information about the modifier keys to send
|
||||
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
|
||||
* [optional - default: false]
|
||||
* altKey - Hold down the alt key
|
||||
* [optional - default: false]
|
||||
* ctrlKey - Hold down the ctrl key
|
||||
* [optional - default: false]
|
||||
* metaKey - Hold down the meta key (command key on Mac)
|
||||
* [optional - default: false]
|
||||
* shiftKey - Hold down the shift key
|
||||
* [optional - default: false]
|
||||
* @param {object} aExpectedEvent
|
||||
* Information about the expected event to occur
|
||||
* Elements: target - Element which should receive the event
|
||||
* [optional - default: current element]
|
||||
* type - Type of the expected key event
|
||||
*/
|
||||
MozMillTextBox.prototype.sendKeys = function (aText, aModifiers, aExpectedEvent) {
|
||||
if (!this.element) {
|
||||
throw new Error("could not find element " + this.getInfo());
|
||||
}
|
||||
|
||||
var element = this.element;
|
||||
Array.forEach(aText, function(letter) {
|
||||
var win = element.ownerDocument? element.ownerDocument.defaultView : element;
|
||||
element.focus();
|
||||
|
||||
if (aExpectedEvent) {
|
||||
var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : element;
|
||||
EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target, aExpectedEvent.type,
|
||||
"MozMillTextBox.sendKeys()", win);
|
||||
} else {
|
||||
EventUtils.synthesizeKey(letter, aModifiers || {}, win);
|
||||
}
|
||||
});
|
||||
|
||||
frame.events.pass({'function':'MozMillTextBox.type()'});
|
||||
return true;
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['findCallerFrame'];
|
||||
|
||||
|
||||
/**
|
||||
* @namespace Defines utility methods for handling stack frames
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find the frame to use for logging the test result. If a start frame has
|
||||
* been specified, we walk down the stack until a frame with the same filename
|
||||
* as the start frame has been found. The next file in the stack will be the
|
||||
* frame to use for logging the result.
|
||||
*
|
||||
* @memberOf stack
|
||||
* @param {Object} [aStartFrame=Components.stack] Frame to start from walking up the stack.
|
||||
* @returns {Object} Frame of the stack to use for logging the result.
|
||||
*/
|
||||
function findCallerFrame(aStartFrame) {
|
||||
let frame = Components.stack;
|
||||
let filename = frame.filename.replace(/(.*)-> /, "");
|
||||
|
||||
// If a start frame has been specified, walk up the stack until we have
|
||||
// found the corresponding file
|
||||
if (aStartFrame) {
|
||||
filename = aStartFrame.filename.replace(/(.*)-> /, "");
|
||||
|
||||
while (frame.caller &&
|
||||
frame.filename && (frame.filename.indexOf(filename) == -1)) {
|
||||
frame = frame.caller;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk even up more until the next file has been found
|
||||
while (frame.caller &&
|
||||
(!frame.filename || (frame.filename.indexOf(filename) != -1)))
|
||||
frame = frame.caller;
|
||||
|
||||
return frame;
|
||||
}
|
@ -1,522 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["openFile", "saveFile", "saveAsFile", "genBoiler",
|
||||
"getFile", "Copy", "getChromeWindow", "getWindows", "runEditor",
|
||||
"runFile", "getWindowByTitle", "getWindowByType", "tempfile",
|
||||
"getMethodInWindows", "getPreference", "setPreference",
|
||||
"sleep", "assert", "unwrapNode", "TimeoutError", "waitFor",
|
||||
"takeScreenshot",
|
||||
];
|
||||
|
||||
var hwindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
|
||||
.getService(Components.interfaces.nsIAppShellService)
|
||||
.hiddenDOMWindow;
|
||||
|
||||
var uuidgen = Components.classes["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Components.interfaces.nsIUUIDGenerator);
|
||||
|
||||
function Copy (obj) {
|
||||
for (var n in obj) {
|
||||
this[n] = obj[n];
|
||||
}
|
||||
}
|
||||
|
||||
function getChromeWindow(aWindow) {
|
||||
var chromeWin = aWindow
|
||||
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIWebNavigation)
|
||||
.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindow)
|
||||
.QueryInterface(Components.interfaces.nsIDOMChromeWindow);
|
||||
return chromeWin;
|
||||
}
|
||||
|
||||
function getWindows(type) {
|
||||
if (type == undefined) {
|
||||
type = "";
|
||||
}
|
||||
var windows = []
|
||||
var enumerator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator)
|
||||
.getEnumerator(type);
|
||||
while(enumerator.hasMoreElements()) {
|
||||
windows.push(enumerator.getNext());
|
||||
}
|
||||
if (type == "") {
|
||||
windows.push(hwindow);
|
||||
}
|
||||
return windows;
|
||||
}
|
||||
|
||||
function getMethodInWindows (methodName) {
|
||||
for each(w in getWindows()) {
|
||||
if (w[methodName] != undefined) {
|
||||
return w[methodName];
|
||||
}
|
||||
}
|
||||
throw new Error("Method with name: '" + methodName + "' is not in any open window.");
|
||||
}
|
||||
|
||||
function getWindowByTitle(title) {
|
||||
for each(w in getWindows()) {
|
||||
if (w.document.title && w.document.title == title) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getWindowByType(type) {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
return wm.getMostRecentWindow(type);
|
||||
}
|
||||
|
||||
function tempfile(appention) {
|
||||
if (appention == undefined) {
|
||||
var appention = "mozmill.utils.tempfile"
|
||||
}
|
||||
var tempfile = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties).get("TmpD", Components.interfaces.nsIFile);
|
||||
tempfile.append(uuidgen.generateUUID().toString().replace('-', '').replace('{', '').replace('}',''))
|
||||
tempfile.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0777);
|
||||
tempfile.append(appention);
|
||||
tempfile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
|
||||
// do whatever you need to the created file
|
||||
return tempfile.clone()
|
||||
}
|
||||
|
||||
var checkChrome = function() {
|
||||
var loc = window.document.location.href;
|
||||
try {
|
||||
loc = window.top.document.location.href;
|
||||
} catch (e) {}
|
||||
|
||||
if (/^chrome:\/\//.test(loc)) { return true; }
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
|
||||
var runFile = function(w){
|
||||
//define the interface
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
//define the file picker window
|
||||
fp.init(w, "Select a File", nsIFilePicker.modeOpen);
|
||||
fp.appendFilter("JavaScript Files","*.js");
|
||||
//show the window
|
||||
var res = fp.show();
|
||||
//if we got a file
|
||||
if (res == nsIFilePicker.returnOK){
|
||||
var thefile = fp.file;
|
||||
//create the paramObj with a files array attrib
|
||||
var paramObj = {};
|
||||
paramObj.files = [];
|
||||
paramObj.files.push(thefile.path);
|
||||
}
|
||||
};
|
||||
|
||||
var saveFile = function(w, content, filename){
|
||||
//define the file interface
|
||||
var file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
//point it at the file we want to get at
|
||||
file.initWithPath(filename);
|
||||
|
||||
// file is nsIFile, data is a string
|
||||
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileOutputStream);
|
||||
|
||||
// use 0x02 | 0x10 to open file for appending.
|
||||
foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
|
||||
// write, create, truncate
|
||||
// In a c file operation, we have no need to set file mode with or operation,
|
||||
// directly using "r" or "w" usually.
|
||||
|
||||
foStream.write(content, content.length);
|
||||
foStream.close();
|
||||
};
|
||||
|
||||
var saveAsFile = function(w, content){
|
||||
//define the interface
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
//define the file picker window
|
||||
fp.init(w, "Select a File", nsIFilePicker.modeSave);
|
||||
fp.appendFilter("JavaScript Files","*.js");
|
||||
//show the window
|
||||
var res = fp.show();
|
||||
//if we got a file
|
||||
if ((res == nsIFilePicker.returnOK) || (res == nsIFilePicker.returnReplace)){
|
||||
var thefile = fp.file;
|
||||
|
||||
//forcing the user to save as a .js file
|
||||
if (thefile.path.indexOf(".js") == -1){
|
||||
//define the file interface
|
||||
var file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
//point it at the file we want to get at
|
||||
file.initWithPath(thefile.path+".js");
|
||||
var thefile = file;
|
||||
}
|
||||
|
||||
// file is nsIFile, data is a string
|
||||
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileOutputStream);
|
||||
|
||||
// use 0x02 | 0x10 to open file for appending.
|
||||
foStream.init(thefile, 0x02 | 0x08 | 0x20, 0666, 0);
|
||||
// write, create, truncate
|
||||
// In a c file operation, we have no need to set file mode with or operation,
|
||||
// directly using "r" or "w" usually.
|
||||
foStream.write(content, content.length);
|
||||
foStream.close();
|
||||
return thefile.path;
|
||||
}
|
||||
};
|
||||
|
||||
var openFile = function(w){
|
||||
//define the interface
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
//define the file picker window
|
||||
fp.init(w, "Select a File", nsIFilePicker.modeOpen);
|
||||
fp.appendFilter("JavaScript Files","*.js");
|
||||
//show the window
|
||||
var res = fp.show();
|
||||
//if we got a file
|
||||
if (res == nsIFilePicker.returnOK){
|
||||
var thefile = fp.file;
|
||||
//create the paramObj with a files array attrib
|
||||
var data = getFile(thefile.path);
|
||||
|
||||
return {path:thefile.path, data:data};
|
||||
}
|
||||
};
|
||||
|
||||
var getFile = function(path){
|
||||
//define the file interface
|
||||
var file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
//point it at the file we want to get at
|
||||
file.initWithPath(path);
|
||||
// define file stream interfaces
|
||||
var data = "";
|
||||
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||
fstream.init(file, -1, 0, 0);
|
||||
sstream.init(fstream);
|
||||
|
||||
//pull the contents of the file out
|
||||
var str = sstream.read(4096);
|
||||
while (str.length > 0) {
|
||||
data += str;
|
||||
str = sstream.read(4096);
|
||||
}
|
||||
|
||||
sstream.close();
|
||||
fstream.close();
|
||||
|
||||
//data = data.replace(/\r|\n|\r\n/g, "");
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called to get the state of an individual preference.
|
||||
*
|
||||
* @param aPrefName string The preference to get the state of.
|
||||
* @param aDefaultValue any The default value if preference was not found.
|
||||
*
|
||||
* @returns any The value of the requested preference
|
||||
*
|
||||
* @see setPref
|
||||
* Code by Henrik Skupin: <hskupin@gmail.com>
|
||||
*/
|
||||
function getPreference(aPrefName, aDefaultValue) {
|
||||
try {
|
||||
var branch = Components.classes["@mozilla.org/preferences-service;1"].
|
||||
getService(Components.interfaces.nsIPrefBranch);
|
||||
switch (typeof aDefaultValue) {
|
||||
case ('boolean'):
|
||||
return branch.getBoolPref(aPrefName);
|
||||
case ('string'):
|
||||
return branch.getCharPref(aPrefName);
|
||||
case ('number'):
|
||||
return branch.getIntPref(aPrefName);
|
||||
default:
|
||||
return branch.getComplexValue(aPrefName);
|
||||
}
|
||||
} catch(e) {
|
||||
return aDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to set the state of an individual preference.
|
||||
*
|
||||
* @param aPrefName string The preference to set the state of.
|
||||
* @param aValue any The value to set the preference to.
|
||||
*
|
||||
* @returns boolean Returns true if value was successfully set.
|
||||
*
|
||||
* @see getPref
|
||||
* Code by Henrik Skupin: <hskupin@gmail.com>
|
||||
*/
|
||||
function setPreference(aName, aValue) {
|
||||
try {
|
||||
var branch = Components.classes["@mozilla.org/preferences-service;1"].
|
||||
getService(Components.interfaces.nsIPrefBranch);
|
||||
switch (typeof aValue) {
|
||||
case ('boolean'):
|
||||
branch.setBoolPref(aName, aValue);
|
||||
break;
|
||||
case ('string'):
|
||||
branch.setCharPref(aName, aValue);
|
||||
break;
|
||||
case ('number'):
|
||||
branch.setIntPref(aName, aValue);
|
||||
break;
|
||||
default:
|
||||
branch.setComplexValue(aName, aValue);
|
||||
}
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep for the given amount of milliseconds
|
||||
*
|
||||
* @param {number} milliseconds
|
||||
* Sleeps the given number of milliseconds
|
||||
*/
|
||||
function sleep(milliseconds) {
|
||||
// We basically just call this once after the specified number of milliseconds
|
||||
var timeup = false;
|
||||
function wait() { timeup = true; }
|
||||
hwindow.setTimeout(wait, milliseconds);
|
||||
|
||||
var thread = Components.classes["@mozilla.org/thread-manager;1"].
|
||||
getService().currentThread;
|
||||
while(!timeup) {
|
||||
thread.processNextEvent(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the callback function evaluates to true
|
||||
*/
|
||||
function assert(callback, message, thisObject) {
|
||||
var result = callback.call(thisObject);
|
||||
|
||||
if (!result) {
|
||||
throw new Error(message || arguments.callee.name + ": Failed for '" + callback + "'");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (node.wrappedJSObject != null) {
|
||||
node = node.wrappedJSObject;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* TimeoutError
|
||||
*
|
||||
* Error object used for timeouts
|
||||
*/
|
||||
function TimeoutError(message, fileName, lineNumber) {
|
||||
var err = new Error();
|
||||
if (err.stack) {
|
||||
this.stack = err.stack;
|
||||
}
|
||||
this.message = message === undefined ? err.message : message;
|
||||
this.fileName = fileName === undefined ? err.fileName : fileName;
|
||||
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
|
||||
};
|
||||
TimeoutError.prototype = new Error();
|
||||
TimeoutError.prototype.constructor = TimeoutError;
|
||||
TimeoutError.prototype.name = 'TimeoutError';
|
||||
|
||||
/**
|
||||
* Waits for the callback evaluates to true
|
||||
*/
|
||||
function waitFor(callback, message, timeout, interval, thisObject) {
|
||||
timeout = timeout || 5000;
|
||||
interval = interval || 100;
|
||||
|
||||
var self = {counter: 0, result: callback.call(thisObject)};
|
||||
|
||||
function wait() {
|
||||
self.counter += interval;
|
||||
self.result = callback.call(thisObject);
|
||||
}
|
||||
|
||||
var timeoutInterval = hwindow.setInterval(wait, interval);
|
||||
var thread = Components.classes["@mozilla.org/thread-manager;1"].
|
||||
getService().currentThread;
|
||||
|
||||
while((self.result != true) && (self.counter < timeout)) {
|
||||
thread.processNextEvent(true);
|
||||
}
|
||||
|
||||
hwindow.clearInterval(timeoutInterval);
|
||||
|
||||
if (self.counter >= timeout) {
|
||||
message = message || arguments.callee.name + ": Timeout exceeded for '" + callback + "'";
|
||||
throw new TimeoutError(message);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the x and y chrome offset for an element
|
||||
* See https://developer.mozilla.org/en/DOM/window.innerHeight
|
||||
*
|
||||
* Note this function will not work if the user has custom toolbars (via extension) at the bottom or left/right of the screen
|
||||
*/
|
||||
function getChromeOffset(elem) {
|
||||
var win = elem.ownerDocument.defaultView;
|
||||
// Calculate x offset
|
||||
var chromeWidth = 0;
|
||||
if (win["name"] != "sidebar") {
|
||||
chromeWidth = win.outerWidth - win.innerWidth;
|
||||
}
|
||||
|
||||
// Calculate y offset
|
||||
var chromeHeight = win.outerHeight - win.innerHeight;
|
||||
// chromeHeight == 0 means elem is already in the chrome and doesn't need the addonbar offset
|
||||
if (chromeHeight > 0) {
|
||||
// window.innerHeight doesn't include the addon or find bar, so account for these if present
|
||||
var addonbar = win.document.getElementById("addon-bar");
|
||||
if (addonbar) {
|
||||
chromeHeight -= addonbar.scrollHeight;
|
||||
}
|
||||
var findbar = win.document.getElementById("FindToolbar");
|
||||
if (findbar) {
|
||||
chromeHeight -= findbar.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return {'x':chromeWidth, 'y':chromeHeight};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a screenshot of the specified DOM node
|
||||
*/
|
||||
function takeScreenshot(node, name, highlights) {
|
||||
var rect, win, width, height, left, top, needsOffset;
|
||||
// node can be either a window or an arbitrary DOM node
|
||||
try {
|
||||
win = node.ownerDocument.defaultView; // node is an arbitrary DOM node
|
||||
rect = node.getBoundingClientRect();
|
||||
width = rect.width;
|
||||
height = rect.height;
|
||||
top = rect.top;
|
||||
left = rect.left;
|
||||
// offset for highlights not needed as they will be relative to this node
|
||||
needsOffset = false;
|
||||
} catch (e) {
|
||||
win = node; // node is a window
|
||||
width = win.innerWidth;
|
||||
height = win.innerHeight;
|
||||
top = 0;
|
||||
left = 0;
|
||||
// offset needed for highlights to take 'outerHeight' of window into account
|
||||
needsOffset = true;
|
||||
}
|
||||
|
||||
var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
var ctx = canvas.getContext("2d");
|
||||
// Draws the DOM contents of the window to the canvas
|
||||
ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
|
||||
|
||||
// This section is for drawing a red rectangle around each element passed in via the highlights array
|
||||
if (highlights) {
|
||||
ctx.lineWidth = "2";
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.save();
|
||||
|
||||
for (var i = 0; i < highlights.length; ++i) {
|
||||
var elem = highlights[i];
|
||||
rect = elem.getBoundingClientRect();
|
||||
|
||||
var offsetY = 0, offsetX = 0;
|
||||
if (needsOffset) {
|
||||
var offset = getChromeOffset(elem);
|
||||
offsetX = offset.x;
|
||||
offsetY = offset.y;
|
||||
} else {
|
||||
// Don't need to offset the window chrome, just make relative to containing node
|
||||
offsetY = -top;
|
||||
offsetX = -left;
|
||||
}
|
||||
|
||||
// Draw the rectangle
|
||||
ctx.strokeRect(rect.left + offsetX, rect.top + offsetY, rect.width, rect.height);
|
||||
}
|
||||
} // end highlights
|
||||
|
||||
// if there is a name save the file, else return dataURL
|
||||
if (name) {
|
||||
return saveCanvas(canvas, name);
|
||||
}
|
||||
return canvas.toDataURL("image/png","");
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a canvas as input and saves it to the file tempdir/name.png
|
||||
* Returns the filepath of the saved file
|
||||
*/
|
||||
function saveCanvas(canvas, name) {
|
||||
var file = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties)
|
||||
.get("TmpD", Components.interfaces.nsIFile);
|
||||
file.append("mozmill_screens");
|
||||
file.append(name + ".png");
|
||||
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
|
||||
|
||||
// create a data url from the canvas and then create URIs of the source and targets
|
||||
var io = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
var source = io.newURI(canvas.toDataURL("image/png", ""), "UTF8", null);
|
||||
var target = io.newFileURI(file)
|
||||
|
||||
// prepare to save the canvas data
|
||||
var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
|
||||
.createInstance(Components.interfaces.nsIWebBrowserPersist);
|
||||
|
||||
persist.persistFlags = Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
|
||||
persist.persistFlags |= Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
|
||||
|
||||
// save the canvas data to the file
|
||||
persist.saveURI(source, null, null, null, null, file);
|
||||
|
||||
return file.path;
|
||||
}
|
292
services/sync/tps/extensions/mozmill/resource/modules/windows.js
Normal file
292
services/sync/tps/extensions/mozmill/resource/modules/windows.js
Normal file
@ -0,0 +1,292 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["init", "map"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
// imports
|
||||
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
|
||||
|
||||
var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
/**
|
||||
* The window map is used to store information about the current state of
|
||||
* open windows, e.g. loaded state
|
||||
*/
|
||||
var map = {
|
||||
_windows : { },
|
||||
|
||||
/**
|
||||
* Check if a given window id is contained in the map of windows
|
||||
*
|
||||
* @param {Number} aWindowId
|
||||
* Outer ID of the window to check.
|
||||
* @returns {Boolean} True if the window is part of the map, otherwise false.
|
||||
*/
|
||||
contains : function (aWindowId) {
|
||||
return (aWindowId in this._windows);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the value of the specified window's property.
|
||||
*
|
||||
* @param {Number} aWindowId
|
||||
* Outer ID of the window to check.
|
||||
* @param {String} aProperty
|
||||
* Property to retrieve the value from
|
||||
* @return {Object} Value of the window's property
|
||||
*/
|
||||
getValue : function (aWindowId, aProperty) {
|
||||
if (!this.contains(aWindowId)) {
|
||||
return undefined;
|
||||
} else {
|
||||
var win = this._windows[aWindowId];
|
||||
|
||||
return (aProperty in win) ? win[aProperty]
|
||||
: undefined;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the entry for a given window
|
||||
*
|
||||
* @param {Number} aWindowId
|
||||
* Outer ID of the window to check.
|
||||
*/
|
||||
remove : function (aWindowId) {
|
||||
if (this.contains(aWindowId)) {
|
||||
delete this._windows[aWindowId];
|
||||
}
|
||||
|
||||
// dump("* current map: " + JSON.stringify(this._windows) + "\n");
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the property value of a given window
|
||||
*
|
||||
* @param {Number} aWindowId
|
||||
* Outer ID of the window to check.
|
||||
* @param {String} aProperty
|
||||
* Property to update the value for
|
||||
* @param {Object}
|
||||
* Value to set
|
||||
*/
|
||||
update : function (aWindowId, aProperty, aValue) {
|
||||
if (!this.contains(aWindowId)) {
|
||||
this._windows[aWindowId] = { };
|
||||
}
|
||||
|
||||
this._windows[aWindowId][aProperty] = aValue;
|
||||
// dump("* current map: " + JSON.stringify(this._windows) + "\n");
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the internal loaded state of the given content window. To identify
|
||||
* an active (re)load action we make use of an uuid.
|
||||
*
|
||||
* @param {Window} aId - The outer id of the window to update
|
||||
* @param {Boolean} aIsLoaded - Has the window been loaded
|
||||
*/
|
||||
updatePageLoadStatus : function (aId, aIsLoaded) {
|
||||
this.update(aId, "loaded", aIsLoaded);
|
||||
|
||||
var uuid = this.getValue(aId, "id_load_in_transition");
|
||||
|
||||
// If no uuid has been set yet or when the page gets unloaded create a new id
|
||||
if (!uuid || !aIsLoaded) {
|
||||
uuid = uuidgen.generateUUID();
|
||||
this.update(aId, "id_load_in_transition", uuid);
|
||||
}
|
||||
|
||||
// dump("*** Page status updated: id=" + aId + ", loaded=" + aIsLoaded + ", uuid=" + uuid + "\n");
|
||||
},
|
||||
|
||||
/**
|
||||
* This method only applies to content windows, where we have to check if it has
|
||||
* been successfully loaded or reloaded. An uuid allows us to wait for the next
|
||||
* load action triggered by e.g. controller.open().
|
||||
*
|
||||
* @param {Window} aId - The outer id of the content window to check
|
||||
*
|
||||
* @returns {Boolean} True if the content window has been loaded
|
||||
*/
|
||||
hasPageLoaded : function (aId) {
|
||||
var load_current = this.getValue(aId, "id_load_in_transition");
|
||||
var load_handled = this.getValue(aId, "id_load_handled");
|
||||
|
||||
var isLoaded = this.contains(aId) && this.getValue(aId, "loaded") &&
|
||||
(load_current !== load_handled);
|
||||
|
||||
if (isLoaded) {
|
||||
// Backup the current uuid so we can check later if another page load happened.
|
||||
this.update(aId, "id_load_handled", load_current);
|
||||
}
|
||||
|
||||
// dump("** Page has been finished loading: id=" + aId + ", status=" + isLoaded + ", uuid=" + load_current + "\n");
|
||||
|
||||
return isLoaded;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Observer when a new top-level window is ready
|
||||
var windowReadyObserver = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
// Not in all cases we get a ChromeWindow. So ensure we really operate
|
||||
// on such an instance. Otherwise load events will not be handled.
|
||||
var win = utils.getChromeWindow(aSubject);
|
||||
|
||||
// var id = utils.getWindowId(win);
|
||||
// dump("*** 'toplevel-window-ready' observer notification: id=" + id + "\n");
|
||||
attachEventListeners(win);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Observer when a top-level window is closed
|
||||
var windowCloseObserver = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
var id = utils.getWindowId(aSubject);
|
||||
// dump("*** 'outer-window-destroyed' observer notification: id=" + id + "\n");
|
||||
|
||||
map.remove(id);
|
||||
}
|
||||
};
|
||||
|
||||
// Bug 915554
|
||||
// Support for the old Private Browsing Mode (eg. ESR17)
|
||||
// TODO: remove once ESR17 is no longer supported
|
||||
var enterLeavePrivateBrowsingObserver = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
handleAttachEventListeners();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach event listeners
|
||||
*
|
||||
* @param {ChromeWindow} aWindow
|
||||
* Window to attach listeners on.
|
||||
*/
|
||||
function attachEventListeners(aWindow) {
|
||||
// These are the event handlers
|
||||
var pageShowHandler = function (aEvent) {
|
||||
var doc = aEvent.originalTarget;
|
||||
|
||||
// Only update the flag if we have a document as target
|
||||
// see https://bugzilla.mozilla.org/show_bug.cgi?id=690829
|
||||
if ("defaultView" in doc) {
|
||||
var id = utils.getWindowId(doc.defaultView);
|
||||
// dump("*** 'pageshow' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
|
||||
map.updatePageLoadStatus(id, true);
|
||||
}
|
||||
|
||||
// We need to add/remove the unload/pagehide event listeners to preserve caching.
|
||||
aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
aWindow.addEventListener("pagehide", pageHideHandler, true);
|
||||
};
|
||||
|
||||
var DOMContentLoadedHandler = function (aEvent) {
|
||||
var doc = aEvent.originalTarget;
|
||||
|
||||
// Only update the flag if we have a document as target
|
||||
if ("defaultView" in doc) {
|
||||
var id = utils.getWindowId(doc.defaultView);
|
||||
// dump("*** 'DOMContentLoaded' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
|
||||
|
||||
// We only care about error pages for DOMContentLoaded
|
||||
var errorRegex = /about:.+(error)|(blocked)\?/;
|
||||
if (errorRegex.exec(doc.baseURI)) {
|
||||
// Wait about 1s to be sure the DOM is ready
|
||||
utils.sleep(1000);
|
||||
|
||||
map.updatePageLoadStatus(id, true);
|
||||
}
|
||||
|
||||
// We need to add/remove the unload event listener to preserve caching.
|
||||
aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
}
|
||||
};
|
||||
|
||||
// beforeunload is still needed because pagehide doesn't fire before the page is unloaded.
|
||||
// still use pagehide for cases when beforeunload doesn't get fired
|
||||
var beforeUnloadHandler = function (aEvent) {
|
||||
var doc = aEvent.originalTarget;
|
||||
|
||||
// Only update the flag if we have a document as target
|
||||
if ("defaultView" in doc) {
|
||||
var id = utils.getWindowId(doc.defaultView);
|
||||
// dump("*** 'beforeunload' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
|
||||
map.updatePageLoadStatus(id, false);
|
||||
}
|
||||
|
||||
aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
};
|
||||
|
||||
var pageHideHandler = function (aEvent) {
|
||||
var doc = aEvent.originalTarget;
|
||||
|
||||
// Only update the flag if we have a document as target
|
||||
if ("defaultView" in doc) {
|
||||
var id = utils.getWindowId(doc.defaultView);
|
||||
// dump("*** 'pagehide' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
|
||||
map.updatePageLoadStatus(id, false);
|
||||
}
|
||||
// If event.persisted is true the beforeUnloadHandler would never fire
|
||||
// and we have to remove the event handler here to avoid memory leaks.
|
||||
if (aEvent.persisted)
|
||||
aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
|
||||
};
|
||||
|
||||
var onWindowLoaded = function (aEvent) {
|
||||
var id = utils.getWindowId(aWindow);
|
||||
// dump("*** 'load' event: id=" + id + ", baseURI=" + aWindow.document.baseURI + "\n");
|
||||
|
||||
map.update(id, "loaded", true);
|
||||
|
||||
// Note: Error pages will never fire a "pageshow" event. For those we
|
||||
// have to wait for the "DOMContentLoaded" event. That's the final state.
|
||||
// Error pages will always have a baseURI starting with
|
||||
// "about:" followed by "error" or "blocked".
|
||||
aWindow.addEventListener("DOMContentLoaded", DOMContentLoadedHandler, true);
|
||||
|
||||
// Page is ready
|
||||
aWindow.addEventListener("pageshow", pageShowHandler, true);
|
||||
|
||||
// Leave page (use caching)
|
||||
aWindow.addEventListener("pagehide", pageHideHandler, true);
|
||||
};
|
||||
|
||||
// If the window has already been finished loading, call the load handler
|
||||
// directly. Otherwise attach it to the current window.
|
||||
if (aWindow.document.readyState === 'complete') {
|
||||
onWindowLoaded();
|
||||
} else {
|
||||
aWindow.addEventListener("load", onWindowLoaded, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach event listeners to all already open top-level windows
|
||||
function handleAttachEventListeners() {
|
||||
var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
|
||||
getService(Ci.nsIWindowMediator).getEnumerator("");
|
||||
while (enumerator.hasMoreElements()) {
|
||||
var win = enumerator.getNext();
|
||||
attachEventListeners(win);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Activate observer for new top level windows
|
||||
var observerService = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
observerService.addObserver(windowReadyObserver, "toplevel-window-ready", false);
|
||||
observerService.addObserver(windowCloseObserver, "outer-window-destroyed", false);
|
||||
observerService.addObserver(enterLeavePrivateBrowsingObserver, "private-browsing", false);
|
||||
|
||||
handleAttachEventListeners();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,49 +1,65 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['inArray', 'getSet', 'indexOf', 'rindexOf', 'compare'];
|
||||
var EXPORTED_SYMBOLS = ['inArray', 'getSet', 'indexOf',
|
||||
'remove', 'rindexOf', 'compare'];
|
||||
|
||||
function inArray (array, value) {
|
||||
for (i in array) {
|
||||
|
||||
function remove(array, from, to) {
|
||||
var rest = array.slice((to || from) + 1 || array.length);
|
||||
array.length = from < 0 ? array.length + from : from;
|
||||
|
||||
return array.push.apply(array, rest);
|
||||
}
|
||||
|
||||
function inArray(array, value) {
|
||||
for (var i in array) {
|
||||
if (value == array[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSet (array) {
|
||||
function getSet(array) {
|
||||
var narray = [];
|
||||
for (i in array) {
|
||||
if ( !inArray(narray, array[i]) ) {
|
||||
|
||||
for (var i in array) {
|
||||
if (!inArray(narray, array[i])) {
|
||||
narray.push(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return narray;
|
||||
}
|
||||
|
||||
function indexOf (array, v, offset) {
|
||||
for (i in array) {
|
||||
function indexOf(array, v, offset) {
|
||||
for (var i in array) {
|
||||
if (offset == undefined || i >= offset) {
|
||||
if ( !isNaN(i) && array[i] == v) {
|
||||
if (!isNaN(i) && array[i] == v) {
|
||||
return new Number(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function rindexOf (array, v) {
|
||||
var l = array.length;
|
||||
for (i in array) {
|
||||
|
||||
for (var i in array) {
|
||||
if (!isNaN(i)) {
|
||||
var i = new Number(i)
|
||||
var i = new Number(i);
|
||||
}
|
||||
|
||||
if (!isNaN(i) && array[l - i] == v) {
|
||||
return l - i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -51,10 +67,12 @@ function compare (array, carray) {
|
||||
if (array.length != carray.length) {
|
||||
return false;
|
||||
}
|
||||
for (i in array) {
|
||||
|
||||
for (var i in array) {
|
||||
if (array[i] != carray[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['getAttributes'];
|
||||
|
||||
|
||||
var getAttributes = function (node) {
|
||||
var attributes = {};
|
||||
for (i in node.attributes) {
|
||||
if ( !isNaN(i) ) {
|
||||
|
||||
for (var i in node.attributes) {
|
||||
if (!isNaN(i)) {
|
||||
try {
|
||||
var attr = node.attributes[i];
|
||||
attributes[attr.name] = attr.value;
|
||||
} catch (err) {
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
@ -10,16 +10,36 @@
|
||||
* httpd.js.
|
||||
*/
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"HTTP_400",
|
||||
"HTTP_401",
|
||||
"HTTP_402",
|
||||
"HTTP_403",
|
||||
"HTTP_404",
|
||||
"HTTP_405",
|
||||
"HTTP_406",
|
||||
"HTTP_407",
|
||||
"HTTP_408",
|
||||
"HTTP_409",
|
||||
"HTTP_410",
|
||||
"HTTP_411",
|
||||
"HTTP_412",
|
||||
"HTTP_413",
|
||||
"HTTP_414",
|
||||
"HTTP_415",
|
||||
"HTTP_417",
|
||||
"HTTP_500",
|
||||
"HTTP_501",
|
||||
"HTTP_502",
|
||||
"HTTP_503",
|
||||
"HTTP_504",
|
||||
"HTTP_505",
|
||||
"HttpError",
|
||||
"HttpServer",
|
||||
];
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = ['getServer'];
|
||||
|
||||
/**
|
||||
* Overwrite both dump functions because we do not wanna have this output for Mozmill
|
||||
*/
|
||||
function dump() {}
|
||||
function dumpn() {}
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
@ -58,7 +78,7 @@ function NS_ASSERT(cond, msg)
|
||||
}
|
||||
|
||||
/** Constructs an HTTP error object. */
|
||||
function HttpError(code, description)
|
||||
this.HttpError = function HttpError(code, description)
|
||||
{
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
@ -74,30 +94,30 @@ HttpError.prototype =
|
||||
/**
|
||||
* Errors thrown to trigger specific HTTP server responses.
|
||||
*/
|
||||
const HTTP_400 = new HttpError(400, "Bad Request");
|
||||
const HTTP_401 = new HttpError(401, "Unauthorized");
|
||||
const HTTP_402 = new HttpError(402, "Payment Required");
|
||||
const HTTP_403 = new HttpError(403, "Forbidden");
|
||||
const HTTP_404 = new HttpError(404, "Not Found");
|
||||
const HTTP_405 = new HttpError(405, "Method Not Allowed");
|
||||
const HTTP_406 = new HttpError(406, "Not Acceptable");
|
||||
const HTTP_407 = new HttpError(407, "Proxy Authentication Required");
|
||||
const HTTP_408 = new HttpError(408, "Request Timeout");
|
||||
const HTTP_409 = new HttpError(409, "Conflict");
|
||||
const HTTP_410 = new HttpError(410, "Gone");
|
||||
const HTTP_411 = new HttpError(411, "Length Required");
|
||||
const HTTP_412 = new HttpError(412, "Precondition Failed");
|
||||
const HTTP_413 = new HttpError(413, "Request Entity Too Large");
|
||||
const HTTP_414 = new HttpError(414, "Request-URI Too Long");
|
||||
const HTTP_415 = new HttpError(415, "Unsupported Media Type");
|
||||
const HTTP_417 = new HttpError(417, "Expectation Failed");
|
||||
this.HTTP_400 = new HttpError(400, "Bad Request");
|
||||
this.HTTP_401 = new HttpError(401, "Unauthorized");
|
||||
this.HTTP_402 = new HttpError(402, "Payment Required");
|
||||
this.HTTP_403 = new HttpError(403, "Forbidden");
|
||||
this.HTTP_404 = new HttpError(404, "Not Found");
|
||||
this.HTTP_405 = new HttpError(405, "Method Not Allowed");
|
||||
this.HTTP_406 = new HttpError(406, "Not Acceptable");
|
||||
this.HTTP_407 = new HttpError(407, "Proxy Authentication Required");
|
||||
this.HTTP_408 = new HttpError(408, "Request Timeout");
|
||||
this.HTTP_409 = new HttpError(409, "Conflict");
|
||||
this.HTTP_410 = new HttpError(410, "Gone");
|
||||
this.HTTP_411 = new HttpError(411, "Length Required");
|
||||
this.HTTP_412 = new HttpError(412, "Precondition Failed");
|
||||
this.HTTP_413 = new HttpError(413, "Request Entity Too Large");
|
||||
this.HTTP_414 = new HttpError(414, "Request-URI Too Long");
|
||||
this.HTTP_415 = new HttpError(415, "Unsupported Media Type");
|
||||
this.HTTP_417 = new HttpError(417, "Expectation Failed");
|
||||
|
||||
const HTTP_500 = new HttpError(500, "Internal Server Error");
|
||||
const HTTP_501 = new HttpError(501, "Not Implemented");
|
||||
const HTTP_502 = new HttpError(502, "Bad Gateway");
|
||||
const HTTP_503 = new HttpError(503, "Service Unavailable");
|
||||
const HTTP_504 = new HttpError(504, "Gateway Timeout");
|
||||
const HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
|
||||
this.HTTP_500 = new HttpError(500, "Internal Server Error");
|
||||
this.HTTP_501 = new HttpError(501, "Not Implemented");
|
||||
this.HTTP_502 = new HttpError(502, "Bad Gateway");
|
||||
this.HTTP_503 = new HttpError(503, "Service Unavailable");
|
||||
this.HTTP_504 = new HttpError(504, "Gateway Timeout");
|
||||
this.HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
|
||||
|
||||
/** Creates a hash with fields corresponding to the values in arr. */
|
||||
function array2obj(arr)
|
||||
@ -266,7 +286,7 @@ function toDateString(date)
|
||||
{
|
||||
var hrs = date.getUTCHours();
|
||||
var rv = (hrs < 10) ? "0" + hrs : hrs;
|
||||
|
||||
|
||||
var mins = date.getUTCMinutes();
|
||||
rv += ":";
|
||||
rv += (mins < 10) ? "0" + mins : mins;
|
||||
@ -451,7 +471,15 @@ nsHttpServer.prototype =
|
||||
onStopListening: function(socket, status)
|
||||
{
|
||||
dumpn(">>> shutting down server on port " + socket.port);
|
||||
for (var n in this._connections) {
|
||||
if (!this._connections[n]._requestStarted) {
|
||||
this._connections[n].close();
|
||||
}
|
||||
}
|
||||
this._socketClosed = true;
|
||||
if (this._hasOpenConnections()) {
|
||||
dumpn("*** open connections!!!");
|
||||
}
|
||||
if (!this._hasOpenConnections())
|
||||
{
|
||||
dumpn("*** no open connections, notifying async from onStopListening");
|
||||
@ -493,12 +521,14 @@ nsHttpServer.prototype =
|
||||
this._host = host;
|
||||
|
||||
// The listen queue needs to be long enough to handle
|
||||
// network.http.max-persistent-connections-per-server concurrent connections,
|
||||
// plus a safety margin in case some other process is talking to
|
||||
// the server as well.
|
||||
// network.http.max-persistent-connections-per-server or
|
||||
// network.http.max-persistent-connections-per-proxy concurrent
|
||||
// connections, plus a safety margin in case some other process is
|
||||
// talking to the server as well.
|
||||
var prefs = getRootPrefBranch();
|
||||
var maxConnections =
|
||||
prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5;
|
||||
var maxConnections = 5 + Math.max(
|
||||
prefs.getIntPref("network.http.max-persistent-connections-per-server"),
|
||||
prefs.getIntPref("network.http.max-persistent-connections-per-proxy"));
|
||||
|
||||
try
|
||||
{
|
||||
@ -507,18 +537,52 @@ nsHttpServer.prototype =
|
||||
var loopback = false;
|
||||
}
|
||||
|
||||
var socket = new ServerSocket(this._port,
|
||||
// When automatically selecting a port, sometimes the chosen port is
|
||||
// "blocked" from clients. We don't want to use these ports because
|
||||
// tests will intermittently fail. So, we simply keep trying to to
|
||||
// get a server socket until a valid port is obtained. We limit
|
||||
// ourselves to finite attempts just so we don't loop forever.
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
var socket;
|
||||
for (var i = 100; i; i--)
|
||||
{
|
||||
var temp = new ServerSocket(this._port,
|
||||
loopback, // true = localhost, false = everybody
|
||||
maxConnections);
|
||||
|
||||
var allowed = ios.allowPort(temp.port, "http");
|
||||
if (!allowed)
|
||||
{
|
||||
dumpn(">>>Warning: obtained ServerSocket listens on a blocked " +
|
||||
"port: " + temp.port);
|
||||
}
|
||||
|
||||
if (!allowed && this._port == -1)
|
||||
{
|
||||
dumpn(">>>Throwing away ServerSocket with bad port.");
|
||||
temp.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
socket = temp;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!socket) {
|
||||
throw new Error("No socket server available. Are there no available ports?");
|
||||
}
|
||||
|
||||
dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
|
||||
" pending connections");
|
||||
socket.asyncListen(this);
|
||||
this._identity._initialize(port, host, true);
|
||||
this._port = socket.port;
|
||||
this._identity._initialize(socket.port, host, true);
|
||||
this._socket = socket;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
dumpn("!!! could not start server on port " + port + ": " + e);
|
||||
dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
|
||||
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
},
|
||||
@ -587,6 +651,14 @@ nsHttpServer.prototype =
|
||||
this._handler.registerPathHandler(path, handler);
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServer.registerPrefixHandler
|
||||
//
|
||||
registerPrefixHandler: function(prefix, handler)
|
||||
{
|
||||
this._handler.registerPrefixHandler(prefix, handler);
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServer.registerErrorHandler
|
||||
//
|
||||
@ -759,6 +831,10 @@ nsHttpServer.prototype =
|
||||
// Fire a pending server-stopped notification if it's our responsibility.
|
||||
if (!this._hasOpenConnections() && this._socketClosed)
|
||||
this._notifyStopped();
|
||||
// Bug 508125: Add a GC here else we'll use gigabytes of memory running
|
||||
// mochitests. We can't rely on xpcshell doing an automated GC, as that
|
||||
// would interfere with testing GC stuff...
|
||||
Components.utils.forceGC();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -772,6 +848,7 @@ nsHttpServer.prototype =
|
||||
}
|
||||
};
|
||||
|
||||
this.HttpServer = nsHttpServer;
|
||||
|
||||
//
|
||||
// RFC 2396 section 3.2.2:
|
||||
@ -790,7 +867,7 @@ const HOST_REGEX =
|
||||
// toplabel
|
||||
"[a-z](?:[a-z0-9-]*[a-z0-9])?" +
|
||||
"|" +
|
||||
// IPv4 address
|
||||
// IPv4 address
|
||||
"\\d+\\.\\d+\\.\\d+\\.\\d+" +
|
||||
")$",
|
||||
"i");
|
||||
@ -1001,7 +1078,7 @@ ServerIdentity.prototype =
|
||||
// Not the default primary location, nothing special to do here
|
||||
this.remove("http", "127.0.0.1", this._defaultPort);
|
||||
}
|
||||
|
||||
|
||||
// This is a *very* tricky bit of reasoning here; make absolutely sure the
|
||||
// tests for this code pass before you commit changes to it.
|
||||
if (this._primaryScheme == "http" &&
|
||||
@ -1097,14 +1174,25 @@ function Connection(input, output, server, port, outgoingPort, number)
|
||||
*/
|
||||
this.request = null;
|
||||
|
||||
/** State variables for debugging. */
|
||||
this._closed = this._processed = false;
|
||||
/** This allows a connection to disambiguate between a peer initiating a
|
||||
* close and the socket being forced closed on shutdown.
|
||||
*/
|
||||
this._closed = false;
|
||||
|
||||
/** State variable for debugging. */
|
||||
this._processed = false;
|
||||
|
||||
/** whether or not 1st line of request has been received */
|
||||
this._requestStarted = false;
|
||||
}
|
||||
Connection.prototype =
|
||||
{
|
||||
/** Closes this connection's input/output streams. */
|
||||
close: function()
|
||||
{
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
dumpn("*** closing connection " + this.number +
|
||||
" on port " + this._outgoingPort);
|
||||
|
||||
@ -1162,6 +1250,11 @@ Connection.prototype =
|
||||
return "<Connection(" + this.number +
|
||||
(this.request ? ", " + this.request.path : "") +"): " +
|
||||
(this._closed ? "closed" : "open") + ">";
|
||||
},
|
||||
|
||||
requestStarted: function()
|
||||
{
|
||||
this._requestStarted = true;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1348,6 +1441,7 @@ RequestReader.prototype =
|
||||
{
|
||||
this._parseRequestLine(line.value);
|
||||
this._state = READER_IN_HEADERS;
|
||||
this._connection.requestStarted();
|
||||
return true;
|
||||
}
|
||||
catch (e)
|
||||
@ -1433,7 +1527,7 @@ RequestReader.prototype =
|
||||
this._handleResponse();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (e)
|
||||
@ -1606,7 +1700,10 @@ RequestReader.prototype =
|
||||
// between fields, even though only a single SP is required (section 19.3)
|
||||
var request = line.split(/[ \t]+/);
|
||||
if (!request || request.length != 3)
|
||||
{
|
||||
dumpn("*** No request in line");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
metadata._method = request[0];
|
||||
|
||||
@ -1614,7 +1711,10 @@ RequestReader.prototype =
|
||||
var ver = request[2];
|
||||
var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
|
||||
if (!match)
|
||||
{
|
||||
dumpn("*** No HTTP version in line");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
// determine HTTP version
|
||||
try
|
||||
@ -1639,7 +1739,10 @@ RequestReader.prototype =
|
||||
{
|
||||
// No absolute paths in the request line in HTTP prior to 1.1
|
||||
if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
|
||||
{
|
||||
dumpn("*** Metadata version too low");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@ -1653,11 +1756,18 @@ RequestReader.prototype =
|
||||
if (port === -1)
|
||||
{
|
||||
if (scheme === "http")
|
||||
{
|
||||
port = 80;
|
||||
}
|
||||
else if (scheme === "https")
|
||||
{
|
||||
port = 443;
|
||||
}
|
||||
else
|
||||
{
|
||||
dumpn("*** Unknown scheme: " + scheme);
|
||||
throw HTTP_400;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
@ -1665,11 +1775,15 @@ RequestReader.prototype =
|
||||
// If the host is not a valid host on the server, the response MUST be a
|
||||
// 400 (Bad Request) error message (section 5.2). Alternately, the URI
|
||||
// is malformed.
|
||||
dumpn("*** Threw when dealing with URI: " + e);
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
|
||||
{
|
||||
dumpn("*** serverIdentity unknown or path does not start with '/'");
|
||||
throw HTTP_400;
|
||||
}
|
||||
}
|
||||
|
||||
var splitter = fullPath.indexOf("?");
|
||||
@ -1713,6 +1827,8 @@ RequestReader.prototype =
|
||||
var line = {};
|
||||
while (true)
|
||||
{
|
||||
dumpn("*** Last name: '" + lastName + "'");
|
||||
dumpn("*** Last val: '" + lastVal + "'");
|
||||
NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
|
||||
lastName === undefined ?
|
||||
"lastVal without lastName? lastVal: '" + lastVal + "'" :
|
||||
@ -1727,6 +1843,7 @@ RequestReader.prototype =
|
||||
}
|
||||
|
||||
var lineText = line.value;
|
||||
dumpn("*** Line text: '" + lineText + "'");
|
||||
var firstChar = lineText.charAt(0);
|
||||
|
||||
// blank line means end of headers
|
||||
@ -1741,7 +1858,7 @@ RequestReader.prototype =
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
dumpn("*** e == " + e);
|
||||
dumpn("*** setHeader threw on last header, e == " + e);
|
||||
throw HTTP_400;
|
||||
}
|
||||
}
|
||||
@ -1759,7 +1876,7 @@ RequestReader.prototype =
|
||||
// multi-line header if we've already seen a header line
|
||||
if (!lastName)
|
||||
{
|
||||
// we don't have a header to continue!
|
||||
dumpn("We don't have a header to continue!");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
@ -1778,7 +1895,7 @@ RequestReader.prototype =
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
dumpn("*** e == " + e);
|
||||
dumpn("*** setHeader threw on a header, e == " + e);
|
||||
throw HTTP_400;
|
||||
}
|
||||
}
|
||||
@ -1786,7 +1903,7 @@ RequestReader.prototype =
|
||||
var colon = lineText.indexOf(":"); // first colon must be splitter
|
||||
if (colon < 1)
|
||||
{
|
||||
// no colon or missing header field-name
|
||||
dumpn("*** No colon or missing header field-name");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
@ -1811,12 +1928,14 @@ const CR = 0x0D, LF = 0x0A;
|
||||
* character; the first CRLF is the lowest index i where
|
||||
* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
|
||||
* if such an |i| exists, and -1 otherwise
|
||||
* @param start : uint
|
||||
* start index from which to begin searching in array
|
||||
* @returns int
|
||||
* the index of the first CRLF if any were present, -1 otherwise
|
||||
*/
|
||||
function findCRLF(array)
|
||||
function findCRLF(array, start)
|
||||
{
|
||||
for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1))
|
||||
for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1))
|
||||
{
|
||||
if (array[i + 1] == LF)
|
||||
return i;
|
||||
@ -1833,6 +1952,9 @@ function LineData()
|
||||
{
|
||||
/** An array of queued bytes from which to get line-based characters. */
|
||||
this._data = [];
|
||||
|
||||
/** Start index from which to search for CRLF. */
|
||||
this._start = 0;
|
||||
}
|
||||
LineData.prototype =
|
||||
{
|
||||
@ -1842,7 +1964,22 @@ LineData.prototype =
|
||||
*/
|
||||
appendBytes: function(bytes)
|
||||
{
|
||||
Array.prototype.push.apply(this._data, bytes);
|
||||
var count = bytes.length;
|
||||
var quantum = 262144; // just above half SpiderMonkey's argument-count limit
|
||||
if (count < quantum)
|
||||
{
|
||||
Array.prototype.push.apply(this._data, bytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// Large numbers of bytes may cause Array.prototype.push to be called with
|
||||
// more arguments than the JavaScript engine supports. In that case append
|
||||
// bytes in fixed-size amounts until all bytes are appended.
|
||||
for (var start = 0; start < count; start += quantum)
|
||||
{
|
||||
var slice = bytes.slice(start, Math.min(start + quantum, count));
|
||||
Array.prototype.push.apply(this._data, slice);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1860,23 +1997,38 @@ LineData.prototype =
|
||||
readLine: function(out)
|
||||
{
|
||||
var data = this._data;
|
||||
var length = findCRLF(data);
|
||||
var length = findCRLF(data, this._start);
|
||||
if (length < 0)
|
||||
{
|
||||
this._start = data.length;
|
||||
|
||||
// But if our data ends in a CR, we have to back up one, because
|
||||
// the first byte in the next packet might be an LF and if we
|
||||
// start looking at data.length we won't find it.
|
||||
if (data.length > 0 && data[data.length - 1] === CR)
|
||||
--this._start;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset for future lines.
|
||||
this._start = 0;
|
||||
|
||||
//
|
||||
// We have the index of the CR, so remove all the characters, including
|
||||
// CRLF, from the array with splice, and convert the removed array into the
|
||||
// corresponding string, from which we then strip the trailing CRLF.
|
||||
// CRLF, from the array with splice, and convert the removed array
|
||||
// (excluding the trailing CRLF characters) into the corresponding string.
|
||||
//
|
||||
// Getting the line in this matter acknowledges that substring is an O(1)
|
||||
// operation in SpiderMonkey because strings are immutable, whereas two
|
||||
// splices, both from the beginning of the data, are less likely to be as
|
||||
// cheap as a single splice plus two extra character conversions.
|
||||
//
|
||||
var line = String.fromCharCode.apply(null, data.splice(0, length + 2));
|
||||
out.value = line.substring(0, length);
|
||||
var leading = data.splice(0, length + 2);
|
||||
var quantum = 262144;
|
||||
var line = "";
|
||||
for (var start = 0; start < length; start += quantum)
|
||||
{
|
||||
var slice = leading.slice(start, Math.min(start + quantum, length));
|
||||
line += String.fromCharCode.apply(null, slice);
|
||||
}
|
||||
|
||||
out.value = line;
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -1911,7 +2063,7 @@ function createHandlerFunc(handler)
|
||||
*/
|
||||
function defaultIndexHandler(metadata, response)
|
||||
{
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
|
||||
var path = htmlEscape(decodeURI(metadata.path));
|
||||
|
||||
@ -2018,6 +2170,7 @@ function toInternalPath(path, encoded)
|
||||
return comps.join("/");
|
||||
}
|
||||
|
||||
const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
|
||||
|
||||
/**
|
||||
* Adds custom-specified headers for the given file to the given response, if
|
||||
@ -2045,7 +2198,7 @@ function maybeAddHeaders(file, metadata, response)
|
||||
return;
|
||||
|
||||
const PR_RDONLY = 0x01;
|
||||
var fis = new FileInputStream(headerFile, PR_RDONLY, 0444,
|
||||
var fis = new FileInputStream(headerFile, PR_RDONLY, PERMS_READONLY,
|
||||
Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
||||
|
||||
try
|
||||
@ -2078,7 +2231,7 @@ function maybeAddHeaders(file, metadata, response)
|
||||
code = status.substring(0, space);
|
||||
description = status.substring(space + 1, status.length);
|
||||
}
|
||||
|
||||
|
||||
response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
|
||||
|
||||
line.value = "";
|
||||
@ -2149,6 +2302,15 @@ function ServerHandler(server)
|
||||
*/
|
||||
this._overridePaths = {};
|
||||
|
||||
/**
|
||||
* Custom request handlers for the path prefixes on the server in which this
|
||||
* resides. Path-handler pairs are stored as property-value pairs in this
|
||||
* property.
|
||||
*
|
||||
* @see ServerHandler.prototype._defaultPaths
|
||||
*/
|
||||
this._overridePrefixes = {};
|
||||
|
||||
/**
|
||||
* Custom request handlers for the error handlers in the server in which this
|
||||
* resides. Path-handler pairs are stored as property-value pairs in this
|
||||
@ -2213,7 +2375,23 @@ ServerHandler.prototype =
|
||||
}
|
||||
else
|
||||
{
|
||||
this._handleDefault(request, response);
|
||||
var longestPrefix = "";
|
||||
for (let prefix in this._overridePrefixes) {
|
||||
if (prefix.length > longestPrefix.length &&
|
||||
path.substr(0, prefix.length) == prefix)
|
||||
{
|
||||
longestPrefix = prefix;
|
||||
}
|
||||
}
|
||||
if (longestPrefix.length > 0)
|
||||
{
|
||||
dumpn("calling prefix override for " + longestPrefix);
|
||||
this._overridePrefixes[longestPrefix](request, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._handleDefault(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
@ -2318,6 +2496,18 @@ ServerHandler.prototype =
|
||||
this._handlerToField(handler, this._overridePaths, path);
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServer.registerPrefixHandler
|
||||
//
|
||||
registerPrefixHandler: function(path, handler)
|
||||
{
|
||||
// XXX true path validation!
|
||||
if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
|
||||
this._handlerToField(handler, this._overridePrefixes, path);
|
||||
},
|
||||
|
||||
//
|
||||
// see nsIHttpServer.registerDirectory
|
||||
//
|
||||
@ -2458,7 +2648,10 @@ ServerHandler.prototype =
|
||||
{
|
||||
var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
|
||||
if (!rangeMatch)
|
||||
{
|
||||
dumpn("*** Range header bogosity: '" + metadata.getHeader("Range") + "'");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
if (rangeMatch[1] !== undefined)
|
||||
start = parseInt(rangeMatch[1], 10);
|
||||
@ -2467,7 +2660,10 @@ ServerHandler.prototype =
|
||||
end = parseInt(rangeMatch[2], 10);
|
||||
|
||||
if (start === undefined && end === undefined)
|
||||
{
|
||||
dumpn("*** More Range header bogosity: '" + metadata.getHeader("Range") + "'");
|
||||
throw HTTP_400;
|
||||
}
|
||||
|
||||
// No start given, so the end is really the count of bytes from the
|
||||
// end of the file.
|
||||
@ -2537,7 +2733,7 @@ ServerHandler.prototype =
|
||||
var type = this._getTypeFromFile(file);
|
||||
if (type === SJS_TYPE)
|
||||
{
|
||||
var fis = new FileInputStream(file, PR_RDONLY, 0444,
|
||||
var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
|
||||
Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
||||
|
||||
try
|
||||
@ -2574,6 +2770,10 @@ ServerHandler.prototype =
|
||||
{
|
||||
self._setObjectState(k, v);
|
||||
});
|
||||
s.importFunction(function registerPathHandler(p, h)
|
||||
{
|
||||
self.registerPathHandler(p, h);
|
||||
});
|
||||
|
||||
// Make it possible for sjs files to access their location
|
||||
this._setState(path, "__LOCATION__", file.path);
|
||||
@ -2586,7 +2786,7 @@ ServerHandler.prototype =
|
||||
// getting the line number where we evaluate the SJS file. Don't
|
||||
// separate these two lines!
|
||||
var line = new Error().lineNumber;
|
||||
Cu.evalInSandbox(sis.read(file.fileSize), s);
|
||||
Cu.evalInSandbox(sis.read(file.fileSize), s, "latest");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
@ -2627,7 +2827,7 @@ ServerHandler.prototype =
|
||||
maybeAddHeaders(file, metadata, response);
|
||||
response.setHeader("Content-Length", "" + count, false);
|
||||
|
||||
var fis = new FileInputStream(file, PR_RDONLY, 0444,
|
||||
var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
|
||||
Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
||||
|
||||
offset = offset || 0;
|
||||
@ -2878,6 +3078,7 @@ ServerHandler.prototype =
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
dumpn("*** toInternalPath threw " + e);
|
||||
throw HTTP_400; // malformed path
|
||||
}
|
||||
|
||||
@ -2962,7 +3163,7 @@ ServerHandler.prototype =
|
||||
dumpn("*** error in request: " + errorCode);
|
||||
|
||||
this._handleError(errorCode, new Request(connection.port), response);
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a request which generates the given error code, using the
|
||||
@ -3072,7 +3273,7 @@ ServerHandler.prototype =
|
||||
{
|
||||
// none of the data in metadata is reliable, so hard-code everything here
|
||||
response.setStatusLine("1.1", 400, "Bad Request");
|
||||
response.setHeader("Content-Type", "text/plain", false);
|
||||
response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
|
||||
|
||||
var body = "Bad request\n";
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
@ -3080,7 +3281,7 @@ ServerHandler.prototype =
|
||||
403: function(metadata, response)
|
||||
{
|
||||
response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
|
||||
var body = "<html>\
|
||||
<head><title>403 Forbidden</title></head>\
|
||||
@ -3093,7 +3294,7 @@ ServerHandler.prototype =
|
||||
404: function(metadata, response)
|
||||
{
|
||||
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
|
||||
var body = "<html>\
|
||||
<head><title>404 Not Found</title></head>\
|
||||
@ -3113,7 +3314,7 @@ ServerHandler.prototype =
|
||||
response.setStatusLine(metadata.httpVersion,
|
||||
416,
|
||||
"Requested Range Not Satisfiable");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
|
||||
var body = "<html>\
|
||||
<head>\
|
||||
@ -3132,7 +3333,7 @@ ServerHandler.prototype =
|
||||
response.setStatusLine(metadata.httpVersion,
|
||||
500,
|
||||
"Internal Server Error");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
|
||||
var body = "<html>\
|
||||
<head><title>500 Internal Server Error</title></head>\
|
||||
@ -3147,7 +3348,7 @@ ServerHandler.prototype =
|
||||
501: function(metadata, response)
|
||||
{
|
||||
response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
|
||||
var body = "<html>\
|
||||
<head><title>501 Not Implemented</title></head>\
|
||||
@ -3161,7 +3362,7 @@ ServerHandler.prototype =
|
||||
505: function(metadata, response)
|
||||
{
|
||||
response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
|
||||
var body = "<html>\
|
||||
<head><title>505 HTTP Version Not Supported</title></head>\
|
||||
@ -3183,7 +3384,7 @@ ServerHandler.prototype =
|
||||
"/": function(metadata, response)
|
||||
{
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
|
||||
var body = "<html>\
|
||||
<head><title>httpd.js</title></head>\
|
||||
@ -3201,7 +3402,7 @@ ServerHandler.prototype =
|
||||
"/trace": function(metadata, response)
|
||||
{
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/plain", false);
|
||||
response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
|
||||
|
||||
var body = "Request-URI: " +
|
||||
metadata.scheme + "://" + metadata.host + ":" + metadata.port +
|
||||
@ -3211,7 +3412,7 @@ ServerHandler.prototype =
|
||||
|
||||
if (metadata.queryString)
|
||||
body += "?" + metadata.queryString;
|
||||
|
||||
|
||||
body += " HTTP/" + metadata.httpVersion + "\r\n";
|
||||
|
||||
var headEnum = metadata.headers;
|
||||
@ -4569,7 +4770,10 @@ const headerUtils =
|
||||
normalizeFieldName: function(fieldName)
|
||||
{
|
||||
if (fieldName == "")
|
||||
{
|
||||
dumpn("*** Empty fieldName");
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
for (var i = 0, sz = fieldName.length; i < sz; i++)
|
||||
{
|
||||
@ -4620,9 +4824,13 @@ const headerUtils =
|
||||
val = val.replace(/^ +/, "").replace(/ +$/, "");
|
||||
|
||||
// that should have taken care of all CTLs, so val should contain no CTLs
|
||||
dumpn("*** Normalized value: '" + val + "'");
|
||||
for (var i = 0, len = val.length; i < len; i++)
|
||||
if (isCTL(val.charCodeAt(i)))
|
||||
{
|
||||
dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
|
||||
// normalize, however, so this can be construed as a tightening of the
|
||||
@ -4757,17 +4965,17 @@ nsHttpHeaders.prototype =
|
||||
var value = headerUtils.normalizeFieldValue(fieldValue);
|
||||
|
||||
// The following three headers are stored as arrays because their real-world
|
||||
// syntax prevents joining individual headers into a single header using
|
||||
// syntax prevents joining individual headers into a single header using
|
||||
// ",". See also <http://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
|
||||
if (merge && name in this._headers)
|
||||
{
|
||||
if (name === "www-authenticate" ||
|
||||
name === "proxy-authenticate" ||
|
||||
name === "set-cookie")
|
||||
name === "set-cookie")
|
||||
{
|
||||
this._headers[name].push(value);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
this._headers[name][0] += "," + value;
|
||||
NS_ASSERT(this._headers[name].length === 1,
|
||||
@ -4790,8 +4998,8 @@ nsHttpHeaders.prototype =
|
||||
* @returns string
|
||||
* the field value for the given header, possibly with non-semantic changes
|
||||
* (i.e., leading/trailing whitespace stripped, whitespace runs replaced
|
||||
* with spaces, etc.) at the option of the implementation; multiple
|
||||
* instances of the header will be combined with a comma, except for
|
||||
* with spaces, etc.) at the option of the implementation; multiple
|
||||
* instances of the header will be combined with a comma, except for
|
||||
* the three headers noted in the description of getHeaderValues
|
||||
*/
|
||||
getHeader: function(fieldName)
|
||||
@ -5053,7 +5261,7 @@ Request.prototype =
|
||||
//
|
||||
// see nsIPropertyBag.getProperty
|
||||
//
|
||||
getProperty: function(name)
|
||||
getProperty: function(name)
|
||||
{
|
||||
this._ensurePropertyBag();
|
||||
return this._bag.getProperty(name);
|
||||
@ -5075,7 +5283,7 @@ Request.prototype =
|
||||
|
||||
|
||||
// PRIVATE IMPLEMENTATION
|
||||
|
||||
|
||||
/** Ensures a property bag has been created for ad-hoc behaviors. */
|
||||
_ensurePropertyBag: function()
|
||||
{
|
||||
@ -5086,10 +5294,8 @@ Request.prototype =
|
||||
|
||||
|
||||
// XPCOM trappings
|
||||
if (XPCOMUtils.generateNSGetFactory)
|
||||
var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
|
||||
else
|
||||
var NSGetModule = XPCOMUtils.generateNSGetModule([nsHttpServer]);
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
|
||||
|
||||
/**
|
||||
* Creates a new HTTP server listening for loopback traffic on the given port,
|
||||
@ -5147,20 +5353,3 @@ function server(port, basePath)
|
||||
|
||||
DEBUG = false;
|
||||
}
|
||||
|
||||
function getServer (port, basePath) {
|
||||
if (basePath) {
|
||||
var lp = Cc["@mozilla.org/file/local;1"]
|
||||
.createInstance(Ci.nsILocalFile);
|
||||
lp.initWithPath(basePath);
|
||||
}
|
||||
|
||||
var srv = new nsHttpServer();
|
||||
if (lp)
|
||||
srv.registerDirectory("/", lp);
|
||||
srv.registerContentType("sjs", SJS_TYPE);
|
||||
srv.identity.setPrimary("http", "localhost", port);
|
||||
srv._port = port;
|
||||
|
||||
return srv;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['getLength', ];//'compare'];
|
||||
|
||||
@ -9,6 +9,7 @@ var getLength = function (obj) {
|
||||
for (i in obj) {
|
||||
len++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
@ -1,38 +1,46 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['listDirectory', 'getFileForPath', 'abspath', 'getPlatform'];
|
||||
|
||||
function listDirectory (file) {
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function listDirectory(file) {
|
||||
// file is the given directory (nsIFile)
|
||||
var entries = file.directoryEntries;
|
||||
var array = [];
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
|
||||
while (entries.hasMoreElements()) {
|
||||
var entry = entries.getNext();
|
||||
entry.QueryInterface(Components.interfaces.nsIFile);
|
||||
entry.QueryInterface(Ci.nsIFile);
|
||||
array.push(entry);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function getFileForPath (path) {
|
||||
var file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
function getFileForPath(path) {
|
||||
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(path);
|
||||
return file;
|
||||
}
|
||||
|
||||
function abspath (rel, file) {
|
||||
function abspath(rel, file) {
|
||||
var relSplit = rel.split('/');
|
||||
|
||||
if (relSplit[0] == '..' && !file.isDirectory()) {
|
||||
file = file.parent;
|
||||
}
|
||||
for each(p in relSplit) {
|
||||
|
||||
for each(var p in relSplit) {
|
||||
if (p == '..') {
|
||||
file = file.parent;
|
||||
} else if (p == '.'){
|
||||
} else if (p == '.') {
|
||||
if (!file.isDirectory()) {
|
||||
file = file.parent;
|
||||
}
|
||||
@ -40,14 +48,10 @@ function abspath (rel, file) {
|
||||
file.append(p);
|
||||
}
|
||||
}
|
||||
|
||||
return file.path;
|
||||
}
|
||||
|
||||
function getPlatform () {
|
||||
var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Components.interfaces.nsIXULRuntime);
|
||||
mPlatform = xulRuntime.OS.toLowerCase();
|
||||
return mPlatform;
|
||||
function getPlatform() {
|
||||
return Services.appinfo.OS.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,6 +1,38 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* ***** 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 Jetpack.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Atul Varma <atul@mozilla.com>
|
||||
*
|
||||
* 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 ***** */
|
||||
|
||||
(function(global) {
|
||||
const Cc = Components.classes;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ['trim', 'vslice'];
|
||||
|
||||
|
462
services/sync/tps/extensions/mozmill/resource/stdlib/utils.js
Normal file
462
services/sync/tps/extensions/mozmill/resource/stdlib/utils.js
Normal file
@ -0,0 +1,462 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["applicationName", "assert", "Copy", "getBrowserObject",
|
||||
"getChromeWindow", "getWindows", "getWindowByTitle",
|
||||
"getWindowByType", "getWindowId", "getMethodInWindows",
|
||||
"getPreference", "saveDataURL", "setPreference",
|
||||
"sleep", "startTimer", "stopTimer", "takeScreenshot",
|
||||
"unwrapNode", "waitFor"
|
||||
];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const applicationIdMap = {
|
||||
'{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'Firefox',
|
||||
'{99bceaaa-e3c6-48c1-b981-ef9b46b67d60}': 'MetroFirefox'
|
||||
}
|
||||
const applicationName = applicationIdMap[Services.appinfo.ID] || Services.appinfo.name;
|
||||
|
||||
var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
|
||||
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
|
||||
var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
|
||||
|
||||
var assert = new assertions.Assert();
|
||||
|
||||
var hwindow = Services.appShell.hiddenDOMWindow;
|
||||
|
||||
var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
function Copy (obj) {
|
||||
for (var n in obj) {
|
||||
this[n] = obj[n];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the browser object of the specified window
|
||||
*
|
||||
* @param {Window} aWindow
|
||||
* Window to get the browser element from.
|
||||
*
|
||||
* @returns {Object} The browser element
|
||||
*/
|
||||
function getBrowserObject(aWindow) {
|
||||
switch(applicationName) {
|
||||
case "MetroFirefox":
|
||||
return aWindow.Browser;
|
||||
case "Firefox":
|
||||
default:
|
||||
return aWindow.gBrowser;
|
||||
}
|
||||
}
|
||||
|
||||
function getChromeWindow(aWindow) {
|
||||
var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow)
|
||||
.QueryInterface(Ci.nsIDOMChromeWindow);
|
||||
|
||||
return chromeWin;
|
||||
}
|
||||
|
||||
function getWindows(type) {
|
||||
if (type == undefined) {
|
||||
type = "";
|
||||
}
|
||||
|
||||
var windows = [];
|
||||
var enumerator = Services.wm.getEnumerator(type);
|
||||
|
||||
while (enumerator.hasMoreElements()) {
|
||||
windows.push(enumerator.getNext());
|
||||
}
|
||||
|
||||
if (type == "") {
|
||||
windows.push(hwindow);
|
||||
}
|
||||
|
||||
return windows;
|
||||
}
|
||||
|
||||
function getMethodInWindows(methodName) {
|
||||
for each (var w in getWindows()) {
|
||||
if (w[methodName] != undefined) {
|
||||
return w[methodName];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Method with name: '" + methodName + "' is not in any open window.");
|
||||
}
|
||||
|
||||
function getWindowByTitle(title) {
|
||||
for each (var w in getWindows()) {
|
||||
if (w.document.title && w.document.title == title) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Window with title: '" + title + "' not found.");
|
||||
}
|
||||
|
||||
function getWindowByType(type) {
|
||||
return Services.wm.getMostRecentWindow(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the outer window id for the given window.
|
||||
*
|
||||
* @param {Number} aWindow
|
||||
* Window to retrieve the id from.
|
||||
* @returns {Boolean} The outer window id
|
||||
**/
|
||||
function getWindowId(aWindow) {
|
||||
try {
|
||||
// Normally we can retrieve the id via window utils
|
||||
return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).
|
||||
outerWindowID;
|
||||
} catch (e) {
|
||||
// ... but for observer notifications we need another interface
|
||||
return aWindow.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
}
|
||||
}
|
||||
|
||||
var checkChrome = function () {
|
||||
var loc = window.document.location.href;
|
||||
try {
|
||||
loc = window.top.document.location.href;
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
return /^chrome:\/\//.test(loc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to get the state of an individual preference.
|
||||
*
|
||||
* @param aPrefName string The preference to get the state of.
|
||||
* @param aDefaultValue any The default value if preference was not found.
|
||||
*
|
||||
* @returns any The value of the requested preference
|
||||
*
|
||||
* @see setPref
|
||||
* Code by Henrik Skupin: <hskupin@gmail.com>
|
||||
*/
|
||||
function getPreference(aPrefName, aDefaultValue) {
|
||||
try {
|
||||
var branch = Services.prefs;
|
||||
|
||||
switch (typeof aDefaultValue) {
|
||||
case ('boolean'):
|
||||
return branch.getBoolPref(aPrefName);
|
||||
case ('string'):
|
||||
return branch.getCharPref(aPrefName);
|
||||
case ('number'):
|
||||
return branch.getIntPref(aPrefName);
|
||||
default:
|
||||
return branch.getComplexValue(aPrefName);
|
||||
}
|
||||
} catch (e) {
|
||||
return aDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to set the state of an individual preference.
|
||||
*
|
||||
* @param aPrefName string The preference to set the state of.
|
||||
* @param aValue any The value to set the preference to.
|
||||
*
|
||||
* @returns boolean Returns true if value was successfully set.
|
||||
*
|
||||
* @see getPref
|
||||
* Code by Henrik Skupin: <hskupin@gmail.com>
|
||||
*/
|
||||
function setPreference(aName, aValue) {
|
||||
try {
|
||||
var branch = Services.prefs;
|
||||
|
||||
switch (typeof aValue) {
|
||||
case ('boolean'):
|
||||
branch.setBoolPref(aName, aValue);
|
||||
break;
|
||||
case ('string'):
|
||||
branch.setCharPref(aName, aValue);
|
||||
break;
|
||||
case ('number'):
|
||||
branch.setIntPref(aName, aValue);
|
||||
break;
|
||||
default:
|
||||
branch.setComplexValue(aName, aValue);
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep for the given amount of milliseconds
|
||||
*
|
||||
* @param {number} milliseconds
|
||||
* Sleeps the given number of milliseconds
|
||||
*/
|
||||
function sleep(milliseconds) {
|
||||
var timeup = false;
|
||||
|
||||
hwindow.setTimeout(function () { timeup = true; }, milliseconds);
|
||||
var thread = Services.tm.currentThread;
|
||||
|
||||
while (!timeup) {
|
||||
thread.processNextEvent(true);
|
||||
}
|
||||
|
||||
broker.pass({'function':'utils.sleep()'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the callback function evaluates to true
|
||||
*/
|
||||
function assert(callback, message, thisObject) {
|
||||
var result = callback.call(thisObject);
|
||||
|
||||
if (!result) {
|
||||
throw new Error(message || arguments.callee.name + ": Failed for '" + callback + "'");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (node.wrappedJSObject != null) {
|
||||
node = node.wrappedJSObject;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the callback evaluates to true
|
||||
*/
|
||||
function waitFor(callback, message, timeout, interval, thisObject) {
|
||||
broker.log({'function': 'utils.waitFor() - DEPRECATED',
|
||||
'message': 'utils.waitFor() is deprecated. Use assert.waitFor() instead'});
|
||||
assert.waitFor(callback, message, timeout, interval, thisObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the x and y chrome offset for an element
|
||||
* See https://developer.mozilla.org/en/DOM/window.innerHeight
|
||||
*
|
||||
* Note this function will not work if the user has custom toolbars (via extension) at the bottom or left/right of the screen
|
||||
*/
|
||||
function getChromeOffset(elem) {
|
||||
var win = elem.ownerDocument.defaultView;
|
||||
// Calculate x offset
|
||||
var chromeWidth = 0;
|
||||
|
||||
if (win["name"] != "sidebar") {
|
||||
chromeWidth = win.outerWidth - win.innerWidth;
|
||||
}
|
||||
|
||||
// Calculate y offset
|
||||
var chromeHeight = win.outerHeight - win.innerHeight;
|
||||
// chromeHeight == 0 means elem is already in the chrome and doesn't need the addonbar offset
|
||||
if (chromeHeight > 0) {
|
||||
// window.innerHeight doesn't include the addon or find bar, so account for these if present
|
||||
var addonbar = win.document.getElementById("addon-bar");
|
||||
if (addonbar) {
|
||||
chromeHeight -= addonbar.scrollHeight;
|
||||
}
|
||||
|
||||
var findbar = win.document.getElementById("FindToolbar");
|
||||
if (findbar) {
|
||||
chromeHeight -= findbar.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return {'x':chromeWidth, 'y':chromeHeight};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a screenshot of the specified DOM node
|
||||
*/
|
||||
function takeScreenshot(node, highlights) {
|
||||
var rect, win, width, height, left, top, needsOffset;
|
||||
// node can be either a window or an arbitrary DOM node
|
||||
try {
|
||||
// node is an arbitrary DOM node
|
||||
win = node.ownerDocument.defaultView;
|
||||
rect = node.getBoundingClientRect();
|
||||
width = rect.width;
|
||||
height = rect.height;
|
||||
top = rect.top;
|
||||
left = rect.left;
|
||||
// offset for highlights not needed as they will be relative to this node
|
||||
needsOffset = false;
|
||||
} catch (e) {
|
||||
// node is a window
|
||||
win = node;
|
||||
width = win.innerWidth;
|
||||
height = win.innerHeight;
|
||||
top = 0;
|
||||
left = 0;
|
||||
// offset needed for highlights to take 'outerHeight' of window into account
|
||||
needsOffset = true;
|
||||
}
|
||||
|
||||
var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
var ctx = canvas.getContext("2d");
|
||||
// Draws the DOM contents of the window to the canvas
|
||||
ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
|
||||
|
||||
// This section is for drawing a red rectangle around each element passed in via the highlights array
|
||||
if (highlights) {
|
||||
ctx.lineWidth = "2";
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.save();
|
||||
|
||||
for (var i = 0; i < highlights.length; ++i) {
|
||||
var elem = highlights[i];
|
||||
rect = elem.getBoundingClientRect();
|
||||
|
||||
var offsetY = 0, offsetX = 0;
|
||||
if (needsOffset) {
|
||||
var offset = getChromeOffset(elem);
|
||||
offsetX = offset.x;
|
||||
offsetY = offset.y;
|
||||
} else {
|
||||
// Don't need to offset the window chrome, just make relative to containing node
|
||||
offsetY = -top;
|
||||
offsetX = -left;
|
||||
}
|
||||
|
||||
// Draw the rectangle
|
||||
ctx.strokeRect(rect.left + offsetX, rect.top + offsetY, rect.width, rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
return canvas.toDataURL("image/jpeg", 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the dataURL content to the specified file. It will be stored in either the persisted screenshot or temporary folder.
|
||||
*
|
||||
* @param {String} aDataURL
|
||||
* The dataURL to save
|
||||
* @param {String} aFilename
|
||||
* Target file name without extension
|
||||
*
|
||||
* @returns {Object} The hash containing the path of saved file, and the failure bit
|
||||
*/
|
||||
function saveDataURL(aDataURL, aFilename) {
|
||||
var frame = {}; Cu.import('resource://mozmill/modules/frame.js', frame);
|
||||
const FILE_PERMISSIONS = parseInt("0644", 8);
|
||||
|
||||
var file;
|
||||
file = Cc['@mozilla.org/file/local;1']
|
||||
.createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(frame.persisted['screenshots']['path']);
|
||||
file.append(aFilename + ".jpg");
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FILE_PERMISSIONS);
|
||||
|
||||
// Create an output stream to write to file
|
||||
let foStream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
foStream.init(file, 0x02 | 0x08 | 0x10, FILE_PERMISSIONS, foStream.DEFER_OPEN);
|
||||
|
||||
let dataURI = NetUtil.newURI(aDataURL, "UTF8", null);
|
||||
if (!dataURI.schemeIs("data")) {
|
||||
throw TypeError("aDataURL parameter has to have 'data'" +
|
||||
" scheme instead of '" + dataURI.scheme + "'");
|
||||
}
|
||||
|
||||
// Write asynchronously to buffer;
|
||||
// Input and output streams are closed after write
|
||||
|
||||
let ready = false;
|
||||
let failure = false;
|
||||
|
||||
function sync(aStatus) {
|
||||
if (!Components.isSuccessCode(aStatus)) {
|
||||
failure = true;
|
||||
}
|
||||
ready = true;
|
||||
}
|
||||
|
||||
NetUtil.asyncFetch(dataURI, function (aInputStream, aAsyncFetchResult) {
|
||||
if (!Components.isSuccessCode(aAsyncFetchResult)) {
|
||||
// An error occurred!
|
||||
sync(aAsyncFetchResult);
|
||||
} else {
|
||||
// Consume the input stream.
|
||||
NetUtil.asyncCopy(aInputStream, foStream, function (aAsyncCopyResult) {
|
||||
sync(aAsyncCopyResult);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
assert.waitFor(function () {
|
||||
return ready;
|
||||
}, "DataURL has been saved to '" + file.path + "'");
|
||||
|
||||
return {filename: file.path, failure: failure};
|
||||
}
|
||||
|
||||
/**
|
||||
* Some very brain-dead timer functions useful for performance optimizations
|
||||
* This is only enabled in debug mode
|
||||
*
|
||||
**/
|
||||
var gutility_mzmltimer = 0;
|
||||
/**
|
||||
* Starts timer initializing with current EPOC time in milliseconds
|
||||
*
|
||||
* @returns none
|
||||
**/
|
||||
function startTimer(){
|
||||
dump("TIMERCHECK:: starting now: " + Date.now() + "\n");
|
||||
gutility_mzmltimer = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the timer and outputs current elapsed time since start of timer. It
|
||||
* will print out a message you provide with its "time check" so you can
|
||||
* correlate in the log file and figure out elapsed time of specific functions.
|
||||
*
|
||||
* @param aMsg string The debug message to print with the timer check
|
||||
*
|
||||
* @returns none
|
||||
**/
|
||||
function checkTimer(aMsg){
|
||||
var end = Date.now();
|
||||
dump("TIMERCHECK:: at " + aMsg + " is: " + (end - gutility_mzmltimer) + "\n");
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["TPS", "SYNC_WIPE_SERVER", "SYNC_RESET_CLIENT",
|
||||
"SYNC_WIPE_CLIENT"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://tps/logger.jsm");
|
||||
|
||||
var utils = {}; Cu.import('resource://mozmill/modules/utils.js', utils);
|
||||
|
||||
const SYNC_RESET_CLIENT = "reset-client";
|
||||
const SYNC_WIPE_CLIENT = "wipe-client";
|
||||
const SYNC_WIPE_REMOTE = "wipe-remote";
|
||||
const SYNC_WIPE_SERVER = "wipe-server";
|
||||
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(CI.nsIPrefBranch);
|
||||
|
||||
var syncFinishedCallback = function() {
|
||||
Logger.logInfo('syncFinishedCallback returned ' + !TPS._waitingForSync);
|
||||
return !TPS._waitingForSync;
|
||||
};
|
||||
|
||||
var TPS = {
|
||||
_waitingForSync: false,
|
||||
_syncErrors: 0,
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
observe: function TPS__observe(subject, topic, data) {
|
||||
Logger.logInfo('Mozmill observed: ' + topic);
|
||||
switch(topic) {
|
||||
case "weave:service:sync:error":
|
||||
if (this._waitingForSync && this._syncErrors == 0) {
|
||||
Logger.logInfo("sync error; retrying...");
|
||||
this._syncErrors++;
|
||||
Utils.namedTimer(function() {
|
||||
Weave.service.sync();
|
||||
}, 1000, this, "resync");
|
||||
}
|
||||
else if (this._waitingForSync) {
|
||||
this._syncErrors = "sync error, see log";
|
||||
this._waitingForSync = false;
|
||||
}
|
||||
break;
|
||||
case "weave:service:sync:finish":
|
||||
if (this._waitingForSync) {
|
||||
this._syncErrors = 0;
|
||||
this._waitingForSync = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
SetupSyncAccount: function TPS__SetupSyncAccount() {
|
||||
try {
|
||||
let serverURL = prefs.getCharPref('tps.serverURL');
|
||||
if (serverURL) {
|
||||
Weave.Service.serverURL = serverURL;
|
||||
}
|
||||
}
|
||||
catch(e) {}
|
||||
|
||||
// Needs to be updated if this Mozmill sanity test is needed for Firefox Accounts
|
||||
Weave.Service.identity.account = prefs.getCharPref('tps.account.username');
|
||||
Weave.Service.Identity.basicPassword = prefs.getCharPref('tps.account.password');
|
||||
Weave.Service.identity.syncKey = prefs.getCharPref('tps.account.passphrase');
|
||||
Weave.Svc.Obs.notify("weave:service:setup-complete");
|
||||
},
|
||||
|
||||
Sync: function TPS__Sync(options) {
|
||||
Logger.logInfo('Mozmill starting sync operation: ' + options);
|
||||
switch(options) {
|
||||
case SYNC_WIPE_REMOTE:
|
||||
Weave.Svc.Prefs.set("firstSync", "wipeRemote");
|
||||
break;
|
||||
case SYNC_WIPE_CLIENT:
|
||||
Weave.Svc.Prefs.set("firstSync", "wipeClient");
|
||||
break;
|
||||
case SYNC_RESET_CLIENT:
|
||||
Weave.Svc.Prefs.set("firstSync", "resetClient");
|
||||
break;
|
||||
default:
|
||||
Weave.Svc.Prefs.reset("firstSync");
|
||||
}
|
||||
|
||||
if (Weave.Status.service != Weave.STATUS_OK) {
|
||||
return "Sync status not ok: " + Weave.Status.service;
|
||||
}
|
||||
|
||||
this._syncErrors = 0;
|
||||
|
||||
if (options == SYNC_WIPE_SERVER) {
|
||||
Weave.Service.wipeServer();
|
||||
} else {
|
||||
this._waitingForSync = true;
|
||||
Weave.Service.sync();
|
||||
utils.waitFor(syncFinishedCallback, null, 20000, 500, TPS);
|
||||
}
|
||||
return this._syncErrors;
|
||||
},
|
||||
};
|
||||
|
||||
Services.obs.addObserver(TPS, "weave:service:sync:finish", true);
|
||||
Services.obs.addObserver(TPS, "weave:service:sync:error", true);
|
||||
Logger.init();
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* listed symbols will exposed on import, and only when and where imported.
|
||||
*/
|
||||
|
||||
let EXPORTED_SYMBOLS = ["TPS"];
|
||||
let EXPORTED_SYMBOLS = ["ACTIONS", "TPS"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
@ -40,7 +40,7 @@ var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch);
|
||||
|
||||
var mozmillInit = {};
|
||||
Cu.import('resource://mozmill/modules/init.js', mozmillInit);
|
||||
Cu.import('resource://mozmill/driver/mozmill.js', mozmillInit);
|
||||
|
||||
// Options for wiping data during a sync
|
||||
const SYNC_RESET_CLIENT = "resetClient";
|
||||
@ -795,7 +795,7 @@ let TPS = {
|
||||
frame.events.addListener('setTest', this.MozmillSetTestListener.bind(this));
|
||||
frame.events.addListener('endTest', this.MozmillEndTestListener.bind(this));
|
||||
this.StartAsyncOperation();
|
||||
frame.runTestFile(mozmillfile.path, false);
|
||||
frame.runTestFile(mozmillfile.path, null);
|
||||
},
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user