Bug 678628 - allow TPS to run Mozmill tests, f=philikon, a=testonly, DONTBUILD

--HG--
rename : services/sync/tps/chrome.manifest => services/sync/tps/extensions/tps/chrome.manifest
rename : services/sync/tps/components/tps-cmdline.js => services/sync/tps/extensions/tps/components/tps-cmdline.js
rename : services/sync/tps/install.rdf => services/sync/tps/extensions/tps/install.rdf
rename : services/sync/tps/modules/bookmarks.jsm => services/sync/tps/extensions/tps/modules/bookmarks.jsm
rename : services/sync/tps/modules/forms.jsm => services/sync/tps/extensions/tps/modules/forms.jsm
rename : services/sync/tps/modules/history.jsm => services/sync/tps/extensions/tps/modules/history.jsm
rename : services/sync/tps/modules/logger.jsm => services/sync/tps/extensions/tps/modules/logger.jsm
rename : services/sync/tps/modules/passwords.jsm => services/sync/tps/extensions/tps/modules/passwords.jsm
rename : services/sync/tps/modules/prefs.jsm => services/sync/tps/extensions/tps/modules/prefs.jsm
rename : services/sync/tps/modules/quit.js => services/sync/tps/extensions/tps/modules/quit.js
rename : services/sync/tps/modules/tabs.jsm => services/sync/tps/extensions/tps/modules/tabs.jsm
rename : services/sync/tps/modules/tps.jsm => services/sync/tps/extensions/tps/modules/tps.jsm
extra : rebase_source : d4eed9a707ffe801de9b1bb4e6f08d31e670eec4
This commit is contained in:
Jonathan Griffin 2011-08-22 14:27:25 -07:00
Родитель 02681d375f
Коммит 4bd38f2535
43 изменённых файлов: 12654 добавлений и 16 удалений

Просмотреть файл

@ -21,7 +21,8 @@
"test_privbrw_tabs.js", "test_privbrw_tabs.js",
"test_bookmarks_in_same_named_folder.js", "test_bookmarks_in_same_named_folder.js",
"test_client_wipe.js", "test_client_wipe.js",
"test_special_tabs.js" "test_special_tabs.js",
"test_mozmill_sanity.js"
] ]
} }

Просмотреть файл

@ -0,0 +1,60 @@
/* * ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert <cmtalbert@gmail.com>
* Jonathan Griffin <jgriffin@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 ***** */
var jum = {}; Components.utils.import('resource://mozmill/modules/jum.js', jum);
var setupModule = function(module) {
controller = mozmill.getBrowserController();
jum.assert(true, "SetupModule passes");
}
var setupTest = function(module) {
jum.assert(true, "SetupTest passes");
}
var testTestStep = function() {
jum.assert(true, "test Passes");
controller.open("http://www.mozilla.org");
}
var teardownTest = function () {
jum.assert(true, "teardownTest passes");
}
var teardownModule = function() {
jum.assert(true, "teardownModule passes");
}

Просмотреть файл

@ -0,0 +1,53 @@
/* 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);
var setupModule = function(module) {
module.controller = mozmill.getBrowserController();
};
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);
};

Просмотреть файл

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* The list of phases mapped to their corresponding profiles. The object
* here must be in strict JSON format, as it will get parsed by the Python
* testrunner (no single quotes, extra comma's, etc).
*/
var phases = { "phase1": "profile1",
"phase2": "profile2" };
/*
* Test phases
*/
Phase('phase1', [
[RunMozmillTest, 'mozmill_sanity.js'],
[Sync, SYNC_WIPE_SERVER]
]);
Phase('phase2', [
[Sync],
[RunMozmillTest, 'mozmill_sanity2.js'],
]);

Просмотреть файл

@ -0,0 +1,2 @@
resource mozmill resource/

Просмотреть файл

@ -0,0 +1,3 @@
/* debugging prefs */
pref("browser.dom.window.dump.enabled", true);
pref("javascript.options.showInConsole", true);

Просмотреть файл

@ -0,0 +1,59 @@
<?xml version="1.0"?>
<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:unpack>true</em:unpack>
<em:targetApplication>
<!-- Firefox -->
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>3.5</em:minVersion>
<em:maxVersion>9.*</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>

Просмотреть файл

