Browse Source

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
Samuel Maddock 4 years ago
parent
commit
3250ef551c

+ 10 - 0
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`:

+ 9 - 1
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(

+ 8 - 0
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;

+ 1 - 0
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

+ 22 - 2
shell/renderer/extensions/electron_extensions_renderer_client.cc

@@ -4,7 +4,11 @@
 
 #include "shell/renderer/extensions/electron_extensions_renderer_client.h"
 
+#include <string>
+
 #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() {

+ 57 - 3
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> 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<v8::Context> 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<v8::Context> RendererClientBase::GetContext(
     blink::WebLocalFrame* frame,
     v8::Isolate* isolate) const {

+ 22 - 0
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> 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<v8::Context> 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.

+ 12 - 0
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<string>(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);
+    });
+  });
 });

+ 1 - 0
spec-main/fixtures/extensions/mv3-service-worker/background.js

@@ -0,0 +1 @@
+console.log('service worker installed');

+ 10 - 0
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"]
+}