gecko-dev/layout/style/Loader.cpp

2691 строка
92 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: ft=cpp tw=78 sw=2 et ts=2
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This Original Code has been modified by IBM Corporation.
* Modifications made by IBM described herein are Copyright (c)
* International Business Machines Corporation, 2000. Modifications
* to Mozilla code or documentation identified per MPL Section 3.3
*
* Date Modified by Description of modification
* 04/20/2000 IBM Corp. OS/2 VisualAge build.
*/
/* loading of CSS style sheets using the network APIs */
#include "mozilla/ArrayUtils.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/css/Loader.h"
#include "mozilla/StyleSheetInlines.h"
#include "nsIRunnable.h"
#include "nsIUnicharStreamLoader.h"
#include "nsSyncLoadService.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIDOMNode.h"
#include "nsIDOMDocument.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsIProtocolHandler.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "nsContentPolicyUtils.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIClassOfService.h"
#include "nsIScriptError.h"
#include "nsMimeTypes.h"
#include "nsIStyleSheetLinkingElement.h"
#include "nsICSSLoaderObserver.h"
#include "nsCSSParser.h"
#include "mozilla/css/ImportRule.h"
#include "nsThreadUtils.h"
#include "nsGkAtoms.h"
#include "nsIThreadInternal.h"
#include "nsINetworkPredictor.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/URL.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/ConsoleReportCollector.h"
#ifdef MOZ_XUL
#include "nsXULPrototypeCache.h"
#endif
#include "nsIMediaList.h"
#include "nsIDOMStyleSheet.h"
#include "nsError.h"
#include "nsIContentSecurityPolicy.h"
#include "mozilla/dom/SRICheck.h"
#include "mozilla/dom/EncodingUtils.h"
using mozilla::dom::EncodingUtils;
using namespace mozilla::dom;
/**
* OVERALL ARCHITECTURE
*
* The CSS Loader gets requests to load various sorts of style sheets:
* inline style from <style> elements, linked style, @import-ed child
* sheets, non-document sheets. The loader handles the following tasks:
* 1) Creation of the actual style sheet objects: CreateSheet()
* 2) setting of the right media, title, enabled state, etc on the
* sheet: PrepareSheet()
* 3) Insertion of the sheet in the proper cascade order:
* InsertSheetInDoc() and InsertChildSheet()
* 4) Load of the sheet: LoadSheet() including security checks
* 5) Parsing of the sheet: ParseSheet()
* 6) Cleanup: SheetComplete()
*
* The detailed documentation for these functions is found with the
* function implementations.
*
* The following helper object is used:
* SheetLoadData -- a small class that is used to store all the
* information needed for the loading of a sheet;
* this class handles listening for the stream
* loader completion and also handles charset
* determination.
*/
namespace mozilla {
namespace css {
/*********************************************
* Data needed to properly load a stylesheet *
*********************************************/
static_assert(eAuthorSheetFeatures == 0 &&
eUserSheetFeatures == 1 &&
eAgentSheetFeatures == 2,
"sheet parsing mode constants won't fit "
"in SheetLoadData::mParsingMode");
class SheetLoadData final : public nsIRunnable,
public nsIUnicharStreamLoaderObserver,
public nsIThreadObserver
{
protected:
virtual ~SheetLoadData(void);
public:
// Data for loading a sheet linked from a document
SheetLoadData(Loader* aLoader,
const nsSubstring& aTitle,
nsIURI* aURI,
StyleSheet* aSheet,
nsIStyleSheetLinkingElement* aOwningElement,
bool aIsAlternate,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aLoaderPrincipal,
nsINode* aRequestingNode);
// Data for loading a sheet linked from an @import rule
SheetLoadData(Loader* aLoader,
nsIURI* aURI,
StyleSheet* aSheet,
SheetLoadData* aParentData,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aLoaderPrincipal,
nsINode* aRequestingNode);
// Data for loading a non-document sheet
SheetLoadData(Loader* aLoader,
nsIURI* aURI,
StyleSheet* aSheet,
bool aSyncLoad,
bool aUseSystemPrincipal,
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aLoaderPrincipal,
nsINode* aRequestingNode);
already_AddRefed<nsIURI> GetReferrerURI();
void ScheduleLoadEventIfNeeded(nsresult aStatus);
NS_DECL_ISUPPORTS
NS_DECL_NSIRUNNABLE
NS_DECL_NSITHREADOBSERVER
NS_DECL_NSIUNICHARSTREAMLOADEROBSERVER
// Hold a ref to the CSSLoader so we can call back to it to let it
// know the load finished
RefPtr<Loader> mLoader;
// Title needed to pull datas out of the pending datas table when
// the preferred title is changed
nsString mTitle;
// Charset we decided to use for the sheet
nsCString mCharset;
// URI we're loading. Null for inline sheets
nsCOMPtr<nsIURI> mURI;
// Should be 1 for non-inline sheets.
uint32_t mLineNumber;
// The sheet we're loading data for
RefPtr<StyleSheet> mSheet;
// Linked list of datas for the same URI as us
SheetLoadData* mNext; // strong ref
// Load data for the sheet that @import-ed us if we were @import-ed
// during the parse
RefPtr<SheetLoadData> mParentData;
// Number of sheets we @import-ed that are still loading
uint32_t mPendingChildren;
// mSyncLoad is true when the load needs to be synchronous -- right
// now only for LoadSheetSync and children of sync loads.
bool mSyncLoad : 1;
// mIsNonDocumentSheet is true if the load was triggered by LoadSheetSync or
// LoadSheet or an @import from such a sheet. Non-document sheet loads can
// proceed even if we have no document.
bool mIsNonDocumentSheet : 1;
// mIsLoading is true from the moment we are placed in the loader's
// "loading datas" table (right after the async channel is opened)
// to the moment we are removed from said table (due to the load
// completing or being cancelled).
bool mIsLoading : 1;
// mIsCancelled is set to true when a sheet load is stopped by
// Stop() or StopLoadingSheet() (which was removed in Bug 556446).
// SheetLoadData::OnStreamComplete() checks this to avoid parsing
// sheets that have been cancelled and such.
bool mIsCancelled : 1;
// mMustNotify is true if the load data is being loaded async and
// the original function call that started the load has returned.
// This applies only to observer notifications; load/error events
// are fired for any SheetLoadData that has a non-null
// mOwningElement.
bool mMustNotify : 1;
// mWasAlternate is true if the sheet was an alternate when the load data was
// created.
bool mWasAlternate : 1;
// mUseSystemPrincipal is true if the system principal should be used for
// this sheet, no matter what the channel principal is. Only true for sync
// loads.
bool mUseSystemPrincipal : 1;
// If true, this SheetLoadData is being used as a way to handle
// async observer notification for an already-complete sheet.
bool mSheetAlreadyComplete : 1;
// This is the element that imported the sheet. Needed to get the
// charset set on it and to fire load/error events.
nsCOMPtr<nsIStyleSheetLinkingElement> mOwningElement;
// The observer that wishes to be notified of load completion
nsCOMPtr<nsICSSLoaderObserver> mObserver;
// The principal that identifies who started loading us.
nsCOMPtr<nsIPrincipal> mLoaderPrincipal;
// The node that identifies who started loading us.
nsCOMPtr<nsINode> mRequestingNode;
// The charset to use if the transport and sheet don't indicate one.
// May be empty. Must be empty if mOwningElement is non-null.
nsCString mCharsetHint;
// The status our load ended up with; this determines whether we
// should fire error events or load events. This gets initialized
// by ScheduleLoadEventIfNeeded, and is only used after that has
// been called.
MOZ_INIT_OUTSIDE_CTOR nsresult mStatus;
private:
void FireLoadEvent(nsIThreadInternal* aThread);
};
#include "mozilla/Logging.h"
static mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");
static mozilla::LazyLogModule gSriPRLog("SRI");
#define LOG_ERROR(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Error, args)
#define LOG_WARN(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Warning, args)
#define LOG_DEBUG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
#define LOG(args) LOG_DEBUG(args)
#define LOG_ERROR_ENABLED() MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Error)
#define LOG_WARN_ENABLED() MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Warning)
#define LOG_DEBUG_ENABLED() MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Debug)
#define LOG_ENABLED() LOG_DEBUG_ENABLED()
#define LOG_URI(format, uri) \
PR_BEGIN_MACRO \
NS_ASSERTION(uri, "Logging null uri"); \
if (LOG_ENABLED()) { \
LOG((format, uri->GetSpecOrDefault().get())); \
} \
PR_END_MACRO
// And some convenience strings...
static const char* const gStateStrings[] = {
"eSheetStateUnknown",
"eSheetNeedsParser",
"eSheetPending",
"eSheetLoading",
"eSheetComplete"
};
/********************************
* SheetLoadData implementation *
********************************/
NS_IMPL_ISUPPORTS(SheetLoadData, nsIUnicharStreamLoaderObserver, nsIRunnable,
nsIThreadObserver)
SheetLoadData::SheetLoadData(Loader* aLoader,
const nsSubstring& aTitle,
nsIURI* aURI,
StyleSheet* aSheet,
nsIStyleSheetLinkingElement* aOwningElement,
bool aIsAlternate,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aLoaderPrincipal,
nsINode* aRequestingNode)
: mLoader(aLoader),
mTitle(aTitle),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mPendingChildren(0),
mSyncLoad(false),
mIsNonDocumentSheet(false),
mIsLoading(false),
mIsCancelled(false),
mMustNotify(false),
mWasAlternate(aIsAlternate),
mUseSystemPrincipal(false),
mSheetAlreadyComplete(false),
mOwningElement(aOwningElement),
mObserver(aObserver),
mLoaderPrincipal(aLoaderPrincipal),
mRequestingNode(aRequestingNode)
{
NS_PRECONDITION(mLoader, "Must have a loader!");
}
SheetLoadData::SheetLoadData(Loader* aLoader,
nsIURI* aURI,
StyleSheet* aSheet,
SheetLoadData* aParentData,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aLoaderPrincipal,
nsINode* aRequestingNode)
: mLoader(aLoader),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mParentData(aParentData),
mPendingChildren(0),
mSyncLoad(false),
mIsNonDocumentSheet(false),
mIsLoading(false),
mIsCancelled(false),
mMustNotify(false),
mWasAlternate(false),
mUseSystemPrincipal(false),
mSheetAlreadyComplete(false),
mOwningElement(nullptr),
mObserver(aObserver),
mLoaderPrincipal(aLoaderPrincipal),
mRequestingNode(aRequestingNode)
{
NS_PRECONDITION(mLoader, "Must have a loader!");
if (mParentData) {
mSyncLoad = mParentData->mSyncLoad;
mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
++(mParentData->mPendingChildren);
}
NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
"Shouldn't use system principal for async loads");
}
SheetLoadData::SheetLoadData(Loader* aLoader,
nsIURI* aURI,
StyleSheet* aSheet,
bool aSyncLoad,
bool aUseSystemPrincipal,
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aLoaderPrincipal,
nsINode* aRequestingNode)
: mLoader(aLoader),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mPendingChildren(0),
mSyncLoad(aSyncLoad),
mIsNonDocumentSheet(true),
mIsLoading(false),
mIsCancelled(false),
mMustNotify(false),
mWasAlternate(false),
mUseSystemPrincipal(aUseSystemPrincipal),
mSheetAlreadyComplete(false),
mOwningElement(nullptr),
mObserver(aObserver),
mLoaderPrincipal(aLoaderPrincipal),
mRequestingNode(aRequestingNode),
mCharsetHint(aCharset)
{
NS_PRECONDITION(mLoader, "Must have a loader!");
NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
"Shouldn't use system principal for async loads");
}
SheetLoadData::~SheetLoadData()
{
NS_CSS_NS_RELEASE_LIST_MEMBER(SheetLoadData, this, mNext);
}
NS_IMETHODIMP
SheetLoadData::Run()
{
mLoader->HandleLoadEvent(this);
return NS_OK;
}
NS_IMETHODIMP
SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
{
return NS_OK;
}
NS_IMETHODIMP
SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
bool aMayWait)
{
// XXXkhuey this is insane!
// We want to fire our load even before or after event processing,
// whichever comes first.
FireLoadEvent(aThread);
return NS_OK;
}
NS_IMETHODIMP
SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
bool aEventWasProcessed)
{
// XXXkhuey this too!
// We want to fire our load even before or after event processing,
// whichever comes first.
FireLoadEvent(aThread);
return NS_OK;
}
void
SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
{
// First remove ourselves as a thread observer. But we need to keep
// ourselves alive while doing that!
RefPtr<SheetLoadData> kungFuDeathGrip(this);
aThread->RemoveObserver(this);
// Now fire the event
nsCOMPtr<nsINode> node = do_QueryInterface(mOwningElement);
NS_ASSERTION(node, "How did that happen???");
nsContentUtils::DispatchTrustedEvent(node->OwnerDoc(),
node,
NS_SUCCEEDED(mStatus) ?
NS_LITERAL_STRING("load") :
NS_LITERAL_STRING("error"),
false, false);
// And unblock onload
if (mLoader->mDocument) {
mLoader->mDocument->UnblockOnload(true);
}
}
void
SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus)
{
if (!mOwningElement) {
return;
}
mStatus = aStatus;
nsCOMPtr<nsIThread> thread = do_GetMainThread();
nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
// Make sure to block onload here
if (mLoader->mDocument) {
mLoader->mDocument->BlockOnload();
}
}
}
/*********************
* Style sheet reuse *
*********************/
bool
LoaderReusableStyleSheets::FindReusableStyleSheet(nsIURI* aURL,
RefPtr<CSSStyleSheet>& aResult)
{
MOZ_ASSERT(aURL);
for (size_t i = mReusableSheets.Length(); i > 0; --i) {
size_t index = i - 1;
bool sameURI;
MOZ_ASSERT(mReusableSheets[index]->GetOriginalURI());
nsresult rv = aURL->Equals(mReusableSheets[index]->GetOriginalURI(),
&sameURI);
if (!NS_FAILED(rv) && sameURI) {
aResult = mReusableSheets[index];
mReusableSheets.RemoveElementAt(index);
return true;
}
}
return false;
}
/*************************
* Loader Implementation *
*************************/
Loader::Loader(StyleBackendType aType)
: mDocument(nullptr)
, mDatasToNotifyOn(0)
, mCompatMode(eCompatibility_FullStandards)
, mStyleBackendType(Some(aType))
, mEnabled(true)
, mReporter(new ConsoleReportCollector())
#ifdef DEBUG
, mSyncCallback(false)
#endif
{
}
Loader::Loader(nsIDocument* aDocument)
: mDocument(aDocument)
, mDatasToNotifyOn(0)
, mCompatMode(eCompatibility_FullStandards)
, mEnabled(true)
, mReporter(new ConsoleReportCollector())
#ifdef DEBUG
, mSyncCallback(false)
#endif
{
// We can just use the preferred set, since there are no sheets in the
// document yet (if there are, how did they get there? _we_ load the sheets!)
// and hence the selected set makes no sense at this time.
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
if (domDoc) {
domDoc->GetPreferredStyleSheetSet(mPreferredSheet);
}
}
Loader::~Loader()
{
NS_ASSERTION(!mSheets || mSheets->mLoadingDatas.Count() == 0,
"How did we get destroyed when there are loading data?");
NS_ASSERTION(!mSheets || mSheets->mPendingDatas.Count() == 0,
"How did we get destroyed when there are pending data?");
// Note: no real need to revoke our stylesheet loaded events -- they
// hold strong references to us, so if we're going away that means
// they're all done.
}
void
Loader::DropDocumentReference(void)
{
mDocument = nullptr;
// Flush out pending datas just so we don't leak by accident. These
// loads should short-circuit through the mDocument check in
// LoadSheet and just end up in SheetComplete immediately
if (mSheets) {
StartAlternateLoads();
}
}
nsresult
Loader::SetPreferredSheet(const nsAString& aTitle)
{
#ifdef DEBUG
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(mDocument);
if (doc) {
nsAutoString currentPreferred;
doc->GetLastStyleSheetSet(currentPreferred);
if (DOMStringIsNull(currentPreferred)) {
doc->GetPreferredStyleSheetSet(currentPreferred);
}
NS_ASSERTION(currentPreferred.Equals(aTitle),
"Unexpected argument to SetPreferredSheet");
}
#endif
mPreferredSheet = aTitle;
// start any pending alternates that aren't alternates anymore
if (mSheets) {
LoadDataArray arr(mSheets->mPendingDatas.Count());
for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
SheetLoadData* data = iter.Data();
MOZ_ASSERT(data, "Must have a data");
// Note that we don't want to affect what the selected style set is, so
// use true for aHasAlternateRel.
if (!data->mLoader->IsAlternate(data->mTitle, true)) {
arr.AppendElement(data);
iter.Remove();
}
}
mDatasToNotifyOn += arr.Length();
for (uint32_t i = 0; i < arr.Length(); ++i) {
--mDatasToNotifyOn;
LoadSheet(arr[i], eSheetNeedsParser, false);
}
}
return NS_OK;
}
static const char kCharsetSym[] = "@charset \"";
static bool GetCharsetFromData(const char* aStyleSheetData,
uint32_t aDataLength,
nsACString& aCharset)
{
aCharset.Truncate();
if (aDataLength <= sizeof(kCharsetSym) - 1)
return false;
if (strncmp(aStyleSheetData,
kCharsetSym,
sizeof(kCharsetSym) - 1)) {
return false;
}
for (uint32_t i = sizeof(kCharsetSym) - 1; i < aDataLength; ++i) {
char c = aStyleSheetData[i];
if (c == '"') {
++i;
if (i < aDataLength && aStyleSheetData[i] == ';') {
return true;
}
// fail
break;
}
aCharset.Append(c);
}
// Did not see end quote or semicolon
aCharset.Truncate();
return false;
}
NS_IMETHODIMP
SheetLoadData::OnDetermineCharset(nsIUnicharStreamLoader* aLoader,
nsISupports* aContext,
nsACString const& aSegment,
nsACString& aCharset)
{
NS_PRECONDITION(!mOwningElement || mCharsetHint.IsEmpty(),
"Can't have element _and_ charset hint");
LOG_URI("SheetLoadData::OnDetermineCharset for '%s'", mURI);
// The precedence is (per CSS3 Syntax 2012-11-08 ED):
// BOM
// Channel
// @charset rule
// charset attribute on the referrer
// encoding of the referrer
// UTF-8
aCharset.Truncate();
if (nsContentUtils::CheckForBOM((const unsigned char*)aSegment.BeginReading(),
aSegment.Length(),
aCharset)) {
// aCharset is now either "UTF-16BE", "UTF-16BE" or "UTF-8"
// which will swallow the BOM.
mCharset.Assign(aCharset);
LOG((" Setting from BOM to: %s", PromiseFlatCString(aCharset).get()));
return NS_OK;
}
nsCOMPtr<nsIChannel> channel;
nsAutoCString specified;
aLoader->GetChannel(getter_AddRefs(channel));
if (channel) {
channel->GetContentCharset(specified);
if (EncodingUtils::FindEncodingForLabel(specified, aCharset)) {
mCharset.Assign(aCharset);
LOG((" Setting from HTTP to: %s", PromiseFlatCString(aCharset).get()));
return NS_OK;
}
}
if (GetCharsetFromData(aSegment.BeginReading(),
aSegment.Length(),
specified)) {
if (EncodingUtils::FindEncodingForLabel(specified, aCharset)) {
// FindEncodingForLabel currently never returns UTF-16LE but will
// probably change to never return UTF-16 instead, so check both here
// to avoid relying on the exact behavior.
if (aCharset.EqualsLiteral("UTF-16") ||
aCharset.EqualsLiteral("UTF-16BE") ||
aCharset.EqualsLiteral("UTF-16LE")) {
// Be consistent with HTML <meta> handling in face of impossibility.
// When the @charset rule itself evidently was not UTF-16-encoded,
// it saying UTF-16 has to be a lie.
aCharset.AssignLiteral("UTF-8");
}
mCharset.Assign(aCharset);
LOG((" Setting from @charset rule to: %s",
PromiseFlatCString(aCharset).get()));
return NS_OK;
}
}
// Now try the charset on the <link> or processing instruction
// that loaded us
if (mOwningElement) {
nsAutoString specified16;
mOwningElement->GetCharset(specified16);
if (EncodingUtils::FindEncodingForLabel(specified16, aCharset)) {
mCharset.Assign(aCharset);
LOG((" Setting from charset attribute to: %s",
PromiseFlatCString(aCharset).get()));
return NS_OK;
}
}
// In the preload case, the value of the charset attribute on <link> comes
// in via mCharsetHint instead.
if (EncodingUtils::FindEncodingForLabel(mCharsetHint, aCharset)) {
mCharset.Assign(aCharset);
LOG((" Setting from charset attribute (preload case) to: %s",
PromiseFlatCString(aCharset).get()));
return NS_OK;
}
// Try charset from the parent stylesheet.
if (mParentData) {
aCharset = mParentData->mCharset;
if (!aCharset.IsEmpty()) {
mCharset.Assign(aCharset);
LOG((" Setting from parent sheet to: %s",
PromiseFlatCString(aCharset).get()));
return NS_OK;
}
}
if (mLoader->mDocument) {
// no useful data on charset. Try the document charset.
aCharset = mLoader->mDocument->GetDocumentCharacterSet();
MOZ_ASSERT(!aCharset.IsEmpty());
mCharset.Assign(aCharset);
LOG((" Setting from document to: %s", PromiseFlatCString(aCharset).get()));
return NS_OK;
}
aCharset.AssignLiteral("UTF-8");
mCharset = aCharset;
LOG((" Setting from default to: %s", PromiseFlatCString(aCharset).get()));
return NS_OK;
}
already_AddRefed<nsIURI>
SheetLoadData::GetReferrerURI()
{
nsCOMPtr<nsIURI> uri;
if (mParentData)
uri = mParentData->mSheet->GetSheetURI();
if (!uri && mLoader->mDocument)
uri = mLoader->mDocument->GetDocumentURI();
return uri.forget();
}
/*
* Here we need to check that the load did not give us an http error
* page and check the mimetype on the channel to make sure we're not
* loading non-text/css data in standards mode.
*/
NS_IMETHODIMP
SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
nsISupports* aContext,
nsresult aStatus,
const nsAString& aBuffer)
{
LOG(("SheetLoadData::OnStreamComplete"));
NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");
if (mIsCancelled) {
// Just return. Don't call SheetComplete -- it's already been
// called and calling it again will lead to an extra NS_RELEASE on
// this data and a likely crash.
return NS_OK;
}
if (!mLoader->mDocument && !mIsNonDocumentSheet) {
// Sorry, we don't care about this load anymore
LOG_WARN((" No document and not non-document sheet; dropping load"));
mLoader->SheetComplete(this, NS_BINDING_ABORTED);
return NS_OK;
}
if (NS_FAILED(aStatus)) {
LOG_WARN((" Load failed: status 0x%x", aStatus));
// Handle sheet not loading error because source was a tracking URL.
// We make a note of this sheet node by including it in a dedicated
// array of blocked tracking nodes under its parent document.
//
// Multiple sheet load instances might be tied to this request,
// we annotate each one linked to a valid owning element (node).
if (aStatus == NS_ERROR_TRACKING_URI) {
nsIDocument* doc = mLoader->GetDocument();
if (doc) {
for (SheetLoadData* data = this; data; data = data->mNext) {
// mOwningElement may be null but AddBlockTrackingNode can cope
nsCOMPtr<nsIContent> content = do_QueryInterface(data->mOwningElement);
doc->AddBlockedTrackingNode(content);
}
}
}
mLoader->SheetComplete(this, aStatus);
return NS_OK;
}
nsCOMPtr<nsIChannel> channel;
nsresult result = aLoader->GetChannel(getter_AddRefs(channel));
if (NS_FAILED(result)) {
LOG_WARN((" No channel from loader"));
mLoader->SheetComplete(this, result);
return NS_OK;
}
nsCOMPtr<nsIURI> originalURI;
channel->GetOriginalURI(getter_AddRefs(originalURI));
// If the channel's original URI is "chrome:", we want that, since
// the observer code in nsXULPrototypeCache depends on chrome stylesheets
// having a chrome URI. (Whether or not chrome stylesheets come through
// this codepath seems nondeterministic.)
// Otherwise we want the potentially-HTTP-redirected URI.
nsCOMPtr<nsIURI> channelURI;
NS_GetFinalChannelURI(channel, getter_AddRefs(channelURI));
if (!channelURI || !originalURI) {
NS_ERROR("Someone just violated the nsIRequest contract");
LOG_WARN((" Channel without a URI. Bad!"));
mLoader->SheetComplete(this, NS_ERROR_UNEXPECTED);
return NS_OK;
}
nsCOMPtr<nsIPrincipal> principal;
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
result = NS_ERROR_NOT_AVAILABLE;
if (secMan) { // Could be null if we already shut down
if (mUseSystemPrincipal) {
result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
} else {
result = secMan->GetChannelResultPrincipal(channel, getter_AddRefs(principal));
}
}
if (NS_FAILED(result)) {
LOG_WARN((" Couldn't get principal"));
mLoader->SheetComplete(this, result);
return NS_OK;
}
mSheet->SetPrincipal(principal);
// If it's an HTTP channel, we want to make sure this is not an
// error document we got.
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
bool requestSucceeded;
result = httpChannel->GetRequestSucceeded(&requestSucceeded);
if (NS_SUCCEEDED(result) && !requestSucceeded) {
LOG((" Load returned an error page"));
mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
return NS_OK;
}
}
nsAutoCString contentType;
if (channel) {
channel->GetContentType(contentType);
}
// In standards mode, a style sheet must have one of these MIME
// types to be processed at all. In quirks mode, we accept any
// MIME type, but only if the style sheet is same-origin with the
// requesting document or parent sheet. See bug 524223.
bool validType = contentType.EqualsLiteral("text/css") ||
contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
contentType.IsEmpty();
if (!validType) {
const char *errorMessage;
uint32_t errorFlag;
bool sameOrigin = true;
if (mLoaderPrincipal) {
bool subsumed;
result = mLoaderPrincipal->Subsumes(principal, &subsumed);
if (NS_FAILED(result) || !subsumed) {
sameOrigin = false;
}
}
if (sameOrigin && mLoader->mCompatMode == eCompatibility_NavQuirks) {
errorMessage = "MimeNotCssWarn";
errorFlag = nsIScriptError::warningFlag;
} else {
errorMessage = "MimeNotCss";
errorFlag = nsIScriptError::errorFlag;
}
const nsAFlatString& specUTF16 =
NS_ConvertUTF8toUTF16(channelURI->GetSpecOrDefault());
const nsAFlatString& ctypeUTF16 = NS_ConvertASCIItoUTF16(contentType);
const char16_t *strings[] = { specUTF16.get(), ctypeUTF16.get() };
nsCOMPtr<nsIURI> referrer = GetReferrerURI();
nsContentUtils::ReportToConsole(errorFlag,
NS_LITERAL_CSTRING("CSS Loader"),
mLoader->mDocument,
nsContentUtils::eCSS_PROPERTIES,
errorMessage,
strings, ArrayLength(strings),
referrer);
if (errorFlag == nsIScriptError::errorFlag) {
LOG_WARN((" Ignoring sheet with improper MIME type %s",
contentType.get()));
mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
return NS_OK;
}
}
SRIMetadata sriMetadata;
mSheet->GetIntegrity(sriMetadata);
if (sriMetadata.IsEmpty()) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
if (loadInfo->GetEnforceSRI()) {
LOG((" Load was blocked by SRI"));
MOZ_LOG(gSriPRLog, mozilla::LogLevel::Debug,
("css::Loader::OnStreamComplete, required SRI not found"));
mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
// log the failed load to web console
nsCOMPtr<nsIContentSecurityPolicy> csp;
loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
nsAutoCString spec;
mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(spec);
// line number unknown. mRequestingNode doesn't bear this info.
csp->LogViolationDetails(
nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_STYLE,
NS_ConvertUTF8toUTF16(spec), EmptyString(),
0, EmptyString(), EmptyString());
return NS_OK;
}
} else {
nsAutoCString sourceUri;
if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
}
nsresult rv = SRICheck::VerifyIntegrity(sriMetadata, aLoader, aBuffer,
sourceUri, mLoader->mReporter);
mLoader->mReporter->FlushConsoleReports(mLoader->mDocument);
if (NS_FAILED(rv)) {
LOG((" Load was blocked by SRI"));
MOZ_LOG(gSriPRLog, mozilla::LogLevel::Debug,
("css::Loader::OnStreamComplete, bad metadata"));
mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
return NS_OK;
}
}
// Enough to set the URIs on mSheet, since any sibling datas we have share
// the same mInner as mSheet and will thus get the same URI.
mSheet->SetURIs(channelURI, originalURI, channelURI);
bool completed;
result = mLoader->ParseSheet(aBuffer, this, completed);
NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
return result;
}
bool
Loader::IsAlternate(const nsAString& aTitle, bool aHasAlternateRel)
{
// A sheet is alternate if it has a nonempty title that doesn't match the
// currently selected style set. But if there _is_ no currently selected
// style set, the sheet wasn't marked as an alternate explicitly, and aTitle
// is nonempty, we should select the style set corresponding to aTitle, since
// that's a preferred sheet.
if (aTitle.IsEmpty()) {
return false;
}
if (!aHasAlternateRel && mDocument && mPreferredSheet.IsEmpty()) {
// There's no preferred set yet, and we now have a sheet with a title.
// Make that be the preferred set.
mDocument->SetHeaderData(nsGkAtoms::headerDefaultStyle, aTitle);
// We're definitely not an alternate
return false;
}
return !aTitle.Equals(mPreferredSheet);
}
nsresult
Loader::ObsoleteSheet(nsIURI* aURI)
{
if (!mSheets) {
return NS_OK;
}
if (!aURI) {
return NS_ERROR_INVALID_ARG;
}
for (auto iter = mSheets->mCompleteSheets.Iter(); !iter.Done(); iter.Next()) {
nsIURI* sheetURI = iter.Key()->GetURI();
bool areEqual;
nsresult rv = sheetURI->Equals(aURI, &areEqual);
if (NS_SUCCEEDED(rv) && areEqual) {
iter.Remove();
}
}
return NS_OK;
}
nsresult
Loader::CheckContentPolicy(nsIPrincipal* aSourcePrincipal,
nsIURI* aTargetURI,
nsISupports* aContext,
bool aIsPreload)
{
// When performing a system load (e.g. aUseSystemPrincipal = true)
// then aSourcePrincipal == null; don't consult content policies.
if (!aSourcePrincipal) {
return NS_OK;
}
nsContentPolicyType contentPolicyType =
aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
nsresult rv = NS_CheckContentLoadPolicy(contentPolicyType,
aTargetURI,
aSourcePrincipal,
aContext,
NS_LITERAL_CSTRING("text/css"),
nullptr, //extra param
&shouldLoad,
nsContentUtils::GetContentPolicy(),
nsContentUtils::GetSecurityManager());
if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
return NS_ERROR_CONTENT_BLOCKED;
}
return NS_OK;
}
/**
* CreateSheet() creates a CSSStyleSheet object for the given URI,
* if any. If there is no URI given, we just create a new style sheet
* object. Otherwise, we check for an existing style sheet object for
* that uri in various caches and clone it if we find it. Cloned
* sheets will have the title/media/enabled state of the sheet they
* are clones off; make sure to call PrepareSheet() on the result of
* CreateSheet().
*/
nsresult
Loader::CreateSheet(nsIURI* aURI,
nsIContent* aLinkingContent,
nsIPrincipal* aLoaderPrincipal,
css::SheetParsingMode aParsingMode,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
bool aSyncLoad,
bool aHasAlternateRel,
const nsAString& aTitle,
StyleSheetState& aSheetState,
bool *aIsAlternate,
RefPtr<StyleSheet>* aSheet)
{
LOG(("css::Loader::CreateSheet"));
NS_PRECONDITION(aSheet, "Null out param!");
if (!mSheets) {
mSheets = new Sheets();
}
*aSheet = nullptr;
aSheetState = eSheetStateUnknown;
// Check the alternate state before doing anything else, because it
// can mess with our hashtables.
*aIsAlternate = IsAlternate(aTitle, aHasAlternateRel);
// XXXheycam Cached sheets currently must be CSSStyleSheets.
if (aURI && GetStyleBackendType() == StyleBackendType::Gecko) {
aSheetState = eSheetComplete;
RefPtr<StyleSheet> sheet;
// First, the XUL cache
#ifdef MOZ_XUL
if (IsChromeURI(aURI)) {
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
if (cache) {
if (cache->IsEnabled()) {
sheet = cache->GetStyleSheet(aURI);
LOG((" From XUL cache: %p", sheet.get()));
}
}
}
#endif
bool fromCompleteSheets = false;
if (!sheet) {
// Then our per-document complete sheets.
URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
StyleSheet* completeSheet = nullptr;
mSheets->mCompleteSheets.Get(&key, &completeSheet);
sheet = completeSheet;
LOG((" From completed: %p", sheet.get()));
fromCompleteSheets = !!sheet;
}
if (sheet) {
if (sheet->IsServo()) {
MOZ_CRASH("stylo: can't clone ServoStyleSheets yet");
}
// This sheet came from the XUL cache or our per-document hashtable; it
// better be a complete sheet.
NS_ASSERTION(sheet->AsGecko()->IsComplete(),
"Sheet thinks it's not complete while we think it is");
// Make sure it hasn't been modified; if it has, we can't use it
if (sheet->AsGecko()->IsModified()) {
LOG((" Not cloning completed sheet %p because it's been modified",
sheet.get()));
sheet = nullptr;
fromCompleteSheets = false;
}
}
// Then loading sheets
if (!sheet && !aSyncLoad) {
aSheetState = eSheetLoading;
SheetLoadData* loadData = nullptr;
URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
mSheets->mLoadingDatas.Get(&key, &loadData);
if (loadData) {
sheet = loadData->mSheet;
LOG((" From loading: %p", sheet.get()));
#ifdef DEBUG
bool debugEqual;
NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
(aLoaderPrincipal && loadData->mLoaderPrincipal &&
NS_SUCCEEDED(aLoaderPrincipal->
Equals(loadData->mLoaderPrincipal,
&debugEqual)) && debugEqual),
"Principals should be the same");
#endif
}
// Then alternate sheets
if (!sheet) {
aSheetState = eSheetPending;
loadData = nullptr;
mSheets->mPendingDatas.Get(&key, &loadData);
if (loadData) {
sheet = loadData->mSheet;
LOG((" From pending: %p", sheet.get()));
#ifdef DEBUG
bool debugEqual;
NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
(aLoaderPrincipal && loadData->mLoaderPrincipal &&
NS_SUCCEEDED(aLoaderPrincipal->
Equals(loadData->mLoaderPrincipal,
&debugEqual)) && debugEqual),
"Principals should be the same");
#endif
}
}
}
if (sheet) {
// The sheet we have now should be either incomplete or unmodified
if (sheet->IsServo()) {
MOZ_CRASH("stylo: can't clone ServoStyleSheets yet");
}
NS_ASSERTION(!sheet->AsGecko()->IsModified() ||
!sheet->AsGecko()->IsComplete(),
"Unexpected modified complete sheet");
NS_ASSERTION(sheet->AsGecko()->IsComplete() ||
aSheetState != eSheetComplete,
"Sheet thinks it's not complete while we think it is");
RefPtr<CSSStyleSheet> clonedSheet =
sheet->AsGecko()->Clone(nullptr, nullptr, nullptr, nullptr);
*aSheet = Move(clonedSheet);
if (*aSheet && fromCompleteSheets &&
!sheet->AsGecko()->GetOwnerNode() &&
!sheet->AsGecko()->GetParentSheet()) {
// The sheet we're cloning isn't actually referenced by
// anyone. Replace it in the cache, so that if our CSSOM is
// later modified we don't end up with two copies of our inner
// hanging around.
URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
NS_ASSERTION((*aSheet)->AsGecko()->IsComplete(),
"Should only be caching complete sheets");
mSheets->mCompleteSheets.Put(&key, *aSheet);
}
}
}
if (!*aSheet) {
aSheetState = eSheetNeedsParser;
nsIURI *sheetURI;
nsCOMPtr<nsIURI> baseURI;
nsIURI* originalURI;
if (!aURI) {
// Inline style. Use the document's base URL so that @import in
// the inline sheet picks up the right base.
NS_ASSERTION(aLinkingContent, "Inline stylesheet without linking content?");
baseURI = aLinkingContent->GetBaseURI();
sheetURI = aLinkingContent->OwnerDoc()->GetDocumentURI();
originalURI = nullptr;
} else {
baseURI = aURI;
sheetURI = aURI;
originalURI = aURI;
}
SRIMetadata sriMetadata;
if (!aIntegrity.IsEmpty()) {
MOZ_LOG(gSriPRLog, mozilla::LogLevel::Debug,
("css::Loader::CreateSheet, integrity=%s",
NS_ConvertUTF16toUTF8(aIntegrity).get()));
nsAutoCString sourceUri;
if (mDocument && mDocument->GetDocumentURI()) {
mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
}
SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter,
&sriMetadata);
}
if (GetStyleBackendType() == StyleBackendType::Gecko) {
*aSheet = new CSSStyleSheet(aParsingMode, aCORSMode, aReferrerPolicy, sriMetadata);
} else {
*aSheet = new ServoStyleSheet(aParsingMode, aCORSMode, aReferrerPolicy, sriMetadata);
}
(*aSheet)->SetURIs(sheetURI, originalURI, baseURI);
}
NS_ASSERTION(*aSheet, "We should have a sheet by now!");
NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!");
LOG((" State: %s", gStateStrings[aSheetState]));
return NS_OK;
}
/**
* PrepareSheet() handles setting the media and title on the sheet, as
* well as setting the enabled state based on the title and whether
* the sheet had "alternate" in its rel.
*/
void
Loader::PrepareSheet(StyleSheet* aSheet,
const nsSubstring& aTitle,
const nsSubstring& aMediaString,
nsMediaList* aMediaList,
Element* aScopeElement,
bool isAlternate)
{
NS_PRECONDITION(aSheet, "Must have a sheet!");
// XXXheycam Need to set media, title, etc. on ServoStyleSheets.
if (aSheet->IsServo()) {
NS_WARNING("stylo: should set metadata on ServoStyleSheets. See bug 1290209.");
return;
}
CSSStyleSheet* sheet = aSheet->AsGecko();
RefPtr<nsMediaList> mediaList(aMediaList);
if (!aMediaString.IsEmpty()) {
NS_ASSERTION(!aMediaList,
"must not provide both aMediaString and aMediaList");
mediaList = new nsMediaList();
nsCSSParser mediumParser(this);
// We have aMediaString only when linked from link elements, style
// elements, or PIs, so pass true.
mediumParser.ParseMediaList(aMediaString, nullptr, 0, mediaList, true);
}
sheet->SetMedia(mediaList);
sheet->SetTitle(aTitle);
sheet->SetEnabled(!isAlternate);
sheet->SetScopeElement(aScopeElement);
}
/**
* InsertSheetInDoc handles ordering of sheets in the document. Here
* we have two types of sheets -- those with linking elements and
* those without. The latter are loaded by Link: headers.
* The following constraints are observed:
* 1) Any sheet with a linking element comes after all sheets without
* linking elements
* 2) Sheets without linking elements are inserted in the order in
* which the inserting requests come in, since all of these are
* inserted during header data processing in the content sink
* 3) Sheets with linking elements are ordered based on document order
* as determined by CompareDocumentPosition.
*/
nsresult
Loader::InsertSheetInDoc(StyleSheet* aSheet,
nsIContent* aLinkingContent,
nsIDocument* aDocument)
{
LOG(("css::Loader::InsertSheetInDoc"));
NS_PRECONDITION(aSheet, "Nothing to insert");
NS_PRECONDITION(aDocument, "Must have a document to insert into");
// XXX Need to cancel pending sheet loads for this element, if any
int32_t sheetCount = aDocument->GetNumberOfStyleSheets();
/*
* Start the walk at the _end_ of the list, since in the typical
* case we'll just want to append anyway. We want to break out of
* the loop when insertionPoint points to just before the index we
* want to insert at. In other words, when we leave the loop
* insertionPoint is the index of the stylesheet that immediately
* precedes the one we're inserting.
*/
int32_t insertionPoint;
for (insertionPoint = sheetCount - 1; insertionPoint >= 0; --insertionPoint) {
StyleSheet* curSheet = aDocument->GetStyleSheetAt(insertionPoint);
NS_ASSERTION(curSheet, "There must be a sheet here!");
nsCOMPtr<nsINode> sheetOwner = curSheet->GetOwnerNode();
if (sheetOwner && !aLinkingContent) {
// Keep moving; all sheets with a sheetOwner come after all
// sheets without a linkingNode
continue;
}
if (!sheetOwner) {
// Aha! The current sheet has no sheet owner, so we want to
// insert after it no matter whether we have a linkingNode
break;
}
NS_ASSERTION(aLinkingContent != sheetOwner,
"Why do we still have our old sheet?");
// Have to compare
if (nsContentUtils::PositionIsBefore(sheetOwner, aLinkingContent)) {
// The current sheet comes before us, and it better be the first
// such, because now we break
break;
}
}
++insertionPoint; // adjust the index to the spot we want to insert in
// XXX <meta> elements do not implement nsIStyleSheetLinkingElement;
// need to fix this for them to be ordered correctly.
nsCOMPtr<nsIStyleSheetLinkingElement>
linkingElement = do_QueryInterface(aLinkingContent);
if (linkingElement) {
linkingElement->SetStyleSheet(aSheet); // This sets the ownerNode on the sheet
}
aDocument->BeginUpdate(UPDATE_STYLE);
aDocument->InsertStyleSheetAt(aSheet, insertionPoint);
aDocument->EndUpdate(UPDATE_STYLE);
LOG((" Inserting into document at position %d", insertionPoint));
return NS_OK;
}
/**
* InsertChildSheet handles ordering of @import-ed sheet in their
* parent sheets. Here we want to just insert based on order of the
* @import rules that imported the sheets. In theory we can't just
* append to the end because the CSSOM can insert @import rules. In
* practice, we get the call to load the child sheet before the CSSOM
* has finished inserting the @import rule, so we have no idea where
* to put it anyway. So just append for now. (In the future if we
* want to insert the sheet at the correct position, we'll need to
* restore CSSStyleSheet::InsertStyleSheetAt, which was removed in
* bug 1220506.)
*/
nsresult
Loader::InsertChildSheet(StyleSheet* aSheet,
StyleSheet* aParentSheet,
ImportRule* aParentRule)
{
LOG(("css::Loader::InsertChildSheet"));
NS_PRECONDITION(aSheet, "Nothing to insert");
NS_PRECONDITION(aParentSheet, "Need a parent to insert into");
NS_PRECONDITION(aParentSheet, "How did we get imported?");
// XXXheycam The InsertChildSheet API doesn't work with ServoStyleSheets,
// since they won't have Gecko ImportRules in them.
if (aSheet->IsServo()) {
return NS_ERROR_FAILURE;
}
// child sheets should always start out enabled, even if they got
// cloned off of top-level sheets which were disabled
aSheet->AsGecko()->SetEnabled(true);
aParentSheet->AppendStyleSheet(aSheet);
aParentRule->SetSheet(aSheet->AsGecko()); // This sets the ownerRule on the sheet
LOG((" Inserting into parent sheet"));
// LOG((" Inserting into parent sheet at position %d", insertionPoint));
return NS_OK;
}
/**
* LoadSheet handles the actual load of a sheet. If the load is
* supposed to be synchronous it just opens a channel synchronously
* using the given uri, wraps the resulting stream in a converter
* stream and calls ParseSheet. Otherwise it tries to look for an
* existing load for this URI and piggyback on it. Failing all that,
* a new load is kicked off asynchronously.
*/
nsresult
Loader::LoadSheet(SheetLoadData* aLoadData,
StyleSheetState aSheetState,
bool aIsPreload)
{
LOG(("css::Loader::LoadSheet"));
NS_PRECONDITION(aLoadData, "Need a load data");
NS_PRECONDITION(aLoadData->mURI, "Need a URI to load");
NS_PRECONDITION(aLoadData->mSheet, "Need a sheet to load into");
NS_PRECONDITION(aSheetState != eSheetComplete, "Why bother?");
NS_PRECONDITION(!aLoadData->mUseSystemPrincipal || aLoadData->mSyncLoad,
"Shouldn't use system principal for async loads");
NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
LOG_URI(" Load from: '%s'", aLoadData->mURI);
nsresult rv = NS_OK;
if (!mDocument && !aLoadData->mIsNonDocumentSheet) {
// No point starting the load; just release all the data and such.
LOG_WARN((" No document and not non-document sheet; pre-dropping load"));
SheetComplete(aLoadData, NS_BINDING_ABORTED);
return NS_BINDING_ABORTED;
}
SRIMetadata sriMetadata;
aLoadData->mSheet->GetIntegrity(sriMetadata);
if (aLoadData->mSyncLoad) {
LOG((" Synchronous load"));
NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
NS_ASSERTION(aSheetState == eSheetNeedsParser,
"Sync loads can't reuse existing async loads");
// Create a nsIUnicharStreamLoader instance to which we will feed
// the data from the sync load. Do this before creating the
// channel to make error recovery simpler.
nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to create stream loader for sync load"));
SheetComplete(aLoadData, rv);
return rv;
}
if (mDocument) {
mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
mDocument);
}
nsSecurityFlags securityFlags =
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
nsILoadInfo::SEC_ALLOW_CHROME;
nsContentPolicyType contentPolicyType =
aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
// Just load it
nsCOMPtr<nsIChannel> channel;
// Note that we are calling NS_NewChannelWithTriggeringPrincipal() with both
// a node and a principal.
// This is because of a case where the node is the document being styled and
// the principal is the stylesheet (perhaps from a different origin) that is
// applying the styles.
if (aLoadData->mRequestingNode && aLoadData->mLoaderPrincipal) {
rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
aLoadData->mURI,
aLoadData->mRequestingNode,
aLoadData->mLoaderPrincipal,
securityFlags,
contentPolicyType);
}
else {
// either we are loading something inside a document, in which case
// we should always have a requestingNode, or we are loading something
// outside a document, in which case the loadingPrincipal and the
// triggeringPrincipal should always be the systemPrincipal.
rv = NS_NewChannel(getter_AddRefs(channel),
aLoadData->mURI,
nsContentUtils::GetSystemPrincipal(),
securityFlags,
contentPolicyType);
}
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to create channel"));
SheetComplete(aLoadData, rv);
return rv;
}
nsCOMPtr<nsIInputStream> stream;
rv = channel->Open2(getter_AddRefs(stream));
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to open URI synchronously"));
SheetComplete(aLoadData, rv);
return rv;
}
// Force UA sheets to be UTF-8.
// XXX this is only necessary because the default in
// SheetLoadData::OnDetermineCharset is wrong (bug 521039).
channel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
// Manually feed the streamloader the contents of the stream.
// This will call back into OnStreamComplete
// and thence to ParseSheet. Regardless of whether this fails,
// SheetComplete has been called.
return nsSyncLoadService::PushSyncStreamToListener(stream,
streamLoader,
channel);
}
SheetLoadData* existingData = nullptr;
URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
aLoadData->mLoaderPrincipal,
aLoadData->mSheet->GetCORSMode(),
aLoadData->mSheet->GetReferrerPolicy());
if (aSheetState == eSheetLoading) {
mSheets->mLoadingDatas.Get(&key, &existingData);
NS_ASSERTION(existingData, "CreateSheet lied about the state");
}
else if (aSheetState == eSheetPending){
mSheets->mPendingDatas.Get(&key, &existingData);
NS_ASSERTION(existingData, "CreateSheet lied about the state");
}
if (existingData) {
LOG((" Glomming on to existing load"));
SheetLoadData* data = existingData;
while (data->mNext) {
data = data->mNext;
}
data->mNext = aLoadData; // transfer ownership
if (aSheetState == eSheetPending && !aLoadData->mWasAlternate) {
// Kick the load off; someone cares about it right away
#ifdef DEBUG
SheetLoadData* removedData;
NS_ASSERTION(mSheets->mPendingDatas.Get(&key, &removedData) &&
removedData == existingData,
"Bad pending table.");
#endif
mSheets->mPendingDatas.Remove(&key);
LOG((" Forcing load of pending data"));
return LoadSheet(existingData, eSheetNeedsParser, aIsPreload);
}
// All done here; once the load completes we'll be marked complete
// automatically
return NS_OK;
}
nsCOMPtr<nsILoadGroup> loadGroup;
if (mDocument) {
loadGroup = mDocument->GetDocumentLoadGroup();
// load for a document with no loadgrup indicates that something is
// completely bogus, let's bail out early.
if (!loadGroup) {
LOG_ERROR((" Failed to query loadGroup from document"));
SheetComplete(aLoadData, NS_ERROR_UNEXPECTED);
return NS_ERROR_UNEXPECTED;
}
}
#ifdef DEBUG
mSyncCallback = true;
#endif
CORSMode ourCORSMode = aLoadData->mSheet->GetCORSMode();
nsSecurityFlags securityFlags =
ourCORSMode == CORS_NONE
? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
: nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
if (ourCORSMode == CORS_ANONYMOUS) {
securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
} else if (ourCORSMode == CORS_USE_CREDENTIALS) {
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
}
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
nsContentPolicyType contentPolicyType =
aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
nsCOMPtr<nsIChannel> channel;
// Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
// and a principal. This is because of a case where the node is the document
// being styled and the principal is the stylesheet (perhaps from a different
// origin) that is applying the styles.
if (aLoadData->mRequestingNode && aLoadData->mLoaderPrincipal) {
rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
aLoadData->mURI,
aLoadData->mRequestingNode,
aLoadData->mLoaderPrincipal,
securityFlags,
contentPolicyType,
loadGroup,
nullptr, // aCallbacks
nsIChannel::LOAD_NORMAL |
nsIChannel::LOAD_CLASSIFY_URI);
}
else {
// either we are loading something inside a document, in which case
// we should always have a requestingNode, or we are loading something
// outside a document, in which case the loadingPrincipal and the
// triggeringPrincipal should always be the systemPrincipal.
rv = NS_NewChannel(getter_AddRefs(channel),
aLoadData->mURI,
nsContentUtils::GetSystemPrincipal(),
securityFlags,
contentPolicyType,
loadGroup,
nullptr, // aCallbacks
nsIChannel::LOAD_NORMAL |
nsIChannel::LOAD_CLASSIFY_URI);
}
if (NS_FAILED(rv)) {
#ifdef DEBUG
mSyncCallback = false;
#endif
LOG_ERROR((" Failed to create channel"));
SheetComplete(aLoadData, rv);
return rv;
}
if (!aLoadData->mWasAlternate) {
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
if (cos) {
cos->AddClassFlags(nsIClassOfService::Leader);
}
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
// Send a minimal Accept header for text/css
httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
false);
nsCOMPtr<nsIURI> referrerURI = aLoadData->GetReferrerURI();
if (referrerURI)
httpChannel->SetReferrerWithPolicy(referrerURI,
aLoadData->mSheet->GetReferrerPolicy());
nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
if (internalChannel) {
internalChannel->SetIntegrityMetadata(sriMetadata.GetIntegrityString());
}
// Set the initiator type
nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
if (timedChannel) {
if (aLoadData->mParentData) {
timedChannel->SetInitiatorType(NS_LITERAL_STRING("css"));
} else {
timedChannel->SetInitiatorType(NS_LITERAL_STRING("link"));
}
}
}
// Now tell the channel we expect text/css data back.... We do
// this before opening it, so it's only treated as a hint.
channel->SetContentType(NS_LITERAL_CSTRING("text/css"));
// We don't have to hold on to the stream loader. The ownership
// model is: Necko owns the stream loader, which owns the load data,
// which owns us
nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
if (NS_FAILED(rv)) {
#ifdef DEBUG
mSyncCallback = false;
#endif
LOG_ERROR((" Failed to create stream loader"));
SheetComplete(aLoadData, rv);
return rv;
}
if (mDocument) {
mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
mDocument);
}
rv = channel->AsyncOpen2(streamLoader);
#ifdef DEBUG
mSyncCallback = false;
#endif
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to create stream loader"));
SheetComplete(aLoadData, rv);
return rv;
}
mSheets->mLoadingDatas.Put(&key, aLoadData);
aLoadData->mIsLoading = true;
return NS_OK;
}
/**
* ParseSheet handles parsing the data stream. The main idea here is
* to push the current load data onto the parse stack before letting
* the CSS parser at the data stream. That lets us handle @import
* correctly.
*/
nsresult
Loader::ParseSheet(const nsAString& aInput,
SheetLoadData* aLoadData,
bool& aCompleted)
{
LOG(("css::Loader::ParseSheet"));
NS_PRECONDITION(aLoadData, "Must have load data");
NS_PRECONDITION(aLoadData->mSheet, "Must have sheet to parse into");
aCompleted = false;
// Push our load data on the stack so any kids can pick it up
mParsingDatas.AppendElement(aLoadData);
nsIURI* sheetURI = aLoadData->mSheet->GetSheetURI();
nsIURI* baseURI = aLoadData->mSheet->GetBaseURI();
nsresult rv;
if (aLoadData->mSheet->IsGecko()) {
nsCSSParser parser(this, aLoadData->mSheet->AsGecko());
rv = parser.ParseSheet(aInput, sheetURI, baseURI,
aLoadData->mSheet->Principal(),
aLoadData->mLineNumber);
} else {
rv =
aLoadData->mSheet->AsServo()->ParseSheet(aInput, sheetURI, baseURI,
aLoadData->mSheet->Principal(),
aLoadData->mLineNumber);
}
mParsingDatas.RemoveElementAt(mParsingDatas.Length() - 1);
if (NS_FAILED(rv)) {
LOG_ERROR((" Low-level error in parser!"));
SheetComplete(aLoadData, rv);
return rv;
}
NS_ASSERTION(aLoadData->mPendingChildren == 0 || !aLoadData->mSyncLoad,
"Sync load has leftover pending children!");
if (aLoadData->mPendingChildren == 0) {
LOG((" No pending kids from parse"));
aCompleted = true;
SheetComplete(aLoadData, NS_OK);
}
// Otherwise, the children are holding strong refs to the data and
// will call SheetComplete() on it when they complete.
return NS_OK;
}
/**
* SheetComplete is the do-it-all cleanup function. It removes the
* load data from the "loading" hashtable, adds the sheet to the
* "completed" hashtable, massages the XUL cache, handles siblings of
* the load data (other loads for the same URI), handles unblocking
* blocked parent loads as needed, and most importantly calls
* NS_RELEASE on the load data to destroy the whole mess.
*/
void
Loader::SheetComplete(SheetLoadData* aLoadData, nsresult aStatus)
{
LOG(("css::Loader::SheetComplete"));
// 8 is probably big enough for all our common cases. It's not likely that
// imports will nest more than 8 deep, and multiple sheets with the same URI
// are rare.
AutoTArray<RefPtr<SheetLoadData>, 8> datasToNotify;
DoSheetComplete(aLoadData, aStatus, datasToNotify);
// Now it's safe to go ahead and notify observers
uint32_t count = datasToNotify.Length();
mDatasToNotifyOn += count;
for (uint32_t i = 0; i < count; ++i) {
--mDatasToNotifyOn;
SheetLoadData* data = datasToNotify[i];
NS_ASSERTION(data && data->mMustNotify, "How did this data get here?");
if (data->mObserver) {
LOG((" Notifying observer %p for data %p. wasAlternate: %d",
data->mObserver.get(), data, data->mWasAlternate));
data->mObserver->StyleSheetLoaded(data->mSheet, data->mWasAlternate,
aStatus);
}
nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver> >::ForwardIterator iter(mObservers);
nsCOMPtr<nsICSSLoaderObserver> obs;
while (iter.HasMore()) {
obs = iter.GetNext();
LOG((" Notifying global observer %p for data %p. wasAlternate: %d",
obs.get(), data, data->mWasAlternate));
obs->StyleSheetLoaded(data->mSheet, data->mWasAlternate, aStatus);
}
}
if (mSheets->mLoadingDatas.Count() == 0 && mSheets->mPendingDatas.Count() > 0) {
LOG((" No more loading sheets; starting alternates"));
StartAlternateLoads();
}
}
void
Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
LoadDataArray& aDatasToNotify)
{
LOG(("css::Loader::DoSheetComplete"));
NS_PRECONDITION(aLoadData, "Must have a load data!");
NS_PRECONDITION(aLoadData->mSheet, "Must have a sheet");
NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
LOG(("Load completed, status: 0x%x", aStatus));
// Twiddle the hashtables
if (aLoadData->mURI) {
LOG_URI(" Finished loading: '%s'", aLoadData->mURI);
// Remove the data from the list of loading datas
if (aLoadData->mIsLoading) {
URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
aLoadData->mLoaderPrincipal,
aLoadData->mSheet->GetCORSMode(),
aLoadData->mSheet->GetReferrerPolicy());
#ifdef DEBUG
SheetLoadData *loadingData;
NS_ASSERTION(mSheets->mLoadingDatas.Get(&key, &loadingData) &&
loadingData == aLoadData,
"Bad loading table");
#endif
mSheets->mLoadingDatas.Remove(&key);
aLoadData->mIsLoading = false;
}
}
// Go through and deal with the whole linked list.
SheetLoadData* data = aLoadData;
while (data) {
if (!data->mSheetAlreadyComplete) {
// If mSheetAlreadyComplete, then the sheet could well be modified between
// when we posted the async call to SheetComplete and now, since the sheet
// was page-accessible during that whole time.
MOZ_ASSERT(!(data->mSheet->IsGecko() &&
data->mSheet->AsGecko()->IsModified()),
"should not get marked modified during parsing");
data->mSheet->SetComplete();
data->ScheduleLoadEventIfNeeded(aStatus);
}
if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
// Don't notify here so we don't trigger script. Remember the
// info we need to notify, then do it later when it's safe.
aDatasToNotify.AppendElement(data);
// On append failure, just press on. We'll fail to notify the observer,
// but not much we can do about that....
}
NS_ASSERTION(!data->mParentData ||
data->mParentData->mPendingChildren != 0,
"Broken pending child count on our parent");
// If we have a parent, our parent is no longer being parsed, and
// we are the last pending child, then our load completion
// completes the parent too. Note that the parent _can_ still be
// being parsed (eg if the child (us) failed to open the channel
// or some such).
if (data->mParentData &&
--(data->mParentData->mPendingChildren) == 0 &&
!mParsingDatas.Contains(data->mParentData)) {
DoSheetComplete(data->mParentData, aStatus, aDatasToNotify);
}
data = data->mNext;
}
// Now that it's marked complete, put the sheet in our cache.
// If we ever start doing this for failure aStatus, we'll need to
// adjust the PostLoadEvent code that thinks anything already
// complete must have loaded succesfully.
if (NS_SUCCEEDED(aStatus) && aLoadData->mURI) {
// Pick our sheet to cache carefully. Ideally, we want to cache
// one of the sheets that will be kept alive by a document or
// parent sheet anyway, so that if someone then accesses it via
// CSSOM we won't have extra clones of the inner lying around.
if (aLoadData->mSheet->IsGecko()) {
data = aLoadData;
CSSStyleSheet* sheet = aLoadData->mSheet->AsGecko();
while (data) {
if (data->mSheet->GetParentSheet() || data->mSheet->GetOwnerNode()) {
sheet = data->mSheet->AsGecko();
break;
}
data = data->mNext;
}
#ifdef MOZ_XUL
if (IsChromeURI(aLoadData->mURI)) {
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
if (cache && cache->IsEnabled()) {
if (!cache->GetStyleSheet(aLoadData->mURI)) {
LOG((" Putting sheet in XUL prototype cache"));
NS_ASSERTION(sheet->IsComplete(),
"Should only be caching complete sheets");
cache->PutStyleSheet(sheet);
}
}
}
else {
#endif
URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
aLoadData->mLoaderPrincipal,
aLoadData->mSheet->GetCORSMode(),
aLoadData->mSheet->GetReferrerPolicy());
NS_ASSERTION(sheet->IsComplete(),
"Should only be caching complete sheets");
mSheets->mCompleteSheets.Put(&key, sheet);
#ifdef MOZ_XUL
}
#endif
} else {
NS_WARNING("stylo: Stylesheet caching not yet supported - see bug 1290218.");
}
}
NS_RELEASE(aLoadData); // this will release parents and siblings and all that
}
nsresult
Loader::LoadInlineStyle(nsIContent* aElement,
const nsAString& aBuffer,
uint32_t aLineNumber,
const nsAString& aTitle,
const nsAString& aMedia,
Element* aScopeElement,
nsICSSLoaderObserver* aObserver,
bool* aCompleted,
bool* aIsAlternate)
{
LOG(("css::Loader::LoadInlineStyle"));
NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
*aCompleted = true;
if (!mEnabled) {
LOG_WARN((" Not enabled"));
return NS_ERROR_NOT_AVAILABLE;
}
NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
NS_ASSERTION(owningElement, "Element is not a style linking element!");
// Since we're not planning to load a URI, no need to hand a principal to the
// load data or to CreateSheet(). Also, OK to use CORS_NONE for the CORS
// mode and mDocument's ReferrerPolicy.
StyleSheetState state;
RefPtr<StyleSheet> sheet;
nsresult rv = CreateSheet(nullptr, aElement, nullptr, eAuthorSheetFeatures,
CORS_NONE, mDocument->GetReferrerPolicy(),
EmptyString(), // no inline integrity checks
false, false, aTitle, state, aIsAlternate,
&sheet);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(state == eSheetNeedsParser,
"Inline sheets should not be cached");
LOG((" Sheet is alternate: %d", *aIsAlternate));
PrepareSheet(sheet, aTitle, aMedia, nullptr, aScopeElement, *aIsAlternate);
if (aElement->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
ShadowRoot* containingShadow = aElement->GetContainingShadow();
MOZ_ASSERT(containingShadow);
containingShadow->InsertSheet(sheet, aElement);
} else {
rv = InsertSheetInDoc(sheet, aElement, mDocument);
NS_ENSURE_SUCCESS(rv, rv);
}
SheetLoadData* data = new SheetLoadData(this, aTitle, nullptr, sheet,
owningElement, *aIsAlternate,
aObserver, nullptr, static_cast<nsINode*>(aElement));
// We never actually load this, so just set its principal directly
sheet->SetPrincipal(aElement->NodePrincipal());
NS_ADDREF(data);
data->mLineNumber = aLineNumber;
// Parse completion releases the load data
rv = ParseSheet(aBuffer, data, *aCompleted);
NS_ENSURE_SUCCESS(rv, rv);
// If aCompleted is true, |data| may well be deleted by now.
if (!*aCompleted) {
data->mMustNotify = true;
}
return rv;
}
nsresult
Loader::LoadStyleLink(nsIContent* aElement,
nsIURI* aURL,
const nsAString& aTitle,
const nsAString& aMedia,
bool aHasAlternateRel,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
nsICSSLoaderObserver* aObserver,
bool* aIsAlternate)
{
LOG(("css::Loader::LoadStyleLink"));
NS_PRECONDITION(aURL, "Must have URL to load");
NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
LOG_URI(" Link uri: '%s'", aURL);
LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aTitle).get()));
LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aMedia).get()));
LOG((" Link alternate rel: %d", aHasAlternateRel));
if (!mEnabled) {
LOG_WARN((" Not enabled"));
return NS_ERROR_NOT_AVAILABLE;
}
NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
nsIPrincipal* principal =
aElement ? aElement->NodePrincipal() : mDocument->NodePrincipal();
nsISupports* context = aElement;
if (!context) {
context = mDocument;
}
nsresult rv = CheckContentPolicy(principal, aURL, context, false);
if (NS_WARN_IF(NS_FAILED(rv))) {
// Don't fire the error event if our document is loaded as data. We're
// supposed to not even try to do loads in that case... Unfortunately, we
// implement that via nsDataDocumentContentPolicy, which doesn't have a good
// way to communicate back to us that _it_ is the thing that blocked the
// load.
if (aElement && !mDocument->IsLoadedAsData()) {
// Fire an async error event on it.
RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
new LoadBlockingAsyncEventDispatcher(aElement,
NS_LITERAL_STRING("error"),
false, false);
loadBlockingAsyncDispatcher->PostDOMEvent();
}
return rv;
}
StyleSheetState state;
RefPtr<StyleSheet> sheet;
rv = CreateSheet(aURL, aElement, principal, eAuthorSheetFeatures,
aCORSMode, aReferrerPolicy, aIntegrity, false,
aHasAlternateRel, aTitle, state, aIsAlternate,
&sheet);
NS_ENSURE_SUCCESS(rv, rv);
LOG((" Sheet is alternate: %d", *aIsAlternate));
PrepareSheet(sheet, aTitle, aMedia, nullptr, nullptr, *aIsAlternate);
rv = InsertSheetInDoc(sheet, aElement, mDocument);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
if (state == eSheetComplete) {
LOG((" Sheet already complete: 0x%p", sheet.get()));
if (aObserver || !mObservers.IsEmpty() || owningElement) {
rv = PostLoadEvent(aURL, sheet, aObserver, *aIsAlternate,
owningElement);
return rv;
}
return NS_OK;
}
// Now we need to actually load it
nsCOMPtr<nsINode> requestingNode = do_QueryInterface(context);
SheetLoadData* data = new SheetLoadData(this, aTitle, aURL, sheet,
owningElement, *aIsAlternate,
aObserver, principal, requestingNode);
NS_ADDREF(data);
// If we have to parse and it's an alternate non-inline, defer it
if (aURL && state == eSheetNeedsParser && mSheets->mLoadingDatas.Count() != 0 &&
*aIsAlternate) {
LOG((" Deferring alternate sheet load"));
URIPrincipalReferrerPolicyAndCORSModeHashKey key(data->mURI,
data->mLoaderPrincipal,
data->mSheet->GetCORSMode(),
data->mSheet->GetReferrerPolicy());
mSheets->mPendingDatas.Put(&key, data);
data->mMustNotify = true;
return NS_OK;
}
// Load completion will free the data
rv = LoadSheet(data, state, false);
NS_ENSURE_SUCCESS(rv, rv);
data->mMustNotify = true;
return rv;
}
static bool
HaveAncestorDataWithURI(SheetLoadData *aData, nsIURI *aURI)
{
if (!aData->mURI) {
// Inline style; this won't have any ancestors
MOZ_ASSERT(!aData->mParentData,
"How does inline style have a parent?");
return false;
}
bool equal;
if (NS_FAILED(aData->mURI->Equals(aURI, &equal)) || equal) {
return true;
}
// Datas down the mNext chain have the same URI as aData, so we
// don't have to compare to them. But they might have different
// parents, and we have to check all of those.
while (aData) {
if (aData->mParentData &&
HaveAncestorDataWithURI(aData->mParentData, aURI)) {
return true;
}
aData = aData->mNext;
}
return false;
}
nsresult
Loader::LoadChildSheet(StyleSheet* aParentSheet,
nsIURI* aURL,
nsMediaList* aMedia,
ImportRule* aParentRule,
LoaderReusableStyleSheets* aReusableSheets)
{
LOG(("css::Loader::LoadChildSheet"));
NS_PRECONDITION(aURL, "Must have a URI to load");
NS_PRECONDITION(aParentSheet, "Must have a parent sheet");
if (!mEnabled) {
LOG_WARN((" Not enabled"));
return NS_ERROR_NOT_AVAILABLE;
}
LOG_URI(" Child uri: '%s'", aURL);
nsCOMPtr<nsINode> owningNode;
// check for an owning document: if none, don't bother walking up the parent
// sheets
if (aParentSheet->GetOwningDocument()) {
StyleSheet* topSheet = aParentSheet;
while (StyleSheet* parent = topSheet->GetParentSheet()) {
topSheet = parent;
}
owningNode = topSheet->GetOwnerNode();
}
nsISupports* context = owningNode;
if (!context) {
context = mDocument;
}
nsIPrincipal* principal = aParentSheet->Principal();
nsresult rv = CheckContentPolicy(principal, aURL, context, false);
NS_ENSURE_SUCCESS(rv, rv);
SheetLoadData* parentData = nullptr;
nsCOMPtr<nsICSSLoaderObserver> observer;
int32_t count = mParsingDatas.Length();
if (count > 0) {
LOG((" Have a parent load"));
parentData = mParsingDatas.ElementAt(count - 1);
// Check for cycles
if (HaveAncestorDataWithURI(parentData, aURL)) {
// Houston, we have a loop, blow off this child and pretend this never
// happened
LOG_ERROR((" @import cycle detected, dropping load"));
return NS_OK;
}
NS_ASSERTION(parentData->mSheet == aParentSheet,
"Unexpected call to LoadChildSheet");
} else {
LOG((" No parent load; must be CSSOM"));
// No parent load data, so the sheet will need to be notified when
// we finish, if it can be, if we do the load asynchronously.
// XXXheycam ServoStyleSheet doesn't implement nsICSSLoaderObserver yet.
MOZ_ASSERT(aParentSheet->IsGecko(),
"stylo: ServoStyleSheets don't support child sheet loading yet");
observer = aParentSheet->AsGecko();
}
// Now that we know it's safe to load this (passes security check and not a
// loop) do so.
RefPtr<StyleSheet> sheet;
RefPtr<CSSStyleSheet> reusableSheet;
StyleSheetState state;
if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, reusableSheet)) {
sheet = reusableSheet;
aParentRule->SetSheet(reusableSheet);
state = eSheetComplete;
} else {
bool isAlternate;
const nsSubstring& empty = EmptyString();
// For now, use CORS_NONE for child sheets
rv = CreateSheet(aURL, nullptr, principal,
aParentSheet->ParsingMode(),
CORS_NONE, aParentSheet->GetReferrerPolicy(),
EmptyString(), // integrity is only checked on main sheet
parentData ? parentData->mSyncLoad : false,
false, empty, state, &isAlternate, &sheet);
NS_ENSURE_SUCCESS(rv, rv);
PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate);
}
rv = InsertChildSheet(sheet, aParentSheet, aParentRule);
NS_ENSURE_SUCCESS(rv, rv);
if (state == eSheetComplete) {
LOG((" Sheet already complete"));
// We're completely done. No need to notify, even, since the
// @import rule addition/modification will trigger the right style
// changes automatically.
return NS_OK;
}
nsCOMPtr<nsINode> requestingNode = do_QueryInterface(context);
SheetLoadData* data = new SheetLoadData(this, aURL, sheet, parentData,
observer, principal, requestingNode);
NS_ADDREF(data);
bool syncLoad = data->mSyncLoad;
// Load completion will release the data
rv = LoadSheet(data, state, false);
NS_ENSURE_SUCCESS(rv, rv);
// If syncLoad is true, |data| will be deleted by now.
if (!syncLoad) {
data->mMustNotify = true;
}
return rv;
}
nsresult
Loader::LoadSheetSync(nsIURI* aURL,
SheetParsingMode aParsingMode,
bool aUseSystemPrincipal,
RefPtr<StyleSheet>* aSheet)
{
LOG(("css::Loader::LoadSheetSync"));
return InternalLoadNonDocumentSheet(aURL,
false, aParsingMode, aUseSystemPrincipal,
nullptr, EmptyCString(),
aSheet, nullptr);
}
nsresult
Loader::LoadSheet(nsIURI* aURL,
nsIPrincipal* aOriginPrincipal,
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
RefPtr<StyleSheet>* aSheet)
{
LOG(("css::Loader::LoadSheet(aURL, aObserver, aSheet) api call"));
NS_PRECONDITION(aSheet, "aSheet is null");
return InternalLoadNonDocumentSheet(aURL,
false, eAuthorSheetFeatures, false,
aOriginPrincipal, aCharset,
aSheet, aObserver);
}
nsresult
Loader::LoadSheet(nsIURI* aURL,
bool aIsPreload,
nsIPrincipal* aOriginPrincipal,
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity)
{
LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
return InternalLoadNonDocumentSheet(aURL,
aIsPreload, eAuthorSheetFeatures, false,
aOriginPrincipal, aCharset,
nullptr, aObserver,
aCORSMode, aReferrerPolicy, aIntegrity);
}
nsresult
Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
bool aIsPreload,
SheetParsingMode aParsingMode,
bool aUseSystemPrincipal,
nsIPrincipal* aOriginPrincipal,
const nsCString& aCharset,
RefPtr<StyleSheet>* aSheet,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity)
{
NS_PRECONDITION(aURL, "Must have a URI to load");
NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
NS_PRECONDITION(!aUseSystemPrincipal || !aObserver,
"Shouldn't load system-principal sheets async");
NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
LOG_URI(" Non-document sheet uri: '%s'", aURL);
if (aSheet) {
*aSheet = nullptr;
}
if (!mEnabled) {
LOG_WARN((" Not enabled"));
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = CheckContentPolicy(aOriginPrincipal, aURL, mDocument, aIsPreload);
NS_ENSURE_SUCCESS(rv, rv);
StyleSheetState state;
bool isAlternate;
RefPtr<StyleSheet> sheet;
bool syncLoad = (aObserver == nullptr);
const nsSubstring& empty = EmptyString();
rv = CreateSheet(aURL, nullptr, aOriginPrincipal, aParsingMode,
aCORSMode, aReferrerPolicy, aIntegrity, syncLoad,
false, empty, state, &isAlternate, &sheet);
NS_ENSURE_SUCCESS(rv, rv);
PrepareSheet(sheet, empty, empty, nullptr, nullptr, isAlternate);
if (state == eSheetComplete) {
LOG((" Sheet already complete"));
if (aObserver || !mObservers.IsEmpty()) {
rv = PostLoadEvent(aURL, sheet, aObserver, false, nullptr);
}
if (aSheet) {
sheet.swap(*aSheet);
}
return rv;
}
SheetLoadData* data =
new SheetLoadData(this, aURL, sheet, syncLoad,
aUseSystemPrincipal, aCharset, aObserver,
aOriginPrincipal, mDocument);
NS_ADDREF(data);
rv = LoadSheet(data, state, aIsPreload);
NS_ENSURE_SUCCESS(rv, rv);
if (aSheet) {
sheet.swap(*aSheet);
}
if (aObserver) {
data->mMustNotify = true;
}
return rv;
}
nsresult
Loader::PostLoadEvent(nsIURI* aURI,
StyleSheet* aSheet,
nsICSSLoaderObserver* aObserver,
bool aWasAlternate,
nsIStyleSheetLinkingElement* aElement)
{
LOG(("css::Loader::PostLoadEvent"));
NS_PRECONDITION(aSheet, "Must have sheet");
NS_PRECONDITION(aObserver || !mObservers.IsEmpty() || aElement,
"Must have observer or element");
RefPtr<SheetLoadData> evt =
new SheetLoadData(this, EmptyString(), // title doesn't matter here
aURI,
aSheet,
aElement,
aWasAlternate,
aObserver,
nullptr,
mDocument);
NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY);
if (!mPostedEvents.AppendElement(evt)) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsresult rv = NS_DispatchToCurrentThread(evt);
if (NS_FAILED(rv)) {
NS_WARNING("failed to dispatch stylesheet load event");
mPostedEvents.RemoveElement(evt);
} else {
// We'll unblock onload when we handle the event.
if (mDocument) {
mDocument->BlockOnload();
}
// We want to notify the observer for this data.
evt->mMustNotify = true;
evt->mSheetAlreadyComplete = true;
// If we get to this code, aSheet loaded correctly at some point, so
// we can just use NS_OK for the status. Note that we do this here
// and not from inside our SheetComplete so that we don't end up
// running the load event async.
evt->ScheduleLoadEventIfNeeded(NS_OK);
}
return rv;
}
void
Loader::HandleLoadEvent(SheetLoadData* aEvent)
{
// XXXbz can't assert this yet.... May not have an observer because
// we're unblocking the parser
// NS_ASSERTION(aEvent->mObserver, "Must have observer");
NS_ASSERTION(aEvent->mSheet, "Must have sheet");
// Very important: this needs to come before the SheetComplete call
// below, so that HasPendingLoads() will test true as needed under
// notifications we send from that SheetComplete call.
mPostedEvents.RemoveElement(aEvent);
if (!aEvent->mIsCancelled) {
// SheetComplete will call Release(), so give it a reference to do
// that with.
NS_ADDREF(aEvent);
SheetComplete(aEvent, NS_OK);
}
if (mDocument) {
mDocument->UnblockOnload(true);
}
}
static void
StopLoadingSheets(
nsDataHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey, SheetLoadData*>& aDatas,
Loader::LoadDataArray& aArr)
{
for (auto iter = aDatas.Iter(); !iter.Done(); iter.Next()) {
SheetLoadData* data = iter.Data();
MOZ_ASSERT(data, "Must have a data!");
data->mIsLoading = false; // we will handle the removal right here
data->mIsCancelled = true;
aArr.AppendElement(data);
iter.Remove();
}
}
nsresult
Loader::Stop()
{
uint32_t pendingCount = mSheets ? mSheets->mPendingDatas.Count() : 0;
uint32_t loadingCount = mSheets ? mSheets->mLoadingDatas.Count() : 0;
LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());
if (pendingCount) {
StopLoadingSheets(mSheets->mPendingDatas, arr);
}
if (loadingCount) {
StopLoadingSheets(mSheets->mLoadingDatas, arr);
}
uint32_t i;
for (i = 0; i < mPostedEvents.Length(); ++i) {
SheetLoadData* data = mPostedEvents[i];
data->mIsCancelled = true;
if (arr.AppendElement(data)) {
// SheetComplete() calls Release(), so give this an extra ref.
NS_ADDREF(data);
}
#ifdef DEBUG
else {
NS_NOTREACHED("We preallocated this memory... shouldn't really fail, "
"except we never check that preallocation succeeds.");
}
#endif
}
mPostedEvents.Clear();
mDatasToNotifyOn += arr.Length();
for (i = 0; i < arr.Length(); ++i) {
--mDatasToNotifyOn;
SheetComplete(arr[i], NS_BINDING_ABORTED);
}
return NS_OK;
}
bool
Loader::HasPendingLoads()
{
return
(mSheets && mSheets->mLoadingDatas.Count() != 0) ||
(mSheets && mSheets->mPendingDatas.Count() != 0) ||
mPostedEvents.Length() != 0 ||
mDatasToNotifyOn != 0;
}
nsresult
Loader::AddObserver(nsICSSLoaderObserver* aObserver)
{
NS_PRECONDITION(aObserver, "Must have observer");
if (mObservers.AppendElementUnlessExists(aObserver)) {
return NS_OK;
}
return NS_ERROR_OUT_OF_MEMORY;
}
void
Loader::RemoveObserver(nsICSSLoaderObserver* aObserver)
{
mObservers.RemoveElement(aObserver);
}
void
Loader::StartAlternateLoads()
{
NS_PRECONDITION(mSheets, "Don't call me!");
LoadDataArray arr(mSheets->mPendingDatas.Count());
for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
arr.AppendElement(iter.Data());
iter.Remove();
}
mDatasToNotifyOn += arr.Length();
for (uint32_t i = 0; i < arr.Length(); ++i) {
--mDatasToNotifyOn;
LoadSheet(arr[i], eSheetNeedsParser, false);
}
}
NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Loader)
if (tmp->mSheets) {
for (auto iter = tmp->mSheets->mCompleteSheets.Iter();
!iter.Done();
iter.Next()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "Sheet cache nsCSSLoader");
if (iter.UserData()->IsGecko()) {
CSSStyleSheet* sheet = iter.UserData()->AsGecko();
cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMCSSStyleSheet*, sheet));
}
}
}
nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>>::ForwardIterator
it(tmp->mObservers);
while (it.HasMore()) {
ImplCycleCollectionTraverse(cb, it.GetNext(),
"mozilla::css::Loader.mObservers");
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
if (tmp->mSheets) {
tmp->mSheets->mCompleteSheets.Clear();
}
tmp->mObservers.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Loader, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Loader, Release)
size_t
Loader::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t n = aMallocSizeOf(this);
if (mSheets) {
n += mSheets->mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (auto iter = mSheets->mCompleteSheets.ConstIter();
!iter.Done();
iter.Next()) {
// If aSheet has a parent, then its parent will report it so we don't
// have to worry about it here. Likewise, if aSheet has an owning node,
// then the document that node is in will report it.
const StyleSheet* sheet = iter.UserData();
n += (sheet->GetOwnerNode() || sheet->GetParentSheet())
? 0
: sheet->SizeOfIncludingThis(aMallocSizeOf);
}
}
n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
// Measurement of the following members may be added later if DMD finds it is
// worthwhile:
// - mLoadingDatas: transient, and should be small
// - mPendingDatas: transient, and should be small
// - mParsingDatas: transient, and should be small
// - mPostedEvents: transient, and should be small
//
// The following members aren't measured:
// - mDocument, because it's a weak backpointer
// - mPreferredSheet, because it can be a shared string
return n;
}
StyleBackendType
Loader::GetStyleBackendType() const
{
MOZ_ASSERT(mStyleBackendType || mDocument,
"you must construct a Loader with a document or set a "
"StyleBackendType on it before calling GetStyleBackendType");
if (mStyleBackendType) {
return *mStyleBackendType;
}
return mDocument->GetStyleBackendType();
}
} // namespace css
} // namespace mozilla