blob: e05d63b7f3e886c2d310c2bf8e0b5bee687a1e0a [file] [log] [blame]
// Copyright 2017 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/workers/dedicated_worker.h"
#include <memory>
#include "services/network/public/mojom/fetch_api.mojom-blink.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "services/service_manager/public/mojom/interface_provider.mojom-blink.h"
#include "third_party/blink/public/mojom/script/script_type.mojom-blink.h"
#include "third_party/blink/public/mojom/worker/dedicated_worker_factory.mojom-blink.h"
#include "third_party/blink/public/platform/web_content_settings_client.h"
#include "third_party/blink/public/platform/web_layer_tree_view.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/post_message_helper.h"
#include "third_party/blink/renderer/core/core_initializer.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/events/message_event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.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/frame/use_counter.h"
#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h"
#include "third_party/blink/renderer/core/loader/appcache/application_cache_host.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/loader/worker_fetch_context.h"
#include "third_party/blink/renderer/core/messaging/post_message_options.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/script/script.h"
#include "third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.h"
#include "third_party/blink/renderer/core/workers/worker_classic_script_loader.h"
#include "third_party/blink/renderer/core/workers/worker_clients.h"
#include "third_party/blink/renderer/core/workers/worker_content_settings_client.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
namespace blink {
namespace {
service_manager::mojom::blink::InterfaceProviderPtrInfo
ConnectToWorkerInterfaceProvider(
ExecutionContext* execution_context,
scoped_refptr<const SecurityOrigin> script_origin) {
mojom::blink::DedicatedWorkerFactoryPtr worker_factory;
execution_context->GetInterfaceProvider()->GetInterface(&worker_factory);
service_manager::mojom::blink::InterfaceProviderPtrInfo
interface_provider_ptr;
worker_factory->CreateDedicatedWorker(
script_origin, mojo::MakeRequest(&interface_provider_ptr));
return interface_provider_ptr;
}
// Indicates whether the origin of worker top-level script's request URL is
// same-origin as the parent execution context's origin or not.
// This is used for UMA and thus the existing values should not be changed.
enum class WorkerTopLevelScriptOriginType {
kSameOrigin = 0,
kDataUrl = 1,
// Cross-origin worker request URL (e.g. https://example.com/worker.js)
// from an chrome-extension: page.
kCrossOriginFromExtension = 2,
// Cross-origin worker request URL from a non chrome-extension: page.
// There are no known cases for this, and we investigate whether there are
// really no occurrences.
kCrossOriginOthers = 3,
kMaxValue = kCrossOriginOthers
};
void CountTopLevelScriptRequestUrlOriginType(
const SecurityOrigin& context_origin,
const KURL& request_url) {
WorkerTopLevelScriptOriginType origin_type;
if (request_url.ProtocolIsData()) {
origin_type = WorkerTopLevelScriptOriginType::kDataUrl;
} else if (context_origin.IsSameSchemeHostPort(
SecurityOrigin::Create(request_url).get())) {
origin_type = WorkerTopLevelScriptOriginType::kSameOrigin;
} else if (context_origin.Protocol() == "chrome-extension") {
// Note: using "chrome-extension" scheme check here is a layering
// violation. Do not use this except for UMA purpose.
origin_type = WorkerTopLevelScriptOriginType::kCrossOriginFromExtension;
} else {
origin_type = WorkerTopLevelScriptOriginType::kCrossOriginOthers;
}
UMA_HISTOGRAM_ENUMERATION(
"Worker.TopLevelScript.OriginType.RequestUrl.DedicatedWorker",
origin_type);
}
} // namespace
DedicatedWorker* DedicatedWorker::Create(ExecutionContext* context,
const String& url,
const WorkerOptions* options,
ExceptionState& exception_state) {
DCHECK(context->IsContextThread());
UseCounter::Count(context, WebFeature::kWorkerStart);
if (context->IsContextDestroyed()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError,
"The context provided is invalid.");
return nullptr;
}
KURL script_request_url = ResolveURL(context, url, exception_state,
mojom::RequestContextType::SCRIPT);
if (!script_request_url.IsValid()) {
// Don't throw an exception here because it's already thrown in
// ResolveURL().
return nullptr;
}
// TODO(nhiroki): Remove this flag check once module loading for
// DedicatedWorker is enabled by default (https://crbug.com/680046).
if (options->type() == "module" &&
!RuntimeEnabledFeatures::ModuleDedicatedWorkerEnabled()) {
exception_state.ThrowTypeError(
"Module scripts are not supported on DedicatedWorker yet. You can try "
"the feature with '--enable-experimental-web-platform-features' flag "
"(see https://crbug.com/680046)");
return nullptr;
}
if (context->IsWorkerGlobalScope())
UseCounter::Count(context, WebFeature::kNestedDedicatedWorker);
DedicatedWorker* worker = MakeGarbageCollected<DedicatedWorker>(
context, script_request_url, options);
worker->Start();
return worker;
}
DedicatedWorker::DedicatedWorker(ExecutionContext* context,
const KURL& script_request_url,
const WorkerOptions* options)
: AbstractWorker(context),
script_request_url_(script_request_url),
options_(options),
context_proxy_(
MakeGarbageCollected<DedicatedWorkerMessagingProxy>(context, this)) {
DCHECK(context->IsContextThread());
DCHECK(script_request_url_.IsValid());
DCHECK(context_proxy_);
}
DedicatedWorker::~DedicatedWorker() {
DCHECK(!GetExecutionContext() || GetExecutionContext()->IsContextThread());
context_proxy_->ParentObjectDestroyed();
}
void DedicatedWorker::postMessage(ScriptState* script_state,
const ScriptValue& message,
Vector<ScriptValue>& transfer,
ExceptionState& exception_state) {
PostMessageOptions* options = PostMessageOptions::Create();
if (!transfer.IsEmpty())
options->setTransfer(transfer);
postMessage(script_state, message, options, exception_state);
}
void DedicatedWorker::postMessage(ScriptState* script_state,
const ScriptValue& message,
const PostMessageOptions* options,
ExceptionState& exception_state) {
DCHECK(GetExecutionContext()->IsContextThread());
BlinkTransferableMessage transferable_message;
Transferables transferables;
scoped_refptr<SerializedScriptValue> serialized_message =
PostMessageHelper::SerializeMessageByMove(script_state->GetIsolate(),
message, options, transferables,
exception_state);
if (exception_state.HadException())
return;
DCHECK(serialized_message);
transferable_message.message = serialized_message;
// Disentangle the port in preparation for sending it to the remote context.
transferable_message.ports = MessagePort::DisentanglePorts(
ExecutionContext::From(script_state), transferables.message_ports,
exception_state);
if (exception_state.HadException())
return;
transferable_message.user_activation =
PostMessageHelper::CreateUserActivationSnapshot(GetExecutionContext(),
options);
transferable_message.sender_stack_trace_id =
ThreadDebugger::From(script_state->GetIsolate())
->StoreCurrentStackTrace("Worker.postMessage");
context_proxy_->PostMessageToWorkerGlobalScope(
std::move(transferable_message));
}
// https://html.spec.whatwg.org/multipage/workers.html#worker-processing-model
void DedicatedWorker::Start() {
DCHECK(GetExecutionContext()->IsContextThread());
v8_inspector::V8StackTraceId stack_id =
ThreadDebugger::From(GetExecutionContext()->GetIsolate())
->StoreCurrentStackTrace("Worker Created");
if (auto* scope = DynamicTo<WorkerGlobalScope>(*GetExecutionContext()))
scope->EnsureFetcher();
if (RuntimeEnabledFeatures::OffMainThreadWorkerScriptFetchEnabled() ||
options_->type() == "module") {
// Specify empty source code here because scripts will be fetched on the
// worker thread.
auto* outside_settings_object =
MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
*GetExecutionContext()
->Fetcher()
->Context()
.GetFetchClientSettingsObject());
context_proxy_->StartWorkerGlobalScope(
CreateGlobalScopeCreationParams(
script_request_url_, network::mojom::ReferrerPolicy::kDefault),
options_, script_request_url_, outside_settings_object, stack_id,
String() /* source_code */);
return;
}
if (options_->type() == "classic") {
// Legacy code path (to be deprecated, see https://crbug.com/835717):
// A worker thread will start after scripts are fetched on the current
// thread.
classic_script_loader_ = MakeGarbageCollected<WorkerClassicScriptLoader>();
classic_script_loader_->LoadTopLevelScriptAsynchronously(
*GetExecutionContext(), GetExecutionContext()->Fetcher(),
script_request_url_, mojom::RequestContextType::WORKER,
network::mojom::FetchRequestMode::kSameOrigin,
network::mojom::FetchCredentialsMode::kSameOrigin,
GetExecutionContext()->GetSecurityContext().AddressSpace(),
WTF::Bind(&DedicatedWorker::OnResponse, WrapPersistent(this)),
WTF::Bind(&DedicatedWorker::OnFinished, WrapPersistent(this),
stack_id));
return;
}
NOTREACHED() << "Invalid type: " << options_->type();
}
void DedicatedWorker::terminate() {
DCHECK(!GetExecutionContext() || GetExecutionContext()->IsContextThread());
context_proxy_->TerminateGlobalScope();
}
BeginFrameProviderParams DedicatedWorker::CreateBeginFrameProviderParams() {
DCHECK(GetExecutionContext()->IsContextThread());
// If we don't have a frame or we are not in Document, some of the SinkIds
// won't be initialized. If that's the case, the Worker will initialize it by
// itself later.
BeginFrameProviderParams begin_frame_provider_params;
if (auto* document = DynamicTo<Document>(GetExecutionContext())) {
LocalFrame* frame = document->GetFrame();
WebLayerTreeView* layer_tree_view = nullptr;
if (frame && frame->GetPage()) {
layer_tree_view =
frame->GetPage()->GetChromeClient().GetWebLayerTreeView(frame);
if (layer_tree_view) {
begin_frame_provider_params.parent_frame_sink_id =
layer_tree_view->GetFrameSinkId();
}
}
begin_frame_provider_params.frame_sink_id =
Platform::Current()->GenerateFrameSinkId();
}
return begin_frame_provider_params;
}
void DedicatedWorker::ContextDestroyed(ExecutionContext*) {
DCHECK(GetExecutionContext()->IsContextThread());
if (classic_script_loader_)
classic_script_loader_->Cancel();
terminate();
}
bool DedicatedWorker::HasPendingActivity() const {
DCHECK(!GetExecutionContext() || GetExecutionContext()->IsContextThread());
// The worker context does not exist while loading, so we must ensure that the
// worker object is not collected, nor are its event listeners.
return context_proxy_->HasPendingActivity() || classic_script_loader_;
}
void DedicatedWorker::DispatchErrorEventForScriptFetchFailure() {
DCHECK(!GetExecutionContext() || GetExecutionContext()->IsContextThread());
// TODO(nhiroki): Add a console error message.
DispatchEvent(*Event::CreateCancelable(event_type_names::kError));
}
const String DedicatedWorker::Name() const {
return options_->name();
}
WorkerClients* DedicatedWorker::CreateWorkerClients() {
WorkerClients* worker_clients = WorkerClients::Create();
CoreInitializer::GetInstance().ProvideLocalFileSystemToWorker(
*worker_clients);
CoreInitializer::GetInstance().ProvideIndexedDBClientToWorker(
*worker_clients);
std::unique_ptr<WebContentSettingsClient> client;
if (auto* document = DynamicTo<Document>(GetExecutionContext())) {
LocalFrame* frame = document->GetFrame();
client = frame->Client()->CreateWorkerContentSettingsClient();
} else if (GetExecutionContext()->IsWorkerGlobalScope()) {
WebContentSettingsClient* web_worker_content_settings_client =
WorkerContentSettingsClient::From(*GetExecutionContext())
->GetWebContentSettingsClient();
if (web_worker_content_settings_client)
client = web_worker_content_settings_client->Clone();
}
ProvideContentSettingsClientToWorker(worker_clients, std::move(client));
return worker_clients;
}
void DedicatedWorker::OnResponse() {
DCHECK(GetExecutionContext()->IsContextThread());
probe::didReceiveScriptResponse(GetExecutionContext(),
classic_script_loader_->Identifier());
}
void DedicatedWorker::OnFinished(const v8_inspector::V8StackTraceId& stack_id) {
DCHECK(GetExecutionContext()->IsContextThread());
if (classic_script_loader_->Canceled()) {
// Do nothing.
} else if (classic_script_loader_->Failed()) {
context_proxy_->DidFailToFetchScript();
} else {
CountTopLevelScriptRequestUrlOriginType(
*GetExecutionContext()->GetSecurityOrigin(), script_request_url_);
network::mojom::ReferrerPolicy referrer_policy =
network::mojom::ReferrerPolicy::kDefault;
if (!classic_script_loader_->GetReferrerPolicy().IsNull()) {
SecurityPolicy::ReferrerPolicyFromHeaderValue(
classic_script_loader_->GetReferrerPolicy(),
kDoNotSupportReferrerPolicyLegacyKeywords, &referrer_policy);
}
const KURL script_response_url = classic_script_loader_->ResponseURL();
DCHECK(script_request_url_ == script_response_url ||
SecurityOrigin::AreSameSchemeHostPort(script_request_url_,
script_response_url));
auto* outside_settings_object =
MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
*GetExecutionContext()
->Fetcher()
->Context()
.GetFetchClientSettingsObject());
context_proxy_->StartWorkerGlobalScope(
CreateGlobalScopeCreationParams(script_response_url, referrer_policy),
options_, script_response_url, outside_settings_object, stack_id,
classic_script_loader_->SourceText());
probe::scriptImported(GetExecutionContext(),
classic_script_loader_->Identifier(),
classic_script_loader_->SourceText());
}
classic_script_loader_ = nullptr;
}
std::unique_ptr<GlobalScopeCreationParams>
DedicatedWorker::CreateGlobalScopeCreationParams(
const KURL& script_url,
network::mojom::ReferrerPolicy referrer_policy) {
base::UnguessableToken parent_devtools_token;
std::unique_ptr<WorkerSettings> settings;
if (auto* document = DynamicTo<Document>(GetExecutionContext())) {
if (document->GetFrame())
parent_devtools_token = document->GetFrame()->GetDevToolsFrameToken();
settings = std::make_unique<WorkerSettings>(document->GetSettings());
} else {
WorkerGlobalScope* worker_global_scope =
To<WorkerGlobalScope>(GetExecutionContext());
parent_devtools_token =
worker_global_scope->GetThread()->GetDevToolsWorkerToken();
settings = WorkerSettings::Copy(worker_global_scope->GetWorkerSettings());
}
mojom::ScriptType script_type = (options_->type() == "classic")
? mojom::ScriptType::kClassic
: mojom::ScriptType::kModule;
scoped_refptr<WebWorkerFetchContext> web_worker_fetch_context;
if (auto* document = DynamicTo<Document>(GetExecutionContext())) {
LocalFrame* frame = document->GetFrame();
web_worker_fetch_context = frame->Client()->CreateWorkerFetchContext();
web_worker_fetch_context->SetApplicationCacheHostID(
frame->Loader()
.GetDocumentLoader()
->GetApplicationCacheHost()
->GetHostID());
web_worker_fetch_context->SetIsOnSubframe(!frame->IsMainFrame());
} else if (auto* scope =
DynamicTo<WorkerGlobalScope>(GetExecutionContext())) {
web_worker_fetch_context =
static_cast<WorkerFetchContext&>(scope->Fetcher()->Context())
.GetWebWorkerFetchContext()
->CloneForNestedWorker();
}
return std::make_unique<GlobalScopeCreationParams>(
script_url, script_type, GetExecutionContext()->UserAgent(),
std::move(web_worker_fetch_context),
GetExecutionContext()->GetContentSecurityPolicy()->Headers(),
referrer_policy, GetExecutionContext()->GetSecurityOrigin(),
GetExecutionContext()->IsSecureContext(),
GetExecutionContext()->GetHttpsState(), CreateWorkerClients(),
GetExecutionContext()->GetSecurityContext().AddressSpace(),
OriginTrialContext::GetTokens(GetExecutionContext()).get(),
parent_devtools_token, std::move(settings), kV8CacheOptionsDefault,
nullptr /* worklet_module_responses_map */,
ConnectToWorkerInterfaceProvider(GetExecutionContext(),
SecurityOrigin::Create(script_url)),
CreateBeginFrameProviderParams(),
GetExecutionContext()->GetSecurityContext().GetFeaturePolicy(),
GetExecutionContext()->GetAgentClusterID());
}
const AtomicString& DedicatedWorker::InterfaceName() const {
return event_target_names::kWorker;
}
void DedicatedWorker::Trace(blink::Visitor* visitor) {
visitor->Trace(context_proxy_);
visitor->Trace(options_);
visitor->Trace(classic_script_loader_);
AbstractWorker::Trace(visitor);
}
} // namespace blink