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. |
| 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 LOG_TYPE. |
| 80 */ |
| 81 var logEvent = function(eventType) { |
| 82 chrome.embeddedSearch.newTabPage.logEvent(eventType); |
| 83 }; |
| 84 |
| 85 |
| 86 /** |
| 87 * Down counts 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 * Handles 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'))}, |
| 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. |
| 199 */ |
| 200 var renderTile = function(data) { |
| 201 var tile = document.createElement('a'); |
| 202 |
| 203 if (data == null) { |
| 204 tile.className = 'mv-empty-tile'; |
| 205 return tile; |
| 206 } |
| 207 |
| 208 logEvent(LOG_TYPE.NTP_TILE); |
| 209 |
| 210 tile.className = 'mv-tile'; |
| 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="' + configData['removeThumbnailTooltip'] + |
| 215 '" class="mv-x"></div>'; |
| 216 |
| 217 tile.href = data.url; |
| 218 tile.title = data.title; |
| 219 tile.addEventListener('keypress', function(ev) { |
| 220 if (ev.keyCode == 127) { // DELETE |
| 221 blacklistTile(tile); |
| 222 ev.stopPropagation(); |
| 223 return false; |
| 224 } |
| 225 }); |
| 226 // TODO(fserb): remove this or at least change to mouseenter. |
| 227 tile.addEventListener('mouseover', function() { |
| 228 logEvent(LOG_TYPE.NTP_MOUSEOVER); |
| 229 }); |
| 230 |
| 231 var title = tile.querySelector('.mv-title'); |
| 232 title.innerText = data.title; |
| 233 title.style.direction = data.direction || 'ltr'; |
| 234 |
| 235 var thumb = tile.querySelector('.mv-thumb'); |
| 236 if (data.thumbnailUrl) { |
| 237 var img = document.createElement('img'); |
| 238 img.title = data.title; |
| 239 img.src = data.thumbnailUrl; |
| 240 loadedCounter += 1; |
| 241 img.addEventListener('load', countLoad); |
| 242 img.addEventListener('error', countLoad); |
| 243 img.addEventListener('error', function(ev) { |
| 244 thumb.classList.add('failed-img'); |
| 245 thumb.removeChild(img); |
| 246 logEvent(LOG_TYPE.NTP_THUMBNAIL_ERROR); |
| 247 }); |
| 248 thumb.appendChild(img); |
| 249 logEvent(LOG_TYPE.NTP_THUMBNAIL_TILE); |
| 250 } else { |
| 251 thumb.classList.add('failed-img'); |
| 252 } |
| 253 |
| 254 var favicon = tile.querySelector('.mv-favicon'); |
| 255 if (data.faviconUrl) { |
| 256 var fi = document.createElement('img'); |
| 257 fi.src = '../' + data.faviconUrl; |
| 258 // Set the title to empty so screen readers won't say the image name. |
| 259 fi.title = ''; |
| 260 loadedCounter += 1; |
| 261 fi.addEventListener('load', countLoad); |
| 262 fi.addEventListener('error', countLoad); |
| 263 fi.addEventListener('error', function(ev) { |
| 264 favicon.classList.add('failed-favicon'); |
| 265 }); |
| 266 favicon.appendChild(fi); |
| 267 } else { |
| 268 favicon.classList.add('failed-favicon'); |
| 269 } |
| 270 |
| 271 var mvx = tile.querySelector('.mv-x'); |
| 272 mvx.addEventListener('click', function(ev) { |
| 273 blacklistTile(tile); |
| 274 ev.stopPropagation(); |
| 275 return false; |
| 276 }); |
| 277 |
| 278 return tile; |
| 279 }; |
| 280 |
| 281 |
| 282 /** |
| 283 * Do some initialization and parses the query arguments passed to the iframe. |
| 284 */ |
| 285 var init = function() { |
| 286 // Creates a new DOM element to hold the tiles. |
| 287 tiles = document.createElement('div'); |
| 288 |
| 289 // Parse query arguments. |
| 290 var query = window.location.search.substring(1).split('&'); |
| 291 var args = {}; |
| 292 for (var i = 0; i < query.length; ++i) { |
| 293 var val = query[i].split('='); |
| 294 if (val[0] == '') continue; |
| 295 args[decodeURIComponent(val[0])] = decodeURIComponent(val[1]); |
| 296 } |
| 297 |
| 298 // Enable RTL. |
| 299 if (args['rtl'] == '1') { |
| 300 var html = document.querySelector('html'); |
| 301 html.dir = 'rtl'; |
| 302 } |
| 303 |
| 304 window.addEventListener('message', handlePostMessage); |
| 305 }; |
| 306 |
| 307 |
| 308 window.addEventListener('DOMContentLoaded', init); |
| 309 })(); |
OLD | NEW |