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 @@ ...@@ -4,6 +4,8 @@
#include "chrome/browser/chromeos/web_applications/chrome_camera_app_ui_delegate.h" #include "chrome/browser/chromeos/web_applications/chrome_camera_app_ui_delegate.h"
#include <vector>
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/system/sys_info.h" #include "base/system/sys_info.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h" #include "chrome/browser/apps/app_service/app_service_proxy.h"
...@@ -24,6 +26,7 @@ ...@@ -24,6 +26,7 @@
#include "content/public/browser/web_ui_data_source.h" #include "content/public/browser/web_ui_data_source.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h" #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "ui/gfx/native_widget_types.h" #include "ui/gfx/native_widget_types.h"
#include "url/gurl.h"
namespace { namespace {
...@@ -36,15 +39,15 @@ constexpr int kDefaultWindowHeight = 486; ...@@ -36,15 +39,15 @@ constexpr int kDefaultWindowHeight = 486;
void ChromeCameraAppUIDelegate::CameraAppDialog::ShowIntent( void ChromeCameraAppUIDelegate::CameraAppDialog::ShowIntent(
const std::string& queries, const std::string& queries,
gfx::NativeWindow parent) { gfx::NativeWindow parent) {
CameraAppDialog* dialog = std::string url = chromeos::kChromeUICameraAppMainURL + queries;
new CameraAppDialog(chromeos::kChromeUICameraAppMainURL + queries); CameraAppDialog* dialog = new CameraAppDialog(url);
dialog->ShowSystemDialog(parent); dialog->ShowSystemDialog(parent);
} }
ChromeCameraAppUIDelegate::CameraAppDialog::CameraAppDialog( ChromeCameraAppUIDelegate::CameraAppDialog::CameraAppDialog(
const std::string& url) const std::string& url)
: chromeos::SystemWebDialogDelegate(GURL(url), /*title=*/base::string16()) { : chromeos::SystemWebDialogDelegate(GURL(url),
} /*title=*/base::string16()) {}
ChromeCameraAppUIDelegate::CameraAppDialog::~CameraAppDialog() {} ChromeCameraAppUIDelegate::CameraAppDialog::~CameraAppDialog() {}
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
#include "content/public/browser/media_stream_request.h" #include "content/public/browser/media_stream_request.h"
#include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui.h"
class GURL;
namespace content { namespace content {
struct MediaStreamRequest; struct MediaStreamRequest;
class RenderFrameHost; class RenderFrameHost;
......
...@@ -110,8 +110,7 @@ const struct { ...@@ -110,8 +110,7 @@ const struct {
const char* path; const char* path;
int id; int id;
} kGritResourceMap[] = { } kGritResourceMap[] = {
{"js/browser_proxy/browser_proxy.js", {"js/browser_proxy/browser_proxy.js", IDR_CAMERA_WEBUI_BROWSER_PROXY_JS},
IDR_CAMERA_WEBUI_BROWSER_PROXY_JS},
{"js/mojo/camera_intent.mojom-lite.js", {"js/mojo/camera_intent.mojom-lite.js",
IDR_CAMERA_CAMERA_INTENT_MOJOM_LITE_JS}, IDR_CAMERA_CAMERA_INTENT_MOJOM_LITE_JS},
{"js/mojo/image_capture.mojom-lite.js", {"js/mojo/image_capture.mojom-lite.js",
...@@ -122,16 +121,14 @@ const struct { ...@@ -122,16 +121,14 @@ const struct {
IDR_CAMERA_CAMERA_METADATA_MOJOM_LITE_JS}, IDR_CAMERA_CAMERA_METADATA_MOJOM_LITE_JS},
{"js/mojo/camera_metadata_tags.mojom-lite.js", {"js/mojo/camera_metadata_tags.mojom-lite.js",
IDR_CAMERA_CAMERA_METADATA_TAGS_MOJOM_LITE_JS}, IDR_CAMERA_CAMERA_METADATA_TAGS_MOJOM_LITE_JS},
{"js/mojo/camera_app.mojom-lite.js", {"js/mojo/camera_app.mojom-lite.js", IDR_CAMERA_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/mojo_bindings_lite.js", IDR_MOJO_MOJO_BINDINGS_LITE_JS},
{"js/mojo/camera_app_helper.mojom-lite.js", {"js/mojo/camera_app_helper.mojom-lite.js",
IDR_CAMERA_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/time.mojom-lite.js", IDR_CAMERA_TIME_MOJOM_LITE_JS},
{"js/mojo/idle_manager.mojom-lite.js", {"js/mojo/idle_manager.mojom-lite.js",
IDR_CAMERA_IDLE_MANAGER_MOJOM_LITE_JS}, IDR_CAMERA_IDLE_MANAGER_MOJOM_LITE_JS},
{"js/mojo/camera_app.mojom-lite.js", {"js/mojo/camera_app.mojom-lite.js", IDR_CAMERA_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/geometry.mojom-lite.js", IDR_CAMERA_GEOMETRY_MOJOM_LITE_JS},
{"js/mojo/range.mojom-lite.js", IDR_CAMERA_RANGE_MOJOM_LITE_JS}, {"js/mojo/range.mojom-lite.js", IDR_CAMERA_RANGE_MOJOM_LITE_JS},
}; };
......
...@@ -96,6 +96,7 @@ copy("chrome_camera_app_images") { ...@@ -96,6 +96,7 @@ copy("chrome_camera_app_images") {
copy("chrome_camera_app_js") { copy("chrome_camera_app_js") {
sources = [ sources = [
"js/app_window.js",
"js/async_job_queue.js", "js/async_job_queue.js",
"js/background.js", "js/background.js",
"js/background_ops.js", "js/background_ops.js",
...@@ -111,6 +112,7 @@ copy("chrome_camera_app_js") { ...@@ -111,6 +112,7 @@ copy("chrome_camera_app_js") {
"js/perf.js", "js/perf.js",
"js/sound.js", "js/sound.js",
"js/state.js", "js/state.js",
"js/test_bridge.js",
"js/toast.js", "js/toast.js",
"js/tooltip.js", "js/tooltip.js",
"js/type.js", "js/type.js",
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
<release seq="1"> <release seq="1">
<structures> <structures>
<structure name="IDR_CAMERA_ANALYTICS_JS" file="js/lib/analytics.js" type="chrome_html" /> <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_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_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" /> <structure name="IDR_CAMERA_BACKGROUND_JS" file="js/background.js" type="chrome_html" />
...@@ -64,6 +65,8 @@ ...@@ -64,6 +65,8 @@
<structure name="IDR_CAMERA_SETTINGS_JS" file="js/views/settings.js" type="chrome_html" /> <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_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_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_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_TOAST_JS" file="js/toast.js" type="chrome_html" />
<structure name="IDR_CAMERA_TOOLTIP_JS" file="js/tooltip.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") { ...@@ -22,6 +22,7 @@ js_type_check("closure_compile") {
js_library("compile_resources") { js_library("compile_resources") {
sources = [ sources = [
"app_window.js",
"async_job_queue.js", "async_job_queue.js",
"background.js", "background.js",
"background_ops.js", "background_ops.js",
...@@ -60,6 +61,7 @@ js_library("compile_resources") { ...@@ -60,6 +61,7 @@ js_library("compile_resources") {
"perf.js", "perf.js",
"sound.js", "sound.js",
"state.js", "state.js",
"test_bridge.js",
"toast.js", "toast.js",
"tooltip.js", "tooltip.js",
"type.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 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// eslint-disable-next-line no-unused-vars
import {AppWindow} from './app_window.js';
import { import {
BackgroundOps, // eslint-disable-line no-unused-vars BackgroundOps, // eslint-disable-line no-unused-vars
ForegroundOps, // 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'; ...@@ -11,7 +13,8 @@ import {browserProxy} from './browser_proxy/browser_proxy.js';
import {TestingErrorCallback} from './error.js'; import {TestingErrorCallback} from './error.js';
import {Intent} from './intent.js'; import {Intent} from './intent.js';
import {initMetrics, setMetricsEnabled} from './metrics.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. * Fixed minimum width of the window inner-bounds in pixels.
...@@ -135,6 +138,12 @@ class CCAWindow { ...@@ -135,6 +138,12 @@ class CCAWindow {
*/ */
this.appWindow_ = null; this.appWindow_ = null;
/**
* @type {?AppWindow}
* @private
*/
this.testAppWindow_ = null;
/** /**
* @type {?ForegroundOps} * @type {?ForegroundOps}
* @private * @private
...@@ -205,6 +214,9 @@ class CCAWindow { ...@@ -205,6 +214,9 @@ class CCAWindow {
if (this.testingCallbacks_ !== null) { if (this.testingCallbacks_ !== null) {
this.testingCallbacks_.onClosed(windowUrl); this.testingCallbacks_.onClosed(windowUrl);
} }
if (this.testAppWindow_ !== null) {
this.testAppWindow_.notifyClosed();
}
}); });
appWindow.contentWindow['backgroundOps'] = this; appWindow.contentWindow['backgroundOps'] = this;
if (this.testingCallbacks_ !== null) { if (this.testingCallbacks_ !== null) {
...@@ -220,6 +232,13 @@ class CCAWindow { ...@@ -220,6 +232,13 @@ class CCAWindow {
this.foregroundOps_ = ops; this.foregroundOps_ = ops;
} }
/**
* @override
*/
bindAppWindow(appWindow) {
this.testAppWindow_ = appWindow;
}
/** /**
* @override * @override
*/ */
...@@ -546,13 +565,15 @@ function handleExternalConnectionFromTest(port) { ...@@ -546,13 +565,15 @@ function handleExternalConnectionFromTest(port) {
return; return;
} }
switch (port.name) { 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': case 'SET_PERF_CONNECTION':
port.onMessage.addListener((event) => { port.onMessage.addListener((event) => {
if (perfLoggerForTesting === null) { if (perfLoggerForTesting === null) {
perfLoggerForTesting = new PerfLogger(); perfLoggerForTesting = new PerfLogger();
perfLoggerForTesting.addListener((event, duration, extras) => { perfLoggerForTesting.addListener(({event, duration, perfInfo}) => {
port.postMessage({event, duration, extras}); port.postMessage({event, duration, extras: perfInfo});
}); });
} }
...@@ -600,4 +621,4 @@ chrome.app.runtime.onLaunched.addListener((launchData) => { ...@@ -600,4 +621,4 @@ chrome.app.runtime.onLaunched.addListener((launchData) => {
} }
}); });
browserProxy.addOnConnectExternalListener(handleExternalConnectionFromTest); chrome.runtime.onConnectExternal.addListener(handleExternalConnectionFromTest);
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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 // eslint-disable-next-line no-unused-vars
import {TestingErrorCallback} from './error.js'; import {TestingErrorCallback} from './error.js';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
...@@ -39,6 +41,12 @@ export class BackgroundOps { ...@@ -39,6 +41,12 @@ export class BackgroundOps {
*/ */
bindForegroundOps(ops) {} 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. * Gets intent associate with CCA Window object.
* @return {?Intent} * @return {?Intent}
......
...@@ -92,6 +92,11 @@ class ChromeAppBrowserProxy { ...@@ -92,6 +92,11 @@ class ChromeAppBrowserProxy {
items); items);
} }
/** @override */
localStorageClear() {
return promisify(chrome.storage.local.clear.bind(chrome.storage.local))();
}
/** @override */ /** @override */
async getBoard() { async getBoard() {
const values = await promisify(chrome.chromeosInfoPrivate.get)(['board']); const values = await promisify(chrome.chromeosInfoPrivate.get)(['board']);
...@@ -145,16 +150,6 @@ class ChromeAppBrowserProxy { ...@@ -145,16 +150,6 @@ class ChromeAppBrowserProxy {
return this.getI18nMessage('@@bidi_dir'); return this.getI18nMessage('@@bidi_dir');
} }
/** @override */
addOnMessageExternalListener(listener) {
chrome.runtime.onMessageExternal.addListener(listener);
}
/** @override */
addOnConnectExternalListener(listener) {
chrome.runtime.onConnectExternal.addListener(listener);
}
/** @override */ /** @override */
addDummyHistoryIfNotAvailable() { addDummyHistoryIfNotAvailable() {
// Since GA will use history.length to generate hash but it is not available // Since GA will use history.length to generate hash but it is not available
...@@ -262,6 +257,18 @@ class ChromeAppBrowserProxy { ...@@ -262,6 +257,18 @@ class ChromeAppBrowserProxy {
const id = 'gfdkimpbcpahaombhbimeihdjnejgicl'; // Feedback extension id. const id = 'gfdkimpbcpahaombhbimeihdjnejgicl'; // Feedback extension id.
chrome.runtime.sendMessage(id, data); 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(); export const browserProxy = new ChromeAppBrowserProxy();
...@@ -48,6 +48,12 @@ export class BrowserProxy { ...@@ -48,6 +48,12 @@ export class BrowserProxy {
*/ */
async localStorageRemove(items) {} async localStorageRemove(items) {}
/**
* @return {!Promise}
* @abstract
*/
async localStorageClear() {}
/** /**
* @return {!Promise<string>} * @return {!Promise<string>}
* @abstract * @abstract
...@@ -100,19 +106,6 @@ export class BrowserProxy { ...@@ -100,19 +106,6 @@ export class BrowserProxy {
*/ */
getTextDirection() {} getTextDirection() {}
/**
* @param {function(*, !MessageSender, function(string)): (boolean|undefined)}
* listener
* @abstract
*/
addOnMessageExternalListener(listener) {}
/**
* @param {function(!Port)} listener
* @abstract
*/
addOnConnectExternalListener(listener) {}
/** /**
* @abstract * @abstract
*/ */
...@@ -168,4 +161,17 @@ export class BrowserProxy { ...@@ -168,4 +161,17 @@ export class BrowserProxy {
* @abstract * @abstract
*/ */
openFeedback() {} openFeedback() {}
/**
* @param {function(): void} listener
* @abstract
*/
setupUnloadListener(listener) {}
/**
* @param {function(): !Promise} callback
* @return {!Promise}
* @abstract
*/
async setLaunchingFromWindowCreationStartTime(callback) {}
} }
...@@ -101,6 +101,11 @@ class WebUIBrowserProxy { ...@@ -101,6 +101,11 @@ class WebUIBrowserProxy {
} }
} }
/** @override */
async localStorageClear() {
window.localStorage.clear();
}
/** @override */ /** @override */
async getBoard() { async getBoard() {
return window.loadTimeData.getString('board_name'); return window.loadTimeData.getString('board_name');
...@@ -141,16 +146,6 @@ class WebUIBrowserProxy { ...@@ -141,16 +146,6 @@ class WebUIBrowserProxy {
return window.loadTimeData.getString('textdirection'); return window.loadTimeData.getString('textdirection');
} }
/** @override */
addOnMessageExternalListener(listener) {
throw new NotImplementedError();
}
/** @override */
addOnConnectExternalListener(listener) {
throw new NotImplementedError();
}
/** @override */ /** @override */
addDummyHistoryIfNotAvailable() { addDummyHistoryIfNotAvailable() {
// no-ops // no-ops
...@@ -170,6 +165,7 @@ class WebUIBrowserProxy { ...@@ -170,6 +165,7 @@ class WebUIBrowserProxy {
const intent = url.includes('intent') ? Intent.create(new URL(url)) : null; const intent = url.includes('intent') ? Intent.create(new URL(url)) : null;
return /** @type {!BackgroundOps} */ ({ return /** @type {!BackgroundOps} */ ({
bindForegroundOps: (ops) => {}, bindForegroundOps: (ops) => {},
bindAppWindow: (appWindow) => {},
getIntent: () => intent, getIntent: () => intent,
getPerfLogger: () => perfLogger, getPerfLogger: () => perfLogger,
getTestingErrorCallback: () => null, getTestingErrorCallback: () => null,
...@@ -215,6 +211,16 @@ class WebUIBrowserProxy { ...@@ -215,6 +211,16 @@ class WebUIBrowserProxy {
ChromeHelper.getInstance().openFeedbackDialog( ChromeHelper.getInstance().openFeedbackDialog(
this.getI18nMessage('feedback_description_placeholder')); this.getI18nMessage('feedback_description_placeholder'));
} }
/** @override */
setupUnloadListener(listener) {
window.addEventListener('unload', listener);
}
/** @override */
async setLaunchingFromWindowCreationStartTime(callback) {
await callback();
}
} }
export const browserProxy = new WebUIBrowserProxy(); export const browserProxy = new WebUIBrowserProxy();
......
...@@ -2,37 +2,15 @@ ...@@ -2,37 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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 {assertInstanceof} from './chrome_util.js';
import * as metrics from './metrics.js'; import * as metrics from './metrics.js';
import {
/** ErrorInfo, // eslint-disable-line no-unused-vars
* Types of error used in ERROR metrics. ErrorLevel,
* @enum {string} ErrorType,
*/ } from './type.js';
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
/** /**
* Callback for reporting error in testing run. * Callback for reporting error in testing run.
...@@ -142,6 +120,11 @@ export function formatErrorStack(error) { ...@@ -142,6 +120,11 @@ export function formatErrorStack(error) {
*/ */
let onTestingError = null; let onTestingError = null;
/**
* @type {?AppWindow}
*/
const appWindow = window['appWindow'];
/** /**
* Initializes error collecting functions. * Initializes error collecting functions.
* @param {?TestingErrorCallback} onError Callback for reporting error in * @param {?TestingErrorCallback} onError Callback for reporting error in
...@@ -193,10 +176,16 @@ export function reportError(type, level, error) { ...@@ -193,10 +176,16 @@ export function reportError(type, level, error) {
} }
triggeredErrorSet.add(hash); 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) { if (onTestingError !== null) {
onTestingError({type, level, stack: formatErrorStack(error), time}); onTestingError({type, level, stack: formatErrorStack(error), time});
return; return;
} }
if (appWindow !== null) {
appWindow.reportError({type, level, stack: formatErrorStack(error), time});
return;
}
metrics.sendErrorEvent( metrics.sendErrorEvent(
{type, level, errorName, fileName, funcName, lineNo, colNo}); {type, level, errorName, fileName, funcName, lineNo, colNo});
} }
...@@ -2,7 +2,27 @@ ...@@ -2,7 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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'); const mainScript = document.createElement('script');
mainScript.setAttribute('type', 'module'); mainScript.setAttribute('type', 'module');
mainScript.setAttribute('src', '/js/main.js'); mainScript.setAttribute('src', '/js/main.js');
......
...@@ -121,8 +121,11 @@ export class Intent { ...@@ -121,8 +121,11 @@ export class Intent {
return; return;
} }
this.done_ = true; 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); this.logResult(metrics.IntentResultType.CANCELED);
await this.chromeHelper_.cancel(this.intentId);
} }
/** /**
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// eslint-disable-next-line no-unused-vars
import {AppWindow} from './app_window.js';
import { import {
BackgroundOps, // eslint-disable-line no-unused-vars BackgroundOps, // eslint-disable-line no-unused-vars
ForegroundOps, // eslint-disable-line no-unused-vars ForegroundOps, // eslint-disable-line no-unused-vars
...@@ -19,10 +21,9 @@ import {GalleryButton} from './gallerybutton.js'; ...@@ -19,10 +21,9 @@ import {GalleryButton} from './gallerybutton.js';
import * as metrics from './metrics.js'; import * as metrics from './metrics.js';
import * as filesystem from './models/file_system.js'; import * as filesystem from './models/file_system.js';
import * as nav from './nav.js'; import * as nav from './nav.js';
import {PerfEvent} from './perf.js';
import * as state from './state.js'; import * as state from './state.js';
import * as tooltip from './tooltip.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 * as util from './util.js';
import {Camera} from './views/camera.js'; import {Camera} from './views/camera.js';
import {CameraIntent} from './views/camera_intent.js'; import {CameraIntent} from './views/camera_intent.js';
...@@ -35,6 +36,11 @@ import { ...@@ -35,6 +36,11 @@ import {
import {View} from './views/view.js'; import {View} from './views/view.js';
import {Warning} from './views/warning.js'; import {Warning} from './views/warning.js';
/**
* @type {?AppWindow}
*/
const appWindow = window['appWindow'];
/** /**
* Creates the Camera App main object. * Creates the Camera App main object.
* @implements {ForegroundOps} * @implements {ForegroundOps}
...@@ -124,6 +130,7 @@ export class App { ...@@ -124,6 +130,7 @@ export class App {
nav.open(ViewName.SPLASH); nav.open(ViewName.SPLASH);
this.backgroundOps_.bindForegroundOps(this); this.backgroundOps_.bindForegroundOps(this);
this.backgroundOps_.bindAppWindow(appWindow);
} }
/** /**
...@@ -211,7 +218,16 @@ export class App { ...@@ -211,7 +218,16 @@ export class App {
const isSuccess = await this.cameraView_.start(); const isSuccess = await this.cameraView_.start();
nav.close(ViewName.SPLASH); nav.close(ViewName.SPLASH);
nav.open(ViewName.CAMERA); 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}); metrics.sendLaunchEvent({ackMigrate: false});
...@@ -264,6 +280,18 @@ let instance = null; ...@@ -264,6 +280,18 @@ let instance = null;
} }
const bgOps = browserProxy.getBackgroundOps(); 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(); const testErrorCallback = bgOps.getTestingErrorCallback();
metrics.initMetrics(); metrics.initMetrics();
if (testErrorCallback !== null) { if (testErrorCallback !== null) {
...@@ -276,8 +304,22 @@ let instance = null; ...@@ -276,8 +304,22 @@ let instance = null;
const perfLogger = bgOps.getPerfLogger(); const perfLogger = bgOps.getPerfLogger();
// Setup listener for performance events. // Setup listener for performance events.
perfLogger.addListener((event, duration, extras) => { perfLogger.addListener(({event, duration, perfInfo}) => {
metrics.sendPerfEvent({event, duration, extras}); 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); const states = Object.values(PerfEvent);
states.push(state.State.TAKING); states.push(state.State.TAKING);
...@@ -302,17 +344,6 @@ let instance = null; ...@@ -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( instance = new App(
/** @type {!BackgroundOps} */ (bgOps)); /** @type {!BackgroundOps} */ (bgOps));
await instance.start(); await instance.start();
......
...@@ -5,12 +5,13 @@ ...@@ -5,12 +5,13 @@
import {browserProxy} from './browser_proxy/browser_proxy.js'; import {browserProxy} from './browser_proxy/browser_proxy.js';
import {assert} from './chrome_util.js'; import {assert} from './chrome_util.js';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import {PerfEvent} from './perf.js';
import * as state from './state.js'; import * as state from './state.js';
import { import {
Facing, // eslint-disable-line no-unused-vars Facing, // eslint-disable-line no-unused-vars
Mode, 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'; } from './type.js';
/** /**
...@@ -306,9 +307,9 @@ export class PerfEventParam { ...@@ -306,9 +307,9 @@ export class PerfEventParam {
this.duration; 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 { ...@@ -316,9 +317,9 @@ export class PerfEventParam {
* Sends perf type event. * Sends perf type event.
* @param {!PerfEventParam} param * @param {!PerfEventParam} param
*/ */
export function sendPerfEvent({event, duration, extras = {}}) { export function sendPerfEvent({event, duration, perfInfo = {}}) {
const resolution = extras['resolution'] || ''; const resolution = perfInfo['resolution'] || '';
const facing = extras['facing'] || ''; const facing = perfInfo['facing'] || '';
sendEvent( sendEvent(
{ {
eventCategory: 'perf', eventCategory: 'perf',
......
...@@ -4,28 +4,10 @@ ...@@ -4,28 +4,10 @@
import {ChromeHelper} from './mojo/chrome_helper.js'; import {ChromeHelper} from './mojo/chrome_helper.js';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import {PerfInformation} from './type.js'; import {PerfEntry, PerfEvent, PerfInformation} from './type.js';
/** /**
* Type for performance event. * @typedef {function(!PerfEntry): void}
* @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=)}
*/ */
let PerfEventListener; // eslint-disable-line no-unused-vars let PerfEventListener; // eslint-disable-line no-unused-vars
...@@ -77,14 +59,15 @@ export class PerfLogger { ...@@ -77,14 +59,15 @@ export class PerfLogger {
/** /**
* Starts the measurement for given event. * Starts the measurement for given event.
* @param {!PerfEvent} event Target 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)) { if (this.startTimeMap_.has(event)) {
console.error(`Failed to start event ${event} since the previous one is console.error(`Failed to start event ${event} since the previous one is
not stopped.`); not stopped.`);
return; return;
} }
this.startTimeMap_.set(event, performance.now()); this.startTimeMap_.set(event, startTime);
ChromeHelper.getInstance().startTracing(event); ChromeHelper.getInstance().startTracing(event);
} }
...@@ -117,26 +100,8 @@ export class PerfLogger { ...@@ -117,26 +100,8 @@ export class PerfLogger {
const duration = performance.now() - startTime; const duration = performance.now() - startTime;
ChromeHelper.getInstance().stopTracing(event); ChromeHelper.getInstance().stopTracing(event);
this.listeners_.forEach((listener) => listener(event, duration, perfInfo)); 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);
}
});
} }
/** /**
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
// found in the LICENSE file. // found in the LICENSE file.
import {assert} from './chrome_util.js'; import {assert} from './chrome_util.js';
import {PerfEvent} from './perf.js';
import { import {
Mode, Mode,
PerfEvent,
PerfInformation, // eslint-disable-line no-unused-vars PerfInformation, // eslint-disable-line no-unused-vars
ViewName, ViewName,
} from './type.js'; } 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 = { ...@@ -122,14 +122,6 @@ export const ViewName = {
// comment syntax. The implementation of syntax is tracked here: // comment syntax. The implementation of syntax is tracked here:
// https://github.com/google/closure-compiler/issues/3041 // https://github.com/google/closure-compiler/issues/3041
/**
* @typedef {{
* hasError: (boolean|undefined),
* resolution: (!Resolution|undefined),
* }}
*/
export let PerfInformation;
/** /**
* @typedef {{ * @typedef {{
* width: number, * width: number,
...@@ -166,3 +158,67 @@ export let MaxFpsInfo; ...@@ -166,3 +158,67 @@ export let MaxFpsInfo;
* @typedef {!Array<!FpsRange>} * @typedef {!Array<!FpsRange>}
*/ */
export let FpsRangeList; 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 @@ ...@@ -4,10 +4,10 @@
import {browserProxy} from './browser_proxy/browser_proxy.js'; import {browserProxy} from './browser_proxy/browser_proxy.js';
import {assertInstanceof} from './chrome_util.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 state from './state.js';
import * as tooltip from './tooltip.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. * Creates a canvas element for 2D drawing.
......
...@@ -6,11 +6,11 @@ import {Filenamer} from '../../../models/file_namer.js'; ...@@ -6,11 +6,11 @@ import {Filenamer} from '../../../models/file_namer.js';
import * as filesystem from '../../../models/file_system.js'; import * as filesystem from '../../../models/file_system.js';
import {DeviceOperator, parseMetadata} from '../../../mojo/device_operator.js'; import {DeviceOperator, parseMetadata} from '../../../mojo/device_operator.js';
import {CrosImageCapture} from '../../../mojo/image_capture.js'; import {CrosImageCapture} from '../../../mojo/image_capture.js';
import {PerfEvent} from '../../../perf.js';
import * as state from '../../../state.js'; import * as state from '../../../state.js';
import * as toast from '../../../toast.js'; import * as toast from '../../../toast.js';
import { import {
Facing, // eslint-disable-line no-unused-vars Facing, // eslint-disable-line no-unused-vars
PerfEvent,
Resolution, Resolution,
} from '../../../type.js'; } from '../../../type.js';
import * as util from '../../../util.js'; import * as util from '../../../util.js';
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
import {Filenamer} from '../../../models/file_namer.js'; import {Filenamer} from '../../../models/file_namer.js';
import {CrosImageCapture} from '../../../mojo/image_capture.js'; import {CrosImageCapture} from '../../../mojo/image_capture.js';
import {PerfEvent} from '../../../perf.js';
import * as state from '../../../state.js'; import * as state from '../../../state.js';
import * as toast from '../../../toast.js'; import * as toast from '../../../toast.js';
import { import {
Facing, // eslint-disable-line no-unused-vars Facing, // eslint-disable-line no-unused-vars
PerfEvent,
Resolution, // eslint-disable-line no-unused-vars Resolution, // eslint-disable-line no-unused-vars
} from '../../../type.js'; } from '../../../type.js';
import * as util from '../../../util.js'; import * as util from '../../../util.js';
......
...@@ -9,12 +9,12 @@ import {Filenamer} from '../../../models/file_namer.js'; ...@@ -9,12 +9,12 @@ import {Filenamer} from '../../../models/file_namer.js';
import { import {
VideoSaver, // eslint-disable-line no-unused-vars VideoSaver, // eslint-disable-line no-unused-vars
} from '../../../models/video_saver.js'; } from '../../../models/video_saver.js';
import {PerfEvent} from '../../../perf.js';
import * as sound from '../../../sound.js'; import * as sound from '../../../sound.js';
import * as state from '../../../state.js'; import * as state from '../../../state.js';
import * as toast from '../../../toast.js'; import * as toast from '../../../toast.js';
import { import {
Facing, // eslint-disable-line no-unused-vars Facing, // eslint-disable-line no-unused-vars
PerfEvent,
Resolution, Resolution,
ResolutionList, // eslint-disable-line no-unused-vars ResolutionList, // eslint-disable-line no-unused-vars
} from '../../../type.js'; } from '../../../type.js';
......
...@@ -9,9 +9,8 @@ import {Camera3DeviceInfo} from '../../device/camera3_device_info.js'; ...@@ -9,9 +9,8 @@ import {Camera3DeviceInfo} from '../../device/camera3_device_info.js';
import {DeviceInfoUpdater} from '../../device/device_info_updater.js'; import {DeviceInfoUpdater} from '../../device/device_info_updater.js';
import * as dom from '../../dom.js'; import * as dom from '../../dom.js';
import * as nav from '../../nav.js'; import * as nav from '../../nav.js';
import {PerfEvent} from '../../perf.js';
import * as state from '../../state.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'; import * as util from '../../util.js';
/** /**
......
...@@ -167,7 +167,15 @@ export class CameraIntent extends Camera { ...@@ -167,7 +167,15 @@ export class CameraIntent extends Camera {
}); });
if (confirmed) { if (confirmed) {
await this.intent_.finish(); 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; return;
} }
this.focus(); // Refocus the visible shutter button for ChromeVox. 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 @@ ...@@ -7,8 +7,7 @@
namespace chromeos { namespace chromeos {
const char kChromeUICameraAppHost[] = "camera-app"; const char kChromeUICameraAppHost[] = "camera-app";
const char kChromeUICameraAppMainURL[] = const char kChromeUICameraAppMainURL[] = "chrome://camera-app/views/main.html";
"chrome://camera-app/views/main.html";
const char kChromeUICameraAppURL[] = "chrome://camera-app/"; const char kChromeUICameraAppURL[] = "chrome://camera-app/";
} // namespace chromeos } // 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