1/* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {ChangeDetectorRef, Component, Inject} from '@angular/core'; 18import {assertDefined, assertUnreachable} from 'common/assert_utils'; 19import {FunctionUtils} from 'common/function_utils'; 20import {TimeUtils} from 'common/time_utils'; 21import { 22 Message, 23 MessageBugReport, 24 MessageFiles, 25 MessagePing, 26 MessageTimestamp, 27 MessageType, 28 TimestampType, 29} from 'cross_tool/messages'; 30 31@Component({ 32 selector: 'app-root', 33 template: ` 34 <span class="app-title">Remote Tool Mock (simulates cross-tool protocol)</span> 35 36 <hr/> 37 <p>Open Winscope tab</p> 38 <input 39 class="button-open-winscope" 40 type="button" 41 value="Open" 42 (click)="onButtonOpenWinscopeClick()"/> 43 44 <hr/> 45 <p>Send bugreport</p> 46 <input 47 class="button-send-bugreport" 48 type="file" 49 value="" 50 (change)="onButtonSendBugreportClick($event)"/> 51 52 <hr/> 53 <p>Send file</p> 54 <input 55 class="button-send-files" 56 type="file" 57 value="" 58 (change)="onButtonSendFilesClick($event)"/> 59 60 <hr/> 61 <p>Send timestamp [ns]</p> 62 <input class="input-timestamp" type="number" id="name" name="name"/> 63 <input 64 class="button-send-realtime-timestamp" 65 type="button" 66 value="Send" 67 (click)="onButtonSendRealtimeTimestampClick()"/> 68 <input 69 class="button-send-boottime-timestamp" 70 type="button" 71 value="Send" 72 (click)="onButtonSendBoottimeTimestampClick()"/> 73 <hr/> 74 <p>Received realtime timestamp:</p> 75 <p class="paragraph-received-realtime-timestamp"></p> 76 <p>Received boottime timestamp:</p> 77 <p class="paragraph-received-boottime-timestamp"></p> 78 `, 79}) 80export class AppComponent { 81 static readonly TARGET = 'http://localhost:8080'; 82 static readonly TIMESTAMP_IN_BUGREPORT_MESSAGE = 1670509911000000000n; 83 static readonly TIMESTAMP_IN_FILES_MESSAGE = 15725894416n; 84 85 private winscope: Window | null = null; 86 private isWinscopeUp = false; 87 private onMessagePongReceived = FunctionUtils.DO_NOTHING; 88 89 constructor( 90 @Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef, 91 ) { 92 window.addEventListener('message', (event) => { 93 this.onMessageReceived(event); 94 }); 95 } 96 97 async onButtonOpenWinscopeClick() { 98 this.openWinscope(); 99 await this.waitWinscopeUp(); 100 } 101 102 async onButtonSendBugreportClick(event: Event) { 103 const file = await this.readInputFile(event); 104 this.sendBugreport(file); 105 } 106 107 async onButtonSendFilesClick(event: Event) { 108 const file = await this.readInputFile(event); 109 this.sendFiles([file]); 110 } 111 112 onButtonSendRealtimeTimestampClick() { 113 const inputTimestampElement = assertDefined( 114 document.querySelector('.input-timestamp'), 115 ) as HTMLInputElement; 116 this.sendTimestamp( 117 BigInt(inputTimestampElement.value), 118 TimestampType.CLOCK_REALTIME, 119 ); 120 } 121 122 onButtonSendBoottimeTimestampClick() { 123 const inputTimestampElement = assertDefined( 124 document.querySelector('.input-timestamp'), 125 ) as HTMLInputElement; 126 this.sendTimestamp( 127 BigInt(inputTimestampElement.value), 128 TimestampType.CLOCK_BOOTTIME, 129 ); 130 } 131 132 private openWinscope() { 133 this.printStatus('OPENING WINSCOPE'); 134 135 this.winscope = window.open(AppComponent.TARGET); 136 if (!this.winscope) { 137 throw new Error('Failed to open winscope'); 138 } 139 140 this.printStatus('OPENED WINSCOPE'); 141 } 142 143 private async waitWinscopeUp() { 144 this.printStatus('WAITING WINSCOPE UP'); 145 146 const promise = new Promise<void>((resolve) => { 147 this.onMessagePongReceived = () => { 148 this.isWinscopeUp = true; 149 resolve(); 150 }; 151 }); 152 153 setTimeout(async () => { 154 while (!this.isWinscopeUp) { 155 assertDefined(this.winscope).postMessage( 156 new MessagePing(), 157 AppComponent.TARGET, 158 ); 159 await TimeUtils.sleepMs(10); 160 } 161 }, 0); 162 163 await promise; 164 165 this.printStatus('DONE WAITING (WINSCOPE IS UP)'); 166 } 167 168 private sendBugreport(file: File) { 169 this.printStatus('SENDING BUGREPORT'); 170 171 assertDefined(this.winscope).postMessage( 172 new MessageBugReport(file, AppComponent.TIMESTAMP_IN_BUGREPORT_MESSAGE), 173 AppComponent.TARGET, 174 ); 175 176 this.printStatus('SENT BUGREPORT'); 177 } 178 179 private sendFiles(files: File[]) { 180 this.printStatus('SENDING FILES'); 181 182 assertDefined(this.winscope).postMessage( 183 new MessageFiles( 184 files, 185 AppComponent.TIMESTAMP_IN_FILES_MESSAGE, 186 TimestampType.CLOCK_BOOTTIME, 187 ), 188 AppComponent.TARGET, 189 ); 190 191 this.printStatus('SENT FILES'); 192 } 193 194 private sendTimestamp(value: bigint, type: TimestampType) { 195 this.printStatus('SENDING TIMESTAMP'); 196 197 assertDefined(this.winscope).postMessage( 198 new MessageTimestamp(value, type), 199 AppComponent.TARGET, 200 ); 201 202 this.printStatus('SENT TIMESTAMP'); 203 } 204 205 private onMessageReceived(event: MessageEvent) { 206 const message = event.data as Message; 207 if (!message.type) { 208 console.log( 209 'Cross-tool protocol received unrecognized message:', 210 message, 211 ); 212 return; 213 } 214 215 switch (message.type) { 216 case MessageType.PING: 217 console.log( 218 'Cross-tool protocol received unexpected ping message:', 219 message, 220 ); 221 break; 222 case MessageType.PONG: 223 this.onMessagePongReceived(); 224 break; 225 case MessageType.BUGREPORT: 226 console.log( 227 'Cross-tool protocol received unexpected bugreport message:', 228 message, 229 ); 230 break; 231 case MessageType.TIMESTAMP: 232 console.log('Cross-tool protocol received timestamp message:', message); 233 this.onMessageTimestampReceived(message as MessageTimestamp); 234 break; 235 case MessageType.FILES: 236 console.log( 237 'Cross-tool protocol received unexpected files message:', 238 message, 239 ); 240 break; 241 default: 242 console.log( 243 'Cross-tool protocol received unrecognized message:', 244 message, 245 ); 246 break; 247 } 248 } 249 250 private onMessageTimestampReceived(message: MessageTimestamp) { 251 let paragraph: HTMLParagraphElement | undefined; 252 253 const timestampType = assertDefined(message.timestampType); 254 switch (timestampType) { 255 case TimestampType.UNKNOWN: 256 throw Error("Winscope shouldn't send timestamps with UNKNOWN type"); 257 case TimestampType.CLOCK_BOOTTIME: { 258 paragraph = document.querySelector( 259 '.paragraph-received-boottime-timestamp', 260 ) as HTMLParagraphElement; 261 break; 262 } 263 case TimestampType.CLOCK_REALTIME: { 264 paragraph = document.querySelector( 265 '.paragraph-received-realtime-timestamp', 266 ) as HTMLParagraphElement; 267 break; 268 } 269 default: 270 assertUnreachable(timestampType); 271 } 272 273 paragraph.textContent = message.timestampNs.toString(); 274 this.changeDetectorRef.detectChanges(); 275 } 276 277 private printStatus(status: string) { 278 console.log('STATUS: ' + status); 279 } 280 281 private async readInputFile(event: Event): Promise<File> { 282 const files: FileList | null = (event?.target as HTMLInputElement)?.files; 283 284 if (!files || !files[0]) { 285 throw new Error('Failed to read input files'); 286 } 287 288 return files[0]; 289 } 290} 291