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',
31 BLACKLIST_BUTTON_INNER
: 'mv-x-inner',
33 DEFAULT_THEME
: 'default-theme',
34 DELAYED_HIDE_NOTIFICATION
: 'mv-notice-delayed-hide',
36 FAKEBOX_DISABLE
: 'fakebox-disable', // Makes fakebox non-interactive
37 FAKEBOX_FOCUS
: 'fakebox-focused', // Applies focus styles to the fakebox
38 // Applies drag focus style to the fakebox
39 FAKEBOX_DRAG_FOCUS
: 'fakebox-drag-focused',
40 FAVICON
: 'mv-favicon',
41 FAVICON_FALLBACK
: 'mv-favicon-fallback',
42 FOCUSED
: 'mv-focused',
43 HIDE_BLACKLIST_BUTTON
: 'mv-x-hide', // hides blacklist button during animation
44 HIDE_FAKEBOX_AND_LOGO
: 'hide-fakebox-logo',
45 HIDE_NOTIFICATION
: 'mv-notice-hide',
46 // Vertically centers the most visited section for a non-Google provided page.
47 NON_GOOGLE_PAGE
: 'non-google-page',
48 PAGE
: 'mv-page', // page tiles
49 PAGE_READY
: 'mv-page-ready', // page tile when ready
50 RTL
: 'rtl', // Right-to-left language text.
51 THUMBNAIL
: 'mv-thumb',
52 THUMBNAIL_FALLBACK
: 'mv-thumb-fallback',
53 THUMBNAIL_MASK
: 'mv-mask',
55 TILE_INNER
: 'mv-tile-inner',
61 * Enum for HTML element ids.
66 ATTRIBUTION
: 'attribution',
67 ATTRIBUTION_TEXT
: 'attribution-text',
68 CUSTOM_THEME_STYLE
: 'ct-style',
70 FAKEBOX_INPUT
: 'fakebox-input',
71 FAKEBOX_TEXT
: 'fakebox-text',
73 NOTIFICATION
: 'mv-notice',
74 NOTIFICATION_CLOSE_BUTTON
: 'mv-notice-x',
75 NOTIFICATION_MESSAGE
: 'mv-msg',
76 NTP_CONTENTS
: 'ntp-contents',
77 RESTORE_ALL_LINK
: 'mv-restore',
94 * Enum for the state of the NTP when it is disposed.
98 var NTP_DISPOSE_STATE
= {
99 NONE
: 0, // Preserve the NTP appearance and functionality
101 HIDE_FAKEBOX_AND_LOGO
: 2
106 * The JavaScript button event value for a middle click.
110 var MIDDLE_MOUSE_BUTTON
= 1;
114 * The container for the tile elements.
121 * The notification displayed when a page is blacklisted.
128 * The container for the theme 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.
143 * The container for NTP elements.
150 * The array of rendered tiles, ordered by appearance.
151 * @type {!Array<Tile>}
157 * The last blacklisted tile if any, which by definition should not be filler.
160 var lastBlacklistedTile
= null;
164 * The iframe element which is currently keyboard focused, or null.
167 var focusedIframe
= null;
171 * True if a page has been blacklisted and we're waiting on the
172 * onmostvisitedchange callback. See renderAllTiles() for how this is used.
175 var isBlacklisting
= false;
179 * Current number of tiles columns shown based on the window width, including
180 * those that just contain filler.
183 var numColumnsShown
= 0;
187 * A flag to indicate Most Visited changed caused by user action. If true, then
188 * in renderAllTiles() tiles remain visible so no flickering occurs.
191 var userInitiatedMostVisitedChange
= false;
195 * The browser embeddedSearch.newTabPage object.
202 * The browser embeddedSearch.searchBox object.
205 var searchboxApiHandle
;
209 * The state of the NTP when a query is entered into the Omnibox.
210 * @type {NTP_DISPOSE_STATE}
212 var omniboxInputBehavior
= NTP_DISPOSE_STATE
.NONE
;
216 * The state of the NTP when a query is entered into the Fakebox.
217 * @type {NTP_DISPOSE_STATE}
219 var fakeboxInputBehavior
= NTP_DISPOSE_STATE
.HIDE_FAKEBOX_AND_LOGO
;
222 /** @type {number} @const */
223 var MAX_NUM_TILES_TO_SHOW
= 8;
226 /** @type {number} @const */
227 var MIN_NUM_COLUMNS
= 2;
230 /** @type {number} @const */
231 var MAX_NUM_COLUMNS
= 4;
234 /** @type {number} @const */
239 * Minimum total padding to give to the left and right of the most visited
240 * section. Used to determine how many tiles to show.
244 var MIN_TOTAL_HORIZONTAL_PADDING
= 200;
248 * The filename for a most visited iframe src which shows a page title.
252 var MOST_VISITED_TITLE_IFRAME
= 'title.html';
256 * The filename for a most visited iframe src which shows a thumbnail image.
260 var MOST_VISITED_THUMBNAIL_IFRAME
= 'thumbnail.html';
264 * The color of the title in RRGGBBAA format.
267 var titleColor
= null;
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 * Heuristic to determine whether a theme should be considered to be dark, so
310 * the colors of various UI elements can be adjusted.
311 * @param {ThemeBackgroundInfo|undefined} info Theme background information.
312 * @return {boolean} Whether the theme is dark.
315 function getIsThemeDark(info
) {
318 // Heuristic: light text implies dark theme.
319 var rgba
= info
.textColorRgba
;
320 var luminance
= 0.3 * rgba
[0] + 0.59 * rgba
[1] + 0.11 * rgba
[2];
321 return luminance
>= 128;
326 * Updates the NTP based on the current theme.
329 function renderTheme() {
330 var fakeboxText
= $(IDS
.FAKEBOX_TEXT
);
332 fakeboxText
.innerHTML
= '';
333 if (configData
.translatedStrings
.searchboxPlaceholder
) {
334 fakeboxText
.textContent
=
335 configData
.translatedStrings
.searchboxPlaceholder
;
339 var info
= ntpApiHandle
.themeBackgroundInfo
;
340 var isThemeDark
= getIsThemeDark(info
);
341 ntpContents
.classList
.toggle(CLASSES
.DARK
, isThemeDark
);
343 titleColor
= NTP_DESIGN
.titleColor
;
347 if (!info
.usingDefaultTheme
&& info
.textColorRgba
) {
348 titleColor
= convertToRRGGBBAAColor(info
.textColorRgba
);
350 titleColor
= isThemeDark
?
351 NTP_DESIGN
.titleColorAgainstDark
: NTP_DESIGN
.titleColor
;
354 var background
= [convertToRGBAColor(info
.backgroundColorRgba
),
357 info
.imageHorizontalAlignment
,
358 info
.imageVerticalAlignment
].join(' ').trim();
360 document
.body
.style
.background
= background
;
361 document
.body
.classList
.toggle(CLASSES
.ALTERNATE_LOGO
, info
.alternateLogo
);
362 updateThemeAttribution(info
.attributionUrl
);
363 setCustomThemeStyle(info
);
368 * Updates the NTP based on the current theme, then rerenders all tiles.
371 function onThemeChange() {
373 tilesContainer
.innerHTML
= '';
379 * Updates the NTP style according to theme.
380 * @param {Object=} opt_themeInfo The information about the theme. If it is
381 * omitted the style will be reverted to the default.
384 function setCustomThemeStyle(opt_themeInfo
) {
385 var customStyleElement
= $(IDS
.CUSTOM_THEME_STYLE
);
386 var head
= document
.head
;
387 if (opt_themeInfo
&& !opt_themeInfo
.usingDefaultTheme
) {
388 ntpContents
.classList
.remove(CLASSES
.DEFAULT_THEME
);
391 ' color: ' + convertToRGBAColor(opt_themeInfo
.textColorLightRgba
) + ';' +
394 ' color: ' + convertToRGBAColor(opt_themeInfo
.textColorRgba
) + ';' +
396 '#mv-notice-links span {' +
397 ' color: ' + convertToRGBAColor(opt_themeInfo
.textColorLightRgba
) + ';' +
400 ' -webkit-filter: drop-shadow(0 0 0 ' +
401 convertToRGBAColor(opt_themeInfo
.textColorRgba
) + ');' +
403 '.mv-page-ready .mv-mask {' +
404 ' border: 1px solid ' +
405 convertToRGBAColor(opt_themeInfo
.sectionBorderColorRgba
) + ';' +
407 '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask {' +
409 convertToRGBAColor(opt_themeInfo
.headerColorRgba
) + ';' +
412 if (customStyleElement
) {
413 customStyleElement
.textContent
= themeStyle
;
415 customStyleElement
= document
.createElement('style');
416 customStyleElement
.type
= 'text/css';
417 customStyleElement
.id
= IDS
.CUSTOM_THEME_STYLE
;
418 customStyleElement
.textContent
= themeStyle
;
419 head
.appendChild(customStyleElement
);
423 ntpContents
.classList
.add(CLASSES
.DEFAULT_THEME
);
424 if (customStyleElement
)
425 head
.removeChild(customStyleElement
);
431 * Renders the attribution if the URL is present, otherwise hides it.
432 * @param {string} url The URL of the attribution image, if any.
435 function updateThemeAttribution(url
) {
437 setAttributionVisibility_(false);
441 var attributionImage
= attribution
.querySelector('img');
442 if (!attributionImage
) {
443 attributionImage
= new Image();
444 attribution
.appendChild(attributionImage
);
446 attributionImage
.style
.content
= url
;
447 setAttributionVisibility_(true);
452 * Sets the visibility of the theme attribution.
453 * @param {boolean} show True to show the attribution.
456 function setAttributionVisibility_(show
) {
458 attribution
.style
.display
= show
? '' : 'none';
464 * Converts an Array of color components into RRGGBBAA format.
465 * @param {Array<number>} color Array of rgba color components.
466 * @return {string} Color string in RRGGBBAA format.
469 function convertToRRGGBBAAColor(color
) {
470 return color
.map(function(t
) {
471 return ('0' + t
.toString(16)).slice(-2); // To 2-digit, 0-padded hex.
477 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
478 * @param {Array<number>} color Array of rgba color components.
479 * @return {string} CSS color in RGBA format.
482 function convertToRGBAColor(color
) {
483 return 'rgba(' + color
[0] + ',' + color
[1] + ',' + color
[2] + ',' +
484 color
[3] / 255 + ')';
489 * Called when page data change.
491 function onMostVisitedChange() {
497 * Rerenders all tiles based on Most Visited page data.
499 function renderAllTiles() {
500 if (isBlacklisting
) {
501 // Trigger the blacklist animation, which then triggers reloadAllTiles().
502 var lastBlacklistedTileElem
= lastBlacklistedTile
.elem
;
503 lastBlacklistedTileElem
.addEventListener(
504 'webkitTransitionEnd', blacklistAnimationDone
);
505 lastBlacklistedTileElem
.classList
.add(CLASSES
.BLACKLIST
);
513 * Handles the end of the blacklist animation by showing the notification and
514 * re-rendering the new set of tiles.
516 function blacklistAnimationDone() {
518 isBlacklisting
= false;
519 tilesContainer
.classList
.remove(CLASSES
.HIDE_BLACKLIST_BUTTON
);
520 lastBlacklistedTile
.elem
.removeEventListener(
521 'webkitTransitionEnd', blacklistAnimationDone
);
522 // Need to call explicitly to re-render the tiles, since the initial
523 // renderAllTiles() issued by the blacklist function only triggered the
530 * Fetches new data, creates, and renders tiles.
532 function reloadAllTiles() {
533 var pages
= ntpApiHandle
.mostVisited
;
536 for (var i
= 0; i
< MAX_NUM_TILES_TO_SHOW
; ++i
)
537 tiles
.push(createTile(pages
[i
], i
));
539 tilesContainer
.innerHTML
= '';
540 renderAndShowTiles();
545 * Binds onload events for a tile's internal iframe elements.
546 * @param {Tile} tile The main tile to bind events to.
547 * @param {Barrier} tileVisibilityBarrier A barrier to make all tiles visible
548 * the moment all tiles are loaded.
550 function bindTileOnloadEvents(tile
, tileVisibilityBarrier
) {
551 if (tile
.titleElem
) {
552 tileVisibilityBarrier
.add();
553 tile
.titleElem
.onload = function() {
554 tileVisibilityBarrier
.remove();
557 if (tile
.thumbnailElem
) {
558 tileVisibilityBarrier
.add();
559 tile
.thumbnailElem
.onload = function() {
560 tile
.elem
.classList
.add(CLASSES
.PAGE_READY
);
561 tileVisibilityBarrier
.remove();
568 * Renders the current list of visible tiles to DOM, and hides tiles that are
569 * already in the DOM but should not be seen.
571 function renderAndShowTiles() {
572 var numExisting
= tilesContainer
.querySelectorAll('.' + CLASSES
.TILE
).length
;
573 // Only add visible tiles to the DOM, to avoid creating invisible tiles that
574 // produce meaningless impression metrics. However, if a tile becomes
575 // invisible then we leave it in DOM to prevent reload if it's shown again.
576 var numDesired
= Math
.min(tiles
.length
, numColumnsShown
* NUM_ROWS
);
578 // If we need to render new tiles, manage the visibility to hide intermediate
579 // load states of the iframes.
580 if (numExisting
< numDesired
) {
581 var showAll = function() {
582 for (var i
= 0; i
< numDesired
; ++i
) {
583 if (tiles
[i
].titleElem
|| tiles
[i
].thumbnailElem
)
584 tiles
[i
].elem
.classList
.add(CLASSES
.PAGE_READY
);
587 var tileVisibilityBarrier
= new Barrier(showAll
);
589 if (!userInitiatedMostVisitedChange
) {
590 // Make titleContainer invisible, but still taking up space.
591 // titleContainer becomes visible again (1) on timeout, or (2) when all
592 // tiles finish loading (using tileVisibilityBarrier).
593 window
.setTimeout(function() {
594 tileVisibilityBarrier
.cancel();
596 }, MOST_VISITED_PAINT_TIMEOUT_MSEC
);
598 userInitiatedMostVisitedChange
= false;
600 for (var i
= numExisting
; i
< numDesired
; ++i
) {
601 bindTileOnloadEvents(tiles
[i
], tileVisibilityBarrier
);
602 tilesContainer
.appendChild(tiles
[i
].elem
);
606 // Show only the desired tiles. Note that .hidden does not work for
607 // inline-block elements like tiles[i].elem.
608 for (var i
= 0; i
< numDesired
; ++i
)
609 tiles
[i
].elem
.style
.display
= 'inline-block';
610 // If |numDesired| < |numExisting| then hide extra tiles (e.g., this occurs
611 // when window is downsized).
612 for (; i
< numExisting
; ++i
)
613 tiles
[i
].elem
.style
.display
= 'none';
618 * Builds a URL to display a most visited tile title in an iframe.
619 * @param {number} rid The restricted ID.
620 * @param {number} position The position of the iframe in the UI.
621 * @return {string} An URL to display the most visited title in an iframe.
623 function getMostVisitedTitleIframeUrl(rid
, position
) {
624 var url
= 'chrome-search://most-visited/' +
625 encodeURIComponent(MOST_VISITED_TITLE_IFRAME
);
627 'rid=' + encodeURIComponent(rid
),
628 'f=' + encodeURIComponent(NTP_DESIGN
.fontFamily
),
629 'fs=' + encodeURIComponent(NTP_DESIGN
.fontSize
),
630 'c=' + encodeURIComponent(titleColor
),
631 'pos=' + encodeURIComponent(position
)];
632 if (NTP_DESIGN
.titleTextAlign
)
633 params
.push('ta=' + encodeURIComponent(NTP_DESIGN
.titleTextAlign
));
634 if (NTP_DESIGN
.titleTextFade
)
635 params
.push('tf=' + encodeURIComponent(NTP_DESIGN
.titleTextFade
));
636 return url
+ '?' + params
.join('&');
641 * Builds a URL to display a most visited tile thumbnail in an iframe.
642 * @param {number} rid The restricted ID.
643 * @param {number} position The position of the iframe in the UI.
644 * @return {string} An URL to display the most visited thumbnail in an iframe.
646 function getMostVisitedThumbnailIframeUrl(rid
, position
) {
647 var url
= 'chrome-search://most-visited/' +
648 encodeURIComponent(MOST_VISITED_THUMBNAIL_IFRAME
);
650 'rid=' + encodeURIComponent(rid
),
651 'f=' + encodeURIComponent(NTP_DESIGN
.fontFamily
),
652 'fs=' + encodeURIComponent(NTP_DESIGN
.fontSize
),
653 'c=' + encodeURIComponent(NTP_DESIGN
.thumbnailTextColor
),
654 'pos=' + encodeURIComponent(position
)];
655 if (NTP_DESIGN
.thumbnailFallback
)
656 params
.push('etfb=1');
657 return url
+ '?' + params
.join('&');
662 * Creates a Tile with the specified page data. If no data is provided, a
663 * filler Tile is created.
664 * @param {?Object} page The page data.
665 * @param {number} position The position of the tile.
666 * @return {Tile} The new Tile.
668 function createTile(page
, position
) {
669 var tileElem
= document
.createElement('div');
670 tileElem
.classList
.add(CLASSES
.TILE
);
671 // Prevent tile from being selected (and highlighted) when areas outside the
672 // iframes are clicked.
673 tileElem
.addEventListener('mousedown', function(e
) {
678 return new Tile(tileElem
);
682 tileElem
.classList
.add(CLASSES
.PAGE
);
684 var navigateFunction = function(e
) {
686 ntpApiHandle
.navigateContentWindow(rid
, getDispositionFromEvent(e
));
689 // The click handler for navigating to the page identified by the RID.
690 tileElem
.addEventListener('click', navigateFunction
);
692 // Container of tile contents.
693 var innerElem
= createAndAppendElement(tileElem
, 'div', CLASSES
.TILE_INNER
);
695 // The iframe which renders the page title.
696 var titleElem
= document
.createElement('iframe');
697 // Enable tab navigation on the iframe, which will move the selection to the
698 // link element (which also has a tabindex).
699 titleElem
.tabIndex
= '0';
701 // Make the iframe presentational for accessibility so screen readers perceive
702 // the iframe content as just part of the same page.
703 titleElem
.setAttribute('role', 'presentation');
705 // Why iframes have IDs:
707 // On navigating back to the NTP we see several onmostvisitedchange() events
708 // in series with incrementing RIDs. After the first event, a set of iframes
709 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
710 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
711 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
712 // first set of iframes into the most recent set of iframes.
714 // Giving iframes distinct ids seems to cause some invalidation and prevent
715 // associating the incorrect data.
717 // TODO(jered): Find and fix the root (probably Blink) bug.
719 // Keep this ID here. See comment above.
720 titleElem
.id
= 'title-' + rid
;
721 titleElem
.className
= CLASSES
.TITLE
;
722 titleElem
.src
= getMostVisitedTitleIframeUrl(rid
, position
);
723 innerElem
.appendChild(titleElem
);
725 // A fallback element for missing thumbnails.
726 if (NTP_DESIGN
.thumbnailFallback
) {
727 var fallbackElem
= createAndAppendElement(
728 innerElem
, 'div', CLASSES
.THUMBNAIL_FALLBACK
);
729 if (NTP_DESIGN
.thumbnailFallback
=== THUMBNAIL_FALLBACK
.DOT
)
730 createAndAppendElement(fallbackElem
, 'div', CLASSES
.DOT
);
733 // The iframe which renders either a thumbnail or domain element.
734 var thumbnailElem
= document
.createElement('iframe');
735 thumbnailElem
.tabIndex
= '-1';
736 thumbnailElem
.setAttribute('aria-hidden', 'true');
737 // Keep this ID here. See comment above.
738 thumbnailElem
.id
= 'thumb-' + rid
;
739 thumbnailElem
.className
= CLASSES
.THUMBNAIL
;
740 thumbnailElem
.src
= getMostVisitedThumbnailIframeUrl(rid
, position
);
741 innerElem
.appendChild(thumbnailElem
);
743 // The button used to blacklist this page.
744 var blacklistButton
= createAndAppendElement(
745 innerElem
, 'div', CLASSES
.BLACKLIST_BUTTON
);
746 createAndAppendElement(
747 blacklistButton
, 'div', CLASSES
.BLACKLIST_BUTTON_INNER
);
748 var blacklistFunction
= generateBlacklistFunction(rid
);
749 blacklistButton
.addEventListener('click', blacklistFunction
);
750 blacklistButton
.title
= configData
.translatedStrings
.removeThumbnailTooltip
;
752 // A helper mask on top of the tile that is used to create hover border
753 // and/or to darken the thumbnail on focus.
754 var maskElement
= createAndAppendElement(
755 innerElem
, 'div', CLASSES
.THUMBNAIL_MASK
);
757 // The page favicon, or a fallback.
758 var favicon
= createAndAppendElement(innerElem
, 'div', CLASSES
.FAVICON
);
759 if (page
.faviconUrl
) {
760 favicon
.style
.backgroundImage
= 'url(' + page
.faviconUrl
+ ')';
762 favicon
.classList
.add(CLASSES
.FAVICON_FALLBACK
);
764 return new Tile(tileElem
, innerElem
, titleElem
, thumbnailElem
, rid
);
769 * Generates a function to be called when the page with the corresponding RID
771 * @param {number} rid The RID of the page being blacklisted.
772 * @return {function(Event=)} A function which handles the blacklisting of the
773 * page by updating state variables and notifying Chrome.
775 function generateBlacklistFunction(rid
) {
777 // Prevent navigation when the page is being blacklisted.
781 userInitiatedMostVisitedChange
= true;
782 isBlacklisting
= true;
783 tilesContainer
.classList
.add(CLASSES
.HIDE_BLACKLIST_BUTTON
);
784 lastBlacklistedTile
= getTileByRid(rid
);
785 ntpApiHandle
.deleteMostVisitedItem(rid
);
791 * Shows the blacklist notification and triggers a delay to hide it.
793 function showNotification() {
794 notification
.classList
.remove(CLASSES
.HIDE_NOTIFICATION
);
795 notification
.classList
.remove(CLASSES
.DELAYED_HIDE_NOTIFICATION
);
796 notification
.scrollTop
;
797 notification
.classList
.add(CLASSES
.DELAYED_HIDE_NOTIFICATION
);
802 * Hides the blacklist notification.
804 function hideNotification() {
805 notification
.classList
.add(CLASSES
.HIDE_NOTIFICATION
);
806 notification
.classList
.remove(CLASSES
.DELAYED_HIDE_NOTIFICATION
);
811 * Handles a click on the notification undo link by hiding the notification and
815 userInitiatedMostVisitedChange
= true;
817 var lastBlacklistedRID
= lastBlacklistedTile
.rid
;
818 if (typeof lastBlacklistedRID
!= 'undefined')
819 ntpApiHandle
.undoMostVisitedDeletion(lastBlacklistedRID
);
824 * Handles a click on the restore all notification link by hiding the
825 * notification and informing Chrome.
827 function onRestoreAll() {
828 userInitiatedMostVisitedChange
= true;
830 ntpApiHandle
.undoAllMostVisitedDeletions();
835 * Recomputes the number of tile columns, and width of various contents based
836 * on the width of the window.
837 * @return {boolean} Whether the number of tile columns has changed.
839 function updateContentWidth() {
840 var tileRequiredWidth
= NTP_DESIGN
.tileWidth
+ NTP_DESIGN
.tileMargin
;
841 // If innerWidth is zero, then use the maximum snap size.
842 var maxSnapSize
= MAX_NUM_COLUMNS
* tileRequiredWidth
-
843 NTP_DESIGN
.tileMargin
+ MIN_TOTAL_HORIZONTAL_PADDING
;
844 var innerWidth
= window
.innerWidth
|| maxSnapSize
;
845 // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin.
846 var availableWidth
= innerWidth
+ NTP_DESIGN
.tileMargin
-
847 MIN_TOTAL_HORIZONTAL_PADDING
;
848 var newNumColumns
= Math
.floor(availableWidth
/ tileRequiredWidth
);
849 if (newNumColumns
< MIN_NUM_COLUMNS
)
850 newNumColumns
= MIN_NUM_COLUMNS
;
851 else if (newNumColumns
> MAX_NUM_COLUMNS
)
852 newNumColumns
= MAX_NUM_COLUMNS
;
854 if (numColumnsShown
=== newNumColumns
)
857 numColumnsShown
= newNumColumns
;
858 var tilesContainerWidth
= numColumnsShown
* tileRequiredWidth
;
859 tilesContainer
.style
.width
= tilesContainerWidth
+ 'px';
861 // -2 to account for border.
862 var fakeboxWidth
= (tilesContainerWidth
- NTP_DESIGN
.tileMargin
- 2);
863 fakebox
.style
.width
= fakeboxWidth
+ 'px';
870 * Resizes elements because the number of tile columns may need to change in
871 * response to resizing. Also shows or hides extra tiles tiles according to the
872 * new width of the page.
874 function onResize() {
875 if (updateContentWidth()) {
876 // Render without clearing tiles.
877 renderAndShowTiles();
883 * Returns the tile corresponding to the specified page RID.
884 * @param {number} rid The page RID being looked up.
885 * @return {Tile} The corresponding tile.
887 function getTileByRid(rid
) {
888 for (var i
= 0, length
= tiles
.length
; i
< length
; ++i
) {
898 * Handles new input by disposing the NTP, according to where the input was
901 function onInputStart() {
902 if (fakebox
&& isFakeboxFocused()) {
903 setFakeboxFocus(false);
904 setFakeboxDragFocus(false);
906 } else if (!isFakeboxFocused()) {
913 * Disposes the NTP, according to where the input was entered.
914 * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
916 function disposeNtp(wasFakeboxInput
) {
917 var behavior
= wasFakeboxInput
? fakeboxInputBehavior
: omniboxInputBehavior
;
918 if (behavior
== NTP_DISPOSE_STATE
.DISABLE_FAKEBOX
)
919 setFakeboxActive(false);
920 else if (behavior
== NTP_DISPOSE_STATE
.HIDE_FAKEBOX_AND_LOGO
)
921 setFakeboxAndLogoVisibility(false);
926 * Restores the NTP (re-enables the fakebox and unhides the logo.)
928 function restoreNtp() {
929 setFakeboxActive(true);
930 setFakeboxAndLogoVisibility(true);
935 * @param {boolean} focus True to focus the fakebox.
937 function setFakeboxFocus(focus
) {
938 document
.body
.classList
.toggle(CLASSES
.FAKEBOX_FOCUS
, focus
);
942 * @param {boolean} focus True to show a dragging focus to the fakebox.
944 function setFakeboxDragFocus(focus
) {
945 document
.body
.classList
.toggle(CLASSES
.FAKEBOX_DRAG_FOCUS
, focus
);
949 * @return {boolean} True if the fakebox has focus.
951 function isFakeboxFocused() {
952 return document
.body
.classList
.contains(CLASSES
.FAKEBOX_FOCUS
) ||
953 document
.body
.classList
.contains(CLASSES
.FAKEBOX_DRAG_FOCUS
);
958 * @param {boolean} enable True to enable the fakebox.
960 function setFakeboxActive(enable
) {
961 document
.body
.classList
.toggle(CLASSES
.FAKEBOX_DISABLE
, !enable
);
966 * @param {!Event} event The click event.
967 * @return {boolean} True if the click occurred in an enabled fakebox.
969 function isFakeboxClick(event
) {
970 return fakebox
.contains(event
.target
) &&
971 !document
.body
.classList
.contains(CLASSES
.FAKEBOX_DISABLE
);
976 * @param {boolean} show True to show the fakebox and logo.
978 function setFakeboxAndLogoVisibility(show
) {
979 document
.body
.classList
.toggle(CLASSES
.HIDE_FAKEBOX_AND_LOGO
, !show
);
984 * Shortcut for document.getElementById.
985 * @param {string} id of the element.
986 * @return {HTMLElement} with the id.
989 return document
.getElementById(id
);
994 * Utility function which creates an element with an optional classname and
995 * appends it to the specified parent.
996 * @param {Element} parent The parent to append the new element.
997 * @param {string} name The name of the new element.
998 * @param {string=} opt_class The optional classname of the new element.
999 * @return {Element} The new element.
1001 function createAndAppendElement(parent
, name
, opt_class
) {
1002 var child
= document
.createElement(name
);
1004 child
.classList
.add(opt_class
);
1005 parent
.appendChild(child
);
1011 * Removes a node from its parent.
1012 * @param {Node} node The node to remove.
1014 function removeNode(node
) {
1015 node
.parentNode
.removeChild(node
);
1020 * @param {!Element} element The element to register the handler for.
1021 * @param {number} keycode The keycode of the key to register.
1022 * @param {!Function} handler The key handler to register.
1024 function registerKeyHandler(element
, keycode
, handler
) {
1025 element
.addEventListener('keydown', function(event
) {
1026 if (event
.keyCode
== keycode
)
1033 * @return {Object} the handle to the embeddedSearch API.
1035 function getEmbeddedSearchApiHandle() {
1037 return window
.cideb
;
1038 if (window
.chrome
&& window
.chrome
.embeddedSearch
)
1039 return window
.chrome
.embeddedSearch
;
1045 * Event handler for the focus changed and blacklist messages on link elements.
1046 * Used to toggle visual treatment on the tiles (depending on the message).
1047 * @param {Event} event Event received.
1049 function handlePostMessage(event
) {
1050 if (event
.origin
!== 'chrome-search://most-visited')
1053 if (event
.data
=== 'linkFocused') {
1054 var activeElement
= document
.activeElement
;
1055 if (activeElement
.classList
.contains(CLASSES
.TITLE
)) {
1056 activeElement
.classList
.add(CLASSES
.FOCUSED
);
1057 focusedIframe
= activeElement
;
1059 } else if (event
.data
=== 'linkBlurred') {
1061 focusedIframe
.classList
.remove(CLASSES
.FOCUSED
);
1062 focusedIframe
= null;
1063 } else if (event
.data
.indexOf('tileBlacklisted') === 0) {
1064 var tilePosition
= event
.data
.split(',')[1];
1066 generateBlacklistFunction(tiles
[parseInt(tilePosition
, 10)].rid
)();
1072 * Prepares the New Tab Page by adding listeners, rendering the current
1073 * theme, the most visited pages section, and Google-specific elements for a
1074 * Google-provided page.
1077 tilesContainer
= $(IDS
.TILES
);
1078 notification
= $(IDS
.NOTIFICATION
);
1079 attribution
= $(IDS
.ATTRIBUTION
);
1080 ntpContents
= $(IDS
.NTP_CONTENTS
);
1082 if (configData
.isGooglePage
) {
1083 var logo
= document
.createElement('div');
1085 logo
.title
= 'Google';
1087 fakebox
= document
.createElement('div');
1088 fakebox
.id
= IDS
.FAKEBOX
;
1089 var fakeboxHtml
= [];
1090 fakeboxHtml
.push('<div id="' + IDS
.FAKEBOX_TEXT
+ '"></div>');
1091 fakeboxHtml
.push('<input id="' + IDS
.FAKEBOX_INPUT
+
1092 '" autocomplete="off" tabindex="-1" type="url" aria-hidden="true">');
1093 fakeboxHtml
.push('<div id="cursor"></div>');
1094 fakebox
.innerHTML
= fakeboxHtml
.join('');
1096 ntpContents
.insertBefore(fakebox
, ntpContents
.firstChild
);
1097 ntpContents
.insertBefore(logo
, ntpContents
.firstChild
);
1099 document
.body
.classList
.add(CLASSES
.NON_GOOGLE_PAGE
);
1102 // Hide notifications after fade out, so we can't focus on links via keyboard.
1103 notification
.addEventListener('webkitTransitionEnd', hideNotification
);
1105 var notificationMessage
= $(IDS
.NOTIFICATION_MESSAGE
);
1106 notificationMessage
.textContent
=
1107 configData
.translatedStrings
.thumbnailRemovedNotification
;
1109 var undoLink
= $(IDS
.UNDO_LINK
);
1110 undoLink
.addEventListener('click', onUndo
);
1111 registerKeyHandler(undoLink
, KEYCODE
.ENTER
, onUndo
);
1112 undoLink
.textContent
= configData
.translatedStrings
.undoThumbnailRemove
;
1114 var restoreAllLink
= $(IDS
.RESTORE_ALL_LINK
);
1115 restoreAllLink
.addEventListener('click', onRestoreAll
);
1116 registerKeyHandler(restoreAllLink
, KEYCODE
.ENTER
, onUndo
);
1117 restoreAllLink
.textContent
=
1118 configData
.translatedStrings
.restoreThumbnailsShort
;
1120 $(IDS
.ATTRIBUTION_TEXT
).textContent
=
1121 configData
.translatedStrings
.attributionIntro
;
1123 var notificationCloseButton
= $(IDS
.NOTIFICATION_CLOSE_BUTTON
);
1124 createAndAppendElement(
1125 notificationCloseButton
, 'div', CLASSES
.BLACKLIST_BUTTON_INNER
);
1126 notificationCloseButton
.addEventListener('click', hideNotification
);
1128 window
.addEventListener('resize', onResize
);
1129 updateContentWidth();
1131 var topLevelHandle
= getEmbeddedSearchApiHandle();
1133 ntpApiHandle
= topLevelHandle
.newTabPage
;
1134 ntpApiHandle
.onthemechange
= onThemeChange
;
1135 ntpApiHandle
.onmostvisitedchange
= onMostVisitedChange
;
1137 ntpApiHandle
.oninputstart
= onInputStart
;
1138 ntpApiHandle
.oninputcancel
= restoreNtp
;
1140 if (ntpApiHandle
.isInputInProgress
)
1146 searchboxApiHandle
= topLevelHandle
.searchBox
;
1149 // Listener for updating the key capture state.
1150 document
.body
.onmousedown = function(event
) {
1151 if (isFakeboxClick(event
))
1152 searchboxApiHandle
.startCapturingKeyStrokes();
1153 else if (isFakeboxFocused())
1154 searchboxApiHandle
.stopCapturingKeyStrokes();
1156 searchboxApiHandle
.onkeycapturechange = function() {
1157 setFakeboxFocus(searchboxApiHandle
.isKeyCaptureEnabled
);
1159 var inputbox
= $(IDS
.FAKEBOX_INPUT
);
1161 inputbox
.onpaste = function(event
) {
1162 event
.preventDefault();
1163 // Send pasted text to Omnibox.
1164 var text
= event
.clipboardData
.getData('text/plain');
1166 searchboxApiHandle
.paste(text
);
1168 inputbox
.ondrop = function(event
) {
1169 event
.preventDefault();
1170 var text
= event
.dataTransfer
.getData('text/plain');
1172 searchboxApiHandle
.paste(text
);
1175 inputbox
.ondragenter = function() {
1176 setFakeboxDragFocus(true);
1178 inputbox
.ondragleave = function() {
1179 setFakeboxDragFocus(false);
1183 // Update the fakebox style to match the current key capturing state.
1184 setFakeboxFocus(searchboxApiHandle
.isKeyCaptureEnabled
);
1187 if (searchboxApiHandle
.rtl
) {
1188 $(IDS
.NOTIFICATION
).dir
= 'rtl';
1189 // Grabbing the root HTML element.
1190 document
.documentElement
.setAttribute('dir', 'rtl');
1191 // Add class for setting alignments based on language directionality.
1192 document
.documentElement
.classList
.add(CLASSES
.RTL
);
1193 $(IDS
.TILES
).dir
= 'rtl';
1196 window
.addEventListener('message', handlePostMessage
);
1201 * Binds event listeners.
1204 document
.addEventListener('DOMContentLoaded', init
);
1213 if (!window
.localNTPUnitTest
) {
1214 LocalNTP().listen();