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