forked from mirrors/gecko-dev
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
34 changed files with 5706 additions and 4391 deletions
|
|
@ -2,10 +2,10 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
||||||
|
|
||||||
Components.utils.import('resource://tps/mozmill/sync.jsm');
|
Components.utils.import('resource://tps/tps.jsm');
|
||||||
|
|
||||||
var setupModule = function(module) {
|
var setupModule = function(module) {
|
||||||
controller = mozmill.getBrowserController();
|
module.controller = mozmill.getBrowserController();
|
||||||
assert.ok(true, "SetupModule passes");
|
assert.ok(true, "SetupModule passes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,8 +16,9 @@ var setupTest = function(module) {
|
||||||
var testTestStep = function() {
|
var testTestStep = function() {
|
||||||
assert.ok(true, "test Passes");
|
assert.ok(true, "test Passes");
|
||||||
controller.open("http://www.mozilla.org");
|
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 () {
|
var teardownTest = function () {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
/* Any copyright is dedicated to the Public Domain.
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
* 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 jum = {}; Components.utils.import('resource://mozmill/modules/jum.js', jum);
|
|
||||||
|
|
||||||
var setupModule = function(module) {
|
var setupModule = function(module) {
|
||||||
module.controller = mozmill.getBrowserController();
|
module.controller = mozmill.getBrowserController();
|
||||||
|
|
@ -11,43 +10,6 @@ var testGetNode = function() {
|
||||||
controller.open("about:support");
|
controller.open("about:support");
|
||||||
controller.waitForPageLoad();
|
controller.waitForPageLoad();
|
||||||
|
|
||||||
var appbox = new elementslib.ID(controller.tabs.activeTab, "application-box");
|
var appbox = findElement.ID(controller.tabs.activeTab, "application-box");
|
||||||
jum.assert(appbox.getNode().innerHTML == 'Firefox', 'correct app name');
|
assert.waitFor(() => appbox.getNode().textContent == '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);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
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#"
|
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||||
|
|
||||||
<Description about="urn:mozilla:install-manifest">
|
<Description about="urn:mozilla:install-manifest">
|
||||||
<em:id>mozmill@mozilla.com</em:id>
|
<em:id>mozmill@mozilla.com</em:id>
|
||||||
<em:name>MozMill</em:name>
|
<em:name>Mozmill</em:name>
|
||||||
<em:version>2.0b1</em:version>
|
<em:version>2.0.6</em:version>
|
||||||
<em:creator>Adam Christian</em:creator>
|
<em:description>UI Automation tool for Mozilla applications</em:description>
|
||||||
<em:description>A testing extension based on the Windmill Testing Framework client source</em:description>
|
|
||||||
<em:unpack>true</em:unpack>
|
<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>
|
<em:targetApplication>
|
||||||
<!-- Firefox -->
|
|
||||||
<Description>
|
<Description>
|
||||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
<em:id>toolkit@mozilla.org</em:id>
|
||||||
<em:minVersion>3.5</em:minVersion>
|
<em:minVersion>10.0</em:minVersion>
|
||||||
<em:maxVersion>12.*</em:maxVersion>
|
<em:maxVersion>31.*</em:maxVersion>
|
||||||
</Description>
|
</Description>
|
||||||
</em:targetApplication>
|
</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>
|
</Description>
|
||||||
</RDF>
|
</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
|
/* 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
|
* 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",
|
"Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
|
||||||
];
|
];
|
||||||
|
|
||||||
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
|
const Cc = Components.classes;
|
||||||
var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
|
const Ci = Components.interfaces;
|
||||||
var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
|
const Cu = Components.utils;
|
||||||
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);
|
|
||||||
|
|
||||||
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 count = 0;
|
||||||
var i = 0;
|
var i = 0;
|
||||||
while(i < str.length) {
|
|
||||||
|
while (i < str.length) {
|
||||||
i = str.indexOf('"', i);
|
i = str.indexOf('"', i);
|
||||||
if (i != -1) {
|
if (i != -1) {
|
||||||
count++;
|
count++;
|
||||||
|
|
@ -26,6 +33,7 @@ var countQuotes = function(str){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -53,10 +61,12 @@ var smartSplit = function (str) {
|
||||||
var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
|
var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
|
||||||
var ret = []
|
var ret = []
|
||||||
var match = re.exec(str);
|
var match = re.exec(str);
|
||||||
|
|
||||||
while (match != null) {
|
while (match != null) {
|
||||||
ret.push(match[0].replace(/^\//, ""));
|
ret.push(match[0].replace(/^\//, ""));
|
||||||
match = re.exec(str);
|
match = re.exec(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -67,9 +77,12 @@ var smartSplit = function (str) {
|
||||||
* if no document is provided
|
* if no document is provided
|
||||||
*/
|
*/
|
||||||
function defaultDocuments() {
|
function defaultDocuments() {
|
||||||
var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);
|
var win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||||
win = windowManager.getMostRecentWindow("navigator:browser");
|
|
||||||
return [win.gBrowser.selectedBrowser.contentDocument, win.document];
|
return [
|
||||||
|
win.document,
|
||||||
|
utils.getBrowserObject(win).selectedBrowser.contentWindow.document
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -84,30 +97,37 @@ function nodeSearch(doc, func, string) {
|
||||||
} else {
|
} else {
|
||||||
var documents = defaultDocuments();
|
var documents = defaultDocuments();
|
||||||
}
|
}
|
||||||
|
|
||||||
var e = null;
|
var e = null;
|
||||||
var element = null;
|
var element = null;
|
||||||
|
|
||||||
//inline function to recursively find the element in the DOM, cross frame.
|
//inline function to recursively find the element in the DOM, cross frame.
|
||||||
var search = function(win, func, string) {
|
var search = function (win, func, string) {
|
||||||
if (win == null)
|
if (win == null) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//do the lookup in the current window
|
//do the lookup in the current window
|
||||||
element = func.call(win, string);
|
element = func.call(win, string);
|
||||||
|
|
||||||
if (!element || (element.length == 0)) {
|
if (!element || (element.length == 0)) {
|
||||||
var frames = win.frames;
|
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);
|
search(frames[i], func, string);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
e = element;
|
||||||
}
|
}
|
||||||
else { e = element; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (var i = 0; i < documents.length; ++i) {
|
for (var i = 0; i < documents.length; ++i) {
|
||||||
var win = documents[i].defaultView;
|
var win = documents[i].defaultView;
|
||||||
search(win, func, string);
|
search(win, func, string);
|
||||||
if (e) break;
|
if (e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -120,11 +140,15 @@ function Selector(_document, selector, index) {
|
||||||
if (selector == undefined) {
|
if (selector == undefined) {
|
||||||
throw new Error('Selector constructor did not recieve enough arguments.');
|
throw new Error('Selector constructor did not recieve enough arguments.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
|
|
||||||
this.getNodeForDocument = function (s) {
|
this.getNodeForDocument = function (s) {
|
||||||
return this.document.querySelectorAll(s);
|
return this.document.querySelectorAll(s);
|
||||||
};
|
};
|
||||||
|
|
||||||
var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
|
var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
|
||||||
|
|
||||||
return nodes ? nodes[index || 0] : null;
|
return nodes ? nodes[index || 0] : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -137,9 +161,11 @@ function ID(_document, nodeID) {
|
||||||
if (nodeID == undefined) {
|
if (nodeID == undefined) {
|
||||||
throw new Error('ID constructor did not recieve enough arguments.');
|
throw new Error('ID constructor did not recieve enough arguments.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getNodeForDocument = function (nodeID) {
|
this.getNodeForDocument = function (nodeID) {
|
||||||
return this.document.getElementById(nodeID);
|
return this.document.getElementById(nodeID);
|
||||||
};
|
};
|
||||||
|
|
||||||
return nodeSearch(_document, this.getNodeForDocument, nodeID);
|
return nodeSearch(_document, this.getNodeForDocument, nodeID);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -154,40 +180,48 @@ function Link(_document, linkName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getNodeForDocument = function (linkName) {
|
this.getNodeForDocument = function (linkName) {
|
||||||
var getText = function(el){
|
var getText = function (el) {
|
||||||
var text = "";
|
var text = "";
|
||||||
if (el.nodeType == 3){ //textNode
|
|
||||||
if (el.data != undefined){
|
if (el.nodeType == 3) { //textNode
|
||||||
|
if (el.data != undefined) {
|
||||||
text = el.data;
|
text = el.data;
|
||||||
} else {
|
} else {
|
||||||
text = el.innerHTML;
|
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++) {
|
for (var i = 0; i < el.childNodes.length; i++) {
|
||||||
var child = el.childNodes.item(i);
|
var child = el.childNodes.item(i);
|
||||||
text += getText(child);
|
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;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
//sometimes the windows won't have this function
|
//sometimes the windows won't have this function
|
||||||
try {
|
try {
|
||||||
var links = this.document.getElementsByTagName('a'); }
|
var links = this.document.getElementsByTagName('a');
|
||||||
catch(err){ // ADD LOG LINE mresults.write('Error: '+ err, 'lightred');
|
} catch (e) {
|
||||||
|
// ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < links.length; i++) {
|
for (var i = 0; i < links.length; i++) {
|
||||||
var el = links[i];
|
var el = links[i];
|
||||||
//if (getText(el).indexOf(this.linkName) != -1) {
|
//if (getText(el).indexOf(this.linkName) != -1) {
|
||||||
if (el.innerHTML.indexOf(linkName) != -1){
|
if (el.innerHTML.indexOf(linkName) != -1) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -214,14 +248,20 @@ function XPath(_document, expr) {
|
||||||
} else {
|
} else {
|
||||||
xpe = new this.document.defaultView.XPathEvaluator();
|
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 result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
|
||||||
var found = [];
|
var found = [];
|
||||||
var res;
|
var res;
|
||||||
while (res = result.iterateNext())
|
|
||||||
|
while (res = result.iterateNext()) {
|
||||||
found.push(res);
|
found.push(res);
|
||||||
|
}
|
||||||
|
|
||||||
return found[0];
|
return found[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
return nodeSearch(_document, this.getNodeForDocument, expr);
|
return nodeSearch(_document, this.getNodeForDocument, expr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -234,14 +274,19 @@ function Name(_document, nName) {
|
||||||
if (nName == undefined) {
|
if (nName == undefined) {
|
||||||
throw new Error('Name constructor did not recieve enough arguments.');
|
throw new Error('Name constructor did not recieve enough arguments.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getNodeForDocument = function (s) {
|
this.getNodeForDocument = function (s) {
|
||||||
try{
|
try{
|
||||||
var els = this.document.getElementsByName(s);
|
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 null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return nodeSearch(_document, this.getNodeForDocument, nName);
|
return nodeSearch(_document, this.getNodeForDocument, nName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -249,110 +294,138 @@ function Name(_document, nName) {
|
||||||
var _returnResult = function (results) {
|
var _returnResult = function (results) {
|
||||||
if (results.length == 0) {
|
if (results.length == 0) {
|
||||||
return null
|
return null
|
||||||
} else if (results.length == 1) {
|
}
|
||||||
|
else if (results.length == 1) {
|
||||||
return results[0];
|
return results[0];
|
||||||
} else {
|
} else {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _forChildren = function (element, name, value) {
|
var _forChildren = function (element, name, value) {
|
||||||
var results = [];
|
var results = [];
|
||||||
var nodes = [e for each (e in element.childNodes) if (e)]
|
var nodes = [e for each (e in element.childNodes) if (e)]
|
||||||
|
|
||||||
for (var i in nodes) {
|
for (var i in nodes) {
|
||||||
var n = nodes[i];
|
var n = nodes[i];
|
||||||
if (n[name] == value) {
|
if (n[name] == value) {
|
||||||
results.push(n);
|
results.push(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
var _forAnonChildren = function (_document, element, name, value) {
|
var _forAnonChildren = function (_document, element, name, value) {
|
||||||
var results = [];
|
var results = [];
|
||||||
var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
|
var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
|
||||||
|
|
||||||
for (var i in nodes ) {
|
for (var i in nodes ) {
|
||||||
var n = nodes[i];
|
var n = nodes[i];
|
||||||
if (n[name] == value) {
|
if (n[name] == value) {
|
||||||
results.push(n);
|
results.push(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
var _byID = function (_document, parent, value) {
|
var _byID = function (_document, parent, value) {
|
||||||
return _returnResult(_forChildren(parent, 'id', value));
|
return _returnResult(_forChildren(parent, 'id', value));
|
||||||
}
|
}
|
||||||
|
|
||||||
var _byName = function (_document, parent, value) {
|
var _byName = function (_document, parent, value) {
|
||||||
return _returnResult(_forChildren(parent, 'tagName', value));
|
return _returnResult(_forChildren(parent, 'tagName', value));
|
||||||
}
|
}
|
||||||
|
|
||||||
var _byAttrib = function (parent, attributes) {
|
var _byAttrib = function (parent, attributes) {
|
||||||
var results = [];
|
var results = [];
|
||||||
|
|
||||||
var nodes = parent.childNodes;
|
var nodes = parent.childNodes;
|
||||||
|
|
||||||
for (var i in nodes) {
|
for (var i in nodes) {
|
||||||
var n = nodes[i];
|
var n = nodes[i];
|
||||||
requirementPass = 0;
|
requirementPass = 0;
|
||||||
requirementLength = 0;
|
requirementLength = 0;
|
||||||
|
|
||||||
for (var a in attributes) {
|
for (var a in attributes) {
|
||||||
requirementLength++;
|
requirementLength++;
|
||||||
try {
|
try {
|
||||||
if (n.getAttribute(a) == attributes[a]) {
|
if (n.getAttribute(a) == attributes[a]) {
|
||||||
requirementPass++;
|
requirementPass++;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
// Workaround any bugs in custom attribute crap in XUL elements
|
// Workaround any bugs in custom attribute crap in XUL elements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requirementPass == requirementLength) {
|
if (requirementPass == requirementLength) {
|
||||||
results.push(n);
|
results.push(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _returnResult(results)
|
return _returnResult(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _byAnonAttrib = function (_document, parent, attributes) {
|
var _byAnonAttrib = function (_document, parent, attributes) {
|
||||||
var results = [];
|
var results = [];
|
||||||
|
|
||||||
if (objects.getLength(attributes) == 1) {
|
if (objects.getLength(attributes) == 1) {
|
||||||
for (var i in attributes) {var k = i; var v = attributes[i]; }
|
for (var i in attributes) {
|
||||||
var result = _document.getAnonymousElementByAttribute(parent, k, v)
|
var k = i;
|
||||||
|
var v = attributes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = _document.getAnonymousElementByAttribute(parent, k, v);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
|
var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
|
||||||
|
|
||||||
function resultsForNodes (nodes) {
|
function resultsForNodes (nodes) {
|
||||||
for (var i in nodes) {
|
for (var i in nodes) {
|
||||||
var n = nodes[i];
|
var n = nodes[i];
|
||||||
requirementPass = 0;
|
requirementPass = 0;
|
||||||
requirementLength = 0;
|
requirementLength = 0;
|
||||||
|
|
||||||
for (var a in attributes) {
|
for (var a in attributes) {
|
||||||
requirementLength++;
|
requirementLength++;
|
||||||
if (n.getAttribute(a) == attributes[a]) {
|
if (n.getAttribute(a) == attributes[a]) {
|
||||||
requirementPass++;
|
requirementPass++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requirementPass == requirementLength) {
|
if (requirementPass == requirementLength) {
|
||||||
results.push(n);
|
results.push(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultsForNodes(nodes)
|
|
||||||
|
resultsForNodes(nodes);
|
||||||
if (results.length == 0) {
|
if (results.length == 0) {
|
||||||
resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
|
resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
|
||||||
}
|
}
|
||||||
|
|
||||||
return _returnResult(results)
|
return _returnResult(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _byIndex = function (_document, parent, i) {
|
var _byIndex = function (_document, parent, i) {
|
||||||
if (parent instanceof Array) {
|
if (parent instanceof Array) {
|
||||||
return parent[i];
|
return parent[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent.childNodes[i];
|
return parent.childNodes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
var _anonByName = function (_document, parent, value) {
|
var _anonByName = function (_document, parent, value) {
|
||||||
return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
|
return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
|
||||||
}
|
}
|
||||||
|
|
||||||
var _anonByAttrib = function (_document, parent, value) {
|
var _anonByAttrib = function (_document, parent, value) {
|
||||||
return _byAnonAttrib(_document, parent, value);
|
return _byAnonAttrib(_document, parent, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var _anonByIndex = function (_document, parent, i) {
|
var _anonByIndex = function (_document, parent, i) {
|
||||||
return _document.getAnonymousNodes(parent)[i];
|
return _document.getAnonymousNodes(parent)[i];
|
||||||
}
|
}
|
||||||
|
|
@ -362,18 +435,32 @@ var _anonByIndex = function (_document, parent, i) {
|
||||||
*
|
*
|
||||||
* Finds an element by Lookup expression
|
* Finds an element by Lookup expression
|
||||||
*/
|
*/
|
||||||
function Lookup (_document, expression) {
|
function Lookup(_document, expression) {
|
||||||
if (expression == undefined) {
|
if (expression == undefined) {
|
||||||
throw new Error('Lookup constructor did not recieve enough arguments.');
|
throw new Error('Lookup constructor did not recieve enough arguments.');
|
||||||
}
|
}
|
||||||
|
|
||||||
var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
|
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 nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
|
||||||
var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
|
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
|
// Handle case where only index is provided
|
||||||
var cases = nCases;
|
var cases = nCases;
|
||||||
|
|
||||||
|
|
@ -381,21 +468,27 @@ function Lookup (_document, expression) {
|
||||||
if (withs.endsWith(exp, ']')) {
|
if (withs.endsWith(exp, ']')) {
|
||||||
var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
|
var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle anon
|
// Handle anon
|
||||||
if (withs.startsWith(exp, 'anon')) {
|
if (withs.startsWith(exp, 'anon')) {
|
||||||
var exp = strings.vslice(exp, '(', ')');
|
exp = strings.vslice(exp, '(', ')');
|
||||||
var cases = aCases;
|
cases = aCases;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withs.startsWith(exp, '[')) {
|
if (withs.startsWith(exp, '[')) {
|
||||||
try {
|
try {
|
||||||
var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
|
var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '[', ']')+' ||');
|
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) {
|
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;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -403,30 +496,28 @@ function Lookup (_document, expression) {
|
||||||
if (withs.startsWith(exp, c)) {
|
if (withs.startsWith(exp, c)) {
|
||||||
try {
|
try {
|
||||||
var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
|
var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
|
||||||
} catch(err) {
|
} catch (e) {
|
||||||
throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '(', ')')+' ||');
|
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 (!result) {
|
||||||
if ( withs.startsWith(exp, '{') ) {
|
if (withs.startsWith(exp, '{')) {
|
||||||
try {
|
try {
|
||||||
var obj = json2.JSON.parse(exp)
|
var obj = json2.JSON.parse(exp);
|
||||||
} catch(err) {
|
} catch (e) {
|
||||||
throw new Error(err+'. String to be parsed was || '+exp+' ||');
|
throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cases == aCases) {
|
if (cases == aCases) {
|
||||||
var result = _anonByAttrib(_document, parent, obj)
|
var result = _anonByAttrib(_document, parentNode, obj);
|
||||||
} else {
|
} 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
|
// Final return
|
||||||
|
|
@ -437,8 +528,10 @@ function Lookup (_document, expression) {
|
||||||
// TODO: Check length and raise error
|
// TODO: Check length and raise error
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maybe we should cause an exception here
|
// Maybe we should cause an exception here
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
return expSplit.reduce(reduceLookup);
|
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
|
/* 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
|
* 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",
|
var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
|
||||||
"getBrowserController", "newBrowserController",
|
"getBrowserController", "newBrowserController",
|
||||||
|
|
@ -8,119 +8,153 @@ var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
|
||||||
"newMail3PaneController", "getMail3PaneController",
|
"newMail3PaneController", "getMail3PaneController",
|
||||||
"wm", "platform", "getAddrbkController",
|
"wm", "platform", "getAddrbkController",
|
||||||
"getMsgComposeController", "getDownloadsController",
|
"getMsgComposeController", "getDownloadsController",
|
||||||
"Application", "cleanQuit",
|
"Application", "findElement",
|
||||||
"getPlacesController", 'isMac', 'isLinux', 'isWindows',
|
"getPlacesController", 'isMac', 'isLinux', 'isWindows',
|
||||||
"firePythonCallback"
|
"firePythonCallback", "getAddons"
|
||||||
];
|
];
|
||||||
|
|
||||||
// imports
|
const Cc = Components.classes;
|
||||||
var controller = {}; Components.utils.import('resource://mozmill/modules/controller.js', controller);
|
const Ci = Components.interfaces;
|
||||||
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
|
const Cu = Components.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);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||||
} catch(e) { /* Firefox 4 only */ }
|
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
|
// platform information
|
||||||
var platform = os.getPlatform();
|
var platform = os.getPlatform();
|
||||||
var isMac = false;
|
var isMac = false;
|
||||||
var isWindows = false;
|
var isWindows = false;
|
||||||
var isLinux = false;
|
var isLinux = false;
|
||||||
|
|
||||||
if (platform == "darwin"){
|
if (platform == "darwin"){
|
||||||
isMac = true;
|
isMac = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform == "winnt"){
|
if (platform == "winnt"){
|
||||||
isWindows = true;
|
isWindows = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform == "linux"){
|
if (platform == "linux"){
|
||||||
isLinux = true;
|
isLinux = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
var wm = Services.wm;
|
||||||
.getService(Components.interfaces.nsIWindowMediator);
|
|
||||||
|
|
||||||
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
|
var appInfo = Services.appinfo;
|
||||||
.getService(Components.interfaces.nsIXULAppInfo);
|
var Application = utils.applicationName;
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
applicationDictionary = {
|
/**
|
||||||
"{718e30fb-e89b-41dd-9da7-e25a45638b28}": "Sunbird",
|
* Retrieves the list with information about installed add-ons.
|
||||||
"{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "SeaMonkey",
|
*
|
||||||
"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "Firefox",
|
* @returns {String} JSON data of installed add-ons
|
||||||
"{3550f703-e582-4d05-9a08-453d09bdfdc6}": 'Thunderbird',
|
*/
|
||||||
|
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) {
|
// Put all our necessary information into JSON and return it:
|
||||||
// Default to Firefox
|
// appinfo, startupinfo, and addons
|
||||||
var Application = 'Firefox';
|
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
|
// get startup time if available
|
||||||
// see http://blog.mozilla.com/tglek/2011/04/26/measuring-startup-speed-correctly/
|
// see http://blog.mozilla.com/tglek/2011/04/26/measuring-startup-speed-correctly/
|
||||||
var startupInfo = {};
|
function getStartupInfo() {
|
||||||
try {
|
var startupInfo = {};
|
||||||
var _startupInfo = Components.classes["@mozilla.org/toolkit/app-startup;1"]
|
|
||||||
.getService(Components.interfaces.nsIAppStartup).getStartupInfo();
|
try {
|
||||||
for (var i in _startupInfo) {
|
var _startupInfo = Services.startup.getStartupInfo();
|
||||||
startupInfo[i] = _startupInfo[i].getTime(); // convert from Date object to ms since epoch
|
for (var time in _startupInfo) {
|
||||||
|
// convert from Date object to ms since epoch
|
||||||
|
startupInfo[time] = _startupInfo[time].getTime();
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
startupInfo = null;
|
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 () {
|
function newBrowserController () {
|
||||||
return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
|
return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
|
||||||
|
|
@ -128,34 +162,39 @@ function newBrowserController () {
|
||||||
|
|
||||||
function getBrowserController () {
|
function getBrowserController () {
|
||||||
var browserWindow = wm.getMostRecentWindow("navigator:browser");
|
var browserWindow = wm.getMostRecentWindow("navigator:browser");
|
||||||
|
|
||||||
if (browserWindow == null) {
|
if (browserWindow == null) {
|
||||||
return newBrowserController();
|
return newBrowserController();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return new controller.MozMillController(browserWindow);
|
return new controller.MozMillController(browserWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlacesController () {
|
function getPlacesController () {
|
||||||
utils.getMethodInWindows('PlacesCommandHook').showPlacesOrganizer('AllBookmarks');
|
utils.getMethodInWindows('PlacesCommandHook').showPlacesOrganizer('AllBookmarks');
|
||||||
|
|
||||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAddonsController () {
|
function getAddonsController () {
|
||||||
if (Application == 'SeaMonkey') {
|
if (Application == 'SeaMonkey') {
|
||||||
utils.getMethodInWindows('toEM')();
|
utils.getMethodInWindows('toEM')();
|
||||||
} else if (Application == 'Thunderbird') {
|
}
|
||||||
|
else if (Application == 'Thunderbird') {
|
||||||
utils.getMethodInWindows('openAddonsMgr')();
|
utils.getMethodInWindows('openAddonsMgr')();
|
||||||
} else if (Application == 'Sunbird') {
|
}
|
||||||
|
else if (Application == 'Sunbird') {
|
||||||
utils.getMethodInWindows('goOpenAddons')();
|
utils.getMethodInWindows('goOpenAddons')();
|
||||||
} else {
|
} else {
|
||||||
utils.getMethodInWindows('BrowserOpenAddonsMgr')();
|
utils.getMethodInWindows('BrowserOpenAddonsMgr')();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDownloadsController() {
|
function getDownloadsController() {
|
||||||
utils.getMethodInWindows('BrowserDownloadsUI')();
|
utils.getMethodInWindows('BrowserDownloadsUI')();
|
||||||
|
|
||||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +204,7 @@ function getPreferencesController() {
|
||||||
} else {
|
} else {
|
||||||
utils.getMethodInWindows('openPreferences')();
|
utils.getMethodInWindows('openPreferences')();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
return new controller.MozMillController(wm.getMostRecentWindow(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,10 +215,10 @@ function newMail3PaneController () {
|
||||||
|
|
||||||
function getMail3PaneController () {
|
function getMail3PaneController () {
|
||||||
var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
|
var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
|
||||||
|
|
||||||
if (mail3PaneWindow == null) {
|
if (mail3PaneWindow == null) {
|
||||||
return newMail3PaneController();
|
return newMail3PaneController();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return new controller.MozMillController(mail3PaneWindow);
|
return new controller.MozMillController(mail3PaneWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +228,7 @@ function newAddrbkController () {
|
||||||
utils.getMethodInWindows("toAddressBook")();
|
utils.getMethodInWindows("toAddressBook")();
|
||||||
utils.sleep(2000);
|
utils.sleep(2000);
|
||||||
var addyWin = wm.getMostRecentWindow("mail:addressbook");
|
var addyWin = wm.getMostRecentWindow("mail:addressbook");
|
||||||
|
|
||||||
return new controller.MozMillController(addyWin);
|
return new controller.MozMillController(addyWin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,35 +236,50 @@ function getAddrbkController () {
|
||||||
var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
|
var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
|
||||||
if (addrbkWindow == null) {
|
if (addrbkWindow == null) {
|
||||||
return newAddrbkController();
|
return newAddrbkController();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return new controller.MozMillController(addrbkWindow);
|
return new controller.MozMillController(addrbkWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function firePythonCallback (filename, method, args, kwargs) {
|
function firePythonCallback (filename, method, args, kwargs) {
|
||||||
obj = {'filename': filename, 'method': method};
|
obj = {'filename': filename, 'method': method};
|
||||||
obj['test'] = frame.events.currentModule.__file__;
|
|
||||||
obj['args'] = args || [];
|
obj['args'] = args || [];
|
||||||
obj['kwargs'] = kwargs || {};
|
obj['kwargs'] = kwargs || {};
|
||||||
frame.events.fireEvent("firePythonCallback", obj);
|
|
||||||
|
broker.sendMessage("firePythonCallback", obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function timer (name) {
|
function timer (name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.timers = {};
|
this.timers = {};
|
||||||
frame.timers.push(this);
|
|
||||||
this.actions = [];
|
this.actions = [];
|
||||||
|
|
||||||
|
frame.timers.push(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.prototype.start = function (name) {
|
timer.prototype.start = function (name) {
|
||||||
this.timers[name].startTime = (new Date).getTime();
|
this.timers[name].startTime = (new Date).getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.prototype.stop = function (name) {
|
timer.prototype.stop = function (name) {
|
||||||
var t = this.timers[name];
|
var t = this.timers[name];
|
||||||
|
|
||||||
t.endTime = (new Date).getTime();
|
t.endTime = (new Date).getTime();
|
||||||
t.totalTime = (t.endTime - t.startTime);
|
t.totalTime = (t.endTime - t.startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.prototype.end = function () {
|
timer.prototype.end = function () {
|
||||||
frame.events.fireEvent("timer", this);
|
frame.events.fireEvent("timer", this);
|
||||||
frame.timers.remove(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
|
/* 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
|
* 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 EXPORTED_SYMBOLS = ['Assert', 'Expect'];
|
||||||
var mozmillFrame = {};
|
|
||||||
Cu.import('resource://mozmill/modules/frame.js', mozmillFrame);
|
|
||||||
|
|
||||||
|
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
|
* @name assertions
|
||||||
* @namespace Defines expect and assert methods to be used for 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 */
|
Assert.prototype = {
|
||||||
var Expect = function() {}
|
|
||||||
|
|
||||||
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
|
* @param {object} aResult
|
||||||
* Test result details used for reporting.
|
* Test result details used for reporting.
|
||||||
* <dl>
|
* <dl>
|
||||||
* <dd>fileName</dd>
|
* <dd>fileName</dd>
|
||||||
* <dt>Name of the file in which the assertion failed.</dt>
|
* <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>
|
* <dt>Function in which the assertion failed.</dt>
|
||||||
* <dd>lineNumber</dd>
|
* <dd>lineNumber</dd>
|
||||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
* <dt>Line number of the file in which the assertion failed.</dt>
|
||||||
* <dd>message</dd>
|
* <dd>message</dd>
|
||||||
* <dt>Message why the assertion failed.</dt>
|
* <dt>Message why the assertion failed.</dt>
|
||||||
* </dl>
|
* </dl>
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
_logFail: function Expect__logFail(aResult) {
|
_logFail: function Assert__logFail(aResult) {
|
||||||
mozmillFrame.events.fail({fail: aResult});
|
throw new errors.AssertionError(aResult.message,
|
||||||
|
aResult.fileName,
|
||||||
|
aResult.lineNumber,
|
||||||
|
aResult.functionName,
|
||||||
|
aResult.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,7 +179,7 @@ Expect.prototype = {
|
||||||
* <dl>
|
* <dl>
|
||||||
* <dd>fileName</dd>
|
* <dd>fileName</dd>
|
||||||
* <dt>Name of the file in which the assertion failed.</dt>
|
* <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>
|
* <dt>Function in which the assertion failed.</dt>
|
||||||
* <dd>lineNumber</dd>
|
* <dd>lineNumber</dd>
|
||||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
* <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>
|
* <dt>Message why the assertion failed.</dt>
|
||||||
* </dl>
|
* </dl>
|
||||||
*/
|
*/
|
||||||
_logPass: function Expect__logPass(aResult) {
|
_logPass: function Assert__logPass(aResult) {
|
||||||
mozmillFrame.events.pass({pass: aResult});
|
broker.pass({pass: aResult});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -68,9 +200,11 @@ Expect.prototype = {
|
||||||
* Message to show for the test result
|
* Message to show for the test result
|
||||||
* @param {string} aDiagnosis
|
* @param {string} aDiagnosis
|
||||||
* Diagnose message to show for the test result
|
* Diagnose message to show for the test result
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Result of the test.
|
* @returns {boolean} Result of the test.
|
||||||
*/
|
*/
|
||||||
_test: function Expect__test(aCondition, aMessage, aDiagnosis) {
|
_test: function Assert__test(aCondition, aMessage, aDiagnosis) {
|
||||||
let diagnosis = aDiagnosis || "";
|
let diagnosis = aDiagnosis || "";
|
||||||
let message = aMessage || "";
|
let message = aMessage || "";
|
||||||
|
|
||||||
|
|
@ -78,19 +212,23 @@ Expect.prototype = {
|
||||||
message = aMessage ? message + " - " + diagnosis : diagnosis;
|
message = aMessage ? message + " - " + diagnosis : diagnosis;
|
||||||
|
|
||||||
// Build result data
|
// Build result data
|
||||||
let frame = Components.stack;
|
let frame = stack.findCallerFrame(Components.stack);
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
'fileName' : frame.filename.replace(/(.*)-> /, ""),
|
'fileName' : frame.filename.replace(/(.*)-> /, ""),
|
||||||
'function' : frame.name,
|
'functionName' : frame.name,
|
||||||
'lineNumber' : frame.lineNumber,
|
'lineNumber' : frame.lineNumber,
|
||||||
'message' : message
|
'message' : message
|
||||||
};
|
};
|
||||||
|
|
||||||
// Log test result
|
// Log test result
|
||||||
if (aCondition)
|
if (aCondition) {
|
||||||
this._logPass(result);
|
this._logPass(result);
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
|
result.stack = Components.stack;
|
||||||
this._logFail(result);
|
this._logFail(result);
|
||||||
|
}
|
||||||
|
|
||||||
return aCondition;
|
return aCondition;
|
||||||
},
|
},
|
||||||
|
|
@ -102,7 +240,7 @@ Expect.prototype = {
|
||||||
* Message to show for the test result.
|
* Message to show for the test result.
|
||||||
* @returns {boolean} Always returns true.
|
* @returns {boolean} Always returns true.
|
||||||
*/
|
*/
|
||||||
pass: function Expect_pass(aMessage) {
|
pass: function Assert_pass(aMessage) {
|
||||||
return this._test(true, aMessage, undefined);
|
return this._test(true, aMessage, undefined);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -111,9 +249,11 @@ Expect.prototype = {
|
||||||
*
|
*
|
||||||
* @param {string} aMessage
|
* @param {string} aMessage
|
||||||
* Message to show for the test result.
|
* Message to show for the test result.
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Always returns false.
|
* @returns {boolean} Always returns false.
|
||||||
*/
|
*/
|
||||||
fail: function Expect_fail(aMessage) {
|
fail: function Assert_fail(aMessage) {
|
||||||
return this._test(false, aMessage, undefined);
|
return this._test(false, aMessage, undefined);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -124,16 +264,18 @@ Expect.prototype = {
|
||||||
* Value to test.
|
* Value to test.
|
||||||
* @param {string} aMessage
|
* @param {string} aMessage
|
||||||
* Message to show for the test result.
|
* Message to show for the test result.
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Result of the test.
|
* @returns {boolean} Result of the test.
|
||||||
*/
|
*/
|
||||||
ok: function Expect_ok(aValue, aMessage) {
|
ok: function Assert_ok(aValue, aMessage) {
|
||||||
let condition = !!aValue;
|
let condition = !!aValue;
|
||||||
let diagnosis = "got '" + aValue + "'";
|
let diagnosis = "got '" + aValue + "'";
|
||||||
|
|
||||||
return this._test(condition, aMessage, diagnosis);
|
return this._test(condition, aMessage, diagnosis);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if both specified values are identical.
|
* Test if both specified values are identical.
|
||||||
*
|
*
|
||||||
* @param {boolean|string|number|object} aValue
|
* @param {boolean|string|number|object} aValue
|
||||||
|
|
@ -142,16 +284,18 @@ Expect.prototype = {
|
||||||
* Value to strictly compare with.
|
* Value to strictly compare with.
|
||||||
* @param {string} aMessage
|
* @param {string} aMessage
|
||||||
* Message to show for the test result
|
* Message to show for the test result
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Result of the test.
|
* @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 condition = (aValue === aExpected);
|
||||||
let diagnosis = "got '" + aValue + "', expected '" + aExpected + "'";
|
let diagnosis = "'" + aValue + "' should equal '" + aExpected + "'";
|
||||||
|
|
||||||
return this._test(condition, aMessage, diagnosis);
|
return this._test(condition, aMessage, diagnosis);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if both specified values are not identical.
|
* Test if both specified values are not identical.
|
||||||
*
|
*
|
||||||
* @param {boolean|string|number|object} aValue
|
* @param {boolean|string|number|object} aValue
|
||||||
|
|
@ -160,15 +304,81 @@ Expect.prototype = {
|
||||||
* Value to strictly compare with.
|
* Value to strictly compare with.
|
||||||
* @param {string} aMessage
|
* @param {string} aMessage
|
||||||
* Message to show for the test result
|
* Message to show for the test result
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Result of the test.
|
* @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 condition = (aValue !== aExpected);
|
||||||
let diagnosis = "got '" + aValue + "', not expected '" + aExpected + "'";
|
let diagnosis = "'" + aValue + "' should not equal '" + aExpected + "'";
|
||||||
|
|
||||||
return this._test(condition, aMessage, diagnosis);
|
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.
|
* Test if the regular expression matches the string.
|
||||||
*
|
*
|
||||||
|
|
@ -178,9 +388,11 @@ Expect.prototype = {
|
||||||
* Regular expression to use for testing that a match exists.
|
* Regular expression to use for testing that a match exists.
|
||||||
* @param {string} aMessage
|
* @param {string} aMessage
|
||||||
* Message to show for the test result
|
* Message to show for the test result
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Result of the test.
|
* @returns {boolean} Result of the test.
|
||||||
*/
|
*/
|
||||||
match: function Expect_match(aString, aRegex, aMessage) {
|
match: function Assert_match(aString, aRegex, aMessage) {
|
||||||
// XXX Bug 634948
|
// XXX Bug 634948
|
||||||
// Regex objects are transformed to strings when evaluated in a sandbox
|
// Regex objects are transformed to strings when evaluated in a sandbox
|
||||||
// For now lets re-create the regex from its string representation
|
// For now lets re-create the regex from its string representation
|
||||||
|
|
@ -190,8 +402,7 @@ Expect.prototype = {
|
||||||
|
|
||||||
pattern = matches[1];
|
pattern = matches[1];
|
||||||
flags = matches[2];
|
flags = matches[2];
|
||||||
}
|
} catch (e) {
|
||||||
catch (ex) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let regex = new RegExp(pattern, flags);
|
let regex = new RegExp(pattern, flags);
|
||||||
|
|
@ -210,9 +421,11 @@ Expect.prototype = {
|
||||||
* Regular expression to use for testing that a match does not exist.
|
* Regular expression to use for testing that a match does not exist.
|
||||||
* @param {string} aMessage
|
* @param {string} aMessage
|
||||||
* Message to show for the test result
|
* Message to show for the test result
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Result of the test.
|
* @returns {boolean} Result of the test.
|
||||||
*/
|
*/
|
||||||
notMatch: function Expect_notMatch(aString, aRegex, aMessage) {
|
notMatch: function Assert_notMatch(aString, aRegex, aMessage) {
|
||||||
// XXX Bug 634948
|
// XXX Bug 634948
|
||||||
// Regex objects are transformed to strings when evaluated in a sandbox
|
// Regex objects are transformed to strings when evaluated in a sandbox
|
||||||
// For now lets re-create the regex from its string representation
|
// For now lets re-create the regex from its string representation
|
||||||
|
|
@ -222,8 +435,7 @@ Expect.prototype = {
|
||||||
|
|
||||||
pattern = matches[1];
|
pattern = matches[1];
|
||||||
flags = matches[2];
|
flags = matches[2];
|
||||||
}
|
} catch (e) {
|
||||||
catch (ex) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let regex = new RegExp(pattern, flags);
|
let regex = new RegExp(pattern, flags);
|
||||||
|
|
@ -243,9 +455,11 @@ Expect.prototype = {
|
||||||
* the expected error class
|
* the expected error class
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
* message to present if assertion fails
|
* message to present if assertion fails
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Result of the test.
|
* @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)));
|
return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -258,9 +472,11 @@ Expect.prototype = {
|
||||||
* the expected error class
|
* the expected error class
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
* message to present if assertion fails
|
* message to present if assertion fails
|
||||||
|
* @throws {errors.AssertionError}
|
||||||
|
*
|
||||||
* @returns {boolean} Result of the test.
|
* @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)));
|
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()
|
adapted from node.js's assert._throws()
|
||||||
https://github.com/joyent/node/blob/master/lib/assert.js
|
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;
|
var actual;
|
||||||
|
|
||||||
if (typeof expected === 'string') {
|
if (typeof expected === 'string') {
|
||||||
|
|
@ -299,80 +515,153 @@ Expect.prototype = {
|
||||||
!this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
|
!this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
|
||||||
throw actual;
|
throw actual;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._test(true, message);
|
return this._test(true, message);
|
||||||
},
|
},
|
||||||
|
|
||||||
_expectedException : function Expect__expectedException(actual, expected) {
|
/**
|
||||||
if (!actual || !expected) {
|
* Test if the string contains the pattern.
|
||||||
return false;
|
*
|
||||||
|
* @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) {
|
var hwindow = Services.appShell.hiddenDOMWindow;
|
||||||
return expected.test(actual);
|
var timeoutInterval = hwindow.setInterval(wait, interval);
|
||||||
} else if (actual instanceof expected) {
|
var thread = Services.tm.currentThread;
|
||||||
return true;
|
|
||||||
} else if (expected.call({}, actual) === true) {
|
while (self.result !== true && !self.timeIsUp) {
|
||||||
return true;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* non-fatal assertions */
|
||||||
* AssertionError
|
var Expect = function () {}
|
||||||
*
|
|
||||||
* 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';
|
|
||||||
|
|
||||||
|
Expect.prototype = new Assert();
|
||||||
var Assert = function() {}
|
|
||||||
|
|
||||||
Assert.prototype = new Expect();
|
|
||||||
|
|
||||||
Assert.prototype.AssertionError = AssertionError;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Assert class implements fatal assertions, and can be used in cases
|
* Log a test as failing by adding a fail frame.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* @param {object} aResult
|
* @param {object} aResult
|
||||||
* Test result details used for reporting.
|
* Test result details used for reporting.
|
||||||
* <dl>
|
* <dl>
|
||||||
* <dd>fileName</dd>
|
* <dd>fileName</dd>
|
||||||
* <dt>Name of the file in which the assertion failed.</dt>
|
* <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>
|
* <dt>Function in which the assertion failed.</dt>
|
||||||
* <dd>lineNumber</dd>
|
* <dd>lineNumber</dd>
|
||||||
* <dt>Line number of the file in which the assertion failed.</dt>
|
* <dt>Line number of the file in which the assertion failed.</dt>
|
||||||
* <dd>message</dd>
|
* <dd>message</dd>
|
||||||
* <dt>Message why the assertion failed.</dt>
|
* <dt>Message why the assertion failed.</dt>
|
||||||
* </dl>
|
* </dl>
|
||||||
* @throws {AssertionError }
|
|
||||||
*/
|
*/
|
||||||
Assert.prototype._logFail = function Assert__logFail(aResult) {
|
Expect.prototype._logFail = function Expect__logFail(aResult) {
|
||||||
throw new AssertionError(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
|
try {
|
||||||
assertions.Expect = Expect;
|
Assert.prototype.waitFor.apply(this, arguments);
|
||||||
assertions.Assert = Assert;
|
}
|
||||||
|
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
|
/* 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
|
* 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
|
* @namespace Defines useful methods to work with localized content
|
||||||
*/
|
*/
|
||||||
var l10n = exports;
|
var l10n = exports;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the localized content for a given DTD entity
|
* Retrieve the localized content for a given DTD entity
|
||||||
*
|
*
|
||||||
|
|
@ -54,14 +56,11 @@ function getEntity(aDTDs, aEntityId) {
|
||||||
* @returns {String} Value of the requested property
|
* @returns {String} Value of the requested property
|
||||||
*/
|
*/
|
||||||
function getProperty(aURL, aProperty) {
|
function getProperty(aURL, aProperty) {
|
||||||
var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
|
var bundle = Services.strings.createBundle(aURL);
|
||||||
getService(Ci.nsIStringBundleService);
|
|
||||||
var bundle = sbs.createBundle(aURL);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return bundle.GetStringFromName(aProperty);
|
return bundle.GetStringFromName(aProperty);
|
||||||
}
|
} catch (ex) {
|
||||||
catch (ex) {
|
|
||||||
throw new Error("Unkown property '" + aProperty + "'");
|
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
|
/* 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
|
* 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]) {
|
if (value == array[i]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSet (array) {
|
function getSet(array) {
|
||||||
var narray = [];
|
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]);
|
narray.push(array[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return narray;
|
return narray;
|
||||||
}
|
}
|
||||||
|
|
||||||
function indexOf (array, v, offset) {
|
function indexOf(array, v, offset) {
|
||||||
for (i in array) {
|
for (var i in array) {
|
||||||
if (offset == undefined || i >= offset) {
|
if (offset == undefined || i >= offset) {
|
||||||
if ( !isNaN(i) && array[i] == v) {
|
if (!isNaN(i) && array[i] == v) {
|
||||||
return new Number(i);
|
return new Number(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rindexOf (array, v) {
|
function rindexOf (array, v) {
|
||||||
var l = array.length;
|
var l = array.length;
|
||||||
for (i in array) {
|
|
||||||
|
for (var i in array) {
|
||||||
if (!isNaN(i)) {
|
if (!isNaN(i)) {
|
||||||
var i = new Number(i)
|
var i = new Number(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNaN(i) && array[l - i] == v) {
|
if (!isNaN(i) && array[l - i] == v) {
|
||||||
return l - i;
|
return l - i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,10 +67,12 @@ function compare (array, carray) {
|
||||||
if (array.length != carray.length) {
|
if (array.length != carray.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (i in array) {
|
|
||||||
|
for (var i in array) {
|
||||||
if (array[i] != carray[i]) {
|
if (array[i] != carray[i]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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 EXPORTED_SYMBOLS = ['getAttributes'];
|
||||||
|
|
||||||
|
|
||||||
var getAttributes = function (node) {
|
var getAttributes = function (node) {
|
||||||
var attributes = {};
|
var attributes = {};
|
||||||
for (i in node.attributes) {
|
|
||||||
if ( !isNaN(i) ) {
|
for (var i in node.attributes) {
|
||||||
|
if (!isNaN(i)) {
|
||||||
try {
|
try {
|
||||||
var attr = node.attributes[i];
|
var attr = node.attributes[i];
|
||||||
attributes[attr.name] = attr.value;
|
attributes[attr.name] = attr.value;
|
||||||
} catch (err) {
|
}
|
||||||
|
catch (e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,36 @@
|
||||||
* httpd.js.
|
* 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");
|
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 Cc = Components.classes;
|
||||||
const Ci = Components.interfaces;
|
const Ci = Components.interfaces;
|
||||||
const Cr = Components.results;
|
const Cr = Components.results;
|
||||||
|
|
@ -58,7 +78,7 @@ function NS_ASSERT(cond, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs an HTTP error object. */
|
/** Constructs an HTTP error object. */
|
||||||
function HttpError(code, description)
|
this.HttpError = function HttpError(code, description)
|
||||||
{
|
{
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
@ -74,30 +94,30 @@ HttpError.prototype =
|
||||||
/**
|
/**
|
||||||
* Errors thrown to trigger specific HTTP server responses.
|
* Errors thrown to trigger specific HTTP server responses.
|
||||||
*/
|
*/
|
||||||
const HTTP_400 = new HttpError(400, "Bad Request");
|
this.HTTP_400 = new HttpError(400, "Bad Request");
|
||||||
const HTTP_401 = new HttpError(401, "Unauthorized");
|
this.HTTP_401 = new HttpError(401, "Unauthorized");
|
||||||
const HTTP_402 = new HttpError(402, "Payment Required");
|
this.HTTP_402 = new HttpError(402, "Payment Required");
|
||||||
const HTTP_403 = new HttpError(403, "Forbidden");
|
this.HTTP_403 = new HttpError(403, "Forbidden");
|
||||||
const HTTP_404 = new HttpError(404, "Not Found");
|
this.HTTP_404 = new HttpError(404, "Not Found");
|
||||||
const HTTP_405 = new HttpError(405, "Method Not Allowed");
|
this.HTTP_405 = new HttpError(405, "Method Not Allowed");
|
||||||
const HTTP_406 = new HttpError(406, "Not Acceptable");
|
this.HTTP_406 = new HttpError(406, "Not Acceptable");
|
||||||
const HTTP_407 = new HttpError(407, "Proxy Authentication Required");
|
this.HTTP_407 = new HttpError(407, "Proxy Authentication Required");
|
||||||
const HTTP_408 = new HttpError(408, "Request Timeout");
|
this.HTTP_408 = new HttpError(408, "Request Timeout");
|
||||||
const HTTP_409 = new HttpError(409, "Conflict");
|
this.HTTP_409 = new HttpError(409, "Conflict");
|
||||||
const HTTP_410 = new HttpError(410, "Gone");
|
this.HTTP_410 = new HttpError(410, "Gone");
|
||||||
const HTTP_411 = new HttpError(411, "Length Required");
|
this.HTTP_411 = new HttpError(411, "Length Required");
|
||||||
const HTTP_412 = new HttpError(412, "Precondition Failed");
|
this.HTTP_412 = new HttpError(412, "Precondition Failed");
|
||||||
const HTTP_413 = new HttpError(413, "Request Entity Too Large");
|
this.HTTP_413 = new HttpError(413, "Request Entity Too Large");
|
||||||
const HTTP_414 = new HttpError(414, "Request-URI Too Long");
|
this.HTTP_414 = new HttpError(414, "Request-URI Too Long");
|
||||||
const HTTP_415 = new HttpError(415, "Unsupported Media Type");
|
this.HTTP_415 = new HttpError(415, "Unsupported Media Type");
|
||||||
const HTTP_417 = new HttpError(417, "Expectation Failed");
|
this.HTTP_417 = new HttpError(417, "Expectation Failed");
|
||||||
|
|
||||||
const HTTP_500 = new HttpError(500, "Internal Server Error");
|
this.HTTP_500 = new HttpError(500, "Internal Server Error");
|
||||||
const HTTP_501 = new HttpError(501, "Not Implemented");
|
this.HTTP_501 = new HttpError(501, "Not Implemented");
|
||||||
const HTTP_502 = new HttpError(502, "Bad Gateway");
|
this.HTTP_502 = new HttpError(502, "Bad Gateway");
|
||||||
const HTTP_503 = new HttpError(503, "Service Unavailable");
|
this.HTTP_503 = new HttpError(503, "Service Unavailable");
|
||||||
const HTTP_504 = new HttpError(504, "Gateway Timeout");
|
this.HTTP_504 = new HttpError(504, "Gateway Timeout");
|
||||||
const HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
|
this.HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
|
||||||
|
|
||||||
/** Creates a hash with fields corresponding to the values in arr. */
|
/** Creates a hash with fields corresponding to the values in arr. */
|
||||||
function array2obj(arr)
|
function array2obj(arr)
|
||||||
|
|
@ -451,7 +471,15 @@ nsHttpServer.prototype =
|
||||||
onStopListening: function(socket, status)
|
onStopListening: function(socket, status)
|
||||||
{
|
{
|
||||||
dumpn(">>> shutting down server on port " + socket.port);
|
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;
|
this._socketClosed = true;
|
||||||
|
if (this._hasOpenConnections()) {
|
||||||
|
dumpn("*** open connections!!!");
|
||||||
|
}
|
||||||
if (!this._hasOpenConnections())
|
if (!this._hasOpenConnections())
|
||||||
{
|
{
|
||||||
dumpn("*** no open connections, notifying async from onStopListening");
|
dumpn("*** no open connections, notifying async from onStopListening");
|
||||||
|
|
@ -493,12 +521,14 @@ nsHttpServer.prototype =
|
||||||
this._host = host;
|
this._host = host;
|
||||||
|
|
||||||
// The listen queue needs to be long enough to handle
|
// The listen queue needs to be long enough to handle
|
||||||
// network.http.max-persistent-connections-per-server concurrent connections,
|
// network.http.max-persistent-connections-per-server or
|
||||||
// plus a safety margin in case some other process is talking to
|
// network.http.max-persistent-connections-per-proxy concurrent
|
||||||
// the server as well.
|
// connections, plus a safety margin in case some other process is
|
||||||
|
// talking to the server as well.
|
||||||
var prefs = getRootPrefBranch();
|
var prefs = getRootPrefBranch();
|
||||||
var maxConnections =
|
var maxConnections = 5 + Math.max(
|
||||||
prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5;
|
prefs.getIntPref("network.http.max-persistent-connections-per-server"),
|
||||||
|
prefs.getIntPref("network.http.max-persistent-connections-per-proxy"));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -507,18 +537,52 @@ nsHttpServer.prototype =
|
||||||
var loopback = false;
|
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
|
loopback, // true = localhost, false = everybody
|
||||||
maxConnections);
|
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 +
|
dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
|
||||||
" pending connections");
|
" pending connections");
|
||||||
socket.asyncListen(this);
|
socket.asyncListen(this);
|
||||||
this._identity._initialize(port, host, true);
|
this._port = socket.port;
|
||||||
|
this._identity._initialize(socket.port, host, true);
|
||||||
this._socket = socket;
|
this._socket = socket;
|
||||||
}
|
}
|
||||||
catch (e)
|
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;
|
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -587,6 +651,14 @@ nsHttpServer.prototype =
|
||||||
this._handler.registerPathHandler(path, handler);
|
this._handler.registerPathHandler(path, handler);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// see nsIHttpServer.registerPrefixHandler
|
||||||
|
//
|
||||||
|
registerPrefixHandler: function(prefix, handler)
|
||||||
|
{
|
||||||
|
this._handler.registerPrefixHandler(prefix, handler);
|
||||||
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
// see nsIHttpServer.registerErrorHandler
|
// see nsIHttpServer.registerErrorHandler
|
||||||
//
|
//
|
||||||
|
|
@ -759,6 +831,10 @@ nsHttpServer.prototype =
|
||||||
// Fire a pending server-stopped notification if it's our responsibility.
|
// Fire a pending server-stopped notification if it's our responsibility.
|
||||||
if (!this._hasOpenConnections() && this._socketClosed)
|
if (!this._hasOpenConnections() && this._socketClosed)
|
||||||
this._notifyStopped();
|
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:
|
// RFC 2396 section 3.2.2:
|
||||||
|
|
@ -1097,14 +1174,25 @@ function Connection(input, output, server, port, outgoingPort, number)
|
||||||
*/
|
*/
|
||||||
this.request = null;
|
this.request = null;
|
||||||
|
|
||||||
/** State variables for debugging. */
|
/** This allows a connection to disambiguate between a peer initiating a
|
||||||
this._closed = this._processed = false;
|
* 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 =
|
Connection.prototype =
|
||||||
{
|
{
|
||||||
/** Closes this connection's input/output streams. */
|
/** Closes this connection's input/output streams. */
|
||||||
close: function()
|
close: function()
|
||||||
{
|
{
|
||||||
|
if (this._closed)
|
||||||
|
return;
|
||||||
|
|
||||||
dumpn("*** closing connection " + this.number +
|
dumpn("*** closing connection " + this.number +
|
||||||
" on port " + this._outgoingPort);
|
" on port " + this._outgoingPort);
|
||||||
|
|
||||||
|
|
@ -1162,6 +1250,11 @@ Connection.prototype =
|
||||||
return "<Connection(" + this.number +
|
return "<Connection(" + this.number +
|
||||||
(this.request ? ", " + this.request.path : "") +"): " +
|
(this.request ? ", " + this.request.path : "") +"): " +
|
||||||
(this._closed ? "closed" : "open") + ">";
|
(this._closed ? "closed" : "open") + ">";
|
||||||
|
},
|
||||||
|
|
||||||
|
requestStarted: function()
|
||||||
|
{
|
||||||
|
this._requestStarted = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1348,6 +1441,7 @@ RequestReader.prototype =
|
||||||
{
|
{
|
||||||
this._parseRequestLine(line.value);
|
this._parseRequestLine(line.value);
|
||||||
this._state = READER_IN_HEADERS;
|
this._state = READER_IN_HEADERS;
|
||||||
|
this._connection.requestStarted();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
|
|
@ -1606,7 +1700,10 @@ RequestReader.prototype =
|
||||||
// between fields, even though only a single SP is required (section 19.3)
|
// between fields, even though only a single SP is required (section 19.3)
|
||||||
var request = line.split(/[ \t]+/);
|
var request = line.split(/[ \t]+/);
|
||||||
if (!request || request.length != 3)
|
if (!request || request.length != 3)
|
||||||
|
{
|
||||||
|
dumpn("*** No request in line");
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
|
}
|
||||||
|
|
||||||
metadata._method = request[0];
|
metadata._method = request[0];
|
||||||
|
|
||||||
|
|
@ -1614,7 +1711,10 @@ RequestReader.prototype =
|
||||||
var ver = request[2];
|
var ver = request[2];
|
||||||
var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
|
var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
|
||||||
if (!match)
|
if (!match)
|
||||||
|
{
|
||||||
|
dumpn("*** No HTTP version in line");
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
|
}
|
||||||
|
|
||||||
// determine HTTP version
|
// determine HTTP version
|
||||||
try
|
try
|
||||||
|
|
@ -1639,7 +1739,10 @@ RequestReader.prototype =
|
||||||
{
|
{
|
||||||
// No absolute paths in the request line in HTTP prior to 1.1
|
// No absolute paths in the request line in HTTP prior to 1.1
|
||||||
if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
|
if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
|
||||||
|
{
|
||||||
|
dumpn("*** Metadata version too low");
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -1653,11 +1756,18 @@ RequestReader.prototype =
|
||||||
if (port === -1)
|
if (port === -1)
|
||||||
{
|
{
|
||||||
if (scheme === "http")
|
if (scheme === "http")
|
||||||
|
{
|
||||||
port = 80;
|
port = 80;
|
||||||
|
}
|
||||||
else if (scheme === "https")
|
else if (scheme === "https")
|
||||||
|
{
|
||||||
port = 443;
|
port = 443;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
dumpn("*** Unknown scheme: " + scheme);
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
|
|
@ -1665,11 +1775,15 @@ RequestReader.prototype =
|
||||||
// If the host is not a valid host on the server, the response MUST be a
|
// 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
|
// 400 (Bad Request) error message (section 5.2). Alternately, the URI
|
||||||
// is malformed.
|
// is malformed.
|
||||||
|
dumpn("*** Threw when dealing with URI: " + e);
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
|
if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
|
||||||
|
{
|
||||||
|
dumpn("*** serverIdentity unknown or path does not start with '/'");
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var splitter = fullPath.indexOf("?");
|
var splitter = fullPath.indexOf("?");
|
||||||
|
|
@ -1713,6 +1827,8 @@ RequestReader.prototype =
|
||||||
var line = {};
|
var line = {};
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
dumpn("*** Last name: '" + lastName + "'");
|
||||||
|
dumpn("*** Last val: '" + lastVal + "'");
|
||||||
NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
|
NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
|
||||||
lastName === undefined ?
|
lastName === undefined ?
|
||||||
"lastVal without lastName? lastVal: '" + lastVal + "'" :
|
"lastVal without lastName? lastVal: '" + lastVal + "'" :
|
||||||
|
|
@ -1727,6 +1843,7 @@ RequestReader.prototype =
|
||||||
}
|
}
|
||||||
|
|
||||||
var lineText = line.value;
|
var lineText = line.value;
|
||||||
|
dumpn("*** Line text: '" + lineText + "'");
|
||||||
var firstChar = lineText.charAt(0);
|
var firstChar = lineText.charAt(0);
|
||||||
|
|
||||||
// blank line means end of headers
|
// blank line means end of headers
|
||||||
|
|
@ -1741,7 +1858,7 @@ RequestReader.prototype =
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
dumpn("*** e == " + e);
|
dumpn("*** setHeader threw on last header, e == " + e);
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1759,7 +1876,7 @@ RequestReader.prototype =
|
||||||
// multi-line header if we've already seen a header line
|
// multi-line header if we've already seen a header line
|
||||||
if (!lastName)
|
if (!lastName)
|
||||||
{
|
{
|
||||||
// we don't have a header to continue!
|
dumpn("We don't have a header to continue!");
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1778,7 +1895,7 @@ RequestReader.prototype =
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
dumpn("*** e == " + e);
|
dumpn("*** setHeader threw on a header, e == " + e);
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1786,7 +1903,7 @@ RequestReader.prototype =
|
||||||
var colon = lineText.indexOf(":"); // first colon must be splitter
|
var colon = lineText.indexOf(":"); // first colon must be splitter
|
||||||
if (colon < 1)
|
if (colon < 1)
|
||||||
{
|
{
|
||||||
// no colon or missing header field-name
|
dumpn("*** No colon or missing header field-name");
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1811,12 +1928,14 @@ const CR = 0x0D, LF = 0x0A;
|
||||||
* character; the first CRLF is the lowest index i where
|
* character; the first CRLF is the lowest index i where
|
||||||
* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
|
* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
|
||||||
* if such an |i| exists, and -1 otherwise
|
* if such an |i| exists, and -1 otherwise
|
||||||
|
* @param start : uint
|
||||||
|
* start index from which to begin searching in array
|
||||||
* @returns int
|
* @returns int
|
||||||
* the index of the first CRLF if any were present, -1 otherwise
|
* 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)
|
if (array[i + 1] == LF)
|
||||||
return i;
|
return i;
|
||||||
|
|
@ -1833,6 +1952,9 @@ function LineData()
|
||||||
{
|
{
|
||||||
/** An array of queued bytes from which to get line-based characters. */
|
/** An array of queued bytes from which to get line-based characters. */
|
||||||
this._data = [];
|
this._data = [];
|
||||||
|
|
||||||
|
/** Start index from which to search for CRLF. */
|
||||||
|
this._start = 0;
|
||||||
}
|
}
|
||||||
LineData.prototype =
|
LineData.prototype =
|
||||||
{
|
{
|
||||||
|
|
@ -1842,7 +1964,22 @@ LineData.prototype =
|
||||||
*/
|
*/
|
||||||
appendBytes: function(bytes)
|
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)
|
readLine: function(out)
|
||||||
{
|
{
|
||||||
var data = this._data;
|
var data = this._data;
|
||||||
var length = findCRLF(data);
|
var length = findCRLF(data, this._start);
|
||||||
if (length < 0)
|
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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset for future lines.
|
||||||
|
this._start = 0;
|
||||||
|
|
||||||
//
|
//
|
||||||
// We have the index of the CR, so remove all the characters, including
|
// 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
|
// CRLF, from the array with splice, and convert the removed array
|
||||||
// corresponding string, from which we then strip the trailing CRLF.
|
// (excluding the trailing CRLF characters) into the corresponding string.
|
||||||
//
|
//
|
||||||
// Getting the line in this matter acknowledges that substring is an O(1)
|
var leading = data.splice(0, length + 2);
|
||||||
// operation in SpiderMonkey because strings are immutable, whereas two
|
var quantum = 262144;
|
||||||
// splices, both from the beginning of the data, are less likely to be as
|
var line = "";
|
||||||
// cheap as a single splice plus two extra character conversions.
|
for (var start = 0; start < length; start += quantum)
|
||||||
//
|
{
|
||||||
var line = String.fromCharCode.apply(null, data.splice(0, length + 2));
|
var slice = leading.slice(start, Math.min(start + quantum, length));
|
||||||
out.value = line.substring(0, length);
|
line += String.fromCharCode.apply(null, slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.value = line;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1911,7 +2063,7 @@ function createHandlerFunc(handler)
|
||||||
*/
|
*/
|
||||||
function defaultIndexHandler(metadata, response)
|
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));
|
var path = htmlEscape(decodeURI(metadata.path));
|
||||||
|
|
||||||
|
|
@ -2018,6 +2170,7 @@ function toInternalPath(path, encoded)
|
||||||
return comps.join("/");
|
return comps.join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds custom-specified headers for the given file to the given response, if
|
* Adds custom-specified headers for the given file to the given response, if
|
||||||
|
|
@ -2045,7 +2198,7 @@ function maybeAddHeaders(file, metadata, response)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const PR_RDONLY = 0x01;
|
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);
|
Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -2149,6 +2302,15 @@ function ServerHandler(server)
|
||||||
*/
|
*/
|
||||||
this._overridePaths = {};
|
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
|
* 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
|
* resides. Path-handler pairs are stored as property-value pairs in this
|
||||||
|
|
@ -2213,7 +2375,23 @@ ServerHandler.prototype =
|
||||||
}
|
}
|
||||||
else
|
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)
|
catch (e)
|
||||||
|
|
@ -2318,6 +2496,18 @@ ServerHandler.prototype =
|
||||||
this._handlerToField(handler, this._overridePaths, path);
|
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
|
// see nsIHttpServer.registerDirectory
|
||||||
//
|
//
|
||||||
|
|
@ -2458,7 +2648,10 @@ ServerHandler.prototype =
|
||||||
{
|
{
|
||||||
var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
|
var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
|
||||||
if (!rangeMatch)
|
if (!rangeMatch)
|
||||||
|
{
|
||||||
|
dumpn("*** Range header bogosity: '" + metadata.getHeader("Range") + "'");
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
|
}
|
||||||
|
|
||||||
if (rangeMatch[1] !== undefined)
|
if (rangeMatch[1] !== undefined)
|
||||||
start = parseInt(rangeMatch[1], 10);
|
start = parseInt(rangeMatch[1], 10);
|
||||||
|
|
@ -2467,7 +2660,10 @@ ServerHandler.prototype =
|
||||||
end = parseInt(rangeMatch[2], 10);
|
end = parseInt(rangeMatch[2], 10);
|
||||||
|
|
||||||
if (start === undefined && end === undefined)
|
if (start === undefined && end === undefined)
|
||||||
|
{
|
||||||
|
dumpn("*** More Range header bogosity: '" + metadata.getHeader("Range") + "'");
|
||||||
throw HTTP_400;
|
throw HTTP_400;
|
||||||
|
}
|
||||||
|
|
||||||
// No start given, so the end is really the count of bytes from the
|
// No start given, so the end is really the count of bytes from the
|
||||||
// end of the file.
|
// end of the file.
|
||||||
|
|
@ -2537,7 +2733,7 @@ ServerHandler.prototype =
|
||||||
var type = this._getTypeFromFile(file);
|
var type = this._getTypeFromFile(file);
|
||||||
if (type === SJS_TYPE)
|
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);
|
Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -2574,6 +2770,10 @@ ServerHandler.prototype =
|
||||||
{
|
{
|
||||||
self._setObjectState(k, v);
|
self._setObjectState(k, v);
|
||||||
});
|
});
|
||||||
|
s.importFunction(function registerPathHandler(p, h)
|
||||||
|
{
|
||||||
|
self.registerPathHandler(p, h);
|
||||||
|
});
|
||||||
|
|
||||||
// Make it possible for sjs files to access their location
|
// Make it possible for sjs files to access their location
|
||||||
this._setState(path, "__LOCATION__", file.path);
|
this._setState(path, "__LOCATION__", file.path);
|
||||||
|
|
@ -2586,7 +2786,7 @@ ServerHandler.prototype =
|
||||||
// getting the line number where we evaluate the SJS file. Don't
|
// getting the line number where we evaluate the SJS file. Don't
|
||||||
// separate these two lines!
|
// separate these two lines!
|
||||||
var line = new Error().lineNumber;
|
var line = new Error().lineNumber;
|
||||||
Cu.evalInSandbox(sis.read(file.fileSize), s);
|
Cu.evalInSandbox(sis.read(file.fileSize), s, "latest");
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
|
|
@ -2627,7 +2827,7 @@ ServerHandler.prototype =
|
||||||
maybeAddHeaders(file, metadata, response);
|
maybeAddHeaders(file, metadata, response);
|
||||||
response.setHeader("Content-Length", "" + count, false);
|
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);
|
Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
||||||
|
|
||||||
offset = offset || 0;
|
offset = offset || 0;
|
||||||
|
|
@ -2878,6 +3078,7 @@ ServerHandler.prototype =
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
|
dumpn("*** toInternalPath threw " + e);
|
||||||
throw HTTP_400; // malformed path
|
throw HTTP_400; // malformed path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3072,7 +3273,7 @@ ServerHandler.prototype =
|
||||||
{
|
{
|
||||||
// none of the data in metadata is reliable, so hard-code everything here
|
// none of the data in metadata is reliable, so hard-code everything here
|
||||||
response.setStatusLine("1.1", 400, "Bad Request");
|
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";
|
var body = "Bad request\n";
|
||||||
response.bodyOutputStream.write(body, body.length);
|
response.bodyOutputStream.write(body, body.length);
|
||||||
|
|
@ -3080,7 +3281,7 @@ ServerHandler.prototype =
|
||||||
403: function(metadata, response)
|
403: function(metadata, response)
|
||||||
{
|
{
|
||||||
response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
|
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>\
|
var body = "<html>\
|
||||||
<head><title>403 Forbidden</title></head>\
|
<head><title>403 Forbidden</title></head>\
|
||||||
|
|
@ -3093,7 +3294,7 @@ ServerHandler.prototype =
|
||||||
404: function(metadata, response)
|
404: function(metadata, response)
|
||||||
{
|
{
|
||||||
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
|
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>\
|
var body = "<html>\
|
||||||
<head><title>404 Not Found</title></head>\
|
<head><title>404 Not Found</title></head>\
|
||||||
|
|
@ -3113,7 +3314,7 @@ ServerHandler.prototype =
|
||||||
response.setStatusLine(metadata.httpVersion,
|
response.setStatusLine(metadata.httpVersion,
|
||||||
416,
|
416,
|
||||||
"Requested Range Not Satisfiable");
|
"Requested Range Not Satisfiable");
|
||||||
response.setHeader("Content-Type", "text/html", false);
|
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||||
|
|
||||||
var body = "<html>\
|
var body = "<html>\
|
||||||
<head>\
|
<head>\
|
||||||
|
|
@ -3132,7 +3333,7 @@ ServerHandler.prototype =
|
||||||
response.setStatusLine(metadata.httpVersion,
|
response.setStatusLine(metadata.httpVersion,
|
||||||
500,
|
500,
|
||||||
"Internal Server Error");
|
"Internal Server Error");
|
||||||
response.setHeader("Content-Type", "text/html", false);
|
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||||
|
|
||||||
var body = "<html>\
|
var body = "<html>\
|
||||||
<head><title>500 Internal Server Error</title></head>\
|
<head><title>500 Internal Server Error</title></head>\
|
||||||
|
|
@ -3147,7 +3348,7 @@ ServerHandler.prototype =
|
||||||
501: function(metadata, response)
|
501: function(metadata, response)
|
||||||
{
|
{
|
||||||
response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
|
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>\
|
var body = "<html>\
|
||||||
<head><title>501 Not Implemented</title></head>\
|
<head><title>501 Not Implemented</title></head>\
|
||||||
|
|
@ -3161,7 +3362,7 @@ ServerHandler.prototype =
|
||||||
505: function(metadata, response)
|
505: function(metadata, response)
|
||||||
{
|
{
|
||||||
response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
|
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>\
|
var body = "<html>\
|
||||||
<head><title>505 HTTP Version Not Supported</title></head>\
|
<head><title>505 HTTP Version Not Supported</title></head>\
|
||||||
|
|
@ -3183,7 +3384,7 @@ ServerHandler.prototype =
|
||||||
"/": function(metadata, response)
|
"/": function(metadata, response)
|
||||||
{
|
{
|
||||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
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>\
|
var body = "<html>\
|
||||||
<head><title>httpd.js</title></head>\
|
<head><title>httpd.js</title></head>\
|
||||||
|
|
@ -3201,7 +3402,7 @@ ServerHandler.prototype =
|
||||||
"/trace": function(metadata, response)
|
"/trace": function(metadata, response)
|
||||||
{
|
{
|
||||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
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: " +
|
var body = "Request-URI: " +
|
||||||
metadata.scheme + "://" + metadata.host + ":" + metadata.port +
|
metadata.scheme + "://" + metadata.host + ":" + metadata.port +
|
||||||
|
|
@ -4569,7 +4770,10 @@ const headerUtils =
|
||||||
normalizeFieldName: function(fieldName)
|
normalizeFieldName: function(fieldName)
|
||||||
{
|
{
|
||||||
if (fieldName == "")
|
if (fieldName == "")
|
||||||
|
{
|
||||||
|
dumpn("*** Empty fieldName");
|
||||||
throw Cr.NS_ERROR_INVALID_ARG;
|
throw Cr.NS_ERROR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0, sz = fieldName.length; i < sz; i++)
|
for (var i = 0, sz = fieldName.length; i < sz; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -4620,9 +4824,13 @@ const headerUtils =
|
||||||
val = val.replace(/^ +/, "").replace(/ +$/, "");
|
val = val.replace(/^ +/, "").replace(/ +$/, "");
|
||||||
|
|
||||||
// that should have taken care of all CTLs, so val should contain no CTLs
|
// 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++)
|
for (var i = 0, len = val.length; i < len; i++)
|
||||||
if (isCTL(val.charCodeAt(i)))
|
if (isCTL(val.charCodeAt(i)))
|
||||||
|
{
|
||||||
|
dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
|
||||||
throw Cr.NS_ERROR_INVALID_ARG;
|
throw Cr.NS_ERROR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
// XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
|
// XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
|
||||||
// normalize, however, so this can be construed as a tightening of the
|
// normalize, however, so this can be construed as a tightening of the
|
||||||
|
|
@ -5086,10 +5294,8 @@ Request.prototype =
|
||||||
|
|
||||||
|
|
||||||
// XPCOM trappings
|
// XPCOM trappings
|
||||||
if (XPCOMUtils.generateNSGetFactory)
|
|
||||||
var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
|
||||||
else
|
|
||||||
var NSGetModule = XPCOMUtils.generateNSGetModule([nsHttpServer]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new HTTP server listening for loopback traffic on the given port,
|
* Creates a new HTTP server listening for loopback traffic on the given port,
|
||||||
|
|
@ -5147,20 +5353,3 @@ function server(port, basePath)
|
||||||
|
|
||||||
DEBUG = false;
|
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
|
/* 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
|
* 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'];
|
var EXPORTED_SYMBOLS = ['getLength', ];//'compare'];
|
||||||
|
|
||||||
|
|
@ -9,6 +9,7 @@ var getLength = function (obj) {
|
||||||
for (i in obj) {
|
for (i in obj) {
|
||||||
len++;
|
len++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,46 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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'];
|
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)
|
// file is the given directory (nsIFile)
|
||||||
var entries = file.directoryEntries;
|
var entries = file.directoryEntries;
|
||||||
var array = [];
|
var array = [];
|
||||||
while (entries.hasMoreElements())
|
|
||||||
{
|
while (entries.hasMoreElements()) {
|
||||||
var entry = entries.getNext();
|
var entry = entries.getNext();
|
||||||
entry.QueryInterface(Components.interfaces.nsIFile);
|
entry.QueryInterface(Ci.nsIFile);
|
||||||
array.push(entry);
|
array.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileForPath (path) {
|
function getFileForPath(path) {
|
||||||
var file = Components.classes["@mozilla.org/file/local;1"]
|
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||||
.createInstance(Components.interfaces.nsILocalFile);
|
|
||||||
file.initWithPath(path);
|
file.initWithPath(path);
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
function abspath (rel, file) {
|
function abspath(rel, file) {
|
||||||
var relSplit = rel.split('/');
|
var relSplit = rel.split('/');
|
||||||
|
|
||||||
if (relSplit[0] == '..' && !file.isDirectory()) {
|
if (relSplit[0] == '..' && !file.isDirectory()) {
|
||||||
file = file.parent;
|
file = file.parent;
|
||||||
}
|
}
|
||||||
for each(p in relSplit) {
|
|
||||||
|
for each(var p in relSplit) {
|
||||||
if (p == '..') {
|
if (p == '..') {
|
||||||
file = file.parent;
|
file = file.parent;
|
||||||
} else if (p == '.'){
|
} else if (p == '.') {
|
||||||
if (!file.isDirectory()) {
|
if (!file.isDirectory()) {
|
||||||
file = file.parent;
|
file = file.parent;
|
||||||
}
|
}
|
||||||
|
|
@ -40,14 +48,10 @@ function abspath (rel, file) {
|
||||||
file.append(p);
|
file.append(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return file.path;
|
return file.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlatform () {
|
function getPlatform() {
|
||||||
var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
|
return Services.appinfo.OS.toLowerCase();
|
||||||
.getService(Components.interfaces.nsIXULRuntime);
|
|
||||||
mPlatform = xulRuntime.OS.toLowerCase();
|
|
||||||
return mPlatform;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,38 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* ***** BEGIN LICENSE BLOCK *****
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
*
|
||||||
|
* 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) {
|
(function(global) {
|
||||||
const Cc = Components.classes;
|
const Cc = Components.classes;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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'];
|
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.
|
* 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;
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||||
.getService(Ci.nsIPrefBranch);
|
.getService(Ci.nsIPrefBranch);
|
||||||
|
|
||||||
var mozmillInit = {};
|
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
|
// Options for wiping data during a sync
|
||||||
const SYNC_RESET_CLIENT = "resetClient";
|
const SYNC_RESET_CLIENT = "resetClient";
|
||||||
|
|
@ -795,7 +795,7 @@ let TPS = {
|
||||||
frame.events.addListener('setTest', this.MozmillSetTestListener.bind(this));
|
frame.events.addListener('setTest', this.MozmillSetTestListener.bind(this));
|
||||||
frame.events.addListener('endTest', this.MozmillEndTestListener.bind(this));
|
frame.events.addListener('endTest', this.MozmillEndTestListener.bind(this));
|
||||||
this.StartAsyncOperation();
|
this.StartAsyncOperation();
|
||||||
frame.runTestFile(mozmillfile.path, false);
|
frame.runTestFile(mozmillfile.path, null);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue