| 154 | // never executes — and the property at stake here is what the browser is left |
| 155 | // holding, which only running it can show. |
| 156 | const runPopupScript = (html: string) => { |
| 157 | const script = /<script>\n?([\s\S]*?)<\/script>/.exec(html)?.[1]; |
| 158 | expect(script).toBeDefined(); |
| 159 | const store = new Map<string, string>(); |
| 160 | const timers: { readonly fn: () => void; readonly ms: number }[] = []; |
| 161 | const pagehideListeners: (() => void)[] = []; |
| 162 | let closed = false; |
| 163 | const win = { |
| 164 | opener: null, |
| 165 | location: { origin: "https://app.example" }, |
| 166 | close: () => { |
| 167 | closed = true; |
| 168 | }, |
| 169 | addEventListener: (type: string, listener: () => void) => { |
| 170 | if (type === "pagehide") pagehideListeners.push(listener); |
| 171 | }, |
| 172 | }; |
| 173 | const fn = new Function( |
| 174 | "window", |
| 175 | "localStorage", |
| 176 | "setTimeout", |
| 177 | "BroadcastChannel", |
| 178 | script ?? "", |
| 179 | ) as (w: unknown, ls: unknown, st: unknown, bc: unknown) => void; |
| 180 | fn( |
| 181 | win, |
| 182 | { |
| 183 | setItem: (k: string, v: string) => store.set(k, v), |
| 184 | removeItem: (k: string) => store.delete(k), |
| 185 | }, |
| 186 | (cb: () => void, ms: number) => { |
| 187 | timers.push({ fn: cb, ms }); |
| 188 | return timers.length; |
| 189 | }, |
| 190 | undefined, |
| 191 | ); |
| 192 | return { |
| 193 | store, |
| 194 | isClosed: () => closed, |
| 195 | runTimers: () => { |
| 196 | for (const t of [...timers]) t.fn(); |
| 197 | }, |
| 198 | // Simulates the document dying (user closes the window): pagehide fires, |
| 199 | // pending timers never do. |
| 200 | firePagehide: () => { |
| 201 | for (const listener of [...pagehideListeners]) listener(); |
| 202 | }, |
| 203 | }; |
| 204 | }; |
| 205 | |
| 206 | it("clears the stored result after handing it over on success", () => { |
| 207 | const html = popupDocument(successPayload, "chan-1"); |