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 * Specifications for the NTP design.
117 var NTP_DESIGN
= getNtpDesign(configData
.ntpDesignName
);
121 * The container for the tile elements.
128 * The notification displayed when a page is blacklisted.
135 * The container for the theme attribution.
142 * The "fakebox" - an input field that looks like a regular searchbox. When it
143 * is focused, any text the user types goes directly into the omnibox.
150 * The container for NTP elements.
157 * The array of rendered tiles, ordered by appearance.
158 * @type {!Array.<Tile>}
164 * The last blacklisted tile if any, which by definition should not be filler.
167 var lastBlacklistedTile
= null;
171 * The iframe element which is currently keyboard focused, or null.
174 var focusedIframe
= null;
178 * True if a page has been blacklisted and we're waiting on the
179 * onmostvisitedchange callback. See onMostVisitedChange() for how this
183 var isBlacklisting
= false;
187 * Current number of tiles columns shown based on the window width, including
188 * those that just contain filler.
191 var numColumnsShown
= 0;
195 * A flag to indicate Most Visited changed caused by user action. If true, then
196 * in onMostVisitedChange() tiles remain visible so no flickering occurs.
199 var userInitiatedMostVisitedChange
= false;
203 * The browser embeddedSearch.newTabPage object.
210 * The browser embeddedSearch.searchBox object.
213 var searchboxApiHandle
;
217 * The state of the NTP when a query is entered into the Omnibox.
218 * @type {NTP_DISPOSE_STATE}
220 var omniboxInputBehavior
= NTP_DISPOSE_STATE
.NONE
;
224 * The state of the NTP when a query is entered into the Fakebox.
225 * @type {NTP_DISPOSE_STATE}
227 var fakeboxInputBehavior
= NTP_DISPOSE_STATE
.HIDE_FAKEBOX_AND_LOGO
;
230 /** @type {number} @const */
231 var MAX_NUM_TILES_TO_SHOW
= 8;
234 /** @type {number} @const */
235 var MIN_NUM_COLUMNS
= 2;
238 /** @type {number} @const */
239 var MAX_NUM_COLUMNS
= 4;
242 /** @type {number} @const */
247 * Minimum total padding to give to the left and right of the most visited
248 * section. Used to determine how many tiles to show.
252 var MIN_TOTAL_HORIZONTAL_PADDING
= 200;
256 * The filename for a most visited iframe src which shows a page title.
260 var MOST_VISITED_TITLE_IFRAME
= 'title.html';
264 * The filename for a most visited iframe src which shows a thumbnail image.
268 var MOST_VISITED_THUMBNAIL_IFRAME
= 'thumbnail.html';
272 * The color of the title in RRGGBBAA format.
275 var titleColor
= null;
279 * Hide most visited tiles for at most this many milliseconds while painting.
283 var MOST_VISITED_PAINT_TIMEOUT_MSEC
= 500;
287 * A Tile is either a rendering of a Most Visited page or "filler" used to
288 * pad out the section when not enough pages exist.
290 * @param {Element} elem The element for rendering the tile.
291 * @param {Element=} opt_innerElem The element for contents of tile.
292 * @param {Element=} opt_titleElem The element for rendering the title.
293 * @param {Element=} opt_thumbnailElem The element for rendering the thumbnail.
294 * @param {number=} opt_rid The RID for the corresponding Most Visited page.
295 * Should only be left unspecified when creating a filler tile.
298 function Tile(elem
, opt_innerElem
, opt_titleElem
, opt_thumbnailElem
, opt_rid
) {
299 /** @type {Element} */
302 /** @type {Element|undefined} */
303 this.innerElem
= opt_innerElem
;
305 /** @type {Element|undefined} */
306 this.titleElem
= opt_titleElem
;
308 /** @type {Element|undefined} */
309 this.thumbnailElem
= opt_thumbnailElem
;
311 /** @type {number|undefined} */
317 * Heuristic to determine whether a theme should be considered to be dark, so
318 * the colors of various UI elements can be adjusted.
319 * @param {ThemeBackgroundInfo|undefined} info Theme background information.
320 * @return {boolean} Whether the theme is dark.
323 function getIsThemeDark(info
) {
326 // Heuristic: light text implies dark theme.
327 var rgba
= info
.textColorRgba
;
328 var luminance
= 0.3 * rgba
[0] + 0.59 * rgba
[1] + 0.11 * rgba
[2];
329 return luminance
>= 128;
334 * Updates the NTP based on the current theme.
337 function renderTheme() {
338 var fakeboxText
= $(IDS
.FAKEBOX_TEXT
);
340 fakeboxText
.innerHTML
= '';
341 if (NTP_DESIGN
.showFakeboxHint
&&
342 configData
.translatedStrings
.searchboxPlaceholder
) {
343 fakeboxText
.textContent
=
344 configData
.translatedStrings
.searchboxPlaceholder
;
348 var info
= ntpApiHandle
.themeBackgroundInfo
;
349 var isThemeDark
= getIsThemeDark(info
);
350 ntpContents
.classList
.toggle(CLASSES
.DARK
, isThemeDark
);
352 titleColor
= NTP_DESIGN
.titleColor
;
356 if (!info
.usingDefaultTheme
&& info
.textColorRgba
) {
357 titleColor
= convertToRRGGBBAAColor(info
.textColorRgba
);
359 titleColor
= isThemeDark
?
360 NTP_DESIGN
.titleColorAgainstDark
: NTP_DESIGN
.titleColor
;
363 var background
= [convertToRGBAColor(info
.backgroundColorRgba
),
366 info
.imageHorizontalAlignment
,
367 info
.imageVerticalAlignment
].join(' ').trim();
369 document
.body
.style
.background
= background
;
370 document
.body
.classList
.toggle(CLASSES
.ALTERNATE_LOGO
, info
.alternateLogo
);
371 updateThemeAttribution(info
.attributionUrl
);
372 setCustomThemeStyle(info
);
377 * Updates the NTP based on the current theme, then rerenders all tiles.
380 function onThemeChange() {
382 tilesContainer
.innerHTML
= '';
383 renderAndShowTiles();
388 * Updates the NTP style according to theme.
389 * @param {Object=} opt_themeInfo The information about the theme. If it is
390 * omitted the style will be reverted to the default.
393 function setCustomThemeStyle(opt_themeInfo
) {
394 var customStyleElement
= $(IDS
.CUSTOM_THEME_STYLE
);
395 var head
= document
.head
;
396 if (opt_themeInfo
&& !opt_themeInfo
.usingDefaultTheme
) {
397 ntpContents
.classList
.remove(CLASSES
.DEFAULT_THEME
);
400 ' color: ' + convertToRGBAColor(opt_themeInfo
.textColorLightRgba
) + ';' +
403 ' color: ' + convertToRGBAColor(opt_themeInfo
.textColorRgba
) + ';' +
405 '#mv-notice-links span {' +
406 ' color: ' + convertToRGBAColor(opt_themeInfo
.textColorLightRgba
) + ';' +
409 ' -webkit-filter: drop-shadow(0 0 0 ' +
410 convertToRGBAColor(opt_themeInfo
.textColorRgba
) + ');' +
412 '.mv-page-ready .mv-mask {' +
413 ' border: 1px solid ' +
414 convertToRGBAColor(opt_themeInfo
.sectionBorderColorRgba
) + ';' +
416 '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask {' +
418 convertToRGBAColor(opt_themeInfo
.headerColorRgba
) + ';' +
421 if (customStyleElement
) {
422 customStyleElement
.textContent
= themeStyle
;
424 customStyleElement
= document
.createElement('style');
425 customStyleElement
.type
= 'text/css';
426 customStyleElement
.id
= IDS
.CUSTOM_THEME_STYLE
;
427 customStyleElement
.textContent
= themeStyle
;
428 head
.appendChild(customStyleElement
);
432 ntpContents
.classList
.add(CLASSES
.DEFAULT_THEME
);
433 if (customStyleElement
)
434 head
.removeChild(customStyleElement
);
440 * Renders the attribution if the URL is present, otherwise hides it.
441 * @param {string} url The URL of the attribution image, if any.
444 function updateThemeAttribution(url
) {
446 setAttributionVisibility_(false);
450 var attributionImage
= attribution
.querySelector('img');
451 if (!attributionImage
) {
452 attributionImage
= new Image();
453 attribution
.appendChild(attributionImage
);
455 attributionImage
.style
.content
= url
;
456 setAttributionVisibility_(true);
461 * Sets the visibility of the theme attribution.
462 * @param {boolean} show True to show the attribution.
465 function setAttributionVisibility_(show
) {
467 attribution
.style
.display
= show
? '' : 'none';
473 * Converts an Array of color components into RRGGBBAA format.
474 * @param {Array.<number>} color Array of rgba color components.
475 * @return {string} Color string in RRGGBBAA format.
478 function convertToRRGGBBAAColor(color
) {
479 return color
.map(function(t
) {
480 return ('0' + t
.toString(16)).slice(-2); // To 2-digit, 0-padded hex.
486 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
487 * @param {Array.<number>} color Array of rgba color components.
488 * @return {string} CSS color in RGBA format.
491 function convertToRGBAColor(color
) {
492 return 'rgba(' + color
[0] + ',' + color
[1] + ',' + color
[2] + ',' +
493 color
[3] / 255 + ')';
498 * Handles a new set of Most Visited page data.
500 function onMostVisitedChange() {
501 if (isBlacklisting
) {
502 // Trigger the blacklist animation, which then triggers reloadAllTiles().
503 var lastBlacklistedTileElem
= lastBlacklistedTile
.elem
;
504 lastBlacklistedTileElem
.addEventListener(
505 'webkitTransitionEnd', blacklistAnimationDone
);
506 lastBlacklistedTileElem
.classList
.add(CLASSES
.BLACKLIST
);
514 * Handles the end of the blacklist animation by showing the notification and
515 * re-rendering the new set of tiles.
517 function blacklistAnimationDone() {
519 isBlacklisting
= false;
520 tilesContainer
.classList
.remove(CLASSES
.HIDE_BLACKLIST_BUTTON
);
521 lastBlacklistedTile
.elem
.removeEventListener(
522 'webkitTransitionEnd', blacklistAnimationDone
);
523 // Need to call explicitly to re-render the tiles, since the initial
524 // onmostvisitedchange issued by the blacklist function only triggered
531 * Fetches new data, creates, and renders tiles.
533 function reloadAllTiles() {
534 var pages
= ntpApiHandle
.mostVisited
;
537 for (var i
= 0; i
< MAX_NUM_TILES_TO_SHOW
; ++i
)
538 tiles
.push(createTile(pages
[i
], i
));
540 tilesContainer
.innerHTML
= '';
541 renderAndShowTiles();
546 * Binds onload events for a tile's internal <iframe> elements.
547 * @param {Tile} tile The main tile to bind events to.
548 * @param {Barrier} tileVisibilityBarrier A barrier to make all tiles visible
549 * the moment all tiles are loaded.
551 function bindTileOnloadEvents(tile
, tileVisibilityBarrier
) {
552 if (tile
.titleElem
) {
553 tileVisibilityBarrier
.add();
554 tile
.titleElem
.onload = function() {
555 tileVisibilityBarrier
.remove();
558 if (tile
.thumbnailElem
) {
559 tileVisibilityBarrier
.add();
560 tile
.thumbnailElem
.onload = function() {
561 tile
.elem
.classList
.add(CLASSES
.PAGE_READY
);
562 tileVisibilityBarrier
.remove();
569 * Renders the current list of visible tiles to DOM, and hides tiles that are
570 * already in the DOM but should not be seen.
572 function renderAndShowTiles() {
573 var numExisting
= tilesContainer
.querySelectorAll('.' + CLASSES
.TILE
).length
;
574 // Only add visible tiles to the DOM, to avoid creating invisible tiles that
575 // produce meaningless impression metrics. However, if a tile becomes
576 // invisible then we leave it in DOM to prevent reload if it's shown again.
577 var numDesired
= Math
.min(tiles
.length
, numColumnsShown
* NUM_ROWS
);
579 // If we need to render new tiles, manage the visibility to hide intermediate
580 // load states of the <iframe>s.
581 if (numExisting
< numDesired
) {
582 var showAll = function() {
583 for (var i
= 0; i
< numDesired
; ++i
) {
584 if (tiles
[i
].titleElem
|| tiles
[i
].thumbnailElem
)
585 tiles
[i
].elem
.classList
.add(CLASSES
.PAGE_READY
);
588 var tileVisibilityBarrier
= new Barrier(showAll
);
590 if (!userInitiatedMostVisitedChange
) {
591 // Make titleContainer invisible, but still taking up space.
592 // titleContainer becomes visible again (1) on timeout, or (2) when all
593 // tiles finish loading (using tileVisibilityBarrier).
594 window
.setTimeout(function() {
595 tileVisibilityBarrier
.cancel();
597 }, MOST_VISITED_PAINT_TIMEOUT_MSEC
);
599 userInitiatedMostVisitedChange
= false;
601 for (var i
= numExisting
; i
< numDesired
; ++i
) {
602 bindTileOnloadEvents(tiles
[i
], tileVisibilityBarrier
);
603 tilesContainer
.appendChild(tiles
[i
].elem
);
607 // Show only the desired tiles. Note that .hidden does not work for
608 // inline-block elements like tiles[i].elem.
609 for (var i
= 0; i
< numDesired
; ++i
)
610 tiles
[i
].elem
.style
.display
= 'inline-block';
611 // If |numDesired| < |numExisting| then hide extra tiles (e.g., this occurs
612 // when window is downsized).
613 for (; i
< numExisting
; ++i
)
614 tiles
[i
].elem
.style
.display
= 'none';
619 * Builds a URL to display a most visited tile title in an iframe.
620 * @param {number} rid The restricted ID.
621 * @param {number} position The position of the iframe in the UI.
622 * @return {string} An URL to display the most visited title in an iframe.
624 function getMostVisitedTitleIframeUrl(rid
, position
) {
625 var url
= 'chrome-search://most-visited/' +
626 encodeURIComponent(MOST_VISITED_TITLE_IFRAME
);
628 'rid=' + encodeURIComponent(rid
),
629 'f=' + encodeURIComponent(NTP_DESIGN
.fontFamily
),
630 'fs=' + encodeURIComponent(NTP_DESIGN
.fontSize
),
631 'c=' + encodeURIComponent(titleColor
),
632 'pos=' + encodeURIComponent(position
)];
633 if (NTP_DESIGN
.titleTextAlign
)
634 params
.push('ta=' + encodeURIComponent(NTP_DESIGN
.titleTextAlign
));
635 if (NTP_DESIGN
.titleTextFade
)
636 params
.push('tf=' + encodeURIComponent(NTP_DESIGN
.titleTextFade
));
637 return url
+ '?' + params
.join('&');
642 * Builds a URL to display a most visited tile thumbnail in an iframe.
643 * @param {number} rid The restricted ID.
644 * @param {number} position The position of the iframe in the UI.
645 * @return {string} An URL to display the most visited thumbnail in an iframe.
647 function getMostVisitedThumbnailIframeUrl(rid
, position
) {
648 var url
= 'chrome-search://most-visited/' +
649 encodeURIComponent(MOST_VISITED_THUMBNAIL_IFRAME
);
651 'rid=' + encodeURIComponent(rid
),
652 'f=' + encodeURIComponent(NTP_DESIGN
.fontFamily
),
653 'fs=' + encodeURIComponent(NTP_DESIGN
.fontSize
),
654 'c=' + encodeURIComponent(NTP_DESIGN
.thumbnailTextColor
),
655 'pos=' + encodeURIComponent(position
)];
656 if (NTP_DESIGN
.thumbnailFallback
)
657 params
.push('etfb=1');
658 return url
+ '?' + params
.join('&');
663 * Creates a Tile with the specified page data. If no data is provided, a
664 * filler Tile is created.
665 * @param {Object} page The page data.
666 * @param {number} position The position of the tile.
667 * @return {Tile} The new Tile.
669 function createTile(page
, position
) {
670 var tileElem
= document
.createElement('div');
671 tileElem
.classList
.add(CLASSES
.TILE
);
672 // Prevent tile from being selected (and highlighted) when areas outside the
673 // <iframe>s are clicked.
674 tileElem
.addEventListener('mousedown', function(e
) {
677 var innerElem
= createAndAppendElement(tileElem
, 'div', CLASSES
.TILE_INNER
);
681 tileElem
.classList
.add(CLASSES
.PAGE
);
683 var navigateFunction = function(e
) {
685 ntpApiHandle
.navigateContentWindow(rid
, getDispositionFromEvent(e
));
688 // The click handler for navigating to the page identified by the RID.
689 tileElem
.addEventListener('click', navigateFunction
);
691 // The iframe which renders the page title.
692 var titleElem
= document
.createElement('iframe');
693 // Enable tab navigation on the iframe, which will move the selection to the
694 // link element (which also has a tabindex).
695 titleElem
.tabIndex
= '0';
697 // Why iframes have IDs:
699 // On navigating back to the NTP we see several onmostvisitedchange() events
700 // in series with incrementing RIDs. After the first event, a set of iframes
701 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
702 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
703 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
704 // first set of iframes into the most recent set of iframes.
706 // Giving iframes distinct ids seems to cause some invalidation and prevent
707 // associating the incorrect data.
709 // TODO(jered): Find and fix the root (probably Blink) bug.
711 // Keep this ID here. See comment above.
712 titleElem
.id
= 'title-' + rid
;
713 titleElem
.className
= CLASSES
.TITLE
;
714 titleElem
.src
= getMostVisitedTitleIframeUrl(rid
, position
);
715 innerElem
.appendChild(titleElem
);
717 // A fallback element for missing thumbnails.
718 if (NTP_DESIGN
.thumbnailFallback
) {
719 var fallbackElem
= createAndAppendElement(
720 innerElem
, 'div', CLASSES
.THUMBNAIL_FALLBACK
);
721 if (NTP_DESIGN
.thumbnailFallback
=== THUMBNAIL_FALLBACK
.DOT
)
722 createAndAppendElement(fallbackElem
, 'div', CLASSES
.DOT
);
725 // The iframe which renders either a thumbnail or domain element.
726 var thumbnailElem
= document
.createElement('iframe');
727 thumbnailElem
.tabIndex
= '-1';
728 thumbnailElem
.setAttribute('aria-hidden', 'true');
729 // Keep this ID here. See comment above.
730 thumbnailElem
.id
= 'thumb-' + rid
;
731 thumbnailElem
.className
= CLASSES
.THUMBNAIL
;
732 thumbnailElem
.src
= getMostVisitedThumbnailIframeUrl(rid
, position
);
733 innerElem
.appendChild(thumbnailElem
);
735 // The button used to blacklist this page.
736 var blacklistButton
= createAndAppendElement(
737 innerElem
, 'div', CLASSES
.BLACKLIST_BUTTON
);
738 createAndAppendElement(
739 blacklistButton
, 'div', CLASSES
.BLACKLIST_BUTTON_INNER
);
740 var blacklistFunction
= generateBlacklistFunction(rid
);
741 blacklistButton
.addEventListener('click', blacklistFunction
);
742 blacklistButton
.title
= configData
.translatedStrings
.removeThumbnailTooltip
;
744 // A helper mask on top of the tile that is used to create hover border
745 // and/or to darken the thumbnail on focus.
746 var maskElement
= createAndAppendElement(
747 innerElem
, 'div', CLASSES
.THUMBNAIL_MASK
);
749 // The page favicon, or a fallback.
750 var favicon
= createAndAppendElement(innerElem
, 'div', CLASSES
.FAVICON
);
751 if (page
.faviconUrl
) {
752 favicon
.style
.backgroundImage
= 'url(' + page
.faviconUrl
+ ')';
754 favicon
.classList
.add(CLASSES
.FAVICON_FALLBACK
);
756 return new Tile(tileElem
, innerElem
, titleElem
, thumbnailElem
, rid
);
758 return new Tile(tileElem
);
764 * Generates a function to be called when the page with the corresponding RID
766 * @param {number} rid The RID of the page being blacklisted.
767 * @return {function(Event=)} A function which handles the blacklisting of the
768 * page by updating state variables and notifying Chrome.
770 function generateBlacklistFunction(rid
) {
772 // Prevent navigation when the page is being blacklisted.
776 userInitiatedMostVisitedChange
= true;
777 isBlacklisting
= true;
778 tilesContainer
.classList
.add(CLASSES
.HIDE_BLACKLIST_BUTTON
);
779 lastBlacklistedTile
= getTileByRid(rid
);
780 ntpApiHandle
.deleteMostVisitedItem(rid
);
786 * Shows the blacklist notification and triggers a delay to hide it.
788 function showNotification() {
789 notification
.classList
.remove(CLASSES
.HIDE_NOTIFICATION
);
790 notification
.classList
.remove(CLASSES
.DELAYED_HIDE_NOTIFICATION
);
791 notification
.scrollTop
;
792 notification
.classList
.add(CLASSES
.DELAYED_HIDE_NOTIFICATION
);
797 * Hides the blacklist notification.
799 function hideNotification() {
800 notification
.classList
.add(CLASSES
.HIDE_NOTIFICATION
);
801 notification
.classList
.remove(CLASSES
.DELAYED_HIDE_NOTIFICATION
);
806 * Handles a click on the notification undo link by hiding the notification and
810 userInitiatedMostVisitedChange
= true;
812 var lastBlacklistedRID
= lastBlacklistedTile
.rid
;
813 if (typeof lastBlacklistedRID
!= 'undefined')
814 ntpApiHandle
.undoMostVisitedDeletion(lastBlacklistedRID
);
819 * Handles a click on the restore all notification link by hiding the
820 * notification and informing Chrome.
822 function onRestoreAll() {
823 userInitiatedMostVisitedChange
= true;
825 ntpApiHandle
.undoAllMostVisitedDeletions();
830 * Recomputes the number of tile columns, and width of various contents based
831 * on the width of the window.
832 * @return {boolean} Whether the number of tile columns has changed.
834 function updateContentWidth() {
835 var tileRequiredWidth
= NTP_DESIGN
.tileWidth
+ NTP_DESIGN
.tileMargin
;
836 // If innerWidth is zero, then use the maximum snap size.
837 var maxSnapSize
= MAX_NUM_COLUMNS
* tileRequiredWidth
-
838 NTP_DESIGN
.tileMargin
+ MIN_TOTAL_HORIZONTAL_PADDING
;
839 var innerWidth
= window
.innerWidth
|| maxSnapSize
;
840 // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin.
841 var availableWidth
= innerWidth
+ NTP_DESIGN
.tileMargin
-
842 MIN_TOTAL_HORIZONTAL_PADDING
;
843 var newNumColumns
= Math
.floor(availableWidth
/ tileRequiredWidth
);
844 if (newNumColumns
< MIN_NUM_COLUMNS
)
845 newNumColumns
= MIN_NUM_COLUMNS
;
846 else if (newNumColumns
> MAX_NUM_COLUMNS
)
847 newNumColumns
= MAX_NUM_COLUMNS
;
849 if (numColumnsShown
=== newNumColumns
)
852 numColumnsShown
= newNumColumns
;
853 var tilesContainerWidth
= numColumnsShown
* tileRequiredWidth
;
854 tilesContainer
.style
.width
= tilesContainerWidth
+ 'px';
856 // -2 to account for border.
857 var fakeboxWidth
= (tilesContainerWidth
- NTP_DESIGN
.tileMargin
- 2);
858 fakebox
.style
.width
= fakeboxWidth
+ 'px';
865 * Resizes elements because the number of tile columns may need to change in
866 * response to resizing. Also shows or hides extra tiles tiles according to the
867 * new width of the page.
869 function onResize() {
870 if (updateContentWidth()) {
871 // Render without clearing tiles.
872 renderAndShowTiles();
878 * Returns the tile corresponding to the specified page RID.
879 * @param {number} rid The page RID being looked up.
880 * @return {Tile} The corresponding tile.
882 function getTileByRid(rid
) {
883 for (var i
= 0, length
= tiles
.length
; i
< length
; ++i
) {
893 * Handles new input by disposing the NTP, according to where the input was
896 function onInputStart() {
897 if (fakebox
&& isFakeboxFocused()) {
898 setFakeboxFocus(false);
899 setFakeboxDragFocus(false);
901 } else if (!isFakeboxFocused()) {
908 * Disposes the NTP, according to where the input was entered.
909 * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
911 function disposeNtp(wasFakeboxInput
) {
912 var behavior
= wasFakeboxInput
? fakeboxInputBehavior
: omniboxInputBehavior
;
913 if (behavior
== NTP_DISPOSE_STATE
.DISABLE_FAKEBOX
)
914 setFakeboxActive(false);
915 else if (behavior
== NTP_DISPOSE_STATE
.HIDE_FAKEBOX_AND_LOGO
)
916 setFakeboxAndLogoVisibility(false);
921 * Restores the NTP (re-enables the fakebox and unhides the logo.)
923 function restoreNtp() {
924 setFakeboxActive(true);
925 setFakeboxAndLogoVisibility(true);
930 * @param {boolean} focus True to focus the fakebox.
932 function setFakeboxFocus(focus
) {
933 document
.body
.classList
.toggle(CLASSES
.FAKEBOX_FOCUS
, focus
);
937 * @param {boolean} focus True to show a dragging focus to the fakebox.
939 function setFakeboxDragFocus(focus
) {
940 document
.body
.classList
.toggle(CLASSES
.FAKEBOX_DRAG_FOCUS
, focus
);
944 * @return {boolean} True if the fakebox has focus.
946 function isFakeboxFocused() {
947 return document
.body
.classList
.contains(CLASSES
.FAKEBOX_FOCUS
) ||
948 document
.body
.classList
.contains(CLASSES
.FAKEBOX_DRAG_FOCUS
);
953 * @param {boolean} enable True to enable the fakebox.
955 function setFakeboxActive(enable
) {
956 document
.body
.classList
.toggle(CLASSES
.FAKEBOX_DISABLE
, !enable
);
961 * @param {!Event} event The click event.
962 * @return {boolean} True if the click occurred in an enabled fakebox.
964 function isFakeboxClick(event
) {
965 return fakebox
.contains(event
.target
) &&
966 !document
.body
.classList
.contains(CLASSES
.FAKEBOX_DISABLE
);
971 * @param {boolean} show True to show the fakebox and logo.
973 function setFakeboxAndLogoVisibility(show
) {
974 document
.body
.classList
.toggle(CLASSES
.HIDE_FAKEBOX_AND_LOGO
, !show
);
979 * Shortcut for document.getElementById.
980 * @param {string} id of the element.
981 * @return {HTMLElement} with the id.
984 return document
.getElementById(id
);
989 * Utility function which creates an element with an optional classname and
990 * appends it to the specified parent.
991 * @param {Element} parent The parent to append the new element.
992 * @param {string} name The name of the new element.
993 * @param {string=} opt_class The optional classname of the new element.
994 * @return {Element} The new element.
996 function createAndAppendElement(parent
, name
, opt_class
) {
997 var child
= document
.createElement(name
);
999 child
.classList
.add(opt_class
);
1000 parent
.appendChild(child
);
1006 * Removes a node from its parent.
1007 * @param {Node} node The node to remove.
1009 function removeNode(node
) {
1010 node
.parentNode
.removeChild(node
);
1015 * @param {!Element} element The element to register the handler for.
1016 * @param {number} keycode The keycode of the key to register.
1017 * @param {!Function} handler The key handler to register.
1019 function registerKeyHandler(element
, keycode
, handler
) {
1020 element
.addEventListener('keydown', function(event
) {
1021 if (event
.keyCode
== keycode
)
1028 * @return {Object} the handle to the embeddedSearch API.
1030 function getEmbeddedSearchApiHandle() {
1032 return window
.cideb
;
1033 if (window
.chrome
&& window
.chrome
.embeddedSearch
)
1034 return window
.chrome
.embeddedSearch
;
1040 * Event handler for the focus changed and blacklist messages on link elements.
1041 * Used to toggle visual treatment on the tiles (depending on the message).
1042 * @param {Event} event Event received.
1044 function handlePostMessage(event
) {
1045 if (event
.origin
!== 'chrome-search://most-visited')
1048 if (event
.data
=== 'linkFocused') {
1049 var activeElement
= document
.activeElement
;
1050 if (activeElement
.classList
.contains(CLASSES
.TITLE
)) {
1051 activeElement
.classList
.add(CLASSES
.FOCUSED
);
1052 focusedIframe
= activeElement
;
1054 } else if (event
.data
=== 'linkBlurred') {
1056 focusedIframe
.classList
.remove(CLASSES
.FOCUSED
);
1057 focusedIframe
= null;
1058 } else if (event
.data
.indexOf('tileBlacklisted') === 0) {
1059 var tilePosition
= event
.data
.split(',')[1];
1061 generateBlacklistFunction(tiles
[parseInt(tilePosition
, 10)].rid
)();
1067 * Prepares the New Tab Page by adding listeners, rendering the current
1068 * theme, the most visited pages section, and Google-specific elements for a
1069 * Google-provided page.
1072 tilesContainer
= $(IDS
.TILES
);
1073 notification
= $(IDS
.NOTIFICATION
);
1074 attribution
= $(IDS
.ATTRIBUTION
);
1075 ntpContents
= $(IDS
.NTP_CONTENTS
);
1077 if (configData
.isGooglePage
) {
1078 var logo
= document
.createElement('div');
1081 fakebox
= document
.createElement('div');
1082 fakebox
.id
= IDS
.FAKEBOX
;
1083 var fakeboxHtml
= [];
1084 fakeboxHtml
.push('<div id="' + IDS
.FAKEBOX_TEXT
+ '"></div>');
1085 fakeboxHtml
.push('<input id="' + IDS
.FAKEBOX_INPUT
+
1086 '" autocomplete="off" tabindex="-1" type="url" aria-hidden="true">');
1087 fakeboxHtml
.push('<div id="cursor"></div>');
1088 fakebox
.innerHTML
= fakeboxHtml
.join('');
1090 ntpContents
.insertBefore(fakebox
, ntpContents
.firstChild
);
1091 ntpContents
.insertBefore(logo
, ntpContents
.firstChild
);
1093 document
.body
.classList
.add(CLASSES
.NON_GOOGLE_PAGE
);
1096 // Hide notifications after fade out, so we can't focus on links via keyboard.
1097 notification
.addEventListener('webkitTransitionEnd', hideNotification
);
1099 var notificationMessage
= $(IDS
.NOTIFICATION_MESSAGE
);
1100 notificationMessage
.textContent
=
1101 configData
.translatedStrings
.thumbnailRemovedNotification
;
1103 var undoLink
= $(IDS
.UNDO_LINK
);
1104 undoLink
.addEventListener('click', onUndo
);
1105 registerKeyHandler(undoLink
, KEYCODE
.ENTER
, onUndo
);
1106 undoLink
.textContent
= configData
.translatedStrings
.undoThumbnailRemove
;
1108 var restoreAllLink
= $(IDS
.RESTORE_ALL_LINK
);
1109 restoreAllLink
.addEventListener('click', onRestoreAll
);
1110 registerKeyHandler(restoreAllLink
, KEYCODE
.ENTER
, onUndo
);
1111 restoreAllLink
.textContent
=
1112 configData
.translatedStrings
.restoreThumbnailsShort
;
1114 $(IDS
.ATTRIBUTION_TEXT
).textContent
=
1115 configData
.translatedStrings
.attributionIntro
;
1117 var notificationCloseButton
= $(IDS
.NOTIFICATION_CLOSE_BUTTON
);
1118 createAndAppendElement(
1119 notificationCloseButton
, 'div', CLASSES
.BLACKLIST_BUTTON_INNER
);
1120 notificationCloseButton
.addEventListener('click', hideNotification
);
1122 window
.addEventListener('resize', onResize
);
1123 updateContentWidth();
1125 var topLevelHandle
= getEmbeddedSearchApiHandle();
1127 ntpApiHandle
= topLevelHandle
.newTabPage
;
1128 ntpApiHandle
.onthemechange
= onThemeChange
;
1129 ntpApiHandle
.onmostvisitedchange
= onMostVisitedChange
;
1131 ntpApiHandle
.oninputstart
= onInputStart
;
1132 ntpApiHandle
.oninputcancel
= restoreNtp
;
1134 if (ntpApiHandle
.isInputInProgress
)
1138 onMostVisitedChange();
1140 searchboxApiHandle
= topLevelHandle
.searchBox
;
1143 // Listener for updating the key capture state.
1144 document
.body
.onmousedown = function(event
) {
1145 if (isFakeboxClick(event
))
1146 searchboxApiHandle
.startCapturingKeyStrokes();
1147 else if (isFakeboxFocused())
1148 searchboxApiHandle
.stopCapturingKeyStrokes();
1150 searchboxApiHandle
.onkeycapturechange = function() {
1151 setFakeboxFocus(searchboxApiHandle
.isKeyCaptureEnabled
);
1153 var inputbox
= $(IDS
.FAKEBOX_INPUT
);
1155 inputbox
.onpaste = function(event
) {
1156 event
.preventDefault();
1157 // Send pasted text to Omnibox.
1158 var text
= event
.clipboardData
.getData('text/plain');
1160 searchboxApiHandle
.paste(text
);
1162 inputbox
.ondrop = function(event
) {
1163 event
.preventDefault();
1164 var text
= event
.dataTransfer
.getData('text/plain');
1166 searchboxApiHandle
.paste(text
);
1169 inputbox
.ondragenter = function() {
1170 setFakeboxDragFocus(true);
1172 inputbox
.ondragleave = function() {
1173 setFakeboxDragFocus(false);
1177 // Update the fakebox style to match the current key capturing state.
1178 setFakeboxFocus(searchboxApiHandle
.isKeyCaptureEnabled
);
1181 if (searchboxApiHandle
.rtl
) {
1182 $(IDS
.NOTIFICATION
).dir
= 'rtl';
1183 document
.body
.setAttribute('dir', 'rtl');
1184 // Add class for setting alignments based on language directionality.
1185 document
.body
.classList
.add(CLASSES
.RTL
);
1186 $(IDS
.TILES
).dir
= 'rtl';
1189 window
.addEventListener('message', handlePostMessage
);
1194 * Binds event listeners.
1197 document
.addEventListener('DOMContentLoaded', init
);
1206 if (!window
.localNTPUnitTest
) {
1207 LocalNTP().listen();