pw-repl 0.6.1 → 0.7.2
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/AGENTS.md +2 -2
- package/README.md +3 -2
- package/bin/pw-repl.js +17 -7
- package/lib/background.js +16 -1
- package/lib/cli-names.js +14 -4
- package/lib/commands.js +4 -0
- package/lib/dialogs.js +18 -3
- package/lib/elements.js +68 -3
- package/lib/emulation.js +52 -9
- package/lib/help.js +108 -20
- package/lib/inspect.js +7 -4
- package/lib/modes.js +24 -2
- package/lib/record.js +484 -0
- package/lib/runner.js +8 -3
- package/lib/syntax.js +8 -1
- package/lib/tabs.js +3 -0
- package/lib/tabstate.js +2 -0
- package/lib/util.js +6 -1
- package/package.json +1 -1
- package/skill/SKILL.md +15 -9
package/lib/help.js
CHANGED
|
@@ -38,14 +38,15 @@ Common tasks:
|
|
|
38
38
|
see each step as I click watch on --live
|
|
39
39
|
watch a tab someone will open watch on --next-tab [url-part]
|
|
40
40
|
requests and console together capture on, then capture off
|
|
41
|
+
a video of the page record on, then record off
|
|
41
42
|
break the backend on purpose route <glob> <status> <json>, route <glob> abort, network off
|
|
42
43
|
change or slow an API response route <glob> patch <json>, route <glob> delay <secs>
|
|
43
44
|
slow the whole network network slow
|
|
44
45
|
clean up modes off
|
|
45
46
|
|
|
46
|
-
Modes (watch, capture, route, network off or slow, emulate) stay on until turned off;
|
|
47
|
-
the selected tab's: (watch routes:1) pw>. tab, watch, capture, route, network,
|
|
48
|
-
their own show their state and what you can run next.
|
|
47
|
+
Modes (watch, capture, record, route, network off or slow, emulate, viewport) stay on until turned off;
|
|
48
|
+
the prompt shows the selected tab's: (watch routes:1) pw>. tab, watch, capture, record, route, network,
|
|
49
|
+
emulate and modes on their own show their state and what you can run next.
|
|
49
50
|
|
|
50
51
|
Topics (help <topic>):
|
|
51
52
|
tabs open, select, close, navigate network requests, bodies, console, fakes, network off
|
|
@@ -55,7 +56,9 @@ Topics (help <topic>):
|
|
|
55
56
|
help <command> for usage and caveats (e.g. help route); help --all for everything.
|
|
56
57
|
|
|
57
58
|
Know playwright-cli? Its command names work here too (tab-list, go-back, set-color-scheme, route
|
|
58
|
-
--status=...); help playwright-cli lists them
|
|
59
|
+
--status=...); help playwright-cli lists them.
|
|
60
|
+
|
|
61
|
+
Recording video? help video says which way suits what you want.`);
|
|
59
62
|
|
|
60
63
|
const TOPICS = {
|
|
61
64
|
tabs: {
|
|
@@ -71,15 +74,15 @@ fill for selectors with spaces. mousemove and the other mouse commands act at a
|
|
|
71
74
|
},
|
|
72
75
|
inspect: {
|
|
73
76
|
intro: `Output is capped; --all right after the command, or at the end, shows everything (text --all body).`,
|
|
74
|
-
commands: ['snapshot', 'watch', 'text', 'html', 'attrs', 'listeners', 'count', 'visible', 'links', 'inputs', 'screenshot', 'viewport', 'emulate', 'wait', 'sleep'],
|
|
77
|
+
commands: ['snapshot', 'watch', 'text', 'html', 'attrs', 'listeners', 'count', 'visible', 'links', 'inputs', 'screenshot', 'highlight', 'record', 'viewport', 'emulate', 'wait', 'sleep'],
|
|
75
78
|
},
|
|
76
79
|
network: {
|
|
77
80
|
intro: `requests and console record all the time; a capture records only while it runs.`,
|
|
78
81
|
commands: ['requests', 'body', 'console', 'capture', 'route', 'network'],
|
|
79
82
|
},
|
|
80
83
|
devtools: {
|
|
81
|
-
intro: `Cookie and storage listings omit values. eval, cdp, html, screenshots and URLs can
|
|
82
|
-
sensitive data (cdp Network.getCookies shows cookie values).`,
|
|
84
|
+
intro: `Cookie and storage listings omit values. eval, cdp, html, screenshots, recordings and URLs can
|
|
85
|
+
still show sensitive data (cdp Network.getCookies shows cookie values).`,
|
|
83
86
|
commands: ['eval', 'cdp', 'cookies', 'storage'],
|
|
84
87
|
},
|
|
85
88
|
session: {
|
|
@@ -102,7 +105,7 @@ selected tab, if any: (watch network:off routes:2 capture) pw>. modes lists them
|
|
|
102
105
|
|
|
103
106
|
Times (requests, console, watch) are local, with their UTC offset: 18:16:15.721-06:00.
|
|
104
107
|
|
|
105
|
-
Dialogs are reported and never answered on their own; dialog answers one.`,
|
|
108
|
+
Dialogs are reported and never answered on their own; dialog accept or dismiss answers one, from any client, whoever opened it.`,
|
|
106
109
|
commands: ['modes', 'dialog', 'help', 'quit'],
|
|
107
110
|
},
|
|
108
111
|
};
|
|
@@ -219,7 +222,7 @@ out; --full shows it unchanged.
|
|
|
219
222
|
case), each with the named elements around it. A hit with no named element around it prints without
|
|
220
223
|
a path. A hit on text (a label) keeps the ref of the element holding it, so snapshot <that ref>
|
|
221
224
|
shows what sits beside it, such as the value next to the label. --regex <pattern> is --grep by a
|
|
222
|
-
regular expression: snapshot --regex "Total: \\d+".
|
|
225
|
+
regular expression: snapshot --regex "Total: \\d+". Refs belong to the tab whose snapshot gave them.
|
|
223
226
|
|
|
224
227
|
An element with no role or name of its own (a card that is only a div) is left out, with its ref;
|
|
225
228
|
snapshot --full keeps it.
|
|
@@ -284,6 +287,27 @@ says how many.`,
|
|
|
284
287
|
visible: { usage: 'visible <selector>', summary: 'whether the first match is visible' },
|
|
285
288
|
links: { usage: 'links [--all]', summary: 'links on the page (text and href)' },
|
|
286
289
|
inputs: { usage: 'inputs [--all]', summary: 'form controls on the page; values are omitted' },
|
|
290
|
+
highlight: {
|
|
291
|
+
usage: 'highlight [<selector> [--style=<css>] | off]',
|
|
292
|
+
summary: 'draw a box over elements, for a person or a screenshot',
|
|
293
|
+
detail: `highlight <selector> draws Playwright's highlight over every match, as playwright-cli's does: a
|
|
294
|
+
tinted box, labelled with its locator, that follows the element as the page scrolls or changes, and is
|
|
295
|
+
in screenshots. --style=<css> adds CSS to the box, quoted if it has spaces; this circles e5 in blue:
|
|
296
|
+
highlight e5 --style="outline: 3px solid blue; border-radius: 50%"
|
|
297
|
+
|
|
298
|
+
highlight on its own lists the selected tab's highlights. highlight --hide <selector> hides one, and
|
|
299
|
+
highlight --hide or highlight off hides them all. A highlight is a mode of its tab: modes shows it,
|
|
300
|
+
modes off hides it, and the REPL hides every one as it stops. Loading a new page drops them; a page that
|
|
301
|
+
changes its route without loading keeps them.
|
|
302
|
+
|
|
303
|
+
The box is drawn over the page, in an element of Playwright's own added to it (x-pw-glass); the page's
|
|
304
|
+
own elements are not changed, and clicks and typing reach them as before. Playwright always labels the
|
|
305
|
+
box, and the label can cover what is just below it. For a screenshot without one, eval can outline
|
|
306
|
+
the element itself (with border-radius: 50% for a circle). That changes the page, so put it back
|
|
307
|
+
afterwards, as it was if it had a style of its own:
|
|
308
|
+
eval document.querySelector("#rate-3").style.outline = "3px solid blue"
|
|
309
|
+
eval document.querySelector("#rate-3").style.outline = ""`,
|
|
310
|
+
},
|
|
287
311
|
screenshot: {
|
|
288
312
|
usage: 'screenshot [<ref>] [--full] [-d <secs>] [name]',
|
|
289
313
|
summary: 'save a PNG of the viewport, --full page or an element',
|
|
@@ -299,7 +323,41 @@ phone shows: a page laid out wider than the phone (no viewport meta tag) comes o
|
|
|
299
323
|
|
|
300
324
|
--delay counts down out loud first (maximum 60s), so someone can hold a hover or open a menu.`,
|
|
301
325
|
},
|
|
302
|
-
|
|
326
|
+
record: {
|
|
327
|
+
usage: 'record [on [file] [seconds] | off [file]]',
|
|
328
|
+
summary: 'record the selected tab\'s page to a video',
|
|
329
|
+
detail: `record on starts recording the selected tab, and record off stops it and saves the file: its
|
|
330
|
+
path, length, size and bytes. record on its own, or record off when nothing is recording, says whether the
|
|
331
|
+
selected tab is recording, and how your last recording ended (record off, its seconds, its tab closing
|
|
332
|
+
or the REPL stopping) with what record off would have said. tab close says what became of a recording it ended. A REPL stopped with pw-repl stop cannot be asked: stop prints what became of each recording it ended, and of one that ended by itself that record has not shown since.
|
|
333
|
+
|
|
334
|
+
record off <file> saves it under that name instead. The format is set by record on: a name that does not end as its file does, or cannot be used, is refused, and it goes on recording. --filename=<file> is file too, as playwright-cli
|
|
335
|
+
writes it.
|
|
336
|
+
|
|
337
|
+
The file's ending picks the format: record on clip.mp4 records an MP4, and record on clip.webm, or record on with no file, a WebM (there is no --mp4 or --webm). The file is absolute or relative to the folder pw-repl send runs in, quoted if it has spaces, and
|
|
338
|
+
owner-only, as a screenshot is; it never replaces a file. Without one, it is recording-<timestamp>.webm where screenshot saves. With seconds,
|
|
339
|
+
it stops by itself after them (at most 3600, which is also the default); commands run meanwhile. One recording per tab. Another client with the tab selected can stop it too, as it can any mode; both are then told whose it was.
|
|
340
|
+
|
|
341
|
+
It brings the tab to the front of its window first, as screenshot does. It records the page only,
|
|
342
|
+
from inside the browser, at the viewport's size in CSS pixels, scrollbars included, made even (an
|
|
343
|
+
odd width or height loses a pixel), a phone's too under emulate mobile: a Pixel 7's is 412 wide, less
|
|
344
|
+
sharp than the phone draws it. It has no pointer and no browser toolbars,
|
|
345
|
+
and a tab behind another, hidden or headless keeps recording. Set the size first with viewport
|
|
346
|
+
(1280x720 fills a 16:9 player); a page that changes size while recording is fitted into the first size.
|
|
347
|
+
Chrome sends a frame as the page draws, and a still page keeps its real length.
|
|
348
|
+
|
|
349
|
+
It needs ffmpeg: $PW_FFMPEG, and only it when it is set (the REPL's, not send's); otherwise Playwright's own (npx playwright-core install ffmpeg, in $PLAYWRIGHT_BROWSERS_PATH or ~/.cache/ms-playwright), then one on the PATH. Playwright's writes only WebM; an .mp4 needs a system ffmpeg, with H.264. record on fails at once, recording nothing, without an ffmpeg that can write its format. If ffmpeg exits while recording, the recording ends then, the pane says what was saved, and the next record off fails with it. ffmpeg writes in blocks, so what it has not written yet is lost: the last seconds, or at first all of it. WebM plays in browsers,
|
|
350
|
+
GitHub, GitLab and Slack; MP4 plays everywhere. It takes about 15 MB a minute.
|
|
351
|
+
|
|
352
|
+
For a video with the pointer, or of the person's screen, help video compares the other ways.`,
|
|
353
|
+
},
|
|
354
|
+
viewport: {
|
|
355
|
+
usage: 'viewport [WxH | off]',
|
|
356
|
+
summary: 'show, set or unset the viewport size',
|
|
357
|
+
detail: `viewport 1280x720 sets the selected tab's page to that size, until viewport off, modes off or the
|
|
358
|
+
REPL exits. The window is not resized. viewport on its own shows the size, and whether it was set or is
|
|
359
|
+
the window's. emulate mobile sets a phone's size instead, and each refuses while the other is on.`,
|
|
360
|
+
},
|
|
303
361
|
emulate: {
|
|
304
362
|
usage: 'emulate [<what> [off] | off]',
|
|
305
363
|
summary: 'emulate a phone, dark mode, a locale or a timezone',
|
|
@@ -312,10 +370,12 @@ emulate locale <tag> the language (Accept-Language, navigator.language) and
|
|
|
312
370
|
emulate timezone <zone> an IANA timezone, e.g. Asia/Tokyo
|
|
313
371
|
emulate <what> off stop one (dark or light off stops the color scheme); emulate off stops all
|
|
314
372
|
|
|
315
|
-
Per tab, until turned off or the REPL exits
|
|
373
|
+
Per tab, until turned off or the REPL exits; emulate mobile <device> while another is on switches to it.
|
|
374
|
+
emulate on its own shows what is on.
|
|
316
375
|
|
|
317
376
|
The page sees the user agent, touch and navigator.languages from its next load: reload after
|
|
318
|
-
turning mobile or locale on or off.
|
|
377
|
+
turning mobile or locale on or off. Touch means the page sees a touch screen; click still clicks
|
|
378
|
+
with the mouse, and sends no touch events. Dark, light and timezone apply at once, but what the page
|
|
319
379
|
already drew with them (times, colors set by its script) changes only when it reloads.
|
|
320
380
|
|
|
321
381
|
While mobile is on, viewport refuses: the device sets the size.`,
|
|
@@ -453,28 +513,31 @@ already happened (requests lists those).`,
|
|
|
453
513
|
summary: 'send one CDP command through a temporary session',
|
|
454
514
|
detail: `The session is detached afterwards, so subscriptions and settings do not persist.
|
|
455
515
|
|
|
456
|
-
Browser.close and Target.closeTarget are refused.`,
|
|
516
|
+
Browser.close and Target.closeTarget are refused, and so is Page.handleJavaScriptDialog: dialog answers one.`,
|
|
457
517
|
},
|
|
458
518
|
cookies: { usage: 'cookies [--all]', summary: 'cookie names, domains and flags; values are omitted' },
|
|
459
519
|
storage: { usage: 'storage [--all]', summary: 'localStorage keys; values are omitted' },
|
|
460
520
|
modes: {
|
|
461
521
|
usage: 'modes [off [--mine]]',
|
|
462
522
|
summary: 'the modes on in every tab; modes off turns them all off',
|
|
463
|
-
detail: `The modes are watch, network off or slow, route, capture
|
|
464
|
-
its own command: watch on|off, network off|slow|on, route <glob> ... |
|
|
465
|
-
capture on|off, emulate ... | emulate off
|
|
523
|
+
detail: `The modes are watch, network off or slow, route, capture, record, emulate, viewport and highlight.
|
|
524
|
+
Each is turned on and off with its own command: watch on|off, network off|slow|on, route <glob> ... |
|
|
525
|
+
route off <glob>|--all, capture on|off, record on|off, emulate ... | emulate off, viewport <WxH> |
|
|
526
|
+
viewport off, highlight <selector> | highlight off.
|
|
466
527
|
|
|
467
|
-
modes off turns off every one in every tab; a capture it stops is kept for capture to show
|
|
528
|
+
modes off turns off every one in every tab; a capture it stops is kept for capture to show, and a
|
|
529
|
+
recording it stops is saved, as record off would. With
|
|
468
530
|
clients (help session), modes says which client turned each on, and modes off --mine turns off only
|
|
469
531
|
the sender's own.`,
|
|
470
532
|
},
|
|
471
533
|
dialog: {
|
|
472
534
|
usage: 'dialog [accept [text] | dismiss]',
|
|
473
535
|
summary: 'show, accept or dismiss an open alert, confirm or prompt',
|
|
474
|
-
detail: `While a dialog is open, its page and the commands that read it wait. dialog runs at once, ahead
|
|
475
|
-
of them; it answers the selected tab's dialog, or the only one open. accept text answers a prompt.
|
|
536
|
+
detail: `While a dialog is open, its page and the commands that read it wait, a click that opened it included, until it is answered. Commands run one at a time, so every client's commands behind that one wait too, in any tab: in a shared REPL, answer a dialog you open. dialog runs at once, ahead
|
|
537
|
+
of them; it answers the selected tab's dialog (marked *), or the only one open. accept text answers a prompt, and accept alone keeps its default, as OK in the browser does.
|
|
476
538
|
|
|
477
|
-
|
|
539
|
+
An agent can answer any dialog, one it opened included; one the person answers in the browser is gone
|
|
540
|
+
from dialog then. cdp cannot answer one: Chrome lets only a session that saw it open do so. With nobody at the browser (e.g. headless), it is the only way to answer one.`,
|
|
478
541
|
},
|
|
479
542
|
help: { usage: 'help [topic | command | --all]', summary: 'this help; --all prints every topic and command in full' },
|
|
480
543
|
quit: {
|
|
@@ -510,6 +573,22 @@ function renderAll() {
|
|
|
510
573
|
return [OVERVIEW, ...sections].join('\n\n');
|
|
511
574
|
}
|
|
512
575
|
|
|
576
|
+
// Not a command: the ways to record a video, since none is right for every use.
|
|
577
|
+
const VIDEO = `There are three ways to record a video of a page, and each gives something up.
|
|
578
|
+
|
|
579
|
+
record on (help record) records the selected tab's page from inside the browser: headless, hidden and
|
|
580
|
+
background tabs too, with nothing to set up but ffmpeg. It has no pointer and no browser toolbars. For
|
|
581
|
+
quick clips, and for an agent recording its own work.
|
|
582
|
+
|
|
583
|
+
A screen recorder on the person's machine (OBS, ffmpeg -f x11grab, macOS screencapture -v) records their
|
|
584
|
+
screen as they see it, pointer included, smooth and in real time. It records the screen or a window, not
|
|
585
|
+
a tab: size the window, and crop off the toolbars, for just the page. Ask before starting one, or have
|
|
586
|
+
them start it. It cannot see a headless browser: for a browser of the REPL's own, --launch --headed on
|
|
587
|
+
a screen. For a polished video of a person's browser.
|
|
588
|
+
|
|
589
|
+
Microsoft's playwright-cli (the npm package @playwright/cli; video-start and video-stop) records a
|
|
590
|
+
browser of its own, not this one, which starts logged out. For a scripted flow in a clean browser.`;
|
|
591
|
+
|
|
513
592
|
function render(topicOrCommand) {
|
|
514
593
|
if (!topicOrCommand) return OVERVIEW;
|
|
515
594
|
if (topicOrCommand === 'playwright-cli') {
|
|
@@ -530,6 +609,7 @@ Different on purpose, since the browser may be someone else's too:
|
|
|
530
609
|
no snapshot after each command a person reads the pane too; snapshot when you need one
|
|
531
610
|
-s=<session> -c <name> is the nearest: a client with a selected tab of its own`);
|
|
532
611
|
}
|
|
612
|
+
if (topicOrCommand === 'video') return wrap(VIDEO);
|
|
533
613
|
if (topicOrCommand === '--all') return renderAll();
|
|
534
614
|
if (Object.hasOwn(TOPICS, topicOrCommand)) {
|
|
535
615
|
const topic = TOPICS[topicOrCommand];
|
|
@@ -538,6 +618,14 @@ Different on purpose, since the browser may be someone else's too:
|
|
|
538
618
|
return Object.hasOwn(COMMANDS, topicOrCommand) ? `${text}\n\n${renderCommand(topicOrCommand)}` : text;
|
|
539
619
|
}
|
|
540
620
|
if (Object.hasOwn(COMMANDS, topicOrCommand)) return renderCommand(topicOrCommand);
|
|
621
|
+
// A playwright-cli name: what it runs here, with that command's help.
|
|
622
|
+
const cli = require('./cli-names').describe(topicOrCommand);
|
|
623
|
+
if (cli?.nearest) return wrap(`${topicOrCommand} is playwright-cli's; ${cli.nearest}`);
|
|
624
|
+
if (cli) {
|
|
625
|
+
const here = cli.as.split(/\s/)[0];
|
|
626
|
+
const intro = wrap(`${topicOrCommand} is playwright-cli's name for ${cli.as} here.`);
|
|
627
|
+
return Object.hasOwn(COMMANDS, here) ? `${intro}\n\n${renderCommand(here)}` : intro;
|
|
628
|
+
}
|
|
541
629
|
return null;
|
|
542
630
|
}
|
|
543
631
|
|
package/lib/inspect.js
CHANGED
|
@@ -9,9 +9,10 @@ const { toSelector, REF, unquote } = require('./syntax');
|
|
|
9
9
|
const { regexOf, COMMAND_TIMEOUT } = require('./util');
|
|
10
10
|
const { tabState } = require('./tabstate');
|
|
11
11
|
const { keptSession } = require('./cdp');
|
|
12
|
+
const { CDP_REFUSED } = require('./dialogs');
|
|
12
13
|
const { onElement, soleSelector } = require('./elements');
|
|
13
14
|
const { waitForLoad, waitForRequest } = require('./requestlog');
|
|
14
|
-
const { deviceMetrics } = require('./emulation');
|
|
15
|
+
const { deviceMetrics, viewportMetrics } = require('./emulation');
|
|
15
16
|
|
|
16
17
|
const SCREENSHOT_DELAY_MAX = 60;
|
|
17
18
|
|
|
@@ -283,11 +284,12 @@ const commands = {
|
|
|
283
284
|
}
|
|
284
285
|
} finally {
|
|
285
286
|
// Playwright's screenshot resets the screen size and pixel ratio it
|
|
286
|
-
// finds; an emulated phone's are set again.
|
|
287
|
-
|
|
287
|
+
// finds; an emulated phone's, or a viewport's, are set again.
|
|
288
|
+
const viewport = tabState(state.page).viewport;
|
|
289
|
+
if (device || viewport) {
|
|
288
290
|
const session = await keptSession(state.page);
|
|
289
291
|
await session.send('Emulation.clearDeviceMetricsOverride');
|
|
290
|
-
await deviceMetrics(session, device);
|
|
292
|
+
await (device ? deviceMetrics(session, device) : viewportMetrics(session, viewport));
|
|
291
293
|
}
|
|
292
294
|
}
|
|
293
295
|
try {
|
|
@@ -428,6 +430,7 @@ const commands = {
|
|
|
428
430
|
const match = /^(\S+)\s+([\s\S]+)$/.exec(args || '');
|
|
429
431
|
if (!match) throw new Error('Usage: cdp <method> <JSON object>');
|
|
430
432
|
if (['Browser.close', 'Target.closeTarget'].includes(match[1])) throw new Error('Browser lifecycle commands are reserved; use quit or tab close.');
|
|
433
|
+
if (match[1] === 'Page.handleJavaScriptDialog') throw new Error(CDP_REFUSED);
|
|
431
434
|
const params = JSON.parse(match[2]);
|
|
432
435
|
if (!params || Array.isArray(params) || typeof params !== 'object') throw new Error('Parameters must be a JSON object');
|
|
433
436
|
const operation = async () => {
|
package/lib/modes.js
CHANGED
|
@@ -5,9 +5,11 @@ const { hints } = require('./util');
|
|
|
5
5
|
const { tabState } = require('./tabstate');
|
|
6
6
|
const { unroute } = require('./routes');
|
|
7
7
|
const { setNetwork } = require('./network');
|
|
8
|
-
const { emulatedKinds, setEmulation } = require('./emulation');
|
|
8
|
+
const { emulatedKinds, setEmulation, setViewport } = require('./emulation');
|
|
9
|
+
const { recordingOf, finishRecording } = require('./record');
|
|
9
10
|
const { currentCapture, endCapture } = require('./capture');
|
|
10
11
|
const { nextTabWanted, waitingText, stopWaitingForNextTab, stopWatching } = require('./watch');
|
|
12
|
+
const { hideHighlights } = require('./elements');
|
|
11
13
|
|
|
12
14
|
// The modes turned on in a tab, in the order the prompt shows them.
|
|
13
15
|
function activeModes(p) {
|
|
@@ -17,9 +19,14 @@ function activeModes(p) {
|
|
|
17
19
|
if (network) modes.push(network.offline ? 'network:off' : 'network:slow');
|
|
18
20
|
const emulated = emulatedKinds(tabState(p).emulation);
|
|
19
21
|
if (emulated.length) modes.push(`emulate:${emulated.join(',')}`);
|
|
22
|
+
const viewport = tabState(p).viewport;
|
|
23
|
+
if (viewport) modes.push(`viewport:${viewport.width}x${viewport.height}`);
|
|
20
24
|
const routes = tabState(p).routes?.size;
|
|
21
25
|
if (routes) modes.push(`routes:${routes}`);
|
|
26
|
+
const highlights = tabState(p).highlights?.size;
|
|
27
|
+
if (highlights) modes.push(`highlight:${highlights}`);
|
|
22
28
|
if (currentCapture()?.page === p) modes.push('capture');
|
|
29
|
+
if (recordingOf(p)) modes.push('record');
|
|
23
30
|
return modes;
|
|
24
31
|
}
|
|
25
32
|
|
|
@@ -47,7 +54,11 @@ function listModes() {
|
|
|
47
54
|
const owners = modeOwners(p);
|
|
48
55
|
if (owners.some(([owner]) => owner)) out.log(` on by ${ownersText(owners)}`);
|
|
49
56
|
}
|
|
50
|
-
|
|
57
|
+
// With clients' modes among them, the sender's own first: modes off turns off everyone's.
|
|
58
|
+
const clients = on.some(([p]) => modeOwners(p).some(([owner]) => owner));
|
|
59
|
+
out.log(hints(clients && state.client
|
|
60
|
+
? [['modes off --mine', 'turn off the ones you turned on'], ['modes off', 'turn them all off, everyone\'s']]
|
|
61
|
+
: [['modes off', 'turn them all off']]));
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
// Who turned on each mode in a tab: [client, what], client null for the prompt or an unnamed sender.
|
|
@@ -59,9 +70,14 @@ function modeOwners(p) {
|
|
|
59
70
|
if (network) owners.push([network.owner ?? null, network.offline ? 'network:off' : 'network:slow']);
|
|
60
71
|
const emulated = tabState(p).emulation;
|
|
61
72
|
if (emulated) owners.push([emulated.owner ?? null, 'emulate']);
|
|
73
|
+
const viewport = tabState(p).viewport;
|
|
74
|
+
if (viewport) owners.push([viewport.owner ?? null, 'viewport']);
|
|
62
75
|
for (const [glob, route] of tabState(p).routes || []) owners.push([route.owner ?? null, `route ${glob}`]);
|
|
76
|
+
for (const [selector, h] of tabState(p).highlights || []) owners.push([h.owner ?? null, `highlight ${selector}`]);
|
|
63
77
|
const capture = currentCapture();
|
|
64
78
|
if (capture?.page === p) owners.push([capture.owner ?? null, 'capture']);
|
|
79
|
+
const recording = recordingOf(p);
|
|
80
|
+
if (recording) owners.push([recording.owner ?? null, 'record']);
|
|
65
81
|
return owners;
|
|
66
82
|
}
|
|
67
83
|
|
|
@@ -92,7 +108,13 @@ async function allModesOff(mine = false) {
|
|
|
92
108
|
const globs = [...(tabState(p).routes || [])].filter(([, route]) => ours(route.owner)).map(([glob]) => glob);
|
|
93
109
|
if (globs.length) { await unroute(p, globs); done.push(`${globs.length} route${globs.length === 1 ? '' : 's'} removed`); }
|
|
94
110
|
if ('network' in tabState(p) && ours(tabState(p).network.owner)) { await setNetwork(p, null); done.push('network on'); }
|
|
111
|
+
const hidden = await hideHighlights(p, mine);
|
|
112
|
+
if (hidden) done.push(`${hidden} highlight${hidden === 1 ? '' : 's'} hidden`);
|
|
113
|
+
// Before the page's size is reset, which the recording would otherwise end with.
|
|
114
|
+
const recording = recordingOf(p);
|
|
115
|
+
if (recording && ours(recording.owner)) done.push(`record off (${(await finishRecording(recording)).replace(/\n/g, '; ')})`);
|
|
95
116
|
if ('emulation' in tabState(p) && ours(tabState(p).emulation.owner)) { await setEmulation(p, {}); done.push('emulate off'); }
|
|
117
|
+
if ('viewport' in tabState(p) && ours(tabState(p).viewport.owner)) { await setViewport(p, null); done.push('viewport off'); }
|
|
96
118
|
} catch (error) {
|
|
97
119
|
problem = error.message;
|
|
98
120
|
}
|