Skip to content
4 changes: 2 additions & 2 deletions packages/playwright-core/browsers.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
},
{
"name": "firefox",
"revision": "1525",
"revision": "1528",
"installByDefault": true,
"browserVersion": "151.0",
"title": "Firefox"
},
{
"name": "firefox-beta",
"revision": "1520",
"revision": "1521",
"installByDefault": false,
"browserVersion": "152.0b1",
"title": "Firefox Beta"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ export class CRNetworkManager {
if (this._page) {
sessionInfo.eventListeners.push(...[
eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page!.frameManager.onWebSocketCreated(e.requestId, e.url)),
eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!.frameManager.onWebSocketRequest(e.requestId)),
eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page!.frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)),
eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page!.frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)),
eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page!.frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)),
eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers, '\n'), e.wallTime, e.timestamp)),
eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page!.frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText, headersObjectToArray(e.response.headers, '\n'))),
eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page!.frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)),
eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page!.frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)),
eventsHelper.addEventListener(session, 'Network.webSocketClosed', e => this._page!.frameManager.webSocketClosed(e.requestId)),
eventsHelper.addEventListener(session, 'Network.webSocketFrameError', e => this._page!.frameManager.webSocketError(e.requestId, e.errorMessage)),
]);
Expand Down
37 changes: 27 additions & 10 deletions packages/playwright-core/src/server/firefox/ffNetworkManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,21 @@ import { eventsHelper } from '@utils/eventsHelper';
import * as network from '../network';

import type { FFSession } from './ffConnection';
import type { FFPage } from './ffPage';
import type { HeadersArray } from '../../server/types';
import type { RegisteredListener } from '@utils/eventsHelper';
import type * as frames from '../frames';
import type { Page } from '../page';
import type * as types from '../types';
import type { Protocol } from './protocol';

