web mining addon aufgabe

This commit is contained in:
Michael Scholz 2013-05-02 20:37:07 +02:00
parent 4cde28bd0c
commit b249ee20d5
98 changed files with 18774 additions and 0 deletions

View File

@ -0,0 +1,152 @@
/* ***** 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 Weave.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dan Mills <thunder@mozilla.com>
* Atul Varma <atul@mozilla.com>
* Drew Willcoxon <adw@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 ***** */
// For more information on the context in which this script is executed, see:
// https://developer.mozilla.org/en/Extensions/Bootstrapped_extensions
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
// Object containing information about the XPCOM harness service
// that manages our addon.
var gHarness;
var ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
var manager = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
// Dynamically evaluate and initialize the XPCOM component in
// components/harness.js, which bootstraps our addon. (We want to keep
// components/harness.js around so that versions of Gecko that don't
// support rebootless addons can still work.)
function setupHarness(installPath, loadReason) {
var harnessJs = installPath.clone();
harnessJs.append("components");
harnessJs.append("harness.js");
var path = ios.newFileURI(harnessJs).spec;
var harness = {};
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript(path, harness);
var HarnessService = harness.buildHarnessService(installPath);
var factory = HarnessService.prototype._xpcom_factory;
var proto = HarnessService.prototype;
// We want to keep this factory around for the lifetime of
// the addon so legacy code with access to Components can
// access the addon if needed.
manager.registerFactory(proto.classID,
proto.classDescription,
proto.contractID,
factory);
var harnessService = factory.createInstance(null, Ci.nsISupports);
harnessService = harnessService.wrappedJSObject;
gHarness = {
service: harnessService,
classID: proto.classID,
contractID: proto.contractID,
factory: factory
};
if (loadReason == "startup")
// Simulate a startup event; the harness service will take care of
// waiting until the app is ready for the extension's code to run.
harnessService.observe(null, "profile-after-change", null);
else
harnessService.load(loadReason);
}
function reasonToString(reason) {
// If you change these names, change them in harness.js's lifeCycleObserver192
// too.
switch (reason) {
case ADDON_INSTALL:
return "install";
case ADDON_UNINSTALL:
return "uninstall";
case ADDON_ENABLE:
return "enable";
case ADDON_DISABLE:
return "disable";
case ADDON_UPGRADE:
return "upgrade";
case ADDON_DOWNGRADE:
return "downgrade";
// The startup and shutdown strings are also used outside of
// lifeCycleObserver192.
case APP_STARTUP:
return "startup";
case APP_SHUTDOWN:
return "shutdown";
}
return undefined;
}
function install(data, reason) {
// We shouldn't start up here; startup() will always be called when
// an extension should load, and install() sometimes gets called when
// an extension has been installed but is disabled.
}
function startup(data, reason) {
if (!gHarness)
setupHarness(data.installPath, reasonToString(reason));
}
function shutdown(data, reason) {
if (gHarness) {
var harness = gHarness;
gHarness = undefined;
harness.service.unload(reasonToString(reason));
manager.unregisterFactory(harness.classID, harness.factory);
}
}
function uninstall(data, reason) {
// We shouldn't shutdown here; shutdown() will always be called when
// an extension should shutdown, and uninstall() sometimes gets
// called when startup() has never been called before it.
}

View File

@ -0,0 +1,678 @@
/* ***** 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 Weave.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dan Mills <thunder@mozilla.com>
* Atul Varma <atul@mozilla.com>
* Drew Willcoxon <adw@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 ***** */
// This file contains an XPCOM component which "bootstraps" a Jetpack
// program.
//
// The main entry point, `NSGetModule()`, is data-driven, and obtains
// a lot of its configuration information from either the
// `HARNESS_OPTIONS` environment variable (if present) or a JSON file
// called `harness-options.json` in the root directory of the extension
// or application it's a part of.
//
// `NSGetModule()` then uses this configuration information to
// dynamically create an XPCOM component called a "Harness Service",
// which is responsible for setting up and shutting down the Jetpack
// program's CommonJS environment. It's also the main mechanism through
// which other parts of the application can communicate with the Jetpack
// program.
//
// If we're on Gecko 1.9.3, which supports rebootless extensions, the
// bootstrap.js file actually evaluates this file and calls parts of
// it automatically.
//
// It should be noted that a lot of what's done by the Harness Service is
// very similar to what's normally done by a `chrome.manifest` file: the
// difference here is that everything the Harness Service does is
// undoable during the lifetime of the application. This is the
// foundation of what makes it possible for Jetpack-based extensions
// to be installed and uninstalled without needing to reboot the
// application being extended.
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const obSvc = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
const ioSvc = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
const THUNDERBIRD_ID = "{3550f703-e582-4d05-9a08-453d09bdfdc6}";
const FENNEC_ID = "{a23983c0-fd0e-11dc-95ff-0800200c9a66}";
// This function builds and returns a Harness Service XPCOM component.
//
// Parameters:
//
// rootFileSpec - nsILocalFile corresponding to root of extension
// (required).
//
// dump - function to output string to console (required).
//
// logError - function to log an exception (required).
//
// onQuit - function called when the app quits (required).
//
// options - JSON configuration information passed in from the
// environment (required).
function buildHarnessService(rootFileSpec, dump, logError,
onQuit, options) {
if (arguments.length == 1) {
({dump, logError, onQuit, options}) = getDefaults(rootFileSpec);
}
// The loader for securable modules, typically a Cuddlefish loader.
var loader;
// Singleton Harness Service.
var harnessService;
// Whether we've initialized or not yet.
var isStarted;
// Whether we've been asked to quit or not yet.
var isQuitting;
// The Jetpack program's main module.
var program;
var ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var resProt = ioService.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
function quit(status) {
if (status === undefined)
status = "OK";
if (status != "OK" && status != "FAIL") {
dump("Warning: quit() expected 'OK' or 'FAIL' as an " +
"argument, but got '" + status + "' instead.");
status = "FAIL";
}
if (isQuitting)
return;
isQuitting = true;
if (harnessService)
harnessService.unload();
onQuit(status);
}
function logErrorAndBail(e) {
logError(e);
quit("FAIL");
}
function ensureIsDir(dir) {
if (!(dir.exists() && dir.isDirectory))
throw new Error("directory not found: " + dir.path);
}
function getDir(path) {
var dir = Cc['@mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
dir.initWithPath(path);
ensureIsDir(dir);
return dir;
}
function buildLoader() {
// TODO: This variable doesn't seem to be used, we should
// be able to remove it.
var compMgr = Components.manager;
compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
for (name in options.resources) {
var path = options.resources[name];
var dir;
if (typeof(path) == "string")
dir = getDir(path);
else {
dir = rootFileSpec.clone();
path.forEach(function(part) { dir.append(part); });
ensureIsDir(dir);
}
var dirUri = ioService.newFileURI(dir);
resProt.setSubstitution(name, dirUri);
}
var jsm = {};
Cu.import(options.loader, jsm);
var packaging = new Packaging();
var loader = new jsm.Loader({rootPaths: options.rootPaths.slice(),
print: dump,
packaging: packaging,
globals: { packaging: packaging }
});
packaging.__setLoader(loader);
return loader;
}
// This will be exposed as the 'packaging' global to all
// modules loaded within our loader.
function Packaging() {
this.__packages = options.manifest;
}
Packaging.prototype = {
__setLoader: function setLoader(loader) {
this.__loader = loader;
},
get root() {
return rootFileSpec.clone();
},
get harnessService() {
return harnessService;
},
get buildHarnessService() {
return buildHarnessService;
},
get options() {
return options;
},
enableE10s: options.enable_e10s,
jetpackID: options.jetpackID,
bundleID: options.bundleID,
getModuleInfo: function getModuleInfo(path) {
var i = this.__packages[path];
var info = { dependencies: i.requires,
needsChrome: i.chrome,
'e10s-adapter': i['e10s-adapter'],
name: i.name,
packageName: i.packageName,
hash: i.hash
};
if (info.packageName in options.packageData)
info.packageData = options.packageData[info.packageName];
return info;
},
// TODO: This has been superseded by require('self').getURL() and
// should be deprecated.
getURLForData: function getURLForData(path) {
var traceback = this.__loader.require("traceback");
var callerInfo = traceback.get().slice(-2)[0];
var info = this.getModuleInfo(callerInfo.filename);
if ('packageData' in info) {
var url = this.__loader.require("url");
return url.URL(path, info.packageData).toString();
} else
throw new Error("No data for package " + pkgName);
},
createLoader: function createLoader() {
return buildLoader();
}
};
// Singleton XPCOM component that is responsible for instantiating
// a Cuddlefish loader and running the main program, if any.
function HarnessService() {
this.wrappedJSObject = this;
}
HarnessService.prototype = {
get classDescription() {
// This needs to be unique, lest we regress bug 554489.
return "Harness Service for " + options.bootstrap.contractID;
},
get contractID() { return options.bootstrap.contractID; },
get classID() { return Components.ID(options.bootstrap.classID); },
_xpcom_categories: [{ category: "profile-after-change" }],
_xpcom_factory: {
get singleton() {
return harnessService;
},
createInstance: function(outer, iid) {
if (outer)
throw Cr.NS_ERROR_NO_AGGREGATION;
if (!harnessService)
harnessService = new HarnessService();
return harnessService.QueryInterface(iid);
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
get loader() {
if (!loader)
loader = buildLoader();
return loader;
},
get options() {
return options;
},
load: function Harness_load(reason) {
if (isStarted)
return;
isStarted = true;
obSvc.addObserver(this, "quit-application-granted", true);
if (options.main) {
try {
if (reason)
options.loadReason = reason;
program = this.loader.require(options.main);
if ('main' in program)
program.main(options, {quit: quit, print: dump});
// Send application readiness notification
const APP_READY_TOPIC = options.jetpackID + "_APPLICATION_READY";
obSvc.notifyObservers(null, APP_READY_TOPIC, null);
} catch (e) {
this.loader.console.exception(e);
quit("FAIL");
}
}
},
unload: function Harness_unload(reason) {
if (!isStarted)
return;
isStarted = false;
harnessService = null;
obSvc.removeObserver(this, "quit-application-granted");
lifeCycleObserver192.unload();
// Notify the program of unload.
if (program) {
if (typeof(program.onUnload) === "function") {
try {
program.onUnload(reason);
}
catch (err) {
if (loader)
loader.console.exception(err);
}
}
program = null;
}
// Notify the loader of unload.
if (loader) {
loader.unload(reason);
loader = null;
}
for (name in options.resources)
resProt.setSubstitution(name, null);
},
observe: function Harness_observe(subject, topic, data) {
try {
switch (topic) {
case "profile-after-change":
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
switch (appInfo.ID) {
case THUNDERBIRD_ID:
case FENNEC_ID:
obSvc.addObserver(this, "xul-window-visible", true);
break;
case FIREFOX_ID:
obSvc.addObserver(this, "sessionstore-windows-restored", true);
break;
default:
obSvc.addObserver(this, "final-ui-startup", true);
break;
}
lifeCycleObserver192.init(options.bundleID, logError);
break;
case "final-ui-startup": // XULRunner
case "sessionstore-windows-restored": // Firefox
case "xul-window-visible": // Thunderbird, Fennec
obSvc.removeObserver(this, topic);
this.load(lifeCycleObserver192.loadReason || "startup");
break;
case "quit-application-granted":
this.unload(lifeCycleObserver192.unloadReason || "shutdown");
quit("OK");
break;
}
} catch (e) {
logErrorAndBail(e);
}
}
};
var factory = HarnessService.prototype._xpcom_factory;
if (!factory.wrappedJSObject)
factory.wrappedJSObject = factory;
return HarnessService;
}
// This is an error logger of last resort; if we're here, then
// we weren't able to initialize Cuddlefish and display a nice
// traceback through it.
function defaultLogError(e, print) {
if (!print)
print = dump;
print(e + " (" + e.fileName + ":" + e.lineNumber + ")\n");
if (e.stack)
print("stack:\n" + e.stack + "\n");
}
// Builds an onQuit() function that writes a result file if necessary
// and does some other extra things to enhance developer ergonomics.
function buildDevQuit(options, dump) {
// Absolute path to a file that we put our result code in. Ordinarily
// we'd just exit the process with a zero or nonzero return code, but
// there doesn't appear to be a way to do this in XULRunner.
var resultFile = options.resultFile;
// Whether we've written resultFile or not.
var fileWritten = false;
function attemptQuit() {
var appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
getService(Ci.nsIAppStartup);
appStartup.quit(Ci.nsIAppStartup.eAttemptQuit);
}
return function onQuit(result) {
dump(result + "\n");
function writeResult() {
if (!fileWritten)
try {
var file = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
file.initWithPath(resultFile);
var foStream = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
foStream.init(file, -1, -1, 0);
foStream.write(result, result.length);
foStream.close();
fileWritten = true;
} catch (e) {
dump(e + "\n");
}
}
writeResult();
attemptQuit();
};
}
function buildForsakenConsoleDump(dump) {
var buffer = "";
var cService = Cc['@mozilla.org/consoleservice;1'].getService()
.QueryInterface(Ci.nsIConsoleService);
function stringify(arg) {
try {
return String(arg);
}
catch(ex) {
return "<toString() error>";
}
}
return function forsakenConsoleDump(msg) {
// No harm in calling dump() just in case the
// end-user *can* see the console...
dump(msg);
msg = stringify(msg);
if (msg.indexOf('\n') >= 0) {
cService.logStringMessage(buffer + msg);
buffer = "";
} else {
buffer += msg;
}
};
}
function getDefaults(rootFileSpec) {
// Default options to pass back.
var options;
try {
var environ = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
var jsonData;
if (environ.exists("HARNESS_OPTIONS"))
jsonData = environ.get("HARNESS_OPTIONS");
else {
var optionsFile = rootFileSpec.clone();
optionsFile.append('harness-options.json');
if (optionsFile.exists()) {
var fiStream = Cc['@mozilla.org/network/file-input-stream;1']
.createInstance(Ci.nsIFileInputStream);
var siStream = Cc['@mozilla.org/scriptableinputstream;1']
.createInstance(Ci.nsIScriptableInputStream);
fiStream.init(optionsFile, 1, 0, false);
siStream.init(fiStream);
var data = new String();
data += siStream.read(-1);
siStream.close();
fiStream.close();
jsonData = data;
} else
throw new Error("HARNESS_OPTIONS env var must exist.");
}
options = JSON.parse(jsonData);
} catch (e) {
defaultLogError(e);
throw e;
}
var onQuit = function() {};
var doDump = dump;
if ('resultFile' in options)
onQuit = buildDevQuit(options, print);
else
// If we're not being run by cfx or some other kind of tool that is
// ensuring dump() calls are visible, we'll have to log to the
// forsaken Error Console.
doDump = buildForsakenConsoleDump(doDump);
var logFile;
var logStream;
if ('logFile' in options) {
logFile = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
logFile.initWithPath(options.logFile);
logStream = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
logStream.init(logFile, -1, -1, 0);
}
function print(msg) {
doDump(msg);
if (logStream && typeof(msg) == "string") {
logStream.write(msg, msg.length);
logStream.flush();
}
}
function logError(e) {
defaultLogError(e, print);
}
return {options: options, onQuit: onQuit, dump: print,
logError: logError};
}
// Gecko 2, entry point for non-bootstrapped extensions (which register this
// component via chrome.manifest.)
// FIXME: no install/uninstall notifications on 2.0 for non-bootstrapped addons
function NSGetFactory(cid) {
try {
if (!NSGetFactory.fn) {
var rootFileSpec = __LOCATION__.parent.parent;
var HarnessService = buildHarnessService(rootFileSpec);
NSGetFactory.fn = XPCOMUtils.generateNSGetFactory([HarnessService]);
}
} catch(e) {
Components.utils.reportError(e);
dump(e);
throw e;
}
return NSGetFactory.fn(cid);
}
// Everything below is only used on Gecko 1.9.2 or below.
function NSGetModule(compMgr, fileSpec) {
var rootFileSpec = fileSpec.parent.parent;
var HarnessService = buildHarnessService(rootFileSpec);
return XPCOMUtils.generateModule([HarnessService]);
}
// Program life-cycle events originate in bootstrap.js on 1.9.3. But 1.9.2
// doesn't use bootstrap.js, so we need to do a little extra work there to
// determine the reasons for app startup and shutdown. That's what this
// singleton is for. On 1.9.3 all methods are no-ops.
var lifeCycleObserver192 = {
get loadReason() {
if (this._inited) {
// If you change these names, change them in bootstrap.js too.
if (this._addonIsNew)
return "install";
return "startup";
}
return undefined;
},
get unloadReason() {
if (this._inited) {
// If you change these names, change them in bootstrap.js too.
switch (this._emState) {
case "item-uninstalled":
return "uninstall";
case "item-disabled":
return "disable";
}
return "shutdown";
}
return undefined;
},
// This must be called first to initialize the singleton. It must be called
// on profile-after-change.
init: function lifeCycleObserver192_init(bundleID, logError) {
// This component is present in 1.9.2 but not 2.0.
if ("@mozilla.org/extensions/manager;1" in Cc && !this._inited) {
obSvc.addObserver(this, "em-action-requested", true);
this._bundleID = bundleID;
this._logError = logError;
this._inited = true;
try {
// This throws if the pref doesn't exist, which is the case when no
// new add-ons were installed.
var addonIdStr = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch).
getCharPref("extensions.newAddons");
}
catch (err) {}
if (addonIdStr) {
var addonIds = addonIdStr.split(",");
this._addonIsNew = addonIds.indexOf(this._bundleID) >= 0;
}
}
},
unload: function lifeCycleObserver192_unload() {
if (this._inited && !this._unloaded) {
obSvc.removeObserver(this, "em-action-requested");
delete this._logError;
this._unloaded = true;
}
},
observe: function lifeCycleObserver192_observe(subj, topic, data) {
try {
if (topic === "em-action-requested") {
if (subj instanceof Ci.nsIUpdateItem && subj.id === this._bundleID)
this._emState = data;
}
}
catch (err) {
this._logError(err);
}
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
])
};

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?><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>jid0-GN3ivO79cgfs9k4P3lxdo7TPFa4@jetpack</em:id>
<em:version>0.1</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:unpack>true</em:unpack>
<!-- Target Application this extension can install into,
with minimum and maximum supported versions. For a list
of valid values, see:
https://addons.mozilla.org/en-US/firefox/pages/appversions
-->
<!-- Firefox -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>4.0b7</em:minVersion>
<em:maxVersion>4.0.*</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>keaddon</em:name>
<em:description>a basic add-on</em:description>
<em:creator>Clemens Dörrhöfer</em:creator>
<em:iconURL/>
<em:icon64URL/>
<em:optionsURL/>
</Description>
</RDF>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>Page Worker test</title>
</head>
<body>
<p id="paragraph">Lorem ipsum dolor sit amet.</p>
</body>
</html>

View File

@ -0,0 +1,27 @@
function testLoadContentPage() {
// get title directly
postMessage(["assertEqual", document.title, "Page Worker test",
"Correct page title accessed directly"]);
// get <p> directly
let p = document.getElementById("paragraph");
postMessage(["assert", !!p, "<p> can be accessed directly"]);
postMessage(["assertEqual", p.firstChild.nodeValue,
"Lorem ipsum dolor sit amet.",
"Correct text node expected"]);
// Modify page
let div = document.createElement("div");
div.setAttribute("id", "block");
div.appendChild(document.createTextNode("Test text created"));
document.body.appendChild(div);
// Check back the modification
div = document.getElementById("block");
postMessage(["assert", !!div, "<div> can be accessed directly"]);
postMessage(["assertEqual", div.firstChild.nodeValue,
"Test text created", "Correct text node expected"]);
postMessage(["done"]);
}
window.addEventListener("DOMContentLoaded", testLoadContentPage, true);

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>foo</title>
</head>
<body>
<p>bar</p>
</body>
</html>

View File

@ -0,0 +1,248 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Paul OShannessy <paul@oshannessy.com> (Original Author)
* Dietrich Ayala <dietrich@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 ***** */
const {Cc,Ci} = require("chrome");
const errors = require("errors");
const apiUtils = require("api-utils");
/*
While these data flavors resemble Internet media types, they do
no directly map to them.
*/
const kAllowableFlavors = [
"text/unicode",
"text/html"
/* CURRENTLY UNSUPPORTED FLAVORS
"text/plain",
"image/png",
"image/jpg",
"image/gif"
"text/x-moz-text-internal",
"AOLMAIL",
"application/x-moz-file",
"text/x-moz-url",
"text/x-moz-url-data",
"text/x-moz-url-desc",
"text/x-moz-url-priv",
"application/x-moz-nativeimage",
"application/x-moz-nativehtml",
"application/x-moz-file-promise-url",
"application/x-moz-file-promise-dest-filename",
"application/x-moz-file-promise",
"application/x-moz-file-promise-dir"
*/
];
/*
Aliases for common flavors. Not all flavors will
get an alias. New aliases must be approved by a
Jetpack API druid.
*/
const kFlavorMap = [
{ short: "text", long: "text/unicode" },
{ short: "html", long: "text/html" }
// Images are currently unsupported.
//{ short: "image", long: "image/png" },
];
let clipboardService = Cc["@mozilla.org/widget/clipboard;1"].
getService(Ci.nsIClipboard);
let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
exports.set = function(aData, aDataType) {
let options = {
data: aData,
datatype: aDataType || "text"
};
options = apiUtils.validateOptions(options, {
data: {
is: ["string"]
},
datatype: {
is: ["string"]
}
});
var flavor = fromJetpackFlavor(options.datatype);
if (!flavor)
throw new Error("Invalid flavor");
// Additional checks for using the simple case
if (flavor == "text/unicode") {
clipboardHelper.copyString(options.data);
return true;
}
// Below are the more complex cases where we actually have to work with a
// nsITransferable object
var xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
if (!xferable)
throw new Error("Couldn't set the clipboard due to an internal error " +
"(couldn't create a Transferable object).");
switch (flavor) {
case "text/html":
var str = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
str.data = options.data;
xferable.addDataFlavor(flavor);
xferable.setTransferData(flavor, str, options.data.length * 2);
break;
// TODO: images!
// TODO: add a text/unicode flavor for HTML text that
// returns a plaintextified representation of the HTML.
default:
throw new Error("Unable to handle the flavor " + flavor + ".");
}
// TODO: Not sure if this will ever actually throw. -zpao
try {
clipboardService.setData(
xferable,
null,
clipboardService.kGlobalClipboard
);
} catch (e) {
throw new Error("Couldn't set clipboard data due to an internal error: " + e);
}
return true;
};
exports.get = function(aDataType) {
let options = {
datatype: aDataType || "text"
};
options = apiUtils.validateOptions(options, {
datatype: {
is: ["string"]
}
});
var xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
if (!xferable)
throw new Error("Couldn't set the clipboard due to an internal error " +
"(couldn't create a Transferable object).");
var flavor = fromJetpackFlavor(options.datatype);
// Ensure that the user hasn't requested a flavor that we don't support.
if (!flavor)
throw new Error("Getting the clipboard with the flavor '" + flavor +
"' is > not supported.");
// TODO: Check for matching flavor first? Probably not worth it.
xferable.addDataFlavor(flavor);
// Get the data into our transferable.
clipboardService.getData(
xferable,
clipboardService.kGlobalClipboard
);
var data = {};
var dataLen = {};
try {
xferable.getTransferData(flavor, data, dataLen);
} catch (e) {
// Clipboard doesn't contain data in flavor, return null.
return null;
}
// There's no data available, return.
if (data.value === null)
return null;
// TODO: Add flavors here as we support more in kAllowableFlavors.
switch (flavor) {
case "text/unicode":
case "text/html":
data = data.value.QueryInterface(Ci.nsISupportsString).data;
break;
default:
data = null;
}
return data;
};
exports.__defineGetter__("currentFlavors", function() {
// Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each.
// This doesn't seem like the most efficient way, but we can't get
// confirmation for specific flavors any other way. This is supposed to be
// an inexpensive call, so performance shouldn't be impacted (much).
var currentFlavors = [];
for each (var flavor in kAllowableFlavors) {
var matches = clipboardService.hasDataMatchingFlavors(
[flavor],
1,
clipboardService.kGlobalClipboard
);
if (matches)
currentFlavors.push(toJetpackFlavor(flavor));
}
return currentFlavors;
});
// SUPPORT FUNCTIONS ////////////////////////////////////////////////////////
function toJetpackFlavor(aFlavor) {
for each (flavorMap in kFlavorMap)
if (flavorMap.long == aFlavor)
return flavorMap.short;
// Return null in the case where we don't match
return null;
}
function fromJetpackFlavor(aJetpackFlavor) {
// TODO: Handle proper flavors better
for each (flavorMap in kFlavorMap)
if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor)
return flavorMap.long;
// Return null in the case where we don't match.
return null;
}

View File

@ -0,0 +1,110 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim:set ts=2 sw=2 sts=2 et filetype=javascript
* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (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 ***** */
const { Cc, Ci, Cr } = require("chrome");
const apiUtils = require("api-utils");
const errors = require("errors");
try {
let alertServ = Cc["@mozilla.org/alerts-service;1"].
getService(Ci.nsIAlertsService);
// The unit test sets this to a mock notification function.
var notify = alertServ.showAlertNotification.bind(alertServ);
}
catch (err) {
// An exception will be thrown if the platform doesn't provide an alert
// service, e.g., if Growl is not installed on OS X. In that case, use a
// mock notification function that just logs to the console.
notify = notifyUsingConsole;
}
exports.notify = function notifications_notify(options) {
let valOpts = validateOptions(options);
let clickObserver = !valOpts.onClick ? null : {
observe: function notificationClickObserved(subject, topic, data) {
if (topic === "alertclickcallback")
errors.catchAndLog(valOpts.onClick).call(exports, valOpts.data);
}
};
function notifyWithOpts(notifyFn) {
notifyFn(valOpts.iconURL, valOpts.title, valOpts.text, !!clickObserver,
valOpts.data, clickObserver);
}
try {
notifyWithOpts(notify);
}
catch (err if err instanceof Ci.nsIException &&
err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
console.warn("The notification icon named by " + valOpts.iconURL +
" does not exist. A default icon will be used instead.");
delete valOpts.iconURL;
notifyWithOpts(notify);
}
catch (err) {
notifyWithOpts(notifyUsingConsole);
}
};
function notifyUsingConsole(iconURL, title, text) {
title = title ? "[" + title + "]" : "";
text = text || "";
let str = [title, text].filter(function (s) s).join(" ");
console.log(str);
}
function validateOptions(options) {
return apiUtils.validateOptions(options, {
data: {
is: ["string", "undefined"]
},
iconURL: {
is: ["string", "undefined"]
},
onClick: {
is: ["function", "undefined"]
},
text: {
is: ["string", "undefined"]
},
title: {
is: ["string", "undefined"]
}
});
}

View File

@ -0,0 +1,198 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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 Packages.
*
* The Initial Developer of the Original Code is Nickolay Ponomarev.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Nickolay Ponomarev <asqueella@gmail.com> (Original Author)
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
const observers = require("observer-service");
const { Worker, Loader } = require('content');
const { EventEmitter } = require('events');
const { List } = require('list');
const { Registry } = require('utils/registry');
const xulApp = require("xul-app");
const { MatchPattern } = require('match-pattern');
// Whether or not the host application dispatches a document-element-inserted
// notification when the document element is inserted into the DOM of a page.
// The notification was added in Gecko 2.0b6, it's a better time to attach
// scripts with contentScriptWhen "start" than content-document-global-created,
// since libraries like jQuery assume the presence of the document element.
const HAS_DOCUMENT_ELEMENT_INSERTED =
xulApp.versionInRange(xulApp.platformVersion, "2.0b6", "*");
const ON_CONTENT = HAS_DOCUMENT_ELEMENT_INSERTED ? 'document-element-inserted' :
'content-document-global-created';
const ON_READY = 'DOMContentLoaded';
const ERR_INCLUDE = 'The PageMod must have a string or array `include` option.';
// rules registry
const RULES = {};
const Rules = EventEmitter.resolve({ toString: null }).compose(List, {
add: function() Array.slice(arguments).forEach(function onAdd(rule) {
if (this._has(rule)) return;
// registering rule to the rules registry
if (!(rule in RULES))
RULES[rule] = new MatchPattern(rule);
this._add(rule);
this._emit('add', rule);
}.bind(this)),
remove: function() Array.slice(arguments).forEach(function onRemove(rule) {
if (!this._has(rule)) return;
this._remove(rule);
this._emit('remove', rule);
}.bind(this)),
});
/**
* PageMod constructor (exported below).
* @constructor
*/
const PageMod = Loader.compose(EventEmitter, {
on: EventEmitter.required,
_listeners: EventEmitter.required,
contentScript: Loader.required,
contentScriptFile: Loader.required,
contentScriptWhen: Loader.required,
include: null,
constructor: function PageMod(options) {
this._onAttach = this._onAttach.bind(this);
this._onReady = this._onReady.bind(this);
this._onContent = this._onContent.bind(this);
options = options || {};
if ('contentScript' in options)
this.contentScript = options.contentScript;
if ('contentScriptFile' in options)
this.contentScriptFile = options.contentScriptFile;
if ('contentScriptWhen' in options)
this.contentScriptWhen = options.contentScriptWhen;
if ('onAttach' in options)
this.on('attach', options.onAttach);
if ('onError' in options)
this.on('error', options.onError);
let include = options.include;
let rules = this.include = Rules();
rules.on('add', this._onRuleAdd = this._onRuleAdd.bind(this));
rules.on('remove', this._onRuleRemove = this._onRuleRemove.bind(this));
// Validate 'include'
if (typeof(include) == "object" && !Array.isArray(include))
throw new Error(ERR_INCLUDE);
if (typeof(include) == "number" || typeof(include) == "undefined")
throw new Error(ERR_INCLUDE);
if (Array.isArray(include))
rules.add.apply(null, include);
else
rules.add(include);
this.on('error', this._onUncaughtError = this._onUncaughtError.bind(this));
pageModManager.add(this._public);
},
destroy: function destroy() {
pageModManager.remove(this._public);
},
_onContent: function _onContent(window) {
if (!pageModManager.has(this))
return; // not registered yet
if ('ready' == this.contentScriptWhen)
window.addEventListener(ON_READY, this._onReady , false);
else
this._onAttach(window);
},
_onReady: function _onReady(event) {
let window = event.target.defaultView;
window.removeEventListener(ON_READY, this._onReady, false);
this._onAttach(window);
},
_onAttach: function _onAttach(window) {
this._emit('attach', Worker({
window: window.wrappedJSObject,
contentScript: this.contentScript,
contentScriptFile: this.contentScriptFile,
onError: this._onUncaughtError
}));
},
_onRuleAdd: function _onRuleAdd(url) {
pageModManager.on(url, this._onContent);
},
_onRuleRemove: function _onRuleRemove(url) {
pageModManager.off(url, this._onContent);
},
_onUncaughtError: function _onUncaughtError(e) {
if (this._listeners('error').length == 1)
console.exception(e);
}
});
exports.PageMod = function(options) PageMod(options)
exports.PageMod.prototype = PageMod.prototype;
const PageModManager = Registry.resolve({
constructor: '_init',
_destructor: '_registryDestructor'
}).compose({
constructor: function PageModRegistry(constructor) {
this._init(PageMod);
observers.add(
ON_CONTENT, this._onContentWindow = this._onContentWindow.bind(this)
);
},
_destructor: function _destructor() {
observers.remove(ON_CONTENT, this._onContentWindow);
for (let rule in RULES) {
this._removeAllListeners(rule);
delete RULES[rule];
}
this._registryDestructor();
},
_onContentWindow: function _onContentWindow(domObj) {
let window = HAS_DOCUMENT_ELEMENT_INSERTED ? domObj.defaultView : domObj;
// XML documents don't have windows, and we don't yet support them.
if (!window)
return;
for (let rule in RULES)
if (RULES[rule].test(window.document.URL))
this._emit(rule, window);
},
off: function off(topic, listener) {
this.removeListener(topic, listener);
if (!this._listeners(topic).length)
delete RULES[topic];
}
});
const pageModManager = PageModManager();

View File

@ -0,0 +1,97 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Felipe Gomes <felipc@gmail.com> (Original Author)
* Myk Melez <myk@mozilla.org>
* Irakli Gozalishvili <gozala@mozilla.com>
* Drew Willcoxon <adw@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 ***** */
"use strict";
const { Symbiont } = require("content");
const { Trait } = require("traits");
if (!require("xul-app").isOneOf(["Firefox", "Thunderbird"])) {
throw new Error([
"The page-worker module currently supports only Firefox and Thunderbird. ",
"In the future, we would like it to support other applications, however. ",
"Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
"information."
].join(""));
}
const Page = Trait.compose(
Symbiont.resolve({
constructor: '_initSymbiont'
}),
{
_frame: Trait.required,
_initFrame: Trait.required,
postMessage: Symbiont.required,
on: Symbiont.required,
destroy: Symbiont.required,
constructor: function Page(options) {
options = options || {};
this.contentURL = 'contentURL' in options ? options.contentURL
: 'about:blank';
if ('contentScriptWhen' in options)
this.contentScriptWhen = options.contentScriptWhen;
if ('contentScriptFile' in options)
this.contentScriptFile = options.contentScriptFile;
if ('contentScript' in options)
this.contentScript = options.contentScript;
if ('allow' in options)
this.allow = options.allow;
if ('onError' in options)
this.on('error', options.onError);
if ('onMessage' in options)
this.on('message', options.onMessage);
this.on('propertyChange', this._onChange.bind(this));
this._initSymbiont();
},
_onChange: function _onChange(e) {
if ('contentURL' in e)
this._initFrame(this._frame);
}
}
);
exports.Page = function(options) Page(options);
exports.Page.prototype = Page.prototype;

View File

@ -0,0 +1,358 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Myk Melez <myk@mozilla.org> (Original Author)
* Irakli Gozalishvili <gozala@mazilla.com>
* Mihai Sucan <mihai.sucan@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 ***** */
if (!require("xul-app").is("Firefox")) {
throw new Error([
"The panel module currently supports only Firefox. In the future ",
"we would like it to support other applications, however. Please see ",
"https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps ",
"for more information."
].join(""));
}
const { Ci } = require("chrome");
const { validateOptions: valid } = require("api-utils");
const { Symbiont } = require("content");
const { EventEmitter } = require('events');
const timer = require("timer");
require("xpcom").utils.defineLazyServiceGetter(
this,
"windowMediator",
"@mozilla.org/appshell/window-mediator;1",
"nsIWindowMediator"
);
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
ON_SHOW = 'popupshown',
ON_HIDE = 'popuphidden',
validNumber = { is: ['number', 'undefined', 'null'] };
/**
* Emits show and hide events.
*/
const Panel = Symbiont.resolve({
constructor: '_init',
_onInit: '_onSymbiontInit',
destroy: '_symbiontDestructor'
}).compose({
_frame: Symbiont.required,
_init: Symbiont.required,
_onSymbiontInit: Symbiont.required,
_symbiontDestructor: Symbiont.required,
_emit: Symbiont.required,
_asyncEmit: Symbiont.required,
on: Symbiont.required,
removeListener: Symbiont.required,
_destructor: Symbiont.required,
_inited: false,
/**
* If set to `true` frame loaders between xul panel frame and
* hidden frame are swapped. If set to `false` frame loaders are
* set back to normal. Setting the value that was already set will
* have no effect.
*/
set _frameLoadersSwapped(value) {
if (this.__frameLoadersSwapped == value) return;
this._frame.QueryInterface(Ci.nsIFrameLoaderOwner)
.swapFrameLoaders(this._viewFrame);
this.__frameLoadersSwapped = value;
},
__frameLoadersSwapped: false,
constructor: function Panel(options) {
this._onShow = this._onShow.bind(this);
this._onHide = this._onHide.bind(this);
this.on('inited', this._onSymbiontInit.bind(this));
options = options || {};
if ('onShow' in options)
this.on('show', options.onShow);
if ('onHide' in options)
this.on('hide', options.onHide);
if ('width' in options)
this.width = options.width;
if ('height' in options)
this.height = options.height;
if ('contentURL' in options)
this.contentURL = options.contentURL;
this._init(options);
},
_destructor: function _destructor() {
this.hide();
this._removeAllListeners('show');
// defer cleanup to be performed after panel gets hidden
this._xulPanel = null;
this._symbiontDestructor(this);
this._removeAllListeners(this, 'hide');
},
destroy: function destroy() {
this._destructor();
},
/* Public API: Panel.width */
get width() this._width,
set width(value)
this._width = valid({ $: value }, { $: validNumber }).$ || this._width,
_width: 320,
/* Public API: Panel.height */
get height() this._height,
set height(value)
this._height = valid({ $: value }, { $: validNumber }).$ || this._height,
_height: 240,
/* Public API: Panel.isShowing */
get isShowing() !!this._xulPanel && this._xulPanel.state == "open",
/* Public API: Panel.show */
show: function show(anchor) {
anchor = anchor || null;
let document = getWindow(anchor).document;
let xulPanel = this._xulPanel;
if (!xulPanel) {
xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel');
xulPanel.setAttribute("type", "arrow");
// One anonymous node has a big padding that doesn't work well with
// Jetpack, as we would like to display an iframe that completely fills
// the panel.
// -> Use a XBL wrapper with inner stylesheet to remove this padding.
let css = ".panel-inner-arrowcontent, .panel-arrowcontent {padding: 0;}";
let originalXBL = "chrome://global/content/bindings/popup.xml#arrowpanel";
let binding =
'<bindings xmlns="http://www.mozilla.org/xbl">' +
'<binding id="id" extends="' + originalXBL + '">' +
'<resources>' +
'<stylesheet src="data:text/css,' +
document.defaultView.encodeURIComponent(css) + '"/>' +
'</resources>' +
'</binding>' +
'</bindings>';
xulPanel.style.MozBinding = 'url("data:text/xml,' +
document.defaultView.encodeURIComponent(binding) + '")';
let frame = document.createElementNS(XUL_NS, 'iframe');
frame.setAttribute('type', 'content');
frame.setAttribute('flex', '1');
frame.setAttribute('transparent', 'transparent');
// Load an empty document in order to have an immediatly loaded iframe,
// so swapFrameLoaders is going to work without having to wait for load.
frame.setAttribute("src","data:,");
xulPanel.appendChild(frame);
document.getElementById("mainPopupSet").appendChild(xulPanel);
}
let { width, height } = this, x, y, position;
if (!anchor) {
// Open the popup in the middle of the window.
x = document.documentElement.clientWidth / 2 - width / 2;
y = document.documentElement.clientHeight / 2 - height / 2;
position = null;
}
else {
// Open the popup by the anchor.
let rect = anchor.getBoundingClientRect();
let window = anchor.ownerDocument.defaultView;
let zoom = window.mozScreenPixelsPerCSSPixel;
let screenX = rect.left + window.mozInnerScreenX * zoom;
let screenY = rect.top + window.mozInnerScreenY * zoom;
// Set up the vertical position of the popup relative to the anchor
// (always display the arrow on anchor center)
let horizontal, vertical;
if (screenY > window.screen.availHeight / 2 + height)
vertical = "top";
else
vertical = "bottom";
if (screenY > window.screen.availWidth / 2 + width)
horizontal = "left";
else
horizontal = "right";
let verticalInverse = vertical == "top" ? "bottom" : "top";
position = vertical + "center " + verticalInverse + horizontal;
// Allow panel to flip itself if the panel can't be displayed at the
// specified position (useful if we compute a bad position or if the
// user moves the window and panel remains visible)
xulPanel.setAttribute("flip","both");
}
// Resize the iframe instead of using panel.sizeTo
// because sizeTo doesn't work with arrow panels
xulPanel.firstChild.style.width = width + "px";
xulPanel.firstChild.style.height = height + "px";
// Wait for the XBL binding to be constructed
function waitForBinding() {
if (!xulPanel.openPopup) {
timer.setTimeout(waitForBinding, 50);
return;
}
xulPanel.openPopup(anchor, position, x, y);
}
waitForBinding();
return this._public;
},
/* Public API: Panel.hide */
hide: function hide() {
// The popuphiding handler takes care of swapping back the frame loaders
// and removing the XUL panel from the application window, we just have to
// trigger it by hiding the popup.
// XXX Sometimes I get "TypeError: xulPanel.hidePopup is not a function"
// when quitting the host application while a panel is visible. To suppress
// them, this now checks for "hidePopup" in xulPanel before calling it.
// It's not clear if there's an actual issue or the error is just normal.
let xulPanel = this._xulPanel;
if (xulPanel && "hidePopup" in xulPanel)
xulPanel.hidePopup();
return this._public;
},
/* Public API: Panel.resize */
resize: function resize(width, height) {
this.width = width;
this.height = height;
this._xulPanel.sizeTo(width, height);
},
// While the panel is visible, this is the XUL <panel> we use to display it.
// Otherwise, it's null.
get _xulPanel() this.__xulPanel,
set _xulPanel(value) {
let xulPanel = this.__xulPanel;
if (value === xulPanel) return;
if (xulPanel) {
xulPanel.removeEventListener(ON_HIDE, this._onHide, false);
xulPanel.removeEventListener(ON_SHOW, this._onShow, false);
xulPanel.parentNode.removeChild(xulPanel);
}
if (value) {
value.addEventListener(ON_HIDE, this._onHide, false);
value.addEventListener(ON_SHOW, this._onShow, false);
}
this.__xulPanel = value;
},
__xulPanel: null,
get _viewFrame() this.__xulPanel.children[0],
/**
* When the XUL panel becomes hidden, we swap frame loaders back to move
* the content of the panel to the hidden frame & remove panel element.
*/
_onHide: function _onHide() {
try {
this._frameLoadersSwapped = false;
this._xulPanel = null;
this._emit('hide');
} catch(e) {
this._emit('error', e);
}
},
/**
* When the XUL panel becomes shown, we swap frame loaders between panel
* frame and hidden frame to preserve state of the content dom.
*/
_onShow: function _onShow() {
try {
if (!this._inited) // defer if not initialized yet
return this.on('inited', this._onShow.bind(this));
this._frameLoadersSwapped = true;
this._emit('show');
} catch(e) {
this._emit('error', e);
}
},
/**
* Notification that panel was fully initialized.
*/
_onInit: function _onInit() {
this._inited = true;
// perform all deferred tasks like initSymbiont, show, hide ...
// TODO: We're publicly exposing a private event here; this
// 'inited' event should really be made private, somehow.
this._emit('inited');
this._removeAllListeners('inited');
}
});
exports.Panel = function(options) Panel(options)
exports.Panel.prototype = Panel.prototype;
function getWindow(anchor) {
let window;
if (anchor) {
let anchorWindow = anchor.ownerDocument.defaultView.top;
let anchorDocument = anchorWindow.document;
let enumerator = windowMediator.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let enumWindow = enumerator.getNext();
// Check if the anchor is in this browser window.
if (enumWindow == anchorWindow) {
window = anchorWindow;
break;
}
// Check if the anchor is in a browser tab in this browser window.
let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
if (browser) {
window = enumWindow;
break;
}
// Look in other subdocuments (sidebar, etc.)?
}
}
// If we didn't find the anchor's window (or we have no anchor),
// return the most recent browser window.
if (!window)
window = windowMediator.getMostRecentWindow("navigator:browser");
return window;
}

View File

@ -0,0 +1,82 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 ***** */
const { Trait } = require("light-traits");
const utils = require("passwords/utils");
const defer = require("utils/function").Enqueued;
/**
* Utility function that returns `onComplete` and `onError` callbacks form the
* given `options` objects. Also properties are removed from the passed
* `options` objects.
* @param {Object} options
* Object that is passed to the exported functions of this module.
* @returns {Function[]}
* Array with two elements `onComplete` and `onError` functions.
*/
function getCallbacks(options) {
let value = [
'onComplete' in options ? defer(options.onComplete) : null,
'onError' in options ? defer(options.onError) : defer(console.exception)
];
delete options.onComplete;
delete options.onError;
return value;
};
/**
* Creates a wrapper function that tries to call `onComplete` with a return
* value of the wrapped function or falls back to `onError` if wrapped function
* throws an exception.
*/
function createWrapperMethod(wrapped) {
return function (options) {
let [ onComplete, onError ] = getCallbacks(options);
try {
onComplete(wrapped(options));
} catch (exception) {
onError(exception);
}
};
}
exports.search = createWrapperMethod(utils.search);
exports.store = createWrapperMethod(utils.store);
exports.remove = createWrapperMethod(utils.remove);

View File

@ -0,0 +1,94 @@
/* ***** 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 Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Paul OShannessy <paul@oshannessy.com>
* Irakli Gozalishvili <gozala@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 ***** */
const {Cc,Ci} = require("chrome");
const observers = require("observer-service");
const { EventEmitter } = require("events");
const { setTimeout } = require("timer");
const unload = require("unload");
const ON_START = "start";
const ON_STOP = "stop";
const ON_TRANSITION = "private-browsing-transition-complete";
let pbService;
// Currently, only Firefox implements the private browsing service.
if (require("xul-app").is("Firefox")) {
pbService = Cc["@mozilla.org/privatebrowsing;1"].
getService(Ci.nsIPrivateBrowsingService);
}
const privateBrowsing = EventEmitter.compose({
constructor: function PrivateBrowsing() {
// Binding method to instance since it will be used with `setTimeout`.
this._emitOnObject = this._emitOnObject.bind(this);
this.unload = this.unload.bind(this);
// Report unhandled errors from listeners
this.on("error", console.exception.bind(console));
unload.ensure(this);
// We only need to add observers if `pbService` exists.
if (pbService) {
observers.add(ON_TRANSITION, this.onTransition.bind(this));
this._isActive = pbService.privateBrowsingEnabled;
}
},
unload: function _destructor() {
this._removeAllListeners(ON_START);
this._removeAllListeners(ON_STOP);
},
// We don't need to do anything with cancel here.
onTransition: function onTransition() {
let isActive = this._isActive = pbService.privateBrowsingEnabled;
setTimeout(this._emitOnObject, 0, exports, isActive ? ON_START : ON_STOP);
},
get isActive() this._isActive,
set isActive(value) {
if (pbService)
pbService.privateBrowsingEnabled = !!value;
},
_isActive: false
})()
Object.defineProperty(exports, "isActive", {
get: function() privateBrowsing.isActive
});
exports.activate = function activate() privateBrowsing.isActive = true;
exports.deactivate = function deactivate() privateBrowsing.isActive = false;
exports.on = privateBrowsing.on;
exports.removeListener = privateBrowsing.removeListener;

View File

@ -0,0 +1,299 @@
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Paul OShannessy <paul@oshannessy.com> (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 ***** */
const xpcom = require("xpcom");
const xhr = require("xhr");
const errors = require("errors");
const apiUtils = require("api-utils");
// Ugly but will fix with: https://bugzilla.mozilla.org/show_bug.cgi?id=596248
const EventEmitter = require('events').EventEmitter.compose({
constructor: function EventEmitter() this
});
// Instead of creating a new validator for each request, just make one and reuse it.
const validator = new OptionsValidator({
url: {
//XXXzpao should probably verify that url is a valid url as well
is: ["string"]
},
headers: {
map: function (v) v || {},
is: ["object"],
},
content: {
map: function (v) v || null,
is: ["string", "object", "null"],
},
contentType: {
map: function (v) v || "application/x-www-form-urlencoded",
is: ["string"]
}
});
const REUSE_ERROR = "This request object has been used already. You must " +
"create a new one to make a new request."
function Request(options) {
const self = EventEmitter(),
_public = self._public;
// request will hold the actual XHR object
let request;
let response;
if ('onComplete' in options)
self.on('complete', options.onComplete)
options = validator.validateOptions(options);
// function to prep the request since it's the same between GET and POST
function makeRequest(mode) {
// If this request has already been used, then we can't reuse it. Throw an error.
if (request) {
throw new Error(REUSE_ERROR);
}
request = new xhr.XMLHttpRequest();
let url = options.url;
// Build the data to be set. For GET requests, we want to append that to
// the URL before opening the request.
let data = makeQueryString(options.content);
if (mode == "GET" && data) {
// If the URL already has ? in it, then we want to just use &
url = url + (/\?/.test(url) ? "&" : "?") + data;
}
// open the request
request.open(mode, url);
// request header must be set after open, but before send
request.setRequestHeader("Content-Type", options.contentType);
// set other headers
for (let k in options.headers) {
request.setRequestHeader(k, options.headers[k]);
}
// handle the readystate, create the response, and call the callback
request.onreadystatechange = function () {
if (request.readyState == 4) {
response = new Response(request);
errors.catchAndLog(function () {
self._emit('complete', response);
})();
}
}
// actually send the request. we only want to send data on POST requests
request.send(mode == "POST" ? data : null);
}
// Map these setters/getters to the options
["url", "headers", "content", "contentType"].forEach(function (k) {
_public.__defineGetter__(k, function () options[k]);
_public.__defineSetter__(k, function (v) {
// This will automatically rethrow errors from apiUtils.validateOptions.
return options[k] = validator.validateSingleOption(k, v);
});
});
// response should be available as a getter
_public.__defineGetter__("response", function () response);
_public.get = function () {
makeRequest("GET");
return this;
};
_public.post = function () {
makeRequest("POST");
return this;
};
return _public;
}
exports.Request = Request;
// Converts an object of unordered key-vals to a string that can be passed
// as part of a request
function makeQueryString(content) {
// Explicitly return null if we have null, and empty string, or empty object.
if (!content) {
return null;
}
// If content is already a string, just return it as is.
if (typeof(content) == "string") {
return content;
}
// At this point we have a k:v object. Iterate over it and encode each value.
// Arrays and nested objects will get encoded as needed. For example...
//
// { foo: [1, 2, { omg: "bbq", "all your base!": "are belong to us" }], bar: "baz" }
//
// will be encoded as
//
// foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz
//
// Keys (including "[" and "]") and values will be encoded with
// fixedEncodeURIComponent before returning.
//
// Execution was inspired by jQuery, but some details have changed and numeric
// array keys are included (whereas they are not in jQuery).
let encodedContent = [];
function add(key, val) {
encodedContent.push(fixedEncodeURIComponent(key) + "=" +
fixedEncodeURIComponent(val));
}
function make(key, val) {
if (typeof(val) == "object") {
for ([k, v] in Iterator(val)) {
make(key + "[" + k + "]", v);
}
}
else {
add(key, val)
}
}
for ([k, v] in Iterator(content)) {
make(k, v);
}
return encodedContent.join("&");
//XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had
// trouble getting that working. It would also be nice to stay
// backwards-compat as long as possible. Keeping this in for now...
// let formData = Cc["@mozilla.org/files/formdata;1"].
// createInstance(Ci.nsIDOMFormData);
// for ([k, v] in Iterator(content)) {
// formData.append(k, v);
// }
// return formData;
}
// encodes a string safely for application/x-www-form-urlencoded
// adheres to RFC 3986
// see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Functions/encodeURIComponent
function fixedEncodeURIComponent (str) {
return encodeURIComponent(str).replace(/%20/g, "+").replace(/!/g, "%21").
replace(/'/g, "%27").replace(/\(/g, "%28").
replace(/\)/g, "%29").replace(/\*/g, "%2A");
}
function Response(request) {
// Define the straight mappings of our value to original request value
xpcom.utils.defineLazyGetter(this, "text", function () request.responseText);
xpcom.utils.defineLazyGetter(this, "xml", function () {
throw new Error("Sorry, the 'xml' property is no longer available. " +
"see bug 611042 for more information.");
});
xpcom.utils.defineLazyGetter(this, "status", function () request.status);
xpcom.utils.defineLazyGetter(this, "statusText", function () request.statusText);
// this.json should be the JS object, so we need to attempt to parse it.
xpcom.utils.defineLazyGetter(this, "json", function () {
let _json = null;
try {
_json = JSON.parse(this.text);
}
catch (e) {}
return _json;
});
// this.headers also should be a JS object, so we need to split up the raw
// headers string provided by the request.
xpcom.utils.defineLazyGetter(this, "headers", function () {
let _headers = {};
let lastKey;
// Since getAllResponseHeaders() will return null if there are no headers,
// defend against it by defaulting to ""
let rawHeaders = request.getAllResponseHeaders() || "";
rawHeaders.split("\n").forEach(function (h) {
// According to the HTTP spec, the header string is terminated by an empty
// line, so we can just skip it.
if (!h.length) {
return;
}
let index = h.indexOf(":");
// The spec allows for leading spaces, so instead of assuming a single
// leading space, just trim the values.
let key = h.substring(0, index).trim(),
val = h.substring(index + 1).trim();
// For empty keys, that means that the header value spanned multiple lines.
// In that case we should append the value to the value of lastKey with a
// new line. We'll assume lastKey will be set because there should never
// be an empty key on the first pass.
if (key) {
_headers[key] = val;
lastKey = key;
}
else {
_headers[lastKey] += "\n" + val;
}
});
return _headers;
})
}
// apiUtils.validateOptions doesn't give the ability to easily validate single
// options, so this is a wrapper that provides that ability.
function OptionsValidator(rules) {
this.rules = rules;
this.validateOptions = function (options) {
return apiUtils.validateOptions(options, this.rules);
}
this.validateSingleOption = function (field, value) {
// We need to create a single rule object from our listed rules. To avoid
// JavaScript String warnings, check for the field & default to an empty object.
let singleRule = {};
if (field in this.rules) {
singleRule[field] = this.rules[field];
}
let singleOption = {};
singleOption[field] = value;
// This should throw if it's invalid, which will bubble up & out.
return apiUtils.validateOptions(singleOption, singleRule)[field];
}
}

View File

@ -0,0 +1,353 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Eric H. Jung <eric.jung@yahoo.com>
* Irakli Gozalishivili <gozala@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 ***** */
if (!require("xul-app").is("Firefox")) {
throw new Error([
"The selection module currently supports only Firefox. In the future ",
"we would like it to support other applications, however. Please see ",
"https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
].join(""));
}
let { Ci } = require("chrome"),
{ setTimeout } = require("timer"),
{ EventEmitter } = require('events');
// The selection type HTML
const HTML = 0x01;
// The selection type TEXT
const TEXT = 0x02;
// The selection type DOM (internal use only)
const DOM = 0x03;
/**
* Creates an object from which a selection can be set, get, etc. Each
* object has an associated with a range number. Range numbers are the
* 0-indexed counter of selection ranges as explained at
* https://developer.mozilla.org/en/DOM/Selection.
*
* @param rangeNumber
* The zero-based range index into the selection
*/
function Selection(rangeNumber) {
// In order to hide the private rangeNumber argument from API consumers while
// still enabling Selection getters/setters to access it, the getters/setters
// are defined as lexical closures in the Selector constructor.
this.__defineGetter__("text", function () getSelection(TEXT, rangeNumber));
this.__defineSetter__("text", function (str) setSelection(str, rangeNumber));
this.__defineGetter__("html", function () getSelection(HTML, rangeNumber));
this.__defineSetter__("html", function (str) setSelection(str, rangeNumber));
this.__defineGetter__("isContiguous", function () {
let sel = getSelection(DOM, rangeNumber);
// It isn't enough to check that rangeCount is zero. If one or more ranges
// are selected and then unselected, rangeCount is set to one, not zero.
// Therefore, if rangeCount is one, we also check if the selection is
// collapsed.
if (sel.rangeCount == 0)
return null;
if (sel.rangeCount == 1) {
let range = safeGetRange(sel, 0);
return range && range.collapsed ? null : true;
}
return false;
});
}
require("xpcom").utils.defineLazyServiceGetter(this, "windowMediator",
"@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator");
/**
* Returns the most recent content window
*/
function context() {
// Overlay names should probably go into the xul-app module instead of here
return windowMediator.getMostRecentWindow("navigator:browser").document.
commandDispatcher.focusedWindow;
}
/**
* Returns the current selection from most recent content window. Depending on
* the specified |type|, the value returned can be a string of text, stringified
* HTML, or a DOM selection object as described at
* https://developer.mozilla.org/en/DOM/Selection.
*
* @param type
* Specifies the return type of the selection. Valid values are the one
* of the constants HTML, TEXT, or DOM.
*
* @param rangeNumber
* Specifies the zero-based range index of the returned selection.
*/
function getSelection(type, rangeNumber) {
let window, selection;
try {
window = context();
selection = window.getSelection();
}
catch (e) {
return null;
}
// Get the selected content as the specified type
if (type == DOM)
return selection;
else if (type == TEXT) {
let range = safeGetRange(selection, rangeNumber);
return range ? range.toString() : null;
}
else if (type == HTML) {
let range = safeGetRange(selection, rangeNumber);
// Another way, but this includes the xmlns attribute for all elements in
// Gecko 1.9.2+ :
// return Cc["@mozilla.org/xmlextras/xmlserializer;1"].
// createInstance(Ci.nsIDOMSerializer).serializeToSTring(range.
// cloneContents());
if (!range)
return null;
let node = window.document.createElement("span");
node.appendChild(range.cloneContents());
return node.innerHTML;
}
throw new Error("Type " + type + " is unrecognized.");
}
/**
* Returns the specified range in a selection without throwing an exception.
*
* @param selection
* A selection object as described at
* https://developer.mozilla.org/en/DOM/Selection
*
* @param rangeNumber
* Specifies the zero-based range index of the returned selection.
*/
function safeGetRange(selection, rangeNumber) {
try {
let range = selection.getRangeAt(rangeNumber);
if (!range || range.toString() == "")
return null;
return range;
}
catch (e) {
return null;
}
}
/**
* Sets the current selection of the most recent content document by changing
* the existing selected text/HTML range to the specified value.
*
* @param val
* The value for the new selection
*
* @param rangeNumber
* The zero-based range index of the selection to be set
*
*/
function setSelection(val, rangeNumber) {
// Make sure we have a window context & that there is a current selection.
// Selection cannot be set unless there is an existing selection.
let window, range;
try {
window = context();
range = window.getSelection().getRangeAt(rangeNumber);
}
catch (e) {
// Rethrow with a more developer-friendly message than the caught
// exception.
throw new Error("It isn't possible to change the selection, as there isn't currently a selection");
}
// Get rid of the current selection and insert our own
range.deleteContents();
let node = window.document.createElement("span");
range.surroundContents(node);
// Some relevant JEP-111 requirements:
// Setting the text property replaces the selection with the value to
// which the property is set and sets the html property to the same value
// to which the text property is being set.
// Setting the html property replaces the selection with the value to
// which the property is set and sets the text property to the text version
// of the HTML value.
// This sets both the HTML and text properties.
node.innerHTML = val;
}
function onLoad(event) {
SelectionListenerManager.onLoad(event);
}
function onUnload(event) {
SelectionListenerManager.onUnload(event);
}
let SelectionListenerManager = {
QueryInterface: require("xpcom").utils.generateQI([Ci.nsISelectionListener]),
// The collection of listeners wanting to be notified of selection changes
listeners: EventEmitter.compose({
emit: function emit(type) this._emitOnObject(exports, type),
off: function() this._removeAllListeners.apply(this, arguments)
})(),
/**
* This is the nsISelectionListener implementation. This function is called
* by Gecko when a selection is changed interactively.
*
* We only pay attention to the SELECTALL, KEYPRESS, and MOUSEUP selection
* reasons. All reasons are listed here:
*
* http://mxr.mozilla.org/mozilla1.9.2/source/content/base/public/
* nsISelectionListener.idl
*
* The other reasons (NO_REASON, DRAG_REASON, MOUSEDOWN_REASON) aren't
* applicable to us.
*/
notifySelectionChanged: function notifySelectionChanged(document, selection,
reason) {
if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(function(type) reason &
Ci.nsISelectionListener[type + "_REASON"]) || selection.toString() == "")
return;
setTimeout(this.listeners.emit, 0, 'select')
},
/**
* Part of the Tracker implementation. This function is called by the
* tabs module when a browser is being tracked. Often, that means a new tab
* has been opened, but it can also mean an addon has been installed while
* tabs are already opened. In that case, this function is called for those
* already-opened tabs.
*
* @param browser
* The browser being tracked
*/
onTrack: function onTrack(browser) {
browser.addEventListener("load", onLoad, true);
browser.addEventListener("unload", onUnload, true);
},
onLoad: function onLoad(event) {
// Nothing to do without a useful window
let window = event.target.defaultView;
if (!window)
return;
// Wrap the add selection call with some number of setTimeout 0 because some
// reason it's possible to add a selection listener "too early". 2 sometimes
// works for gmail, and more consistently with 3, so make it 5 to be safe.
let count = 0;
let self = this;
function wrap(count, func) {
if (count-- > 0)
require("timer").setTimeout(wrap, 0);
else
self.addSelectionListener(window);
}
wrap();
},
addSelectionListener: function addSelectionListener(window) {
if (window.jetpack_core_selection_listener)
return;
let selection = window.getSelection();
if (selection instanceof Ci.nsISelectionPrivate)
selection.addSelectionListener(this);
window.jetpack_core_selection_listener = true;
},
onUnload: function onUnload(event) {
// Nothing to do without a useful window
let window = event.target.defaultView;
if (!window)
return;
this.removeSelectionListener(window);
this.listeners.off('error');
this.listeners.off('selection');
},
removeSelectionListener: function removeSelectionListener(window) {
if (!window.jetpack_core_selection_listener)
return;
let selection = window.getSelection();
if (selection instanceof Ci.nsISelectionPrivate)
selection.removeSelectionListener(this);
window.jetpack_core_selection_listener = false;
},
/**
* Part of the TabTracker implementation. This function is called by the
* tabs module when a browser is being untracked. Usually, that means a tab
* has been closed.
*
* @param browser
* The browser being untracked
*/
onUntrack: function onUntrack(browser) {
browser.removeEventListener("load", onLoad, true);
browser.removeEventListener("unload", onUnload, true);
}
};
SelectionListenerManager.listeners.on('error', console.error);
/**
* Install |SelectionListenerManager| as tab tracker in order to watch
* tab opening/closing
*/
require("tab-browser").Tracker(SelectionListenerManager);
/**
* Exports an iterator so that discontiguous selections can be iterated.
*/
exports.__iterator__ = function __iterator__() {
for (let i = 0, sel = getSelection(DOM); i < sel.rangeCount; i++)
yield new Selection(i);
};
exports.on = SelectionListenerManager.listeners.on;
exports.removeListener = SelectionListenerManager.listeners.removeListener;
// Export the Selection singleton. Its rangeNumber is always zero.
Selection.call(exports, 0);

View File

@ -0,0 +1,258 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim:set ts=2 sw=2 sts=2 et filetype=javascript
* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (Original Author)
* Irakli Gozalishvili <gozala@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 ***** */
const {Cc,Ci} = require("chrome");
const file = require("file");
const prefs = require("preferences-service");
const jpSelf = require("self");
const timer = require("timer");
const unload = require("unload");
const { EventEmitter } = require("events");
const { Trait } = require("traits");
const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
const QUOTA_DEFAULT = 5242880; // 5 MiB
const JETPACK_DIR_BASENAME = "jetpack";
// simpleStorage.storage
exports.__defineGetter__("storage", function () manager.root);
exports.__defineSetter__("storage", function (val) manager.root = val);
// simpleStorage.quotaUsage
exports.__defineGetter__("quotaUsage", function () manager.quotaUsage);
// A generic JSON store backed by a file on disk. This should be isolated
// enough to move to its own module if need be...
function JsonStore(options) {
this.filename = options.filename;
this.quota = options.quota;
this.writePeriod = options.writePeriod;
this.onOverQuota = options.onOverQuota;
this.onWrite = options.onWrite;
unload.ensure(this);
this.writeTimer = timer.setInterval(this.write.bind(this),
this.writePeriod);
}
JsonStore.prototype = {
// The store's root.
get root() {
return this._root === undefined ? {} : this._root;
},
// Performs some type checking.
set root(val) {
let types = ["array", "boolean", "null", "number", "object", "string"];
if (types.indexOf(typeof(val)) < 0) {
throw new Error("storage must be one of the following types: " +
types.join(", "));
}
this._root = val;
return val;
},
// Percentage of quota used, as a number [0, Inf). > 1 implies over quota.
// Undefined if there is no quota.
get quotaUsage() {
return this.quota > 0 ?
JSON.stringify(this.root).length / this.quota :
undefined;
},
// Removes the backing file and all empty subdirectories.
purge: function JsonStore_purge() {
try {
// This'll throw if the file doesn't exist.
file.remove(this.filename);
let parentPath = this.filename;
do {
parentPath = file.dirname(parentPath);
// This'll throw if the dir isn't empty.
file.rmdir(parentPath);
} while (file.basename(parentPath) !== JETPACK_DIR_BASENAME);
}
catch (err) {}
},
// Initializes the root by reading the backing file.
read: function JsonStore_read() {
try {
let str = file.read(this.filename);
// Ideally we'd log the parse error with console.error(), but logged
// errors cause tests to fail. Supporting "known" errors in the test
// harness appears to be non-trivial. Maybe later.
this.root = JSON.parse(str);
}
catch (err) {
this.root = {};
}
},
// If the store is under quota, writes the root to the backing file.
// Otherwise quota observers are notified and nothing is written.
write: function JsonStore_write() {
if (this.quotaUsage > 1)
this.onOverQuota(this);
else
this._write();
},
// Cleans up on unload. If unloading because of uninstall, the store is
// purged; otherwise it's written.
unload: function JsonStore_unload(reason) {
timer.clearInterval(this.writeTimer);
this.writeTimer = null;
if (reason === "uninstall")
this.purge();
else
this._write();
},
// True if the root is an empty object.
get _isEmpty() {
if (this.root && typeof(this.root) === "object") {
let empty = true;
for (let key in this.root) {
empty = false;
break;
}
return empty;
}
return false;
},
// Writes the root to the backing file, notifying write observers when
// complete. If the store is over quota or if it's empty and the store has
// never been written, nothing is written and write observers aren't notified.
_write: function JsonStore__write() {
// If the store is empty and the file doesn't yet exist, don't write.
if (this._isEmpty && !file.exists(this.filename))
return;
// If the store is over quota, don't write. The current under-quota state
// should persist.
if (this.quotaUsage > 1)
return;
// Finally, write.
let stream = file.open(this.filename, "w");
try {
stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
if (err)
console.error("Error writing simple storage file: " + this.filename);
else if (this.onWrite)
this.onWrite(this);
}.bind(this));
}
catch (err) {
// writeAsync closes the stream after it's done, so only close on error.
stream.close();
}
}
};
// This manages a JsonStore singleton and tailors its use to simple storage.
// The root of the JsonStore is lazy-loaded: The backing file is only read the
// first time the root's gotten.
let manager = Trait.compose(EventEmitter, Trait.compose({
jsonStore: null,
// The filename of the store, based on the profile dir and extension ID.
get filename() {
let storeFile = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties).
get("ProfD", Ci.nsIFile);
storeFile.append(JETPACK_DIR_BASENAME);
storeFile.append(jpSelf.id);
storeFile.append("simple-storage");
file.mkpath(storeFile.path);
storeFile.append("store.json");
return storeFile.path;
},
get quotaUsage() {
return this.jsonStore.quotaUsage;
},
get root() {
if (!this.rootInited) {
this.jsonStore.read();
this.rootInited = true;
}
return this.jsonStore.root;
},
set root(val) {
let rv = this.jsonStore.root = val;
this.rootInited = true;
return rv;
},
unload: function manager_unload() {
this._removeAllListeners("OverQuota");
this._removeAllListeners("error");
},
constructor: function manager_constructor() {
// Log unhandled errors.
this.on("error", console.exception.bind(console));
unload.ensure(this);
this.jsonStore = new JsonStore({
filename: this.filename,
writePeriod: prefs.get(WRITE_PERIOD_PREF, WRITE_PERIOD_DEFAULT),
quota: prefs.get(QUOTA_PREF, QUOTA_DEFAULT),
onOverQuota: this._emitOnObject.bind(this, exports, "OverQuota")
});
}
}))();
exports.on = manager.on;
exports.removeListener = manager.removeListener;

View File

@ -0,0 +1,62 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dietrich Ayala <dietrich@mozilla.com> (Original author)
* Felipe Gomes <felipc@gmail.com>
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
if (!require("xul-app").is("Firefox")) {
throw new Error([
"The tabs module currently supports only Firefox. In the future ",
"we would like it to support other applications, however. Please see ",
"https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
].join(""));
}
const { browserWindows } = require("windows");
const { tabs } = require("windows/tabs");
Object.defineProperties(tabs, {
open: { value: function open(options) {
if (options.inNewWindow)
// `tabs` option is under review and may be removed.
return browserWindows.open({ tabs: [ options ] });
// Open in active window if new window was not required.
return browserWindows.activeWindow.tabs.open(options);
}}
});
// It's a hack but we will be able to remove it once will implemnet CommonJS
// feature that would allow us to override exports.
exports.__proto__ = tabs;

View File

@ -0,0 +1,656 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dietrich Ayala <dietrich@mozilla.com> (Original Author)
* Drew Willcoxon <adw@mozilla.com>
* Irakli Gozalishvili <gozala@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 ***** */
const {Cc, Ci} = require("chrome");
// Widget content types
const CONTENT_TYPE_URI = 1;
const CONTENT_TYPE_HTML = 2;
const CONTENT_TYPE_IMAGE = 3;
const ERR_CONTENT = "No content or contentURL property found. Widgets must "
+ "have one or the other.",
ERR_LABEL = "The widget must have a non-empty label property.";
// Supported events, mapping from DOM event names to our event names
const EVENTS = {
"click": "click",
"mouseover": "mouseover",
"mouseout": "mouseout",
};
if (!require("xul-app").is("Firefox")) {
throw new Error([
"The widget module currently supports only Firefox. In the future ",
"it will support other applications. Please see ",
"https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
].join(""));
}
const { validateOptions } = require("api-utils");
const panels = require("panel");
const { EventEmitter } = require("events");
const { Trait } = require("traits");
const { Loader, Symbiont } = require("content");
const valid = {
number: { is: ["null", "undefined", "number"] },
string: { is: ["null", "undefined", "string"] },
label: {
is: ["string"],
ok: function (v) v.length > 0,
msg: ERR_LABEL
},
panel: {
is: ["null", "undefined", "object"],
ok: function(v) !v || v instanceof panels.Panel
}
}
function validate(name, suspect, validation) {
let $1 = {}
$1[name] = suspect
let $2 = {}
$2[name] = validation
return validateOptions($1, $2)[name]
}
const eventBus = Trait.compose(EventEmitter, Trait.compose({
constructor: function EventBus() this
}))();
// The widget object.
const Widget = Trait.compose(Loader, Trait.compose({
constructor: function Widget(options) {
eventBus.on('event', this._onEvent.bind(this));
this.on('error', this._defaultErrorHandler.bind(this));
this._label = validate("label", options.label, valid.label);
this.tooltip = "tooltip" in options ? options.tooltip : this._label
if ("id" in options)
this._id = options.id;
else
console.warn('You have to define an unique "id" attribute to your widget '
+ 'in order to be able to remember its position.');
browserManager.validate(this._public);
if ("width" in options)
this.width = options.width;
if ("panel" in options)
this.panel = options.panel;
if ("onClick" in options)
this.on("click", options.onClick);
if ("onMouseover" in options)
this.on("mouseover", options.onMouseover);
if ("onMouseout" in options)
this.on("mouseout", options.onMouseout);
if ("content" in options)
this._content = options.content;
if ("contentURL" in options)
this.contentURL = options.contentURL;
if ("contentScriptWhen" in options)
this.contentScriptWhen = options.contentScriptWhen;
if ("contentScriptFile" in options)
this.contentScriptFile = options.contentScriptFile;
if ("contentScript" in options)
this.contentScript = options.contentScript;
if ("allow" in options)
this.allow = options.allow;
if ("onError" in options)
this.on("error", options.onError);
if ("onMessage" in options)
this.on("message", options.onMessage);
if (!(this._content || this.contentURL))
throw new Error(ERR_CONTENT);
let self = this;
this.on('propertyChange', function(change) {
if ('contentURL' in change)
browserManager.updateItem(self._public, "contentURL", self.contentURL);
});
browserManager.addItem(this._public);
},
_defaultErrorHandler: function Widget__defaultErrorHandler(e) {
if (1 == this._listeners('error').length)
console.exception(e)
},
_onEvent: function Widget__onEvent(type, target, eventData, domNode) {
if (target === this._public) {
this._emit(type, eventData);
// Special case for click events: if the widget doesn't have a click
// handler, but it does have a panel, display the panel.
if ("click" == type && !this._listeners("click").length && this.panel)
this.panel.show(domNode);
}
},
get id() this._id,
_id: null,
get label() this._label,
_label: null,
get width() this._width,
set width(value) {
value = validate("width", value, valid.number);
if (null === value || undefined === value) value = 16;
if (value !== this._width)
browserManager.updateItem(this._public, "width", this._width = value);
},
_width: 16,
get tooltip() this._tooltip,
set tooltip(value) {
value = validate("tooltip", value, valid.string);
if (value !== this._tooltip)
browserManager.updateItem(this._public, "tooltip", this._tooltip = value);
},
_tooltip: null,
get content() this._content,
set content(value) {
value = validate("content", value, valid.string);
if (value !== this._content)
browserManager.updateItem(this._public, "content", this._content = value);
},
_content: null,
get panel() this._panel,
set panel(value) {
value = validate("panel", value, valid.panel);
if (value !== this._panel)
this._panel = value;
},
_panel: null,
postMessage: function Widget_postMessage(message) {
browserManager.updateItem(this._public, "postMessage", message);
},
destroy: function Widget_destroy() {
browserManager.removeItem(this._public);
}
}));
exports.Widget = function(options) Widget(options);
exports.Widget.prototype = Widget.prototype;
// Keeps track of all browser windows.
// Exposes methods for adding/removing/updating widgets
// across all open windows (and future ones).
let browserManager = {
items: [],
windows: [],
// Registers the manager to listen for window openings and closings. Note
// that calling this method can cause onTrack to be called immediately if
// there are open windows.
init: function () {
let windowTracker = new (require("window-utils").WindowTracker)(this);
require("unload").ensure(windowTracker);
},
// Registers a window with the manager. This is a WindowTracker callback.
onTrack: function browserManager_onTrack(window) {
if (this._isBrowserWindow(window)) {
let win = new BrowserWindow(window);
win.addItems(this.items);
this.windows.push(win);
}
},
// Unregisters a window from the manager. It's told to undo all
// modifications. This is a WindowTracker callback. Note that when
// WindowTracker is unloaded, it calls onUntrack for every currently opened
// window. The browserManager therefore doesn't need to specially handle
// unload itself, since unloading the browserManager means untracking all
// currently opened windows.
onUntrack: function browserManager_onUntrack(window) {
if (this._isBrowserWindow(window)) {
for (let i = 0; i < this.windows.length; i++) {
if (this.windows[i].window == window) {
let win = this.windows.splice(i, 1)[0];
win.destroy();
return;
}
}
}
},
// Used to validate widget by browserManager before adding it,
// in order to check input very early in widget constructor
validate : function (item) {
let idx = this.items.indexOf(item);
if (idx > -1)
throw new Error("The widget " + item + " has already been added.");
if (item.id) {
let sameId = this.items.filter(function(i) i.id == item.id);
if (sameId.length > 0)
throw new Error("This widget ID is already used: " + item.id);
} else {
item.id = this.items.length;
}
},
// Registers an item with the manager. It's added to all currently registered
// windows, and when new windows are registered it will be added to them, too.
addItem: function browserManager_addItem(item) {
this.items.push(item);
this.windows.forEach(function (w) w.addItems([item]));
},
// Updates the content of an item registered with the manager,
// propagating the change to all windows.
updateItem: function browserManager_updateItem(item, property, value) {
let idx = this.items.indexOf(item);
if (idx != -1)
this.windows.forEach(function (w) w.updateItem(item, property, value));
},
// Unregisters an item from the manager. It's removed from all windows that
// are currently registered.
removeItem: function browserManager_removeItem(item) {
let idx = this.items.indexOf(item);
if (idx > -1) {
this.items.splice(idx, 1);
if (item.panel)
item.panel.destroy();
this.windows.forEach(function (w) w.removeItems([item]));
}
},
_isBrowserWindow: function browserManager__isBrowserWindow(win) {
let winType = win.document.documentElement.getAttribute("windowtype");
return winType === "navigator:browser";
}
};
// Keeps track of a single browser window. Responsible for providing a
// description of the window's current context and determining whether an item
// matches the current context.
//
// This is where the core of how a widget's content is added to a window lives.
//
// TODO: If other apps besides Firefox want to support the add-on bar in
// whatever way is appropriate for them, plugging in a substitute for this class
// should be the way to do it. Make it easy for them. See bug 560716.
function BrowserWindow(window) {
this.window = window;
this.doc = window.document;
this._init();
}
BrowserWindow.prototype = {
_init: function BW__init() {
// Array of objects:
// {
// widget: widget object,
// node: dom node,
// eventListeners: hash of event listeners
// symbiont: contentSymbiont
// }
this._items = [];
},
get container() {
if (!this._container) {
// If being run in a version of Firefox <4, create a separate
// addon bar. TODO: just use the status bar?
let container = this.doc.getElementById("addon-bar");
if (!container) {
let toolbox = this.doc.createElement("toolbox");
// Share browser's palette.
let browserToolbox = this.doc.getElementById("navigator-toolbox");
toolbox.palette = browserToolbox.palette;
container = this.doc.createElement("toolbar");
container.setAttribute("id", "addon-bar");
container.setAttribute("customizable", "true");
// TODO: needs localization
container.setAttribute("toolbarname", "Add-ons Toolbar");
container.setAttribute("align", "right");
container.style.minHeight = "18px";
container.style.padding = "2px";
container.style.margin = "0px";
toolbox.appendChild(container);
let statusbar = this.doc.getElementById("status-bar");
statusbar.parentNode.insertBefore(toolbox, statusbar);
}
this._container = container;
}
return this._container;
},
// Adds an array of items to the window.
addItems: function BW_addItems(items) {
items.forEach(this._addItemToWindow, this);
},
// Update a property of a widget.
updateItem: function BW_updateItem(updatedItem, property, value) {
let item = this._items.filter(function(item) item.widget == updatedItem).shift();
if (item) {
switch(property) {
case "contentURL":
case "content":
this.setContent(item);
break;
case "width":
item.node.style.minWidth = value + "px";
item.node.querySelector("iframe").style.width = value + "px";
break;
case "tooltip":
item.node.setAttribute("tooltiptext", value);
break;
case "postMessage":
item.symbiont.postMessage(value);
break;
}
}
},
// Add a widget to this window.
_addItemToWindow: function BW__addItemToWindow(widget) {
// XUL element container for widget
let node = this.doc.createElement("toolbaritem");
let guid = require("xpcom").makeUuid().toString();
// Temporary fix around require("self") failing on unit-test execution ...
let jetpackID = "testID";
try {
jetpackID = require("self").id;
} catch(e) {}
// Compute an unique and stable widget id with jetpack id and widget.id
let id = "widget:" + jetpackID + "-" + widget.id;
node.setAttribute("id", id);
node.setAttribute("label", widget.label);
node.setAttribute("tooltiptext", widget.tooltip);
node.setAttribute("align", "center");
// TODO move into a stylesheet, configurable by consumers.
// Either widget.style, exposing the style object, or a URL
// (eg, can load local stylesheet file).
node.setAttribute("style", [
"overflow: hidden; margin: 1px 2px 1px 2px; padding: 0px;",
"min-height: 16px;",
].join(""));
node.style.minWidth = widget.width + "px";
// Add to the customization palette
let toolbox = this.doc.getElementById("navigator-toolbox");
let palette = toolbox.palette;
palette.appendChild(node);
// Search for widget toolbar by reading toolbar's currentset attribute
let container = null;
let toolbars = this.doc.getElementsByTagName("toolbar");
for(let i = 0, l = toolbars.length; i < l; i++) {
let toolbar = toolbars[i];
if (toolbar.getAttribute("currentset").indexOf(id) == -1)
continue;
container = toolbar;
}
// if widget isn't in any toolbar, add it to the addon-bar
// TODO: we may want some "first-launch" module to do this only on very
// first execution
if (!container) {
// TODO: find a way to make the following code work when we use "cfx run":
// http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#8586
// until then, force display of addon bar directly from sdk code
// https://bugzilla.mozilla.org/show_bug.cgi?id=627484
if (this.container.collapsed)
this.window.toggleAddonBar();
container = this.container;
}
// Now retrieve a reference to the next toolbar item
// by reading currentset attribute on the toolbar
let nextNode = null;
let currentSet = container.getAttribute("currentset");
let ids = (currentSet == "__empty") ? [] : currentSet.split(",");
let idx = ids.indexOf(id);
if (idx != -1) {
for(let i = idx; i < ids.length; i++) {
nextNode = this.doc.getElementById(ids[i]);
if (nextNode)
break;
}
}
// Finally insert our widget in the right toolbar and in the right position
container.insertItem(id, nextNode, null, false);
let item = {widget: widget, node: node};
this._fillItem(item);
this._items.push(item);
},
// Initial population of a widget's content.
_fillItem: function BS__fillItem(item) {
// Create element
var iframe = this.doc.createElement("iframe");
iframe.setAttribute("type", "content");
iframe.setAttribute("transparent", "transparent");
iframe.style.overflow = "hidden";
iframe.style.height = "16px";
iframe.style.maxHeight = "16px";
iframe.style.width = item.widget.width + "px";
iframe.setAttribute("flex", "1");
iframe.style.border = "none";
iframe.style.padding = "0px";
// Do this early, because things like contentWindow are null
// until the node is attached to a document.
item.node.appendChild(iframe);
// add event handlers
this.addEventHandlers(item);
// set content
this.setContent(item);
},
// Get widget content type.
getContentType: function BW_getContentType(widget) {
if (widget.content)
return CONTENT_TYPE_HTML;
return (widget.contentURL && /\.(jpg|gif|png|ico)$/.test(widget.contentURL))
? CONTENT_TYPE_IMAGE : CONTENT_TYPE_URI;
},
// Set widget content.
setContent: function BW_setContent(item) {
let type = this.getContentType(item.widget);
let contentURL = null;
switch (type) {
case CONTENT_TYPE_HTML:
contentURL = "data:text/html," + encodeURI(item.widget.content);
break;
case CONTENT_TYPE_URI:
contentURL = item.widget.contentURL;
break;
case CONTENT_TYPE_IMAGE:
let imageURL = item.widget.contentURL;
contentURL = "data:text/html,<html><body><img src='" +
encodeURI(imageURL) + "'></body></html>";
break;
default:
throw new Error("The widget's type cannot be determined.");
}
let iframe = item.node.firstElementChild;
item.symbiont = Symbiont({
frame: iframe,
contentURL: contentURL,
contentScriptFile: item.widget.contentScriptFile,
contentScript: item.widget.contentScript,
contentScriptWhen: item.widget.contentScriptWhen,
allow: item.widget.allow,
onMessage: function(message) {
require("timer").setTimeout(function() {
eventBus._emit("event", "message", item.widget, message);
}, 0);
}
});
},
// Set up all supported events for a widget.
addEventHandlers: function BW_addEventHandlers(item) {
let contentType = this.getContentType(item.widget);
// Detect if document consists of a single image.
function isImageDoc(doc) {
return doc.body.childNodes.length == 1 &&
doc.body.firstElementChild &&
doc.body.firstElementChild.tagName == "IMG";
}
let listener = function(e) {
// Ignore event firings that target the iframe.
if (e.target == item.node.firstElementChild)
return;
// The widget only supports left-click for now,
// so ignore right-clicks.
if (e.type == "click" && e.button == 2)
return;
// Proxy event to the widget
require("timer").setTimeout(function() {
eventBus._emit("event", EVENTS[e.type], item.widget, null, item.node);
}, 0);
};
item.eventListeners = {};
let iframe = item.node.firstElementChild;
for (let [type, method] in Iterator(EVENTS)) {
iframe.addEventListener(type, listener, true, true);
// Store listeners for later removal
item.eventListeners[type] = listener;
}
// On document load, make modifications required for nice default
// presentation.
function loadListener(e) {
// Ignore event firings that target the iframe
if (e.target == iframe)
return;
// Ignore about:blank loads
if (e.type == "load" && e.target.location == "about:blank")
return;
let doc = e.target;
if (contentType == CONTENT_TYPE_IMAGE || isImageDoc(doc)) {
// Force image content to size.
// Add-on authors must size their images correctly.
doc.body.firstElementChild.style.width = item.widget.width + "px";
doc.body.firstElementChild.style.height = "16px";
}
// Allow all content to fill the box by default.
doc.body.style.margin = "0";
}
iframe.addEventListener("load", loadListener, true, true);
item.eventListeners["load"] = loadListener;
},
// Removes an array of items from the window.
removeItems: function BW_removeItems(removedItems) {
removedItems.forEach(function(removedItem) {
let entry = this._items.filter(function(entry) entry.widget == removedItem).shift();
if (entry) {
// remove event listeners
for (let [type, listener] in Iterator(entry.eventListeners))
entry.node.firstElementChild.removeEventListener(type, listener, true);
// remove dom node
entry.node.parentNode.removeChild(entry.node);
// remove entry
this._items.splice(this._items.indexOf(entry), 1);
// cleanup symbiont
entry.symbiont.destroy();
// cleanup entry itself
entry.eventListeners = null;
entry.widget = null;
entry.symbiont = null;
}
}, this);
},
// Undoes all modifications to the window. The BrowserWindow
// should not be used afterward.
destroy: function BW_destroy() {
// Remove all items from the panel
let len = this._items.length;
for (let i = 0; i < len; i++)
this.removeItems([this._items[0].widget]);
this.window.removeEventListener("keypress", this, false);
}
};
// Init the browserManager only after setting prototypes and such above, because
// it will cause browserManager.onTrack to be called immediately if there are
// open windows.
browserManager.init();

View File

@ -0,0 +1,236 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Felipe Gomes <felipc@gmail.com> (Original author)
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
if (!require("xul-app").is("Firefox")) {
throw new Error([
"The windows module currently supports only Firefox. In the future",
" we would like it to support other applications, however. Please see ",
"https://bugzilla.mozilla.org/show_bug.cgi?id=571449 for more information."
].join(""));
}
const { Cc, Ci } = require('chrome'),
{ Trait } = require('traits'),
{ List } = require('list'),
{ EventEmitter } = require('events'),
{ WindowTabs, WindowTabTracker } = require('windows/tabs'),
{ WindowDom } = require('windows/dom'),
{ WindowLoader } = require('windows/loader'),
{ WindowTrackerTrait } = require('window-utils'),
{ Options } = require('tabs/tab'),
// { Sidebars } = require('window/sidebars');
{ utils } = require('xpcom'),
apiUtils = require('api-utils'),
unload = require('unload'),
WM = Cc['@mozilla.org/appshell/window-mediator;1'].
getService(Ci.nsIWindowMediator),
BROWSER = 'navigator:browser';
/**
* Window trait composes safe wrappers for browser window that are E10S
* compatible.
*/
const BrowserWindowTrait = Trait.compose(
EventEmitter,
WindowDom.resolve({ close: '_close' }),
WindowTabs,
WindowTabTracker,
WindowLoader,
/* WindowSidebars, */
Trait.compose({
_emit: Trait.required,
_close: Trait.required,
_load: Trait.required,
/**
* Constructor returns wrapper of the specified chrome window.
* @param {nsIWindow} window
*/
constructor: function BrowserWindow(options) {
// make sure we don't have unhandled errors
this.on('error', console.exception.bind(console));
if ('onOpen' in options)
this.on('open', options.onOpen);
if ('onClose' in options)
this.on('close', options.onClose);
if ('window' in options)
this._window = options.window;
if ('tabs' in options) {
this._tabOptions = Array.isArray(options.tabs) ?
options.tabs.map(Options) :
[ Options(options.tabs) ];
}
else if ('url' in options) {
this._tabOptions = [ Options(options.url) ];
}
this._load();
return this;
},
_tabOptions: [],
_onLoad: function() {
try {
this._initWindowTabTracker();
} catch(e) {
this._emit('error', e)
}
this._emitOnObject(browserWindows, 'open', this._public);
},
_onUnload: function() {
this._destroyWindowTabTracker();
this._emitOnObject(browserWindows, 'close', this._public);
this._window = null;
// Removing reference from the windows array.
windows.splice(windows.indexOf(this), 1);
this._removeAllListeners('close');
this._removeAllListeners('open');
this._removeAllListeners('ready');
},
close: function close(callback) {
// maybe we should deprecate this with message ?
if (callback) this.on('close', callback);
return this._close();
}
})
);
/**
* Wrapper for `BrowserWindowTrait`. Creates new instance if wrapper for
* window doesn't exists yet. If wrapper already exists then returns it
* instead.
* @params {Object} options
* Options that are passed to the the `BrowserWindowTrait`
* @returns {BrowserWindow}
* @see BrowserWindowTrait
*/
function BrowserWindow(options) {
let chromeWindow = options.window;
for each (let window in windows) {
if (chromeWindow == window._window)
return window._public
}
let window = BrowserWindowTrait(options);
windows.push(window);
return window._public;
}
// to have proper `instanceof` behavior will go away when #596248 is fixed.
BrowserWindow.prototype = BrowserWindowTrait.prototype;
exports.BrowserWindow = BrowserWindow
const windows = [];
/**
* `BrowserWindows` trait is composed out of `List` trait and it represents
* "live" list of currently open browser windows. Instance mutates itself
* whenever new browser window gets opened / closed.
*/
// Very stupid to resolve all `toStrings` but this will be fixed by #596248
const browserWindows = Trait.resolve({ toString: null }).compose(
List.resolve({ constructor: '_initList' }),
EventEmitter.resolve({ toString: null }),
WindowTrackerTrait.resolve({ constructor: '_initTracker', toString: null }),
Trait.compose({
_emit: Trait.required,
_add: Trait.required,
_remove: Trait.required,
// public API
/**
* Constructor creates instance of `Windows` that represents live list of open
* windows.
*/
constructor: function BrowserWindows() {
this._trackedWindows = [];
this._initList();
this._initTracker();
unload.when(this._destructor.bind(this));
},
_destructor: function _destructor() {
this._removeAllListeners('open');
this._removeAllListeners('close');
},
/**
* This property represents currently active window.
* Property is non-enumerable, in order to preserve array like enumeration.
* @type {Window|null}
*/
get activeWindow() {
let window = WM.getMostRecentWindow(BROWSER);
return this._isBrowser(window) ? BrowserWindow({ window: window }) : null;
},
open: function open(options) {
if (typeof options === "string")
// `tabs` option is under review and may be removed.
options = { tabs: [Options(options)] };
return BrowserWindow(options);
},
/**
* Returns true if specified window is a browser window.
* @param {nsIWindow} window
* @returns {Boolean}
*/
_isBrowser: function _isBrowser(window)
BROWSER === window.document.documentElement.getAttribute("windowtype")
,
/**
* Internal listener which is called whenever new window gets open.
* Creates wrapper and adds to this list.
* @param {nsIWindow} chromeWindow
*/
_onTrack: function _onTrack(chromeWindow) {
if (!this._isBrowser(chromeWindow)) return;
let window = BrowserWindow({ window: chromeWindow });
this._add(window);
this._emit('open', window);
},
/**
* Internal listener which is called whenever window gets closed.
* Cleans up references and removes wrapper from this list.
* @param {nsIWindow} window
*/
_onUntrack: function _onUntrack(chromeWindow) {
if (!this._isBrowser(chromeWindow)) return;
let window = BrowserWindow({ window: chromeWindow });
// `_onUnload` method of the `BrowserWindow` will remove `chromeWindow`
// from the `windows` array.
this._remove(window);
this._emit('close', window);
}
}).resolve({ toString: null })
)();
exports.browserWindows = browserWindows;

View File

@ -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 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 ***** */
// This is the first code that's ever run in a Jetpack process. It sets up
// infrastructure and receivers needed to start a Jetpack-based addon
// in a separate process.
// A list of scripts to inject into all new CommonJS module sandboxes.
var injectedSandboxScripts = [];
// A table of all CommonJS modules currently loaded.
var modules = {};
// This object represents the chrome process, and can be used to
// communicate with it.
var chrome = {
createHandle: function() {
return createHandle();
},
on: function(type, listener) {
registerReceiver(type, listener);
},
removeListener: function(type, listener) {
unregisterReceiver(type, listener);
},
send: function(type) {
sendMessage.apply(this, arguments);
},
call: function(name) {
var result = callMessage.apply(this, arguments);
if (result.length > 1)
throw new Error("More than one result received for call '" + name +
"': " + result.length);
if (result.length == 0)
throw new Error("No receiver registered for call '" + name + "'");
if (result[0].exception) {
throw Object.create(Error.prototype, {
message: { value: result[0].exception.message, enumerable: true },
fileName: { value: result[0].exception.fileName, enumerable: true },
lineNumber: { value: result[0].exception.lineNumber, enumerable: true },
// Concatenate the stack from the other process with one from this
// process, so callers have access to the full stack.
stack: { value: result[0].exception.stack + (new Error()).stack,
enumerable: true }
});
}
return result[0].returnValue;
}
};
// Use this for really low-level debugging of this script.
function dump(msg) {
// Don't use chrome.send() to avoid infinite recursion when
// debugging chrome.send() itself.
sendMessage("dump", msg);
}
// Taken from plain-text-console.js.
function stringify(arg) {
try {
return String(arg);
}
catch(ex) {
return "<toString() error>";
}
}
// Set up our "proxy" objects that just send messages to our parent
// process to do the real work.
var console = {
exception: function(e) {
chrome.send('console:exception', e);
},
trace: function() {
chrome.send('console:trace', new Error());
}
};
['log', 'debug', 'info', 'warn', 'error'].forEach(function(method) {
console[method] = function() {
chrome.send('console:' + method, Array.map(arguments, stringify));
}
});
var memory = {
track: function() {
/* TODO */
}
};
function makeRequire(base) {
var resolvedNames = {};
function require(name) {
// first, have we already require()d this name from this base? Just
// re-use the module
if (name && name in resolvedNames)
return resolvedNames[name].exports;
// if not, resolve relative import names by asking the browser-process
// side for the URL/filename of the module this points to
var response = chrome.call("require", base, name);
switch (response.code) {
case "not-found":
throw new Error("Unknown module '" + name + "'.");
case "access-denied":
throw new Error("Module '" + name + "' requires chrome privileges " +
"and has no e10s adapter.");
case "error":
throw new Error("An unexpected error occurred in the chrome " +
"process.");
case "ok":
break;
default:
throw new Error("Internal error: unknown response code '" +
response.code + "'");
};
// do we already have a module for this filename?
if (response.script.filename in modules) {
module = resolvedNames[name] = modules[response.script.filename];
return module.exports;
}
var module = createSandbox();
function injectScript(script) {
evalInSandbox(module, '//@line 1 "' + script.filename +
'"\n' + script.contents);
}
injectedSandboxScripts.forEach(injectScript);
modules[response.script.filename] = resolvedNames[name] = module;
// Set up the globals of the sandbox.
module.exports = {};
module.console = console;
module.memory = memory;
module.require = makeRequire(response.script.filename);
module.__url__ = response.script.filename;
if (response.needsMessaging)
module.chrome = chrome;
injectScript(response.script);
return module.exports;
};
return require;
}
chrome.on(
"addInjectedSandboxScript",
function(name, script) {
injectedSandboxScripts.push(script);
});
chrome.on(
"startMain",
function(name, mainName, options) {
var mainRequire = makeRequire(null);
var main = mainRequire(mainName);
var callbacks = {
quit: function quit(status) {
if (status === undefined)
status = "OK";
chrome.send("quit", status);
}
};
if ('main' in main)
main.main(options, callbacks);
});

View File

@ -0,0 +1,184 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (Original Author)
* Edward Lee <edilee@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 ***** */
// The possible return values of getTypeOf.
const VALID_TYPES = [
"array",
"boolean",
"function",
"null",
"number",
"object",
"string",
"undefined",
];
/**
* Returns a function C that creates instances of privateCtor. C may be called
* with or without the new keyword. The prototype of each instance returned
* from C is C.prototype, and C.prototype is an object whose prototype is
* privateCtor.prototype. Instances returned from C will therefore be instances
* of both C and privateCtor. Additionally, the constructor of each instance
* returned from C is C.
*
* @param privateCtor
* A constructor.
* @return A function that makes new instances of privateCtor.
*/
exports.publicConstructor = function publicConstructor(privateCtor) {
function PublicCtor() {
let obj = { constructor: PublicCtor, __proto__: PublicCtor.prototype };
memory.track(obj, privateCtor.name);
privateCtor.apply(obj, arguments);
return obj;
}
PublicCtor.prototype = { __proto__: privateCtor.prototype };
return PublicCtor;
};
/**
* Returns a validated options dictionary given some requirements. If any of
* the requirements are not met, an exception is thrown.
*
* @param options
* An object, the options dictionary to validate. It's not modified.
* If it's null or otherwise falsey, an empty object is assumed.
* @param requirements
* An object whose keys are the expected keys in options. Any key in
* options that is not present in requirements is ignored. Each value
* in requirements is itself an object describing the requirements of
* its key. There are four optional keys in this object:
* map: A function that's passed the value of the key in options.
* map's return value is taken as the key's value in the final
* validated options, is, and ok. If map throws an exception
* it's caught and discarded, and the key's value is its value in
* options.
* is: An array containing any number of the typeof type names. If
* the key's value is none of these types, it fails validation.
* Arrays and null are identified by the special type names
* "array" and "null"; "object" will not match either. No type
* coercion is done.
* ok: A function that's passed the key's value. If it returns
* false, the value fails validation.
* msg: If the key's value fails validation, an exception is thrown.
* This string will be used as its message. If undefined, a
* generic message is used, unless is is defined, in which case
* the message will state that the value needs to be one of the
* given types.
* @return An object whose keys are those keys in requirements that are also in
* options and whose values are the corresponding return values of map
* or the corresponding values in options. Note that any keys not
* shared by both requirements and options are not in the returned
* object.
*/
exports.validateOptions = function validateOptions(options, requirements) {
options = options || {};
let validatedOptions = {};
let mapThrew = false;
for (let [key, req] in Iterator(requirements)) {
let [optsVal, keyInOpts] = (key in options) ?
[options[key], true] :
[undefined, false];
if (req.map) {
try {
optsVal = req.map(optsVal);
}
catch (err) {
mapThrew = true;
}
}
if (req.is) {
// Sanity check the caller's type names.
req.is.forEach(function (typ) {
if (VALID_TYPES.indexOf(typ) < 0) {
let msg = 'Internal error: invalid requirement type "' + typ + '".';
throw new Error(msg);
}
});
if (req.is.indexOf(getTypeOf(optsVal)) < 0)
throw requirementError(key, req);
}
if (req.ok && !req.ok(optsVal))
throw requirementError(key, req);
if (keyInOpts || (req.map && !mapThrew))
validatedOptions[key] = optsVal;
}
return validatedOptions;
};
exports.addIterator = function addIterator(obj, keysValsGenerator) {
obj.__iterator__ = function(keysOnly, keysVals) {
let keysValsIterator = keysValsGenerator.call(this);
// "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values,
// and "for (.. in Iterator(..))" gets [key, value] pairs.
let index = keysOnly ? 0 : 1;
while (true)
yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index];
};
};
// Similar to typeof, except arrays and null are identified by "array" and
// "null", not "object".
let getTypeOf = exports.getTypeOf = function getTypeOf(val) {
let typ = typeof(val);
if (typ === "object") {
if (!val)
return "null";
if (Array.isArray(val))
return "array";
}
return typ;
}
// Returns a new Error with a nice message.
function requirementError(key, requirement) {
let msg = requirement.msg;
if (!msg) {
msg = 'The option "' + key + '" ';
msg += requirement.is ?
"must be one of the following types: " + requirement.is.join(", ") :
"is invalid.";
}
return new Error(msg);
}

View File

@ -0,0 +1,93 @@
/* ***** 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 String Bundle.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Myk Melez <myk@mozilla.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 ***** */
const {Cc,Ci} = require("chrome");
const apiUtils = require("api-utils");
/**
* A bundle of strings.
*
* @param url {String}
* the URL of the string bundle
*/
exports.StringBundle = apiUtils.publicConstructor(function StringBundle(url) {
let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService).
createBundle(url);
this.__defineGetter__("url", function () url);
/**
* Get a string from the bundle.
*
* @param name {String}
* the name of the string to get
* @param args {array} [optional]
* an array of arguments that replace occurrences of %S in the string
*
* @returns {String} the value of the string
*/
this.get = function strings_get(name, args) {
try {
if (args)
return stringBundle.formatStringFromName(name, args, args.length);
else
return stringBundle.GetStringFromName(name);
}
catch(ex) {
// f.e. "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE)
// [nsIStringBundle.GetStringFromName]"
throw new Error("String '" + name + "' could not be retrieved from the " +
"bundle due to an unknown error (it doesn't exist?).");
}
},
/**
* Iterate the strings in the bundle.
*
*/
apiUtils.addIterator(
this,
function keysValsGen() {
let enumerator = stringBundle.getSimpleEnumeration();
while (enumerator.hasMoreElements()) {
let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
yield [elem.key, elem.value];
}
}
);
});

View File

@ -0,0 +1,133 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim:set ts=2 sw=2 sts=2 et filetype=javascript
* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (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 ***** */
exports.ByteReader = ByteReader;
exports.ByteWriter = ByteWriter;
const {Cc, Ci} = require("chrome");
// This just controls the maximum number of bytes we read in at one time.
const BUFFER_BYTE_LEN = 0x8000;
function ByteReader(inputStream) {
const self = this;
let stream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
stream.setInputStream(inputStream);
let manager = new StreamManager(this, stream);
this.read = function ByteReader_read(numBytes) {
manager.ensureOpened();
if (typeof(numBytes) !== "number")
numBytes = Infinity;
let data = "";
let read = 0;
try {
while (true) {
let avail = stream.available();
let toRead = Math.min(numBytes - read, avail, BUFFER_BYTE_LEN);
if (toRead <= 0)
break;
data += stream.readBytes(toRead);
read += toRead;
}
}
catch (err) {
throw new Error("Error reading from stream: " + err);
}
return data;
};
}
function ByteWriter(outputStream) {
const self = this;
let stream = Cc["@mozilla.org/binaryoutputstream;1"].
createInstance(Ci.nsIBinaryOutputStream);
stream.setOutputStream(outputStream);
let manager = new StreamManager(this, stream);
this.write = function ByteWriter_write(str) {
manager.ensureOpened();
try {
stream.writeBytes(str, str.length);
}
catch (err) {
throw new Error("Error writing to stream: " + err);
}
};
}
// This manages the lifetime of stream, a ByteReader or ByteWriter. It defines
// closed and close() on stream and registers an unload listener that closes
// rawStream if it's still opened. It also provides ensureOpened(), which
// throws an exception if the stream is closed.
function StreamManager(stream, rawStream) {
const self = this;
this.rawStream = rawStream;
this.opened = true;
stream.__defineGetter__("closed", function stream_closed() {
return !self.opened;
});
stream.close = function stream_close() {
self.ensureOpened();
self.unload();
};
require("unload").ensure(this);
}
StreamManager.prototype = {
ensureOpened: function StreamManager_ensureOpened() {
if (!this.opened)
throw new Error("The stream is closed and cannot be used.");
},
unload: function StreamManager_unload() {
this.rawStream.close();
this.opened = false;
}
};

View File

@ -0,0 +1,139 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (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 ***** */
exports.Collection = Collection;
/**
* Adds a collection property to the given object. Setting the property to a
* scalar value empties the collection and adds the value. Setting it to an
* array empties the collection and adds all the items in the array.
*
* @param obj
* The property will be defined on this object.
* @param propName
* The name of the property.
* @param array
* If given, this will be used as the collection's backing array.
*/
exports.addCollectionProperty = function addCollProperty(obj, propName, array) {
array = array || [];
let publicIface = new Collection(array);
obj.__defineSetter__(propName, function (itemOrItems) {
array.splice(0, array.length);
publicIface.add(itemOrItems);
});
obj.__defineGetter__(propName, function () {
return publicIface;
});
};
/**
* A collection is ordered, like an array, but its items are unique, like a set.
*
* @param array
* The collection is backed by an array. If this is given, it will be
* used as the backing array. This way the caller can fully control the
* collection. Otherwise a new empty array will be used, and no one but
* the collection will have access to it.
*/
function Collection(array) {
array = array || [];
/**
* Provides iteration over the collection. Items are yielded in the order
* they were added.
*/
this.__iterator__ = function Collection___iterator__() {
let items = array.slice();
for (let i = 0; i < items.length; i++)
yield items[i];
};
/**
* The number of items in the collection.
*/
this.__defineGetter__("length", function Collection_get_length() {
return array.length;
});
/**
* Adds a single item or an array of items to the collection. Any items
* already contained in the collection are ignored.
*
* @param itemOrItems
* An item or array of items.
* @return The collection.
*/
this.add = function Collection_add(itemOrItems) {
let items = toArray(itemOrItems);
for (let i = 0; i < items.length; i++) {
let item = items[i];
if (array.indexOf(item) < 0)
array.push(item);
}
return this;
};
/**
* Removes a single item or an array of items from the collection. Any items
* not contained in the collection are ignored.
*
* @param itemOrItems
* An item or array of items.
* @return The collection.
*/
this.remove = function Collection_remove(itemOrItems) {
let items = toArray(itemOrItems);
for (let i = 0; i < items.length; i++) {
let idx = array.indexOf(items[i]);
if (idx >= 0)
array.splice(idx, 1);
}
return this;
};
};
function toArray(itemOrItems) {
let isArr = itemOrItems &&
itemOrItems.constructor &&
itemOrItems.constructor.name === "Array";
return isArr ? itemOrItems : [itemOrItems];
}

View File

@ -0,0 +1,44 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
exports.Loader = require('content/loader').Loader;
exports.Symbiont = require('content/symbiont').Symbiont;
exports.Worker = require('content/worker').Worker;

View File

@ -0,0 +1,198 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
const { EventEmitter } = require('events');
const { validateOptions, getTypeOf } = require('api-utils');
const { URL, toFilename } = require('url');
const file = require("file");
// map of property validations
const valid = {
contentURL: {
ok: function (value) {
try {
URL(value);
}
catch(e) {
return false;
}
return true;
},
msg: 'The `contentURL` option must be a valid URL.'
},
contentScriptFile: {
is: ['undefined', 'null', 'string', 'array'],
map: function(value) 'undefined' === getTypeOf(value) ? null : value,
ok: function(value) {
if (getTypeOf(value) === 'array') {
// Make sure every item is a local file URL.
return value.every(function (item) {
try {
toFilename(item);
return true;
}
catch(e) {
return false;
}
});
}
return true;
},
msg: 'The `contentScriptFile` option must be a local file URL or an array of'
+ 'URLs.'
},
contentScript: {
is: ['undefined', 'null', 'string', 'array'],
map: function(value) 'undefined' === getTypeOf(value) ? null : value,
ok: function(value) 'array' !== getTypeOf(value) ? true :
value.every(function(item) 'string' === getTypeOf(item))
,
msg: 'The script option must be a string or an array of strings.'
},
contentScriptWhen: {
is: ['string'],
ok: function(value) ['start', 'ready'].indexOf(value) >= 0,
map: function(value) value || 'start',
msg: 'The `contentScriptWhen` option must be either "start" or "ready".'
}
};
/**
* Shortcut function to validate property with validation.
* @param {Object|Number|String} suspect
* value to validate
* @param {Object} validation
* validation rule passed to `api-utils`
*/
function validate(suspect, validation) validateOptions(
{ $: suspect },
{ $: validation }
).$
function Allow(script) ({
get script() script,
set script(value) script = !!value
})
/**
* Trait is intended to be used in some composition. It provides set of core
* properties and bounded validations to them. Trait is useful for all the
* compositions providing high level APIs for interaction with content.
* Property changes emit `"propertyChange"` events on instances.
*/
const Loader = EventEmitter.compose({
/**
* Permissions for the content, with the following keys:
* @property {Object} [allow = { script: true }]
* @property {Boolean} [allow.script = true]
* Whether or not to execute script in the content. Defaults to true.
*/
get allow() this._allow || (this._allow = Allow(true)),
set allow(value) this.allow.script = value && value.script,
_allow: null,
/**
* The content to load. Either a string of HTML or a URL.
* @type {String}
*/
get contentURL() this._contentURL,
set contentURL(value) {
value = validate(value, valid.contentURL);
if (this._contentURL != value) {
this._emit('propertyChange', {
contentURL: this._contentURL = value
});
}
},
_contentURL: null,
/**
* When to load the content scripts.
* Possible values are "start" (default), which loads them as soon as
* the window object for the page has been created, and "ready", which
* loads them once the DOM content of the page has been loaded.
* Property change emits `propertyChange` event on instance with this key
* and new value.
* @type {'start'|'ready'}
*/
get contentScriptWhen() this._contentScriptWhen,
set contentScriptWhen(value) {
value = validate(value, valid.contentScriptWhen);
if (value !== this._contentScriptWhen) {
this._emit('propertyChange', {
contentScriptWhen: this._contentScriptWhen = value
});
}
},
_contentScriptWhen: 'start',
/**
* The URLs of content scripts.
* Property change emits `propertyChange` event on instance with this key
* and new value.
* @type {String[]}
*/
get contentScriptFile() this._contentScriptFile,
set contentScriptFile(value) {
value = validate(value, valid.contentScriptFile);
if (value != this._contentScriptFile) {
this._emit('propertyChange', {
contentScriptFile: this._contentScriptFile = value
});
}
},
_contentScriptFile: null,
/**
* The texts of content script.
* Property change emits `propertyChange` event on instance with this key
* and new value.
* @type {String|undefined}
*/
get contentScript() this._contentScript,
set contentScript(value) {
value = validate(value, valid.contentScript);
if (value != this._contentScript) {
this._emit('propertyChange', {
contentScript: this._contentScript = value
});
}
},
_contentScript: null
});
exports.Loader = Loader;

View File

@ -0,0 +1,174 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Myk Melez <myk@mozilla.org> (Original Author)
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
const { Worker } = require('./worker');
const { Loader } = require('./loader');
const hiddenFrames = require("hidden-frame");
const observers = require('observer-service');
const unload = require("unload");
const xulApp = require("xul-app");
const HAS_DOCUMENT_ELEMENT_INSERTED =
xulApp.versionInRange(xulApp.platformVersion, "2.0b6", "*");
const ON_START = HAS_DOCUMENT_ELEMENT_INSERTED ? 'document-element-inserted' :
'content-document-global-created';
const ON_READY = 'DOMContentLoaded';
/**
* This trait is layered on top of `Worker` and in contrast to symbiont
* Worker constructor requires `content` option that represents content
* that will be loaded in the provided frame, if frame is not provided
* Worker will create hidden one.
*/
const Symbiont = Worker.resolve({
constructor: '_onInit',
destroy: '_workerDestroy'
}).compose({
_window: Worker.required,
_onInit: Worker.required,
/**
* The constructor requires all the options that are required by
* `require('content').Worker` with the difference that the `frame` option
* is optional. If `frame` is not provided, `contentURL` is expected.
* @param {Object} options
* @param {String} options.contentURL
* URL of a content to load into `this._frame` and create worker for.
* @param {Element} [options.frame]
* iframe element that is used to load `options.contentURL` into.
* if frame is not provided hidden iframe will be created.
*/
constructor: function Symbiont(options) {
options = options || {};
if ('contentURL' in options)
this.contentURL = options.contentURL;
if ('contentScriptWhen' in options)
this.contentScriptWhen = options.contentScriptWhen;
if ('contentScriptFile' in options)
this.contentScriptFile = options.contentScriptFile;
if ('contentScript' in options)
this.contentScript = options.contentScript;
if ('allow' in options)
this.allow = options.allow;
if ('onError' in options)
this.on('error', options.onError);
if ('onMessage' in options)
this.on('message', options.onMessage);
if ('frame' in options) {
this._initFrame(options.frame);
}
else {
let self = this;
hiddenFrames.add(hiddenFrames.HiddenFrame({
onReady: function onFrame() {
self._initFrame(this.element);
}
}));
}
unload.when(this.destroy.bind(this));
},
destroy: function destroy() {
// The frame might not have been initialized yet.
if (!this._frame)
return;
if ('ready' === this.contentScriptWhen)
this._frame.removeEventListener(ON_READY, this._onReady, true);
else
observers.remove(ON_START, this._onStart);
this._frame = null;
this._workerDestroy();
},
/**
* XUL iframe or browser elements with attribute `type` being `content`.
* Used to create `ContentSymbiont` from.
* @type {nsIFrame|nsIBrowser}
*/
_frame: null,
/**
* Listener to the `'frameReady"` event (emitted when `iframe` is ready).
* Removes listener, sets right permissions to the frame and loads content.
*/
_initFrame: function _initFrame(frame) {
this._frame = frame;
frame.docShell.allowJavascript = this.allow.script;
frame.setAttribute("src", this._contentURL);
if ('ready' === this.contentScriptWhen) {
frame.addEventListener(
ON_READY,
this._onReady = this._onReady.bind(this),
true
);
} else {
observers.add(ON_START, this._onStart = this._onStart.bind(this));
}
},
/**
* Creates port when the DOM is ready. Called if the value of
* `contentScriptWhen` is "ready".
*/
_onReady: function _onReady(event) {
let frame = this._frame;
if (event.target == frame.contentDocument) {
frame.removeEventListener(ON_READY, this._onReady, true);
this._window = frame.contentWindow.wrappedJSObject;
this._onInit();
}
},
/**
* Creates port when the global object is created. Called if the value of
* `contentScriptWhen` is "start".
*/
_onStart: function _onStart(domObj) {
let window = HAS_DOCUMENT_ELEMENT_INSERTED ? domObj.defaultView : domObj;
if (window && window == this._frame.contentWindow) {
observers.remove(ON_START, this._onStart);
this._window = window.wrappedJSObject;
this._onInit();
}
}
}, Loader);
exports.Symbiont = Symbiont;

View File

@ -0,0 +1,376 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
const es5code = require('cuddlefish').es5code;
const { Trait } = require('traits');
const { EventEmitter } = require('events');
const { Ci, Cu, Cc } = require('chrome');
const timer = require('timer');
const { toFilename } = require('url');
const file = require('file');
const unload = require('unload');
const JS_VERSION = '1.8';
const ERR_DESTROYED =
"The page has been destroyed and can no longer be used.";
/**
* Extended `EventEmitter` allowing us to emit events asynchronously.
*/
const AsyncEventEmitter = EventEmitter.compose({
/**
* Emits event in the next turn of event loop.
*/
_asyncEmit: function _asyncEmit() {
timer.setTimeout(function emitter(emit, scope, params) {
emit.apply(scope, params);
}, 0, this._emit, this, arguments)
}
});
/**
* Function for sending data to the port. Used to send messages
* from the worker to the symbiont and other way round.
* Function validates that data is a `JSON` or primitive value and emits
* 'message' event on the port in the next turn of the event loop.
* _Later this will be sending data across process boundaries._
* @param {JSON|String|Number|Boolean} data
*/
function postMessage(data) {
if (!this._port)
throw new Error(ERR_DESTROYED);
this._port._asyncEmit('message', JSON.parse(JSON.stringify(data)));
}
/**
* Local trait providing implementation of the workers global scope.
* Used to configure global object in the sandbox.
* @see http://www.w3.org/TR/workers/#workerglobalscope
*/
const WorkerGlobalScope = AsyncEventEmitter.compose({
on: Trait.required,
_removeAllListeners: Trait.required,
// wrapped functions from `'timer'` module.
// Wrapper adds `try catch` blocks to the callbacks in order to
// emit `error` event on a symbiont if exception is thrown in
// the Worker global scope.
// @see http://www.w3.org/TR/workers/#workerutils
setTimeout: function setTimeout(callback, delay) {
let params = Array.slice(arguments, 2);
return timer.setTimeout(function(port) {
try {
callback.apply(null, params);
} catch(e) {
port._asyncEmit('error', e);
}
}, delay, this._port);
},
clearTimeout: timer.clearTimeout,
setInterval: function setInterval(callback, delay) {
let params = Array.slice(arguments, 2);
return timer.setInterval(function(port) {
try {
callback.apply(null, params);
} catch(e) {
port._asyncEmit('error', e);
}
}, delay, this._port);
},
clearInterval: timer.clearInterval,
/**
* `onMessage` function defined in the global scope of the worker context.
*/
get onMessage() this._onMessage,
set onMessage(value) {
let listener = this._onMessage;
if (listener && value !== listener) {
this.removeListener('message', listener);
this._onMessage = undefined;
}
if (value)
this.on('message', this._onMessage = value);
},
_onMessage: undefined,
/**
* @see postMesssage
*/
postMessage: postMessage,
/**
* Alias to the global scope in the context of worker. Similar to
* `window` concept.
*/
get self() this._public,
/**
* Configures sandbox and loads content scripts into it.
* @param {Worker} port
* content worker
*/
constructor: function WorkerGlobalScope(port) {
// connect ports
this._port = port;
port._port = this;
this.on('unload', this._destructor = this._destructor.bind(this));
// XXX I think the principal should be `this._port._frame.contentWindow`,
// but script doesn't work correctly when I set it to that value.
// Events don't get registered; even dump() fails.
//
// FIXME: figure out the problem and resolve it, so we can restrict
// the sandbox to the same set of privileges the page has (plus any others
// it gets to access through the object that created it).
//
// XXX when testing `this._port.frame.contentWindow`, I found that setting
// the principal to its `this._port.frame.contentWindow.wrappedJSObject`
// resolved some test leaks; that was before I started clearing the
// principal of the sandbox on unload, though, so perhaps it is no longer
// a problem.
let sandbox = this._sandbox = new Cu.Sandbox(
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
// Shimming natives in sandbox so that they support ES5 features
Cu.evalInSandbox(es5code.contents, sandbox, "1.8", es5code.filename);
let window = port._window;
let publicAPI = this._public;
let keys = Object.getOwnPropertyNames(publicAPI);
for each (let key in keys) {
if ('onMessage' === key) continue;
Object.defineProperty(
sandbox, key, Object.getOwnPropertyDescriptor(publicAPI, key)
);
}
Object.defineProperties(sandbox, {
onMessage: {
get: function() publicAPI.onMesssage,
set: function(value) publicAPI.onMessage = value,
configurable: true
},
console: { value: console, configurable: true },
});
// Chain the global object for the sandbox to the global object for
// the frame. This supports JavaScript libraries like jQuery that depend
// on the presence of certain properties in the global object, like window,
// document, location, and navigator.
sandbox.__proto__ = window;
// Alternate approach:
// Define each individual global on which JavaScript libraries depend
// in the global object of the sandbox. This is hard to get right,
// since it requires a priori knowledge of the libraries developers use,
// and exceptions in those libraries aren't always reported. It's also
// brittle, prone to breaking when those libraries change. But it might
// make it easier to avoid namespace conflicts.
// In my testing with jQuery, I found that the library needed window,
// document, location, and navigator to avoid throwing exceptions,
// although even with those globals defined, the library still doesn't
// work, so it also needs something else about which it unfortunately does
// not complain.
// sandbox.window = window;
// sandbox.document = window.document;
// sandbox.location = window.location;
// sandbox.navigator = window.navigator;
// The order of `contentScriptFile` and `contentScript` evaluation is
// intentional, so programs can load libraries like jQuery from script URLs
// and use them in scripts.
let contentScriptFile = ('contentScriptFile' in port) ? port.contentScriptFile
: null,
contentScript = ('contentScript' in port) ? port.contentScript : null;
if (contentScriptFile) {
if (Array.isArray(contentScriptFile))
this._importScripts.apply(this, contentScriptFile);
else
this._importScripts(contentScriptFile);
}
if (contentScript) {
this._evaluate(
Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
);
}
},
_destructor: function _destructor() {
this._removeAllListeners();
let publicAPI = this._public,
sandbox = this._sandbox;
delete sandbox.__proto__;
for (let key in publicAPI)
delete sandbox[key];
this._sandbox = null;
this._port = null;
this._onMessage = undefined;
},
/**
* JavaScript sandbox where all the content scripts are evaluated.
* {Sandbox}
*/
_sandbox: null,
/**
* Reference to the worker.
* @type {Worker}
*/
_port: null,
/**
* Evaluates code in the sandbox.
* @param {String} code
* JavaScript source to evaluate.
* @param {String} [filename='javascript:' + code]
* Name of the file
*/
_evaluate: function(code, filename) {
filename = filename || 'javascript:' + code;
try {
Cu.evalInSandbox(code, this._sandbox, JS_VERSION, filename, 1);
}
catch(e) {
this._port._asyncEmit('error', e);
}
},
/**
* Imports scripts to the sandbox by reading files under urls and
* evaluating its source. If exception occurs during evaluation
* `"error"` event is emitted on the worker.
* This is actually an analog to the `importScript` method in web
* workers but in our case it's not exposed even though content
* scripts may be able to do it synchronously since IO operation
* takes place in the UI process.
*/
_importScripts: function _importScripts(url) {
let urls = Array.slice(arguments, 0);
for each (let contentScriptFile in urls) {
try {
let filename = toFilename(contentScriptFile);
this._evaluate(file.read(filename), filename);
}
catch(e) {
this._port._asyncEmit('error', e)
}
}
}
});
/**
* Message-passing facility for communication between code running
* in the content and add-on process.
* @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker
*/
const Worker = AsyncEventEmitter.compose({
on: Trait.required,
_asyncEmit: Trait.required,
_removeAllListeners: Trait.required,
/**
* Sends a message to the worker's global scope. Method takes single
* argument, which represents data to be sent to the worker. The data may
* be any primitive type value or `JSON`. Call of this method asynchronously
* emits `message` event with data value in the global scope of this
* symbiont.
*
* `message` event listeners can be set either by calling
* `self.on` with a first argument string `"message"` or by
* implementing `onMessage` function in the global scope of this worker.
* @param {Number|String|JSON} data
*/
postMessage: postMessage,
constructor: function Worker(options) {
options = options || {};
if ('window' in options)
this._window = options.window;
if ('contentScriptFile' in options)
this.contentScriptFile = options.contentScriptFile;
if ('contentScript' in options)
this.contentScript = options.contentScript;
if ('onError' in options)
this.on('error', options.onError);
if ('onMessage' in options)
this.on('message', options.onMessage);
unload.when(this.destroy.bind(this));
WorkerGlobalScope(this); // will set this._port pointing to the private API
},
get url() {
return this._window.document.location.href;
},
get tab() {
let tab = require("tabs/tab");
return tab.getTabForWindow(this._window);
},
/**
* Tells _port to unload itself and removes all the references from itself.
*/
destroy: function destroy() {
this._removeAllListeners('message');
this._removeAllListeners('error');
if (this._port) // maybe unloaded before port is created
this._port._emit('unload');
this._port = null;
this._window = null;
},
/**
* Reference to the global scope of the worker.
* @type {WorkerGlobalScope}
*/
_port: null,
/**
* Reference to the window that is accessible from
* the content scripts.
* @type {Object}
*/
_window: null,
});
exports.Worker = Worker;

View File

@ -0,0 +1,139 @@
/* vim:set ts=2 sw=2 sts=2
* ***** 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 Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
// `var` is being used in the module in order to make it reusable in
// environments in which `let` and `const` is not yet supported.
// Returns `object`'s property value, where `name` is a name of the property.
function get(object, name) {
return object[name];
}
// Assigns `value` to the `object`'s property, where `name` is the name of the
// property.
function set(object, name, value) {
return object[name] = value;
}
/**
* Given an `object` containing a property with the given `name`, create
* a property descriptor that can be used to define alias/proxy properties
* on other objects. A change in the value of an alias will propagate
* to the aliased property and vice versa.
*/
function createAliasProperty(object, name) {
// Getting own property descriptor of an `object` for the given `name` as
// we are going to create proxy analog.
var property = Object.getOwnPropertyDescriptor(object, name);
var descriptor = {
configurable: property.configurable,
enumerable: property.enumerable,
alias: true
};
// If the original property has a getter and/or setter, bind a
// corresponding getter/setter in the alias descriptor to the original
// object, so the `this` object in the getter/setter is the original object
// rather than the alias.
if ("get" in property)
descriptor.get = property.get.bind(object);
if ("set" in property)
descriptor.set = property.set.bind(object);
// If original property was a value property.
if ("value" in property) {
// If original property is a method using it's `object` bounded copy.
if (typeof property.value === "function") {
descriptor.value = property.value.bind(object);
// Also preserving writability of the original property.
descriptor.writable = property.writable;
}
// If the original property was just a data property, we create proxy
// accessors using our custom get/set functions to propagate changes to the
// original `object` and vice versa.
else {
descriptor.get = get.bind(null, object, name);
descriptor.set = set.bind(null, object, name);
}
}
return descriptor;
}
// Defines property on `object` object with a name `alias` if given if not
// defaults to `name` that represents an alias of `source[name]`. If aliased
// property was an assessor or a method `this` pseudo-variable will be `source`
// when invoked. If aliased property was a data property changes on any of the
// aliases will propagate to the `source[name]` and also other way round.
function defineAlias(source, target, name, alias) {
return Object.defineProperty(target, alias || name,
createAliasProperty(source, name));
}
/**
* Function takes any `object` and returns a proxy for its own public
* properties. By default properties are considered to be public if they don't
* start with `"_"`, but default behavior can be overridden if needed, by
* passing array of public property `names` as a second argument. By default
* returned object will be direct decedent of the given `object`'s prototype,
* but this can be overridden by passing third optional argument, that will be
* used as `prototype` instead.
* @param {Object} object
* Object to create cortex for.
* @param {String[]} [names]
* Optional array of public property names.
* @param {Object} [prototype]
* Optional argument that will be used as `prototype` of the returned object,
* if not provided `Object.getPrototypeOf(object)` is used instead.
*/
exports.Cortex = function Cortex(object, names, prototype) {
// Creating a cortex object from the given `prototype`, if one was not
// provided then `prototype` of a given `object` is used. This allows
// consumer to define expected behavior `instanceof`. In common case
// `prototype` argument can be omitted to preserve same behavior of
// `instanceof` as on original `object`.
var cortex = Object.create(prototype || Object.getPrototypeOf(object));
// Creating alias properties on the `cortex` object for all the own
// properties of the original `object` that are contained in `names` array.
// If `names` array is not provided then all the properties that don't
// start with `"_"` are aliased.
Object.getOwnPropertyNames(object).forEach(function (name) {
if ((!names && "_" !== name.charAt(0)) || (names && ~names.indexOf(name)))
defineAlias(object, cortex, name);
});
return cortex;
}

View File

@ -0,0 +1,267 @@
/* ***** 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 = {};
// Load the SecurableModule prerequisite.
var securableModule;
var myURI = Components.stack.filename.split(" -> ").slice(-1)[0];
if (global.require)
// We're being loaded in a SecurableModule.
securableModule = require("securable-module");
else {
var ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
var securableModuleURI = ios.newURI("securable-module.js", null,
ios.newURI(myURI, null, null));
if (securableModuleURI.scheme == "chrome") {
// The securable-module module is at a chrome URI, so we can't
// simply load it via Cu.import(). Let's assume we're in a
// chrome-privileged document and use mozIJSSubScriptLoader.
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
// Import the script, don't pollute the global scope.
securableModule = {__proto__: global};
loader.loadSubScript(securableModuleURI.spec, securableModule);
securableModule = securableModule.SecurableModule;
} else {
securableModule = {};
try {
Cu.import(securableModuleURI.spec, securableModule);
} catch (e if e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
Cu.reportError("Failed to load " + securableModuleURI.spec);
}
}
}
var localFS = new securableModule.LocalFileSystem(myURI);
var es5path = localFS.resolveModule(null, "es5");
var es5code = exports.es5code = localFS.getFile(es5path);
es5code.filename = es5path;
function unloadLoader(reason) {
this.require("unload").send(reason);
}
function maybeLoadMainInJetpackProcess(delegate, packaging) {
return function getModuleExports(basePath, module) {
if (module == packaging.options.main) {
var mainURL = this.fs.resolveModule(basePath, module);
var mainInfo = packaging.getModuleInfo(mainURL);
if (!mainInfo.needsChrome) {
var loader = this;
return {
main: function main(options, callbacks) {
var e10s = loader.require("e10s");
var process = e10s.AddonProcess();
loader.console.log("starting main in remote process.");
process.send("startMain", options.main);
}
};
} else
return null;
}
return (delegate ? delegate.call(this, basePath, module) : null);
};
}
function makeGetModuleExports(delegate) {
return function getModuleExports(basePath, module) {
switch (module) {
case "chrome":
var chrome = { Cc: Components.classes,
Ci: Components.interfaces,
Cu: Components.utils,
Cr: Components.results,
Cm: Components.manager,
components: Components };
return chrome;
case "parent-loader":
return this;
default:
return (delegate ? delegate.call(this, basePath, module) : null);
}
};
}
function modifyModuleSandbox(sandbox, options) {
sandbox.evaluate(es5code);
var filename = options.filename ? options.filename : null;
sandbox.defineProperty("__url__", filename);
}
function makeManifestChecker(packaging) {
var mc = {
_allow: function _allow(loader, basePath, module, module_info) {
if (!basePath) {
return true; /* top-level import */
}
let mi = packaging.getModuleInfo(basePath);
if (mi.needsChrome)
/* The module requires chrome, it can import whatever it
* wants. */
return true;
if (!mi.dependencies) {
/* the parent isn't in the manifest: we know nothing about it */
} else {
if (mi.dependencies[module]) {
/* they're on the list: the require() is allowed, but let's
check that they're loading the right thing */
let parent_mi = packaging.getModuleInfo(basePath);
// parent_mi is the parent, who invoked require()
// module_info is the child, the output of resolveModule
var should_load = parent_mi.dependencies[module].url;
var is_loading = module_info.filename;
if (!should_load) {
/* the linker wasn't able to find the target module when the
XPI was constructed. */
loader.console.warn("require("+ module +") (called from " +
basePath + ") is loading " + is_loading +
", but the manifest couldn't find it");
} else if (should_load != is_loading) {
loader.console.warn("require(" + module + ") (called from " +
basePath + ") is loading " + is_loading +
", but is supposed to be loading " +
should_load);
//return false; // enable this in 0.9
}
return true;
}
}
loader.console.warn("undeclared require(" + module +
") called from " + basePath);
//return false; // enable this in 0.9
return true;
},
allowEval: function allowEval(loader, basePath, module, module_info) {
return this._allow(loader, basePath, module, module_info);
},
allowImport: function allowImport(loader, basePath, module, module_info,
exports) {
if (module == "chrome") {
let parent_mi = packaging.getModuleInfo(basePath);
if (parent_mi.needsChrome)
return true; /* chrome is on the list, allow it */
loader.console.warn("undeclared require(chrome) called from " +
basePath);
//return false; // enable this in 0.9
return true;
}
return this._allow(loader, basePath, module, module_info);
}
};
return mc;
}
var Loader = exports.Loader = function Loader(options) {
var globals = {};
if (options.globals)
for (var name in options.globals)
globals[name] = options.globals[name];
if (options.console)
globals.console = options.console;
if (options.memory)
globals.memory = options.memory;
if ('modules' in options)
throw new Error('options.modules is no longer supported');
var getModuleExports = makeGetModuleExports(options.getModuleExports);
var manifestChecker = undefined;
if (options.packaging) {
manifestChecker = makeManifestChecker(options.packaging);
if (options.packaging.enableE10s)
getModuleExports = maybeLoadMainInJetpackProcess(getModuleExports,
options.packaging);
}
var loaderOptions = {rootPath: options.rootPath,
rootPaths: options.rootPaths,
fs: options.fs,
defaultPrincipal: "system",
globals: globals,
modifyModuleSandbox: modifyModuleSandbox,
securityPolicy: manifestChecker,
getModuleExports: getModuleExports};
var loader = new securableModule.Loader(loaderOptions);
if (!globals.console) {
var console = loader.require("plain-text-console");
globals.console = new console.PlainTextConsole(options.print);
}
if (!globals.memory)
globals.memory = loader.require("memory");
loader.console = globals.console;
loader.memory = globals.memory;
loader.unload = unloadLoader;
return loader;
};
if (global.window) {
// We're being loaded in a chrome window, or a web page with
// UniversalXPConnect privileges.
global.Cuddlefish = 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);

View File

@ -0,0 +1,243 @@
/* ***** 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 ***** */
let {Cc, Ci, Cr} = require('chrome');
let url = require("url");
let file = require("file");
let errors = require("errors");
let jetpackService = Cc["@mozilla.org/jetpack/service;1"]
.getService(Ci.nsIJetpackService);
function AddonProcess(jetpack) {
var syncListeners = {};
this.on = function(name, cb) {
jetpack.registerReceiver(name, function() {
try {
// Intentionally do not return the return value of
// the function; we want developers to use registerCall() for that.
cb.apply(undefined, arguments);
} catch (e) {
console.exception(e);
}
});
};
this.registerCall = function(name, cb) {
if (name in syncListeners)
throw new Error("call already registered for '" + name + "'");
syncListeners[name] = true;
jetpack.registerReceiver(name, errors.catchAndReturn(cb));
};
this.send = function() {
return jetpack.sendMessage.apply(this, arguments);
};
this.createHandle = function() {
return jetpack.createHandle();
};
this.destroy = function() {
try {
jetpack.destroy();
} catch (e if e.result == Cr.NS_ERROR_NOT_INITIALIZED) {}
};
}
function makeScriptFrom(fs, moduleURL) {
// TODO: Why can't we just return fs.getFile(moduleURL) here?
return {
filename: moduleURL,
contents: fs.getFile(moduleURL).contents
};
}
var defaultConsole = console;
exports.AddonProcess = function createAddonProcess(options) {
if (!options)
options = {};
var jetpack = jetpackService.createJetpack();
var process = new AddonProcess(jetpack);
var registeredModules = {};
var console = options.console || defaultConsole;
var pkg = options.packaging || packaging;
// Whenever our add-on is disabled or uninstalled, we want to
// destroy the remote process.
require("unload").when(function() {
process.destroy();
process = null;
});
// Set up message receivers that the remote process will use to
// communicate with us.
['log', 'debug', 'info', 'warn', 'error'].forEach(function(method) {
process.on("console:" + method, function(name, args) {
console[method].apply(console, args);
});
});
function remoteException(exception) {
return {
toString: function toString() {
return "Error: " + this.message;
},
__proto__: exception
};
}
process.on("quit", function(name, status) {
if (options.quit)
options.quit(status);
});
process.on("console:trace", function(name, exception) {
var traceback = require("traceback");
var stack = traceback.fromException(remoteException(exception));
console.log(traceback.format(stack.slice(0, -2)));
});
process.on("console:exception", function(name, exception) {
console.exception(remoteException(exception));
});
jetpack.registerReceiver("dump", function(name, msg) {
dump(msg);
});
jetpack.registerReceiver(
"core:exception",
function(name, exception) {
console.log("An exception occurred in the child Jetpack process.");
console.exception(remoteException(exception));
});
process.registerCall(
"require",
function(name, base, path) {
var loader = options.loader || require("parent-loader");
var parentFS = loader.fs;
var moduleURL = parentFS.resolveModule(base, path);
if (!moduleURL)
return {code: "not-found"};
var moduleInfo = pkg.getModuleInfo(moduleURL);
var moduleName = path;
function maybeImportAdapterModule() {
var adapterModuleName = moduleName + "-e10s-adapter";
var adapterModuleURL = parentFS.resolveModule(base,
adapterModuleName);
var adapterModuleInfo = null;
if (adapterModuleURL)
adapterModuleInfo = pkg.getModuleInfo(adapterModuleURL);
if (moduleInfo['e10s-adapter'] != adapterModuleURL) {
console.warn("Adapter module URL is " + adapterModuleURL +
" but expected " + moduleInfo['e10s-adapter']);
return {code: "error"};
}
if (adapterModuleInfo) {
// e10s adapter found!
try {
if (!(adapterModuleURL in registeredModules)) {
// This e10s adapter has already been loaded for this
// addon process, and we only really need to give it the
// absolute URL of the adapter.
registeredModules[adapterModuleURL] = true;
loader.require(adapterModuleName).register(process);
}
} catch (e) {
console.exception(e);
return {code: "error"};
}
return {
code: "ok",
needsMessaging: true,
script: makeScriptFrom(parentFS, adapterModuleURL)
};
}
return null;
}
if (moduleInfo) {
if (moduleInfo.needsChrome) {
return maybeImportAdapterModule() || {code: "access-denied"};
} else {
// Even if a module doesn't explicitly require chrome privileges, if
// an e10s adapter exists for it, use it, because said module might
// import other modules that require chrome.
//
// In the future we may want to look at the module's dependencies to
// determine whether importing an adapter is a better idea.
return maybeImportAdapterModule() || {
code: "ok",
needsMessaging: false,
script: makeScriptFrom(parentFS, moduleURL)
};
}
} else {
return maybeImportAdapterModule() || {code: "not-found"};
}
});
var bootURL = require("self").data.url("bootstrap-remote-process.js");
var bootFilename = url.toFilename(bootURL);
var bootJS = file.read(bootFilename);
// The try ... catch is a workaround for bug 589308.
jetpack.evalScript('//@line 1 "' + bootFilename + '"\n' +
"try { " + bootJS + " } catch (e) { " +
"sendMessage('core:exception', e); }");
process.send("addInjectedSandboxScript",
require("cuddlefish").es5code);
return process;
};

View File

@ -0,0 +1,90 @@
/* ***** 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 logToConsole(e) {
console.exception(e);
}
var catchAndLog = exports.catchAndLog = function(callback,
defaultResponse,
logException) {
if (!logException)
logException = logToConsole;
return function() {
try {
return callback.apply(this, arguments);
} catch (e) {
logException(e);
return defaultResponse;
}
};
};
exports.catchAndLogProps = function catchAndLogProps(object,
props,
defaultResponse,
logException) {
if (typeof(props) == "string")
props = [props];
props.forEach(
function(property) {
object[property] = catchAndLog(object[property],
defaultResponse,
logException);
});
};
/**
* Catch and return an exception while calling the callback. If the callback
* doesn't throw, return the return value of the callback in a way that makes it
* possible to distinguish between a return value and an exception.
*
* This function is useful when you need to pass the result of a call across
* a process boundary (across which exceptions don't propagate). It probably
* doesn't need to be factored out into this module, since it is only used by
* a single caller, but putting it here works around bug 625560.
*/
exports.catchAndReturn = function(callback) {
return function() {
try {
return { returnValue: callback.apply(this, arguments) };
}
catch (exception) {
return { exception: exception };
}
};
};

View File

@ -0,0 +1,587 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
// Override the default Iterator function with one that passes
// a second argument to custom iterator methods that identifies
// the call as originating from an Iterator function so the custom
// iterator method can return [key, value] pairs just like default
// iterators called via the default Iterator function.
Iterator = (function(DefaultIterator) {
return function Iterator(obj, keysOnly) {
if ("__iterator__" in obj && !keysOnly)
return obj.__iterator__.call(obj, false, true);
return DefaultIterator(obj, keysOnly);
};
})(Iterator);
(function(exports) {
const // local shortcuts
hasOwn = Object.prototype.hasOwnProperty,
getGetter = Object.prototype.__lookupGetter__,
getSetter = Object.prototype.__lookupSetter__,
setGetter = Object.prototype.__defineGetter__,
setSetter = Object.prototype.__defineSetter__,
ObjectToString = Object.prototype.toString;
const
ID = '__es5_guid__',
PROTO = {};
let // registry
GUID = 0,
REGISTRY = {
inextensible: {},
frozen: {},
sealed: {}
},
DESCRIPTORS = {};
/**
* function gets `guid` for an object if it's in a registry. If
* object is not in a registry and `force` argument is `true` object
* gets unique identifier, special iterator hiding it & entry in registry.
* as a result of a call `guid` is returned if not in registry and `force` is
* not `true` `null` is returned.
* @param {Object} object
* object to get a `guid` for.
* @param {Boolean} force
* if `true` and object is not in registry it will be added there.
*/
function _guid(object, force) {
let guid = object[ID];
if (!guid && force) {
guid = object[ID] = ++ GUID;
if (!('__iterator__' in object))
defineIterator(object);
}
return guid || null
}
function noSetter() {
throw new TypeError('setting a property that has only a getter');
}
function nonWritable() {
throw new TypeError('setting a property that is read-only');
}
/**
* Function that generates ES5 object mutation functions(freeze, seal
* preventExtensions). Since in ES3 we can't implement ES5 `object` mutations
* we have a local registry for tracking mutated objects.
* @param {String} name
* property in mutation phase registry that is set to `true` when called
*/
function Mutate(name) {
let registry = REGISTRY[name];
return function(object) {
registry[_guid(object, true)] = true;
return object;
}
}
/**
* Function that generates ES5 mutation phase checker functions. Since in
* ES3 we can't implement ES5 `object` mutations we have a local registry
* for tracking mutated objects in order to evaluate next statement to `true`
* `Object.isFrozen(Object.freeze({}))`
* @param {String} name
* property in mutation phase registry that represents mutation
* @param {Boolean} invert
* Weather or not registry values should be inverted by generated function.
* Values are inverted by `Object.isExtensible` because default is `true`.
*/
function IsMutated(name, invert) {
let registry = REGISTRY[name];
return function(object)
invert ? !registry[_guid(object)] : !!registry[_guid(object)];
}
/**
* Defines custom `__iterator__` that wraps original `__iterator__` & uses
* locar object despriptor registry to iterate only on enumerable properties.
* '__iterator__' getter is used as a proxy to an iterator, what allows
* it to determine whether or not `Iterator` wrapper is used.
* @see https://developer.mozilla.org/en/New_in_JavaScript_1.7#Iterators
*/
function defineIterator(object) {
let onKeyValue = false,
ITERATOR = { __iterator__: undefined };
function iterator(onKeys) {
ITERATOR.__proto__ = this;
for (let key in ITERATOR) {
if (key === ID || '__iterator__' === key)
continue;
let descriptor = getPropertyDescriptor(this, key);
if (descriptor && false === descriptor.enumerable)
continue;
yield onKeyValue ? [key, this[key]] : onKeys ? key : this[key];
}
}
setGetter.call(object, '__iterator__', function __es5iterator__() {
let stack = Error().stack.split(/\n/);
onKeyValue = (
stack[2].indexOf('Iterator(') == 0 || // native implementation of bind
stack[3].indexOf('Iterator(') == 0 // custom implementation of bind
);
return iterator;
});
setSetter.call(object, '__iterator__', function(value) iterator = value);
}
/**
* Almost identical to the ES5 `Object.getOwnPropertyDescriptor` function.
* Only difference is that this function will return descriptors for
* inherited properties along with own. This helper function is used by custom
* iterators. They need to check each property (including inherited ones)
* whether or not it's enumerable.
* @see #__iterator__
* @see #iterator
*/
function getPropertyDescriptor(object, name) {
let descriptor = getOwnPropertyDescriptor(object, name);
if (!descriptor) {
let proto = object.__proto__;
if (proto)
descriptor = getPropertyDescriptor(proto, name);
}
if (descriptor && ('value' in descriptor))
descriptor.value = object[name];
return descriptor;
}
/** ES5 15.4.3.2 */
function isArray(object) ObjectToString.call(object) == '[object Array]';
/** ES5 15.2.3.2 */
function getPrototypeOf(object) object.__proto__;
/**
* ES5 15.2.3.8
* Can't implement this feature in ES3. Function just pretends to be
* doing whatever specs say, instead it registers state so that check
* calling `Object.isSealed` on sealed object will return `true`
*/
let seal = Mutate('sealed');
/**
* ES5 15.2.3.9
* Can't implement this feature in ES3. Function just pretends to be
* doing whatever specs say, instead it registers state so that checking
* calling `Object.isFrozen` on frozen object will return `true`.
*/
let freeze = Mutate('frozen');
/**
* ES5 15.2.3.10
* Can't implement this feature in ES3. Function just pretends to be
* doing whatever specs say, instead it registers state so that checking
* calling `Object.isExtensible` on inextensible object will return `true`.
*/
let preventExtensions = Mutate('inextensible');
/**
* ES5 15.2.3.11
* Can't implement this feature in ES3. Function just pretends to be
* doing whatever specs say, instead it looks into state registry, where
* all the mutation functions store state.
*/
let isSealed = IsMutated('sealed');
/**
* ES5 15.2.3.12
* Can't implement this feature in ES3. Function just pretends to be
* doing whatever specs say, instead it looks into state registry, where
* all the mutation functions store state.
*/
let isFrozen = IsMutated('frozen');
/**
* ES5 15.2.3.13
* Can't implement this feature in ES3. Function just pretends to be
* doing whatever specs say, instead it looks into state registry, where
* all the mutation functions store state.
*/
let isExtensible = exports.isExtensible = IsMutated('inextensible', true);
/** ES5 15.2.3.14 */
function keys(object) {
let result = [];
for (let name in object) {
if (hasOwn.call(object, name))
result.push(name);
}
return result;
}
/** ES5 15.2.3.4 */
function getOwnPropertyNames(object) {
let ITERATOR = { __proto__: object, __iterator__: undefined };
let result = [];
for (let name in ITERATOR) {
if (hasOwn.call(object, name) && ID !== name) {
let skip = false;
if ('__iterator__' === name) {
let iteratror = getGetter.call(object, '__iterator__');
skip = iteratror && '__es5iterator__' === iteratror.name;
}
if (!skip)
result.push(name);
}
}
return result;
}
/** ES5 15.2.3.3 */
function getOwnPropertyDescriptor(object, name) {
if (!hasOwn.call(object, name))
return undefined;
let descriptor = {
configurable: true,
enumerable: true
};
// lets override descriptor with values from registry if are any
let _descriptor, objectDescriptor = DESCRIPTORS[_guid(object)];
if (objectDescriptor && (_descriptor = objectDescriptor[name])) {
descriptor.enumerable = _descriptor.enumerable;
descriptor.configurable = _descriptor.configurable;
if ('writable' in _descriptor)
descriptor.writable = _descriptor.writable;
}
if (false === descriptor.writable) {
// special case where want to pretend that we have value not a getter
descriptor.value = object[name]
}
else {
let get = getGetter.call(object, name),
set = getSetter.call(object, name);
if (!get && !set) {
descriptor.value = object[name];
descriptor.writable = true;
}
else {
descriptor.get = get;
descriptor.set = set;
}
}
return descriptor;
}
/**
* ES5 15.2.3.6
* Partially implements ES5 `Object.defineProperty`.
* - Non configurable properties can't be implement in ES3, so all the non
* configurable properties will be created as configurable ones.
* - Non writable properties can't be implemented in ES3, getters and setters
* will be created instead, but `Object.getOwnPropertyDescriptor` will still
* behave as expected (will return descriptor for non writable property not a
* getter)
* - Defining properties using ES5 functions will break your custom iterators
* if you have any. Think twice before employing custom iterator cause in
* majority of cases you can just make properties non enumerable. In case you
* really need to have custom iterator be smart about it, make sure to add
* it after running ES5 functions and don't ignore previous iterators. Please
* see example below for inspiration:
* let object = Object.create({}, {
* myField: { value: 6 }
* });
* object.__iterator__ = (function(original) {
* return function myIterator() {
* this.__iterator__ = original;
* for (let key in this) {
* // your logic here
* }
* this.__iterator__ = myIterator;
* }
* })(object.__iterator__);
*/
function defineProperty(object, name, descriptor) {
if ('object' !== typeof object && 'function' !== typeof object)
throw new TypeError('Object prototype may only be an Object or null.');
if (descriptor && 'object' !== typeof descriptor)
throw new TypeError('Property descriptor list must be an Object.');
if ('value' in descriptor) { // if it's property
if ('get' in descriptor || 'set' in descriptor) {
throw new TypeError('Invalid property. "value" present on property'
+ 'with getter or setter.');
}
if (false === descriptor.writable) {
let value = descriptor.value;
setGetter.call(object, name, function() value);
setSetter.call(object, name, nonWritable);
}
else {
// temporary removing proto to avoid inherited getter / setter
let proto = object.__proto__;
object.__proto__ = PROTO;
delete object[name];
object[name] = descriptor.value;
object.__proto__ = proto;
}
}
else { // if it's a setter / getter
if ('writable' in descriptor)
throw new TypeError('Invalid property. "writable" present on property'
+ 'with getter or setter.');
let get = descriptor.get, hasGet = (typeof get == "function"),
set = descriptor.set, hasSet = (typeof set == "function");
if (hasGet)
setGetter.call(object, name, get);
if (hasSet)
setSetter.call(object, name, descriptor.set);
// should throw if only getter is assigned
else if (hasGet)
setSetter.call(object, name, noSetter);
}
// registering descriptor
let guid = _guid(object, true);
let ObjectRegistry = DESCRIPTORS[guid] || (DESCRIPTORS[guid] = {});
let registry = ObjectRegistry[name] || (ObjectRegistry[name] = {});
if ('writable' in descriptor)
registry.writable = !!descriptor.writable;
let enumerable = registry.enumerable =
'enumerable' in descriptor ? !!descriptor.enumerable : false;
registry.configurable =
'configurable' in descriptor ? !!descriptor.configurable : false;
return object;
}
/**
* ES5 15.2.3.7
* Some functionality can't be implemented using ES5.
* @see #defineProperty
*/
function defineProperties(object, descriptor) {
let names = getOwnPropertyNames(descriptor);
for each (let name in names)
defineProperty(object, name, descriptor[name]);
return object;
}
/**
* ES5 15.2.3.5
* Some functionality can't be implemented using ES5.
* @see #defineProperty
*/
function create(proto, descriptor) {
if (typeof proto != 'object')
throw new TypeError(
'typeof prototype[' + (typeof proto) + '] != "object"'
);
let inheritsIterator = (
(!descriptor || !('__iterator__' in descriptor))
&& proto && '__iterator__' in proto
);
let object = {};
if (inheritsIterator)
object.__iterator__ = undefined;
if (typeof descriptor !== "undefined")
defineProperties(object, descriptor);
if (inheritsIterator)
delete object.__iterator__;
object.__proto__ = proto;
return object;
}
/** ES-5 15.3.4.5 */
function bind(that) {
/** 1. Let Target be the this value. **/
let target = this;
/**
2. If IsCallable(Target) is false, throw a TypeError exception.
XXX this gets pretty close, for all intents and purposes, letting
some duck-types slide
*/
if (typeof target.apply != "function" || typeof target.call != "function")
return new TypeError();
/**
3. Let A be a new (possibly empty) internal list of all of the
argument values provided after thisArg (arg1, arg2 etc), in order.
*/
let boundArgs = Array.slice(arguments);
/**
4. Let F be a new native ECMAScript object.
9. Set the [[Prototype]] internal property of F to the standard
built-in Function prototype object as specified in 15.3.3.1.
10. Set the [[Call]] internal property of F as described in
15.3.4.5.1.
11. Set the [[Construct]] internal property of F as described in
15.3.4.5.2.
12. Set the [[HasInstance]] internal property of F as described in
15.3.4.5.3.
13. The [[Scope]] internal property of F is unused and need not
exist.
*/
function bound() {
let params = boundArgs.concat(Array.slice(arguments))
if (this instanceof bound) {
/**
15.3.4.5.2 [[Construct]]
When the [[Construct]] internal method of a function object,
F that was created using the bind function is called with a
list of arguments ExtraArgs the following steps are taken:
1. Let target be the value of F's [[TargetFunction]]
internal property.
2. If target has no [[Construct]] internal method, a
TypeError exception is thrown.
3. Let boundArgs be the value of F's [[BoundArgs]] internal
property.
4. Let args be a new list containing the same values as the
list boundArgs in the same order followed by the same
values as the list ExtraArgs in the same order.
*/
let self = create(target.prototype);
target.apply(self, params);
return self;
}
else {
/**
15.3.4.5.1 [[Call]]
When the [[Call]] internal method of a function object, F,
which was created using the bind function is called with a
this value and a list of arguments ExtraArgs the following
steps are taken:
1. Let boundArgs be the value of F's [[BoundArgs]] internal
property.
2. Let boundThis be the value of F's [[BoundThis]] internal
property.
3. Let target be the value of F's [[TargetFunction]] internal
property.
4. Let args be a new list containing the same values as the
list boundArgs in the same order followed by the same
values as the list ExtraArgs in the same order. 5. Return
the result of calling the [[Call]] internal method of
target providing boundThis as the this value and providing
args as the arguments.
equiv: target.call(this, ...boundArgs, ...args)
*/
return target.call.apply(target, params);
}
}
/**
5. Set the [[TargetFunction]] internal property of F to Target.
extra:
*/
bound.bound = target;
/**
6. Set the [[BoundThis]] internal property of F to the value of
thisArg.
extra:
*/
bound.boundTo = that;
/**
7. Set the [[BoundArgs]] internal property of F to A.
extra:
*/
bound.boundArgs = boundArgs;
/**
14. If the [[Class]] internal property of Target is "Function", then
a. Let L be the length property of Target minus the length of A.
b. Set the length own property of F to either 0 or L, whichever is
larger.
15. Else set the length own property of F to 0.
*/
// #Note can't modify length in es3.
/**
16. The length own property of F is given attributes as specified in
15.3.5.1.
#TODO
17. Set the [[Extensible]] internal property of F to true.
#TODO
18. Call the [[DefineOwnProperty]] internal method of F with
arguments "caller", PropertyDescriptor {[[Value]]: null,
[[Writable]]: false, [[Enumerable]]: false, [[Configurable]]:
false}, and false.
#TODO
19. Call the [[DefineOwnProperty]] internal method of F with
arguments "arguments", PropertyDescriptor {[[Value]]: null,
[[Writable]]: false, [[Enumerable]]: false, [[Configurable]]:
false}, and false.
NOTE Function objects created using Function.prototype.bind do not
have a prototype property.
*/
// #Note can't delete prototype in es3.
return bound;
}
/**
* Implements missing ES5 features in passed `Object`, `Array`, `Function`.
* @param {Object}
* `Object` to be extended with a missing ES5 features.
* @param {Array}
* `Array` to be extended with a missing ES5 features.
* @param {Function}
* `Function` to be extended with a missing ES5 features.
* @see http://www.ecmascript.org/docs/tc39-2009-043.pdf
*/
exports.init = function init(Object, Array, Function) {
if (Array) {
if (!Array.isArray)
Array.isArray = isArray;
}
if (Object) {
if (!Object.seal)
Object.seal = seal;
if (!Object.freeze)
Object.freeze = freeze;
if (!Object.preventExtensions)
Object.preventExtensions = preventExtensions;
if (!Object.isSealed)
Object.isSealed = isSealed;
if (!Object.isFrozen)
Object.isFrozen = isFrozen;
if (!Object.isExtensible)
Object.isExtensible = isExtensible;
if (!Object.keys)
Object.keys = keys;
if (!Object.getPrototypeOf)
Object.getPrototypeOf = getPrototypeOf;
if (!Object.getOwnPropertyNames)
Object.getOwnPropertyNames = getOwnPropertyNames;
if (!Object.getOwnPropertyDescriptor)
Object.getOwnPropertyDescriptor = getOwnPropertyDescriptor;
if (!Object.defineProperty)
Object.defineProperty = defineProperty;
if (!Object.defineProperties)
Object.defineProperties = defineProperties;
if (!Object.create)
Object.create = create;
}
if (Function) {
if (!Function.prototype.bind)
Function.prototype.bind = bind;
}
};
exports.init(Object, Array, Function);
})(this.exports ? exports : {});

View File

@ -0,0 +1,196 @@
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
* Drew Willcoxon <adw@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 ***** */
"use strict";
const ERROR_TYPE = 'error',
UNCAUGHT_ERROR = 'An error event was dispatched for which there was'
+ ' no listener.',
BAD_LISTENER = 'The event listener must be a function.';
/**
* This object is used to create an `EventEmitter` that, useful for composing
* objects that emit events. It implements an interface like `EventTarget` from
* DOM Level 2, which is implemented by Node objects in implementations that
* support the DOM Event Model.
* @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget
* @see http://nodejs.org/api.html#EventEmitter
* @see http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/EventDispatcher.html
*/
const eventEmitter = {
/**
* Registers an event `listener` that is called every time events of
* specified `type` are emitted.
* @param {String} type
* The type of event.
* @param {Function} listener
* The listener function that processes the event.
* @example
* worker.on('message', function (data) {
* console.log('data received: ' + data)
* })
*/
on: function on(type, listener) {
if ('function' !== typeof listener)
throw new Error(BAD_LISTENER);
let listeners = this._listeners(type);
if (0 > listeners.indexOf(listener))
listeners.push(listener);
return this._public;
},
/**
* Registers an event `listener` that is called once the next time an event
* of the specified `type` is emitted.
* @param {String} type
* The type of the event.
* @param {Function} listener
* The listener function that processes the event.
*/
once: function once(type, listener) {
this.on(type, function selfRemovableListener() {
this.removeListener(type, selfRemovableListener);
listener.apply(this, arguments);
});
},
/**
* Unregister `listener` for the specified event type.
* @param {String} type
* The type of event.
* @param {Function} listener
* The listener function that processes the event.
*/
removeListener: function removeListener(type, listener) {
if ('function' !== typeof listener)
throw new Error(BAD_LISTENER);
let listeners = this._listeners(type),
index = listeners.indexOf(listener);
if (0 <= index)
listeners.splice(index, 1);
return this._public;
},
/**
* Hash of listeners on this EventEmitter.
*/
_events: null,
/**
* Returns an array of listeners for the specified event `type`. This array
* can be manipulated, e.g. to remove listeners.
* @param {String} type
* The type of event.
*/
_listeners: function listeners(type) {
let events = this._events || (this._events = {});
return events[type] || (events[type] = []);
},
/**
* Execute each of the listeners in order with the supplied arguments.
* Returns `true` if listener for this event was called, `false` if there are
* no listeners for this event `type`.
*
* All the exceptions that are thrown by listeners during the emit
* are caught and can be handled by listeners of 'error' event. Thrown
* exceptions are passed as an argument to an 'error' event listener.
* If no 'error' listener is registered exception will propagate to a
* caller of this method.
*
* **It's recommended to have a default 'error' listener in all the complete
* composition that in worst case may dump errors to the console.**
*
* @param {String} type
* The type of event.
* @params {Object|Number|String|Boolean}
* Arguments that will be passed to listeners.
* @returns {Boolean}
*/
_emit: function _emit(type, event) {
let args = Array.slice(arguments);
args.unshift(this._public);
return this._emitOnObject.apply(this, args);
},
/**
* A version of _emit that lets you specify the object on which listeners are
* called. This is a hack that is sometimes necessary when such an object
* (exports, for example) cannot be an EventEmitter for some reason, but other
* object(s) managing events for the object are EventEmitters. Once bug
* 577782 is fixed, this method shouldn't be necessary.
*
* @param {object} targetObj
* The object on which listeners will be called.
* @param {string} type
* The event name.
* @param {value} event
* The first argument to pass to listeners.
* @param {value} ...
* More arguments to pass to listeners.
* @returns {boolean}
*/
_emitOnObject: function _emitOnObject(targetObj, type, event /* , ... */) {
let listeners = this._listeners(type).slice(0);
// If there is no 'error' event listener then throw.
if (type === ERROR_TYPE && !listeners.length)
console.exception(event);
if (!listeners.length)
return false;
let params = Array.slice(arguments, 2);
for each (let listener in listeners) {
try {
listener.apply(targetObj, params);
} catch(e) {
this._emit('error', e);
}
}
return true;
},
/**
* Removes all the event listeners for the specified event `type`.
* @param {String} type
* The type of event.
*/
_removeAllListeners: function _removeAllListeners(type) {
this._listeners(type).splice(0);
return this;
}
};
exports.EventEmitter = require("traits").Trait.compose(eventEmitter);
exports.EventEmitterTrait = require('light-traits').Trait(eventEmitter);

View File

@ -0,0 +1,213 @@
/* ***** 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 nsINarwhal.
*
* The Initial Developer of the Original Code is
* Irakli Gozalishvili <rfobic@gmail.com>.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <rfobic@gmail.com>
* Atul Varma <atul@mozilla.com>
* Drew Willcoxon <adw@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 ***** */
const {Cc,Ci,Cr} = require("chrome");
const byteStreams = require("byte-streams");
const textStreams = require("text-streams");
const xpcom = require("xpcom");
// Flags passed when opening a file. See nsprpub/pr/include/prio.h.
const OPEN_FLAGS = {
RDONLY: 0x01,
WRONLY: 0x02,
CREATE_FILE: 0x08,
APPEND: 0x10,
TRUNCATE: 0x20,
EXCL: 0x80
};
var dirsvc = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
function MozFile(path) {
var file = Cc['@mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
file.initWithPath(path);
return file;
}
function ensureReadable(file) {
if (!file.isReadable())
throw new Error("path is not readable: " + file.path);
}
function ensureDir(file) {
ensureExists(file);
if (!file.isDirectory())
throw new Error("path is not a directory: " + file.path);
}
function ensureFile(file) {
ensureExists(file);
if (!file.isFile())
throw new Error("path is not a file: " + file.path);
}
function ensureExists(file) {
if (!file.exists()) {
throw xpcom.friendlyError(Cr.NS_ERROR_FILE_NOT_FOUND, {
filename: file.path
});
}
}
exports.exists = function exists(filename) {
return MozFile(filename).exists();
};
exports.isFile = function isFile(filename) {
return MozFile(filename).isFile();
};
exports.read = function read(filename) {
var stream = exports.open(filename);
try {
var str = stream.read();
}
finally {
stream.close();
}
return str;
};
exports.join = function join(base) {
if (arguments.length < 2)
throw new Error("need at least 2 args");
base = MozFile(base);
for (var i = 1; i < arguments.length; i++)
base.append(arguments[i]);
return base.path;
};
exports.dirname = function dirname(path) {
var parent = MozFile(path).parent;
return parent ? parent.path : "";
};
exports.basename = function basename(path) {
var leafName = MozFile(path).leafName;
// On Windows, leafName when the path is a volume letter and colon ("c:") is
// the path itself. But such a path has no basename, so we want the empty
// string.
return leafName == path ? "" : leafName;
};
exports.list = function list(path) {
var file = MozFile(path);
ensureDir(file);
ensureReadable(file);
var entries = file.directoryEntries;
var entryNames = [];
while(entries.hasMoreElements()) {
var entry = entries.getNext();
entry.QueryInterface(Ci.nsIFile);
entryNames.push(entry.leafName);
}
return entryNames;
};
exports.open = function open(filename, mode) {
var file = MozFile(filename);
if (typeof(mode) !== "string")
mode = "";
// File opened for write only.
if (/w/.test(mode)) {
if (file.exists())
ensureFile(file);
var stream = Cc['@mozilla.org/network/file-output-stream;1'].
createInstance(Ci.nsIFileOutputStream);
var openFlags = OPEN_FLAGS.WRONLY |
OPEN_FLAGS.CREATE_FILE |
OPEN_FLAGS.TRUNCATE;
var permFlags = 0644; // u+rw go+r
try {
stream.init(file, openFlags, permFlags, 0);
}
catch (err) {
throw xpcom.friendlyError(err, { filename: filename });
}
return /b/.test(mode) ?
new byteStreams.ByteWriter(stream) :
new textStreams.TextWriter(stream);
}
// File opened for read only, the default.
ensureFile(file);
stream = Cc['@mozilla.org/network/file-input-stream;1'].
createInstance(Ci.nsIFileInputStream);
try {
stream.init(file, OPEN_FLAGS.RDONLY, 0, 0);
}
catch (err) {
throw xpcom.friendlyError(err, { filename: filename });
}
return /b/.test(mode) ?
new byteStreams.ByteReader(stream) :
new textStreams.TextReader(stream);
};
exports.remove = function remove(path) {
var file = MozFile(path);
ensureFile(file);
file.remove(false);
};
exports.mkpath = function mkpath(path) {
var file = MozFile(path);
if (!file.exists())
file.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); // u+rwx go+rx
else if (!file.isDirectory())
throw new Error("The path already exists and is not a directory: " + path);
};
exports.rmdir = function rmdir(path) {
var file = MozFile(path);
ensureDir(file);
try {
file.remove(false);
}
catch (err) {
// Bug 566950 explains why we're not catching a specific exception here.
throw new Error("The directory is not empty: " + path);
}
};

View File

@ -0,0 +1,110 @@
/* ***** 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 ***** */
if (this.chrome) {
var timer = require("timer");
var ut = require("unit-test");
chrome.on(
"runTest",
function(name, test) {
var runner = new ut.TestRunner();
runner.start({
test: test.testHandle,
onDone: function() {
test.passed = runner.test.passed;
test.failed = runner.test.failed;
test.errors = runner.test.errors;
chrome.send("testDone", test);
}
});
});
exports.main = function(options, callbacks) {
function makeTest(suite, name, test) {
return function runTest(runner) {
console.info("executing '" + suite + "." + name + "' remotely");
test(runner);
};
}
var tests = [];
options.suites.forEach(function(suite) {
var module = require(suite);
for (testName in module) {
var handle = chrome.createHandle();
handle.testFunction = makeTest(suite, testName, module[testName]);
handle.name = suite + "." + testName;
tests.push({testHandle: handle, name: handle.name});
}
});
chrome.send("testsFound", tests, options.finderHandle);
}
} else {
exports.register = function(addon) {
addon.on("testDone", function(name, remoteTest) {
var runner = remoteTest.testHandle.runner;
runner.passed += remoteTest.passed;
runner.failed += remoteTest.failed;
runner.test.passed = remoteTest.passed;
runner.test.failed = remoteTest.failed;
runner.test.errors = remoteTest.errors;
runner.done();
});
addon.on("testPass", function(name, remoteTest, msg) {
remoteTest.testHandle.runner.pass(msg);
});
addon.on("testFail", function(name, remoteTest, msg) {
remoteTest.testHandle.runner.fail(msg);
});
addon.on("testsFound", function(name, remoteTests,
finderHandle) {
var tests = [];
remoteTests.forEach(function(remoteTest) {
tests.push({
testFunction: function(runner) {
remoteTest.testHandle.runner = runner;
runner.waitUntilDone();
addon.send("runTest", remoteTest);
},
name: remoteTest.name
});
});
finderHandle.onTestsFound(tests);
});
};
}

View File

@ -0,0 +1 @@
// this file left intentionally blank

View File

@ -0,0 +1,198 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Felipe Gomes <felipc@gmail.com> (Original Author)
* Myk Melez <myk@mozilla.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 ***** */
const {Cc, Ci} = require("chrome");
const errors = require("errors");
const apiUtils = require("api-utils");
const timer = require("timer");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let hostFrame, hostDocument, hiddenWindow, isHostFrameReady = false;
if (!require("xul-app").isOneOf(["Firefox", "Thunderbird"])) {
throw new Error([
"The hidden-frame module currently supports only Firefox and Thunderbird. ",
"In the future, we would like it to support other applications, however. ",
"Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
"information."
].join(""));
}
let appShellService = Cc["@mozilla.org/appshell/appShellService;1"].
getService(Ci.nsIAppShellService);
hiddenWindow = appShellService.hiddenDOMWindow;
if (!hiddenWindow) {
throw new Error([
"The hidden-frame module needs an app that supports a hidden window. ",
"We would like it to support other applications, however. Please see ",
"https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more information."
].join(""));
}
// Check if we can use the hidden window itself to host our iframes.
// If it's not a suitable host, the hostFrame will be lazily created
// by the first HiddenFrame instance.
if (hiddenWindow.location.protocol == "chrome:" &&
(hiddenWindow.document.contentType == "application/vnd.mozilla.xul+xml" ||
hiddenWindow.document.contentType == "application/xhtml+xml")) {
hostFrame = hiddenWindow;
hostDocument = hiddenWindow.document;
isHostFrameReady = true;
}
function setHostFrameReady() {
hostDocument = hostFrame.contentDocument;
hostFrame.removeEventListener("DOMContentLoaded", setHostFrameReady, false);
isHostFrameReady = true;
}
// This cache is used to access friend properties between functions
// without exposing them on the public API.
let cache = [];
exports.HiddenFrame = apiUtils.publicConstructor(HiddenFrame);
function HiddenFrame(options) {
options = options || {};
let self = this;
for each (let [key, val] in Iterator(apiUtils.validateOptions(options, {
onReady: {
is: ["undefined", "function", "array"],
ok: function(v) {
if (apiUtils.getTypeOf(v) === "array") {
// make sure every item is a function
return v.every(function (item) typeof(item) === "function")
}
return true;
}
}
}))) {
if (typeof(val) != "undefined")
options[key] = val;
}
require("collection").addCollectionProperty(this, "onReady");
if (options.onReady)
this.onReady.add(options.onReady);
if (!hostFrame) {
hostFrame = hiddenWindow.document.createElement("iframe");
// ugly ugly hack. This is the most lightweight chrome:// file I could find on the tree
// This hack should be removed by proper platform support on bug 565388
hostFrame.setAttribute("src", "chrome://global/content/mozilla.xhtml");
hostFrame.addEventListener("DOMContentLoaded", setHostFrameReady, false);
hiddenWindow.document.body.appendChild(hostFrame);
}
this.toString = function toString() "[object Frame]";
}
exports.add = function JP_SDK_Frame_add(frame) {
if (!(frame instanceof HiddenFrame))
throw new Error("The object to be added must be a HiddenFrame.");
// This instance was already added.
if (cache.filter(function (v) v.frame === frame)[0])
return frame;
function createElement() {
hostFrame.removeEventListener("DOMContentLoaded", createElement, false);
let element = hostDocument.createElementNS(XUL_NS, "iframe");
element.setAttribute("type", "content");
hostDocument.documentElement.appendChild(element);
/* Public API: hiddenFrame.element */
frame.__defineGetter__("element", function () element);
// Notify consumers that the frame is ready.
function onReadyListener(event) {
element.removeEventListener("DOMContentLoaded", onReadyListener, false);
if (event.target == element.contentDocument) {
for (let handler in frame.onReady)
errors.catchAndLog(function () handler.call(frame))();
}
}
element.addEventListener("DOMContentLoaded", onReadyListener, false);
cache.push({
frame: frame,
element: element,
unload: function unload() {
hostDocument.documentElement.removeChild(element);
}
});
}
/* Begin element construction or schedule it for later */
if (isHostFrameReady) {
createElement();
} else {
hostFrame.addEventListener("DOMContentLoaded", createElement, false);
}
return frame;
}
exports.remove = function remove(frame) {
if (!(frame instanceof HiddenFrame))
throw new Error("The object to be removed must be a HiddenFrame.");
let entry = cache.filter(function (v) v.frame === frame)[0];
if (!entry)
return;
entry.unload();
cache.splice(cache.indexOf(entry), 1);
}
require("unload").when(function () {
for each (let entry in cache.slice())
exports.remove(entry.frame);
if (hostFrame && hostFrame !== hiddenWindow)
hiddenWindow.document.body.removeChild(hostFrame);
});

View File

@ -0,0 +1,626 @@
/* vim:ts=2:sts=2:sw=2:
* ***** 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 Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <rfobic@gmail.com> (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 strict";
// `var` is being used in the module in order to make it reusable in
// environments in which `let` is not yet supported.
// Shortcut to `Object.prototype.hasOwnProperty.call`.
// owns(object, name) would be the same as
// Object.prototype.hasOwnProperty.call(object, name);
var owns = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
/**
* Whether or not given property descriptors are equivalent. They are
* equivalent either if both are marked as 'conflict' or 'required' property
* or if all the properties of descriptors are equal.
* @param {Object} actual
* @param {Object} expected
*/
function equivalentDescriptors(actual, expected) {
return (actual.conflict && expected.conflict) ||
(actual.required && expected.required) ||
equalDescriptors(actual, expected);
}
/**
* Whether or not given property descriptors define equal properties.
*/
function equalDescriptors(actual, expected) {
return actual.get === expected.get &&
actual.set === expected.set &&
actual.value === expected.value &&
!!actual.enumerable === !!expected.enumerable &&
!!actual.configurable === !!expected.configurable &&
!!actual.writable === !!expected.writable;
}
// Utilities that throwing exceptions for a properties that are marked
// as "required" or "conflict" properties.
function throwConflictPropertyError(name) {
throw new Error("Remaining conflicting property: `" + name + "`");
}
function throwRequiredPropertyError(name) {
throw new Error("Missing required property: `" + name + "`");
}
/**
* Generates custom **required** property descriptor. Descriptor contains
* non-standard property `required` that is equal to `true`.
* @param {String} name
* property name to generate descriptor for.
* @returns {Object}
* custom property descriptor
*/
function RequiredPropertyDescriptor(name) {
// Creating function by binding first argument to a property `name` on the
// `throwConflictPropertyError` function. Created function is used as a
// getter & setter of the created property descriptor. This way we ensure
// that we throw exception late (on property access) if object with
// `required` property was instantiated using built-in `Object.create`.
var accessor = throwRequiredPropertyError.bind(null, name);
return { get: accessor, set: accessor, required: true };
}
/**
* Generates custom **conflicting** property descriptor. Descriptor contains
* non-standard property `conflict` that is equal to `true`.
* @param {String} name
* property name to generate descriptor for.
* @returns {Object}
* custom property descriptor
*/
function ConflictPropertyDescriptor(name) {
// For details see `RequiredPropertyDescriptor` since idea is same.
var accessor = throwConflictPropertyError.bind(null, name);
return { get: accessor, set: accessor, conflict: true };
}
/**
* Tests if property is marked as `required` property.
*/
function isRequiredProperty(object, name) {
return !!object[name].required;
}
/**
* Tests if property is marked as `conflict` property.
*/
function isConflictProperty(object, name) {
return !!object[name].conflict;
}
/**
* Function tests whether or not method of the `source` object with a given
* `name` is inherited from `Object.prototype`.
*/
function isBuiltInMethod(name, source) {
var target = Object.prototype[name];
// If methods are equal then we know it's `true`.
return target == source ||
// If `source` object comes form a different sandbox `==` will evaluate
// to `false`, in that case we check if functions names and sources match.
(String(target) === String(source) && target.name === source.name);
}
/**
* Function overrides `toString` and `constructor` methods of a given `target`
* object with a same-named methods of a given `source` if methods of `target`
* object are inherited / copied from `Object.prototype`.
* @see create
*/
function overrideBuiltInMethods(target, source) {
if (isBuiltInMethod("toString", target.toString)) {
Object.defineProperty(target, "toString", {
value: source.toString,
configurable: true,
enumerable: false
});
}
if (isBuiltInMethod("constructor", target.constructor)) {
Object.defineProperty(target, "constructor", {
value: source.constructor,
configurable: true,
enumerable: false
});
}
}
/**
* Composes new trait with the same own properties as the original trait,
* except that all property names appearing in the first argument are replaced
* by "required" property descriptors.
* @param {String[]} keys
* Array of strings property names.
* @param {Object} trait
* A trait some properties of which should be excluded.
* @returns {Object}
* @example
* var newTrait = exclude(["name", ...], trait)
*/
function exclude(names, trait) {
var map = {};
Object.keys(trait).forEach(function(name) {
// If property is not excluded (the array of names does not contain it),
// or it is a "required" property, copy it to the property descriptor `map`
// that will be used for creation of resulting trait.
if (!~names.indexOf(name) || isRequiredProperty(trait, name))
map[name] = { value: trait[name], enumerable: true };
// For all the `names` in the exclude name array we create required
// property descriptors and copy them to the `map`.
else
map[name] = { value: RequiredPropertyDescriptor(name), enumerable: true };
});
return Object.create(Trait.prototype, map);
}
/**
* Composes new instance of `Trait` with a properties of a given `trait`,
* except that all properties whose name is an own property of `renames` will
* be renamed to `renames[name]` and a `"required"` property for name will be
* added instead.
*
* For each renamed property, a required property is generated. If
* the `renames` map two properties to the same name, a conflict is generated.
* If the `renames` map a property to an existing unrenamed property, a
* conflict is generated.
*
* @param {Object} renames
* An object whose own properties serve as a mapping from old names to new
* names.
* @param {Object} trait
* A new trait with renamed properties.
* @returns {Object}
* @example
*
* // Return trait with `bar` property equal to `trait.foo` and with
* // `foo` and `baz` "required" properties.
* var renamedTrait = rename({ foo: "bar", baz: null }), trait);
*
* // t1 and t2 are equivalent traits
* var t1 = rename({a: "b"}, t);
* var t2 = compose(exclude(["a"], t), { a: { required: true }, b: t[a] });
*/
function rename(renames, trait) {
var map = {};
// Loop over all the properties of the given `trait` and copy them to a
// property descriptor `map` that will be used for the creation of the
// resulting trait. Also, rename properties in the `map` as specified by
// `renames`.
Object.keys(trait).forEach(function(name) {
var alias;
// If the property is in the `renames` map, and it isn't a "required"
// property (which should never need to be aliased because "required"
// properties never conflict), then we must try to rename it.
if (owns(renames, name) && !isRequiredProperty(trait, name)) {
alias = renames[name];
// If the `map` already has the `alias`, and it isn't a "required"
// property, that means the `alias` conflicts with an existing name for a
// provided trait (that can happen if >=2 properties are aliased to the
// same name). In this case we mark it as a conflicting property.
// Otherwise, everything is fine, and we copy property with an `alias`
// name.
if (owns(map, alias) && !map[alias].value.required) {
map[alias] = {
value: ConflictPropertyDescriptor(alias),
enumerable: true
};
}
else {
map[alias] = {
value: trait[name],
enumerable: true
};
}
// Regardless of whether or not the rename was successful, we check to
// see if the original `name` exists in the map (such a property
// could exist if previous another property was aliased to this `name`).
// If it isn't, we mark it as "required", to make sure the caller
// provides another value for the old name, which methods of the trait
// might continue to reference.
if (!owns(map, name)) {
map[name] = {
value: RequiredPropertyDescriptor(name),
enumerable: true
};
}
}
// Otherwise, either the property isn't in the `renames` map (thus the
// caller is not trying to rename it) or it is a "required" property.
// Either way, we don't have to alias the property, we just have to copy it
// to the map.
else {
// The property isn't in the map yet, so we copy it over.
if (!owns(map, name)) {
map[name] = { value: trait[name], enumerable: true };
}
// The property is already in the map (that means another property was
// aliased with this `name`, which creates a conflict if the property is
// not marked as "required"), so we have to mark it as a "conflict"
// property.
else if (!isRequiredProperty(trait, name)) {
map[name] = {
value: ConflictPropertyDescriptor(name),
enumerable: true
};
}
}
});
return Object.create(Trait.prototype, map);
}
/**
* Composes new resolved trait, with all the same properties as the original
* `trait`, except that all properties whose name is an own property of
* `resolutions` will be renamed to `resolutions[name]`.
*
* If `resolutions[name]` is `null`, the value is mapped to a property
* descriptor that is marked as a "required" property.
*/
function resolve(resolutions, trait) {
var renames = {};
var exclusions = [];
// Go through each mapping in `resolutions` object and distribute it either
// to `renames` or `exclusions`.
Object.keys(resolutions).forEach(function(name) {
// If `resolutions[name]` is a truthy value then it's a mapping old -> new
// so we copy it to `renames` map.
if (resolutions[name])
renames[name] = resolutions[name];
// Otherwise it's not a mapping but an exclusion instead in which case we
// add it to the `exclusions` array.
else
exclusions.push(name);
});
// First `exclude` **then** `rename` and order is important since
// `exclude` and `rename` are not associative.
return rename(renames, exclude(exclusions, trait));
}
/**
* Create a Trait (a custom property descriptor map) that represents the given
* `object`'s own properties. Property descriptor map is a "custom", because it
* inherits from `Trait.prototype` and it's property descriptors may contain
* two attributes that is not part of the ES5 specification:
*
* - "required" (this property must be provided by another trait
* before an instance of this trait can be created)
* - "conflict" (when the trait is composed with another trait,
* a unique value for this property is provided by two or more traits)
*
* Data properties bound to the `Trait.required` singleton exported by
* this module will be marked as "required" properties.
*
* @param {Object} object
* Map of properties to compose trait from.
* @returns {Trait}
* Trait / Property descriptor map containing all the own properties of the
* given argument.
*/
function trait(object) {
var map;
var trait = object;
if (!(object instanceof Trait)) {
// If the passed `object` is not already an instance of `Trait`, we create
// a property descriptor `map` containing descriptors for the own properties
// of the given `object`. `map` is then used to create a `Trait` instance
// after all properties are mapped. Note that we can't create a trait and
// then just copy properties into it since that will fail for inherited
// read-only properties.
map = {};
// Each own property of the given `object` is mapped to a data property
// whose value is a property descriptor.
Object.keys(object).forEach(function (name) {
// If property of an `object` is equal to a `Trait.required`, it means
// that it was marked as "required" property, in which case we map it
// to "required" property.
if (Trait.required ==
Object.getOwnPropertyDescriptor(object, name).value) {
map[name] = {
value: RequiredPropertyDescriptor(name),
enumerable: true
};
}
// Otherwise property is mapped to it's property descriptor.
else {
map[name] = {
value: Object.getOwnPropertyDescriptor(object, name),
enumerable: true
};
}
});
trait = Object.create(Trait.prototype, map);
}
return trait;
}
/**
* Compose a property descriptor map that inherits from `Trait.prototype` and
* contains property descriptors for all the own properties of the passed
* traits.
*
* If two or more traits have own properties with the same name, the returned
* trait will contain a "conflict" property for that name. Composition is a
* commutative and associative operation, and the order of its arguments is
* irrelevant.
*/
function compose(trait1, trait2/*, ...*/) {
// Create a new property descriptor `map` to which all the own properties
// of the passed traits are copied. This map will be used to create a `Trait`
// instance that will be the result of this composition.
var map = {};
// Properties of each passed trait are copied to the composition.
Array.prototype.forEach.call(arguments, function(trait) {
// Copying each property of the given trait.
Object.keys(trait).forEach(function(name) {
// If `map` already owns a property with the `name` and it is not
// marked "required".
if (owns(map, name) && !map[name].value.required) {
// If the source trait's property with the `name` is marked as
// "required", we do nothing, as the requirement was already resolved
// by a property in the `map` (because it already contains a
// non-required property with that `name`). But if properties are just
// different, we have a name clash and we substitute it with a property
// that is marked "conflict".
if (!isRequiredProperty(trait, name) &&
!equivalentDescriptors(map[name].value, trait[name])
) {
map[name] = {
value: ConflictPropertyDescriptor(name),
enumerable: true
};
}
}
// Otherwise, the `map` does not have an own property with the `name`, or
// it is marked "required". Either way, the trait's property is copied to
// the map (if the property of the `map` is marked "required", it is going
// to be resolved by the property that is being copied).
else {
map[name] = { value: trait[name], enumerable: true };
}
});
});
return Object.create(Trait.prototype, map);
}
/**
* `defineProperties` is like `Object.defineProperties`, except that it
* ensures that:
* - An exception is thrown if any property in a given `properties` map
* is marked as "required" property and same named property is not
* found in a given `prototype`.
* - An exception is thrown if any property in a given `properties` map
* is marked as "conflict" property.
* @param {Object} object
* Object to define properties on.
* @param {Object} properties
* Properties descriptor map.
* @returns {Object}
* `object` that was passed as a first argument.
*/
function defineProperties(object, properties) {
// Create a map into which we will copy each verified property from the given
// `properties` description map. We use it to verify that none of the
// provided properties is marked as a "conflict" property and that all
// "required" properties are resolved by a property of an `object`, so we
// can throw an exception before mutating object if that isn't the case.
var verifiedProperties = {};
// Coping each property from a given `properties` descriptor map to a
// verified map of property descriptors.
Object.keys(properties).forEach(function(name) {
// If property is marked as "required" property and we don't have a same
// named property in a given `object` we throw an exception. If `object`
// has same named property just skip this property since required property
// is was inherited and there for requirement was satisfied.
if (isRequiredProperty(properties, name)) {
if (!(name in object))
throwRequiredPropertyError(name);
}
// If property is marked as "conflict" property we throw an exception.
else if (isConflictProperty(properties, name)) {
throwConflictPropertyError(name);
}
// If property is not marked neither as "required" nor "conflict" property
// we copy it to verified properties map.
else {
verifiedProperties[name] = properties[name];
}
});
// If no exceptions were thrown yet, we know that our verified property
// descriptor map has no properties marked as "conflict" or "required",
// so we just delegate to the built-in `Object.defineProperties`.
return Object.defineProperties(object, verifiedProperties);
}
/**
* `create` is like `Object.create`, except that it ensures that:
* - An exception is thrown if any property in a given `properties` map
* is marked as "required" property and same named property is not
* found in a given `prototype`.
* - An exception is thrown if any property in a given `properties` map
* is marked as "conflict" property.
* @param {Object} prototype
* prototype of the composed object
* @param {Object} properties
* Properties descriptor map.
* @returns {Object}
* An object that inherits form a given `prototype` and implements all the
* properties defined by a given `properties` descriptor map.
*/
function create(prototype, properties) {
// Creating an instance of the given `prototype`.
var object = Object.create(prototype);
// Overriding `toString`, `constructor` methods if they are just inherited
// from `Object.prototype` with a same named methods of the `Trait.prototype`
// that will have more relevant behavior.
overrideBuiltInMethods(object, Trait.prototype);
// Trying to define given `properties` on the `object`. We use our custom
// `defineProperties` function instead of build-in `Object.defineProperties`
// that behaves exactly the same, except that it will throw if any
// property in the given `properties` descriptor is marked as "required" or
// "conflict" property.
return defineProperties(object, properties);
}
/**
* Composes new trait. If two or more traits have own properties with the
* same name, the new trait will contain a "conflict" property for that name.
* "compose" is a commutative and associative operation, and the order of its
* arguments is not significant.
*
* **Note:** Use `Trait.compose` instead of calling this function with more
* than one argument. The multiple-argument functionality is strictly for
* backward compatibility.
*
* @params {Object} trait
* Takes traits as an arguments
* @returns {Object}
* New trait containing the combined own properties of all the traits.
* @example
* var newTrait = compose(trait_1, trait_2, ..., trait_N)
*/
function Trait(trait1, trait2) {
// If the function was called with one argument, the argument should be
// an object whose properties are mapped to property descriptors on a new
// instance of Trait, so we delegate to the trait function.
// If the function was called with more than one argument, those arguments
// should be instances of Trait or plain property descriptor maps
// whose properties should be mixed into a new instance of Trait,
// so we delegate to the compose function.
return trait2 === undefined ? trait(trait1) : compose.apply(null, arguments);
}
Object.freeze(Object.defineProperties(Trait.prototype, {
toString: {
value: function toString() {
return "[object " + this.constructor.name + "]";
}
},
/**
* `create` is like `Object.create`, except that it ensures that:
* - An exception is thrown if this trait defines a property that is
* marked as required property and same named property is not
* found in a given `prototype`.
* - An exception is thrown if this trait contains property that is
* marked as "conflict" property.
* @param {Object}
* prototype of the compared object
* @returns {Object}
* An object with all of the properties described by the trait.
*/
create: {
value: function createTrait(prototype) {
return create(undefined === prototype ? Object.prototype : prototype,
this);
},
enumerable: true
},
/**
* Composes a new resolved trait, with all the same properties as the original
* trait, except that all properties whose name is an own property of
* `resolutions` will be renamed to the value of `resolutions[name]`. If
* `resolutions[name]` is `null`, the property is marked as "required".
* @param {Object} resolutions
* An object whose own properties serve as a mapping from old names to new
* names, or to `null` if the property should be excluded.
* @returns {Object}
* New trait with the same own properties as the original trait but renamed.
*/
resolve: {
value: function resolveTrait(resolutions) {
return resolve(resolutions, this);
},
enumerable: true
}
}));
/**
* @see compose
*/
Trait.compose = Object.freeze(compose);
Object.freeze(compose.prototype);
/**
* Constant singleton, representing placeholder for required properties.
* @type {Object}
*/
Trait.required = Object.freeze(Object.create(Object.prototype, {
toString: {
value: Object.freeze(function toString() {
return "<Trait.required>";
})
}
}));
Object.freeze(Trait.required.toString.prototype);
exports.Trait = Object.freeze(Trait);

View File

@ -0,0 +1,147 @@
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
const { Trait } = require('traits');
/**
* @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/list
*/
const Iterable = Trait.compose({
/**
* Hash map of key-values to iterate over.
* Note: That this property can be a getter if you need dynamic behavior.
* @type {Object}
*/
_keyValueMap: Trait.required,
/**
* Custom iterator providing `Iterable`s enumeration behavior.
* @param {Boolean} onKeys
*/
__iterator__: function __iterator__(onKeys, onKeyValue) {
let map = this._keyValueMap;
for (let key in map)
yield onKeyValue ? [key, map[key]] : onKeys ? key : map[key];
}
});
exports.Iterable = Iterable;
/**
* An ordered collection (also known as a sequence) disallowing duplicate
* elements. List is composed out of `Iterable` there for it provides custom
* enumeration behavior that is similar to array (enumerates only on the
* elements of the list). List is a base trait and is meant to be a part of
* composition, since all of it's API is private except length property.
*/
const List = Trait.resolve({ toString: null }).compose({
_keyValueMap: null,
/**
* List constructor can take any number of element to populate itself.
* @params {Object|String|Number} element
* @example
* List(1,2,3).length == 3 // true
*/
constructor: function List() {
this._keyValueMap = [];
for (let i = 0, ii = arguments.length; i < ii; i++)
this._add(arguments[i]);
},
/**
* Number of elements in this list.
* @type {Number}
*/
get length() this._keyValueMap.length,
/**
* Returns a string representing this list.
* @returns {String}
*/
toString: function toString() 'List(' + this._keyValueMap + ')',
/**
* Returns `true` if this list contains the specified `element`.
* @param {Object|Number|String} element
* @returns {Boolean}
*/
_has: function _has(element) 0 <= this._keyValueMap.indexOf(element),
/**
* Appends the specified `element` to the end of this list, if it doesn't
* contains it. Ignores the call if `element` is already contained.
* @param {Object|Number|String} element
*/
_add: function _add(element) {
let list = this._keyValueMap,
index = list.indexOf(element);
if (0 > index)
list.push(this._public[list.length] = element);
},
/**
* Removes specified `element` from this list, if it contains it.
* Ignores the call if `element` is not contained.
* @param {Object|Number|String} element
*/
_remove: function _remove(element) {
let list = this._keyValueMap,
index = list.indexOf(element);
if (0 <= index) {
delete this._public[list.length];
list.splice(index, 1);
for (let length = list.length; index < length; index++)
this._public[index] = list[index];
}
},
/**
* Removes all of the elements from this list.
*/
_clear: function _clear() {
for (let i = 0, ii = this._keyValueMap.length; i < ii; i ++)
delete this._public[i];
this._keyValueMap.splice(0);
},
/**
* Custom iterator providing `List`s enumeration behavior.
* We cant reuse `_iterator` that is defined by `Iterable` since it provides
* iteration in an arbitrary order.
* @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
* @param {Boolean} onKeys
*/
__iterator__: function __iterator__(onKeys, onKeyValue) {
let array = this._keyValueMap.slice(0),
i = -1;
for each(let element in array)
yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
}
});
exports.List = List;

View File

@ -0,0 +1,102 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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 Nickolay Ponomarev.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Nickolay Ponomarev <asqueella@gmail.com> (Original Author)
* Irakli Gozalishvili <gozala@mozilla.com>
* Drew Willcoxon <adw@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 ***** */
const { URL } = require("url");
exports.MatchPattern = MatchPattern;
function MatchPattern(pattern) {
let firstWildcardPosition = pattern.indexOf("*");
let lastWildcardPosition = pattern.lastIndexOf("*");
if (firstWildcardPosition != lastWildcardPosition)
throw new Error("There can be at most one '*' character in a wildcard.");
if (firstWildcardPosition == 0) {
if (pattern.length == 1)
this.anyWebPage = true;
else if (pattern[1] != ".")
throw new Error("Expected a *.<domain name> string, got: " + pattern);
else
this.domain = pattern.substr(2);
}
else {
if (pattern.indexOf(":") == -1) {
throw new Error("When not using *.example.org wildcard, the string " +
"supplied is expected to be either an exact URL to " +
"match or a URL prefix. The provided string ('" +
pattern + "') is unlikely to match any pages.");
}
if (firstWildcardPosition == -1)
this.exactURL = pattern;
else if (firstWildcardPosition == pattern.length - 1)
this.urlPrefix = pattern.substr(0, pattern.length - 1);
else {
throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
"in an unexpected position. It is expected to be the " +
"first or the last character in the wildcard.");
}
}
}
MatchPattern.prototype = {
test: function MatchPattern_test(urlStr) {
try {
var url = URL(urlStr);
}
catch (err) {
return false;
}
if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
return true;
if (this.exactURL && this.exactURL == urlStr)
return true;
if (this.domain && url.host &&
url.host.slice(-this.domain.length) == this.domain)
return true;
if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
return true;
return false;
}
};

View File

@ -0,0 +1,144 @@
/* ***** 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 ***** */
const {Cc,Ci,Cu,components} = require("chrome");
var trackedObjects = {};
var Compacter = {
INTERVAL: 5000,
notify: function(timer) {
var newTrackedObjects = {};
for (name in trackedObjects) {
var oldBin = trackedObjects[name];
var newBin = [];
var strongRefs = [];
for (var i = 0; i < oldBin.length; i++) {
var strongRef = oldBin[i].weakref.get();
if (strongRef && strongRefs.indexOf(strongRef) == -1) {
strongRefs.push(strongRef);
newBin.push(oldBin[i]);
}
}
if (newBin.length)
newTrackedObjects[name] = newBin;
}
trackedObjects = newTrackedObjects;
}
};
var timer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
timer.initWithCallback(Compacter,
Compacter.INTERVAL,
Ci.nsITimer.TYPE_REPEATING_SLACK);
var track = exports.track = function track(object, bin, stackFrameNumber) {
var frame = components.stack.caller;
var weakref = Cu.getWeakReference(object);
if (!bin)
bin = object.constructor.name;
if (bin == "Object")
bin = frame.name;
if (!bin)
bin = "generic";
if (!(bin in trackedObjects))
trackedObjects[bin] = [];
if (stackFrameNumber > 0)
for (var i = 0; i < stackFrameNumber; i++)
frame = frame.caller;
trackedObjects[bin].push({weakref: weakref,
created: new Date(),
filename: frame.filename,
lineNo: frame.lineNumber,
bin: bin});
};
var getBins = exports.getBins = function getBins() {
var names = [];
for (name in trackedObjects)
names.push(name);
return names;
};
var getObjects = exports.getObjects = function getObjects(bin) {
function getLiveObjectsInBin(bin, array) {
for (var i = 0; i < bin.length; i++) {
var object = bin[i].weakref.get();
if (object)
array.push(bin[i]);
}
}
var results = [];
if (bin) {
if (bin in trackedObjects)
getLiveObjectsInBin(trackedObjects[bin], results);
} else
for (name in trackedObjects)
getLiveObjectsInBin(trackedObjects[name], results);
return results;
};
var gc = exports.gc = function gc() {
// Components.utils.forceGC() doesn't currently perform
// cycle collection, which means that e.g. DOM elements
// won't be collected by it. Fortunately, there are
// other ways...
var window = Cc["@mozilla.org/appshell/appShellService;1"]
.getService(Ci.nsIAppShellService)
.hiddenDOMWindow;
var test_utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
test_utils.garbageCollect();
Compacter.notify();
// Not sure why, but sometimes it appears that we don't get
// them all with just one CC, so let's do it again.
test_utils.garbageCollect();
};
require("unload").when(
function() {
trackedObjects = {};
if (timer) {
timer.cancel();
timer = null;
}
});

View File

@ -0,0 +1,209 @@
/* ***** 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 Observers.
*
* The Initial Developer of the Original Code is Daniel Aquino.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Aquino <mr.danielaquino@gmail.com>
* Myk Melez <myk@mozilla.org>
* 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 ***** */
const {Cc,Ci} = require("chrome");
var xpcom = require("xpcom");
/**
* A service for adding, removing and notifying observers of notifications.
* Wraps the nsIObserverService interface.
*
* @version 0.2
*/
var service = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
/**
* A cache of observers that have been added.
*
* We use this to remove observers when a caller calls |Observers.remove|.
*/
var cache = [];
/**
* Topics specifically available to Jetpack-generated extensions.
*
* Using these predefined consts instead of the platform strings is good:
* - allows us to scope topics specifically for Jetpacks
* - addons aren't dependent on strings nor behavior of core platform topics
* - the core platform topics are not clearly named
*
*/
exports.topics = {
/**
* A topic indicating that the application is in a state usable
* by add-ons.
*/
get APPLICATION_READY() packaging.jetpackID + "_APPLICATION_READY"
};
/**
* Register the given callback as an observer of the given topic.
*
* @param topic {String}
* the topic to observe
*
* @param callback {Object}
* the callback; an Object that implements nsIObserver or a Function
* that gets called when the notification occurs
*
* @param thisObject {Object} [optional]
* the object to use as |this| when calling a Function callback
*
* @returns the observer
*/
var add = exports.add = function add(topic, callback, thisObject) {
var observer = new Observer(topic, callback, thisObject);
service.addObserver(observer, topic, true);
cache.push(observer);
return observer;
};
/**
* Unregister the given callback as an observer of the given topic.
*
* @param topic {String}
* the topic being observed
*
* @param callback {Object}
* the callback doing the observing
*
* @param thisObject {Object} [optional]
* the object being used as |this| when calling a Function callback
*/
var remove = exports.remove = function remove(topic, callback, thisObject) {
// This seems fairly inefficient, but I'm not sure how much better
// we can make it. We could index by topic, but we can't index by callback
// or thisObject, as far as I know, since the keys to JavaScript hashes
// (a.k.a. objects) can apparently only be primitive values.
var [observer] = cache.filter(function(v) {
return (v.topic == topic &&
v.callback == callback &&
v.thisObject == thisObject);
});
if (observer) {
service.removeObserver(observer, topic);
cache.splice(cache.indexOf(observer), 1);
}
};
/**
* Notify observers about something.
*
* @param topic {String}
* the topic to notify observers about
*
* @param subject {Object} [optional]
* some information about the topic; can be any JS object or primitive
*
* @param data {String} [optional] [deprecated]
* some more information about the topic; deprecated as the subject
* is sufficient to pass all needed information to the JS observers
* that this module targets; if you have multiple values to pass to
* the observer, wrap them in an object and pass them via the subject
* parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
*/
var notify = exports.notify = function notify(topic, subject, data) {
subject = (typeof subject == "undefined") ? null : new Subject(subject);
data = (typeof data == "undefined") ? null : data;
service.notifyObservers(subject, topic, data);
};
function Observer(topic, callback, thisObject) {
memory.track(this);
this.topic = topic;
this.callback = callback;
this.thisObject = thisObject;
}
Observer.prototype = {
QueryInterface: xpcom.utils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe: function(subject, topic, data) {
// Extract the wrapped object for subjects that are one of our
// wrappers around a JS object. This way we support both wrapped
// subjects created using this module and those that are real
// XPCOM components.
if (subject && typeof subject == "object" &&
("wrappedJSObject" in subject) &&
("observersModuleSubjectWrapper" in subject.wrappedJSObject))
subject = subject.wrappedJSObject.object;
try {
if (typeof this.callback == "function") {
if (this.thisObject)
this.callback.call(this.thisObject, subject, data);
else
this.callback(subject, data);
} else // typeof this.callback == "object" (nsIObserver)
this.callback.observe(subject, topic, data);
} catch (e) {
console.exception(e);
}
}
};
function Subject(object) {
// Double-wrap the object and set a property identifying the
// wrappedJSObject as one of our wrappers to distinguish between
// subjects that are one of our wrappers (which we should unwrap
// when notifying our observers) and those that are real JS XPCOM
// components (which we should pass through unaltered).
this.wrappedJSObject = {
observersModuleSubjectWrapper: true,
object: object
};
}
Subject.prototype = {
QueryInterface: xpcom.utils.generateQI([]),
getHelperForLanguage: function() {},
getInterfaces: function() {}
};
require("unload").when(
function removeAllObservers() {
// Make a copy of cache first, since cache will be changing as we
// iterate through it.
cache.slice().forEach(
function(observer) {
remove(observer.topic, observer.callback, observer.thisObject);
});
});

View File

@ -0,0 +1,134 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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 Packages.
*
* The Initial Developer of the Original Code is Red Hat.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Matěj Cepl <mcepl@redhat.com> (Original Author)
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
const { Cc, Ci, components: { Constructor: CConstructor } } = require("chrome");
const { uri: ADDON_URI } = require("self");
const loginManager = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
const { URL: parseURL } = require("url");
const LoginInfo = CConstructor("@mozilla.org/login-manager/loginInfo;1",
"nsILoginInfo", "init");
function filterMatchingLogins(loginInfo)
Object.keys(this).every(function(key) loginInfo[key] === this[key], this);
/**
* Removes `user`, `password` and `path` fields from the given `url` if it's
* 'http', 'https' or 'ftp'. All other URLs are returned unchanged.
* @example
* http://user:pass@www.site.com/foo/?bar=baz#bang -> http://www.site.com
*/
function normalizeURL(url) {
let { scheme, host, port } = parseURL(url);
// We normalize URL only if it's `http`, `https` or `ftp`. All other types of
// URLs (`resource`, `chrome`, etc..) should not be normalized as they are
// used with add-on associated credentials path.
return scheme === "http" || scheme === "https" || scheme === "ftp" ?
scheme + "://" + (host || "") + (port ? ":" + port : "") :
url
}
function Login(options) {
let login = Object.create(Login.prototype);
Object.keys(options || {}).forEach(function(key) {
if (key === 'url')
login.hostname = normalizeURL(options.url);
else if (key === 'formSubmitURL')
login.formSubmitURL = options.formSubmitURL ?
normalizeURL(options.formSubmitURL) : null;
else if (key === 'realm')
login.httpRealm = options.realm;
else
login[key] = options[key];
});
return login;
}
Login.prototype.toJSON = function toJSON() {
return {
url: this.hostname || ADDON_URI,
realm: this.httpRealm || null,
formSubmitURL: this.formSubmitURL || null,
username: this.username || null,
password: this.password || null,
usernameField: this.usernameField || '',
passwordField: this.passwordField || '',
}
};
Login.prototype.toLoginInfo = function toLoginInfo() {
let { url, realm, formSubmitURL, username, password, usernameField,
passwordField } = this.toJSON();
return new LoginInfo(url, formSubmitURL, realm, username, password,
usernameField, passwordField);
};
function loginToJSON(value) Login(value).toJSON()
/**
* Returns array of `nsILoginInfo` objects that are stored in the login manager
* and have all the properties with matching values as a given `options` object.
* @param {Object} options
* @returns {nsILoginInfo[]}
*/
exports.search = function search(options) {
return loginManager.getAllLogins()
.filter(filterMatchingLogins, Login(options))
.map(loginToJSON);
};
/**
* Stores login info created from the given `options` to the applications
* built-in login management system.
* @param {Object} options.
*/
exports.store = function store(options) {
loginManager.addLogin(Login(options).toLoginInfo());
};
/**
* Removes login info from the applications built-in login management system.
* _Please note: When removing a login info the specified properties must
* exactly match to the one that is already stored or exception will be thrown._
* @param {Object} options.
*/
exports.remove = function remove(options) {
loginManager.removeLogin(Login(options).toLoginInfo());
};

View File

@ -0,0 +1,112 @@
/* ***** 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 ***** */
const {Cc,Ci} = require("chrome");
function stringify(arg) {
try {
return String(arg);
}
catch(ex) {
return "<toString() error>";
}
}
function stringifyArgs(args) {
return Array.map(args, stringify).join(" ");
}
function message(print, level, args) {
print(level + ": " + stringifyArgs(args) + "\n");
}
var Console = exports.PlainTextConsole = function PlainTextConsole(print) {
if (!print)
print = dump;
if (print === dump) {
// If we're just using dump(), auto-enable preferences so
// that the developer actually sees the console output.
var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
prefs.setBoolPref("browser.dom.window.dump.enabled", true);
}
this.print = print;
// Binding all the public methods to an instance so that they can be used
// as callback / listener functions straightaway.
this.log = this.log.bind(this);
this.info = this.info.bind(this);
this.warn = this.warn.bind(this);
this.error = this.error.bind(this);
this.debug = this.debug.bind(this);
this.exception = this.exception.bind(this);
this.trace = this.trace.bind(this);
};
Console.prototype = {
log: function log() {
message(this.print, "info", arguments);
},
info: function info() {
message(this.print, "info", arguments);
},
warn: function warn() {
message(this.print, "warning", arguments);
},
error: function error() {
message(this.print, "error", arguments);
},
debug: function debug() {
message(this.print, "debug", arguments);
},
exception: function exception(e) {
var fullString = ("An exception occurred.\n" +
require("traceback").format(e) + "\n" + e);
this.error(fullString);
},
trace: function trace() {
var traceback = require("traceback");
var stack = traceback.get();
stack.splice(-1, 1);
message(this.print, "info", [traceback.format(stack)]);
}
};

View File

@ -0,0 +1,137 @@
/* ***** 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 Preferences.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Myk Melez <myk@mozilla.org>
* Daniel Aquino <mr.danielaquino@gmail.com>
* 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 ***** */
// The minimum and maximum integers that can be set as preferences.
// The range of valid values is narrower than the range of valid JS values
// because the native preferences code treats integers as NSPR PRInt32s,
// which are 32-bit signed integers on all platforms.
const MAX_INT = Math.pow(2, 31) - 1;
const MIN_INT = -MAX_INT;
const {Cc,Ci,Cr} = require("chrome");
var prefSvc = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).getBranch(null);
var get = exports.get = function get(name, defaultValue) {
switch (prefSvc.getPrefType(name)) {
case Ci.nsIPrefBranch.PREF_STRING:
return prefSvc.getComplexValue(name, Ci.nsISupportsString).data;
case Ci.nsIPrefBranch.PREF_INT:
return prefSvc.getIntPref(name);
case Ci.nsIPrefBranch.PREF_BOOL:
return prefSvc.getBoolPref(name);
case Ci.nsIPrefBranch.PREF_INVALID:
return defaultValue;
default:
// This should never happen.
throw new Error("Error getting pref " + name +
"; its value's type is " +
prefSvc.getPrefType(name) +
", which I don't know " +
"how to handle.");
}
};
var set = exports.set = function set(name, value) {
var prefType;
if (typeof value != "undefined" && value != null)
prefType = value.constructor.name;
switch (prefType) {
case "String":
{
var string = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
string.data = value;
prefSvc.setComplexValue(name, Ci.nsISupportsString, string);
}
break;
case "Number":
// We throw if the number is outside the range, since the result
// will never be what the consumer wanted to store, but we only warn
// if the number is non-integer, since the consumer might not mind
// the loss of precision.
if (value > MAX_INT || value < MIN_INT)
throw new Error("you cannot set the " + name +
" pref to the number " + value +
", as number pref values must be in the signed " +
"32-bit integer range -(2^31-1) to 2^31-1. " +
"To store numbers outside that range, store " +
"them as strings.");
if (value % 1 != 0)
throw new Error("cannot store non-integer number: " + value);
prefSvc.setIntPref(name, value);
break;
case "Boolean":
prefSvc.setBoolPref(name, value);
break;
default:
throw new Error("can't set pref " + name + " to value '" + value +
"'; it isn't a String, Number, or Boolean");
}
};
var has = exports.has = function has(name) {
return (prefSvc.getPrefType(name) != Ci.nsIPrefBranch.PREF_INVALID);
};
var isSet = exports.isSet = function isSet(name) {
return (has(name) && prefSvc.prefHasUserValue(name));
};
var reset = exports.reset = function reset(name) {
try {
prefSvc.clearUserPref(name);
} catch (e if e.result == Cr.NS_ERROR_UNEXPECTED) {
// The pref service throws NS_ERROR_UNEXPECTED when the caller tries
// to reset a pref that doesn't exist or is already set to its default
// value. This interface fails silently in those cases, so callers
// can unconditionally reset a pref without having to check if it needs
// resetting first or trap exceptions after the fact. It passes through
// other exceptions, however, so callers know about them, since we don't
// know what other exceptions might be thrown and what they might mean.
}
};

View File

@ -0,0 +1,630 @@
/* ***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2009-2010 the Mozilla Foundation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the Mozilla Foundation nor the names
* of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** 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);
// Even though manifest.py does some dependency scanning, that
// scan is done as part of an evaluation of what the add-on needs
// for security purposes. The following regexps are used to scan for
// dependencies inside a simplified define() callback:
// define(function(require, exports, module){ var a = require('a'); });
// and are used at runtime ensure the dependencies needed by
// the define factory function are already evaluated and ready.
// Even though this loader is a sync loader, and could fetch the module
// as the require() call happens, it would differ in behavior as
// compared to the async browser case, which would make sure to execute
// the dependencies first before executing the define() factory function.
// So this dependency scanning and evaluation is kept to match the
// async behavior.
var commentRegExp = /(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg;
var cjsRequireRegExp = /require\(["']([\w\!\-_\.\/]+)["']\)/g;
var cjsStandardDeps = ['require', 'exports', 'module'];
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 ('modules' in options)
throw new Error('options.modules is no longer supported');
// pathAccessed used to know if a module was accessed/required
// by another module, and in that case, assigning the module value
// via a define callback is not allowed.
if (options.pathAccessed === undefined)
options.pathAccessed = {};
if (options.globals === undefined)
options.globals = {};
this.fs = options.fs;
this.sandboxFactory = options.sandboxFactory;
this.sandboxes = {};
this.modules = {};
this.pathAccessed = options.pathAccessed;
this.module_infos = {};
this.pathToModule = {};
this.defineUsed = {};
this.globals = options.globals;
this.getModuleExports = options.getModuleExports;
this.modifyModuleSandbox = options.modifyModuleSandbox;
this.securityPolicy = options.securityPolicy;
};
exports.Loader.prototype = {
_makeApi: function _makeApi(basePath) {
var self = this;
function syncRequire(module) {
var exports;
if (self.getModuleExports)
exports = self.getModuleExports(basePath, module);
var module_info = null; /* null for require("chrome") */
if (!exports) {
var path = self.fs.resolveModule(basePath, module);
if (!path)
throw new Error('Module "' + module + '" not found');
// Track accesses to this module via its normalized path
if (!self.pathAccessed[path]) {
self.pathAccessed[path] = 0;
}
self.pathAccessed[path] += 1;
// Remember the name of the module as it maps to its path
self.pathToModule[path] = module;
if (path in self.modules) {
module_info = self.module_infos[path];
} else {
module_info = self.fs.getFile(path);
/* module_info.filename is read by sandbox.evaluate() to
generate tracebacks, so the property must be named
".filename" even though ".url" might be more accurate */
if (module_info.filename === undefined)
module_info.filename = path;
if (self.securityPolicy &&
!self.securityPolicy.allowEval(self, basePath, module,
module_info))
throw new Error("access denied to execute module: " +
module);
var sandbox = self.sandboxFactory.createSandbox(module_info);
self.sandboxes[path] = sandbox;
for (name in self.globals)
sandbox.defineProperty(name, self.globals[name]);
var api = self._makeApi(path);
sandbox.defineProperty('require', api.require);
sandbox.defineProperty('define', api.define);
self.module_infos[path] = module_info;
if (self.modifyModuleSandbox)
self.modifyModuleSandbox(sandbox, module_info);
/* set up an environment in which module code can use CommonJS
patterns like:
module.exports = newobj;
module.setExports(newobj);
if (module.id == "main") stuff();
define("async", function() {return newobj});
*/
sandbox.evaluate("var module = {exports: {}};");
sandbox.evaluate("module.setExports = function(obj) {module.exports = obj; return obj;};");
sandbox.evaluate("var exports = module.exports;");
sandbox.evaluate("module.id = '" + module + "';");
var preeval_exports = sandbox.getProperty("exports");
self.modules[path] = sandbox.getProperty("exports");
sandbox.evaluate(module_info);
var posteval_exports = sandbox.getProperty("module").exports;
if (posteval_exports !== preeval_exports) {
/* if they used module.exports= or module.setExports(), get
the new value now. If they used define(), we must be
careful to leave self.modules[path] alone, as it will have
been modified in the asyncMain() callback-handling code,
fired during sandbox.evaluate(). */
if (self.defineUsed[path]) {
// you can do one or the other, not both
throw new Error("define() was used, so module.exports= and "
+ "module.setExports() may not be used: "
+ path);
}
self.modules[path] = posteval_exports;
}
}
exports = self.modules[path];
}
if (self.securityPolicy &&
!self.securityPolicy.allowImport(self, basePath, module,
module_info, exports))
throw new Error("access denied to import module: " + module);
return exports;
};
// START support Async module-style require and define calls.
// If the only argument to require is a string, then the module that
// is represented by that string is fetched for the appropriate context.
//
// If the first argument is an array, then it will be treated as an array
// of dependency string names to fetch. An optional function callback can
// be specified to execute when all of those dependencies are available.
function asyncRequire(deps, callback) {
if (typeof deps === "string" && !callback) {
// Just return the module wanted via sync require.
return syncRequire(deps);
} else {
asyncMain(null, basePath, null, deps, callback);
return undefined;
}
}
// The function that handles definitions of modules. Differs from
// require() in that a string for the module should be the first
// argument, and the function to execute after dependencies are loaded
// should return a value to define the module corresponding to the first
// argument's name.
function define (name, deps, callback) {
// Only allow one call to define per module/file.
if (self.defineUsed[basePath]) {
throw new Error("Only one call to define() allowed per file: " +
basePath);
} else {
self.defineUsed[basePath] = true;
}
// For anonymous modules, the namePath is the basePath
var namePath = basePath,
exports = {}, exported;
// Adjust args if an anonymous module
if (typeof name !== 'string') {
callback = deps;
deps = name;
name = null;
}
// If just a define({}) call (no dependencies),
// adjust args accordingly.
if (!Array.isArray(deps)) {
callback = deps;
deps = null;
}
// Set up the path if we have a name
if (name) {
// Make sure that the name matches the expected name, otherwise
// throw an error.
namePath = self.fs.resolveModule(basePath, name);
if (self.pathToModule[namePath] !== name) {
throw new Error("Mismatched define(). Named module " + name +
" does not match expected name of " +
self.pathToModule[basePath] +
" in " + basePath);
}
}
// If the callback is not an actual function, it means it already
// has the definition of the module as a literal value.
if (!deps && callback && typeof callback !== 'function') {
self.modules[namePath] = callback;
return;
}
// Set the exports value now in case other modules need a handle
// on it for cyclical cases.
self.modules[namePath] = exports;
// Load dependencies and call the module's definition function.
exported = asyncMain(name, namePath, exports, deps, callback);
// Assign output of function to name, if exports was not
// in play (which asyncMain already figured out).
if (exported !== undefined) {
if (self.pathAccessed[namePath] > 1) {
// Another module already accessed the exported value,
// need to throw to avoid nasty circular dependency weirdness
throw new Error('Module "' + (name || namePath) + '" cannot use ' +
'return from define to define the module ' +
'after another module has referenced its ' +
'exported value.');
} else {
self.modules[namePath] = exported;
}
}
}
// The function that handles the main async module work, for both
// require([], function(){}) calls and define calls.
// It makes sure all the dependencies exist before calling the
// callback function. It will return the result of the callback
// function if "exports" is not a dependency.
function asyncMain (name, namePath, exports, deps, callback) {
if (typeof deps === 'function') {
callback = deps;
deps = null;
}
if (!deps) {
deps = [];
// The shortened form of the async wrapper for CommonJS modules:
// define(function (require, exports, module) {});
// require calls could be inside the function, so toString it
// and pull out the dependencies.
// Remove comments from the callback string,
// look for require calls, and pull them into the dependencies.
// The comment regexp is not very robust, but good enough to
// avoid commented out require calls and to find normal, sync
// require calls in the function.
callback
.toString()
.replace(commentRegExp, "")
.replace(cjsRequireRegExp, function (match, dep) {
deps.push(dep);
});
// Prepend standard require, exports, and module dependencies
// (and in that *exact* order per spec), but only add as many as
// was asked for via the callback's function argument length.
// In particular, do *not* pass exports if it was not asked for.
// By asking for exports as a dependency the rest of this
// asyncRequire code assumes then that the return value from the
// function should not be used as the exported module value.
deps = cjsStandardDeps.slice(0, callback.length).concat(deps);
}
var depModules = [],
usesExports = false,
exported;
// Load all the dependencies, with the "require", "exports" and
// "module" ones getting special handling to match the traditional
// CommonJS sync module expectations.
deps.forEach(function (dep) {
if (dep === "require") {
depModules.push(asyncRequire);
} else if (dep === "module") {
depModules.push({
id: name
});
} else if (dep === "exports") {
usesExports = true;
depModules.push(exports);
} else {
var overridden;
if (self.getModuleExports)
overridden = self.getModuleExports(basePath, dep);
if (overridden) {
depModules.push(overridden);
return;
}
var depPath = self.fs.resolveModule(basePath, dep);
if (!self.modules[depPath]) {
syncRequire(dep);
}
depModules.push(self.modules[depPath]);
}
});
// Execute the function.
if (callback) {
exported = callback.apply(null, depModules);
}
if (exported !== undefined) {
if (usesExports) {
throw new Error('Inside "' + namePath + '", cannot use exports ' +
'and also return a value from a define ' +
'definition function');
} else {
return exported;
}
}
return undefined;
};
return {
require: asyncRequire,
define: define
};
// END support for Async module-style
},
// 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, callback) {
return (this._makeApi(null).require)(module, callback);
},
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]);
var api = this._makeApi(null);
sandbox.defineProperty('require', api.require);
sandbox.defineProperty('define', api.define);
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 || path.charAt(0) != '.')
baseURI = this._rootURI;
else
baseURI = ios.newURI(base, null, null);
var newURI = ios.newURI(path, null, baseURI);
if (newURI.spec.indexOf(this._rootURIDir) == 0) {
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;
}
return null;
},
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);

View File

@ -0,0 +1,95 @@
// While this adapter is complete, it most likely isn't very secure,
// in that it allows the remote addon process to ask for any content
// on the host filesystem.
/* ***** 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 ***** */
if (this.chrome) {
exports.id = chrome.call("self:id");
exports.data = {
load: function(path) {
return chrome.call("self:load", path, new Error().stack);
},
url: function(path) {
return chrome.call("self:url", path, new Error().stack);
}
};
} else {
// Here we basically have to reimplement the self module.
let file = require("file");
let url = require("url");
let traceback = require("traceback");
let packageData = packaging.options.packageData;
let resourcePackages = packaging.options.resourcePackages;
let id = packaging.jetpackID;
function caller(stack, levels) {
var e = {
stack: stack
};
let callerInfo = traceback.fromException(e).slice(-2-levels)[0];
let info = url.URL(callerInfo.filename);
let pkgName = resourcePackages[info.host];
// pkgName is "my-package", suitable for lookup in options["packageData"]
return pkgName;
}
function getURL(name, stack, level) {
let pkgName = caller(stack, level);
// packageData[] = "resource://jetpack-JID-PKGNAME-data/"
if (pkgName in packageData)
return url.URL(name, packageData[pkgName]).toString();
throw new Error("No data for package " + pkgName);
}
exports.register = function(addon) {
addon.registerCall("self:id", function(name) {
return id;
});
addon.registerCall("self:load", function(name, path, stack) {
let data_url = getURL(path, stack, 1);
let fn = url.toFilename(data_url);
let data = file.read(fn);
return data;
});
addon.registerCall("self:url", function(name, path, stack) {
return getURL(path, stack, 1);
});
}
}

View File

@ -0,0 +1,59 @@
let file = require("file");
let url = require("url");
let traceback = require("traceback");
let packageData = packaging.options.packageData;
let resourcePackages = packaging.options.resourcePackages;
let id = packaging.jetpackID;
exports.id = id;
// Some XPCOM APIs require valid URIs as an argument for certain operations (see
// `nsILoginManager` for example). This property represents add-on associated
// unique URI string that can be used for that.
exports.uri = "addon:" + id;
// what URI was our Nth parent stack frame loaded from? We use this to
// determine "who" has called our load() or url() methods. This is an
// unpleasant hack that needs to be replaced: the real question to ask is
// "who" did the require("self") call. The "self" module should not be a
// singleton: each invocation of require() could get a separate one. I
// *think* the right level of granularity is that each package gets a
// separate instance: all packages in an XPI bundle will share the same
// ID, but each package will have a separate resource/data directory. So
// package1.moduleA and package1.moduleB will both get the same data when
// they do require("self").data.load("foo.txt"), but package2.moduleC
// will get something different for the same code.
// The biggest problem with using stack introspection at the time of
// load()/url() is confusion: the module is allowed to pass their
// require("self").data object to someone else, with the expectation that
// the recipient is going to get the same data they would have gotten.
// The second biggest problem is confused deputy.
function caller(levels) {
let callerInfo = traceback.get().slice(-2-levels)[0];
let info = url.URL(callerInfo.filename);
let pkgName = resourcePackages[info.host];
// pkgName is "my-package", suitable for lookup in options["packageData"]
return pkgName;
}
function getURL(name, level) {
let pkgName = caller(level+1);
// packageData[] = "resource://jetpack-JID-PKGNAME-data/"
if (pkgName in packageData)
return url.URL(name, packageData[pkgName]).toString();
throw new Error("No data for package " + pkgName);
}
exports.data = {
load: function load(name) {
let data_url = getURL(name, 1);
let fn = url.toFilename(data_url);
let data = file.read(fn);
return data;
},
url: function url(name) { return getURL(name, 1); }
}

View File

@ -0,0 +1,761 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Atul Varma <atul@mozilla.com>
* Dietrich Ayala <dietrich@mozilla.com>
* Felipe Gomes <felipc@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 ***** */
const {Cc,Ci,Cu} = require("chrome");
var NetUtil = {};
Cu.import("resource://gre/modules/NetUtil.jsm", NetUtil);
NetUtil = NetUtil.NetUtil;
const errors = require("errors");
const windowUtils = require("window-utils");
const apiUtils = require("api-utils");
const collection = require("collection");
// TODO: The hard-coding of app-specific info here isn't very nice;
// ideally such app-specific info should be more decoupled, and the
// module should be extensible, allowing for support of new apps at
// runtime, perhaps by inspecting supported packages (e.g. via
// dynamically-named modules or package-defined extension points).
if (!require("xul-app").is("Firefox")) {
throw new Error([
"The tab-browser module currently supports only Firefox. In the future ",
"it will support other applications. Please see ",
"https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
].join(""));
}
// Utility function to open a new browser window.
function openBrowserWindow(callback, url) {
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIWindowWatcher);
let urlString = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
urlString.data = url;
let window = ww.openWindow(null, "chrome://browser/content/browser.xul",
"_blank", "chrome,all,dialog=no", urlString);
if (callback) {
function onLoad(event) {
if (event.target && event.target.defaultView == window) {
window.removeEventListener("load", onLoad, true);
try {
require("timer").setTimeout(function () {
callback(event);
}, 10);
} catch (e) { console.exception(e); }
}
}
window.addEventListener("load", onLoad, true);
}
return window;
}
// Open a URL in a new tab
exports.addTab = function addTab(url, options) {
if (!options)
options = {};
options.url = url;
options = apiUtils.validateOptions(options, {
// TODO: take URL object instead of string (bug 564524)
url: {
is: ["string"],
ok: function (v) !!v,
msg: "The url parameter must have be a non-empty string."
},
inNewWindow: {
is: ["undefined", "null", "boolean"]
},
inBackground: {
is: ["undefined", "null", "boolean"]
},
onLoad: {
is: ["undefined", "null", "function"]
},
isPinned: {
is: ["undefined", "boolean"]
}
});
var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
if (!win || options.inNewWindow) {
openBrowserWindow(function(e) {
if(options.isPinned) {
//get the active tab in the recently created window
let mainWindow = e.target.defaultView;
mainWindow.gBrowser.pinTab(mainWindow.gBrowser.selectedTab);
}
require("errors").catchAndLog(function(e) options.onLoad(e))(e);
}, options.url);
} else {
let tab = win.gBrowser.addTab(options.url);
if (!options.inBackground)
win.gBrowser.selectedTab = tab;
if (options.onLoad) {
let tabBrowser = win.gBrowser.getBrowserForTab(tab);
tabBrowser.addEventListener("load", function(e) {
if (e.target.defaultView.content.location == "about:blank")
return;
// remove event handler from addTab - don't want notified
// for subsequent loads in same tab.
tabBrowser.removeEventListener("load", arguments.callee, true);
require("errors").catchAndLog(function(e) options.onLoad(e))(e);
}, true);
}
}
}
// Iterate over a window's tabbrowsers
function tabBrowserIterator(window) {
var browsers = window.document.querySelectorAll("tabbrowser");
for (var i = 0; i < browsers.length; i++)
yield browsers[i];
}
// Iterate over a tabbrowser's tabs
function tabIterator(tabbrowser) {
var tabs = tabbrowser.tabContainer;
for (var i = 0; i < tabs.children.length; i++) {
yield tabs.children[i];
}
}
// Tracker for all tabbrowsers across all windows,
// or a single tabbrowser if the window is given.
function Tracker(delegate, window) {
this._delegate = delegate;
this._browsers = [];
this._window = window;
this._windowTracker = new windowUtils.WindowTracker(this);
require("unload").ensure(this);
}
Tracker.prototype = {
__iterator__: function __iterator__() {
for (var i = 0; i < this._browsers.length; i++)
yield this._browsers[i];
},
get: function get(index) {
return this._browsers[index];
},
onTrack: function onTrack(window) {
if (this._window && window != this._window)
return;
for (browser in tabBrowserIterator(window))
this._browsers.push(browser);
if (this._delegate)
for (browser in tabBrowserIterator(window))
this._delegate.onTrack(browser);
},
onUntrack: function onUntrack(window) {
if (this._window && window != this._window)
return;
for (browser in tabBrowserIterator(window)) {
let index = this._browsers.indexOf(browser);
if (index != -1)
this._browsers.splice(index, 1);
else
console.error("internal error: browser tab not found");
}
if (this._delegate)
for (browser in tabBrowserIterator(window))
this._delegate.onUntrack(browser);
},
get length() {
return this._browsers.length;
},
unload: function unload() {
this._windowTracker.unload();
}
};
exports.Tracker = apiUtils.publicConstructor(Tracker);
// Tracker for all tabs across all windows,
// or a single window if it's given.
function TabTracker(delegate, window) {
this._delegate = delegate;
this._tabs = [];
this._tracker = new Tracker(this, window);
require("unload").ensure(this);
}
TabTracker.prototype = {
_TAB_EVENTS: ["TabOpen", "TabClose"],
_safeTrackTab: function safeTrackTab(tab) {
this._tabs.push(tab);
try {
this._delegate.onTrack(tab);
} catch (e) {
console.exception(e);
}
},
_safeUntrackTab: function safeUntrackTab(tab) {
var index = this._tabs.indexOf(tab);
if (index == -1)
console.error("internal error: tab not found");
this._tabs.splice(index, 1);
try {
this._delegate.onUntrack(tab);
} catch (e) {
console.exception(e);
}
},
handleEvent: function handleEvent(event) {
switch (event.type) {
case "TabOpen":
this._safeTrackTab(event.target);
break;
case "TabClose":
this._safeUntrackTab(event.target);
break;
default:
throw new Error("internal error: unknown event type: " +
event.type);
}
},
onTrack: function onTrack(tabbrowser) {
for (tab in tabIterator(tabbrowser))
this._safeTrackTab(tab);
var self = this;
this._TAB_EVENTS.forEach(
function(eventName) {
tabbrowser.tabContainer.addEventListener(eventName, self, true);
});
},
onUntrack: function onUntrack(tabbrowser) {
for (tab in tabIterator(tabbrowser))
this._safeUntrackTab(tab);
var self = this;
this._TAB_EVENTS.forEach(
function(eventName) {
tabbrowser.tabContainer.removeEventListener(eventName, self, true);
});
},
unload: function unload() {
this._tracker.unload();
}
};
exports.TabTracker = apiUtils.publicConstructor(TabTracker);
exports.whenContentLoaded = function whenContentLoaded(callback) {
var cb = require("errors").catchAndLog(function eventHandler(event) {
if (event.target && event.target.defaultView)
callback(event.target.defaultView);
});
var tracker = new Tracker({
onTrack: function(tabBrowser) {
tabBrowser.addEventListener("DOMContentLoaded", cb, false);
},
onUntrack: function(tabBrowser) {
tabBrowser.removeEventListener("DOMContentLoaded", cb, false);
}
});
return tracker;
};
exports.__defineGetter__("activeTab", function() {
const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator);
let mainWindow = wm.getMostRecentWindow("navigator:browser");
return mainWindow.gBrowser.selectedTab;
});
/******************* TabModule *********************/
// Supported tab events
const events = [
"onActivate",
"onDeactivate",
"onOpen",
"onClose",
"onReady",
"onLoad",
"onPaint"
];
exports.tabEvents = events;
/**
* TabModule
*
* Constructor for a module that implements the tabs API
*/
let TabModule = exports.TabModule = function TabModule(window) {
let self = this;
/**
* Tab
*
* Safe object representing a tab.
*/
let tabConstructor = apiUtils.publicConstructor(function(element) {
if (!element)
throw new Error("no tab element.");
let win = element.ownerDocument.defaultView;
if (!win)
throw new Error("element has no window.");
if (window && win != window)
throw new Error("module's window and element's window don't match.");
let browser = win.gBrowser.getBrowserForTab(element);
this.__defineGetter__("title", function() browser.contentDocument.title);
this.__defineGetter__("location", function() browser.contentDocument.location);
this.__defineSetter__("location", function(val) browser.contentDocument.location = val);
this.__defineGetter__("contentWindow", function() browser.contentWindow);
this.__defineGetter__("contentDocument", function() browser.contentDocument);
this.__defineGetter__("favicon", function() {
let pageURI = NetUtil.newURI(browser.contentDocument.location);
let fs = Cc["@mozilla.org/browser/favicon-service;1"].
getService(Ci.nsIFaviconService);
let faviconURL;
try {
let faviconURI = fs.getFaviconForPage(pageURI);
faviconURL = fs.getFaviconDataAsDataURL(faviconURI);
} catch(ex) {
let data = getChromeURLContents("chrome://mozapps/skin/places/defaultFavicon.png");
let encoded = browser.contentWindow.btoa(data);
faviconURL = "data:image/png;base64," + encoded;
}
return faviconURL;
});
this.__defineGetter__("style", function() null); // TODO
this.__defineGetter__("index", function() win.gBrowser.getBrowserIndexForDocument(browser.contentDocument));
this.__defineGetter__("thumbnail", function() getThumbnailCanvasForTab(element, browser.contentWindow));
this.close = function() win.gBrowser.removeTab(element);
this.move = function(index) {
win.gBrowser.moveTabTo(element, index);
};
this.__defineGetter__("isPinned", function() element.pinned);
this.pin = function() win.gBrowser.pinTab(element);
this.unpin = function() win.gBrowser.unpinTab(element);
// Set up the event handlers
let tab = this;
events.filter(function(e) e != "onOpen").forEach(function(e) {
// create a collection for each event
collection.addCollectionProperty(tab, e);
// make tabs setter for each event, for adding via property assignment
tab.__defineSetter__(e, function(val) tab[e].add(val));
});
// listen for events, filtered on this tab
eventsTabDelegate.addTabDelegate(this);
});
/**
* tabs.activeTab
*/
this.__defineGetter__("activeTab", function() {
try {
return window ? tabConstructor(window.gBrowser.selectedTab)
: tabConstructor(exports.activeTab);
}
catch (e) { }
return null;
});
this.__defineSetter__("activeTab", function(tab) {
let [tabElement, win] = getElementAndWindowForTab(tab, window);
if (tabElement) {
// set as active tab
win.gBrowser.selectedTab = tabElement;
// focus the window
win.focus();
}
});
this.open = function TM_open(options) {
open(options, tabConstructor, window);
}
// Set up the event handlers
events.forEach(function(eventHandler) {
// create a collection for each event
collection.addCollectionProperty(self, eventHandler);
// make tabs setter for each event, for adding via property assignment
self.__defineSetter__(eventHandler, function(val) self[eventHandler].add(val));
});
// Tracker that listens for tab events, and proxies
// them to registered event listeners.
let eventsTabDelegate = {
selectedTab: null,
tabs: [],
addTabDelegate: function TETT_addTabDelegate(tabObj) {
this.tabs.push(tabObj);
},
pushTabEvent: function TETT_pushTabEvent(event, tab) {
for (let callback in self[event]) {
require("errors").catchAndLog(function(tab) {
callback(new tabConstructor(tab));
})(tab);
}
if (event != "onOpen") {
this.tabs.forEach(function(tabObj) {
if (tabObj[event].length) {
let [tabEl,] = getElementAndWindowForTab(tabObj, window);
if (tabEl == tab) {
for (let callback in tabObj[event])
require("errors").catchAndLog(function() callback())();
}
}
// if being closed, remove the tab object from the cache
// of tabs to notify about events.
if (event == "onClose")
this.tabs.splice(this.tabs.indexOf(tabObj), 1);
}, this);
}
},
unload: function() {
this.selectedTab = null;
this.tabs.splice(0);
}
};
require("unload").ensure(eventsTabDelegate);
let eventsTabTracker = new ModuleTabTracker({
onTrack: function TETT_onTrack(tab) {
eventsTabDelegate.pushTabEvent("onOpen", tab);
},
onUntrack: function TETT_onUntrack(tab) {
eventsTabDelegate.pushTabEvent("onClose", tab);
},
onSelect: function TETT_onSelect(tab) {
if (eventsTabDelegate.selectedTab)
eventsTabDelegate.pushTabEvent("onDeactivate", tab);
eventsTabDelegate.selectedTab = new tabConstructor(tab);
eventsTabDelegate.pushTabEvent("onActivate", tab);
},
onReady: function TETT_onReady(tab) {
eventsTabDelegate.pushTabEvent("onReady", tab);
},
onLoad: function TETT_onLoad(tab) {
eventsTabDelegate.pushTabEvent("onLoad", tab);
},
onPaint: function TETT_onPaint(tab) {
eventsTabDelegate.pushTabEvent("onPaint", tab);
}
}, window);
require("unload").ensure(eventsTabTracker);
// Iterator for all tabs
this.__iterator__ = function tabsIterator() {
for (let i = 0; i < eventsTabTracker._tabs.length; i++)
yield tabConstructor(eventsTabTracker._tabs[i]);
}
this.__defineGetter__("length", function() eventsTabTracker._tabs.length);
// Cleanup when unloaded
this.unload = function TM_unload() {
// Unregister tabs event listeners
events.forEach(function(e) self[e] = []);
}
require("unload").ensure(this);
} // End of TabModule constructor
/**
* tabs.open - open a URL in a new tab
*/
function open(options, tabConstructor, window) {
if (typeof options === "string")
options = { url: options };
options = apiUtils.validateOptions(options, {
url: {
is: ["string"]
},
inNewWindow: {
is: ["undefined", "boolean"]
},
inBackground: {
is: ["undefined", "boolean"]
},
isPinned: {
is: ["undefined", "boolean"]
},
onOpen: {
is: ["undefined", "function"]
}
});
if (window)
options.inNewWindow = false;
let win = window || require("window-utils").activeBrowserWindow;
if (!win || options.inNewWindow)
openURLInNewWindow(options, tabConstructor);
else
openURLInNewTab(options, win, tabConstructor);
}
function openURLInNewWindow(options, tabConstructor) {
let addTabOptions = {
inNewWindow: true
};
if (options.onOpen) {
addTabOptions.onLoad = function(e) {
let win = e.target.defaultView;
let tabEl = win.gBrowser.tabContainer.childNodes[0];
let tabBrowser = win.gBrowser.getBrowserForTab(tabEl);
tabBrowser.addEventListener("load", function(e) {
tabBrowser.removeEventListener("load", arguments.callee, true);
let tab = tabConstructor(tabEl);
require("errors").catchAndLog(function(e) options.onOpen(e))(tab);
}, true);
};
}
if (options.isPinned) {
addTabOptions.isPinned = true;
}
exports.addTab(options.url.toString(), addTabOptions);
}
function openURLInNewTab(options, window, tabConstructor) {
window.focus();
let tabEl = window.gBrowser.addTab(options.url.toString());
if (!options.inBackground)
window.gBrowser.selectedTab = tabEl;
if (options.isPinned)
window.gBrowser.pinTab(tabEl);
if (options.onOpen) {
let tabBrowser = window.gBrowser.getBrowserForTab(tabEl);
tabBrowser.addEventListener("load", function(e) {
// remove event handler from addTab - don't want to be notified
// for subsequent loads in same tab.
tabBrowser.removeEventListener("load", arguments.callee, true);
let tab = tabConstructor(tabEl);
require("timer").setTimeout(function() {
require("errors").catchAndLog(function(tab) options.onOpen(tab))(tab);
}, 10);
}, true);
}
}
function getElementAndWindowForTab(tabObj, window) {
// iterate over open windows, or use single window if provided
let windowIterator = window ? function() { yield window; }
: require("window-utils").windowIterator;
for (let win in windowIterator()) {
if (win.gBrowser) {
// find the tab element at tab.index
let index = win.gBrowser.getBrowserIndexForDocument(tabObj.contentDocument);
if (index > -1)
return [win.gBrowser.tabContainer.getItemAtIndex(index), win];
}
}
return [null, null];
}
// Tracker for all tabs across all windows
// This is tab-browser.TabTracker, but with
// support for additional events added.
function ModuleTabTracker(delegate, window) {
this._delegate = delegate;
this._tabs = [];
this._tracker = new Tracker(this, window);
require("unload").ensure(this);
}
ModuleTabTracker.prototype = {
_TAB_EVENTS: ["TabOpen", "TabClose", "TabSelect", "DOMContentLoaded",
"load", "MozAfterPaint"],
_safeTrackTab: function safeTrackTab(tab) {
tab.addEventListener("load", this, false);
tab.linkedBrowser.addEventListener("MozAfterPaint", this, false);
this._tabs.push(tab);
try {
this._delegate.onTrack(tab);
} catch (e) {
console.exception(e);
}
},
_safeUntrackTab: function safeUntrackTab(tab) {
tab.removeEventListener("load", this, false);
tab.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
var index = this._tabs.indexOf(tab);
if (index == -1)
throw new Error("internal error: tab not found");
this._tabs.splice(index, 1);
try {
this._delegate.onUntrack(tab);
} catch (e) {
console.exception(e);
}
},
_safeSelectTab: function safeSelectTab(tab) {
var index = this._tabs.indexOf(tab);
if (index == -1)
console.error("internal error: tab not found");
try {
if (this._delegate.onSelect)
this._delegate.onSelect(tab);
} catch (e) {
console.exception(e);
}
},
_safeDOMContentLoaded: function safeDOMContentLoaded(event) {
let tabBrowser = event.currentTarget;
let tabBrowserIndex = tabBrowser.getBrowserIndexForDocument(event.target);
// TODO: I'm seeing this when loading data url images
if (tabBrowserIndex == -1)
return;
let tab = tabBrowser.tabContainer.getItemAtIndex(tabBrowserIndex);
let index = this._tabs.indexOf(tab);
if (index == -1)
console.error("internal error: tab not found");
try {
if (this._delegate.onReady)
this._delegate.onReady(tab);
} catch (e) {
console.exception(e);
}
},
_safeLoad: function safeLoad(event) {
let tab = event.target;
let index = this._tabs.indexOf(tab);
if (index == -1)
console.error("internal error: tab not found");
try {
if (this._delegate.onLoad)
this._delegate.onLoad(tab);
} catch (e) {
console.exception(e);
}
},
_safeMozAfterPaint: function safeMozAfterPaint(event) {
let win = event.currentTarget.ownerDocument.defaultView;
let tabIndex = win.gBrowser.getBrowserIndexForDocument(event.target.document);
if (tabIndex == -1)
return;
let tab = win.gBrowser.tabContainer.getItemAtIndex(tabIndex);
let index = this._tabs.indexOf(tab);
if (index == -1)
console.error("internal error: tab not found");
try {
if (this._delegate.onPaint)
this._delegate.onPaint(tab);
} catch (e) {
console.exception(e);
}
},
handleEvent: function handleEvent(event) {
switch (event.type) {
case "TabOpen":
this._safeTrackTab(event.target);
break;
case "TabClose":
this._safeUntrackTab(event.target);
break;
case "TabSelect":
this._safeSelectTab(event.target);
break;
case "DOMContentLoaded":
this._safeDOMContentLoaded(event);
break;
case "load":
this._safeLoad(event);
break;
case "MozAfterPaint":
this._safeMozAfterPaint(event);
break;
default:
throw new Error("internal error: unknown event type: " +
event.type);
}
},
onTrack: function onTrack(tabbrowser) {
for (tab in tabIterator(tabbrowser))
this._safeTrackTab(tab);
tabbrowser.tabContainer.addEventListener("TabOpen", this, false);
tabbrowser.tabContainer.addEventListener("TabClose", this, false);
tabbrowser.tabContainer.addEventListener("TabSelect", this, false);
tabbrowser.ownerDocument.defaultView.gBrowser.addEventListener("DOMContentLoaded", this, false);
},
onUntrack: function onUntrack(tabbrowser) {
for (tab in tabIterator(tabbrowser))
this._safeUntrackTab(tab);
tabbrowser.tabContainer.removeEventListener("TabOpen", this, false);
tabbrowser.tabContainer.removeEventListener("TabClose", this, false);
tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
tabbrowser.ownerDocument.defaultView.gBrowser.removeEventListener("DOMContentLoaded", this, false);
},
unload: function unload() {
this._tracker.unload();
}
};
// Utility to get a thumbnail canvas from a tab object
function getThumbnailCanvasForTab(tabEl, window) {
var thumbnail = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
thumbnail.mozOpaque = true;
var window = tabEl.linkedBrowser.contentWindow;
thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
var aspectRatio = 0.5625; // 16:9
thumbnail.height = Math.round(thumbnail.width * aspectRatio);
var ctx = thumbnail.getContext("2d");
var snippetWidth = window.innerWidth * .6;
var scale = thumbnail.width / snippetWidth;
ctx.scale(scale, scale);
ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth, snippetWidth * aspectRatio, "rgb(255,255,255)");
return thumbnail;
}
// Utility to return the contents of the target of a chrome URL
function getChromeURLContents(chromeURL) {
let io = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
let channel = io.newChannel(chromeURL, null, null);
let input = channel.open();
let stream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
stream.setInputStream(input);
let str = stream.readBytes(input.available());
stream.close();
input.close();
return str;
}

View File

@ -0,0 +1,56 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
const ON_PREFIX = "on";
const TAB_PREFIX = "Tab";
const EVENTS = {
ready: "DOMContentLoaded",
open: "TabOpen",
close: "TabClose",
activate: "TabSelect",
deactivate: null
}
exports.EVENTS = EVENTS;
Object.keys(EVENTS).forEach(function(name) {
EVENTS[name] = {
name: name,
listener: ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1),
dom: EVENTS[name]
}
});

View File

@ -0,0 +1,296 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
const { Ci } = require('chrome');
const { Trait } = require("traits");
const { EventEmitter } = require("events");
const { validateOptions } = require("api-utils");
const { Enqueued } = require("utils/function");
const { EVENTS } = require("tabs/events");
const { getThumbnailURIForWindow } = require("utils/thumbnail");
const { getFaviconURIForLocation } = require("utils/data");
// Array of the inner instances of all the wrapped tabs.
const TABS = [];
/**
* Trait used to create tab wrappers.
*/
const TabTrait = Trait.compose(EventEmitter, {
on: Trait.required,
_emit: Trait.required,
/**
* Tab DOM element that is being wrapped.
*/
_tab: null,
/**
* Window wrapper whose tab this object represents.
*/
window: null,
constructor: function Tab(options) {
this._onReady = this._onReady.bind(this);
this.on('error', this._onError = this._onError.bind(this));
this._tab = options.tab;
let window = this.window = options.window;
// Setting event listener if was passed.
for each (let type in EVENTS) {
let listener = options[type.listener];
if (listener)
this.on(type.name, options[type.listener]);
if ('ready' != type.name) // window spreads this event.
window.tabs.on(type.name, this._onEvent.bind(this, type.name));
}
this.on(EVENTS.close.name, this.destroy.bind(this));
this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true);
if (options.isPinned)
this.pin();
// Since we will have to identify tabs by a DOM elements facade function
// is used as constructor that collects all the instances and makes sure
// that they more then one wrapper is not created per tab.
return this;
},
_onError: function _onError(error) {
if (1 <= this._listeners('error').length)
console.exception(error);
},
destroy: function destroy() {
for each (let type in EVENTS)
this._removeAllListeners(type.name);
this._browser.removeEventListener(EVENTS.ready.dom, this._onReady,
true);
},
/**
* Internal listener that emits public event 'ready' when the page of this
* tab is loaded.
*/
_onReady: function _onReady(event) {
// IFrames events will bubble so we need to ignore those.
if (event.target == this._contentDocument)
this._emit(EVENTS.ready.name, this._public);
},
/**
* Internal tab event router. Window will emit tab related events for all it's
* tabs, this listener will propagate all the events for this tab to it's
* listeners.
*/
_onEvent: function _onEvent(type, tab) {
if (tab == this._public)
this._emit(type, tab);
},
/**
* Browser DOM element where page of this tab is currently loaded.
*/
get _browser() this._window.gBrowser.getBrowserForTab(this._tab),
/**
* Window DOM element containing this tab.
*/
get _window() this._tab.ownerDocument.defaultView,
/**
* Document object of the page that is currently loaded in this tab.
*/
get _contentDocument() this._browser.contentDocument,
/**
* Window object of the page that is currently loaded in this tab.
*/
get _contentWindow() this._browser.contentWindow,
/**
* The title of the page currently loaded in the tab.
* Changing this property changes an actual title.
* @type {String}
*/
get title() this._contentDocument.title,
set title(value) this._contentDocument.title = String(value),
/**
* Location of the page currently loaded in this tab.
* Changing this property will loads page under under the specified location.
* @type {String}
*/
get url() String(this._contentDocument.location),
set url(value) this._changeLocation(String(value)),
// "TabOpen" event is fired when it's still "about:blank" is loaded in the
// changing `location` property of the `contentDocument` has no effect since
// seems to be either ignored or overridden by internal listener, there for
// location change is enqueued for the next turn of event loop.
_changeLocation: Enqueued(function(url) this._contentDocument.location = url),
/**
* URI of the favicon for the page currently loaded in this tab.
* @type {String}
*/
get favicon() getFaviconURIForLocation(this.url),
/**
* The CSS style for the tab
*/
get style() null, // TODO
/**
* The index of the tab relative to other tabs in the application window.
* Changing this property will change order of the actual position of the tab.
* @type {Number}
*/
get index()
this._window.gBrowser.getBrowserIndexForDocument(this._contentDocument),
set index(value) this._window.gBrowser.moveTabTo(this._tab, value),
/**
* Thumbnail data URI of the page currently loaded in this tab.
* @type {String}
*/
getThumbnail: function getThumbnail()
getThumbnailURIForWindow(this._contentWindow),
/**
* Whether or not tab is pinned (Is an app-tab).
* @type {Boolean}
*/
get isPinned() this._tab.pinned,
pin: function pin() {
this._window.gBrowser.pinTab(this._tab);
},
unpin: function unpin() {
this._window.gBrowser.unpinTab(this._tab);
},
/**
* Create a worker for this tab, first argument is options given to Worker.
* @type {Worker}
*/
attach: function attach(options) {
let { Worker } = require("content/worker");
options.window = this._contentWindow.wrappedJSObject;
return Worker(options);
},
/**
* Make this tab active.
* Please note: That this function is called synchronous since in E10S that
* will be the case. Besides this function is called from a constructor where
* we would like to return instance before firing a 'TabActivated' event.
*/
activate: Enqueued(function activate() {
if (this._window) // Ignore if window is closed by the time this is invoked.
this._window.gBrowser.selectedTab = this._tab;
}),
/**
* Close the tab
*/
close: function close(callback) {
if (callback)
this.on(EVENTS.close.name, callback);
this._window.gBrowser.removeTab(this._tab);
}
});
function Tab(options) {
let chromeTab = options.tab;
for each (let tab in TABS) {
if (chromeTab == tab._tab)
return tab._public;
}
let tab = TabTrait(options);
TABS.push(tab);
return tab._public;
}
Tab.prototype = TabTrait.prototype;
exports.Tab = Tab;
function Options(options) {
if ("string" === typeof options)
options = { url: options };
return validateOptions(options, {
url: { is: ["string"] },
inBackground: { is: ["undefined", "boolean"] },
isPinned: { is: ["undefined", "boolean"] },
onOpen: { is: ["undefined", "function"] },
onClose: { is: ["undefined", "function"] },
onReady: { is: ["undefined", "function"] },
onActivate: { is: ["undefined", "function"] },
onDeactivate: { is: ["undefined", "function"] }
});
}
exports.Options = Options;
exports.getTabForWindow = function (win) {
// Get browser window
let topWindow = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
if (!topWindow.gBrowser) return null;
// Get top window object, in case we are in a content iframe
let topContentWindow;
try {
topContentWindow= win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
} catch(e) {
// It may throw if win is not a valid content window
return null;
}
function getWindowID(obj) {
return obj.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.currentInnerWindowID;
}
// Search for related Tab
let topWindowId = getWindowID(topContentWindow);
for (let i = 0; i < topWindow.gBrowser.browsers.length; i++) {
let w = topWindow.gBrowser.browsers[i].contentWindow;
if (getWindowID(w) == topWindowId) {
return Tab({
window: require("windows").BrowserWindow({window:topContentWindow}),
tab: topWindow.gBrowser.tabs[i]
});
}
}
// We were unable to find the related tab!
return null;
}

View File

@ -0,0 +1,126 @@
/* vim:ts=2:sts=2:sw=2:
* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
const BaseAssert = require("./test/assert").Assert;
const { isFunction, isObject } = require("type");
/**
* Function takes test `suite` object in CommonJS format and defines all of the
* tests from that suite and nested suites in a jetpack format on a given
* `target` object. Optionally third argument `prefix` can be passed to prefix
* all the test names.
*/
function defineTestSuite(target, suite, prefix) {
prefix = prefix || "";
// If suite defines `Assert` that's what `assert` object have to be created
// from and passed to a test function (This allows custom assertion functions)
// See for details: http://wiki.commonjs.org/wiki/Unit_Testing/1.1
let Assert = suite.Assert || BaseAssert;
// Going through each item in the test suite and wrapping it into a
// Jetpack test format.
Object.keys(suite).forEach(function(key) {
// If name starts with test then it's a test function or suite.
if (key.indexOf("test") === 0) {
let test = suite[key];
// For each test function so we create a wrapper test function in a
// jetpack format and copy that to a `target` exports.
if (isFunction(test)) {
// Since names of the test may match across suites we use full object
// path as a name to avoid overriding same function.
target[prefix + key] = function(options) {
// Creating `assert` functions for this test.
let assert = Assert(options);
// If CommonJS test function expects more than one argument
// it means that test is async and second argument is a callback
// to notify that test is finished.
if (1 < test.length) {
// Letting test runner know that test is executed async and
// creating a callback function that CommonJS tests will call
// once it's done.
options.waitUntilDone();
test(assert, function() {
options.done();
});
}
// Otherwise CommonJS test is synchronous so we call it only with
// one argument.
else {
test(assert);
}
}
}
// If it's an object then it's a test suite containing test function
// and / or nested test suites. In that case we just extend prefix used
// and call this function to copy and wrap tests from nested suite.
else if (isObject(test)) {
test.Assert = test.Assert || Assert;
defineTestSuite(target, test, prefix + key + ".");
}
}
});
}
/**
* This function is a CommonJS test runner function, but since Jetpack test
* runner and test format is different from CommonJS this function shims given
* `exports` with all its tests into a Jetpack test format so that the built-in
* test runner will be able to run CommonJS test without manual changes.
*/
exports.run = function run(exports) {
// We can't leave old properties on exports since those are test in a CommonJS
// format that why we move everything to a new `suite` object.
let suite = {};
Object.keys(exports).forEach(function(key) {
suite[key] = exports[key];
delete exports[key];
});
// Now we wrap all the CommonJS tests to a Jetpack format and define
// those to a given `exports` object since that where jetpack test runner
// will look for them.
defineTestSuite(exports, suite);
};

View File

@ -0,0 +1,360 @@
/* vim:ts=2:sts=2:sw=2:
* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
const { isFunction, isNull, isObject, isString, isRegExp, isArray,
isUndefined, instanceOf, source } = require("type");
/**
* The `AssertionError` is defined in assert.
* @extends Error
* @example
* new assert.AssertionError({
* message: message,
* actual: actual,
* expected: expected
* })
*/
function AssertionError(options) {
let assertionError = Object.create(AssertionError.prototype);
if (isString(options))
options = { message: options };
if ("actual" in options)
assertionError.actual = options.actual;
if ("expected" in options)
assertionError.expected = options.expected;
if ("operator" in options)
assertionError.operator = options.operator;
assertionError.message = options.message;
assertionError.stack = new Error().stack;
return assertionError;
}
AssertionError.prototype = Object.create(Error.prototype, {
constructor: { value: AssertionError },
name: { value: "AssertionError", enumerable: true },
toString: { value: function toString() {
let value;
if (this.message) {
value = this.name + " : " + this.message;
}
else {
value = [
this.name + " : ",
source(this.expected),
this.operator,
source(this.actual)
].join(" ");
}
return value;
}}
});
exports.AssertionError = AssertionError;
function Assert(logger) {
return Object.create(Assert.prototype, { _log: { value: logger }});
}
Assert.prototype = {
fail: function fail(e) {
this._log.fail(e.message);
},
pass: function pass(message) {
this._log.pass(message);
},
error: function error(e) {
this._log.exception(e);
},
ok: function ok(value, message) {
if (!!!value) {
this.fail({
actual: value,
expected: true,
message: message,
operator: "=="
});
}
else {
this.pass(message);
}
},
/**
* The equality assertion tests shallow, coercive equality with `==`.
* @example
* assert.equal(1, 1, "one is one");
*/
equal: function equal(actual, expected, message) {
if (actual == expected) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "=="
});
}
},
/**
* The non-equality assertion tests for whether two objects are not equal
* with `!=`.
* @example
* assert.notEqual(1, 2, "one is not two");
*/
notEqual: function notEqual(actual, expected, message) {
if (actual != expected) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "!=",
});
}
},
/**
* The equivalence assertion tests a deep (with `===`) equality relation.
* @example
* assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects")
*/
deepEqual: function deepEqual(actual, expected, message) {
if (isDeepEqual(actual, expected)) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "deepEqual"
});
}
},
/**
* The non-equivalence assertion tests for any deep (with `===`) inequality.
* @example
* assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }),
* "object's inherit from different prototypes");
*/
notDeepEqual: function notDeepEqual(actual, expected, message) {
if (!isDeepEqual(actual, expected)) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "notDeepEqual"
});
}
},
/**
* The strict equality assertion tests strict equality, as determined by
* `===`.
* @example
* assert.strictEqual(null, null, "`null` is `null`")
*/
strictEqual: function strictEqual(actual, expected, message) {
if (actual === expected) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "==="
});
}
},
/**
* The strict non-equality assertion tests for strict inequality, as
* determined by `!==`.
* @example
* assert.notStrictEqual(null, undefined, "`null` is not `undefined`");
*/
notStrictEqual: function notStrictEqual(actual, expected, message) {
if (actual !== expected) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "!=="
})
}
},
/**
* The assertion whether or not given `block` throws an exception. If optional
* `Error` argument is provided and it's type of function thrown error is
* asserted to be an instance of it, if type of `Error` is string then message
* of throw exception is asserted to contain it.
* @param {Function} block
* Function that is expected to throw.
* @param {Error|RegExp} [Error]
* Error constructor that is expected to be thrown or a string that
* must be contained by a message of the thrown exception, or a RegExp
* matching a message of the thrown exception.
* @param {String} message
* Description message
*
* @examples
*
* assert.throws(function block() {
* doSomething(4)
* }, "Object is expected", "Incorrect argument is passed");
*
* assert.throws(function block() {
* Object.create(5)
* }, TypeError, "TypeError is thrown");
*/
throws: function throws(block, Error, message) {
let threw = false;
let exception = null;
// If third argument is not provided and second argument is a string it
// means that optional `Error` argument was not passed, so we shift
// arguments.
if (isString(Error) && isUndefined(message)) {
message = Error;
Error = undefined;
}
// Executing given `block`.
try {
block();
}
catch (e) {
threw = true;
exception = e;
}
// If exception was thrown and `Error` argument was not passed assert is
// passed.
if (threw && (isUndefined(Error) ||
// If passed `Error` is RegExp using it's test method to
// assert thrown exception message.
(isRegExp(Error) && Error.test(exception.message)) ||
// If passed `Error` is a constructor function testing if
// thrown exception is an instance of it.
(isFunction(Error) && instanceOf(exception, Error))))
{
this.pass(message);
}
// Otherwise we report assertion failure.
else {
let failure = {
message: message,
operator: "throws"
};
if (exception)
failure = e.actual = exception;
if (Error)
failure = e.expected = Error;
this.fail(failure);
}
}
};
exports.Assert = Assert;
function isDeepEqual(actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
}
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
else if (isDate(actual) && isDate(expected)) {
return actual.getTime() === expected.getTime();
}
// XXX specification bug: this should be specified
else if (isAtom(actual) || isAtom(actual)) {
return expected === actual;
}
// 7.3. Other pairs that do not both pass typeof value == "object",
// equivalence is determined by ==.
else if (!isObject(actual) && !isObject(expected)) {
return actual == expected;
}
// 7.4. For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical "prototype" property. Note: this
// accounts for both named and indexed properties on Arrays.
else {
return actual.prototype === expected.prototype &&
isEquivalent(actual, expected);
}
}
function isEquivalent(a, b, stack) {
return isEquivalentArray(Object.keys(a).sort(),
Object.keys(b).sort()) &&
Object.keys(a).every(function(key) {
return isDeepEqual(a[key], b[key], stack)
});
}
function isArrayEquivalent(a, b, stack) {
return isArray(a) && isArray(b) &&
a.every(function(value, index) {
return isDeepEqual(value, b[index]);
});
}

View File

@ -0,0 +1,271 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim:set ts=2 sw=2 sts=2 et filetype=javascript
* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (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 ***** */
const {Cc,Ci,Cu,components} = require("chrome");
var NetUtil = {};
Cu.import("resource://gre/modules/NetUtil.jsm", NetUtil);
NetUtil = NetUtil.NetUtil;
// NetUtil.asyncCopy() uses this buffer length, and since we call it, for best
// performance we use it, too.
const BUFFER_BYTE_LEN = 0x8000;
const PR_UINT32_MAX = 0xffffffff;
const DEFAULT_CHARSET = "UTF-8";
exports.TextReader = TextReader;
exports.TextWriter = TextWriter;
/**
* An input stream that reads text from a backing stream using a given text
* encoding.
*
* @param inputStream
* The stream is backed by this nsIInputStream. It must already be
* opened.
* @param charset
* Text in inputStream is expected to be in this character encoding. If
* not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl for
* documentation on how to determine other valid values for this.
*/
function TextReader(inputStream, charset) {
const self = this;
charset = checkCharset(charset);
let stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
stream.init(inputStream, charset, BUFFER_BYTE_LEN,
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
let manager = new StreamManager(this, stream);
/**
* Reads a string from the stream. If the stream is closed, an exception is
* thrown.
*
* @param numChars
* The number of characters to read. If not given, the remainder of
* the stream is read.
* @return The string read. If the stream is already at EOS, returns the
* empty string.
*/
this.read = function TextReader_read(numChars) {
manager.ensureOpened();
let readAll = false;
if (typeof(numChars) === "number")
numChars = Math.max(numChars, 0);
else
readAll = true;
let str = "";
let totalRead = 0;
let chunkRead = 1;
// Read in numChars or until EOS, whichever comes first. Note that the
// units here are characters, not bytes.
while (true) {
let chunk = {};
let toRead = readAll ?
PR_UINT32_MAX :
Math.min(numChars - totalRead, PR_UINT32_MAX);
if (toRead <= 0 || chunkRead <= 0)
break;
// The converter stream reads in at most BUFFER_BYTE_LEN bytes in a call
// to readString, enough to fill its byte buffer. chunkRead will be the
// number of characters encoded by the bytes in that buffer.
chunkRead = stream.readString(toRead, chunk);
str += chunk.value;
totalRead += chunkRead;
}
return str;
};
}
/**
* A buffered output stream that writes text to a backing stream using a given
* text encoding.
*
* @param outputStream
* The stream is backed by this nsIOutputStream. It must already be
* opened.
* @param charset
* Text will be written to outputStream using this character encoding.
* If not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl
* for documentation on how to determine other valid values for this.
*/
function TextWriter(outputStream, charset) {
const self = this;
charset = checkCharset(charset);
let stream = outputStream;
// Buffer outputStream if it's not already.
let ioUtils = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
if (!ioUtils.outputStreamIsBuffered(outputStream)) {
stream = Cc["@mozilla.org/network/buffered-output-stream;1"].
createInstance(Ci.nsIBufferedOutputStream);
stream.init(outputStream, BUFFER_BYTE_LEN);
}
// I'd like to use nsIConverterOutputStream. But NetUtil.asyncCopy(), which
// we use below in writeAsync(), naturally expects its sink to be an instance
// of nsIOutputStream, which nsIConverterOutputStream's only implementation is
// not. So we use uconv and manually convert all strings before writing to
// outputStream.
let uconv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
uconv.charset = charset;
let manager = new StreamManager(this, stream);
/**
* Flushes the backing stream's buffer.
*/
this.flush = function TextWriter_flush() {
manager.ensureOpened();
stream.flush();
};
/**
* Writes a string to the stream. If the stream is closed, an exception is
* thrown.
*
* @param str
* The string to write.
*/
this.write = function TextWriter_write(str) {
manager.ensureOpened();
let istream = uconv.convertToInputStream(str);
let len = istream.available();
while (len > 0) {
stream.writeFrom(istream, len);
len = istream.available();
}
istream.close();
};
/**
* Writes a string on a background thread. After the write completes, the
* backing stream's buffer is flushed, and both the stream and the backing
* stream are closed, also on the background thread. If the stream is already
* closed, an exception is thrown immediately.
*
* @param str
* The string to write.
* @param callback
* An optional function. If given, it's called as callback(error) when
* the write completes. error is an Error object or undefined if there
* was no error. Inside callback, |this| is the stream object.
*/
this.writeAsync = function TextWriter_writeAsync(str, callback) {
manager.ensureOpened();
let istream = uconv.convertToInputStream(str);
NetUtil.asyncCopy(istream, stream, function (result) {
let err = components.isSuccessCode(result) ? undefined :
new Error("An error occured while writing to the stream: " + result);
if (err)
console.error(err);
// asyncCopy() closes its output (and input) stream.
manager.opened = false;
if (typeof(callback) === "function") {
try {
callback.call(self, err);
}
catch (exc) {
console.exception(exc);
}
}
});
};
}
// This manages the lifetime of stream, a TextReader or TextWriter. It defines
// closed and close() on stream and registers an unload listener that closes
// rawStream if it's still opened. It also provides ensureOpened(), which
// throws an exception if the stream is closed.
function StreamManager(stream, rawStream) {
const self = this;
this.rawStream = rawStream;
this.opened = true;
/**
* True iff the stream is closed.
*/
stream.__defineGetter__("closed", function stream_closed() {
return !self.opened;
});
/**
* Closes both the stream and its backing stream. If the stream is already
* closed, an exception is thrown. For TextWriters, this first flushes the
* backing stream's buffer.
*/
stream.close = function stream_close() {
self.ensureOpened();
self.unload();
};
require("unload").ensure(this);
}
StreamManager.prototype = {
ensureOpened: function StreamManager_ensureOpened() {
if (!this.opened)
throw new Error("The stream is closed and cannot be used.");
},
unload: function StreamManager_unload() {
// TextWriter.writeAsync() causes rawStream to close and therefore sets
// opened to false, so check that we're still opened.
if (this.opened) {
// Calling close() on both an nsIUnicharInputStream and
// nsIBufferedOutputStream closes their backing streams. It also forces
// nsIOutputStreams to flush first.
this.rawStream.close();
this.opened = false;
}
}
};
function checkCharset(charset) {
return typeof(charset) === "string" ? charset : DEFAULT_CHARSET;
}

View File

@ -0,0 +1,73 @@
// This implementation is neither secure nor complete,
// because timer functionality should be implemented
// natively in-process by bug 568695.
/* ***** 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 ***** */
if (this.chrome) {
var callbacks = {};
exports.setTimeout = function setTimeout(cb, ms) {
var id = chrome.call("setTimeout", ms);
callbacks[id] = cb;
return id;
};
exports.clearTimeout = function clearTimeout(id) {
delete callbacks[id];
chrome.send("clearTimeout", id);
};
chrome.on("onTimeout", function(name, id) {
var cb = callbacks[id];
delete callbacks[id];
if (cb)
cb(); // yay race conditions
});
} else {
exports.register = function(addon) {
var timer = require("timer");
addon.registerCall("setTimeout", function(name, ms) {
var id = timer.setTimeout(function() {
addon.send("onTimeout", id);
}, ms);
return id;
});
addon.on("clearTimeout", function(name, id) {
timer.clearTimeout(id);
});
};
}

View File

@ -0,0 +1,139 @@
/* ***** 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>
* Drew Willcoxon <adw@mozilla.com>
* Irakli Gozalishvili <gozala@mozilla.com>
* Erik Vold <erikvvold@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 ***** */
const {Cc,Ci} = require("chrome");
var xpcom = require("xpcom");
var timerClass = Cc["@mozilla.org/timer;1"];
var nextID = 1;
var timers = {};
function TimerCallback(timerID, callback, params) {
this._callback = callback;
this._params = params;
};
TimerCallback.prototype = {
QueryInterface : xpcom.utils.generateQI([Ci.nsITimerCallback])
};
function TimeoutCallback(timerID, callback, params) {
memory.track(this);
TimerCallback.apply(this, arguments)
this._timerID = timerID;
};
TimeoutCallback.prototype = new TimerCallback();
TimeoutCallback.prototype.notify = function notifyOnTimeout(timer) {
try {
delete timers[this._timerID];
this._callback.apply(null, this._params);
} catch (e) {
console.exception(e);
}
};
function IntervalCallback(timerID, callback, params) {
memory.track(this);
TimerCallback.apply(this, arguments)
};
IntervalCallback.prototype = new TimerCallback();
IntervalCallback.prototype.notify = function notifyOnInterval() {
try {
this._callback.apply(null, this._params);
} catch (e) {
console.exception(e);
}
};
var setTimeout = exports.setTimeout = function setTimeout(callback, delay) {
return makeTimer(
Ci.nsITimer.TYPE_ONE_SHOT,
callback,
TimeoutCallback,
delay,
Array.slice(arguments, 2));
};
var clearTimeout = exports.clearTimeout = function clearTimeout(timerID) {
cancelTimer(timerID);
};
var setInterval = exports.setInterval = function setInterval(callback, delay) {
return makeTimer(
Ci.nsITimer.TYPE_REPEATING_SLACK,
callback,
IntervalCallback,
delay,
Array.slice(arguments, 2));
};
var clearInterval = exports.clearInterval = function clearInterval(timerID) {
cancelTimer(timerID);
};
function makeTimer(type, callback, callbackType, delay, params) {
var timer = timerClass.createInstance(Ci.nsITimer);
memory.track(timer, "nsITimer");
var timerID = nextID++;
timers[timerID] = timer;
timer.initWithCallback(
new callbackType(timerID, callback, params),
delay || 0,
type
);
return timerID;
}
function cancelTimer(timerID) {
var timer = timers[timerID];
if (timer) {
timer.cancel();
delete timers[timerID];
}
}
require("unload").when(
function cancelAllPendingTimers() {
var timerIDs = [timerID for (timerID in timers)];
timerIDs.forEach(function(timerID) { cancelTimer(timerID); });
});

View File

@ -0,0 +1,153 @@
/* ***** 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 ***** */
const {Cc,Ci,components} = require("chrome");
// Undo the auto-parentification of URLs done in bug 418356.
function deParentifyURL(url) {
return url ? url.split(" -> ").slice(-1)[0] : url;
}
// TODO: We might want to move this function to url or some similar
// module.
function getLocalFile(path) {
var ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
var channel = ios.newChannel(path, null, null);
var iStream = channel.open();
var siStream = Cc['@mozilla.org/scriptableinputstream;1']
.createInstance(Ci.nsIScriptableInputStream);
siStream.init(iStream);
var data = new String();
data += siStream.read(-1);
siStream.close();
iStream.close();
return data;
}
function safeGetFileLine(path, line) {
try {
var scheme = require("url").URL(path).scheme;
// TODO: There should be an easier, more accurate way to figure out
// what's the case here.
if (!(scheme == "http" || scheme == "https"))
return getLocalFile(path).split("\n")[line - 1];
} catch (e) {}
return null;
}
function errorStackToJSON(stack) {
var lines = stack.split("\n");
var frames = [];
lines.forEach(
function(line) {
if (!line)
return;
var atIndex = line.indexOf("@");
var colonIndex = line.lastIndexOf(":");
var filename = deParentifyURL(line.slice(atIndex + 1, colonIndex));
var lineNo = parseInt(line.slice(colonIndex + 1));
var funcSig = line.slice(0, atIndex);
var funcName = funcSig.slice(0, funcSig.indexOf("("));
frames.unshift({filename: filename,
funcName: funcName,
lineNo: lineNo});
});
return frames;
};
function nsIStackFramesToJSON(frame) {
var stack = [];
while (frame) {
if (frame.filename) {
var filename = deParentifyURL(frame.filename);
stack.splice(0, 0, {filename: filename,
lineNo: frame.lineNumber,
funcName: frame.name});
}
frame = frame.caller;
}
return stack;
};
var fromException = exports.fromException = function fromException(e) {
if (e instanceof Ci.nsIException)
return nsIStackFramesToJSON(e.location);
if (e.stack && e.stack.length)
return errorStackToJSON(e.stack);
if (e.fileName && typeof(e.lineNumber == "number"))
return [{filename: deParentifyURL(e.fileName),
lineNo: e.lineNumber,
funcName: null}];
return [];
};
var get = exports.get = function get() {
return nsIStackFramesToJSON(components.stack.caller);
};
var format = exports.format = function format(tbOrException) {
if (tbOrException === undefined) {
tbOrException = get();
tbOrException.splice(-1, 1);
}
var tb;
if (typeof(tbOrException) == "object" &&
tbOrException.constructor.name == "Array")
tb = tbOrException;
else
tb = fromException(tbOrException);
var lines = ["Traceback (most recent call last):"];
tb.forEach(
function(frame) {
if (!(frame.filename || frame.lineNo || frame.funcName))
return;
lines.push(' File "' + frame.filename + '", line ' +
frame.lineNo + ', in ' + frame.funcName);
var sourceLine = safeGetFileLine(frame.filename, frame.lineNo);
if (sourceLine)
lines.push(' ' + sourceLine.trim());
});
return lines.join("\n");
};

View File

@ -0,0 +1,215 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
const {
compose: _compose,
override: _override,
resolve: _resolve,
trait: _trait,
//create: _create,
required,
} = require('traits/core');
const defineProperties = Object.defineProperties,
freeze = Object.freeze,
create = Object.create;
/**
* Work around bug 608959 by defining the _create function here instead of
* importing it from traits/core. For docs on this function, see the create
* function in that module.
*
* FIXME: remove this workaround in favor of importing the function once that
* bug has been fixed.
*/
function _create(proto, trait) {
let properties = {},
keys = Object.getOwnPropertyNames(trait);
for each(let key in keys) {
let descriptor = trait[key];
if (descriptor.required &&
!Object.prototype.hasOwnProperty.call(proto, key))
throw new Error('Missing required property: ' + key);
else if (descriptor.conflict)
throw new Error('Remaining conflicting property: ' + key);
else
properties[key] = descriptor;
}
return Object.create(proto, properties);
}
/**
* Placeholder for `Trait.prototype`
*/
let TraitProto = Object.prototype;
function Get(key) this[key]
function Set(key, value) this[key] = value
/**
* Creates anonymous trait descriptor from the passed argument, unless argument
* is a trait constructor. In later case trait's already existing properties
* descriptor is returned.
* This is module's internal function and is used as a gateway to a trait's
* internal properties descriptor.
* @param {Function} $
* Composed trait's constructor.
* @returns {Object}
* Private trait property of the composition.
*/
function TraitDescriptor(object)
(
'function' == typeof object &&
(object.prototype == TraitProto || object.prototype instanceof Trait)
) ? object._trait(TraitDescriptor) : _trait(object)
function Public(instance, trait) {
let result = {},
keys = Object.getOwnPropertyNames(trait);
for each (let key in keys) {
if ('_' === key.charAt(0) && '__iterator__' !== key )
continue;
let property = trait[key],
descriptor = {
configurable: property.configurable,
enumerable: property.enumerable
};
if (property.get)
descriptor.get = property.get.bind(instance);
if (property.set)
descriptor.set = property.set.bind(instance);
if ('value' in property) {
let value = property.value;
if ('function' === typeof value) {
descriptor.value = property.value.bind(instance);
descriptor.writable = property.writable;
} else {
descriptor.get = Get.bind(instance, key);
descriptor.set = Set.bind(instance, key);
}
}
result[key] = descriptor;
}
return result;
}
/**
* This is private function that composes new trait with privates.
*/
function Composition(trait) {
function Trait() {
let self = _create({}, trait);
self._public = create(Trait.prototype, Public(self, trait));
delete self._public.constructor;
if (Object === self.constructor)
self.constructor = Trait;
else
return self.constructor.apply(self, arguments) || self._public;
return self._public;
}
defineProperties(Trait, {
prototype: { value: freeze(create(TraitProto, {
constructor: { value: constructor, writable: true }
}))}, // writable is `true` to avoid getters in custom ES5
displayName: { value: (trait.constructor || constructor).name },
compose: { value: compose, enumerable: true },
override: { value: override, enumerable: true },
resolve: { value: resolve, enumerable: true },
required: { value: required, enumerable: true },
_trait: { value: function _trait(caller)
caller === TraitDescriptor ? trait : undefined
}
});
return freeze(Trait);
}
/**
* Composes new trait out of itself and traits / property maps passed as an
* arguments. If two or more traits / property maps have properties with the
* same name, the new trait will contain a "conflict" property for that name.
* This is a commutative and associative operation, and the order of its
* arguments is not significant.
* @params {Object|Function}
* List of Traits or property maps to create traits from.
* @returns {Function}
* New trait containing the combined properties of all the traits.
*/
function compose() {
let traits = Array.slice(arguments, 0);
traits.push(this);
return Composition(_compose.apply(null, traits.map(TraitDescriptor)));
}
/**
* Composes a new trait with all of the combined properties of `this` and the
* argument traits. In contrast to `compose`, `override` immediately resolves
* all conflicts resulting from this composition by overriding the properties of
* later traits. Trait priority is from left to right. I.e. the properties of
* the leftmost trait are never overridden.
* @params {Object} trait
* @returns {Object}
*/
function override() {
let traits = Array.slice(arguments, 0);
traits.push(this);
return Composition(_override.apply(null, traits.map(TraitDescriptor)));
}
/**
* Composes new resolved trait, with all the same properties as this
* trait, except that all properties whose name is an own property of
* `resolutions` will be renamed to `resolutions[name]`. If it is
* `resolutions[name]` is `null` value is changed into a required property
* descriptor.
*/
function resolve(resolutions)
Composition(_resolve(resolutions, TraitDescriptor(this)))
/**
* Base Trait, that all the traits are composed of.
*/
const Trait = Composition({
/**
* Internal property holding public API of this instance.
*/
_public: { value: null, configurable: true, writable: true },
toString: { value: function() '[object ' + this.constructor.name + ']' }
});
TraitProto = Trait.prototype;
exports.Trait = Trait;

View File

@ -0,0 +1,337 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
// Design inspired by: http://www.traitsjs.org/
// shortcuts
const getOwnPropertyNames = Object.getOwnPropertyNames,
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
hasOwn = Object.prototype.hasOwnProperty,
_create = Object.create;
/**
* Compares two trait custom property descriptors if they are the same. If
* both are `conflict` or all the properties of descriptor are equal returned
* value will be `true`, otherwise it will be `false`.
* @param {Object} desc1
* @param {Object} desc2
*/
function areSame(desc1, desc2) {
return (desc1.conflict && desc2.conflict) || (
desc1.get === desc2.get &&
desc1.set === desc2.set &&
desc1.value === desc2.value &&
desc1.enumerable === desc2.enumerable &&
desc1.required === desc2.required &&
desc1.conflict === desc2.conflict
);
}
/**
* Converts array to an object whose own property names represent
* values of array.
* @param {String[]} names
* @returns {Object}
* @example
* Map(['foo', ...]) => { foo: true, ...}
*/
function Map(names) {
let map = {};
for each (let name in names)
map[name] = true;
return map;
}
const ERR_CONFLICT = 'Remaining conflicting property: ',
ERR_REQUIRED = 'Missing required property: ';
/**
* Constant singleton, representing placeholder for required properties.
* @type {Object}
*/
const required = { toString: function()'<Trait.required>' };
exports.required = required;
/**
* Generates custom **required** property descriptor. Descriptor contains
* non-standard property `required` that is equal to `true`.
* @param {String} name
* property name to generate descriptor for.
* @returns {Object}
* custom property descriptor
*/
function Required(name) {
function required() { throw new Error(ERR_REQUIRED + name) }
return {
get: required,
set: required,
required: true
};
}
/**
* Generates custom **conflicting** property descriptor. Descriptor contains
* non-standard property `conflict` that is equal to `true`.
* @param {String} name
* property name to generate descriptor for.
* @returns {Object}
* custom property descriptor
*/
function Conflict(name) {
function conflict() { throw new Error(ERR_CONFLICT + name) }
return {
get: conflict,
set: conflict,
conflict: true
};
}
/**
* Function generates custom properties descriptor of the `object`s own
* properties. All the inherited properties are going to be ignored.
* Properties with values matching `required` singleton will be marked as
* 'required' properties.
* @param {Object} object
* Set of properties to generate trait from.
* @returns {Object}
* Properties descriptor of all of the `object`'s own properties.
*/
function trait(properties) {
let result = {},
keys = getOwnPropertyNames(properties);
for each (let key in keys) {
let descriptor = getOwnPropertyDescriptor(properties, key);
result[key] = (required === descriptor.value) ? Required(key) : descriptor;
}
return result;
}
exports.Trait = exports.trait = trait;
/**
* Composes new trait. If two or more traits have own properties with the
* same name, the new trait will contain a 'conflict' property for that name.
* 'compose' is a commutative and associative operation, and the order of its
* arguments is not significant.
*
* @params {Object} trait
* Takes traits as an arguments
* @returns {Object}
* New trait containing the combined own properties of all the traits.
* @example
* var newTrait = compose(trait_1, trait_2, ..., trait_N);
*/
function compose(trait1, trait2) {
let traits = Array.slice(arguments, 0),
result = {};
for each (let trait in traits) {
let keys = getOwnPropertyNames(trait);
for each (let key in keys) {
let descriptor = trait[key];
// if property already exists and it's not a requirement
if (hasOwn.call(result, key) && !result[key].required) {
if (descriptor.required)
continue;
if (!areSame(descriptor, result[key]))
result[key] = Conflict(key);
} else {
result[key] = descriptor;
}
}
}
return result;
}
exports.compose = compose;
/**
* Composes new trait with the same own properties as the original trait,
* except that all property names appearing in the first argument are replaced
* by 'required' property descriptors.
* @param {String[]} keys
* Array of strings property names.
* @param {Object} trait
* A trait some properties of which should be excluded.
* @returns {Object}
* @example
* var newTrait = exclude(['name', ...], trait)
*/
function exclude(keys, trait) {
let exclusions = Map(keys),
result = {},
keys = getOwnPropertyNames(trait);
for each (let key in keys) {
if (!hasOwn.call(exclusions, key) || trait[key].required)
result[key] = trait[key];
else
result[key] = Required(key);
}
return result;
}
/**
* Composes a new trait with all of the combined properties of the argument
* traits. In contrast to `compose`, `override` immediately resolves all
* conflicts resulting from this composition by overriding the properties of
* later traits. Trait priority is from left to right. I.e. the properties of
* the leftmost trait are never overridden.
* @params {Object} trait
* @returns {Object}
* @examples
* // override is associative:
* override(t1,t2,t3)
* // is equivalent to
* override(t1, override(t2, t3))
* // or
* to override(override(t1, t2), t3)
*
* // override is not commutative:
* override(t1,t2)
* // is not equivalent to
* override(t2,t1)
*/
function override() {
let traits = Array.slice(arguments, 0),
result = {};
for each (let trait in traits) {
let keys = getOwnPropertyNames(trait);
for each(let key in keys) {
let descriptor = trait[key];
if (!hasOwn.call(result, key) || result[key].required)
result[key] = descriptor;
}
}
return result;
}
exports.override = override;
/**
* Composes a new trait with the same properties as the original trait, except
* that all properties whose name is an own property of map will be renamed to
* map[name], and a 'required' property for name will be added instead.
* @param {Object} map
* An object whose own properties serve as a mapping from old names to new
* names.
* @param {Object} trait
* A trait object
* @returns {Object}
* @example
* var newTrait = rename(map, trait);
*/
function rename(map, trait) {
let result = {},
keys = getOwnPropertyNames(trait);
for each(let key in keys) {
// must be renamed & it's not requirement
if (hasOwn.call(map, key) && !trait[key].required) {
let alias = map[key];
if (hasOwn.call(result, alias) && !result[alias].required)
result[alias] = Conflict(alias);
else
result[alias] = trait[key];
if (!hasOwn.call(result, key))
result[key] = Required(key);
} else { // must not be renamed or its a requirement
// property is not in result trait yet
if (!hasOwn.call(result, key))
result[key] = trait[key];
// property is already in resulted trait & it's not requirement
else if (!trait[key].required)
result[key] = Conflict(key);
}
}
return result;
}
/**
* Composes new resolved trait, with all the same properties as the original
* trait, except that all properties whose name is an own property of
* resolutions will be renamed to `resolutions[name]`. If it is
* `resolutions[name]` is `null` value is changed into a required property
* descriptor.
* function can be implemented as `rename(map,exclude(exclusions, trait))`
* where map is the subset of mappings from oldName to newName and exclusions
* is an array of all the keys that map to `null`.
* Note: it's important to **first** `exclude`, **then** `rename`, since
* `exclude` and rename are not associative.
* @param {Object} resolutions
* An object whose own properties serve as a mapping from old names to new
* names, or to `null` if the property should be excluded.
* @param {Object} trait
* A trait object
* @returns {Object}
* Resolved trait with the same own properties as the original trait.
*/
function resolve(resolutions, trait) {
let renames = {},
exclusions = [],
keys = getOwnPropertyNames(resolutions);
for each (let key in keys) { // pre-process renamed and excluded properties
if (resolutions[key]) // old name -> new name
renames[key] = resolutions[key];
else // name -> undefined
exclusions.push(key);
}
return rename(renames, exclude(exclusions, trait));
}
exports.resolve = resolve;
/**
* `create` is like `Object.create`, except that it ensures that:
* - an exception is thrown if 'trait' still contains required properties
* - an exception is thrown if 'trait' still contains conflicting
* properties
* @param {Object}
* prototype of the completed object
* @param {Object} trait
* trait object to be turned into a complete object
* @returns {Object}
* An object with all of the properties described by the trait.
*/
function create(proto, trait) {
let properties = {},
keys = getOwnPropertyNames(trait);
for each(let key in keys) {
let descriptor = trait[key];
if (descriptor.required && !hasOwn.call(proto, key))
throw new Error(ERR_REQUIRED + key);
else if (descriptor.conflict)
throw new Error(ERR_CONFLICT + key);
else
properties[key] = descriptor;
}
return _create(proto, properties);
}
exports.create = create;

View File

@ -0,0 +1,372 @@
/* vim:ts=2:sts=2:sw=2:
* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
/**
* Returns `true` if `value` is `undefined`.
* @examples
* var foo; isUndefined(foo); // true
* isUndefined(0); // false
*/
function isUndefined(value) {
return value === undefined;
}
exports.isUndefined = isUndefined;
/**
* Returns `true` if value is `null`.
* @examples
* isNull(null); // true
* isNull(undefined); // false
*/
function isNull(value) {
return value === null;
}
exports.isNull = isNull;
/**
* Returns `true` if value is a string.
* @examples
* isString("moe"); // true
*/
function isString(value) {
return typeof value === "string";
}
exports.isString = isString;
/**
* Returns `true` if `value` is a number.
* @examples
* isNumber(8.4 * 5); // true
*/
function isNumber(value) {
return typeof value === "number";
}
exports.isNumber = isNumber;
/**
* Returns `true` if `value` is a `RegExp`.
* @examples
* isRegExp(/moe/); // true
*/
function isRegExp(value) {
return isObject(value) && instanceOf(value, RegExp);
}
exports.isRegExp = isRegExp;
/**
* Returns true if `value` is a `Date`.
* @examples
* isDate(new Date()); // true
*/
function isDate(value) {
return isObject(value) && instanceOf(Date);
}
exports.isDate = isDate;
/**
* Returns true if object is a Function.
* @examples
* isFunction(function foo(){}) // true
*/
function isFunction(value) {
return typeof value === "function";
}
exports.isFunction = isFunction;
/**
* Returns `true` if `value` is an object (please note that `null` is considered
* to be an atom and not an object).
* @examples
* isObject({}) // true
* isObject(null) // false
*/
function isObject(value) {
return typeof value === "object" && value !== null;
}
exports.isObject = isObject;
/**
* Returns true if `value` is an Array.
* @examples
* isArray([1, 2, 3]) // true
* isArray({ 0: 'foo', length: 1 }) // false
*/
var isArray = Array.isArray || function isArray(value) {
Object.prototype.toString.call(value) === "[object Array]";
}
exports.isArray = isArray;
/**
* Returns `true` if `value` is an Arguments object.
* @examples
* (function(){ return isArguments(arguments); })(1, 2, 3); // true
* isArguments([1,2,3]); // false
*/
function isArguments(value) {
Object.prototype.toString.call(value) === "[object Arguments]";
}
exports.isArguments = isArguments;
/**
* Returns true if it is a primitive `value`. (null, undefined, number,
* boolean, string)
* @examples
* isPrimitive(3) // true
* isPrimitive('foo') // true
* isPrimitive({ bar: 3 }) // false
*/
function isPrimitive(value) {
return !isFunction(value) && !isObject(value);
}
exports.isPrimitive = isPrimitive;
/**
* Returns `true` if given `object` is flat (it is direct decedent of
* `Object.prototype` or `null`).
* @examples
* isFlat({}) // true
* isFlat(new Type()) // false
*/
function isFlat(object) {
return isObject(object) && (isNull(Object.getPrototypeOf(object)) ||
isNull(Object.getPrototypeOf(
Object.getPrototypeOf(object))));
}
exports.isFlat = isFlat;
/**
* Returns `true` if object contains no values.
*/
function isEmpty(object) {
if (isObject(object)) {
for (var key in object)
return false;
return true;
}
return false;
}
exports.isEmpty = isEmpty;
/**
* Returns `true` if `value` is an array / flat object containing only atomic
* values and other flat objects.
*/
function isJSON(value, visited) {
// Adding value to array of visited values.
(visited || (visited = [])).push(value);
// If `value` is an atom return `true` cause it's valid JSON.
return isPrimitive(value) ||
// If `value` is an array of JSON values that has not been visited
// yet.
(isArray(value) && value.every(function(element) {
return isJSON(element, visited);
})) ||
// If `value` is a plain object containing properties with a JSON
// values it's a valid JSON.
(isFlat(value) && Object.keys(value).every(function(key) {
var $ = Object.getOwnPropertyDescriptor(value, key);
// Check every proprety of a plain object to verify that
// it's neither getter nor setter, but a JSON value, that
// has not been visited yet.
return ((!isObject($.value) || !~visited.indexOf($.value)) &&
!('get' in $) && !('set' in $) &&
isJSON($.value, visited));
}));
}
exports.isJSON = function (value) {
return isJSON(value);
};
/**
* Returns if `value` is an instance of a given `Type`. This is exactly same as
* `value instanceof Type` with a difference that `Type` can be from a scope
* that has a different top level object. (Like in case where `Type` is a
* function from different iframe / jetpack module / sandbox).
*/
function instanceOf(value, Type) {
var isConstructorNameSame;
var isConstructorSourceSame;
// If `instanceof` returned `true` we know result right away.
var isInstanceOf = value instanceof Type;
// If `instanceof` returned `false` we do ducktype check since `Type` may be
// from a different sandbox. If a constructor of the `value` or a constructor
// of the value's prototype has same name and source we assume that it's an
// instance of the Type.
if (!isInstanceOf) {
isConstructorNameSame = value.constructor.name === Type.name;
isConstructorSourceSame = String(value.constructor) == String(Type);
isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
instanceOf(Object.getPrototypeOf(value), Type);
}
return isInstanceOf;
}
exports.instanceOf = instanceOf;
/**
* Function returns textual representation of a value passed to it. Function
* takes additional `indent` argument that is used for indentation. Also
* optional `limit` argument may be passed to limit amount of detail returned.
* @param {Object} value
* @param {String} [indent=" "]
* @param {Number} [limit]
*/
function source(value, indent, limit, offset, visited) {
var result;
var names;
var nestingIndex;
var isCompact = !isUndefined(limit);
indent = indent || " ";
offset = (offset || "");
result = "";
visited = visited || [];
if (isUndefined(value)) {
result += "undefined";
}
else if (isNull(value)) {
result += "null";
}
else if (isString(value)) {
result += '"' + value + '"';
}
else if (isFunction(value)) {
value = String(value).split("\n");
if (isCompact && value.length > 2) {
value = value.splice(0, 2);
value.push("...}");
}
result += value.join("\n" + offset);
}
else if (isArray(value)) {
if ((nestingIndex = (visited.indexOf(value) + 1))) {
result = "#" + nestingIndex + "#";
}
else {
visited.push(value);
if (isCompact)
value = value.slice(0, limit);
result += "[\n";
result += value.map(function(value) {
return offset + indent + source(value, indent, limit, offset + indent,
visited);
}).join(",\n");
result += isCompact && value.length > limit ?
",\n" + offset + "...]" : "\n" + offset + "]";
}
}
else if (isObject(value)) {
if ((nestingIndex = (visited.indexOf(value) + 1))) {
result = "#" + nestingIndex + "#"
}
else {
visited.push(value)
names = Object.keys(value);
result += "{ // " + value + "\n";
result += (isCompact ? names.slice(0, limit) : names).map(function(name) {
var _limit = isCompact ? limit - 1 : limit;
var descriptor = Object.getOwnPropertyDescriptor(value, name);
var result = offset + indent + "// ";
var accessor;
if (0 <= name.indexOf(" "))
name = '"' + name + '"';
if (descriptor.writable)
result += "writable ";
if (descriptor.configurable)
result += "configurable ";
if (descriptor.enumerable)
result += "enumerable ";
result += "\n";
if ("value" in descriptor) {
result += offset + indent + name + ": ";
result += source(descriptor.value, indent, _limit, indent + offset,
visited);
}
else {
if (descriptor.get) {
result += offset + indent + "get " + name + " ";
accessor = source(descriptor.get, indent, _limit, indent + offset,
visited);
result += accessor.substr(accessor.indexOf("{"));
}
if (descriptor.set) {
result += offset + indent + "set " + name + " ";
accessor = source(descriptor.set, indent, _limit, indent + offset,
visited);
result += accessor.substr(accessor.indexOf("{"));
}
}
return result;
}).join(",\n");
if (isCompact) {
if (names.length > limit && limit > 0) {
result += ",\n" + offset + indent + "//...";
}
}
else {
if (names.length)
result += ",";
result += "\n" + offset + indent + '"__proto__": ';
result += source(Object.getPrototypeOf(value), indent, 0,
offset + indent);
}
result += "\n" + offset + "}";
}
}
else {
result += String(value);
}
return result;
}
exports.source = function (value, indentation, limit) {
return source(value, indentation, limit);
};

View File

@ -0,0 +1,112 @@
/* ***** 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 ***** */
// We don't actually use chrome directly, but we do access the
// filesystem and scan it to dynamically import modules, so
// we put this here to tell the module loader to give us
// permission to require() whatever we want.
require("chrome");
var file = require("file");
var TestFinder = exports.TestFinder = function TestFinder(options) {
memory.track(this);
this.dirs = options.dirs || [];
this.filter = options.filter || function() { return true; };
this.testInProcess = options.testInProcess === false ? false : true;
this.testOutOfProcess = options.testOutOfProcess === true ? true : false;
};
TestFinder.prototype = {
_makeTest: function _makeTest(suite, name, test) {
function runTest(runner) {
console.info("executing '" + suite + "." + name + "'");
test(runner);
}
return runTest;
},
findTests: function findTests(cb) {
var self = this;
var tests = [];
var remoteSuites = [];
var filter;
if (typeof(this.filter) == "string") {
var filterRegex = new RegExp(self.filter);
filter = function(name) {
return filterRegex.test(name);
};
} else if (typeof(this.filter) == "function")
filter = this.filter;
this.dirs.forEach(
function(dir) {
var suites = [name.slice(0, -3)
for each (name in file.list(dir))
if (/^test-.*\.js$/.test(name) && filter(name))];
suites.forEach(
function(suite) {
var loader = require("parent-loader");
var url = loader.fs.resolveModule(null, suite);
var moduleInfo = packaging.getModuleInfo(url);
var module = require(suite);
if (self.testInProcess)
for (name in module)
tests.push({
testFunction: self._makeTest(suite, name, module[name]),
name: suite + "." + name
});
if (!moduleInfo.needsChrome)
remoteSuites.push(suite);
});
});
if (this.testOutOfProcess && remoteSuites.length > 0) {
var process = require("e10s").AddonProcess();
var finderHandle = process.createHandle();
finderHandle.onTestsFound = function(testsFound) {
cb(tests.concat(testsFound));
};
process.send("startMain", "find-tests", {
suites: remoteSuites,
finderHandle: finderHandle
});
} else
cb(tests);
}
};

View File

@ -0,0 +1,276 @@
/* vim:st=2:sts=2:sw=2:
* ***** 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>
* Irakli Gozalishvili <gozala@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 timer = require("timer");
exports.findAndRunTests = function findAndRunTests(options) {
var TestFinder = require("unit-test-finder").TestFinder;
var finder = new TestFinder({
dirs: options.dirs,
filter: options.filter,
testInProcess: options.testInProcess,
testOutOfProcess: options.testOutOfProcess
});
var runner = new TestRunner({fs: options.fs});
finder.findTests(
function (tests) {
runner.startMany({tests: tests,
onDone: options.onDone});
});
};
var TestRunner = exports.TestRunner = function TestRunner(options) {
if (options) {
this.fs = options.fs;
}
memory.track(this);
this.passed = 0;
this.failed = 0;
this.testRunSummary = [];
};
TestRunner.prototype = {
toString: function toString() "[object TestRunner]",
DEFAULT_PAUSE_TIMEOUT: 10000,
_logTestFailed: function _logTestFailed(why) {
this.test.errors[why]++;
if (!this.testFailureLogged) {
console.error("TEST FAILED: " + this.test.name + " (" + why + ")");
this.testFailureLogged = true;
}
},
makeSandboxedLoader: function makeSandboxedLoader(options) {
if (!this.fs)
console.error("Hey, either you didn't pass .fs when building the" +
" TestRunner, or you used 'new' when calling" +
" test.makeSandboxedLoader. Don't do that.");
if (!options)
options = {console: console};
options.fs = this.fs;
var Cuddlefish = require("cuddlefish");
if ("moduleOverrides" in options) {
var moduleOverrides = options.moduleOverrides;
delete options.moduleOverrides;
function getModuleExports(basePath, module) {
if (module in moduleOverrides)
return moduleOverrides[module];
return null;
}
options.getModuleExports = getModuleExports;
}
return new Cuddlefish.Loader(options);
},
pass: function pass(message) {
console.info("pass:", message);
this.passed++;
this.test.passed++;
},
fail: function fail(message) {
this._logTestFailed("failure");
console.error("fail:", message);
console.trace();
this.failed++;
this.test.failed++;
},
exception: function exception(e) {
this._logTestFailed("exception");
console.exception(e);
this.failed++;
this.test.failed++;
},
assertMatches: function assertMatches(string, regexp, message) {
if (regexp.test(string)) {
if (!message)
message = uneval(string) + " matches " + uneval(regexp);
this.pass(message);
} else {
var no = uneval(string) + " doesn't match " + uneval(regexp);
if (!message)
message = no;
else
message = message + " (" + no + ")";
this.fail(message);
}
},
assertRaises: function assertRaises(func, predicate, message) {
try {
func();
if (message)
this.fail(message + " (no exception thrown)");
else
this.fail("function failed to throw exception");
} catch (e) {
var errorMessage;
if (typeof(e) == "string")
errorMessage = e;
else
errorMessage = e.message;
if (typeof(predicate) == "string")
this.assertEqual(errorMessage, predicate, message);
else
this.assertMatches(errorMessage, predicate, message);
}
},
assert: function assert(a, message) {
if (!a) {
if (!message)
message = "assertion failed, value is " + a;
this.fail(message);
} else
this.pass(message || "assertion successful");
},
assertNotEqual: function assertNotEqual(a, b, message) {
if (a != b) {
if (!message)
message = "a != b != " + uneval(a);
this.pass(message);
} else {
var equality = uneval(a) + " == " + uneval(b);
if (!message)
message = equality;
else
message += " (" + equality + ")";
this.fail(message);
}
},
assertEqual: function assertEqual(a, b, message) {
if (a == b) {
if (!message)
message = "a == b == " + uneval(a);
this.pass(message);
} else {
var inequality = uneval(a) + " != " + uneval(b);
if (!message)
message = inequality;
else
message += " (" + inequality + ")";
this.fail(message);
}
},
done: function done() {
if (!this.isDone) {
this.isDone = true;
if (this.waitTimeout !== null) {
timer.clearTimeout(this.waitTimeout);
this.waitTimeout = null;
}
if (this.test.passed == 0 && this.test.failed == 0) {
this._logTestFailed("empty test");
this.failed++;
this.test.failed++;
}
this.testRunSummary.push({
name: this.test.name,
passed: this.test.passed,
failed: this.test.failed,
errors: [error for (error in this.test.errors)].join(", ")
});
if (this.onDone !== null) {
var onDone = this.onDone;
var self = this;
this.onDone = null;
timer.setTimeout(function() { onDone(self); }, 0);
}
}
},
waitUntilDone: function waitUntilDone(ms) {
if (ms === undefined)
ms = this.DEFAULT_PAUSE_TIMEOUT;
var self = this;
function tiredOfWaiting() {
self._logTestFailed("timed out");
self.failed++;
self.test.failed++;
self.done();
}
this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms);
},
startMany: function startMany(options) {
function runNextTest(self) {
var test = options.tests.shift();
if (test)
self.start({test: test, onDone: runNextTest});
else
options.onDone(self);
}
runNextTest(this);
},
start: function start(options) {
this.test = options.test;
this.test.passed = 0;
this.test.failed = 0;
this.test.errors = {};
this.isDone = false;
this.onDone = options.onDone;
this.waitTimeout = null;
this.testFailureLogged = false;
try {
this.test.testFunction(this);
} catch (e) {
this.exception(e);
}
if (this.waitTimeout === null)
this.done();
}
};

View File

@ -0,0 +1,49 @@
// Parts of this module were taken from narwhal:
//
// http://narwhaljs.org
var observers = [];
var unloaders = [];
var when = exports.when = function when(observer) {
observers.unshift(observer);
};
var send = exports.send = function send(reason) {
observers.forEach(function (observer) {
observer(reason);
});
};
var addMethod = exports.addMethod = function addMethod(obj, unloader) {
var called = false;
function unloadWrapper(reason) {
if (!called) {
called = true;
var index = unloaders.indexOf(unloadWrapper);
if (index == -1)
throw new Error("internal error: unloader not found");
unloaders.splice(index, 1);
unloader.apply(obj, [reason]);
}
};
unloaders.push(unloadWrapper);
obj.unload = unloadWrapper;
};
var ensure = exports.ensure = function ensure(obj) {
if (!("unload" in obj))
throw new Error("object has no 'unload' property");
addMethod(obj, obj.unload);
};
when(
function(reason) {
unloaders.slice().forEach(
function(unloadWrapper) {
unloadWrapper(reason);
});
});

View File

@ -0,0 +1,91 @@
/* ***** 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 Add-on SDK.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com>
* Myk Melez <myk@mozilla.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 ***** */
if (this.chrome) {
exports.toFilename = function(spec) chrome.call("url:toFilename", spec);
exports.fromFilename = function(spec) chrome.call("url:fromFilename", spec);
let URL = exports.URL = function URL(spec, base) {
// We have to force the `spec` and `base` arguments, if defined, to be
// strings before sending them across the process boundary, since the
// boundary will drop their custom toString() methods if they are URL
// objects, and the other side depends on being able to convert them to
// strings.
let result = chrome.call("url:URL",
typeof spec == "undefined" ? spec : "" + spec,
typeof base == "undefined" ? base : "" + base);
let { scheme, userPass, host, port, path } = result.url;
return Object.create(URL.prototype, {
scheme: { value: scheme, enumerable: true },
userPass: { value: userPass, enumerable: true },
host: { value: host, enumerable: true },
port: { value: port, enumerable: true },
path: { value: path, enumerable: true },
toString: { value: function() chrome.call("url:toString", result.handle) }
});
}
}
else {
const { URL, toFilename, fromFilename } = require("url");
exports.register = function register(addon) {
addon.registerCall("url:toFilename", function(name, spec) toFilename(spec));
addon.registerCall("url:fromFilename",
function(name, spec) fromFilename(spec));
addon.registerCall("url:URL", function(name, spec, base) {
let url = URL(spec, base);
// We create a handle to give the addon process access to the toString()
// method, which cannot traverse the process boundary but also can't be
// duplicated in the addon process because it accesses private information
// (the spec of the URL). The handle doesn't need to be rooted, as it
// can be GCed as soon as all references to it are removed.
let handle = addon.createHandle();
handle.isRooted = false;
handle.url = url;
return { url: url, handle: handle };
});
addon.registerCall("url:toString",
function(name, handle) handle.url.toString());
}
}

View File

@ -0,0 +1,121 @@
/* ***** 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 ***** */
const {Cc,Ci,Cr} = require("chrome");
var ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
var resProt = ios.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
function newURI(uriStr, base) {
try {
let baseURI = base ? ios.newURI(base, null, null) : null;
return ios.newURI(uriStr, null, baseURI);
}
catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
throw new Error("malformed URI: " + uriStr);
}
catch (e if (e.result == Cr.NS_ERROR_FAILURE ||
e.result == Cr.NS_ERROR_ILLEGAL_VALUE)) {
throw new Error("invalid URI: " + uriStr);
}
}
function resolveResourceURI(uri) {
var resolved;
try {
resolved = resProt.resolveURI(uri);
} catch (e if e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
throw new Error("resource does not exist: " + uri.spec);
};
return resolved;
}
let fromFilename = exports.fromFilename = function fromFilename(path) {
var file = Cc['@mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
file.initWithPath(path);
return ios.newFileURI(file).spec;
};
let toFilename = exports.toFilename = function toFilename(url) {
var uri = newURI(url);
if (uri.scheme == "resource")
uri = newURI(resolveResourceURI(uri));
if (uri.scheme == "chrome") {
var channel = ios.newChannelFromURI(uri);
try {
channel = channel.QueryInterface(Ci.nsIFileChannel);
return channel.file.path;
} catch (e if e.result == Cr.NS_NOINTERFACE) {
throw new Error("chrome url isn't on filesystem: " + url);
}
}
if (uri.scheme == "file") {
var file = uri.QueryInterface(Ci.nsIFileURL).file;
return file.path;
}
throw new Error("cannot map to filename: " + url);
};
function URL(url, base) {
var uri = newURI(url, base);
var userPass = null;
try {
userPass = uri.userPass ? uri.userPass : null;
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
var host = null;
try {
host = uri.host;
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
var port = null;
try {
port = uri.port == -1 ? null : uri.port;
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
this.__defineGetter__("scheme", function() uri.scheme);
this.__defineGetter__("userPass", function() userPass);
this.__defineGetter__("host", function() host);
this.__defineGetter__("port", function() port);
this.__defineGetter__("path", function() uri.path);
this.toString = function URL_toString() uri.spec;
};
exports.URL = require("api-utils").publicConstructor(URL);

View File

@ -0,0 +1,104 @@
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
const { Cc, Ci, Cu } = require("chrome");
const IOService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
const AppShellService = Cc["@mozilla.org/appshell/appShellService;1"].
getService(Ci.nsIAppShellService);
Cu.import("resource://gre/modules/NetUtil.jsm", this);
const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
getService(Ci.nsIFaviconService);
const PNG_B64 = "data:image/png;base64,";
const DEF_FAVICON_URI = "chrome://mozapps/skin/places/defaultFavicon.png";
let DEF_FAVICON = null;
/**
* Takes URI of the page and returns associated favicon URI.
* If page under passed uri has no favicon then base64 encoded data URI of
* default faveicon is returned.
* @param {String} uri
* @returns {String}
*/
exports.getFaviconURIForLocation = function getFaviconURIForLocation(uri) {
let pageURI = NetUtil.newURI(uri);
try {
return FaviconService.getFaviconDataAsDataURL(
FaviconService.getFaviconForPage(pageURI));
}
catch(e) {
if (!DEF_FAVICON) {
DEF_FAVICON = PNG_B64 +
base64Encode(getChromeURIContent(DEF_FAVICON_URI));
}
return DEF_FAVICON;
}
}
/**
* Takes chrome URI and returns content under that URI.
* @param {String} chromeURI
* @returns {String}
*/
function getChromeURIContent(chromeURI) {
let channel = IOService.newChannel(chromeURI, null, null);
let input = channel.open();
let stream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
stream.setInputStream(input);
let content = stream.readBytes(input.available());
stream.close();
input.close();
return content;
}
exports.getChromeURIContent = getChromeURIContent;
/**
* Creates a base-64 encoded ASCII string from a string of binary data.
*/
function base64Encode(data) AppShellService.hiddenDOMWindow.btoa(String(data));
exports.base64Encode = base64Encode;
/**
* Decodes a string of data which has been encoded using base-64 encoding.
*/
function base64Decode(data) AppShellService.hiddenDOMWindow.atob(String(data));
exports.base64Decode = base64Decode;

View File

@ -0,0 +1,64 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
var { setTimeout } = require("timer");
/**
* Takes a function and returns a wrapped one instead, calling which will call
* original function in the next turn of event loop. This is basically utility
* to do `setTimeout(function() { ... }, 0)`, with a difference that returned
* function is reused, instead of creating a new one each time. This also allows
* to use this functions as event listeners.
*/
function Enqueued(callee) {
return function enqueued()
setTimeout(invoke, 0, callee, arguments, this);
}
exports.Enqueued = Enqueued;
/**
* Invokes `callee` by passing `params` as an arguments and `self` as `this`
* pseudo-variable. Returns value that is returned by a callee.
* @param {Function} callee
* Function to invoke.
* @param {Array} params
* Arguments to invoke function with.
* @param {Object} self
* Object to be passed as a `this` pseudo variable.
*/
function invoke(callee, params, self) callee.apply(self, params);
exports.invoke = invoke;

View File

@ -0,0 +1,90 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@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 ***** */
"use strict";
const { EventEmitter } = require('events');
const unload = require("unload");
const Registry = EventEmitter.compose({
_registry: null,
_constructor: null,
constructor: function Registry(constructor) {
this._registry = [];
this._constructor = constructor;
this.on('error', this._onError = this._onError.bind(this));
unload.when(this._destructor.bind(this));
},
_destructor: function _destructor() {
let _registry = this._registry.slice(0);
for each (instance in _registry)
this._emit('remove', instance);
this._registry.splice(0);
},
_onError: function _onError(e) {
if (!this._listeners('error').length)
console.error(e);
},
has: function has(instance) {
let _registry = this._registry;
return (
(0 <= _registry.indexOf(instance)) ||
(instance && instance._public && 0 <= _registry.indexOf(instance._public))
);
},
add: function add(instance) {
let { _constructor, _registry } = this;
if (!(instance instanceof _constructor))
instance = new _constructor(instance);
if (0 > _registry.indexOf(instance)) {
_registry.push(instance);
this._emit('add', instance);
return instance;
}
},
remove: function remove(instance) {
let _registry = this._registry;
let index = _registry.indexOf(instance)
if (0 <= index) {
this._emit('remove', instance);
_registry.splice(index, 1);
}
}
});
exports.Registry = Registry;

View File

@ -0,0 +1,76 @@
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
const { Cc, Ci, Cu } = require("chrome");
const AppShellService = Cc["@mozilla.org/appshell/appShellService;1"].
getService(Ci.nsIAppShellService);
const NS = "http://www.w3.org/1999/xhtml";
const COLOR = "rgb(255,255,255)";
/**
* Creates canvas element with a thumbnail of the passed window.
* @param {Window} window
* @returns {Element}
*/
function getThumbnailCanvasForWindow(window) {
let aspectRatio = 0.5625; // 16:9
let thumbnail = AppShellService.hiddenDOMWindow.document
.createElementNS(NS, "canvas");
thumbnail.mozOpaque = true;
thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
thumbnail.height = Math.round(thumbnail.width * aspectRatio);
let ctx = thumbnail.getContext("2d");
let snippetWidth = window.innerWidth * .6;
let scale = thumbnail.width / snippetWidth;
ctx.scale(scale, scale);
ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth,
snippetWidth * aspectRatio, COLOR);
return thumbnail;
}
exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow;
/**
* Creates Base64 encoded data URI of the thumbnail for the passed window.
* @param {Window} window
* @returns {String}
*/
exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) {
return getThumbnailCanvasForWindow(window).toDataURL()
};

View File

@ -0,0 +1,176 @@
/* ***** 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 ***** */
const {Cc,Ci} = require("chrome");
var errors = require("errors");
var gWindowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Ci.nsIWindowWatcher);
const { EventEmitter } = require('events'),
{ Trait } = require('traits');
/**
* An iterator for XUL windows currently in the application.
*
* @return A generator that yields XUL windows exposing the
* nsIDOMWindow interface.
*/
var windowIterator = exports.windowIterator = function windowIterator() {
let winEnum = gWindowWatcher.getWindowEnumerator();
while (winEnum.hasMoreElements())
yield winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
};
var WindowTracker = exports.WindowTracker = function WindowTracker(delegate) {
this.delegate = delegate;
this._loadingWindows = [];
for (window in windowIterator())
this._regWindow(window);
gWindowWatcher.registerNotification(this);
require("unload").ensure(this);
};
WindowTracker.prototype = {
_regLoadingWindow: function _regLoadingWindow(window) {
this._loadingWindows.push(window);
window.addEventListener("load", this, true);
},
_unregLoadingWindow: function _unregLoadingWindow(window) {
var index = this._loadingWindows.indexOf(window);
if (index != -1) {
this._loadingWindows.splice(index, 1);
window.removeEventListener("load", this, true);
}
},
_regWindow: function _regWindow(window) {
if (window.document.readyState == "complete") {
this._unregLoadingWindow(window);
this.delegate.onTrack(window);
} else
this._regLoadingWindow(window);
},
_unregWindow: function _unregWindow(window) {
if (window.document.readyState == "complete")
this.delegate.onUntrack(window);
else
this._unregLoadingWindow(window);
},
unload: function unload() {
gWindowWatcher.unregisterNotification(this);
for (window in windowIterator())
this._unregWindow(window);
},
handleEvent: function handleEvent(event) {
if (event.type == "load" && event.target) {
var window = event.target.defaultView;
if (window)
this._regWindow(window);
}
},
observe: function observe(subject, topic, data) {
var window = subject.QueryInterface(Ci.nsIDOMWindow);
if (topic == "domwindowopened")
this._regWindow(window);
else
this._unregWindow(window);
}
};
errors.catchAndLogProps(WindowTracker.prototype, ["handleEvent", "observe"]);
const WindowTrackerTrait = Trait.compose({
_onTrack: Trait.required,
_onUntrack: Trait.required,
constructor: function WindowTrackerTrait() {
new WindowTracker({
onTrack: this._onTrack.bind(this),
onUntrack: this._onUntrack.bind(this)
});
}
});
exports.WindowTrackerTrait = WindowTrackerTrait;
var gDocsToClose = [];
function onDocUnload(event) {
var index = gDocsToClose.indexOf(event.target);
if (index == -1)
throw new Error("internal error: unloading document not found");
var document = gDocsToClose.splice(index, 1)[0];
// Just in case, let's remove the event listener too.
document.defaultView.removeEventListener("unload", onDocUnload, false);
}
onDocUnload = require("errors").catchAndLog(onDocUnload);
exports.closeOnUnload = function closeOnUnload(window) {
window.addEventListener("unload", onDocUnload, false);
gDocsToClose.push(window.document);
};
exports.__defineGetter__("activeWindow", function() {
return Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator)
.getMostRecentWindow(null);
});
exports.__defineSetter__("activeWindow", function(window) {
try {
window.focus();
}
catch (e) { }
});
exports.__defineGetter__("activeBrowserWindow", function() {
return Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
});
require("unload").when(
function() {
gDocsToClose.slice().forEach(
function(doc) { doc.defaultView.close(); });
});

View File

@ -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 Jetpack.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict';
const { Trait } = require('traits');
const WindowDom = Trait.compose({
_window: Trait.required,
get title() {
let window = this._window;
return window && window.document ? window.document.title : null
},
close: function close() {
let window = this._window;
if (window) window.close();
return this._public;
},
activate: function activate() {
let window = this._window;
if (window) window.focus();
return this._public;
}
});
exports.WindowDom = WindowDom;

View File

@ -0,0 +1,151 @@
/* ***** 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) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
const { Cc, Ci } = require('chrome'),
{ setTimeout } = require("timer"),
{ Trait } = require('traits'),
WM = Cc['@mozilla.org/appshell/window-mediator;1'].
getService(Ci.nsIWindowMediator),
URI_BROWSER = 'chrome://browser/content/browser.xul',
NAME = '_blank',
FEATURES = 'chrome,all,dialog=no',
PARAMS = [ URI_BROWSER, NAME, FEATURES ],
ON_LOAD = 'load',
ON_UNLOAD = 'unload',
STATE_LOADED = 'complete',
BROWSER = 'navigator:browser';
/**
* Trait provides private `_window` property and requires `_onLoad` property
* that will be called when `_window` is loaded. If `_window` property value
* is changed with already loaded window `_onLoad` still will be called.
*/
const WindowLoader = Trait.compose({
/**
* Internal listener that is called when window is loaded.
* Please keep in mind that this trait will not handle exceptions that may
* be thrown by this method so method itself should take care of
* handling them.
* @param {nsIWindow} window
*/
_onLoad: Trait.required,
_tabOptions: Trait.required,
/**
* Internal listener that is called when `_window`'s DOM 'unload' event
* is dispatched. Please note that this trait will not handle exceptions that
* may be thrown by this method so method itself should take care of
* handling them.
*/
_onUnload: Trait.required,
_load: function _load() {
if (this.__window) return;
let params = PARAMS.slice()
params.push(this._tabOptions.map(function(options) options.url).join("|"))
let browser = WM.getMostRecentWindow(BROWSER);
this._window = browser.openDialog.apply(browser, params);
},
/**
* Private window who's load event is being tracked. Once window is loaded
* `_onLoad` is called.
* @type {nsIWindow}
*/
get _window() this.__window,
set _window(window) {
let _window = this.__window;
if (!window) window = null;
if (window == _window) return;
if (_window) {
_window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
_window.removeEventListener(ON_LOAD, this.__loadListener, false);
}
if (!window) return;
window.addEventListener(
ON_UNLOAD,
this.__unloadListener ||
(this.__unloadListener = this._unloadListener.bind(this))
,
false
);
this.__window = window;
// If window is not loaded yet setting up a listener.
if (STATE_LOADED != window.document.readyState) {
window.addEventListener(
ON_LOAD,
this.__loadListener ||
(this.__loadListener = this._loadListener.bind(this))
,
false
);
}
else { // If window is loaded calling listener next turn of event loop.
this._onLoad(window)
}
return window;
},
__window: null,
/**
* Internal method used for listening 'load' event on the `_window`.
* Method takes care of removing itself from 'load' event listeners once
* event is being handled.
*/
_loadListener: function _loadListener(event) {
let window = this._window;
if (!event.target || event.target.defaultView != window) return;
window.removeEventListener(ON_LOAD, this.__loadListener, false);
this._onLoad(window);
},
__loadListener: null,
/**
* Internal method used for listening 'unload' event on the `_window`.
* Method takes care of removing itself from 'unload' event listeners once
* event is being handled.
*/
_unloadListener: function _unloadListener(event) {
let window = this._window;
if (!event.target
|| event.target.defaultView != window
|| STATE_LOADED != window.document.readyState
) return;
window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
this._onUnload(window);
},
__unloadListener: null
});
exports.WindowLoader = WindowLoader;

View File

@ -0,0 +1,202 @@
/* ***** 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
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Irakli Gozalishvili <gozala@mozilla.com> (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 strict";
const { Trait } = require("traits");
const { List } = require("list");
const { Tab, Options } = require("tabs/tab");
const { EventEmitter } = require("events");
const { EVENTS } = require("tabs/events");
const TAB_BROWSER = "tabbrowser";
/**
* This is a trait that is used in composition of window wrapper. Trait tracks
* tab related events of the wrapped window in order to keep truck of open
* tabs and maintain their wrappers. Every new tab is gets wrapped and jetpack
* type event is emitted.
*/
const WindowTabTracker = Trait.compose({
/**
* Chrome window whose tabs are tracked.
*/
_window: Trait.required,
/**
* Function used to emit events.
*/
_emit: Trait.required,
_tabOptions: Trait.required,
/**
* Function to add event listeners.
*/
on: Trait.required,
/**
* Live array of the window tabContainers.
*/
get _tabContainers()
Array.slice(this._window.document.getElementsByTagName(TAB_BROWSER))
.map(function(tabBrowser) tabBrowser.tabContainer),
/**
* Initializes tab tracker for a browser window.
*/
_initWindowTabTracker: function _initWindowTabTracker() {
this.tabs;
// Some XULRunner apps may have more than one tab browser.
for each (let tabContainer in this._tabContainers) {
let tabs = Array.slice(tabContainer.children);
// Emulating 'open' events for all open tabs.
for each (let tab in tabs)
this._onTabEvent(EVENTS.open, { target: tab });
this._onTabEvent(EVENTS.activate,
{ target: this._window.gBrowser.selectedTab });
// Setting event listeners to track tab events.
for each (let type in EVENTS) {
if (!type.dom) continue;
tabContainer.addEventListener(type.dom,
this._onTabEvent.bind(this, type),
false);
}
}
},
_destroyWindowTabTracker: function _destroyWindowTabTracker() {
for each (let tab in this.tabs)
this._emitEvent(EVENTS.close, tab);
this._tabs._clear();
},
/**
* Tab event router. Function is called on every tab related DOM event.
* For each event jetpack style event is emitted with a wrapped tab as
* an argument.
* @param {String} type
* Event type.
* @param {Event} event
*/
_onTabEvent: function _onTabEvent(type, event) {
let options = this._tabOptions.shift() || {};
options.tab = event.target;
options.window = this._public;
var tab = Tab(options);
// Piping 'ready' events from open tabs to the window listeners.
if (type == EVENTS.open)
tab.on(EVENTS.ready.name, this._emitEvent.bind(this, EVENTS.ready));
this._emitEvent(type, tab);
},
_emitEvent: function _emitEvent(type, tab) {
// Notifies combined tab list that tab was added / removed.
tabs._emit(type.name, tab);
// Notifies contained tab list that window was added / removed.
this._tabs._emit(type.name, tab);
}
});
exports.WindowTabTracker = WindowTabTracker;
/**
* This trait is used to create live representation of open tab lists. Each
* window wrapper's tab list is represented by an object created from this
* trait. It is also used to represent list of all the open windows. Trait is
* composed out of `EventEmitter` in order to emit 'TabOpen', 'TabClose' events.
* **Please note** that objects created by this trait can't be exposed outside
* instead you should expose it's `_public` property, see comments in
* constructor for details.
*/
const TabList = List.resolve({ constructor: "_init" }).compose(
// This is ugly, but necessary. Will be removed by #596248
EventEmitter.resolve({ toString: null }),
Trait.compose({
on: Trait.required,
_emit: Trait.required,
constructor: function TabList(options) {
this._window = options.window;
this.on('error', this._onError = this._onError.bind(this));
// Add new items to the list
this.on(EVENTS.open.name, this._add.bind(this));
// Remove closed items from the list
this.on(EVENTS.close.name, this._remove.bind(this));
// Emit events for closed items
this.on(EVENTS.activate.name, this._onActivate.bind(this));
// Initialize list.
this._init();
// This list is not going to emit any events, object holding this list
// will do it instead, to make that possible we return a private API.
return this;
},
_onActivate: function _onActivate(value) {
this._emit(EVENTS.deactivate.name, this._activeTab);
this._activeTab = value;
},
_onError: function _onError(error) {
if (1 <= this._listeners('error').length)
console.exception(error);
},
get activeTab() this._activeTab,
_activeTab: null,
open: function open(options) {
options = Options(options);
this._window._tabOptions.push(options);
let tab = this._window._window.gBrowser.addTab(options.url);
if (!options.inBackground)
this._window._window.gBrowser.selectedTab = tab;
}
// This is ugly, but necessary. Will be removed by #596248
}).resolve({ toString: null })
);
/**
* Combined list of all open tabs on all the windows.
* type {TabList}
*/
var tabs = TabList({ window: null });
exports.tabs = tabs._public;
/**
* Trait is a part of composition that represents window wrapper. This trait is
* composed out of `WindowTabTracker` that allows it to keep track of open tabs
* on the window being wrapped.
*/
const WindowTabs = Trait.compose(
WindowTabTracker,
Trait.compose({
_window: Trait.required,
/**
* List of tabs
*/
get tabs() (this._tabs || (this._tabs = TabList({ window: this })))._public,
_tabs: null,
})
);
exports.WindowTabs = WindowTabs;

View File

@ -0,0 +1,179 @@
/* ***** 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 ***** */
const {Cc,Ci} = require("chrome");
// ## Implementation Notes ##
//
// Making `XMLHttpRequest` objects available to Jetpack code involves a
// few key principles universal to all low-level module implementations:
//
// * **Unloadability**. A Jetpack-based extension using this module can be
// asked to unload itself at any time, e.g. because the user decides to
// uninstall or disable the extension. This means we need to keep track of
// all in-progress reqests and abort them on unload.
//
// * **Developer-Ergonomic Tracebacks**. Whenever an exception is raised
// by a Jetpack-based extension, we want it to be logged in a
// place that is specific to that extension--so that a developer
// can distinguish it from an error on a web page or in another
// extension, for instance. We also want it to be logged with a
// full stack traceback, which the Mozilla platform doesn't usually
// do.
//
// Because of this, we don't actually want to give the Mozilla
// platform's "real" XHR implementation to clients, but instead provide
// a simple wrapper that trivially delegates to the implementation in
// all cases except where callbacks are involved: whenever Mozilla
// platform code calls into the extension, such as during the XHR's
// `onreadystatechange` callback, we want to wrap the client's callback
// in a try-catch clause that traps any exceptions raised by the
// callback and logs them via console.exception() instead of allowing
// them to propagate back into Mozilla platform code.
// This is a private list of all active requests, so we know what to
// abort if we're asked to unload.
var requests = [];
// Events on XHRs that we should listen for, so we know when to remove
// a request from our private list.
const TERMINATE_EVENTS = ["load", "error", "abort"];
// Read-only properties of XMLHttpRequest objects that we want to
// directly delegate to.
const READ_ONLY_PROPS = ["readyState", "responseText", "responseXML",
"status", "statusText"];
// Methods of XMLHttpRequest that we want to directly delegate to.
const DELEGATED_METHODS = ["abort", "getAllResponseHeaders",
"getResponseHeader", "overrideMimeType",
"send", "sendAsBinary", "setRequestHeader",
"open"];
var getRequestCount = exports.getRequestCount = function getRequestCount() {
return requests.length;
};
var XMLHttpRequest = exports.XMLHttpRequest = function XMLHttpRequest() {
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
// For the sake of simplicity, don't tie this request to any UI.
req.mozBackgroundRequest = true;
memory.track(req, "XMLHttpRequest");
this._req = req;
this._orsc = null;
requests.push(this);
var self = this;
this._boundCleanup = function _boundCleanup() {
self._cleanup();
};
TERMINATE_EVENTS.forEach(
function(name) {
self._req.addEventListener(name, self._boundCleanup, false);
});
};
XMLHttpRequest.prototype = {
_cleanup: function _cleanup() {
this.onreadystatechange = null;
var index = requests.indexOf(this);
if (index != -1) {
var self = this;
TERMINATE_EVENTS.forEach(
function(name) {
self._req.removeEventListener(name, self._boundCleanup, false);
});
requests.splice(index, 1);
}
},
_unload: function _unload() {
this._req.abort();
this._cleanup();
},
addEventListener: function addEventListener() {
throw new Error("not implemented");
},
removeEventListener: function removeEventListener() {
throw new Error("not implemented");
},
set upload(newValue) {
throw new Error("not implemented");
},
get onreadystatechange() {
return this._orsc;
},
set onreadystatechange(cb) {
this._orsc = cb;
if (cb) {
var self = this;
this._req.onreadystatechange = function() {
try {
self._orsc.apply(self, arguments);
} catch (e) {
console.exception(e);
}
};
} else
this._req.onreadystatechange = null;
}
};
READ_ONLY_PROPS.forEach(
function(name) {
XMLHttpRequest.prototype.__defineGetter__(
name,
function() {
return this._req[name];
});
});
DELEGATED_METHODS.forEach(
function(name) {
XMLHttpRequest.prototype[name] = function() {
return this._req[name].apply(this._req, arguments);
};
});
require("unload").when(
function() {
requests.slice().forEach(function(request) { request._unload(); });
});

View File

@ -0,0 +1,217 @@
/* ***** 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>
* Drew Willcoxon <adw@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 ***** */
const {Cc,Ci,Cm,Cr,Cu} = require("chrome");
var jsm = {};
Cu.import("resource://gre/modules/XPCOMUtils.jsm", jsm);
var utils = exports.utils = jsm.XPCOMUtils;
Cm.QueryInterface(Ci.nsIComponentRegistrar);
var factories = [];
function Factory(options) {
memory.track(this);
this.wrappedJSObject = this;
this.create = options.create;
this.uuid = options.uuid;
this.name = options.name;
this.contractID = options.contractID;
Cm.registerFactory(this.uuid,
this.name,
this.contractID,
this);
var self = this;
factories.push(this);
}
Factory.prototype = {
createInstance: function(outer, iid) {
try {
if (outer)
throw Cr.NS_ERROR_NO_AGGREGATION;
return (new this.create()).QueryInterface(iid);
} catch (e) {
console.exception(e);
if (e instanceof Ci.nsIException)
throw e;
else
throw Cr.NS_ERROR_FAILURE;
}
},
unregister: function() {
var index = factories.indexOf(this);
if (index == -1)
throw new Error("factory already unregistered");
var self = this;
factories.splice(index, 1);
Cm.unregisterFactory(this.uuid, this);
},
QueryInterface: utils.generateQI([Ci.nsIFactory])
};
var makeUuid = exports.makeUuid = function makeUuid() {
var uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
var uuid = uuidGenerator.generateUUID();
return uuid;
};
var autoRegister = exports.autoRegister = function autoRegister(path) {
// TODO: This assumes that the url points to a directory
// that contains subdirectories corresponding to OS/ABI and then
// further subdirectories corresponding to Gecko platform version.
// we should probably either behave intelligently here or allow
// the caller to pass-in more options if e.g. there aren't
// Gecko-specific binaries for a component (which will be the case
// if only frozen interfaces are used).
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
var runtime = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULRuntime);
var osDirName = runtime.OS + "_" + runtime.XPCOMABI;
var platformVersion = appInfo.platformVersion.substring(0, 5);
var file = Cc['@mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
file.initWithPath(path);
file.append(osDirName);
file.append(platformVersion);
if (!(file.exists() && file.isDirectory()))
throw new Error("component not available for OS/ABI " +
osDirName + " and platform " + platformVersion);
Cm.QueryInterface(Ci.nsIComponentRegistrar);
Cm.autoRegister(file);
};
var register = exports.register = function register(options) {
options = {__proto__: options};
if (!options.uuid)
options.uuid = makeUuid();
return new Factory(options);
};
var getClass = exports.getClass = function getClass(contractID, iid) {
if (!iid)
iid = Ci.nsISupports;
return Cm.getClassObjectByContractID(contractID, iid);
};
/**
* Returns an Error instance that is a more descriptive version of the raw XPCOM
* errOrResult. opts is used by some exceptions to include helpful info in
* their messages such as a filename, and as such its properties depend on the
* type of exception being thrown. opts need not be defined for errors that
* don't use it. See below for a list of supported options.
*
* If there is no friendly version of errOrResult, then if it's an nsIException,
* an Error whose message is errOrResult's message is returned; if it's a
* result, an Error with a simple numeric message is returned; and if it's an
* Error, it itself is returned.
*
* @param errOrResult
* An nsIException, Error, or one of the Components.results.
* @param opts
* An optional options object. The following properies are supported:
* @prop filename
* The name of the file being accessed when the exception was
* thrown, if any.
* @return An Error instance.
*/
var friendlyError = exports.friendlyError =
function friendlyError(errOrResult, opts) {
opts = opts || {};
var result = errOrResult instanceof Ci.nsIException ?
errOrResult.result :
errOrResult;
// Common options to be used below.
var filename = opts.filename || "(filename unknown)";
// If you add an error message, update testFriendlyError in test-xpcom.js.
// If the message includes options, also update this method's comment.
switch (result) {
case Cr.NS_BASE_STREAM_CLOSED:
return new Error("The stream is closed and cannot be read or written.");
case Cr.NS_ERROR_FILE_IS_DIRECTORY:
return new Error("The stream was opened on a directory, which cannot " +
"be read or written: " + filename);
case Cr.NS_ERROR_FILE_NOT_FOUND:
return new Error("path does not exist: " + filename);
}
// errOrResult should be an nsIException, ...
if (errOrResult instanceof Ci.nsIException)
return new Error("XPCOM error: " + errOrResult.message);
// ... one of Components.results, a number, ...
if (typeof(errOrResult) === "number") {
// Look up the result's name to make the message a little nicer.
for (let [name, val] in Iterator(Cr)) {
if (val === errOrResult) {
return new Error("XPCOM error " + name +
" (0x" + errOrResult.toString(16) + ")");
}
}
}
// ... or an Error.
if (errOrResult.constructor.name === "Error")
return errOrResult;
// We've been called wrong if we get here.
return new Error("Unknown error: " + errOrResult);
};
require("unload").when(
function() {
var copy = factories.slice();
copy.reverse();
copy.forEach(function(factory) { factory.unregister(); });
});

View File

@ -0,0 +1,91 @@
/* ***** 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 ***** */
const {Cc, Ci} = require("chrome");
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
var ID = exports.ID = appInfo.ID;
var name = exports.name = appInfo.name;
var version = exports.version = appInfo.version;
var platformVersion = exports.platformVersion = appInfo.platformVersion;
// The following mapping of application names to GUIDs was taken from:
//
// https://addons.mozilla.org/en-US/firefox/pages/appversions
//
// Using the GUID instead of the app's name is preferable because sometimes
// re-branded versions of a product have different names: for instance,
// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
// GUID.
var ids = exports.ids = {
Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
Fennec: "{a23983c0-fd0e-11dc-95ff-0800200c9a66}",
Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
};
var is = exports.is = function is(name) {
if (!(name in ids))
throw new Error("Unkown Mozilla Application: " + name);
return ID == ids[name];
};
var isOneOf = exports.isOneOf = function isOneOf(names) {
for (var i = 0; i < names.length; i++)
if (is(names[i]))
return true;
return false;
};
/**
* Use this to check whether the given version (e.g. xulApp.platformVersion)
* is in the given range. Versions must be in version comparator-compatible
* format. See MDC for details:
* https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
*/
var versionInRange = exports.versionInRange =
function versionInRange(version, lowInclusive, highExclusive) {
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator);
return (vc.compare(version, lowInclusive) >= 0) &&
(vc.compare(version, highExclusive) < 0);
}

View File

@ -0,0 +1,26 @@
var text = "";
var cleantext = "";
var paragraphs = document.getElementsByTagName('p');
var open = '<';
var close = '>';
for(var i=0; i<paragraphs.length; i++) {
text += paragraphs[i].innerHTML;
}
var doAppend = true;
var tmp = "";
for(var i=0; i<text.length; i++) {
tmp = text.charAt(i);
if( tmp == open ) {
doAppend = false;
}
if(doAppend) {
cleantext += tmp;
}
if( tmp == close ) {
doAppend = true;
}
}
//cleantext = unescape(cleantext);
postMessage(cleantext);

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="900" height="600"><rect width="900" height="600" fill="#ED2939"/><rect width="600" height="600" fill="#fff"/><rect width="300" height="600" fill="#002395"/></svg>

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="600" viewBox="0 0 5 3">
<desc>Flag of Germany</desc>
<rect id="black_stripe" width="5" height="3" y="0" x="0" fill="#000"/>
<rect id="red_stripe" width="5" height="2" y="1" x="0" fill="#D00"/>
<rect id="gold_stripe" width="5" height="1" y="2" x="0" fill="#FFCE00"/>
</svg>

After

Width:  |  Height:  |  Size: 491 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 230 KiB

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30" width="1200" height="600">
<clipPath id="t">
<path d="M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z"/>
</clipPath>
<path d="M0,0 v30 h60 v-30 z" fill="#00247d"/>
<path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" stroke-width="6"/>
<path d="M0,0 L60,30 M60,0 L0,30" clip-path="url(#t)" stroke="#cf142b" stroke-width="4"/>
<path d="M30,0 v30 M0,15 h60" stroke="#fff" stroke-width="10"/>
<path d="M30,0 v30 M0,15 h60" stroke="#cf142b" stroke-width="6"/>
</svg>

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -0,0 +1,4 @@
exports.german = 'de';
exports.french = 'fr';
exports.spanish = 'es';
exports.english = 'en';

View File

@ -0,0 +1,65 @@
var widgets = require("widget");
var pageMod = require("page-mod");
var student = require("student");
var data = require("self").data;
var workers = new Array();
var mod = null;
exports.main = function(options, callback) {
mod = pageMod.PageMod(
{
include: "*",
contentScriptWhen:"ready",
contentScriptFile: data.url("./contentScripts/keworker.js"),
onAttach: function onAttach(worker) {
worker.on('message', handleMessage);
workers.push(worker);
}
}
);
var widget = widgets.Widget(
{
id: "ke",
label: "Knowledge Engineering",
contentURL: data.url("keicon.png")
}
);
function handleMessage(message) {
var lang = require("language");
if(message.length > 0) {
//TODO: Iconswitch
var language = student.student(message);
console.log(language);
switch(language) {
case lang.german:
widget.contentURL = data.url("./flag/de.png");
break;
case lang.spanish:
widget.contentURL = data.url("./flag/es.png");
break;
case lang.english:
widget.contentURL = data.url("./flag/en.png");
break;
case lang.french:
widget.contentURL = data.url("./flag/fr.png");
break;
default:
widget.contentURL = data.url("./keicon.png");
}
//TODO: response
}
}
console.log("The add-on is running.");
}
exports.onUnload = function(reason) {
if(mod != null) {mod.destroy();}
for(var i=0; i<workers.length; i++) {
workers[i].destroy();
}
}

View File

@ -0,0 +1,54 @@
var lang = require("language");
var util = require("utility");
function student(text) {
// word counter
var deWordCount = 0;
var enWordCount = 0;
var frWordCount = 0;
// tokens
var tokens = util.tokenize(text);
//check every token
for (var i=0; i<tokens.length; i++) {
//token == german stopword
if (util.array_contains(util.getGermanStopwords(), tokens[i])){
deWordCount++;
}
//token == english stopword
if (util.array_contains(util.getEnglishStopwords(), tokens[i])){
enWordCount++;
}
//token == french stopword
if (util.array_contains(util.getFrenchStopwords(), tokens[i])){
frWordCount++;
}
}
console.log("german: " + deWordCount);
console.log("english: " + enWordCount);
console.log("french: " + frWordCount);
/** return language with most hits */
if (deWordCount >=enWordCount && deWordCount >= frWordCount) {
return lang.german;
}
if (enWordCount >= deWordCount && enWordCount >= frWordCount) {
return lang.english;
}
if (frWordCount >= enWordCount && frWordCount >= deWordCount) {
return lang.french;
}
return lang.german;
}
exports.student = student;

View File

@ -0,0 +1,13 @@
/*
Tokenize a text at whitespace.
Some punctuations are removed
*/
function tokenize(text) {
var lct = text.toLowerCase();
lct = lct.replace(/(\.|,|!|\?|'|"|\\|\/|\|)/g,"");
var result = lct.split(/\W/g);
return result;
}
exports.tokenize = tokenize;

View File

@ -0,0 +1,83 @@
const main = require("main");
const lang = require("language");
exports.test_test_run = function(test) {
test.pass("Unit test running!");
};
exports.test_id = function(test) {
test.assert(require("self").id.length > 0);
};
exports.test_url = function(test) {
require("request").Request({
url: "http://www.mozilla.org/",
onComplete: function(response) {
test.assertEqual(response.statusText, "OK");
test.done();
}
}).get();
test.waitUntilDone(20000);
};
exports.test_open_tab = function(test) {
const tabs = require("tabs");
tabs.open({
url: "http://www.mozilla.org/",
onReady: function(tab) {
test.assertEqual(tab.url, "http://www.mozilla.org/");
test.done();
}
});
test.waitUntilDone(20000);
};
var errormessage = "";
exports.test_util_countElements = function(test) {
const util = require("utility");
test.assert(compareObjects(util.countElements(["du", "du", "hallo", "hallo", "du"]),{"hallo":2, "du":3}),errormessage);
};
exports.test_util_toCharArray = function(test) {
const util = require("utility");
test.assert(compareArrays(util.toCharArray("test"), ["t","e","s","t"]), errormessage);
};
exports.test_util_toCharPairs = function(test) {
const util = require("utility");
test.assert(compareArrays(util.toCharPairs("mainz"),["ma", "ai", "in", "nz"]), errormessage);
};
exports.test_util_tokenize = function(test) {
const util = require("utility");
test.assert(compareArrays(util.tokenize("Dem Igel geht's gut."),["dem","igel","gehts","gut"]), errormessage);
};
exports.test_student_student = function(test) {
const student = require("student");
var text = "blubber";
test.assertEqual(student.student(text), lang.german, "Geht nicht weil.");
};
function compareObjects(a,b) {
for(var key in a) {
if( a[key] != b[key] ) {
return false;
}
}
return true;
};
function compareArrays(a,b) {
if (a.length != b.length) {
errormessage = "Arrays of unequal size";
return false
}
for(var i=0; i<a.length; i++) {
if (a[i] != b[i]) {
errormessage = a[i] + " != " + b[i];
return false;
}
}
return true;
};