extension-develop 3.18.3 → 3.18.4-canary.321.403955d

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.
Files changed (37) hide show
  1. package/dist/0~dev-server.mjs +6 -2
  2. package/dist/0~rslib-runtime.mjs +1 -17
  3. package/dist/0~rspack-config.mjs +261 -47
  4. package/dist/946.mjs +544 -534
  5. package/dist/962.mjs +4 -2
  6. package/dist/extension-js-devtools/chrome/content_scripts/content-0.js +2 -2
  7. package/dist/extension-js-devtools/chrome/devtools/index.js +1 -1
  8. package/dist/extension-js-devtools/chrome/manifest.json +3 -3
  9. package/dist/extension-js-devtools/chromium/content_scripts/content-0.js +2 -2
  10. package/dist/extension-js-devtools/chromium/devtools/index.js +1 -1
  11. package/dist/extension-js-devtools/chromium/manifest.json +3 -3
  12. package/dist/extension-js-devtools/edge/content_scripts/content-0.js +2 -2
  13. package/dist/extension-js-devtools/edge/devtools/index.js +1 -1
  14. package/dist/extension-js-devtools/edge/manifest.json +3 -3
  15. package/dist/extension-js-devtools/extension-js/chrome/events.ndjson +2 -2
  16. package/dist/extension-js-devtools/extension-js/chrome/ready.json +5 -5
  17. package/dist/extension-js-devtools/extension-js/chromium/events.ndjson +2 -2
  18. package/dist/extension-js-devtools/extension-js/chromium/ready.json +5 -5
  19. package/dist/extension-js-devtools/extension-js/edge/events.ndjson +2 -2
  20. package/dist/extension-js-devtools/extension-js/edge/ready.json +5 -5
  21. package/dist/extension-js-devtools/extension-js/firefox/events.ndjson +2 -2
  22. package/dist/extension-js-devtools/extension-js/firefox/ready.json +5 -5
  23. package/dist/extension-js-devtools/firefox/content_scripts/content-0.js +2 -2
  24. package/dist/extension-js-devtools/firefox/devtools/index.js +1 -1
  25. package/dist/extension-js-theme/extension-js/chrome/events.ndjson +2 -2
  26. package/dist/extension-js-theme/extension-js/chrome/ready.json +5 -5
  27. package/dist/extension-js-theme/extension-js/chromium/events.ndjson +2 -2
  28. package/dist/extension-js-theme/extension-js/chromium/ready.json +5 -5
  29. package/dist/extension-js-theme/extension-js/edge/events.ndjson +2 -2
  30. package/dist/extension-js-theme/extension-js/edge/ready.json +5 -5
  31. package/dist/extension-js-theme/extension-js/firefox/events.ndjson +4 -4
  32. package/dist/extension-js-theme/extension-js/firefox/ready.json +5 -5
  33. package/dist/feature-scripts-content-script-wrapper.js +7 -37
  34. package/dist/feature-scripts-content-script-wrapper.mjs +7 -37
  35. package/package.json +2 -7
  36. package/dist/resolve-paths-loader.js +0 -1300
  37. package/dist/resolve-paths-loader.mjs +0 -1300
@@ -569,11 +569,15 @@ class BridgeBroker {
569
569
  reload: this.allowControl,
570
570
  open: this.allowControl ? isFirefox ? [
571
571
  'popup',
572
- 'options'
572
+ 'options',
573
+ 'action',
574
+ 'command'
573
575
  ] : [
574
576
  'popup',
575
577
  'options',
576
- 'sidebar'
578
+ 'sidebar',
579
+ 'action',
580
+ 'command'
577
581
  ] : [],
578
582
  deepDom: 'chromium' === this.engine
579
583
  }
@@ -1,16 +1,5 @@
1
1
  import { createRequire as __extjsCreateRequire } from "node:module"; const require = __extjsCreateRequire(import.meta.url);
2
- var __webpack_modules__ = {};
3
- var __webpack_module_cache__ = {};
4
- function __webpack_require__(moduleId) {
5
- var cachedModule = __webpack_module_cache__[moduleId];
6
- if (void 0 !== cachedModule) return cachedModule.exports;
7
- var module = __webpack_module_cache__[moduleId] = {
8
- exports: {}
9
- };
10
- __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
11
- return module.exports;
12
- }
13
- __webpack_require__.m = __webpack_modules__;
2
+ var __webpack_require__ = {};
14
3
  (()=>{
15
4
  __webpack_require__.d = (exports, definition)=>{
16
5
  for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) Object.defineProperty(exports, key, {
@@ -19,11 +8,6 @@ __webpack_require__.m = __webpack_modules__;
19
8
  });
20
9
  };
21
10
  })();
