Commit 004f26fd authored by huangs@chromium.org's avatar huangs@chromium.org

[Local NTP] Implementing Material Design styling

We also want the "classical" NTP to coexist with the new design, and
switchable via Finch. Key changes:
- Adding classes to #ntp-content, then using CSS to control styling.
  - .classical for old NTP
  - .md for Material Design
  - .default-theme for values that will be overriden by themes. This is
    needed so dynamic CSS changes in setCustomThemeStyle() are not
    subsumed by specialized .classical and .md.
  - .dark: for dark background.
- The .mv-mask <div> is promoted to handle tile effects, including
  border, shadow, and background. Borders are drawn differently now.
  This lead to fix of 1px offset of .mv-title .

Some new designs are still in flux. Our goal is to get a reasonable bulk
committed for m38, then worry about some tune-ups later.

TEST=Run "chrome.exe --force-fieldtrials=MaterialDesignNTP/Enabled/",
visit chrome-search://local-ntp/local-ntp.html . If chrome.exe is run
without the switch, the local NTP should be identical to before, except
for 1px .mv-title shift.

BUG=400332

Review URL: https://codereview.chromium.org/473583002

Cr-Commit-Position: refs/heads/master@{#289682}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@289682 0039d316-1c4b-4281-b951-d872f2087c98
parent cf44ce93
......@@ -17,7 +17,7 @@ body {
.non-google-page #ntp-contents {
position: absolute;
top: -webkit-calc(50% - 155px);
top: calc(50% - 155px);
width: 100%;
}
......@@ -57,10 +57,10 @@ body.alternate-logo #logo {
border: 1px solid rgb(185, 185, 185);
border-radius: 1px;
border-top-color: rgb(160, 160, 160);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
cursor: text;
font-size: 18px;
height: 36px;
line-height: 36px;
max-width: 620px;
position: relative;
/* #fakebox width (here and below) should be 2px less than #mv-tiles
......@@ -73,6 +73,10 @@ body.alternate-logo #logo {
border-top-color: rgb(144, 144, 144);
}
.classical #fakebox {
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}
body.fakebox-focused #fakebox {
border: 1px solid rgb(77, 144, 254);
}
......@@ -89,12 +93,29 @@ body.fakebox-focused #fakebox {
width: 100%;
}
body.rtl #fakebox > input {
body[dir=rtl] #fakebox > input {
padding-left: 0;
padding-right: 8px;
right: 0;
}
#fakebox-text {
color: #bbb;
font-family: arial, sans-serif;
font-size: 16px;
height: 16px;
left: 9px;
margin-top: 1px;
position: absolute;
vertical-align: middle;
visibility: hidden;
}
body[dir=rtl] #fakebox-text {
left: auto;
right: 9px;
}
#cursor {
background: #333;
bottom: 5px;
......@@ -105,7 +126,7 @@ body.rtl #fakebox > input {
width: 1px;
}
body.rtl #cursor {
body[dir=rtl] #cursor {
left: auto;
right: 9px;
}
......@@ -119,6 +140,11 @@ body.rtl #cursor {
}
}
body.fakebox-drag-focused #fakebox-text,
body.fakebox-focused #fakebox-text {
visibility: inherit;
}
body.fakebox-drag-focused #cursor {
visibility: inherit;
}
......@@ -130,80 +156,276 @@ body.fakebox-focused #cursor {
#most-visited {
-webkit-user-select: none;
margin-top: 51px;
text-align: -webkit-center;
}
.classical #most-visited {
margin-top: 51px;
}
.md #most-visited {
margin-top: 50px;
}
#mv-tiles {
height: calc(2 * 138px);
line-height: 138px;
margin: 0;
position: relative;
text-align: left;
}
body[dir=rtl] #mv-tiles {
text-align: right;
}
.classical #mv-tiles {
height: calc(2 * 138px);
line-height: 138px;
}
.md #mv-tiles {
height: calc(2 * 126px);
line-height: 126px;
}
.mv-tile {
-webkit-transition-duration: 200ms;
-webkit-transition-property: -webkit-transform, margin, opacity, width;
background: -webkit-linear-gradient(#f2f2f2, #e8e8e8);
border: 1px solid transparent;
display: inline-block;
position: relative;
vertical-align: top;
}
.mv-page-ready {
cursor: pointer;
outline: none;
}
.classical .mv-tile {
background: linear-gradient(#f2f2f2, #e8e8e8);
border-radius: 3px;
box-shadow: inset 0 2px 3px rgba(0, 0, 0, .09);
display: inline-block;
height: 83px;
height: 85px;
margin-left: 10px;
margin-right: 10px; /* Total horizontal margins add to TILE_MARGIN. */
position: relative;
vertical-align: top;
width: 138px;
width: 140px;
}
.classical .mv-page-ready {
-webkit-transition-duration: 200ms;
-webkit-transition-property: -webkit-transform, margin, opacity, width;
}
.md .mv-tile {
background: #f2f2f2;
border-radius: 1px;
height: 114px;
margin-left: 6px;
margin-right: 6px;
width: 146px;
}
.md .mv-page-ready {
-webkit-transition-duration: 200ms;
-webkit-transition-property: -webkit-transform, margin, opacity, width;
}
.md.dark .mv-tile {
background: #333;
}
.mv-tile-inner {
visibility: hidden;
}
.mv-page-ready .mv-tile-inner {
visibility: visible;
}
/* Class applied to tiles to trigger the blacklist animation. */
.mv-tile.mv-blacklist {
-webkit-transform: scale(0.5);
opacity: 0;
}
.mv-page-ready {
border: 1px solid #c0c0c0;
cursor: pointer;
outline: none;
.classical .mv-tile.mv-blacklist {
-webkit-transform: scale(0.5);
}
.mv-page-ready:hover,
.mv-page-ready:focus {
border-color: #7f7f7f
.md .mv-tile.mv-blacklist {
-webkit-transform: scale(0, 0);
-webkit-transform-origin: 0 41px;
margin-left: 0;
margin-right: 0;
width: 0;
}
.mv-thumb,
/* .mv-mask is used to alter tile appearance, including borders, shadows, */
/* and backgrounds. */
.mv-mask {
border: none;
background: transparent;
border-style: solid;
border-width: 1px;
cursor: pointer;
height: 83px;
left: 0;
pointer-events: none;
position: absolute;
top: 0;
width: 138px;
}
.classical .mv-mask {
box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.09);
}
.md .mv-mask {
border-color: transparent;
border-radius: 2px;
height: 112px;
width: 144px;
}
/* Styling border. */
.classical .mv-page-ready .mv-mask {
border-style: solid;
}
.default-theme.classical .mv-page-ready .mv-mask {
border-color: #c0c0c0;
}
.default-theme.classical .mv-page-ready:hover .mv-mask,
.default-theme.classical .mv-page-ready:focus .mv-mask {
border-color: #7f7f7f;
}
.default-theme.md.old-hover .mv-page-ready:hover .mv-mask,
.default-theme.md.old-hover .mv-page-ready:focus .mv-mask {
border-color: #999;
}
.default-theme.md.dark .mv-page-ready:hover .mv-mask,
.default-theme.md.dark .mv-page-ready:focus .mv-mask,
.default-theme.md.old-hover.dark .mv-page-ready:hover .mv-mask {
border-color: #888;
}
/* Styling shadow. */
.md .mv-page-ready .mv-mask {
-webkit-transition: box-shadow 200ms, border 200ms;
}
.default-theme.md .mv-page-ready:hover .mv-mask {
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.default-theme..md.dark .mv-page-ready:hover .mv-mask,
.default-theme..md.old-hover .mv-page-ready:hover .mv-mask {
box-shadow: none;
}
/* Styling background. */
.classical .mv-page:focus .mv-mask {
-webkit-transition: background-color 100ms ease-in-out;
background: linear-gradient(rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0) 80%, rgba(255, 255, 255, 0.9));
background-color: rgba(0, 0, 0, 0.35);
}
.md .mv-page:focus .mv-mask {
-webkit-transition: box-shadow 200ms, border 200ms,
background-color 100ms ease-in-out, ;
background: rgba(0, 0, 0, 0.3);
border-color: rgba(0, 0, 0, 0.3);
}
.mv-title {
border: none;
bottom: -28px;
position: absolute;
}
.classical .mv-title {
bottom: -27px;
height: 18px;
left: 0;
position: absolute;
width: 140px;
}
.mv-mask {
opacity: 0.35;
pointer-events: none;
.md .mv-title {
bottom: auto;
height: 15px;
left: 28px;
top: 7px;
width: 112px;
}
.mv-page:focus .mv-mask {
-webkit-transition: background-color 100ms ease-in-out;
background: -webkit-linear-gradient(rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0) 80%, rgba(255, 255, 255, 0.9));
background-color: black;
@media (-webkit-min-device-pixel-ratio: 2) {
.md .mv-title {
top: 6px;
}
}
body[dir=rtl] .md .mv-title {
left: auto;
right: 28px;
}
.mv-thumb {
border: none;
cursor: pointer;
position: absolute;
}
.classical .mv-thumb,
.classical .mv-mask {
height: 83px;
width: 138px;
}
.classical .mv-thumb {
border-radius: 2px;
left: 1px;
top: 1px;
}
.classical .mv-mask {
border-radius: 3px;
left: 0;
top: 0;
}
.md .mv-thumb,
.md .mv-thumb-fallback {
border-radius: 0;
height: 82px;
left: 4px;
top: 28px;
width: 138px;
}
body[dir=rtl] .md .mv-thumb,
body[dir=rtl] .md .mv-thumb-fallback {
left: auto;
right: 4px;
}
.md .mv-thumb-fallback {
background: #fff;
padding: none;
position: absolute;
}
.md.dark .mv-thumb-fallback {
background: #555;
}
.md .mv-thumb-fallback .dot {
background: #f2f2f2;
border-radius: 16px;
display: block;
height: 32px;
left: 50%;
margin-left: -16px;
margin-top: -16px;
position: absolute;
top: 50%;
width: 32px;
}
.md.dark .mv-thumb-fallback .dot {
background: #333;
}
.mv-x-hide .mv-x {
......@@ -212,35 +434,58 @@ body.fakebox-focused #cursor {
/* An X button to blacklist a tile or hide the notification. */
.mv-x {
background: transparent url(images/close_2.png);
background-color: transparent;
background-image: url(images/close_2.png);
border: none;
cursor: default;
height: 16px;
width: 16px;
}
.mv-page .mv-x {
-webkit-transition: opacity 150ms;
opacity: 0;
position: absolute;
}
.mv-x:hover,
#mv-notice-x:focus {
background: transparent url(images/close_2_hover.png);
background-image: url(images/close_2_hover.png);
}
.mv-x:active {
background: transparent url(images/close_2_active.png);
background-image: url(images/close_2_active.png);
}
.mv-page .mv-x {
-webkit-transition: opacity 150ms;
opacity: 0;
position: absolute;
.classical .mv-page .mv-x {
right: 2px;
top: 2px;
}
body.rtl .mv-page .mv-x {
.md .mv-x {
background-color: rgba(187,187,187,0.8);
border-radius: 8px;
}
.md.dark .mv-x {
background-color: rgba(119,119,119,0.8);
}
.md .mv-page .mv-x {
right: 4px;
top: 5px;
}
body[dir=rtl] .classical .mv-page .mv-x {
left: 2px;
right: auto;
}
body[dir=rtl] .md .mv-page .mv-x {
left: 4px;
right: auto;
}
.mv-page-ready:hover .mv-x {
-webkit-transition-delay: 500ms;
opacity: 1;
......@@ -252,14 +497,28 @@ body.rtl .mv-page .mv-x {
.mv-favicon {
background-size: 16px;
bottom: -8px;
height: 16px;
left: 61px;
pointer-events: none;
position: absolute;
width: 16px;
}
.classical .mv-favicon {
bottom: -7px;
left: 62px;
}
.md .mv-favicon {
left: 6px;
top: 6px;
}
body[dir=rtl] .md .mv-favicon {
left: auto;
right: 6px;
top: 6px;
}
/* The notification shown when a tile is blacklisted. */
#mv-notice {
font-size: 12px;
......@@ -287,6 +546,10 @@ body.rtl .mv-page .mv-x {
text-decoration: underline;
}
.default-theme.dark #mv-msg {
color: #fff;
}
#mv-notice-links .mv-x {
-webkit-margin-start: 8px;
outline: none;
......@@ -316,7 +579,7 @@ body.rtl .mv-page .mv-x {
z-index: -1;
}
body.rtl #attribution {
body[dir=rtl] #attribution {
text-align: right;
}
......@@ -335,7 +598,7 @@ body.rtl #attribution {
right: 8px;
}
body.rtl #attribution,body.rtl #recent-tabs {
body[dir=rtl] #attribution,body[dir=rtl] #recent-tabs {
left: 8px;
right: auto;
}
......@@ -28,12 +28,16 @@ var CLASSES = {
ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
BLACKLIST_BUTTON: 'mv-x',
DARK: 'dark',
DEFAULT_THEME: 'default-theme',
DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
DOT: 'dot',
FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
// Applies drag focus style to the fakebox
FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
FAVICON: 'mv-favicon',
FAVICON_FALLBACK: 'mv-favicon-fallback',
HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
HIDE_NOTIFICATION: 'mv-notice-hide',
......@@ -43,8 +47,10 @@ var CLASSES = {
PAGE_READY: 'mv-page-ready', // page tile when ready
RTL: 'rtl', // Right-to-left language text.
THUMBNAIL: 'mv-thumb',
THUMBNAIL_FALLBACK: 'mv-thumb-fallback',
THUMBNAIL_MASK: 'mv-mask',
TILE: 'mv-tile',
TILE_INNER: 'mv-tile-inner',
TITLE: 'mv-title'
};
......@@ -60,6 +66,7 @@ var IDS = {
CUSTOM_THEME_STYLE: 'ct-style',
FAKEBOX: 'fakebox',
FAKEBOX_INPUT: 'fakebox-input',
FAKEBOX_TEXT: 'fakebox-text',
LOGO: 'logo',
NOTIFICATION: 'mv-notice',
NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
......@@ -168,6 +175,13 @@ var lastBlacklistedTile = null;
var isBlacklisting = false;
/**
* Stores whether the current theme has a dark background.
* @type {boolean}
*/
var isBackgroundDark = false;
/**
* Current number of tiles columns shown based on the window width, including
* those that just contain filler.
......@@ -266,16 +280,20 @@ var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500;
* pad out the section when not enough pages exist.
*
* @param {Element} elem The element for rendering the tile.
* @param {Element=} opt_innerElem The element for contents of tile.
* @param {Element=} opt_titleElem The element for rendering the title.
* @param {Element=} opt_thumbnailElem The element for rendering the thumbnail.
* @param {number=} opt_rid The RID for the corresponding Most Visited page.
* Should only be left unspecified when creating a filler tile.
* @constructor
*/
function Tile(elem, opt_titleElem, opt_thumbnailElem, opt_rid) {
function Tile(elem, opt_innerElem, opt_titleElem, opt_thumbnailElem, opt_rid) {
/** @type {Element} */
this.elem = elem;
/** @type {Element|undefined} */
this.innerElem = opt_innerElem;
/** @type {Element|undefined} */
this.titleElem = opt_titleElem;
......@@ -287,14 +305,34 @@ function Tile(elem, opt_titleElem, opt_thumbnailElem, opt_rid) {
}
/**
* Determines whether a theme should be considered to have dark background.
* @param {ThemeBackgroundInfo} info Theme background information.
* @return {boolean} Whether the theme has dark background.
* @private
*/
function getIsBackgroundDark(info) {
if (info.imageUrl)
return true;
var rgba = info.backgroundColorRgba;
var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2];
return luminance < 128;
}
/**
* Updates the NTP based on the current theme.
* @private
*/
function onThemeChange() {
function renderTheme() {
var info = ntpApiHandle.themeBackgroundInfo;
if (!info)
if (!info) {
isBackgroundDark = false;
return;
}
isBackgroundDark = getIsBackgroundDark(info);
ntpContents.classList.toggle(CLASSES.DARK, isBackgroundDark);
var background = [convertToRGBAColor(info.backgroundColorRgba),
info.imageUrl,
......@@ -305,7 +343,15 @@ function onThemeChange() {
document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
updateThemeAttribution(info.attributionUrl);
setCustomThemeStyle(info);
}
/**
* Updates the NTP based on the current theme, then rerenders all tiles.
* @private
*/
function onThemeChange() {
renderTheme();
tilesContainer.innerHTML = '';
renderAndShowTiles();
}
......@@ -322,6 +368,7 @@ function setCustomThemeStyle(opt_themeInfo) {
var head = document.head;
if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
ntpContents.classList.remove(CLASSES.DEFAULT_THEME);
var themeStyle =
'#attribution {' +
' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
......@@ -336,11 +383,11 @@ function setCustomThemeStyle(opt_themeInfo) {
' -webkit-filter: drop-shadow(0 0 0 ' +
convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
'}' +
'.mv-page-ready {' +
'.mv-page-ready .mv-mask {' +
' border: 1px solid ' +
convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
'}' +
'.mv-page-ready:hover, .mv-page-ready:focus {' +
'.mv-page-ready:hover .mv-mask, .mv-page-ready:focus .mv-mask {' +
' border-color: ' +
convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
'}';
......@@ -355,8 +402,10 @@ function setCustomThemeStyle(opt_themeInfo) {
head.appendChild(customStyleElement);
}
} else if (customStyleElement) {
head.removeChild(customStyleElement);
} else {
ntpContents.classList.add(CLASSES.DEFAULT_THEME);
if (customStyleElement)
head.removeChild(customStyleElement);
}
}
......@@ -412,10 +461,10 @@ function convertToRGBAColor(color) {
function onMostVisitedChange() {
if (isBlacklisting) {
// Trigger the blacklist animation, which then triggers reloadAllTiles().
var lastBlacklistedTileElement = lastBlacklistedTile.elem;
lastBlacklistedTileElement.addEventListener(
var lastBlacklistedTileElem = lastBlacklistedTile.elem;
lastBlacklistedTileElem.addEventListener(
'webkitTransitionEnd', blacklistAnimationDone);
lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST);
lastBlacklistedTileElem.classList.add(CLASSES.BLACKLIST);
} else {
reloadAllTiles();
}
......@@ -457,22 +506,19 @@ function reloadAllTiles() {
/**
* Binds onload events for a tile's internal <iframe> elements.
* @param {Tile} tile The main tile to bind events to.
* @param {Barrier} tileVisibilityBarrier A barrier to make tile visible the
* moment all tiles are loaded.
* @param {Barrier} tileVisibilityBarrier A barrier to make all tiles visible
* the moment all tiles are loaded.
*/
function bindTileOnloadEvents(tile, tileVisibilityBarrier) {
if (tile.titleElem) {
tileVisibilityBarrier.add();
tile.titleElem.onload = function() {
tile.titleElem.hidden = false;
tileVisibilityBarrier.remove();
};
}
if (tile.thumbnailElem) {
tileVisibilityBarrier.add();
tile.thumbnailElem.onload = function() {
tile.thumbnailElem.hidden = false;
tile.elem.classList.add(CLASSES.PAGE_READY);
tileVisibilityBarrier.remove();
};
......@@ -494,18 +540,21 @@ function renderAndShowTiles() {
// If we need to render new tiles, manage the visibility to hide intermediate
// load states of the <iframe>s.
if (numExisting < numDesired) {
var tileVisibilityBarrier = new Barrier(function() {
tilesContainer.style.visibility = 'visible';
});
var showAll = function() {
for (var i = 0; i < numDesired; ++i) {
if (tiles[i].titleElem || tiles[i].thumbnailElem)
tiles[i].elem.classList.add(CLASSES.PAGE_READY);
}
};
var tileVisibilityBarrier = new Barrier(showAll);
if (!userInitiatedMostVisitedChange) {
// Make titleContainer invisible, but still taking up space.
// titleContainer becomes visible again (1) on timeout, or (2) when all
// tiles finish loading (using tileVisibilityBarrier).
tilesContainer.style.visibility = 'hidden';
window.setTimeout(function() {
tileVisibilityBarrier.cancel();
tilesContainer.style.visibility = 'visible';
showAll();
}, MOST_VISITED_PAINT_TIMEOUT_MSEC);
}
userInitiatedMostVisitedChange = false;
......@@ -516,8 +565,8 @@ function renderAndShowTiles() {
}
}
// Show only the desired tiles. Not using .hidden because it does not work for
// inline-block elements.
// Show only the desired tiles. Note that .hidden does not work for
// inline-block elements like tiles[i].elem.
for (var i = 0; i < numDesired; ++i)
tiles[i].elem.style.display = 'inline-block';
// If |numDesired| < |numExisting| then hide extra tiles (e.g., this occurs
......@@ -536,11 +585,13 @@ function renderAndShowTiles() {
function getMostVisitedTitleIframeUrl(rid, position) {
var url = 'chrome-search://most-visited/' +
encodeURIComponent(MOST_VISITED_TITLE_IFRAME);
var titleColor = isBackgroundDark ? NTP_DESIGN.titleColorAgainstDark :
NTP_DESIGN.titleColor;
var params = [
'rid=' + encodeURIComponent(rid),
'f=' + encodeURIComponent(NTP_DESIGN.fontFamily),
'fs=' + encodeURIComponent(NTP_DESIGN.fontSize),
'c=' + encodeURIComponent(NTP_DESIGN.titleColor),
'c=' + encodeURIComponent(titleColor),
'pos=' + encodeURIComponent(position)];
if (NTP_DESIGN.titleTextAlign)
params.push('ta=' + encodeURIComponent(NTP_DESIGN.titleTextAlign));
......@@ -565,6 +616,8 @@ function getMostVisitedThumbnailIframeUrl(rid, position) {
'fs=' + encodeURIComponent(NTP_DESIGN.fontSize),
'c=' + encodeURIComponent(NTP_DESIGN.thumbnailTextColor),
'pos=' + encodeURIComponent(position)];
if (NTP_DESIGN.thumbnailFallback)
params.push('etfb=1');
return url + '?' + params.join('&');
}
......@@ -577,12 +630,13 @@ function getMostVisitedThumbnailIframeUrl(rid, position) {
* @return {Tile} The new Tile.
*/
function createTile(page, position) {
var tileElement = document.createElement('div');
tileElement.classList.add(CLASSES.TILE);
var tileElem = document.createElement('div');
tileElem.classList.add(CLASSES.TILE);
var innerElem = createAndAppendElement(tileElem, 'div', CLASSES.TILE_INNER);
if (page) {
var rid = page.rid;
tileElement.classList.add(CLASSES.PAGE);
tileElem.classList.add(CLASSES.PAGE);
var navigateFunction = function(e) {
e.preventDefault();
......@@ -590,15 +644,15 @@ function createTile(page, position) {
};
// The click handler for navigating to the page identified by the RID.
tileElement.addEventListener('click', navigateFunction);
tileElem.addEventListener('click', navigateFunction);
// Make thumbnails tab-accessible.
tileElement.setAttribute('tabindex', '1');
registerKeyHandler(tileElement, KEYCODE.ENTER, navigateFunction);
tileElem.setAttribute('tabindex', '1');
registerKeyHandler(tileElem, KEYCODE.ENTER, navigateFunction);
// The iframe which renders the page title.
var titleElement = document.createElement('iframe');
titleElement.tabIndex = '-1';
var titleElem = document.createElement('iframe');
titleElem.tabIndex = '-1';
// Why iframes have IDs:
//
......@@ -615,45 +669,53 @@ function createTile(page, position) {
// TODO(jered): Find and fix the root (probably Blink) bug.
// Keep this ID here. See comment above.
titleElement.id = 'title-' + rid;
titleElement.className = CLASSES.TITLE;
titleElement.hidden = true;
titleElement.src = getMostVisitedTitleIframeUrl(rid, position);
tileElement.appendChild(titleElement);
titleElem.id = 'title-' + rid;
titleElem.className = CLASSES.TITLE;
titleElem.src = getMostVisitedTitleIframeUrl(rid, position);
innerElem.appendChild(titleElem);
// A fallback element for missing thumbnails.
if (NTP_DESIGN.thumbnailFallback) {
var fallbackElem = createAndAppendElement(
innerElem, 'div', CLASSES.THUMBNAIL_FALLBACK);
if (NTP_DESIGN.thumbnailFallback === THUMBNAIL_FALLBACK.DOT)
createAndAppendElement(fallbackElem, 'div', CLASSES.DOT);
}
// The iframe which renders either a thumbnail or domain element.
var thumbnailElement = document.createElement('iframe');
thumbnailElement.tabIndex = '-1';
var thumbnailElem = document.createElement('iframe');
thumbnailElem.tabIndex = '-1';
// Keep this ID here. See comment above.
thumbnailElement.id = 'thumb-' + rid;
thumbnailElement.className = CLASSES.THUMBNAIL;
thumbnailElement.hidden = true;
thumbnailElement.src = getMostVisitedThumbnailIframeUrl(rid, position);
tileElement.appendChild(thumbnailElement);
// A mask to darken the thumbnail on focus.
var maskElement = createAndAppendElement(
tileElement, 'div', CLASSES.THUMBNAIL_MASK);
thumbnailElem.id = 'thumb-' + rid;
thumbnailElem.className = CLASSES.THUMBNAIL;
thumbnailElem.src = getMostVisitedThumbnailIframeUrl(rid, position);
innerElem.appendChild(thumbnailElem);
// The button used to blacklist this page.
var blacklistButton = createAndAppendElement(
tileElement, 'div', CLASSES.BLACKLIST_BUTTON);
innerElem, 'div', CLASSES.BLACKLIST_BUTTON);
var blacklistFunction = generateBlacklistFunction(rid);
blacklistButton.addEventListener('click', blacklistFunction);
blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip;
// A helper mask on top of the tile that is used to create hover border
// and/or to darken the thumbnail on focus.
var maskElement = createAndAppendElement(
innerElem, 'div', CLASSES.THUMBNAIL_MASK);
// When a tile is focused, have delete also blacklist the page.
registerKeyHandler(tileElement, KEYCODE.DELETE, blacklistFunction);
registerKeyHandler(tileElem, KEYCODE.DELETE, blacklistFunction);
// The page favicon, if any.
var faviconUrl = page.faviconUrl;
if (faviconUrl) {
var favicon = createAndAppendElement(tileElement, 'div', CLASSES.FAVICON);
favicon.style.backgroundImage = 'url(' + faviconUrl + ')';
// The page favicon, or a fallback.
var favicon = createAndAppendElement(innerElem, 'div', CLASSES.FAVICON);
if (page.faviconUrl) {
favicon.style.backgroundImage = 'url(' + page.faviconUrl + ')';
} else {
favicon.classList.add(CLASSES.FAVICON_FALLBACK);
}
return new Tile(tileElement, titleElement, thumbnailElement, rid);
return new Tile(tileElem, innerElem, titleElem, thumbnailElem, rid);
} else {
return new Tile(tileElement);
return new Tile(tileElem);
}
}
......@@ -747,8 +809,7 @@ function onResize() {
var tilesContainerWidth = numColumnsShown * tileRequiredWidth;
tilesContainer.style.width = tilesContainerWidth + 'px';
if (fakebox) {
// -2 to account for border.
fakebox.style.width =
fakebox.style.width = // -2 to account for border.
(tilesContainerWidth - NTP_DESIGN.tileMargin - 2) + 'px';
}
// Render without clearing tiles.
......@@ -936,10 +997,15 @@ function init() {
fakebox = document.createElement('div');
fakebox.id = IDS.FAKEBOX;
fakebox.innerHTML =
'<input id="' + IDS.FAKEBOX_INPUT +
'" autocomplete="off" tabindex="-1" aria-hidden="true">' +
'<div id="cursor"></div>';
var fakeboxHtml = [];
fakeboxHtml.push('<input id="' + IDS.FAKEBOX_INPUT +
'" autocomplete="off" tabindex="-1" aria-hidden="true">');
if (NTP_DESIGN.showFakeboxHint && configData.searchboxPlaceholder) {
fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '">' +
configData.searchboxPlaceholder + '</div>');
}
fakeboxHtml.push('<div id="cursor"></div>');
fakebox.innerHTML = fakeboxHtml.join('');
ntpContents.insertBefore(fakebox, ntpContents.firstChild);
ntpContents.insertBefore(logo, ntpContents.firstChild);
......@@ -983,7 +1049,7 @@ function init() {
if (ntpApiHandle.isInputInProgress)
onInputStart();
onThemeChange();
renderTheme();
onMostVisitedChange();
searchboxApiHandle = topLevelHandle.searchBox;
......
......@@ -4,26 +4,33 @@
/**
* @fileoverview Specifications for the NTP design, and an accessor to presets.
* @fileoverview Specifications for NTP design, and an accessor to presets.
*/
var THUMBNAIL_FALLBACK = {
DOT: 'dot' // Draw single dot.
};
/**
* Specifications for an NTP design (not comprehensive).
*
* name: A unique identifier for the style.
* appropriate CSS will take effect.
* fontFamily: Font family to use for title and thumbnail <iframe>s.
* fontSize: Font size to use for the <iframe>s, in px.
* tileWidth: The width of each suggestion tile, in px.
* tileMargin: Spacing between successive tiles, in px.
* titleColor: The RRGGBB color of title text.
* titleColorAgainstDark: The RRGGBB color of title text against a dark theme.
* titleTextAlign: (Optional) The alignment of title text. If unspecified, the
* default value is 'center'.
* titleTextFade: (Optional) The number of pixels beyond which title
* text begins to fade. This overrides the default ellipsis style.
* thumbnailTextColor: The RRGGBB color that thumbnail <iframe> may use to
* display text message in place of missing thumbnail.
* thumbnailFallback: (Optional) A value in THUMBNAIL_FALLBACK to specify the
* thumbnail fallback strategy. If unassigned, then the thumbnail.html
* <iframe> would handle the fallback.
* showFakeboxHint: Whether to display text in the fakebox.
*
* @typedef {{
* name: string,
......@@ -32,9 +39,12 @@
* tileWidth: number,
* tileMargin: number,
* titleColor: string,
* titleColorAgainstDark: string,
* titleTextAlign: string|null|undefined,
* titleTextFade: string|null|undefined,
* thumbnailTextColor: string
* thumbnailTextColor: string,
* thumbnailFallback: string|null|undefined
* showFakeboxHint: string|null|undefined
* }}
*/
var NtpDesign;
......@@ -46,16 +56,38 @@ var NtpDesign;
* @return {NtpDesign} The NTP design corresponding to name.
*/
function getNtpDesign(opt_name) {
// TODO(huangs): Add new style.
return {
name: 'classical',
fontFamily: 'arial, sans-serif',
fontSize: 11,
tileWidth: 140,
tileMargin: 20,
titleColor: '777777',
// No titleTextAlign: defaults to 'center'.
// No titleTextFade: by default we have ellipsis.
thumbnailTextColor: '777777'
};
var ntpDesign = null;
if (opt_name === 'md') {
ntpDesign = {
name: opt_name,
fontFamily: 'arial, sans-serif',
fontSize: 12,
tileWidth: 146,
tileMargin: 12,
titleColor: '000000',
titleColorAgainstDark: 'd2d2d2',
titleTextAlign: 'inherit',
titleTextFade: 112 - 24, // 112px wide title with 24 pixel fade at end.
thumbnailTextColor: '777777',
thumbnailFallback: THUMBNAIL_FALLBACK.DOT,
showFakeboxHint: true
};
} else {
ntpDesign = {
name: 'classical',
fontFamily: 'arial, sans-serif',
fontSize: 11,
tileWidth: 140,
tileMargin: 20,
titleColor: '777777',
titleColorAgainstDark: '777777',
titleTextAlign: 'center',
titleTextFade: null, // Default to ellipsis.
thumbnailTextColor: '777777',
thumbnailFallback: null, // Default to false.
showFakeboxHint: false
};
}
return ntpDesign;
}
......@@ -23,14 +23,7 @@ div {
width: 90%;
}
img,
.shadow {
border-radius: 2px;
img {
height: 100%;
width: 100%;
}
.shadow {
box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.09);
position: absolute;
}
\ No newline at end of file
......@@ -42,17 +42,15 @@ window.addEventListener('DOMContentLoaded', function() {
function createThumbnail(src) {
var image = document.createElement('img');
image.onload = function() {
var shadow = document.createElement('span');
shadow.className = 'shadow';
var link = createMostVisitedLink(
params, data.url, data.title, undefined, data.direction,
data.provider);
link.appendChild(shadow);
link.appendChild(image);
displayLink(link);
};
image.onerror = function() {
if (data.domain) {
// If no external thumbnail fallback (etfb), and have domain.
if (!params.etfb && data.domain) {
showDomainElement();
logEvent(NTP_LOGGING_EVENT_TYPE.NTP_GRAY_TILE_FALLBACK);
} else {
......
......@@ -156,6 +156,9 @@ function createMostVisitedLink(params, href, title, text, direction, provider) {
}
// Else follow <a> normally, so transition type would be LINK.
});
link.addEventListener('mousedown', function(e) {
e.preventDefault(); // Prevent drag-select.
});
return link;
}
......@@ -163,11 +166,11 @@ function createMostVisitedLink(params, href, title, text, direction, provider) {
/**
* Decodes most visited styles from URL parameters.
* - c: A hexadecimal number interpreted as a hex color code.
* - f: font-family.
* - fs: font-size as a number in pixels.
* - ta: text-align property, as a string.
* - tf: specifying a text fade starting position, in pixels.
* - c: A hexadecimal number interpreted as a hex color code.
* @param {Object.<string, string>} params URL parameters specifying style.
* @param {boolean} isTitle if the style is for the Most Visited Title.
* @return {Object} Styles suitable for CSS interpolation.
......
......@@ -36,7 +36,8 @@ namespace {
const char kMaterialDesignNTPFieldTrialName[] = "MaterialDesignNTP";
const char kMaterialDesignNTPFieldTrialEnabledPrefix[] = "Enabled";
// Name to be used for the new design in local resources.
// Names of NTP designs in local resources, also used in CSS.
const char kClassicalNTPName[] = "classical";
const char kMaterialDesignNTPName[] = "md";
// Signifies a locally constructed resource, i.e. not from grit/.
......@@ -188,7 +189,7 @@ void LocalNtpSource::StartDataRequest(
if (stripped_path == kLocalNTPFilename) {
SendResourceWithClass(
IDR_LOCAL_NTP_HTML,
IsMaterialDesignEnabled() ? kMaterialDesignNTPName : "",
IsMaterialDesignEnabled() ? kMaterialDesignNTPName : kClassicalNTPName,
callback);
return;
}
......
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