• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {TRACE_SUFFIX} from '../common/constants';
17
18import {
19  ConsumerPortResponse,
20  hasProperty,
21  isReadBuffersResponse,
22  Typed,
23} from './consumer_port_types';
24import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
25
26export interface ChromeExtensionError extends Typed {
27  error: string;
28}
29
30export interface ChromeExtensionStatus extends Typed {
31  status: string;
32}
33
34export interface GetCategoriesResponse extends Typed {
35  categories: string[];
36}
37
38export type ChromeExtensionMessage = ChromeExtensionError|ChromeExtensionStatus|
39    ConsumerPortResponse|GetCategoriesResponse;
40
41export function isChromeExtensionError(obj: Typed):
42    obj is ChromeExtensionError {
43  return obj.type === 'ChromeExtensionError';
44}
45
46export function isChromeExtensionStatus(obj: Typed):
47    obj is ChromeExtensionStatus {
48  return obj.type === 'ChromeExtensionStatus';
49}
50
51function isObject(obj: unknown): obj is object {
52  return typeof obj === 'object' && obj !== null;
53}
54
55export function isGetCategoriesResponse(obj: unknown):
56    obj is GetCategoriesResponse {
57  if (!(isObject(obj) && hasProperty(obj, 'type') &&
58        obj.type === 'GetCategoriesResponse')) {
59    return false;
60  }
61
62  return hasProperty(obj, 'categories') && Array.isArray(obj.categories);
63}
64
65// This class acts as a proxy from the record controller (running in a worker),
66// to the frontend. This is needed because we can't directly talk with the
67// extension from a web-worker, so we use a MessagePort to communicate with the
68// frontend, that will consecutively forward it to the extension.
69
70// Rationale for the binaryEncode / binaryDecode calls below:
71// Messages to/from extensions need to be JSON serializable. ArrayBuffers are
72// not supported. For this reason here we use binaryEncode/Decode.
73// See https://developer.chrome.com/extensions/messaging#simple
74
75export class ChromeExtensionConsumerPort extends RpcConsumerPort {
76  private extensionPort: MessagePort;
77
78  constructor(extensionPort: MessagePort, consumer: Consumer) {
79    super(consumer);
80    this.extensionPort = extensionPort;
81    this.extensionPort.onmessage = this.onExtensionMessage.bind(this);
82  }
83
84  onExtensionMessage(message: {data: ChromeExtensionMessage}) {
85    if (isChromeExtensionError(message.data)) {
86      this.sendErrorMessage(message.data.error);
87      return;
88    }
89    if (isChromeExtensionStatus(message.data)) {
90      this.sendStatus(message.data.status);
91      return;
92    }
93
94    // In this else branch message.data will be a ConsumerPortResponse.
95    if (isReadBuffersResponse(message.data) && message.data.slices) {
96      const slice = message.data.slices[0].data as unknown as string;
97      message.data.slices[0].data = binaryDecode(slice);
98    }
99    this.sendMessage(message.data);
100  }
101
102  handleCommand(method: string, requestData: Uint8Array): void {
103    const reqEncoded = binaryEncode(requestData);
104    this.extensionPort.postMessage({method, requestData: reqEncoded});
105  }
106
107  getRecordedTraceSuffix(): string {
108    return `${TRACE_SUFFIX}.gz`;
109  }
110}
111