| /* |
| Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) |
| Copyright (C) 2001 Dirk Mueller (mueller@kde.org) |
| Copyright (C) 2002 Waldo Bastian (bastian@kde.org) |
| Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) |
| Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Library General Public |
| License as published by the Free Software Foundation; either |
| version 2 of the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Library General Public License for more details. |
| |
| You should have received a copy of the GNU Library General Public License |
| along with this library; see the file COPYING.LIB. If not, write to |
| the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| Boston, MA 02110-1301, USA. |
| |
| This class provides all functionality needed for loading images, style |
| sheets and html pages from the web. It has a memory cache for these objects. |
| */ |
| |
| #include "third_party/blink/renderer/core/loader/resource/script_resource.h" |
| |
| #include <utility> |
| |
| #include "services/network/public/mojom/request_context_frame_type.mojom-blink.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/web_memory_allocator_dump.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/integrity_metadata.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_client_walker.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/script_cached_metadata_handler.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" |
| #include "third_party/blink/renderer/platform/loader/subresource_integrity.h" |
| #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" |
| #include "third_party/blink/renderer/platform/shared_buffer.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // Returns true if the given request context is a script-like destination |
| // defined in the Fetch spec: |
| // https://fetch.spec.whatwg.org/#request-destination-script-like |
| bool IsRequestContextSupported(mojom::RequestContextType request_context) { |
| // TODO(nhiroki): Support |kRequestContextSharedWorker| for module loading for |
| // shared workers (https://crbug.com/824646). |
| // TODO(nhiroki): Support "audioworklet" and "paintworklet" destinations. |
| switch (request_context) { |
| case mojom::RequestContextType::SCRIPT: |
| case mojom::RequestContextType::WORKER: |
| case mojom::RequestContextType::SERVICE_WORKER: |
| return true; |
| default: |
| break; |
| } |
| NOTREACHED() << "Incompatible request context type: " << request_context; |
| return false; |
| } |
| |
| } // namespace |
| |
| ScriptResource* ScriptResource::Fetch(FetchParameters& params, |
| ResourceFetcher* fetcher, |
| ResourceClient* client, |
| StreamingAllowed streaming_allowed) { |
| DCHECK_EQ(params.GetResourceRequest().GetFrameType(), |
| network::mojom::RequestContextFrameType::kNone); |
| DCHECK(IsRequestContextSupported( |
| params.GetResourceRequest().GetRequestContext())); |
| ScriptResource* resource = ToScriptResource( |
| fetcher->RequestResource(params, ScriptResourceFactory(), client)); |
| |
| if (streaming_allowed != kAllowStreaming) { |
| // Advance the |streaming_state_| to kStreamingNotAllowed by calling |
| // SetClientIsWaitingForFinished unless it is explicitly allowed.' |
| // |
| // Do this in a task rather than directly to make sure that we don't call |
| // the finished callbacks of other clients synchronously. |
| |
| // TODO(leszeks): Previous behaviour, without script streaming, was to |
| // synchronously notify the given client, with the assumption that other |
| // clients were already finished. If this behaviour becomes necessary, we |
| // would have to either check that streaming wasn't started (if that would |
| // be a logic error), or cancel any existing streaming. |
| fetcher->Context().GetLoadingTaskRunner()->PostTask( |
| FROM_HERE, WTF::Bind(&ScriptResource::SetClientIsWaitingForFinished, |
| WrapWeakPersistent(resource))); |
| } |
| |
| return resource; |
| } |
| |
| ScriptResource::ScriptResource( |
| const ResourceRequest& resource_request, |
| const ResourceLoaderOptions& options, |
| const TextResourceDecoderOptions& decoder_options) |
| : TextResource(resource_request, |
| ResourceType::kScript, |
| options, |
| decoder_options) {} |
| |
| ScriptResource::~ScriptResource() = default; |
| |
| void ScriptResource::Trace(blink::Visitor* visitor) { |
| visitor->Trace(streamer_); |
| TextResource::Trace(visitor); |
| } |
| |
| void ScriptResource::OnMemoryDump(WebMemoryDumpLevelOfDetail level_of_detail, |
| WebProcessMemoryDump* memory_dump) const { |
| Resource::OnMemoryDump(level_of_detail, memory_dump); |
| const String name = GetMemoryDumpName() + "/decoded_script"; |
| auto* dump = memory_dump->CreateMemoryAllocatorDump(name); |
| dump->AddScalar("size", "bytes", source_text_.CharactersSizeInBytes()); |
| memory_dump->AddSuballocation( |
| dump->Guid(), String(WTF::Partitions::kAllocatedObjectPoolName)); |
| } |
| |
| const ParkableString& ScriptResource::SourceText() { |
| CHECK(IsFinishedInternal()); |
| |
| if (source_text_.IsNull() && Data()) { |
| String source_text = DecodedText(); |
| ClearData(); |
| SetDecodedSize(source_text.CharactersSizeInBytes()); |
| source_text_ = ParkableString(source_text.ReleaseImpl()); |
| } |
| |
| return source_text_; |
| } |
| |
| SingleCachedMetadataHandler* ScriptResource::CacheHandler() { |
| return static_cast<SingleCachedMetadataHandler*>(Resource::CacheHandler()); |
| } |
| |
| CachedMetadataHandler* ScriptResource::CreateCachedMetadataHandler( |
| std::unique_ptr<CachedMetadataSender> send_callback) { |
| return new ScriptCachedMetadataHandler(Encoding(), std::move(send_callback)); |
| } |
| |
| void ScriptResource::SetSerializedCachedMetadata(const char* data, |
| size_t size) { |
| Resource::SetSerializedCachedMetadata(data, size); |
| ScriptCachedMetadataHandler* cache_handler = |
| static_cast<ScriptCachedMetadataHandler*>(Resource::CacheHandler()); |
| if (cache_handler) { |
| cache_handler->SetSerializedCachedMetadata(data, size); |
| } |
| } |
| |
| void ScriptResource::DestroyDecodedDataForFailedRevalidation() { |
| source_text_ = ParkableString(); |
| // Make sure there's no streaming. |
| DCHECK(!streamer_); |
| DCHECK_EQ(streaming_state_, StreamingState::kStreamingNotAllowed); |
| SetDecodedSize(0); |
| } |
| |
| void ScriptResource::SetRevalidatingRequest(const ResourceRequest& request) { |
| CHECK_EQ(streaming_state_, StreamingState::kFinishedNotificationSent); |
| if (streamer_) { |
| CHECK(streamer_->IsStreamingFinished()); |
| streamer_ = nullptr; |
| } |
| // Revalidation requests don't actually load the current Resource, so disable |
| // streaming. |
| not_streaming_reason_ = ScriptStreamer::kRevalidate; |
| streaming_state_ = StreamingState::kStreamingNotAllowed; |
| CheckStreamingState(); |
| |
| TextResource::SetRevalidatingRequest(request); |
| } |
| |
| AccessControlStatus ScriptResource::CalculateAccessControlStatus() const { |
| DCHECK_NE(GetResponse().GetType(), network::mojom::FetchResponseType::kError); |
| return GetResponse().IsCORSSameOrigin() ? kSharableCrossOrigin |
| : kOpaqueResource; |
| } |
| |
| bool ScriptResource::CanUseCacheValidator() const { |
| // Do not revalidate until ClassicPendingScript is removed, i.e. the script |
| // content is retrieved in ScriptLoader::ExecuteScriptBlock(). |
| // crbug.com/692856 |
| if (HasClientsOrObservers()) |
| return false; |
| |
| // Do not revalidate until streaming is complete. |
| if (!IsFinishedInternal()) |
| return false; |
| |
| return Resource::CanUseCacheValidator(); |
| } |
| |
| void ScriptResource::NotifyDataReceived(const char* data, size_t size) { |
| CheckStreamingState(); |
| if (streamer_) { |
| DCHECK_EQ(streaming_state_, StreamingState::kStreaming); |
| streamer_->NotifyAppendData(); |
| } |
| TextResource::NotifyDataReceived(data, size); |
| } |
| |
| void ScriptResource::NotifyFinished() { |
| DCHECK(IsLoaded()); |
| switch (streaming_state_) { |
| case StreamingState::kCanStartStreaming: |
| // Do nothing, expect either a StartStreaming() call to transition us to |
| // kStreaming, or an SetClientIsWaitingForFinished() call to transition us |
| // into kStreamingNotAllowed. These will then transition again since |
| // IsLoaded will be true. |
| break; |
| case StreamingState::kStreaming: |
| AdvanceStreamingState(StreamingState::kWaitingForStreamingToEnd); |
| DCHECK(streamer_); |
| streamer_->NotifyFinished(); |
| // Don't call the base NotifyFinished until streaming finishes too (which |
| // might happen immediately in the above ScriptStreamer::NotifyFinished |
| // call) |
| break; |
| case StreamingState::kStreamingNotAllowed: |
| AdvanceStreamingState(StreamingState::kFinishedNotificationSent); |
| TextResource::NotifyFinished(); |
| break; |
| case StreamingState::kWaitingForStreamingToEnd: |
| case StreamingState::kFinishedNotificationSent: |
| // Not possible. |
| CHECK(false); |
| break; |
| } |
| } |
| |
| bool ScriptResource::IsFinishedInternal() const { |
| CheckStreamingState(); |
| return streaming_state_ == StreamingState::kFinishedNotificationSent; |
| } |
| |
| void ScriptResource::StreamingFinished() { |
| CHECK(streamer_); |
| CHECK_EQ(streaming_state_, StreamingState::kWaitingForStreamingToEnd); |
| AdvanceStreamingState(StreamingState::kFinishedNotificationSent); |
| TextResource::NotifyFinished(); |
| } |
| |
| void ScriptResource::StartStreaming( |
| scoped_refptr<base::SingleThreadTaskRunner> loading_task_runner) { |
| CheckStreamingState(); |
| |
| if (streamer_) { |
| return; |
| } |
| |
| if (streaming_state_ != StreamingState::kCanStartStreaming) { |
| return; |
| } |
| |
| // Don't bother streaming if there was an error, it won't work anyway. |
| if (ErrorOccurred()) { |
| return; |
| } |
| |
| CHECK(!IsCacheValidator()); |
| |
| streamer_ = |
| ScriptStreamer::Create(this, loading_task_runner, ¬_streaming_reason_); |
| if (streamer_) { |
| AdvanceStreamingState(StreamingState::kStreaming); |
| |
| // If there is any data already, send it to the streamer. |
| if (Data()) { |
| // Note that we don't need to iterate through the segments of the data, as |
| // the streamer will do that itself. |
| CHECK_GT(Data()->size(), 0u); |
| streamer_->NotifyAppendData(); |
| } |
| // If the we're is already loaded, notify the streamer about that too. |
| if (IsLoaded()) { |
| AdvanceStreamingState(StreamingState::kWaitingForStreamingToEnd); |
| |
| // Do this in a task rather than directly to make sure that we don't call |
| // the finished callback in the same stack as starting streaming -- this |
| // can cause issues with the client expecting to be not finished when |
| // starting streaming (e.g. ClassicPendingScript::IsReady == false), but |
| // ending up finished by the end of this method. |
| loading_task_runner->PostTask(FROM_HERE, |
| WTF::Bind(&ScriptStreamer::NotifyFinished, |
| WrapPersistent(streamer_.Get()))); |
| } |
| } |
| |
| CheckStreamingState(); |
| return; |
| } |
| |
| void ScriptResource::SetClientIsWaitingForFinished() { |
| // No-op if streaming already started or finished. |
| CheckStreamingState(); |
| if (streaming_state_ != StreamingState::kCanStartStreaming) |
| return; |
| |
| AdvanceStreamingState(StreamingState::kStreamingNotAllowed); |
| not_streaming_reason_ = ScriptStreamer::kStreamingDisabled; |
| // Trigger the finished notification if needed. |
| if (IsLoaded()) { |
| AdvanceStreamingState(StreamingState::kFinishedNotificationSent); |
| TextResource::NotifyFinished(); |
| } |
| } |
| |
| ScriptStreamer* ScriptResource::TakeStreamer() { |
| CHECK(IsFinishedInternal()); |
| if (!streamer_) |
| return nullptr; |
| |
| ScriptStreamer* streamer = streamer_; |
| streamer_ = nullptr; |
| not_streaming_reason_ = ScriptStreamer::kSecondScriptResourceUse; |
| return streamer; |
| } |
| |
| void ScriptResource::AdvanceStreamingState(StreamingState new_state) { |
| switch (streaming_state_) { |
| case StreamingState::kCanStartStreaming: |
| CHECK(new_state == StreamingState::kStreaming || |
| new_state == StreamingState::kStreamingNotAllowed); |
| break; |
| case StreamingState::kStreaming: |
| CHECK(streamer_); |
| CHECK_EQ(new_state, StreamingState::kWaitingForStreamingToEnd); |
| break; |
| case StreamingState::kWaitingForStreamingToEnd: |
| CHECK(streamer_); |
| CHECK_EQ(new_state, StreamingState::kFinishedNotificationSent); |
| break; |
| case StreamingState::kStreamingNotAllowed: |
| CHECK_EQ(new_state, StreamingState::kFinishedNotificationSent); |
| break; |
| case StreamingState::kFinishedNotificationSent: |
| CHECK(false); |
| break; |
| } |
| |
| streaming_state_ = new_state; |
| CheckStreamingState(); |
| } |
| |
| void ScriptResource::CheckStreamingState() const { |
| // TODO(leszeks): Eventually convert these CHECKs into DCHECKs once the logic |
| // is a bit more baked in. |
| switch (streaming_state_) { |
| case StreamingState::kCanStartStreaming: |
| CHECK(!streamer_); |
| break; |
| case StreamingState::kStreaming: |
| CHECK(streamer_); |
| CHECK(!streamer_->IsFinished()); |
| // kStreaming can be entered both when loading (if streaming is started |
| // before load completes) or when loaded (if streaming is started after |
| // load completes). In the latter case, the state will almost immediately |
| // advance to kWaitingForStreamingToEnd. |
| CHECK(IsLoaded() || IsLoading()); |
| break; |
| case StreamingState::kWaitingForStreamingToEnd: |
| CHECK(streamer_); |
| CHECK(!streamer_->IsFinished()); |
| CHECK(IsLoaded()); |
| break; |
| case StreamingState::kStreamingNotAllowed: |
| CHECK(!streamer_); |
| // TODO(leszeks): We could CHECK(!IsLoaded()) if not for the immediate |
| // kCanStartStreaming -> kStreamingNotAllowed -> kFinishedNotificationSent |
| // transition in SetClientIsWaitingForFinished when IsLoaded. |
| break; |
| case StreamingState::kFinishedNotificationSent: |
| CHECK(!streamer_ || streamer_->IsFinished()); |
| CHECK(IsLoaded()); |
| break; |
| } |
| } |
| |
| } // namespace blink |