pake-cli 3.11.4 → 3.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,7 @@
20
20
  "start_to_tray": false,
21
21
  "force_internal_navigation": false,
22
22
  "internal_url_regex": "",
23
+ "enable_find": false,
23
24
  "new_window": false
24
25
  }
25
26
  ],
@@ -26,6 +26,8 @@ pub struct WindowConfig {
26
26
  pub force_internal_navigation: bool,
27
27
  #[serde(default)]
28
28
  pub internal_url_regex: String,
29
+ #[serde(default)]
30
+ pub enable_find: bool,
29
31
  #[serde(default = "default_zoom")]
30
32
  pub zoom: u32,
31
33
  #[serde(default)]
@@ -2,6 +2,7 @@ use crate::util::{check_file_or_append, get_download_message_with_lang, show_toa
2
2
  use std::fs::{self, File};
3
3
  use std::io::Write;
4
4
  use std::str::FromStr;
5
+ use std::sync::atomic::{AtomicI64, Ordering};
5
6
  use tauri::http::Method;
6
7
  use tauri::{command, AppHandle, Manager, Url, WebviewWindow};
7
8
  use tauri_plugin_http::reqwest::{ClientBuilder, Request};
@@ -9,6 +10,62 @@ use tauri_plugin_http::reqwest::{ClientBuilder, Request};
9
10
  #[cfg(target_os = "macos")]
10
11
  use tauri::Theme;
11
12
 
13
+ static BADGE_COUNT: AtomicI64 = AtomicI64::new(0);
14
+ const MAX_BADGE_COUNT: i64 = 99_999;
15
+ const MAX_BADGE_LABEL_CHARS: usize = 16;
16
+
17
+ fn normalize_badge_count(count: Option<i64>) -> Option<i64> {
18
+ count.filter(|n| (1..=MAX_BADGE_COUNT).contains(n))
19
+ }
20
+
21
+ fn normalize_badge_label(label: Option<&str>) -> Result<Option<String>, String> {
22
+ let Some(label) = label.map(str::trim).filter(|label| !label.is_empty()) else {
23
+ return Ok(None);
24
+ };
25
+
26
+ if label.chars().count() > MAX_BADGE_LABEL_CHARS {
27
+ return Err(format!(
28
+ "Badge label must be {MAX_BADGE_LABEL_CHARS} characters or fewer"
29
+ ));
30
+ }
31
+
32
+ Ok(Some(label.to_string()))
33
+ }
34
+
35
+ fn apply_badge(app: &AppHandle, count: Option<i64>) -> Result<(), String> {
36
+ let label = normalize_badge_count(count).map(|n| n.to_string());
37
+ apply_badge_label(app, label.as_deref())
38
+ }
39
+
40
+ #[cfg(target_os = "macos")]
41
+ fn apply_badge_label(app: &AppHandle, label: Option<&str>) -> Result<(), String> {
42
+ use objc2::MainThreadMarker;
43
+ use objc2_app_kit::NSApplication;
44
+ use objc2_foundation::NSString;
45
+
46
+ let label = label.map(str::to_owned);
47
+ app.run_on_main_thread(move || {
48
+ let Some(mtm) = MainThreadMarker::new() else {
49
+ return;
50
+ };
51
+ let dock_tile = NSApplication::sharedApplication(mtm).dockTile();
52
+ let ns_label = label.as_deref().map(NSString::from_str);
53
+ dock_tile.setBadgeLabel(ns_label.as_deref());
54
+ })
55
+ .map_err(|e| format!("Failed to dispatch dock badge update: {e}"))
56
+ }
57
+
58
+ #[cfg(not(target_os = "macos"))]
59
+ fn apply_badge_label(app: &AppHandle, label: Option<&str>) -> Result<(), String> {
60
+ let window = app
61
+ .get_webview_window("pake")
62
+ .ok_or("Main window not found")?;
63
+ let count = label.and_then(|s| s.parse::<i64>().ok());
64
+ window
65
+ .set_badge_count(count)
66
+ .map_err(|e| format!("Failed to set badge count: {e}"))
67
+ }
68
+
12
69
  #[derive(serde::Deserialize)]
13
70
  pub struct DownloadFileParams {
14
71
  url: String,
@@ -144,6 +201,34 @@ pub fn send_notification(app: AppHandle, params: NotificationParams) -> Result<(
144
201
  Ok(())
145
202
  }
146
203
 
204
+ #[command]
205
+ pub fn set_dock_badge(app: AppHandle, count: Option<i64>) -> Result<(), String> {
206
+ let normalized = normalize_badge_count(count);
207
+ BADGE_COUNT.store(normalized.unwrap_or(0), Ordering::SeqCst);
208
+ apply_badge(&app, normalized)
209
+ }
210
+
211
+ #[command]
212
+ pub fn increment_dock_badge(app: AppHandle) -> Result<(), String> {
213
+ let current = BADGE_COUNT.load(Ordering::SeqCst);
214
+ let next = current.saturating_add(1).clamp(1, MAX_BADGE_COUNT);
215
+ BADGE_COUNT.store(next, Ordering::SeqCst);
216
+ apply_badge(&app, Some(next))
217
+ }
218
+
219
+ #[command]
220
+ pub fn clear_dock_badge(app: AppHandle) -> Result<(), String> {
221
+ BADGE_COUNT.store(0, Ordering::SeqCst);
222
+ apply_badge(&app, None)
223
+ }
224
+
225
+ #[command]
226
+ pub fn set_dock_badge_label(app: AppHandle, label: Option<String>) -> Result<(), String> {
227
+ BADGE_COUNT.store(0, Ordering::SeqCst);
228
+ let label = normalize_badge_label(label.as_deref())?;
229
+ apply_badge_label(&app, label.as_deref())
230
+ }
231
+
147
232
  #[command]
148
233
  pub async fn update_theme_mode(app: AppHandle, mode: String) {
149
234
  #[cfg(target_os = "macos")]
@@ -6,24 +6,36 @@ use tauri::menu::{AboutMetadata, Menu, MenuItem, PredefinedMenuItem, Submenu};
6
6
  use tauri::{AppHandle, Manager, Wry};
7
7
  use tauri_plugin_opener::OpenerExt;
8
8
 
9
- pub fn get_menu(app: &AppHandle<Wry>, allow_multi_window: bool) -> tauri::Result<Menu<Wry>> {
9
+ pub fn set_app_menu(
10
+ app: &AppHandle<Wry>,
11
+ allow_multi_window: bool,
12
+ enable_find: bool,
13
+ ) -> tauri::Result<()> {
10
14
  let pake_version = env!("CARGO_PKG_VERSION");
11
15
  let pake_menu_item_title = format!("Built with Pake V{}", pake_version);
12
16
 
17
+ let window_submenu = window_menu(app)?;
18
+
13
19
  let menu = Menu::with_items(
14
20
  app,
15
21
  &[
16
22
  &app_menu(app)?,
17
23
  &file_menu(app, allow_multi_window)?,
18
- &edit_menu(app)?,
24
+ &edit_menu(app, enable_find)?,
19
25
  &view_menu(app)?,
20
26
  &navigation_menu(app)?,
21
- &window_menu(app)?,
27
+ &window_submenu,
22
28
  &help_menu(app, &pake_menu_item_title)?,
23
29
  ],
24
30
  )?;
25
31
 
26
- Ok(menu)
32
+ app.set_menu(menu)?;
33
+
34
+ // AppKit injects Move & Resize, Fill, Center, Full Screen Tile, and
35
+ // window-cycling once the submenu is registered as the windows menu.
36
+ window_submenu.set_as_windows_menu_for_nsapp()?;
37
+
38
+ Ok(())
27
39
  }
28
40
 
29
41
  fn app_menu(app: &AppHandle<Wry>) -> tauri::Result<Submenu<Wry>> {
@@ -69,7 +81,7 @@ fn file_menu(app: &AppHandle<Wry>, allow_multi_window: bool) -> tauri::Result<Su
69
81
  Ok(file_menu)
70
82
  }
71
83
 
72
- fn edit_menu(app: &AppHandle<Wry>) -> tauri::Result<Submenu<Wry>> {
84
+ fn edit_menu(app: &AppHandle<Wry>, enable_find: bool) -> tauri::Result<Submenu<Wry>> {
73
85
  let edit_menu = Submenu::new(app, "Edit", true)?;
74
86
  edit_menu.append(&PredefinedMenuItem::undo(app, None)?)?;
75
87
  edit_menu.append(&PredefinedMenuItem::redo(app, None)?)?;
@@ -86,6 +98,30 @@ fn edit_menu(app: &AppHandle<Wry>) -> tauri::Result<Submenu<Wry>> {
86
98
  )?)?;
87
99
  edit_menu.append(&PredefinedMenuItem::select_all(app, None)?)?;
88
100
  edit_menu.append(&PredefinedMenuItem::separator(app)?)?;
101
+ if enable_find {
102
+ edit_menu.append(&MenuItem::with_id(
103
+ app,
104
+ "find",
105
+ "Find",
106
+ true,
107
+ Some("CmdOrCtrl+F"),
108
+ )?)?;
109
+ edit_menu.append(&MenuItem::with_id(
110
+ app,
111
+ "find_next",
112
+ "Find Next",
113
+ true,
114
+ Some("CmdOrCtrl+G"),
115
+ )?)?;
116
+ edit_menu.append(&MenuItem::with_id(
117
+ app,
118
+ "find_previous",
119
+ "Find Previous",
120
+ true,
121
+ Some("CmdOrCtrl+Shift+G"),
122
+ )?)?;
123
+ edit_menu.append(&PredefinedMenuItem::separator(app)?)?;
124
+ }
89
125
  edit_menu.append(&MenuItem::with_id(
90
126
  app,
91
127
  "copy_url",
@@ -255,6 +291,21 @@ pub fn handle_menu_click(app_handle: &AppHandle, id: &str) {
255
291
  let _ = window.eval("triggerPasteAsPlainText()");
256
292
  }
257
293
  }
294
+ "find" => {
295
+ if let Some(window) = app_handle.get_webview_window("pake") {
296
+ let _ = window.eval("window.pakeFind?.open()");
297
+ }
298
+ }
299
+ "find_next" => {
300
+ if let Some(window) = app_handle.get_webview_window("pake") {
301
+ let _ = window.eval("window.pakeFind?.next()");
302
+ }
303
+ }
304
+ "find_previous" => {
305
+ if let Some(window) = app_handle.get_webview_window("pake") {
306
+ let _ = window.eval("window.pakeFind?.previous()");
307
+ }
308
+ }
258
309
  "clear_cache_restart" => {
259
310
  if let Some(window) = app_handle.get_webview_window("pake") {
260
311
  if let Ok(_) = window.clear_all_browsing_data() {
@@ -119,49 +119,54 @@ pub fn set_global_shortcut(
119
119
  let app_handle = app.clone();
120
120
  let shortcut_hotkey = match Shortcut::from_str(&shortcut) {
121
121
  Ok(s) => s,
122
- Err(_) => return Ok(()),
122
+ Err(error) => {
123
+ eprintln!("[Pake] Invalid activation shortcut '{shortcut}': {error}");
124
+ return Ok(());
125
+ }
123
126
  };
124
127
  let last_triggered = Arc::new(Mutex::new(Instant::now()));
125
128
 
126
- app_handle
127
- .plugin(
128
- tauri_plugin_global_shortcut::Builder::new()
129
- .with_handler({
130
- let last_triggered = Arc::clone(&last_triggered);
131
- move |app, event, _shortcut| {
132
- let Ok(mut last_triggered) = last_triggered.lock() else {
133
- return;
134
- };
135
- if Instant::now().duration_since(*last_triggered)
136
- < Duration::from_millis(300)
137
- {
138
- return;
139
- }
140
- *last_triggered = Instant::now();
129
+ if let Err(error) = app_handle.plugin(
130
+ tauri_plugin_global_shortcut::Builder::new()
131
+ .with_handler({
132
+ let last_triggered = Arc::clone(&last_triggered);
133
+ move |app, event, _shortcut| {
134
+ let Ok(mut last_triggered) = last_triggered.lock() else {
135
+ return;
136
+ };
137
+ if Instant::now().duration_since(*last_triggered) < Duration::from_millis(300) {
138
+ return;
139
+ }
140
+ *last_triggered = Instant::now();
141
141
 
142
- if shortcut_hotkey.eq(event) {
143
- if let Some(window) = app.get_webview_window("pake") {
144
- let is_visible = window.is_visible().unwrap_or(false);
145
- if is_visible {
146
- let _ = window.hide();
147
- } else {
148
- let _ = window.show();
149
- let _ = window.set_focus();
150
- #[cfg(target_os = "linux")]
151
- if _init_fullscreen && !window.is_fullscreen().unwrap_or(false)
152
- {
153
- let _ = window.set_fullscreen(true);
154
- }
142
+ if shortcut_hotkey.eq(event) {
143
+ if let Some(window) = app.get_webview_window("pake") {
144
+ let is_visible = window.is_visible().unwrap_or(false);
145
+ if is_visible {
146
+ let _ = window.hide();
147
+ } else {
148
+ let _ = window.show();
149
+ let _ = window.set_focus();
150
+ #[cfg(target_os = "linux")]
151
+ if _init_fullscreen && !window.is_fullscreen().unwrap_or(false) {
152
+ let _ = window.set_fullscreen(true);
155
153
  }
156
154
  }
157
155
  }
158
156
  }
159
- })
160
- .build(),
161
- )
162
- .expect("Failed to set global shortcut");
157
+ }
158
+ })
159
+ .build(),
160
+ ) {
161
+ eprintln!(
162
+ "[Pake] Failed to register global shortcut plugin '{shortcut}': {error}; continuing without it."
163
+ );
164
+ return Ok(());
165
+ }
163
166
 
164
- let _ = app.global_shortcut().register(shortcut_hotkey);
167
+ if let Err(error) = app.global_shortcut().register(shortcut_hotkey) {
168
+ eprintln!("[Pake] Failed to bind global shortcut '{shortcut}': {error}");
169
+ }
165
170
 
166
171
  Ok(())
167
172
  }
@@ -50,8 +50,12 @@ impl MultiWindowState {
50
50
  }
51
51
  }
52
52
 
53
- pub fn set_window(app: &AppHandle, config: &PakeConfig, tauri_config: &Config) -> WebviewWindow {
54
- build_window_with_label(app, config, tauri_config, "pake").expect("Failed to build window")
53
+ pub fn set_window(
54
+ app: &AppHandle,
55
+ config: &PakeConfig,
56
+ tauri_config: &Config,
57
+ ) -> tauri::Result<WebviewWindow> {
58
+ build_window_with_label(app, config, tauri_config, "pake")
55
59
  }
56
60
 
57
61
  pub fn open_additional_window(app: &AppHandle) -> tauri::Result<WebviewWindow> {
@@ -122,10 +126,12 @@ fn build_window_with_label(
122
126
  tauri_config: &Config,
123
127
  label: &str,
124
128
  ) -> tauri::Result<WebviewWindow> {
125
- let window_config = config
126
- .windows
127
- .first()
128
- .expect("At least one window configuration is required");
129
+ let window_config = config.windows.first().ok_or_else(|| {
130
+ tauri::Error::Io(std::io::Error::new(
131
+ std::io::ErrorKind::InvalidData,
132
+ "pake.json must define at least one window configuration",
133
+ ))
134
+ })?;
129
135
  let url = match window_config.url_type.as_str() {
130
136
  "web" => {
131
137
  let parsed = window_config.url.parse().map_err(|err| {
@@ -177,12 +183,14 @@ fn build_window(
177
183
  .product_name
178
184
  .clone()
179
185
  .unwrap_or_else(|| "pake".to_string());
180
- let _data_dir = get_data_dir(app, package_name);
186
+ let _data_dir = get_data_dir(app, package_name).map_err(tauri::Error::Io)?;
181
187
 
182
- let window_config = config
183
- .windows
184
- .first()
185
- .expect("At least one window configuration is required");
188
+ let window_config = config.windows.first().ok_or_else(|| {
189
+ tauri::Error::Io(std::io::Error::new(
190
+ std::io::ErrorKind::InvalidData,
191
+ "pake.json must define at least one window configuration",
192
+ ))
193
+ })?;
186
194
 
187
195
  let user_agent = config.user_agent.get();
188
196
 
@@ -273,10 +281,15 @@ fn build_window(
273
281
  });
274
282
  }
275
283
 
276
- // Add initialization scripts
284
+ // Add initialization scripts. Order matters: pakeConfig must land before
285
+ // any script that reads it (e.g. fullscreen polyfill checks for an opt-out
286
+ // flag), and toast must register `window.pakeToast` before Rust code
287
+ // calls show_toast().
277
288
  window_builder = window_builder
278
289
  .initialization_script(&config_script)
279
- .initialization_script(include_str!("../inject/component.js"))
290
+ .initialization_script(include_str!("../inject/find.js"))
291
+ .initialization_script(include_str!("../inject/toast.js"))
292
+ .initialization_script(include_str!("../inject/fullscreen.js"))
280
293
  .initialization_script(include_str!("../inject/event.js"))
281
294
  .initialization_script(include_str!("../inject/style.js"))
282
295
  .initialization_script(include_str!("../inject/theme_refresh.js"))
@@ -391,7 +404,9 @@ fn build_window(
391
404
  }
392
405
 
393
406
  if let Some(features) = new_window_features {
394
- // macOS popup webviews must reuse the opener webview configuration.
407
+ // Reuse only opener-provided position/size on macOS; sharing the opener
408
+ // WKWebViewConfiguration triggers duplicate WKScriptMessageHandler
409
+ // registrations on macOS 26+ and crashes the app (issue #1194).
395
410
  #[cfg(target_os = "macos")]
396
411
  {
397
412
  if let Some(position) = features.position() {
@@ -402,9 +417,7 @@ fn build_window(
402
417
  window_builder = window_builder.inner_size(size.width, size.height);
403
418
  }
404
419
 
405
- window_builder = window_builder
406
- .with_webview_configuration(features.opener().target_configuration.clone())
407
- .focused(true);
420
+ window_builder = window_builder.focused(true);
408
421
  }
409
422
 
410
423
  #[cfg(not(target_os = "macos"))]
@@ -417,3 +430,37 @@ fn build_window(
417
430
 
418
431
  window_builder.build()
419
432
  }
433
+
434
+ #[cfg(all(test, target_os = "windows"))]
435
+ mod proxy_arg_tests {
436
+ use super::*;
437
+
438
+ fn parse(url: &str) -> Url {
439
+ Url::from_str(url).unwrap()
440
+ }
441
+
442
+ #[test]
443
+ fn http_url_with_explicit_port() {
444
+ let arg = build_proxy_browser_arg(&parse("http://127.0.0.1:7890")).unwrap();
445
+ assert_eq!(arg, "--proxy-server=http://127.0.0.1:7890");
446
+ }
447
+
448
+ #[test]
449
+ fn http_url_uses_default_port_when_missing() {
450
+ let arg = build_proxy_browser_arg(&parse("http://proxy.local")).unwrap();
451
+ assert_eq!(arg, "--proxy-server=http://proxy.local:80");
452
+ }
453
+
454
+ #[test]
455
+ fn socks5_url_uses_default_port_when_missing() {
456
+ let arg = build_proxy_browser_arg(&parse("socks5://proxy.local")).unwrap();
457
+ assert_eq!(arg, "--proxy-server=socks5://proxy.local:1080");
458
+ }
459
+
460
+ #[test]
461
+ fn https_scheme_is_not_supported_yet() {
462
+ // https proxies fall back to platform proxy_url; we only emit a CLI arg
463
+ // for http/socks5 today.
464
+ assert!(build_proxy_browser_arg(&parse("https://proxy.local:8443")).is_none());
465
+ }
466
+ }
@@ -1025,10 +1025,46 @@ document.addEventListener("DOMContentLoaded", () => {
1025
1025
  });
1026
1026
  });
1027
1027
 
1028
- document.addEventListener("DOMContentLoaded", function () {
1028
+ // Bridge the Web Notification + Web Badging APIs to Pake's Rust commands so
1029
+ // pages running inside the webview can drive the macOS dock badge (and
1030
+ // taskbar badge on Linux/Windows). Installs synchronously instead of waiting
1031
+ // for DOMContentLoaded so feature-detection on Notification/setAppBadge
1032
+ // returns the polyfill before site scripts run.
1033
+ (function () {
1034
+ const invoke = window.__TAURI__?.core?.invoke;
1035
+ if (!invoke) return;
1036
+
1029
1037
  let permVal = "granted";
1030
1038
  let lastNotifTime = 0;
1031
1039
  let lastNotif = null;
1040
+ // Pages that drive the badge directly via setAppBadge own its lifecycle;
1041
+ // notifications-driven counts auto-clear on the next user interaction.
1042
+ let pageManagedBadge = false;
1043
+ let autoBadgeActive = false;
1044
+
1045
+ const normalizeBadgeCount = (count) => {
1046
+ if (typeof count !== "number" || !Number.isFinite(count)) {
1047
+ throw new TypeError("Badge count must be a finite number.");
1048
+ }
1049
+ const normalized = Math.floor(count);
1050
+ return normalized > 0 ? Math.min(normalized, 99999) : null;
1051
+ };
1052
+ const setBadge = (count) => {
1053
+ pageManagedBadge = true;
1054
+ autoBadgeActive = false;
1055
+ return invoke("set_dock_badge", { count }).catch(() => {});
1056
+ };
1057
+ const clearBadge = () => invoke("clear_dock_badge").catch(() => {});
1058
+ const setLabel = (label) => {
1059
+ pageManagedBadge = true;
1060
+ autoBadgeActive = false;
1061
+ return invoke("set_dock_badge_label", { label }).catch(() => {});
1062
+ };
1063
+ const incrementAutoBadge = () => {
1064
+ if (pageManagedBadge) return Promise.resolve();
1065
+ autoBadgeActive = true;
1066
+ return invoke("increment_dock_badge").catch(() => {});
1067
+ };
1032
1068
 
1033
1069
  window.addEventListener("focus", () => {
1034
1070
  if (lastNotif?.onclick && Date.now() - lastNotifTime < 5000) {
@@ -1037,11 +1073,17 @@ document.addEventListener("DOMContentLoaded", function () {
1037
1073
  }
1038
1074
  });
1039
1075
 
1040
- window.Notification = function (title, options) {
1041
- const { invoke } = window.__TAURI__.core;
1076
+ const clearAutoBadge = () => {
1077
+ if (pageManagedBadge || !autoBadgeActive) return;
1078
+ autoBadgeActive = false;
1079
+ clearBadge();
1080
+ };
1081
+ document.addEventListener("click", clearAutoBadge, true);
1082
+ document.addEventListener("keydown", clearAutoBadge, true);
1083
+
1084
+ const wrappedNotification = function (title, options) {
1042
1085
  const body = options?.body || "";
1043
1086
  let icon = options?.icon || "";
1044
-
1045
1087
  if (icon.startsWith("/")) {
1046
1088
  icon = window.location.origin + icon;
1047
1089
  }
@@ -1056,24 +1098,68 @@ document.addEventListener("DOMContentLoaded", function () {
1056
1098
 
1057
1099
  lastNotifTime = Date.now();
1058
1100
  lastNotif = notif;
1059
-
1060
- invoke("send_notification", { params: { title, body, icon } }).then(() => {
1061
- if (notif.onshow) notif.onshow(new Event("show"));
1062
- });
1101
+ invoke("send_notification", { params: { title, body, icon } })
1102
+ .then(() => incrementAutoBadge())
1103
+ .then(() => {
1104
+ if (notif.onshow) notif.onshow(new Event("show"));
1105
+ });
1063
1106
 
1064
1107
  return notif;
1065
1108
  };
1066
1109
 
1067
- window.Notification.requestPermission = async () => "granted";
1068
-
1069
- Object.defineProperty(window.Notification, "permission", {
1110
+ wrappedNotification.requestPermission = async () => "granted";
1111
+ Object.defineProperty(wrappedNotification, "permission", {
1070
1112
  enumerable: true,
1071
1113
  get: () => permVal,
1072
1114
  set: (v) => {
1073
1115
  permVal = v;
1074
1116
  },
1075
1117
  });
1076
- });
1118
+
1119
+ try {
1120
+ Object.defineProperty(window, "Notification", {
1121
+ configurable: true,
1122
+ writable: true,
1123
+ value: wrappedNotification,
1124
+ });
1125
+ } catch (_) {}
1126
+
1127
+ // Web Badging API: https://wicg.github.io/badging/
1128
+ // setAppBadge() with no argument shows an indicator dot; with a number,
1129
+ // shows the count (0 clears). clearAppBadge() removes the badge entirely.
1130
+ const setAppBadge = (count) => {
1131
+ if (count === undefined) return setLabel("•");
1132
+ let normalized;
1133
+ try {
1134
+ normalized = normalizeBadgeCount(count);
1135
+ } catch (error) {
1136
+ return Promise.reject(error);
1137
+ }
1138
+ if (normalized === null) {
1139
+ pageManagedBadge = false;
1140
+ autoBadgeActive = false;
1141
+ return clearBadge();
1142
+ }
1143
+ return setBadge(normalized);
1144
+ };
1145
+ const clearAppBadge = () => {
1146
+ pageManagedBadge = false;
1147
+ autoBadgeActive = false;
1148
+ return clearBadge();
1149
+ };
1150
+ try {
1151
+ Object.defineProperty(navigator, "setAppBadge", {
1152
+ configurable: true,
1153
+ writable: true,
1154
+ value: setAppBadge,
1155
+ });
1156
+ Object.defineProperty(navigator, "clearAppBadge", {
1157
+ configurable: true,
1158
+ writable: true,
1159
+ value: clearAppBadge,
1160
+ });
1161
+ } catch (_) {}
1162
+ })();
1077
1163
 
1078
1164
  function setDefaultZoom() {
1079
1165
  const htmlZoom = window.localStorage.getItem("htmlZoom");