diff options
author | Istiaque Ahmed <lazyboy@chromium.org> | 2019-12-31 00:32:49 +0000 |
---|---|---|
committer | Michal Klocek <michal.klocek@qt.io> | 2020-04-22 18:10:21 +0000 |
commit | 5a7809f25c4e10895eb166a8e9d6301c37aea1cb (patch) | |
tree | a45e03835b4c59d2bbb16ce0056afadcd141eebb | |
parent | 6f916658d9ff036f97fa21e287b543a7acd90e00 (diff) | |
download | qtwebengine-chromium-5a7809f25c4e10895eb166a8e9d6301c37aea1cb.tar.gz |
[Backport] CVE-2020-6454 1/2
Extension SW: make renderer aware of ActivationSequence.
This CL sends ActivationSequence info on extension load
(through ExtensionMsg_Load IPC) to renderers. Therefore, worker
notifications from renderer now carry the sequence back to
browser process / ServiceWorkerTaskQueue. This makes renderer
notifications deterministic-ally map-able to an extension load sequence,
as opposed to guessing an ActivationSequence (esp. when an extension is
deactivated and then activated).
This CL also drops the knowledge of a WorkerState upon extension
deactivation as we can safely ignore expired renderer notifications
arriving after that.
RendererExtensionRegistry stores ActivationSequences for all
worker based extensions.
Bug: 1022247, 1003244
Change-Id: I2e8d99b16f2e611dda02b1f9c62b90d8325b7ff8
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
18 files changed, 291 insertions, 181 deletions
diff --git a/chromium/extensions/browser/extension_registrar.cc b/chromium/extensions/browser/extension_registrar.cc index cd41cfda08f..55a5cbae2a9 100644 --- a/chromium/extensions/browser/extension_registrar.cc +++ b/chromium/extensions/browser/extension_registrar.cc @@ -434,13 +434,17 @@ void ExtensionRegistrar::ActivateExtension(const Extension* extension, base::Bind(&ExtensionRegistrar::OnExtensionRegisteredWithRequestContexts, weak_factory_.GetWeakPtr(), WrapRefCounted(extension))); - renderer_helper_->OnExtensionLoaded(*extension); - + // Activate the extension before calling + // RendererStartupHelper::OnExtensionLoaded() below, so that we have + // activation information ready while we send ExtensionMsg_Load IPC. + // // TODO(lazyboy): We should move all logic that is required to start up an // extension to a separate class, instead of calling adhoc methods like // service worker ones below. ActivateTaskQueueForExtension(browser_context_, extension); + renderer_helper_->OnExtensionLoaded(*extension); + // Tell subsystems that use the ExtensionRegistryObserver::OnExtensionLoaded // about the new extension. // diff --git a/chromium/extensions/browser/extension_service_worker_message_filter.cc b/chromium/extensions/browser/extension_service_worker_message_filter.cc index 33f883da4da..cb246ae3e1e 100644 --- a/chromium/extensions/browser/extension_service_worker_message_filter.cc +++ b/chromium/extensions/browser/extension_service_worker_message_filter.cc @@ -146,6 +146,7 @@ void ExtensionServiceWorkerMessageFilter::OnDidInitializeServiceWorkerContext( void ExtensionServiceWorkerMessageFilter::OnDidStartServiceWorkerContext( const ExtensionId& extension_id, + int activation_sequence, const GURL& service_worker_scope, int64_t service_worker_version_id, int thread_id) { @@ -161,12 +162,13 @@ void ExtensionServiceWorkerMessageFilter::OnDidStartServiceWorkerContext( ServiceWorkerTaskQueue::Get(browser_context_) ->DidStartServiceWorkerContext(render_process_id_, extension_id, - service_worker_scope, + activation_sequence, service_worker_scope, service_worker_version_id, thread_id); } void ExtensionServiceWorkerMessageFilter::OnDidStopServiceWorkerContext( const ExtensionId& extension_id, + int activation_sequence, const GURL& service_worker_scope, int64_t service_worker_version_id, int thread_id) { @@ -182,7 +184,7 @@ void ExtensionServiceWorkerMessageFilter::OnDidStopServiceWorkerContext( ServiceWorkerTaskQueue::Get(browser_context_) ->DidStopServiceWorkerContext(render_process_id_, extension_id, - service_worker_scope, + activation_sequence, service_worker_scope, service_worker_version_id, thread_id); } diff --git a/chromium/extensions/browser/extension_service_worker_message_filter.h b/chromium/extensions/browser/extension_service_worker_message_filter.h index 049d5771c6b..07a72ab13b5 100644 --- a/chromium/extensions/browser/extension_service_worker_message_filter.h +++ b/chromium/extensions/browser/extension_service_worker_message_filter.h @@ -56,10 +56,12 @@ class ExtensionServiceWorkerMessageFilter int64_t service_worker_version_id, int thread_id); void OnDidStartServiceWorkerContext(const ExtensionId& extension_id, + int activation_sequence, const GURL& service_worker_scope, int64_t service_worker_version_id, int thread_id); void OnDidStopServiceWorkerContext(const ExtensionId& extension_id, + int activation_sequence, const GURL& service_worker_scope, int64_t service_worker_version_id, int thread_id); diff --git a/chromium/extensions/browser/renderer_startup_helper.cc b/chromium/extensions/browser/renderer_startup_helper.cc index ff445f550d9..0c05644d32a 100644 --- a/chromium/extensions/browser/renderer_startup_helper.cc +++ b/chromium/extensions/browser/renderer_startup_helper.cc @@ -23,12 +23,14 @@ #include "extensions/browser/extension_util.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/guest_view/web_view/web_view_guest.h" +#include "extensions/browser/service_worker_task_queue.h" #include "extensions/common/cors_util.h" #include "extensions/common/extension_messages.h" #include "extensions/common/extension_set.h" #include "extensions/common/extensions_client.h" #include "extensions/common/features/feature_channel.h" #include "extensions/common/features/feature_session_type.h" +#include "extensions/common/manifest_handlers/background_info.h" #include "extensions/common/permissions/permissions_data.h" #include "ui/base/webui/web_ui_util.h" #include "url/origin.h" @@ -56,6 +58,17 @@ bool IsExtensionVisibleToContext(const Extension& extension, util::IsIncognitoEnabled(extension.id(), browser_context); } +// Returns the current ActivationSequence of |extension| if the extension is +// Service Worker-based, otherwise returns base::nullopt. +base::Optional<int> GetWorkerActivationSequence(BrowserContext* browser_context, + const Extension& extension) { + if (BackgroundInfo::IsServiceWorkerBased(&extension)) { + return ServiceWorkerTaskQueue::Get(browser_context) + ->GetCurrentSequence(extension.id()); + } + return base::nullopt; +} + } // namespace RendererStartupHelper::RendererStartupHelper(BrowserContext* browser_context) @@ -153,8 +166,9 @@ void RendererStartupHelper::InitializeProcess( // I am not sure this is possible to know this here, at such a low // level of the stack. Perhaps site isolation can help. bool include_tab_permissions = true; - loaded_extensions.push_back( - ExtensionMsg_Loaded_Params(ext.get(), include_tab_permissions)); + loaded_extensions.push_back(ExtensionMsg_Loaded_Params( + ext.get(), include_tab_permissions, + GetWorkerActivationSequence(renderer_context, *ext))); extension_process_map_[ext->id()].insert(process); } @@ -248,7 +262,8 @@ void RendererStartupHelper::OnExtensionLoaded(const Extension& extension) { // Uninitialized renderers will be informed of the extension load during the // first batch of messages. std::vector<ExtensionMsg_Loaded_Params> params; - params.emplace_back(&extension, false /* no tab permissions */); + params.emplace_back(&extension, false /* no tab permissions */, + GetWorkerActivationSequence(browser_context_, extension)); for (content::RenderProcessHost* process : initialized_processes_) { if (!IsExtensionVisibleToContext(extension, process->GetBrowserContext())) diff --git a/chromium/extensions/browser/service_worker/worker_id.cc b/chromium/extensions/browser/service_worker/worker_id.cc index bdfa21d8334..7e66ff0ad1c 100644 --- a/chromium/extensions/browser/service_worker/worker_id.cc +++ b/chromium/extensions/browser/service_worker/worker_id.cc @@ -24,4 +24,8 @@ bool WorkerId::operator==(const WorkerId& other) const { version_id == other.version_id && thread_id == other.thread_id; } +bool WorkerId::operator!=(const WorkerId& other) const { + return !this->operator==(other); +} + } // namespace extensions diff --git a/chromium/extensions/browser/service_worker/worker_id.h b/chromium/extensions/browser/service_worker/worker_id.h index 21f91c6c9a0..5ce79cdfab5 100644 --- a/chromium/extensions/browser/service_worker/worker_id.h +++ b/chromium/extensions/browser/service_worker/worker_id.h @@ -20,6 +20,7 @@ struct WorkerId { bool operator<(const WorkerId& other) const; bool operator==(const WorkerId& other) const; + bool operator!=(const WorkerId& other) const; }; } // namespace extensions diff --git a/chromium/extensions/browser/service_worker_task_queue.cc b/chromium/extensions/browser/service_worker_task_queue.cc index dedb8c39f7d..087cdb0d519 100644 --- a/chromium/extensions/browser/service_worker_task_queue.cc +++ b/chromium/extensions/browser/service_worker_task_queue.cc @@ -40,6 +40,36 @@ const char kServiceWorkerVersion[] = "version"; ServiceWorkerTaskQueue::TestObserver* g_test_observer = nullptr; +// ServiceWorkerRegistration state of an activated extension. +enum class RegistrationState { + // Not registered. + kNotRegistered, + // Registration is inflight. + kPending, + // Registration is complete. + kRegistered, +}; + +// Browser process worker state of an activated extension. +enum class BrowserState { + // Initial state, not started. + kInitial, + // Worker is in the process of starting from the browser process. + kStarting, + // Worker has completed starting (i.e. has seen DidStartWorkerForScope). + kStarted, +}; + +// Render process worker state of an activated extension. +enum class RendererState { + // Initial state, neither started nor stopped. + kInitial, + // Worker thread has started. + kStarted, + // Worker thread has not started or has been stopped. + kStopped, +}; + } // namespace ServiceWorkerTaskQueue::ServiceWorkerTaskQueue(BrowserContext* browser_context) @@ -108,19 +138,44 @@ void ServiceWorkerTaskQueue::StartServiceWorkerOnCoreThreadToRunTasks( context_id, task_queue_weak)); } -// The current state of a worker. -struct ServiceWorkerTaskQueue::WorkerState { - // Whether or not worker has completed starting (DidStartWorkerForScope). - bool browser_ready = false; +// The current worker related state of an activated extension. +class ServiceWorkerTaskQueue::WorkerState { + public: + WorkerState() = default; + + WorkerState(const WorkerState&) = delete; + WorkerState& operator=(const WorkerState&) = delete; - // Whether or not worker is ready in the renderer - // (DidStartServiceWorkerContext). - bool renderer_ready = false; + void SetWorkerId(const WorkerId& worker_id, ProcessManager* process_manager) { + if (worker_id_ && *worker_id_ != worker_id) { + // Sanity check that the old worker is gone. + DCHECK(!process_manager->HasServiceWorker(*worker_id_)); + // Clear stale renderer state if there's any. + renderer_state_ = RendererState::kInitial; + } + worker_id_ = worker_id; + } - // If |browser_ready| = true, this is the ActivationSequence of the worker. - base::Optional<ActivationSequence> sequence; + bool ready() const { + return registration_state_ == RegistrationState::kRegistered && + browser_state_ == BrowserState::kStarted && + renderer_state_ == RendererState::kStarted && worker_id_.has_value(); + } + bool has_pending_tasks() const { return !pending_tasks_.empty(); } - WorkerState() = default; + private: + friend class ServiceWorkerTaskQueue; + + RegistrationState registration_state_ = RegistrationState::kNotRegistered; + BrowserState browser_state_ = BrowserState::kInitial; + RendererState renderer_state_ = RendererState::kInitial; + + // Pending tasks that will be run once the worker becomes ready. + std::vector<PendingTask> pending_tasks_; + + // Contains the worker's WorkerId associated with this WorkerState, once we + // have discovered info about the worker. + base::Optional<WorkerId> worker_id_; }; void ServiceWorkerTaskQueue::DidStartWorkerForScope( @@ -135,11 +190,12 @@ void ServiceWorkerTaskQueue::DidStartWorkerForScope( // Extension run with |sequence| was already deactivated. // TODO(lazyboy): Add a DCHECK that the worker in question is actually // shutting down soon. - DCHECK(!base::Contains(pending_tasks_, context_id)); + DCHECK(!GetWorkerState(context_id)); return; } - const LazyContextId& lazy_context_id = context_id.first; + WorkerState* worker_state = GetWorkerState(context_id); + DCHECK(worker_state); const WorkerId worker_id = {extension_id, process_id, version_id, thread_id}; // Note: If the worker has already stopped on worker thread @@ -150,15 +206,12 @@ void ServiceWorkerTaskQueue::DidStartWorkerForScope( // renderer before we execute tasks in the browser process. This will also // avoid holding the worker in |worker_state_map_| until deactivation as noted // above. - WorkerState* worker_state = - GetOrCreateWorkerState(WorkerKey(lazy_context_id, worker_id)); - DCHECK(worker_state); - DCHECK(!worker_state->browser_ready) << "Worker was already loaded"; - worker_state->browser_ready = true; - worker_state->sequence = sequence; + DCHECK_NE(BrowserState::kStarted, worker_state->browser_state_) + << "Worker was already loaded"; + worker_state->SetWorkerId(worker_id, ProcessManager::Get(browser_context_)); + worker_state->browser_state_ = BrowserState::kStarted; - RunPendingTasksIfWorkerReady(lazy_context_id, version_id, process_id, - thread_id); + RunPendingTasksIfWorkerReady(context_id); } void ServiceWorkerTaskQueue::DidStartWorkerFail( @@ -167,9 +220,10 @@ void ServiceWorkerTaskQueue::DidStartWorkerFail( if (!IsCurrentSequence(context_id.first.extension_id(), context_id.second)) { // This can happen is when the registration got unregistered right before we // tried to start it. See crbug.com/999027 for details. - DCHECK(!base::Contains(pending_tasks_, context_id)); + DCHECK(!GetWorkerState(context_id)); return; } + // TODO(lazyboy): Handle failure cases. DCHECK(false) << "DidStartWorkerFail: " << context_id.first.extension_id(); } @@ -188,50 +242,70 @@ void ServiceWorkerTaskQueue::DidInitializeServiceWorkerContext( void ServiceWorkerTaskQueue::DidStartServiceWorkerContext( int render_process_id, const ExtensionId& extension_id, + int activation_sequence, const GURL& service_worker_scope, int64_t service_worker_version_id, int thread_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - LazyContextId context_id(browser_context_, extension_id, - service_worker_scope); + if (!IsCurrentSequence(extension_id, activation_sequence)) + return; + + SequencedContextId context_id( + LazyContextId(browser_context_, extension_id, service_worker_scope), + activation_sequence); const WorkerId worker_id = {extension_id, render_process_id, service_worker_version_id, thread_id}; - WorkerState* worker_state = - GetOrCreateWorkerState(WorkerKey(context_id, worker_id)); - DCHECK(!worker_state->renderer_ready) << "Worker already started"; - worker_state->renderer_ready = true; - - RunPendingTasksIfWorkerReady(context_id, service_worker_version_id, - render_process_id, thread_id); + WorkerState* worker_state = GetWorkerState(context_id); + DCHECK(worker_state); + // If |worker_state| had a worker running previously, for which we didn't + // see DidStopServiceWorkerContext notification (typically happens on render + // process shutdown), then we'd preserve stale state in |renderer_state_|. + // + // This isn't a problem because the next browser process readiness + // (DidStartWorkerForScope) or the next renderer process readiness + // (DidStartServiceWorkerContext) will clear the state, whichever happens + // first. + // + // TODO(lazyboy): Update the renderer state in RenderProcessExited() and + // uncomment the following DCHECK: + // DCHECK_NE(RendererState::kStarted, worker_state->renderer_state_) + // << "Worker already started"; + worker_state->SetWorkerId(worker_id, ProcessManager::Get(browser_context_)); + worker_state->renderer_state_ = RendererState::kStarted; + + RunPendingTasksIfWorkerReady(context_id); } void ServiceWorkerTaskQueue::DidStopServiceWorkerContext( int render_process_id, const ExtensionId& extension_id, + int activation_sequence, const GURL& service_worker_scope, int64_t service_worker_version_id, int thread_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (!IsCurrentSequence(extension_id, activation_sequence)) + return; + const WorkerId worker_id = {extension_id, render_process_id, service_worker_version_id, thread_id}; ProcessManager::Get(browser_context_)->UnregisterServiceWorker(worker_id); - LazyContextId context_id(browser_context_, extension_id, - service_worker_scope); + SequencedContextId context_id( + LazyContextId(browser_context_, extension_id, service_worker_scope), + activation_sequence); - WorkerKey worker_key(context_id, worker_id); - WorkerState* worker_state = GetWorkerState(worker_key); - if (!worker_state) { + WorkerState* worker_state = GetWorkerState(context_id); + DCHECK(worker_state); + + if (worker_state->worker_id_ != worker_id) { // We can see DidStopServiceWorkerContext right after DidInitialize and // without DidStartServiceWorkerContext. return; } - // Clean up both the renderer and browser readiness states. - // One caveat is that although this is renderer notification, we also clear - // the browser readiness state, this is because a worker can be - // |browser_ready| and was waiting for DidStartServiceWorkerContext, but - // instead received DidStopServiceWorkerContext. - worker_state_map_.erase(worker_key); + DCHECK_NE(RendererState::kStopped, worker_state->renderer_state_); + worker_state->renderer_state_ = RendererState::kStopped; + worker_state->worker_id_ = base::nullopt; } // static @@ -258,11 +332,13 @@ void ServiceWorkerTaskQueue::AddPendingTask( DCHECK(sequence) << "Trying to add pending task to an inactive extension: " << lazy_context_id.extension_id(); const SequencedContextId context_id(lazy_context_id, *sequence); - auto& tasks = pending_tasks_[context_id]; + WorkerState* worker_state = GetWorkerState(context_id); + DCHECK(worker_state); + auto& tasks = worker_state->pending_tasks_; bool needs_start_worker = tasks.empty(); tasks.push_back(std::move(task)); - if (pending_registrations_.count(context_id) > 0) { + if (worker_state->registration_state_ != RegistrationState::kRegistered) { // If the worker hasn't finished registration, wait for it to complete. // DidRegisterServiceWorker will Start worker to run |task| later. return; @@ -280,6 +356,11 @@ void ServiceWorkerTaskQueue::ActivateExtension(const Extension* extension) { const ExtensionId extension_id = extension->id(); ActivationSequence current_sequence = ++next_activation_sequence_; activation_sequences_[extension_id] = current_sequence; + SequencedContextId context_id( + LazyContextId(browser_context_, extension_id, extension->url()), + current_sequence); + DCHECK(!base::Contains(worker_state_map_, context_id)); + WorkerState& worker_state = worker_state_map_[context_id]; // Note: version.IsValid() = false implies we didn't have any prefs stored. base::Version version = RetrieveRegisteredServiceWorkerVersion(extension_id); @@ -291,15 +372,13 @@ void ServiceWorkerTaskQueue::ActivateExtension(const Extension* extension) { } if (service_worker_already_registered) { + worker_state.registration_state_ = RegistrationState::kRegistered; // TODO(https://crbug.com/901101): We should kick off an async check to see // if the registration is *actually* there and re-register if necessary. return; } - SequencedContextId context_id( - LazyContextId(browser_context_, extension_id, extension->url()), - current_sequence); - pending_registrations_.insert(context_id); + worker_state.registration_state_ = RegistrationState::kPending; GURL script_url = extension->GetResourceURL( BackgroundInfo::GetBackgroundServiceWorkerScript(extension)); blink::mojom::ServiceWorkerRegistrationOptions option; @@ -328,16 +407,13 @@ void ServiceWorkerTaskQueue::DeactivateExtension(const Extension* extension) { SequencedContextId context_id( LazyContextId(browser_context_, extension_id, extension->url()), *sequence); - ClearPendingTasks(context_id); - - // Clear loaded worker if it was waiting for start. - // Note that we don't clear the entire state here as we expect the renderer to - // stop shortly after this and its notification will clear the state. - ClearBrowserReadyForWorkers( - LazyContextId(browser_context_, extension_id, extension->url()), - *sequence); + WorkerState* worker_state = GetWorkerState(context_id); + DCHECK(worker_state); + // TODO(lazyboy): Run orphaned tasks with nullptr ContextInfo. + worker_state->pending_tasks_.clear(); + worker_state_map_.erase(context_id); - content::BrowserContext::GetStoragePartitionForSite(browser_context_, + content::BrowserContext::GetStoragePartitionForSite(browser_context_, extension->url()) ->GetServiceWorkerContext() ->UnregisterServiceWorker( @@ -354,6 +430,9 @@ void ServiceWorkerTaskQueue::RunTasksAfterStartWorker( if (lazy_context_id.browser_context() != browser_context_) return; + WorkerState* worker_state = GetWorkerState(context_id); + DCHECK_NE(BrowserState::kStarted, worker_state->browser_state_); + content::StoragePartition* partition = BrowserContext::GetStoragePartitionForSite( lazy_context_id.browser_context(), @@ -377,35 +456,30 @@ void ServiceWorkerTaskQueue::RunTasksAfterStartWorker( void ServiceWorkerTaskQueue::DidRegisterServiceWorker( const SequencedContextId& context_id, bool success) { - pending_registrations_.erase(context_id); ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); const ExtensionId& extension_id = context_id.first.extension_id(); DCHECK(registry); const Extension* extension = registry->enabled_extensions().GetByID(extension_id); if (!extension) { - // DeactivateExtension must have cleared |pending_tasks_| already. - DCHECK(!base::Contains(pending_tasks_, context_id)); return; } + if (!IsCurrentSequence(extension_id, context_id.second)) + return; + + WorkerState* worker_state = GetWorkerState(context_id); + DCHECK(worker_state); if (!success) { - if (!IsCurrentSequence(extension_id, context_id.second)) { - // DeactivateExtension must have cleared |pending_tasks_| already. - DCHECK(!base::Contains(pending_tasks_, context_id)); - return; - } // TODO(lazyboy): Handle failure case thoroughly. DCHECK(false) << "Failed to register Service Worker"; return; } + worker_state->registration_state_ = RegistrationState::kRegistered; SetRegisteredServiceWorkerInfo(extension->id(), extension->version()); - auto pending_tasks_iter = pending_tasks_.find(context_id); - const bool has_pending_tasks = pending_tasks_iter != pending_tasks_.end() && - pending_tasks_iter->second.size() > 0u; - if (has_pending_tasks) { + if (worker_state->has_pending_tasks()) { // TODO(lazyboy): If worker for |context_id| is already running, consider // not calling StartWorker. This isn't straightforward as service worker's // internal state is mostly on the core thread. @@ -467,45 +541,35 @@ void ServiceWorkerTaskQueue::RemoveRegisteredServiceWorkerInfo( } void ServiceWorkerTaskQueue::RunPendingTasksIfWorkerReady( - const LazyContextId& context_id, - int64_t version_id, - int process_id, - int thread_id) { - WorkerState* worker_state = GetWorkerState(WorkerKey( - context_id, - {context_id.extension_id(), process_id, version_id, thread_id})); + const SequencedContextId& context_id) { + WorkerState* worker_state = GetWorkerState(context_id); DCHECK(worker_state); - if (!worker_state->browser_ready || !worker_state->renderer_ready) { + if (!worker_state->ready()) { // Worker isn't ready yet, wait for next event and run the tasks then. return; } - base::Optional<int> sequence = worker_state->sequence; - DCHECK(sequence.has_value()); // Running |pending_tasks_[context_id]| marks the completion of // DidStartWorkerForScope, clean up |browser_ready| state of the worker so // that new tasks can be queued up. - worker_state->browser_ready = false; - - auto iter = pending_tasks_.find(SequencedContextId(context_id, *sequence)); - DCHECK(iter != pending_tasks_.end()) << "Worker ready, but no tasks to run!"; - std::vector<PendingTask> tasks = std::move(iter->second); - pending_tasks_.erase(iter); + worker_state->browser_state_ = BrowserState::kInitial; + + DCHECK(worker_state->has_pending_tasks()) + << "Worker ready, but no tasks to run!"; + std::vector<PendingTask> tasks; + std::swap(worker_state->pending_tasks_, tasks); + DCHECK(worker_state->worker_id_); + const auto& worker_id = *worker_state->worker_id_; for (auto& task : tasks) { auto context_info = std::make_unique<LazyContextTaskQueue::ContextInfo>( - context_id.extension_id(), - content::RenderProcessHost::FromID(process_id), version_id, thread_id, - context_id.service_worker_scope()); + context_id.first.extension_id(), + content::RenderProcessHost::FromID(worker_id.render_process_id), + worker_id.version_id, worker_id.thread_id, + context_id.first.service_worker_scope()); std::move(task).Run(std::move(context_info)); } } -void ServiceWorkerTaskQueue::ClearPendingTasks( - const SequencedContextId& context_id) { - // TODO(lazyboy): Run orphaned tasks with nullptr ContextInfo. - pending_tasks_.erase(context_id); -} - bool ServiceWorkerTaskQueue::IsCurrentSequence( const ExtensionId& extension_id, ActivationSequence sequence) const { @@ -522,44 +586,11 @@ ServiceWorkerTaskQueue::GetCurrentSequence( return iter->second; } -ServiceWorkerTaskQueue::WorkerState* -ServiceWorkerTaskQueue::GetOrCreateWorkerState(const WorkerKey& worker_key) { - auto iter = worker_state_map_.find(worker_key); - if (iter == worker_state_map_.end()) - iter = worker_state_map_.emplace(worker_key, WorkerState()).first; - return &(iter->second); -} - ServiceWorkerTaskQueue::WorkerState* ServiceWorkerTaskQueue::GetWorkerState( - const WorkerKey& worker_key) { - auto iter = worker_state_map_.find(worker_key); - if (iter == worker_state_map_.end()) - return nullptr; - return &(iter->second); -} - -void ServiceWorkerTaskQueue::ClearBrowserReadyForWorkers( - const LazyContextId& context_id, - ActivationSequence sequence) { - // TODO(lazyboy): We could use |worker_state_map_|.lower_bound() to avoid - // iterating over all workers. Note that it would require creating artificial - // WorkerKey with |context_id|. - for (auto iter = worker_state_map_.begin(); - iter != worker_state_map_.end();) { - if (iter->first.first != context_id || iter->second.sequence != sequence) { - ++iter; - continue; - } - - iter->second.browser_ready = false; - iter->second.sequence = base::nullopt; - - // Clean up stray entries if renderer readiness was also gone. - if (!iter->second.renderer_ready) - iter = worker_state_map_.erase(iter); - else - ++iter; - } + const SequencedContextId& context_id) { + auto worker_iter = worker_state_map_.find(context_id); + return worker_iter == worker_state_map_.end() ? nullptr + : &worker_iter->second; } } // namespace extensions diff --git a/chromium/extensions/browser/service_worker_task_queue.h b/chromium/extensions/browser/service_worker_task_queue.h index 2e5e215bd19..76b22858cc5 100644 --- a/chromium/extensions/browser/service_worker_task_queue.h +++ b/chromium/extensions/browser/service_worker_task_queue.h @@ -72,6 +72,12 @@ class Extension; class ServiceWorkerTaskQueue : public KeyedService, public LazyContextTaskQueue { public: + // Unique identifier for an extension's activation->deactivation span. + // TODO(lazyboy): Move this under extensions/common/ for consistency, so that + // renderer process can use this instead of using "int" directly. We'd also + // want StrongAlias for this. + using ActivationSequence = int; + explicit ServiceWorkerTaskQueue(content::BrowserContext* browser_context); ~ServiceWorkerTaskQueue() override; @@ -102,16 +108,24 @@ class ServiceWorkerTaskQueue : public KeyedService, // has completed executing. void DidStartServiceWorkerContext(int render_process_id, const ExtensionId& extension_id, + int activation_sequence, const GURL& service_worker_scope, int64_t service_worker_version_id, int thread_id); // Called once an extension Service Worker was destroyed. void DidStopServiceWorkerContext(int render_process_id, const ExtensionId& extension_id, + int activation_sequence, const GURL& service_worker_scope, int64_t service_worker_version_id, int thread_id); + // Returns the current ActivationSequence for an extension, if the extension + // is currently activated. Returns base::nullopt if the extension isn't + // activated. + base::Optional<ActivationSequence> GetCurrentSequence( + const ExtensionId& extension_id) const; + class TestObserver { public: TestObserver(); @@ -130,14 +144,9 @@ class ServiceWorkerTaskQueue : public KeyedService, static void SetObserverForTest(TestObserver* observer); private: - // Unique identifier for an extension's activation->deactivation span. - using ActivationSequence = int; using SequencedContextId = std::pair<LazyContextId, ActivationSequence>; - // Key used to identify a WorkerState within the worker container. - using WorkerKey = std::pair<LazyContextId, WorkerId>; - - struct WorkerState; + class WorkerState; static void DidStartWorkerForScopeOnCoreThread( const SequencedContextId& context_id, @@ -184,40 +193,19 @@ class ServiceWorkerTaskQueue : public KeyedService, // If the worker with |context_id| has seen worker start // (DidStartWorkerForScope) and load (DidStartServiceWorkerContext) then runs // all pending tasks for that worker. - void RunPendingTasksIfWorkerReady(const LazyContextId& context_id, - int64_t version_id, - int process_id, - int thread_id); - - void ClearPendingTasks(const SequencedContextId& context_id); + void RunPendingTasksIfWorkerReady(const SequencedContextId& context_id); // Returns true if |sequence| is the current activation sequence for // |extension_id|. bool IsCurrentSequence(const ExtensionId& extension_id, ActivationSequence sequence) const; - // Returns the current ActivationSequence for an extension, if the extension - // is currently activated. Returns base::nullopt if the extension isn't - // activated. - base::Optional<ActivationSequence> GetCurrentSequence( - const ExtensionId& extension_id) const; - - WorkerState* GetOrCreateWorkerState(const WorkerKey& worker_key); - WorkerState* GetWorkerState(const WorkerKey& worker_key); - void ClearBrowserReadyForWorkers(const LazyContextId& context_id, - ActivationSequence sequence); + WorkerState* GetWorkerState(const SequencedContextId& context_id); ActivationSequence next_activation_sequence_ = 0; - // Set of extension ids that hasn't completed Service Worker registration. - std::set<SequencedContextId> pending_registrations_; - - // The state of each workers we know about. - std::map<WorkerKey, WorkerState> worker_state_map_; - - // Pending tasks for a |LazyContextId| with an ActivationSequence. - // These tasks will be run once the corresponding worker becomes ready. - std::map<SequencedContextId, std::vector<PendingTask>> pending_tasks_; + // The state of worker of each activated extension. + std::map<SequencedContextId, WorkerState> worker_state_map_; content::BrowserContext* const browser_context_ = nullptr; diff --git a/chromium/extensions/common/extension_messages.cc b/chromium/extensions/common/extension_messages.cc index 1edd698679e..9f8d1b048c4 100644 --- a/chromium/extensions/common/extension_messages.cc +++ b/chromium/extensions/common/extension_messages.cc @@ -61,7 +61,8 @@ ExtensionMsg_Loaded_Params::~ExtensionMsg_Loaded_Params() {} ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params( const Extension* extension, - bool include_tab_permissions) + bool include_tab_permissions, + base::Optional<int> worker_activation_sequence) : manifest(static_cast<base::DictionaryValue&&>( extension->manifest()->value()->Clone())), location(extension->location()), @@ -76,6 +77,7 @@ ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params( uses_default_policy_blocked_allowed_hosts( extension->permissions_data()->UsesDefaultPolicyHostRestrictions()), id(extension->id()), + worker_activation_sequence(worker_activation_sequence), creation_flags(extension->creation_flags()) { if (include_tab_permissions) { for (const auto& pair : @@ -327,6 +329,7 @@ void ParamTraits<ExtensionMsg_Loaded_Params>::Write(base::Pickle* m, WriteParam(m, p.policy_blocked_hosts); WriteParam(m, p.policy_allowed_hosts); WriteParam(m, p.uses_default_policy_blocked_allowed_hosts); + WriteParam(m, p.worker_activation_sequence); } bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const base::Pickle* m, @@ -341,7 +344,8 @@ bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const base::Pickle* m, ReadParam(m, iter, &p->tab_specific_permissions) && ReadParam(m, iter, &p->policy_blocked_hosts) && ReadParam(m, iter, &p->policy_allowed_hosts) && - ReadParam(m, iter, &p->uses_default_policy_blocked_allowed_hosts); + ReadParam(m, iter, &p->uses_default_policy_blocked_allowed_hosts) && + ReadParam(m, iter, &p->worker_activation_sequence); } void ParamTraits<ExtensionMsg_Loaded_Params>::Log(const param_type& p, diff --git a/chromium/extensions/common/extension_messages.h b/chromium/extensions/common/extension_messages.h index 632f9846d82..f207638c2fa 100644 --- a/chromium/extensions/common/extension_messages.h +++ b/chromium/extensions/common/extension_messages.h @@ -356,7 +356,8 @@ struct ExtensionMsg_Loaded_Params { ExtensionMsg_Loaded_Params(); ~ExtensionMsg_Loaded_Params(); ExtensionMsg_Loaded_Params(const extensions::Extension* extension, - bool include_tab_permissions); + bool include_tab_permissions, + base::Optional<int> worker_activation_sequence); ExtensionMsg_Loaded_Params(ExtensionMsg_Loaded_Params&& other); ExtensionMsg_Loaded_Params& operator=(ExtensionMsg_Loaded_Params&& other); @@ -391,6 +392,10 @@ struct ExtensionMsg_Loaded_Params { // We keep this separate so that it can be used in logging. std::string id; + // If this extension is Service Worker based, then this contains the + // activation sequence of the extension. + base::Optional<int> worker_activation_sequence; + // Send creation flags so extension is initialized identically. int creation_flags; @@ -1060,16 +1065,18 @@ IPC_MESSAGE_CONTROL3(ExtensionHostMsg_DidInitializeServiceWorkerContext, // straightforward as it changes SW IPC ordering with respect of rest of // Chrome. // See https://crbug.com/879015#c4 for details. -IPC_MESSAGE_CONTROL4(ExtensionHostMsg_DidStartServiceWorkerContext, +IPC_MESSAGE_CONTROL5(ExtensionHostMsg_DidStartServiceWorkerContext, std::string /* extension_id */, + int /* activation_sequence */, GURL /* service_worker_scope */, int64_t /* service_worker_version_id */, int /* worker_thread_id */) // Tells the browser that an extension service worker context has been // destroyed. -IPC_MESSAGE_CONTROL4(ExtensionHostMsg_DidStopServiceWorkerContext, +IPC_MESSAGE_CONTROL5(ExtensionHostMsg_DidStopServiceWorkerContext, std::string /* extension_id */, + int /* activation_sequence */, GURL /* service_worker_scope */, int64_t /* service_worker_version_id */, int /* worker_thread_id */) diff --git a/chromium/extensions/common/extension_messages_unittest.cc b/chromium/extensions/common/extension_messages_unittest.cc index 208436da1fc..f99d59e6c3e 100644 --- a/chromium/extensions/common/extension_messages_unittest.cc +++ b/chromium/extensions/common/extension_messages_unittest.cc @@ -81,7 +81,7 @@ TEST(ExtensionMessageTypesTest, TestLoadedParams) { extension->permissions_data()->SetPolicyHostRestrictions( runtime_blocked_hosts, runtime_allowed_hosts); - ExtensionMsg_Loaded_Params params_in(extension.get(), true); + ExtensionMsg_Loaded_Params params_in(extension.get(), true, base::nullopt); EXPECT_EQ(extension->id(), params_in.id); { diff --git a/chromium/extensions/renderer/dispatcher.cc b/chromium/extensions/renderer/dispatcher.cc index f4c7df774de..456e51a3aba 100644 --- a/chromium/extensions/renderer/dispatcher.cc +++ b/chromium/extensions/renderer/dispatcher.cc @@ -451,8 +451,11 @@ void Dispatcher::WillEvaluateServiceWorkerOnWorkerThread( std::unique_ptr<IPCMessageSender> ipc_sender = IPCMessageSender::CreateWorkerThreadIPCMessageSender( worker_dispatcher, service_worker_version_id); + int worker_activation_sequence = + *RendererExtensionRegistry::Get()->GetWorkerActivationSequence( + extension->id()); worker_dispatcher->AddWorkerData( - service_worker_version_id, context, + service_worker_version_id, worker_activation_sequence, context, CreateBindingsSystem(std::move(ipc_sender))); worker_thread_util::SetWorkerContextProxy(context_proxy); @@ -986,6 +989,10 @@ void Dispatcher::OnLoaded( // consider making this a release CHECK. NOTREACHED(); } + if (param.worker_activation_sequence) { + extension_registry->SetWorkerActivationSequence( + extension, *param.worker_activation_sequence); + } if (param.uses_default_policy_blocked_allowed_hosts) { extension->permissions_data()->SetUsesDefaultHostRestrictions(); } else { diff --git a/chromium/extensions/renderer/renderer_extension_registry.cc b/chromium/extensions/renderer/renderer_extension_registry.cc index 4fb14dde74b..93936af5c24 100644 --- a/chromium/extensions/renderer/renderer_extension_registry.cc +++ b/chromium/extensions/renderer/renderer_extension_registry.cc @@ -7,6 +7,7 @@ #include "base/lazy_instance.h" #include "base/logging.h" #include "content/public/renderer/render_thread.h" +#include "extensions/common/manifest_handlers/background_info.h" namespace extensions { @@ -102,4 +103,24 @@ bool RendererExtensionRegistry::ExtensionBindingsAllowed( return extensions_.ExtensionBindingsAllowed(url); } +void RendererExtensionRegistry::SetWorkerActivationSequence( + const scoped_refptr<const Extension>& extension, + int worker_activation_sequence) { + DCHECK(content::RenderThread::Get()); + DCHECK(Contains(extension->id())); + DCHECK(BackgroundInfo::IsServiceWorkerBased(extension.get())); + + base::AutoLock lock(lock_); + worker_activation_sequences_[extension->id()] = worker_activation_sequence; +} + +base::Optional<int> RendererExtensionRegistry::GetWorkerActivationSequence( + const ExtensionId& extension_id) const { + base::AutoLock lock(lock_); + auto iter = worker_activation_sequences_.find(extension_id); + if (iter == worker_activation_sequences_.end()) + return base::nullopt; + return iter->second; +} + } // namespace extensions diff --git a/chromium/extensions/renderer/renderer_extension_registry.h b/chromium/extensions/renderer/renderer_extension_registry.h index 97c24bbf535..a62d26c9b17 100644 --- a/chromium/extensions/renderer/renderer_extension_registry.h +++ b/chromium/extensions/renderer/renderer_extension_registry.h @@ -10,7 +10,9 @@ #include <string> #include "base/macros.h" +#include "base/optional.h" #include "base/synchronization/lock.h" +#include "extensions/common/extension_id.h" #include "extensions/common/extension_set.h" class GURL; @@ -49,9 +51,23 @@ class RendererExtensionRegistry { ExtensionIdSet GetIDs() const; bool ExtensionBindingsAllowed(const GURL& url) const; + // ActivationSequence related methods. + // + // Sets ActivationSequence for a Service Worker based |extension|. + void SetWorkerActivationSequence( + const scoped_refptr<const Extension>& extension, + int worker_activation_sequence); + // Returns the current activation sequence for worker based extension with + // |extension_id|. Returns base::nullopt otherwise. + base::Optional<int> GetWorkerActivationSequence( + const ExtensionId& extension_id) const; + private: ExtensionSet extensions_; + // Maps extension id to ActivationSequence, for worker based extensions. + std::map<ExtensionId, int> worker_activation_sequences_; + mutable base::Lock lock_; DISALLOW_COPY_AND_ASSIGN(RendererExtensionRegistry); diff --git a/chromium/extensions/renderer/service_worker_data.cc b/chromium/extensions/renderer/service_worker_data.cc index 3c96f099e00..1f95bd2bb50 100644 --- a/chromium/extensions/renderer/service_worker_data.cc +++ b/chromium/extensions/renderer/service_worker_data.cc @@ -10,9 +10,11 @@ namespace extensions { ServiceWorkerData::ServiceWorkerData( int64_t service_worker_version_id, + int activation_sequence, ScriptContext* context, std::unique_ptr<NativeExtensionBindingsSystem> bindings_system) : service_worker_version_id_(service_worker_version_id), + activation_sequence_(activation_sequence), context_(context), v8_schema_registry_(new V8SchemaRegistry), bindings_system_(std::move(bindings_system)) {} diff --git a/chromium/extensions/renderer/service_worker_data.h b/chromium/extensions/renderer/service_worker_data.h index 1ee28d193a1..5cd152a378e 100644 --- a/chromium/extensions/renderer/service_worker_data.h +++ b/chromium/extensions/renderer/service_worker_data.h @@ -20,6 +20,7 @@ class ServiceWorkerData { public: ServiceWorkerData( int64_t service_worker_version_id, + int activation_sequence, ScriptContext* context, std::unique_ptr<NativeExtensionBindingsSystem> bindings_system); ~ServiceWorkerData(); @@ -31,10 +32,12 @@ class ServiceWorkerData { int64_t service_worker_version_id() const { return service_worker_version_id_; } + int activation_sequence() const { return activation_sequence_; } ScriptContext* context() const { return context_; } private: const int64_t service_worker_version_id_; + const int activation_sequence_; ScriptContext* const context_ = nullptr; std::unique_ptr<V8SchemaRegistry> v8_schema_registry_; diff --git a/chromium/extensions/renderer/worker_thread_dispatcher.cc b/chromium/extensions/renderer/worker_thread_dispatcher.cc index efbcdc376f9..9906e300109 100644 --- a/chromium/extensions/renderer/worker_thread_dispatcher.cc +++ b/chromium/extensions/renderer/worker_thread_dispatcher.cc @@ -264,12 +264,14 @@ void WorkerThreadDispatcher::OnDispatchOnDisconnect( void WorkerThreadDispatcher::AddWorkerData( int64_t service_worker_version_id, + int activation_sequence, ScriptContext* script_context, std::unique_ptr<NativeExtensionBindingsSystem> bindings_system) { ServiceWorkerData* data = g_data_tls.Pointer()->Get(); if (!data) { - ServiceWorkerData* new_data = new ServiceWorkerData( - service_worker_version_id, script_context, std::move(bindings_system)); + ServiceWorkerData* new_data = + new ServiceWorkerData(service_worker_version_id, activation_sequence, + script_context, std::move(bindings_system)); g_data_tls.Pointer()->Set(new_data); } @@ -300,8 +302,8 @@ void WorkerThreadDispatcher::DidStartContext( const int thread_id = content::WorkerThread::GetCurrentId(); DCHECK_NE(thread_id, kMainThreadId); Send(new ExtensionHostMsg_DidStartServiceWorkerContext( - data->context()->GetExtensionID(), service_worker_scope, - service_worker_version_id, thread_id)); + data->context()->GetExtensionID(), data->activation_sequence(), + service_worker_scope, service_worker_version_id, thread_id)); } void WorkerThreadDispatcher::DidStopContext(const GURL& service_worker_scope, @@ -311,8 +313,8 @@ void WorkerThreadDispatcher::DidStopContext(const GURL& service_worker_scope, DCHECK_NE(thread_id, kMainThreadId); DCHECK_EQ(service_worker_version_id, data->service_worker_version_id()); Send(new ExtensionHostMsg_DidStopServiceWorkerContext( - data->context()->GetExtensionID(), service_worker_scope, - service_worker_version_id, thread_id)); + data->context()->GetExtensionID(), data->activation_sequence(), + service_worker_scope, service_worker_version_id, thread_id)); } void WorkerThreadDispatcher::RemoveWorkerData( diff --git a/chromium/extensions/renderer/worker_thread_dispatcher.h b/chromium/extensions/renderer/worker_thread_dispatcher.h index dc341bb6db1..f13874ef9e8 100644 --- a/chromium/extensions/renderer/worker_thread_dispatcher.h +++ b/chromium/extensions/renderer/worker_thread_dispatcher.h @@ -63,6 +63,7 @@ class WorkerThreadDispatcher : public content::RenderThreadObserver, void AddWorkerData( int64_t service_worker_version_id, + int activation_sequence, ScriptContext* script_context, std::unique_ptr<NativeExtensionBindingsSystem> bindings_system); void RemoveWorkerData(int64_t service_worker_version_id); |