Index: chrome/browser/resources/local_ntp/local_ntp.js |
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js |
index 9aaa9a814bb1b7410f2e92e39f7db5a0e03bb55f..ba193ba55287e85565d6e8173d7a56cd1e58b854 100644 |
--- a/chrome/browser/resources/local_ntp/local_ntp.js |
+++ b/chrome/browser/resources/local_ntp/local_ntp.js |
@@ -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', |
@@ -169,6 +176,13 @@ 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. |
* @type {number} |
@@ -266,17 +280,21 @@ 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; |
/** @type {Element|undefined} */ |
@@ -288,13 +306,33 @@ 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; |