Index: chrome/browser/resources/local_ntp/most_visited_single.js |
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.js b/chrome/browser/resources/local_ntp/most_visited_single.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9b4a52d425a382e2a0377bdc0237679daed931b5 |
--- /dev/null |
+++ b/chrome/browser/resources/local_ntp/most_visited_single.js |
@@ -0,0 +1,310 @@ |
+/* Copyright 2015 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. */ |
+ |
+ // Single iframe for NTP tiles. |
+(function() { |
+'use strict'; |
+ |
+ |
+/** |
+ * The different types of events that are logged from the NTP. This enum is |
+ * used to transfer information from the NTP JavaScript to the renderer and is |
+ * not used as a UMA enum histogram's logged value. |
+ * Note: Keep in sync with common/ntp_logging_events.h |
+ * @enum {number} |
+ * @const |
+ */ |
+var LOG_TYPE = { |
+ // The suggestion is coming from the server. Unused here. |
+ NTP_SERVER_SIDE_SUGGESTION: 0, |
+ // The suggestion is coming from the client. |
+ NTP_CLIENT_SIDE_SUGGESTION: 1, |
+ // Indicates a tile was rendered, no matter if it's a thumbnail, a gray tile |
+ // or an external tile. |
+ NTP_TILE: 2, |
+ // The tile uses a local thumbnail image. |
+ NTP_THUMBNAIL_TILE: 3, |
+ // Used when no thumbnail is specified and a gray tile with the domain is used |
+ // as the main tile. Unused here. |
+ NTP_GRAY_TILE: 4, |
+ // The visuals of that tile are handled externally by the page itself. |
+ // Unused here. |
+ NTP_EXTERNAL_TILE: 5, |
+ // There was an error in loading both the thumbnail image and the fallback |
+ // (if it was provided), resulting in a gray tile. |
+ NTP_THUMBNAIL_ERROR: 6, |
+ // Used a gray tile with the domain as the fallback for a failed thumbnail. |
+ // Unused here. |
+ NTP_GRAY_TILE_FALLBACK: 7, |
+ // The visuals of that tile's fallback are handled externally. Unused here. |
+ NTP_EXTERNAL_TILE_FALLBACK: 8, |
+ // The user moused over an NTP tile or title. |
huangs
2015/03/13 03:49:52
Can remove "or title".
fserb
2015/03/13 17:22:44
Done.
|
+ NTP_MOUSEOVER: 9 |
+}; |
+ |
+ |
+/** |
+ * Total number of tiles to show at any time. If the host page doesn't send |
+ * enough tiles, we fill them blank. |
+ * @const {number} |
+ */ |
+var NUMBER_OF_TILES = 8; |
+ |
+ |
+/** |
+ * The origin of this request. |
+ * @const {string} |
+ */ |
+var DOMAIN_ORIGIN = '{{ORIGIN}}'; |
+ |
+ |
+/** |
+ * Counter for DOM elements that we are waiting to finish loading. |
+ * @type {number} |
+ */ |
+var loadedCounter = 1; |
+ |
+ |
+/** |
+ * DOM element containing the tiles we are going to present next. |
+ * Works as a double-buffer that is shown when we receive a "show" postMessage. |
+ * @type {Element} |
+ */ |
+var tiles = null; |
+ |
+ |
+/** |
+ * Log an event on the NTP. |
+ * @param {number} eventType Event from NTP_LOGGING_EVENT_TYPE. |
huangs
2015/03/13 03:49:52
NTP_LOGGING_EVENT_TYPE => LOG_TYPE
fserb
2015/03/13 17:22:45
Done.
|
+ */ |
+var logEvent = function(eventType) { |
+ chrome.embeddedSearch.newTabPage.logEvent(eventType); |
+}; |
+ |
+ |
+/** |
+ * Down count the DOM elements that we are waiting for the page to load. |
huangs
2015/03/13 03:49:52
NIT: "Down counts" -- if a function comment starts
fserb
2015/03/13 17:22:45
Done.
|
+ * When we get to 0, we send a message to the parent window. |
+ * This is usually used as an EventListener of onload/onerror. |
+ */ |
+var countLoad = function() { |
+ loadedCounter -= 1; |
+ if (loadedCounter <= 0) { |
+ window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN); |
+ loadedCounter = 1; |
+ } |
+}; |
+ |
+ |
+/** |
+ * Handle postMessages coming from the host page to the iframe. |
huangs
2015/03/13 03:49:52
NIT: Handles
fserb
2015/03/13 17:22:45
Done.
|
+ * We try to keep the logic here to a minimum and just dispatch to the relevant |
+ * functions. |
+ **/ |
+var handlePostMessage = function(event) { |
+ var cmd = event.data.cmd; |
+ |
+ if (cmd == 'tile') { |
+ addTile(event.data); |
+ } else if (cmd == 'show') { |
+ showTiles(); |
+ countLoad(); |
+ } else { |
+ console.error('Unknown command: ' + event.data); |
+ } |
+}; |
+ |
+ |
+/** |
+ * Called when the host page has finished sending us tile information and |
+ * we are ready to show the new tiles and drop the old ones. |
+ */ |
+var showTiles = function() { |
+ // Store the tiles on the current closure. |
+ var cur = tiles; |
+ |
+ // Create empty tiles until we have NUMBER_OF_TILES. |
+ while (cur.childNodes.length < NUMBER_OF_TILES) { |
+ addTile({}); |
+ } |
+ |
+ var parent = document.querySelector('#most-visited'); |
+ |
+ // Mark old tile DIV for removal after the transition animation is done. |
+ var old = parent.querySelector('#mv-tiles'); |
+ if (old) { |
+ old.id = 'mv-tiles-old'; |
+ cur.addEventListener('webkitTransitionEnd', function(ev) { |
+ if (ev.target === cur) { |
+ parent.removeChild(old); |
+ } |
+ }); |
+ } |
+ |
+ // Add new tileset. |
+ cur.id = 'mv-tiles'; |
+ parent.appendChild(cur); |
+ // We want the CSS transition to trigger, so need to add to the DOM before |
+ // setting the style. |
+ setTimeout(function() { |
huangs
2015/03/13 03:49:52
Would requestAnimationFrame() do the same thing?
fserb
2015/03/13 17:22:45
not the same thing.
|
+ cur.style.opacity = 1.0; |
+ }, 0); |
+ |
+ // Make sure the tiles variable contain the next tileset we may use. |
+ tiles = document.createElement('div'); |
+}; |
+ |
+ |
+/** |
+ * Called when the host page wants to add a suggestion tile. |
+ * For Most Visited, it grabs the data from Chrome and pass on. |
+ * For host page generated it just passes the data. |
+ * @param {object} args Data for the tile to be rendered. |
+ */ |
+var addTile = function(args) { |
+ if (args.rid) { |
+ var data = chrome.embeddedSearch.searchBox.getMostVisitedItemData(args.rid); |
+ tiles.appendChild(renderTile(data)); |
+ logEvent(LOG_TYPE.NTP_CLIENT_SIDE_SUGGESTION); |
+ } else { |
+ tiles.appendChild(renderTile(null)); |
huangs
2015/03/13 03:49:52
Would you need to log NTP_SERVER_SIDE_SUGGESTION ?
fserb
2015/03/13 17:22:45
When I handle it, yes.
|
+ } |
+}; |
+ |
+ |
+/** |
+ * Called when the user decided to add a tile to the blacklist. |
+ * It sets of the animation for the blacklist and sends the blacklisted id |
+ * to the host page. |
+ * @param {Element} tile DOM node of the tile we want to remove. |
+ */ |
+var blacklistTile = function(tile) { |
+ tile.classList.add('blacklisted'); |
+ var sent = false; |
+ tile.addEventListener('webkitTransitionEnd', function() { |
+ if (sent) return; |
+ sent = true; |
+ window.parent.postMessage({cmd: 'tileBlacklisted', |
+ rid: Number(tile.getAttribute('data-rid'))}, |
+ DOMAIN_ORIGIN); |
+ }); |
+}; |
+ |
+ |
+/** |
+ * Renders a MostVisited tile to the DOM. |
+ * @param {object} data Object containing rid, url, title, favicon, thumbnail. |
+ * data is null if you want to construct an empty tile. |
+ */ |
+var renderTile = function(data) { |
+ var tile = document.createElement('a'); |
+ |
+ if (data == null) { |
+ tile.className = 'mv-empty-tile'; |
+ return tile; |
+ } |
+ |
+ logEvent(LOG_TYPE.NTP_TILE); |
+ |
+ tile.className = 'mv-tile'; |
+ tile.setAttribute('data-rid', data.rid); |
+ tile.innerHTML = '<div class="mv-favicon"></div>' + |
+ '<div class="mv-title"></div><div class="mv-thumb"></div>' + |
+ '<div title="' + configData['removeThumbnailTooltip'] + |
+ '" class="mv-x"></div>'; |
+ |
+ tile.href = data.url; |
+ tile.title = data.title; |
+ tile.addEventListener('keypress', function(ev) { |
+ if (ev.keyCode == 127) { // DELETE |
+ blacklistTile(tile); |
+ ev.stopPropagation(); |
+ return false; |
+ } |
+ }); |
+ // TODO(fserb): remove this or at least change to mouseenter. |
+ tile.addEventListener('mouseover', function() { |
+ logEvent(LOG_TYPE.NTP_MOUSEOVER); |
+ }); |
+ |
+ var title = tile.querySelector('.mv-title'); |
+ title.innerText = data.title; |
+ title.style.direction = data.direction || 'ltr'; |
+ |
+ var thumb = tile.querySelectorAll('.mv-thumb')[0]; |
huangs
2015/03/13 03:49:52
querySelector
fserb
2015/03/13 17:22:44
Done.
|
+ |
huangs
2015/03/13 03:49:52
NIT: Don't need this new line.
fserb
2015/03/13 17:22:45
Done.
|
+ if (data.thumbnailUrl) { |
+ var img = document.createElement('img'); |
+ img.title = data.title; |
+ img.src = data.thumbnailUrl; |
+ loadedCounter += 1; |
+ img.addEventListener('load', countLoad); |
+ img.addEventListener('error', countLoad); |
+ img.addEventListener('error', function(ev) { |
+ thumb.classList.add('failed-img'); |
+ thumb.removeChild(img); |
+ logEvent(LOG_TYPE.NTP_THUMBNAIL_ERROR); |
+ }); |
+ thumb.appendChild(img); |
+ logEvent(LOG_TYPE.NTP_THUMBNAIL_TILE); |
+ } else { |
+ thumb.classList.add('failed-img'); |
+ } |
+ |
+ var favicon = tile.querySelectorAll('.mv-favicon')[0]; |
huangs
2015/03/13 03:49:52
querySelector
fserb
2015/03/13 17:22:45
Done.
|
+ if (data.faviconUrl) { |
+ var fi = document.createElement('img'); |
+ fi.src = '../' + data.faviconUrl; |
+ // We set the title to empty, so it doesn't say the image name on chromevox. |
huangs
2015/03/13 03:49:52
NIT: // Set title to empty so screen readers won't
fserb
2015/03/13 17:22:45
Done.
|
+ fi.title = ''; |
+ loadedCounter += 1; |
+ fi.addEventListener('load', countLoad); |
+ fi.addEventListener('error', countLoad); |
+ fi.addEventListener('error', function(ev) { |
+ favicon.classList.add('failed-favicon'); |
+ }); |
+ favicon.appendChild(fi); |
+ } else { |
+ favicon.classList.add('failed-favicon'); |
+ } |
+ |
+ var mvx = tile.querySelectorAll('.mv-x')[0]; |
huangs
2015/03/13 03:49:52
querySelector
fserb
2015/03/13 17:22:44
Done.
|
+ mvx.addEventListener('click', function(ev) { |
+ blacklistTile(tile); |
+ ev.stopPropagation(); |
+ return false; |
+ }); |
+ |
+ return tile; |
+}; |
+ |
+ |
+/** |
+ * Do some initialization and parses the query arguments passed to the iframe. |
+ */ |
+var init = function() { |
+ // Creates a new DOM element to hold the tiles. |
+ tiles = document.createElement('div'); |
+ |
+ // Parse query arguments. |
+ var query = window.location.search.substring(1).split('&'); |
+ var args = {}; |
+ for (var i = 0; i < query.length; ++i) { |
+ var val = query[i].split('='); |
+ if (val[0] == '') continue; |
+ args[decodeURIComponent(val[0])] = decodeURIComponent(val[1]); |
+ } |
+ |
+ // Enable RTL. |
+ if (args['rtl'] == '1') { |
+ var html = document.querySelector('html'); |
+ html.dir = 'rtl'; |
+ } |
+ |
+ window.addEventListener('message', handlePostMessage); |
+}; |
+ |
+ |
+window.addEventListener('DOMContentLoaded', init); |
+})(); |