Bug 982610 - Update TPS to use latest Mozmill 2.0.6. r=hskupin DONTBUILD

--HG--
extra : rebase_source : 6e967421250dd6093c0fcc89dcbd078c0812fcb6
This commit is contained in:
Cosmin Malutan 2014-05-22 18:25:50 +02:00
parent dfc8cc59ba
commit e2b5275fc3
34 changed files with 5706 additions and 4391 deletions

View File

@ -2,10 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import('resource://tps/mozmill/sync.jsm');
Components.utils.import('resource://tps/tps.jsm');
var setupModule = function(module) {
controller = mozmill.getBrowserController();
module.controller = mozmill.getBrowserController();
assert.ok(true, "SetupModule passes");
}
@ -16,8 +16,9 @@ var setupTest = function(module) {
var testTestStep = function() {
assert.ok(true, "test Passes");
controller.open("http://www.mozilla.org");
TPS.SetupSyncAccount();
assert.equal(TPS.Sync(SYNC_WIPE_SERVER), 0, "sync succeeded");
TPS.Login();
TPS.Sync(ACTIONS.ACTION_SYNC_WIPE_CLIENT);
}
var teardownTest = function () {

View File

@ -1,7 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var jum = {}; Components.utils.import('resource://mozmill/modules/jum.js', jum);
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var setupModule = function(module) {
module.controller = mozmill.getBrowserController();
@ -11,43 +10,6 @@ var testGetNode = function() {
controller.open("about:support");
controller.waitForPageLoad();
var appbox = new elementslib.ID(controller.tabs.activeTab, "application-box");
jum.assert(appbox.getNode().innerHTML == 'Firefox', 'correct app name');
};
const NAV_BAR = '/id("main-window")/id("tab-view-deck")/{"flex":"1"}' +
'/id("navigator-toolbox")/id("nav-bar")';
const SEARCH_BAR = NAV_BAR + '/id("search-container")/id("searchbar")';
const SEARCH_TEXTBOX = SEARCH_BAR + '/anon({"anonid":"searchbar-textbox"})';
const SEARCH_DROPDOWN = SEARCH_TEXTBOX + '/[0]/anon({"anonid":"searchbar-engine-button"})';
const SEARCH_POPUP = SEARCH_DROPDOWN + '/anon({"anonid":"searchbar-popup"})';
const SEARCH_INPUT = SEARCH_TEXTBOX + '/anon({"class":"autocomplete-textbox-container"})' +
'/anon({"anonid":"textbox-input-box"})' +
'/anon({"anonid":"input"})';
const SEARCH_CONTEXT = SEARCH_TEXTBOX + '/anon({"anonid":"textbox-input-box"})' +
'/anon({"anonid":"input-box-contextmenu"})';
const SEARCH_GO_BUTTON = SEARCH_TEXTBOX + '/anon({"class":"search-go-container"})' +
'/anon({"class":"search-go-button"})';
const SEARCH_AUTOCOMPLETE = '/id("main-window")/id("mainPopupSet")/id("PopupAutoComplete")';
var testLookupExpressions = function() {
var item;
item = new elementslib.Lookup(controller.window.document, NAV_BAR);
controller.click(item);
item = new elementslib.Lookup(controller.window.document, SEARCH_BAR);
controller.click(item);
item = new elementslib.Lookup(controller.window.document, SEARCH_TEXTBOX);
controller.click(item);
item = new elementslib.Lookup(controller.window.document, SEARCH_DROPDOWN);
controller.click(item);
item = new elementslib.Lookup(controller.window.document, SEARCH_POPUP);
controller.click(item);
item = new elementslib.Lookup(controller.window.document, SEARCH_INPUT);
controller.click(item);
item = new elementslib.Lookup(controller.window.document, SEARCH_CONTEXT);
controller.click(item);
item = new elementslib.Lookup(controller.window.document, SEARCH_GO_BUTTON);
controller.click(item);
item = new elementslib.Lookup(controller.window.document, SEARCH_AUTOCOMPLETE);
controller.click(item);
var appbox = findElement.ID(controller.tabs.activeTab, "application-box");
assert.waitFor(() => appbox.getNode().textContent == 'Firefox', 'correct app name');
};

0
services/sync/tps/extensions/mozmill/chrome.manifest Normal file → Executable file
View File

View 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
View File

@ -5,59 +5,24 @@
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>mozmill@mozilla.com</em:id>
<em:name>MozMill</em:name>
<em:version>2.0b1</em:version>
<em:creator>Adam Christian</em:creator>
<em:description>A testing extension based on the Windmill Testing Framework client source</em:description>
<em:name>Mozmill</em:name>
<em:version>2.0.6</em:version>
<em:description>UI Automation tool for Mozilla applications</em:description>
<em:unpack>true</em:unpack>
<em:creator>Mozilla Automation and Testing Team</em:creator>
<em:contributor>Adam Christian</em:contributor>
<em:contributor>Mikeal Rogers</em:contributor>
<em:targetApplication>
<!-- Firefox -->
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>3.5</em:minVersion>
<em:maxVersion>12.*</em:maxVersion>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>10.0</em:minVersion>
<em:maxVersion>31.*</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<!-- Thunderbird -->
<Description>
<em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
<em:minVersion>3.0a1pre</em:minVersion>
<em:maxVersion>9.*</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<!-- Sunbird -->
<Description>
<em:id>{718e30fb-e89b-41dd-9da7-e25a45638b28}</em:id>
<em:minVersion>0.6a1</em:minVersion>
<em:maxVersion>1.0pre</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<!-- SeaMonkey -->
<Description>
<em:id>{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}</em:id>
<em:minVersion>2.0a1</em:minVersion>
<em:maxVersion>9.*</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<!-- Songbird -->
<Description>
<em:id>songbird@songbirdnest.com</em:id>
<em:minVersion>0.3pre</em:minVersion>
<em:maxVersion>1.3.0a</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>1.9.1</em:minVersion>
<em:maxVersion>9.*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,30 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ["Elem", "ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
"Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
];
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
var objects = {}; Components.utils.import('resource://mozmill/stdlib/objects.js', objects);
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
var countQuotes = function(str){
Cu.import("resource://gre/modules/Services.jsm");
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2);
var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom);
var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects);
var countQuotes = function (str) {
var count = 0;
var i = 0;
while(i < str.length) {
while (i < str.length) {
i = str.indexOf('"', i);
if (i != -1) {
count++;
@ -26,6 +33,7 @@ var countQuotes = function(str){
break;
}
}
return count;
};
@ -53,10 +61,12 @@ var smartSplit = function (str) {
var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
var ret = []
var match = re.exec(str);
while (match != null) {
ret.push(match[0].replace(/^\//, ""));
match = re.exec(str);
}
return ret;
};
@ -67,9 +77,12 @@ var smartSplit = function (str) {
* if no document is provided
*/
function defaultDocuments() {
var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);
win = windowManager.getMostRecentWindow("navigator:browser");
return [win.gBrowser.selectedBrowser.contentDocument, win.document];
var win = Services.wm.getMostRecentWindow("navigator:browser");
return [
win.document,
utils.getBrowserObject(win).selectedBrowser.contentWindow.document
];
};
/**
@ -84,30 +97,37 @@ function nodeSearch(doc, func, string) {
} else {
var documents = defaultDocuments();
}
var e = null;
var element = null;
//inline function to recursively find the element in the DOM, cross frame.
var search = function(win, func, string) {
if (win == null)
var search = function (win, func, string) {
if (win == null) {
return;
}
//do the lookup in the current window
element = func.call(win, string);
if (!element || (element.length == 0)) {
var frames = win.frames;
for (var i=0; i < frames.length; i++) {
for (var i = 0; i < frames.length; i++) {
search(frames[i], func, string);
}
} else {
e = element;
}
else { e = element; }
};
for (var i = 0; i < documents.length; ++i) {
var win = documents[i].defaultView;
search(win, func, string);
if (e) break;
if (e) {
break;
}
}
return e;
};
@ -120,11 +140,15 @@ function Selector(_document, selector, index) {
if (selector == undefined) {
throw new Error('Selector constructor did not recieve enough arguments.');
}
this.selector = selector;
this.getNodeForDocument = function (s) {
return this.document.querySelectorAll(s);
};
var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
return nodes ? nodes[index || 0] : null;
};
@ -137,9 +161,11 @@ function ID(_document, nodeID) {
if (nodeID == undefined) {
throw new Error('ID constructor did not recieve enough arguments.');
}
this.getNodeForDocument = function (nodeID) {
return this.document.getElementById(nodeID);
};
return nodeSearch(_document, this.getNodeForDocument, nodeID);
};
@ -154,40 +180,48 @@ function Link(_document, linkName) {
}
this.getNodeForDocument = function (linkName) {
var getText = function(el){
var getText = function (el) {
var text = "";
if (el.nodeType == 3){ //textNode
if (el.data != undefined){
if (el.nodeType == 3) { //textNode
if (el.data != undefined) {
text = el.data;
} else {
text = el.innerHTML;
}
text = text.replace(/n|r|t/g, " ");
text = text.replace(/n|r|t/g, " ");
}
if (el.nodeType == 1){ //elementNode
else if (el.nodeType == 1) { //elementNode
for (var i = 0; i < el.childNodes.length; i++) {
var child = el.childNodes.item(i);
text += getText(child);
}
if (el.tagName == "P" || el.tagName == "BR" || el.tagName == "HR" || el.tagName == "DIV") {
text += "n";
if (el.tagName == "P" || el.tagName == "BR" ||
el.tagName == "HR" || el.tagName == "DIV") {
text += "\n";
}
}
return text;
};
//sometimes the windows won't have this function
try {
var links = this.document.getElementsByTagName('a'); }
catch(err){ // ADD LOG LINE mresults.write('Error: '+ err, 'lightred');
var links = this.document.getElementsByTagName('a');
} catch (e) {
// ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
}
for (var i = 0; i < links.length; i++) {
var el = links[i];
//if (getText(el).indexOf(this.linkName) != -1) {
if (el.innerHTML.indexOf(linkName) != -1){
if (el.innerHTML.indexOf(linkName) != -1) {
return el;
}
}
return null;
};
@ -214,14 +248,20 @@ function XPath(_document, expr) {
} else {
xpe = new this.document.defaultView.XPathEvaluator();
}
var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement : aNode.ownerDocument.documentElement);
var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement
: aNode.ownerDocument.documentElement);
var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
var found = [];
var res;
while (res = result.iterateNext())
while (res = result.iterateNext()) {
found.push(res);
}
return found[0];
};
return nodeSearch(_document, this.getNodeForDocument, expr);
};
@ -234,14 +274,19 @@ function Name(_document, nName) {
if (nName == undefined) {
throw new Error('Name constructor did not recieve enough arguments.');
}
this.getNodeForDocument = function (s) {
try{
var els = this.document.getElementsByName(s);
if (els.length > 0) { return els[0]; }
if (els.length > 0) {
return els[0];
}
} catch (e) {
}
catch(err){};
return null;
};
return nodeSearch(_document, this.getNodeForDocument, nName);
};
@ -249,110 +294,138 @@ function Name(_document, nName) {
var _returnResult = function (results) {
if (results.length == 0) {
return null
} else if (results.length == 1) {
}
else if (results.length == 1) {
return results[0];
} else {
return results;
}
}
var _forChildren = function (element, name, value) {
var results = [];
var nodes = [e for each (e in element.childNodes) if (e)]
for (var i in nodes) {
var n = nodes[i];
if (n[name] == value) {
results.push(n);
}
}
return results;
}
var _forAnonChildren = function (_document, element, name, value) {
var results = [];
var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
for (var i in nodes ) {
var n = nodes[i];
if (n[name] == value) {
results.push(n);
}
}
return results;
}
var _byID = function (_document, parent, value) {
return _returnResult(_forChildren(parent, 'id', value));
}
var _byName = function (_document, parent, value) {
return _returnResult(_forChildren(parent, 'tagName', value));
}
var _byAttrib = function (parent, attributes) {
var results = [];
var nodes = parent.childNodes;
for (var i in nodes) {
var n = nodes[i];
requirementPass = 0;
requirementLength = 0;
for (var a in attributes) {
requirementLength++;
try {
if (n.getAttribute(a) == attributes[a]) {
requirementPass++;
}
} catch (err) {
} catch (e) {
// Workaround any bugs in custom attribute crap in XUL elements
}
}
if (requirementPass == requirementLength) {
results.push(n);
}
}
return _returnResult(results)
}
var _byAnonAttrib = function (_document, parent, attributes) {
var results = [];
if (objects.getLength(attributes) == 1) {
for (var i in attributes) {var k = i; var v = attributes[i]; }
var result = _document.getAnonymousElementByAttribute(parent, k, v)
for (var i in attributes) {
var k = i;
var v = attributes[i];
}
var result = _document.getAnonymousElementByAttribute(parent, k, v);
if (result) {
return result;
}
}
var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
function resultsForNodes (nodes) {
for (var i in nodes) {
var n = nodes[i];
requirementPass = 0;
requirementLength = 0;
for (var a in attributes) {
requirementLength++;
if (n.getAttribute(a) == attributes[a]) {
requirementPass++;
}
}
if (requirementPass == requirementLength) {
results.push(n);
}
}
}
resultsForNodes(nodes)
resultsForNodes(nodes);
if (results.length == 0) {
resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
}
return _returnResult(results)
}
var _byIndex = function (_document, parent, i) {
if (parent instanceof Array) {
return parent[i];
}
return parent.childNodes[i];
}
var _anonByName = function (_document, parent, value) {
return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
}
var _anonByAttrib = function (_document, parent, value) {
return _byAnonAttrib(_document, parent, value);
}
var _anonByIndex = function (_document, parent, i) {
return _document.getAnonymousNodes(parent)[i];
}
@ -362,18 +435,32 @@ var _anonByIndex = function (_document, parent, i) {
*
* Finds an element by Lookup expression
*/
function Lookup (_document, expression) {
function Lookup(_document, expression) {
if (expression == undefined) {
throw new Error('Lookup constructor did not recieve enough arguments.');
}
var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
expSplit.unshift(_document)
expSplit.unshift(_document);
var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
/**
* Reduces the lookup expression
* @param {Object} parentNode
* Parent node (previousValue of the formerly executed reduce callback)
* @param {String} exp
* Lookup expression for the parents child node
*
* @returns {Object} Node found by the given expression
*/
var reduceLookup = function (parentNode, exp) {
// Abort in case the parent node was not found
if (!parentNode) {
return false;
}
var reduceLookup = function (parent, exp) {
// Handle case where only index is provided
var cases = nCases;
@ -381,21 +468,27 @@ function Lookup (_document, expression) {
if (withs.endsWith(exp, ']')) {
var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
}
// Handle anon
if (withs.startsWith(exp, 'anon')) {
var exp = strings.vslice(exp, '(', ')');
var cases = aCases;
exp = strings.vslice(exp, '(', ')');
cases = aCases;
}
if (withs.startsWith(exp, '[')) {
try {
var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
} catch (err) {
throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '[', ']')+' ||');
} catch (e) {
throw new SyntaxError(e + '. String to be parsed was || ' +
strings.vslice(exp, '[', ']') + ' ||');
}
var r = cases['index'](_document, parent, obj);
var r = cases['index'](_document, parentNode, obj);
if (r == null) {
throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
throw new SyntaxError('Expression "' + exp +
'" returned null. Anonymous == ' + (cases == aCases));
}
return r;
}
@ -403,30 +496,28 @@ function Lookup (_document, expression) {
if (withs.startsWith(exp, c)) {
try {
var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
} catch(err) {
throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '(', ')')+' ||');
} catch (e) {
throw new SyntaxError(e + '. String to be parsed was || ' +
strings.vslice(exp, '(', ')') + ' ||');
}
var result = cases[c](_document, parent, obj);
var result = cases[c](_document, parentNode, obj);
}
}
if (!result) {
if ( withs.startsWith(exp, '{') ) {
if (withs.startsWith(exp, '{')) {
try {
var obj = json2.JSON.parse(exp)
} catch(err) {
throw new Error(err+'. String to be parsed was || '+exp+' ||');
var obj = json2.JSON.parse(exp);
} catch (e) {
throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
}
if (cases == aCases) {
var result = _anonByAttrib(_document, parent, obj)
var result = _anonByAttrib(_document, parentNode, obj);
} else {
var result = _byAttrib(parent, obj)
var result = _byAttrib(parentNode, obj);
}
}
if (!result) {
throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
}
}
// Final return
@ -437,8 +528,10 @@ function Lookup (_document, expression) {
// TODO: Check length and raise error
return result;
}
// Maybe we should cause an exception here
return false;
};
return expSplit.reduce(reduceLookup);
};

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
"getBrowserController", "newBrowserController",
@ -8,119 +8,153 @@ var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
"newMail3PaneController", "getMail3PaneController",
"wm", "platform", "getAddrbkController",
"getMsgComposeController", "getDownloadsController",
"Application", "cleanQuit",
"Application", "findElement",
"getPlacesController", 'isMac', 'isLinux', 'isWindows',
"firePythonCallback"
"firePythonCallback", "getAddons"
];
// imports
var controller = {}; Components.utils.import('resource://mozmill/modules/controller.js', controller);
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
var os = {}; Components.utils.import('resource://mozmill/stdlib/os.js', os);
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
try {
Components.utils.import("resource://gre/modules/AddonManager.jsm");
} catch(e) { /* Firefox 4 only */ }
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// imports
var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
var controller = {}; Cu.import('resource://mozmill/driver/controller.js', controller);
var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
var findElement = {}; Cu.import('resource://mozmill/driver/mozelement.js', findElement);
var os = {}; Cu.import('resource://mozmill/stdlib/os.js', os);
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
var windows = {}; Cu.import('resource://mozmill/modules/windows.js', windows);
const DEBUG = false;
// This is a useful "check" timer. See utils.js, good for debugging
if (DEBUG) {
utils.startTimer();
}
var assert = new assertions.Assert();
// platform information
var platform = os.getPlatform();
var isMac = false;
var isWindows = false;
var isLinux = false;
if (platform == "darwin"){
isMac = true;
}
if (platform == "winnt"){
isWindows = true;
}
if (platform == "linux"){
isLinux = true;
}
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var wm = Services.wm;
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULAppInfo);
var locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
.getService(Components.interfaces.nsIXULChromeRegistry)
.getSelectedLocale("global");
var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
getService(Components.interfaces.nsIConsoleService);
var appInfo = Services.appinfo;
var Application = utils.applicationName;
applicationDictionary = {
"{718e30fb-e89b-41dd-9da7-e25a45638b28}": "Sunbird",
"{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "SeaMonkey",
"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "Firefox",
"{3550f703-e582-4d05-9a08-453d09bdfdc6}": 'Thunderbird',
/**
* Retrieves the list with information about installed add-ons.
*
* @returns {String} JSON data of installed add-ons
*/
function getAddons() {
var addons = null;
AddonManager.getAllAddons(function (addonList) {
var tmp_list = [ ];
addonList.forEach(function (addon) {
var tmp = { };
// We have to filter out properties of type 'function' of the addon
// object, which will break JSON.stringify() and result in incomplete
// addon information.
for (var key in addon) {
if (typeof(addon[key]) !== "function") {
tmp[key] = addon[key];
}
}
tmp_list.push(tmp);
});
addons = tmp_list;
});
try {
// Sychronize with getAllAddons so we do not return too early
assert.waitFor(function () {
return !!addons;
})
return addons;
} catch (e) {
return null;
}
}
var Application = applicationDictionary[appInfo.ID];
/**
* Retrieves application details for the Mozmill report
*
* @return {String} JSON data of application details
*/
function getApplicationDetails() {
var locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry)
.getSelectedLocale("global");
if (Application == undefined) {
// Default to Firefox
var Application = 'Firefox';
// Put all our necessary information into JSON and return it:
// appinfo, startupinfo, and addons
var details = {
application_id: appInfo.ID,
application_name: Application,
application_version: appInfo.version,
application_locale: locale,
platform_buildid: appInfo.platformBuildID,
platform_version: appInfo.platformVersion,
addons: getAddons(),
startupinfo: getStartupInfo(),
paths: {
appdata: Services.dirsvc.get('UAppData', Ci.nsIFile).path,
profile: Services.dirsvc.get('ProfD', Ci.nsIFile).path
}
};
return JSON.stringify(details);
}
// get startup time if available
// see http://blog.mozilla.com/tglek/2011/04/26/measuring-startup-speed-correctly/
var startupInfo = {};
try {
var _startupInfo = Components.classes["@mozilla.org/toolkit/app-startup;1"]
.getService(Components.interfaces.nsIAppStartup).getStartupInfo();
for (var i in _startupInfo) {
startupInfo[i] = _startupInfo[i].getTime(); // convert from Date object to ms since epoch
function getStartupInfo() {
var startupInfo = {};
try {
var _startupInfo = Services.startup.getStartupInfo();
for (var time in _startupInfo) {
// convert from Date object to ms since epoch
startupInfo[time] = _startupInfo[time].getTime();
}
} catch(e) {
} catch (e) {
startupInfo = null;
}
return startupInfo;
}
// keep list of installed addons to send to jsbridge for test run report
var addons = "null"; // this will be JSON parsed
if(typeof AddonManager != "undefined") {
AddonManager.getAllAddons(function(addonList) {
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = 'utf-8';
function replacer(key, value) {
if (typeof(value) == "string") {
try {
return converter.ConvertToUnicode(value);
} catch(e) {
var newstring = '';
for (var i=0; i < value.length; i++) {
replacement = '';
if ((32 <= value.charCodeAt(i)) && (value.charCodeAt(i) < 127)) {
// eliminate non-convertable characters;
newstring += value.charAt(i);
} else {
newstring += replacement;
}
}
return newstring;
}
}
return value;
}
addons = converter.ConvertToUnicode(JSON.stringify(addonList, replacer))
});
}
function cleanQuit () {
utils.getMethodInWindows('goQuitApplication')();
}
function addHttpResource (directory, namespace) {
return 'http://localhost:4545/'+namespace;
}
function newBrowserController () {
return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
@ -128,34 +162,39 @@ function newBrowserController () {
function getBrowserController () {
var browserWindow = wm.getMostRecentWindow("navigator:browser");
if (browserWindow == null) {
return newBrowserController();
}
else {
} else {
return new controller.MozMillController(browserWindow);
}
}
function getPlacesController () {
utils.getMethodInWindows('PlacesCommandHook').showPlacesOrganizer('AllBookmarks');
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
function getAddonsController () {
if (Application == 'SeaMonkey') {
utils.getMethodInWindows('toEM')();
} else if (Application == 'Thunderbird') {
}
else if (Application == 'Thunderbird') {
utils.getMethodInWindows('openAddonsMgr')();
} else if (Application == 'Sunbird') {
}
else if (Application == 'Sunbird') {
utils.getMethodInWindows('goOpenAddons')();
} else {
utils.getMethodInWindows('BrowserOpenAddonsMgr')();
}
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
function getDownloadsController() {
utils.getMethodInWindows('BrowserDownloadsUI')();
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
@ -165,6 +204,7 @@ function getPreferencesController() {
} else {
utils.getMethodInWindows('openPreferences')();
}
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
@ -175,10 +215,10 @@ function newMail3PaneController () {
function getMail3PaneController () {
var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
if (mail3PaneWindow == null) {
return newMail3PaneController();
}
else {
} else {
return new controller.MozMillController(mail3PaneWindow);
}
}
@ -188,6 +228,7 @@ function newAddrbkController () {
utils.getMethodInWindows("toAddressBook")();
utils.sleep(2000);
var addyWin = wm.getMostRecentWindow("mail:addressbook");
return new controller.MozMillController(addyWin);
}
@ -195,35 +236,50 @@ function getAddrbkController () {
var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
if (addrbkWindow == null) {
return newAddrbkController();
}
else {
} else {
return new controller.MozMillController(addrbkWindow);
}
}
function firePythonCallback (filename, method, args, kwargs) {
obj = {'filename': filename, 'method': method};
obj['test'] = frame.events.currentModule.__file__;
obj['args'] = args || [];
obj['kwargs'] = kwargs || {};
frame.events.fireEvent("firePythonCallback", obj);
broker.sendMessage("firePythonCallback", obj);
}
function timer (name) {
this.name = name;
this.timers = {};
frame.timers.push(this);
this.actions = [];
frame.timers.push(this);
}
timer.prototype.start = function (name) {
this.timers[name].startTime = (new Date).getTime();
}
timer.prototype.stop = function (name) {
var t = this.timers[name];
t.endTime = (new Date).getTime();
t.totalTime = (t.endTime - t.startTime);
}
timer.prototype.end = function () {
frame.events.fireEvent("timer", this);
frame.timers.remove(this);
}
// Initialization
/**
* Initialize Mozmill
*/
function initialize() {
windows.init();
}
initialize();

View File

@ -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);
}

View File

@ -1,42 +1,174 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
// Use the frame module of Mozmill to raise non-fatal failures
var mozmillFrame = {};
Cu.import('resource://mozmill/modules/frame.js', mozmillFrame);
var EXPORTED_SYMBOLS = ['Assert', 'Expect'];
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
var stack = {}; Cu.import('resource://mozmill/modules/stack.js', stack);
/**
* @name assertions
* @namespace Defines expect and assert methods to be used for assertions.
*/
var assertions = exports;
/**
* The Assert class implements fatal assertions, and can be used in cases
* when a failing test has to directly abort the current test function. All
* remaining tasks will not be performed.
*
*/
var Assert = function () {}
/* non-fatal assertions */
var Expect = function() {}
Assert.prototype = {
Expect.prototype = {
// The following deepEquals implementation is from Narwhal under this license:
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
//
// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
//
// Originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
_deepEqual: function (actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
} else if (actual instanceof Date && expected instanceof Date) {
return actual.getTime() === expected.getTime();
// 7.3. Other pairs that do not both pass typeof value == 'object',
// equivalence is determined by ==.
} else if (typeof actual != 'object' && typeof expected != 'object') {
return actual == expected;
// 7.4. For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return this._objEquiv(actual, expected);
}
},
_objEquiv: function (a, b) {
if (a == null || a == undefined || b == null || b == undefined)
return false;
// an identical 'prototype' property.
if (a.prototype !== b.prototype) return false;
function isArguments(object) {
return Object.prototype.toString.call(object) == '[object Arguments]';
}
//~~~I've managed to break Object.keys through screwy arguments passing.
// Converting to array solves the problem.
if (isArguments(a)) {
if (!isArguments(b)) {
return false;
}
a = pSlice.call(a);
b = pSlice.call(b);
return _deepEqual(a, b);
}
try {
var ka = Object.keys(a),
kb = Object.keys(b),
key, i;
} catch (e) {//happens when one is a string literal and the other isn't
return false;
}
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length != kb.length)
return false;
//the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] != kb[i])
return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!this._deepEqual(a[key], b[key])) return false;
}
return true;
},
_expectedException : function Assert__expectedException(actual, expected) {
if (!actual || !expected) {
return false;
}
if (expected instanceof RegExp) {
return expected.test(actual);
} else if (actual instanceof expected) {
return true;
} else if (expected.call({}, actual) === true) {
return true;
} else if (actual.name === expected.name) {
return true;
}
return false;
},
/**
* Log a test as failing by adding a fail frame.
* Log a test as failing by throwing an AssertionException.
*
* @param {object} aResult
* Test result details used for reporting.
* <dl>
* <dd>fileName</dd>
* <dt>Name of the file in which the assertion failed.</dt>
* <dd>function</dd>
* <dd>functionName</dd>
* <dt>Function in which the assertion failed.</dt>
* <dd>lineNumber</dd>
* <dt>Line number of the file in which the assertion failed.</dt>
* <dd>message</dd>
* <dt>Message why the assertion failed.</dt>
* </dl>
* @throws {errors.AssertionError}
*
*/
_logFail: function Expect__logFail(aResult) {
mozmillFrame.events.fail({fail: aResult});
_logFail: function Assert__logFail(aResult) {
throw new errors.AssertionError(aResult.message,
aResult.fileName,
aResult.lineNumber,
aResult.functionName,
aResult.name);
},
/**
@ -47,7 +179,7 @@ Expect.prototype = {
* <dl>
* <dd>fileName</dd>
* <dt>Name of the file in which the assertion failed.</dt>
* <dd>function</dd>
* <dd>functionName</dd>
* <dt>Function in which the assertion failed.</dt>
* <dd>lineNumber</dd>
* <dt>Line number of the file in which the assertion failed.</dt>
@ -55,8 +187,8 @@ Expect.prototype = {
* <dt>Message why the assertion failed.</dt>
* </dl>
*/
_logPass: function Expect__logPass(aResult) {
mozmillFrame.events.pass({pass: aResult});
_logPass: function Assert__logPass(aResult) {
broker.pass({pass: aResult});
},
/**
@ -68,9 +200,11 @@ Expect.prototype = {
* Message to show for the test result
* @param {string} aDiagnosis
* Diagnose message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
_test: function Expect__test(aCondition, aMessage, aDiagnosis) {
_test: function Assert__test(aCondition, aMessage, aDiagnosis) {
let diagnosis = aDiagnosis || "";
let message = aMessage || "";
@ -78,19 +212,23 @@ Expect.prototype = {
message = aMessage ? message + " - " + diagnosis : diagnosis;
// Build result data
let frame = Components.stack;
let frame = stack.findCallerFrame(Components.stack);
let result = {
'fileName' : frame.filename.replace(/(.*)-> /, ""),
'function' : frame.name,
'lineNumber' : frame.lineNumber,
'message' : message
'fileName' : frame.filename.replace(/(.*)-> /, ""),
'functionName' : frame.name,
'lineNumber' : frame.lineNumber,
'message' : message
};
// Log test result
if (aCondition)
if (aCondition) {
this._logPass(result);
else
}
else {
result.stack = Components.stack;
this._logFail(result);
}
return aCondition;
},
@ -102,7 +240,7 @@ Expect.prototype = {
* Message to show for the test result.
* @returns {boolean} Always returns true.
*/
pass: function Expect_pass(aMessage) {
pass: function Assert_pass(aMessage) {
return this._test(true, aMessage, undefined);
},
@ -111,9 +249,11 @@ Expect.prototype = {
*
* @param {string} aMessage
* Message to show for the test result.
* @throws {errors.AssertionError}
*
* @returns {boolean} Always returns false.
*/
fail: function Expect_fail(aMessage) {
fail: function Assert_fail(aMessage) {
return this._test(false, aMessage, undefined);
},
@ -124,16 +264,18 @@ Expect.prototype = {
* Value to test.
* @param {string} aMessage
* Message to show for the test result.
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
ok: function Expect_ok(aValue, aMessage) {
ok: function Assert_ok(aValue, aMessage) {
let condition = !!aValue;
let diagnosis = "got '" + aValue + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
/**
* Test if both specified values are identical.
*
* @param {boolean|string|number|object} aValue
@ -142,16 +284,18 @@ Expect.prototype = {
* Value to strictly compare with.
* @param {string} aMessage
* Message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
equal: function Expect_equal(aValue, aExpected, aMessage) {
equal: function Assert_equal(aValue, aExpected, aMessage) {
let condition = (aValue === aExpected);
let diagnosis = "got '" + aValue + "', expected '" + aExpected + "'";
let diagnosis = "'" + aValue + "' should equal '" + aExpected + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
/**
* Test if both specified values are not identical.
*
* @param {boolean|string|number|object} aValue
@ -160,15 +304,81 @@ Expect.prototype = {
* Value to strictly compare with.
* @param {string} aMessage
* Message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
notEqual: function Expect_notEqual(aValue, aExpected, aMessage) {
notEqual: function Assert_notEqual(aValue, aExpected, aMessage) {
let condition = (aValue !== aExpected);
let diagnosis = "got '" + aValue + "', not expected '" + aExpected + "'";
let diagnosis = "'" + aValue + "' should not equal '" + aExpected + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Test if an object equals another object
*
* @param {object} aValue
* The object to test.
* @param {object} aExpected
* The object to strictly compare with.
* @param {string} aMessage
* Message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
deepEqual: function equal(aValue, aExpected, aMessage) {
let condition = this._deepEqual(aValue, aExpected);
try {
var aValueString = JSON.stringify(aValue);
} catch (e) {
var aValueString = String(aValue);
}
try {
var aExpectedString = JSON.stringify(aExpected);
} catch (e) {
var aExpectedString = String(aExpected);
}
let diagnosis = "'" + aValueString + "' should equal '" +
aExpectedString + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Test if an object does not equal another object
*
* @param {object} aValue
* The object to test.
* @param {object} aExpected
* The object to strictly compare with.
* @param {string} aMessage
* Message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
notDeepEqual: function notEqual(aValue, aExpected, aMessage) {
let condition = !this._deepEqual(aValue, aExpected);
try {
var aValueString = JSON.stringify(aValue);
} catch (e) {
var aValueString = String(aValue);
}
try {
var aExpectedString = JSON.stringify(aExpected);
} catch (e) {
var aExpectedString = String(aExpected);
}
let diagnosis = "'" + aValueString + "' should not equal '" +
aExpectedString + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Test if the regular expression matches the string.
*
@ -178,9 +388,11 @@ Expect.prototype = {
* Regular expression to use for testing that a match exists.
* @param {string} aMessage
* Message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
match: function Expect_match(aString, aRegex, aMessage) {
match: function Assert_match(aString, aRegex, aMessage) {
// XXX Bug 634948
// Regex objects are transformed to strings when evaluated in a sandbox
// For now lets re-create the regex from its string representation
@ -190,8 +402,7 @@ Expect.prototype = {
pattern = matches[1];
flags = matches[2];
}
catch (ex) {
} catch (e) {
}
let regex = new RegExp(pattern, flags);
@ -210,9 +421,11 @@ Expect.prototype = {
* Regular expression to use for testing that a match does not exist.
* @param {string} aMessage
* Message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
notMatch: function Expect_notMatch(aString, aRegex, aMessage) {
notMatch: function Assert_notMatch(aString, aRegex, aMessage) {
// XXX Bug 634948
// Regex objects are transformed to strings when evaluated in a sandbox
// For now lets re-create the regex from its string representation
@ -222,8 +435,7 @@ Expect.prototype = {
pattern = matches[1];
flags = matches[2];
}
catch (ex) {
} catch (e) {
}
let regex = new RegExp(pattern, flags);
@ -243,9 +455,11 @@ Expect.prototype = {
* the expected error class
* @param {string} message
* message to present if assertion fails
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
throws : function Expect_throws(block, /*optional*/error, /*optional*/message) {
throws : function Assert_throws(block, /*optional*/error, /*optional*/message) {
return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
},
@ -258,9 +472,11 @@ Expect.prototype = {
* the expected error class
* @param {string} message
* message to present if assertion fails
* @throws {errors.AssertionError}
*
* @returns {boolean} Result of the test.
*/
doesNotThrow : function Expect_doesNotThrow(block, /*optional*/error, /*optional*/message) {
doesNotThrow : function Assert_doesNotThrow(block, /*optional*/error, /*optional*/message) {
return this._throws.apply(this, [false].concat(Array.prototype.slice.call(arguments)));
},
@ -270,7 +486,7 @@ Expect.prototype = {
adapted from node.js's assert._throws()
https://github.com/joyent/node/blob/master/lib/assert.js
*/
_throws : function Expect__throws(shouldThrow, block, expected, message) {
_throws : function Assert__throws(shouldThrow, block, expected, message) {
var actual;
if (typeof expected === 'string') {
@ -299,80 +515,153 @@ Expect.prototype = {
!this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
throw actual;
}
return this._test(true, message);
},
_expectedException : function Expect__expectedException(actual, expected) {
if (!actual || !expected) {
return false;
/**
* Test if the string contains the pattern.
*
* @param {String} aString String to test.
* @param {String} aPattern Pattern to look for in the string
* @param {String} aMessage Message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {Boolean} Result of the test.
*/
contain: function Assert_contain(aString, aPattern, aMessage) {
let condition = (aString.indexOf(aPattern) !== -1);
let diagnosis = "'" + aString + "' should contain '" + aPattern + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Test if the string does not contain the pattern.
*
* @param {String} aString String to test.
* @param {String} aPattern Pattern to look for in the string
* @param {String} aMessage Message to show for the test result
* @throws {errors.AssertionError}
*
* @returns {Boolean} Result of the test.
*/
notContain: function Assert_notContain(aString, aPattern, aMessage) {
let condition = (aString.indexOf(aPattern) === -1);
let diagnosis = "'" + aString + "' should not contain '" + aPattern + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Waits for the callback evaluates to true
*
* @param {Function} aCallback
* Callback for evaluation
* @param {String} aMessage
* Message to show for result
* @param {Number} aTimeout
* Timeout in waiting for evaluation
* @param {Number} aInterval
* Interval between evaluation attempts
* @param {Object} aThisObject
* this object
* @throws {errors.AssertionError}
*
* @returns {Boolean} Result of the test.
*/
waitFor: function Assert_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
var timeout = aTimeout || 5000;
var interval = aInterval || 100;
var self = {
timeIsUp: false,
result: aCallback.call(aThisObject)
};
var deadline = Date.now() + timeout;
function wait() {
if (self.result !== true) {
self.result = aCallback.call(aThisObject);
self.timeIsUp = Date.now() > deadline;
}
}
if (expected instanceof RegExp) {
return expected.test(actual);
} else if (actual instanceof expected) {
return true;
} else if (expected.call({}, actual) === true) {
return true;
var hwindow = Services.appShell.hiddenDOMWindow;
var timeoutInterval = hwindow.setInterval(wait, interval);
var thread = Services.tm.currentThread;
while (self.result !== true && !self.timeIsUp) {
thread.processNextEvent(true);
let type = typeof(self.result);
if (type !== 'boolean')
throw TypeError("waitFor() callback has to return a boolean" +
" instead of '" + type + "'");
}
return false;
hwindow.clearInterval(timeoutInterval);
if (self.result !== true && self.timeIsUp) {
aMessage = aMessage || arguments.callee.name + ": Timeout exceeded for '" + aCallback + "'";
throw new errors.TimeoutError(aMessage);
}
broker.pass({'function':'assert.waitFor()'});
return true;
}
}
/**
* AssertionError
*
* Error object thrown by failing assertions
*/
function AssertionError(message, fileName, lineNumber) {
var err = new Error();
if (err.stack) {
this.stack = err.stack;
}
this.message = message === undefined ? err.message : message;
this.fileName = fileName === undefined ? err.fileName : fileName;
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
};
AssertionError.prototype = new Error();
AssertionError.prototype.constructor = AssertionError;
AssertionError.prototype.name = 'AssertionError';
/* non-fatal assertions */
var Expect = function () {}
var Assert = function() {}
Assert.prototype = new Expect();
Assert.prototype.AssertionError = AssertionError;
Expect.prototype = new Assert();
/**
* The Assert class implements fatal assertions, and can be used in cases
* when a failing test has to directly abort the current test function. All
* remaining tasks will not be performed.
*
*/
/**
* Log a test as failing by throwing an AssertionException.
* Log a test as failing by adding a fail frame.
*
* @param {object} aResult
* Test result details used for reporting.
* <dl>
* <dd>fileName</dd>
* <dt>Name of the file in which the assertion failed.</dt>
* <dd>function</dd>
* <dd>functionName</dd>
* <dt>Function in which the assertion failed.</dt>
* <dd>lineNumber</dd>
* <dt>Line number of the file in which the assertion failed.</dt>
* <dd>message</dd>
* <dt>Message why the assertion failed.</dt>
* </dl>
* @throws {AssertionError }
*/
Assert.prototype._logFail = function Assert__logFail(aResult) {
throw new AssertionError(aResult);
Expect.prototype._logFail = function Expect__logFail(aResult) {
broker.fail({fail: aResult});
}
/**
* Waits for the callback evaluates to true
*
* @param {Function} aCallback
* Callback for evaluation
* @param {String} aMessage
* Message to show for result
* @param {Number} aTimeout
* Timeout in waiting for evaluation
* @param {Number} aInterval
* Interval between evaluation attempts
* @param {Object} aThisObject
* this object
*/
Expect.prototype.waitFor = function Expect_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
let condition = true;
let message = aMessage;
// Export of variables
assertions.Expect = Expect;
assertions.Assert = Assert;
try {
Assert.prototype.waitFor.apply(this, arguments);
}
catch (ex if ex instanceof errors.AssertionError) {
message = ex.message;
condition = false;
}
return this._test(condition, message);
}

View 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;

View 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 }
});

View File

@ -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();

View File

@ -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(/&nbsp;/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,
}
}

View File

@ -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;
}

View File

@ -1,12 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* @namespace Defines useful methods to work with localized content
*/
var l10n = exports;
Cu.import("resource://gre/modules/Services.jsm");
/**
* Retrieve the localized content for a given DTD entity
*
@ -54,14 +56,11 @@ function getEntity(aDTDs, aEntityId) {
* @returns {String} Value of the requested property
*/
function getProperty(aURL, aProperty) {
var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
var bundle = sbs.createBundle(aURL);
var bundle = Services.strings.createBundle(aURL);
try {
return bundle.GetStringFromName(aProperty);
}
catch (ex) {
} catch (ex) {
throw new Error("Unkown property '" + aProperty + "'");
}
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;
}

View 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();
}

View File

@ -1,49 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['inArray', 'getSet', 'indexOf', 'rindexOf', 'compare'];
var EXPORTED_SYMBOLS = ['inArray', 'getSet', 'indexOf',
'remove', 'rindexOf', 'compare'];
function inArray (array, value) {
for (i in array) {
function remove(array, from, to) {
var rest = array.slice((to || from) + 1 || array.length);
array.length = from < 0 ? array.length + from : from;
return array.push.apply(array, rest);
}
function inArray(array, value) {
for (var i in array) {
if (value == array[i]) {
return true;
}
}
return false;
}
function getSet (array) {
function getSet(array) {
var narray = [];
for (i in array) {
if ( !inArray(narray, array[i]) ) {
for (var i in array) {
if (!inArray(narray, array[i])) {
narray.push(array[i]);
}
}
return narray;
}
function indexOf (array, v, offset) {
for (i in array) {
function indexOf(array, v, offset) {
for (var i in array) {
if (offset == undefined || i >= offset) {
if ( !isNaN(i) && array[i] == v) {
if (!isNaN(i) && array[i] == v) {
return new Number(i);
}
}
}
return -1;
}
function rindexOf (array, v) {
var l = array.length;
for (i in array) {
for (var i in array) {
if (!isNaN(i)) {
var i = new Number(i)
var i = new Number(i);
}
if (!isNaN(i) && array[l - i] == v) {
return l - i;
}
}
return -1;
}
@ -51,10 +67,12 @@ function compare (array, carray) {
if (array.length != carray.length) {
return false;
}
for (i in array) {
for (var i in array) {
if (array[i] != carray[i]) {
return false;
}
}
return true;
}

View File

@ -1,21 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['getAttributes'];
var getAttributes = function (node) {
var attributes = {};
for (i in node.attributes) {
if ( !isNaN(i) ) {
for (var i in node.attributes) {
if (!isNaN(i)) {
try {
var attr = node.attributes[i];
attributes[attr.name] = attr.value;
} catch (err) {
}
catch (e) {
}
}
}
return attributes;
}

View File

@ -10,16 +10,36 @@
* httpd.js.
*/
this.EXPORTED_SYMBOLS = [
"HTTP_400",
"HTTP_401",
"HTTP_402",
"HTTP_403",
"HTTP_404",
"HTTP_405",
"HTTP_406",
"HTTP_407",
"HTTP_408",
"HTTP_409",
"HTTP_410",
"HTTP_411",
"HTTP_412",
"HTTP_413",
"HTTP_414",
"HTTP_415",
"HTTP_417",
"HTTP_500",
"HTTP_501",
"HTTP_502",
"HTTP_503",
"HTTP_504",
"HTTP_505",
"HttpError",
"HttpServer",
];
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
var EXPORTED_SYMBOLS = ['getServer'];
/**
* Overwrite both dump functions because we do not wanna have this output for Mozmill
*/
function dump() {}
function dumpn() {}
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
@ -58,7 +78,7 @@ function NS_ASSERT(cond, msg)
}
/** Constructs an HTTP error object. */
function HttpError(code, description)
this.HttpError = function HttpError(code, description)
{
this.code = code;
this.description = description;
@ -74,30 +94,30 @@ HttpError.prototype =
/**
* Errors thrown to trigger specific HTTP server responses.
*/
const HTTP_400 = new HttpError(400, "Bad Request");
const HTTP_401 = new HttpError(401, "Unauthorized");
const HTTP_402 = new HttpError(402, "Payment Required");
const HTTP_403 = new HttpError(403, "Forbidden");
const HTTP_404 = new HttpError(404, "Not Found");
const HTTP_405 = new HttpError(405, "Method Not Allowed");
const HTTP_406 = new HttpError(406, "Not Acceptable");
const HTTP_407 = new HttpError(407, "Proxy Authentication Required");
const HTTP_408 = new HttpError(408, "Request Timeout");
const HTTP_409 = new HttpError(409, "Conflict");
const HTTP_410 = new HttpError(410, "Gone");
const HTTP_411 = new HttpError(411, "Length Required");
const HTTP_412 = new HttpError(412, "Precondition Failed");
const HTTP_413 = new HttpError(413, "Request Entity Too Large");
const HTTP_414 = new HttpError(414, "Request-URI Too Long");
const HTTP_415 = new HttpError(415, "Unsupported Media Type");
const HTTP_417 = new HttpError(417, "Expectation Failed");
this.HTTP_400 = new HttpError(400, "Bad Request");
this.HTTP_401 = new HttpError(401, "Unauthorized");
this.HTTP_402 = new HttpError(402, "Payment Required");
this.HTTP_403 = new HttpError(403, "Forbidden");
this.HTTP_404 = new HttpError(404, "Not Found");
this.HTTP_405 = new HttpError(405, "Method Not Allowed");
this.HTTP_406 = new HttpError(406, "Not Acceptable");
this.HTTP_407 = new HttpError(407, "Proxy Authentication Required");
this.HTTP_408 = new HttpError(408, "Request Timeout");
this.HTTP_409 = new HttpError(409, "Conflict");
this.HTTP_410 = new HttpError(410, "Gone");
this.HTTP_411 = new HttpError(411, "Length Required");
this.HTTP_412 = new HttpError(412, "Precondition Failed");
this.HTTP_413 = new HttpError(413, "Request Entity Too Large");
this.HTTP_414 = new HttpError(414, "Request-URI Too Long");
this.HTTP_415 = new HttpError(415, "Unsupported Media Type");
this.HTTP_417 = new HttpError(417, "Expectation Failed");
const HTTP_500 = new HttpError(500, "Internal Server Error");
const HTTP_501 = new HttpError(501, "Not Implemented");
const HTTP_502 = new HttpError(502, "Bad Gateway");
const HTTP_503 = new HttpError(503, "Service Unavailable");
const HTTP_504 = new HttpError(504, "Gateway Timeout");
const HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
this.HTTP_500 = new HttpError(500, "Internal Server Error");
this.HTTP_501 = new HttpError(501, "Not Implemented");
this.HTTP_502 = new HttpError(502, "Bad Gateway");
this.HTTP_503 = new HttpError(503, "Service Unavailable");
this.HTTP_504 = new HttpError(504, "Gateway Timeout");
this.HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
/** Creates a hash with fields corresponding to the values in arr. */
function array2obj(arr)
@ -266,7 +286,7 @@ function toDateString(date)
{
var hrs = date.getUTCHours();
var rv = (hrs < 10) ? "0" + hrs : hrs;
var mins = date.getUTCMinutes();
rv += ":";
rv += (mins < 10) ? "0" + mins : mins;
@ -451,7 +471,15 @@ nsHttpServer.prototype =
onStopListening: function(socket, status)
{
dumpn(">>> shutting down server on port " + socket.port);
for (var n in this._connections) {
if (!this._connections[n]._requestStarted) {
this._connections[n].close();
}
}
this._socketClosed = true;
if (this._hasOpenConnections()) {
dumpn("*** open connections!!!");
}
if (!this._hasOpenConnections())
{
dumpn("*** no open connections, notifying async from onStopListening");
@ -493,12 +521,14 @@ nsHttpServer.prototype =
this._host = host;
// The listen queue needs to be long enough to handle
// network.http.max-persistent-connections-per-server concurrent connections,
// plus a safety margin in case some other process is talking to
// the server as well.
// network.http.max-persistent-connections-per-server or
// network.http.max-persistent-connections-per-proxy concurrent
// connections, plus a safety margin in case some other process is
// talking to the server as well.
var prefs = getRootPrefBranch();
var maxConnections =
prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5;
var maxConnections = 5 + Math.max(
prefs.getIntPref("network.http.max-persistent-connections-per-server"),
prefs.getIntPref("network.http.max-persistent-connections-per-proxy"));
try
{
@ -507,18 +537,52 @@ nsHttpServer.prototype =
var loopback = false;
}
var socket = new ServerSocket(this._port,
// When automatically selecting a port, sometimes the chosen port is
// "blocked" from clients. We don't want to use these ports because
// tests will intermittently fail. So, we simply keep trying to to
// get a server socket until a valid port is obtained. We limit
// ourselves to finite attempts just so we don't loop forever.
var ios = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var socket;
for (var i = 100; i; i--)
{
var temp = new ServerSocket(this._port,
loopback, // true = localhost, false = everybody
maxConnections);
var allowed = ios.allowPort(temp.port, "http");
if (!allowed)
{
dumpn(">>>Warning: obtained ServerSocket listens on a blocked " +
"port: " + temp.port);
}
if (!allowed && this._port == -1)
{
dumpn(">>>Throwing away ServerSocket with bad port.");
temp.close();
continue;
}
socket = temp;
break;
}
if (!socket) {
throw new Error("No socket server available. Are there no available ports?");
}
dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
" pending connections");
socket.asyncListen(this);
this._identity._initialize(port, host, true);
this._port = socket.port;
this._identity._initialize(socket.port, host, true);
this._socket = socket;
}
catch (e)
{
dumpn("!!! could not start server on port " + port + ": " + e);
dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
throw Cr.NS_ERROR_NOT_AVAILABLE;
}
},
@ -587,6 +651,14 @@ nsHttpServer.prototype =
this._handler.registerPathHandler(path, handler);
},
//
// see nsIHttpServer.registerPrefixHandler
//
registerPrefixHandler: function(prefix, handler)
{
this._handler.registerPrefixHandler(prefix, handler);
},
//
// see nsIHttpServer.registerErrorHandler
//
@ -759,6 +831,10 @@ nsHttpServer.prototype =
// Fire a pending server-stopped notification if it's our responsibility.
if (!this._hasOpenConnections() && this._socketClosed)
this._notifyStopped();
// Bug 508125: Add a GC here else we'll use gigabytes of memory running
// mochitests. We can't rely on xpcshell doing an automated GC, as that
// would interfere with testing GC stuff...
Components.utils.forceGC();
},
/**
@ -772,6 +848,7 @@ nsHttpServer.prototype =
}
};
this.HttpServer = nsHttpServer;
//
// RFC 2396 section 3.2.2:
@ -790,7 +867,7 @@ const HOST_REGEX =
// toplabel
"[a-z](?:[a-z0-9-]*[a-z0-9])?" +
"|" +
// IPv4 address
// IPv4 address
"\\d+\\.\\d+\\.\\d+\\.\\d+" +
")$",
"i");
@ -1001,7 +1078,7 @@ ServerIdentity.prototype =
// Not the default primary location, nothing special to do here
this.remove("http", "127.0.0.1", this._defaultPort);
}
// This is a *very* tricky bit of reasoning here; make absolutely sure the
// tests for this code pass before you commit changes to it.
if (this._primaryScheme == "http" &&
@ -1097,14 +1174,25 @@ function Connection(input, output, server, port, outgoingPort, number)
*/
this.request = null;
/** State variables for debugging. */
this._closed = this._processed = false;
/** This allows a connection to disambiguate between a peer initiating a
* close and the socket being forced closed on shutdown.
*/
this._closed = false;
/** State variable for debugging. */
this._processed = false;
/** whether or not 1st line of request has been received */
this._requestStarted = false;
}
Connection.prototype =
{
/** Closes this connection's input/output streams. */
close: function()
{
if (this._closed)
return;
dumpn("*** closing connection " + this.number +
" on port " + this._outgoingPort);
@ -1162,6 +1250,11 @@ Connection.prototype =
return "<Connection(" + this.number +
(this.request ? ", " + this.request.path : "") +"): " +
(this._closed ? "closed" : "open") + ">";
},
requestStarted: function()
{
this._requestStarted = true;
}
};
@ -1348,6 +1441,7 @@ RequestReader.prototype =
{
this._parseRequestLine(line.value);
this._state = READER_IN_HEADERS;
this._connection.requestStarted();
return true;
}
catch (e)
@ -1433,7 +1527,7 @@ RequestReader.prototype =
this._handleResponse();
return true;
}
return false;
}
catch (e)
@ -1606,7 +1700,10 @@ RequestReader.prototype =
// between fields, even though only a single SP is required (section 19.3)
var request = line.split(/[ \t]+/);
if (!request || request.length != 3)
{
dumpn("*** No request in line");
throw HTTP_400;
}
metadata._method = request[0];
@ -1614,7 +1711,10 @@ RequestReader.prototype =
var ver = request[2];
var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
if (!match)
{
dumpn("*** No HTTP version in line");
throw HTTP_400;
}
// determine HTTP version
try
@ -1639,7 +1739,10 @@ RequestReader.prototype =
{
// No absolute paths in the request line in HTTP prior to 1.1
if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
{
dumpn("*** Metadata version too low");
throw HTTP_400;
}
try
{
@ -1653,11 +1756,18 @@ RequestReader.prototype =
if (port === -1)
{
if (scheme === "http")
{
port = 80;
}
else if (scheme === "https")
{
port = 443;
}
else
{
dumpn("*** Unknown scheme: " + scheme);
throw HTTP_400;
}
}
}
catch (e)
@ -1665,11 +1775,15 @@ RequestReader.prototype =
// If the host is not a valid host on the server, the response MUST be a
// 400 (Bad Request) error message (section 5.2). Alternately, the URI
// is malformed.
dumpn("*** Threw when dealing with URI: " + e);
throw HTTP_400;
}
if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
{
dumpn("*** serverIdentity unknown or path does not start with '/'");
throw HTTP_400;
}
}
var splitter = fullPath.indexOf("?");
@ -1713,6 +1827,8 @@ RequestReader.prototype =
var line = {};
while (true)
{
dumpn("*** Last name: '" + lastName + "'");
dumpn("*** Last val: '" + lastVal + "'");
NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
lastName === undefined ?
"lastVal without lastName? lastVal: '" + lastVal + "'" :
@ -1727,6 +1843,7 @@ RequestReader.prototype =
}
var lineText = line.value;
dumpn("*** Line text: '" + lineText + "'");
var firstChar = lineText.charAt(0);
// blank line means end of headers
@ -1741,7 +1858,7 @@ RequestReader.prototype =
}
catch (e)
{
dumpn("*** e == " + e);
dumpn("*** setHeader threw on last header, e == " + e);
throw HTTP_400;
}
}
@ -1759,7 +1876,7 @@ RequestReader.prototype =
// multi-line header if we've already seen a header line
if (!lastName)
{
// we don't have a header to continue!
dumpn("We don't have a header to continue!");
throw HTTP_400;
}
@ -1778,7 +1895,7 @@ RequestReader.prototype =
}
catch (e)
{
dumpn("*** e == " + e);
dumpn("*** setHeader threw on a header, e == " + e);
throw HTTP_400;
}
}
@ -1786,7 +1903,7 @@ RequestReader.prototype =
var colon = lineText.indexOf(":"); // first colon must be splitter
if (colon < 1)
{
// no colon or missing header field-name
dumpn("*** No colon or missing header field-name");
throw HTTP_400;
}
@ -1811,12 +1928,14 @@ const CR = 0x0D, LF = 0x0A;
* character; the first CRLF is the lowest index i where
* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
* if such an |i| exists, and -1 otherwise
* @param start : uint
* start index from which to begin searching in array
* @returns int
* the index of the first CRLF if any were present, -1 otherwise
*/
function findCRLF(array)
function findCRLF(array, start)
{
for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1))
for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1))
{
if (array[i + 1] == LF)
return i;
@ -1833,6 +1952,9 @@ function LineData()
{
/** An array of queued bytes from which to get line-based characters. */
this._data = [];
/** Start index from which to search for CRLF. */
this._start = 0;
}
LineData.prototype =
{
@ -1842,7 +1964,22 @@ LineData.prototype =
*/
appendBytes: function(bytes)
{
Array.prototype.push.apply(this._data, bytes);
var count = bytes.length;
var quantum = 262144; // just above half SpiderMonkey's argument-count limit
if (count < quantum)
{
Array.prototype.push.apply(this._data, bytes);
return;
}
// Large numbers of bytes may cause Array.prototype.push to be called with
// more arguments than the JavaScript engine supports. In that case append
// bytes in fixed-size amounts until all bytes are appended.
for (var start = 0; start < count; start += quantum)
{
var slice = bytes.slice(start, Math.min(start + quantum, count));
Array.prototype.push.apply(this._data, slice);
}
},
/**
@ -1860,23 +1997,38 @@ LineData.prototype =
readLine: function(out)
{
var data = this._data;
var length = findCRLF(data);
var length = findCRLF(data, this._start);
if (length < 0)
{
this._start = data.length;
// But if our data ends in a CR, we have to back up one, because
// the first byte in the next packet might be an LF and if we
// start looking at data.length we won't find it.
if (data.length > 0 && data[data.length - 1] === CR)
--this._start;
return false;
}
// Reset for future lines.
this._start = 0;
//
// We have the index of the CR, so remove all the characters, including
// CRLF, from the array with splice, and convert the removed array into the
// corresponding string, from which we then strip the trailing CRLF.
// CRLF, from the array with splice, and convert the removed array
// (excluding the trailing CRLF characters) into the corresponding string.
//
// Getting the line in this matter acknowledges that substring is an O(1)
// operation in SpiderMonkey because strings are immutable, whereas two
// splices, both from the beginning of the data, are less likely to be as
// cheap as a single splice plus two extra character conversions.
//
var line = String.fromCharCode.apply(null, data.splice(0, length + 2));
out.value = line.substring(0, length);
var leading = data.splice(0, length + 2);
var quantum = 262144;
var line = "";
for (var start = 0; start < length; start += quantum)
{
var slice = leading.slice(start, Math.min(start + quantum, length));
line += String.fromCharCode.apply(null, slice);
}
out.value = line;
return true;
},
@ -1911,7 +2063,7 @@ function createHandlerFunc(handler)
*/
function defaultIndexHandler(metadata, response)
{
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var path = htmlEscape(decodeURI(metadata.path));
@ -2018,6 +2170,7 @@ function toInternalPath(path, encoded)
return comps.join("/");
}
const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
/**
* Adds custom-specified headers for the given file to the given response, if
@ -2045,7 +2198,7 @@ function maybeAddHeaders(file, metadata, response)
return;
const PR_RDONLY = 0x01;
var fis = new FileInputStream(headerFile, PR_RDONLY, 0444,
var fis = new FileInputStream(headerFile, PR_RDONLY, PERMS_READONLY,
Ci.nsIFileInputStream.CLOSE_ON_EOF);
try
@ -2078,7 +2231,7 @@ function maybeAddHeaders(file, metadata, response)
code = status.substring(0, space);
description = status.substring(space + 1, status.length);
}
response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
line.value = "";
@ -2149,6 +2302,15 @@ function ServerHandler(server)
*/
this._overridePaths = {};
/**
* Custom request handlers for the path prefixes on the server in which this
* resides. Path-handler pairs are stored as property-value pairs in this
* property.
*
* @see ServerHandler.prototype._defaultPaths
*/
this._overridePrefixes = {};
/**
* Custom request handlers for the error handlers in the server in which this
* resides. Path-handler pairs are stored as property-value pairs in this
@ -2213,7 +2375,23 @@ ServerHandler.prototype =
}
else
{
this._handleDefault(request, response);
var longestPrefix = "";
for (let prefix in this._overridePrefixes) {
if (prefix.length > longestPrefix.length &&
path.substr(0, prefix.length) == prefix)
{
longestPrefix = prefix;
}
}
if (longestPrefix.length > 0)
{
dumpn("calling prefix override for " + longestPrefix);
this._overridePrefixes[longestPrefix](request, response);
}
else
{
this._handleDefault(request, response);
}
}
}
catch (e)
@ -2318,6 +2496,18 @@ ServerHandler.prototype =
this._handlerToField(handler, this._overridePaths, path);
},
//
// see nsIHttpServer.registerPrefixHandler
//
registerPrefixHandler: function(path, handler)
{
// XXX true path validation!
if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")
throw Cr.NS_ERROR_INVALID_ARG;
this._handlerToField(handler, this._overridePrefixes, path);
},
//
// see nsIHttpServer.registerDirectory
//
@ -2458,7 +2648,10 @@ ServerHandler.prototype =
{
var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
if (!rangeMatch)
{
dumpn("*** Range header bogosity: '" + metadata.getHeader("Range") + "'");
throw HTTP_400;
}
if (rangeMatch[1] !== undefined)
start = parseInt(rangeMatch[1], 10);
@ -2467,7 +2660,10 @@ ServerHandler.prototype =
end = parseInt(rangeMatch[2], 10);
if (start === undefined && end === undefined)
{
dumpn("*** More Range header bogosity: '" + metadata.getHeader("Range") + "'");
throw HTTP_400;
}
// No start given, so the end is really the count of bytes from the
// end of the file.
@ -2537,7 +2733,7 @@ ServerHandler.prototype =
var type = this._getTypeFromFile(file);
if (type === SJS_TYPE)
{
var fis = new FileInputStream(file, PR_RDONLY, 0444,
var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
Ci.nsIFileInputStream.CLOSE_ON_EOF);
try
@ -2574,6 +2770,10 @@ ServerHandler.prototype =
{
self._setObjectState(k, v);
});
s.importFunction(function registerPathHandler(p, h)
{
self.registerPathHandler(p, h);
});
// Make it possible for sjs files to access their location
this._setState(path, "__LOCATION__", file.path);
@ -2586,7 +2786,7 @@ ServerHandler.prototype =
// getting the line number where we evaluate the SJS file. Don't
// separate these two lines!
var line = new Error().lineNumber;
Cu.evalInSandbox(sis.read(file.fileSize), s);
Cu.evalInSandbox(sis.read(file.fileSize), s, "latest");
}
catch (e)
{
@ -2627,7 +2827,7 @@ ServerHandler.prototype =
maybeAddHeaders(file, metadata, response);
response.setHeader("Content-Length", "" + count, false);
var fis = new FileInputStream(file, PR_RDONLY, 0444,
var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
Ci.nsIFileInputStream.CLOSE_ON_EOF);
offset = offset || 0;
@ -2878,6 +3078,7 @@ ServerHandler.prototype =
}
catch (e)
{
dumpn("*** toInternalPath threw " + e);
throw HTTP_400; // malformed path
}
@ -2962,7 +3163,7 @@ ServerHandler.prototype =
dumpn("*** error in request: " + errorCode);
this._handleError(errorCode, new Request(connection.port), response);
},
},
/**
* Handles a request which generates the given error code, using the
@ -3072,7 +3273,7 @@ ServerHandler.prototype =
{
// none of the data in metadata is reliable, so hard-code everything here
response.setStatusLine("1.1", 400, "Bad Request");
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
var body = "Bad request\n";
response.bodyOutputStream.write(body, body.length);
@ -3080,7 +3281,7 @@ ServerHandler.prototype =
403: function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "<html>\
<head><title>403 Forbidden</title></head>\
@ -3093,7 +3294,7 @@ ServerHandler.prototype =
404: function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "<html>\
<head><title>404 Not Found</title></head>\
@ -3113,7 +3314,7 @@ ServerHandler.prototype =
response.setStatusLine(metadata.httpVersion,
416,
"Requested Range Not Satisfiable");
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "<html>\
<head>\
@ -3132,7 +3333,7 @@ ServerHandler.prototype =
response.setStatusLine(metadata.httpVersion,
500,
"Internal Server Error");
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "<html>\
<head><title>500 Internal Server Error</title></head>\
@ -3147,7 +3348,7 @@ ServerHandler.prototype =
501: function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "<html>\
<head><title>501 Not Implemented</title></head>\
@ -3161,7 +3362,7 @@ ServerHandler.prototype =
505: function(metadata, response)
{
response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "<html>\
<head><title>505 HTTP Version Not Supported</title></head>\
@ -3183,7 +3384,7 @@ ServerHandler.prototype =
"/": function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "<html>\
<head><title>httpd.js</title></head>\
@ -3201,7 +3402,7 @@ ServerHandler.prototype =
"/trace": function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
var body = "Request-URI: " +
metadata.scheme + "://" + metadata.host + ":" + metadata.port +
@ -3211,7 +3412,7 @@ ServerHandler.prototype =
if (metadata.queryString)
body += "?" + metadata.queryString;
body += " HTTP/" + metadata.httpVersion + "\r\n";
var headEnum = metadata.headers;
@ -4569,7 +4770,10 @@ const headerUtils =
normalizeFieldName: function(fieldName)
{
if (fieldName == "")
{
dumpn("*** Empty fieldName");
throw Cr.NS_ERROR_INVALID_ARG;
}
for (var i = 0, sz = fieldName.length; i < sz; i++)
{
@ -4620,9 +4824,13 @@ const headerUtils =
val = val.replace(/^ +/, "").replace(/ +$/, "");
// that should have taken care of all CTLs, so val should contain no CTLs
dumpn("*** Normalized value: '" + val + "'");
for (var i = 0, len = val.length; i < len; i++)
if (isCTL(val.charCodeAt(i)))
{
dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
throw Cr.NS_ERROR_INVALID_ARG;
}
// XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
// normalize, however, so this can be construed as a tightening of the
@ -4757,17 +4965,17 @@ nsHttpHeaders.prototype =
var value = headerUtils.normalizeFieldValue(fieldValue);
// The following three headers are stored as arrays because their real-world
// syntax prevents joining individual headers into a single header using
// syntax prevents joining individual headers into a single header using
// ",". See also <http://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
if (merge && name in this._headers)
{
if (name === "www-authenticate" ||
name === "proxy-authenticate" ||
name === "set-cookie")
name === "set-cookie")
{
this._headers[name].push(value);
}
else
else
{
this._headers[name][0] += "," + value;
NS_ASSERT(this._headers[name].length === 1,
@ -4790,8 +4998,8 @@ nsHttpHeaders.prototype =
* @returns string
* the field value for the given header, possibly with non-semantic changes
* (i.e., leading/trailing whitespace stripped, whitespace runs replaced
* with spaces, etc.) at the option of the implementation; multiple
* instances of the header will be combined with a comma, except for
* with spaces, etc.) at the option of the implementation; multiple
* instances of the header will be combined with a comma, except for
* the three headers noted in the description of getHeaderValues
*/
getHeader: function(fieldName)
@ -5053,7 +5261,7 @@ Request.prototype =
//
// see nsIPropertyBag.getProperty
//
getProperty: function(name)
getProperty: function(name)
{
this._ensurePropertyBag();
return this._bag.getProperty(name);
@ -5075,7 +5283,7 @@ Request.prototype =
// PRIVATE IMPLEMENTATION
/** Ensures a property bag has been created for ad-hoc behaviors. */
_ensurePropertyBag: function()
{
@ -5086,10 +5294,8 @@ Request.prototype =
// XPCOM trappings
if (XPCOMUtils.generateNSGetFactory)
var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
else
var NSGetModule = XPCOMUtils.generateNSGetModule([nsHttpServer]);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
/**
* Creates a new HTTP server listening for loopback traffic on the given port,
@ -5147,20 +5353,3 @@ function server(port, basePath)
DEBUG = false;
}
function getServer (port, basePath) {
if (basePath) {
var lp = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
lp.initWithPath(basePath);
}
var srv = new nsHttpServer();
if (lp)
srv.registerDirectory("/", lp);
srv.registerContentType("sjs", SJS_TYPE);
srv.identity.setPrimary("http", "localhost", port);
srv._port = port;
return srv;
}

View File

@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['getLength', ];//'compare'];
@ -9,6 +9,7 @@ var getLength = function (obj) {
for (i in obj) {
len++;
}
return len;
}

View File

@ -1,38 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['listDirectory', 'getFileForPath', 'abspath', 'getPlatform'];
function listDirectory (file) {
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
function listDirectory(file) {
// file is the given directory (nsIFile)
var entries = file.directoryEntries;
var array = [];
while (entries.hasMoreElements())
{
while (entries.hasMoreElements()) {
var entry = entries.getNext();
entry.QueryInterface(Components.interfaces.nsIFile);
entry.QueryInterface(Ci.nsIFile);
array.push(entry);
}
return array;
}
function getFileForPath (path) {
var file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
function getFileForPath(path) {
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(path);
return file;
}
function abspath (rel, file) {
function abspath(rel, file) {
var relSplit = rel.split('/');
if (relSplit[0] == '..' && !file.isDirectory()) {
file = file.parent;
}
for each(p in relSplit) {
for each(var p in relSplit) {
if (p == '..') {
file = file.parent;
} else if (p == '.'){
} else if (p == '.') {
if (!file.isDirectory()) {
file = file.parent;
}
@ -40,14 +48,10 @@ function abspath (rel, file) {
file.append(p);
}
}
return file.path;
}
function getPlatform () {
var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULRuntime);
mPlatform = xulRuntime.OS.toLowerCase();
return mPlatform;
function getPlatform() {
return Services.appinfo.OS.toLowerCase();
}

View File

@ -1,6 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Jetpack.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Atul Varma <atul@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
(function(global) {
const Cc = Components.classes;

View File

@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['trim', 'vslice'];

View 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");
}

View File

@ -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();

View File

@ -7,7 +7,7 @@
* listed symbols will exposed on import, and only when and where imported.
*/
let EXPORTED_SYMBOLS = ["TPS"];
let EXPORTED_SYMBOLS = ["ACTIONS", "TPS"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
@ -40,7 +40,7 @@ var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
var mozmillInit = {};
Cu.import('resource://mozmill/modules/init.js', mozmillInit);
Cu.import('resource://mozmill/driver/mozmill.js', mozmillInit);
// Options for wiping data during a sync
const SYNC_RESET_CLIENT = "resetClient";
@ -795,7 +795,7 @@ let TPS = {
frame.events.addListener('setTest', this.MozmillSetTestListener.bind(this));
frame.events.addListener('endTest', this.MozmillEndTestListener.bind(this));
this.StartAsyncOperation();
frame.runTestFile(mozmillfile.path, false);
frame.runTestFile(mozmillfile.path, null);
},
/**