Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 /* Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 * Use of this source code is governed by a BSD-style license that can be | |
| 3 * found in the LICENSE file. */ | |
| 4 | |
| 5 // Single iframe for NTP tiles. | |
| 6 (function() { | |
| 7 'use strict'; | |
| 8 | |
| 9 | |
| 10 /** | |
| 11 * The different types of events that are logged from the NTP. This enum is | |
| 12 * used to transfer information from the NTP JavaScript to the renderer and is | |
| 13 * not used as a UMA enum histogram's logged value. | |
| 14 * Note: Keep in sync with common/ntp_logging_events.h | |
| 15 * @enum {number} | |
| 16 * @const | |
| 17 */ | |
| 18 var LOG_TYPE = { | |
| 19 // The suggestion is coming from the server. Unused here. | |
| 20 NTP_SERVER_SIDE_SUGGESTION: 0, | |
| 21 // The suggestion is coming from the client. | |
| 22 NTP_CLIENT_SIDE_SUGGESTION: 1, | |
| 23 // Indicates a tile was rendered, no matter if it's a thumbnail, a gray tile | |
| 24 // or an external tile. | |
| 25 NTP_TILE: 2, | |
| 26 // The tile uses a local thumbnail image. | |
| 27 NTP_THUMBNAIL_TILE: 3, | |
| 28 // Used when no thumbnail is specified and a gray tile with the domain is used | |
| 29 // as the main tile. Unused here. | |
| 30 NTP_GRAY_TILE: 4, | |
| 31 // The visuals of that tile are handled externally by the page itself. | |
| 32 // Unused here. | |
| 33 NTP_EXTERNAL_TILE: 5, | |
| 34 // There was an error in loading both the thumbnail image and the fallback | |
| 35 // (if it was provided), resulting in a gray tile. | |
| 36 NTP_THUMBNAIL_ERROR: 6, | |
| 37 // Used a gray tile with the domain as the fallback for a failed thumbnail. | |
| 38 // Unused here. | |
| 39 NTP_GRAY_TILE_FALLBACK: 7, | |
| 40 // The visuals of that tile's fallback are handled externally. Unused here. | |
| 41 NTP_EXTERNAL_TILE_FALLBACK: 8, | |
| 42 // The user moused over an NTP tile or title. | |
| 43 NTP_MOUSEOVER: 9 | |
| 44 }; | |
| 45 | |
| 46 | |
| 47 /** | |
| 48 * Total number of tiles to show at any time. If the host page doesn't send | |
| 49 * enough tiles, we fill them blank. | |
| 50 * @const {number} | |
| 51 */ | |
| 52 var NUMBER_OF_TILES = 8; | |
| 53 | |
| 54 | |
| 55 /** | |
| 56 * The origin of this request. | |
| 57 * @const {string} | |
| 58 */ | |
| 59 var DOMAIN_ORIGIN = '{{ORIGIN}}'; | |
| 60 | |
| 61 | |
| 62 /** | |
| 63 * Counter for DOM elements that we are waiting to finish loading. | |
| 64 * @type {number} | |
| 65 */ | |
| 66 var loadedCounter = 1; | |
| 67 | |
| 68 | |
| 69 /** | |
| 70 * DOM element containing the tiles we are going to present next. | |
| 71 * Works as a double-buffer that is shown when we receive a "show" postMessage. | |
| 72 * @type {Element} | |
| 73 */ | |
| 74 var tiles = null; | |
| 75 | |
| 76 | |
| 77 /** | |
| 78 * Log an event on the NTP. | |
| 79 * @param {number} eventType Event from NTP_LOGGING_EVENT_TYPE. | |
| 80 */ | |
| 81 var logEvent = function(eventType) { | |
| 82 chrome.embeddedSearch.newTabPage.logEvent(eventType); | |
| 83 }; | |
| 84 | |
| 85 | |
| 86 /** | |
| 87 * Down count the DOM elements that we are waiting for the page to load. | |
| 88 * When we get to 0, we send a message to the parent window. | |
| 89 * This is usually used as an EventListener of onload/onerror. | |
| 90 */ | |
| 91 var countLoad = function() { | |
| 92 loadedCounter -= 1; | |
| 93 if (loadedCounter <= 0) { | |
| 94 window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN); | |
| 95 loadedCounter = 1; | |
| 96 } | |
| 97 }; | |
| 98 | |
| 99 | |
| 100 /** | |
| 101 * Handle postMessages coming from the host page to the iframe. | |
| 102 * We try to keep the logic here to a minimum and just dispatch to the relevant | |
| 103 * functions. | |
| 104 **/ | |
| 105 var handlePostMessage = function(event) { | |
| 106 var cmd = event.data.cmd; | |
| 107 | |
| 108 if (cmd == 'tile') { | |
| 109 addTile(event.data); | |
| 110 } else if (cmd == 'show') { | |
| 111 showTiles(); | |
| 112 countLoad(); | |
| 113 } else { | |
| 114 console.error('Unknown command: ' + event.data); | |
| 115 } | |
| 116 }; | |
| 117 | |
| 118 | |
| 119 /** | |
| 120 * Called when the host page has finished sending us tile information and | |
| 121 * we are ready to show the new tiles and drop the old ones. | |
| 122 */ | |
| 123 var showTiles = function() { | |
| 124 // Store the tiles on the current closure. | |
| 125 var cur = tiles; | |
| 126 | |
| 127 // Create empty tiles until we have NUMBER_OF_TILES. | |
| 128 while (cur.childNodes.length < NUMBER_OF_TILES) { | |
| 129 addTile({}); | |
| 130 } | |
| 131 | |
| 132 var parent = document.querySelector('#most-visited'); | |
| 133 | |
| 134 // Mark old tile DIV for removal after the transition animation is done. | |
| 135 var old = parent.querySelector('#mv-tiles'); | |
| 136 if (old) { | |
| 137 old.id = 'mv-tiles-old'; | |
| 138 cur.addEventListener('webkitTransitionEnd', function(ev) { | |
| 139 if (ev.target === cur) { | |
| 140 parent.removeChild(old); | |
| 141 } | |
| 142 }); | |
| 143 } | |
| 144 | |
| 145 // Add new tileset. | |
| 146 cur.id = 'mv-tiles'; | |
| 147 parent.appendChild(cur); | |
| 148 // We want the CSS transition to trigger, so need to add to the DOM before | |
| 149 // setting the style. | |
| 150 setTimeout(function() { | |
| 151 cur.style.opacity = 1.0; | |
| 152 }, 0); | |
| 153 | |
| 154 // Make sure the tiles variable contain the next tileset we may use. | |
| 155 tiles = document.createElement('div'); | |
| 156 }; | |
| 157 | |
| 158 | |
| 159 /** | |
| 160 * Called when the host page wants to add a suggestion tile. | |
| 161 * For Most Visited, it grabs the data from Chrome and pass on. | |
| 162 * For host page generated it just passes the data. | |
| 163 * @param {object} args Data for the tile to be rendered. | |
| 164 */ | |
| 165 var addTile = function(args) { | |
| 166 if (args.rid) { | |
| 167 var data = chrome.embeddedSearch.searchBox.getMostVisitedItemData(args.rid); | |
| 168 tiles.appendChild(renderTile(data)); | |
| 169 logEvent(LOG_TYPE.NTP_CLIENT_SIDE_SUGGESTION); | |
| 170 } else { | |
| 171 tiles.appendChild(renderTile(null)); | |
| 172 } | |
| 173 }; | |
| 174 | |
| 175 | |
| 176 /** | |
| 177 * Called when the user decided to add a tile to the blacklist. | |
| 178 * It sets of the animation for the blacklist and sends the blacklisted id | |
| 179 * to the host page. | |
| 180 * @param {Element} tile DOM node of the tile we want to remove. | |
| 181 */ | |
| 182 var blacklistTile = function(tile) { | |
| 183 tile.classList.add('blacklisted'); | |
| 184 var sent = false; | |
| 185 tile.addEventListener('webkitTransitionEnd', function() { | |
| 186 if (sent) return; | |
| 187 sent = true; | |
| 188 window.parent.postMessage({cmd: 'tileBlacklisted', | |
| 189 rid: Number(tile.getAttribute('data-rid'))}, | |
|
Mathieu
2015/03/12 17:39:19
how does this work when there is no 'rid', such as
fserb
2015/03/12 17:51:56
Currently it doesn't.
For server suggestions we do
| |
| 190 DOMAIN_ORIGIN); | |
| 191 }); | |
| 192 }; | |
| 193 | |
| 194 | |
| 195 /** | |
| 196 * Renders a MostVisited tile to the DOM. | |
| 197 * @param {object} data Object containing rid, url, title, favicon, thumbnail. | |
| 198 * data is null if you want to construct an empty tile. | |
|
Mathieu
2015/03/12 17:39:19
nit: it's indent by 4 only
nit: |data| is null if.
fserb
2015/03/12 17:51:57
Done.
| |
| 199 */ | |
| 200 var renderTile = function(data) { | |
| 201 var tile = document.createElement('a'); | |
| 202 tile.className = 'mv-tile'; | |
|
Mathieu
2015/03/12 17:39:19
nit: would move this line below the if statement.
fserb
2015/03/12 17:51:56
Done.
| |
| 203 | |
| 204 if (data == null) { | |
| 205 tile.className = 'mv-empty-tile'; | |
| 206 return tile; | |
| 207 } | |
| 208 | |
| 209 logEvent(LOG_TYPE.NTP_TILE); | |
| 210 | |
| 211 tile.setAttribute('data-rid', data.rid); | |
| 212 tile.innerHTML = '<div class="mv-favicon"></div>' + | |
| 213 '<div class="mv-title"></div><div class="mv-thumb"></div>' + | |
| 214 '<div title="Don\'t show on this page" class="mv-x"></div>'; | |
|
Mathieu
2015/03/12 17:39:19
this message should be translated
fserb
2015/03/12 17:51:57
good catch! Done.
| |
| 215 | |
| 216 tile.href = data.url; | |
| 217 tile.title = data.title; | |
| 218 tile.addEventListener('keypress', function(ev) { | |
| 219 if (ev.keyCode == 127) { // DELETE | |
| 220 blacklistTile(tile); | |
| 221 ev.stopPropagation(); | |
| 222 return false; | |
| 223 } | |
| 224 }); | |
| 225 tile.addEventListener('mouseover', function() { | |
|
Mathieu
2015/03/12 17:39:19
Add a todo to remove this
fserb
2015/03/12 17:51:56
Done.
fserb
2015/03/12 17:51:56
Done.
| |
| 226 logEvent(LOG_TYPE.NTP_MOUSEOVER); | |
| 227 }); | |
| 228 | |
| 229 var title = tile.querySelector('.mv-title'); | |
| 230 title.innerText = data.title; | |
| 231 title.style.direction = data.direction || 'ltr'; | |
| 232 | |
| 233 var thumb = tile.querySelectorAll('.mv-thumb')[0]; | |
| 234 | |
| 235 if (data.thumbnailUrl) { | |
| 236 var img = document.createElement('img'); | |
| 237 img.title = data.title; | |
| 238 img.src = data.thumbnailUrl; | |
| 239 loadedCounter += 1; | |
| 240 img.addEventListener('load', countLoad); | |
| 241 img.addEventListener('error', countLoad); | |
|
Mathieu
2015/03/12 17:39:19
can you combine these 'error' lines, same below
fserb
2015/03/12 17:51:57
I could, but I'd like to keep it separate.
The per
| |
| 242 img.addEventListener('error', function(ev) { | |
| 243 thumb.classList.add('failed-img'); | |
| 244 thumb.removeChild(img); | |
| 245 logEvent(LOG_TYPE.NTP_THUMBNAIL_ERROR); | |
| 246 }); | |
| 247 thumb.appendChild(img); | |
| 248 logEvent(LOG_TYPE.NTP_THUMBNAIL_TILE); | |
| 249 } else { | |
| 250 thumb.classList.add('failed-img'); | |
| 251 } | |
| 252 | |
| 253 var favicon = tile.querySelectorAll('.mv-favicon')[0]; | |
| 254 if (data.faviconUrl) { | |
| 255 var fi = document.createElement('img'); | |
| 256 fi.src = '../' + data.faviconUrl; | |
|
Mathieu
2015/03/12 17:39:19
can you explain via comment why this is here?
fserb
2015/03/13 17:22:44
Done.
Mathieu
2015/03/14 13:15:58
I meant the src line... It's not immediately clear
| |
| 257 fi.title = ''; | |
| 258 loadedCounter += 1; | |
| 259 fi.addEventListener('load', countLoad); | |
| 260 fi.addEventListener('error', countLoad); | |
| 261 fi.addEventListener('error', function(ev) { | |
| 262 favicon.classList.add('failed-favicon'); | |
| 263 }); | |
| 264 favicon.appendChild(fi); | |
| 265 } else { | |
| 266 favicon.classList.add('failed-favicon'); | |
| 267 } | |
| 268 | |
| 269 var mvx = tile.querySelectorAll('.mv-x')[0]; | |
| 270 mvx.addEventListener('click', function(ev) { | |
| 271 blacklistTile(tile); | |
| 272 ev.stopPropagation(); | |
| 273 return false; | |
| 274 }); | |
| 275 | |
| 276 return tile; | |
| 277 }; | |
| 278 | |
| 279 | |
| 280 /** | |
| 281 * Do some initialization and parses the query arguments passed to the iframe. | |
| 282 */ | |
| 283 var init = function() { | |
| 284 // Creates a new DOM element to hold the tiles. | |
| 285 tiles = document.createElement('div'); | |
| 286 | |
| 287 // Parse query arguments. | |
| 288 var query = window.location.search.substring(1).split('&'); | |
| 289 var args = {}; | |
| 290 for (var i = 0; i < query.length; ++i) { | |
| 291 var val = query[i].split('='); | |
| 292 if (val[0] == '') continue; | |
| 293 args[decodeURIComponent(val[0])] = decodeURIComponent(val[1]); | |
| 294 } | |
| 295 | |
| 296 // Enable RTL. | |
| 297 if (args['rtl'] == '1') { | |
| 298 var html = document.querySelector('html'); | |
| 299 html.dir = 'rtl'; | |
| 300 } | |
| 301 | |
| 302 window.addEventListener('message', handlePostMessage); | |
| 303 }; | |
| 304 | |
| 305 | |
| 306 window.addEventListener('DOMContentLoaded', init); | |
| 307 })(); | |
| OLD | NEW |