blob: 2aaecb06b6b6bf47c52e3ae69ba221092ddbc640 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* 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/modules/exported/web_embedded_worker_impl.h"
#include <memory>
#include <utility>
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/referrer_policy.mojom-blink.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom-blink.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_provider.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/platform/web_worker_fetch_context.h"
#include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_client.h"
#include "third_party/blink/public/web/web_console_message.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/core/execution_context/security_context.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/inspector/worker_devtools_params.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.h"
#include "third_party/blink/renderer/core/loader/worker_fetch_context.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/worker_backing_thread_startup_data.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/modules/service_worker/service_worker_global_scope_proxy.h"
#include "third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager.h"
#include "third_party/blink/renderer/modules/service_worker/service_worker_thread.h"
#include "third_party/blink/renderer/platform/heap/handle.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/loader/fetch/resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/network/network_utils.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
// static
std::unique_ptr<WebEmbeddedWorker> WebEmbeddedWorker::Create(
WebServiceWorkerContextClient* client,
std::unique_ptr<WebServiceWorkerInstalledScriptsManagerParams>
installed_scripts_manager_params,
mojo::ScopedMessagePipeHandle content_settings_handle,
mojo::ScopedMessagePipeHandle cache_storage,
mojo::ScopedMessagePipeHandle interface_provider,
mojo::ScopedMessagePipeHandle browser_interface_broker) {
return std::make_unique<WebEmbeddedWorkerImpl>(
std::move(client), std::move(installed_scripts_manager_params),
std::make_unique<ServiceWorkerContentSettingsProxy>(
// Chrome doesn't use interface versioning.
// TODO(falken): Is that comment about versioning correct?
mojom::blink::WorkerContentSettingsProxyPtrInfo(
std::move(content_settings_handle), 0u)),
mojom::blink::CacheStoragePtrInfo(std::move(cache_storage),
mojom::blink::CacheStorage::Version_),
service_manager::mojom::blink::InterfaceProviderPtrInfo(
std::move(interface_provider),
service_manager::mojom::blink::InterfaceProvider::Version_),
mojo::PendingRemote<mojom::blink::BrowserInterfaceBroker>(
std::move(browser_interface_broker),
mojom::blink::BrowserInterfaceBroker::Version_));
}
// static
std::unique_ptr<WebEmbeddedWorkerImpl> WebEmbeddedWorkerImpl::CreateForTesting(
WebServiceWorkerContextClient* client,
std::unique_ptr<ServiceWorkerInstalledScriptsManager>
installed_scripts_manager) {
auto worker_impl = std::make_unique<WebEmbeddedWorkerImpl>(
client, nullptr /* installed_scripts_manager_params */,
std::make_unique<ServiceWorkerContentSettingsProxy>(
nullptr /* host_info */),
nullptr /* cache_storage_info */, nullptr /* interface_provider_info */,
mojo::NullRemote() /* browser_interface_broker */);
worker_impl->installed_scripts_manager_ =
std::move(installed_scripts_manager);
return worker_impl;
}
WebEmbeddedWorkerImpl::WebEmbeddedWorkerImpl(
WebServiceWorkerContextClient* client,
std::unique_ptr<WebServiceWorkerInstalledScriptsManagerParams>
installed_scripts_manager_params,
std::unique_ptr<ServiceWorkerContentSettingsProxy> content_settings_client,
mojom::blink::CacheStoragePtrInfo cache_storage_info,
service_manager::mojom::blink::InterfaceProviderPtrInfo
interface_provider_info,
mojo::PendingRemote<mojom::blink::BrowserInterfaceBroker>
browser_interface_broker)
: worker_context_client_(client),
content_settings_client_(std::move(content_settings_client)),
pause_after_download_state_(kDontPauseAfterDownload),
cache_storage_info_(std::move(cache_storage_info)),
interface_provider_info_(std::move(interface_provider_info)),
browser_interface_broker_(std::move(browser_interface_broker)) {
if (installed_scripts_manager_params) {
DCHECK(installed_scripts_manager_params->manager_request.is_valid());
DCHECK(installed_scripts_manager_params->manager_host_ptr.is_valid());
Vector<KURL> installed_scripts_urls;
installed_scripts_urls.AppendRange(
installed_scripts_manager_params->installed_scripts_urls.begin(),
installed_scripts_manager_params->installed_scripts_urls.end());
installed_scripts_manager_ = std::make_unique<
ServiceWorkerInstalledScriptsManager>(
installed_scripts_urls,
mojom::blink::ServiceWorkerInstalledScriptsManagerRequest(
std::move(installed_scripts_manager_params->manager_request)),
mojom::blink::ServiceWorkerInstalledScriptsManagerHostPtrInfo(
std::move(installed_scripts_manager_params->manager_host_ptr),
mojom::blink::ServiceWorkerInstalledScriptsManagerHost::Version_),
Platform::Current()->GetIOTaskRunner());
}
}
WebEmbeddedWorkerImpl::~WebEmbeddedWorkerImpl() {
// TerminateWorkerContext() must be called before the destructor.
DCHECK(asked_to_terminate_);
}
void WebEmbeddedWorkerImpl::StartWorkerContext(
const WebEmbeddedWorkerStartData& data) {
DCHECK(!asked_to_terminate_);
DCHECK_EQ(pause_after_download_state_, kDontPauseAfterDownload);
worker_start_data_ = data;
// TODO(mkwst): This really needs to be piped through from the requesting
// document, like we're doing for SharedWorkers. That turns out to be
// incredibly convoluted, and since ServiceWorkers are locked to the same
// origin as the page which requested them, the only time it would come
// into play is a DNS poisoning attack after the page load. It's something
// we should fix, but we're taking this shortcut for the prototype.
//
// https://crbug.com/590714
KURL script_url = worker_start_data_.script_url;
worker_start_data_.address_space = network::mojom::IPAddressSpace::kPublic;
if (network_utils::IsReservedIPAddress(script_url.Host()))
worker_start_data_.address_space = network::mojom::IPAddressSpace::kPrivate;
if (SecurityOrigin::Create(script_url)->IsLocalhost())
worker_start_data_.address_space = network::mojom::IPAddressSpace::kLocal;
if (data.pause_after_download_mode ==
WebEmbeddedWorkerStartData::kPauseAfterDownload)
pause_after_download_state_ = kDoPauseAfterDownload;
devtools_worker_token_ = data.devtools_worker_token;
// |loader_factory| is null since all loads for new scripts go through
// ServiceWorkerNetworkProviderForServiceWorker::script_loader_factory()
// rather than the shadow page's loader.
shadow_page_ = std::make_unique<WorkerShadowPage>(
this, nullptr /* loader_factory */,
std::move(worker_start_data_.privacy_preferences));
wait_for_debugger_mode_ = worker_start_data_.wait_for_debugger_mode;
shadow_page_->Initialize(worker_start_data_.script_url);
}
void WebEmbeddedWorkerImpl::TerminateWorkerContext() {
if (asked_to_terminate_)
return;
asked_to_terminate_ = true;
if (!shadow_page_->WasInitialized()) {
// This deletes 'this'.
worker_context_client_->WorkerContextFailedToStartOnInitiatorThread();
return;
}
if (!worker_thread_) {
// The worker thread has not been created yet if the worker is asked to
// terminate during waiting for debugger or paused after download.
DCHECK(worker_start_data_.wait_for_debugger_mode ==
WebEmbeddedWorkerStartData::kWaitForDebugger ||
pause_after_download_state_ == kIsPausedAfterDownload);
// This deletes 'this'.
worker_context_client_->WorkerContextFailedToStartOnInitiatorThread();
return;
}
worker_thread_->Terminate();
}
void WebEmbeddedWorkerImpl::ResumeAfterDownload() {
// TODO(bashi): Remove this method. This does nothing anymore.
DCHECK(!asked_to_terminate_);
}
void WebEmbeddedWorkerImpl::AddMessageToConsole(
const WebConsoleMessage& message) {
shadow_page_->GetDocument()->AddConsoleMessage(ConsoleMessage::Create(
mojom::ConsoleMessageSource::kOther, message.level, message.text,
std::make_unique<SourceLocation>(message.url, message.line_number,
message.column_number, nullptr)));
}
void WebEmbeddedWorkerImpl::OnShadowPageInitialized() {
DCHECK(!asked_to_terminate_);
// This shadow page's address space will be used for creating outside
// FetchClientSettingsObject.
shadow_page_->GetDocument()->SetAddressSpace(
worker_start_data_.address_space);
StartWorkerThread();
}
void WebEmbeddedWorkerImpl::StartWorkerThread() {
DCHECK(!asked_to_terminate_);
// For now we don't use global scope name for service workers.
const String global_scope_name = g_empty_string;
// TODO(crbug.com/967265,937177): Plumb these starter parameters from an
// appropriate Document. See comment in CreateFetchClientSettingsObject() for
// details.
scoped_refptr<const SecurityOrigin> starter_origin =
SecurityOrigin::Create(worker_start_data_.script_url);
// This roughly equals to shadow document's IsSecureContext() as a shadow
// document have a frame with no parent.
// See also Document::InitSecureContextState().
bool starter_secure_context =
starter_origin->IsPotentiallyTrustworthy() ||
SchemeRegistry::SchemeShouldBypassSecureContextCheck(
starter_origin->Protocol());
const HttpsState starter_https_state =
CalculateHttpsState(starter_origin.get());
scoped_refptr<WebWorkerFetchContext> web_worker_fetch_context =
worker_context_client_->CreateWorkerFetchContextOnInitiatorThread();
// Create WorkerSettings. Currently we block all mixed-content requests from
// a ServiceWorker.
// TODO(bashi): Set some of these settings from WebPreferences. We may want
// to propagate and update these settings from the browser process in a way
// similar to mojom::RendererPreference{Watcher}.
auto worker_settings = std::make_unique<WorkerSettings>(
false /* disable_reading_from_canvas */,
true /* strict_mixed_content_checking */,
false /* allow_running_of_insecure_content */,
false /* strictly_block_blockable_mixed_content */,
GenericFontFamilySettings());
std::unique_ptr<GlobalScopeCreationParams> global_scope_creation_params;
String source_code;
std::unique_ptr<Vector<uint8_t>> cached_meta_data;
bool is_script_installed = installed_scripts_manager_ &&
installed_scripts_manager_->IsScriptInstalled(
worker_start_data_.script_url);
// We don't have to set ContentSecurityPolicy and ReferrerPolicy. They're
// served by the worker script loader or the installed scripts manager on the
// worker thread.
global_scope_creation_params = std::make_unique<GlobalScopeCreationParams>(
worker_start_data_.script_url, worker_start_data_.script_type,
OffMainThreadWorkerScriptFetchOption::kEnabled, global_scope_name,
worker_start_data_.user_agent, std::move(web_worker_fetch_context),
Vector<CSPHeaderAndType>(), network::mojom::ReferrerPolicy::kDefault,
starter_origin.get(), starter_secure_context, starter_https_state,
nullptr /* worker_clients */, std::move(content_settings_client_),
base::nullopt /* response_address_space */,
nullptr /* OriginTrialTokens */, devtools_worker_token_,
std::move(worker_settings),
static_cast<V8CacheOptions>(worker_start_data_.v8_cache_options),
nullptr /* worklet_module_respones_map */,
std::move(interface_provider_info_), std::move(browser_interface_broker_),
BeginFrameProviderParams(), nullptr /* parent_feature_policy */,
base::UnguessableToken() /* agent_cluster_id */);
// Generate the full code cache in the first execution of the script.
global_scope_creation_params->v8_cache_options =
kV8CacheOptionsFullCodeWithoutHeatCheck;
worker_thread_ = std::make_unique<ServiceWorkerThread>(
ServiceWorkerGlobalScopeProxy::Create(*this, *worker_context_client_),
std::move(installed_scripts_manager_), std::move(cache_storage_info_));
auto devtools_params = std::make_unique<WorkerDevToolsParams>();
devtools_params->devtools_worker_token = devtools_worker_token_;
devtools_params->wait_for_debugger =
wait_for_debugger_mode_ == WebEmbeddedWorkerStartData::kWaitForDebugger;
mojo::PendingRemote<mojom::blink::DevToolsAgent> devtools_agent_remote;
devtools_params->agent_receiver =
devtools_agent_remote.InitWithNewPipeAndPassReceiver();
mojo::PendingReceiver<mojom::blink::DevToolsAgentHost>
devtools_agent_host_receiver =
devtools_params->agent_host_remote.InitWithNewPipeAndPassReceiver();
worker_thread_->Start(std::move(global_scope_creation_params),
WorkerBackingThreadStartupData::CreateDefault(),
std::move(devtools_params));
// If this is an installed service worker, the installed script will be read
// from the service worker script storage on the worker thread.
if (is_script_installed) {
switch (worker_start_data_.script_type) {
case mojom::ScriptType::kClassic:
worker_thread_->RunInstalledClassicScript(
worker_start_data_.script_url, v8_inspector::V8StackTraceId());
break;
case mojom::ScriptType::kModule:
worker_thread_->RunInstalledModuleScript(
worker_start_data_.script_url,
CreateFetchClientSettingsObjectData(starter_origin.get(),
starter_https_state),
network::mojom::CredentialsMode::kOmit);
break;
}
} else {
std::unique_ptr<CrossThreadFetchClientSettingsObjectData>
fetch_client_setting_object_data = CreateFetchClientSettingsObjectData(
starter_origin.get(), starter_https_state);
// If this is a new (not installed) service worker, we are in the Update
// algorithm here:
// > Switching on job's worker type, run these substeps with the following
// > options:
// https://w3c.github.io/ServiceWorker/#update-algorithm
switch (worker_start_data_.script_type) {
// > "classic": Fetch a classic worker script given job's serialized
// > script url, job's client, "serviceworker", and the to-be-created
// > environment settings object for this service worker.
case mojom::ScriptType::kClassic:
worker_thread_->FetchAndRunClassicScript(
worker_start_data_.script_url,
std::move(fetch_client_setting_object_data),
nullptr /* outside_resource_timing_notifier */,
v8_inspector::V8StackTraceId());
break;
// > "module": Fetch a module worker script graph given job’s serialized
// > script url, job’s client, "serviceworker", "omit", and the
// > to-be-created environment settings object for this service worker.
case mojom::ScriptType::kModule:
worker_thread_->FetchAndRunModuleScript(
worker_start_data_.script_url,
std::move(fetch_client_setting_object_data),
nullptr /* outside_resource_timing_notifier */,
network::mojom::CredentialsMode::kOmit);
break;
}
}
// We are now ready to inspect worker thread.
worker_context_client_->WorkerReadyForInspectionOnInitiatorThread(
devtools_agent_remote.PassPipe(),
devtools_agent_host_receiver.PassPipe());
}
std::unique_ptr<CrossThreadFetchClientSettingsObjectData>
WebEmbeddedWorkerImpl::CreateFetchClientSettingsObjectData(
const SecurityOrigin* security_origin,
const HttpsState& https_state) {
// TODO(crbug.com/967265): Currently we create an incomplete outside settings
// object from |worker_start_data_| but we should create a proper outside
// settings objects depending on the situation. For new worker case, this
// should be the Document that called navigator.serviceWorker.register(). For
// ServiceWorkerRegistration#update() case, it should be the Document that
// called update(). For soft update case, it seems to be 'null' document.
//
// To get a correct settings, we need to make a way to pass the settings
// object over mojo IPCs.
const KURL& script_url = worker_start_data_.script_url;
return std::make_unique<CrossThreadFetchClientSettingsObjectData>(
script_url.Copy() /* global_object_url */,
script_url.Copy() /* base_url */, security_origin->IsolatedCopy(),
network::mojom::ReferrerPolicy::kDefault,
script_url.GetString().IsolatedCopy() /* outgoing_referrer */,
https_state, AllowedByNosniff::MimeTypeCheck::kLax,
worker_start_data_.address_space,
kBlockAllMixedContent /* insecure_requests_policy */,
FetchClientSettingsObject::InsecureNavigationsSet(),
false /* mixed_autoupgrade_opt_out */);
}
void WebEmbeddedWorkerImpl::WaitForShutdownForTesting() {
DCHECK(worker_thread_);
worker_thread_->WaitForShutdownForTesting();
}
} // namespace blink