Skip to content
This repository was archived by the owner on May 25, 2021. It is now read-only.

Sanity for AppRef.isStable observable #1417

Merged
merged 3 commits into from
Nov 19, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 85 additions & 98 deletions src/backend/backend.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import { compare } from '../utils/patch';
import { isAngular, isDebugMode } from './utils/app-check';

import {
MutableTree,
Node,
Path,
instanceWithMetadata,
serializePath,
} from '../tree';
import { MutableTree, Node, Path, instanceWithMetadata, serializePath } from '../tree';

import { onElementFound, onFindElement } from './utils/find-element';

import {
parseModulesFromRootElement,
parseModulesFromRouter,
NgModulesRegistry,
} from './utils/parse-modules';
import { parseModulesFromRootElement, parseModulesFromRouter, NgModulesRegistry } from './utils/parse-modules';

import {
parseNgVersion,
} from './utils/parse-ng-version';
import { parseNgVersion } from './utils/parse-ng-version';

import { createTreeFromElements } from '../tree/mutable-tree-factory';

Expand All @@ -30,12 +18,10 @@ import {
MessageFactory,
MessageType,
browserDispatch,
browserSubscribe,
browserSubscribe
} from '../communication';

import {
parameterTypes,
} from '../tree/decorators';
import { parameterTypes } from '../tree/decorators';

import { send } from './indirect-connection';

Expand All @@ -46,7 +32,7 @@ import {
parseRoutes,
getNodeFromPartialPath,
getNodeInstanceParent,
getNodeProvider,
getNodeProvider
} from './utils';

import { serialize } from '../utils';
Expand All @@ -57,7 +43,7 @@ import { MessagePipeBackend } from 'feature-modules/.lib';
import { highlighter } from 'feature-modules/highlighter/backend/index';
import { ApplicationRef, NgModuleRef } from '@angular/core';
import { timer, Subscription, Subject } from 'rxjs';
import { takeWhile } from 'rxjs/operators';
import { takeWhile, filter } from 'rxjs/operators';

declare const ng;
declare const getAllAngularRootElements: () => Element[];
Expand Down Expand Up @@ -94,7 +80,7 @@ const parsedModulesData: NgModulesRegistry = {
modules: {},
names: [],
configs: {},
tokenIdMap: {},
tokenIdMap: {}
};

const featureModulesPipe = new MessagePipeBackend({
Expand All @@ -114,11 +100,13 @@ const runAndHandleUncaughtExceptions = (fn: () => any) => {
try {
return fn();
} catch (e) {
send(MessageFactory.uncaughtApplicationError({
name: e.name,
stack: e.stack,
message: e.message,
}));
send(
MessageFactory.uncaughtApplicationError({
name: e.name,
stack: e.stack,
message: e.message
})
);
}
};

Expand All @@ -131,7 +119,7 @@ const sendNgModulesMessage = () => {
const ngModulesMessage = {
names: parsedModulesData.names,
tokenIdMap: parsedModulesData.tokenIdMap,
configs: parsedModulesData.configs,
configs: parsedModulesData.configs
};
messageBuffer.enqueue(MessageFactory.ngModules(ngModulesMessage));
send(MessageFactory.push());
Expand All @@ -142,15 +130,15 @@ const updateComponentTree = async (roots: Array<any>, sendUpdates: boolean = tru
if (sendUpdates) {
if (previousTree == null || Math.abs(previousCount - count) > deltaThreshold) {
messageBuffer.enqueue(MessageFactory.completeTree(tree));
}
else {
} else {
const changes = previousTree.diff(tree);
if (changes.length > 0) {
lastTreeMessage = 'diff';
messageBuffer.enqueue(MessageFactory.treeDiff(changes));
}
else {
if (lastTreeMessage === 'no-diff') { return; }
} else {
if (lastTreeMessage === 'no-diff') {
return;
}
lastTreeMessage = 'no-diff';
messageBuffer.enqueue(MessageFactory.treeUnchanged());
}
Expand All @@ -165,18 +153,15 @@ const updateComponentTree = async (roots: Array<any>, sendUpdates: boolean = tru
highlighter.useComponentTreeInstance(previousTree);

previousCount = count;

};

const updateLazyLoadedNgModules = (routers): Promise<void> => {
return Promise.resolve().then(() => {

routers.forEach(router => {
parseModulesFromRouter(router, parsedModulesData);
});

sendNgModulesMessage();

});
};

Expand Down Expand Up @@ -206,14 +191,17 @@ const updateRouterTree = () => {
let ngModuleRef: NgModuleRef<any>;
let isStableSubscription: Subscription;

const collectRoots = () => getAllAngularRootElements().map(r => ng.probe(r)).filter(x => x !== null);
const collectRoots = () =>
getAllAngularRootElements()
.map(r => ng.probe(r))
.filter(x => x !== null);

const listenForSomeTimeAndMaybeResubscribe = (timeMs: number) => {
timer(CHECK_AFTER_NG_MODULE_DESTROY_RATE_MS, CHECK_AFTER_NG_MODULE_DESTROY_RATE_MS).pipe(
takeWhile((_, i) => !ngModuleRef && i < 100)
).forEach(() => {
resubscribe();
});
timer(CHECK_AFTER_NG_MODULE_DESTROY_RATE_MS, CHECK_AFTER_NG_MODULE_DESTROY_RATE_MS)
.pipe(takeWhile((_, i) => !ngModuleRef && i < 100))
.forEach(() => {
resubscribe();
});
};

const resubscribe = () => {
Expand All @@ -229,26 +217,39 @@ const resubscribe = () => {
parsedModulesData.tokenIdMap = {};

setTimeout(() => {
Promise.resolve().then(() => {
runAndHandleUncaughtExceptions(() => {
const roots = collectRoots();
if (roots.length) {
const appRef: ApplicationRef = parseModulesFromRootElement(roots[0], parsedModulesData);
if (isStableSubscription) { isStableSubscription.unsubscribe(); }
isStableSubscription = appRef.isStable.subscribe((e) => {
updateComponentTree(collectRoots());
updateRouterTree();
send(MessageFactory.ping());
});
ngModuleRef = (appRef as any)._injector;
ngModuleRef.onDestroy(() => {
ngModuleRef = undefined;
listenForSomeTimeAndMaybeResubscribe(1000);
});
sendNgModulesMessage();
}
});
})
Promise.resolve()
.then(() => {
runAndHandleUncaughtExceptions(() => {
const roots = collectRoots();
if (roots.length) {
let sanity;
// Adding sanity threshold to make sure
// larger app's doesn't get flooded
const sanityThreshold = 0.5 * 1000; // 0.5 seconds
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@feeloor Please add a comment explaining what is going on. Rest looks good to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sumitarora I've now added a few comments. 👍

const appRef: ApplicationRef = parseModulesFromRootElement(roots[0], parsedModulesData);
if (isStableSubscription) {
isStableSubscription.unsubscribe();
}
isStableSubscription = appRef.isStable
.pipe(
// Make sure sanity is undefined (initial run) or that sanitythreshold is passed
filter(() => sanity === undefined || new Date().getTime() - sanity > sanityThreshold)
)
.subscribe(e => {
sanity = new Date().getTime();
updateComponentTree(collectRoots());
updateRouterTree();
send(MessageFactory.ping());
});
ngModuleRef = (appRef as any)._injector;
ngModuleRef.onDestroy(() => {
ngModuleRef = undefined;
listenForSomeTimeAndMaybeResubscribe(1000);
});
sendNgModulesMessage();
}
});
})
.then(() =>
runAndHandleUncaughtExceptions(() => {
previousRoutes = null;
Expand All @@ -264,16 +265,12 @@ const selectedComponentPropertyKey = '$$el';

const noSelectedComponentWarningText = 'There is no component selected.';

Object.defineProperty(
window, selectedComponentPropertyKey,
{
value: noSelectedComponentWarningText,
configurable: true
}
);
Object.defineProperty(window, selectedComponentPropertyKey, {
value: noSelectedComponentWarningText,
configurable: true
});

const messageHandler = (message: Message<any>) => {

featureModulesPipe.handleIncomingMessage(message);

return runAndHandleUncaughtExceptions(() => {
Expand All @@ -289,10 +286,8 @@ const messageHandler = (message: Message<any>) => {
if (!isAngular()) {
send(MessageFactory.notNgApp());
} else if (!isDebugMode()) {
send(MessageFactory.applicationError(
new ApplicationError(ApplicationErrorType.ProductionMode)));
}
else {
send(MessageFactory.applicationError(new ApplicationError(ApplicationErrorType.ProductionMode)));
} else {
resubscribe();
}

Expand All @@ -315,28 +310,24 @@ const messageHandler = (message: Message<any>) => {
return;

case MessageType.UpdateProperty:
return updateProperty(previousTree,
message.content.path,
message.content.newValue);
return updateProperty(previousTree, message.content.path, message.content.newValue);

case MessageType.UpdateProviderProperty:
return updateProviderProperty(previousTree,
return updateProviderProperty(
previousTree,
message.content.path,
message.content.token,
message.content.propertyPath,
message.content.newValue);
message.content.newValue
);

case MessageType.EmitValue:
return emitValue(previousTree,
message.content.path,
message.content.value);

return emitValue(previousTree, message.content.path, message.content.value);
}
return undefined;
});
};


browserSubscribe(messageHandler);

// We do not store component instance properties on the node itself because
Expand Down Expand Up @@ -394,11 +385,9 @@ const emitValue = (tree: MutableTree, path: Path, newValue) => {
const emittable = instanceParent[path[path.length - 1]];
if (typeof emittable.emit === 'function') {
emittable.emit(newValue);
}
else if (typeof emittable.next === 'function') {
} else if (typeof emittable.next === 'function') {
emittable.next(newValue);
}
else {
} else {
throw new Error(`Cannot emit value for ${serializePath(path)}`);
}
});
Expand All @@ -412,11 +401,11 @@ export const routersFromRoots = () => {
const routers = [];

for (const element of collectRoots()) {
const routerFn = parameterTypes(element.componentInstance).reduce((prev, curr, idx, p) =>
prev ? prev : p[idx] !== null && p[idx].name === 'Router' ? p[idx] : null, null);
if (routerFn &&
element.componentInstance.router &&
element.componentInstance.router instanceof routerFn) {
const routerFn = parameterTypes(element.componentInstance).reduce(
(prev, curr, idx, p) => (prev ? prev : p[idx] !== null && p[idx].name === 'Router' ? p[idx] : null),
null
);
if (routerFn && element.componentInstance.router && element.componentInstance.router instanceof routerFn) {
routers.push(element.componentInstance.router);
}
}
Expand Down Expand Up @@ -512,12 +501,11 @@ export const applicationOperations = {
* The function references of the event handlers are stored on the global scope of backend.ts
* so we can remove them by reference.
*/
const findElement = (message) => {
let currentNode: Node,
currentHighlights: any;
const findElement = message => {
let currentNode: Node, currentHighlights: any;

if (message.content.start) {
onMouseOver = (e) => {
onMouseOver = e => {
if (currentHighlights) {
clearHighlights(currentHighlights.map);
}
Expand All @@ -544,6 +532,5 @@ const findElement = (message) => {
}
};


// add custom operations
extendWindowOperations(window || global || this, { inspectedApplication: applicationOperations });