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.
7 * @fileoverview The local InstantExtended NTP.
12 * Controls rendering the new tab page for InstantExtended.
13 * @return {Object} A limited interface for testing the local NTP.
16 <include src="../../../../ui/webui/resources/js/assert.js">
17 <include src="local_ntp_design.js">
18 <include src="local_ntp_util.js">
19 <include src="window_disposition_util.js">
23 * Enum for classnames.
28 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
29 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
30 BLACKLIST_BUTTON: 'mv-x',
32 DEFAULT_THEME: 'default-theme',
33 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
35 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
36 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
37 // Applies drag focus style to the fakebox
38 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
39 FAVICON: 'mv-favicon',
40 FAVICON_FALLBACK: 'mv-favicon-fallback',
41 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
42 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
43 HIDE_NOTIFICATION: 'mv-notice-hide',
44 // Vertically centers the most visited section for a non-Google provided page.
45 NON_GOOGLE_PAGE: 'non-google-page',
46 PAGE: 'mv-page', // page tiles
47 PAGE_READY: 'mv-page-ready', // page tile when ready
48 RTL: 'rtl', // Right-to-left language text.
49 THUMBNAIL: 'mv-thumb',
50 THUMBNAIL_FALLBACK: 'mv-thumb-fallback',
51 THUMBNAIL_MASK: 'mv-mask',
53 TILE_INNER: 'mv-tile-inner',
59 * Enum for HTML element ids.
64 ATTRIBUTION: 'attribution',
65 ATTRIBUTION_TEXT: 'attribution-text',
66 CUSTOM_THEME_STYLE: 'ct-style',
68 FAKEBOX_INPUT: 'fakebox-input',
69 FAKEBOX_TEXT: 'fakebox-text',
71 NOTIFICATION: 'mv-notice',
72 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
73 NOTIFICATION_MESSAGE: 'mv-msg',
74 NTP_CONTENTS: 'ntp-contents',
75 RESTORE_ALL_LINK: 'mv-restore',
93 * Enum for the state of the NTP when it is disposed.
97 var NTP_DISPOSE_STATE = {
98 NONE: 0, // Preserve the NTP appearance and functionality
100 HIDE_FAKEBOX_AND_LOGO: 2
105 * The JavaScript button event value for a middle click.
109 var MIDDLE_MOUSE_BUTTON = 1;
113 * Specifications for the NTP design.
116 var NTP_DESIGN = getNtpDesign(configData.ntpDesignName);
120 * The container for the tile elements.
127 * The notification displayed when a page is blacklisted.
134 * The container for the theme attribution.
141 * The "fakebox" - an input field that looks like a regular searchbox. When it
142 * is focused, any text the user types goes directly into the omnibox.
149 * The container for NTP elements.
156 * The array of rendered tiles, ordered by appearance.
157 * @type {!Array.<Tile>}
163 * The last blacklisted tile if any, which by definition should not be filler.
166 var lastBlacklistedTile = null;
170 * True if a page has been blacklisted and we're waiting on the
171 * onmostvisitedchange callback. See onMostVisitedChange() for how this
175 var isBlacklisting = false;
179 * Stores whether the current theme has a dark background.
182 var isBackgroundDark = false;
186 * Current number of tiles columns shown based on the window width, including
187 * those that just contain filler.
190 var numColumnsShown = 0;
194 * A flag to indicate Most Visited changed caused by user action. If true, then
195 * in onMostVisitedChange() tiles remain visible so no flickering occurs.
198 var userInitiatedMostVisitedChange = false;
202 * The browser embeddedSearch.newTabPage object.
209 * The browser embeddedSearch.searchBox object.
212 var searchboxApiHandle;
216 * The state of the NTP when a query is entered into the Omnibox.
217 * @type {NTP_DISPOSE_STATE}
219 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE;
223 * The state of the NTP when a query is entered into the Fakebox.
224 * @type {NTP_DISPOSE_STATE}
226 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO;
229 /** @type {number} @const */
230 var MAX_NUM_TILES_TO_SHOW = 8;
233 /** @type {number} @const */
234 var MIN_NUM_COLUMNS = 2;
237 /** @type {number} @const */
238 var MAX_NUM_COLUMNS = 4;
241 /** @type {number} @const */
246 * Minimum total padding to give to the left and right of the most visited
247 * section. Used to determine how many tiles to show.
251 var MIN_TOTAL_HORIZONTAL_PADDING = 200;
255 * The filename for a most visited iframe src which shows a page title.
259 var MOST_VISITED_TITLE_IFRAME = 'title.html';
263 * The filename for a most visited iframe src which shows a thumbnail image.
267 var MOST_VISITED_THUMBNAIL_IFRAME = 'thumbnail.html';
271 * Hide most visited tiles for at most this many milliseconds while painting.
275 var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500;
279 * A Tile is either a rendering of a Most Visited page or "filler" used to
280 * pad out the section when not enough pages exist.
282 * @param {Element} elem The element for rendering the tile.
283 * @param {Element=} opt_innerElem The element for contents of tile.
284 * @param {Element=} opt_titleElem The element for rendering the title.
285 * @param {Element=} opt_thumbnailElem The element for rendering the thumbnail.
286 * @param {number=} opt_rid The RID for the corresponding Most Visited page.
287 * Should only be left unspecified when creating a filler tile.
290 function Tile(elem, opt_innerElem, opt_titleElem, opt_thumbnailElem, opt_rid) {
291 /** @type {Element} */
294 /** @type {Element|undefined} */
295 this.innerElem = opt_innerElem;
297 /** @type {Element|undefined} */
298 this.titleElem = opt_titleElem;
300 /** @type {Element|undefined} */
301 this.thumbnailElem = opt_thumbnailElem;
303 /** @type {number|undefined} */
309 * Determines whether a theme should be considered to have dark background.
310 * @param {ThemeBackgroundInfo} info Theme background information.
311 * @return {boolean} Whether the theme has dark background.
314 function getIsBackgroundDark(info) {
317 var rgba = info.backgroundColorRgba;
318 var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2];
319 return luminance < 128;
324 * Updates the NTP based on the current theme.
327 function renderTheme() {
328 var info = ntpApiHandle.themeBackgroundInfo;
330 isBackgroundDark = false;
334 isBackgroundDark = getIsBackgroundDark(info);
335 ntpContents.classList.toggle(CLASSES.DARK, isBackgroundDark);
337 var background = [convertToRGBAColor(info.backgroundColorRgba),
340 info.imageHorizontalAlignment,
341 info.imageVerticalAlignment].join(' ').trim();
342 document.body.style.background = background;
343 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
344 updateThemeAttribution(info.attributionUrl);
345 setCustomThemeStyle(info);
350 * Updates the NTP based on the current theme, then rerenders all tiles.
353 function onThemeChange() {
355 tilesContainer.innerHTML = '';
356 renderAndShowTiles();
361 * Updates the NTP style according to theme.
362 * @param {Object=} opt_themeInfo The information about the theme. If it is
363 * omitted the style will be reverted to the default.
366 function setCustomThemeStyle(opt_themeInfo) {
367 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE);
368 var head = document.head;
370 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
371 ntpContents.classList.remove(CLASSES.DEFAULT_THEME);
374 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
377 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' +
379 '#mv-notice-links span {' +
380 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
383 ' -webkit-filter: drop-shadow(0 0 0 ' +
384 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
386 '.mv-page-ready .mv-mask {' +
387 ' border: 1px solid ' +
388 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
390 '.mv-page-ready:hover .mv-mask, .mv-page-ready:focus .mv-mask {' +
392 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
395 if (customStyleElement) {
396 customStyleElement.textContent = themeStyle;
398 customStyleElement = document.createElement('style');
399 customStyleElement.type = 'text/css';
400 customStyleElement.id = IDS.CUSTOM_THEME_STYLE;
401 customStyleElement.textContent = themeStyle;
402 head.appendChild(customStyleElement);
406 ntpContents.classList.add(CLASSES.DEFAULT_THEME);
407 if (customStyleElement)
408 head.removeChild(customStyleElement);
414 * Renders the attribution if the URL is present, otherwise hides it.
415 * @param {string} url The URL of the attribution image, if any.
418 function updateThemeAttribution(url) {
420 setAttributionVisibility_(false);
424 var attributionImage = attribution.querySelector('img');
425 if (!attributionImage) {
426 attributionImage = new Image();
427 attribution.appendChild(attributionImage);
429 attributionImage.style.content = url;
430 setAttributionVisibility_(true);
435 * Sets the visibility of the theme attribution.
436 * @param {boolean} show True to show the attribution.
439 function setAttributionVisibility_(show) {
441 attribution.style.display = show ? '' : 'none';
447 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
448 * @param {Array.<number>} color Array of rgba color components.
449 * @return {string} CSS color in RGBA format.
452 function convertToRGBAColor(color) {
453 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
454 color[3] / 255 + ')';
459 * Handles a new set of Most Visited page data.
461 function onMostVisitedChange() {
462 if (isBlacklisting) {
463 // Trigger the blacklist animation, which then triggers reloadAllTiles().
464 var lastBlacklistedTileElem = lastBlacklistedTile.elem;
465 lastBlacklistedTileElem.addEventListener(
466 'webkitTransitionEnd', blacklistAnimationDone);
467 lastBlacklistedTileElem.classList.add(CLASSES.BLACKLIST);
475 * Handles the end of the blacklist animation by showing the notification and
476 * re-rendering the new set of tiles.
478 function blacklistAnimationDone() {
480 isBlacklisting = false;
481 tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON);
482 lastBlacklistedTile.elem.removeEventListener(
483 'webkitTransitionEnd', blacklistAnimationDone);
484 // Need to call explicitly to re-render the tiles, since the initial
485 // onmostvisitedchange issued by the blacklist function only triggered
492 * Fetches new data, creates, and renders tiles.
494 function reloadAllTiles() {
495 var pages = ntpApiHandle.mostVisited;
498 for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i)
499 tiles.push(createTile(pages[i], i));
501 tilesContainer.innerHTML = '';
502 renderAndShowTiles();
507 * Binds onload events for a tile's internal <iframe> elements.
508 * @param {Tile} tile The main tile to bind events to.
509 * @param {Barrier} tileVisibilityBarrier A barrier to make all tiles visible
510 * the moment all tiles are loaded.
512 function bindTileOnloadEvents(tile, tileVisibilityBarrier) {
513 if (tile.titleElem) {
514 tileVisibilityBarrier.add();
515 tile.titleElem.onload = function() {
516 tileVisibilityBarrier.remove();
519 if (tile.thumbnailElem) {
520 tileVisibilityBarrier.add();
521 tile.thumbnailElem.onload = function() {
522 tile.elem.classList.add(CLASSES.PAGE_READY);
523 tileVisibilityBarrier.remove();
530 * Renders the current list of visible tiles to DOM, and hides tiles that are
531 * already in the DOM but should not be seen.
533 function renderAndShowTiles() {
534 var numExisting = tilesContainer.querySelectorAll('.' + CLASSES.TILE).length;
535 // Only add visible tiles to the DOM, to avoid creating invisible tiles that
536 // produce meaningless impression metrics. However, if a tile becomes
537 // invisible then we leave it in DOM to prevent reload if it's shown again.
538 var numDesired = Math.min(tiles.length, numColumnsShown * NUM_ROWS);
540 // If we need to render new tiles, manage the visibility to hide intermediate
541 // load states of the <iframe>s.
542 if (numExisting < numDesired) {
543 var showAll = function() {
544 for (var i = 0; i < numDesired; ++i) {
545 if (tiles[i].titleElem || tiles[i].thumbnailElem)
546 tiles[i].elem.classList.add(CLASSES.PAGE_READY);
549 var tileVisibilityBarrier = new Barrier(showAll);
551 if (!userInitiatedMostVisitedChange) {
552 // Make titleContainer invisible, but still taking up space.
553 // titleContainer becomes visible again (1) on timeout, or (2) when all
554 // tiles finish loading (using tileVisibilityBarrier).
555 window.setTimeout(function() {
556 tileVisibilityBarrier.cancel();
558 }, MOST_VISITED_PAINT_TIMEOUT_MSEC);
560 userInitiatedMostVisitedChange = false;
562 for (var i = numExisting; i < numDesired; ++i) {
563 bindTileOnloadEvents(tiles[i], tileVisibilityBarrier);
564 tilesContainer.appendChild(tiles[i].elem);
568 // Show only the desired tiles. Note that .hidden does not work for
569 // inline-block elements like tiles[i].elem.
570 for (var i = 0; i < numDesired; ++i)
571 tiles[i].elem.style.display = 'inline-block';
572 // If |numDesired| < |numExisting| then hide extra tiles (e.g., this occurs
573 // when window is downsized).
574 for (; i < numExisting; ++i)
575 tiles[i].elem.style.display = 'none';
580 * Builds a URL to display a most visited tile title in an iframe.
581 * @param {number} rid The restricted ID.
582 * @param {number} position The position of the iframe in the UI.
583 * @return {string} An URL to display the most visited title in an iframe.
585 function getMostVisitedTitleIframeUrl(rid, position) {
586 var url = 'chrome-search://most-visited/' +
587 encodeURIComponent(MOST_VISITED_TITLE_IFRAME);
588 var titleColor = isBackgroundDark ? NTP_DESIGN.titleColorAgainstDark :
589 NTP_DESIGN.titleColor;
591 'rid=' + encodeURIComponent(rid),
592 'f=' + encodeURIComponent(NTP_DESIGN.fontFamily),
593 'fs=' + encodeURIComponent(NTP_DESIGN.fontSize),
594 'c=' + encodeURIComponent(titleColor),
595 'pos=' + encodeURIComponent(position)];
596 if (NTP_DESIGN.titleTextAlign)
597 params.push('ta=' + encodeURIComponent(NTP_DESIGN.titleTextAlign));
598 if (NTP_DESIGN.titleTextFade)
599 params.push('tf=' + encodeURIComponent(NTP_DESIGN.titleTextFade));
600 return url + '?' + params.join('&');
605 * Builds a URL to display a most visited tile thumbnail in an iframe.
606 * @param {number} rid The restricted ID.
607 * @param {number} position The position of the iframe in the UI.
608 * @return {string} An URL to display the most visited thumbnail in an iframe.
610 function getMostVisitedThumbnailIframeUrl(rid, position) {
611 var url = 'chrome-search://most-visited/' +
612 encodeURIComponent(MOST_VISITED_THUMBNAIL_IFRAME);
614 'rid=' + encodeURIComponent(rid),
615 'f=' + encodeURIComponent(NTP_DESIGN.fontFamily),
616 'fs=' + encodeURIComponent(NTP_DESIGN.fontSize),
617 'c=' + encodeURIComponent(NTP_DESIGN.thumbnailTextColor),
618 'pos=' + encodeURIComponent(position)];
619 if (NTP_DESIGN.thumbnailFallback)
620 params.push('etfb=1');
621 return url + '?' + params.join('&');
626 * Creates a Tile with the specified page data. If no data is provided, a
627 * filler Tile is created.
628 * @param {Object} page The page data.
629 * @param {number} position The position of the tile.
630 * @return {Tile} The new Tile.
632 function createTile(page, position) {
633 var tileElem = document.createElement('div');
634 tileElem.classList.add(CLASSES.TILE);
635 var innerElem = createAndAppendElement(tileElem, 'div', CLASSES.TILE_INNER);
639 tileElem.classList.add(CLASSES.PAGE);
641 var navigateFunction = function(e) {
643 ntpApiHandle.navigateContentWindow(rid, getDispositionFromEvent(e));
646 // The click handler for navigating to the page identified by the RID.
647 tileElem.addEventListener('click', navigateFunction);
649 // Make thumbnails tab-accessible.
650 tileElem.setAttribute('tabindex', '1');
651 registerKeyHandler(tileElem, KEYCODE.ENTER, navigateFunction);
653 // The iframe which renders the page title.
654 var titleElem = document.createElement('iframe');
655 titleElem.tabIndex = '-1';
657 // Why iframes have IDs:
659 // On navigating back to the NTP we see several onmostvisitedchange() events
660 // in series with incrementing RIDs. After the first event, a set of iframes
661 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
662 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
663 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
664 // first set of iframes into the most recent set of iframes.
666 // Giving iframes distinct ids seems to cause some invalidation and prevent
667 // associating the incorrect data.
669 // TODO(jered): Find and fix the root (probably Blink) bug.
671 // Keep this ID here. See comment above.
672 titleElem.id = 'title-' + rid;
673 titleElem.className = CLASSES.TITLE;
674 titleElem.src = getMostVisitedTitleIframeUrl(rid, position);
675 innerElem.appendChild(titleElem);
677 // A fallback element for missing thumbnails.
678 if (NTP_DESIGN.thumbnailFallback) {
679 var fallbackElem = createAndAppendElement(
680 innerElem, 'div', CLASSES.THUMBNAIL_FALLBACK);
681 if (NTP_DESIGN.thumbnailFallback === THUMBNAIL_FALLBACK.DOT)
682 createAndAppendElement(fallbackElem, 'div', CLASSES.DOT);
685 // The iframe which renders either a thumbnail or domain element.
686 var thumbnailElem = document.createElement('iframe');
687 thumbnailElem.tabIndex = '-1';
688 // Keep this ID here. See comment above.
689 thumbnailElem.id = 'thumb-' + rid;
690 thumbnailElem.className = CLASSES.THUMBNAIL;
691 thumbnailElem.src = getMostVisitedThumbnailIframeUrl(rid, position);
692 innerElem.appendChild(thumbnailElem);
694 // The button used to blacklist this page.
695 var blacklistButton = createAndAppendElement(
696 innerElem, 'div', CLASSES.BLACKLIST_BUTTON);
697 var blacklistFunction = generateBlacklistFunction(rid);
698 blacklistButton.addEventListener('click', blacklistFunction);
699 blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip;
701 // A helper mask on top of the tile that is used to create hover border
702 // and/or to darken the thumbnail on focus.
703 var maskElement = createAndAppendElement(
704 innerElem, 'div', CLASSES.THUMBNAIL_MASK);
706 // When a tile is focused, have delete also blacklist the page.
707 registerKeyHandler(tileElem, KEYCODE.DELETE, blacklistFunction);
709 // The page favicon, or a fallback.
710 var favicon = createAndAppendElement(innerElem, 'div', CLASSES.FAVICON);
711 if (page.faviconUrl) {
712 favicon.style.backgroundImage = 'url(' + page.faviconUrl + ')';
714 favicon.classList.add(CLASSES.FAVICON_FALLBACK);
716 return new Tile(tileElem, innerElem, titleElem, thumbnailElem, rid);
718 return new Tile(tileElem);
724 * Generates a function to be called when the page with the corresponding RID
726 * @param {number} rid The RID of the page being blacklisted.
727 * @return {function(Event)} A function which handles the blacklisting of the
728 * page by updating state variables and notifying Chrome.
730 function generateBlacklistFunction(rid) {
732 // Prevent navigation when the page is being blacklisted.
735 userInitiatedMostVisitedChange = true;
736 isBlacklisting = true;
737 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON);
738 lastBlacklistedTile = getTileByRid(rid);
739 ntpApiHandle.deleteMostVisitedItem(rid);
745 * Shows the blacklist notification and triggers a delay to hide it.
747 function showNotification() {
748 notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
749 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
750 notification.scrollTop;
751 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
756 * Hides the blacklist notification.
758 function hideNotification() {
759 notification.classList.add(CLASSES.HIDE_NOTIFICATION);
764 * Handles a click on the notification undo link by hiding the notification and
768 userInitiatedMostVisitedChange = true;
770 var lastBlacklistedRID = lastBlacklistedTile.rid;
771 if (typeof lastBlacklistedRID != 'undefined')
772 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID);
777 * Handles a click on the restore all notification link by hiding the
778 * notification and informing Chrome.
780 function onRestoreAll() {
781 userInitiatedMostVisitedChange = true;
783 ntpApiHandle.undoAllMostVisitedDeletions();
788 * Resizes elements because the number of tile columns may need to change in
789 * response to resizing. Also shows or hides extra tiles tiles according to the
790 * new width of the page.
792 function onResize() {
793 var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin;
794 // If innerWidth is zero, then use the maximum snap size.
795 var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth -
796 NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING;
797 var innerWidth = window.innerWidth || maxSnapSize;
798 // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin.
799 var availableWidth = innerWidth + NTP_DESIGN.tileMargin -
800 MIN_TOTAL_HORIZONTAL_PADDING;
801 var newNumColumns = Math.floor(availableWidth / tileRequiredWidth);
802 if (newNumColumns < MIN_NUM_COLUMNS)
803 newNumColumns = MIN_NUM_COLUMNS;
804 else if (newNumColumns > MAX_NUM_COLUMNS)
805 newNumColumns = MAX_NUM_COLUMNS;
807 if (numColumnsShown != newNumColumns) {
808 numColumnsShown = newNumColumns;
809 var tilesContainerWidth = numColumnsShown * tileRequiredWidth;
810 tilesContainer.style.width = tilesContainerWidth + 'px';
812 fakebox.style.width = // -2 to account for border.
813 (tilesContainerWidth - NTP_DESIGN.tileMargin - 2) + 'px';
815 // Render without clearing tiles.
816 renderAndShowTiles();
822 * Returns the tile corresponding to the specified page RID.
823 * @param {number} rid The page RID being looked up.
824 * @return {Tile} The corresponding tile.
826 function getTileByRid(rid) {
827 for (var i = 0, length = tiles.length; i < length; ++i) {
837 * Handles new input by disposing the NTP, according to where the input was
840 function onInputStart() {
841 if (fakebox && isFakeboxFocused()) {
842 setFakeboxFocus(false);
843 setFakeboxDragFocus(false);
845 } else if (!isFakeboxFocused()) {
852 * Disposes the NTP, according to where the input was entered.
853 * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
855 function disposeNtp(wasFakeboxInput) {
856 var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior;
857 if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX)
858 setFakeboxActive(false);
859 else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO)
860 setFakeboxAndLogoVisibility(false);
865 * Restores the NTP (re-enables the fakebox and unhides the logo.)
867 function restoreNtp() {
868 setFakeboxActive(true);
869 setFakeboxAndLogoVisibility(true);
874 * @param {boolean} focus True to focus the fakebox.
876 function setFakeboxFocus(focus) {
877 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
881 * @param {boolean} focus True to show a dragging focus to the fakebox.
883 function setFakeboxDragFocus(focus) {
884 document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
888 * @return {boolean} True if the fakebox has focus.
890 function isFakeboxFocused() {
891 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
892 document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
897 * @param {boolean} enable True to enable the fakebox.
899 function setFakeboxActive(enable) {
900 document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable);
905 * @param {!Event} event The click event.
906 * @return {boolean} True if the click occurred in an enabled fakebox.
908 function isFakeboxClick(event) {
909 return fakebox.contains(event.target) &&
910 !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE);
915 * @param {boolean} show True to show the fakebox and logo.
917 function setFakeboxAndLogoVisibility(show) {
918 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show);
923 * Shortcut for document.getElementById.
924 * @param {string} id of the element.
925 * @return {HTMLElement} with the id.
928 return document.getElementById(id);
933 * Utility function which creates an element with an optional classname and
934 * appends it to the specified parent.
935 * @param {Element} parent The parent to append the new element.
936 * @param {string} name The name of the new element.
937 * @param {string=} opt_class The optional classname of the new element.
938 * @return {Element} The new element.
940 function createAndAppendElement(parent, name, opt_class) {
941 var child = document.createElement(name);
943 child.classList.add(opt_class);
944 parent.appendChild(child);
950 * Removes a node from its parent.
951 * @param {Node} node The node to remove.
953 function removeNode(node) {
954 node.parentNode.removeChild(node);
959 * @param {!Element} element The element to register the handler for.
960 * @param {number} keycode The keycode of the key to register.
961 * @param {!Function} handler The key handler to register.
963 function registerKeyHandler(element, keycode, handler) {
964 element.addEventListener('keydown', function(event) {
965 if (event.keyCode == keycode)
972 * @return {Object} the handle to the embeddedSearch API.
974 function getEmbeddedSearchApiHandle() {
977 if (window.chrome && window.chrome.embeddedSearch)
978 return window.chrome.embeddedSearch;
984 * Prepares the New Tab Page by adding listeners, rendering the current
985 * theme, the most visited pages section, and Google-specific elements for a
986 * Google-provided page.
989 tilesContainer = $(IDS.TILES);
990 notification = $(IDS.NOTIFICATION);
991 attribution = $(IDS.ATTRIBUTION);
992 ntpContents = $(IDS.NTP_CONTENTS);
994 if (configData.isGooglePage) {
995 var logo = document.createElement('div');
998 fakebox = document.createElement('div');
999 fakebox.id = IDS.FAKEBOX;
1000 var fakeboxHtml = [];
1001 fakeboxHtml.push('<input id="' + IDS.FAKEBOX_INPUT +
1002 '" autocomplete="off" tabindex="-1" aria-hidden="true">');
1003 if (NTP_DESIGN.showFakeboxHint &&
1004 configData.translatedStrings.searchboxPlaceholder) {
1005 fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '">' +
1006 configData.translatedStrings.searchboxPlaceholder + '</div>');
1008 fakeboxHtml.push('<div id="cursor"></div>');
1009 fakebox.innerHTML = fakeboxHtml.join('');
1011 ntpContents.insertBefore(fakebox, ntpContents.firstChild);
1012 ntpContents.insertBefore(logo, ntpContents.firstChild);
1014 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
1017 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
1018 notificationMessage.textContent =
1019 configData.translatedStrings.thumbnailRemovedNotification;
1021 var undoLink = $(IDS.UNDO_LINK);
1022 undoLink.addEventListener('click', onUndo);
1023 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
1024 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
1026 var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
1027 restoreAllLink.addEventListener('click', onRestoreAll);
1028 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo);
1029 restoreAllLink.textContent =
1030 configData.translatedStrings.restoreThumbnailsShort;
1032 $(IDS.ATTRIBUTION_TEXT).textContent =
1033 configData.translatedStrings.attributionIntro;
1035 var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
1036 notificationCloseButton.addEventListener('click', hideNotification);
1038 window.addEventListener('resize', onResize);
1041 var topLevelHandle = getEmbeddedSearchApiHandle();
1043 ntpApiHandle = topLevelHandle.newTabPage;
1044 ntpApiHandle.onthemechange = onThemeChange;
1045 ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
1047 ntpApiHandle.oninputstart = onInputStart;
1048 ntpApiHandle.oninputcancel = restoreNtp;
1050 if (ntpApiHandle.isInputInProgress)
1054 onMostVisitedChange();
1056 searchboxApiHandle = topLevelHandle.searchBox;
1059 // Listener for updating the key capture state.
1060 document.body.onmousedown = function(event) {
1061 if (isFakeboxClick(event))
1062 searchboxApiHandle.startCapturingKeyStrokes();
1063 else if (isFakeboxFocused())
1064 searchboxApiHandle.stopCapturingKeyStrokes();
1066 searchboxApiHandle.onkeycapturechange = function() {
1067 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1069 var inputbox = $(IDS.FAKEBOX_INPUT);
1071 inputbox.onpaste = function(event) {
1072 event.preventDefault();
1073 searchboxApiHandle.paste();
1075 inputbox.ondrop = function(event) {
1076 event.preventDefault();
1077 var text = event.dataTransfer.getData('text/plain');
1079 searchboxApiHandle.paste(text);
1082 inputbox.ondragenter = function() {
1083 setFakeboxDragFocus(true);
1085 inputbox.ondragleave = function() {
1086 setFakeboxDragFocus(false);
1090 // Update the fakebox style to match the current key capturing state.
1091 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1094 if (searchboxApiHandle.rtl) {
1095 $(IDS.NOTIFICATION).dir = 'rtl';
1096 document.body.setAttribute('dir', 'rtl');
1097 // Add class for setting alignments based on language directionality.
1098 document.body.classList.add(CLASSES.RTL);
1099 $(IDS.TILES).dir = 'rtl';
1105 * Binds event listeners.
1108 document.addEventListener('DOMContentLoaded', init);
1117 if (!window.localNTPUnitTest) {
1118 LocalNTP().listen();