extension 3.14.2 → 3.14.3

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/631.cjs CHANGED
@@ -14,153 +14,6 @@ exports.modules = {
14
14
  var external_fs_ = __webpack_require__("fs");
15
15
  var content_script_targets = __webpack_require__("./browsers/browsers-lib/content-script-targets.ts");
16
16
  var content_script_contracts = __webpack_require__("./browsers/browsers-lib/content-script-contracts.ts");
17
- async function deriveExtensionIdFromTargetsHelper(cdp, outPath, maxRetries = 6, backoffMs = 150, profilePath, extensionPaths) {
18
- let expectedName;
19
- let expectedVersion;
20
- let expectedManifestVersion;
21
- let expectedNameIsMsg = false;
22
- try {
23
- const manifest = JSON.parse(external_fs_.readFileSync(external_path_.join(outPath, 'manifest.json'), 'utf-8'));
24
- expectedName = manifest?.name;
25
- expectedVersion = manifest?.version;
26
- expectedManifestVersion = manifest?.manifest_version;
27
- expectedNameIsMsg = 'string' == typeof expectedName && /__MSG_/i.test(expectedName);
28
- if (expectedNameIsMsg) {
29
- const defaultLocale = String(manifest?.default_locale || '').trim();
30
- const msgKeyMatch = String(expectedName || '').match(/__MSG_(.+)__/i);
31
- const msgKey = msgKeyMatch ? msgKeyMatch[1] : '';
32
- if (defaultLocale && msgKey) {
33
- const messagesPath = external_path_.join(outPath, '_locales', defaultLocale, 'messages.json');
34
- if (external_fs_.existsSync(messagesPath)) {
35
- const messagesJson = JSON.parse(external_fs_.readFileSync(messagesPath, 'utf-8'));
36
- const resolved = String(messagesJson?.[msgKey]?.message || '').trim();
37
- if (resolved) {
38
- expectedName = resolved;
39
- expectedNameIsMsg = false;
40
- }
41
- }
42
- }
43
- }
44
- } catch {}
45
- const trimTrailingSep = (p)=>{
46
- let end = p.length;
47
- while(end > 0 && ('/' === p[end - 1] || '\\' === p[end - 1]))end--;
48
- return p.slice(0, end);
49
- };
50
- const normalizePath = (p)=>{
51
- try {
52
- const resolved = external_path_.resolve(p);
53
- if (external_fs_.existsSync(resolved)) return trimTrailingSep(external_fs_.realpathSync(resolved));
54
- return trimTrailingSep(resolved);
55
- } catch {
56
- return trimTrailingSep(external_path_.resolve(p));
57
- }
58
- };
59
- const resolvedOutPath = normalizePath(outPath);
60
- const normalizedCandidates = Array.isArray(extensionPaths) ? extensionPaths.map((p)=>p ? normalizePath(p) : '').filter(Boolean) : [];
61
- const resolvedCandidates = normalizedCandidates.length ? normalizedCandidates : [
62
- resolvedOutPath
63
- ];
64
- const platformIsCaseInsensitive = 'win32' === process.platform || 'darwin' === process.platform;
65
- const normalizeForCompare = (p)=>platformIsCaseInsensitive ? p.toLowerCase() : p;
66
- const matchesAnyCandidate = (p)=>{
67
- const n = normalizeForCompare(p);
68
- return resolvedCandidates.some((candidate)=>n === normalizeForCompare(candidate));
69
- };
70
- const deriveFromProfile = ()=>{
71
- if (!profilePath) return null;
72
- const candidates = [];
73
- const pushPrefIfExists = (dir)=>{
74
- const prefPath = external_path_.join(dir, 'Preferences');
75
- if (external_fs_.existsSync(prefPath)) candidates.push(prefPath);
76
- };
77
- try {
78
- pushPrefIfExists(profilePath);
79
- pushPrefIfExists(external_path_.join(profilePath, 'Default'));
80
- const entries = external_fs_.readdirSync(profilePath);
81
- for (const entry of entries)if (/^Profile\s+\d+$/i.test(entry)) pushPrefIfExists(external_path_.join(profilePath, entry));
82
- } catch {}
83
- for (const prefPath of candidates)try {
84
- if (!external_fs_.existsSync(prefPath)) continue;
85
- const prefs = JSON.parse(external_fs_.readFileSync(prefPath, 'utf-8'));
86
- const settings = prefs?.extensions?.settings;
87
- if (!settings || 'object' != typeof settings) continue;
88
- const entries = Object.entries(settings);
89
- let fallbackId = null;
90
- for (const [id, info] of entries){
91
- const storedPath = String(info?.path || '');
92
- if (!storedPath) continue;
93
- const normalized = normalizePath(storedPath);
94
- if (!matchesAnyCandidate(normalized)) continue;
95
- const manifestName = String(info?.manifest?.name || '');
96
- const manifestVersion = String(info?.manifest?.version || '');
97
- if (expectedName && manifestName === expectedName) return id;
98
- if (expectedVersion && manifestVersion === expectedVersion) return id;
99
- fallbackId = id;
100
- }
101
- if (fallbackId) return fallbackId;
102
- } catch {}
103
- return null;
104
- };
105
- let retries = 0;
106
- let deferredFirstEvalId = null;
107
- let deferredUrlDerivedId = null;
108
- const hasExpectedManifestIdentity = Boolean(expectedName || expectedVersion || expectedManifestVersion);
109
- while(retries <= maxRetries){
110
- try {
111
- const targets = await cdp.getTargets();
112
- const profileCandidateId = deriveFromProfile();
113
- let firstEvalId = null;
114
- let evalIdCount = 0;
115
- let urlDerivedId = null;
116
- for (const t of targets || []){
117
- const url = String(t?.url || '');
118
- const type = String(t?.type || '');
119
- const typeOk = [
120
- 'service_worker',
121
- 'background_page',
122
- 'worker'
123
- ].includes(type);
124
- if (!typeOk) continue;
125
- const urlMatch = url.match(/^chrome-extension:\/\/([^\/]+)/);
126
- if (!urlDerivedId && urlMatch?.[1]) urlDerivedId = String(urlMatch[1]);
127
- if (url && !url.startsWith('chrome-extension://')) continue;
128
- const targetId = t?.targetId;
129
- if (targetId) try {
130
- const sessionId = await cdp.attachToTarget(targetId);
131
- if (!sessionId) continue;
132
- await cdp.sendCommand('Runtime.enable', {}, sessionId);
133
- const info = await cdp.evaluate(sessionId, '(()=>{try{const m=chrome.runtime.getManifest?.();return {id:chrome.runtime?.id||"",name:m?.name||"",version:m?.version||"",manifestVersion:m?.manifest_version||0}}catch(_){return null}})()');
134
- const id = String(info?.id || '').trim();
135
- if (!id) continue;
136
- evalIdCount += 1;
137
- if (!firstEvalId) firstEvalId = id;
138
- if (profileCandidateId && id === profileCandidateId) return id;
139
- const gotName = String(info?.name || '');
140
- const gotVersion = String(info?.version || '');
141
- const gotManifestVersion = Number(info?.manifestVersion || 0);
142
- const nameMatches = expectedName && !expectedNameIsMsg ? gotName === expectedName : false;
143
- const versionMatches = expectedVersion ? gotVersion === expectedVersion : false;
144
- const manifestVersionMatches = expectedManifestVersion ? gotManifestVersion === expectedManifestVersion : false;
145
- if (nameMatches && (!profileCandidateId || id === profileCandidateId)) return id;
146
- if (expectedVersion && versionMatches && (expectedManifestVersion ? manifestVersionMatches : true) && (!profileCandidateId || id === profileCandidateId)) return id;
147
- } catch {}
148
- }
149
- if (1 === evalIdCount && firstEvalId) {
150
- if (!hasExpectedManifestIdentity) return firstEvalId;
151
- deferredFirstEvalId = deferredFirstEvalId || firstEvalId;
152
- }
153
- if (profileCandidateId) return profileCandidateId;
154
- if (urlDerivedId) {
155
- if (!hasExpectedManifestIdentity) return urlDerivedId;
156
- deferredUrlDerivedId = deferredUrlDerivedId || urlDerivedId;
157
- }
158
- } catch {}
159
- await new Promise((r)=>setTimeout(r, backoffMs));
160
- retries++;
161
- }
162
- return deriveFromProfile() || deferredFirstEvalId || deferredUrlDerivedId;
163
- }
164
17
  var external_ws_ = __webpack_require__("ws");
165
18
  var external_ws_default = /*#__PURE__*/ __webpack_require__.n(external_ws_);
166
19
  var constants = __webpack_require__("./browsers/browsers-lib/constants.ts");
@@ -648,6 +501,17 @@ exports.modules = {
648
501
  else obj[key] = value;
649
502
  return obj;
650
503
  }
504
+ const EXTENSION_AUTO_ATTACH_FILTER = [
505
+ {
506
+ type: 'page',
507
+ exclude: true
508
+ },
509
+ {
510
+ type: 'iframe',
511
+ exclude: true
512
+ },
513
+ {}
514
+ ];
651
515
  class CDPClient {
652
516
  isDev() {
653
517
  return 'true' === process.env.EXTENSION_AUTHOR_MODE;
@@ -1001,6 +865,153 @@ exports.modules = {
1001
865
  this.host = host;
1002
866
  }
1003
867
  }
868
+ async function deriveExtensionIdFromTargetsHelper(cdp, outPath, maxRetries = 6, backoffMs = 150, profilePath, extensionPaths) {
869
+ let expectedName;
870
+ let expectedVersion;
871
+ let expectedManifestVersion;
872
+ let expectedNameIsMsg = false;
873
+ try {
874
+ const manifest = JSON.parse(external_fs_.readFileSync(external_path_.join(outPath, 'manifest.json'), 'utf-8'));
875
+ expectedName = manifest?.name;
876
+ expectedVersion = manifest?.version;
877
+ expectedManifestVersion = manifest?.manifest_version;
878
+ expectedNameIsMsg = 'string' == typeof expectedName && /__MSG_/i.test(expectedName);
879
+ if (expectedNameIsMsg) {
880
+ const defaultLocale = String(manifest?.default_locale || '').trim();
881
+ const msgKeyMatch = String(expectedName || '').match(/__MSG_(.+)__/i);
882
+ const msgKey = msgKeyMatch ? msgKeyMatch[1] : '';
883
+ if (defaultLocale && msgKey) {
884
+ const messagesPath = external_path_.join(outPath, '_locales', defaultLocale, 'messages.json');
885
+ if (external_fs_.existsSync(messagesPath)) {
886
+ const messagesJson = JSON.parse(external_fs_.readFileSync(messagesPath, 'utf-8'));
887
+ const resolved = String(messagesJson?.[msgKey]?.message || '').trim();
888
+ if (resolved) {
889
+ expectedName = resolved;
890
+ expectedNameIsMsg = false;
891
+ }
892
+ }
893
+ }
894
+ }
895
+ } catch {}
896
+ const trimTrailingSep = (p)=>{
897
+ let end = p.length;
898
+ while(end > 0 && ('/' === p[end - 1] || '\\' === p[end - 1]))end--;
899
+ return p.slice(0, end);
900
+ };
901
+ const normalizePath = (p)=>{
902
+ try {
903
+ const resolved = external_path_.resolve(p);
904
+ if (external_fs_.existsSync(resolved)) return trimTrailingSep(external_fs_.realpathSync(resolved));
905
+ return trimTrailingSep(resolved);
906
+ } catch {
907
+ return trimTrailingSep(external_path_.resolve(p));
908
+ }
909
+ };
910
+ const resolvedOutPath = normalizePath(outPath);
911
+ const normalizedCandidates = Array.isArray(extensionPaths) ? extensionPaths.map((p)=>p ? normalizePath(p) : '').filter(Boolean) : [];
912
+ const resolvedCandidates = normalizedCandidates.length ? normalizedCandidates : [
913
+ resolvedOutPath
914
+ ];
915
+ const platformIsCaseInsensitive = 'win32' === process.platform || 'darwin' === process.platform;
916
+ const normalizeForCompare = (p)=>platformIsCaseInsensitive ? p.toLowerCase() : p;
917
+ const matchesAnyCandidate = (p)=>{
918
+ const n = normalizeForCompare(p);
919
+ return resolvedCandidates.some((candidate)=>n === normalizeForCompare(candidate));
920
+ };
921
+ const deriveFromProfile = ()=>{
922
+ if (!profilePath) return null;
923
+ const candidates = [];
924
+ const pushPrefIfExists = (dir)=>{
925
+ const prefPath = external_path_.join(dir, 'Preferences');
926
+ if (external_fs_.existsSync(prefPath)) candidates.push(prefPath);
927
+ };
928
+ try {
929
+ pushPrefIfExists(profilePath);
930
+ pushPrefIfExists(external_path_.join(profilePath, 'Default'));
931
+ const entries = external_fs_.readdirSync(profilePath);
932
+ for (const entry of entries)if (/^Profile\s+\d+$/i.test(entry)) pushPrefIfExists(external_path_.join(profilePath, entry));
933
+ } catch {}
934
+ for (const prefPath of candidates)try {
935
+ if (!external_fs_.existsSync(prefPath)) continue;
936
+ const prefs = JSON.parse(external_fs_.readFileSync(prefPath, 'utf-8'));
937
+ const settings = prefs?.extensions?.settings;
938
+ if (!settings || 'object' != typeof settings) continue;
939
+ const entries = Object.entries(settings);
940
+ let fallbackId = null;
941
+ for (const [id, info] of entries){
942
+ const storedPath = String(info?.path || '');
943
+ if (!storedPath) continue;
944
+ const normalized = normalizePath(storedPath);
945
+ if (!matchesAnyCandidate(normalized)) continue;
946
+ const manifestName = String(info?.manifest?.name || '');
947
+ const manifestVersion = String(info?.manifest?.version || '');
948
+ if (expectedName && manifestName === expectedName) return id;
949
+ if (expectedVersion && manifestVersion === expectedVersion) return id;
950
+ fallbackId = id;
951
+ }
952
+ if (fallbackId) return fallbackId;
953
+ } catch {}
954
+ return null;
955
+ };
956
+ let retries = 0;
957
+ let deferredFirstEvalId = null;
958
+ let deferredUrlDerivedId = null;
959
+ const hasExpectedManifestIdentity = Boolean(expectedName || expectedVersion || expectedManifestVersion);
960
+ while(retries <= maxRetries){
961
+ try {
962
+ const targets = await cdp.getTargets();
963
+ const profileCandidateId = deriveFromProfile();
964
+ let firstEvalId = null;
965
+ let evalIdCount = 0;
966
+ let urlDerivedId = null;
967
+ for (const t of targets || []){
968
+ const url = String(t?.url || '');
969
+ const type = String(t?.type || '');
970
+ const typeOk = [
971
+ 'service_worker',
972
+ 'background_page',
973
+ 'worker'
974
+ ].includes(type);
975
+ if (!typeOk) continue;
976
+ const urlMatch = url.match(/^chrome-extension:\/\/([^\/]+)/);
977
+ if (!urlDerivedId && urlMatch?.[1]) urlDerivedId = String(urlMatch[1]);
978
+ if (url && !url.startsWith('chrome-extension://')) continue;
979
+ const targetId = t?.targetId;
980
+ if (targetId) try {
981
+ const sessionId = await cdp.attachToTarget(targetId);
982
+ if (!sessionId) continue;
983
+ await cdp.sendCommand('Runtime.enable', {}, sessionId);
984
+ const info = await cdp.evaluate(sessionId, '(()=>{try{const m=chrome.runtime.getManifest?.();return {id:chrome.runtime?.id||"",name:m?.name||"",version:m?.version||"",manifestVersion:m?.manifest_version||0}}catch(_){return null}})()');
985
+ const id = String(info?.id || '').trim();
986
+ if (!id) continue;
987
+ evalIdCount += 1;
988
+ if (!firstEvalId) firstEvalId = id;
989
+ if (profileCandidateId && id === profileCandidateId) return id;
990
+ const gotName = String(info?.name || '');
991
+ const gotVersion = String(info?.version || '');
992
+ const gotManifestVersion = Number(info?.manifestVersion || 0);
993
+ const nameMatches = expectedName && !expectedNameIsMsg ? gotName === expectedName : false;
994
+ const versionMatches = expectedVersion ? gotVersion === expectedVersion : false;
995
+ const manifestVersionMatches = expectedManifestVersion ? gotManifestVersion === expectedManifestVersion : false;
996
+ if (nameMatches && (!profileCandidateId || id === profileCandidateId)) return id;
997
+ if (expectedVersion && versionMatches && (expectedManifestVersion ? manifestVersionMatches : true) && (!profileCandidateId || id === profileCandidateId)) return id;
998
+ } catch {}
999
+ }
1000
+ if (1 === evalIdCount && firstEvalId) {
1001
+ if (!hasExpectedManifestIdentity) return firstEvalId;
1002
+ deferredFirstEvalId = deferredFirstEvalId || firstEvalId;
1003
+ }
1004
+ if (profileCandidateId) return profileCandidateId;
1005
+ if (urlDerivedId) {
1006
+ if (!hasExpectedManifestIdentity) return urlDerivedId;
1007
+ deferredUrlDerivedId = deferredUrlDerivedId || urlDerivedId;
1008
+ }
1009
+ } catch {}
1010
+ await new Promise((r)=>setTimeout(r, backoffMs));
1011
+ retries++;
1012
+ }
1013
+ return deriveFromProfile() || deferredFirstEvalId || deferredUrlDerivedId;
1014
+ }
1004
1015
  function isRecoverableBootstrapError(error) {
1005
1016
  const msg = String(error?.message || error || '').toLowerCase();
1006
1017
  return msg.includes('econnreset') || msg.includes('websocket is not open') || msg.includes('cdp transport is not open') || msg.includes('cdp connection closed') || msg.includes('cdp pipe closed') || msg.includes('socket hang up') || msg.includes('timed out') || msg.includes('no cdp websocket url');
@@ -1015,7 +1026,8 @@ exports.modules = {
1015
1026
  await cdp.sendCommand('Target.setAutoAttach', {
1016
1027
  autoAttach: true,
1017
1028
  waitForDebuggerOnStart: false,
1018
- flatten: true
1029
+ flatten: true,
1030
+ filter: EXTENSION_AUTO_ATTACH_FILTER
1019
1031
  });
1020
1032
  return cdp;
1021
1033
  } catch (error) {
@@ -1045,7 +1057,8 @@ exports.modules = {
1045
1057
  await cdp.sendCommand('Target.setAutoAttach', {
1046
1058
  autoAttach: true,
1047
1059
  waitForDebuggerOnStart: false,
1048
- flatten: true
1060
+ flatten: true,
1061
+ filter: EXTENSION_AUTO_ATTACH_FILTER
1049
1062
  });
1050
1063
  return cdp;
1051
1064
  } catch (error) {
@@ -1265,6 +1278,117 @@ exports.modules = {
1265
1278
  }
1266
1279
  return reinjectedTabs;
1267
1280
  }
1281
+ async registerContentScriptsForFutureNavigations(rules) {
1282
+ this.activeContentScriptRules = [
1283
+ ...rules
1284
+ ];
1285
+ if (!this.cdp) return;
1286
+ if (!this.contentScriptTargetListenerInstalled) {
1287
+ this.contentScriptTargetListenerInstalled = true;
1288
+ this.installContentScriptTargetListener();
1289
+ }
1290
+ try {
1291
+ const targets = await this.cdp.getTargets();
1292
+ for (const target of targets || []){
1293
+ if ('page' !== String(target?.type || '')) continue;
1294
+ const targetId = String(target?.targetId || '');
1295
+ const url = String(target?.url || '');
1296
+ if (targetId && url) {
1297
+ if (this.urlMatchesAnyActiveRule(url)) await this.ensureWatchedPageSession(targetId, url);
1298
+ }
1299
+ }
1300
+ } catch {}
1301
+ }
1302
+ installContentScriptTargetListener() {
1303
+ if (!this.cdp) return;
1304
+ this.cdp.onProtocolEvent((raw)=>{
1305
+ const method = String(raw.method || '');
1306
+ const params = raw.params;
1307
+ const sessionId = String(raw.sessionId || '');
1308
+ if ('Target.targetCreated' === method || 'Target.targetInfoChanged' === method) {
1309
+ const targetInfo = params?.targetInfo;
1310
+ if (!targetInfo || 'page' !== targetInfo.type) return;
1311
+ const targetId = String(targetInfo.targetId || '');
1312
+ const url = String(targetInfo.url || '');
1313
+ if (!targetId) return;
1314
+ if (!this.urlMatchesAnyActiveRule(url)) {
1315
+ for (const info of this.watchedPageSessions.values())if (info.targetId === targetId) info.url = url;
1316
+ return;
1317
+ }
1318
+ this.ensureWatchedPageSession(targetId, url).catch(()=>{});
1319
+ return;
1320
+ }
1321
+ if ('Runtime.executionContextCreated' === method) {
1322
+ if (!sessionId) return;
1323
+ const session = this.watchedPageSessions.get(sessionId);
1324
+ if (!session) return;
1325
+ const context = params?.context;
1326
+ if (!context) return;
1327
+ this.evaluateContentScriptOnNewContext(sessionId, session, context).catch(()=>{});
1328
+ return;
1329
+ }
1330
+ if ('Target.detachedFromTarget' === method) {
1331
+ if (sessionId) this.watchedPageSessions.delete(sessionId);
1332
+ }
1333
+ });
1334
+ }
1335
+ urlMatchesAnyActiveRule(url) {
1336
+ if (!url || 0 === this.activeContentScriptRules.length) return false;
1337
+ return this.activeContentScriptRules.some((rule)=>(0, content_script_targets.lM)(url, [
1338
+ rule
1339
+ ]));
1340
+ }
1341
+ async ensureWatchedPageSession(targetId, url) {
1342
+ if (!this.cdp) return;
1343
+ for (const info of this.watchedPageSessions.values())if (info.targetId === targetId) {
1344
+ info.url = url;
1345
+ return;
1346
+ }
1347
+ let sessionId;
1348
+ try {
1349
+ sessionId = await this.cdp.attachToTarget(targetId);
1350
+ } catch {
1351
+ return;
1352
+ }
1353
+ this.watchedPageSessions.set(sessionId, {
1354
+ targetId,
1355
+ url
1356
+ });
1357
+ try {
1358
+ await this.cdp.sendCommand('Runtime.enable', {}, sessionId);
1359
+ } catch {
1360
+ this.watchedPageSessions.delete(sessionId);
1361
+ }
1362
+ }
1363
+ async evaluateContentScriptOnNewContext(sessionId, session, context) {
1364
+ if (!this.cdp) return;
1365
+ const contextId = context.id;
1366
+ if ('number' != typeof contextId) return;
1367
+ if (!this.urlMatchesAnyActiveRule(session.url)) return;
1368
+ const auxData = context.auxData || {};
1369
+ const origin = String(context.origin || '');
1370
+ const extensionId = this.extensionId || await this.deriveExtensionIdFromTargets();
1371
+ const extensionOrigin = extensionId ? `chrome-extension://${extensionId}` : '';
1372
+ const matchingRules = this.activeContentScriptRules.filter((rule)=>{
1373
+ if (!(0, content_script_targets.lM)(session.url, [
1374
+ rule
1375
+ ])) return false;
1376
+ if ('main' === rule.world) return 'default' === auxData.type && true === auxData.isDefault;
1377
+ return 'isolated' === auxData.type && origin === extensionOrigin;
1378
+ });
1379
+ if (0 === matchingRules.length) return;
1380
+ for (const rule of matchingRules){
1381
+ const bundlePath = (0, content_script_targets.nG)(this.outPath, rule.index, 'js');
1382
+ if (!bundlePath || !external_fs_.existsSync(bundlePath)) continue;
1383
+ const source = this.patchReinjectSourceForInvalidatedRuntime(external_fs_.readFileSync(bundlePath, 'utf-8'));
1384
+ if (!source.trim()) continue;
1385
+ const bundleId = (0, content_script_contracts.Y)(rule.index);
1386
+ const expression = this.buildReinjectExpression(bundleId, source, true);
1387
+ try {
1388
+ await this.cdp.evaluateInContext(sessionId, expression, contextId);
1389
+ } catch {}
1390
+ }
1391
+ }
1268
1392
  async reinjectMatchingTabsViaExtensionRuntime(rules) {
1269
1393
  if (!this.cdp || 0 === rules.length) {
1270
1394
  this.lastRuntimeReinjectionReport = {
@@ -1482,7 +1606,8 @@ exports.modules = {
1482
1606
  await this.cdp.sendCommand('Target.setAutoAttach', {
1483
1607
  autoAttach: true,
1484
1608
  waitForDebuggerOnStart: false,
1485
- flatten: true
1609
+ flatten: true,
1610
+ filter: EXTENSION_AUTO_ATTACH_FILTER
1486
1611
  });
1487
1612
  } catch (error) {
1488
1613
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.warn(messages.wXK(String(error?.message || error)));
@@ -1835,6 +1960,9 @@ exports.modules = {
1835
1960
  cdp_extension_controller_define_property(this, "cdp", null);
1836
1961
  cdp_extension_controller_define_property(this, "extensionId", null);
1837
1962
  cdp_extension_controller_define_property(this, "lastRuntimeReinjectionReport", null);
1963
+ cdp_extension_controller_define_property(this, "activeContentScriptRules", []);
1964
+ cdp_extension_controller_define_property(this, "contentScriptTargetListenerInstalled", false);
1965
+ cdp_extension_controller_define_property(this, "watchedPageSessions", new Map());
1838
1966
  this.outPath = args.outPath;
1839
1967
  this.browser = args.browser;
1840
1968
  this.cdpPort = args.cdpPort;
package/dist/browsers.cjs CHANGED
@@ -227,12 +227,12 @@ var __webpack_modules__ = {
227
227
  var fs__rspack_import_0 = __webpack_require__("fs");
228
228
  var path__rspack_import_1 = __webpack_require__("path");
229
229
  var _content_script_contracts__rspack_import_2 = __webpack_require__("./browsers/browsers-lib/content-script-contracts.ts");
230
- function isCanonicalContentScriptEntryName(entryName) {
231
- return 'string' == typeof entryName && entryName.startsWith(_content_script_contracts__rspack_import_2.F);
232
- }
233
230
  function parseCanonicalContentScriptEntryIndex(entryName) {
234
- if (!isCanonicalContentScriptEntryName(entryName)) return;
235
- const suffix = entryName.slice(_content_script_contracts__rspack_import_2.F.length);
231
+ if ('string' != typeof entryName) return;
232
+ const base = entryName.replace(/\.[a-f0-9]+\.(js|css)$/i, '').replace(/\.(js|css)$/i, '');
233
+ if (!base.startsWith(_content_script_contracts__rspack_import_2.F)) return;
234
+ const suffix = base.slice(_content_script_contracts__rspack_import_2.F.length);
235
+ if (!/^\d+$/.test(suffix)) return;
236
236
  const index = Number(suffix);
237
237
  return Number.isInteger(index) && index >= 0 ? index : void 0;
238
238
  }
@@ -260,8 +260,8 @@ var __webpack_modules__ = {
260
260
  }
261
261
  function getContentScriptRulesFromManifest(manifest) {
262
262
  const contentScripts = Array.isArray(manifest?.content_scripts) ? manifest.content_scripts : [];
263
- return contentScripts.map((contentScript, index)=>({
264
- index,
263
+ return contentScripts.map((contentScript, arrayIndex)=>({
264
+ index: resolveCanonicalIndexFromEntry(contentScript) ?? arrayIndex,
265
265
  world: contentScript?.world === 'MAIN' ? 'main' : 'extension',
266
266
  matches: normalizeStringArray(contentScript?.matches),
267
267
  excludeMatches: normalizeStringArray(contentScript?.exclude_matches),
@@ -269,6 +269,26 @@ var __webpack_modules__ = {
269
269
  excludeGlobs: normalizeStringArray(contentScript?.exclude_globs)
270
270
  }));
271
271
  }
272
+ function resolveCanonicalIndexFromEntry(entry) {
273
+ const js = Array.isArray(entry?.js) ? entry.js : [];
274
+ for (const rawPath of js){
275
+ if ('string' != typeof rawPath) continue;
276
+ const parsed = parseCanonicalAssetPath(rawPath, 'js');
277
+ if (void 0 !== parsed) return parsed;
278
+ }
279
+ const css = Array.isArray(entry?.css) ? entry.css : [];
280
+ for (const rawPath of css){
281
+ if ('string' != typeof rawPath) continue;
282
+ const parsed = parseCanonicalAssetPath(rawPath, 'css');
283
+ if (void 0 !== parsed) return parsed;
284
+ }
285
+ }
286
+ function parseCanonicalAssetPath(assetPath, ext) {
287
+ const match = new RegExp(`^content_scripts\\/content-(\\d+)(?:\\.[a-f0-9]+)?\\.${ext}$`, 'i').exec(assetPath);
288
+ if (!match) return;
289
+ const index = Number(match[1]);
290
+ return Number.isInteger(index) && index >= 0 ? index : void 0;
291
+ }
272
292
  function selectContentScriptRules(rules, entryNames) {
273
293
  const selectedIndices = new Set();
274
294
  for (const entryName of entryNames){
@@ -1500,7 +1520,8 @@ var __webpack_modules__ = {
1500
1520
  '--no-pings',
1501
1521
  '--enable-features=SidePanelUpdates',
1502
1522
  '--disable-features=DisableLoadExtensionCommandLineSwitch',
1503
- '--enable-unsafe-extension-debugging'
1523
+ '--enable-unsafe-extension-debugging',
1524
+ '--silent-debugger-extension-api'
1504
1525
  ];
1505
1526
  function isPlainObject(value) {
1506
1527
  return !!value && 'object' == typeof value && !Array.isArray(value);
@@ -4942,7 +4963,8 @@ var __webpack_exports__ = {};
4942
4963
  }
4943
4964
  function resolveContentScriptRulesForReload(compilationLike, extensionsToLoad, entries) {
4944
4965
  if (!entries || 0 === entries.length) return [];
4945
- const extensionRoot = extensionsToLoad?.[0];
4966
+ const outputPath = compilationLike?.options?.output?.path;
4967
+ const extensionRoot = outputPath || extensionsToLoad?.[0];
4946
4968
  const rules = (0, _browsers_lib_content_script_targets__rspack_import_1.iw)(compilationLike, extensionRoot);
4947
4969
  return (0, _browsers_lib_content_script_targets__rspack_import_1.Il)(rules, entries);
4948
4970
  }
@@ -5011,6 +5033,9 @@ var __webpack_exports__ = {};
5011
5033
  if ('function' == typeof ctrl.reloadMatchingTabsForContentScripts) {
5012
5034
  const rules = resolveContentScriptRulesForReload(compilationLike, opts.extensionsToLoad, instruction.changedContentScriptEntries);
5013
5035
  await ctrl.reloadMatchingTabsForContentScripts(rules);
5036
+ if ('function' == typeof ctrl.registerContentScriptsForFutureNavigations) try {
5037
+ await ctrl.registerContentScriptsForFutureNavigations(rules);
5038
+ } catch {}
5014
5039
  return;
5015
5040
  }
5016
5041
  }
package/dist/cli.cjs CHANGED
@@ -228,12 +228,12 @@ var __webpack_modules__ = {
228
228
  var fs__rspack_import_0 = __webpack_require__("fs");
229
229
  var path__rspack_import_1 = __webpack_require__("path");
230
230
  var _content_script_contracts__rspack_import_2 = __webpack_require__("./browsers/browsers-lib/content-script-contracts.ts");
231
- function isCanonicalContentScriptEntryName(entryName) {
232
- return 'string' == typeof entryName && entryName.startsWith(_content_script_contracts__rspack_import_2.F);
233
- }
234
231
  function parseCanonicalContentScriptEntryIndex(entryName) {
235
- if (!isCanonicalContentScriptEntryName(entryName)) return;
236
- const suffix = entryName.slice(_content_script_contracts__rspack_import_2.F.length);
232
+ if ('string' != typeof entryName) return;
233
+ const base = entryName.replace(/\.[a-f0-9]+\.(js|css)$/i, '').replace(/\.(js|css)$/i, '');
234
+ if (!base.startsWith(_content_script_contracts__rspack_import_2.F)) return;
235
+ const suffix = base.slice(_content_script_contracts__rspack_import_2.F.length);
236
+ if (!/^\d+$/.test(suffix)) return;
237
237
  const index = Number(suffix);
238
238
  return Number.isInteger(index) && index >= 0 ? index : void 0;
239
239
  }
@@ -261,8 +261,8 @@ var __webpack_modules__ = {
261
261
  }
262
262
  function getContentScriptRulesFromManifest(manifest) {
263
263
  const contentScripts = Array.isArray(manifest?.content_scripts) ? manifest.content_scripts : [];
264
- return contentScripts.map((contentScript, index)=>({
265
- index,
264
+ return contentScripts.map((contentScript, arrayIndex)=>({
265
+ index: resolveCanonicalIndexFromEntry(contentScript) ?? arrayIndex,
266
266
  world: contentScript?.world === 'MAIN' ? 'main' : 'extension',
267
267
  matches: normalizeStringArray(contentScript?.matches),
268
268
  excludeMatches: normalizeStringArray(contentScript?.exclude_matches),
@@ -270,6 +270,26 @@ var __webpack_modules__ = {
270
270
  excludeGlobs: normalizeStringArray(contentScript?.exclude_globs)
271
271
  }));
272
272
  }
273
+ function resolveCanonicalIndexFromEntry(entry) {
274
+ const js = Array.isArray(entry?.js) ? entry.js : [];
275
+ for (const rawPath of js){
276
+ if ('string' != typeof rawPath) continue;
277
+ const parsed = parseCanonicalAssetPath(rawPath, 'js');
278
+ if (void 0 !== parsed) return parsed;
279
+ }
280
+ const css = Array.isArray(entry?.css) ? entry.css : [];
281
+ for (const rawPath of css){
282
+ if ('string' != typeof rawPath) continue;
283
+ const parsed = parseCanonicalAssetPath(rawPath, 'css');
284
+ if (void 0 !== parsed) return parsed;
285
+ }
286
+ }
287
+ function parseCanonicalAssetPath(assetPath, ext) {
288
+ const match = new RegExp(`^content_scripts\\/content-(\\d+)(?:\\.[a-f0-9]+)?\\.${ext}$`, 'i').exec(assetPath);
289
+ if (!match) return;
290
+ const index = Number(match[1]);
291
+ return Number.isInteger(index) && index >= 0 ? index : void 0;
292
+ }
273
293
  function selectContentScriptRules(rules, entryNames) {
274
294
  const selectedIndices = new Set();
275
295
  for (const entryName of entryNames){
@@ -1336,7 +1356,8 @@ var __webpack_modules__ = {
1336
1356
  }
1337
1357
  function resolveContentScriptRulesForReload(compilationLike, extensionsToLoad, entries) {
1338
1358
  if (!entries || 0 === entries.length) return [];
1339
- const extensionRoot = extensionsToLoad?.[0];
1359
+ const outputPath = compilationLike?.options?.output?.path;
1360
+ const extensionRoot = outputPath || extensionsToLoad?.[0];
1340
1361
  const rules = (0, _browsers_lib_content_script_targets__rspack_import_1.iw)(compilationLike, extensionRoot);
1341
1362
  return (0, _browsers_lib_content_script_targets__rspack_import_1.Il)(rules, entries);
1342
1363
  }
@@ -1405,6 +1426,9 @@ var __webpack_modules__ = {
1405
1426
  if ('function' == typeof ctrl.reloadMatchingTabsForContentScripts) {
1406
1427
  const rules = resolveContentScriptRulesForReload(compilationLike, opts.extensionsToLoad, instruction.changedContentScriptEntries);
1407
1428
  await ctrl.reloadMatchingTabsForContentScripts(rules);
1429
+ if ('function' == typeof ctrl.registerContentScriptsForFutureNavigations) try {
1430
+ await ctrl.registerContentScriptsForFutureNavigations(rules);
1431
+ } catch {}
1408
1432
  return;
1409
1433
  }
1410
1434
  }
@@ -1702,7 +1726,8 @@ var __webpack_modules__ = {
1702
1726
  '--no-pings',
1703
1727
  '--enable-features=SidePanelUpdates',
1704
1728
  '--disable-features=DisableLoadExtensionCommandLineSwitch',
1705
- '--enable-unsafe-extension-debugging'
1729
+ '--enable-unsafe-extension-debugging',
1730
+ '--silent-debugger-extension-api'
1706
1731
  ];
1707
1732
  function isPlainObject(value) {
1708
1733
  return !!value && 'object' == typeof value && !Array.isArray(value);
@@ -14,8 +14,11 @@ type ManifestLike = {
14
14
  exclude_matches?: string[];
15
15
  include_globs?: string[];
16
16
  exclude_globs?: string[];
17
+ js?: string[];
18
+ css?: string[];
17
19
  }>;
18
20
  };
21
+ export declare function parseCanonicalContentScriptEntryIndex(entryName: string): number | undefined;
19
22
  export declare function isContentScriptEntryName(entryName: string): boolean;
20
23
  export declare function isCanonicalContentScriptAsset(assetName: string): boolean;
21
24
  /**
@@ -46,7 +46,7 @@ export interface BrowserConfig {
46
46
  *
47
47
  * Each flag disables or modifies a specific browser feature for a more controlled development environment.
48
48
  */
49
- export type DefaultBrowserFlags = '--no-first-run' | '--disable-client-side-phishing-detection' | '--disable-component-extensions-with-background-pages' | '--disable-default-apps' | '--disable-features=InterestFeedContentSuggestions' | '--disable-features=Translate' | '--hide-scrollbars' | '--mute-audio' | '--no-default-browser-check' | '--ash-no-nudges' | '--disable-search-engine-choice-screen' | '--disable-features=MediaRoute' | '--use-mock-keychain' | '--disable-background-networking' | '--disable-breakpad' | '--disable-component-update' | '--disable-domain-reliability' | '--disable-features=AutofillServerCommunicatio' | '--disable-features=CertificateTransparencyComponentUpdate' | '--disable-sync' | '--disable-features=OptimizationHints' | '--disable-features=DialMediaRouteProvider' | '--no-pings' | '--enable-features=SidePanelUpdates' | '--enable-unsafe-extension-debugging';
49
+ export type DefaultBrowserFlags = '--no-first-run' | '--disable-client-side-phishing-detection' | '--disable-component-extensions-with-background-pages' | '--disable-default-apps' | '--disable-features=InterestFeedContentSuggestions' | '--disable-features=Translate' | '--hide-scrollbars' | '--mute-audio' | '--no-default-browser-check' | '--ash-no-nudges' | '--disable-search-engine-choice-screen' | '--disable-features=MediaRoute' | '--use-mock-keychain' | '--disable-background-networking' | '--disable-breakpad' | '--disable-component-update' | '--disable-domain-reliability' | '--disable-features=AutofillServerCommunicatio' | '--disable-features=CertificateTransparencyComponentUpdate' | '--disable-sync' | '--disable-features=OptimizationHints' | '--disable-features=DialMediaRouteProvider' | '--no-pings' | '--enable-features=SidePanelUpdates' | '--enable-unsafe-extension-debugging' | '--silent-debugger-extension-api';
50
50
  /**
51
51
  * Options for the browser plugin.
52
52
  */
@@ -1,5 +1,9 @@
1
1
  import type { Readable, Writable } from 'stream';
2
2
  import { type ExtensionRootMetaPayload } from './page';
3
+ export declare const EXTENSION_AUTO_ATTACH_FILTER: Array<{
4
+ type?: string;
5
+ exclude?: boolean;
6
+ }>;
3
7
  export declare class CDPClient {
4
8
  private port;
5
9
  private host;
@@ -17,6 +17,9 @@ export declare class CDPExtensionController {
17
17
  private cdp;
18
18
  private extensionId;
19
19
  private lastRuntimeReinjectionReport;
20
+ private activeContentScriptRules;
21
+ private contentScriptTargetListenerInstalled;
22
+ private watchedPageSessions;
20
23
  constructor(args: {
21
24
  outPath: string;
22
25
  browser: 'chrome' | 'edge' | 'chromium-based';
@@ -38,6 +41,30 @@ export declare class CDPExtensionController {
38
41
  allowCoarseCleanup?: boolean;
39
42
  sourceOverridesByBundleId?: Record<string, string>;
40
43
  }): Promise<number>;
44
+ /**
45
+ * Install a persistent CDP listener that re-injects the latest content-
46
+ * script bundle into any page that navigates to a URL matching a rule —
47
+ * including fresh tabs, full page reloads, and same-tab URL changes.
48
+ *
49
+ * Chrome binds its static `content_scripts` list to the bytes it read when
50
+ * the extension was loaded, and those cached bytes don't change when
51
+ * Rspack rewrites the hashed bundle on edit. We can't reload the extension
52
+ * without losing SW state and closing popup/sidebar. So for every matching
53
+ * navigation we wait for Chrome to create the extension's isolated world
54
+ * (which happens at `document_start` just before the cached content script
55
+ * runs) and evaluate the fresh bundle source in that exact world. The
56
+ * wrapper's `__EXTENSIONJS_DEV_REINJECT__` registry keyed on the canonical
57
+ * bundle id unmounts the stale generation before mounting the new one, so
58
+ * what the user sees is the latest build.
59
+ *
60
+ * Called on every `content-scripts` reload instruction with the latest
61
+ * rules. Safe to call repeatedly — rules are simply replaced.
62
+ */
63
+ registerContentScriptsForFutureNavigations(rules: ContentScriptTargetRule[]): Promise<void>;
64
+ private installContentScriptTargetListener;
65
+ private urlMatchesAnyActiveRule;
66
+ private ensureWatchedPageSession;
67
+ private evaluateContentScriptOnNewContext;
41
68
  reinjectMatchingTabsViaExtensionRuntime(rules: ContentScriptTargetRule[]): Promise<number>;
42
69
  private reloadPageTarget;
43
70
  hardReload(): Promise<boolean>;
package/package.json CHANGED
@@ -38,7 +38,7 @@
38
38
  "extension": "./bin/extension.cjs"
39
39
  },
40
40
  "name": "extension",
41
- "version": "3.14.2",
41
+ "version": "3.14.3",
42
42
  "description": "Create cross-browser extensions with no build configuration.",
43
43
  "homepage": "https://extension.js.org/",
44
44
  "bugs": {
@@ -100,9 +100,9 @@
100
100
  "cross-spawn": "^7.0.6",
101
101
  "edge-location": "2.2.0",
102
102
  "firefox-location2": "3.0.0",
103
- "extension-create": "3.14.2",
104
- "extension-develop": "3.14.2",
105
- "extension-install": "3.14.2",
103
+ "extension-create": "3.14.3",
104
+ "extension-develop": "3.14.3",
105
+ "extension-install": "3.14.3",
106
106
  "commander": "^14.0.3",
107
107
  "pintor": "0.3.0",
108
108
  "semver": "^7.7.3",