Commit 288faf08 authored by Steve Becker's avatar Steve Becker Committed by Commit Bot

Make Badging API available through WorkerNavigator for service workers

Exposes setExperimentalAppBadge() and clearExperimentalAppBadge() on the
navigator for service workers.  When a service worker uses the badging
API, the badge update affects all web apps within the scope of the
service worker's registration.  Future changes can expose the badging API
to dedicated workers and shared workers.  The WICG draft spec
(https://wicg.github.io/badging/) states that dedicated workers and shared
workers may set a badge when they are a service worker client.

To expose badging on WorkerNavigator, this change updates the existing
badging code under /blink/renderer/modules/badging/.  The change adds a
new IDL file, worker_navigator_badge.idl.  The new IDL defines a partial
WorkerNavigator interface to add the set and clear badging functions. The
change updates the existing NavigatorBadge class to include the new
WorkerNavigator IDL bindings, which are identical to the existing
Navigator IDL bindings except they pass a WorkerNavigator& parameter
instead of Navigator&.  The change moves the core implementation from the
existing Navigator IDL bindings into SetAppBadgeHelper() and
ClearAppBadgeHelper().  These helpers enable both the Navigator IDL
bindings and the WorkerNavigator IDL bindings to use the same
implementation.

The next part of the change registers a blink::mojom::BadgeService
binder in the ServiceWorkerGlobalScope's browser interface binders.
The ServiceWorkerGlobalScope needs this registration to connect the
NavigatorBadge class to the badging::BadgeManager class in
chrome/browser.  Without this registration, the JavaScript badging
APIs do not trigger any badge updates for service workers.  For the
Window, the binder registration occurs through
ContentBrowserClient::RegisterBrowserInterfaceBindersForFrame().
Unfortunately, RegisterBrowserInterfaceBindersForServiceWorker() does
not exist.  Additionally, the Window binder registration provides the
RenderFrameHost as context when creating a new mojo binding.  The
BadgeManager uses the RenderFrameHost as a binding context to find the
URL to use when setting a badge.  ServiceWorkerProviderHost is like
RenderFrameHost, but for service workers.  ServiceWorkerProviderHost
can provide the service worker registration scope URL to use when
setting a badge.  Unfortunately, ServiceWorkerProviderHost is not
publicly exposed to chrome/browser.  This makes creating a
RegisterBrowserInterfaceBindersForServiceWorker() problematic since
the service worker cannot provide the same amount of context as the
Window/Frame.

Instead of using something like
ContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(), this
change registers the badging binder in the content layer in
browser_interface_binder.cc.  Keeping the binder in the content layer
allows it to use the ServiceWorkerProviderHost to get the context it
needs and then pass that context to the BadgeManager in chrome/browser.
The change adds the
ContentBrowserClient::BindBadgeServiceReceiverFromServiceWorker(), which
allows the content badging binder to pass the service worker's Render
Process Host and its scope URL to the BadgeManager, which the
BadgeManager uses as the binding context.

The change updates the BadgeManager class to work with both service
workers and documents.  There are two differences between service
worker and document badge updates:

1) A service worker badge update may affect multiple apps.  A document
badge update may affect up to one app.

2) Service workers and documents provide different mojo binding
contexts.  Service workers provide a RenderProcessHost and a scope URL.
Documents provide a RenderFrameHost and a frame ID.

To deal with these differences, this change updates the pre-existing
BadgeManager::BindingContext struct to be an abstract interface base
class.  The class has one method to override: GetAppIdsForBadging(),
which returns the list of AppIDs to update after a badge change.  The
change adds two derived classes FrameBindingContext and
ServiceWorkerBindingContext.  Each class handles the differences
described above.

