blob: c6a4c44796cd2a0b17859daf090dd7e710d47acb [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/html/LinkStyle.h"
#include "core/css/StyleSheetContents.h"
#include "core/dom/Document.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameClient.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/CrossOriginAttribute.h"
#include "core/html/HTMLLinkElement.h"
#include "core/html_names.h"
#include "core/loader/SubresourceIntegrityHelper.h"
#include "core/loader/resource/CSSStyleSheetResource.h"
#include "platform/Histogram.h"
#include "platform/loader/SubresourceIntegrity.h"
#include "platform/loader/fetch/FetchParameters.h"
#include "platform/loader/fetch/ResourceLoaderOptions.h"
#include "platform/loader/fetch/ResourceRequest.h"
#include "platform/network/mime/ContentType.h"
#include "platform/network/mime/MIMETypeRegistry.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/ReferrerPolicy.h"
#include "platform/weborigin/SecurityPolicy.h"
#include "platform/wtf/text/AtomicString.h"
namespace blink {
using namespace HTMLNames;
static bool StyleSheetTypeIsSupported(const String& type) {
String trimmed_type = ContentType(type).GetType();
return trimmed_type.IsEmpty() ||
MIMETypeRegistry::IsSupportedStyleSheetMIMEType(trimmed_type);
}
LinkStyle* LinkStyle::Create(HTMLLinkElement* owner) {
return new LinkStyle(owner);
}
LinkStyle::LinkStyle(HTMLLinkElement* owner)
: LinkResource(owner),
disabled_state_(kUnset),
pending_sheet_type_(kNone),
loading_(false),
fired_load_(false),
loaded_sheet_(false),
fetch_following_cors_(false) {}
LinkStyle::~LinkStyle() = default;
enum StyleSheetCacheStatus {
kStyleSheetNewEntry,
kStyleSheetInDiskCache,
kStyleSheetInMemoryCache,
kStyleSheetCacheStatusCount,
};
void LinkStyle::NotifyFinished(Resource* resource) {
if (!owner_->isConnected()) {
// While the stylesheet is asynchronously loading, the owner can be
// disconnected from a document.
// In that case, cancel any processing on the loaded content.
loading_ = false;
RemovePendingSheet();
if (sheet_)
ClearSheet();
return;
}
CSSStyleSheetResource* cached_style_sheet = ToCSSStyleSheetResource(resource);
// See the comment in PendingScript.cpp about why this check is necessary
// here, instead of in the resource fetcher. https://crbug.com/500701.
if (!cached_style_sheet->ErrorOccurred() &&
!owner_->FastGetAttribute(integrityAttr).IsEmpty() &&
!cached_style_sheet->IntegrityMetadata().IsEmpty()) {
ResourceIntegrityDisposition disposition =
cached_style_sheet->IntegrityDisposition();
SubresourceIntegrityHelper::DoReport(
GetDocument(), cached_style_sheet->IntegrityReportInfo());
if (disposition == ResourceIntegrityDisposition::kFailed) {
loading_ = false;
RemovePendingSheet();
NotifyLoadedSheetAndAllCriticalSubresources(
Node::kErrorOccurredLoadingSubresource);
return;
}
}
CSSParserContext* parser_context = CSSParserContext::Create(
GetDocument(), cached_style_sheet->GetResponse().Url(),
cached_style_sheet->GetReferrerPolicy(), cached_style_sheet->Encoding());
if (StyleSheetContents* parsed_sheet =
cached_style_sheet->CreateParsedStyleSheetFromCache(parser_context)) {
if (sheet_)
ClearSheet();
sheet_ = CSSStyleSheet::Create(parsed_sheet, *owner_);
sheet_->SetMediaQueries(MediaQuerySet::Create(owner_->Media()));
if (owner_->IsInDocumentTree())
SetSheetTitle(owner_->title());
SetCrossOriginStylesheetStatus(sheet_.Get());
loading_ = false;
parsed_sheet->CheckLoaded();
return;
}
StyleSheetContents* style_sheet =
StyleSheetContents::Create(cached_style_sheet->Url(), parser_context);
if (sheet_)
ClearSheet();
sheet_ = CSSStyleSheet::Create(style_sheet, *owner_);
sheet_->SetMediaQueries(MediaQuerySet::Create(owner_->Media()));
if (owner_->IsInDocumentTree())
SetSheetTitle(owner_->title());
SetCrossOriginStylesheetStatus(sheet_.Get());
style_sheet->ParseAuthorStyleSheet(cached_style_sheet,
GetDocument().GetSecurityOrigin());
loading_ = false;
style_sheet->NotifyLoadedSheet(cached_style_sheet);
style_sheet->CheckLoaded();
if (style_sheet->IsCacheableForResource()) {
const_cast<CSSStyleSheetResource*>(cached_style_sheet)
->SaveParsedStyleSheet(style_sheet);
}
ClearResource();
}
bool LinkStyle::SheetLoaded() {
if (!StyleSheetIsLoading()) {
RemovePendingSheet();
return true;
}
return false;
}
void LinkStyle::NotifyLoadedSheetAndAllCriticalSubresources(
Node::LoadedSheetErrorStatus error_status) {
if (fired_load_)
return;
loaded_sheet_ = (error_status == Node::kNoErrorLoadingSubresource);
if (owner_)
owner_->ScheduleEvent();
fired_load_ = true;
}
void LinkStyle::StartLoadingDynamicSheet() {
DCHECK_LT(pending_sheet_type_, kBlocking);
AddPendingSheet(kBlocking);
}
void LinkStyle::ClearSheet() {
DCHECK(sheet_);
DCHECK_EQ(sheet_->ownerNode(), owner_);
sheet_.Release()->ClearOwnerNode();
}
bool LinkStyle::StyleSheetIsLoading() const {
if (loading_)
return true;
if (!sheet_)
return false;
return sheet_->Contents()->IsLoading();
}
void LinkStyle::AddPendingSheet(PendingSheetType type) {
if (type <= pending_sheet_type_)
return;
pending_sheet_type_ = type;
if (pending_sheet_type_ == kNonBlocking)
return;
GetDocument().GetStyleEngine().AddPendingSheet(style_engine_context_);
}
void LinkStyle::RemovePendingSheet() {
DCHECK(owner_);
PendingSheetType type = pending_sheet_type_;
pending_sheet_type_ = kNone;
if (type == kNone)
return;
if (type == kNonBlocking) {
// Tell StyleEngine to re-compute styleSheets of this m_owner's treescope.
GetDocument().GetStyleEngine().ModifiedStyleSheetCandidateNode(*owner_);
return;
}
GetDocument().GetStyleEngine().RemovePendingSheet(*owner_,
style_engine_context_);
}
void LinkStyle::SetDisabledState(bool disabled) {
LinkStyle::DisabledState old_disabled_state = disabled_state_;
disabled_state_ = disabled ? kDisabled : kEnabledViaScript;
if (old_disabled_state == disabled_state_)
return;
// If we change the disabled state while the sheet is still loading, then we
// have to perform three checks:
if (StyleSheetIsLoading()) {
// Check #1: The sheet becomes disabled while loading.
if (disabled_state_ == kDisabled)
RemovePendingSheet();
// Check #2: An alternate sheet becomes enabled while it is still loading.
if (owner_->RelAttribute().IsAlternate() &&
disabled_state_ == kEnabledViaScript)
AddPendingSheet(kBlocking);
// Check #3: A main sheet becomes enabled while it was still loading and
// after it was disabled via script. It takes really terrible code to make
// this happen (a double toggle for no reason essentially). This happens
// on virtualplastic.net, which manages to do about 12 enable/disables on
// only 3 sheets. :)
if (!owner_->RelAttribute().IsAlternate() &&
disabled_state_ == kEnabledViaScript && old_disabled_state == kDisabled)
AddPendingSheet(kBlocking);
// If the sheet is already loading just bail.
return;
}
if (sheet_) {
sheet_->setDisabled(disabled);
return;
}
if (disabled_state_ == kEnabledViaScript && owner_->ShouldProcessStyle())
Process();
}
void LinkStyle::SetCrossOriginStylesheetStatus(CSSStyleSheet* sheet) {
if (fetch_following_cors_ && GetResource() &&
!GetResource()->ErrorOccurred()) {
// Record the security origin the CORS access check succeeded at, if cross
// origin. Only origins that are script accessible to it may access the
// stylesheet's rules.
sheet->SetAllowRuleAccessFromOrigin(GetDocument().GetSecurityOrigin());
}
fetch_following_cors_ = false;
}
// TODO(yoav): move that logic to LinkLoader
LinkStyle::LoadReturnValue LinkStyle::LoadStylesheetIfNeeded(
const KURL& url,
const WTF::TextEncoding& charset,
const String& type) {
if (disabled_state_ == kDisabled || !owner_->RelAttribute().IsStyleSheet() ||
!StyleSheetTypeIsSupported(type) || !ShouldLoadResource() ||
!url.IsValid())
return kNotNeeded;
if (GetResource()) {
RemovePendingSheet();
ClearResource();
ClearFetchFollowingCORS();
}
if (!owner_->ShouldLoadLink())
return kBail;
loading_ = true;
String title = owner_->title();
if (!title.IsEmpty() && !owner_->IsAlternate() &&
disabled_state_ != kEnabledViaScript && owner_->IsInDocumentTree()) {
GetDocument().GetStyleEngine().SetPreferredStylesheetSetNameIfNotSet(title);
}
bool media_query_matches = true;
LocalFrame* frame = LoadingFrame();
if (!owner_->Media().IsEmpty() && frame) {
scoped_refptr<MediaQuerySet> media = MediaQuerySet::Create(owner_->Media());
MediaQueryEvaluator evaluator(frame);
media_query_matches = evaluator.Eval(*media);
}
// Don't hold up layout tree construction and script execution on
// stylesheets that are not needed for the layout at the moment.
bool blocking = media_query_matches && !owner_->IsAlternate() &&
owner_->IsCreatedByParser();
AddPendingSheet(blocking ? kBlocking : kNonBlocking);
ResourceRequest resource_request(GetDocument().CompleteURL(url));
ReferrerPolicy referrer_policy = owner_->GetReferrerPolicy();
if (referrer_policy != kReferrerPolicyDefault) {
resource_request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer(
referrer_policy, url, GetDocument().OutgoingReferrer()));
}
ResourceLoaderOptions options;
options.initiator_info.name = owner_->localName();
FetchParameters params(resource_request, options);
params.SetCharset(charset);
// Load stylesheets that are not needed for the layout immediately with low
// priority. When the link element is created by scripts, load the
// stylesheets asynchronously but in high priority.
if (!media_query_matches || owner_->IsAlternate())
params.SetDefer(FetchParameters::kLazyLoad);
params.SetContentSecurityPolicyNonce(owner_->nonce());
CrossOriginAttributeValue cross_origin =
GetCrossOriginAttributeValue(owner_->FastGetAttribute(crossoriginAttr));
if (cross_origin != kCrossOriginAttributeNotSet) {
params.SetCrossOriginAccessControl(GetDocument().GetSecurityOrigin(),
cross_origin);
SetFetchFollowingCORS();
}
String integrity_attr = owner_->FastGetAttribute(integrityAttr);
if (!integrity_attr.IsEmpty()) {
IntegrityMetadataSet metadata_set;
SubresourceIntegrity::ParseIntegrityAttribute(integrity_attr, metadata_set);
params.SetIntegrityMetadata(metadata_set);
params.MutableResourceRequest().SetFetchIntegrity(integrity_attr);
}
CSSStyleSheetResource::Fetch(params, GetDocument().Fetcher(), this);
if (loading_ && !GetResource()) {
// Fetch() synchronous failure case.
// The request may have been denied if (for example) the stylesheet is
// local and the document is remote, or if there was a Content Security
// Policy Failure.
loading_ = false;
RemovePendingSheet();
NotifyLoadedSheetAndAllCriticalSubresources(
Node::kErrorOccurredLoadingSubresource);
}
return kLoaded;
}
void LinkStyle::Process() {
DCHECK(owner_->ShouldProcessStyle());
const LinkLoadParameters params(
owner_->RelAttribute(),
GetCrossOriginAttributeValue(owner_->FastGetAttribute(crossoriginAttr)),
owner_->TypeValue().DeprecatedLower(),
owner_->AsValue().DeprecatedLower(), owner_->Media().DeprecatedLower(),
owner_->nonce(), owner_->IntegrityValue(), owner_->GetReferrerPolicy(),
owner_->GetNonEmptyURLAttribute(hrefAttr),
owner_->FastGetAttribute(srcsetAttr),
owner_->FastGetAttribute(imgsizesAttr));
WTF::TextEncoding charset = GetCharset();
if (owner_->RelAttribute().GetIconType() != kInvalidIcon &&
params.href.IsValid() && !params.href.IsEmpty()) {
if (!owner_->ShouldLoadLink())
return;
if (!GetDocument().GetSecurityOrigin()->CanDisplay(params.href))
return;
if (!GetDocument().GetContentSecurityPolicy()->AllowImageFromSource(
params.href))
return;
if (GetDocument().GetFrame() && GetDocument().GetFrame()->Client()) {
GetDocument().GetFrame()->Client()->DispatchDidChangeIcons(
owner_->RelAttribute().GetIconType());
}
}
if (!owner_->LoadLink(params))
return;
if (LoadStylesheetIfNeeded(params.href, charset, params.type) == kNotNeeded &&
sheet_) {
// we no longer contain a stylesheet, e.g. perhaps rel or type was changed
ClearSheet();
GetDocument().GetStyleEngine().SetNeedsActiveStyleUpdate(
owner_->GetTreeScope());
}
}
void LinkStyle::SetSheetTitle(const String& title) {
if (!owner_->IsInDocumentTree() || !owner_->RelAttribute().IsStyleSheet())
return;
if (sheet_)
sheet_->SetTitle(title);
if (title.IsEmpty() || !IsUnset() || owner_->IsAlternate())
return;
const KURL& href = owner_->GetNonEmptyURLAttribute(hrefAttr);
if (href.IsValid() && !href.IsEmpty())
GetDocument().GetStyleEngine().SetPreferredStylesheetSetNameIfNotSet(title);
}
void LinkStyle::OwnerRemoved() {
if (StyleSheetIsLoading())
RemovePendingSheet();
if (sheet_)
ClearSheet();
}
void LinkStyle::Trace(blink::Visitor* visitor) {
visitor->Trace(sheet_);
LinkResource::Trace(visitor);
ResourceClient::Trace(visitor);
}
} // namespace blink