export class FFNetworkManager {
private _session: FFSession;
private _requests: Map<string, InterceptableRequest>;
private _page: Page;
private _page: FFPage;
private _eventListeners: RegisteredListener[];
private _webSocketRequestIds = new Set<string>();

constructor(session: FFSession, page: Page) {
constructor(session: FFSession, page: FFPage) {
this._session = session;

this._requests = new Map();
Expand All @@ -59,26 +60,36 @@ export class FFNetworkManager {

_onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
const redirectedFrom = event.redirectedFrom ? (this._requests.get(event.redirectedFrom) || null) : null;
const frame = redirectedFrom ? redirectedFrom.request.frame() : (event.frameId ? this._page.frameManager.frame(event.frameId) : null);
const frame = redirectedFrom ? redirectedFrom.request.frame() : (event.frameId ? this._page._page.frameManager.frame(event.frameId) : null);
if (!frame)
return;
// Align with Chromium and WebKit and not expose preflight OPTIONS requests to the client.
if (event.method === 'OPTIONS' && !event.isIntercepted)
return;
if (redirectedFrom)
this._requests.delete(redirectedFrom._id);
// Align with Chromium and WebKit by having WebSocket be handled separately from other network activity.
if (event.cause === 'TYPE_WEBSOCKET') {
this._webSocketRequestIds.add(event.requestId);
this._page._onWebSocketRequestWillBeSent(event.requestId, event.url, event.headers);
return;
}
const request = new InterceptableRequest(frame, redirectedFrom, event);
let route;
if (event.isIntercepted)
route = new FFRouteImpl(this._session, request);
this._requests.set(request._id, request);
this._page.frameManager.requestStarted(request.request, route);
this._page._page.frameManager.requestStarted(request.request, route);
}

_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
const request = this._requests.get(event.requestId);
if (!request)
if (!request) {
// Align with Chromium and WebKit by having WebSocket be handled separately from other network activity.
if (this._webSocketRequestIds.has(event.requestId))
this._page._onWebSocketResponseReceived(event.requestId, event.status, event.statusText, event.headers);
return;
}
const getResponseBody = async () => {
const response = await this._session.send('Network.getResponseBody', {
requestId: request._id
Expand Down Expand Up @@ -124,13 +135,19 @@ export class FFNetworkManager {
response.setRawResponseHeaders(null);
// Headers size are not available in Firefox.
response.setResponseHeadersSize(null);
this._page.frameManager.requestReceivedResponse(response);
this._page._page.frameManager.requestReceivedResponse(response);
}

_onRequestFinished(event: Protocol.Network.requestFinishedPayload) {
const request = this._requests.get(event.requestId);
if (!request)
if (!request) {
// Align with Chromium and WebKit by having WebSocket be handled separately from other network activity.
if (this._webSocketRequestIds.has(event.requestId)) {
this._webSocketRequestIds.delete(event.requestId);
this._page._onWebSocketRequestFinished(event.requestId);
}
return;
}
const response = request.request._existingResponse()!;
response.setTransferSize(event.transferSize);
response.setEncodedBodySize(event.encodedBodySize);
Expand All @@ -145,7 +162,7 @@ export class FFNetworkManager {
response._requestFinished(responseEndTime);
}
response._setHttpVersion(event.protocolVersion ?? null);
this._page.frameManager.reportRequestFinished(request.request, response);
this._page._page.frameManager.reportRequestFinished(request.request, response);
}

_onRequestFailed(event: Protocol.Network.requestFailedPayload) {
Expand All @@ -161,7 +178,7 @@ export class FFNetworkManager {
response._setHttpVersion(null);
}
request.request._setFailureText(event.errorCode);
this._page.frameManager.requestFailed(request.request, event.errorCode === 'NS_BINDING_ABORTED');
this._page._page.frameManager.requestFailed(request.request, event.errorCode === 'NS_BINDING_ABORTED');
}
}

Expand Down
57 changes: 53 additions & 4 deletions packages/playwright-core/src/server/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
* limitations under the License.
*/

import { assert } from '@isomorphic/assert';
import { splitErrorMessage } from '@isomorphic/stackTrace';
import { eventsHelper } from '@utils/eventsHelper';
import * as dialog from '../dialog';
import * as dom from '../dom';
import * as network from '../network';
import { InitScript } from '../page';
import { Page, Worker } from '../page';
import { FFSession } from './ffConnection';
Expand Down Expand Up @@ -53,6 +55,8 @@ export class FFPage implements PageDelegate {
private _eventListeners: RegisteredListener[];
private _workers = new Map<string, { frameId: string, session: FFSession }>();
private _initScripts: { initScript: InitScript, worldName?: string }[] = [];
private _webSocketRequests = new Map<string, { url: string, headers: types.HeadersArray }>();
private _webSocketResponses = new Map<string, { status: number, statusText: string, headers: types.HeadersArray }>();

constructor(session: FFSession, browserContext: FFBrowserContext, opener: FFPage | null) {
this._session = session;
Expand All @@ -64,7 +68,7 @@ export class FFPage implements PageDelegate {
this._browserContext = browserContext;
this._page = new Page(this, browserContext);
this.rawMouse.setPage(this._page);
this._networkManager = new FFNetworkManager(session, this._page);
this._networkManager = new FFNetworkManager(session, this);
this._page.on(Page.Events.FrameDetached, frame => this._removeContextsForFrame(frame));
// TODO: remove Page.willOpenNewWindowAsynchronously from the protocol.
this._eventListeners = [
Expand All @@ -90,6 +94,7 @@ export class FFPage implements PageDelegate {
eventsHelper.addEventListener(this._session, 'Page.crashed', this._onCrashed.bind(this)),

eventsHelper.addEventListener(this._session, 'Page.webSocketCreated', this._onWebSocketCreated.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.webSocketOpened', this._onWebSocketOpened.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.webSocketClosed', this._onWebSocketClosed.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.webSocketFrameReceived', this._onWebSocketFrameReceived.bind(this)),
eventsHelper.addEventListener(this._session, 'Page.webSocketFrameSent', this._onWebSocketFrameSent.bind(this)),
Expand Down Expand Up @@ -119,7 +124,51 @@ export class FFPage implements PageDelegate {

_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));
}

_onWebSocketRequestWillBeSent(requestId: string, url: string, headers: types.HeadersArray) {
this._webSocketRequests.set(requestId, { url, headers });
}

_onWebSocketResponseReceived(requestId: string, status: number, statusText: string, headers: types.HeadersArray) {
this._webSocketResponses.set(requestId, { status, statusText, headers });
}

_onWebSocketRequestFinished(requestId: string) {
const response = this._webSocketResponses.get(requestId);
assert(response);
// If the request does not succeed then the WebSocket will never open, so pretend that it did.
if (response.status >= 400) {
const request = this._webSocketRequests.get(requestId);
assert(request);

this._webSocketRequests.delete(requestId);
this._webSocketResponses.delete(requestId);

const url = network.parseURL(request.url);
assert(url);
url.protocol = url.protocol === 'https' ? 'wss' : 'ws';

this._page.frameManager.onWebSocketCreated(requestId, url.toString());
this._page.frameManager.onWebSocketRequest(requestId, request.headers);
this._page.frameManager.onWebSocketResponse(requestId, response.status, response.statusText, response.headers);
this._page.frameManager.webSocketClosed(requestId);
return;
}
}

_onWebSocketOpened(event: Protocol.Page.webSocketOpenedPayload) {
const request = this._webSocketRequests.get(event.requestId);
assert(request);

const response = this._webSocketResponses.get(event.requestId);
assert(response);

this._webSocketRequests.delete(event.requestId);
this._webSocketResponses.delete(event.requestId);

this._page.frameManager.onWebSocketRequest(webSocketId(event.frameId, event.wsid), request.headers);
this._page.frameManager.onWebSocketResponse(webSocketId(event.frameId, event.wsid), response.status, response.statusText, response.headers);
}

_onWebSocketClosed(event: Protocol.Page.webSocketClosedPayload) {
Expand All @@ -129,11 +178,11 @@ export class FFPage implements PageDelegate {
}

_onWebSocketFrameReceived(event: Protocol.Page.webSocketFrameReceivedPayload) {
this._page.frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data);
this._page.frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp);
}

_onWebSocketFrameSent(event: Protocol.Page.webSocketFrameSentPayload) {
this._page.frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data);
this._page.frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp);
}

_onExecutionContextCreated(payload: Protocol.Runtime.executionContextCreatedPayload) {
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/server/firefox/protocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,12 +486,14 @@ export namespace Protocol {
wsid: string;
opcode: number;
data: string;
timestamp: number;
}
export type webSocketFrameReceivedPayload = {
frameId: string;
wsid: string;
opcode: number;
data: string;
timestamp: number;
}
export type screencastFramePayload = {
data: string;
Expand Down Expand Up @@ -605,7 +607,7 @@ export namespace Protocol {
}|null;
};
export type screenshotParameters = {
mimeType: ("image/png"|"image/jpeg");
mimeType: ("image/png"|"image/jpeg"|"image/webp");
clip: {
x: number;
y: number;
Expand Down
35 changes: 24 additions & 11 deletions packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,43 +403,56 @@ export class FrameManager {
this._webSockets.set(requestId, ws);
}

onWebSocketRequest(requestId: string) {
onWebSocketRequest(requestId: string, headers: types.HeadersArray, wallTime?: number, timestamp?: number) {
const ws = this._webSockets.get(requestId);
if (ws && ws.markAsNotified())
if (!ws)
return;

if (ws.markAsNotified())
this._page.emit(Page.Events.WebSocket, ws);

ws.requestSent(headers, wallTime, timestamp);
}

onWebSocketResponse(requestId: string, status: number, statusText: string) {
onWebSocketResponse(requestId: string, status: number, statusText: string, headers: types.HeadersArray) {
const ws = this._webSockets.get(requestId);
if (status < 400)
if (!ws)
return;
if (ws)

ws.responseReceived(status, statusText, headers);
if (status >= 400)
ws.error(`${statusText}: ${status}`);
}

onWebSocketFrameSent(requestId: string, opcode: number, data: string) {
onWebSocketFrameSent(requestId: string, opcode: number, data: string, timestamp: number) {
const ws = this._webSockets.get(requestId);
if (ws)
ws.frameSent(opcode, data);
ws.frameSent(opcode, data, timestamp);
}

webSocketFrameReceived(requestId: string, opcode: number, data: string) {
webSocketFrameReceived(requestId: string, opcode: number, data: string, timestamp: number) {
const ws = this._webSockets.get(requestId);
if (ws)
ws.frameReceived(opcode, data);
ws.frameReceived(opcode, data, timestamp);
}

webSocketClosed(requestId: string) {
const ws = this._webSockets.get(requestId);
if (ws)
if (ws) {
if (ws.markAsNotified())
this._page.emit(Page.Events.WebSocket, ws);
ws.closed();
}
this._webSockets.delete(requestId);
}

webSocketError(requestId: string, errorMessage: string): void {
const ws = this._webSockets.get(requestId);
if (ws)
if (ws) {
if (ws.markAsNotified())
this._page.emit(Page.Events.WebSocket, ws);
ws.error(errorMessage);
}
}

private _fireInternalFrameNavigation(frame: Frame, event: NavigationEvent) {
Expand Down
Loading
Loading