refactor(devtools): remove MutationObserver and rename observer to execution hooks

This commit is contained in:
mgechev
2020-10-03 01:30:11 +03:00
committed by Minko Gechev
parent ffb46bdc3c
commit 26f8808f4b
6 changed files with 42 additions and 56 deletions
@@ -10,7 +10,7 @@ import {
ComponentExplorerViewQuery,
} from 'protocol';
import { ComponentTreeNode, getLatestComponentState, queryDirectiveForest, updateState } from './component-tree';
import { start as startProfiling, stop as stopProfiling } from './observer/capture';
import { start as startProfiling, stop as stopProfiling } from './hooks/capture';
import { serializeDirectiveState } from './state-serializer/state-serializer';
import { ComponentInspector, ComponentInspectorOptions } from './component-inspector/component-inspector';
import { setConsoleReference } from './set-console-reference';
@@ -22,7 +22,7 @@ import {
appIsAngularIvy,
} from './angular-check';
import { debounceTime } from 'rxjs/operators';
import { disableTimingAPI, enableTimingAPI, initializeOrGetDirectiveForestObserver } from './observer';
import { disableTimingAPI, enableTimingAPI, initializeOrGetDirectiveForestHooks } from './hooks';
import { runOutsideAngular } from './utils';
export const subscribeToClientEvents = (messageBus: MessageBus<Events>): void => {
@@ -51,7 +51,7 @@ export const subscribeToClientEvents = (messageBus: MessageBus<Events>): void =>
// update requests, instead we want to request an update at most
// every 250ms
runOutsideAngular(() => {
initializeOrGetDirectiveForestObserver()
initializeOrGetDirectiveForestHooks()
.changeDetection$.pipe(debounceTime(250))
.subscribe(() => messageBus.emit('componentTreeDirty'));
});
@@ -71,18 +71,18 @@ const getLatestComponentExplorerViewCallback = (messageBus: MessageBus<Events>)
) => {
// We want to force re-indexing of the component tree.
// Pressing the refresh button means the user saw stuck UI.
initializeOrGetDirectiveForestObserver().indexForest();
initializeOrGetDirectiveForestHooks().indexForest();
if (!query) {
messageBus.emit('latestComponentExplorerView', [
{
forest: prepareForestForSerialization(initializeOrGetDirectiveForestObserver().getDirectiveForest()),
forest: prepareForestForSerialization(initializeOrGetDirectiveForestHooks().getDirectiveForest()),
},
]);
return;
}
messageBus.emit('latestComponentExplorerView', [
{
forest: prepareForestForSerialization(initializeOrGetDirectiveForestObserver().getDirectiveForest()),
forest: prepareForestForSerialization(initializeOrGetDirectiveForestHooks().getDirectiveForest()),
properties: getLatestComponentState(query),
},
]);
@@ -100,7 +100,7 @@ const stopProfilingCallback = (messageBus: MessageBus<Events>) => () => {
};
const selectedComponentCallback = (position: ElementPosition) => {
const node = queryDirectiveForest(position, initializeOrGetDirectiveForestObserver().getDirectiveForest());
const node = queryDirectiveForest(position, initializeOrGetDirectiveForestHooks().getDirectiveForest());
setConsoleReference({ node, position });
};
@@ -109,7 +109,7 @@ const getNestedPropertiesCallback = (messageBus: MessageBus<Events>) => (
propPath: string[]
) => {
const emitEmpty = () => messageBus.emit('nestedProperties', [position, { props: {} }, propPath]);
const node = queryDirectiveForest(position.element, initializeOrGetDirectiveForestObserver().getDirectiveForest());
const node = queryDirectiveForest(position.element, initializeOrGetDirectiveForestHooks().getDirectiveForest());
if (!node) {
return emitEmpty();
}
@@ -138,7 +138,7 @@ const checkForAngular = (messageBus: MessageBus<Events>, attempt = 0): void => {
return;
}
if (appIsIvy) {
initializeOrGetDirectiveForestObserver();
initializeOrGetDirectiveForestHooks();
}
messageBus.emit('ngAvailability', [
{ version: ngVersion.toString(), devMode: appIsAngularInDevMode(), ivy: appIsIvy },
@@ -192,12 +192,12 @@ export const prepareForestForSerialization = (roots: ComponentTreeNode[]): Seria
? {
name: node.component.name,
isElement: node.component.isElement,
id: initializeOrGetDirectiveForestObserver().getDirectiveId(node.component.instance),
id: initializeOrGetDirectiveForestHooks().getDirectiveId(node.component.instance),
}
: null,
directives: node.directives.map((d) => ({
name: d.name,
id: initializeOrGetDirectiveForestObserver().getDirectiveId(d.instance),
id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance),
})),
children: prepareForestForSerialization(node.children),
} as SerializableComponentTreeNode;
@@ -2,7 +2,7 @@ import { unHighlight, highlight, findComponentAndHost } from '../highlighter';
import { Type } from '@angular/core';
import { buildDirectiveForest, ComponentTreeNode, findNodeInForest } from '../component-tree';
import { ElementPosition } from 'protocol';
import { initializeOrGetDirectiveForestObserver } from '../observer';
import { initializeOrGetDirectiveForestHooks } from '../hooks';
export interface ComponentInspectorOptions {
onComponentEnter: (id: number) => void;
@@ -46,9 +46,7 @@ export class ComponentInspector {
e.preventDefault();
if (this._selectedComponent.component && this._selectedComponent.host) {
this._onComponentSelect(
initializeOrGetDirectiveForestObserver().getDirectiveId(this._selectedComponent.component)
);
this._onComponentSelect(initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component));
}
}
@@ -63,9 +61,7 @@ export class ComponentInspector {
unHighlight();
if (this._selectedComponent.component && this._selectedComponent.host) {
highlight(this._selectedComponent.host);
this._onComponentEnter(
initializeOrGetDirectiveForestObserver().getDirectiveId(this._selectedComponent.component)
);
this._onComponentEnter(initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component));
}
}
@@ -1,15 +1,15 @@
import { DirectiveForestObserver, Hooks } from './observer';
import { DirectiveForestHooks, Hooks } from './hooks';
import { ElementPosition, ProfilerFrame, ElementProfile, DirectiveProfile, LifecycleProfile } from 'protocol';
import { runOutsideAngular, isCustomElement } from '../utils';
import { getDirectiveName } from '../highlighter';
import { ComponentTreeNode } from '../component-tree';
import { initializeOrGetDirectiveForestObserver } from '.';
import { initializeOrGetDirectiveForestHooks } from '.';
let inProgress = false;
let inChangeDetection = false;
let eventMap: Map<any, DirectiveProfile>;
let frameDuration = 0;
let observerHooks: Partial<Hooks> = {};
let hooks: Partial<Hooks> = {};
export const start = (onFrame: (frame: ProfilerFrame) => void): void => {
if (inProgress) {
@@ -17,15 +17,15 @@ export const start = (onFrame: (frame: ProfilerFrame) => void): void => {
}
eventMap = new Map<any, DirectiveProfile>();
inProgress = true;
observerHooks = getObserverHooks(onFrame);
initializeOrGetDirectiveForestObserver().subscribe(observerHooks);
hooks = getHooks(onFrame);
initializeOrGetDirectiveForestHooks().subscribe(hooks);
};
export const stop = (): ProfilerFrame => {
const observer = initializeOrGetDirectiveForestObserver();
const result = flushBuffer(observer);
initializeOrGetDirectiveForestObserver().unsubscribe(observerHooks);
observerHooks = {};
const directiveForestHooks = initializeOrGetDirectiveForestHooks();
const result = flushBuffer(directiveForestHooks);
initializeOrGetDirectiveForestHooks().unsubscribe(hooks);
hooks = {};
inProgress = false;
return result;
};
@@ -42,7 +42,7 @@ const getEventStart = (map: { [key: string]: number }, directive: any, label: st
return map[key];
};
const getObserverHooks = (onFrame: (frame: ProfilerFrame) => void) => {
const getHooks = (onFrame: (frame: ProfilerFrame) => void) => {
const timeStartMap: { [key: string]: number } = {};
return {
// We flush here because it's possible the current node to overwrite
@@ -63,7 +63,7 @@ const getObserverHooks = (onFrame: (frame: ProfilerFrame) => void) => {
runOutsideAngular(() => {
Promise.resolve().then(() => {
inChangeDetection = false;
onFrame(flushBuffer(initializeOrGetDirectiveForestObserver(), source));
onFrame(flushBuffer(initializeOrGetDirectiveForestHooks(), source));
});
});
}
@@ -196,14 +196,14 @@ const prepareInitialFrame = (source: string, duration: number) => {
duration,
directives: [],
};
const observer = initializeOrGetDirectiveForestObserver();
const directiveForest = observer.getDirectiveForest();
const directiveForestHooks = initializeOrGetDirectiveForestHooks();
const directiveForest = directiveForestHooks.getDirectiveForest();
const traverse = (node: ComponentTreeNode, children = frame.directives) => {
let position: ElementPosition | undefined;
if (node.component) {
position = observer.getDirectivePosition(node.component.instance);
position = directiveForestHooks.getDirectivePosition(node.component.instance);
} else {
position = observer.getDirectivePosition(node.directives[0].instance);
position = directiveForestHooks.getDirectivePosition(node.directives[0].instance);
}
if (position === undefined) {
return;
@@ -235,12 +235,12 @@ const prepareInitialFrame = (source: string, duration: number) => {
return frame;
};
const flushBuffer = (obs: DirectiveForestObserver, source: string = '') => {
const flushBuffer = (directiveForestHooks: DirectiveForestHooks, source: string = '') => {
const items = Array.from(eventMap.keys());
const positions: ElementPosition[] = [];
const positionDirective = new Map<ElementPosition, any>();
items.forEach((dir) => {
const position = obs.getDirectivePosition(dir);
const position = directiveForestHooks.getDirectivePosition(dir);
if (position === undefined) {
return;
}
@@ -102,15 +102,10 @@ const getLifeCycleName = (obj: {}, fn: any): keyof LifecycleProfile | 'unknown'
};
/**
* This is a temporal "polyfill" until we receive more comprehensive framework
* debugging APIs. This observer checks for new elements added. When it detects
* this has happened, it checks if any of the elements in the tree with root
* the added element is a component. If it is, it throws a creation event.
* The polyfill also patches the tView template function reference to allow
* tracking of how much time we spend in the particular component in change detection.
* This is a temporal "polyfill" until we receive
* more comprehensive framework debugging APIs.
*/
export class DirectiveForestObserver {
private _mutationObserver = new MutationObserver(this._onMutation.bind(this));
export class DirectiveForestHooks {
private _patched = new Map<any, () => void>();
private _undoLifecyclePatch: (() => void)[] = [];
private _lastChangeDetection = new Map<any, number>();
@@ -150,15 +145,10 @@ export class DirectiveForestObserver {
}
initialize(): void {
this._mutationObserver.observe(document, {
subtree: true,
childList: true,
});
this.indexForest();
}
destroy(): void {
this._mutationObserver.disconnect();
this._lastChangeDetection = new Map<any, number>();
this._tracker.destroy();
@@ -1,5 +1,5 @@
import { getDirectiveName } from '../highlighter';
import { DirectiveForestObserver } from './observer';
import { DirectiveForestHooks } from './hooks';
import { LifecycleProfile } from 'protocol';
const markName = (s: string, method: Method) => `🅰️ ${s}#${method}`;
@@ -36,12 +36,12 @@ export const disableTimingAPI = () => (timingAPIFlag = false);
const timingAPIEnabled = () => timingAPIFlag;
export let observer: DirectiveForestObserver;
export const initializeOrGetDirectiveForestObserver = () => {
if (observer) {
return observer;
export let directiveForestHooks: DirectiveForestHooks;
export const initializeOrGetDirectiveForestHooks = () => {
if (directiveForestHooks) {
return directiveForestHooks;
}
observer = new DirectiveForestObserver({
directiveForestHooks = new DirectiveForestHooks({
onChangeDetectionStart(component: any): void {
if (!timingAPIEnabled()) {
return;
@@ -67,6 +67,6 @@ export const initializeOrGetDirectiveForestObserver = () => {
endMark(getDirectiveName(component), lifecyle);
},
});
observer.initialize();
return observer;
directiveForestHooks.initialize();
return directiveForestHooks;
};