Browse Source

fix: navigator.setAppBadge/clearAppBadge from a service worker (#28011)

Co-authored-by: John Kleinschmidt <[email protected]>
trop[bot] 4 years ago
parent
commit
f13ddca55f

+ 21 - 0
shell/browser/badging/badge_manager.cc

@@ -47,6 +47,27 @@ void BadgeManager::BindFrameReceiver(
                                 std::move(context));
 }
 
+void BadgeManager::BindServiceWorkerReceiver(
+    content::RenderProcessHost* service_worker_process_host,
+    const GURL& service_worker_scope,
+    mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  auto* browser_context = service_worker_process_host->GetBrowserContext();
+
+  auto* badge_manager =
+      badging::BadgeManagerFactory::GetInstance()->GetForBrowserContext(
+          browser_context);
+  if (!badge_manager)
+    return;
+
+  auto context = std::make_unique<BadgeManager::ServiceWorkerBindingContext>(
+      service_worker_process_host->GetID(), service_worker_scope);
+
+  badge_manager->receivers_.Add(badge_manager, std::move(receiver),
+                                std::move(context));
+}
+
 std::string BadgeManager::GetBadgeString(base::Optional<int> badge_content) {
   if (!badge_content)
     return "•";

+ 19 - 0
shell/browser/badging/badge_manager.h

@@ -37,6 +37,10 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
   static void BindFrameReceiver(
       content::RenderFrameHost* frame,
       mojo::PendingReceiver<blink::mojom::BadgeService> receiver);
+  static void BindServiceWorkerReceiver(
+      content::RenderProcessHost* service_worker_process_host,
+      const GURL& service_worker_scope,
+      mojo::PendingReceiver<blink::mojom::BadgeService> receiver);
 
   // Determines the text to put on the badge based on some badge_content.
   static std::string GetBadgeString(base::Optional<int> badge_content);
@@ -66,6 +70,21 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
     int frame_id_;
   };
 
+  // The BindingContext for ServiceWorkerGlobalScope execution contexts.
+  class ServiceWorkerBindingContext final : public BindingContext {
+   public:
+    ServiceWorkerBindingContext(int process_id, const GURL& scope)
+        : process_id_(process_id), scope_(scope) {}
+    ~ServiceWorkerBindingContext() override = default;
+
+    int GetProcessId() { return process_id_; }
+    GURL GetScope() { return scope_; }
+
+   private:
+    int process_id_;
+    GURL scope_;
+  };
+
   // blink::mojom::BadgeService:
   // Note: These are private to stop them being called outside of mojo as they
   // require a mojo binding context.

+ 8 - 0
shell/browser/electron_browser_client.cc

@@ -1781,4 +1781,12 @@ content::BluetoothDelegate* ElectronBrowserClient::GetBluetoothDelegate() {
   return bluetooth_delegate_.get();
 }
 
+void ElectronBrowserClient::BindBadgeServiceReceiverFromServiceWorker(
+    content::RenderProcessHost* service_worker_process_host,
+    const GURL& service_worker_scope,
+    mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
+  badging::BadgeManager::BindServiceWorkerReceiver(
+      service_worker_process_host, service_worker_scope, std::move(receiver));
+}
+
 }  // namespace electron

+ 4 - 0
shell/browser/electron_browser_client.h

