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

This commit is contained in:
John Kleinschmidt 2021-03-04 20:12:03 -05:00 committed by GitHub
parent d92bab0e29
commit afb7d9f550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 202 additions and 26 deletions

View file

@ -47,6 +47,27 @@ void BadgeManager::BindFrameReceiver(
std::move(context)); 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) { std::string BadgeManager::GetBadgeString(base::Optional<int> badge_content) {
if (!badge_content) if (!badge_content)
return ""; return "";

View file

@ -37,6 +37,10 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
static void BindFrameReceiver( static void BindFrameReceiver(
content::RenderFrameHost* frame, content::RenderFrameHost* frame,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver); 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. // Determines the text to put on the badge based on some badge_content.
static std::string GetBadgeString(base::Optional<int> 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_; 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: // blink::mojom::BadgeService:
// Note: These are private to stop them being called outside of mojo as they // Note: These are private to stop them being called outside of mojo as they
// require a mojo binding context. // require a mojo binding context.

View file

@ -1776,4 +1776,12 @@ content::BluetoothDelegate* ElectronBrowserClient::GetBluetoothDelegate() {
return bluetooth_delegate_.get(); 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 } // namespace electron

View file

@ -73,6 +73,10 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
void RegisterBrowserInterfaceBindersForFrame( void RegisterBrowserInterfaceBindersForFrame(
content::RenderFrameHost* render_frame_host, content::RenderFrameHost* render_frame_host,
mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override; 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) #if defined(OS_LINUX)
void GetAdditionalMappedFilesForChildProcess( void GetAdditionalMappedFilesForChildProcess(
const base::CommandLine& command_line, const base::CommandLine& command_line,

View file

@ -1578,12 +1578,6 @@ describe('navigator.clipboard', () => {
ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.setAppBadge/clearAppBadge', () => { ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.setAppBadge/clearAppBadge', () => {
let w: BrowserWindow; let w: BrowserWindow;
before(async () => {
w = new BrowserWindow({
show: false
});
await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
});
const expectedBadgeCount = 42; const expectedBadgeCount = 42;
@ -1603,30 +1597,96 @@ ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.se
return badgeCount; return badgeCount;
} }
after(() => { describe('in the renderer', () => {
app.badgeCount = 0; before(async () => {
closeAllWindows(); 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 () => { describe('in a service worker', () => {
const result = await fireAppBadgeAction('set', expectedBadgeCount); beforeEach(async () => {
expect(result).to.equal('success'); w = new BrowserWindow({
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); show: false,
}); webPreferences: {
nodeIntegration: true,
partition: 'sw-file-scheme-spec',
contextIsolation: false
}
});
});
it('setAppBadge can set an empty(dot) value', async () => { afterEach(() => {
const result = await fireAppBadgeAction('set'); app.badgeCount = 0;
expect(result).to.equal('success'); closeAllWindows();
expect(waitForBadgeCount(0)).to.eventually.equal(0); });
});
it('clearAppBadge can clear a value', async () => { it('setAppBadge can be called in a ServiceWorker', (done) => {
let result = await fireAppBadgeAction('set', expectedBadgeCount); w.webContents.on('ipc-message', (event, channel, message) => {
expect(result).to.equal('success'); if (channel === 'reload') {
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount); w.webContents.reload();
result = await fireAppBadgeAction('clear'); } else if (channel === 'error') {
expect(result).to.equal('success'); done(message);
expect(waitForBadgeCount(0)).to.eventually.equal(0); } 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' });
});
}); });
}); });

View file

@ -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>

View file

@ -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);
}
});