Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 : #ifndef mozilla_dom_ScriptLoader_h
8 : #define mozilla_dom_ScriptLoader_h
9 :
10 : #include "nsCOMPtr.h"
11 : #include "nsRefPtrHashtable.h"
12 : #include "mozilla/Encoding.h"
13 : #include "nsIScriptElement.h"
14 : #include "nsCOMArray.h"
15 : #include "nsCycleCollectionParticipant.h"
16 : #include "nsTArray.h"
17 : #include "nsAutoPtr.h"
18 : #include "nsICacheInfoChannel.h"
19 : #include "nsIDocument.h"
20 : #include "nsIIncrementalStreamLoader.h"
21 : #include "nsURIHashKey.h"
22 : #include "mozilla/CORSMode.h"
23 : #include "mozilla/dom/ScriptLoadRequest.h"
24 : #include "mozilla/dom/SRIMetadata.h"
25 : #include "mozilla/dom/SRICheck.h"
26 : #include "mozilla/MozPromise.h"
27 : #include "mozilla/net/ReferrerPolicy.h"
28 : #include "mozilla/Vector.h"
29 :
30 : class nsIURI;
31 :
32 : namespace JS {
33 : class SourceBufferHolder;
34 : } // namespace JS
35 :
36 : namespace mozilla {
37 : namespace dom {
38 :
39 : class AutoJSAPI;
40 : class ModuleLoadRequest;
41 : class ModuleScript;
42 : class ScriptLoadHandler;
43 : class ScriptRequestProcessor;
44 :
45 : //////////////////////////////////////////////////////////////
46 : // Script loader implementation
47 : //////////////////////////////////////////////////////////////
48 :
49 : class ScriptLoader final : public nsISupports
50 : {
51 : class MOZ_STACK_CLASS AutoCurrentScriptUpdater
52 : {
53 : public:
54 5 : AutoCurrentScriptUpdater(ScriptLoader* aScriptLoader,
55 : nsIScriptElement* aCurrentScript)
56 5 : : mOldScript(aScriptLoader->mCurrentScript)
57 5 : , mScriptLoader(aScriptLoader)
58 : {
59 5 : mScriptLoader->mCurrentScript = aCurrentScript;
60 5 : }
61 :
62 5 : ~AutoCurrentScriptUpdater()
63 5 : {
64 5 : mScriptLoader->mCurrentScript.swap(mOldScript);
65 5 : }
66 :
67 : private:
68 : nsCOMPtr<nsIScriptElement> mOldScript;
69 : ScriptLoader* mScriptLoader;
70 : };
71 :
72 : friend class ModuleLoadRequest;
73 : friend class ScriptRequestProcessor;
74 : friend class ScriptLoadHandler;
75 : friend class AutoCurrentScriptUpdater;
76 :
77 : public:
78 : explicit ScriptLoader(nsIDocument* aDocument);
79 :
80 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
81 168 : NS_DECL_CYCLE_COLLECTION_CLASS(ScriptLoader)
82 :
83 : /**
84 : * The loader maintains a weak reference to the document with
85 : * which it is initialized. This call forces the reference to
86 : * be dropped.
87 : */
88 0 : void DropDocumentReference()
89 : {
90 0 : mDocument = nullptr;
91 0 : }
92 :
93 : /**
94 : * Add an observer for all scripts loaded through this loader.
95 : *
96 : * @param aObserver observer for all script processing.
97 : */
98 0 : nsresult AddObserver(nsIScriptLoaderObserver* aObserver)
99 : {
100 0 : return mObservers.AppendObject(aObserver) ? NS_OK :
101 0 : NS_ERROR_OUT_OF_MEMORY;
102 : }
103 :
104 : /**
105 : * Remove an observer.
106 : *
107 : * @param aObserver observer to be removed
108 : */
109 0 : void RemoveObserver(nsIScriptLoaderObserver* aObserver)
110 : {
111 0 : mObservers.RemoveObject(aObserver);
112 0 : }
113 :
114 : /**
115 : * Process a script element. This will include both loading the
116 : * source of the element if it is not inline and evaluating
117 : * the script itself.
118 : *
119 : * If the script is an inline script that can be executed immediately
120 : * (i.e. there are no other scripts pending) then ScriptAvailable
121 : * and ScriptEvaluated will be called before the function returns.
122 : *
123 : * If true is returned the script could not be executed immediately.
124 : * In this case ScriptAvailable is guaranteed to be called at a later
125 : * point (as well as possibly ScriptEvaluated).
126 : *
127 : * @param aElement The element representing the script to be loaded and
128 : * evaluated.
129 : */
130 : bool ProcessScriptElement(nsIScriptElement* aElement);
131 :
132 : /**
133 : * Gets the currently executing script. This is useful if you want to
134 : * generate a unique key based on the currently executing script.
135 : */
136 62 : nsIScriptElement* GetCurrentScript()
137 : {
138 62 : return mCurrentScript;
139 : }
140 :
141 0 : nsIScriptElement* GetCurrentParserInsertedScript()
142 : {
143 0 : return mCurrentParserInsertedScript;
144 : }
145 :
146 : /**
147 : * Whether the loader is enabled or not.
148 : * When disabled, processing of new script elements is disabled.
149 : * Any call to ProcessScriptElement() will return false. Note that
150 : * this DOES NOT disable currently loading or executing scripts.
151 : */
152 0 : bool GetEnabled()
153 : {
154 0 : return mEnabled;
155 : }
156 :
157 21 : void SetEnabled(bool aEnabled)
158 : {
159 21 : if (!mEnabled && aEnabled) {
160 0 : ProcessPendingRequestsAsync();
161 : }
162 21 : mEnabled = aEnabled;
163 21 : }
164 :
165 : /**
166 : * Add/remove a blocker for parser-blocking scripts (and XSLT
167 : * scripts). Blockers will stop such scripts from executing, but not from
168 : * loading.
169 : */
170 0 : void AddParserBlockingScriptExecutionBlocker()
171 : {
172 0 : ++mParserBlockingBlockerCount;
173 0 : }
174 :
175 0 : void RemoveParserBlockingScriptExecutionBlocker()
176 : {
177 0 : if (!--mParserBlockingBlockerCount && ReadyToExecuteScripts()) {
178 0 : ProcessPendingRequestsAsync();
179 : }
180 0 : }
181 :
182 : /**
183 : * Add/remove a blocker for execution of all scripts. Blockers will stop
184 : * scripts from executing, but not from loading.
185 : */
186 0 : void AddExecuteBlocker()
187 : {
188 0 : ++mBlockerCount;
189 0 : }
190 :
191 0 : void RemoveExecuteBlocker()
192 : {
193 0 : MOZ_ASSERT(mBlockerCount);
194 0 : if (!--mBlockerCount) {
195 0 : ProcessPendingRequestsAsync();
196 : }
197 0 : }
198 :
199 : /**
200 : * Convert the given buffer to a UTF-16 string.
201 : * @param aChannel Channel corresponding to the data. May be null.
202 : * @param aData The data to convert
203 : * @param aLength Length of the data
204 : * @param aHintCharset Hint for the character set (e.g., from a charset
205 : * attribute). May be the empty string.
206 : * @param aDocument Document which the data is loaded for. Must not be
207 : * null.
208 : * @param aBufOut [out] char16_t array allocated by ConvertToUTF16 and
209 : * containing data converted to unicode. Caller must
210 : * js_free() this data when no longer needed.
211 : * @param aLengthOut [out] Length of array returned in aBufOut in number
212 : * of char16_t code units.
213 : */
214 : static nsresult ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
215 : uint32_t aLength,
216 : const nsAString& aHintCharset,
217 : nsIDocument* aDocument,
218 : char16_t*& aBufOut, size_t& aLengthOut);
219 :
220 : static inline nsresult
221 0 : ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
222 : uint32_t aLength, const nsAString& aHintCharset,
223 : nsIDocument* aDocument,
224 : JS::UniqueTwoByteChars& aBufOut, size_t& aLengthOut)
225 : {
226 : char16_t* bufOut;
227 : nsresult rv = ConvertToUTF16(aChannel, aData, aLength, aHintCharset,
228 0 : aDocument, bufOut, aLengthOut);
229 0 : if (NS_SUCCEEDED(rv)) {
230 0 : aBufOut.reset(bufOut);
231 : }
232 0 : return rv;
233 : };
234 :
235 : /**
236 : * Handle the completion of a stream. This is called by the
237 : * ScriptLoadHandler object which observes the IncrementalStreamLoader
238 : * loading the script. The streamed content is expected to be stored on the
239 : * aRequest argument.
240 : */
241 : nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
242 : ScriptLoadRequest* aRequest,
243 : nsresult aChannelStatus,
244 : nsresult aSRIStatus,
245 : mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier);
246 :
247 : /**
248 : * Returns wether any request is queued, and not executed yet.
249 : */
250 : bool HasPendingRequests();
251 :
252 : /**
253 : * Processes any pending requests that are ready for processing.
254 : */
255 : void ProcessPendingRequests();
256 :
257 : /**
258 : * Starts deferring deferred scripts and puts them in the mDeferredRequests
259 : * queue instead.
260 : */
261 25 : void BeginDeferringScripts()
262 : {
263 25 : mDeferEnabled = true;
264 25 : if (mDocument) {
265 25 : mDocument->BlockOnload();
266 : }
267 25 : }
268 :
269 : /**
270 : * Notifies the script loader that parsing is done. If aTerminated is true,
271 : * this will drop any pending scripts that haven't run yet. Otherwise, it
272 : * will stops deferring scripts and immediately processes the
273 : * mDeferredRequests queue.
274 : *
275 : * WARNING: This function will synchronously execute content scripts, so be
276 : * prepared that the world might change around you.
277 : */
278 : void ParsingComplete(bool aTerminated);
279 :
280 : /**
281 : * Returns the number of pending scripts, deferred or not.
282 : */
283 : uint32_t HasPendingOrCurrentScripts()
284 : {
285 : return mCurrentScript || mParserBlockingRequest;
286 : }
287 :
288 : /**
289 : * Adds aURI to the preload list and starts loading it.
290 : *
291 : * @param aURI The URI of the external script.
292 : * @param aCharset The charset parameter for the script.
293 : * @param aType The type parameter for the script.
294 : * @param aCrossOrigin The crossorigin attribute for the script.
295 : * Void if not present.
296 : * @param aIntegrity The expect hash url, if avail, of the request
297 : * @param aScriptFromHead Whether or not the script was a child of head
298 : */
299 : virtual void PreloadURI(nsIURI* aURI, const nsAString& aCharset,
300 : const nsAString& aType,
301 : const nsAString& aCrossOrigin,
302 : const nsAString& aIntegrity,
303 : bool aScriptFromHead,
304 : const mozilla::net::ReferrerPolicy aReferrerPolicy);
305 :
306 : /**
307 : * Process a request that was deferred so that the script could be compiled
308 : * off thread.
309 : */
310 : nsresult ProcessOffThreadRequest(ScriptLoadRequest* aRequest);
311 :
312 0 : bool AddPendingChildLoader(ScriptLoader* aChild)
313 : {
314 0 : return mPendingChildLoaders.AppendElement(aChild) != nullptr;
315 : }
316 :
317 0 : mozilla::dom::DocGroup* GetDocGroup() const
318 : {
319 0 : return mDocument->GetDocGroup();
320 : }
321 :
322 : /**
323 : * Register the fact that we saw the load event, and that we need to save the
324 : * bytecode at the next loop cycle unless new scripts are waiting in the
325 : * pipeline.
326 : */
327 : void LoadEventFired();
328 :
329 : private:
330 : virtual ~ScriptLoader();
331 :
332 : ScriptLoadRequest* CreateLoadRequest(ScriptKind aKind,
333 : nsIScriptElement* aElement,
334 : uint32_t aVersion,
335 : mozilla::CORSMode aCORSMode,
336 : const mozilla::dom::SRIMetadata& aIntegrity);
337 :
338 : /**
339 : * Unblocks the creator parser of the parser-blocking scripts.
340 : */
341 : void UnblockParser(ScriptLoadRequest* aParserBlockingRequest);
342 :
343 : /**
344 : * Asynchronously resumes the creator parser of the parser-blocking scripts.
345 : */
346 : void ContinueParserAsync(ScriptLoadRequest* aParserBlockingRequest);
347 :
348 :
349 : /**
350 : * Helper function to check the content policy for a given request.
351 : */
352 : static nsresult CheckContentPolicy(nsIDocument* aDocument,
353 : nsISupports* aContext,
354 : nsIURI* aURI,
355 : const nsAString& aType,
356 : bool aIsPreLoad);
357 :
358 : /**
359 : * Start a load for aRequest's URI.
360 : */
361 : nsresult StartLoad(ScriptLoadRequest* aRequest);
362 :
363 : /**
364 : * Abort the current stream, and re-start with a new load request from scratch
365 : * without requesting any alternate data. Returns NS_BINDING_RETARGETED on
366 : * success, as this error code is used to abort the input stream.
367 : */
368 : nsresult RestartLoad(ScriptLoadRequest* aRequest);
369 :
370 : /**
371 : * Process any pending requests asynchronously (i.e. off an event) if there
372 : * are any. Note that this is a no-op if there aren't any currently pending
373 : * requests.
374 : *
375 : * This function is virtual to allow cross-library calls to SetEnabled()
376 : */
377 : virtual void ProcessPendingRequestsAsync();
378 :
379 : /**
380 : * If true, the loader is ready to execute parser-blocking scripts, and so are
381 : * all its ancestors. If the loader itself is ready but some ancestor is not,
382 : * this function will add an execute blocker and ask the ancestor to remove it
383 : * once it becomes ready.
384 : */
385 : bool ReadyToExecuteParserBlockingScripts();
386 :
387 : /**
388 : * Return whether just this loader is ready to execute parser-blocking
389 : * scripts.
390 : */
391 48 : bool SelfReadyToExecuteParserBlockingScripts()
392 : {
393 48 : return ReadyToExecuteScripts() && !mParserBlockingBlockerCount;
394 : }
395 :
396 : /**
397 : * Return whether this loader is ready to execute scripts in general.
398 : */
399 131 : bool ReadyToExecuteScripts()
400 : {
401 131 : return mEnabled && !mBlockerCount;
402 : }
403 :
404 : nsresult AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest);
405 : nsresult ProcessRequest(ScriptLoadRequest* aRequest);
406 : nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest);
407 : void FireScriptAvailable(nsresult aResult,
408 : ScriptLoadRequest* aRequest);
409 : void FireScriptEvaluated(nsresult aResult,
410 : ScriptLoadRequest* aRequest);
411 : nsresult EvaluateScript(ScriptLoadRequest* aRequest);
412 :
413 : /**
414 : * Queue the current script load request to be saved, when the page
415 : * initialization ends. The page initialization end is defined as being the
416 : * time when the load event got received, and when no more scripts are waiting
417 : * to be executed.
418 : */
419 : void RegisterForBytecodeEncoding(ScriptLoadRequest* aRequest);
420 :
421 : /**
422 : * Check if all conditions are met, i-e that the onLoad event fired and that
423 : * no more script have to be processed. If all conditions are met, queue an
424 : * event to encode all the bytecode and save them on the cache.
425 : */
426 : void MaybeTriggerBytecodeEncoding();
427 :
428 : /**
429 : * Iterate over all script load request and save the bytecode of executed
430 : * functions on the cache provided by the channel.
431 : */
432 : void EncodeBytecode();
433 : void EncodeRequestBytecode(JSContext* aCx, ScriptLoadRequest* aRequest);
434 :
435 : void GiveUpBytecodeEncoding();
436 :
437 : already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
438 : nsresult FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI& jsapi,
439 : ScriptLoadRequest* aRequest,
440 : JS::Handle<JSObject*> aScopeChain,
441 : JS::CompileOptions* aOptions);
442 :
443 : uint32_t NumberOfProcessors();
444 : nsresult PrepareLoadedRequest(ScriptLoadRequest* aRequest,
445 : nsIIncrementalStreamLoader* aLoader,
446 : nsresult aStatus);
447 :
448 : void AddDeferRequest(ScriptLoadRequest* aRequest);
449 : bool MaybeRemovedDeferRequests();
450 :
451 : void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
452 :
453 : JS::SourceBufferHolder GetScriptSource(ScriptLoadRequest* aRequest,
454 : nsAutoString& inlineData);
455 :
456 : bool ModuleScriptsEnabled();
457 :
458 : void SetModuleFetchStarted(ModuleLoadRequest *aRequest);
459 : void SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest* aRequest,
460 : nsresult aResult);
461 :
462 : bool IsFetchingModule(ModuleLoadRequest* aRequest) const;
463 :
464 : bool ModuleMapContainsModule(ModuleLoadRequest* aRequest) const;
465 : RefPtr<mozilla::GenericPromise> WaitForModuleFetch(ModuleLoadRequest* aRequest);
466 : ModuleScript* GetFetchedModule(nsIURI* aURL) const;
467 :
468 : friend bool
469 : HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp);
470 :
471 : // Returns wether we should save the bytecode of this script after the
472 : // execution of the script.
473 : static bool
474 : ShouldCacheBytecode(ScriptLoadRequest* aRequest);
475 :
476 : nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
477 : nsresult ProcessFetchedModuleSource(ModuleLoadRequest* aRequest);
478 : void ProcessLoadedModuleTree(ModuleLoadRequest* aRequest);
479 : bool InstantiateModuleTree(ModuleLoadRequest* aRequest);
480 : void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
481 :
482 : RefPtr<mozilla::GenericPromise>
483 : StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest, nsIURI* aURI);
484 :
485 : nsIDocument* mDocument; // [WEAK]
486 : nsCOMArray<nsIScriptLoaderObserver> mObservers;
487 : ScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;
488 : // mLoadingAsyncRequests holds async requests while they're loading; when they
489 : // have been loaded they are moved to mLoadedAsyncRequests.
490 : ScriptLoadRequestList mLoadingAsyncRequests;
491 : ScriptLoadRequestList mLoadedAsyncRequests;
492 : ScriptLoadRequestList mDeferRequests;
493 : ScriptLoadRequestList mXSLTRequests;
494 : RefPtr<ScriptLoadRequest> mParserBlockingRequest;
495 :
496 : // List of script load request that are holding a buffer which has to be saved
497 : // on the cache.
498 : ScriptLoadRequestList mBytecodeEncodingQueue;
499 :
500 : // In mRequests, the additional information here is stored by the element.
501 8 : struct PreloadInfo
502 : {
503 : RefPtr<ScriptLoadRequest> mRequest;
504 : nsString mCharset;
505 : };
506 :
507 : friend void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField);
508 : friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
509 : ScriptLoader::PreloadInfo& aField,
510 : const char* aName, uint32_t aFlags);
511 :
512 : struct PreloadRequestComparator
513 : {
514 7 : bool Equals(const PreloadInfo& aPi, ScriptLoadRequest* const& aRequest) const
515 : {
516 7 : return aRequest == aPi.mRequest;
517 : }
518 : };
519 :
520 : struct PreloadURIComparator
521 : {
522 : bool Equals(const PreloadInfo& aPi, nsIURI* const &aURI) const;
523 : };
524 :
525 : nsTArray<PreloadInfo> mPreloads;
526 :
527 : nsCOMPtr<nsIScriptElement> mCurrentScript;
528 : nsCOMPtr<nsIScriptElement> mCurrentParserInsertedScript;
529 : nsTArray< RefPtr<ScriptLoader> > mPendingChildLoaders;
530 : uint32_t mParserBlockingBlockerCount;
531 : uint32_t mBlockerCount;
532 : uint32_t mNumberOfProcessors;
533 : bool mEnabled;
534 : bool mDeferEnabled;
535 : bool mDocumentParsingDone;
536 : bool mBlockingDOMContentLoaded;
537 : bool mLoadEventFired;
538 :
539 : // Module map
540 : nsRefPtrHashtable<nsURIHashKey, mozilla::GenericPromise::Private> mFetchingModules;
541 : nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
542 :
543 : nsCOMPtr<nsIConsoleReportCollector> mReporter;
544 :
545 : // Logging
546 : static LazyLogModule gCspPRLog;
547 : static LazyLogModule gScriptLoaderLog;
548 : };
549 :
550 : class nsAutoScriptLoaderDisabler
551 : {
552 : public:
553 0 : explicit nsAutoScriptLoaderDisabler(nsIDocument* aDoc)
554 0 : {
555 0 : mLoader = aDoc->ScriptLoader();
556 0 : mWasEnabled = mLoader->GetEnabled();
557 0 : if (mWasEnabled) {
558 0 : mLoader->SetEnabled(false);
559 : }
560 0 : }
561 :
562 0 : ~nsAutoScriptLoaderDisabler()
563 0 : {
564 0 : if (mWasEnabled) {
565 0 : mLoader->SetEnabled(true);
566 : }
567 0 : }
568 :
569 : bool mWasEnabled;
570 : RefPtr<ScriptLoader> mLoader;
571 : };
572 :
573 : } // namespace dom
574 : } // namespace mozilla
575 :
576 : #endif // mozilla_dom_ScriptLoader_h
|