Commit d14bec64 authored by John Lee's avatar John Lee Committed by Commit Bot

Tab Strip WebUI: Add loading indicator

This CL also hides the favicon if the tab is still loading and there
is no favicon specified. This is more inline with the native tabstrip.

Bug: 1004985
Change-Id: I223bd1b25ab7e2d4d8a264eb2d3c8dc468bdeabc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1810062Reviewed-by: default avatarRebekah Potter <rbpotter@chromium.org>
Commit-Queue: John Lee <johntlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#699088}
parent 4fb5fa23
...@@ -38,13 +38,47 @@ ...@@ -38,13 +38,47 @@
padding-inline-start: 12px; padding-inline-start: 12px;
} }
#favicon { #faviconContainer {
flex-shrink: 0; flex-shrink: 0;
height: 16px; height: 16px;
margin-inline-end: 8px; margin-inline-end: 8px;
position: relative;
width: 16px; width: 16px;
} }
#loading,
#favicon {
height: 100%;
position: absolute;
width: 100%;
}
#loading {
-webkit-mask-image: url(chrome://resources/images/throbber_small.svg);
background-color: var(--tabstrip-primary-text-color);
display: none;
}
#favicon {
background-size: contain;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
transition: border-radius 250ms;
}
:host([loading]) #loading,
:host([loading]) #favicon {
display: block;
}
:host([loading]) #favicon {
border-radius: 50%;
height: calc(100% - 6px);
overflow: hidden;
width: calc(100% - 6px);
}
#titleText { #titleText {
font-size: 100%; font-size: 100%;
font-weight: normal; font-weight: normal;
...@@ -135,7 +169,10 @@ ...@@ -135,7 +169,10 @@
<div id="dragImage"> <div id="dragImage">
<header id="title"> <header id="title">
<span id="favicon"></span> <div id="faviconContainer">
<div id="loading"></div>
<div id="favicon"></div>
</div>
<h2 id="titleText"></h2> <h2 id="titleText"></h2>
<button id="close"> <button id="close">
<span id="closeIcon"></span> <span id="closeIcon"></span>
......
...@@ -9,6 +9,12 @@ import {TabsApiProxy} from './tabs_api_proxy.js'; ...@@ -9,6 +9,12 @@ import {TabsApiProxy} from './tabs_api_proxy.js';
export const DEFAULT_ANIMATION_DURATION = 125; export const DEFAULT_ANIMATION_DURATION = 125;
/** @const @enum {string} */
const STATUS = {
LOADING: 'loading',
COMPLETE: 'complete',
};
export class TabElement extends CustomElement { export class TabElement extends CustomElement {
static get template() { static get template() {
return `{__html_template__}`; return `{__html_template__}`;
...@@ -65,18 +71,29 @@ export class TabElement extends CustomElement { ...@@ -65,18 +71,29 @@ export class TabElement extends CustomElement {
/** @param {!Tab} tab */ /** @param {!Tab} tab */
set tab(tab) { set tab(tab) {
this.toggleAttribute('active', tab.active); this.toggleAttribute('active', tab.active);
// TODO(johntlee): Update loading status to also be based on whether
// the tab is navigating to a new document.
this.toggleAttribute('loading', tab.status === STATUS.LOADING);
this.toggleAttribute('pinned', tab.pinned); this.toggleAttribute('pinned', tab.pinned);
if (!this.tab_ || this.tab_.title !== tab.title) { if (!this.tab_ || this.tab_.title !== tab.title) {
this.titleTextEl_.textContent = tab.title; this.titleTextEl_.textContent = tab.title;
} }
if (tab.favIconUrl && if (tab.favIconUrl) {
(!this.tab_ || this.tab_.favIconUrl !== tab.favIconUrl)) { if (!this.tab_ || this.tab_.favIconUrl !== tab.favIconUrl) {
this.faviconEl_.style.backgroundImage = getFavicon(tab.favIconUrl); this.faviconEl_.style.backgroundImage = getFavicon(tab.favIconUrl);
} else if (!this.tab_ || this.tab_.url !== tab.url) { }
this.faviconEl_.style.backgroundImage = } else {
getFaviconForPageURL(tab.url, false); if (tab.status === STATUS.COMPLETE) {
// If the tab has finished loading and there still is no favicon,
// fallback to a favicon generated by the page URL. This guarantees
// there is always some favicon, even if it's the default icon.
this.faviconEl_.style.backgroundImage =
getFaviconForPageURL(tab.url, false);
} else {
this.faviconEl_.style.backgroundImage = 'none';
}
} }
// Expose the ID to an attribute to allow easy querySelector use // Expose the ID to an attribute to allow easy querySelector use
......
...@@ -369,6 +369,15 @@ class TabListElement extends CustomElement { ...@@ -369,6 +369,15 @@ class TabListElement extends CustomElement {
const tabElement = this.findTabElement_(tabId); const tabElement = this.findTabElement_(tabId);
if (tabElement) { if (tabElement) {
// While a tab may go in and out of a loading state, the Extensions API
// only dispatches |onUpdated| events up until the first time a tab
// reaches a non-loading state. Therefore, the UI should ignore any
// updates to a |status| of a tab unless the API specifically has
// dispatched an event indicating the status has changed.
if (!changeInfo.status) {
tab.status = tabElement.tab.status;
}
tabElement.tab = tab; tabElement.tab = tab;
if (changeInfo.pinned !== undefined) { if (changeInfo.pinned !== undefined) {
......
...@@ -14,6 +14,7 @@ suite('Tab', function() { ...@@ -14,6 +14,7 @@ suite('Tab', function() {
const tab = { const tab = {
id: 1001, id: 1001,
status: 'complete',
title: 'My title', title: 'My title',
}; };
...@@ -63,6 +64,13 @@ suite('Tab', function() { ...@@ -63,6 +64,13 @@ suite('Tab', function() {
assertFalse(tabElement.hasAttribute('pinned')); assertFalse(tabElement.hasAttribute('pinned'));
}); });
test('toggles a [loading] attribute when loading', () => {
tabElement.tab = Object.assign({}, tab, {status: 'loading'});
assertTrue(tabElement.hasAttribute('loading'));
tabElement.tab = Object.assign({}, tab, {status: 'complete'});
assertFalse(tabElement.hasAttribute('loading'));
});
test('clicking on the element activates the tab', () => { test('clicking on the element activates the tab', () => {
tabElement.click(); tabElement.click();
return testTabsApiProxy.whenCalled('activateTab', tabId => { return testTabsApiProxy.whenCalled('activateTab', tabId => {
...@@ -104,6 +112,15 @@ suite('Tab', function() { ...@@ -104,6 +112,15 @@ suite('Tab', function() {
getFaviconForPageURL(expectedPageUrl, false)); getFaviconForPageURL(expectedPageUrl, false));
}); });
test(
'removes the favicon if the tab is loading and there is no favicon URL',
() => {
delete tab.favIconUrl;
tabElement.tab = Object.assign({}, tab, {status: 'loading'});
const faviconElement = tabElement.shadowRoot.querySelector('#favicon');
assertEquals(faviconElement.style.backgroundImage, 'none');
});
test('hides the thumbnail if there is no source yet', () => { test('hides the thumbnail if there is no source yet', () => {
const thumbnailImage = tabElement.shadowRoot.querySelector('#thumbnailImg'); const thumbnailImage = tabElement.shadowRoot.querySelector('#thumbnailImg');
assertFalse(thumbnailImage.hasAttribute('src')); assertFalse(thumbnailImage.hasAttribute('src'));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment