Merge changes Ida110947,I8289c8b2

* changes:
  The great formatting
  Fix eslint errors
This commit is contained in:
Nataniel Borges
2023-01-01 10:06:10 +00:00
committed by Android (Google) Code Review
243 changed files with 8244 additions and 7453 deletions

View File

@@ -13,25 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let webpackConfig = require("./webpack.config.common");
const webpackConfig = require('./webpack.config.common');
delete webpackConfig.entry;
delete webpackConfig.output;
module.exports = (config) => {
config.set({
frameworks: ["jasmine", "webpack"],
plugins: [
"karma-webpack",
"karma-chrome-launcher",
"karma-jasmine",
"karma-sourcemap-loader",
],
files: [{ pattern: "src/main.component.spec.ts", watched: false }],
frameworks: ['jasmine', 'webpack'],
plugins: ['karma-webpack', 'karma-chrome-launcher', 'karma-jasmine', 'karma-sourcemap-loader'],
files: [{pattern: 'src/main.component.spec.ts', watched: false}],
preprocessors: {
'src/main.component.spec.ts': ['webpack', 'sourcemap']
'src/main.component.spec.ts': ['webpack', 'sourcemap'],
},
singleRun: true,
browsers: ["ChromeHeadless"],
webpack: webpackConfig
browsers: ['ChromeHeadless'],
webpack: webpackConfig,
});
}
};

View File

@@ -21,16 +21,16 @@
// and change the hardcoded version here
exports.config = {
specs: ["dist/e2e.spec/e2e/*.spec.js"],
specs: ['dist/e2e.spec/e2e/*.spec.js'],
directConnect: true,
capabilities: {
browserName: "chrome",
browserName: 'chrome',
chromeOptions: {
args: ["--headless", "--disable-gpu", "--window-size=1280x1024"]
}
args: ['--headless', '--disable-gpu', '--window-size=1280x1024'],
},
},
chromeDriver: "./node_modules/webdriver-manager/selenium/chromedriver_108.0.5359.71",
chromeDriver: './node_modules/webdriver-manager/selenium/chromedriver_108.0.5359.71',
allScriptsTimeout: 10000,
getPageTimeout: 10000,
@@ -39,11 +39,11 @@ exports.config = {
defaultTimeoutInterval: 10000,
},
onPrepare: function() {
onPrepare: function () {
// allow specifying the file protocol within browser.get(...)
browser.ignoreSynchronization = true;
browser.waitForAngular();
browser.sleep(500);
browser.resetUrl = "file:///";
}
browser.resetUrl = 'file:///';
},
};

View File

@@ -14,21 +14,18 @@
* limitations under the License.
*/
import {
MessageType,
OpenBuganizerResponse,
OpenRequest,
WebCommandMessage} from "./messages";
import {FunctionUtils} from "common/utils/function_utils";
import {FunctionUtils} from 'common/utils/function_utils';
import {
BuganizerAttachmentsDownloadEmitter,
OnBuganizerAttachmentsDownloaded,
OnBuganizerAttachmentsDownloadStart,
OnBuganizerAttachmentsDownloaded
} from "interfaces/buganizer_attachments_download_emitter";
} from 'interfaces/buganizer_attachments_download_emitter';
import {MessageType, OpenBuganizerResponse, OpenRequest, WebCommandMessage} from './messages';
export class AbtChromeExtensionProtocol implements BuganizerAttachmentsDownloadEmitter {
static readonly ABT_EXTENSION_ID = "mbbaofdfoekifkfpgehgffcpagbbjkmj";
private onAttachmentsDownloadStart: OnBuganizerAttachmentsDownloadStart = FunctionUtils.DO_NOTHING;
static readonly ABT_EXTENSION_ID = 'mbbaofdfoekifkfpgehgffcpagbbjkmj';
private onAttachmentsDownloadStart: OnBuganizerAttachmentsDownloadStart =
FunctionUtils.DO_NOTHING;
private onttachmentsDownloaded: OnBuganizerAttachmentsDownloaded = FunctionUtils.DO_NOTHING_ASYNC;
setOnBuganizerAttachmentsDownloadStart(callback: OnBuganizerAttachmentsDownloadStart) {
@@ -41,14 +38,14 @@ export class AbtChromeExtensionProtocol implements BuganizerAttachmentsDownloadE
run() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("source") !== "openFromExtension" || !chrome) {
if (urlParams.get('source') !== 'openFromExtension' || !chrome) {
return;
}
this.onAttachmentsDownloadStart();
const openRequestMessage: OpenRequest = {
action: MessageType.OPEN_REQUEST
action: MessageType.OPEN_REQUEST,
};
chrome.runtime.sendMessage(
@@ -62,20 +59,18 @@ export class AbtChromeExtensionProtocol implements BuganizerAttachmentsDownloadE
if (this.isOpenBuganizerResponseMessage(message)) {
await this.onOpenBuganizerResponseMessageReceived(message);
} else {
console.warn("ABT chrome extension protocol received unexpected message:", message);
console.warn('ABT chrome extension protocol received unexpected message:', message);
}
}
private async onOpenBuganizerResponseMessageReceived(message: OpenBuganizerResponse) {
console.log(
"ABT chrome extension protocol received OpenBuganizerResponse message:", message
);
console.log('ABT chrome extension protocol received OpenBuganizerResponse message:', message);
if (message.attachments.length === 0) {
console.warn("ABT chrome extension protocol received no attachments");
console.warn('ABT chrome extension protocol received no attachments');
}
const filesBlobPromises = message.attachments.map(async attachment => {
const filesBlobPromises = message.attachments.map(async (attachment) => {
const fileQueryResponse = await fetch(attachment.objectUrl);
const blob = await fileQueryResponse.blob();
@@ -91,8 +86,9 @@ export class AbtChromeExtensionProtocol implements BuganizerAttachmentsDownloadE
await this.onttachmentsDownloaded(files);
}
private isOpenBuganizerResponseMessage(message: WebCommandMessage):
message is OpenBuganizerResponse {
private isOpenBuganizerResponseMessage(
message: WebCommandMessage
): message is OpenBuganizerResponse {
return message.action === MessageType.OPEN_BUGANIZER_RESPONSE;
}
}

View File

@@ -14,19 +14,21 @@
* limitations under the License.
*/
import {FunctionUtils} from "common/utils/function_utils";
import {FunctionUtils} from 'common/utils/function_utils';
import {
BuganizerAttachmentsDownloadEmitter,
OnBuganizerAttachmentsDownloaded,
OnBuganizerAttachmentsDownloadStart,
OnBuganizerAttachmentsDownloaded
} from "interfaces/buganizer_attachments_download_emitter";
import {Runnable} from "interfaces/runnable";
} from 'interfaces/buganizer_attachments_download_emitter';
import {Runnable} from 'interfaces/runnable';
export class AbtChromeExtensionProtocolStub implements
BuganizerAttachmentsDownloadEmitter,
Runnable {
onBuganizerAttachmentsDownloadStart: OnBuganizerAttachmentsDownloadStart = FunctionUtils.DO_NOTHING;
onBuganizerAttachmentsDownloaded: OnBuganizerAttachmentsDownloaded = FunctionUtils.DO_NOTHING_ASYNC;
export class AbtChromeExtensionProtocolStub
implements BuganizerAttachmentsDownloadEmitter, Runnable
{
onBuganizerAttachmentsDownloadStart: OnBuganizerAttachmentsDownloadStart =
FunctionUtils.DO_NOTHING;
onBuganizerAttachmentsDownloaded: OnBuganizerAttachmentsDownloaded =
FunctionUtils.DO_NOTHING_ASYNC;
setOnBuganizerAttachmentsDownloadStart(callback: OnBuganizerAttachmentsDownloadStart) {
this.onBuganizerAttachmentsDownloadStart = callback;

View File

@@ -52,10 +52,10 @@ export declare interface OpenBuganizerResponse extends WebCommandMessage {
issueId: string;
/** issue title */
issueTitle: string|undefined;
issueTitle: string | undefined;
/** issue access level */
issueAccessLevel: IssueAccessLimit|undefined;
issueAccessLevel: IssueAccessLimit | undefined;
/** Attachment list. */
attachments: AttachmentMetadata[];
@@ -96,9 +96,9 @@ export interface BugReportMetadata {
* http://go/buganizer/concepts/access-control#accesslimit
*/
export const enum IssueAccessLimit {
INTERNAL = "",
VISIBLE_TO_PARTNERS = "Visible to Partners",
VISIBLE_TO_PUBLIC = "Visible to Public",
INTERNAL = '',
VISIBLE_TO_PARTNERS = 'Visible to Partners',
VISIBLE_TO_PUBLIC = 'Visible to Public',
}
/**

View File

@@ -14,66 +14,68 @@
* limitations under the License.
*/
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { ScrollingModule } from "@angular/cdk/scrolling";
import { CommonModule } from "@angular/common";
import { MatCardModule } from "@angular/material/card";
import { MatButtonModule } from "@angular/material/button";
import { MatGridListModule } from "@angular/material/grid-list";
import { MatListModule } from "@angular/material/list";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { MatCheckboxModule } from "@angular/material/checkbox";
import { MatDividerModule } from "@angular/material/divider";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatIconModule } from "@angular/material/icon";
import { MatInputModule } from "@angular/material/input";
import { MatSelectModule } from "@angular/material/select";
import { MatRadioModule } from "@angular/material/radio";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { HttpClientModule } from "@angular/common/http";
import { MatSliderModule } from "@angular/material/slider";
import { MatTooltipModule } from "@angular/material/tooltip";
import { MatToolbarModule } from "@angular/material/toolbar";
import { MatTabsModule } from "@angular/material/tabs";
import { MatSnackBarModule } from "@angular/material/snack-bar";
import { AdbProxyComponent } from "./components/adb_proxy.component";
import { AppComponent } from "./components/app.component";
import { CollectTracesComponent } from "./components/collect_traces.component";
import { LoadProgressComponent } from "./components/load_progress.component";
import { ParserErrorSnackBarComponent } from "./components/parser_error_snack_bar_component";
import { TraceConfigComponent } from "./components/trace_config.component";
import { TraceViewComponent } from "./components/trace_view.component";
import { UploadTracesComponent } from "./components/upload_traces.component";
import { WebAdbComponent } from "./components/web_adb.component";
import { CoordinatesTableComponent } from "viewers/components/coordinates_table.component";
import { HierarchyComponent } from "viewers/components/hierarchy.component";
import { ImeAdditionalPropertiesComponent } from "viewers/components/ime_additional_properties.component";
import { PropertiesComponent } from "viewers/components/properties.component";
import { PropertiesTableComponent } from "viewers/components/properties_table.component";
import { PropertyGroupsComponent } from "viewers/components/property_groups.component";
import { RectsComponent } from "viewers/components/rects/rects.component";
import { TransformMatrixComponent } from "viewers/components/transform_matrix.component";
import { TreeComponent } from "viewers/components/tree.component";
import { TreeNodeComponent } from "viewers/components/tree_node.component";
import { TreeNodeDataViewComponent } from "viewers/components/tree_node_data_view.component";
import { TreeNodePropertiesDataViewComponent } from "viewers/components/tree_node_properties_data_view.component";
import { ViewerInputMethodComponent } from "viewers/components/viewer_input_method.component";
import { ViewerProtologComponent} from "viewers/viewer_protolog/viewer_protolog.component";
import { ViewerScreenRecordingComponent } from "viewers/viewer_screen_recording/viewer_screen_recording.component";
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
import { ViewerTransactionsComponent } from "viewers/viewer_transactions/viewer_transactions.component";
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
import { TimelineComponent } from "./components/timeline/timeline.component";
import { MiniTimelineComponent } from "./components/timeline/mini_timeline.component";
import { ExpandedTimelineComponent } from "./components/timeline/expanded_timeline.component";
import { SingleTimelineComponent } from "./components/timeline/single_timeline.component";
import { MatDrawerContent, MatDrawer, MatDrawerContainer } from "./components/bottomnav/bottom_drawer.component";
import {DragDropModule} from '@angular/cdk/drag-drop';
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common';
import {HttpClientModule} from '@angular/common/http';
import {NgModule} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatDividerModule} from '@angular/material/divider';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatGridListModule} from '@angular/material/grid-list';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatListModule} from '@angular/material/list';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatRadioModule} from '@angular/material/radio';
import {MatSelectModule} from '@angular/material/select';
import {MatSliderModule} from '@angular/material/slider';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatTabsModule} from '@angular/material/tabs';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {CoordinatesTableComponent} from 'viewers/components/coordinates_table.component';
import {HierarchyComponent} from 'viewers/components/hierarchy.component';
import {ImeAdditionalPropertiesComponent} from 'viewers/components/ime_additional_properties.component';
import {PropertiesComponent} from 'viewers/components/properties.component';
import {PropertiesTableComponent} from 'viewers/components/properties_table.component';
import {PropertyGroupsComponent} from 'viewers/components/property_groups.component';
import {RectsComponent} from 'viewers/components/rects/rects.component';
import {TransformMatrixComponent} from 'viewers/components/transform_matrix.component';
import {TreeComponent} from 'viewers/components/tree.component';
import {TreeNodeComponent} from 'viewers/components/tree_node.component';
import {TreeNodeDataViewComponent} from 'viewers/components/tree_node_data_view.component';
import {TreeNodePropertiesDataViewComponent} from 'viewers/components/tree_node_properties_data_view.component';
import {ViewerInputMethodComponent} from 'viewers/components/viewer_input_method.component';
import {ViewerProtologComponent} from 'viewers/viewer_protolog/viewer_protolog.component';
import {ViewerScreenRecordingComponent} from 'viewers/viewer_screen_recording/viewer_screen_recording.component';
import {ViewerSurfaceFlingerComponent} from 'viewers/viewer_surface_flinger/viewer_surface_flinger.component';
import {ViewerTransactionsComponent} from 'viewers/viewer_transactions/viewer_transactions.component';
import {ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager.component';
import {AdbProxyComponent} from './components/adb_proxy.component';
import {AppComponent} from './components/app.component';
import {
MatDrawer,
MatDrawerContainer,
MatDrawerContent,
} from './components/bottomnav/bottom_drawer.component';
import {CollectTracesComponent} from './components/collect_traces.component';
import {LoadProgressComponent} from './components/load_progress.component';
import {ParserErrorSnackBarComponent} from './components/parser_error_snack_bar_component';
import {ExpandedTimelineComponent} from './components/timeline/expanded_timeline.component';
import {MiniTimelineComponent} from './components/timeline/mini_timeline.component';
import {SingleTimelineComponent} from './components/timeline/single_timeline.component';
import {TimelineComponent} from './components/timeline/timeline.component';
import {TraceConfigComponent} from './components/trace_config.component';
import {TraceViewComponent} from './components/trace_view.component';
import {UploadTracesComponent} from './components/upload_traces.component';
import {WebAdbComponent} from './components/web_adb.component';
@NgModule({
declarations: [
@@ -110,7 +112,7 @@ import { MatDrawerContent, MatDrawer, MatDrawerContainer } from "./components/bo
MatDrawer,
MatDrawerContent,
MatDrawerContainer,
LoadProgressComponent
LoadProgressComponent,
],
imports: [
BrowserModule,
@@ -141,6 +143,6 @@ import { MatDrawerContent, MatDrawer, MatDrawerContainer } from "./components/bo
DragDropModule,
ReactiveFormsModule,
],
bootstrap: [AppComponent]
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

@@ -15,10 +15,10 @@
*/
export enum Color {
SELECTOR_COLOR = "#8AB4F8",
SELECTION_BACKGROUND = "#E8F0FE",
ACTIVE_POINTER = "#1967D2",
GUIDE_BAR = "#3C4043",
GUIDE_BAR_LIGHT = "#9AA0A6",
SELECTOR_COLOR = '#8AB4F8',
SELECTION_BACKGROUND = '#E8F0FE',
ACTIVE_POINTER = '#1967D2',
GUIDE_BAR = '#3C4043',
GUIDE_BAR_LIGHT = '#9AA0A6',
ACTIVE_BORDER = ACTIVE_POINTER,
}
}

View File

@@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { AdbProxyComponent } from "./adb_proxy.component";
import { proxyClient, ProxyState } from "trace_collection/proxy_client";
import { MatIconModule } from "@angular/material/icon";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { MatButtonModule } from "@angular/material/button";
import { NO_ERRORS_SCHEMA } from "@angular/core";
import {CommonModule} from '@angular/common';
import {NO_ERRORS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MatButtonModule} from '@angular/material/button';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {proxyClient, ProxyState} from 'trace_collection/proxy_client';
import {AdbProxyComponent} from './adb_proxy.component';
describe("AdbProxyComponent", () => {
describe('AdbProxyComponent', () => {
let fixture: ComponentFixture<AdbProxyComponent>;
let component: AdbProxyComponent;
let htmlElement: HTMLElement;
@@ -37,10 +37,10 @@ describe("AdbProxyComponent", () => {
MatFormFieldModule,
MatInputModule,
BrowserAnimationsModule,
MatButtonModule
MatButtonModule,
],
declarations: [AdbProxyComponent],
schemas: [NO_ERRORS_SCHEMA]
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
fixture = TestBed.createComponent(AdbProxyComponent);
component = fixture.componentInstance;
@@ -48,37 +48,41 @@ describe("AdbProxyComponent", () => {
htmlElement = fixture.nativeElement;
});
it("can be created", () => {
it('can be created', () => {
expect(component).toBeTruthy();
});
it("check correct icon and message displays if no proxy", () => {
it('check correct icon and message displays if no proxy', () => {
component.proxy.setState(ProxyState.NO_PROXY);
fixture.detectChanges();
expect(htmlElement.querySelector(".further-adb-info-text")?.innerHTML).toContain("Launch the Winscope ADB Connect proxy");
expect(htmlElement.querySelector('.further-adb-info-text')?.innerHTML).toContain(
'Launch the Winscope ADB Connect proxy'
);
});
it("check correct icon and message displays if invalid proxy", () => {
it('check correct icon and message displays if invalid proxy', () => {
component.proxy.setState(ProxyState.INVALID_VERSION);
fixture.detectChanges();
expect(htmlElement.querySelector(".adb-info")?.innerHTML).toBe("Your local proxy version is incompatible with Winscope.");
expect(htmlElement.querySelector(".adb-icon")?.innerHTML).toBe("update");
expect(htmlElement.querySelector('.adb-info')?.innerHTML).toBe(
'Your local proxy version is incompatible with Winscope.'
);
expect(htmlElement.querySelector('.adb-icon')?.innerHTML).toBe('update');
});
it("check correct icon and message displays if unauthorised proxy", () => {
it('check correct icon and message displays if unauthorised proxy', () => {
component.proxy.setState(ProxyState.UNAUTH);
fixture.detectChanges();
expect(htmlElement.querySelector(".adb-info")?.innerHTML).toBe("Proxy authorisation required.");
expect(htmlElement.querySelector(".adb-icon")?.innerHTML).toBe("lock");
expect(htmlElement.querySelector('.adb-info')?.innerHTML).toBe('Proxy authorisation required.');
expect(htmlElement.querySelector('.adb-icon')?.innerHTML).toBe('lock');
});
it("check retry button acts as expected", async () => {
it('check retry button acts as expected', async () => {
component.proxy.setState(ProxyState.NO_PROXY);
fixture.detectChanges();
spyOn(component, "restart").and.callThrough();
const button: HTMLButtonElement | null = htmlElement.querySelector(".retry");
spyOn(component, 'restart').and.callThrough();
const button: HTMLButtonElement | null = htmlElement.querySelector('.retry');
expect(button).toBeInstanceOf(HTMLButtonElement);
button?.dispatchEvent(new Event("click"));
button?.dispatchEvent(new Event('click'));
await fixture.whenStable();
expect(component.restart).toHaveBeenCalled();
});

View File

@@ -13,24 +13,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { proxyClient, ProxyClient, ProxyState } from "trace_collection/proxy_client";
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {proxyClient, ProxyClient, ProxyState} from 'trace_collection/proxy_client';
@Component({
selector: "adb-proxy",
selector: 'adb-proxy',
template: `
<ng-container [ngSwitch]="proxy.state">
<ng-container *ngSwitchCase="states.NO_PROXY">
<div class="further-adb-info-text">
<p class="mat-body-1">Launch the Winscope ADB Connect proxy to capture traces directly from your browser.</p>
<p class="mat-body-1">
Launch the Winscope ADB Connect proxy to capture traces directly from your browser.
</p>
<p class="mat-body-1">Python 3.5+ and ADB are required.</p>
<p class="mat-body-1">Run: <code>python3 $ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py</code></p>
<p class="mat-body-1">
Run:
<code>
python3 $ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py
</code>
</p>
<p class="mat-body-1">Or get it from the AOSP repository.</p>
</div>
<div class="further-adb-info-actions">
<button color="primary" mat-stroked-button (click)="downloadFromAosp()">Download from AOSP</button>
<button color="primary" mat-stroked-button class="retry" (click)="restart()">Retry</button>
<button color="primary" mat-stroked-button (click)="downloadFromAosp()">
Download from AOSP
</button>
<button color="primary" mat-stroked-button class="retry" (click)="restart()">
Retry
</button>
</div>
</ng-container>
@@ -41,13 +52,22 @@ import { proxyClient, ProxyClient, ProxyState } from "trace_collection/proxy_cli
<span class="adb-info">Your local proxy version is incompatible with Winscope.</span>
</p>
<p class="mat-body-1">Please update the proxy to version {{ proxyVersion }}.</p>
<p class="mat-body-1">Run: <code>python3 $ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py</code></p>
<p class="mat-body-1">
Run:
<code>
python3 $ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py
</code>
</p>
<p class="mat-body-1">Or get it from the AOSP repository.</p>
</div>
<div class="further-adb-info-actions">
<button color="primary" mat-stroked-button (click)="downloadFromAosp()">Download from AOSP</button>
<button color="primary" mat-stroked-button class="retry" (click)="restart()">Retry</button>
<button color="primary" mat-stroked-button (click)="downloadFromAosp()">
Download from AOSP
</button>
<button color="primary" mat-stroked-button class="retry" (click)="restart()">
Retry
</button>
</div>
</ng-container>
@@ -59,13 +79,17 @@ import { proxyClient, ProxyClient, ProxyState } from "trace_collection/proxy_cli
</p>
<p class="mat-body-1">Enter Winscope proxy token:</p>
<mat-form-field>
<input matInput [(ngModel)]="proxyKeyItem" name="proxy-key"/>
<input matInput [(ngModel)]="proxyKeyItem" name="proxy-key" />
</mat-form-field>
<p class="mat-body-1">The proxy token is printed to console on proxy launch, copy and paste it above.</p>
<p class="mat-body-1">
The proxy token is printed to console on proxy launch, copy and paste it above.
</p>
</div>
<div class="further-adb-info-actions">
<button color="primary" mat-stroked-button class="retry" (click)="restart()">Connect</button>
<button color="primary" mat-stroked-button class="retry" (click)="restart()">
Connect
</button>
</div>
</ng-container>
@@ -95,23 +119,24 @@ import { proxyClient, ProxyClient, ProxyState } from "trace_collection/proxy_cli
.adb-info {
margin-left: 5px;
}
`
]
`,
],
})
export class AdbProxyComponent {
@Input()
proxy: ProxyClient = proxyClient;
proxy: ProxyClient = proxyClient;
@Output()
proxyChange = new EventEmitter<ProxyClient>();
proxyChange = new EventEmitter<ProxyClient>();
@Output()
addKey = new EventEmitter<string>();
addKey = new EventEmitter<string>();
states = ProxyState;
proxyKeyItem = "";
proxyKeyItem = '';
readonly proxyVersion = this.proxy.VERSION;
readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py";
readonly downloadProxyUrl: string =
'https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py';
public restart() {
this.addKey.emit(this.proxyKeyItem);
@@ -120,6 +145,6 @@ export class AdbProxyComponent {
}
public downloadFromAosp() {
window.open(this.downloadProxyUrl, "_blank")?.focus();
window.open(this.downloadProxyUrl, '_blank')?.focus();
}
}

View File

@@ -13,43 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChangeDetectionStrategy } from "@angular/core";
import {ComponentFixture, TestBed, ComponentFixtureAutoDetect} from "@angular/core/testing";
import { CommonModule } from "@angular/common";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { MatCardModule } from "@angular/material/card";
import { MatButtonModule } from "@angular/material/button";
import { MatDividerModule } from "@angular/material/divider";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatIconModule } from "@angular/material/icon";
import { MatSelectModule } from "@angular/material/select";
import { MatSliderModule } from "@angular/material/slider";
import { MatSnackBarModule } from "@angular/material/snack-bar";
import { MatToolbarModule } from "@angular/material/toolbar";
import { MatTooltipModule } from "@angular/material/tooltip";
import {CommonModule} from '@angular/common';
import {ChangeDetectionStrategy} from '@angular/core';
import {ComponentFixture, ComponentFixtureAutoDetect, TestBed} from '@angular/core/testing';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatDividerModule} from '@angular/material/divider';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon';
import {MatSelectModule} from '@angular/material/select';
import {MatSliderModule} from '@angular/material/slider';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatTooltipModule} from '@angular/material/tooltip';
import { AppComponent } from "./app.component";
import { MatDrawer, MatDrawerContainer, MatDrawerContent } from "./bottomnav/bottom_drawer.component";
import { CollectTracesComponent } from "./collect_traces.component";
import { UploadTracesComponent } from "./upload_traces.component";
import { AdbProxyComponent } from "./adb_proxy.component";
import { WebAdbComponent } from "./web_adb.component";
import { TraceConfigComponent } from "./trace_config.component";
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
import { TimelineComponent } from "./timeline/timeline.component";
import { MiniTimelineComponent } from "./timeline/mini_timeline.component";
import { TraceViewComponent } from "./trace_view.component";
import {ViewerSurfaceFlingerComponent} from 'viewers/viewer_surface_flinger/viewer_surface_flinger.component';
import {AdbProxyComponent} from './adb_proxy.component';
import {AppComponent} from './app.component';
import {MatDrawer, MatDrawerContainer, MatDrawerContent} from './bottomnav/bottom_drawer.component';
import {CollectTracesComponent} from './collect_traces.component';
import {MiniTimelineComponent} from './timeline/mini_timeline.component';
import {TimelineComponent} from './timeline/timeline.component';
import {TraceConfigComponent} from './trace_config.component';
import {TraceViewComponent} from './trace_view.component';
import {UploadTracesComponent} from './upload_traces.component';
import {WebAdbComponent} from './web_adb.component';
describe("AppComponent", () => {
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let component: AppComponent;
let htmlElement: HTMLElement;
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
{ provide: ComponentFixtureAutoDetect, useValue: true }
],
providers: [{provide: ComponentFixtureAutoDetect, useValue: true}],
imports: [
CommonModule,
FormsModule,
@@ -63,7 +61,7 @@ describe("AppComponent", () => {
MatSnackBarModule,
MatToolbarModule,
MatTooltipModule,
ReactiveFormsModule
ReactiveFormsModule,
],
declarations: [
AdbProxyComponent,
@@ -78,45 +76,47 @@ describe("AppComponent", () => {
TraceViewComponent,
UploadTracesComponent,
ViewerSurfaceFlingerComponent,
WebAdbComponent
WebAdbComponent,
],
}).overrideComponent(AppComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
})
.overrideComponent(AppComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default},
})
.compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
htmlElement = fixture.nativeElement;
});
it("can be created", () => {
it('can be created', () => {
expect(component).toBeTruthy();
});
it("has the expected title", () => {
expect(component.title).toEqual("winscope-ng");
it('has the expected title', () => {
expect(component.title).toEqual('winscope-ng');
});
it("renders the page title", () => {
expect(htmlElement.querySelector(".app-title")?.innerHTML).toContain("Winscope");
it('renders the page title', () => {
expect(htmlElement.querySelector('.app-title')?.innerHTML).toContain('Winscope');
});
it("displays correct elements when no data loaded", () => {
it('displays correct elements when no data loaded', () => {
component.dataLoaded = false;
fixture.detectChanges();
expect(htmlElement.querySelector(".welcome-info")).toBeTruthy();
expect(htmlElement.querySelector(".active-trace-file-info")).toBeFalsy();
expect(htmlElement.querySelector(".collect-traces-card")).toBeTruthy();
expect(htmlElement.querySelector(".upload-traces-card")).toBeTruthy();
expect(htmlElement.querySelector(".viewers")).toBeFalsy();
expect(htmlElement.querySelector('.welcome-info')).toBeTruthy();
expect(htmlElement.querySelector('.active-trace-file-info')).toBeFalsy();
expect(htmlElement.querySelector('.collect-traces-card')).toBeTruthy();
expect(htmlElement.querySelector('.upload-traces-card')).toBeTruthy();
expect(htmlElement.querySelector('.viewers')).toBeFalsy();
});
it("displays correct elements when data loaded", () => {
it('displays correct elements when data loaded', () => {
component.dataLoaded = true;
fixture.detectChanges();
expect(htmlElement.querySelector(".welcome-info")).toBeFalsy();
expect(htmlElement.querySelector(".active-trace-file-info")).toBeTruthy();
expect(htmlElement.querySelector(".collect-traces-card")).toBeFalsy();
expect(htmlElement.querySelector(".upload-traces-card")).toBeFalsy();
expect(htmlElement.querySelector(".viewers")).toBeTruthy();
expect(htmlElement.querySelector('.welcome-info')).toBeFalsy();
expect(htmlElement.querySelector('.active-trace-file-info')).toBeTruthy();
expect(htmlElement.querySelector('.collect-traces-card')).toBeFalsy();
expect(htmlElement.querySelector('.upload-traces-card')).toBeFalsy();
expect(htmlElement.querySelector('.viewers')).toBeTruthy();
});
});

View File

@@ -17,112 +17,100 @@
import {
ChangeDetectorRef,
Component,
Injector,
Inject,
Injector,
ViewChild,
ViewEncapsulation
} from "@angular/core";
import { createCustomElement } from "@angular/elements";
import { TimelineComponent} from "./timeline/timeline.component";
import {AbtChromeExtensionProtocol} from "abt_chrome_extension/abt_chrome_extension_protocol";
import {CrossToolProtocol} from "cross_tool/cross_tool_protocol";
import { Mediator } from "app/mediator";
import { TraceData } from "app/trace_data";
import { PersistentStore } from "common/utils/persistent_store";
import { Timestamp } from "common/trace/timestamp";
import { FileUtils } from "common/utils/file_utils";
import { proxyClient, ProxyState } from "trace_collection/proxy_client";
import { ViewerInputMethodComponent } from "viewers/components/viewer_input_method.component";
import { View, Viewer } from "viewers/viewer";
import { ViewerProtologComponent} from "viewers/viewer_protolog/viewer_protolog.component";
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
import { ViewerTransactionsComponent } from "viewers/viewer_transactions/viewer_transactions.component";
import { ViewerScreenRecordingComponent } from "viewers/viewer_screen_recording/viewer_screen_recording.component";
import { TraceType } from "common/trace/trace_type";
import { TimelineData } from "app/timeline_data";
import { TracingConfig } from "trace_collection/tracing_config";
import {TRACE_INFO} from "app/trace_info";
import {UploadTracesComponent} from "./upload_traces.component";
import {TraceDataListener} from "interfaces/trace_data_listener";
ViewEncapsulation,
} from '@angular/core';
import {createCustomElement} from '@angular/elements';
import {AbtChromeExtensionProtocol} from 'abt_chrome_extension/abt_chrome_extension_protocol';
import {Mediator} from 'app/mediator';
import {TimelineData} from 'app/timeline_data';
import {TraceData} from 'app/trace_data';
import {TRACE_INFO} from 'app/trace_info';
import {Timestamp} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {FileUtils} from 'common/utils/file_utils';
import {PersistentStore} from 'common/utils/persistent_store';
import {CrossToolProtocol} from 'cross_tool/cross_tool_protocol';
import {TraceDataListener} from 'interfaces/trace_data_listener';
import {proxyClient, ProxyState} from 'trace_collection/proxy_client';
import {TracingConfig} from 'trace_collection/tracing_config';
import {ViewerInputMethodComponent} from 'viewers/components/viewer_input_method.component';
import {View, Viewer} from 'viewers/viewer';
import {ViewerProtologComponent} from 'viewers/viewer_protolog/viewer_protolog.component';
import {ViewerScreenRecordingComponent} from 'viewers/viewer_screen_recording/viewer_screen_recording.component';
import {ViewerSurfaceFlingerComponent} from 'viewers/viewer_surface_flinger/viewer_surface_flinger.component';
import {ViewerTransactionsComponent} from 'viewers/viewer_transactions/viewer_transactions.component';
import {ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager.component';
import {TimelineComponent} from './timeline/timeline.component';
import {UploadTracesComponent} from './upload_traces.component';
@Component({
selector: "app-root",
selector: 'app-root',
template: `
<mat-toolbar class="toolbar">
<span class="app-title">Winscope</span>
<a href="http://go/winscope-legacy">
<button color="primary" mat-button>
Open legacy Winscope
</button>
<button color="primary" mat-button>Open legacy Winscope</button>
</a>
<div class="spacer">
<span *ngIf="dataLoaded" class="active-trace-file-info mat-body-2">
{{activeTraceFileInfo}}
{{ activeTraceFileInfo }}
</span>
</div>
<button *ngIf="dataLoaded" color="primary" mat-stroked-button
(click)="mediator.onWinscopeUploadNew()">
<button
*ngIf="dataLoaded"
color="primary"
mat-stroked-button
(click)="mediator.onWinscopeUploadNew()">
Upload New
</button>
<button
mat-icon-button
matTooltip="Report bug"
(click)="goToLink('https://b.corp.google.com/issues/new?component=909476')">
<mat-icon>
bug_report
</mat-icon>
mat-icon-button
matTooltip="Report bug"
(click)="goToLink('https://b.corp.google.com/issues/new?component=909476')">
<mat-icon> bug_report </mat-icon>
</button>
<button
mat-icon-button
matTooltip="Switch to {{ isDarkModeOn ? 'light' : 'dark'}} mode"
(click)="setDarkMode(!isDarkModeOn)">
mat-icon-button
matTooltip="Switch to {{ isDarkModeOn ? 'light' : 'dark' }} mode"
(click)="setDarkMode(!isDarkModeOn)">
<mat-icon>
{{ isDarkModeOn ? "brightness_5" : "brightness_4" }}
{{ isDarkModeOn ? 'brightness_5' : 'brightness_4' }}
</mat-icon>
</button>
</mat-toolbar>
<mat-divider></mat-divider>
<mat-drawer-container class="example-container" autosize disableClose
autoFocus>
<mat-drawer-container class="example-container" autosize disableClose autoFocus>
<mat-drawer-content>
<ng-container *ngIf="dataLoaded; else noLoadedTracesBlock">
<trace-view
class="viewers"
[viewers]="viewers"
[store]="store"
(downloadTracesButtonClick)="onDownloadTracesButtonClick()"
(activeViewChanged)="onActiveViewChanged($event)"
></trace-view>
class="viewers"
[viewers]="viewers"
[store]="store"
(downloadTracesButtonClick)="onDownloadTracesButtonClick()"
(activeViewChanged)="onActiveViewChanged($event)"></trace-view>
<mat-divider></mat-divider>
</ng-container>
</mat-drawer-content>
<mat-drawer #drawer mode="overlay" opened="true"
[baseHeight]="collapsedTimelineHeight">
<mat-drawer #drawer mode="overlay" opened="true" [baseHeight]="collapsedTimelineHeight">
<timeline
*ngIf="dataLoaded"
[timelineData]="timelineData"
[activeViewTraceTypes]="activeView?.dependencies"
[availableTraces]="getLoadedTraceTypes()"
(collapsedTimelineSizeChanged)="onCollapsedTimelineSizeChanged($event)"
></timeline>
*ngIf="dataLoaded"
[timelineData]="timelineData"
[activeViewTraceTypes]="activeView?.dependencies"
[availableTraces]="getLoadedTraceTypes()"
(collapsedTimelineSizeChanged)="onCollapsedTimelineSizeChanged($event)"></timeline>
</mat-drawer>
</mat-drawer-container>
<ng-template #noLoadedTracesBlock>
@@ -134,17 +122,15 @@ import {TraceDataListener} from "interfaces/trace_data_listener";
<div class="card-grid landing-grid">
<collect-traces
class="collect-traces-card homepage-card"
[traceData]="traceData"
(traceDataLoaded)="mediator.onWinscopeTraceDataLoaded()"
[store]="store"
></collect-traces>
class="collect-traces-card homepage-card"
[traceData]="traceData"
(traceDataLoaded)="mediator.onWinscopeTraceDataLoaded()"
[store]="store"></collect-traces>
<upload-traces
class="upload-traces-card homepage-card"
[traceData]="traceData"
(traceDataLoaded)="mediator.onWinscopeTraceDataLoaded()"
></upload-traces>
class="upload-traces-card homepage-card"
[traceData]="traceData"
(traceDataLoaded)="mediator.onWinscopeTraceDataLoaded()"></upload-traces>
</div>
</div>
</div>
@@ -194,12 +180,12 @@ import {TraceDataListener} from "interfaces/trace_data_listener";
flex-grow: 1;
margin: auto;
}
`
`,
],
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
})
export class AppComponent implements TraceDataListener {
title = "winscope-ng";
title = 'winscope-ng';
changeDetectorRef: ChangeDetectorRef;
traceData = new TraceData();
timelineData = new TimelineData();
@@ -220,7 +206,7 @@ export class AppComponent implements TraceDataListener {
isDarkModeOn!: boolean;
dataLoaded = false;
activeView?: View;
activeTraceFileInfo = "";
activeTraceFileInfo = '';
collapsedTimelineHeight = 0;
@ViewChild(UploadTracesComponent) uploadTracesComponent?: UploadTracesComponent;
@ViewChild(TimelineComponent) timelineComponent?: TimelineComponent;
@@ -231,33 +217,45 @@ export class AppComponent implements TraceDataListener {
) {
this.changeDetectorRef = changeDetectorRef;
const storeDarkMode = this.store.get("dark-mode");
const prefersDarkQuery = window.matchMedia?.("(prefers-color-scheme: dark)");
this.setDarkMode(storeDarkMode != null ? storeDarkMode == "true" : prefersDarkQuery.matches);
const storeDarkMode = this.store.get('dark-mode');
const prefersDarkQuery = window.matchMedia?.('(prefers-color-scheme: dark)');
this.setDarkMode(storeDarkMode != null ? storeDarkMode == 'true' : prefersDarkQuery.matches);
if (!customElements.get("viewer-input-method")) {
customElements.define("viewer-input-method",
createCustomElement(ViewerInputMethodComponent, {injector}));
if (!customElements.get('viewer-input-method')) {
customElements.define(
'viewer-input-method',
createCustomElement(ViewerInputMethodComponent, {injector})
);
}
if (!customElements.get("viewer-protolog")) {
customElements.define("viewer-protolog",
createCustomElement(ViewerProtologComponent, {injector}));
if (!customElements.get('viewer-protolog')) {
customElements.define(
'viewer-protolog',
createCustomElement(ViewerProtologComponent, {injector})
);
}
if (!customElements.get("viewer-screen-recording")) {
customElements.define("viewer-screen-recording",
createCustomElement(ViewerScreenRecordingComponent, {injector}));
if (!customElements.get('viewer-screen-recording')) {
customElements.define(
'viewer-screen-recording',
createCustomElement(ViewerScreenRecordingComponent, {injector})
);
}
if (!customElements.get("viewer-surface-flinger")) {
customElements.define("viewer-surface-flinger",
createCustomElement(ViewerSurfaceFlingerComponent, {injector}));
if (!customElements.get('viewer-surface-flinger')) {
customElements.define(
'viewer-surface-flinger',
createCustomElement(ViewerSurfaceFlingerComponent, {injector})
);
}
if (!customElements.get("viewer-transactions")) {
customElements.define("viewer-transactions",
createCustomElement(ViewerTransactionsComponent, {injector}));
if (!customElements.get('viewer-transactions')) {
customElements.define(
'viewer-transactions',
createCustomElement(ViewerTransactionsComponent, {injector})
);
}
if (!customElements.get("viewer-window-manager")) {
customElements.define("viewer-window-manager",
createCustomElement(ViewerWindowManagerComponent, {injector}));
if (!customElements.get('viewer-window-manager')) {
customElements.define(
'viewer-window-manager',
createCustomElement(ViewerWindowManagerComponent, {injector})
);
}
TracingConfig.getInstance().initialize(localStorage);
@@ -281,7 +279,7 @@ export class AppComponent implements TraceDataListener {
return this.traceData.getLoadedTraces().map((trace) => trace.type);
}
getVideoData(): Blob|undefined {
getVideoData(): Blob | undefined {
return this.timelineData.getScreenRecordingVideo();
}
@@ -298,17 +296,17 @@ export class AppComponent implements TraceDataListener {
}
public setDarkMode(enabled: boolean) {
document.body.classList.toggle("dark-mode", enabled);
this.store.add("dark-mode", `${enabled}`);
document.body.classList.toggle('dark-mode', enabled);
this.store.add('dark-mode', `${enabled}`);
this.isDarkModeOn = enabled;
}
async onDownloadTracesButtonClick() {
const traceFiles = await this.makeTraceFilesForDownload();
const zipFileBlob = await FileUtils.createZipArchive(traceFiles);
const zipFileName = "winscope.zip";
const zipFileName = 'winscope.zip';
const a = document.createElement("a");
const a = document.createElement('a');
document.body.appendChild(a);
const url = window.URL.createObjectURL(zipFileBlob);
a.href = url;
@@ -324,18 +322,17 @@ export class AppComponent implements TraceDataListener {
this.timelineData.setActiveViewTraceTypes(view.dependencies);
}
goToLink(url: string){
window.open(url, "_blank");
goToLink(url: string) {
window.open(url, '_blank');
}
private makeActiveTraceFileInfo(view: View): string {
const traceFile = this.traceData
.getLoadedTraces()
.find(trace => trace.type === view.dependencies[0])
?.traceFile;
.find((trace) => trace.type === view.dependencies[0])?.traceFile;
if (!traceFile) {
return "";
return '';
}
if (!traceFile.parentArchive) {
@@ -346,9 +343,9 @@ export class AppComponent implements TraceDataListener {
}
private async makeTraceFilesForDownload(): Promise<File[]> {
return this.traceData.getLoadedTraces().map(trace => {
return this.traceData.getLoadedTraces().map((trace) => {
const traceType = TRACE_INFO[trace.type].name;
const newName = traceType + "/" + FileUtils.removeDirFromFileName(trace.traceFile.file.name);
const newName = traceType + '/' + FileUtils.removeDirFromFileName(trace.traceFile.file.name);
return new File([trace.traceFile.file], newName);
});
}

View File

@@ -14,8 +14,8 @@
* limitations under the License.
*/
import {Viewer} from "viewers/viewer";
import {TraceDataListener} from "interfaces/trace_data_listener";
import {TraceDataListener} from 'interfaces/trace_data_listener';
import {Viewer} from 'viewers/viewer';
export class AppComponentStub implements TraceDataListener {
onTraceDataLoaded(viewers: Viewer[]) {

View File

@@ -1,46 +1,43 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
animate,
AnimationTriggerMetadata,
state,
style,
transition,
trigger,
} from '@angular/animations';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ElementRef,
forwardRef,
Inject,
ViewEncapsulation,
ElementRef,
NgZone,
Injectable,
ViewChild,
Input,
} from "@angular/core";
import {
animate,
state,
style,
transition,
trigger,
AnimationTriggerMetadata,
} from "@angular/animations";
import {Subject} from "rxjs";
import {
debounceTime,
takeUntil,
} from "rxjs/operators";
NgZone,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import {Subject} from 'rxjs';
import {debounceTime, takeUntil} from 'rxjs/operators';
/**
* Animations used by the Material drawers.
@@ -50,30 +47,30 @@ export const matDrawerAnimations: {
readonly transformDrawer: AnimationTriggerMetadata;
} = {
/** Animation that slides a drawer in and out. */
transformDrawer: trigger("transform", [
transformDrawer: trigger('transform', [
// We remove the `transform` here completely, rather than setting it to zero, because:
// 1. Having a transform can cause elements with ripples or an animated
// transform to shift around in Chrome with an RTL layout (see #10023).
// 2. 3d transforms causes text to appear blurry on IE and Edge.
state(
"open, open-instant",
'open, open-instant',
style({
"transform": "none",
"visibility": "visible",
}),
transform: 'none',
visibility: 'visible',
})
),
state(
"void",
'void',
style({
// Avoids the shadow showing up when closed in SSR.
"box-shadow": "none",
"visibility": "hidden",
}),
'box-shadow': 'none',
visibility: 'hidden',
})
),
transition("void => open-instant", animate("0ms")),
transition('void => open-instant', animate('0ms')),
transition(
"void <=> open, open-instant => void",
animate("400ms cubic-bezier(0.25, 0.8, 0.25, 1)"),
'void <=> open, open-instant => void',
animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)')
),
]),
};
@@ -83,33 +80,38 @@ export const matDrawerAnimations: {
*/
@Injectable()
@Component({
selector: "mat-drawer",
exportAs: "matDrawer",
selector: 'mat-drawer',
exportAs: 'matDrawer',
template: `
<div class="mat-drawer-inner-container" #content>
<ng-content></ng-content>
</div>
`,
styles: [`
.mat-drawer.mat-drawer-bottom {
left: 0; right: 0; bottom: 0; top: unset;
position: fixed;
z-index: 5;
background-color: #F8F9FA;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15);
}
`],
styles: [
`
.mat-drawer.mat-drawer-bottom {
left: 0;
right: 0;
bottom: 0;
top: unset;
position: fixed;
z-index: 5;
background-color: #f8f9fa;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15);
}
`,
],
animations: [matDrawerAnimations.transformDrawer],
host: {
"class": "mat-drawer mat-drawer-bottom",
class: 'mat-drawer mat-drawer-bottom',
// must prevent the browser from aligning text based on value
"[attr.align]": "null",
'[attr.align]': 'null',
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class MatDrawer {
@Input() mode: "push"|"overlay" = "overlay";
@Input() mode: 'push' | 'overlay' = 'overlay';
@Input() baseHeight = 0;
public getBaseHeight() {
@@ -118,9 +120,10 @@ export class MatDrawer {
}
@Component({
selector: "mat-drawer-content",
template: "<ng-content></ng-content>",
styles: [`
selector: 'mat-drawer-content',
template: '<ng-content></ng-content>',
styles: [
`
.mat-drawer-content {
display: flex;
flex-direction: column;
@@ -131,22 +134,22 @@ export class MatDrawer {
width: 100%;
flex-grow: 1;
}
`],
`,
],
host: {
"class": "mat-drawer-content",
"[style.margin-top.px]": "contentMargins.top",
"[style.margin-bottom.px]": "contentMargins.bottom",
class: 'mat-drawer-content',
'[style.margin-top.px]': 'contentMargins.top',
'[style.margin-bottom.px]': 'contentMargins.bottom',
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class MatDrawerContent /*extends MatDrawerContentBase*/ {
private contentMargins: {top: number | null; bottom: number | null} = {top: null, bottom: null};
constructor(
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef,
@Inject(forwardRef(() => MatDrawerContainer)) public container: MatDrawerContainer,
@Inject(forwardRef(() => MatDrawerContainer)) public container: MatDrawerContainer
) {}
ngAfterContentInit() {
@@ -161,26 +164,27 @@ export class MatDrawerContent /*extends MatDrawerContentBase*/ {
}
@Component({
selector: "mat-drawer-container",
exportAs: "matDrawerContainer",
selector: 'mat-drawer-container',
exportAs: 'matDrawerContainer',
template: `
<ng-content select="mat-drawer-content">
</ng-content>
<ng-content select="mat-drawer-content"> </ng-content>
<ng-content select="mat-drawer"></ng-content>
`,
styles: [`
.mat-drawer-container {
display: flex;
flex-direction: column;
flex-grow: 1;
align-items: center;
align-content: center;
justify-content: center;
}
`],
styles: [
`
.mat-drawer-container {
display: flex;
flex-direction: column;
flex-grow: 1;
align-items: center;
align-content: center;
justify-content: center;
}
`,
],
host: {
"class": "mat-drawer-container",
class: 'mat-drawer-container',
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
@@ -189,7 +193,7 @@ export class MatDrawerContent /*extends MatDrawerContentBase*/ {
export class MatDrawerContainer /*extends MatDrawerContainerBase*/ {
/** Drawer that belong to this container. */
@ContentChild(MatDrawer) drawer!: MatDrawer;
@ContentChild(MatDrawer, { read: ElementRef }) drawerView!: ElementRef;
@ContentChild(MatDrawer, {read: ElementRef}) drawerView!: ElementRef;
@ContentChild(MatDrawerContent) content!: MatDrawerContent;
@ViewChild(MatDrawerContent) userContent!: MatDrawerContent;
@@ -209,9 +213,7 @@ export class MatDrawerContainer /*extends MatDrawerContainerBase*/ {
/** Emits when the component is destroyed. */
private readonly destroyed = new Subject<void>();
constructor(
@Inject(NgZone) private ngZone: NgZone,
) {}
constructor(@Inject(NgZone) private ngZone: NgZone) {}
ngAfterContentInit() {
this.updateContentMargins();
@@ -221,7 +223,7 @@ export class MatDrawerContainer /*extends MatDrawerContainerBase*/ {
this.doCheckSubject
.pipe(
debounceTime(10), // Arbitrary debounce time, less than a frame at 60fps
takeUntil(this.destroyed),
takeUntil(this.destroyed)
)
.subscribe(() => this.updateContentMargins());
});
@@ -250,7 +252,7 @@ export class MatDrawerContainer /*extends MatDrawerContainerBase*/ {
const baseHeight = this.drawer.getBaseHeight();
const height = this.getDrawerHeight();
const shiftAmount = this.drawer.mode === "push" ? Math.max(0, height - baseHeight) : 0;
const shiftAmount = this.drawer.mode === 'push' ? Math.max(0, height - baseHeight) : 0;
top -= shiftAmount;
bottom += baseHeight + shiftAmount;
@@ -276,4 +278,4 @@ export class MatDrawerContainer /*extends MatDrawerContainerBase*/ {
getDrawerHeight(): number {
return this.drawerView.nativeElement ? this.drawerView.nativeElement.offsetHeight || 0 : 0;
}
}
}

View File

@@ -14,18 +14,18 @@
* limitations under the License.
*/
import { CanvasMouseHandler } from "./canvas_mouse_handler";
import {CanvasMouseHandler} from './canvas_mouse_handler';
export type padding = {left: number, top: number, right: number, bottom: number};
export type padding = {left: number; top: number; right: number; bottom: number};
export interface CanvasDrawer {
draw(): void;
handler: CanvasMouseHandler
canvas: HTMLCanvasElement
ctx: CanvasRenderingContext2D
padding: padding
getXScale(): number
getYScale(): number
getWidth(): number
getHeight(): number
}
handler: CanvasMouseHandler;
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
padding: padding;
getXScale(): number;
getYScale(): number;
getWidth(): number;
getHeight(): number;
}

View File

@@ -14,42 +14,49 @@
* limitations under the License.
*/
import { CanvasDrawer } from "./canvas_drawer";
import { DraggableCanvasObject } from "./draggable_canvas_object";
import {CanvasDrawer} from './canvas_drawer';
import {DraggableCanvasObject} from './draggable_canvas_object';
export type DragListener = (x: number, y: number) => void;
export type DropListener = DragListener;
export class CanvasMouseHandler {
// Ordered top most element to bottom most
private draggableObjects = new Array<DraggableCanvasObject>();
private draggingObject: DraggableCanvasObject|undefined = undefined;
private draggingObject: DraggableCanvasObject | undefined = undefined;
private onDrag = new Map<DraggableCanvasObject, DragListener>();
private onDrop = new Map<DraggableCanvasObject, DropListener>();
constructor(
private drawer: CanvasDrawer,
private defaultCursor: string = "auto",
private defaultCursor: string = 'auto',
private onUnhandledMouseDown: (x: number, y: number) => void = (x, y) => {}
) {
this.drawer.canvas.addEventListener("mousemove", (event) => { this.handleMouseMove(event); });
this.drawer.canvas.addEventListener("mousedown", (event) => { this.handleMouseDown(event); });
this.drawer.canvas.addEventListener("mouseup", (event) => { this.handleMouseUp(event); });
this.drawer.canvas.addEventListener("mouseout", (event) => { this.handleMouseUp(event); });
this.drawer.canvas.addEventListener('mousemove', (event) => {
this.handleMouseMove(event);
});
this.drawer.canvas.addEventListener('mousedown', (event) => {
this.handleMouseDown(event);
});
this.drawer.canvas.addEventListener('mouseup', (event) => {
this.handleMouseUp(event);
});
this.drawer.canvas.addEventListener('mouseout', (event) => {
this.handleMouseUp(event);
});
}
public registerDraggableObject(
draggableObject: DraggableCanvasObject,
onDrag: DragListener,
onDrop: DropListener,
onDrop: DropListener
) {
this.onDrag.set(draggableObject, onDrag);
this.onDrop.set(draggableObject, onDrop);
}
public notifyDrawnOnTop(draggableObject: DraggableCanvasObject,) {
public notifyDrawnOnTop(draggableObject: DraggableCanvasObject) {
const foundIndex = this.draggableObjects.indexOf(draggableObject);
if (foundIndex !== -1) {
this.draggableObjects.splice(foundIndex, 1);
@@ -60,7 +67,7 @@ export class CanvasMouseHandler {
private handleMouseDown(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
const { mouseX, mouseY } = this.getPos(e);
const {mouseX, mouseY} = this.getPos(e);
const clickedObject = this.objectAt(mouseX, mouseY);
if (clickedObject !== undefined) {
@@ -74,7 +81,7 @@ export class CanvasMouseHandler {
private handleMouseMove(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
const { mouseX, mouseY } = this.getPos(e);
const {mouseX, mouseY} = this.getPos(e);
if (this.draggingObject !== undefined) {
const onDragCallback = this.onDrag.get(this.draggingObject);
@@ -89,7 +96,7 @@ export class CanvasMouseHandler {
private handleMouseUp(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
const { mouseX, mouseY } = this.getPos(e);
const {mouseX, mouseY} = this.getPos(e);
if (this.draggingObject !== undefined) {
const onDropCallback = this.onDrop.get(this.draggingObject);
@@ -111,29 +118,34 @@ export class CanvasMouseHandler {
}
if (mouseX > this.drawer.getWidth() - this.drawer.padding.right) {
mouseX = this.drawer.getWidth()- this.drawer.padding.right;
mouseX = this.drawer.getWidth() - this.drawer.padding.right;
}
return { mouseX, mouseY };
return {mouseX, mouseY};
}
private updateCursor(mouseX: number, mouseY: number) {
const hoverObject = this.objectAt(mouseX, mouseY);
if (hoverObject !== undefined) {
if (hoverObject === this.draggingObject) {
this.drawer.canvas.style.cursor = "grabbing";
this.drawer.canvas.style.cursor = 'grabbing';
} else {
this.drawer.canvas.style.cursor = "grab";
this.drawer.canvas.style.cursor = 'grab';
}
} else {
this.drawer.canvas.style.cursor = this.defaultCursor;
}
}
private objectAt(mouseX: number, mouseY: number): DraggableCanvasObject|undefined {
private objectAt(mouseX: number, mouseY: number): DraggableCanvasObject | undefined {
for (const object of this.draggableObjects) {
object.definePath(this.drawer.ctx);
if (this.drawer.ctx.isPointInPath(mouseX * this.drawer.getXScale(), mouseY * this.drawer.getYScale())) {
if (
this.drawer.ctx.isPointInPath(
mouseX * this.drawer.getXScale(),
mouseY * this.drawer.getYScale()
)
) {
return object;
}
}
@@ -141,4 +153,3 @@ export class CanvasMouseHandler {
return undefined;
}
}

View File

@@ -14,17 +14,17 @@
* limitations under the License.
*/
import { MathUtils } from "three/src/Three";
import { Segment } from "../timeline/utils";
import { CanvasDrawer } from "./canvas_drawer";
import {MathUtils} from 'three/src/Three';
import {Segment} from '../timeline/utils';
import {CanvasDrawer} from './canvas_drawer';
export type drawConfig = {
fillStyle: string,
fill: boolean
}
fillStyle: string;
fill: boolean;
};
export class DraggableCanvasObject {
private draggingPosition: number|undefined;
private draggingPosition: number | undefined;
constructor(
private drawer: CanvasDrawer,
@@ -33,17 +33,21 @@ export class DraggableCanvasObject {
private drawConfig: drawConfig,
private onDrag: (x: number) => void,
private onDrop: (x: number) => void,
private rangeGetter: () => Segment,
private rangeGetter: () => Segment
) {
this.drawer.handler.registerDraggableObject(this, (x: number, ) => {
this.draggingPosition = this.clampPositionToRange(x);
this.onDrag(this.draggingPosition);
this.drawer.draw();
}, (x: number, ) => {
this.draggingPosition = undefined;
this.onDrop(this.clampPositionToRange(x));
this.drawer.draw();
});
this.drawer.handler.registerDraggableObject(
this,
(x: number) => {
this.draggingPosition = this.clampPositionToRange(x);
this.onDrag(this.draggingPosition);
this.drawer.draw();
},
(x: number) => {
this.draggingPosition = undefined;
this.onDrop(this.clampPositionToRange(x));
this.drawer.draw();
}
);
}
get range(): Segment {
@@ -74,4 +78,4 @@ export class DraggableCanvasObject {
private clampPositionToRange(x: number): number {
return MathUtils.clamp(x, this.range.from, this.range.to);
}
}
}

View File

@@ -13,22 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {CollectTracesComponent} from "./collect_traces.component";
import { MatIconModule } from "@angular/material/icon";
import { MatCardModule } from "@angular/material/card";
import { AdbProxyComponent } from "./adb_proxy.component";
import { WebAdbComponent } from "./web_adb.component";
import { TraceConfigComponent } from "./trace_config.component";
import { MatListModule } from "@angular/material/list";
import { MatButtonModule } from "@angular/material/button";
import { MatDividerModule } from "@angular/material/divider";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { NO_ERRORS_SCHEMA } from "@angular/core";
import { MatSnackBar, MatSnackBarModule } from "@angular/material/snack-bar";
import {NO_ERRORS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatDividerModule} from '@angular/material/divider';
import {MatIconModule} from '@angular/material/icon';
import {MatListModule} from '@angular/material/list';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatSnackBar, MatSnackBarModule} from '@angular/material/snack-bar';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AdbProxyComponent} from './adb_proxy.component';
import {CollectTracesComponent} from './collect_traces.component';
import {TraceConfigComponent} from './trace_config.component';
import {WebAdbComponent} from './web_adb.component';
describe("CollectTracesComponent", () => {
describe('CollectTracesComponent', () => {
let fixture: ComponentFixture<CollectTracesComponent>;
let component: CollectTracesComponent;
let htmlElement: HTMLElement;
@@ -43,7 +43,7 @@ describe("CollectTracesComponent", () => {
MatDividerModule,
MatProgressBarModule,
BrowserAnimationsModule,
MatSnackBarModule
MatSnackBarModule,
],
providers: [MatSnackBar],
declarations: [
@@ -52,7 +52,7 @@ describe("CollectTracesComponent", () => {
WebAdbComponent,
TraceConfigComponent,
],
schemas: [NO_ERRORS_SCHEMA]
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
fixture = TestBed.createComponent(CollectTracesComponent);
component = fixture.componentInstance;
@@ -60,137 +60,141 @@ describe("CollectTracesComponent", () => {
component.isAdbProxy = true;
});
it("can be created", () => {
it('can be created', () => {
expect(component).toBeTruthy();
});
it("renders the expected card title", () => {
it('renders the expected card title', () => {
fixture.detectChanges();
expect(htmlElement.querySelector(".title")?.innerHTML).toContain("Collect Traces");
expect(htmlElement.querySelector('.title')?.innerHTML).toContain('Collect Traces');
});
it("displays connecting message", () => {
it('displays connecting message', () => {
component.connect.isConnectingState = jasmine.createSpy().and.returnValue(true);
fixture.detectChanges();
expect(htmlElement.querySelector(".connecting-message")?.innerHTML).toContain("Connecting...");
expect(htmlElement.querySelector('.connecting-message')?.innerHTML).toContain('Connecting...');
});
it("displays adb set up", async () => {
it('displays adb set up', async () => {
component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false);
fixture.detectChanges();
fixture.whenStable().then( () => {
expect(htmlElement.querySelector(".set-up-adb")).toBeTruthy();
const proxyTab: HTMLButtonElement | null = htmlElement.querySelector(".proxy-tab");
fixture.whenStable().then(() => {
expect(htmlElement.querySelector('.set-up-adb')).toBeTruthy();
const proxyTab: HTMLButtonElement | null = htmlElement.querySelector('.proxy-tab');
expect(proxyTab).toBeInstanceOf(HTMLButtonElement);
const webTab: HTMLButtonElement | null = htmlElement.querySelector(".web-tab");
const webTab: HTMLButtonElement | null = htmlElement.querySelector('.web-tab');
expect(webTab).toBeInstanceOf(HTMLButtonElement);
});
});
it("displays adb proxy element", async () => {
component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false);
component.isAdbProxy = true;
fixture.detectChanges();
fixture.whenStable().then( () => {
expect(htmlElement.querySelector("adb-proxy")).toBeTruthy();
expect(htmlElement.querySelector("web-adb")).toBeFalsy();
});
});
it("displays web adb element", async () => {
component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false);
component.isAdbProxy = false;
fixture.detectChanges();
fixture.whenStable().then( () => {
expect(htmlElement.querySelector("adb-proxy")).toBeFalsy();
expect(htmlElement.querySelector("web-adb")).toBeTruthy();
});
});
it("changes to adb workflow tab", async () => {
it('displays adb proxy element', async () => {
component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false);
component.isAdbProxy = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
const webTab: HTMLButtonElement | null = htmlElement.querySelector(".web-tab");
expect(htmlElement.querySelector('adb-proxy')).toBeTruthy();
expect(htmlElement.querySelector('web-adb')).toBeFalsy();
});
});
it('displays web adb element', async () => {
component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false);
component.isAdbProxy = false;
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(htmlElement.querySelector('adb-proxy')).toBeFalsy();
expect(htmlElement.querySelector('web-adb')).toBeTruthy();
});
});
it('changes to adb workflow tab', async () => {
component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false);
component.isAdbProxy = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
const webTab: HTMLButtonElement | null = htmlElement.querySelector('.web-tab');
expect(webTab).toBeInstanceOf(HTMLButtonElement);
webTab?.dispatchEvent(new Event("click"));
webTab?.dispatchEvent(new Event('click'));
fixture.whenStable().then(() => {
expect(component.displayWebAdbTab).toHaveBeenCalled();
});
});
});
it("displays no connected devices", async () => {
it('displays no connected devices', async () => {
component.connect.isDevicesState = jasmine.createSpy().and.returnValue(true);
component.connect.devices = jasmine.createSpy().and.returnValue({});
fixture.detectChanges();
fixture.whenStable().then( () => {
const el = htmlElement.querySelector(".devices-connecting");
fixture.whenStable().then(() => {
const el = htmlElement.querySelector('.devices-connecting');
expect(el).toBeTruthy();
expect(el?.innerHTML).toContain("No devices detected");
expect(el?.innerHTML).toContain('No devices detected');
});
});
it("displays connected authorised devices", async () => {
it('displays connected authorised devices', async () => {
component.connect.isDevicesState = jasmine.createSpy().and.returnValue(true);
component.connect.devices = jasmine.createSpy().and.returnValue({"35562": {model: "Pixel 6", authorised:true}});
component.connect.devices = jasmine
.createSpy()
.and.returnValue({'35562': {model: 'Pixel 6', authorised: true}});
fixture.detectChanges();
fixture.whenStable().then( () => {
const el = htmlElement.querySelector(".devices-connecting");
fixture.whenStable().then(() => {
const el = htmlElement.querySelector('.devices-connecting');
expect(el).toBeTruthy();
expect(el?.innerHTML).toContain("Connected devices:");
expect(el?.innerHTML).toContain("Pixel 6");
expect(el?.innerHTML).toContain("smartphone");
expect(el?.innerHTML).toContain('Connected devices:');
expect(el?.innerHTML).toContain('Pixel 6');
expect(el?.innerHTML).toContain('smartphone');
});
});
it("displays connected unauthorised devices", async () => {
it('displays connected unauthorised devices', async () => {
component.connect.isDevicesState = jasmine.createSpy().and.returnValue(true);
component.connect.devices = jasmine.createSpy().and.returnValue({"35562": {model: "Pixel 6", authorised:false}});
component.connect.devices = jasmine
.createSpy()
.and.returnValue({'35562': {model: 'Pixel 6', authorised: false}});
fixture.detectChanges();
fixture.whenStable().then( () => {
const el = htmlElement.querySelector(".devices-connecting");
fixture.whenStable().then(() => {
const el = htmlElement.querySelector('.devices-connecting');
expect(el).toBeTruthy();
expect(el?.innerHTML).toContain("Connected devices:");
expect(el?.innerHTML).toContain("unauthorised");
expect(el?.innerHTML).toContain("screen_lock_portrait");
expect(el?.innerHTML).toContain('Connected devices:');
expect(el?.innerHTML).toContain('unauthorised');
expect(el?.innerHTML).toContain('screen_lock_portrait');
});
});
it("displays trace collection config elements", async () => {
it('displays trace collection config elements', async () => {
component.connect.isStartTraceState = jasmine.createSpy().and.returnValue(true);
const mock = {model: "Pixel 6", authorised:true};
component.connect.devices = jasmine.createSpy().and.returnValue({"35562": mock});
const mock = {model: 'Pixel 6', authorised: true};
component.connect.devices = jasmine.createSpy().and.returnValue({'35562': mock});
component.connect.selectedDevice = jasmine.createSpy().and.returnValue(mock);
fixture.detectChanges();
fixture.whenStable().then( () => {
const el = htmlElement.querySelector(".trace-collection-config");
fixture.whenStable().then(() => {
const el = htmlElement.querySelector('.trace-collection-config');
expect(el).toBeTruthy();
expect(el?.innerHTML).toContain("smartphone");
expect(el?.innerHTML).toContain("Pixel 6");
expect(el?.innerHTML).toContain("35562");
expect(el?.innerHTML).toContain('smartphone');
expect(el?.innerHTML).toContain('Pixel 6');
expect(el?.innerHTML).toContain('35562');
const traceSection = htmlElement.querySelector(".trace-section");
const traceSection = htmlElement.querySelector('.trace-section');
expect(traceSection).toBeTruthy();
const dumpSection = htmlElement.querySelector(".dump-section");
const dumpSection = htmlElement.querySelector('.dump-section');
expect(dumpSection).toBeTruthy();
});
});
it("start trace button works as expected", async () => {
it('start trace button works as expected', async () => {
component.connect.isStartTraceState = jasmine.createSpy().and.returnValue(true);
const mock = {model: "Pixel 6", authorised:true};
component.connect.devices = jasmine.createSpy().and.returnValue({"35562": mock});
const mock = {model: 'Pixel 6', authorised: true};
component.connect.devices = jasmine.createSpy().and.returnValue({'35562': mock});
component.connect.selectedDevice = jasmine.createSpy().and.returnValue(mock);
fixture.detectChanges();
fixture.whenStable().then( () => {
const start: HTMLButtonElement | null = htmlElement.querySelector(".start-btn");
fixture.whenStable().then(() => {
const start: HTMLButtonElement | null = htmlElement.querySelector('.start-btn');
expect(start).toBeInstanceOf(HTMLButtonElement);
start?.dispatchEvent(new Event("click"));
start?.dispatchEvent(new Event('click'));
fixture.whenStable().then(() => {
expect(component.startTracing).toHaveBeenCalled();
expect(component.connect.startTrace).toHaveBeenCalled();
@@ -198,17 +202,17 @@ describe("CollectTracesComponent", () => {
});
});
it("dump state button works as expected", async () => {
it('dump state button works as expected', async () => {
component.connect.isStartTraceState = jasmine.createSpy().and.returnValue(true);
const mock = {model: "Pixel 6", authorised:true};
component.connect.devices = jasmine.createSpy().and.returnValue({"35562": mock});
const mock = {model: 'Pixel 6', authorised: true};
component.connect.devices = jasmine.createSpy().and.returnValue({'35562': mock});
component.connect.selectedDevice = jasmine.createSpy().and.returnValue(mock);
fixture.detectChanges();
fixture.whenStable().then( () => {
const dump: HTMLButtonElement | null = htmlElement.querySelector(".dump-btn");
fixture.whenStable().then(() => {
const dump: HTMLButtonElement | null = htmlElement.querySelector('.dump-btn');
expect(dump).toBeInstanceOf(HTMLButtonElement);
dump?.dispatchEvent(new Event("click"));
dump?.dispatchEvent(new Event('click'));
fixture.whenStable().then(() => {
expect(component.dumpState).toHaveBeenCalled();
expect(component.connect.dumpState).toHaveBeenCalled();
@@ -216,51 +220,51 @@ describe("CollectTracesComponent", () => {
});
});
it("change device button works as expected", async () => {
it('change device button works as expected', async () => {
component.connect.isStartTraceState = jasmine.createSpy().and.returnValue(true);
const mock = {model: "Pixel 6", authorised:true};
component.connect.devices = jasmine.createSpy().and.returnValue({"35562": mock});
const mock = {model: 'Pixel 6', authorised: true};
component.connect.devices = jasmine.createSpy().and.returnValue({'35562': mock});
component.connect.selectedDevice = jasmine.createSpy().and.returnValue(mock);
fixture.detectChanges();
fixture.whenStable().then( () => {
const change: HTMLButtonElement | null = htmlElement.querySelector(".change-btn");
fixture.whenStable().then(() => {
const change: HTMLButtonElement | null = htmlElement.querySelector('.change-btn');
expect(change).toBeInstanceOf(HTMLButtonElement);
change?.dispatchEvent(new Event("click"));
change?.dispatchEvent(new Event('click'));
fixture.whenStable().then(() => {
expect(component.connect.resetLastDevice).toHaveBeenCalled();
});
});
});
it("displays unknown error message", () => {
it('displays unknown error message', () => {
component.connect.isErrorState = jasmine.createSpy().and.returnValue(true);
component.connect.proxy!.errorText = "bad things are happening";
component.connect.proxy!.errorText = 'bad things are happening';
fixture.detectChanges();
fixture.whenStable().then( () => {
const el = htmlElement.querySelector(".unknown-error");
expect(el?.innerHTML).toContain("Error:");
expect(el?.innerHTML).toContain("bad things are happening");
const retry: HTMLButtonElement | null = htmlElement.querySelector(".retry-btn");
fixture.whenStable().then(() => {
const el = htmlElement.querySelector('.unknown-error');
expect(el?.innerHTML).toContain('Error:');
expect(el?.innerHTML).toContain('bad things are happening');
const retry: HTMLButtonElement | null = htmlElement.querySelector('.retry-btn');
expect(retry).toBeInstanceOf(HTMLButtonElement);
retry?.dispatchEvent(new Event("click"));
retry?.dispatchEvent(new Event('click'));
fixture.whenStable().then(() => {
expect(component.connect.restart).toHaveBeenCalled();
});
});
});
it("displays end tracing elements", () => {
it('displays end tracing elements', () => {
component.connect.isEndTraceState = jasmine.createSpy().and.returnValue(true);
fixture.detectChanges();
fixture.whenStable().then( () => {
const el = htmlElement.querySelector(".end-tracing");
expect(el?.innerHTML).toContain("Tracing...");
expect(htmlElement.querySelector("mat-progress-bar")).toBeTruthy();
fixture.whenStable().then(() => {
const el = htmlElement.querySelector('.end-tracing');
expect(el?.innerHTML).toContain('Tracing...');
expect(htmlElement.querySelector('mat-progress-bar')).toBeTruthy();
const end: HTMLButtonElement | null = htmlElement.querySelector(".end");
const end: HTMLButtonElement | null = htmlElement.querySelector('.end');
expect(end).toBeInstanceOf(HTMLButtonElement);
end?.dispatchEvent(new Event("click"));
end?.dispatchEvent(new Event('click'));
fixture.whenStable().then(() => {
expect(component.endTrace).toHaveBeenCalled();
expect(component.connect.endTrace).toHaveBeenCalled();
@@ -268,12 +272,12 @@ describe("CollectTracesComponent", () => {
});
});
it("displays loading data elements", () => {
it('displays loading data elements', () => {
component.connect.isLoadDataState = jasmine.createSpy().and.returnValue(true);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(htmlElement.querySelector(".load-data")?.innerHTML).toContain("Loading data...");
expect(htmlElement.querySelector("mat-progress-bar")).toBeTruthy();
expect(htmlElement.querySelector('.load-data')?.innerHTML).toContain('Loading data...');
expect(htmlElement.querySelector('mat-progress-bar')).toBeTruthy();
});
});
});

View File

@@ -18,38 +18,55 @@ import {
ChangeDetectorRef,
Component,
EventEmitter,
Input,
Inject,
Output,
OnInit,
OnDestroy,
Input,
NgZone,
ViewEncapsulation
} from "@angular/core";
import { TraceFile} from "common/trace/trace";
import { TraceData} from "app/trace_data";
import { ProxyConnection } from "trace_collection/proxy_connection";
import { Connection } from "trace_collection/connection";
import { ProxyState } from "trace_collection/proxy_client";
import { traceConfigurations, configMap, SelectionConfiguration, EnableConfiguration } from "trace_collection/trace_collection_utils";
import { PersistentStore } from "common/utils/persistent_store";
import { MatSnackBar } from "@angular/material/snack-bar";
import { ParserErrorSnackBarComponent } from "./parser_error_snack_bar_component";
import { TracingConfig } from "trace_collection/tracing_config";
OnDestroy,
OnInit,
Output,
ViewEncapsulation,
} from '@angular/core';
import {MatSnackBar} from '@angular/material/snack-bar';
import {TraceData} from 'app/trace_data';
import {TraceFile} from 'common/trace/trace';
import {PersistentStore} from 'common/utils/persistent_store';
import {Connection} from 'trace_collection/connection';
import {ProxyState} from 'trace_collection/proxy_client';
import {ProxyConnection} from 'trace_collection/proxy_connection';
import {
configMap,
EnableConfiguration,
SelectionConfiguration,
traceConfigurations,
} from 'trace_collection/trace_collection_utils';
import {TracingConfig} from 'trace_collection/tracing_config';
import {ParserErrorSnackBarComponent} from './parser_error_snack_bar_component';
@Component({
selector: "collect-traces",
selector: 'collect-traces',
template: `
<mat-card class="collect-card">
<mat-card-title class="title">Collect Traces</mat-card-title>
<mat-card-content class="collect-card-content">
<p *ngIf="connect.isConnectingState()" class="connecting-message mat-body-1">Connecting...</p>
<p *ngIf="connect.isConnectingState()" class="connecting-message mat-body-1">
Connecting...
</p>
<div *ngIf="!connect.adbSuccess()" class="set-up-adb">
<button class="proxy-tab" color="primary" mat-stroked-button [ngClass]="tabClass(true)" (click)="displayAdbProxyTab()">ADB Proxy</button>
<button
class="proxy-tab"
color="primary"
mat-stroked-button
[ngClass]="tabClass(true)"
(click)="displayAdbProxyTab()">
ADB Proxy
</button>
<!-- <button class="web-tab" color="primary" mat-raised-button [ngClass]="tabClass(false)" (click)="displayWebAdbTab()">Web ADB</button> -->
<adb-proxy *ngIf="isAdbProxy" [(proxy)]="connect.proxy!" (addKey)="onAddKey($event)"></adb-proxy>
<adb-proxy
*ngIf="isAdbProxy"
[(proxy)]="connect.proxy!"
(addKey)="onAddKey($event)"></adb-proxy>
<!-- <web-adb *ngIf="!isAdbProxy"></web-adb> TODO: fix web adb workflow -->
</div>
@@ -64,41 +81,66 @@ import { TracingConfig } from "trace_collection/tracing_config";
<mat-list-item
*ngFor="let deviceId of objectKeys(connect.devices())"
(click)="connect.selectDevice(deviceId)"
class="available-device"
>
class="available-device">
<mat-icon matListIcon>
{{ connect.devices()[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }}
{{
connect.devices()[deviceId].authorised ? 'smartphone' : 'screen_lock_portrait'
}}
</mat-icon>
<p matLine>
{{ connect.devices()[deviceId].authorised ? connect.devices()[deviceId]?.model : "unauthorised" }} ({{ deviceId }})
{{
connect.devices()[deviceId].authorised
? connect.devices()[deviceId]?.model
: 'unauthorised'
}}
({{ deviceId }})
</p>
</mat-list-item>
</mat-list>
</div>
</div>
<div *ngIf="connect.isStartTraceState() || connect.isEndTraceState() || connect.isLoadDataState()" class="trace-collection-config">
<div
*ngIf="
connect.isStartTraceState() || connect.isEndTraceState() || connect.isLoadDataState()
"
class="trace-collection-config">
<mat-list>
<mat-list-item>
<mat-icon matListIcon>smartphone</mat-icon>
<p matLine>
{{ connect.selectedDevice()?.model }} ({{ connect.selectedDeviceId() }})
<button color="primary" class="change-btn" mat-button (click)="connect.resetLastDevice()" [disabled]="connect.isEndTraceState() || connect.isLoadDataState()">Change device</button>
<button
color="primary"
class="change-btn"
mat-button
(click)="connect.resetLastDevice()"
[disabled]="connect.isEndTraceState() || connect.isLoadDataState()">
Change device
</button>
</p>
</mat-list-item>
</mat-list>
<mat-tab-group class="tracing-tabs">
<mat-tab label="Trace" [disabled]="connect.isEndTraceState() || connect.isLoadDataState()">
<mat-tab
label="Trace"
[disabled]="connect.isEndTraceState() || connect.isLoadDataState()">
<div class="tabbed-section">
<div class="trace-section" *ngIf="tracingConfig.tracingConfigIsSet() && connect.isStartTraceState()">
<div
class="trace-section"
*ngIf="tracingConfig.tracingConfigIsSet() && connect.isStartTraceState()">
<trace-config [traces]="tracingConfig.getTracingConfig()"></trace-config>
<div class="start-btn">
<button color="primary" mat-stroked-button (click)="startTracing()">Start trace</button>
<button color="primary" mat-stroked-button (click)="startTracing()">
Start trace
</button>
</div>
</div>
<div class="loading-info" *ngIf="!tracingConfig.tracingConfigIsSet() && connect.isStartTraceState()">
<div
class="loading-info"
*ngIf="!tracingConfig.tracingConfigIsSet() && connect.isStartTraceState()">
<p class="mat-body-1">Loading tracing config...</p>
</div>
@@ -109,7 +151,9 @@ import { TracingConfig } from "trace_collection/tracing_config";
<p class="mat-body-1">Tracing...</p>
</div>
<div class="end-btn">
<button color="primary" mat-raised-button (click)="endTrace()">End trace</button>
<button color="primary" mat-raised-button (click)="endTrace()">
End trace
</button>
</div>
</div>
@@ -117,14 +161,20 @@ import { TracingConfig } from "trace_collection/tracing_config";
<load-progress [progressPercentage]="loadProgress" [message]="'Loading data...'">
</load-progress>
<div class="end-btn">
<button color="primary" mat-raised-button (click)="endTrace()" disabled="true">End trace</button>
<button color="primary" mat-raised-button (click)="endTrace()" disabled="true">
End trace
</button>
</div>
</div>
</div>
</mat-tab>
<mat-tab label="Dump" [disabled]="connect.isEndTraceState() || connect.isLoadDataState()">
<mat-tab
label="Dump"
[disabled]="connect.isEndTraceState() || connect.isLoadDataState()">
<div class="tabbed-section">
<div class="dump-section" *ngIf="tracingConfig.tracingConfigIsSet() && connect.isStartTraceState()">
<div
class="dump-section"
*ngIf="tracingConfig.tracingConfigIsSet() && connect.isStartTraceState()">
<h3 class="mat-subheading-2">Dump targets</h3>
<div class="selection">
<mat-checkbox
@@ -132,10 +182,13 @@ import { TracingConfig } from "trace_collection/tracing_config";
color="primary"
class="dump-checkbox"
[(ngModel)]="tracingConfig.getDumpConfig()[dumpKey].run"
>{{tracingConfig.getDumpConfig()[dumpKey].name}}</mat-checkbox>
>{{ tracingConfig.getDumpConfig()[dumpKey].name }}</mat-checkbox
>
</div>
<div class="dump-btn">
<button color="primary" mat-stroked-button (click)="dumpState()">Dump state</button>
<button color="primary" mat-stroked-button (click)="dumpState()">
Dump state
</button>
</div>
</div>
@@ -143,9 +196,10 @@ import { TracingConfig } from "trace_collection/tracing_config";
<p class="mat-body-1">Loading dumping config...</p>
</div>
<load-progress *ngIf="connect.isLoadDataState()"
[progressPercentage]="loadProgress"
[message]="'Loading data...'">
<load-progress
*ngIf="connect.isLoadDataState()"
[progressPercentage]="loadProgress"
[message]="'Loading data...'">
</load-progress>
</div>
</mat-tab>
@@ -158,15 +212,17 @@ import { TracingConfig } from "trace_collection/tracing_config";
Error:
</p>
<pre> {{ connect.proxy?.errorText }} </pre>
<button color="primary" class="retry-btn" mat-raised-button (click)="connect.restart()">Retry</button>
<button color="primary" class="retry-btn" mat-raised-button (click)="connect.restart()">
Retry
</button>
</div>
</mat-card-content>
</mat-card>
`,
styles: [
`
.change-btn, .retry-btn {
.change-btn,
.retry-btn {
margin-left: 5px;
}
.mat-card.collect-card {
@@ -198,16 +254,25 @@ import { TracingConfig } from "trace_collection/tracing_config";
flex-direction: column;
gap: 10px;
}
.trace-section, .dump-section, .end-tracing, .load-data {
.trace-section,
.dump-section,
.end-tracing,
.load-data {
height: 100%;
}
.trace-collection-config {
height: 100%;
}
.proxy-tab, .web-tab, .start-btn, .dump-btn, .end-btn {
.proxy-tab,
.web-tab,
.start-btn,
.dump-btn,
.end-btn {
align-self: flex-start;
}
.start-btn, .dump-btn, .end-btn {
.start-btn,
.dump-btn,
.end-btn {
margin: auto 0 0 0;
padding: 1rem 0 0 0;
}
@@ -232,7 +297,8 @@ import { TracingConfig } from "trace_collection/tracing_config";
height: 100%;
}
.no-device-detected p, .device-selection p.instruction {
.no-device-detected p,
.device-selection p.instruction {
padding-top: 1rem;
opacity: 0.6;
font-size: 1.2rem;
@@ -272,7 +338,8 @@ import { TracingConfig } from "trace_collection/tracing_config";
height: 100%;
}
.load-data p, .end-tracing p {
.load-data p,
.end-tracing p {
opacity: 0.7;
}
@@ -292,9 +359,9 @@ import { TracingConfig } from "trace_collection/tracing_config";
load-progress {
height: 100%;
}
`
`,
],
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
})
export class CollectTracesComponent implements OnInit, OnDestroy {
objectKeys = Object.keys;
@@ -364,24 +431,20 @@ export class CollectTracesComponent implements OnInit, OnDestroy {
}
public startTracing() {
console.log("begin tracing");
console.log('begin tracing');
this.tracingConfig.requestedTraces = this.requestedTraces();
const reqEnableConfig = this.requestedEnableConfig();
const reqSelectedSfConfig = this.requestedSelection("layers_trace");
const reqSelectedWmConfig = this.requestedSelection("window_trace");
const reqSelectedSfConfig = this.requestedSelection('layers_trace');
const reqSelectedWmConfig = this.requestedSelection('window_trace');
if (this.tracingConfig.requestedTraces.length < 1) {
this.connect.throwNoTargetsError();
return;
}
this.connect.startTrace(
reqEnableConfig,
reqSelectedSfConfig,
reqSelectedWmConfig
);
this.connect.startTrace(reqEnableConfig, reqSelectedSfConfig, reqSelectedWmConfig);
}
public async dumpState() {
console.log("begin dump");
console.log('begin dump');
this.tracingConfig.requestedDumps = this.requestedDumps();
const dumpSuccessful = await this.connect.dumpState();
if (dumpSuccessful) {
@@ -392,7 +455,7 @@ export class CollectTracesComponent implements OnInit, OnDestroy {
}
public async endTrace() {
console.log("end tracing");
console.log('end tracing');
await this.connect.endTrace();
await this.loadFiles();
}
@@ -400,11 +463,11 @@ export class CollectTracesComponent implements OnInit, OnDestroy {
public tabClass(adbTab: boolean) {
let isActive: string;
if (adbTab) {
isActive = this.isAdbProxy ? "active" : "inactive";
isActive = this.isAdbProxy ? 'active' : 'inactive';
} else {
isActive = !this.isAdbProxy ? "active" : "inactive";
isActive = !this.isAdbProxy ? 'active' : 'inactive';
}
return ["tab", isActive];
return ['tab', isActive];
}
private onProxyChange(newState: ProxyState) {
@@ -414,47 +477,41 @@ export class CollectTracesComponent implements OnInit, OnDestroy {
private requestedTraces() {
const tracesFromCollection: Array<string> = [];
const tracingConfig = this.tracingConfig.getTracingConfig();
const req = Object.keys(tracingConfig)
.filter((traceKey:string) => {
const traceConfig = tracingConfig[traceKey];
if (traceConfig.isTraceCollection) {
traceConfig.config?.enableConfigs.forEach((innerTrace:EnableConfiguration) => {
if (innerTrace.enabled) {
tracesFromCollection.push(innerTrace.key);
}
});
return false;
}
return traceConfig.run;
});
const req = Object.keys(tracingConfig).filter((traceKey: string) => {
const traceConfig = tracingConfig[traceKey];
if (traceConfig.isTraceCollection) {
traceConfig.config?.enableConfigs.forEach((innerTrace: EnableConfiguration) => {
if (innerTrace.enabled) {
tracesFromCollection.push(innerTrace.key);
}
});
return false;
}
return traceConfig.run;
});
return req.concat(tracesFromCollection);
}
private requestedDumps() {
const dumpConfig = this.tracingConfig.getDumpConfig();
return Object.keys(dumpConfig)
.filter((dumpKey:string) => {
return dumpConfig[dumpKey].run;
});
return Object.keys(dumpConfig).filter((dumpKey: string) => {
return dumpConfig[dumpKey].run;
});
}
private requestedEnableConfig(): Array<string> {
const req: Array<string> = [];
const tracingConfig = this.tracingConfig.getTracingConfig();
Object.keys(tracingConfig)
.forEach((traceKey:string) => {
const trace = tracingConfig[traceKey];
if(!trace.isTraceCollection
&& trace.run
&& trace.config
&& trace.config.enableConfigs) {
trace.config.enableConfigs.forEach((con:EnableConfiguration) => {
if (con.enabled) {
req.push(con.key);
}
});
}
});
Object.keys(tracingConfig).forEach((traceKey: string) => {
const trace = tracingConfig[traceKey];
if (!trace.isTraceCollection && trace.run && trace.config && trace.config.enableConfigs) {
trace.config.enableConfigs.forEach((con: EnableConfiguration) => {
if (con.enabled) {
req.push(con.key);
}
});
}
});
return req;
}
@@ -464,22 +521,20 @@ export class CollectTracesComponent implements OnInit, OnDestroy {
return undefined;
}
const selected: configMap = {};
tracingConfig[traceType].config?.selectionConfigs.forEach(
(con: SelectionConfiguration) => {
selected[con.key] = con.value;
}
);
tracingConfig[traceType].config?.selectionConfigs.forEach((con: SelectionConfiguration) => {
selected[con.key] = con.value;
});
return selected;
}
private async loadFiles() {
console.log("loading files", this.connect.adbData());
console.log('loading files', this.connect.adbData());
this.traceData.clear();
const traceFiles = this.connect.adbData().map(file => new TraceFile(file));
const traceFiles = this.connect.adbData().map((file) => new TraceFile(file));
const parserErrors = await this.traceData.loadTraces(traceFiles);
ParserErrorSnackBarComponent.showIfNeeded(this.ngZone, this.snackBar, parserErrors);
this.traceDataLoaded.emit();
console.log("finished loading data!");
console.log('finished loading data!');
}
private onLoadProgressUpdate(progress: number) {

View File

@@ -14,26 +14,25 @@
* limitations under the License.
*/
import {Component, Input} from "@angular/core";
import {Component, Input} from '@angular/core';
@Component({
selector: "load-progress",
selector: 'load-progress',
template: `
<div class="container-progress">
<p class="mat-body-3">
<mat-icon fontIcon="sync">
</mat-icon>
<mat-icon fontIcon="sync"> </mat-icon>
</p>
<mat-progress-bar *ngIf="progressPercentage === undefined"
mode="indeterminate">
<mat-progress-bar *ngIf="progressPercentage === undefined" mode="indeterminate">
</mat-progress-bar>
<mat-progress-bar *ngIf="progressPercentage !== undefined"
mode="determinate"
[value]="progressPercentage">
<mat-progress-bar
*ngIf="progressPercentage !== undefined"
mode="determinate"
[value]="progressPercentage">
</mat-progress-bar>
<p class="mat-body-1">{{message}}</p>
<p class="mat-body-1">{{ message }}</p>
</div>
`,
styles: [
@@ -60,10 +59,10 @@ import {Component, Input} from "@angular/core";
mat-card-content {
flex-grow: 1;
}
`
]
`,
],
})
export class LoadProgressComponent {
@Input() progressPercentage?: number;
@Input() message = "Loading...";
@Input() message = 'Loading...';
}

View File

@@ -13,17 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Component, Inject, NgZone} from "@angular/core";
import {MAT_SNACK_BAR_DATA, MatSnackBar, MatSnackBarRef} from "@angular/material/snack-bar";
import {ParserError, ParserErrorType} from "parsers/parser_factory";
import {TRACE_INFO} from "app/trace_info";
import {Component, Inject, NgZone} from '@angular/core';
import {MatSnackBar, MatSnackBarRef, MAT_SNACK_BAR_DATA} from '@angular/material/snack-bar';
import {TRACE_INFO} from 'app/trace_info';
import {ParserError, ParserErrorType} from 'parsers/parser_factory';
@Component({
selector: "upload-snack-bar",
selector: 'upload-snack-bar',
template: `
<div class="snack-bar-container">
<p *ngFor="let message of messages" class="mat-body-1">
{{message}}
{{ message }}
</p>
<button color="primary" mat-button class="snack-bar-action" (click)="snackBarRef.dismiss()">
Close
@@ -39,16 +39,14 @@ import {TRACE_INFO} from "app/trace_info";
.snack-bar-action {
margin-left: 12px;
}
`
]
`,
],
})
export class ParserErrorSnackBarComponent {
constructor(
@Inject(MatSnackBarRef) public snackBarRef: MatSnackBarRef<ParserErrorSnackBarComponent>,
@Inject(MAT_SNACK_BAR_DATA) public messages: string[]
) {
}
) {}
static showIfNeeded(ngZone: NgZone, snackBar: MatSnackBar, errors: ParserError[]) {
const messages = this.convertErrorsToMessages(errors);
@@ -76,7 +74,7 @@ export class ParserErrorSnackBarComponent {
const countUsed = Math.min(groupedErrors.length, CROP_THRESHOLD);
const countCropped = groupedErrors.length - countUsed;
groupedErrors.slice(0, countUsed).forEach(error => {
groupedErrors.slice(0, countUsed).forEach((error) => {
messages.push(this.convertErrorToMessage(error));
});
@@ -89,39 +87,38 @@ export class ParserErrorSnackBarComponent {
}
private static convertErrorToMessage(error: ParserError): string {
const fileName = error.trace !== undefined ?
error.trace.name : "<no file name>";
const traceTypeName = error.traceType !== undefined ?
TRACE_INFO[error.traceType].name : "<unknown>";
const fileName = error.trace !== undefined ? error.trace.name : '<no file name>';
const traceTypeName =
error.traceType !== undefined ? TRACE_INFO[error.traceType].name : '<unknown>';
switch (error.type) {
case ParserErrorType.NO_INPUT_FILES:
return "No input files";
case ParserErrorType.UNSUPPORTED_FORMAT:
return `${fileName}: unsupported file format`;
case ParserErrorType.OVERRIDE: {
return `${fileName}: overridden by another trace of type ${traceTypeName}`;
}
default:
return `${fileName}: unknown error occurred`;
case ParserErrorType.NO_INPUT_FILES:
return 'No input files';
case ParserErrorType.UNSUPPORTED_FORMAT:
return `${fileName}: unsupported file format`;
case ParserErrorType.OVERRIDE: {
return `${fileName}: overridden by another trace of type ${traceTypeName}`;
}
default:
return `${fileName}: unknown error occurred`;
}
}
private static makeCroppedMessage(type: ParserErrorType, count: number): string {
switch(type) {
case ParserErrorType.OVERRIDE:
return `... (cropped ${count} overridden trace messages)`;
case ParserErrorType.UNSUPPORTED_FORMAT:
return `... (cropped ${count} unsupported file format messages)`;
default:
return `... (cropped ${count} unknown error messages)`;
switch (type) {
case ParserErrorType.OVERRIDE:
return `... (cropped ${count} overridden trace messages)`;
case ParserErrorType.UNSUPPORTED_FORMAT:
return `... (cropped ${count} unsupported file format messages)`;
default:
return `... (cropped ${count} unknown error messages)`;
}
}
private static groupErrorsByType(errors: ParserError[]): Map<ParserErrorType, ParserError[]> {
const groups = new Map<ParserErrorType, ParserError[]>();
errors.forEach(error => {
errors.forEach((error) => {
if (groups.get(error.type) === undefined) {
groups.set(error.type, []);
}

View File

@@ -14,33 +14,43 @@
* limitations under the License.
*/
import { Component, ElementRef, EventEmitter, HostListener, Input, Output, QueryList, ViewChild, ViewChildren } from "@angular/core";
import { TimelineData } from "app/timeline_data";
import { TRACE_INFO } from "app/trace_info";
import { Timestamp } from "common/trace/timestamp";
import { SingleTimelineComponent } from "./single_timeline.component";
import {
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
Output,
QueryList,
ViewChild,
ViewChildren,
} from '@angular/core';
import {TimelineData} from 'app/timeline_data';
import {TRACE_INFO} from 'app/trace_info';
import {Timestamp} from 'common/trace/timestamp';
import {SingleTimelineComponent} from './single_timeline.component';
@Component({
selector: "expanded-timeline",
selector: 'expanded-timeline',
template: `
<div id="expanded-timeline-wrapper" #expandedTimelineWrapper>
<div *ngFor="let timeline of this.data | keyvalue" class="timeline">
<div class="icon-wrapper">
<mat-icon class="icon"
[style]="{ color: TRACE_INFO[timeline.key].color }">
<mat-icon class="icon" [style]="{color: TRACE_INFO[timeline.key].color}">
{{ TRACE_INFO[timeline.key].icon }}
</mat-icon>
</div>
<!-- TODO: Timestamp variables are passed to single-timeline, but single-timeline takes bigint parameters. Why the heck is this working??? -->
<single-timeline
[color]="TRACE_INFO[timeline.key].color"
[entries]="timeline.value"
[selected]="timelineData.getCurrentTimestampFor(timeline.key)?.timestamp?.getValueNs() ?? undefined"
[start]="start"
[end]="end"
(onTimestampChanged)="onTimestampChanged.emit($event)"
class="single-timeline"
></single-timeline>
[color]="TRACE_INFO[timeline.key].color"
[entries]="timeline.value"
[selected]="
timelineData.getCurrentTimestampFor(timeline.key)?.timestamp?.getValueNs() ?? undefined
"
[start]="start"
[end]="end"
(onTimestampChanged)="onTimestampChanged.emit($event)"
class="single-timeline"></single-timeline>
<div class="icon-wrapper">
<mat-icon class="icon placeholder-icon"></mat-icon>
</div>
@@ -71,57 +81,59 @@ import { SingleTimelineComponent } from "./single_timeline.component";
</div> -->
</div>
`,
styles: [`
#expanded-timeline-wrapper {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
}
#pointer-overlay {
pointer-events:none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: stretch;
}
.timeline {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
}
.timeline .single-timeline {
flex-grow: 1;
}
.selection-cursor {
flex-grow: 1;
}
.timeline {
border-bottom: 1px solid #F1F3F4;
}
.icon-wrapper {
background-color: #F1F3F4;
align-self: stretch;
display: flex;
justify-content: center;
}
.icon {
margin: 1rem;
align-self: center;
}
.units-row {
flex-grow: 1;
align-self: baseline;
}
.units-row .placeholder-icon {
visibility: hidden;
}
`]
styles: [
`
#expanded-timeline-wrapper {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
}
#pointer-overlay {
pointer-events: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: stretch;
}
.timeline {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
}
.timeline .single-timeline {
flex-grow: 1;
}
.selection-cursor {
flex-grow: 1;
}
.timeline {
border-bottom: 1px solid #f1f3f4;
}
.icon-wrapper {
background-color: #f1f3f4;
align-self: stretch;
display: flex;
justify-content: center;
}
.icon {
margin: 1rem;
align-self: center;
}
.units-row {
flex-grow: 1;
align-self: baseline;
}
.units-row .placeholder-icon {
visibility: hidden;
}
`,
],
})
export class ExpandedTimelineComponent {
@Input() timelineData!: TimelineData;
@@ -129,8 +141,8 @@ export class ExpandedTimelineComponent {
@Output() onTimestampChanged = new EventEmitter<Timestamp>();
@ViewChild("canvas", {static: false}) canvasRef!: ElementRef<HTMLCanvasElement>;
@ViewChild("expandedTimelineWrapper", {static: false}) warpperRef!: ElementRef;
@ViewChild('canvas', {static: false}) canvasRef!: ElementRef<HTMLCanvasElement>;
@ViewChild('expandedTimelineWrapper', {static: false}) warpperRef!: ElementRef;
@ViewChildren(SingleTimelineComponent) singleTimelines!: QueryList<SingleTimelineComponent>;
TRACE_INFO = TRACE_INFO;
@@ -144,7 +156,9 @@ export class ExpandedTimelineComponent {
}
get sortedMergedTimestamps() {
return Array.from(this.data.values()).flatMap( it => it ).sort();
return Array.from(this.data.values())
.flatMap((it) => it)
.sort();
}
get start() {
@@ -155,7 +169,7 @@ export class ExpandedTimelineComponent {
return this.timelineData.getSelectionRange().to;
}
@HostListener("window:resize", ["$event"])
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
this.resizeCanvases();
}
@@ -167,15 +181,15 @@ export class ExpandedTimelineComponent {
for (const timeline of this.singleTimelines) {
timeline.canvas.width = 0;
timeline.canvas.height = 0;
timeline.canvas.style.width = "auto";
timeline.canvas.style.height = "auto";
timeline.canvas.style.width = 'auto';
timeline.canvas.style.height = 'auto';
}
for (const timeline of this.singleTimelines) {
timeline.initializeCanvas();
timeline.canvas.height = 0;
timeline.canvas.style.width = "auto";
timeline.canvas.style.height = "auto";
timeline.canvas.style.width = 'auto';
timeline.canvas.style.height = 'auto';
}
}
}

View File

@@ -14,20 +14,20 @@
* limitations under the License.
*/
import { TRACE_INFO } from "app/trace_info";
import { Color } from "../../colors";
import { TraceType } from "common/trace/trace_type";
import { CanvasDrawer } from "../canvas/canvas_drawer";
import { DraggableCanvasObject } from "../canvas/draggable_canvas_object";
import { CanvasMouseHandler } from "../canvas/canvas_mouse_handler";
import { BigIntSegment, Segment, TimelineData } from "./utils";
import {TRACE_INFO} from 'app/trace_info';
import {TraceType} from 'common/trace/trace_type';
import {Color} from '../../colors';
import {CanvasDrawer} from '../canvas/canvas_drawer';
import {CanvasMouseHandler} from '../canvas/canvas_mouse_handler';
import {DraggableCanvasObject} from '../canvas/draggable_canvas_object';
import {BigIntSegment, Segment, TimelineData} from './utils';
export class MiniCanvasDrawerInput {
constructor(
public fullRange: BigIntSegment,
public selectedPosition: bigint,
public selection: BigIntSegment,
public timelineEntries: TimelineData,
public timelineEntries: TimelineData
) {}
public transform(mapToRange: Segment): MiniCanvasDrawerData {
@@ -43,7 +43,7 @@ export class MiniCanvasDrawerInput {
);
}
private computeTransformedTraceSegments(transformer: Transformer): Map<TraceType, number[]> {
private computeTransformedTraceSegments(transformer: Transformer): Map<TraceType, number[]> {
const transformedTraceSegments = new Map<TraceType, number[]>();
this.timelineEntries.forEach((entries, traceType) => {
@@ -75,20 +75,21 @@ export class Transformer {
}
public transform(x: bigint): number {
return this.toOffset + this.targetWidth * Number(x - this.fromOffset) / Number(this.fromWidth);
return (
this.toOffset + (this.targetWidth * Number(x - this.fromOffset)) / Number(this.fromWidth)
);
}
public untransform(x: number): bigint {
x = Math.round(x);
return this.fromOffset + BigInt((x - this.toOffset)) * this.fromWidth / BigInt(this.targetWidth);
return (
this.fromOffset + (BigInt(x - this.toOffset) * this.fromWidth) / BigInt(this.targetWidth)
);
}
}
class MiniCanvasDrawerOutput {
constructor(
public selectedPosition: bigint,
public selection: BigIntSegment,
) {}
constructor(public selectedPosition: bigint, public selection: BigIntSegment) {}
}
class MiniCanvasDrawerData {
@@ -100,18 +101,14 @@ class MiniCanvasDrawerData {
) {}
public toOutput(): MiniCanvasDrawerOutput {
return new MiniCanvasDrawerOutput(
this.transformer.untransform(this.selectedPosition),
{
from: this.transformer.untransform(this.selection.from),
to: this.transformer.untransform(this.selection.to),
}
);
return new MiniCanvasDrawerOutput(this.transformer.untransform(this.selectedPosition), {
from: this.transformer.untransform(this.selection.from),
to: this.transformer.untransform(this.selection.to),
});
}
}
export class MiniCanvasDrawer implements CanvasDrawer {
public ctx: CanvasRenderingContext2D;
public handler: CanvasMouseHandler;
@@ -142,7 +139,7 @@ export class MiniCanvasDrawer implements CanvasDrawer {
get usableRange() {
return {
from: this.padding.left,
to: this.getWidth() - this.padding.left - this.padding.right
to: this.getWidth() - this.padding.left - this.padding.right,
};
}
@@ -156,12 +153,12 @@ export class MiniCanvasDrawer implements CanvasDrawer {
private onPointerPositionDragging: (pos: bigint) => void,
private onPointerPositionChanged: (pos: bigint) => void,
private onSelectionChanged: (selection: BigIntSegment) => void,
private onUnhandledClick: (pos: bigint) => void,
private onUnhandledClick: (pos: bigint) => void
) {
const ctx = canvas.getContext("2d");
const ctx = canvas.getContext('2d');
if (ctx === null) {
throw Error("MiniTimeline canvas context was null!");
throw Error('MiniTimeline canvas context was null!');
}
this.ctx = ctx;
@@ -169,7 +166,7 @@ export class MiniCanvasDrawer implements CanvasDrawer {
const onUnhandledClickInternal = (x: number, y: number) => {
this.onUnhandledClick(this.input.transformer.untransform(x));
};
this.handler = new CanvasMouseHandler(this, "pointer", onUnhandledClickInternal);
this.handler = new CanvasMouseHandler(this, 'pointer', onUnhandledClickInternal);
this.activePointer = new DraggableCanvasObject(
this,
@@ -189,7 +186,7 @@ export class MiniCanvasDrawer implements CanvasDrawer {
},
{
fillStyle: Color.ACTIVE_POINTER,
fill: true
fill: true,
},
(x) => {
this.input.selectedPosition = x;
@@ -211,14 +208,14 @@ export class MiniCanvasDrawer implements CanvasDrawer {
this.selection.from = x;
this.onSelectionChanged({
from: this.input.transformer.untransform(x),
to: this.input.transformer.untransform(this.selection.to)
to: this.input.transformer.untransform(this.selection.to),
});
};
const onRightSelectionChanged = (x: number) => {
this.selection.to = x;
this.onSelectionChanged({
from: this.input.transformer.untransform(this.selection.from),
to: this.input.transformer.untransform(x)
to: this.input.transformer.untransform(x),
});
};
@@ -246,7 +243,7 @@ export class MiniCanvasDrawer implements CanvasDrawer {
() => {
return {
from: this.usableRange.from,
to: this.rightFocusSectionSelector.position - selectorArrowWidth - barWidth
to: this.rightFocusSectionSelector.position - selectorArrowWidth - barWidth,
};
}
);
@@ -270,7 +267,7 @@ export class MiniCanvasDrawer implements CanvasDrawer {
() => {
return {
from: this.leftFocusSectionSelector.position + selectorArrowWidth + barWidth,
to: this.usableRange.to
to: this.usableRange.to,
};
}
);
@@ -323,14 +320,19 @@ export class MiniCanvasDrawer implements CanvasDrawer {
this.ctx.globalAlpha = 0.8;
this.ctx.fillStyle = Color.SELECTION_BACKGROUND;
const width = this.selection.to - this.selection.from;
this.ctx.fillRect(this.selection.from, this.padding.top + triangleHeight / 2, width, this.innerHeight - triangleHeight / 2);
this.ctx.fillRect(
this.selection.from,
this.padding.top + triangleHeight / 2,
width,
this.innerHeight - triangleHeight / 2
);
this.ctx.restore();
}
private drawTraceLines() {
const lineHeight = this.innerHeight / 8;
let fromTop = this.padding.top + this.innerHeight * 2 / 3 - lineHeight;
let fromTop = this.padding.top + (this.innerHeight * 2) / 3 - lineHeight;
this.timelineEntries.forEach((entries, traceType) => {
// TODO: Only if active or a selected trace
@@ -343,45 +345,67 @@ export class MiniCanvasDrawer implements CanvasDrawer {
this.ctx.globalAlpha = 1.0;
}
fromTop -= lineHeight * 4/3;
fromTop -= (lineHeight * 4) / 3;
});
}
private drawTimelineGuides() {
const edgeBarHeight = this.innerHeight * 1 / 2;
const edgeBarHeight = (this.innerHeight * 1) / 2;
const edgeBarWidth = 4;
const boldBarHeight = this.innerHeight * 1 / 5;
const boldBarHeight = (this.innerHeight * 1) / 5;
const boldBarWidth = edgeBarWidth;
const lightBarHeight = this.innerHeight * 1 / 6;
const lightBarHeight = (this.innerHeight * 1) / 6;
const lightBarWidth = 2;
const minSpacing = lightBarWidth * 7;
const barsInSetWidth = (9 * lightBarWidth + boldBarWidth);
const barSets = Math.floor((this.getWidth() - edgeBarWidth * 2 - minSpacing) / (barsInSetWidth + 10 * minSpacing));
const barsInSetWidth = 9 * lightBarWidth + boldBarWidth;
const barSets = Math.floor(
(this.getWidth() - edgeBarWidth * 2 - minSpacing) / (barsInSetWidth + 10 * minSpacing)
);
const bars = barSets * 10;
// Draw start bar
this.ctx.fillStyle = Color.GUIDE_BAR;
this.ctx.fillRect(0, this.padding.top + this.innerHeight - edgeBarHeight, edgeBarWidth, edgeBarHeight);
this.ctx.fillRect(
0,
this.padding.top + this.innerHeight - edgeBarHeight,
edgeBarWidth,
edgeBarHeight
);
// Draw end bar
this.ctx.fillStyle = Color.GUIDE_BAR;
this.ctx.fillRect(this.getWidth() - edgeBarWidth, this.padding.top + this.innerHeight - edgeBarHeight, edgeBarWidth, edgeBarHeight);
this.ctx.fillRect(
this.getWidth() - edgeBarWidth,
this.padding.top + this.innerHeight - edgeBarHeight,
edgeBarWidth,
edgeBarHeight
);
const spacing = (this.getWidth() - barSets * barsInSetWidth - edgeBarWidth) / (bars);
const spacing = (this.getWidth() - barSets * barsInSetWidth - edgeBarWidth) / bars;
let start = edgeBarWidth + spacing;
for (let i = 1; i < bars; i++) {
if (i % 10 == 0) {
// Draw boldbar
this.ctx.fillStyle = Color.GUIDE_BAR;
this.ctx.fillRect(start, this.padding.top + this.innerHeight - boldBarHeight, boldBarWidth, boldBarHeight);
this.ctx.fillRect(
start,
this.padding.top + this.innerHeight - boldBarHeight,
boldBarWidth,
boldBarHeight
);
start += boldBarWidth; // TODO: Shift a bit
} else {
// Draw lightbar
this.ctx.fillStyle = Color.GUIDE_BAR_LIGHT;
this.ctx.fillRect(start, this.padding.top + this.innerHeight - lightBarHeight, lightBarWidth, lightBarHeight);
this.ctx.fillRect(
start,
this.padding.top + this.innerHeight - lightBarHeight,
lightBarWidth,
lightBarHeight
);
start += lightBarWidth;
}
start += spacing;

View File

@@ -14,26 +14,37 @@
* limitations under the License.
*/
import { Component, ElementRef, EventEmitter, HostListener, Input, Output, SimpleChanges, ViewChild } from "@angular/core";
import { TimelineData } from "app/timeline_data";
import { Timestamp } from "common/trace/timestamp";
import { TraceType } from "common/trace/trace_type";
import { MiniCanvasDrawer, MiniCanvasDrawerInput } from "./mini_canvas_drawer";
import {
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
Output,
SimpleChanges,
ViewChild,
} from '@angular/core';
import {TimelineData} from 'app/timeline_data';
import {Timestamp} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {MiniCanvasDrawer, MiniCanvasDrawerInput} from './mini_canvas_drawer';
@Component({
selector: "mini-timeline",
selector: 'mini-timeline',
template: `
<div id="mini-timeline-wrapper" #miniTimelineWrapper>
<canvas #canvas></canvas>
</div>
`,
styles: [`
#mini-timeline-wrapper {
width: 100%;
min-height: 5em;
height: 100%;
}
`]
styles: [
`
#mini-timeline-wrapper {
width: 100%;
min-height: 5em;
height: 100%;
}
`,
],
})
export class MiniTimelineComponent {
@Input() timelineData!: TimelineData;
@@ -41,15 +52,15 @@ export class MiniTimelineComponent {
@Input() selectedTraces!: TraceType[];
@Output() changeTimestamp = new EventEmitter<Timestamp>();
@Output() changeSeekTimestamp = new EventEmitter<Timestamp|undefined>();
@Output() changeSeekTimestamp = new EventEmitter<Timestamp | undefined>();
@ViewChild("miniTimelineWrapper", {static: false}) miniTimelineWrapper!: ElementRef;
@ViewChild("canvas", {static: false}) canvasRef!: ElementRef;
@ViewChild('miniTimelineWrapper', {static: false}) miniTimelineWrapper!: ElementRef;
@ViewChild('canvas', {static: false}) canvasRef!: ElementRef;
get canvas(): HTMLCanvasElement {
return this.canvasRef.nativeElement;
}
private drawer: MiniCanvasDrawer|undefined = undefined;
private drawer: MiniCanvasDrawer | undefined = undefined;
ngAfterViewInit(): void {
this.makeHiPPICanvas();
@@ -72,7 +83,7 @@ export class MiniTimelineComponent {
const timestampType = this.timelineData.getTimestampType()!;
this.timelineData.setSelectionRange({
from: new Timestamp(timestampType, selection.from),
to: new Timestamp(timestampType, selection.to)
to: new Timestamp(timestampType, selection.to),
});
},
updateTimestampCallback
@@ -90,12 +101,12 @@ export class MiniTimelineComponent {
return new MiniCanvasDrawerInput(
{
from: this.timelineData.getFullRange().from.getValueNs(),
to: this.timelineData.getFullRange().to.getValueNs()
to: this.timelineData.getFullRange().to.getValueNs(),
},
this.currentTimestamp.getValueNs(),
{
from: this.timelineData.getSelectionRange().from.getValueNs(),
to: this.timelineData.getSelectionRange().to.getValueNs()
to: this.timelineData.getSelectionRange().to.getValueNs(),
},
this.getTimelinesToShow()
);
@@ -104,7 +115,13 @@ export class MiniTimelineComponent {
private getTimelinesToShow() {
const timelines = new Map<TraceType, bigint[]>();
for (const type of this.selectedTraces) {
timelines.set(type, this.timelineData.getTimelines().get(type)!.map(it => it.getValueNs()));
timelines.set(
type,
this.timelineData
.getTimelines()
.get(type)!
.map((it) => it.getValueNs())
);
}
return timelines;
}
@@ -113,8 +130,8 @@ export class MiniTimelineComponent {
// Reset any size before computing new size to avoid it interfering with size computations
this.canvas.width = 0;
this.canvas.height = 0;
this.canvas.style.width = "auto";
this.canvas.style.height = "auto";
this.canvas.style.width = 'auto';
this.canvas.style.height = 'auto';
const width = this.miniTimelineWrapper.nativeElement.offsetWidth;
const height = this.miniTimelineWrapper.nativeElement.offsetHeight;
@@ -124,19 +141,19 @@ export class MiniTimelineComponent {
this.canvas.width = HiPPIwidth;
this.canvas.height = HiPPIheight;
this.canvas.style.width = width + "px";
this.canvas.style.height = height + "px";
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
// ensure all drawing operations are scaled
if (window.devicePixelRatio !== 1) {
const context = this.canvas.getContext("2d")!;
const context = this.canvas.getContext('2d')!;
context.scale(window.devicePixelRatio, window.devicePixelRatio);
}
}
@HostListener("window:resize", ["$event"])
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
this.makeHiPPICanvas();
this.drawer?.draw();
}
}
}

View File

@@ -14,38 +14,48 @@
* limitations under the License.
*/
import { Component, ElementRef, EventEmitter, HostListener, Input, Output, SimpleChanges, ViewChild } from "@angular/core";
import { Color } from "app/colors";
import {
Component,
ElementRef,
EventEmitter,
Input,
Output,
SimpleChanges,
ViewChild,
} from '@angular/core';
import {Color} from 'app/colors';
export type entry = bigint;
@Component({
selector: "single-timeline",
selector: 'single-timeline',
template: `
<div class="single-timeline" #wrapper>
<canvas #canvas></canvas>
</div>
`,
styles: [`
.single-timeline {
height: 2rem;
padding: 1rem 0;
}
`]
styles: [
`
.single-timeline {
height: 2rem;
padding: 1rem 0;
}
`,
],
})
export class SingleTimelineComponent {
@Input() selected: bigint | undefined = undefined;
@Input() color = "#AF5CF7";
@Input() color = '#AF5CF7';
@Input() start!: bigint;
@Input() end!: bigint;
@Input() entries!: bigint[];
@Output() onTimestampChanged = new EventEmitter<bigint>();
@ViewChild("canvas", {static: false}) canvasRef!: ElementRef;
@ViewChild("wrapper", {static: false}) wrapperRef!: ElementRef;
@ViewChild('canvas', {static: false}) canvasRef!: ElementRef;
@ViewChild('wrapper', {static: false}) wrapperRef!: ElementRef;
hoveringEntry: entry|null = null;
hoveringEntry: entry | null = null;
private viewInitialized = false;
@@ -54,10 +64,10 @@ export class SingleTimelineComponent {
}
get ctx(): CanvasRenderingContext2D {
const ctx = this.canvas.getContext("2d");
const ctx = this.canvas.getContext('2d');
if (ctx == null) {
throw Error("Failed to get canvas context!");
throw Error('Failed to get canvas context!');
}
return ctx;
@@ -65,7 +75,7 @@ export class SingleTimelineComponent {
ngOnInit() {
if (this.start == undefined || this.end == undefined || this.entries == undefined) {
throw Error("Not all required inputs have been set");
throw Error('Not all required inputs have been set');
}
}
@@ -79,34 +89,41 @@ export class SingleTimelineComponent {
// Reset any size before computing new size to avoid it interfering with size computations
this.canvas.width = 0;
this.canvas.height = 0;
this.canvas.style.width = "auto";
this.canvas.style.height = "auto";
this.canvas.style.width = 'auto';
this.canvas.style.height = 'auto';
const computedStyle = getComputedStyle(this.wrapperRef.nativeElement);
const width = this.wrapperRef.nativeElement.offsetWidth;
const height = this.wrapperRef.nativeElement.offsetHeight
- parseFloat(computedStyle.paddingTop)
- parseFloat(computedStyle.paddingBottom);
const height =
this.wrapperRef.nativeElement.offsetHeight -
parseFloat(computedStyle.paddingTop) -
parseFloat(computedStyle.paddingBottom);
const HiPPIwidth = window.devicePixelRatio * width;
const HiPPIheight = window.devicePixelRatio * height;
this.canvas.width = HiPPIwidth;
this.canvas.height = HiPPIheight;
this.canvas.style.width = width + "px";
this.canvas.style.height = height + "px";
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
// ensure all drawing operations are scaled
if (window.devicePixelRatio !== 1) {
const context = this.canvas.getContext("2d")!;
const context = this.canvas.getContext('2d')!;
context.scale(window.devicePixelRatio, window.devicePixelRatio);
}
this.redraw();
this.canvas.addEventListener("mousemove", (event: MouseEvent) => { this.handleMouseMove(event); });
this.canvas.addEventListener("mousedown", (event: MouseEvent) => { this.handleMouseDown(event); });
this.canvas.addEventListener("mouseout", (event: MouseEvent) => { this.handleMouseOut(event); });
this.canvas.addEventListener('mousemove', (event: MouseEvent) => {
this.handleMouseMove(event);
});
this.canvas.addEventListener('mousedown', (event: MouseEvent) => {
this.handleMouseDown(event);
});
this.canvas.addEventListener('mouseout', (event: MouseEvent) => {
this.handleMouseOut(event);
});
this.viewInitialized = true;
}
@@ -187,7 +204,7 @@ export class SingleTimelineComponent {
for (const entry of this.entries) {
this.defineEntryPath(entry);
if (this.ctx.isPointInPath(mouseX, mouseY)) {
this.canvas.style.cursor = "pointer";
this.canvas.style.cursor = 'pointer';
return entry;
}
}
@@ -196,9 +213,9 @@ export class SingleTimelineComponent {
private updateCursor(mouseX: number, mouseY: number) {
if (this.getEntryAt(mouseX, mouseY) != null) {
this.canvas.style.cursor = "pointer";
this.canvas.style.cursor = 'pointer';
}
this.canvas.style.cursor = "auto";
this.canvas.style.cursor = 'auto';
}
private handleMouseDown(e: MouseEvent) {
@@ -235,9 +252,17 @@ export class SingleTimelineComponent {
}
private defineEntryPath(entry: entry, padding = 0) {
const xPos = Number(BigInt(this.availableWidth) * (entry - this.start) / (this.end - this.start));
const xPos = Number(
(BigInt(this.availableWidth) * (entry - this.start)) / (this.end - this.start)
);
rect(this.ctx, xPos + padding, padding, this.entryWidth - 2 * padding, this.entryWidth - 2 * padding);
rect(
this.ctx,
xPos + padding,
padding,
this.entryWidth - 2 * padding,
this.entryWidth - 2 * padding
);
}
private redraw() {
@@ -277,7 +302,6 @@ export class SingleTimelineComponent {
this.ctx.restore();
}
}
function rect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number) {
@@ -288,4 +312,4 @@ function rect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h:
ctx.lineTo(x, y + h);
ctx.lineTo(x, y);
ctx.closePath();
}
}

View File

@@ -13,27 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ChangeDetectionStrategy} from "@angular/core";
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatButtonModule} from "@angular/material/button";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatIconModule} from "@angular/material/icon";
import {MatSelectModule} from "@angular/material/select";
import {ChangeDetectionStrategy} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon';
import {MatSelectModule} from '@angular/material/select';
import {MatDrawer, MatDrawerContainer, MatDrawerContent} from "app/components/bottomnav/bottom_drawer.component";
import {TimelineComponent} from "./timeline.component";
import {ExpandedTimelineComponent} from "./expanded_timeline.component";
import {MiniTimelineComponent} from "./mini_timeline.component";
import {TimelineData} from "app/timeline_data";
import {TraceType} from "common/trace/trace_type";
import {RealTimestamp, Timestamp} from "common/trace/timestamp";
import {By} from "@angular/platform-browser";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {MatInputModule} from "@angular/material/input";
import { SingleTimelineComponent } from "./single_timeline.component";
import {MatInputModule} from '@angular/material/input';
import {By} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {
MatDrawer,
MatDrawerContainer,
MatDrawerContent,
} from 'app/components/bottomnav/bottom_drawer.component';
import {TimelineData} from 'app/timeline_data';
import {RealTimestamp, Timestamp} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {ExpandedTimelineComponent} from './expanded_timeline.component';
import {MiniTimelineComponent} from './mini_timeline.component';
import {SingleTimelineComponent} from './single_timeline.component';
import {TimelineComponent} from './timeline.component';
describe("TimelineComponent", () => {
describe('TimelineComponent', () => {
let fixture: ComponentFixture<TimelineComponent>;
let component: TimelineComponent;
let htmlElement: HTMLElement;
@@ -58,10 +62,12 @@ describe("TimelineComponent", () => {
MatDrawerContent,
MiniTimelineComponent,
TimelineComponent,
]
}).overrideComponent(TimelineComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
],
})
.overrideComponent(TimelineComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default},
})
.compileComponents();
fixture = TestBed.createComponent(TimelineComponent);
component = fixture.componentInstance;
htmlElement = fixture.nativeElement;
@@ -70,40 +76,52 @@ describe("TimelineComponent", () => {
component.timelineData = timelineData;
});
it("can be created", () => {
it('can be created', () => {
expect(component).toBeTruthy();
});
it("can be expanded", () => {
it('can be expanded', () => {
const timestamps = [timestamp(100), timestamp(110)];
component.timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: timestamps
}], undefined);
component.timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: timestamps,
},
],
undefined
);
fixture.detectChanges();
const button = htmlElement.querySelector(`.${component.TOGGLE_BUTTON_CLASS}`);
expect(button).toBeTruthy();
// initially not expanded
let expandedTimelineElement = fixture.debugElement.query(By.directive(ExpandedTimelineComponent));
let expandedTimelineElement = fixture.debugElement.query(
By.directive(ExpandedTimelineComponent)
);
expect(expandedTimelineElement).toBeFalsy();
button!.dispatchEvent(new Event("click"));
button!.dispatchEvent(new Event('click'));
expandedTimelineElement = fixture.debugElement.query(By.directive(ExpandedTimelineComponent));
expect(expandedTimelineElement).toBeTruthy();
button!.dispatchEvent(new Event("click"));
button!.dispatchEvent(new Event('click'));
expandedTimelineElement = fixture.debugElement.query(By.directive(ExpandedTimelineComponent));
expect(expandedTimelineElement).toBeFalsy();
});
it("handles no timestamps", () => {
it('handles no timestamps', () => {
const timestamps: Timestamp[] = [];
component.timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: timestamps
}], undefined);
component.timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: timestamps,
},
],
undefined
);
fixture.detectChanges();
// no expand button
@@ -115,12 +133,12 @@ describe("TimelineComponent", () => {
expect(miniTimelineElement).toBeFalsy();
// error message shown
const errorMessageContainer = htmlElement.querySelector(".no-timestamps-msg");
const errorMessageContainer = htmlElement.querySelector('.no-timestamps-msg');
expect(errorMessageContainer).toBeTruthy();
expect(errorMessageContainer!.textContent).toContain("No timeline to show!");
expect(errorMessageContainer!.textContent).toContain('No timeline to show!');
});
it("processes active trace input and updates selected traces", () => {
it('processes active trace input and updates selected traces', () => {
component.activeViewTraceTypes = [TraceType.SURFACE_FLINGER];
expect(component.internalActiveTrace).toEqual(TraceType.SURFACE_FLINGER);
expect(component.selectedTraces).toEqual([TraceType.SURFACE_FLINGER]);
@@ -131,17 +149,14 @@ describe("TimelineComponent", () => {
component.activeViewTraceTypes = [TraceType.TRANSACTIONS];
expect(component.internalActiveTrace).toEqual(TraceType.TRANSACTIONS);
expect(component.selectedTraces).toEqual([
TraceType.SURFACE_FLINGER,
TraceType.TRANSACTIONS
]);
expect(component.selectedTraces).toEqual([TraceType.SURFACE_FLINGER, TraceType.TRANSACTIONS]);
component.activeViewTraceTypes = [TraceType.WINDOW_MANAGER];
expect(component.internalActiveTrace).toEqual(TraceType.WINDOW_MANAGER);
expect(component.selectedTraces).toEqual([
TraceType.SURFACE_FLINGER,
TraceType.TRANSACTIONS,
TraceType.WINDOW_MANAGER
TraceType.WINDOW_MANAGER,
]);
component.activeViewTraceTypes = [TraceType.PROTO_LOG];
@@ -149,11 +164,11 @@ describe("TimelineComponent", () => {
expect(component.selectedTraces).toEqual([
TraceType.TRANSACTIONS,
TraceType.WINDOW_MANAGER,
TraceType.PROTO_LOG
TraceType.PROTO_LOG,
]);
});
it("handles undefined active trace input", () => {
it('handles undefined active trace input', () => {
component.activeViewTraceTypes = undefined;
expect(component.internalActiveTrace).toBeUndefined();
expect(component.selectedTraces).toEqual([]);
@@ -167,91 +182,115 @@ describe("TimelineComponent", () => {
expect(component.selectedTraces).toEqual([TraceType.SURFACE_FLINGER]);
});
it("handles some traces with no timestamps", () => {
component.timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: []
}, {
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(100)]
}], undefined);
it('handles some traces with no timestamps', () => {
component.timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [],
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(100)],
},
],
undefined
);
fixture.detectChanges();
});
it("next button disabled if no next entry", () => {
component.timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp(100), timestamp(110)]
}, {
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(90), timestamp(101), timestamp(110), timestamp(112)]
}], undefined);
it('next button disabled if no next entry', () => {
component.timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp(100), timestamp(110)],
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(90), timestamp(101), timestamp(110), timestamp(112)],
},
],
undefined
);
component.activeViewTraceTypes = [TraceType.SURFACE_FLINGER];
component.timelineData.setCurrentTimestamp(timestamp(100));
fixture.detectChanges();
expect(component.timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(100n);
const nextEntryButton = fixture.debugElement.query(By.css("#next_entry_button"));
const nextEntryButton = fixture.debugElement.query(By.css('#next_entry_button'));
expect(nextEntryButton).toBeTruthy();
expect(nextEntryButton.nativeElement.getAttribute("disabled")).toBeFalsy();
expect(nextEntryButton.nativeElement.getAttribute('disabled')).toBeFalsy();
component.timelineData.setCurrentTimestamp(timestamp(90));
fixture.detectChanges();
expect(nextEntryButton.nativeElement.getAttribute("disabled")).toBeFalsy();
expect(nextEntryButton.nativeElement.getAttribute('disabled')).toBeFalsy();
component.timelineData.setCurrentTimestamp(timestamp(110));
fixture.detectChanges();
expect(nextEntryButton.nativeElement.getAttribute("disabled")).toBeTruthy();
expect(nextEntryButton.nativeElement.getAttribute('disabled')).toBeTruthy();
component.timelineData.setCurrentTimestamp(timestamp(112));
fixture.detectChanges();
expect(nextEntryButton.nativeElement.getAttribute("disabled")).toBeTruthy();
expect(nextEntryButton.nativeElement.getAttribute('disabled')).toBeTruthy();
});
it("prev button disabled if no prev entry", () => {
component.timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp(100), timestamp(110)]
}, {
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(90), timestamp(101), timestamp(110), timestamp(112)]
}], undefined);
it('prev button disabled if no prev entry', () => {
component.timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp(100), timestamp(110)],
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(90), timestamp(101), timestamp(110), timestamp(112)],
},
],
undefined
);
component.activeViewTraceTypes = [TraceType.SURFACE_FLINGER];
component.timelineData.setCurrentTimestamp(timestamp(100));
fixture.detectChanges();
expect(component.timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(100n);
const prevEntryButton = fixture.debugElement.query(By.css("#prev_entry_button"));
const prevEntryButton = fixture.debugElement.query(By.css('#prev_entry_button'));
expect(prevEntryButton).toBeTruthy();
expect(prevEntryButton.nativeElement.getAttribute("disabled")).toBeTruthy();
expect(prevEntryButton.nativeElement.getAttribute('disabled')).toBeTruthy();
component.timelineData.setCurrentTimestamp(timestamp(90));
fixture.detectChanges();
expect(prevEntryButton.nativeElement.getAttribute("disabled")).toBeTruthy();
expect(prevEntryButton.nativeElement.getAttribute('disabled')).toBeTruthy();
component.timelineData.setCurrentTimestamp(timestamp(110));
fixture.detectChanges();
expect(prevEntryButton.nativeElement.getAttribute("disabled")).toBeFalsy();
expect(prevEntryButton.nativeElement.getAttribute('disabled')).toBeFalsy();
component.timelineData.setCurrentTimestamp(timestamp(112));
fixture.detectChanges();
expect(prevEntryButton.nativeElement.getAttribute("disabled")).toBeFalsy();
expect(prevEntryButton.nativeElement.getAttribute('disabled')).toBeFalsy();
});
it("changes timestamp on next entry button press", () => {
component.timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp(100), timestamp(110)]
}, {
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(90), timestamp(101), timestamp(110), timestamp(112)]
}], undefined);
it('changes timestamp on next entry button press', () => {
component.timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp(100), timestamp(110)],
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(90), timestamp(101), timestamp(110), timestamp(112)],
},
],
undefined
);
component.activeViewTraceTypes = [TraceType.SURFACE_FLINGER];
component.timelineData.setCurrentTimestamp(timestamp(100));
fixture.detectChanges();
expect(component.timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(100n);
const nextEntryButton = fixture.debugElement.query(By.css("#next_entry_button"));
const nextEntryButton = fixture.debugElement.query(By.css('#next_entry_button'));
expect(nextEntryButton).toBeTruthy();
component.timelineData.setCurrentTimestamp(timestamp(105));
@@ -282,19 +321,25 @@ describe("TimelineComponent", () => {
expect(component.timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(112n);
});
it("changes timestamp on previous entry button press", () => {
component.timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp(100), timestamp(110)]
}, {
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(90), timestamp(101), timestamp(110), timestamp(112)]
}], undefined);
it('changes timestamp on previous entry button press', () => {
component.timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp(100), timestamp(110)],
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp(90), timestamp(101), timestamp(110), timestamp(112)],
},
],
undefined
);
component.activeViewTraceTypes = [TraceType.SURFACE_FLINGER];
component.timelineData.setCurrentTimestamp(timestamp(100));
fixture.detectChanges();
expect(component.timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(100n);
const prevEntryButton = fixture.debugElement.query(By.css("#prev_entry_button"));
const prevEntryButton = fixture.debugElement.query(By.css('#prev_entry_button'));
expect(prevEntryButton).toBeTruthy();
// In this state we are already on the first entry at timestamp 100, so

View File

@@ -1,137 +1,148 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ChangeDetectorRef,
Component,
Input,
Inject,
ViewEncapsulation,
Output,
EventEmitter,
ViewChild,
HostListener,
ElementRef,
} from "@angular/core";
import { FormControl, FormGroup, Validators} from "@angular/forms";
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
import { TraceType } from "common/trace/trace_type";
import { TRACE_INFO } from "app/trace_info";
import { TimelineData } from "app/timeline_data";
import { MiniTimelineComponent } from "./mini_timeline.component";
import { ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType } from "common/trace/timestamp";
import { TimeUtils } from "common/utils/time_utils";
import {TimestampChangeListener} from "interfaces/timestamp_change_listener";
EventEmitter,
HostListener,
Inject,
Input,
Output,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
import {TimelineData} from 'app/timeline_data';
import {TRACE_INFO} from 'app/trace_info';
import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {TimeUtils} from 'common/utils/time_utils';
import {TimestampChangeListener} from 'interfaces/timestamp_change_listener';
import {MiniTimelineComponent} from './mini_timeline.component';
@Component({
selector: "timeline",
selector: 'timeline',
encapsulation: ViewEncapsulation.None,
template: `
<div id="expanded-nav" *ngIf="expanded">
<div id="video-content" *ngIf="videoUrl !== undefined">
<video
*ngIf="getVideoCurrentTime() !== undefined"
id="video"
[currentTime]="getVideoCurrentTime()"
[src]="videoUrl">
</video>
<div *ngIf="getVideoCurrentTime() === undefined" class="no-video-message">
<p>No screenrecording frame to show</p>
<p>Current timestamp before first screenrecording frame.</p>
</div>
<div id="video-content" *ngIf="videoUrl !== undefined">
<video
*ngIf="getVideoCurrentTime() !== undefined"
id="video"
[currentTime]="getVideoCurrentTime()"
[src]="videoUrl"></video>
<div *ngIf="getVideoCurrentTime() === undefined" class="no-video-message">
<p>No screenrecording frame to show</p>
<p>Current timestamp before first screenrecording frame.</p>
</div>
<expanded-timeline
[timelineData]="timelineData"
[currentTimestamp]="currentTimestamp"
(onTimestampChanged)="updateCurrentTimestamp($event)"
id="expanded-timeline"
></expanded-timeline>
</div>
<expanded-timeline
[timelineData]="timelineData"
[currentTimestamp]="currentTimestamp"
(onTimestampChanged)="updateCurrentTimestamp($event)"
id="expanded-timeline"></expanded-timeline>
</div>
<div class="navbar" #collapsedTimeline>
<ng-template [ngIf]="timelineData.hasMoreThanOneDistinctTimestamp()">
<div id="time-selector">
<button mat-icon-button
id="prev_entry_button"
color="primary"
(click)="moveToPreviousEntry()"
[disabled]="!hasPrevEntry()">
<mat-icon>chevron_left</mat-icon>
</button>
<form [formGroup]="timestampForm" class="time-selector-form">
<mat-form-field
class="time-input"
appearance="fill"
(change)="humanElapsedTimeInputChange($event)"
*ngIf="!usingRealtime()">
<input matInput name="humanElapsedTimeInput" [formControl]="selectedElapsedTimeFormControl" />
</mat-form-field>
<mat-form-field
class="time-input"
appearance="fill"
(change)="humanRealTimeInputChanged($event)"
*ngIf="usingRealtime()">
<input matInput name="humanRealTimeInput" [formControl]="selectedRealTimeFormControl" />
</mat-form-field>
<mat-form-field
class="time-input"
appearance="fill"
(change)="nanosecondsInputTimeChange($event)">
<input matInput name="nsTimeInput" [formControl]="selectedNsFormControl" />
</mat-form-field>
</form>
<button mat-icon-button
id="next_entry_button"
color="primary"
(click)="moveToNextEntry()"
[disabled]="!hasNextEntry()">
<mat-icon>chevron_right</mat-icon>
</button>
<button
mat-icon-button
id="prev_entry_button"
color="primary"
(click)="moveToPreviousEntry()"
[disabled]="!hasPrevEntry()">
<mat-icon>chevron_left</mat-icon>
</button>
<form [formGroup]="timestampForm" class="time-selector-form">
<mat-form-field
class="time-input"
appearance="fill"
(change)="humanElapsedTimeInputChange($event)"
*ngIf="!usingRealtime()">
<input
matInput
name="humanElapsedTimeInput"
[formControl]="selectedElapsedTimeFormControl" />
</mat-form-field>
<mat-form-field
class="time-input"
appearance="fill"
(change)="humanRealTimeInputChanged($event)"
*ngIf="usingRealtime()">
<input
matInput
name="humanRealTimeInput"
[formControl]="selectedRealTimeFormControl" />
</mat-form-field>
<mat-form-field
class="time-input"
appearance="fill"
(change)="nanosecondsInputTimeChange($event)">
<input matInput name="nsTimeInput" [formControl]="selectedNsFormControl" />
</mat-form-field>
</form>
<button
mat-icon-button
id="next_entry_button"
color="primary"
(click)="moveToNextEntry()"
[disabled]="!hasNextEntry()">
<mat-icon>chevron_right</mat-icon>
</button>
</div>
<div id="trace-selector">
<mat-form-field appearance="none">
<mat-select #traceSelector [formControl]="selectedTracesFormControl" multiple (closed)="onTraceSelectionClosed()">
<div class="tip">
Select up to 2 additional traces to display.
</div>
<mat-option
*ngFor="let trace of availableTraces"
[value]="trace"
[style]="{
color: TRACE_INFO[trace].color,
opacity: isOptionDisabled(trace) ? 0.5 : 1.0
}"
[disabled]="isOptionDisabled(trace)"
>
<mat-icon>{{ TRACE_INFO[trace].icon }}</mat-icon>
{{ TRACE_INFO[trace].name }}
</mat-option>
<div class="actions">
<button mat-button color="primary" (click)="traceSelector.close()">Cancel</button>
<button mat-flat-button color="primary" (click)="applyNewTraceSelection(); traceSelector.close()">Apply</button>
</div>
<mat-select-trigger class="shown-selection">
<mat-icon
*ngFor="let selectedTrace of selectedTraces"
[style]="{color: TRACE_INFO[selectedTrace].color}"
>
{{ TRACE_INFO[selectedTrace].icon }}
</mat-icon>
</mat-select-trigger>
</mat-select>
</mat-form-field>
<mat-form-field appearance="none">
<mat-select
#traceSelector
[formControl]="selectedTracesFormControl"
multiple
(closed)="onTraceSelectionClosed()">
<div class="tip">Select up to 2 additional traces to display.</div>
<mat-option
*ngFor="let trace of availableTraces"
[value]="trace"
[style]="{
color: TRACE_INFO[trace].color,
opacity: isOptionDisabled(trace) ? 0.5 : 1.0
}"
[disabled]="isOptionDisabled(trace)">
<mat-icon>{{ TRACE_INFO[trace].icon }}</mat-icon>
{{ TRACE_INFO[trace].name }}
</mat-option>
<div class="actions">
<button mat-button color="primary" (click)="traceSelector.close()">Cancel</button>
<button
mat-flat-button
color="primary"
(click)="applyNewTraceSelection(); traceSelector.close()">
Apply
</button>
</div>
<mat-select-trigger class="shown-selection">
<mat-icon
*ngFor="let selectedTrace of selectedTraces"
[style]="{color: TRACE_INFO[selectedTrace].color}">
{{ TRACE_INFO[selectedTrace].icon }}
</mat-icon>
</mat-select-trigger>
</mat-select>
</mat-form-field>
</div>
<mini-timeline
[timelineData]="timelineData"
@@ -140,142 +151,146 @@ import {TimestampChangeListener} from "interfaces/timestamp_change_listener";
(changeTimestamp)="updateCurrentTimestamp($event)"
(changeSeekTimestamp)="updateSeekTimestamp($event)"
id="mini-timeline"
#miniTimeline
></mini-timeline>
#miniTimeline></mini-timeline>
<div id="toggle" *ngIf="timelineData.hasMoreThanOneDistinctTimestamp()">
<button mat-icon-button
[class]="TOGGLE_BUTTON_CLASS"
color="primary"
aria-label="Toggle Expanded Timeline"
(click)="toggleExpand()">
<mat-icon *ngIf="!expanded">expand_less</mat-icon>
<mat-icon *ngIf="expanded">expand_more</mat-icon>
</button>
<button
mat-icon-button
[class]="TOGGLE_BUTTON_CLASS"
color="primary"
aria-label="Toggle Expanded Timeline"
(click)="toggleExpand()">
<mat-icon *ngIf="!expanded">expand_less</mat-icon>
<mat-icon *ngIf="expanded">expand_more</mat-icon>
</button>
</div>
</ng-template >
</ng-template>
<div *ngIf="!timelineData.hasTimestamps()" class="no-timestamps-msg">
<p class="mat-body-2">No timeline to show!</p>
<p class="mat-body-1">All loaded traces contain no timestamps!</p>
</div>
<div *ngIf="timelineData.hasTimestamps() && !timelineData.hasMoreThanOneDistinctTimestamp()" class="no-timestamps-msg">
<div
*ngIf="timelineData.hasTimestamps() && !timelineData.hasMoreThanOneDistinctTimestamp()"
class="no-timestamps-msg">
<p class="mat-body-2">No timeline to show!</p>
<p class="mat-body-1">Only a single timestamp has been recorded.</p>
</div>
</div>
`,
styles: [`
.navbar {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
justify-content: center;
}
#expanded-nav {
display: flex;
border-bottom: 1px solid #3333
}
#time-selector {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.time-selector-form {
display: flex;
flex-direction: column;
width: 15em;
}
.time-selector-form .time-input {
width: 100%;
margin-bottom: -1.34375em;
text-align: center;
}
#mini-timeline {
flex-grow: 1;
align-self: stretch;
}
#video-content {
position: relative;
min-width: 20rem;
min-height: 35rem;
align-self: stretch;
text-align: center;
border: 2px solid black;
flex-basis: 0px;
flex-grow: 1;
display: flex;
align-items: center;
}
#video {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
}
#expanded-nav {
display: flex;
flex-direction: row;
}
#expanded-timeline {
flex-grow: 1;
}
#trace-selector .mat-form-field-infix {
width: 50px;
padding: 0 0.75rem 0 0.5rem;
border-top: unset;
}
#trace-selector .mat-icon {
padding: 2px;
}
#trace-selector .shown-selection {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: auto;
}
#trace-selector .mat-select-trigger {
height: unset;
}
#trace-selector .mat-form-field-wrapper {
padding: 0;
}
.mat-select-panel {
max-height: unset!important;
font-family: 'Roboto', sans-serif;
}
.tip {
padding: 1.5rem;
font-weight: 200;
border-bottom: solid 1px #DADCE0;
}
.actions {
border-top: solid 1px #DADCE0;
width: 100%;
padding: 1.5rem;
float: right;
display: flex;
justify-content: flex-end;
}
.no-video-message {
padding: 1rem;
font-family: 'Roboto', sans-serif;
}
.no-timestamps-msg {
padding: 1rem;
align-items: center;
display: flex;
flex-direction: column;
}
`],
`,
styles: [
`
.navbar {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
justify-content: center;
}
#expanded-nav {
display: flex;
border-bottom: 1px solid #3333;
}
#time-selector {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.time-selector-form {
display: flex;
flex-direction: column;
width: 15em;
}
.time-selector-form .time-input {
width: 100%;
margin-bottom: -1.34375em;
text-align: center;
}
#mini-timeline {
flex-grow: 1;
align-self: stretch;
}
#video-content {
position: relative;
min-width: 20rem;
min-height: 35rem;
align-self: stretch;
text-align: center;
border: 2px solid black;
flex-basis: 0px;
flex-grow: 1;
display: flex;
align-items: center;
}
#video {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
}
#expanded-nav {
display: flex;
flex-direction: row;
}
#expanded-timeline {
flex-grow: 1;
}
#trace-selector .mat-form-field-infix {
width: 50px;
padding: 0 0.75rem 0 0.5rem;
border-top: unset;
}
#trace-selector .mat-icon {
padding: 2px;
}
#trace-selector .shown-selection {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: auto;
}
#trace-selector .mat-select-trigger {
height: unset;
}
#trace-selector .mat-form-field-wrapper {
padding: 0;
}
.mat-select-panel {
max-height: unset !important;
font-family: 'Roboto', sans-serif;
}
.tip {
padding: 1.5rem;
font-weight: 200;
border-bottom: solid 1px #dadce0;
}
.actions {
border-top: solid 1px #dadce0;
width: 100%;
padding: 1.5rem;
float: right;
display: flex;
justify-content: flex-end;
}
.no-video-message {
padding: 1rem;
font-family: 'Roboto', sans-serif;
}
.no-timestamps-msg {
padding: 1rem;
align-items: center;
display: flex;
flex-direction: column;
}
`,
],
})
export class TimelineComponent implements TimestampChangeListener {
public readonly TOGGLE_BUTTON_CLASS: string = "button-toggle-expansion";
public readonly TOGGLE_BUTTON_CLASS: string = 'button-toggle-expansion';
public readonly MAX_SELECTED_TRACES = 3;
@Input() set activeViewTraceTypes(types: TraceType[]|undefined) {
@Input() set activeViewTraceTypes(types: TraceType[] | undefined) {
if (!types) {
return;
}
@@ -297,35 +312,44 @@ export class TimelineComponent implements TimestampChangeListener {
this.selectedTracesFormControl.setValue(this.selectedTraces);
}
public internalActiveTrace: TraceType|undefined = undefined;
public internalActiveTrace: TraceType | undefined = undefined;
@Input() timelineData!: TimelineData;
@Input() availableTraces: TraceType[] = [];
@Output() collapsedTimelineSizeChanged = new EventEmitter<number>();
@ViewChild("miniTimeline") private miniTimelineComponent!: MiniTimelineComponent;
@ViewChild("collapsedTimeline") private collapsedTimelineRef!: ElementRef;
@ViewChild('miniTimeline') private miniTimelineComponent!: MiniTimelineComponent;
@ViewChild('collapsedTimeline') private collapsedTimelineRef!: ElementRef;
selectedTraces: TraceType[] = [];
selectedTracesFormControl = new FormControl();
selectedElapsedTimeFormControl = new FormControl("undefined", Validators.compose([
Validators.required,
Validators.pattern(TimeUtils.HUMAN_ELAPSED_TIMESTAMP_REGEX)]));
selectedRealTimeFormControl = new FormControl("undefined", Validators.compose([
Validators.required,
Validators.pattern(TimeUtils.HUMAN_REAL_TIMESTAMP_REGEX)]));
selectedNsFormControl = new FormControl("undefined", Validators.compose([
Validators.required,
Validators.pattern(TimeUtils.NS_TIMESTAMP_REGEX)]));
selectedElapsedTimeFormControl = new FormControl(
'undefined',
Validators.compose([
Validators.required,
Validators.pattern(TimeUtils.HUMAN_ELAPSED_TIMESTAMP_REGEX),
])
);
selectedRealTimeFormControl = new FormControl(
'undefined',
Validators.compose([
Validators.required,
Validators.pattern(TimeUtils.HUMAN_REAL_TIMESTAMP_REGEX),
])
);
selectedNsFormControl = new FormControl(
'undefined',
Validators.compose([Validators.required, Validators.pattern(TimeUtils.NS_TIMESTAMP_REGEX)])
);
timestampForm = new FormGroup({
selectedElapsedTime: this.selectedElapsedTimeFormControl,
selectedRealTime: this.selectedRealTimeFormControl,
selectedNs: this.selectedNsFormControl,
});
videoUrl: SafeUrl|undefined;
videoUrl: SafeUrl | undefined;
private expanded = false;
@@ -333,8 +357,8 @@ export class TimelineComponent implements TimestampChangeListener {
constructor(
@Inject(DomSanitizer) private sanitizer: DomSanitizer,
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef) {
}
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
if (this.timelineData.hasTimestamps()) {
@@ -343,8 +367,9 @@ export class TimelineComponent implements TimestampChangeListener {
const screenRecordingVideo = this.timelineData.getScreenRecordingVideo();
if (screenRecordingVideo) {
this.videoUrl =
this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(screenRecordingVideo));
this.videoUrl = this.sanitizer.bypassSecurityTrustUrl(
URL.createObjectURL(screenRecordingVideo)
);
}
}
@@ -357,7 +382,7 @@ export class TimelineComponent implements TimestampChangeListener {
return this.timelineData.searchCorrespondingScreenRecordingTimeSeconds(this.currentTimestamp);
}
private seekTimestamp: Timestamp|undefined;
private seekTimestamp: Timestamp | undefined;
get currentTimestamp(): Timestamp {
if (this.seekTimestamp !== undefined) {
@@ -366,13 +391,13 @@ export class TimelineComponent implements TimestampChangeListener {
const timestamp = this.timelineData.getCurrentTimestamp();
if (timestamp === undefined) {
throw Error("A timestamp should have been set by the time the timeline is loaded");
throw Error('A timestamp should have been set by the time the timeline is loaded');
}
return timestamp;
}
onCurrentTimestampChanged(timestamp: Timestamp|undefined): void {
onCurrentTimestampChanged(timestamp: Timestamp | undefined): void {
if (!timestamp) {
return;
}
@@ -392,14 +417,18 @@ export class TimelineComponent implements TimestampChangeListener {
return this.timelineData.getTimestampType() === TimestampType.REAL;
}
updateSeekTimestamp(timestamp: Timestamp|undefined) {
updateSeekTimestamp(timestamp: Timestamp | undefined) {
this.seekTimestamp = timestamp;
this.updateTimeInputValuesToCurrentTimestamp();
}
private updateTimeInputValuesToCurrentTimestamp() {
this.selectedElapsedTimeFormControl.setValue(TimeUtils.format(new ElapsedTimestamp(this.currentTimestamp.getValueNs()), false));
this.selectedRealTimeFormControl.setValue(TimeUtils.format(new RealTimestamp(this.currentTimestamp.getValueNs())));
this.selectedElapsedTimeFormControl.setValue(
TimeUtils.format(new ElapsedTimestamp(this.currentTimestamp.getValueNs()), false)
);
this.selectedRealTimeFormControl.setValue(
TimeUtils.format(new RealTimestamp(this.currentTimestamp.getValueNs()))
);
this.selectedNsFormControl.setValue(`${this.currentTimestamp.getValueNs()} ns`);
}
@@ -409,8 +438,10 @@ export class TimelineComponent implements TimestampChangeListener {
}
// Reached limit of options and is not a selected element
if ((this.selectedTracesFormControl.value?.length ?? 0) >= this.MAX_SELECTED_TRACES
&& this.selectedTracesFormControl.value?.find((el: TraceType) => el === trace) === undefined) {
if (
(this.selectedTracesFormControl.value?.length ?? 0) >= this.MAX_SELECTED_TRACES &&
this.selectedTracesFormControl.value?.find((el: TraceType) => el === trace) === undefined
) {
return true;
}
@@ -425,14 +456,14 @@ export class TimelineComponent implements TimestampChangeListener {
this.selectedTraces = this.selectedTracesFormControl.value;
}
@HostListener("document:keydown", ["$event"])
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
switch (event.key) {
case "ArrowLeft": {
case 'ArrowLeft': {
this.moveToPreviousEntry();
break;
}
case "ArrowRight": {
case 'ArrowRight': {
this.moveToNextEntry();
break;
}
@@ -440,16 +471,20 @@ export class TimelineComponent implements TimestampChangeListener {
}
hasPrevEntry(): boolean {
if (!this.internalActiveTrace ||
(this.timelineData.getTimelines().get(this.internalActiveTrace)?.length ?? 0) === 0) {
if (
!this.internalActiveTrace ||
(this.timelineData.getTimelines().get(this.internalActiveTrace)?.length ?? 0) === 0
) {
return false;
}
return this.timelineData.getPreviousTimestampFor(this.internalActiveTrace) !== undefined;
}
hasNextEntry(): boolean {
if (!this.internalActiveTrace ||
(this.timelineData.getTimelines().get(this.internalActiveTrace)?.length ?? 0) === 0) {
if (
!this.internalActiveTrace ||
(this.timelineData.getTimelines().get(this.internalActiveTrace)?.length ?? 0) === 0
) {
return false;
}
return this.timelineData.getNextTimestampFor(this.internalActiveTrace) !== undefined;
@@ -470,7 +505,7 @@ export class TimelineComponent implements TimestampChangeListener {
}
humanElapsedTimeInputChange(event: Event) {
if (event.type !== "change") {
if (event.type !== 'change') {
return;
}
const target = event.target as HTMLInputElement;
@@ -480,7 +515,7 @@ export class TimelineComponent implements TimestampChangeListener {
}
humanRealTimeInputChanged(event: Event) {
if (event.type !== "change") {
if (event.type !== 'change') {
return;
}
const target = event.target as HTMLInputElement;
@@ -491,7 +526,7 @@ export class TimelineComponent implements TimestampChangeListener {
}
nanosecondsInputTimeChange(event: Event) {
if (event.type !== "change") {
if (event.type !== 'change') {
return;
}
const target = event.target as HTMLInputElement;

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
import {Timestamp} from "common/trace/timestamp";
import {TimestampChangeListener} from "interfaces/timestamp_change_listener";
import {Timestamp} from 'common/trace/timestamp';
import {TimestampChangeListener} from 'interfaces/timestamp_change_listener';
export class TimelineComponentStub implements TimestampChangeListener {
onCurrentTimestampChanged(timestamp: Timestamp|undefined) {
onCurrentTimestampChanged(timestamp: Timestamp | undefined) {
// do nothing
}
}

View File

@@ -14,8 +14,8 @@
* limitations under the License.
*/
import { TraceType } from "common/trace/trace_type";
import {TraceType} from 'common/trace/trace_type';
export type Segment = { from: number, to: number }
export type BigIntSegment = { from: bigint, to: bigint }
export type TimelineData = Map<TraceType, bigint[]>
export type Segment = {from: number; to: number};
export type BigIntSegment = {from: bigint; to: bigint};
export type TimelineData = Map<TraceType, bigint[]>;

View File

@@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CommonModule } from "@angular/common";
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {TraceConfigComponent} from "./trace_config.component";
import { MatCheckboxModule } from "@angular/material/checkbox";
import { MatDividerModule } from "@angular/material/divider";
import { MatFormFieldModule } from "@angular/material/form-field";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { MatInputModule } from "@angular/material/input";
import { MatSelectModule } from "@angular/material/select";
import { NO_ERRORS_SCHEMA } from "@angular/core";
import {CommonModule} from '@angular/common';
import {NO_ERRORS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatDividerModule} from '@angular/material/divider';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {TraceConfigComponent} from './trace_config.component';
describe("TraceConfigComponent", () => {
describe('TraceConfigComponent', () => {
let fixture: ComponentFixture<TraceConfigComponent>;
let component: TraceConfigComponent;
let htmlElement: HTMLElement;
@@ -38,97 +38,100 @@ describe("TraceConfigComponent", () => {
MatFormFieldModule,
MatInputModule,
MatSelectModule,
BrowserAnimationsModule
BrowserAnimationsModule,
],
declarations: [TraceConfigComponent],
schemas: [NO_ERRORS_SCHEMA]
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
fixture = TestBed.createComponent(TraceConfigComponent);
component = fixture.componentInstance;
htmlElement = fixture.nativeElement;
component.traces = {
"layers_trace": {
name: "layers_trace",
layers_trace: {
name: 'layers_trace',
isTraceCollection: undefined,
run: false,
config: {
enableConfigs: [{
name:"trace buffers",
key:"tracebuffers",
enabled:true
}],
selectionConfigs: [{
key: "tracinglevel",
name: "tracing level",
options: [
"verbose",
"debug",
"critical",
],
value: "debug"
}]
}}
enableConfigs: [
{
name: 'trace buffers',
key: 'tracebuffers',
enabled: true,
},
],
selectionConfigs: [
{
key: 'tracinglevel',
name: 'tracing level',
options: ['verbose', 'debug', 'critical'],
value: 'debug',
},
],
},
},
};
});
it("can be created", () => {
it('can be created', () => {
expect(component).toBeTruthy();
});
it("check that trace checkbox ticked on default run", () => {
component.traces["layers_trace"].run = true;
it('check that trace checkbox ticked on default run', () => {
component.traces['layers_trace'].run = true;
fixture.detectChanges();
const box = htmlElement.querySelector(".trace-checkbox");
expect(box?.innerHTML).toContain("aria-checked=\"true\"");
expect(box?.innerHTML).toContain("layers_trace");
const box = htmlElement.querySelector('.trace-checkbox');
expect(box?.innerHTML).toContain('aria-checked="true"');
expect(box?.innerHTML).toContain('layers_trace');
});
it("check that trace checkbox not ticked on default run", () => {
component.traces["layers_trace"].run = false;
it('check that trace checkbox not ticked on default run', () => {
component.traces['layers_trace'].run = false;
fixture.detectChanges();
const box = htmlElement.querySelector(".trace-checkbox");
expect(box?.innerHTML).toContain("aria-checked=\"false\"");
const box = htmlElement.querySelector('.trace-checkbox');
expect(box?.innerHTML).toContain('aria-checked="false"');
});
it("check that correct advanced enable config shows", () => {
component.traces["layers_trace"].config!.selectionConfigs = [];
it('check that correct advanced enable config shows', () => {
component.traces['layers_trace'].config!.selectionConfigs = [];
fixture.detectChanges();
const enable_config_opt = htmlElement.querySelector(".enable-config-opt");
const enable_config_opt = htmlElement.querySelector('.enable-config-opt');
expect(enable_config_opt).toBeTruthy();
expect(enable_config_opt?.innerHTML).toContain("trace buffers");
expect(enable_config_opt?.innerHTML).not.toContain("tracing level");
expect(enable_config_opt?.innerHTML).toContain('trace buffers');
expect(enable_config_opt?.innerHTML).not.toContain('tracing level');
const selection_config_opt = htmlElement.querySelector(".selection-config-opt");
const selection_config_opt = htmlElement.querySelector('.selection-config-opt');
expect(selection_config_opt).toBeFalsy();
});
it("check that correct advanced selection config shows", () => {
component.traces["layers_trace"].config!.enableConfigs = [];
it('check that correct advanced selection config shows', () => {
component.traces['layers_trace'].config!.enableConfigs = [];
fixture.detectChanges();
const enable_config_opt = htmlElement.querySelector(".enable-config-opt");
const enable_config_opt = htmlElement.querySelector('.enable-config-opt');
expect(enable_config_opt).toBeFalsy();
const selection_config_opt = htmlElement.querySelector(".selection-config-opt");
const selection_config_opt = htmlElement.querySelector('.selection-config-opt');
expect(selection_config_opt).toBeTruthy();
expect(selection_config_opt?.innerHTML).not.toContain("trace buffers");
expect(selection_config_opt?.innerHTML).toContain("tracing level");
expect(selection_config_opt?.innerHTML).not.toContain('trace buffers');
expect(selection_config_opt?.innerHTML).toContain('tracing level');
});
it("check that changing enable config causes box to change", async () => {
component.traces["layers_trace"].config!.enableConfigs[0].enabled = false;
it('check that changing enable config causes box to change', async () => {
component.traces['layers_trace'].config!.enableConfigs[0].enabled = false;
fixture.detectChanges();
await fixture.whenStable();
expect(htmlElement.querySelector(".enable-config")?.innerHTML).toContain("aria-checked=\"false\"");
expect(htmlElement.querySelector('.enable-config')?.innerHTML).toContain(
'aria-checked="false"'
);
});
it("check that changing selected config causes select to change", async () => {
it('check that changing selected config causes select to change', async () => {
fixture.detectChanges();
expect(htmlElement.querySelector(".config-selection")?.innerHTML).toContain("value=\"debug\"");
component.traces["layers_trace"].config!.selectionConfigs[0].value = "verbose";
expect(htmlElement.querySelector('.config-selection')?.innerHTML).toContain('value="debug"');
component.traces['layers_trace'].config!.selectionConfigs[0].value = 'verbose';
fixture.detectChanges();
await fixture.whenStable();
expect(htmlElement.querySelector(".config-selection")?.innerHTML).toContain("value=\"verbose\"");
expect(htmlElement.querySelector('.config-selection')?.innerHTML).toContain('value="verbose"');
});
});

View File

@@ -13,11 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Input } from "@angular/core";
import { EnableConfiguration, SelectionConfiguration, TraceConfiguration, TraceConfigurationMap } from "trace_collection/trace_collection_utils";
import {Component, Input} from '@angular/core';
import {
EnableConfiguration,
SelectionConfiguration,
TraceConfiguration,
TraceConfigurationMap,
} from 'trace_collection/trace_collection_utils';
@Component({
selector: "trace-config",
selector: 'trace-config',
template: `
<h3 class="mat-subheading-2">Trace targets</h3>
@@ -29,13 +34,14 @@ import { EnableConfiguration, SelectionConfiguration, TraceConfiguration, TraceC
[checked]="traces[traceKey].run"
[indeterminate]="traces[traceKey].isTraceCollection ? someTraces(traces[traceKey]) : false"
(change)="changeRunTrace($event.checked, traces[traceKey])"
>{{traces[traceKey].name}}</mat-checkbox>
>{{ traces[traceKey].name }}</mat-checkbox
>
</div>
<ng-container *ngFor="let traceKey of advancedConfigTraces()">
<mat-divider></mat-divider>
<h3 class="mat-subheading-2">{{traces[traceKey].name}} configuration</h3>
<h3 class="mat-subheading-2">{{ traces[traceKey].name }} configuration</h3>
<div *ngIf="traces[traceKey].config?.enableConfigs.length > 0" class="enable-config-opt">
<mat-checkbox
@@ -45,22 +51,26 @@ import { EnableConfiguration, SelectionConfiguration, TraceConfiguration, TraceC
[disabled]="!traces[traceKey].run && !traces[traceKey].isTraceCollection"
[(ngModel)]="enableConfig.enabled"
(ngModelChange)="changeTraceCollectionConfig(traces[traceKey])"
>{{enableConfig.name}}</mat-checkbox>
>{{ enableConfig.name }}</mat-checkbox
>
</div>
<div *ngIf="traces[traceKey].config?.selectionConfigs.length > 0" class="selection-config-opt">
<div
*ngIf="traces[traceKey].config?.selectionConfigs.length > 0"
class="selection-config-opt">
<mat-form-field
*ngFor="let selectionConfig of traceSelectionConfigs(traces[traceKey])"
class="config-selection"
appearance="fill">
<mat-label>{{ selectionConfig.name }}</mat-label>
<mat-label>{{selectionConfig.name}}</mat-label>
<mat-select class="selected-value" [(value)]="selectionConfig.value" [disabled]="!traces[traceKey].run">
<mat-option
*ngFor="let option of selectionConfig.options"
value="{{option}}"
>{{ option }}</mat-option>
<mat-select
class="selected-value"
[(value)]="selectionConfig.value"
[disabled]="!traces[traceKey].run">
<mat-option *ngFor="let option of selectionConfig.options" value="{{ option }}">{{
option
}}</mat-option>
</mat-select>
</mat-form-field>
</div>
@@ -73,16 +83,16 @@ import { EnableConfiguration, SelectionConfiguration, TraceConfiguration, TraceC
grid-template-columns: repeat(3, 1fr);
column-gap: 10px;
}
.enable-config-opt, .selection-config-opt {
.enable-config-opt,
.selection-config-opt {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
}
`
]
`,
],
})
export class TraceConfigComponent {
objectKeys = Object.keys;
@Input() traces!: TraceConfigurationMap;
@@ -114,8 +124,7 @@ export class TraceConfigComponent {
}
public someTraces(trace: TraceConfiguration): boolean {
return this.traceEnableConfigs(trace).filter(trace => trace.enabled).length > 0
&& !trace.run;
return this.traceEnableConfigs(trace).filter((trace) => trace.enabled).length > 0 && !trace.run;
}
public changeRunTrace(run: boolean, trace: TraceConfiguration): void {
@@ -127,7 +136,7 @@ export class TraceConfigComponent {
public changeTraceCollectionConfig(trace: TraceConfiguration): void {
if (trace.isTraceCollection) {
trace.run = this.traceEnableConfigs(trace).every((c: EnableConfiguration) => c.enabled);
trace.run = this.traceEnableConfigs(trace).every((c: EnableConfiguration) => c.enabled);
}
}
}

View File

@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {CommonModule} from "@angular/common";
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA} from "@angular/core";
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {MatCardModule} from "@angular/material/card";
import {MatDividerModule} from "@angular/material/divider";
import {TraceViewComponent} from "./trace_view.component";
import {ViewerStub} from "viewers/viewer_stub";
import {CommonModule} from '@angular/common';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MatCardModule} from '@angular/material/card';
import {MatDividerModule} from '@angular/material/divider';
import {ViewerStub} from 'viewers/viewer_stub';
import {TraceViewComponent} from './trace_view.component';
describe("TraceViewComponent", () => {
describe('TraceViewComponent', () => {
let fixture: ComponentFixture<TraceViewComponent>;
let component: TraceViewComponent;
let htmlElement: HTMLElement;
@@ -29,84 +29,77 @@ describe("TraceViewComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [TraceViewComponent],
imports: [
CommonModule,
MatCardModule,
MatDividerModule
],
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
imports: [CommonModule, MatCardModule, MatDividerModule],
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
fixture = TestBed.createComponent(TraceViewComponent);
htmlElement = fixture.nativeElement;
component = fixture.componentInstance;
component.viewers = [
new ViewerStub("Title0", "Content0"),
new ViewerStub("Title1", "Content1")
new ViewerStub('Title0', 'Content0'),
new ViewerStub('Title1', 'Content1'),
];
component.ngOnChanges();
fixture.detectChanges();
});
it("can be created", () => {
it('can be created', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
it("creates viewer tabs", () => {
const tabs: NodeList = htmlElement.querySelectorAll(".tab");
it('creates viewer tabs', () => {
const tabs: NodeList = htmlElement.querySelectorAll('.tab');
expect(tabs.length).toEqual(2);
expect(tabs.item(0)!.textContent).toEqual("Title0");
expect(tabs.item(1)!.textContent).toEqual("Title1");
expect(tabs.item(0)!.textContent).toContain('Title0');
expect(tabs.item(1)!.textContent).toContain('Title1');
});
it("changes active view on click", () => {
it('changes active view on click', () => {
const getVisibleTabContents = () => {
const contents: HTMLElement[] = [];
htmlElement
.querySelectorAll(".trace-view-content div")
.forEach(content => {
if ((content as HTMLElement).style.display != "none") {
contents.push(content as HTMLElement);
}
});
htmlElement.querySelectorAll('.trace-view-content div').forEach((content) => {
if ((content as HTMLElement).style.display != 'none') {
contents.push(content as HTMLElement);
}
});
return contents;
};
const tabButtons = htmlElement.querySelectorAll(".tab");
const tabButtons = htmlElement.querySelectorAll('.tab');
// Initially tab 0
fixture.detectChanges();
let visibleTabContents = getVisibleTabContents();
expect(visibleTabContents.length).toEqual(1);
expect(visibleTabContents[0].innerHTML).toEqual("Content0");
expect(visibleTabContents[0].innerHTML).toEqual('Content0');
// Switch to tab 1
tabButtons[1].dispatchEvent(new Event("click"));
tabButtons[1].dispatchEvent(new Event('click'));
fixture.detectChanges();
visibleTabContents = getVisibleTabContents();
expect(visibleTabContents.length).toEqual(1);
expect(visibleTabContents[0].innerHTML).toEqual("Content1");
expect(visibleTabContents[0].innerHTML).toEqual('Content1');
// Switch to tab 0
tabButtons[0].dispatchEvent(new Event("click"));
tabButtons[0].dispatchEvent(new Event('click'));
fixture.detectChanges();
visibleTabContents = getVisibleTabContents();
expect(visibleTabContents.length).toEqual(1);
expect(visibleTabContents[0].innerHTML).toEqual("Content0");
expect(visibleTabContents[0].innerHTML).toEqual('Content0');
});
it("emits event on download button click", () => {
const spy = spyOn(component.downloadTracesButtonClick, "emit");
it('emits event on download button click', () => {
const spy = spyOn(component.downloadTracesButtonClick, 'emit');
const downloadButton: null|HTMLButtonElement =
htmlElement.querySelector(".save-button");
const downloadButton: null | HTMLButtonElement = htmlElement.querySelector('.save-button');
expect(downloadButton).toBeInstanceOf(HTMLButtonElement);
downloadButton?.dispatchEvent(new Event("click"));
downloadButton?.dispatchEvent(new Event('click'));
fixture.detectChanges();
expect(spy).toHaveBeenCalledTimes(1);
downloadButton?.dispatchEvent(new Event("click"));
downloadButton?.dispatchEvent(new Event('click'));
fixture.detectChanges();
expect(spy).toHaveBeenCalledTimes(2);
});

View File

@@ -14,17 +14,16 @@
* limitations under the License.
*/
import {Component, ElementRef, EventEmitter, Inject, Input, Output} from "@angular/core";
import { TraceType } from "common/trace/trace_type";
import { PersistentStore } from "common/utils/persistent_store";
import {Viewer, View, ViewType} from "viewers/viewer";
import {Component, ElementRef, EventEmitter, Inject, Input, Output} from '@angular/core';
import {PersistentStore} from 'common/utils/persistent_store';
import {View, Viewer, ViewType} from 'viewers/viewer';
interface Tab extends View {
addedToDom: boolean;
}
@Component({
selector: "trace-view",
selector: 'trace-view',
template: `
<div class="overlay">
<div class="draggable-container" cdkDrag cdkDragBoundary=".overlay">
@@ -40,24 +39,24 @@ interface Tab extends View {
<div class="header-items-wrapper">
<nav mat-tab-nav-bar class="tabs-navigation-bar">
<a
*ngFor="let tab of tabs"
mat-tab-link
[active]="isCurrentActiveTab(tab)"
(click)="onTabClick(tab)"
class="tab"
>{{tab.title}}</a>
*ngFor="let tab of tabs"
mat-tab-link
[active]="isCurrentActiveTab(tab)"
(click)="onTabClick(tab)"
class="tab">
{{ tab.title }}
</a>
</nav>
<button
color="primary"
mat-button
class="save-button"
(click)="downloadTracesButtonClick.emit()"
>Download all traces
color="primary"
mat-button
class="save-button"
(click)="downloadTracesButtonClick.emit()">
Download all traces
</button>
</div>
<mat-divider></mat-divider>
<div class="trace-view-content">
</div>
<div class="trace-view-content"></div>
`,
styles: [
`
@@ -91,8 +90,8 @@ interface Tab extends View {
height: 100%;
overflow: auto;
}
`
]
`,
],
})
export class TraceViewComponent {
@Input() viewers!: Viewer[];
@@ -103,7 +102,7 @@ export class TraceViewComponent {
private elementRef: ElementRef;
public tabs: Tab[] = [];
private currentActiveTab: undefined|Tab;
private currentActiveTab: undefined | Tab;
constructor(@Inject(ElementRef) elementRef: ElementRef) {
this.elementRef = elementRef;
@@ -120,10 +119,10 @@ export class TraceViewComponent {
private renderViewsTab() {
this.tabs = this.viewers
.map(viewer => viewer.getViews())
.map((viewer) => viewer.getViews())
.flat()
.filter(view => (view.type === ViewType.TAB))
.map(view => {
.filter((view) => view.type === ViewType.TAB)
.map((view) => {
return {
type: view.type,
htmlElement: view.htmlElement,
@@ -133,7 +132,7 @@ export class TraceViewComponent {
};
});
this.tabs.forEach(tab => {
this.tabs.forEach((tab) => {
// TODO: setting "store" this way is a hack.
// Store should be part of View's interface.
(tab.htmlElement as any).store = this.store;
@@ -146,28 +145,31 @@ export class TraceViewComponent {
private renderViewsOverlay() {
const views: View[] = this.viewers
.map(viewer => viewer.getViews())
.map((viewer) => viewer.getViews())
.flat()
.filter(view => (view.type === ViewType.OVERLAY));
.filter((view) => view.type === ViewType.OVERLAY);
if (views.length > 1) {
throw new Error(
"Only one overlay view is supported. To allow more overlay views, either create more than" +
" one draggable containers in this component or move the cdkDrag directives into the" +
" overlay view when the new Angular's directive composition API is available" +
" (https://github.com/angular/angular/issues/8785).");
'Only one overlay view is supported. To allow more overlay views, either create more than' +
' one draggable containers in this component or move the cdkDrag directives into the' +
" overlay view when the new Angular's directive composition API is available" +
' (https://github.com/angular/angular/issues/8785).'
);
}
views.forEach(view => {
view.htmlElement.style.pointerEvents = "all";
const container = this.elementRef.nativeElement.querySelector(".overlay .draggable-container")!;
views.forEach((view) => {
view.htmlElement.style.pointerEvents = 'all';
const container = this.elementRef.nativeElement.querySelector(
'.overlay .draggable-container'
)!;
container.appendChild(view.htmlElement);
});
}
private showTab(tab: Tab) {
if (this.currentActiveTab) {
this.currentActiveTab.htmlElement.style.display = "none";
this.currentActiveTab.htmlElement.style.display = 'none';
}
if (!tab.addedToDom) {
@@ -176,12 +178,11 @@ export class TraceViewComponent {
// (added to the DOM) it has style.display == "". This fixes the
// initialization/rendering issues with cdk-virtual-scroll-viewport
// components inside the tab contents.
const traceViewContent = this.elementRef.nativeElement.querySelector(".trace-view-content")!;
const traceViewContent = this.elementRef.nativeElement.querySelector('.trace-view-content')!;
traceViewContent.appendChild(tab.htmlElement);
tab.addedToDom = true;
}
else {
tab.htmlElement.style.display = "";
} else {
tab.htmlElement.style.display = '';
}
this.currentActiveTab = tab;

View File

@@ -13,23 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {UploadTracesComponent} from "./upload_traces.component";
import { MatCardModule } from "@angular/material/card";
import { MatSnackBar, MatSnackBarModule } from "@angular/material/snack-bar";
import {TraceData} from "app/trace_data";
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MatCardModule} from '@angular/material/card';
import {MatSnackBar, MatSnackBarModule} from '@angular/material/snack-bar';
import {TraceData} from 'app/trace_data';
import {UploadTracesComponent} from './upload_traces.component';
describe("UploadTracesComponent", () => {
describe('UploadTracesComponent', () => {
let fixture: ComponentFixture<UploadTracesComponent>;
let component: UploadTracesComponent;
let htmlElement: HTMLElement;
beforeAll(async () => {
await TestBed.configureTestingModule({
imports: [
MatCardModule,
MatSnackBarModule
],
imports: [MatCardModule, MatSnackBarModule],
providers: [MatSnackBar],
declarations: [UploadTracesComponent],
}).compileComponents();
@@ -43,8 +40,7 @@ describe("UploadTracesComponent", () => {
component.traceData = traceData;
});
it("can be created", () => {
it('can be created', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -20,90 +20,83 @@ import {
Inject,
Input,
NgZone,
Output
} from "@angular/core";
import {MatSnackBar} from "@angular/material/snack-bar";
import {TraceData} from "app/trace_data";
import {TRACE_INFO} from "app/trace_info";
import {Trace, TraceFile} from "common/trace/trace";
import {FileUtils, OnFile} from "common/utils/file_utils";
import {ParserErrorSnackBarComponent} from "./parser_error_snack_bar_component";
import {FilesDownloadListener} from "interfaces/files_download_listener";
Output,
} from '@angular/core';
import {MatSnackBar} from '@angular/material/snack-bar';
import {TraceData} from 'app/trace_data';
import {TRACE_INFO} from 'app/trace_info';
import {Trace, TraceFile} from 'common/trace/trace';
import {FileUtils, OnFile} from 'common/utils/file_utils';
import {FilesDownloadListener} from 'interfaces/files_download_listener';
import {ParserErrorSnackBarComponent} from './parser_error_snack_bar_component';
@Component({
selector: "upload-traces",
selector: 'upload-traces',
template: `
<mat-card class="upload-card">
<mat-card-title class="title">Upload Traces</mat-card-title>
<mat-card-content
class="drop-box"
ref="drop-box"
(dragleave)="onFileDragOut($event)"
(dragover)="onFileDragIn($event)"
(drop)="onHandleFileDrop($event)"
(click)="fileDropRef.click()"
>
class="drop-box"
ref="drop-box"
(dragleave)="onFileDragOut($event)"
(dragover)="onFileDragIn($event)"
(drop)="onHandleFileDrop($event)"
(click)="fileDropRef.click()">
<input
id="fileDropRef"
hidden
type="file"
multiple
#fileDropRef
(change)="onInputFiles($event)"
/>
id="fileDropRef"
hidden
type="file"
multiple
#fileDropRef
(change)="onInputFiles($event)" />
<load-progress *ngIf="isLoadingFiles"
[progressPercentage]="progressPercentage"
[message]="progressMessage">
<load-progress
*ngIf="isLoadingFiles"
[progressPercentage]="progressPercentage"
[message]="progressMessage">
</load-progress>
<mat-list
*ngIf="!isLoadingFiles && this.traceData.getLoadedTraces().length > 0"
class="uploaded-files">
*ngIf="!isLoadingFiles && this.traceData.getLoadedTraces().length > 0"
class="uploaded-files">
<mat-list-item *ngFor="let trace of this.traceData.getLoadedTraces()">
<mat-icon matListIcon>
{{TRACE_INFO[trace.type].icon}}
{{ TRACE_INFO[trace.type].icon }}
</mat-icon>
<p matLine>
{{trace.traceFile.file.name}} ({{TRACE_INFO[trace.type].name}})
</p>
<p matLine>{{ trace.traceFile.file.name }} ({{ TRACE_INFO[trace.type].name }})</p>
<button color="primary" mat-icon-button
(click)="onRemoveTrace($event, trace)">
<button color="primary" mat-icon-button (click)="onRemoveTrace($event, trace)">
<mat-icon>close</mat-icon>
</button>
</mat-list-item>
</mat-list>
<div *ngIf="!isLoadingFiles && traceData.getLoadedTraces().length === 0"
class="drop-info">
<div *ngIf="!isLoadingFiles && traceData.getLoadedTraces().length === 0" class="drop-info">
<p class="mat-body-3 icon">
<mat-icon inline fontIcon="upload"></mat-icon>
</p>
<p class="mat-body-1">
Drag your .winscope file(s) or click to upload
</p>
<p class="mat-body-1">Drag your .winscope file(s) or click to upload</p>
</div>
</mat-card-content>
<div *ngIf="!isLoadingFiles && traceData.getLoadedTraces().length > 0"
class="trace-actions-container">
<button color="primary" mat-raised-button class="load-btn"
(click)="onViewTracesButtonClick()">
<div
*ngIf="!isLoadingFiles && traceData.getLoadedTraces().length > 0"
class="trace-actions-container">
<button
color="primary"
mat-raised-button
class="load-btn"
(click)="onViewTracesButtonClick()">
View traces
</button>
<button color="primary" mat-stroked-button for="fileDropRef"
(click)="fileDropRef.click()">
<button color="primary" mat-stroked-button for="fileDropRef" (click)="fileDropRef.click()">
Upload another file
</button>
<button color="primary" mat-stroked-button
(click)="onClearButtonClick()">
Clear all
</button>
<button color="primary" mat-stroked-button (click)="onClearButtonClick()">Clear all</button>
</div>
</mat-card>
`,
@@ -171,13 +164,13 @@ import {FilesDownloadListener} from "interfaces/files_download_listener";
mat-card-content {
flex-grow: 1;
}
`
]
`,
],
})
export class UploadTracesComponent implements FilesDownloadListener {
TRACE_INFO = TRACE_INFO;
isLoadingFiles = false;
progressMessage = "";
progressMessage = '';
progressPercentage?: number;
@Input() traceData!: TraceData;
@@ -187,8 +180,7 @@ export class UploadTracesComponent implements FilesDownloadListener {
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef,
@Inject(MatSnackBar) private snackBar: MatSnackBar,
@Inject(NgZone) private ngZone: NgZone
) {
}
) {}
ngOnInit() {
this.traceData.clear();
@@ -196,7 +188,7 @@ export class UploadTracesComponent implements FilesDownloadListener {
public onFilesDownloadStart() {
this.isLoadingFiles = true;
this.progressMessage = "Downloading files...";
this.progressMessage = 'Downloading files...';
this.progressPercentage = undefined;
this.changeDetectorRef.detectChanges();
}
@@ -233,7 +225,7 @@ export class UploadTracesComponent implements FilesDownloadListener {
e.preventDefault();
e.stopPropagation();
const droppedFiles = e.dataTransfer?.files;
if(!droppedFiles) return;
if (!droppedFiles) return;
await this.processFiles(Array.from(droppedFiles));
}
@@ -250,7 +242,7 @@ export class UploadTracesComponent implements FilesDownloadListener {
const onProgressUpdate = (progress: number) => {
const now = Date.now();
if ((Date.now() - lastUiProgressUpdate) < UI_PROGRESS_UPDATE_PERIOD_MS) {
if (Date.now() - lastUiProgressUpdate < UI_PROGRESS_UPDATE_PERIOD_MS) {
// Let's limit the amount of UI updates, because the progress bar component
// renders weird stuff when updated too frequently
return;
@@ -267,11 +259,11 @@ export class UploadTracesComponent implements FilesDownloadListener {
};
this.isLoadingFiles = true;
this.progressMessage = "Unzipping files...";
this.progressMessage = 'Unzipping files...';
this.changeDetectorRef.detectChanges();
await FileUtils.unzipFilesIfNeeded(files, onFile, onProgressUpdate);
this.progressMessage = "Parsing files...";
this.progressMessage = 'Parsing files...';
this.changeDetectorRef.detectChanges();
const parserErrors = await this.traceData.loadTraces(traceFiles, onProgressUpdate);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {FilesDownloadListener} from "interfaces/files_download_listener";
import {FilesDownloadListener} from 'interfaces/files_download_listener';
export class UploadTracesComponentStub implements FilesDownloadListener {
onFilesDownloadStart() {

View File

@@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {WebAdbComponent} from "./web_adb.component";
import { MatIconModule } from "@angular/material/icon";
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {MatIconModule} from '@angular/material/icon';
import {WebAdbComponent} from './web_adb.component';
describe("WebAdbComponent", () => {
describe('WebAdbComponent', () => {
let fixture: ComponentFixture<WebAdbComponent>;
let component: WebAdbComponent;
let htmlElement: HTMLElement;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
MatIconModule
],
imports: [MatIconModule],
declarations: [WebAdbComponent],
}).compileComponents();
fixture = TestBed.createComponent(WebAdbComponent);
@@ -34,13 +32,13 @@ describe("WebAdbComponent", () => {
htmlElement = fixture.nativeElement;
});
it("can be created", () => {
it('can be created', () => {
expect(component).toBeTruthy();
});
it("renders the info message", () => {
it('renders the info message', () => {
fixture.detectChanges();
expect(htmlElement.querySelector(".adb-info")?.innerHTML).toBe("Add new device");
expect(htmlElement.querySelector(".adb-icon")?.innerHTML).toBe("info");
expect(htmlElement.querySelector('.adb-info')?.innerHTML).toBe('Add new device');
expect(htmlElement.querySelector('.adb-icon')?.innerHTML).toBe('info');
});
});

View File

@@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Component} from "@angular/core";
import {Component} from '@angular/core';
@Component({
selector: "web-adb",
selector: 'web-adb',
template: `
<p class="text-icon-wrapper mat-body-1">
<mat-icon class="adb-icon">info</mat-icon>
<span class="adb-info">Add new device</span>
</p>
<p class="mat-body-1">Click the button below to follow instructions in the Chrome pop-up.</p>
<p class="mat-body-1">Selecting a device will kill all existing ADB connections.</p>
<button color="primary" class="web-select-btn" mat-raised-button>Select a device</button>
<p class="text-icon-wrapper mat-body-1">
<mat-icon class="adb-icon">info</mat-icon>
<span class="adb-info">Add new device</span>
</p>
<p class="mat-body-1">Click the button below to follow instructions in the Chrome pop-up.</p>
<p class="mat-body-1">Selecting a device will kill all existing ADB connections.</p>
<button color="primary" class="web-select-btn" mat-raised-button>Select a device</button>
`,
styles: [
`
@@ -33,11 +33,12 @@ import {Component} from "@angular/core";
flex-direction: row;
align-items: center;
}
.adb-info, .web-select-btn {
.adb-info,
.web-select-btn {
margin-left: 5px;
}
`
]
`,
],
})
export class WebAdbComponent {
adbDevice = null;

View File

@@ -14,23 +14,23 @@
* limitations under the License.
*/
import {AppComponentStub} from "./components/app_component_stub";
import {TimelineComponentStub} from "./components/timeline/timeline_component_stub";
import {UploadTracesComponentStub} from "./components/upload_traces_component_stub";
import {Mediator} from "./mediator";
import {AbtChromeExtensionProtocolStub} from "abt_chrome_extension/abt_chrome_extension_protocol_stub";
import {CrossToolProtocolStub} from "cross_tool/cross_tool_protocol_stub";
import {TraceFile} from "common/trace/trace";
import {RealTimestamp} from "common/trace/timestamp";
import {UnitTestUtils} from "test/unit/utils";
import {ViewerFactory} from "viewers/viewer_factory";
import {ViewerStub} from "viewers/viewer_stub";
import {TimelineData} from "./timeline_data";
import {TraceData} from "./trace_data";
import {MockStorage} from "test/unit/mock_storage";
import {AbtChromeExtensionProtocolStub} from 'abt_chrome_extension/abt_chrome_extension_protocol_stub';
import {RealTimestamp} from 'common/trace/timestamp';
import {TraceFile} from 'common/trace/trace';
import {CrossToolProtocolStub} from 'cross_tool/cross_tool_protocol_stub';
import {MockStorage} from 'test/unit/mock_storage';
import {UnitTestUtils} from 'test/unit/utils';
import {ViewerFactory} from 'viewers/viewer_factory';
import {ViewerStub} from 'viewers/viewer_stub';
import {AppComponentStub} from './components/app_component_stub';
import {TimelineComponentStub} from './components/timeline/timeline_component_stub';
import {UploadTracesComponentStub} from './components/upload_traces_component_stub';
import {Mediator} from './mediator';
import {TimelineData} from './timeline_data';
import {TraceData} from './trace_data';
describe("Mediator", () => {
const viewerStub = new ViewerStub("Title");
describe('Mediator', () => {
const viewerStub = new ViewerStub('Title');
let traceData: TraceData;
let timelineData: TimelineData;
let abtChromeExtensionProtocol: AbtChromeExtensionProtocolStub;
@@ -63,13 +63,13 @@ describe("Mediator", () => {
mediator.setTimelineComponent(timelineComponent);
mediator.setUploadTracesComponent(uploadTracesComponent);
spyOn(ViewerFactory.prototype, "createViewers").and.returnValue([viewerStub]);
spyOn(ViewerFactory.prototype, 'createViewers').and.returnValue([viewerStub]);
});
it("handles data load event from Winscope", async () => {
spyOn(timelineData, "initialize").and.callThrough();
spyOn(appComponent, "onTraceDataLoaded");
spyOn(viewerStub, "notifyCurrentTraceEntries");
it('handles data load event from Winscope', async () => {
spyOn(timelineData, 'initialize').and.callThrough();
spyOn(appComponent, 'onTraceDataLoaded');
spyOn(viewerStub, 'notifyCurrentTraceEntries');
await loadTraces();
expect(timelineData.initialize).toHaveBeenCalledTimes(0);
@@ -89,16 +89,16 @@ describe("Mediator", () => {
//TODO: test "data from ABT chrome extension" when FileUtils is fully compatible with Node.js
// (b/262269229).
it("handles start download event from ABT chrome extension", () => {
spyOn(uploadTracesComponent, "onFilesDownloadStart");
it('handles start download event from ABT chrome extension', () => {
spyOn(uploadTracesComponent, 'onFilesDownloadStart');
expect(uploadTracesComponent.onFilesDownloadStart).toHaveBeenCalledTimes(0);
abtChromeExtensionProtocol.onBuganizerAttachmentsDownloadStart();
expect(uploadTracesComponent.onFilesDownloadStart).toHaveBeenCalledTimes(1);
});
it("handles empty downloaded files from ABT chrome extension", async () => {
spyOn(uploadTracesComponent, "onFilesDownloaded");
it('handles empty downloaded files from ABT chrome extension', async () => {
spyOn(uploadTracesComponent, 'onFilesDownloaded');
expect(uploadTracesComponent.onFilesDownloaded).toHaveBeenCalledTimes(0);
// Pass files even if empty so that the upload component will update the progress bar
@@ -107,13 +107,13 @@ describe("Mediator", () => {
expect(uploadTracesComponent.onFilesDownloaded).toHaveBeenCalledTimes(1);
});
it("propagates current timestamp changed through timeline", async () => {
it('propagates current timestamp changed through timeline', async () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
spyOn(viewerStub, "notifyCurrentTraceEntries");
spyOn(timelineComponent, "onCurrentTimestampChanged");
spyOn(crossToolProtocol, "sendTimestamp");
spyOn(viewerStub, 'notifyCurrentTraceEntries');
spyOn(timelineComponent, 'onCurrentTimestampChanged');
spyOn(crossToolProtocol, 'sendTimestamp');
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(0);
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
@@ -137,13 +137,13 @@ describe("Mediator", () => {
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(2);
});
describe("timestamp received from remote tool", () => {
it("propagates timestamp changes", async () => {
describe('timestamp received from remote tool', () => {
it('propagates timestamp changes', async () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
spyOn(viewerStub, "notifyCurrentTraceEntries");
spyOn(timelineComponent, "onCurrentTimestampChanged");
spyOn(viewerStub, 'notifyCurrentTraceEntries');
spyOn(timelineComponent, 'onCurrentTimestampChanged');
expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(0);
expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
@@ -167,8 +167,8 @@ describe("Mediator", () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
spyOn(viewerStub, "notifyCurrentTraceEntries");
spyOn(crossToolProtocol, "sendTimestamp");
spyOn(viewerStub, 'notifyCurrentTraceEntries');
spyOn(crossToolProtocol, 'sendTimestamp');
// receive timestamp
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
@@ -176,8 +176,8 @@ describe("Mediator", () => {
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
});
it("defers propagation till traces are loaded and visualized", async () => {
spyOn(timelineComponent, "onCurrentTimestampChanged");
it('defers propagation till traces are loaded and visualized', async () => {
spyOn(timelineComponent, 'onCurrentTimestampChanged');
// keep timestamp for later
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
@@ -196,12 +196,17 @@ describe("Mediator", () => {
const loadTraces = async () => {
const traces = [
new TraceFile(await UnitTestUtils.getFixtureFile(
"traces/elapsed_and_real_timestamp/SurfaceFlinger.pb")),
new TraceFile(await UnitTestUtils.getFixtureFile(
"traces/elapsed_and_real_timestamp/WindowManager.pb")),
new TraceFile(await UnitTestUtils.getFixtureFile(
"traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4")),
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
),
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb')
),
new TraceFile(
await UnitTestUtils.getFixtureFile(
'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4'
)
),
];
const errors = await traceData.loadTraces(traces);
expect(errors).toEqual([]);

View File

@@ -14,25 +14,26 @@
* limitations under the License.
*/
import {TimelineData} from "./timeline_data";
import {TraceData} from "./trace_data";
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceType} from "common/trace/trace_type";
import {BuganizerAttachmentsDownloadEmitter} from "interfaces/buganizer_attachments_download_emitter";
import {FilesDownloadListener} from "interfaces/files_download_listener";
import {RemoteBugreportReceiver} from "interfaces/remote_bugreport_receiver";
import {RemoteTimestampReceiver} from "interfaces/remote_timestamp_receiver";
import {RemoteTimestampSender} from "interfaces/remote_timestamp_sender";
import {Runnable} from "interfaces/runnable";
import {TimestampChangeListener} from "interfaces/timestamp_change_listener";
import {TraceDataListener} from "interfaces/trace_data_listener";
import {Viewer} from "viewers/viewer";
import {ViewerFactory} from "viewers/viewer_factory";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {BuganizerAttachmentsDownloadEmitter} from 'interfaces/buganizer_attachments_download_emitter';
import {FilesDownloadListener} from 'interfaces/files_download_listener';
import {RemoteBugreportReceiver} from 'interfaces/remote_bugreport_receiver';
import {RemoteTimestampReceiver} from 'interfaces/remote_timestamp_receiver';
import {RemoteTimestampSender} from 'interfaces/remote_timestamp_sender';
import {Runnable} from 'interfaces/runnable';
import {TimestampChangeListener} from 'interfaces/timestamp_change_listener';
import {TraceDataListener} from 'interfaces/trace_data_listener';
import {Viewer} from 'viewers/viewer';
import {ViewerFactory} from 'viewers/viewer_factory';
import {TimelineData} from './timeline_data';
import {TraceData} from './trace_data';
export type CrossToolProtocolDependencyInversion =
RemoteBugreportReceiver & RemoteTimestampReceiver & RemoteTimestampSender;
export type AbtChromeExtensionProtocolDependencyInversion =
BuganizerAttachmentsDownloadEmitter & Runnable;
export type CrossToolProtocolDependencyInversion = RemoteBugreportReceiver &
RemoteTimestampReceiver &
RemoteTimestampSender;
export type AbtChromeExtensionProtocolDependencyInversion = BuganizerAttachmentsDownloadEmitter &
Runnable;
export type AppComponentDependencyInversion = TraceDataListener;
export type TimelineComponentDependencyInversion = TimestampChangeListener;
export type UploadTracesComponentDependencyInversion = FilesDownloadListener;
@@ -50,7 +51,7 @@ export class Mediator {
private viewers: Viewer[] = [];
private isChangingCurrentTimestamp = false;
private isTraceDataVisualized = false;
private lastRemoteToolTimestampReceived: Timestamp|undefined;
private lastRemoteToolTimestampReceived: Timestamp | undefined;
constructor(
traceData: TraceData,
@@ -58,8 +59,8 @@ export class Mediator {
abtChromeExtensionProtocol: AbtChromeExtensionProtocolDependencyInversion,
crossToolProtocol: CrossToolProtocolDependencyInversion,
appComponent: AppComponentDependencyInversion,
storage: Storage) {
storage: Storage
) {
this.traceData = traceData;
this.timelineData = timelineData;
this.abtChromeExtensionProtocol = abtChromeExtensionProtocol;
@@ -67,13 +68,15 @@ export class Mediator {
this.appComponent = appComponent;
this.storage = storage;
this.timelineData.setOnCurrentTimestampChanged(timestamp => {
this.timelineData.setOnCurrentTimestampChanged((timestamp) => {
this.onWinscopeCurrentTimestampChanged(timestamp);
});
this.crossToolProtocol.setOnBugreportReceived(async (bugreport: File, timestamp?: Timestamp) => {
await this.onRemoteBugreportReceived(bugreport, timestamp);
});
this.crossToolProtocol.setOnBugreportReceived(
async (bugreport: File, timestamp?: Timestamp) => {
await this.onRemoteBugreportReceived(bugreport, timestamp);
}
);
this.crossToolProtocol.setOnTimestampReceived(async (timestamp: Timestamp) => {
this.onRemoteTimestampReceived(timestamp);
@@ -83,18 +86,20 @@ export class Mediator {
this.onBuganizerAttachmentsDownloadStart();
});
this.abtChromeExtensionProtocol.setOnBuganizerAttachmentsDownloaded(async (attachments: File[]) => {
await this.onBuganizerAttachmentsDownloaded(attachments);
});
this.abtChromeExtensionProtocol.setOnBuganizerAttachmentsDownloaded(
async (attachments: File[]) => {
await this.onBuganizerAttachmentsDownloaded(attachments);
}
);
}
public setUploadTracesComponent(
uploadTracesComponent: UploadTracesComponentDependencyInversion|undefined
uploadTracesComponent: UploadTracesComponentDependencyInversion | undefined
) {
this.uploadTracesComponent = uploadTracesComponent;
}
public setTimelineComponent(timelineComponent: TimelineComponentDependencyInversion|undefined) {
public setTimelineComponent(timelineComponent: TimelineComponentDependencyInversion | undefined) {
this.timelineComponent = timelineComponent;
}
@@ -110,19 +115,19 @@ export class Mediator {
this.processTraceData();
}
public onWinscopeCurrentTimestampChanged(timestamp: Timestamp|undefined) {
public onWinscopeCurrentTimestampChanged(timestamp: Timestamp | undefined) {
this.executeIgnoringRecursiveTimestampNotifications(() => {
const entries = this.traceData.getTraceEntries(timestamp);
this.viewers.forEach(viewer => {
this.viewers.forEach((viewer) => {
viewer.notifyCurrentTraceEntries(entries);
});
if (timestamp) {
if (timestamp.getType() !== TimestampType.REAL) {
console.warn(
"Cannot propagate timestamp change to remote tool." +
` Remote tool expects timestamp type ${TimestampType.REAL},` +
` but Winscope wants to notify timestamp type ${timestamp.getType()}.`
'Cannot propagate timestamp change to remote tool.' +
` Remote tool expects timestamp type ${TimestampType.REAL},` +
` but Winscope wants to notify timestamp type ${timestamp.getType()}.`
);
} else {
this.crossToolProtocol.sendTimestamp(timestamp);
@@ -159,9 +164,9 @@ export class Mediator {
if (this.timelineData.getTimestampType() !== timestamp.getType()) {
console.warn(
"Cannot apply new timestamp received from remote tool." +
` Remote tool notified timestamp type ${timestamp.getType()},` +
` but Winscope is accepting timestamp type ${this.timelineData.getTimestampType()}.`
'Cannot apply new timestamp received from remote tool.' +
` Remote tool notified timestamp type ${timestamp.getType()},` +
` but Winscope is accepting timestamp type ${this.timelineData.getTimestampType()}.`
);
return;
}
@@ -171,7 +176,7 @@ export class Mediator {
}
const entries = this.traceData.getTraceEntries(timestamp);
this.viewers.forEach(viewer => {
this.viewers.forEach((viewer) => {
viewer.notifyCurrentTraceEntries(entries);
});
@@ -200,7 +205,7 @@ export class Mediator {
}
private createViewers() {
const traceTypes = this.traceData.getLoadedTraces().map(trace => trace.type);
const traceTypes = this.traceData.getLoadedTraces().map((trace) => trace.type);
this.viewers = new ViewerFactory().createViewers(new Set<TraceType>(traceTypes), this.storage);
// Make sure to update the viewers active entries as soon as they are created.

View File

@@ -14,53 +14,55 @@
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceType} from "common/trace/trace_type";
import {TimelineData} from "./timeline_data";
import {Timeline} from "./trace_data";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {TimelineData} from './timeline_data';
import {Timeline} from './trace_data';
class TimestampChangedObserver {
onCurrentTimestampChanged(timestamp: Timestamp|undefined) {
onCurrentTimestampChanged(timestamp: Timestamp | undefined) {
// do nothing
}
}
describe("TimelineData", () => {
describe('TimelineData', () => {
let timelineData: TimelineData;
const timestampChangedObserver = new TimestampChangedObserver();
const timestamp10 = new Timestamp(TimestampType.REAL, 10n);
const timestamp11 = new Timestamp(TimestampType.REAL, 11n);
const timelines: Timeline[] = [{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp10]
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp11]
}];
const timelines: Timeline[] = [
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [timestamp10],
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [timestamp11],
},
];
beforeEach(() => {
timelineData = new TimelineData();
timelineData.setOnCurrentTimestampChanged(timestamp => {
timelineData.setOnCurrentTimestampChanged((timestamp) => {
timestampChangedObserver.onCurrentTimestampChanged(timestamp);
});
});
it("sets timelines", () => {
it('sets timelines', () => {
expect(timelineData.getCurrentTimestamp()).toBeUndefined();
timelineData.initialize(timelines, undefined);
expect(timelineData.getCurrentTimestamp()).toEqual(timestamp10);
});
it("uses first timestamp by default", () => {
it('uses first timestamp by default', () => {
timelineData.initialize(timelines, undefined);
expect(timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(10n);
});
it("uses explicit timestamp if set", () => {
it('uses explicit timestamp if set', () => {
timelineData.initialize(timelines, undefined);
expect(timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(10n);
@@ -72,7 +74,7 @@ describe("TimelineData", () => {
expect(timelineData.getCurrentTimestamp()).toEqual(explicitTimestamp);
});
it("sets active trace types and update current timestamp accordingly", () => {
it('sets active trace types and update current timestamp accordingly', () => {
timelineData.initialize(timelines, undefined);
timelineData.setActiveViewTraceTypes([]);
@@ -88,8 +90,8 @@ describe("TimelineData", () => {
expect(timelineData.getCurrentTimestamp()).toEqual(timestamp10);
});
it("notifies callback when current timestamp changes", () => {
spyOn(timestampChangedObserver, "onCurrentTimestampChanged");
it('notifies callback when current timestamp changes', () => {
spyOn(timestampChangedObserver, 'onCurrentTimestampChanged');
expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
timelineData.initialize(timelines, undefined);
@@ -102,7 +104,7 @@ describe("TimelineData", () => {
it("doesn't notify observers when current timestamp doesn't change", () => {
timelineData.initialize(timelines, undefined);
spyOn(timestampChangedObserver, "onCurrentTimestampChanged");
spyOn(timestampChangedObserver, 'onCurrentTimestampChanged');
expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER]);
@@ -112,47 +114,69 @@ describe("TimelineData", () => {
expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
});
it("hasTimestamps()", () => {
it('hasTimestamps()', () => {
expect(timelineData.hasTimestamps()).toBeFalse();
timelineData.initialize([], undefined);
expect(timelineData.hasTimestamps()).toBeFalse();
timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: []
}], undefined);
timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [],
},
],
undefined
);
expect(timelineData.hasTimestamps()).toBeFalse();
timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [new Timestamp(TimestampType.REAL, 10n)]
}], undefined);
timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [new Timestamp(TimestampType.REAL, 10n)],
},
],
undefined
);
expect(timelineData.hasTimestamps()).toBeTrue();
});
it("hasMoreThanOneDistinctTimestamp()", () => {
it('hasMoreThanOneDistinctTimestamp()', () => {
expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
timelineData.initialize([], undefined);
expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [new Timestamp(TimestampType.REAL, 10n)]
}, {
traceType: TraceType.WINDOW_MANAGER,
timestamps: [new Timestamp(TimestampType.REAL, 10n)]
}], undefined);
timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [new Timestamp(TimestampType.REAL, 10n)],
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [new Timestamp(TimestampType.REAL, 10n)],
},
],
undefined
);
expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
timelineData.initialize([{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [new Timestamp(TimestampType.REAL, 10n)]
}, {
traceType: TraceType.WINDOW_MANAGER,
timestamps: [new Timestamp(TimestampType.REAL, 11n)]
}], undefined);
timelineData.initialize(
[
{
traceType: TraceType.SURFACE_FLINGER,
timestamps: [new Timestamp(TimestampType.REAL, 10n)],
},
{
traceType: TraceType.WINDOW_MANAGER,
timestamps: [new Timestamp(TimestampType.REAL, 11n)],
},
],
undefined
);
expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeTrue();
});
});

View File

@@ -14,17 +14,17 @@
* limitations under the License.
*/
import {Timeline} from "./trace_data";
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceType} from "common/trace/trace_type";
import { ArrayUtils } from "common/utils/array_utils";
import { FunctionUtils} from "common/utils/function_utils";
import {TimeUtils} from "common/utils/time_utils";
import {ScreenRecordingUtils} from "common/trace/screen_recording_utils";
import {ScreenRecordingUtils} from 'common/trace/screen_recording_utils';
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {ArrayUtils} from 'common/utils/array_utils';
import {FunctionUtils} from 'common/utils/function_utils';
import {TimeUtils} from 'common/utils/time_utils';
import {Timeline} from './trace_data';
export type TimestampCallbackType = (timestamp: Timestamp|undefined) => void;
export type TimeRange = { from: Timestamp, to: Timestamp }
type TimestampWithIndex = {index: number, timestamp: Timestamp};
export type TimestampCallbackType = (timestamp: Timestamp | undefined) => void;
export type TimeRange = {from: Timestamp; to: Timestamp};
type TimestampWithIndex = {index: number; timestamp: Timestamp};
export class TimelineData {
private timelines = new Map<TraceType, Timestamp[]>();
@@ -35,21 +35,21 @@ export class TimelineData {
private activeViewTraceTypes: TraceType[] = []; // dependencies of current active view
private onCurrentTimestampChanged: TimestampCallbackType = FunctionUtils.DO_NOTHING;
public initialize(timelines: Timeline[], screenRecordingVideo: Blob|undefined) {
public initialize(timelines: Timeline[], screenRecordingVideo: Blob | undefined) {
this.clear();
this.screenRecordingVideo = screenRecordingVideo;
const allTimestamps = timelines.flatMap(timeline => timeline.timestamps);
if (allTimestamps.some(timestamp => timestamp.getType() != allTimestamps[0].getType())) {
throw Error("Added timeline has inconsistent timestamps.");
const allTimestamps = timelines.flatMap((timeline) => timeline.timestamps);
if (allTimestamps.some((timestamp) => timestamp.getType() != allTimestamps[0].getType())) {
throw Error('Added timeline has inconsistent timestamps.');
}
if (allTimestamps.length > 0) {
this.timestampType = allTimestamps[0].getType();
}
timelines.forEach(timeline => {
timelines.forEach((timeline) => {
this.timelines.set(timeline.traceType, timeline.timestamps);
});
@@ -60,7 +60,7 @@ export class TimelineData {
this.onCurrentTimestampChanged = callback;
}
getCurrentTimestamp(): Timestamp|undefined {
getCurrentTimestamp(): Timestamp | undefined {
if (this.explicitlySetTimestamp !== undefined) {
return this.explicitlySetTimestamp;
}
@@ -70,18 +70,18 @@ export class TimelineData {
return this.getFirstTimestamp();
}
public setCurrentTimestamp(timestamp: Timestamp|undefined) {
public setCurrentTimestamp(timestamp: Timestamp | undefined) {
if (!this.hasTimestamps()) {
console.warn("Attempted to set timestamp on traces with no timestamps/entries...");
console.warn('Attempted to set timestamp on traces with no timestamps/entries...');
return;
}
if (timestamp !== undefined) {
if (this.timestampType === undefined) {
throw Error("Attempted to set explicit timestamp but no timestamp type is available");
throw Error('Attempted to set explicit timestamp but no timestamp type is available');
}
if (timestamp.getType() !== this.timestampType) {
throw Error("Attempted to set explicit timestamp with incompatible type");
throw Error('Attempted to set explicit timestamp with incompatible type');
}
}
@@ -90,24 +90,23 @@ export class TimelineData {
});
}
public setActiveViewTraceTypes(types: TraceType[]) {
this.applyOperationAndNotifyIfCurrentTimestampChanged(() => {
this.activeViewTraceTypes = types;
});
}
public getTimestampType(): TimestampType|undefined {
public getTimestampType(): TimestampType | undefined {
return this.timestampType;
}
public getFullRange(): TimeRange {
if (!this.hasTimestamps()) {
throw Error("Trying to get full range when there are no timestamps");
throw Error('Trying to get full range when there are no timestamps');
}
return {
from: this.getFirstTimestamp()!,
to: this.getLastTimestamp()!
to: this.getLastTimestamp()!,
};
}
@@ -123,15 +122,15 @@ export class TimelineData {
this.explicitlySetSelection = selection;
}
public getTimelines(): Map<TraceType, Timestamp[]> {
public getTimelines(): Map<TraceType, Timestamp[]> {
return this.timelines;
}
public getScreenRecordingVideo(): Blob|undefined {
public getScreenRecordingVideo(): Blob | undefined {
return this.screenRecordingVideo;
}
public searchCorrespondingScreenRecordingTimeSeconds(timestamp: Timestamp): number|undefined {
public searchCorrespondingScreenRecordingTimeSeconds(timestamp: Timestamp): number | undefined {
const timestamps = this.timelines.get(TraceType.SCREEN_RECORDING);
if (!timestamps) {
return undefined;
@@ -139,7 +138,10 @@ export class TimelineData {
const firstTimestamp = timestamps[0];
const correspondingTimestamp = this.searchCorrespondingTimestampFor(TraceType.SCREEN_RECORDING, timestamp)?.timestamp;
const correspondingTimestamp = this.searchCorrespondingTimestampFor(
TraceType.SCREEN_RECORDING,
timestamp
)?.timestamp;
if (correspondingTimestamp === undefined) {
return undefined;
}
@@ -148,25 +150,29 @@ export class TimelineData {
}
public hasTimestamps(): boolean {
return Array.from(this.timelines.values()).some(timestamps => timestamps.length > 0);
return Array.from(this.timelines.values()).some((timestamps) => timestamps.length > 0);
}
public hasMoreThanOneDistinctTimestamp(): boolean {
return this.hasTimestamps() && this.getFirstTimestamp() !== this.getLastTimestamp();
}
public getCurrentTimestampFor(type: TraceType): Timestamp|undefined {
public getCurrentTimestampFor(type: TraceType): Timestamp | undefined {
return this.searchCorrespondingTimestampFor(type, this.getCurrentTimestamp())?.timestamp;
}
public getPreviousTimestampFor(type: TraceType): Timestamp|undefined {
const currentIndex =
this.searchCorrespondingTimestampFor(type, this.getCurrentTimestamp())?.index;
public getPreviousTimestampFor(type: TraceType): Timestamp | undefined {
const currentIndex = this.searchCorrespondingTimestampFor(
type,
this.getCurrentTimestamp()
)?.index;
if (currentIndex === undefined) {
// Only acceptable reason for this to be undefined is if we are before the first entry for this type
if (this.timelines.get(type)!.length === 0 ||
this.getCurrentTimestamp()!.getValueNs() < this.timelines.get(type)![0].getValueNs()) {
if (
this.timelines.get(type)!.length === 0 ||
this.getCurrentTimestamp()!.getValueNs() < this.timelines.get(type)![0].getValueNs()
) {
return undefined;
}
throw Error(`Missing active timestamp for trace type ${type}`);
@@ -180,7 +186,7 @@ export class TimelineData {
return this.timelines.get(type)?.[previousIndex];
}
public getNextTimestampFor(type: TraceType): Timestamp|undefined {
public getNextTimestampFor(type: TraceType): Timestamp | undefined {
const currentIndex =
this.searchCorrespondingTimestampFor(type, this.getCurrentTimestamp())?.index ?? -1;
@@ -190,7 +196,7 @@ export class TimelineData {
const timestamps = this.timelines.get(type);
if (timestamps === undefined) {
throw Error("Timestamps for tracetype not found");
throw Error('Timestamps for tracetype not found');
}
const nextIndex = currentIndex + 1;
if (nextIndex >= timestamps.length) {
@@ -225,36 +231,38 @@ export class TimelineData {
});
}
private getFirstTimestamp(): Timestamp|undefined {
private getFirstTimestamp(): Timestamp | undefined {
if (!this.hasTimestamps()) {
return undefined;
}
return Array.from(this.timelines.values())
.map(timestamps => timestamps[0])
.filter(timestamp => timestamp !== undefined)
.reduce((prev, current) => prev < current ? prev : current);
.map((timestamps) => timestamps[0])
.filter((timestamp) => timestamp !== undefined)
.reduce((prev, current) => (prev < current ? prev : current));
}
private getLastTimestamp(): Timestamp|undefined {
private getLastTimestamp(): Timestamp | undefined {
if (!this.hasTimestamps()) {
return undefined;
}
return Array.from(this.timelines.values())
.map(timestamps => timestamps[timestamps.length-1])
.filter(timestamp => timestamp !== undefined)
.reduce((prev, current) => prev > current ? prev : current);
.map((timestamps) => timestamps[timestamps.length - 1])
.filter((timestamp) => timestamp !== undefined)
.reduce((prev, current) => (prev > current ? prev : current));
}
private searchCorrespondingTimestampFor(type: TraceType, timestamp: Timestamp|undefined):
TimestampWithIndex|undefined {
private searchCorrespondingTimestampFor(
type: TraceType,
timestamp: Timestamp | undefined
): TimestampWithIndex | undefined {
if (timestamp === undefined) {
return undefined;
}
if (timestamp.getType() !== this.timestampType) {
throw Error("Invalid timestamp type");
throw Error('Invalid timestamp type');
}
const timeline = this.timelines.get(type);
@@ -265,17 +273,17 @@ export class TimelineData {
if (index === undefined) {
return undefined;
}
return { index, timestamp: timeline[index] };
return {index, timestamp: timeline[index]};
}
private getFirstTimestampOfActiveViewTraces(): Timestamp|undefined {
private getFirstTimestampOfActiveViewTraces(): Timestamp | undefined {
if (this.activeViewTraceTypes.length === 0) {
return undefined;
}
const activeTimestamps = this.activeViewTraceTypes
.map(traceType => this.timelines.get(traceType)!)
.map(timestamps => timestamps[0])
.filter(timestamp => timestamp !== undefined)
.map((traceType) => this.timelines.get(traceType)!)
.map((timestamps) => timestamps[0])
.filter((timestamp) => timestamp !== undefined)
.sort(TimeUtils.compareFn);
if (activeTimestamps.length === 0) {
return undefined;

View File

@@ -13,28 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceFile} from "common/trace/trace";
import {TraceType} from "common/trace/trace_type";
import {TraceData} from "./trace_data";
import {UnitTestUtils} from "test/unit/utils";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceFile} from 'common/trace/trace';
import {TraceType} from 'common/trace/trace_type';
import {UnitTestUtils} from 'test/unit/utils';
import {TraceData} from './trace_data';
describe("TraceData", () => {
describe('TraceData', () => {
let traceData: TraceData;
beforeEach(async () => {
traceData = new TraceData();
});
it("can load valid trace files", async () => {
it('can load valid trace files', async () => {
expect(traceData.getLoadedTraces().length).toEqual(0);
await loadValidSfWmTraces();
expect(traceData.getLoadedTraces().length).toEqual(2);
});
it("is robust to invalid trace files", async () => {
it('is robust to invalid trace files', async () => {
const invalidTraceFiles = [
new TraceFile(await UnitTestUtils.getFixtureFile("winscope_homepage.png"))
new TraceFile(await UnitTestUtils.getFixtureFile('winscope_homepage.png')),
];
const errors = await traceData.loadTraces(invalidTraceFiles);
@@ -42,22 +42,20 @@ describe("TraceData", () => {
expect(traceData.getLoadedTraces().length).toEqual(0);
});
it("is robust to mixed valid and invalid trace files", async () => {
it('is robust to mixed valid and invalid trace files', async () => {
expect(traceData.getLoadedTraces().length).toEqual(0);
const traces = [
new TraceFile(await UnitTestUtils.getFixtureFile("winscope_homepage.png")),
new TraceFile(await UnitTestUtils.getFixtureFile("traces/dump_WindowManager.pb"))
new TraceFile(await UnitTestUtils.getFixtureFile('winscope_homepage.png')),
new TraceFile(await UnitTestUtils.getFixtureFile('traces/dump_WindowManager.pb')),
];
const errors = await traceData.loadTraces(traces);
expect(traceData.getLoadedTraces().length).toEqual(1);
expect(errors.length).toEqual(1);
});
it("is robust to trace files with no entries", async () => {
it('is robust to trace files with no entries', async () => {
const traceFilesWithNoEntries = [
new TraceFile(
await UnitTestUtils.getFixtureFile("traces/no_entries_InputMethodClients.pb")
)
new TraceFile(await UnitTestUtils.getFixtureFile('traces/no_entries_InputMethodClients.pb')),
];
const errors = await traceData.loadTraces(traceFilesWithNoEntries);
@@ -71,7 +69,7 @@ describe("TraceData", () => {
expect(timelines[0].timestamps).toEqual([]);
});
it("can remove traces", async () => {
it('can remove traces', async () => {
await loadValidSfWmTraces();
expect(traceData.getLoadedTraces().length).toEqual(2);
@@ -82,24 +80,26 @@ describe("TraceData", () => {
expect(traceData.getLoadedTraces().length).toEqual(0);
});
it("gets loaded traces", async () => {
it('gets loaded traces', async () => {
await loadValidSfWmTraces();
const traces = traceData.getLoadedTraces();
expect(traces.length).toEqual(2);
expect(traces[0].traceFile.file).toBeTruthy();
const actualTraceTypes = new Set(traces.map(trace => trace.type));
const actualTraceTypes = new Set(traces.map((trace) => trace.type));
const expectedTraceTypes = new Set([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
expect(actualTraceTypes).toEqual(expectedTraceTypes);
});
it("gets trace entries for a given timestamp", async () => {
it('gets trace entries for a given timestamp', async () => {
const traceFiles = [
new TraceFile(await UnitTestUtils.getFixtureFile(
"traces/elapsed_and_real_timestamp/SurfaceFlinger.pb")),
new TraceFile(await UnitTestUtils.getFixtureFile(
"traces/elapsed_and_real_timestamp/WindowManager.pb"))
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
),
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb')
),
];
const errors = await traceData.loadTraces(traceFiles);
@@ -124,26 +124,29 @@ describe("TraceData", () => {
}
});
it("gets timelines", async () => {
it('gets timelines', async () => {
await loadValidSfWmTraces();
const timelines = traceData.getTimelines();
const actualTraceTypes = new Set(timelines.map(timeline => timeline.traceType));
const actualTraceTypes = new Set(timelines.map((timeline) => timeline.traceType));
const expectedTraceTypes = new Set([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
expect(actualTraceTypes).toEqual(expectedTraceTypes);
timelines.forEach(timeline => {
timelines.forEach((timeline) => {
expect(timeline.timestamps.length).toBeGreaterThan(0);
});
});
it("gets screenrecording data", async () => {
it('gets screenrecording data', async () => {
expect(traceData.getScreenRecordingVideo()).toBeUndefined();
const traceFiles = [
new TraceFile(await UnitTestUtils.getFixtureFile(
"traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4"))
new TraceFile(
await UnitTestUtils.getFixtureFile(
'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4'
)
),
];
await traceData.loadTraces(traceFiles);
@@ -152,7 +155,7 @@ describe("TraceData", () => {
expect(video!.size).toBeGreaterThan(0);
});
it("can be cleared", async () => {
it('can be cleared', async () => {
await loadValidSfWmTraces();
expect(traceData.getLoadedTraces().length).toBeGreaterThan(0);
expect(traceData.getTimelines().length).toBeGreaterThan(0);
@@ -164,10 +167,12 @@ describe("TraceData", () => {
const loadValidSfWmTraces = async () => {
const traceFiles = [
new TraceFile(await UnitTestUtils.getFixtureFile(
"traces/elapsed_and_real_timestamp/SurfaceFlinger.pb")),
new TraceFile(await UnitTestUtils.getFixtureFile(
"traces/elapsed_and_real_timestamp/WindowManager.pb")),
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
),
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb')
),
];
const errors = await traceData.loadTraces(traceFiles);

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*/
import {ArrayUtils} from "common/utils/array_utils";
import {ScreenRecordingTraceEntry} from "common/trace/screen_recording";
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {Trace, TraceFile} from "common/trace/trace";
import {TraceType} from "common/trace/trace_type";
import {FunctionUtils, OnProgressUpdateType} from "common/utils/function_utils";
import {Parser} from "parsers/parser";
import {ParserError, ParserFactory} from "parsers/parser_factory";
import {ScreenRecordingTraceEntry} from 'common/trace/screen_recording';
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {Trace, TraceFile} from 'common/trace/trace';
import {TraceType} from 'common/trace/trace_type';
import {ArrayUtils} from 'common/utils/array_utils';
import {FunctionUtils, OnProgressUpdateType} from 'common/utils/function_utils';
import {Parser} from 'parsers/parser';
import {ParserError, ParserFactory} from 'parsers/parser_factory';
interface Timeline {
traceType: TraceType;
@@ -35,43 +35,47 @@ class TraceData {
public async loadTraces(
traceFiles: TraceFile[],
onLoadProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING):
Promise<ParserError[]> {
const [parsers, parserErrors] =
await this.parserFactory.createParsers(traceFiles, onLoadProgressUpdate);
onLoadProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING
): Promise<ParserError[]> {
const [parsers, parserErrors] = await this.parserFactory.createParsers(
traceFiles,
onLoadProgressUpdate
);
this.parsers = parsers;
return parserErrors;
}
public removeTrace(type: TraceType) {
this.parsers = this.parsers.filter(parser => parser.getTraceType() !== type);
this.parsers = this.parsers.filter((parser) => parser.getTraceType() !== type);
}
public getLoadedTraces(): Trace[] {
return this.parsers.map((parser: Parser) => parser.getTrace());
}
public getTraceEntries(timestamp: Timestamp|undefined): Map<TraceType, any> {
public getTraceEntries(timestamp: Timestamp | undefined): Map<TraceType, any> {
const traceEntries: Map<TraceType, any> = new Map<TraceType, any>();
if (!timestamp) {
return traceEntries;
}
this.parsers.forEach(parser => {
this.parsers.forEach((parser) => {
const targetTimestamp = timestamp;
const entry = parser.getTraceEntry(targetTimestamp);
let prevEntry = null;
const parserTimestamps = parser.getTimestamps(timestamp.getType());
if (parserTimestamps === undefined) {
throw new Error(`Unexpected timestamp type ${timestamp.getType()}.`
+ ` Not supported by parser for trace type: ${parser.getTraceType()}`);
throw new Error(
`Unexpected timestamp type ${timestamp.getType()}.` +
` Not supported by parser for trace type: ${parser.getTraceType()}`
);
}
const index = ArrayUtils.binarySearchLowerOrEqual(parserTimestamps, targetTimestamp);
if (index !== undefined && index > 0) {
prevEntry = parser.getTraceEntry(parserTimestamps[index-1]);
prevEntry = parser.getTraceEntry(parserTimestamps[index - 1]);
}
if (entry !== undefined) {
@@ -86,7 +90,7 @@ class TraceData {
const timelines = this.parsers.map((parser): Timeline => {
const timestamps = parser.getTimestamps(this.getCommonTimestampType());
if (timestamps === undefined) {
throw Error("Failed to get timestamps from parser");
throw Error('Failed to get timestamps from parser');
}
return {traceType: parser.getTraceType(), timestamps: timestamps};
});
@@ -94,9 +98,10 @@ class TraceData {
return timelines;
}
public getScreenRecordingVideo(): undefined|Blob {
const parser = this.parsers
.find((parser) => parser.getTraceType() === TraceType.SCREEN_RECORDING);
public getScreenRecordingVideo(): undefined | Blob {
const parser = this.parsers.find(
(parser) => parser.getTraceType() === TraceType.SCREEN_RECORDING
);
if (!parser) {
return undefined;
}
@@ -122,13 +127,13 @@ class TraceData {
const priorityOrder = [TimestampType.REAL, TimestampType.ELAPSED];
for (const type of priorityOrder) {
if (this.parsers.every(it => it.getTimestamps(type) !== undefined)) {
if (this.parsers.every((it) => it.getTimestamps(type) !== undefined)) {
this.commonTimestampType = type;
return this.commonTimestampType;
}
}
throw Error("Failed to find common timestamp type across all traces");
throw Error('Failed to find common timestamp type across all traces');
}
}

View File

@@ -1,21 +1,21 @@
import { TraceType } from "common/trace/trace_type";
import {TraceType} from 'common/trace/trace_type';
const WINDOW_MANAGER_ICON = "view_compact";
const SURFACE_FLINGER_ICON = "filter_none";
const SCREEN_RECORDING_ICON = "videocam";
const TRANSACTION_ICON = "timeline";
const WAYLAND_ICON = "filter_none";
const PROTO_LOG_ICON = "notes";
const SYSTEM_UI_ICON = "filter_none";
const LAUNCHER_ICON = "filter_none";
const IME_ICON = "keyboard";
const ACCESSIBILITY_ICON = "filter_none";
const TAG_ICON = "details";
const TRACE_ERROR_ICON = "warning";
const WINDOW_MANAGER_ICON = 'view_compact';
const SURFACE_FLINGER_ICON = 'filter_none';
const SCREEN_RECORDING_ICON = 'videocam';
const TRANSACTION_ICON = 'timeline';
const WAYLAND_ICON = 'filter_none';
const PROTO_LOG_ICON = 'notes';
const SYSTEM_UI_ICON = 'filter_none';
const LAUNCHER_ICON = 'filter_none';
const IME_ICON = 'keyboard';
const ACCESSIBILITY_ICON = 'filter_none';
const TAG_ICON = 'details';
const TRACE_ERROR_ICON = 'warning';
type iconMap = {
[key: number]: string;
}
[key: number]: string;
};
export const TRACE_ICONS: iconMap = {
[TraceType.ACCESSIBILITY]: ACCESSIBILITY_ICON,

View File

@@ -14,108 +14,108 @@
* limitations under the License.
*/
import { TraceType } from "common/trace/trace_type";
import {TraceType} from 'common/trace/trace_type';
const WINDOW_MANAGER_ICON = "web";
const SURFACE_FLINGER_ICON = "layers";
const SCREEN_RECORDING_ICON = "videocam";
const TRANSACTION_ICON = "show_chart";
const WAYLAND_ICON = "filter_none";
const PROTO_LOG_ICON = "notes";
const SYSTEM_UI_ICON = "filter_none";
const LAUNCHER_ICON = "filter_none";
const IME_ICON = "keyboard_alt";
const ACCESSIBILITY_ICON = "accessibility_new";
const TAG_ICON = "details";
const TRACE_ERROR_ICON = "warning";
const WINDOW_MANAGER_ICON = 'web';
const SURFACE_FLINGER_ICON = 'layers';
const SCREEN_RECORDING_ICON = 'videocam';
const TRANSACTION_ICON = 'show_chart';
const WAYLAND_ICON = 'filter_none';
const PROTO_LOG_ICON = 'notes';
const SYSTEM_UI_ICON = 'filter_none';
const LAUNCHER_ICON = 'filter_none';
const IME_ICON = 'keyboard_alt';
const ACCESSIBILITY_ICON = 'accessibility_new';
const TAG_ICON = 'details';
const TRACE_ERROR_ICON = 'warning';
type traceInfoMap = {
[key: number]: {
name: string,
icon: string,
color: string,
};
}
[key: number]: {
name: string;
icon: string;
color: string;
};
};
export const TRACE_INFO: traceInfoMap = {
[TraceType.ACCESSIBILITY]: {
name: "Accessibility",
name: 'Accessibility',
icon: ACCESSIBILITY_ICON,
color: "#FF63B8",
color: '#FF63B8',
},
[TraceType.WINDOW_MANAGER]: {
name: "Window Manager",
name: 'Window Manager',
icon: WINDOW_MANAGER_ICON,
color: "#AF5CF7",
color: '#AF5CF7',
},
[TraceType.SURFACE_FLINGER]: {
name: "Surface Flinger",
name: 'Surface Flinger',
icon: SURFACE_FLINGER_ICON,
color: "#4ECDE6",
color: '#4ECDE6',
},
[TraceType.SCREEN_RECORDING]: {
name: "Screen Recording",
name: 'Screen Recording',
icon: SCREEN_RECORDING_ICON,
color: "#8A9CF9",
color: '#8A9CF9',
},
[TraceType.TRANSACTIONS]: {
name: "Transactions",
name: 'Transactions',
icon: TRANSACTION_ICON,
color: "#5BB974",
color: '#5BB974',
},
[TraceType.TRANSACTIONS_LEGACY]: {
name: "Transactions Legacy",
name: 'Transactions Legacy',
icon: TRANSACTION_ICON,
color: "#5BB974",
color: '#5BB974',
},
[TraceType.WAYLAND]: {
name: "Wayland",
name: 'Wayland',
icon: WAYLAND_ICON,
color: "#FDC274",
color: '#FDC274',
},
[TraceType.WAYLAND_DUMP]: {
name: "Wayland Dump",
name: 'Wayland Dump',
icon: WAYLAND_ICON,
color: "#D01884",
color: '#D01884',
},
[TraceType.PROTO_LOG]: {
name: "ProtoLog",
name: 'ProtoLog',
icon: PROTO_LOG_ICON,
color: "#40A58A",
color: '#40A58A',
},
[TraceType.SYSTEM_UI]: {
name: "System UI",
name: 'System UI',
icon: SYSTEM_UI_ICON,
color: "#7A86FF",
color: '#7A86FF',
},
[TraceType.LAUNCHER]: {
name: "Launcher",
name: 'Launcher',
icon: LAUNCHER_ICON,
color: "#137333",
color: '#137333',
},
[TraceType.INPUT_METHOD_CLIENTS]: {
name: "IME Clients",
name: 'IME Clients',
icon: IME_ICON,
color: "#FA903E",
color: '#FA903E',
},
[TraceType.INPUT_METHOD_SERVICE]: {
name: "IME Service",
name: 'IME Service',
icon: IME_ICON,
color: "#F29900",
color: '#F29900',
},
[TraceType.INPUT_METHOD_MANAGER_SERVICE]: {
name: "IME Manager Service",
name: 'IME Manager Service',
icon: IME_ICON,
color: "#D93025",
color: '#D93025',
},
[TraceType.TAG]: {
name: "Tag",
name: 'Tag',
icon: TAG_ICON,
color: "#4575B4",
color: '#4575B4',
},
[TraceType.ERROR]: {
name: "Error",
name: 'Error',
icon: TRACE_ERROR_ICON,
color: "#D73027",
color: '#D73027',
},
};

View File

@@ -14,283 +14,308 @@
* limitations under the License.
*/
import {ArrayUtils} from 'common/utils/array_utils';
import {PropertiesDump} from 'viewers/common/ui_tree_utils';
import intDefMapping from '../../../../../../../prebuilts/misc/common/winscope/intDefMapping.json';
import {
toSize, toActiveBuffer, toColor, toColor3, toPoint, toPointF, toRect,
toRectF, toRegion, toMatrix22, toTransform, toInsets
toActiveBuffer,
toColor,
toColor3,
toInsets,
toMatrix22,
toPoint,
toPointF,
toRect,
toRectF,
toRegion,
toSize,
toTransform,
} from './common';
import {ArrayUtils} from "common/utils/array_utils";
import { PropertiesDump } from "viewers/common/ui_tree_utils";
import intDefMapping from
'../../../../../../../prebuilts/misc/common/winscope/intDefMapping.json';
import config from './Configuration.json'
import config from './Configuration.json';
function readIntdefMap(): Map<string, string> {
const map = new Map<string, string>();
const keys = Object.keys(config.intDefColumn);
const map = new Map<string, string>();
const keys = Object.keys(config.intDefColumn);
keys.forEach(key => {
const value = config.intDefColumn[<keyof typeof config.intDefColumn>key];
map.set(key, value);
});
keys.forEach((key) => {
const value = config.intDefColumn[<keyof typeof config.intDefColumn>key];
map.set(key, value);
});
return map;
return map;
}
export default class ObjectFormatter {
static displayDefaults: boolean = false
private static INVALID_ELEMENT_PROPERTIES = config.invalidProperties;
static displayDefaults: boolean = false;
private static INVALID_ELEMENT_PROPERTIES = config.invalidProperties;
private static FLICKER_INTDEF_MAP = readIntdefMap();
private static FLICKER_INTDEF_MAP = readIntdefMap();
static cloneObject(entry: any): any {
let obj: any = {}
const properties = ObjectFormatter.getProperties(entry);
properties.forEach(prop => obj[prop] = entry[prop]);
return obj;
}
static cloneObject(entry: any): any {
const obj: any = {};
const properties = ObjectFormatter.getProperties(entry);
properties.forEach((prop) => (obj[prop] = entry[prop]));
return obj;
}
/**
* Get the true properties of an entry excluding functions, kotlin gernerated
* variables, explicitly excluded properties, and flicker objects already in
* the hierarchy that shouldn't be traversed when formatting the entry
* @param entry The entry for which we want to get the properties for
* @return The "true" properties of the entry as described above
*/
static getProperties(entry: any): string[] {
const props: string[] = [];
let obj = entry;
/**
* Get the true properties of an entry excluding functions, kotlin gernerated
* variables, explicitly excluded properties, and flicker objects already in
* the hierarchy that shouldn't be traversed when formatting the entry
* @param entry The entry for which we want to get the properties for
* @return The "true" properties of the entry as described above
*/
static getProperties(entry: any): string[] {
const props: string[] = [];
let obj = entry;
do {
const properties = Object.getOwnPropertyNames(obj).filter(it => {
// filter out functions
if (typeof (entry[it]) === 'function') return false;
// internal propertires from kotlinJs
if (it.includes(`$`)) return false;
// private kotlin variables from kotlin
if (it.startsWith(`_`)) return false;
// some predefined properties used only internally (e.g., children, ref, diff)
if (this.INVALID_ELEMENT_PROPERTIES.includes(it)) return false;
do {
const properties = Object.getOwnPropertyNames(obj).filter((it) => {
// filter out functions
if (typeof entry[it] === 'function') return false;
// internal propertires from kotlinJs
if (it.includes(`$`)) return false;
// private kotlin variables from kotlin
if (it.startsWith(`_`)) return false;
// some predefined properties used only internally (e.g., children, ref, diff)
if (this.INVALID_ELEMENT_PROPERTIES.includes(it)) return false;
const value = entry[it];
// only non-empty arrays of non-flicker objects (otherwise they are in hierarchy)
if (Array.isArray(value) && value.length > 0) return !value[0].stableId;
// non-flicker object
return !(value?.stableId);
});
properties.forEach(function (prop) {
if (typeof (entry[prop]) !== 'function' && props.indexOf(prop) === -1) {
props.push(prop);
}
});
} while (obj = Object.getPrototypeOf(obj));
return props;
}
/**
* Format a Winscope entry to be displayed in the UI
* Accounts for different user display settings (e.g. hiding empty/default values)
* @param obj The raw object to format
* @return The formatted object
*/
static format(obj: any): PropertiesDump {
const properties = this.getProperties(obj);
const sortedProperties = properties.sort()
const result: PropertiesDump = {};
sortedProperties.forEach(entry => {
const key = entry;
const value: any = obj[key];
if (value === null || value === undefined) {
if (this.displayDefaults) {
result[key] = value;
}
return
}
if (value || this.displayDefaults) {
// raw values (e.g., false or 0)
if (!value) {
result[key] = value
// flicker obj
} else if (value.prettyPrint) {
const isEmpty = value.isEmpty === true;
if (!isEmpty || this.displayDefaults) {
result[key] = value.prettyPrint();
}
} else {
// converted proto to flicker
const translatedObject = this.translateObject(key, value);
if (translatedObject) {
if (translatedObject.prettyPrint) {
result[key] = translatedObject.prettyPrint();
}
else {
result[key] = translatedObject;
}
// objects - recursive call
} else if (value && typeof (value) == `object`) {
const childObj = this.format(value) as any;
const isEmpty = Object.entries(childObj).length == 0 || childObj.isEmpty;
if (!isEmpty || this.displayDefaults) {
result[key] = childObj;
}
} else {
// values
result[key] = this.translateIntDef(obj, key, value);
}
}
}
})
return result;
}
/**
* Translate some predetermined proto objects into their flicker equivalent
*
* Returns null if the object cannot be translated
*
* @param obj Object to translate
*/
private static translateObject(key: string, obj: any) {
const type = obj?.$type?.name ?? obj?.constructor?.name;
switch (type) {
case `SizeProto`: return toSize(obj);
case `ActiveBufferProto`: return toActiveBuffer(obj);
case `Color3`: return toColor3(obj);
case `ColorProto`: return toColor(obj);
case `Long`: return obj?.toString();
case `PointProto`: return toPoint(obj);
case `PositionProto`: return toPointF(obj);
// It is necessary to check for a keyword insets because the proto
// definition of insets and rects uses the same object type
case `RectProto`: return key.toLowerCase().includes("insets") ? toInsets(obj) : toRect(obj);
case `Matrix22`: return toMatrix22(obj);
case `FloatRectProto`: return toRectF(obj);
case `RegionProto`: return toRegion(obj);
case `TransformProto`: return toTransform(obj);
case 'ColorTransformProto': {
const formatted = this.formatColorTransform(obj.val);
return `${formatted}`;
}
const value = entry[it];
// only non-empty arrays of non-flicker objects (otherwise they are in hierarchy)
if (Array.isArray(value) && value.length > 0) return !value[0].stableId;
// non-flicker object
return !value?.stableId;
});
properties.forEach(function (prop) {
if (typeof entry[prop] !== 'function' && props.indexOf(prop) === -1) {
props.push(prop);
}
});
obj = Object.getPrototypeOf(obj);
} while (obj);
// Raw long number (no type name, no constructor name, no useful toString() method)
if (ArrayUtils.equal(Object.keys(obj).sort(), ["high_", "low_"])) {
const high = BigInt(obj.high_) << 32n;
let low = BigInt(obj.low_);
if (low < 0) {
low = -low;
}
return (high | low).toString();
return props;
}
/**
* Format a Winscope entry to be displayed in the UI
* Accounts for different user display settings (e.g. hiding empty/default values)
* @param obj The raw object to format
* @return The formatted object
*/
static format(obj: any): PropertiesDump {
const properties = this.getProperties(obj);
const sortedProperties = properties.sort();
const result: PropertiesDump = {};
sortedProperties.forEach((entry) => {
const key = entry;
const value: any = obj[key];
if (value === null || value === undefined) {
if (this.displayDefaults) {
result[key] = value;
}
return;
}
return null;
}
private static formatColorTransform(vals: any) {
const fixedVals = vals.map((v: any) => v.toFixed(1));
let formatted = ``;
for (let i = 0; i < fixedVals.length; i += 4) {
formatted += `[`;
formatted += fixedVals.slice(i, i + 4).join(', ');
formatted += `] `;
}
return formatted;
}
/**
* Obtains from the proto field, the metadata related to the typedef type (if any)
*
* @param obj Proto object
* @param propertyName Property to search
*/
private static getTypeDefSpec(obj: any, propertyName: string): string | null {
const fields = obj?.$type?.fields;
if (!fields) {
return null;
}
const options = fields[propertyName]?.options;
if (!options) {
return null;
}
return options["(.android.typedef)"];
}
/**
* Translate intdef properties into their string representation
*
* For proto objects check the
*
* @param parentObj Object containing the value to parse
* @param propertyName Property to search
* @param value Property value
*/
private static translateIntDef(parentObj: any, propertyName: string, value: any): string {
const parentClassName = parentObj.constructor.name;
const propertyPath = `${parentClassName}.${propertyName}`;
let translatedValue: string = value;
// Parse Flicker objects (no intdef annotation supported)
if (this.FLICKER_INTDEF_MAP.has(propertyPath)) {
translatedValue = this.getIntFlagsAsStrings(value,
<string>this.FLICKER_INTDEF_MAP.get(propertyPath));
if (value || this.displayDefaults) {
// raw values (e.g., false or 0)
if (!value) {
result[key] = value;
// flicker obj
} else if (value.prettyPrint) {
const isEmpty = value.isEmpty === true;
if (!isEmpty || this.displayDefaults) {
result[key] = value.prettyPrint();
}
} else {
// If it's a proto, search on the proto definition for the intdef type
const typeDefSpec = this.getTypeDefSpec(parentObj, propertyName);
if (typeDefSpec) {
translatedValue = this.getIntFlagsAsStrings(value, typeDefSpec);
// converted proto to flicker
const translatedObject = this.translateObject(key, value);
if (translatedObject) {
if (translatedObject.prettyPrint) {
result[key] = translatedObject.prettyPrint();
} else {
result[key] = translatedObject;
}
// objects - recursive call
} else if (value && typeof value == `object`) {
const childObj = this.format(value) as any;
const isEmpty = Object.entries(childObj).length == 0 || childObj.isEmpty;
if (!isEmpty || this.displayDefaults) {
result[key] = childObj;
}
} else {
// values
result[key] = this.translateIntDef(obj, key, value);
}
}
}
});
return translatedValue;
return result;
}
/**
* Translate some predetermined proto objects into their flicker equivalent
*
* Returns null if the object cannot be translated
*
* @param obj Object to translate
*/
private static translateObject(key: string, obj: any) {
const type = obj?.$type?.name ?? obj?.constructor?.name;
switch (type) {
case `SizeProto`:
return toSize(obj);
case `ActiveBufferProto`:
return toActiveBuffer(obj);
case `Color3`:
return toColor3(obj);
case `ColorProto`:
return toColor(obj);
case `Long`:
return obj?.toString();
case `PointProto`:
return toPoint(obj);
case `PositionProto`:
return toPointF(obj);
// It is necessary to check for a keyword insets because the proto
// definition of insets and rects uses the same object type
case `RectProto`:
return key.toLowerCase().includes('insets') ? toInsets(obj) : toRect(obj);
case `Matrix22`:
return toMatrix22(obj);
case `FloatRectProto`:
return toRectF(obj);
case `RegionProto`:
return toRegion(obj);
case `TransformProto`:
return toTransform(obj);
case 'ColorTransformProto': {
const formatted = this.formatColorTransform(obj.val);
return `${formatted}`;
}
}
/**
* Translate a property from its numerical value into its string representation
*
* @param intFlags Property value
* @param annotationType IntDef type to use
*/
private static getIntFlagsAsStrings(intFlags: any, annotationType: string): string {
const flags = [];
const mapping = intDefMapping[<keyof typeof intDefMapping>annotationType].values;
const knownFlagValues = Object.keys(mapping).reverse().map(x => parseInt(x));
if (knownFlagValues.length == 0) {
console.warn("No mapping for type", annotationType)
return intFlags + ""
}
// Will only contain bits that have not been associated with a flag.
const parsedIntFlags = parseInt(intFlags);
let leftOver = parsedIntFlags;
for (const flagValue of knownFlagValues) {
if (((leftOver & flagValue) && ((intFlags & flagValue) === flagValue))
|| (parsedIntFlags === 0 && flagValue === 0)) {
flags.push(mapping[<keyof typeof mapping>flagValue]);
leftOver = leftOver & ~flagValue;
}
}
if (flags.length === 0) {
console.error('No valid flag mappings found for ',
intFlags, 'of type', annotationType);
}
if (leftOver) {
// If 0 is a valid flag value that isn't in the intDefMapping
// it will be ignored
flags.push(leftOver);
}
return flags.join(' | ');
// Raw long number (no type name, no constructor name, no useful toString() method)
if (ArrayUtils.equal(Object.keys(obj).sort(), ['high_', 'low_'])) {
const high = BigInt(obj.high_) << 32n;
let low = BigInt(obj.low_);
if (low < 0) {
low = -low;
}
return (high | low).toString();
}
return null;
}
private static formatColorTransform(vals: any) {
const fixedVals = vals.map((v: any) => v.toFixed(1));
let formatted = ``;
for (let i = 0; i < fixedVals.length; i += 4) {
formatted += `[`;
formatted += fixedVals.slice(i, i + 4).join(', ');
formatted += `] `;
}
return formatted;
}
/**
* Obtains from the proto field, the metadata related to the typedef type (if any)
*
* @param obj Proto object
* @param propertyName Property to search
*/
private static getTypeDefSpec(obj: any, propertyName: string): string | null {
const fields = obj?.$type?.fields;
if (!fields) {
return null;
}
const options = fields[propertyName]?.options;
if (!options) {
return null;
}
return options['(.android.typedef)'];
}
/**
* Translate intdef properties into their string representation
*
* For proto objects check the
*
* @param parentObj Object containing the value to parse
* @param propertyName Property to search
* @param value Property value
*/
private static translateIntDef(parentObj: any, propertyName: string, value: any): string {
const parentClassName = parentObj.constructor.name;
const propertyPath = `${parentClassName}.${propertyName}`;
let translatedValue: string = value;
// Parse Flicker objects (no intdef annotation supported)
if (this.FLICKER_INTDEF_MAP.has(propertyPath)) {
translatedValue = this.getIntFlagsAsStrings(
value,
<string>this.FLICKER_INTDEF_MAP.get(propertyPath)
);
} else {
// If it's a proto, search on the proto definition for the intdef type
const typeDefSpec = this.getTypeDefSpec(parentObj, propertyName);
if (typeDefSpec) {
translatedValue = this.getIntFlagsAsStrings(value, typeDefSpec);
}
}
return translatedValue;
}
/**
* Translate a property from its numerical value into its string representation
*
* @param intFlags Property value
* @param annotationType IntDef type to use
*/
private static getIntFlagsAsStrings(intFlags: any, annotationType: string): string {
const flags = [];
const mapping = intDefMapping[<keyof typeof intDefMapping>annotationType].values;
const knownFlagValues = Object.keys(mapping)
.reverse()
.map((x) => parseInt(x));
if (knownFlagValues.length == 0) {
console.warn('No mapping for type', annotationType);
return intFlags + '';
}
// Will only contain bits that have not been associated with a flag.
const parsedIntFlags = parseInt(intFlags);
let leftOver = parsedIntFlags;
for (const flagValue of knownFlagValues) {
if (
(leftOver & flagValue && (intFlags & flagValue) === flagValue) ||
(parsedIntFlags === 0 && flagValue === 0)
) {
flags.push(mapping[<keyof typeof mapping>flagValue]);
leftOver = leftOver & ~flagValue;
}
}
if (flags.length === 0) {
console.error('No valid flag mappings found for ', intFlags, 'of type', annotationType);
}
if (leftOver) {
// If 0 is a valid flag value that isn't in the intDefMapping
// it will be ignored
flags.push(leftOver);
}
return flags.join(' | ');
}
}

View File

@@ -14,82 +14,75 @@
* limitations under the License.
*/
import { TimeUtils } from 'common/utils/time_utils';
// Imports all the compiled common Flicker library classes and exports them
// as clean es6 modules rather than having them be commonjs modules
// WM
const WindowManagerTrace = require('flicker').com.android.server.wm.traces.
common.windowmanager.WindowManagerTrace;
const WindowManagerState = require('flicker').com.android.server.wm.traces.
common.windowmanager.WindowManagerState;
const WindowManagerTraceEntryBuilder = require('flicker').com.android.server.wm.
traces.common.windowmanager.WindowManagerTraceEntryBuilder;
const Activity = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.Activity;
const Configuration = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.Configuration;
const ConfigurationContainer = require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.ConfigurationContainer;
const DisplayArea = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.DisplayArea;
const DisplayContent = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.DisplayContent;
const DisplayCutout = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.DisplayCutout;
const KeyguardControllerState = require('flicker').com.android.server.wm.
traces.common.windowmanager.windows.KeyguardControllerState;
const RootWindowContainer = require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.RootWindowContainer;
const Task = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.Task;
const TaskFragment = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.TaskFragment;
const WindowConfiguration = require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.WindowConfiguration;
const WindowContainer = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.WindowContainer;
const WindowLayoutParams= require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.WindowLayoutParams;
const WindowManagerPolicy = require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.WindowManagerPolicy;
const WindowState = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.WindowState;
const WindowToken = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.WindowToken;
const WindowManagerTrace =
require('flicker').com.android.server.wm.traces.common.windowmanager.WindowManagerTrace;
const WindowManagerState =
require('flicker').com.android.server.wm.traces.common.windowmanager.WindowManagerState;
const WindowManagerTraceEntryBuilder =
require('flicker').com.android.server.wm.traces.common.windowmanager
.WindowManagerTraceEntryBuilder;
const Activity =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.Activity;
const Configuration =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.Configuration;
const ConfigurationContainer =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows
.ConfigurationContainer;
const DisplayArea =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.DisplayArea;
const DisplayContent =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.DisplayContent;
const DisplayCutout =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.DisplayCutout;
const KeyguardControllerState =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows
.KeyguardControllerState;
const RootWindowContainer =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.RootWindowContainer;
const Task = require('flicker').com.android.server.wm.traces.common.windowmanager.windows.Task;
const TaskFragment =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.TaskFragment;
const WindowConfiguration =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.WindowConfiguration;
const WindowContainer =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.WindowContainer;
const WindowLayoutParams =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.WindowLayoutParams;
const WindowManagerPolicy =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.WindowManagerPolicy;
const WindowState =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.WindowState;
const WindowToken =
require('flicker').com.android.server.wm.traces.common.windowmanager.windows.WindowToken;
// SF
const Layer = require('flicker').com.android.server.wm.traces.common.
layers.Layer;
const LayerProperties = require('flicker').com.android.server.wm.traces.common.
layers.LayerProperties;
const BaseLayerTraceEntry = require('flicker').com.android.server.wm.traces.common.
layers.BaseLayerTraceEntry;
const LayerTraceEntry = require('flicker').com.android.server.wm.traces.common.
layers.LayerTraceEntry;
const LayerTraceEntryBuilder = require('flicker').com.android.server.wm.traces.
common.layers.LayerTraceEntryBuilder;
const LayersTrace = require('flicker').com.android.server.wm.traces.common.
layers.LayersTrace;
const Matrix22 = require('flicker').com.android.server.wm.traces.common
.Matrix22;
const Matrix33 = require('flicker').com.android.server.wm.traces.common
.Matrix33;
const Transform = require('flicker').com.android.server.wm.traces.common.
layers.Transform;
const Display = require('flicker').com.android.server.wm.traces.common.
layers.Display;
const Layer = require('flicker').com.android.server.wm.traces.common.layers.Layer;
const LayerProperties =
require('flicker').com.android.server.wm.traces.common.layers.LayerProperties;
const BaseLayerTraceEntry =
require('flicker').com.android.server.wm.traces.common.layers.BaseLayerTraceEntry;
const LayerTraceEntry =
require('flicker').com.android.server.wm.traces.common.layers.LayerTraceEntry;
const LayerTraceEntryBuilder =
require('flicker').com.android.server.wm.traces.common.layers.LayerTraceEntryBuilder;
const LayersTrace = require('flicker').com.android.server.wm.traces.common.layers.LayersTrace;
const Matrix22 = require('flicker').com.android.server.wm.traces.common.Matrix22;
const Matrix33 = require('flicker').com.android.server.wm.traces.common.Matrix33;
const Transform = require('flicker').com.android.server.wm.traces.common.layers.Transform;
const Display = require('flicker').com.android.server.wm.traces.common.layers.Display;
// Common
const Size = require('flicker').com.android.server.wm.traces.common.Size;
const ActiveBuffer = require('flicker').com.android.server.wm.traces.common
.ActiveBuffer;
const ActiveBuffer = require('flicker').com.android.server.wm.traces.common.ActiveBuffer;
const Color3 = require('flicker').com.android.server.wm.traces.common.Color3;
const Color = require('flicker').com.android.server.wm.traces.common.Color;
const Insets = require('flicker').com.android.server.wm.traces.common.Insets;
const PlatformConsts = require('flicker').com.android.server.wm.traces.common
.service.PlatformConsts;
const PlatformConsts =
require('flicker').com.android.server.wm.traces.common.service.PlatformConsts;
const Point = require('flicker').com.android.server.wm.traces.common.Point;
const PointF = require('flicker').com.android.server.wm.traces.common.PointF;
const Rect = require('flicker').com.android.server.wm.traces.common.Rect;
@@ -99,6 +92,7 @@ const Region = require('flicker').com.android.server.wm.traces.common.region.Reg
// Service
const TaggingEngine = require('flicker').com.android.server.wm.traces.common.service.TaggingEngine;
const EMPTY_SIZE = Size.Companion.EMPTY;
const EMPTY_BUFFER = ActiveBuffer.Companion.EMPTY;
const EMPTY_COLOR3 = Color3.Companion.EMPTY;
const EMPTY_COLOR = Color.Companion.EMPTY;
@@ -113,14 +107,14 @@ const EMPTY_TRANSFORM = new Transform(0, EMPTY_MATRIX33);
function toSize(proto) {
if (proto == null) {
return EMPTY_BOUNDS;
return EMPTY_SIZE;
}
const width = proto.width ?? proto.w ?? 0;
const height = proto.height ?? proto.h ?? 0;
if (width || height) {
return new Size(width, height);
}
return EMPTY_BOUNDS;
return EMPTY_SIZE;
}
function toActiveBuffer(proto) {
@@ -201,7 +195,6 @@ function toInsets(proto) {
return EMPTY_INSETS;
}
function toRect(proto) {
if (proto == null) {
return EMPTY_RECT;

View File

@@ -14,87 +14,96 @@
* limitations under the License.
*/
import { Layer, LayerProperties, Rect, toActiveBuffer, toColor, toRect, toRectF, toRegion } from "../common"
import { shortenName } from '../mixin'
import Transform from './Transform'
import {
Layer,
LayerProperties,
Rect,
toActiveBuffer,
toColor,
toRect,
toRectF,
toRegion,
} from '../common';
import {shortenName} from '../mixin';
import Transform from './Transform';
Layer.fromProto = function (proto: any, excludesCompositionState = false): Layer {
const visibleRegion = toRegion(proto.visibleRegion)
const activeBuffer = toActiveBuffer(proto.activeBuffer)
const bounds = toRectF(proto.bounds)
const color = toColor(proto.color)
const screenBounds = toRectF(proto.screenBounds)
const sourceBounds = toRectF(proto.sourceBounds)
const transform = Transform.fromProto(proto.transform, proto.position)
const bufferTransform = Transform.fromProto(proto.bufferTransform, /* position */ null)
const hwcCrop = toRectF(proto.hwcCrop)
const hwcFrame = toRect(proto.hwcFrame)
const requestedColor = toColor(proto.requestedColor)
const requestedTransform =
Transform.fromProto(proto.requestedTransform, proto.requestedPosition)
const cornerRadiusCrop = toRectF(proto.cornerRadiusCrop)
const inputTransform =
Transform.fromProto(proto.inputWindowInfo ? proto.inputWindowInfo.transform : null)
const inputRegion =
toRegion(proto.inputWindowInfo ? proto.inputWindowInfo.touchableRegion : null)
let crop: Rect
if (proto.crop) {
crop = toRect(proto.crop)
};
const visibleRegion = toRegion(proto.visibleRegion);
const activeBuffer = toActiveBuffer(proto.activeBuffer);
const bounds = toRectF(proto.bounds);
const color = toColor(proto.color);
const screenBounds = toRectF(proto.screenBounds);
const sourceBounds = toRectF(proto.sourceBounds);
const transform = Transform.fromProto(proto.transform, proto.position);
const bufferTransform = Transform.fromProto(proto.bufferTransform, /* position */ null);
const hwcCrop = toRectF(proto.hwcCrop);
const hwcFrame = toRect(proto.hwcFrame);
const requestedColor = toColor(proto.requestedColor);
const requestedTransform = Transform.fromProto(proto.requestedTransform, proto.requestedPosition);
const cornerRadiusCrop = toRectF(proto.cornerRadiusCrop);
const inputTransform = Transform.fromProto(
proto.inputWindowInfo ? proto.inputWindowInfo.transform : null
);
const inputRegion = toRegion(
proto.inputWindowInfo ? proto.inputWindowInfo.touchableRegion : null
);
let crop: Rect;
if (proto.crop) {
crop = toRect(proto.crop);
}
const properties = new LayerProperties(
visibleRegion,
activeBuffer,
/* flags */ proto.flags,
bounds,
color,
/* isOpaque */ proto.isOpaque,
/* shadowRadius */ proto.shadowRadius,
/* cornerRadius */ proto.cornerRadius,
/* type */ proto.type ?? ``,
screenBounds,
transform,
sourceBounds,
/* effectiveScalingMode */ proto.effectiveScalingMode,
bufferTransform,
/* hwcCompositionType */ proto.hwcCompositionType,
hwcCrop,
hwcFrame,
/* backgroundBlurRadius */ proto.backgroundBlurRadius,
crop,
/* isRelativeOf */ proto.isRelativeOf,
/* zOrderRelativeOfId */ proto.zOrderRelativeOf,
/* stackId */ proto.layerStack,
requestedTransform,
requestedColor,
cornerRadiusCrop,
inputTransform,
inputRegion,
excludesCompositionState
);
const properties = new LayerProperties(
visibleRegion,
activeBuffer,
/* flags */ proto.flags,
bounds,
color,
/* isOpaque */ proto.isOpaque,
/* shadowRadius */ proto.shadowRadius,
/* cornerRadius */ proto.cornerRadius,
/* type */ proto.type ?? ``,
screenBounds,
transform,
sourceBounds,
/* effectiveScalingMode */ proto.effectiveScalingMode,
bufferTransform,
/* hwcCompositionType */ proto.hwcCompositionType,
hwcCrop,
hwcFrame,
/* backgroundBlurRadius */ proto.backgroundBlurRadius,
crop,
/* isRelativeOf */ proto.isRelativeOf,
/* zOrderRelativeOfId */ proto.zOrderRelativeOf,
/* stackId */ proto.layerStack,
requestedTransform,
requestedColor,
cornerRadiusCrop,
inputTransform,
inputRegion,
excludesCompositionState
);
const entry = new Layer(
/* name */ proto.name ?? ``,
/* id */ proto.id,
/*parentId */ proto.parent,
/* z */ proto.z,
/* currFrame */ proto.currFrame,
properties
);
const entry = new Layer(
/* name */ proto.name ?? ``,
/* id */ proto.id,
/*parentId */ proto.parent,
/* z */ proto.z,
/* currFrame */ proto.currFrame,
properties
);
addAttributes(entry, proto);
return entry
}
addAttributes(entry, proto);
return entry;
};
function addAttributes(entry: Layer, proto: any) {
entry.kind = `${entry.id}`;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.rect = entry.bounds;
entry.rect.transform = entry.transform;
entry.rect.ref = entry;
entry.rect.label = entry.name;
entry.kind = `${entry.id}`;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.rect = entry.bounds;
entry.rect.transform = entry.transform;
entry.rect.ref = entry;
entry.rect.label = entry.name;
}
export {Layer};

View File

@@ -14,73 +14,79 @@
* limitations under the License.
*/
import { Display, LayerTraceEntry, LayerTraceEntryBuilder, toRect, toSize, toTransform } from "../common";
import {Layer} from "./Layer";
import {getPropertiesForDisplay} from "../mixin";
import { TimeUtils } from "common/utils/time_utils";
import { ElapsedTimestamp, RealTimestamp } from "common/trace/timestamp";
import {ElapsedTimestamp, RealTimestamp} from 'common/trace/timestamp';
import {TimeUtils} from 'common/utils/time_utils';
import {
Display,
LayerTraceEntry,
LayerTraceEntryBuilder,
toRect,
toSize,
toTransform,
} from '../common';
import {getPropertiesForDisplay} from '../mixin';
import {Layer} from './Layer';
LayerTraceEntry.fromProto = function (
protos: object[],
displayProtos: object[],
elapsedTimestamp: bigint,
vSyncId: number,
hwcBlob: string,
where = "",
realToElapsedTimeOffsetNs: bigint|undefined = undefined,
useElapsedTime = false,
excludesCompositionState = false
protos: object[],
displayProtos: object[],
elapsedTimestamp: bigint,
vSyncId: number,
hwcBlob: string,
where = '',
realToElapsedTimeOffsetNs: bigint | undefined = undefined,
useElapsedTime = false,
excludesCompositionState = false
): LayerTraceEntry {
const layers = protos.map(it => Layer.fromProto(it, excludesCompositionState));
const displays = (displayProtos || []).map(it => newDisplay(it));
const builder = new LayerTraceEntryBuilder(
`${elapsedTimestamp}`,
layers,
displays,
vSyncId,
hwcBlob,
where,
`${realToElapsedTimeOffsetNs ?? 0}`
);
const entry: LayerTraceEntry = builder.build();
const layers = protos.map((it) => Layer.fromProto(it, excludesCompositionState));
const displays = (displayProtos || []).map((it) => newDisplay(it));
const builder = new LayerTraceEntryBuilder(
`${elapsedTimestamp}`,
layers,
displays,
vSyncId,
hwcBlob,
where,
`${realToElapsedTimeOffsetNs ?? 0}`
);
const entry: LayerTraceEntry = builder.build();
addAttributes(entry, protos,
realToElapsedTimeOffsetNs === undefined || useElapsedTime);
return entry;
}
addAttributes(entry, protos, realToElapsedTimeOffsetNs === undefined || useElapsedTime);
return entry;
};
function addAttributes(entry: LayerTraceEntry, protos: object[], useElapsedTime = false) {
entry.kind = "entry";
// Avoid parsing the entry root because it is an array of layers
// containing all trace information, this slows down the property tree.
// Instead parse only key properties for debugging
const newObj = getPropertiesForDisplay(entry);
if (newObj.rects) delete newObj.rects;
if (newObj.flattenedLayers) delete newObj.flattenedLayers;
if (newObj.physicalDisplays) delete newObj.physicalDisplays;
if (newObj.physicalDisplayBounds) delete newObj.physicalDisplayBounds;
if (newObj.isVisible) delete newObj.isVisible;
entry.proto = newObj;
if (useElapsedTime || entry.clockTimestamp == undefined) {
entry.name = TimeUtils.format(new ElapsedTimestamp(BigInt(entry.elapsedTimestamp)));
entry.shortName = entry.name;
} else {
entry.name = TimeUtils.format(new RealTimestamp(entry.clockTimestamp));
entry.shortName = entry.name;
}
entry.isVisible = true;
entry.kind = 'entry';
// Avoid parsing the entry root because it is an array of layers
// containing all trace information, this slows down the property tree.
// Instead parse only key properties for debugging
const newObj = getPropertiesForDisplay(entry);
if (newObj.rects) delete newObj.rects;
if (newObj.flattenedLayers) delete newObj.flattenedLayers;
if (newObj.physicalDisplays) delete newObj.physicalDisplays;
if (newObj.physicalDisplayBounds) delete newObj.physicalDisplayBounds;
if (newObj.isVisible) delete newObj.isVisible;
entry.proto = newObj;
if (useElapsedTime || entry.clockTimestamp == undefined) {
entry.name = TimeUtils.format(new ElapsedTimestamp(BigInt(entry.elapsedTimestamp)));
entry.shortName = entry.name;
} else {
entry.name = TimeUtils.format(new RealTimestamp(entry.clockTimestamp));
entry.shortName = entry.name;
}
entry.isVisible = true;
}
function newDisplay(proto: any): Display {
return new Display(
proto.id,
proto.name,
proto.layerStack,
toSize(proto.size),
toRect(proto.layerStackSpaceRect),
toTransform(proto.transform),
proto.isVirtual
)
return new Display(
proto.id,
proto.name,
proto.layerStack,
toSize(proto.size),
toRect(proto.layerStackSpaceRect),
toTransform(proto.transform),
proto.isVirtual
);
}
export {LayerTraceEntry};

View File

@@ -14,77 +14,75 @@
* limitations under the License.
*/
import { Transform, Matrix33 } from "../common"
import {Matrix33, Transform} from '../common';
Transform.fromProto = function (transformProto: any, positionProto: any): Transform {
const entry = new Transform(
transformProto?.type ?? 0,
getMatrix(transformProto, positionProto))
const entry = new Transform(transformProto?.type ?? 0, getMatrix(transformProto, positionProto));
return entry
}
return entry;
};
function getMatrix(transform: any, position: any): Matrix33 {
const x = position?.x ?? 0
const y = position?.y ?? 0
const x = position?.x ?? 0;
const y = position?.y ?? 0;
if (transform == null || isSimpleTransform(transform.type)) {
return getDefaultTransform(transform?.type, x, y)
}
if (transform == null || isSimpleTransform(transform.type)) {
return getDefaultTransform(transform?.type, x, y);
}
return new Matrix33(transform.dsdx, transform.dtdx, x, transform.dsdy, transform.dtdy, y)
return new Matrix33(transform.dsdx, transform.dtdx, x, transform.dsdy, transform.dtdy, y);
}
function getDefaultTransform(type: number, x: number, y: number): Matrix33 {
// IDENTITY
if (!type) {
return new Matrix33(1, 0, x, 0, 1, y)
}
// IDENTITY
if (!type) {
return new Matrix33(1, 0, x, 0, 1, y);
}
// ROT_270 = ROT_90|FLIP_H|FLIP_V
if (isFlagSet(type, ROT_90_VAL | FLIP_V_VAL | FLIP_H_VAL)) {
return new Matrix33(0, -1, x, 1, 0, y)
}
// ROT_270 = ROT_90|FLIP_H|FLIP_V
if (isFlagSet(type, ROT_90_VAL | FLIP_V_VAL | FLIP_H_VAL)) {
return new Matrix33(0, -1, x, 1, 0, y);
}
// ROT_180 = FLIP_H|FLIP_V
if (isFlagSet(type, FLIP_V_VAL | FLIP_H_VAL)) {
return new Matrix33(-1, 0, x, 0, -1, y)
}
// ROT_180 = FLIP_H|FLIP_V
if (isFlagSet(type, FLIP_V_VAL | FLIP_H_VAL)) {
return new Matrix33(-1, 0, x, 0, -1, y);
}
// ROT_90
if (isFlagSet(type, ROT_90_VAL)) {
return new Matrix33(0, 1, x, -1, 0, y)
}
// ROT_90
if (isFlagSet(type, ROT_90_VAL)) {
return new Matrix33(0, 1, x, -1, 0, y);
}
// IDENTITY
if (isFlagClear(type, SCALE_VAL | ROTATE_VAL)) {
return new Matrix33(1, 0, x, 0, 1, y)
}
// IDENTITY
if (isFlagClear(type, SCALE_VAL | ROTATE_VAL)) {
return new Matrix33(1, 0, x, 0, 1, y);
}
throw new Error(`Unknown transform type ${type}`)
throw new Error(`Unknown transform type ${type}`);
}
export function isFlagSet(type: number, bits: number): Boolean {
var type = type || 0;
return (type & bits) === bits;
type = type || 0;
return (type & bits) === bits;
}
export function isFlagClear(type: number, bits: number): Boolean {
return (type & bits) === 0;
return (type & bits) === 0;
}
export function isSimpleTransform(type: number): Boolean {
return isFlagClear(type, ROT_INVALID_VAL | SCALE_VAL)
return isFlagClear(type, ROT_INVALID_VAL | SCALE_VAL);
}
/* transform type flags */
const ROTATE_VAL = 0x0002
const SCALE_VAL = 0x0004
const ROTATE_VAL = 0x0002;
const SCALE_VAL = 0x0004;
/* orientation flags */
const FLIP_H_VAL = 0x0100 // (1 << 0 << 8)
const FLIP_V_VAL = 0x0200 // (1 << 1 << 8)
const ROT_90_VAL = 0x0400 // (1 << 2 << 8)
const ROT_INVALID_VAL = 0x8000 // (0x80 << 8)
const FLIP_H_VAL = 0x0100; // (1 << 0 << 8)
const FLIP_V_VAL = 0x0200; // (1 << 1 << 8)
const ROT_90_VAL = 0x0400; // (1 << 2 << 8)
const ROT_INVALID_VAL = 0x8000; // (0x80 << 8)
export default Transform
export default Transform;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import ObjectFormatter from "./ObjectFormatter"
import ObjectFormatter from './ObjectFormatter';
/**
* Get the properties of a WM object for display.
@@ -23,39 +23,39 @@ import ObjectFormatter from "./ObjectFormatter"
* @param proto Associated proto object
*/
export function getPropertiesForDisplay(entry: any): any {
if (!entry) {
return
}
if (!entry) {
return;
}
let obj: any = {}
const properties = ObjectFormatter.getProperties(entry)
properties.forEach(prop => obj[prop] = entry[prop]);
let obj: any = {};
const properties = ObjectFormatter.getProperties(entry);
properties.forEach((prop) => (obj[prop] = entry[prop]));
// we remove the children property from the object to avoid it showing the
// the properties view of the element as we can always see those elements'
// properties by changing the target element in the hierarchy tree view.
if (obj.children) delete obj.children
if (obj.proto) delete obj.proto
// we remove the children property from the object to avoid it showing the
// the properties view of the element as we can always see those elements'
// properties by changing the target element in the hierarchy tree view.
if (obj.children) delete obj.children;
if (obj.proto) delete obj.proto;
obj.proto = Object.assign({}, entry.proto)
if (obj.proto.children) delete obj.proto.children
if (obj.proto.childWindows) delete obj.proto.childWindows
if (obj.proto.childrenWindows) delete obj.proto.childrenWindows
if (obj.proto.childContainers) delete obj.proto.childContainers
if (obj.proto.windowToken) delete obj.proto.windowToken
if (obj.proto.rootDisplayArea) delete obj.proto.rootDisplayArea
if (obj.proto.rootWindowContainer) delete obj.proto.rootWindowContainer
if (obj.proto.windowContainer?.children) delete obj.proto.windowContainer.children
obj = ObjectFormatter.format(obj)
obj.proto = Object.assign({}, entry.proto);
if (obj.proto.children) delete obj.proto.children;
if (obj.proto.childWindows) delete obj.proto.childWindows;
if (obj.proto.childrenWindows) delete obj.proto.childrenWindows;
if (obj.proto.childContainers) delete obj.proto.childContainers;
if (obj.proto.windowToken) delete obj.proto.windowToken;
if (obj.proto.rootDisplayArea) delete obj.proto.rootDisplayArea;
if (obj.proto.rootWindowContainer) delete obj.proto.rootWindowContainer;
if (obj.proto.windowContainer?.children) delete obj.proto.windowContainer.children;
obj = ObjectFormatter.format(obj);
return obj
return obj;
}
export function shortenName(name: any): string {
const classParts = (name + "").split(".")
if (classParts.length <= 3) {
return name
}
const className = classParts.slice(-1)[0] // last element
return `${classParts[0]}.${classParts[1]}.(...).${className}`
const classParts = (name + '').split('.');
if (classParts.length <= 3) {
return name;
}
const className = classParts.slice(-1)[0]; // last element
return `${classParts[0]}.${classParts[1]}.(...).${className}`;
}

View File

@@ -14,42 +14,42 @@
* limitations under the License.
*/
import { shortenName } from '../mixin'
import { Activity } from "../common"
import WindowContainer from "./WindowContainer"
import {Activity} from '../common';
import {shortenName} from '../mixin';
import WindowContainer from './WindowContainer';
Activity.fromProto = function (proto: any, nextSeq: () => number): Activity {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowToken.windowContainer,
/* protoChildren */ proto.windowToken.windowContainer?.children ?? [],
/* isActivityInTree */ true,
/* computedZ */ nextSeq,
/* nameOverride */ null,
/* identifierOverride */ proto.identifier,
);
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowToken.windowContainer,
/* protoChildren */ proto.windowToken.windowContainer?.children ?? [],
/* isActivityInTree */ true,
/* computedZ */ nextSeq,
/* nameOverride */ null,
/* identifierOverride */ proto.identifier
);
const entry = new Activity(
proto.name,
proto.state,
proto.visible,
proto.frontOfTask,
proto.procId,
proto.translucent,
windowContainer
);
const entry = new Activity(
proto.name,
proto.state,
proto.visible,
proto.frontOfTask,
proto.procId,
proto.translucent,
windowContainer
);
addAttributes(entry, proto);
return entry;
}
}
addAttributes(entry, proto);
return entry;
}
};
function addAttributes(entry: Activity, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default Activity;

View File

@@ -14,33 +14,37 @@
* limitations under the License.
*/
import { shortenName } from '../mixin'
import { DisplayArea } from "../common"
import WindowContainer from "./WindowContainer"
import {DisplayArea} from '../common';
import {shortenName} from '../mixin';
import WindowContainer from './WindowContainer';
DisplayArea.fromProto = function (proto: any, isActivityInTree: Boolean, nextSeq: () => number): DisplayArea {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
/* nameOverride */ proto.name,
);
DisplayArea.fromProto = function (
proto: any,
isActivityInTree: Boolean,
nextSeq: () => number
): DisplayArea {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
/* nameOverride */ proto.name
);
const entry = new DisplayArea(proto.isTaskDisplayArea, windowContainer);
const entry = new DisplayArea(proto.isTaskDisplayArea, windowContainer);
addAttributes(entry, proto);
return entry;
}
}
addAttributes(entry, proto);
return entry;
}
};
function addAttributes(entry: DisplayArea, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default DisplayArea;

View File

@@ -14,74 +14,78 @@
* limitations under the License.
*/
import { shortenName } from '../mixin'
import { toInsets, toRect, DisplayContent, DisplayCutout, PlatformConsts, Rect } from "../common"
import WindowContainer from "./WindowContainer"
import {DisplayContent, DisplayCutout, PlatformConsts, Rect, toInsets, toRect} from '../common';
import {shortenName} from '../mixin';
import WindowContainer from './WindowContainer';
DisplayContent.fromProto = function (proto: any, isActivityInTree: Boolean, nextSeq: () => number): DisplayContent {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.rootDisplayArea.windowContainer,
/* protoChildren */ proto.rootDisplayArea.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
/* nameOverride */ proto.displayInfo?.name ?? null,
);
const displayRectWidth = proto.displayInfo?.logicalWidth ?? 0;
const displayRectHeight = proto.displayInfo?.logicalHeight ?? 0;
const appRectWidth = proto.displayInfo?.appWidth ?? 0;
const appRectHeight = proto.displayInfo?.appHeight ?? 0;
const defaultBounds = proto.pinnedStackController?.defaultBounds ?? null;
const movementBounds = proto.pinnedStackController?.movementBounds ?? null;
DisplayContent.fromProto = function (
proto: any,
isActivityInTree: Boolean,
nextSeq: () => number
): DisplayContent {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.rootDisplayArea.windowContainer,
/* protoChildren */ proto.rootDisplayArea.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
/* nameOverride */ proto.displayInfo?.name ?? null
);
const displayRectWidth = proto.displayInfo?.logicalWidth ?? 0;
const displayRectHeight = proto.displayInfo?.logicalHeight ?? 0;
const appRectWidth = proto.displayInfo?.appWidth ?? 0;
const appRectHeight = proto.displayInfo?.appHeight ?? 0;
const defaultBounds = proto.pinnedStackController?.defaultBounds ?? null;
const movementBounds = proto.pinnedStackController?.movementBounds ?? null;
const entry = new DisplayContent(
proto.id,
proto.focusedRootTaskId,
proto.resumedActivity?.title ?? "",
proto.singleTaskInstance,
toRect(defaultBounds),
toRect(movementBounds),
new Rect(0, 0, displayRectWidth, displayRectHeight),
new Rect(0, 0, appRectWidth, appRectHeight),
proto.dpi,
proto.displayInfo?.flags ?? 0,
toRect(proto.displayFrames?.stableBounds),
proto.surfaceSize,
proto.focusedApp,
proto.appTransition?.lastUsedAppTransition ?? "",
proto.appTransition?.appTransitionState ?? "",
PlatformConsts.Rotation.Companion.getByValue(proto.displayRotation?.rotation ?? 0),
proto.displayRotation?.lastOrientation ?? 0,
createDisplayCutout(proto.displayInfo?.cutout),
windowContainer
);
const entry = new DisplayContent(
proto.id,
proto.focusedRootTaskId,
proto.resumedActivity?.title ?? '',
proto.singleTaskInstance,
toRect(defaultBounds),
toRect(movementBounds),
new Rect(0, 0, displayRectWidth, displayRectHeight),
new Rect(0, 0, appRectWidth, appRectHeight),
proto.dpi,
proto.displayInfo?.flags ?? 0,
toRect(proto.displayFrames?.stableBounds),
proto.surfaceSize,
proto.focusedApp,
proto.appTransition?.lastUsedAppTransition ?? '',
proto.appTransition?.appTransitionState ?? '',
PlatformConsts.Rotation.Companion.getByValue(proto.displayRotation?.rotation ?? 0),
proto.displayRotation?.lastOrientation ?? 0,
createDisplayCutout(proto.displayInfo?.cutout),
windowContainer
);
addAttributes(entry, proto);
return entry;
}
}
addAttributes(entry, proto);
return entry;
}
};
function createDisplayCutout(proto: any | null): DisplayCutout | null {
if(proto == null) {
return null;
} else {
return new DisplayCutout(
toInsets(proto?.insets),
toRect(proto?.boundLeft),
toRect(proto?.boundTop),
toRect(proto?.boundRight),
toRect(proto?.boundBottom),
toInsets(proto?.waterfallInsets)
);
}
if (proto == null) {
return null;
} else {
return new DisplayCutout(
toInsets(proto?.insets),
toRect(proto?.boundLeft),
toRect(proto?.boundTop),
toRect(proto?.boundRight),
toRect(proto?.boundBottom),
toInsets(proto?.waterfallInsets)
);
}
}
function addAttributes(entry: DisplayContent, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default DisplayContent;

View File

@@ -14,52 +14,52 @@
* limitations under the License.
*/
import { shortenName } from '../mixin'
import { Task, toRect } from "../common"
import WindowContainer from "./WindowContainer"
import {Task, toRect} from '../common';
import {shortenName} from '../mixin';
import WindowContainer from './WindowContainer';
Task.fromProto = function (proto: any, isActivityInTree: Boolean, nextSeq: () => number): Task {
if (proto == null) {
return null;
} else {
const windowContainerProto = proto.taskFragment?.windowContainer ?? proto.windowContainer;
const windowContainer = WindowContainer.fromProto(
/* proto */ windowContainerProto,
/* protoChildren */ windowContainerProto?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
);
if (proto == null) {
return null;
} else {
const windowContainerProto = proto.taskFragment?.windowContainer ?? proto.windowContainer;
const windowContainer = WindowContainer.fromProto(
/* proto */ windowContainerProto,
/* protoChildren */ windowContainerProto?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq
);
const entry = new Task(
proto.taskFragment?.activityType ?? proto.activityType,
proto.fillsParent,
toRect(proto.bounds),
proto.id,
proto.rootTaskId,
proto.taskFragment?.displayId,
toRect(proto.lastNonFullscreenBounds),
proto.realActivity,
proto.origActivity,
proto.resizeMode,
proto.resumedActivity?.title ?? "",
proto.animatingBounds,
proto.surfaceWidth,
proto.surfaceHeight,
proto.createdByOrganizer,
proto.taskFragment?.minWidth ?? proto.minWidth,
proto.taskFragment?.minHeight ?? proto.minHeight,
windowContainer
);
const entry = new Task(
proto.taskFragment?.activityType ?? proto.activityType,
proto.fillsParent,
toRect(proto.bounds),
proto.id,
proto.rootTaskId,
proto.taskFragment?.displayId,
toRect(proto.lastNonFullscreenBounds),
proto.realActivity,
proto.origActivity,
proto.resizeMode,
proto.resumedActivity?.title ?? '',
proto.animatingBounds,
proto.surfaceWidth,
proto.surfaceHeight,
proto.createdByOrganizer,
proto.taskFragment?.minWidth ?? proto.minWidth,
proto.taskFragment?.minHeight ?? proto.minHeight,
windowContainer
);
addAttributes(entry, proto);
return entry;
}
}
addAttributes(entry, proto);
return entry;
}
};
function addAttributes(entry: Task, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default Task;

View File

@@ -14,37 +14,41 @@
* limitations under the License.
*/
import { shortenName } from '../mixin'
import { TaskFragment } from "../common"
import WindowContainer from "./WindowContainer"
import {TaskFragment} from '../common';
import {shortenName} from '../mixin';
import WindowContainer from './WindowContainer';
TaskFragment.fromProto = function (proto: any, isActivityInTree: Boolean, nextSeq: () => number): TaskFragment {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
);
const entry = new TaskFragment(
proto.activityType,
proto.displayId,
proto.minWidth,
proto.minHeight,
windowContainer,
);
TaskFragment.fromProto = function (
proto: any,
isActivityInTree: Boolean,
nextSeq: () => number
): TaskFragment {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq
);
const entry = new TaskFragment(
proto.activityType,
proto.displayId,
proto.minWidth,
proto.minHeight,
windowContainer
);
addAttributes(entry, proto);
return entry;
}
}
addAttributes(entry, proto);
return entry;
}
};
function addAttributes(entry: TaskFragment, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default TaskFragment;

View File

@@ -14,123 +14,137 @@
* limitations under the License.
*/
import { shortenName } from '../mixin'
import {shortenName} from '../mixin';
import {
Configuration,
ConfigurationContainer,
toRect,
WindowConfiguration,
WindowContainer
} from "../common"
Configuration,
ConfigurationContainer,
toRect,
WindowConfiguration,
WindowContainer,
} from '../common';
import Activity from "./Activity"
import DisplayArea from "./DisplayArea"
import DisplayContent from "./DisplayContent"
import Task from "./Task"
import TaskFragment from "./TaskFragment"
import WindowState from "./WindowState"
import WindowToken from "./WindowToken"
import Activity from './Activity';
import DisplayArea from './DisplayArea';
import DisplayContent from './DisplayContent';
import Task from './Task';
import TaskFragment from './TaskFragment';
import WindowState from './WindowState';
import WindowToken from './WindowToken';
WindowContainer.fromProto = function (
proto: any,
protoChildren: any[],
isActivityInTree: boolean,
nextSeq: () => number,
nameOverride: string|null = null,
identifierOverride: string|null = null,
tokenOverride: any = null,
proto: any,
protoChildren: any[],
isActivityInTree: boolean,
nextSeq: () => number,
nameOverride: string | null = null,
identifierOverride: string | null = null,
tokenOverride: any = null
): WindowContainer {
if (proto == null) {
return null;
}
if (proto == null) {
return null;
}
const containerOrder = nextSeq();
const children = protoChildren
.filter(it => it != null)
.map(it => WindowContainer.childrenFromProto(it, isActivityInTree, nextSeq))
.filter(it => it != null);
const containerOrder = nextSeq();
const children = protoChildren
.filter((it) => it != null)
.map((it) => WindowContainer.childrenFromProto(it, isActivityInTree, nextSeq))
.filter((it) => it != null);
const identifier: any = identifierOverride ?? proto.identifier;
const name: string = nameOverride ?? identifier?.title ?? "";
const token: string = tokenOverride?.toString(16) ?? identifier?.hashCode?.toString(16) ?? "";
const identifier: any = identifierOverride ?? proto.identifier;
const name: string = nameOverride ?? identifier?.title ?? '';
const token: string = tokenOverride?.toString(16) ?? identifier?.hashCode?.toString(16) ?? '';
const config = createConfigurationContainer(proto.configurationContainer);
const entry = new WindowContainer(
name,
token,
proto.orientation,
proto.surfaceControl?.layerId ?? 0,
proto.visible,
config,
children,
containerOrder
);
const config = createConfigurationContainer(proto.configurationContainer);
const entry = new WindowContainer(
name,
token,
proto.orientation,
proto.surfaceControl?.layerId ?? 0,
proto.visible,
config,
children,
containerOrder
);
addAttributes(entry, proto);
return entry;
}
addAttributes(entry, proto);
return entry;
};
function addAttributes(entry: WindowContainer, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
type WindowContainerChildType = DisplayContent|DisplayArea|Task|TaskFragment|Activity|WindowToken|WindowState|WindowContainer;
type WindowContainerChildType =
| DisplayContent
| DisplayArea
| Task
| TaskFragment
| Activity
| WindowToken
| WindowState
| WindowContainer;
WindowContainer.childrenFromProto = function(proto: any, isActivityInTree: Boolean, nextSeq: () => number): WindowContainerChildType {
return DisplayContent.fromProto(proto.displayContent, isActivityInTree, nextSeq) ??
DisplayArea.fromProto(proto.displayArea, isActivityInTree, nextSeq) ??
Task.fromProto(proto.task, isActivityInTree, nextSeq) ??
TaskFragment.fromProto(proto.taskFragment, isActivityInTree, nextSeq) ??
Activity.fromProto(proto.activity, nextSeq) ??
WindowToken.fromProto(proto.windowToken, isActivityInTree, nextSeq) ??
WindowState.fromProto(proto.window, isActivityInTree, nextSeq) ??
WindowContainer.fromProto(proto.windowContainer, nextSeq);
}
WindowContainer.childrenFromProto = function (
proto: any,
isActivityInTree: Boolean,
nextSeq: () => number
): WindowContainerChildType {
return (
DisplayContent.fromProto(proto.displayContent, isActivityInTree, nextSeq) ??
DisplayArea.fromProto(proto.displayArea, isActivityInTree, nextSeq) ??
Task.fromProto(proto.task, isActivityInTree, nextSeq) ??
TaskFragment.fromProto(proto.taskFragment, isActivityInTree, nextSeq) ??
Activity.fromProto(proto.activity, nextSeq) ??
WindowToken.fromProto(proto.windowToken, isActivityInTree, nextSeq) ??
WindowState.fromProto(proto.window, isActivityInTree, nextSeq) ??
WindowContainer.fromProto(proto.windowContainer, nextSeq)
);
};
function createConfigurationContainer(proto: any): ConfigurationContainer {
const entry = new ConfigurationContainer(
createConfiguration(proto?.overrideConfiguration ?? null),
createConfiguration(proto?.fullConfiguration ?? null),
createConfiguration(proto?.mergedOverrideConfiguration ?? null)
);
const entry = new ConfigurationContainer(
createConfiguration(proto?.overrideConfiguration ?? null),
createConfiguration(proto?.fullConfiguration ?? null),
createConfiguration(proto?.mergedOverrideConfiguration ?? null)
);
entry.obj = entry;
return entry;
entry.obj = entry;
return entry;
}
function createConfiguration(proto: any): Configuration {
if (proto == null) {
return null;
}
var windowConfiguration = null;
if (proto == null) {
return null;
}
let windowConfiguration = null;
if (proto != null && proto.windowConfiguration != null) {
windowConfiguration = createWindowConfiguration(proto.windowConfiguration);
}
if (proto != null && proto.windowConfiguration != null) {
windowConfiguration = createWindowConfiguration(proto.windowConfiguration);
}
return new Configuration(
windowConfiguration,
proto?.densityDpi ?? 0,
proto?.orientation ?? 0,
proto?.screenHeightDp ?? 0,
proto?.screenHeightDp ?? 0,
proto?.smallestScreenWidthDp ?? 0,
proto?.screenLayout ?? 0,
proto?.uiMode ?? 0
);
return new Configuration(
windowConfiguration,
proto?.densityDpi ?? 0,
proto?.orientation ?? 0,
proto?.screenHeightDp ?? 0,
proto?.screenHeightDp ?? 0,
proto?.smallestScreenWidthDp ?? 0,
proto?.screenLayout ?? 0,
proto?.uiMode ?? 0
);
}
function createWindowConfiguration(proto: any): WindowConfiguration {
return new WindowConfiguration(
toRect(proto.appBounds),
toRect(proto.bounds),
toRect(proto.maxBounds),
proto.windowingMode,
proto.activityType
);
return new WindowConfiguration(
toRect(proto.appBounds),
toRect(proto.bounds),
toRect(proto.maxBounds),
proto.windowingMode,
proto.activityType
);
}
export default WindowContainer;

View File

@@ -14,120 +14,124 @@
* limitations under the License.
*/
import { ElapsedTimestamp, RealTimestamp } from "common/trace/timestamp";
import { TimeUtils } from "common/utils/time_utils";
import {ElapsedTimestamp, RealTimestamp} from 'common/trace/timestamp';
import {TimeUtils} from 'common/utils/time_utils';
import {
KeyguardControllerState,
PlatformConsts,
RootWindowContainer,
WindowManagerPolicy,
WindowManagerState,
WindowManagerTraceEntryBuilder
} from "../common"
KeyguardControllerState,
PlatformConsts,
RootWindowContainer,
WindowManagerPolicy,
WindowManagerState,
WindowManagerTraceEntryBuilder,
} from '../common';
import WindowContainer from "./WindowContainer"
import WindowContainer from './WindowContainer';
WindowManagerState.fromProto = function (
proto: any,
elapsedTimestamp: bigint = 0n,
where: string = "",
realToElapsedTimeOffsetNs: bigint|undefined = undefined,
useElapsedTime = false,
proto: any,
elapsedTimestamp: bigint = 0n,
where: string = '',
realToElapsedTimeOffsetNs: bigint | undefined = undefined,
useElapsedTime = false
): WindowManagerState {
var inputMethodWIndowAppToken = "";
if (proto.inputMethodWindow != null) {
proto.inputMethodWindow.hashCode.toString(16)
};
const inputMethodWIndowAppToken = '';
if (proto.inputMethodWindow != null) {
proto.inputMethodWindow.hashCode.toString(16);
}
let parseOrder = 0;
const nextSeq = () => ++parseOrder;
const rootWindowContainer = createRootWindowContainer(proto.rootWindowContainer, nextSeq);
const keyguardControllerState = createKeyguardControllerState(
proto.rootWindowContainer.keyguardController);
const policy = createWindowManagerPolicy(proto.policy);
let parseOrder = 0;
const nextSeq = () => ++parseOrder;
const rootWindowContainer = createRootWindowContainer(proto.rootWindowContainer, nextSeq);
const keyguardControllerState = createKeyguardControllerState(
proto.rootWindowContainer.keyguardController
);
const policy = createWindowManagerPolicy(proto.policy);
const entry = new WindowManagerTraceEntryBuilder(
`${elapsedTimestamp}`,
policy,
proto.focusedApp,
proto.focusedDisplayId,
proto.focusedWindow?.title ?? "",
inputMethodWIndowAppToken,
proto.rootWindowContainer.isHomeRecentsComponent,
proto.displayFrozen,
proto.rootWindowContainer.pendingActivities.map((it: any) => it.title),
rootWindowContainer,
keyguardControllerState,
where,
`${realToElapsedTimeOffsetNs ?? 0}`,
).build();
const entry = new WindowManagerTraceEntryBuilder(
`${elapsedTimestamp}`,
policy,
proto.focusedApp,
proto.focusedDisplayId,
proto.focusedWindow?.title ?? '',
inputMethodWIndowAppToken,
proto.rootWindowContainer.isHomeRecentsComponent,
proto.displayFrozen,
proto.rootWindowContainer.pendingActivities.map((it: any) => it.title),
rootWindowContainer,
keyguardControllerState,
where,
`${realToElapsedTimeOffsetNs ?? 0}`
).build();
addAttributes(entry, proto, realToElapsedTimeOffsetNs === undefined || useElapsedTime);
return entry
}
addAttributes(entry, proto, realToElapsedTimeOffsetNs === undefined || useElapsedTime);
return entry;
};
function addAttributes(entry: WindowManagerState, proto: any, useElapsedTime = false) {
entry.kind = entry.constructor.name;
if (!entry.isComplete()) {
entry.isIncompleteReason = entry.getIsIncompleteReason();
}
entry.proto = proto;
if (useElapsedTime || entry.clockTimestamp == undefined) {
entry.name = TimeUtils.format(new ElapsedTimestamp(BigInt(entry.elapsedTimestamp)));
entry.shortName = entry.name;
} else {
entry.name = TimeUtils.format(new RealTimestamp(BigInt(entry.clockTimestamp)));
entry.shortName = entry.name;
}
entry.isVisible = true;
entry.kind = entry.constructor.name;
if (!entry.isComplete()) {
entry.isIncompleteReason = entry.getIsIncompleteReason();
}
entry.proto = proto;
if (useElapsedTime || entry.clockTimestamp == undefined) {
entry.name = TimeUtils.format(new ElapsedTimestamp(BigInt(entry.elapsedTimestamp)));
entry.shortName = entry.name;
} else {
entry.name = TimeUtils.format(new RealTimestamp(BigInt(entry.clockTimestamp)));
entry.shortName = entry.name;
}
entry.isVisible = true;
}
function createWindowManagerPolicy(proto: any): WindowManagerPolicy {
return new WindowManagerPolicy(
proto.focusedAppToken ?? "",
proto.forceStatusBar,
proto.forceStatusBarFromKeyguard,
proto.keyguardDrawComplete,
proto.keyguardOccluded,
proto.keyguardOccludedChanged,
proto.keyguardOccludedPending,
proto.lastSystemUiFlags,
proto.orientation,
PlatformConsts.Rotation.Companion.getByValue(proto.rotation),
proto.rotationMode,
proto.screenOnFully,
proto.windowManagerDrawComplete
);
return new WindowManagerPolicy(
proto.focusedAppToken ?? '',
proto.forceStatusBar,
proto.forceStatusBarFromKeyguard,
proto.keyguardDrawComplete,
proto.keyguardOccluded,
proto.keyguardOccludedChanged,
proto.keyguardOccludedPending,
proto.lastSystemUiFlags,
proto.orientation,
PlatformConsts.Rotation.Companion.getByValue(proto.rotation),
proto.rotationMode,
proto.screenOnFully,
proto.windowManagerDrawComplete
);
}
function createRootWindowContainer(proto: any, nextSeq: () => number): RootWindowContainer {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* childrenProto */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ false,
/* computedZ */ nextSeq
);
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* childrenProto */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ false,
/* computedZ */ nextSeq
);
if (windowContainer == null) {
throw new Error(`Window container should not be null.\n${JSON.stringify(proto)}`);
}
const entry = new RootWindowContainer(windowContainer);
return entry;
if (windowContainer == null) {
throw new Error(`Window container should not be null.\n${JSON.stringify(proto)}`);
}
const entry = new RootWindowContainer(windowContainer);
return entry;
}
function createKeyguardControllerState(proto: any): KeyguardControllerState {
const keyguardOccludedStates: any = {};
const keyguardOccludedStates: any = {};
if (proto) {
proto.keyguardOccludedStates.forEach((it: any) =>
keyguardOccludedStates[<keyof typeof keyguardOccludedStates>it.displayId] = it.keyguardOccluded);
}
return new KeyguardControllerState(
proto?.isAodShowing ?? false,
proto?.isKeyguardShowing ?? false,
keyguardOccludedStates
if (proto) {
proto.keyguardOccludedStates.forEach(
(it: any) =>
(keyguardOccludedStates[<keyof typeof keyguardOccludedStates>it.displayId] =
it.keyguardOccluded)
);
}
return new KeyguardControllerState(
proto?.isAodShowing ?? false,
proto?.isKeyguardShowing ?? false,
keyguardOccludedStates
);
}
export {WindowManagerState};

View File

@@ -14,123 +14,127 @@
* limitations under the License.
*/
import { shortenName } from '../mixin'
import { toRect, Size, WindowState, WindowLayoutParams } from "../common"
import WindowContainer from "./WindowContainer"
import {Size, toRect, WindowLayoutParams, WindowState} from '../common';
import {shortenName} from '../mixin';
import WindowContainer from './WindowContainer';
WindowState.fromProto = function (proto: any, isActivityInTree: Boolean, nextSeq: () => number): WindowState {
if (proto == null) {
return null;
} else {
const windowParams = createWindowLayoutParams(proto.attributes);
const identifierName = getIdentifier(proto);
const windowType = getWindowType(proto, identifierName);
const name = getName(identifierName);
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
/* nameOverride */ name,
/* identifierOverride */ proto.identifier
);
WindowState.fromProto = function (
proto: any,
isActivityInTree: Boolean,
nextSeq: () => number
): WindowState {
if (proto == null) {
return null;
} else {
const windowParams = createWindowLayoutParams(proto.attributes);
const identifierName = getIdentifier(proto);
const windowType = getWindowType(proto, identifierName);
const name = getName(identifierName);
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
/* nameOverride */ name,
/* identifierOverride */ proto.identifier
);
const entry = new WindowState(
windowParams,
proto.displayId,
proto.stackId,
proto.animator?.surface?.layer ?? 0,
proto.animator?.surface?.shown ?? false,
windowType,
new Size(proto.requestedWidth, proto.requestedHeight),
toRect(proto.surfacePosition),
toRect(proto.windowFrames?.frame ?? null),
toRect(proto.windowFrames?.containingFrame ?? null),
toRect(proto.windowFrames?.parentFrame ?? null),
toRect(proto.windowFrames?.contentFrame ?? null),
toRect(proto.windowFrames?.contentInsets ?? null),
toRect(proto.surfaceInsets),
toRect(proto.givenContentInsets),
toRect(proto.animator?.lastClipRect ?? null),
windowContainer,
/* isAppWindow */ isActivityInTree
);
const entry = new WindowState(
windowParams,
proto.displayId,
proto.stackId,
proto.animator?.surface?.layer ?? 0,
proto.animator?.surface?.shown ?? false,
windowType,
new Size(proto.requestedWidth, proto.requestedHeight),
toRect(proto.surfacePosition),
toRect(proto.windowFrames?.frame ?? null),
toRect(proto.windowFrames?.containingFrame ?? null),
toRect(proto.windowFrames?.parentFrame ?? null),
toRect(proto.windowFrames?.contentFrame ?? null),
toRect(proto.windowFrames?.contentInsets ?? null),
toRect(proto.surfaceInsets),
toRect(proto.givenContentInsets),
toRect(proto.animator?.lastClipRect ?? null),
windowContainer,
/* isAppWindow */ isActivityInTree
);
addAttributes(entry, proto);
return entry;
}
}
addAttributes(entry, proto);
return entry;
}
};
function createWindowLayoutParams(proto: any): WindowLayoutParams {
return new WindowLayoutParams(
/* type */ proto?.type ?? 0,
/* x */ proto?.x ?? 0,
/* y */ proto?.y ?? 0,
/* width */ proto?.width ?? 0,
/* height */ proto?.height ?? 0,
/* horizontalMargin */ proto?.horizontalMargin ?? 0,
/* verticalMargin */ proto?.verticalMargin ?? 0,
/* gravity */ proto?.gravity ?? 0,
/* softInputMode */ proto?.softInputMode ?? 0,
/* format */ proto?.format ?? 0,
/* windowAnimations */ proto?.windowAnimations ?? 0,
/* alpha */ proto?.alpha ?? 0,
/* screenBrightness */ proto?.screenBrightness ?? 0,
/* buttonBrightness */ proto?.buttonBrightness ?? 0,
/* rotationAnimation */ proto?.rotationAnimation ?? 0,
/* preferredRefreshRate */ proto?.preferredRefreshRate ?? 0,
/* preferredDisplayModeId */ proto?.preferredDisplayModeId ?? 0,
/* hasSystemUiListeners */ proto?.hasSystemUiListeners ?? false,
/* inputFeatureFlags */ proto?.inputFeatureFlags ?? 0,
/* userActivityTimeout */ proto?.userActivityTimeout ?? 0,
/* colorMode */ proto?.colorMode ?? 0,
/* flags */ proto?.flags ?? 0,
/* privateFlags */ proto?.privateFlags ?? 0,
/* systemUiVisibilityFlags */ proto?.systemUiVisibilityFlags ?? 0,
/* subtreeSystemUiVisibilityFlags */ proto?.subtreeSystemUiVisibilityFlags ?? 0,
/* appearance */ proto?.appearance ?? 0,
/* behavior */ proto?.behavior ?? 0,
/* fitInsetsTypes */ proto?.fitInsetsTypes ?? 0,
/* fitInsetsSides */ proto?.fitInsetsSides ?? 0,
/* fitIgnoreVisibility */ proto?.fitIgnoreVisibility ?? false
)
return new WindowLayoutParams(
/* type */ proto?.type ?? 0,
/* x */ proto?.x ?? 0,
/* y */ proto?.y ?? 0,
/* width */ proto?.width ?? 0,
/* height */ proto?.height ?? 0,
/* horizontalMargin */ proto?.horizontalMargin ?? 0,
/* verticalMargin */ proto?.verticalMargin ?? 0,
/* gravity */ proto?.gravity ?? 0,
/* softInputMode */ proto?.softInputMode ?? 0,
/* format */ proto?.format ?? 0,
/* windowAnimations */ proto?.windowAnimations ?? 0,
/* alpha */ proto?.alpha ?? 0,
/* screenBrightness */ proto?.screenBrightness ?? 0,
/* buttonBrightness */ proto?.buttonBrightness ?? 0,
/* rotationAnimation */ proto?.rotationAnimation ?? 0,
/* preferredRefreshRate */ proto?.preferredRefreshRate ?? 0,
/* preferredDisplayModeId */ proto?.preferredDisplayModeId ?? 0,
/* hasSystemUiListeners */ proto?.hasSystemUiListeners ?? false,
/* inputFeatureFlags */ proto?.inputFeatureFlags ?? 0,
/* userActivityTimeout */ proto?.userActivityTimeout ?? 0,
/* colorMode */ proto?.colorMode ?? 0,
/* flags */ proto?.flags ?? 0,
/* privateFlags */ proto?.privateFlags ?? 0,
/* systemUiVisibilityFlags */ proto?.systemUiVisibilityFlags ?? 0,
/* subtreeSystemUiVisibilityFlags */ proto?.subtreeSystemUiVisibilityFlags ?? 0,
/* appearance */ proto?.appearance ?? 0,
/* behavior */ proto?.behavior ?? 0,
/* fitInsetsTypes */ proto?.fitInsetsTypes ?? 0,
/* fitInsetsSides */ proto?.fitInsetsSides ?? 0,
/* fitIgnoreVisibility */ proto?.fitIgnoreVisibility ?? false
);
}
function getWindowType(proto: any, identifierName: string): number {
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
return WindowState.WINDOW_TYPE_STARTING;
} else if (proto.animatingExit) {
return WindowState.WINDOW_TYPE_EXITING;
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
return WindowState.WINDOW_TYPE_STARTING;
}
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
return WindowState.WINDOW_TYPE_STARTING;
} else if (proto.animatingExit) {
return WindowState.WINDOW_TYPE_EXITING;
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
return WindowState.WINDOW_TYPE_STARTING;
}
return 0;
return 0;
}
function getName(identifierName: string): string {
var name = identifierName;
let name = identifierName;
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
name = identifierName.substring(WindowState.STARTING_WINDOW_PREFIX.length);
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
name = identifierName.substring(WindowState.DEBUGGER_WINDOW_PREFIX.length);
}
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
name = identifierName.substring(WindowState.STARTING_WINDOW_PREFIX.length);
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
name = identifierName.substring(WindowState.DEBUGGER_WINDOW_PREFIX.length);
}
return name;
return name;
}
function getIdentifier(proto: any): string {
return proto.windowContainer.identifier?.title ?? proto.identifier?.title ?? "";
return proto.windowContainer.identifier?.title ?? proto.identifier?.title ?? '';
}
function addAttributes(entry: WindowState, proto: any) {
entry.kind = entry.constructor.name;
entry.rect = entry.frame;
entry.rect.ref = entry;
entry.rect.label = entry.name;
entry.proto = proto;
entry.shortName = shortenName(entry.name);
entry.kind = entry.constructor.name;
entry.rect = entry.frame;
entry.rect.ref = entry;
entry.rect.label = entry.name;
entry.proto = proto;
entry.shortName = shortenName(entry.name);
}
export default WindowState
export default WindowState;

View File

@@ -14,29 +14,33 @@
* limitations under the License.
*/
import { shortenName } from '../mixin'
import { WindowToken } from "../common"
import WindowContainer from "./WindowContainer"
import {WindowToken} from '../common';
import {shortenName} from '../mixin';
import WindowContainer from './WindowContainer';
WindowToken.fromProto = function (proto: any, isActivityInTree: Boolean, nextSeq: () => number): WindowToken {
if (proto == null) {
return null;
}
WindowToken.fromProto = function (
proto: any,
isActivityInTree: Boolean,
nextSeq: () => number
): WindowToken {
if (proto == null) {
return null;
}
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
/* nameOverride */ null,
/* identifierOverride */ null,
/* tokenOverride */ proto.hashCode
);
const entry = new WindowToken(windowContainer);
entry.kind = entry.constructor.name;
entry.proto = proto;
entry.shortName = shortenName(entry.name);
return entry;
}
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children ?? [],
/* isActivityInTree */ isActivityInTree,
/* computedZ */ nextSeq,
/* nameOverride */ null,
/* identifierOverride */ null,
/* tokenOverride */ proto.hashCode
);
const entry = new WindowToken(windowContainer);
entry.kind = entry.constructor.name;
entry.proto = proto;
entry.shortName = shortenName(entry.name);
return entry;
};
export default WindowToken;

View File

@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {TimeUtils} from "common/utils/time_utils";
import configJson from "../../../../../../frameworks/base/data/etc/services.core.protolog.json";
import { ElapsedTimestamp, RealTimestamp, TimestampType } from "./timestamp";
import {TimeUtils} from 'common/utils/time_utils';
import configJson from '../../../../../../frameworks/base/data/etc/services.core.protolog.json';
import {ElapsedTimestamp, RealTimestamp, TimestampType} from './timestamp';
class ProtoLogTraceEntry {
constructor(public messages: LogMessage[], public currentMessageIndex: number) {
}
constructor(public messages: LogMessage[], public currentMessageIndex: number) {}
}
class LogMessage {
@@ -30,7 +29,14 @@ class LogMessage {
at: string;
timestamp: bigint;
constructor(text: string, time: string, tag: string, level: string, at: string, timestamp: bigint) {
constructor(
text: string,
time: string,
tag: string,
level: string,
at: string,
timestamp: bigint
) {
this.text = text;
this.time = time;
this.tag = tag;
@@ -41,18 +47,30 @@ class LogMessage {
}
class FormattedLogMessage extends LogMessage {
constructor(proto: any, timestampType: TimestampType, realToElapsedTimeOffsetNs: bigint|undefined) {
const text = (
constructor(
proto: any,
timestampType: TimestampType,
realToElapsedTimeOffsetNs: bigint | undefined
) {
const text =
proto.messageHash.toString() +
" - [" + proto.strParams.toString() +
"] [" + proto.sint64Params.toString() +
"] [" + proto.doubleParams.toString() +
"] [" + proto.booleanParams.toString() + "]"
);
' - [' +
proto.strParams.toString() +
'] [' +
proto.sint64Params.toString() +
'] [' +
proto.doubleParams.toString() +
'] [' +
proto.booleanParams.toString() +
']';
let time: string;
let timestamp: bigint;
if (timestampType === TimestampType.REAL && realToElapsedTimeOffsetNs !== undefined && realToElapsedTimeOffsetNs != 0n) {
if (
timestampType === TimestampType.REAL &&
realToElapsedTimeOffsetNs !== undefined &&
realToElapsedTimeOffsetNs != 0n
) {
timestamp = realToElapsedTimeOffsetNs + BigInt(proto.elapsedRealtimeNanos);
time = TimeUtils.format(new RealTimestamp(timestamp));
} else {
@@ -60,23 +78,28 @@ class FormattedLogMessage extends LogMessage {
time = TimeUtils.format(new ElapsedTimestamp(timestamp));
}
super(
text,
time,
"INVALID",
"invalid",
"",
timestamp);
super(text, time, 'INVALID', 'invalid', '', timestamp);
}
}
class UnformattedLogMessage extends LogMessage {
constructor(proto: any, timestampType: TimestampType, realToElapsedTimeOffsetNs: bigint|undefined, message: any) {
constructor(
proto: any,
timestampType: TimestampType,
realToElapsedTimeOffsetNs: bigint | undefined,
message: any
) {
let time: string;
let timestamp: bigint;
if (timestampType === TimestampType.REAL && realToElapsedTimeOffsetNs !== undefined && realToElapsedTimeOffsetNs != 0n) {
if (
timestampType === TimestampType.REAL &&
realToElapsedTimeOffsetNs !== undefined &&
realToElapsedTimeOffsetNs != 0n
) {
timestamp = realToElapsedTimeOffsetNs + BigInt(proto.elapsedRealtimeNanos);
time = TimeUtils.format(new RealTimestamp(realToElapsedTimeOffsetNs + BigInt(proto.elapsedRealtimeNanos)));
time = TimeUtils.format(
new RealTimestamp(realToElapsedTimeOffsetNs + BigInt(proto.elapsedRealtimeNanos))
);
} else {
timestamp = BigInt(proto.elapsedRealtimeNanos);
time = TimeUtils.format(new ElapsedTimestamp(timestamp));
@@ -94,7 +117,7 @@ class UnformattedLogMessage extends LogMessage {
}
function formatText(messageFormat: any, data: any) {
let out = "";
let out = '';
const strParams: string[] = data.strParams;
let strParamsIdx = 0;
@@ -105,44 +128,43 @@ function formatText(messageFormat: any, data: any) {
const booleanParams: number[] = data.booleanParams;
let booleanParamsIdx = 0;
for (let i = 0; i < messageFormat.length;) {
if (messageFormat[i] == "%") {
for (let i = 0; i < messageFormat.length; ) {
if (messageFormat[i] == '%') {
if (i + 1 >= messageFormat.length) {
// Should never happen - protologtool checks for that
throw new Error("Invalid format string");
throw new Error('Invalid format string');
}
switch (messageFormat[i + 1]) {
case "%":
out += "%";
break;
case "d":
out += getParam(sint64Params, sint64ParamsIdx++).toString(10);
break;
case "o":
out += getParam(sint64Params, sint64ParamsIdx++).toString(8);
break;
case "x":
out += getParam(sint64Params, sint64ParamsIdx++).toString(16);
break;
case "f":
out += getParam(doubleParams, doubleParamsIdx++).toFixed(6);
break;
case "e":
out += getParam(doubleParams, doubleParamsIdx++).toExponential();
break;
case "g":
out += getParam(doubleParams, doubleParamsIdx++).toString();
break;
case "s":
out += getParam(strParams, strParamsIdx++);
break;
case "b":
out += getParam(booleanParams, booleanParamsIdx++).toString();
break;
default:
// Should never happen - protologtool checks for that
throw new Error("Invalid format string conversion: " +
messageFormat[i + 1]);
case '%':
out += '%';
break;
case 'd':
out += getParam(sint64Params, sint64ParamsIdx++).toString(10);
break;
case 'o':
out += getParam(sint64Params, sint64ParamsIdx++).toString(8);
break;
case 'x':
out += getParam(sint64Params, sint64ParamsIdx++).toString(16);
break;
case 'f':
out += getParam(doubleParams, doubleParamsIdx++).toFixed(6);
break;
case 'e':
out += getParam(doubleParams, doubleParamsIdx++).toExponential();
break;
case 'g':
out += getParam(doubleParams, doubleParamsIdx++).toString();
break;
case 's':
out += getParam(strParams, strParamsIdx++);
break;
case 'b':
out += getParam(booleanParams, booleanParamsIdx++).toString();
break;
default:
// Should never happen - protologtool checks for that
throw new Error('Invalid format string conversion: ' + messageFormat[i + 1]);
}
i += 2;
} else {
@@ -155,7 +177,7 @@ function formatText(messageFormat: any, data: any) {
function getParam<T>(arr: T[], idx: number): T {
if (arr.length <= idx) {
throw new Error("No param for format string conversion");
throw new Error('No param for format string conversion');
}
return arr[idx];
}

View File

@@ -15,9 +15,7 @@
*/
class ScreenRecordingTraceEntry {
constructor(public videoTimeSeconds: number,
public videoData: Blob) {
}
constructor(public videoTimeSeconds: number, public videoData: Blob) {}
}
export {ScreenRecordingTraceEntry};

View File

@@ -14,12 +14,12 @@
* limitations under the License.
*/
import {Timestamp} from "./timestamp";
import {Timestamp} from './timestamp';
class ScreenRecordingUtils {
static timestampToVideoTimeSeconds(firstTimestamp: Timestamp, currentTimestamp: Timestamp) {
if (firstTimestamp.getType() !== currentTimestamp.getType()) {
throw new Error("Attempted to use timestamps with different type");
throw new Error('Attempted to use timestamps with different type');
}
const videoTimeSeconds =
Number(currentTimestamp.getValueNs() - firstTimestamp.getValueNs()) / 1000000000;

View File

@@ -14,23 +14,23 @@
* limitations under the License.
*/
import { Timestamp, TimestampType } from "./timestamp";
import {Timestamp, TimestampType} from './timestamp';
describe("Timestamp", () => {
describe("from", () => {
it("throws when missing elapsed timestamp", () => {
describe('Timestamp', () => {
describe('from', () => {
it('throws when missing elapsed timestamp', () => {
expect(() => {
Timestamp.from(TimestampType.REAL, 100n);
}).toThrow();
});
it("can create real timestamp", () => {
it('can create real timestamp', () => {
const timestamp = Timestamp.from(TimestampType.REAL, 100n, 500n);
expect(timestamp.getType()).toBe(TimestampType.REAL);
expect(timestamp.getValueNs()).toBe(600n);
});
it("can create elapsed timestamp", () => {
it('can create elapsed timestamp', () => {
let timestamp = Timestamp.from(TimestampType.ELAPSED, 100n, 500n);
expect(timestamp.getType()).toBe(TimestampType.ELAPSED);
expect(timestamp.getValueNs()).toBe(100n);
@@ -40,4 +40,4 @@ describe("Timestamp", () => {
expect(timestamp.getValueNs()).toBe(100n);
});
});
});
});

View File

@@ -15,8 +15,8 @@
*/
export enum TimestampType {
ELAPSED = "ELAPSED",
REAL = "REAL",
ELAPSED = 'ELAPSED',
REAL = 'REAL',
}
export class Timestamp {
@@ -25,19 +25,21 @@ export class Timestamp {
this.valueNs = valueNs;
}
static from(timestampType: TimestampType, elapsedTimestamp: bigint, realToElapsedTimeOffsetNs: bigint | undefined = undefined) {
static from(
timestampType: TimestampType,
elapsedTimestamp: bigint,
realToElapsedTimeOffsetNs: bigint | undefined = undefined
) {
switch (timestampType) {
case TimestampType.REAL:
if (realToElapsedTimeOffsetNs === undefined) {
throw new Error("realToElapsedTimeOffsetNs can't be undefined to use real timestamp");
}
return new Timestamp(TimestampType.REAL, elapsedTimestamp + realToElapsedTimeOffsetNs);
break;
case TimestampType.ELAPSED:
return new Timestamp(TimestampType.ELAPSED, elapsedTimestamp);
break;
default:
throw new Error("Unhandled timestamp type");
case TimestampType.REAL:
if (realToElapsedTimeOffsetNs === undefined) {
throw new Error("realToElapsedTimeOffsetNs can't be undefined to use real timestamp");
}
return new Timestamp(TimestampType.REAL, elapsedTimestamp + realToElapsedTimeOffsetNs);
case TimestampType.ELAPSED:
return new Timestamp(TimestampType.ELAPSED, elapsedTimestamp);
default:
throw new Error('Unhandled timestamp type');
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {TraceType} from "./trace_type";
import {TraceType} from './trace_type';
export interface Trace {
type: TraceType;
@@ -22,9 +22,5 @@ export interface Trace {
}
export class TraceFile {
constructor(
public file: File,
public parentArchive?: File
) {
}
constructor(public file: File, public parentArchive?: File) {}
}

View File

@@ -26,8 +26,8 @@ export interface TraceTreeNode {
inputMethodService?: any;
inputMethodManagerService?: any;
where?: string;
elapsedRealtimeNanos?: number|bigint;
clockTimeNanos?: number|bigint;
elapsedRealtimeNanos?: number | bigint;
clockTimeNanos?: number | bigint;
shortName?: string;
type?: string;
id?: string | number;

View File

@@ -14,16 +14,15 @@
* limitations under the License.
*/
import { TimestampType } from "./timestamp";
import {TimestampType} from './timestamp';
class TransactionsTraceEntry {
constructor(
public entriesProto: any[],
public timestampType: TimestampType,
public realToElapsedTimeOffsetNs: bigint|undefined,
public currentEntryIndex: number,
) {
}
public realToElapsedTimeOffsetNs: bigint | undefined,
public currentEntryIndex: number
) {}
}
export {TransactionsTraceEntry};

View File

@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ArrayUtils} from "./array_utils";
import {ArrayUtils} from './array_utils';
describe("ArrayUtils", () => {
it("equal", () => {
describe('ArrayUtils', () => {
it('equal', () => {
expect(ArrayUtils.equal([], [1])).toBeFalse();
expect(ArrayUtils.equal([1], [])).toBeFalse();
@@ -38,7 +38,7 @@ describe("ArrayUtils", () => {
expect(ArrayUtils.equal(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3]))).toBeTrue();
});
it("searchSubarray", () => {
it('searchSubarray', () => {
expect(ArrayUtils.searchSubarray([], [0])).toEqual(undefined);
expect(ArrayUtils.searchSubarray([], [])).toEqual(0);
expect(ArrayUtils.searchSubarray([0], [])).toEqual(0);
@@ -53,10 +53,9 @@ describe("ArrayUtils", () => {
expect(ArrayUtils.searchSubarray([0, 1, 2], [1, 2])).toEqual(1);
expect(ArrayUtils.searchSubarray([0, 1, 2], [2])).toEqual(2);
expect(ArrayUtils.searchSubarray([0, 1, 2], [2, 3])).toEqual(undefined);
});
it("binarySearchLowerOrEqual", () => {
it('binarySearchLowerOrEqual', () => {
// no match
expect(ArrayUtils.binarySearchLowerOrEqual([], 5)).toBeUndefined();
expect(ArrayUtils.binarySearchLowerOrEqual([6], 5)).toBeUndefined();
@@ -87,7 +86,7 @@ describe("ArrayUtils", () => {
expect(ArrayUtils.binarySearchLowerOrEqual([4, 4, 4, 5, 5, 5, 5, 6], 5)).toEqual(3);
});
it("toUintLittleEndian", () => {
it('toUintLittleEndian', () => {
const buffer = new Uint8Array([0, 0, 1, 1]);
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff]), 0, -1)).toEqual(0n);
@@ -103,18 +102,28 @@ describe("ArrayUtils", () => {
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0x00, 0x01]), 0, 2)).toEqual(256n);
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff]), 0, 2)).toEqual(0xffffn);
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff]), 0, 4)).toEqual(0xffffffffn);
expect(ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff]), 0, 4)).toEqual(
0xffffffffn
);
expect(
ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 0, 8))
.toEqual(0xffffffffffffffffn);
ArrayUtils.toUintLittleEndian(
new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
0,
8
)
).toEqual(0xffffffffffffffffn);
expect(
ArrayUtils.toUintLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 0, 9))
.toEqual(0xffffffffffffffffffn);
ArrayUtils.toUintLittleEndian(
new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
0,
9
)
).toEqual(0xffffffffffffffffffn);
});
it("toIntLittleEndian", () => {
it('toIntLittleEndian', () => {
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff]), 0, -1)).toEqual(0n);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff]), 0, 0)).toEqual(0n);
@@ -129,35 +138,75 @@ describe("ArrayUtils", () => {
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x80]), 0, 2)).toEqual(-32767n);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff]), 0, 2)).toEqual(-1n);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0x7f]), 0, 4)).toEqual(0x7fffffffn);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x00, 0x00, 0x00, 0x80]), 0, 4)).toEqual(-0x80000000n);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x00, 0x00, 0x80]), 0, 4)).toEqual(-0x7fffffffn);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff]), 0, 4)).toEqual(-1n);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0x7f]), 0, 4)).toEqual(
0x7fffffffn
);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x00, 0x00, 0x00, 0x80]), 0, 4)).toEqual(
-0x80000000n
);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x00, 0x00, 0x80]), 0, 4)).toEqual(
-0x7fffffffn
);
expect(ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff]), 0, 4)).toEqual(
-1n
);
expect(
ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]), 0, 8)
ArrayUtils.toIntLittleEndian(
new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]),
0,
8
)
).toEqual(0x7fffffffffffffffn);
expect(
ArrayUtils.toIntLittleEndian(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 0, 8)
ArrayUtils.toIntLittleEndian(
new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]),
0,
8
)
).toEqual(-0x8000000000000000n);
expect(
ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 0, 8)
ArrayUtils.toIntLittleEndian(
new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]),
0,
8
)
).toEqual(-0x7fffffffffffffffn);
expect(
ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 0, 8)
ArrayUtils.toIntLittleEndian(
new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
0,
8
)
).toEqual(-1n);
expect(
ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]), 0, 9)
ArrayUtils.toIntLittleEndian(
new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]),
0,
9
)
).toEqual(0x7fffffffffffffffffn);
expect(
ArrayUtils.toIntLittleEndian(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 0, 9)
ArrayUtils.toIntLittleEndian(
new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]),
0,
9
)
).toEqual(-0x800000000000000000n);
expect(
ArrayUtils.toIntLittleEndian(new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 0, 9)
ArrayUtils.toIntLittleEndian(
new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]),
0,
9
)
).toEqual(-0x7fffffffffffffffffn);
expect(
ArrayUtils.toIntLittleEndian(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 0, 9)
ArrayUtils.toIntLittleEndian(
new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
0,
9
)
).toEqual(-1n);
});
});

View File

@@ -39,7 +39,10 @@ class ArrayUtils {
return true;
}
static searchSubarray<T>(array: T[] | TypedArray, subarray: T[] | TypedArray): number|undefined {
static searchSubarray<T>(
array: T[] | TypedArray,
subarray: T[] | TypedArray
): number | undefined {
for (let i = 0; i + subarray.length <= array.length; ++i) {
let match = true;
@@ -58,7 +61,7 @@ class ArrayUtils {
return undefined;
}
static binarySearchLowerOrEqual<T>(values: T[] | TypedArray, target: T): number|undefined {
static binarySearchLowerOrEqual<T>(values: T[] | TypedArray, target: T): number | undefined {
if (values.length == 0) {
return undefined;
}
@@ -66,9 +69,9 @@ class ArrayUtils {
let low = 0;
let high = values.length - 1;
let result: number|undefined = undefined;
let result: number | undefined = undefined;
while(low <= high) {
while (low <= high) {
const mid = (low + high) >> 1;
if (values[mid] < target) {
@@ -76,11 +79,9 @@ class ArrayUtils {
result = mid;
}
low = mid + 1;
}
else if (values[mid] > target) {
} else if (values[mid] > target) {
high = mid - 1;
}
else {
} else {
result = mid;
high = mid - 1;
}
@@ -91,7 +92,7 @@ class ArrayUtils {
static toUintLittleEndian(buffer: Uint8Array, start: number, end: number): bigint {
let result = 0n;
for (let i = end-1; i >= start; --i) {
for (let i = end - 1; i >= start; --i) {
result *= 256n;
result += BigInt(buffer[i]);
}
@@ -99,7 +100,7 @@ class ArrayUtils {
}
static toIntLittleEndian(buffer: Uint8Array, start: number, end: number): bigint {
const numOfBits = BigInt(Math.max(0, 8 * (end-start)));
const numOfBits = BigInt(Math.max(0, 8 * (end - start)));
if (numOfBits <= 0n) {
return 0n;
}

View File

@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import JSZip from "jszip";
import {FunctionUtils, OnProgressUpdateType} from "./function_utils";
import JSZip from 'jszip';
import {FunctionUtils, OnProgressUpdateType} from './function_utils';
export type OnFile = (file: File, parentArchive: File|undefined) => void;
export type OnFile = (file: File, parentArchive: File | undefined) => void;
class FileUtils {
static getFileExtension(file: File) {
const split = file.name.split(".");
const split = file.name.split('.');
if (split.length > 1) {
return split.pop();
}
@@ -28,8 +28,8 @@ class FileUtils {
}
static removeDirFromFileName(name: string) {
if (name.includes("/")) {
const startIndex = name.lastIndexOf("/") + 1;
if (name.includes('/')) {
const startIndex = name.lastIndexOf('/') + 1;
return name.slice(startIndex);
} else {
return name;
@@ -38,17 +38,18 @@ class FileUtils {
static async createZipArchive(files: File[]): Promise<Blob> {
const zip = new JSZip();
for (let i=0; i < files.length; i++) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
const blob = await file.arrayBuffer();
zip.file(file.name, blob);
}
return await zip.generateAsync({type: "blob"});
return await zip.generateAsync({type: 'blob'});
}
static async unzipFile(
file: File,
onProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING): Promise<File[]> {
onProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING
): Promise<File[]> {
const unzippedFiles: File[] = [];
const zip = new JSZip();
const content = await zip.loadAsync(file);
@@ -61,12 +62,12 @@ class FileUtils {
continue;
} else {
const name = this.removeDirFromFileName(filename);
const fileBlob = await file.async("blob");
const fileBlob = await file.async('blob');
const unzippedFile = new File([fileBlob], name);
unzippedFiles.push(unzippedFile);
}
onProgressUpdate(100 * (index + 1) / filenames.length);
onProgressUpdate((100 * (index + 1)) / filenames.length);
}
return unzippedFiles;
@@ -75,19 +76,19 @@ class FileUtils {
static async unzipFilesIfNeeded(
files: File[],
onFile: OnFile,
onProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING) {
for (let i=0; i<files.length; i++) {
onProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING
) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
const onSubprogressUpdate = (subPercentage: number) => {
const percentage = 100 * i / files.length
+ subPercentage / files.length;
const percentage = (100 * i) / files.length + subPercentage / files.length;
onProgressUpdate(percentage);
};
if (FileUtils.isZipFile(file)) {
const unzippedFile = await FileUtils.unzipFile(file, onSubprogressUpdate);
unzippedFile.forEach(unzippedFile => onFile(unzippedFile, file));
unzippedFile.forEach((unzippedFile) => onFile(unzippedFile, file));
} else {
onFile(file, undefined);
}
@@ -95,7 +96,7 @@ class FileUtils {
}
static isZipFile(file: File) {
return this.getFileExtension(file) === "zip";
return this.getFileExtension(file) === 'zip';
}
}

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
export type Schema = Omit<GlobalConfig, "set">;
export type Schema = Omit<GlobalConfig, 'set'>;
class GlobalConfig {
readonly MODE: "DEV"|"PROD" = "PROD" as const;
readonly MODE: 'DEV' | 'PROD' = 'PROD' as const;
set(config: Schema) {
Object.assign(this, config);

View File

@@ -14,125 +14,125 @@
* limitations under the License.
*/
import { MockStorage } from "test/unit/mock_storage";
import {PersistentStoreProxy, StoreObject} from "./persistent_store_proxy";
import {MockStorage} from 'test/unit/mock_storage';
import {PersistentStoreProxy, StoreObject} from './persistent_store_proxy';
describe("PersistentStoreObject", () => {
it("uses defaults when no store is available", () => {
describe('PersistentStoreObject', () => {
it('uses defaults when no store is available', () => {
const mockStorage = new MockStorage();
const defaultValues = {
"key1": "value",
"key2": true
key1: 'value',
key2: true,
};
const storeObject = PersistentStoreProxy.new("storeKey", defaultValues, mockStorage);
const storeObject = PersistentStoreProxy.new('storeKey', defaultValues, mockStorage);
expect(storeObject["key1"]).toBe("value");
expect(storeObject["key2"]).toBe(true);
expect(storeObject['key1']).toBe('value');
expect(storeObject['key2']).toBe(true);
});
it("can update properties", () => {
it('can update properties', () => {
const mockStorage = new MockStorage();
const defaultValues = {
"key1": "value",
"key2": true
key1: 'value',
key2: true,
};
const storeObject = PersistentStoreProxy.new("storeKey", defaultValues, mockStorage);
const storeObject = PersistentStoreProxy.new('storeKey', defaultValues, mockStorage);
storeObject["key1"] = "someOtherValue";
storeObject["key2"] = false;
expect(storeObject["key1"]).toBe("someOtherValue");
expect(storeObject["key2"]).toBe(false);
storeObject['key1'] = 'someOtherValue';
storeObject['key2'] = false;
expect(storeObject['key1']).toBe('someOtherValue');
expect(storeObject['key2']).toBe(false);
});
it("uses explicitly set store data", () => {
it('uses explicitly set store data', () => {
const mockStorage = new MockStorage();
const defaultValues = {
"key1": "value",
"key2": true
key1: 'value',
key2: true,
};
const storeObject = PersistentStoreProxy.new("storeKey", defaultValues, mockStorage);
storeObject["key1"] = "someOtherValue";
storeObject["key2"] = false;
const storeObject = PersistentStoreProxy.new('storeKey', defaultValues, mockStorage);
storeObject['key1'] = 'someOtherValue';
storeObject['key2'] = false;
const newStoreObject = PersistentStoreProxy.new("storeKey", defaultValues, mockStorage);
expect(newStoreObject["key1"]).toBe("someOtherValue");
expect(newStoreObject["key2"]).toBe(false);
const newStoreObject = PersistentStoreProxy.new('storeKey', defaultValues, mockStorage);
expect(newStoreObject['key1']).toBe('someOtherValue');
expect(newStoreObject['key2']).toBe(false);
});
it("uses default values if not explicitly set", () => {
it('uses default values if not explicitly set', () => {
const mockStorage = new MockStorage();
const defaultValues = {
"key1": "value",
"key2": true
key1: 'value',
key2: true,
};
const storeObject = PersistentStoreProxy.new("storeKey", defaultValues, mockStorage);
expect(storeObject["key1"]).toBe("value");
expect(storeObject["key2"]).toBe(true);
const storeObject = PersistentStoreProxy.new('storeKey', defaultValues, mockStorage);
expect(storeObject['key1']).toBe('value');
expect(storeObject['key2']).toBe(true);
const newDefaultValues = {
"key1": "someOtherValue",
"key2": false
key1: 'someOtherValue',
key2: false,
};
const newStoreObject = PersistentStoreProxy.new("storeKey", newDefaultValues, mockStorage);
expect(newStoreObject["key1"]).toBe("someOtherValue");
expect(newStoreObject["key2"]).toBe(false);
const newStoreObject = PersistentStoreProxy.new('storeKey', newDefaultValues, mockStorage);
expect(newStoreObject['key1']).toBe('someOtherValue');
expect(newStoreObject['key2']).toBe(false);
});
it("can't update non leaf configs", () => {
const mockStorage = new MockStorage();
const defaultValues: StoreObject = {
"key1": "value",
"key2": {
"key3": true
}
key1: 'value',
key2: {
key3: true,
},
};
const storeObject = PersistentStoreProxy.new("storeKey", defaultValues, mockStorage);
expect(() => storeObject["key2"] = false).toThrow();
const storeObject = PersistentStoreProxy.new('storeKey', defaultValues, mockStorage);
expect(() => (storeObject['key2'] = false)).toThrow();
});
it("can get nested configs", () => {
it('can get nested configs', () => {
const mockStorage = new MockStorage();
const defaultValues = {
"key1": "value",
"key2": {
"key3": true
}
key1: 'value',
key2: {
key3: true,
},
};
const storeObject = PersistentStoreProxy.new("storeKey", defaultValues, mockStorage);
expect(storeObject["key2"]["key3"]).toBe(true);
const storeObject = PersistentStoreProxy.new('storeKey', defaultValues, mockStorage);
expect(storeObject['key2']['key3']).toBe(true);
});
it("can update schema", () => {
it('can update schema', () => {
const mockStorage = new MockStorage();
const schema1 = {
"key1": "value1",
"key2": {
"key3": true
}
key1: 'value1',
key2: {
key3: true,
},
};
const storeObject1 = PersistentStoreProxy.new("storeKey", schema1, mockStorage);
expect(storeObject1["key1"]).toBe("value1");
expect(storeObject1["key2"]["key3"]).toBe(true);
const storeObject1 = PersistentStoreProxy.new('storeKey', schema1, mockStorage);
expect(storeObject1['key1']).toBe('value1');
expect(storeObject1['key2']['key3']).toBe(true);
// Change from default value to ensure we update the storage
storeObject1["key1"] = "someOtherValue";
storeObject1["key2"]["key3"] = false;
storeObject1['key1'] = 'someOtherValue';
storeObject1['key2']['key3'] = false;
const schema2 = {
"key1": {
"key3": "value2"
key1: {
key3: 'value2',
},
"key2": true
key2: true,
};
const storeObject2 = PersistentStoreProxy.new("storeKey", schema2, mockStorage);
expect(storeObject2["key1"]["key3"]).toBe("value2");
expect(storeObject2["key2"]).toBe(true);
const storeObject2 = PersistentStoreProxy.new('storeKey', schema2, mockStorage);
expect(storeObject2['key1']['key3']).toBe('value2');
expect(storeObject2['key2']).toBe(true);
});
});
});

View File

@@ -14,24 +14,33 @@
* limitations under the License.
*/
export type StoreObject = { [key: string|number]: StoreObject|string|boolean|undefined } | StoreObject[] | string[] | boolean[]
export type StoreObject =
| {[key: string | number]: StoreObject | string | boolean | undefined}
| StoreObject[]
| string[]
| boolean[];
export class PersistentStoreProxy {
public static new<T extends StoreObject>(key: string, defaultState: T, storage: Storage): T {
const storedState = JSON.parse(storage.getItem(key) ?? "{}");
const storedState = JSON.parse(storage.getItem(key) ?? '{}');
const currentState = mergeDeep({}, deepClone(defaultState));
mergeDeepKeepingStructure(currentState, storedState);
return wrapWithPersistentStoreProxy<T>(key, currentState, storage);
}
}
function wrapWithPersistentStoreProxy<T extends StoreObject>(storeKey: string, object: T, storage: Storage, baseObject: T = object): T {
function wrapWithPersistentStoreProxy<T extends StoreObject>(
storeKey: string,
object: T,
storage: Storage,
baseObject: T = object
): T {
const updatableProps: string[] = [];
let key: number|string;
let key: number | string;
for (key in object) {
const value = object[key];
if (typeof value === "string" || typeof value === "boolean" || value === undefined) {
if (typeof value === 'string' || typeof value === 'boolean' || value === undefined) {
if (!Array.isArray(object)) {
updatableProps.push(key);
}
@@ -42,10 +51,10 @@ function wrapWithPersistentStoreProxy<T extends StoreObject>(storeKey: string, o
const proxyObj = new Proxy(object, {
set: (target, prop, newValue) => {
if (typeof prop === "symbol") {
if (typeof prop === 'symbol') {
throw Error("Can't use symbol keys only strings");
}
if (Array.isArray(target) && typeof prop === "number") {
if (Array.isArray(target) && typeof prop === 'number') {
target[prop] = newValue;
storage.setItem(storeKey, JSON.stringify(baseObject));
return true;
@@ -55,15 +64,17 @@ function wrapWithPersistentStoreProxy<T extends StoreObject>(storeKey: string, o
storage.setItem(storeKey, JSON.stringify(baseObject));
return true;
}
throw Error(`Object property '${prop}' is not updatable. Can only update leaf keys: [${updatableProps}]`);
}
throw Error(
`Object property '${prop}' is not updatable. Can only update leaf keys: [${updatableProps}]`
);
},
});
return proxyObj;
}
function isObject(item: any): boolean {
return (item && typeof item === "object" && !Array.isArray(item));
return item && typeof item === 'object' && !Array.isArray(item);
}
/**
@@ -85,7 +96,7 @@ function mergeDeepKeepingStructure(target: any, source: any): any {
}
if (!isObject(target[key]) && !isObject(source[key])) {
Object.assign(target, { [key]: source[key] });
Object.assign(target, {[key]: source[key]});
continue;
}
}
@@ -101,10 +112,10 @@ function mergeDeep(target: any, ...sources: any): any {
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
if (!target[key]) Object.assign(target, {[key]: {}});
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
Object.assign(target, {[key]: source[key]});
}
}
}
@@ -114,4 +125,4 @@ function mergeDeep(target: any, ...sources: any): any {
function deepClone(obj: StoreObject): StoreObject {
return JSON.parse(JSON.stringify(obj));
}
}

View File

@@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from "common/trace/timestamp";
import {TimeUtils} from "./time_utils";
import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'common/trace/timestamp';
import {TimeUtils} from './time_utils';
describe("TimeUtils", () => {
describe('TimeUtils', () => {
const MILLISECOND = BigInt(1000000);
const SECOND = BigInt(1000) * MILLISECOND;
const MINUTE = BigInt(60) * SECOND;
const HOUR = BigInt(60) * MINUTE;
const DAY = BigInt(24) * HOUR;
describe("compareFn", () => {
it("throws if timestamps have different type", () => {
describe('compareFn', () => {
it('throws if timestamps have different type', () => {
const real = new RealTimestamp(10n);
const elapsed = new ElapsedTimestamp(10n);
@@ -33,7 +33,7 @@ describe("TimeUtils", () => {
}).toThrow();
});
it("allows to sort arrays", () => {
it('allows to sort arrays', () => {
const array = [
new RealTimestamp(100n),
new RealTimestamp(10n),
@@ -54,109 +54,146 @@ describe("TimeUtils", () => {
});
});
it("nanosecondsToHuman", () => {
expect(TimeUtils.format(new ElapsedTimestamp(0n), true)) .toEqual("0ms");
expect(TimeUtils.format(new ElapsedTimestamp(0n), false)) .toEqual("0ns");
expect(TimeUtils.format(new ElapsedTimestamp(1000n), true)) .toEqual("0ms");
expect(TimeUtils.format(new ElapsedTimestamp(1000n), false)) .toEqual("1000ns");
expect(TimeUtils.format(new ElapsedTimestamp(MILLISECOND-1n), true)).toEqual("0ms");
expect(TimeUtils.format(new ElapsedTimestamp(MILLISECOND), true)).toEqual("1ms");
expect(TimeUtils.format(new ElapsedTimestamp(10n * MILLISECOND), true)).toEqual("10ms");
it('nanosecondsToHuman', () => {
expect(TimeUtils.format(new ElapsedTimestamp(0n), true)).toEqual('0ms');
expect(TimeUtils.format(new ElapsedTimestamp(0n), false)).toEqual('0ns');
expect(TimeUtils.format(new ElapsedTimestamp(1000n), true)).toEqual('0ms');
expect(TimeUtils.format(new ElapsedTimestamp(1000n), false)).toEqual('1000ns');
expect(TimeUtils.format(new ElapsedTimestamp(MILLISECOND - 1n), true)).toEqual('0ms');
expect(TimeUtils.format(new ElapsedTimestamp(MILLISECOND), true)).toEqual('1ms');
expect(TimeUtils.format(new ElapsedTimestamp(10n * MILLISECOND), true)).toEqual('10ms');
expect(TimeUtils.format(new ElapsedTimestamp(SECOND-1n), true)).toEqual("999ms");
expect(TimeUtils.format(new ElapsedTimestamp(SECOND), true)).toEqual("1s0ms");
expect(TimeUtils.format(new ElapsedTimestamp(SECOND + MILLISECOND), true)).toEqual("1s1ms");
expect(TimeUtils.format(new ElapsedTimestamp(SECOND + MILLISECOND), false)).toEqual("1s1ms0ns");
expect(TimeUtils.format(new ElapsedTimestamp(SECOND - 1n), true)).toEqual('999ms');
expect(TimeUtils.format(new ElapsedTimestamp(SECOND), true)).toEqual('1s0ms');
expect(TimeUtils.format(new ElapsedTimestamp(SECOND + MILLISECOND), true)).toEqual('1s1ms');
expect(TimeUtils.format(new ElapsedTimestamp(SECOND + MILLISECOND), false)).toEqual('1s1ms0ns');
expect(TimeUtils.format(new ElapsedTimestamp(MINUTE-1n), true)).toEqual("59s999ms");
expect(TimeUtils.format(new ElapsedTimestamp(MINUTE), true)).toEqual("1m0s0ms");
expect(TimeUtils.format(new ElapsedTimestamp(MINUTE + SECOND + MILLISECOND), true)).toEqual("1m1s1ms");
expect(TimeUtils.format(new ElapsedTimestamp(MINUTE + SECOND + MILLISECOND + 1n), true)).toEqual("1m1s1ms");
expect(TimeUtils.format(new ElapsedTimestamp(MINUTE + SECOND + MILLISECOND + 1n), false)).toEqual("1m1s1ms1ns");
expect(TimeUtils.format(new ElapsedTimestamp(MINUTE - 1n), true)).toEqual('59s999ms');
expect(TimeUtils.format(new ElapsedTimestamp(MINUTE), true)).toEqual('1m0s0ms');
expect(TimeUtils.format(new ElapsedTimestamp(MINUTE + SECOND + MILLISECOND), true)).toEqual(
'1m1s1ms'
);
expect(
TimeUtils.format(new ElapsedTimestamp(MINUTE + SECOND + MILLISECOND + 1n), true)
).toEqual('1m1s1ms');
expect(
TimeUtils.format(new ElapsedTimestamp(MINUTE + SECOND + MILLISECOND + 1n), false)
).toEqual('1m1s1ms1ns');
expect(TimeUtils.format(new ElapsedTimestamp(HOUR-1n), true)).toEqual("59m59s999ms");
expect(TimeUtils.format(new ElapsedTimestamp(HOUR-1n), false)).toEqual("59m59s999ms999999ns");
expect(TimeUtils.format(new ElapsedTimestamp(HOUR), true)).toEqual("1h0m0s0ms");
expect(TimeUtils.format(new ElapsedTimestamp(HOUR + MINUTE + SECOND + MILLISECOND), true)).toEqual("1h1m1s1ms");
expect(TimeUtils.format(new ElapsedTimestamp(HOUR - 1n), true)).toEqual('59m59s999ms');
expect(TimeUtils.format(new ElapsedTimestamp(HOUR - 1n), false)).toEqual('59m59s999ms999999ns');
expect(TimeUtils.format(new ElapsedTimestamp(HOUR), true)).toEqual('1h0m0s0ms');
expect(
TimeUtils.format(new ElapsedTimestamp(HOUR + MINUTE + SECOND + MILLISECOND), true)
).toEqual('1h1m1s1ms');
expect(TimeUtils.format(new ElapsedTimestamp(DAY-1n), true)).toEqual("23h59m59s999ms");
expect(TimeUtils.format(new ElapsedTimestamp(DAY), true)).toEqual("1d0h0m0s0ms");
expect(TimeUtils.format(new ElapsedTimestamp(DAY + HOUR + MINUTE + SECOND + MILLISECOND), true)).toEqual("1d1h1m1s1ms");
expect(TimeUtils.format(new ElapsedTimestamp(DAY - 1n), true)).toEqual('23h59m59s999ms');
expect(TimeUtils.format(new ElapsedTimestamp(DAY), true)).toEqual('1d0h0m0s0ms');
expect(
TimeUtils.format(new ElapsedTimestamp(DAY + HOUR + MINUTE + SECOND + MILLISECOND), true)
).toEqual('1d1h1m1s1ms');
});
it("humanElapsedToNanoseconds", () => {
expect(TimeUtils.parseHumanElapsed("0ns")).toEqual(new ElapsedTimestamp(0n));
expect(TimeUtils.parseHumanElapsed("1000ns")).toEqual(new ElapsedTimestamp(1000n));
expect(TimeUtils.parseHumanElapsed("0ms")).toEqual(new ElapsedTimestamp(0n));
expect(TimeUtils.parseHumanElapsed("1ms")).toEqual(new ElapsedTimestamp(MILLISECOND));
expect(TimeUtils.parseHumanElapsed("10ms")).toEqual(new ElapsedTimestamp(10n * MILLISECOND));
it('humanElapsedToNanoseconds', () => {
expect(TimeUtils.parseHumanElapsed('0ns')).toEqual(new ElapsedTimestamp(0n));
expect(TimeUtils.parseHumanElapsed('1000ns')).toEqual(new ElapsedTimestamp(1000n));
expect(TimeUtils.parseHumanElapsed('0ms')).toEqual(new ElapsedTimestamp(0n));
expect(TimeUtils.parseHumanElapsed('1ms')).toEqual(new ElapsedTimestamp(MILLISECOND));
expect(TimeUtils.parseHumanElapsed('10ms')).toEqual(new ElapsedTimestamp(10n * MILLISECOND));
expect(TimeUtils.parseHumanElapsed("999ms")).toEqual(new ElapsedTimestamp(999n * MILLISECOND));
expect(TimeUtils.parseHumanElapsed("1s")).toEqual(new ElapsedTimestamp(SECOND));
expect(TimeUtils.parseHumanElapsed("1s0ms")).toEqual(new ElapsedTimestamp(SECOND));
expect(TimeUtils.parseHumanElapsed("1s0ms0ns")).toEqual(new ElapsedTimestamp(SECOND));
expect(TimeUtils.parseHumanElapsed("1s0ms1ns")).toEqual(new ElapsedTimestamp(SECOND + 1n));
expect(TimeUtils.parseHumanElapsed("0d1s1ms")).toEqual(new ElapsedTimestamp(SECOND + MILLISECOND));
expect(TimeUtils.parseHumanElapsed('999ms')).toEqual(new ElapsedTimestamp(999n * MILLISECOND));
expect(TimeUtils.parseHumanElapsed('1s')).toEqual(new ElapsedTimestamp(SECOND));
expect(TimeUtils.parseHumanElapsed('1s0ms')).toEqual(new ElapsedTimestamp(SECOND));
expect(TimeUtils.parseHumanElapsed('1s0ms0ns')).toEqual(new ElapsedTimestamp(SECOND));
expect(TimeUtils.parseHumanElapsed('1s0ms1ns')).toEqual(new ElapsedTimestamp(SECOND + 1n));
expect(TimeUtils.parseHumanElapsed('0d1s1ms')).toEqual(
new ElapsedTimestamp(SECOND + MILLISECOND)
);
expect(TimeUtils.parseHumanElapsed("1m0s0ms")).toEqual(new ElapsedTimestamp(MINUTE));
expect(TimeUtils.parseHumanElapsed("1m1s1ms")).toEqual(new ElapsedTimestamp(MINUTE + SECOND + MILLISECOND));
expect(TimeUtils.parseHumanElapsed('1m0s0ms')).toEqual(new ElapsedTimestamp(MINUTE));
expect(TimeUtils.parseHumanElapsed('1m1s1ms')).toEqual(
new ElapsedTimestamp(MINUTE + SECOND + MILLISECOND)
);
expect(TimeUtils.parseHumanElapsed("1h0m")).toEqual(new ElapsedTimestamp(HOUR));
expect(TimeUtils.parseHumanElapsed("1h1m1s1ms")).toEqual(new ElapsedTimestamp(HOUR + MINUTE + SECOND + MILLISECOND));
expect(TimeUtils.parseHumanElapsed('1h0m')).toEqual(new ElapsedTimestamp(HOUR));
expect(TimeUtils.parseHumanElapsed('1h1m1s1ms')).toEqual(
new ElapsedTimestamp(HOUR + MINUTE + SECOND + MILLISECOND)
);
expect(TimeUtils.parseHumanElapsed("1d0s1ms")).toEqual(new ElapsedTimestamp(DAY + MILLISECOND));
expect(TimeUtils.parseHumanElapsed("1d1h1m1s1ms")).toEqual(new ElapsedTimestamp(DAY + HOUR + MINUTE + SECOND + MILLISECOND));
expect(TimeUtils.parseHumanElapsed('1d0s1ms')).toEqual(new ElapsedTimestamp(DAY + MILLISECOND));
expect(TimeUtils.parseHumanElapsed('1d1h1m1s1ms')).toEqual(
new ElapsedTimestamp(DAY + HOUR + MINUTE + SECOND + MILLISECOND)
);
expect(TimeUtils.parseHumanElapsed("1d")).toEqual(new ElapsedTimestamp(DAY));
expect(TimeUtils.parseHumanElapsed("1d1ms")).toEqual(new ElapsedTimestamp(DAY + MILLISECOND));
expect(TimeUtils.parseHumanElapsed('1d')).toEqual(new ElapsedTimestamp(DAY));
expect(TimeUtils.parseHumanElapsed('1d1ms')).toEqual(new ElapsedTimestamp(DAY + MILLISECOND));
});
it("humanToNanoseconds throws on invalid input format", () => {
const invalidFormatError = new Error("Invalid elapsed timestamp format");
expect(() => TimeUtils.parseHumanElapsed("1d1h1m1s0ns1ms") )
.toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanElapsed("1dns") )
.toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanElapsed("100") )
.toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanElapsed("") )
.toThrow(invalidFormatError);
it('humanToNanoseconds throws on invalid input format', () => {
const invalidFormatError = new Error('Invalid elapsed timestamp format');
expect(() => TimeUtils.parseHumanElapsed('1d1h1m1s0ns1ms')).toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanElapsed('1dns')).toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanElapsed('100')).toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanElapsed('')).toThrow(invalidFormatError);
});
it("nanosecondsToHumanReal", () => {
it('nanosecondsToHumanReal', () => {
const NOV_10_2022 = 1668038400000n * MILLISECOND;
expect(TimeUtils.format(new RealTimestamp(0n), true))
.toEqual("1970-01-01T00:00:00.000");
expect(TimeUtils.format(new RealTimestamp(
NOV_10_2022 + 22n * HOUR + 4n * MINUTE + 54n * SECOND + 186n * MILLISECOND + 123212n), true))
.toEqual("2022-11-10T22:04:54.186");
expect(TimeUtils.format(new RealTimestamp(NOV_10_2022), true))
.toEqual("2022-11-10T00:00:00.000");
expect(TimeUtils.format(new RealTimestamp(NOV_10_2022 + 1n), true))
.toEqual("2022-11-10T00:00:00.000");
expect(TimeUtils.format(new RealTimestamp(0n), true)).toEqual('1970-01-01T00:00:00.000');
expect(
TimeUtils.format(
new RealTimestamp(
NOV_10_2022 + 22n * HOUR + 4n * MINUTE + 54n * SECOND + 186n * MILLISECOND + 123212n
),
true
)
).toEqual('2022-11-10T22:04:54.186');
expect(TimeUtils.format(new RealTimestamp(NOV_10_2022), true)).toEqual(
'2022-11-10T00:00:00.000'
);
expect(TimeUtils.format(new RealTimestamp(NOV_10_2022 + 1n), true)).toEqual(
'2022-11-10T00:00:00.000'
);
expect(TimeUtils.format(new RealTimestamp(0n), false))
.toEqual("1970-01-01T00:00:00.000000000");
expect(TimeUtils.format(new RealTimestamp(
NOV_10_2022 + 22n * HOUR + 4n * MINUTE + 54n * SECOND + 186n * MILLISECOND + 123212n), false))
.toEqual("2022-11-10T22:04:54.186123212");
expect(TimeUtils.format(new RealTimestamp(NOV_10_2022), false))
.toEqual("2022-11-10T00:00:00.000000000");
expect(TimeUtils.format(new RealTimestamp(NOV_10_2022 + 1n), false))
.toEqual("2022-11-10T00:00:00.000000001");
expect(TimeUtils.format(new RealTimestamp(0n), false)).toEqual('1970-01-01T00:00:00.000000000');
expect(
TimeUtils.format(
new RealTimestamp(
NOV_10_2022 + 22n * HOUR + 4n * MINUTE + 54n * SECOND + 186n * MILLISECOND + 123212n
),
false
)
).toEqual('2022-11-10T22:04:54.186123212');
expect(TimeUtils.format(new RealTimestamp(NOV_10_2022), false)).toEqual(
'2022-11-10T00:00:00.000000000'
);
expect(TimeUtils.format(new RealTimestamp(NOV_10_2022 + 1n), false)).toEqual(
'2022-11-10T00:00:00.000000001'
);
});
it("humanRealToNanoseconds", () => {
it('humanRealToNanoseconds', () => {
const NOV_10_2022 = 1668038400000n * MILLISECOND;
expect(TimeUtils.parseHumanReal("2022-11-10T22:04:54.186123212"))
.toEqual(new RealTimestamp(NOV_10_2022 + 22n * HOUR + 4n * MINUTE + 54n * SECOND + 186n * MILLISECOND + 123212n));
expect(TimeUtils.parseHumanReal("2022-11-10T22:04:54.186123212Z")).toEqual(new RealTimestamp(1668117894186123212n));
expect(TimeUtils.parseHumanReal("2022-11-10T22:04:54.186000212")).toEqual(new RealTimestamp(1668117894186000212n));
expect(TimeUtils.parseHumanReal("2022-11-10T22:04:54.006000002")).toEqual(new RealTimestamp(1668117894006000002n));
expect(TimeUtils.parseHumanReal("2022-11-10T06:04:54.006000002")).toEqual(new RealTimestamp(1668060294006000002n));
expect(TimeUtils.parseHumanReal('2022-11-10T22:04:54.186123212')).toEqual(
new RealTimestamp(
NOV_10_2022 + 22n * HOUR + 4n * MINUTE + 54n * SECOND + 186n * MILLISECOND + 123212n
)
);
expect(TimeUtils.parseHumanReal('2022-11-10T22:04:54.186123212Z')).toEqual(
new RealTimestamp(1668117894186123212n)
);
expect(TimeUtils.parseHumanReal('2022-11-10T22:04:54.186000212')).toEqual(
new RealTimestamp(1668117894186000212n)
);
expect(TimeUtils.parseHumanReal('2022-11-10T22:04:54.006000002')).toEqual(
new RealTimestamp(1668117894006000002n)
);
expect(TimeUtils.parseHumanReal('2022-11-10T06:04:54.006000002')).toEqual(
new RealTimestamp(1668060294006000002n)
);
});
it("canReverseDateFormatting", () => {
it('canReverseDateFormatting', () => {
let timestamp = new RealTimestamp(1668117894186123212n);
expect(TimeUtils.parseHumanReal(TimeUtils.format(timestamp))).toEqual(timestamp);
@@ -164,41 +201,48 @@ describe("TimeUtils", () => {
expect(TimeUtils.parseHumanElapsed(TimeUtils.format(timestamp))).toEqual(timestamp);
});
it("humanToNanoseconds throws on invalid input format", () => {
const invalidFormatError = new Error("Invalid real timestamp format");
expect(() => TimeUtils.parseHumanReal("23h59m59s999ms5ns") )
.toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanReal("1d") )
.toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanReal("100") )
.toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanReal("06h4m54s, 10 Nov 2022") )
.toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanReal("") )
.toThrow(invalidFormatError);
it('humanToNanoseconds throws on invalid input format', () => {
const invalidFormatError = new Error('Invalid real timestamp format');
expect(() => TimeUtils.parseHumanReal('23h59m59s999ms5ns')).toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanReal('1d')).toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanReal('100')).toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanReal('06h4m54s, 10 Nov 2022')).toThrow(invalidFormatError);
expect(() => TimeUtils.parseHumanReal('')).toThrow(invalidFormatError);
});
it("nano second regex accept all expected inputs", () => {
expect(TimeUtils.NS_TIMESTAMP_REGEX.test("123")).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test("123ns")).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test("123 ns")).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test(" 123 ns ")).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test(" 123 ")).toBeTrue();
it('nano second regex accept all expected inputs', () => {
expect(TimeUtils.NS_TIMESTAMP_REGEX.test('123')).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test('123ns')).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test('123 ns')).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test(' 123 ns ')).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test(' 123 ')).toBeTrue();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test("1a23")).toBeFalse();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test("a123 ns")).toBeFalse();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test("")).toBeFalse();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test('1a23')).toBeFalse();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test('a123 ns')).toBeFalse();
expect(TimeUtils.NS_TIMESTAMP_REGEX.test('')).toBeFalse();
});
it("format real", () => {
expect(TimeUtils.format(Timestamp.from(TimestampType.REAL, 100n, 500n))).toEqual("1970-01-01T00:00:00.000000600");
expect(TimeUtils.format(Timestamp.from(TimestampType.REAL, 100n * MILLISECOND, 500n), true)).toEqual("1970-01-01T00:00:00.100");
it('format real', () => {
expect(TimeUtils.format(Timestamp.from(TimestampType.REAL, 100n, 500n))).toEqual(
'1970-01-01T00:00:00.000000600'
);
expect(
TimeUtils.format(Timestamp.from(TimestampType.REAL, 100n * MILLISECOND, 500n), true)
).toEqual('1970-01-01T00:00:00.100');
});
it("format elapsed", () => {
expect(TimeUtils.format(Timestamp.from(TimestampType.ELAPSED, 100n * MILLISECOND, 500n), true)).toEqual("100ms");
expect(TimeUtils.format(Timestamp.from(TimestampType.ELAPSED, 100n * MILLISECOND), true)).toEqual("100ms");
expect(TimeUtils.format(Timestamp.from(TimestampType.ELAPSED, 100n * MILLISECOND, 500n))).toEqual("100ms0ns");
expect(TimeUtils.format(Timestamp.from(TimestampType.ELAPSED, 100n * MILLISECOND))).toEqual("100ms0ns");
it('format elapsed', () => {
expect(
TimeUtils.format(Timestamp.from(TimestampType.ELAPSED, 100n * MILLISECOND, 500n), true)
).toEqual('100ms');
expect(
TimeUtils.format(Timestamp.from(TimestampType.ELAPSED, 100n * MILLISECOND), true)
).toEqual('100ms');
expect(
TimeUtils.format(Timestamp.from(TimestampType.ELAPSED, 100n * MILLISECOND, 500n))
).toEqual('100ms0ns');
expect(TimeUtils.format(Timestamp.from(TimestampType.ELAPSED, 100n * MILLISECOND))).toEqual(
'100ms0ns'
);
});
});

View File

@@ -14,43 +14,46 @@
* limitations under the License.
*/
import { ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType } from "common/trace/timestamp";
import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'common/trace/timestamp';
export class TimeUtils {
static compareFn(a: Timestamp, b: Timestamp): number {
if (a.getType() !== b.getType()) {
throw new Error("Attempted to compare two timestamps with different type");
throw new Error('Attempted to compare two timestamps with different type');
}
return Number(a.getValueNs() - b.getValueNs());
}
static format(timestamp: Timestamp, hideNs = false): string {
switch (timestamp.getType()) {
case TimestampType.ELAPSED: {
return TimeUtils.nanosecondsToHumanElapsed(timestamp.getValueNs(), hideNs);
}
case TimestampType.REAL: {
return TimeUtils.nanosecondsToHumanReal(timestamp.getValueNs(), hideNs);
}
default: {
throw Error("Unhandled timestamp type");
}
case TimestampType.ELAPSED: {
return TimeUtils.nanosecondsToHumanElapsed(timestamp.getValueNs(), hideNs);
}
case TimestampType.REAL: {
return TimeUtils.nanosecondsToHumanReal(timestamp.getValueNs(), hideNs);
}
default: {
throw Error('Unhandled timestamp type');
}
}
}
private static nanosecondsToHumanElapsed(timestampNanos: number|bigint, hideNs = true): string {
private static nanosecondsToHumanElapsed(timestampNanos: number | bigint, hideNs = true): string {
timestampNanos = BigInt(timestampNanos);
const units = TimeUtils.units;
let leftNanos = timestampNanos;
const parts = units.slice().reverse().map(({nanosInUnit, unit}) => {
let amountOfUnit = BigInt(0);
if (leftNanos >= nanosInUnit) {
amountOfUnit = leftNanos / BigInt(nanosInUnit);
}
leftNanos = leftNanos % BigInt(nanosInUnit);
return `${amountOfUnit}${unit}`;
});
const parts = units
.slice()
.reverse()
.map(({nanosInUnit, unit}) => {
let amountOfUnit = BigInt(0);
if (leftNanos >= nanosInUnit) {
amountOfUnit = leftNanos / BigInt(nanosInUnit);
}
leftNanos = leftNanos % BigInt(nanosInUnit);
return `${amountOfUnit}${unit}`;
});
if (hideNs) {
parts.pop();
@@ -61,38 +64,41 @@ export class TimeUtils {
parts.shift();
}
return parts.join("");
return parts.join('');
}
private static nanosecondsToHumanReal(timestampNanos: number|bigint, hideNs = true): string {
private static nanosecondsToHumanReal(timestampNanos: number | bigint, hideNs = true): string {
timestampNanos = BigInt(timestampNanos);
const ms = timestampNanos / 1000000n;
const extraNanos = timestampNanos % 1000000n;
const formattedTimestamp = new Date(Number(ms)).toISOString().replace("Z", "");
const formattedTimestamp = new Date(Number(ms)).toISOString().replace('Z', '');
if (hideNs) {
return formattedTimestamp;
} else {
return `${formattedTimestamp}${extraNanos.toString().padStart(6, "0")}`;
return `${formattedTimestamp}${extraNanos.toString().padStart(6, '0')}`;
}
}
static parseHumanElapsed(timestampHuman: string): Timestamp {
if (!TimeUtils.HUMAN_ELAPSED_TIMESTAMP_REGEX.test(timestampHuman)) {
throw Error("Invalid elapsed timestamp format");
throw Error('Invalid elapsed timestamp format');
}
const units = TimeUtils.units;
const usedUnits = timestampHuman.split(/[0-9]+/).filter(it => it !== "");
const usedValues = timestampHuman.split(/[a-z]+/).filter(it => it !== "").map(it => parseInt(it));
const usedUnits = timestampHuman.split(/[0-9]+/).filter((it) => it !== '');
const usedValues = timestampHuman
.split(/[a-z]+/)
.filter((it) => it !== '')
.map((it) => parseInt(it));
let ns = BigInt(0);
for (let i = 0; i < usedUnits.length; i++) {
const unit = usedUnits[i];
const value = usedValues[i];
const unitData = units.find(it => it.unit == unit)!;
const unitData = units.find((it) => it.unit == unit)!;
ns += BigInt(unitData.nanosInUnit) * BigInt(value);
}
@@ -101,44 +107,48 @@ export class TimeUtils {
static parseHumanReal(timestampHuman: string): Timestamp {
if (!TimeUtils.HUMAN_REAL_TIMESTAMP_REGEX.test(timestampHuman)) {
throw Error("Invalid real timestamp format");
throw Error('Invalid real timestamp format');
}
// Add trailing Z if it isn't there yet
if (timestampHuman[timestampHuman.length - 1] != "Z") {
timestampHuman += "Z";
if (timestampHuman[timestampHuman.length - 1] != 'Z') {
timestampHuman += 'Z';
}
// Date.parse only considers up to millisecond precision
let nanoSeconds = 0;
if (timestampHuman.includes(".")) {
const milliseconds = timestampHuman.split(".")[1].replace("Z", "");
nanoSeconds = parseInt(milliseconds.padEnd(9, "0").slice(3));
if (timestampHuman.includes('.')) {
const milliseconds = timestampHuman.split('.')[1].replace('Z', '');
nanoSeconds = parseInt(milliseconds.padEnd(9, '0').slice(3));
}
return new RealTimestamp(BigInt(Date.parse(timestampHuman)) * BigInt(TimeUtils.TO_NANO["ms"]) + BigInt(nanoSeconds));
return new RealTimestamp(
BigInt(Date.parse(timestampHuman)) * BigInt(TimeUtils.TO_NANO['ms']) + BigInt(nanoSeconds)
);
}
static TO_NANO = {
"ns": 1,
"ms": 1000000,
"s": 1000000 * 1000,
"m": 1000000 * 1000 * 60,
"h": 1000000 * 1000 * 60 * 60,
"d": 1000000 * 1000 * 60 * 60 * 24
ns: 1,
ms: 1000000,
s: 1000000 * 1000,
m: 1000000 * 1000 * 60,
h: 1000000 * 1000 * 60 * 60,
d: 1000000 * 1000 * 60 * 60 * 24,
};
static units = [
{nanosInUnit: TimeUtils.TO_NANO["ns"], unit: "ns"},
{nanosInUnit: TimeUtils.TO_NANO["ms"], unit: "ms"},
{nanosInUnit: TimeUtils.TO_NANO["s"], unit: "s"},
{nanosInUnit: TimeUtils.TO_NANO["m"], unit: "m"},
{nanosInUnit: TimeUtils.TO_NANO["h"], unit: "h"},
{nanosInUnit: TimeUtils.TO_NANO["d"], unit: "d"},
{nanosInUnit: TimeUtils.TO_NANO['ns'], unit: 'ns'},
{nanosInUnit: TimeUtils.TO_NANO['ms'], unit: 'ms'},
{nanosInUnit: TimeUtils.TO_NANO['s'], unit: 's'},
{nanosInUnit: TimeUtils.TO_NANO['m'], unit: 'm'},
{nanosInUnit: TimeUtils.TO_NANO['h'], unit: 'h'},
{nanosInUnit: TimeUtils.TO_NANO['d'], unit: 'd'},
];
// (?=.) checks there is at least one character with a lookahead match
static readonly HUMAN_ELAPSED_TIMESTAMP_REGEX = /^(?=.)([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s)?([0-9]+ms)?([0-9]+ns)?$/;
static readonly HUMAN_REAL_TIMESTAMP_REGEX = /^[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}([0-9]{6})?Z?$/;
static readonly HUMAN_ELAPSED_TIMESTAMP_REGEX =
/^(?=.)([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s)?([0-9]+ms)?([0-9]+ns)?$/;
static readonly HUMAN_REAL_TIMESTAMP_REGEX =
/^[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}([0-9]{6})?Z?$/;
static readonly NS_TIMESTAMP_REGEX = /^\s*[0-9]+(\s?ns)?\s*$/;
}

View File

@@ -22,9 +22,8 @@ interface TreeNode {
type FilterType = (node: TreeNode | undefined | null) => boolean;
class TreeUtils
{
public static findDescendantNode(node: TreeNode, isTargetNode: FilterType): TreeNode|undefined {
class TreeUtils {
public static findDescendantNode(node: TreeNode, isTargetNode: FilterType): TreeNode | undefined {
if (isTargetNode(node)) {
return node;
}
@@ -43,7 +42,7 @@ class TreeUtils
return undefined;
}
public static findAncestorNode(node: TreeNode, isTargetNode: FilterType): TreeNode|undefined {
public static findAncestorNode(node: TreeNode, isTargetNode: FilterType): TreeNode | undefined {
let ancestor = node.parent;
while (ancestor && !isTargetNode(ancestor)) {
@@ -54,24 +53,26 @@ class TreeUtils
}
public static makeNodeFilter(filterString: string): FilterType {
const filterStrings = filterString.split(",");
const filterStrings = filterString.split(',');
const positive: any[] = [];
const negative: any[] = [];
filterStrings.forEach((f) => {
f = f.trim();
if (f.startsWith("!")) {
const regex = new RegExp(f.substring(1), "i");
if (f.startsWith('!')) {
const regex = new RegExp(f.substring(1), 'i');
negative.push((s: any) => !regex.test(s));
} else {
const regex = new RegExp(f, "i");
const regex = new RegExp(f, 'i');
positive.push((s: any) => regex.test(s));
}
});
const filter = (item: TreeNode | undefined | null) => {
if (item) {
const apply = (f: any) => f(`${item.name}`);
return (positive.length === 0 || positive.some(apply)) &&
(negative.length === 0 || negative.every(apply));
return (
(positive.length === 0 || positive.some(apply)) &&
(negative.length === 0 || negative.every(apply))
);
}
return false;
};

View File

@@ -14,31 +14,27 @@
* limitations under the License.
*/
import {Message, MessageBugReport, MessagePong, MessageTimestamp, MessageType} from "./messages";
import {OriginAllowList} from "./origin_allow_list";
import {RealTimestamp} from "common/trace/timestamp";
import {FunctionUtils} from "common/utils/function_utils";
import {RemoteBugreportReceiver, OnBugreportReceived} from "interfaces/remote_bugreport_receiver";
import {RemoteTimestampReceiver, OnTimestampReceived} from "interfaces/remote_timestamp_receiver";
import {RemoteTimestampSender} from "interfaces/remote_timestamp_sender";
import {RealTimestamp} from 'common/trace/timestamp';
import {FunctionUtils} from 'common/utils/function_utils';
import {OnBugreportReceived, RemoteBugreportReceiver} from 'interfaces/remote_bugreport_receiver';
import {OnTimestampReceived, RemoteTimestampReceiver} from 'interfaces/remote_timestamp_receiver';
import {RemoteTimestampSender} from 'interfaces/remote_timestamp_sender';
import {Message, MessageBugReport, MessagePong, MessageTimestamp, MessageType} from './messages';
import {OriginAllowList} from './origin_allow_list';
class RemoteTool {
constructor(
public readonly window: Window,
public readonly origin: string) {
}
constructor(public readonly window: Window, public readonly origin: string) {}
}
export class CrossToolProtocol implements
RemoteBugreportReceiver,
RemoteTimestampReceiver,
RemoteTimestampSender {
export class CrossToolProtocol
implements RemoteBugreportReceiver, RemoteTimestampReceiver, RemoteTimestampSender
{
private remoteTool?: RemoteTool;
private onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
private onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING;
constructor() {
window.addEventListener("message", async (event) => {
window.addEventListener('message', async (event) => {
await this.onMessageReceived(event);
});
}
@@ -58,13 +54,18 @@ export class CrossToolProtocol implements
const message = new MessageTimestamp(timestamp.getValueNs());
this.remoteTool.window.postMessage(message, this.remoteTool.origin);
console.log("Cross-tool protocol sent timestamp message:", message);
console.log('Cross-tool protocol sent timestamp message:', message);
}
private async onMessageReceived(event: MessageEvent) {
if (!OriginAllowList.isAllowed(event.origin)) {
console.log("Cross-tool protocol ignoring message from non-allowed origin.",
"Origin:", event.origin, "Message:", event.data);
console.log(
'Cross-tool protocol ignoring message from non-allowed origin.',
'Origin:',
event.origin,
'Message:',
event.data
);
return;
}
@@ -77,35 +78,34 @@ export class CrossToolProtocol implements
this.remoteTool = new RemoteTool(event.source as Window, event.origin);
}
switch(message.type) {
case MessageType.PING:
console.log("Cross-tool protocol received ping message:", message);
(event.source as Window).postMessage(new MessagePong(), event.origin);
break;
case MessageType.PONG:
console.log("Cross-tool protocol received unexpected pong message:", message);
break;
case MessageType.BUGREPORT:
console.log("Cross-tool protocol received bugreport message:", message);
await this.onMessageBugreportReceived(message as MessageBugReport);
break;
case MessageType.TIMESTAMP:
console.log("Cross-tool protocol received timestamp message:", message);
this.onMessageTimestampReceived(message as MessageTimestamp);
break;
case MessageType.FILES:
console.log("Cross-tool protocol received unexpected files message", message);
break;
default:
console.log("Cross-tool protocol received unsupported message type:", message);
break;
switch (message.type) {
case MessageType.PING:
console.log('Cross-tool protocol received ping message:', message);
(event.source as Window).postMessage(new MessagePong(), event.origin);
break;
case MessageType.PONG:
console.log('Cross-tool protocol received unexpected pong message:', message);
break;
case MessageType.BUGREPORT:
console.log('Cross-tool protocol received bugreport message:', message);
await this.onMessageBugreportReceived(message as MessageBugReport);
break;
case MessageType.TIMESTAMP:
console.log('Cross-tool protocol received timestamp message:', message);
this.onMessageTimestampReceived(message as MessageTimestamp);
break;
case MessageType.FILES:
console.log('Cross-tool protocol received unexpected files message', message);
break;
default:
console.log('Cross-tool protocol received unsupported message type:', message);
break;
}
}
private async onMessageBugreportReceived(message: MessageBugReport) {
const timestamp = message.timestampNs !== undefined
? new RealTimestamp(message.timestampNs)
: undefined;
const timestamp =
message.timestampNs !== undefined ? new RealTimestamp(message.timestampNs) : undefined;
this.onBugreportReceived(message.file, timestamp);
}

View File

@@ -14,16 +14,15 @@
* limitations under the License.
*/
import {RealTimestamp} from "common/trace/timestamp";
import {FunctionUtils} from "common/utils/function_utils";
import {RemoteBugreportReceiver, OnBugreportReceived} from "interfaces/remote_bugreport_receiver";
import {RemoteTimestampReceiver, OnTimestampReceived} from "interfaces/remote_timestamp_receiver";
import {RemoteTimestampSender} from "interfaces/remote_timestamp_sender";
import {RealTimestamp} from 'common/trace/timestamp';
import {FunctionUtils} from 'common/utils/function_utils';
import {OnBugreportReceived, RemoteBugreportReceiver} from 'interfaces/remote_bugreport_receiver';
import {OnTimestampReceived, RemoteTimestampReceiver} from 'interfaces/remote_timestamp_receiver';
import {RemoteTimestampSender} from 'interfaces/remote_timestamp_sender';
export class CrossToolProtocolStub implements
RemoteBugreportReceiver,
RemoteTimestampReceiver,
RemoteTimestampSender {
export class CrossToolProtocolStub
implements RemoteBugreportReceiver, RemoteTimestampReceiver, RemoteTimestampSender
{
onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING;

View File

@@ -38,28 +38,17 @@ export class MessagePong implements Message {
export class MessageBugReport implements Message {
type = MessageType.BUGREPORT;
constructor(
public file: File,
public timestampNs?: bigint,
public issueId?: string
) {}
constructor(public file: File, public timestampNs?: bigint, public issueId?: string) {}
}
export class MessageTimestamp implements Message {
type = MessageType.TIMESTAMP;
constructor(
public timestampNs: bigint,
public sections?: string[]
) {}
constructor(public timestampNs: bigint, public sections?: string[]) {}
}
export class MessageFiles implements Message {
type = MessageType.FILES;
constructor(
public files: File[],
public timestampNs?: bigint,
public issueId?: string
) {}
constructor(public files: File[], public timestampNs?: bigint, public issueId?: string) {}
}

View File

@@ -14,41 +14,41 @@
* limitations under the License.
*/
import {OriginAllowList} from "./origin_allow_list";
import {OriginAllowList} from './origin_allow_list';
describe("OriginAllowList", () => {
describe("dev mode", () => {
const mode = "DEV" as const;
describe('OriginAllowList', () => {
describe('dev mode', () => {
const mode = 'DEV' as const;
it("allows localhost", () => {
expect(OriginAllowList.isAllowed("http://localhost:8081", mode)).toBeTrue();
expect(OriginAllowList.isAllowed("https://localhost:8081", mode)).toBeTrue();
it('allows localhost', () => {
expect(OriginAllowList.isAllowed('http://localhost:8081', mode)).toBeTrue();
expect(OriginAllowList.isAllowed('https://localhost:8081', mode)).toBeTrue();
});
});
describe("prod mode", () => {
const mode = "PROD" as const;
describe('prod mode', () => {
const mode = 'PROD' as const;
it("allows google.com", () => {
expect(OriginAllowList.isAllowed("https://google.com", mode)).toBeTrue();
expect(OriginAllowList.isAllowed("https://subdomain.google.com", mode)).toBeTrue();
it('allows google.com', () => {
expect(OriginAllowList.isAllowed('https://google.com', mode)).toBeTrue();
expect(OriginAllowList.isAllowed('https://subdomain.google.com', mode)).toBeTrue();
});
it("denies pseudo google.com", () => {
expect(OriginAllowList.isAllowed("https://evilgoogle.com", mode)).toBeFalse();
expect(OriginAllowList.isAllowed("https://evil.com/google.com", mode)).toBeFalse();
it('denies pseudo google.com', () => {
expect(OriginAllowList.isAllowed('https://evilgoogle.com', mode)).toBeFalse();
expect(OriginAllowList.isAllowed('https://evil.com/google.com', mode)).toBeFalse();
});
it("allows googleplex.com", () => {
expect(OriginAllowList.isAllowed("https://googleplex.com", mode)).toBeTrue();
expect(OriginAllowList.isAllowed("https://subdomain.googleplex.com", mode))
.toBeTrue();
it('allows googleplex.com', () => {
expect(OriginAllowList.isAllowed('https://googleplex.com', mode)).toBeTrue();
expect(OriginAllowList.isAllowed('https://subdomain.googleplex.com', mode)).toBeTrue();
});
it("denies pseudo googleplex.com", () => {
expect(OriginAllowList.isAllowed("https://evilgoogleplex.com", mode)).toBeFalse();
expect(OriginAllowList.isAllowed("https://evil.com/subdomain.googleplex.com", mode))
.toBeFalse();
it('denies pseudo googleplex.com', () => {
expect(OriginAllowList.isAllowed('https://evilgoogleplex.com', mode)).toBeFalse();
expect(
OriginAllowList.isAllowed('https://evil.com/subdomain.googleplex.com', mode)
).toBeFalse();
});
});
});

View File

@@ -14,17 +14,17 @@
* limitations under the License.
*/
import {globalConfig} from "common/utils/global_config";
import {globalConfig} from 'common/utils/global_config';
export class OriginAllowList {
private static readonly ALLOW_LIST_PROD = [
new RegExp("^https://([^\\/]*\\.)*googleplex\\.com$"),
new RegExp("^https://([^\\/]*\\.)*google\\.com$"),
new RegExp('^https://([^\\/]*\\.)*googleplex\\.com$'),
new RegExp('^https://([^\\/]*\\.)*google\\.com$'),
];
private static readonly ALLOW_LIST_DEV = [
...OriginAllowList.ALLOW_LIST_PROD,
new RegExp("^(http|https)://localhost:8081$"), // remote tool mock
new RegExp('^(http|https)://localhost:8081$'), // remote tool mock
];
static isAllowed(originUrl: string, mode = globalConfig.MODE): boolean {
@@ -40,13 +40,13 @@ export class OriginAllowList {
}
private static getList(mode: typeof globalConfig.MODE): RegExp[] {
switch(mode) {
case "DEV":
return OriginAllowList.ALLOW_LIST_DEV;
case "PROD":
return OriginAllowList.ALLOW_LIST_PROD;
default:
throw new Error(`Unhandled mode: ${globalConfig.MODE}`);
switch (mode) {
case 'DEV':
return OriginAllowList.ALLOW_LIST_DEV;
case 'PROD':
return OriginAllowList.ALLOW_LIST_PROD;
default:
throw new Error(`Unhandled mode: ${globalConfig.MODE}`);
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {RealTimestamp} from "common/trace/timestamp";
import {RealTimestamp} from 'common/trace/timestamp';
export type OnBugreportReceived = (bugreport: File, timestamp?: RealTimestamp) => Promise<void>;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {RealTimestamp} from "common/trace/timestamp";
import {RealTimestamp} from 'common/trace/timestamp';
export type OnTimestampReceived = (timestamp: RealTimestamp) => void;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {RealTimestamp} from "common/trace/timestamp";
import {RealTimestamp} from 'common/trace/timestamp';
export interface RemoteTimestampSender {
sendTimestamp(timestamp: RealTimestamp): void;

View File

@@ -14,8 +14,8 @@
* limitations under the License.
*/
import {Timestamp} from "common/trace/timestamp";
import {Timestamp} from 'common/trace/timestamp';
export interface TimestampChangeListener {
onCurrentTimestampChanged(timestamp: Timestamp|undefined): void;
onCurrentTimestampChanged(timestamp: Timestamp | undefined): void;
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {Viewer} from "viewers/viewer";
import {Viewer} from 'viewers/viewer';
export interface TraceDataListener {
onTraceDataUnloaded(): void;

View File

@@ -13,23 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import "zone.js";
import "zone.js/testing";
import {TestBed} from "@angular/core/testing";
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";
// organize-imports-ignore
import 'zone.js';
import 'zone.js/testing';
import {TestBed} from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
context(
path: string,
deep?: boolean,
filter?: RegExp
): {
<T>(id: string): T;
keys(): string[];
};
};
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// load all tests of Angular components
const context = require.context("./", true, /\.component\.spec\.ts$/);
const context = require.context('./', true, /\.component\.spec\.ts$/);
context.keys().forEach(context);

View File

@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {AppModule} from "./app/app.module";
import {globalConfig} from "common/utils/global_config";
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {globalConfig} from 'common/utils/global_config';
import {AppModule} from './app/app.module';
globalConfig.set({
MODE: "DEV",
MODE: 'DEV',
});
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));

View File

@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {enableProdMode} from "@angular/core";
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {AppModule} from "./app/app.module";
import {enableProdMode} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app/app.module';
enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
import {ArrayUtils} from "common/utils/array_utils";
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {Trace, TraceFile} from "common/trace/trace";
import {TraceType} from "common/trace/trace_type";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {Trace, TraceFile} from 'common/trace/trace';
import {TraceType} from 'common/trace/trace_type';
import {ArrayUtils} from 'common/utils/array_utils';
abstract class Parser {
protected trace: TraceFile;
@@ -32,9 +32,11 @@ abstract class Parser {
const traceBuffer = new Uint8Array(await this.trace.file.arrayBuffer());
const magicNumber = this.getMagicNumber();
if (magicNumber !== undefined)
{
const bufferContainsMagicNumber = ArrayUtils.equal(magicNumber, traceBuffer.slice(0, magicNumber.length));
if (magicNumber !== undefined) {
const bufferContainsMagicNumber = ArrayUtils.equal(
magicNumber,
traceBuffer.slice(0, magicNumber.length)
);
if (!bufferContainsMagicNumber) {
throw TypeError("buffer doesn't contain expected magic number");
}
@@ -68,7 +70,7 @@ abstract class Parser {
}
for (const fieldName in protoObj.$type.fields) {
if (protoObj.$type.fields.hasOwnProperty(fieldName)) {
if (Object.prototype.hasOwnProperty.call(protoObj.$type.fields, fieldName)) {
const fieldProperties = protoObj.$type.fields[fieldName];
const field = protoObj[fieldName];
@@ -84,7 +86,8 @@ abstract class Parser {
}
if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
protoObj[fieldName] =
fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
continue;
}
this.addDefaultProtoFields(protoObj[fieldName]);
@@ -99,11 +102,11 @@ abstract class Parser {
public getTrace(): Trace {
return {
type: this.getTraceType(),
traceFile: this.trace
traceFile: this.trace,
};
}
public getTimestamps(type: TimestampType): undefined|Timestamp[] {
public getTimestamps(type: TimestampType): undefined | Timestamp[] {
return this.timestamps.get(type);
}
@@ -115,7 +118,7 @@ abstract class Parser {
//TODO (b/256564627):
// - factor out timestamp search policy. Receive index parameter instead.
// - make async for possible lazy disk reads in the future
public getTraceEntry(timestamp: Timestamp): undefined|any {
public getTraceEntry(timestamp: Timestamp): undefined | any {
const timestamps = this.getTimestamps(timestamp.getType());
if (timestamps === undefined) {
throw TypeError(`Timestamps with type "${timestamp.getType()}" not available`);
@@ -128,10 +131,14 @@ abstract class Parser {
return this.processDecodedEntry(index, timestamp.getType(), this.decodedEntries[index]);
}
protected abstract getMagicNumber(): undefined|number[];
protected abstract getMagicNumber(): undefined | number[];
protected abstract decodeTrace(trace: Uint8Array): any[];
protected abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined|Timestamp;
protected abstract processDecodedEntry(index: number, timestampType: TimestampType, decodedEntry: any): any;
protected abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined | Timestamp;
protected abstract processDecodedEntry(
index: number,
timestampType: TimestampType,
decodedEntry: any
): any;
}
export {Parser};

View File

@@ -13,75 +13,71 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceType} from "common/trace/trace_type";
import {Parser} from "./parser";
import {UnitTestUtils} from "test/unit/utils";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {UnitTestUtils} from 'test/unit/utils';
import {Parser} from './parser';
describe("ParserAccessibility", () => {
describe("trace with elapsed + real timestamp", () => {
describe('ParserAccessibility', () => {
describe('trace with elapsed + real timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_and_real_timestamp/Accessibility.pb");
parser = await UnitTestUtils.getParser('traces/elapsed_and_real_timestamp/Accessibility.pb');
});
it("has expected trace type", () => {
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.ACCESSIBILITY);
});
it("provides elapsed timestamps", () => {
it('provides elapsed timestamps', () => {
const expected = [
new Timestamp(TimestampType.ELAPSED, 14499089524n),
new Timestamp(TimestampType.ELAPSED, 14499599656n),
new Timestamp(TimestampType.ELAPSED, 14953120693n),
];
expect(parser.getTimestamps(TimestampType.ELAPSED)!.slice(0, 3))
.toEqual(expected);
expect(parser.getTimestamps(TimestampType.ELAPSED)!.slice(0, 3)).toEqual(expected);
});
it("provides real timestamps", () => {
it('provides real timestamps', () => {
const expected = [
new Timestamp(TimestampType.REAL, 1659107089100052652n),
new Timestamp(TimestampType.REAL, 1659107089100562784n),
new Timestamp(TimestampType.REAL, 1659107089554083821n),
];
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3))
.toEqual(expected);
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it("retrieves trace entry from elapsed timestamp", () => {
it('retrieves trace entry from elapsed timestamp', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 14499599656n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
.toEqual(14499599656n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos)).toEqual(14499599656n);
});
it("retrieves trace entry from real timestamp", () => {
it('retrieves trace entry from real timestamp', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107089100562784n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
.toEqual(14499599656n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos)).toEqual(14499599656n);
});
});
describe("trace with elapsed (only) timestamp", () => {
describe('trace with elapsed (only) timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_timestamp/Accessibility.pb");
parser = await UnitTestUtils.getParser('traces/elapsed_timestamp/Accessibility.pb');
});
it("has expected trace type", () => {
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.ACCESSIBILITY);
});
it("provides elapsed timestamps", () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0])
.toEqual(new Timestamp(TimestampType.ELAPSED, 850297444302n));
it('provides elapsed timestamps', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0]).toEqual(
new Timestamp(TimestampType.ELAPSED, 850297444302n)
);
});
it("doesn't provide real timestamps", () => {
expect(parser.getTimestamps(TimestampType.REAL))
.toEqual(undefined);
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
});
});
});

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceFile} from "common/trace/trace";
import {TraceType} from "common/trace/trace_type";
import {Parser} from "./parser";
import {AccessibilityTraceFileProto} from "./proto_types";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceFile} from 'common/trace/trace';
import {TraceType} from 'common/trace/trace_type';
import {Parser} from './parser';
import {AccessibilityTraceFileProto} from './proto_types';
class ParserAccessibility extends Parser {
constructor(trace: TraceFile) {
@@ -36,21 +36,22 @@ class ParserAccessibility extends Parser {
override decodeTrace(buffer: Uint8Array): any[] {
const decoded = <any>AccessibilityTraceFileProto.decode(buffer);
if (Object.prototype.hasOwnProperty.call(decoded, "realToElapsedTimeOffsetNanos")) {
if (Object.prototype.hasOwnProperty.call(decoded, 'realToElapsedTimeOffsetNanos')) {
this.realToElapsedTimeOffsetNs = BigInt(decoded.realToElapsedTimeOffsetNanos);
}
else {
} else {
this.realToElapsedTimeOffsetNs = undefined;
}
return decoded.entry;
}
override getTimestamp(type: TimestampType, entryProto: any): undefined|Timestamp {
override getTimestamp(type: TimestampType, entryProto: any): undefined | Timestamp {
if (type === TimestampType.ELAPSED) {
return new Timestamp(type, BigInt(entryProto.elapsedRealtimeNanos));
}
else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) {
return new Timestamp(type, this.realToElapsedTimeOffsetNs + BigInt(entryProto.elapsedRealtimeNanos));
} else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) {
return new Timestamp(
type,
this.realToElapsedTimeOffsetNs + BigInt(entryProto.elapsedRealtimeNanos)
);
}
return undefined;
}
@@ -59,7 +60,7 @@ class ParserAccessibility extends Parser {
return entryProto;
}
private realToElapsedTimeOffsetNs: undefined|bigint;
private realToElapsedTimeOffsetNs: undefined | bigint;
private static readonly MAGIC_NUMBER = [0x09, 0x41, 0x31, 0x31, 0x59, 0x54, 0x52, 0x41, 0x43]; // .A11YTRAC
}

View File

@@ -13,26 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {Parser} from "./parser";
import {CommonTestUtils} from "test/common/utils";
import {UnitTestUtils} from "test/unit/utils";
import {ParserFactory} from "./parser_factory";
import {TraceFile} from "common/trace/trace";
import {TraceType} from "common/trace/trace_type";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceFile} from 'common/trace/trace';
import {TraceType} from 'common/trace/trace_type';
import {CommonTestUtils} from 'test/common/utils';
import {UnitTestUtils} from 'test/unit/utils';
import {Parser} from './parser';
import {ParserFactory} from './parser_factory';
describe("Parser", () => {
it("is robust to empty trace file", async () => {
const trace = new TraceFile(
await CommonTestUtils.getFixtureFile("traces/empty.pb"),
undefined
);
describe('Parser', () => {
it('is robust to empty trace file', async () => {
const trace = new TraceFile(await CommonTestUtils.getFixtureFile('traces/empty.pb'), undefined);
const [parsers, errors] = await new ParserFactory().createParsers([trace]);
expect(parsers.length).toEqual(0);
});
it("is robust to trace with no entries", async () => {
const parser = await UnitTestUtils.getParser("traces/no_entries_InputMethodClients.pb");
it('is robust to trace with no entries', async () => {
const parser = await UnitTestUtils.getParser('traces/no_entries_InputMethodClients.pb');
expect(parser.getTraceType()).toEqual(TraceType.INPUT_METHOD_CLIENTS);
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual([]);
@@ -45,93 +42,96 @@ describe("Parser", () => {
expect(parser.getTraceEntry(timestampReal)).toBeUndefined();
});
describe("real timestamp", () => {
describe('real timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_and_real_timestamp/WindowManager.pb");
parser = await UnitTestUtils.getParser('traces/elapsed_and_real_timestamp/WindowManager.pb');
});
it("provides timestamps", () => {
it('provides timestamps', () => {
const expected = [
new Timestamp(TimestampType.REAL, 1659107089075566202n),
new Timestamp(TimestampType.REAL, 1659107089999048990n),
new Timestamp(TimestampType.REAL, 1659107090010194213n)
new Timestamp(TimestampType.REAL, 1659107090010194213n),
];
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3))
.toEqual(expected);
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it("retrieves trace entry (no timestamp matches)", () => {
it('retrieves trace entry (no timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107089075566201n);
expect(parser.getTraceEntry(timestamp))
.toEqual(undefined);
expect(parser.getTraceEntry(timestamp)).toEqual(undefined);
});
it("retrieves trace entry (equal timestamp matches)", () => {
it('retrieves trace entry (equal timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107089075566202n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.unixNanos.toString()))
.toEqual(1659107089075566202n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.unixNanos.toString())).toEqual(
1659107089075566202n
);
});
it("retrieves trace entry (equal timestamp matches)", () => {
it('retrieves trace entry (equal timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107089999048990n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.unixNanos.toString()))
.toEqual(1659107089999048990n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.unixNanos.toString())).toEqual(
1659107089999048990n
);
});
it("retrieves trace entry (lower timestamp matches)", () => {
it('retrieves trace entry (lower timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107089999048991n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.unixNanos.toString()))
.toEqual(1659107089999048990n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.unixNanos.toString())).toEqual(
1659107089999048990n
);
});
});
describe("elapsed timestamp", () => {
describe('elapsed timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_timestamp/WindowManager.pb");
parser = await UnitTestUtils.getParser('traces/elapsed_timestamp/WindowManager.pb');
});
it("provides timestamps", () => {
it('provides timestamps', () => {
const expected = [
new Timestamp(TimestampType.ELAPSED, 850254319343n),
new Timestamp(TimestampType.ELAPSED, 850763506110n),
new Timestamp(TimestampType.ELAPSED, 850782750048n)
new Timestamp(TimestampType.ELAPSED, 850782750048n),
];
expect(parser.getTimestamps(TimestampType.ELAPSED))
.toEqual(expected);
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(expected);
});
it("retrieves trace entry (no timestamp matches)", () => {
it('retrieves trace entry (no timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 850254319342n);
expect(parser.getTraceEntry(timestamp))
.toEqual(undefined);
expect(parser.getTraceEntry(timestamp)).toEqual(undefined);
});
it("retrieves trace entry (equal timestamp matches)", () => {
it('retrieves trace entry (equal timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 850254319343n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.elapsedNanos.toString()))
.toEqual(850254319343n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.elapsedNanos.toString())).toEqual(
850254319343n
);
});
it("retrieves trace entry (equal timestamp matches)", () => {
it('retrieves trace entry (equal timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 850763506110n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.elapsedNanos.toString()))
.toEqual(850763506110n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.elapsedNanos.toString())).toEqual(
850763506110n
);
});
it("retrieves trace entry (lower timestamp matches)", () => {
it('retrieves trace entry (lower timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 850254319344n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.elapsedNanos.toString()))
.toEqual(850254319343n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.elapsedNanos.toString())).toEqual(
850254319343n
);
});
it("retrieves trace entry (equal timestamp matches)", () => {
it('retrieves trace entry (equal timestamp matches)', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 850763506111n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.elapsedNanos.toString()))
.toEqual(850763506110n);
expect(BigInt(parser.getTraceEntry(timestamp)!.timestamp.elapsedNanos.toString())).toEqual(
850763506110n
);
});
});
});

View File

@@ -14,21 +14,21 @@
* limitations under the License.
*/
import {TraceType} from "common/trace/trace_type";
import {TraceFile} from "common/trace/trace";
import {FunctionUtils, OnProgressUpdateType} from "common/utils/function_utils";
import {Parser} from "./parser";
import {ParserAccessibility} from "./parser_accessibility";
import {ParserInputMethodClients} from "./parser_input_method_clients";
import {ParserInputMethodManagerService} from "./parser_input_method_manager_service";
import {ParserInputMethodService} from "./parser_input_method_service";
import {ParserProtoLog} from "./parser_protolog";
import {ParserScreenRecording} from "./parser_screen_recording";
import {ParserScreenRecordingLegacy} from "./parser_screen_recording_legacy";
import {ParserSurfaceFlinger} from "./parser_surface_flinger";
import {ParserTransactions} from "./parser_transactions";
import {ParserWindowManager} from "./parser_window_manager";
import {ParserWindowManagerDump} from "./parser_window_manager_dump";
import {TraceFile} from 'common/trace/trace';
import {TraceType} from 'common/trace/trace_type';
import {FunctionUtils, OnProgressUpdateType} from 'common/utils/function_utils';
import {Parser} from './parser';
import {ParserAccessibility} from './parser_accessibility';
import {ParserInputMethodClients} from './parser_input_method_clients';
import {ParserInputMethodManagerService} from './parser_input_method_manager_service';
import {ParserInputMethodService} from './parser_input_method_service';
import {ParserProtoLog} from './parser_protolog';
import {ParserScreenRecording} from './parser_screen_recording';
import {ParserScreenRecordingLegacy} from './parser_screen_recording_legacy';
import {ParserSurfaceFlinger} from './parser_surface_flinger';
import {ParserTransactions} from './parser_transactions';
import {ParserWindowManager} from './parser_window_manager';
import {ParserWindowManagerDump} from './parser_window_manager_dump';
export class ParserFactory {
static readonly PARSERS = [
@@ -49,8 +49,8 @@ export class ParserFactory {
async createParsers(
traceFiles: TraceFile[],
onProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING):
Promise<[Parser[], ParserError[]]> {
onProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING
): Promise<[Parser[], ParserError[]]> {
const errors: ParserError[] = [];
if (traceFiles.length === 0) {
@@ -69,8 +69,7 @@ export class ParserFactory {
this.parsers.set(parser.getTraceType(), parser);
}
break;
}
catch(error) {
} catch (error) {
// skip current parser
}
}
@@ -80,7 +79,7 @@ export class ParserFactory {
errors.push(new ParserError(ParserErrorType.UNSUPPORTED_FORMAT, traceFile.file));
}
onProgressUpdate(100 * (index + 1) / traceFiles.length);
onProgressUpdate((100 * (index + 1)) / traceFiles.length);
}
return [Array.from(this.parsers.values()), errors];
@@ -89,30 +88,42 @@ export class ParserFactory {
private shouldUseParser(newParser: Parser, errors: ParserError[]): boolean {
const oldParser = this.parsers.get(newParser.getTraceType());
if (!oldParser) {
console.log(`Loaded trace ${newParser.getTrace().traceFile.file.name} (trace type: ${newParser.getTraceType()})`);
console.log(
`Loaded trace ${
newParser.getTrace().traceFile.file.name
} (trace type: ${newParser.getTraceType()})`
);
return true;
}
if (newParser.getEntriesLength() > oldParser.getEntriesLength()) {
console.log(
`Loaded trace ${newParser.getTrace().traceFile.file.name} (trace type: ${newParser.getTraceType()}).` +
` Replace trace ${oldParser.getTrace().traceFile.file.name}`
`Loaded trace ${
newParser.getTrace().traceFile.file.name
} (trace type: ${newParser.getTraceType()}).` +
` Replace trace ${oldParser.getTrace().traceFile.file.name}`
);
errors.push(
new ParserError(
ParserErrorType.OVERRIDE, oldParser.getTrace().traceFile.file, oldParser.getTraceType()
ParserErrorType.OVERRIDE,
oldParser.getTrace().traceFile.file,
oldParser.getTraceType()
)
);
return true;
}
console.log(
`Skipping trace ${newParser.getTrace().traceFile.file.name} (trace type: ${newParser.getTraceType()}).` +
` Keep trace ${oldParser.getTrace().traceFile.file.name}`
`Skipping trace ${
newParser.getTrace().traceFile.file.name
} (trace type: ${newParser.getTraceType()}).` +
` Keep trace ${oldParser.getTrace().traceFile.file.name}`
);
errors.push(
new ParserError(
ParserErrorType.OVERRIDE, newParser.getTrace().traceFile.file, newParser.getTraceType()
ParserErrorType.OVERRIDE,
newParser.getTrace().traceFile.file,
newParser.getTraceType()
)
);
return false;
@@ -122,13 +133,13 @@ export class ParserFactory {
export enum ParserErrorType {
NO_INPUT_FILES,
UNSUPPORTED_FORMAT,
OVERRIDE
OVERRIDE,
}
export class ParserError {
constructor(
public type: ParserErrorType,
public trace: File|undefined = undefined,
public traceType: TraceType|undefined = undefined) {
}
public trace: File | undefined = undefined,
public traceType: TraceType | undefined = undefined
) {}
}

View File

@@ -13,78 +13,77 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceType} from "common/trace/trace_type";
import {Parser} from "./parser";
import {UnitTestUtils} from "test/unit/utils";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {UnitTestUtils} from 'test/unit/utils';
import {Parser} from './parser';
describe("ParserInputMethodlClients", () => {
describe("trace with elapsed + real timestamp", () => {
describe('ParserInputMethodlClients', () => {
describe('trace with elapsed + real timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_and_real_timestamp/InputMethodClients.pb");
parser = await UnitTestUtils.getParser(
'traces/elapsed_and_real_timestamp/InputMethodClients.pb'
);
});
it("has expected trace type", () => {
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.INPUT_METHOD_CLIENTS);
});
it("provides elapsed timestamps", () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)!.length)
.toEqual(13);
it('provides elapsed timestamps', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)!.length).toEqual(13);
const expected = [
new Timestamp(TimestampType.ELAPSED, 15613638434n),
new Timestamp(TimestampType.ELAPSED, 15647516364n),
new Timestamp(TimestampType.ELAPSED, 15677650967n),
];
expect(parser.getTimestamps(TimestampType.ELAPSED)!.slice(0, 3))
.toEqual(expected);
expect(parser.getTimestamps(TimestampType.ELAPSED)!.slice(0, 3)).toEqual(expected);
});
it("provides real timestamps", () => {
it('provides real timestamps', () => {
const expected = [
new Timestamp(TimestampType.REAL, 1659107090215405395n),
new Timestamp(TimestampType.REAL, 1659107090249283325n),
new Timestamp(TimestampType.REAL, 1659107090279417928n),
];
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3))
.toEqual(expected);
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it("retrieves trace entry from elapsed timestamp", () => {
it('retrieves trace entry from elapsed timestamp', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 15647516364n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
.toEqual(15647516364n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos)).toEqual(15647516364n);
});
it("retrieves trace entry from real timestamp", () => {
it('retrieves trace entry from real timestamp', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107090249283325n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
.toEqual(15647516364n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos)).toEqual(15647516364n);
});
});
describe("trace with elapsed (only) timestamp", () => {
describe('trace with elapsed (only) timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_timestamp/InputMethodClients.pb");
parser = await UnitTestUtils.getParser('traces/elapsed_timestamp/InputMethodClients.pb');
});
it("has expected trace type", () => {
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.INPUT_METHOD_CLIENTS);
});
it("provides elapsed timestamps", () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0])
.toEqual(new Timestamp(TimestampType.ELAPSED, 1149083651642n));
it('provides elapsed timestamps', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0]).toEqual(
new Timestamp(TimestampType.ELAPSED, 1149083651642n)
);
});
it("doesn't provide real timestamps", () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0])
.toEqual(new Timestamp(TimestampType.ELAPSED, 1149083651642n));
expect(parser.getTimestamps(TimestampType.ELAPSED)![0]).toEqual(
new Timestamp(TimestampType.ELAPSED, 1149083651642n)
);
});
});
});

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceFile} from "common/trace/trace";
import {TraceType} from "common/trace/trace_type";
import {Parser} from "./parser";
import {InputMethodClientsTraceFileProto} from "./proto_types";
import { TraceTreeNode } from "common/trace/trace_tree_node";
import { TimeUtils } from "common/utils/time_utils";
import { ImeUtils } from "viewers/common/ime_utils";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceFile} from 'common/trace/trace';
import {TraceTreeNode} from 'common/trace/trace_tree_node';
import {TraceType} from 'common/trace/trace_type';
import {TimeUtils} from 'common/utils/time_utils';
import {ImeUtils} from 'viewers/common/ime_utils';
import {Parser} from './parser';
import {InputMethodClientsTraceFileProto} from './proto_types';
class ParserInputMethodClients extends Parser {
constructor(trace: TraceFile) {
@@ -39,61 +39,72 @@ class ParserInputMethodClients extends Parser {
override decodeTrace(buffer: Uint8Array): any[] {
const decoded = <any>InputMethodClientsTraceFileProto.decode(buffer);
if (Object.prototype.hasOwnProperty.call(decoded, "realToElapsedTimeOffsetNanos")) {
if (Object.prototype.hasOwnProperty.call(decoded, 'realToElapsedTimeOffsetNanos')) {
this.realToElapsedTimeOffsetNs = BigInt(decoded.realToElapsedTimeOffsetNanos);
}
else {
} else {
this.realToElapsedTimeOffsetNs = undefined;
}
return (<any>InputMethodClientsTraceFileProto.decode(buffer)).entry;
}
override getTimestamp(type: TimestampType, entryProto: any): undefined|Timestamp {
override getTimestamp(type: TimestampType, entryProto: any): undefined | Timestamp {
if (type === TimestampType.ELAPSED) {
return new Timestamp(type, BigInt(entryProto.elapsedRealtimeNanos));
}
else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs != undefined) {
return new Timestamp(type, BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs);
} else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs != undefined) {
return new Timestamp(
type,
BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs
);
}
return undefined;
}
override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: TraceTreeNode): TraceTreeNode {
override processDecodedEntry(
index: number,
timestampType: TimestampType,
entryProto: TraceTreeNode
): TraceTreeNode {
if (entryProto.elapsedRealtimeNanos === undefined) {
throw Error("Missing elapsedRealtimeNanos on entry");
throw Error('Missing elapsedRealtimeNanos on entry');
}
let clockTimeNanos: bigint|undefined = undefined;
if (this.realToElapsedTimeOffsetNs !== undefined
&& entryProto.elapsedRealtimeNanos !== undefined) {
let clockTimeNanos: bigint | undefined = undefined;
if (
this.realToElapsedTimeOffsetNs !== undefined &&
entryProto.elapsedRealtimeNanos !== undefined
) {
clockTimeNanos = BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs;
}
const timestamp = Timestamp.from(timestampType, BigInt(entryProto.elapsedRealtimeNanos), this.realToElapsedTimeOffsetNs);
const timestamp = Timestamp.from(
timestampType,
BigInt(entryProto.elapsedRealtimeNanos),
this.realToElapsedTimeOffsetNs
);
return {
name: TimeUtils.format(timestamp) + " - " + entryProto.where,
kind: "InputMethodClient entry",
name: TimeUtils.format(timestamp) + ' - ' + entryProto.where,
kind: 'InputMethodClient entry',
children: [
{
obj: ImeUtils.transformInputConnectionCall(entryProto.client),
kind: "Client",
name: entryProto.client?.viewRootImpl?.view ?? "",
kind: 'Client',
name: entryProto.client?.viewRootImpl?.view ?? '',
children: [],
stableId: "client",
id: "client",
}
stableId: 'client',
id: 'client',
},
],
obj: entryProto,
stableId: "entry",
id: "entry",
stableId: 'entry',
id: 'entry',
elapsedRealtimeNanos: entryProto.elapsedRealtimeNanos,
clockTimeNanos,
};
}
private realToElapsedTimeOffsetNs: undefined|bigint;
private realToElapsedTimeOffsetNs: undefined | bigint;
private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMCTRACE
}

View File

@@ -13,65 +13,69 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceType} from "common/trace/trace_type";
import {Parser} from "./parser";
import {UnitTestUtils} from "test/unit/utils";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {UnitTestUtils} from 'test/unit/utils';
import {Parser} from './parser';
describe("ParserInputMethodManagerService", () => {
describe("trace with elapsed + real timestamp", () => {
describe('ParserInputMethodManagerService', () => {
describe('trace with elapsed + real timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_and_real_timestamp/InputMethodManagerService.pb");
parser = await UnitTestUtils.getParser(
'traces/elapsed_and_real_timestamp/InputMethodManagerService.pb'
);
});
it("has expected trace type", () => {
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.INPUT_METHOD_MANAGER_SERVICE);
});
it("provides elapsed timestamps", () => {
expect(parser.getTimestamps(TimestampType.ELAPSED))
.toEqual([new Timestamp(TimestampType.ELAPSED, 15963782518n)]);
it('provides elapsed timestamps', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual([
new Timestamp(TimestampType.ELAPSED, 15963782518n),
]);
});
it("provides real timestamps", () => {
expect(parser.getTimestamps(TimestampType.REAL))
.toEqual([new Timestamp(TimestampType.REAL, 1659107090565549479n)]);
it('provides real timestamps', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual([
new Timestamp(TimestampType.REAL, 1659107090565549479n),
]);
});
it("retrieves trace entry from elapsed timestamp", () => {
it('retrieves trace entry from elapsed timestamp', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 15963782518n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
.toEqual(15963782518n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos)).toEqual(15963782518n);
});
it("retrieves trace entry from real timestamp", () => {
it('retrieves trace entry from real timestamp', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107090565549479n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
.toEqual(15963782518n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos)).toEqual(15963782518n);
});
});
describe("trace with elapsed (only) timestamp", () => {
describe('trace with elapsed (only) timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_timestamp/InputMethodManagerService.pb");
parser = await UnitTestUtils.getParser(
'traces/elapsed_timestamp/InputMethodManagerService.pb'
);
});
it("has expected trace type", () => {
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.INPUT_METHOD_MANAGER_SERVICE);
});
it("provides elapsed timestamps", () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0])
.toEqual(new Timestamp(TimestampType.ELAPSED, 1149226290110n));
it('provides elapsed timestamps', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0]).toEqual(
new Timestamp(TimestampType.ELAPSED, 1149226290110n)
);
});
it("doesn't provide real timestamps", () => {
expect(parser.getTimestamps(TimestampType.REAL))
.toEqual(undefined);
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
});
});
});

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceFile} from "common/trace/trace";
import {TraceType} from "common/trace/trace_type";
import { TraceTreeNode } from "common/trace/trace_tree_node";
import { TimeUtils } from "common/utils/time_utils";
import {Parser} from "./parser";
import {InputMethodManagerServiceTraceFileProto} from "./proto_types";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceFile} from 'common/trace/trace';
import {TraceTreeNode} from 'common/trace/trace_tree_node';
import {TraceType} from 'common/trace/trace_type';
import {TimeUtils} from 'common/utils/time_utils';
import {Parser} from './parser';
import {InputMethodManagerServiceTraceFileProto} from './proto_types';
class ParserInputMethodManagerService extends Parser {
constructor(trace: TraceFile) {
@@ -38,60 +38,71 @@ class ParserInputMethodManagerService extends Parser {
override decodeTrace(buffer: Uint8Array): any[] {
const decoded = <any>InputMethodManagerServiceTraceFileProto.decode(buffer);
if (Object.prototype.hasOwnProperty.call(decoded, "realToElapsedTimeOffsetNanos")) {
if (Object.prototype.hasOwnProperty.call(decoded, 'realToElapsedTimeOffsetNanos')) {
this.realToElapsedTimeOffsetNs = BigInt(decoded.realToElapsedTimeOffsetNanos);
}
else {
} else {
this.realToElapsedTimeOffsetNs = undefined;
}
return decoded.entry;
}
protected override getTimestamp(type: TimestampType, entryProto: any): undefined|Timestamp {
protected override getTimestamp(type: TimestampType, entryProto: any): undefined | Timestamp {
if (type === TimestampType.ELAPSED) {
return new Timestamp(TimestampType.ELAPSED, BigInt(entryProto.elapsedRealtimeNanos));
}
else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) {
return new Timestamp(type, this.realToElapsedTimeOffsetNs + BigInt(entryProto.elapsedRealtimeNanos));
} else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) {
return new Timestamp(
type,
this.realToElapsedTimeOffsetNs + BigInt(entryProto.elapsedRealtimeNanos)
);
}
return undefined;
}
protected override processDecodedEntry(index: number, timestampType: TimestampType, entryProto: TraceTreeNode): TraceTreeNode {
protected override processDecodedEntry(
index: number,
timestampType: TimestampType,
entryProto: TraceTreeNode
): TraceTreeNode {
if (entryProto.elapsedRealtimeNanos === undefined) {
throw Error("Missing elapsedRealtimeNanos on entry");
throw Error('Missing elapsedRealtimeNanos on entry');
}
let clockTimeNanos: bigint|undefined = undefined;
if (this.realToElapsedTimeOffsetNs !== undefined
&& entryProto.elapsedRealtimeNanos !== undefined) {
let clockTimeNanos: bigint | undefined = undefined;
if (
this.realToElapsedTimeOffsetNs !== undefined &&
entryProto.elapsedRealtimeNanos !== undefined
) {
clockTimeNanos = BigInt(entryProto.elapsedRealtimeNanos) + this.realToElapsedTimeOffsetNs;
}
const timestamp = Timestamp.from(timestampType, BigInt(entryProto.elapsedRealtimeNanos), this.realToElapsedTimeOffsetNs);
const timestamp = Timestamp.from(
timestampType,
BigInt(entryProto.elapsedRealtimeNanos),
this.realToElapsedTimeOffsetNs
);
return {
name: TimeUtils.format(timestamp) + " - " + entryProto.where,
kind: "InputMethodManagerService entry",
name: TimeUtils.format(timestamp) + ' - ' + entryProto.where,
kind: 'InputMethodManagerService entry',
children: [
{
obj: entryProto.inputMethodManagerService,
kind: "InputMethodManagerService",
name: "",
kind: 'InputMethodManagerService',
name: '',
children: [],
stableId: "managerservice",
id: "managerservice",
}
stableId: 'managerservice',
id: 'managerservice',
},
],
obj: entryProto,
stableId: "entry",
id: "entry",
stableId: 'entry',
id: 'entry',
elapsedRealtimeNanos: entryProto.elapsedRealtimeNanos,
clockTimeNanos,
};
}
private realToElapsedTimeOffsetNs: undefined|bigint;
private realToElapsedTimeOffsetNs: undefined | bigint;
private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x4d, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMMTRACE
}

View File

@@ -13,71 +13,65 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceType} from "common/trace/trace_type";
import {Parser} from "./parser";
import {UnitTestUtils} from "test/unit/utils";
import {Timestamp, TimestampType} from 'common/trace/timestamp';
import {TraceType} from 'common/trace/trace_type';
import {UnitTestUtils} from 'test/unit/utils';
import {Parser} from './parser';
describe("ParserInputMethodService", () => {
describe("trace with elapsed + real timestamp", () => {
describe('ParserInputMethodService', () => {
describe('trace with elapsed + real timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_and_real_timestamp/InputMethodService.pb");
parser = await UnitTestUtils.getParser(
'traces/elapsed_and_real_timestamp/InputMethodService.pb'
);
});
it("has expected trace type", () => {
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.INPUT_METHOD_SERVICE);
});
it("provides elapsed timestamps", () => {
const expected = [
new Timestamp(TimestampType.ELAPSED, 16578752896n),
];
expect(parser.getTimestamps(TimestampType.ELAPSED))
.toEqual(expected);
it('provides elapsed timestamps', () => {
const expected = [new Timestamp(TimestampType.ELAPSED, 16578752896n)];
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(expected);
});
it("provides real timestamps", () => {
const expected = [
new Timestamp(TimestampType.REAL, 1659107091180519857n),
];
expect(parser.getTimestamps(TimestampType.REAL))
.toEqual(expected);
it('provides real timestamps', () => {
const expected = [new Timestamp(TimestampType.REAL, 1659107091180519857n)];
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
});
it("retrieves trace entry from elapsed timestamp", () => {
it('retrieves trace entry from elapsed timestamp', () => {
const timestamp = new Timestamp(TimestampType.ELAPSED, 16578752896n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
.toEqual(16578752896n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos)).toEqual(16578752896n);
});
it("retrieves trace entry from real timestamp", () => {
it('retrieves trace entry from real timestamp', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107091180519857n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos))
.toEqual(16578752896n);
expect(BigInt(parser.getTraceEntry(timestamp)!.elapsedRealtimeNanos)).toEqual(16578752896n);
});
});
describe("trace with elapsed (only) timestamp", () => {
describe('trace with elapsed (only) timestamp', () => {
let parser: Parser;
beforeAll(async () => {
parser = await UnitTestUtils.getParser("traces/elapsed_timestamp/InputMethodService.pb");
parser = await UnitTestUtils.getParser('traces/elapsed_timestamp/InputMethodService.pb');
});
it("has expected trace type", () => {
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.INPUT_METHOD_SERVICE);
});
it("provides elapsed timestamps", () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0])
.toEqual(new Timestamp(TimestampType.ELAPSED, 1149230019887n));
it('provides elapsed timestamps', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)![0]).toEqual(
new Timestamp(TimestampType.ELAPSED, 1149230019887n)
);
});
it("doesn't provide real timestamps", () => {
expect(parser.getTimestamps(TimestampType.REAL))
.toEqual(undefined);
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
});
});
});

Some files were not shown because too many files have changed in this diff Show More