Skip to content

Commit

Permalink
attempt to fix missing requests (#7640) (#7712)
Browse files Browse the repository at this point in the history
this should fix #7640 issue with request interception

- implement the `attachedToTarget` event for iframes
- call Fetch.enable for iframes
- add the `Runtime.runIfWaitingForDebugger` call just to make this thing
work (playwright does the same trick).
- ignore typing errors for sessionId argument
(cyrus-and/chrome-remote-interface#534)
- I did not manage to create a test due to its complexity and unknown
cause
  • Loading branch information
AlexKamaev committed May 23, 2023
1 parent cb5c9ca commit df43d42
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 44 deletions.
55 changes: 38 additions & 17 deletions src/native-automation/request-pipeline/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ERROR_ROUTE from '../error-route';

import {
ContinueRequestArgs,
SessionId,
SessionStorageInfo,
SpecialServiceRoutes,
} from '../types';
Expand Down Expand Up @@ -124,7 +125,7 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi
requestPipelineMockLogger('%s\n%s', event.networkId, pipelineContext.mock.error);
}

private async _handleMockResponse (mockedResponse: IncomingMessageLike, pipelineContext: NativeAutomationPipelineContext, event: RequestPausedEvent): Promise<void> {
private async _handleMockResponse (mockedResponse: IncomingMessageLike, pipelineContext: NativeAutomationPipelineContext, event: RequestPausedEvent, sessionId: SessionId): Promise<void> {
if (this._stopped)
return;

Expand All @@ -138,12 +139,12 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi
};

if (pipelineContext.reqOpts.isAjax)
await this._resourceInjector.processNonProxiedContent(fulfillInfo, this._client);
await this._resourceInjector.processNonProxiedContent(fulfillInfo, this._client, sessionId);
else {
await this._resourceInjector.processHTMLPageContent(fulfillInfo, {
isIframe: false,
contextStorage: this.contextStorage,
}, this._client);
}, this._client, sessionId);
}

requestPipelineMockLogger(`sent mocked response for the ${event.requestId}`);
Expand Down Expand Up @@ -175,9 +176,9 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi
return pipelineContext.injectableUserScripts;
}

private async _respondToOtherRequest (event: RequestPausedEvent): Promise<void> {
private async _respondToOtherRequest (event: RequestPausedEvent, sessionId: SessionId): Promise<void> {
if (isRedirectStatusCode(event.responseStatusCode)) {
await safeContinueResponse(this._client, { requestId: event.requestId });
await safeContinueResponse(this._client, { requestId: event.requestId }, sessionId);

return;
}
Expand Down Expand Up @@ -223,7 +224,7 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi
contextStorage: this.contextStorage,
userScripts,
},
this._client);
this._client, sessionId);

this._contextInfo.dispose(getRequestId(event));

Expand All @@ -232,7 +233,7 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi
else {
const continueResponseRequest = this._createContinueResponseRequest(event, modified);

await safeContinueResponse(this._client, continueResponseRequest);
await safeContinueResponse(this._client, continueResponseRequest, sessionId);

this._contextInfo.dispose(getRequestId(event));
}
Expand Down Expand Up @@ -277,15 +278,15 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi
return new Error(event.responseErrorReason);
}

