extension 4.0.15 → 4.0.16

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.
package/dist/cli.cjs CHANGED
@@ -85,6 +85,14 @@ var __webpack_modules__ = {
85
85
  if (fromBrowserSpecificSettings) return fromBrowserSpecificSettings;
86
86
  return toNormalizedId(manifest?.applications?.gecko?.id);
87
87
  }
88
+ function expectedChromiumExtensionId(outPath) {
89
+ try {
90
+ const manifest = JSON.parse(node_fs__rspack_import_1.readFileSync(node_path__rspack_import_2.join(outPath, 'manifest.json'), 'utf-8'));
91
+ const fromKey = deriveChromiumExtensionIdFromManifest(manifest);
92
+ if (fromKey) return fromKey;
93
+ } catch {}
94
+ return deriveChromiumExtensionIdFromPath(outPath);
95
+ }
88
96
  function resolveExtensionId(args) {
89
97
  const fromInfo = toNormalizedId(args.info?.extensionId);
90
98
  if (fromInfo) return fromInfo;
@@ -210,7 +218,8 @@ var __webpack_modules__ = {
210
218
  }
211
219
  __webpack_require__.d(__webpack_exports__, {
212
220
  MK: ()=>printProdBannerOnce,
213
- ai: ()=>printDevBannerOnce
221
+ ai: ()=>printDevBannerOnce,
222
+ vy: ()=>expectedChromiumExtensionId
214
223
  });
215
224
  },
216
225
  "./browsers/browsers-lib/browser-family.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