@ -0,0 +1,410 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <mail@hskupin.info> (Original Author)
*
* 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 ***** */
// Use the frame module of Mozmill to raise non-fatal failures
var mozmillFrame = {};
Cu.import('resource://mozmill/modules/frame.js', mozmillFrame);
/**
* @name assertions
* @namespace Defines expect and assert methods to be used for assertions.
*/
var assertions = exports;
/* non-fatal assertions */
var Expect = function() {}
Expect.prototype = {
/**
* 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>
* <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>
*/
_logFail: function Expect__logFail(aResult) {
mozmillFrame.events.fail({fail: aResult});
},
/**
* Log a test as passing by adding a pass 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>
* <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>
*/
_logPass: function Expect__logPass(aResult) {
mozmillFrame.events.pass({pass: aResult});
},
/**
* Test the condition and mark test as passed or failed
*
* @param {boolean} aCondition
* Condition to test.
* @param {string} aMessage
* Message to show for the test result
* @param {string} aDiagnosis
* Diagnose message to show for the test result
* @returns {boolean} Result of the test.
*/
_test: function Expect__test(aCondition, aMessage, aDiagnosis) {
let diagnosis = aDiagnosis || "";
let message = aMessage || "";
if (diagnosis)
message = aMessage ? message + " - " + diagnosis : diagnosis;
// Build result data
let frame = Components.stack;
let result = {
'fileName' : frame.filename.replace(/(.*)-> /, ""),
'function' : frame.name,
'lineNumber' : frame.lineNumber,
'message' : message
};
// Log test result
if (aCondition)
this._logPass(result);
else
this._logFail(result);
return aCondition;
},
/**
* Perform an always passing test
*
* @param {string} aMessage
* Message to show for the test result.
* @returns {boolean} Always returns true.
*/
pass: function Expect_pass(aMessage) {
return this._test(true, aMessage, undefined);
},
/**
* Perform an always failing test
*
* @param {string} aMessage
* Message to show for the test result.
* @returns {boolean} Always returns false.
*/
fail: function Expect_fail(aMessage) {
return this._test(false, aMessage, undefined);
},
/**
* Test if the value pass
*
* @param {boolean|string|number|object} aValue
* Value to test.
* @param {string} aMessage
* Message to show for the test result.
* @returns {boolean} Result of the test.
*/
ok: function Expect_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
* Value to test.
* @param {boolean|string|number|object} aExpected
* Value to strictly compare with.
* @param {string} aMessage
* Message to show for the test result
* @returns {boolean} Result of the test.
*/
equal: function Expect_equal(aValue, aExpected, aMessage) {
let condition = (aValue === aExpected);
let diagnosis = "got '" + aValue + "', expected '" + aExpected + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Test if both specified values are not identical.
*
* @param {boolean|string|number|object} aValue
* Value to test.
* @param {boolean|string|number|object} aExpected
* Value to strictly compare with.
* @param {string} aMessage
* Message to show for the test result
* @returns {boolean} Result of the test.
*/
notEqual: function Expect_notEqual(aValue, aExpected, aMessage) {
let condition = (aValue !== aExpected);
let diagnosis = "got '" + aValue + "', not expected '" + aExpected + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Test if the regular expression matches the string.
*
* @param {string} aString
* String to test.
* @param {RegEx} aRegex
* Regular expression to use for testing that a match exists.
* @param {string} aMessage
* Message to show for the test result
* @returns {boolean} Result of the test.
*/
match: function Expect_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
let pattern = flags = "";
try {
let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
pattern = matches[1];
flags = matches[2];
}
catch (ex) {
}
let regex = new RegExp(pattern, flags);
let condition = (aString.match(regex) !== null);
let diagnosis = "'" + regex + "' matches for '" + aString + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Test if the regular expression does not match the string.
*
* @param {string} aString
* String to test.
* @param {RegEx} aRegex
* Regular expression to use for testing that a match does not exist.
* @param {string} aMessage
* Message to show for the test result
* @returns {boolean} Result of the test.
*/
notMatch: function Expect_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
let pattern = flags = "";
try {
let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
pattern = matches[1];
flags = matches[2];
}
catch (ex) {
}
let regex = new RegExp(pattern, flags);
let condition = (aString.match(regex) === null);
let diagnosis = "'" + regex + "' doesn't match for '" + aString + "'";
return this._test(condition, aMessage, diagnosis);
},
/**
* Test if a code block throws an exception.
*
* @param {string} block
* function to call to test for exception
* @param {RegEx} error
* the expected error class
* @param {string} message
* message to present if assertion fails
* @returns {boolean} Result of the test.
*/
throws : function Expect_throws(block, /*optional*/error, /*optional*/message) {
return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
},
/**
* Test if a code block doesn't throw an exception.
*
* @param {string} block
* function to call to test for exception
* @param {RegEx} error
* the expected error class
* @param {string} message
* message to present if assertion fails
* @returns {boolean} Result of the test.
*/
doesNotThrow : function Expect_doesNotThrow(block, /*optional*/error, /*optional*/message) {
return this._throws.apply(this, [false].concat(Array.prototype.slice.call(arguments)));
},
/* Tests whether a code block throws the expected exception
class. helper for throws() and doesNotThrow()
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) {
var actual;
if (typeof expected === 'string') {
message = expected;
expected = null;
}
try {
block();
} catch (e) {
actual = e;
}
message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
(message ? ' ' + message : '.');
if (shouldThrow && !actual) {
return this._test(false, message, 'Missing expected exception');
}
if (!shouldThrow && this._expectedException(actual, expected)) {
return this._test(false, message, 'Got unwanted exception');
}
if ((shouldThrow && actual && expected &&
!this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
throw actual;
}
return this._test(true, message);
},
_expectedException : function Expect__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;
}
return false;
}
}
/**
* 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';
var Assert = function() {}
Assert.prototype = new Expect();
Assert.prototype.AssertionError = AssertionError;
/**
* 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.
*
* @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>
* <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);
}
// Export of variables
assertions.Expect = Expect;
assertions.Assert = Assert;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,478 @@
// ***** BEGIN LICENSE BLOCK *****// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Adam Christian.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Adam Christian <adam.christian@gmail.com>
// Mikeal Rogers <mikeal.rogers@gmail.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 *****
var EXPORTED_SYMBOLS = ["Elem", "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);
var countQuotes = function(str){
var count = 0;
var i = 0;
while(i < str.length) {
i = str.indexOf('"', i);
if (i != -1) {
count++;
i++;
} else {
break;
}
}
return count;
};
/**
* smartSplit()
*
* Takes a lookup string as input and returns
* a list of each node in the string
*/
var smartSplit = function (str) {
// Ensure we have an even number of quotes
if (countQuotes(str) % 2 != 0) {
throw new Error ("Invalid Lookup Expression");
}
/**
* This regex matches a single "node" in a lookup string.
* In otherwords, it matches the part between the two '/'s
*
* Regex Explanation:
* \/ - start matching at the first forward slash
* ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
* [^\/]* - match the remainder of text outside of last quote but before next slash
*/
var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
var ret = []
var match = re.exec(str);
while (match != null) {
ret.push(match[0].replace(/^\//, ""));
match = re.exec(str);
}
return ret;
};
/**
* defaultDocuments()
*
* Returns a list of default documents in which to search for elements
* 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];
};
/**
* nodeSearch()
*
* Takes an optional document, callback and locator string
* Returns a handle to the located element or null
*/
function nodeSearch(doc, func, string) {
if (doc != undefined) {
var documents = [doc];
} 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)
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++) {
search(frames[i], func, string);
}
}
else { e = element; }
};
for (var i = 0; i < documents.length; ++i) {
var win = documents[i].defaultView;
search(win, func, string);
if (e) break;
}
return e;
};
/**
* Selector()
*
* Finds an element by selector string
*/
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;
};
/**
* ID()
*
* Finds an element by ID
*/
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);
};
/**
* Link()
*
* Finds a link by innerHTML
*/
function Link(_document, linkName) {
if (linkName == undefined) {
throw new Error('Link constructor did not recieve enough arguments.');
}
this.getNodeForDocument = function (linkName) {
var getText = function(el){
var text = "";
if (el.nodeType == 3){ //textNode
if (el.data != undefined){
text = el.data;
} else {
text = el.innerHTML;
}
text = text.replace(/n|r|t/g, " ");
}
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";
}
}
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');
}
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){
return el;
}
}
return null;
};
return nodeSearch(_document, this.getNodeForDocument, linkName);
};
/**
* XPath()
*
* Finds an element by XPath
*/
function XPath(_document, expr) {
if (expr == undefined) {
throw new Error('XPath constructor did not recieve enough arguments.');
}
this.getNodeForDocument = function (s) {
var aNode = this.document;
var aExpr = s;
var xpe = null;
if (this.document.defaultView == null) {
xpe = new getMethodInWindows('XPathEvaluator')();
} else {
xpe = new this.document.defaultView.XPathEvaluator();
}
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())
found.push(res);
return found[0];
};
return nodeSearch(_document, this.getNodeForDocument, expr);
};
/**
* Name()
*
* Finds an element by Name
*/
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]; }
}
catch(err){};
return null;
};
return nodeSearch(_document, this.getNodeForDocument, nName);
};
var _returnResult = function (results) {
if (results.length == 0) {
return null
} 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) {
// 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)
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)
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];
}
/**
* Lookup()
*
* Finds an element by Lookup 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)
var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
var reduceLookup = function (parent, exp) {
// Handle case where only index is provided
var cases = nCases;
// Handle ending index before any of the expression gets mangled
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;
}
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, '[', ']')+' ||');
}
var r = cases['index'](_document, parent, obj);
if (r == null) {
throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
}
return r;
}
for (var c in cases) {
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, '(', ')')+' ||');
}
var result = cases[c](_document, parent, obj);
}
}
if (!result) {
if ( withs.startsWith(exp, '{') ) {
try {
var obj = json2.JSON.parse(exp)
} catch(err) {
throw new Error(err+'. String to be parsed was || '+exp+' ||');
}
if (cases == aCases) {
var result = _anonByAttrib(_document, parent, obj)
} else {
var result = _byAttrib(parent, obj)
}
}
if (!result) {
throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
}
}
// Final return
if (expIndex) {
// TODO: Check length and raise error
return result[expIndex];
} else {
// TODO: Check length and raise error
return result;
}
// Maybe we should cause an exception here
return false;
};
return expSplit.reduce(reduceLookup);
};

Просмотреть файл

@ -0,0 +1,595 @@
// ***** BEGIN LICENSE BLOCK *****// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Mikeal Rogers.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.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 *****
var EXPORTED_SYMBOLS = ['loadFile','Collector','Runner','events',
'jsbridge', 'runTestFile', 'log', 'getThread',
'timers', 'persisted'];
var httpd = {}; Components.utils.import('resource://mozmill/stdlib/httpd.js', httpd);
var os = {}; Components.utils.import('resource://mozmill/stdlib/os.js', os);
var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
var securableModule = {}; Components.utils.import('resource://mozmill/stdlib/securable-module.js', securableModule);
var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
getService(Components.interfaces.nsIConsoleService);
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var subscriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
var uuidgen = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Components.interfaces.nsIUUIDGenerator);
var persisted = {};
var moduleLoader = new securableModule.Loader({
rootPaths: ["resource://mozmill/modules/"],
defaultPrincipal: "system",
globals : { Cc: Components.classes,
Ci: Components.interfaces,
Cu: Components.utils,
Cr: Components.results}
});
arrayRemove = function(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);
};
mozmill = undefined; mozelement = undefined;
var loadTestResources = function () {
// load resources we want in our tests
if (mozmill == undefined) {
mozmill = {};
Components.utils.import("resource://mozmill/modules/mozmill.js", mozmill);
}
if (mozelement == undefined) {
mozelement = {};
Components.utils.import("resource://mozmill/modules/mozelement.js", mozelement);
}
}
var loadFile = function(path, collector) {
// load a test module from a file and add some candy
var file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(path);
var uri = ios.newFileURI(file).spec;
loadTestResources();
var assertions = moduleLoader.require("./assertions");
var module = {
collector: collector,
mozmill: mozmill,
elementslib: mozelement,
findElement: mozelement,
persisted: persisted,
Cc: Components.classes,
Ci: Components.interfaces,
Cu: Components.utils,
Cr: Components.results,
log: log,
assert: new assertions.Assert(),
expect: new assertions.Expect()
}
module.require = function (mod) {
var loader = new securableModule.Loader({
rootPaths: [ios.newFileURI(file.parent).spec,
"resource://mozmill/modules/"],
defaultPrincipal: "system",
globals : { mozmill: mozmill,
elementslib: mozelement, // This a quick hack to maintain backwards compatibility with 1.5.x
findElement: mozelement,
persisted: persisted,
Cc: Components.classes,
Ci: Components.interfaces,
Cu: Components.utils,
log: log }
});
return loader.require(mod);
}
if (collector != undefined) {
collector.current_file = file;
collector.current_path = path;
}
try {
subscriptLoader.loadSubScript(uri, module, "UTF-8");
} catch(e) {
events.fail(e);
var obj = {
'filename':path,
'passed':false,
'failed':true,
'passes':0,
'fails' :1,
'name' :'Unknown Test',
};
events.fireEvent('endTest', obj);
Components.utils.reportError(e);
}
module.__file__ = path;
module.__uri__ = uri;
return module;
}
function stateChangeBase (possibilties, restrictions, target, cmeta, v) {
if (possibilties) {
if (!arrays.inArray(possibilties, v)) {
// TODO Error value not in this.poss
return;
}
}
if (restrictions) {
for (var i in restrictions) {
var r = restrictions[i];
if (!r(v)) {
// TODO error value did not pass restriction
return;
}
}
}
// Fire jsbridge notification, logging notification, listener notifications
events[target] = v;
events.fireEvent(cmeta, target);
}
timers = [];
var events = {
'currentState' : null,
'currentModule': null,
'currentTest' : null,
'userShutdown' : false,
'appQuit' : false,
'listeners' : {},
}
events.setState = function (v) {
return stateChangeBase(['dependencies', 'setupModule', 'teardownModule',
'setupTest', 'teardownTest', 'test', 'collection'],
null, 'currentState', 'setState', v);
}
events.toggleUserShutdown = function (obj){
if (this.userShutdown) {
this.fail({'function':'frame.events.toggleUserShutdown', 'message':'Shutdown expected but none detected before timeout', 'userShutdown': obj});
}
this.userShutdown = obj;
}
events.isUserShutdown = function () {
return Boolean(this.userShutdown);
}
events.setTest = function (test, invokedFromIDE) {
test.__passes__ = [];
test.__fails__ = [];
test.__invokedFromIDE__ = invokedFromIDE;
events.currentTest = test;
test.__start__ = Date.now();
var obj = {'filename':events.currentModule.__file__,
'name':test.__name__,
}
events.fireEvent('setTest', obj);
}
events.endTest = function (test) {
// report the end of a test
test.status = 'done';
events.currentTest = null;
test.__end__ = Date.now();
var obj = {'filename':events.currentModule.__file__,
'passed':test.__passes__.length,
'failed':test.__fails__.length,
'passes':test.__passes__,
'fails' :test.__fails__,
'name' :test.__name__,
'time_start':test.__start__,
'time_end':test.__end__
}
if (test.skipped) {
obj['skipped'] = true;
obj.skipped_reason = test.skipped_reason;
}
if (test.meta) {
obj.meta = test.meta;
}
// Report the test result only if the test is a true test or if it is a
// failing setup/teardown
var shouldSkipReporting = false;
if (test.__passes__ &&
(test.__name__ == 'setupModule' ||
test.__name__ == 'setupTest' ||
test.__name__ == 'teardownTest' ||
test.__name__ == 'teardownModule')) {
shouldSkipReporting = true;
}
if (!shouldSkipReporting) {
events.fireEvent('endTest', obj);
}
}
events.setModule = function (v) {
return stateChangeBase( null, [function (v) {return (v.__file__ != undefined)}],
'currentModule', 'setModule', v);
}
events.pass = function (obj) {
// a low level event, such as a keystroke, succeeds
if (events.currentTest) {
events.currentTest.__passes__.push(obj);
}
for each(var timer in timers) {
timer.actions.push(
{"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":obj,
"result":"pass"}
);
}
events.fireEvent('pass', obj);
}
events.fail = function (obj) {
var error = obj.exception;
if(error) {
// Error objects aren't enumerable https://bugzilla.mozilla.org/show_bug.cgi?id=637207
obj.exception = {
name: error.name,
message: error.message,
lineNumber: error.lineNumber,
fileName: error.fileName,
stack: error.stack
};
}
// a low level event, such as a keystroke, fails
if (events.currentTest) {
events.currentTest.__fails__.push(obj);
}
for each(var time in timers) {
timer.actions.push(
{"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":obj,
"result":"fail"}
);
}
events.fireEvent('fail', obj);
}
events.skip = function (reason) {
// this is used to report skips associated with setupModule and setupTest
// and nothing else
events.currentTest.skipped = true;
events.currentTest.skipped_reason = reason;
for each(var timer in timers) {
timer.actions.push(
{"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":reason,
"result":"skip"}
);
}
events.fireEvent('skip', reason);
}
events.fireEvent = function (name, obj) {
if (this.listeners[name]) {
for (var i in this.listeners[name]) {
this.listeners[name][i](obj);
}
}
for each(var listener in this.globalListeners) {
listener(name, obj);
}
}
events.globalListeners = [];
events.addListener = function (name, listener) {
if (this.listeners[name]) {
this.listeners[name].push(listener);
} else if (name =='') {
this.globalListeners.push(listener)
} else {
this.listeners[name] = [listener];
}
}
events.removeListener = function(listener) {
for (var listenerIndex in this.listeners) {
var e = this.listeners[listenerIndex];
for (var i in e){
if (e[i] == listener) {
this.listeners[listenerIndex] = arrayRemove(e, i);
}
}
}
for (var i in this.globalListeners) {
if (this.globalListeners[i] == listener) {
this.globalListeners = arrayRemove(this.globalListeners, i);
}
}
}
var log = function (obj) {
events.fireEvent('log', obj);
}
try {
var jsbridge = {}; Components.utils.import('resource://jsbridge/modules/events.js', jsbridge);
} catch(err) {
var jsbridge = null;
aConsoleService.logStringMessage("jsbridge not available.");
}
if (jsbridge) {
events.addListener('', function (name, obj) {jsbridge.fireEvent('mozmill.'+name, obj)} );
}
function Collector () {
// the collector handles HTTPD and initilizing the module
this.test_modules_by_filename = {};
this.testing = [];
this.httpd_started = false;
this.http_port = 43336;
this.http_server = httpd.getServer(this.http_port);
}
Collector.prototype.startHttpd = function () {
while (this.httpd == undefined) {
try {
this.http_server.start(this.http_port);
this.httpd = this.http_server;
} catch(e) { // Failure most likely due to port conflict
this.http_port++;
this.http_server = httpd.getServer(this.http_port);
};
}
}
Collector.prototype.stopHttpd = function () {
if (this.httpd) {
this.httpd.stop(function(){}); // Callback needed to pause execution until the server has been properly shutdown
this.httpd = null;
}
}
Collector.prototype.addHttpResource = function (directory, ns) {
if (!this.httpd) {
this.startHttpd();
}
if (!ns) {
ns = '/';
} else {
ns = '/' + ns + '/';
}
var lp = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
lp.initWithPath(os.abspath(directory, this.current_file));
this.httpd.registerDirectory(ns, lp);
return 'http://localhost:' + this.http_port + ns
}
Collector.prototype.initTestModule = function (filename, name) {
var test_module = loadFile(filename, this);
test_module.__tests__ = [];
for (var i in test_module) {
if (typeof(test_module[i]) == "function") {
test_module[i].__name__ = i;
if (i == "setupTest") {
test_module.__setupTest__ = test_module[i];
} else if (i == "setupModule") {
test_module.__setupModule__ = test_module[i];
} else if (i == "teardownTest") {
test_module.__teardownTest__ = test_module[i];
} else if (i == "teardownModule") {
test_module.__teardownModule__ = test_module[i];
} else if (withs.startsWith(i, "test")) {
if (name && (i != name)) {
continue;
}
name = null;
test_module.__tests__.push(test_module[i]);
}
}
}
test_module.collector = this;
test_module.status = 'loaded';
this.test_modules_by_filename[filename] = test_module;
return test_module;
}
// Observer which gets notified when the application quits
function AppQuitObserver() {
this.register();
}
AppQuitObserver.prototype = {
observe: function(subject, topic, data) {
events.appQuit = true;
},
register: function() {
var obsService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
obsService.addObserver(this, "quit-application", false);
},
unregister: function() {
var obsService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
obsService.removeObserver(this, "quit-application");
}
}
function Runner (collector, invokedFromIDE) {
this.collector = collector;
this.invokedFromIDE = invokedFromIDE
events.fireEvent('startRunner', true);
var m = {}; Components.utils.import('resource://mozmill/modules/mozmill.js', m);
this.platform = m.platform;
}
Runner.prototype.runTestFile = function (filename, name) {
this.collector.initTestModule(filename, name);
this.runTestModule(this.collector.test_modules_by_filename[filename]);
}
Runner.prototype.end = function () {
try {
events.fireEvent('persist', persisted);
} catch(e) {
events.fireEvent('error', "persist serialization failed.");
}
this.collector.stopHttpd();
events.fireEvent('endRunner', true);
}
Runner.prototype.wrapper = function (func, arg) {
thread = Components.classes["@mozilla.org/thread-manager;1"]
.getService(Components.interfaces.nsIThreadManager)
.currentThread;
// skip excluded platforms
if (func.EXCLUDED_PLATFORMS != undefined) {
if (arrays.inArray(func.EXCLUDED_PLATFORMS, this.platform)) {
events.skip("Platform exclusion");
return;
}
}
// skip function if requested
if (func.__force_skip__ != undefined) {
events.skip(func.__force_skip__);
return;
}
// execute the test function
try {
if (arg) {
func(arg);
} else {
func();
}
// If a user shutdown was expected but the application hasn't quit, throw a failure
if (events.isUserShutdown()) {
utils.sleep(500); // Prevents race condition between mozrunner hard process kill and normal FFx shutdown
if (events.userShutdown['user'] && !events.appQuit) {
events.fail({'function':'Runner.wrapper',
'message':'Shutdown expected but none detected before end of test',
'userShutdown': events.userShutdown});
}
}
} catch (e) {
// Allow the exception if a user shutdown was expected
if (!events.isUserShutdown()) {
events.fail({'exception': e, 'test':func})
Components.utils.reportError(e);
}
}
}
Runner.prototype.runTestModule = function (module) {
events.setModule(module);
module.__status__ = 'running';
if (module.__setupModule__) {
events.setState('setupModule');
events.setTest(module.__setupModule__);
this.wrapper(module.__setupModule__, module);
var setupModulePassed = (events.currentTest.__fails__.length == 0 && !events.currentTest.skipped);
events.endTest(module.__setupModule__);
} else {
var setupModulePassed = true;
}
if (setupModulePassed) {
var observer = new AppQuitObserver();
for (var i in module.__tests__) {
events.appQuit = false;
var test = module.__tests__[i];
// TODO: introduce per-test timeout:
// https://bugzilla.mozilla.org/show_bug.cgi?id=574871
if (module.__setupTest__) {
events.setState('setupTest');
events.setTest(module.__setupTest__);
this.wrapper(module.__setupTest__, test);
var setupTestPassed = (events.currentTest.__fails__.length == 0 && !events.currentTest.skipped);
events.endTest(module.__setupTest__);
} else {
var setupTestPassed = true;
}
events.setState('test');
events.setTest(test, this.invokedFromIDE);
if (setupTestPassed) {
this.wrapper(test);
if (events.userShutdown && !events.userShutdown['user']) {
events.endTest(test);
break;
}
} else {
events.skip("setupTest failed.");
}
if (module.__teardownTest__) {
events.setState('teardownTest');
events.setTest(module.__teardownTest__);
this.wrapper(module.__teardownTest__, test);
events.endTest(module.__teardownTest__);
}
events.endTest(test)
}
observer.unregister();
} else {
for each(var test in module.__tests__) {
events.setTest(test);
events.skip("setupModule failed.");
events.endTest(test);
}
}
if (module.__teardownModule__) {
events.setState('teardownModule');
events.setTest(module.__teardownModule__);
this.wrapper(module.__teardownModule__, module);
events.endTest(module.__teardownModule__);
}
module.__status__ = 'done';
}
var runTestFile = function (filename, invokedFromIDE, name) {
var runner = new Runner(new Collector(), invokedFromIDE);
runner.runTestFile(filename, name);
runner.end();
return true;
}
var getThread = function () {
return thread;
}

