( command: string, args: string[], session: TabSession, bm: BrowserManager )
| 130 | }; |
| 131 | |
| 132 | export async function handleWriteCommand( |
| 133 | command: string, |
| 134 | args: string[], |
| 135 | session: TabSession, |
| 136 | bm: BrowserManager |
| 137 | ): Promise<string> { |
| 138 | const page = session.getPage(); |
| 139 | // Frame-aware target for locator-based operations (click, fill, etc.) |
| 140 | const target = session.getActiveFrameOrPage(); |
| 141 | const inFrame = session.getFrame() !== null; |
| 142 | |
| 143 | switch (command) { |
| 144 | case 'goto': { |
| 145 | if (inFrame) throw new Error('Cannot use goto inside a frame. Run \'frame main\' first.'); |
| 146 | const url = args[0]; |
| 147 | if (!url) throw new Error('Usage: browse goto <url>'); |
| 148 | // Clear loadedHtml BEFORE navigation — a timeout after the main-frame commit |
| 149 | // must not leave stale content that could resurrect on a later context recreation. |
| 150 | session.clearLoadedHtml(); |
| 151 | const normalizedUrl = await validateNavigationUrl(url); |
| 152 | const response = await page.goto(normalizedUrl, { waitUntil: 'domcontentloaded', timeout: 15000 }); |
| 153 | const status = response?.status() || 'unknown'; |
| 154 | return `Navigated to ${normalizedUrl} (${status})`; |
| 155 | } |
| 156 | |
| 157 | case 'back': { |
| 158 | if (inFrame) throw new Error('Cannot use back inside a frame. Run \'frame main\' first.'); |
| 159 | session.clearLoadedHtml(); |
| 160 | await page.goBack({ waitUntil: 'domcontentloaded', timeout: 15000 }); |
| 161 | return `Back → ${page.url()}`; |
| 162 | } |
| 163 | |
| 164 | case 'forward': { |
| 165 | if (inFrame) throw new Error('Cannot use forward inside a frame. Run \'frame main\' first.'); |
| 166 | session.clearLoadedHtml(); |
| 167 | await page.goForward({ waitUntil: 'domcontentloaded', timeout: 15000 }); |
| 168 | return `Forward → ${page.url()}`; |
| 169 | } |
| 170 | |
| 171 | case 'reload': { |
| 172 | if (inFrame) throw new Error('Cannot use reload inside a frame. Run \'frame main\' first.'); |
| 173 | session.clearLoadedHtml(); |
| 174 | await page.reload({ waitUntil: 'domcontentloaded', timeout: 15000 }); |
| 175 | return `Reloaded ${page.url()}`; |
| 176 | } |
| 177 | |
| 178 | case 'load-html': { |
| 179 | if (inFrame) throw new Error('Cannot use load-html inside a frame. Run \'frame main\' first.'); |
| 180 | |
| 181 | // --from-file <path.json>: read inline HTML from a JSON payload. Used by |
| 182 | // make-pdf to dodge Windows argv size limits on large rendered HTML. |
| 183 | // The JSON shape is { html: string, waitUntil?: "load"|"domcontentloaded"|"networkidle" }. |
| 184 | // The safe-dirs + magic-byte + size-cap checks below still apply to the |
| 185 | // INLINE HTML content, not to the payload file path itself. |
| 186 | let fromFilePayload: { html: string; waitUntil?: SetContentWaitUntil } | null = null; |
| 187 | let filePath: string | undefined; |
| 188 | let waitUntil: SetContentWaitUntil = 'domcontentloaded'; |
| 189 | for (let i = 0; i < args.length; i++) { |
no test coverage detected