Skip to content

Commit

Permalink
fix(puppet): non-popups getting opener
Browse files Browse the repository at this point in the history
  • Loading branch information
blakebyrnes committed Feb 10, 2021
1 parent 3b452d8 commit e79584f
Show file tree
Hide file tree
Showing 11 changed files with 42 additions and 41 deletions.
1 change: 1 addition & 0 deletions core/test/navigation.test.ts
Expand Up @@ -440,6 +440,7 @@ describe('PaintingStable tests', () => {
koaServer.get('/grid/:filename', async ctx => {
const filename = ctx.params.filename;
if (filename === 'data.json') {
await new Promise(resolve => setTimeout(resolve, 100));
const records = [];
for (let i = 0; i < 200; i += 1) {
records.push(
Expand Down
4 changes: 3 additions & 1 deletion puppet-chrome/lib/Browser.ts
Expand Up @@ -77,7 +77,9 @@ export class Browser extends TypedEventEmitter<IBrowserEvents> implements IPuppe
private onAttachedToTarget(event: Protocol.Target.AttachedToTargetEvent) {
const { targetInfo, sessionId } = event;

assert(targetInfo.browserContextId, `targetInfo: ${JSON.stringify(targetInfo, null, 2)}`);
if (!targetInfo.browserContextId) {
assert(targetInfo.browserContextId, `targetInfo: ${JSON.stringify(targetInfo, null, 2)}`);
}

if (targetInfo.type === 'page') {
const cdpSession = this.connection.getSession(sessionId);
Expand Down
5 changes: 4 additions & 1 deletion puppet-chrome/lib/BrowserContext.ts
Expand Up @@ -15,6 +15,7 @@ import { IBoundLog } from '@secret-agent/core-interfaces/ILog';
import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping';
import IRegisteredEventListener from '@secret-agent/core-interfaces/IRegisteredEventListener';
import { CanceledPromiseError } from '@secret-agent/commons/interfaces/IPendingWaitEvent';
import { IPuppetPage } from '@secret-agent/puppet-interfaces/IPuppetPage';
import { Page } from './Page';
import { Browser } from './Browser';
import { CDPSession } from './CDPSession';
Expand All @@ -41,6 +42,7 @@ export class BrowserContext

private _emulation: IBrowserEmulationSettings;

private readonly createdTargetIds = new Set<string>();
private readonly pages: Page[] = [];
private readonly browser: Browser;
private readonly id: string;
Expand Down Expand Up @@ -76,6 +78,7 @@ export class BrowserContext
url: 'about:blank',
browserContextId: this.id,
});
this.createdTargetIds.add(targetId);

await this.attachToTarget(targetId);

Expand Down Expand Up @@ -111,7 +114,7 @@ export class BrowserContext

let opener = targetInfo.openerId ? this.getPageWithId(targetInfo.openerId) || null : null;
// make the first page the active page
if (!opener && this.pages.length) opener = this.pages[0];
if (!opener && !this.createdTargetIds.has(targetInfo.targetId)) opener = this.pages[0];
const page = new Page(cdpSession, targetInfo.targetId, this, this.logger, opener);
this.pages.push(page);
// eslint-disable-next-line promise/catch-or-return
Expand Down
40 changes: 24 additions & 16 deletions puppet-chrome/lib/CDPSession.ts
Expand Up @@ -18,9 +18,10 @@
import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping';
import { Protocol } from 'devtools-protocol';
import { EventEmitter } from 'events';
import { IConnectionCallback } from '@secret-agent/puppet-interfaces/IConnectionCallback';
import { CanceledPromiseError } from '@secret-agent/commons/interfaces/IPendingWaitEvent';
import { TypedEventEmitter } from '@secret-agent/commons/eventUtils';
import IResolvablePromise from '@secret-agent/core-interfaces/IResolvablePromise';
import { createPromise } from '@secret-agent/commons/utils';
import ProtocolError from './ProtocolError';
import { Connection } from './Connection';
import RemoteObject = Protocol.Runtime.RemoteObject;
Expand All @@ -39,7 +40,10 @@ export class CDPSession extends EventEmitter {

private readonly sessionId: string;
private readonly targetType: string;
private readonly pendingMessages: Map<number, IConnectionCallback> = new Map();
private readonly pendingMessages: Map<
number,
{ resolvable: IResolvablePromise<any>; method: string }
> = new Map();

constructor(connection: Connection, targetType: string, sessionId: string) {
super();
Expand All @@ -54,7 +58,7 @@ export class CDPSession extends EventEmitter {
sendInitiator?: object,
): Promise<ProtocolMapping.Commands[T]['returnType']> {
if (!this.isConnected()) {
throw new CanceledPromiseError(`Session closed before api call (${method})`);
throw new CanceledPromiseError(`${method} called after session closed (${this.sessionId})`);
}

const message = {
Expand All @@ -71,28 +75,29 @@ export class CDPSession extends EventEmitter {
},
sendInitiator,
);
const resolvable = createPromise<ProtocolMapping.Commands[T]['returnType']>();

return await new Promise((resolve, reject) => {
this.pendingMessages.set(id, { resolve, reject, error: new CanceledPromiseError(), method });
});
this.pendingMessages.set(id, { resolvable, method });
return await resolvable.promise;
}

onMessage(object: ICDPSendResponseMessage & ICDPEventMessage): void {
this.messageEvents.emit('receive', { ...object });
if (!object.id) {
setImmediate(() => this.emit(object.method, object.params));
this.emit(object.method, object.params);
return;
}

const callback = this.pendingMessages.get(object.id);
if (!callback) return;
const pending = this.pendingMessages.get(object.id);
if (!pending) return;

const { resolvable, method } = pending;

this.pendingMessages.delete(object.id);
if (object.error) {
const protocolError = new ProtocolError(callback.error.stack, callback.method, object.error);
setImmediate(() => callback.reject(protocolError));
resolvable.reject(new ProtocolError(resolvable.stack, method, object.error));
} else {
setImmediate(() => callback.resolve(object.result));
resolvable.resolve(object.result);
}
}

Expand All @@ -105,10 +110,13 @@ export class CDPSession extends EventEmitter {
}

onClosed(): void {
for (const callback of this.pendingMessages.values()) {
const error = callback.error;
error.message = `Cancel Pending Promise (${callback.method}): Target closed.`;
callback.reject(error);
for (const { resolvable, method } of this.pendingMessages.values()) {
const error = new CanceledPromiseError(`Cancel Pending Promise (${method}): Target closed.`);
error.stack += `\n${'------DEVTOOLS'.padEnd(
50,
'-',
)}\n${`------DEVTOOLS-SESSION-ID =${this.sessionId}`.padEnd(50, '-')}\n${resolvable.stack}`;
resolvable.reject(error);
}
this.pendingMessages.clear();
this.connection = null;
Expand Down
4 changes: 1 addition & 3 deletions puppet-chrome/lib/Connection.ts
Expand Up @@ -22,14 +22,13 @@ import {
import IConnectionTransport, {
IConnectionTransportEvents,
} from '@secret-agent/puppet-interfaces/IConnectionTransport';
import { IPuppetConnectionEvents } from '@secret-agent/puppet-interfaces/IPuppetConnection';
import IRegisteredEventListener from '@secret-agent/core-interfaces/IRegisteredEventListener';
import Log from '@secret-agent/commons/Logger';
import { CDPSession } from './CDPSession';

const { log } = Log(module);

export class Connection extends TypedEventEmitter<IPuppetConnectionEvents> {
export class Connection extends TypedEventEmitter<{ disconnected: void }> {
public readonly rootSession: CDPSession;
public isClosed = false;

Expand Down Expand Up @@ -85,7 +84,6 @@ export class Connection extends TypedEventEmitter<IPuppetConnectionEvents> {

const cdpSession = this.sessionsById.get(object.sessionId || '');
if (cdpSession) {
// make asynchronous so we don't have accidental bugs where things are behaving synchronous until stack backs up
cdpSession.onMessage(object);
} else {
log.warn('MessageWithUnknownSession', { sessionId: null, message: object });
Expand Down
6 changes: 0 additions & 6 deletions puppet-interfaces/IConnectionCallback.ts

This file was deleted.

7 changes: 0 additions & 7 deletions puppet-interfaces/IPuppetConnection.ts

This file was deleted.

4 changes: 0 additions & 4 deletions puppet-interfaces/IPuppetEngine.ts

This file was deleted.

1 change: 1 addition & 0 deletions puppet-interfaces/IPuppetWorker.ts
Expand Up @@ -4,6 +4,7 @@ export interface IPuppetWorker extends ITypedEventEmitter<IPuppetWorkerEvents> {
id: string;
url: string;
type: string;
isReady: Promise<Error | null>;
evaluate<T>(expression: string): Promise<T>;
}

Expand Down
9 changes: 7 additions & 2 deletions puppet/lib/PipeTransport.ts
Expand Up @@ -44,6 +44,7 @@ export class PipeTransport
log.error('PipeTransport.WriteError', { error, sessionId: null }),
),
);
this.emit = this.emit.bind(this);
}

send(message: string) {
Expand All @@ -68,15 +69,19 @@ export class PipeTransport
return;
}
const message = this.pendingMessage + buffer.toString(undefined, 0, end);
this.emit('message', message);
this.emitMessage(message);

let start = end + 1;
end = buffer.indexOf('\0', start);
while (end !== -1) {
this.emit('message', buffer.toString(undefined, start, end));
this.emitMessage(buffer.toString(undefined, start, end));
start = end + 1;
end = buffer.indexOf('\0', start);
}
this.pendingMessage = buffer.toString(undefined, start);
}

private emitMessage(message: string): void {
setImmediate(this.emit, 'message', message);
}
}
2 changes: 1 addition & 1 deletion puppet/test/Worker.test.ts
Expand Up @@ -49,7 +49,7 @@ describe.each([[Chrome80.engine], [Chrome83.engine]])(
const worker = page.workers[0];
expect(worker.url).toContain('worker.js');

await new Promise(setImmediate);
await worker.isReady;

expect(await worker.evaluate(`self.workerFunction()`)).toBe('worker function result');

Expand Down

0 comments on commit e79584f

Please sign in to comment.