extension-develop 3.18.3 → 3.18.4-canary.320.767e107

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