blob: bccc8cbd29df000ca16a2ef80a3f6605d1c4970b [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 "third_party/blink/renderer/core/html/link_style.h"
#include "services/network/public/mojom/referrer_policy.mojom-shared.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/html/cross_origin_attribute.h"
#include "third_party/blink/renderer/core/html/html_link_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/loader/importance_attribute.h"
#include "third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h"
#include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/subresource_integrity.h"
#include "third_party/blink/renderer/platform/network/mime/content_type.h"
#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink {
using namespace html_names;
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 MakeGarbageCollected<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 pending_script.cc about why this check is necessary
// here, instead of in the resource fetcher. https://crbug.com/500701.
if (!cached_style_sheet->ErrorOccurred() &&
!owner_->FastGetAttribute(kIntegrityAttr).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->GetResponse().IsOpaqueResponseFromServiceWorker(),
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 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;
}
LinkStyle::LoadReturnValue LinkStyle::LoadStylesheetIfNeeded(
const LinkLoadParameters& params,
const WTF::TextEncoding& charset) {
if (disabled_state_ == kDisabled || !owner_->RelAttribute().IsStyleSheet() ||
!StyleSheetTypeIsSupported(params.type) || !ShouldLoadResource() ||
!params.href.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);
if (params.cross_origin != kCrossOriginAttributeNotSet) {
SetFetchFollowingCors();
}
// 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.
FetchParameters::DeferOption defer_option =
!media_query_matches || owner_->IsAlternate() ? FetchParameters::kLazyLoad
: FetchParameters::kNoDefer;
owner_->LoadStylesheet(params, charset, defer_option, 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(kCrossoriginAttr)),
owner_->TypeValue().DeprecatedLower(),
owner_->AsValue().DeprecatedLower(), owner_->Media().DeprecatedLower(),
owner_->nonce(), owner_->IntegrityValue(),
owner_->ImportanceValue().LowerASCII(), owner_->GetReferrerPolicy(),
owner_->GetNonEmptyURLAttribute(kHrefAttr),
owner_->FastGetAttribute(kImagesrcsetAttr),
owner_->FastGetAttribute(kImagesizesAttr));
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 (!sheet_ && !owner_->LoadLink(params))
return;
if (LoadStylesheetIfNeeded(params, charset) == 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(kHrefAttr);
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