mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
This reverts commit ebcdc8dc96.
PR Close #54629
This commit is contained in:
@@ -7,7 +7,6 @@ ng_module(
|
||||
srcs = ["devtools-app.component.ts"],
|
||||
deps = [
|
||||
"//devtools/projects/ng-devtools",
|
||||
"//devtools/projects/ng-devtools/src/lib:frame_manager",
|
||||
"//devtools/projects/protocol",
|
||||
"//devtools/src:iframe_message_bus",
|
||||
"//packages/common",
|
||||
|
||||
@@ -11,13 +11,11 @@ import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol';
|
||||
|
||||
import {IFrameMessageBus} from '../../../../../src/iframe-message-bus';
|
||||
import {DevToolsComponent} from 'ng-devtools';
|
||||
import {FrameManager} from '../../../../../projects/ng-devtools/src/lib/frame_manager';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [DevToolsComponent],
|
||||
providers: [
|
||||
{provide: FrameManager, useFactory: () => FrameManager.initialize(null)},
|
||||
{
|
||||
provide: MessageBus,
|
||||
useFactory(): MessageBus<Events> {
|
||||
|
||||
@@ -86,10 +86,6 @@ export const subscribeToClientEvents = (
|
||||
|
||||
messageBus.on('logProvider', logProvider);
|
||||
|
||||
messageBus.on('log', ({message, level}) => {
|
||||
console[level](`[Angular DevTools]: ${message}`);
|
||||
});
|
||||
|
||||
if (appIsAngularInDevMode() && appIsSupportedAngularVersion() && appIsAngularIvy()) {
|
||||
setupInspector(messageBus);
|
||||
// Often websites have `scroll` event listener which triggers
|
||||
@@ -220,8 +216,8 @@ const getRoutes = (messageBus: MessageBus<Events>) => {
|
||||
const checkForAngular = (messageBus: MessageBus<Events>): void => {
|
||||
const ngVersion = getAngularVersion();
|
||||
const appIsIvy = appIsAngularIvy();
|
||||
|
||||
if (!ngVersion) {
|
||||
setTimeout(() => checkForAngular(messageBus), 500);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
load("//devtools/tools:ng_module.bzl", "ng_module")
|
||||
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
|
||||
load("//devtools/tools:typescript.bzl", "ts_test_library")
|
||||
load("//devtools/tools:defaults.bzl", "karma_web_test_suite")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
@@ -14,18 +12,13 @@ ng_module(
|
||||
name = "lib",
|
||||
srcs = glob(
|
||||
include = ["*.ts"],
|
||||
exclude = [
|
||||
"theme-service.ts",
|
||||
"frame_manager.ts",
|
||||
"*_spec.ts",
|
||||
],
|
||||
exclude = ["theme-service.ts"],
|
||||
),
|
||||
angular_assets = [
|
||||
"devtools.component.html",
|
||||
":devtools_component_styles",
|
||||
],
|
||||
deps = [
|
||||
":frame_manager",
|
||||
":theme",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs",
|
||||
"//devtools/projects/protocol",
|
||||
@@ -41,58 +34,6 @@ ng_module(
|
||||
],
|
||||
)
|
||||
|
||||
ts_test_library(
|
||||
name = "devtools_test",
|
||||
srcs = ["devtools_spec.ts"],
|
||||
deps = [
|
||||
":frame_manager",
|
||||
":lib",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs",
|
||||
"//devtools/projects/protocol",
|
||||
"//packages/core",
|
||||
"//packages/core/testing",
|
||||
],
|
||||
)
|
||||
|
||||
karma_web_test_suite(
|
||||
name = "test",
|
||||
deps = [
|
||||
":devtools_test",
|
||||
],
|
||||
)
|
||||
|
||||
ng_module(
|
||||
name = "frame_manager",
|
||||
srcs = glob(
|
||||
include = ["frame_manager.ts"],
|
||||
),
|
||||
deps = [
|
||||
"//devtools/projects/ng-devtools/src/lib/application-environment",
|
||||
"//devtools/projects/protocol",
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
ts_test_library(
|
||||
name = "test_frame_manager_lib",
|
||||
srcs = [
|
||||
"frame_manager_spec.ts",
|
||||
],
|
||||
deps = [
|
||||
":frame_manager",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-environment",
|
||||
"//devtools/projects/protocol",
|
||||
"//packages/core/testing",
|
||||
],
|
||||
)
|
||||
|
||||
karma_web_test_suite(
|
||||
name = "test_frame_manager",
|
||||
deps = [
|
||||
":test_frame_manager_lib",
|
||||
],
|
||||
)
|
||||
|
||||
ng_module(
|
||||
name = "theme",
|
||||
srcs = glob(
|
||||
|
||||
@@ -15,15 +15,6 @@ export interface Environment {
|
||||
LATEST_SHA: string;
|
||||
}
|
||||
|
||||
export const TOP_LEVEL_FRAME_ID = 0;
|
||||
|
||||
export interface Frame {
|
||||
id: number;
|
||||
name: string;
|
||||
url: URL;
|
||||
}
|
||||
|
||||
export abstract class ApplicationEnvironment {
|
||||
abstract get environment(): Environment;
|
||||
abstract frameSelectorEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {DirectivePosition, ElementPosition} from 'protocol';
|
||||
|
||||
export abstract class ApplicationOperations {
|
||||
abstract viewSource(position: ElementPosition, directiveIndex?: number, target?: URL): void;
|
||||
abstract selectDomElement(position: ElementPosition, target?: URL): void;
|
||||
abstract inspect(directivePosition: DirectivePosition, objectPath: string[], target?: URL): void;
|
||||
abstract viewSource(position: ElementPosition, directiveIndex?: number): void;
|
||||
abstract selectDomElement(position: ElementPosition): void;
|
||||
abstract inspect(directivePosition: DirectivePosition, objectPath: string[]): void;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ ng_module(
|
||||
":devtools_tabs_component_styles",
|
||||
],
|
||||
deps = [
|
||||
"//devtools/projects/ng-devtools/src/lib:frame_manager",
|
||||
"//devtools/projects/ng-devtools/src/lib:theme",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-environment",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer",
|
||||
@@ -43,7 +42,6 @@ ts_test_library(
|
||||
srcs = ["devtools-tabs.spec.ts"],
|
||||
deps = [
|
||||
":devtools-tabs",
|
||||
"//devtools/projects/ng-devtools/src/lib:frame_manager",
|
||||
"//devtools/projects/ng-devtools/src/lib:theme",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-environment",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer",
|
||||
@@ -58,6 +56,7 @@ ts_test_library(
|
||||
],
|
||||
)
|
||||
|
||||
# todo(aleksanderbodurri): fix this test suite
|
||||
karma_web_test_suite(
|
||||
name = "test",
|
||||
deps = [
|
||||
@@ -66,3 +65,16 @@ karma_web_test_suite(
|
||||
"//packages/platform-browser/animations",
|
||||
],
|
||||
)
|
||||
|
||||
# spec_bundle(
|
||||
# name = "test_bundle",
|
||||
# deps = [
|
||||
# ":devtools_tabs_test",
|
||||
# "//packages/platform-browser/animations",
|
||||
# "//packages/animations",
|
||||
# "//packages/common/http",
|
||||
# "//packages/core",
|
||||
# "//packages/core/src/util",
|
||||
# ],
|
||||
# platform = "browser",
|
||||
# )
|
||||
|
||||
@@ -10,21 +10,6 @@
|
||||
<mat-icon> info </mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<select matTooltip="Select a frame to inspect with Angular Devtools" class="frame-selector" (change)="emitSelectedFrame($event.target.value)">
|
||||
@for (frame of frameManager.frames; track frame.id) {
|
||||
<option [value]="frame.id" [selected]="frameManager.isSelectedFrame(frame)">
|
||||
@if (frame.id === TOP_LEVEL_FRAME_ID) {
|
||||
top
|
||||
} @else {
|
||||
{{ frame.name }} ({{ frame.id }})
|
||||
}
|
||||
</option>
|
||||
} @empty {
|
||||
<option value="0" selected>top</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@for (tab of tabs; track $index) {
|
||||
<a class="mat-tab-link" mat-tab-link (click)="changeTab(tab)" [active]="activeTab === tab">
|
||||
{{ tab }}
|
||||
@@ -42,23 +27,19 @@
|
||||
</section>
|
||||
}
|
||||
</nav>
|
||||
|
||||
<mat-tab-nav-panel #tabPanel>
|
||||
@if (!applicationEnvironment.frameSelectorEnabled || frameManager.selectedFrame !== null) {
|
||||
<div class="tab-content">
|
||||
<ng-directive-explorer
|
||||
[showCommentNodes]="showCommentNodes"
|
||||
[isHydrationEnabled]="isHydrationEnabled"
|
||||
[class.hidden]="activeTab !== 'Components'"
|
||||
(toggleInspector)="toggleInspector()"
|
||||
/>
|
||||
<ng-profiler [class.hidden]="activeTab !== 'Profiler'"/>
|
||||
<ng-router-tree [routes]="routes" [class.hidden]="activeTab !== 'Router Tree'"/>
|
||||
<ng-injector-tree [class.hidden]="activeTab !== 'Injector Tree'"/>
|
||||
</div>
|
||||
}
|
||||
<div class="tab-content">
|
||||
<ng-directive-explorer
|
||||
[showCommentNodes]="showCommentNodes"
|
||||
[isHydrationEnabled]="isHydrationEnabled"
|
||||
[class.hidden]="activeTab !== 'Components'"
|
||||
(toggleInspector)="toggleInspector()"
|
||||
/>
|
||||
<ng-profiler [class.hidden]="activeTab !== 'Profiler'"/>
|
||||
<ng-router-tree [routes]="routes" [class.hidden]="activeTab !== 'Router Tree'"/>
|
||||
<ng-injector-tree [class.hidden]="activeTab !== 'Injector Tree'"/>
|
||||
</div>
|
||||
</mat-tab-nav-panel>
|
||||
|
||||
<mat-menu #menu="matMenu">
|
||||
<div mat-menu-item disableRipple (click)="$event.stopPropagation(); toggleTimingAPI()">
|
||||
<mat-slide-toggle [checked]="timingAPIEnabled">
|
||||
@@ -76,7 +57,6 @@
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #info="matMenu">
|
||||
<a mat-menu-item href="https://angular.io/devtools" target="_blank">
|
||||
<mat-icon>library_books</mat-icon>
|
||||
|
||||
@@ -119,22 +119,3 @@ mat-icon {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.frame-selector {
|
||||
background-color: #e2e2e2;
|
||||
border-radius: 2px;
|
||||
color: #474747;
|
||||
border: none;
|
||||
margin: 4px 4px 2px 4px;
|
||||
padding: 2px;
|
||||
outline-offset: -2px;
|
||||
width: 100px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:host-context(.dark-theme) {
|
||||
.frame-selector {
|
||||
background-color: #464646;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import {AfterViewInit, Component, Input, OnInit, ViewChild} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu';
|
||||
@@ -24,8 +15,7 @@ import {MatTabLink, MatTabNav, MatTabNavPanel} from '@angular/material/tabs';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {Events, MessageBus, Route} from 'protocol';
|
||||
|
||||
import {ApplicationEnvironment, Frame, TOP_LEVEL_FRAME_ID} from '../application-environment/index';
|
||||
import {FrameManager} from '../frame_manager';
|
||||
import {ApplicationEnvironment} from '../application-environment/index';
|
||||
import {Theme, ThemeService} from '../theme-service';
|
||||
|
||||
import {DirectiveExplorerComponent} from './directive-explorer/directive-explorer.component';
|
||||
@@ -62,28 +52,25 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
|
||||
@Input() angularVersion: string | undefined = undefined;
|
||||
@Input() isHydrationEnabled = false;
|
||||
|
||||
@Output() frameSelected = new EventEmitter<Frame>();
|
||||
@ViewChild(DirectiveExplorerComponent) directiveExplorer!: DirectiveExplorerComponent;
|
||||
@ViewChild('navBar', {static: true}) navbar!: MatTabNav;
|
||||
|
||||
applicationEnvironment = inject(ApplicationEnvironment);
|
||||
activeTab: Tabs = 'Components';
|
||||
|
||||
inspectorRunning = false;
|
||||
routerTreeEnabled = false;
|
||||
showCommentNodes = false;
|
||||
timingAPIEnabled = false;
|
||||
|
||||
currentTheme!: Theme;
|
||||
|
||||
routes: Route[] = [];
|
||||
|
||||
frameManager = inject(FrameManager);
|
||||
|
||||
TOP_LEVEL_FRAME_ID = TOP_LEVEL_FRAME_ID;
|
||||
|
||||
constructor(
|
||||
public tabUpdate: TabUpdate,
|
||||
public themeService: ThemeService,
|
||||
private _messageBus: MessageBus<Events>,
|
||||
private _applicationEnvironment: ApplicationEnvironment,
|
||||
) {
|
||||
this.themeService.currentTheme
|
||||
.pipe(takeUntilDestroyed())
|
||||
@@ -94,11 +81,6 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
|
||||
});
|
||||
}
|
||||
|
||||
emitSelectedFrame(frameId: string): void {
|
||||
const frame = this.frameManager.frames.find((frame) => frame.id === parseInt(frameId, 10));
|
||||
this.frameSelected.emit(frame);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.navbar.stretchTabs = false;
|
||||
}
|
||||
@@ -113,7 +95,7 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
get latestSHA(): string {
|
||||
return this.applicationEnvironment.environment.LATEST_SHA.slice(0, 8);
|
||||
return this._applicationEnvironment.environment.LATEST_SHA.slice(0, 8);
|
||||
}
|
||||
|
||||
changeTab(tab: Tabs): void {
|
||||
|
||||
@@ -19,7 +19,6 @@ import {Theme, ThemeService} from '../theme-service';
|
||||
import {DevToolsTabsComponent} from './devtools-tabs.component';
|
||||
import {TabUpdate} from './tab-update/index';
|
||||
import {DirectiveExplorerComponent} from './directive-explorer/directive-explorer.component';
|
||||
import {FrameManager} from '../frame_manager';
|
||||
|
||||
@Component({
|
||||
selector: 'ng-directive-explorer',
|
||||
@@ -37,7 +36,6 @@ describe('DevtoolsTabsComponent', () => {
|
||||
beforeEach(() => {
|
||||
messageBusMock = jasmine.createSpyObj('messageBus', ['on', 'once', 'emit', 'destroy']);
|
||||
applicationEnvironmentMock = jasmine.createSpyObj('applicationEnvironment', ['environment']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatTooltip, MatMenuModule, DevToolsTabsComponent],
|
||||
providers: [
|
||||
@@ -45,7 +43,6 @@ describe('DevtoolsTabsComponent', () => {
|
||||
{provide: ThemeService, useFactory: () => ({currentTheme: new Subject<Theme>()})},
|
||||
{provide: MessageBus, useValue: messageBusMock},
|
||||
{provide: ApplicationEnvironment, useValue: applicationEnvironmentMock},
|
||||
{provide: FrameManager, useFactory: () => FrameManager.initialize(123)},
|
||||
],
|
||||
}).overrideComponent(DevToolsTabsComponent, {
|
||||
remove: {imports: [DirectiveExplorerComponent]},
|
||||
@@ -77,24 +74,4 @@ describe('DevtoolsTabsComponent', () => {
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('inspectorEnd');
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('removeHighlightOverlay');
|
||||
});
|
||||
|
||||
it('should emit a selectedFrame when emitSelectedFrame is called', () => {
|
||||
let contentScriptConnected: Function = () => {};
|
||||
|
||||
// mock message bus on method with jasmine fake call in order to pick out callback
|
||||
// and call it with frame
|
||||
(messageBusMock.on as any).and.callFake((topic: string, cb: Function) => {
|
||||
if (topic === 'contentScriptConnected') {
|
||||
contentScriptConnected = cb;
|
||||
}
|
||||
});
|
||||
|
||||
const frameId = 1;
|
||||
expect(contentScriptConnected).toEqual(jasmine.any(Function));
|
||||
contentScriptConnected(frameId, 'name', 'http://localhost:4200/url');
|
||||
spyOn(comp.frameSelected, 'emit');
|
||||
comp.emitSelectedFrame('1');
|
||||
|
||||
expect(comp.frameSelected.emit).toHaveBeenCalledWith(comp.frameManager.frames[0]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,14 +43,10 @@ ts_test_library(
|
||||
srcs = ["directive-explorer.spec.ts"],
|
||||
deps = [
|
||||
":directive-explorer",
|
||||
"//devtools/projects/ng-devtools/src/lib:frame_manager",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-operations",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/tab-update",
|
||||
"//devtools/projects/protocol",
|
||||
"//packages/core",
|
||||
"//packages/core/testing",
|
||||
@@ -63,5 +59,8 @@ karma_web_test_suite(
|
||||
name = "test",
|
||||
deps = [
|
||||
":directive_explorer_test",
|
||||
"//packages/common/http",
|
||||
"//packages/platform-browser",
|
||||
"//packages/platform-browser/animations",
|
||||
],
|
||||
)
|
||||
|
||||
+12
-46
@@ -11,7 +11,6 @@ import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Input,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
@@ -33,7 +32,6 @@ import {
|
||||
|
||||
import {SplitComponent} from '../../../lib/vendor/angular-split/public_api';
|
||||
import {ApplicationOperations} from '../../application-operations/index';
|
||||
import {FrameManager} from '../../frame_manager';
|
||||
|
||||
import {BreadcrumbsComponent} from './directive-forest/breadcrumbs/breadcrumbs.component';
|
||||
import {FlatNode} from './directive-forest/component-data-source';
|
||||
@@ -124,12 +122,11 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
|
||||
private _refreshRetryTimeout: null | ReturnType<typeof setTimeout> = null;
|
||||
|
||||
constructor(
|
||||
private readonly _appOperations: ApplicationOperations,
|
||||
private readonly _messageBus: MessageBus<Events>,
|
||||
private readonly _propResolver: ElementPropertyResolver,
|
||||
private readonly _cdr: ChangeDetectorRef,
|
||||
private readonly _ngZone: NgZone,
|
||||
private readonly _frameManager: FrameManager,
|
||||
private _appOperations: ApplicationOperations,
|
||||
private _messageBus: MessageBus<Events>,
|
||||
private _propResolver: ElementPropertyResolver,
|
||||
private _cdr: ChangeDetectorRef,
|
||||
private _ngZone: NgZone,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -201,37 +198,18 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
|
||||
(directive) => directive.name === directiveName,
|
||||
);
|
||||
|
||||
const selectedFrame = this._frameManager.selectedFrame;
|
||||
if (!this._frameManager.frameHasUniqueUrl(selectedFrame)) {
|
||||
this._messageBus.emit('log', [
|
||||
{
|
||||
level: 'warn',
|
||||
message: `The currently inspected frame does not have a unique url on this page. Cannot view source.`,
|
||||
},
|
||||
]);
|
||||
if (directiveIndex === -1) {
|
||||
// view the component definition
|
||||
this._appOperations.viewSource(this.currentSelectedElement.position);
|
||||
return;
|
||||
}
|
||||
|
||||
this._appOperations.viewSource(
|
||||
this.currentSelectedElement.position,
|
||||
directiveIndex !== -1 ? directiveIndex : undefined,
|
||||
new URL(selectedFrame!.url),
|
||||
);
|
||||
// view the directive definition
|
||||
this._appOperations.viewSource(this.currentSelectedElement.position, directiveIndex);
|
||||
}
|
||||
|
||||
handleSelectDomElement(node: IndexedNode): void {
|
||||
const selectedFrame = this._frameManager.selectedFrame;
|
||||
if (!this._frameManager.frameHasUniqueUrl(selectedFrame)) {
|
||||
this._messageBus.emit('log', [
|
||||
{
|
||||
level: 'warn',
|
||||
message: `The currently inspected frame does not have a unique url on this page. Cannot select DOM element.`,
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
this._appOperations.selectDomElement(node.position, new URL(selectedFrame!.url));
|
||||
this._appOperations.selectDomElement(node.position);
|
||||
}
|
||||
|
||||
highlight(node: FlatNode): void {
|
||||
@@ -300,19 +278,7 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
|
||||
directivePosition: DirectivePosition;
|
||||
}): void {
|
||||
const objectPath = constructPathOfKeysToPropertyValue(node.prop);
|
||||
|
||||
const selectedFrame = this._frameManager.selectedFrame;
|
||||
if (!this._frameManager.frameHasUniqueUrl(selectedFrame)) {
|
||||
this._messageBus.emit('log', [
|
||||
{
|
||||
level: 'warn',
|
||||
message: `The currently inspected frame does not have a unique url on this page. Cannot inspect object.`,
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
this._appOperations.inspect(directivePosition, objectPath, new URL(selectedFrame!.url));
|
||||
this._appOperations.inspect(directivePosition, objectPath);
|
||||
}
|
||||
|
||||
hightlightHydrationNodes() {
|
||||
|
||||
+17
-238
@@ -6,94 +6,37 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {Events, MessageBus, PropertyQueryTypes} from 'protocol';
|
||||
|
||||
import {ApplicationOperations} from '../../application-operations';
|
||||
import {DirectivePosition, MessageBus, PropType, PropertyQueryTypes} from 'protocol';
|
||||
|
||||
import {DirectiveExplorerComponent} from './directive-explorer.component';
|
||||
import {DirectiveForestComponent} from './directive-forest/directive-forest.component';
|
||||
import {IndexedNode} from './directive-forest/index-forest';
|
||||
import {ElementPropertyResolver} from './property-resolver/element-property-resolver';
|
||||
|
||||
import SpyObj = jasmine.SpyObj;
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {FrameManager} from '../../frame_manager';
|
||||
import {TabUpdate} from '../tab-update';
|
||||
import {Component, EventEmitter, Input, Output, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {ElementPropertyResolver, FlatNode} from './property-resolver/element-property-resolver';
|
||||
import {BreadcrumbsComponent} from './directive-forest/breadcrumbs/breadcrumbs.component';
|
||||
import {PropertyTabComponent} from './property-tab/property-tab.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ng-directive-forest',
|
||||
template: '',
|
||||
standalone: true,
|
||||
})
|
||||
class MockDirectiveForestComponent {
|
||||
@Input() forest: IndexedNode[] = [];
|
||||
@Input() currentSelectedElement: IndexedNode | null = null;
|
||||
@Input() showCommentNodes = false;
|
||||
@Output() selectNode = new EventEmitter<IndexedNode>();
|
||||
@Output() selectDomElement = new EventEmitter<IndexedNode>();
|
||||
@Output() setParents = new EventEmitter<IndexedNode>();
|
||||
@Output() highlightComponent = new EventEmitter<IndexedNode>();
|
||||
@Output() removeComponentHighlight = new EventEmitter<void>();
|
||||
@Output() toggleInspector = new EventEmitter<void>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng-breadcrumbs',
|
||||
template: '',
|
||||
standalone: true,
|
||||
})
|
||||
class MockBreadcrumbsComponent {
|
||||
@Input() parents: IndexedNode[] = [];
|
||||
@Output() handleSelect = new EventEmitter<any>();
|
||||
@Output() mouseLeaveNode = new EventEmitter<any>();
|
||||
@Output() mouseOverNode = new EventEmitter<any>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng-property-tab',
|
||||
template: '',
|
||||
standalone: true,
|
||||
})
|
||||
class MockPropertyTabComponent {
|
||||
@Input() currentSelectedElement: IndexedNode | null = null;
|
||||
@Output() inspect = new EventEmitter<{node: FlatNode; directivePosition: DirectivePosition}>();
|
||||
@Output() viewSource = new EventEmitter<string>();
|
||||
}
|
||||
|
||||
describe('DirectiveExplorerComponent', () => {
|
||||
let messageBusMock: SpyObj<any>;
|
||||
let messageBusMock: SpyObj<MessageBus<Events>>;
|
||||
let fixture: ComponentFixture<DirectiveExplorerComponent>;
|
||||
let comp: DirectiveExplorerComponent;
|
||||
let applicationOperationsSpy: SpyObj<ApplicationOperations>;
|
||||
let contentScriptConnected = (frameId: number, name: string, url: string) => {};
|
||||
let frameConnected = (frameId: number) => {};
|
||||
|
||||
beforeEach(() => {
|
||||
applicationOperationsSpy = jasmine.createSpyObj<ApplicationOperations>('_appOperations', [
|
||||
'viewSource',
|
||||
'selectDomElement',
|
||||
'inspect',
|
||||
]);
|
||||
|
||||
messageBusMock = jasmine.createSpyObj('messageBus', ['on', 'once', 'emit', 'destroy']);
|
||||
|
||||
messageBusMock.on.and.callFake((topic: string, cb: Function) => {
|
||||
if (topic === 'contentScriptConnected') {
|
||||
contentScriptConnected = cb as (frameId: number, name: string, url: string) => void;
|
||||
}
|
||||
if (topic === 'frameConnected') {
|
||||
frameConnected = cb as (frameId: number) => void;
|
||||
}
|
||||
});
|
||||
messageBusMock.emit.and.callFake((topic: string, args: any[]) => {
|
||||
if (topic === 'enableFrameConnection') {
|
||||
frameConnected(args[0]);
|
||||
}
|
||||
});
|
||||
messageBusMock = jasmine.createSpyObj<MessageBus<Events>>('messageBus', [
|
||||
'on',
|
||||
'once',
|
||||
'emit',
|
||||
'destroy',
|
||||
]);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
@@ -103,24 +46,14 @@ describe('DirectiveExplorerComponent', () => {
|
||||
provide: ElementPropertyResolver,
|
||||
useValue: new ElementPropertyResolver(messageBusMock),
|
||||
},
|
||||
{provide: FrameManager, useFactory: () => FrameManager.initialize(123)},
|
||||
],
|
||||
}).overrideComponent(DirectiveExplorerComponent, {
|
||||
add: {schemas: [CUSTOM_ELEMENTS_SCHEMA]},
|
||||
remove: {imports: [DirectiveForestComponent]},
|
||||
});
|
||||
|
||||
fixture = TestBed.overrideComponent(DirectiveExplorerComponent, {
|
||||
remove: {imports: [DirectiveForestComponent, BreadcrumbsComponent, PropertyTabComponent]},
|
||||
add: {
|
||||
imports: [MockDirectiveForestComponent, MockBreadcrumbsComponent, MockPropertyTabComponent],
|
||||
},
|
||||
}).createComponent(DirectiveExplorerComponent);
|
||||
fixture = TestBed.createComponent(DirectiveExplorerComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
TestBed.inject(FrameManager);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create instance from class', () => {
|
||||
@@ -128,6 +61,8 @@ describe('DirectiveExplorerComponent', () => {
|
||||
});
|
||||
|
||||
it('subscribe to backend events', () => {
|
||||
comp.subscribeToBackendEvents();
|
||||
expect(messageBusMock.on).toHaveBeenCalledTimes(2);
|
||||
expect(messageBusMock.on).toHaveBeenCalledWith(
|
||||
'latestComponentExplorerView',
|
||||
jasmine.any(Function),
|
||||
@@ -138,9 +73,7 @@ describe('DirectiveExplorerComponent', () => {
|
||||
describe('refresh', () => {
|
||||
it('should emit getLatestComponentExplorerView event on refresh', () => {
|
||||
comp.refresh();
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('getLatestComponentExplorerView', [
|
||||
undefined,
|
||||
]);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should emit getLatestComponentExplorerView event with null view query', () => {
|
||||
@@ -177,6 +110,7 @@ describe('DirectiveExplorerComponent', () => {
|
||||
const position = [0];
|
||||
nodeMock.position = position;
|
||||
comp.handleNodeSelection(nodeMock);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledTimes(2);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('setSelectedComponent', [nodeMock.position]);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('getLatestComponentExplorerView', [
|
||||
{
|
||||
@@ -192,9 +126,11 @@ describe('DirectiveExplorerComponent', () => {
|
||||
describe('hydration', () => {
|
||||
it('should highlight hydration nodes', () => {
|
||||
comp.hightlightHydrationNodes();
|
||||
expect(messageBusMock.emit).toHaveBeenCalledTimes(1);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('createHydrationOverlay');
|
||||
|
||||
comp.removeHydrationNodesHightlights();
|
||||
expect(messageBusMock.emit).toHaveBeenCalledTimes(2);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('removeHydrationOverlay');
|
||||
});
|
||||
|
||||
@@ -210,161 +146,4 @@ describe('DirectiveExplorerComponent', () => {
|
||||
expect(toggle2).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('applicaton operations', () => {
|
||||
describe('view source', () => {
|
||||
it('should not call application operations view source if no frames are detected', () => {
|
||||
const directiveName = 'test';
|
||||
comp.currentSelectedElement = {
|
||||
directives: [{name: directiveName}],
|
||||
position: [0],
|
||||
children: [] as IndexedNode[],
|
||||
} as IndexedNode;
|
||||
comp.viewSource(directiveName);
|
||||
expect(applicationOperationsSpy.viewSource).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should not call application operations view source if a frame is selected that does not have a unique url on the page', () => {
|
||||
contentScriptConnected(0, 'test1', 'http://localhost:4200/url');
|
||||
contentScriptConnected(1, 'test2', 'http://localhost:4200/url');
|
||||
|
||||
const directiveName = 'test';
|
||||
comp.currentSelectedElement = {
|
||||
directives: [{name: directiveName}],
|
||||
position: [0],
|
||||
children: [] as IndexedNode[],
|
||||
} as IndexedNode;
|
||||
|
||||
comp.viewSource(directiveName);
|
||||
|
||||
expect(applicationOperationsSpy.viewSource).toHaveBeenCalledTimes(0);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('enableFrameConnection', [0, 123]);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('log', [
|
||||
{
|
||||
level: 'warn',
|
||||
message: `The currently inspected frame does not have a unique url on this page. Cannot view source.`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call application operations view source if a frame is selected that has a unique url on the page', () => {
|
||||
contentScriptConnected(0, 'test1', 'http://localhost:4200/url');
|
||||
contentScriptConnected(1, 'test2', 'http://localhost:4200/url2');
|
||||
|
||||
const directiveName = 'test';
|
||||
comp.currentSelectedElement = {
|
||||
directives: [{name: directiveName}],
|
||||
position: [0],
|
||||
children: [] as IndexedNode[],
|
||||
} as IndexedNode;
|
||||
|
||||
comp.viewSource(directiveName);
|
||||
|
||||
expect(applicationOperationsSpy.viewSource).toHaveBeenCalledTimes(1);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('enableFrameConnection', [0, 123]);
|
||||
expect(applicationOperationsSpy.viewSource).toHaveBeenCalledWith(
|
||||
[0], // current selected element position
|
||||
0, // directive index
|
||||
new URL('http://localhost:4200/url'), // selected frame url
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('select dom element', () => {
|
||||
it('should not call application operations select dom element if no frames are detected', () => {
|
||||
comp.handleSelectDomElement({position: [0], children: [] as IndexedNode[]} as IndexedNode);
|
||||
expect(applicationOperationsSpy.selectDomElement).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should not call application operations select dom element if a frame is selected that does not have a unique url on the page', () => {
|
||||
contentScriptConnected(0, 'test1', 'http://localhost:4200/url');
|
||||
contentScriptConnected(1, 'test2', 'http://localhost:4200/url');
|
||||
|
||||
comp.handleSelectDomElement({position: [0], children: [] as IndexedNode[]} as IndexedNode);
|
||||
|
||||
expect(applicationOperationsSpy.selectDomElement).toHaveBeenCalledTimes(0);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('enableFrameConnection', [0, 123]);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('log', [
|
||||
{
|
||||
level: 'warn',
|
||||
message: `The currently inspected frame does not have a unique url on this page. Cannot select DOM element.`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call application operations select dom element if a frame is selected that has a unique url on the page', () => {
|
||||
contentScriptConnected(0, 'test1', 'http://localhost:4200/url');
|
||||
contentScriptConnected(1, 'test2', 'http://localhost:4200/url2');
|
||||
|
||||
comp.handleSelectDomElement({position: [0], children: [] as IndexedNode[]} as IndexedNode);
|
||||
|
||||
expect(applicationOperationsSpy.selectDomElement).toHaveBeenCalledTimes(1);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('enableFrameConnection', [0, 123]);
|
||||
expect(applicationOperationsSpy.selectDomElement).toHaveBeenCalledWith(
|
||||
[0], // current selected element position
|
||||
new URL('http://localhost:4200/url'), // selected frame url
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspect', () => {
|
||||
let node: FlatNode;
|
||||
let directivePosition: DirectivePosition;
|
||||
|
||||
beforeEach(() => {
|
||||
node = {
|
||||
expandable: true,
|
||||
prop: {
|
||||
name: 'foo',
|
||||
parent: null,
|
||||
descriptor: {
|
||||
expandable: true,
|
||||
editable: false,
|
||||
type: PropType.String,
|
||||
preview: 'preview',
|
||||
containerType: null,
|
||||
},
|
||||
},
|
||||
level: 1,
|
||||
};
|
||||
directivePosition = {element: [0], directive: 0};
|
||||
});
|
||||
|
||||
it('should not call application operations inspect if no frames are detected', () => {
|
||||
comp.inspect({node, directivePosition});
|
||||
expect(applicationOperationsSpy.selectDomElement).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should not call application operations inspect if a frame is selected that does not have a unique url on the page', () => {
|
||||
contentScriptConnected(0, 'test1', 'http://localhost:4200/url');
|
||||
contentScriptConnected(1, 'test2', 'http://localhost:4200/url');
|
||||
|
||||
comp.inspect({node, directivePosition});
|
||||
|
||||
expect(applicationOperationsSpy.inspect).toHaveBeenCalledTimes(0);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('enableFrameConnection', [0, 123]);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('log', [
|
||||
{
|
||||
level: 'warn',
|
||||
message: `The currently inspected frame does not have a unique url on this page. Cannot inspect object.`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call application operations inspect if a frame is selected that has a unique url on the page', () => {
|
||||
contentScriptConnected(0, 'test1', 'http://localhost:4200/url');
|
||||
contentScriptConnected(1, 'test2', 'http://localhost:4200/url2');
|
||||
|
||||
comp.inspect({node, directivePosition});
|
||||
|
||||
expect(applicationOperationsSpy.inspect).toHaveBeenCalledTimes(1);
|
||||
expect(messageBusMock.emit).toHaveBeenCalledWith('enableFrameConnection', [0, 123]);
|
||||
expect(applicationOperationsSpy.inspect).toHaveBeenCalledWith(
|
||||
directivePosition,
|
||||
['foo'],
|
||||
new URL('http://localhost:4200/url'), // selected frame url
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
-2
@@ -47,8 +47,6 @@ ng_module(
|
||||
"property-tab-body.component.html",
|
||||
] + _STYLE_LABELS,
|
||||
deps = [
|
||||
"//devtools/projects/ng-devtools/src/lib:frame_manager",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-environment",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection:injector_tree_visualizer",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection:resolution_path",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest",
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
<mat-toolbar>
|
||||
{{ directive }}
|
||||
<button matTooltip="Open source" (click)="handleViewSource($event)">
|
||||
<mat-icon> code </mat-icon>
|
||||
</button>
|
||||
{{ directive }}
|
||||
<button matTooltip="Open source" (click)="handleViewSource($event)">
|
||||
<mat-icon> code </mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
@@ -1,57 +1,58 @@
|
||||
<div class="devtools mat-typography mat-app-background" style="height: 100%;">
|
||||
@switch (angularStatus) {
|
||||
@case (AngularStatus.EXISTS) {
|
||||
@if (angularIsInDevMode) {
|
||||
@if (supportedVersion) {
|
||||
<div class="devtools-wrapper noselect" [@enterAnimation]>
|
||||
<ng-devtools-tabs (frameSelected)="inspectFrame($event)" [isHydrationEnabled]="hydration" [angularVersion]="angularVersion"></ng-devtools-tabs>
|
||||
</div>
|
||||
} @else {
|
||||
<p class="text-message">
|
||||
Angular Devtools only supports Angular versions 12 and above
|
||||
</p>
|
||||
}
|
||||
<div class="mat-typography mat-app-background" style="height: 100%;">
|
||||
@if (angularExists === true) {
|
||||
@if (angularIsInDevMode) {
|
||||
@if (supportedVersion) {
|
||||
<div class="devtools-wrapper noselect" [@enterAnimation]>
|
||||
<ng-devtools-tabs [angularVersion]="angularVersion" [isHydrationEnabled]="hydration"/>
|
||||
</div>
|
||||
} @else {
|
||||
<p class="text-message" matTooltip="A dev build is when the `optimization` flag is set to `false` in the angular.json config file.">
|
||||
We detected an application built with production configuration. Angular DevTools only supports development build.
|
||||
<p class="text-message">
|
||||
Angular Devtools only supports Angular versions 12 and above
|
||||
</p>
|
||||
}
|
||||
}
|
||||
@case (AngularStatus.DOES_NOT_EXIST) {
|
||||
<p class="text-message not-detected">
|
||||
<span class="info-icon" matTooltip="You see this message because the app is still loading, or this is not an Angular application">i</span>
|
||||
Angular application not detected.
|
||||
} @else {
|
||||
<p class="text-message" title="A dev build is when the `optimization` flag is set to `true` in the angular.json config file.">
|
||||
We detected an application built with production configuration. Angular DevTools only supports development build.
|
||||
</p>
|
||||
}
|
||||
@case (AngularStatus.UNKNOWN) {
|
||||
<div class="initializing">
|
||||
<div class="loading">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 223 236" width="120">
|
||||
<g clip-path="url(#a)">
|
||||
<path fill="url(#b)" d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"></path>
|
||||
<path fill="url(#c)" d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"></path>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="b" x1="49.009" x2="225.829" y1="213.75" y2="129.722" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E40035"></stop>
|
||||
<stop offset=".24" stop-color="#F60A48"></stop>
|
||||
<stop offset=".352" stop-color="#F20755"></stop>
|
||||
<stop offset=".494" stop-color="#DC087D"></stop>
|
||||
<stop offset=".745" stop-color="#9717E7"></stop>
|
||||
<stop offset="1" stop-color="#6C00F5"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="41.025" x2="156.741" y1="28.344" y2="160.344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF31D9"></stop>
|
||||
<stop offset="1" stop-color="#FF5BE1" stop-opacity="0"></stop>
|
||||
</linearGradient>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h223v236H0z"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
@if (angularExists === false) {
|
||||
<p class="text-message">
|
||||
<span class="info-icon" matTooltip="You see this message because the app is still loading, or this is not an Angular application">i</span
|
||||
>
|
||||
Angular application not detected.
|
||||
</p>
|
||||
} @else {
|
||||
@if (angularExists === null) {
|
||||
<div class="initializing">
|
||||
<div class="loading">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 223 236" width="120">
|
||||
<g clip-path="url(#a)">
|
||||
<path fill="url(#b)" d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"></path>
|
||||
<path fill="url(#c)" d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"></path>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="b" x1="49.009" x2="225.829" y1="213.75" y2="129.722" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E40035"></stop>
|
||||
<stop offset=".24" stop-color="#F60A48"></stop>
|
||||
<stop offset=".352" stop-color="#F20755"></stop>
|
||||
<stop offset=".494" stop-color="#DC087D"></stop>
|
||||
<stop offset=".745" stop-color="#9717E7"></stop>
|
||||
<stop offset="1" stop-color="#6C00F5"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="41.025" x2="156.741" y1="28.344" y2="160.344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF31D9"></stop>
|
||||
<stop offset="1" stop-color="#FF5BE1" stop-opacity="0"></stop>
|
||||
</linearGradient>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h223v236H0z"></path>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,36 +9,13 @@
|
||||
import {animate, style, transition, trigger} from '@angular/animations';
|
||||
import {Platform} from '@angular/cdk/platform';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Component, inject, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, Inject, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Events, MessageBus} from 'protocol';
|
||||
import {interval} from 'rxjs';
|
||||
|
||||
import {FrameManager} from './frame_manager';
|
||||
import {ThemeService} from './theme-service';
|
||||
import {MatTooltip, MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {DevToolsTabsComponent} from './devtools-tabs/devtools-tabs.component';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
import {Frame} from './application-environment';
|
||||
|
||||
const DETECT_ANGULAR_ATTEMPTS = 10;
|
||||
|
||||
enum AngularStatus {
|
||||
/**
|
||||
* This page may have Angular but we don't know yet. We're still trying to detect it.
|
||||
*/
|
||||
UNKNOWN,
|
||||
|
||||
/**
|
||||
* We've given up on trying to detect Angular. We tried ${DETECT_ANGULAR_ATTEMPTS} times and
|
||||
* failed.
|
||||
*/
|
||||
DOES_NOT_EXIST,
|
||||
|
||||
/**
|
||||
* Angular was detected somewhere on the page.
|
||||
*/
|
||||
EXISTS,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng-devtools',
|
||||
@@ -51,11 +28,10 @@ enum AngularStatus {
|
||||
]),
|
||||
],
|
||||
standalone: true,
|
||||
imports: [DevToolsTabsComponent, MatTooltip, MatProgressSpinnerModule, MatTooltipModule],
|
||||
imports: [DevToolsTabsComponent, MatTooltip],
|
||||
})
|
||||
export class DevToolsComponent implements OnInit, OnDestroy {
|
||||
AngularStatus = AngularStatus;
|
||||
angularStatus: AngularStatus = AngularStatus.UNKNOWN;
|
||||
angularExists: boolean | null = null;
|
||||
angularVersion: string | boolean | undefined = undefined;
|
||||
angularIsInDevMode = true;
|
||||
hydration: boolean = false;
|
||||
@@ -63,28 +39,26 @@ export class DevToolsComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly _firefoxStyleName = 'firefox_styles.css';
|
||||
private readonly _chromeStyleName = 'chrome_styles.css';
|
||||
private readonly _messageBus = inject<MessageBus<Events>>(MessageBus);
|
||||
private readonly _themeService = inject(ThemeService);
|
||||
private readonly _platform = inject(Platform);
|
||||
private readonly _document = inject(DOCUMENT);
|
||||
private readonly _frameManager = inject(FrameManager);
|
||||
|
||||
constructor(
|
||||
private _messageBus: MessageBus<Events>,
|
||||
private _themeService: ThemeService,
|
||||
private _platform: Platform,
|
||||
@Inject(DOCUMENT) private _document: Document,
|
||||
) {}
|
||||
|
||||
private _interval$ = interval(500).subscribe((attempt) => {
|
||||
if (attempt === DETECT_ANGULAR_ATTEMPTS) {
|
||||
this.angularStatus = AngularStatus.DOES_NOT_EXIST;
|
||||
if (attempt === 10) {
|
||||
this.angularExists = false;
|
||||
}
|
||||
this._messageBus.emit('queryNgAvailability');
|
||||
});
|
||||
|
||||
inspectFrame(frame: Frame) {
|
||||
this._frameManager.inspectFrame(frame);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._themeService.initializeThemeWatcher();
|
||||
|
||||
this._messageBus.once('ngAvailability', ({version, devMode, ivy, hydration}) => {
|
||||
this.angularStatus = version ? AngularStatus.EXISTS : AngularStatus.DOES_NOT_EXIST;
|
||||
this.angularExists = !!version;
|
||||
this.angularVersion = version;
|
||||
this.angularIsInDevMode = devMode;
|
||||
this.ivy = ivy;
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {FrameManager} from './frame_manager';
|
||||
import {DevToolsComponent} from './devtools.component';
|
||||
import {DevToolsTabsComponent} from './devtools-tabs/devtools-tabs.component';
|
||||
import {MessageBus} from 'protocol';
|
||||
|
||||
@Component({
|
||||
selector: 'ng-devtools-tabs',
|
||||
template: '',
|
||||
standalone: true,
|
||||
})
|
||||
export class MockNgDevToolsTabs {}
|
||||
|
||||
describe('DevtoolsComponent', () => {
|
||||
let fixture: ComponentFixture<DevToolsComponent>;
|
||||
let component: DevToolsComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockMessageBus = jasmine.createSpyObj('MessageBus', ['on', 'emit', 'once']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: MessageBus, useValue: mockMessageBus}],
|
||||
}).overrideComponent(DevToolsComponent, {
|
||||
remove: {imports: [DevToolsTabsComponent], providers: [FrameManager]},
|
||||
add: {
|
||||
imports: [MockNgDevToolsTabs],
|
||||
providers: [{provide: FrameManager, useFactory: () => FrameManager.initialize(123)}],
|
||||
},
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(DevToolsComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render ng devtools tabs when Angular Status is EXISTS and is in dev mode and is supported version', () => {
|
||||
component.angularStatus = component.AngularStatus.EXISTS;
|
||||
component.angularIsInDevMode = true;
|
||||
component.angularVersion = '0.0.0';
|
||||
component.ivy = true;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('ng-devtools-tabs')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render Angular Devtools dev mode only support text when Angular Status is EXISTS and is angular is not in dev mode', () => {
|
||||
component.angularStatus = component.AngularStatus.EXISTS;
|
||||
component.angularIsInDevMode = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('.devtools').textContent).toContain(
|
||||
'We detected an application built with production configuration. Angular DevTools only supports development build.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render version support message when Angular Status is EXISTS and angular version is not supported', () => {
|
||||
component.angularStatus = component.AngularStatus.EXISTS;
|
||||
component.angularIsInDevMode = true;
|
||||
component.angularVersion = '1.0.0';
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('.devtools').textContent).toContain(
|
||||
'Angular Devtools only supports Angular versions 12 and above',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render Angular application not detected when Angular Status is DOES_NOT_EXIST', () => {
|
||||
component.angularStatus = component.AngularStatus.DOES_NOT_EXIST;
|
||||
fixture.detectChanges();
|
||||
// expect the text to be "Angular application not detected"
|
||||
expect(fixture.nativeElement.querySelector('.not-detected').textContent).toContain(
|
||||
'Angular application not detected',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render loading svg when Angular Status is UNKNOWN', () => {
|
||||
component.angularStatus = component.AngularStatus.UNKNOWN;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('.loading svg')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable, inject} from '@angular/core';
|
||||
import {Events, MessageBus} from 'protocol';
|
||||
|
||||
import {Frame, TOP_LEVEL_FRAME_ID} from './application-environment';
|
||||
|
||||
@Injectable()
|
||||
export class FrameManager {
|
||||
private _selectedFrameId: number | null = null;
|
||||
private _frames = new Map<number, Frame>();
|
||||
private _inspectedWindowTabId: number | null = null;
|
||||
private _frameUrlToFrameIds = new Map<string, Set<number>>();
|
||||
private _messageBus = inject<MessageBus<Events>>(MessageBus);
|
||||
|
||||
get frames(): Frame[] {
|
||||
return Array.from(this._frames.values());
|
||||
}
|
||||
|
||||
get selectedFrame(): Frame | null {
|
||||
if (this._selectedFrameId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._frames.get(this._selectedFrameId) ?? null;
|
||||
}
|
||||
|
||||
static initialize(inspectedWindowTabIdTestOnly?: number | null) {
|
||||
const manager = new FrameManager();
|
||||
manager.initialize(inspectedWindowTabIdTestOnly);
|
||||
return manager;
|
||||
}
|
||||
|
||||
private initialize(inspectedWindowTabIdTestOnly?: number | null): void {
|
||||
if (inspectedWindowTabIdTestOnly === undefined) {
|
||||
this._inspectedWindowTabId = globalThis.chrome.devtools.inspectedWindow.tabId;
|
||||
} else {
|
||||
this._inspectedWindowTabId = inspectedWindowTabIdTestOnly;
|
||||
}
|
||||
|
||||
this._messageBus.on('frameConnected', (frameId: number) => {
|
||||
if (this._frames.has(frameId)) {
|
||||
this._selectedFrameId = frameId;
|
||||
}
|
||||
});
|
||||
|
||||
this._messageBus.on('contentScriptConnected', (frameId: number, name: string, url: string) => {
|
||||
// fragments are not considered when doing URL matching on a page
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=841429
|
||||
const urlWithoutHash = new URL(url);
|
||||
urlWithoutHash.hash = '';
|
||||
|
||||
this.addFrame({name, id: frameId, url: urlWithoutHash});
|
||||
|
||||
if (this.frames.length === 1) {
|
||||
this.inspectFrame(this._frames.get(frameId)!);
|
||||
}
|
||||
});
|
||||
|
||||
this._messageBus.on('contentScriptDisconnected', (frameId: number) => {
|
||||
if (!this._frames.has(frameId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeFrame(this._frames.get(frameId)!);
|
||||
|
||||
// Defensive check. This case should never happen, since we're always connected to at least
|
||||
// the top level frame.
|
||||
if (this.frames.length === 0) {
|
||||
this._selectedFrameId = null;
|
||||
console.error('Angular DevTools is not connected to any frames.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (frameId === this._selectedFrameId) {
|
||||
this._selectedFrameId = TOP_LEVEL_FRAME_ID;
|
||||
this.inspectFrame(this._frames.get(this._selectedFrameId!)!);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isSelectedFrame(frame: Frame): boolean {
|
||||
return this._selectedFrameId === frame.id;
|
||||
}
|
||||
|
||||
inspectFrame(frame: Frame): void {
|
||||
if (this._inspectedWindowTabId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._frames.has(frame.id)) {
|
||||
throw new Error('Attempted to inspect a frame that is not connected to Angular DevTools.');
|
||||
}
|
||||
|
||||
this._selectedFrameId = null;
|
||||
this._messageBus.emit('enableFrameConnection', [frame.id, this._inspectedWindowTabId]);
|
||||
}
|
||||
|
||||
frameHasUniqueUrl(frame: Frame | null): boolean {
|
||||
if (frame === null) {
|
||||
return false;
|
||||
}
|
||||
const frameUrl = frame.url.toString();
|
||||
const frameIds = this._frameUrlToFrameIds.get(frameUrl) ?? new Set<number>();
|
||||
return frameIds.size === 1;
|
||||
}
|
||||
|
||||
private addFrame(frame: Frame): void {
|
||||
this._frames.set(frame.id, frame);
|
||||
const frameUrl = frame.url.toString();
|
||||
const frameIdSet = this._frameUrlToFrameIds.get(frameUrl) ?? new Set<number>();
|
||||
frameIdSet.add(frame.id);
|
||||
this._frameUrlToFrameIds.set(frameUrl, frameIdSet);
|
||||
}
|
||||
|
||||
private removeFrame(frame: Frame): void {
|
||||
const frameId = frame.id;
|
||||
const frameUrl = frame.url.toString();
|
||||
const urlFrameIds = this._frameUrlToFrameIds.get(frameUrl) ?? new Set<number>();
|
||||
urlFrameIds.delete(frameId);
|
||||
if (urlFrameIds.size === 0) {
|
||||
this._frameUrlToFrameIds.delete(frameUrl);
|
||||
}
|
||||
this._frames.delete(frameId);
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Events, MessageBus} from 'protocol';
|
||||
import {FrameManager} from './frame_manager';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {Frame} from './application-environment';
|
||||
|
||||
describe('FrameManager', () => {
|
||||
let frameManager: FrameManager;
|
||||
let messageBus: MessageBus<Events>;
|
||||
let topicToCallback: {[topic: string]: Function | null};
|
||||
|
||||
function getFrameFromFrameManager(frameId: number): Frame | undefined {
|
||||
return frameManager.frames.find((f: Frame) => f.id === frameId);
|
||||
}
|
||||
|
||||
function frameConnected(frameId: number): void {
|
||||
topicToCallback['frameConnected']!(frameId);
|
||||
}
|
||||
|
||||
function contentScriptConnected(frameId: number, name: string, url: string): void {
|
||||
topicToCallback['contentScriptConnected']!(frameId, name, url);
|
||||
}
|
||||
|
||||
function contentScriptDisconnected(frameId: number): void {
|
||||
topicToCallback['contentScriptDisconnected']!(frameId);
|
||||
}
|
||||
|
||||
const topLevelFrameId = 0;
|
||||
const otherFrameId = 1;
|
||||
const tabId = 123;
|
||||
|
||||
beforeEach(() => {
|
||||
topicToCallback = {
|
||||
frameConnected: null,
|
||||
contentScriptConnected: null,
|
||||
contentScriptDisconnected: null,
|
||||
};
|
||||
messageBus = jasmine.createSpyObj('MessageBus', ['on', 'emit']);
|
||||
|
||||
(messageBus.on as any).and.callFake((topic: string, cb: Function) => {
|
||||
topicToCallback[topic] = cb;
|
||||
});
|
||||
|
||||
(messageBus.emit as any).and.callFake((topic: string, args: any[]) => {
|
||||
if (topic === 'enableFrameConnection') {
|
||||
frameConnected(args[0]);
|
||||
}
|
||||
});
|
||||
|
||||
const testModule = TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: MessageBus, useValue: messageBus},
|
||||
{provide: FrameManager, useFactory: () => FrameManager.initialize(123)},
|
||||
],
|
||||
});
|
||||
|
||||
frameManager = testModule.inject(FrameManager);
|
||||
});
|
||||
|
||||
it('should add frame when contentScriptConnected event is emitted', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
expect(frameManager.frames.length).toBe(1);
|
||||
expect(frameManager.frames[0].id).toBe(topLevelFrameId);
|
||||
expect(frameManager.frames[0].name).toBe('name');
|
||||
expect(frameManager.frames[0].url.toString()).toBe('http://localhost:4200/url');
|
||||
});
|
||||
|
||||
it('should set the selected frame to the first frame when there is only one frame', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
expect(frameManager.selectedFrame?.id).toBe(topLevelFrameId);
|
||||
});
|
||||
|
||||
it('should set selected frame when frameConnected event is emitted', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'http://localhost:4200/url2');
|
||||
frameConnected(otherFrameId);
|
||||
expect(frameManager.selectedFrame?.id).toBe(otherFrameId);
|
||||
});
|
||||
|
||||
it('should remove frame when contentScriptDisconnected event is emitted', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'http://localhost:4200/url2');
|
||||
expect(frameManager.frames.length).toBe(2);
|
||||
contentScriptDisconnected(otherFrameId);
|
||||
expect(frameManager.frames.length).toBe(1);
|
||||
expect(frameManager.frames[0].id).toBe(topLevelFrameId);
|
||||
|
||||
const errorSpy = spyOn(console, 'error');
|
||||
contentScriptDisconnected(topLevelFrameId);
|
||||
expect(frameManager.frames.length).toBe(0);
|
||||
expect(errorSpy).toHaveBeenCalledWith('Angular DevTools is not connected to any frames.');
|
||||
});
|
||||
|
||||
it('should set selected frame to top level frame when contentScriptDisconnected event is emitted for selected frame', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'http://localhost:4200/url2');
|
||||
frameConnected(otherFrameId);
|
||||
expect(frameManager.selectedFrame?.id).toBe(otherFrameId);
|
||||
contentScriptDisconnected(otherFrameId);
|
||||
expect(frameManager.selectedFrame?.id).toBe(topLevelFrameId);
|
||||
});
|
||||
|
||||
it('should not set selected frame to top level frame when contentScriptDisconnected event is emitted for non selected frame', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'http://localhost:4200/url2');
|
||||
frameConnected(topLevelFrameId);
|
||||
expect(frameManager.selectedFrame?.id).toBe(topLevelFrameId);
|
||||
contentScriptDisconnected(otherFrameId);
|
||||
expect(frameManager.selectedFrame?.id).toBe(topLevelFrameId);
|
||||
});
|
||||
|
||||
it('should not set selected frame to top level frame when contentScriptDisconnected event is emitted for non existing frame', () => {
|
||||
const nonExistingFrameId = 3;
|
||||
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'http://localhost:4200/url2');
|
||||
frameConnected(otherFrameId);
|
||||
expect(frameManager.selectedFrame?.id).toBe(otherFrameId);
|
||||
contentScriptDisconnected(nonExistingFrameId);
|
||||
expect(frameManager.selectedFrame?.id).toBe(otherFrameId);
|
||||
});
|
||||
|
||||
it('isSelectedFrame should return true when frame matches selected frame', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'http://localhost:4200/url2');
|
||||
const topLevelFrame = getFrameFromFrameManager(topLevelFrameId);
|
||||
const otherFrame = getFrameFromFrameManager(otherFrameId);
|
||||
expect(topLevelFrame).toBeDefined();
|
||||
expect(otherFrame).toBeDefined();
|
||||
expect(frameManager.isSelectedFrame(topLevelFrame!)).toBe(true);
|
||||
});
|
||||
|
||||
it('isSelectedFrame should return false when frame does not match selected frame', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'http://localhost:4200/url2');
|
||||
const topLevelFrame = getFrameFromFrameManager(topLevelFrameId);
|
||||
const otherFrame = getFrameFromFrameManager(otherFrameId);
|
||||
expect(topLevelFrame).toBeDefined();
|
||||
expect(otherFrame).toBeDefined();
|
||||
expect(frameManager.isSelectedFrame(otherFrame!)).toBe(false);
|
||||
});
|
||||
|
||||
it('inspectFrame should emit enableFrameConnection message', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
const topLevelFrame = getFrameFromFrameManager(topLevelFrameId);
|
||||
expect(topLevelFrame).toBeDefined();
|
||||
frameManager.inspectFrame(topLevelFrame!);
|
||||
expect(messageBus.emit).toHaveBeenCalledWith('enableFrameConnection', [topLevelFrameId, tabId]);
|
||||
});
|
||||
|
||||
it('inspectFrame should set selected frame', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'http://localhost:4200/url');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'https://angular.dev/');
|
||||
const topLevelFrame = getFrameFromFrameManager(topLevelFrameId);
|
||||
expect(topLevelFrame).toBeDefined();
|
||||
frameManager.inspectFrame(topLevelFrame!);
|
||||
expect(frameManager.selectedFrame?.id).toBe(topLevelFrameId);
|
||||
});
|
||||
|
||||
it('frameHasUniqueUrl should return false when a two frames have the same url', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'https://angular.dev/');
|
||||
contentScriptConnected(otherFrameId, 'name2', 'https://angular.dev/');
|
||||
expect(frameManager.selectedFrame?.url.toString()).toBe('https://angular.dev/');
|
||||
expect(frameManager.frameHasUniqueUrl(frameManager.selectedFrame!)).toBe(false);
|
||||
});
|
||||
|
||||
it('frameHasUniqueUrl should return true when only one frame has a given url', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'https://angular.dev/');
|
||||
contentScriptConnected(otherFrameId, 'name', 'https://angular.dev/overview');
|
||||
expect(frameManager.selectedFrame?.url.toString()).toBe('https://angular.dev/');
|
||||
expect(frameManager.frameHasUniqueUrl(frameManager.selectedFrame!)).toBe(true);
|
||||
});
|
||||
|
||||
it('frameHasUniqueUrl should not consider url fragments as part of the url comparison', () => {
|
||||
contentScriptConnected(topLevelFrameId, 'name', 'https://angular.dev/guide/components');
|
||||
contentScriptConnected(
|
||||
otherFrameId,
|
||||
'name',
|
||||
'https://angular.dev/guide/components#using-components',
|
||||
);
|
||||
expect(frameManager.selectedFrame?.url.toString()).toBe('https://angular.dev/guide/components');
|
||||
expect(frameManager.frameHasUniqueUrl(frameManager.selectedFrame!)).toBe(false);
|
||||
});
|
||||
|
||||
it('frameHasUniqueUrl should return false when frame is null', () => {
|
||||
expect(frameManager.frameHasUniqueUrl(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -16,14 +16,12 @@ import {ChromeApplicationEnvironment} from './chrome-application-environment';
|
||||
import {ChromeApplicationOperations} from './chrome-application-operations';
|
||||
import {ZoneAwareChromeMessageBus} from './zone-aware-chrome-message-bus';
|
||||
import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol';
|
||||
import {FrameManager} from '../../../../projects/ng-devtools/src/lib/frame_manager';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserAnimationsModule, DevToolsComponent, MatSelect],
|
||||
bootstrap: [AppComponent],
|
||||
providers: [
|
||||
{provide: FrameManager, useFactory: () => FrameManager.initialize()},
|
||||
{
|
||||
provide: ApplicationOperations,
|
||||
useClass: ChromeApplicationOperations,
|
||||
|
||||
@@ -36,18 +36,18 @@ class MockPort {
|
||||
this.sender = properties.sender;
|
||||
}
|
||||
|
||||
postMessage(message: any): void {
|
||||
postMessage(message: any) {
|
||||
this.messagesPosted.push(message);
|
||||
}
|
||||
|
||||
onMessage = {
|
||||
addListener: (listener: Function): void => {
|
||||
addListener: (listener: Function) => {
|
||||
this.onMessageListeners.push(listener);
|
||||
},
|
||||
};
|
||||
|
||||
onDisconnect = {
|
||||
addListener: (listener: Function): void => {
|
||||
addListener: (listener: Function) => {
|
||||
this.onDisconnectListeners.push(listener);
|
||||
},
|
||||
};
|
||||
@@ -72,27 +72,22 @@ function mockSpyProperty(obj: any, property: string, value: any) {
|
||||
describe('Tab Manager - ', () => {
|
||||
let tabs: Tabs;
|
||||
const tabId = 12345;
|
||||
// let devtoolsPort: MockPort;
|
||||
let chromeRuntime: jasmine.SpyObj<typeof chrome.runtime>;
|
||||
let tabManager: TabManager;
|
||||
let tab: DevToolsConnection;
|
||||
let chromeRuntimeOnConnectListeners: ((port: MockPort) => void)[] = [];
|
||||
|
||||
function connectToChromeRuntime(port: MockPort): void {
|
||||
for (const listener of chromeRuntimeOnConnectListeners) {
|
||||
listener(port);
|
||||
}
|
||||
function connectToChromeRuntime(port: MockPort) {
|
||||
chromeRuntimeOnConnectListeners.forEach((listener) => listener(port));
|
||||
}
|
||||
|
||||
function emitMessageToPort(port: MockPort, message: any): void {
|
||||
for (const listener of port.onMessageListeners) {
|
||||
listener(message);
|
||||
}
|
||||
function emitMessageToPort(port: MockPort, message: any) {
|
||||
port.onMessageListeners.forEach((listener) => listener(message));
|
||||
}
|
||||
|
||||
function emitDisconnectToPort(port: MockPort) {
|
||||
for (const listener of port.onDisconnectListeners) {
|
||||
listener();
|
||||
}
|
||||
port.onDisconnectListeners.forEach((listener) => listener());
|
||||
}
|
||||
|
||||
function createDevToolsPort() {
|
||||
@@ -136,7 +131,6 @@ describe('Tab Manager - ', () => {
|
||||
},
|
||||
});
|
||||
connectToChromeRuntime(port);
|
||||
emitMessageToPort(port, {topic: 'backendReady'});
|
||||
return port;
|
||||
}
|
||||
|
||||
@@ -158,7 +152,7 @@ describe('Tab Manager - ', () => {
|
||||
});
|
||||
|
||||
it('should set frame connection as enabled when an enableFrameConnection message is recieved', () => {
|
||||
const contentScriptPort = createContentScriptPort();
|
||||
createContentScriptPort();
|
||||
const devtoolsPort = createDevToolsPort();
|
||||
tab = tabs[tabId]!;
|
||||
|
||||
@@ -198,27 +192,24 @@ describe('Tab Manager - ', () => {
|
||||
it('should not pipe messages from the content script and devtools script to each other when the content script frame is disabled', () => {
|
||||
const contentScriptPort = createContentScriptPort();
|
||||
const devtoolsPort = createDevToolsPort();
|
||||
tab = tabs[tabId]!;
|
||||
|
||||
expect(tab?.contentScripts[contentScriptFrameId]?.enabled).toBe(false);
|
||||
|
||||
emitMessageToPort(contentScriptPort, TEST_MESSAGE_ONE);
|
||||
assertArrayDoesNotHaveObj(contentScriptPort.messagesPosted, TEST_MESSAGE_ONE);
|
||||
expect(contentScriptPort.messagesPosted.length).toBe(0);
|
||||
|
||||
emitMessageToPort(devtoolsPort, TEST_MESSAGE_TWO);
|
||||
assertArrayDoesNotHaveObj(devtoolsPort.messagesPosted, TEST_MESSAGE_TWO);
|
||||
expect(devtoolsPort.messagesPosted.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should set backendReady when the contentPort recieves the backendReady message', () => {
|
||||
const contentScriptPort = createContentScriptPort();
|
||||
const devtoolsPort = createDevToolsPort();
|
||||
tab = tabs[tabId]!;
|
||||
|
||||
emitMessageToPort(devtoolsPort, {
|
||||
topic: 'enableFrameConnection',
|
||||
args: [contentScriptFrameId, tabId],
|
||||
});
|
||||
|
||||
emitMessageToPort(contentScriptPort, {topic: 'backendReady'});
|
||||
expect(tab?.contentScripts[contentScriptFrameId]?.backendReady).toBe(true);
|
||||
assertArrayHasObj(devtoolsPort.messagesPosted, {
|
||||
topic: 'contentScriptConnected',
|
||||
@@ -229,7 +220,6 @@ describe('Tab Manager - ', () => {
|
||||
it('should set tab.devtools to null when the devtoolsPort disconnects', () => {
|
||||
const contentScriptPort = createContentScriptPort();
|
||||
const devtoolsPort = createDevToolsPort();
|
||||
tab = tabs[tabId]!;
|
||||
|
||||
emitMessageToPort(devtoolsPort, {
|
||||
topic: 'enableFrameConnection',
|
||||
@@ -321,7 +311,6 @@ describe('Tab Manager - ', () => {
|
||||
const devtoolsPort = createDevToolsPort();
|
||||
createTopLevelContentScriptPort();
|
||||
createChildContentScriptPort();
|
||||
tab = tabs[tabId]!;
|
||||
|
||||
expect(tab?.contentScripts[topLevelFrameId]?.enabled).toBe(false);
|
||||
expect(tab?.contentScripts[childFrameId]?.enabled).toBe(false);
|
||||
|
||||
@@ -20,7 +20,6 @@ ng_module(
|
||||
],
|
||||
deps = [
|
||||
"//devtools/projects/ng-devtools",
|
||||
"//devtools/projects/ng-devtools/src/lib:frame_manager",
|
||||
"//devtools/projects/protocol",
|
||||
"//devtools/src:iframe_message_bus",
|
||||
"//packages/common",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Component, ElementRef, ViewChild} from '@angular/core';
|
||||
import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol';
|
||||
|
||||
import {IFrameMessageBus} from '../../iframe-message-bus';
|
||||
import {DevToolsComponent} from 'ng-devtools';
|
||||
@@ -14,6 +15,21 @@ import {DevToolsComponent} from 'ng-devtools';
|
||||
@Component({
|
||||
templateUrl: './devtools-app.component.html',
|
||||
styleUrls: ['./devtools-app.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: MessageBus,
|
||||
useFactory(): MessageBus<Events> {
|
||||
return new PriorityAwareMessageBus(
|
||||
new IFrameMessageBus(
|
||||
'angular-devtools',
|
||||
'angular-devtools-backend',
|
||||
// tslint:disable-next-line: no-non-null-assertion
|
||||
() => (document.querySelector('#sample-app') as HTMLIFrameElement).contentWindow!,
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
imports: [DevToolsComponent],
|
||||
})
|
||||
|
||||
@@ -11,9 +11,6 @@ import {NgModule} from '@angular/core';
|
||||
import {RouterModule} from '@angular/router';
|
||||
|
||||
import {AppDevToolsComponent} from './devtools-app.component';
|
||||
import {FrameManager} from '../../../projects/ng-devtools/src/lib/frame_manager';
|
||||
import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol';
|
||||
import {IFrameMessageBus} from '../../../src/iframe-message-bus';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -27,21 +24,5 @@ import {IFrameMessageBus} from '../../../src/iframe-message-bus';
|
||||
]),
|
||||
AppDevToolsComponent,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: MessageBus,
|
||||
useFactory(): MessageBus<Events> {
|
||||
return new PriorityAwareMessageBus(
|
||||
new IFrameMessageBus(
|
||||
'angular-devtools',
|
||||
'angular-devtools-backend',
|
||||
// tslint:disable-next-line: no-non-null-assertion
|
||||
() => (document.querySelector('#sample-app') as HTMLIFrameElement).contentWindow!,
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
{provide: FrameManager, useFactory: () => FrameManager.initialize(null)},
|
||||
],
|
||||
})
|
||||
export class DevToolsModule {}
|
||||
|
||||
@@ -11,7 +11,6 @@ import {ApplicationEnvironment, Environment} from 'ng-devtools';
|
||||
import {environment} from './environments/environment';
|
||||
|
||||
export class DemoApplicationEnvironment extends ApplicationEnvironment {
|
||||
frameSelectorEnabled = false;
|
||||
override get environment(): Environment {
|
||||
return environment;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user