For testing, this change updates WebAppBadgingBrowserTest to include
service worker test cases.  The change added new test data to
chrome/test/data/web_app_badging/ to facilitate service worker testing.
The new browser tests use the main frame to post a message to the
service worker.  Depending on the message content, the service worker
responds by either calling setExperimentalAppBadge() or
clearExperimentalAppBadge().  The change also generalizes
WebAppBadgingBrowserTest to handle when a service worker badge change
affects multiple apps.  Before this change, WebAppBadgingBrowserTest
expected exactly 1 badge update.  After this change, the test can
expect multiple badge changes with at most 1 badge change per app.

The change also updates the badging origin trial API web_tests to
include service workers. I attempted to update the existing badging
web_tests to include workers, but since these tests run as file://,
all workers failed to run due to null origin errors.  I was also
unsuccessful moving these tests to run as https:// since the https://
server did not have the required resources to mock mojo.

Bug: 1036202
Change-Id: I66b2bf66b787965d6aa4ac35b663e522ffc4bd67
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2009376
Commit-Queue: Steve Becker <stevebe@microsoft.com>
Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Reviewed-by: default avatarJay Harris <harrisjay@chromium.org>
Cr-Commit-Position: refs/heads/master@{#735705}
parent dbef1c60
file://third_party/blink/renderer/modules/badging/OWNERS
harrisjay@chromium.org
mgiuca@chromium.org
# COMPONENT: UI>Browser>WebAppInstalls
......@@ -17,6 +17,8 @@
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/web_applications/components/app_registrar.h"
#include "chrome/browser/web_applications/components/web_app_provider_base.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "ui/base/l10n/l10n_util.h"
......@@ -44,18 +46,42 @@ void BadgeManager::SetDelegate(std::unique_ptr<BadgeManagerDelegate> delegate) {
delegate_ = std::move(delegate);
}
void BadgeManager::BindReceiver(
void BadgeManager::BindFrameReceiver(
content::RenderFrameHost* frame,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
Profile* profile = Profile::FromBrowserContext(
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto* profile = Profile::FromBrowserContext(
content::WebContents::FromRenderFrameHost(frame)->GetBrowserContext());
badging::BadgeManager* badge_manager =
auto* badge_manager =
badging::BadgeManagerFactory::GetInstance()->GetForProfile(profile);
if (!badge_manager)
return;
auto context = std::make_unique<FrameBindingContext>(
frame->GetProcess()->GetID(), frame->GetRoutingID());
badge_manager->receivers_.Add(badge_manager, std::move(receiver),
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* profile = Profile::FromBrowserContext(
service_worker_process_host->GetBrowserContext());
auto* badge_manager =
badging::BadgeManagerFactory::GetInstance()->GetForProfile(profile);
if (!badge_manager)
return;
BindingContext context(frame->GetProcess()->GetID(), frame->GetRoutingID());
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));
}
......@@ -99,38 +125,39 @@ void BadgeManager::SetBadge(blink::mojom::BadgeValuePtr mojo_value) {
return;
}
const base::Optional<web_app::AppId> app_id =
GetAppIdForBadging(receivers_.current_context());
if (!app_id)
return;
const std::vector<web_app::AppId> app_ids =
receivers_.current_context()->GetAppIdsForBadging();
// Convert the mojo badge representation into a BadgeManager::BadgeValue.
BadgeValue value = mojo_value->is_flag()
? base::nullopt
: base::make_optional(mojo_value->get_number());
UpdateBadge(app_id.value(), base::make_optional(value));
for (const auto& app_id : app_ids)
UpdateBadge(app_id, base::make_optional(value));
}
void BadgeManager::ClearBadge() {
const base::Optional<web_app::AppId> app_id =
GetAppIdForBadging(receivers_.current_context());
if (!app_id)
return;
const std::vector<web_app::AppId> app_ids =
receivers_.current_context()->GetAppIdsForBadging();
UpdateBadge(app_id.value(), base::nullopt);
for (const auto& app_id : app_ids)
UpdateBadge(app_id, base::nullopt);
}
base::Optional<web_app::AppId> BadgeManager::GetAppIdForBadging(
const BindingContext& context) {
std::vector<web_app::AppId>
BadgeManager::FrameBindingContext::GetAppIdsForBadging() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::RenderFrameHost* frame =
content::RenderFrameHost::FromID(context.process_id, context.frame_id);
content::RenderFrameHost::FromID(process_id_, frame_id_);
if (!frame)
return base::nullopt;
return std::vector<web_app::AppId>{};
content::WebContents* contents =
content::WebContents::FromRenderFrameHost(frame);
if (!contents)
return base::nullopt;
return std::vector<web_app::AppId>{};
const web_app::AppRegistrar& registrar =
web_app::WebAppProviderBase::GetProviderBase(
......@@ -139,7 +166,26 @@ base::Optional<web_app::AppId> BadgeManager::GetAppIdForBadging(
const base::Optional<web_app::AppId> app_id =
registrar.FindAppWithUrlInScope(frame->GetLastCommittedURL());
return app_id;
if (!app_id)
return std::vector<web_app::AppId>{};
return std::vector<web_app::AppId>{app_id.value()};
}
std::vector<web_app::AppId>
BadgeManager::ServiceWorkerBindingContext::GetAppIdsForBadging() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::RenderProcessHost* render_process_host =
content::RenderProcessHost::FromID(process_id_);
if (!render_process_host)
return std::vector<web_app::AppId>{};
const web_app::AppRegistrar& registrar =
web_app::WebAppProviderBase::GetProviderBase(
Profile::FromBrowserContext(render_process_host->GetBrowserContext()))
->registrar();
return registrar.FindAppsInScope(scope_);
}
std::string GetBadgeString(base::Optional<uint64_t> badge_content) {
......
......@@ -8,6 +8,7 @@
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/optional.h"
......@@ -21,6 +22,7 @@ class Profile;
namespace content {
class RenderFrameHost;
class RenderProcessHost;
} // namespace content
namespace badging {
......@@ -33,8 +35,9 @@ constexpr uint64_t kMaxBadgeContent = 99u;
// delegate.
class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
public:
// The badge being applied to a URL. If the optional is |base::nullopt| then
// the badge is "flag". Otherwise the badge is a non-zero integer.
// The badge being applied to a document URL or service worker scope. If the
// optional is |base::nullopt| then the badge is "flag". Otherwise the badge
// is a non-zero integer.
using BadgeValue = base::Optional<uint64_t>;
explicit BadgeManager(Profile* profile);
......@@ -43,9 +46,13 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
// Sets the delegate used for setting/clearing badges.
void SetDelegate(std::unique_ptr<BadgeManagerDelegate> delegate);
static void BindReceiver(
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);
// Gets the badge for |app_id|. This will be base::nullopt if the app is not
// badged.
......@@ -55,14 +62,50 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
void ClearBadgeForTesting(const web_app::AppId& app_id);
private:
// The BindingContext of a mojo request. Allows mojo calls to be tied back to
// the |RenderFrameHost| they belong to without trusting the renderer for that
// information.
struct BindingContext {
BindingContext(int process_id, int frame_id)
: process_id(process_id), frame_id(frame_id) {}
int process_id;
int frame_id;
// The BindingContext of a mojo request. Allows mojo calls to be tied back
// to the execution context they belong to without trusting the renderer for
// that information. This is an abstract base class that different types of
// execution contexts derive.
class BindingContext {
public:
virtual ~BindingContext() = default;
// Gets the list of app IDs to badge, based on the state of this
// BindingContext. Returns an empty list when no apps exist for this
// BindingContext.
virtual std::vector<web_app::AppId> GetAppIdsForBadging() const = 0;
};
// The BindingContext for Window execution contexts.
class FrameBindingContext final : public BindingContext {
public:
FrameBindingContext(int process_id, int frame_id)
: process_id_(process_id), frame_id_(frame_id) {}
~FrameBindingContext() override = default;
// Returns the AppId that matches the frame's URL. Returns either 0 or 1
// AppIds.
std::vector<web_app::AppId> GetAppIdsForBadging() const override;
private:
int process_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;
// Returns the list of AppIds within the service worker's scope. Returns
// either 0, 1 or more AppIds.
std::vector<web_app::AppId> GetAppIdsForBadging() const override;
private:
int process_id_;
GURL scope_;
};
// Updates the badge for |app_id| to be |value|, if it is not base::nullopt.
......@@ -76,15 +119,11 @@ class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
void SetBadge(blink::mojom::BadgeValuePtr value) override;
void ClearBadge() override;
// Gets the app id to badge, based on the |context|. base::nullopt if
// |context| isn't inside an app.
base::Optional<web_app::AppId> GetAppIdForBadging(
const BindingContext& context);
// All the mojo receivers for the BadgeManager. Keeps track of the
// render_frame the binding is associated with, so as to not have to rely
// on the renderer passing it in.
mojo::ReceiverSet<blink::mojom::BadgeService, BindingContext> receivers_;
mojo::ReceiverSet<blink::mojom::BadgeService, std::unique_ptr<BindingContext>>
receivers_;
// Delegate which handles actual setting and clearing of the badge.
// Note: This is currently only set on Windows and MacOS.
......
......@@ -456,7 +456,7 @@ void PopulateChromeFrameBinders(
#endif
#else
map->Add<blink::mojom::BadgeService>(
base::BindRepeating(&badging::BadgeManager::BindReceiver));
base::BindRepeating(&badging::BadgeManager::BindFrameReceiver));
if (base::FeatureList::IsEnabled(features::kWebPayments)) {
map->Add<payments::mojom::PaymentRequest>(
base::BindRepeating(&payments::CreatePaymentRequest));
......
......@@ -399,6 +399,10 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient {
content::RenderFrameHost* render_frame_host,
const std::string& interface_name,
mojo::ScopedInterfaceEndpointHandle* handle) override;
void BindBadgeServiceReceiverFromServiceWorker(
content::RenderProcessHost* service_worker_process_host,
const GURL& service_worker_scope,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) override;
void BindGpuHostReceiver(mojo::GenericPendingReceiver receiver) override;
void BindHostReceiverForRenderer(
content::RenderProcessHost* render_process_host,
......
......@@ -9,6 +9,7 @@
#include "base/bind.h"
#include "base/task/post_task.h"
#include "build/build_config.h"
#include "chrome/browser/badging/badge_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/cache_stats_recorder.h"
#include "chrome/browser/chrome_browser_interface_binders.h"
......@@ -298,6 +299,16 @@ bool ChromeContentBrowserClient::BindAssociatedReceiverFromFrame(
return false;
}
void ChromeContentBrowserClient::BindBadgeServiceReceiverFromServiceWorker(
content::RenderProcessHost* service_worker_process_host,
const GURL& service_worker_scope,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
#if !defined(OS_ANDROID)
badging::BadgeManager::BindServiceWorkerReceiver(
service_worker_process_host, service_worker_scope, std::move(receiver));
#endif
}
void ChromeContentBrowserClient::BindGpuHostReceiver(
mojo::GenericPendingReceiver receiver) {
if (auto r = receiver.As<metrics::mojom::CallStackProfileCollector>())
......
<html>
<head><title>This is a test page with a x-site iframe and an in-scope iframe</title>
</head>
<body>
<iframe name="in-scope" src="/ssl/google.html" id="in-scope"></iframe>
<iframe name="sub-app" src="/ssl/blank_page.html" id="sub-app"></iframe>
<iframe name="cross-site" id="cross-site"></iframe>
<script type="text/javascript">
const crossFrame = document.getElementById('cross-site');
const crossSiteUrl = new URLSearchParams(location.search).get('url');
crossFrame.src = crossSiteUrl;
</script>
</body>
</html>
<html>
<head>
<title>This is a badging test page with iframes and service workers</title>
</head>
<body>
<iframe name="in-scope" src="/web_app_badging/blank.html" id="in-scope"></iframe>
<iframe name="sub-app" src="/web_app_badging/blank.html" id="sub-app"></iframe>
<iframe name="cross-site" id="cross-site"></iframe>
<script type="text/javascript">
'use strict';
// Note: The url for the cross site frame is embedded in the query string.
const crossFrame = document.getElementById('cross-site');
const crossSiteUrl = new URLSearchParams(location.search).get('url');
crossFrame.src = crossSiteUrl;
// BrowserTests should use EvalJs() to call registerServiceWorker().
// EvalJs() provides this function's return value, which enables the
// BrowserTest to verify successful service worker registration.
// For example:
// ASSERT_EQ("OK", EvalJs(main_frame_,
// "registerServiceWorker(script, scope);"));
async function registerServiceWorker(script, scope) {
try {
await navigator.serviceWorker.register(script, { scope });
return 'OK';
} catch (error) {
return `EXCEPTION: ${error}`;
}
}
async function postMessageToServiceWorker(scope, message) {
const registration = await navigator.serviceWorker.getRegistration(scope);
if (!registration) {
return `ERROR: No service worker registration exists for scope: '${scope}.'`;
}
const serviceWorker = registration.active || registration.waiting || registration.installing;
serviceWorker.postMessage(message);
return await waitForMessageFromServiceWorker();
}
function waitForMessageFromServiceWorker() {
return new Promise(resolve => {
navigator.serviceWorker.addEventListener('message', event => {
resolve(event.data);
});
});
}
</script>
</body>
</html>
<html>
<head>
<title>I am a blank page.</title>
</head>
<body>
Nothing to see here.
</body>
</html>
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
// Adds a message event handler that responds to 'set-app-badge' and
// 'clear-app-badge' commands by running setAppBadge() or
// clearAppBadge() on this ServiceWorkerGlobalScope. Responds with
// a message to the sender after the set/clearAppBadge() promise settles.
//
// Here's how to send a valid message to this service worker:
//
// (1) serviceWorker.postMessage({ command: 'set-app-badge', value: 29 });
// (2) serviceWorker.postMessage({ command: 'set-app-badge' });
// (3) serviceWorker.postMessage({ command: 'clear-app-badge' });
addEventListener('message', async function (event) {
try {
const command = event.data.command;
switch (command) {
case 'set-app-badge':
const badgeValue = event.data.value;
if (badgeValue !== undefined) {
await navigator.setExperimentalAppBadge(badgeValue);
} else {
await navigator.setExperimentalAppBadge();
}
event.source.postMessage('OK');
break;
case 'clear-app-badge':
await navigator.clearExperimentalAppBadge();
event.source.postMessage('OK');
break;
default:
throw `Unknown command: '${command}'`;
}
} catch (error) {
event.source.postMessage(`EXCEPTION: ${error}`);
}
});
......@@ -65,6 +65,7 @@
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
#include "third_party/blink/public/mojom/background_fetch/background_fetch.mojom.h"
#include "third_party/blink/public/mojom/badging/badging.mojom.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
#include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom.h"
#include "third_party/blink/public/mojom/choosers/color_chooser.mojom.h"
......@@ -165,6 +166,33 @@ void BindTextDetection(
GetShapeDetectionService()->BindTextDetection(std::move(receiver));
}
void BindBadgeServiceForServiceWorkerOnUI(
int service_worker_process_id,
const GURL& service_worker_scope,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::RenderProcessHost* render_process_host =
content::RenderProcessHost::FromID(service_worker_process_id);
if (!render_process_host)
return;
GetContentClient()->browser()->BindBadgeServiceReceiverFromServiceWorker(
render_process_host, service_worker_scope, std::move(receiver));
}
void BindBadgeServiceForServiceWorker(
ServiceWorkerProviderHost* service_worker_host,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
content::RunOrPostTaskOnThread(
FROM_HERE, content::BrowserThread::UI,
base::BindOnce(&BindBadgeServiceForServiceWorkerOnUI,
service_worker_host->worker_process_id(),
service_worker_host->running_hosted_version()->scope(),
std::move(receiver)));
}
void BindColorChooserFactoryForFrame(
RenderFrameHost* host,
mojo::PendingReceiver<blink::mojom::ColorChooserFactory> receiver) {
......@@ -863,6 +891,8 @@ void PopulateServiceWorkerBinders(ServiceWorkerProviderHost* host,
map->Add<blink::mojom::QuicTransportConnector>(base::BindRepeating(
&ServiceWorkerProviderHost::CreateQuicTransportConnector,
base::Unretained(host)));
map->Add<blink::mojom::BadgeService>(
base::BindRepeating(&BindBadgeServiceForServiceWorker, host));
// render process host binders
map->Add<media::mojom::VideoDecodePerfHistory>(BindServiceWorkerReceiver(
......
......@@ -44,6 +44,7 @@
#include "services/network/public/mojom/websocket.mojom-forward.h"
#include "storage/browser/file_system/file_system_context.h"
#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
#include "third_party/blink/public/mojom/badging/badging.mojom-forward.h"
#include "third_party/blink/public/mojom/credentialmanager/credential_manager.mojom-forward.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-forward.h"
#include "ui/accessibility/ax_mode.h"
......@@ -1018,6 +1019,17 @@ class CONTENT_EXPORT ContentBrowserClient {
const std::string& interface_name,
mojo::ScopedInterfaceEndpointHandle* handle);
// TODO(https://crbug.com/1045214): Generalize ContentBrowserClient support
// for service worker-scoped binders.
//
// Binds a remote ServiceWorkerGlobalScope to a badge service. After
// receiving a badge update from a ServiceWorkerGlobalScope, the badge
// service must update the badge for each app under |service_worker_scope|.
virtual void BindBadgeServiceReceiverFromServiceWorker(
RenderProcessHost* service_worker_process_host,
const GURL& service_worker_scope,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {}
// Handles an unhandled incoming interface binding request from the GPU
// process. Called on the IO thread.
virtual void BindGpuHostReceiver(mojo::GenericPendingReceiver receiver) {}
......
file://third_party/blink/renderer/modules/badging/OWNERS
file://chrome/browser/badging/OWNERS
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
estevenson@chromium.org
mgiuca@chromium.org
harrisjay@chromium.org
# TEAM: apps-dev@chromium.org
# COMPONENT: Platform>Apps
file://chrome/browser/badging/OWNERS
......@@ -8,17 +8,19 @@ is under [active development].
### API
See the [explainer] for details. The Badge interface is a member on Window
and exposes two static methods:
See the [explainer] for details. The NavigatorBadge mixin interface is
included by Navigator and WorkerNavigator, which exposes two methods:
setAppBadge() and clearAppBadge().
[explainer]: https://github.com/WICG/badging/blob/master/explainer.md
* `set(contents)`: Sets the associated app's badge as a "flag" (the argument
is ignored).
* `clear()`: Sets the associated app's badge to nothing.
* `setAppBadge(optional [EnforceRange] unsigned long long contents)`: Sets the
associated app's badge to |contents|.
* When |contents| is omitted, sets the associated app's badge to "flag".
* When |contents| is 0, sets the associated app's badge to nothing.
* `clearAppBadge()`: Sets the associated app's badge to nothing.
### Testing
`web_tests/badging/*.html` tests that the API accepts/rejects the appropriate
inputs (with a mock Mojo service). Testing at other layers will be added
during implementation.
inputs (with a mock Mojo service).
......@@ -8,6 +8,8 @@
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/workers/worker_navigator.h"
namespace blink {
......@@ -34,32 +36,62 @@ NavigatorBadge::NavigatorBadge(ExecutionContext* context) {
// static
ScriptPromise NavigatorBadge::setAppBadge(ScriptState* script_state,
Navigator& /*navigator*/) {
From(script_state)
.badge_service_->SetBadge(mojom::blink::BadgeValue::NewFlag(0));
return ScriptPromise::CastUndefined(script_state);
return SetAppBadgeHelper(script_state, mojom::blink::BadgeValue::NewFlag(0));
}
// static
ScriptPromise NavigatorBadge::setAppBadge(ScriptState* script_state,
Navigator& navigator,
WorkerNavigator& /*navigator*/) {
return SetAppBadgeHelper(script_state, mojom::blink::BadgeValue::NewFlag(0));
}
// static
ScriptPromise NavigatorBadge::setAppBadge(ScriptState* script_state,
Navigator& /*navigator*/,
uint64_t content) {
if (content == 0)
return NavigatorBadge::clearAppBadge(script_state, navigator);
return SetAppBadgeHelper(script_state,
mojom::blink::BadgeValue::NewNumber(content));
}
From(script_state)
.badge_service_->SetBadge(mojom::blink::BadgeValue::NewNumber(content));
return ScriptPromise::CastUndefined(script_state);
// static
ScriptPromise NavigatorBadge::setAppBadge(ScriptState* script_state,
WorkerNavigator& /*navigator*/,
uint64_t content) {
return SetAppBadgeHelper(script_state,
mojom::blink::BadgeValue::NewNumber(content));
}
// static
ScriptPromise NavigatorBadge::clearAppBadge(ScriptState* script_state,
Navigator& /*navigator*/) {
From(script_state).badge_service_->ClearBadge();
return ScriptPromise::CastUndefined(script_state);
return ClearAppBadgeHelper(script_state);
}
// static
ScriptPromise NavigatorBadge::clearAppBadge(ScriptState* script_state,
WorkerNavigator& /*navigator*/) {
return ClearAppBadgeHelper(script_state);
}
void NavigatorBadge::Trace(blink::Visitor* visitor) {
Supplement<ExecutionContext>::Trace(visitor);
}
// static
ScriptPromise NavigatorBadge::SetAppBadgeHelper(
ScriptState* script_state,
mojom::blink::BadgeValuePtr badge_value) {
if (badge_value->is_number() && badge_value->get_number() == 0)
return ClearAppBadgeHelper(script_state);
From(script_state).badge_service_->SetBadge(std::move(badge_value));
return ScriptPromise::CastUndefined(script_state);
}
// static
ScriptPromise NavigatorBadge::ClearAppBadgeHelper(ScriptState* script_state) {
From(script_state).badge_service_->ClearBadge();
return ScriptPromise::CastUndefined(script_state);
}
} // namespace blink
......@@ -7,12 +7,14 @@
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/mojom/badging/badging.mojom-blink.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/supplementable.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
class Navigator;
class ScriptPromise;
class WorkerNavigator;
class NavigatorBadge final : public GarbageCollected<NavigatorBadge>,
public Supplement<ExecutionContext> {
......@@ -26,12 +28,24 @@ class NavigatorBadge final : public GarbageCollected<NavigatorBadge>,
// Badge IDL interface.
static ScriptPromise setAppBadge(ScriptState*, Navigator&);
static ScriptPromise setAppBadge(ScriptState*, WorkerNavigator&);
static ScriptPromise setAppBadge(ScriptState*, Navigator&, uint64_t content);
static ScriptPromise setAppBadge(ScriptState*,
WorkerNavigator&,
uint64_t content);
static ScriptPromise clearAppBadge(ScriptState*, Navigator&);
static ScriptPromise clearAppBadge(ScriptState*, WorkerNavigator&);
void Trace(blink::Visitor*) override;
private:
static ScriptPromise SetAppBadgeHelper(
ScriptState* script_state,
mojom::blink::BadgeValuePtr badge_value);
static ScriptPromise ClearAppBadgeHelper(ScriptState* script_state);
mojo::Remote<blink::mojom::blink::BadgeService> badge_service_;
};
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://wicg.github.io/badging/
[
SecureContext,
RuntimeEnabled=Badging,
ImplementedAs=NavigatorBadge
] partial interface WorkerNavigator {
[Exposed=ServiceWorker, CallWith=ScriptState, MeasureAs=BadgeSet, ImplementedAs=setAppBadge]
Promise<void> setExperimentalAppBadge(optional [EnforceRange] unsigned long long contents);
[Exposed=ServiceWorker, CallWith=ScriptState, MeasureAs=BadgeClear, ImplementedAs=clearAppBadge]
Promise<void> clearExperimentalAppBadge();
};
......@@ -942,6 +942,7 @@ modules_dependency_idl_files =
"background_sync/service_worker_global_scope_sync.idl",
"background_sync/service_worker_registration_sync.idl",
"badging/navigator_badge.idl",
"badging/worker_navigator_badge.idl",
"battery/navigator_battery.idl",
"beacon/navigator_beacon.idl",
"bluetooth/navigator_bluetooth.idl",
......
......@@ -9,6 +9,7 @@ generate_token.py http://127.0.0.1:8000 BadgingV2 --expire-timestamp=2000000000
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/origin-trials-helper.js"></script>
<script src="/serviceworker/resources/test-helpers.js"></script>
<script>
function assert_function_on(object, function_name, explanation) {
......@@ -19,4 +20,12 @@ test(t => {
assert_function_on(navigator, 'setExperimentalAppBadge', 'setExperimentalAppBadge is not defined on navigator');
assert_function_on(navigator, 'clearExperimentalAppBadge', 'clearExperimentalAppBadge is not defined on navigator');
}, 'Badge API interfaces and properties in Origin-Trial enabled document.');
// Only run "disabled" tests if the feature is not enabled via runtime flags.
if (!self.internals.runtimeFlags.badgingEnabled) {
service_worker_test('resources/badging-serviceworker-disabled.js');
}
// ServiceWorkerGlobalScope must expose badging through origin trials.
service_worker_test('resources/badging-serviceworker-enabled.php');
</script>
'use strict';
importScripts('/resources/testharness.js');
test(t => {
assert_false('setExperimentalAppBadge' in navigator, 'setExperimentalAppBadge does not exist in navigator');
assert_false('clearExperimentalAppBadge' in navigator, 'clearExperimentalAppBadge does not exist in navigator');
}, 'Badge API interfaces and properties in Origin-Trial disabled service worker.');
done();
<?php
// Generate token with the command:
// generate_token.py http://127.0.0.1:8000 BadgingV2 --expire-timestamp=2000000000
header("Origin-Trial: AqzH1yAjqt/6grJkR3r1584FLOYa+kkfoenZBdnmBOShEN/eGrOF7OoxdPXg5e2b+KeB+ysH8qp/F9eyimHZygIAAABReyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiQmFkZ2luZ1YyIiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9");
header('Content-Type: application/javascript');
?>
'use strict';
importScripts('/resources/testharness.js');
function assert_function_on(object, function_name, explanation) {
assert_equals(typeof object[function_name], 'function', explanation);
}
test(t => {
assert_function_on(navigator, 'setExperimentalAppBadge', 'setExperimentalAppBadge is not defined on navigator');
assert_function_on(navigator, 'clearExperimentalAppBadge', 'clearExperimentalAppBadge is not defined on navigator');
}, 'Badge API interfaces and properties in Origin-Trial enabled service worker.');
done();
......@@ -3762,7 +3762,9 @@ interface WorkerNavigator
getter usb
getter userAgent
getter wakeLock
method clearExperimentalAppBadge
method constructor
method setExperimentalAppBadge
interface WritableStream
attribute @@toStringTag
getter locked
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment