Skip to content

Commit

Permalink
Merge pull request #148 from GoogleChrome/mzgoddard/missing-context-d…
Browse files Browse the repository at this point in the history
…estroyed-workaround

Detect stale contexts
  • Loading branch information
hoch authored Jun 1, 2022
2 parents fbc2bf3 + b025ff0 commit ef8072c
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 160 deletions.
30 changes: 30 additions & 0 deletions src/chrome/DebuggerPageDomain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @file
* Strings passed to `chrome.debugger.sendCommand` and received from
* `chrome.debugger.onEvent` callbacks.
*/

import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping';

/** @see https://chromedevtools.github.io/devtools-protocol/tot/Page/#methods */
export enum PageDebuggerMethod {
disable = 'Page.disable',
enable = 'Page.enable',
}

/** @see https://chromedevtools.github.io/devtools-protocol/tot/Page/#events */
export enum PageDebuggerEvent {
domContentEventFired = 'Page.domContentEventFired',
frameAttached = 'Page.frameAttached',
frameDetached = 'Page.frameDetached',
frameNavigated = 'Page.frameNavigated',
frameRequestedNavigation = 'Page.frameRequestedNavigation',
frameStartedLoading = 'Page.frameStartedLoading',
frameStoppedLoading = 'Page.frameStoppedLoading',
lifecycleEvent = 'Page.lifecycleEvent',
loadEventFired = 'Page.loadEventFired',
}

