fix: enable navigator.setAppBadge/clearAppBadge (#27067)
This commit is contained in:
parent
8b74361b0c
commit
c5a41defbd
19 changed files with 481 additions and 41 deletions
4
docs/api/app.md
Normal file → Executable file
4
docs/api/app.md
Normal file → Executable file
|
@ -1174,9 +1174,9 @@ For `infoType` equal to `basic`:
|
||||||
|
|
||||||
Using `basic` should be preferred if only basic information like `vendorId` or `driverId` is needed.
|
Using `basic` should be preferred if only basic information like `vendorId` or `driverId` is needed.
|
||||||
|
|
||||||
### `app.setBadgeCount(count)` _Linux_ _macOS_
|
### `app.setBadgeCount([count])` _Linux_ _macOS_
|
||||||
|
|
||||||
* `count` Integer
|
* `count` Integer (optional) - If a value is provided, set the badge to the provided value otherwise, on macOS, display a plain white dot (e.g. unknown number of notifications). On Linux, if a value is not provided the badge will not display.
|
||||||
|
|
||||||
Returns `Boolean` - Whether the call succeeded.
|
Returns `Boolean` - Whether the call succeeded.
|
||||||
|
|
||||||
|
|
|
@ -83,5 +83,18 @@
|
||||||
<message name="IDS_DOWNLOAD_MORE_ACTIONS"
|
<message name="IDS_DOWNLOAD_MORE_ACTIONS"
|
||||||
desc="Tooltip of a button on the downloads page that shows a menu with actions like 'Open downloads folder' or 'Clear all'">
|
desc="Tooltip of a button on the downloads page that shows a menu with actions like 'Open downloads folder' or 'Clear all'">
|
||||||
More actions
|
More actions
|
||||||
</message>
|
</message>
|
||||||
|
<!-- Badging -->
|
||||||
|
<message name="IDS_SATURATED_BADGE_CONTENT" desc="The content to display when the application's badge is too large to display to indicate that the badge is more than a given maximum. This string should be as short as possible, preferably only one character beyond the content">
|
||||||
|
<ph name="MAXIMUM_VALUE">$1<ex>99</ex></ph>+
|
||||||
|
</message>
|
||||||
|
<message name="IDS_BADGE_UNREAD_NOTIFICATIONS_SATURATED" desc="The accessibility text which will be read by a screen reader when the notification count is too large to display (e.g. greater than 99).">
|
||||||
|
{MAX_UNREAD_NOTIFICATIONS, plural, =1 {More than 1 unread notification} other {More than # unread notifications}}
|
||||||
|
</message>
|
||||||
|
<message name="IDS_BADGE_UNREAD_NOTIFICATIONS_UNSPECIFIED" desc="The accessibility text which will be read by a screen reader when there are some unspecified number of notifications, or user attention is required">
|
||||||
|
Unread Notifications
|
||||||
|
</message>
|
||||||
|
<message name="IDS_BADGE_UNREAD_NOTIFICATIONS" desc="The accessibility text which will be read by a screen reader when there are notifcatications">
|
||||||
|
{UNREAD_NOTIFICATIONS, plural, =1 {1 Unread Notification} other {# Unread Notifications}}
|
||||||
|
</message>
|
||||||
</grit-part>
|
</grit-part>
|
||||||
|
|
|
@ -328,6 +328,10 @@ filenames = {
|
||||||
"shell/browser/api/ui_event.h",
|
"shell/browser/api/ui_event.h",
|
||||||
"shell/browser/auto_updater.cc",
|
"shell/browser/auto_updater.cc",
|
||||||
"shell/browser/auto_updater.h",
|
"shell/browser/auto_updater.h",
|
||||||
|
"shell/browser/badging/badge_manager.cc",
|
||||||
|
"shell/browser/badging/badge_manager.h",
|
||||||
|
"shell/browser/badging/badge_manager_factory.cc",
|
||||||
|
"shell/browser/badging/badge_manager_factory.h",
|
||||||
"shell/browser/browser.cc",
|
"shell/browser/browser.cc",
|
||||||
"shell/browser/browser.h",
|
"shell/browser/browser.h",
|
||||||
"shell/browser/browser_observer.h",
|
"shell/browser/browser_observer.h",
|
||||||
|
|
81
shell/browser/badging/badge_manager.cc
Executable file
81
shell/browser/badging/badge_manager.cc
Executable file
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
#include "shell/browser/badging/badge_manager.h"
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/i18n/number_formatting.h"
|
||||||
|
#include "base/strings/utf_string_conversions.h"
|
||||||
|
#include "build/build_config.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 "content/public/browser/web_contents.h"
|
||||||
|
#include "shell/browser/badging/badge_manager_factory.h"
|
||||||
|
#include "shell/browser/browser.h"
|
||||||
|
#include "ui/base/l10n/l10n_util.h"
|
||||||
|
#include "ui/strings/grit/ui_strings.h"
|
||||||
|
|
||||||
|
namespace badging {
|
||||||
|
|
||||||
|
BadgeManager::BadgeManager() = default;
|
||||||
|
BadgeManager::~BadgeManager() = default;
|
||||||
|
|
||||||
|
// static
|
||||||
|
void BadgeManager::BindFrameReceiver(
|
||||||
|
content::RenderFrameHost* frame,
|
||||||
|
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
|
||||||
|
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||||
|
|
||||||
|
auto* browser_context =
|
||||||
|
content::WebContents::FromRenderFrameHost(frame)->GetBrowserContext();
|
||||||
|
|
||||||
|
auto* badge_manager =
|
||||||
|
badging::BadgeManagerFactory::GetInstance()->GetForBrowserContext(
|
||||||
|
browser_context);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BadgeManager::GetBadgeString(base::Optional<int> badge_content) {
|
||||||
|
if (!badge_content)
|
||||||
|
return "•";
|
||||||
|
|
||||||
|
if (badge_content > kMaxBadgeContent) {
|
||||||
|
return base::UTF16ToUTF8(l10n_util::GetStringFUTF16(
|
||||||
|
IDS_SATURATED_BADGE_CONTENT, base::FormatNumber(kMaxBadgeContent)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base::UTF16ToUTF8(base::FormatNumber(badge_content.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BadgeManager::SetBadge(blink::mojom::BadgeValuePtr mojo_value) {
|
||||||
|
if (mojo_value->is_number() && mojo_value->get_number() == 0) {
|
||||||
|
mojo::ReportBadMessage(
|
||||||
|
"|value| should not be zero when it is |number| (ClearBadge should be "
|
||||||
|
"called instead)!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base::Optional<int> value =
|
||||||
|
mojo_value->is_flag() ? base::nullopt
|
||||||
|
: base::make_optional(mojo_value->get_number());
|
||||||
|
|
||||||
|
electron::Browser::Get()->SetBadgeCount(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BadgeManager::ClearBadge() {
|
||||||
|
electron::Browser::Get()->SetBadgeCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace badging
|
90
shell/browser/badging/badge_manager.h
Normal file
90
shell/browser/badging/badge_manager.h
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
#ifndef SHELL_BROWSER_BADGING_BADGE_MANAGER_H_
|
||||||
|
#define SHELL_BROWSER_BADGING_BADGE_MANAGER_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "base/macros.h"
|
||||||
|
#include "base/optional.h"
|
||||||
|
#include "components/keyed_service/core/keyed_service.h"
|
||||||
|
#include "mojo/public/cpp/bindings/receiver_set.h"
|
||||||
|
#include "third_party/blink/public/mojom/badging/badging.mojom.h"
|
||||||
|
#include "url/gurl.h"
|
||||||
|
|
||||||
|
namespace content {
|
||||||
|
class RenderFrameHost;
|
||||||
|
class RenderProcessHost;
|
||||||
|
} // namespace content
|
||||||
|
|
||||||
|
namespace badging {
|
||||||
|
|
||||||
|
// The maximum value of badge contents before saturation occurs.
|
||||||
|
constexpr int kMaxBadgeContent = 99;
|
||||||
|
|
||||||
|
// Maintains a record of badge contents and dispatches badge changes to a
|
||||||
|
// delegate.
|
||||||
|
class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
|
||||||
|
public:
|
||||||
|
BadgeManager();
|
||||||
|
~BadgeManager() override;
|
||||||
|
|
||||||
|
static void BindFrameReceiver(
|
||||||
|
content::RenderFrameHost* frame,
|
||||||
|
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);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
int GetProcessId() { return process_id_; }
|
||||||
|
int GetFrameId() { return frame_id_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int process_id_;
|
||||||
|
int frame_id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// blink::mojom::BadgeService:
|
||||||
|
// Note: These are private to stop them being called outside of mojo as they
|
||||||
|
// require a mojo binding context.
|
||||||
|
void SetBadge(blink::mojom::BadgeValuePtr value) override;
|
||||||
|
void ClearBadge() override;
|
||||||
|
|
||||||
|
// 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, 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.
|
||||||
|
// std::unique_ptr<BadgeManagerDelegate> delegate_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(BadgeManager);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace badging
|
||||||
|
|
||||||
|
#endif // SHELL_BROWSER_BADGING_BADGE_MANAGER_H_
|
41
shell/browser/badging/badge_manager_factory.cc
Normal file
41
shell/browser/badging/badge_manager_factory.cc
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
#include "shell/browser/badging/badge_manager_factory.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "base/bind.h"
|
||||||
|
#include "base/memory/ptr_util.h"
|
||||||
|
#include "base/memory/singleton.h"
|
||||||
|
#include "components/keyed_service/content/browser_context_dependency_manager.h"
|
||||||
|
#include "shell/browser/badging/badge_manager.h"
|
||||||
|
|
||||||
|
namespace badging {
|
||||||
|
|
||||||
|
// static
|
||||||
|
BadgeManager* BadgeManagerFactory::GetForBrowserContext(
|
||||||
|
content::BrowserContext* context) {
|
||||||
|
return static_cast<badging::BadgeManager*>(
|
||||||
|
GetInstance()->GetServiceForBrowserContext(context, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
BadgeManagerFactory* BadgeManagerFactory::GetInstance() {
|
||||||
|
return base::Singleton<BadgeManagerFactory>::get();
|
||||||
|
}
|
||||||
|
|
||||||
|
BadgeManagerFactory::BadgeManagerFactory()
|
||||||
|
: BrowserContextKeyedServiceFactory(
|
||||||
|
"BadgeManager",
|
||||||
|
BrowserContextDependencyManager::GetInstance()) {}
|
||||||
|
|
||||||
|
BadgeManagerFactory::~BadgeManagerFactory() {}
|
||||||
|
|
||||||
|
KeyedService* BadgeManagerFactory::BuildServiceInstanceFor(
|
||||||
|
content::BrowserContext* context) const {
|
||||||
|
return new BadgeManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace badging
|
44
shell/browser/badging/badge_manager_factory.h
Normal file
44
shell/browser/badging/badge_manager_factory.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
#ifndef SHELL_BROWSER_BADGING_BADGE_MANAGER_FACTORY_H_
|
||||||
|
#define SHELL_BROWSER_BADGING_BADGE_MANAGER_FACTORY_H_
|
||||||
|
|
||||||
|
#include "base/macros.h"
|
||||||
|
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
|
||||||
|
|
||||||
|
namespace base {
|
||||||
|
template <typename T>
|
||||||
|
struct DefaultSingletonTraits;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace badging {
|
||||||
|
|
||||||
|
class BadgeManager;
|
||||||
|
|
||||||
|
// Singleton that provides access to context specific BadgeManagers.
|
||||||
|
class BadgeManagerFactory : public BrowserContextKeyedServiceFactory {
|
||||||
|
public:
|
||||||
|
// Gets the BadgeManager for the specified context
|
||||||
|
static BadgeManager* GetForBrowserContext(content::BrowserContext* context);
|
||||||
|
|
||||||
|
// Returns the BadgeManagerFactory singleton.
|
||||||
|
static BadgeManagerFactory* GetInstance();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct base::DefaultSingletonTraits<BadgeManagerFactory>;
|
||||||
|
|
||||||
|
BadgeManagerFactory();
|
||||||
|
~BadgeManagerFactory() override;
|
||||||
|
|
||||||
|
// BrowserContextKeyedServiceFactory
|
||||||
|
KeyedService* BuildServiceInstanceFor(
|
||||||
|
content::BrowserContext* context) const override;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(BadgeManagerFactory);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace badging
|
||||||
|
|
||||||
|
#endif // SHELL_BROWSER_BADGING_BADGE_MANAGER_FACTORY_H_
|
0
shell/browser/browser.cc
Normal file → Executable file
0
shell/browser/browser.cc
Normal file → Executable file
12
shell/browser/browser.h
Normal file → Executable file
12
shell/browser/browser.h
Normal file → Executable file
|
@ -23,6 +23,7 @@
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
|
#include "shell/browser/ui/win/taskbar_host.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(OS_MAC)
|
#if defined(OS_MAC)
|
||||||
|
@ -107,7 +108,7 @@ class Browser : public WindowListObserver {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Set/Get the badge count.
|
// Set/Get the badge count.
|
||||||
bool SetBadgeCount(int count);
|
bool SetBadgeCount(base::Optional<int> count);
|
||||||
int GetBadgeCount();
|
int GetBadgeCount();
|
||||||
|
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
|
@ -364,6 +365,15 @@ class Browser : public WindowListObserver {
|
||||||
base::DictionaryValue about_panel_options_;
|
base::DictionaryValue about_panel_options_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(OS_WIN)
|
||||||
|
void UpdateBadgeContents(HWND hwnd,
|
||||||
|
const base::Optional<std::string>& badge_content,
|
||||||
|
const std::string& badge_alt_string);
|
||||||
|
|
||||||
|
// In charge of running taskbar related APIs.
|
||||||
|
TaskbarHost taskbar_host_;
|
||||||
|
#endif
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(Browser);
|
DISALLOW_COPY_AND_ASSIGN(Browser);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -130,10 +130,10 @@ base::string16 Browser::GetApplicationNameForProtocol(const GURL& url) {
|
||||||
return base::ASCIIToUTF16(GetXdgAppOutput(argv).value_or(std::string()));
|
return base::ASCIIToUTF16(GetXdgAppOutput(argv).value_or(std::string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Browser::SetBadgeCount(int count) {
|
bool Browser::SetBadgeCount(base::Optional<int> count) {
|
||||||
if (IsUnityRunning()) {
|
if (IsUnityRunning() && count.has_value()) {
|
||||||
unity::SetDownloadCount(count);
|
unity::SetDownloadCount(count.value());
|
||||||
badge_count_ = count;
|
badge_count_ = count.value();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "base/strings/string_number_conversions.h"
|
#include "base/strings/string_number_conversions.h"
|
||||||
#include "base/strings/sys_string_conversions.h"
|
#include "base/strings/sys_string_conversions.h"
|
||||||
#include "net/base/mac/url_conversions.h"
|
#include "net/base/mac/url_conversions.h"
|
||||||
|
#include "shell/browser/badging/badge_manager.h"
|
||||||
#include "shell/browser/mac/dict_util.h"
|
#include "shell/browser/mac/dict_util.h"
|
||||||
#include "shell/browser/mac/electron_application.h"
|
#include "shell/browser/mac/electron_application.h"
|
||||||
#include "shell/browser/mac/electron_application_delegate.h"
|
#include "shell/browser/mac/electron_application_delegate.h"
|
||||||
|
@ -219,9 +220,15 @@ base::string16 Browser::GetApplicationNameForProtocol(const GURL& url) {
|
||||||
|
|
||||||
void Browser::SetAppUserModelID(const base::string16& name) {}
|
void Browser::SetAppUserModelID(const base::string16& name) {}
|
||||||
|
|
||||||
bool Browser::SetBadgeCount(int count) {
|
bool Browser::SetBadgeCount(base::Optional<int> count) {
|
||||||
DockSetBadgeText(count != 0 ? base::NumberToString(count) : "");
|
DockSetBadgeText(!count.has_value() || count.value() != 0
|
||||||
badge_count_ = count;
|
? badging::BadgeManager::GetBadgeString(count)
|
||||||
|
: "");
|
||||||
|
if (count.has_value()) {
|
||||||
|
badge_count_ = count.value();
|
||||||
|
} else {
|
||||||
|
badge_count_ = 0;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
101
shell/browser/browser_win.cc
Normal file → Executable file
101
shell/browser/browser_win.cc
Normal file → Executable file
|
@ -28,16 +28,21 @@
|
||||||
#include "chrome/browser/icon_manager.h"
|
#include "chrome/browser/icon_manager.h"
|
||||||
#include "electron/electron_version.h"
|
#include "electron/electron_version.h"
|
||||||
#include "shell/browser/api/electron_api_app.h"
|
#include "shell/browser/api/electron_api_app.h"
|
||||||
|
#include "shell/browser/badging/badge_manager.h"
|
||||||
#include "shell/browser/electron_browser_main_parts.h"
|
#include "shell/browser/electron_browser_main_parts.h"
|
||||||
#include "shell/browser/ui/message_box.h"
|
#include "shell/browser/ui/message_box.h"
|
||||||
#include "shell/browser/ui/win/jump_list.h"
|
#include "shell/browser/ui/win/jump_list.h"
|
||||||
|
#include "shell/browser/window_list.h"
|
||||||
#include "shell/common/application_info.h"
|
#include "shell/common/application_info.h"
|
||||||
#include "shell/common/gin_converters/file_path_converter.h"
|
#include "shell/common/gin_converters/file_path_converter.h"
|
||||||
#include "shell/common/gin_converters/image_converter.h"
|
#include "shell/common/gin_converters/image_converter.h"
|
||||||
#include "shell/common/gin_helper/arguments.h"
|
#include "shell/common/gin_helper/arguments.h"
|
||||||
#include "shell/common/gin_helper/dictionary.h"
|
#include "shell/common/gin_helper/dictionary.h"
|
||||||
#include "shell/common/skia_util.h"
|
#include "shell/common/skia_util.h"
|
||||||
|
#include "skia/ext/legacy_display_globals.h"
|
||||||
|
#include "ui/base/l10n/l10n_util.h"
|
||||||
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
|
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
|
||||||
|
#include "ui/strings/grit/ui_strings.h"
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
|
@ -584,8 +589,100 @@ v8::Local<v8::Promise> Browser::GetApplicationInfoForProtocol(
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Browser::SetBadgeCount(int count) {
|
bool Browser::SetBadgeCount(base::Optional<int> count) {
|
||||||
return false;
|
base::Optional<std::string> badge_content;
|
||||||
|
if (count.has_value() && count.value() == 0) {
|
||||||
|
badge_content = base::nullopt;
|
||||||
|
} else {
|
||||||
|
badge_content = badging::BadgeManager::GetBadgeString(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are 3 different cases when the badge has a value:
|
||||||
|
// 1. |contents| is between 1 and 99 inclusive => Set the accessibility text
|
||||||
|
// to a pluralized notification count (e.g. 4 Unread Notifications).
|
||||||
|
// 2. |contents| is greater than 99 => Set the accessibility text to
|
||||||
|
// More than |kMaxBadgeContent| unread notifications, so the
|
||||||
|
// accessibility text matches what is displayed on the badge (e.g. More
|
||||||
|
// than 99 notifications).
|
||||||
|
// 3. The badge is set to 'flag' => Set the accessibility text to something
|
||||||
|
// less specific (e.g. Unread Notifications).
|
||||||
|
std::string badge_alt_string;
|
||||||
|
if (count.has_value()) {
|
||||||
|
badge_count_ = count.value();
|
||||||
|
badge_alt_string = (uint64_t)badge_count_ <= badging::kMaxBadgeContent
|
||||||
|
// Case 1.
|
||||||
|
? l10n_util::GetPluralStringFUTF8(
|
||||||
|
IDS_BADGE_UNREAD_NOTIFICATIONS, badge_count_)
|
||||||
|
// Case 2.
|
||||||
|
: l10n_util::GetPluralStringFUTF8(
|
||||||
|
IDS_BADGE_UNREAD_NOTIFICATIONS_SATURATED,
|
||||||
|
badging::kMaxBadgeContent);
|
||||||
|
} else {
|
||||||
|
// Case 3.
|
||||||
|
badge_alt_string =
|
||||||
|
l10n_util::GetStringUTF8(IDS_BADGE_UNREAD_NOTIFICATIONS_UNSPECIFIED);
|
||||||
|
badge_count_ = 0;
|
||||||
|
}
|
||||||
|
for (auto* window : WindowList::GetWindows()) {
|
||||||
|
// On Windows set the badge on the first window found.
|
||||||
|
UpdateBadgeContents(window->GetAcceleratedWidget(), badge_content,
|
||||||
|
badge_alt_string);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Browser::UpdateBadgeContents(
|
||||||
|
HWND hwnd,
|
||||||
|
const base::Optional<std::string>& badge_content,
|
||||||
|
const std::string& badge_alt_string) {
|
||||||
|
SkBitmap badge;
|
||||||
|
if (badge_content) {
|
||||||
|
std::string content = badge_content.value();
|
||||||
|
constexpr int kOverlayIconSize = 16;
|
||||||
|
// This is the color used by the Windows 10 Badge API, for platform
|
||||||
|
// consistency.
|
||||||
|
constexpr int kBackgroundColor = SkColorSetRGB(0x26, 0x25, 0x2D);
|
||||||
|
constexpr int kForegroundColor = SK_ColorWHITE;
|
||||||
|
constexpr int kRadius = kOverlayIconSize / 2;
|
||||||
|
// The minimum gap to have between our content and the edge of the badge.
|
||||||
|
constexpr int kMinMargin = 3;
|
||||||
|
// The amount of space we have to render the icon.
|
||||||
|
constexpr int kMaxBounds = kOverlayIconSize - 2 * kMinMargin;
|
||||||
|
constexpr int kMaxTextSize = 24; // Max size for our text.
|
||||||
|
constexpr int kMinTextSize = 7; // Min size for our text.
|
||||||
|
|
||||||
|
badge.allocN32Pixels(kOverlayIconSize, kOverlayIconSize);
|
||||||
|
SkCanvas canvas(badge, skia::LegacyDisplayGlobals::GetSkSurfaceProps());
|
||||||
|
|
||||||
|
SkPaint paint;
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
paint.setColor(kBackgroundColor);
|
||||||
|
|
||||||
|
canvas.clear(SK_ColorTRANSPARENT);
|
||||||
|
canvas.drawCircle(kRadius, kRadius, kRadius, paint);
|
||||||
|
|
||||||
|
paint.reset();
|
||||||
|
paint.setColor(kForegroundColor);
|
||||||
|
|
||||||
|
SkFont font;
|
||||||
|
|
||||||
|
SkRect bounds;
|
||||||
|
int text_size = kMaxTextSize;
|
||||||
|
// Find the largest |text_size| larger than |kMinTextSize| in which
|
||||||
|
// |content| fits into our 16x16px icon, with margins.
|
||||||
|
do {
|
||||||
|
font.setSize(text_size--);
|
||||||
|
font.measureText(content.c_str(), content.size(), SkTextEncoding::kUTF8,
|
||||||
|
&bounds);
|
||||||
|
} while (text_size >= kMinTextSize &&
|
||||||
|
(bounds.width() > kMaxBounds || bounds.height() > kMaxBounds));
|
||||||
|
|
||||||
|
canvas.drawSimpleText(
|
||||||
|
content.c_str(), content.size(), SkTextEncoding::kUTF8,
|
||||||
|
kRadius - bounds.width() / 2 - bounds.x(),
|
||||||
|
kRadius - bounds.height() / 2 - bounds.y(), font, paint);
|
||||||
|
}
|
||||||
|
taskbar_host_.SetOverlayIcon(hwnd, badge, badge_alt_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
#include "shell/browser/api/electron_api_session.h"
|
#include "shell/browser/api/electron_api_session.h"
|
||||||
#include "shell/browser/api/electron_api_web_contents.h"
|
#include "shell/browser/api/electron_api_web_contents.h"
|
||||||
#include "shell/browser/api/electron_api_web_request.h"
|
#include "shell/browser/api/electron_api_web_request.h"
|
||||||
|
#include "shell/browser/badging/badge_manager.h"
|
||||||
#include "shell/browser/child_web_contents_tracker.h"
|
#include "shell/browser/child_web_contents_tracker.h"
|
||||||
#include "shell/browser/electron_autofill_driver_factory.h"
|
#include "shell/browser/electron_autofill_driver_factory.h"
|
||||||
#include "shell/browser/electron_browser_context.h"
|
#include "shell/browser/electron_browser_context.h"
|
||||||
|
@ -1622,12 +1623,6 @@ void ElectronBrowserClient::BindHostReceiverForRenderer(
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindBadgeManagerFrameReceiver(
|
|
||||||
content::RenderFrameHost* frame,
|
|
||||||
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
|
|
||||||
LOG(WARNING) << "The Chromium Badging API is not available in Electron";
|
|
||||||
}
|
|
||||||
|
|
||||||
void BindElectronBrowser(
|
void BindElectronBrowser(
|
||||||
content::RenderFrameHost* frame_host,
|
content::RenderFrameHost* frame_host,
|
||||||
mojo::PendingReceiver<electron::mojom::ElectronBrowser> receiver) {
|
mojo::PendingReceiver<electron::mojom::ElectronBrowser> receiver) {
|
||||||
|
@ -1671,7 +1666,7 @@ void ElectronBrowserClient::RegisterBrowserInterfaceBindersForFrame(
|
||||||
map->Add<network_hints::mojom::NetworkHintsHandler>(
|
map->Add<network_hints::mojom::NetworkHintsHandler>(
|
||||||
base::BindRepeating(&BindNetworkHintsHandler));
|
base::BindRepeating(&BindNetworkHintsHandler));
|
||||||
map->Add<blink::mojom::BadgeService>(
|
map->Add<blink::mojom::BadgeService>(
|
||||||
base::BindRepeating(&BindBadgeManagerFrameReceiver));
|
base::BindRepeating(&badging::BadgeManager::BindFrameReceiver));
|
||||||
map->Add<electron::mojom::ElectronBrowser>(
|
map->Add<electron::mojom::ElectronBrowser>(
|
||||||
base::BindRepeating(&BindElectronBrowser));
|
base::BindRepeating(&BindElectronBrowser));
|
||||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||||
|
|
|
@ -1209,7 +1209,9 @@ void NativeWindowViews::SetProgressBar(double progress,
|
||||||
void NativeWindowViews::SetOverlayIcon(const gfx::Image& overlay,
|
void NativeWindowViews::SetOverlayIcon(const gfx::Image& overlay,
|
||||||
const std::string& description) {
|
const std::string& description) {
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
taskbar_host_.SetOverlayIcon(GetAcceleratedWidget(), overlay, description);
|
SkBitmap overlay_bitmap = overlay.AsBitmap();
|
||||||
|
taskbar_host_.SetOverlayIcon(GetAcceleratedWidget(), overlay_bitmap,
|
||||||
|
description);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,13 +166,12 @@ bool TaskbarHost::SetProgressBar(HWND window,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TaskbarHost::SetOverlayIcon(HWND window,
|
bool TaskbarHost::SetOverlayIcon(HWND window,
|
||||||
const gfx::Image& overlay,
|
const SkBitmap& overlay,
|
||||||
const std::string& text) {
|
const std::string& text) {
|
||||||
if (!InitializeTaskbar())
|
if (!InitializeTaskbar())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
base::win::ScopedHICON icon(
|
base::win::ScopedHICON icon(IconUtil::CreateHICONFromSkBitmap(overlay));
|
||||||
IconUtil::CreateHICONFromSkBitmap(overlay.AsBitmap()));
|
|
||||||
return SUCCEEDED(taskbar_->SetOverlayIcon(window, icon.get(),
|
return SUCCEEDED(taskbar_->SetOverlayIcon(window, icon.get(),
|
||||||
base::UTF8ToUTF16(text).c_str()));
|
base::UTF8ToUTF16(text).c_str()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ class TaskbarHost {
|
||||||
|
|
||||||
// Set the overlay icon in taskbar.
|
// Set the overlay icon in taskbar.
|
||||||
bool SetOverlayIcon(HWND window,
|
bool SetOverlayIcon(HWND window,
|
||||||
const gfx::Image& overlay,
|
const SkBitmap& overlay,
|
||||||
const std::string& text);
|
const std::string& text);
|
||||||
|
|
||||||
// Set the region of the window to show as a thumbnail in taskbar.
|
// Set the region of the window to show as a thumbnail in taskbar.
|
||||||
|
|
|
@ -551,46 +551,42 @@ describe('app module', () => {
|
||||||
const platformIsNotSupported =
|
const platformIsNotSupported =
|
||||||
(process.platform === 'win32') ||
|
(process.platform === 'win32') ||
|
||||||
(process.platform === 'linux' && !app.isUnityRunning());
|
(process.platform === 'linux' && !app.isUnityRunning());
|
||||||
const platformIsSupported = !platformIsNotSupported;
|
|
||||||
|
|
||||||
const expectedBadgeCount = 42;
|
const expectedBadgeCount = 42;
|
||||||
|
|
||||||
after(() => { app.badgeCount = 0; });
|
after(() => { app.badgeCount = 0; });
|
||||||
|
|
||||||
describe('on supported platform', () => {
|
ifdescribe(!platformIsNotSupported)('on supported platform', () => {
|
||||||
it('with properties', () => {
|
describe('with properties', () => {
|
||||||
it('sets a badge count', function () {
|
it('sets a badge count', function () {
|
||||||
if (platformIsNotSupported) return this.skip();
|
|
||||||
|
|
||||||
app.badgeCount = expectedBadgeCount;
|
app.badgeCount = expectedBadgeCount;
|
||||||
expect(app.badgeCount).to.equal(expectedBadgeCount);
|
expect(app.badgeCount).to.equal(expectedBadgeCount);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with functions', () => {
|
describe('with functions', () => {
|
||||||
it('sets a badge count', function () {
|
it('sets a numerical badge count', function () {
|
||||||
if (platformIsNotSupported) return this.skip();
|
|
||||||
|
|
||||||
app.setBadgeCount(expectedBadgeCount);
|
app.setBadgeCount(expectedBadgeCount);
|
||||||
expect(app.getBadgeCount()).to.equal(expectedBadgeCount);
|
expect(app.getBadgeCount()).to.equal(expectedBadgeCount);
|
||||||
});
|
});
|
||||||
|
it('sets an non numeric (dot) badge count', function () {
|
||||||
|
app.setBadgeCount();
|
||||||
|
// Badge count should be zero when non numeric (dot) is requested
|
||||||
|
expect(app.getBadgeCount()).to.equal(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('on unsupported platform', () => {
|
ifdescribe(process.platform !== 'win32' && platformIsNotSupported)('on unsupported platform', () => {
|
||||||
it('with properties', () => {
|
describe('with properties', () => {
|
||||||
it('does not set a badge count', function () {
|
it('does not set a badge count', function () {
|
||||||
if (platformIsSupported) return this.skip();
|
|
||||||
|
|
||||||
app.badgeCount = 9999;
|
app.badgeCount = 9999;
|
||||||
expect(app.badgeCount).to.equal(0);
|
expect(app.badgeCount).to.equal(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with functions', () => {
|
describe('with functions', () => {
|
||||||
it('does not set a badge count)', function () {
|
it('does not set a badge count)', function () {
|
||||||
if (platformIsSupported) return this.skip();
|
|
||||||
|
|
||||||
app.setBadgeCount(9999);
|
app.setBadgeCount(9999);
|
||||||
expect(app.getBadgeCount()).to.equal(0);
|
expect(app.getBadgeCount()).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1550,3 +1550,57 @@ describe('navigator.clipboard', () => {
|
||||||
expect(clipboard).to.not.equal('Read permission denied.');
|
expect(clipboard).to.not.equal('Read permission denied.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const fireAppBadgeAction: any = (action: string, value: any) => {
|
||||||
|
return w.webContents.executeJavaScript(`
|
||||||
|
navigator.${action}AppBadge(${value}).then(() => 'success').catch(err => err.message)`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// For some reason on macOS changing the badge count doesn't happen right away, so wait
|
||||||
|
// until it changes.
|
||||||
|
async function waitForBadgeCount (value: number) {
|
||||||
|
let badgeCount = app.getBadgeCount();
|
||||||
|
while (badgeCount !== value) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
badgeCount = app.getBadgeCount();
|
||||||
|
}
|
||||||
|
return badgeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -22,6 +22,13 @@ describe('chromium feature', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
navigator.setAppBadge(42);
|
navigator.setAppBadge(42);
|
||||||
}).to.not.throw();
|
}).to.not.throw();
|
||||||
|
expect(() => {
|
||||||
|
// setAppBadge with no argument should show dot
|
||||||
|
navigator.setAppBadge();
|
||||||
|
}).to.not.throw();
|
||||||
|
expect(() => {
|
||||||
|
navigator.clearAppBadge();
|
||||||
|
}).to.not.throw();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue