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 #include "ImportManager.h"
8 
9 #include "mozilla/EventListenerManager.h"
10 #include "HTMLLinkElement.h"
11 #include "nsContentPolicyUtils.h"
12 #include "nsContentUtils.h"
13 #include "nsIChannel.h"
14 #include "nsIContentPolicy.h"
15 #include "nsIContentSecurityPolicy.h"
16 #include "nsIDocument.h"
17 #include "nsIDOMDocument.h"
18 #include "nsIDOMEvent.h"
19 #include "nsIPrincipal.h"
20 #include "nsIScriptObjectPrincipal.h"
21 #include "nsScriptLoader.h"
22 #include "nsNetUtil.h"
23 
24 //-----------------------------------------------------------------------------
25 // AutoError
26 //-----------------------------------------------------------------------------
27 
28 class AutoError {
29 public:
AutoError(mozilla::dom::ImportLoader * loader,bool scriptsBlocked=true)30   explicit AutoError(mozilla::dom::ImportLoader* loader, bool scriptsBlocked = true)
31     : mLoader(loader)
32     , mPassed(false)
33     , mScriptsBlocked(scriptsBlocked)
34   {}
35 
~AutoError()36   ~AutoError()
37   {
38     if (!mPassed) {
39       mLoader->Error(mScriptsBlocked);
40     }
41   }
42 
Pass()43   void Pass() { mPassed = true; }
44 
45 private:
46   mozilla::dom::ImportLoader* mLoader;
47   bool mPassed;
48   bool mScriptsBlocked;
49 };
50 
51 namespace mozilla {
52 namespace dom {
53 
54 //-----------------------------------------------------------------------------
55 // ImportLoader::Updater
56 //-----------------------------------------------------------------------------
57 
58 void
GetReferrerChain(nsINode * aNode,nsTArray<nsINode * > & aResult)59 ImportLoader::Updater::GetReferrerChain(nsINode* aNode,
60                                         nsTArray<nsINode*>& aResult)
61 {
62   // We fill up the array backward. First the last link: aNode.
63   MOZ_ASSERT(mLoader->mLinks.Contains(aNode));
64 
65   aResult.AppendElement(aNode);
66   nsINode* node = aNode;
67   RefPtr<ImportManager> manager = mLoader->Manager();
68   for (ImportLoader* referrersLoader = manager->Find(node->OwnerDoc());
69        referrersLoader;
70        referrersLoader = manager->Find(node->OwnerDoc()))
71   {
72     // Then walking up the main referrer chain and append each link
73     // to the array.
74     node = referrersLoader->GetMainReferrer();
75     MOZ_ASSERT(node);
76     aResult.AppendElement(node);
77   }
78 
79   // The reversed order is more useful for consumers.
80   // XXX: This should probably go to nsTArray or some generic utility
81   // lib for our containers that we don't have... I would really like to
82   // get rid of this part...
83   uint32_t l = aResult.Length();
84   for (uint32_t i = 0; i < l / 2; i++) {
85     Swap(aResult[i], aResult[l - i - 1]);
86   }
87 }
88 
89 bool
ShouldUpdate(nsTArray<nsINode * > & aNewPath)90 ImportLoader::Updater::ShouldUpdate(nsTArray<nsINode*>& aNewPath)
91 {
92   if (mLoader->Manager()->GetNearestPredecessor(mLoader->GetMainReferrer()) !=
93       mLoader->mBlockingPredecessor) {
94     return true;
95   }
96   // Let's walk down on the main referrer chains of both the current main and
97   // the new link, and find the last pair of links that are from the same
98   // document. This is the junction point between the two referrer chain. Their
99   // order in the subimport list of that document will determine if we have to
100   // update the spanning tree or this new edge changes nothing in the script
101   // execution order.
102   nsTArray<nsINode*> oldPath;
103   GetReferrerChain(mLoader->mLinks[mLoader->mMainReferrer], oldPath);
104   uint32_t max = std::min(oldPath.Length(), aNewPath.Length());
105   MOZ_ASSERT(max > 0);
106   uint32_t lastCommonImportAncestor = 0;
107 
108   for (uint32_t i = 0;
109        i < max && oldPath[i]->OwnerDoc() == aNewPath[i]->OwnerDoc();
110        i++)
111   {
112     lastCommonImportAncestor = i;
113   }
114 
115   MOZ_ASSERT(lastCommonImportAncestor < max);
116   nsINode* oldLink = oldPath[lastCommonImportAncestor];
117   nsINode* newLink = aNewPath[lastCommonImportAncestor];
118 
119   if ((lastCommonImportAncestor == max - 1) &&
120       newLink == oldLink ) {
121     // If one chain contains the other entirely, then this is a simple cycle,
122     // nothing to be done here.
123     MOZ_ASSERT(oldPath.Length() != aNewPath.Length(),
124                "This would mean that new link == main referrer link");
125     return false;
126   }
127 
128   MOZ_ASSERT(aNewPath != oldPath,
129              "How could this happen?");
130   nsIDocument* doc = oldLink->OwnerDoc();
131   MOZ_ASSERT(doc->HasSubImportLink(newLink));
132   MOZ_ASSERT(doc->HasSubImportLink(oldLink));
133 
134   return doc->IndexOfSubImportLink(newLink) < doc->IndexOfSubImportLink(oldLink);
135 }
136 
137 void
UpdateMainReferrer(uint32_t aNewIdx)138 ImportLoader::Updater::UpdateMainReferrer(uint32_t aNewIdx)
139 {
140   MOZ_ASSERT(aNewIdx < mLoader->mLinks.Length());
141   nsINode* newMainReferrer = mLoader->mLinks[aNewIdx];
142 
143   // This new link means we have to execute our scripts sooner...
144   // Let's make sure that unblocking a loader does not trigger a script execution.
145   // So we start with placing the new blockers and only then will we remove any
146   // blockers.
147   if (mLoader->IsBlocking()) {
148     // Our import parent is changed, let's block the new one and later unblock
149     // the old one.
150     newMainReferrer->OwnerDoc()->
151       ScriptLoader()->AddParserBlockingScriptExecutionBlocker();
152     newMainReferrer->OwnerDoc()->BlockDOMContentLoaded();
153   }
154 
155   if (mLoader->mDocument) {
156     // Our nearest predecessor has changed. So let's add the ScriptLoader to the
157     // new one if there is any. And remove it from the old one.
158     RefPtr<ImportManager> manager = mLoader->Manager();
159     nsScriptLoader* loader = mLoader->mDocument->ScriptLoader();
160     ImportLoader*& pred = mLoader->mBlockingPredecessor;
161     ImportLoader* newPred = manager->GetNearestPredecessor(newMainReferrer);
162     if (pred) {
163       if (newPred) {
164         newPred->AddBlockedScriptLoader(loader);
165       }
166       pred->RemoveBlockedScriptLoader(loader);
167     }
168   }
169 
170   if (mLoader->IsBlocking()) {
171     mLoader->mImportParent->
172       ScriptLoader()->RemoveParserBlockingScriptExecutionBlocker();
173     mLoader->mImportParent->UnblockDOMContentLoaded();
174   }
175 
176   // Finally update mMainReferrer to point to the newly added link.
177   mLoader->mMainReferrer = aNewIdx;
178   mLoader->mImportParent = newMainReferrer->OwnerDoc();
179 }
180 
181 nsINode*
NextDependant(nsINode * aCurrentLink,nsTArray<nsINode * > & aPath,NodeTable & aVisitedNodes,bool aSkipChildren)182 ImportLoader::Updater::NextDependant(nsINode* aCurrentLink,
183                                      nsTArray<nsINode*>& aPath,
184                                      NodeTable& aVisitedNodes, bool aSkipChildren)
185 {
186   // Depth first graph traversal.
187   if (!aSkipChildren) {
188     // "first child"
189     ImportLoader* loader = mLoader->Manager()->Find(aCurrentLink);
190     if (loader && loader->GetDocument()) {
191       nsINode* firstSubImport = loader->GetDocument()->GetSubImportLink(0);
192       if (firstSubImport && !aVisitedNodes.Contains(firstSubImport)) {
193         aPath.AppendElement(aCurrentLink);
194         aVisitedNodes.PutEntry(firstSubImport);
195         return firstSubImport;
196       }
197     }
198   }
199 
200   aPath.AppendElement(aCurrentLink);
201   // "(parent's) next sibling"
202   while(aPath.Length() > 1) {
203     aCurrentLink = aPath[aPath.Length() - 1];
204     aPath.RemoveElementAt(aPath.Length() - 1);
205 
206     // Let's find the next "sibling"
207     ImportLoader* loader =  mLoader->Manager()->Find(aCurrentLink->OwnerDoc());
208     MOZ_ASSERT(loader && loader->GetDocument(), "How can this happend?");
209     nsIDocument* doc = loader->GetDocument();
210     MOZ_ASSERT(doc->HasSubImportLink(aCurrentLink));
211     uint32_t idx = doc->IndexOfSubImportLink(aCurrentLink);
212     nsINode* next = doc->GetSubImportLink(idx + 1);
213     if (next) {
214       // Note: If we found an already visited link that means the parent links has
215       // closed the circle it's always the "first child" section that should find
216       // the first already visited node. Let's just assert that.
217       MOZ_ASSERT(!aVisitedNodes.Contains(next));
218       aVisitedNodes.PutEntry(next);
219       return next;
220     }
221   }
222 
223   return nullptr;
224 }
225 
226 void
UpdateDependants(nsINode * aNode,nsTArray<nsINode * > & aPath)227 ImportLoader::Updater::UpdateDependants(nsINode* aNode,
228                                         nsTArray<nsINode*>& aPath)
229 {
230   NodeTable visitedNodes;
231   nsINode* current = aNode;
232   uint32_t initialLength = aPath.Length();
233   bool neededUpdate = true;
234   while ((current = NextDependant(current, aPath, visitedNodes, !neededUpdate))) {
235     if (!current || aPath.Length() <= initialLength) {
236       break;
237     }
238     ImportLoader* loader = mLoader->Manager()->Find(current);
239     if (!loader) {
240       continue;
241     }
242     Updater& updater = loader->mUpdater;
243     neededUpdate = updater.ShouldUpdate(aPath);
244     if (neededUpdate) {
245       updater.UpdateMainReferrer(loader->mLinks.IndexOf(current));
246     }
247   }
248 }
249 
250 void
UpdateSpanningTree(nsINode * aNode)251 ImportLoader::Updater::UpdateSpanningTree(nsINode* aNode)
252 {
253   if (mLoader->mReady || mLoader->mStopped) {
254     // Scripts already executed, nothing to be done here.
255     return;
256   }
257 
258   if (mLoader->mLinks.Length() == 1) {
259     // If this is the first referrer, let's mark it.
260     mLoader->mMainReferrer = 0;
261     return;
262   }
263 
264   nsTArray<nsINode*> newReferrerChain;
265   GetReferrerChain(aNode, newReferrerChain);
266   if (ShouldUpdate(newReferrerChain)) {
267     UpdateMainReferrer(mLoader->mLinks.Length() - 1);
268     UpdateDependants(aNode, newReferrerChain);
269   }
270 }
271 
272 //-----------------------------------------------------------------------------
273 // ImportLoader
274 //-----------------------------------------------------------------------------
275 
276 NS_INTERFACE_MAP_BEGIN(ImportLoader)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)277   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
278   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
279   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ImportLoader)
280 NS_INTERFACE_MAP_END
281 
282 NS_IMPL_CYCLE_COLLECTING_ADDREF(ImportLoader)
283 NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportLoader)
284 
285 NS_IMPL_CYCLE_COLLECTION(ImportLoader,
286                          mDocument,
287                          mImportParent,
288                          mLinks)
289 
290 ImportLoader::ImportLoader(nsIURI* aURI, nsIDocument* aImportParent)
291   : mURI(aURI)
292   , mImportParent(aImportParent)
293   , mBlockingPredecessor(nullptr)
294   , mReady(false)
295   , mStopped(false)
296   , mBlockingScripts(false)
297   , mUpdater(this)
298 {
299 }
300 
301 void
BlockScripts()302 ImportLoader::BlockScripts()
303 {
304   MOZ_ASSERT(!mBlockingScripts);
305   mImportParent->ScriptLoader()->AddParserBlockingScriptExecutionBlocker();
306   mImportParent->BlockDOMContentLoaded();
307   mBlockingScripts = true;
308 }
309 
310 void
UnblockScripts()311 ImportLoader::UnblockScripts()
312 {
313   MOZ_ASSERT(mBlockingScripts);
314   mImportParent->ScriptLoader()->RemoveParserBlockingScriptExecutionBlocker();
315   mImportParent->UnblockDOMContentLoaded();
316   for (uint32_t i = 0; i < mBlockedScriptLoaders.Length(); i++) {
317     mBlockedScriptLoaders[i]->RemoveParserBlockingScriptExecutionBlocker();
318   }
319   mBlockedScriptLoaders.Clear();
320   mBlockingScripts = false;
321 }
322 
323 void
SetBlockingPredecessor(ImportLoader * aLoader)324 ImportLoader::SetBlockingPredecessor(ImportLoader* aLoader)
325 {
326   mBlockingPredecessor = aLoader;
327 }
328 
329 void
DispatchEventIfFinished(nsINode * aNode)330 ImportLoader::DispatchEventIfFinished(nsINode* aNode)
331 {
332   MOZ_ASSERT(!(mReady && mStopped));
333   if (mReady) {
334     DispatchLoadEvent(aNode);
335   }
336   if (mStopped) {
337     DispatchErrorEvent(aNode);
338   }
339 }
340 
341 void
AddBlockedScriptLoader(nsScriptLoader * aScriptLoader)342 ImportLoader::AddBlockedScriptLoader(nsScriptLoader* aScriptLoader)
343 {
344   if (mBlockedScriptLoaders.Contains(aScriptLoader)) {
345     return;
346   }
347 
348   aScriptLoader->AddParserBlockingScriptExecutionBlocker();
349 
350   // Let's keep track of the pending script loaders.
351   mBlockedScriptLoaders.AppendElement(aScriptLoader);
352 }
353 
354 bool
RemoveBlockedScriptLoader(nsScriptLoader * aScriptLoader)355 ImportLoader::RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader)
356 {
357   aScriptLoader->RemoveParserBlockingScriptExecutionBlocker();
358   return mBlockedScriptLoaders.RemoveElement(aScriptLoader);
359 }
360 
361 void
AddLinkElement(nsINode * aNode)362 ImportLoader::AddLinkElement(nsINode* aNode)
363 {
364   // If a new link element is added to the import tree that
365   // refers to an import that is already finished loading or
366   // stopped trying, we need to fire the corresponding event
367   // on it.
368   mLinks.AppendElement(aNode);
369   mUpdater.UpdateSpanningTree(aNode);
370   DispatchEventIfFinished(aNode);
371 }
372 
373 void
RemoveLinkElement(nsINode * aNode)374 ImportLoader::RemoveLinkElement(nsINode* aNode)
375 {
376   mLinks.RemoveElement(aNode);
377 }
378 
379 // Events has to be fired with a script runner, so mImport can
380 // be set on the link element before the load event is fired even
381 // if ImportLoader::Get returns an already loaded import and we
382 // fire the load event immediately on the new referring link element.
383 class AsyncEvent : public Runnable {
384 public:
AsyncEvent(nsINode * aNode,bool aSuccess)385   AsyncEvent(nsINode* aNode, bool aSuccess)
386     : mNode(aNode)
387     , mSuccess(aSuccess)
388   {
389     MOZ_ASSERT(mNode);
390   }
391 
Run()392   NS_IMETHOD Run() override {
393     return nsContentUtils::DispatchTrustedEvent(mNode->OwnerDoc(),
394                                                 mNode,
395                                                 mSuccess ? NS_LITERAL_STRING("load")
396                                                          : NS_LITERAL_STRING("error"),
397                                                 /* aCanBubble = */ false,
398                                                 /* aCancelable = */ false);
399   }
400 
401 private:
402   nsCOMPtr<nsINode> mNode;
403   bool mSuccess;
404 };
405 
406 void
DispatchErrorEvent(nsINode * aNode)407 ImportLoader::DispatchErrorEvent(nsINode* aNode)
408 {
409   nsContentUtils::AddScriptRunner(new AsyncEvent(aNode, /* aSuccess = */ false));
410 }
411 
412 void
DispatchLoadEvent(nsINode * aNode)413 ImportLoader::DispatchLoadEvent(nsINode* aNode)
414 {
415   nsContentUtils::AddScriptRunner(new AsyncEvent(aNode, /* aSuccess = */ true));
416 }
417 
418 void
Done()419 ImportLoader::Done()
420 {
421   mReady = true;
422   uint32_t l = mLinks.Length();
423   for (uint32_t i = 0; i < l; i++) {
424     DispatchLoadEvent(mLinks[i]);
425   }
426   UnblockScripts();
427   ReleaseResources();
428 }
429 
430 void
Error(bool aUnblockScripts)431 ImportLoader::Error(bool aUnblockScripts)
432 {
433   mDocument = nullptr;
434   mStopped = true;
435   uint32_t l = mLinks.Length();
436   for (uint32_t i = 0; i < l; i++) {
437     DispatchErrorEvent(mLinks[i]);
438   }
439   if (aUnblockScripts) {
440     UnblockScripts();
441   }
442   ReleaseResources();
443 }
444 
445 // Release all the resources we don't need after there is no more
446 // data available on the channel, and the parser is done.
ReleaseResources()447 void ImportLoader::ReleaseResources()
448 {
449   mParserStreamListener = nullptr;
450   mImportParent = nullptr;
451 }
452 
453 nsIPrincipal*
Principal()454 ImportLoader::Principal()
455 {
456   MOZ_ASSERT(mImportParent);
457   nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
458   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(master);
459   MOZ_ASSERT(sop);
460   return sop->GetPrincipal();
461 }
462 
463 void
Open()464 ImportLoader::Open()
465 {
466   AutoError ae(this, false);
467 
468   nsCOMPtr<nsILoadGroup> loadGroup =
469     mImportParent->MasterDocument()->GetDocumentLoadGroup();
470 
471   nsCOMPtr<nsIChannel> channel;
472   nsresult rv = NS_NewChannel(getter_AddRefs(channel),
473                               mURI,
474                               mImportParent,
475                               nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
476                               nsIContentPolicy::TYPE_SUBDOCUMENT,
477                               loadGroup,
478                               nullptr,  // aCallbacks
479                               nsIRequest::LOAD_BACKGROUND);
480 
481   NS_ENSURE_SUCCESS_VOID(rv);
482   rv = channel->AsyncOpen2(this);
483   NS_ENSURE_SUCCESS_VOID(rv);
484 
485   BlockScripts();
486   ae.Pass();
487 }
488 
489 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsISupports * aContext,nsIInputStream * aStream,uint64_t aOffset,uint32_t aCount)490 ImportLoader::OnDataAvailable(nsIRequest* aRequest,
491                               nsISupports* aContext,
492                               nsIInputStream* aStream,
493                               uint64_t aOffset,
494                               uint32_t aCount)
495 {
496   MOZ_ASSERT(mParserStreamListener);
497 
498   AutoError ae(this);
499   nsresult rv;
500   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv);
501   NS_ENSURE_SUCCESS(rv, rv);
502 
503   rv = mParserStreamListener->OnDataAvailable(channel, aContext,
504                                               aStream, aOffset,
505                                               aCount);
506   NS_ENSURE_SUCCESS(rv, rv);
507   ae.Pass();
508   return rv;
509 }
510 
511 NS_IMETHODIMP
HandleEvent(nsIDOMEvent * aEvent)512 ImportLoader::HandleEvent(nsIDOMEvent *aEvent)
513 {
514 #ifdef DEBUG
515   nsAutoString type;
516   aEvent->GetType(type);
517   MOZ_ASSERT(type.EqualsLiteral("DOMContentLoaded"));
518 #endif
519   Done();
520   return NS_OK;
521 }
522 
523 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatus)524 ImportLoader::OnStopRequest(nsIRequest* aRequest,
525                             nsISupports* aContext,
526                             nsresult aStatus)
527 {
528   // OnStartRequest throws a special error code to let us know that we
529   // shouldn't do anything else.
530   if (aStatus == NS_ERROR_DOM_ABORT_ERR) {
531     // We failed in OnStartRequest, nothing more to do (we've already
532     // dispatched an error event) just return here.
533     MOZ_ASSERT(mStopped);
534     return NS_OK;
535   }
536 
537   if (mParserStreamListener) {
538     mParserStreamListener->OnStopRequest(aRequest, aContext, aStatus);
539   }
540 
541   if (!mDocument) {
542     // If at this point we don't have a document, then the error was
543     // already reported.
544     return NS_ERROR_DOM_ABORT_ERR;
545   }
546 
547   nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mDocument);
548   EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
549   manager->AddEventListenerByType(this,
550                                   NS_LITERAL_STRING("DOMContentLoaded"),
551                                   TrustedEventsAtSystemGroupBubble());
552   return NS_OK;
553 }
554 
555 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest,nsISupports * aContext)556 ImportLoader::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
557 {
558   AutoError ae(this);
559   nsIPrincipal* principal = Principal();
560 
561   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
562   if (!channel) {
563     return NS_ERROR_DOM_ABORT_ERR;
564   }
565 
566   if (nsContentUtils::IsSystemPrincipal(principal)) {
567     // We should never import non-system documents and run their scripts with system principal!
568     nsCOMPtr<nsIPrincipal> channelPrincipal;
569     nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(channel,
570                                                                     getter_AddRefs(channelPrincipal));
571     if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
572       return NS_ERROR_FAILURE;
573     }
574   }
575   channel->SetOwner(principal);
576 
577   nsAutoCString type;
578   channel->GetContentType(type);
579   if (!type.EqualsLiteral("text/html")) {
580     NS_WARNING("ImportLoader wrong content type");
581     return NS_ERROR_DOM_ABORT_ERR;
582   }
583 
584   // The scope object is same for all the imports in an import tree,
585   // let's get it form the import parent.
586   nsCOMPtr<nsIGlobalObject> global = mImportParent->GetScopeObject();
587   nsCOMPtr<nsIDOMDocument> importDoc;
588   nsCOMPtr<nsIURI> baseURI = mImportParent->GetBaseURI();
589   const nsAString& emptyStr = EmptyString();
590   nsresult rv = NS_NewDOMDocument(getter_AddRefs(importDoc),
591                                   emptyStr, emptyStr, nullptr, mURI,
592                                   baseURI, principal, false, global,
593                                   DocumentFlavorHTML);
594   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
595 
596   // The imported document must know which master document it belongs to.
597   mDocument = do_QueryInterface(importDoc);
598   nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
599   mDocument->SetMasterDocument(master);
600 
601   // We want to inherit the sandbox flags and fullscreen enabled flag
602   // from the master document.
603   mDocument->SetSandboxFlags(master->GetSandboxFlags());
604 
605   // We have to connect the blank document we created with the channel we opened,
606   // and create its own LoadGroup for it.
607   nsCOMPtr<nsIStreamListener> listener;
608   nsCOMPtr<nsILoadGroup> loadGroup;
609   channel->GetLoadGroup(getter_AddRefs(loadGroup));
610   nsCOMPtr<nsILoadGroup> newLoadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
611   NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
612   newLoadGroup->SetLoadGroup(loadGroup);
613   rv = mDocument->StartDocumentLoad("import", channel, newLoadGroup,
614                                     nullptr, getter_AddRefs(listener),
615                                     true);
616   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
617 
618   nsCOMPtr<nsIURI> originalURI;
619   rv = channel->GetOriginalURI(getter_AddRefs(originalURI));
620   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
621 
622   nsCOMPtr<nsIURI> URI;
623   rv = channel->GetURI(getter_AddRefs(URI));
624   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
625   MOZ_ASSERT(URI, "URI of a channel should never be null");
626 
627   bool equals;
628   rv = URI->Equals(originalURI, &equals);
629   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
630 
631   if (!equals) {
632     // In case of a redirection we must add the new URI to the import map.
633     Manager()->AddLoaderWithNewURI(this, URI);
634   }
635 
636   // Let's start the parser.
637   mParserStreamListener = listener;
638   rv = listener->OnStartRequest(aRequest, aContext);
639   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
640 
641   ae.Pass();
642   return NS_OK;
643 }
644 
645 //-----------------------------------------------------------------------------
646 // ImportManager
647 //-----------------------------------------------------------------------------
648 
NS_IMPL_CYCLE_COLLECTION(ImportManager,mImports)649 NS_IMPL_CYCLE_COLLECTION(ImportManager,
650                          mImports)
651 
652 NS_INTERFACE_MAP_BEGIN(ImportManager)
653   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ImportManager)
654 NS_INTERFACE_MAP_END
655 
656 NS_IMPL_CYCLE_COLLECTING_ADDREF(ImportManager)
657 NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportManager)
658 
659 already_AddRefed<ImportLoader>
660 ImportManager::Get(nsIURI* aURI, nsINode* aNode, nsIDocument* aOrigDocument)
661 {
662   // Check if we have a loader for that URI, if not create one,
663   // and start it up.
664   RefPtr<ImportLoader> loader;
665   mImports.Get(aURI, getter_AddRefs(loader));
666   bool needToStart = false;
667   if (!loader) {
668     loader = new ImportLoader(aURI, aOrigDocument);
669     mImports.Put(aURI, loader);
670     needToStart = true;
671   }
672 
673   MOZ_ASSERT(loader);
674   // Let's keep track of the sub imports links in each document. It will
675   // be used later for scrip execution order calculation. (see UpdateSpanningTree)
676   // NOTE: removing and adding back the link to the tree somewhere else will
677   // NOT have an effect on script execution order.
678   if (!aOrigDocument->HasSubImportLink(aNode)) {
679     aOrigDocument->AddSubImportLink(aNode);
680   }
681 
682   loader->AddLinkElement(aNode);
683 
684   if (needToStart) {
685     loader->Open();
686   }
687 
688   return loader.forget();
689 }
690 
691 ImportLoader*
Find(nsIDocument * aImport)692 ImportManager::Find(nsIDocument* aImport)
693 {
694   return mImports.GetWeak(aImport->GetDocumentURIObject());
695 }
696 
697 ImportLoader*
Find(nsINode * aLink)698 ImportManager::Find(nsINode* aLink)
699 {
700   HTMLLinkElement* linkElement = static_cast<HTMLLinkElement*>(aLink);
701   nsCOMPtr<nsIURI> uri = linkElement->GetHrefURI();
702   return mImports.GetWeak(uri);
703 }
704 
705 void
AddLoaderWithNewURI(ImportLoader * aLoader,nsIURI * aNewURI)706 ImportManager::AddLoaderWithNewURI(ImportLoader* aLoader, nsIURI* aNewURI)
707 {
708   mImports.Put(aNewURI, aLoader);
709 }
710 
GetNearestPredecessor(nsINode * aNode)711 ImportLoader* ImportManager::GetNearestPredecessor(nsINode* aNode)
712 {
713   // Return the previous link if there is any in the same document.
714   nsIDocument* doc = aNode->OwnerDoc();
715   int32_t idx = doc->IndexOfSubImportLink(aNode);
716   MOZ_ASSERT(idx != -1, "aNode must be a sub import link of its owner document");
717 
718   for (; idx > 0; idx--) {
719     HTMLLinkElement* link =
720       static_cast<HTMLLinkElement*>(doc->GetSubImportLink(idx - 1));
721     nsCOMPtr<nsIURI> uri = link->GetHrefURI();
722     RefPtr<ImportLoader> ret;
723     mImports.Get(uri, getter_AddRefs(ret));
724     // Only main referrer links are interesting.
725     if (ret->GetMainReferrer() == link) {
726       return ret;
727     }
728   }
729 
730   if (idx == 0) {
731     if (doc->IsMasterDocument()) {
732       // If there is no previous one, and it was the master document, then
733       // there is no predecessor.
734       return nullptr;
735     }
736     // Else we find the main referrer of the import parent of the link's document.
737     // And do a recursion.
738     ImportLoader* owner = Find(doc);
739     MOZ_ASSERT(owner);
740     nsCOMPtr<nsINode> mainReferrer = owner->GetMainReferrer();
741     return GetNearestPredecessor(mainReferrer);
742   }
743 
744   return nullptr;
745 }
746 
747 } // namespace dom
748 } // namespace mozilla
749