22
- (()=>{
23
- __webpack_require__.add = function(modules) {
24
- Object.assign(__webpack_require__.m, modules);
25
- };
26
- })();
27
11
  (()=>{
28
12
  __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
29
13
  })();
@@ -375,7 +375,9 @@ function getFilename(feature, filePath) {
375
375
  '.js',
376
376
  '.jsx',
377
377
  '.tsx',
378
- '.ts'
378
+ '.ts',
379
+ '.vue',
380
+ '.svelte'
379
381
  ].includes(entryExt)) fileOutputpath = fileOutputpath.replace(entryExt, '.js');
380
382
  if ([
381
383
  '.html',
@@ -817,6 +819,7 @@ function storage(manifest) {
817
819
  };
818
820
  }
819
821
  const theme_getBasename = (filepath)=>__rspack_external_path.basename(filepath);
822
+ const rewriteThemeImage = (value)=>getFilename(`theme/images/${theme_getBasename(value)}`, value);
820
823
  function theme(manifest) {
821
824
  return manifest.theme && {
822
825
  theme: {
@@ -824,7 +827,7 @@ function theme(manifest) {
824
827
  ...manifest.theme.images && {
825
828
  images: Object.fromEntries(Object.entries(manifest.theme.images).map(([key, value])=>[
826
829
  key,
827
- getFilename(`theme/images/${theme_getBasename(value)}`, value)
830
+ Array.isArray(value) ? value.map(rewriteThemeImage) : rewriteThemeImage(value)
828
831
  ]))
829
832
  }
830
833
  }
@@ -851,16 +854,18 @@ function normalizeOutputPath(originalPath) {
851
854
  }
852
855
  function webAccessibleResources(manifest) {
853
856
  if (!manifest.web_accessible_resources || !manifest.web_accessible_resources.length) return;
854
- if (Array.isArray(manifest.web_accessible_resources) && 'string' == typeof manifest.web_accessible_resources[0]) return {
855
- web_accessible_resources: manifest.web_accessible_resources.map((resource)=>getFilename(normalizeOutputPath(resource), resource))
856
- };
857
- const v3 = manifest.web_accessible_resources.filter((entry)=>Array.isArray(entry.resources)).map((entry)=>({
857
+ const mapResource = (resource)=>getFilename(normalizeOutputPath(resource), resource);
858
+ const entries = manifest.web_accessible_resources.map((entry)=>{
859
+ if ('string' == typeof entry) return mapResource(entry);
860
+ if (!entry || !Array.isArray(entry.resources)) return;
861
+ return {
858
862
  ...entry,
859
- resources: (entry.resources || []).map((res)=>getFilename(normalizeOutputPath(res), res))
860
- }));
861
- if (0 === v3.length) return;
863
+ resources: entry.resources.map(mapResource)
864
+ };
865
+ }).filter((entry)=>void 0 !== entry);
866
+ if (0 === entries.length) return;
862
867
  return {
863
- web_accessible_resources: v3
868
+ web_accessible_resources: entries
864
869
  };
865
870
  }
866
871
  function contentSecurityPolicy(manifest) {
@@ -2910,6 +2915,7 @@ class JsFrameworksPlugin {
2910
2915
  issuerLayer: {
2911
2916
  not: EXTENSIONJS_CONTENT_SCRIPT_LAYER
2912
2917
  },
2918
+ type: "javascript/esm",
2913
2919
  exclude: [
2914
2920
  ...swcRuleBase.exclude,
2915
2921
  (resourcePath)=>isfeatureScriptsContentLike(resourcePath)
@@ -3108,6 +3114,25 @@ function purgeStaleHashedContentScripts(compilation, currentNames) {
3108
3114
  }
3109
3115
  } catch {}
3110
3116
  }
3117
+ function isGeckoBrowser(browser) {
3118
+ const b = String(browser);
3119
+ return 'firefox' === b || b.includes('firefox') || b.includes('gecko');
3120
+ }
3121
+ function patchGeckoBackground(manifest, browser) {
3122
+ if (!isGeckoBrowser(browser)) return manifest;
3123
+ const background = manifest.background;
3124
+ if (!background || !background.service_worker || background.scripts) return manifest;
3125
+ const { service_worker, type, ...rest } = background;
3126
+ return {
3127
+ ...manifest,
3128
+ background: {
3129
+ ...rest,
3130
+ scripts: [
3131
+ service_worker
3132
+ ]
3133
+ }
3134
+ };
3135
+ }
3111
3136
  class UpdateManifest {
3112
3137
  manifestPath;
3113
3138
  browser;
@@ -3133,6 +3158,7 @@ class UpdateManifest {
3133
3158
  if (compilation.errors.length > 0) return;
3134
3159
  const manifest = getManifestContent(compilation, this.manifestPath);
3135
3160
  let patchedManifest = buildCanonicalManifest(this.manifestPath, manifest, this.browser);
3161
+ patchedManifest = patchGeckoBackground(patchedManifest, this.browser);
3136
3162
  const overrides = getManifestOverrides(this.manifestPath, manifest);
3137
3163
  if ('development' === compiler.options.mode) patchedManifest = patchDevContentScriptManifestPaths(compilation, patchedManifest);
3138
3164
  if ('development' === compiler.options.mode) {
@@ -3169,6 +3195,18 @@ function getSharedFor(compilation) {
3169
3195
  }
3170
3196
  return shared;
3171
3197
  }
3198
+ function cleanMatches(matches) {
3199
+ return matches.map((match)=>{
3200
+ try {
3201
+ const url = new URL(match.replace(/^\*:\/\//, 'https://'));
3202
+ if (match.endsWith(url.pathname)) return `${match.substring(0, match.length - url.pathname.length)}/*`;
3203
+ if ('/' === url.pathname) return `${match}/*`;
3204
+ return match;
3205
+ } catch {
3206
+ return match;
3207
+ }
3208
+ });
3209
+ }
3172
3210
  function warFieldError(filePath, opts) {
3173
3211
  const displayPath = opts?.overrideNotFoundPath || filePath;
3174
3212
  const lines = [];
@@ -3179,6 +3217,10 @@ function warFieldError(filePath, opts) {
3179
3217
  lines.push(`To reference files in ${pintor.yellow('public/')}, use a leading '/' (e.g. ${pintor.yellow('/open-panel.gif')}). These resolve from the built extension root.`);
3180
3218
  lines.push('');
3181
3219
  lines.push(`Fix: Add the file to ${pintor.yellow('public/')} or update the path to the correct '/...' location.`);
3220
+ } else if (opts?.sourceSibling) {
3221
+ lines.push(`Found ${pintor.yellow(opts.sourceSibling)}, but web_accessible_resources entries are copied as-is, not compiled.`);
3222
+ lines.push('');
3223
+ lines.push(`Fix: Import the file from a script so it gets bundled, or move a prebuilt copy to ${pintor.yellow('public/')} and reference it with a leading '/'.`);
3182
3224
  } else {
3183
3225
  lines.push("Relative paths must point to a real source file so the build can emit it.");
3184
3226
  lines.push('');
@@ -3189,6 +3231,16 @@ function warFieldError(filePath, opts) {
3189
3231
  lines.push(`${pintor.red('NOT FOUND')} ${pintor.underline(displayPath)}`);
3190
3232
  return lines.join('\n');
3191
3233
  }
3234
+ function warStringEntryInMv3(entry) {
3235
+ const lines = [];
3236
+ lines.push(`Check the ${pintor.yellow('web_accessible_resources')} field in your ${pintor.yellow('manifest.json')} file.`);
3237
+ lines.push(`Manifest V3 requires object entries with ${pintor.yellow('resources')} and ${pintor.yellow('matches')} (or ${pintor.yellow('extension_ids')}). Plain string entries are the Manifest V2 format and Chrome rejects them at load time.`);
3238
+ lines.push('');
3239
+ lines.push(`Fix: Wrap it like ${pintor.yellow(`{"resources": ["${entry}"], "matches": ["<all_urls>"]}`)} with the matches your pages need.`);
3240
+ lines.push('');
3241
+ lines.push(`${pintor.red('INVALID MV3 ENTRY')} ${pintor.underline(entry)}`);
3242
+ return lines.join('\n');
3243
+ }
3192
3244
  function warInvalidMatchPattern(pattern) {
3193
3245
  const lines = [];
3194
3246
  lines.push(`Check the ${pintor.yellow('web_accessible_resources')} field in your ${pintor.yellow('manifest.json')} file.`);
@@ -3229,6 +3281,33 @@ function emitFileAsAsset(compilation, absPath) {
3229
3281
  compilation.fileDependencies.add(absPath);
3230
3282
  return unixify(outName);
3231
3283
  }
3284
+ function findSourceSibling(absOutputPath) {
3285
+ const candidatesByExt = {
3286
+ '.js': [
3287
+ '.ts',
3288
+ '.tsx',
3289
+ '.jsx',
3290
+ '.vue',
3291
+ '.svelte'
3292
+ ],
3293
+ '.css': [
3294
+ '.scss',
3295
+ '.sass',
3296
+ '.less'
3297
+ ],
3298
+ '.html': [
3299
+ '.njk',
3300
+ '.nunjucks'
3301
+ ]
3302
+ };
3303
+ const ext = __rspack_external_path.extname(absOutputPath);
3304
+ const sourceExts = candidatesByExt[ext];
3305
+ if (!sourceExts) return;
3306
+ for (const sourceExt of sourceExts){
3307
+ const candidate = absOutputPath.slice(0, -ext.length) + sourceExt;
3308
+ if (__rspack_external_fs.existsSync(candidate)) return candidate;
3309
+ }
3310
+ }
3232
3311
  function isFirefox(browser) {
3233
3312
  return !!browser && browser.toLowerCase().includes('firefox');
3234
3313
  }
@@ -3264,30 +3343,33 @@ function resolveUserDeclaredWAR(compilation, manifestPath, manifest, browser) {
3264
3343
  v2,
3265
3344
  v3
3266
3345
  };
3267
- const isV2 = Array.isArray(war) && 'string' == typeof war[0];
3346
+ const isMv2 = 3 !== manifestObj.manifest_version;
3268
3347
  const manifestDir = __rspack_external_path.dirname(manifestPath);
3269
3348
  const projectPath = compilation.options?.context || manifestDir;
3270
- const pushResource = (matches, resource)=>{
3271
- if (2 === manifestObj.manifest_version || isV2) return void v2.add(resource);
3349
+ const pushResource = (matches, resource, extra)=>{
3350
+ if (isMv2) return void v2.add(resource);
3272
3351
  const key = (matches || []).join(',');
3273
3352
  let group = v3.find((g)=>g.matches.join(',') === key);
3274
- if (!group) {
3353
+ if (group) {
3354
+ if (extra && !group.extra) group.extra = extra;
3355
+ } else {
3275
3356
  group = {
3276
3357
  matches: matches || [],
3277
- resources: new Set()
3358
+ resources: new Set(),
3359
+ extra
3278
3360
  };
3279
3361
  v3.push(group);
3280
3362
  }
3281
3363
  group.resources.add(resource);
3282
3364
  };
3283
- const handleOne = (matches, res)=>{
3365
+ const handleOne = (matches, res, extra)=>{
3284
3366
  compilation.errors = compilation.errors || [];
3285
3367
  compilation.warnings = compilation.warnings || [];
3286
3368
  const normalizedOutput = normalizeManifestOutputPath(res);
3287
3369
  const publicCandidate = __rspack_external_path.join(projectPath, 'public', normalizedOutput);
3288
- if (__rspack_external_fs.existsSync(publicCandidate)) return void pushResource(matches, normalizedOutput);
3370
+ if (__rspack_external_fs.existsSync(publicCandidate)) return void pushResource(matches, normalizedOutput, extra);
3289
3371
  __rspack_external_path.isAbsolute(res) || __rspack_external_path.join(manifestDir, isPublicRootLike(res) ? toPublicOutput(res) : res);
3290
- if (/[*?\[\]{}]/.test(res)) return void pushResource(matches, res);
3372
+ if (/[*?\[\]{}]/.test(res)) return void pushResource(matches, res, extra);
3291
3373
  if (isPublicRootLike(res)) {
3292
3374
  const output = normalizeManifestOutputPath(res);
3293
3375
  const publicAbs = __rspack_external_path.join(projectPath, 'public', output);
@@ -3318,7 +3400,7 @@ function resolveUserDeclaredWAR(compilation, manifestPath, manifest, browser) {
3318
3400
  err.file = 'manifest.json';
3319
3401
  compilation.warnings.push(err);
3320
3402
  }
3321
- pushResource(matches, output);
3403
+ pushResource(matches, output, extra);
3322
3404
  return;
3323
3405
  }
3324
3406
  const abs = __rspack_external_path.isAbsolute(res) ? res : __rspack_external_path.join(manifestDir, res);
@@ -3330,46 +3412,47 @@ function resolveUserDeclaredWAR(compilation, manifestPath, manifest, browser) {
3330
3412
  const assetEmitted = Boolean(asset2 && asset2.name === res) || __rspack_external_fs.existsSync(builtAbs);
3331
3413
  if (__rspack_external_fs.existsSync(publicAbsMaybe) || assetEmitted) {
3332
3414
  const output = toPublicOutput('/' + res);
3333
- pushResource(matches, output);
3415
+ pushResource(matches, output, extra);
3334
3416
  return;
3335
3417
  }
3418
+ const sourceSibling = findSourceSibling(abs);
3336
3419
  const msg = warFieldError(abs, {
3337
- relativeRef: res
3420
+ relativeRef: res,
3421
+ sourceSibling: sourceSibling ? __rspack_external_path.relative(manifestDir, sourceSibling) : void 0
3338
3422
  });
3339
3423
  const warn = new core_WebpackError(msg);
3340
3424
  warn.file = 'manifest.json';
3341
3425
  warn.name = 'WARRelativeAssetMissing';
3342
3426
  compilation.warnings.push(warn);
3343
- pushResource(matches, res);
3344
3427
  return;
3345
3428
  }
3346
3429
  const emitted = emitFileAsAsset(compilation, abs);
3347
- pushResource(matches, emitted);
3430
+ pushResource(matches, emitted, extra);
3348
3431
  };
3349
- if (isV2) war.forEach((res)=>handleOne(void 0, res));
3350
- else war.forEach((group)=>{
3351
- const matches = group.matches || [];
3432
+ war.forEach((entry)=>{
3433
+ if ('string' == typeof entry) {
3434
+ if (isMv2) return void handleOne(void 0, entry);
3435
+ const msg = warStringEntryInMv3(entry);
3436
+ const err = new core_WebpackError(msg);
3437
+ err.file = 'manifest.json';
3438
+ err.name = 'WARStringEntryInMv3';
3439
+ compilation.errors = compilation.errors || [];
3440
+ compilation.errors.push(err);
3441
+ return;
3442
+ }
3443
+ if (!entry || 'object' != typeof entry) return;
3444
+ const matches = entry.matches || [];
3352
3445
  validateMatchesOrReport(compilation, matches, browser);
3353
- if (!Array.isArray(group.resources)) return;
3354
- group.resources.forEach((res)=>handleOne(matches, res));
3446
+ if (!Array.isArray(entry.resources)) return;
3447
+ const { resources: _resources, matches: _matches, ...extra } = entry;
3448
+ const extraFields = Object.keys(extra).length > 0 ? extra : void 0;
3449
+ entry.resources.forEach((res)=>handleOne(matches, res, extraFields));
3355
3450
  });
3356
3451
  return {
3357
3452
  v2,
3358
3453
  v3
3359
3454
  };
3360
3455
  }
3361
- function cleanMatches(matches) {
3362
- return matches.map((match)=>{
3363
- try {
3364
- const url = new URL(match.replace(/^\*:\/\//, 'https://'));
3365
- if (match.endsWith(url.pathname)) return `${match.substring(0, match.length - url.pathname.length)}/*`;
3366
- if ('/' === url.pathname) return `${match}/*`;
3367
- return match;
3368
- } catch {
3369
- return match;
3370
- }
3371
- });
3372
- }
3373
3456
  function getAssetSource(compilation, filename) {
3374
3457
  const byGet = 'function' == typeof compilation.getAsset ? compilation.getAsset(filename) : void 0;
3375
3458
  const byAssets = !byGet && compilation.assets ? compilation.assets[filename] : void 0;
@@ -3434,6 +3517,7 @@ function mergeIntoV3Group(groups, normalizedMatches, resources, options) {
3434
3517
  ].sort()
3435
3518
  });
3436
3519
  }
3520
+ const assetScanCache = new WeakMap();
3437
3521
  function isCanonicalContentScriptCss(resource) {
3438
3522
  return /^content_scripts\/content-\d+\.css$/.test(resource);
3439
3523
  }
@@ -3446,6 +3530,7 @@ function generateManifestPatches(compilation, manifestPath, entryImports, browse
3446
3530
  const canonicalManifest = getManifestContent(compilation, manifestPath);
3447
3531
  const resolved = resolveUserDeclaredWAR(compilation, manifestPath, canonicalManifest, browser);
3448
3532
  const webAccessibleResourcesV3 = 3 === canonicalManifest.manifest_version ? resolved.v3.map((g)=>({
3533
+ ...g.extra || {},
3449
3534
  matches: g.matches,
3450
3535
  resources: Array.from(g.resources)
3451
3536
  })) : [];
@@ -3455,11 +3540,17 @@ function generateManifestPatches(compilation, manifestPath, entryImports, browse
3455
3540
  const normalizedMatches = cleanMatches(matches);
3456
3541
  const jsFiles = Array.isArray(contentScript.js) ? contentScript.js : [];
3457
3542
  for (const jsFile of jsFiles){
3458
- const source = getAssetSource(compilation, jsFile);
3459
- if (!source) continue;
3460
- const re = /assets\/[A-Za-z0-9._-]+/g;
3461
- const found = source.match(re) || [];
3462
- const filtered = Array.from(new Set(found.filter((r)=>!r.endsWith('.js') && !r.endsWith('.map')))).sort();
3543
+ const assetForCache = 'function' == typeof compilation.getAsset ? compilation.getAsset(jsFile) : void 0;
3544
+ const cacheKey = assetForCache && 'object' == typeof assetForCache.source ? assetForCache.source : void 0;
3545
+ let filtered = cacheKey ? assetScanCache.get(cacheKey) : void 0;
3546
+ if (!filtered) {
3547
+ const source = getAssetSource(compilation, jsFile);
3548
+ if (!source) continue;
3549
+ const re = /assets\/[A-Za-z0-9._-]+/g;
3550
+ const found = source.match(re) || [];
3551
+ filtered = Array.from(new Set(found.filter((r)=>!r.endsWith('.js') && !r.endsWith('.map')))).sort();
3552
+ if (cacheKey) assetScanCache.set(cacheKey, filtered);
3553
+ }
3463
3554
  if (0 !== filtered.length) mergeIntoV3Group(webAccessibleResourcesV3, normalizedMatches, filtered);
3464
3555
  }
3465
3556
  }
@@ -3513,7 +3604,8 @@ function generateManifestPatches(compilation, manifestPath, entryImports, browse
3513
3604
  const emittedCssUnderContentScripts = assetKeys.filter((k)=>k.startsWith("content_scripts/")).filter((k)=>k.endsWith('.css'));
3514
3605
  const onDiskCssUnderContentScripts = [];
3515
3606
  const outputPath = compilation.options.output?.path;
3516
- if (outputPath) try {
3607
+ const isDevModeBuild = 'production' !== (compilation.options?.mode || 'development');
3608
+ if (outputPath && isDevModeBuild) try {
3517
3609
  const csDir = __rspack_external_path.join(outputPath, "content_scripts");
3518
3610
  if (__rspack_external_fs.existsSync(csDir)) {
3519
3611
  for (const name of __rspack_external_fs.readdirSync(csDir))if (name.endsWith('.css')) onDiskCssUnderContentScripts.push(`content_scripts/${name}`);
@@ -3542,6 +3634,7 @@ function generateManifestPatches(compilation, manifestPath, entryImports, browse
3542
3634
  }
3543
3635
  if (3 === canonicalManifest.manifest_version) {
3544
3636
  if (webAccessibleResourcesV3.length > 0) canonicalManifest.web_accessible_resources = webAccessibleResourcesV3.map((entry)=>({
3637
+ ...entry,
3545
3638
  resources: Array.from(new Set(entry.resources)).sort(),
3546
3639
  matches: Array.from(new Set(entry.matches)).sort()
3547
3640
  })).sort((a, b)=>a.matches.join(',').localeCompare(b.matches.join(',')));
@@ -6647,6 +6740,44 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
6647
6740
  var consoleRef = g.console || {};
6648
6741
  g.__extjsBridgeProducerInstalled = true;
6649
6742
 
6743
+ // Capture extension event listeners at install time. The producer is
6744
+ // prepended to the background bundle, so these wraps run BEFORE user code
6745
+ // registers its listeners — letting the bridge replay them on demand. The
6746
+ // platform exposes no API to dispatch these events, and CDP attaches too
6747
+ // late to wrap addListener, so this is the only path. Replay invokes the
6748
+ // handler WITHOUT a user gesture, so the gesture-derived activeTab grant
6749
+ // does NOT apply (callers are told gesture:false). This is engine-agnostic:
6750
+ // it works on Chromium and Gecko because it only touches addListener.
6751
+ //
6752
+ // captureEvent transparently wraps addListener/removeListener (delegating to
6753
+ // the originals) and records callbacks into the sink array.
6754
+ function captureEvent(event, sink) {
6755
+ try {
6756
+ if (!event || typeof event.addListener !== "function") return;
6757
+ var origAdd = event.addListener.bind(event);
6758
+ event.addListener = function (cb) {
6759
+ if (typeof cb === "function" && sink.indexOf(cb) === -1) sink.push(cb);
6760
+ return origAdd(cb);
6761
+ };
6762
+ if (typeof event.removeListener === "function") {
6763
+ var origRemove = event.removeListener.bind(event);
6764
+ event.removeListener = function (cb) {
6765
+ var i = sink.indexOf(cb);
6766
+ if (i !== -1) sink.splice(i, 1);
6767
+ return origRemove(cb);
6768
+ };
6769
+ }
6770
+ } catch (e) { /* non-fatal: trigger falls back to its no-listener reply */ }
6771
+ }
6772
+
6773
+ // Use g.chrome (not the hoisted chrome var below) — this runs first.
6774
+ var actionClickedListeners = [];
6775
+ var commandListeners = [];
6776
+ if (g.chrome) {
6777
+ captureEvent(g.chrome.action && g.chrome.action.onClicked, actionClickedListeners);
6778
+ captureEvent(g.chrome.commands && g.chrome.commands.onCommand, commandListeners);
6779
+ }
6780
+
6650
6781
  var LEVELS = ["log", "info", "warn", "error", "debug", "trace"];
6651
6782
  var socket = null;
6652
6783
  var open = false;
@@ -6751,6 +6882,42 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
6751
6882
  }
6752
6883
  if (op === "open") {
6753
6884
  var surface = args.surface || ctx;
6885
+ // getPopup is promise-style on Gecko and (MV3) Chromium; fall back to
6886
+ // callback-style for older Chromium. Always resolves to a string.
6887
+ var getActionPopup = function (cb) {
6888
+ try {
6889
+ var r = chrome.action && chrome.action.getPopup && chrome.action.getPopup({});
6890
+ if (r && typeof r.then === "function") {
6891
+ r.then(function (p) { cb(p || ""); }, function () { cb(""); });
6892
+ return;
6893
+ }
6894
+ } catch (e) {}
6895
+ try { chrome.action.getPopup({}, function (p) { cb(p || ""); }); }
6896
+ catch (e) { cb(""); }
6897
+ };
6898
+ // Resolve the tab a replayed event should carry: an explicit args.tabId,
6899
+ // else the active tab of the focused window.
6900
+ var resolveActiveTab = function (a, cb) {
6901
+ if (a && typeof a.tabId === "number") {
6902
+ try { chrome.tabs.get(a.tabId, function (t) { cb(t || {id: a.tabId}); }); return; }
6903
+ catch (e) {}
6904
+ }
6905
+ try { chrome.tabs.query({active: true, lastFocusedWindow: true}, function (tabs) { cb((tabs && tabs[0]) || undefined); }); }
6906
+ catch (e) { cb(undefined); }
6907
+ };
6908
+ // Replaying a listener carries no user gesture, so activeTab is never
6909
+ // granted. Warn when the manifest declares it (handler will diverge
6910
+ // from a real click).
6911
+ var activeTabWarning = function () {
6912
+ try {
6913
+ var m = chrome.runtime.getManifest();
6914
+ var perms = (m && m.permissions) || [];
6915
+ if (perms.indexOf("activeTab") !== -1) {
6916
+ return "replayed without a user gesture: activeTab is NOT granted, so APIs that depend on it (scripting on the active tab, captureVisibleTab) behave differently than a real click";
6917
+ }
6918
+ } catch (e) {}
6919
+ return null;
6920
+ };
6754
6921
  if (surface === "popup") {
6755
6922
  if (chrome.action && chrome.action.openPopup) {
6756
6923
  chrome.action.openPopup().then(function () { replyOk(cmdId, {opened: "popup"}); }, function (e) { replyErr(cmdId, "Unsupported", "openPopup: " + e); });
@@ -6764,6 +6931,47 @@ const BRIDGE_PRODUCER_SOURCE = `;(function () {
6764
6931
  chrome.sidePanel.open({windowId: w.id}).then(function () { replyOk(cmdId, {opened: "sidebar"}); }, function (e) { replyErr(cmdId, "Unsupported", "sidePanel.open: " + e); });
6765
6932
  });
6766
6933
  } else { replyErr(cmdId, "Unsupported", "sidePanel not available (engine: " + engineName() + ")"); }
6934
+ } else if (surface === "action") {
6935
+ // Trigger the toolbar action. With a default_popup, clicking the icon
6936
+ // opens it (reuse openPopup). Without a popup, clicking fires
6937
+ // chrome.action.onClicked — we replay the listeners captured at install.
6938
+ if (chrome.action) {
6939
+ getActionPopup(function (popup) {
6940
+ if (popup) {
6941
+ try {
6942
+ chrome.action.openPopup().then(function () { replyOk(cmdId, {triggered: "popup"}); }, function (e) { replyErr(cmdId, "Unsupported", "openPopup: " + e); });
6943
+ } catch (e) { replyErr(cmdId, "Unsupported", "openPopup: " + e); }
6944
+ } else if (actionClickedListeners.length) {
6945
+ resolveActiveTab(args, function (tab) {
6946
+ var fired = 0;
6947
+ for (var i = 0; i < actionClickedListeners.length; i++) {
6948
+ try { actionClickedListeners[i](tab); fired++; } catch (e) {}
6949
+ }
6950
+ var reply = {triggered: "onClicked", listeners: fired, gesture: false};
6951
+ var warning = activeTabWarning();
6952
+ if (warning) reply.warning = warning;
6953
+ replyOk(cmdId, reply);
6954
+ });
6955
+ } else {
6956
+ replyErr(cmdId, "Unsupported", "action has no popup and no onClicked listener registered");
6957
+ }
6958
+ });
6959
+ } else { replyErr(cmdId, "Unsupported", "chrome.action not available (engine: " + engineName() + ")"); }
6960
+ } else if (surface === "command") {
6961
+ // Replay a captured chrome.commands.onCommand listener (keyboard
6962
+ // shortcut). Same no-gesture caveat as onClicked.
6963
+ var commandName = (args && args.name) || undefined;
6964
+ if (!commandListeners.length) {
6965
+ replyErr(cmdId, "Unsupported", "no chrome.commands.onCommand listener registered");
6966
+ } else {
6967
+ resolveActiveTab(args, function (tab) {
6968
+ var fired = 0;
6969
+ for (var i = 0; i < commandListeners.length; i++) {
6970
+ try { commandListeners[i](commandName, tab); fired++; } catch (e) {}
6971
+ }
6972
+ replyOk(cmdId, {triggered: "command", command: commandName || null, listeners: fired, gesture: false});
6973
+ });
6974
+ }
6767
6975
  } else { replyErr(cmdId, "BadRequest", "unknown surface: " + surface); }
6768
6976
  return;
6769
6977
  }
@@ -7068,7 +7276,7 @@ function buildBridgeProducerSource(opts) {
7068
7276
  if (!opts.controlPort || opts.controlPort < 1) return '';
7069
7277
  return BRIDGE_PRODUCER_SOURCE.replace(/__EXTJS_CONTROL_PORT__/g, String(opts.controlPort)).replace(/__EXTJS_INSTANCE_ID__/g, String(opts.instanceId)).replace(/__EXTJS_CONTEXT__/g, String(opts.context || 'background'));
7070
7278
  }
7071
- const inject_bridge_producer_BACKGROUND_ASSET = /(^|\/)background\/(?:service_worker|script)\.js$/i;
7279
+ const inject_bridge_producer_BACKGROUND_ASSET = /(^|\/)background\/(?:service_worker|scripts?)\.js$/i;
7072
7280
  class InjectBridgeProducer {
7073
7281
  apply(compiler) {
7074
7282
  const controlPort = parseInt(String(process.env.EXTENSION_CONTROL_PORT || ''), 10);
@@ -8697,6 +8905,12 @@ function webpackConfig(projectStructure, devOptions) {
8697
8905
  mode: devOptions.mode || 'development',
8698
8906
  entry: {},
8699
8907
  target: 'web',
8908
+ externals: [
8909
+ ({ request }, callback)=>{
8910
+ if ('string' == typeof request && /^(chrome|moz)-extension:/i.test(request)) return callback(null, request, 'asset');
8911
+ callback();
8912
+ }
8913
+ ],
8700
8914
  context: packageJsonDir,
8701
8915
  cache: false,
8702
8916
  devtool: 'production' === (devOptions.mode || 'development') ? false : 3 === manifest.manifest_version ? 'cheap-source-map' : 'eval-cheap-source-map',