Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(311)

Side by Side Diff: chrome/browser/resources/local_ntp/most_visited_single.js

Issue 2600683002: Run tools/clang-format-js on some of chrome/browser/resources/ (Closed)
Patch Set: hackhackhack Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* Copyright 2015 The Chromium Authors. All rights reserved. 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 2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file. */ 3 * found in the LICENSE file. */
4 4
5 // Single iframe for NTP tiles. 5 // Single iframe for NTP tiles.
6 (function() { 6 (function() {
7 'use strict'; 7 'use strict';
8 8
9 9
10 /** 10 /**
11 * The different types of events that are logged from the NTP. This enum is 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 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. 13 * not used as a UMA enum histogram's logged value.
14 * Note: Keep in sync with common/ntp_logging_events.h 14 * Note: Keep in sync with common/ntp_logging_events.h
15 * @enum {number} 15 * @enum {number}
16 * @const 16 * @const
17 */ 17 */
18 var LOG_TYPE = { 18 var LOG_TYPE = {
19 // All NTP Tiles have finished loading (successfully or failing). 19 // All NTP Tiles have finished loading (successfully or failing).
20 NTP_ALL_TILES_LOADED: 11, 20 NTP_ALL_TILES_LOADED: 11,
21 }; 21 };
22 22
23 23
24 /** 24 /**
25 * The different sources that an NTP tile can have. 25 * The different sources that an NTP tile can have.
26 * Note: Keep in sync with components/ntp_tiles/ntp_tile_source.h 26 * Note: Keep in sync with components/ntp_tiles/ntp_tile_source.h
27 * @enum {number} 27 * @enum {number}
28 * @const 28 * @const
29 */ 29 */
30 var NTPTileSource = { 30 var NTPTileSource = {
31 TOP_SITES: 0, 31 TOP_SITES: 0,
32 SUGGESTIONS_SERVICE: 1, 32 SUGGESTIONS_SERVICE: 1,
33 POPULAR: 3, 33 POPULAR: 3,
34 WHITELIST: 4, 34 WHITELIST: 4,
35 }; 35 };
36 36
37 37
38 /** 38 /**
39 * Total number of tiles to show at any time. If the host page doesn't send 39 * Total number of tiles to show at any time. If the host page doesn't send
40 * enough tiles, we fill them blank. 40 * enough tiles, we fill them blank.
41 * @const {number} 41 * @const {number}
42 */ 42 */
43 var NUMBER_OF_TILES = 8; 43 var NUMBER_OF_TILES = 8;
44 44
45 45
46 /** 46 /**
47 * Whether to use icons instead of thumbnails. 47 * Whether to use icons instead of thumbnails.
48 * @type {boolean} 48 * @type {boolean}
49 */ 49 */
50 var USE_ICONS = false; 50 var USE_ICONS = false;
51 51
52 52
53 /** 53 /**
54 * Number of lines to display in titles. 54 * Number of lines to display in titles.
55 * @type {number} 55 * @type {number}
56 */ 56 */
57 var NUM_TITLE_LINES = 1; 57 var NUM_TITLE_LINES = 1;
58 58
59 59
60 /** 60 /**
61 * The origin of this request. 61 * The origin of this request.
62 * @const {string} 62 * @const {string}
63 */ 63 */
64 var DOMAIN_ORIGIN = '{{ORIGIN}}'; 64 var DOMAIN_ORIGIN = '{{ORIGIN}}';
65 65
66 66
67 /** 67 /**
68 * Counter for DOM elements that we are waiting to finish loading. 68 * Counter for DOM elements that we are waiting to finish loading.
69 * @type {number} 69 * @type {number}
70 */ 70 */
71 var loadedCounter = 1; 71 var loadedCounter = 1;
72 72
73 73
74 /** 74 /**
75 * DOM element containing the tiles we are going to present next. 75 * DOM element containing the tiles we are going to present next.
76 * Works as a double-buffer that is shown when we receive a "show" postMessage. 76 * Works as a double-buffer that is shown when we receive a "show"
77 * @type {Element} 77 * postMessage.
78 */ 78 * @type {Element}
79 var tiles = null; 79 */
80 80 var tiles = null;
81 81
82 /** 82
83 * List of parameters passed by query args. 83 /**
84 * @type {Object} 84 * List of parameters passed by query args.
85 */ 85 * @type {Object}
86 var queryArgs = {}; 86 */
87 87 var queryArgs = {};
88 88
89 /** 89
90 * Log an event on the NTP. 90 /**
91 * @param {number} eventType Event from LOG_TYPE. 91 * Log an event on the NTP.
92 */ 92 * @param {number} eventType Event from LOG_TYPE.
93 var logEvent = function(eventType) { 93 */
94 chrome.embeddedSearch.newTabPage.logEvent(eventType); 94 var logEvent = function(eventType) {
95 }; 95 chrome.embeddedSearch.newTabPage.logEvent(eventType);
96 96 };
97 /** 97
98 * Log impression of an NTP tile. 98 /**
99 * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES. 99 * Log impression of an NTP tile.
100 * @param {number} tileSource The source from NTPTileSource. 100 * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES.
101 */ 101 * @param {number} tileSource The source from NTPTileSource.
102 function logMostVisitedImpression(tileIndex, tileSource) { 102 */
103 chrome.embeddedSearch.newTabPage.logMostVisitedImpression(tileIndex, 103 function logMostVisitedImpression(tileIndex, tileSource) {
104 tileSource); 104 chrome.embeddedSearch.newTabPage.logMostVisitedImpression(
105 } 105 tileIndex, tileSource);
106
107 /**
108 * Log click on an NTP tile.
109 * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES.
110 * @param {number} tileSource The source from NTPTileSource.
111 */
112 function logMostVisitedNavigation(tileIndex, tileSource) {
113 chrome.embeddedSearch.newTabPage.logMostVisitedNavigation(tileIndex,
114 tileSource);
115 }
116
117 /**
118 * Down counts the DOM elements that we are waiting for the page to load.
119 * When we get to 0, we send a message to the parent window.
120 * This is usually used as an EventListener of onload/onerror.
121 */
122 var countLoad = function() {
123 loadedCounter -= 1;
124 if (loadedCounter <= 0) {
125 showTiles();
126 logEvent(LOG_TYPE.NTP_ALL_TILES_LOADED);
127 window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN);
128 loadedCounter = 1;
129 } 106 }
130 }; 107
131 108 /**
132 109 * Log click on an NTP tile.
133 /** 110 * @param {number} tileIndex Position of the tile, >= 0 and < NUMBER_OF_TILES.
134 * Handles postMessages coming from the host page to the iframe. 111 * @param {number} tileSource The source from NTPTileSource.
135 * Mostly, it dispatches every command to handleCommand. 112 */
136 */ 113 function logMostVisitedNavigation(tileIndex, tileSource) {
137 var handlePostMessage = function(event) { 114 chrome.embeddedSearch.newTabPage.logMostVisitedNavigation(
138 if (event.data instanceof Array) { 115 tileIndex, tileSource);
139 for (var i = 0; i < event.data.length; ++i) {
140 handleCommand(event.data[i]);
141 }
142 } else {
143 handleCommand(event.data);
144 } 116 }
145 }; 117
146 118 /**
147 119 * Down counts the DOM elements that we are waiting for the page to load.
148 /** 120 * When we get to 0, we send a message to the parent window.
149 * Handles a single command coming from the host page to the iframe. 121 * This is usually used as an EventListener of onload/onerror.
150 * We try to keep the logic here to a minimum and just dispatch to the relevant 122 */
151 * functions. 123 var countLoad = function() {
152 */ 124 loadedCounter -= 1;
153 var handleCommand = function(data) { 125 if (loadedCounter <= 0) {
154 var cmd = data.cmd; 126 showTiles();
155 127 logEvent(LOG_TYPE.NTP_ALL_TILES_LOADED);
156 if (cmd == 'tile') { 128 window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN);
157 addTile(data); 129 loadedCounter = 1;
158 } else if (cmd == 'show') { 130 }
159 countLoad(); 131 };
160 hideOverflowTiles(data); 132
161 } else if (cmd == 'updateTheme') { 133
162 updateTheme(data); 134 /**
163 } else if (cmd == 'tilesVisible') { 135 * Handles postMessages coming from the host page to the iframe.
164 hideOverflowTiles(data); 136 * Mostly, it dispatches every command to handleCommand.
165 } else { 137 */
166 console.error('Unknown command: ' + JSON.stringify(data)); 138 var handlePostMessage = function(event) {
167 } 139 if (event.data instanceof Array) {
168 }; 140 for (var i = 0; i < event.data.length; ++i) {
169 141 handleCommand(event.data[i]);
170 142 }
171 var updateTheme = function(info) { 143 } else {
172 var themeStyle = []; 144 handleCommand(event.data);
173 145 }
174 if (info.tileBorderColor) { 146 };
175 themeStyle.push('.thumb-ntp .mv-tile {' + 147
176 'border: 1px solid ' + info.tileBorderColor + '; }'); 148
177 } 149 /**
178 if (info.tileHoverBorderColor) { 150 * Handles a single command coming from the host page to the iframe.
179 themeStyle.push('.thumb-ntp .mv-tile:hover {' + 151 * We try to keep the logic here to a minimum and just dispatch to the
180 'border-color: ' + info.tileHoverBorderColor + '; }'); 152 * relevant
181 } 153 * functions.
182 if (info.isThemeDark) { 154 */
183 themeStyle.push('.thumb-ntp .mv-tile, .thumb-ntp .mv-empty-tile { ' + 155 var handleCommand = function(data) {
184 'background: rgb(51,51,51); }'); 156 var cmd = data.cmd;
185 themeStyle.push('.thumb-ntp .mv-thumb.failed-img { ' + 157
186 'background-color: #555; }'); 158 if (cmd == 'tile') {
187 themeStyle.push('.thumb-ntp .mv-thumb.failed-img::after { ' + 159 addTile(data);
188 'border-color: #333; }'); 160 } else if (cmd == 'show') {
189 themeStyle.push('.thumb-ntp .mv-x { ' + 161 countLoad();
190 'background: linear-gradient(to left, ' + 162 hideOverflowTiles(data);
191 'rgb(51,51,51) 60%, transparent); }'); 163 } else if (cmd == 'updateTheme') {
192 themeStyle.push('html[dir=rtl] .thumb-ntp .mv-x { ' + 164 updateTheme(data);
193 'background: linear-gradient(to right, ' + 165 } else if (cmd == 'tilesVisible') {
194 'rgb(51,51,51) 60%, transparent); }'); 166 hideOverflowTiles(data);
195 themeStyle.push('.thumb-ntp .mv-x::after { ' + 167 } else {
196 'background-color: rgba(255,255,255,0.7); }'); 168 console.error('Unknown command: ' + JSON.stringify(data));
197 themeStyle.push('.thumb-ntp .mv-x:hover::after { ' + 169 }
198 'background-color: #fff; }'); 170 };
199 themeStyle.push('.thumb-ntp .mv-x:active::after { ' + 171
200 'background-color: rgba(255,255,255,0.5); }'); 172
201 themeStyle.push('.icon-ntp .mv-tile:focus { ' + 173 var updateTheme = function(info) {
202 'background: rgba(255,255,255,0.2); }'); 174 var themeStyle = [];
203 } 175
204 if (info.tileTitleColor) { 176 if (info.tileBorderColor) {
205 themeStyle.push('body { color: ' + info.tileTitleColor + '; }'); 177 themeStyle.push(
206 } 178 '.thumb-ntp .mv-tile {' +
207 179 'border: 1px solid ' + info.tileBorderColor + '; }');
208 document.querySelector('#custom-theme').textContent = themeStyle.join('\n'); 180 }
209 }; 181 if (info.tileHoverBorderColor) {
210 182 themeStyle.push(
211 183 '.thumb-ntp .mv-tile:hover {' +
212 /** 184 'border-color: ' + info.tileHoverBorderColor + '; }');
213 * Hides extra tiles that don't fit on screen. 185 }
214 */ 186 if (info.isThemeDark) {
215 var hideOverflowTiles = function(data) { 187 themeStyle.push(
216 var tileAndEmptyTileList = document.querySelectorAll( 188 '.thumb-ntp .mv-tile, .thumb-ntp .mv-empty-tile { ' +
217 '#mv-tiles .mv-tile,#mv-tiles .mv-empty-tile'); 189 'background: rgb(51,51,51); }');
218 for (var i = 0; i < tileAndEmptyTileList.length; ++i) { 190 themeStyle.push(
219 tileAndEmptyTileList[i].classList.toggle('hidden', i >= data.maxVisible); 191 '.thumb-ntp .mv-thumb.failed-img { ' +
220 } 192 'background-color: #555; }');
221 }; 193 themeStyle.push(
222 194 '.thumb-ntp .mv-thumb.failed-img::after { ' +
223 195 'border-color: #333; }');
224 /** 196 themeStyle.push(
225 * Removes all old instances of #mv-tiles that are pending for deletion. 197 '.thumb-ntp .mv-x { ' +
226 */ 198 'background: linear-gradient(to left, ' +
227 var removeAllOldTiles = function() { 199 'rgb(51,51,51) 60%, transparent); }');
228 var parent = document.querySelector('#most-visited'); 200 themeStyle.push(
229 var oldList = parent.querySelectorAll('.mv-tiles-old'); 201 'html[dir=rtl] .thumb-ntp .mv-x { ' +
230 for (var i = 0; i < oldList.length; ++i) { 202 'background: linear-gradient(to right, ' +
231 parent.removeChild(oldList[i]); 203 'rgb(51,51,51) 60%, transparent); }');
232 } 204 themeStyle.push(
233 }; 205 '.thumb-ntp .mv-x::after { ' +
234 206 'background-color: rgba(255,255,255,0.7); }');
235 207 themeStyle.push(
236 /** 208 '.thumb-ntp .mv-x:hover::after { ' +
237 * Called when the host page has finished sending us tile information and 209 'background-color: #fff; }');
238 * we are ready to show the new tiles and drop the old ones. 210 themeStyle.push(
239 */ 211 '.thumb-ntp .mv-x:active::after { ' +
240 var showTiles = function() { 212 'background-color: rgba(255,255,255,0.5); }');
241 // Store the tiles on the current closure. 213 themeStyle.push(
242 var cur = tiles; 214 '.icon-ntp .mv-tile:focus { ' +
243 215 'background: rgba(255,255,255,0.2); }');
244 // Create empty tiles until we have NUMBER_OF_TILES. 216 }
245 while (cur.childNodes.length < NUMBER_OF_TILES) { 217 if (info.tileTitleColor) {
246 addTile({}); 218 themeStyle.push('body { color: ' + info.tileTitleColor + '; }');
247 } 219 }
248 220
249 var parent = document.querySelector('#most-visited'); 221 document.querySelector('#custom-theme').textContent = themeStyle.join('\n');
250 222 };
251 // Only fade in the new tiles if there were tiles before. 223
252 var fadeIn = false; 224
253 var old = parent.querySelector('#mv-tiles'); 225 /**
254 if (old) { 226 * Hides extra tiles that don't fit on screen.
255 fadeIn = true; 227 */
256 // Mark old tile DIV for removal after the transition animation is done. 228 var hideOverflowTiles = function(data) {
257 old.removeAttribute('id'); 229 var tileAndEmptyTileList = document.querySelectorAll(
258 old.classList.add('mv-tiles-old'); 230 '#mv-tiles .mv-tile,#mv-tiles .mv-empty-tile');
259 old.style.opacity = 0.0; 231 for (var i = 0; i < tileAndEmptyTileList.length; ++i) {
260 cur.addEventListener('webkitTransitionEnd', function(ev) { 232 tileAndEmptyTileList[i].classList.toggle('hidden', i >= data.maxVisible);
261 if (ev.target === cur) { 233 }
262 removeAllOldTiles(); 234 };
235
236
237 /**
238 * Removes all old instances of #mv-tiles that are pending for deletion.
239 */
240 var removeAllOldTiles = function() {
241 var parent = document.querySelector('#most-visited');
242 var oldList = parent.querySelectorAll('.mv-tiles-old');
243 for (var i = 0; i < oldList.length; ++i) {
244 parent.removeChild(oldList[i]);
245 }
246 };
247
248
249 /**
250 * Called when the host page has finished sending us tile information and
251 * we are ready to show the new tiles and drop the old ones.
252 */
253 var showTiles = function() {
254 // Store the tiles on the current closure.
255 var cur = tiles;
256
257 // Create empty tiles until we have NUMBER_OF_TILES.
258 while (cur.childNodes.length < NUMBER_OF_TILES) {
259 addTile({});
260 }
261
262 var parent = document.querySelector('#most-visited');
263
264 // Only fade in the new tiles if there were tiles before.
265 var fadeIn = false;
266 var old = parent.querySelector('#mv-tiles');
267 if (old) {
268 fadeIn = true;
269 // Mark old tile DIV for removal after the transition animation is done.
270 old.removeAttribute('id');
271 old.classList.add('mv-tiles-old');
272 old.style.opacity = 0.0;
273 cur.addEventListener('webkitTransitionEnd', function(ev) {
274 if (ev.target === cur) {
275 removeAllOldTiles();
276 }
277 });
278 }
279
280 // Add new tileset.
281 cur.id = 'mv-tiles';
282 parent.appendChild(cur);
283 // getComputedStyle causes the initial style (opacity 0) to be applied, so
284 // that when we then set it to 1, that triggers the CSS transition.
285 if (fadeIn) {
286 window.getComputedStyle(cur).opacity;
287 }
288 cur.style.opacity = 1.0;
289
290 // Make sure the tiles variable contain the next tileset we may use.
291 tiles = document.createElement('div');
292 };
293
294
295 /**
296 * Called when the host page wants to add a suggestion tile.
297 * For Most Visited, it grabs the data from Chrome and pass on.
298 * For host page generated it just passes the data.
299 * @param {object} args Data for the tile to be rendered.
300 */
301 var addTile = function(args) {
302 if (isFinite(args.rid)) {
303 // If a valid number passed in |args.rid|: a local chrome suggestion.
304 var data =
305 chrome.embeddedSearch.newTabPage.getMostVisitedItemData(args.rid);
306 if (!data)
307 return;
308
309 data.tid = data.rid;
310 if (!data.faviconUrl) {
311 data.faviconUrl = 'chrome-search://favicon/size/16@' +
312 window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid;
313 }
314 tiles.appendChild(renderTile(data));
315 } else if (args.url) {
316 // If a URL is passed: a server-side suggestion.
317 args.tileSource = NTPTileSource.SUGGESTIONS_SERVICE;
318 // check sanity of the arguments
319 if (/^javascript:/i.test(args.url) ||
320 /^javascript:/i.test(args.thumbnailUrl))
321 return;
322 tiles.appendChild(renderTile(args));
323 } else { // an empty tile
324 tiles.appendChild(renderTile(null));
325 }
326 };
327
328 /**
329 * Called when the user decided to add a tile to the blacklist.
330 * It sets of the animation for the blacklist and sends the blacklisted id
331 * to the host page.
332 * @param {Element} tile DOM node of the tile we want to remove.
333 */
334 var blacklistTile = function(tile) {
335 tile.classList.add('blacklisted');
336 tile.addEventListener('webkitTransitionEnd', function(ev) {
337 if (ev.propertyName != 'width')
338 return;
339
340 window.parent.postMessage(
341 {cmd: 'tileBlacklisted', tid: Number(tile.getAttribute('data-tid'))},
342 DOMAIN_ORIGIN);
343 });
344 };
345
346
347 /**
348 * Returns whether the given URL has a known, safe scheme.
349 * @param {string} url URL to check.
350 */
351 var isSchemeAllowed = function(url) {
352 return url.startsWith('http://') || url.startsWith('https://') ||
353 url.startsWith('ftp://') || url.startsWith('file://') ||
354 url.startsWith('chrome-extension://');
355 };
356
357
358 /**
359 * Renders a MostVisited tile to the DOM.
360 * @param {object} data Object containing rid, url, title, favicon, thumbnail.
361 * data is null if you want to construct an empty tile.
362 */
363 var renderTile = function(data) {
364 var tile = document.createElement('a');
365
366 if (data == null) {
367 tile.className = 'mv-empty-tile';
368 return tile;
369 }
370
371 // The tile will be appended to tiles.
372 var position = tiles.children.length;
373 logMostVisitedImpression(position, data.tileSource);
374
375 tile.className = 'mv-tile';
376 tile.setAttribute('data-tid', data.tid);
377 var html = [];
378 if (!USE_ICONS) {
379 html.push('<div class="mv-favicon"></div>');
380 }
381 html.push('<div class="mv-title"></div><div class="mv-thumb"></div>');
382 html.push('<div class="mv-x" role="button"></div>');
383 tile.innerHTML = html.join('');
384 tile.lastElementChild.title = queryArgs['removeTooltip'] || '';
385
386 if (isSchemeAllowed(data.url)) {
387 tile.href = data.url;
388 }
389 tile.setAttribute('aria-label', data.title);
390 tile.title = data.title;
391
392 tile.addEventListener('click', function(ev) {
393 logMostVisitedNavigation(position, data.tileSource);
394 });
395
396 tile.addEventListener('keydown', function(event) {
397 if (event.keyCode == 46 /* DELETE */ ||
398 event.keyCode == 8 /* BACKSPACE */) {
399 event.preventDefault();
400 event.stopPropagation();
401 blacklistTile(this);
402 } else if (
403 event.keyCode == 13 /* ENTER */ || event.keyCode == 32 /* SPACE */) {
404 event.preventDefault();
405 this.click();
406 } else if (event.keyCode >= 37 && event.keyCode <= 40 /* ARROWS */) {
407 // specify the direction of movement
408 var inArrowDirection = function(origin, target) {
409 return (event.keyCode == 37 /* LEFT */ &&
410 origin.offsetTop == target.offsetTop &&
411 origin.offsetLeft > target.offsetLeft) ||
412 (event.keyCode == 38 /* UP */ &&
413 origin.offsetTop > target.offsetTop &&
414 origin.offsetLeft == target.offsetLeft) ||
415 (event.keyCode == 39 /* RIGHT */ &&
416 origin.offsetTop == target.offsetTop &&
417 origin.offsetLeft < target.offsetLeft) ||
418 (event.keyCode == 40 /* DOWN */ &&
419 origin.offsetTop < target.offsetTop &&
420 origin.offsetLeft == target.offsetLeft);
421 };
422
423 var nonEmptyTiles = document.querySelectorAll('#mv-tiles .mv-tile');
424 var nextTile = null;
425 // Find the closest tile in the appropriate direction.
426 for (var i = 0; i < nonEmptyTiles.length; i++) {
427 if (inArrowDirection(this, nonEmptyTiles[i]) &&
428 (!nextTile || inArrowDirection(nonEmptyTiles[i], nextTile))) {
429 nextTile = nonEmptyTiles[i];
430 }
431 }
432 if (nextTile) {
433 nextTile.focus();
434 }
263 } 435 }
264 }); 436 });
265 } 437
266 438 var title = tile.querySelector('.mv-title');
267 // Add new tileset. 439 title.innerText = data.title;
268 cur.id = 'mv-tiles'; 440 title.style.direction = data.direction || 'ltr';
269 parent.appendChild(cur); 441 if (NUM_TITLE_LINES > 1) {
270 // getComputedStyle causes the initial style (opacity 0) to be applied, so 442 title.classList.add('multiline');
271 // that when we then set it to 1, that triggers the CSS transition. 443 }
272 if (fadeIn) { 444
273 window.getComputedStyle(cur).opacity; 445 if (USE_ICONS) {
274 } 446 var thumb = tile.querySelector('.mv-thumb');
275 cur.style.opacity = 1.0; 447 if (data.largeIconUrl) {
276 448 var img = document.createElement('img');
277 // Make sure the tiles variable contain the next tileset we may use. 449 img.title = data.title;
278 tiles = document.createElement('div'); 450 img.src = data.largeIconUrl;
279 }; 451 img.classList.add('large-icon');
280 452 loadedCounter += 1;
281 453 img.addEventListener('load', countLoad);
282 /** 454 img.addEventListener('load', function(ev) {
283 * Called when the host page wants to add a suggestion tile. 455 thumb.classList.add('large-icon-outer');
284 * For Most Visited, it grabs the data from Chrome and pass on. 456 });
285 * For host page generated it just passes the data. 457 img.addEventListener('error', countLoad);
286 * @param {object} args Data for the tile to be rendered. 458 img.addEventListener('error', function(ev) {
287 */ 459 thumb.classList.add('failed-img');
288 var addTile = function(args) { 460 thumb.removeChild(img);
289 if (isFinite(args.rid)) { 461 });
290 // If a valid number passed in |args.rid|: a local chrome suggestion. 462 thumb.appendChild(img);
291 var data = 463 } else {
292 chrome.embeddedSearch.newTabPage.getMostVisitedItemData(args.rid); 464 thumb.classList.add('failed-img');
293 if (!data) 465 }
294 return; 466 } else { // THUMBNAILS
295 467 // We keep track of the outcome of loading possible thumbnails for this
296 data.tid = data.rid; 468 // tile. Possible values:
297 if (!data.faviconUrl) { 469 // - null: waiting for load/error
298 data.faviconUrl = 'chrome-search://favicon/size/16@' + 470 // - false: error
299 window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid; 471 // - a string: URL that loaded correctly.
300 } 472 // This is populated by acceptImage/rejectImage and loadBestImage
301 tiles.appendChild(renderTile(data)); 473 // decides the best one to load.
302 } else if (args.url) { 474 var results = [];
303 // If a URL is passed: a server-side suggestion. 475 var thumb = tile.querySelector('.mv-thumb');
304 args.tileSource = NTPTileSource.SUGGESTIONS_SERVICE; 476 var img = document.createElement('img');
305 // check sanity of the arguments 477 var loaded = false;
306 if (/^javascript:/i.test(args.url) || 478
307 /^javascript:/i.test(args.thumbnailUrl)) 479 var loadBestImage = function() {
308 return; 480 if (loaded) {
309 tiles.appendChild(renderTile(args)); 481 return;
310 } else { // an empty tile 482 }
311 tiles.appendChild(renderTile(null)); 483 for (var i = 0; i < results.length; ++i) {
312 } 484 if (results[i] === null) {
313 }; 485 return;
314 486 }
315 /** 487 if (results[i] != false) {
316 * Called when the user decided to add a tile to the blacklist. 488 img.src = results[i];
317 * It sets of the animation for the blacklist and sends the blacklisted id 489 loaded = true;
318 * to the host page. 490 return;
319 * @param {Element} tile DOM node of the tile we want to remove. 491 }
320 */ 492 }
321 var blacklistTile = function(tile) { 493 thumb.classList.add('failed-img');
322 tile.classList.add('blacklisted'); 494 thumb.removeChild(img);
323 tile.addEventListener('webkitTransitionEnd', function(ev) { 495 countLoad();
324 if (ev.propertyName != 'width') return;
325
326 window.parent.postMessage({cmd: 'tileBlacklisted',
327 tid: Number(tile.getAttribute('data-tid'))},
328 DOMAIN_ORIGIN);
329 });
330 };
331
332
333 /**
334 * Returns whether the given URL has a known, safe scheme.
335 * @param {string} url URL to check.
336 */
337 var isSchemeAllowed = function(url) {
338 return url.startsWith('http://') || url.startsWith('https://') ||
339 url.startsWith('ftp://') || url.startsWith('file://') ||
340 url.startsWith('chrome-extension://');
341 };
342
343
344 /**
345 * Renders a MostVisited tile to the DOM.
346 * @param {object} data Object containing rid, url, title, favicon, thumbnail.
347 * data is null if you want to construct an empty tile.
348 */
349 var renderTile = function(data) {
350 var tile = document.createElement('a');
351
352 if (data == null) {
353 tile.className = 'mv-empty-tile';
354 return tile;
355 }
356
357 // The tile will be appended to tiles.
358 var position = tiles.children.length;
359 logMostVisitedImpression(position, data.tileSource);
360
361 tile.className = 'mv-tile';
362 tile.setAttribute('data-tid', data.tid);
363 var html = [];
364 if (!USE_ICONS) {
365 html.push('<div class="mv-favicon"></div>');
366 }
367 html.push('<div class="mv-title"></div><div class="mv-thumb"></div>');
368 html.push('<div class="mv-x" role="button"></div>');
369 tile.innerHTML = html.join('');
370 tile.lastElementChild.title = queryArgs['removeTooltip'] || '';
371
372 if (isSchemeAllowed(data.url)) {
373 tile.href = data.url;
374 }
375 tile.setAttribute('aria-label', data.title);
376 tile.title = data.title;
377
378 tile.addEventListener('click', function(ev) {
379 logMostVisitedNavigation(position, data.tileSource);
380 });
381
382 tile.addEventListener('keydown', function(event) {
383 if (event.keyCode == 46 /* DELETE */ ||
384 event.keyCode == 8 /* BACKSPACE */) {
385 event.preventDefault();
386 event.stopPropagation();
387 blacklistTile(this);
388 } else if (event.keyCode == 13 /* ENTER */ ||
389 event.keyCode == 32 /* SPACE */) {
390 event.preventDefault();
391 this.click();
392 } else if (event.keyCode >= 37 && event.keyCode <= 40 /* ARROWS */) {
393 // specify the direction of movement
394 var inArrowDirection = function(origin, target) {
395 return (event.keyCode == 37 /* LEFT */ &&
396 origin.offsetTop == target.offsetTop &&
397 origin.offsetLeft > target.offsetLeft) ||
398 (event.keyCode == 38 /* UP */ &&
399 origin.offsetTop > target.offsetTop &&
400 origin.offsetLeft == target.offsetLeft) ||
401 (event.keyCode == 39 /* RIGHT */ &&
402 origin.offsetTop == target.offsetTop &&
403 origin.offsetLeft < target.offsetLeft) ||
404 (event.keyCode == 40 /* DOWN */ &&
405 origin.offsetTop < target.offsetTop &&
406 origin.offsetLeft == target.offsetLeft);
407 }; 496 };
408 497
409 var nonEmptyTiles = document.querySelectorAll('#mv-tiles .mv-tile'); 498 var acceptImage = function(idx, url) {
410 var nextTile = null; 499 return function(ev) {
411 // Find the closest tile in the appropriate direction. 500 results[idx] = url;
412 for (var i = 0; i < nonEmptyTiles.length; i++) { 501 loadBestImage();
413 if (inArrowDirection(this, nonEmptyTiles[i]) && 502 };
414 (!nextTile || inArrowDirection(nonEmptyTiles[i], nextTile))) { 503 };
415 nextTile = nonEmptyTiles[i]; 504
416 } 505 var rejectImage = function(idx) {
417 } 506 return function(ev) {
418 if (nextTile) { 507 results[idx] = false;
419 nextTile.focus(); 508 loadBestImage();
420 } 509 };
421 } 510 };
422 }); 511
423
424 var title = tile.querySelector('.mv-title');
425 title.innerText = data.title;
426 title.style.direction = data.direction || 'ltr';
427 if (NUM_TITLE_LINES > 1) {
428 title.classList.add('multiline');
429 }
430
431 if (USE_ICONS) {
432 var thumb = tile.querySelector('.mv-thumb');
433 if (data.largeIconUrl) {
434 var img = document.createElement('img');
435 img.title = data.title; 512 img.title = data.title;
436 img.src = data.largeIconUrl; 513 img.classList.add('thumbnail');
437 img.classList.add('large-icon');
438 loadedCounter += 1; 514 loadedCounter += 1;
439 img.addEventListener('load', countLoad); 515 img.addEventListener('load', countLoad);
440 img.addEventListener('load', function(ev) {
441 thumb.classList.add('large-icon-outer');
442 });
443 img.addEventListener('error', countLoad); 516 img.addEventListener('error', countLoad);
444 img.addEventListener('error', function(ev) { 517 img.addEventListener('error', function(ev) {
445 thumb.classList.add('failed-img'); 518 thumb.classList.add('failed-img');
446 thumb.removeChild(img); 519 thumb.removeChild(img);
447 }); 520 });
448 thumb.appendChild(img); 521 thumb.appendChild(img);
449 } else { 522
450 thumb.classList.add('failed-img'); 523 if (data.thumbnailUrl) {
451 } 524 img.src = data.thumbnailUrl;
452 } else { // THUMBNAILS 525 } else {
453 // We keep track of the outcome of loading possible thumbnails for this 526 // Get all thumbnailUrls for the tile.
454 // tile. Possible values: 527 // They are ordered from best one to be used to worst.
455 // - null: waiting for load/error 528 for (var i = 0; i < data.thumbnailUrls.length; ++i) {
456 // - false: error 529 results.push(null);
457 // - a string: URL that loaded correctly.
458 // This is populated by acceptImage/rejectImage and loadBestImage
459 // decides the best one to load.
460 var results = [];
461 var thumb = tile.querySelector('.mv-thumb');
462 var img = document.createElement('img');
463 var loaded = false;
464
465 var loadBestImage = function() {
466 if (loaded) {
467 return;
468 }
469 for (var i = 0; i < results.length; ++i) {
470 if (results[i] === null) {
471 return;
472 } 530 }
473 if (results[i] != false) { 531 for (var i = 0; i < data.thumbnailUrls.length; ++i) {
474 img.src = results[i]; 532 if (data.thumbnailUrls[i]) {
475 loaded = true; 533 var image = new Image();
476 return; 534 image.src = data.thumbnailUrls[i];
535 image.onload = acceptImage(i, data.thumbnailUrls[i]);
536 image.onerror = rejectImage(i);
537 } else {
538 rejectImage(i)(null);
539 }
477 } 540 }
478 } 541 }
479 thumb.classList.add('failed-img'); 542
480 thumb.removeChild(img); 543 var favicon = tile.querySelector('.mv-favicon');
481 countLoad(); 544 if (data.faviconUrl) {
482 }; 545 var fi = document.createElement('img');
483 546 fi.src = data.faviconUrl;
484 var acceptImage = function(idx, url) { 547 // Set the title to empty so screen readers won't say the image name.
485 return function(ev) { 548 fi.title = '';
486 results[idx] = url; 549 loadedCounter += 1;
487 loadBestImage(); 550 fi.addEventListener('load', countLoad);
488 }; 551 fi.addEventListener('error', countLoad);
489 }; 552 fi.addEventListener('error', function(ev) {
490 553 favicon.classList.add('failed-favicon');
491 var rejectImage = function(idx) { 554 });
492 return function(ev) { 555 favicon.appendChild(fi);
493 results[idx] = false; 556 } else {
494 loadBestImage(); 557 favicon.classList.add('failed-favicon');
495 }; 558 }
496 }; 559 }
497 560
498 img.title = data.title; 561 var mvx = tile.querySelector('.mv-x');
499 img.classList.add('thumbnail'); 562 mvx.addEventListener('click', function(ev) {
500 loadedCounter += 1; 563 removeAllOldTiles();
501 img.addEventListener('load', countLoad); 564 blacklistTile(tile);
502 img.addEventListener('error', countLoad); 565 ev.preventDefault();
503 img.addEventListener('error', function(ev) { 566 ev.stopPropagation();
504 thumb.classList.add('failed-img');
505 thumb.removeChild(img);
506 }); 567 });
507 thumb.appendChild(img); 568
508 569 return tile;
509 if (data.thumbnailUrl) { 570 };
510 img.src = data.thumbnailUrl; 571
511 } else { 572
512 // Get all thumbnailUrls for the tile. 573 /**
513 // They are ordered from best one to be used to worst. 574 * Do some initialization and parses the query arguments passed to the iframe.
514 for (var i = 0; i < data.thumbnailUrls.length; ++i) { 575 */
515 results.push(null); 576 var init = function() {
516 } 577 // Creates a new DOM element to hold the tiles.
517 for (var i = 0; i < data.thumbnailUrls.length; ++i) { 578 tiles = document.createElement('div');
518 if (data.thumbnailUrls[i]) { 579
519 var image = new Image(); 580 // Parse query arguments.
520 image.src = data.thumbnailUrls[i]; 581 var query = window.location.search.substring(1).split('&');
521 image.onload = acceptImage(i, data.thumbnailUrls[i]); 582 queryArgs = {};
522 image.onerror = rejectImage(i); 583 for (var i = 0; i < query.length; ++i) {
523 } else { 584 var val = query[i].split('=');
524 rejectImage(i)(null); 585 if (val[0] == '')
525 } 586 continue;
526 } 587 queryArgs[decodeURIComponent(val[0])] = decodeURIComponent(val[1]);
527 } 588 }
528 589
529 var favicon = tile.querySelector('.mv-favicon'); 590 // Apply class for icon NTP, if specified.
530 if (data.faviconUrl) { 591 USE_ICONS = queryArgs['icons'] == '1';
531 var fi = document.createElement('img'); 592 if ('ntl' in queryArgs) {
532 fi.src = data.faviconUrl; 593 var ntl = parseInt(queryArgs['ntl'], 10);
533 // Set the title to empty so screen readers won't say the image name. 594 if (isFinite(ntl))
534 fi.title = ''; 595 NUM_TITLE_LINES = ntl;
535 loadedCounter += 1; 596 }
536 fi.addEventListener('load', countLoad); 597
537 fi.addEventListener('error', countLoad); 598 // Duplicating NTP_DESIGN.mainClass.
538 fi.addEventListener('error', function(ev) { 599 document.querySelector('#most-visited')
539 favicon.classList.add('failed-favicon'); 600 .classList.add(USE_ICONS ? 'icon-ntp' : 'thumb-ntp');
540 }); 601
541 favicon.appendChild(fi); 602 // Enable RTL.
542 } else { 603 if (queryArgs['rtl'] == '1') {
543 favicon.classList.add('failed-favicon'); 604 var html = document.querySelector('html');
544 } 605 html.dir = 'rtl';
545 } 606 }
546 607
547 var mvx = tile.querySelector('.mv-x'); 608 window.addEventListener('message', handlePostMessage);
548 mvx.addEventListener('click', function(ev) { 609 };
549 removeAllOldTiles(); 610
550 blacklistTile(tile); 611
551 ev.preventDefault(); 612 window.addEventListener('DOMContentLoaded', init);
552 ev.stopPropagation();
553 });
554
555 return tile;
556 };
557
558
559 /**
560 * Do some initialization and parses the query arguments passed to the iframe.
561 */
562 var init = function() {
563 // Creates a new DOM element to hold the tiles.
564 tiles = document.createElement('div');
565
566 // Parse query arguments.
567 var query = window.location.search.substring(1).split('&');
568 queryArgs = {};
569 for (var i = 0; i < query.length; ++i) {
570 var val = query[i].split('=');
571 if (val[0] == '') continue;
572 queryArgs[decodeURIComponent(val[0])] = decodeURIComponent(val[1]);
573 }
574
575 // Apply class for icon NTP, if specified.
576 USE_ICONS = queryArgs['icons'] == '1';
577 if ('ntl' in queryArgs) {
578 var ntl = parseInt(queryArgs['ntl'], 10);
579 if (isFinite(ntl))
580 NUM_TITLE_LINES = ntl;
581 }
582
583 // Duplicating NTP_DESIGN.mainClass.
584 document.querySelector('#most-visited').classList.add(
585 USE_ICONS ? 'icon-ntp' : 'thumb-ntp');
586
587 // Enable RTL.
588 if (queryArgs['rtl'] == '1') {
589 var html = document.querySelector('html');
590 html.dir = 'rtl';
591 }
592
593 window.addEventListener('message', handlePostMessage);
594 };
595
596
597 window.addEventListener('DOMContentLoaded', init);
598 })(); 613 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698