Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | /* vim: set ts=8 sts=4 et sw=4 tw=99: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include "mozJSSubScriptLoader.h" |
michael@0 | 8 | #include "mozJSComponentLoader.h" |
michael@0 | 9 | #include "mozJSLoaderUtils.h" |
michael@0 | 10 | |
michael@0 | 11 | #include "nsIURI.h" |
michael@0 | 12 | #include "nsIIOService.h" |
michael@0 | 13 | #include "nsIChannel.h" |
michael@0 | 14 | #include "nsIInputStream.h" |
michael@0 | 15 | #include "nsNetCID.h" |
michael@0 | 16 | #include "nsNetUtil.h" |
michael@0 | 17 | #include "nsIFileURL.h" |
michael@0 | 18 | #include "nsScriptLoader.h" |
michael@0 | 19 | #include "nsIScriptSecurityManager.h" |
michael@0 | 20 | #include "nsThreadUtils.h" |
michael@0 | 21 | |
michael@0 | 22 | #include "jsapi.h" |
michael@0 | 23 | #include "jsfriendapi.h" |
michael@0 | 24 | #include "js/OldDebugAPI.h" |
michael@0 | 25 | #include "nsJSPrincipals.h" |
michael@0 | 26 | #include "xpcpublic.h" // For xpc::SystemErrorReporter |
michael@0 | 27 | #include "xpcprivate.h" // For xpc::OptionsBase |
michael@0 | 28 | #include "jswrapper.h" |
michael@0 | 29 | |
michael@0 | 30 | #include "mozilla/scache/StartupCache.h" |
michael@0 | 31 | #include "mozilla/scache/StartupCacheUtils.h" |
michael@0 | 32 | #include "mozilla/unused.h" |
michael@0 | 33 | |
michael@0 | 34 | using namespace mozilla::scache; |
michael@0 | 35 | using namespace JS; |
michael@0 | 36 | using namespace xpc; |
michael@0 | 37 | using namespace mozilla; |
michael@0 | 38 | |
michael@0 | 39 | class MOZ_STACK_CLASS LoadSubScriptOptions : public OptionsBase { |
michael@0 | 40 | public: |
michael@0 | 41 | LoadSubScriptOptions(JSContext *cx = xpc_GetSafeJSContext(), |
michael@0 | 42 | JSObject *options = nullptr) |
michael@0 | 43 | : OptionsBase(cx, options) |
michael@0 | 44 | , target(cx) |
michael@0 | 45 | , charset(NullString()) |
michael@0 | 46 | , ignoreCache(false) |
michael@0 | 47 | { } |
michael@0 | 48 | |
michael@0 | 49 | virtual bool Parse() { |
michael@0 | 50 | return ParseObject("target", &target) && |
michael@0 | 51 | ParseString("charset", charset) && |
michael@0 | 52 | ParseBoolean("ignoreCache", &ignoreCache); |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | RootedObject target; |
michael@0 | 56 | nsString charset; |
michael@0 | 57 | bool ignoreCache; |
michael@0 | 58 | }; |
michael@0 | 59 | |
michael@0 | 60 | |
michael@0 | 61 | /* load() error msgs, XXX localize? */ |
michael@0 | 62 | #define LOAD_ERROR_NOSERVICE "Error creating IO Service." |
michael@0 | 63 | #define LOAD_ERROR_NOURI "Error creating URI (invalid URL scheme?)" |
michael@0 | 64 | #define LOAD_ERROR_NOSCHEME "Failed to get URI scheme. This is bad." |
michael@0 | 65 | #define LOAD_ERROR_URI_NOT_LOCAL "Trying to load a non-local URI." |
michael@0 | 66 | #define LOAD_ERROR_NOSTREAM "Error opening input stream (invalid filename?)" |
michael@0 | 67 | #define LOAD_ERROR_NOCONTENT "ContentLength not available (not a local URL?)" |
michael@0 | 68 | #define LOAD_ERROR_BADCHARSET "Error converting to specified charset" |
michael@0 | 69 | #define LOAD_ERROR_BADREAD "File Read Error." |
michael@0 | 70 | #define LOAD_ERROR_READUNDERFLOW "File Read Error (underflow.)" |
michael@0 | 71 | #define LOAD_ERROR_NOPRINCIPALS "Failed to get principals." |
michael@0 | 72 | #define LOAD_ERROR_NOSPEC "Failed to get URI spec. This is bad." |
michael@0 | 73 | #define LOAD_ERROR_CONTENTTOOBIG "ContentLength is too large" |
michael@0 | 74 | |
michael@0 | 75 | mozJSSubScriptLoader::mozJSSubScriptLoader() : mSystemPrincipal(nullptr) |
michael@0 | 76 | { |
michael@0 | 77 | // Force construction of the JS component loader. We may need it later. |
michael@0 | 78 | nsCOMPtr<xpcIJSModuleLoader> componentLoader = |
michael@0 | 79 | do_GetService(MOZJSCOMPONENTLOADER_CONTRACTID); |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | mozJSSubScriptLoader::~mozJSSubScriptLoader() |
michael@0 | 83 | { |
michael@0 | 84 | /* empty */ |
michael@0 | 85 | } |
michael@0 | 86 | |
michael@0 | 87 | NS_IMPL_ISUPPORTS(mozJSSubScriptLoader, mozIJSSubScriptLoader) |
michael@0 | 88 | |
michael@0 | 89 | static nsresult |
michael@0 | 90 | ReportError(JSContext *cx, const char *msg) |
michael@0 | 91 | { |
michael@0 | 92 | RootedValue exn(cx, JS::StringValue(JS_NewStringCopyZ(cx, msg))); |
michael@0 | 93 | JS_SetPendingException(cx, exn); |
michael@0 | 94 | return NS_OK; |
michael@0 | 95 | } |
michael@0 | 96 | |
michael@0 | 97 | nsresult |
michael@0 | 98 | mozJSSubScriptLoader::ReadScript(nsIURI *uri, JSContext *cx, JSObject *targetObjArg, |
michael@0 | 99 | const nsAString &charset, const char *uriStr, |
michael@0 | 100 | nsIIOService *serv, nsIPrincipal *principal, |
michael@0 | 101 | bool reuseGlobal, JS::MutableHandleScript script, |
michael@0 | 102 | JS::MutableHandleFunction function) |
michael@0 | 103 | { |
michael@0 | 104 | RootedObject target_obj(cx, targetObjArg); |
michael@0 | 105 | |
michael@0 | 106 | script.set(nullptr); |
michael@0 | 107 | function.set(nullptr); |
michael@0 | 108 | |
michael@0 | 109 | // Instead of calling NS_OpenURI, we create the channel ourselves and call |
michael@0 | 110 | // SetContentType, to avoid expensive MIME type lookups (bug 632490). |
michael@0 | 111 | nsCOMPtr<nsIChannel> chan; |
michael@0 | 112 | nsCOMPtr<nsIInputStream> instream; |
michael@0 | 113 | nsresult rv = NS_NewChannel(getter_AddRefs(chan), uri, serv, |
michael@0 | 114 | nullptr, nullptr, nsIRequest::LOAD_NORMAL); |
michael@0 | 115 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 116 | chan->SetContentType(NS_LITERAL_CSTRING("application/javascript")); |
michael@0 | 117 | rv = chan->Open(getter_AddRefs(instream)); |
michael@0 | 118 | } |
michael@0 | 119 | |
michael@0 | 120 | if (NS_FAILED(rv)) { |
michael@0 | 121 | return ReportError(cx, LOAD_ERROR_NOSTREAM); |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | int64_t len = -1; |
michael@0 | 125 | |
michael@0 | 126 | rv = chan->GetContentLength(&len); |
michael@0 | 127 | if (NS_FAILED(rv) || len == -1) { |
michael@0 | 128 | return ReportError(cx, LOAD_ERROR_NOCONTENT); |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | if (len > INT32_MAX) { |
michael@0 | 132 | return ReportError(cx, LOAD_ERROR_CONTENTTOOBIG); |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | nsCString buf; |
michael@0 | 136 | rv = NS_ReadInputStreamToString(instream, buf, len); |
michael@0 | 137 | if (NS_FAILED(rv)) |
michael@0 | 138 | return rv; |
michael@0 | 139 | |
michael@0 | 140 | /* set our own error reporter so we can report any bad things as catchable |
michael@0 | 141 | * exceptions, including the source/line number */ |
michael@0 | 142 | JSErrorReporter er = JS_SetErrorReporter(cx, xpc::SystemErrorReporter); |
michael@0 | 143 | |
michael@0 | 144 | JS::CompileOptions options(cx); |
michael@0 | 145 | options.setFileAndLine(uriStr, 1); |
michael@0 | 146 | if (!charset.IsVoid()) { |
michael@0 | 147 | jschar *scriptBuf = nullptr; |
michael@0 | 148 | size_t scriptLength = 0; |
michael@0 | 149 | |
michael@0 | 150 | rv = nsScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf.get()), len, |
michael@0 | 151 | charset, nullptr, scriptBuf, scriptLength); |
michael@0 | 152 | |
michael@0 | 153 | JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength, |
michael@0 | 154 | JS::SourceBufferHolder::GiveOwnership); |
michael@0 | 155 | |
michael@0 | 156 | if (NS_FAILED(rv)) { |
michael@0 | 157 | return ReportError(cx, LOAD_ERROR_BADCHARSET); |
michael@0 | 158 | } |
michael@0 | 159 | |
michael@0 | 160 | if (!reuseGlobal) { |
michael@0 | 161 | JS::Compile(cx, target_obj, options, srcBuf, script); |
michael@0 | 162 | } else { |
michael@0 | 163 | JS::CompileFunction(cx, target_obj, options, |
michael@0 | 164 | nullptr, 0, nullptr, |
michael@0 | 165 | srcBuf, |
michael@0 | 166 | function); |
michael@0 | 167 | } |
michael@0 | 168 | } else { |
michael@0 | 169 | // We only use lazy source when no special encoding is specified because |
michael@0 | 170 | // the lazy source loader doesn't know the encoding. |
michael@0 | 171 | if (!reuseGlobal) { |
michael@0 | 172 | options.setSourceIsLazy(true); |
michael@0 | 173 | script.set(JS::Compile(cx, target_obj, options, buf.get(), len)); |
michael@0 | 174 | } else { |
michael@0 | 175 | function.set(JS::CompileFunction(cx, target_obj, options, |
michael@0 | 176 | nullptr, 0, nullptr, buf.get(), |
michael@0 | 177 | len)); |
michael@0 | 178 | } |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | /* repent for our evil deeds */ |
michael@0 | 182 | JS_SetErrorReporter(cx, er); |
michael@0 | 183 | |
michael@0 | 184 | return NS_OK; |
michael@0 | 185 | } |
michael@0 | 186 | |
michael@0 | 187 | NS_IMETHODIMP |
michael@0 | 188 | mozJSSubScriptLoader::LoadSubScript(const nsAString &url, |
michael@0 | 189 | HandleValue target, |
michael@0 | 190 | const nsAString &charset, |
michael@0 | 191 | JSContext *cx, |
michael@0 | 192 | MutableHandleValue retval) |
michael@0 | 193 | { |
michael@0 | 194 | /* |
michael@0 | 195 | * Loads a local url and evals it into the current cx |
michael@0 | 196 | * Synchronous (an async version would be cool too.) |
michael@0 | 197 | * url: The url to load. Must be local so that it can be loaded |
michael@0 | 198 | * synchronously. |
michael@0 | 199 | * target_obj: Optional object to eval the script onto (defaults to context |
michael@0 | 200 | * global) |
michael@0 | 201 | * charset: Optional character set to use for reading |
michael@0 | 202 | * returns: Whatever jsval the script pointed to by the url returns. |
michael@0 | 203 | * Should ONLY (O N L Y !) be called from JavaScript code. |
michael@0 | 204 | */ |
michael@0 | 205 | LoadSubScriptOptions options(cx); |
michael@0 | 206 | options.charset = charset; |
michael@0 | 207 | options.target = target.isObject() ? &target.toObject() : nullptr; |
michael@0 | 208 | return DoLoadSubScriptWithOptions(url, options, cx, retval); |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | |
michael@0 | 212 | NS_IMETHODIMP |
michael@0 | 213 | mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString &url, |
michael@0 | 214 | HandleValue optionsVal, |
michael@0 | 215 | JSContext *cx, |
michael@0 | 216 | MutableHandleValue retval) |
michael@0 | 217 | { |
michael@0 | 218 | if (!optionsVal.isObject()) |
michael@0 | 219 | return NS_ERROR_INVALID_ARG; |
michael@0 | 220 | LoadSubScriptOptions options(cx, &optionsVal.toObject()); |
michael@0 | 221 | if (!options.Parse()) |
michael@0 | 222 | return NS_ERROR_INVALID_ARG; |
michael@0 | 223 | return DoLoadSubScriptWithOptions(url, options, cx, retval); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | nsresult |
michael@0 | 227 | mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString &url, |
michael@0 | 228 | LoadSubScriptOptions &options, |
michael@0 | 229 | JSContext *cx, |
michael@0 | 230 | MutableHandleValue retval) |
michael@0 | 231 | { |
michael@0 | 232 | nsresult rv = NS_OK; |
michael@0 | 233 | |
michael@0 | 234 | /* set the system principal if it's not here already */ |
michael@0 | 235 | if (!mSystemPrincipal) { |
michael@0 | 236 | nsCOMPtr<nsIScriptSecurityManager> secman = |
michael@0 | 237 | do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); |
michael@0 | 238 | if (!secman) |
michael@0 | 239 | return NS_OK; |
michael@0 | 240 | |
michael@0 | 241 | rv = secman->GetSystemPrincipal(getter_AddRefs(mSystemPrincipal)); |
michael@0 | 242 | if (NS_FAILED(rv) || !mSystemPrincipal) |
michael@0 | 243 | return rv; |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | RootedObject targetObj(cx); |
michael@0 | 247 | mozJSComponentLoader *loader = mozJSComponentLoader::Get(); |
michael@0 | 248 | rv = loader->FindTargetObject(cx, &targetObj); |
michael@0 | 249 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 250 | |
michael@0 | 251 | // We base reusingGlobal off of what the loader told us, but we may not |
michael@0 | 252 | // actually be using that object. |
michael@0 | 253 | bool reusingGlobal = !JS_IsGlobalObject(targetObj); |
michael@0 | 254 | |
michael@0 | 255 | if (options.target) |
michael@0 | 256 | targetObj = options.target; |
michael@0 | 257 | |
michael@0 | 258 | // Remember an object out of the calling compartment so that we |
michael@0 | 259 | // can properly wrap the result later. |
michael@0 | 260 | nsCOMPtr<nsIPrincipal> principal = mSystemPrincipal; |
michael@0 | 261 | RootedObject result_obj(cx, targetObj); |
michael@0 | 262 | targetObj = JS_FindCompilationScope(cx, targetObj); |
michael@0 | 263 | if (!targetObj) |
michael@0 | 264 | return NS_ERROR_FAILURE; |
michael@0 | 265 | |
michael@0 | 266 | if (targetObj != result_obj) |
michael@0 | 267 | principal = GetObjectPrincipal(targetObj); |
michael@0 | 268 | |
michael@0 | 269 | JSAutoCompartment ac(cx, targetObj); |
michael@0 | 270 | |
michael@0 | 271 | /* load up the url. From here on, failures are reflected as ``custom'' |
michael@0 | 272 | * js exceptions */ |
michael@0 | 273 | nsCOMPtr<nsIURI> uri; |
michael@0 | 274 | nsAutoCString uriStr; |
michael@0 | 275 | nsAutoCString scheme; |
michael@0 | 276 | |
michael@0 | 277 | // Figure out who's calling us |
michael@0 | 278 | JS::AutoFilename filename; |
michael@0 | 279 | if (!JS::DescribeScriptedCaller(cx, &filename)) { |
michael@0 | 280 | // No scripted frame means we don't know who's calling, bail. |
michael@0 | 281 | return NS_ERROR_FAILURE; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | // Suppress caching if we're compiling as content. |
michael@0 | 285 | StartupCache* cache = (principal == mSystemPrincipal) |
michael@0 | 286 | ? StartupCache::GetSingleton() |
michael@0 | 287 | : nullptr; |
michael@0 | 288 | nsCOMPtr<nsIIOService> serv = do_GetService(NS_IOSERVICE_CONTRACTID); |
michael@0 | 289 | if (!serv) { |
michael@0 | 290 | return ReportError(cx, LOAD_ERROR_NOSERVICE); |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | // Make sure to explicitly create the URI, since we'll need the |
michael@0 | 294 | // canonicalized spec. |
michael@0 | 295 | rv = NS_NewURI(getter_AddRefs(uri), NS_LossyConvertUTF16toASCII(url).get(), nullptr, serv); |
michael@0 | 296 | if (NS_FAILED(rv)) { |
michael@0 | 297 | return ReportError(cx, LOAD_ERROR_NOURI); |
michael@0 | 298 | } |
michael@0 | 299 | |
michael@0 | 300 | rv = uri->GetSpec(uriStr); |
michael@0 | 301 | if (NS_FAILED(rv)) { |
michael@0 | 302 | return ReportError(cx, LOAD_ERROR_NOSPEC); |
michael@0 | 303 | } |
michael@0 | 304 | |
michael@0 | 305 | rv = uri->GetScheme(scheme); |
michael@0 | 306 | if (NS_FAILED(rv)) { |
michael@0 | 307 | return ReportError(cx, LOAD_ERROR_NOSCHEME); |
michael@0 | 308 | } |
michael@0 | 309 | |
michael@0 | 310 | if (!scheme.EqualsLiteral("chrome")) { |
michael@0 | 311 | // This might be a URI to a local file, though! |
michael@0 | 312 | nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri); |
michael@0 | 313 | nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(innerURI); |
michael@0 | 314 | if (!fileURL) { |
michael@0 | 315 | return ReportError(cx, LOAD_ERROR_URI_NOT_LOCAL); |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | // For file URIs prepend the filename with the filename of the |
michael@0 | 319 | // calling script, and " -> ". See bug 418356. |
michael@0 | 320 | nsAutoCString tmp(filename.get()); |
michael@0 | 321 | tmp.AppendLiteral(" -> "); |
michael@0 | 322 | tmp.Append(uriStr); |
michael@0 | 323 | |
michael@0 | 324 | uriStr = tmp; |
michael@0 | 325 | } |
michael@0 | 326 | |
michael@0 | 327 | bool writeScript = false; |
michael@0 | 328 | JSVersion version = JS_GetVersion(cx); |
michael@0 | 329 | nsAutoCString cachePath; |
michael@0 | 330 | cachePath.AppendPrintf("jssubloader/%d", version); |
michael@0 | 331 | PathifyURI(uri, cachePath); |
michael@0 | 332 | |
michael@0 | 333 | RootedFunction function(cx); |
michael@0 | 334 | RootedScript script(cx); |
michael@0 | 335 | if (cache && !options.ignoreCache) |
michael@0 | 336 | rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script); |
michael@0 | 337 | if (!script) { |
michael@0 | 338 | rv = ReadScript(uri, cx, targetObj, options.charset, |
michael@0 | 339 | static_cast<const char*>(uriStr.get()), serv, |
michael@0 | 340 | principal, reusingGlobal, &script, &function); |
michael@0 | 341 | writeScript = !!script; |
michael@0 | 342 | } |
michael@0 | 343 | |
michael@0 | 344 | if (NS_FAILED(rv) || (!script && !function)) |
michael@0 | 345 | return rv; |
michael@0 | 346 | |
michael@0 | 347 | if (function) { |
michael@0 | 348 | script = JS_GetFunctionScript(cx, function); |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | loader->NoteSubScript(script, targetObj); |
michael@0 | 352 | |
michael@0 | 353 | |
michael@0 | 354 | bool ok = false; |
michael@0 | 355 | if (function) { |
michael@0 | 356 | ok = JS_CallFunction(cx, targetObj, function, JS::HandleValueArray::empty(), |
michael@0 | 357 | retval); |
michael@0 | 358 | } else { |
michael@0 | 359 | ok = JS_ExecuteScriptVersion(cx, targetObj, script, retval, version); |
michael@0 | 360 | } |
michael@0 | 361 | |
michael@0 | 362 | if (ok) { |
michael@0 | 363 | JSAutoCompartment rac(cx, result_obj); |
michael@0 | 364 | if (!JS_WrapValue(cx, retval)) |
michael@0 | 365 | return NS_ERROR_UNEXPECTED; |
michael@0 | 366 | } |
michael@0 | 367 | |
michael@0 | 368 | if (cache && ok && writeScript) { |
michael@0 | 369 | WriteCachedScript(cache, cachePath, cx, mSystemPrincipal, script); |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | return NS_OK; |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | /** |
michael@0 | 376 | * Let us compile scripts from a URI off the main thread. |
michael@0 | 377 | */ |
michael@0 | 378 | |
michael@0 | 379 | class ScriptPrecompiler : public nsIStreamLoaderObserver |
michael@0 | 380 | { |
michael@0 | 381 | public: |
michael@0 | 382 | NS_DECL_ISUPPORTS |
michael@0 | 383 | NS_DECL_NSISTREAMLOADEROBSERVER |
michael@0 | 384 | |
michael@0 | 385 | ScriptPrecompiler(nsIObserver* aObserver, |
michael@0 | 386 | nsIPrincipal* aPrincipal, |
michael@0 | 387 | nsIChannel* aChannel) |
michael@0 | 388 | : mObserver(aObserver) |
michael@0 | 389 | , mPrincipal(aPrincipal) |
michael@0 | 390 | , mChannel(aChannel) |
michael@0 | 391 | , mScriptBuf(nullptr) |
michael@0 | 392 | , mScriptLength(0) |
michael@0 | 393 | {} |
michael@0 | 394 | |
michael@0 | 395 | virtual ~ScriptPrecompiler() |
michael@0 | 396 | { |
michael@0 | 397 | if (mScriptBuf) { |
michael@0 | 398 | js_free(mScriptBuf); |
michael@0 | 399 | } |
michael@0 | 400 | } |
michael@0 | 401 | |
michael@0 | 402 | static void OffThreadCallback(void *aToken, void *aData); |
michael@0 | 403 | |
michael@0 | 404 | /* Sends the "done" notification back. Main thread only. */ |
michael@0 | 405 | void SendObserverNotification(); |
michael@0 | 406 | |
michael@0 | 407 | private: |
michael@0 | 408 | nsRefPtr<nsIObserver> mObserver; |
michael@0 | 409 | nsRefPtr<nsIPrincipal> mPrincipal; |
michael@0 | 410 | nsRefPtr<nsIChannel> mChannel; |
michael@0 | 411 | jschar* mScriptBuf; |
michael@0 | 412 | size_t mScriptLength; |
michael@0 | 413 | }; |
michael@0 | 414 | |
michael@0 | 415 | NS_IMPL_ISUPPORTS(ScriptPrecompiler, nsIStreamLoaderObserver); |
michael@0 | 416 | |
michael@0 | 417 | class NotifyPrecompilationCompleteRunnable : public nsRunnable |
michael@0 | 418 | { |
michael@0 | 419 | public: |
michael@0 | 420 | NS_DECL_NSIRUNNABLE |
michael@0 | 421 | |
michael@0 | 422 | NotifyPrecompilationCompleteRunnable(ScriptPrecompiler* aPrecompiler) |
michael@0 | 423 | : mPrecompiler(aPrecompiler) |
michael@0 | 424 | , mToken(nullptr) |
michael@0 | 425 | {} |
michael@0 | 426 | |
michael@0 | 427 | void SetToken(void* aToken) { |
michael@0 | 428 | MOZ_ASSERT(aToken && !mToken); |
michael@0 | 429 | mToken = aToken; |
michael@0 | 430 | } |
michael@0 | 431 | |
michael@0 | 432 | protected: |
michael@0 | 433 | nsRefPtr<ScriptPrecompiler> mPrecompiler; |
michael@0 | 434 | void* mToken; |
michael@0 | 435 | }; |
michael@0 | 436 | |
michael@0 | 437 | /* RAII helper class to send observer notifications */ |
michael@0 | 438 | class AutoSendObserverNotification { |
michael@0 | 439 | public: |
michael@0 | 440 | AutoSendObserverNotification(ScriptPrecompiler* aPrecompiler) |
michael@0 | 441 | : mPrecompiler(aPrecompiler) |
michael@0 | 442 | {} |
michael@0 | 443 | |
michael@0 | 444 | ~AutoSendObserverNotification() { |
michael@0 | 445 | if (mPrecompiler) { |
michael@0 | 446 | mPrecompiler->SendObserverNotification(); |
michael@0 | 447 | } |
michael@0 | 448 | } |
michael@0 | 449 | |
michael@0 | 450 | void Disarm() { |
michael@0 | 451 | mPrecompiler = nullptr; |
michael@0 | 452 | } |
michael@0 | 453 | |
michael@0 | 454 | private: |
michael@0 | 455 | ScriptPrecompiler* mPrecompiler; |
michael@0 | 456 | }; |
michael@0 | 457 | |
michael@0 | 458 | NS_IMETHODIMP |
michael@0 | 459 | NotifyPrecompilationCompleteRunnable::Run(void) |
michael@0 | 460 | { |
michael@0 | 461 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 462 | MOZ_ASSERT(mPrecompiler); |
michael@0 | 463 | |
michael@0 | 464 | AutoSendObserverNotification notifier(mPrecompiler); |
michael@0 | 465 | |
michael@0 | 466 | if (mToken) { |
michael@0 | 467 | JSRuntime *rt = XPCJSRuntime::Get()->Runtime(); |
michael@0 | 468 | NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE); |
michael@0 | 469 | JS::FinishOffThreadScript(nullptr, rt, mToken); |
michael@0 | 470 | } |
michael@0 | 471 | |
michael@0 | 472 | return NS_OK; |
michael@0 | 473 | } |
michael@0 | 474 | |
michael@0 | 475 | NS_IMETHODIMP |
michael@0 | 476 | ScriptPrecompiler::OnStreamComplete(nsIStreamLoader* aLoader, |
michael@0 | 477 | nsISupports* aContext, |
michael@0 | 478 | nsresult aStatus, |
michael@0 | 479 | uint32_t aLength, |
michael@0 | 480 | const uint8_t* aString) |
michael@0 | 481 | { |
michael@0 | 482 | AutoSendObserverNotification notifier(this); |
michael@0 | 483 | |
michael@0 | 484 | // Just notify that we are done with this load. |
michael@0 | 485 | NS_ENSURE_SUCCESS(aStatus, NS_OK); |
michael@0 | 486 | |
michael@0 | 487 | // Convert data to jschar* and prepare to call CompileOffThread. |
michael@0 | 488 | nsAutoString hintCharset; |
michael@0 | 489 | nsresult rv = |
michael@0 | 490 | nsScriptLoader::ConvertToUTF16(mChannel, aString, aLength, |
michael@0 | 491 | hintCharset, nullptr, |
michael@0 | 492 | mScriptBuf, mScriptLength); |
michael@0 | 493 | |
michael@0 | 494 | NS_ENSURE_SUCCESS(rv, NS_OK); |
michael@0 | 495 | |
michael@0 | 496 | // Our goal is to cache persistently the compiled script and to avoid quota |
michael@0 | 497 | // checks. Since the caching mechanism decide the persistence type based on |
michael@0 | 498 | // the principal, we create a new global with the app's principal. |
michael@0 | 499 | // We then enter its compartment to compile with its principal. |
michael@0 | 500 | AutoSafeJSContext cx; |
michael@0 | 501 | RootedValue v(cx); |
michael@0 | 502 | SandboxOptions sandboxOptions; |
michael@0 | 503 | sandboxOptions.sandboxName.AssignASCII("asm.js precompilation"); |
michael@0 | 504 | sandboxOptions.invisibleToDebugger = true; |
michael@0 | 505 | sandboxOptions.discardSource = true; |
michael@0 | 506 | rv = CreateSandboxObject(cx, &v, mPrincipal, sandboxOptions); |
michael@0 | 507 | NS_ENSURE_SUCCESS(rv, NS_OK); |
michael@0 | 508 | |
michael@0 | 509 | JSAutoCompartment ac(cx, js::UncheckedUnwrap(&v.toObject())); |
michael@0 | 510 | |
michael@0 | 511 | JS::CompileOptions options(cx, JSVERSION_DEFAULT); |
michael@0 | 512 | options.forceAsync = true; |
michael@0 | 513 | options.compileAndGo = true; |
michael@0 | 514 | options.installedFile = true; |
michael@0 | 515 | |
michael@0 | 516 | nsCOMPtr<nsIURI> uri; |
michael@0 | 517 | mChannel->GetURI(getter_AddRefs(uri)); |
michael@0 | 518 | nsAutoCString spec; |
michael@0 | 519 | uri->GetSpec(spec); |
michael@0 | 520 | options.setFile(spec.get()); |
michael@0 | 521 | |
michael@0 | 522 | if (!JS::CanCompileOffThread(cx, options, mScriptLength)) { |
michael@0 | 523 | NS_WARNING("Can't compile script off thread!"); |
michael@0 | 524 | return NS_OK; |
michael@0 | 525 | } |
michael@0 | 526 | |
michael@0 | 527 | nsRefPtr<NotifyPrecompilationCompleteRunnable> runnable = |
michael@0 | 528 | new NotifyPrecompilationCompleteRunnable(this); |
michael@0 | 529 | |
michael@0 | 530 | if (!JS::CompileOffThread(cx, options, |
michael@0 | 531 | mScriptBuf, mScriptLength, |
michael@0 | 532 | OffThreadCallback, |
michael@0 | 533 | static_cast<void*>(runnable))) { |
michael@0 | 534 | NS_WARNING("Failed to compile script off thread!"); |
michael@0 | 535 | return NS_OK; |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | unused << runnable.forget(); |
michael@0 | 539 | notifier.Disarm(); |
michael@0 | 540 | |
michael@0 | 541 | return NS_OK; |
michael@0 | 542 | } |
michael@0 | 543 | |
michael@0 | 544 | /* static */ |
michael@0 | 545 | void |
michael@0 | 546 | ScriptPrecompiler::OffThreadCallback(void* aToken, void* aData) |
michael@0 | 547 | { |
michael@0 | 548 | nsRefPtr<NotifyPrecompilationCompleteRunnable> runnable = |
michael@0 | 549 | dont_AddRef(static_cast<NotifyPrecompilationCompleteRunnable*>(aData)); |
michael@0 | 550 | runnable->SetToken(aToken); |
michael@0 | 551 | |
michael@0 | 552 | NS_DispatchToMainThread(runnable); |
michael@0 | 553 | } |
michael@0 | 554 | |
michael@0 | 555 | void |
michael@0 | 556 | ScriptPrecompiler::SendObserverNotification() |
michael@0 | 557 | { |
michael@0 | 558 | MOZ_ASSERT(mChannel && mObserver); |
michael@0 | 559 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 560 | |
michael@0 | 561 | nsCOMPtr<nsIURI> uri; |
michael@0 | 562 | mChannel->GetURI(getter_AddRefs(uri)); |
michael@0 | 563 | mObserver->Observe(uri, "script-precompiled", nullptr); |
michael@0 | 564 | } |
michael@0 | 565 | |
michael@0 | 566 | NS_IMETHODIMP |
michael@0 | 567 | mozJSSubScriptLoader::PrecompileScript(nsIURI* aURI, |
michael@0 | 568 | nsIPrincipal* aPrincipal, |
michael@0 | 569 | nsIObserver *aObserver) |
michael@0 | 570 | { |
michael@0 | 571 | nsCOMPtr<nsIChannel> channel; |
michael@0 | 572 | nsresult rv = NS_NewChannel(getter_AddRefs(channel), |
michael@0 | 573 | aURI, nullptr, nullptr, nullptr, |
michael@0 | 574 | nsIRequest::LOAD_NORMAL, nullptr); |
michael@0 | 575 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 576 | |
michael@0 | 577 | nsRefPtr<ScriptPrecompiler> loadObserver = |
michael@0 | 578 | new ScriptPrecompiler(aObserver, aPrincipal, channel); |
michael@0 | 579 | |
michael@0 | 580 | nsCOMPtr<nsIStreamLoader> loader; |
michael@0 | 581 | rv = NS_NewStreamLoader(getter_AddRefs(loader), loadObserver); |
michael@0 | 582 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 583 | |
michael@0 | 584 | nsCOMPtr<nsIStreamListener> listener = loader.get(); |
michael@0 | 585 | rv = channel->AsyncOpen(listener, nullptr); |
michael@0 | 586 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 587 | |
michael@0 | 588 | return NS_OK; |
michael@0 | 589 | } |