fix: cap huge selections before shortening
The shortcut and toolbar read window.getSelection() without a limit, so a select-all on a very large page handed prepareAction millions of characters. Every binary-search probe re-encoded the whole candidate prefix: about 1.1 s for 5M characters, paid again by the dialog. Each code point adds at least one character to the URL, so nothing past the first MAX_URL_CHARS can fit. Cap there before any encoding. A capped source never takes the ready path: renderPrompt trims, so a prefix of mostly whitespace could fit and silently drop the rest without consent. Output is unchanged: 2,000 random cases matched develop exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { DEFAULT_SETTINGS } from '../lib/defaults.js';
|
||||
import { MAX_URL_CHARS } from '../lib/build-url.js';
|
||||
const { prepareAction } = await import('../lib/prepare-action.js').catch(()=>({}));
|
||||
const req = (overrides={}) => ({actionId:'ask',source:'Hello',settings:{...DEFAULT_SETTINGS},...overrides});
|
||||
test('ready actions preserve source and hints, translation asks only when needed', () => {
|
||||
@@ -38,3 +39,22 @@ test('long selections preserve instructions, repetition and Unicode with explici
|
||||
const translated=prepareAction(req({actionId:'translate',language:'Spanish',source:'x'.repeat(20000)}));
|
||||
assert.equal(translated.kind,'shortened');assert.match(new URL(translated.url).searchParams.get('q'), /Spanish/);
|
||||
});
|
||||
test('huge selections are capped before shortening, with identical output', () => {
|
||||
assert.equal(typeof prepareAction, 'function');
|
||||
// Select-all on a very large page; shortening this took over a second uncapped.
|
||||
const source='😀 word '.repeat(625000);
|
||||
const started=performance.now();
|
||||
const out=prepareAction(req({source}));
|
||||
const elapsed=performance.now()-started;
|
||||
assert.equal(out.kind,'shortened');
|
||||
assert.ok(elapsed<250,`took ${elapsed.toFixed(0)} ms`);
|
||||
const capped=Array.from(source).slice(0,MAX_URL_CHARS).join('');
|
||||
assert.deepEqual(prepareAction(req({source:capped})),out);
|
||||
});
|
||||
test('a selection too long to send whole always needs consent, even if its start would fit', () => {
|
||||
assert.equal(typeof prepareAction, 'function');
|
||||
// The first MAX_URL_CHARS code points trim down to "a"; sending just that would drop the rest unasked.
|
||||
const out=prepareAction(req({source:'a'+' '.repeat(MAX_URL_CHARS)+'b'}));
|
||||
assert.equal(out.kind,'shortened');
|
||||
assert.ok(new URL(out.url).searchParams.get('q').includes('[Selected text shortened]'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user