1 // Copyright 2015 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.
20 * Alias for document.getElementById.
21 * @param {string} id The ID of the element to find.
22 * @return {HTMLElement} The found element or null if not found.
25 return document.getElementById(id);
30 * Specifications for an NTP design (not comprehensive).
32 * fakeboxWingSize: Extra distance for fakebox to extend beyond beyond the list
34 * fontFamily: Font family to use for title and thumbnail iframes.
35 * fontSize: Font size to use for the iframes, in px.
36 * mainClass: Class applied to #ntp-contents to control CSS.
37 * numTitleLines: Number of lines to display in titles.
38 * showFavicon: Whether to show favicon.
39 * thumbnailTextColor: The 4-component color that thumbnail iframe may use to
40 * display text message in place of missing thumbnail.
41 * thumbnailFallback: (Optional) A value in THUMBNAIL_FALLBACK to specify the
42 * thumbnail fallback strategy. If unassigned, then the thumbnail.html
43 * iframe would handle the fallback.
44 * tileWidth: The width of each suggestion tile, in px.
45 * tileMargin: Spacing between successive tiles, in px.
46 * titleColor: The 4-component color of title text.
47 * titleColorAgainstDark: The 4-component color of title text against a dark
49 * titleTextAlign: (Optional) The alignment of title text. If unspecified, the
50 * default value is 'center'.
51 * titleTextFade: (Optional) The number of pixels beyond which title
52 * text begins to fade. This overrides the default ellipsis style.
55 * fakeboxWingSize: number,
59 * numTitleLines: number,
60 * showFavicon: boolean,
61 * thumbnailTextColor: string,
62 * thumbnailFallback: string|null|undefined,
66 * titleColorAgainstDark: string,
67 * titleTextAlign: string|null|undefined,
68 * titleTextFade: number|null|undefined
73 fontFamily: 'arial, sans-serif',
75 mainClass: 'thumb-ntp',
78 thumbnailTextColor: [50, 50, 50, 255],
79 thumbnailFallback: 'dot', // Draw single dot.
82 titleColor: [50, 50, 50, 255],
83 titleColorAgainstDark: [210, 210, 210, 255],
84 titleTextAlign: 'inherit',
85 titleTextFade: 122 - 36 // 112px wide title with 32 pixel fade at end.
90 * Modifies NTP_DESIGN parameters for icon NTP.
92 function modifyNtpDesignForIcons() {
93 NTP_DESIGN.fakeboxWingSize = 132;
94 NTP_DESIGN.mainClass = 'icon-ntp';
95 NTP_DESIGN.numTitleLines = 2;
96 NTP_DESIGN.showFavicon = false;
97 NTP_DESIGN.thumbnailFallback = null;
98 NTP_DESIGN.tileWidth = 48 + 2 * 18;
99 NTP_DESIGN.tileMargin = 60 - 18 * 2;
100 NTP_DESIGN.titleColor = [120, 120, 120, 255];
101 NTP_DESIGN.titleColorAgainstDark = [210, 210, 210, 255];
102 NTP_DESIGN.titleTextAlign = 'center';
103 delete NTP_DESIGN.titleTextFade;
108 * Enum for classnames.
113 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
115 DEFAULT_THEME: 'default-theme',
116 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
117 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
118 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
119 // Applies drag focus style to the fakebox
120 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
121 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
122 HIDE_NOTIFICATION: 'mv-notice-hide',
123 LEFT_ALIGN_ATTRIBUTION: 'left-align-attribution',
124 // Vertically centers the most visited section for a non-Google provided page.
125 NON_GOOGLE_PAGE: 'non-google-page',
126 RTL: 'rtl' // Right-to-left language text.
131 * Enum for HTML element ids.
136 ATTRIBUTION: 'attribution',
137 ATTRIBUTION_TEXT: 'attribution-text',
138 CUSTOM_THEME_STYLE: 'ct-style',
140 FAKEBOX_INPUT: 'fakebox-input',
141 FAKEBOX_TEXT: 'fakebox-text',
143 NOTIFICATION: 'mv-notice',
144 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
145 NOTIFICATION_MESSAGE: 'mv-msg',
146 NTP_CONTENTS: 'ntp-contents',
147 RESTORE_ALL_LINK: 'mv-restore',
164 * Enum for the state of the NTP when it is disposed.
168 var NTP_DISPOSE_STATE = {
169 NONE: 0, // Preserve the NTP appearance and functionality
171 HIDE_FAKEBOX_AND_LOGO: 2
176 * The notification displayed when a page is blacklisted.
183 * The container for the theme attribution.
190 * The "fakebox" - an input field that looks like a regular searchbox. When it
191 * is focused, any text the user types goes directly into the omnibox.
198 * The container for NTP elements.
205 * The last blacklisted tile rid if any, which by definition should not be
209 var lastBlacklistedTile = null;
213 * Current number of tiles columns shown based on the window width, including
214 * those that just contain filler.
217 var numColumnsShown = 0;
221 * The browser embeddedSearch.newTabPage object.
228 * The browser embeddedSearch.searchBox object.
231 var searchboxApiHandle;
235 * The state of the NTP when a query is entered into the Omnibox.
236 * @type {NTP_DISPOSE_STATE}
238 var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE;
242 * The state of the NTP when a query is entered into the Fakebox.
243 * @type {NTP_DISPOSE_STATE}
245 var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO;
248 /** @type {number} @const */
249 var MAX_NUM_TILES_TO_SHOW = 8;
252 /** @type {number} @const */
253 var MIN_NUM_COLUMNS = 2;
256 /** @type {number} @const */
257 var MAX_NUM_COLUMNS = 4;
260 /** @type {number} @const */
265 * Minimum total padding to give to the left and right of the most visited
266 * section. Used to determine how many tiles to show.
270 var MIN_TOTAL_HORIZONTAL_PADDING = 200;
274 * Heuristic to determine whether a theme should be considered to be dark, so
275 * the colors of various UI elements can be adjusted.
276 * @param {ThemeBackgroundInfo|undefined} info Theme background information.
277 * @return {boolean} Whether the theme is dark.
280 function getIsThemeDark(info) {
283 // Heuristic: light text implies dark theme.
284 var rgba = info.textColorRgba;
285 var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2];
286 return luminance >= 128;
291 * Updates the NTP based on the current theme.
294 function renderTheme() {
295 var fakeboxText = $(IDS.FAKEBOX_TEXT);
297 fakeboxText.innerHTML = '';
298 if (configData.translatedStrings.searchboxPlaceholder) {
299 fakeboxText.textContent =
300 configData.translatedStrings.searchboxPlaceholder;
304 var info = ntpApiHandle.themeBackgroundInfo;
305 var isThemeDark = getIsThemeDark(info);
306 ntpContents.classList.toggle(CLASSES.DARK, isThemeDark);
311 var background = [convertToRGBAColor(info.backgroundColorRgba),
314 info.imageHorizontalAlignment,
315 info.imageVerticalAlignment].join(' ').trim();
317 document.body.style.background = background;
318 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
319 updateThemeAttribution(info.attributionUrl, info.imageHorizontalAlignment);
320 setCustomThemeStyle(info);
322 var themeinfo = {cmd: 'updateTheme'};
323 if (!info.usingDefaultTheme) {
324 themeinfo.tileBorderColor = convertToRGBAColor(info.sectionBorderColorRgba);
325 themeinfo.tileHoverBorderColor = convertToRGBAColor(info.headerColorRgba);
327 themeinfo.isThemeDark = isThemeDark;
329 var titleColor = NTP_DESIGN.titleColor;
330 if (!info.usingDefaultTheme && info.textColorRgba) {
331 titleColor = info.textColorRgba;
332 } else if (isThemeDark) {
333 titleColor = NTP_DESIGN.titleColorAgainstDark;
335 themeinfo.tileTitleColor = convertToRGBAColor(titleColor);
337 $('mv-single').contentWindow.postMessage(themeinfo, '*');
342 * Updates the NTP based on the current theme, then rerenders all tiles.
345 function onThemeChange() {
351 * Updates the NTP style according to theme.
352 * @param {Object=} opt_themeInfo The information about the theme. If it is
353 * omitted the style will be reverted to the default.
356 function setCustomThemeStyle(opt_themeInfo) {
357 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE);
358 var head = document.head;
359 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) {
360 ntpContents.classList.remove(CLASSES.DEFAULT_THEME);
363 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
366 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' +
368 '#mv-notice-links span {' +
369 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' +
372 ' -webkit-filter: drop-shadow(0 0 0 ' +
373 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' +
375 '.mv-page-ready .mv-mask {' +
376 ' border: 1px solid ' +
377 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' +
379 '.mv-page-ready:hover .mv-mask, .mv-page-ready .mv-focused ~ .mv-mask {' +
381 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' +
384 if (customStyleElement) {
385 customStyleElement.textContent = themeStyle;
387 customStyleElement = document.createElement('style');
388 customStyleElement.type = 'text/css';
389 customStyleElement.id = IDS.CUSTOM_THEME_STYLE;
390 customStyleElement.textContent = themeStyle;
391 head.appendChild(customStyleElement);
395 ntpContents.classList.add(CLASSES.DEFAULT_THEME);
396 if (customStyleElement)
397 head.removeChild(customStyleElement);
403 * Renders the attribution if the URL is present, otherwise hides it.
404 * @param {string} url The URL of the attribution image, if any.
405 * @param {string} themeBackgroundAlignment The alignment of the theme
406 * background image. This is used to compute the attribution's alignment.
409 function updateThemeAttribution(url, themeBackgroundAlignment) {
411 setAttributionVisibility_(false);
415 var attributionImage = attribution.querySelector('img');
416 if (!attributionImage) {
417 attributionImage = new Image();
418 attribution.appendChild(attributionImage);
420 attributionImage.style.content = url;
422 // To avoid conflicts, place the attribution on the left for themes that
423 // right align their background images.
424 attribution.classList.toggle(CLASSES.LEFT_ALIGN_ATTRIBUTION,
425 themeBackgroundAlignment == 'right');
426 setAttributionVisibility_(true);
431 * Sets the visibility of the theme attribution.
432 * @param {boolean} show True to show the attribution.
435 function setAttributionVisibility_(show) {
437 attribution.style.display = show ? '' : 'none';
443 * Converts an Array of color components into RRGGBBAA format.
444 * @param {Array<number>} color Array of rgba color components.
445 * @return {string} Color string in RRGGBBAA format.
448 function convertToRRGGBBAAColor(color) {
449 return color.map(function(t) {
450 return ('0' + t.toString(16)).slice(-2); // To 2-digit, 0-padded hex.
456 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
457 * @param {Array<number>} color Array of rgba color components.
458 * @return {string} CSS color in RGBA format.
461 function convertToRGBAColor(color) {
462 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
463 color[3] / 255 + ')';
468 * Called when page data change.
470 function onMostVisitedChange() {
476 * Fetches new data, creates, and renders tiles.
478 function reloadTiles() {
479 var pages = ntpApiHandle.mostVisited;
481 for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) {
482 cmds.push({cmd: 'tile', rid: pages[i].rid});
484 cmds.push({cmd: 'show', maxVisible: numColumnsShown * NUM_ROWS});
486 $('mv-single').contentWindow.postMessage(cmds, '*');
491 * Shows the blacklist notification and triggers a delay to hide it.
493 function showNotification() {
494 notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
495 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
496 notification.scrollTop;
497 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
502 * Hides the blacklist notification.
504 function hideNotification() {
505 notification.classList.add(CLASSES.HIDE_NOTIFICATION);
506 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
511 * Handles a click on the notification undo link by hiding the notification and
516 if (lastBlacklistedTile != null) {
517 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile);
523 * Handles a click on the restore all notification link by hiding the
524 * notification and informing Chrome.
526 function onRestoreAll() {
528 ntpApiHandle.undoAllMostVisitedDeletions();
533 * Recomputes the number of tile columns, and width of various contents based
534 * on the width of the window.
535 * @return {boolean} Whether the number of tile columns has changed.
537 function updateContentWidth() {
538 var tileRequiredWidth = NTP_DESIGN.tileWidth + NTP_DESIGN.tileMargin;
539 // If innerWidth is zero, then use the maximum snap size.
540 var maxSnapSize = MAX_NUM_COLUMNS * tileRequiredWidth -
541 NTP_DESIGN.tileMargin + MIN_TOTAL_HORIZONTAL_PADDING;
542 var innerWidth = window.innerWidth || maxSnapSize;
543 // Each tile has left and right margins that sum to NTP_DESIGN.tileMargin.
544 var availableWidth = innerWidth + NTP_DESIGN.tileMargin -
545 NTP_DESIGN.fakeboxWingSize * 2 - MIN_TOTAL_HORIZONTAL_PADDING;
546 var newNumColumns = Math.floor(availableWidth / tileRequiredWidth);
547 if (newNumColumns < MIN_NUM_COLUMNS)
548 newNumColumns = MIN_NUM_COLUMNS;
549 else if (newNumColumns > MAX_NUM_COLUMNS)
550 newNumColumns = MAX_NUM_COLUMNS;
552 if (numColumnsShown === newNumColumns)
555 numColumnsShown = newNumColumns;
556 // We add an extra pixel because rounding errors on different zooms can
557 // make the width shorter than it should be.
558 var tilesContainerWidth = Math.ceil(numColumnsShown * tileRequiredWidth) + 1;
559 $(IDS.TILES).style.width = tilesContainerWidth + 'px';
561 // -2 to account for border.
562 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2);
563 fakeboxWidth += NTP_DESIGN.fakeboxWingSize * 2;
564 fakebox.style.width = fakeboxWidth + 'px';
571 * Resizes elements because the number of tile columns may need to change in
572 * response to resizing. Also shows or hides extra tiles tiles according to the
573 * new width of the page.
575 function onResize() {
576 updateContentWidth();
577 $('mv-single').contentWindow.postMessage(
578 {cmd: 'tilesVisible', maxVisible: numColumnsShown * NUM_ROWS}, '*');
583 * Handles new input by disposing the NTP, according to where the input was
586 function onInputStart() {
587 if (fakebox && isFakeboxFocused()) {
588 setFakeboxFocus(false);
589 setFakeboxDragFocus(false);
591 } else if (!isFakeboxFocused()) {
598 * Disposes the NTP, according to where the input was entered.
599 * @param {boolean} wasFakeboxInput True if the input was in the fakebox.
601 function disposeNtp(wasFakeboxInput) {
602 var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior;
603 if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX)
604 setFakeboxActive(false);
605 else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO)
606 setFakeboxAndLogoVisibility(false);
611 * Restores the NTP (re-enables the fakebox and unhides the logo.)
613 function restoreNtp() {
614 setFakeboxActive(true);
615 setFakeboxAndLogoVisibility(true);
620 * @param {boolean} focus True to focus the fakebox.
622 function setFakeboxFocus(focus) {
623 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
627 * @param {boolean} focus True to show a dragging focus to the fakebox.
629 function setFakeboxDragFocus(focus) {
630 document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
634 * @return {boolean} True if the fakebox has focus.
636 function isFakeboxFocused() {
637 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
638 document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
643 * @param {boolean} enable True to enable the fakebox.
645 function setFakeboxActive(enable) {
646 document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable);
651 * @param {!Event} event The click event.
652 * @return {boolean} True if the click occurred in an enabled fakebox.
654 function isFakeboxClick(event) {
655 return fakebox.contains(event.target) &&
656 !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE);
661 * @param {boolean} show True to show the fakebox and logo.
663 function setFakeboxAndLogoVisibility(show) {
664 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show);
669 * Shortcut for document.getElementById.
670 * @param {string} id of the element.
671 * @return {HTMLElement} with the id.
674 return document.getElementById(id);
679 * Utility function which creates an element with an optional classname and
680 * appends it to the specified parent.
681 * @param {Element} parent The parent to append the new element.
682 * @param {string} name The name of the new element.
683 * @param {string=} opt_class The optional classname of the new element.
684 * @return {Element} The new element.
686 function createAndAppendElement(parent, name, opt_class) {
687 var child = document.createElement(name);
689 child.classList.add(opt_class);
690 parent.appendChild(child);
696 * @param {!Element} element The element to register the handler for.
697 * @param {number} keycode The keycode of the key to register.
698 * @param {!Function} handler The key handler to register.
700 function registerKeyHandler(element, keycode, handler) {
701 element.addEventListener('keydown', function(event) {
702 if (event.keyCode == keycode)
709 * @return {Object} the handle to the embeddedSearch API.
711 function getEmbeddedSearchApiHandle() {
714 if (window.chrome && window.chrome.embeddedSearch)
715 return window.chrome.embeddedSearch;
721 * Event handler for the focus changed and blacklist messages on link elements.
722 * Used to toggle visual treatment on the tiles (depending on the message).
723 * @param {Event} event Event received.
725 function handlePostMessage(event) {
726 var cmd = event.data.cmd;
727 var args = event.data;
728 if (cmd == 'tileBlacklisted') {
730 lastBlacklistedTile = args.tid;
732 ntpApiHandle.deleteMostVisitedItem(args.tid);
738 * Prepares the New Tab Page by adding listeners, rendering the current
739 * theme, the most visited pages section, and Google-specific elements for a
740 * Google-provided page.
743 notification = $(IDS.NOTIFICATION);
744 attribution = $(IDS.ATTRIBUTION);
745 ntpContents = $(IDS.NTP_CONTENTS);
747 if (configData.isGooglePage) {
748 var logo = document.createElement('div');
750 logo.title = 'Google';
752 fakebox = document.createElement('div');
753 fakebox.id = IDS.FAKEBOX;
754 var fakeboxHtml = [];
755 fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '"></div>');
756 fakeboxHtml.push('<input id="' + IDS.FAKEBOX_INPUT +
757 '" autocomplete="off" tabindex="-1" type="url" aria-hidden="true">');
758 fakeboxHtml.push('<div id="cursor"></div>');
759 fakebox.innerHTML = fakeboxHtml.join('');
761 ntpContents.insertBefore(fakebox, ntpContents.firstChild);
762 ntpContents.insertBefore(logo, ntpContents.firstChild);
764 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE);
767 // Modify design for experimental icon NTP, if specified.
768 if (configData.useIcons)
769 modifyNtpDesignForIcons();
770 document.querySelector('#ntp-contents').classList.add(NTP_DESIGN.mainClass);
772 // Hide notifications after fade out, so we can't focus on links via keyboard.
773 notification.addEventListener('webkitTransitionEnd', hideNotification);
775 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
776 notificationMessage.textContent =
777 configData.translatedStrings.thumbnailRemovedNotification;
779 var undoLink = $(IDS.UNDO_LINK);
780 undoLink.addEventListener('click', onUndo);
781 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
782 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
784 var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
785 restoreAllLink.addEventListener('click', onRestoreAll);
786 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo);
787 restoreAllLink.textContent =
788 configData.translatedStrings.restoreThumbnailsShort;
790 $(IDS.ATTRIBUTION_TEXT).textContent =
791 configData.translatedStrings.attributionIntro;
793 var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
794 createAndAppendElement(
795 notificationCloseButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER);
796 notificationCloseButton.addEventListener('click', hideNotification);
798 window.addEventListener('resize', onResize);
799 updateContentWidth();
801 var topLevelHandle = getEmbeddedSearchApiHandle();
803 ntpApiHandle = topLevelHandle.newTabPage;
804 ntpApiHandle.onthemechange = onThemeChange;
805 ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
807 ntpApiHandle.oninputstart = onInputStart;
808 ntpApiHandle.oninputcancel = restoreNtp;
810 if (ntpApiHandle.isInputInProgress)
813 searchboxApiHandle = topLevelHandle.searchBox;
816 // Listener for updating the key capture state.
817 document.body.onmousedown = function(event) {
818 if (isFakeboxClick(event))
819 searchboxApiHandle.startCapturingKeyStrokes();
820 else if (isFakeboxFocused())
821 searchboxApiHandle.stopCapturingKeyStrokes();
823 searchboxApiHandle.onkeycapturechange = function() {
824 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
826 var inputbox = $(IDS.FAKEBOX_INPUT);
828 inputbox.onpaste = function(event) {
829 event.preventDefault();
830 // Send pasted text to Omnibox.
831 var text = event.clipboardData.getData('text/plain');
833 searchboxApiHandle.paste(text);
835 inputbox.ondrop = function(event) {
836 event.preventDefault();
837 var text = event.dataTransfer.getData('text/plain');
839 searchboxApiHandle.paste(text);
841 setFakeboxDragFocus(false);
843 inputbox.ondragenter = function() {
844 setFakeboxDragFocus(true);
846 inputbox.ondragleave = function() {
847 setFakeboxDragFocus(false);
851 // Update the fakebox style to match the current key capturing state.
852 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
855 if (searchboxApiHandle.rtl) {
856 $(IDS.NOTIFICATION).dir = 'rtl';
857 // Grabbing the root HTML element.
858 document.documentElement.setAttribute('dir', 'rtl');
859 // Add class for setting alignments based on language directionality.
860 document.documentElement.classList.add(CLASSES.RTL);
863 var iframe = document.createElement('iframe');
864 // Change the order of tabbing the page to start with NTP tiles.
865 iframe.setAttribute('tabindex', '1');
866 iframe.id = 'mv-single';
870 if (searchboxApiHandle.rtl)
872 if (window.configData.useIcons)
873 args.push('icons=1');
874 if (NTP_DESIGN.numTitleLines > 1)
875 args.push('ntl=' + NTP_DESIGN.numTitleLines);
877 args.push('removeTooltip=' +
878 encodeURIComponent(configData.translatedStrings.removeThumbnailTooltip));
880 iframe.src = '//most-visited/single.html?' + args.join('&');
881 $(IDS.TILES).appendChild(iframe);
883 iframe.onload = function() {
888 window.addEventListener('message', handlePostMessage);
893 * Binds event listeners.
896 document.addEventListener('DOMContentLoaded', init);
905 if (!window.localNTPUnitTest) {