Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / resources / local_ntp / most_visited_single.js
blob952c333242cadd100b548cc248c945e99587c21d
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. */
5 // Single iframe for NTP tiles.
6 (function() {
7 'use strict';
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
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 // A NTP Tile has finished loading (successfully or failing).
45 NTP_TILE_LOADED: 10,
49 /**
50 * Total number of tiles to show at any time. If the host page doesn't send
51 * enough tiles, we fill them blank.
52 * @const {number}
54 var NUMBER_OF_TILES = 8;
57 /**
58 * The origin of this request.
59 * @const {string}
61 var DOMAIN_ORIGIN = '{{ORIGIN}}';
64 /**
65 * Counter for DOM elements that we are waiting to finish loading.
66 * @type {number}
68 var loadedCounter = 1;
71 /**
72 * DOM element containing the tiles we are going to present next.
73 * Works as a double-buffer that is shown when we receive a "show" postMessage.
74 * @type {Element}
76 var tiles = null;
79 /**
80 * List of parameters passed by query args.
81 * @type {Object}
83 var queryArgs = {};
86 /**
87 * Log an event on the NTP.
88 * @param {number} eventType Event from LOG_TYPE.
90 var logEvent = function(eventType) {
91 chrome.embeddedSearch.newTabPage.logEvent(eventType);
95 /**
96 * Down counts the DOM elements that we are waiting for the page to load.
97 * When we get to 0, we send a message to the parent window.
98 * This is usually used as an EventListener of onload/onerror.
100 var countLoad = function() {
101 loadedCounter -= 1;
102 if (loadedCounter <= 0) {
103 logEvent(LOG_TYPE.NTP_TILE_LOADED);
104 window.parent.postMessage({cmd: 'loaded'}, DOMAIN_ORIGIN);
105 loadedCounter = 1;
111 * Handles postMessages coming from the host page to the iframe.
112 * Mostly, it dispatches every command to handleCommand.
114 var handlePostMessage = function(event) {
115 if (event.data instanceof Array) {
116 for (var i = 0; i < event.data.length; ++i) {
117 handleCommand(event.data[i]);
119 } else {
120 handleCommand(event.data);
126 * Handles a single command coming from the host page to the iframe.
127 * We try to keep the logic here to a minimum and just dispatch to the relevant
128 * functions.
130 var handleCommand = function(data) {
131 var cmd = data.cmd;
133 if (cmd == 'tile') {
134 addTile(data);
135 } else if (cmd == 'show') {
136 showTiles();
137 hideOverflowTiles(data);
138 countLoad();
139 } else if (cmd == 'updateTheme') {
140 updateTheme(data);
141 } else if (cmd == 'tilesVisible') {
142 hideOverflowTiles(data);
143 } else {
144 console.error('Unknown command: ' + JSON.stringify(data));
149 var updateTheme = function(info) {
150 var themeStyle = [];
152 if (info.tileBorderColor) {
153 themeStyle.push('.mv-tile {' +
154 'border: 1px solid ' + info.tileBorderColor + '; }');
156 if (info.tileHoverBorderColor) {
157 themeStyle.push('.mv-tile:hover {' +
158 'border-color: ' + info.tileHoverBorderColor + '; }');
160 if (info.isThemeDark) {
161 themeStyle.push('.mv-tile, .mv-empty-tile { background: rgb(51,51,51); }');
162 themeStyle.push('.mv-thumb.failed-img { background-color: #555; }');
163 themeStyle.push('.mv-thumb.failed-img::after { border-color: #333; }');
164 themeStyle.push('.mv-x { ' +
165 'background: linear-gradient(to left, ' +
166 'rgb(51,51,51) 60%, transparent); }');
167 themeStyle.push('html[dir=rtl] .mv-x { ' +
168 'background: linear-gradient(to right, ' +
169 'rgb(51,51,51) 60%, transparent); }');
170 themeStyle.push('.mv-x::after { ' +
171 'background-color: rgba(255,255,255,0.7); }');
172 themeStyle.push('.mv-x:hover::after { background-color: #fff; }');
173 themeStyle.push('.mv-x:active::after { ' +
174 'background-color: rgba(255,255,255,0.5); }');
176 if (info.tileTitleColor) {
177 themeStyle.push('body { color: ' + info.tileTitleColor + '; }');
180 document.querySelector('#custom-theme').textContent = themeStyle.join('\n');
185 * Hides extra tiles that don't fit on screen.
187 var hideOverflowTiles = function(data) {
188 var tileAndEmptyTileList = document.querySelectorAll(
189 '#mv-tiles .mv-tile,#mv-tiles .mv-empty-tile');
190 for (var i = 0; i < tileAndEmptyTileList.length; ++i) {
191 tileAndEmptyTileList[i].classList.toggle('hidden', i >= data.maxVisible);
197 * Removes all old instances of #mv-tiles that are pending for deletion.
199 var removeAllOldTiles = function() {
200 var parent = document.querySelector('#most-visited');
201 var oldList = parent.querySelectorAll('.mv-tiles-old');
202 for (var i = 0; i < oldList.length; ++i) {
203 parent.removeChild(oldList[i]);
209 * Called when the host page has finished sending us tile information and
210 * we are ready to show the new tiles and drop the old ones.
212 var showTiles = function() {
213 // Store the tiles on the current closure.
214 var cur = tiles;
216 // Create empty tiles until we have NUMBER_OF_TILES.
217 while (cur.childNodes.length < NUMBER_OF_TILES) {
218 addTile({});
221 var parent = document.querySelector('#most-visited');
223 // Mark old tile DIV for removal after the transition animation is done.
224 var old = parent.querySelector('#mv-tiles');
225 if (old) {
226 old.removeAttribute('id');
227 old.classList.add('mv-tiles-old');
228 cur.addEventListener('webkitTransitionEnd', function(ev) {
229 if (ev.target === cur) {
230 removeAllOldTiles();
235 // Add new tileset.
236 cur.id = 'mv-tiles';
237 parent.appendChild(cur);
238 // We want the CSS transition to trigger, so need to add to the DOM before
239 // setting the style.
240 setTimeout(function() {
241 cur.style.opacity = 1.0;
242 }, 0);
244 // Make sure the tiles variable contain the next tileset we may use.
245 tiles = document.createElement('div');
250 * Called when the host page wants to add a suggestion tile.
251 * For Most Visited, it grabs the data from Chrome and pass on.
252 * For host page generated it just passes the data.
253 * @param {object} args Data for the tile to be rendered.
255 var addTile = function(args) {
256 if (args.rid) {
257 var data = chrome.embeddedSearch.searchBox.getMostVisitedItemData(args.rid);
258 data.faviconUrl = 'chrome-search://favicon/size/16@' +
259 window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.rid;
260 tiles.appendChild(renderTile(data));
261 logEvent(LOG_TYPE.NTP_CLIENT_SIDE_SUGGESTION);
262 } else {
263 tiles.appendChild(renderTile(null));
269 * Called when the user decided to add a tile to the blacklist.
270 * It sets of the animation for the blacklist and sends the blacklisted id
271 * to the host page.
272 * @param {Element} tile DOM node of the tile we want to remove.
274 var blacklistTile = function(tile) {
275 tile.classList.add('blacklisted');
276 tile.addEventListener('webkitTransitionEnd', function(ev) {
277 if (ev.propertyName != 'width') return;
279 window.parent.postMessage({cmd: 'tileBlacklisted',
280 rid: Number(tile.getAttribute('data-rid'))},
281 DOMAIN_ORIGIN);
287 * Renders a MostVisited tile to the DOM.
288 * @param {object} data Object containing rid, url, title, favicon, thumbnail.
289 * data is null if you want to construct an empty tile.
291 var renderTile = function(data) {
292 var tile = document.createElement('a');
294 if (data == null) {
295 tile.className = 'mv-empty-tile';
296 return tile;
299 logEvent(LOG_TYPE.NTP_TILE);
301 tile.className = 'mv-tile';
302 tile.setAttribute('data-rid', data.rid);
303 var tooltip = queryArgs['removeTooltip'] || '';
304 var html = [];
305 html.push('<div class="mv-favicon"></div>');
306 html.push('<div class="mv-title"></div><div class="mv-thumb"></div>');
307 html.push('<div title="' + tooltip + '" class="mv-x"></div>');
308 tile.innerHTML = html.join('');
310 tile.href = data.url;
311 tile.title = data.title;
312 tile.addEventListener('keypress', function(ev) {
313 if (ev.keyCode == 127) { // DELETE
314 blacklistTile(tile);
315 ev.stopPropagation();
316 return false;
319 // TODO(fserb): remove this or at least change to mouseenter.
320 tile.addEventListener('mouseover', function() {
321 logEvent(LOG_TYPE.NTP_MOUSEOVER);
324 var title = tile.querySelector('.mv-title');
325 title.innerText = data.title;
326 title.style.direction = data.direction || 'ltr';
328 var thumb = tile.querySelector('.mv-thumb');
329 if (data.largeIconUrl || data.thumbnailUrl) {
330 var img = document.createElement('img');
331 img.title = data.title;
332 if (data.largeIconUrl) {
333 img.src = data.largeIconUrl;
334 img.classList.add('large-icon');
335 } else {
336 img.src = data.thumbnailUrl;
337 img.classList.add('thumbnail');
339 loadedCounter += 1;
340 img.addEventListener('load', countLoad);
341 if (data.largeIconUrl) {
342 img.addEventListener('load', function(ev) {
343 thumb.classList.add('large-icon-outer');
346 img.addEventListener('error', countLoad);
347 img.addEventListener('error', function(ev) {
348 thumb.classList.add('failed-img');
349 thumb.removeChild(img);
350 logEvent(LOG_TYPE.NTP_THUMBNAIL_ERROR);
352 thumb.appendChild(img);
353 logEvent(LOG_TYPE.NTP_THUMBNAIL_TILE);
354 } else {
355 thumb.classList.add('failed-img');
358 var favicon = tile.querySelector('.mv-favicon');
359 if (data.faviconUrl) {
360 var fi = document.createElement('img');
361 fi.src = data.faviconUrl;
362 // Set the title to empty so screen readers won't say the image name.
363 fi.title = '';
364 loadedCounter += 1;
365 fi.addEventListener('load', countLoad);
366 fi.addEventListener('error', countLoad);
367 fi.addEventListener('error', function(ev) {
368 favicon.classList.add('failed-favicon');
370 favicon.appendChild(fi);
371 } else {
372 favicon.classList.add('failed-favicon');
375 var mvx = tile.querySelector('.mv-x');
376 mvx.addEventListener('click', function(ev) {
377 removeAllOldTiles();
378 blacklistTile(tile);
379 ev.preventDefault();
380 ev.stopPropagation();
383 return tile;
388 * Do some initialization and parses the query arguments passed to the iframe.
390 var init = function() {
391 // Creates a new DOM element to hold the tiles.
392 tiles = document.createElement('div');
394 // Parse query arguments.
395 var query = window.location.search.substring(1).split('&');
396 queryArgs = {};
397 for (var i = 0; i < query.length; ++i) {
398 var val = query[i].split('=');
399 if (val[0] == '') continue;
400 queryArgs[decodeURIComponent(val[0])] = decodeURIComponent(val[1]);
403 // Enable RTL.
404 if (queryArgs['rtl'] == '1') {
405 var html = document.querySelector('html');
406 html.dir = 'rtl';
409 window.addEventListener('message', handlePostMessage);
413 window.addEventListener('DOMContentLoaded', init);
414 })();