1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  */
31 
32 #include "third_party/blink/renderer/core/loader/link_loader.h"
33 
34 #include "third_party/blink/renderer/core/dom/document.h"
35 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
36 #include "third_party/blink/renderer/core/frame/local_frame.h"
37 #include "third_party/blink/renderer/core/frame/web_feature.h"
38 #include "third_party/blink/renderer/core/loader/importance_attribute.h"
39 #include "third_party/blink/renderer/core/loader/link_load_parameters.h"
40 #include "third_party/blink/renderer/core/loader/link_loader_client.h"
41 #include "third_party/blink/renderer/core/loader/preload_helper.h"
42 #include "third_party/blink/renderer/core/loader/private/prerender_handle.h"
43 #include "third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h"
44 #include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h"
45 #include "third_party/blink/renderer/core/page/viewport_description.h"
46 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
47 #include "third_party/blink/renderer/platform/loader/fetch/resource_client.h"
48 #include "third_party/blink/renderer/platform/loader/fetch/resource_finish_observer.h"
49 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
50 #include "third_party/blink/renderer/platform/loader/subresource_integrity.h"
51 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
52 
53 namespace blink {
54 
55 class WebPrescientNetworking;
56 
57 namespace {
58 
59 // Decide the prerender type based on the link rel attribute. Returns
60 // base::nullopt if the attribute doesn't indicate the prerender type.
PrerenderRelTypeFromRelAttribute(const LinkRelAttribute & rel_attribute,Document & document)61 base::Optional<mojom::blink::PrerenderRelType> PrerenderRelTypeFromRelAttribute(
62     const LinkRelAttribute& rel_attribute,
63     Document& document) {
64   base::Optional<mojom::blink::PrerenderRelType> rel_type;
65   if (rel_attribute.IsLinkPrerender()) {
66     UseCounter::Count(document, WebFeature::kLinkRelPrerender);
67     rel_type = mojom::blink::PrerenderRelType::kPrerender;
68   }
69   if (rel_attribute.IsLinkNext()) {
70     UseCounter::Count(document, WebFeature::kLinkRelNext);
71     // Prioritize mojom::blink::PrerenderRelType::kPrerender.
72     if (!rel_type)
73       rel_type = mojom::blink::PrerenderRelType::kNext;
74   }
75   return rel_type;
76 }
77 
78 }  // namespace
79 
80 class LinkLoader::FinishObserver final : public ResourceFinishObserver {
81   USING_PRE_FINALIZER(FinishObserver, ClearResource);
82 
83  public:
FinishObserver(LinkLoader * loader,Resource * resource)84   FinishObserver(LinkLoader* loader, Resource* resource)
85       : loader_(loader), resource_(resource) {
86     resource_->AddFinishObserver(
87         this, loader_->client_->GetLoadingTaskRunner().get());
88   }
89 
90   // ResourceFinishObserver implementation
NotifyFinished()91   void NotifyFinished() override {
92     if (!resource_)
93       return;
94     loader_->NotifyFinished();
95     ClearResource();
96   }
DebugName() const97   String DebugName() const override {
98     return "LinkLoader::ResourceFinishObserver";
99   }
100 
GetResource()101   Resource* GetResource() { return resource_; }
ClearResource()102   void ClearResource() {
103     if (!resource_)
104       return;
105     resource_->RemoveFinishObserver(this);
106     resource_ = nullptr;
107   }
108 
Trace(Visitor * visitor) const109   void Trace(Visitor* visitor) const override {
110     visitor->Trace(loader_);
111     visitor->Trace(resource_);
112     blink::ResourceFinishObserver::Trace(visitor);
113   }
114 
115  private:
116   Member<LinkLoader> loader_;
117   Member<Resource> resource_;
118 };
119 
LinkLoader(LinkLoaderClient * client,scoped_refptr<base::SingleThreadTaskRunner> task_runner)120 LinkLoader::LinkLoader(LinkLoaderClient* client,
121                        scoped_refptr<base::SingleThreadTaskRunner> task_runner)
122     : client_(client) {
123   DCHECK(client_);
124 }
125 
126 LinkLoader::~LinkLoader() = default;
127 
NotifyFinished()128 void LinkLoader::NotifyFinished() {
129   DCHECK(finish_observer_);
130   Resource* resource = finish_observer_->GetResource();
131   if (resource->ErrorOccurred() ||
132       (resource->IsLinkPreload() &&
133        resource->IntegrityDisposition() ==
134            ResourceIntegrityDisposition::kFailed)) {
135     client_->LinkLoadingErrored();
136   } else {
137     client_->LinkLoaded();
138   }
139 }
140 
141 // https://html.spec.whatwg.org/C/#link-type-modulepreload
NotifyModuleLoadFinished(ModuleScript * module)142 void LinkLoader::NotifyModuleLoadFinished(ModuleScript* module) {
143   // Step 11. "If result is null, fire an event named error at the link element,
144   // and return." [spec text]
145   // Step 12. "Fire an event named load at the link element." [spec text]
146   if (!module)
147     client_->LinkLoadingErrored();
148   else
149     client_->LinkLoaded();
150 }
151 
DidStartPrerender()152 void LinkLoader::DidStartPrerender() {
153   client_->DidStartLinkPrerender();
154 }
155 
DidStopPrerender()156 void LinkLoader::DidStopPrerender() {
157   client_->DidStopLinkPrerender();
158 }
159 
DidSendLoadForPrerender()160 void LinkLoader::DidSendLoadForPrerender() {
161   client_->DidSendLoadForLinkPrerender();
162 }
163 
DidSendDOMContentLoadedForPrerender()164 void LinkLoader::DidSendDOMContentLoadedForPrerender() {
165   client_->DidSendDOMContentLoadedForLinkPrerender();
166 }
167 
GetResourceForTesting()168 Resource* LinkLoader::GetResourceForTesting() {
169   return finish_observer_ ? finish_observer_->GetResource() : nullptr;
170 }
171 
LoadLink(const LinkLoadParameters & params,Document & document)172 bool LinkLoader::LoadLink(const LinkLoadParameters& params,
173                           Document& document) {
174   // If any loading process is in progress, abort it.
175   Abort();
176 
177   if (!client_->ShouldLoadLink())
178     return false;
179 
180   PreloadHelper::DnsPrefetchIfNeeded(params, &document, document.GetFrame(),
181                                      PreloadHelper::kLinkCalledFromMarkup);
182 
183   PreloadHelper::PreconnectIfNeeded(params, &document, document.GetFrame(),
184                                     PreloadHelper::kLinkCalledFromMarkup);
185 
186   Resource* resource = PreloadHelper::PreloadIfNeeded(
187       params, document, NullURL(), PreloadHelper::kLinkCalledFromMarkup,
188       nullptr /* viewport_description */,
189       client_->IsLinkCreatedByParser() ? kParserInserted : kNotParserInserted);
190   if (!resource) {
191     resource = PreloadHelper::PrefetchIfNeeded(params, document);
192   }
193   if (resource)
194     finish_observer_ = MakeGarbageCollected<FinishObserver>(this, resource);
195 
196   PreloadHelper::ModulePreloadIfNeeded(
197       params, document, nullptr /* viewport_description */, this);
198 
199   base::Optional<mojom::blink::PrerenderRelType> prerender_rel_type =
200       PrerenderRelTypeFromRelAttribute(params.rel, document);
201   if (prerender_rel_type) {
202     // The previous prerender should already be aborted by Abort().
203     DCHECK(!prerender_);
204     prerender_ = PrerenderHandle::Create(document, this, params.href,
205                                          *prerender_rel_type);
206   }
207   return true;
208 }
209 
LoadStylesheet(const LinkLoadParameters & params,const AtomicString & local_name,const WTF::TextEncoding & charset,FetchParameters::DeferOption defer_option,Document & document,ResourceClient * link_client)210 void LinkLoader::LoadStylesheet(const LinkLoadParameters& params,
211                                 const AtomicString& local_name,
212                                 const WTF::TextEncoding& charset,
213                                 FetchParameters::DeferOption defer_option,
214                                 Document& document,
215                                 ResourceClient* link_client) {
216   ExecutionContext* context = document.GetExecutionContext();
217   ResourceRequest resource_request(context->CompleteURL(params.href));
218   resource_request.SetReferrerPolicy(params.referrer_policy);
219 
220   mojom::FetchImportanceMode importance_mode =
221       GetFetchImportanceAttributeValue(params.importance);
222   DCHECK(importance_mode == mojom::FetchImportanceMode::kImportanceAuto ||
223          RuntimeEnabledFeatures::PriorityHintsEnabled(context));
224   resource_request.SetFetchImportanceMode(importance_mode);
225 
226   ResourceLoaderOptions options(context->GetCurrentWorld());
227   options.initiator_info.name = local_name;
228   FetchParameters link_fetch_params(std::move(resource_request), options);
229   link_fetch_params.SetCharset(charset);
230 
231   link_fetch_params.SetDefer(defer_option);
232 
233   link_fetch_params.SetContentSecurityPolicyNonce(params.nonce);
234 
235   CrossOriginAttributeValue cross_origin = params.cross_origin;
236   if (cross_origin != kCrossOriginAttributeNotSet) {
237     link_fetch_params.SetCrossOriginAccessControl(context->GetSecurityOrigin(),
238                                                   cross_origin);
239   }
240 
241   String integrity_attr = params.integrity;
242   if (!integrity_attr.IsEmpty()) {
243     IntegrityMetadataSet metadata_set;
244     SubresourceIntegrity::ParseIntegrityAttribute(
245         integrity_attr, SubresourceIntegrityHelper::GetFeatures(context),
246         metadata_set);
247     link_fetch_params.SetIntegrityMetadata(metadata_set);
248     link_fetch_params.MutableResourceRequest().SetFetchIntegrity(
249         integrity_attr);
250   }
251 
252   CSSStyleSheetResource::Fetch(link_fetch_params, context->Fetcher(),
253                                link_client);
254 }
255 
Abort()256 void LinkLoader::Abort() {
257   if (prerender_) {
258     prerender_->Cancel();
259     prerender_.Clear();
260   }
261   if (finish_observer_) {
262     finish_observer_->ClearResource();
263     finish_observer_ = nullptr;
264   }
265 }
266 
Trace(Visitor * visitor) const267 void LinkLoader::Trace(Visitor* visitor) const {
268   visitor->Trace(finish_observer_);
269   visitor->Trace(client_);
270   visitor->Trace(prerender_);
271   SingleModuleClient::Trace(visitor);
272   PrerenderClient::Trace(visitor);
273 }
274 
275 }  // namespace blink
276