aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js')
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js660
1 files changed, 660 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js
new file mode 100644
index 0000000..23030e9
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js
@@ -0,0 +1,660 @@
+/* ***** 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 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.
+
+"use strict";
+
+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 (let 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,
+ metadata: options.metadata,
+ uriPrefix: options.uriPrefix,
+ name: options.name,
+ 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,
+ uriPrefix: options.uriPrefix,
+
+ bundleID: options.bundleID,
+
+ getModuleInfo: function getModuleInfo(path) {
+ return this.__packages[path];
+ },
+
+ 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 (let 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;
+
+ var level = "error";
+ print(e + " (" + e.fileName + ":" + e.lineNumber + ")\n", level);
+ if (e.stack)
+ print("stack:\n" + e.stack + "\n", level);
+}
+
+// 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, level) {
+ msg = stringify(msg);
+ if (msg.indexOf('\n') >= 0) {
+ var str = buffer + msg;
+ if (level === "error") {
+ var err = Cc["@mozilla.org/scripterror;1"]
+ .createInstance(Ci.nsIScriptError);
+ str = str.replace(/^error: /, "");
+ err.init(str, null, null, 0, 0, 0, "Add-on SDK");
+ cService.logMessage(err);
+ }
+ else
+ cService.logStringMessage(str);
+ 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;
+ 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.json file must exist.");
+ }
+
+ options = JSON.parse(jsonData);
+ } catch (e) {
+ defaultLogError(e);
+ throw e;
+ }
+
+ var onQuit = function() {};
+ var doDump = buildForsakenConsoleDump(dump);
+
+ if ('resultFile' in options)
+ onQuit = buildDevQuit(options, print);
+
+ 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, 26 /* PR_WRONLY | PR_APPEND | PR_CREATE_FILE */,
+ -1 , 0);
+ }
+
+ function print(msg, level) {
+ doDump(msg, level);
+ 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,
+ ])
+};