1// Copyright (C) 2019 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {binaryDecode, binaryEncode} from '../base/string_utils'; 16import {Actions} from '../common/actions'; 17import {TRACE_SUFFIX} from '../common/constants'; 18 19import { 20 ConsumerPortResponse, 21 isReadBuffersResponse, 22 Typed 23} from './consumer_port_types'; 24import {globals} from './globals'; 25import {Consumer, RpcConsumerPort} from './record_controller_interfaces'; 26 27export interface ChromeExtensionError extends Typed { 28 error: string; 29} 30 31export interface ChromeExtensionStatus extends Typed { 32 status: string; 33} 34 35export interface GetCategoriesResponse extends Typed { 36 categories: string[]; 37} 38 39export type ChromeExtensionMessage = ChromeExtensionError|ChromeExtensionStatus| 40 ConsumerPortResponse|GetCategoriesResponse; 41 42function isError(obj: Typed): obj is ChromeExtensionError { 43 return obj.type === 'ChromeExtensionError'; 44} 45 46function isStatus(obj: Typed): obj is ChromeExtensionStatus { 47 return obj.type === 'ChromeExtensionStatus'; 48} 49 50function isGetCategoriesResponse(obj: Typed): obj is GetCategoriesResponse { 51 return obj.type === 'GetCategoriesResponse'; 52} 53 54// This class acts as a proxy from the record controller (running in a worker), 55// to the frontend. This is needed because we can't directly talk with the 56// extension from a web-worker, so we use a MessagePort to communicate with the 57// frontend, that will consecutively forward it to the extension. 58 59// Rationale for the binaryEncode / binaryDecode calls below: 60// Messages to/from extensions need to be JSON serializable. ArrayBuffers are 61// not supported. For this reason here we use binaryEncode/Decode. 62// See https://developer.chrome.com/extensions/messaging#simple 63 64export class ChromeExtensionConsumerPort extends RpcConsumerPort { 65 private extensionPort: MessagePort; 66 67 constructor(extensionPort: MessagePort, consumer: Consumer) { 68 super(consumer); 69 this.extensionPort = extensionPort; 70 this.extensionPort.onmessage = this.onExtensionMessage.bind(this); 71 } 72 73 onExtensionMessage(message: {data: ChromeExtensionMessage}) { 74 if (isError(message.data)) { 75 this.sendErrorMessage(message.data.error); 76 return; 77 } 78 if (isStatus(message.data)) { 79 this.sendStatus(message.data.status); 80 return; 81 } 82 if (isGetCategoriesResponse(message.data)) { 83 globals.dispatch(Actions.setChromeCategories(message.data)); 84 return; 85 } 86 87 // In this else branch message.data will be a ConsumerPortResponse. 88 if (isReadBuffersResponse(message.data) && message.data.slices) { 89 const slice = message.data.slices[0].data as unknown as string; 90 message.data.slices[0].data = binaryDecode(slice); 91 } 92 this.sendMessage(message.data); 93 } 94 95 handleCommand(method: string, requestData: Uint8Array): void { 96 const reqEncoded = binaryEncode(requestData); 97 this.extensionPort.postMessage({method, requestData: reqEncoded}); 98 } 99 100 getRecordedTraceSuffix(): string { 101 return `${TRACE_SUFFIX}.gz`; 102 } 103} 104