Просмотреть файл

@ -0,0 +1,212 @@
/* ***** 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 Mozilla Corporation Code.
*
* The Initial Developer of the Original Code is
* Adam Christian.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Adam Christian <adam.christian@gmail.com>
* Mikeal Rogers <mikeal.rogers@gmail.com>
* Henrik Skupin <hskupin@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 ***** */
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();

Просмотреть файл

@ -0,0 +1,396 @@
// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Mikeal Rogers.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.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 *****
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);
}
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,
}
}

Просмотреть файл

@ -0,0 +1,265 @@
// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Adam Christian.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.com>
// M.-A. Darche <mozdev@cynode.org>
//
// 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 *****
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;
}

Просмотреть файл

@ -0,0 +1,105 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is MozMill Test code.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henrik Skupin <mail@hskupin.info> (Original Author)
* Adrian Kalla <akalla@aviary.pl>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* @namespace Defines useful methods to work with localized content
*/
var l10n = exports;
/**
* Retrieve the localized content for a given DTD entity
*
* @memberOf l10n
* @param {String[]} aDTDs Array of URLs for DTD files.
* @param {String} aEntityId ID of the entity to get the localized content of.
*
* @returns {String} Localized content
*/
function getEntity(aDTDs, aEntityId) {
// Add xhtml11.dtd to prevent missing entity errors with XHTML files
aDTDs.push("resource:///res/dtd/xhtml11.dtd");
// Build a string of external entities
var references = "";
for (i = 0; i < aDTDs.length; i++) {
var id = 'dtd' + i;
references += '<!ENTITY % ' + id + ' SYSTEM "' + aDTDs[i] + '">%' + id + ';';
}
var header = '<?xml version="1.0"?><!DOCTYPE elem [' + references + ']>';
var element = '<elem id="entity">&' + aEntityId + ';</elem>';
var content = header + element;
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
createInstance(Ci.nsIDOMParser);
var doc = parser.parseFromString(content, 'text/xml');
var node = doc.querySelector('elem[id="entity"]');
if (!node) {
throw new Error("Unkown entity '" + aEntityId + "'");
}
return node.textContent;
}
/**
* Retrieve the localized content for a given property
*
* @memberOf l10n
* @param {String} aURL URL of the .properties file.
* @param {String} aProperty The property to get the value of.
*
* @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);
try {
return bundle.GetStringFromName(aProperty);
}
catch (ex) {
throw new Error("Unkown property '" + aProperty + "'");
}
}
// Export of functions
l10n.getEntity = getEntity;
l10n.getProperty = getProperty;

Просмотреть файл

@ -0,0 +1,702 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozmill Elements.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
*
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Andrew Halberstadt <halbersa@gmail.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 ***** */
var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
"MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
"MozMillTextBox", "subclasses",
];
var EventUtils = {}; Components.utils.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
// A list of all the subclasses available. Shared modules can push their own subclasses onto this list
var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
/**
* createInstance()
*
* Returns an new instance of a MozMillElement
* The type of the element is automatically determined
*/
function createInstance(locatorType, locator, elem) {
if (elem) {
var args = {"element":elem};
for (var i = 0; i < subclasses.length; ++i) {
if (subclasses[i].isType(elem)) {
return new subclasses[i](locatorType, locator, args);
}
}
if (MozMillElement.isType(elem)) return new MozMillElement(locatorType, locator, args);
}
throw new Error("could not find element " + locatorType + ": " + locator);
};
var Elem = function(node) {
return createInstance("Elem", node, node);
};
var Selector = function(_document, selector, index) {
return createInstance("Selector", selector, elementslib.Selector(_document, selector, index));
};
var ID = function(_document, nodeID) {
return createInstance("ID", nodeID, elementslib.ID(_document, nodeID));
};
var Link = function(_document, linkName) {
return createInstance("Link", linkName, elementslib.Link(_document, linkName));
};
var XPath = function(_document, expr) {
return createInstance("XPath", expr, elementslib.XPath(_document, expr));
};
var Name = function(_document, nName) {
return createInstance("Name", nName, elementslib.Name(_document, nName));
};
var Lookup = function(_document, expression) {
return createInstance("Lookup", expression, elementslib.Lookup(_document, expression));
};
/**
* MozMillElement
* The base class for all mozmill elements
*/
function MozMillElement(locatorType, locator, args) {
args = args || {};
this._locatorType = locatorType;
this._locator = locator;
this._element = args["element"];
this._document = args["document"];
this._owner = args["owner"];
// Used to maintain backwards compatibility with controller.js
this.isElement = true;
}
// Static method that returns true if node is of this element type
MozMillElement.isType = function(node) {
return true;
};
// This getter is the magic behind lazy loading (note distinction between _element and element)
MozMillElement.prototype.__defineGetter__("element", function() {
if (this._element == undefined) {
if (elementslib[this._locatorType]) {
this._element = elementslib[this._locatorType](this._document, this._locator);
} else if (this._locatorType == "Elem") {
this._element = this._locator;
} else {
throw new Error("Unknown locator type: " + this._locatorType);
}
}
return this._element;
});
// Returns the actual wrapped DOM node
MozMillElement.prototype.getNode = function() {
return this.element;
};
MozMillElement.prototype.getInfo = function() {
return this._locatorType + ": " + this._locator;
};
/**
* Sometimes an element which once existed will no longer exist in the DOM
* This function re-searches for the element
*/
MozMillElement.prototype.exists = function() {
this._element = undefined;
if (this.element) return true;
return false;
};
/**
* Synthesize a keypress event on the given element
*
* @param {string} aKey
* Key to use for synthesizing the keypress event. It can be a simple
* character like "k" or a string like "VK_ESCAPE" for command keys
* @param {object} aModifiers
* Information about the modifier keys to send
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
* [optional - default: false]
* altKey - Hold down the alt key
* [optional - default: false]
* ctrlKey - Hold down the ctrl key
* [optional - default: false]
* metaKey - Hold down the meta key (command key on Mac)
* [optional - default: false]
* shiftKey - Hold down the shift key
* [optional - default: false]
* @param {object} aExpectedEvent
* Information about the expected event to occur
* Elements: target - Element which should receive the event
* [optional - default: current element]
* type - Type of the expected key event
*/
MozMillElement.prototype.keypress = function(aKey, aModifiers, aExpectedEvent) {
if (!this.element) {
throw new Error("Could not find element " + this.getInfo());
}
var win = this.element.ownerDocument? this.element.ownerDocument.defaultView : this.element;
this.element.focus();
if (aExpectedEvent) {
var target = aExpectedEvent.target? aExpectedEvent.target.getNode() : this.element;
EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
"MozMillElement.keypress()", win);
} else {
EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
}
frame.events.pass({'function':'MozMillElement.keypress()'});
return true;
};
/**
* Synthesize a general mouse event on the given element
*
* @param {ElemBase} aTarget
* Element which will receive the mouse event
* @param {number} aOffsetX
* Relative x offset in the elements bounds to click on
* @param {number} aOffsetY
* Relative y offset in the elements bounds to click on
* @param {object} aEvent
* Information about the event to send
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
* [optional - default: false]
* altKey - Hold down the alt key
* [optional - default: false]
* button - Mouse button to use
* [optional - default: 0]
* clickCount - Number of counts to click
* [optional - default: 1]
* ctrlKey - Hold down the ctrl key
* [optional - default: false]
* metaKey - Hold down the meta key (command key on Mac)
* [optional - default: false]
* shiftKey - Hold down the shift key
* [optional - default: false]
* type - Type of the mouse event ('click', 'mousedown',
* 'mouseup', 'mouseover', 'mouseout')
* [optional - default: 'mousedown' + 'mouseup']
* @param {object} aExpectedEvent
* Information about the expected event to occur
* Elements: target - Element which should receive the event
* [optional - default: current element]
* type - Type of the expected mouse event
*/
MozMillElement.prototype.mouseEvent = function(aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
if (!this.element) {
throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
}
// If no offset is given we will use the center of the element to click on.
var rect = this.element.getBoundingClientRect();
if (isNaN(aOffsetX)) {
aOffsetX = rect.width / 2;
}
if (isNaN(aOffsetY)) {
aOffsetY = rect.height / 2;
}
// Scroll element into view otherwise the click will fail
if (this.element.scrollIntoView) {
this.element.scrollIntoView();
}
if (aExpectedEvent) {
// The expected event type has to be set
if (!aExpectedEvent.type)
throw new Error(arguments.callee.name + ": Expected event type not specified");
// If no target has been specified use the specified element
var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : this.element;
if (!target) {
throw new Error(arguments.callee.name + ": could not find element " + aExpectedEvent.target.getInfo());
}
EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
target, aExpectedEvent.event,
"MozMillElement.mouseEvent()",
this.element.ownerDocument.defaultView);
} else {
EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
this.element.ownerDocument.defaultView);
}
};
/**
* Synthesize a mouse click event on the given element
*/
MozMillElement.prototype.click = function(left, top, expectedEvent) {
// Handle menu items differently
if (this.element && this.element.tagName == "menuitem") {
this.element.click();
} else {
this.mouseEvent(left, top, {}, expectedEvent);
}
frame.events.pass({'function':'MozMillElement.click()'});
};
/**
* Synthesize a double click on the given element
*/
MozMillElement.prototype.doubleClick = function(left, top, expectedEvent) {
this.mouseEvent(left, top, {clickCount: 2}, expectedEvent);
frame.events.pass({'function':'MozMillElement.doubleClick()'});
return true;
};
/**
* Synthesize a mouse down event on the given element
*/
MozMillElement.prototype.mouseDown = function (button, left, top, expectedEvent) {
this.mouseEvent(left, top, {button: button, type: "mousedown"}, expectedEvent);
frame.events.pass({'function':'MozMillElement.mouseDown()'});
return true;
};
/**
* Synthesize a mouse out event on the given element
*/
MozMillElement.prototype.mouseOut = function (button, left, top, expectedEvent) {
this.mouseEvent(left, top, {button: button, type: "mouseout"}, expectedEvent);
frame.events.pass({'function':'MozMillElement.mouseOut()'});
return true;
};
/**
* Synthesize a mouse over event on the given element
*/
MozMillElement.prototype.mouseOver = function (button, left, top, expectedEvent) {
this.mouseEvent(left, top, {button: button, type: "mouseover"}, expectedEvent);
frame.events.pass({'function':'MozMillElement.mouseOver()'});
return true;
};
/**
* Synthesize a mouse up event on the given element
*/
MozMillElement.prototype.mouseUp = function (button, left, top, expectedEvent) {
this.mouseEvent(left, top, {button: button, type: "mouseup"}, expectedEvent);
frame.events.pass({'function':'MozMillElement.mouseUp()'});
return true;
};
/**
* Synthesize a mouse middle click event on the given element
*/
MozMillElement.prototype.middleClick = function(left, top, expectedEvent) {
this.mouseEvent(left, top, {button: 1}, expectedEvent);
frame.events.pass({'function':'MozMillElement.middleClick()'});
return true;
};
/**
* Synthesize a mouse right click event on the given element
*/
MozMillElement.prototype.rightClick = function(left, top, expectedEvent) {
this.mouseEvent(left, top, {type : "contextmenu", button: 2 }, expectedEvent);
frame.events.pass({'function':'MozMillElement.rightClick()'});
return true;
};
MozMillElement.prototype.waitForElement = function(timeout, interval) {
var elem = this;
utils.waitFor(function() {
return elem.exists();
}, "Timeout exceeded for waitForElement " + this.getInfo(), timeout, interval);
frame.events.pass({'function':'MozMillElement.waitForElement()'});
};
MozMillElement.prototype.waitForElementNotPresent = function(timeout, interval) {
var elem = this;
utils.waitFor(function() {
return !elem.exists();
}, "Timeout exceeded for waitForElementNotPresent " + this.getInfo(), timeout, interval);
frame.events.pass({'function':'MozMillElement.waitForElementNotPresent()'});
};
MozMillElement.prototype.waitThenClick = function (timeout, interval, left, top, expectedEvent) {
this.waitForElement(timeout, interval);
this.click(left, top, expectedEvent);
};
// Dispatches an HTMLEvent
MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
canBubble = canBubble || true;
var evt = this.element.ownerDocument.createEvent('HTMLEvents');
evt.shiftKey = modifiers["shift"];
evt.metaKey = modifiers["meta"];
evt.altKey = modifiers["alt"];
evt.ctrlKey = modifiers["ctrl"];
evt.initEvent(eventType, canBubble, true);
this.element.dispatchEvent(evt);
};
//---------------------------------------------------------------------------------------------------------------------------------------
/**
* MozMillCheckBox
* Checkbox element, inherits from MozMillElement
*/
MozMillCheckBox.prototype = new MozMillElement();
MozMillCheckBox.prototype.parent = MozMillElement.prototype;
MozMillCheckBox.prototype.constructor = MozMillCheckBox;
function MozMillCheckBox(locatorType, locator, args) {
this.parent.constructor.call(this, locatorType, locator, args);
}
// Static method returns true if node is this type of element
MozMillCheckBox.isType = function(node) {
if ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
(node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
(node.localName.toLowerCase() == 'checkbox')) {
return true;
}
return false;
};
/**
* Enable/Disable a checkbox depending on the target state
*/
MozMillCheckBox.prototype.check = function(state) {
var result = false;
if (!this.element) {
throw new Error("could not find element " + this.getInfo());
return false;
}
// If we have a XUL element, unwrap its XPCNativeWrapper
if (this.element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
this.element = utils.unwrapNode(this.element);
}
state = (typeof(state) == "boolean") ? state : false;
if (state != this.element.checked) {
this.click();
var element = this.element;
utils.waitFor(function() {
return element.checked == state;
}, "Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
result = true;
}
frame.events.pass({'function':'MozMillCheckBox.check(' + this.getInfo() + ', state: ' + state + ')'});
return result;
};
//----------------------------------------------------------------------------------------------------------------------------------------
/**
* MozMillRadio
* Radio button inherits from MozMillElement
*/
MozMillRadio.prototype = new MozMillElement();
MozMillRadio.prototype.parent = MozMillElement.prototype;
MozMillRadio.prototype.constructor = MozMillRadio;
function MozMillRadio(locatorType, locator, args) {
this.parent.constructor.call(this, locatorType, locator, args);
}
// Static method returns true if node is this type of element
MozMillRadio.isType = function(node) {
if ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
(node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
(node.localName.toLowerCase() == 'radio') ||
(node.localName.toLowerCase() == 'radiogroup')) {
return true;
}
return false;
};
/**
* Select the given radio button
*
* index - Specifies which radio button in the group to select (only applicable to radiogroup elements)
* Defaults to the first radio button in the group
*/
MozMillRadio.prototype.select = function(index) {
if (!this.element) {
throw new Error("could not find element " + this.getInfo());
}
if (this.element.localName.toLowerCase() == "radiogroup") {
var element = this.element.getElementsByTagName("radio")[index || 0];
new MozMillRadio("Elem", element).click();
} else {
var element = this.element;
this.click();
}
utils.waitFor(function() {
// If we have a XUL element, unwrap its XPCNativeWrapper
if (element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
element = utils.unwrapNode(element);
return element.selected == true;
}
return element.checked == true;
}, "Radio button " + this.getInfo() + " could not be selected", 500);
frame.events.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
return true;
};
//----------------------------------------------------------------------------------------------------------------------------------------
/**
* MozMillDropList
* DropList inherits from MozMillElement
*/
MozMillDropList.prototype = new MozMillElement();
MozMillDropList.prototype.parent = MozMillElement.prototype;
MozMillDropList.prototype.constructor = MozMillDropList;
function MozMillDropList(locatorType, locator, args) {
this.parent.constructor.call(this, locatorType, locator, args);
};
// Static method returns true if node is this type of element
MozMillDropList.isType = function(node) {
if ((node.localName.toLowerCase() == 'toolbarbutton' && (node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
(node.localName.toLowerCase() == 'menu') ||
(node.localName.toLowerCase() == 'menulist') ||
(node.localName.toLowerCase() == 'select' )) {
return true;
}
return false;
};
/* Select the specified option and trigger the relevant events of the element */
MozMillDropList.prototype.select = function (indx, option, value) {
if (!this.element){
throw new Error("Could not find element " + this.getInfo());
}
//if we have a select drop down
if (this.element.localName.toLowerCase() == "select"){
var item = null;
// The selected item should be set via its index
if (indx != undefined) {
// Resetting a menulist has to be handled separately
if (indx == -1) {
this.dispatchEvent('focus', false);
this.element.selectedIndex = indx;
this.dispatchEvent('change', true);
frame.events.pass({'function':'MozMillDropList.select()'});
return true;
} else {
item = this.element.options.item(indx);
}
} else {
for (var i = 0; i < this.element.options.length; i++) {
var entry = this.element.options.item(i);
if (option != undefined && entry.innerHTML == option ||
value != undefined && entry.value == value) {
item = entry;
break;
}
}
}
// Click the item
try {
// EventUtils.synthesizeMouse doesn't work.
this.dispatchEvent('focus', false);
item.selected = true;
this.dispatchEvent('change', true);
frame.events.pass({'function':'MozMillDropList.select()'});
return true;
} catch (ex) {
throw new Error("No item selected for element " + this.getInfo());
return false;
}
}
//if we have a xul menupopup select accordingly
else if (this.element.namespaceURI.toLowerCase() == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
var ownerDoc = this.element.ownerDocument;
// Unwrap the XUL element's XPCNativeWrapper
this.element = utils.unwrapNode(this.element);
// Get the list of menuitems
menuitems = this.element.getElementsByTagName("menupopup")[0].getElementsByTagName("menuitem");
var item = null;
if (indx != undefined) {
if (indx == -1) {
this.dispatchEvent('focus', false);
this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild = null;
this.dispatchEvent('change', true);
frame.events.pass({'function':'MozMillDropList.select()'});
return true;
} else {
item = menuitems[indx];
}
} else {
for (var i = 0; i < menuitems.length; i++) {
var entry = menuitems[i];
if (option != undefined && entry.label == option ||
value != undefined && entry.value == value) {
item = entry;
break;
}
}
}
// Click the item
try {
EventUtils.synthesizeMouse(this.element, 1, 1, {}, ownerDoc.defaultView);
// Scroll down until item is visible
for (var i = 0; i <= menuitems.length; ++i) {
var selected = this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild;
if (item == selected) {
break;
}
EventUtils.synthesizeKey("VK_DOWN", {}, ownerDoc.defaultView);
}
EventUtils.synthesizeMouse(item, 1, 1, {}, ownerDoc.defaultView);
frame.events.pass({'function':'MozMillDropList.select()'});
return true;
} catch (ex) {
throw new Error('No item selected for element ' + this.getInfo());
return false;
}
}
};
//----------------------------------------------------------------------------------------------------------------------------------------
/**
* MozMillTextBox
* TextBox inherits from MozMillElement
*/
MozMillTextBox.prototype = new MozMillElement();
MozMillTextBox.prototype.parent = MozMillElement.prototype;
MozMillTextBox.prototype.constructor = MozMillTextBox;
function MozMillTextBox(locatorType, locator, args) {
this.parent.constructor.call(this, locatorType, locator, args);
};
// Static method returns true if node is this type of element
MozMillTextBox.isType = function(node) {
if ((node.localName.toLowerCase() == 'input' && (node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
(node.localName.toLowerCase() == 'textarea') ||
(node.localName.toLowerCase() == 'textbox')) {
return true;
}
return false;
};
/**
* Synthesize keypress events for each character on the given element
*
* @param {string} aText
* The text to send as single keypress events
* @param {object} aModifiers
* Information about the modifier keys to send
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
* [optional - default: false]
* altKey - Hold down the alt key
* [optional - default: false]
* ctrlKey - Hold down the ctrl key
* [optional - default: false]
* metaKey - Hold down the meta key (command key on Mac)
* [optional - default: false]
* shiftKey - Hold down the shift key
* [optional - default: false]
* @param {object} aExpectedEvent
* Information about the expected event to occur
* Elements: target - Element which should receive the event
* [optional - default: current element]
* type - Type of the expected key event
*/
MozMillTextBox.prototype.sendKeys = function (aText, aModifiers, aExpectedEvent) {
if (!this.element) {
throw new Error("could not find element " + this.getInfo());
}
var element = this.element;
Array.forEach(aText, function(letter) {
var win = element.ownerDocument? element.ownerDocument.defaultView : element;
element.focus();
if (aExpectedEvent) {
var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : element;
EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target, aExpectedEvent.type,
"MozMillTextBox.sendKeys()", win);
} else {
EventUtils.synthesizeKey(letter, aModifiers || {}, win);
}
});
frame.events.pass({'function':'MozMillTextBox.type()'});
return true;
};

Просмотреть файл

@ -0,0 +1,263 @@
// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Mikeal Rogers.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.com>
// Gary Kwong <nth10sd@gmail.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 *****
var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
"getBrowserController", "newBrowserController",
"getAddonsController", "getPreferencesController",
"newMail3PaneController", "getMail3PaneController",
"wm", "platform", "getAddrbkController",
"getMsgComposeController", "getDownloadsController",
"Application", "cleanQuit",
"getPlacesController", 'isMac', 'isLinux', 'isWindows',
"firePythonCallback"
];
// 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);
try {
Components.utils.import("resource://gre/modules/AddonManager.jsm");
} catch(e) { /* Firefox 4 only */ }
// 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 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);
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',
}
var Application = applicationDictionary[appInfo.ID];
if (Application == undefined) {
// Default to Firefox
var Application = 'Firefox';
}
// 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
}
} catch(e) {
startupInfo = null;
}
// 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')());
}
function getBrowserController () {
var browserWindow = wm.getMostRecentWindow("navigator:browser");
if (browserWindow == null) {
return newBrowserController();
}
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') {
utils.getMethodInWindows('openAddonsMgr')();
} 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(''));
}
function getPreferencesController() {
if (Application == 'Thunderbird') {
utils.getMethodInWindows('openOptionsDialog')();
} else {
utils.getMethodInWindows('openPreferences')();
}
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
// Thunderbird functions
function newMail3PaneController () {
return new controller.MozMillController(utils.getMethodInWindows('toMessengerWindow')());
}
function getMail3PaneController () {
var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
if (mail3PaneWindow == null) {
return newMail3PaneController();
}
else {
return new controller.MozMillController(mail3PaneWindow);
}
}
// Thunderbird - Address book window
function newAddrbkController () {
utils.getMethodInWindows("toAddressBook")();
utils.sleep(2000);
var addyWin = wm.getMostRecentWindow("mail:addressbook");
return new controller.MozMillController(addyWin);
}
function getAddrbkController () {
var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
if (addrbkWindow == null) {
return newAddrbkController();
}
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);
}
function timer (name) {
this.name = name;
this.timers = {};
frame.timers.push(this);
this.actions = [];
}
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);
}

