| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import './strings.m.js'; |
| import './middle_slot_promo.js'; |
| import './most_visited.js'; |
| import './customize_dialog.js'; |
| import './voice_search_overlay.js'; |
| import './iframe.js'; |
| import './fakebox.js'; |
| import './realbox.js'; |
| import './logo.js'; |
| import './modules/module_wrapper.js'; |
| import './modules/modules.js'; // Registers module descriptors. |
| import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; |
| import 'chrome://resources/cr_elements/shared_style_css.m.js'; |
| |
| import {assert} from 'chrome://resources/js/assert.m.js'; |
| import {hexColorToSkColor, skColorToRgba} from 'chrome://resources/js/color_utils.js'; |
| import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js'; |
| import {EventTracker} from 'chrome://resources/js/event_tracker.m.js'; |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; |
| import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {BackgroundManager} from './background_manager.js'; |
| import {BrowserProxy} from './browser_proxy.js'; |
| import {BackgroundSelection, BackgroundSelectionType} from './customize_dialog.js'; |
| import {ModuleDescriptor} from './modules/module_descriptor.js'; |
| import {ModuleRegistry} from './modules/module_registry.js'; |
| import {oneGoogleBarApi} from './one_google_bar_api.js'; |
| import {PromoBrowserCommandProxy} from './promo_browser_command_proxy.js'; |
| import {$$} from './utils.js'; |
| |
| /** |
| * @typedef {{ |
| * commandId: promoBrowserCommand.mojom.Command<number>, |
| * clickInfo: !promoBrowserCommand.mojom.ClickInfo |
| * }} |
| */ |
| let CommandData; |
| |
| class AppElement extends PolymerElement { |
| static get is() { |
| return 'ntp-app'; |
| } |
| |
| static get template() { |
| return html`{__html_template__}`; |
| } |
| |
| static get properties() { |
| return { |
| /** @private */ |
| iframeOneGoogleBarEnabled_: { |
| type: Boolean, |
| value: () => { |
| const params = new URLSearchParams(window.location.search); |
| if (params.has('ogbinline')) { |
| return false; |
| } |
| return loadTimeData.getBoolean('iframeOneGoogleBarEnabled') || |
| params.has('ogbiframe'); |
| }, |
| reflectToAttribute: true, |
| }, |
| |
| /** @private */ |
| oneGoogleBarModalOverlaysEnabled_: { |
| type: Boolean, |
| value: () => |
| loadTimeData.getBoolean('oneGoogleBarModalOverlaysEnabled'), |
| }, |
| |
| /** @private */ |
| oneGoogleBarIframePath_: { |
| type: String, |
| value: () => { |
| const params = new URLSearchParams(); |
| params.set( |
| 'paramsencoded', |
| btoa(window.location.search.replace(/^[?]/, '&'))); |
| return `chrome-untrusted://new-tab-page/one-google-bar?${params}`; |
| }, |
| }, |
| |
| /** @private */ |
| oneGoogleBarLoaded_: { |
| observer: 'oneGoogleBarLoadedChange_', |
| type: Boolean, |
| value: false, |
| }, |
| |
| /** @private */ |
| oneGoogleBarDarkThemeEnabled_: { |
| type: Boolean, |
| computed: `computeOneGoogleBarDarkThemeEnabled_(oneGoogleBarLoaded_, |
| theme_, backgroundSelection_)`, |
| observer: 'onOneGoogleBarDarkThemeEnabledChange_', |
| }, |
| |
| /** @private */ |
| showIframedOneGoogleBar_: { |
| type: Boolean, |
| value: false, |
| computed: `computeShowIframedOneGoogleBar_(iframeOneGoogleBarEnabled_, |
| lazyRender_)`, |
| }, |
| |
| /** @private {!newTabPage.mojom.Theme} */ |
| theme_: { |
| observer: 'onThemeChange_', |
| type: Object, |
| }, |
| |
| /** @private */ |
| showCustomizeDialog_: Boolean, |
| |
| /** @private */ |
| showVoiceSearchOverlay_: Boolean, |
| |
| /** @private */ |
| showBackgroundImage_: { |
| computed: 'computeShowBackgroundImage_(theme_, backgroundSelection_)', |
| observer: 'onShowBackgroundImageChange_', |
| reflectToAttribute: true, |
| type: Boolean, |
| }, |
| |
| /** @private {!BackgroundSelection} */ |
| backgroundSelection_: { |
| type: Object, |
| value: () => ({type: BackgroundSelectionType.NO_SELECTION}), |
| observer: 'updateBackgroundImagePath_', |
| }, |
| |
| /** @private */ |
| backgroundImageAttribution1_: { |
| type: String, |
| computed: `computeBackgroundImageAttribution1_(theme_, |
| backgroundSelection_)`, |
| }, |
| |
| /** @private */ |
| backgroundImageAttribution2_: { |
| type: String, |
| computed: `computeBackgroundImageAttribution2_(theme_, |
| backgroundSelection_)`, |
| }, |
| |
| /** @private */ |
| backgroundImageAttributionUrl_: { |
| type: String, |
| computed: `computeBackgroundImageAttributionUrl_(theme_, |
| backgroundSelection_)`, |
| }, |
| |
| /** @private */ |
| doodleAllowed_: { |
| computed: 'computeDoodleAllowed_(showBackgroundImage_, theme_)', |
| type: Boolean, |
| }, |
| |
| /** @private {skia.mojom.SkColor} */ |
| backgroundColor_: { |
| computed: 'computeBackgroundColor_(showBackgroundImage_, theme_)', |
| type: Object, |
| }, |
| |
| /** @private */ |
| logoColor_: { |
| type: String, |
| computed: 'computeLogoColor_(theme_, backgroundSelection_)', |
| }, |
| |
| /** @private */ |
| singleColoredLogo_: { |
| computed: 'computeSingleColoredLogo_(theme_, backgroundSelection_)', |
| type: Boolean, |
| }, |
| |
| /** @private */ |
| realboxEnabled_: { |
| type: Boolean, |
| value: () => loadTimeData.getBoolean('realboxEnabled'), |
| }, |
| |
| /** @private */ |
| realboxShown_: { |
| type: Boolean, |
| computed: 'computeRealboxShown_(theme_)', |
| }, |
| |
| /** |
| * If true, renders additional elements that were not deemed crucial to |
| * to show up immediately on load. |
| * @private |
| */ |
| lazyRender_: Boolean, |
| |
| /** @private {!Array<!ModuleDescriptor>} */ |
| moduleDescriptors_: Object, |
| }; |
| } |
| |
| constructor() { |
| performance.mark('app-creation-start'); |
| super(); |
| /** @private {!newTabPage.mojom.PageCallbackRouter} */ |
| this.callbackRouter_ = BrowserProxy.getInstance().callbackRouter; |
| /** @private {newTabPage.mojom.PageHandlerRemote} */ |
| this.pageHandler_ = BrowserProxy.getInstance().handler; |
| /** @private {!BackgroundManager} */ |
| this.backgroundManager_ = BackgroundManager.getInstance(); |
| /** @private {?number} */ |
| this.setThemeListenerId_ = null; |
| /** @private {!EventTracker} */ |
| this.eventTracker_ = new EventTracker(); |
| this.loadOneGoogleBar_(); |
| /** @private {boolean} */ |
| this.shouldPrintPerformance_ = |
| new URLSearchParams(location.search).has('print_perf'); |
| /** |
| * Initialized with the start of the performance timeline in case the |
| * background image load is not triggered by JS. |
| * @private {number} |
| */ |
| this.backgroundImageLoadStartEpoch_ = performance.timeOrigin; |
| /** @private {number} */ |
| this.backgroundImageLoadStart_ = 0; |
| } |
| |
| /** @override */ |
| connectedCallback() { |
| super.connectedCallback(); |
| this.setThemeListenerId_ = |
| this.callbackRouter_.setTheme.addListener(theme => { |
| performance.measure('theme-set'); |
| this.theme_ = theme; |
| }); |
| this.eventTracker_.add(window, 'message', (event) => { |
| /** @type {!Object} */ |
| const data = event.data; |
| // Something in OneGoogleBar is sending a message that is received here. |
| // Need to ignore it. |
| if (typeof data !== 'object') { |
| return; |
| } |
| if ('frameType' in data && data.frameType === 'one-google-bar') { |
| this.handleOneGoogleBarMessage_(event); |
| } |
| }); |
| this.eventTracker_.add(window, 'keydown', e => this.onWindowKeydown_(e)); |
| if (this.shouldPrintPerformance_) { |
| // It is possible that the background image has already loaded by now. |
| // If it has, we request it to re-send the load time so that we can |
| // actually catch the load time. |
| this.backgroundManager_.getBackgroundImageLoadTime().then( |
| time => { |
| const duration = time - this.backgroundImageLoadStartEpoch_; |
| this.printPerformanceDatum_( |
| 'background-image-load', this.backgroundImageLoadStart_, |
| duration); |
| this.printPerformanceDatum_( |
| 'background-image-loaded', |
| this.backgroundImageLoadStart_ + duration); |
| }, |
| () => { |
| console.error('Failed to capture background image load time'); |
| }); |
| } |
| FocusOutlineManager.forDocument(document); |
| } |
| |
| /** @override */ |
| disconnectedCallback() { |
| super.disconnectedCallback(); |
| this.callbackRouter_.removeListener(assert(this.setThemeListenerId_)); |
| this.eventTracker_.removeAll(); |
| } |
| |
| /** @override */ |
| ready() { |
| super.ready(); |
| // Let the browser breath and then render remaining elements. |
| BrowserProxy.getInstance().waitForLazyRender().then(() => { |
| this.lazyRender_ = true; |
| }); |
| this.printPerformance_(); |
| performance.measure('app-creation', 'app-creation-start'); |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| computeOneGoogleBarDarkThemeEnabled_() { |
| if (!this.theme_ || !this.oneGoogleBarLoaded_) { |
| return false; |
| } |
| switch (this.backgroundSelection_.type) { |
| case BackgroundSelectionType.IMAGE: |
| return true; |
| case BackgroundSelectionType.NO_BACKGROUND: |
| case BackgroundSelectionType.DAILY_REFRESH: |
| case BackgroundSelectionType.NO_SELECTION: |
| default: |
| return this.theme_.isDark; |
| } |
| } |
| |
| /** |
| * @return {!Promise} |
| * @private |
| */ |
| async loadOneGoogleBar_() { |
| if (this.iframeOneGoogleBarEnabled_) { |
| const oneGoogleBar = document.querySelector('#oneGoogleBar'); |
| oneGoogleBar.remove(); |
| return; |
| } |
| |
| const {parts} = await this.pageHandler_.getOneGoogleBarParts( |
| window.location.search.replace(/^[?]/, '&')); |
| if (!parts) { |
| return; |
| } |
| |
| const inHeadStyle = document.createElement('style'); |
| inHeadStyle.type = 'text/css'; |
| inHeadStyle.appendChild(document.createTextNode(parts.inHeadStyle)); |
| document.head.appendChild(inHeadStyle); |
| |
| const inHeadScript = document.createElement('script'); |
| inHeadScript.type = 'text/javascript'; |
| inHeadScript.appendChild(document.createTextNode(parts.inHeadScript)); |
| document.head.appendChild(inHeadScript); |
| |
| this.oneGoogleBarLoaded_ = true; |
| const oneGoogleBar = document.querySelector('#oneGoogleBar'); |
| oneGoogleBar.innerHTML = parts.barHtml; |
| |
| const afterBarScript = document.createElement('script'); |
| afterBarScript.type = 'text/javascript'; |
| afterBarScript.appendChild(document.createTextNode(parts.afterBarScript)); |
| oneGoogleBar.parentNode.insertBefore( |
| afterBarScript, oneGoogleBar.nextSibling); |
| |
| document.querySelector('#oneGoogleBarEndOfBody').innerHTML = |
| parts.endOfBodyHtml; |
| |
| const endOfBodyScript = document.createElement('script'); |
| endOfBodyScript.type = 'text/javascript'; |
| endOfBodyScript.appendChild(document.createTextNode(parts.endOfBodyScript)); |
| document.body.appendChild(endOfBodyScript); |
| |
| this.pageHandler_.onOneGoogleBarRendered(BrowserProxy.getInstance().now()); |
| oneGoogleBarApi.trackDarkModeChanges(); |
| } |
| |
| /** @private */ |
| onOneGoogleBarDarkThemeEnabledChange_() { |
| if (!this.oneGoogleBarLoaded_) { |
| return; |
| } |
| if (this.iframeOneGoogleBarEnabled_) { |
| $$(this, '#oneGoogleBar').postMessage({ |
| type: 'enableDarkTheme', |
| enabled: this.oneGoogleBarDarkThemeEnabled_, |
| }); |
| return; |
| } |
| oneGoogleBarApi.setForegroundLight(this.oneGoogleBarDarkThemeEnabled_); |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| computeShowIframedOneGoogleBar_() { |
| return this.iframeOneGoogleBarEnabled_ && this.lazyRender_; |
| } |
| |
| /** |
| * @return {string} |
| * @private |
| */ |
| computeBackgroundImageAttribution1_() { |
| switch (this.backgroundSelection_.type) { |
| case BackgroundSelectionType.NO_SELECTION: |
| return this.theme_ && this.theme_.backgroundImageAttribution1 || ''; |
| case BackgroundSelectionType.IMAGE: |
| return this.backgroundSelection_.image.attribution1; |
| case BackgroundSelectionType.NO_BACKGROUND: |
| case BackgroundSelectionType.DAILY_REFRESH: |
| default: |
| return ''; |
| } |
| } |
| |
| /** |
| * @return {string} |
| * @private |
| */ |
| computeBackgroundImageAttribution2_() { |
| switch (this.backgroundSelection_.type) { |
| case BackgroundSelectionType.NO_SELECTION: |
| return this.theme_ && this.theme_.backgroundImageAttribution2 || ''; |
| case BackgroundSelectionType.IMAGE: |
| return this.backgroundSelection_.image.attribution2; |
| case BackgroundSelectionType.NO_BACKGROUND: |
| case BackgroundSelectionType.DAILY_REFRESH: |
| default: |
| return ''; |
| } |
| } |
| |
| /** |
| * @return {string} |
| * @private |
| */ |
| computeBackgroundImageAttributionUrl_() { |
| switch (this.backgroundSelection_.type) { |
| case BackgroundSelectionType.NO_SELECTION: |
| return this.theme_ && this.theme_.backgroundImageAttributionUrl ? |
| this.theme_.backgroundImageAttributionUrl.url : |
| ''; |
| case BackgroundSelectionType.IMAGE: |
| return this.backgroundSelection_.image.attributionUrl.url; |
| case BackgroundSelectionType.NO_BACKGROUND: |
| case BackgroundSelectionType.DAILY_REFRESH: |
| default: |
| return ''; |
| } |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| computeRealboxShown_() { |
| // If realbox is to match the Omnibox's theme, keep it hidden until the |
| // theme arrives. Otherwise mismatching colors will cause flicker. |
| return !loadTimeData.getBoolean('realboxMatchOmniboxTheme') || |
| !!this.theme_; |
| } |
| |
| /** @private */ |
| async onLazyRendered_() { |
| if (!loadTimeData.getBoolean('modulesEnabled')) { |
| return; |
| } |
| this.moduleDescriptors_ = |
| await ModuleRegistry.getInstance().initializeModules(); |
| } |
| |
| /** @private */ |
| onOpenVoiceSearch_() { |
| this.showVoiceSearchOverlay_ = true; |
| this.pageHandler_.onVoiceSearchAction( |
| newTabPage.mojom.VoiceSearchAction.kActivateSearchBox); |
| } |
| |
| /** @private */ |
| onCustomizeClick_() { |
| this.showCustomizeDialog_ = true; |
| } |
| |
| /** @private */ |
| onCustomizeDialogClose_() { |
| this.showCustomizeDialog_ = false; |
| } |
| |
| /** @private */ |
| onVoiceSearchOverlayClose_() { |
| this.showVoiceSearchOverlay_ = false; |
| } |
| |
| /** |
| * Handles <CTRL> + <SHIFT> + <.> (also <CMD> + <SHIFT> + <.> on mac) to open |
| * voice search. |
| * @param {KeyboardEvent} e |
| * @private |
| */ |
| onWindowKeydown_(e) { |
| let ctrlKeyPressed = e.ctrlKey; |
| // <if expr="is_macosx"> |
| ctrlKeyPressed = ctrlKeyPressed || e.metaKey; |
| // </if> |
| if (ctrlKeyPressed && e.code === 'Period' && e.shiftKey) { |
| this.showVoiceSearchOverlay_ = true; |
| this.pageHandler_.onVoiceSearchAction( |
| newTabPage.mojom.VoiceSearchAction.kActivateKeyboard); |
| } |
| } |
| |
| /** |
| * @param {skia.mojom.SkColor} skColor |
| * @return {string} |
| * @private |
| */ |
| rgbaOrInherit_(skColor) { |
| return skColor ? skColorToRgba(skColor) : 'inherit'; |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| computeShowBackgroundImage_() { |
| switch (this.backgroundSelection_.type) { |
| case BackgroundSelectionType.NO_SELECTION: |
| return !!this.theme_ && !!this.theme_.backgroundImage; |
| case BackgroundSelectionType.IMAGE: |
| return true; |
| case BackgroundSelectionType.NO_BACKGROUND: |
| case BackgroundSelectionType.DAILY_REFRESH: |
| default: |
| return false; |
| } |
| } |
| |
| /** @private */ |
| onShowBackgroundImageChange_() { |
| this.backgroundManager_.setShowBackgroundImage(this.showBackgroundImage_); |
| } |
| |
| /** @private */ |
| onThemeChange_() { |
| if (this.theme_) { |
| this.backgroundManager_.setBackgroundColor(this.theme_.backgroundColor); |
| } |
| this.updateBackgroundImagePath_(); |
| } |
| |
| /** |
| * Set the #backgroundImage |path| only when different and non-empty. Reset |
| * the customize dialog background selection if the dialog is closed. |
| * |
| * The ntp-untrusted-iframe |path| is set directly. When using a data binding |
| * instead, the quick updates to the |path| result in iframe loading an error |
| * page. |
| * @private |
| */ |
| updateBackgroundImagePath_() { |
| // The |backgroundSelection_| is retained after the dialog commits the |
| // change to the theme. Since |backgroundSelection_| has precedence over |
| // the theme background, the |backgroundSelection_| needs to be reset when |
| // the theme is updated. This is only necessary when the dialog is closed. |
| // If the dialog is open, it will either commit the |backgroundSelection_| |
| // or reset |backgroundSelection_| on cancel. |
| // |
| // Update after background image path is updated so the image is not shown |
| // before the path is updated. |
| if (!this.showCustomizeDialog_ && |
| this.backgroundSelection_.type !== |
| BackgroundSelectionType.NO_SELECTION) { |
| // Wait when local image is selected, then no background is previewed |
| // followed by selecting a new local image. This avoids a flicker. The |
| // iframe with the old image is shown briefly before it navigates to a new |
| // iframe location, then fetches and renders the new local image. |
| if (this.backgroundSelection_.type === |
| BackgroundSelectionType.NO_BACKGROUND) { |
| setTimeout(() => { |
| this.backgroundSelection_ = { |
| type: BackgroundSelectionType.NO_SELECTION |
| }; |
| }, 100); |
| } else { |
| this.backgroundSelection_ = { |
| type: BackgroundSelectionType.NO_SELECTION |
| }; |
| } |
| } |
| /** @type {newTabPage.mojom.BackgroundImage|undefined} */ |
| let backgroundImage; |
| switch (this.backgroundSelection_.type) { |
| case BackgroundSelectionType.NO_SELECTION: |
| backgroundImage = this.theme_ && this.theme_.backgroundImage; |
| break; |
| case BackgroundSelectionType.IMAGE: |
| backgroundImage = { |
| url: {url: this.backgroundSelection_.image.imageUrl.url} |
| }; |
| break; |
| } |
| if (backgroundImage) { |
| this.backgroundManager_.setBackgroundImage(backgroundImage); |
| } |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| computeDoodleAllowed_() { |
| return loadTimeData.getBoolean('themeModeDoodlesEnabled') || |
| !this.showBackgroundImage_ && this.theme_ && this.theme_.isDefault && |
| !this.theme_.isDark; |
| } |
| |
| /** |
| * @return {skia.mojom.SkColor} |
| * @private |
| */ |
| computeBackgroundColor_() { |
| if (this.showBackgroundImage_) { |
| return null; |
| } |
| return this.theme_ && this.theme_.backgroundColor; |
| } |
| |
| /** |
| * @return {skia.mojom.SkColor} |
| * @private |
| */ |
| computeLogoColor_() { |
| switch (this.backgroundSelection_.type) { |
| case BackgroundSelectionType.IMAGE: |
| return hexColorToSkColor('#ffffff'); |
| case BackgroundSelectionType.NO_SELECTION: |
| case BackgroundSelectionType.NO_BACKGROUND: |
| case BackgroundSelectionType.DAILY_REFRESH: |
| default: |
| return this.theme_ && |
| (this.theme_.logoColor || |
| (this.theme_.isDark ? hexColorToSkColor('#ffffff') : null)); |
| } |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| computeSingleColoredLogo_() { |
| switch (this.backgroundSelection_.type) { |
| case BackgroundSelectionType.IMAGE: |
| return true; |
| case BackgroundSelectionType.DAILY_REFRESH: |
| case BackgroundSelectionType.NO_BACKGROUND: |
| case BackgroundSelectionType.NO_SELECTION: |
| default: |
| return this.theme_ && (!!this.theme_.logoColor || this.theme_.isDark); |
| } |
| } |
| |
| /** |
| * Sends the command and the accompanying mouse click info received from the |
| * promo of the given source and origin to the browser. Relays the execution |
| * status response back to the source promo frame. |commandSource| and |
| * |commandOrigin| are used only to send the execution status response back to |
| * the source promo frame and should not be used for anything else. |
| * @param {!CommandData} commandData Command and mouse click info. |
| * @param {Window} commandSource Source promo frame. |
| * @param {string} commandOrigin Origin of the source promo frame. |
| * @private |
| */ |
| executePromoBrowserCommand_(commandData, commandSource, commandOrigin) { |
| // Make sure we don't send unsupported commands to the browser. |
| /** @type {!promoBrowserCommand.mojom.Command} */ |
| const commandId = Object.values(promoBrowserCommand.mojom.Command) |
| .includes(commandData.commandId) ? |
| commandData.commandId : |
| promoBrowserCommand.mojom.Command.kUnknownCommand; |
| |
| PromoBrowserCommandProxy.getInstance() |
| .handler.executeCommand(commandId, commandData.clickInfo) |
| .then(({commandExecuted}) => { |
| commandSource.postMessage(commandExecuted, commandOrigin); |
| }); |
| } |
| |
| /** |
| * Handles messages from the OneGoogleBar iframe. The messages that are |
| * handled include show bar on load and overlay updates. |
| * |
| * 'overlaysUpdated' message includes the updated array of overlay rects that |
| * are shown. |
| * |
| * When modal overlays are enabled, activate/deactivate controls if the |
| * OneGoogleBar is layered on top of #content with a backdrop. This would |
| * happen when OneGoogleBar has an overlay open. |
| * @param {!MessageEvent} event |
| * @private |
| */ |
| handleOneGoogleBarMessage_(event) { |
| /** @type {!Object} */ |
| const data = event.data; |
| if (data.messageType === 'loaded') { |
| if (!this.oneGoogleBarModalOverlaysEnabled_) { |
| const oneGoogleBar = $$(this, '#oneGoogleBar'); |
| oneGoogleBar.style.clipPath = 'url(#oneGoogleBarClipPath)'; |
| oneGoogleBar.style.zIndex = '1000'; |
| } |
| this.oneGoogleBarLoaded_ = true; |
| this.pageHandler_.onOneGoogleBarRendered( |
| BrowserProxy.getInstance().now()); |
| } else if (data.messageType === 'overlaysUpdated') { |
| this.$.oneGoogleBarClipPath.querySelectorAll('rect').forEach(el => { |
| el.remove(); |
| }); |
| const overlayRects = /** @type {!Array<!DOMRect>} */ (data.data); |
| overlayRects.forEach(({x, y, width, height}) => { |
| const rectElement = |
| document.createElementNS('http://www.w3.org/2000/svg', 'rect'); |
| // Add 8px around every rect to ensure shadows are not cutoff. |
| rectElement.setAttribute('x', x - 8); |
| rectElement.setAttribute('y', y - 8); |
| rectElement.setAttribute('width', width + 16); |
| rectElement.setAttribute('height', height + 16); |
| this.$.oneGoogleBarClipPath.appendChild(rectElement); |
| }); |
| } else if (data.messageType === 'activate') { |
| this.$.oneGoogleBarOverlayBackdrop.toggleAttribute('show', true); |
| $$(this, '#oneGoogleBar').style.zIndex = '1000'; |
| } else if (data.messageType === 'deactivate') { |
| this.$.oneGoogleBarOverlayBackdrop.toggleAttribute('show', false); |
| $$(this, '#oneGoogleBar').style.zIndex = '0'; |
| } else if (data.messageType === 'execute-browser-command') { |
| this.executePromoBrowserCommand_( |
| /** @type {!CommandData} */ (data.data), event.source, event.origin); |
| } |
| } |
| |
| /** @private */ |
| oneGoogleBarLoadedChange_() { |
| if (this.oneGoogleBarLoaded_ && this.iframeOneGoogleBarEnabled_ && |
| this.oneGoogleBarModalOverlaysEnabled_) { |
| this.setupShortcutDragDropOneGoogleBarWorkaround_(); |
| } |
| } |
| |
| /** @private */ |
| onMiddleSlotPromoLoaded_() { |
| const onResize = () => { |
| const promoElement = $$(this, 'ntp-middle-slot-promo'); |
| const hidePromo = this.$.mostVisited.getBoundingClientRect().bottom >= |
| promoElement.offsetTop; |
| promoElement.style.visibility = hidePromo ? 'hidden' : 'visible'; |
| }; |
| this.eventTracker_.add(window, 'resize', onResize); |
| onResize(); |
| } |
| |
| /** @private */ |
| onModulesRendered_() { |
| this.pageHandler_.onModulesRendered(BrowserProxy.getInstance().now()); |
| } |
| |
| /** |
| * During a shortcut drag, an iframe behind ntp-most-visited will prevent |
| * 'dragover' events from firing. To workaround this, 'pointer-events: none' |
| * can be set on the iframe. When doing this after the 'dragstart' event is |
| * fired is too late. We can instead set 'pointer-events: none' when the |
| * pointer enters ntp-most-visited. |
| * |
| * 'pointerenter' and pointerleave' events fire during drag. The iframe |
| * 'pointer-events' needs to be reset to the original value when 'dragend' |
| * fires if the pointer has left ntp-most-visited. |
| * @private |
| */ |
| setupShortcutDragDropOneGoogleBarWorkaround_() { |
| const iframe = $$(this, '#oneGoogleBar'); |
| let resetAtDragEnd = false; |
| let dragging = false; |
| let originalPointerEvents; |
| this.eventTracker_.add(this.$.mostVisited, 'pointerenter', () => { |
| if (dragging) { |
| resetAtDragEnd = false; |
| return; |
| } |
| originalPointerEvents = getComputedStyle(iframe).pointerEvents; |
| iframe.style.pointerEvents = 'none'; |
| }); |
| this.eventTracker_.add(this.$.mostVisited, 'pointerleave', () => { |
| if (dragging) { |
| resetAtDragEnd = true; |
| return; |
| } |
| iframe.style.pointerEvents = originalPointerEvents; |
| }); |
| this.eventTracker_.add(this.$.mostVisited, 'dragstart', () => { |
| dragging = true; |
| }); |
| this.eventTracker_.add(this.$.mostVisited, 'dragend', () => { |
| dragging = false; |
| if (resetAtDragEnd) { |
| resetAtDragEnd = false; |
| iframe.style.pointerEvents = originalPointerEvents; |
| } |
| }); |
| } |
| |
| /** @private */ |
| printPerformanceDatum_(name, time, auxTime = 0) { |
| if (!this.shouldPrintPerformance_) { |
| return; |
| } |
| if (!auxTime) { |
| console.log(`${name}: ${time}`); |
| } else { |
| console.log(`${name}: ${time} (${auxTime})`); |
| } |
| } |
| |
| /** |
| * Prints performance measurements to the console. Also, installs performance |
| * observer to continuously print performance measurements after. |
| * @private |
| */ |
| printPerformance_() { |
| if (!this.shouldPrintPerformance_) { |
| return; |
| } |
| const entryTypes = ['paint', 'measure']; |
| const log = (entry) => { |
| this.printPerformanceDatum_( |
| entry.name, entry.duration ? entry.duration : entry.startTime, |
| entry.duration && entry.startTime ? entry.startTime : 0); |
| }; |
| const observer = new PerformanceObserver(list => { |
| list.getEntries().forEach((entry) => { |
| log(entry); |
| }); |
| }); |
| observer.observe({entryTypes: entryTypes}); |
| performance.getEntries().forEach((entry) => { |
| if (!entryTypes.includes(entry.entryType)) { |
| return; |
| } |
| log(entry); |
| }); |
| } |
| } |
| |
| customElements.define(AppElement.is, AppElement); |