summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/tab_strip
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/tab_strip')
-rw-r--r--chromium/chrome/browser/resources/tab_strip/BUILD.gn22
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicator.html77
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicator.js19
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/picture_in_picture_alt.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/serial_port.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_audio_muting_rounded.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_audio_rounded.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_bluetooth_connected.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_media_capturing_with_arrow.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_media_recording.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_usb_connected.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/alert_indicators/vr_headset.svg1
-rw-r--r--chromium/chrome/browser/resources/tab_strip/tab.html248
-rw-r--r--chromium/chrome/browser/resources/tab_strip/tab.js80
-rw-r--r--chromium/chrome/browser/resources/tab_strip/tab_list.html38
-rw-r--r--chromium/chrome/browser/resources/tab_strip/tab_list.js307
-rw-r--r--chromium/chrome/browser/resources/tab_strip/tab_strip.html25
-rw-r--r--chromium/chrome/browser/resources/tab_strip/tab_strip_embedder_proxy.js39
-rw-r--r--chromium/chrome/browser/resources/tab_strip/tab_strip_resources.grd60
-rw-r--r--chromium/chrome/browser/resources/tab_strip/tabs_api_proxy.js72
-rw-r--r--chromium/chrome/browser/resources/tab_strip/types.js32
21 files changed, 779 insertions, 249 deletions
diff --git a/chromium/chrome/browser/resources/tab_strip/BUILD.gn b/chromium/chrome/browser/resources/tab_strip/BUILD.gn
index ece86c1ad36..f6b804866d5 100644
--- a/chromium/chrome/browser/resources/tab_strip/BUILD.gn
+++ b/chromium/chrome/browser/resources/tab_strip/BUILD.gn
@@ -7,13 +7,18 @@ import("//tools/polymer/polymer.gni")
js_type_check("closure_compile") {
deps = [
+ ":alert_indicator",
":custom_element",
":tab",
":tab_list",
+ ":tab_strip_embedder_proxy",
":tabs_api_proxy",
]
}
+js_library("alert_indicator") {
+}
+
js_library("custom_element") {
}
@@ -28,26 +33,31 @@ js_library("tab") {
deps = [
"//ui/webui/resources/js:icon.m",
]
- externs_list = [ "$externs_path/chrome.js" ]
}
js_library("tab_list") {
- deps = [
- ":types",
- ]
- externs_list = [ "$externs_path/chrome.js" ]
}
-js_library("types") {
+js_library("tab_strip_embedder_proxy") {
+ deps = [
+ "//ui/webui/resources/js:cr.m",
+ ]
}
group("tab_strip_modules") {
deps = [
+ ":alert_indicator_module",
":tab_list_module",
":tab_module",
]
}
+polymer_modulizer("alert_indicator") {
+ js_file = "alert_indicator.js"
+ html_file = "alert_indicator.html"
+ html_type = "v3-ready"
+}
+
polymer_modulizer("tab") {
js_file = "tab.js"
html_file = "tab.html"
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicator.html b/chromium/chrome/browser/resources/tab_strip/alert_indicator.html
new file mode 100644
index 00000000000..5a4e4c7481c
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicator.html
@@ -0,0 +1,77 @@
+<style>
+ @keyframes fade-in {
+ 0% { opacity: 0; }
+ 100% { opacity: 1; }
+ }
+
+ @keyframes fade-out {
+ 0% { opacity: 1; }
+ 100% { opacity: 0; }
+ }
+
+ :host {
+ -webkit-mask: center / contain no-repeat;
+ animation: fade-in 200ms ease-in forwards;
+ background-color: currentColor;
+ display: block;
+ height: 100%;
+ opacity: 0;
+ width: 16px;
+ }
+
+ :host([fade-out]) {
+ animation: fade-out 1000ms ease-in;
+ }
+
+ :host([alert-state='pip']) {
+ -webkit-mask-image: url(alert_indicators/picture_in_picture_alt.svg);
+ background-color: var(--tabstrip-indicator-pip-color);
+ }
+
+ :host([alert-state='serial']) {
+ -webkit-mask-image: url(alert_indicators/serial_port.svg);
+ }
+
+ :host([alert-state='muted']) {
+ -webkit-mask-image: url(alert_indicators/tab_audio_muting_rounded.svg);
+ }
+
+ :host([alert-state='audio']) {
+ -webkit-mask-image: url(alert_indicators/tab_audio_rounded.svg);
+ }
+
+ :host([alert-state='bluetooth']) {
+ -webkit-mask-image: url(alert_indicators/tab_bluetooth_connected.svg);
+ }
+
+ :host([alert-state='capturing']) {
+ -webkit-mask-image:
+ url(alert_indicators/tab_media_capturing_with_arrow.svg);
+ background-color: var(--tabstrip-indicator-capturing-color);
+ }
+
+ :host([alert-state='recording']) {
+ -webkit-mask-image: url(alert_indicators/tab_media_recording.svg);
+ background-color: var(--tabstrip-indicator-recording-color);
+ }
+
+ :host([alert-state='usb']) {
+ -webkit-mask-image: url(alert_indicators/tab_usb_connected.svg);
+ -webkit-mask-size: 20px;
+ }
+
+ :host([alert-state='vr']) {
+ -webkit-mask-image: url(alert_indicators/vr_headset.svg);
+ }
+
+ :host([alert-state='capturing']),
+ :host([alert-state='recording']) {
+ animation:
+ fade-in 200ms ease-in 0,
+ fade-out 1000ms ease-in 200ms,
+ fade-in 200ms ease-in 1200s,
+ fade-out 1000ms ease-in 1400ms,
+ fade-in 200ms ease-in 2400ms;
+ animation-fill-mode: forwards;
+ }
+</style>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicator.js b/chromium/chrome/browser/resources/tab_strip/alert_indicator.js
new file mode 100644
index 00000000000..2d41eab42f6
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicator.js
@@ -0,0 +1,19 @@
+// Copyright 2019 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 {CustomElement} from './custom_element.js';
+
+export class AlertIndicatorElement extends CustomElement {
+ static get template() {
+ return `{__html_template__}`;
+ }
+
+ /** @override */
+ remove() {
+ this.toggleAttribute('fade-out', true);
+ this.addEventListener('animationend', () => super.remove(), {once: true});
+ }
+}
+
+customElements.define('tabstrip-alert-indicator', AlertIndicatorElement);
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/picture_in_picture_alt.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/picture_in_picture_alt.svg
new file mode 100644
index 00000000000..7ef00c31419
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/picture_in_picture_alt.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M 16 14 H 0 V 2 h 16 v 12 Z M 1 13 h 14 V 3 H 1 v 10 Z M 8 8 h 6 v 4 H 8 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/serial_port.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/serial_port.svg
new file mode 100644
index 00000000000..1bf89c75d27
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/serial_port.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M 22 9 V 7 h -2 V 5 c 0 -1.1 -0.9 -2 -2 -2 H 4 c -1.1 0 -2 0.9 -2 2 v 14 c 0 1.1 0.9 2 2 2 h 14 c 1.1 0 2 -0.9 2 -2 v -2 h 2 v -2 h -2 v -2 h 2 v -2 h -2 V 9 h 2 Z m -4 10 H 4 V 5 h 14 v 14 Z M 6 13 h 5 v 4 H 6 Z m 6 -6 h 4 v 3 h -4 Z M 6 7 h 5 v 5 H 6 Z m 6 4 h 4 v 6 h -4 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_audio_muting_rounded.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_audio_muting_rounded.svg
new file mode 100644
index 00000000000..3da0244cbe4
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_audio_muting_rounded.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path d="M 8.5 6 C 8.5 5.02 7.93 4.17 7.11 3.76 L 7.11 4.99 L 8.47 6.35 C 8.49 6.24 8.5 6.12 8.5 6 Z M 9.89 6 C 9.89 6.52 9.78 7.01 9.59 7.47 L 10.43 8.31 C 10.79 7.62 11 6.83 11 6 C 11 3.62 9.34 1.63 7.11 1.13 L 7.11 2.27 C 8.72 2.75 9.89 4.24 9.89 6 Z M 1.71 1 L 1 1.71 L 3.63 4.33 L 1 4.33 L 1 7.67 L 3.22 7.67 L 6 10.44 L 6 6.71 L 8.36 9.07 C 7.99 9.36 7.57 9.58 7.11 9.72 L 7.11 10.87 C 7.88 10.69 8.57 10.34 9.16 9.86 L 10.29 11 L 11 10.29 L 6 5.29 L 1.71 1 Z M 6 1.56 L 4.84 2.72 L 6 3.88 L 6 1.56 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_audio_rounded.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_audio_rounded.svg
new file mode 100644
index 00000000000..a09790ea5ed
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_audio_rounded.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path d="M 1 4.29 L 1 7.71 L 3.22 7.71 L 6 10.56 L 6 1.44 L 3.22 4.29 L 1 4.29 Z M 8.5 6 C 8.5 4.99 7.93 4.12 7.11 3.7 L 7.11 8.29 C 7.93 7.88 8.5 7.01 8.5 6 Z M 7.11 1 L 7.11 2.17 C 8.72 2.66 9.89 4.19 9.89 6 C 9.89 7.81 8.72 9.34 7.11 9.83 L 7.11 11 C 9.34 10.48 11 8.44 11 6 C 11 3.56 9.34 1.52 7.11 1 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_bluetooth_connected.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_bluetooth_connected.svg
new file mode 100644
index 00000000000..7b55099bb99
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_bluetooth_connected.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M 16.8 2 h -1.4 v 10.63 L 8.97 6.2 L 7 8.17 L 14.83 16 L 7 23.83 L 8.97 25.8 l 6.43 -6.43 V 30 h 1.4 l 7.99 -7.99 L 18.77 16 l 6.02 -6.01 L 16.8 2 Z M 18 7 l 3 3 l -3 3 l 0.2 -5.64 L 18 7 Z m 3 15.01 L 18 25 v -6 l 2.83 3.01 H 21 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_media_capturing_with_arrow.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_media_capturing_with_arrow.svg
new file mode 100644
index 00000000000..55d264b73a5
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_media_capturing_with_arrow.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M 0 2 L 16 2 L 16 14 L 0 14 L 0 2 Z M 1 3 L 1 13 L 15 13 L 15 3 L 1 3 Z M 2 4 L 14 4 L 14 12 L 2 12 L 2 4 Z M 7.92 9 L 7.92 11 L 11.5 8 L 7.92 5 L 7.92 7 C 5.14 7 4.38 9 4 11 C 4.93 9.6 5.88 8.96 7.92 9 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_media_recording.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_media_recording.svg
new file mode 100644
index 00000000000..d9ffeafa73a
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_media_recording.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill-rule="evenodd"><path d="M 16 28 c 6.63 0 12 -5.37 12 -12 C 28 9.37 22.63 4 16 4 C 9.37 4 4 9.37 4 16 c 0 6.63 5.37 12 12 12 Z m 0 -2 c 5.52 0 10 -4.48 10 -10 C 26 10.48 21.52 6 16 6 C 10.48 6 6 10.48 6 16 c 0 5.52 4.48 10 10 10 Z m 0 -2 c 4.42 0 8 -3.58 8 -8 c 0 -4.42 -3.58 -8 -8 -8 c -4.42 0 -8 3.58 -8 8 c 0 4.42 3.58 8 8 8 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_usb_connected.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_usb_connected.svg
new file mode 100644
index 00000000000..c378a79e433
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/tab_usb_connected.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M 19 16 h 1 v 2 h -3 v -8 h 2 l -3 -4 l -3 4 h 2 v 8 h -3 v -2 c 0.85 -0.53 1.34 -1.23 1 -2 c 0.34 -1.27 -0.64 -2.25 -2 -2 c -1.02 -0.25 -2 0.73 -2 2 c 0 0.77 0.49 1.47 1 2 v 2 c 0.18 0.99 1.06 1.87 2 2 h 3 v 3 c -0.59 0.26 -1.07 0.98 -1 2 c -0.07 1.03 0.91 2 2 2 c 1.29 0 2.27 -0.97 2 -2 c 0.27 -1.02 -0.21 -1.74 -1 -2 v -3 h 3 c 1.14 -0.13 2.01 -1.01 2 -2 v -2 h 1 v -4 h -4 v 4 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/alert_indicators/vr_headset.svg b/chromium/chrome/browser/resources/tab_strip/alert_indicators/vr_headset.svg
new file mode 100644
index 00000000000..47d8a07244e
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/alert_indicators/vr_headset.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path d="M 17.4 5.57 A 2.12 2.12 0 0 0 15.94 5 H 4.06 c -0.55 0 -1.07 0.2 -1.46 0.56 c -0.39 0.37 -0.6 0.85 -0.6 1.36 v 6.14 c 0 0.52 0.21 1 0.6 1.36 c 0.39 0.37 0.91 0.56 1.46 0.56 h 2.85 c 0.37 0 0.74 -0.09 1.06 -0.28 c 0.32 -0.18 0.58 -0.43 0.76 -0.74 l 0.81 -1.4 a 0.48 0.48 0 0 1 0.13 -0.62 a 0.56 0.56 0 0 1 0.67 0 c 0.2 0.15 0.25 0.41 0.13 0.62 l 0.81 1.4 c 0.18 0.3 0.44 0.56 0.76 0.74 c 0.32 0.18 0.68 0.28 1.06 0.28 h 2.85 c 0.55 0 1.07 -0.2 1.46 -0.56 c 0.39 -0.36 0.6 -0.85 0.6 -1.36 V 6.93 c 0 -0.51 -0.21 -1 -0.6 -1.36 Z M 6.5 11.5 c -1.11 0 -2 -0.9 -2 -2 c 0 -1.1 0.9 -2 2 -2 c 1.1 0 2 0.9 2 2 c 0 1.1 -0.89 2 -2 2 Z m 7 0 c -1.11 0 -2 -0.9 -2 -2 c 0 -1.1 0.9 -2 2 -2 c 1.1 0 2 0.9 2 2 c 0 1.1 -0.89 2 -2 2 Z" /></svg>
diff --git a/chromium/chrome/browser/resources/tab_strip/tab.html b/chromium/chrome/browser/resources/tab_strip/tab.html
index 1e715432804..85517760d2d 100644
--- a/chromium/chrome/browser/resources/tab_strip/tab.html
+++ b/chromium/chrome/browser/resources/tab_strip/tab.html
@@ -1,38 +1,170 @@
<style>
:host {
- border-radius: var(--tabstrip-card-border-radius);
- box-shadow: var(--tabstrip-elevation-box-shadow);
+ --tabstrip-tab-title-height: 40px;
+ --tabstrip-tab-transition-duration: 250ms;
+
cursor: pointer;
+ height: var(--tabstrip-tab-height);
+ position: relative;
+ width: var(--tabstrip-tab-width);
+ }
+
+ #dragImage {
+ background: var(--tabstrip-tab-background-color);
+ border-radius: var(--tabstrip-tab-border-radius);
+ box-shadow: 0 0 0 1px var(--tabstrip-tab-separator-color);
+ color: var(--tabstrip-tab-text-color);
display: flex;
flex-direction: column;
- height: 230px;
+ height: 100%;
overflow: hidden;
- width: 280px;
+ width: 100%;
}
- :host([active]) {
- box-shadow: 0 0 0 2px var(--tabstrip-focus-color);
+ :host([active]) #dragImage {
+ box-shadow: 0 0 0 2px var(--tabstrip-tab-active-border-color);
outline: none;
}
#title {
align-items: center;
- background: var(--tabstrip-card-background-color);
- border-block-end: 1px solid var(--tabstrip-separator-color);
+ border-block-end: 1px solid var(--tabstrip-tab-separator-color);
box-sizing: border-box;
display: flex;
- height: 40px;
+ height: var(--tabstrip-tab-title-height);
justify-content: center;
margin: 0;
- padding-inline-end: 4px;
- padding-inline-start: 12px;
+ overflow: hidden;
}
- #favicon {
+ #faviconContainer {
+ --favicon-size: 16px;
flex-shrink: 0;
- height: 16px;
+ height: var(--favicon-size);
margin-inline-end: 8px;
- width: 16px;
+ margin-inline-start: 12px;
+ max-width: var(--favicon-size);
+ position: relative;
+ /* When transitioning to the default visible state, the margin and max-width
+ * transitions should finish first, then the opacity should be set to 1.
+ * This prevents the favicon and loading spinners from looking cropped
+ * while the element transitions. */
+ transition: margin var(--tabstrip-tab-transition-duration),
+ max-width var(--tabstrip-tab-transition-duration),
+ opacity 0ms linear var(--tabstrip-tab-transition-duration);
+ width: var(--favicon-size);
+ }
+
+ :host([hide-icon_]) #faviconContainer {
+ margin-inline-end: 0;
+ max-width: 0;
+ opacity: 0;
+ /* When transitioning to the hidden state, set opacity immediately to 0
+ * while transitioning the other values normally. */
+ transition: margin var(--tabstrip-tab-transition-duration),
+ max-width var(--tabstrip-tab-transition-duration),
+ opacity 0ms;
+ }
+
+ :host([pinned_]) #faviconContainer {
+ margin: 0;
+ }
+
+ #progressSpinner,
+ #favicon,
+ #crashedIcon {
+ height: var(--favicon-size);
+ left: 50%;
+ position: absolute;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ width: var(--favicon-size);
+ }
+
+ #progressSpinner {
+ -webkit-mask:
+ url(chrome://resources/images/throbber_small.svg)
+ center/contain no-repeat;
+ display: none;
+ }
+
+ #favicon {
+ background-size: contain;
+ transition: border-radius var(--tabstrip-tab-transition-duration);
+ }
+
+ #crashedIcon {
+ -webkit-mask:
+ url(chrome://theme/IDR_CRASH_SAD_FAVICON@2x)
+ center/contain no-repeat;
+ background-color: currentColor;
+ opacity: 0;
+ transform: translate(-50%, 100%);
+ }
+
+ #blocked {
+ background: var(--tabstrip-tab-blocked-color);
+ border: solid 1px var(--tabstrip-tab-background-color);
+ border-radius: 50%;
+ bottom: 0;
+ display: none;
+ height: 6px;
+ position: absolute;
+ right: 0;
+ transform: translate(50%, 50%);
+ width: 6px;
+ }
+
+ :host([waiting_]) #progressSpinner,
+ :host([loading_]) #progressSpinner {
+ display: block;
+ }
+
+ :host([loading_]) #favicon {
+ border-radius: 50%;
+ height: calc(var(--favicon-size) - 6px);
+ overflow: hidden;
+ width: calc(var(--favicon-size) - 6px);
+ }
+
+ :host([waiting_]) #progressSpinner {
+ background-color: var(--tabstrip-tab-waiting-spinning-color);
+ transform: /* Center first, then flip horizontally. */
+ translate(-50%, -50%) scaleX(-1);
+ }
+
+ :host([waiting_]) #favicon {
+ display: none;
+ }
+
+ :host([loading_]) #progressSpinner {
+ background-color: var(--tabstrip-tab-loading-spinning-color);
+ }
+
+ :host([crashed_]) #favicon {
+ opacity: 0;
+ transform: translate(-50%, 100%);
+ transition:
+ opacity var(--tabstrip-tab-transition-duration),
+ transform var(--tabstrip-tab-transition-duration);
+ }
+
+ :host([crashed_]) #crashedIcon {
+ opacity: 1;
+ transform: translate(-50%, -50%);
+ transition:
+ opacity var(--tabstrip-tab-transition-duration),
+ transform var(--tabstrip-tab-transition-duration);
+ /* Wait until transition for #favicon finishes. */
+ transition-delay: var(--tabstrip-tab-transition-duration);
+ }
+
+ :host([blocked_]) #blocked {
+ display: block;
+ }
+
+ :host([active][blocked_]) #blocked {
+ display: none;
}
#titleText {
@@ -48,62 +180,100 @@
align-items: center;
background-color: transparent;
border: 0;
+ color: inherit;
+ cursor: pointer;
display: flex;
flex-shrink: 0;
- height: 32px;
+ height: 100%;
justify-content: center;
margin-inline-start: auto;
padding: 0;
position: relative;
- width: 32px;
+ width: 36px;
}
#closeIcon {
- background:
- url(chrome://resources/images/icon_clear.svg) center/contain no-repeat;
+ -webkit-mask:
+ url(chrome://resources/images/icon_clear.svg)center/contain no-repeat;
+ background-color: currentColor;
display: block;
- height: 24px;
+ height: 18px;
position: relative;
- width: 24px;
+ width: 18px;
}
#thumbnail {
- background: var(--tabstrip-card-background-color);
+ align-items: center;
+ background: var(--tabstrip-tab-background-color);
+ display: flex;
flex: 1;
+ justify-content: center;
}
#thumbnailImg {
- height: 100%;
- object-fit: contain;
+ height: calc(var(--tabstrip-tab-height) - var(--tabstrip-tab-title-height));
+ object-fit: cover;
+ pointer-events: none;
+ width: var(--tabstrip-tab-width);
+ }
+
+ #thumbnailImg:not([src]) {
+ display: none;
+ pointer-events: none;
width: 100%;
}
/* Pinned tab styles */
- :host([pinned]) {
- height: 50px;
- width: 50px;
+ :host([pinned_]) {
+ height: var(--tabstrip-pinned-tab-size);
+ width: var(--tabstrip-pinned-tab-size);
}
- :host([pinned]) #title {
+ :host([pinned_]) #title {
border-block-end: 0;
height: 100%;
}
- :host([pinned]) #titleText,
- :host([pinned]) #close,
- :host([pinned]) #thumbnail {
+ :host([pinned_]) #titleText,
+ :host([pinned_]) #close,
+ :host([pinned_]) #thumbnail {
display: none;
}
+
+ :host([dragging_]) #dragPlaceholder {
+ background: var(--tabstrip-tab-background-color);
+ border-radius: var(--tabstrip-tab-border-radius);
+ height: 100%;
+ opacity: 0.5;
+ width: 100%;
+ }
+
+ /* When being dragged, the contents of the drag image needs to be off-screen
+ * with nothing else on top or below obscuring it. */
+ :host([dragging_]) #dragImage {
+ box-shadow: none;
+ position: absolute;
+ top: -999px;
+ }
</style>
-<header id="title">
- <span id="favicon"></span>
- <h2 id="titleText"></h2>
- <button id="close">
- <span id="closeIcon"></span>
- </button>
-</header>
+<div id="dragPlaceholder"></div>
+
+<div id="dragImage">
+ <header id="title">
+ <div id="faviconContainer">
+ <div id="progressSpinner"></div>
+ <div id="favicon"></div>
+ <div id="crashedIcon"></div>
+ <div id="blocked"></div>
+ </div>
+ <h2 id="titleText"></h2>
+ <button id="close">
+ <span id="closeIcon"></span>
+ </button>
+ </header>
-<div id="thumbnail">
- <img id="thumbnailImg">
+ <div id="thumbnail">
+ <img id="thumbnailImg">
+ </div>
</div>
diff --git a/chromium/chrome/browser/resources/tab_strip/tab.js b/chromium/chrome/browser/resources/tab_strip/tab.js
index 0b16488f0ca..a6908f2018a 100644
--- a/chromium/chrome/browser/resources/tab_strip/tab.js
+++ b/chromium/chrome/browser/resources/tab_strip/tab.js
@@ -2,10 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import {getFavicon, getFaviconForPageURL} from 'chrome://resources/js/icon.m.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {getFavicon} from 'chrome://resources/js/icon.m.js';
import {CustomElement} from './custom_element.js';
-import {TabsApiProxy} from './tabs_api_proxy.js';
+import {TabStripEmbedderProxy} from './tab_strip_embedder_proxy.js';
+import {TabData, TabNetworkState, TabsApiProxy} from './tabs_api_proxy.js';
export const DEFAULT_ANIMATION_DURATION = 125;
@@ -22,6 +24,11 @@ export class TabElement extends CustomElement {
/** @type {!HTMLElement} */ (this.shadowRoot.querySelector('#close'));
/** @private {!HTMLElement} */
+ this.dragImage_ =
+ /** @type {!HTMLElement} */ (
+ this.shadowRoot.querySelector('#dragImage'));
+
+ /** @private {!HTMLElement} */
this.faviconEl_ =
/** @type {!HTMLElement} */ (this.shadowRoot.querySelector('#favicon'));
@@ -34,54 +41,79 @@ export class TabElement extends CustomElement {
this.thumbnail_ =
/** @type {!Image} */ (this.shadowRoot.querySelector('#thumbnailImg'));
- /** @private {!Tab} */
+ /** @private {!TabData} */
this.tab_;
/** @private {!TabsApiProxy} */
this.tabsApi_ = TabsApiProxy.getInstance();
+ /** @private {!TabStripEmbedderProxy} */
+ this.embedderApi_ = TabStripEmbedderProxy.getInstance();
+
/** @private {!HTMLElement} */
this.titleTextEl_ = /** @type {!HTMLElement} */ (
this.shadowRoot.querySelector('#titleText'));
this.addEventListener('click', this.onClick_.bind(this));
+ this.addEventListener('contextmenu', this.onContextMenu_.bind(this));
this.closeButtonEl_.addEventListener('click', this.onClose_.bind(this));
}
- /** @return {!Tab} */
+ /** @return {!TabData} */
get tab() {
return this.tab_;
}
- /** @param {!Tab} tab */
+ /** @param {!TabData} tab */
set tab(tab) {
+ assert(this.tab_ !== tab);
this.toggleAttribute('active', tab.active);
- this.toggleAttribute('pinned', tab.pinned);
+ this.toggleAttribute('hide-icon_', !tab.showIcon);
+ this.toggleAttribute(
+ 'waiting_',
+ !tab.shouldHideThrobber &&
+ tab.networkState === TabNetworkState.WAITING);
+ this.toggleAttribute(
+ 'loading_',
+ !tab.shouldHideThrobber &&
+ tab.networkState === TabNetworkState.LOADING);
+ this.toggleAttribute('pinned_', tab.pinned);
+ this.toggleAttribute('blocked_', tab.blocked);
+ this.setAttribute('draggable', tab.pinned);
+ this.toggleAttribute('crashed_', tab.crashed);
if (!this.tab_ || this.tab_.title !== tab.title) {
this.titleTextEl_.textContent = tab.title;
}
- if (tab.favIconUrl &&
- (!this.tab_ || this.tab_.favIconUrl !== tab.favIconUrl)) {
- this.faviconEl_.style.backgroundImage = getFavicon(tab.favIconUrl);
- } else if (!this.tab_ || this.tab_.url !== tab.url) {
- this.faviconEl_.style.backgroundImage =
- getFaviconForPageURL(tab.url, false);
+ if (tab.networkState === TabNetworkState.WAITING ||
+ (tab.networkState === TabNetworkState.LOADING &&
+ tab.isDefaultFavicon)) {
+ this.faviconEl_.style.backgroundImage = 'none';
+ } else if (tab.favIconUrl) {
+ this.faviconEl_.style.backgroundImage = `url(${tab.favIconUrl})`;
+ } else {
+ this.faviconEl_.style.backgroundImage = getFavicon('');
}
// Expose the ID to an attribute to allow easy querySelector use
this.setAttribute('data-tab-id', tab.id);
if (!this.tab_ || this.tab_.id !== tab.id) {
- // Request thumbnail updates
- chrome.send('addTrackedTab', [tab.id]);
+ this.tabsApi_.trackThumbnailForTab(tab.id);
}
this.tab_ = Object.freeze(tab);
}
/**
+ * @return {!HTMLElement}
+ */
+ getDragImage() {
+ return this.dragImage_;
+ }
+
+ /**
* @param {string} imgData
*/
updateThumbnail(imgData) {
@@ -95,6 +127,19 @@ export class TabElement extends CustomElement {
}
this.tabsApi_.activateTab(this.tab_.id);
+ this.embedderApi_.closeContainer();
+ }
+
+ /** @private */
+ onContextMenu_(event) {
+ event.preventDefault();
+
+ if (!this.tab_) {
+ return;
+ }
+
+ this.embedderApi_.showTabContextMenu(
+ this.tab_.id, event.clientX, event.clientY);
}
/**
@@ -111,6 +156,13 @@ export class TabElement extends CustomElement {
}
/**
+ * @param {boolean} dragging
+ */
+ setDragging(dragging) {
+ this.toggleAttribute('dragging_', dragging);
+ }
+
+ /**
* @return {!Promise}
*/
slideIn() {
diff --git a/chromium/chrome/browser/resources/tab_strip/tab_list.html b/chromium/chrome/browser/resources/tab_strip/tab_list.html
index 680c562aa68..1b6056cd4d3 100644
--- a/chromium/chrome/browser/resources/tab_strip/tab_list.html
+++ b/chromium/chrome/browser/resources/tab_strip/tab_list.html
@@ -1,36 +1,28 @@
<style>
:host {
+ background: var(--tabstrip-background-color);
+ box-sizing: border-box;
display: flex;
+ min-width: fit-content;
padding: 16px;
- width: fit-content;
+ width: 100%;
}
#pinnedTabsContainer {
+ /* 4 pinned tabs should fit in the same space vertically as 1 unpinned
+ * tab. 30px is subtracted from the height of an unpinned tab as there
+ * are three 10px gaps to separate each of the 4 pinned tabs. */
+ --tabstrip-pinned-tab-size: calc((var(--tabstrip-tab-height) - 30px) / 4);
+
display: grid;
- grid-auto-columns: 50px;
+ grid-auto-columns: var(--tabstrip-pinned-tab-size);
grid-auto-flow: column;
grid-gap: 10px;
- grid-template-rows: repeat(4, 50px);
+ grid-template-rows: repeat(4, var(--tabstrip-pinned-tab-size));
margin-inline-end: 16px;
}
- #pinnedTabsContainer[empty] {
- display: none;
- }
-
- .ghost-pinned-tab {
- background: var(--tabstrip-card-background-color);
- border-radius: var(--tabstrip-card-border-radius);
- box-shadow: var(--tabstrip-elevation-box-shadow);
- opacity: 0.5;
- }
-
- /* The #pinnedTabsContainer can only fit a maximum of 4 pinned tabs. The
- * ghost-pinned-tab elements are meant to add as placeholders if there
- * are not enough actual pinned tabs to fill an entire column. Therefore,
- * all ghost-pinned-tabs after the 4 * nth element should be hidden. */
- .ghost-pinned-tab:nth-child(4n + 1),
- .ghost-pinned-tab:nth-child(4n + 1) ~ .ghost-pinned-tab {
+ #pinnedTabsContainer:empty {
display: none;
}
@@ -43,9 +35,5 @@
}
</style>
-<div id="pinnedTabsContainer" empty>
- <div class="ghost-pinned-tab"></div>
- <div class="ghost-pinned-tab"></div>
- <div class="ghost-pinned-tab"></div>
-</div>
+<div id="pinnedTabsContainer"></div>
<div id="tabsContainer"></div>
diff --git a/chromium/chrome/browser/resources/tab_strip/tab_list.js b/chromium/chrome/browser/resources/tab_strip/tab_list.js
index dd72aec77bd..4d747e65f62 100644
--- a/chromium/chrome/browser/resources/tab_strip/tab_list.js
+++ b/chromium/chrome/browser/resources/tab_strip/tab_list.js
@@ -4,12 +4,29 @@
import './tab.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
import {addWebUIListener} from 'chrome://resources/js/cr.m.js';
+
import {CustomElement} from './custom_element.js';
import {TabElement} from './tab.js';
-import {TabsApiProxy} from './tabs_api_proxy.js';
-
-const GHOST_PINNED_TAB_COUNT = 3;
+import {TabStripEmbedderProxy} from './tab_strip_embedder_proxy.js';
+import {TabData, TabsApiProxy} from './tabs_api_proxy.js';
+
+/**
+ * The amount of padding to leave between the edge of the screen and the active
+ * tab when auto-scrolling. This should leave some room to show the previous or
+ * next tab to afford to users that there more tabs if the user scrolls.
+ * @const {number}
+ */
+const SCROLL_PADDING = 32;
+
+/**
+ * @param {!Element} element
+ * @return {boolean}
+ */
+function isTabElement(element) {
+ return element.tagName === 'TABSTRIP-TAB';
+}
class TabListElement extends CustomElement {
static get template() {
@@ -28,27 +45,45 @@ class TabListElement extends CustomElement {
*/
this.animationPromises = Promise.resolve();
+ /**
+ * The TabElement that is currently being dragged.
+ * @private {!TabElement|undefined}
+ */
+ this.draggedItem_;
+
/** @private {!Element} */
this.pinnedTabsContainerElement_ =
/** @type {!Element} */ (
this.shadowRoot.querySelector('#pinnedTabsContainer'));
+ /** @private {!Element} */
+ this.scrollingParent_ = document.documentElement;
+
+ /** @private {!TabStripEmbedderProxy} */
+ this.tabStripEmbedderProxy_ = TabStripEmbedderProxy.getInstance();
+
/** @private {!TabsApiProxy} */
this.tabsApi_ = TabsApiProxy.getInstance();
- /** @private {!Object} */
- this.tabsApiHandler_ = this.tabsApi_.callbackRouter;
-
/** @private {!Element} */
this.tabsContainerElement_ =
/** @type {!Element} */ (
this.shadowRoot.querySelector('#tabsContainer'));
- /** @private {number} */
- this.windowId_;
+ addWebUIListener('theme-changed', () => this.fetchAndUpdateColors_());
+ this.tabStripEmbedderProxy_.observeThemeChanges();
addWebUIListener(
'tab-thumbnail-updated', this.tabThumbnailUpdated_.bind(this));
+
+ this.addEventListener(
+ 'dragstart', (e) => this.onDragStart_(/** @type {!DragEvent} */ (e)));
+ this.addEventListener(
+ 'dragend', (e) => this.onDragEnd_(/** @type {!DragEvent} */ (e)));
+ this.addEventListener(
+ 'dragover', (e) => this.onDragOver_(/** @type {!DragEvent} */ (e)));
+ document.addEventListener(
+ 'visibilitychange', () => this.moveOrScrollToActiveTab_());
}
/**
@@ -60,33 +95,23 @@ class TabListElement extends CustomElement {
}
connectedCallback() {
- this.tabsApi_.getCurrentWindow().then((currentWindow) => {
- this.windowId_ = currentWindow.id;
-
- // TODO(johntlee): currentWindow.tabs is guaranteed to be defined because
- // `populate: true` is passed in as part of the arguments to the API.
- // Once the closure compiler is able to type `assert` to return a truthy
- // type even when being used with modules, the conditionals should be
- // replaced with `assert` (b/138729777).
- if (currentWindow.tabs) {
- for (const tab of currentWindow.tabs) {
- if (tab) {
- this.onTabCreated_(tab);
- }
- }
- }
-
- this.tabsApiHandler_.onActivated.addListener(
- this.onTabActivated_.bind(this));
- this.tabsApiHandler_.onCreated.addListener(this.onTabCreated_.bind(this));
- this.tabsApiHandler_.onMoved.addListener(this.onTabMoved_.bind(this));
- this.tabsApiHandler_.onRemoved.addListener(this.onTabRemoved_.bind(this));
- this.tabsApiHandler_.onUpdated.addListener(this.onTabUpdated_.bind(this));
+ this.fetchAndUpdateColors_();
+ this.tabsApi_.getTabs().then(tabs => {
+ tabs.forEach(tab => this.onTabCreated_(tab));
+ this.moveOrScrollToActiveTab_();
+
+ addWebUIListener('tab-created', tab => this.onTabCreated_(tab));
+ addWebUIListener(
+ 'tab-moved', (tabId, newIndex) => this.onTabMoved_(tabId, newIndex));
+ addWebUIListener('tab-removed', tabId => this.onTabRemoved_(tabId));
+ addWebUIListener('tab-updated', tab => this.onTabUpdated_(tab));
+ addWebUIListener(
+ 'tab-active-changed', tabId => this.onTabActivated_(tabId));
});
}
/**
- * @param {!Tab} tab
+ * @param {!TabData} tab
* @return {!TabElement}
* @private
*/
@@ -106,6 +131,24 @@ class TabListElement extends CustomElement {
this.shadowRoot.querySelector(`tabstrip-tab[data-tab-id="${tabId}"]`));
}
+ /** @private */
+ fetchAndUpdateColors_() {
+ this.tabStripEmbedderProxy_.getColors().then(colors => {
+ for (const [cssVariable, rgbaValue] of Object.entries(colors)) {
+ this.style.setProperty(cssVariable, rgbaValue);
+ }
+ });
+ }
+
+ /**
+ * @return {?TabElement}
+ * @private
+ */
+ getActiveTab_() {
+ return /** @type {?TabElement} */ (
+ this.shadowRoot.querySelector('tabstrip-tab[active]'));
+ }
+
/**
* @param {!TabElement} tabElement
* @param {number} index
@@ -121,111 +164,207 @@ class TabListElement extends CustomElement {
} else {
// Pinned tabs are in their own container, so the index of non-pinned
// tabs need to be offset by the number of pinned tabs
- const offsetIndex = index -
- (this.pinnedTabsContainerElement_.childElementCount -
- GHOST_PINNED_TAB_COUNT);
+ const offsetIndex =
+ index - this.pinnedTabsContainerElement_.childElementCount;
this.tabsContainerElement_.insertBefore(
tabElement, this.tabsContainerElement_.childNodes[offsetIndex]);
}
+ }
+
+ /** @private */
+ moveOrScrollToActiveTab_() {
+ const activeTab = this.getActiveTab_();
+ if (!activeTab) {
+ return;
+ }
- this.updatePinnedTabsState_();
+ if (!this.tabStripEmbedderProxy_.isVisible() && !activeTab.tab.pinned &&
+ this.tabsContainerElement_.firstChild !== activeTab) {
+ this.tabsApi_.moveTab(
+ activeTab.tab.id, this.pinnedTabsContainerElement_.childElementCount);
+ } else {
+ this.scrollToTab_(activeTab);
+ }
}
/**
- * @param {!TabActivatedInfo} activeInfo
+ * @param {!DragEvent} event
* @private
*/
- onTabActivated_(activeInfo) {
- if (activeInfo.windowId !== this.windowId_) {
+ onDragEnd_(event) {
+ if (!this.draggedItem_) {
return;
}
- const previouslyActiveTab =
- this.shadowRoot.querySelector('tabstrip-tab[active]');
- if (previouslyActiveTab) {
- previouslyActiveTab.tab = /** @type {!Tab} */ (
- Object.assign({}, previouslyActiveTab.tab, {active: false}));
+ this.draggedItem_.setDragging(false);
+ this.draggedItem_ = undefined;
+ }
+
+ /**
+ * @param {!DragEvent} event
+ * @private
+ */
+ onDragOver_(event) {
+ event.preventDefault();
+ const dragOverItem = event.path.find((pathItem) => {
+ return pathItem !== this.draggedItem_ && isTabElement(pathItem);
+ });
+
+ if (!dragOverItem || !this.draggedItem_ || !dragOverItem.tab.pinned) {
+ return;
}
- const newlyActiveTab = this.findTabElement_(activeInfo.tabId);
- newlyActiveTab.tab = /** @type {!Tab} */ (
- Object.assign({}, newlyActiveTab.tab, {active: true}));
+ event.dataTransfer.dropEffect = 'move';
+
+ const dragOverIndex =
+ Array.from(dragOverItem.parentNode.children).indexOf(dragOverItem);
+ this.tabsApi_.moveTab(this.draggedItem_.tab.id, dragOverIndex);
}
/**
- * @param {!Tab} tab
+ * @param {!DragEvent} event
* @private
*/
- onTabCreated_(tab) {
- if (tab.windowId !== this.windowId_) {
+ onDragStart_(event) {
+ const draggedItem = event.path[0];
+ if (!isTabElement(draggedItem)) {
return;
}
- const tabElement = this.createTabElement_(tab);
- this.insertTabOrMoveTo_(tabElement, tab.index);
- this.addAnimationPromise_(tabElement.slideIn());
+ assert(draggedItem.tab.pinned);
+ this.draggedItem_ = /** @type {!TabElement} */ (draggedItem);
+ this.draggedItem_.setDragging(true);
+ event.dataTransfer.effectAllowed = 'move';
+ event.dataTransfer.setDragImage(
+ this.draggedItem_.getDragImage(),
+ event.pageX - this.draggedItem_.offsetLeft,
+ event.pageY - this.draggedItem_.offsetTop);
}
/**
* @param {number} tabId
- * @param {!TabMovedInfo} moveInfo
* @private
*/
- onTabMoved_(tabId, moveInfo) {
- if (moveInfo.windowId !== this.windowId_) {
- return;
+ onTabActivated_(tabId) {
+ // There may be more than 1 TabElement marked as active if other events
+ // have updated a Tab to have an active state. For example, if a
+ // tab is created with an already active state, there may be 2 active
+ // TabElements: the newly created tab and the previously active tab.
+ this.shadowRoot.querySelectorAll('tabstrip-tab[active]')
+ .forEach((previouslyActiveTab) => {
+ if (previouslyActiveTab.tab.id !== tabId) {
+ previouslyActiveTab.tab = /** @type {!TabData} */ (
+ Object.assign({}, previouslyActiveTab.tab, {active: false}));
+ }
+ });
+
+ const newlyActiveTab = this.findTabElement_(tabId);
+ if (newlyActiveTab) {
+ newlyActiveTab.tab = /** @type {!TabData} */ (
+ Object.assign({}, newlyActiveTab.tab, {active: true}));
+ this.moveOrScrollToActiveTab_();
}
+ }
- const movedTab = this.findTabElement_(tabId);
- if (movedTab) {
- this.insertTabOrMoveTo_(movedTab, moveInfo.toIndex);
+ /**
+ * @param {!TabData} tab
+ * @private
+ */
+ onTabCreated_(tab) {
+ const tabElement = this.createTabElement_(tab);
+
+ if (tab.active && !tab.pinned &&
+ tab.index !== this.pinnedTabsContainerElement_.childElementCount) {
+ // Newly created active tabs should first be moved to the very beginning
+ // of the tab strip to enforce the tab strip's most recently used ordering
+ this.tabsApi_
+ .moveTab(tab.id, this.pinnedTabsContainerElement_.childElementCount)
+ .then(() => {
+ this.insertTabOrMoveTo_(
+ tabElement, this.pinnedTabsContainerElement_.childElementCount);
+ this.addAnimationPromise_(tabElement.slideIn());
+ });
+ } else {
+ this.insertTabOrMoveTo_(tabElement, tab.index);
+ this.addAnimationPromise_(tabElement.slideIn());
}
}
/**
* @param {number} tabId
- * @param {!WindowRemoveInfo} removeInfo
+ * @param {number} newIndex
* @private
*/
- onTabRemoved_(tabId, removeInfo) {
- if (removeInfo.windowId !== this.windowId_) {
- return;
+ onTabMoved_(tabId, newIndex) {
+ const movedTab = this.findTabElement_(tabId);
+ if (movedTab) {
+ this.insertTabOrMoveTo_(movedTab, newIndex);
+ if (movedTab.tab.active) {
+ this.scrollToTab_(movedTab);
+ }
}
+ }
+ /**
+ * @param {number} tabId
+ * @private
+ */
+ onTabRemoved_(tabId) {
const tabElement = this.findTabElement_(tabId);
if (tabElement) {
- this.addAnimationPromise_(new Promise(async resolve => {
- await tabElement.slideOut();
- this.updatePinnedTabsState_();
- resolve();
- }));
+ this.addAnimationPromise_(tabElement.slideOut());
}
}
/**
- * @param {number} tabId
- * @param {!Tab} changeInfo
- * @param {!Tab} tab
+ * @param {!TabData} tab
* @private
*/
- onTabUpdated_(tabId, changeInfo, tab) {
- if (tab.windowId !== this.windowId_) {
+ onTabUpdated_(tab) {
+ const tabElement = this.findTabElement_(tab.id);
+ if (!tabElement) {
return;
}
- const tabElement = this.findTabElement_(tabId);
- if (tabElement) {
- tabElement.tab = tab;
+ const previousTab = tabElement.tab;
+ tabElement.tab = tab;
- if (changeInfo.pinned !== undefined) {
- // If the tab is being pinned or unpinned, we need to move it to its new
- // location
- this.insertTabOrMoveTo_(tabElement, tab.index);
+ if (previousTab.pinned !== tab.pinned) {
+ // If the tab is being pinned or unpinned, we need to move it to its new
+ // location
+ this.insertTabOrMoveTo_(tabElement, tab.index);
+ if (tab.active) {
+ this.scrollToTab_(tabElement);
}
}
}
/**
+ * @param {!TabElement} tabElement
+ * @private
+ */
+ scrollToTab_(tabElement) {
+ this.animationPromises.then(() => {
+ const screenLeft = this.scrollingParent_.scrollLeft;
+ const screenRight = screenLeft + this.scrollingParent_.offsetWidth;
+
+ if (screenLeft > tabElement.offsetLeft) {
+ // If the element's left is to the left of the visible screen, scroll
+ // such that the element's left edge is aligned with the screen's edge
+ this.scrollingParent_.scrollLeft =
+ tabElement.offsetLeft - SCROLL_PADDING;
+ } else if (screenRight < tabElement.offsetLeft + tabElement.offsetWidth) {
+ // If the element's right is to the right of the visible screen, scroll
+ // such that the element's right edge is aligned with the screen's right
+ // edge.
+ this.scrollingParent_.scrollLeft = tabElement.offsetLeft +
+ tabElement.offsetWidth - this.scrollingParent_.offsetWidth +
+ SCROLL_PADDING;
+ }
+ });
+ }
+
+ /**
* @param {number} tabId
* @param {string} imgData
* @private
@@ -236,14 +375,6 @@ class TabListElement extends CustomElement {
tab.updateThumbnail(imgData);
}
}
-
- /** @private */
- updatePinnedTabsState_() {
- this.pinnedTabsContainerElement_.toggleAttribute(
- 'empty',
- this.pinnedTabsContainerElement_.childElementCount ===
- GHOST_PINNED_TAB_COUNT);
- }
}
customElements.define('tabstrip-tab-list', TabListElement);
diff --git a/chromium/chrome/browser/resources/tab_strip/tab_strip.html b/chromium/chrome/browser/resources/tab_strip/tab_strip.html
index 088f0ea0097..20b58a70a3c 100644
--- a/chromium/chrome/browser/resources/tab_strip/tab_strip.html
+++ b/chromium/chrome/browser/resources/tab_strip/tab_strip.html
@@ -13,30 +13,15 @@
--google-blue-300-rgb: 138, 180, 248;
--google-blue-500-rgb: 66, 133, 244;
- --tabstrip-background-color: rgb(var(--google-grey-50-rgb));
- --tabstrip-card-background-color: white;
- --tabstrip-card-border-radius: 8px;
- --tabstrip-elevation-box-shadow:
- 0 0 0 1px rgb(var(--google-grey-300-rgb));
- --tabstrip-focus-color: rgb(var(--google-blue-500-rgb));
- --tabstrip-primary-text-color: rgb(var(--google-grey-900-rgb));
- --tabstrip-separator-color: rgb(var(--google-grey-300-rgb));
- }
-
- @media (prefers-color-scheme: dark) {
- html {
- --tabstrip-background-color: rgba(var(--google-grey-900-rgb));
- --tabstrip-card-background-color: rgba(255, 255, 255, 0.04);
- --tabstrip-elevation-box-shadow: none;
- --tabstrip-focus-color: rgb(var(--google-blue-300-rgb));
- --tabstrip-primary-text-color: rgb(var(--google-grey-200-rgb));
- --tabstrip-separator-color: rgb(255, 255, 255, 0.1);
- }
+ --tabstrip-background-color: $i18n{frameColor};
+ --tabstrip-tab-height: 216px;
+ --tabstrip-tab-width: 288px;
+ --tabstrip-tab-border-radius: 8px;
+ --tabstrip-tab-active-border-color: rgb(var(--google-blue-500-rgb));
}
body {
background: var(--tabstrip-background-color);
- color: var(--tabstrip-primary-text-color);
margin: 0;
padding: 0;
}
diff --git a/chromium/chrome/browser/resources/tab_strip/tab_strip_embedder_proxy.js b/chromium/chrome/browser/resources/tab_strip/tab_strip_embedder_proxy.js
new file mode 100644
index 00000000000..6852de8b29f
--- /dev/null
+++ b/chromium/chrome/browser/resources/tab_strip/tab_strip_embedder_proxy.js
@@ -0,0 +1,39 @@
+// Copyright 2019 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 {addSingletonGetter, addWebUIListener, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+
+export class TabStripEmbedderProxy {
+ /** @return {boolean} */
+ isVisible() {
+ return document.visibilityState === 'visible';
+ }
+
+ /**
+ * @return {!Promise<!Object<string, string>>} Object with CSS variables
+ * as keys and rgba strings as values
+ */
+ getColors() {
+ return sendWithPromise('getThemeColors');
+ }
+
+ observeThemeChanges() {
+ chrome.send('observeThemeChanges');
+ }
+
+ /**
+ * @param {number} tabId
+ * @param {number} locationX
+ * @param {number} locationY
+ */
+ showTabContextMenu(tabId, locationX, locationY) {
+ chrome.send('showTabContextMenu', [tabId, locationX, locationY]);
+ }
+
+ closeContainer() {
+ chrome.send('closeContainer');
+ }
+}
+
+addSingletonGetter(TabStripEmbedderProxy);
diff --git a/chromium/chrome/browser/resources/tab_strip/tab_strip_resources.grd b/chromium/chrome/browser/resources/tab_strip/tab_strip_resources.grd
index 72257fd7930..97074dac942 100644
--- a/chromium/chrome/browser/resources/tab_strip/tab_strip_resources.grd
+++ b/chromium/chrome/browser/resources/tab_strip/tab_strip_resources.grd
@@ -39,6 +39,66 @@
use_base_dir="false"
type="chrome_html"
compress="gzip"/>
+ <structure
+ name="IDR_TAB_STRIP_ALERT_INDICATOR_JS"
+ file="${root_gen_dir}/chrome/browser/resources/tab_strip/alert_indicator.js"
+ use_base_dir="false"
+ type="chrome_html"
+ compress="gzip"/>
+ <structure
+ name="IDR_TAB_STRIP_EMBEDDER_PROXY_JS"
+ file="tab_strip_embedder_proxy.js"
+ type="chrome_html"
+ compress="gzip"/>
</structures>
+
+ <includes>
+ <!-- Alert indicators -->
+ <include
+ name="IDR_TAB_STRIP_PICTURE_IN_PICTURE_ALT_SVG"
+ file="alert_indicators/picture_in_picture_alt.svg"
+ type="BINDATA"
+ compress="gzip" />
+ <include
+ name="IDR_TAB_STRIP_SERIAL_PORT_SVG"
+ file="alert_indicators/serial_port.svg"
+ type="BINDATA"
+ compress="gzip" />
+ <include
+ name="IDR_TAB_STRIP_TAB_AUDIO_MUTING_ROUNDED_SVG"
+ file="alert_indicators/tab_audio_muting_rounded.svg"
+ type="BINDATA"
+ compress="gzip" />
+ <include
+ name="IDR_TAB_STRIP_TAB_AUDIO_ROUNDED_SVG"
+ file="alert_indicators/tab_audio_rounded.svg"
+ type="BINDATA"
+ compress="gzip" />
+ <include
+ name="IDR_TAB_STRIP_TAB_BLUETOOTH_CONNECTED_SVG"
+ file="alert_indicators/tab_bluetooth_connected.svg"
+ type="BINDATA"
+ compress="gzip" />
+ <include
+ name="IDR_TAB_STRIP_TAB_MEDIA_CAPTURING_WITH_ARROW_SVG"
+ file="alert_indicators/tab_media_capturing_with_arrow.svg"
+ type="BINDATA"
+ compress="gzip" />
+ <include
+ name="IDR_TAB_STRIP_TAB_MEDIA_RECORING_SVG"
+ file="alert_indicators/tab_media_recording.svg"
+ type="BINDATA"
+ compress="gzip" />
+ <include
+ name="IDR_TAB_STRIP_TAB_USB_CONNECTED_SVG"
+ file="alert_indicators/tab_usb_connected.svg"
+ type="BINDATA"
+ compress="gzip" />
+ <include
+ name="IDR_TAB_STRIP_VR_HEADSET_SVG"
+ file="alert_indicators/vr_headset.svg"
+ type="BINDATA"
+ compress="gzip" />
+ </includes>
</release>
</grit>
diff --git a/chromium/chrome/browser/resources/tab_strip/tabs_api_proxy.js b/chromium/chrome/browser/resources/tab_strip/tabs_api_proxy.js
index 9ce6ad61f28..34b13b9f75f 100644
--- a/chromium/chrome/browser/resources/tab_strip/tabs_api_proxy.js
+++ b/chromium/chrome/browser/resources/tab_strip/tabs_api_proxy.js
@@ -2,23 +2,46 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
-export class TabsApiProxy {
- constructor() {
- /** @type {!Object<string, !ChromeEvent>} */
- this.callbackRouter = {
- onActivated: chrome.tabs.onActivated,
- onCreated: chrome.tabs.onCreated,
- onMoved: chrome.tabs.onMoved,
- onRemoved: chrome.tabs.onRemoved,
- onUpdated: chrome.tabs.onUpdated,
- };
- }
+/**
+ * Must be kept in sync with TabNetworkState from
+ * //chrome/browser/ui/tabs/tab_network_state.h.
+ * @enum {number}
+ */
+export const TabNetworkState = {
+ NONE: 0,
+ WAITING: 1,
+ LOADING: 2,
+ ERROR: 3,
+};
+
+/**
+ * @typedef {{
+ * active: boolean,
+ * blocked: boolean,
+ * crashed: boolean,
+ * favIconUrl: (string|undefined),
+ * id: number,
+ * index: number,
+ * isDefaultFavicon: boolean,
+ * networkState: !TabNetworkState,
+ * pinned: boolean,
+ * shouldHideThrobber: boolean,
+ * showIcon: boolean,
+ * title: string,
+ * url: string,
+ * }}
+ */
+export let TabData;
+
+/** @typedef {!Tab} */
+let ExtensionsApiTab;
+export class TabsApiProxy {
/**
* @param {number} tabId
- * @return {!Promise<!Tab>}
+ * @return {!Promise<!ExtensionsApiTab>}
*/
activateTab(tabId) {
return new Promise(resolve => {
@@ -27,18 +50,10 @@ export class TabsApiProxy {
}
/**
- * @return {!Promise<!ChromeWindow>}
+ * @return {!Promise<!Array<!TabData>>}
*/
- getCurrentWindow() {
- const options = {
- populate: true, // populate window data with tabs data
- windowTypes: ['normal'], // prevent devtools from being returned
- };
- return new Promise(resolve => {
- chrome.windows.getCurrent(options, currentWindow => {
- resolve(currentWindow);
- });
- });
+ getTabs() {
+ return sendWithPromise('getTabs');
}
/**
@@ -54,7 +69,7 @@ export class TabsApiProxy {
/**
* @param {number} tabId
* @param {number} newIndex
- * @return {!Promise<!Tab>}
+ * @return {!Promise<!ExtensionsApiTab>}
*/
moveTab(tabId, newIndex) {
return new Promise(resolve => {
@@ -63,6 +78,13 @@ export class TabsApiProxy {
});
});
}
+
+ /**
+ * @param {number} tabId
+ */
+ trackThumbnailForTab(tabId) {
+ chrome.send('addTrackedTab', [tabId]);
+ }
}
addSingletonGetter(TabsApiProxy);
diff --git a/chromium/chrome/browser/resources/tab_strip/types.js b/chromium/chrome/browser/resources/tab_strip/types.js
deleted file mode 100644
index 6496711a0da..00000000000
--- a/chromium/chrome/browser/resources/tab_strip/types.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2019 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 Closure typedefs for Tab Strip.
- */
-
-/**
- * @typedef {{
- * tabId: number,
- * windowId: number,
- * }}
- */
-let TabActivatedInfo;
-
-/**
- * @typedef {{
- * fromIndex: number,
- * toIndex: number,
- * windowId: number,
- * }}
- */
-let TabMovedInfo;
-
-/**
- * @typedef {{
- * isWindowClosing: boolean,
- * windowId: number,
- * }}
- */
-let WindowRemoveInfo;