Browse Source

fix: make sure service worker scheme is registered with allowServiceWorkers (#28326)

* Fix custom scheme not registered as service worker scheme

* ServiceWorker loaders do not have WebContents associated

* Add test for service worker

* Revert "Fix custom scheme not registered as service worker scheme"

This reverts commit a249235b220a0edcfcb906e0b3b3c0486ece73a6.

* Add scheme to ServiceWorkerSchemes
Cheng Zhao 4 years ago
parent
commit
1e9e2f8cf6

+ 8 - 0
shell/browser/api/electron_api_protocol.cc

@@ -10,6 +10,7 @@
 
 #include "base/command_line.h"
 #include "base/stl_util.h"
+#include "content/common/url_schemes.h"
 #include "content/public/browser/child_process_security_policy.h"
 #include "gin/object_template_builder.h"
 #include "shell/browser/browser.h"
@@ -124,6 +125,13 @@ void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower,
     }
     if (custom_scheme.options.allowServiceWorkers) {
       service_worker_schemes.push_back(custom_scheme.scheme);
+      // There is no API to add service worker scheme, but there is an API to
+      // return const reference to the schemes vector.
+      // If in future the API is changed to return a copy instead of reference,
+      // the compilation will fail, and we should add a patch at that time.
+      auto& mutable_schemes = const_cast<std::vector<std::string>&>(
+          content::GetServiceWorkerSchemes());
+      mutable_schemes.push_back(custom_scheme.scheme);
     }
     if (custom_scheme.options.stream) {
       g_streaming_schemes.push_back(custom_scheme.scheme);

+ 13 - 9
shell/browser/electron_browser_client.cc

@@ -1352,22 +1352,26 @@ void ElectronBrowserClient::RegisterNonNetworkSubresourceURLLoaderFactories(
     int render_process_id,
     int render_frame_id,
     NonNetworkURLLoaderFactoryMap* factories) {
-  content::RenderFrameHost* frame_host =
-      content::RenderFrameHost::FromID(render_process_id, render_frame_id);
-  content::WebContents* web_contents =
-      content::WebContents::FromRenderFrameHost(frame_host);
+  auto* render_process_host =
+      content::RenderProcessHost::FromID(render_process_id);
+  DCHECK(render_process_host);
+  if (!render_process_host || !render_process_host->GetBrowserContext())
+    return;
+
+  ProtocolRegistry::FromBrowserContext(render_process_host->GetBrowserContext())
+      ->RegisterURLLoaderFactories(URLLoaderFactoryType::kDocumentSubResource,
+                                   factories);
 
-  if (web_contents) {
-    ProtocolRegistry::FromBrowserContext(web_contents->GetBrowserContext())
-        ->RegisterURLLoaderFactories(URLLoaderFactoryType::kDocumentSubResource,
-                                     factories);
-  }
 #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
   auto factory = extensions::CreateExtensionURLLoaderFactory(render_process_id,
                                                              render_frame_id);
   if (factory)
     factories->emplace(extensions::kExtensionScheme, std::move(factory));
 
+  content::RenderFrameHost* frame_host =
+      content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(frame_host);
   if (!web_contents)
     return;
 

+ 31 - 0
spec-main/api-protocol-spec.ts

@@ -1,4 +1,5 @@
 import { expect } from 'chai';
+import { v4 } from 'uuid';
 import { protocol, webContents, WebContents, session, BrowserWindow, ipcMain } from 'electron/main';
 import { AddressInfo } from 'net';
 import * as ChildProcess from 'child_process';
@@ -724,6 +725,36 @@ describe('protocol module', () => {
     });
   });
 
+  describe('protocol.registerSchemesAsPrivileged allowServiceWorkers', () => {
+    const { serviceWorkerScheme } = global as any;
+    protocol.registerStringProtocol(serviceWorkerScheme, (request, cb) => {
+      if (request.url.endsWith('.js')) {
+        cb({
+          mimeType: 'text/javascript',
+          charset: 'utf-8',
+          data: 'console.log("Loaded")'
+        });
+      } else {
+        cb({
+          mimeType: 'text/html',
+          charset: 'utf-8',
+          data: '<!DOCTYPE html>'
+        });
+      }
+    });
+    after(() => protocol.unregisterProtocol(serviceWorkerScheme));
+
+    it('should fail when registering invalid service worker', async () => {
+      await contents.loadURL(`${serviceWorkerScheme}://${v4()}.com`);
+      await expect(contents.executeJavaScript(`navigator.serviceWorker.register('${v4()}.notjs', {scope: './'})`)).to.be.rejected();
+    });
+
+    it('should be able to register service worker for custom scheme', async () => {
+      await contents.loadURL(`${serviceWorkerScheme}://${v4()}.com`);
+      await contents.executeJavaScript(`navigator.serviceWorker.register('${v4()}.js', {scope: './'})`);
+    });
+  });
+
   describe.skip('protocol.registerSchemesAsPrivileged standard', () => {
     const standardScheme = (global as any).standardScheme;
     const origin = `${standardScheme}://fake-host`;

+ 2 - 0
spec-main/index.js

@@ -34,9 +34,11 @@ app.commandLine.appendSwitch('use-fake-device-for-media-stream');
 
 global.standardScheme = 'app';
 global.zoomScheme = 'zoom';
+global.serviceWorkerScheme = 'sw';
 protocol.registerSchemesAsPrivileged([
   { scheme: global.standardScheme, privileges: { standard: true, secure: true, stream: false } },
   { scheme: global.zoomScheme, privileges: { standard: true, secure: true } },
+  { scheme: global.serviceWorkerScheme, privileges: { allowServiceWorkers: true, standard: true, secure: true } },
   { scheme: 'cors-blob', privileges: { corsEnabled: true, supportFetchAPI: true } },
   { scheme: 'cors', privileges: { corsEnabled: true, supportFetchAPI: true } },
   { scheme: 'no-cors', privileges: { supportFetchAPI: true } },