local_ntp.js revision 9ab5563a3196760eb381d102cbb2bc0f7abc6a50
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. 4 5 6/** 7 * @fileoverview The local InstantExtended NTP. 8 */ 9 10/** 11 * Controls rendering the new tab page for InstantExtended. 12 * @return {Object} A limited interface for testing the local NTP. 13 */ 14function LocalNTP() { 15<include src="../../../../ui/webui/resources/js/assert.js"> 16 17 18 19/** 20 * Enum for classnames. 21 * @enum {string} 22 * @const 23 */ 24var CLASSES = { 25 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme 26 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation 27 BLACKLIST_BUTTON: 'mv-x', 28 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', 29 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive 30 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox 31 FAVICON: 'mv-favicon', 32 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation 33 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', 34 HIDE_NOTIFICATION: 'mv-notice-hide', 35 // Vertically centers the most visited section for a non-Google provided page. 36 NON_GOOGLE_PAGE: 'non-google-page', 37 PAGE: 'mv-page', // page tiles 38 PAGE_READY: 'mv-page-ready', // page tile when ready 39 ROW: 'mv-row', // tile row 40 RTL: 'rtl', // Right-to-left language text. 41 THUMBNAIL: 'mv-thumb', 42 THUMBNAIL_MASK: 'mv-mask', 43 TILE: 'mv-tile', 44 TITLE: 'mv-title' 45}; 46 47 48/** 49 * Enum for HTML element ids. 50 * @enum {string} 51 * @const 52 */ 53var IDS = { 54 ATTRIBUTION: 'attribution', 55 ATTRIBUTION_TEXT: 'attribution-text', 56 CUSTOM_THEME_STYLE: 'ct-style', 57 FAKEBOX: 'fakebox', 58 LOGO: 'logo', 59 NOTIFICATION: 'mv-notice', 60 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', 61 NOTIFICATION_MESSAGE: 'mv-msg', 62 NTP_CONTENTS: 'ntp-contents', 63 RECENT_TABS: 'recent-tabs', 64 RESTORE_ALL_LINK: 'mv-restore', 65 TILES: 'mv-tiles', 66 UNDO_LINK: 'mv-undo' 67}; 68 69 70/** 71 * Enum for keycodes. 72 * @enum {number} 73 * @const 74 */ 75var KEYCODE = { 76 DELETE: 46, 77 ENTER: 13 78}; 79 80 81/** 82 * Enum for the state of the NTP when it is disposed. 83 * @enum {number} 84 * @const 85 */ 86var NTP_DISPOSE_STATE = { 87 NONE: 0, // Preserve the NTP appearance and functionality 88 DISABLE_FAKEBOX: 1, 89 HIDE_FAKEBOX_AND_LOGO: 2 90}; 91 92 93/** 94 * The JavaScript button event value for a middle click. 95 * @type {number} 96 * @const 97 */ 98var MIDDLE_MOUSE_BUTTON = 1; 99 100 101/** 102 * Possible behaviors for navigateContentWindow. 103 * @enum {number} 104 */ 105var WindowOpenDisposition = { 106 CURRENT_TAB: 1, 107 NEW_BACKGROUND_TAB: 2 108}; 109 110 111/** 112 * The container for the tile elements. 113 * @type {Element} 114 */ 115var tilesContainer; 116 117 118/** 119 * The notification displayed when a page is blacklisted. 120 * @type {Element} 121 */ 122var notification; 123 124 125/** 126 * The container for the theme attribution. 127 * @type {Element} 128 */ 129var attribution; 130 131 132/** 133 * The "fakebox" - an input field that looks like a regular searchbox. When it 134 * is focused, any text the user types goes directly into the omnibox. 135 * @type {Element} 136 */ 137var fakebox; 138 139 140/** 141 * The container for NTP elements. 142 * @type {Element} 143 */ 144var ntpContents; 145 146 147/** 148 * The array of rendered tiles, ordered by appearance. 149 * @type {!Array.<Tile>} 150 */ 151var tiles = []; 152 153 154/** 155 * The last blacklisted tile if any, which by definition should not be filler. 156 * @type {?Tile} 157 */ 158var lastBlacklistedTile = null; 159 160 161/** 162 * True if a page has been blacklisted and we're waiting on the 163 * onmostvisitedchange callback. See onMostVisitedChange() for how this 164 * is used. 165 * @type {boolean} 166 */ 167var isBlacklisting = false; 168 169 170/** 171 * Current number of tiles columns shown based on the window width, including 172 * those that just contain filler. 173 * @type {number} 174 */ 175var numColumnsShown = 0; 176 177 178/** 179 * True if the user initiated the current most visited change and false 180 * otherwise. 181 * @type {boolean} 182 */ 183var userInitiatedMostVisitedChange = false; 184 185 186/** 187 * The browser embeddedSearch.newTabPage object. 188 * @type {Object} 189 */ 190var ntpApiHandle; 191 192 193/** 194 * The browser embeddedSearch.searchBox object. 195 * @type {Object} 196 */ 197var searchboxApiHandle; 198 199 200/** 201 * The state of the NTP when a query is entered into the Omnibox. 202 * @type {NTP_DISPOSE_STATE} 203 */ 204var omniboxInputBehavior = NTP_DISPOSE_STATE.NONE; 205 206 207/** 208 * The state of the NTP when a query is entered into the Fakebox. 209 * @type {NTP_DISPOSE_STATE} 210 */ 211var fakeboxInputBehavior = NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO; 212 213 214/** 215 * Total tile width. Should be equal to mv-tile's width + 2 * border-width. 216 * @private {number} 217 * @const 218 */ 219var TILE_WIDTH = 140; 220 221 222/** 223 * Margin between tiles. Should be equal to mv-tile's -webkit-margin-start. 224 * @private {number} 225 * @const 226 */ 227var TILE_MARGIN_START = 20; 228 229 230/** @type {number} @const */ 231var MAX_NUM_TILES_TO_SHOW = 8; 232 233 234/** @type {number} @const */ 235var MIN_NUM_COLUMNS = 2; 236 237 238/** @type {number} @const */ 239var MAX_NUM_COLUMNS = 4; 240 241 242/** @type {number} @const */ 243var NUM_ROWS = 2; 244 245 246/** 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. 249 * @type {number} 250 * @const 251 */ 252var MIN_TOTAL_HORIZONTAL_PADDING = 200; 253 254 255/** 256 * The filename for a most visited iframe src which shows a page title. 257 * @type {string} 258 * @const 259 */ 260var MOST_VISITED_TITLE_IFRAME = 'title.html'; 261 262 263/** 264 * The filename for a most visited iframe src which shows a thumbnail image. 265 * @type {string} 266 * @const 267 */ 268var MOST_VISITED_THUMBNAIL_IFRAME = 'thumbnail.html'; 269 270 271/** 272 * The hex color for most visited tile elements. 273 * @type {string} 274 * @const 275 */ 276var MOST_VISITED_COLOR = '777777'; 277 278 279/** 280 * The font family for most visited tile elements. 281 * @type {string} 282 * @const 283 */ 284var MOST_VISITED_FONT_FAMILY = 'arial, sans-serif'; 285 286 287/** 288 * The font size for most visited tile elements. 289 * @type {number} 290 * @const 291 */ 292var MOST_VISITED_FONT_SIZE = 11; 293 294 295/** 296 * Hide most visited tiles for at most this many milliseconds while painting. 297 * @type {number} 298 * @const 299 */ 300var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500; 301 302 303/** 304 * A Tile is either a rendering of a Most Visited page or "filler" used to 305 * pad out the section when not enough pages exist. 306 * 307 * @param {Element} elem The element for rendering the tile. 308 * @param {number=} opt_rid The RID for the corresponding Most Visited page. 309 * Should only be left unspecified when creating a filler tile. 310 * @constructor 311 */ 312function Tile(elem, opt_rid) { 313 /** @type {Element} */ 314 this.elem = elem; 315 316 /** @type {number|undefined} */ 317 this.rid = opt_rid; 318} 319 320 321/** 322 * Updates the NTP based on the current theme. 323 * @private 324 */ 325function onThemeChange() { 326 var info = ntpApiHandle.themeBackgroundInfo; 327 if (!info) 328 return; 329 330 var background = [convertToRGBAColor(info.backgroundColorRgba), 331 info.imageUrl, 332 info.imageTiling, 333 info.imageHorizontalAlignment, 334 info.imageVerticalAlignment].join(' ').trim(); 335 document.body.style.background = background; 336 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo); 337 updateThemeAttribution(info.attributionUrl); 338 setCustomThemeStyle(info); 339 renderTiles(); 340} 341 342 343/** 344 * Updates the NTP style according to theme. 345 * @param {Object=} opt_themeInfo The information about the theme. If it is 346 * omitted the style will be reverted to the default. 347 * @private 348 */ 349function setCustomThemeStyle(opt_themeInfo) { 350 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE); 351 var head = document.head; 352 353 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) { 354 var themeStyle = 355 '#attribution {' + 356 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + 357 '}' + 358 '#mv-msg {' + 359 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' + 360 '}' + 361 '#mv-notice-links span {' + 362 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + 363 '}' + 364 '#mv-notice-x {' + 365 ' -webkit-filter: drop-shadow(0 0 0 ' + 366 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' + 367 '}' + 368 '.mv-page-ready {' + 369 ' border: 1px solid ' + 370 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' + 371 '}' + 372 '.mv-page-ready:hover, .mv-page-ready:focus {' + 373 ' border-color: ' + 374 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' + 375 '}'; 376 377 if (customStyleElement) { 378 customStyleElement.textContent = themeStyle; 379 } else { 380 customStyleElement = document.createElement('style'); 381 customStyleElement.type = 'text/css'; 382 customStyleElement.id = IDS.CUSTOM_THEME_STYLE; 383 customStyleElement.textContent = themeStyle; 384 head.appendChild(customStyleElement); 385 } 386 387 } else if (customStyleElement) { 388 head.removeChild(customStyleElement); 389 } 390} 391 392 393/** 394 * Renders the attribution if the image is present and loadable. Otherwise 395 * hides it. 396 * @param {string} url The URL of the attribution image, if any. 397 * @private 398 */ 399function updateThemeAttribution(url) { 400 if (!url) { 401 setAttributionVisibility_(false); 402 return; 403 } 404 var attributionImage = new Image(); 405 attributionImage.onload = function() { 406 var oldAttributionImage = attribution.querySelector('img'); 407 if (oldAttributionImage) 408 removeNode(oldAttributionImage); 409 attribution.appendChild(attributionImage); 410 setAttributionVisibility_(true); 411 }; 412 attributionImage.onerror = function() { 413 setAttributionVisibility_(false); 414 }; 415 attributionImage.src = url; 416} 417 418 419/** 420 * Sets the visibility of the theme attribution. 421 * @param {boolean} show True to show the attribution. 422 * @private 423 */ 424function setAttributionVisibility_(show) { 425 if (attribution) { 426 attribution.style.display = show ? '' : 'none'; 427 } 428} 429 430 431 /** 432 * Converts an Array of color components into RGBA format "rgba(R,G,B,A)". 433 * @param {Array.<number>} color Array of rgba color components. 434 * @return {string} CSS color in RGBA format. 435 * @private 436 */ 437function convertToRGBAColor(color) { 438 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + 439 color[3] / 255 + ')'; 440} 441 442 443/** 444 * Handles a new set of Most Visited page data. 445 */ 446function onMostVisitedChange() { 447 var pages = ntpApiHandle.mostVisited; 448 449 if (isBlacklisting) { 450 // Trigger the blacklist animation and re-render the tiles when it 451 // completes. 452 var lastBlacklistedTileElement = lastBlacklistedTile.elem; 453 lastBlacklistedTileElement.addEventListener( 454 'webkitTransitionEnd', blacklistAnimationDone); 455 lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST); 456 457 } else { 458 // Otherwise render the tiles using the new data without animation. 459 tiles = []; 460 for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i) { 461 tiles.push(createTile(pages[i], i)); 462 } 463 if (!userInitiatedMostVisitedChange) { 464 tilesContainer.hidden = true; 465 window.setTimeout(function() { 466 if (tilesContainer) { 467 tilesContainer.hidden = false; 468 } 469 }, MOST_VISITED_PAINT_TIMEOUT_MSEC); 470 } 471 renderTiles(); 472 } 473} 474 475 476/** 477 * Renders the current set of tiles. 478 */ 479function renderTiles() { 480 var rows = tilesContainer.children; 481 for (var i = 0; i < rows.length; ++i) { 482 removeChildren(rows[i]); 483 } 484 485 for (var i = 0, length = tiles.length; 486 i < Math.min(length, numColumnsShown * NUM_ROWS); ++i) { 487 rows[Math.floor(i / numColumnsShown)].appendChild(tiles[i].elem); 488 } 489} 490 491 492/** 493 * Shows most visited tiles if all child iframes are loaded, and hides them 494 * otherwise. 495 */ 496function updateMostVisitedVisibility() { 497 var iframes = tilesContainer.querySelectorAll('iframe'); 498 var ready = true; 499 for (var i = 0, numIframes = iframes.length; i < numIframes; i++) { 500 if (iframes[i].hidden) { 501 ready = false; 502 break; 503 } 504 } 505 if (ready) { 506 tilesContainer.hidden = false; 507 userInitiatedMostVisitedChange = false; 508 } 509} 510 511 512/** 513 * Builds a URL to display a most visited tile component in an iframe. 514 * @param {string} filename The desired most visited component filename. 515 * @param {number} rid The restricted ID. 516 * @param {string} color The text color for text in the iframe. 517 * @param {string} fontFamily The font family for text in the iframe. 518 * @param {number} fontSize The font size for text in the iframe. 519 * @param {number} position The position of the iframe in the UI. 520 * @return {string} An URL to display the most visited component in an iframe. 521 */ 522function getMostVisitedIframeUrl(filename, rid, color, fontFamily, fontSize, 523 position) { 524 return 'chrome-search://most-visited/' + encodeURIComponent(filename) + '?' + 525 ['rid=' + encodeURIComponent(rid), 526 'c=' + encodeURIComponent(color), 527 'f=' + encodeURIComponent(fontFamily), 528 'fs=' + encodeURIComponent(fontSize), 529 'pos=' + encodeURIComponent(position)].join('&'); 530} 531 532 533/** 534 * Creates a Tile with the specified page data. If no data is provided, a 535 * filler Tile is created. 536 * @param {Object} page The page data. 537 * @param {number} position The position of the tile. 538 * @return {Tile} The new Tile. 539 */ 540function createTile(page, position) { 541 var tileElement = document.createElement('div'); 542 tileElement.classList.add(CLASSES.TILE); 543 544 if (page) { 545 var rid = page.rid; 546 tileElement.classList.add(CLASSES.PAGE); 547 548 var navigateFunction = function() { 549 ntpApiHandle.navigateContentWindow(rid); 550 }; 551 552 // The click handler for navigating to the page identified by the RID. 553 tileElement.addEventListener('click', navigateFunction); 554 555 // Make thumbnails tab-accessible. 556 tileElement.setAttribute('tabindex', '1'); 557 registerKeyHandler(tileElement, KEYCODE.ENTER, navigateFunction); 558 559 // The iframe which renders the page title. 560 var titleElement = document.createElement('iframe'); 561 titleElement.tabIndex = '-1'; 562 563 // Why iframes have IDs: 564 // 565 // On navigating back to the NTP we see several onmostvisitedchange() events 566 // in series with incrementing RIDs. After the first event, a set of iframes 567 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get 568 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1. 569 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the 570 // first set of iframes into the most recent set of iframes. 571 // 572 // Giving iframes distinct ids seems to cause some invalidation and prevent 573 // associating the incorrect data. 574 // 575 // TODO(jered): Find and fix the root (probably Blink) bug. 576 577 titleElement.src = getMostVisitedIframeUrl( 578 MOST_VISITED_TITLE_IFRAME, rid, MOST_VISITED_COLOR, 579 MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position); 580 581 // Keep this id here. See comment above. 582 titleElement.id = 'title-' + rid; 583 titleElement.hidden = true; 584 titleElement.onload = function() { 585 titleElement.hidden = false; 586 updateMostVisitedVisibility(); 587 }; 588 titleElement.className = CLASSES.TITLE; 589 tileElement.appendChild(titleElement); 590 591 // The iframe which renders either a thumbnail or domain element. 592 var thumbnailElement = document.createElement('iframe'); 593 thumbnailElement.tabIndex = '-1'; 594 thumbnailElement.src = getMostVisitedIframeUrl( 595 MOST_VISITED_THUMBNAIL_IFRAME, rid, MOST_VISITED_COLOR, 596 MOST_VISITED_FONT_FAMILY, MOST_VISITED_FONT_SIZE, position); 597 598 // Keep this id here. See comment above. 599 thumbnailElement.id = 'thumb-' + rid; 600 thumbnailElement.hidden = true; 601 thumbnailElement.onload = function() { 602 thumbnailElement.hidden = false; 603 tileElement.classList.add(CLASSES.PAGE_READY); 604 updateMostVisitedVisibility(); 605 }; 606 thumbnailElement.className = CLASSES.THUMBNAIL; 607 tileElement.appendChild(thumbnailElement); 608 609 // A mask to darken the thumbnail on focus. 610 var maskElement = createAndAppendElement( 611 tileElement, 'div', CLASSES.THUMBNAIL_MASK); 612 613 // The button used to blacklist this page. 614 var blacklistButton = createAndAppendElement( 615 tileElement, 'div', CLASSES.BLACKLIST_BUTTON); 616 var blacklistFunction = generateBlacklistFunction(rid); 617 blacklistButton.addEventListener('click', blacklistFunction); 618 blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip; 619 620 // When a tile is focused, have delete also blacklist the page. 621 registerKeyHandler(tileElement, KEYCODE.DELETE, blacklistFunction); 622 623 // The page favicon, if any. 624 var faviconUrl = page.faviconUrl; 625 if (faviconUrl) { 626 var favicon = createAndAppendElement( 627 tileElement, 'div', CLASSES.FAVICON); 628 favicon.style.backgroundImage = 'url(' + faviconUrl + ')'; 629 } 630 return new Tile(tileElement, rid); 631 } else { 632 return new Tile(tileElement); 633 } 634} 635 636 637/** 638 * Generates a function to be called when the page with the corresponding RID 639 * is blacklisted. 640 * @param {number} rid The RID of the page being blacklisted. 641 * @return {function(Event)} A function which handles the blacklisting of the 642 * page by updating state variables and notifying Chrome. 643 */ 644function generateBlacklistFunction(rid) { 645 return function(e) { 646 // Prevent navigation when the page is being blacklisted. 647 e.stopPropagation(); 648 649 userInitiatedMostVisitedChange = true; 650 isBlacklisting = true; 651 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON); 652 lastBlacklistedTile = getTileByRid(rid); 653 ntpApiHandle.deleteMostVisitedItem(rid); 654 }; 655} 656 657 658/** 659 * Shows the blacklist notification and triggers a delay to hide it. 660 */ 661function showNotification() { 662 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); 663 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); 664 notification.scrollTop; 665 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); 666} 667 668 669/** 670 * Hides the blacklist notification. 671 */ 672function hideNotification() { 673 notification.classList.add(CLASSES.HIDE_NOTIFICATION); 674} 675 676 677/** 678 * Handles the end of the blacklist animation by showing the notification and 679 * re-rendering the new set of tiles. 680 */ 681function blacklistAnimationDone() { 682 showNotification(); 683 isBlacklisting = false; 684 tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON); 685 lastBlacklistedTile.elem.removeEventListener( 686 'webkitTransitionEnd', blacklistAnimationDone); 687 // Need to call explicitly to re-render the tiles, since the initial 688 // onmostvisitedchange issued by the blacklist function only triggered 689 // the animation. 690 onMostVisitedChange(); 691} 692 693 694/** 695 * Handles a click on the notification undo link by hiding the notification and 696 * informing Chrome. 697 */ 698function onUndo() { 699 userInitiatedMostVisitedChange = true; 700 hideNotification(); 701 var lastBlacklistedRID = lastBlacklistedTile.rid; 702 if (typeof lastBlacklistedRID != 'undefined') 703 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID); 704} 705 706 707/** 708 * Handles a click on the restore all notification link by hiding the 709 * notification and informing Chrome. 710 */ 711function onRestoreAll() { 712 userInitiatedMostVisitedChange = true; 713 hideNotification(); 714 ntpApiHandle.undoAllMostVisitedDeletions(); 715} 716 717 718/** 719 * Re-renders the tiles if the number of columns has changed. As a temporary 720 * fix for crbug/240510, updates the width of the fakebox and most visited tiles 721 * container. 722 */ 723function onResize() { 724 // If innerWidth is zero, then use the maximum snap size. 725 var innerWidth = window.innerWidth || 820; 726 727 // These values should remain in sync with local_ntp.css. 728 // TODO(jeremycho): Delete once the root cause of crbug/240510 is resolved. 729 var setWidths = function(tilesContainerWidth) { 730 tilesContainer.style.width = tilesContainerWidth + 'px'; 731 if (fakebox) 732 fakebox.style.width = (tilesContainerWidth - 2) + 'px'; 733 }; 734 if (innerWidth >= 820) 735 setWidths(620); 736 else if (innerWidth >= 660) 737 setWidths(460); 738 else 739 setWidths(300); 740 741 var tileRequiredWidth = TILE_WIDTH + TILE_MARGIN_START; 742 // Adds margin-start to the available width to compensate the extra margin 743 // counted above for the first tile (which does not have a margin-start). 744 var availableWidth = innerWidth + TILE_MARGIN_START - 745 MIN_TOTAL_HORIZONTAL_PADDING; 746 var numColumnsToShow = Math.floor(availableWidth / tileRequiredWidth); 747 numColumnsToShow = Math.max(MIN_NUM_COLUMNS, 748 Math.min(MAX_NUM_COLUMNS, numColumnsToShow)); 749 if (numColumnsToShow != numColumnsShown) { 750 numColumnsShown = numColumnsToShow; 751 renderTiles(); 752 } 753} 754 755 756/** 757 * Returns the tile corresponding to the specified page RID. 758 * @param {number} rid The page RID being looked up. 759 * @return {Tile} The corresponding tile. 760 */ 761function getTileByRid(rid) { 762 for (var i = 0, length = tiles.length; i < length; ++i) { 763 var tile = tiles[i]; 764 if (tile.rid == rid) 765 return tile; 766 } 767 return null; 768} 769 770 771/** 772 * Handles new input by disposing the NTP, according to where the input was 773 * entered. 774 */ 775function onInputStart() { 776 if (fakebox && isFakeboxFocused()) { 777 setFakeboxFocus(false); 778 disposeNtp(true); 779 } else if (!isFakeboxFocused()) { 780 disposeNtp(false); 781 } 782} 783 784 785/** 786 * Disposes the NTP, according to where the input was entered. 787 * @param {boolean} wasFakeboxInput True if the input was in the fakebox. 788 */ 789function disposeNtp(wasFakeboxInput) { 790 var behavior = wasFakeboxInput ? fakeboxInputBehavior : omniboxInputBehavior; 791 if (behavior == NTP_DISPOSE_STATE.DISABLE_FAKEBOX) 792 setFakeboxActive(false); 793 else if (behavior == NTP_DISPOSE_STATE.HIDE_FAKEBOX_AND_LOGO) 794 setFakeboxAndLogoVisibility(false); 795} 796 797 798/** 799 * Restores the NTP (reloads the custom theme, re-enables the fakebox and 800 * unhides the logo.) 801 */ 802function restoreNtp() { 803 setFakeboxActive(true); 804 setFakeboxAndLogoVisibility(true); 805 onThemeChange(); 806} 807 808 809/** 810 * @param {boolean} focus True to focus the fakebox. 811 */ 812function setFakeboxFocus(focus) { 813 document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus); 814} 815 816 817/** 818 * @return {boolean} True if the fakebox has focus. 819 */ 820function isFakeboxFocused() { 821 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS); 822} 823 824 825/** 826 * @param {boolean} enable True to enable the fakebox. 827 */ 828function setFakeboxActive(enable) { 829 document.body.classList.toggle(CLASSES.FAKEBOX_DISABLE, !enable); 830} 831 832 833/** 834 * @param {!Event} event The click event. 835 * @return {boolean} True if the click occurred in an enabled fakebox. 836 */ 837function isFakeboxClick(event) { 838 return fakebox.contains(event.target) && 839 !document.body.classList.contains(CLASSES.FAKEBOX_DISABLE); 840} 841 842 843/** 844 * @param {boolean} show True to show the fakebox and logo. 845 */ 846function setFakeboxAndLogoVisibility(show) { 847 document.body.classList.toggle(CLASSES.HIDE_FAKEBOX_AND_LOGO, !show); 848} 849 850 851/** 852 * Shortcut for document.getElementById. 853 * @param {string} id of the element. 854 * @return {HTMLElement} with the id. 855 */ 856function $(id) { 857 return document.getElementById(id); 858} 859 860 861/** 862 * Utility function which creates an element with an optional classname and 863 * appends it to the specified parent. 864 * @param {Element} parent The parent to append the new element. 865 * @param {string} name The name of the new element. 866 * @param {string=} opt_class The optional classname of the new element. 867 * @return {Element} The new element. 868 */ 869function createAndAppendElement(parent, name, opt_class) { 870 var child = document.createElement(name); 871 if (opt_class) 872 child.classList.add(opt_class); 873 parent.appendChild(child); 874 return child; 875} 876 877 878/** 879 * Removes a node from its parent. 880 * @param {Node} node The node to remove. 881 */ 882function removeNode(node) { 883 node.parentNode.removeChild(node); 884} 885 886 887/** 888 * Removes all the child nodes on a DOM node. 889 * @param {Node} node Node to remove children from. 890 */ 891function removeChildren(node) { 892 node.innerHTML = ''; 893} 894 895 896/** 897 * @param {!Element} element The element to register the handler for. 898 * @param {number} keycode The keycode of the key to register. 899 * @param {!Function} handler The key handler to register. 900 */ 901function registerKeyHandler(element, keycode, handler) { 902 element.addEventListener('keydown', function(event) { 903 if (event.keyCode == keycode) 904 handler(event); 905 }); 906} 907 908 909/** 910 * @return {Object} the handle to the embeddedSearch API. 911 */ 912function getEmbeddedSearchApiHandle() { 913 if (window.cideb) 914 return window.cideb; 915 if (window.chrome && window.chrome.embeddedSearch) 916 return window.chrome.embeddedSearch; 917 return null; 918} 919 920/** 921 * Extract the desired navigation behavior from a click button. 922 * @param {number} button The Event#button property of a click event. 923 * @return {WindowOpenDisposition} The desired behavior for 924 * navigateContentWindow. 925 */ 926function getDispositionFromClickButton(button) { 927 if (button == MIDDLE_MOUSE_BUTTON) 928 return WindowOpenDisposition.NEW_BACKGROUND_TAB; 929 return WindowOpenDisposition.CURRENT_TAB; 930} 931 932 933/** 934 * Prepares the New Tab Page by adding listeners, rendering the current 935 * theme, the most visited pages section, and Google-specific elements for a 936 * Google-provided page. 937 */ 938function init() { 939 tilesContainer = $(IDS.TILES); 940 notification = $(IDS.NOTIFICATION); 941 attribution = $(IDS.ATTRIBUTION); 942 ntpContents = $(IDS.NTP_CONTENTS); 943 944 for (var i = 0; i < NUM_ROWS; i++) { 945 var row = document.createElement('div'); 946 row.classList.add(CLASSES.ROW); 947 tilesContainer.appendChild(row); 948 } 949 950 if (configData.isGooglePage) { 951 var logo = document.createElement('div'); 952 logo.id = IDS.LOGO; 953 954 fakebox = document.createElement('div'); 955 fakebox.id = IDS.FAKEBOX; 956 fakebox.innerHTML = 957 '<input autocomplete="off" tabindex="-1" aria-hidden="true">' + 958 '<div id=cursor></div>'; 959 960 ntpContents.insertBefore(fakebox, ntpContents.firstChild); 961 ntpContents.insertBefore(logo, ntpContents.firstChild); 962 } else { 963 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); 964 } 965 966 var recentTabsText = configData.translatedStrings.recentTabs; 967 if (recentTabsText) { 968 var recentTabsLink = document.createElement('span'); 969 recentTabsLink.id = IDS.RECENT_TABS; 970 recentTabsLink.addEventListener('click', function(event) { 971 ntpApiHandle.navigateContentWindow( 972 'chrome://history', getDispositionFromClickButton(event.button)); 973 }); 974 recentTabsLink.textContent = recentTabsText; 975 ntpContents.appendChild(recentTabsLink); 976 // Move the attribution up to prevent it from overlapping. 977 attribution.style.bottom = '28px'; 978 } 979 980 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE); 981 notificationMessage.textContent = 982 configData.translatedStrings.thumbnailRemovedNotification; 983 var undoLink = $(IDS.UNDO_LINK); 984 undoLink.addEventListener('click', onUndo); 985 registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo); 986 undoLink.textContent = configData.translatedStrings.undoThumbnailRemove; 987 var restoreAllLink = $(IDS.RESTORE_ALL_LINK); 988 restoreAllLink.addEventListener('click', onRestoreAll); 989 registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onUndo); 990 restoreAllLink.textContent = 991 configData.translatedStrings.restoreThumbnailsShort; 992 $(IDS.ATTRIBUTION_TEXT).textContent = 993 configData.translatedStrings.attributionIntro; 994 995 var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON); 996 notificationCloseButton.addEventListener('click', hideNotification); 997 998 userInitiatedMostVisitedChange = false; 999 window.addEventListener('resize', onResize); 1000 onResize(); 1001 1002 var topLevelHandle = getEmbeddedSearchApiHandle(); 1003 1004 ntpApiHandle = topLevelHandle.newTabPage; 1005 ntpApiHandle.onthemechange = onThemeChange; 1006 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; 1007 1008 ntpApiHandle.oninputstart = onInputStart; 1009 ntpApiHandle.oninputcancel = restoreNtp; 1010 1011 if (ntpApiHandle.isInputInProgress) 1012 onInputStart(); 1013 1014 onThemeChange(); 1015 onMostVisitedChange(); 1016 1017 searchboxApiHandle = topLevelHandle.searchBox; 1018 1019 if (fakebox) { 1020 // Listener for updating the key capture state. 1021 document.body.onclick = function(event) { 1022 if (isFakeboxClick(event)) 1023 searchboxApiHandle.startCapturingKeyStrokes(); 1024 else if (isFakeboxFocused()) 1025 searchboxApiHandle.stopCapturingKeyStrokes(); 1026 }; 1027 searchboxApiHandle.onkeycapturechange = function() { 1028 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); 1029 }; 1030 } 1031 1032 if (searchboxApiHandle.rtl) { 1033 $(IDS.NOTIFICATION).dir = 'rtl'; 1034 // Add class for setting alignments based on language directionality. 1035 document.body.classList.add(CLASSES.RTL); 1036 $(IDS.TILES).dir = 'rtl'; 1037 } 1038} 1039 1040 1041/** 1042 * Binds event listeners. 1043 */ 1044function listen() { 1045 document.addEventListener('DOMContentLoaded', init); 1046} 1047 1048return { 1049 init: init, 1050 listen: listen 1051}; 1052} 1053 1054if (!window.localNTPUnitTest) { 1055 LocalNTP().listen(); 1056} 1057