@@ -476,6 +485,12 @@ var __webpack_modules__ = {
476
485
  function chromiumManifestLoadBlockers(extensionPath, blockers) {
477
486
  return `${getLoggingPrefix('warn')} ${pintor__rspack_import_8_default().brightYellow('This manifest declares shapes Chrome refuses, the whole extension will not load.')}\n${pintor__rspack_import_8_default().gray('PATH')} ${pintor__rspack_import_8_default().underline(extensionPath)}\n` + blockers.map((blocker)=>`${pintor__rspack_import_8_default().gray('REASON')} ${pintor__rspack_import_8_default().red(blocker)}\n`).join('') + "Chrome rejects the extension at load, so no service worker or content script ever runs. Fix these in the source manifest.";
478
487
  }
488
+ function chromiumExtensionLoadRefused(extensionPath, reason) {
489
+ return `${getLoggingPrefix('error')} ${pintor__rspack_import_8_default().red('The browser refused to load this extension, so it is NOT running.')}\n${pintor__rspack_import_8_default().gray('PATH')} ${pintor__rspack_import_8_default().underline(extensionPath)}\n` + (reason ? `${pintor__rspack_import_8_default().gray('REASON')} ${pintor__rspack_import_8_default().red(reason)}\n` : '') + "No service worker, content script, or page from this extension will run, and no Extension ID was assigned. Fix the reason above and save. If the browser does not pick it up, restart the dev session.";
490
+ }
491
+ function geckoAddonLoadRefused(addonPath, reason) {
492
+ return `${getLoggingPrefix('error')} ${pintor__rspack_import_8_default().red('The browser refused to load this add-on, so it is NOT running.')}\n${pintor__rspack_import_8_default().gray('PATH')} ${pintor__rspack_import_8_default().underline(addonPath)}\n` + (reason ? `${pintor__rspack_import_8_default().gray('REASON')} ${pintor__rspack_import_8_default().red(reason)}\n` : '') + "No background script, content script, or page from this add-on will run, and no Extension ID was assigned. Fix the reason above and save. If the browser does not pick it up, restart the dev session.";
493
+ }
479
494
  function devChannelSnapshotInUse(binaryPath) {
480
495
  return `${getLoggingPrefix('warn')} ${pintor__rspack_import_8_default().brightYellow('Running a Chromium tip-of-tree snapshot (dev channel, not a stable release).')}\n${pintor__rspack_import_8_default().gray('PATH')} ${pintor__rspack_import_8_default().underline(binaryPath)}\nBehavior may differ from stable Chrome. Install a stable browser or remove the snapshot to stop using it.`;
481
496
  }
@@ -835,6 +850,7 @@ var __webpack_modules__ = {
835
850
  Cs: ()=>safariToolchainMissing,
836
851
  Dh: ()=>invalidGeckoBinaryPath,
837
852
  E8: ()=>chromiumDryRunFlags,
853
+ EE: ()=>geckoAddonLoadRefused,
838
854
  EI: ()=>firefoxRdpClientConnected,
839
855
  F1: ()=>cdpClientBrowserConnectionEstablished,
840
856
  F4: ()=>enhancedProcessManagementUncaughtException,
@@ -852,6 +868,7 @@ var __webpack_modules__ = {
852
868
  L: ()=>chromiumManifestLoadBlockers,
853
869
  LD: ()=>firefoxDryRunBinary,
854
870
  M3: ()=>cdpClientConnected,
871
+ M6: ()=>chromiumExtensionLoadRefused,
855
872
  MG: ()=>prettyPuppeteerInstallGuidance,
856
873
  MQ: ()=>firefoxFailedToStart,
857
874
  MS: ()=>errorConnectingToBrowser,
@@ -1224,6 +1241,21 @@ var __webpack_modules__ = {
1224
1241
  node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1225
1242
  } catch {}
1226
1243
  }
1244
+ function stampReadyExtensionLoadRefused(extensionOutputPath, reason) {
1245
+ try {
1246
+ if (!extensionOutputPath) return;
1247
+ const readyPath = readyPathFor(extensionOutputPath);
1248
+ if (!node_fs__rspack_import_0.existsSync(readyPath)) return;
1249
+ const ready = JSON.parse(node_fs__rspack_import_0.readFileSync(readyPath, 'utf-8'));
1250
+ ready.status = 'error';
1251
+ ready.code = 'extension_load_refused';
1252
+ const browserLabel = String(ready.browser || 'the browser');
1253
+ ready.message = `${browserLabel.charAt(0).toUpperCase() + browserLabel.slice(1)} refused to load the extension at ${extensionOutputPath}${reason ? `: ${reason}` : ''}`;
1254
+ ready.extensionLoadRefusedAt = new Date().toISOString();
1255
+ if (reason) ready.extensionLoadRefusedReason = reason;
1256
+ node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1257
+ } catch {}
1258
+ }
1227
1259
  function stampReadyBrowserExited(extensionOutputPath, code) {
1228
1260
  try {
1229
1261
  if (!extensionOutputPath) return;
@@ -1241,8 +1273,9 @@ var __webpack_modules__ = {
1241
1273
  } catch {}
1242
1274
  }
1243
1275
  __webpack_require__.d(__webpack_exports__, {
1244
- W: ()=>stampReadyBrowserExited,
1245
- _: ()=>stampReadyRdpPort
1276
+ Wi: ()=>stampReadyBrowserExited,
1277
+ __: ()=>stampReadyRdpPort,
1278
+ sf: ()=>stampReadyExtensionLoadRefused
1246
1279
  });
1247
1280
  },
1248
1281
  "./browsers/browsers-lib/resolve-profile.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
@@ -1448,6 +1481,9 @@ var __webpack_modules__ = {
1448
1481
  function parseEnvBrowserFlags(raw) {
1449
1482
  return String(raw || '').split(/\s+/).map((flag)=>flag.trim()).filter(Boolean);
1450
1483
  }
1484
+ function isHeadlessGuardRequested(env = process.env) {
1485
+ return /^(1|true)$/i.test(String(env.EXTENSION_HEADLESS || '').trim());
1486
+ }
1451
1487
  function mergeChromiumFeatureSwitches(flags) {
1452
1488
  const merged = [];
1453
1489
  const featureValues = {
@@ -1608,6 +1644,7 @@ var __webpack_modules__ = {
1608
1644
  Py: ()=>mergeChromiumFeatureSwitches,
1609
1645
  RE: ()=>markManagedEphemeralProfile,
1610
1646
  W0: ()=>parseEnvBrowserFlags,
1647
+ ZO: ()=>isHeadlessGuardRequested,
1611
1648
  aY: ()=>findAvailablePortNear,
1612
1649
  jl: ()=>deriveDebugPortWithInstance,
1613
1650
  ov: ()=>filterBrowserFlags,
@@ -1688,6 +1725,20 @@ var __webpack_modules__ = {
1688
1725
  let cdpController;
1689
1726
  if (enableCdp) cdpController = ctx.getController?.();
1690
1727
  return {
1728
+ getExtensionLoadRefusal () {
1729
+ return launcher.getExtensionLoadRefusal();
1730
+ },
1731
+ async retryExtensionLoad () {
1732
+ if (!cdpController?.verifyGuestLoaded) return {
1733
+ status: 'unknown'
1734
+ };
1735
+ const outcome = await cdpController.verifyGuestLoaded();
1736
+ if ('loaded' === outcome.status) {
1737
+ launcher.clearExtensionLoadRefusal();
1738
+ await launcher.printBannerOnRecovery();
1739
+ }
1740
+ return outcome;
1741
+ },
1691
1742
  async enableUnifiedLogging (logOpts) {
1692
1743
  if (cdpController?.enableUnifiedLogging) await cdpController.enableUnifiedLogging({
1693
1744
  level: logOpts.level,
@@ -1719,7 +1770,8 @@ var __webpack_modules__ = {
1719
1770
  logTimestamps: opts.logTimestamps,
1720
1771
  logColor: opts.logColor,
1721
1772
  logUrl: opts.logUrl,
1722
- logTab: opts.logTab
1773
+ logTab: opts.logTab,
1774
+ logSink: opts.logSink
1723
1775
  };
1724
1776
  const pluginOptions = {
1725
1777
  extension: opts.extensionsToLoad,
@@ -1747,6 +1799,15 @@ var __webpack_modules__ = {
1747
1799
  await launcher.runOnce(compilationLike, launchRequest);
1748
1800
  const rdpController = ctx.getController?.();
1749
1801
  return {
1802
+ getExtensionLoadRefusal () {
1803
+ return firefoxOpts.extensionLoadRefused || null;
1804
+ },
1805
+ async retryExtensionLoad () {
1806
+ if (!firefoxOpts.retryAddonInstall) return {
1807
+ status: 'unknown'
1808
+ };
1809
+ return await firefoxOpts.retryAddonInstall();
1810
+ },
1750
1811
  async enableUnifiedLogging (logOpts) {
1751
1812
  if (!rdpController?.enableUnifiedLogging) return;
1752
1813
  await rdpController.enableUnifiedLogging({
@@ -2093,6 +2154,7 @@ var __webpack_modules__ = {
2093
2154
  var output_binaries_resolver = __webpack_require__("./browsers/browsers-lib/output-binaries-resolver.ts");
2094
2155
  var process_teardown = __webpack_require__("./browsers/browsers-lib/process-teardown.ts");
2095
2156
  var ready_message = __webpack_require__("./browsers/browsers-lib/ready-message.ts");
2157
+ var ready_stamp = __webpack_require__("./browsers/browsers-lib/ready-stamp.ts");
2096
2158
  var runtime_options = __webpack_require__("./browsers/browsers-lib/runtime-options.ts");
2097
2159
  var shared_utils = __webpack_require__("./browsers/browsers-lib/shared-utils.ts");
2098
2160
  var discovery = __webpack_require__("./browsers/run-chromium/cdp/discovery.ts");
@@ -2325,6 +2387,7 @@ var __webpack_modules__ = {
2325
2387
  ...configOptions.browserFlags || [],
2326
2388
  ...(0, shared_utils.W0)(process.env.EXTENSION_BROWSER_FLAGS)
2327
2389
  ];
2390
+ if ((0, shared_utils.ZO)() && !baseFlags.some((flag)=>flag.startsWith('--headless'))) baseFlags.push('--headless=new');
2328
2391
  return (0, shared_utils.Py)(baseFlags);
2329
2392
  }
2330
2393
  function logChromiumDryRun(browserBinaryLocation, chromiumConfig) {
@@ -2505,7 +2568,6 @@ var __webpack_modules__ = {
2505
2568
  throw error;
2506
2569
  }
2507
2570
  }
2508
- var ready_stamp = __webpack_require__("./browsers/browsers-lib/ready-stamp.ts");
2509
2571
  function _define_property(obj, key, value) {
2510
2572
  if (key in obj) Object.defineProperty(obj, key, {
2511
2573
  value: value,
@@ -2536,6 +2598,15 @@ var __webpack_modules__ = {
2536
2598
  });
2537
2599
  }
2538
2600
  class ChromiumLaunchPlugin {
2601
+ getExtensionLoadRefusal() {
2602
+ return this.extensionLoadRefused || null;
2603
+ }
2604
+ clearExtensionLoadRefusal() {
2605
+ this.extensionLoadRefused = void 0;
2606
+ }
2607
+ async printBannerOnRecovery() {
2608
+ await this.bannerOnRecovery?.();
2609
+ }
2539
2610
  async runOnce(compilation, opts) {
2540
2611
  if (!this.logger) this.logger = {
2541
2612
  info: (...a)=>console.log(...a),
@@ -2560,7 +2631,7 @@ var __webpack_modules__ = {
2560
2631
  if (this.didLaunch) return;
2561
2632
  await this.launchChromium(stats.compilation);
2562
2633
  this.didLaunch = true;
2563
- if (!this.didReportReady) console.log((0, ready_message.G)(stats.compilation.options.mode, this.options.browser));
2634
+ if (!this.didReportReady && !this.extensionLoadRefused) console.log((0, ready_message.G)(stats.compilation.options.mode, this.options.browser));
2564
2635
  } catch (error) {
2565
2636
  try {
2566
2637
  this.logger.error(messages._D(this.options.browser, error));
@@ -2927,10 +2998,13 @@ var __webpack_modules__ = {
2927
2998
  }
2928
2999
  if (!portReady && 'true' === process.env.EXTENSION_AUTHOR_MODE) this.logger.warn?.(`[browser] Debug port ${selectedPort} not bound after spawn. CDP may fail`);
2929
3000
  }
2930
- if ('development' === compilation.options.mode && !this.didReportReady) {
3001
+ const reportReady = ()=>{
3002
+ if ('development' !== compilation.options.mode) return;
3003
+ if (this.didReportReady || this.extensionLoadRefused) return;
2931
3004
  console.log((0, ready_message.G)(compilation.options.mode, this.options.browser));
2932
3005
  this.didReportReady = true;
2933
- }
3006
+ };
3007
+ if (!enableCdp) reportReady();
2934
3008
  try {
2935
3009
  const mode = compilation?.options?.mode || 'development';
2936
3010
  try {
@@ -2962,12 +3036,18 @@ var __webpack_modules__ = {
2962
3036
  mod.setupCdpAfterLaunch(compilation, cdpConfig, chromiumConfig, pipeStreams),
2963
3037
  new Promise((_, reject)=>setTimeout(()=>reject(new Error(`CDP setup did not complete within ${CDP_SETUP_TIMEOUT_MS / 1000}s. Chrome likely rejected the extension at launch, open chrome://extensions in the dev browser window for the exact error. Common causes: MV3 content_security_policy with 'unsafe-inline', manifest keys Chrome does not support, or manifest references to files missing from the output. Reload/HMR cannot attach until this is fixed.`)), CDP_SETUP_TIMEOUT_MS).unref?.())
2964
3038
  ]);
3039
+ if (cdpConfig.extensionLoadRefused) {
3040
+ this.extensionLoadRefused = cdpConfig.extensionLoadRefused;
3041
+ this.bannerOnRecovery = cdpConfig.printBannerOnRecovery;
3042
+ }
2965
3043
  if (cdpConfig.cdpController) this.ctx.setController(cdpConfig.cdpController);
2966
3044
  }
3045
+ reportReady();
2967
3046
  } catch (error) {
2968
3047
  const message = String(error && error.message);
2969
3048
  const hint = /timed out|did not complete/i.test(message) ? " Chrome likely rejected the extension at launch, open chrome://extensions in the dev browser window for the exact error. Common causes: MV3 content_security_policy with 'unsafe-inline', manifest keys Chrome does not support, or manifest references to missing files. Reload/HMR cannot attach until this is fixed." : '';
2970
3049
  console.error(`[browser] ${message}${hint}`);
3050
+ reportReady();
2971
3051
  }
2972
3052
  }
2973
3053
  async launchWithDirectSpawn(binary, chromeFlags, usePipe = false) {
@@ -3018,7 +3098,7 @@ var __webpack_modules__ = {
3018
3098
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) this.logger.info(messages.nn(code || 0));
3019
3099
  if (!(0, process_teardown.Op)(child)) {
3020
3100
  this.logger.error(this.closeHandlerContext?.isDevMode ? `[browser] ${this.options.browser} exited mid-session (code ${code ?? 'unknown'}). The dev server is still running but reloads cannot be delivered, restart "extension dev" to relaunch the browser.` : `[browser] ${this.options.browser} exited (code ${code ?? 'unknown'}); the preview session is over.`);
3021
- (0, ready_stamp.W)(this.closeHandlerContext?.extensionOutputPath, code);
3101
+ (0, ready_stamp.Wi)(this.closeHandlerContext?.extensionOutputPath, code);
3022
3102
  }
3023
3103
  disposeSignalHandlers?.();
3024
3104
  const userDataDir = launchArgs.find((arg)=>arg.startsWith('--user-data-dir='))?.slice('--user-data-dir='.length).replace(/^"|"$/g, '');
@@ -3053,6 +3133,8 @@ var __webpack_modules__ = {
3053
3133
  _define_property(this, "ctx", void 0);
3054
3134
  _define_property(this, "didLaunch", void 0);
3055
3135
  _define_property(this, "didReportReady", void 0);
3136
+ _define_property(this, "extensionLoadRefused", void 0);
3137
+ _define_property(this, "bannerOnRecovery", void 0);
3056
3138
  _define_property(this, "logger", void 0);
3057
3139
  _define_property(this, "closeHandlerContext", void 0);
3058
3140
  this.options = options;
@@ -3243,7 +3325,7 @@ var __webpack_modules__ = {
3243
3325
  } : null;
3244
3326
  }
3245
3327
  function isFirefoxHeadlessRequested(env = process.env) {
3246
- return /^(1|true)$/i.test(String(env.MOZ_HEADLESS || '').trim());
3328
+ return /^(1|true)$/i.test(String(env.MOZ_HEADLESS || '').trim()) || (0, shared_utils.ZO)(env);
3247
3329
  }
3248
3330
  class FirefoxBinaryDetector {
3249
3331
  static generateFirefoxArgs(binaryPath, profilePath, debugPort, additionalArgs = [], headless = false) {
@@ -3265,7 +3347,9 @@ var __webpack_modules__ = {
3265
3347
  '-start-debugger-server',
3266
3348
  String(debugPort)
3267
3349
  ] : [],
3268
- '--foreground',
3350
+ ...headless ? [] : [
3351
+ '--foreground'
3352
+ ],
3269
3353
  ...additionalArgs
3270
3354
  ];
3271
3355
  return {
@@ -3286,7 +3370,9 @@ var __webpack_modules__ = {
3286
3370
  '-start-debugger-server',
3287
3371
  String(debugPort)
3288
3372
  ] : [],
3289
- '--foreground',
3373
+ ...headless ? [] : [
3374
+ '--foreground'
3375
+ ],
3290
3376
  ...additionalArgs
3291
3377
  ];
3292
3378
  return {
@@ -3529,6 +3615,22 @@ var __webpack_modules__ = {
3529
3615
  if (external_node_fs_.existsSync(external_node_path_.join(distFirefox, 'manifest.json'))) return distFirefox;
3530
3616
  return candidate;
3531
3617
  }
3618
+ function classifyAddonInstallFailure(error) {
3619
+ if (error instanceof Error) return {
3620
+ status: 'unknown'
3621
+ };
3622
+ const reply = error;
3623
+ if (!reply || 'object' != typeof reply || !reply.error) return {
3624
+ status: 'unknown'
3625
+ };
3626
+ const reason = String(reply.message || '').trim();
3627
+ return reason ? {
3628
+ status: 'refused',
3629
+ reason
3630
+ } : {
3631
+ status: 'unknown'
3632
+ };
3633
+ }
3532
3634
  async function getAddonsActorWithRetry(client, cached, tries = 40, delayMs = 250) {
3533
3635
  if (cached) return cached;
3534
3636
  let addonsActor;
@@ -4185,6 +4287,9 @@ var __webpack_modules__ = {
4185
4287
  const RETRY_INTERVAL = constants.FQ;
4186
4288
  const RETRY_LOG_EVERY_N_ATTEMPTS = 10;
4187
4289
  class RemoteFirefox {
4290
+ getAddonInstallRefusalReason() {
4291
+ return this.addonInstallRefusalReason || null;
4292
+ }
4188
4293
  selectPrimaryAddonPath(compilation, candidateAddonPaths) {
4189
4294
  const normalizedOutputPath = String(compilation?.options?.output?.path || '').replace(/\\/g, '/');
4190
4295
  if (normalizedOutputPath) {
@@ -4274,6 +4379,8 @@ var __webpack_modules__ = {
4274
4379
  if (primaryAddonPath && String(addonPath) === String(primaryAddonPath) && maybeId) primaryUserAddonId = maybeId;
4275
4380
  if (isManager) await waitForManagerWelcome(client);
4276
4381
  } catch (err) {
4382
+ const outcome = classifyAddonInstallFailure(err);
4383
+ if ('refused' === outcome.status) this.addonInstallRefusalReason = outcome.reason;
4277
4384
  const message = requestErrorToMessage(err);
4278
4385
  throw new Error(messages.WV(this.options.browser, message));
4279
4386
  }
@@ -4321,6 +4428,7 @@ var __webpack_modules__ = {
4321
4428
  remote_firefox_define_property(this, "cachedAddonsActor", void 0);
4322
4429
  remote_firefox_define_property(this, "lastInstalledAddonPath", void 0);
4323
4430
  remote_firefox_define_property(this, "derivedExtensionId", void 0);
4431
+ remote_firefox_define_property(this, "addonInstallRefusalReason", void 0);
4324
4432
  this.options = configOptions;
4325
4433
  }
4326
4434
  }
@@ -4338,6 +4446,9 @@ var __webpack_modules__ = {
4338
4446
  async ensureLoaded(compilation) {
4339
4447
  await this.remote.installAddons(compilation);
4340
4448
  }
4449
+ getAddonInstallRefusalReason() {
4450
+ return this.remote.getAddonInstallRefusalReason();
4451
+ }
4341
4452
  async enableUnifiedLogging(opts) {
4342
4453
  await this.remote.enableUnifiedLogging(opts);
4343
4454
  }
@@ -4370,6 +4481,7 @@ var __webpack_modules__ = {
4370
4481
  return await fn();
4371
4482
  } catch (error) {
4372
4483
  lastError = error;
4484
+ if (controller.getAddonInstallRefusalReason()) break;
4373
4485
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) try {
4374
4486
  const msg = error?.message || String(error);
4375
4487
  console.warn(`[browser] Firefox RDP setup retry ${i + 1}/${attempts}: ${msg}`);
@@ -4379,10 +4491,19 @@ var __webpack_modules__ = {
4379
4491
  }
4380
4492
  throw lastError;
4381
4493
  };
4382
- if (plugin.rdpController) await retry(()=>plugin.rdpController.ensureLoaded?.(compilation) || Promise.resolve());
4383
- else {
4384
- await retry(()=>controller.ensureLoaded(compilation));
4385
- plugin.rdpController = controller;
4494
+ const withRefusalReason = (error)=>{
4495
+ const reason = controller.getAddonInstallRefusalReason();
4496
+ if (reason && error && 'object' == typeof error) error.extensionLoadRefusedReason = reason;
4497
+ return error;
4498
+ };
4499
+ try {
4500
+ if (plugin.rdpController) await retry(()=>plugin.rdpController.ensureLoaded?.(compilation) || Promise.resolve());
4501
+ else {
4502
+ await retry(()=>controller.ensureLoaded(compilation));
4503
+ plugin.rdpController = controller;
4504
+ }
4505
+ } catch (error) {
4506
+ throw withRefusalReason(error);
4386
4507
  }
4387
4508
  return controller;
4388
4509
  }
@@ -4465,8 +4586,8 @@ var __webpack_modules__ = {
4465
4586
  error: (...a)=>console.error(...a),
4466
4587
  debug: (...a)=>console?.debug?.(...a)
4467
4588
  };
4468
- if ('development' === options.mode) this.ctx.logger?.info?.((0, ready_message.G)(options.mode, this.host.browser));
4469
4589
  await this.launch(compilation, options);
4590
+ if ('development' === options.mode && !this.host.extensionLoadRefused) this.ctx.logger?.info?.((0, ready_message.G)(options.mode, this.host.browser));
4470
4591
  this.ctx.didLaunch = true;
4471
4592
  }
4472
4593
  apply(compiler) {
@@ -4486,8 +4607,8 @@ var __webpack_modules__ = {
4486
4607
  return;
4487
4608
  }
4488
4609
  if (this.ctx.didLaunch) return void done();
4489
- if ('development' === stats.compilation.options.mode) console.log((0, ready_message.G)(stats.compilation.options.mode, this.host.browser));
4490
4610
  await this.launch(stats.compilation, (0, runtime_options.zU)(this.host, stats.compilation.options.mode));
4611
+ if ('development' === stats.compilation.options.mode && !this.host.extensionLoadRefused) console.log((0, ready_message.G)(stats.compilation.options.mode, this.host.browser));
4491
4612
  this.ctx.didLaunch = true;
4492
4613
  } catch (error) {
4493
4614
  this.ctx.logger?.error?.(messages.MQ(error));
@@ -4648,10 +4769,24 @@ var __webpack_modules__ = {
4648
4769
  (0, shared_utils.sW)(profilePath);
4649
4770
  });
4650
4771
  this.wireChildLifecycle();
4651
- const ctrl = await setupRdpAfterLaunch(this.host, compilation, debugPort);
4772
+ let ctrl;
4773
+ try {
4774
+ ctrl = await setupRdpAfterLaunch(this.host, compilation, debugPort);
4775
+ } catch (error) {
4776
+ const reason = error?.extensionLoadRefusedReason;
4777
+ if (!reason) {
4778
+ (0, ready_stamp.__)(this.extensionOutputPath, debugPort);
4779
+ throw error;
4780
+ }
4781
+ this.reportAddonLoadRefused(reason);
4782
+ this.host.retryAddonInstall = ()=>this.retryAddonInstall(compilation, debugPort);
4783
+ (0, ready_stamp.__)(this.extensionOutputPath, debugPort);
4784
+ this.scheduleWatchTimeout();
4785
+ return;
4786
+ }
4652
4787
  this.host.rdpController = ctrl;
4653
4788
  this.ctx.setController(ctrl);
4654
- (0, ready_stamp._)(this.extensionOutputPath, debugPort);
4789
+ (0, ready_stamp.__)(this.extensionOutputPath, debugPort);
4655
4790
  this.scheduleWatchTimeout();
4656
4791
  try {
4657
4792
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) {
@@ -4672,12 +4807,14 @@ var __webpack_modules__ = {
4672
4807
  ...'win32' === process.platform ? [
4673
4808
  '-wait-for-browser'
4674
4809
  ] : [],
4675
- '--foreground',
4810
+ ...isFirefoxHeadlessRequested() ? [] : [
4811
+ '--foreground'
4812
+ ],
4676
4813
  ...firefoxArgs
4677
4814
  ];
4678
4815
  this.child = await this.spawnFirefoxChild(binaryPath, args, wslFallbackBinary);
4679
4816
  this.wireChildLifecycle();
4680
- if (debugPort > 0) (0, ready_stamp._)(this.extensionOutputPath, debugPort);
4817
+ if (debugPort > 0) (0, ready_stamp.__)(this.extensionOutputPath, debugPort);
4681
4818
  this.scheduleWatchTimeout();
4682
4819
  }
4683
4820
  }
@@ -4730,7 +4867,7 @@ var __webpack_modules__ = {
4730
4867
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) this.ctx.logger?.info?.(messages.Th(this.host.browser));
4731
4868
  if (!(0, process_teardown.Op)(child)) {
4732
4869
  this.ctx.logger?.error?.(`[browser] ${this.host.browser} exited (code ${code ?? 'unknown'}) without being asked to. The add-on may have been rejected or the browser crashed; the session cannot be driven.`);
4733
- (0, ready_stamp.W)(this.extensionOutputPath, code);
4870
+ (0, ready_stamp.Wi)(this.extensionOutputPath, code);
4734
4871
  }
4735
4872
  this.cleanupInstance().catch((err)=>{
4736
4873
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) this.ctx.logger?.error?.(`[browser] Cleanup error on child close: ${err?.message || err}`);
@@ -4740,6 +4877,36 @@ var __webpack_modules__ = {
4740
4877
  this.pipeChildOutput(child);
4741
4878
  disposeProcessHandlers = setupFirefoxProcessHandlers(this.host.browser, ()=>this.child, ()=>this.cleanupInstance());
4742
4879
  }
4880
+ async retryAddonInstall(compilation, debugPort) {
4881
+ try {
4882
+ const ctrl = await setupRdpAfterLaunch(this.host, compilation, debugPort);
4883
+ this.host.rdpController = ctrl;
4884
+ this.ctx.setController(ctrl);
4885
+ this.host.extensionLoadRefused = void 0;
4886
+ return {
4887
+ status: 'loaded'
4888
+ };
4889
+ } catch (error) {
4890
+ const reason = error?.extensionLoadRefusedReason;
4891
+ return reason ? {
4892
+ status: 'refused',
4893
+ reason
4894
+ } : {
4895
+ status: 'unknown'
4896
+ };
4897
+ }
4898
+ }
4899
+ reportAddonLoadRefused(reason) {
4900
+ const refusedPath = this.extensionOutputPath || '';
4901
+ console.error(messages.EE(refusedPath, reason));
4902
+ this.host.logSink?.({
4903
+ level: 'error',
4904
+ text: `extension_load_refused: ${refusedPath}${reason ? ` - ${reason}` : ''}`,
4905
+ source: 'browser'
4906
+ });
4907
+ (0, ready_stamp.sf)(this.extensionOutputPath, reason);
4908
+ this.host.extensionLoadRefused = reason;
4909
+ }
4743
4910
  async cleanupInstance() {
4744
4911
  (0, process_teardown.Df)(this.child, this.host.browser);
4745
4912
  }
@@ -10,6 +10,7 @@ type HostPort = {
10
10
  host?: string;
11
11
  port?: number | string;
12
12
  };
13
+ export declare function expectedChromiumExtensionId(outPath: string): string;
13
14
  export declare function printDevBannerOnce(opts: {
14
15
  browser: BrowserType;
15
16
  outPath: string;
@@ -20,6 +20,8 @@ export declare function mv3BackgroundScriptsNotSupportedByChromium(extensionPath
20
20
  export declare function unsupportedManifestVersionOnChromium(extensionPath: string, declared: unknown): string;
21
21
  export declare function chromiumInvalidMatchPatterns(extensionPath: string, patterns: string[]): string;
22
22
  export declare function chromiumManifestLoadBlockers(extensionPath: string, blockers: string[]): string;
23
+ export declare function chromiumExtensionLoadRefused(extensionPath: string, reason: string): string;
24
+ export declare function geckoAddonLoadRefused(addonPath: string, reason: string): string;
23
25
  export declare function devChannelSnapshotInUse(binaryPath: string): string;
24
26
  export declare function browserLaunchError(browser: Browser, error: unknown): string;
25
27
  export declare function enhancedProcessManagementCleanup(browser: Browser): string;
@@ -1,2 +1,3 @@
1
1
  export declare function stampReadyRdpPort(extensionOutputPath: string | undefined, rdpPort: number): void;
2
+ export declare function stampReadyExtensionLoadRefused(extensionOutputPath: string | undefined, reason: string): void;
2
3
  export declare function stampReadyBrowserExited(extensionOutputPath: string | undefined, code: number | null): void;
@@ -15,6 +15,7 @@ export declare function chooseChromiumBinaryPreferringStable(opts: {
15
15
  preferManagedSnapshot?: boolean;
16
16
  }): ChromiumBinaryChoice;
17
17
  export declare function parseEnvBrowserFlags(raw: string | undefined | null): string[];
18
+ export declare function isHeadlessGuardRequested(env?: NodeJS.ProcessEnv): boolean;
18
19
  export declare function mergeChromiumFeatureSwitches(flags: string[]): string[];
19
20
  export declare function findAvailablePortNear(startPort: number, maxAttempts?: number, host?: string): Promise<number>;
20
21
  export declare function prepareChromiumProfileForLaunch(profilePath: string): {
@@ -185,6 +185,11 @@ export interface BrowserLogSinkEvent {
185
185
  }
186
186
  /** Host-provided sink for {@link BrowserLogSinkEvent}s. Must never throw. */
187
187
  export type BrowserLogSink = (event: BrowserLogSinkEvent) => void;
188
+ export interface ExtensionLoadRetryResult {
189
+ status: 'loaded' | 'refused' | 'unknown';
190
+ reason?: string;
191
+ extensionId?: string;
192
+ }
188
193
  export interface Controller {
189
194
  enableUnifiedLogging: (opts: {
190
195
  level?: string;
@@ -199,4 +204,5 @@ export interface Controller {
199
204
  method?: string;
200
205
  params?: unknown;
201
206
  }) => void) => void;
207
+ verifyGuestLoaded?: () => Promise<ExtensionLoadRetryResult>;
202
208
  }
@@ -1,4 +1,4 @@
1
- import type { BrowserLogSink, BrowserType } from './browsers-types';
1
+ import type { BrowserLogSink, BrowserType, ExtensionLoadRetryResult } from './browsers-types';
2
2
  export type { BrowserType, CompilationLike, Controller } from './browsers-types';
3
3
  /**
4
4
  * Options for launching a browser with an extension loaded.
@@ -49,6 +49,7 @@ export interface BrowserLaunchOptions {
49
49
  * (`setupFirefoxProcessHandlers` / Chromium equivalents). The controller is
50
50
  * deliberately not responsible for teardown, so there is no `close()`.
51
51
  */
52
+ export type { ExtensionLoadRetryResult };
52
53
  export interface BrowserController {
53
54
  enableUnifiedLogging(opts: {
54
55
  level?: string;
@@ -59,6 +60,10 @@ export interface BrowserController {
59
60
  urlFilter?: string;
60
61
  tabFilter?: number | string;
61
62
  }): Promise<void>;
63
+ /** The browser's refusal reason for this session, or null when it loaded. */
64
+ getExtensionLoadRefusal?(): string | null;
65
+ /** Re-offer the current dist. Only ever called while the session is refused. */
66
+ retryExtensionLoad?(): Promise<ExtensionLoadRetryResult>;
62
67
  }
63
68
  /**
64
69
  * Launch a browser with the given extension(s) loaded.
@@ -1,5 +1,16 @@
1
1
  import type { CDPClient } from '../cdp-client';
2
2
  export declare function uninstallStaleUnpackedLoads(cdp: CDPClient, profilePath: string | undefined, outPath: string): Promise<string[]>;
3
+ export declare function declaresBackgroundContext(outPath: string): boolean;
4
+ export type LoadUnpackedOutcome = {
5
+ status: 'loaded';
6
+ extensionId: string;
7
+ } | {
8
+ status: 'refused';
9
+ reason: string;
10
+ } | {
11
+ status: 'unknown';
12
+ };
13
+ export declare function loadUnpacked(cdp: CDPClient, outPath: string): Promise<LoadUnpackedOutcome>;
3
14
  export declare function loadUnpackedIfNeeded(cdp: CDPClient, outPath: string): Promise<string | null>;
4
15
  export declare function readManifestInfo(outPath: string): {
5
16
  name?: string;
@@ -1,6 +1,7 @@
1
1
  import type { Readable, Writable } from 'node:stream';
2
2
  import type { BrowserLogSink } from '../../../browsers-types';
3
3
  import type { CdpProtocolMessage } from '../../chromium-types';
4
+ import { type LoadUnpackedOutcome } from './ensure';
4
5
  interface ExtensionInfoResult {
5
6
  extensionId: string;
6
7
  name?: string;
@@ -16,6 +17,7 @@ export declare class CDPExtensionController {
16
17
  private readonly logSink?;
17
18
  private cdp;
18
19
  private extensionId;
20
+ private loadRefusalReason;
19
21
  constructor(args: {
20
22
  outPath: string;
21
23
  browser: 'chrome' | 'edge' | 'chromium-based';
@@ -28,6 +30,9 @@ export declare class CDPExtensionController {
28
30
  });
29
31
  connect(): Promise<void>;
30
32
  openTab(url: string): Promise<void>;
33
+ verifyGuestLoaded(): Promise<LoadUnpackedOutcome>;
34
+ getLoadRefusalReason(): string | null;
35
+ private waitForExtensionTarget;
31
36
  ensureLoaded(): Promise<ExtensionInfoResult>;
32
37
  private deriveExtensionIdFromTargets;
33
38
  private classifyOwnership;
@@ -1,7 +1,7 @@
1
+ import { stampReadyBrowserExited } from '../../browsers-lib/ready-stamp';
1
2
  import type { CompilationLike } from '../../browsers-types';
2
3
  import type { ChromiumContext } from '../chromium-context';
3
4
  import type { ChromiumLaunchOptions } from '../chromium-types';
4
- import { stampReadyBrowserExited } from '../../browsers-lib/ready-stamp';
5
5
  export { stampReadyBrowserExited };
6
6
  /**
7
7
  * ChromiumLaunchPlugin
@@ -17,6 +17,11 @@ export declare class ChromiumLaunchPlugin {
17
17
  private readonly ctx;
18
18
  private didLaunch;
19
19
  private didReportReady;
20
+ private extensionLoadRefused;
21
+ getExtensionLoadRefusal(): string | null;
22
+ clearExtensionLoadRefusal(): void;
23
+ printBannerOnRecovery(): Promise<void>;
24
+ private bannerOnRecovery;
20
25
  private logger;
21
26
  private closeHandlerContext;
22
27
  constructor(options: ChromiumLaunchOptions, ctx: ChromiumContext);
@@ -41,6 +41,8 @@ export interface ChromiumPluginRuntime extends ChromiumLaunchOptions {
41
41
  bannerPrintedOnce?: boolean;
42
42
  cdpController?: Controller;
43
43
  browserVersionLine?: string;
44
+ extensionLoadRefused?: string;
45
+ printBannerOnRecovery?: () => Promise<void>;
44
46
  }
45
47
  export interface ChromiumLogger {
46
48
  level?: LogLevel | 'off' | string;
@@ -26,6 +26,8 @@ export declare class FirefoxLaunchPlugin {
26
26
  private spawnFirefoxChild;
27
27
  private pipeChildOutput;
28
28
  private wireChildLifecycle;
29
+ private retryAddonInstall;
30
+ private reportAddonLoadRefused;
29
31
  private cleanupInstance;
30
32
  private scheduleWatchTimeout;
31
33
  private printInstallHint;
@@ -1,4 +1,4 @@
1
- import type { PluginInterface } from '../browsers-types';
1
+ import type { BrowserLogSink, ExtensionLoadRetryResult, PluginInterface } from '../browsers-types';
2
2
  import type { FirefoxRDPController } from './rdp/rdp-extension-controller';
3
3
  export type FirefoxPluginLike = Pick<PluginInterface, 'extension' | 'browserFlags' | 'profile' | 'persistProfile' | 'keepProfileChanges' | 'copyFromProfile' | 'preferences' | 'startingUrl' | 'geckoBinary' | 'instanceId' | 'port' | 'logLevel' | 'logContexts' | 'logFormat' | 'logTimestamps' | 'logColor' | 'logUrl' | 'logTab' | 'dryRun'> & {
4
4
  browser: PluginInterface['browser'];
@@ -6,4 +6,7 @@ export type FirefoxPluginLike = Pick<PluginInterface, 'extension' | 'browserFlag
6
6
  export interface FirefoxPluginRuntime extends FirefoxPluginLike {
7
7
  rdpController?: FirefoxRDPController;
8
8
  browserVersionLine?: string;
9
+ logSink?: BrowserLogSink;
10
+ extensionLoadRefused?: string;
11
+ retryAddonInstall?: () => Promise<ExtensionLoadRetryResult>;
9
12
  }