/*
* 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, Inject} from '@angular/core';
import {assertDefined, assertUnreachable} from 'common/assert_utils';
import {FunctionUtils} from 'common/function_utils';
import {TimeUtils} from 'common/time_utils';
import {
Message,
MessageBugReport,
MessageFiles,
MessagePing,
MessageTimestamp,
MessageType,
TimestampType,
} from 'cross_tool/messages';
@Component({
selector: 'app-root',
template: `
Remote Tool Mock (simulates cross-tool protocol)
Open Winscope tab
Send bugreport
Send file
Send timestamp [ns]
Received realtime timestamp:
Received boottime timestamp:
`,
})
export class AppComponent {
static readonly TARGET = 'http://localhost:8080';
static readonly TIMESTAMP_IN_BUGREPORT_MESSAGE = 1670509911000000000n;
static readonly TIMESTAMP_IN_FILES_MESSAGE = 15725894416n;
private winscope: Window | null = null;
private isWinscopeUp = false;
private onMessagePongReceived = FunctionUtils.DO_NOTHING;
constructor(
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef,
) {
window.addEventListener('message', (event) => {
this.onMessageReceived(event);
});
}
async onButtonOpenWinscopeClick() {
this.openWinscope();
await this.waitWinscopeUp();
}
async onButtonSendBugreportClick(event: Event) {
const file = await this.readInputFile(event);
this.sendBugreport(file);
}
async onButtonSendFilesClick(event: Event) {
const file = await this.readInputFile(event);
this.sendFiles([file]);
}
onButtonSendRealtimeTimestampClick() {
const inputTimestampElement = assertDefined(
document.querySelector('.input-timestamp'),
) as HTMLInputElement;
this.sendTimestamp(
BigInt(inputTimestampElement.value),
TimestampType.CLOCK_REALTIME,
);
}
onButtonSendBoottimeTimestampClick() {
const inputTimestampElement = assertDefined(
document.querySelector('.input-timestamp'),
) as HTMLInputElement;
this.sendTimestamp(
BigInt(inputTimestampElement.value),
TimestampType.CLOCK_BOOTTIME,
);
}
private openWinscope() {
this.printStatus('OPENING WINSCOPE');
this.winscope = window.open(AppComponent.TARGET);
if (!this.winscope) {
throw new Error('Failed to open winscope');
}
this.printStatus('OPENED WINSCOPE');
}
private async waitWinscopeUp() {
this.printStatus('WAITING WINSCOPE UP');
const promise = new Promise((resolve) => {
this.onMessagePongReceived = () => {
this.isWinscopeUp = true;
resolve();
};
});
setTimeout(async () => {
while (!this.isWinscopeUp) {
assertDefined(this.winscope).postMessage(
new MessagePing(),
AppComponent.TARGET,
);
await TimeUtils.sleepMs(10);
}
}, 0);
await promise;
this.printStatus('DONE WAITING (WINSCOPE IS UP)');
}
private sendBugreport(file: File) {
this.printStatus('SENDING BUGREPORT');
assertDefined(this.winscope).postMessage(
new MessageBugReport(file, AppComponent.TIMESTAMP_IN_BUGREPORT_MESSAGE),
AppComponent.TARGET,
);
this.printStatus('SENT BUGREPORT');
}
private sendFiles(files: File[]) {
this.printStatus('SENDING FILES');
assertDefined(this.winscope).postMessage(
new MessageFiles(
files,
AppComponent.TIMESTAMP_IN_FILES_MESSAGE,
TimestampType.CLOCK_BOOTTIME,
),
AppComponent.TARGET,
);
this.printStatus('SENT FILES');
}
private sendTimestamp(value: bigint, type: TimestampType) {
this.printStatus('SENDING TIMESTAMP');
assertDefined(this.winscope).postMessage(
new MessageTimestamp(value, type),
AppComponent.TARGET,
);
this.printStatus('SENT TIMESTAMP');
}
private onMessageReceived(event: MessageEvent) {
const message = event.data as Message;
if (!message.type) {
console.log(
'Cross-tool protocol received unrecognized message:',
message,
);
return;
}
switch (message.type) {
case MessageType.PING:
console.log(
'Cross-tool protocol received unexpected ping message:',
message,
);
break;
case MessageType.PONG:
this.onMessagePongReceived();
break;
case MessageType.BUGREPORT:
console.log(
'Cross-tool protocol received unexpected bugreport message:',
message,
);
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 unrecognized message:',
message,
);
break;
}
}
private onMessageTimestampReceived(message: MessageTimestamp) {
let paragraph: HTMLParagraphElement | undefined;
const timestampType = assertDefined(message.timestampType);
switch (timestampType) {
case TimestampType.UNKNOWN:
throw Error("Winscope shouldn't send timestamps with UNKNOWN type");
case TimestampType.CLOCK_BOOTTIME: {
paragraph = document.querySelector(
'.paragraph-received-boottime-timestamp',
) as HTMLParagraphElement;
break;
}
case TimestampType.CLOCK_REALTIME: {
paragraph = document.querySelector(
'.paragraph-received-realtime-timestamp',
) as HTMLParagraphElement;
break;
}
default:
assertUnreachable(timestampType);
}
paragraph.textContent = message.timestampNs.toString();
this.changeDetectorRef.detectChanges();
}
private printStatus(status: string) {
console.log('STATUS: ' + status);
}
private async readInputFile(event: Event): Promise {
const files: FileList | null = (event?.target as HTMLInputElement)?.files;
if (!files || !files[0]) {
throw new Error('Failed to read input files');
}
return files[0];
}
}