зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
02681d375f
Коммит
4bd38f2535
|
@ -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(/ /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 ' '),
|
||||||
|
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:
|
||||||
|
|
Загрузка…
Ссылка в новой задаче