/** @see https://chromedevtools.github.io/devtools-protocol/tot/Page/#types */
export type PageDebuggerEventParams<Name extends PageDebuggerEvent> =
ProtocolMapping.Events[Name];
2 changes: 1 addition & 1 deletion src/devtools.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<title>DevTools: Audion Extension</title>
</head>
<body>
<script src="devtools.js"></script>
<script src="audion-devtools.js"></script>
</body>
</html>
93 changes: 66 additions & 27 deletions src/devtools/DebuggerAttachEventController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Observable,
of,
Subject,
Subscriber,
} from 'rxjs';
import {
catchError,
Expand All @@ -21,6 +22,7 @@ import {
} from 'rxjs/operators';

import {chrome} from '../chrome';
import {PageDebuggerMethod} from '../chrome/DebuggerPageDomain';
import {WebAudioDebuggerMethod} from '../chrome/DebuggerWebAudioDomain';

/**
Expand Down Expand Up @@ -80,6 +82,8 @@ export interface DebuggerAttachEventState {
permission: AttachPermission;
attachInterest: number;
attachState: BinaryTransition;
pageEventInterest: number;
pageEventState: BinaryTransition;
webAudioEventInterest: number;
webAudioEventState: BinaryTransition;
}
Expand Down Expand Up @@ -120,6 +124,12 @@ export class DebuggerAttachEventController {
/** How many subscriptions want to attach to `chrome.debugger`. */
attachInterest$: CounterSubject;
attachState$: Observable<BinaryTransition>;
/**
* How many subscriptions want to receive page events through
* `chrome.debugger.onEvent`.
*/
pageEventInterest$: CounterSubject;
pageEventState$: Observable<BinaryTransition>;
/**
* How many subscriptions want to receive web audio events through
* `chrome.debugger.onEvent`.
Expand All @@ -146,6 +156,15 @@ export class DebuggerAttachEventController {
activateAction: () => attach({tabId}, debuggerVersion),
deactivateAction: () => detach({tabId}),
}),
// How many entities want to listen to page events through `onEvent`.
pageEventInterest: new CounterSubject(0),
// must be IS_ACTIVE for `onEvent` to receive events.
pageEventState: new BinaryTransitionSubject({
initialState: BinaryTransition.IS_INACTIVE,
activateAction: () => sendCommand({tabId}, PageDebuggerMethod.enable),
deactivateAction: () =>
sendCommand({tabId}, PageDebuggerMethod.disable),
}),
// How many entities want to listen to web audio events through `onEvent`.
webAudioEventInterest: new CounterSubject(0),
// webAudioEventState must be IS_ACTIVE for `onEvent` to receive events.
Expand All @@ -160,6 +179,8 @@ export class DebuggerAttachEventController {
this.permission$ = debuggerSubject.permission;
this.attachInterest$ = debuggerSubject.attachInterest;
this.attachState$ = debuggerSubject.attachState;
this.pageEventInterest$ = debuggerSubject.pageEventInterest;
this.pageEventState$ = debuggerSubject.pageEventState;
this.webAudioEventInterest$ = debuggerSubject.webAudioEventInterest;
this.webAudioEventState$ = debuggerSubject.webAudioEventState;

Expand All @@ -174,6 +195,8 @@ export class DebuggerAttachEventController {
previous.permission === current.permission &&
previous.attachInterest === current.attachInterest &&
previous.attachState === current.attachState &&
previous.pageEventInterest === current.pageEventInterest &&
previous.pageEventState === current.pageEventState &&
previous.webAudioEventInterest === current.webAudioEventInterest &&
previous.webAudioEventState === current.webAudioEventState,
),
Expand Down Expand Up @@ -225,33 +248,19 @@ export class DebuggerAttachEventController {
},
});

// Govern receiving web audio events through `chrome.debugger.onEvent`.
debuggerState$.subscribe({
next(state) {
if (
state.attachState === BinaryTransition.IS_ACTIVE &&
state.webAudioEventInterest > 0
) {
// Start receiving events. The attachemnt is active and some entities
// are listeneing for events.
debuggerSubject.webAudioEventState.activate();
} else {
if (state.attachState === BinaryTransition.IS_ACTIVE) {
// Stop receiving events. The attachment is still active but no
// entities are listening for events.
debuggerSubject.webAudioEventState.deactivate();
} else {
// "Skip" deactivation of receiving events and immediately go to
// the inactive state. The process of detachment either requested by
// the extension or initiated otherwise has implicitly stopped
// reception of events.
debuggerSubject.webAudioEventState.next(
BinaryTransition.IS_INACTIVE,
);
}
}
},
});
// Govern receiving events through `chrome.debugger.onEvent`.
debuggerState$.subscribe(
activateEventWhileAttached(
debuggerSubject.pageEventState,
({pageEventInterest}) => pageEventInterest > 0,
),
);
debuggerState$.subscribe(
activateEventWhileAttached(
debuggerSubject.webAudioEventState,
({webAudioEventInterest}) => webAudioEventInterest > 0,
),
);
}

/**
Expand All @@ -270,6 +279,36 @@ export class DebuggerAttachEventController {
}
}

function activateEventWhileAttached(
eventState: BinaryTransitionSubject,
interestExists: (state: DebuggerAttachEventState) => boolean,
): Partial<Subscriber<DebuggerAttachEventState>> {
return {
next(state) {
if (
state.attachState === BinaryTransition.IS_ACTIVE &&
interestExists(state)
) {
// Start receiving events. The attachemnt is active and some entities
// are listening for events.
eventState.activate();
} else {
if (state.attachState === BinaryTransition.IS_ACTIVE) {
// Stop receiving events. The attachment is still active but no
// entities are listening for events.
eventState.deactivate();
} else {
// "Skip" deactivation of receiving events and immediately go to the
// inactive state. The process of detachment either requested by the
// extension or initiated otherwise has implicitly stopped reception
// of events.
eventState.next(BinaryTransition.IS_INACTIVE);
}
}
},
};
}

/**
* Create a function that returns an observable that completes when the api
* calls back.
Expand Down
44 changes: 44 additions & 0 deletions src/devtools/DebuggerEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {filter, map, Observable} from 'rxjs';
import {chrome} from '../chrome';
import {fromChromeEvent} from '../utils/rxChrome';
import {DebuggerAttachEventController} from './DebuggerAttachEventController';
import {Audion} from './Types';

type DebuggerDomain = 'page' | 'webAudio';

interface DebuggerEventsOptions<D extends DebuggerDomain> {
domain: D;
}

type DebuggerDomainEvent<D extends DebuggerDomain> = D extends 'page'
? Audion.PageEvent
: D extends 'webAudio'
? Audion.WebAudioEvent
: never;

export class DebuggerEventsObservable<
D extends DebuggerDomain,
> extends Observable<DebuggerDomainEvent<D>> {
constructor(
public attachController: DebuggerAttachEventController,
public options: DebuggerEventsOptions<D>,
) {
super((subscriber) => {
attachController.attachInterest$.increment();
attachController[options.domain + 'EventInterest$'].increment();
const subscription = fromChromeEvent(chrome.debugger.onEvent)
.pipe(
map(([debuggeeId, method, params]) => ({method, params})),
filter(({method}) =>
method.toLowerCase().startsWith(options.domain.toLowerCase()),
),
)
.subscribe(subscriber);
subscription.add(() => {
attachController.attachInterest$.decrement();
attachController[options.domain + 'EventInterest$'].decrement();
});
return subscription;
});
}
}
9 changes: 9 additions & 0 deletions src/devtools/Types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/// <reference path="../chrome/DebuggerWebAudioDomain.ts" />

import {Protocol} from 'devtools-protocol/types/protocol';
import {
PageDebuggerEvent,
PageDebuggerEventParams,
} from '../chrome/DebuggerPageDomain';

import {
WebAudioDebuggerEvent,
Expand Down Expand Up @@ -89,6 +93,11 @@ export namespace Audion {
edges: Protocol.WebAudio.NodesConnectedEvent[];
}

export type PageEvent<N extends PageDebuggerEvent = PageDebuggerEvent> = {
method: N;
params: PageDebuggerEventParams<N>[0];
};

export type WebAudioEvent<
N extends WebAudioDebuggerEvent = WebAudioDebuggerEvent,
> = {
Expand Down
Loading

0 comments on commit ef8072c

Please sign in to comment.