| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 | 5 |
| 6 /** | 6 /** |
| 7 * @fileoverview The local InstantExtended NTP. | 7 * @fileoverview The local InstantExtended NTP. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 | 10 |
| 11 /** | 11 /** |
| 12 * Controls rendering the new tab page for InstantExtended. | 12 * Controls rendering the new tab page for InstantExtended. |
| 13 * @return {Object} A limited interface for testing the local NTP. | 13 * @return {Object} A limited interface for testing the local NTP. |
| 14 */ | 14 */ |
| 15 function LocalNTP() { | 15 function LocalNTP() { |
| 16 <include src="../../../../ui/webui/resources/js/assert.js"> | 16 'use strict'; |
| 17 <include src="local_ntp_design.js"> | |
| 18 <include src="local_ntp_util.js"> | |
| 19 <include src="window_disposition_util.js"> | |
| 20 | 17 |
| 18 <include src="../../../../ui/webui/resources/js/util.js"> |
| 19 <include src='local_ntp_design.js'> |
| 21 | 20 |
| 22 /** | 21 /** |
| 23 * Enum for classnames. | 22 * Enum for classnames. |
| 24 * @enum {string} | 23 * @enum {string} |
| 25 * @const | 24 * @const |
| 26 */ | 25 */ |
| 27 var CLASSES = { | 26 var CLASSES = { |
| 28 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme | 27 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', | |
| 32 DARK: 'dark', | 28 DARK: 'dark', |
| 33 DEFAULT_THEME: 'default-theme', | 29 DEFAULT_THEME: 'default-theme', |
| 34 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', | 30 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', |
| 35 DOT: 'dot', | |
| 36 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive | 31 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive |
| 37 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox | 32 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox |
| 38 // Applies drag focus style to the fakebox | 33 // Applies drag focus style to the fakebox |
| 39 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', | 34 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', | 35 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', |
| 45 HIDE_NOTIFICATION: 'mv-notice-hide', | 36 HIDE_NOTIFICATION: 'mv-notice-hide', |
| 46 // Vertically centers the most visited section for a non-Google provided page. | 37 // Vertically centers the most visited section for a non-Google provided page. |
| 47 NON_GOOGLE_PAGE: 'non-google-page', | 38 NON_GOOGLE_PAGE: 'non-google-page', |
| 48 PAGE: 'mv-page', // page tiles | 39 RTL: 'rtl' // Right-to-left language text. |
| 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', | |
| 54 TILE: 'mv-tile', | |
| 55 TILE_INNER: 'mv-tile-inner', | |
| 56 TITLE: 'mv-title' | |
| 57 }; | 40 }; |
| 58 | 41 |
| 59 | 42 |
| 60 /** | 43 /** |
| 61 * Enum for HTML element ids. | 44 * Enum for HTML element ids. |
| 62 * @enum {string} | 45 * @enum {string} |
| 63 * @const | 46 * @const |
| 64 */ | 47 */ |
| 65 var IDS = { | 48 var IDS = { |
| 66 ATTRIBUTION: 'attribution', | 49 ATTRIBUTION: 'attribution', |
| (...skipping 29 matching lines...) Expand all Loading... |
| 96 * @const | 79 * @const |
| 97 */ | 80 */ |
| 98 var NTP_DISPOSE_STATE = { | 81 var NTP_DISPOSE_STATE = { |
| 99 NONE: 0, // Preserve the NTP appearance and functionality | 82 NONE: 0, // Preserve the NTP appearance and functionality |
| 100 DISABLE_FAKEBOX: 1, | 83 DISABLE_FAKEBOX: 1, |
| 101 HIDE_FAKEBOX_AND_LOGO: 2 | 84 HIDE_FAKEBOX_AND_LOGO: 2 |
| 102 }; | 85 }; |
| 103 | 86 |
| 104 | 87 |
| 105 /** | 88 /** |
| 106 * The JavaScript button event value for a middle click. | |
| 107 * @type {number} | |
| 108 * @const | |
| 109 */ | |
| 110 var MIDDLE_MOUSE_BUTTON = 1; | |
| 111 | |
| 112 | |
| 113 /** | |
| 114 * The container for the tile elements. | |
| 115 * @type {Element} | |
| 116 */ | |
| 117 var tilesContainer; | |
| 118 | |
| 119 | |
| 120 /** | |
| 121 * The notification displayed when a page is blacklisted. | 89 * The notification displayed when a page is blacklisted. |
| 122 * @type {Element} | 90 * @type {Element} |
| 123 */ | 91 */ |
| 124 var notification; | 92 var notification; |
| 125 | 93 |
| 126 | 94 |
| 127 /** | 95 /** |
| 128 * The container for the theme attribution. | 96 * The container for the theme attribution. |
| 129 * @type {Element} | 97 * @type {Element} |
| 130 */ | 98 */ |
| 131 var attribution; | 99 var attribution; |
| 132 | 100 |
| 133 | 101 |
| 134 /** | 102 /** |
| 135 * The "fakebox" - an input field that looks like a regular searchbox. When it | 103 * 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. | 104 * is focused, any text the user types goes directly into the omnibox. |
| 137 * @type {Element} | 105 * @type {Element} |
| 138 */ | 106 */ |
| 139 var fakebox; | 107 var fakebox; |
| 140 | 108 |
| 141 | 109 |
| 142 /** | 110 /** |
| 143 * The container for NTP elements. | 111 * The container for NTP elements. |
| 144 * @type {Element} | 112 * @type {Element} |
| 145 */ | 113 */ |
| 146 var ntpContents; | 114 var ntpContents; |
| 147 | 115 |
| 148 | 116 |
| 149 /** | 117 /** |
| 150 * The array of rendered tiles, ordered by appearance. | 118 * The last blacklisted tile rid if any, which by definition should not be |
| 151 * @type {!Array<Tile>} | 119 * filler. |
| 152 */ | 120 * @type {?number} |
| 153 var tiles = []; | |
| 154 | |
| 155 | |
| 156 /** | |
| 157 * The last blacklisted tile if any, which by definition should not be filler. | |
| 158 * @type {?Tile} | |
| 159 */ | 121 */ |
| 160 var lastBlacklistedTile = null; | 122 var lastBlacklistedTile = null; |
| 161 | 123 |
| 162 | 124 |
| 163 /** | 125 /** |
| 164 * The iframe element which is currently keyboard focused, or null. | |
| 165 * @type {?Element} | |
| 166 */ | |
| 167 var focusedIframe = null; | |
| 168 | |
| 169 | |
| 170 /** | |
| 171 * True if a page has been blacklisted and we're waiting on the | |
| 172 * onmostvisitedchange callback. See renderAllTiles() for how this is used. | |
| 173 * @type {boolean} | |
| 174 */ | |
| 175 var isBlacklisting = false; | |
| 176 | |
| 177 | |
| 178 /** | |
| 179 * Current number of tiles columns shown based on the window width, including | 126 * Current number of tiles columns shown based on the window width, including |
| 180 * those that just contain filler. | 127 * those that just contain filler. |
| 181 * @type {number} | 128 * @type {number} |
| 182 */ | 129 */ |
| 183 var numColumnsShown = 0; | 130 var numColumnsShown = 0; |
| 184 | 131 |
| 185 | 132 |
| 186 /** | 133 /** |
| 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. | |
| 189 * @type {boolean} | |
| 190 */ | |
| 191 var userInitiatedMostVisitedChange = false; | |
| 192 | |
| 193 | |
| 194 /** | |
| 195 * The browser embeddedSearch.newTabPage object. | 134 * The browser embeddedSearch.newTabPage object. |
| 196 * @type {Object} | 135 * @type {Object} |
| 197 */ | 136 */ |
| 198 var ntpApiHandle; | 137 var ntpApiHandle; |
| 199 | 138 |
| 200 | 139 |
| 201 /** | 140 /** |
| 202 * The browser embeddedSearch.searchBox object. | 141 * The browser embeddedSearch.searchBox object. |
| 203 * @type {Object} | 142 * @type {Object} |
| 204 */ | 143 */ |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 238 /** | 177 /** |
| 239 * Minimum total padding to give to the left and right of the most visited | 178 * Minimum total padding to give to the left and right of the most visited |
| 240 * section. Used to determine how many tiles to show. | 179 * section. Used to determine how many tiles to show. |
| 241 * @type {number} | 180 * @type {number} |
| 242 * @const | 181 * @const |
| 243 */ | 182 */ |
| 244 var MIN_TOTAL_HORIZONTAL_PADDING = 200; | 183 var MIN_TOTAL_HORIZONTAL_PADDING = 200; |
| 245 | 184 |
| 246 | 185 |
| 247 /** | 186 /** |
| 248 * The filename for a most visited iframe src which shows a page title. | |
| 249 * @type {string} | |
| 250 * @const | |
| 251 */ | |
| 252 var MOST_VISITED_TITLE_IFRAME = 'title.html'; | |
| 253 | |
| 254 | |
| 255 /** | |
| 256 * The filename for a most visited iframe src which shows a thumbnail image. | |
| 257 * @type {string} | |
| 258 * @const | |
| 259 */ | |
| 260 var MOST_VISITED_THUMBNAIL_IFRAME = 'thumbnail.html'; | |
| 261 | |
| 262 | |
| 263 /** | |
| 264 * The color of the title in RRGGBBAA format. | 187 * The color of the title in RRGGBBAA format. |
| 265 * @type {?string} | 188 * @type {?string} |
| 266 */ | 189 */ |
| 267 var titleColor = null; | 190 var titleColor = null; |
| 268 | 191 |
| 269 | 192 |
| 270 /** | 193 /** |
| 271 * Hide most visited tiles for at most this many milliseconds while painting. | |
| 272 * @type {number} | |
| 273 * @const | |
| 274 */ | |
| 275 var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500; | |
| 276 | |
| 277 | |
| 278 /** | |
| 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. | |
| 281 * | |
| 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. | |
| 288 * @constructor | |
| 289 */ | |
| 290 function Tile(elem, opt_innerElem, opt_titleElem, opt_thumbnailElem, opt_rid) { | |
| 291 /** @type {Element} */ | |
| 292 this.elem = elem; | |
| 293 | |
| 294 /** @type {Element|undefined} */ | |
| 295 this.innerElem = opt_innerElem; | |
| 296 | |
| 297 /** @type {Element|undefined} */ | |
| 298 this.titleElem = opt_titleElem; | |
| 299 | |
| 300 /** @type {Element|undefined} */ | |
| 301 this.thumbnailElem = opt_thumbnailElem; | |
| 302 | |
| 303 /** @type {number|undefined} */ | |
| 304 this.rid = opt_rid; | |
| 305 } | |
| 306 | |
| 307 | |
| 308 /** | |
| 309 * Heuristic to determine whether a theme should be considered to be dark, so | 194 * Heuristic to determine whether a theme should be considered to be dark, so |
| 310 * the colors of various UI elements can be adjusted. | 195 * the colors of various UI elements can be adjusted. |
| 311 * @param {ThemeBackgroundInfo|undefined} info Theme background information. | 196 * @param {ThemeBackgroundInfo|undefined} info Theme background information. |
| 312 * @return {boolean} Whether the theme is dark. | 197 * @return {boolean} Whether the theme is dark. |
| 313 * @private | 198 * @private |
| 314 */ | 199 */ |
| 315 function getIsThemeDark(info) { | 200 function getIsThemeDark(info) { |
| 316 if (!info) | 201 if (!info) |
| 317 return false; | 202 return false; |
| 318 // Heuristic: light text implies dark theme. | 203 // Heuristic: light text implies dark theme. |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 363 setCustomThemeStyle(info); | 248 setCustomThemeStyle(info); |
| 364 } | 249 } |
| 365 | 250 |
| 366 | 251 |
| 367 /** | 252 /** |
| 368 * Updates the NTP based on the current theme, then rerenders all tiles. | 253 * Updates the NTP based on the current theme, then rerenders all tiles. |
| 369 * @private | 254 * @private |
| 370 */ | 255 */ |
| 371 function onThemeChange() { | 256 function onThemeChange() { |
| 372 renderTheme(); | 257 renderTheme(); |
| 373 tilesContainer.innerHTML = ''; | |
| 374 renderAllTiles(); | |
| 375 } | 258 } |
| 376 | 259 |
| 377 | 260 |
| 378 /** | 261 /** |
| 379 * Updates the NTP style according to theme. | 262 * Updates the NTP style according to theme. |
| 380 * @param {Object=} opt_themeInfo The information about the theme. If it is | 263 * @param {Object=} opt_themeInfo The information about the theme. If it is |
| 381 * omitted the style will be reverted to the default. | 264 * omitted the style will be reverted to the default. |
| 382 * @private | 265 * @private |
| 383 */ | 266 */ |
| 384 function setCustomThemeStyle(opt_themeInfo) { | 267 function setCustomThemeStyle(opt_themeInfo) { |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 482 function convertToRGBAColor(color) { | 365 function convertToRGBAColor(color) { |
| 483 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + | 366 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + |
| 484 color[3] / 255 + ')'; | 367 color[3] / 255 + ')'; |
| 485 } | 368 } |
| 486 | 369 |
| 487 | 370 |
| 488 /** | 371 /** |
| 489 * Called when page data change. | 372 * Called when page data change. |
| 490 */ | 373 */ |
| 491 function onMostVisitedChange() { | 374 function onMostVisitedChange() { |
| 492 renderAllTiles(); | 375 reloadTiles(); |
| 493 } | |
| 494 | |
| 495 | |
| 496 /** | |
| 497 * Rerenders all tiles based on Most Visited page data. | |
| 498 */ | |
| 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); | |
| 506 } else { | |
| 507 reloadAllTiles(); | |
| 508 } | |
| 509 } | |
| 510 | |
| 511 | |
| 512 /** | |
| 513 * Handles the end of the blacklist animation by showing the notification and | |
| 514 * re-rendering the new set of tiles. | |
| 515 */ | |
| 516 function blacklistAnimationDone() { | |
| 517 showNotification(); | |
| 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 | |
| 524 // animation. | |
| 525 reloadAllTiles(); | |
| 526 } | 376 } |
| 527 | 377 |
| 528 | 378 |
| 529 /** | 379 /** |
| 530 * Fetches new data, creates, and renders tiles. | 380 * Fetches new data, creates, and renders tiles. |
| 531 */ | 381 */ |
| 532 function reloadAllTiles() { | 382 function reloadTiles() { |
| 533 var pages = ntpApiHandle.mostVisited; | 383 var pages = ntpApiHandle.mostVisited; |
| 384 var iframe = $('mv-single').contentWindow; |
| 534 | 385 |
| 535 tiles = []; | 386 tiles = []; |
| 536 for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i) | 387 for (var i = 0; i < Math.min(MAX_NUM_TILES_TO_SHOW, pages.length); ++i) { |
| 537 tiles.push(createTile(pages[i], i)); | 388 iframe.postMessage({cmd: 'tile', rid: pages[i].rid}, '*'); |
| 538 | 389 } |
| 539 tilesContainer.innerHTML = ''; | 390 iframe.postMessage({cmd: 'show'}, '*'); |
| 540 renderAndShowTiles(); | |
| 541 } | 391 } |
| 542 | 392 |
| 543 | 393 |
| 544 /** | |
| 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. | |
| 549 */ | |
| 550 function bindTileOnloadEvents(tile, tileVisibilityBarrier) { | |
| 551 if (tile.titleElem) { | |
| 552 tileVisibilityBarrier.add(); | |
| 553 tile.titleElem.onload = function() { | |
| 554 tileVisibilityBarrier.remove(); | |
| 555 }; | |
| 556 } | |
| 557 if (tile.thumbnailElem) { | |
| 558 tileVisibilityBarrier.add(); | |
| 559 tile.thumbnailElem.onload = function() { | |
| 560 tile.elem.classList.add(CLASSES.PAGE_READY); | |
| 561 tileVisibilityBarrier.remove(); | |
| 562 }; | |
| 563 } | |
| 564 } | |
| 565 | |
| 566 | |
| 567 /** | |
| 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. | |
| 570 */ | |
| 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); | |
| 577 | |
| 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); | |
| 585 } | |
| 586 }; | |
| 587 var tileVisibilityBarrier = new Barrier(showAll); | |
| 588 | |
| 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(); | |
| 595 showAll(); | |
| 596 }, MOST_VISITED_PAINT_TIMEOUT_MSEC); | |
| 597 } | |
| 598 userInitiatedMostVisitedChange = false; | |
| 599 | |
| 600 for (var i = numExisting; i < numDesired; ++i) { | |
| 601 bindTileOnloadEvents(tiles[i], tileVisibilityBarrier); | |
| 602 tilesContainer.appendChild(tiles[i].elem); | |
| 603 } | |
| 604 } | |
| 605 | |
| 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'; | |
| 614 } | |
| 615 | |
| 616 | |
| 617 /** | |
| 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. | |
| 622 */ | |
| 623 function getMostVisitedTitleIframeUrl(rid, position) { | |
| 624 var url = 'chrome-search://most-visited/' + | |
| 625 encodeURIComponent(MOST_VISITED_TITLE_IFRAME); | |
| 626 var params = [ | |
| 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('&'); | |
| 637 } | |
| 638 | |
| 639 | |
| 640 /** | |
| 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. | |
| 645 */ | |
| 646 function getMostVisitedThumbnailIframeUrl(rid, position) { | |
| 647 var url = 'chrome-search://most-visited/' + | |
| 648 encodeURIComponent(MOST_VISITED_THUMBNAIL_IFRAME); | |
| 649 var params = [ | |
| 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('&'); | |
| 658 } | |
| 659 | |
| 660 | |
| 661 /** | |
| 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. | |
| 667 */ | |
| 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) { | |
| 674 e.preventDefault(); | |
| 675 }); | |
| 676 | |
| 677 if (!page) { | |
| 678 return new Tile(tileElem); | |
| 679 } | |
| 680 | |
| 681 var rid = page.rid; | |
| 682 tileElem.classList.add(CLASSES.PAGE); | |
| 683 | |
| 684 var navigateFunction = function(e) { | |
| 685 e.preventDefault(); | |
| 686 ntpApiHandle.navigateContentWindow(rid, getDispositionFromEvent(e)); | |
| 687 }; | |
| 688 | |
| 689 // The click handler for navigating to the page identified by the RID. | |
| 690 tileElem.addEventListener('click', navigateFunction); | |
| 691 | |
| 692 // Container of tile contents. | |
| 693 var innerElem = createAndAppendElement(tileElem, 'div', CLASSES.TILE_INNER); | |
| 694 | |
| 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'; | |
| 700 | |
| 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'); | |
| 704 | |
| 705 // Why iframes have IDs: | |
| 706 // | |
| 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. | |
| 713 // | |
| 714 // Giving iframes distinct ids seems to cause some invalidation and prevent | |
| 715 // associating the incorrect data. | |
| 716 // | |
| 717 // TODO(jered): Find and fix the root (probably Blink) bug. | |
| 718 | |
| 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); | |
| 724 | |
| 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); | |
| 731 } | |
| 732 | |
| 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); | |
| 742 | |
| 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; | |
| 751 | |
| 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); | |
| 756 | |
| 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 + ')'; | |
| 761 } else { | |
| 762 favicon.classList.add(CLASSES.FAVICON_FALLBACK); | |
| 763 } | |
| 764 return new Tile(tileElem, innerElem, titleElem, thumbnailElem, rid); | |
| 765 } | |
| 766 | |
| 767 | |
| 768 /** | |
| 769 * Generates a function to be called when the page with the corresponding RID | |
| 770 * is blacklisted. | |
| 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. | |
| 774 */ | |
| 775 function generateBlacklistFunction(rid) { | |
| 776 return function(e) { | |
| 777 // Prevent navigation when the page is being blacklisted. | |
| 778 if (e) | |
| 779 e.stopPropagation(); | |
| 780 | |
| 781 userInitiatedMostVisitedChange = true; | |
| 782 isBlacklisting = true; | |
| 783 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON); | |
| 784 lastBlacklistedTile = getTileByRid(rid); | |
| 785 ntpApiHandle.deleteMostVisitedItem(rid); | |
| 786 }; | |
| 787 } | |
| 788 | |
| 789 | |
| 790 /** | 394 /** |
| 791 * Shows the blacklist notification and triggers a delay to hide it. | 395 * Shows the blacklist notification and triggers a delay to hide it. |
| 792 */ | 396 */ |
| 793 function showNotification() { | 397 function showNotification() { |
| 794 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); | 398 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); |
| 795 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); | 399 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); |
| 796 notification.scrollTop; | 400 notification.scrollTop; |
| 797 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); | 401 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); |
| 798 } | 402 } |
| 799 | 403 |
| 800 | 404 |
| 801 /** | 405 /** |
| 802 * Hides the blacklist notification. | 406 * Hides the blacklist notification. |
| 803 */ | 407 */ |
| 804 function hideNotification() { | 408 function hideNotification() { |
| 805 notification.classList.add(CLASSES.HIDE_NOTIFICATION); | 409 notification.classList.add(CLASSES.HIDE_NOTIFICATION); |
| 806 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); | 410 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); |
| 807 } | 411 } |
| 808 | 412 |
| 809 | 413 |
| 810 /** | 414 /** |
| 811 * Handles a click on the notification undo link by hiding the notification and | 415 * Handles a click on the notification undo link by hiding the notification and |
| 812 * informing Chrome. | 416 * informing Chrome. |
| 813 */ | 417 */ |
| 814 function onUndo() { | 418 function onUndo() { |
| 815 userInitiatedMostVisitedChange = true; | |
| 816 hideNotification(); | 419 hideNotification(); |
| 817 var lastBlacklistedRID = lastBlacklistedTile.rid; | 420 if (lastBlacklistedTile != null) { |
| 818 if (typeof lastBlacklistedRID != 'undefined') | 421 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile); |
| 819 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID); | 422 } |
| 820 } | 423 } |
| 821 | 424 |
| 822 | 425 |
| 823 /** | 426 /** |
| 824 * Handles a click on the restore all notification link by hiding the | 427 * Handles a click on the restore all notification link by hiding the |
| 825 * notification and informing Chrome. | 428 * notification and informing Chrome. |
| 826 */ | 429 */ |
| 827 function onRestoreAll() { | 430 function onRestoreAll() { |
| 828 userInitiatedMostVisitedChange = true; | |
| 829 hideNotification(); | 431 hideNotification(); |
| 830 ntpApiHandle.undoAllMostVisitedDeletions(); | 432 ntpApiHandle.undoAllMostVisitedDeletions(); |
| 831 } | 433 } |
| 832 | 434 |
| 833 | 435 |
| 834 /** | 436 /** |
| 835 * Recomputes the number of tile columns, and width of various contents based | 437 * Recomputes the number of tile columns, and width of various contents based |
| 836 * on the width of the window. | 438 * on the width of the window. |
| 837 * @return {boolean} Whether the number of tile columns has changed. | 439 * @return {boolean} Whether the number of tile columns has changed. |
| 838 */ | 440 */ |
| (...skipping 10 matching lines...) Expand all Loading... |
| 849 if (newNumColumns < MIN_NUM_COLUMNS) | 451 if (newNumColumns < MIN_NUM_COLUMNS) |
| 850 newNumColumns = MIN_NUM_COLUMNS; | 452 newNumColumns = MIN_NUM_COLUMNS; |
| 851 else if (newNumColumns > MAX_NUM_COLUMNS) | 453 else if (newNumColumns > MAX_NUM_COLUMNS) |
| 852 newNumColumns = MAX_NUM_COLUMNS; | 454 newNumColumns = MAX_NUM_COLUMNS; |
| 853 | 455 |
| 854 if (numColumnsShown === newNumColumns) | 456 if (numColumnsShown === newNumColumns) |
| 855 return false; | 457 return false; |
| 856 | 458 |
| 857 numColumnsShown = newNumColumns; | 459 numColumnsShown = newNumColumns; |
| 858 var tilesContainerWidth = numColumnsShown * tileRequiredWidth; | 460 var tilesContainerWidth = numColumnsShown * tileRequiredWidth; |
| 859 tilesContainer.style.width = tilesContainerWidth + 'px'; | 461 $(IDS.TILES).style.width = tilesContainerWidth + 'px'; |
| 860 if (fakebox) { | 462 if (fakebox) { |
| 861 // -2 to account for border. | 463 // -2 to account for border. |
| 862 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2); | 464 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2); |
| 863 fakebox.style.width = fakeboxWidth + 'px'; | 465 fakebox.style.width = fakeboxWidth + 'px'; |
| 864 } | 466 } |
| 865 return true; | 467 return true; |
| 866 } | 468 } |
| 867 | 469 |
| 868 | 470 |
| 869 /** | 471 /** |
| 870 * Resizes elements because the number of tile columns may need to change in | 472 * 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 | 473 * response to resizing. Also shows or hides extra tiles tiles according to the |
| 872 * new width of the page. | 474 * new width of the page. |
| 873 */ | 475 */ |
| 874 function onResize() { | 476 function onResize() { |
| 875 if (updateContentWidth()) { | 477 updateContentWidth(); |
| 876 // Render without clearing tiles. | |
| 877 renderAndShowTiles(); | |
| 878 } | |
| 879 } | 478 } |
| 880 | 479 |
| 881 | 480 |
| 882 /** | |
| 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. | |
| 886 */ | |
| 887 function getTileByRid(rid) { | |
| 888 for (var i = 0, length = tiles.length; i < length; ++i) { | |
| 889 var tile = tiles[i]; | |
| 890 if (tile.rid == rid) | |
| 891 return tile; | |
| 892 } | |
| 893 return null; | |
| 894 } | |
| 895 | |
| 896 | |
| 897 /** | 481 /** |
| 898 * Handles new input by disposing the NTP, according to where the input was | 482 * Handles new input by disposing the NTP, according to where the input was |
| 899 * entered. | 483 * entered. |
| 900 */ | 484 */ |
| 901 function onInputStart() { | 485 function onInputStart() { |
| 902 if (fakebox && isFakeboxFocused()) { | 486 if (fakebox && isFakeboxFocused()) { |
| 903 setFakeboxFocus(false); | 487 setFakeboxFocus(false); |
| 904 setFakeboxDragFocus(false); | 488 setFakeboxDragFocus(false); |
| 905 disposeNtp(true); | 489 disposeNtp(true); |
| 906 } else if (!isFakeboxFocused()) { | 490 } else if (!isFakeboxFocused()) { |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1001 function createAndAppendElement(parent, name, opt_class) { | 585 function createAndAppendElement(parent, name, opt_class) { |
| 1002 var child = document.createElement(name); | 586 var child = document.createElement(name); |
| 1003 if (opt_class) | 587 if (opt_class) |
| 1004 child.classList.add(opt_class); | 588 child.classList.add(opt_class); |
| 1005 parent.appendChild(child); | 589 parent.appendChild(child); |
| 1006 return child; | 590 return child; |
| 1007 } | 591 } |
| 1008 | 592 |
| 1009 | 593 |
| 1010 /** | 594 /** |
| 1011 * Removes a node from its parent. | |
| 1012 * @param {Node} node The node to remove. | |
| 1013 */ | |
| 1014 function removeNode(node) { | |
| 1015 node.parentNode.removeChild(node); | |
| 1016 } | |
| 1017 | |
| 1018 | |
| 1019 /** | |
| 1020 * @param {!Element} element The element to register the handler for. | 595 * @param {!Element} element The element to register the handler for. |
| 1021 * @param {number} keycode The keycode of the key to register. | 596 * @param {number} keycode The keycode of the key to register. |
| 1022 * @param {!Function} handler The key handler to register. | 597 * @param {!Function} handler The key handler to register. |
| 1023 */ | 598 */ |
| 1024 function registerKeyHandler(element, keycode, handler) { | 599 function registerKeyHandler(element, keycode, handler) { |
| 1025 element.addEventListener('keydown', function(event) { | 600 element.addEventListener('keydown', function(event) { |
| 1026 if (event.keyCode == keycode) | 601 if (event.keyCode == keycode) |
| 1027 handler(event); | 602 handler(event); |
| 1028 }); | 603 }); |
| 1029 } | 604 } |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1040 return null; | 615 return null; |
| 1041 } | 616 } |
| 1042 | 617 |
| 1043 | 618 |
| 1044 /** | 619 /** |
| 1045 * Event handler for the focus changed and blacklist messages on link elements. | 620 * 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). | 621 * Used to toggle visual treatment on the tiles (depending on the message). |
| 1047 * @param {Event} event Event received. | 622 * @param {Event} event Event received. |
| 1048 */ | 623 */ |
| 1049 function handlePostMessage(event) { | 624 function handlePostMessage(event) { |
| 1050 if (event.origin !== 'chrome-search://most-visited') | 625 var cmd = event.data.cmd; |
| 1051 return; | 626 var args = event.data; |
| 627 if (cmd == 'tileBlacklisted') { |
| 628 showNotification(); |
| 629 lastBlacklistedTile = args.rid; |
| 1052 | 630 |
| 1053 if (event.data === 'linkFocused') { | 631 ntpApiHandle.deleteMostVisitedItem(args.rid); |
| 1054 var activeElement = document.activeElement; | |
| 1055 if (activeElement.classList.contains(CLASSES.TITLE)) { | |
| 1056 activeElement.classList.add(CLASSES.FOCUSED); | |
| 1057 focusedIframe = activeElement; | |
| 1058 } | |
| 1059 } else if (event.data === 'linkBlurred') { | |
| 1060 if (focusedIframe) | |
| 1061 focusedIframe.classList.remove(CLASSES.FOCUSED); | |
| 1062 focusedIframe = null; | |
| 1063 } else if (event.data.indexOf('tileBlacklisted') === 0) { | |
| 1064 var tilePosition = event.data.split(',')[1]; | |
| 1065 if (tilePosition) | |
| 1066 generateBlacklistFunction(tiles[parseInt(tilePosition, 10)].rid)(); | |
| 1067 } | 632 } |
| 1068 } | 633 } |
| 1069 | 634 |
| 1070 | 635 |
| 1071 /** | 636 /** |
| 1072 * Prepares the New Tab Page by adding listeners, rendering the current | 637 * Prepares the New Tab Page by adding listeners, rendering the current |
| 1073 * theme, the most visited pages section, and Google-specific elements for a | 638 * theme, the most visited pages section, and Google-specific elements for a |
| 1074 * Google-provided page. | 639 * Google-provided page. |
| 1075 */ | 640 */ |
| 1076 function init() { | 641 function init() { |
| 1077 tilesContainer = $(IDS.TILES); | |
| 1078 notification = $(IDS.NOTIFICATION); | 642 notification = $(IDS.NOTIFICATION); |
| 1079 attribution = $(IDS.ATTRIBUTION); | 643 attribution = $(IDS.ATTRIBUTION); |
| 1080 ntpContents = $(IDS.NTP_CONTENTS); | 644 ntpContents = $(IDS.NTP_CONTENTS); |
| 1081 | 645 |
| 1082 if (configData.isGooglePage) { | 646 if (configData.isGooglePage) { |
| 1083 var logo = document.createElement('div'); | 647 var logo = document.createElement('div'); |
| 1084 logo.id = IDS.LOGO; | 648 logo.id = IDS.LOGO; |
| 1085 logo.title = 'Google'; | 649 logo.title = 'Google'; |
| 1086 | 650 |
| 1087 fakebox = document.createElement('div'); | 651 fakebox = document.createElement('div'); |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1134 ntpApiHandle.onthemechange = onThemeChange; | 698 ntpApiHandle.onthemechange = onThemeChange; |
| 1135 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; | 699 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; |
| 1136 | 700 |
| 1137 ntpApiHandle.oninputstart = onInputStart; | 701 ntpApiHandle.oninputstart = onInputStart; |
| 1138 ntpApiHandle.oninputcancel = restoreNtp; | 702 ntpApiHandle.oninputcancel = restoreNtp; |
| 1139 | 703 |
| 1140 if (ntpApiHandle.isInputInProgress) | 704 if (ntpApiHandle.isInputInProgress) |
| 1141 onInputStart(); | 705 onInputStart(); |
| 1142 | 706 |
| 1143 renderTheme(); | 707 renderTheme(); |
| 1144 renderAllTiles(); | |
| 1145 | 708 |
| 1146 searchboxApiHandle = topLevelHandle.searchBox; | 709 searchboxApiHandle = topLevelHandle.searchBox; |
| 1147 | 710 |
| 1148 if (fakebox) { | 711 if (fakebox) { |
| 1149 // Listener for updating the key capture state. | 712 // Listener for updating the key capture state. |
| 1150 document.body.onmousedown = function(event) { | 713 document.body.onmousedown = function(event) { |
| 1151 if (isFakeboxClick(event)) | 714 if (isFakeboxClick(event)) |
| 1152 searchboxApiHandle.startCapturingKeyStrokes(); | 715 searchboxApiHandle.startCapturingKeyStrokes(); |
| 1153 else if (isFakeboxFocused()) | 716 else if (isFakeboxFocused()) |
| 1154 searchboxApiHandle.stopCapturingKeyStrokes(); | 717 searchboxApiHandle.stopCapturingKeyStrokes(); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 1183 // Update the fakebox style to match the current key capturing state. | 746 // Update the fakebox style to match the current key capturing state. |
| 1184 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); | 747 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); |
| 1185 } | 748 } |
| 1186 | 749 |
| 1187 if (searchboxApiHandle.rtl) { | 750 if (searchboxApiHandle.rtl) { |
| 1188 $(IDS.NOTIFICATION).dir = 'rtl'; | 751 $(IDS.NOTIFICATION).dir = 'rtl'; |
| 1189 // Grabbing the root HTML element. | 752 // Grabbing the root HTML element. |
| 1190 document.documentElement.setAttribute('dir', 'rtl'); | 753 document.documentElement.setAttribute('dir', 'rtl'); |
| 1191 // Add class for setting alignments based on language directionality. | 754 // Add class for setting alignments based on language directionality. |
| 1192 document.documentElement.classList.add(CLASSES.RTL); | 755 document.documentElement.classList.add(CLASSES.RTL); |
| 1193 $(IDS.TILES).dir = 'rtl'; | |
| 1194 } | 756 } |
| 1195 | 757 |
| 758 var iframe = document.createElement('iframe'); |
| 759 iframe.id = 'mv-single'; |
| 760 var args = []; |
| 761 |
| 762 if (searchboxApiHandle.rtl) { |
| 763 args.push('rtl=1'); |
| 764 } |
| 765 |
| 766 iframe.src = '//most-visited/single.html?' + args.join('&'); |
| 767 $(IDS.TILES).appendChild(iframe); |
| 768 |
| 769 iframe.onload = function() { |
| 770 reloadTiles(); |
| 771 }; |
| 772 |
| 1196 window.addEventListener('message', handlePostMessage); | 773 window.addEventListener('message', handlePostMessage); |
| 1197 } | 774 } |
| 1198 | 775 |
| 1199 | 776 |
| 1200 /** | 777 /** |
| 1201 * Binds event listeners. | 778 * Binds event listeners. |
| 1202 */ | 779 */ |
| 1203 function listen() { | 780 function listen() { |
| 1204 document.addEventListener('DOMContentLoaded', init); | 781 document.addEventListener('DOMContentLoaded', init); |
| 1205 } | 782 } |
| 1206 | 783 |
| 1207 return { | 784 return { |
| 1208 init: init, | 785 init: init, |
| 1209 listen: listen | 786 listen: listen |
| 1210 }; | 787 }; |
| 1211 } | 788 } |
| 1212 | 789 |
| 1213 if (!window.localNTPUnitTest) { | 790 if (!window.localNTPUnitTest) { |
| 1214 LocalNTP().listen(); | 791 LocalNTP().listen(); |
| 1215 } | 792 } |
| OLD | NEW |