web mining addon aufgabe
152
ss2013/1_Web Mining/Uebungen/1_Uebung/keaddon/bootstrap.js
vendored
Normal 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.
|
||||
}
|
||||
@ -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,
|
||||
])
|
||||
};
|
||||
1546
ss2013/1_Web Mining/Uebungen/1_Uebung/keaddon/harness-options.json
Normal file
BIN
ss2013/1_Web Mining/Uebungen/1_Uebung/keaddon/icon.png
Normal file
|
After Width: | Height: | Size: 674 B |
35
ss2013/1_Web Mining/Uebungen/1_Uebung/keaddon/install.rdf
Normal 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>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Page Worker test</title>
|
||||
</head>
|
||||
<body>
|
||||
<p id="paragraph">Lorem ipsum dolor sit amet.</p>
|
||||
</body>
|
||||
</html>
|
||||
@ -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);
|
||||
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>foo</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>bar</p>
|
||||
</body>
|
||||
</html>
|
||||
@ -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 O’Shannessy <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;
|
||||
}
|
||||
@ -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"]
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -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();
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
@ -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 O’Shannessy <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;
|
||||
|
||||
@ -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 O’Shannessy <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];
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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();
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
@ -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];
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
@ -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;
|
||||
};
|
||||
@ -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 };
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -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 : {});
|
||||
@ -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);
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
// this file left intentionally blank
|
||||
@ -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);
|
||||
});
|
||||
@ -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);
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
@ -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;
|
||||
}
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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());
|
||||
};
|
||||
@ -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)]);
|
||||
}
|
||||
};
|
||||
@ -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.
|
||||
}
|
||||
};
|
||||
@ -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);
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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); }
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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]
|
||||
}
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
};
|
||||
@ -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]);
|
||||
});
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -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); });
|
||||
});
|
||||
|
||||
@ -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");
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
@ -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();
|
||||
}
|
||||
};
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
};
|
||||
@ -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(); });
|
||||
});
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
@ -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(); });
|
||||
});
|
||||
@ -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(); });
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
@ -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 |
@ -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 |
|
After Width: | Height: | Size: 230 KiB |
@ -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 |
|
After Width: | Height: | Size: 760 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 835 B |
|
After Width: | Height: | Size: 674 B |
@ -0,0 +1,4 @@
|
||||
exports.german = 'de';
|
||||
exports.french = 'fr';
|
||||
exports.spanish = 'es';
|
||||
exports.english = 'en';
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
};
|
||||