Bumping manifests a=b2g-bump
[gecko.git] / layout / style / Loader.cpp
blob2605d0f2ca1faf31e4bd7c6b38fa1a2fb651ecf8
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: ft=cpp tw=78 sw=2 et ts=2
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 * This Original Code has been modified by IBM Corporation.
9 * Modifications made by IBM described herein are Copyright (c)
10 * International Business Machines Corporation, 2000. Modifications
11 * to Mozilla code or documentation identified per MPL Section 3.3
13 * Date Modified by Description of modification
14 * 04/20/2000 IBM Corp. OS/2 VisualAge build.
17 /* loading of CSS style sheets using the network APIs */
19 #include "mozilla/ArrayUtils.h"
20 #include "mozilla/LoadInfo.h"
21 #include "mozilla/MemoryReporting.h"
23 #include "mozilla/css/Loader.h"
24 #include "nsIRunnable.h"
25 #include "nsIUnicharStreamLoader.h"
26 #include "nsSyncLoadService.h"
27 #include "nsCOMPtr.h"
28 #include "nsString.h"
29 #include "nsIContent.h"
30 #include "nsIDocument.h"
31 #include "nsIDOMNode.h"
32 #include "nsIDOMDocument.h"
33 #include "nsIURI.h"
34 #include "nsNetUtil.h"
35 #include "nsContentUtils.h"
36 #include "nsIScriptSecurityManager.h"
37 #include "nsContentPolicyUtils.h"
38 #include "nsIHttpChannel.h"
39 #include "nsIHttpChannelInternal.h"
40 #include "nsIScriptError.h"
41 #include "nsMimeTypes.h"
42 #include "nsIStyleSheetLinkingElement.h"
43 #include "nsICSSLoaderObserver.h"
44 #include "nsCSSParser.h"
45 #include "mozilla/CSSStyleSheet.h"
46 #include "mozilla/css/ImportRule.h"
47 #include "nsThreadUtils.h"
48 #include "nsGkAtoms.h"
49 #include "nsIThreadInternal.h"
50 #include "nsCrossSiteListenerProxy.h"
51 #include "nsINetworkPredictor.h"
52 #include "mozilla/dom/ShadowRoot.h"
53 #include "mozilla/dom/URL.h"
55 #ifdef MOZ_XUL
56 #include "nsXULPrototypeCache.h"
57 #endif
59 #include "nsIMediaList.h"
60 #include "nsIDOMStyleSheet.h"
61 #include "nsError.h"
63 #include "nsIChannelPolicy.h"
64 #include "nsIContentSecurityPolicy.h"
66 #include "mozilla/dom/EncodingUtils.h"
67 using mozilla::dom::EncodingUtils;
69 using namespace mozilla::dom;
71 /**
72 * OVERALL ARCHITECTURE
74 * The CSS Loader gets requests to load various sorts of style sheets:
75 * inline style from <style> elements, linked style, @import-ed child
76 * sheets, non-document sheets. The loader handles the following tasks:
78 * 1) Checking whether the load is allowed: CheckLoadAllowed()
79 * 2) Creation of the actual style sheet objects: CreateSheet()
80 * 3) setting of the right media, title, enabled state, etc on the
81 * sheet: PrepareSheet()
82 * 4) Insertion of the sheet in the proper cascade order:
83 * InsertSheetInDoc() and InsertChildSheet()
84 * 5) Load of the sheet: LoadSheet()
85 * 6) Parsing of the sheet: ParseSheet()
86 * 7) Cleanup: SheetComplete()
88 * The detailed documentation for these functions is found with the
89 * function implementations.
91 * The following helper object is used:
92 * SheetLoadData -- a small class that is used to store all the
93 * information needed for the loading of a sheet;
94 * this class handles listening for the stream
95 * loader completion and also handles charset
96 * determination.
99 namespace mozilla {
100 namespace css {
102 /*********************************************
103 * Data needed to properly load a stylesheet *
104 *********************************************/
106 class SheetLoadData : public nsIRunnable,
107 public nsIUnicharStreamLoaderObserver,
108 public nsIThreadObserver
110 protected:
111 virtual ~SheetLoadData(void);
113 public:
114 // Data for loading a sheet linked from a document
115 SheetLoadData(Loader* aLoader,
116 const nsSubstring& aTitle,
117 nsIURI* aURI,
118 CSSStyleSheet* aSheet,
119 nsIStyleSheetLinkingElement* aOwningElement,
120 bool aIsAlternate,
121 nsICSSLoaderObserver* aObserver,
122 nsIPrincipal* aLoaderPrincipal);
124 // Data for loading a sheet linked from an @import rule
125 SheetLoadData(Loader* aLoader,
126 nsIURI* aURI,
127 CSSStyleSheet* aSheet,
128 SheetLoadData* aParentData,
129 nsICSSLoaderObserver* aObserver,
130 nsIPrincipal* aLoaderPrincipal);
132 // Data for loading a non-document sheet
133 SheetLoadData(Loader* aLoader,
134 nsIURI* aURI,
135 CSSStyleSheet* aSheet,
136 bool aSyncLoad,
137 bool aAllowUnsafeRules,
138 bool aUseSystemPrincipal,
139 const nsCString& aCharset,
140 nsICSSLoaderObserver* aObserver,
141 nsIPrincipal* aLoaderPrincipal);
143 already_AddRefed<nsIURI> GetReferrerURI();
145 void ScheduleLoadEventIfNeeded(nsresult aStatus);
147 NS_DECL_ISUPPORTS
148 NS_DECL_NSIRUNNABLE
149 NS_DECL_NSITHREADOBSERVER
150 NS_DECL_NSIUNICHARSTREAMLOADEROBSERVER
152 // Hold a ref to the CSSLoader so we can call back to it to let it
153 // know the load finished
154 nsRefPtr<Loader> mLoader;
156 // Title needed to pull datas out of the pending datas table when
157 // the preferred title is changed
158 nsString mTitle;
160 // Charset we decided to use for the sheet
161 nsCString mCharset;
163 // URI we're loading. Null for inline sheets
164 nsCOMPtr<nsIURI> mURI;
166 // Should be 1 for non-inline sheets.
167 uint32_t mLineNumber;
169 // The sheet we're loading data for
170 nsRefPtr<CSSStyleSheet> mSheet;
172 // Linked list of datas for the same URI as us
173 SheetLoadData* mNext; // strong ref
175 // Load data for the sheet that @import-ed us if we were @import-ed
176 // during the parse
177 nsRefPtr<SheetLoadData> mParentData;
179 // Number of sheets we @import-ed that are still loading
180 uint32_t mPendingChildren;
182 // mSyncLoad is true when the load needs to be synchronous -- right
183 // now only for LoadSheetSync and children of sync loads.
184 bool mSyncLoad : 1;
186 // mIsNonDocumentSheet is true if the load was triggered by LoadSheetSync or
187 // LoadSheet or an @import from such a sheet. Non-document sheet loads can
188 // proceed even if we have no document.
189 bool mIsNonDocumentSheet : 1;
191 // mIsLoading is true from the moment we are placed in the loader's
192 // "loading datas" table (right after the async channel is opened)
193 // to the moment we are removed from said table (due to the load
194 // completing or being cancelled).
195 bool mIsLoading : 1;
197 // mIsCancelled is set to true when a sheet load is stopped by
198 // Stop() or StopLoadingSheet() (which was removed in Bug 556446).
199 // SheetLoadData::OnStreamComplete() checks this to avoid parsing
200 // sheets that have been cancelled and such.
201 bool mIsCancelled : 1;
203 // mMustNotify is true if the load data is being loaded async and
204 // the original function call that started the load has returned.
205 // This applies only to observer notifications; load/error events
206 // are fired for any SheetLoadData that has a non-null
207 // mOwningElement.
208 bool mMustNotify : 1;
210 // mWasAlternate is true if the sheet was an alternate when the load data was
211 // created.
212 bool mWasAlternate : 1;
214 // mAllowUnsafeRules is true if we should allow unsafe rules to be parsed
215 // in the loaded sheet.
216 bool mAllowUnsafeRules : 1;
218 // mUseSystemPrincipal is true if the system principal should be used for
219 // this sheet, no matter what the channel principal is. Only true for sync
220 // loads.
221 bool mUseSystemPrincipal : 1;
223 // If true, this SheetLoadData is being used as a way to handle
224 // async observer notification for an already-complete sheet.
225 bool mSheetAlreadyComplete : 1;
227 // This is the element that imported the sheet. Needed to get the
228 // charset set on it and to fire load/error events.
229 nsCOMPtr<nsIStyleSheetLinkingElement> mOwningElement;
231 // The observer that wishes to be notified of load completion
232 nsCOMPtr<nsICSSLoaderObserver> mObserver;
234 // The principal that identifies who started loading us.
235 nsCOMPtr<nsIPrincipal> mLoaderPrincipal;
237 // The charset to use if the transport and sheet don't indicate one.
238 // May be empty. Must be empty if mOwningElement is non-null.
239 nsCString mCharsetHint;
241 // The status our load ended up with; this determines whether we
242 // should fire error events or load events. This gets initialized
243 // by ScheduleLoadEventIfNeeded, and is only used after that has
244 // been called.
245 nsresult mStatus;
247 private:
248 void FireLoadEvent(nsIThreadInternal* aThread);
251 #ifdef MOZ_LOGGING
252 // #define FORCE_PR_LOG /* Allow logging in the release build */
253 #endif /* MOZ_LOGGING */
254 #include "prlog.h"
256 #ifdef PR_LOGGING
257 static PRLogModuleInfo *
258 GetLoaderLog()
260 static PRLogModuleInfo *sLog;
261 if (!sLog)
262 sLog = PR_NewLogModule("nsCSSLoader");
263 return sLog;
265 #endif /* PR_LOGGING */
267 #define LOG_FORCE(args) PR_LOG(GetLoaderLog(), PR_LOG_ALWAYS, args)
268 #define LOG_ERROR(args) PR_LOG(GetLoaderLog(), PR_LOG_ERROR, args)
269 #define LOG_WARN(args) PR_LOG(GetLoaderLog(), PR_LOG_WARNING, args)
270 #define LOG_DEBUG(args) PR_LOG(GetLoaderLog(), PR_LOG_DEBUG, args)
271 #define LOG(args) LOG_DEBUG(args)
273 #define LOG_FORCE_ENABLED() PR_LOG_TEST(GetLoaderLog(), PR_LOG_ALWAYS)
274 #define LOG_ERROR_ENABLED() PR_LOG_TEST(GetLoaderLog(), PR_LOG_ERROR)
275 #define LOG_WARN_ENABLED() PR_LOG_TEST(GetLoaderLog(), PR_LOG_WARNING)
276 #define LOG_DEBUG_ENABLED() PR_LOG_TEST(GetLoaderLog(), PR_LOG_DEBUG)
277 #define LOG_ENABLED() LOG_DEBUG_ENABLED()
279 #ifdef PR_LOGGING
280 #define LOG_URI(format, uri) \
281 PR_BEGIN_MACRO \
282 NS_ASSERTION(uri, "Logging null uri"); \
283 if (LOG_ENABLED()) { \
284 nsAutoCString _logURISpec; \
285 uri->GetSpec(_logURISpec); \
286 LOG((format, _logURISpec.get())); \
288 PR_END_MACRO
289 #else // PR_LOGGING
290 #define LOG_URI(format, uri)
291 #endif // PR_LOGGING
293 // And some convenience strings...
294 #ifdef PR_LOGGING
295 static const char* const gStateStrings[] = {
296 "eSheetStateUnknown",
297 "eSheetNeedsParser",
298 "eSheetPending",
299 "eSheetLoading",
300 "eSheetComplete"
302 #endif
304 /********************************
305 * SheetLoadData implementation *
306 ********************************/
307 NS_IMPL_ISUPPORTS(SheetLoadData, nsIUnicharStreamLoaderObserver, nsIRunnable,
308 nsIThreadObserver)
310 SheetLoadData::SheetLoadData(Loader* aLoader,
311 const nsSubstring& aTitle,
312 nsIURI* aURI,
313 CSSStyleSheet* aSheet,
314 nsIStyleSheetLinkingElement* aOwningElement,
315 bool aIsAlternate,
316 nsICSSLoaderObserver* aObserver,
317 nsIPrincipal* aLoaderPrincipal)
318 : mLoader(aLoader),
319 mTitle(aTitle),
320 mURI(aURI),
321 mLineNumber(1),
322 mSheet(aSheet),
323 mNext(nullptr),
324 mPendingChildren(0),
325 mSyncLoad(false),
326 mIsNonDocumentSheet(false),
327 mIsLoading(false),
328 mIsCancelled(false),
329 mMustNotify(false),
330 mWasAlternate(aIsAlternate),
331 mAllowUnsafeRules(false),
332 mUseSystemPrincipal(false),
333 mSheetAlreadyComplete(false),
334 mOwningElement(aOwningElement),
335 mObserver(aObserver),
336 mLoaderPrincipal(aLoaderPrincipal)
338 NS_PRECONDITION(mLoader, "Must have a loader!");
341 SheetLoadData::SheetLoadData(Loader* aLoader,
342 nsIURI* aURI,
343 CSSStyleSheet* aSheet,
344 SheetLoadData* aParentData,
345 nsICSSLoaderObserver* aObserver,
346 nsIPrincipal* aLoaderPrincipal)
347 : mLoader(aLoader),
348 mURI(aURI),
349 mLineNumber(1),
350 mSheet(aSheet),
351 mNext(nullptr),
352 mParentData(aParentData),
353 mPendingChildren(0),
354 mSyncLoad(false),
355 mIsNonDocumentSheet(false),
356 mIsLoading(false),
357 mIsCancelled(false),
358 mMustNotify(false),
359 mWasAlternate(false),
360 mAllowUnsafeRules(false),
361 mUseSystemPrincipal(false),
362 mSheetAlreadyComplete(false),
363 mOwningElement(nullptr),
364 mObserver(aObserver),
365 mLoaderPrincipal(aLoaderPrincipal)
367 NS_PRECONDITION(mLoader, "Must have a loader!");
368 if (mParentData) {
369 mSyncLoad = mParentData->mSyncLoad;
370 mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
371 mAllowUnsafeRules = mParentData->mAllowUnsafeRules;
372 mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
373 ++(mParentData->mPendingChildren);
376 NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
377 "Shouldn't use system principal for async loads");
380 SheetLoadData::SheetLoadData(Loader* aLoader,
381 nsIURI* aURI,
382 CSSStyleSheet* aSheet,
383 bool aSyncLoad,
384 bool aAllowUnsafeRules,
385 bool aUseSystemPrincipal,
386 const nsCString& aCharset,
387 nsICSSLoaderObserver* aObserver,
388 nsIPrincipal* aLoaderPrincipal)
389 : mLoader(aLoader),
390 mURI(aURI),
391 mLineNumber(1),
392 mSheet(aSheet),
393 mNext(nullptr),
394 mPendingChildren(0),
395 mSyncLoad(aSyncLoad),
396 mIsNonDocumentSheet(true),
397 mIsLoading(false),
398 mIsCancelled(false),
399 mMustNotify(false),
400 mWasAlternate(false),
401 mAllowUnsafeRules(aAllowUnsafeRules),
402 mUseSystemPrincipal(aUseSystemPrincipal),
403 mSheetAlreadyComplete(false),
404 mOwningElement(nullptr),
405 mObserver(aObserver),
406 mLoaderPrincipal(aLoaderPrincipal),
407 mCharsetHint(aCharset)
409 NS_PRECONDITION(mLoader, "Must have a loader!");
411 NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
412 "Shouldn't use system principal for async loads");
415 SheetLoadData::~SheetLoadData()
417 NS_IF_RELEASE(mNext);
420 NS_IMETHODIMP
421 SheetLoadData::Run()
423 mLoader->HandleLoadEvent(this);
424 return NS_OK;
427 NS_IMETHODIMP
428 SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
430 return NS_OK;
433 NS_IMETHODIMP
434 SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
435 bool aMayWait,
436 uint32_t aRecursionDepth)
438 // We want to fire our load even before or after event processing,
439 // whichever comes first.
440 FireLoadEvent(aThread);
441 return NS_OK;
444 NS_IMETHODIMP
445 SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
446 uint32_t aRecursionDepth,
447 bool aEventWasProcessed)
449 // We want to fire our load even before or after event processing,
450 // whichever comes first.
451 FireLoadEvent(aThread);
452 return NS_OK;
455 void
456 SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
459 // First remove ourselves as a thread observer. But we need to keep
460 // ourselves alive while doing that!
461 nsRefPtr<SheetLoadData> kungFuDeathGrip(this);
462 aThread->RemoveObserver(this);
464 // Now fire the event
465 nsCOMPtr<nsINode> node = do_QueryInterface(mOwningElement);
466 NS_ASSERTION(node, "How did that happen???");
468 nsContentUtils::DispatchTrustedEvent(node->OwnerDoc(),
469 node,
470 NS_SUCCEEDED(mStatus) ?
471 NS_LITERAL_STRING("load") :
472 NS_LITERAL_STRING("error"),
473 false, false);
475 // And unblock onload
476 if (mLoader->mDocument) {
477 mLoader->mDocument->UnblockOnload(true);
481 void
482 SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus)
484 if (!mOwningElement) {
485 return;
488 mStatus = aStatus;
490 nsCOMPtr<nsIThread> thread = do_GetMainThread();
491 nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
492 if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
493 // Make sure to block onload here
494 if (mLoader->mDocument) {
495 mLoader->mDocument->BlockOnload();
500 /*************************
501 * Loader Implementation *
502 *************************/
504 Loader::Loader(void)
505 : mDocument(nullptr)
506 , mDatasToNotifyOn(0)
507 , mCompatMode(eCompatibility_FullStandards)
508 , mEnabled(true)
509 #ifdef DEBUG
510 , mSyncCallback(false)
511 #endif
515 Loader::Loader(nsIDocument* aDocument)
516 : mDocument(aDocument)
517 , mDatasToNotifyOn(0)
518 , mCompatMode(eCompatibility_FullStandards)
519 , mEnabled(true)
520 #ifdef DEBUG
521 , mSyncCallback(false)
522 #endif
524 // We can just use the preferred set, since there are no sheets in the
525 // document yet (if there are, how did they get there? _we_ load the sheets!)
526 // and hence the selected set makes no sense at this time.
527 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
528 if (domDoc) {
529 domDoc->GetPreferredStyleSheetSet(mPreferredSheet);
533 Loader::~Loader()
535 NS_ASSERTION(!mSheets || mSheets->mLoadingDatas.Count() == 0,
536 "How did we get destroyed when there are loading data?");
537 NS_ASSERTION(!mSheets || mSheets->mPendingDatas.Count() == 0,
538 "How did we get destroyed when there are pending data?");
539 // Note: no real need to revoke our stylesheet loaded events -- they
540 // hold strong references to us, so if we're going away that means
541 // they're all done.
544 void
545 Loader::DropDocumentReference(void)
547 mDocument = nullptr;
548 // Flush out pending datas just so we don't leak by accident. These
549 // loads should short-circuit through the mDocument check in
550 // LoadSheet and just end up in SheetComplete immediately
551 if (mSheets) {
552 StartAlternateLoads();
556 static PLDHashOperator
557 CollectNonAlternates(URIPrincipalAndCORSModeHashKey *aKey,
558 SheetLoadData* &aData,
559 void* aClosure)
561 NS_PRECONDITION(aData, "Must have a data");
562 NS_PRECONDITION(aClosure, "Must have an array");
564 // Note that we don't want to affect what the selected style set is,
565 // so use true for aHasAlternateRel.
566 if (aData->mLoader->IsAlternate(aData->mTitle, true)) {
567 return PL_DHASH_NEXT;
570 static_cast<Loader::LoadDataArray*>(aClosure)->AppendElement(aData);
571 return PL_DHASH_REMOVE;
574 nsresult
575 Loader::SetPreferredSheet(const nsAString& aTitle)
577 #ifdef DEBUG
578 nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(mDocument);
579 if (doc) {
580 nsAutoString currentPreferred;
581 doc->GetLastStyleSheetSet(currentPreferred);
582 if (DOMStringIsNull(currentPreferred)) {
583 doc->GetPreferredStyleSheetSet(currentPreferred);
585 NS_ASSERTION(currentPreferred.Equals(aTitle),
586 "Unexpected argument to SetPreferredSheet");
588 #endif
590 mPreferredSheet = aTitle;
592 // start any pending alternates that aren't alternates anymore
593 if (mSheets) {
594 LoadDataArray arr(mSheets->mPendingDatas.Count());
595 mSheets->mPendingDatas.Enumerate(CollectNonAlternates, &arr);
597 mDatasToNotifyOn += arr.Length();
598 for (uint32_t i = 0; i < arr.Length(); ++i) {
599 --mDatasToNotifyOn;
600 LoadSheet(arr[i], eSheetNeedsParser);
604 return NS_OK;
607 static const char kCharsetSym[] = "@charset \"";
609 static bool GetCharsetFromData(const char* aStyleSheetData,
610 uint32_t aDataLength,
611 nsACString& aCharset)
613 aCharset.Truncate();
614 if (aDataLength <= sizeof(kCharsetSym) - 1)
615 return false;
617 if (strncmp(aStyleSheetData,
618 kCharsetSym,
619 sizeof(kCharsetSym) - 1)) {
620 return false;
623 for (uint32_t i = sizeof(kCharsetSym) - 1; i < aDataLength; ++i) {
624 char c = aStyleSheetData[i];
625 if (c == '"') {
626 ++i;
627 if (i < aDataLength && aStyleSheetData[i] == ';') {
628 return true;
630 // fail
631 break;
633 aCharset.Append(c);
636 // Did not see end quote or semicolon
637 aCharset.Truncate();
638 return false;
641 NS_IMETHODIMP
642 SheetLoadData::OnDetermineCharset(nsIUnicharStreamLoader* aLoader,
643 nsISupports* aContext,
644 nsACString const& aSegment,
645 nsACString& aCharset)
647 NS_PRECONDITION(!mOwningElement || mCharsetHint.IsEmpty(),
648 "Can't have element _and_ charset hint");
650 LOG_URI("SheetLoadData::OnDetermineCharset for '%s'", mURI);
652 // The precedence is (per CSS3 Syntax 2012-11-08 ED):
653 // BOM
654 // Channel
655 // @charset rule
656 // charset attribute on the referrer
657 // encoding of the referrer
658 // UTF-8
660 aCharset.Truncate();
662 if (nsContentUtils::CheckForBOM((const unsigned char*)aSegment.BeginReading(),
663 aSegment.Length(),
664 aCharset)) {
665 // aCharset is now either "UTF-16BE", "UTF-16BE" or "UTF-8"
666 // which will swallow the BOM.
667 mCharset.Assign(aCharset);
668 #ifdef PR_LOGGING
669 LOG((" Setting from BOM to: %s", PromiseFlatCString(aCharset).get()));
670 #endif
671 return NS_OK;
674 nsCOMPtr<nsIChannel> channel;
675 nsAutoCString specified;
676 aLoader->GetChannel(getter_AddRefs(channel));
677 if (channel) {
678 channel->GetContentCharset(specified);
679 if (EncodingUtils::FindEncodingForLabel(specified, aCharset)) {
680 mCharset.Assign(aCharset);
681 #ifdef PR_LOGGING
682 LOG((" Setting from HTTP to: %s", PromiseFlatCString(aCharset).get()));
683 #endif
684 return NS_OK;
688 if (GetCharsetFromData(aSegment.BeginReading(),
689 aSegment.Length(),
690 specified)) {
691 if (EncodingUtils::FindEncodingForLabel(specified, aCharset)) {
692 // FindEncodingForLabel currently never returns UTF-16LE but will
693 // probably change to never return UTF-16 instead, so check both here
694 // to avoid relying on the exact behavior.
695 if (aCharset.EqualsLiteral("UTF-16") ||
696 aCharset.EqualsLiteral("UTF-16BE") ||
697 aCharset.EqualsLiteral("UTF-16LE")) {
698 // Be consistent with HTML <meta> handling in face of impossibility.
699 // When the @charset rule itself evidently was not UTF-16-encoded,
700 // it saying UTF-16 has to be a lie.
701 aCharset.AssignLiteral("UTF-8");
703 mCharset.Assign(aCharset);
704 #ifdef PR_LOGGING
705 LOG((" Setting from @charset rule to: %s",
706 PromiseFlatCString(aCharset).get()));
707 #endif
708 return NS_OK;
712 // Now try the charset on the <link> or processing instruction
713 // that loaded us
714 if (mOwningElement) {
715 nsAutoString specified16;
716 mOwningElement->GetCharset(specified16);
717 if (EncodingUtils::FindEncodingForLabel(specified16, aCharset)) {
718 mCharset.Assign(aCharset);
719 #ifdef PR_LOGGING
720 LOG((" Setting from charset attribute to: %s",
721 PromiseFlatCString(aCharset).get()));
722 #endif
723 return NS_OK;
727 // In the preload case, the value of the charset attribute on <link> comes
728 // in via mCharsetHint instead.
729 if (EncodingUtils::FindEncodingForLabel(mCharsetHint, aCharset)) {
730 mCharset.Assign(aCharset);
731 #ifdef PR_LOGGING
732 LOG((" Setting from charset attribute (preload case) to: %s",
733 PromiseFlatCString(aCharset).get()));
734 #endif
735 return NS_OK;
738 // Try charset from the parent stylesheet.
739 if (mParentData) {
740 aCharset = mParentData->mCharset;
741 if (!aCharset.IsEmpty()) {
742 mCharset.Assign(aCharset);
743 #ifdef PR_LOGGING
744 LOG((" Setting from parent sheet to: %s",
745 PromiseFlatCString(aCharset).get()));
746 #endif
747 return NS_OK;
751 if (mLoader->mDocument) {
752 // no useful data on charset. Try the document charset.
753 aCharset = mLoader->mDocument->GetDocumentCharacterSet();
754 MOZ_ASSERT(!aCharset.IsEmpty());
755 mCharset.Assign(aCharset);
756 #ifdef PR_LOGGING
757 LOG((" Setting from document to: %s", PromiseFlatCString(aCharset).get()));
758 #endif
759 return NS_OK;
762 aCharset.AssignLiteral("UTF-8");
763 mCharset = aCharset;
764 #ifdef PR_LOGGING
765 LOG((" Setting from default to: %s", PromiseFlatCString(aCharset).get()));
766 #endif
767 return NS_OK;
770 already_AddRefed<nsIURI>
771 SheetLoadData::GetReferrerURI()
773 nsCOMPtr<nsIURI> uri;
774 if (mParentData)
775 uri = mParentData->mSheet->GetSheetURI();
776 if (!uri && mLoader->mDocument)
777 uri = mLoader->mDocument->GetDocumentURI();
778 return uri.forget();
782 * Here we need to check that the load did not give us an http error
783 * page and check the mimetype on the channel to make sure we're not
784 * loading non-text/css data in standards mode.
786 NS_IMETHODIMP
787 SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
788 nsISupports* aContext,
789 nsresult aStatus,
790 const nsAString& aBuffer)
792 LOG(("SheetLoadData::OnStreamComplete"));
793 NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");
795 if (mIsCancelled) {
796 // Just return. Don't call SheetComplete -- it's already been
797 // called and calling it again will lead to an extra NS_RELEASE on
798 // this data and a likely crash.
799 return NS_OK;
802 if (!mLoader->mDocument && !mIsNonDocumentSheet) {
803 // Sorry, we don't care about this load anymore
804 LOG_WARN((" No document and not non-document sheet; dropping load"));
805 mLoader->SheetComplete(this, NS_BINDING_ABORTED);
806 return NS_OK;
809 if (NS_FAILED(aStatus)) {
810 LOG_WARN((" Load failed: status 0x%x", aStatus));
811 // Handle sheet not loading error because source was a tracking URL.
812 // We make a note of this sheet node by including it in a dedicated
813 // array of blocked tracking nodes under its parent document.
815 // Multiple sheet load instances might be tied to this request,
816 // we annotate each one linked to a valid owning element (node).
817 if (aStatus == NS_ERROR_TRACKING_URI) {
818 nsIDocument* doc = mLoader->GetDocument();
819 if (doc) {
820 for (SheetLoadData* data = this; data; data = data->mNext) {
821 // mOwningElement may be null but AddBlockTrackingNode can cope
822 nsCOMPtr<nsIContent> content = do_QueryInterface(data->mOwningElement);
823 doc->AddBlockedTrackingNode(content);
827 mLoader->SheetComplete(this, aStatus);
828 return NS_OK;
831 nsCOMPtr<nsIChannel> channel;
832 nsresult result = aLoader->GetChannel(getter_AddRefs(channel));
833 if (NS_FAILED(result)) {
834 LOG_WARN((" No channel from loader"));
835 mLoader->SheetComplete(this, result);
836 return NS_OK;
839 nsCOMPtr<nsIURI> originalURI;
840 channel->GetOriginalURI(getter_AddRefs(originalURI));
842 // If the channel's original URI is "chrome:", we want that, since
843 // the observer code in nsXULPrototypeCache depends on chrome stylesheets
844 // having a chrome URI. (Whether or not chrome stylesheets come through
845 // this codepath seems nondeterministic.)
846 // Otherwise we want the potentially-HTTP-redirected URI.
847 nsCOMPtr<nsIURI> channelURI;
848 NS_GetFinalChannelURI(channel, getter_AddRefs(channelURI));
850 if (!channelURI || !originalURI) {
851 NS_ERROR("Someone just violated the nsIRequest contract");
852 LOG_WARN((" Channel without a URI. Bad!"));
853 mLoader->SheetComplete(this, NS_ERROR_UNEXPECTED);
854 return NS_OK;
857 nsCOMPtr<nsIPrincipal> principal;
858 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
859 result = NS_ERROR_NOT_AVAILABLE;
860 if (secMan) { // Could be null if we already shut down
861 if (mUseSystemPrincipal) {
862 result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
863 } else {
864 result = secMan->GetChannelPrincipal(channel, getter_AddRefs(principal));
868 if (NS_FAILED(result)) {
869 LOG_WARN((" Couldn't get principal"));
870 mLoader->SheetComplete(this, result);
871 return NS_OK;
874 mSheet->SetPrincipal(principal);
876 // If it's an HTTP channel, we want to make sure this is not an
877 // error document we got.
878 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
879 if (httpChannel) {
880 bool requestSucceeded;
881 result = httpChannel->GetRequestSucceeded(&requestSucceeded);
882 if (NS_SUCCEEDED(result) && !requestSucceeded) {
883 LOG((" Load returned an error page"));
884 mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
885 return NS_OK;
889 nsAutoCString contentType;
890 if (channel) {
891 channel->GetContentType(contentType);
894 // In standards mode, a style sheet must have one of these MIME
895 // types to be processed at all. In quirks mode, we accept any
896 // MIME type, but only if the style sheet is same-origin with the
897 // requesting document or parent sheet. See bug 524223.
899 bool validType = contentType.EqualsLiteral("text/css") ||
900 contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
901 contentType.IsEmpty();
903 if (!validType) {
904 const char *errorMessage;
905 uint32_t errorFlag;
906 bool sameOrigin = true;
908 if (mLoaderPrincipal) {
909 bool subsumed;
910 result = mLoaderPrincipal->Subsumes(principal, &subsumed);
911 if (NS_FAILED(result) || !subsumed) {
912 sameOrigin = false;
916 if (sameOrigin && mLoader->mCompatMode == eCompatibility_NavQuirks) {
917 errorMessage = "MimeNotCssWarn";
918 errorFlag = nsIScriptError::warningFlag;
919 } else {
920 errorMessage = "MimeNotCss";
921 errorFlag = nsIScriptError::errorFlag;
924 nsAutoCString spec;
925 channelURI->GetSpec(spec);
927 const nsAFlatString& specUTF16 = NS_ConvertUTF8toUTF16(spec);
928 const nsAFlatString& ctypeUTF16 = NS_ConvertASCIItoUTF16(contentType);
929 const char16_t *strings[] = { specUTF16.get(), ctypeUTF16.get() };
931 nsCOMPtr<nsIURI> referrer = GetReferrerURI();
932 nsContentUtils::ReportToConsole(errorFlag,
933 NS_LITERAL_CSTRING("CSS Loader"),
934 mLoader->mDocument,
935 nsContentUtils::eCSS_PROPERTIES,
936 errorMessage,
937 strings, ArrayLength(strings),
938 referrer);
940 if (errorFlag == nsIScriptError::errorFlag) {
941 LOG_WARN((" Ignoring sheet with improper MIME type %s",
942 contentType.get()));
943 mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
944 return NS_OK;
948 // Enough to set the URIs on mSheet, since any sibling datas we have share
949 // the same mInner as mSheet and will thus get the same URI.
950 mSheet->SetURIs(channelURI, originalURI, channelURI);
952 bool completed;
953 result = mLoader->ParseSheet(aBuffer, this, completed);
954 NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
955 return result;
958 bool
959 Loader::IsAlternate(const nsAString& aTitle, bool aHasAlternateRel)
961 // A sheet is alternate if it has a nonempty title that doesn't match the
962 // currently selected style set. But if there _is_ no currently selected
963 // style set, the sheet wasn't marked as an alternate explicitly, and aTitle
964 // is nonempty, we should select the style set corresponding to aTitle, since
965 // that's a preferred sheet.
966 if (aTitle.IsEmpty()) {
967 return false;
970 if (!aHasAlternateRel && mDocument && mPreferredSheet.IsEmpty()) {
971 // There's no preferred set yet, and we now have a sheet with a title.
972 // Make that be the preferred set.
973 mDocument->SetHeaderData(nsGkAtoms::headerDefaultStyle, aTitle);
974 // We're definitely not an alternate
975 return false;
978 return !aTitle.Equals(mPreferredSheet);
981 /* static */ PLDHashOperator
982 Loader::RemoveEntriesWithURI(URIPrincipalAndCORSModeHashKey* aKey,
983 nsRefPtr<CSSStyleSheet>& aSheet,
984 void* aUserData)
986 nsIURI* obsoleteURI = static_cast<nsIURI*>(aUserData);
987 nsIURI* sheetURI = aKey->GetURI();
988 bool areEqual;
989 nsresult rv = sheetURI->Equals(obsoleteURI, &areEqual);
990 if (NS_SUCCEEDED(rv) && areEqual) {
991 return PL_DHASH_REMOVE;
993 return PL_DHASH_NEXT;
996 nsresult
997 Loader::ObsoleteSheet(nsIURI* aURI)
999 if (!mSheets) {
1000 return NS_OK;
1002 if (!aURI) {
1003 return NS_ERROR_INVALID_ARG;
1005 mSheets->mCompleteSheets.Enumerate(RemoveEntriesWithURI, aURI);
1006 return NS_OK;
1010 * CheckLoadAllowed will return success if the load is allowed,
1011 * failure otherwise.
1013 * @param aSourcePrincipal the principal of the node or document or parent
1014 * sheet loading the sheet
1015 * @param aTargetURI the uri of the sheet to be loaded
1016 * @param aContext the node owning the sheet. This is the element or document
1017 * owning the stylesheet (possibly indirectly, for child sheets)
1019 nsresult
1020 Loader::CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
1021 nsIURI* aTargetURI,
1022 nsISupports* aContext)
1024 LOG(("css::Loader::CheckLoadAllowed"));
1026 nsresult rv;
1028 if (aSourcePrincipal) {
1029 // Check with the security manager
1030 nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
1031 rv =
1032 secMan->CheckLoadURIWithPrincipal(aSourcePrincipal, aTargetURI,
1033 nsIScriptSecurityManager::ALLOW_CHROME);
1034 if (NS_FAILED(rv)) { // failure is normal here; don't warn
1035 return rv;
1038 LOG((" Passed security check"));
1040 // Check with content policy
1042 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
1043 rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_STYLESHEET,
1044 aTargetURI,
1045 aSourcePrincipal,
1046 aContext,
1047 NS_LITERAL_CSTRING("text/css"),
1048 nullptr, //extra param
1049 &shouldLoad,
1050 nsContentUtils::GetContentPolicy(),
1051 nsContentUtils::GetSecurityManager());
1053 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
1054 LOG((" Load blocked by content policy"));
1055 return NS_ERROR_CONTENT_BLOCKED;
1059 return NS_OK;
1063 * CreateSheet() creates a CSSStyleSheet object for the given URI,
1064 * if any. If there is no URI given, we just create a new style sheet
1065 * object. Otherwise, we check for an existing style sheet object for
1066 * that uri in various caches and clone it if we find it. Cloned
1067 * sheets will have the title/media/enabled state of the sheet they
1068 * are clones off; make sure to call PrepareSheet() on the result of
1069 * CreateSheet().
1071 nsresult
1072 Loader::CreateSheet(nsIURI* aURI,
1073 nsIContent* aLinkingContent,
1074 nsIPrincipal* aLoaderPrincipal,
1075 CORSMode aCORSMode,
1076 bool aSyncLoad,
1077 bool aHasAlternateRel,
1078 const nsAString& aTitle,
1079 StyleSheetState& aSheetState,
1080 bool *aIsAlternate,
1081 CSSStyleSheet** aSheet)
1083 LOG(("css::Loader::CreateSheet"));
1084 NS_PRECONDITION(aSheet, "Null out param!");
1086 if (!mSheets) {
1087 mSheets = new Sheets();
1090 *aSheet = nullptr;
1091 aSheetState = eSheetStateUnknown;
1093 // Check the alternate state before doing anything else, because it
1094 // can mess with our hashtables.
1095 *aIsAlternate = IsAlternate(aTitle, aHasAlternateRel);
1097 if (aURI) {
1098 aSheetState = eSheetComplete;
1099 nsRefPtr<CSSStyleSheet> sheet;
1101 // First, the XUL cache
1102 #ifdef MOZ_XUL
1103 if (IsChromeURI(aURI)) {
1104 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
1105 if (cache) {
1106 if (cache->IsEnabled()) {
1107 sheet = cache->GetStyleSheet(aURI);
1108 LOG((" From XUL cache: %p", sheet.get()));
1112 #endif
1114 bool fromCompleteSheets = false;
1115 if (!sheet) {
1116 // Then our per-document complete sheets.
1117 URIPrincipalAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode);
1119 mSheets->mCompleteSheets.Get(&key, getter_AddRefs(sheet));
1120 LOG((" From completed: %p", sheet.get()));
1122 fromCompleteSheets = !!sheet;
1125 if (sheet) {
1126 // This sheet came from the XUL cache or our per-document hashtable; it
1127 // better be a complete sheet.
1128 NS_ASSERTION(sheet->IsComplete(),
1129 "Sheet thinks it's not complete while we think it is");
1131 // Make sure it hasn't been modified; if it has, we can't use it
1132 if (sheet->IsModified()) {
1133 LOG((" Not cloning completed sheet %p because it's been modified",
1134 sheet.get()));
1135 sheet = nullptr;
1136 fromCompleteSheets = false;
1140 // Then loading sheets
1141 if (!sheet && !aSyncLoad) {
1142 aSheetState = eSheetLoading;
1143 SheetLoadData* loadData = nullptr;
1144 URIPrincipalAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode);
1145 mSheets->mLoadingDatas.Get(&key, &loadData);
1146 if (loadData) {
1147 sheet = loadData->mSheet;
1148 LOG((" From loading: %p", sheet.get()));
1150 #ifdef DEBUG
1151 bool debugEqual;
1152 NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
1153 (aLoaderPrincipal && loadData->mLoaderPrincipal &&
1154 NS_SUCCEEDED(aLoaderPrincipal->
1155 Equals(loadData->mLoaderPrincipal,
1156 &debugEqual)) && debugEqual),
1157 "Principals should be the same");
1158 #endif
1161 // Then alternate sheets
1162 if (!sheet) {
1163 aSheetState = eSheetPending;
1164 loadData = nullptr;
1165 mSheets->mPendingDatas.Get(&key, &loadData);
1166 if (loadData) {
1167 sheet = loadData->mSheet;
1168 LOG((" From pending: %p", sheet.get()));
1170 #ifdef DEBUG
1171 bool debugEqual;
1172 NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
1173 (aLoaderPrincipal && loadData->mLoaderPrincipal &&
1174 NS_SUCCEEDED(aLoaderPrincipal->
1175 Equals(loadData->mLoaderPrincipal,
1176 &debugEqual)) && debugEqual),
1177 "Principals should be the same");
1178 #endif
1183 if (sheet) {
1184 // The sheet we have now should be either incomplete or unmodified
1185 NS_ASSERTION(!sheet->IsModified() || !sheet->IsComplete(),
1186 "Unexpected modified complete sheet");
1187 NS_ASSERTION(sheet->IsComplete() || aSheetState != eSheetComplete,
1188 "Sheet thinks it's not complete while we think it is");
1190 *aSheet = sheet->Clone(nullptr, nullptr, nullptr, nullptr).take();
1191 if (*aSheet && fromCompleteSheets &&
1192 !sheet->GetOwnerNode() && !sheet->GetParentSheet()) {
1193 // The sheet we're cloning isn't actually referenced by
1194 // anyone. Replace it in the cache, so that if our CSSOM is
1195 // later modified we don't end up with two copies of our inner
1196 // hanging around.
1197 URIPrincipalAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode);
1198 NS_ASSERTION((*aSheet)->IsComplete(),
1199 "Should only be caching complete sheets");
1200 mSheets->mCompleteSheets.Put(&key, *aSheet);
1205 if (!*aSheet) {
1206 aSheetState = eSheetNeedsParser;
1207 nsIURI *sheetURI;
1208 nsCOMPtr<nsIURI> baseURI;
1209 nsIURI* originalURI;
1210 if (!aURI) {
1211 // Inline style. Use the document's base URL so that @import in
1212 // the inline sheet picks up the right base.
1213 NS_ASSERTION(aLinkingContent, "Inline stylesheet without linking content?");
1214 baseURI = aLinkingContent->GetBaseURI();
1215 sheetURI = aLinkingContent->OwnerDoc()->GetDocumentURI();
1216 originalURI = nullptr;
1217 } else {
1218 baseURI = aURI;
1219 sheetURI = aURI;
1220 originalURI = aURI;
1223 nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode);
1224 sheet->SetURIs(sheetURI, originalURI, baseURI);
1225 sheet.forget(aSheet);
1228 NS_ASSERTION(*aSheet, "We should have a sheet by now!");
1229 NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!");
1230 LOG((" State: %s", gStateStrings[aSheetState]));
1232 return NS_OK;
1236 * PrepareSheet() handles setting the media and title on the sheet, as
1237 * well as setting the enabled state based on the title and whether
1238 * the sheet had "alternate" in its rel.
1240 void
1241 Loader::PrepareSheet(CSSStyleSheet* aSheet,
1242 const nsSubstring& aTitle,
1243 const nsSubstring& aMediaString,
1244 nsMediaList* aMediaList,
1245 Element* aScopeElement,
1246 bool isAlternate)
1248 NS_PRECONDITION(aSheet, "Must have a sheet!");
1250 nsRefPtr<nsMediaList> mediaList(aMediaList);
1252 if (!aMediaString.IsEmpty()) {
1253 NS_ASSERTION(!aMediaList,
1254 "must not provide both aMediaString and aMediaList");
1255 mediaList = new nsMediaList();
1257 nsCSSParser mediumParser(this);
1259 // We have aMediaString only when linked from link elements, style
1260 // elements, or PIs, so pass true.
1261 mediumParser.ParseMediaList(aMediaString, nullptr, 0, mediaList, true);
1264 aSheet->SetMedia(mediaList);
1266 aSheet->SetTitle(aTitle);
1267 aSheet->SetEnabled(! isAlternate);
1268 aSheet->SetScopeElement(aScopeElement);
1272 * InsertSheetInDoc handles ordering of sheets in the document. Here
1273 * we have two types of sheets -- those with linking elements and
1274 * those without. The latter are loaded by Link: headers.
1275 * The following constraints are observed:
1276 * 1) Any sheet with a linking element comes after all sheets without
1277 * linking elements
1278 * 2) Sheets without linking elements are inserted in the order in
1279 * which the inserting requests come in, since all of these are
1280 * inserted during header data processing in the content sink
1281 * 3) Sheets with linking elements are ordered based on document order
1282 * as determined by CompareDocumentPosition.
1284 nsresult
1285 Loader::InsertSheetInDoc(CSSStyleSheet* aSheet,
1286 nsIContent* aLinkingContent,
1287 nsIDocument* aDocument)
1289 LOG(("css::Loader::InsertSheetInDoc"));
1290 NS_PRECONDITION(aSheet, "Nothing to insert");
1291 NS_PRECONDITION(aDocument, "Must have a document to insert into");
1293 // XXX Need to cancel pending sheet loads for this element, if any
1295 int32_t sheetCount = aDocument->GetNumberOfStyleSheets();
1298 * Start the walk at the _end_ of the list, since in the typical
1299 * case we'll just want to append anyway. We want to break out of
1300 * the loop when insertionPoint points to just before the index we
1301 * want to insert at. In other words, when we leave the loop
1302 * insertionPoint is the index of the stylesheet that immediately
1303 * precedes the one we're inserting.
1305 int32_t insertionPoint;
1306 for (insertionPoint = sheetCount - 1; insertionPoint >= 0; --insertionPoint) {
1307 nsIStyleSheet *curSheet = aDocument->GetStyleSheetAt(insertionPoint);
1308 NS_ASSERTION(curSheet, "There must be a sheet here!");
1309 nsCOMPtr<nsIDOMStyleSheet> domSheet = do_QueryInterface(curSheet);
1310 NS_ASSERTION(domSheet, "All the \"normal\" sheets implement nsIDOMStyleSheet");
1311 nsCOMPtr<nsIDOMNode> sheetOwner;
1312 domSheet->GetOwnerNode(getter_AddRefs(sheetOwner));
1313 if (sheetOwner && !aLinkingContent) {
1314 // Keep moving; all sheets with a sheetOwner come after all
1315 // sheets without a linkingNode
1316 continue;
1319 if (!sheetOwner) {
1320 // Aha! The current sheet has no sheet owner, so we want to
1321 // insert after it no matter whether we have a linkingNode
1322 break;
1325 nsCOMPtr<nsINode> sheetOwnerNode = do_QueryInterface(sheetOwner);
1326 NS_ASSERTION(aLinkingContent != sheetOwnerNode,
1327 "Why do we still have our old sheet?");
1329 // Have to compare
1330 if (nsContentUtils::PositionIsBefore(sheetOwnerNode, aLinkingContent)) {
1331 // The current sheet comes before us, and it better be the first
1332 // such, because now we break
1333 break;
1337 ++insertionPoint; // adjust the index to the spot we want to insert in
1339 // XXX <meta> elements do not implement nsIStyleSheetLinkingElement;
1340 // need to fix this for them to be ordered correctly.
1341 nsCOMPtr<nsIStyleSheetLinkingElement>
1342 linkingElement = do_QueryInterface(aLinkingContent);
1343 if (linkingElement) {
1344 linkingElement->SetStyleSheet(aSheet); // This sets the ownerNode on the sheet
1347 aDocument->BeginUpdate(UPDATE_STYLE);
1348 aDocument->InsertStyleSheetAt(aSheet, insertionPoint);
1349 aDocument->EndUpdate(UPDATE_STYLE);
1350 LOG((" Inserting into document at position %d", insertionPoint));
1352 return NS_OK;
1356 * InsertChildSheet handles ordering of @import-ed sheet in their
1357 * parent sheets. Here we want to just insert based on order of the
1358 * @import rules that imported the sheets. In theory we can't just
1359 * append to the end because the CSSOM can insert @import rules. In
1360 * practice, we get the call to load the child sheet before the CSSOM
1361 * has finished inserting the @import rule, so we have no idea where
1362 * to put it anyway. So just append for now.
1364 nsresult
1365 Loader::InsertChildSheet(CSSStyleSheet* aSheet,
1366 CSSStyleSheet* aParentSheet,
1367 ImportRule* aParentRule)
1369 LOG(("css::Loader::InsertChildSheet"));
1370 NS_PRECONDITION(aSheet, "Nothing to insert");
1371 NS_PRECONDITION(aParentSheet, "Need a parent to insert into");
1372 NS_PRECONDITION(aParentSheet, "How did we get imported?");
1374 // child sheets should always start out enabled, even if they got
1375 // cloned off of top-level sheets which were disabled
1376 aSheet->SetEnabled(true);
1378 aParentSheet->AppendStyleSheet(aSheet);
1379 aParentRule->SetSheet(aSheet); // This sets the ownerRule on the sheet
1381 LOG((" Inserting into parent sheet"));
1382 // LOG((" Inserting into parent sheet at position %d", insertionPoint));
1384 return NS_OK;
1388 * LoadSheet handles the actual load of a sheet. If the load is
1389 * supposed to be synchronous it just opens a channel synchronously
1390 * using the given uri, wraps the resulting stream in a converter
1391 * stream and calls ParseSheet. Otherwise it tries to look for an
1392 * existing load for this URI and piggyback on it. Failing all that,
1393 * a new load is kicked off asynchronously.
1395 nsresult
1396 Loader::LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState)
1398 LOG(("css::Loader::LoadSheet"));
1399 NS_PRECONDITION(aLoadData, "Need a load data");
1400 NS_PRECONDITION(aLoadData->mURI, "Need a URI to load");
1401 NS_PRECONDITION(aLoadData->mSheet, "Need a sheet to load into");
1402 NS_PRECONDITION(aSheetState != eSheetComplete, "Why bother?");
1403 NS_PRECONDITION(!aLoadData->mUseSystemPrincipal || aLoadData->mSyncLoad,
1404 "Shouldn't use system principal for async loads");
1405 NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
1407 LOG_URI(" Load from: '%s'", aLoadData->mURI);
1409 nsresult rv = NS_OK;
1411 if (!mDocument && !aLoadData->mIsNonDocumentSheet) {
1412 // No point starting the load; just release all the data and such.
1413 LOG_WARN((" No document and not non-document sheet; pre-dropping load"));
1414 SheetComplete(aLoadData, NS_BINDING_ABORTED);
1415 return NS_BINDING_ABORTED;
1418 if (aLoadData->mSyncLoad) {
1419 LOG((" Synchronous load"));
1420 NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
1421 NS_ASSERTION(aSheetState == eSheetNeedsParser,
1422 "Sync loads can't reuse existing async loads");
1424 // Create a nsIUnicharStreamLoader instance to which we will feed
1425 // the data from the sync load. Do this before creating the
1426 // channel to make error recovery simpler.
1427 nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
1428 rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
1429 if (NS_FAILED(rv)) {
1430 LOG_ERROR((" Failed to create stream loader for sync load"));
1431 SheetComplete(aLoadData, rv);
1432 return rv;
1435 if (mDocument) {
1436 mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
1437 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1438 mDocument);
1441 // Just load it
1442 nsCOMPtr<nsIInputStream> stream;
1443 nsCOMPtr<nsIChannel> channel;
1444 rv = NS_OpenURI(getter_AddRefs(stream), aLoadData->mURI, nullptr,
1445 nullptr, nullptr, nsIRequest::LOAD_NORMAL,
1446 getter_AddRefs(channel));
1447 if (NS_FAILED(rv)) {
1448 LOG_ERROR((" Failed to open URI synchronously"));
1449 SheetComplete(aLoadData, rv);
1450 return rv;
1453 NS_ASSERTION(channel, "NS_OpenURI lied?");
1455 // Force UA sheets to be UTF-8.
1456 // XXX this is only necessary because the default in
1457 // SheetLoadData::OnDetermineCharset is wrong (bug 521039).
1458 channel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
1460 // Manually feed the streamloader the contents of the stream we
1461 // got from NS_OpenURI. This will call back into OnStreamComplete
1462 // and thence to ParseSheet. Regardless of whether this fails,
1463 // SheetComplete has been called.
1464 return nsSyncLoadService::PushSyncStreamToListener(stream,
1465 streamLoader,
1466 channel);
1469 SheetLoadData* existingData = nullptr;
1471 URIPrincipalAndCORSModeHashKey key(aLoadData->mURI,
1472 aLoadData->mLoaderPrincipal,
1473 aLoadData->mSheet->GetCORSMode());
1474 if (aSheetState == eSheetLoading) {
1475 mSheets->mLoadingDatas.Get(&key, &existingData);
1476 NS_ASSERTION(existingData, "CreateSheet lied about the state");
1478 else if (aSheetState == eSheetPending){
1479 mSheets->mPendingDatas.Get(&key, &existingData);
1480 NS_ASSERTION(existingData, "CreateSheet lied about the state");
1483 if (existingData) {
1484 LOG((" Glomming on to existing load"));
1485 SheetLoadData* data = existingData;
1486 while (data->mNext) {
1487 data = data->mNext;
1489 data->mNext = aLoadData; // transfer ownership
1490 if (aSheetState == eSheetPending && !aLoadData->mWasAlternate) {
1491 // Kick the load off; someone cares about it right away
1493 #ifdef DEBUG
1494 SheetLoadData* removedData;
1495 NS_ASSERTION(mSheets->mPendingDatas.Get(&key, &removedData) &&
1496 removedData == existingData,
1497 "Bad pending table.");
1498 #endif
1500 mSheets->mPendingDatas.Remove(&key);
1502 LOG((" Forcing load of pending data"));
1503 return LoadSheet(existingData, eSheetNeedsParser);
1505 // All done here; once the load completes we'll be marked complete
1506 // automatically
1507 return NS_OK;
1510 #ifdef DEBUG
1511 mSyncCallback = true;
1512 #endif
1513 nsCOMPtr<nsILoadGroup> loadGroup;
1514 // Content Security Policy information to pass into channel
1515 nsCOMPtr<nsIChannelPolicy> channelPolicy;
1516 if (mDocument) {
1517 loadGroup = mDocument->GetDocumentLoadGroup();
1518 NS_ASSERTION(loadGroup,
1519 "No loadgroup for stylesheet; onload will fire early");
1520 nsCOMPtr<nsIContentSecurityPolicy> csp;
1521 rv = mDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
1522 NS_ENSURE_SUCCESS(rv, rv);
1523 if (csp) {
1524 channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
1525 channelPolicy->SetContentSecurityPolicy(csp);
1526 channelPolicy->SetLoadType(nsIContentPolicy::TYPE_STYLESHEET);
1530 nsCOMPtr<nsIChannel> channel;
1531 rv = NS_NewChannel(getter_AddRefs(channel),
1532 aLoadData->mURI, nullptr, loadGroup, nullptr,
1533 nsIChannel::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI,
1534 channelPolicy);
1536 if (NS_FAILED(rv)) {
1537 #ifdef DEBUG
1538 mSyncCallback = false;
1539 #endif
1540 LOG_ERROR((" Failed to create channel"));
1541 SheetComplete(aLoadData, rv);
1542 return rv;
1545 nsCOMPtr<nsIHttpChannelInternal>
1546 internalHttpChannel(do_QueryInterface(channel));
1547 if (internalHttpChannel)
1548 internalHttpChannel->SetLoadAsBlocking(!aLoadData->mWasAlternate);
1550 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1551 if (httpChannel) {
1552 // send a minimal Accept header for text/css
1553 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
1554 NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
1555 false);
1556 nsCOMPtr<nsIURI> referrerURI = aLoadData->GetReferrerURI();
1557 if (referrerURI)
1558 httpChannel->SetReferrer(referrerURI);
1560 // Set the initiator type
1561 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
1562 if (timedChannel) {
1563 if (aLoadData->mParentData) {
1564 timedChannel->SetInitiatorType(NS_LITERAL_STRING("css"));
1565 } else {
1566 timedChannel->SetInitiatorType(NS_LITERAL_STRING("link"));
1571 // Now tell the channel we expect text/css data back.... We do
1572 // this before opening it, so it's only treated as a hint.
1573 channel->SetContentType(NS_LITERAL_CSTRING("text/css"));
1575 if (aLoadData->mLoaderPrincipal) {
1576 bool inherit;
1577 rv = NS_URIChainHasFlags(aLoadData->mURI,
1578 nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
1579 &inherit);
1580 inherit =
1581 ((NS_SUCCEEDED(rv) && inherit) ||
1582 (nsContentUtils::URIIsLocalFile(aLoadData->mURI) &&
1583 NS_SUCCEEDED(aLoadData->mLoaderPrincipal->
1584 CheckMayLoad(aLoadData->mURI, false, false))));
1585 nsCOMPtr<nsILoadInfo> loadInfo =
1586 new LoadInfo(aLoadData->mLoaderPrincipal,
1587 inherit ?
1588 LoadInfo::eInheritPrincipal : LoadInfo::eDontInheritPrincipal,
1589 LoadInfo::eNotSandboxed);
1590 channel->SetLoadInfo(loadInfo);
1593 // We don't have to hold on to the stream loader. The ownership
1594 // model is: Necko owns the stream loader, which owns the load data,
1595 // which owns us
1596 nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
1597 rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
1598 if (NS_FAILED(rv)) {
1599 #ifdef DEBUG
1600 mSyncCallback = false;
1601 #endif
1602 LOG_ERROR((" Failed to create stream loader"));
1603 SheetComplete(aLoadData, rv);
1604 return rv;
1607 nsCOMPtr<nsIStreamListener> channelListener;
1608 CORSMode ourCORSMode = aLoadData->mSheet->GetCORSMode();
1609 if (ourCORSMode != CORS_NONE) {
1610 bool withCredentials = (ourCORSMode == CORS_USE_CREDENTIALS);
1611 LOG((" Doing CORS-enabled load; credentials %d", withCredentials));
1612 nsRefPtr<nsCORSListenerProxy> corsListener =
1613 new nsCORSListenerProxy(streamLoader, aLoadData->mLoaderPrincipal,
1614 withCredentials);
1615 rv = corsListener->Init(channel);
1616 if (NS_FAILED(rv)) {
1617 #ifdef DEBUG
1618 mSyncCallback = false;
1619 #endif
1620 LOG_ERROR((" Initial CORS check failed"));
1621 SheetComplete(aLoadData, rv);
1622 return rv;
1624 channelListener = corsListener;
1625 } else {
1626 channelListener = streamLoader;
1629 if (mDocument) {
1630 mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
1631 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1632 mDocument);
1635 rv = channel->AsyncOpen(channelListener, nullptr);
1637 #ifdef DEBUG
1638 mSyncCallback = false;
1639 #endif
1641 if (NS_FAILED(rv)) {
1642 LOG_ERROR((" Failed to create stream loader"));
1643 SheetComplete(aLoadData, rv);
1644 return rv;
1647 mSheets->mLoadingDatas.Put(&key, aLoadData);
1648 aLoadData->mIsLoading = true;
1650 return NS_OK;
1654 * ParseSheet handles parsing the data stream. The main idea here is
1655 * to push the current load data onto the parse stack before letting
1656 * the CSS parser at the data stream. That lets us handle @import
1657 * correctly.
1659 nsresult
1660 Loader::ParseSheet(const nsAString& aInput,
1661 SheetLoadData* aLoadData,
1662 bool& aCompleted)
1664 LOG(("css::Loader::ParseSheet"));
1665 NS_PRECONDITION(aLoadData, "Must have load data");
1666 NS_PRECONDITION(aLoadData->mSheet, "Must have sheet to parse into");
1668 aCompleted = false;
1670 nsCSSParser parser(this, aLoadData->mSheet);
1672 // Push our load data on the stack so any kids can pick it up
1673 mParsingDatas.AppendElement(aLoadData);
1674 nsIURI* sheetURI = aLoadData->mSheet->GetSheetURI();
1675 nsIURI* baseURI = aLoadData->mSheet->GetBaseURI();
1676 nsresult rv = parser.ParseSheet(aInput, sheetURI, baseURI,
1677 aLoadData->mSheet->Principal(),
1678 aLoadData->mLineNumber,
1679 aLoadData->mAllowUnsafeRules);
1680 mParsingDatas.RemoveElementAt(mParsingDatas.Length() - 1);
1682 if (NS_FAILED(rv)) {
1683 LOG_ERROR((" Low-level error in parser!"));
1684 SheetComplete(aLoadData, rv);
1685 return rv;
1688 NS_ASSERTION(aLoadData->mPendingChildren == 0 || !aLoadData->mSyncLoad,
1689 "Sync load has leftover pending children!");
1691 if (aLoadData->mPendingChildren == 0) {
1692 LOG((" No pending kids from parse"));
1693 aCompleted = true;
1694 SheetComplete(aLoadData, NS_OK);
1696 // Otherwise, the children are holding strong refs to the data and
1697 // will call SheetComplete() on it when they complete.
1699 return NS_OK;
1703 * SheetComplete is the do-it-all cleanup function. It removes the
1704 * load data from the "loading" hashtable, adds the sheet to the
1705 * "completed" hashtable, massages the XUL cache, handles siblings of
1706 * the load data (other loads for the same URI), handles unblocking
1707 * blocked parent loads as needed, and most importantly calls
1708 * NS_RELEASE on the load data to destroy the whole mess.
1710 void
1711 Loader::SheetComplete(SheetLoadData* aLoadData, nsresult aStatus)
1713 LOG(("css::Loader::SheetComplete"));
1715 // 8 is probably big enough for all our common cases. It's not likely that
1716 // imports will nest more than 8 deep, and multiple sheets with the same URI
1717 // are rare.
1718 nsAutoTArray<nsRefPtr<SheetLoadData>, 8> datasToNotify;
1719 DoSheetComplete(aLoadData, aStatus, datasToNotify);
1721 // Now it's safe to go ahead and notify observers
1722 uint32_t count = datasToNotify.Length();
1723 mDatasToNotifyOn += count;
1724 for (uint32_t i = 0; i < count; ++i) {
1725 --mDatasToNotifyOn;
1727 SheetLoadData* data = datasToNotify[i];
1728 NS_ASSERTION(data && data->mMustNotify, "How did this data get here?");
1729 if (data->mObserver) {
1730 LOG((" Notifying observer 0x%x for data 0x%x. wasAlternate: %d",
1731 data->mObserver.get(), data, data->mWasAlternate));
1732 data->mObserver->StyleSheetLoaded(data->mSheet, data->mWasAlternate,
1733 aStatus);
1736 nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver> >::ForwardIterator iter(mObservers);
1737 nsCOMPtr<nsICSSLoaderObserver> obs;
1738 while (iter.HasMore()) {
1739 obs = iter.GetNext();
1740 LOG((" Notifying global observer 0x%x for data 0x%s. wasAlternate: %d",
1741 obs.get(), data, data->mWasAlternate));
1742 obs->StyleSheetLoaded(data->mSheet, data->mWasAlternate, aStatus);
1746 if (mSheets->mLoadingDatas.Count() == 0 && mSheets->mPendingDatas.Count() > 0) {
1747 LOG((" No more loading sheets; starting alternates"));
1748 StartAlternateLoads();
1752 void
1753 Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
1754 LoadDataArray& aDatasToNotify)
1756 LOG(("css::Loader::DoSheetComplete"));
1757 NS_PRECONDITION(aLoadData, "Must have a load data!");
1758 NS_PRECONDITION(aLoadData->mSheet, "Must have a sheet");
1759 NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
1761 LOG(("Load completed, status: 0x%x", aStatus));
1763 // Twiddle the hashtables
1764 if (aLoadData->mURI) {
1765 LOG_URI(" Finished loading: '%s'", aLoadData->mURI);
1766 // Remove the data from the list of loading datas
1767 if (aLoadData->mIsLoading) {
1768 URIPrincipalAndCORSModeHashKey key(aLoadData->mURI,
1769 aLoadData->mLoaderPrincipal,
1770 aLoadData->mSheet->GetCORSMode());
1771 #ifdef DEBUG
1772 SheetLoadData *loadingData;
1773 NS_ASSERTION(mSheets->mLoadingDatas.Get(&key, &loadingData) &&
1774 loadingData == aLoadData,
1775 "Bad loading table");
1776 #endif
1778 mSheets->mLoadingDatas.Remove(&key);
1779 aLoadData->mIsLoading = false;
1783 // Go through and deal with the whole linked list.
1784 SheetLoadData* data = aLoadData;
1785 while (data) {
1786 if (!data->mSheetAlreadyComplete) {
1787 // If mSheetAlreadyComplete, then the sheet could well be modified between
1788 // when we posted the async call to SheetComplete and now, since the sheet
1789 // was page-accessible during that whole time.
1790 NS_ABORT_IF_FALSE(!data->mSheet->IsModified(),
1791 "should not get marked modified during parsing");
1792 data->mSheet->SetComplete();
1793 data->ScheduleLoadEventIfNeeded(aStatus);
1795 if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
1796 // Don't notify here so we don't trigger script. Remember the
1797 // info we need to notify, then do it later when it's safe.
1798 aDatasToNotify.AppendElement(data);
1800 // On append failure, just press on. We'll fail to notify the observer,
1801 // but not much we can do about that....
1804 NS_ASSERTION(!data->mParentData ||
1805 data->mParentData->mPendingChildren != 0,
1806 "Broken pending child count on our parent");
1808 // If we have a parent, our parent is no longer being parsed, and
1809 // we are the last pending child, then our load completion
1810 // completes the parent too. Note that the parent _can_ still be
1811 // being parsed (eg if the child (us) failed to open the channel
1812 // or some such).
1813 if (data->mParentData &&
1814 --(data->mParentData->mPendingChildren) == 0 &&
1815 !mParsingDatas.Contains(data->mParentData)) {
1816 DoSheetComplete(data->mParentData, aStatus, aDatasToNotify);
1819 data = data->mNext;
1822 // Now that it's marked complete, put the sheet in our cache.
1823 // If we ever start doing this for failure aStatus, we'll need to
1824 // adjust the PostLoadEvent code that thinks anything already
1825 // complete must have loaded succesfully.
1826 if (NS_SUCCEEDED(aStatus) && aLoadData->mURI) {
1827 // Pick our sheet to cache carefully. Ideally, we want to cache
1828 // one of the sheets that will be kept alive by a document or
1829 // parent sheet anyway, so that if someone then accesses it via
1830 // CSSOM we won't have extra clones of the inner lying around.
1831 data = aLoadData;
1832 CSSStyleSheet* sheet = aLoadData->mSheet;
1833 while (data) {
1834 if (data->mSheet->GetParentSheet() || data->mSheet->GetOwnerNode()) {
1835 sheet = data->mSheet;
1836 break;
1838 data = data->mNext;
1840 #ifdef MOZ_XUL
1841 if (IsChromeURI(aLoadData->mURI)) {
1842 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
1843 if (cache && cache->IsEnabled()) {
1844 if (!cache->GetStyleSheet(aLoadData->mURI)) {
1845 LOG((" Putting sheet in XUL prototype cache"));
1846 NS_ASSERTION(sheet->IsComplete(),
1847 "Should only be caching complete sheets");
1848 cache->PutStyleSheet(sheet);
1852 else {
1853 #endif
1854 URIPrincipalAndCORSModeHashKey key(aLoadData->mURI,
1855 aLoadData->mLoaderPrincipal,
1856 aLoadData->mSheet->GetCORSMode());
1857 NS_ASSERTION(sheet->IsComplete(),
1858 "Should only be caching complete sheets");
1859 mSheets->mCompleteSheets.Put(&key, sheet);
1860 #ifdef MOZ_XUL
1862 #endif
1865 NS_RELEASE(aLoadData); // this will release parents and siblings and all that
1868 nsresult
1869 Loader::LoadInlineStyle(nsIContent* aElement,
1870 const nsAString& aBuffer,
1871 uint32_t aLineNumber,
1872 const nsAString& aTitle,
1873 const nsAString& aMedia,
1874 Element* aScopeElement,
1875 nsICSSLoaderObserver* aObserver,
1876 bool* aCompleted,
1877 bool* aIsAlternate)
1879 LOG(("css::Loader::LoadInlineStyle"));
1880 NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
1882 *aCompleted = true;
1884 if (!mEnabled) {
1885 LOG_WARN((" Not enabled"));
1886 return NS_ERROR_NOT_AVAILABLE;
1889 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
1891 nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
1892 NS_ASSERTION(owningElement, "Element is not a style linking element!");
1894 // Since we're not planning to load a URI, no need to hand a principal to the
1895 // load data or to CreateSheet(). Also, OK to use CORS_NONE for the CORS
1896 // mode.
1897 StyleSheetState state;
1898 nsRefPtr<CSSStyleSheet> sheet;
1899 nsresult rv = CreateSheet(nullptr, aElement, nullptr, CORS_NONE, false, false,
1900 aTitle, state, aIsAlternate, getter_AddRefs(sheet));
1901 NS_ENSURE_SUCCESS(rv, rv);
1902 NS_ASSERTION(state == eSheetNeedsParser,
1903 "Inline sheets should not be cached");
1905 LOG((" Sheet is alternate: %d", *aIsAlternate));
1907 PrepareSheet(sheet, aTitle, aMedia, nullptr, aScopeElement, *aIsAlternate);
1909 if (aElement->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
1910 ShadowRoot* containingShadow = aElement->GetContainingShadow();
1911 MOZ_ASSERT(containingShadow);
1912 containingShadow->InsertSheet(sheet, aElement);
1913 } else {
1914 rv = InsertSheetInDoc(sheet, aElement, mDocument);
1915 NS_ENSURE_SUCCESS(rv, rv);
1918 SheetLoadData* data = new SheetLoadData(this, aTitle, nullptr, sheet,
1919 owningElement, *aIsAlternate,
1920 aObserver, nullptr);
1922 // We never actually load this, so just set its principal directly
1923 sheet->SetPrincipal(aElement->NodePrincipal());
1925 NS_ADDREF(data);
1926 data->mLineNumber = aLineNumber;
1927 // Parse completion releases the load data
1928 rv = ParseSheet(aBuffer, data, *aCompleted);
1929 NS_ENSURE_SUCCESS(rv, rv);
1931 // If aCompleted is true, |data| may well be deleted by now.
1932 if (!*aCompleted) {
1933 data->mMustNotify = true;
1935 return rv;
1938 nsresult
1939 Loader::LoadStyleLink(nsIContent* aElement,
1940 nsIURI* aURL,
1941 const nsAString& aTitle,
1942 const nsAString& aMedia,
1943 bool aHasAlternateRel,
1944 CORSMode aCORSMode,
1945 nsICSSLoaderObserver* aObserver,
1946 bool* aIsAlternate)
1948 LOG(("css::Loader::LoadStyleLink"));
1949 NS_PRECONDITION(aURL, "Must have URL to load");
1950 NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
1952 LOG_URI(" Link uri: '%s'", aURL);
1953 LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aTitle).get()));
1954 LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aMedia).get()));
1955 LOG((" Link alternate rel: %d", aHasAlternateRel));
1957 if (!mEnabled) {
1958 LOG_WARN((" Not enabled"));
1959 return NS_ERROR_NOT_AVAILABLE;
1962 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
1964 nsIPrincipal* principal =
1965 aElement ? aElement->NodePrincipal() : mDocument->NodePrincipal();
1967 nsISupports* context = aElement;
1968 if (!context) {
1969 context = mDocument;
1971 nsresult rv = CheckLoadAllowed(principal, aURL, context);
1972 if (NS_FAILED(rv)) return rv;
1974 LOG((" Passed load check"));
1976 StyleSheetState state;
1977 nsRefPtr<CSSStyleSheet> sheet;
1978 rv = CreateSheet(aURL, aElement, principal, aCORSMode, false,
1979 aHasAlternateRel, aTitle, state, aIsAlternate,
1980 getter_AddRefs(sheet));
1981 NS_ENSURE_SUCCESS(rv, rv);
1983 LOG((" Sheet is alternate: %d", *aIsAlternate));
1985 PrepareSheet(sheet, aTitle, aMedia, nullptr, nullptr, *aIsAlternate);
1987 rv = InsertSheetInDoc(sheet, aElement, mDocument);
1988 NS_ENSURE_SUCCESS(rv, rv);
1990 nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
1992 if (state == eSheetComplete) {
1993 LOG((" Sheet already complete: 0x%p",
1994 static_cast<void*>(sheet.get())));
1995 if (aObserver || !mObservers.IsEmpty() || owningElement) {
1996 rv = PostLoadEvent(aURL, sheet, aObserver, *aIsAlternate,
1997 owningElement);
1998 return rv;
2001 return NS_OK;
2004 // Now we need to actually load it
2005 SheetLoadData* data = new SheetLoadData(this, aTitle, aURL, sheet,
2006 owningElement, *aIsAlternate,
2007 aObserver, principal);
2008 NS_ADDREF(data);
2010 // If we have to parse and it's an alternate non-inline, defer it
2011 if (aURL && state == eSheetNeedsParser && mSheets->mLoadingDatas.Count() != 0 &&
2012 *aIsAlternate) {
2013 LOG((" Deferring alternate sheet load"));
2014 URIPrincipalAndCORSModeHashKey key(data->mURI, data->mLoaderPrincipal,
2015 data->mSheet->GetCORSMode());
2016 mSheets->mPendingDatas.Put(&key, data);
2018 data->mMustNotify = true;
2019 return NS_OK;
2022 // Load completion will free the data
2023 rv = LoadSheet(data, state);
2024 NS_ENSURE_SUCCESS(rv, rv);
2026 data->mMustNotify = true;
2027 return rv;
2030 static bool
2031 HaveAncestorDataWithURI(SheetLoadData *aData, nsIURI *aURI)
2033 if (!aData->mURI) {
2034 // Inline style; this won't have any ancestors
2035 NS_ABORT_IF_FALSE(!aData->mParentData,
2036 "How does inline style have a parent?");
2037 return false;
2040 bool equal;
2041 if (NS_FAILED(aData->mURI->Equals(aURI, &equal)) || equal) {
2042 return true;
2045 // Datas down the mNext chain have the same URI as aData, so we
2046 // don't have to compare to them. But they might have different
2047 // parents, and we have to check all of those.
2048 while (aData) {
2049 if (aData->mParentData &&
2050 HaveAncestorDataWithURI(aData->mParentData, aURI)) {
2051 return true;
2054 aData = aData->mNext;
2057 return false;
2060 nsresult
2061 Loader::LoadChildSheet(CSSStyleSheet* aParentSheet,
2062 nsIURI* aURL,
2063 nsMediaList* aMedia,
2064 ImportRule* aParentRule)
2066 LOG(("css::Loader::LoadChildSheet"));
2067 NS_PRECONDITION(aURL, "Must have a URI to load");
2068 NS_PRECONDITION(aParentSheet, "Must have a parent sheet");
2070 if (!mEnabled) {
2071 LOG_WARN((" Not enabled"));
2072 return NS_ERROR_NOT_AVAILABLE;
2075 LOG_URI(" Child uri: '%s'", aURL);
2077 nsCOMPtr<nsIDOMNode> owningNode;
2079 // check for an owning document: if none, don't bother walking up the parent
2080 // sheets
2081 if (aParentSheet->GetOwningDocument()) {
2082 nsCOMPtr<nsIDOMStyleSheet> nextParentSheet(aParentSheet);
2083 NS_ENSURE_TRUE(nextParentSheet, NS_ERROR_FAILURE); //Not a stylesheet!?
2085 nsCOMPtr<nsIDOMStyleSheet> topSheet;
2086 //traverse our way to the top-most sheet
2087 do {
2088 topSheet.swap(nextParentSheet);
2089 topSheet->GetParentStyleSheet(getter_AddRefs(nextParentSheet));
2090 } while (nextParentSheet);
2092 topSheet->GetOwnerNode(getter_AddRefs(owningNode));
2095 nsISupports* context = owningNode;
2096 if (!context) {
2097 context = mDocument;
2100 nsIPrincipal* principal = aParentSheet->Principal();
2101 nsresult rv = CheckLoadAllowed(principal, aURL, context);
2102 if (NS_FAILED(rv)) return rv;
2104 LOG((" Passed load check"));
2106 SheetLoadData* parentData = nullptr;
2107 nsCOMPtr<nsICSSLoaderObserver> observer;
2109 int32_t count = mParsingDatas.Length();
2110 if (count > 0) {
2111 LOG((" Have a parent load"));
2112 parentData = mParsingDatas.ElementAt(count - 1);
2113 // Check for cycles
2114 if (HaveAncestorDataWithURI(parentData, aURL)) {
2115 // Houston, we have a loop, blow off this child and pretend this never
2116 // happened
2117 LOG_ERROR((" @import cycle detected, dropping load"));
2118 return NS_OK;
2121 NS_ASSERTION(parentData->mSheet == aParentSheet,
2122 "Unexpected call to LoadChildSheet");
2123 } else {
2124 LOG((" No parent load; must be CSSOM"));
2125 // No parent load data, so the sheet will need to be notified when
2126 // we finish, if it can be, if we do the load asynchronously.
2127 observer = aParentSheet;
2130 // Now that we know it's safe to load this (passes security check and not a
2131 // loop) do so.
2132 nsRefPtr<CSSStyleSheet> sheet;
2133 bool isAlternate;
2134 StyleSheetState state;
2135 const nsSubstring& empty = EmptyString();
2136 // For now, use CORS_NONE for child sheets
2137 rv = CreateSheet(aURL, nullptr, principal, CORS_NONE,
2138 parentData ? parentData->mSyncLoad : false,
2139 false, empty, state, &isAlternate, getter_AddRefs(sheet));
2140 NS_ENSURE_SUCCESS(rv, rv);
2142 PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate);
2144 rv = InsertChildSheet(sheet, aParentSheet, aParentRule);
2145 NS_ENSURE_SUCCESS(rv, rv);
2147 if (state == eSheetComplete) {
2148 LOG((" Sheet already complete"));
2149 // We're completely done. No need to notify, even, since the
2150 // @import rule addition/modification will trigger the right style
2151 // changes automatically.
2152 return NS_OK;
2155 SheetLoadData* data = new SheetLoadData(this, aURL, sheet, parentData,
2156 observer, principal);
2158 NS_ADDREF(data);
2159 bool syncLoad = data->mSyncLoad;
2161 // Load completion will release the data
2162 rv = LoadSheet(data, state);
2163 NS_ENSURE_SUCCESS(rv, rv);
2165 // If syncLoad is true, |data| will be deleted by now.
2166 if (!syncLoad) {
2167 data->mMustNotify = true;
2169 return rv;
2172 nsresult
2173 Loader::LoadSheetSync(nsIURI* aURL, bool aAllowUnsafeRules,
2174 bool aUseSystemPrincipal,
2175 CSSStyleSheet** aSheet)
2177 LOG(("css::Loader::LoadSheetSync"));
2178 return InternalLoadNonDocumentSheet(aURL, aAllowUnsafeRules,
2179 aUseSystemPrincipal, nullptr,
2180 EmptyCString(), aSheet, nullptr);
2183 nsresult
2184 Loader::LoadSheet(nsIURI* aURL,
2185 nsIPrincipal* aOriginPrincipal,
2186 const nsCString& aCharset,
2187 nsICSSLoaderObserver* aObserver,
2188 CSSStyleSheet** aSheet)
2190 LOG(("css::Loader::LoadSheet(aURL, aObserver, aSheet) api call"));
2191 NS_PRECONDITION(aSheet, "aSheet is null");
2192 return InternalLoadNonDocumentSheet(aURL, false, false,
2193 aOriginPrincipal, aCharset,
2194 aSheet, aObserver);
2197 nsresult
2198 Loader::LoadSheet(nsIURI* aURL,
2199 nsIPrincipal* aOriginPrincipal,
2200 const nsCString& aCharset,
2201 nsICSSLoaderObserver* aObserver,
2202 CORSMode aCORSMode)
2204 LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
2205 return InternalLoadNonDocumentSheet(aURL, false, false,
2206 aOriginPrincipal, aCharset,
2207 nullptr, aObserver, aCORSMode);
2210 nsresult
2211 Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
2212 bool aAllowUnsafeRules,
2213 bool aUseSystemPrincipal,
2214 nsIPrincipal* aOriginPrincipal,
2215 const nsCString& aCharset,
2216 CSSStyleSheet** aSheet,
2217 nsICSSLoaderObserver* aObserver,
2218 CORSMode aCORSMode)
2220 NS_PRECONDITION(aURL, "Must have a URI to load");
2221 NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
2222 NS_PRECONDITION(!aUseSystemPrincipal || !aObserver,
2223 "Shouldn't load system-principal sheets async");
2224 NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
2226 LOG_URI(" Non-document sheet uri: '%s'", aURL);
2228 if (aSheet) {
2229 *aSheet = nullptr;
2232 if (!mEnabled) {
2233 LOG_WARN((" Not enabled"));
2234 return NS_ERROR_NOT_AVAILABLE;
2237 nsresult rv = CheckLoadAllowed(aOriginPrincipal, aURL, mDocument);
2238 if (NS_FAILED(rv)) {
2239 return rv;
2242 StyleSheetState state;
2243 bool isAlternate;
2244 nsRefPtr<CSSStyleSheet> sheet;
2245 bool syncLoad = (aObserver == nullptr);
2246 const nsSubstring& empty = EmptyString();
2248 rv = CreateSheet(aURL, nullptr, aOriginPrincipal, aCORSMode, syncLoad, false,
2249 empty, state, &isAlternate, getter_AddRefs(sheet));
2250 NS_ENSURE_SUCCESS(rv, rv);
2252 PrepareSheet(sheet, empty, empty, nullptr, nullptr, isAlternate);
2254 if (state == eSheetComplete) {
2255 LOG((" Sheet already complete"));
2256 if (aObserver || !mObservers.IsEmpty()) {
2257 rv = PostLoadEvent(aURL, sheet, aObserver, false, nullptr);
2259 if (aSheet) {
2260 sheet.swap(*aSheet);
2262 return rv;
2265 SheetLoadData* data =
2266 new SheetLoadData(this, aURL, sheet, syncLoad, aAllowUnsafeRules,
2267 aUseSystemPrincipal, aCharset, aObserver,
2268 aOriginPrincipal);
2270 NS_ADDREF(data);
2271 rv = LoadSheet(data, state);
2272 NS_ENSURE_SUCCESS(rv, rv);
2274 if (aSheet) {
2275 sheet.swap(*aSheet);
2277 if (aObserver) {
2278 data->mMustNotify = true;
2281 return rv;
2284 nsresult
2285 Loader::PostLoadEvent(nsIURI* aURI,
2286 CSSStyleSheet* aSheet,
2287 nsICSSLoaderObserver* aObserver,
2288 bool aWasAlternate,
2289 nsIStyleSheetLinkingElement* aElement)
2291 LOG(("css::Loader::PostLoadEvent"));
2292 NS_PRECONDITION(aSheet, "Must have sheet");
2293 NS_PRECONDITION(aObserver || !mObservers.IsEmpty() || aElement,
2294 "Must have observer or element");
2296 nsRefPtr<SheetLoadData> evt =
2297 new SheetLoadData(this, EmptyString(), // title doesn't matter here
2298 aURI,
2299 aSheet,
2300 aElement,
2301 aWasAlternate,
2302 aObserver,
2303 nullptr);
2304 NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY);
2306 if (!mPostedEvents.AppendElement(evt)) {
2307 return NS_ERROR_OUT_OF_MEMORY;
2310 nsresult rv = NS_DispatchToCurrentThread(evt);
2311 if (NS_FAILED(rv)) {
2312 NS_WARNING("failed to dispatch stylesheet load event");
2313 mPostedEvents.RemoveElement(evt);
2314 } else {
2315 // We'll unblock onload when we handle the event.
2316 if (mDocument) {
2317 mDocument->BlockOnload();
2320 // We want to notify the observer for this data.
2321 evt->mMustNotify = true;
2322 evt->mSheetAlreadyComplete = true;
2324 // If we get to this code, aSheet loaded correctly at some point, so
2325 // we can just use NS_OK for the status. Note that we do this here
2326 // and not from inside our SheetComplete so that we don't end up
2327 // running the load event async.
2328 evt->ScheduleLoadEventIfNeeded(NS_OK);
2331 return rv;
2334 void
2335 Loader::HandleLoadEvent(SheetLoadData* aEvent)
2337 // XXXbz can't assert this yet.... May not have an observer because
2338 // we're unblocking the parser
2339 // NS_ASSERTION(aEvent->mObserver, "Must have observer");
2340 NS_ASSERTION(aEvent->mSheet, "Must have sheet");
2342 // Very important: this needs to come before the SheetComplete call
2343 // below, so that HasPendingLoads() will test true as needed under
2344 // notifications we send from that SheetComplete call.
2345 mPostedEvents.RemoveElement(aEvent);
2347 if (!aEvent->mIsCancelled) {
2348 // SheetComplete will call Release(), so give it a reference to do
2349 // that with.
2350 NS_ADDREF(aEvent);
2351 SheetComplete(aEvent, NS_OK);
2354 if (mDocument) {
2355 mDocument->UnblockOnload(true);
2359 static PLDHashOperator
2360 StopLoadingSheetCallback(URIPrincipalAndCORSModeHashKey* aKey,
2361 SheetLoadData*& aData,
2362 void* aClosure)
2364 NS_PRECONDITION(aData, "Must have a data!");
2365 NS_PRECONDITION(aClosure, "Must have a loader");
2367 aData->mIsLoading = false; // we will handle the removal right here
2368 aData->mIsCancelled = true;
2370 static_cast<Loader::LoadDataArray*>(aClosure)->AppendElement(aData);
2372 return PL_DHASH_REMOVE;
2375 nsresult
2376 Loader::Stop()
2378 uint32_t pendingCount =
2379 mSheets ? mSheets->mPendingDatas.Count() : 0;
2380 uint32_t loadingCount =
2381 mSheets ? mSheets->mLoadingDatas.Count() : 0;
2382 LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());
2384 if (pendingCount) {
2385 mSheets->mPendingDatas.Enumerate(StopLoadingSheetCallback, &arr);
2387 if (loadingCount) {
2388 mSheets->mLoadingDatas.Enumerate(StopLoadingSheetCallback, &arr);
2391 uint32_t i;
2392 for (i = 0; i < mPostedEvents.Length(); ++i) {
2393 SheetLoadData* data = mPostedEvents[i];
2394 data->mIsCancelled = true;
2395 if (arr.AppendElement(data)) {
2396 // SheetComplete() calls Release(), so give this an extra ref.
2397 NS_ADDREF(data);
2399 #ifdef DEBUG
2400 else {
2401 NS_NOTREACHED("We preallocated this memory... shouldn't really fail, "
2402 "except we never check that preallocation succeeds.");
2404 #endif
2406 mPostedEvents.Clear();
2408 mDatasToNotifyOn += arr.Length();
2409 for (i = 0; i < arr.Length(); ++i) {
2410 --mDatasToNotifyOn;
2411 SheetComplete(arr[i], NS_BINDING_ABORTED);
2413 return NS_OK;
2416 bool
2417 Loader::HasPendingLoads()
2419 return
2420 (mSheets && mSheets->mLoadingDatas.Count() != 0) ||
2421 (mSheets && mSheets->mPendingDatas.Count() != 0) ||
2422 mPostedEvents.Length() != 0 ||
2423 mDatasToNotifyOn != 0;
2426 nsresult
2427 Loader::AddObserver(nsICSSLoaderObserver* aObserver)
2429 NS_PRECONDITION(aObserver, "Must have observer");
2430 if (mObservers.AppendElementUnlessExists(aObserver)) {
2431 return NS_OK;
2434 return NS_ERROR_OUT_OF_MEMORY;
2437 void
2438 Loader::RemoveObserver(nsICSSLoaderObserver* aObserver)
2440 mObservers.RemoveElement(aObserver);
2443 static PLDHashOperator
2444 CollectLoadDatas(URIPrincipalAndCORSModeHashKey *aKey,
2445 SheetLoadData* &aData,
2446 void* aClosure)
2448 static_cast<Loader::LoadDataArray*>(aClosure)->AppendElement(aData);
2449 return PL_DHASH_REMOVE;
2452 void
2453 Loader::StartAlternateLoads()
2455 NS_PRECONDITION(mSheets, "Don't call me!");
2456 LoadDataArray arr(mSheets->mPendingDatas.Count());
2457 mSheets->mPendingDatas.Enumerate(CollectLoadDatas, &arr);
2459 mDatasToNotifyOn += arr.Length();
2460 for (uint32_t i = 0; i < arr.Length(); ++i) {
2461 --mDatasToNotifyOn;
2462 LoadSheet(arr[i], eSheetNeedsParser);
2466 static PLDHashOperator
2467 TraverseSheet(URIPrincipalAndCORSModeHashKey*,
2468 CSSStyleSheet* aSheet,
2469 void* aClosure)
2471 nsCycleCollectionTraversalCallback* cb =
2472 static_cast<nsCycleCollectionTraversalCallback*>(aClosure);
2473 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "Sheet cache nsCSSLoader");
2474 cb->NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIStyleSheet*, aSheet));
2475 return PL_DHASH_NEXT;
2478 NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)
2480 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Loader)
2481 if (tmp->mSheets) {
2482 tmp->mSheets->mCompleteSheets.EnumerateRead(TraverseSheet, &cb);
2484 nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>>::ForwardIterator
2485 it(tmp->mObservers);
2486 while (it.HasMore()) {
2487 ImplCycleCollectionTraverse(cb, it.GetNext(),
2488 "mozilla::css::Loader.mObservers");
2490 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2492 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
2493 if (tmp->mSheets) {
2494 tmp->mSheets->mCompleteSheets.Clear();
2496 tmp->mObservers.Clear();
2497 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2499 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Loader, AddRef)
2500 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Loader, Release)
2502 struct SheetMemoryCounter {
2503 size_t size;
2504 mozilla::MallocSizeOf mallocSizeOf;
2507 static size_t
2508 CountSheetMemory(URIPrincipalAndCORSModeHashKey* /* unused */,
2509 const nsRefPtr<CSSStyleSheet>& aSheet,
2510 mozilla::MallocSizeOf aMallocSizeOf,
2511 void* /* unused */)
2513 // If aSheet has a parent, then its parent will report it so we don't
2514 // have to worry about it here.
2515 // Likewise, if aSheet has an owning node, then the document that
2516 // node is in will report it.
2517 if (aSheet->GetOwnerNode() || aSheet->GetParentSheet()) {
2518 return 0;
2520 return aSheet->SizeOfIncludingThis(aMallocSizeOf);
2523 size_t
2524 Loader::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
2526 size_t s = aMallocSizeOf(this);
2528 if (mSheets) {
2529 s += mSheets->mCompleteSheets.SizeOfExcludingThis(CountSheetMemory, aMallocSizeOf);
2531 s += mObservers.SizeOfExcludingThis(aMallocSizeOf);
2533 // Measurement of the following members may be added later if DMD finds it is
2534 // worthwhile:
2535 // - mLoadingDatas: transient, and should be small
2536 // - mPendingDatas: transient, and should be small
2537 // - mParsingDatas: transient, and should be small
2538 // - mPostedEvents: transient, and should be small
2540 // The following members aren't measured:
2541 // - mDocument, because it's a weak backpointer
2542 // - mPreferredSheet, because it can be a shared string
2544 return s;
2547 } // namespace css
2548 } // namespace mozilla