Skip to content

Commit afb7d9f

Browse files
authored
fix: navigator.setAppBadge/clearAppBadge from a service worker (electron#27950)
1 parent d92bab0 commit afb7d9f

File tree

7 files changed

+202
-26
lines changed

7 files changed

+202
-26
lines changed

shell/browser/badging/badge_manager.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,27 @@ void BadgeManager::BindFrameReceiver(
4747
std::move(context));
4848
}
4949

50+
void BadgeManager::BindServiceWorkerReceiver(
51+
content::RenderProcessHost* service_worker_process_host,
52+
const GURL& service_worker_scope,
53+
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
54+
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
55+
56+
auto* browser_context = service_worker_process_host->GetBrowserContext();
57+
58+
auto* badge_manager =
59+
badging::BadgeManagerFactory::GetInstance()->GetForBrowserContext(
60+
browser_context);
61+
if (!badge_manager)
62+
return;
63+
64+
auto context = std::make_unique<BadgeManager::ServiceWorkerBindingContext>(
65+
service_worker_process_host->GetID(), service_worker_scope);
66+
67+
badge_manager->receivers_.Add(badge_manager, std::move(receiver),
68+
std::move(context));
69+
}
70+
5071
std::string BadgeManager::GetBadgeString(base::Optional<int> badge_content) {
5172
if (!badge_content)
5273
return "";

shell/browser/badging/badge_manager.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
3737
static void BindFrameReceiver(
3838
content::RenderFrameHost* frame,
3939
mojo::PendingReceiver<blink::mojom::BadgeService> receiver);
40+
static void BindServiceWorkerReceiver(
41+
content::RenderProcessHost* service_worker_process_host,
42+
const GURL& service_worker_scope,
43+
mojo::PendingReceiver<blink::mojom::BadgeService> receiver);
4044

4145
// Determines the text to put on the badge based on some badge_content.
4246
static std::string GetBadgeString(base::Optional<int> badge_content);
@@ -66,6 +70,21 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
6670
int frame_id_;
6771
};
6872

73+
// The BindingContext for ServiceWorkerGlobalScope execution contexts.
74+
class ServiceWorkerBindingContext final : public BindingContext {
75+
public:
76+
ServiceWorkerBindingContext(int process_id, const GURL& scope)
77+
: process_id_(process_id), scope_(scope) {}
78+
~ServiceWorkerBindingContext() override = default;
79+
80+
int GetProcessId() { return process_id_; }
81+
GURL GetScope() { return scope_; }
82+
83+
private:
84+
int process_id_;
85+
GURL scope_;
86+
};
87+
6988
// blink::mojom::BadgeService:
7089
// Note: These are private to stop them being called outside of mojo as they
7190
// require a mojo binding context.

shell/browser/electron_browser_client.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,4 +1776,12 @@ content::BluetoothDelegate* ElectronBrowserClient::GetBluetoothDelegate() {
17761776
return bluetooth_delegate_.get();
17771777
}
17781778

1779+
void ElectronBrowserClient::BindBadgeServiceReceiverFromServiceWorker(
1780+
content::RenderProcessHost* service_worker_process_host,
1781+
const GURL& service_worker_scope,
1782+
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
1783+
badging::BadgeManager::BindServiceWorkerReceiver(
1784+
service_worker_process_host, service_worker_scope, std::move(receiver));
1785+
}
1786+
17791787
} // namespace electron

