diff options
Diffstat (limited to 'chromium/chrome/browser/resources/tab_strip')
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; |