Просмотреть файл

@ -0,0 +1,557 @@
// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Adam Christian.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Adam Christian <adam.christian@gmail.com>
// Mikeal Rogers <mikeal.rogers@gmail.com>
// Henrik Skupin <hskupin@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 *****
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;
}

Просмотреть файл

@ -0,0 +1,820 @@
// Export all available functions for Mozmill
var EXPORTED_SYMBOLS = ["sendMouseEvent", "sendChar", "sendString", "sendKey",
"synthesizeMouse", "synthesizeMouseScroll", "synthesizeKey",
"synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent",
"synthesizeDragStart", "synthesizeDrop", "synthesizeText",
"disableNonTestMouseEvents", "synthesizeComposition",
"synthesizeQuerySelectedText", "synthesizeQueryTextContent",
"synthesizeQueryCaretRect", "synthesizeQueryTextRect",
"synthesizeQueryEditorRect", "synthesizeCharAtPoint",
"synthesizeSelectionSet"];
/**
* Get the array with available key events
*/
function getKeyEvent(aWindow) {
var win = aWindow.wrappedJSObject ? aWindow.wrappedJSObject : aWindow;
return win.KeyEvent;
}
/**
* EventUtils provides some utility methods for creating and sending DOM events.
* Current methods:
* sendMouseEvent
* sendChar
* sendString
* sendKey
*/
/**
* Send a mouse event to the node aTarget (aTarget can be an id, or an
* actual node) . The "event" passed in to aEvent is just a JavaScript
* object with the properties set that the real mouse event object should
* have. This includes the type of the mouse event.
* E.g. to send an click event to the node with id 'node' you might do this:
*
* sendMouseEvent({type:'click'}, 'node');
*/
function sendMouseEvent(aEvent, aTarget, aWindow) {
if (['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
throw new Error("sendMouseEvent doesn't know about event type '"+aEvent.type+"'");
}
if (!aWindow) {
aWindow = window;
}
if (!(aTarget instanceof Element)) {
aTarget = aWindow.document.getElementById(aTarget);
}
var event = aWindow.document.createEvent('MouseEvent');
var typeArg = aEvent.type;
var canBubbleArg = true;
var cancelableArg = true;
var viewArg = aWindow;
var detailArg = aEvent.detail || (aEvent.type == 'click' ||
aEvent.type == 'mousedown' ||
aEvent.type == 'mouseup' ? 1 : 0);
var screenXArg = aEvent.screenX || 0;
var screenYArg = aEvent.screenY || 0;
var clientXArg = aEvent.clientX || 0;
var clientYArg = aEvent.clientY || 0;
var ctrlKeyArg = aEvent.ctrlKey || false;
var altKeyArg = aEvent.altKey || false;
var shiftKeyArg = aEvent.shiftKey || false;
var metaKeyArg = aEvent.metaKey || false;
var buttonArg = aEvent.button || 0;
var relatedTargetArg = aEvent.relatedTarget || null;
event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
screenXArg, screenYArg, clientXArg, clientYArg,
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
buttonArg, relatedTargetArg);
aTarget.dispatchEvent(event);
}
/**
* Send the char aChar to the node with id aTarget. If aTarget is not
* provided, use "target". This method handles casing of chars (sends the
* right charcode, and sends a shift key for uppercase chars). No other
* modifiers are handled at this point.
*
* For now this method only works for English letters (lower and upper case)
* and the digits 0-9.
*
* Returns true if the keypress event was accepted (no calls to preventDefault
* or anything like that), false otherwise.
*/
function sendChar(aChar, aTarget) {
// DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9.
var hasShift = (aChar == aChar.toUpperCase());
var charCode = aChar.charCodeAt(0);
var keyCode = charCode;
if (!hasShift) {
// For lowercase letters, the keyCode is actually 32 less than the charCode
keyCode -= 0x20;
}
return __doEventDispatch(aTarget, charCode, keyCode, hasShift);
}
/**
* Send the string aStr to the node with id aTarget. If aTarget is not
* provided, use "target".
*
* For now this method only works for English letters (lower and upper case)
* and the digits 0-9.
*/
function sendString(aStr, aTarget) {
for (var i = 0; i < aStr.length; ++i) {
sendChar(aStr.charAt(i), aTarget);
}
}
/**
* Send the non-character key aKey to the node with id aTarget. If aTarget is
* not provided, use "target". The name of the key should be a lowercase
* version of the part that comes after "DOM_VK_" in the KeyEvent constant
* name for this key. No modifiers are handled at this point.
*
* Returns true if the keypress event was accepted (no calls to preventDefault
* or anything like that), false otherwise.
*/
function sendKey(aKey, aTarget, aWindow) {
if (!aWindow)
aWindow = window;
keyName = "DOM_VK_" + aKey.toUpperCase();
if (!getKeyEvent(aWindow)[keyName]) {
throw "Unknown key: " + keyName;
}
return __doEventDispatch(aTarget, 0, getKeyEvent(aWindow)[keyName], false);
}
/**
* Actually perform event dispatch given a charCode, keyCode, and boolean for
* whether "shift" was pressed. Send the event to the node with id aTarget. If
* aTarget is not provided, use "target".
*
* Returns true if the keypress event was accepted (no calls to preventDefault
* or anything like that), false otherwise.
*/
function __doEventDispatch(aTarget, aCharCode, aKeyCode, aHasShift) {
if (aTarget === undefined) {
aTarget = "target";
}
var event = document.createEvent("KeyEvents");
event.initKeyEvent("keydown", true, true, document.defaultView,
false, false, aHasShift, false,
aKeyCode, 0);
var accepted = $(aTarget).dispatchEvent(event);
// Preventing the default keydown action also prevents the default
// keypress action.
event = document.createEvent("KeyEvents");
if (aCharCode) {
event.initKeyEvent("keypress", true, true, document.defaultView,
false, false, aHasShift, false,
0, aCharCode);
} else {
event.initKeyEvent("keypress", true, true, document.defaultView,
false, false, aHasShift, false,
aKeyCode, 0);
}
if (!accepted) {
event.preventDefault();
}
accepted = $(aTarget).dispatchEvent(event);
// Always send keyup
var event = document.createEvent("KeyEvents");
event.initKeyEvent("keyup", true, true, document.defaultView,
false, false, aHasShift, false,
aKeyCode, 0);
$(aTarget).dispatchEvent(event);
return accepted;
}
/**
* Parse the key modifier flags from aEvent. Used to share code between
* synthesizeMouse and synthesizeKey.
*/
function _parseModifiers(aEvent)
{
var hwindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
.getService(Components.interfaces.nsIAppShellService)
.hiddenDOMWindow;
const masks = Components.interfaces.nsIDOMNSEvent;
var mval = 0;
if (aEvent.shiftKey)
mval |= masks.SHIFT_MASK;
if (aEvent.ctrlKey)
mval |= masks.CONTROL_MASK;
if (aEvent.altKey)
mval |= masks.ALT_MASK;
if (aEvent.metaKey)
mval |= masks.META_MASK;
if (aEvent.accelKey)
mval |= (hwindow.navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK :
masks.CONTROL_MASK;
return mval;
}
/**
* Synthesize a mouse event on a target. The actual client point is determined
* by taking the aTarget's client box and offseting it by aOffsetX and
* aOffsetY. This allows mouse clicks to be simulated by calling this method.
*
* aEvent is an object which may contain the properties:
* shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
*
* If the type is specified, an mouse event of that type is fired. Otherwise,
* a mousedown followed by a mouse up is performed.
*
* aWindow is optional, and defaults to the current window object.
*/
function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
{
if (!aWindow)
aWindow = window;
var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
if (utils) {
var button = aEvent.button || 0;
var clickCount = aEvent.clickCount || 1;
var modifiers = _parseModifiers(aEvent);
var rect = aTarget.getBoundingClientRect();
var left = rect.left + aOffsetX;
var top = rect.top + aOffsetY;
if (aEvent.type) {
utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers);
}
else {
utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers);
utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers);
}
}
}
/**
* Synthesize a mouse scroll event on a target. The actual client point is determined
* by taking the aTarget's client box and offseting it by aOffsetX and
* aOffsetY.
*
* aEvent is an object which may contain the properties:
* shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels
*
* If the type is specified, a mouse scroll event of that type is fired. Otherwise,
* "DOMMouseScroll" is used.
*
* If the axis is specified, it must be one of "horizontal" or "vertical". If not specified,
* "vertical" is used.
*
* 'delta' is the amount to scroll by (can be positive or negative). It must
* be specified.
*
* 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags.
*
* aWindow is optional, and defaults to the current window object.
*/
function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
{
if (!aWindow)
aWindow = window;
var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
if (utils) {
// See nsMouseScrollFlags in nsGUIEvent.h
const kIsVertical = 0x02;
const kIsHorizontal = 0x04;
const kHasPixels = 0x08;
var button = aEvent.button || 0;
var modifiers = _parseModifiers(aEvent);
var rect = aTarget.getBoundingClientRect();
var left = rect.left;
var top = rect.top;
var type = aEvent.type || "DOMMouseScroll";
var axis = aEvent.axis || "vertical";
var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
if (aEvent.hasPixels) {
scrollFlags |= kHasPixels;
}
utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button,
scrollFlags, aEvent.delta, modifiers);
}
}
/**
* Synthesize a key event. It is targeted at whatever would be targeted by an
* actual keypress by the user, typically the focused element.
*
* aKey should be either a character or a keycode starting with VK_ such as
* VK_ENTER.
*
* aEvent is an object which may contain the properties:
* shiftKey, ctrlKey, altKey, metaKey, accessKey, type
*
* If the type is specified, a key event of that type is fired. Otherwise,
* a keydown, a keypress and then a keyup event are fired in sequence.
*
* aWindow is optional, and defaults to the current window object.
*/
function synthesizeKey(aKey, aEvent, aWindow)
{
if (!aWindow)
aWindow = window;
var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
if (utils) {
var keyCode = 0, charCode = 0;
if (aKey.indexOf("VK_") == 0)
keyCode = getKeyEvent(aWindow)["DOM_" + aKey];
else
charCode = aKey.charCodeAt(0);
var modifiers = _parseModifiers(aEvent);
if (aEvent.type) {
utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers);
}
else {
var keyDownDefaultHappened =
utils.sendKeyEvent("keydown", keyCode, charCode, modifiers);
utils.sendKeyEvent("keypress", keyCode, charCode, modifiers,
!keyDownDefaultHappened);
utils.sendKeyEvent("keyup", keyCode, charCode, modifiers);
}
}
}
var _gSeenEvent = false;
/**
* Indicate that an event with an original target of aExpectedTarget and
* a type of aExpectedEvent is expected to be fired, or not expected to
* be fired.
*/
function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
{
if (!aExpectedTarget || !aExpectedEvent)
return null;
_gSeenEvent = false;
var type = (aExpectedEvent.charAt(0) == "!") ?
aExpectedEvent.substring(1) : aExpectedEvent;
var eventHandler = function(event) {
var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
event.type == type);
if (!epassed)
throw new Error(aTestName + " " + type + " event target " +
(_gSeenEvent ? "twice" : ""));
_gSeenEvent = true;
};
aExpectedTarget.addEventListener(type, eventHandler, false);
return eventHandler;
}
/**
* Check if the event was fired or not. The event handler aEventHandler
* will be removed.
*/
function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName)
{
if (aEventHandler) {
var expectEvent = (aExpectedEvent.charAt(0) != "!");
var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
aExpectedTarget.removeEventListener(type, aEventHandler, false);
var desc = type + " event";
if (expectEvent)
desc += " not";
if (_gSeenEvent != expectEvent)
throw new Error(aTestName + ": " + desc + " fired.");
}
_gSeenEvent = false;
}
/**
* Similar to synthesizeMouse except that a test is performed to see if an
* event is fired at the right target as a result.
*
* aExpectedTarget - the expected originalTarget of the event.
* aExpectedEvent - the expected type of the event, such as 'select'.
* aTestName - the test name when outputing results
*
* To test that an event is not fired, use an expected type preceded by an
* exclamation mark, such as '!select'. This might be used to test that a
* click on a disabled element doesn't fire certain events for instance.
*
* aWindow is optional, and defaults to the current window object.
*/
function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent,
aExpectedTarget, aExpectedEvent, aTestName,
aWindow)
{
var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
_checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
}
/**
* Similar to synthesizeKey except that a test is performed to see if an
* event is fired at the right target as a result.
*
* aExpectedTarget - the expected originalTarget of the event.
* aExpectedEvent - the expected type of the event, such as 'select'.
* aTestName - the test name when outputing results
*
* To test that an event is not fired, use an expected type preceded by an
* exclamation mark, such as '!select'.
*
* aWindow is optional, and defaults to the current window object.
*/
function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
aTestName, aWindow)
{
var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
synthesizeKey(key, aEvent, aWindow);
_checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
}
/**
* Emulate a dragstart event.
* element - element to fire the dragstart event on
* expectedDragData - the data you expect the data transfer to contain afterwards
* This data is in the format:
* [ [ {type: value, data: value, test: function}, ... ], ... ]
* can be null
* aWindow - optional; defaults to the current window object.
* x - optional; initial x coordinate
* y - optional; initial y coordinate
* Returns null if data matches.
* Returns the event.dataTransfer if data does not match
*
* eqTest is an optional function if comparison can't be done with x == y;
* function (actualData, expectedData) {return boolean}
* @param actualData from dataTransfer
* @param expectedData from expectedDragData
* see bug 462172 for example of use
*
*/
function synthesizeDragStart(element, expectedDragData, aWindow, x, y)
{
if (!aWindow)
aWindow = window;
x = x || 2;
y = y || 2;
const step = 9;
var result = "trapDrag was not called";
var trapDrag = function(event) {
try {
var dataTransfer = event.dataTransfer;
result = null;
if (!dataTransfer)
throw "no dataTransfer";
if (expectedDragData == null ||
dataTransfer.mozItemCount != expectedDragData.length)
throw dataTransfer;
for (var i = 0; i < dataTransfer.mozItemCount; i++) {
var dtTypes = dataTransfer.mozTypesAt(i);
if (dtTypes.length != expectedDragData[i].length)
throw dataTransfer;
for (var j = 0; j < dtTypes.length; j++) {
if (dtTypes[j] != expectedDragData[i][j].type)
throw dataTransfer;
var dtData = dataTransfer.mozGetDataAt(dtTypes[j],i);
if (expectedDragData[i][j].eqTest) {
if (!expectedDragData[i][j].eqTest(dtData, expectedDragData[i][j].data))
throw dataTransfer;
}
else if (expectedDragData[i][j].data != dtData)
throw dataTransfer;
}
}
} catch(ex) {
result = ex;
}
event.preventDefault();
event.stopPropagation();
}
aWindow.addEventListener("dragstart", trapDrag, false);
synthesizeMouse(element, x, y, { type: "mousedown" }, aWindow);
x += step; y += step;
synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
x += step; y += step;
synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
aWindow.removeEventListener("dragstart", trapDrag, false);
synthesizeMouse(element, x, y, { type: "mouseup" }, aWindow);
return result;
}
/**
* Emulate a drop by emulating a dragstart and firing events dragenter, dragover, and drop.
* srcElement - the element to use to start the drag, usually the same as destElement
* but if destElement isn't suitable to start a drag on pass a suitable
* element for srcElement
* destElement - the element to fire the dragover, dragleave and drop events
* dragData - the data to supply for the data transfer
* This data is in the format:
* [ [ {type: value, data: value}, ...], ... ]
* dropEffect - the drop effect to set during the dragstart event, or 'move' if null
* aWindow - optional; defaults to the current window object.
*
* Returns the drop effect that was desired.
*/
function synthesizeDrop(srcElement, destElement, dragData, dropEffect, aWindow)
{
if (!aWindow)
aWindow = window;
var dataTransfer;
var trapDrag = function(event) {
dataTransfer = event.dataTransfer;
for (var i = 0; i < dragData.length; i++) {
var item = dragData[i];
for (var j = 0; j < item.length; j++) {
dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
}
}
dataTransfer.dropEffect = dropEffect || "move";
event.preventDefault();
event.stopPropagation();
}
// need to use real mouse action
aWindow.addEventListener("dragstart", trapDrag, true);
synthesizeMouse(srcElement, 2, 2, { type: "mousedown" }, aWindow);
synthesizeMouse(srcElement, 11, 11, { type: "mousemove" }, aWindow);
synthesizeMouse(srcElement, 20, 20, { type: "mousemove" }, aWindow);
aWindow.removeEventListener("dragstart", trapDrag, true);
event = aWindow.document.createEvent("DragEvents");
event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
destElement.dispatchEvent(event);
var event = aWindow.document.createEvent("DragEvents");
event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
if (destElement.dispatchEvent(event)) {
synthesizeMouse(destElement, 20, 20, { type: "mouseup" }, aWindow);
return "none";
}
if (dataTransfer.dropEffect != "none") {
event = aWindow.document.createEvent("DragEvents");
event.initDragEvent("drop", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
destElement.dispatchEvent(event);
}
synthesizeMouse(destElement, 20, 20, { type: "mouseup" }, aWindow);
return dataTransfer.dropEffect;
}
function disableNonTestMouseEvents(aDisable)
{
var utils =
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
if (utils)
utils.disableNonTestMouseEvents(aDisable);
}
function _getDOMWindowUtils(aWindow)
{
if (!aWindow) {
aWindow = window;
}
return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
}
/**
* Synthesize a composition event.
*
* @param aIsCompositionStart If true, this synthesize compositionstart event.
* Otherwise, compositionend event.
* @param aWindow Optional (If null, current |window| will be used)
*/
function synthesizeComposition(aIsCompositionStart, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return;
}
utils.sendCompositionEvent(aIsCompositionStart ?
"compositionstart" : "compositionend");
}
/**
* Synthesize a text event.
*
* @param aEvent The text event's information, this has |composition|
* and |caret| members. |composition| has |string| and
* |clauses| members. |clauses| must be array object. Each
* object has |length| and |attr|. And |caret| has |start| and
* |length|. See the following tree image.
*
* aEvent
* +-- composition
* | +-- string
* | +-- clauses[]
* | +-- length
* | +-- attr
* +-- caret
* +-- start
* +-- length
*
* Set the composition string to |composition.string|. Set its
* clauses information to the |clauses| array.
*
* When it's composing, set the each clauses' length to the
* |composition.clauses[n].length|. The sum of the all length
* values must be same as the length of |composition.string|.
* Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
* |composition.clauses[n].attr|.
*
* When it's not composing, set 0 to the
* |composition.clauses[0].length| and
* |composition.clauses[0].attr|.
*
* Set caret position to the |caret.start|. It's offset from
* the start of the composition string. Set caret length to
* |caret.length|. If it's larger than 0, it should be wide
* caret. However, current nsEditor doesn't support wide
* caret, therefore, you should always set 0 now.
*
* @param aWindow Optional (If null, current |window| will be used)
*/
function synthesizeText(aEvent, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return;
}
if (!aEvent.composition || !aEvent.composition.clauses ||
!aEvent.composition.clauses[0]) {
return;
}
var firstClauseLength = aEvent.composition.clauses[0].length;
var firstClauseAttr = aEvent.composition.clauses[0].attr;
var secondClauseLength = 0;
var secondClauseAttr = 0;
var thirdClauseLength = 0;
var thirdClauseAttr = 0;
if (aEvent.composition.clauses[1]) {
secondClauseLength = aEvent.composition.clauses[1].length;
secondClauseAttr = aEvent.composition.clauses[1].attr;
if (aEvent.composition.clauses[2]) {
thirdClauseLength = aEvent.composition.clauses[2].length;
thirdClauseAttr = aEvent.composition.clauses[2].attr;
}
}
var caretStart = -1;
var caretLength = 0;
if (aEvent.caret) {
caretStart = aEvent.caret.start;
caretLength = aEvent.caret.length;
}
utils.sendTextEvent(aEvent.composition.string,
firstClauseLength, firstClauseAttr,
secondClauseLength, secondClauseAttr,
thirdClauseLength, thirdClauseAttr,
caretStart, caretLength);
}
/**
* Synthesize a query selected text event.
*
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQuerySelectedText(aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
}
/**
* Synthesize a query text content event.
*
* @param aOffset The character offset. 0 means the first character in the
* selection root.
* @param aLength The length of getting text. If the length is too long,
* the extra length is ignored.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryTextContent(aOffset, aLength, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT,
aOffset, aLength, 0, 0);
}
/**
* Synthesize a query caret rect event.
*
* @param aOffset The caret offset. 0 means left side of the first character
* in the selection root.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryCaretRect(aOffset, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT,
aOffset, 0, 0, 0);
}
/**
* Synthesize a query text rect event.
*
* @param aOffset The character offset. 0 means the first character in the
* selection root.
* @param aLength The length of the text. If the length is too long,
* the extra length is ignored.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryTextRect(aOffset, aLength, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT,
aOffset, aLength, 0, 0);
}
/**
* Synthesize a query editor rect event.
*
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryEditorRect(aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0);
}
/**
* Synthesize a character at point event.
*
* @param aX, aY The offset in the client area of the DOM window.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeCharAtPoint(aX, aY, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT,
0, 0, aX, aY);
}
/**
* Synthesize a selection set event.
*
* @param aOffset The character offset. 0 means the first character in the
* selection root.
* @param aLength The length of the text. If the length is too long,
* the extra length is ignored.
* @param aReverse If true, the selection is from |aOffset + aLength| to
* |aOffset|. Otherwise, from |aOffset| to |aOffset + aLength|.
* @param aWindow Optional (If null, current |window| will be used)
* @return True, if succeeded. Otherwise false.
*/
function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return false;
}
return utils.sendSelectionSetEvent(aOffset, aLength, aReverse);
}

