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 |
index aba6f846b8be4c88f90cd88fb7985937fe93cd42..8298871290c383a5af6e568fa367d9cd286d7bb9 100644 |
--- a/chrome/browser/resources/local_ntp/most_visited_single.js |
+++ b/chrome/browser/resources/local_ntp/most_visited_single.js |
@@ -2,597 +2,612 @@ |
* 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. |
+// 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 = { |
- // All NTP Tiles have finished loading (successfully or failing). |
- NTP_ALL_TILES_LOADED: 11, |
-}; |
- |
- |
-/** |
- * The different sources that an NTP tile can have. |
- * Note: Keep in sync with components/ntp_tiles/ntp_tile_source.h |
- * @enum {number} |
- * @const |
- */ |
-var NTPTileSource = { |
- TOP_SITES: 0, |
- SUGGESTIONS_SERVICE: 1, |
- POPULAR: 3, |
- WHITELIST: 4, |
-}; |
- |
- |
-/** |
- * 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; |
- |
- |
-/** |
- * Whether to use icons instead of thumbnails. |
- * @type {boolean} |
- */ |
-var USE_ICONS = false; |
- |
- |
-/** |
- * Number of lines to display in titles. |
- * @type {number} |
- */ |
-var NUM_TITLE_LINES = 1; |
- |
- |
-/** |
- * 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; |
- |
- |
-/** |
- * List of parameters passed by query args. |
- * @type {Object} |
- */ |
-var queryArgs = {}; |
- |
- |
-/** |
- * Log an event on the NTP. |
- * @param {number} eventType Event from LOG_TYPE. |
- */ |
-var logEvent = function(eventType) { |
- chrome.embeddedSearch.newTabPage.logEvent(eventType); |
-}; |
- |
-/** |
- * Log impression of an NTP tile. |
- * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES. |
- * @param {number} tileSource The source from NTPTileSource. |
- */ |
-function logMostVisitedImpression(tileIndex, tileSource) { |
- chrome.embeddedSearch.newTabPage.logMostVisitedImpression(tileIndex, |
- tileSource); |
-} |
- |
-/** |
- * Log click on an NTP tile. |
- * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES. |
- * @param {number} tileSource The source from NTPTileSource. |
- */ |
-function logMostVisitedNavigation(tileIndex, tileSource) { |
- chrome.embeddedSearch.newTabPage.logMostVisitedNavigation(tileIndex, |
- tileSource); |
-} |
- |
-/** |
- * Down counts the DOM elements that we are waiting for the page to load. |
- * 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) { |
- showTiles(); |
- logEvent(LOG_TYPE.NTP_ALL_TILES_LOADED); |
- window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN); |
- loadedCounter = 1; |
+ '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 = { |
+ // All NTP Tiles have finished loading (successfully or failing). |
+ NTP_ALL_TILES_LOADED: 11, |
+ }; |
+ |
+ |
+ /** |
+ * The different sources that an NTP tile can have. |
+ * Note: Keep in sync with components/ntp_tiles/ntp_tile_source.h |
+ * @enum {number} |
+ * @const |
+ */ |
+ var NTPTileSource = { |
+ TOP_SITES: 0, |
+ SUGGESTIONS_SERVICE: 1, |
+ POPULAR: 3, |
+ WHITELIST: 4, |
+ }; |
+ |
+ |
+ /** |
+ * 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; |
+ |
+ |
+ /** |
+ * Whether to use icons instead of thumbnails. |
+ * @type {boolean} |
+ */ |
+ var USE_ICONS = false; |
+ |
+ |
+ /** |
+ * Number of lines to display in titles. |
+ * @type {number} |
+ */ |
+ var NUM_TITLE_LINES = 1; |
+ |
+ |
+ /** |
+ * 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; |
+ |
+ |
+ /** |
+ * List of parameters passed by query args. |
+ * @type {Object} |
+ */ |
+ var queryArgs = {}; |
+ |
+ |
+ /** |
+ * Log an event on the NTP. |
+ * @param {number} eventType Event from LOG_TYPE. |
+ */ |
+ var logEvent = function(eventType) { |
+ chrome.embeddedSearch.newTabPage.logEvent(eventType); |
+ }; |
+ |
+ /** |
+ * Log impression of an NTP tile. |
+ * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES. |
+ * @param {number} tileSource The source from NTPTileSource. |
+ */ |
+ function logMostVisitedImpression(tileIndex, tileSource) { |
+ chrome.embeddedSearch.newTabPage.logMostVisitedImpression( |
+ tileIndex, tileSource); |
} |
-}; |
+ /** |
+ * Log click on an NTP tile. |
+ * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES. |
+ * @param {number} tileSource The source from NTPTileSource. |
+ */ |
+ function logMostVisitedNavigation(tileIndex, tileSource) { |
+ chrome.embeddedSearch.newTabPage.logMostVisitedNavigation( |
+ tileIndex, tileSource); |
+ } |
-/** |
- * Handles postMessages coming from the host page to the iframe. |
- * Mostly, it dispatches every command to handleCommand. |
- */ |
-var handlePostMessage = function(event) { |
- if (event.data instanceof Array) { |
- for (var i = 0; i < event.data.length; ++i) { |
- handleCommand(event.data[i]); |
+ /** |
+ * Down counts the DOM elements that we are waiting for the page to load. |
+ * 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) { |
+ showTiles(); |
+ logEvent(LOG_TYPE.NTP_ALL_TILES_LOADED); |
+ window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN); |
+ loadedCounter = 1; |
} |
- } else { |
- handleCommand(event.data); |
- } |
-}; |
- |
- |
-/** |
- * Handles a single command coming from the host page to the iframe. |
- * We try to keep the logic here to a minimum and just dispatch to the relevant |
- * functions. |
- */ |
-var handleCommand = function(data) { |
- var cmd = data.cmd; |
- |
- if (cmd == 'tile') { |
- addTile(data); |
- } else if (cmd == 'show') { |
- countLoad(); |
- hideOverflowTiles(data); |
- } else if (cmd == 'updateTheme') { |
- updateTheme(data); |
- } else if (cmd == 'tilesVisible') { |
- hideOverflowTiles(data); |
- } else { |
- console.error('Unknown command: ' + JSON.stringify(data)); |
- } |
-}; |
+ }; |
-var updateTheme = function(info) { |
- var themeStyle = []; |
+ /** |
+ * Handles postMessages coming from the host page to the iframe. |
+ * Mostly, it dispatches every command to handleCommand. |
+ */ |
+ var handlePostMessage = function(event) { |
+ if (event.data instanceof Array) { |
+ for (var i = 0; i < event.data.length; ++i) { |
+ handleCommand(event.data[i]); |
+ } |
+ } else { |
+ handleCommand(event.data); |
+ } |
+ }; |
- if (info.tileBorderColor) { |
- themeStyle.push('.thumb-ntp .mv-tile {' + |
- 'border: 1px solid ' + info.tileBorderColor + '; }'); |
- } |
- if (info.tileHoverBorderColor) { |
- themeStyle.push('.thumb-ntp .mv-tile:hover {' + |
- 'border-color: ' + info.tileHoverBorderColor + '; }'); |
- } |
- if (info.isThemeDark) { |
- themeStyle.push('.thumb-ntp .mv-tile, .thumb-ntp .mv-empty-tile { ' + |
- 'background: rgb(51,51,51); }'); |
- themeStyle.push('.thumb-ntp .mv-thumb.failed-img { ' + |
- 'background-color: #555; }'); |
- themeStyle.push('.thumb-ntp .mv-thumb.failed-img::after { ' + |
- 'border-color: #333; }'); |
- themeStyle.push('.thumb-ntp .mv-x { ' + |
- 'background: linear-gradient(to left, ' + |
- 'rgb(51,51,51) 60%, transparent); }'); |
- themeStyle.push('html[dir=rtl] .thumb-ntp .mv-x { ' + |
- 'background: linear-gradient(to right, ' + |
- 'rgb(51,51,51) 60%, transparent); }'); |
- themeStyle.push('.thumb-ntp .mv-x::after { ' + |
- 'background-color: rgba(255,255,255,0.7); }'); |
- themeStyle.push('.thumb-ntp .mv-x:hover::after { ' + |
- 'background-color: #fff; }'); |
- themeStyle.push('.thumb-ntp .mv-x:active::after { ' + |
- 'background-color: rgba(255,255,255,0.5); }'); |
- themeStyle.push('.icon-ntp .mv-tile:focus { ' + |
- 'background: rgba(255,255,255,0.2); }'); |
- } |
- if (info.tileTitleColor) { |
- themeStyle.push('body { color: ' + info.tileTitleColor + '; }'); |
- } |
- document.querySelector('#custom-theme').textContent = themeStyle.join('\n'); |
-}; |
+ /** |
+ * Handles a single command coming from the host page to the iframe. |
+ * We try to keep the logic here to a minimum and just dispatch to the |
+ * relevant |
+ * functions. |
+ */ |
+ var handleCommand = function(data) { |
+ var cmd = data.cmd; |
+ if (cmd == 'tile') { |
+ addTile(data); |
+ } else if (cmd == 'show') { |
+ countLoad(); |
+ hideOverflowTiles(data); |
+ } else if (cmd == 'updateTheme') { |
+ updateTheme(data); |
+ } else if (cmd == 'tilesVisible') { |
+ hideOverflowTiles(data); |
+ } else { |
+ console.error('Unknown command: ' + JSON.stringify(data)); |
+ } |
+ }; |
-/** |
- * Hides extra tiles that don't fit on screen. |
- */ |
-var hideOverflowTiles = function(data) { |
- var tileAndEmptyTileList = document.querySelectorAll( |
- '#mv-tiles .mv-tile,#mv-tiles .mv-empty-tile'); |
- for (var i = 0; i < tileAndEmptyTileList.length; ++i) { |
- tileAndEmptyTileList[i].classList.toggle('hidden', i >= data.maxVisible); |
- } |
-}; |
+ var updateTheme = function(info) { |
+ var themeStyle = []; |
-/** |
- * Removes all old instances of #mv-tiles that are pending for deletion. |
- */ |
-var removeAllOldTiles = function() { |
- var parent = document.querySelector('#most-visited'); |
- var oldList = parent.querySelectorAll('.mv-tiles-old'); |
- for (var i = 0; i < oldList.length; ++i) { |
- parent.removeChild(oldList[i]); |
- } |
-}; |
+ if (info.tileBorderColor) { |
+ themeStyle.push( |
+ '.thumb-ntp .mv-tile {' + |
+ 'border: 1px solid ' + info.tileBorderColor + '; }'); |
+ } |
+ if (info.tileHoverBorderColor) { |
+ themeStyle.push( |
+ '.thumb-ntp .mv-tile:hover {' + |
+ 'border-color: ' + info.tileHoverBorderColor + '; }'); |
+ } |
+ if (info.isThemeDark) { |
+ themeStyle.push( |
+ '.thumb-ntp .mv-tile, .thumb-ntp .mv-empty-tile { ' + |
+ 'background: rgb(51,51,51); }'); |
+ themeStyle.push( |
+ '.thumb-ntp .mv-thumb.failed-img { ' + |
+ 'background-color: #555; }'); |
+ themeStyle.push( |
+ '.thumb-ntp .mv-thumb.failed-img::after { ' + |
+ 'border-color: #333; }'); |
+ themeStyle.push( |
+ '.thumb-ntp .mv-x { ' + |
+ 'background: linear-gradient(to left, ' + |
+ 'rgb(51,51,51) 60%, transparent); }'); |
+ themeStyle.push( |
+ 'html[dir=rtl] .thumb-ntp .mv-x { ' + |
+ 'background: linear-gradient(to right, ' + |
+ 'rgb(51,51,51) 60%, transparent); }'); |
+ themeStyle.push( |
+ '.thumb-ntp .mv-x::after { ' + |
+ 'background-color: rgba(255,255,255,0.7); }'); |
+ themeStyle.push( |
+ '.thumb-ntp .mv-x:hover::after { ' + |
+ 'background-color: #fff; }'); |
+ themeStyle.push( |
+ '.thumb-ntp .mv-x:active::after { ' + |
+ 'background-color: rgba(255,255,255,0.5); }'); |
+ themeStyle.push( |
+ '.icon-ntp .mv-tile:focus { ' + |
+ 'background: rgba(255,255,255,0.2); }'); |
+ } |
+ if (info.tileTitleColor) { |
+ themeStyle.push('body { color: ' + info.tileTitleColor + '; }'); |
+ } |
+ document.querySelector('#custom-theme').textContent = themeStyle.join('\n'); |
+ }; |
-/** |
- * 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({}); |
- } |
+ /** |
+ * Hides extra tiles that don't fit on screen. |
+ */ |
+ var hideOverflowTiles = function(data) { |
+ var tileAndEmptyTileList = document.querySelectorAll( |
+ '#mv-tiles .mv-tile,#mv-tiles .mv-empty-tile'); |
+ for (var i = 0; i < tileAndEmptyTileList.length; ++i) { |
+ tileAndEmptyTileList[i].classList.toggle('hidden', i >= data.maxVisible); |
+ } |
+ }; |
- var parent = document.querySelector('#most-visited'); |
- |
- // Only fade in the new tiles if there were tiles before. |
- var fadeIn = false; |
- var old = parent.querySelector('#mv-tiles'); |
- if (old) { |
- fadeIn = true; |
- // Mark old tile DIV for removal after the transition animation is done. |
- old.removeAttribute('id'); |
- old.classList.add('mv-tiles-old'); |
- old.style.opacity = 0.0; |
- cur.addEventListener('webkitTransitionEnd', function(ev) { |
- if (ev.target === cur) { |
- removeAllOldTiles(); |
- } |
- }); |
- } |
- // Add new tileset. |
- cur.id = 'mv-tiles'; |
- parent.appendChild(cur); |
- // getComputedStyle causes the initial style (opacity 0) to be applied, so |
- // that when we then set it to 1, that triggers the CSS transition. |
- if (fadeIn) { |
- window.getComputedStyle(cur).opacity; |
- } |
- cur.style.opacity = 1.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 (isFinite(args.rid)) { |
- // If a valid number passed in |args.rid|: a local chrome suggestion. |
- var data = |
- chrome.embeddedSearch.newTabPage.getMostVisitedItemData(args.rid); |
- if (!data) |
- return; |
- |
- data.tid = data.rid; |
- if (!data.faviconUrl) { |
- data.faviconUrl = 'chrome-search://favicon/size/16@' + |
- window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid; |
+ /** |
+ * Removes all old instances of #mv-tiles that are pending for deletion. |
+ */ |
+ var removeAllOldTiles = function() { |
+ var parent = document.querySelector('#most-visited'); |
+ var oldList = parent.querySelectorAll('.mv-tiles-old'); |
+ for (var i = 0; i < oldList.length; ++i) { |
+ parent.removeChild(oldList[i]); |
} |
- tiles.appendChild(renderTile(data)); |
- } else if (args.url) { |
- // If a URL is passed: a server-side suggestion. |
- args.tileSource = NTPTileSource.SUGGESTIONS_SERVICE; |
- // check sanity of the arguments |
- if (/^javascript:/i.test(args.url) || |
- /^javascript:/i.test(args.thumbnailUrl)) |
- return; |
- tiles.appendChild(renderTile(args)); |
- } else { // an empty tile |
- tiles.appendChild(renderTile(null)); |
- } |
-}; |
- |
-/** |
- * 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'); |
- tile.addEventListener('webkitTransitionEnd', function(ev) { |
- if (ev.propertyName != 'width') return; |
- |
- window.parent.postMessage({cmd: 'tileBlacklisted', |
- tid: Number(tile.getAttribute('data-tid'))}, |
- DOMAIN_ORIGIN); |
- }); |
-}; |
- |
- |
-/** |
- * Returns whether the given URL has a known, safe scheme. |
- * @param {string} url URL to check. |
- */ |
-var isSchemeAllowed = function(url) { |
- return url.startsWith('http://') || url.startsWith('https://') || |
- url.startsWith('ftp://') || url.startsWith('file://') || |
- url.startsWith('chrome-extension://'); |
-}; |
- |
- |
-/** |
- * 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; |
- } |
+ }; |
- // The tile will be appended to tiles. |
- var position = tiles.children.length; |
- logMostVisitedImpression(position, data.tileSource); |
- tile.className = 'mv-tile'; |
- tile.setAttribute('data-tid', data.tid); |
- var html = []; |
- if (!USE_ICONS) { |
- html.push('<div class="mv-favicon"></div>'); |
- } |
- html.push('<div class="mv-title"></div><div class="mv-thumb"></div>'); |
- html.push('<div class="mv-x" role="button"></div>'); |
- tile.innerHTML = html.join(''); |
- tile.lastElementChild.title = queryArgs['removeTooltip'] || ''; |
+ /** |
+ * 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; |
- if (isSchemeAllowed(data.url)) { |
- tile.href = data.url; |
- } |
- tile.setAttribute('aria-label', data.title); |
- tile.title = data.title; |
- |
- tile.addEventListener('click', function(ev) { |
- logMostVisitedNavigation(position, data.tileSource); |
- }); |
- |
- tile.addEventListener('keydown', function(event) { |
- if (event.keyCode == 46 /* DELETE */ || |
- event.keyCode == 8 /* BACKSPACE */) { |
- event.preventDefault(); |
- event.stopPropagation(); |
- blacklistTile(this); |
- } else if (event.keyCode == 13 /* ENTER */ || |
- event.keyCode == 32 /* SPACE */) { |
- event.preventDefault(); |
- this.click(); |
- } else if (event.keyCode >= 37 && event.keyCode <= 40 /* ARROWS */) { |
- // specify the direction of movement |
- var inArrowDirection = function(origin, target) { |
- return (event.keyCode == 37 /* LEFT */ && |
- origin.offsetTop == target.offsetTop && |
- origin.offsetLeft > target.offsetLeft) || |
- (event.keyCode == 38 /* UP */ && |
- origin.offsetTop > target.offsetTop && |
- origin.offsetLeft == target.offsetLeft) || |
- (event.keyCode == 39 /* RIGHT */ && |
- origin.offsetTop == target.offsetTop && |
- origin.offsetLeft < target.offsetLeft) || |
- (event.keyCode == 40 /* DOWN */ && |
- origin.offsetTop < target.offsetTop && |
- origin.offsetLeft == target.offsetLeft); |
- }; |
+ // Create empty tiles until we have NUMBER_OF_TILES. |
+ while (cur.childNodes.length < NUMBER_OF_TILES) { |
+ addTile({}); |
+ } |
- var nonEmptyTiles = document.querySelectorAll('#mv-tiles .mv-tile'); |
- var nextTile = null; |
- // Find the closest tile in the appropriate direction. |
- for (var i = 0; i < nonEmptyTiles.length; i++) { |
- if (inArrowDirection(this, nonEmptyTiles[i]) && |
- (!nextTile || inArrowDirection(nonEmptyTiles[i], nextTile))) { |
- nextTile = nonEmptyTiles[i]; |
+ var parent = document.querySelector('#most-visited'); |
+ |
+ // Only fade in the new tiles if there were tiles before. |
+ var fadeIn = false; |
+ var old = parent.querySelector('#mv-tiles'); |
+ if (old) { |
+ fadeIn = true; |
+ // Mark old tile DIV for removal after the transition animation is done. |
+ old.removeAttribute('id'); |
+ old.classList.add('mv-tiles-old'); |
+ old.style.opacity = 0.0; |
+ cur.addEventListener('webkitTransitionEnd', function(ev) { |
+ if (ev.target === cur) { |
+ removeAllOldTiles(); |
} |
+ }); |
+ } |
+ |
+ // Add new tileset. |
+ cur.id = 'mv-tiles'; |
+ parent.appendChild(cur); |
+ // getComputedStyle causes the initial style (opacity 0) to be applied, so |
+ // that when we then set it to 1, that triggers the CSS transition. |
+ if (fadeIn) { |
+ window.getComputedStyle(cur).opacity; |
+ } |
+ cur.style.opacity = 1.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 (isFinite(args.rid)) { |
+ // If a valid number passed in |args.rid|: a local chrome suggestion. |
+ var data = |
+ chrome.embeddedSearch.newTabPage.getMostVisitedItemData(args.rid); |
+ if (!data) |
+ return; |
+ |
+ data.tid = data.rid; |
+ if (!data.faviconUrl) { |
+ data.faviconUrl = 'chrome-search://favicon/size/16@' + |
+ window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid; |
} |
- if (nextTile) { |
- nextTile.focus(); |
- } |
+ tiles.appendChild(renderTile(data)); |
+ } else if (args.url) { |
+ // If a URL is passed: a server-side suggestion. |
+ args.tileSource = NTPTileSource.SUGGESTIONS_SERVICE; |
+ // check sanity of the arguments |
+ if (/^javascript:/i.test(args.url) || |
+ /^javascript:/i.test(args.thumbnailUrl)) |
+ return; |
+ tiles.appendChild(renderTile(args)); |
+ } else { // an empty tile |
+ tiles.appendChild(renderTile(null)); |
} |
- }); |
+ }; |
+ |
+ /** |
+ * 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'); |
+ tile.addEventListener('webkitTransitionEnd', function(ev) { |
+ if (ev.propertyName != 'width') |
+ return; |
- var title = tile.querySelector('.mv-title'); |
- title.innerText = data.title; |
- title.style.direction = data.direction || 'ltr'; |
- if (NUM_TITLE_LINES > 1) { |
- title.classList.add('multiline'); |
- } |
+ window.parent.postMessage( |
+ {cmd: 'tileBlacklisted', tid: Number(tile.getAttribute('data-tid'))}, |
+ DOMAIN_ORIGIN); |
+ }); |
+ }; |
+ |
+ |
+ /** |
+ * Returns whether the given URL has a known, safe scheme. |
+ * @param {string} url URL to check. |
+ */ |
+ var isSchemeAllowed = function(url) { |
+ return url.startsWith('http://') || url.startsWith('https://') || |
+ url.startsWith('ftp://') || url.startsWith('file://') || |
+ url.startsWith('chrome-extension://'); |
+ }; |
+ |
+ |
+ /** |
+ * 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; |
+ } |
+ |
+ // The tile will be appended to tiles. |
+ var position = tiles.children.length; |
+ logMostVisitedImpression(position, data.tileSource); |
+ |
+ tile.className = 'mv-tile'; |
+ tile.setAttribute('data-tid', data.tid); |
+ var html = []; |
+ if (!USE_ICONS) { |
+ html.push('<div class="mv-favicon"></div>'); |
+ } |
+ html.push('<div class="mv-title"></div><div class="mv-thumb"></div>'); |
+ html.push('<div class="mv-x" role="button"></div>'); |
+ tile.innerHTML = html.join(''); |
+ tile.lastElementChild.title = queryArgs['removeTooltip'] || ''; |
+ |
+ if (isSchemeAllowed(data.url)) { |
+ tile.href = data.url; |
+ } |
+ tile.setAttribute('aria-label', data.title); |
+ tile.title = data.title; |
+ |
+ tile.addEventListener('click', function(ev) { |
+ logMostVisitedNavigation(position, data.tileSource); |
+ }); |
- if (USE_ICONS) { |
- var thumb = tile.querySelector('.mv-thumb'); |
- if (data.largeIconUrl) { |
+ tile.addEventListener('keydown', function(event) { |
+ if (event.keyCode == 46 /* DELETE */ || |
+ event.keyCode == 8 /* BACKSPACE */) { |
+ event.preventDefault(); |
+ event.stopPropagation(); |
+ blacklistTile(this); |
+ } else if ( |
+ event.keyCode == 13 /* ENTER */ || event.keyCode == 32 /* SPACE */) { |
+ event.preventDefault(); |
+ this.click(); |
+ } else if (event.keyCode >= 37 && event.keyCode <= 40 /* ARROWS */) { |
+ // specify the direction of movement |
+ var inArrowDirection = function(origin, target) { |
+ return (event.keyCode == 37 /* LEFT */ && |
+ origin.offsetTop == target.offsetTop && |
+ origin.offsetLeft > target.offsetLeft) || |
+ (event.keyCode == 38 /* UP */ && |
+ origin.offsetTop > target.offsetTop && |
+ origin.offsetLeft == target.offsetLeft) || |
+ (event.keyCode == 39 /* RIGHT */ && |
+ origin.offsetTop == target.offsetTop && |
+ origin.offsetLeft < target.offsetLeft) || |
+ (event.keyCode == 40 /* DOWN */ && |
+ origin.offsetTop < target.offsetTop && |
+ origin.offsetLeft == target.offsetLeft); |
+ }; |
+ |
+ var nonEmptyTiles = document.querySelectorAll('#mv-tiles .mv-tile'); |
+ var nextTile = null; |
+ // Find the closest tile in the appropriate direction. |
+ for (var i = 0; i < nonEmptyTiles.length; i++) { |
+ if (inArrowDirection(this, nonEmptyTiles[i]) && |
+ (!nextTile || inArrowDirection(nonEmptyTiles[i], nextTile))) { |
+ nextTile = nonEmptyTiles[i]; |
+ } |
+ } |
+ if (nextTile) { |
+ nextTile.focus(); |
+ } |
+ } |
+ }); |
+ |
+ var title = tile.querySelector('.mv-title'); |
+ title.innerText = data.title; |
+ title.style.direction = data.direction || 'ltr'; |
+ if (NUM_TITLE_LINES > 1) { |
+ title.classList.add('multiline'); |
+ } |
+ |
+ if (USE_ICONS) { |
+ var thumb = tile.querySelector('.mv-thumb'); |
+ if (data.largeIconUrl) { |
+ var img = document.createElement('img'); |
+ img.title = data.title; |
+ img.src = data.largeIconUrl; |
+ img.classList.add('large-icon'); |
+ loadedCounter += 1; |
+ img.addEventListener('load', countLoad); |
+ img.addEventListener('load', function(ev) { |
+ thumb.classList.add('large-icon-outer'); |
+ }); |
+ img.addEventListener('error', countLoad); |
+ img.addEventListener('error', function(ev) { |
+ thumb.classList.add('failed-img'); |
+ thumb.removeChild(img); |
+ }); |
+ thumb.appendChild(img); |
+ } else { |
+ thumb.classList.add('failed-img'); |
+ } |
+ } else { // THUMBNAILS |
+ // We keep track of the outcome of loading possible thumbnails for this |
+ // tile. Possible values: |
+ // - null: waiting for load/error |
+ // - false: error |
+ // - a string: URL that loaded correctly. |
+ // This is populated by acceptImage/rejectImage and loadBestImage |
+ // decides the best one to load. |
+ var results = []; |
+ var thumb = tile.querySelector('.mv-thumb'); |
var img = document.createElement('img'); |
+ var loaded = false; |
+ |
+ var loadBestImage = function() { |
+ if (loaded) { |
+ return; |
+ } |
+ for (var i = 0; i < results.length; ++i) { |
+ if (results[i] === null) { |
+ return; |
+ } |
+ if (results[i] != false) { |
+ img.src = results[i]; |
+ loaded = true; |
+ return; |
+ } |
+ } |
+ thumb.classList.add('failed-img'); |
+ thumb.removeChild(img); |
+ countLoad(); |
+ }; |
+ |
+ var acceptImage = function(idx, url) { |
+ return function(ev) { |
+ results[idx] = url; |
+ loadBestImage(); |
+ }; |
+ }; |
+ |
+ var rejectImage = function(idx) { |
+ return function(ev) { |
+ results[idx] = false; |
+ loadBestImage(); |
+ }; |
+ }; |
+ |
img.title = data.title; |
- img.src = data.largeIconUrl; |
- img.classList.add('large-icon'); |
+ img.classList.add('thumbnail'); |
loadedCounter += 1; |
img.addEventListener('load', countLoad); |
- img.addEventListener('load', function(ev) { |
- thumb.classList.add('large-icon-outer'); |
- }); |
img.addEventListener('error', countLoad); |
img.addEventListener('error', function(ev) { |
thumb.classList.add('failed-img'); |
thumb.removeChild(img); |
}); |
thumb.appendChild(img); |
- } else { |
- thumb.classList.add('failed-img'); |
- } |
- } else { // THUMBNAILS |
- // We keep track of the outcome of loading possible thumbnails for this |
- // tile. Possible values: |
- // - null: waiting for load/error |
- // - false: error |
- // - a string: URL that loaded correctly. |
- // This is populated by acceptImage/rejectImage and loadBestImage |
- // decides the best one to load. |
- var results = []; |
- var thumb = tile.querySelector('.mv-thumb'); |
- var img = document.createElement('img'); |
- var loaded = false; |
- |
- var loadBestImage = function() { |
- if (loaded) { |
- return; |
- } |
- for (var i = 0; i < results.length; ++i) { |
- if (results[i] === null) { |
- return; |
+ |
+ if (data.thumbnailUrl) { |
+ img.src = data.thumbnailUrl; |
+ } else { |
+ // Get all thumbnailUrls for the tile. |
+ // They are ordered from best one to be used to worst. |
+ for (var i = 0; i < data.thumbnailUrls.length; ++i) { |
+ results.push(null); |
} |
- if (results[i] != false) { |
- img.src = results[i]; |
- loaded = true; |
- return; |
+ for (var i = 0; i < data.thumbnailUrls.length; ++i) { |
+ if (data.thumbnailUrls[i]) { |
+ var image = new Image(); |
+ image.src = data.thumbnailUrls[i]; |
+ image.onload = acceptImage(i, data.thumbnailUrls[i]); |
+ image.onerror = rejectImage(i); |
+ } else { |
+ rejectImage(i)(null); |
+ } |
} |
} |
- thumb.classList.add('failed-img'); |
- thumb.removeChild(img); |
- countLoad(); |
- }; |
- var acceptImage = function(idx, url) { |
- return function(ev) { |
- results[idx] = url; |
- loadBestImage(); |
- }; |
- }; |
+ var favicon = tile.querySelector('.mv-favicon'); |
+ if (data.faviconUrl) { |
+ var fi = document.createElement('img'); |
+ fi.src = data.faviconUrl; |
+ // Set the title to empty so screen readers won't say the image name. |
+ 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 rejectImage = function(idx) { |
- return function(ev) { |
- results[idx] = false; |
- loadBestImage(); |
- }; |
- }; |
- |
- img.title = data.title; |
- img.classList.add('thumbnail'); |
- loadedCounter += 1; |
- img.addEventListener('load', countLoad); |
- img.addEventListener('error', countLoad); |
- img.addEventListener('error', function(ev) { |
- thumb.classList.add('failed-img'); |
- thumb.removeChild(img); |
+ var mvx = tile.querySelector('.mv-x'); |
+ mvx.addEventListener('click', function(ev) { |
+ removeAllOldTiles(); |
+ blacklistTile(tile); |
+ ev.preventDefault(); |
+ ev.stopPropagation(); |
}); |
- thumb.appendChild(img); |
- if (data.thumbnailUrl) { |
- img.src = data.thumbnailUrl; |
- } else { |
- // Get all thumbnailUrls for the tile. |
- // They are ordered from best one to be used to worst. |
- for (var i = 0; i < data.thumbnailUrls.length; ++i) { |
- results.push(null); |
- } |
- for (var i = 0; i < data.thumbnailUrls.length; ++i) { |
- if (data.thumbnailUrls[i]) { |
- var image = new Image(); |
- image.src = data.thumbnailUrls[i]; |
- image.onload = acceptImage(i, data.thumbnailUrls[i]); |
- image.onerror = rejectImage(i); |
- } else { |
- rejectImage(i)(null); |
- } |
- } |
+ 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('&'); |
+ queryArgs = {}; |
+ for (var i = 0; i < query.length; ++i) { |
+ var val = query[i].split('='); |
+ if (val[0] == '') |
+ continue; |
+ queryArgs[decodeURIComponent(val[0])] = decodeURIComponent(val[1]); |
} |
- var favicon = tile.querySelector('.mv-favicon'); |
- if (data.faviconUrl) { |
- var fi = document.createElement('img'); |
- fi.src = data.faviconUrl; |
- // Set the title to empty so screen readers won't say the image name. |
- 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'); |
+ // Apply class for icon NTP, if specified. |
+ USE_ICONS = queryArgs['icons'] == '1'; |
+ if ('ntl' in queryArgs) { |
+ var ntl = parseInt(queryArgs['ntl'], 10); |
+ if (isFinite(ntl)) |
+ NUM_TITLE_LINES = ntl; |
} |
- } |
- var mvx = tile.querySelector('.mv-x'); |
- mvx.addEventListener('click', function(ev) { |
- removeAllOldTiles(); |
- blacklistTile(tile); |
- ev.preventDefault(); |
- ev.stopPropagation(); |
- }); |
- |
- 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('&'); |
- queryArgs = {}; |
- for (var i = 0; i < query.length; ++i) { |
- var val = query[i].split('='); |
- if (val[0] == '') continue; |
- queryArgs[decodeURIComponent(val[0])] = decodeURIComponent(val[1]); |
- } |
- |
- // Apply class for icon NTP, if specified. |
- USE_ICONS = queryArgs['icons'] == '1'; |
- if ('ntl' in queryArgs) { |
- var ntl = parseInt(queryArgs['ntl'], 10); |
- if (isFinite(ntl)) |
- NUM_TITLE_LINES = ntl; |
- } |
+ // Duplicating NTP_DESIGN.mainClass. |
+ document.querySelector('#most-visited') |
+ .classList.add(USE_ICONS ? 'icon-ntp' : 'thumb-ntp'); |
- // Duplicating NTP_DESIGN.mainClass. |
- document.querySelector('#most-visited').classList.add( |
- USE_ICONS ? 'icon-ntp' : 'thumb-ntp'); |
- |
- // Enable RTL. |
- if (queryArgs['rtl'] == '1') { |
- var html = document.querySelector('html'); |
- html.dir = 'rtl'; |
- } |
+ // Enable RTL. |
+ if (queryArgs['rtl'] == '1') { |
+ var html = document.querySelector('html'); |
+ html.dir = 'rtl'; |
+ } |
- window.addEventListener('message', handlePostMessage); |
-}; |
+ window.addEventListener('message', handlePostMessage); |
+ }; |
-window.addEventListener('DOMContentLoaded', init); |
+ window.addEventListener('DOMContentLoaded', init); |
})(); |