sourcemonkey 0.0.0-stage → 0.5.0

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 (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. package/skills/SourceMonkey-friction/references/template.md +59 -0
@@ -0,0 +1,138 @@
1
+ // Pure helpers used by the userscript viewer to turn stack-trace
2
+ // references into clickable, readable links: `parseStackRefs` finds
3
+ // them, `displayRefText` decides how they read.
4
+ //
5
+ // References look like
6
+ // sourcemonkey://<scriptId>/<file>:<line>:<col>
7
+ // https://cdn.example.com/jquery.js?v=3:97:11
8
+ // inside the Log tab's entries into clickable links that jump to
9
+ // the matching code tab.
10
+ //
11
+ // We extract any URL with a recognized scheme followed by `:L:C`.
12
+ // The caller filters by matching the URL against tabs it has open;
13
+ // refs whose URL doesn't match render as plain text. That keeps the
14
+ // parser ignorant of script identity — the same code linkifies both
15
+ // main-body refs (`sourcemonkey://<id>/<name>.user.js`) and
16
+ // `@require` refs (their real CDN URLs, query string and all).
17
+ import { decodeForDisplay } from './script-display.js';
18
+ import { ROOT } from './script-source-url.js';
19
+ import { remapStackRef } from './source-map.js';
20
+ // Find every `<scheme>://…:<line>:<col>` ref in `text` and return
21
+ // runs the caller can paint as text or anchors. Recognized schemes:
22
+ // `sourcemonkey`, `http`, `https`, `file`. The URL portion may
23
+ // contain anything except whitespace and parens (V8's named-frame
24
+ // shape is `at fn (URL:L:C)` — without excluding parens the trailing
25
+ // `)` would greedily attach to the URL).
26
+ //
27
+ // Query strings and fragments work fine — they live in the URL part
28
+ // and only the final `:digits:digits` is split off. Port numbers in
29
+ // the authority are also fine for the same reason: the path comes
30
+ // after the port, so the final `:L:C` is always the line:col.
31
+ //
32
+ // IPv6 hosts in `[…]` brackets would parse OK because the brackets
33
+ // don't contain `:digits:digits` at the end — not currently exercised
34
+ // but worth noting if it ever comes up.
35
+ export function parseStackRefs(text) {
36
+ if (!text)
37
+ return [];
38
+ // Greedy `[^\s()]+` plus `:(\d+):(\d+)` at the end — the engine
39
+ // backtracks so the FINAL `:digits:digits` becomes line:col even
40
+ // when the URL contains its own `:digit` runs (port numbers in the
41
+ // authority, etc.).
42
+ const re = /(?:sourcemonkey|https?|file):\/\/[^\s()]+:(\d+):(\d+)/g;
43
+ const runs = [];
44
+ let lastIndex = 0;
45
+ let m;
46
+ while ((m = re.exec(text)) !== null) {
47
+ if (m.index > lastIndex) {
48
+ runs.push({ kind: 'text', text: text.slice(lastIndex, m.index) });
49
+ }
50
+ // Split off the trailing `:line:col` to recover the bare URL.
51
+ const full = m[0];
52
+ const line = parseInt(m[1], 10);
53
+ const col = parseInt(m[2], 10);
54
+ const url = full.slice(0, full.length - (m[1].length + m[2].length + 2));
55
+ runs.push({ kind: 'ref', text: full, url, line, col });
56
+ lastIndex = re.lastIndex;
57
+ }
58
+ if (lastIndex < text.length) {
59
+ runs.push({ kind: 'text', text: text.slice(lastIndex) });
60
+ }
61
+ return runs;
62
+ }
63
+ // Text to paint for a ref, as opposed to the URL used to find its tab.
64
+ //
65
+ // `script-source-url.ts` percent-encodes the script's log label into
66
+ // `//# sourceURL=` — a bare `#` would start a fragment and a space or
67
+ // paren would end the run `parseStackRefs` matches — so script 4's
68
+ // `foo.user.js` arrives as `foo%23E4`. Right on the wire, unreadable in
69
+ // the log. Decoding here is the viewer's usual display-decodes /
70
+ // functional-stays-encoded split (docs/names-and-paths.md).
71
+ //
72
+ // **Only our own URLs decode**, and the reason is not that decoding
73
+ // them is unsafe elsewhere — `displayLocation` decodes any absolute
74
+ // URL and accepts the same `%2F`-reads-as-`/` ambiguity. It is that a
75
+ // stack ref is a *coordinate*, not a name: the user compares it
76
+ // against devtools, pastes it into a Sources-panel search, or matches
77
+ // it to a `@require` line, and all of those want the bytes that were
78
+ // actually in the URL. Our own synthetic URLs are the exception
79
+ // because their encoding is an artifact we introduced, not part of any
80
+ // address the user could go look up.
81
+ //
82
+ // Matched against the full `sourcemonkey://SourceMonkey` prefix rather
83
+ // than the scheme: the scheme is unregistered and a hostile script can
84
+ // put `sourcemonkey://elsewhere/…` in its own log message.
85
+ export function displayRefText(text, url) {
86
+ return url.startsWith(`${ROOT}/`) ? decodeForDisplay(text) : text;
87
+ }
88
+ // Rewrite every ref in `text` (an `Error.stack`, or a console line that
89
+ // carries one) that points into a script's concatenated entry, so it
90
+ // names the `@require` or the script at its own line. For plain-text
91
+ // readers of stacks, such as sm-dev's output; the Log tab maps refs one
92
+ // at a time as it paints them (`resolveLogRef` in userscript.ts).
93
+ //
94
+ // `layouts` are the scripts that may have logged it. A ref stays as it
95
+ // was when it matches none of their entry URLs, or an entry URL that
96
+ // scripts with different layouts share: two `foo.user.js` files run by
97
+ // sm-dev both get `.../foo/foo.user.js`, and mapping with the wrong
98
+ // one's layout would name a confidently wrong file and line.
99
+ //
100
+ // A ref on a line of our own wrap (the head, which holds the GM
101
+ // bindings, a `// @require` marker, a seam, the closing brace) has no
102
+ // original line. Left alone it would read as that line of the user's
103
+ // file, so it is renamed to the script's `__wrapper.js` instead, at
104
+ // the generated line.
105
+ export function remapStackText(text, layouts) {
106
+ if (!text.includes(`${ROOT}/`))
107
+ return text;
108
+ const byUrl = new Map();
109
+ for (const layout of layouts) {
110
+ const seen = byUrl.get(layout.entryUrl);
111
+ if (seen === undefined)
112
+ byUrl.set(layout.entryUrl, layout);
113
+ else if (seen && !sameSegments(seen, layout))
114
+ byUrl.set(layout.entryUrl, null);
115
+ }
116
+ return parseStackRefs(text).map((run) => {
117
+ if (run.kind === 'text')
118
+ return run.text;
119
+ const layout = byUrl.get(run.url);
120
+ if (!layout || layout.segments.length === 0)
121
+ return run.text;
122
+ const ref = remapStackRef(run, layout.entryUrl, layout.segments);
123
+ if (ref)
124
+ return `${ref.url}:${ref.line}:${ref.col}`;
125
+ const folder = layout.entryUrl.slice(0, layout.entryUrl.lastIndexOf('/'));
126
+ return `${folder}/${WRAPPER_FILE}:${run.line}:${run.col}`;
127
+ }).join('');
128
+ }
129
+ // The name `remapStackText` gives frames on our own wrap's lines.
130
+ export const WRAPPER_FILE = '__wrapper.js';
131
+ // Two copies of one script (sm-dev's `copy`) share a URL and a layout,
132
+ // so they don't make the URL ambiguous.
133
+ function sameSegments(a, b) {
134
+ return a.segments.length === b.segments.length && a.segments.every((s, i) => {
135
+ const t = b.segments[i];
136
+ return s.url === t.url && s.generatedLine === t.generatedLine && s.lineCount === t.lineCount;
137
+ });
138
+ }
@@ -0,0 +1 @@
1
+ export declare function sourceUsesUnsafeWindow(bodyText: string | null | undefined): boolean;
@@ -0,0 +1,229 @@
1
+ // Does a script's own source reference `unsafeWindow` without granting
2
+ // it?
3
+ //
4
+ // `@grant unsafeWindow` is how a script is *supposed* to ask for the
5
+ // page's window, and it is what gets the script the binding. Plenty of
6
+ // real scripts never declare it: `unsafeWindow` is implicitly available under Tampermonkey and
7
+ // Violentmonkey — neither keys it off a grant line, measured under TM
8
+ // 5.5.0 and VM 2.49.0 by `tests/examples/unsafe-window/ungranted.user.js`
9
+ // — so an author who only ever tested there has no reason to add one.
10
+ //
11
+ // Measured over the GreasyFork top 2000: 112 scripts use `unsafeWindow`
12
+ // with no grant for it. Without the binding, the identifier is a
13
+ // `ReferenceError`, so honoring the reference is what makes those
14
+ // scripts run at all.
15
+ //
16
+ // This is a text scan, not a parse. We have no JS parser in the
17
+ // extension (the install-time check compiles with `new Function` in a
18
+ // sandboxed page and reports only a verdict — see docs/parse-check.md),
19
+ // and the answer is needed on the storage row. So:
20
+ //
21
+ // - Comments and string / template literals are stripped first, so a
22
+ // mention in a comment or a log message gets no binding. That was
23
+ // 13 of the 126 ungranted mentions in the corpus.
24
+ // - A property key, a `#private` member and a `foo.unsafeWindow`
25
+ // access don't count. A false positive only adds an unused
26
+ // `window` alias.
27
+ // - A regex literal is NOT recognized. `/['"]/` therefore opens a
28
+ // string state that runs to the next quote, and a literal ending in
29
+ // `\/\/` reads as a line comment — either can swallow real code.
30
+ // Normally that costs a missed reference, so the script gets no
31
+ // binding. If it swallows a DECLARATION while leaving a use
32
+ // visible, the script gets a binding it shadows anyway, which is
33
+ // harmless. No corpus script hits it.
34
+ // - A script that DECLARES the name — `const unsafeWindow =
35
+ // window.wrappedJSObject || window;`, a real cross-browser idiom —
36
+ // answers false, including in a declarator list or a destructuring
37
+ // pattern. Its references read its own binding, never ours. (Our binding would be
38
+ // harmless beside it: the user program runs in a nested scope, so
39
+ // the script's declaration shadows it. See wrapCombinedEntry in
40
+ // injector.ts.) The check is scope-blind, so a declaration inside a
41
+ // function suppresses the whole script: that direction only costs
42
+ // the feature.
43
+ //
44
+ // Only the script's own body is scanned, not its `@require` bodies: the
45
+ // requires arrive on a later pipeline step (`attachFiles`). See
46
+ // TODO.md.
47
+ const IDENT = /[$\w]/;
48
+ // Strip comments and quoted text, leaving a space in their place so
49
+ // adjacent tokens can't merge into a new identifier.
50
+ //
51
+ // Written to jump between interesting positions rather than walk every
52
+ // character: this runs on every script body that mentions the name, at
53
+ // load time, beside the row's SHA-256. On the corpus's largest body
54
+ // (1.8 MB) the per-character form cost ~200 ms, more than 20x that hash.
55
+ function stripQuotedAndComments(src) {
56
+ // A fresh regex per call — `lastIndex` is state, and this function
57
+ // recurses into template substitutions.
58
+ const delim = /\/\/|\/\*|["'`]/g;
59
+ const parts = [];
60
+ const n = src.length;
61
+ let kept = 0;
62
+ for (let m = delim.exec(src); m !== null; m = delim.exec(src)) {
63
+ const from = m.index;
64
+ let after;
65
+ let replacement = ' ';
66
+ if (m[0] === '//') {
67
+ const nl = src.indexOf('\n', from);
68
+ after = nl === -1 ? n : nl;
69
+ }
70
+ else if (m[0] === '/*') {
71
+ const close = src.indexOf('*/', from + 2);
72
+ after = close === -1 ? n : close + 2;
73
+ }
74
+ else {
75
+ const quote = m[0];
76
+ // The code found inside this template's `${ ... }` substitutions.
77
+ const inner = [];
78
+ let i = from + 1;
79
+ while (i < n) {
80
+ if (src[i] === '\\') {
81
+ i += 2;
82
+ continue;
83
+ }
84
+ if (src[i] === quote) {
85
+ i += 1;
86
+ break;
87
+ }
88
+ // A substitution holds real code, so scan it as code rather
89
+ // than as text.
90
+ if (quote === '`' && src[i] === '$' && src[i + 1] === '{') {
91
+ let depth = 1;
92
+ i += 2;
93
+ const innerStart = i;
94
+ while (i < n && depth > 0) {
95
+ if (src[i] === '{')
96
+ depth += 1;
97
+ else if (src[i] === '}')
98
+ depth -= 1;
99
+ i += 1;
100
+ }
101
+ inner.push(stripQuotedAndComments(src.slice(innerStart, i - 1)));
102
+ continue;
103
+ }
104
+ i += 1;
105
+ }
106
+ after = i;
107
+ if (inner.length > 0)
108
+ replacement = ` ${inner.join(' ')} `;
109
+ }
110
+ if (from > kept)
111
+ parts.push(src.slice(kept, from));
112
+ parts.push(replacement);
113
+ kept = after;
114
+ delim.lastIndex = after;
115
+ }
116
+ if (n > kept)
117
+ parts.push(src.slice(kept, n));
118
+ return parts.join('');
119
+ }
120
+ // A free `unsafeWindow` — not `foo.unsafeWindow`, not `unsafeWindows`,
121
+ // not a member the source happens to have named that.
122
+ function hasFreeReference(code) {
123
+ let from = 0;
124
+ for (;;) {
125
+ const at = code.indexOf('unsafeWindow', from);
126
+ if (at === -1)
127
+ return false;
128
+ from = at + 'unsafeWindow'.length;
129
+ if (IDENT.test(code[from] ?? ''))
130
+ continue;
131
+ // Immediately preceded by an identifier character → the tail of a
132
+ // longer name (`myunsafeWindow`). Whitespace in between means a
133
+ // separate token, which `typeof unsafeWindow` needs to pass.
134
+ if (IDENT.test(code[at - 1] ?? ''))
135
+ continue;
136
+ // Walk back over whitespace: `foo\n .unsafeWindow` is a property,
137
+ // and `#unsafeWindow` is a private class member.
138
+ let before = at - 1;
139
+ while (before >= 0 && /\s/.test(code[before]))
140
+ before -= 1;
141
+ if (code[before] === '.' || code[before] === '#')
142
+ continue;
143
+ // Followed by `:` → a property key (`{ unsafeWindow: window }`) or a
144
+ // label, which declares a name rather than reading the page's. This
145
+ // also skips `cond ? unsafeWindow : other`, and that is the trade we
146
+ // want: a script using the name in a ternary almost always uses it
147
+ // somewhere else too, while a key is the whole mention.
148
+ let next = from;
149
+ while (next < code.length && /\s/.test(code[next]))
150
+ next += 1;
151
+ if (code[next] === ':')
152
+ continue;
153
+ return true;
154
+ }
155
+ }
156
+ // `function unsafeWindow(` / `class unsafeWindow`.
157
+ const DECLARES_AS_CALLABLE = /(?:^|[^$\w.])(?:function|class)\s+unsafeWindow(?![$\w])/;
158
+ const KEYWORD = /(?:^|[^$\w.])(?:var|let|const)(?![$\w])/g;
159
+ // How far past a `var` / `let` / `const` to keep looking for the end of
160
+ // its declarator list. A statement without a `;` (ASI) would otherwise
161
+ // run the scan into the rest of the file; over-running only ever
162
+ // over-suppresses, but there is no reason to pay for it.
163
+ const DECLARATOR_SPAN = 2000;
164
+ // Is `unsafeWindow` a NAME BEING BOUND anywhere in this source, rather
165
+ // than a value being read? A script that binds the name reads its own
166
+ // value, so it gets neither our binding nor the move to MAIN world.
167
+ //
168
+ // Walks each `var` / `let` / `const` declarator list, tracking bracket
169
+ // depth and whether the current declarator has passed its `=`:
170
+ //
171
+ // const unsafeWindow = window → bound (before any `=`)
172
+ // let uw = window, unsafeWindow=uw → bound (the `,` starts a declarator)
173
+ // const { unsafeWindow } = window → bound (pattern, before the `=`)
174
+ // const app = unsafeWindow.thing → read (after the `=`) — by far the
175
+ // commonest real use, and the one
176
+ // thing this must not claim
177
+ // const xs = [1, unsafeWindow] → read (bracketed, after the `=`)
178
+ //
179
+ // Scope-blind on purpose: a declaration inside a function suppresses the
180
+ // whole script. That direction only costs the feature.
181
+ function declaresBinding(code) {
182
+ if (DECLARES_AS_CALLABLE.test(code))
183
+ return true;
184
+ KEYWORD.lastIndex = 0;
185
+ for (let m = KEYWORD.exec(code); m !== null; m = KEYWORD.exec(code)) {
186
+ const limit = Math.min(code.length, m.index + DECLARATOR_SPAN);
187
+ let depth = 0;
188
+ let afterEquals = false;
189
+ let i = KEYWORD.lastIndex;
190
+ while (i < limit) {
191
+ const c = code[i];
192
+ if (c === '(' || c === '[' || c === '{')
193
+ depth += 1;
194
+ else if (c === ')' || c === ']' || c === '}') {
195
+ if (depth === 0)
196
+ break;
197
+ depth -= 1;
198
+ }
199
+ else if (c === ';' && depth === 0)
200
+ break;
201
+ else if (c === ',' && depth === 0)
202
+ afterEquals = false;
203
+ else if (c === '=' && code[i + 1] !== '=' && !'=!<>'.includes(code[i - 1] ?? '')) {
204
+ afterEquals = true;
205
+ }
206
+ else if (c === 'u' && code.startsWith('unsafeWindow', i)) {
207
+ const after = code[i + 'unsafeWindow'.length] ?? '';
208
+ const prev = code[i - 1] ?? '';
209
+ if (!IDENT.test(after) && !IDENT.test(prev) && prev !== '.') {
210
+ if (!afterEquals)
211
+ return true;
212
+ i += 'unsafeWindow'.length;
213
+ continue;
214
+ }
215
+ }
216
+ i += 1;
217
+ }
218
+ }
219
+ return false;
220
+ }
221
+ export function sourceUsesUnsafeWindow(bodyText) {
222
+ // The overwhelmingly common answer, without paying for the scan.
223
+ if (!bodyText || bodyText.indexOf('unsafeWindow') === -1)
224
+ return false;
225
+ const code = stripQuotedAndComments(bodyText);
226
+ if (declaresBinding(code))
227
+ return false;
228
+ return hasFreeReference(code);
229
+ }
@@ -0,0 +1,40 @@
1
+ export interface UrlMatching {
2
+ matches: string[];
3
+ includes: string[];
4
+ excludes: string[];
5
+ }
6
+ export declare function isPublicSuffix(name: string): boolean;
7
+ export declare function hostEndsInTld(pattern: string): boolean;
8
+ export declare function tldCoarseGlob(pattern: string, isMatch: boolean): string;
9
+ export declare function canonicalMatchPatterns(pattern: string): string[];
10
+ export declare function matchPattern(pattern: string, url: string): boolean;
11
+ export declare const MATCH_SCHEMES: string[];
12
+ export interface MatchPatternProblem {
13
+ pattern: string;
14
+ what: string;
15
+ fix?: string;
16
+ }
17
+ export declare function matchPatternProblem(pattern: string): MatchPatternProblem | null;
18
+ export declare function ignoredMatchMessage(problem: MatchPatternProblem): string;
19
+ export declare function unusableMatchMessage(problem: MatchPatternProblem): string;
20
+ export declare function unusableMatchGroupKey(message: string): string | null;
21
+ export declare function includePattern(pattern: string, url: string): boolean;
22
+ export declare function chromeIncludeGlob(glob: string): string;
23
+ export type PageRegex = [source: string, flags: string];
24
+ export interface PageMatcher {
25
+ include: PageRegex[];
26
+ exclude: PageRegex[];
27
+ tld?: true;
28
+ }
29
+ export declare function pageMatcher(matching: UrlMatching): PageMatcher;
30
+ export declare function urlMatchesScript(url: string, matching: UrlMatching): boolean;
31
+ export interface UrlMatchExplanation {
32
+ matches: boolean;
33
+ /** Which header line decided, e.g. `@exclude *://example.com/admin*`. */
34
+ rule: string | null;
35
+ /** Every `@match` / `@include` line that covers the URL, excluded or not. */
36
+ matchedBy: string[];
37
+ /** Every `@exclude` line that covers it. Any one keeps the script off. */
38
+ excludedBy: string[];
39
+ }
40
+ export declare function explainUrlMatch(url: string, matching: UrlMatching): UrlMatchExplanation;