@@ -73,6 +73,10 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
   void RegisterBrowserInterfaceBindersForFrame(
       content::RenderFrameHost* render_frame_host,
       mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override;
+  void BindBadgeServiceReceiverFromServiceWorker(
+      content::RenderProcessHost* service_worker_process_host,
+      const GURL& service_worker_scope,
+      mojo::PendingReceiver<blink::mojom::BadgeService> receiver) override;
 #if defined(OS_LINUX)
   void GetAdditionalMappedFilesForChildProcess(
       const base::CommandLine& command_line,

+ 86 - 26
spec-main/chromium-spec.ts

@@ -1576,12 +1576,6 @@ describe('navigator.clipboard', () => {
 
 ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.setAppBadge/clearAppBadge', () => {
   let w: BrowserWindow;
-  before(async () => {
-    w = new BrowserWindow({
-      show: false
-    });
-    await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
-  });
 
   const expectedBadgeCount = 42;
 
@@ -1601,30 +1595,96 @@ ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.se
     return badgeCount;
   }
 
-  after(() => {
-    app.badgeCount = 0;
-    closeAllWindows();
-  });
+  describe('in the renderer', () => {
+    before(async () => {
+      w = new BrowserWindow({
+        show: false
+      });
+      await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
+    });
 
-  it('setAppBadge can set a numerical value', async () => {
-    const result = await fireAppBadgeAction('set', expectedBadgeCount);
-    expect(result).to.equal('success');
-    expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
-  });
+    after(() => {
+      app.badgeCount = 0;
+      closeAllWindows();
+    });
 
-  it('setAppBadge can set an empty(dot) value', async () => {
-    const result = await fireAppBadgeAction('set');
-    expect(result).to.equal('success');
-    expect(waitForBadgeCount(0)).to.eventually.equal(0);
+    it('setAppBadge can set a numerical value', async () => {
+      const result = await fireAppBadgeAction('set', expectedBadgeCount);
+      expect(result).to.equal('success');
+      expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
+    });
+
+    it('setAppBadge can set an empty(dot) value', async () => {
+      const result = await fireAppBadgeAction('set');
+      expect(result).to.equal('success');
+      expect(waitForBadgeCount(0)).to.eventually.equal(0);
+    });
+
+    it('clearAppBadge can clear a value', async () => {
+      let result = await fireAppBadgeAction('set', expectedBadgeCount);
+      expect(result).to.equal('success');
+      expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
+      result = await fireAppBadgeAction('clear');
+      expect(result).to.equal('success');
+      expect(waitForBadgeCount(0)).to.eventually.equal(0);
+    });
   });
 
-  it('clearAppBadge can clear a value', async () => {
-    let result = await fireAppBadgeAction('set', expectedBadgeCount);
-    expect(result).to.equal('success');
-    expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
-    result = await fireAppBadgeAction('clear');
-    expect(result).to.equal('success');
-    expect(waitForBadgeCount(0)).to.eventually.equal(0);
+  describe('in a service worker', () => {
+    beforeEach(async () => {
+      w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          nodeIntegration: true,
+          partition: 'sw-file-scheme-spec',
+          contextIsolation: false
+        }
+      });
+    });
+
+    afterEach(() => {
+      app.badgeCount = 0;
+      closeAllWindows();
+    });
+
+    it('setAppBadge can be called in a ServiceWorker', (done) => {
+      w.webContents.on('ipc-message', (event, channel, message) => {
+        if (channel === 'reload') {
+          w.webContents.reload();
+        } else if (channel === 'error') {
+          done(message);
+        } else if (channel === 'response') {
+          expect(message).to.equal('SUCCESS setting app badge');
+          expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
+          session.fromPartition('sw-file-scheme-spec').clearStorageData({
+            storages: ['serviceworkers']
+          }).then(() => done());
+        }
+      });
+      w.webContents.on('crashed', () => done(new Error('WebContents crashed.')));
+      w.loadFile(path.join(fixturesPath, 'pages', 'service-worker', 'badge-index.html'), { search: '?setBadge' });
+    });
+
+    it('clearAppBadge can be called in a ServiceWorker', (done) => {
+      w.webContents.on('ipc-message', (event, channel, message) => {
+        if (channel === 'reload') {
+          w.webContents.reload();
+        } else if (channel === 'setAppBadge') {
+          expect(message).to.equal('SUCCESS setting app badge');
+          expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
+        } else if (channel === 'error') {
+          done(message);
+        } else if (channel === 'response') {
+          expect(message).to.equal('SUCCESS clearing app badge');
+          expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
+          session.fromPartition('sw-file-scheme-spec').clearStorageData({
+            storages: ['serviceworkers']
+          }).then(() => done());
+        }
+      });
+      w.webContents.on('crashed', () => done(new Error('WebContents crashed.')));
+      w.loadFile(path.join(fixturesPath, 'pages', 'service-worker', 'badge-index.html'), { search: '?clearBadge' });
+    });
   });
 });
 

+ 31 - 0
spec/fixtures/pages/service-worker/badge-index.html

@@ -0,0 +1,31 @@
+<script>
+  const ipcRenderer = require('electron').ipcRenderer;
+  let search = (new URL(document.location)).search;  
+
+  async function testIt() {
+    if (search === '?clearBadge') {
+      try {
+        await navigator.setAppBadge(42);
+        ipcRenderer.send('setAppBadge','SUCCESS setting app badge');
+      } catch (error) {
+        ipcRenderer.send('error', `${error.message}\n${error.stack}`);
+      }
+    }
+    navigator.serviceWorker.register('service-worker-badge.js', {scope: './'}).then(function() {
+      if (navigator.serviceWorker.controller) {
+        var xhr = new XMLHttpRequest();
+        xhr.open('GET', 'http://dummy/echo'+search);
+        xhr.setRequestHeader('X-Mock-Response', 'yes');
+        xhr.addEventListener('load', function() {
+          ipcRenderer.send('response', xhr.responseText);
+        });
+        xhr.send();
+      } else {
+        ipcRenderer.send('reload');
+      }
+    }).catch(function(error) {
+      ipcRenderer.send('error', `${error.message}\n${error.stack}`);
+    })
+  }
+  testIt();
+</script>

+ 33 - 0
spec/fixtures/pages/service-worker/service-worker-badge.js

@@ -0,0 +1,33 @@
+self.addEventListener('fetch', async function (event) {
+  const requestUrl = new URL(event.request.url);
+  let responseTxt;
+  if (requestUrl.pathname === '/echo' &&
+    event.request.headers.has('X-Mock-Response')) {
+    if (requestUrl.search === '?setBadge') {
+      if (navigator.setAppBadge()) {
+        try {
+          await navigator.setAppBadge(42);
+          responseTxt = 'SUCCESS setting app badge';
+          await navigator.clearAppBadge();
+        } catch (ex) {
+          responseTxt = 'ERROR setting app badge ' + ex;
+        }
+      } else {
+        responseTxt = 'ERROR navigator.setAppBadge is not available in ServiceWorker!';
+      }
+    } else if (requestUrl.search === '?clearBadge') {
+      if (navigator.clearAppBadge()) {
+        try {
+          await navigator.clearAppBadge();
+          responseTxt = 'SUCCESS clearing app badge';
+        } catch (ex) {
+          responseTxt = 'ERROR clearing app badge ' + ex;
+        }
+      } else {
+        responseTxt = 'ERROR navigator.clearAppBadge is not available in ServiceWorker!';
+      }
+    }
+    const mockResponse = new Response(responseTxt);
+    event.respondWith(mockResponse);
+  }
+});