/src/mozilla-central/js/xpconnect/loader/mozJSSubScriptLoader.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* vim: set ts=8 sts=4 et sw=4 tw=99: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozJSSubScriptLoader.h" |
8 | | #include "mozJSComponentLoader.h" |
9 | | #include "mozJSLoaderUtils.h" |
10 | | |
11 | | #include "nsIURI.h" |
12 | | #include "nsIIOService.h" |
13 | | #include "nsIChannel.h" |
14 | | #include "nsIInputStream.h" |
15 | | #include "nsNetCID.h" |
16 | | #include "nsNetUtil.h" |
17 | | #include "nsIFileURL.h" |
18 | | |
19 | | #include "jsapi.h" |
20 | | #include "jsfriendapi.h" |
21 | | #include "xpcprivate.h" // For xpc::OptionsBase |
22 | | #include "js/CompilationAndEvaluation.h" |
23 | | #include "js/SourceBufferHolder.h" |
24 | | #include "js/Wrapper.h" |
25 | | |
26 | | #include "mozilla/ContentPrincipal.h" |
27 | | #include "mozilla/dom/Promise.h" |
28 | | #include "mozilla/dom/ToJSValue.h" |
29 | | #include "mozilla/dom/ScriptLoader.h" |
30 | | #include "mozilla/HoldDropJSObjects.h" |
31 | | #include "mozilla/ScriptPreloader.h" |
32 | | #include "mozilla/SystemPrincipal.h" |
33 | | #include "mozilla/scache/StartupCache.h" |
34 | | #include "mozilla/scache/StartupCacheUtils.h" |
35 | | #include "mozilla/Unused.h" |
36 | | #include "nsContentUtils.h" |
37 | | #include "nsString.h" |
38 | | #include "nsCycleCollectionParticipant.h" |
39 | | #include "GeckoProfiler.h" |
40 | | |
41 | | using namespace mozilla::scache; |
42 | | using namespace JS; |
43 | | using namespace xpc; |
44 | | using namespace mozilla; |
45 | | using namespace mozilla::dom; |
46 | | |
47 | | class MOZ_STACK_CLASS LoadSubScriptOptions : public OptionsBase { |
48 | | public: |
49 | | explicit LoadSubScriptOptions(JSContext* cx = xpc_GetSafeJSContext(), |
50 | | JSObject* options = nullptr) |
51 | | : OptionsBase(cx, options) |
52 | | , target(cx) |
53 | | , charset(VoidString()) |
54 | | , ignoreCache(false) |
55 | | , async(false) |
56 | | , wantReturnValue(false) |
57 | 0 | { } |
58 | | |
59 | 0 | virtual bool Parse() override { |
60 | 0 | return ParseObject("target", &target) && |
61 | 0 | ParseString("charset", charset) && |
62 | 0 | ParseBoolean("ignoreCache", &ignoreCache) && |
63 | 0 | ParseBoolean("async", &async) && |
64 | 0 | ParseBoolean("wantReturnValue", &wantReturnValue); |
65 | 0 | } |
66 | | |
67 | | RootedObject target; |
68 | | nsString charset; |
69 | | bool ignoreCache; |
70 | | bool async; |
71 | | bool wantReturnValue; |
72 | | }; |
73 | | |
74 | | |
75 | | /* load() error msgs, XXX localize? */ |
76 | | #define LOAD_ERROR_NOSERVICE "Error creating IO Service." |
77 | | #define LOAD_ERROR_NOURI "Error creating URI (invalid URL scheme?)" |
78 | 0 | #define LOAD_ERROR_NOSCHEME "Failed to get URI scheme. This is bad." |
79 | 0 | #define LOAD_ERROR_URI_NOT_LOCAL "Trying to load a non-local URI." |
80 | 0 | #define LOAD_ERROR_NOSTREAM "Error opening input stream (invalid filename?)" |
81 | 0 | #define LOAD_ERROR_NOCONTENT "ContentLength not available (not a local URL?)" |
82 | 0 | #define LOAD_ERROR_BADCHARSET "Error converting to specified charset" |
83 | | #define LOAD_ERROR_NOSPEC "Failed to get URI spec. This is bad." |
84 | 0 | #define LOAD_ERROR_CONTENTTOOBIG "ContentLength is too large" |
85 | | |
86 | | mozJSSubScriptLoader::mozJSSubScriptLoader() |
87 | 0 | { |
88 | 0 | } |
89 | | |
90 | | mozJSSubScriptLoader::~mozJSSubScriptLoader() |
91 | 0 | { |
92 | 0 | } |
93 | | |
94 | | NS_IMPL_ISUPPORTS(mozJSSubScriptLoader, mozIJSSubScriptLoader) |
95 | | |
96 | 0 | #define JSSUB_CACHE_PREFIX(aType) "jssubloader/" aType |
97 | | |
98 | | static void |
99 | | SubscriptCachePath(JSContext* cx, nsIURI* uri, JS::HandleObject targetObj, nsACString& cachePath) |
100 | 0 | { |
101 | 0 | // StartupCache must distinguish between non-syntactic vs global when |
102 | 0 | // computing the cache key. |
103 | 0 | if (!JS_IsGlobalObject(targetObj)) { |
104 | 0 | cachePath.AssignLiteral(JSSUB_CACHE_PREFIX("non-syntactic")); |
105 | 0 | } else { |
106 | 0 | cachePath.AssignLiteral(JSSUB_CACHE_PREFIX("global")); |
107 | 0 | } |
108 | 0 | PathifyURI(uri, cachePath); |
109 | 0 | } |
110 | | |
111 | | static void |
112 | | ReportError(JSContext* cx, const nsACString& msg) |
113 | 0 | { |
114 | 0 | NS_ConvertUTF8toUTF16 ucMsg(msg); |
115 | 0 |
|
116 | 0 | RootedValue exn(cx); |
117 | 0 | if (xpc::NonVoidStringToJsval(cx, ucMsg, &exn)) { |
118 | 0 | JS_SetPendingException(cx, exn); |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | | static void |
123 | | ReportError(JSContext* cx, const char* origMsg, nsIURI* uri) |
124 | 0 | { |
125 | 0 | if (!uri) { |
126 | 0 | ReportError(cx, nsDependentCString(origMsg)); |
127 | 0 | return; |
128 | 0 | } |
129 | 0 | |
130 | 0 | nsAutoCString spec; |
131 | 0 | nsresult rv = uri->GetSpec(spec); |
132 | 0 | if (NS_FAILED(rv)) { |
133 | 0 | spec.AssignLiteral("(unknown)"); |
134 | 0 | } |
135 | 0 |
|
136 | 0 | nsAutoCString msg(origMsg); |
137 | 0 | msg.AppendLiteral(": "); |
138 | 0 | msg.Append(spec); |
139 | 0 | ReportError(cx, msg); |
140 | 0 | } |
141 | | |
142 | | static bool |
143 | | PrepareScript(nsIURI* uri, |
144 | | JSContext* cx, |
145 | | bool wantGlobalScript, |
146 | | const char* uriStr, |
147 | | const nsAString& charset, |
148 | | const char* buf, |
149 | | int64_t len, |
150 | | bool wantReturnValue, |
151 | | MutableHandleScript script) |
152 | 0 | { |
153 | 0 | JS::CompileOptions options(cx); |
154 | 0 | options.setFileAndLine(uriStr, 1) |
155 | 0 | .setNoScriptRval(!wantReturnValue); |
156 | 0 | if (!charset.IsVoid()) { |
157 | 0 | char16_t* scriptBuf = nullptr; |
158 | 0 | size_t scriptLength = 0; |
159 | 0 |
|
160 | 0 | nsresult rv = |
161 | 0 | ScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf), len, |
162 | 0 | charset, nullptr, scriptBuf, scriptLength); |
163 | 0 |
|
164 | 0 | JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength, |
165 | 0 | JS::SourceBufferHolder::GiveOwnership); |
166 | 0 |
|
167 | 0 | if (NS_FAILED(rv)) { |
168 | 0 | ReportError(cx, LOAD_ERROR_BADCHARSET, uri); |
169 | 0 | return false; |
170 | 0 | } |
171 | 0 |
|
172 | 0 | if (wantGlobalScript) { |
173 | 0 | return JS::Compile(cx, options, srcBuf, script); |
174 | 0 | } |
175 | 0 | return JS::CompileForNonSyntacticScope(cx, options, srcBuf, script); |
176 | 0 | } |
177 | 0 | // We only use lazy source when no special encoding is specified because |
178 | 0 | // the lazy source loader doesn't know the encoding. |
179 | 0 | options.setSourceIsLazy(true); |
180 | 0 | if (wantGlobalScript) { |
181 | 0 | return JS::CompileLatin1(cx, options, buf, len, script); |
182 | 0 | } |
183 | 0 | return JS::CompileLatin1ForNonSyntacticScope(cx, options, buf, len, script); |
184 | 0 | } |
185 | | |
186 | | static bool |
187 | | EvalScript(JSContext* cx, |
188 | | HandleObject targetObj, |
189 | | HandleObject loadScope, |
190 | | MutableHandleValue retval, |
191 | | nsIURI* uri, |
192 | | bool startupCache, |
193 | | bool preloadCache, |
194 | | MutableHandleScript script) |
195 | 0 | { |
196 | 0 | MOZ_ASSERT(!js::IsWrapper(targetObj)); |
197 | 0 |
|
198 | 0 | if (JS_IsGlobalObject(targetObj)) { |
199 | 0 | if (!JS::CloneAndExecuteScript(cx, script, retval)) { |
200 | 0 | return false; |
201 | 0 | } |
202 | 0 | } else if (js::IsJSMEnvironment(targetObj)) { |
203 | 0 | if (!ExecuteInJSMEnvironment(cx, script, targetObj)) { |
204 | 0 | return false; |
205 | 0 | } |
206 | 0 | retval.setUndefined(); |
207 | 0 | } else { |
208 | 0 | JS::AutoObjectVector envChain(cx); |
209 | 0 | if (!envChain.append(targetObj)) { |
210 | 0 | return false; |
211 | 0 | } |
212 | 0 | if (!loadScope) { |
213 | 0 | // A null loadScope means we are cross-compartment. In this case, we |
214 | 0 | // should check the target isn't in the JSM loader shared-global or |
215 | 0 | // we will contaiminate all JSMs in the compartment. |
216 | 0 | // |
217 | 0 | // NOTE: If loadScope is already a shared-global JSM, we can't |
218 | 0 | // determine which JSM the target belongs to and have to assume it |
219 | 0 | // is in our JSM. |
220 | 0 | #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED |
221 | 0 | JSObject* targetGlobal = JS::GetNonCCWObjectGlobal(targetObj); |
222 | 0 | MOZ_DIAGNOSTIC_ASSERT(!mozJSComponentLoader::Get()->IsLoaderGlobal(targetGlobal), |
223 | 0 | "Don't load subscript into target in a shared-global JSM"); |
224 | 0 | #endif |
225 | 0 | if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) { |
226 | 0 | return false; |
227 | 0 | } |
228 | 0 | } else if (JS_IsGlobalObject(loadScope)) { |
229 | 0 | if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) { |
230 | 0 | return false; |
231 | 0 | } |
232 | 0 | } else { |
233 | 0 | MOZ_ASSERT(js::IsJSMEnvironment(loadScope)); |
234 | 0 | if (!js::ExecuteInJSMEnvironment(cx, script, loadScope, envChain)) { |
235 | 0 | return false; |
236 | 0 | } |
237 | 0 | retval.setUndefined(); |
238 | 0 | } |
239 | 0 | } |
240 | 0 |
|
241 | 0 | JSAutoRealm rar(cx, targetObj); |
242 | 0 | if (!JS_WrapValue(cx, retval)) { |
243 | 0 | return false; |
244 | 0 | } |
245 | 0 | |
246 | 0 | if (script && (startupCache || preloadCache)) { |
247 | 0 | nsAutoCString cachePath; |
248 | 0 | SubscriptCachePath(cx, uri, targetObj, cachePath); |
249 | 0 |
|
250 | 0 | nsCString uriStr; |
251 | 0 | if (preloadCache && NS_SUCCEEDED(uri->GetSpec(uriStr))) { |
252 | 0 | // Note that, when called during startup, this will keep the |
253 | 0 | // original JSScript object alive for an indefinite amount of time. |
254 | 0 | // This has the side-effect of keeping the global that the script |
255 | 0 | // was compiled for alive, too. |
256 | 0 | // |
257 | 0 | // For most startups, the global in question will be the |
258 | 0 | // CompilationScope, since we pre-compile any scripts that were |
259 | 0 | // needed during the last startup in that scope. But for startups |
260 | 0 | // when a non-cached script is used (e.g., after add-on |
261 | 0 | // installation), this may be a Sandbox global, which may be |
262 | 0 | // nuked but held alive by the JSScript. We can avoid this problem |
263 | 0 | // by using a different scope when compiling the script. See |
264 | 0 | // useCompilationScope in ReadScript(). |
265 | 0 | // |
266 | 0 | // In general, this isn't a problem, since add-on Sandboxes which |
267 | 0 | // use the script preloader are not destroyed until add-on shutdown, |
268 | 0 | // and when add-ons are uninstalled or upgraded, the preloader cache |
269 | 0 | // is immediately flushed after shutdown. But it's possible to |
270 | 0 | // disable and reenable an add-on without uninstalling it, leading |
271 | 0 | // to cached scripts being held alive, and tied to nuked Sandbox |
272 | 0 | // globals. Given the unusual circumstances required to trigger |
273 | 0 | // this, it's not a major concern. But it should be kept in mind. |
274 | 0 | ScriptPreloader::GetSingleton().NoteScript(uriStr, cachePath, script); |
275 | 0 | } |
276 | 0 |
|
277 | 0 | if (startupCache) { |
278 | 0 | JSAutoRealm ar(cx, script); |
279 | 0 | WriteCachedScript(StartupCache::GetSingleton(), cachePath, cx, script); |
280 | 0 | } |
281 | 0 | } |
282 | 0 |
|
283 | 0 | return true; |
284 | 0 | } |
285 | | |
286 | | class AsyncScriptLoader : public nsIIncrementalStreamLoaderObserver |
287 | | { |
288 | | public: |
289 | | NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
290 | | NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER |
291 | | |
292 | | NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AsyncScriptLoader) |
293 | | |
294 | | AsyncScriptLoader(nsIChannel* aChannel, bool aWantReturnValue, |
295 | | JSObject* aTargetObj, JSObject* aLoadScope, |
296 | | const nsAString& aCharset, bool aCache, |
297 | | Promise* aPromise) |
298 | | : mChannel(aChannel) |
299 | | , mTargetObj(aTargetObj) |
300 | | , mLoadScope(aLoadScope) |
301 | | , mPromise(aPromise) |
302 | | , mCharset(aCharset) |
303 | | , mWantReturnValue(aWantReturnValue) |
304 | | , mCache(aCache) |
305 | 0 | { |
306 | 0 | // Needed for the cycle collector to manage mTargetObj. |
307 | 0 | mozilla::HoldJSObjects(this); |
308 | 0 | } |
309 | | |
310 | | private: |
311 | 0 | virtual ~AsyncScriptLoader() { |
312 | 0 | mozilla::DropJSObjects(this); |
313 | 0 | } |
314 | | |
315 | | RefPtr<nsIChannel> mChannel; |
316 | | Heap<JSObject*> mTargetObj; |
317 | | Heap<JSObject*> mLoadScope; |
318 | | RefPtr<Promise> mPromise; |
319 | | nsString mCharset; |
320 | | bool mWantReturnValue; |
321 | | bool mCache; |
322 | | }; |
323 | | |
324 | | NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncScriptLoader) |
325 | | |
326 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncScriptLoader) |
327 | 0 | NS_INTERFACE_MAP_ENTRY(nsIIncrementalStreamLoaderObserver) |
328 | 0 | NS_INTERFACE_MAP_END |
329 | | |
330 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncScriptLoader) |
331 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) |
332 | 0 | tmp->mTargetObj = nullptr; |
333 | 0 | tmp->mLoadScope = nullptr; |
334 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
335 | | |
336 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncScriptLoader) |
337 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) |
338 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
339 | | |
340 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AsyncScriptLoader) |
341 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mTargetObj) |
342 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLoadScope) |
343 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
344 | | |
345 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncScriptLoader) |
346 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncScriptLoader) |
347 | | |
348 | | class MOZ_STACK_CLASS AutoRejectPromise |
349 | | { |
350 | | public: |
351 | | AutoRejectPromise(AutoEntryScript& aAutoEntryScript, |
352 | | Promise* aPromise, |
353 | | nsIGlobalObject* aGlobalObject) |
354 | | : mAutoEntryScript(aAutoEntryScript) |
355 | | , mPromise(aPromise) |
356 | 0 | , mGlobalObject(aGlobalObject) {} |
357 | | |
358 | 0 | ~AutoRejectPromise() { |
359 | 0 | if (mPromise) { |
360 | 0 | JSContext* cx = mAutoEntryScript.cx(); |
361 | 0 | RootedValue rejectionValue(cx, JS::UndefinedValue()); |
362 | 0 | if (mAutoEntryScript.HasException()) { |
363 | 0 | Unused << mAutoEntryScript.PeekException(&rejectionValue); |
364 | 0 | } |
365 | 0 | mPromise->MaybeReject(cx, rejectionValue); |
366 | 0 | } |
367 | 0 | } |
368 | | |
369 | 0 | void ResolvePromise(HandleValue aResolveValue) { |
370 | 0 | mPromise->MaybeResolve(aResolveValue); |
371 | 0 | mPromise = nullptr; |
372 | 0 | } |
373 | | |
374 | | private: |
375 | | AutoEntryScript& mAutoEntryScript; |
376 | | RefPtr<Promise> mPromise; |
377 | | nsCOMPtr<nsIGlobalObject> mGlobalObject; |
378 | | }; |
379 | | |
380 | | NS_IMETHODIMP |
381 | | AsyncScriptLoader::OnIncrementalData(nsIIncrementalStreamLoader* aLoader, |
382 | | nsISupports* aContext, |
383 | | uint32_t aDataLength, |
384 | | const uint8_t* aData, |
385 | | uint32_t *aConsumedData) |
386 | 0 | { |
387 | 0 | return NS_OK; |
388 | 0 | } |
389 | | |
390 | | NS_IMETHODIMP |
391 | | AsyncScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, |
392 | | nsISupports* aContext, |
393 | | nsresult aStatus, |
394 | | uint32_t aLength, |
395 | | const uint8_t* aBuf) |
396 | 0 | { |
397 | 0 | nsCOMPtr<nsIURI> uri; |
398 | 0 | mChannel->GetURI(getter_AddRefs(uri)); |
399 | 0 |
|
400 | 0 | nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(mTargetObj); |
401 | 0 | AutoEntryScript aes(globalObject, "async loadSubScript"); |
402 | 0 | AutoRejectPromise autoPromise(aes, mPromise, globalObject); |
403 | 0 | JSContext* cx = aes.cx(); |
404 | 0 |
|
405 | 0 | if (NS_FAILED(aStatus)) { |
406 | 0 | ReportError(cx, "Unable to load script.", uri); |
407 | 0 | } |
408 | 0 | // Just notify that we are done with this load. |
409 | 0 | NS_ENSURE_SUCCESS(aStatus, NS_OK); |
410 | 0 |
|
411 | 0 | if (aLength == 0) { |
412 | 0 | ReportError(cx, LOAD_ERROR_NOCONTENT, uri); |
413 | 0 | return NS_OK; |
414 | 0 | } |
415 | 0 |
|
416 | 0 | if (aLength > INT32_MAX) { |
417 | 0 | ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri); |
418 | 0 | return NS_OK; |
419 | 0 | } |
420 | 0 |
|
421 | 0 | RootedScript script(cx); |
422 | 0 | nsAutoCString spec; |
423 | 0 | nsresult rv = uri->GetSpec(spec); |
424 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
425 | 0 |
|
426 | 0 | RootedObject targetObj(cx, mTargetObj); |
427 | 0 | RootedObject loadScope(cx, mLoadScope); |
428 | 0 |
|
429 | 0 | if (!PrepareScript(uri, cx, JS_IsGlobalObject(targetObj), spec.get(), |
430 | 0 | mCharset, reinterpret_cast<const char*>(aBuf), aLength, |
431 | 0 | mWantReturnValue, &script)) |
432 | 0 | { |
433 | 0 | return NS_OK; |
434 | 0 | } |
435 | 0 | |
436 | 0 | JS::Rooted<JS::Value> retval(cx); |
437 | 0 | if (EvalScript(cx, targetObj, loadScope, &retval, uri, mCache, |
438 | 0 | mCache && !mWantReturnValue, |
439 | 0 | &script)) { |
440 | 0 | autoPromise.ResolvePromise(retval); |
441 | 0 | } |
442 | 0 |
|
443 | 0 | return NS_OK; |
444 | 0 | } |
445 | | |
446 | | nsresult |
447 | | mozJSSubScriptLoader::ReadScriptAsync(nsIURI* uri, |
448 | | HandleObject targetObj, |
449 | | HandleObject loadScope, |
450 | | const nsAString& charset, |
451 | | nsIIOService* serv, |
452 | | bool wantReturnValue, |
453 | | bool cache, |
454 | | MutableHandleValue retval) |
455 | 0 | { |
456 | 0 | nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(targetObj); |
457 | 0 | ErrorResult result; |
458 | 0 |
|
459 | 0 | AutoJSAPI jsapi; |
460 | 0 | if (NS_WARN_IF(!jsapi.Init(globalObject))) { |
461 | 0 | return NS_ERROR_UNEXPECTED; |
462 | 0 | } |
463 | 0 | |
464 | 0 | RefPtr<Promise> promise = Promise::Create(globalObject, result); |
465 | 0 | if (result.Failed()) { |
466 | 0 | return result.StealNSResult(); |
467 | 0 | } |
468 | 0 | |
469 | 0 | DebugOnly<bool> asJS = ToJSValue(jsapi.cx(), promise, retval); |
470 | 0 | MOZ_ASSERT(asJS, "Should not fail to convert the promise to a JS value"); |
471 | 0 |
|
472 | 0 | // We create a channel and call SetContentType, to avoid expensive MIME type |
473 | 0 | // lookups (bug 632490). |
474 | 0 | nsCOMPtr<nsIChannel> channel; |
475 | 0 | nsresult rv; |
476 | 0 | rv = NS_NewChannel(getter_AddRefs(channel), |
477 | 0 | uri, |
478 | 0 | nsContentUtils::GetSystemPrincipal(), |
479 | 0 | nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, |
480 | 0 | nsIContentPolicy::TYPE_OTHER, |
481 | 0 | nullptr, // aPerformanceStorage |
482 | 0 | nullptr, // aLoadGroup |
483 | 0 | nullptr, // aCallbacks |
484 | 0 | nsIRequest::LOAD_NORMAL, |
485 | 0 | serv); |
486 | 0 |
|
487 | 0 | if (!NS_SUCCEEDED(rv)) { |
488 | 0 | return rv; |
489 | 0 | } |
490 | 0 | |
491 | 0 | channel->SetContentType(NS_LITERAL_CSTRING("application/javascript")); |
492 | 0 |
|
493 | 0 | RefPtr<AsyncScriptLoader> loadObserver = |
494 | 0 | new AsyncScriptLoader(channel, |
495 | 0 | wantReturnValue, |
496 | 0 | targetObj, |
497 | 0 | loadScope, |
498 | 0 | charset, |
499 | 0 | cache, |
500 | 0 | promise); |
501 | 0 |
|
502 | 0 | nsCOMPtr<nsIIncrementalStreamLoader> loader; |
503 | 0 | rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver); |
504 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
505 | 0 |
|
506 | 0 | nsCOMPtr<nsIStreamListener> listener = loader.get(); |
507 | 0 | return channel->AsyncOpen2(listener); |
508 | 0 | } |
509 | | |
510 | | bool |
511 | | mozJSSubScriptLoader::ReadScript(nsIURI* uri, |
512 | | JSContext* cx, |
513 | | HandleObject targetObj, |
514 | | const nsAString& charset, |
515 | | const char* uriStr, |
516 | | nsIIOService* serv, |
517 | | bool wantReturnValue, |
518 | | bool useCompilationScope, |
519 | | MutableHandleScript script) |
520 | 0 | { |
521 | 0 | script.set(nullptr); |
522 | 0 |
|
523 | 0 | // We create a channel and call SetContentType, to avoid expensive MIME type |
524 | 0 | // lookups (bug 632490). |
525 | 0 | nsCOMPtr<nsIChannel> chan; |
526 | 0 | nsCOMPtr<nsIInputStream> instream; |
527 | 0 | nsresult rv; |
528 | 0 | rv = NS_NewChannel(getter_AddRefs(chan), |
529 | 0 | uri, |
530 | 0 | nsContentUtils::GetSystemPrincipal(), |
531 | 0 | nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, |
532 | 0 | nsIContentPolicy::TYPE_OTHER, |
533 | 0 | nullptr, // PerformanceStorage |
534 | 0 | nullptr, // aLoadGroup |
535 | 0 | nullptr, // aCallbacks |
536 | 0 | nsIRequest::LOAD_NORMAL, |
537 | 0 | serv); |
538 | 0 |
|
539 | 0 | if (NS_SUCCEEDED(rv)) { |
540 | 0 | chan->SetContentType(NS_LITERAL_CSTRING("application/javascript")); |
541 | 0 | rv = chan->Open2(getter_AddRefs(instream)); |
542 | 0 | } |
543 | 0 |
|
544 | 0 | if (NS_FAILED(rv)) { |
545 | 0 | ReportError(cx, LOAD_ERROR_NOSTREAM, uri); |
546 | 0 | return false; |
547 | 0 | } |
548 | 0 |
|
549 | 0 | int64_t len = -1; |
550 | 0 |
|
551 | 0 | rv = chan->GetContentLength(&len); |
552 | 0 | if (NS_FAILED(rv) || len == -1) { |
553 | 0 | ReportError(cx, LOAD_ERROR_NOCONTENT, uri); |
554 | 0 | return false; |
555 | 0 | } |
556 | 0 |
|
557 | 0 | if (len > INT32_MAX) { |
558 | 0 | ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri); |
559 | 0 | return false; |
560 | 0 | } |
561 | 0 |
|
562 | 0 | nsCString buf; |
563 | 0 | rv = NS_ReadInputStreamToString(instream, buf, len); |
564 | 0 | NS_ENSURE_SUCCESS(rv, false); |
565 | 0 |
|
566 | 0 | Maybe<JSAutoRealm> ar; |
567 | 0 |
|
568 | 0 | // Note that when using the ScriptPreloader cache with loadSubScript, there |
569 | 0 | // will be a side-effect of keeping the global that the script was compiled |
570 | 0 | // for alive. See note above in EvalScript(). |
571 | 0 | // |
572 | 0 | // This will compile the script in XPConnect compilation scope. When the |
573 | 0 | // script is evaluated, it will be cloned into the target scope to be |
574 | 0 | // executed, avoiding leaks on the first session when we don't have a |
575 | 0 | // startup cache. |
576 | 0 | if (useCompilationScope) { |
577 | 0 | ar.emplace(cx, xpc::CompilationScope()); |
578 | 0 | } |
579 | 0 |
|
580 | 0 | return PrepareScript(uri, cx, JS_IsGlobalObject(targetObj), |
581 | 0 | uriStr, charset, buf.get(), len, wantReturnValue, |
582 | 0 | script); |
583 | 0 | } |
584 | | |
585 | | NS_IMETHODIMP |
586 | | mozJSSubScriptLoader::LoadSubScript(const nsAString& url, |
587 | | HandleValue target, |
588 | | const nsAString& charset, |
589 | | JSContext* cx, |
590 | | MutableHandleValue retval) |
591 | 0 | { |
592 | 0 | /* |
593 | 0 | * Loads a local url and evals it into the current cx |
594 | 0 | * Synchronous (an async version would be cool too.) |
595 | 0 | * url: The url to load. Must be local so that it can be loaded |
596 | 0 | * synchronously. |
597 | 0 | * targetObj: Optional object to eval the script onto (defaults to context |
598 | 0 | * global) |
599 | 0 | * charset: Optional character set to use for reading |
600 | 0 | * returns: Whatever jsval the script pointed to by the url returns. |
601 | 0 | * Should ONLY (O N L Y !) be called from JavaScript code. |
602 | 0 | */ |
603 | 0 | LoadSubScriptOptions options(cx); |
604 | 0 | options.charset = charset; |
605 | 0 | options.target = target.isObject() ? &target.toObject() : nullptr; |
606 | 0 | return DoLoadSubScriptWithOptions(url, options, cx, retval); |
607 | 0 | } |
608 | | |
609 | | |
610 | | NS_IMETHODIMP |
611 | | mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString& url, |
612 | | HandleValue optionsVal, |
613 | | JSContext* cx, |
614 | | MutableHandleValue retval) |
615 | 0 | { |
616 | 0 | if (!optionsVal.isObject()) { |
617 | 0 | return NS_ERROR_INVALID_ARG; |
618 | 0 | } |
619 | 0 | LoadSubScriptOptions options(cx, &optionsVal.toObject()); |
620 | 0 | if (!options.Parse()) { |
621 | 0 | return NS_ERROR_INVALID_ARG; |
622 | 0 | } |
623 | 0 | return DoLoadSubScriptWithOptions(url, options, cx, retval); |
624 | 0 | } |
625 | | |
626 | | nsresult |
627 | | mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString& url, |
628 | | LoadSubScriptOptions& options, |
629 | | JSContext* cx, |
630 | | MutableHandleValue retval) |
631 | 0 | { |
632 | 0 | nsresult rv = NS_OK; |
633 | 0 | RootedObject targetObj(cx); |
634 | 0 | RootedObject loadScope(cx); |
635 | 0 | mozJSComponentLoader* loader = mozJSComponentLoader::Get(); |
636 | 0 | loader->FindTargetObject(cx, &loadScope); |
637 | 0 |
|
638 | 0 | if (options.target) { |
639 | 0 | targetObj = options.target; |
640 | 0 | } else { |
641 | 0 | targetObj = loadScope; |
642 | 0 | } |
643 | 0 |
|
644 | 0 | targetObj = JS_FindCompilationScope(cx, targetObj); |
645 | 0 | if (!targetObj || !loadScope) { |
646 | 0 | return NS_ERROR_FAILURE; |
647 | 0 | } |
648 | 0 | |
649 | 0 | MOZ_ASSERT(!js::IsWrapper(targetObj), |
650 | 0 | "JS_FindCompilationScope must unwrap"); |
651 | 0 |
|
652 | 0 | if (js::GetObjectCompartment(loadScope) != js::GetObjectCompartment(targetObj)) { |
653 | 0 | loadScope = nullptr; |
654 | 0 | } |
655 | 0 |
|
656 | 0 | /* load up the url. From here on, failures are reflected as ``custom'' |
657 | 0 | * js exceptions */ |
658 | 0 | nsCOMPtr<nsIURI> uri; |
659 | 0 | nsAutoCString uriStr; |
660 | 0 | nsAutoCString scheme; |
661 | 0 |
|
662 | 0 | // Figure out who's calling us |
663 | 0 | JS::AutoFilename filename; |
664 | 0 | if (!JS::DescribeScriptedCaller(cx, &filename)) { |
665 | 0 | // No scripted frame means we don't know who's calling, bail. |
666 | 0 | return NS_ERROR_FAILURE; |
667 | 0 | } |
668 | 0 | |
669 | 0 | JSAutoRealm ar(cx, targetObj); |
670 | 0 |
|
671 | 0 | nsCOMPtr<nsIIOService> serv = do_GetService(NS_IOSERVICE_CONTRACTID); |
672 | 0 | if (!serv) { |
673 | 0 | ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOSERVICE)); |
674 | 0 | return NS_OK; |
675 | 0 | } |
676 | 0 |
|
677 | 0 | NS_LossyConvertUTF16toASCII asciiUrl(url); |
678 | 0 | AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING( |
679 | 0 | "mozJSSubScriptLoader::DoLoadSubScriptWithOptions", OTHER, asciiUrl); |
680 | 0 |
|
681 | 0 | // Make sure to explicitly create the URI, since we'll need the |
682 | 0 | // canonicalized spec. |
683 | 0 | rv = NS_NewURI(getter_AddRefs(uri), asciiUrl.get(), nullptr, serv); |
684 | 0 | if (NS_FAILED(rv)) { |
685 | 0 | ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOURI)); |
686 | 0 | return NS_OK; |
687 | 0 | } |
688 | 0 |
|
689 | 0 | rv = uri->GetSpec(uriStr); |
690 | 0 | if (NS_FAILED(rv)) { |
691 | 0 | ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOSPEC)); |
692 | 0 | return NS_OK; |
693 | 0 | } |
694 | 0 |
|
695 | 0 | rv = uri->GetScheme(scheme); |
696 | 0 | if (NS_FAILED(rv)) { |
697 | 0 | ReportError(cx, LOAD_ERROR_NOSCHEME, uri); |
698 | 0 | return NS_OK; |
699 | 0 | } |
700 | 0 |
|
701 | 0 | if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("app") && |
702 | 0 | !scheme.EqualsLiteral("blob")) { |
703 | 0 | // This might be a URI to a local file, though! |
704 | 0 | nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri); |
705 | 0 | nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(innerURI); |
706 | 0 | if (!fileURL) { |
707 | 0 | ReportError(cx, LOAD_ERROR_URI_NOT_LOCAL, uri); |
708 | 0 | return NS_OK; |
709 | 0 | } |
710 | 0 |
|
711 | 0 | // For file URIs prepend the filename with the filename of the |
712 | 0 | // calling script, and " -> ". See bug 418356. |
713 | 0 | nsAutoCString tmp(filename.get()); |
714 | 0 | tmp.AppendLiteral(" -> "); |
715 | 0 | tmp.Append(uriStr); |
716 | 0 |
|
717 | 0 | uriStr = tmp; |
718 | 0 | } |
719 | 0 |
|
720 | 0 | // Suppress caching if we're compiling as content or if we're loading a |
721 | 0 | // blob: URI. |
722 | 0 | bool useCompilationScope = false; |
723 | 0 | auto* principal = BasePrincipal::Cast(GetObjectPrincipal(targetObj)); |
724 | 0 | bool isSystem = principal->Is<SystemPrincipal>(); |
725 | 0 | if (!isSystem && principal->Is<ContentPrincipal>()) { |
726 | 0 | auto* content = principal->As<ContentPrincipal>(); |
727 | 0 |
|
728 | 0 | nsAutoCString scheme; |
729 | 0 | content->mCodebase->GetScheme(scheme); |
730 | 0 |
|
731 | 0 | // We want to enable caching for scripts with Activity Stream's |
732 | 0 | // codebase URLs. |
733 | 0 | if (scheme.EqualsLiteral("about")) { |
734 | 0 | nsAutoCString filePath; |
735 | 0 | content->mCodebase->GetFilePath(filePath); |
736 | 0 |
|
737 | 0 | useCompilationScope = filePath.EqualsLiteral("home") || |
738 | 0 | filePath.EqualsLiteral("newtab") || |
739 | 0 | filePath.EqualsLiteral("welcome"); |
740 | 0 | isSystem = true; |
741 | 0 | } |
742 | 0 | } |
743 | 0 | bool ignoreCache = options.ignoreCache || !isSystem || scheme.EqualsLiteral("blob"); |
744 | 0 |
|
745 | 0 | StartupCache* cache = ignoreCache ? nullptr : StartupCache::GetSingleton(); |
746 | 0 |
|
747 | 0 | nsAutoCString cachePath; |
748 | 0 | SubscriptCachePath(cx, uri, targetObj, cachePath); |
749 | 0 |
|
750 | 0 | RootedScript script(cx); |
751 | 0 | if (!options.ignoreCache) { |
752 | 0 | if (!options.wantReturnValue) { |
753 | 0 | script = ScriptPreloader::GetSingleton().GetCachedScript(cx, cachePath); |
754 | 0 | } |
755 | 0 | if (!script && cache) { |
756 | 0 | rv = ReadCachedScript(cache, cachePath, cx, &script); |
757 | 0 | } |
758 | 0 | if (NS_FAILED(rv) || !script) { |
759 | 0 | // ReadCachedScript may have set a pending exception. |
760 | 0 | JS_ClearPendingException(cx); |
761 | 0 | } |
762 | 0 | } |
763 | 0 |
|
764 | 0 | // If we are doing an async load, trigger it and bail out. |
765 | 0 | if (!script && options.async) { |
766 | 0 | return ReadScriptAsync(uri, targetObj, loadScope, options.charset, serv, |
767 | 0 | options.wantReturnValue, !!cache, retval); |
768 | 0 | } |
769 | 0 | |
770 | 0 | if (script) { |
771 | 0 | // |script| came from the cache, so don't bother writing it |
772 | 0 | // |back there. |
773 | 0 | cache = nullptr; |
774 | 0 | } else if (!ReadScript(uri, cx, targetObj, options.charset, |
775 | 0 | static_cast<const char*>(uriStr.get()), serv, |
776 | 0 | options.wantReturnValue, useCompilationScope, &script)) { |
777 | 0 | return NS_OK; |
778 | 0 | } |
779 | 0 | |
780 | 0 | Unused << EvalScript(cx, targetObj, loadScope, retval, uri, !!cache, |
781 | 0 | !ignoreCache && !options.wantReturnValue, |
782 | 0 | &script); |
783 | 0 | return NS_OK; |
784 | 0 | } |