summaryrefslogtreecommitdiff
path: root/chromium/ui/webui
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-12-10 16:19:40 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-12-10 16:01:50 +0000
commit51f6c2793adab2d864b3d2b360000ef8db1d3e92 (patch)
tree835b3b4446b012c75e80177cef9fbe6972cc7dbe /chromium/ui/webui
parent6036726eb981b6c4b42047513b9d3f4ac865daac (diff)
downloadqtwebengine-chromium-51f6c2793adab2d864b3d2b360000ef8db1d3e92.tar.gz
BASELINE: Update Chromium to 71.0.3578.93
Change-Id: I6a32086c33670e1b033f8b10e6bf1fd4da1d105d Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/ui/webui')
-rw-r--r--chromium/ui/webui/PLATFORM_OWNERS2
-rw-r--r--chromium/ui/webui/mojo_web_ui_controller.cc6
-rw-r--r--chromium/ui/webui/resources/cr_components/BUILD.gn5
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/BUILD.gn3
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/.eslintrc.js13
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/BUILD.gn129
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/OWNERS1
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.html31
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.js46
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.html4
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js108
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/icons.html36
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html10
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.js40
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html67
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js312
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.html3
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.js41
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html4
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js33
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html19
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.html51
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.js167
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_failed_page.html18
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_failed_page.js43
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_icon_1x.pngbin0 -> 10988 bytes
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_icon_2x.pngbin0 -> 24434 bytes
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.html35
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js59
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_icon_1x.pngbin0 -> 8246 bytes
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_icon_2x.pngbin0 -> 18298 bytes
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.html135
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.js155
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page.html42
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page.js34
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html5
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.js141
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/network/network_config.html2
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js3
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn19
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.html1
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js47
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html256
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js60
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.html103
-rw-r--r--chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js377
-rw-r--r--chromium/ui/webui/resources/cr_components/cr_components_images.grdp21
-rw-r--r--chromium/ui/webui/resources/cr_components/cr_components_resources.grdp134
-rw-r--r--chromium/ui/webui/resources/cr_elements/BUILD.gn1
-rw-r--r--chromium/ui/webui/resources/cr_elements/README.md (renamed from chromium/ui/webui/resources/cr_elements/READE.md)0
-rw-r--r--chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js2
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html2
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js20
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js2
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js66
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_input/cr_input.html42
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_input/cr_input.js30
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_input/cr_input_style_css.html12
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js24
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.html3
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js2
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_slider/BUILD.gn4
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.html227
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.js518
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html1
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_view_manager/BUILD.gn19
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.html26
-rw-r--r--chromium/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.js115
-rw-r--r--chromium/ui/webui/resources/cr_elements/policy/BUILD.gn1
-rw-r--r--chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior.js36
-rw-r--r--chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js5
-rw-r--r--chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.js5
-rw-r--r--chromium/ui/webui/resources/cr_elements/shared_vars_css.html4
-rw-r--r--chromium/ui/webui/resources/cr_elements_resources.grdp6
-rw-r--r--chromium/ui/webui/resources/js/assert.js2
-rw-r--r--chromium/ui/webui/resources/js/cr/ui/dialogs.js21
-rw-r--r--chromium/ui/webui/resources/js/cr/ui/focus_grid.js2
-rw-r--r--chromium/ui/webui/resources/js/cr/ui/focus_row.js217
-rw-r--r--chromium/ui/webui/resources/js/util.js12
-rw-r--r--chromium/ui/webui/resources/js/webui_resource_test.js59
-rw-r--r--chromium/ui/webui/resources/webui_resources.grd1
81 files changed, 3859 insertions, 449 deletions
diff --git a/chromium/ui/webui/PLATFORM_OWNERS b/chromium/ui/webui/PLATFORM_OWNERS
index 29e5a4ae960..a93051bd3f8 100644
--- a/chromium/ui/webui/PLATFORM_OWNERS
+++ b/chromium/ui/webui/PLATFORM_OWNERS
@@ -1,10 +1,8 @@
# Please use more specific OWNERS when possible.
-bauerb@chromium.org
calamity@chromium.org
dpapad@chromium.org
dschuyler@chromium.org
michaelpg@chromium.org
-pam@chromium.org
scottchen@chromium.org
stevenjb@chromium.org
tommycli@chromium.org
diff --git a/chromium/ui/webui/mojo_web_ui_controller.cc b/chromium/ui/webui/mojo_web_ui_controller.cc
index e2de8d20f8a..769e194449c 100644
--- a/chromium/ui/webui/mojo_web_ui_controller.cc
+++ b/chromium/ui/webui/mojo_web_ui_controller.cc
@@ -24,6 +24,12 @@ void MojoWebUIController::OnInterfaceRequestFromFrame(
content::RenderFrameHost* render_frame_host,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle* interface_pipe) {
+ if (!registry_.CanBindInterface(interface_name)) {
+ LOG(WARNING) << "Cannot bind request to " << interface_name << "; ignoring "
+ << "request.";
+ return;
+ }
+
// Right now, this is expected to be called only for main frames.
if (render_frame_host->GetParent()) {
LOG(ERROR) << "Terminating renderer for requesting " << interface_name
diff --git a/chromium/ui/webui/resources/cr_components/BUILD.gn b/chromium/ui/webui/resources/cr_components/BUILD.gn
index f3d71a3a372..b39708e6607 100644
--- a/chromium/ui/webui/resources/cr_components/BUILD.gn
+++ b/chromium/ui/webui/resources/cr_components/BUILD.gn
@@ -7,6 +7,9 @@ import("//third_party/closure_compiler/compile_js.gni")
group("closure_compile") {
deps = [
"certificate_manager:closure_compile",
- "chromeos:closure_compile",
]
+
+ if (is_chromeos) {
+ deps += [ "chromeos:closure_compile" ]
+ }
}
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/BUILD.gn b/chromium/ui/webui/resources/cr_components/chromeos/BUILD.gn
index 0fd1a54821e..3c7e36300ab 100644
--- a/chromium/ui/webui/resources/cr_components/chromeos/BUILD.gn
+++ b/chromium/ui/webui/resources/cr_components/chromeos/BUILD.gn
@@ -4,9 +4,12 @@
import("//third_party/closure_compiler/compile_js.gni")
+assert(is_chromeos, "Only ChromeOS components belong here.")
+
group("closure_compile") {
deps = [
":chromeos_resources",
+ "multidevice_setup:closure_compile",
"network:closure_compile",
"quick_unlock:closure_compile",
]
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/.eslintrc.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/.eslintrc.js
new file mode 100644
index 00000000000..25e21f992eb
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/.eslintrc.js
@@ -0,0 +1,13 @@
+// Copyright 2018 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.
+
+module.exports = {
+ 'env': {
+ 'browser': true,
+ 'es6': true,
+ },
+ 'rules': {
+ 'no-var': 'error',
+ },
+};
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/BUILD.gn b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/BUILD.gn
new file mode 100644
index 00000000000..616ad6e48f5
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/BUILD.gn
@@ -0,0 +1,129 @@
+# Copyright 2018 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.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+assert(is_chromeos, "MultiDevice UI is Chrome OS only.")
+
+js_type_check("closure_compile") {
+ deps = [
+ ":button_bar",
+ ":fake_mojo_service",
+ ":mojo_api",
+ ":multidevice_setup",
+ ":multidevice_setup_browser_proxy",
+ ":multidevice_setup_delegate",
+ ":setup_failed_page",
+ ":setup_succeeded_page",
+ ":start_setup_page",
+ ":ui_page_container_behavior",
+ "//ui/webui/resources/js:web_ui_listener_behavior",
+ ]
+}
+
+js_library("button_bar") {
+}
+
+js_library("fake_mojo_service") {
+ deps = [
+ "//ui/webui/resources/js:cr",
+ ]
+
+ extra_deps = [
+ "//chromeos/services/device_sync/public/mojom:mojom_js",
+ "//chromeos/services/multidevice_setup/public/mojom:mojom_js",
+ "//mojo/public/mojom/base:base_js",
+ ]
+
+ externs_list = [
+ "$root_gen_dir/chromeos/services/device_sync/public/mojom/device_sync.mojom.externs.js",
+ "$root_gen_dir/chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.externs.js",
+ "$root_gen_dir/mojo/public/mojom/base/time.mojom.externs.js",
+ "$externs_path/mojo.js",
+ ]
+}
+
+js_library("mojo_api") {
+ deps = [
+ "//ui/webui/resources/js:cr",
+ ]
+}
+
+js_library("multidevice_setup") {
+ deps = [
+ ":button_bar",
+ ":fake_mojo_service",
+ ":mojo_api",
+ ":multidevice_setup_delegate",
+ ":password_page",
+ ":setup_failed_page",
+ ":setup_succeeded_page",
+ ":start_setup_page",
+ "//ui/webui/resources/js:cr",
+ "//ui/webui/resources/js:web_ui_listener_behavior",
+ ]
+
+ extra_deps = [
+ "//chromeos/services/device_sync/public/mojom:mojom_js",
+ "//chromeos/services/multidevice_setup/public/mojom:mojom_js",
+ "//mojo/public/mojom/base:base_js",
+ ]
+
+ externs_list = [
+ "$root_gen_dir/chromeos/services/device_sync/public/mojom/device_sync.mojom.externs.js",
+ "$root_gen_dir/chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.externs.js",
+ "$root_gen_dir/mojo/public/mojom/base/time.mojom.externs.js",
+ "$externs_path/mojo.js",
+ ]
+}
+
+js_library("multidevice_setup_browser_proxy") {
+ deps = [
+ "//ui/webui/resources/js:cr",
+ ]
+}
+
+js_library("multidevice_setup_delegate") {
+ deps = [
+ "//ui/webui/resources/js:cr",
+ ]
+}
+
+js_library("password_page") {
+ deps = [
+ ":multidevice_setup_browser_proxy",
+ ":ui_page_container_behavior",
+ "//ui/webui/resources/cr_elements/cr_input:cr_input",
+ "//ui/webui/resources/js:cr",
+ ]
+ externs_list = [ "$externs_path/quick_unlock_private.js" ]
+ extra_sources = [ "$interfaces_path/quick_unlock_private_interface.js" ]
+}
+
+js_library("setup_failed_page") {
+ deps = [
+ ":ui_page_container_behavior",
+ ]
+}
+
+js_library("setup_succeeded_page") {
+ deps = [
+ ":multidevice_setup_browser_proxy",
+ ":ui_page_container_behavior",
+ ]
+}
+
+js_library("start_setup_page") {
+ deps = [
+ ":ui_page_container_behavior",
+ "//ui/webui/resources/js:web_ui_listener_behavior",
+ ]
+}
+
+js_library("ui_page_container_behavior") {
+ deps = [
+ "//ui/webui/resources/js:cr",
+ "//ui/webui/resources/js:i18n_behavior",
+ ]
+}
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/OWNERS b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/OWNERS
new file mode 100644
index 00000000000..aef4ecb9195
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/OWNERS
@@ -0,0 +1 @@
+file://chromeos/services/multidevice_setup/OWNERS
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.html
new file mode 100644
index 00000000000..7b6c70c4df6
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.html
@@ -0,0 +1,31 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
+
+<dom-module id="button-bar">
+ <template>
+ <style include="multidevice-setup-shared">
+ :host {
+ display: flex;
+ }
+ </style>
+ <div id="backward"
+ on-click="onBackwardButtonClicked_"
+ hidden$="[[backwardButtonHidden]]">
+ <slot name="backward-button"></slot>
+ </div>
+ <div class="flex"></div>
+ <div id="cancel"
+ on-click="onCancelButtonClicked_"
+ hidden$="[[cancelButtonHidden]]">
+ <slot name="cancel-button"></slot>
+ </div>
+ <div id="forward"
+ on-click="onForwardButtonClicked_"
+ hidden$="[[forwardButtonHidden]]">
+ <slot name="forward-button"></slot>
+ </div>
+ </template>
+ <script src="chrome://resources/cr_components/chromeos/multidevice_setup/button_bar.js">
+ </script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.js
new file mode 100644
index 00000000000..f556598dce9
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.js
@@ -0,0 +1,46 @@
+// Copyright 2018 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.
+
+/**
+ * DOM Element containing (page-dependent) navigation buttons for the
+ * MultiDevice Setup WebUI.
+ */
+Polymer({
+ is: 'button-bar',
+
+ properties: {
+ /** Whether the forward button should be hidden. */
+ forwardButtonHidden: {
+ type: Boolean,
+ value: true,
+ },
+
+ /** Whether the cancel button should be hidden. */
+ cancelButtonHidden: {
+ type: Boolean,
+ value: true,
+ },
+
+ /** Whether the backward button should be hidden. */
+ backwardButtonHidden: {
+ type: Boolean,
+ value: true,
+ },
+ },
+
+ /** @private */
+ onForwardButtonClicked_: function() {
+ this.fire('forward-navigation-requested');
+ },
+
+ /** @private */
+ onCancelButtonClicked_: function() {
+ this.fire('cancel-requested');
+ },
+
+ /** @private */
+ onBackwardButtonClicked_: function() {
+ this.fire('backward-navigation-requested');
+ },
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.html
new file mode 100644
index 00000000000..12556f5782b
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.html
@@ -0,0 +1,4 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+
+<script src="chrome://resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js">
+</script>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js
new file mode 100644
index 00000000000..48e3eaa4ef0
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.js
@@ -0,0 +1,108 @@
+// Copyright 2018 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.
+
+/**
+ * @implements {chromeos.multideviceSetup.mojom.MultiDeviceSetupImpl}
+ */
+class FakeMojoService {
+ constructor() {
+ /**
+ * The number of devices to return in a getEligibleHostDevices() call.
+ * @type {number}
+ */
+ this.deviceCount = 2;
+
+ /**
+ * Whether calls to setHostDevice() should succeed.
+ * @type {boolean}
+ */
+ this.shouldSetHostSucceed = true;
+ }
+
+ /** @override */
+ setAccountStatusChangeDelegate(delegate) {
+ // Unimplemented; never called from setup flow.
+ assertNotReached();
+ }
+
+ /** @override */
+ addHostStatusObserver(observer) {
+ // Unimplemented; never called from setup flow.
+ assertNotReached();
+ }
+
+ /** @override */
+ addFeatureStateObserver(observer) {
+ // Unimplemented; never called from setup flow.
+ assertNotReached();
+ }
+
+ /** @override */
+ getEligibleHostDevices() {
+ const deviceNames = ['Pixel', 'Pixel XL', 'Nexus 5', 'Nexus 6P'];
+ let devices = [];
+ for (let i = 0; i < this.deviceCount; i++) {
+ const deviceName = deviceNames[i % 4];
+ devices.push({deviceName: deviceName, deviceId: deviceName + '--' + i});
+ }
+ return new Promise(function(resolve, reject) {
+ resolve({eligibleHostDevices: devices});
+ });
+ }
+
+ /** @override */
+ setHostDevice(deviceId) {
+ if (this.shouldSetHostSucceed) {
+ console.log(
+ 'setHostDevice(' + deviceId + ') called; simulating ' +
+ 'success.');
+ } else {
+ console.warn('setHostDevice() called; simulating failure.');
+ }
+ return new Promise((resolve, reject) => {
+ resolve({success: this.shouldSetHostSucceed});
+ });
+ }
+
+ /** @override */
+ removeHostDevice() {
+ // Unimplemented; never called from setup flow.
+ assertNotReached();
+ }
+
+ /** @override */
+ getHostStatus() {
+ return new Promise((resolve, reject) => {
+ reject('Unimplemented; never called from setup flow.');
+ });
+ }
+
+ /** @override */
+ setFeatureEnabledState() {
+ return new Promise((resolve, reject) => {
+ reject('Unimplemented; never called from setup flow.');
+ });
+ }
+
+ /** @override */
+ getFeatureStates() {
+ return new Promise((resolve, reject) => {
+ reject('Unimplemented; never called from setup flow.');
+ });
+ }
+
+ /** @override */
+ retrySetHostNow() {
+ return new Promise((resolve, reject) => {
+ reject('Unimplemented; never called from setup flow.');
+ });
+ }
+
+ /** @override */
+ triggerEventForDebugging(type) {
+ return new Promise((resolve, reject) => {
+ reject('Unimplemented; never called from setup flow.');
+ });
+ }
+}
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/icons.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/icons.html
new file mode 100644
index 00000000000..0232cf476e4
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/icons.html
@@ -0,0 +1,36 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
+
+<iron-iconset-svg name="multidevice-setup-icons-32" size="32">
+ <svg>
+ <defs>
+ <g id="google-g" fill="none" fill-rule="evenodd">
+ <path d="M30.42 16.83a16.66 16.66 0 0 0-.264-2.966H16.5v5.609h7.804c-.336 1.812-1.358 3.348-2.894 4.376v3.638h4.686c2.742-2.524 4.324-6.242 4.324-10.657z" fill="#4285F4"></path>
+ <path d="M16.5 31c3.915 0 7.197-1.298 9.596-3.513l-4.686-3.638c-1.298.87-2.96 1.384-4.91 1.384-3.777 0-6.973-2.55-8.113-5.978H3.542v3.757C5.928 27.75 10.832 31 16.5 31z" fill="#34A853"></path>
+ <path d="M8.387 19.255c-.29-.87-.455-1.8-.455-2.755 0-.956.165-1.885.455-2.755V9.988H3.542A14.494 14.494 0 0 0 2 16.5c0 2.34.56 4.554 1.542 6.512l4.845-3.757z" fill="#FBBC05"></path>
+ <path d="M16.5 7.767c2.129 0 4.04.732 5.543 2.168l4.159-4.158C23.69 3.437 20.408 2 16.5 2 10.832 2 5.928 5.25 3.542 9.988l4.845 3.757c1.14-3.427 4.336-5.978 8.113-5.978z" fill="#EA4335"></path>
+ <path d="M2 2h29v29H2z"></path>
+ </g>
+ <g id="error-icon" fill="none" fill-rule="evenodd" transform="translate(-4 -4)">
+ <path d="M20 4C11.168 4 4 11.168 4 20s7.168 16 16 16 16-7.168 16-16S28.832 4 20 4zm1.6 24h-3.2v-3.2h3.2V28zm0-6.4h-3.2V12h3.2v9.6z" fill="#D93025" fill-rule="nonzero"></path>
+ <path d="M.8.8h38.4v38.4H.8z"></path>
+ </g>
+ </defs>
+ </svg>
+</iron-iconset-svg>
+<iron-iconset-svg name="multidevice-setup-icons-20" size="20">
+ <svg>
+ <defs>
+ <g id="messages" fill="none" fill-rule="evenodd">
+ <path d="M16.3107503,3 L3.66666667,3 C2.75,3 2,3.75 2,4.66666667 L2,18.3161621 L5.33333333,15 L16.3107503,15 C17.227417,15 17.977417,14.2328288 17.977417,13.3161621 L17.977417,4.66666667 C17.977417,3.75 17.227417,3 16.3107503,3 Z M16,13 L4,13 L4,5 L16,5 L16,13 Z M6,8 L8,8 L8,10 L6,10 L6,8 Z M9,8 L11,8 L11,10 L9,10 L9,8 Z M12,8 L14,8 L14,10 L12,10 L12,8 Z" fill="#9AA0A6"></path>
+ </g>
+ <g id="downloads" fill="none" fill-rule="evenodd">
+ <path d="M2,13 L4,13 L4,16 L16,16 L16,13 L18,13 L18,16 C18,17.1 17.1,18 16,18 L4,18 C2.9,18 2,17.1 2,16 L2,13 Z M13.59,7.59 L11,10.17 L11,2 L9,2 L9,10.17 L6.41,7.59 L5,9 L10,14 L15,9 L13.59,7.59 Z" fill="#9AA0A6"></path>
+ </g>
+ <g id="features" fill="none" fill-rule="evenodd">
+ <path d="M5,5 L18,5 L18,3.5 L5.16080729,3.5 C4.24414063,3.5 3.49414062,4.23125 3.49414062,5.125 L3.49414062,14 L1,14 L1,17 L11,17 L11,14 L5,14 L5,5 Z M18.1666667,6.49829102 L13.3713582,6.49829102 C12.9130249,6.49829102 12.5,6.86391602 12.5,7.31079102 L12.5,16.171875 C12.5,16.61875 12.9130249,17 13.3713582,17 L18.1666667,17 C18.625,17 19,16.61875 19,16.171875 L19,7.31079102 C19,6.86391602 18.625,6.49829102 18.1666667,6.49829102 Z M17.5,14 L14,14 L14,8.5 L17.5,8.5 L17.5,14 Z" fill="#9AA0A6"></path>
+ </g>
+ </defs>
+ </svg>
+</iron-iconset-svg>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html
new file mode 100644
index 00000000000..2bcb1bbc67d
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html
@@ -0,0 +1,10 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<script src="chrome://resources/js/mojo_bindings.js"></script>
+<script src="chrome://resources/js/time.mojom.js"></script>
+<script src="chrome://resources/js/chromeos/device_sync.mojom.js"></script>
+<script src="chrome://resources/js/chromeos/multidevice_setup.mojom.js">
+</script>
+<script src="chrome://resources/js/chromeos/multidevice_setup_constants.mojom.js">
+</script>
+<script src="chrome://resources/cr_components/chromeos/multidevice_setup/mojo_api.js">
+</script>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.js
new file mode 100644
index 00000000000..d2aba99703f
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.js
@@ -0,0 +1,40 @@
+// Copyright 2018 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.
+
+cr.define('multidevice_setup', function() {
+ /** @interface */
+ class MojoInterfaceProvider {
+ /**
+ * @return {!chromeos.multideviceSetup.mojom.MultiDeviceSetupImpl}
+ */
+ getInterfacePtr() {}
+ }
+
+ /** @implements {multidevice_setup.MojoInterfaceProvider} */
+ class MojoInterfaceProviderImpl {
+ constructor() {
+ /** @private {?chromeos.multideviceSetup.mojom.MultiDeviceSetupPtr} */
+ this.ptr_ = null;
+ }
+
+ /** @override */
+ getInterfacePtr() {
+ if (!this.ptr_) {
+ this.ptr_ = new chromeos.multideviceSetup.mojom.MultiDeviceSetupPtr();
+ Mojo.bindInterface(
+ chromeos.multideviceSetup.mojom.MultiDeviceSetup.name,
+ mojo.makeRequest(this.ptr_).handle);
+ }
+
+ return this.ptr_;
+ }
+ }
+
+ cr.addSingletonGetter(MojoInterfaceProviderImpl);
+
+ return {
+ MojoInterfaceProvider: MojoInterfaceProvider,
+ MojoInterfaceProviderImpl: MojoInterfaceProviderImpl,
+ };
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html
new file mode 100644
index 00000000000..a75218ee419
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html
@@ -0,0 +1,67 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/button_bar.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/mojo_api.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/password_page.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/setup_failed_page.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/start_setup_page.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
+
+<dom-module id="multidevice-setup">
+ <template>
+ <style include="multidevice-setup-shared">
+ :host {
+ @apply --layout-vertical;
+ box-sizing: border-box;
+ color: var(--google-grey-700);
+ font-size: 13px;
+ height: 640px;
+ line-height: 16px;
+ margin: auto;
+ padding: 60px 32px 32px 32px;
+ width: 768px;
+ }
+
+ iron-pages {
+ padding: 0 32px;
+ }
+ </style>
+ <iron-pages attr-for-selected="is"
+ selected="[[visiblePageName_]]"
+ selected-item="{{visiblePage_}}">
+ <template is="dom-if" if="[[shouldPasswordPageBeIncluded_(delegate)]]"
+ restamp>
+ <password-page auth-token="{{authToken_}}"
+ forward-button-disabled="{{passwordPageForwardButtonDisabled_}}"
+ password-field-valid="{{passwordFieldValid}}"
+ on-user-submitted-password="onUserSubmittedPassword_">
+ </password-page>
+ </template>
+ <setup-failed-page></setup-failed-page>
+ <template is="dom-if"
+ if="[[shouldSetupSucceededPageBeIncluded_(delegate)]]" restamp>
+ <setup-succeeded-page></setup-succeeded-page>
+ </template>
+ <start-setup-page devices="[[devices_]]"
+ selected-device-id="{{selectedDeviceId_}}"
+ delegate="[[delegate]]">
+ </start-setup-page>
+ </iron-pages>
+ <div class="flex"></div>
+ <button-bar forward-button-hidden="[[!forwardButtonText]]"
+ backward-button-hidden="[[!backwardButtonText]]"
+ cancel-button-hidden="[[!cancelButtonText]]">
+ <slot name="backward-button" slot="backward-button"></slot>
+ <slot name="cancel-button" slot="cancel-button"></slot>
+ <slot name="forward-button" slot="forward-button"></slot>
+ </button-bar>
+ </template>
+ <script src="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js">
+ </script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js
new file mode 100644
index 00000000000..3dc0dd457d8
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js
@@ -0,0 +1,312 @@
+// Copyright 2018 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.
+
+cr.exportPath('multidevice_setup');
+
+/** @enum {string} */
+multidevice_setup.PageName = {
+ FAILURE: 'setup-failed-page',
+ PASSWORD: 'password-page',
+ SUCCESS: 'setup-succeeded-page',
+ START: 'start-setup-page',
+};
+
+cr.define('multidevice_setup', function() {
+ const PageName = multidevice_setup.PageName;
+
+ const MultiDeviceSetup = Polymer({
+ is: 'multidevice-setup',
+
+ behaviors: [WebUIListenerBehavior],
+
+ properties: {
+ /**
+ * Delegate object which performs differently in OOBE vs. non-OOBE mode.
+ * @type {!multidevice_setup.MultiDeviceSetupDelegate}
+ */
+ delegate: Object,
+
+ /**
+ * Text to be shown on the forward navigation button.
+ * @type {string|undefined}
+ */
+ forwardButtonText: {
+ type: String,
+ computed: 'getForwardButtonText_(visiblePage_)',
+ notify: true,
+ },
+
+ /** Whether the forward button should be disabled. */
+ forwardButtonDisabled: {
+ type: Boolean,
+ computed: 'shouldForwardButtonBeDisabled_(' +
+ 'passwordPageForwardButtonDisabled_, visiblePageName_)',
+ notify: true
+ },
+
+ /**
+ * Text to be shown on the cancel button.
+ * @type {string|undefined}
+ */
+ cancelButtonText: {
+ type: String,
+ computed: 'getCancelButtonText_(visiblePage_)',
+ notify: true,
+ },
+
+ /**
+ * Text to be shown on the backward navigation button.
+ * @type {string|undefined}
+ */
+ backwardButtonText: {
+ type: String,
+ computed: 'getBackwardButtonText_(visiblePage_)',
+ notify: true,
+ },
+
+ /**
+ * Element name of the currently visible page.
+ *
+ * @private {!multidevice_setup.PageName}
+ */
+ visiblePageName_: {
+ type: String,
+ value: PageName.START,
+ notify: true, // For testing purposes only.
+ },
+
+ /**
+ * DOM Element corresponding to the visible page.
+ *
+ * @private {!PasswordPageElement|!StartSetupPageElement|
+ * !SetupSucceededPageElement|!SetupFailedPageElement}
+ */
+ visiblePage_: Object,
+
+ /**
+ * Authentication token, which is generated by the password page.
+ * @private {string}
+ */
+ authToken_: {
+ type: String,
+ },
+
+ /**
+ * Array of objects representing all potential MultiDevice hosts.
+ *
+ * @private {!Array<!chromeos.deviceSync.mojom.RemoteDevice>}
+ */
+ devices_: Array,
+
+ /**
+ * Unique identifier for the currently selected host device.
+ *
+ * Undefined if the no list of potential hosts has been received from mojo
+ * service.
+ *
+ * @private {string|undefined}
+ */
+ selectedDeviceId_: String,
+
+ /**
+ * Whether the password page reports that the forward button should be
+ * disabled. This field is only relevant when the password page is
+ * visible.
+ * @private {boolean}
+ */
+ passwordPageForwardButtonDisabled_: Boolean,
+
+ /**
+ * Provider of an interface to the MultiDeviceSetup Mojo service.
+ * @private {!multidevice_setup.MojoInterfaceProvider}
+ */
+ mojoInterfaceProvider_: Object
+ },
+
+ listeners: {
+ 'backward-navigation-requested': 'onBackwardNavigationRequested_',
+ 'cancel-requested': 'onCancelRequested_',
+ 'forward-navigation-requested': 'onForwardNavigationRequested_',
+ },
+
+ /** @override */
+ created: function() {
+ this.mojoInterfaceProvider_ =
+ multidevice_setup.MojoInterfaceProviderImpl.getInstance();
+ },
+
+ /** @override */
+ ready: function() {
+ this.addWebUIListener(
+ 'multidevice_setup.initializeSetupFlow',
+ this.initializeSetupFlow.bind(this));
+ },
+
+ initializeSetupFlow: function() {
+ this.mojoInterfaceProvider_.getInterfacePtr()
+ .getEligibleHostDevices()
+ .then((responseParams) => {
+ if (responseParams.eligibleHostDevices.length == 0) {
+ console.warn('Potential host list is empty.');
+ return;
+ }
+
+ this.devices_ = responseParams.eligibleHostDevices;
+ })
+ .catch((error) => {
+ console.warn('Mojo service failure: ' + error);
+ });
+ },
+
+ /** @private */
+ onCancelRequested_: function() {
+ this.exitSetupFlow_();
+ },
+
+ /** @private */
+ onBackwardNavigationRequested_: function() {
+ // The back button is only visible on the password page.
+ assert(this.visiblePageName_ == PageName.PASSWORD);
+
+ this.$$('password-page').clearPasswordTextInput();
+ this.visiblePageName_ = PageName.START;
+ },
+
+ /** @private */
+ onForwardNavigationRequested_: function() {
+ if (this.forwardButtonDisabled)
+ return;
+
+ this.visiblePage_.getCanNavigateToNextPage().then((canNavigate) => {
+ if (!canNavigate)
+ return;
+ this.navigateForward_();
+ });
+ },
+
+ /** @private */
+ navigateForward_: function() {
+ switch (this.visiblePageName_) {
+ case PageName.FAILURE:
+ this.visiblePageName_ = PageName.START;
+ return;
+ case PageName.PASSWORD:
+ this.$$('password-page').clearPasswordTextInput();
+ this.setHostDevice_();
+ return;
+ case PageName.SUCCESS:
+ this.exitSetupFlow_();
+ return;
+ case PageName.START:
+ if (this.delegate.isPasswordRequiredToSetHost())
+ this.visiblePageName_ = PageName.PASSWORD;
+ else
+ this.setHostDevice_();
+ return;
+ }
+ },
+
+ /** @private */
+ setHostDevice_: function() {
+ // An authentication token must be set if a password is required.
+ assert(this.delegate.isPasswordRequiredToSetHost() == !!this.authToken_);
+
+ let deviceId = /** @type {string} */ (this.selectedDeviceId_);
+ this.delegate.setHostDevice(deviceId, this.authToken_)
+ .then((responseParams) => {
+ if (!responseParams.success) {
+ console.warn('Failure setting host with device ID: ' + deviceId);
+ return;
+ }
+
+ if (this.delegate.shouldExitSetupFlowAfterSettingHost()) {
+ this.exitSetupFlow_();
+ return;
+ }
+
+ this.visiblePageName_ = PageName.SUCCESS;
+ this.fire('forward-button-focus-requested');
+ })
+ .catch((error) => {
+ console.warn('Mojo service failure: ' + error);
+ });
+ },
+
+ /** @private */
+ onUserSubmittedPassword_: function() {
+ this.onForwardNavigationRequested_();
+ },
+
+ /**
+ * @return {string|undefined} The forward button text, which is undefined
+ * if no button should be displayed.
+ * @private
+ */
+ getForwardButtonText_: function() {
+ if (!this.visiblePage_)
+ return undefined;
+ return this.visiblePage_.forwardButtonText;
+ },
+
+ /**
+ * @return {boolean} Whether the forward button should be disabled.
+ * @private
+ */
+ shouldForwardButtonBeDisabled_: function() {
+ return (this.visiblePageName_ == PageName.PASSWORD) &&
+ this.passwordPageForwardButtonDisabled_;
+ },
+
+ /**
+ * @return {string|undefined} The cancel button text, which is undefined
+ * if no button should be displayed.
+ * @private
+ */
+ getCancelButtonText_: function() {
+ if (!this.visiblePage_)
+ return undefined;
+ return this.visiblePage_.cancelButtonText;
+ },
+
+ /**
+ * @return {string|undefined} The backward button text, which is undefined
+ * if no button should be displayed.
+ * @private
+ */
+ getBackwardButtonText_: function() {
+ if (!this.visiblePage_)
+ return undefined;
+ return this.visiblePage_.backwardButtonText;
+ },
+
+ /**
+ * @return {boolean}
+ * @private
+ */
+ shouldPasswordPageBeIncluded_: function() {
+ return this.delegate.isPasswordRequiredToSetHost();
+ },
+
+ /**
+ * @return {boolean}
+ * @private
+ */
+ shouldSetupSucceededPageBeIncluded_: function() {
+ return !this.delegate.shouldExitSetupFlowAfterSettingHost();
+ },
+
+ /**
+ * Notifies observers that the setup flow has completed.
+ *
+ * @private
+ */
+ exitSetupFlow_: function() {
+ this.fire('setup-exited');
+ },
+ });
+
+ return {
+ MultiDeviceSetup: MultiDeviceSetup,
+ };
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.html
new file mode 100644
index 00000000000..82f8d961c25
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.html
@@ -0,0 +1,3 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<script src="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.js">
+</script>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.js
new file mode 100644
index 00000000000..be88d746277
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.js
@@ -0,0 +1,41 @@
+// Copyright 2018 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.
+
+cr.define('multidevice_setup', function() {
+ /** @interface */
+ class BrowserProxy {
+ /**
+ * Requests profile information; namely, a dictionary containing the user's
+ * e-mail address and profile photo.
+ * @return {!Promise<{profilePhotoUrl: string, email: string}>}
+ */
+ getProfileInfo() {}
+
+ /**
+ * Opens settings to the MultiDevice individual feature settings subpage.
+ * (a.k.a. Connected Devices).
+ */
+ openMultiDeviceSettings() {}
+ }
+
+ /** @implements {multidevice_setup.BrowserProxy} */
+ class BrowserProxyImpl {
+ /** @override */
+ getProfileInfo() {
+ return cr.sendWithPromise('getProfileInfo');
+ }
+
+ /** @override */
+ openMultiDeviceSettings() {
+ chrome.send('openMultiDeviceSettings');
+ }
+ }
+
+ cr.addSingletonGetter(BrowserProxyImpl);
+
+ return {
+ BrowserProxy: BrowserProxy,
+ BrowserProxyImpl: BrowserProxyImpl,
+ };
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html
new file mode 100644
index 00000000000..46ce0b9a518
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html
@@ -0,0 +1,4 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<script src="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js">
+</script>
+
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js
new file mode 100644
index 00000000000..8526f38216b
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js
@@ -0,0 +1,33 @@
+// Copyright 2018 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.
+
+cr.define('multidevice_setup', function() {
+ /**
+ * Interface which provides the ability to set the host device and perform
+ * related logic.
+ * @interface
+ */
+ class MultiDeviceSetupDelegate {
+ /** @return {boolean} */
+ isPasswordRequiredToSetHost() {}
+
+ /**
+ * @param {string} hostDeviceId The ID of the host to set.
+ * @param {string=} opt_authToken An auth token to authenticate the request;
+ * only necessary if isPasswordRequiredToSetHost() returns true.
+ * @return {!Promise<{success: boolean}>}
+ */
+ setHostDevice(hostDeviceId, opt_authToken) {}
+
+ /** @return {boolean} */
+ shouldExitSetupFlowAfterSettingHost() {}
+
+ /** @return {string} */
+ getStartSetupCancelButtonTextId() {}
+ }
+
+ return {
+ MultiDeviceSetupDelegate: MultiDeviceSetupDelegate,
+ };
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html
new file mode 100644
index 00000000000..c9403122d45
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html
@@ -0,0 +1,19 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
+<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
+<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
+<link rel="import" href="chrome://resources/html/md_select_css.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout.html">
+
+<dom-module id="multidevice-setup-shared">
+ <template>
+ <style include="iron-flex paper-button-style cr-shared-style md-select">
+ a {
+ color: var(--google-blue-600);
+ text-decoration: none;
+ }
+ </style>
+ </template>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.html
new file mode 100644
index 00000000000..b5c52c0f573
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.html
@@ -0,0 +1,51 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+
+<dom-module id="password-page">
+ <template>
+ <style include="multidevice-setup-shared">
+ #user-info-container {
+ @apply --layout-horizontal;
+ align-items: center;
+ color: var(--paper-grey-600);
+ }
+
+ #profile-photo {
+ border-radius: 50%;
+ height: 20px;
+ margin-right: 8px;
+ width: 20px;
+ }
+
+ #passwordInput {
+ height: 32px;
+ margin-top: 64px;
+ width: 560px;
+ }
+ </style>
+ <ui-page header-text="[[headerText]]" icon-name="google-g">
+ <div id="content-container" slot="additional-content">
+ <div id="user-info-container">
+ <img id="profile-photo" src="[[profilePhotoUrl_]]"></img>
+ <span id="email">[[email_]]</span>
+ </div>
+ <cr-input id="passwordInput" type="password"
+ placeholder="[[i18n('enterPassword')]]"
+ invalid="[[passwordInvalid_]]"
+ error-message="[[i18n('wrongPassword')]]"
+ value="{{inputValue_}}"
+ aria-disabled="false"
+ on-keypress="onInputKeypress_"
+ autofocus>
+ </cr-input>
+ </div>
+ </ui-page>
+ </template>
+ <script src="chrome://resources/cr_components/chromeos/multidevice_setup/password_page.js">
+ </script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.js
new file mode 100644
index 00000000000..ba2abe0bf6f
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.js
@@ -0,0 +1,167 @@
+// Copyright 2018 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.
+
+Polymer({
+ is: 'password-page',
+
+ behaviors: [
+ UiPageContainerBehavior,
+ ],
+
+ properties: {
+ /**
+ * Whether forward button should be disabled. In this context, the forward
+ * button should be disabled if the user has not entered a password or if
+ * the user has submitted an incorrect password and has not yet edited it.
+ * @type {boolean}
+ */
+ forwardButtonDisabled: {
+ type: Boolean,
+ computed: 'shouldForwardButtonBeDisabled_(' +
+ 'inputValue_, passwordInvalid_, waitingForPasswordCheck_)',
+ notify: true,
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ forwardButtonTextId: {
+ type: String,
+ value: 'done',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ cancelButtonTextId: {
+ type: String,
+ value: 'cancel',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ backwardButtonTextId: {
+ type: String,
+ value: 'back',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ headerId: {
+ type: String,
+ value: 'passwordPageHeader',
+ },
+
+ /**
+ * Authentication token; retrieved using the quickUnlockPrivate API.
+ * @type {string}
+ */
+ authToken: {
+ type: String,
+ value: '',
+ notify: true,
+ },
+
+ /** @private {string} */
+ profilePhotoUrl_: {
+ type: String,
+ value: '',
+ },
+
+ /** @private {string} */
+ email_: {
+ type: String,
+ value: '',
+ },
+
+ /** @private {!QuickUnlockPrivate} */
+ quickUnlockPrivate_: {
+ type: Object,
+ value: chrome.quickUnlockPrivate,
+ },
+
+ /** @private {string} */
+ inputValue_: {
+ type: String,
+ value: '',
+ observer: 'onInputValueChange_',
+ },
+
+ /** @private {boolean} */
+ passwordInvalid_: {
+ type: Boolean,
+ value: false,
+ },
+
+ /** @private {boolean} */
+ waitingForPasswordCheck_: {
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ /** @private {?multidevice_setup.BrowserProxy} */
+ browserProxy_: null,
+
+ clearPasswordTextInput: function() {
+ this.$.passwordInput.value = '';
+ },
+
+ /** @override */
+ created: function() {
+ this.browserProxy_ = multidevice_setup.BrowserProxyImpl.getInstance();
+ },
+
+ /** @override */
+ attached: function() {
+ this.browserProxy_.getProfileInfo().then((profileInfo) => {
+ this.profilePhotoUrl_ = profileInfo.profilePhotoUrl;
+ this.email_ = profileInfo.email;
+ });
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ getCanNavigateToNextPage: function() {
+ return new Promise((resolve) => {
+ if (this.waitingForPasswordCheck_) {
+ resolve(false /* canNavigate */);
+ return;
+ }
+ this.waitingForPasswordCheck_ = true;
+ this.quickUnlockPrivate_.getAuthToken(this.inputValue_, (tokenInfo) => {
+ this.waitingForPasswordCheck_ = false;
+ if (chrome.runtime.lastError) {
+ this.passwordInvalid_ = true;
+ // Select the password text if the user entered an incorrect password.
+ this.$.passwordInput.select();
+ resolve(false /* canNavigate */);
+ return;
+ }
+ this.authToken = tokenInfo.token;
+ this.passwordInvalid_ = false;
+ resolve(true /* canNavigate */);
+ });
+ });
+ },
+
+ /** @private */
+ onInputValueChange_: function() {
+ this.passwordInvalid_ = false;
+ },
+
+ /**
+ * @param {!Event} e
+ * @private
+ */
+ onInputKeypress_: function(e) {
+ // We are only listening for the user trying to enter their password.
+ if (e.key != 'Enter')
+ return;
+
+ this.fire('user-submitted-password');
+ },
+
+ /**
+ * @return {boolean} Whether the forward button should be disabled.
+ * @private
+ */
+ shouldForwardButtonBeDisabled_: function() {
+ return this.passwordInvalid_ || !this.inputValue_ ||
+ this.waitingForPasswordCheck_;
+ },
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_failed_page.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_failed_page.html
new file mode 100644
index 00000000000..181687dffd7
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_failed_page.html
@@ -0,0 +1,18 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+
+<dom-module id="setup-failed-page">
+ <template>
+ <ui-page header-text="[[headerText]]" icon-name="error-icon">
+ <span slot="message" inner-h-t-m-l="[[messageHtml]]"></span>
+ <div slot="additional-content">
+ This is empty... (PlAcEhOlDeR tExT!!)
+ </div>
+ </ui-page>
+ </template>
+ <script src="chrome://resources/cr_components/chromeos/multidevice_setup/setup_failed_page.js">
+ </script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_failed_page.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_failed_page.js
new file mode 100644
index 00000000000..36f2d4ae117
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_failed_page.js
@@ -0,0 +1,43 @@
+// Copyright 2018 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.
+
+Polymer({
+ is: 'setup-failed-page',
+
+ properties: {
+ /** Overridden from UiPageContainerBehavior. */
+ forwardButtonTextId: {
+ type: String,
+ value: 'tryAgain',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ cancelButtonTextId: {
+ type: String,
+ value: 'cancel',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ backwardButtonTextId: {
+ type: String,
+ value: 'back',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ headerId: {
+ type: String,
+ value: 'setupFailedPageHeader',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ messageId: {
+ type: String,
+ value: 'setupFailedPageMessage',
+ },
+ },
+
+ behaviors: [
+ UiPageContainerBehavior,
+ ],
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_icon_1x.png b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_icon_1x.png
new file mode 100644
index 00000000000..03074bd5735
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_icon_1x.png
Binary files differ
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_icon_2x.png b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_icon_2x.png
new file mode 100644
index 00000000000..271b0484e3e
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_icon_2x.png
Binary files differ
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.html
new file mode 100644
index 00000000000..a899610a5d0
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.html
@@ -0,0 +1,35 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+
+<dom-module id="setup-succeeded-page">
+ <template>
+ <style include="multidevice-setup-shared">
+ #page-icon-container {
+ @apply --layout-horizontal;
+ @apply --layout-center-justified;
+ }
+
+ #page-icon {
+ background-image: -webkit-image-set(
+ url(setup_succeeded_icon_1x.png) 1x,
+ url(setup_succeeded_icon_2x.png) 2x);
+ height: 156px;
+ margin-top: 64px;
+ width: 416px;
+ }
+ </style>
+ <ui-page header-text="[[headerText]]" icon-name="google-g">
+ <span slot="message" inner-h-t-m-l="[[messageHtml]]"></span>
+ <div id="page-icon-container" slot="additional-content">
+ <div id="page-icon"></div>
+ </div>
+ </ui-page>
+ </template>
+ <script src="chrome://resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js">
+ </script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js
new file mode 100644
index 00000000000..e9c4eac1bc1
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js
@@ -0,0 +1,59 @@
+// Copyright 2018 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.
+
+cr.exportPath('multidevice_setup');
+
+Polymer({
+ is: 'setup-succeeded-page',
+
+ properties: {
+ /** Overridden from UiPageContainerBehavior. */
+ forwardButtonTextId: {
+ type: String,
+ value: 'done',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ headerId: {
+ type: String,
+ value: 'setupSucceededPageHeader',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ messageId: {
+ type: String,
+ value: 'setupSucceededPageMessage',
+ },
+ },
+
+ behaviors: [
+ UiPageContainerBehavior,
+ ],
+
+ /** @private {?multidevice_setup.BrowserProxy} */
+ browserProxy_: null,
+
+ /** @override */
+ created: function() {
+ this.browserProxy_ = multidevice_setup.BrowserProxyImpl.getInstance();
+ },
+
+ /** @private */
+ openSettings_: function() {
+ this.browserProxy_.openMultiDeviceSettings();
+ },
+
+ /** @private */
+ onSettingsLinkClicked_: function() {
+ this.openSettings_();
+ this.fire('setup-exited');
+ },
+
+ /** @override */
+ ready: function() {
+ let linkElement = this.$$('#settings-link');
+ linkElement.setAttribute('href', '#');
+ linkElement.addEventListener('click', () => this.onSettingsLinkClicked_());
+ },
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_icon_1x.png b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_icon_1x.png
new file mode 100644
index 00000000000..b6a3a196cb8
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_icon_1x.png
Binary files differ
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_icon_2x.png b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_icon_2x.png
new file mode 100644
index 00000000000..c7234e8f191
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_icon_2x.png
Binary files differ
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.html
new file mode 100644
index 00000000000..a410816bbf7
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.html
@@ -0,0 +1,135 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/icons.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
+
+<dom-module id="start-setup-page">
+ <template>
+ <style include="multidevice-setup-shared">
+ #selector-and-details-container {
+ @apply --layout-horizontal;
+ margin-top: 48px;
+ min-height: 246px;
+ }
+
+ #singleDeviceName {
+ color: var(--google-grey-900);
+ margin-top: 16px;
+ }
+
+ #deviceDropdown {
+ margin-top: 16px;
+ }
+
+ #page-icon-container {
+ @apply --layout-horizontal;
+ @apply --layout-center-justified;
+ }
+
+ #page-icon {
+ background-image: -webkit-image-set(url(start_setup_icon_1x.png) 1x,
+ url(start_setup_icon_2x.png) 2x);
+ height: 116px;
+ margin-top: 10px;
+ width: 320px;
+ }
+
+ #deviceSelectionContainer {
+ color: var(--paper-grey-600);
+ }
+
+ #feature-details-container {
+ @apply --layout-vertical;
+ @apply --layout-center-justified;
+ border-left: 1px solid rgb(218, 220, 224);
+ padding-left: 24px;
+ }
+
+ #feature-details-container-header {
+ margin-bottom: 18px;
+ }
+
+ .feature-detail {
+ @apply --layout-horizontal;
+ @apply --layout-center;
+ box-sizing: border-box;
+ min-height: 64px;
+ padding: 10px 0;
+ }
+
+ .feature-detail iron-icon {
+ --iron-icon-height: 20px;
+ --iron-icon-width: 20px;
+ min-width: 20px;
+ }
+
+ .feature-detail span {
+ margin-left: 8px;
+ }
+
+ #footnote {
+ color: var(--paper-grey-600);
+ margin-top: 12px;
+ }
+ </style>
+
+ <ui-page header-text="[[headerText]]" icon-name="google-g">
+ <span slot="message" id="multidevice-summary-message" inner-h-t-m-l="[[messageHtml]]"></span>
+ <div slot="additional-content">
+ <div id="selector-and-details-container">
+ <div id="deviceSelectionContainer" class="flex">
+ [[getDeviceSelectionHeader_(devices)]]
+ <div class="flex"></div>
+ <div id="singleDeviceName"
+ hidden$="[[!doesDeviceListHaveOneElement_(devices)]]">
+ [[getFirstDeviceNameInList_(devices)]]
+ </div>
+ <div hidden$="[[!doesDeviceListHaveMultipleElements_(devices)]]">
+ <select id="deviceDropdown"
+ class="md-select"
+ on-change="onDeviceDropdownSelectionChanged_">
+ <template is="dom-repeat" items="[[devices]]">
+ <option value$="[[item.deviceId]]">
+ [[item.deviceName]]
+ </option>
+ </template>
+ </select>
+ </div>
+ <div id="page-icon-container">
+ <div id="page-icon"></div>
+ </div>
+ </div>
+ <div id="feature-details-container" class="flex">
+ <div id="feature-details-container-header">
+ [[i18n('startSetupPageFeatureListHeader')]]
+ </div>
+ <div class="feature-detail">
+ <iron-icon icon="multidevice-setup-icons-20:messages"></iron-icon>
+ <span id="awm-summary-message" inner-h-t-m-l="
+ [[i18nAdvanced('startSetupPageFeatureListAwm')]]">
+ </span>
+ </div>
+ <div class="feature-detail">
+ <iron-icon icon="multidevice-setup-icons-20:downloads">
+ </iron-icon>
+ <span>[[i18n('startSetupPageFeatureListInstallApps')]]</span>
+ </div>
+ <div class="feature-detail">
+ <iron-icon icon="multidevice-setup-icons-20:features"></iron-icon>
+ <span>[[i18n('startSetupPageFeatureListAddFeatures')]]</span>
+ </div>
+ </div>
+ </div>
+ <div id="footnote">[[i18n('startSetupPageFootnote')]]</div>
+ </div>
+ </ui-page>
+ </template>
+ <script src="chrome://resources/cr_components/chromeos/multidevice_setup/start_setup_page.js">
+ </script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.js
new file mode 100644
index 00000000000..d9f6db488e7
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.js
@@ -0,0 +1,155 @@
+// Copyright 2018 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.
+
+Polymer({
+ is: 'start-setup-page',
+
+ properties: {
+ /** Overridden from UiPageContainerBehavior. */
+ forwardButtonTextId: {
+ type: String,
+ value: 'accept',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ cancelButtonTextId: {
+ type: String,
+ computed: 'getCancelButtonTextId_(delegate)',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ headerId: {
+ type: String,
+ value: 'startSetupPageHeader',
+ },
+
+ /** Overridden from UiPageContainerBehavior. */
+ messageId: {
+ type: String,
+ value: 'startSetupPageMessage',
+ },
+
+ /**
+ * Array of objects representing all potential MultiDevice hosts.
+ *
+ * @type {!Array<!chromeos.deviceSync.mojom.RemoteDevice>}
+ */
+ devices: {
+ type: Array,
+ value: () => [],
+ observer: 'devicesChanged_',
+ },
+
+ /**
+ * Unique identifier for the currently selected host device.
+ *
+ * Undefined if the no list of potential hosts has been received from mojo
+ * service.
+ *
+ * @type {string|undefined}
+ */
+ selectedDeviceId: {
+ type: String,
+ notify: true,
+ },
+
+ /**
+ * Delegate object which performs differently in OOBE vs. non-OOBE mode.
+ * @type {!multidevice_setup.MultiDeviceSetupDelegate}
+ */
+ delegate: Object,
+ },
+
+ behaviors: [
+ UiPageContainerBehavior,
+ I18nBehavior,
+ WebUIListenerBehavior,
+ ],
+
+ /** @override */
+ attached: function() {
+ this.addWebUIListener(
+ 'multidevice_setup.initializeSetupFlow',
+ this.initializeSetupFlow_.bind(this));
+ },
+
+ /** @private */
+ initializeSetupFlow_: function() {
+ // The "Learn More" links are inside a grdp string, so we cannot actually
+ // add an onclick handler directly to the html. Instead, grab the two and
+ // manaully add onclick handlers.
+ let helpArticleLinks = [
+ this.$$('#multidevice-summary-message a'),
+ this.$$('#awm-summary-message a')
+ ];
+ for (let i = 0; i < helpArticleLinks.length; i++) {
+ helpArticleLinks[i].onclick = this.fire.bind(
+ this, 'open-learn-more-webview-requested', helpArticleLinks[i].href);
+ }
+ },
+
+ /**
+ * @param {!multidevice_setup.MultiDeviceSetupDelegate} delegate
+ * @return {string} The cancel button text ID, dependent on OOBE vs. non-OOBE.
+ * @private
+ */
+ getCancelButtonTextId_: function(delegate) {
+ return this.delegate.getStartSetupCancelButtonTextId();
+ },
+
+ /**
+ * @param {!Array<!chromeos.deviceSync.mojom.RemoteDevice>} devices
+ * @return {string} Label for devices selection content.
+ * @private
+ */
+ getDeviceSelectionHeader_(devices) {
+ switch (devices.length) {
+ case 0:
+ return '';
+ case 1:
+ return this.i18n('startSetupPageSingleDeviceHeader');
+ default:
+ return this.i18n('startSetupPageMultipleDeviceHeader');
+ }
+ },
+
+ /**
+ * @param {!Array<!chromeos.deviceSync.mojom.RemoteDevice>} devices
+ * @return {boolean} True if there are more than one potential host devices.
+ * @private
+ */
+ doesDeviceListHaveMultipleElements_: function(devices) {
+ return devices.length > 1;
+ },
+
+ /**
+ * @param {!Array<!chromeos.deviceSync.mojom.RemoteDevice>} devices
+ * @return {boolean} True if there is exactly one potential host device.
+ * @private
+ */
+ doesDeviceListHaveOneElement_: function(devices) {
+ return devices.length == 1;
+ },
+
+ /**
+ * @param {!Array<!chromeos.deviceSync.mojom.RemoteDevice>} devices
+ * @return {string} Name of the first device in device list if there are any.
+ * Returns an empty string otherwise.
+ * @private
+ */
+ getFirstDeviceNameInList_: function(devices) {
+ return devices[0] ? this.devices[0].deviceName : '';
+ },
+
+ /** @private */
+ devicesChanged_: function() {
+ if (this.devices.length > 0)
+ this.selectedDeviceId = this.devices[0].deviceId;
+ },
+
+ /** @private */
+ onDeviceDropdownSelectionChanged_: function() {
+ this.selectedDeviceId = this.$.deviceDropdown.value;
+ },
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page.html
new file mode 100644
index 00000000000..289e08644b8
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page.html
@@ -0,0 +1,42 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/icons.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
+
+<dom-module id="ui-page">
+ <template>
+ <style include="multidevice-setup-shared">
+ iron-icon {
+ --iron-icon-width: 32px;
+ --iron-icon-height: 32px;
+ }
+
+ h1 {
+ color: var(--google-grey-900);
+ font-family: 'Google Sans';
+ font-size: 28px;
+ font-weight: normal;
+ line-height: 28px;
+ margin: 0;
+ padding-top: 36px;
+ }
+
+ #message-container {
+ box-sizing: border-box;
+ min-height: 32px;
+ padding-top: 16px;
+ }
+ </style>
+ <iron-icon icon="[[computeIconIdentifier_(iconName)]]"></iron-icon>
+ <h1>[[headerText]]</h1>
+ <div id="message-container">
+ <slot name="message"></slot>
+ </div>
+ <div id="additional-content-container">
+ <slot name="additional-content"></slot>
+ </div>
+ </template>
+ <script src="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page.js">
+ </script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page.js
new file mode 100644
index 00000000000..d29f7722d10
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page.js
@@ -0,0 +1,34 @@
+// Copyright 2018 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.
+
+/**
+ * An element that encapsulates the structure common to all pages in the WebUI.
+ */
+Polymer({
+ is: 'ui-page',
+
+ properties: {
+ /**
+ * Main heading for the page.
+ *
+ * @type {string}
+ */
+ headerText: String,
+
+ /**
+ * Name of icon within icon set.
+ *
+ * @type {string}
+ */
+ iconName: String,
+ },
+
+ /**
+ * @return {string}
+ * @private
+ */
+ computeIconIdentifier_: function() {
+ return 'multidevice-setup-icons-32:' + this.iconName;
+ },
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html
new file mode 100644
index 00000000000..501cc374471
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html
@@ -0,0 +1,5 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="chrome://resources/html/i18n_behavior.html">
+
+<script src="chrome://resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.js">
+</script>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.js b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.js
new file mode 100644
index 00000000000..e9f16b9ee89
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_page_container_behavior.js
@@ -0,0 +1,141 @@
+// Copyright 2018 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.
+
+/** @polymerBehavior */
+const UiPageContainerBehaviorImpl = {
+ properties: {
+ /**
+ * ID for forward button label, which must be translated for display.
+ *
+ * Undefined if the visible page has no forward-navigation button.
+ *
+ * @type {string|undefined}
+ */
+ forwardButtonTextId: String,
+
+ /**
+ * ID for cancel button label, which must be translated for display.
+ *
+ * Undefined if the visible page has no cancel button.
+ *
+ * @type {string|undefined}
+ */
+ cancelButtonTextId: String,
+
+ /**
+ * ID for backward button label, which must be translated for display.
+ *
+ * Undefined if the visible page has no backward-navigation button.
+ *
+ * @type {string|undefined}
+ */
+ backwardButtonTextId: String,
+
+ /**
+ * ID for text of main UI Page heading.
+ *
+ * @type {string}
+ */
+ headerId: String,
+
+ /**
+ * ID for text of main UI Page message body.
+ *
+ * @type {string}
+ */
+ messageId: String,
+
+ /**
+ * Translated text to display on the forward-naviation button.
+ *
+ * Undefined if the visible page has no forward-navigation button.
+ *
+ * @type {string|undefined}
+ */
+ forwardButtonText: {
+ type: String,
+ computed: 'computeLocalizedText_(forwardButtonTextId)',
+ },
+
+ /**
+ * Translated text to display on the cancel button.
+ *
+ * Undefined if the visible page has no cancel button.
+ *
+ * @type {string|undefined}
+ */
+ cancelButtonText: {
+ type: String,
+ computed: 'computeLocalizedText_(cancelButtonTextId)',
+ },
+
+ /**
+ * Translated text to display on the backward-naviation button.
+ *
+ * Undefined if the visible page has no backward-navigation button.
+ *
+ * @type {string|undefined}
+ */
+ backwardButtonText: {
+ type: String,
+ computed: 'computeLocalizedText_(backwardButtonTextId)',
+ },
+
+ /**
+ * Translated text of main UI Page heading.
+ *
+ * @type {string|undefined}
+ */
+ headerText: {
+ type: String,
+ computed: 'computeLocalizedText_(headerId)',
+ },
+
+ /**
+ * Translated text of main UI Page heading. In general this can include
+ * some markup.
+ *
+ * @type {string|undefined}
+ */
+ messageHtml: {
+ type: String,
+ computed: 'computeLocalizedText_(messageId)',
+ },
+ },
+
+ /**
+ * Returns a promise which always resolves and returns a boolean representing
+ * whether it should be possible to navigate forward. This function is called
+ * before forward navigation is requested; if false is returned, the active
+ * page does not change.
+ * @return {!Promise}
+ */
+ getCanNavigateToNextPage: function() {
+ return new Promise((resolve) => {
+ resolve(true /* canNavigate */);
+ });
+ },
+
+ /**
+ * @param {string} textId Key for the localized string to appear on a
+ * button.
+ * @return {string|undefined} The localized string corresponding to the key
+ * textId. Return value is undefined if textId is not a key
+ * for any localized string. Note: this includes the case in which
+ * textId is undefined.
+ * @private
+ */
+ computeLocalizedText_: function(textId) {
+ if (!this.i18nExists(textId))
+ return;
+
+ return loadTimeData.getString(textId);
+ },
+};
+
+/** @polymerBehavior */
+const UiPageContainerBehavior = [
+ I18nBehavior,
+ UiPageContainerBehaviorImpl,
+];
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_config.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_config.html
index 4c506f20691..f6053ce9367 100644
--- a/chromium/ui/webui/resources/cr_components/chromeos/network/network_config.html
+++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_config.html
@@ -154,7 +154,7 @@
<cr-toggle id="share" checked="{{shareNetwork_}}"
disabled="[[!shareIsEnabled_(guid, configProperties_.*,
security_, eapProperties_.*, shareAllowEnable)]]"
- aria-label="[[i18n('networkConfigShare')]]">
+ aria-labeledby="shareLabel">
</cr-toggle>
</div>
</template>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js
index b5dc5c2bbc2..c827a0ddfe5 100644
--- a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js
+++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js
@@ -191,7 +191,8 @@ Polymer({
(CrOnc.proxyMatches(jsonHttp, proxy.Manual.SecureHTTPProxy) &&
CrOnc.proxyMatches(jsonHttp, proxy.Manual.FTPProxy) &&
CrOnc.proxyMatches(jsonHttp, proxy.Manual.SOCKS)) ||
- (!proxy.Manual.SecureHTTPProxy.Host &&
+ (!proxy.Manual.HTTPProxy.Host &&
+ !proxy.Manual.SecureHTTPProxy.Host &&
!proxy.Manual.FTPProxy.Host && !proxy.Manual.SOCKS.Host);
}
if (proxySettings.ExcludeDomains) {
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn
index a024915b51f..afe7eb05b32 100644
--- a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn
+++ b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/BUILD.gn
@@ -7,6 +7,7 @@ import("//third_party/closure_compiler/compile_js.gni")
js_type_check("closure_compile") {
deps = [
":pin_keyboard",
+ ":setup_pin_keyboard",
]
}
@@ -17,3 +18,21 @@ js_library("pin_keyboard") {
"//ui/webui/resources/js:i18n_behavior",
]
}
+
+js_library("lock_screen_constants") {
+ deps = [
+ "//ui/webui/resources/cr_elements/cr_profile_avatar_selector:cr_profile_avatar_selector",
+ "//ui/webui/resources/js:cr",
+ ]
+}
+
+js_library("setup_pin_keyboard") {
+ deps = [
+ ":lock_screen_constants",
+ ":pin_keyboard",
+ "//ui/webui/resources/cr_components/chromeos/quick_unlock:lock_screen_constants",
+ "//ui/webui/resources/js:i18n_behavior",
+ ]
+ externs_list = [ "$externs_path/quick_unlock_private.js" ]
+ extra_sources = [ "$interfaces_path/quick_unlock_private_interface.js" ]
+}
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.html b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.html
new file mode 100644
index 00000000000..29c7f5cb64f
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.html
@@ -0,0 +1 @@
+<script src="chrome://resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js"></script>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js
new file mode 100644
index 00000000000..e5ee46e6d1a
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/lock_screen_constants.js
@@ -0,0 +1,47 @@
+// Copyright 2016 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 Constants used for logging the pin unlock setup uma.
+ */
+
+/**
+ * Name of the pin unlock setup uma histogram.
+ * @type {string}
+ */
+const PinUnlockUmaHistogramName = 'Settings.PinUnlockSetup';
+
+/**
+ * Stages the user can enter while setting up pin unlock.
+ * @enum {number}
+ */
+const LockScreenProgress = {
+ START_SCREEN_LOCK: 0,
+ ENTER_PASSWORD_CORRECTLY: 1,
+ CHOOSE_PIN_OR_PASSWORD: 2,
+ ENTER_PIN: 3,
+ CONFIRM_PIN: 4,
+ MAX_BUCKET: 5
+};
+
+cr.define('settings', function() {
+ /**
+ * Helper function to send the progress of the pin setup to be recorded in the
+ * histogram.
+ * @param {LockScreenProgress} currentProgress
+ */
+ const recordLockScreenProgress = function(currentProgress) {
+ if (currentProgress >= LockScreenProgress.MAX_BUCKET) {
+ console.error(
+ 'Expected a enumeration value of ' + LockScreenProgress.MAX_BUCKET +
+ ' or lower: Received ' + currentProgress + '.');
+ return;
+ }
+ chrome.send('metricsHandler:recordInHistogram', [
+ PinUnlockUmaHistogramName, currentProgress, LockScreenProgress.MAX_BUCKET
+ ]);
+ };
+
+ return {recordLockScreenProgress: recordLockScreenProgress};
+});
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
index f28f89b9e06..080f3cc0328 100644
--- a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
+++ b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
@@ -36,16 +36,41 @@
<template>
<style include="cr-shared-style">
:host {
+ --backspace-button-ripple-left: calc((var(--backspace-button-width) -
+ var(--pin-button-ripple-width)) / 2);
+ --backspace-button-width: calc(var(--pin-button-width) +
+ var(--pin-button-horizontal-margin) * 2);
+ --pin-button-height: 40px;
+ --pin-button-horizontal-margin: 20px;
+ --pin-button-ripple-height: 48px;
+ --pin-button-ripple-left: calc((var(--pin-button-width) -
+ var(--pin-button-ripple-width)) / 2);
+ --pin-button-ripple-top: calc((var(--pin-button-height) -
+ var(--pin-button-ripple-height)) / 2);
+ --pin-button-ripple-width: 48px;
+ --pin-button-vertical-margin: 8px;
+ --pin-button-width: 40px;
outline: none;
}
#root {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ }
+
+ #rowsContainer {
direction: ltr;
display: block;
+ width: calc((var(--pin-button-width) +
+ var(--pin-button-horizontal-margin) * 2) * 3);
}
.row {
display: flex;
+ margin-bottom: calc(var(--pin-button-vertical-margin) * 2);
+ min-height: 0;
}
:host([enable-password]) #pinInputDiv {
@@ -57,10 +82,10 @@
}
#backspaceButton {
- color: var(--pin-keyboard-backspace-color, #000);
+ color: var(--pin-keyboard-backspace-color, var(--google-grey-700));
left: 0;
opacity: var(--pin-keyboard-backspace-opacity, --dark-primary-opacity);
- padding: 14px;
+ padding: 12px;
position: absolute;
top: 0;
}
@@ -71,75 +96,93 @@
#backspaceButtonContainer {
position: relative;
+ width: var(--backspace-button-width);
}
#backspaceButtonContainer paper-ripple {
- left: var(--pin-keyboard-backspace-paper-ripple-offset, 0);
- top: var(--pin-keyboard-backspace-paper-ripple-offset, 0);
+ left: var(--pin-keyboard-backspace-paper-ripple-offset,
+ var(--backspace-button-ripple-left));
+ top: var(--pin-keyboard-backspace-paper-ripple-offset,
+ var(--pin-button-ripple-top));
}
.digit-button {
+ --paper-button: {
+ min-width: 0;
+ };
align-items: center;
background: none;
border-radius: 0;
box-sizing: border-box;
- color: #000;
+ color: var(--google-grey-900);
display: flex;
flex-direction: column;
- height: 48px;
+ height: var(--pin-button-height)
justify-content: center;
- margin: 0;
- min-height: 48px;
- min-width: 48px;
+ margin: 0 var(--pin-button-horizontal-margin);
+ min-height: 0;
opacity: 0.87px;
- width: 60px;
+ padding: 0;
+ width: var(--pin-button-width);
@apply --pin-keyboard-digit-button;
}
.digit-button inner-text {
- display: flex;
- flex-direction: column;
font-family: 'Roboto';
}
- .letter {
- color: var(--pin-keyboard-letter-color, --paper-blue-grey-700);
- font-size: 9px;
- margin-top: 4px;
+ inner-text.letter {
+ color: var(--pin-keyboard-letter-color, var(--google-grey-700));
+ font-size: 12px;
+ margin-top: 8px;
+
+ @apply --pin-keyboard-digit-button-letter;
}
.number {
- color: var(--pin-keyboard-number-color, --paper-blue-grey-700);
- font-size: 20px;
- height: 52px;
+ color: var(--pin-keyboard-number-color, var(--paper-blue-grey-700));
+ font-size: 18px;
+ height: 16px;
}
paper-ripple {
color: var(--google-grey-700);
- height: 48px;
- left: 6px;
- width: 48px;
+ height: var(--pin-button-ripple-height);
+ left: var(--pin-button-ripple-left);
+ top: var(--pin-button-ripple-top);
+ width: var(--pin-button-ripple-width);
@apply --pin-keyboard-paper-ripple;
}
#pinInput {
+ --cr-input-error-display: none;
+ --cr-input-input: {
+ font-size: 28px;
+ letter-spacing: 28px;
+ };
+ --cr-input-padding-bottom: 1px;
+ --cr-input-padding-end: 0;
+ --cr-input-padding-start: 0;
+ --cr-input-padding-top: 1px;
background-color: white;
border: 0;
box-sizing: border-box;
font-face: Roboto-Regular;
font-size: 13px;
- height: 43px;
left: 0;
opacity: var(--dark-secondary-opacity);
outline: 0;
position: relative;
text-align: center;
- width: 180px;
+ width: 200px;
+
+ @apply --pin-keyboard-pin-input-style;
}
#pinInput[has-content] {
+ --cr-disabled-opacity: var(--dark-primary-opacity);
opacity: var(--dark-primary-opacity);
}
@@ -155,93 +198,98 @@
</style>
<div id="root" on-contextmenu="onContextMenu_" on-tap="focusInput_">
- <div id="pinInputDiv" class="row">
+ <div id="pinInputDiv">
<cr-input id="pinInput" type="password" value="{{value}}"
is-input-rtl$="[[isInputRtl_(value)]]"
has-content$="[[hasInput_(value)]]" invalid="[[hasError]]"
- placeholder="[[getInputPlaceholder_(enablePassword)]]"
- on-keydown="onInputKeyDown_">
+ placeholder="[[getInputPlaceholder_(enablePassword,
+ enablePlaceholder)]]"
+ on-keydown="onInputKeyDown_" force-underline$="[[forceUnderline_]]"
+ disabled="[[isIncognitoUi]]">
</cr-input>
</div>
<slot select="[problem]"></slot>
- <div class="row">
- <paper-button class="digit-button" on-tap="onNumberTap_" value="1"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard1')]]</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- <paper-button class="digit-button" on-tap="onNumberTap_" value="2"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard2')]]</inner-text>
- <inner-text class="letter">ABC</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- <paper-button class="digit-button" on-tap="onNumberTap_" value="3"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard3')]]</inner-text>
- <inner-text class="letter">DEF</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- </div>
- <div class="row">
- <paper-button class="digit-button" on-tap="onNumberTap_" value="4"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard4')]]</inner-text>
- <inner-text class="letter">GHI</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- <paper-button class="digit-button" on-tap="onNumberTap_" value="5"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard5')]]</inner-text>
- <inner-text class="letter">JKL</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- <paper-button class="digit-button" on-tap="onNumberTap_" value="6"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard6')]]</inner-text>
- <inner-text class="letter">MNO</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- </div>
- <div class="row">
- <paper-button class="digit-button" on-tap="onNumberTap_" value="7"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard7')]]</inner-text>
- <inner-text class="letter">PQRS</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- <paper-button class="digit-button" on-tap="onNumberTap_" value="8"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard8')]]</inner-text>
- <inner-text class="letter">TUV</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- <paper-button class="digit-button" on-tap="onNumberTap_" value="9"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard9')]]</inner-text>
- <inner-text class="letter">WXYZ</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- </div>
- <div class="row bottom-row">
- <div class="digit-button"></div>
- <paper-button class="digit-button" on-tap="onNumberTap_" value="0"
- noink>
- <inner-text class="number">[[i18n('pinKeyboard0')]]</inner-text>
- <inner-text class="letter">+</inner-text>
- <paper-ripple class="circle" center></paper-ripple>
- </paper-button>
- <div id="backspaceButtonContainer">
- <paper-icon-button id="backspaceButton" class="digit-button"
- disabled$="[[!hasInput_(value)]]"
- icon="pin-keyboard:backspace"
- on-pointerdown="onBackspacePointerDown_"
- on-pointerout="clearAndReset_"
- on-pointerup="onBackspacePointerUp_"
- title="[[i18n('pinKeyboardDeleteAccessibleName')]]"
+ <div id="rowsContainer">
+ <div class="row">
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="1"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard1')]]</inner-text>
+ <inner-text class="letter">&nbsp;</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="2"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard2')]]</inner-text>
+ <inner-text class="letter">ABC</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="3"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard3')]]</inner-text>
+ <inner-text class="letter">DEF</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ </div>
+ <div class="row">
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="4"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard4')]]</inner-text>
+ <inner-text class="letter">GHI</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="5"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard5')]]</inner-text>
+ <inner-text class="letter">JKL</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="6"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard6')]]</inner-text>
+ <inner-text class="letter">MNO</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ </div>
+ <div class="row">
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="7"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard7')]]</inner-text>
+ <inner-text class="letter">PQRS</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="8"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard8')]]</inner-text>
+ <inner-text class="letter">TUV</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="9"
+ noink>
+ <inner-text class="number">[[i18n('pinKeyboard9')]]</inner-text>
+ <inner-text class="letter">WXYZ</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ </div>
+ <div class="row bottom-row">
+ <div class="digit-button"></div>
+ <paper-button class="digit-button" on-tap="onNumberTap_" value="0"
noink>
- </paper-icon-button>
- <paper-ripple class="circle" center></paper-ripple>
+ <inner-text class="number">[[i18n('pinKeyboard0')]]</inner-text>
+ <inner-text class="letter">+</inner-text>
+ <paper-ripple class="circle" center></paper-ripple>
+ </paper-button>
+ <div id="backspaceButtonContainer">
+ <paper-icon-button id="backspaceButton" class="digit-button"
+ disabled$="[[!hasInput_(value)]]"
+ icon="pin-keyboard:backspace"
+ on-pointerdown="onBackspacePointerDown_"
+ on-pointerout="clearAndReset_"
+ on-pointerup="onBackspacePointerUp_"
+ title="[[i18n('pinKeyboardDeleteAccessibleName')]]"
+ noink>
+ </paper-icon-button>
+ <paper-ripple class="circle" center></paper-ripple>
+ </div>
</div>
</div>
</div>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
index 0f7e3da6463..ceaaae8adca 100644
--- a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
+++ b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
@@ -29,7 +29,7 @@
* @type {number}
* @const
*/
-var REPEAT_BACKSPACE_DELAY_MS = 150;
+const REPEAT_BACKSPACE_DELAY_MS = 150;
/**
* How long the backspace button must be held down before auto backspace
@@ -37,7 +37,7 @@ var REPEAT_BACKSPACE_DELAY_MS = 150;
* @type {number}
* @const
*/
-var INITIAL_BACKSPACE_DELAY_MS = 500;
+const INITIAL_BACKSPACE_DELAY_MS = 500;
/**
* The key codes of the keys allowed to be used on the pin input, in addition to
@@ -45,7 +45,7 @@ var INITIAL_BACKSPACE_DELAY_MS = 500;
* @type {Array<number>}
* @const
*/
-var PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES = [8, 9, 37, 39];
+const PIN_INPUT_ALLOWED_NON_NUMBER_KEY_CODES = [8, 9, 37, 39];
Polymer({
is: 'pin-keyboard',
@@ -103,6 +103,36 @@ Polymer({
value: '',
observer: 'onPinValueChange_',
},
+
+ /**
+ * @private
+ */
+ forceUnderline_: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * Enables pin placeholder.
+ */
+ enablePlaceholder: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * Turns on "incognito mode". (FIXME after https://crbug.com/900351 is
+ * fixed).
+ */
+ isIncognitoUi: {
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ listeners: {
+ 'blur': 'onBlur_',
+ 'focus': 'onFocus_',
},
/**
@@ -174,17 +204,27 @@ Polymer({
this.focus(this.selectionStart_, this.selectionEnd_);
},
+ /** @private */
+ onFocus_: function() {
+ this.forceUnderline_ = true;
+ },
+
+ /** @private */
+ onBlur_: function() {
+ this.forceUnderline_ = false;
+ },
+
/**
* Called when a keypad number has been tapped.
* @param {Event} event The event object.
* @private
*/
onNumberTap_: function(event) {
- var numberValue = event.target.getAttribute('value');
+ let numberValue = event.target.getAttribute('value');
// Add the number where the caret is, then update the selection range of the
// input element.
- var selectionStart = this.selectionStart_;
+ let selectionStart = this.selectionStart_;
this.value = this.value.substring(0, this.selectionStart_) + numberValue +
this.value.substring(this.selectionEnd_);
@@ -223,8 +263,8 @@ Polymer({
// If the input is shown, clear the text based on the caret location or
// selected region of the input element. If it is just a caret, remove the
// character in front of the caret.
- var selectionStart = this.selectionStart_;
- var selectionEnd = this.selectionEnd_;
+ let selectionStart = this.selectionStart_;
+ let selectionEnd = this.selectionEnd_;
if (selectionStart == selectionEnd && selectionStart)
selectionStart--;
@@ -355,9 +395,13 @@ Polymer({
/**
* Computes the value of the pin input placeholder.
* @param {boolean} enablePassword
+ * @param {boolean} enablePlaceholder
* @private
*/
- getInputPlaceholder_: function(enablePassword) {
+ getInputPlaceholder_: function(enablePassword, enablePlaceholder) {
+ if (!enablePlaceholder)
+ return '';
+
return enablePassword ? this.i18n('pinKeyboardPlaceholderPinPassword') :
this.i18n('pinKeyboardPlaceholderPin');
},
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.html b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.html
new file mode 100644
index 00000000000..71e9a15fd8e
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.html
@@ -0,0 +1,103 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_components/chromeos/quick_unlock/lock_screen_constants.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/quick_unlock/pin_keyboard.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
+<link rel="import" href="chrome://resources/cr_elements/icons.html">
+<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
+<link rel="import" href="chrome://resources/html/assert.html">
+<link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
+
+<!--
+
+This module is a "pin setup" keyboard + pin display element.
+It can be integrated into some UI container to set pin unlock.
+
+Usage:
+ <setup-pin-keyboard
+ enable-submit="{{enableSubmit_}}"
+ is-confirm-step="{{isConfirmStep_}}"
+ on-pin-submit="onPinSubmit_"
+ on-set-pin-done="onSetPinDone_"
+ set-modes="{{setModes}}">
+ </setup-pin-keyboard>
+
+Where:
+ * enable-submit - Notification property for the container to enable/disable
+ submit button in the container (if it exists). True when pin can be
+ submitted.
+ * is-confirm-step - Notification property for the container to update UI
+ when pin confirmation is requested. False when initial PIN entry step
+ is active, true when pin confirmation is active.
+ * on-pin-submit - Event handler for the user requested pin submit by pressing
+ "Enter" key on the keyboard. setup-pin-keyboard will
+ not submit pin automatically, delegating this step to outer container.
+ Container must call setup-pin-keyboard.doSubmit() when
+ pin should be submitted.
+ * on-set-pin-done - Event handler for the "set pin done" event, which should
+ normally close the pin setup UI. This object state is reset before
+ sending this event.
+ * set-modes - Reflects property set in password_prompt_dialog.js.
+
+-->
+
+<dom-module id="setup-pin-keyboard">
+ <template>
+ <style include="settings-shared">
+ .error {
+ color: var(--google-red-600);
+ }
+
+ .error > iron-icon {
+ --iron-icon-fill-color: var(--google-red-600);
+ }
+
+ .warning {
+ color: var(--cr-secondary-text-color);
+ }
+
+ .warning > iron-icon {
+ --iron-icon-fill-color: var(--google-grey-refresh-700);
+ }
+
+ #problemDiv {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ height: 32px;
+ min-height: 0;
+ }
+
+ /* Hide this using visibility: hidden instead of hidden so that the
+ dialog does not resize when there are no problems to display. */
+ #problemDiv[invisible] {
+ visibility: hidden;
+ }
+
+ #problemMessage {
+ font-size: 10px;
+ }
+ </style>
+ <pin-keyboard id="pinKeyboard" on-pin-change="onPinChange_"
+ on-submit="onPinSubmit_" value="{{pinKeyboardValue_}}"
+ has-error="[[hasError_(problemMessageId_, problemClass_)]]"
+ enable-placeholder="[[enablePlaceholder]]"
+ is-incognito-ui="[[isIncognitoUi]]">
+ <!-- Warning/error; only shown if title is hidden. -->
+ <div id="problemDiv" class$="[[problemClass_]]"
+ invisible$="[[!problemMessageId_]]" problem>
+ <div>
+ <iron-icon icon="cr:error-outline"></iron-icon>
+ <span id="problemMessage">
+ [[formatProblemMessage_(locale, problemMessageId_,
+ problemMessageParameters_)]]
+ </span>
+ </div>
+ </div>
+ </pin-keyboard>
+ </template>
+ <script
+ src="chrome://resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js">
+ </script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js
new file mode 100644
index 00000000000..9dde5f4717f
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js
@@ -0,0 +1,377 @@
+// Copyright 2018 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
+ * 'setup-pin-keyboard' is the keyboard/input field for choosing a PIN.
+ *
+ * See usage documentation in setup_pin_keyboard.html.
+ *
+ */
+
+(function() {
+'use strict';
+
+/**
+ * Keep in sync with the string keys provided by settings.
+ * @enum {string}
+ */
+const MessageType = {
+ TOO_SHORT: 'configurePinTooShort',
+ TOO_LONG: 'configurePinTooLong',
+ TOO_WEAK: 'configurePinWeakPin',
+ MISMATCH: 'configurePinMismatched'
+};
+
+/** @enum {string} */
+const ProblemType = {
+ WARNING: 'warning',
+ ERROR: 'error'
+};
+
+Polymer({
+ is: 'setup-pin-keyboard',
+
+ behaviors: [I18nBehavior],
+
+ properties: {
+ /**
+ * Reflects property set in password_prompt_dialog.js.
+ * @type {?Object}
+ */
+ setModes: {
+ type: Object,
+ notify: true,
+ },
+
+ /**
+ * The current PIN keyboard value.
+ * @private
+ */
+ pinKeyboardValue_: String,
+
+ /**
+ * Stores the initial PIN value so it can be confirmed.
+ * @private
+ */
+ initialPin_: String,
+
+ /**
+ * The message ID of actual problem message to display.
+ * @private
+ */
+ problemMessageId_: {
+ type: String,
+ value: '',
+ },
+
+ /**
+ * The additional parameters to format for the problem message string.
+ * @private
+ */
+ problemMessageParameters_: {
+ type: String,
+ value: '',
+ },
+
+ /**
+ * The type of problem class to show (warning or error).
+ * @private
+ */
+ problemClass_: String,
+
+ /**
+ * Should the step-specific submit button be displayed?
+ * This has upward data flow only.
+ */
+ enableSubmit: {
+ notify: true,
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * writeUma is a function that handles writing uma stats.
+ *
+ * @type {function(LockScreenProgress)}
+ */
+ writeUma: {
+ type: Object,
+ value: function() {
+ return function() {};
+ }
+ },
+
+ /**
+ * The current step/subpage we are on.
+ * This is has upward data flow only.
+ */
+ isConfirmStep: {
+ notify: true,
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * Interface for chrome.quickUnlockPrivate calls.
+ * @type {QuickUnlockPrivate}
+ */
+ quickUnlockPrivate: Object,
+
+ /**
+ * |pinHasPassedMinimumLength_| tracks whether a user has passed the minimum
+ * length threshold at least once, and all subsequent PIN too short messages
+ * will be displayed as errors. They will be displayed as warnings prior to
+ * this.
+ * @private
+ */
+ pinHasPassedMinimumLength_: {type: Boolean, value: false},
+
+ /**
+ * Enables pin placeholder.
+ */
+ enablePlaceholder: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * Turns on "incognito mode". (FIXME after https://crbug.com/900351 is
+ * fixed).
+ */
+ isIncognitoUi: {
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ focus: function() {
+ this.$.pinKeyboard.focus();
+ },
+
+ /** @override */
+ attached: function() {
+ this.resetState();
+
+ // Show the pin is too short error when first displaying the PIN dialog.
+ this.problemClass_ = ProblemType.WARNING;
+ this.quickUnlockPrivate.getCredentialRequirements(
+ chrome.quickUnlockPrivate.QuickUnlockMode.PIN,
+ this.processPinRequirements_.bind(this, MessageType.TOO_SHORT));
+ },
+
+ /**
+ * Resets the element to the initial state.
+ */
+ resetState: function() {
+ this.initialPin_ = '';
+ this.pinKeyboardValue_ = '';
+ this.enableSubmit = false;
+ this.isConfirmStep = false;
+ this.hideProblem_();
+ this.onPinChange_();
+ },
+
+ /**
+ * Returns true if the PIN is ready to be changed to a new value.
+ * @private
+ * @return {boolean}
+ */
+ canSubmit_: function() {
+ return this.initialPin_ == this.pinKeyboardValue_;
+ },
+
+ /**
+ * Handles writing the appropriate message to |problemMessageId_| &&
+ * |problemMessageParameters_|.
+ * @private
+ * @param {string} messageId
+ * @param {chrome.quickUnlockPrivate.CredentialRequirements} requirements
+ * The requirements received from getCredentialRequirements.
+ */
+ processPinRequirements_: function(messageId, requirements) {
+ let additionalInformation = '';
+ switch (messageId) {
+ case MessageType.TOO_SHORT:
+ additionalInformation = requirements.minLength.toString();
+ break;
+ case MessageType.TOO_LONG:
+ additionalInformation = (requirements.maxLength + 1).toString();
+ break;
+ case MessageType.TOO_WEAK:
+ case MessageType.MISMATCH:
+ break;
+ default:
+ assertNotReached();
+ break;
+ }
+ this.problemMessageId_ = messageId;
+ this.problemMessageParameters_ = additionalInformation;
+ },
+
+ /**
+ * Notify the user about a problem.
+ * @private
+ * @param {string} messageId
+ * @param {string} problemClass
+ */
+ showProblem_: function(messageId, problemClass) {
+ this.quickUnlockPrivate.getCredentialRequirements(
+ chrome.quickUnlockPrivate.QuickUnlockMode.PIN,
+ this.processPinRequirements_.bind(this, messageId));
+ this.problemClass_ = problemClass;
+ this.updateStyles();
+ this.enableSubmit =
+ problemClass != ProblemType.ERROR && messageId != MessageType.TOO_SHORT;
+ },
+
+ /** @private */
+ hideProblem_: function() {
+ this.problemMessageId_ = '';
+ this.problemClass_ = '';
+ },
+
+ /**
+ * Processes the message received from the quick unlock api and hides/shows
+ * the problem based on the message.
+ * @private
+ * @param {chrome.quickUnlockPrivate.CredentialCheck} message The message
+ * received from checkCredential.
+ */
+ processPinProblems_: function(message) {
+ if (!message.errors.length && !message.warnings.length) {
+ this.hideProblem_();
+ this.enableSubmit = true;
+ this.pinHasPassedMinimumLength_ = true;
+ return;
+ }
+
+ if (!message.errors.length ||
+ message.errors[0] !=
+ chrome.quickUnlockPrivate.CredentialProblem.TOO_SHORT) {
+ this.pinHasPassedMinimumLength_ = true;
+ }
+
+ if (message.warnings.length) {
+ assert(
+ message.warnings[0] ==
+ chrome.quickUnlockPrivate.CredentialProblem.TOO_WEAK);
+ this.showProblem_(MessageType.TOO_WEAK, ProblemType.WARNING);
+ }
+
+ if (message.errors.length) {
+ switch (message.errors[0]) {
+ case chrome.quickUnlockPrivate.CredentialProblem.TOO_SHORT:
+ this.showProblem_(
+ MessageType.TOO_SHORT,
+ this.pinHasPassedMinimumLength_ ? ProblemType.ERROR :
+ ProblemType.WARNING);
+ break;
+ case chrome.quickUnlockPrivate.CredentialProblem.TOO_LONG:
+ this.showProblem_(MessageType.TOO_LONG, ProblemType.ERROR);
+ break;
+ case chrome.quickUnlockPrivate.CredentialProblem.TOO_WEAK:
+ this.showProblem_(MessageType.TOO_WEAK, ProblemType.ERROR);
+ break;
+ default:
+ assertNotReached();
+ break;
+ }
+ }
+ },
+
+ /** @private */
+ onPinChange_: function() {
+ if (!this.isConfirmStep) {
+ if (this.pinKeyboardValue_) {
+ this.quickUnlockPrivate.checkCredential(
+ chrome.quickUnlockPrivate.QuickUnlockMode.PIN,
+ this.pinKeyboardValue_, this.processPinProblems_.bind(this));
+ } else {
+ this.enableSubmit = false;
+ }
+ return;
+ }
+
+ this.hideProblem_();
+ this.enableSubmit = this.pinKeyboardValue_.length > 0;
+ },
+
+ /** @private */
+ onPinSubmit_: function() {
+ // Notify container object.
+ this.fire('pin-submit');
+ },
+
+ /**
+ * This is callback for quickUnlockPrivate.QuickUnlockMode.PIN API.
+ *
+ * @private
+ * @param {boolean} didSet
+ */
+ onSetModesCompleted_: function(didSet) {
+ if (!didSet) {
+ console.error('Failed to update pin');
+ return;
+ }
+
+ this.resetState();
+ this.fire('set-pin-done');
+ },
+
+ /** This is called by container object when user initiated submit. */
+ doSubmit: function() {
+ if (!this.isConfirmStep) {
+ if (!this.enableSubmit)
+ return;
+ this.initialPin_ = this.pinKeyboardValue_;
+ this.pinKeyboardValue_ = '';
+ this.isConfirmStep = true;
+ this.onPinChange_();
+ this.$.pinKeyboard.focus();
+ this.writeUma(LockScreenProgress.ENTER_PIN);
+ return;
+ }
+ // onPinSubmit gets called if the user hits enter on the PIN keyboard.
+ // The PIN is not guaranteed to be valid in that case.
+ if (!this.canSubmit_()) {
+ this.showProblem_(MessageType.MISMATCH, ProblemType.ERROR);
+ this.enableSubmit = false;
+ // Focus the PIN keyboard and highlight the entire PIN.
+ this.$.pinKeyboard.focus(0, this.pinKeyboardValue_.length + 1);
+ return;
+ }
+
+ assert(this.setModes);
+ this.setModes.call(
+ null, [chrome.quickUnlockPrivate.QuickUnlockMode.PIN],
+ [this.pinKeyboardValue_], this.onSetModesCompleted_.bind(this));
+ this.writeUma(LockScreenProgress.CONFIRM_PIN);
+ },
+
+ /**
+ * @private
+ * @param {string} problemMessageId
+ * @param {string} problemClass
+ * @return {boolean}
+ */
+ hasError_: function(problemMessageId, problemClass) {
+ return !!problemMessageId && problemClass == ProblemType.ERROR;
+ },
+
+ /**
+ * Formar problem message
+ * @private
+ * @param {string} locale i18n locale data
+ * @param {string} messageId
+ * @param {string} messageParameters
+ * @return {string}
+ */
+ formatProblemMessage_: function(locale, messageId, messageParameters) {
+ return messageId ? this.i18nDynamic(locale, messageId, messageParameters) :
+ '';
+ },
+});
+
+})();
diff --git a/chromium/ui/webui/resources/cr_components/cr_components_images.grdp b/chromium/ui/webui/resources/cr_components/cr_components_images.grdp
new file mode 100644
index 00000000000..9afe1f136f9
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_components/cr_components_images.grdp
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+ <if expr="chromeos">
+ <include name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_START_SETUP_ICON_1X_PNG"
+ file="cr_components/chromeos/multidevice_setup/start_setup_icon_1x.png"
+ type="BINDATA"
+ compress="gzip" />
+ <include name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_START_SETUP_ICON_2X_PNG"
+ file="cr_components/chromeos/multidevice_setup/start_setup_icon_2x.png"
+ type="BINDATA"
+ compress="gzip" />
+ <include name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_SETUP_SUCCEEDED_ICON_1X_PNG"
+ file="cr_components/chromeos/multidevice_setup/setup_succeeded_icon_1x.png"
+ type="BINDATA"
+ compress="gzip" />
+ <include name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_SETUP_SUCCEEDED_ICON_2X_PNG"
+ file="cr_components/chromeos/multidevice_setup/setup_succeeded_icon_2x.png"
+ type="BINDATA"
+ compress="gzip" />
+ </if>
+</grit-part>
diff --git a/chromium/ui/webui/resources/cr_components/cr_components_resources.grdp b/chromium/ui/webui/resources/cr_components/cr_components_resources.grdp
index 2ff0de4098b..3bf1f1cdd56 100644
--- a/chromium/ui/webui/resources/cr_components/cr_components_resources.grdp
+++ b/chromium/ui/webui/resources/cr_components/cr_components_resources.grdp
@@ -123,19 +123,19 @@
file="cr_components/chromeos/network/network_choose_mobile.js"
type="chrome_html"
compress="gzip" />
- <structure name="IDR_WEBUI_CHROMEOS__NETWORK_CONFIG_HTML"
+ <structure name="IDR_WEBUI_CHROMEOS_NETWORK_CONFIG_HTML"
file="cr_components/chromeos/network/network_config.html"
type="chrome_html"
compress="gzip" />
- <structure name="IDR_WEBUI_CHROMEOS__NETWORK_CONFIG_JS"
+ <structure name="IDR_WEBUI_CHROMEOS_NETWORK_CONFIG_JS"
file="cr_components/chromeos/network/network_config.js"
type="chrome_html"
compress="gzip" />
- <structure name="IDR_WEBUI_CHROMEOS__NETWORK_CONFIG_SELECT_HTML"
+ <structure name="IDR_WEBUI_CHROMEOS_NETWORK_CONFIG_SELECT_HTML"
file="cr_components/chromeos/network/network_config_select.html"
type="chrome_html"
compress="gzip" />
- <structure name="IDR_WEBUI_CHROMEOS__NETWORK_CONFIG_SELECT_JS"
+ <structure name="IDR_WEBUI_CHROMEOS_NETWORK_CONFIG_SELECT_JS"
file="cr_components/chromeos/network/network_config_select.js"
type="chrome_html"
compress="gzip" />
@@ -155,11 +155,11 @@
file="cr_components/chromeos/network/network_nameservers.js"
type="chrome_html"
compress="gzip" />
- <structure name="IDR_WEBUI_CHROMEOS__NETWORK_PASSWORD_INPUT_HTML"
+ <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PASSWORD_INPUT_HTML"
file="cr_components/chromeos/network/network_password_input.html"
type="chrome_html"
compress="gzip" />
- <structure name="IDR_WEBUI_CHROMEOS__NETWORK_PASSWORD_INPUT_JS"
+ <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PASSWORD_INPUT_JS"
file="cr_components/chromeos/network/network_password_input.js"
type="chrome_html"
compress="gzip" />
@@ -218,5 +218,127 @@
file="cr_components/chromeos/quick_unlock/pin_keyboard.js"
type="chrome_html"
compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_QUICK_UNLOCK_SETUP_PIN_KEYBOARD_JS"
+ file="cr_components/chromeos/quick_unlock/setup_pin_keyboard.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_QUICK_UNLOCK_SETUP_PIN_KEYBOARD_HTML"
+ file="cr_components/chromeos/quick_unlock/setup_pin_keyboard.html"
+ type="chrome_html"
+ compress="gzip"/>
+ <structure name="IDR_WEBUI_CHROMEOS_QUICK_UNLOCK_LOCK_SCREEN_CONSTANTS_JS"
+ file="cr_components/chromeos/quick_unlock/lock_screen_constants.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_QUICK_UNLOCK_LOCK_SCREEN_CONSTANTS_HTML"
+ file="cr_components/chromeos/quick_unlock/lock_screen_constants.html"
+ type="chrome_html"
+ compress="gzip" />
+
+ <!-- Shared between MultiDevice setup flow and OOBE. -->
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_BROWSER_PROXY_HTML"
+ file="cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_BROWSER_PROXY_JS"
+ file="cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_BUTTON_BAR_HTML"
+ file="cr_components/chromeos/multidevice_setup/button_bar.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_BUTTON_BAR_JS"
+ file="cr_components/chromeos/multidevice_setup/button_bar.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_FAKE_MOJO_SERVICE_HTML"
+ file="cr_components/chromeos/multidevice_setup/fake_mojo_service.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_FAKE_MOJO_SERVICE_JS"
+ file="cr_components/chromeos/multidevice_setup/fake_mojo_service.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_ICONS_HTML"
+ file="cr_components/chromeos/multidevice_setup/icons.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MOJO_API_HTML"
+ file="cr_components/chromeos/multidevice_setup/mojo_api.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MOJO_API_JS"
+ file="cr_components/chromeos/multidevice_setup/mojo_api.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_HTML"
+ file="cr_components/chromeos/multidevice_setup/multidevice_setup.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_JS"
+ file="cr_components/chromeos/multidevice_setup/multidevice_setup.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_DELEGATE_HTML"
+ file="cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_DELEGATE_JS"
+ file="cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_SHARED_CSS_HTML"
+ file="cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_PASSWORD_PAGE_HTML"
+ file="cr_components/chromeos/multidevice_setup/password_page.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_PASSWORD_PAGE_JS"
+ file="cr_components/chromeos/multidevice_setup/password_page.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_SETUP_FAILED_PAGE_HTML"
+ file="cr_components/chromeos/multidevice_setup/setup_failed_page.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_SETUP_FAILED_PAGE_JS"
+ file="cr_components/chromeos/multidevice_setup/setup_failed_page.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_SETUP_SUCCEEDED_PAGE_HTML"
+ file="cr_components/chromeos/multidevice_setup/setup_succeeded_page.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_SETUP_SUCCEEDED_PAGE_JS"
+ file="cr_components/chromeos/multidevice_setup/setup_succeeded_page.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_START_SETUP_PAGE_HTML"
+ file="cr_components/chromeos/multidevice_setup/start_setup_page.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_START_SETUP_PAGE_JS"
+ file="cr_components/chromeos/multidevice_setup/start_setup_page.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_UI_PAGE_CONTAINER_BEHAVIOR_HTML"
+ file="cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_UI_PAGE_CONTAINER_BEHAVIOR_JS"
+ file="cr_components/chromeos/multidevice_setup/ui_page_container_behavior.js"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_UI_PAGE_HTML"
+ file="cr_components/chromeos/multidevice_setup/ui_page.html"
+ type="chrome_html"
+ compress="gzip" />
+ <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_UI_PAGE_JS"
+ file="cr_components/chromeos/multidevice_setup/ui_page.js"
+ type="chrome_html"
+ compress="gzip" />
</if>
</grit-part>
diff --git a/chromium/ui/webui/resources/cr_elements/BUILD.gn b/chromium/ui/webui/resources/cr_elements/BUILD.gn
index 90954848f2e..9927b6e60e8 100644
--- a/chromium/ui/webui/resources/cr_elements/BUILD.gn
+++ b/chromium/ui/webui/resources/cr_elements/BUILD.gn
@@ -22,6 +22,7 @@ group("closure_compile") {
"cr_slider:closure_compile",
"cr_toast:closure_compile",
"cr_toggle:closure_compile",
+ "cr_view_manager:closure_compile",
"policy:closure_compile",
]
}
diff --git a/chromium/ui/webui/resources/cr_elements/READE.md b/chromium/ui/webui/resources/cr_elements/README.md
index b8b47ef98b3..b8b47ef98b3 100644
--- a/chromium/ui/webui/resources/cr_elements/READE.md
+++ b/chromium/ui/webui/resources/cr_elements/README.md
diff --git a/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js b/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js
index 32dfbba4732..66036c7283d 100644
--- a/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js
+++ b/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js
@@ -159,6 +159,8 @@ Polymer({
if (this.cellularDeviceState_)
this.ensureCellularNetwork_(networkStates);
this.networkStateList_ = networkStates;
+ this.fire('network-list-changed', networkStates);
+
var defaultNetwork;
for (var i = 0; i < networkStates.length; ++i) {
var state = networkStates[i];
diff --git a/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html b/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html
index 3bb931d9a4b..3c87967d159 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html
+++ b/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html
@@ -60,7 +60,7 @@
outline: none;
}
</style>
- <dialog id="dialog" tabindex="0">
+ <dialog id="dialog" tabindex="0" on-close="onNativeDialogClose_">
<div class="item-wrapper" tabindex="-1" role="menu">
<slot name="item" id="contentNode"></slot>
</div>
diff --git a/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js b/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
index ec1ac2d8fce..9c70ecede80 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
+++ b/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
@@ -197,6 +197,24 @@ Polymer({
* @param {!Event} e
* @private
*/
+ onNativeDialogClose_: function(e) {
+ // Ignore any 'close' events not fired directly by the <dialog> element.
+ if (e.target !== this.$.dialog)
+ return;
+
+ // TODO(dpapad): This is necessary to make the code work both for Polymer 1
+ // and Polymer 2. Remove once migration to Polymer 2 is completed.
+ e.stopPropagation();
+
+ // Catch and re-fire the 'close' event such that it bubbles across Shadow
+ // DOM v1.
+ this.fire('close');
+ },
+
+ /**
+ * @param {!Event} e
+ * @private
+ */
onTap_: function(e) {
if (e.target == this) {
this.close();
@@ -271,7 +289,7 @@ Polymer({
var options = this.querySelectorAll('.dropdown-item');
var numOptions = options.length;
var focusedIndex =
- Array.prototype.indexOf.call(options, this.root.activeElement);
+ Array.prototype.indexOf.call(options, getDeepActiveElement());
// Handle case where nothing is focused and up is pressed.
if (focusedIndex === -1 && step === -1)
diff --git a/chromium/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js b/chromium/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js
index 45d8db171ec..3eb059ebfcf 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js
+++ b/chromium/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js
@@ -29,7 +29,7 @@ var CrContainerShadowBehavior = {
var dropShadow = document.createElement('div');
// This ID should match the CSS rules in shared_styles_css.html.
dropShadow.id = 'cr-container-shadow';
- this.shadowRoot.insertBefore(dropShadow, this.$.container);
+ this.$.container.parentNode.insertBefore(dropShadow, this.$.container);
// Dummy element used to detect scrolling. Has a 0px height intentionally.
var intersectionProbe = document.createElement('div');
diff --git a/chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js b/chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
index 864898edab5..ebf0ad52afa 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
+++ b/chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
@@ -52,6 +52,15 @@ Polymer({
},
/**
+ * True if the dialog should consume 'keydown' events. If ignoreEnterKey
+ * is true, 'Enter' key won't be consumed.
+ */
+ consumeKeydownEvent: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
* True if the dialog should not be able to be cancelled, which will prevent
* 'Escape' key presses from closing the dialog.
*/
@@ -77,6 +86,9 @@ Polymer({
/** @private {?MutationObserver} */
mutationObserver_: null,
+ /** @private {?Function} */
+ boundKeydown_: null,
+
/** @override */
ready: function() {
// If the active history entry changes (i.e. user clicks back button),
@@ -93,10 +105,13 @@ Polymer({
/** @override */
attached: function() {
var mutationObserverCallback = function() {
- if (this.$.dialog.open)
+ if (this.$.dialog.open) {
this.addIntersectionObserver_();
- else
+ this.addKeydownListener_();
+ } else {
this.removeIntersectionObserver_();
+ this.removeKeydownListener_();
+ }
}.bind(this);
this.mutationObserver_ = new MutationObserver(mutationObserverCallback);
@@ -113,6 +128,7 @@ Polymer({
/** @override */
detached: function() {
this.removeIntersectionObserver_();
+ this.removeKeydownListener_();
if (this.mutationObserver_) {
this.mutationObserver_.disconnect();
this.mutationObserver_ = null;
@@ -163,6 +179,31 @@ Polymer({
}
},
+ /** @private */
+ addKeydownListener_: function() {
+ if (!this.consumeKeydownEvent)
+ return;
+
+ this.boundKeydown_ = this.boundKeydown_ || this.onKeydown_.bind(this);
+
+ this.addEventListener('keydown', this.boundKeydown_);
+
+ // Sometimes <body> is key event's target and in that case the event
+ // will bypass cr-dialog. We should consume those events too in order to
+ // behave modally. This prevents accidentally triggering keyboard commands.
+ document.body.addEventListener('keydown', this.boundKeydown_);
+ },
+
+ /** @private */
+ removeKeydownListener_: function() {
+ if (!this.boundKeydown_)
+ return;
+
+ this.removeEventListener('keydown', this.boundKeydown_);
+ document.body.removeEventListener('keydown', this.boundKeydown_);
+ this.boundKeydown_ = null;
+ },
+
showModal: function() {
this.$.dialog.showModal();
assert(this.$.dialog.open);
@@ -216,6 +257,10 @@ Polymer({
* @private
*/
onNativeDialogCancel_: function(e) {
+ // Ignore any 'cancel' events not fired directly by the <dialog> element.
+ if (e.target !== this.getNative())
+ return;
+
if (this.noCancel) {
e.preventDefault();
return;
@@ -265,6 +310,23 @@ Polymer({
}
},
+ /**
+ * @param {!Event} e
+ * @private
+ */
+ onKeydown_: function(e) {
+ assert(this.consumeKeydownEvent);
+
+ if (!this.getNative().open)
+ return;
+
+ if (this.ignoreEnterKey && e.key == 'Enter')
+ return;
+
+ // Stop propagation to behave modally.
+ e.stopPropagation();
+ },
+
/** @param {!PointerEvent} e */
onPointerdown_: function(e) {
// Only show pulse animation if user left-clicked outside of the dialog
diff --git a/chromium/ui/webui/resources/cr_elements/cr_input/cr_input.html b/chromium/ui/webui/resources/cr_elements/cr_input/cr_input.html
index 5be1ea9e8c9..b328ee4b72e 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_input/cr_input.html
+++ b/chromium/ui/webui/resources/cr_elements/cr_input/cr_input.html
@@ -10,18 +10,22 @@
<template>
<style include="cr-hidden-style cr-input-style">
/*
+ A 'suffix' element will be outside the underlined space, while a
+ 'prefix' element will be inside the underlined space by default.
+
Regarding cr-input's width:
- When there's no element in the 'suffix' slot, setting the width of
- cr-input as follows will work as expected:
+ When there's no element in the 'prefix' or 'suffix' slot, setting
+ the width of cr-input as follows will work as expected:
cr-input {
width: 200px;
}
- However, when there's an element in the 'suffix' slot, setting the
- 'width' will dictate the total with of the input field *plus* the
- 'suffix' element. To set the width of the input field itself when
- a 'suffix' is present, use --cr-input-width.
+ However, when there's an element in the 'suffix' and/or 'prefix'
+ slot, setting the 'width' will dictate the total width of the input
+ field *plus* the 'prefix' and 'suffix' elements. To set the width
+ of the input field + 'prefix' when a 'suffix' is present, use
+ --cr-input-width.
cr-input {
--cr-input-width: 200px;
@@ -72,8 +76,8 @@
color: var(--cr-input-error-color);
display: var(--cr-input-error-display, block);
font-size: var(--cr-form-field-label-font-size);
- height: var(--cr-form-field-label-font-size);
- line-height: var(--cr-form-field-label-font-size);
+ height: var(--cr-form-field-label-height);
+ line-height: var(--cr-form-field-label-line-height);
margin: 8px 0;
visibility: hidden;
}
@@ -82,14 +86,17 @@
visibility: visible;
}
- #row-container {
+ #row-container,
+ #inner-input-container {
align-items: center;
display: flex;
/* This will spread the input field and the suffix apart only if the
host element width is intentionally set to something large. */
justify-content: space-between;
position: relative;
+ }
+ #row-container {
@apply --cr-input-row-container;
}
@@ -103,12 +110,17 @@
<!-- Only attributes that are named inconsistently between html and js
need to use attr$="", such as |tabindex| vs .tabIndex and
|readonly| vs .readOnly. -->
- <input id="input" disabled="[[disabled]]" autofocus="[[autofocus]]"
- value="{{value::input}}" tabindex$="[[tabindex]]" type="[[type]]"
- readonly$="[[readonly]]" maxlength$="[[maxlength]]"
- pattern="[[pattern]]" required="[[required]]"
- incremental="[[incremental]]" minlength$="[[minlength]]"
- max="[[max]]" min="[[min]]">
+ <div id="inner-input-container">
+ <slot name="prefix"></slot>
+ <input id="input" disabled="[[disabled]]" autofocus="[[autofocus]]"
+ value="{{value::input}}" tabindex$="[[tabindex]]" type="[[type]]"
+ readonly$="[[readonly]]" maxlength$="[[maxlength]]"
+ pattern$="[[pattern]]" required="[[required]]"
+ minlength$="[[minlength]]"
+ max="[[max]]" min="[[min]]" on-focus="onInputFocus_"
+ on-blur="onInputBlur_" on-change="onInputChange_"
+ on-keydown="onInputKeydown_">
+ </div>
<div id="underline"></div>
</div>
<slot name="suffix"></slot>
diff --git a/chromium/ui/webui/resources/cr_elements/cr_input/cr_input.js b/chromium/ui/webui/resources/cr_elements/cr_input/cr_input.js
index 2cbd52aa289..67b9484a99b 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_input/cr_input.js
+++ b/chromium/ui/webui/resources/cr_elements/cr_input/cr_input.js
@@ -8,7 +8,6 @@
* Native input attributes that are currently supported by cr-inputs are:
* autofocus
* disabled
- * incremental (only applicable when type="search")
* max (only applicable when type="number")
* min (only applicable when type="number")
* maxlength
@@ -72,8 +71,6 @@ Polymer({
reflectToAttribute: true,
},
- incremental: Boolean,
-
invalid: {
type: Boolean,
value: false,
@@ -150,11 +147,7 @@ Polymer({
},
listeners: {
- 'input.focus': 'onInputFocus_',
- 'input.blur': 'onInputBlur_',
- 'input.change': 'onInputChange_',
- 'input.keydown': 'onInputKeydown_',
- 'focus': 'focusInput_',
+ 'focus': 'onFocus_',
'pointerdown': 'onPointerDown_',
},
@@ -219,9 +212,24 @@ Polymer({
},
/** @private */
+ onFocus_: function() {
+ if (!this.focusInput_())
+ return;
+ // Always select the <input> element on focus. TODO(stevenjb/scottchen):
+ // Native <input> elements only do this for keyboard focus, not when
+ // focus() is called directly. Fix this? https://crbug.com/882612.
+ this.inputElement.select();
+ },
+
+ /**
+ * @return {boolean} Whether the <input> element was focused.
+ * @private
+ */
focusInput_: function() {
- if (this.shadowRoot.activeElement != this.inputElement)
- this.inputElement.focus();
+ if (this.shadowRoot.activeElement == this.inputElement)
+ return false;
+ this.inputElement.focus();
+ return true;
},
/** @private */
@@ -328,4 +336,4 @@ Polymer({
this.invalid = !this.inputElement.checkValidity();
return !this.invalid;
},
-}); \ No newline at end of file
+});
diff --git a/chromium/ui/webui/resources/cr_elements/cr_input/cr_input_style_css.html b/chromium/ui/webui/resources/cr_elements/cr_input/cr_input_style_css.html
index 2b3e104397f..a78334b5223 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_input/cr_input_style_css.html
+++ b/chromium/ui/webui/resources/cr_elements/cr_input/cr_input_style_css.html
@@ -35,6 +35,15 @@
@apply --cr-input-container;
}
+ #inner-input-container {
+ background-color: var(--cr-input-background-color,
+ var(--google-grey-refresh-100));
+ box-sizing: border-box;
+ padding: 0;
+
+ @apply --cr-input-inner-container;
+ }
+
#input {
-webkit-appearance: none;
background-color: var(--cr-input-background-color,
@@ -78,11 +87,12 @@
opacity: 0;
position: absolute;
right: 0;
- transition: opacity 120ms ease-out, width 0 linear 180ms;
+ transition: opacity 120ms ease-out, width 0s linear 180ms;
width: 0;
}
:host([invalid]) #underline,
+ :host([force-underline]) #underline,
:host([focused_]:not([readonly])) #underline {
opacity: 1;
transition: width 180ms ease-out, opacity 120ms ease-in;
diff --git a/chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js b/chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js
index 1c41e4a776f..179a09753c2 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js
+++ b/chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js
@@ -32,6 +32,9 @@ var CrSearchFieldBehavior = {
},
},
+ /** @private {number} */
+ searchDelayTimer_: -1,
+
/**
* @return {!HTMLInputElement} The input field element the behavior should
* use.
@@ -59,6 +62,26 @@ var CrSearchFieldBehavior = {
this.onValueChanged_(value, !!opt_noEvent);
},
+ /** @private */
+ scheduleSearch_: function() {
+ if (this.searchDelayTimer_ >= 0)
+ clearTimeout(this.searchDelayTimer_);
+ // Dispatch 'search' event after:
+ // 0ms if the value is empty
+ // 500ms if the value length is 1
+ // 400ms if the value length is 2
+ // 300ms if the value length is 3
+ // 200ms if the value length is 4 or greater.
+ // The logic here was copied from WebKit's native 'search' event.
+ var length = this.getValue().length;
+ var timeoutMs = length > 0 ? (500 - 100 * (Math.min(length, 4) - 1)) : 0;
+ this.searchDelayTimer_ = setTimeout(() => {
+ this.getSearchInput().dispatchEvent(
+ new CustomEvent('search', {composed: true, detail: this.getValue()}));
+ this.searchDelayTimer_ = -1;
+ }, timeoutMs);
+ },
+
onSearchTermSearch: function() {
this.onValueChanged_(this.getValue(), false);
},
@@ -70,6 +93,7 @@ var CrSearchFieldBehavior = {
*/
onSearchTermInput: function() {
this.hasSearchText = this.$.searchInput.value != '';
+ this.scheduleSearch_();
},
/**
diff --git a/chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.html b/chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.html
index 168a5fec0e7..209dc714d37 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.html
+++ b/chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.html
@@ -49,7 +49,8 @@
immediately as the user types unless the update-value-on-input flag is
explicitly used. -->
<cr-input label="[[label]]" on-click="onClick_" value="[[value]]"
- on-input="onInput_" id="search" autofocus="[[autofocus]]">
+ on-input="onInput_" id="search" autofocus="[[autofocus]]"
+ placeholder="[[placeholder]]">
</cr-input>
<iron-dropdown horizontal-align="left" vertical-align="top"
vertical-offset="52">
diff --git a/chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js b/chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js
index 3ce90c6fe13..e7e94edb832 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js
+++ b/chromium/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js
@@ -20,6 +20,8 @@ Polymer({
reflectToAttribute: true,
},
+ placeholder: String,
+
/** @type {!Array<string>} */
items: Array,
diff --git a/chromium/ui/webui/resources/cr_elements/cr_slider/BUILD.gn b/chromium/ui/webui/resources/cr_elements/cr_slider/BUILD.gn
index 78d6d47bac9..ad5d8f69b5e 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_slider/BUILD.gn
+++ b/chromium/ui/webui/resources/cr_elements/cr_slider/BUILD.gn
@@ -12,6 +12,8 @@ js_type_check("closure_compile") {
js_library("cr_slider") {
deps = [
- "//third_party/polymer/v1_0/components-chromium/paper-slider:paper-slider-extracted",
+ "//third_party/polymer/v1_0/components-chromium/paper-behaviors:paper-ripple-behavior-extracted",
+ "//ui/webui/resources/js:cr",
+ "//ui/webui/resources/js:event_tracker",
]
}
diff --git a/chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.html b/chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
index 05adf450012..f6203a2f110 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
+++ b/chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
@@ -1,52 +1,197 @@
<link rel="import" href="../../html/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-ripple-behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout.html">
+<link rel="import" href="../../html/cr.html">
+<link rel="import" href="../../html/event_tracker.html">
+<link rel="import" href="../hidden_style_css.html">
<link rel="import" href="../shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-slider/paper-slider.html">
<dom-module id="cr-slider">
<template>
- <style>
- paper-slider {
- --paper-slider-active-color: var(--google-blue-600);
- --paper-slider-container-color: var(--google-blue-600-opacity-24);
- --paper-slider-knob-color: var(--google-blue-600);
- --paper-slider-knob-start-color: var(--google-blue-600);
- --paper-slider-knob-start-border-color: var(--google-blue-600);
- --paper-slider-pin-color: var(--google-blue-600);
- --paper-slider-pin-start-color: var(--google-blue-600);
- --paper-slider-markers-color: rgba(255, 255, 255, 0.54);
- --paper-slider-disabled-active-color: var(--google-grey-600);
- --paper-slider-disabled-knob-color: var(--google-grey-600);
- width: 100%;
-
- --paper-slider-pin-text: {
- font-family: Roboto;
- font-size: 12px;
- font-weight: 500;
- line-height: 14px;
- };
- }
-
- :host-context([dir=rtl]) paper-slider {
- --paper-slider-pin-text: {
- font-family: Roboto;
- font-size: 12px;
- font-weight: 500;
- line-height: 14px;
- transform: scale(-1, 1) translate(0, -17px);
- };
- }
-
- paper-slider[disabled] {
- --paper-slider-container-color: var(--google-grey-600-opacity-24);
+ <style include="cr-hidden-style">
+ :host {
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ cursor: default;
+ user-select: none;
+ }
+
+ :host([dragging]) {
+ touch-action: none;
+ }
+
+ #container {
+ height: 32px;
+ position: relative;
+ }
+
+ #barContainer {
+ background-color: var(--google-blue-600-opacity-24);
+ border-radius: 1px;
+ height: 2px;
+ margin: 0 16px;
+ position: absolute;
+ top: 15px;
+ width: calc(100% - 32px);
+ }
+
+ #bar {
+ background-color: var(--google-blue-600);
+ border-radius: 1px;
+ height: 2px;
+ left: 0;
+ position: absolute;
+ transition: width 80ms ease;
+ width: 0;
+ }
+
+ :host-context([dir=rtl]) #bar {
+ left: initial;
+ right: 0;
+ }
+
+ #knobContainer {
+ margin-inline-start: 12px;
+ position: absolute;
+ top: 11px;
+ width: calc(100% - 32px);
+ }
+
+ #knob {
+ background-color: var(--google-blue-600);
+ border: 0;
+ border-radius: 50%;
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4);
+ height: 10px;
+ margin-inline-start: 0;
+ outline: none;
+ position: absolute;
+ transition: margin-inline-start 80ms ease;
+ width: 10px;
+ }
+
+ paper-ripple {
+ color: var(--google-blue-600);
+ height: 32px;
+ left: -11px;
+ pointer-events: none;
+ top: -11px;
+ transition: color linear 80ms;
+ width: 32px;
+ }
+
+ :host-context([dir=rtl]) paper-ripple {
+ left: auto;
+ right: -11px;
+ }
+
+ #markers {
+ left: 0;
+ pointer-events: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+ @apply --layout-horizontal;
+ }
+
+ .active-marker,
+ .inactive-marker {
+ @apply --layout-flex;
+ }
+ #markers::before,
+ #markers::after,
+ .active-marker::after,
+ .inactive-marker::after {
+ border-radius: 50%;
+ content: '';
+ display: block;
+ height: 2px;
+ margin-left: -1px;
+ width: 2px;
+ }
+
+ #markers::before,
+ .active-marker::after {
+ background-color: rgba(255, 255, 255, 0.54);
+ }
+
+ #markers::after,
+ .inactive-marker::after {
+ background-color: rgba(26, 115, 232, 0.54);
+ }
+
+ #labelContainer {
+ cursor: default;
+ margin-inline-start: 1px;
+ opacity: 0;
+ transition: opacity 80ms ease-in-out;
+ user-select: none;
+ width: calc(100% - 32px);
+ }
+
+ #container:hover #labelContainer,
+ .hover #labelContainer,
+ :host([hold-down_]) #labelContainer {
+ opacity: 1;
+ }
+
+ #label {
+ background: var(--google-blue-600);
+ border-radius: 14px;
+ bottom: 28px;
+ color: white;
+ font-size: 12px;
+ line-height: 1.5em;
+ padding: 0 8px;
+ position: absolute;
+ transition: margin-inline-start 80ms ease;
+ white-space: nowrap;
+ }
+
+ :host([disabled]) {
+ pointer-events: none;
+ }
+
+ :host([disabled]) #barContainer {
+ background-color: var(--google-grey-600-opacity-24);
+ }
+
+ :host([disabled]) #bar {
+ background-color: var(--google-grey-600);
+ }
+
+ :host([disabled]) inactive-marker::after,
+ :host([disabled]) #markers::after {
+ background-color: rgba(255, 255, 255, 0.54);
+ }
+
+ :host([disabled]) #knobContainer {
+ margin-inline-start: 9px;
+ top: 9px;
+ }
+ :host([disabled]) #knob {
+ background-color: var(--google-grey-600);
+ border: 2px solid white;
+ box-shadow: unset;
}
</style>
- <paper-slider id="slider"
- disabled$="[[disabled]]" snaps="[[snaps]]" on-change="onChange_"
- max="[[max]]" min="[[min]]" on-up="resetTrackLock_" value="{{value}}"
- max-markers="[[maxMarkers]]" immediate-value="{{immediateValue}}"
- dragging="{{dragging}}">
- </paper-slider>
+ <div id="container">
+ <div id="barContainer">
+ <div id="bar"></div>
+ <div id="markers" hidden$="[[!markerCount]]">
+ <template is="dom-repeat" items="[[getMarkers_(markerCount)]]">
+ <div class$="[[getMarkerClass_(index, immediateValue_, min, max,
+ markerCount)]]"></div>
+ </template>
+ </div>
+ </div>
+ <div id="knobContainer">
+ <div id="knob" tabindex="0"></div>
+ </div>
+ <div id="labelContainer" aria-label="[[label_]]">
+ <div id="label">[[label_]]</div>
+ </div>
+ </div>
</template>
<script src="cr_slider.js"></script>
</dom-module>
diff --git a/chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.js b/chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
index 16180c12ec0..97033f5ade8 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
+++ b/chromium/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
@@ -3,148 +3,420 @@
// found in the LICENSE file.
/**
- * @fileoverview 'cr-slider' is a wrapper around paper-slider to alter the
- * styling. The behavior of the slider remains the same.
+ * @fileoverview 'cr-slider' is a slider component used to select a number from
+ * a continuous or discrete range of numbers.
*/
-Polymer({
- is: 'cr-slider',
- properties: {
- min: Number,
+cr.exportPath('cr_slider');
- max: Number,
+/**
+ * The |value| is the corresponding value that the current slider tick is
+ * associated with. The string |label| is shown in the UI as the label for the
+ * current slider value. The |ariaValue| number is used for aria-valuemin,
+ * aria-valuemax, and aria-valuenow, and is optional. If missing, |value| will
+ * be used instead.
+ * @typedef {{
+ * value: number,
+ * label: string,
+ * ariaValue: (number|undefined),
+ * }}
+ */
+cr_slider.SliderTick;
+
+(() => {
+ /**
+ * @param {number} min
+ * @param {number} max
+ * @param {number} value
+ * @return {number}
+ */
+ function clamp(min, max, value) {
+ return Math.min(max, Math.max(min, value));
+ }
+
+ Polymer({
+ is: 'cr-slider',
+
+ behaviors: [
+ Polymer.PaperRippleBehavior,
+ ],
+
+ properties: {
+ disabled: {
+ type: Boolean,
+ value: false,
+ reflectToAttribute: true,
+ },
+
+ dragging: {
+ type: Boolean,
+ value: false,
+ reflectToAttribute: true,
+ },
+
+ markerCount: {
+ type: Number,
+ value: 0,
+ },
+
+ max: {
+ type: Number,
+ value: 100,
+ },
+
+ min: {
+ type: Number,
+ value: 0,
+ },
+
+ snaps: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * The data associated with each tick on the slider. Each element in the
+ * array contains a value and the label corresponding to that value.
+ * @type {!Array<cr_slider.SliderTick>|!Array<number>}
+ */
+ ticks: {
+ type: Array,
+ value: () => [],
+ observer: 'onTicksChanged_',
+ },
+
+ value: {
+ type: Number,
+ value: 0,
+ notify: true,
+ observer: 'onValueChanged_',
+ },
- snaps: {
- type: Boolean,
- value: true,
+ /**
+ * If true, |value| is updated while dragging happens. If false, |value|
+ * is updated only once, when drag gesture finishes.
+ */
+ updateValueInstantly: {
+ type: Boolean,
+ value: true,
+ },
+
+ /**
+ * |immediateValue_| has the most up-to-date value and is used to render
+ * the slider UI. When dragging, |immediateValue_| is always updated, and
+ * |value| is updated at least once when dragging is stopped.
+ * @private
+ */
+ immediateValue_: {
+ type: Number,
+ value: 0,
+ },
+
+ /** @private */
+ holdDown_: {
+ type: Boolean,
+ value: false,
+ observer: 'onHoldDownChanged_',
+ reflectToAttribute: true,
+ },
+
+ /** @private */
+ label_: {
+ type: String,
+ value: '',
+ },
},
- disabled: {
- type: Boolean,
- observer: 'onDisabledChanged_',
+ hostAttributes: {
+ role: 'slider',
},
- value: Number,
- maxMarkers: Number,
+ observers: [
+ 'updateLabelAndAria_(immediateValue_, min, max)',
+ 'updateKnobAndBar_(immediateValue_, min, max)',
+ ],
- immediateValue: {
- type: Number,
- observer: 'onImmediateValueChanged_',
+ listeners: {
+ focus: 'onFocus_',
+ blur: 'onBlur_',
+ keydown: 'onKeyDown_',
+ pointerdown: 'onPointerDown_',
},
- dragging: Boolean,
- },
+ /** @private {Map<string, number>} */
+ deltaKeyMap_: null,
- listeners: {
- 'focus': 'onFocus_',
- 'blur': 'onBlur_',
- 'keydown': 'onKeyDown_',
- 'pointerdown': 'onPointerDown_',
- 'pointerup': 'onPointerUp_',
- },
+ /** @private {boolean} */
+ isRtl_: false,
- /** @private {boolean} */
- usedMouse_: false,
+ /** @private {EventTracker} */
+ draggingEventTracker_: null,
- /** @override */
- attached: function() {
- this.onDisabledChanged_();
- },
+ /** @override */
+ attached: function() {
+ this.isRtl_ = this.matches(':host-context([dir=rtl]) cr-slider');
+ this.deltaKeyMap_ = new Map([
+ ['ArrowDown', -1],
+ ['ArrowUp', 1],
+ ['PageDown', -1],
+ ['PageUp', 1],
+ ['ArrowLeft', this.isRtl_ ? 1 : -1],
+ ['ArrowRight', this.isRtl_ ? -1 : 1],
+ ]);
+ this.draggingEventTracker_ = new EventTracker();
+ },
- /** @private */
- onFocus_: function() {
- this.$.slider.getRipple().holdDown = true;
- this.$.slider._expandKnob();
- },
+ /**
+ * When markers are displayed on the slider, they are evenly spaced across
+ * the entire slider bar container and are rendered on top of the bar and
+ * bar container. The location of the marks correspond to the discrete
+ * values that the slider can have.
+ * @return {!Array} The array items have no type since this is used to
+ * create |markerCount| number of markers.
+ * @private
+ */
+ getMarkers_: function() {
+ return new Array(Math.max(0, this.markerCount - 1));
+ },
- /** @private */
- onBlur_: function() {
- this.$.slider.getRipple().holdDown = false;
- this.$.slider._resetKnob();
- },
+ /**
+ * @param {number} index
+ * @return {string}
+ * @private
+ */
+ getMarkerClass_: function(index) {
+ const currentStep = (this.markerCount - 1) * this.getRatio_();
+ return index < currentStep ? 'active-marker' : 'inactive-marker';
+ },
- /** @private */
- onChange_: function() {
- this.$.slider._setExpand(!this.usedMouse_);
- this.$.slider.getRipple().holdDown = !this.usedMouse_;
- this.usedMouse_ = false;
- },
+ /**
+ * The ratio is a value from 0 to 1.0 corresponding to a location along the
+ * slider bar where 0 is the minimum value and 1.0 is the maximum value.
+ * This is a helper function used to calculate the bar width, knob location
+ * and label location.
+ * @return {number}
+ * @private
+ */
+ getRatio_: function() {
+ return (this.immediateValue_ - this.min) / (this.max - this.min);
+ },
- /** @private */
- onKeyDown_: function() {
- this.usedMouse_ = false;
- if (!this.disabled)
- this.onFocus_();
- },
+ /** @private */
+ ensureValidValue_: function() {
+ if (this.immediateValue_ == undefined || this.value == undefined)
+ return;
+ let validValue = clamp(this.min, this.max, this.immediateValue_);
+ validValue = this.snaps ? Math.round(validValue) : validValue;
+ this.immediateValue_ = validValue;
+ if (!this.dragging || this.updateValueInstantly)
+ this.value = validValue;
+ },
- /**
- * @param {!MouseEvent} event
- * @private
- */
- onPointerDown_: function(event) {
- if (this.disabled || event.button != 0) {
- event.preventDefault();
- return;
- }
- this.usedMouse_ = true;
- setTimeout(() => {
- this.$.slider.getRipple().holdDown = true;
- });
- },
+ /**
+ * Removes all event listeners related to dragging, and cancels ripple.
+ * @param {number} pointerId
+ * @private
+ */
+ stopDragging_: function(pointerId) {
+ this.dragging = false;
+ this.draggingEventTracker_.removeAll();
+ this.value = this.immediateValue_;
+ // If there is a ripple animation in progress, setTimeout will hold off
+ // on updating |holdDown_|.
+ setTimeout(() => {
+ this.holdDown_ = false;
+ });
+ this.releasePointerCapture(pointerId);
+ },
- /**
- * @param {!MouseEvent} event
- * @private
- */
- onPointerUp_: function(event) {
- if (event.button != 0)
- return;
- this.$.slider.getRipple().holdDown = false;
- },
+ /** @private */
+ onBlur_: function() {
+ this.holdDown_ = false;
+ },
- /**
- * The style is being set in this way to keep the knob size the same
- * regardless of the state or properties set in the paper-slider. paper-slider
- * styles alter the size in multiple places making it difficult to introduce
- * one or two mixins to override the existing paper-slider knob styling.
- * @private
- */
- onDisabledChanged_: function() {
- const knob = this.$.slider.$$('.slider-knob-inner');
- knob.style.boxSizing = 'content-box';
- knob.style.height = '10px';
- knob.style.transform = 'unset';
- knob.style.transition = 'unset';
- knob.style.width = '10px';
- this.$.slider.$$('.bar-container').style.left = '0';
- if (this.disabled) {
- knob.style.backgroundColor = 'var(--google-grey-600)';
- knob.style.border = '2px solid white';
- knob.style.boxShadow = 'unset';
- knob.style.margin = '9px';
- } else {
- knob.style.backgroundColor = 'var(--google-blue-600)';
- knob.style.border = '0';
- knob.style.boxShadow = '0 1px 3px 0 rgba(0, 0, 0, 0.4)';
- knob.style.margin = '11px';
- }
- },
-
- /** @private */
- onImmediateValueChanged_: function() {
- // TODO(dpapad): Need to catch and refire the property changed event in
- // Polymer 2 only, since it does not bubble by default. Remove the
- // condition when migration to Polymer 2 is completed.
- if (Polymer.DomIf)
- this.fire('immediate-value-changed', this.immediateValue);
- },
+ /** @private */
+ onFocus_: function() {
+ this.holdDown_ = true;
+ },
- /**
- * TODO(scottchen): temporary fix until polymer gesture bug resolved. See:
- * https://github.com/PolymerElements/paper-slider/issues/186
- * @private
- */
- resetTrackLock_: function() {
- Polymer.Gestures.gestures.tap.reset();
- },
-});
+ /** @private */
+ onHoldDownChanged_: function() {
+ this.getRipple().holdDown = this.holdDown_;
+ },
+
+ /**
+ * @param {!Event} event
+ * @private
+ */
+ onKeyDown_: function(event) {
+ if (this.disabled)
+ return;
+
+ if (event.metaKey || event.shiftKey || event.altKey || event.ctrlKey)
+ return;
+
+ let handled = true;
+ if (event.key == 'Home')
+ this.value = this.min;
+ else if (event.key == 'End')
+ this.value = this.max;
+ else if (this.deltaKeyMap_.has(event.key)) {
+ const newValue = this.value + this.deltaKeyMap_.get(event.key);
+ this.value = clamp(this.min, this.max, newValue);
+ } else
+ handled = false;
+
+ if (handled) {
+ event.preventDefault();
+ setTimeout(() => {
+ this.holdDown_ = true;
+ });
+ }
+ },
+
+ /**
+ * When the left-mouse button is pressed, the knob location is updated and
+ * dragging starts.
+ * @param {!PointerEvent} event
+ * @private
+ */
+ onPointerDown_: function(event) {
+ if (this.disabled || event.buttons != 1 && event.pointerType == 'mouse')
+ return;
+
+ this.dragging = true;
+ // If there is a ripple animation in progress, setTimeout will hold off on
+ // updating |holdDown_|.
+ setTimeout(() => {
+ this.$.knob.focus();
+ this.holdDown_ = true;
+ });
+ this.updateValueFromClientX_(event.clientX);
+
+ this.setPointerCapture(event.pointerId);
+ const stopDragging = this.stopDragging_.bind(this, event.pointerId);
+
+ this.draggingEventTracker_.add(this, 'pointermove', e => {
+ // If the left-button on the mouse is pressed by itself, then update.
+ // Otherwise stop capturing the mouse events because the drag operation
+ // is complete.
+ if (e.buttons != 1 && e.pointerType == 'mouse') {
+ stopDragging();
+ return;
+ }
+ this.updateValueFromClientX_(e.clientX);
+ });
+ this.draggingEventTracker_.add(this, 'pointercancel', stopDragging);
+ this.draggingEventTracker_.add(this, 'pointerdown', stopDragging);
+ this.draggingEventTracker_.add(this, 'pointerup', stopDragging);
+ this.draggingEventTracker_.add(this, 'keydown', e => {
+ if (e.key == 'Escape' || e.key == 'Tab')
+ stopDragging();
+ });
+ },
+
+ /** @private */
+ onTicksChanged_: function() {
+ if (this.ticks.length == 0) {
+ this.disabled = false;
+ this.snaps = false;
+ } else if (this.ticks.length == 1) {
+ this.disabled = true;
+ } else {
+ this.disabled = false;
+ this.snaps = true;
+ this.max = this.ticks.length - 1;
+ this.min = 0;
+ }
+ this.ensureValidValue_();
+ this.updateLabelAndAria_();
+ },
+
+ /**
+ * Update |immediateValue_| which is used for rendering when |value| is
+ * updated either programmatically or from a keyboard input or a mouse drag
+ * (when |updateValueInstantly| is true).
+ * @private
+ */
+ onValueChanged_: function() {
+ if (this.immediateValue_ == this.value)
+ return;
+
+ this.immediateValue_ = this.value;
+ this.ensureValidValue_();
+ },
+
+ /** @private */
+ updateKnobAndBar_: function() {
+ const percent = `${this.getRatio_() * 100}%`;
+ this.$.bar.style.width = percent;
+ this.$.knob.style.marginInlineStart = percent;
+ },
+
+ /** @private */
+ updateLabelAndAria_: function() {
+ const ticks = this.ticks;
+ const index = this.immediateValue_;
+ if (!ticks || ticks.length == 0 || index >= ticks.length ||
+ !Number.isInteger(index) || !this.snaps) {
+ this.setAttribute('aria-valuetext', index);
+ this.setAttribute('aria-valuemin', this.min);
+ this.setAttribute('aria-valuemax', this.max);
+ this.setAttribute('aria-valuenow', index);
+ return;
+ }
+ const tick = ticks[index];
+ this.label_ = Number.isFinite(tick) ? '' : tick.label;
+
+ // Update label location after it has been rendered.
+ this.async(() => {
+ const label = this.$.label;
+ const parentWidth = label.parentElement.offsetWidth;
+ const labelWidth = label.offsetWidth;
+ // The left and right margin are 16px.
+ const margin = 16;
+ const knobLocation = parentWidth * this.getRatio_() + margin;
+ const offsetStart = knobLocation - (labelWidth / 2);
+ // The label should be centered over the knob. Clamping the offset to a
+ // min and max value prevents the label from being cutoff.
+ const max = parentWidth + 2 * margin - labelWidth;
+ label.style.marginInlineStart =
+ `${Math.round(clamp(0, max, offsetStart))}px`;
+ });
+
+ const ariaValues = [tick, ticks[0], ticks[ticks.length - 1]].map(t => {
+ if (Number.isFinite(t))
+ return t;
+ return Number.isFinite(t.ariaValue) ? t.ariaValue : t.value;
+ });
+ this.setAttribute(
+ 'aria-valuetext',
+ this.label_.length > 0 ? this.label_ : ariaValues[0]);
+ this.setAttribute('aria-valuenow', ariaValues[0]);
+ this.setAttribute('aria-valuemin', ariaValues[1]);
+ this.setAttribute('aria-valuemax', ariaValues[2]);
+ },
+
+ /**
+ * @param {number} clientX
+ * @private
+ */
+ updateValueFromClientX_: function(clientX) {
+ const rect = this.$.barContainer.getBoundingClientRect();
+ let ratio = (clientX - rect.left) / rect.width;
+ if (this.isRtl_)
+ ratio = 1 - ratio;
+ this.immediateValue_ = ratio * (this.max - this.min) + this.min;
+ this.ensureValidValue_();
+ },
+
+ _createRipple: function() {
+ this._rippleContainer = this.$.knob;
+ const ripple = Polymer.PaperRippleBehavior._createRipple();
+ ripple.id = 'ink';
+ ripple.setAttribute('recenters', '');
+ ripple.classList.add('circle', 'toggle-ink');
+ return ripple;
+ },
+ });
+})();
diff --git a/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html b/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html
index 635bfe77f64..0f03fdf8a69 100644
--- a/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html
+++ b/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html
@@ -150,7 +150,6 @@
on-keydown="onSearchTermKeydown_"
on-focus="onInputFocus_"
on-blur="onInputBlur_"
- incremental
autofocus
spellcheck="false">
</div>
diff --git a/chromium/ui/webui/resources/cr_elements/cr_view_manager/BUILD.gn b/chromium/ui/webui/resources/cr_elements/cr_view_manager/BUILD.gn
new file mode 100644
index 00000000000..e1de4429a50
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_elements/cr_view_manager/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2018 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.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_type_check("closure_compile") {
+ deps = [
+ ":cr_view_manager",
+ ]
+}
+
+js_library("cr_view_manager") {
+ deps = [
+ "//ui/webui/resources/js:assert",
+ "//ui/webui/resources/js:cr",
+ ]
+ externs_list = [ "$externs_path/web_animations.js" ]
+}
diff --git a/chromium/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.html b/chromium/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.html
new file mode 100644
index 00000000000..45ff7d81938
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.html
@@ -0,0 +1,26 @@
+<link rel="import" href="../../html/polymer.html">
+
+<link rel="import" href="../../html/assert.html">
+<link rel="import" href="../../html/cr.html">
+
+<dom-module id="cr-view-manager">
+ <template>
+ <style>
+ :host ::slotted([slot=view]) {
+ bottom: 0;
+ display: none;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
+
+ :host ::slotted(.active),
+ :host ::slotted(.closing) {
+ display: block;
+ }
+ </style>
+ <slot name="view"></slot>
+ </template>
+ <script src="cr_view_manager.js"></script>
+</dom-module>
diff --git a/chromium/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.js b/chromium/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.js
new file mode 100644
index 00000000000..44f67b9877d
--- /dev/null
+++ b/chromium/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.js
@@ -0,0 +1,115 @@
+// Copyright 2018 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.
+
+(function() {
+/**
+ * TODO(scottchen): shim for not having Animation.finished implemented. Can
+ * replace with Animation.finished if Chrome implements it (see:
+ * crbug.com/257235).
+ * @param {!Animation} animation
+ * @return {!Promise}
+ */
+function whenFinished(animation) {
+ return new Promise(function(resolve, reject) {
+ animation.addEventListener('finish', resolve);
+ });
+}
+
+/** @type {!Map<string, function(!Element): !Promise>} */
+const viewAnimations = new Map();
+viewAnimations.set('no-animation', () => Promise.resolve());
+viewAnimations.set('fade-in', element => {
+ const animation = element.animate(
+ {
+ opacity: [0, 1],
+ },
+ /** @type {!KeyframeEffectOptions} */ ({
+ duration: 180,
+ easing: 'ease-in-out',
+ iterations: 1,
+ }));
+
+ return whenFinished(animation);
+});
+viewAnimations.set('fade-out', element => {
+ const animation = element.animate(
+ {
+ opacity: [1, 0],
+ },
+ /** @type {!KeyframeEffectOptions} */ ({
+ duration: 180,
+ easing: 'ease-in-out',
+ iterations: 1,
+ }));
+
+ return whenFinished(animation);
+});
+
+Polymer({
+ is: 'cr-view-manager',
+
+ /**
+ * @param {!Element} element
+ * @param {string} animation
+ * @return {!Promise}
+ * @private
+ */
+ exit_: function(element, animation) {
+ const animationFunction = viewAnimations.get(animation);
+ assert(animationFunction);
+
+ element.classList.remove('active');
+ element.classList.add('closing');
+ element.dispatchEvent(
+ new CustomEvent('view-exit-start', {bubbles: true, composed: true}));
+ return animationFunction(element).then(function() {
+ element.classList.remove('closing');
+ element.dispatchEvent(
+ new CustomEvent('view-exit-finish', {bubbles: true, composed: true}));
+ });
+ },
+
+ /**
+ * @param {!Element} view
+ * @param {string} animation
+ * @return {!Promise}
+ * @private
+ */
+ enter_: function(view, animation) {
+ const animationFunction = viewAnimations.get(animation);
+ assert(animationFunction);
+
+ let effectiveView = view.matches('cr-lazy-render') ? view.get() : view;
+
+ effectiveView.classList.add('active');
+ effectiveView.dispatchEvent(
+ new CustomEvent('view-enter-start', {bubbles: true, composed: true}));
+ return animationFunction(effectiveView).then(() => {
+ effectiveView.dispatchEvent(new CustomEvent(
+ 'view-enter-finish', {bubbles: true, composed: true}));
+ });
+ },
+
+ /**
+ * @param {string} newViewId
+ * @param {string=} enterAnimation
+ * @param {string=} exitAnimation
+ * @return {!Promise}
+ */
+ switchView: function(newViewId, enterAnimation, exitAnimation) {
+ const previousView = this.querySelector('.active');
+ const newView = assert(this.querySelector('#' + newViewId));
+
+ const promises = [];
+ if (previousView) {
+ promises.push(this.exit_(previousView, exitAnimation || 'fade-out'));
+ promises.push(this.enter_(newView, enterAnimation || 'fade-in'));
+ } else {
+ promises.push(this.enter_(newView, 'no-animation'));
+ }
+
+ return Promise.all(promises);
+ },
+});
+})(); \ No newline at end of file
diff --git a/chromium/ui/webui/resources/cr_elements/policy/BUILD.gn b/chromium/ui/webui/resources/cr_elements/policy/BUILD.gn
index 244f3a615eb..743f98969f3 100644
--- a/chromium/ui/webui/resources/cr_elements/policy/BUILD.gn
+++ b/chromium/ui/webui/resources/cr_elements/policy/BUILD.gn
@@ -54,6 +54,7 @@ js_library("cr_policy_network_indicator") {
deps = [
":cr_policy_indicator_behavior",
":cr_policy_network_behavior",
+ ":cr_tooltip_icon",
"../chromeos/network:cr_onc_types",
]
}
diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior.js b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior.js
index eb89b2b66db..c20b1ca4ec6 100644
--- a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior.js
+++ b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior.js
@@ -53,22 +53,42 @@ var CrPolicyNetworkBehavior = {
/**
* @param {!CrOnc.ManagedProperty|undefined} property
- * @return {boolean} True if the network property is enforced by a policy.
+ * @return {boolean} True if the network property is editable.
*/
- isNetworkPolicyEnforced: function(property) {
- if (!this.isNetworkPolicyControlled(property))
+ isEditable: function(property) {
+ // If the property is not a dictionary, then the property is not editable.
+ if (typeof property != 'object')
return false;
+
// If the property has a UserEditable sub-property, that determines whether
- // or not it is editable (not enforced).
+ // or not it is editable.
if (typeof property.UserEditable != 'undefined')
- return !property.UserEditable;
+ return property.UserEditable;
// Otherwise if the property has a DeviceEditable sub-property, check that.
if (typeof property.DeviceEditable != 'undefined')
- return !property.DeviceEditable;
+ return property.DeviceEditable;
+
+ // If no 'Editable' sub-property exists, the policy value is not editable.
+ return false;
+ },
+
+ /**
+ * @param {!CrOnc.ManagedProperty|undefined} property
+ * @return {boolean} True if the network property is enforced by a policy.
+ */
+ isNetworkPolicyEnforced: function(property) {
+ return this.isNetworkPolicyControlled(property) &&
+ !this.isEditable(property);
+ },
- // If no 'Editable' sub-property exists, the policy value is enforced.
- return true;
+ /**
+ * @param {!CrOnc.ManagedProperty|undefined} property
+ * @return {boolean} True if the network property is recommended by a policy.
+ */
+ isNetworkPolicyRecommended: function(property) {
+ return this.isNetworkPolicyControlled(property) &&
+ this.isEditable(property);
},
/**
diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js
index 447aa7fb810..b83f94971b0 100644
--- a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js
+++ b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js
@@ -78,4 +78,9 @@ Polymer({
return this.getIndicatorTooltip(
indicatorType, this.pref.controlledByName || '', matches);
},
+
+ /** @return {!Element} */
+ getFocusableElement: function() {
+ return this.$$('cr-tooltip-icon').getFocusableElement();
+ },
});
diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.js b/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.js
index ba918fb0ca7..cd8cd9cc2ab 100644
--- a/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.js
+++ b/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.js
@@ -9,4 +9,9 @@ Polymer({
iconClass: String,
tooltipText: String,
},
+
+ /** @return {!Element} */
+ getFocusableElement: function() {
+ return this.$.indicator;
+ },
}); \ No newline at end of file
diff --git a/chromium/ui/webui/resources/cr_elements/shared_vars_css.html b/chromium/ui/webui/resources/cr_elements/shared_vars_css.html
index b98339dcb19..f774f420268 100644
--- a/chromium/ui/webui/resources/cr_elements/shared_vars_css.html
+++ b/chromium/ui/webui/resources/cr_elements/shared_vars_css.html
@@ -142,13 +142,15 @@
--cr-disabled-opacity: 0.38;
--cr-form-field-bottom-spacing: 16px;
--cr-form-field-label-font-size: 0.625rem;
+ --cr-form-field-label-height: 0.625rem;
+ --cr-form-field-label-line-height: 0.625rem;
--cr-form-field-label: {
color: var(--google-grey-refresh-700);
display: block;
font-size: var(--cr-form-field-label-font-size);
font-weight: 500;
letter-spacing: 0.4px;
- line-height: var(--cr-form-field-label-font-size);
+ line-height: var(--cr-form-field-label-line-height);
margin-bottom: 8px;
}
--google-blue-50: #E8F0FE;
diff --git a/chromium/ui/webui/resources/cr_elements_resources.grdp b/chromium/ui/webui/resources/cr_elements_resources.grdp
index 26c467315df..040b089fbef 100644
--- a/chromium/ui/webui/resources/cr_elements_resources.grdp
+++ b/chromium/ui/webui/resources/cr_elements_resources.grdp
@@ -126,6 +126,12 @@
file="cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js"
type="chrome_html"
compress="gzip" />
+ <structure name="IDR_CR_ELEMENTS_CR_VIEW_MANAGER_HTML"
+ file="cr_elements/cr_view_manager/cr_view_manager.html"
+ type="chrome_html" />
+ <structure name="IDR_CR_ELEMENTS_CR_VIEW_MANAGER_JS"
+ file="cr_elements/cr_view_manager/cr_view_manager.js"
+ type="chrome_html" />
<if expr="chromeos">
<structure name="IDR_CR_ELEMENTS_CHROMEOS_CR_PICTURE_CR_CAMERA_HTML"
file="cr_elements/chromeos/cr_picture/cr_camera.html"
diff --git a/chromium/ui/webui/resources/js/assert.js b/chromium/ui/webui/resources/js/assert.js
index 42fb523fb07..e46a056778e 100644
--- a/chromium/ui/webui/resources/js/assert.js
+++ b/chromium/ui/webui/resources/js/assert.js
@@ -22,6 +22,8 @@ function assert(condition, opt_message) {
message = message + ': ' + opt_message;
var error = new Error(message);
var global = function() {
+ /** @type {boolean} */
+ this.traceAssertionsForTesting;
return this;
}();
if (global.traceAssertionsForTesting)
diff --git a/chromium/ui/webui/resources/js/cr/ui/dialogs.js b/chromium/ui/webui/resources/js/cr/ui/dialogs.js
index 40e86f6c368..a2aff076194 100644
--- a/chromium/ui/webui/resources/js/cr/ui/dialogs.js
+++ b/chromium/ui/webui/resources/js/cr/ui/dialogs.js
@@ -19,6 +19,9 @@ cr.define('cr.ui.dialogs', function() {
this.previousActiveElement_ = null;
this.initDom_();
+
+ /** @private{boolean} */
+ this.showing_ = false;
}
/**
@@ -214,6 +217,7 @@ cr.define('cr.ui.dialogs', function() {
*/
BaseDialog.prototype.show_ = function(
title, opt_onOk, opt_onCancel, opt_onShow) {
+ this.showing_ = true;
// Make all outside nodes unfocusable while the dialog is active.
this.deactivatedNodes_ = this.findFocusableElements_(this.document_);
this.tabIndexes_ = this.deactivatedNodes_.map(function(n) {
@@ -239,10 +243,11 @@ cr.define('cr.ui.dialogs', function() {
var self = this;
setTimeout(function() {
- // Note that we control the opacity of the *container*, but the top/left
- // of the *frame*.
- self.container_.classList.add('shown');
- self.initialFocusElement_.focus();
+ // Check that hide() was not called in between.
+ if (self.showing_) {
+ self.container_.classList.add('shown');
+ self.initialFocusElement_.focus();
+ }
setTimeout(function() {
if (opt_onShow)
opt_onShow();
@@ -252,6 +257,7 @@ cr.define('cr.ui.dialogs', function() {
/** @param {Function=} opt_onHide */
BaseDialog.prototype.hide = function(opt_onHide) {
+ this.showing_ = false;
// Restore focusability.
for (var i = 0; i < this.deactivatedNodes_.length; i++) {
var node = this.deactivatedNodes_[i];
@@ -263,8 +269,6 @@ cr.define('cr.ui.dialogs', function() {
this.deactivatedNodes_ = null;
this.tabIndexes_ = null;
- // Note that we control the opacity of the *container*, but the top/left
- // of the *frame*.
this.container_.classList.remove('shown');
if (this.previousActiveElement_) {
@@ -277,9 +281,10 @@ cr.define('cr.ui.dialogs', function() {
var self = this;
setTimeout(function() {
// Wait until the transition is done before removing the dialog.
- // It is possible to show/hide/show/hide and have hide called twice
+ // Check show() was not called in between.
+ // It is also possible to show/hide/show/hide and have hide called twice
// and container_ already removed from parentNode_.
- if (self.parentNode_ === self.container_.parentNode)
+ if (!self.showing_ && self.parentNode_ === self.container_.parentNode)
self.parentNode_.removeChild(self.container_);
if (opt_onHide)
opt_onHide();
diff --git a/chromium/ui/webui/resources/js/cr/ui/focus_grid.js b/chromium/ui/webui/resources/js/cr/ui/focus_grid.js
index 000fb051966..de710112a36 100644
--- a/chromium/ui/webui/resources/js/cr/ui/focus_grid.js
+++ b/chromium/ui/webui/resources/js/cr/ui/focus_grid.js
@@ -25,7 +25,7 @@ cr.define('cr.ui', function() {
* focusable focusable focusable
*
* @constructor
- * @implements {cr.ui.FocusRow.Delegate}
+ * @implements {cr.ui.FocusRowDelegate}
*/
function FocusGrid() {
/** @type {!Array<!cr.ui.FocusRow>} */
diff --git a/chromium/ui/webui/resources/js/cr/ui/focus_row.js b/chromium/ui/webui/resources/js/cr/ui/focus_row.js
index 91399fb5250..8915b114a4a 100644
--- a/chromium/ui/webui/resources/js/cr/ui/focus_row.js
+++ b/chromium/ui/webui/resources/js/cr/ui/focus_row.js
@@ -14,84 +14,76 @@ cr.define('cr.ui', function() {
* If no items in this row are focused, the row can stay active until focus
* changes to a node inside |this.boundary_|. If |boundary| isn't specified,
* any focus change deactivates the row.
- *
- * @param {!Element} root The root of this focus row. Focus classes are
- * applied to |root| and all added elements must live within |root|.
- * @param {?Element} boundary Focus events are ignored outside of this
- * element.
- * @param {cr.ui.FocusRow.Delegate=} opt_delegate An optional event delegate.
- * @constructor
*/
- function FocusRow(root, boundary, opt_delegate) {
- /** @type {!Element} */
- this.root = root;
-
- /** @private {!Element} */
- this.boundary_ = boundary || document.documentElement;
-
- /** @type {cr.ui.FocusRow.Delegate|undefined} */
- this.delegate = opt_delegate;
-
- /** @protected {!EventTracker} */
- this.eventTracker = new EventTracker;
- }
-
- /** @interface */
- FocusRow.Delegate = function() {};
-
- FocusRow.Delegate.prototype = {
+ class FocusRow {
/**
- * Called when a key is pressed while on a FocusRow's item. If true is
- * returned, further processing is skipped.
- * @param {!cr.ui.FocusRow} row The row that detected a keydown.
- * @param {!Event} e
- * @return {boolean} Whether the event was handled.
+ * @param {!Element} root The root of this focus row. Focus classes are
+ * applied to |root| and all added elements must live within |root|.
+ * @param {?Element} boundary Focus events are ignored outside of this
+ * element.
+ * @param {cr.ui.FocusRowDelegate=} delegate An optional event
+ * delegate.
*/
- onKeydown: assertNotReached,
+ constructor(root, boundary, delegate) {
+ /** @type {!Element} */
+ this.root = root;
- /**
- * @param {!cr.ui.FocusRow} row
- * @param {!Event} e
- */
- onFocus: assertNotReached,
- };
+ /** @private {!Element} */
+ this.boundary_ = boundary || document.documentElement;
- /** @const {string} */
- FocusRow.ACTIVE_CLASS = 'focus-row-active';
+ /** @type {cr.ui.FocusRowDelegate|undefined} */
+ this.delegate = delegate;
- /**
- * Whether it's possible that |element| can be focused.
- * @param {Element} element
- * @return {boolean} Whether the item is focusable.
- */
- FocusRow.isFocusable = function(element) {
- if (!element || element.disabled)
- return false;
+ /** @protected {!EventTracker} */
+ this.eventTracker = new EventTracker;
+ }
- // We don't check that element.tabIndex >= 0 here because inactive rows set
- // a tabIndex of -1.
+ /**
+ * Whether it's possible that |element| can be focused.
+ * @param {Element} element
+ * @return {boolean} Whether the item is focusable.
+ */
+ static isFocusable(element) {
+ if (!element || element.disabled)
+ return false;
- function isVisible(element) {
- assertInstanceof(element, Element);
+ // We don't check that element.tabIndex >= 0 here because inactive rows
+ // set a tabIndex of -1.
+ let current = element;
+ while (true) {
+ assertInstanceof(current, Element);
- var style = window.getComputedStyle(element);
- if (style.visibility == 'hidden' || style.display == 'none')
- return false;
+ var style = window.getComputedStyle(current);
+ if (style.visibility == 'hidden' || style.display == 'none')
+ return false;
- var parent = element.parentNode;
- if (!parent)
- return false;
+ var parent = current.parentNode;
+ if (!parent)
+ return false;
- if (parent == element.ownerDocument || parent instanceof DocumentFragment)
- return true;
+ if (parent == current.ownerDocument ||
+ parent instanceof DocumentFragment)
+ return true;
- return isVisible(parent);
+ current = /** @type {Element} */ (parent);
+ }
}
- return isVisible(element);
- };
+ /**
+ * A focus override is a function that returns an element that should gain
+ * focus. The element may not be directly selectable for example the element
+ * that can gain focus is in a shadow DOM. Allowing an override via a
+ * function leaves the details of how the element is retrieved to the
+ * component.
+ * @param {!Element} element
+ * @return {!Element}
+ */
+ static getFocusableElement(element) {
+ if (element.getFocusableElement)
+ return element.getFocusableElement();
+ return element;
+ }
- FocusRow.prototype = {
/**
* Register a new type of focusable element (or add to an existing one).
*
@@ -100,7 +92,7 @@ cr.define('cr.ui', function() {
* When FocusRow is used within a FocusGrid, these types are used to
* determine equivalent controls when Up/Down are pressed to change rows.
*
- * Another example: mutually exclusive controls that hide eachother on
+ * Another example: mutually exclusive controls that hide each other on
* activation (i.e. Play/Pause) could use the same type (i.e. 'play-pause')
* to indicate they're equivalent.
*
@@ -109,7 +101,7 @@ cr.define('cr.ui', function() {
* from this row's root, or the element itself.
* @return {boolean} Whether a new item was added.
*/
- addItem: function(type, selectorOrElement) {
+ addItem(type, selectorOrElement) {
assert(type);
var element;
@@ -128,30 +120,30 @@ cr.define('cr.ui', function() {
this.eventTracker.add(element, 'keydown', this.onKeydown_.bind(this));
this.eventTracker.add(element, 'mousedown', this.onMousedown_.bind(this));
return true;
- },
+ }
/** Dereferences nodes and removes event handlers. */
- destroy: function() {
+ destroy() {
this.eventTracker.removeAll();
- },
+ }
/**
* @param {!Element} sampleElement An element for to find an equivalent for.
* @return {!Element} An equivalent element to focus for |sampleElement|.
* @protected
*/
- getCustomEquivalent: function(sampleElement) {
+ getCustomEquivalent(sampleElement) {
return assert(this.getFirstFocusable());
- },
+ }
/**
* @return {!Array<!Element>} All registered elements (regardless of
* focusability).
*/
- getElements: function() {
- var elements = this.root.querySelectorAll('[focus-type]');
- return Array.prototype.slice.call(elements);
- },
+ getElements() {
+ return Array.from(this.root.querySelectorAll('[focus-type]'))
+ .map(cr.ui.FocusRow.getFocusableElement);
+ }
/**
* Find the element that best matches |sampleElement|.
@@ -159,7 +151,7 @@ cr.define('cr.ui', function() {
* which previously held focus.
* @return {!Element} The element that best matches sampleElement.
*/
- getEquivalentElement: function(sampleElement) {
+ getEquivalentElement(sampleElement) {
if (this.getFocusableElements().indexOf(sampleElement) >= 0)
return sampleElement;
@@ -171,46 +163,42 @@ cr.define('cr.ui', function() {
}
return this.getCustomEquivalent(sampleElement);
- },
+ }
/**
* @param {string=} opt_type An optional type to search for.
* @return {?Element} The first focusable element with |type|.
*/
- getFirstFocusable: function(opt_type) {
- var filter = opt_type ? '="' + opt_type + '"' : '';
- var elements = this.root.querySelectorAll('[focus-type' + filter + ']');
- for (var i = 0; i < elements.length; ++i) {
- if (cr.ui.FocusRow.isFocusable(elements[i]))
- return elements[i];
- }
- return null;
- },
+ getFirstFocusable(opt_type) {
+ const element = this.getFocusableElements().find(
+ el => !opt_type || el.getAttribute('focus-type') == opt_type);
+ return element || null;
+ }
/** @return {!Array<!Element>} Registered, focusable elements. */
- getFocusableElements: function() {
+ getFocusableElements() {
return this.getElements().filter(cr.ui.FocusRow.isFocusable);
- },
+ }
/**
* @param {!Element} element An element to determine a focus type for.
* @return {string} The focus type for |element| or '' if none.
*/
- getTypeForElement: function(element) {
+ getTypeForElement(element) {
return element.getAttribute('focus-type') || '';
- },
+ }
/** @return {boolean} Whether this row is currently active. */
- isActive: function() {
+ isActive() {
return this.root.classList.contains(FocusRow.ACTIVE_CLASS);
- },
+ }
/**
* Enables/disables the tabIndex of the focusable elements in the FocusRow.
* tabIndex can be set properly.
* @param {boolean} active True if tab is allowed for this row.
*/
- makeActive: function(active) {
+ makeActive(active) {
if (active == this.isActive())
return;
@@ -219,35 +207,35 @@ cr.define('cr.ui', function() {
});
this.root.classList.toggle(FocusRow.ACTIVE_CLASS, active);
- },
+ }
/**
* @param {!Event} e
* @private
*/
- onBlur_: function(e) {
+ onBlur_(e) {
if (!this.boundary_.contains(/** @type {Element} */ (e.relatedTarget)))
return;
var currentTarget = /** @type {!Element} */ (e.currentTarget);
if (this.getFocusableElements().indexOf(currentTarget) >= 0)
this.makeActive(false);
- },
+ }
/**
* @param {!Event} e
* @private
*/
- onFocus_: function(e) {
+ onFocus_(e) {
if (this.delegate)
this.delegate.onFocus(this, e);
- },
+ }
/**
* @param {!Event} e A mousedown event.
* @private
*/
- onMousedown_: function(e) {
+ onMousedown_(e) {
// Only accept left mouse clicks.
if (e.button)
return;
@@ -255,13 +243,13 @@ cr.define('cr.ui', function() {
// Allow the element under the mouse cursor to be focusable.
if (!e.currentTarget.disabled)
e.currentTarget.tabIndex = 0;
- },
+ }
/**
* @param {!Event} e The keydown event.
* @private
*/
- onKeydown_: function(e) {
+ onKeydown_(e) {
var elements = this.getFocusableElements();
var currentElement = /** @type {!Element} */ (e.currentTarget);
var elementIndex = elements.indexOf(currentElement);
@@ -289,10 +277,33 @@ cr.define('cr.ui', function() {
this.getEquivalentElement(elementToFocus).focus();
e.preventDefault();
}
- },
- };
+ }
+ }
+
+ /** @const {string} */
+ FocusRow.ACTIVE_CLASS = 'focus-row-active';
+
+
+ /** @interface */
+ class FocusRowDelegate {
+ /**
+ * Called when a key is pressed while on a FocusRow's item. If true is
+ * returned, further processing is skipped.
+ * @param {!cr.ui.FocusRow} row The row that detected a keydown.
+ * @param {!Event} e
+ * @return {boolean} Whether the event was handled.
+ */
+ onKeydown(row, e) {}
+
+ /**
+ * @param {!cr.ui.FocusRow} row
+ * @param {!Event} e
+ */
+ onFocus(row, e) {}
+ }
return {
- FocusRow: FocusRow,
+ FocusRow,
+ FocusRowDelegate,
};
});
diff --git a/chromium/ui/webui/resources/js/util.js b/chromium/ui/webui/resources/js/util.js
index a5099bf9b56..7139d9baede 100644
--- a/chromium/ui/webui/resources/js/util.js
+++ b/chromium/ui/webui/resources/js/util.js
@@ -33,6 +33,18 @@ function getSVGElement(id) {
}
/**
+ * @return {?Element} The currently focused element (including elements that are
+ * behind a shadow root), or null if nothing is focused.
+ */
+function getDeepActiveElement() {
+ var a = document.activeElement;
+ while (a && a.shadowRoot && a.shadowRoot.activeElement) {
+ a = a.shadowRoot.activeElement;
+ }
+ return a;
+}
+
+/**
* Add an accessible message to the page that will be announced to
* users who have spoken feedback on, but will be invisible to all
* other users. It's removed right away so it doesn't clutter the DOM.
diff --git a/chromium/ui/webui/resources/js/webui_resource_test.js b/chromium/ui/webui/resources/js/webui_resource_test.js
index ed8d79e401c..ea7089b0bac 100644
--- a/chromium/ui/webui/resources/js/webui_resource_test.js
+++ b/chromium/ui/webui/resources/js/webui_resource_test.js
@@ -116,9 +116,28 @@ function assertDeepEquals(expected, observed, opt_message) {
}
/**
- * Defines runTests.
+ * Decorates |window| with runTests() and endTests().
+ *
+ * @param {{
+ * runTests: (function(Object=):void|undefined),
+ * endTests: (function(boolean):void|undefined)
+ * }} exports
*/
(function(exports) {
+
+/**
+ * Optional setup and teardown hooks that can be defined in a test scope.
+ * |setUpPage| is invoked once. |setUp|/|tearDown| are invoked before/after each
+ * test*() declared in the test scope.
+ *
+ * @typedef {{
+ * setUpPage: (function(): void|undefined),
+ * setUp: (function(): void|undefined),
+ * tearDown: (function(): void|undefined),
+ * }}
+ */
+var WebUiTestHarness;
+
/**
* Scope containing testXXX functions.
* @type {!Object}
@@ -126,6 +145,12 @@ function assertDeepEquals(expected, observed, opt_message) {
var testScope = {};
/**
+ * Test harness entrypoints on |testScope|.
+ * @type {!WebUiTestHarness}
+ */
+var testHarness = {};
+
+/**
* List of test cases.
* @type {Array<string>} List of function names for tests to run.
*/
@@ -170,6 +195,7 @@ var runnerStartTime = 0;
function runTests(opt_testScope) {
runnerStartTime = performance.now();
testScope = opt_testScope || window;
+ testHarness = /** @type{!WebUiTestHarness} */ (testScope);
for (var name in testScope) {
// To avoid unnecessary getting properties, test name first.
if (/^test/.test(name) && typeof testScope[name] == 'function')
@@ -180,11 +206,22 @@ function runTests(opt_testScope) {
cleanTestRun = false;
}
try {
- if (testScope.setUpPage)
- testScope.setUpPage();
+ if (testHarness.setUpPage)
+ testHarness.setUpPage();
} catch (err) {
cleanTestRun = false;
}
+ startTesting();
+}
+
+/**
+ * @suppress {missingProperties}
+ */
+function startTesting() {
+ if (window.waitUser) {
+ setTimeout(startTesting, 1000);
+ return;
+ }
continueTesting();
}
@@ -216,9 +253,9 @@ function continueTesting(opt_asyncTestFailure) {
var isAsyncTest = testScope[testName].length;
var testError = false;
try {
- if (testScope.setUp)
- testScope.setUp();
- pendingTearDown = testScope.tearDown || null;
+ if (testHarness.setUp)
+ testHarness.setUp();
+ pendingTearDown = testHarness.tearDown || null;
testScope[testName](continueTesting);
} catch (err) {
console.error('Failure in test ' + testName + '\n' + err);
@@ -257,6 +294,16 @@ exports.runTests = runTests;
exports.endTests = endTests;
})(window);
+/**
+ * @type {!function(Object=):void}
+ */
+window.runTests;
+
+/**
+ * @type {!function(boolean):void}
+ */
+window.endTests;
+
window.onerror = function() {
window.endTests(false);
};
diff --git a/chromium/ui/webui/resources/webui_resources.grd b/chromium/ui/webui/resources/webui_resources.grd
index a24f4983bf4..a2b2e2d14bc 100644
--- a/chromium/ui/webui/resources/webui_resources.grd
+++ b/chromium/ui/webui/resources/webui_resources.grd
@@ -200,6 +200,7 @@ without changes to the corresponding grd file. -->
file="images/trash.png" type="BINDATA" />
<if expr="not is_android">
+ <part file="cr_components/cr_components_images.grdp" />
<part file="cr_elements_images.grdp" />
</if>
</includes>