| // 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-blink.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/link_load_parameters.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/heap/heap.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 { |
| |
| static bool StyleSheetTypeIsSupported(const String& type) { |
| String trimmed_type = ContentType(type).GetType(); |
| return trimmed_type.IsEmpty() || |
| MIMETypeRegistry::IsSupportedStyleSheetMIMEType(trimmed_type); |
| } |
| |
| LinkStyle::LinkStyle(HTMLLinkElement* owner) |
| : LinkResource(owner), |
| disabled_state_(kUnset), |
| pending_sheet_type_(kNone), |
| render_blocking_behavior_(RenderBlockingBehavior::kUnset), |
| loading_(false), |
| fired_load_(false), |
| loaded_sheet_(false) {} |
| |
| LinkStyle::~LinkStyle() = default; |
| |
| 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; |
| } |
| |
| auto* cached_style_sheet = To<CSSStyleSheetResource>(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(html_names::kIntegrityAttr).IsEmpty() && |
| !cached_style_sheet->IntegrityMetadata().IsEmpty()) || |
| resource->IsLinkPreload()) { |
| ResourceIntegrityDisposition disposition = |
| cached_style_sheet->IntegrityDisposition(); |
| |
| SubresourceIntegrityHelper::DoReport( |
| *GetExecutionContext(), cached_style_sheet->IntegrityReportInfo()); |
| |
| if (disposition == ResourceIntegrityDisposition::kFailed) { |
| loading_ = false; |
| RemovePendingSheet(); |
| NotifyLoadedSheetAndAllCriticalSubresources( |
| Node::kErrorOccurredLoadingSubresource); |
| return; |
| } |
| } |
| |
| auto* parser_context = MakeGarbageCollected<CSSParserContext>( |
| GetDocument(), cached_style_sheet->GetResponse().ResponseUrl(), |
| cached_style_sheet->GetResponse().IsCorsSameOrigin(), |
| Referrer(cached_style_sheet->GetResponse().ResponseUrl(), |
| cached_style_sheet->GetReferrerPolicy()), |
| cached_style_sheet->Encoding()); |
| if (cached_style_sheet->GetResourceRequest().IsAdResource()) { |
| parser_context->SetIsAdRelated(); |
| } |
| |
| if (StyleSheetContents* parsed_sheet = |
| cached_style_sheet->CreateParsedStyleSheetFromCache(parser_context)) { |
| if (sheet_) |
| ClearSheet(); |
| sheet_ = MakeGarbageCollected<CSSStyleSheet>(parsed_sheet, *owner_); |
| sheet_->SetMediaQueries( |
| MediaQuerySet::Create(owner_->Media(), GetExecutionContext())); |
| if (owner_->IsInDocumentTree()) |
| SetSheetTitle(owner_->title()); |
| |
| loading_ = false; |
| parsed_sheet->CheckLoaded(); |
| parsed_sheet->SetRenderBlocking(render_blocking_behavior_); |
| |
| return; |
| } |
| |
| auto* style_sheet = MakeGarbageCollected<StyleSheetContents>( |
| parser_context, cached_style_sheet->Url()); |
| |
| if (sheet_) |
| ClearSheet(); |
| |
| sheet_ = MakeGarbageCollected<CSSStyleSheet>(style_sheet, *owner_); |
| sheet_->SetMediaQueries( |
| MediaQuerySet::Create(owner_->Media(), GetExecutionContext())); |
| if (owner_->IsInDocumentTree()) |
| SetSheetTitle(owner_->title()); |
| |
| style_sheet->SetRenderBlocking(render_blocking_behavior_); |
| style_sheet->ParseAuthorStyleSheet(cached_style_sheet); |
| |
| 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; |
| // Whenever the disabled attribute is removed, set the link element's |
| // explicitly enabled attribute to true. |
| if (!disabled) |
| explicitly_enabled_ = true; |
| 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_) { |
| // TODO(crbug.com/1087043): Remove this if() condition once the feature has |
| // landed and no compat issues are reported. |
| if (RuntimeEnabledFeatures::LinkDisabledNewSpecBehaviorEnabled( |
| GetExecutionContext())) { |
| DCHECK(disabled) |
| << "If link is being enabled, sheet_ shouldn't exist yet"; |
| ClearSheet(); |
| GetDocument().GetStyleEngine().SetNeedsActiveStyleUpdate( |
| owner_->GetTreeScope()); |
| return; |
| } else { |
| sheet_->setDisabled(disabled); |
| } |
| } |
| |
| if (disabled_state_ == kEnabledViaScript && owner_->ShouldProcessStyle()) |
| Process(); |
| } |
| |
| 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(); |
| } |
| |
| 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(), GetExecutionContext()); |
| MediaQueryEvaluator evaluator(frame); |
| media_query_matches = evaluator.Eval(*media); |
| } |
| |
| bool is_in_body = owner_->IsDescendantOf(owner_->GetDocument().body()); |
| |
| // Don't hold up layout tree construction and script execution on |
| // stylesheets that are not needed for the layout at the moment. |
| bool critical_style = media_query_matches && !owner_->IsAlternate(); |
| bool render_blocking = critical_style && owner_->IsCreatedByParser(); |
| |
| AddPendingSheet(render_blocking ? kBlocking : kNonBlocking); |
| |
| // 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 = |
| !critical_style ? FetchParameters::kLazyLoad : FetchParameters::kNoDefer; |
| |
| render_blocking_behavior_ = |
| !critical_style |
| ? RenderBlockingBehavior::kNonBlocking |
| : (render_blocking |
| ? (is_in_body ? RenderBlockingBehavior::kInBodyParserBlocking |
| : RenderBlockingBehavior::kBlocking) |
| : RenderBlockingBehavior::kNonBlockingDynamic); |
| |
| owner_->LoadStylesheet(params, charset, defer_option, this, |
| render_blocking_behavior_); |
| |
| 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(html_names::kCrossoriginAttr)), |
| owner_->TypeValue().DeprecatedLower(), |
| owner_->AsValue().DeprecatedLower(), owner_->Media().DeprecatedLower(), |
| owner_->nonce(), owner_->IntegrityValue(), |
| owner_->ImportanceValue().LowerASCII(), owner_->GetReferrerPolicy(), |
| owner_->GetNonEmptyURLAttribute(html_names::kHrefAttr), |
| owner_->FastGetAttribute(html_names::kImagesrcsetAttr), |
| owner_->FastGetAttribute(html_names::kImagesizesAttr)); |
| |
| WTF::TextEncoding charset = GetCharset(); |
| |
| if (owner_->RelAttribute().GetIconType() != |
| mojom::blink::FaviconIconType::kInvalid && |
| params.href.IsValid() && !params.href.IsEmpty()) { |
| if (!owner_->ShouldLoadLink()) |
| return; |
| if (!GetExecutionContext()) |
| return; |
| if (!GetExecutionContext()->GetSecurityOrigin()->CanDisplay(params.href)) |
| return; |
| if (!GetExecutionContext() |
| ->GetContentSecurityPolicy() |
| ->AllowImageFromSource(params.href, params.href, |
| RedirectStatus::kNoRedirect)) { |
| return; |
| } |
| if (GetDocument().GetFrame()) |
| GetDocument().GetFrame()->UpdateFaviconURL(); |
| } |
| |
| 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(html_names::kHrefAttr); |
| if (href.IsValid() && !href.IsEmpty()) |
| GetDocument().GetStyleEngine().SetPreferredStylesheetSetNameIfNotSet(title); |
| } |
| |
| void LinkStyle::OwnerRemoved() { |
| if (StyleSheetIsLoading()) |
| RemovePendingSheet(); |
| |
| if (sheet_) |
| ClearSheet(); |
| } |
| |
| void LinkStyle::Trace(Visitor* visitor) const { |
| visitor->Trace(sheet_); |
| LinkResource::Trace(visitor); |
| ResourceClient::Trace(visitor); |
| } |
| |
| } // namespace blink |