// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert, assertExists} from './assert.js';
import * as Comlink from './lib/comlink.js';
import {isLocalDev} from './models/load_time_data.js';
import {
Ga4EventParams,
Ga4MetricDimension,
GaBaseEvent,
GaHelper,
GaMetricDimension,
MemoryUsageEventDimension,
} from './untrusted_ga_helper.js';
import {VideoProcessorHelper} from './untrusted_video_processor_helper.js';
import {expandPath} from './util.js';
import {WaitableEvent} from './waitable_event.js';
interface UntrustedIFrame {
iframe: HTMLIFrameElement;
pageReadyEvent: WaitableEvent;
}
/**
* Creates the iframe to host the untrusted scripts under
* chrome-untrusted://camera-app and append to the document.
*/
export function createUntrustedIframe(): UntrustedIFrame {
const untrustedPageReady = new WaitableEvent();
const iframe = document.createElement('iframe');
iframe.addEventListener('load', () => untrustedPageReady.signal());
if (isLocalDev()) {
iframe.setAttribute(
'src', expandPath('/views/untrusted_script_loader.html'));
} else {
iframe.setAttribute(
'src',
'chrome-untrusted://camera-app/views/untrusted_script_loader.html');
}
iframe.hidden = true;
iframe.setAttribute('allow', 'cross-origin-isolated');
document.body.appendChild(iframe);
return {iframe, pageReadyEvent: untrustedPageReady};
}
// TODO(pihsun): actually get correct type from the function definition.
interface UntrustedScriptLoader {
loadScript(url: string): Promise<void>;
measureMemoryUsage(): Promise<MemoryMeasurement>;
}
let memoryMeasurementHelper: Comlink.Remote<UntrustedScriptLoader>|null = null;
/**
* Creates JS module by given |scriptUrl| under untrusted context with given
* origin and returns its proxy.
*
* @param untrustedIframe The untrusted iframe.
* @param scriptUrl The URL of the script to load.
*/
export async function injectUntrustedJSModule<T>(
untrustedIframe: UntrustedIFrame,
scriptUrl: string): Promise<Comlink.Remote<T>> {
const {iframe, pageReadyEvent} = untrustedIframe;
await pageReadyEvent.wait();
assert(iframe.contentWindow !== null);
const untrustedRemote = Comlink.wrap<UntrustedScriptLoader>(
Comlink.windowEndpoint(iframe.contentWindow, self));
// Memory measurement for all untrusted scripts can be done on any single
// untrusted frame.
if (memoryMeasurementHelper === null) {
memoryMeasurementHelper = untrustedRemote;
}
await untrustedRemote.loadScript(scriptUrl);
// loadScript adds the script exports to what's exported by the
// untrustedRemote, so we manually cast it to the expected type.
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return untrustedRemote as unknown as Comlink.Remote<T>;
}
let gaHelper: Promise<Comlink.Remote<GaHelper>>|null = null;
let videoProcessorHelper: Promise<Comlink.Remote<VideoProcessorHelper>>|null =
null;
/**
* Measure memory used by untrusted scripts.
*/
export async function measureUntrustedScriptsMemory():
Promise<MemoryMeasurement> {
assert(memoryMeasurementHelper !== null);
return memoryMeasurementHelper.measureMemoryUsage();
}
/**
* Gets the singleton GaHelper instance that is located in an untrusted iframe.
*/
export function getGaHelper(): Promise<Comlink.Remote<GaHelper>> {
return assertExists(gaHelper);
}
/**
* Sets the singleton GaHelper instance. This should only be called on
* initialize by init.ts.
*/
export function setGaHelper(newGaHelper: Promise<Comlink.Remote<GaHelper>>):
void {
assert(gaHelper === null, 'gaHelper should only be initialize once on init');
gaHelper = newGaHelper;
}
/**
* Types of event parameters and dimensions for GA and GA4.
*/
export {Ga4MetricDimension, GaMetricDimension};
export type {Ga4EventParams, GaBaseEvent, MemoryUsageEventDimension};
/**
* Gets the singleton VideoProcessorHelper instance that is located in an
* untrusted iframe.
*/
export function getVideoProcessorHelper():
Promise<Comlink.Remote<VideoProcessorHelper>> {
return assertExists(videoProcessorHelper);
}
/**
* Sets the singleton VideoProcessorHelper instance. This should only be called
* on initialize by init.ts.
*/
export function setVideoProcessorHelper(
newVideoProcessorHelper: Promise<Comlink.Remote<VideoProcessorHelper>>):
void {
assert(
videoProcessorHelper === null,
'videoProcessorHelper should only be initialize once on init');
videoProcessorHelper = newVideoProcessorHelper;
}