cros: Remove default pinned apps trial.
[chromium-blink-merge.git] / chrome / browser / resources / local_ntp / local_ntp.js
blob013cb0e20acf6dbab29b1969b5d4ea8785eab44f
1 // Copyright 2013 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.
6 /**
7  * @fileoverview The local InstantExtended NTP.
8  */
10 /**
11  * Controls rendering the new tab page for InstantExtended.
12  * @return {Object} A limited interface for testing the local NTP.
13  */
14 function LocalNTP() {
15 <include src="../../../../ui/webui/resources/js/assert.js">
19 /**
20  * Enum for classnames.
21  * @enum {string}
22  * @const
23  */
24 var CLASSES = {
25   ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
26   BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
27   BLACKLIST_BUTTON: 'mv-x',
28   DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
29   FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
30   FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
31   // Applies drag focus style to the fakebox
32   FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
33   FAVICON: 'mv-favicon',
34   HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
35   HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
36   HIDE_NOTIFICATION: 'mv-notice-hide',
37   // Vertically centers the most visited section for a non-Google provided page.
38   NON_GOOGLE_PAGE: 'non-google-page',
39   PAGE: 'mv-page', // page tiles
40   PAGE_READY: 'mv-page-ready',  // page tile when ready
41   ROW: 'mv-row',  // tile row
42   RTL: 'rtl',  // Right-to-left language text.
43   THUMBNAIL: 'mv-thumb',
44   THUMBNAIL_MASK: 'mv-mask',
45   TILE: 'mv-tile',
46   TITLE: 'mv-title'
50 /**
51  * Enum for HTML element ids.
52  * @enum {string}
53  * @const
54  */
55 var IDS = {
56   ATTRIBUTION: 'attribution',
57   ATTRIBUTION_TEXT: 'attribution-text',
58   CUSTOM_THEME_STYLE: 'ct-style',
59   FAKEBOX: 'fakebox',
60   FAKEBOX_INPUT: 'fakebox-input',
61   LOGO: 'logo',
62   NOTIFICATION: 'mv-notice',
63   NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
64   NOTIFICATION_MESSAGE: 'mv-msg',
65   NTP_CONTENTS: 'ntp-contents',
66   RESTORE_ALL_LINK: 'mv-restore',
67   TILES: 'mv-tiles',
68   UNDO_LINK: 'mv-undo'
72 /**
73  * Enum for keycodes.
74  * @enum {number}
75  * @const
76  */
77 var KEYCODE = {
78   DELETE: 46,
79   ENTER: 13
83 /**
84  * Enum for the state of the NTP when it is disposed.
85  * @enum {number}
86  * @const
87  */
88 var NTP_DISPOSE_STATE = {
89   NONE: 0,  // Preserve the NTP appearance and functionality
90   DISABLE_FAKEBOX: 1,
91   HIDE_FAKEBOX_AND_LOGO: 2
95 /**
96  * The JavaScript button event value for a middle click.
97  * @type {number}
98  * @const
99  */
100 var MIDDLE_MOUSE_BUTTON = 1;
104  * Possible behaviors for navigateContentWindow.
105  * @enum {number}
106  */
107 var WindowOpenDisposition = {
108   CURRENT_TAB: 1,
109   NEW_BACKGROUND_TAB: 2
114  * The container for the tile elements.
115  * @type {Element}
116  */
117 var tilesContainer;
121  * The notification displayed when a page is blacklisted.
122  * @type {Element}
123  */
124 var notification;
128  * The container for the theme attribution.
129  * @type {Element}
130  */
131 var attribution;
135  * The "fakebox" - an input field that looks like a regular searchbox.  When it
136  * is focused, any text the user types goes directly into the omnibox.
137  * @type {Element}
138  */
139 var fakebox;
143  * The container for NTP elements.
144  * @type {Element}
145  */
146 var ntpContents;
150  * The array of rendered tiles, ordered by appearance.
151  * @type {!Array.<Tile>}
152  */
153 var tiles = [];
157  * The last blacklisted tile if any, which by definition should not be filler.
158  * @type {?Tile}
159  */
160 var lastBlacklistedTile = null;
164  * True if a page has been blacklisted and we're waiting on the
165  * onmostvisitedchange callback. See onMostVisitedChange() for how this
166  * is used.
167  * @type {boolean}
168  */
169 var isBlacklisting = false;
173  * Current number of tiles columns shown based on the window width, including
174  * those that just contain filler.
175  * @type {number}
176  */
177 var numColumnsShown = 0;
181  * True if the user initiated the current most visited change and false
182  * otherwise.
183  * @type {boolean}
184  */
185 var userInitiatedMostVisitedChange = false;
189  * The browser embeddedSearch.newTabPage object.
190  * @type {Object}
191  */
192 var ntpApiHandle;
196  * The browser embeddedSearch.searchBox object.
197  * @type {Object}
198  */
199 var searchboxApiHandle;
203  * The state of the NTP when a query is entered into the Omnibox.
204  * @type {NTP_DISPOSE_STATE}
205  */
206 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE;
210  * The state of the NTP when a query is entered into the Fakebox.
211  * @type {NTP_DISPOSE_STATE}
212  */
213 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO;
217  * Total tile width. Should be equal to mv-tile's width + 2 * border-width.
218  * @private {number}
219  * @const
220  */
221 var TILE_WIDTH = 140;
225  * Margin between tiles. Should be equal to mv-tile's -webkit-margin-start.
226  * @private {number}
227  * @const
228  */
229 var TILE_MARGIN_START = 20;
232 /** @type {number} @const */
233 var MAX_NUM_TILES_TO_SHOW = 8;
236 /** @type {number} @const */
237 var MIN_NUM_COLUMNS = 2;
240 /** @type {number} @const */
241 var MAX_NUM_COLUMNS = 4;
244 /** @type {number} @const */
245 var NUM_ROWS = 2;
249  * Minimum total padding to give to the left and right of the most visited
250  * section. Used to determine how many tiles to show.
251  * @type {number}
252  * @const
253  */
254 var MIN_TOTAL_HORIZONTAL_PADDING = 200;
258  * The filename for a most visited iframe src which shows a page title.
259  * @type {string}
260  * @const
261  */
262 var MOST_VISITED_TITLE_IFRAME = 'title.html';
266  * The filename for a most visited iframe src which shows a thumbnail image.
267  * @type {string}
268  * @const
269  */
270 var MOST_VISITED_THUMBNAIL_IFRAME = 'thumbnail.html';
274  * The hex color for most visited tile elements.
275  * @type {string}
276  * @const
277  */
278 var MOST_VISITED_COLOR = '777777';
282  * The font family for most visited tile elements.
283  * @type {string}
284  * @const
285  */
286 var MOST_VISITED_FONT_FAMILY = 'arial, sans-serif';
290  * The font size for most visited tile elements.
291  * @type {number}
292  * @const
293  */
294 var MOST_VISITED_FONT_SIZE = 11;
298  * Hide most visited tiles for at most this many milliseconds while painting.
299  * @type {number}
300  * @const
301  */
302 var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500;
306  * A Tile is either a rendering of a Most Visited page or "filler" used to
307  * pad out the section when not enough pages exist.
309  * @param {Element} elem The element for rendering the tile.
310  * @param {number=} opt_rid The RID for the corresponding Most Visited page.
311  *     Should only be left unspecified when creating a filler tile.
312  * @constructor
313  */
314 function Tile(elem, opt_rid) {
315   /** @type {Element} */
316   this.elem = elem;
318   /** @type {number|undefined} */
319   this.rid = opt_rid;
324  * Updates the NTP based on the current theme.
325  * @private
326  */
327 function onThemeChange() {
328   var info = ntpApiHandle.themeBackgroundInfo;
329   if (!info)
330     return;
332   var background = [convertToRGBAColor(info.backgroundColorRgba),
333                     info.imageUrl,
334                     info.imageTiling,
335                     info.imageHorizontalAlignment,
336                     info.imageVerticalAlignment].join(' ').trim();
337   document.body.style.background = background;
338   document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
339   updateThemeAttribution(info.attributionUrl);
340   setCustomThemeStyle(info);
341   renderTiles();
346  * Updates the NTP style according to theme.
347  * @param {Object=} opt_themeInfo The information about the theme. If it is
348  * omitted the style will be reverted to the default.
349  * @private
350  */
351 function setCustomThemeStyle(opt_themeInfo) {
352   var customStyleElement = $(IDS.CUSTOM_THEME_STYLE);
353   var head = document.head;
355   if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
356     var themeStyle =
357       '#attribution {' +
358       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
359       '}' +
360       '#mv-msg {' +
361       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' +
362       '}' +
363       '#mv-notice-links span {' +
364       '  color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
365       '}' +
366       '#mv-notice-x {' +
367       '  -webkit-filter: drop-shadow(0 0 0 ' +
368           convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
369       '}' +
370       '.mv-page-ready {' +
371       '  border: 1px solid ' +
372         convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
373       '}' +
374       '.mv-page-ready:hover, .mv-page-ready:focus {' +
375       '  border-color: ' +
376           convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
377       '}';
379     if (customStyleElement) {
380       customStyleElement.textContent = themeStyle;
381     } else {
382       customStyleElement = document.createElement('style');
383       customStyleElement.type = 'text/css';
384       customStyleElement.id = IDS.CUSTOM_THEME_STYLE;
385       customStyleElement.textContent = themeStyle;
386       head.appendChild(customStyleElement);
387     }
389   } else if (customStyleElement) {
390     head.removeChild(customStyleElement);
391   }
396  * Renders the attribution if the URL is present, otherwise hides it.
397  * @param {string} url The URL of the attribution image, if any.
398  * @private
399  */
400 function updateThemeAttribution(url) {
401   if (!url) {
402     setAttributionVisibility_(false);
403     return;
404   }
406   var attributionImage = attribution.querySelector('img');
407   if (!attributionImage) {
408     attributionImage = new Image();
409     attribution.appendChild(attributionImage);
410   }
411   attributionImage.style.content = url;
412   setAttributionVisibility_(true);
417  * Sets the visibility of the theme attribution.
418  * @param {boolean} show True to show the attribution.
419  * @private
420  */
421 function setAttributionVisibility_(show) {
422   if (attribution) {
423     attribution.style.display = show ? '' : 'none';
424   }
428  /**
429  * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
430  * @param {Array.<number>} color Array of rgba color components.
431  * @return {string} CSS color in RGBA format.
432  * @private
433  */
434 function convertToRGBAColor(color) {
435   return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
436                     color[3] / 255 + ')';
441  * Handles a new set of Most Visited page data.
442  */
443 function onMostVisitedChange() {
444   var pages = ntpApiHandle.mostVisited;
446   if (isBlacklisting) {
447     // Trigger the blacklist animation and re-render the tiles when it
448     // completes.
449     var lastBlacklistedTileElement = lastBlacklistedTile.elem;
450     lastBlacklistedTileElement.addEventListener(
451         'webkitTransitionEnd', blacklistAnimationDone);
452     lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST);
454   } else {
455     // Otherwise render the tiles using the new data without animation.
456     tiles = [];
457     for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i) {
458       tiles.push(createTile(pages[i], i));
459     }
460     if (!userInitiatedMostVisitedChange) {
461       tilesContainer.hidden = true;
462       window.setTimeout(function() {
463         if (tilesContainer) {
464           tilesContainer.hidden = false;
465         }
466       }, MOST_VISITED_PAINT_TIMEOUT_MSEC);
467     }
468     renderTiles();
469   }
474  * Renders the current set of tiles.
475  */
476 function renderTiles() {
477   var rows = tilesContainer.children;
478   for (var i = 0; i < rows.length; ++i) {
479     removeChildren(rows[i]);
480   }
482   for (var i = 0, length = tiles.length;
483        i < Math.min(length, numColumnsShown * NUM_ROWS); ++i) {
484     rows[Math.floor(i / numColumnsShown)].appendChild(tiles[i].elem);
485   }
490  * Shows most visited tiles if all child iframes are loaded, and hides them
491  * otherwise.
492  */
493 function updateMostVisitedVisibility() {
494   var iframes = tilesContainer.querySelectorAll('iframe');
495   var ready = true;
496   for (var i = 0, numIframes = iframes.length; i < numIframes; i++) {
497     if (iframes[i].hidden) {
498       ready = false;
499       break;
500     }
501   }
502   if (ready) {
503     tilesContainer.hidden = false;
504     userInitiatedMostVisitedChange = false;
505   }
510  * Builds a URL to display a most visited tile component in an iframe.
511  * @param {string} filename The desired most visited component filename.
512  * @param {number} rid The restricted ID.
513  * @param {string} color The text color for text in the iframe.
514  * @param {string} fontFamily The font family for text in the iframe.
515  * @param {number} fontSize The font size for text in the iframe.
516  * @param {number} position The position of the iframe in the UI.
517  * @return {string} An URL to display the most visited component in an iframe.
518  */
519 function getMostVisitedIframeUrl(filename, rid, color, fontFamily, fontSize,
520     position) {
521   return 'chrome-search://most-visited/' + encodeURIComponent(filename) + '?' +
522       ['rid=' + encodeURIComponent(rid),
523        'c=' + encodeURIComponent(color),
524        'f=' + encodeURIComponent(fontFamily),
525        'fs=' + encodeURIComponent(fontSize),
526        'pos=' + encodeURIComponent(position)].join('&');
531  * Creates a Tile with the specified page data. If no data is provided, a
532  * filler Tile is created.
533  * @param {Object} page The page data.
534  * @param {number} position The position of the tile.
535  * @return {Tile} The new Tile.
536  */
537 function createTile(page, position) {
538   var tileElement = document.createElement('div');
539   tileElement.classList.add(CLASSES.TILE);
541   if (page) {
542     var rid = page.rid;
543     tileElement.classList.add(CLASSES.PAGE);
545     var navigateFunction = function() {
546       ntpApiHandle.navigateContentWindow(rid);
547     };
549     // The click handler for navigating to the page identified by the RID.
550     tileElement.addEventListener('click', navigateFunction);
552     // Make thumbnails tab-accessible.
553     tileElement.setAttribute('tabindex', '1');
554     registerKeyHandler(tileElement, KEYCODE.ENTER, navigateFunction);
556     // The iframe which renders the page title.
557     var titleElement = document.createElement('iframe');
558     titleElement.tabIndex = '-1';
560     // Why iframes have IDs:
561     //
562     // On navigating back to the NTP we see several onmostvisitedchange() events
563     // in series with incrementing RIDs. After the first event, a set of iframes
564     // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
565     // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
566     // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
567     // first set of iframes into the most recent set of iframes.
568     //
569     // Giving iframes distinct ids seems to cause some invalidation and prevent
570     // associating the incorrect data.
571     //
572     // TODO(jered): Find and fix the root (probably Blink) bug.
574     titleElement.src = getMostVisitedIframeUrl(
575         MOST_VISITED_TITLE_IFRAME, rid, MOST_VISITED_COLOR,
576         MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position);
578     // Keep this id here. See comment above.
579     titleElement.id = 'title-' + rid;
580     titleElement.hidden = true;
581     titleElement.onload = function() {
582       titleElement.hidden = false;
583       updateMostVisitedVisibility();
584     };
585     titleElement.className = CLASSES.TITLE;
586     tileElement.appendChild(titleElement);
588     // The iframe which renders either a thumbnail or domain element.
589     var thumbnailElement = document.createElement('iframe');
590     thumbnailElement.tabIndex = '-1';
591     thumbnailElement.src = getMostVisitedIframeUrl(
592         MOST_VISITED_THUMBNAIL_IFRAME, rid, MOST_VISITED_COLOR,
593         MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position);
595     // Keep this id here. See comment above.
596     thumbnailElement.id = 'thumb-' + rid;
597     thumbnailElement.hidden = true;
598     thumbnailElement.onload = function() {
599       thumbnailElement.hidden = false;
600       tileElement.classList.add(CLASSES.PAGE_READY);
601       updateMostVisitedVisibility();
602     };
603     thumbnailElement.className = CLASSES.THUMBNAIL;
604     tileElement.appendChild(thumbnailElement);
606     // A mask to darken the thumbnail on focus.
607     var maskElement = createAndAppendElement(
608         tileElement, 'div', CLASSES.THUMBNAIL_MASK);
610     // The button used to blacklist this page.
611     var blacklistButton = createAndAppendElement(
612         tileElement, 'div', CLASSES.BLACKLIST_BUTTON);
613     var blacklistFunction = generateBlacklistFunction(rid);
614     blacklistButton.addEventListener('click', blacklistFunction);
615     blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip;
617     // When a tile is focused, have delete also blacklist the page.
618     registerKeyHandler(tileElement, KEYCODE.DELETE, blacklistFunction);
620     // The page favicon, if any.
621     var faviconUrl = page.faviconUrl;
622     if (faviconUrl) {
623       var favicon = createAndAppendElement(
624           tileElement, 'div', CLASSES.FAVICON);
625       favicon.style.backgroundImage = 'url(' + faviconUrl + ')';
626     }
627     return new Tile(tileElement, rid);
628   } else {
629     return new Tile(tileElement);
630   }
635  * Generates a function to be called when the page with the corresponding RID
636  * is blacklisted.
637  * @param {number} rid The RID of the page being blacklisted.
638  * @return {function(Event)} A function which handles the blacklisting of the
639  *     page by updating state variables and notifying Chrome.
640  */
641 function generateBlacklistFunction(rid) {
642   return function(e) {
643     // Prevent navigation when the page is being blacklisted.
644     e.stopPropagation();
646     userInitiatedMostVisitedChange = true;
647     isBlacklisting = true;
648     tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON);
649     lastBlacklistedTile = getTileByRid(rid);
650     ntpApiHandle.deleteMostVisitedItem(rid);
651   };
656  * Shows the blacklist notification and triggers a delay to hide it.
657  */
658 function showNotification() {
659   notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
660   notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
661   notification.scrollTop;
662   notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
667  * Hides the blacklist notification.
668  */
669 function hideNotification() {
670   notification.classList.add(CLASSES.HIDE_NOTIFICATION);
675  * Handles the end of the blacklist animation by showing the notification and
676  * re-rendering the new set of tiles.
677  */
678 function blacklistAnimationDone() {
679   showNotification();
680   isBlacklisting = false;
681   tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON);
682   lastBlacklistedTile.elem.removeEventListener(
683       'webkitTransitionEnd', blacklistAnimationDone);
684   // Need to call explicitly to re-render the tiles, since the initial
685   // onmostvisitedchange issued by the blacklist function only triggered
686   // the animation.
687   onMostVisitedChange();
692  * Handles a click on the notification undo link by hiding the notification and
693  * informing Chrome.
694  */
695 function onUndo() {
696   userInitiatedMostVisitedChange = true;
697   hideNotification();
698   var lastBlacklistedRID = lastBlacklistedTile.rid;
699   if (typeof lastBlacklistedRID != 'undefined')
700     ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID);
705  * Handles a click on the restore all notification link by hiding the
706  * notification and informing Chrome.
707  */
708 function onRestoreAll() {
709   userInitiatedMostVisitedChange = true;
710   hideNotification();
711   ntpApiHandle.undoAllMostVisitedDeletions();
716  * Re-renders the tiles if the number of columns has changed.  As a temporary
717  * fix for crbug/240510, updates the width of the fakebox and most visited tiles
718  * container.
719  */
720 function onResize() {
721   // If innerWidth is zero, then use the maximum snap size.
722   var innerWidth = window.innerWidth || 820;
724   // These values should remain in sync with local_ntp.css.
725   // TODO(jeremycho): Delete once the root cause of crbug/240510 is resolved.
726   var setWidths = function(tilesContainerWidth) {
727     tilesContainer.style.width = tilesContainerWidth + 'px';
728     if (fakebox)
729       fakebox.style.width = (tilesContainerWidth - 2) + 'px';
730   };
731   if (innerWidth >= 820)
732     setWidths(620);
733   else if (innerWidth >= 660)
734     setWidths(460);
735   else
736     setWidths(300);
738   var tileRequiredWidth = TILE_WIDTH + TILE_MARGIN_START;
739   // Adds margin-start to the available width to compensate the extra margin
740   // counted above for the first tile (which does not have a margin-start).
741   var availableWidth = innerWidth + TILE_MARGIN_START -
742       MIN_TOTAL_HORIZONTAL_PADDING;
743   var numColumnsToShow = Math.floor(availableWidth / tileRequiredWidth);
744   numColumnsToShow = Math.max(MIN_NUM_COLUMNS,
745                               Math.min(MAX_NUM_COLUMNS, numColumnsToShow));
746   if (numColumnsToShow != numColumnsShown) {
747     numColumnsShown = numColumnsToShow;
748     renderTiles();
749   }
754  * Returns the tile corresponding to the specified page RID.
755  * @param {number} rid The page RID being looked up.
756  * @return {Tile} The corresponding tile.
757  */
758 function getTileByRid(rid) {
759   for (var i = 0, length = tiles.length; i < length; ++i) {
760     var tile = tiles[i];
761     if (tile.rid == rid)
762       return tile;
763   }
764   return null;
769  * Handles new input by disposing the NTP, according to where the input was
770  * entered.
771  */
772 function onInputStart() {
773   if (fakebox && isFakeboxFocused()) {
774     setFakeboxFocus(false);
775     setFakeboxDragFocus(false);
776     disposeNtp(true);
777   } else if (!isFakeboxFocused()) {
778     disposeNtp(false);
779   }
784  * Disposes the NTP, according to where the input was entered.
785  * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
786  */
787 function disposeNtp(wasFakeboxInput) {
788   var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior;
789   if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX)
790     setFakeboxActive(false);
791   else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO)
792     setFakeboxAndLogoVisibility(false);
797  * Restores the NTP (re-enables the fakebox and unhides the logo.)
798  */
799 function restoreNtp() {
800   setFakeboxActive(true);
801   setFakeboxAndLogoVisibility(true);
806  * @param {boolean} focus True to focus the fakebox.
807  */
808 function setFakeboxFocus(focus) {
809   document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
813  * @param {boolean} focus True to show a dragging focus to the fakebox.
814  */
815 function setFakeboxDragFocus(focus) {
816   document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
820  * @return {boolean} True if the fakebox has focus.
821  */
822 function isFakeboxFocused() {
823   return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
824       document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
829  * @param {boolean} enable True to enable the fakebox.
830  */
831 function setFakeboxActive(enable) {
832   document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable);
837  * @param {!Event} event The click event.
838  * @return {boolean} True if the click occurred in an enabled fakebox.
839  */
840 function isFakeboxClick(event) {
841   return fakebox.contains(event.target) &&
842       !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE);
847  * @param {boolean} show True to show the fakebox and logo.
848  */
849 function setFakeboxAndLogoVisibility(show) {
850   document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show);
855  * Shortcut for document.getElementById.
856  * @param {string} id of the element.
857  * @return {HTMLElement} with the id.
858  */
859 function $(id) {
860   return document.getElementById(id);
865  * Utility function which creates an element with an optional classname and
866  * appends it to the specified parent.
867  * @param {Element} parent The parent to append the new element.
868  * @param {string} name The name of the new element.
869  * @param {string=} opt_class The optional classname of the new element.
870  * @return {Element} The new element.
871  */
872 function createAndAppendElement(parent, name, opt_class) {
873   var child = document.createElement(name);
874   if (opt_class)
875     child.classList.add(opt_class);
876   parent.appendChild(child);
877   return child;
882  * Removes a node from its parent.
883  * @param {Node} node The node to remove.
884  */
885 function removeNode(node) {
886   node.parentNode.removeChild(node);
891  * Removes all the child nodes on a DOM node.
892  * @param {Node} node Node to remove children from.
893  */
894 function removeChildren(node) {
895   node.innerHTML = '';
900  * @param {!Element} element The element to register the handler for.
901  * @param {number} keycode The keycode of the key to register.
902  * @param {!Function} handler The key handler to register.
903  */
904 function registerKeyHandler(element, keycode, handler) {
905   element.addEventListener('keydown', function(event) {
906     if (event.keyCode == keycode)
907       handler(event);
908   });
913  * @return {Object} the handle to the embeddedSearch API.
914  */
915 function getEmbeddedSearchApiHandle() {
916   if (window.cideb)
917     return window.cideb;
918   if (window.chrome && window.chrome.embeddedSearch)
919     return window.chrome.embeddedSearch;
920   return null;
924  * Extract the desired navigation behavior from a click button.
925  * @param {number} button The Event#button property of a click event.
926  * @return {WindowOpenDisposition} The desired behavior for
927  *     navigateContentWindow.
928  */
929 function getDispositionFromClickButton(button) {
930   if (button == MIDDLE_MOUSE_BUTTON)
931     return WindowOpenDisposition.NEW_BACKGROUND_TAB;
932   return WindowOpenDisposition.CURRENT_TAB;
937  * Prepares the New Tab Page by adding listeners, rendering the current
938  * theme, the most visited pages section, and Google-specific elements for a
939  * Google-provided page.
940  */
941 function init() {
942   tilesContainer = $(IDS.TILES);
943   notification = $(IDS.NOTIFICATION);
944   attribution = $(IDS.ATTRIBUTION);
945   ntpContents = $(IDS.NTP_CONTENTS);
947   for (var i = 0; i < NUM_ROWS; i++) {
948     var row = document.createElement('div');
949     row.classList.add(CLASSES.ROW);
950     tilesContainer.appendChild(row);
951   }
953   if (configData.isGooglePage) {
954     var logo = document.createElement('div');
955     logo.id = IDS.LOGO;
957     fakebox = document.createElement('div');
958     fakebox.id = IDS.FAKEBOX;
959     fakebox.innerHTML =
960         '<input id="' + IDS.FAKEBOX_INPUT +
961             '" autocomplete="off" tabindex="-1" aria-hidden="true">' +
962         '<div id=cursor></div>';
964     ntpContents.insertBefore(fakebox, ntpContents.firstChild);
965     ntpContents.insertBefore(logo, ntpContents.firstChild);
966   } else {
967     document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
968   }
970   var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
971   notificationMessage.textContent =
972       configData.translatedStrings.thumbnailRemovedNotification;
973   var undoLink = $(IDS.UNDO_LINK);
974   undoLink.addEventListener('click', onUndo);
975   registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
976   undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
977   var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
978   restoreAllLink.addEventListener('click', onRestoreAll);
979   registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo);
980   restoreAllLink.textContent =
981       configData.translatedStrings.restoreThumbnailsShort;
982   $(IDS.ATTRIBUTION_TEXT).textContent =
983       configData.translatedStrings.attributionIntro;
985   var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
986   notificationCloseButton.addEventListener('click', hideNotification);
988   userInitiatedMostVisitedChange = false;
989   window.addEventListener('resize', onResize);
990   onResize();
992   var topLevelHandle = getEmbeddedSearchApiHandle();
994   ntpApiHandle = topLevelHandle.newTabPage;
995   ntpApiHandle.onthemechange = onThemeChange;
996   ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
998   ntpApiHandle.oninputstart = onInputStart;
999   ntpApiHandle.oninputcancel = restoreNtp;
1001   if (ntpApiHandle.isInputInProgress)
1002     onInputStart();
1004   onThemeChange();
1005   onMostVisitedChange();
1007   searchboxApiHandle = topLevelHandle.searchBox;
1009   if (fakebox) {
1010     // Listener for updating the key capture state.
1011     document.body.onmousedown = function(event) {
1012       if (isFakeboxClick(event))
1013         searchboxApiHandle.startCapturingKeyStrokes();
1014       else if (isFakeboxFocused())
1015         searchboxApiHandle.stopCapturingKeyStrokes();
1016     };
1017     searchboxApiHandle.onkeycapturechange = function() {
1018       setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1019     };
1020     var inputbox = $(IDS.FAKEBOX_INPUT);
1021     if (inputbox) {
1022       inputbox.onpaste = function(event) {
1023         event.preventDefault();
1024         searchboxApiHandle.paste();
1025       };
1026       inputbox.ondrop = function(event) {
1027         event.preventDefault();
1028         var text = event.dataTransfer.getData('text/plain');
1029         if (text) {
1030           searchboxApiHandle.paste(text);
1031         }
1032       };
1033       inputbox.ondragenter = function() {
1034         setFakeboxDragFocus(true);
1035       };
1036       inputbox.ondragleave = function() {
1037         setFakeboxDragFocus(false);
1038       };
1039     }
1041     // Update the fakebox style to match the current key capturing state.
1042     setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1043   }
1045   if (searchboxApiHandle.rtl) {
1046     $(IDS.NOTIFICATION).dir = 'rtl';
1047     // Add class for setting alignments based on language directionality.
1048     document.body.classList.add(CLASSES.RTL);
1049     $(IDS.TILES).dir = 'rtl';
1050   }
1055  * Binds event listeners.
1056  */
1057 function listen() {
1058   document.addEventListener('DOMContentLoaded', init);
1061 return {
1062   init: init,
1063   listen: listen
1067 if (!window.localNTPUnitTest) {
1068   LocalNTP().listen();