private async _tryRespondToOtherRequest (event: RequestPausedEvent): Promise<void> {
private async _tryRespondToOtherRequest (event: RequestPausedEvent, sessionId: SessionId): Promise<void> {
try {
if (event.responseErrorReason && this._shouldRedirectToErrorPage(event)) {
const error = this._createError(event);

await this._resourceInjector.redirectToErrorPage(this._client, error, event.request.url);
}
else
await this._respondToOtherRequest(event);
await this._respondToOtherRequest(event, sessionId);
}
catch (err) {
if (event.networkId && this._failedRequestIds.includes(event.networkId)) {
Expand All @@ -298,18 +299,18 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi
}
}

private async _handleOtherRequests (event: RequestPausedEvent): Promise<void> {
private async _handleOtherRequests (event: RequestPausedEvent, sessionId: SessionId): Promise<void> {
requestPipelineOtherRequestLogger('%r', event);

if (!event.responseErrorReason && (isRequest(event) || isRedirectStatusCode(event.responseStatusCode))) {
this._contextInfo.init(event);

await this.requestHookEventProvider.onRequest(event, this._contextInfo);

const pipelineContext = this._contextInfo.getPipelineContext(event.networkId as string);
const pipelineContext = this._contextInfo.getPipelineContext(getRequestId(event));

if (!pipelineContext || !pipelineContext.mock)
await safeContinueRequest(this._client, event, this._createContinueEventArgs(event, pipelineContext.reqOpts));
await safeContinueRequest(this._client, event, sessionId, this._createContinueEventArgs(event, pipelineContext.reqOpts));
else {
requestPipelineMockLogger('begin mocking request %r', event);

Expand All @@ -321,15 +322,15 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi

await this.requestHookEventProvider.onResponse(mockedResponseEvent, mockedResponse.getBody(), this._contextInfo, this._client);

await this._handleMockResponse(mockedResponse, pipelineContext, event);
await this._handleMockResponse(mockedResponse, pipelineContext, event, sessionId);

this._contextInfo.dispose(getRequestId(event));

requestPipelineMockLogger('end mocking request %r', event);
}
}
else
await this._tryRespondToOtherRequest(event);
await this._tryRespondToOtherRequest(event, sessionId);
}

private _getUploadPostData (event: Protocol.Fetch.RequestPausedEvent): string | undefined {
Expand Down Expand Up @@ -371,16 +372,36 @@ export default class NativeAutomationRequestPipeline extends NativeAutomationApi
patterns: ALL_REQUESTS_DATA,
});

this._client.Fetch.on('requestPaused', async (event: RequestPausedEvent) => {
await this._client.Target.setAutoAttach({
autoAttach: true,
waitForDebuggerOnStart: true,
flatten: true,
});

// NOTE: We need to enable the Fetch domain for iframe targets
// to intercept some requests. We need to use the `sessionId` option
// in continueRequest/continueResponse/fulfillRequest methods
await this._client.Target.on('attachedToTarget', async event => {
// @ts-ignore
await this._client.Runtime.runIfWaitingForDebugger(event.sessionId);

if (event.targetInfo.type !== 'worker')
// @ts-ignore
await this._client.Fetch.enable({ patterns: ALL_REQUESTS_DATA }, event.sessionId);

});

// @ts-ignore
this._client.Fetch.on('requestPaused', async (event: RequestPausedEvent, sessionId: SessionId) => {
if (this._stopped)
return;

const specialRequestHandler = getSpecialRequestHandler(event, this.options, this._specialServiceRoutes);

if (specialRequestHandler)
await specialRequestHandler(event, this._client, this.options);
await specialRequestHandler(event, this._client, this.options, sessionId);
else
await this._handleOtherRequests(event);
await this._handleOtherRequests(event, sessionId);
});

this._client.Page.on('frameNavigated', async (event: FrameNavigatedEvent) => {
Expand Down
17 changes: 10 additions & 7 deletions src/native-automation/request-pipeline/safe-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import FulfillRequestRequest = Protocol.Fetch.FulfillRequestRequest;
import ContinueResponseRequest = Protocol.Fetch.ContinueResponseRequest;
import ErrorReason = Protocol.Network.ErrorReason;
import { isRequestPausedEvent } from '../utils/cdp';
import { ContinueRequestArgs } from '../types';
import { ContinueRequestArgs, SessionId } from '../types';

const INVALID_INTERCEPTED_RESPONSE_ERROR_MSG = 'Invalid InterceptionId.';

Expand All @@ -29,35 +29,38 @@ async function connectionResetGuard (handleRequestFn: () => Promise<void>, handl
}
}

export async function safeContinueResponse (client: ProtocolApi, data: RequestPausedEvent | ContinueResponseRequest): Promise<void> {
export async function safeContinueResponse (client: ProtocolApi, data: RequestPausedEvent | ContinueResponseRequest, sessionId: SessionId): Promise<void> {
const isPausedEvent = isRequestPausedEvent(data);

await connectionResetGuard(async () => {
const param = isPausedEvent
? { requestId: data.requestId }
: data;

await client.Fetch.continueResponse(param);
// @ts-ignore
await client.Fetch.continueResponse(param, sessionId);
}, err => {
const formatter = isPausedEvent ? '%r' : '%s';

requestPipelineLogger(`Fetch.continueResponse. Unhandled error %s during processing ${formatter}`, err, data);
});
}

export async function safeFulfillRequest (client: ProtocolApi, fulfillInfo: FulfillRequestRequest): Promise<void> {
export async function safeFulfillRequest (client: ProtocolApi, fulfillInfo: FulfillRequestRequest, sessionId: SessionId): Promise<void> {
await connectionResetGuard(async () => {
await client.Fetch.fulfillRequest(fulfillInfo);
// @ts-ignore
await client.Fetch.fulfillRequest(fulfillInfo, sessionId);
}, err => {
requestPipelineLogger(`Fetch.fulfillRequest. Unhandled error %s during processing %s`, err, fulfillInfo.requestId);
});
}

export async function safeContinueRequest (client: ProtocolApi, event: RequestPausedEvent, continueRequestArgs?: ContinueRequestArgs): Promise<void> {
export async function safeContinueRequest (client: ProtocolApi, event: RequestPausedEvent, sessionId: SessionId, continueRequestArgs?: ContinueRequestArgs): Promise<void> {
const { postData, method, url } = continueRequestArgs || {};

await connectionResetGuard(async () => {
await client.Fetch.continueRequest({ requestId: event.requestId, postData, method, url });
// @ts-ignore
await client.Fetch.continueRequest({ requestId: event.requestId, postData, method, url }, sessionId);
}, err => {
requestPipelineLogger(`Fetch.continueRequest. Unhandled error %s during processing %r`, err, event);
});
Expand Down
30 changes: 17 additions & 13 deletions src/native-automation/request-pipeline/special-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import Protocol from 'devtools-protocol';
import RequestPausedEvent = Protocol.Fetch.RequestPausedEvent;
import { DEFAULT_FAVICON_PATH } from '../../assets/injectables';
import { RequestHandler, SpecialServiceRoutes } from '../types';
import {
RequestHandler,
SessionId,
SpecialServiceRoutes,
} from '../types';
import { ProtocolApi } from 'chrome-remote-interface';
import {
requestPipelineInternalRequestLogger,
Expand All @@ -20,20 +24,20 @@ import {
safeFulfillRequest,
} from './safe-api';

async function handleRequestPauseEvent (event: RequestPausedEvent, client: ProtocolApi): Promise<void> {
async function handleRequestPauseEvent (event: RequestPausedEvent, client: ProtocolApi, sessionId: SessionId): Promise<void> {
if (isRequest(event))
await safeContinueRequest(client, event);
await safeContinueRequest(client, event, sessionId);
else
await safeContinueResponse(client, event);
await safeContinueResponse(client, event, sessionId);
}


const internalRequest = {
condition: (event: RequestPausedEvent): boolean => !event.networkId && event.resourceType !== 'Document',
handler: async (event: RequestPausedEvent, client: ProtocolApi): Promise<void> => {
condition: (event: RequestPausedEvent): boolean => !event.networkId && event.resourceType !== 'Document' && !event.request.url,
handler: async (event: RequestPausedEvent, client: ProtocolApi, options: NativeAutomationInitOptions, sessionId: SessionId): Promise<void> => {
requestPipelineInternalRequestLogger('%r', event);

await handleRequestPauseEvent(event, client);
await handleRequestPauseEvent(event, client, sessionId);
},
} as RequestHandler;

Expand All @@ -48,10 +52,10 @@ const serviceRequest = {

return options.serviceDomains.some(domain => url.startsWith(domain));
},
handler: async (event: RequestPausedEvent, client: ProtocolApi): Promise<void> => {
handler: async (event: RequestPausedEvent, client: ProtocolApi, options: NativeAutomationInitOptions, sessionId: SessionId): Promise<void> => {
requestPipelineServiceRequestLogger('%r', event);

await handleRequestPauseEvent(event, client);
await handleRequestPauseEvent(event, client, sessionId);
},
} as RequestHandler;

Expand All @@ -61,11 +65,11 @@ const defaultFaviconRequest = {

return parsedUrl.pathname === DEFAULT_FAVICON_PATH;
},
handler: async (event: RequestPausedEvent, client: ProtocolApi, options: NativeAutomationInitOptions): Promise<void> => {
handler: async (event: RequestPausedEvent, client: ProtocolApi, options: NativeAutomationInitOptions, sessionId: SessionId): Promise<void> => {
requestPipelineLogger('%r', event);

if (isRequest(event))
await safeContinueRequest(client, event);
await safeContinueRequest(client, event, sessionId);
else {
if (event.responseStatusCode === StatusCodes.NOT_FOUND) { // eslint-disable-line no-lonely-if
const { favIcon } = loadAssets(options.developmentMode);
Expand All @@ -75,10 +79,10 @@ const defaultFaviconRequest = {
responseCode: StatusCodes.OK,
responseHeaders: [ FAVICON_CONTENT_TYPE_HEADER ],
body: toBase64String(favIcon),
});
}, sessionId);
}
else
await safeContinueResponse(client, event);
await safeContinueResponse(client, event, sessionId);
}
},
} as RequestHandler;
Expand Down
13 changes: 7 additions & 6 deletions src/native-automation/resource-injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { redirect, navigateTo } from './utils/cdp';
import {
DocumentResourceInfo,
InjectableResourcesOptions,
SessionId,
SessionStorageInfo,
SpecialServiceRoutes,
} from './types';
Expand Down Expand Up @@ -134,14 +135,14 @@ export default class ResourceInjector {
return stringifyHeaderValues(headers);
}

private async _fulfillRequest (client: ProtocolApi, fulfillRequestInfo: FulfillRequestRequest, body: string): Promise<void> {
private async _fulfillRequest (client: ProtocolApi, fulfillRequestInfo: FulfillRequestRequest, body: string, sessionId: SessionId): Promise<void> {
await safeFulfillRequest(client, {
requestId: fulfillRequestInfo.requestId,
responseCode: fulfillRequestInfo.responseCode || StatusCodes.OK,
responsePhrase: fulfillRequestInfo.responsePhrase,
responseHeaders: this._processResponseHeaders(fulfillRequestInfo.responseHeaders),
body: toBase64String(body),
});
}, sessionId);
}

public async redirectToErrorPage (client: ProtocolApi, err: Error, url: string): Promise<void> {
Expand Down Expand Up @@ -210,7 +211,7 @@ export default class ResourceInjector {
});
}

public async processHTMLPageContent (fulfillRequestInfo: FulfillRequestRequest, injectableResourcesOptions: InjectableResourcesOptions, client: ProtocolApi): Promise<void> {
public async processHTMLPageContent (fulfillRequestInfo: FulfillRequestRequest, injectableResourcesOptions: InjectableResourcesOptions, client: ProtocolApi, sessionId: SessionId): Promise<void> {
const injectableResources = await this._prepareInjectableResources(injectableResourcesOptions);

// NOTE: an unhandled exception interrupts the test execution,
Expand All @@ -224,12 +225,12 @@ export default class ResourceInjector {
this._getPageInjectableResourcesOptions(injectableResourcesOptions),
);

await this._fulfillRequest(client, fulfillRequestInfo, updatedResponseStr);
await this._fulfillRequest(client, fulfillRequestInfo, updatedResponseStr, sessionId);
}
}

public async processNonProxiedContent (fulfillRequestInfo: FulfillRequestRequest, client: ProtocolApi): Promise<void> {
await this._fulfillRequest(client, fulfillRequestInfo, fulfillRequestInfo.body as string);
public async processNonProxiedContent (fulfillRequestInfo: FulfillRequestRequest, client: ProtocolApi, sessionId: SessionId): Promise<void> {
await this._fulfillRequest(client, fulfillRequestInfo, fulfillRequestInfo.body as string, sessionId);
}

private _getPageInjectableResourcesOptions (injectableResourcesOptions: InjectableResourcesOptions): PageRestoreStoragesOptions | undefined {
Expand Down
2 changes: 2 additions & 0 deletions src/native-automation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ export interface ContinueRequestArgs {
method?: string;
url?: string;
}

export type SessionId = string | undefined;
3 changes: 2 additions & 1 deletion src/native-automation/utils/cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export function createRequestPausedEventForResponse (mockedResponse: IncomingMes

export function getRequestId (event: RequestPausedEvent | FrameNavigatedEvent): string {
if (isRequestPausedEvent(event))
return event.networkId as string;
// NOTE: the `networkId` field can be missing
return event.networkId as string || event.requestId;

return event.frame.loaderId;
}

0 comments on commit df43d42

Please sign in to comment.