Просмотреть файл

@ -0,0 +1,93 @@
// ***** BEGIN LICENSE BLOCK *****// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Mikeal Rogers.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.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 *****
var EXPORTED_SYMBOLS = ['inArray', 'getSet', 'indexOf', 'rindexOf', 'compare'];
function inArray (array, value) {
for (i in array) {
if (value == array[i]) {
return true;
}
}
return false;
}
function getSet (array) {
var narray = [];
for (i in array) {
if ( !inArray(narray, array[i]) ) {
narray.push(array[i]);
}
}
return narray;
}
function indexOf (array, v, offset) {
for (i in array) {
if (offset == undefined || i >= offset) {
if ( !isNaN(i) && array[i] == v) {
return new Number(i);
}
}
}
return -1;
}
function rindexOf (array, v) {
var l = array.length;
for (i in array) {
if (!isNaN(i)) {
var i = new Number(i)
}
if (!isNaN(i) && array[l - i] == v) {
return l - i;
}
}
return -1;
}
function compare (array, carray) {
if (array.length != carray.length) {
return false;
}
for (i in array) {
if (array[i] != carray[i]) {
return false;
}
}
return true;
}

Просмотреть файл

@ -0,0 +1,54 @@
// ***** BEGIN LICENSE BLOCK *****// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Mikeal Rogers.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.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 *****
var EXPORTED_SYMBOLS = ['getAttributes'];
var getAttributes = function (node) {
var attributes = {};
for (i in node.attributes) {
if ( !isNaN(i) ) {
try {
var attr = node.attributes[i];
attributes[attr.name] = attr.value;
} catch (err) {
}
}
}
return attributes;
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,469 @@
/*
http://www.JSON.org/json2.js
2008-05-25
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects without a toJSON
method. It can be a function or an array.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the object holding the key.
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array, then it will be used to
select the members to be serialized. It filters the results such
that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
*/
/*jslint evil: true */
/*global JSON */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
test, toJSON, toString
*/
var EXPORTED_SYMBOLS = ["JSON"];
// Create a JSON object only if one does not already exist. We create the
// object in a closure to avoid creating global variables.
JSON = function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
Date.prototype.toJSON = function (key) {
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapeable.lastIndex = 0;
return escapeable.test(string) ?
'"' + string.replace(escapeable, function (a) {
var c = meta[a];
if (typeof c === 'string') {
return c;
}
return '\\u' + ('0000' +
(+(a.charCodeAt(0))).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// If the object has a dontEnum length property, we'll treat it as an array.
if (typeof value.length === 'number' &&
!(value.propertyIsEnumerable('length'))) {
// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value, rep);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value, rep);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// Return the JSON object containing the stringify and parse methods.
return {
stringify: function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
},
parse: function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' + ('0000' +
(+(a.charCodeAt(0))).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
}
};
}();

Просмотреть файл

@ -0,0 +1,86 @@
// ***** BEGIN LICENSE BLOCK *****// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Mikeal Rogers.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.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 *****
var EXPORTED_SYMBOLS = ['getLength', ];//'compare'];
var getLength = function (obj) {
var len = 0;
for (i in obj) {
len++;
}
return len;
}
// var logging = {}; Components.utils.import('resource://mozmill/stdlib/logging.js', logging);
// var objectsLogger = logging.getLogger('objectsLogger');
// var compare = function (obj1, obj2, depth, recursion) {
// if (depth == undefined) {
// var depth = 4;
// }
// if (recursion == undefined) {
// var recursion = 0;
// }
//
// if (recursion > depth) {
// return true;
// }
//
// if (typeof(obj1) != typeof(obj2)) {
// return false;
// }
//
// if (typeof(obj1) == "object" && typeof(obj2) == "object") {
// if ([x for (x in obj1)].length != [x for (x in obj2)].length) {
// return false;
// }
// for (i in obj1) {
// recursion++;
// var result = compare(obj1[i], obj2[i], depth, recursion);
// objectsLogger.info(i+' in recursion '+result);
// if (result == false) {
// return false;
// }
// }
// } else {
// if (obj1 != obj2) {
// return false;
// }
// }
// return true;
// }

Просмотреть файл

@ -0,0 +1,86 @@
// ***** BEGIN LICENSE BLOCK *****// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Mikeal Rogers.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.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 *****
var EXPORTED_SYMBOLS = ['listDirectory', 'getFileForPath', 'abspath', 'getPlatform'];
function listDirectory (file) {
// file is the given directory (nsIFile)
var entries = file.directoryEntries;
var array = [];
while (entries.hasMoreElements())
{
var entry = entries.getNext();
entry.QueryInterface(Components.interfaces.nsIFile);
array.push(entry);
}
return array;
}
function getFileForPath (path) {
var file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(path);
return file;
}
function abspath (rel, file) {
var relSplit = rel.split('/');
if (relSplit[0] == '..' && !file.isDirectory()) {
file = file.parent;
}
for each(p in relSplit) {
if (p == '..') {
file = file.parent;
} else if (p == '.'){
if (!file.isDirectory()) {
file = file.parent;
}
} else {
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;
}

Просмотреть файл

@ -0,0 +1,360 @@
/* ***** 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;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
var exports = {};
var ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
.createInstance(Ci.nsIPrincipal);
function resolvePrincipal(principal, defaultPrincipal) {
if (principal === undefined)
return defaultPrincipal;
if (principal == "system")
return systemPrincipal;
return principal;
}
// The base URI to we use when we're given relative URLs, if any.
var baseURI = null;
if (global.window)
baseURI = ios.newURI(global.location.href, null, null);
exports.baseURI = baseURI;
// The "parent" chrome URI to use if we're loading code that
// needs chrome privileges but may not have a filename that
// matches any of SpiderMonkey's defined system filename prefixes.
// The latter is needed so that wrappers can be automatically
// made for the code. For more information on this, see
// bug 418356:
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=418356
var parentChromeURIString;
if (baseURI)
// We're being loaded from a chrome-privileged document, so
// use its URL as the parent string.
parentChromeURIString = baseURI.spec;
else
// We're being loaded from a chrome-privileged JS module or
// SecurableModule, so use its filename (which may itself
// contain a reference to a parent).
parentChromeURIString = Components.stack.filename;
function maybeParentifyFilename(filename) {
var doParentifyFilename = true;
try {
// TODO: Ideally we should just make
// nsIChromeRegistry.wrappersEnabled() available from script
// and use it here. Until that's in the platform, though,
// we'll play it safe and parentify the filename unless
// we're absolutely certain things will be ok if we don't.
var filenameURI = ios.newURI(options.filename,
null,
baseURI);
if (filenameURI.scheme == 'chrome' &&
filenameURI.path.indexOf('/content/') == 0)
// Content packages will always have wrappers made for them;
// if automatic wrappers have been disabled for the
// chrome package via a chrome manifest flag, then
// this still works too, to the extent that the
// content package is insecure anyways.
doParentifyFilename = false;
} catch (e) {}
if (doParentifyFilename)
return parentChromeURIString + " -> " + filename;
return filename;
}
function getRootDir(urlStr) {
// TODO: This feels hacky, and like there will be edge cases.
return urlStr.slice(0, urlStr.lastIndexOf("/") + 1);
}
exports.SandboxFactory = function SandboxFactory(defaultPrincipal) {
// Unless specified otherwise, use a principal with limited
// privileges.
this._defaultPrincipal = resolvePrincipal(defaultPrincipal,
"http://www.mozilla.org");
},
exports.SandboxFactory.prototype = {
createSandbox: function createSandbox(options) {
var principal = resolvePrincipal(options.principal,
this._defaultPrincipal);
return {
_sandbox: new Cu.Sandbox(principal),
_principal: principal,
get globalScope() {
return this._sandbox;
},
defineProperty: function defineProperty(name, value) {
this._sandbox[name] = value;
},
getProperty: function getProperty(name) {
return this._sandbox[name];
},
evaluate: function evaluate(options) {
if (typeof(options) == 'string')
options = {contents: options};
options = {__proto__: options};
if (typeof(options.contents) != 'string')
throw new Error('Expected string for options.contents');
if (options.lineNo === undefined)
options.lineNo = 1;
if (options.jsVersion === undefined)
options.jsVersion = "1.8";
if (typeof(options.filename) != 'string')
options.filename = '<string>';
if (this._principal == systemPrincipal)
options.filename = maybeParentifyFilename(options.filename);
return Cu.evalInSandbox(options.contents,
this._sandbox,
options.jsVersion,
options.filename,
options.lineNo);
}
};
}
};
exports.Loader = function Loader(options) {
options = {__proto__: options};
if (options.fs === undefined) {
var rootPaths = options.rootPath || options.rootPaths;
if (rootPaths) {
if (rootPaths.constructor.name != "Array")
rootPaths = [rootPaths];
var fses = [new exports.LocalFileSystem(path)
for each (path in rootPaths)];
options.fs = new exports.CompositeFileSystem(fses);
} else
options.fs = new exports.LocalFileSystem();
}
if (options.sandboxFactory === undefined)
options.sandboxFactory = new exports.SandboxFactory(
options.defaultPrincipal
);
if (options.modules === undefined)
options.modules = {};
if (options.globals === undefined)
options.globals = {};
this.fs = options.fs;
this.sandboxFactory = options.sandboxFactory;
this.sandboxes = {};
this.modules = options.modules;
this.globals = options.globals;
};
exports.Loader.prototype = {
_makeRequire: function _makeRequire(rootDir) {
var self = this;
return function require(module) {
if (module == "chrome") {
var chrome = { Cc: Components.classes,
Ci: Components.interfaces,
Cu: Components.utils,
Cr: Components.results,
Cm: Components.manager,
components: Components
};
return chrome;
}
var path = self.fs.resolveModule(rootDir, module);
if (!path)
throw new Error('Module "' + module + '" not found');
if (!(path in self.modules)) {
var options = self.fs.getFile(path);
if (options.filename === undefined)
options.filename = path;
var exports = {};
var sandbox = self.sandboxFactory.createSandbox(options);
self.sandboxes[path] = sandbox;
for (name in self.globals)
sandbox.defineProperty(name, self.globals[name]);
sandbox.defineProperty('require', self._makeRequire(path));
sandbox.evaluate("var exports = {};");
let ES5 = self.modules.es5;
if (ES5) {
let { Object, Array, Function } = sandbox.globalScope;
ES5.init(Object, Array, Function);
}
self.modules[path] = sandbox.getProperty("exports");
sandbox.evaluate(options);
}
return self.modules[path];
};
},
// This is only really used by unit tests and other
// development-related facilities, allowing access to symbols
// defined in the global scope of a module.
findSandboxForModule: function findSandboxForModule(module) {
var path = this.fs.resolveModule(null, module);
if (!path)
throw new Error('Module "' + module + '" not found');
if (!(path in this.sandboxes))
this.require(module);
if (!(path in this.sandboxes))
throw new Error('Internal error: path not in sandboxes: ' +
path);
return this.sandboxes[path];
},
require: function require(module) {
return (this._makeRequire(null))(module);
},
runScript: function runScript(options, extraOutput) {
if (typeof(options) == 'string')
options = {contents: options};
options = {__proto__: options};
var sandbox = this.sandboxFactory.createSandbox(options);
if (extraOutput)
extraOutput.sandbox = sandbox;
for (name in this.globals)
sandbox.defineProperty(name, this.globals[name]);
sandbox.defineProperty('require', this._makeRequire(null));
return sandbox.evaluate(options);
}
};
exports.CompositeFileSystem = function CompositeFileSystem(fses) {
this.fses = fses;
this._pathMap = {};
};
exports.CompositeFileSystem.prototype = {
resolveModule: function resolveModule(base, path) {
for (var i = 0; i < this.fses.length; i++) {
var fs = this.fses[i];
var absPath = fs.resolveModule(base, path);
if (absPath) {
this._pathMap[absPath] = fs;
return absPath;
}
}
return null;
},
getFile: function getFile(path) {
return this._pathMap[path].getFile(path);
}
};
exports.LocalFileSystem = function LocalFileSystem(root) {
if (root === undefined) {
if (!baseURI)
throw new Error("Need a root path for module filesystem");
root = baseURI;
}
if (typeof(root) == 'string')
root = ios.newURI(root, null, baseURI);
if (root instanceof Ci.nsIFile)
root = ios.newFileURI(root);
if (!(root instanceof Ci.nsIURI))
throw new Error('Expected nsIFile, nsIURI, or string for root');
this.root = root.spec;
this._rootURI = root;
this._rootURIDir = getRootDir(root.spec);
};
exports.LocalFileSystem.prototype = {
resolveModule: function resolveModule(base, path) {
path = path + ".js";
var baseURI;
if (!base)
baseURI = this._rootURI;
else
baseURI = ios.newURI(base, null, null);
var newURI = ios.newURI(path, null, baseURI);
var channel = ios.newChannelFromURI(newURI);
try {
channel.open().close();
} catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
return null;
}
return newURI.spec;
},
getFile: function getFile(path) {
var channel = ios.newChannel(path, null, null);
var iStream = channel.open();
var ciStream = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
var bufLen = 0x8000;
ciStream.init(iStream, "UTF-8", bufLen,
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var chunk = {};
var data = "";
while (ciStream.readString(bufLen, chunk) > 0)
data += chunk.value;
ciStream.close();
iStream.close();
return {contents: data};
}
};
if (global.window) {
// We're being loaded in a chrome window, or a web page with
// UniversalXPConnect privileges.
global.SecurableModule = exports;
} else if (global.exports) {
// We're being loaded in a SecurableModule.
for (name in exports) {
global.exports[name] = exports[name];
}
} else {
// We're being loaded in a JS module.
global.EXPORTED_SYMBOLS = [];
for (name in exports) {
global.EXPORTED_SYMBOLS.push(name);
global[name] = exports[name];
}
}
})(this);

Просмотреть файл

@ -0,0 +1,50 @@
// ***** BEGIN LICENSE BLOCK *****// ***** 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 Mozilla Corporation Code.
//
// The Initial Developer of the Original Code is
// Mikeal Rogers.
// Portions created by the Initial Developer are Copyright (C) 2008
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Mikeal Rogers <mikeal.rogers@gmail.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 *****
var EXPORTED_SYMBOLS = ['trim', 'vslice'];
var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
var trim = function (str) {
return (str.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, ""));
}
var vslice = function (str, svalue, evalue) {
var sindex = arrays.indexOf(str, svalue);
var eindex = arrays.rindexOf(str, evalue);
return str.slice(sindex + 1, eindex);
}

Просмотреть файл

@ -0,0 +1,146 @@
/*
Copyright (c) 2006 Lawrence Oluyede <l.oluyede@gmail.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 OR COPYRIGHT HOLDERS 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.
*/
/*
startsWith(str, prefix[, start[, end]]) -> bool
Return true if str ends with the specified prefix, false otherwise.
With optional start, test str beginning at that position.
With optional end, stop comparing str at that position.
prefix can also be an array of strings to try.
*/
var EXPORTED_SYMBOLS = ['startsWith', 'endsWith'];
function startsWith(str, prefix, start, end) {
if (arguments.length < 2) {
throw new TypeError('startsWith() requires at least 2 arguments');
}
// check if start and end are null/undefined or a 'number'
if ((start == null) || (isNaN(new Number(start)))) {
start = 0;
}
if ((end == null) || (isNaN(new Number(end)))) {
end = Number.MAX_VALUE;
}
// if it's an array
if (typeof prefix == "object") {
for (var i = 0, j = prefix.length; i < j; i++) {
var res = _stringTailMatch(str, prefix[i], start, end, true);
if (res) {
return true;
}
}
return false;
}
return _stringTailMatch(str, prefix, start, end, true);
}
/*
endsWith(str, suffix[, start[, end]]) -> bool
Return true if str ends with the specified suffix, false otherwise.
With optional start, test str beginning at that position.
With optional end, stop comparing str at that position.
suffix can also be an array of strings to try.
*/
function endsWith(str, suffix, start, end) {
if (arguments.length < 2) {
throw new TypeError('endsWith() requires at least 2 arguments');
}
// check if start and end are null/undefined or a 'number'
if ((start == null) || (isNaN(new Number(start)))) {
start = 0;
}
if ((end == null) || (isNaN(new Number(end)))) {
end = Number.MAX_VALUE;
}
// if it's an array
if (typeof suffix == "object") {
for (var i = 0, j = suffix.length; i < j; i++) {
var res = _stringTailMatch(str, suffix[i], start, end, false);
if (res) {
return true;
}
}
return false;
}
return _stringTailMatch(str, suffix, start, end, false);
}
/*
Matches the end (direction == false) or start (direction == true) of str
against substr, using the start and end arguments. Returns false
if not found and true if found.
*/
function _stringTailMatch(str, substr, start, end, fromStart) {
var len = str.length;
var slen = substr.length;
var indices = _adjustIndices(start, end, len);
start = indices[0]; end = indices[1]; len = indices[2];
if (fromStart) {
if (start + slen > len) {
return false;
}
} else {
if (end - start < slen || start > len) {
return false;
}
if (end - slen > start) {
start = end - slen;
}
}
if (end - start >= slen) {
return str.substr(start, slen) == substr;
}
return false;
}
function _adjustIndices(start, end, len)
{
if (end > len) {
end = len;
} else if (end < 0) {
end += len;
}
if (end < 0) {
end = 0;
}
if (start < 0) {
start += len;
}
if (start < 0) {
start = 0;
}
return [start, end, len];
}

Просмотреть файл

@ -87,16 +87,15 @@ TPSCmdLineHandler.prototype =
if (logfile == null) if (logfile == null)
logfile = ""; logfile = "";
let uri = cmdLine.resolveURI(uristr).spec;
/* Ignore the platform's online/offline status while running tests. */ /* Ignore the platform's online/offline status while running tests. */
var ios = Components.classes["@mozilla.org/network/io-service;1"] var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService2); .getService(Components.interfaces.nsIIOService2);
ios.manageOfflineStatus = false; ios.manageOfflineStatus = false;
ios.offline = false; ios.offline = false;
Components.utils.import("resource://tps/tps.jsm"); Components.utils.import("resource://tps/tps.jsm");
Components.utils.import("resource://tps/quit.js", TPS); Components.utils.import("resource://tps/quit.js", TPS);
let uri = cmdLine.resolveURI(uristr).spec;
TPS.RunTestPhase(uri, phase, logfile); TPS.RunTestPhase(uri, phase, logfile);
//cmdLine.preventDefault = true; //cmdLine.preventDefault = true;

Просмотреть файл

@ -59,6 +59,12 @@ CU.import("resource://tps/forms.jsm");
CU.import("resource://tps/prefs.jsm"); CU.import("resource://tps/prefs.jsm");
CU.import("resource://tps/tabs.jsm"); CU.import("resource://tps/tabs.jsm");
var hh = CC["@mozilla.org/network/protocol;1?name=http"]
.getService(CI.nsIHttpProtocolHandler);
var mozmillInit = {};
CU.import('resource://mozmill/modules/init.js', mozmillInit);
const ACTION_ADD = "add"; const ACTION_ADD = "add";
const ACTION_VERIFY = "verify"; const ACTION_VERIFY = "verify";
const ACTION_VERIFY_NOT = "verify-not"; const ACTION_VERIFY_NOT = "verify-not";
@ -396,6 +402,27 @@ var TPS =
" on bookmarks"); " on bookmarks");
}, },
MozmillEndTestListener: function TPS__MozmillEndTestListener(obj) {
Logger.logInfo("mozmill endTest: " + JSON.stringify(obj));
if (obj.failed > 0) {
this.DumpError('mozmill test failed, name: ' + obj.name + ', reason: ' + JSON.stringify(obj.fails));
return;
}
else if ('skipped' in obj && obj.skipped) {
this.DumpError('mozmill test failed, name: ' + obj.name + ', reason: ' + obj.skipped_reason);
return;
}
else {
Utils.namedTimer(function() {
this.FinishAsyncOperation();
}, 2000, this, "postmozmilltest");
}
},
MozmillSetTestListener: function TPS__MozmillSetTestListener(obj) {
Logger.logInfo("mozmill setTest: " + obj.name);
},
RunNextTestAction: function() { RunNextTestAction: function() {
try { try {
if (this._currentAction >= if (this._currentAction >=
@ -435,13 +462,13 @@ var TPS =
RunTestPhase: function (file, phase, logpath) { RunTestPhase: function (file, phase, logpath) {
try { try {
Logger.init(logpath); Logger.init(logpath);
Logger.logInfo("Weave version: " + WEAVE_VERSION); Logger.logInfo("Sync version: " + WEAVE_VERSION);
Logger.logInfo("Firefox builddate: " + Services.appinfo.appBuildID); Logger.logInfo("Firefox builddate: " + Services.appinfo.appBuildID);
Logger.logInfo("Firefox version: " + Services.appinfo.version); Logger.logInfo("Firefox version: " + Services.appinfo.version);
// do some weave housekeeping // do some sync housekeeping
if (Weave.Service.isLoggedIn) { if (Weave.Service.isLoggedIn) {
this.DumpError("Weave logged in on startup...profile may be dirty"); this.DumpError("Sync logged in on startup...profile may be dirty");
return; return;
} }
@ -485,6 +512,25 @@ var TPS =
this._phaselist[phasename] = fnlist; this._phaselist[phasename] = fnlist;
}, },
RunMozmillTest: function TPS__RunMozmillTest(testfile) {
var mozmillfile = CC["@mozilla.org/file/local;1"]
.createInstance(CI.nsILocalFile);
if (hh.oscpu.toLowerCase().indexOf('windows') > -1) {
let re = /\/(\w)\/(.*)/;
this.config.testdir = this.config.testdir.replace(re, "$1://$2").replace("/", "\\", "g");
}
mozmillfile.initWithPath(this.config.testdir);
mozmillfile.appendRelativePath(testfile);
Logger.logInfo("Running mozmill test " + mozmillfile.path);
var frame = {};
CU.import('resource://mozmill/modules/frame.js', frame);
frame.events.addListener('setTest', this.MozmillSetTestListener.bind(this));
frame.events.addListener('endTest', this.MozmillEndTestListener.bind(this));
this.StartAsyncOperation();
frame.runTestFile(mozmillfile.path, false);
},
SetPrivateBrowsing: function TPS__SetPrivateBrowsing(options) { SetPrivateBrowsing: function TPS__SetPrivateBrowsing(options) {
let PBSvc = CC["@mozilla.org/privatebrowsing;1"]. let PBSvc = CC["@mozilla.org/privatebrowsing;1"].
getService(CI.nsIPrivateBrowsingService); getService(CI.nsIPrivateBrowsingService);

Просмотреть файл

@ -68,7 +68,7 @@ NEWCONFIG=${CONFIG:0:${#CONFIG}-3}
cd "../../services/sync/tests/tps" cd "../../services/sync/tests/tps"
TESTDIR="`pwd`" TESTDIR="`pwd`"
cd "../../tps" cd "../../tps/extensions"
EXTDIR="`pwd`" EXTDIR="`pwd`"
sed 's|__TESTDIR__|'"${TESTDIR}"'|' "${CONFIG}" | sed 's|__EXTENSIONDIR__|'"${EXTDIR}"'|' > "${NEWCONFIG}" sed 's|__TESTDIR__|'"${TESTDIR}"'|' "${CONFIG}" | sed 's|__EXTENSIONDIR__|'"${EXTDIR}"'|' > "${NEWCONFIG}"

Просмотреть файл

@ -91,7 +91,7 @@ def main():
if configfile is None: if configfile is None:
if os.environ.get('VIRTUAL_ENV'): if os.environ.get('VIRTUAL_ENV'):
configfile = os.path.join(os.path.dirname(__file__), 'config.json') configfile = os.path.join(os.path.dirname(__file__), 'config.json')
else: if configfile is None or not os.access(configfile, os.F_OK):
raise Exception("Unable to find config.json in a VIRTUAL_ENV; you must " raise Exception("Unable to find config.json in a VIRTUAL_ENV; you must "
"specify a config file using the --configfile option") "specify a config file using the --configfile option")

Просмотреть файл

@ -172,20 +172,22 @@ class TPSTestRunner(object):
def make_xpi(self): def make_xpi(self):
"""Build the test extension.""" """Build the test extension."""
tpsdir = os.path.join(self.extensionDir, "tps")
if self.tpsxpi is None: if self.tpsxpi is None:
tpsxpi = os.path.join(self.extensionDir, "tps.xpi") tpsxpi = os.path.join(tpsdir, "tps.xpi")
if os.access(tpsxpi, os.F_OK): if os.access(tpsxpi, os.F_OK):
os.remove(tpsxpi) os.remove(tpsxpi)
if not os.access(os.path.join(self.extensionDir, "install.rdf"), os.F_OK): if not os.access(os.path.join(tpsdir, "install.rdf"), os.F_OK):
raise Exception("extension code not found in %s" % self.extensionDir) raise Exception("extension code not found in %s" % tpsdir)
from zipfile import ZipFile from zipfile import ZipFile
z = ZipFile(tpsxpi, 'w') z = ZipFile(tpsxpi, 'w')
self._zip_add_file(z, 'chrome.manifest', self.extensionDir) self._zip_add_file(z, 'chrome.manifest', tpsdir)
self._zip_add_file(z, 'install.rdf', self.extensionDir) self._zip_add_file(z, 'install.rdf', tpsdir)
self._zip_add_dir(z, 'components', self.extensionDir) self._zip_add_dir(z, 'components', tpsdir)
self._zip_add_dir(z, 'modules', self.extensionDir) self._zip_add_dir(z, 'modules', tpsdir)
z.close() z.close()
self.tpsxpi = tpsxpi self.tpsxpi = tpsxpi
@ -401,6 +403,7 @@ class TPSTestRunner(object):
# build our tps.xpi extension # build our tps.xpi extension
self.extensions.append(self.make_xpi()) self.extensions.append(self.make_xpi())
self.extensions.append(os.path.join(self.extensionDir, "mozmill"))
# build the test list # build the test list
try: try: