Fix infinite recursion on hiding panel when created during fullscreen mode.
[chromium-blink-merge.git] / chrome / browser / resources / local_ntp / local_ntp.js
blobfb8b873216619a004c19a623ce170a4cb6d0796b
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 */
11 /**
12 * Controls rendering the new tab page for InstantExtended.
13 * @return {Object} A limited interface for testing the local NTP.
15 function LocalNTP() {
16 <include src="../../../../ui/webui/resources/js/assert.js">
17 <include src="window_disposition_util.js">
20 /**
21 * Enum for classnames.
22 * @enum {string}
23 * @const
25 var CLASSES = {
26 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
27 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
28 BLACKLIST_BUTTON: 'mv-x',
29 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
30 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
31 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
32 // Applies drag focus style to the fakebox
33 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
34 FAVICON: 'mv-favicon',
35 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
36 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
37 HIDE_NOTIFICATION: 'mv-notice-hide',
38 // Vertically centers the most visited section for a non-Google provided page.
39 NON_GOOGLE_PAGE: 'non-google-page',
40 PAGE: 'mv-page', // page tiles
41 PAGE_READY: 'mv-page-ready', // page tile when ready
42 ROW: 'mv-row', // tile row
43 RTL: 'rtl', // Right-to-left language text.
44 THUMBNAIL: 'mv-thumb',
45 THUMBNAIL_MASK: 'mv-mask',
46 TILE: 'mv-tile',
47 TITLE: 'mv-title'
51 /**
52 * Enum for HTML element ids.
53 * @enum {string}
54 * @const
56 var IDS = {
57 ATTRIBUTION: 'attribution',
58 ATTRIBUTION_TEXT: 'attribution-text',
59 CUSTOM_THEME_STYLE: 'ct-style',
60 FAKEBOX: 'fakebox',
61 FAKEBOX_INPUT: 'fakebox-input',
62 LOGO: 'logo',
63 NOTIFICATION: 'mv-notice',
64 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
65 NOTIFICATION_MESSAGE: 'mv-msg',
66 NTP_CONTENTS: 'ntp-contents',
67 RESTORE_ALL_LINK: 'mv-restore',
68 TILES: 'mv-tiles',
69 UNDO_LINK: 'mv-undo'
73 /**
74 * Enum for keycodes.
75 * @enum {number}
76 * @const
78 var KEYCODE = {
79 DELETE: 46,
80 ENTER: 13
84 /**
85 * Enum for the state of the NTP when it is disposed.
86 * @enum {number}
87 * @const
89 var NTP_DISPOSE_STATE = {
90 NONE: 0, // Preserve the NTP appearance and functionality
91 DISABLE_FAKEBOX: 1,
92 HIDE_FAKEBOX_AND_LOGO: 2
96 /**
97 * The JavaScript button event value for a middle click.
98 * @type {number}
99 * @const
101 var MIDDLE_MOUSE_BUTTON = 1;
105 * The container for the tile elements.
106 * @type {Element}
108 var tilesContainer;
112 * The notification displayed when a page is blacklisted.
113 * @type {Element}
115 var notification;
119 * The container for the theme attribution.
120 * @type {Element}
122 var attribution;
126 * The "fakebox" - an input field that looks like a regular searchbox. When it
127 * is focused, any text the user types goes directly into the omnibox.
128 * @type {Element}
130 var fakebox;
134 * The container for NTP elements.
135 * @type {Element}
137 var ntpContents;
141 * The array of rendered tiles, ordered by appearance.
142 * @type {!Array.<Tile>}
144 var tiles = [];
148 * The last blacklisted tile if any, which by definition should not be filler.
149 * @type {?Tile}
151 var lastBlacklistedTile = null;
155 * True if a page has been blacklisted and we're waiting on the
156 * onmostvisitedchange callback. See onMostVisitedChange() for how this
157 * is used.
158 * @type {boolean}
160 var isBlacklisting = false;
164 * Current number of tiles columns shown based on the window width, including
165 * those that just contain filler.
166 * @type {number}
168 var numColumnsShown = 0;
172 * True if the user initiated the current most visited change and false
173 * otherwise.
174 * @type {boolean}
176 var userInitiatedMostVisitedChange = false;
180 * The browser embeddedSearch.newTabPage object.
181 * @type {Object}
183 var ntpApiHandle;
187 * The browser embeddedSearch.searchBox object.
188 * @type {Object}
190 var searchboxApiHandle;
194 * The state of the NTP when a query is entered into the Omnibox.
195 * @type {NTP_DISPOSE_STATE}
197 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE;
201 * The state of the NTP when a query is entered into the Fakebox.
202 * @type {NTP_DISPOSE_STATE}
204 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO;
208 * Total tile width. Should be equal to mv-tile's width + 2 * border-width.
209 * @private {number}
210 * @const
212 var TILE_WIDTH = 140;
216 * Margin between tiles. Should be equal to mv-tile's -webkit-margin-start.
217 * @private {number}
218 * @const
220 var TILE_MARGIN_START = 20;
223 /** @type {number} @const */
224 var MAX_NUM_TILES_TO_SHOW = 8;
227 /** @type {number} @const */
228 var MIN_NUM_COLUMNS = 2;
231 /** @type {number} @const */
232 var MAX_NUM_COLUMNS = 4;
235 /** @type {number} @const */
236 var NUM_ROWS = 2;
240 * Minimum total padding to give to the left and right of the most visited
241 * section. Used to determine how many tiles to show.
242 * @type {number}
243 * @const
245 var MIN_TOTAL_HORIZONTAL_PADDING = 200;
249 * The filename for a most visited iframe src which shows a page title.
250 * @type {string}
251 * @const
253 var MOST_VISITED_TITLE_IFRAME = 'title.html';
257 * The filename for a most visited iframe src which shows a thumbnail image.
258 * @type {string}
259 * @const
261 var MOST_VISITED_THUMBNAIL_IFRAME = 'thumbnail.html';
265 * The hex color for most visited tile elements.
266 * @type {string}
267 * @const
269 var MOST_VISITED_COLOR = '777777';
273 * The font family for most visited tile elements.
274 * @type {string}
275 * @const
277 var MOST_VISITED_FONT_FAMILY = 'arial, sans-serif';
281 * The font size for most visited tile elements.
282 * @type {number}
283 * @const
285 var MOST_VISITED_FONT_SIZE = 11;
289 * Hide most visited tiles for at most this many milliseconds while painting.
290 * @type {number}
291 * @const
293 var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500;
297 * A Tile is either a rendering of a Most Visited page or "filler" used to
298 * pad out the section when not enough pages exist.
300 * @param {Element} elem The element for rendering the tile.
301 * @param {number=} opt_rid The RID for the corresponding Most Visited page.
302 * Should only be left unspecified when creating a filler tile.
303 * @constructor
305 function Tile(elem, opt_rid) {
306 /** @type {Element} */
307 this.elem = elem;
309 /** @type {number|undefined} */
310 this.rid = opt_rid;
315 * Updates the NTP based on the current theme.
316 * @private
318 function onThemeChange() {
319 var info = ntpApiHandle.themeBackgroundInfo;
320 if (!info)
321 return;
323 var background = [convertToRGBAColor(info.backgroundColorRgba),
324 info.imageUrl,
325 info.imageTiling,
326 info.imageHorizontalAlignment,
327 info.imageVerticalAlignment].join(' ').trim();
328 document.body.style.background = background;
329 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
330 updateThemeAttribution(info.attributionUrl);
331 setCustomThemeStyle(info);
332 renderTiles();
337 * Updates the NTP style according to theme.
338 * @param {Object=} opt_themeInfo The information about the theme. If it is
339 * omitted the style will be reverted to the default.
340 * @private
342 function setCustomThemeStyle(opt_themeInfo) {
343 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE);
344 var head = document.head;
346 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
347 var themeStyle =
348 '#attribution {' +
349 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
350 '}' +
351 '#mv-msg {' +
352 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' +
353 '}' +
354 '#mv-notice-links span {' +
355 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
356 '}' +
357 '#mv-notice-x {' +
358 ' -webkit-filter: drop-shadow(0 0 0 ' +
359 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
360 '}' +
361 '.mv-page-ready {' +
362 ' border: 1px solid ' +
363 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
364 '}' +
365 '.mv-page-ready:hover, .mv-page-ready:focus {' +
366 ' border-color: ' +
367 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
368 '}';
370 if (customStyleElement) {
371 customStyleElement.textContent = themeStyle;
372 } else {
373 customStyleElement = document.createElement('style');
374 customStyleElement.type = 'text/css';
375 customStyleElement.id = IDS.CUSTOM_THEME_STYLE;
376 customStyleElement.textContent = themeStyle;
377 head.appendChild(customStyleElement);
380 } else if (customStyleElement) {
381 head.removeChild(customStyleElement);
387 * Renders the attribution if the URL is present, otherwise hides it.
388 * @param {string} url The URL of the attribution image, if any.
389 * @private
391 function updateThemeAttribution(url) {
392 if (!url) {
393 setAttributionVisibility_(false);
394 return;
397 var attributionImage = attribution.querySelector('img');
398 if (!attributionImage) {
399 attributionImage = new Image();
400 attribution.appendChild(attributionImage);
402 attributionImage.style.content = url;
403 setAttributionVisibility_(true);
408 * Sets the visibility of the theme attribution.
409 * @param {boolean} show True to show the attribution.
410 * @private
412 function setAttributionVisibility_(show) {
413 if (attribution) {
414 attribution.style.display = show ? '' : 'none';
420 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
421 * @param {Array.<number>} color Array of rgba color components.
422 * @return {string} CSS color in RGBA format.
423 * @private
425 function convertToRGBAColor(color) {
426 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
427 color[3] / 255 + ')';
432 * Handles a new set of Most Visited page data.
434 function onMostVisitedChange() {
435 var pages = ntpApiHandle.mostVisited;
437 if (isBlacklisting) {
438 // Trigger the blacklist animation and re-render the tiles when it
439 // completes.
440 var lastBlacklistedTileElement = lastBlacklistedTile.elem;
441 lastBlacklistedTileElement.addEventListener(
442 'webkitTransitionEnd', blacklistAnimationDone);
443 lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST);
445 } else {
446 // Otherwise render the tiles using the new data without animation.
447 tiles = [];
448 for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i) {
449 tiles.push(createTile(pages[i], i));
451 if (!userInitiatedMostVisitedChange) {
452 tilesContainer.hidden = true;
453 window.setTimeout(function() {
454 if (tilesContainer) {
455 tilesContainer.hidden = false;
457 }, MOST_VISITED_PAINT_TIMEOUT_MSEC);
459 renderTiles();
465 * Renders the current set of tiles.
467 function renderTiles() {
468 var rows = tilesContainer.children;
469 for (var i = 0; i < rows.length; ++i) {
470 removeChildren(rows[i]);
473 for (var i = 0, length = tiles.length;
474 i < Math.min(length, numColumnsShown * NUM_ROWS); ++i) {
475 rows[Math.floor(i / numColumnsShown)].appendChild(tiles[i].elem);
481 * Shows most visited tiles if all child iframes are loaded, and hides them
482 * otherwise.
484 function updateMostVisitedVisibility() {
485 var iframes = tilesContainer.querySelectorAll('iframe');
486 var ready = true;
487 for (var i = 0, numIframes = iframes.length; i < numIframes; i++) {
488 if (iframes[i].hidden) {
489 ready = false;
490 break;
493 if (ready) {
494 tilesContainer.hidden = false;
495 userInitiatedMostVisitedChange = false;
501 * Builds a URL to display a most visited tile component in an iframe.
502 * @param {string} filename The desired most visited component filename.
503 * @param {number} rid The restricted ID.
504 * @param {string} color The text color for text in the iframe.
505 * @param {string} fontFamily The font family for text in the iframe.
506 * @param {number} fontSize The font size for text in the iframe.
507 * @param {number} position The position of the iframe in the UI.
508 * @return {string} An URL to display the most visited component in an iframe.
510 function getMostVisitedIframeUrl(filename, rid, color, fontFamily, fontSize,
511 position) {
512 return 'chrome-search://most-visited/' + encodeURIComponent(filename) + '?' +
513 ['rid=' + encodeURIComponent(rid),
514 'c=' + encodeURIComponent(color),
515 'f=' + encodeURIComponent(fontFamily),
516 'fs=' + encodeURIComponent(fontSize),
517 'pos=' + encodeURIComponent(position)].join('&');
522 * Creates a Tile with the specified page data. If no data is provided, a
523 * filler Tile is created.
524 * @param {Object} page The page data.
525 * @param {number} position The position of the tile.
526 * @return {Tile} The new Tile.
528 function createTile(page, position) {
529 var tileElement = document.createElement('div');
530 tileElement.classList.add(CLASSES.TILE);
532 if (page) {
533 var rid = page.rid;
534 tileElement.classList.add(CLASSES.PAGE);
536 var navigateFunction = function(e) {
537 e.preventDefault();
538 ntpApiHandle.navigateContentWindow(rid, getDispositionFromEvent(e));
541 // The click handler for navigating to the page identified by the RID.
542 tileElement.addEventListener('click', navigateFunction);
544 // Make thumbnails tab-accessible.
545 tileElement.setAttribute('tabindex', '1');
546 registerKeyHandler(tileElement, KEYCODE.ENTER, navigateFunction);
548 // The iframe which renders the page title.
549 var titleElement = document.createElement('iframe');
550 titleElement.tabIndex = '-1';
552 // Why iframes have IDs:
554 // On navigating back to the NTP we see several onmostvisitedchange() events
555 // in series with incrementing RIDs. After the first event, a set of iframes
556 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
557 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
558 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
559 // first set of iframes into the most recent set of iframes.
561 // Giving iframes distinct ids seems to cause some invalidation and prevent
562 // associating the incorrect data.
564 // TODO(jered): Find and fix the root (probably Blink) bug.
566 titleElement.src = getMostVisitedIframeUrl(
567 MOST_VISITED_TITLE_IFRAME, rid, MOST_VISITED_COLOR,
568 MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position);
570 // Keep this id here. See comment above.
571 titleElement.id = 'title-' + rid;
572 titleElement.hidden = true;
573 titleElement.onload = function() {
574 titleElement.hidden = false;
575 updateMostVisitedVisibility();
577 titleElement.className = CLASSES.TITLE;
578 tileElement.appendChild(titleElement);
580 // The iframe which renders either a thumbnail or domain element.
581 var thumbnailElement = document.createElement('iframe');
582 thumbnailElement.tabIndex = '-1';
583 thumbnailElement.src = getMostVisitedIframeUrl(
584 MOST_VISITED_THUMBNAIL_IFRAME, rid, MOST_VISITED_COLOR,
585 MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position);
587 // Keep this id here. See comment above.
588 thumbnailElement.id = 'thumb-' + rid;
589 thumbnailElement.hidden = true;
590 thumbnailElement.onload = function() {
591 thumbnailElement.hidden = false;
592 tileElement.classList.add(CLASSES.PAGE_READY);
593 updateMostVisitedVisibility();
595 thumbnailElement.className = CLASSES.THUMBNAIL;
596 tileElement.appendChild(thumbnailElement);
598 // A mask to darken the thumbnail on focus.
599 var maskElement = createAndAppendElement(
600 tileElement, 'div', CLASSES.THUMBNAIL_MASK);
602 // The button used to blacklist this page.
603 var blacklistButton = createAndAppendElement(
604 tileElement, 'div', CLASSES.BLACKLIST_BUTTON);
605 var blacklistFunction = generateBlacklistFunction(rid);
606 blacklistButton.addEventListener('click', blacklistFunction);
607 blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip;
609 // When a tile is focused, have delete also blacklist the page.
610 registerKeyHandler(tileElement, KEYCODE.DELETE, blacklistFunction);
612 // The page favicon, if any.
613 var faviconUrl = page.faviconUrl;
614 if (faviconUrl) {
615 var favicon = createAndAppendElement(
616 tileElement, 'div', CLASSES.FAVICON);
617 favicon.style.backgroundImage = 'url(' + faviconUrl + ')';
619 return new Tile(tileElement, rid);
620 } else {
621 return new Tile(tileElement);
627 * Generates a function to be called when the page with the corresponding RID
628 * is blacklisted.
629 * @param {number} rid The RID of the page being blacklisted.
630 * @return {function(Event)} A function which handles the blacklisting of the
631 * page by updating state variables and notifying Chrome.
633 function generateBlacklistFunction(rid) {
634 return function(e) {
635 // Prevent navigation when the page is being blacklisted.
636 e.stopPropagation();
638 userInitiatedMostVisitedChange = true;
639 isBlacklisting = true;
640 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON);
641 lastBlacklistedTile = getTileByRid(rid);
642 ntpApiHandle.deleteMostVisitedItem(rid);
648 * Shows the blacklist notification and triggers a delay to hide it.
650 function showNotification() {
651 notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
652 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
653 notification.scrollTop;
654 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
659 * Hides the blacklist notification.
661 function hideNotification() {
662 notification.classList.add(CLASSES.HIDE_NOTIFICATION);
667 * Handles the end of the blacklist animation by showing the notification and
668 * re-rendering the new set of tiles.
670 function blacklistAnimationDone() {
671 showNotification();
672 isBlacklisting = false;
673 tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON);
674 lastBlacklistedTile.elem.removeEventListener(
675 'webkitTransitionEnd', blacklistAnimationDone);
676 // Need to call explicitly to re-render the tiles, since the initial
677 // onmostvisitedchange issued by the blacklist function only triggered
678 // the animation.
679 onMostVisitedChange();
684 * Handles a click on the notification undo link by hiding the notification and
685 * informing Chrome.
687 function onUndo() {
688 userInitiatedMostVisitedChange = true;
689 hideNotification();
690 var lastBlacklistedRID = lastBlacklistedTile.rid;
691 if (typeof lastBlacklistedRID != 'undefined')
692 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID);
697 * Handles a click on the restore all notification link by hiding the
698 * notification and informing Chrome.
700 function onRestoreAll() {
701 userInitiatedMostVisitedChange = true;
702 hideNotification();
703 ntpApiHandle.undoAllMostVisitedDeletions();
708 * Re-renders the tiles if the number of columns has changed. As a temporary
709 * fix for crbug/240510, updates the width of the fakebox and most visited tiles
710 * container.
712 function onResize() {
713 // If innerWidth is zero, then use the maximum snap size.
714 var innerWidth = window.innerWidth || 820;
716 // These values should remain in sync with local_ntp.css.
717 // TODO(jeremycho): Delete once the root cause of crbug/240510 is resolved.
718 var setWidths = function(tilesContainerWidth) {
719 tilesContainer.style.width = tilesContainerWidth + 'px';
720 if (fakebox)
721 fakebox.style.width = (tilesContainerWidth - 2) + 'px';
723 if (innerWidth >= 820)
724 setWidths(620);
725 else if (innerWidth >= 660)
726 setWidths(460);
727 else
728 setWidths(300);
730 var tileRequiredWidth = TILE_WIDTH + TILE_MARGIN_START;
731 // Adds margin-start to the available width to compensate the extra margin
732 // counted above for the first tile (which does not have a margin-start).
733 var availableWidth = innerWidth + TILE_MARGIN_START -
734 MIN_TOTAL_HORIZONTAL_PADDING;
735 var numColumnsToShow = Math.floor(availableWidth / tileRequiredWidth);
736 numColumnsToShow = Math.max(MIN_NUM_COLUMNS,
737 Math.min(MAX_NUM_COLUMNS, numColumnsToShow));
738 if (numColumnsToShow != numColumnsShown) {
739 numColumnsShown = numColumnsToShow;
740 renderTiles();
746 * Returns the tile corresponding to the specified page RID.
747 * @param {number} rid The page RID being looked up.
748 * @return {Tile} The corresponding tile.
750 function getTileByRid(rid) {
751 for (var i = 0, length = tiles.length; i < length; ++i) {
752 var tile = tiles[i];
753 if (tile.rid == rid)
754 return tile;
756 return null;
761 * Handles new input by disposing the NTP, according to where the input was
762 * entered.
764 function onInputStart() {
765 if (fakebox && isFakeboxFocused()) {
766 setFakeboxFocus(false);
767 setFakeboxDragFocus(false);
768 disposeNtp(true);
769 } else if (!isFakeboxFocused()) {
770 disposeNtp(false);
776 * Disposes the NTP, according to where the input was entered.
777 * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
779 function disposeNtp(wasFakeboxInput) {
780 var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior;
781 if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX)
782 setFakeboxActive(false);
783 else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO)
784 setFakeboxAndLogoVisibility(false);
789 * Restores the NTP (re-enables the fakebox and unhides the logo.)
791 function restoreNtp() {
792 setFakeboxActive(true);
793 setFakeboxAndLogoVisibility(true);
798 * @param {boolean} focus True to focus the fakebox.
800 function setFakeboxFocus(focus) {
801 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
805 * @param {boolean} focus True to show a dragging focus to the fakebox.
807 function setFakeboxDragFocus(focus) {
808 document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
812 * @return {boolean} True if the fakebox has focus.
814 function isFakeboxFocused() {
815 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
816 document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
821 * @param {boolean} enable True to enable the fakebox.
823 function setFakeboxActive(enable) {
824 document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable);
829 * @param {!Event} event The click event.
830 * @return {boolean} True if the click occurred in an enabled fakebox.
832 function isFakeboxClick(event) {
833 return fakebox.contains(event.target) &&
834 !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE);
839 * @param {boolean} show True to show the fakebox and logo.
841 function setFakeboxAndLogoVisibility(show) {
842 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show);
847 * Shortcut for document.getElementById.
848 * @param {string} id of the element.
849 * @return {HTMLElement} with the id.
851 function $(id) {
852 return document.getElementById(id);
857 * Utility function which creates an element with an optional classname and
858 * appends it to the specified parent.
859 * @param {Element} parent The parent to append the new element.
860 * @param {string} name The name of the new element.
861 * @param {string=} opt_class The optional classname of the new element.
862 * @return {Element} The new element.
864 function createAndAppendElement(parent, name, opt_class) {
865 var child = document.createElement(name);
866 if (opt_class)
867 child.classList.add(opt_class);
868 parent.appendChild(child);
869 return child;
874 * Removes a node from its parent.
875 * @param {Node} node The node to remove.
877 function removeNode(node) {
878 node.parentNode.removeChild(node);
883 * Removes all the child nodes on a DOM node.
884 * @param {Node} node Node to remove children from.
886 function removeChildren(node) {
887 node.innerHTML = '';
892 * @param {!Element} element The element to register the handler for.
893 * @param {number} keycode The keycode of the key to register.
894 * @param {!Function} handler The key handler to register.
896 function registerKeyHandler(element, keycode, handler) {
897 element.addEventListener('keydown', function(event) {
898 if (event.keyCode == keycode)
899 handler(event);
905 * @return {Object} the handle to the embeddedSearch API.
907 function getEmbeddedSearchApiHandle() {
908 if (window.cideb)
909 return window.cideb;
910 if (window.chrome && window.chrome.embeddedSearch)
911 return window.chrome.embeddedSearch;
912 return null;
917 * Prepares the New Tab Page by adding listeners, rendering the current
918 * theme, the most visited pages section, and Google-specific elements for a
919 * Google-provided page.
921 function init() {
922 tilesContainer = $(IDS.TILES);
923 notification = $(IDS.NOTIFICATION);
924 attribution = $(IDS.ATTRIBUTION);
925 ntpContents = $(IDS.NTP_CONTENTS);
927 for (var i = 0; i < NUM_ROWS; i++) {
928 var row = document.createElement('div');
929 row.classList.add(CLASSES.ROW);
930 tilesContainer.appendChild(row);
933 if (configData.isGooglePage) {
934 var logo = document.createElement('div');
935 logo.id = IDS.LOGO;
937 fakebox = document.createElement('div');
938 fakebox.id = IDS.FAKEBOX;
939 fakebox.innerHTML =
940 '<input id="' + IDS.FAKEBOX_INPUT +
941 '" autocomplete="off" tabindex="-1" aria-hidden="true">' +
942 '<div id=cursor></div>';
944 ntpContents.insertBefore(fakebox, ntpContents.firstChild);
945 ntpContents.insertBefore(logo, ntpContents.firstChild);
946 } else {
947 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
950 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
951 notificationMessage.textContent =
952 configData.translatedStrings.thumbnailRemovedNotification;
953 var undoLink = $(IDS.UNDO_LINK);
954 undoLink.addEventListener('click', onUndo);
955 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
956 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
957 var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
958 restoreAllLink.addEventListener('click', onRestoreAll);
959 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo);
960 restoreAllLink.textContent =
961 configData.translatedStrings.restoreThumbnailsShort;
962 $(IDS.ATTRIBUTION_TEXT).textContent =
963 configData.translatedStrings.attributionIntro;
965 var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
966 notificationCloseButton.addEventListener('click', hideNotification);
968 userInitiatedMostVisitedChange = false;
969 window.addEventListener('resize', onResize);
970 onResize();
972 var topLevelHandle = getEmbeddedSearchApiHandle();
974 ntpApiHandle = topLevelHandle.newTabPage;
975 ntpApiHandle.onthemechange = onThemeChange;
976 ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
978 ntpApiHandle.oninputstart = onInputStart;
979 ntpApiHandle.oninputcancel = restoreNtp;
981 if (ntpApiHandle.isInputInProgress)
982 onInputStart();
984 onThemeChange();
985 onMostVisitedChange();
987 searchboxApiHandle = topLevelHandle.searchBox;
989 if (fakebox) {
990 // Listener for updating the key capture state.
991 document.body.onmousedown = function(event) {
992 if (isFakeboxClick(event))
993 searchboxApiHandle.startCapturingKeyStrokes();
994 else if (isFakeboxFocused())
995 searchboxApiHandle.stopCapturingKeyStrokes();
997 searchboxApiHandle.onkeycapturechange = function() {
998 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1000 var inputbox = $(IDS.FAKEBOX_INPUT);
1001 if (inputbox) {
1002 inputbox.onpaste = function(event) {
1003 event.preventDefault();
1004 searchboxApiHandle.paste();
1006 inputbox.ondrop = function(event) {
1007 event.preventDefault();
1008 var text = event.dataTransfer.getData('text/plain');
1009 if (text) {
1010 searchboxApiHandle.paste(text);
1013 inputbox.ondragenter = function() {
1014 setFakeboxDragFocus(true);
1016 inputbox.ondragleave = function() {
1017 setFakeboxDragFocus(false);
1021 // Update the fakebox style to match the current key capturing state.
1022 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1025 if (searchboxApiHandle.rtl) {
1026 $(IDS.NOTIFICATION).dir = 'rtl';
1027 // Add class for setting alignments based on language directionality.
1028 document.body.classList.add(CLASSES.RTL);
1029 $(IDS.TILES).dir = 'rtl';
1035 * Binds event listeners.
1037 function listen() {
1038 document.addEventListener('DOMContentLoaded', init);
1041 return {
1042 init: init,
1043 listen: listen
1047 if (!window.localNTPUnitTest) {
1048 LocalNTP().listen();