addon-sdk/source/app-extension/bootstrap.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/addon-sdk/source/app-extension/bootstrap.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,350 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +// @see http://mxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp
     1.9 +
    1.10 +'use strict';
    1.11 +
    1.12 +// IMPORTANT: Avoid adding any initialization tasks here, if you need to do
    1.13 +// something before add-on is loaded consider addon/runner module instead!
    1.14 +
    1.15 +const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
    1.16 +        results: Cr, manager: Cm } = Components;
    1.17 +const ioService = Cc['@mozilla.org/network/io-service;1'].
    1.18 +                  getService(Ci.nsIIOService);
    1.19 +const resourceHandler = ioService.getProtocolHandler('resource').
    1.20 +                        QueryInterface(Ci.nsIResProtocolHandler);
    1.21 +const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
    1.22 +const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
    1.23 +                     getService(Ci.mozIJSSubScriptLoader);
    1.24 +const prefService = Cc['@mozilla.org/preferences-service;1'].
    1.25 +                    getService(Ci.nsIPrefService).
    1.26 +                    QueryInterface(Ci.nsIPrefBranch);
    1.27 +const appInfo = Cc["@mozilla.org/xre/app-info;1"].
    1.28 +                getService(Ci.nsIXULAppInfo);
    1.29 +const vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
    1.30 +           getService(Ci.nsIVersionComparator);
    1.31 +
    1.32 +
    1.33 +const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable',
    1.34 +                 'install', 'uninstall', 'upgrade', 'downgrade' ];
    1.35 +
    1.36 +const bind = Function.call.bind(Function.bind);
    1.37 +
    1.38 +let loader = null;
    1.39 +let unload = null;
    1.40 +let cuddlefishSandbox = null;
    1.41 +let nukeTimer = null;
    1.42 +
    1.43 +let resourceDomains = [];
    1.44 +function setResourceSubstitution(domain, uri) {
    1.45 +  resourceDomains.push(domain);
    1.46 +  resourceHandler.setSubstitution(domain, uri);
    1.47 +}
    1.48 +
    1.49 +// Utility function that synchronously reads local resource from the given
    1.50 +// `uri` and returns content string.
    1.51 +function readURI(uri) {
    1.52 +  let ioservice = Cc['@mozilla.org/network/io-service;1'].
    1.53 +    getService(Ci.nsIIOService);
    1.54 +  let channel = ioservice.newChannel(uri, 'UTF-8', null);
    1.55 +  let stream = channel.open();
    1.56 +
    1.57 +  let cstream = Cc['@mozilla.org/intl/converter-input-stream;1'].
    1.58 +    createInstance(Ci.nsIConverterInputStream);
    1.59 +  cstream.init(stream, 'UTF-8', 0, 0);
    1.60 +
    1.61 +  let str = {};
    1.62 +  let data = '';
    1.63 +  let read = 0;
    1.64 +  do {
    1.65 +    read = cstream.readString(0xffffffff, str);
    1.66 +    data += str.value;
    1.67 +  } while (read != 0);
    1.68 +
    1.69 +  cstream.close();
    1.70 +
    1.71 +  return data;
    1.72 +}
    1.73 +
    1.74 +// We don't do anything on install & uninstall yet, but in a future
    1.75 +// we should allow add-ons to cleanup after uninstall.
    1.76 +function install(data, reason) {}
    1.77 +function uninstall(data, reason) {}
    1.78 +
    1.79 +function startup(data, reasonCode) {
    1.80 +  try {
    1.81 +    let reason = REASON[reasonCode];
    1.82 +    // URI for the root of the XPI file.
    1.83 +    // 'jar:' URI if the addon is packed, 'file:' URI otherwise.
    1.84 +    // (Used by l10n module in order to fetch `locale` folder)
    1.85 +    let rootURI = data.resourceURI.spec;
    1.86 +
    1.87 +    // TODO: Maybe we should perform read harness-options.json asynchronously,
    1.88 +    // since we can't do anything until 'sessionstore-windows-restored' anyway.
    1.89 +    let options = JSON.parse(readURI(rootURI + './harness-options.json'));
    1.90 +
    1.91 +    let id = options.jetpackID;
    1.92 +    let name = options.name;
    1.93 +
    1.94 +    // Clean the metadata
    1.95 +    options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {};
    1.96 +
    1.97 +    // freeze the permissionss
    1.98 +    Object.freeze(options.metadata[name]['permissions']);
    1.99 +    // freeze the metadata
   1.100 +    Object.freeze(options.metadata[name]);
   1.101 +
   1.102 +    // Register a new resource 'domain' for this addon which is mapping to
   1.103 +    // XPI's `resources` folder.
   1.104 +    // Generate the domain name by using jetpack ID, which is the extension ID
   1.105 +    // by stripping common characters that doesn't work as a domain name:
   1.106 +    let uuidRe =
   1.107 +      /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
   1.108 +
   1.109 +    let domain = id.
   1.110 +      toLowerCase().
   1.111 +      replace(/@/g, '-at-').
   1.112 +      replace(/\./g, '-dot-').
   1.113 +      replace(uuidRe, '$1');
   1.114 +
   1.115 +    let prefixURI = 'resource://' + domain + '/';
   1.116 +    let resourcesURI = ioService.newURI(rootURI + '/resources/', null, null);
   1.117 +    setResourceSubstitution(domain, resourcesURI);
   1.118 +
   1.119 +    // Create path to URLs mapping supported by loader.
   1.120 +    let paths = {
   1.121 +      // Relative modules resolve to add-on package lib
   1.122 +      './': prefixURI + name + '/lib/',
   1.123 +      './tests/': prefixURI + name + '/tests/',
   1.124 +      '': 'resource://gre/modules/commonjs/'
   1.125 +    };
   1.126 +
   1.127 +    // Maps addon lib and tests ressource folders for each package
   1.128 +    paths = Object.keys(options.metadata).reduce(function(result, name) {
   1.129 +      result[name + '/'] = prefixURI + name + '/lib/'
   1.130 +      result[name + '/tests/'] = prefixURI + name + '/tests/'
   1.131 +      return result;
   1.132 +    }, paths);
   1.133 +
   1.134 +    // We need to map tests folder when we run sdk tests whose package name
   1.135 +    // is stripped
   1.136 +    if (name == 'addon-sdk')
   1.137 +      paths['tests/'] = prefixURI + name + '/tests/';
   1.138 +
   1.139 +    let useBundledSDK = options['force-use-bundled-sdk'];
   1.140 +    if (!useBundledSDK) {
   1.141 +      try {
   1.142 +        useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK");
   1.143 +      }
   1.144 +      catch (e) {
   1.145 +        // Pref doesn't exist, allow using Firefox shipped SDK
   1.146 +      }
   1.147 +    }
   1.148 +
   1.149 +    // Starting with Firefox 21.0a1, we start using modules shipped into firefox
   1.150 +    // Still allow using modules from the xpi if the manifest tell us to do so.
   1.151 +    // And only try to look for sdk modules in xpi if the xpi actually ship them
   1.152 +    if (options['is-sdk-bundled'] &&
   1.153 +        (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) {
   1.154 +      // Maps sdk module folders to their resource folder
   1.155 +      paths[''] = prefixURI + 'addon-sdk/lib/';
   1.156 +      // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder,
   1.157 +      // so that it isn't shipped in the xpi. Keep a copy of it in sdk/ folder
   1.158 +      // until we no longer support SDK modules in XPI:
   1.159 +      paths['test'] = prefixURI + 'addon-sdk/lib/sdk/test.js';
   1.160 +    }
   1.161 +
   1.162 +    // Retrieve list of module folder overloads based on preferences in order to
   1.163 +    // eventually used a local modules instead of files shipped into Firefox.
   1.164 +    let branch = prefService.getBranch('extensions.modules.' + id + '.path');
   1.165 +    paths = branch.getChildList('', {}).reduce(function (result, name) {
   1.166 +      // Allows overloading of any sub folder by replacing . by / in pref name
   1.167 +      let path = name.substr(1).split('.').join('/');
   1.168 +      // Only accept overloading folder by ensuring always ending with `/`
   1.169 +      if (path) path += '/';
   1.170 +      let fileURI = branch.getCharPref(name);
   1.171 +
   1.172 +      // On mobile, file URI has to end with a `/` otherwise, setSubstitution
   1.173 +      // takes the parent folder instead.
   1.174 +      if (fileURI[fileURI.length-1] !== '/')
   1.175 +        fileURI += '/';
   1.176 +
   1.177 +      // Maps the given file:// URI to a resource:// in order to avoid various
   1.178 +      // failure that happens with file:// URI and be close to production env
   1.179 +      let resourcesURI = ioService.newURI(fileURI, null, null);
   1.180 +      let resName = 'extensions.modules.' + domain + '.commonjs.path' + name;
   1.181 +      setResourceSubstitution(resName, resourcesURI);
   1.182 +
   1.183 +      result[path] = 'resource://' + resName + '/';
   1.184 +      return result;
   1.185 +    }, paths);
   1.186 +
   1.187 +    // Make version 2 of the manifest
   1.188 +    let manifest = options.manifest;
   1.189 +
   1.190 +    // Import `cuddlefish.js` module using a Sandbox and bootstrap loader.
   1.191 +    let cuddlefishPath = 'loader/cuddlefish.js';
   1.192 +    let cuddlefishURI = 'resource://gre/modules/commonjs/sdk/' + cuddlefishPath;
   1.193 +    if (paths['sdk/']) { // sdk folder has been overloaded
   1.194 +                         // (from pref, or cuddlefish is still in the xpi)
   1.195 +      cuddlefishURI = paths['sdk/'] + cuddlefishPath;
   1.196 +    }
   1.197 +    else if (paths['']) { // root modules folder has been overloaded
   1.198 +      cuddlefishURI = paths[''] + 'sdk/' + cuddlefishPath;
   1.199 +    }
   1.200 +
   1.201 +    cuddlefishSandbox = loadSandbox(cuddlefishURI);
   1.202 +    let cuddlefish = cuddlefishSandbox.exports;
   1.203 +
   1.204 +    // Normalize `options.mainPath` so that it looks like one that will come
   1.205 +    // in a new version of linker.
   1.206 +    let main = options.mainPath;
   1.207 +
   1.208 +    unload = cuddlefish.unload;
   1.209 +    loader = cuddlefish.Loader({
   1.210 +      paths: paths,
   1.211 +      // modules manifest.
   1.212 +      manifest: manifest,
   1.213 +
   1.214 +      // Add-on ID used by different APIs as a unique identifier.
   1.215 +      id: id,
   1.216 +      // Add-on name.
   1.217 +      name: name,
   1.218 +      // Add-on version.
   1.219 +      version: options.metadata[name].version,
   1.220 +      // Add-on package descriptor.
   1.221 +      metadata: options.metadata[name],
   1.222 +      // Add-on load reason.
   1.223 +      loadReason: reason,
   1.224 +
   1.225 +      prefixURI: prefixURI,
   1.226 +      // Add-on URI.
   1.227 +      rootURI: rootURI,
   1.228 +      // options used by system module.
   1.229 +      // File to write 'OK' or 'FAIL' (exit code emulation).
   1.230 +      resultFile: options.resultFile,
   1.231 +      // Arguments passed as --static-args
   1.232 +      staticArgs: options.staticArgs,
   1.233 +      // Add-on preferences branch name
   1.234 +      preferencesBranch: options.preferencesBranch,
   1.235 +
   1.236 +      // Arguments related to test runner.
   1.237 +      modules: {
   1.238 +        '@test/options': {
   1.239 +          allTestModules: options.allTestModules,
   1.240 +          iterations: options.iterations,
   1.241 +          filter: options.filter,
   1.242 +          profileMemory: options.profileMemory,
   1.243 +          stopOnError: options.stopOnError,
   1.244 +          verbose: options.verbose,
   1.245 +          parseable: options.parseable,
   1.246 +          checkMemory: options.check_memory,
   1.247 +        }
   1.248 +      }
   1.249 +    });
   1.250 +
   1.251 +    let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI);
   1.252 +    let require = cuddlefish.Require(loader, module);
   1.253 +
   1.254 +    require('sdk/addon/runner').startup(reason, {
   1.255 +      loader: loader,
   1.256 +      main: main,
   1.257 +      prefsURI: rootURI + 'defaults/preferences/prefs.js'
   1.258 +    });
   1.259 +  } catch (error) {
   1.260 +    dump('Bootstrap error: ' +
   1.261 +         (error.message ? error.message : String(error)) + '\n' +
   1.262 +         (error.stack || error.fileName + ': ' + error.lineNumber) + '\n');
   1.263 +    throw error;
   1.264 +  }
   1.265 +};
   1.266 +
   1.267 +function loadSandbox(uri) {
   1.268 +  let proto = {
   1.269 +    sandboxPrototype: {
   1.270 +      loadSandbox: loadSandbox,
   1.271 +      ChromeWorker: ChromeWorker
   1.272 +    }
   1.273 +  };
   1.274 +  let sandbox = Cu.Sandbox(systemPrincipal, proto);
   1.275 +  // Create a fake commonjs environnement just to enable loading loader.js
   1.276 +  // correctly
   1.277 +  sandbox.exports = {};
   1.278 +  sandbox.module = { uri: uri, exports: sandbox.exports };
   1.279 +  sandbox.require = function (id) {
   1.280 +    if (id !== "chrome")
   1.281 +      throw new Error("Bootstrap sandbox `require` method isn't implemented.");
   1.282 +
   1.283 +    return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
   1.284 +      CC: bind(CC, Components), components: Components,
   1.285 +      ChromeWorker: ChromeWorker });
   1.286 +  };
   1.287 +  scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
   1.288 +  return sandbox;
   1.289 +}
   1.290 +
   1.291 +function unloadSandbox(sandbox) {
   1.292 +  if ("nukeSandbox" in Cu)
   1.293 +    Cu.nukeSandbox(sandbox);
   1.294 +}
   1.295 +
   1.296 +function setTimeout(callback, delay) {
   1.297 +  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.298 +  timer.initWithCallback({ notify: callback }, delay,
   1.299 +                         Ci.nsITimer.TYPE_ONE_SHOT);
   1.300 +  return timer;
   1.301 +}
   1.302 +
   1.303 +function shutdown(data, reasonCode) {
   1.304 +  let reason = REASON[reasonCode];
   1.305 +  if (loader) {
   1.306 +    unload(loader, reason);
   1.307 +    unload = null;
   1.308 +
   1.309 +    // Don't waste time cleaning up if the application is shutting down
   1.310 +    if (reason != "shutdown") {
   1.311 +      // Avoid leaking all modules when something goes wrong with one particular
   1.312 +      // module. Do not clean it up immediatly in order to allow executing some
   1.313 +      // actions on addon disabling.
   1.314 +      // We need to keep a reference to the timer, otherwise it is collected
   1.315 +      // and won't ever fire.
   1.316 +      nukeTimer = setTimeout(nukeModules, 1000);
   1.317 +
   1.318 +      // Bug 944951 - bootstrap.js must remove the added resource: URIs on unload
   1.319 +      resourceDomains.forEach(domain => {
   1.320 +        resourceHandler.setSubstitution(domain, null);
   1.321 +      })
   1.322 +    }
   1.323 +  }
   1.324 +};
   1.325 +
   1.326 +function nukeModules() {
   1.327 +  nukeTimer = null;
   1.328 +  // module objects store `exports` which comes from sandboxes
   1.329 +  // We should avoid keeping link to these object to avoid leaking sandboxes
   1.330 +  for (let key in loader.modules) {
   1.331 +    delete loader.modules[key];
   1.332 +  }
   1.333 +  // Direct links to sandboxes should be removed too
   1.334 +  for (let key in loader.sandboxes) {
   1.335 +    let sandbox = loader.sandboxes[key];
   1.336 +    delete loader.sandboxes[key];
   1.337 +    // Bug 775067: From FF17 we can kill all CCW from a given sandbox
   1.338 +    unloadSandbox(sandbox);
   1.339 +  }
   1.340 +  loader = null;
   1.341 +
   1.342 +  // both `toolkit/loader` and `system/xul-app` are loaded as JSM's via
   1.343 +  // `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when
   1.344 +  // the addon is unload.
   1.345 +
   1.346 +  unloadSandbox(cuddlefishSandbox.loaderSandbox);
   1.347 +  unloadSandbox(cuddlefishSandbox.xulappSandbox);
   1.348 +
   1.349 +  // Bug 764840: We need to unload cuddlefish otherwise it will stay alive
   1.350 +  // and keep a reference to this compartment.
   1.351 +  unloadSandbox(cuddlefishSandbox);
   1.352 +  cuddlefishSandbox = null;
   1.353 +}

mercurial