michael@0: /* ***** BEGIN LICENSE BLOCK ***** michael@0: * Version: MPL 1.1/GPL 2.0/LGPL 2.1 michael@0: * michael@0: * The contents of this file are subject to the Mozilla Public License Version michael@0: * 1.1 (the "License"); you may not use this file except in compliance with michael@0: * the License. You may obtain a copy of the License at michael@0: * http://www.mozilla.org/MPL/ michael@0: * michael@0: * Software distributed under the License is distributed on an "AS IS" basis, michael@0: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License michael@0: * for the specific language governing rights and limitations under the michael@0: * License. michael@0: * michael@0: * The Original Code is Jetpack. michael@0: * michael@0: * The Initial Developer of the Original Code is Mozilla. michael@0: * Portions created by the Initial Developer are Copyright (C) 2007 michael@0: * the Initial Developer. All Rights Reserved. michael@0: * michael@0: * Contributor(s): michael@0: * Atul Varma michael@0: * michael@0: * Alternatively, the contents of this file may be used under the terms of michael@0: * either the GNU General Public License Version 2 or later (the "GPL"), or michael@0: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), michael@0: * in which case the provisions of the GPL or the LGPL are applicable instead michael@0: * of those above. If you wish to allow use of your version of this file only michael@0: * under the terms of either the GPL or the LGPL, and not to allow others to michael@0: * use your version of this file under the terms of the MPL, indicate your michael@0: * decision by deleting the provisions above and replace them with the notice michael@0: * and other provisions required by the GPL or the LGPL. If you do not delete michael@0: * the provisions above, a recipient may use your version of this file under michael@0: * the terms of any one of the MPL, the GPL or the LGPL. michael@0: * michael@0: * ***** END LICENSE BLOCK ***** */ michael@0: michael@0: (function(global) { michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: var exports = {}; michael@0: michael@0: var ios = Cc['@mozilla.org/network/io-service;1'] michael@0: .getService(Ci.nsIIOService); michael@0: michael@0: var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"] michael@0: .createInstance(Ci.nsIPrincipal); michael@0: michael@0: function resolvePrincipal(principal, defaultPrincipal) { michael@0: if (principal === undefined) michael@0: return defaultPrincipal; michael@0: if (principal == "system") michael@0: return systemPrincipal; michael@0: return principal; michael@0: } michael@0: michael@0: // The base URI to we use when we're given relative URLs, if any. michael@0: var baseURI = null; michael@0: if (global.window) michael@0: baseURI = ios.newURI(global.location.href, null, null); michael@0: exports.baseURI = baseURI; michael@0: michael@0: // The "parent" chrome URI to use if we're loading code that michael@0: // needs chrome privileges but may not have a filename that michael@0: // matches any of SpiderMonkey's defined system filename prefixes. michael@0: // The latter is needed so that wrappers can be automatically michael@0: // made for the code. For more information on this, see michael@0: // bug 418356: michael@0: // michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=418356 michael@0: var parentChromeURIString; michael@0: if (baseURI) michael@0: // We're being loaded from a chrome-privileged document, so michael@0: // use its URL as the parent string. michael@0: parentChromeURIString = baseURI.spec; michael@0: else michael@0: // We're being loaded from a chrome-privileged JS module or michael@0: // SecurableModule, so use its filename (which may itself michael@0: // contain a reference to a parent). michael@0: parentChromeURIString = Components.stack.filename; michael@0: michael@0: function maybeParentifyFilename(filename) { michael@0: var doParentifyFilename = true; michael@0: try { michael@0: // TODO: Ideally we should just make michael@0: // nsIChromeRegistry.wrappersEnabled() available from script michael@0: // and use it here. Until that's in the platform, though, michael@0: // we'll play it safe and parentify the filename unless michael@0: // we're absolutely certain things will be ok if we don't. michael@0: var filenameURI = ios.newURI(options.filename, michael@0: null, michael@0: baseURI); michael@0: if (filenameURI.scheme == 'chrome' && michael@0: filenameURI.path.indexOf('/content/') == 0) michael@0: // Content packages will always have wrappers made for them; michael@0: // if automatic wrappers have been disabled for the michael@0: // chrome package via a chrome manifest flag, then michael@0: // this still works too, to the extent that the michael@0: // content package is insecure anyways. michael@0: doParentifyFilename = false; michael@0: } catch (e) {} michael@0: if (doParentifyFilename) michael@0: return parentChromeURIString + " -> " + filename; michael@0: return filename; michael@0: } michael@0: michael@0: function getRootDir(urlStr) { michael@0: // TODO: This feels hacky, and like there will be edge cases. michael@0: return urlStr.slice(0, urlStr.lastIndexOf("/") + 1); michael@0: } michael@0: michael@0: exports.SandboxFactory = function SandboxFactory(defaultPrincipal) { michael@0: // Unless specified otherwise, use a principal with limited michael@0: // privileges. michael@0: this._defaultPrincipal = resolvePrincipal(defaultPrincipal, michael@0: "http://www.mozilla.org"); michael@0: }, michael@0: michael@0: exports.SandboxFactory.prototype = { michael@0: createSandbox: function createSandbox(options) { michael@0: var principal = resolvePrincipal(options.principal, michael@0: this._defaultPrincipal); michael@0: michael@0: return { michael@0: _sandbox: new Cu.Sandbox(principal), michael@0: _principal: principal, michael@0: get globalScope() { michael@0: return this._sandbox; michael@0: }, michael@0: defineProperty: function defineProperty(name, value) { michael@0: this._sandbox[name] = value; michael@0: }, michael@0: getProperty: function getProperty(name) { michael@0: return this._sandbox[name]; michael@0: }, michael@0: evaluate: function evaluate(options) { michael@0: if (typeof(options) == 'string') michael@0: options = {contents: options}; michael@0: options = {__proto__: options}; michael@0: if (typeof(options.contents) != 'string') michael@0: throw new Error('Expected string for options.contents'); michael@0: if (options.lineNo === undefined) michael@0: options.lineNo = 1; michael@0: if (options.jsVersion === undefined) michael@0: options.jsVersion = "1.8"; michael@0: if (typeof(options.filename) != 'string') michael@0: options.filename = ''; michael@0: michael@0: if (this._principal == systemPrincipal) michael@0: options.filename = maybeParentifyFilename(options.filename); michael@0: michael@0: return Cu.evalInSandbox(options.contents, michael@0: this._sandbox, michael@0: options.jsVersion, michael@0: options.filename, michael@0: options.lineNo); michael@0: } michael@0: }; michael@0: } michael@0: }; michael@0: michael@0: exports.Loader = function Loader(options) { michael@0: options = {__proto__: options}; michael@0: if (options.fs === undefined) { michael@0: var rootPaths = options.rootPath || options.rootPaths; michael@0: if (rootPaths) { michael@0: if (rootPaths.constructor.name != "Array") michael@0: rootPaths = [rootPaths]; michael@0: var fses = [new exports.LocalFileSystem(path) michael@0: for each (path in rootPaths)]; michael@0: options.fs = new exports.CompositeFileSystem(fses); michael@0: } else michael@0: options.fs = new exports.LocalFileSystem(); michael@0: } michael@0: if (options.sandboxFactory === undefined) michael@0: options.sandboxFactory = new exports.SandboxFactory( michael@0: options.defaultPrincipal michael@0: ); michael@0: if (options.modules === undefined) michael@0: options.modules = {}; michael@0: if (options.globals === undefined) michael@0: options.globals = {}; michael@0: michael@0: this.fs = options.fs; michael@0: this.sandboxFactory = options.sandboxFactory; michael@0: this.sandboxes = {}; michael@0: this.modules = options.modules; michael@0: this.globals = options.globals; michael@0: }; michael@0: michael@0: exports.Loader.prototype = { michael@0: _makeRequire: function _makeRequire(rootDir) { michael@0: var self = this; michael@0: return function require(module) { michael@0: if (module == "chrome") { michael@0: var chrome = { Cc: Components.classes, michael@0: Ci: Components.interfaces, michael@0: Cu: Components.utils, michael@0: Cr: Components.results, michael@0: Cm: Components.manager, michael@0: components: Components michael@0: }; michael@0: return chrome; michael@0: } michael@0: var path = self.fs.resolveModule(rootDir, module); michael@0: if (!path) michael@0: throw new Error('Module "' + module + '" not found'); michael@0: if (!(path in self.modules)) { michael@0: var options = self.fs.getFile(path); michael@0: if (options.filename === undefined) michael@0: options.filename = path; michael@0: michael@0: var exports = {}; michael@0: var sandbox = self.sandboxFactory.createSandbox(options); michael@0: self.sandboxes[path] = sandbox; michael@0: for (name in self.globals) michael@0: sandbox.defineProperty(name, self.globals[name]); michael@0: sandbox.defineProperty('require', self._makeRequire(path)); michael@0: sandbox.evaluate("var exports = {};"); michael@0: let ES5 = self.modules.es5; michael@0: if (ES5) { michael@0: let { Object, Array, Function } = sandbox.globalScope; michael@0: ES5.init(Object, Array, Function); michael@0: } michael@0: self.modules[path] = sandbox.getProperty("exports"); michael@0: sandbox.evaluate(options); michael@0: } michael@0: return self.modules[path]; michael@0: }; michael@0: }, michael@0: michael@0: // This is only really used by unit tests and other michael@0: // development-related facilities, allowing access to symbols michael@0: // defined in the global scope of a module. michael@0: findSandboxForModule: function findSandboxForModule(module) { michael@0: var path = this.fs.resolveModule(null, module); michael@0: if (!path) michael@0: throw new Error('Module "' + module + '" not found'); michael@0: if (!(path in this.sandboxes)) michael@0: this.require(module); michael@0: if (!(path in this.sandboxes)) michael@0: throw new Error('Internal error: path not in sandboxes: ' + michael@0: path); michael@0: return this.sandboxes[path]; michael@0: }, michael@0: michael@0: require: function require(module) { michael@0: return (this._makeRequire(null))(module); michael@0: }, michael@0: michael@0: runScript: function runScript(options, extraOutput) { michael@0: if (typeof(options) == 'string') michael@0: options = {contents: options}; michael@0: options = {__proto__: options}; michael@0: var sandbox = this.sandboxFactory.createSandbox(options); michael@0: if (extraOutput) michael@0: extraOutput.sandbox = sandbox; michael@0: for (name in this.globals) michael@0: sandbox.defineProperty(name, this.globals[name]); michael@0: sandbox.defineProperty('require', this._makeRequire(null)); michael@0: return sandbox.evaluate(options); michael@0: } michael@0: }; michael@0: michael@0: exports.CompositeFileSystem = function CompositeFileSystem(fses) { michael@0: this.fses = fses; michael@0: this._pathMap = {}; michael@0: }; michael@0: michael@0: exports.CompositeFileSystem.prototype = { michael@0: resolveModule: function resolveModule(base, path) { michael@0: for (var i = 0; i < this.fses.length; i++) { michael@0: var fs = this.fses[i]; michael@0: var absPath = fs.resolveModule(base, path); michael@0: if (absPath) { michael@0: this._pathMap[absPath] = fs; michael@0: return absPath; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: getFile: function getFile(path) { michael@0: return this._pathMap[path].getFile(path); michael@0: } michael@0: }; michael@0: michael@0: exports.LocalFileSystem = function LocalFileSystem(root) { michael@0: if (root === undefined) { michael@0: if (!baseURI) michael@0: throw new Error("Need a root path for module filesystem"); michael@0: root = baseURI; michael@0: } michael@0: if (typeof(root) == 'string') michael@0: root = ios.newURI(root, null, baseURI); michael@0: if (root instanceof Ci.nsIFile) michael@0: root = ios.newFileURI(root); michael@0: if (!(root instanceof Ci.nsIURI)) michael@0: throw new Error('Expected nsIFile, nsIURI, or string for root'); michael@0: michael@0: this.root = root.spec; michael@0: this._rootURI = root; michael@0: this._rootURIDir = getRootDir(root.spec); michael@0: }; michael@0: michael@0: exports.LocalFileSystem.prototype = { michael@0: resolveModule: function resolveModule(base, path) { michael@0: path = path + ".js"; michael@0: michael@0: var baseURI; michael@0: if (!base) michael@0: baseURI = this._rootURI; michael@0: else michael@0: baseURI = ios.newURI(base, null, null); michael@0: var newURI = ios.newURI(path, null, baseURI); michael@0: var channel = ios.newChannelFromURI(newURI); michael@0: try { michael@0: channel.open().close(); michael@0: } catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { michael@0: return null; michael@0: } michael@0: return newURI.spec; michael@0: }, michael@0: getFile: function getFile(path) { michael@0: var channel = ios.newChannel(path, null, null); michael@0: var iStream = channel.open(); michael@0: var ciStream = Cc["@mozilla.org/intl/converter-input-stream;1"]. michael@0: createInstance(Ci.nsIConverterInputStream); michael@0: var bufLen = 0x8000; michael@0: ciStream.init(iStream, "UTF-8", bufLen, michael@0: Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); michael@0: var chunk = {}; michael@0: var data = ""; michael@0: while (ciStream.readString(bufLen, chunk) > 0) michael@0: data += chunk.value; michael@0: ciStream.close(); michael@0: iStream.close(); michael@0: return {contents: data}; michael@0: } michael@0: }; michael@0: michael@0: if (global.window) { michael@0: // We're being loaded in a chrome window, or a web page with michael@0: // UniversalXPConnect privileges. michael@0: global.SecurableModule = exports; michael@0: } else if (global.exports) { michael@0: // We're being loaded in a SecurableModule. michael@0: for (name in exports) { michael@0: global.exports[name] = exports[name]; michael@0: } michael@0: } else { michael@0: // We're being loaded in a JS module. michael@0: global.EXPORTED_SYMBOLS = []; michael@0: for (name in exports) { michael@0: global.EXPORTED_SYMBOLS.push(name); michael@0: global[name] = exports[name]; michael@0: } michael@0: } michael@0: })(this);