Merge changes Ida110947,I8289c8b2
* changes: The great formatting Fix eslint errors
This commit is contained in:
committed by
Android (Google) Code Review
commit
27e7b80d58
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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:///';
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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...';
|
||||
}
|
||||
|
||||
@@ -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, []);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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[]>;
|
||||
|
||||
@@ -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"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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(' | ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
|
||||
class ScreenRecordingTraceEntry {
|
||||
constructor(public videoTimeSeconds: number,
|
||||
public videoData: Blob) {
|
||||
}
|
||||
constructor(public videoTimeSeconds: number, public videoData: Blob) {}
|
||||
}
|
||||
|
||||
export {ScreenRecordingTraceEntry};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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*$/;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Viewer} from "viewers/viewer";
|
||||
import {Viewer} from 'viewers/viewer';
|
||||
|
||||
export interface TraceDataListener {
|
||||
onTraceDataUnloaded(): void;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user