Commit 7d3a4517 authored by Wei Lee's avatar Wei Lee Committed by Commit Bot

[CCA WebUI] Implements TestBridge in CCA

Originally, we use background page and chrome.runtime API for
communication between CCA and Tast tests.

However, since SWA does not have background page and cannot use
chrome.runtime API, we introduce a new implementation using
SharedWorker for communication.

Bug: 980846
Test: Tested with the related Tast CL and run "$ tast run [DUT]
camera.CCAUI*"

Change-Id: I575aeb11db4af90de1eebf63ad7f1d8f1608ded2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2362235Reviewed-by: default avatarShik Chen <shik@chromium.org>
Reviewed-by: default avatarInker Kuo <inker@chromium.org>
Commit-Queue: Wei Lee <wtlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#817490}
parent 1af9a003
......@@ -4,6 +4,8 @@
#include "chrome/browser/chromeos/web_applications/chrome_camera_app_ui_delegate.h"
#include <vector>
#include "base/files/file_path.h"
#include "base/system/sys_info.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
......@@ -24,6 +26,7 @@
#include "content/public/browser/web_ui_data_source.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "ui/gfx/native_widget_types.h"
#include "url/gurl.h"
namespace {
......@@ -36,15 +39,15 @@ constexpr int kDefaultWindowHeight = 486;
void ChromeCameraAppUIDelegate::CameraAppDialog::ShowIntent(
const std::string& queries,
gfx::NativeWindow parent) {
CameraAppDialog* dialog =
new CameraAppDialog(chromeos::kChromeUICameraAppMainURL + queries);
std::string url = chromeos::kChromeUICameraAppMainURL + queries;
CameraAppDialog* dialog = new CameraAppDialog(url);
dialog->ShowSystemDialog(parent);
}
ChromeCameraAppUIDelegate::CameraAppDialog::CameraAppDialog(
const std::string& url)
: chromeos::SystemWebDialogDelegate(GURL(url), /*title=*/base::string16()) {
}
: chromeos::SystemWebDialogDelegate(GURL(url),
/*title=*/base::string16()) {}
ChromeCameraAppUIDelegate::CameraAppDialog::~CameraAppDialog() {}
......
......@@ -10,6 +10,8 @@
#include "content/public/browser/media_stream_request.h"
#include "content/public/browser/web_ui.h"
class GURL;
namespace content {
struct MediaStreamRequest;
class RenderFrameHost;
......
......@@ -110,8 +110,7 @@ const struct {
const char* path;
int id;
} kGritResourceMap[] = {
{"js/browser_proxy/browser_proxy.js",
IDR_CAMERA_WEBUI_BROWSER_PROXY_JS},
{"js/browser_proxy/browser_proxy.js", IDR_CAMERA_WEBUI_BROWSER_PROXY_JS},
{"js/mojo/camera_intent.mojom-lite.js",
IDR_CAMERA_CAMERA_INTENT_MOJOM_LITE_JS},
{"js/mojo/image_capture.mojom-lite.js",
......@@ -122,16 +121,14 @@ const struct {
IDR_CAMERA_CAMERA_METADATA_MOJOM_LITE_JS},
{"js/mojo/camera_metadata_tags.mojom-lite.js",
IDR_CAMERA_CAMERA_METADATA_TAGS_MOJOM_LITE_JS},
{"js/mojo/camera_app.mojom-lite.js",
IDR_CAMERA_CAMERA_APP_MOJOM_LITE_JS},
{"js/mojo/camera_app.mojom-lite.js", IDR_CAMERA_CAMERA_APP_MOJOM_LITE_JS},
{"js/mojo/mojo_bindings_lite.js", IDR_MOJO_MOJO_BINDINGS_LITE_JS},
{"js/mojo/camera_app_helper.mojom-lite.js",
IDR_CAMERA_CAMERA_APP_HELPER_MOJOM_LITE_JS},
{"js/mojo/time.mojom-lite.js", IDR_CAMERA_TIME_MOJOM_LITE_JS},
{"js/mojo/idle_manager.mojom-lite.js",
IDR_CAMERA_IDLE_MANAGER_MOJOM_LITE_JS},
{"js/mojo/camera_app.mojom-lite.js",
IDR_CAMERA_CAMERA_APP_MOJOM_LITE_JS},
{"js/mojo/camera_app.mojom-lite.js", IDR_CAMERA_CAMERA_APP_MOJOM_LITE_JS},
{"js/mojo/geometry.mojom-lite.js", IDR_CAMERA_GEOMETRY_MOJOM_LITE_JS},
{"js/mojo/range.mojom-lite.js", IDR_CAMERA_RANGE_MOJOM_LITE_JS},
};
......
......@@ -96,6 +96,7 @@ copy("chrome_camera_app_images") {
copy("chrome_camera_app_js") {
sources = [
"js/app_window.js",
"js/async_job_queue.js",
"js/background.js",
"js/background_ops.js",
......@@ -111,6 +112,7 @@ copy("chrome_camera_app_js") {
"js/perf.js",
"js/sound.js",
"js/state.js",
"js/test_bridge.js",
"js/toast.js",
"js/tooltip.js",
"js/type.js",
......
......@@ -13,6 +13,7 @@
<release seq="1">
<structures>
<structure name="IDR_CAMERA_ANALYTICS_JS" file="js/lib/analytics.js" type="chrome_html" />
<structure name="IDR_CAMERA_APP_WINDOW_JS" file="js/app_window.js" type="chrome_html" />
<structure name="IDR_CAMERA_ASYNC_JOB_QUEUE_JS" file="js/async_job_queue.js" type="chrome_html" />
<structure name="IDR_CAMERA_ASYNC_WRITER_JS" file="js/models/async_writer.js" type="chrome_html" />
<structure name="IDR_CAMERA_BACKGROUND_JS" file="js/background.js" type="chrome_html" />
......@@ -64,6 +65,8 @@
<structure name="IDR_CAMERA_SETTINGS_JS" file="js/views/settings.js" type="chrome_html" />
<structure name="IDR_CAMERA_SOUND_JS" file="js/sound.js" type="chrome_html" />
<structure name="IDR_CAMERA_STATE_JS" file="js/state.js" type="chrome_html" />
<structure name="IDR_CAMERA_TEST_BRIDGE_JS" file="js/test_bridge.js" type="chrome_html" />
<structure name="IDR_CAMERA_TEST_HTML" file="views/test.html" type="chrome_html" />
<structure name="IDR_CAMERA_TIMERTICK_JS" file="js/views/camera/timertick.js" type="chrome_html" />
<structure name="IDR_CAMERA_TOAST_JS" file="js/toast.js" type="chrome_html" />
<structure name="IDR_CAMERA_TOOLTIP_JS" file="js/tooltip.js" type="chrome_html" />
......
......@@ -22,6 +22,7 @@ js_type_check("closure_compile") {
js_library("compile_resources") {
sources = [
"app_window.js",
"async_job_queue.js",
"background.js",
"background_ops.js",
......@@ -60,6 +61,7 @@ js_library("compile_resources") {
"perf.js",
"sound.js",
"state.js",
"test_bridge.js",
"toast.js",
"tooltip.js",
"type.js",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// eslint-disable-next-line no-unused-vars
import {
ErrorInfo, // eslint-disable-line no-unused-vars
PerfEntry, // eslint-disable-line no-unused-vars
PerfEvent,
} from './type.js';
import {WaitableEvent} from './waitable_event.js';
/**
* Class which is used to coordinate the setup of window between Tast side and
* CCA side. Note that the methods in this class are all marked with "async"
* since the instance of this class will be wrapped by Comlink, which makes
* all synchronous calls asynchronous. Making them async will make type check
* easier.
*/
export class AppWindow {
/**
* @param {boolean} fromColdStart Whether this app is launched from a cold
* start. It is used for performance measurement.
* @public
*/
constructor(fromColdStart) {
/**
* @type {boolean}
* @private
*/
this.fromColdStart_ = fromColdStart;
/**
* A waitable event which will resolve to the URL of the CCA instance just
* launched.
* @type {!WaitableEvent<string>}
* @private
*/
this.readyOnCCASide_ = new WaitableEvent();
/**
* @type {!WaitableEvent}
* @private
*/
this.readyOnTastSide_ = new WaitableEvent();
/**
* @type {!WaitableEvent}
* @private
*/
this.onClosed_ = new WaitableEvent();
/**
* @type {boolean}
* @private
*/
this.inClosingItself_ = false;
/**
* @type {!Array<!ErrorInfo>}
*/
this.errors_ = [];
/**
* @type {!Array<!PerfEntry>}
*/
this.perfs_ = [];
/**
* @type {number}
*/
this.launchedTime_ = performance.now();
}
/**
* Waits until the window is bound and returns the URL of the window.
* @return {!Promise<string>} The URL of the window.
*/
async waitUntilWindowBound() {
return this.readyOnCCASide_.wait();
}
/**
* Binds the URL to the window.
* @param {string} url
* @return {!Promise}
*/
async bindUrl(url) {
this.readyOnCCASide_.signal(url);
}
/**
* Notifies the listener that the window setup is done on Tast side.
* @return {!Promise}
*/
async notifyReadyOnTastSide() {
this.readyOnTastSide_.signal();
}
/**
* Waits until the setup for the window is done on Tast side.
* @return {!Promise}
*/
async waitUntilReadyOnTastSide() {
return this.readyOnTastSide_.wait();
}
/**
* Triggers when CCA is fully launched.
* @return {!Promise}
*/
async onAppLaunched() {
const event = this.fromColdStart_ ?
PerfEvent.LAUNCHING_FROM_LAUNCH_APP_COLD :
PerfEvent.LAUNCHING_FROM_LAUNCH_APP_WARM;
this.perfs_.push({
event: event,
duration: (performance.now() - this.launchedTime_),
});
}
/**
* Notifies the listener that the window is closed.
* @return {!Promise}
*/
async notifyClosed() {
this.onClosed_.signal();
}
/**
* Waits until the window is closed.
* @return {!Promise}
*/
async waitUntilClosed() {
return this.onClosed_.wait();
}
/**
* Notifies the listener that the window is about to close itself.
* @return {!Promise}
*/
async notifyClosingItself() {
this.inClosingItself_ = true;
}
/**
* Check if it has received the signal that the window is about to close
* itself.
* @return {!Promise<boolean>}
*/
async isClosingItself() {
return this.inClosingItself_;
}
/**
* Reports error and makes it visible on Tast side.
* @param {!ErrorInfo} errorInfo Information of the error.
* @return {!Promise}
*/
async reportError(errorInfo) {
this.errors_.push(errorInfo);
}
/**
* Gets all the errors.
* @return {!Promise<!Array<!ErrorInfo>>}
*/
async getErrors() {
return this.errors_;
}
/**
* Reports perf information and makes it visible on Tast side.
* @param {!PerfEntry} perfEntry Information of the perf event.
* @return {!Promise}
*/
async reportPerf(perfEntry) {
this.perfs_.push(perfEntry);
}
/**
* Gets all the perf information.
* @return {!Promise<!Array<!PerfEntry>>}
*/
async getPerfs() {
return this.perfs_;
}
}
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// eslint-disable-next-line no-unused-vars
import {AppWindow} from './app_window.js';
import {
BackgroundOps, // eslint-disable-line no-unused-vars
ForegroundOps, // eslint-disable-line no-unused-vars
......@@ -11,7 +13,8 @@ import {browserProxy} from './browser_proxy/browser_proxy.js';
import {TestingErrorCallback} from './error.js';
import {Intent} from './intent.js';
import {initMetrics, setMetricsEnabled} from './metrics.js';
import {PerfEvent, PerfLogger} from './perf.js';
import {PerfLogger} from './perf.js';
import {PerfEvent} from './type.js';
/**
* Fixed minimum width of the window inner-bounds in pixels.
......@@ -135,6 +138,12 @@ class CCAWindow {
*/
this.appWindow_ = null;
/**
* @type {?AppWindow}
* @private
*/
this.testAppWindow_ = null;
/**
* @type {?ForegroundOps}
* @private
......@@ -205,6 +214,9 @@ class CCAWindow {
if (this.testingCallbacks_ !== null) {
this.testingCallbacks_.onClosed(windowUrl);
}
if (this.testAppWindow_ !== null) {
this.testAppWindow_.notifyClosed();
}
});
appWindow.contentWindow['backgroundOps'] = this;
if (this.testingCallbacks_ !== null) {
......@@ -220,6 +232,13 @@ class CCAWindow {
this.foregroundOps_ = ops;
}
/**
* @override
*/
bindAppWindow(appWindow) {
this.testAppWindow_ = appWindow;
}
/**
* @override
*/
......@@ -546,13 +565,15 @@ function handleExternalConnectionFromTest(port) {
return;
}
switch (port.name) {
// TODO(crbug.com/980846): Remove the old error reporting logic once the
// implementation using TestBridge on Tast side is ready.
case 'SET_PERF_CONNECTION':
port.onMessage.addListener((event) => {
if (perfLoggerForTesting === null) {
perfLoggerForTesting = new PerfLogger();
perfLoggerForTesting.addListener((event, duration, extras) => {
port.postMessage({event, duration, extras});
perfLoggerForTesting.addListener(({event, duration, perfInfo}) => {
port.postMessage({event, duration, extras: perfInfo});
});
}
......@@ -600,4 +621,4 @@ chrome.app.runtime.onLaunched.addListener((launchData) => {
}
});
browserProxy.addOnConnectExternalListener(handleExternalConnectionFromTest);
chrome.runtime.onConnectExternal.addListener(handleExternalConnectionFromTest);
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// eslint-disable-next-line no-unused-vars
import {AppWindow} from './app_window.js';
// eslint-disable-next-line no-unused-vars
import {TestingErrorCallback} from './error.js';
// eslint-disable-next-line no-unused-vars
......@@ -39,6 +41,12 @@ export class BackgroundOps {
*/
bindForegroundOps(ops) {}
/**
* Sets the app window which is associated to the foreground window.
* @param {?AppWindow} appWindow
*/
bindAppWindow(appWindow) {}
/**
* Gets intent associate with CCA Window object.
* @return {?Intent}
......
......@@ -92,6 +92,11 @@ class ChromeAppBrowserProxy {
items);
}
/** @override */
localStorageClear() {
return promisify(chrome.storage.local.clear.bind(chrome.storage.local))();
}
/** @override */
async getBoard() {
const values = await promisify(chrome.chromeosInfoPrivate.get)(['board']);
......@@ -145,16 +150,6 @@ class ChromeAppBrowserProxy {
return this.getI18nMessage('@@bidi_dir');
}
/** @override */
addOnMessageExternalListener(listener) {
chrome.runtime.onMessageExternal.addListener(listener);
}
/** @override */
addOnConnectExternalListener(listener) {
chrome.runtime.onConnectExternal.addListener(listener);
}
/** @override */
addDummyHistoryIfNotAvailable() {
// Since GA will use history.length to generate hash but it is not available
......@@ -262,6 +257,18 @@ class ChromeAppBrowserProxy {
const id = 'gfdkimpbcpahaombhbimeihdjnejgicl'; // Feedback extension id.
chrome.runtime.sendMessage(id, data);
}
/** @override */
setupUnloadListener(listener) {
// Platform app should use chrome.app.window.AppWindow onClosed event
// listener in background page instead of window unload event listener.
}
/** @override */
async setLaunchingFromWindowCreationStartTime(callback) {
// For platform app, the start time of window creation is recorded by
// background page so we don't need to trigger it here.
}
}
export const browserProxy = new ChromeAppBrowserProxy();
......@@ -48,6 +48,12 @@ export class BrowserProxy {
*/
async localStorageRemove(items) {}
/**
* @return {!Promise}
* @abstract
*/
async localStorageClear() {}
/**
* @return {!Promise<string>}
* @abstract
......@@ -100,19 +106,6 @@ export class BrowserProxy {
*/
getTextDirection() {}
/**
* @param {function(*, !MessageSender, function(string)): (boolean|undefined)}
* listener
* @abstract
*/
addOnMessageExternalListener(listener) {}
/**
* @param {function(!Port)} listener
* @abstract
*/
addOnConnectExternalListener(listener) {}
/**
* @abstract
*/
......@@ -168,4 +161,17 @@ export class BrowserProxy {
* @abstract
*/
openFeedback() {}
/**
* @param {function(): void} listener
* @abstract
*/
setupUnloadListener(listener) {}
/**
* @param {function(): !Promise} callback
* @return {!Promise}
* @abstract
*/
async setLaunchingFromWindowCreationStartTime(callback) {}
}
......@@ -101,6 +101,11 @@ class WebUIBrowserProxy {
}
}
/** @override */
async localStorageClear() {
window.localStorage.clear();
}
/** @override */
async getBoard() {
return window.loadTimeData.getString('board_name');
......@@ -141,16 +146,6 @@ class WebUIBrowserProxy {
return window.loadTimeData.getString('textdirection');
}
/** @override */
addOnMessageExternalListener(listener) {
throw new NotImplementedError();
}
/** @override */
addOnConnectExternalListener(listener) {
throw new NotImplementedError();
}
/** @override */
addDummyHistoryIfNotAvailable() {
// no-ops
......@@ -170,6 +165,7 @@ class WebUIBrowserProxy {
const intent = url.includes('intent') ? Intent.create(new URL(url)) : null;
return /** @type {!BackgroundOps} */ ({
bindForegroundOps: (ops) => {},
bindAppWindow: (appWindow) => {},
getIntent: () => intent,
getPerfLogger: () => perfLogger,
getTestingErrorCallback: () => null,
......@@ -215,6 +211,16 @@ class WebUIBrowserProxy {
ChromeHelper.getInstance().openFeedbackDialog(
this.getI18nMessage('feedback_description_placeholder'));
}
/** @override */
setupUnloadListener(listener) {
window.addEventListener('unload', listener);
}
/** @override */
async setLaunchingFromWindowCreationStartTime(callback) {
await callback();
}
}
export const browserProxy = new WebUIBrowserProxy();
......
......@@ -2,37 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// eslint-disable-next-line no-unused-vars
import {AppWindow} from './app_window.js';
import {assertInstanceof} from './chrome_util.js';
import * as metrics from './metrics.js';
/**
* Types of error used in ERROR metrics.
* @enum {string}
*/
export const ErrorType = {
BROKEN_THUMBNAIL: 'broken-thumbnail',
UNCAUGHT_PROMISE: 'uncaught-promise',
};
/**
* Error level used in ERROR metrics.
* @enum {string}
*/
export const ErrorLevel = {
WARNING: 'WARNING',
ERROR: 'ERROR',
};
/**
* Error reported in testing run.
* @typedef {{
* type: !ErrorType,
* level: !ErrorLevel,
* stack: string,
* time: number,
* }}
*/
let ErrorInfo; // eslint-disable-line no-unused-vars
import {
ErrorInfo, // eslint-disable-line no-unused-vars
ErrorLevel,
ErrorType,
} from './type.js';
/**
* Callback for reporting error in testing run.
......@@ -142,6 +120,11 @@ export function formatErrorStack(error) {
*/
let onTestingError = null;
/**
* @type {?AppWindow}
*/
const appWindow = window['appWindow'];
/**
* Initializes error collecting functions.
* @param {?TestingErrorCallback} onError Callback for reporting error in
......@@ -193,10 +176,16 @@ export function reportError(type, level, error) {
}
triggeredErrorSet.add(hash);
// TODO(crbug.com/980846): Remove the old error reporting logic once the
// implementation using TestBridge on Tast side is ready.
if (onTestingError !== null) {
onTestingError({type, level, stack: formatErrorStack(error), time});
return;
}
if (appWindow !== null) {
appWindow.reportError({type, level, stack: formatErrorStack(error), time});
return;
}
metrics.sendErrorEvent(
{type, level, errorName, fileName, funcName, lineNo, colNo});
}
......@@ -2,7 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
document.addEventListener('DOMContentLoaded', () => {
import * as Comlink from './lib/comlink.js';
// eslint-disable-next-line no-unused-vars
document.addEventListener('DOMContentLoaded', async () => {
const workerPath = '/js/test_bridge.js';
// The cast here is a workaround to avoid warning in closure compiler since
// it does not have such signature. We need to fix it in upstream.
// TODO(crbug.com/980846): Remove the cast once the PR is merged:
// https://github.com/google/closure-compiler/pull/3704
const sharedWorker =
new SharedWorker(workerPath, /** @type {string} */ ({type: 'module'}));
const testBridge = Comlink.wrap(sharedWorker.port);
const appWindow = await testBridge.bindWindow(window.location.href);
// TODO(crbug.com/980846): Refactor to use a better way rather than window
// properties to pass data to other modules.
window['appWindow'] = appWindow;
window['windowCreationTime'] = performance.now();
if (appWindow !== null) {
await appWindow.waitUntilReadyOnTastSide();
}
const mainScript = document.createElement('script');
mainScript.setAttribute('type', 'module');
mainScript.setAttribute('src', '/js/main.js');
......
......@@ -121,8 +121,11 @@ export class Intent {
return;
}
this.done_ = true;
await this.chromeHelper_.cancel(this.intentId);
// TODO(crbug.com/1125997): We send the metrics before the actual action
// here to workaround the issue that codes behind "await" might not be
// executed when unloading window.
this.logResult(metrics.IntentResultType.CANCELED);
await this.chromeHelper_.cancel(this.intentId);
}
/**
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// eslint-disable-next-line no-unused-vars
import {AppWindow} from './app_window.js';
import {
BackgroundOps, // eslint-disable-line no-unused-vars
ForegroundOps, // eslint-disable-line no-unused-vars
......@@ -19,10 +21,9 @@ import {GalleryButton} from './gallerybutton.js';
import * as metrics from './metrics.js';
import * as filesystem from './models/file_system.js';
import * as nav from './nav.js';
import {PerfEvent} from './perf.js';
import * as state from './state.js';
import * as tooltip from './tooltip.js';
import {Mode, ViewName} from './type.js';
import {Mode, PerfEvent, ViewName} from './type.js';
import * as util from './util.js';
import {Camera} from './views/camera.js';
import {CameraIntent} from './views/camera_intent.js';
......@@ -35,6 +36,11 @@ import {
import {View} from './views/view.js';
import {Warning} from './views/warning.js';
/**
* @type {?AppWindow}
*/
const appWindow = window['appWindow'];
/**
* Creates the Camera App main object.
* @implements {ForegroundOps}
......@@ -124,6 +130,7 @@ export class App {
nav.open(ViewName.SPLASH);
this.backgroundOps_.bindForegroundOps(this);
this.backgroundOps_.bindAppWindow(appWindow);
}
/**
......@@ -211,7 +218,16 @@ export class App {
const isSuccess = await this.cameraView_.start();
nav.close(ViewName.SPLASH);
nav.open(ViewName.CAMERA);
this.backgroundOps_.getPerfLogger().stopLaunch({hasError: !isSuccess});
await browserProxy.setLaunchingFromWindowCreationStartTime(async () => {
const windowCreationTime = window['windowCreationTime'];
this.backgroundOps_.getPerfLogger().start(
PerfEvent.LAUNCHING_FROM_WINDOW_CREATION, windowCreationTime);
});
this.backgroundOps_.getPerfLogger().stop(
PerfEvent.LAUNCHING_FROM_WINDOW_CREATION, {hasError: !isSuccess});
if (appWindow !== null) {
appWindow.onAppLaunched();
}
})();
metrics.sendLaunchEvent({ackMigrate: false});
......@@ -264,6 +280,18 @@ let instance = null;
}
const bgOps = browserProxy.getBackgroundOps();
browserProxy.setupUnloadListener(() => {
const intent = bgOps.getIntent();
if (intent !== null && !intent.done) {
// TODO(crbug.com/1125997): Move the task to ServiceWorker once it is
// supported on SWA.
intent.cancel();
}
if (appWindow !== null) {
appWindow.notifyClosed();
}
});
const testErrorCallback = bgOps.getTestingErrorCallback();
metrics.initMetrics();
if (testErrorCallback !== null) {
......@@ -276,8 +304,22 @@ let instance = null;
const perfLogger = bgOps.getPerfLogger();
// Setup listener for performance events.
perfLogger.addListener((event, duration, extras) => {
metrics.sendPerfEvent({event, duration, extras});
perfLogger.addListener(({event, duration, perfInfo}) => {
metrics.sendPerfEvent({event, duration, perfInfo});
// Setup for console perf logger.
if (state.get(state.State.PRINT_PERFORMANCE_LOGS)) {
// eslint-disable-next-line no-console
console.log(
'%c%s %s ms %s', 'color: #4E4F97; font-weight: bold;',
event.padEnd(40), duration.toFixed(0).padStart(4),
JSON.stringify(perfInfo));
}
// Setup for Tast tests logger.
if (appWindow !== null) {
appWindow.reportPerf({event, duration, perfInfo});
}
});
const states = Object.values(PerfEvent);
states.push(state.State.TAKING);
......@@ -302,17 +344,6 @@ let instance = null;
});
});
// Setup for console perf logger.
perfLogger.addListener((event, duration, extras) => {
if (state.get(state.State.PRINT_PERFORMANCE_LOGS)) {
// eslint-disable-next-line no-console
console.log(
'%c%s %s ms %s', 'color: #4E4F97; font-weight: bold;',
event.padEnd(40), duration.toFixed(0).padStart(4),
JSON.stringify(extras));
}
});
instance = new App(
/** @type {!BackgroundOps} */ (bgOps));
await instance.start();
......
......@@ -5,12 +5,13 @@
import {browserProxy} from './browser_proxy/browser_proxy.js';
import {assert} from './chrome_util.js';
// eslint-disable-next-line no-unused-vars
import {PerfEvent} from './perf.js';
import * as state from './state.js';
import {
Facing, // eslint-disable-line no-unused-vars
Mode,
Resolution, // eslint-disable-line no-unused-vars
PerfEvent, // eslint-disable-line no-unused-vars
PerfInformation, // eslint-disable-line no-unused-vars
Resolution, // eslint-disable-line no-unused-vars
} from './type.js';
/**
......@@ -306,9 +307,9 @@ export class PerfEventParam {
this.duration;
/**
* @type {!Object|undefined} Optional information for the event.
* @type {!PerfInformation|undefined} Optional information for the event.
*/
this.extras;
this.perfInfo;
}
}
......@@ -316,9 +317,9 @@ export class PerfEventParam {
* Sends perf type event.
* @param {!PerfEventParam} param
*/
export function sendPerfEvent({event, duration, extras = {}}) {
const resolution = extras['resolution'] || '';
const facing = extras['facing'] || '';
export function sendPerfEvent({event, duration, perfInfo = {}}) {
const resolution = perfInfo['resolution'] || '';
const facing = perfInfo['facing'] || '';
sendEvent(
{
eventCategory: 'perf',
......
......@@ -4,28 +4,10 @@
import {ChromeHelper} from './mojo/chrome_helper.js';
// eslint-disable-next-line no-unused-vars
import {PerfInformation} from './type.js';
import {PerfEntry, PerfEvent, PerfInformation} from './type.js';
/**
* Type for performance event.
* @enum {string}
*/
export const PerfEvent = {
PHOTO_TAKING: 'photo-taking',
PHOTO_CAPTURE_SHUTTER: 'photo-capture-shutter',
PHOTO_CAPTURE_POST_PROCESSING: 'photo-capture-post-processing',
VIDEO_CAPTURE_POST_PROCESSING: 'video-capture-post-processing',
PORTRAIT_MODE_CAPTURE_POST_PROCESSING:
'portrait-mode-capture-post-processing',
MODE_SWITCHING: 'mode-switching',
CAMERA_SWITCHING: 'camera-switching',
LAUNCHING_FROM_WINDOW_CREATION: 'launching-from-window-creation',
LAUNCHING_FROM_LAUNCH_APP_COLD: 'launching-from-launch-app-cold',
LAUNCHING_FROM_LAUNCH_APP_WARM: 'launching-from-launch-app-warm',
};
/**
* @typedef {function(!PerfEvent, number, !Object=)}
* @typedef {function(!PerfEntry): void}
*/
let PerfEventListener; // eslint-disable-line no-unused-vars
......@@ -77,14 +59,15 @@ export class PerfLogger {
/**
* Starts the measurement for given event.
* @param {!PerfEvent} event Target event.
* @param {number=} startTime The start time of the event.
*/
start(event) {
start(event, startTime = performance.now()) {
if (this.startTimeMap_.has(event)) {
console.error(`Failed to start event ${event} since the previous one is
not stopped.`);
return;
}
this.startTimeMap_.set(event, performance.now());
this.startTimeMap_.set(event, startTime);
ChromeHelper.getInstance().startTracing(event);
}
......@@ -117,26 +100,8 @@ export class PerfLogger {
const duration = performance.now() - startTime;
ChromeHelper.getInstance().stopTracing(event);
this.listeners_.forEach((listener) => listener(event, duration, perfInfo));
}
/**
* Stops the measurement of launch-related events.
* @param {!PerfInformation=} perfInfo Optional information of this event
* for performance measurement.
*/
stopLaunch(perfInfo) {
const launchEvents = [
PerfEvent.LAUNCHING_FROM_WINDOW_CREATION,
PerfEvent.LAUNCHING_FROM_LAUNCH_APP_COLD,
PerfEvent.LAUNCHING_FROM_LAUNCH_APP_WARM,
];
launchEvents.forEach((event) => {
if (this.startTimeMap_.has(event)) {
this.stop(event, perfInfo);
}
});
this.listeners_.forEach(
(listener) => listener({event, duration, perfInfo}));
}
/**
......
......@@ -3,9 +3,9 @@
// found in the LICENSE file.
import {assert} from './chrome_util.js';
import {PerfEvent} from './perf.js';
import {
Mode,
PerfEvent,
PerfInformation, // eslint-disable-line no-unused-vars
ViewName,
} from './type.js';
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* This scripts should only be loaded as a SharedWorker and the worker is only
* used for communication between Tast tests and CCA instance. Generally, the
* SharedWorker will first be created by Tast tests when constructing the test
* bridge. And when CCA is launched, it will also connect to the SharedWorker
* during its initialization. And the SharedWorker will be close once the test
* bridge is destroyed from Tast tests side.
*/
import {AppWindow} from './app_window.js';
import {assert} from './chrome_util.js';
import * as Comlink from './lib/comlink.js';
/**
* Pending unbound AppWindow requested by tast waiting to be bound by next
* launched CCA window.
* @type {?AppWindow}
*/
let pendingAppWindow = null;
/**
* Whether the app is launched from a cold start. It will be set to false once
* an app instance is launched.
*/
let fromColdStart = true;
/**
* Registers a pending unbound AppWindow which will be bound with the URL
* later once the window is created. This method is expected to be called in
* Tast tests.
* @return {!AppWindow}
*/
export function registerUnboundWindow() {
assert(pendingAppWindow === null);
const appWindow = new AppWindow(fromColdStart);
pendingAppWindow = appWindow;
return Comlink.proxy(appWindow);
}
/**
* Binds the URL to pending AppWindow and exposes AppWindow using the URL.
* @param {string} url The URL to bind.
* @return {?AppWindow}
*/
function bindWindow(url) {
fromColdStart = false;
if (pendingAppWindow !== null) {
const appWindow = pendingAppWindow;
pendingAppWindow = null;
appWindow.bindUrl(url);
return Comlink.proxy(appWindow);
}
return null;
}
const sharedWorkerScope = /** @type {!SharedWorkerGlobalScope} */ (self);
/**
* Triggers when the Shared Worker is connected.
* @param {!Event} event
*/
sharedWorkerScope.onconnect = (event) => {
const port = /** @type {!MessageEvent} */ (event).ports[0];
Comlink.expose(
{
bindWindow,
registerUnboundWindow,
},
port);
port.start();
};
......@@ -122,14 +122,6 @@ export const ViewName = {
// comment syntax. The implementation of syntax is tracked here:
// https://github.com/google/closure-compiler/issues/3041
/**
* @typedef {{
* hasError: (boolean|undefined),
* resolution: (!Resolution|undefined),
* }}
*/
export let PerfInformation;
/**
* @typedef {{
* width: number,
......@@ -166,3 +158,67 @@ export let MaxFpsInfo;
* @typedef {!Array<!FpsRange>}
*/
export let FpsRangeList;
/**
* Type for performance event.
* @enum {string}
*/
export const PerfEvent = {
PHOTO_TAKING: 'photo-taking',
PHOTO_CAPTURE_SHUTTER: 'photo-capture-shutter',
PHOTO_CAPTURE_POST_PROCESSING: 'photo-capture-post-processing',
VIDEO_CAPTURE_POST_PROCESSING: 'video-capture-post-processing',
PORTRAIT_MODE_CAPTURE_POST_PROCESSING:
'portrait-mode-capture-post-processing',
MODE_SWITCHING: 'mode-switching',
CAMERA_SWITCHING: 'camera-switching',
LAUNCHING_FROM_WINDOW_CREATION: 'launching-from-window-creation',
LAUNCHING_FROM_LAUNCH_APP_COLD: 'launching-from-launch-app-cold',
LAUNCHING_FROM_LAUNCH_APP_WARM: 'launching-from-launch-app-warm',
};
/**
* @typedef {{
* hasError: (boolean|undefined),
* resolution: (!Resolution|undefined),
* }}
*/
export let PerfInformation;
/**
* @typedef {{
* event: !PerfEvent,
* duration: number,
* perfInfo: (!PerfInformation|undefined),
* }}
*/
export let PerfEntry;
/**
* Error reported in testing run.
* @typedef {{
* type: !ErrorType,
* level: !ErrorLevel,
* stack: string,
* time: number,
* }}
*/
export let ErrorInfo;
/**
* Types of error used in ERROR metrics.
* @enum {string}
*/
export const ErrorType = {
BROKEN_THUMBNAIL: 'broken-thumbnail',
UNCAUGHT_PROMISE: 'uncaught-promise',
};
/**
* Error level used in ERROR metrics.
* @enum {string}
*/
export const ErrorLevel = {
WARNING: 'WARNING',
ERROR: 'ERROR',
};
......@@ -4,10 +4,10 @@
import {browserProxy} from './browser_proxy/browser_proxy.js';
import {assertInstanceof} from './chrome_util.js';
import {ErrorLevel, ErrorType, reportError} from './error.js';
import {reportError} from './error.js';
import * as state from './state.js';
import * as tooltip from './tooltip.js';
import {Facing} from './type.js';
import {ErrorLevel, ErrorType, Facing} from './type.js';
/**
* Creates a canvas element for 2D drawing.
......
......@@ -6,11 +6,11 @@ import {Filenamer} from '../../../models/file_namer.js';
import * as filesystem from '../../../models/file_system.js';
import {DeviceOperator, parseMetadata} from '../../../mojo/device_operator.js';
import {CrosImageCapture} from '../../../mojo/image_capture.js';
import {PerfEvent} from '../../../perf.js';
import * as state from '../../../state.js';
import * as toast from '../../../toast.js';
import {
Facing, // eslint-disable-line no-unused-vars
PerfEvent,
Resolution,
} from '../../../type.js';
import * as util from '../../../util.js';
......
......@@ -4,11 +4,11 @@
import {Filenamer} from '../../../models/file_namer.js';
import {CrosImageCapture} from '../../../mojo/image_capture.js';
import {PerfEvent} from '../../../perf.js';
import * as state from '../../../state.js';
import * as toast from '../../../toast.js';
import {
Facing, // eslint-disable-line no-unused-vars
Facing, // eslint-disable-line no-unused-vars
PerfEvent,
Resolution, // eslint-disable-line no-unused-vars
} from '../../../type.js';
import * as util from '../../../util.js';
......
......@@ -9,12 +9,12 @@ import {Filenamer} from '../../../models/file_namer.js';
import {
VideoSaver, // eslint-disable-line no-unused-vars
} from '../../../models/video_saver.js';
import {PerfEvent} from '../../../perf.js';
import * as sound from '../../../sound.js';
import * as state from '../../../state.js';
import * as toast from '../../../toast.js';
import {
Facing, // eslint-disable-line no-unused-vars
PerfEvent,
Resolution,
ResolutionList, // eslint-disable-line no-unused-vars
} from '../../../type.js';
......
......@@ -9,9 +9,8 @@ import {Camera3DeviceInfo} from '../../device/camera3_device_info.js';
import {DeviceInfoUpdater} from '../../device/device_info_updater.js';
import * as dom from '../../dom.js';
import * as nav from '../../nav.js';
import {PerfEvent} from '../../perf.js';
import * as state from '../../state.js';
import {Facing, ViewName} from '../../type.js';
import {Facing, PerfEvent, ViewName} from '../../type.js';
import * as util from '../../util.js';
/**
......
......@@ -167,7 +167,15 @@ export class CameraIntent extends Camera {
});
if (confirmed) {
await this.intent_.finish();
window.close();
const appWindow = window['appWindow'];
if (appWindow === null) {
window.close();
} else {
// For test session, we notify tests and let test close the window for
// us.
await appWindow.notifyClosingItself();
}
return;
}
this.focus(); // Refocus the visible shutter button for ChromeVox.
......
<!doctype html>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html>
<head>
</head>
</html>
......@@ -7,8 +7,7 @@
namespace chromeos {
const char kChromeUICameraAppHost[] = "camera-app";
const char kChromeUICameraAppMainURL[] =
"chrome://camera-app/views/main.html";
const char kChromeUICameraAppMainURL[] = "chrome://camera-app/views/main.html";
const char kChromeUICameraAppURL[] = "chrome://camera-app/";
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment