| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 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 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 21 | 21 |
| 22 /** | 22 /** |
| 23 * Enum for classnames. | 23 * Enum for classnames. |
| 24 * @enum {string} | 24 * @enum {string} |
| 25 * @const | 25 * @const |
| 26 */ | 26 */ |
| 27 var CLASSES = { | 27 var CLASSES = { |
| 28 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme | 28 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme |
| 29 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation | 29 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation |
| 30 BLACKLIST_BUTTON: 'mv-x', | 30 BLACKLIST_BUTTON: 'mv-x', |
| 31 DARK: 'dark', |
| 32 DEFAULT_THEME: 'default-theme', |
| 31 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', | 33 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', |
| 34 DOT: 'dot', |
| 32 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive | 35 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive |
| 33 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox | 36 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox |
| 34 // Applies drag focus style to the fakebox | 37 // Applies drag focus style to the fakebox |
| 35 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', | 38 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', |
| 36 FAVICON: 'mv-favicon', | 39 FAVICON: 'mv-favicon', |
| 40 FAVICON_FALLBACK: 'mv-favicon-fallback', |
| 37 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation | 41 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation |
| 38 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', | 42 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', |
| 39 HIDE_NOTIFICATION: 'mv-notice-hide', | 43 HIDE_NOTIFICATION: 'mv-notice-hide', |
| 40 // Vertically centers the most visited section for a non-Google provided page. | 44 // Vertically centers the most visited section for a non-Google provided page. |
| 41 NON_GOOGLE_PAGE: 'non-google-page', | 45 NON_GOOGLE_PAGE: 'non-google-page', |
| 42 PAGE: 'mv-page', // page tiles | 46 PAGE: 'mv-page', // page tiles |
| 43 PAGE_READY: 'mv-page-ready', // page tile when ready | 47 PAGE_READY: 'mv-page-ready', // page tile when ready |
| 44 RTL: 'rtl', // Right-to-left language text. | 48 RTL: 'rtl', // Right-to-left language text. |
| 45 THUMBNAIL: 'mv-thumb', | 49 THUMBNAIL: 'mv-thumb', |
| 50 THUMBNAIL_FALLBACK: 'mv-thumb-fallback', |
| 46 THUMBNAIL_MASK: 'mv-mask', | 51 THUMBNAIL_MASK: 'mv-mask', |
| 47 TILE: 'mv-tile', | 52 TILE: 'mv-tile', |
| 53 TILE_INNER: 'mv-tile-inner', |
| 48 TITLE: 'mv-title' | 54 TITLE: 'mv-title' |
| 49 }; | 55 }; |
| 50 | 56 |
| 51 | 57 |
| 52 /** | 58 /** |
| 53 * Enum for HTML element ids. | 59 * Enum for HTML element ids. |
| 54 * @enum {string} | 60 * @enum {string} |
| 55 * @const | 61 * @const |
| 56 */ | 62 */ |
| 57 var IDS = { | 63 var IDS = { |
| 58 ATTRIBUTION: 'attribution', | 64 ATTRIBUTION: 'attribution', |
| 59 ATTRIBUTION_TEXT: 'attribution-text', | 65 ATTRIBUTION_TEXT: 'attribution-text', |
| 60 CUSTOM_THEME_STYLE: 'ct-style', | 66 CUSTOM_THEME_STYLE: 'ct-style', |
| 61 FAKEBOX: 'fakebox', | 67 FAKEBOX: 'fakebox', |
| 62 FAKEBOX_INPUT: 'fakebox-input', | 68 FAKEBOX_INPUT: 'fakebox-input', |
| 69 FAKEBOX_TEXT: 'fakebox-text', |
| 63 LOGO: 'logo', | 70 LOGO: 'logo', |
| 64 NOTIFICATION: 'mv-notice', | 71 NOTIFICATION: 'mv-notice', |
| 65 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', | 72 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', |
| 66 NOTIFICATION_MESSAGE: 'mv-msg', | 73 NOTIFICATION_MESSAGE: 'mv-msg', |
| 67 NTP_CONTENTS: 'ntp-contents', | 74 NTP_CONTENTS: 'ntp-contents', |
| 68 RESTORE_ALL_LINK: 'mv-restore', | 75 RESTORE_ALL_LINK: 'mv-restore', |
| 69 TILES: 'mv-tiles', | 76 TILES: 'mv-tiles', |
| 70 UNDO_LINK: 'mv-undo' | 77 UNDO_LINK: 'mv-undo' |
| 71 }; | 78 }; |
| 72 | 79 |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 162 /** | 169 /** |
| 163 * True if a page has been blacklisted and we're waiting on the | 170 * True if a page has been blacklisted and we're waiting on the |
| 164 * onmostvisitedchange callback. See onMostVisitedChange() for how this | 171 * onmostvisitedchange callback. See onMostVisitedChange() for how this |
| 165 * is used. | 172 * is used. |
| 166 * @type {boolean} | 173 * @type {boolean} |
| 167 */ | 174 */ |
| 168 var isBlacklisting = false; | 175 var isBlacklisting = false; |
| 169 | 176 |
| 170 | 177 |
| 171 /** | 178 /** |
| 179 * Stores whether the current theme has a dark background. |
| 180 * @type {boolean} |
| 181 */ |
| 182 var isBackgroundDark = false; |
| 183 |
| 184 |
| 185 /** |
| 172 * Current number of tiles columns shown based on the window width, including | 186 * Current number of tiles columns shown based on the window width, including |
| 173 * those that just contain filler. | 187 * those that just contain filler. |
| 174 * @type {number} | 188 * @type {number} |
| 175 */ | 189 */ |
| 176 var numColumnsShown = 0; | 190 var numColumnsShown = 0; |
| 177 | 191 |
| 178 | 192 |
| 179 /** | 193 /** |
| 180 * A flag to indicate Most Visited changed caused by user action. If true, then | 194 * A flag to indicate Most Visited changed caused by user action. If true, then |
| 181 * in onMostVisitedChange() tiles remain visible so no flickering occurs. | 195 * in onMostVisitedChange() tiles remain visible so no flickering occurs. |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 259 * @const | 273 * @const |
| 260 */ | 274 */ |
| 261 var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500; | 275 var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500; |
| 262 | 276 |
| 263 | 277 |
| 264 /** | 278 /** |
| 265 * A Tile is either a rendering of a Most Visited page or "filler" used to | 279 * A Tile is either a rendering of a Most Visited page or "filler" used to |
| 266 * pad out the section when not enough pages exist. | 280 * pad out the section when not enough pages exist. |
| 267 * | 281 * |
| 268 * @param {Element} elem The element for rendering the tile. | 282 * @param {Element} elem The element for rendering the tile. |
| 283 * @param {Element=} opt_innerElem The element for contents of tile. |
| 269 * @param {Element=} opt_titleElem The element for rendering the title. | 284 * @param {Element=} opt_titleElem The element for rendering the title. |
| 270 * @param {Element=} opt_thumbnailElem The element for rendering the thumbnail. | 285 * @param {Element=} opt_thumbnailElem The element for rendering the thumbnail. |
| 271 * @param {number=} opt_rid The RID for the corresponding Most Visited page. | 286 * @param {number=} opt_rid The RID for the corresponding Most Visited page. |
| 272 * Should only be left unspecified when creating a filler tile. | 287 * Should only be left unspecified when creating a filler tile. |
| 273 * @constructor | 288 * @constructor |
| 274 */ | 289 */ |
| 275 function Tile(elem, opt_titleElem, opt_thumbnailElem, opt_rid) { | 290 function Tile(elem, opt_innerElem, opt_titleElem, opt_thumbnailElem, opt_rid) { |
| 276 /** @type {Element} */ | 291 /** @type {Element} */ |
| 277 this.elem = elem; | 292 this.elem = elem; |
| 278 | 293 |
| 279 /** @type {Element|undefined} */ | 294 /** @type {Element|undefined} */ |
| 295 this.innerElem = opt_innerElem; |
| 296 |
| 297 /** @type {Element|undefined} */ |
| 280 this.titleElem = opt_titleElem; | 298 this.titleElem = opt_titleElem; |
| 281 | 299 |
| 282 /** @type {Element|undefined} */ | 300 /** @type {Element|undefined} */ |
| 283 this.thumbnailElem = opt_thumbnailElem; | 301 this.thumbnailElem = opt_thumbnailElem; |
| 284 | 302 |
| 285 /** @type {number|undefined} */ | 303 /** @type {number|undefined} */ |
| 286 this.rid = opt_rid; | 304 this.rid = opt_rid; |
| 287 } | 305 } |
| 288 | 306 |
| 289 | 307 |
| 290 /** | 308 /** |
| 309 * Determines whether a theme should be considered to have dark background. |
| 310 * @param {ThemeBackgroundInfo} info Theme background information. |
| 311 * @return {boolean} Whether the theme has dark background. |
| 312 * @private |
| 313 */ |
| 314 function getIsBackgroundDark(info) { |
| 315 if (info.imageUrl) |
| 316 return true; |
| 317 var rgba = info.backgroundColorRgba; |
| 318 var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2]; |
| 319 return luminance < 128; |
| 320 } |
| 321 |
| 322 |
| 323 /** |
| 291 * Updates the NTP based on the current theme. | 324 * Updates the NTP based on the current theme. |
| 292 * @private | 325 * @private |
| 293 */ | 326 */ |
| 294 function onThemeChange() { | 327 function renderTheme() { |
| 295 var info = ntpApiHandle.themeBackgroundInfo; | 328 var info = ntpApiHandle.themeBackgroundInfo; |
| 296 if (!info) | 329 if (!info) { |
| 330 isBackgroundDark = false; |
| 297 return; | 331 return; |
| 332 } |
| 333 |
| 334 isBackgroundDark = getIsBackgroundDark(info); |
| 335 ntpContents.classList.toggle(CLASSES.DARK, isBackgroundDark); |
| 298 | 336 |
| 299 var background = [convertToRGBAColor(info.backgroundColorRgba), | 337 var background = [convertToRGBAColor(info.backgroundColorRgba), |
| 300 info.imageUrl, | 338 info.imageUrl, |
| 301 info.imageTiling, | 339 info.imageTiling, |
| 302 info.imageHorizontalAlignment, | 340 info.imageHorizontalAlignment, |
| 303 info.imageVerticalAlignment].join(' ').trim(); | 341 info.imageVerticalAlignment].join(' ').trim(); |
| 304 document.body.style.background = background; | 342 document.body.style.background = background; |
| 305 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo); | 343 document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo); |
| 306 updateThemeAttribution(info.attributionUrl); | 344 updateThemeAttribution(info.attributionUrl); |
| 307 setCustomThemeStyle(info); | 345 setCustomThemeStyle(info); |
| 346 } |
| 308 | 347 |
| 348 |
| 349 /** |
| 350 * Updates the NTP based on the current theme, then rerenders all tiles. |
| 351 * @private |
| 352 */ |
| 353 function onThemeChange() { |
| 354 renderTheme(); |
| 309 tilesContainer.innerHTML = ''; | 355 tilesContainer.innerHTML = ''; |
| 310 renderAndShowTiles(); | 356 renderAndShowTiles(); |
| 311 } | 357 } |
| 312 | 358 |
| 313 | 359 |
| 314 /** | 360 /** |
| 315 * Updates the NTP style according to theme. | 361 * Updates the NTP style according to theme. |
| 316 * @param {Object=} opt_themeInfo The information about the theme. If it is | 362 * @param {Object=} opt_themeInfo The information about the theme. If it is |
| 317 * omitted the style will be reverted to the default. | 363 * omitted the style will be reverted to the default. |
| 318 * @private | 364 * @private |
| 319 */ | 365 */ |
| 320 function setCustomThemeStyle(opt_themeInfo) { | 366 function setCustomThemeStyle(opt_themeInfo) { |
| 321 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE); | 367 var customStyleElement = $(IDS.CUSTOM_THEME_STYLE); |
| 322 var head = document.head; | 368 var head = document.head; |
| 323 | 369 |
| 324 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) { | 370 if (opt_themeInfo && !opt_themeInfo.usingDefaultTheme) { |
| 371 ntpContents.classList.remove(CLASSES.DEFAULT_THEME); |
| 325 var themeStyle = | 372 var themeStyle = |
| 326 '#attribution {' + | 373 '#attribution {' + |
| 327 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + | 374 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + |
| 328 '}' + | 375 '}' + |
| 329 '#mv-msg {' + | 376 '#mv-msg {' + |
| 330 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' + | 377 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorRgba) + ';' + |
| 331 '}' + | 378 '}' + |
| 332 '#mv-notice-links span {' + | 379 '#mv-notice-links span {' + |
| 333 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + | 380 ' color: ' + convertToRGBAColor(opt_themeInfo.textColorLightRgba) + ';' + |
| 334 '}' + | 381 '}' + |
| 335 '#mv-notice-x {' + | 382 '#mv-notice-x {' + |
| 336 ' -webkit-filter: drop-shadow(0 0 0 ' + | 383 ' -webkit-filter: drop-shadow(0 0 0 ' + |
| 337 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' + | 384 convertToRGBAColor(opt_themeInfo.textColorRgba) + ');' + |
| 338 '}' + | 385 '}' + |
| 339 '.mv-page-ready {' + | 386 '.mv-page-ready .mv-mask {' + |
| 340 ' border: 1px solid ' + | 387 ' border: 1px solid ' + |
| 341 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' + | 388 convertToRGBAColor(opt_themeInfo.sectionBorderColorRgba) + ';' + |
| 342 '}' + | 389 '}' + |
| 343 '.mv-page-ready:hover, .mv-page-ready:focus {' + | 390 '.mv-page-ready:hover .mv-mask, .mv-page-ready:focus .mv-mask {' + |
| 344 ' border-color: ' + | 391 ' border-color: ' + |
| 345 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' + | 392 convertToRGBAColor(opt_themeInfo.headerColorRgba) + ';' + |
| 346 '}'; | 393 '}'; |
| 347 | 394 |
| 348 if (customStyleElement) { | 395 if (customStyleElement) { |
| 349 customStyleElement.textContent = themeStyle; | 396 customStyleElement.textContent = themeStyle; |
| 350 } else { | 397 } else { |
| 351 customStyleElement = document.createElement('style'); | 398 customStyleElement = document.createElement('style'); |
| 352 customStyleElement.type = 'text/css'; | 399 customStyleElement.type = 'text/css'; |
| 353 customStyleElement.id = IDS.CUSTOM_THEME_STYLE; | 400 customStyleElement.id = IDS.CUSTOM_THEME_STYLE; |
| 354 customStyleElement.textContent = themeStyle; | 401 customStyleElement.textContent = themeStyle; |
| 355 head.appendChild(customStyleElement); | 402 head.appendChild(customStyleElement); |
| 356 } | 403 } |
| 357 | 404 |
| 358 } else if (customStyleElement) { | 405 } else { |
| 359 head.removeChild(customStyleElement); | 406 ntpContents.classList.add(CLASSES.DEFAULT_THEME); |
| 407 if (customStyleElement) |
| 408 head.removeChild(customStyleElement); |
| 360 } | 409 } |
| 361 } | 410 } |
| 362 | 411 |
| 363 | 412 |
| 364 /** | 413 /** |
| 365 * Renders the attribution if the URL is present, otherwise hides it. | 414 * Renders the attribution if the URL is present, otherwise hides it. |
| 366 * @param {string} url The URL of the attribution image, if any. | 415 * @param {string} url The URL of the attribution image, if any. |
| 367 * @private | 416 * @private |
| 368 */ | 417 */ |
| 369 function updateThemeAttribution(url) { | 418 function updateThemeAttribution(url) { |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 405 color[3] / 255 + ')'; | 454 color[3] / 255 + ')'; |
| 406 } | 455 } |
| 407 | 456 |
| 408 | 457 |
| 409 /** | 458 /** |
| 410 * Handles a new set of Most Visited page data. | 459 * Handles a new set of Most Visited page data. |
| 411 */ | 460 */ |
| 412 function onMostVisitedChange() { | 461 function onMostVisitedChange() { |
| 413 if (isBlacklisting) { | 462 if (isBlacklisting) { |
| 414 // Trigger the blacklist animation, which then triggers reloadAllTiles(). | 463 // Trigger the blacklist animation, which then triggers reloadAllTiles(). |
| 415 var lastBlacklistedTileElement = lastBlacklistedTile.elem; | 464 var lastBlacklistedTileElem = lastBlacklistedTile.elem; |
| 416 lastBlacklistedTileElement.addEventListener( | 465 lastBlacklistedTileElem.addEventListener( |
| 417 'webkitTransitionEnd', blacklistAnimationDone); | 466 'webkitTransitionEnd', blacklistAnimationDone); |
| 418 lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST); | 467 lastBlacklistedTileElem.classList.add(CLASSES.BLACKLIST); |
| 419 } else { | 468 } else { |
| 420 reloadAllTiles(); | 469 reloadAllTiles(); |
| 421 } | 470 } |
| 422 } | 471 } |
| 423 | 472 |
| 424 | 473 |
| 425 /** | 474 /** |
| 426 * Handles the end of the blacklist animation by showing the notification and | 475 * Handles the end of the blacklist animation by showing the notification and |
| 427 * re-rendering the new set of tiles. | 476 * re-rendering the new set of tiles. |
| 428 */ | 477 */ |
| (...skipping 21 matching lines...) Expand all Loading... |
| 450 tiles.push(createTile(pages[i], i)); | 499 tiles.push(createTile(pages[i], i)); |
| 451 | 500 |
| 452 tilesContainer.innerHTML = ''; | 501 tilesContainer.innerHTML = ''; |
| 453 renderAndShowTiles(); | 502 renderAndShowTiles(); |
| 454 } | 503 } |
| 455 | 504 |
| 456 | 505 |
| 457 /** | 506 /** |
| 458 * Binds onload events for a tile's internal <iframe> elements. | 507 * Binds onload events for a tile's internal <iframe> elements. |
| 459 * @param {Tile} tile The main tile to bind events to. | 508 * @param {Tile} tile The main tile to bind events to. |
| 460 * @param {Barrier} tileVisibilityBarrier A barrier to make tile visible the | 509 * @param {Barrier} tileVisibilityBarrier A barrier to make all tiles visible |
| 461 * moment all tiles are loaded. | 510 * the moment all tiles are loaded. |
| 462 */ | 511 */ |
| 463 function bindTileOnloadEvents(tile, tileVisibilityBarrier) { | 512 function bindTileOnloadEvents(tile, tileVisibilityBarrier) { |
| 464 if (tile.titleElem) { | 513 if (tile.titleElem) { |
| 465 tileVisibilityBarrier.add(); | 514 tileVisibilityBarrier.add(); |
| 466 tile.titleElem.onload = function() { | 515 tile.titleElem.onload = function() { |
| 467 tile.titleElem.hidden = false; | |
| 468 tileVisibilityBarrier.remove(); | 516 tileVisibilityBarrier.remove(); |
| 469 }; | 517 }; |
| 470 } | 518 } |
| 471 | |
| 472 if (tile.thumbnailElem) { | 519 if (tile.thumbnailElem) { |
| 473 tileVisibilityBarrier.add(); | 520 tileVisibilityBarrier.add(); |
| 474 tile.thumbnailElem.onload = function() { | 521 tile.thumbnailElem.onload = function() { |
| 475 tile.thumbnailElem.hidden = false; | |
| 476 tile.elem.classList.add(CLASSES.PAGE_READY); | 522 tile.elem.classList.add(CLASSES.PAGE_READY); |
| 477 tileVisibilityBarrier.remove(); | 523 tileVisibilityBarrier.remove(); |
| 478 }; | 524 }; |
| 479 } | 525 } |
| 480 } | 526 } |
| 481 | 527 |
| 482 | 528 |
| 483 /** | 529 /** |
| 484 * Renders the current list of visible tiles to DOM, and hides tiles that are | 530 * Renders the current list of visible tiles to DOM, and hides tiles that are |
| 485 * already in the DOM but should not be seen. | 531 * already in the DOM but should not be seen. |
| 486 */ | 532 */ |
| 487 function renderAndShowTiles() { | 533 function renderAndShowTiles() { |
| 488 var numExisting = tilesContainer.querySelectorAll('.' + CLASSES.TILE).length; | 534 var numExisting = tilesContainer.querySelectorAll('.' + CLASSES.TILE).length; |
| 489 // Only add visible tiles to the DOM, to avoid creating invisible tiles that | 535 // Only add visible tiles to the DOM, to avoid creating invisible tiles that |
| 490 // produce meaningless impression metrics. However, if a tile becomes | 536 // produce meaningless impression metrics. However, if a tile becomes |
| 491 // invisible then we leave it in DOM to prevent reload if it's shown again. | 537 // invisible then we leave it in DOM to prevent reload if it's shown again. |
| 492 var numDesired = Math.min(tiles.length, numColumnsShown * NUM_ROWS); | 538 var numDesired = Math.min(tiles.length, numColumnsShown * NUM_ROWS); |
| 493 | 539 |
| 494 // If we need to render new tiles, manage the visibility to hide intermediate | 540 // If we need to render new tiles, manage the visibility to hide intermediate |
| 495 // load states of the <iframe>s. | 541 // load states of the <iframe>s. |
| 496 if (numExisting < numDesired) { | 542 if (numExisting < numDesired) { |
| 497 var tileVisibilityBarrier = new Barrier(function() { | 543 var showAll = function() { |
| 498 tilesContainer.style.visibility = 'visible'; | 544 for (var i = 0; i < numDesired; ++i) { |
| 499 }); | 545 if (tiles[i].titleElem || tiles[i].thumbnailElem) |
| 546 tiles[i].elem.classList.add(CLASSES.PAGE_READY); |
| 547 } |
| 548 }; |
| 549 var tileVisibilityBarrier = new Barrier(showAll); |
| 500 | 550 |
| 501 if (!userInitiatedMostVisitedChange) { | 551 if (!userInitiatedMostVisitedChange) { |
| 502 // Make titleContainer invisible, but still taking up space. | 552 // Make titleContainer invisible, but still taking up space. |
| 503 // titleContainer becomes visible again (1) on timeout, or (2) when all | 553 // titleContainer becomes visible again (1) on timeout, or (2) when all |
| 504 // tiles finish loading (using tileVisibilityBarrier). | 554 // tiles finish loading (using tileVisibilityBarrier). |
| 505 tilesContainer.style.visibility = 'hidden'; | |
| 506 window.setTimeout(function() { | 555 window.setTimeout(function() { |
| 507 tileVisibilityBarrier.cancel(); | 556 tileVisibilityBarrier.cancel(); |
| 508 tilesContainer.style.visibility = 'visible'; | 557 showAll(); |
| 509 }, MOST_VISITED_PAINT_TIMEOUT_MSEC); | 558 }, MOST_VISITED_PAINT_TIMEOUT_MSEC); |
| 510 } | 559 } |
| 511 userInitiatedMostVisitedChange = false; | 560 userInitiatedMostVisitedChange = false; |
| 512 | 561 |
| 513 for (var i = numExisting; i < numDesired; ++i) { | 562 for (var i = numExisting; i < numDesired; ++i) { |
| 514 bindTileOnloadEvents(tiles[i], tileVisibilityBarrier); | 563 bindTileOnloadEvents(tiles[i], tileVisibilityBarrier); |
| 515 tilesContainer.appendChild(tiles[i].elem); | 564 tilesContainer.appendChild(tiles[i].elem); |
| 516 } | 565 } |
| 517 } | 566 } |
| 518 | 567 |
| 519 // Show only the desired tiles. Not using .hidden because it does not work for | 568 // Show only the desired tiles. Note that .hidden does not work for |
| 520 // inline-block elements. | 569 // inline-block elements like tiles[i].elem. |
| 521 for (var i = 0; i < numDesired; ++i) | 570 for (var i = 0; i < numDesired; ++i) |
| 522 tiles[i].elem.style.display = 'inline-block'; | 571 tiles[i].elem.style.display = 'inline-block'; |
| 523 // If |numDesired| < |numExisting| then hide extra tiles (e.g., this occurs | 572 // If |numDesired| < |numExisting| then hide extra tiles (e.g., this occurs |
| 524 // when window is downsized). | 573 // when window is downsized). |
| 525 for (; i < numExisting; ++i) | 574 for (; i < numExisting; ++i) |
| 526 tiles[i].elem.style.display = 'none'; | 575 tiles[i].elem.style.display = 'none'; |
| 527 } | 576 } |
| 528 | 577 |
| 529 | 578 |
| 530 /** | 579 /** |
| 531 * Builds a URL to display a most visited tile title in an iframe. | 580 * Builds a URL to display a most visited tile title in an iframe. |
| 532 * @param {number} rid The restricted ID. | 581 * @param {number} rid The restricted ID. |
| 533 * @param {number} position The position of the iframe in the UI. | 582 * @param {number} position The position of the iframe in the UI. |
| 534 * @return {string} An URL to display the most visited title in an iframe. | 583 * @return {string} An URL to display the most visited title in an iframe. |
| 535 */ | 584 */ |
| 536 function getMostVisitedTitleIframeUrl(rid, position) { | 585 function getMostVisitedTitleIframeUrl(rid, position) { |
| 537 var url = 'chrome-search://most-visited/' + | 586 var url = 'chrome-search://most-visited/' + |
| 538 encodeURIComponent(MOST_VISITED_TITLE_IFRAME); | 587 encodeURIComponent(MOST_VISITED_TITLE_IFRAME); |
| 588 var titleColor = isBackgroundDark ? NTP_DESIGN.titleColorAgainstDark : |
| 589 NTP_DESIGN.titleColor; |
| 539 var params = [ | 590 var params = [ |
| 540 'rid=' + encodeURIComponent(rid), | 591 'rid=' + encodeURIComponent(rid), |
| 541 'f=' + encodeURIComponent(NTP_DESIGN.fontFamily), | 592 'f=' + encodeURIComponent(NTP_DESIGN.fontFamily), |
| 542 'fs=' + encodeURIComponent(NTP_DESIGN.fontSize), | 593 'fs=' + encodeURIComponent(NTP_DESIGN.fontSize), |
| 543 'c=' + encodeURIComponent(NTP_DESIGN.titleColor), | 594 'c=' + encodeURIComponent(titleColor), |
| 544 'pos=' + encodeURIComponent(position)]; | 595 'pos=' + encodeURIComponent(position)]; |
| 545 if (NTP_DESIGN.titleTextAlign) | 596 if (NTP_DESIGN.titleTextAlign) |
| 546 params.push('ta=' + encodeURIComponent(NTP_DESIGN.titleTextAlign)); | 597 params.push('ta=' + encodeURIComponent(NTP_DESIGN.titleTextAlign)); |
| 547 if (NTP_DESIGN.titleTextFade) | 598 if (NTP_DESIGN.titleTextFade) |
| 548 params.push('tf=' + encodeURIComponent(NTP_DESIGN.titleTextFade)); | 599 params.push('tf=' + encodeURIComponent(NTP_DESIGN.titleTextFade)); |
| 549 return url + '?' + params.join('&'); | 600 return url + '?' + params.join('&'); |
| 550 } | 601 } |
| 551 | 602 |
| 552 | 603 |
| 553 /** | 604 /** |
| 554 * Builds a URL to display a most visited tile thumbnail in an iframe. | 605 * Builds a URL to display a most visited tile thumbnail in an iframe. |
| 555 * @param {number} rid The restricted ID. | 606 * @param {number} rid The restricted ID. |
| 556 * @param {number} position The position of the iframe in the UI. | 607 * @param {number} position The position of the iframe in the UI. |
| 557 * @return {string} An URL to display the most visited thumbnail in an iframe. | 608 * @return {string} An URL to display the most visited thumbnail in an iframe. |
| 558 */ | 609 */ |
| 559 function getMostVisitedThumbnailIframeUrl(rid, position) { | 610 function getMostVisitedThumbnailIframeUrl(rid, position) { |
| 560 var url = 'chrome-search://most-visited/' + | 611 var url = 'chrome-search://most-visited/' + |
| 561 encodeURIComponent(MOST_VISITED_THUMBNAIL_IFRAME); | 612 encodeURIComponent(MOST_VISITED_THUMBNAIL_IFRAME); |
| 562 var params = [ | 613 var params = [ |
| 563 'rid=' + encodeURIComponent(rid), | 614 'rid=' + encodeURIComponent(rid), |
| 564 'f=' + encodeURIComponent(NTP_DESIGN.fontFamily), | 615 'f=' + encodeURIComponent(NTP_DESIGN.fontFamily), |
| 565 'fs=' + encodeURIComponent(NTP_DESIGN.fontSize), | 616 'fs=' + encodeURIComponent(NTP_DESIGN.fontSize), |
| 566 'c=' + encodeURIComponent(NTP_DESIGN.thumbnailTextColor), | 617 'c=' + encodeURIComponent(NTP_DESIGN.thumbnailTextColor), |
| 567 'pos=' + encodeURIComponent(position)]; | 618 'pos=' + encodeURIComponent(position)]; |
| 619 if (NTP_DESIGN.thumbnailFallback) |
| 620 params.push('etfb=1'); |
| 568 return url + '?' + params.join('&'); | 621 return url + '?' + params.join('&'); |
| 569 } | 622 } |
| 570 | 623 |
| 571 | 624 |
| 572 /** | 625 /** |
| 573 * Creates a Tile with the specified page data. If no data is provided, a | 626 * Creates a Tile with the specified page data. If no data is provided, a |
| 574 * filler Tile is created. | 627 * filler Tile is created. |
| 575 * @param {Object} page The page data. | 628 * @param {Object} page The page data. |
| 576 * @param {number} position The position of the tile. | 629 * @param {number} position The position of the tile. |
| 577 * @return {Tile} The new Tile. | 630 * @return {Tile} The new Tile. |
| 578 */ | 631 */ |
| 579 function createTile(page, position) { | 632 function createTile(page, position) { |
| 580 var tileElement = document.createElement('div'); | 633 var tileElem = document.createElement('div'); |
| 581 tileElement.classList.add(CLASSES.TILE); | 634 tileElem.classList.add(CLASSES.TILE); |
| 635 var innerElem = createAndAppendElement(tileElem, 'div', CLASSES.TILE_INNER); |
| 582 | 636 |
| 583 if (page) { | 637 if (page) { |
| 584 var rid = page.rid; | 638 var rid = page.rid; |
| 585 tileElement.classList.add(CLASSES.PAGE); | 639 tileElem.classList.add(CLASSES.PAGE); |
| 586 | 640 |
| 587 var navigateFunction = function(e) { | 641 var navigateFunction = function(e) { |
| 588 e.preventDefault(); | 642 e.preventDefault(); |
| 589 ntpApiHandle.navigateContentWindow(rid, getDispositionFromEvent(e)); | 643 ntpApiHandle.navigateContentWindow(rid, getDispositionFromEvent(e)); |
| 590 }; | 644 }; |
| 591 | 645 |
| 592 // The click handler for navigating to the page identified by the RID. | 646 // The click handler for navigating to the page identified by the RID. |
| 593 tileElement.addEventListener('click', navigateFunction); | 647 tileElem.addEventListener('click', navigateFunction); |
| 594 | 648 |
| 595 // Make thumbnails tab-accessible. | 649 // Make thumbnails tab-accessible. |
| 596 tileElement.setAttribute('tabindex', '1'); | 650 tileElem.setAttribute('tabindex', '1'); |
| 597 registerKeyHandler(tileElement, KEYCODE.ENTER, navigateFunction); | 651 registerKeyHandler(tileElem, KEYCODE.ENTER, navigateFunction); |
| 598 | 652 |
| 599 // The iframe which renders the page title. | 653 // The iframe which renders the page title. |
| 600 var titleElement = document.createElement('iframe'); | 654 var titleElem = document.createElement('iframe'); |
| 601 titleElement.tabIndex = '-1'; | 655 titleElem.tabIndex = '-1'; |
| 602 | 656 |
| 603 // Why iframes have IDs: | 657 // Why iframes have IDs: |
| 604 // | 658 // |
| 605 // On navigating back to the NTP we see several onmostvisitedchange() events | 659 // On navigating back to the NTP we see several onmostvisitedchange() events |
| 606 // in series with incrementing RIDs. After the first event, a set of iframes | 660 // in series with incrementing RIDs. After the first event, a set of iframes |
| 607 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get | 661 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get |
| 608 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1. | 662 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1. |
| 609 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the | 663 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the |
| 610 // first set of iframes into the most recent set of iframes. | 664 // first set of iframes into the most recent set of iframes. |
| 611 // | 665 // |
| 612 // Giving iframes distinct ids seems to cause some invalidation and prevent | 666 // Giving iframes distinct ids seems to cause some invalidation and prevent |
| 613 // associating the incorrect data. | 667 // associating the incorrect data. |
| 614 // | 668 // |
| 615 // TODO(jered): Find and fix the root (probably Blink) bug. | 669 // TODO(jered): Find and fix the root (probably Blink) bug. |
| 616 | 670 |
| 617 // Keep this ID here. See comment above. | 671 // Keep this ID here. See comment above. |
| 618 titleElement.id = 'title-' + rid; | 672 titleElem.id = 'title-' + rid; |
| 619 titleElement.className = CLASSES.TITLE; | 673 titleElem.className = CLASSES.TITLE; |
| 620 titleElement.hidden = true; | 674 titleElem.src = getMostVisitedTitleIframeUrl(rid, position); |
| 621 titleElement.src = getMostVisitedTitleIframeUrl(rid, position); | 675 innerElem.appendChild(titleElem); |
| 622 tileElement.appendChild(titleElement); | 676 |
| 677 // A fallback element for missing thumbnails. |
| 678 if (NTP_DESIGN.thumbnailFallback) { |
| 679 var fallbackElem = createAndAppendElement( |
| 680 innerElem, 'div', CLASSES.THUMBNAIL_FALLBACK); |
| 681 if (NTP_DESIGN.thumbnailFallback === THUMBNAIL_FALLBACK.DOT) |
| 682 createAndAppendElement(fallbackElem, 'div', CLASSES.DOT); |
| 683 } |
| 623 | 684 |
| 624 // The iframe which renders either a thumbnail or domain element. | 685 // The iframe which renders either a thumbnail or domain element. |
| 625 var thumbnailElement = document.createElement('iframe'); | 686 var thumbnailElem = document.createElement('iframe'); |
| 626 thumbnailElement.tabIndex = '-1'; | 687 thumbnailElem.tabIndex = '-1'; |
| 627 // Keep this ID here. See comment above. | 688 // Keep this ID here. See comment above. |
| 628 thumbnailElement.id = 'thumb-' + rid; | 689 thumbnailElem.id = 'thumb-' + rid; |
| 629 thumbnailElement.className = CLASSES.THUMBNAIL; | 690 thumbnailElem.className = CLASSES.THUMBNAIL; |
| 630 thumbnailElement.hidden = true; | 691 thumbnailElem.src = getMostVisitedThumbnailIframeUrl(rid, position); |
| 631 thumbnailElement.src = getMostVisitedThumbnailIframeUrl(rid, position); | 692 innerElem.appendChild(thumbnailElem); |
| 632 tileElement.appendChild(thumbnailElement); | |
| 633 | |
| 634 // A mask to darken the thumbnail on focus. | |
| 635 var maskElement = createAndAppendElement( | |
| 636 tileElement, 'div', CLASSES.THUMBNAIL_MASK); | |
| 637 | 693 |
| 638 // The button used to blacklist this page. | 694 // The button used to blacklist this page. |
| 639 var blacklistButton = createAndAppendElement( | 695 var blacklistButton = createAndAppendElement( |
| 640 tileElement, 'div', CLASSES.BLACKLIST_BUTTON); | 696 innerElem, 'div', CLASSES.BLACKLIST_BUTTON); |
| 641 var blacklistFunction = generateBlacklistFunction(rid); | 697 var blacklistFunction = generateBlacklistFunction(rid); |
| 642 blacklistButton.addEventListener('click', blacklistFunction); | 698 blacklistButton.addEventListener('click', blacklistFunction); |
| 643 blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip; | 699 blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip; |
| 644 | 700 |
| 701 // A helper mask on top of the tile that is used to create hover border |
| 702 // and/or to darken the thumbnail on focus. |
| 703 var maskElement = createAndAppendElement( |
| 704 innerElem, 'div', CLASSES.THUMBNAIL_MASK); |
| 705 |
| 645 // When a tile is focused, have delete also blacklist the page. | 706 // When a tile is focused, have delete also blacklist the page. |
| 646 registerKeyHandler(tileElement, KEYCODE.DELETE, blacklistFunction); | 707 registerKeyHandler(tileElem, KEYCODE.DELETE, blacklistFunction); |
| 647 | 708 |
| 648 // The page favicon, if any. | 709 // The page favicon, or a fallback. |
| 649 var faviconUrl = page.faviconUrl; | 710 var favicon = createAndAppendElement(innerElem, 'div', CLASSES.FAVICON); |
| 650 if (faviconUrl) { | 711 if (page.faviconUrl) { |
| 651 var favicon = createAndAppendElement(tileElement, 'div', CLASSES.FAVICON); | 712 favicon.style.backgroundImage = 'url(' + page.faviconUrl + ')'; |
| 652 favicon.style.backgroundImage = 'url(' + faviconUrl + ')'; | 713 } else { |
| 714 favicon.classList.add(CLASSES.FAVICON_FALLBACK); |
| 653 } | 715 } |
| 654 return new Tile(tileElement, titleElement, thumbnailElement, rid); | 716 return new Tile(tileElem, innerElem, titleElem, thumbnailElem, rid); |
| 655 } else { | 717 } else { |
| 656 return new Tile(tileElement); | 718 return new Tile(tileElem); |
| 657 } | 719 } |
| 658 } | 720 } |
| 659 | 721 |
| 660 | 722 |
| 661 /** | 723 /** |
| 662 * Generates a function to be called when the page with the corresponding RID | 724 * Generates a function to be called when the page with the corresponding RID |
| 663 * is blacklisted. | 725 * is blacklisted. |
| 664 * @param {number} rid The RID of the page being blacklisted. | 726 * @param {number} rid The RID of the page being blacklisted. |
| 665 * @return {function(Event)} A function which handles the blacklisting of the | 727 * @return {function(Event)} A function which handles the blacklisting of the |
| 666 * page by updating state variables and notifying Chrome. | 728 * page by updating state variables and notifying Chrome. |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 740 if (newNumColumns < MIN_NUM_COLUMNS) | 802 if (newNumColumns < MIN_NUM_COLUMNS) |
| 741 newNumColumns = MIN_NUM_COLUMNS; | 803 newNumColumns = MIN_NUM_COLUMNS; |
| 742 else if (newNumColumns > MAX_NUM_COLUMNS) | 804 else if (newNumColumns > MAX_NUM_COLUMNS) |
| 743 newNumColumns = MAX_NUM_COLUMNS; | 805 newNumColumns = MAX_NUM_COLUMNS; |
| 744 | 806 |
| 745 if (numColumnsShown != newNumColumns) { | 807 if (numColumnsShown != newNumColumns) { |
| 746 numColumnsShown = newNumColumns; | 808 numColumnsShown = newNumColumns; |
| 747 var tilesContainerWidth = numColumnsShown * tileRequiredWidth; | 809 var tilesContainerWidth = numColumnsShown * tileRequiredWidth; |
| 748 tilesContainer.style.width = tilesContainerWidth + 'px'; | 810 tilesContainer.style.width = tilesContainerWidth + 'px'; |
| 749 if (fakebox) { | 811 if (fakebox) { |
| 750 // -2 to account for border. | 812 fakebox.style.width = // -2 to account for border. |
| 751 fakebox.style.width = | |
| 752 (tilesContainerWidth - NTP_DESIGN.tileMargin - 2) + 'px'; | 813 (tilesContainerWidth - NTP_DESIGN.tileMargin - 2) + 'px'; |
| 753 } | 814 } |
| 754 // Render without clearing tiles. | 815 // Render without clearing tiles. |
| 755 renderAndShowTiles(); | 816 renderAndShowTiles(); |
| 756 } | 817 } |
| 757 } | 818 } |
| 758 | 819 |
| 759 | 820 |
| 760 /** | 821 /** |
| 761 * Returns the tile corresponding to the specified page RID. | 822 * Returns the tile corresponding to the specified page RID. |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 929 notification = $(IDS.NOTIFICATION); | 990 notification = $(IDS.NOTIFICATION); |
| 930 attribution = $(IDS.ATTRIBUTION); | 991 attribution = $(IDS.ATTRIBUTION); |
| 931 ntpContents = $(IDS.NTP_CONTENTS); | 992 ntpContents = $(IDS.NTP_CONTENTS); |
| 932 | 993 |
| 933 if (configData.isGooglePage) { | 994 if (configData.isGooglePage) { |
| 934 var logo = document.createElement('div'); | 995 var logo = document.createElement('div'); |
| 935 logo.id = IDS.LOGO; | 996 logo.id = IDS.LOGO; |
| 936 | 997 |
| 937 fakebox = document.createElement('div'); | 998 fakebox = document.createElement('div'); |
| 938 fakebox.id = IDS.FAKEBOX; | 999 fakebox.id = IDS.FAKEBOX; |
| 939 fakebox.innerHTML = | 1000 var fakeboxHtml = []; |
| 940 '<input id="' + IDS.FAKEBOX_INPUT + | 1001 fakeboxHtml.push('<input id="' + IDS.FAKEBOX_INPUT + |
| 941 '" autocomplete="off" tabindex="-1" aria-hidden="true">' + | 1002 '" autocomplete="off" tabindex="-1" aria-hidden="true">'); |
| 942 '<div id="cursor"></div>'; | 1003 if (NTP_DESIGN.showFakeboxHint && configData.searchboxPlaceholder) { |
| 1004 fakeboxHtml.push('<div id="' + IDS.FAKEBOX_TEXT + '">' + |
| 1005 configData.searchboxPlaceholder + '</div>'); |
| 1006 } |
| 1007 fakeboxHtml.push('<div id="cursor"></div>'); |
| 1008 fakebox.innerHTML = fakeboxHtml.join(''); |
| 943 | 1009 |
| 944 ntpContents.insertBefore(fakebox, ntpContents.firstChild); | 1010 ntpContents.insertBefore(fakebox, ntpContents.firstChild); |
| 945 ntpContents.insertBefore(logo, ntpContents.firstChild); | 1011 ntpContents.insertBefore(logo, ntpContents.firstChild); |
| 946 } else { | 1012 } else { |
| 947 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); | 1013 document.body.classList.add(CLASSES.NON_GOOGLE_PAGE); |
| 948 } | 1014 } |
| 949 | 1015 |
| 950 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE); | 1016 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE); |
| 951 notificationMessage.textContent = | 1017 notificationMessage.textContent = |
| 952 configData.translatedStrings.thumbnailRemovedNotification; | 1018 configData.translatedStrings.thumbnailRemovedNotification; |
| (...skipping 23 matching lines...) Expand all Loading... |
| 976 ntpApiHandle = topLevelHandle.newTabPage; | 1042 ntpApiHandle = topLevelHandle.newTabPage; |
| 977 ntpApiHandle.onthemechange = onThemeChange; | 1043 ntpApiHandle.onthemechange = onThemeChange; |
| 978 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; | 1044 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; |
| 979 | 1045 |
| 980 ntpApiHandle.oninputstart = onInputStart; | 1046 ntpApiHandle.oninputstart = onInputStart; |
| 981 ntpApiHandle.oninputcancel = restoreNtp; | 1047 ntpApiHandle.oninputcancel = restoreNtp; |
| 982 | 1048 |
| 983 if (ntpApiHandle.isInputInProgress) | 1049 if (ntpApiHandle.isInputInProgress) |
| 984 onInputStart(); | 1050 onInputStart(); |
| 985 | 1051 |
| 986 onThemeChange(); | 1052 renderTheme(); |
| 987 onMostVisitedChange(); | 1053 onMostVisitedChange(); |
| 988 | 1054 |
| 989 searchboxApiHandle = topLevelHandle.searchBox; | 1055 searchboxApiHandle = topLevelHandle.searchBox; |
| 990 | 1056 |
| 991 if (fakebox) { | 1057 if (fakebox) { |
| 992 // Listener for updating the key capture state. | 1058 // Listener for updating the key capture state. |
| 993 document.body.onmousedown = function(event) { | 1059 document.body.onmousedown = function(event) { |
| 994 if (isFakeboxClick(event)) | 1060 if (isFakeboxClick(event)) |
| 995 searchboxApiHandle.startCapturingKeyStrokes(); | 1061 searchboxApiHandle.startCapturingKeyStrokes(); |
| 996 else if (isFakeboxFocused()) | 1062 else if (isFakeboxFocused()) |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1043 | 1109 |
| 1044 return { | 1110 return { |
| 1045 init: init, | 1111 init: init, |
| 1046 listen: listen | 1112 listen: listen |
| 1047 }; | 1113 }; |
| 1048 } | 1114 } |
| 1049 | 1115 |
| 1050 if (!window.localNTPUnitTest) { | 1116 if (!window.localNTPUnitTest) { |
| 1051 LocalNTP().listen(); | 1117 LocalNTP().listen(); |
| 1052 } | 1118 } |
| OLD | NEW |