From 7fbbd1822ef7604844dfe771ae786c4b3add7f1d Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 30 Oct 2020 10:34:24 -0700 Subject: [PATCH] feat(firefox): support WebSockets on Firefox (#4289) --- browsers.json | 2 +- src/server/firefox/ffPage.ts | 28 ++++++++++++++++++++++++++++ src/server/firefox/protocol.ts | 33 +++++++++++++++++++++++++++++++++ test/checkCoverage.js | 12 +----------- test/web-socket.spec.ts | 15 ++++++++++----- 5 files changed, 73 insertions(+), 17 deletions(-) diff --git a/browsers.json b/browsers.json index 00d0b177aa..3a5317f04f 100644 --- a/browsers.json +++ b/browsers.json @@ -8,7 +8,7 @@ }, { "name": "firefox", - "revision": "1197", + "revision": "1198", "download": true }, { diff --git a/src/server/firefox/ffPage.ts b/src/server/firefox/ffPage.ts index 71e96efd69..b9c1127ef2 100644 --- a/src/server/firefox/ffPage.ts +++ b/src/server/firefox/ffPage.ts @@ -84,6 +84,11 @@ export class FFPage implements PageDelegate { helper.addEventListener(this._session, 'Page.dispatchMessageFromWorker', this._onDispatchMessageFromWorker.bind(this)), helper.addEventListener(this._session, 'Page.crashed', this._onCrashed.bind(this)), helper.addEventListener(this._session, 'Page.screencastStarted', this._onScreencastStarted.bind(this)), + + helper.addEventListener(this._session, 'Page.webSocketCreated', this._onWebSocketCreated.bind(this)), + helper.addEventListener(this._session, 'Page.webSocketClosed', this._onWebSocketClosed.bind(this)), + helper.addEventListener(this._session, 'Page.webSocketFrameReceived', this._onWebSocketFrameReceived.bind(this)), + helper.addEventListener(this._session, 'Page.webSocketFrameSent', this._onWebSocketFrameSent.bind(this)), ]; this._pagePromise = new Promise(f => this._pageCallback = f); session.once(FFSessionEvents.Disconnected, () => this._page._didDisconnect()); @@ -100,6 +105,25 @@ export class FFPage implements PageDelegate { return this._pagePromise; } + _onWebSocketCreated(event: Protocol.Page.webSocketCreatedPayload) { + this._page._frameManager.onWebSocketCreated(webSocketId(event.frameId, event.wsid), event.requestURL); + this._page._frameManager.onWebSocketRequest(webSocketId(event.frameId, event.wsid)); + } + + _onWebSocketClosed(event: Protocol.Page.webSocketClosedPayload) { + if (event.error) + this._page._frameManager.webSocketError(webSocketId(event.frameId, event.wsid), event.error); + this._page._frameManager.webSocketClosed(webSocketId(event.frameId, event.wsid)); + } + + _onWebSocketFrameReceived(event: Protocol.Page.webSocketFrameReceivedPayload) { + this._page._frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data); + } + + _onWebSocketFrameSent(event: Protocol.Page.webSocketFrameSentPayload) { + this._page._frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data); + } + _onExecutionContextCreated(payload: Protocol.Runtime.executionContextCreatedPayload) { const {executionContextId, auxData} = payload; const frame = this._page._frameManager.frame(auxData ? auxData.frameId : null); @@ -500,3 +524,7 @@ export class FFPage implements PageDelegate { return result.handle; } } + +function webSocketId(frameId: string, wsid: string): string { + return `${frameId}---${wsid}`; +} diff --git a/src/server/firefox/protocol.ts b/src/server/firefox/protocol.ts index ee554c466d..7c43394320 100644 --- a/src/server/firefox/protocol.ts +++ b/src/server/firefox/protocol.ts @@ -413,6 +413,34 @@ export module Protocol { screencastId: string; file: string; } + export type webSocketCreatedPayload = { + frameId: string; + wsid: string; + requestURL: string; + } + export type webSocketOpenedPayload = { + frameId: string; + requestId: string; + wsid: string; + effectiveURL: string; + } + export type webSocketClosedPayload = { + frameId: string; + wsid: string; + error: string; + } + export type webSocketFrameSentPayload = { + frameId: string; + wsid: string; + opcode: number; + data: string; + } + export type webSocketFrameReceivedPayload = { + frameId: string; + wsid: string; + opcode: number; + data: string; + } export type closeParameters = { runBeforeUnload?: boolean; }; @@ -979,6 +1007,11 @@ export module Protocol { "Page.workerDestroyed": Page.workerDestroyedPayload; "Page.dispatchMessageFromWorker": Page.dispatchMessageFromWorkerPayload; "Page.screencastStarted": Page.screencastStartedPayload; + "Page.webSocketCreated": Page.webSocketCreatedPayload; + "Page.webSocketOpened": Page.webSocketOpenedPayload; + "Page.webSocketClosed": Page.webSocketClosedPayload; + "Page.webSocketFrameSent": Page.webSocketFrameSentPayload; + "Page.webSocketFrameReceived": Page.webSocketFrameReceivedPayload; "Runtime.executionContextCreated": Runtime.executionContextCreatedPayload; "Runtime.executionContextDestroyed": Runtime.executionContextDestroyedPayload; "Runtime.console": Runtime.consolePayload; diff --git a/test/checkCoverage.js b/test/checkCoverage.js index 06a98ba67a..8b92af4505 100644 --- a/test/checkCoverage.js +++ b/test/checkCoverage.js @@ -34,16 +34,6 @@ if (browserName !== 'chromium') { api.delete('cDPSession.detach'); } -if (browserName === 'firefox') { - // WebSockets on FF are work in progress. - api.delete('webSocket.url'); - api.delete('webSocket.emit("close")'); - api.delete('webSocket.emit("socketerror")'); - api.delete('webSocket.emit("framereceived")'); - api.delete('webSocket.emit("framesent")'); - api.delete('page.emit("websocket")'); -} - // Some permissions tests are disabled in webkit. See permissions.jest.js if (browserName === 'webkit') api.delete('browserContext.clearPermissions'); @@ -77,4 +67,4 @@ function * getCoverageFiles(dir) { else yield path.join(dir, entry.name); } -} \ No newline at end of file +} diff --git a/test/web-socket.spec.ts b/test/web-socket.spec.ts index 56c4249335..370abe375a 100644 --- a/test/web-socket.spec.ts +++ b/test/web-socket.spec.ts @@ -18,7 +18,6 @@ import { it, describe, expect } from './fixtures'; describe('web socket', (test, { browserName }) => { - test.fixme(browserName === 'firefox'); }, () => { it('should work', async ({ page, server }) => { const value = await page.evaluate(port => { @@ -47,7 +46,7 @@ describe('web socket', (test, { browserName }) => { expect(log.join(':')).toBe(`open:close`); }); - it('should emit frame events', async ({ page, server }) => { + it('should emit frame events', async ({ page, server, isFirefox }) => { let socketClosed; const socketClosePromise = new Promise(f => socketClosed = f); const log = []; @@ -63,7 +62,10 @@ describe('web socket', (test, { browserName }) => { ws.addEventListener('message', () => { ws.close(); }); }, server.PORT); await socketClosePromise; - expect(log.join(':')).toBe('open:sent:received:close'); + if (isFirefox) + expect(log.join(':')).toBe('open:received:sent:close'); + else + expect(log.join(':')).toBe('open:sent:received:close'); }); it('should emit binary frame events', async ({ page, server }) => { @@ -91,7 +93,7 @@ describe('web socket', (test, { browserName }) => { expect(sent[1][i]).toBe(i); }); - it('should emit error', async ({page, server}) => { + it('should emit error', async ({page, server, isFirefox}) => { let callback; const result = new Promise(f => callback = f); page.on('websocket', ws => ws.on('socketerror', callback)); @@ -99,6 +101,9 @@ describe('web socket', (test, { browserName }) => { new WebSocket('ws://localhost:' + port + '/bogus-ws'); }, server.PORT); const message = await result; - expect(message).toContain(': 400'); + if (isFirefox) + expect(message).toBe('CLOSE_ABNORMAL'); + else + expect(message).toContain(': 400'); }); });