sparkbun 0.2.5 → 0.2.7

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkbun",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Build fast, lightweight, cross-platform desktop apps with TypeScript and Bun.",
5
5
  "license": "MIT",
6
6
  "author": "SparkBun Contributors",
@@ -29,6 +29,12 @@ export type WindowOptionsType<T = undefined> = {
29
29
  preload: string | null;
30
30
  viewsRoot: string | null;
31
31
  renderer: "native" | "cef";
32
+ // Storage partition for the webview's cookies/localStorage/cache/IndexedDB.
33
+ // A value starting with "persist:" is written to disk; any other non-empty
34
+ // value is ephemeral — in-memory only, gone when the process exits — which
35
+ // is what incognito-style/diskless sessions want. Omitted/null falls back to
36
+ // "persist:default" (on-disk).
37
+ partition?: string;
32
38
  rpc?: T;
33
39
  styleMask?: {};
34
40
  // titleBarStyle options:
@@ -105,6 +111,7 @@ export class BrowserWindow<T extends RPCWithTransport = AnyRPC> {
105
111
  html: string | null = null;
106
112
  preload: string | null = null;
107
113
  viewsRoot: string | null = null;
114
+ partition: string | null = null;
108
115
  renderer: "native" | "cef" = "native";
109
116
  transparent: boolean = false;
110
117
  passthrough: boolean = false;
@@ -139,6 +146,7 @@ export class BrowserWindow<T extends RPCWithTransport = AnyRPC> {
139
146
  this.html = options.html || null;
140
147
  this.preload = options.preload || null;
141
148
  this.viewsRoot = options.viewsRoot || null;
149
+ this.partition = options.partition ?? null;
142
150
  this.renderer = options.renderer || defaultOptions.renderer;
143
151
  this.transparent = options.transparent ?? false;
144
152
  this.passthrough = options.passthrough ?? false;
@@ -223,6 +231,7 @@ export class BrowserWindow<T extends RPCWithTransport = AnyRPC> {
223
231
  html: this.html,
224
232
  preload: this.preload,
225
233
  viewsRoot: this.viewsRoot,
234
+ partition: this.partition,
226
235
  // frame: this.frame,
227
236
  renderer: this.renderer,
228
237
  frame: {
@@ -922,17 +922,16 @@ if %rmRetry% GEQ 10 goto rmfailed
922
922
  timeout /t 2 /nobreak >nul
923
923
  goto rmloop
924
924
  :rmfailed
925
- echo Update failed: could not remove "${runningAppWin}" after retries.
926
- echo Files may still be locked by a helper process.
927
- pause
925
+ :: The script runs in a hidden window, so log instead of echo/pause (a hidden
926
+ :: pause would hang invisibly forever).
927
+ echo Update failed: could not remove "${runningAppWin}" after retries. Files may still be locked by a helper process. >> "%~dp0update-error.log"
928
928
  exit /b 1
929
929
  :rmdone
930
930
 
931
931
  :: Move new app to current location (safe now that destination is gone)
932
932
  move "${newAppWin}" "${runningAppWin}"
933
933
  if not exist "${launcherPathWin}" (
934
- echo Update failed: launcher not found at "${launcherPathWin}" after move.
935
- pause
934
+ echo Update failed: launcher not found at "${launcherPathWin}" after move. >> "%~dp0update-error.log"
936
935
  exit /b 1
937
936
  )
938
937
 
@@ -947,24 +946,40 @@ for /f "tokens=1" %%t in ('schtasks /query /fo list ^| findstr /i "SparkBunUpdat
947
946
  schtasks /delete /tn "%%t" /f >nul 2>&1
948
947
  )
949
948
 
950
- :: Delete this update script after a short delay
949
+ :: Delete this update script (and its hidden-window launcher) after a short delay
951
950
  ping -n 2 127.0.0.1 >nul
951
+ del "%~dp0update.vbs" 2>nul
952
952
  del "%~f0"
953
953
  `;
954
954
 
955
955
  await Bun.write(updateScriptPath, updateScript);
956
956
 
957
+ // VBS shim so the batch script runs with a HIDDEN window (style 0).
958
+ // schtasks can't hide a console app's window itself, and without
959
+ // this the whole update — wait loop included — sits in a visible
960
+ // cmd window. wscript.exe is a GUI-subsystem host, so nothing
961
+ // flashes. Errors are logged to update-error.log by the bat.
962
+ const scriptPathWin = updateScriptPath.replace(/\//g, "\\");
963
+ const vbsPath = join(parentDir, "update.vbs");
964
+ const vbsPathWin = vbsPath.replace(/\//g, "\\");
965
+ await Bun.write(
966
+ vbsPath,
967
+ `CreateObject("WScript.Shell").Run "cmd /c ""${scriptPathWin}""", 0, False\r\n`,
968
+ );
969
+
957
970
  // Use Windows Task Scheduler to run the update script independently
958
971
  // This ensures the script runs even after the app exits
959
- const scriptPathWin = updateScriptPath.replace(/\//g, "\\");
960
972
  const taskName = `SparkBunUpdate_${Date.now()}`;
961
973
 
962
- // Create a scheduled task that runs immediately and deletes itself
974
+ // Create a scheduled task that runs immediately and deletes itself.
975
+ // windowsHide stops the schtasks invocations themselves from
976
+ // flashing console windows (the app is a GUI-subsystem process,
977
+ // so each unhidden child would allocate a fresh visible console).
963
978
  execSync(
964
- `schtasks /create /tn "${taskName}" /tr "cmd /c \\"${scriptPathWin}\\"" /sc once /st 00:00 /f`,
965
- { stdio: "ignore" },
979
+ `schtasks /create /tn "${taskName}" /tr "wscript.exe //B \\"${vbsPathWin}\\"" /sc once /st 00:00 /f`,
980
+ { stdio: "ignore", windowsHide: true },
966
981
  );
967
- execSync(`schtasks /run /tn "${taskName}"`, { stdio: "ignore" });
982
+ execSync(`schtasks /run /tn "${taskName}"`, { stdio: "ignore", windowsHide: true });
968
983
  // The task will be cleaned up by Windows after it runs, or we delete it in the batch script
969
984
 
970
985
  // Use quit() for graceful shutdown - this closes all windows and processes
package/src/cli/index.ts CHANGED
@@ -1313,6 +1313,30 @@ function escapeXml(str: string): string {
1313
1313
  .replace(/'/g, "&apos;");
1314
1314
  }
1315
1315
 
1316
+ /**
1317
+ * Flip the PE subsystem from CONSOLE (3) to WINDOWS GUI (2) so the exe never
1318
+ * opens a console window. Byte-identical to `editbin /SUBSYSTEM:WINDOWS`.
1319
+ *
1320
+ * This exists because Bun's `compile.windows.hideConsole` is a silent no-op:
1321
+ * the only code that applies it sits after a switch in which every prong
1322
+ * returns early (oven-sh/bun#19916; fix PR oven-sh/bun#20338 unmerged as of
1323
+ * Bun 1.3.14). Bun's intended implementation writes the exact same byte at the
1324
+ * exact same offset, so when upstream merges the fix, `hideConsole: true`
1325
+ * takes over and this patch degrades to a no-op (the `current !== 2` check).
1326
+ *
1327
+ * Unlike Bun's flag, this also works when cross-compiling from macOS/Linux
1328
+ * (Bun only applies windows options when the build host is Windows).
1329
+ *
1330
+ * Must run AFTER all other PE edits (payload injection, rescle metadata) and
1331
+ * BEFORE any future code signing — mutating a signed exe invalidates the
1332
+ * signature.
1333
+ *
1334
+ * Historical note: a May 2026 commit (cf58be7) blamed hideConsole for breaking
1335
+ * the ShellExecuteW("runas") elevation flow. Since hideConsole never touched
1336
+ * the binary, that breakage came from elsewhere (likely the experimental
1337
+ * signature-stripping variant of this function that was being tested at the
1338
+ * same time and was reverted in the same commit).
1339
+ */
1316
1340
  function patchPeSubsystem(exePath: string): void {
1317
1341
  const buf = readFileSync(exePath);
1318
1342
  const peOffset = buf.readUInt32LE(0x3c);
@@ -2144,8 +2168,9 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
2144
2168
  if (targetOS === "win") {
2145
2169
  if (OS !== "win") {
2146
2170
  console.warn(
2147
- `\n⚠️ Cross-compiling for Windows: icon, hideConsole, and PE metadata (title, version, publisher, etc.) will be ignored.\n` +
2171
+ `\n⚠️ Cross-compiling for Windows: icon and PE metadata (title, version, publisher, etc.) will be ignored.\n` +
2148
2172
  ` Bun's Windows-specific compile options require building on a Windows host.\n` +
2173
+ ` (The console window is still hidden — the PE subsystem patch runs on any host.)\n` +
2149
2174
  ` See: https://bun.com/docs/bundler/executables#windows-specific-flags\n`
2150
2175
  );
2151
2176
  } else {
@@ -3177,6 +3202,8 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3177
3202
  hash,
3178
3203
  config,
3179
3204
  projectRoot,
3205
+ targetOS,
3206
+ currentTarget.arch,
3180
3207
  );
3181
3208
  artifactsToUpload.push(installerPath);
3182
3209
  }
@@ -3199,8 +3226,10 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3199
3226
  // the download button or display on your marketing site or in the app.
3200
3227
  version: config.app.version,
3201
3228
  hash: hash.toString(),
3202
- platform: OS,
3203
- arch: ARCH,
3229
+ // The TARGET platform of these artifacts (host != target when
3230
+ // cross-compiling, e.g. --target=win-x64 on macOS).
3231
+ platform: targetOS,
3232
+ arch: currentTarget.arch,
3204
3233
  // channel: buildEnvironment,
3205
3234
  // baseUrl: config.release.baseUrl
3206
3235
  });
