Wed, 31 Dec 2014 07:53:36 +0100
Correct small whitespace inconsistency, lost while renaming variables.
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is Jetpack.
15 *
16 * The Initial Developer of the Original Code is Mozilla.
17 * Portions created by the Initial Developer are Copyright (C) 2007
18 * the Initial Developer. All Rights Reserved.
19 *
20 * Contributor(s):
21 * Atul Varma <atul@mozilla.com>
22 *
23 * Alternatively, the contents of this file may be used under the terms of
24 * either the GNU General Public License Version 2 or later (the "GPL"), or
25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 * in which case the provisions of the GPL or the LGPL are applicable instead
27 * of those above. If you wish to allow use of your version of this file only
28 * under the terms of either the GPL or the LGPL, and not to allow others to
29 * use your version of this file under the terms of the MPL, indicate your
30 * decision by deleting the provisions above and replace them with the notice
31 * and other provisions required by the GPL or the LGPL. If you do not delete
32 * the provisions above, a recipient may use your version of this file under
33 * the terms of any one of the MPL, the GPL or the LGPL.
34 *
35 * ***** END LICENSE BLOCK ***** */
37 (function(global) {
38 const Cc = Components.classes;
39 const Ci = Components.interfaces;
40 const Cu = Components.utils;
41 const Cr = Components.results;
43 var exports = {};
45 var ios = Cc['@mozilla.org/network/io-service;1']
46 .getService(Ci.nsIIOService);
48 var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
49 .createInstance(Ci.nsIPrincipal);
51 function resolvePrincipal(principal, defaultPrincipal) {
52 if (principal === undefined)
53 return defaultPrincipal;
54 if (principal == "system")
55 return systemPrincipal;
56 return principal;
57 }
59 // The base URI to we use when we're given relative URLs, if any.
60 var baseURI = null;
61 if (global.window)
62 baseURI = ios.newURI(global.location.href, null, null);
63 exports.baseURI = baseURI;
65 // The "parent" chrome URI to use if we're loading code that
66 // needs chrome privileges but may not have a filename that
67 // matches any of SpiderMonkey's defined system filename prefixes.
68 // The latter is needed so that wrappers can be automatically
69 // made for the code. For more information on this, see
70 // bug 418356:
71 //
72 // https://bugzilla.mozilla.org/show_bug.cgi?id=418356
73 var parentChromeURIString;
74 if (baseURI)
75 // We're being loaded from a chrome-privileged document, so
76 // use its URL as the parent string.
77 parentChromeURIString = baseURI.spec;
78 else
79 // We're being loaded from a chrome-privileged JS module or
80 // SecurableModule, so use its filename (which may itself
81 // contain a reference to a parent).
82 parentChromeURIString = Components.stack.filename;
84 function maybeParentifyFilename(filename) {
85 var doParentifyFilename = true;
86 try {
87 // TODO: Ideally we should just make
88 // nsIChromeRegistry.wrappersEnabled() available from script
89 // and use it here. Until that's in the platform, though,
90 // we'll play it safe and parentify the filename unless
91 // we're absolutely certain things will be ok if we don't.
92 var filenameURI = ios.newURI(options.filename,
93 null,
94 baseURI);
95 if (filenameURI.scheme == 'chrome' &&
96 filenameURI.path.indexOf('/content/') == 0)
97 // Content packages will always have wrappers made for them;
98 // if automatic wrappers have been disabled for the
99 // chrome package via a chrome manifest flag, then
100 // this still works too, to the extent that the
101 // content package is insecure anyways.
102 doParentifyFilename = false;
103 } catch (e) {}
104 if (doParentifyFilename)
105 return parentChromeURIString + " -> " + filename;
106 return filename;
107 }
109 function getRootDir(urlStr) {
110 // TODO: This feels hacky, and like there will be edge cases.
111 return urlStr.slice(0, urlStr.lastIndexOf("/") + 1);
112 }
114 exports.SandboxFactory = function SandboxFactory(defaultPrincipal) {
115 // Unless specified otherwise, use a principal with limited
116 // privileges.
117 this._defaultPrincipal = resolvePrincipal(defaultPrincipal,
118 "http://www.mozilla.org");
119 },
121 exports.SandboxFactory.prototype = {
122 createSandbox: function createSandbox(options) {
123 var principal = resolvePrincipal(options.principal,
124 this._defaultPrincipal);
126 return {
127 _sandbox: new Cu.Sandbox(principal),
128 _principal: principal,
129 get globalScope() {
130 return this._sandbox;
131 },
132 defineProperty: function defineProperty(name, value) {
133 this._sandbox[name] = value;
134 },
135 getProperty: function getProperty(name) {
136 return this._sandbox[name];
137 },
138 evaluate: function evaluate(options) {
139 if (typeof(options) == 'string')
140 options = {contents: options};
141 options = {__proto__: options};
142 if (typeof(options.contents) != 'string')
143 throw new Error('Expected string for options.contents');
144 if (options.lineNo === undefined)
145 options.lineNo = 1;
146 if (options.jsVersion === undefined)
147 options.jsVersion = "1.8";
148 if (typeof(options.filename) != 'string')
149 options.filename = '<string>';
151 if (this._principal == systemPrincipal)
152 options.filename = maybeParentifyFilename(options.filename);
154 return Cu.evalInSandbox(options.contents,
155 this._sandbox,
156 options.jsVersion,
157 options.filename,
158 options.lineNo);
159 }
160 };
161 }
162 };
164 exports.Loader = function Loader(options) {
165 options = {__proto__: options};
166 if (options.fs === undefined) {
167 var rootPaths = options.rootPath || options.rootPaths;
168 if (rootPaths) {
169 if (rootPaths.constructor.name != "Array")
170 rootPaths = [rootPaths];
171 var fses = [new exports.LocalFileSystem(path)
172 for each (path in rootPaths)];
173 options.fs = new exports.CompositeFileSystem(fses);
174 } else
175 options.fs = new exports.LocalFileSystem();
176 }
177 if (options.sandboxFactory === undefined)
178 options.sandboxFactory = new exports.SandboxFactory(
179 options.defaultPrincipal
180 );
181 if (options.modules === undefined)
182 options.modules = {};
183 if (options.globals === undefined)
184 options.globals = {};
186 this.fs = options.fs;
187 this.sandboxFactory = options.sandboxFactory;
188 this.sandboxes = {};
189 this.modules = options.modules;
190 this.globals = options.globals;
191 };
193 exports.Loader.prototype = {
194 _makeRequire: function _makeRequire(rootDir) {
195 var self = this;
196 return function require(module) {
197 if (module == "chrome") {
198 var chrome = { Cc: Components.classes,
199 Ci: Components.interfaces,
200 Cu: Components.utils,
201 Cr: Components.results,
202 Cm: Components.manager,
203 components: Components
204 };
205 return chrome;
206 }
207 var path = self.fs.resolveModule(rootDir, module);
208 if (!path)
209 throw new Error('Module "' + module + '" not found');
210 if (!(path in self.modules)) {
211 var options = self.fs.getFile(path);
212 if (options.filename === undefined)
213 options.filename = path;
215 var exports = {};
216 var sandbox = self.sandboxFactory.createSandbox(options);
217 self.sandboxes[path] = sandbox;
218 for (name in self.globals)
219 sandbox.defineProperty(name, self.globals[name]);
220 sandbox.defineProperty('require', self._makeRequire(path));
221 sandbox.evaluate("var exports = {};");
222 let ES5 = self.modules.es5;
223 if (ES5) {
224 let { Object, Array, Function } = sandbox.globalScope;
225 ES5.init(Object, Array, Function);
226 }
227 self.modules[path] = sandbox.getProperty("exports");
228 sandbox.evaluate(options);
229 }
230 return self.modules[path];
231 };
232 },
234 // This is only really used by unit tests and other
235 // development-related facilities, allowing access to symbols
236 // defined in the global scope of a module.
237 findSandboxForModule: function findSandboxForModule(module) {
238 var path = this.fs.resolveModule(null, module);
239 if (!path)
240 throw new Error('Module "' + module + '" not found');
241 if (!(path in this.sandboxes))
242 this.require(module);
243 if (!(path in this.sandboxes))
244 throw new Error('Internal error: path not in sandboxes: ' +
245 path);
246 return this.sandboxes[path];
247 },
249 require: function require(module) {
250 return (this._makeRequire(null))(module);
251 },
253 runScript: function runScript(options, extraOutput) {
254 if (typeof(options) == 'string')
255 options = {contents: options};
256 options = {__proto__: options};
257 var sandbox = this.sandboxFactory.createSandbox(options);
258 if (extraOutput)
259 extraOutput.sandbox = sandbox;
260 for (name in this.globals)
261 sandbox.defineProperty(name, this.globals[name]);
262 sandbox.defineProperty('require', this._makeRequire(null));
263 return sandbox.evaluate(options);
264 }
265 };
267 exports.CompositeFileSystem = function CompositeFileSystem(fses) {
268 this.fses = fses;
269 this._pathMap = {};
270 };
272 exports.CompositeFileSystem.prototype = {
273 resolveModule: function resolveModule(base, path) {
274 for (var i = 0; i < this.fses.length; i++) {
275 var fs = this.fses[i];
276 var absPath = fs.resolveModule(base, path);
277 if (absPath) {
278 this._pathMap[absPath] = fs;
279 return absPath;
280 }
281 }
282 return null;
283 },
284 getFile: function getFile(path) {
285 return this._pathMap[path].getFile(path);
286 }
287 };
289 exports.LocalFileSystem = function LocalFileSystem(root) {
290 if (root === undefined) {
291 if (!baseURI)
292 throw new Error("Need a root path for module filesystem");
293 root = baseURI;
294 }
295 if (typeof(root) == 'string')
296 root = ios.newURI(root, null, baseURI);
297 if (root instanceof Ci.nsIFile)
298 root = ios.newFileURI(root);
299 if (!(root instanceof Ci.nsIURI))
300 throw new Error('Expected nsIFile, nsIURI, or string for root');
302 this.root = root.spec;
303 this._rootURI = root;
304 this._rootURIDir = getRootDir(root.spec);
305 };
307 exports.LocalFileSystem.prototype = {
308 resolveModule: function resolveModule(base, path) {
309 path = path + ".js";
311 var baseURI;
312 if (!base)
313 baseURI = this._rootURI;
314 else
315 baseURI = ios.newURI(base, null, null);
316 var newURI = ios.newURI(path, null, baseURI);
317 var channel = ios.newChannelFromURI(newURI);
318 try {
319 channel.open().close();
320 } catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
321 return null;
322 }
323 return newURI.spec;
324 },
325 getFile: function getFile(path) {
326 var channel = ios.newChannel(path, null, null);
327 var iStream = channel.open();
328 var ciStream = Cc["@mozilla.org/intl/converter-input-stream;1"].
329 createInstance(Ci.nsIConverterInputStream);
330 var bufLen = 0x8000;
331 ciStream.init(iStream, "UTF-8", bufLen,
332 Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
333 var chunk = {};
334 var data = "";
335 while (ciStream.readString(bufLen, chunk) > 0)
336 data += chunk.value;
337 ciStream.close();
338 iStream.close();
339 return {contents: data};
340 }
341 };
343 if (global.window) {
344 // We're being loaded in a chrome window, or a web page with
345 // UniversalXPConnect privileges.
346 global.SecurableModule = exports;
347 } else if (global.exports) {
348 // We're being loaded in a SecurableModule.
349 for (name in exports) {
350 global.exports[name] = exports[name];
351 }
352 } else {
353 // We're being loaded in a JS module.
354 global.EXPORTED_SYMBOLS = [];
355 for (name in exports) {
356 global.EXPORTED_SYMBOLS.push(name);
357 global[name] = exports[name];
358 }
359 }
360 })(this);