From 3250ef551c837d30c87d98e496f973b19647af1a Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Thu, 11 Feb 2021 10:58:03 -0500 Subject: [PATCH] feat: support registering MV3 extension service workers (#27562) * feat: support registering MV3 extension service workers * feat: load chrome extension APIs in worker context * feat: add more ContentRendererClient service worker overrides * fix: lint error * refactor: emit object for 'registration-completed' * docs: clarify when registration-completed emits --- docs/api/service-workers.md | 10 ++++ shell/app/electron_content_client.cc | 10 +++- .../electron_api_service_worker_context.cc | 8 +++ .../api/electron_api_service_worker_context.h | 1 + .../electron_extensions_renderer_client.cc | 24 +++++++- shell/renderer/renderer_client_base.cc | 60 ++++++++++++++++++- shell/renderer/renderer_client_base.h | 22 +++++++ spec-main/extensions-spec.ts | 12 ++++ .../mv3-service-worker/background.js | 1 + .../mv3-service-worker/manifest.json | 10 ++++ 10 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 spec-main/fixtures/extensions/mv3-service-worker/background.js create mode 100644 spec-main/fixtures/extensions/mv3-service-worker/manifest.json diff --git a/docs/api/service-workers.md b/docs/api/service-workers.md index b3d9351200a0..a18133df192f 100644 --- a/docs/api/service-workers.md +++ b/docs/api/service-workers.md @@ -45,6 +45,16 @@ Returns: Emitted when a service worker logs something to the console. +#### Event: 'registration-completed' + +Returns: + +* `event` Event +* `details` Object - Information about the registered service worker + * `scope` String - The base URL that a service worker is registered for + +Emitted when a service worker has been registered. Can occur after a call to [`navigator.serviceWorker.register('/sw.js')`](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register) successfully resolves or when a Chrome extension is loaded. + ### Instance Methods The following methods are available on instances of `ServiceWorkers`: diff --git a/shell/app/electron_content_client.cc b/shell/app/electron_content_client.cc index 49a436b00b2d..530637ac4d54 100644 --- a/shell/app/electron_content_client.cc +++ b/shell/app/electron_content_client.cc @@ -207,7 +207,15 @@ void ElectronContentClient::AddAdditionalSchemes(Schemes* schemes) { } schemes->service_worker_schemes.emplace_back(url::kFileScheme); - schemes->standard_schemes.emplace_back(extensions::kExtensionScheme); + +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + schemes->standard_schemes.push_back(extensions::kExtensionScheme); + schemes->savable_schemes.push_back(extensions::kExtensionScheme); + schemes->secure_schemes.push_back(extensions::kExtensionScheme); + schemes->service_worker_schemes.push_back(extensions::kExtensionScheme); + schemes->cors_enabled_schemes.push_back(extensions::kExtensionScheme); + schemes->csp_bypassing_schemes.push_back(extensions::kExtensionScheme); +#endif } void ElectronContentClient::AddPepperPlugins( diff --git a/shell/browser/api/electron_api_service_worker_context.cc b/shell/browser/api/electron_api_service_worker_context.cc index edf90f5a87f5..459b859e30f5 100644 --- a/shell/browser/api/electron_api_service_worker_context.cc +++ b/shell/browser/api/electron_api_service_worker_context.cc @@ -15,6 +15,7 @@ #include "gin/object_template_builder.h" #include "shell/browser/electron_browser_context.h" #include "shell/browser/javascript_environment.h" +#include "shell/common/gin_converters/gurl_converter.h" #include "shell/common/gin_converters/value_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/function_template_extensions.h" @@ -102,6 +103,13 @@ void ServiceWorkerContext::OnReportConsoleMessage( .Build()); } +void ServiceWorkerContext::OnRegistrationCompleted(const GURL& scope) { + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + Emit("registration-completed", + gin::DataObjectBuilder(isolate).Set("scope", scope).Build()); +} + void ServiceWorkerContext::OnDestruct(content::ServiceWorkerContext* context) { if (context == service_worker_context_) { delete this; diff --git a/shell/browser/api/electron_api_service_worker_context.h b/shell/browser/api/electron_api_service_worker_context.h index 108073eafffb..c528b0089bb8 100644 --- a/shell/browser/api/electron_api_service_worker_context.h +++ b/shell/browser/api/electron_api_service_worker_context.h @@ -34,6 +34,7 @@ class ServiceWorkerContext void OnReportConsoleMessage(int64_t version_id, const GURL& scope, const content::ConsoleMessage& message) override; + void OnRegistrationCompleted(const GURL& scope) override; void OnDestruct(content::ServiceWorkerContext* context) override; // gin::Wrappable diff --git a/shell/renderer/extensions/electron_extensions_renderer_client.cc b/shell/renderer/extensions/electron_extensions_renderer_client.cc index b003ea50d972..b0c51f4226c1 100644 --- a/shell/renderer/extensions/electron_extensions_renderer_client.cc +++ b/shell/renderer/extensions/electron_extensions_renderer_client.cc @@ -4,7 +4,11 @@ #include "shell/renderer/extensions/electron_extensions_renderer_client.h" +#include + #include "content/public/renderer/render_thread.h" +#include "extensions/common/constants.h" +#include "extensions/common/manifest_handlers/background_info.h" #include "extensions/renderer/dispatcher.h" #include "shell/common/world_ids.h" #include "shell/renderer/extensions/electron_extensions_dispatcher_delegate.h" @@ -35,8 +39,24 @@ extensions::Dispatcher* ElectronExtensionsRendererClient::GetDispatcher() { bool ElectronExtensionsRendererClient:: ExtensionAPIEnabledForServiceWorkerScript(const GURL& scope, const GURL& script_url) const { - // TODO(nornagon): adapt logic from chrome's version - return true; + if (!script_url.SchemeIs(extensions::kExtensionScheme)) + return false; + + const extensions::Extension* extension = + extensions::RendererExtensionRegistry::Get()->GetExtensionOrAppByURL( + script_url); + + if (!extension || + !extensions::BackgroundInfo::IsServiceWorkerBased(extension)) + return false; + + if (scope != extension->url()) + return false; + + const std::string& sw_script = + extensions::BackgroundInfo::GetBackgroundServiceWorkerScript(extension); + + return extension->GetResourceURL(sw_script) == script_url; } bool ElectronExtensionsRendererClient::AllowPopup() { diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc index f8e865a9b3d5..9189c3f878a9 100644 --- a/shell/renderer/renderer_client_base.cc +++ b/shell/renderer/renderer_client_base.cc @@ -116,9 +116,6 @@ RendererClientBase::RendererClientBase() { ParseSchemesCLISwitch(command_line, switches::kSecureSchemes); for (const std::string& scheme : secure_schemes_list) url::AddSecureScheme(scheme.data()); - // In Chrome we should set extension's origins to match the pages they can - // work on, but in Electron currently we just let extensions do anything. - url::AddSecureScheme(extensions::kExtensionScheme); // We rely on the unique process host id which is notified to the // renderer process via command line switch from the content layer, // if this switch is removed from the content layer for some reason, @@ -411,6 +408,63 @@ void RendererClientBase::RunScriptsAtDocumentEnd( #endif } +bool RendererClientBase::AllowScriptExtensionForServiceWorker( + const url::Origin& script_origin) { +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + return script_origin.scheme() == extensions::kExtensionScheme; +#else + return false; +#endif +} + +void RendererClientBase::DidInitializeServiceWorkerContextOnWorkerThread( + blink::WebServiceWorkerContextProxy* context_proxy, + const GURL& service_worker_scope, + const GURL& script_url) { +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + extensions_renderer_client_->GetDispatcher() + ->DidInitializeServiceWorkerContextOnWorkerThread( + context_proxy, service_worker_scope, script_url); +#endif +} + +void RendererClientBase::WillEvaluateServiceWorkerOnWorkerThread( + blink::WebServiceWorkerContextProxy* context_proxy, + v8::Local v8_context, + int64_t service_worker_version_id, + const GURL& service_worker_scope, + const GURL& script_url) { +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + extensions_renderer_client_->GetDispatcher() + ->WillEvaluateServiceWorkerOnWorkerThread( + context_proxy, v8_context, service_worker_version_id, + service_worker_scope, script_url); +#endif +} + +void RendererClientBase::DidStartServiceWorkerContextOnWorkerThread( + int64_t service_worker_version_id, + const GURL& service_worker_scope, + const GURL& script_url) { +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + extensions_renderer_client_->GetDispatcher() + ->DidStartServiceWorkerContextOnWorkerThread( + service_worker_version_id, service_worker_scope, script_url); +#endif +} + +void RendererClientBase::WillDestroyServiceWorkerContextOnWorkerThread( + v8::Local context, + int64_t service_worker_version_id, + const GURL& service_worker_scope, + const GURL& script_url) { +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + extensions_renderer_client_->GetDispatcher() + ->WillDestroyServiceWorkerContextOnWorkerThread( + context, service_worker_version_id, service_worker_scope, script_url); +#endif +} + v8::Local RendererClientBase::GetContext( blink::WebLocalFrame* frame, v8::Isolate* isolate) const { diff --git a/shell/renderer/renderer_client_base.h b/shell/renderer/renderer_client_base.h index 6332b0cf2670..14b766b8cfdd 100644 --- a/shell/renderer/renderer_client_base.h +++ b/shell/renderer/renderer_client_base.h @@ -117,6 +117,28 @@ class RendererClientBase : public content::ContentRendererClient void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override; void RunScriptsAtDocumentIdle(content::RenderFrame* render_frame) override; + bool AllowScriptExtensionForServiceWorker( + const url::Origin& script_origin) override; + void DidInitializeServiceWorkerContextOnWorkerThread( + blink::WebServiceWorkerContextProxy* context_proxy, + const GURL& service_worker_scope, + const GURL& script_url) override; + void WillEvaluateServiceWorkerOnWorkerThread( + blink::WebServiceWorkerContextProxy* context_proxy, + v8::Local v8_context, + int64_t service_worker_version_id, + const GURL& service_worker_scope, + const GURL& script_url) override; + void DidStartServiceWorkerContextOnWorkerThread( + int64_t service_worker_version_id, + const GURL& service_worker_scope, + const GURL& script_url) override; + void WillDestroyServiceWorkerContextOnWorkerThread( + v8::Local context, + int64_t service_worker_version_id, + const GURL& service_worker_scope, + const GURL& script_url) override; + protected: #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) // app_shell embedders may need custom extensions client interfaces. diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 62e0da59d4d7..039500cb82e6 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -656,4 +656,16 @@ describe('chrome extensions', () => { expect(textContent).to.equal('script loaded ok\n'); }); }); + + describe('manifest v3', () => { + it('registers background service worker', async () => { + const customSession = session.fromPartition(`persist:${uuid.v4()}`); + const registrationPromise = new Promise(resolve => { + customSession.serviceWorkers.once('registration-completed', (event, { scope }) => resolve(scope)); + }); + const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'mv3-service-worker')); + const scope = await registrationPromise; + expect(scope).equals(extension.url); + }); + }); }); diff --git a/spec-main/fixtures/extensions/mv3-service-worker/background.js b/spec-main/fixtures/extensions/mv3-service-worker/background.js new file mode 100644 index 000000000000..c4d4a3003587 --- /dev/null +++ b/spec-main/fixtures/extensions/mv3-service-worker/background.js @@ -0,0 +1 @@ +console.log('service worker installed'); diff --git a/spec-main/fixtures/extensions/mv3-service-worker/manifest.json b/spec-main/fixtures/extensions/mv3-service-worker/manifest.json new file mode 100644 index 000000000000..9e11c2f1e531 --- /dev/null +++ b/spec-main/fixtures/extensions/mv3-service-worker/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "MV3 Service Worker", + "description": "Test for extension service worker support.", + "version": "1.0", + "manifest_version": 3, + "background": { + "service_worker": "background.js" + }, + "permissions": ["scripting"] +}