diff --git a/shell/browser/badging/badge_manager.cc b/shell/browser/badging/badge_manager.cc index 4cbd5595b01d..d99be915ca9e 100755 --- a/shell/browser/badging/badge_manager.cc +++ b/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 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( + 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 badge_content) { if (!badge_content) return "•"; diff --git a/shell/browser/badging/badge_manager.h b/shell/browser/badging/badge_manager.h index a4cb546b986f..5df9679b5d14 100644 --- a/shell/browser/badging/badge_manager.h +++ b/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 receiver); + static void BindServiceWorkerReceiver( + content::RenderProcessHost* service_worker_process_host, + const GURL& service_worker_scope, + mojo::PendingReceiver receiver); // Determines the text to put on the badge based on some badge_content. static std::string GetBadgeString(base::Optional 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. diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index 7ae833bb084a..80240d148f71 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -1776,4 +1776,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 receiver) { + badging::BadgeManager::BindServiceWorkerReceiver( + service_worker_process_host, service_worker_scope, std::move(receiver)); +} + } // namespace electron diff --git a/shell/browser/electron_browser_client.h b/shell/browser/electron_browser_client.h index abda113521ee..18da46835c25 100644 --- a/shell/browser/electron_browser_client.h +++ b/shell/browser/electron_browser_client.h @@ -73,6 +73,10 @@ class ElectronBrowserClient : public content::ContentBrowserClient, void RegisterBrowserInterfaceBindersForFrame( content::RenderFrameHost* render_frame_host, mojo::BinderMapWithContext* map) override; + void BindBadgeServiceReceiverFromServiceWorker( + content::RenderProcessHost* service_worker_process_host, + const GURL& service_worker_scope, + mojo::PendingReceiver receiver) override; #if defined(OS_LINUX) void GetAdditionalMappedFilesForChildProcess( const base::CommandLine& command_line, diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index 7d30afddb033..f8a28f600a9a 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -1578,12 +1578,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; @@ -1603,30 +1597,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')); + }); + + after(() => { + app.badgeCount = 0; + closeAllWindows(); + }); + + 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('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); - }); + describe('in a service worker', () => { + beforeEach(async () => { + w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: true, + partition: 'sw-file-scheme-spec', + contextIsolation: false + } + }); + }); - 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); - }); + afterEach(() => { + app.badgeCount = 0; + closeAllWindows(); + }); - 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('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' }); + }); }); }); diff --git a/spec/fixtures/pages/service-worker/badge-index.html b/spec/fixtures/pages/service-worker/badge-index.html new file mode 100644 index 000000000000..480a60a9aee8 --- /dev/null +++ b/spec/fixtures/pages/service-worker/badge-index.html @@ -0,0 +1,31 @@ + diff --git a/spec/fixtures/pages/service-worker/service-worker-badge.js b/spec/fixtures/pages/service-worker/service-worker-badge.js new file mode 100644 index 000000000000..84215bfb3323 --- /dev/null +++ b/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); + } +});