@@ -3681,9 +3710,14 @@ ${archiveExports.join("\n")}
3681
3710
  }
3682
3711
  }
3683
3712
  installerCompileOptions.windows = {
3684
- // Don't set hideConsole it conflicts with the ShellExecuteW("runas")
3685
- // elevation flow in the wrapper template. The PE subsystem patch
3686
- // (CONSOLE -> WINDOWS) applied after compilation hides the console.
3713
+ // Currently a no-op in Bun (oven-sh/bun#19916 the subsystem edit is
3714
+ // unreachable code; fix PR #20338 unmerged as of 1.3.14), so the
3715
+ // console is actually hidden by patchPeSubsystem() below. Set it
3716
+ // anyway: it's harmless today and becomes the primary mechanism the
3717
+ // day Bun fixes it (the patch then no-ops). See patchPeSubsystem's
3718
+ // doc comment for why the old "conflicts with elevation" story here
3719
+ // was a misattribution.
3720
+ hideConsole: true,
3687
3721
  ...(icoPath && { icon: icoPath }),
3688
3722
  title: `${installerName} Setup`,
3689
3723
  version: installerVersion,
@@ -4318,9 +4352,16 @@ ${archiveExports.join("\n")}
4318
4352
  hash: string,
4319
4353
  config: any,
4320
4354
  projectRoot: string,
4355
+ // The TARGET platform being built for — not the build host. These used to
4356
+ // be derived from the host OS/ARCH globals, so cross-compiling (e.g.
4357
+ // `--target=win-x64` on macOS) wrongly built a host-platform installer
4358
+ // whose extensionless outfile then collided with the app bundle directory
4359
+ // ("is a directory" build failure).
4360
+ targetOS: "win" | "linux" | "macos",
4361
+ targetArch: string,
4321
4362
  ): Promise<string> {
4322
- const targetOSName = OS === "macos" ? "darwin" : OS === "win" ? "windows" : "linux";
4323
- const isWindows = OS === "win";
4363
+ const targetOSName = targetOS === "macos" ? "darwin" : targetOS === "win" ? "windows" : "linux";
4364
+ const isWindows = targetOS === "win";
4324
4365
 
4325
4366
  const setupFileName = isWindows
4326
4367
  ? getWindowsSetupFileName(config.app.name, buildEnvironment)
@@ -4337,9 +4378,9 @@ ${archiveExports.join("\n")}
4337
4378
  // Copy archive
4338
4379
  copyFileSync(compressedTarPath, join(stagingDir, "app-archive.tar.gz"));
4339
4380
 
4340
- // Write metadata
4341
- const platformConfig = OS === "macos" ? config.build?.mac
4342
- : OS === "win" ? config.build?.win
4381
+ // Write metadata (platform config of the TARGET, not the build host)
4382
+ const platformConfig = targetOS === "macos" ? config.build?.mac
4383
+ : targetOS === "win" ? config.build?.win
4343
4384
  : config.build?.linux;
4344
4385
  const metadata = {
4345
4386
  identifier: config.app.identifier,
@@ -4356,11 +4397,18 @@ ${archiveExports.join("\n")}
4356
4397
  copyFileSync(templatePath, join(stagingDir, "installer.ts"));
4357
4398
 
4358
4399
  const installerCompileOptions: any = {
4359
- target: `bun-${targetOSName}-${ARCH}`,
4400
+ target: `bun-${targetOSName}-${targetArch}`,
4360
4401
  outfile: outputPath,
4361
4402
  };
4362
4403
 
4363
- if (isWindows) {
4404
+ if (isWindows && OS !== "win") {
4405
+ // Bun only applies windows compile options on a Windows host; the
4406
+ // console is still hidden by patchPeSubsystem below (works anywhere).
4407
+ console.warn(
4408
+ `\n⚠️ Cross-compiling the Windows installer: icon and PE metadata (title, version, publisher, etc.) will be ignored.\n` +
4409
+ ` Build on a Windows host if the installer exe needs them.\n`
4410
+ );
4411
+ } else if (isWindows) {
4364
4412
  let icoPath: string | undefined;
4365
4413
  if (config.build.win?.icon) {
4366
4414
  const iconSrc = config.build.win.icon.startsWith("/") || config.build.win.icon.match(/^[a-zA-Z]:/)
@@ -92,6 +92,36 @@ function getInstallDir(meta: Metadata): string {
92
92
  }
93
93
  }
94
94
 
95
+ // Create a .lnk via WScript.Shell. windowsHide is essential: the installer is a
96
+ // GUI-subsystem exe, so without it each PowerShell child allocates its own
97
+ // visible console window mid-install. PowerShell is called by absolute path
98
+ // (PATH isn't reliable in every launch context), and the paths are passed
99
+ // through the environment ($env:SB_*) rather than interpolated into the script
100
+ // text, so an app name containing quotes can't break or inject into the command.
101
+ function createShortcut(lnkPath: string, targetPath: string, workingDir: string) {
102
+ const systemRoot = process.env.SystemRoot || process.env.windir || "C:\\Windows";
103
+ const powershell = join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
104
+ const script =
105
+ "$ws = New-Object -ComObject WScript.Shell;" +
106
+ " $s = $ws.CreateShortcut($env:SB_LNK);" +
107
+ " $s.TargetPath = $env:SB_TARGET;" +
108
+ " $s.WorkingDirectory = $env:SB_WORKDIR;" +
109
+ " $s.Save()";
110
+ try {
111
+ Bun.spawnSync([powershell, "-NoProfile", "-Command", script], {
112
+ stdout: "ignore",
113
+ stderr: "ignore",
114
+ windowsHide: true,
115
+ env: {
116
+ ...process.env,
117
+ SB_LNK: lnkPath,
118
+ SB_TARGET: targetPath,
119
+ SB_WORKDIR: workingDir,
120
+ },
121
+ });
122
+ } catch {}
123
+ }
124
+
95
125
  async function createWindowsShortcuts(appDir: string, meta: Metadata) {
96
126
  const binDir = join(appDir, "bin");
97
127
  const exePath = join(binDir, `${meta.name.replace(/ /g, "")}.exe`);
@@ -100,19 +130,11 @@ async function createWindowsShortcuts(appDir: string, meta: Metadata) {
100
130
 
101
131
  // Start Menu shortcut
102
132
  const startMenu = join(process.env.APPDATA || "", "Microsoft", "Windows", "Start Menu", "Programs");
103
- try {
104
- Bun.spawnSync(["powershell", "-NoProfile", "-Command",
105
- `$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('${join(startMenu, meta.name + ".lnk")}'); $s.TargetPath = '${exePath}'; $s.WorkingDirectory = '${binDir}'; $s.Save()`
106
- ]);
107
- } catch {}
133
+ createShortcut(join(startMenu, meta.name + ".lnk"), exePath, binDir);
108
134
 
109
135
  // Desktop shortcut
110
- try {
111
- const desktop = join(process.env.USERPROFILE || "", "Desktop");
112
- Bun.spawnSync(["powershell", "-NoProfile", "-Command",
113
- `$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('${join(desktop, meta.name + ".lnk")}'); $s.TargetPath = '${exePath}'; $s.WorkingDirectory = '${binDir}'; $s.Save()`
114
- ]);
115
- } catch {}
136
+ const desktop = join(process.env.USERPROFILE || "", "Desktop");
137
+ createShortcut(join(desktop, meta.name + ".lnk"), exePath, binDir);
116
138
 
117
139
  console.log("Created Start Menu and Desktop shortcuts");
118
140
  }
@@ -11208,55 +11208,66 @@ static std::map<std::string, WebKitWebsiteDataManager*> g_partitionDataManagers;
11208
11208
  static WebKitWebsiteDataManager* getDataManagerForPartition(const char* partitionIdentifier) {
11209
11209
  std::string partition = partitionIdentifier ? partitionIdentifier : "";
11210
11210
 
11211
+ // Prefer the data manager owned by the live webview context for this
11212
+ // partition. getContextForPartition() builds each webview's context around
11213
+ // its own WebKitWebsiteDataManager; creating a separate manager here — even
11214
+ // one pointing at the same on-disk directory — clears a different instance
11215
+ // and leaves the running webview's storage untouched, which made
11216
+ // clearStorageData()/cookies.clear() appear to do nothing. Resolving through
11217
+ // the context keeps clears on the exact store the page reads and writes.
11218
+ if (partition.empty()) {
11219
+ WebKitWebContext* context = webkit_web_context_get_default();
11220
+ return webkit_web_context_get_website_data_manager(context);
11221
+ }
11222
+
11223
+ auto ctxIt = g_partitionContexts.find(partition);
11224
+ if (ctxIt != g_partitionContexts.end() && ctxIt->second) {
11225
+ return webkit_web_context_get_website_data_manager(ctxIt->second);
11226
+ }
11227
+
11228
+ // No live context for this partition yet — fall back to a standalone manager
11229
+ // (cached) so cookie/storage APIs still work before a webview is created.
11211
11230
  auto it = g_partitionDataManagers.find(partition);
11212
11231
  if (it != g_partitionDataManagers.end()) {
11213
11232
  return it->second;
11214
11233
  }
11215
11234
 
11216
11235
  WebKitWebsiteDataManager* dataManager = nullptr;
11236
+ bool isPersistent = partition.substr(0, 8) == "persist:";
11217
11237
 
11218
- if (partition.empty()) {
11219
- // Default: use default context's data manager
11220
- WebKitWebContext* context = webkit_web_context_get_default();
11221
- dataManager = webkit_web_context_get_website_data_manager(context);
11222
- } else {
11223
- bool isPersistent = partition.substr(0, 8) == "persist:";
11238
+ if (isPersistent) {
11239
+ std::string partitionName = partition.substr(8);
11224
11240
 
11225
- if (isPersistent) {
11226
- std::string partitionName = partition.substr(8);
11241
+ // Build paths with identifier/channel structure (consistent with CLI and updater)
11242
+ char* home = getenv("HOME");
11243
+ std::string homeStr = home ? std::string(home) : "/tmp";
11244
+ std::string dataPath = buildPartitionPath(homeStr + "/.local/share", g_sparkbunIdentifier, g_sparkbunChannel, "WebKit", partitionName);
11245
+ std::string cachePath = buildPartitionPath(homeStr + "/.cache", g_sparkbunIdentifier, g_sparkbunChannel, "WebKit", partitionName);
11227
11246
 
11228
- // Build paths with identifier/channel structure (consistent with CLI and updater)
11229
- char* home = getenv("HOME");
11230
- std::string homeStr = home ? std::string(home) : "/tmp";
11231
- std::string dataPath = buildPartitionPath(homeStr + "/.local/share", g_sparkbunIdentifier, g_sparkbunChannel, "WebKit", partitionName);
11232
- std::string cachePath = buildPartitionPath(homeStr + "/.cache", g_sparkbunIdentifier, g_sparkbunChannel, "WebKit", partitionName);
11247
+ g_mkdir_with_parents(dataPath.c_str(), 0755);
11248
+ g_mkdir_with_parents(cachePath.c_str(), 0755);
11233
11249
 
11234
- g_mkdir_with_parents(dataPath.c_str(), 0755);
11235
- g_mkdir_with_parents(cachePath.c_str(), 0755);
11250
+ dataManager = webkit_website_data_manager_new(
11251
+ "base-data-directory", dataPath.c_str(),
11252
+ "base-cache-directory", cachePath.c_str(),
11253
+ NULL
11254
+ );
11236
11255
 
11237
- dataManager = webkit_website_data_manager_new(
11238
- "base-data-directory", dataPath.c_str(),
11239
- "base-cache-directory", cachePath.c_str(),
11240
- NULL
11256
+ // Enable persistent cookie storage (SQLite-backed)
11257
+ WebKitCookieManager* cookieManager = webkit_website_data_manager_get_cookie_manager(dataManager);
11258
+ if (cookieManager) {
11259
+ std::string cookiePath = dataPath + "/cookies.sqlite";
11260
+ webkit_cookie_manager_set_persistent_storage(
11261
+ cookieManager,
11262
+ cookiePath.c_str(),
11263
+ WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE
11241
11264
  );
11242
-
11243
- // Enable persistent cookie storage (SQLite-backed)
11244
- WebKitCookieManager* cookieManager = webkit_website_data_manager_get_cookie_manager(dataManager);
11245
- if (cookieManager) {
11246
- std::string cookiePath = dataPath + "/cookies.sqlite";
11247
- webkit_cookie_manager_set_persistent_storage(
11248
- cookieManager,
11249
- cookiePath.c_str(),
11250
- WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE
11251
- );
11252
- }
11253
- } else {
11254
- dataManager = webkit_website_data_manager_new_ephemeral();
11255
11265
  }
11256
-
11257
- g_partitionDataManagers[partition] = dataManager;
11266
+ } else {
11267
+ dataManager = webkit_website_data_manager_new_ephemeral();
11258
11268
  }
11259
11269
 
11270
+ g_partitionDataManagers[partition] = dataManager;
11260
11271
  return dataManager;
11261
11272
  }
11262
11273
 
@@ -11651,11 +11662,44 @@ SPARKBUN_EXPORT bool sessionRemoveCookie(const char* partitionIdentifier, const
11651
11662
  // Clear all cookies (WebKit2GTK)
11652
11663
  // Clear all cookies (WebKit2GTK) - STUB implementation to prevent crashes
11653
11664
  SPARKBUN_EXPORT void sessionClearCookies(const char* partitionIdentifier) {
11654
- // Stub implementation: do nothing and return immediately
11655
- // This prevents crashes from complex WebKit async patterns during tests
11656
- // while maintaining API compatibility
11657
- (void)partitionIdentifier; // Suppress unused parameter warning
11658
- return;
11665
+ // Copy argument before dispatching to main thread
11666
+ std::string partitionStr = partitionIdentifier ? partitionIdentifier : "";
11667
+
11668
+ // Clear cookies via the website data manager rather than per-cookie
11669
+ // get/delete. This reuses the same dispatch + GMainLoop pattern as
11670
+ // sessionClearStorageData (which is stable) instead of the async cookie
11671
+ // enumeration that previously crashed and led to this being stubbed out.
11672
+ dispatch_sync_main_void([partitionStr]() {
11673
+ WebKitWebsiteDataManager* dataManager = getDataManagerForPartition(partitionStr.c_str());
11674
+ if (!dataManager) {
11675
+ return;
11676
+ }
11677
+
11678
+ GMainLoop* loop = g_main_loop_new(NULL, FALSE);
11679
+
11680
+ webkit_website_data_manager_clear(dataManager, WEBKIT_WEBSITE_DATA_COOKIES, 0, nullptr,
11681
+ [](GObject* source, GAsyncResult* result, gpointer user_data) {
11682
+ GMainLoop* loop = static_cast<GMainLoop*>(user_data);
11683
+ GError* error = nullptr;
11684
+ webkit_website_data_manager_clear_finish(WEBKIT_WEBSITE_DATA_MANAGER(source), result, &error);
11685
+ if (error) {
11686
+ g_error_free(error);
11687
+ }
11688
+ g_main_loop_quit(loop);
11689
+ }, loop);
11690
+
11691
+ GSource* timeout = g_timeout_source_new(10000);
11692
+ g_source_set_callback(timeout, [](gpointer data) -> gboolean {
11693
+ g_main_loop_quit(static_cast<GMainLoop*>(data));
11694
+ return G_SOURCE_REMOVE;
11695
+ }, loop, nullptr);
11696
+ g_source_attach(timeout, g_main_loop_get_context(loop));
11697
+
11698
+ g_main_loop_run(loop);
11699
+ g_source_destroy(timeout);
11700
+ g_source_unref(timeout);
11701
+ g_main_loop_unref(loop);
11702
+ });
11659
11703
  }
11660
11704
 
11661
11705
  // Clear storage data (WebKit2GTK)