shell/browser/electron_browser_client.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
7373
void RegisterBrowserInterfaceBindersForFrame(
7474
content::RenderFrameHost* render_frame_host,
7575
mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override;
76+
void BindBadgeServiceReceiverFromServiceWorker(
77+
content::RenderProcessHost* service_worker_process_host,
78+
const GURL& service_worker_scope,
79+
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) override;
7680
#if defined(OS_LINUX)
7781
void GetAdditionalMappedFilesForChildProcess(
7882
const base::CommandLine& command_line,

spec-main/chromium-spec.ts

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,12 +1578,6 @@ describe('navigator.clipboard', () => {
15781578

15791579
ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.setAppBadge/clearAppBadge', () => {
15801580
let w: BrowserWindow;
1581-
before(async () => {
1582-
w = new BrowserWindow({
1583-
show: false
1584-
});
1585-
await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
1586-
});
15871581

15881582
const expectedBadgeCount = 42;
15891583

@@ -1603,30 +1597,96 @@ ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.se
16031597
return badgeCount;
16041598
}
16051599

1606-
after(() => {
1607-
app.badgeCount = 0;
1608-
closeAllWindows();
1609-
});
1600+
describe('in the renderer', () => {
1601+
before(async () => {
1602+
w = new BrowserWindow({
1603+
show: false
1604+
});
1605+
await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
1606+
});
16101607

1611-
it('setAppBadge can set a numerical value', async () => {
1612-
const result = await fireAppBadgeAction('set', expectedBadgeCount);
1613-
expect(result).to.equal('success');
1614-
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
1615-
});
1608+
after(() => {
1609+
app.badgeCount = 0;
1610+
closeAllWindows();
1611+
});
16161612

1617-
it('setAppBadge can set an empty(dot) value', async () => {
1618-
const result = await fireAppBadgeAction('set');
1619-
expect(result).to.equal('success');
1620-
expect(waitForBadgeCount(0)).to.eventually.equal(0);
1613+
it('setAppBadge can set a numerical value', async () => {
1614+
const result = await fireAppBadgeAction('set', expectedBadgeCount);
1615+
expect(result).to.equal('success');
1616+
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
1617+
});
1618+
1619+
it('setAppBadge can set an empty(dot) value', async () => {
1620+
const result = await fireAppBadgeAction('set');
1621+
expect(result).to.equal('success');
1622+
expect(waitForBadgeCount(0)).to.eventually.equal(0);
1623+
});
1624+
1625+
it('clearAppBadge can clear a value', async () => {
1626+
let result = await fireAppBadgeAction('set', expectedBadgeCount);
1627+
expect(result).to.equal('success');
1628+
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
1629+
result = await fireAppBadgeAction('clear');
1630+
expect(result).to.equal('success');
1631+
expect(waitForBadgeCount(0)).to.eventually.equal(0);
1632+
});
16211633
});
16221634

1623-
it('clearAppBadge can clear a value', async () => {
1624-
let result = await fireAppBadgeAction('set', expectedBadgeCount);
1625-
expect(result).to.equal('success');
1626-
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
1627-
result = await fireAppBadgeAction('clear');
1628-
expect(result).to.equal('success');
1629-
expect(waitForBadgeCount(0)).to.eventually.equal(0);
1635+
describe('in a service worker', () => {
1636+
beforeEach(async () => {
1637+
w = new BrowserWindow({
1638+
show: false,
1639+
webPreferences: {
1640+
nodeIntegration: true,
1641+
partition: 'sw-file-scheme-spec',
1642+
contextIsolation: false
1643+
}
1644+
});
1645+
});
1646+
1647+
afterEach(() => {
1648+
app.badgeCount = 0;
1649+
closeAllWindows();
1650+
});
1651+
1652+
it('setAppBadge can be called in a ServiceWorker', (done) => {
1653+
w.webContents.on('ipc-message', (event, channel, message) => {
1654+
if (channel === 'reload') {
1655+
w.webContents.reload();
1656+
} else if (channel === 'error') {
1657+
done(message);
1658+
} else if (channel === 'response') {
1659+
expect(message).to.equal('SUCCESS setting app badge');
1660+
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
1661+
session.fromPartition('sw-file-scheme-spec').clearStorageData({
1662+
storages: ['serviceworkers']
1663+
}).then(() => done());
1664+
}
1665+
});
1666+
w.webContents.on('crashed', () => done(new Error('WebContents crashed.')));
1667+
w.loadFile(path.join(fixturesPath, 'pages', 'service-worker', 'badge-index.html'), { search: '?setBadge' });
1668+
});
1669+
1670+
it('clearAppBadge can be called in a ServiceWorker', (done) => {
1671+
w.webContents.on('ipc-message', (event, channel, message) => {
1672+
if (channel === 'reload') {
1673+
w.webContents.reload();
1674+
} else if (channel === 'setAppBadge') {
1675+
expect(message).to.equal('SUCCESS setting app badge');
1676+
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
1677+
} else if (channel === 'error') {
1678+
done(message);
1679+
} else if (channel === 'response') {
1680+
expect(message).to.equal('SUCCESS clearing app badge');
1681+
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
1682+
session.fromPartition('sw-file-scheme-spec').clearStorageData({
1683+
storages: ['serviceworkers']
1684+
}).then(() => done());
1685+
}
1686+
});
1687+
w.webContents.on('crashed', () => done(new Error('WebContents crashed.')));
1688+
w.loadFile(path.join(fixturesPath, 'pages', 'service-worker', 'badge-index.html'), { search: '?clearBadge' });
1689+
});
16301690
});
16311691
});
16321692

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script>
2+
const ipcRenderer = require('electron').ipcRenderer;
3+
let search = (new URL(document.location)).search;
4+
5+
async function testIt() {
6+
if (search === '?clearBadge') {
7+
try {
8+
await navigator.setAppBadge(42);
9+
ipcRenderer.send('setAppBadge','SUCCESS setting app badge');
10+
} catch (error) {
11+
ipcRenderer.send('error', `${error.message}\n${error.stack}`);
12+
}
13+
}
14+
navigator.serviceWorker.register('service-worker-badge.js', {scope: './'}).then(function() {
15+
if (navigator.serviceWorker.controller) {
16+
var xhr = new XMLHttpRequest();
17+
xhr.open('GET', 'http://dummy/echo'+search);
18+
xhr.setRequestHeader('X-Mock-Response', 'yes');
19+
xhr.addEventListener('load', function() {
20+
ipcRenderer.send('response', xhr.responseText);
21+
});
22+
xhr.send();
23+
} else {
24+
ipcRenderer.send('reload');
25+
}
26+
}).catch(function(error) {
27+
ipcRenderer.send('error', `${error.message}\n${error.stack}`);
28+
})
29+
}
30+
testIt();
31+
</script>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
self.addEventListener('fetch', async function (event) {
2+
const requestUrl = new URL(event.request.url);
3+
let responseTxt;
4+
if (requestUrl.pathname === '/echo' &&
5+
event.request.headers.has('X-Mock-Response')) {
6+
if (requestUrl.search === '?setBadge') {
7+
if (navigator.setAppBadge()) {
8+
try {
9+
await navigator.setAppBadge(42);
10+
responseTxt = 'SUCCESS setting app badge';
11+
await navigator.clearAppBadge();
12+
} catch (ex) {
13+
responseTxt = 'ERROR setting app badge ' + ex;
14+
}
15+
} else {
16+
responseTxt = 'ERROR navigator.setAppBadge is not available in ServiceWorker!';
17+
}
18+
} else if (requestUrl.search === '?clearBadge') {
19+
if (navigator.clearAppBadge()) {
20+
try {
21+
await navigator.clearAppBadge();
22+
responseTxt = 'SUCCESS clearing app badge';
23+
} catch (ex) {
24+
responseTxt = 'ERROR clearing app badge ' + ex;
25+
}
26+
} else {
27+
responseTxt = 'ERROR navigator.clearAppBadge is not available in ServiceWorker!';
28+
}
29+
}
30+
const mockResponse = new Response(responseTxt);
31+
event.respondWith(mockResponse);
32+
}
33+
});

0 commit comments

Comments
 (0)