doc: record the $-pattern and frame-focus corrections

The spec prescribed replaceAll's string form and the plan hard-coded it,
so the $-substitution defect originated in the design documents rather
than in execution. Both now specify the function replacer.

Also replaces the spec's frame-reporting idea with the document.hasFocus()
gate that shipped; frame provenance was only ever needed for auto-submit,
which Appendix B rejects.
This commit is contained in:
golem 2026-08-01 23:17:23 -06:00
parent 51bf4d5c01
commit 8fcc93c541
2 changed files with 33 additions and 5 deletions

@ -486,7 +486,12 @@ function applyTemplate(template, query) {
}
const tpl = typeof template === "string" ? template : "";
if (tpl.includes("{query}")) {
return tpl.replaceAll("{query}", query);
// Corrected during execution: the replacer MUST be a function. In the
// string form, $&, $`, $' and $$ are substitution patterns in the
// replacement — and the replacement is arbitrary page-selected text.
// Measured on the default {query} template: "awk print $& here" produced
// "awk print {query} here".
return tpl.replaceAll("{query}", () => query);
}
const trimmed = tpl.trim();
return trimmed === "" ? query : `${trimmed} ${query}`;
@ -704,7 +709,11 @@ async function readSelection() {
let results;
try {
results = await browser.tabs.executeScript(tabId, {
code: "window.getSelection().toString()",
// Corrected during execution: gated on document.hasFocus(). Each frame
// owns an independent Selection that is not cleared when the user
// selects elsewhere, so an unfocused (possibly cross-origin) frame's
// stale selection could otherwise win on unspecified result ordering.
code: "document.hasFocus() ? window.getSelection().toString() : ''",
allFrames: true,
matchAboutBlank: true,
});

@ -293,7 +293,14 @@ mid-2025; the root form is what OpenAI's own extension uses.
1. **Build the prompt.** Let `q = String(rawQuery ?? "").trim()`.
- If `q === ""``prompt = ""` (the template is ignored entirely).
- Else if the template contains `{query}` → replace **every** occurrence
(`replaceAll`).
(`replaceAll`). Pass a **replacer function**, not the string:
`tpl.replaceAll("{query}", () => q)`. In the string form, `$&`, `` $` ``,
`$'`, and `$$` are substitution patterns *in the replacement*, and the
replacement here is arbitrary page-selected text. Corrected during
execution after this was measured on the default `{query}` template:
selecting `awk print $& here` produced `awk print {query} here`, and
`PID is $$ in bash` produced `PID is $ in bash`. Shell and regex snippets
are a headline use case, so this fired on ordinary input for every user.
- Else if the trimmed template is empty → `prompt = q`.
- Else → `prompt = trimmedTemplate + " " + q` (template treated as a prefix;
the selection is never silently discarded).
@ -345,8 +352,20 @@ both.
Pass **both** `allFrames: true` (a selection inside an iframe is invisible
to the top frame) and `matchAboutBlank: true` (needed for `about:blank`
frames). Have the injected snippet report which frame it came from, and
take the first non-empty result.
frames), then take the first non-empty result.
Corrected during execution: gate the injected snippet on
`document.hasFocus()`
`"document.hasFocus() ? window.getSelection().toString() : ''"`. Each frame
owns an independent `Selection`, and a selection made in a frame is *not*
cleared when the user selects elsewhere, so without the gate a stale
selection in an unfocused (possibly cross-origin) frame can win, decided by
an `executeScript` result ordering that is not specified. `hasFocus()` is
true for the focused document and all its ancestors, so the top frame wins
when the user selected there and an iframe is used only when it is itself
focused. This supersedes the earlier idea of having the snippet report its
own frame — that was only ever needed to establish provenance for
auto-submit, which Appendix B rejects.
**Null-check every element of the result array.** `executeScript` does not
only throw — on parent-process `about:` pages such as `about:addons` and