summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/addon/runner.js
blob: 3977a04e4ae2e9d80ef1c2627b19c47c5f43ce99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, Cu } = require('chrome');
const { rootURI, metadata, isNative } = require('@loader/options');
const { id, loadReason } = require('../self');
const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
const { once } = require('../system/events');
const { exit, env, staticArgs } = require('../system');
const { when: unload } = require('../system/unload');
const globals = require('../system/globals');
const xulApp = require('../system/xul-app');
const { get } = require('../preferences/service');
const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
                        getService(Ci.nsIAppShellService);
const { preferences } = metadata;

const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
  return Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}).
         BrowserToolboxProcess;
});

// Initializes default preferences
function setDefaultPrefs(prefsURI) {
  const prefs = Cc['@mozilla.org/preferences-service;1'].
                getService(Ci.nsIPrefService).
                QueryInterface(Ci.nsIPrefBranch2);
  const branch = prefs.getDefaultBranch('');
  const sandbox = Sandbox({
    name: prefsURI,
    prototype: {
      pref: function(key, val) {
        switch (typeof val) {
          case 'boolean':
            branch.setBoolPref(key, val);
            break;
          case 'number':
            if (val % 1 == 0) // number must be a integer, otherwise ignore it
              branch.setIntPref(key, val);
            break;
          case 'string':
            branch.setCharPref(key, val);
            break;
        }
      }
    }
  });
  // load preferences.
  evaluate(sandbox, prefsURI);
}

function definePseudo(loader, id, exports) {
  let uri = resolveURI(id, loader.mapping);
  loader.modules[uri] = { exports: exports };
}

function startup(reason, options) {
  return Startup.onceInitialized.then(() => {
    // Inject globals ASAP in order to have console API working ASAP
    Object.defineProperties(options.loader.globals, descriptor(globals));

    // NOTE: Module is intentionally required only now because it relies
    // on existence of hidden window, which does not exists until startup.
    let { ready } = require('../addon/window');
    // Load localization manifest and .properties files.
    // Run the addon even in case of error (best effort approach)
    require('../l10n/loader').
      load(rootURI).
      then(null, function failure(error) {
        if (!isNative)
          console.info("Error while loading localization: " + error.message);
      }).
      then(function onLocalizationReady(data) {
        // Exports data to a pseudo module so that api-utils/l10n/core
        // can get access to it
        definePseudo(options.loader, '@l10n/data', data ? data : null);
        return ready;
      }).then(function() {
        run(options);
      }).then(null, console.exception);
    return void 0; // otherwise we raise a warning, see bug 910304
  });
}

function run(options) {
  try {
    // Try initializing HTML localization before running main module. Just print
    // an exception in case of error, instead of preventing addon to be run.
    try {
      // Do not enable HTML localization while running test as it is hard to
      // disable. Because unit tests are evaluated in a another Loader who
      // doesn't have access to this current loader.
      if (options.main !== 'sdk/test/runner') {
        require('../l10n/html').enable();
      }
    }
    catch(error) {
      console.exception(error);
    }

    // native-options does stuff directly with preferences key from package.json
    if (preferences && preferences.length > 0) {
      try {
        require('../preferences/native-options').
          enable({ preferences: preferences, id: id }).
          catch(console.exception);
      }
      catch (error) {
        console.exception(error);
      }
    }
    else {
      // keeping support for addons packaged with older SDK versions,
      // when cfx didn't include the 'preferences' key in @loader/options

      // Initialize inline options localization, without preventing addon to be
      // run in case of error
      try {
        require('../l10n/prefs').enable();
      }
      catch(error) {
        console.exception(error);
      }

      // TODO: When bug 564675 is implemented this will no longer be needed
      // Always set the default prefs, because they disappear on restart
      if (options.prefsURI) {
        // Only set if `prefsURI` specified
        try {
          setDefaultPrefs(options.prefsURI);
        }
        catch (err) {
          // cfx bootstrap always passes prefsURI, even in addons without prefs
        }
      }
    }

    // this is where the addon's main.js finally run.
    let program = main(options.loader, options.main);

    if (typeof(program.onUnload) === 'function')
      unload(program.onUnload);

    if (typeof(program.main) === 'function') {
      program.main({
        loadReason: loadReason,
        staticArgs: staticArgs
      }, {
        print: function print(_) { dump(_ + '\n') },
        quit: exit
      });
    }

    if (get("extensions." + id + ".sdk.debug.show", false)) {
      BrowserToolboxProcess.init({ addonID: id });
    }
  } catch (error) {
    console.exception(error);
    throw error;
  }
}
exports.startup = startup;

// If add-on is lunched via `cfx run` we need to use `system.exit` to let
// cfx know we're done (`cfx test` will take care of exit so we don't do
// anything here).
if (env.CFX_COMMAND === 'run') {
  unload(function(reason) {
    if (reason === 'shutdown')
      exit(0);
  });
}