| /* |
| * Copyright (C) 2011, 2012 Google Inc. All rights reserved. |
| * Copyright (C) 2013, Intel Corporation |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/loader/threadable_loader.h" |
| |
| #include <memory> |
| |
| #include "services/network/public/cpp/cors/cors_error_status.h" |
| #include "services/network/public/mojom/cors.mojom-blink.h" |
| #include "services/network/public/mojom/fetch_api.mojom-blink.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/core/frame/frame_console.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/loader/threadable_loader_client.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/workers/worker_global_scope.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/heap/self_keep_alive.h" |
| #include "third_party/blink/renderer/platform/loader/cors/cors.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // DetachedClient is a ThreadableLoaderClient for a "detached" |
| // ThreadableLoader. It's for fetch requests with keepalive set, so |
| // it keeps itself alive during loading. |
| class DetachedClient final : public GarbageCollected<DetachedClient>, |
| public ThreadableLoaderClient { |
| public: |
| explicit DetachedClient(ThreadableLoader* loader) |
| : self_keep_alive_(PERSISTENT_FROM_HERE, this), loader_(loader) {} |
| ~DetachedClient() override {} |
| |
| void DidFinishLoading(uint64_t identifier) override { |
| self_keep_alive_.Clear(); |
| } |
| void DidFail(const ResourceError&) override { self_keep_alive_.Clear(); } |
| void DidFailRedirectCheck() override { self_keep_alive_.Clear(); } |
| void Trace(Visitor* visitor) const override { |
| visitor->Trace(loader_); |
| ThreadableLoaderClient::Trace(visitor); |
| } |
| |
| private: |
| SelfKeepAlive<DetachedClient> self_keep_alive_; |
| // Keep it alive. |
| const Member<ThreadableLoader> loader_; |
| }; |
| |
| } // namespace |
| |
| ThreadableLoader::ThreadableLoader( |
| ExecutionContext& execution_context, |
| ThreadableLoaderClient* client, |
| const ResourceLoaderOptions& resource_loader_options, |
| ResourceFetcher* resource_fetcher) |
| : client_(client), |
| execution_context_(execution_context), |
| resource_fetcher_(resource_fetcher), |
| resource_loader_options_(resource_loader_options), |
| request_mode_(network::mojom::RequestMode::kSameOrigin), |
| timeout_timer_(execution_context_->GetTaskRunner(TaskType::kNetworking), |
| this, |
| &ThreadableLoader::DidTimeout) { |
| DCHECK(client); |
| if (!resource_fetcher_) { |
| if (auto* scope = DynamicTo<WorkerGlobalScope>(*execution_context_)) |
| scope->EnsureFetcher(); |
| resource_fetcher_ = execution_context_->Fetcher(); |
| } |
| } |
| |
| void ThreadableLoader::Start(ResourceRequest request) { |
| const auto request_context = request.GetRequestContext(); |
| if (request.GetMode() == network::mojom::RequestMode::kNoCors) { |
| SECURITY_CHECK(cors::IsNoCorsAllowedContext(request_context)); |
| } |
| |
| // Setting an outgoing referer is only supported in the async code path. |
| DCHECK(resource_loader_options_.synchronous_policy == |
| kRequestAsynchronously || |
| request.ReferrerString() == Referrer::ClientReferrerString()); |
| |
| // kPreventPreflight can be used only when the CORS is enabled. |
| DCHECK(request.CorsPreflightPolicy() == |
| network::mojom::CorsPreflightPolicy::kConsiderPreflight || |
| cors::IsCorsEnabledRequestMode(request.GetMode())); |
| |
| request_started_ = base::TimeTicks::Now(); |
| request_mode_ = request.GetMode(); |
| |
| // Set the service worker mode to none if "bypass for network" in DevTools is |
| // enabled. |
| bool should_bypass_service_worker = false; |
| probe::ShouldBypassServiceWorker(execution_context_, |
| &should_bypass_service_worker); |
| if (should_bypass_service_worker) |
| request.SetSkipServiceWorker(true); |
| |
| const bool async = |
| resource_loader_options_.synchronous_policy == kRequestAsynchronously; |
| if (!timeout_.is_zero()) { |
| if (!async) { |
| request.SetTimeoutInterval(timeout_); |
| } else if (!timeout_timer_.IsActive()) { |
| timeout_timer_.StartOneShot(timeout_, FROM_HERE); |
| } |
| } |
| |
| FetchParameters params(std::move(request), resource_loader_options_); |
| DCHECK(!GetResource()); |
| |
| checker_.WillAddClient(); |
| if (request_context == mojom::blink::RequestContextType::VIDEO || |
| request_context == mojom::blink::RequestContextType::AUDIO) { |
| DCHECK(async); |
| RawResource::FetchMedia(params, resource_fetcher_, this); |
| } else if (request_context == mojom::blink::RequestContextType::MANIFEST) { |
| DCHECK(async); |
| RawResource::FetchManifest(params, resource_fetcher_, this); |
| } else if (async) { |
| RawResource::Fetch(params, resource_fetcher_, this); |
| } else { |
| RawResource::FetchSynchronously(params, resource_fetcher_, this); |
| } |
| } |
| |
| ThreadableLoader::~ThreadableLoader() {} |
| |
| void ThreadableLoader::SetTimeout(const base::TimeDelta& timeout) { |
| timeout_ = timeout; |
| |
| // |request_started_| <= base::TimeTicks() indicates loading is either not yet |
| // started or is already finished, and thus we don't need to do anything with |
| // timeout_timer_. |
| if (request_started_ <= base::TimeTicks()) { |
| DCHECK(!timeout_timer_.IsActive()); |
| return; |
| } |
| DCHECK_EQ(kRequestAsynchronously, |
| resource_loader_options_.synchronous_policy); |
| timeout_timer_.Stop(); |
| |
| // At the time of this method's implementation, it is only ever called for an |
| // inflight request by XMLHttpRequest. |
| // |
| // The XHR request says to resolve the time relative to when the request |
| // was initially sent, however other uses of this method may need to |
| // behave differently, in which case this should be re-arranged somehow. |
| if (!timeout_.is_zero()) { |
| base::TimeDelta elapsed_time = base::TimeTicks::Now() - request_started_; |
| base::TimeDelta resolved_time = |
| std::max(timeout_ - elapsed_time, base::TimeDelta()); |
| timeout_timer_.StartOneShot(resolved_time, FROM_HERE); |
| } |
| } |
| |
| void ThreadableLoader::Cancel() { |
| // Cancel can re-enter, and therefore |resource()| might be null here as a |
| // result. |
| if (!client_ || !GetResource()) { |
| Clear(); |
| return; |
| } |
| |
| DispatchDidFail(ResourceError::CancelledError(GetResource()->Url())); |
| } |
| |
| void ThreadableLoader::Detach() { |
| Resource* resource = GetResource(); |
| if (!resource) |
| return; |
| client_ = MakeGarbageCollected<DetachedClient>(this); |
| } |
| |
| void ThreadableLoader::SetDefersLoading(bool value) { |
| if (GetResource() && GetResource()->Loader()) { |
| GetResource()->Loader()->SetDefersLoading( |
| value ? WebURLLoader::DeferType::kDeferred |
| : WebURLLoader::DeferType::kNotDeferred); |
| } |
| } |
| |
| void ThreadableLoader::Clear() { |
| client_ = nullptr; |
| timeout_timer_.Stop(); |
| request_started_ = base::TimeTicks(); |
| if (GetResource()) |
| checker_.WillRemoveClient(); |
| ClearResource(); |
| } |
| |
| bool ThreadableLoader::RedirectReceived( |
| Resource* resource, |
| const ResourceRequest& new_request, |
| const ResourceResponse& redirect_response) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| checker_.RedirectReceived(); |
| |
| return client_->WillFollowRedirect(new_request.Url(), redirect_response); |
| } |
| |
| void ThreadableLoader::RedirectBlocked() { |
| DCHECK(client_); |
| checker_.RedirectBlocked(); |
| |
| // Tells the client that a redirect was received but not followed (for an |
| // unknown reason). |
| ThreadableLoaderClient* client = client_; |
| Clear(); |
| client->DidFailRedirectCheck(); |
| } |
| |
| void ThreadableLoader::DataSent(Resource* resource, |
| uint64_t bytes_sent, |
| uint64_t total_bytes_to_be_sent) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| DCHECK_EQ(kRequestAsynchronously, |
| resource_loader_options_.synchronous_policy); |
| |
| checker_.DataSent(); |
| client_->DidSendData(bytes_sent, total_bytes_to_be_sent); |
| } |
| |
| void ThreadableLoader::DataDownloaded(Resource* resource, |
| uint64_t data_length) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| |
| checker_.DataDownloaded(); |
| client_->DidDownloadData(data_length); |
| } |
| |
| void ThreadableLoader::DidDownloadToBlob(Resource* resource, |
| scoped_refptr<BlobDataHandle> blob) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| |
| checker_.DidDownloadToBlob(); |
| client_->DidDownloadToBlob(std::move(blob)); |
| } |
| |
| void ThreadableLoader::ResponseReceived(Resource* resource, |
| const ResourceResponse& response) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| DCHECK(!response.WasFallbackRequiredByServiceWorker()); |
| |
| checker_.ResponseReceived(); |
| |
| // Now the following check is not needed as the service worker added their own |
| // checks and today memory cache and preload matching rules are more strict. |
| // TODO(crbug.com/1053866): Remove the check. |
| if (response.WasFetchedViaServiceWorker() && |
| request_mode_ != network::mojom::RequestMode::kNoCors && |
| response.GetType() == network::mojom::FetchResponseType::kOpaque) { |
| DispatchDidFail(ResourceError( |
| response.CurrentRequestUrl(), |
| network::CorsErrorStatus(network::mojom::CorsError::kInvalidResponse))); |
| return; |
| } |
| client_->DidReceiveResponse(resource->InspectorId(), response); |
| } |
| |
| void ThreadableLoader::ResponseBodyReceived(Resource* resource, |
| BytesConsumer& body) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| |
| checker_.ResponseBodyReceived(); |
| client_->DidStartLoadingResponseBody(body); |
| } |
| |
| void ThreadableLoader::CachedMetadataReceived( |
| Resource* resource, |
| mojo_base::BigBuffer cached_metadata) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| |
| checker_.SetSerializedCachedMetadata(); |
| |
| client_->DidReceiveCachedMetadata(std::move(cached_metadata)); |
| } |
| |
| void ThreadableLoader::DataReceived(Resource* resource, |
| const char* data, |
| size_t data_length) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| |
| checker_.DataReceived(); |
| |
| // TODO(junov): Fix the ThreadableLoader ecosystem to use size_t. Until then, |
| // we use safeCast to trap potential overflows. |
| client_->DidReceiveData(data, SafeCast<unsigned>(data_length)); |
| } |
| |
| void ThreadableLoader::NotifyFinished(Resource* resource) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, GetResource()); |
| |
| checker_.NotifyFinished(resource); |
| |
| if (resource->ErrorOccurred()) { |
| DispatchDidFail(resource->GetResourceError()); |
| return; |
| } |
| |
| ThreadableLoaderClient* client = client_; |
| // Protect the resource in |DidFinishLoading| in order not to release the |
| // downloaded file. |
| Persistent<Resource> protect = GetResource(); |
| Clear(); |
| client->DidFinishLoading(resource->InspectorId()); |
| } |
| |
| void ThreadableLoader::DidTimeout(TimerBase* timer) { |
| DCHECK_EQ(kRequestAsynchronously, |
| resource_loader_options_.synchronous_policy); |
| DCHECK_EQ(timer, &timeout_timer_); |
| // ClearResource() may be called in Clear() and some other places. Clear() |
| // calls Stop() on |timeout_|. In the other places, the resource is set |
| // again. If the creation fails, Clear() is called. So, here, GetResource() is |
| // always non-nullptr. |
| DCHECK(GetResource()); |
| // When |client_| is set to nullptr only in Clear() where |timeout_| |
| // is stopped. So, |client_| is always non-nullptr here. |
| DCHECK(client_); |
| |
| DispatchDidFail(ResourceError::TimeoutError(GetResource()->Url())); |
| } |
| |
| void ThreadableLoader::DispatchDidFail(const ResourceError& error) { |
| Resource* resource = GetResource(); |
| if (resource) |
| resource->SetResponseType(network::mojom::FetchResponseType::kError); |
| ThreadableLoaderClient* client = client_; |
| Clear(); |
| client->DidFail(error); |
| } |
| |
| void ThreadableLoader::Trace(Visitor* visitor) const { |
| visitor->Trace(execution_context_); |
| visitor->Trace(client_); |
| visitor->Trace(resource_fetcher_); |
| visitor->Trace(timeout_timer_); |
| RawResourceClient::Trace(visitor); |
| } |
| |
| } // namespace blink |