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