blob: 1724c2a747ef7b8986ec9fb226d750d138f8fd91 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import './iframe.js';
import './realbox/realbox.js';
import './logo.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import {startColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
import {HelpBubbleMixin, HelpBubbleMixinInterface} from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin.js';
import {ClickInfo, Command} from 'chrome://resources/js/browser_command.mojom-webui.js';
import {BrowserCommandProxy} from 'chrome://resources/js/browser_command/browser_command_proxy.js';
import {hexColorToSkColor, skColorToRgba} from 'chrome://resources/js/color_utils.js';
import {EventTracker} from 'chrome://resources/js/event_tracker.js';
import {FocusOutlineManager} from 'chrome://resources/js/focus_outline_manager.js';
import {getTrustedScriptURL} from 'chrome://resources/js/static_types.js';
import {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js';
import {DomIf, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {getTemplate} from './app.html.js';
import {BackgroundManager} from './background_manager.js';
import {CustomizeDialogPage} from './customize_dialog_types.js';
import {loadTimeData} from './i18n_setup.js';
import {IframeElement} from './iframe.js';
import {LogoElement} from './logo.js';
import {recordLoadDuration} from './metrics_utils.js';
import {CustomizeChromeSection, NtpBackgroundImageSource, PageCallbackRouter, PageHandlerRemote, Theme} from './new_tab_page.mojom-webui.js';
import {NewTabPageProxy} from './new_tab_page_proxy.js';
import {$$} from './utils.js';
import {Action as VoiceAction, recordVoiceAction} from './voice_search_overlay.js';
import {WindowProxy} from './window_proxy.js';
interface ExecutePromoBrowserCommandData {
commandId: Command;
clickInfo: ClickInfo;
}
interface CanShowPromoWithBrowserCommandData {
frameType: string;
messageType: string;
commandId: Command;
}
/**
* Elements on the NTP. This enum must match the numbering for NTPElement in
* enums.xml. These values are persisted to logs. Entries should not be
* renumbered, removed or reused.
*/
export enum NtpElement {
OTHER = 0,
BACKGROUND = 1,
ONE_GOOGLE_BAR = 2,
LOGO = 3,
REALBOX = 4,
MOST_VISITED = 5,
MIDDLE_SLOT_PROMO = 6,
MODULE = 7,
CUSTOMIZE = 8, // Obsolete
CUSTOMIZE_BUTTON = 9,
CUSTOMIZE_DIALOG = 10,
}
/**
* Customize Chrome entry points. This enum must match the numbering for
* NtpCustomizeChromeEntryPoint in enums.xml. These values are persisted to
* logs. Entries should not be renumbered, removed or reused.
*/
export enum NtpCustomizeChromeEntryPoint {
CUSTOMIZE_BUTTON = 0,
MODULE = 1,
URL = 2,
}
const CUSTOMIZE_URL_PARAM: string = 'customize';
const OGB_IFRAME_ORIGIN = 'chrome-untrusted://new-tab-page';
export const CUSTOMIZE_CHROME_BUTTON_ELEMENT_ID =
'NewTabPageUI::kCustomizeChromeButtonElementId';
function recordClick(element: NtpElement) {
chrome.metricsPrivate.recordEnumerationValue(
'NewTabPage.Click', element, Object.keys(NtpElement).length);
}
function recordCustomizeChromeOpen(element: NtpCustomizeChromeEntryPoint) {
chrome.metricsPrivate.recordEnumerationValue(
'NewTabPage.CustomizeChromeOpened', element,
Object.keys(NtpCustomizeChromeEntryPoint).length);
}
// Adds a <script> tag that holds the lazy loaded code.
function ensureLazyLoaded() {
const script = document.createElement('script');
script.type = 'module';
script.src = getTrustedScriptURL`./lazy_load.js`;
document.body.appendChild(script);
}
const AppElementBase = HelpBubbleMixin(PolymerElement) as
{new (): PolymerElement & HelpBubbleMixinInterface};
export interface AppElement {
$: {
customizeDialogIf: DomIf,
oneGoogleBarClipPath: HTMLElement,
logo: LogoElement,
};
}
export class AppElement extends AppElementBase {
static get is() {
return 'ntp-app';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
oneGoogleBarIframeOrigin_: {
type: String,
value: OGB_IFRAME_ORIGIN,
},
oneGoogleBarIframePath_: {
type: String,
value: () => {
const params = new URLSearchParams();
params.set(
'paramsencoded',
btoa(window.location.search.replace(/^[?]/, '&')));
return `${OGB_IFRAME_ORIGIN}/one-google-bar?${params}`;
},
},
theme_: {
observer: 'onThemeChange_',
type: Object,
},
showCustomize_: {
type: Boolean,
value: () =>
WindowProxy.getInstance().url.searchParams.has(CUSTOMIZE_URL_PARAM),
},
showCustomizeDialog_: {
type: Boolean,
computed:
'computeShowCustomizeDialog_(customizeChromeEnabled_, showCustomize_)',
},
selectedCustomizeDialogPage_: {
type: String,
value: () =>
WindowProxy.getInstance().url.searchParams.get(CUSTOMIZE_URL_PARAM),
},
showVoiceSearchOverlay_: Boolean,
showBackgroundImage_: {
computed: 'computeShowBackgroundImage_(theme_)',
observer: 'onShowBackgroundImageChange_',
reflectToAttribute: true,
type: Boolean,
},
backgroundImageAttribution1_: {
type: String,
computed: `computeBackgroundImageAttribution1_(theme_)`,
},
backgroundImageAttribution2_: {
type: String,
computed: `computeBackgroundImageAttribution2_(theme_)`,
},
backgroundImageAttributionUrl_: {
type: String,
computed: `computeBackgroundImageAttributionUrl_(theme_)`,
},
backgroundColor_: {
computed: 'computeBackgroundColor_(showBackgroundImage_, theme_)',
type: Object,
},
customizeChromeEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('customizeChromeEnabled'),
},
logoColor_: {
type: String,
computed: 'computeLogoColor_(theme_)',
},
singleColoredLogo_: {
computed: 'computeSingleColoredLogo_(theme_)',
type: Boolean,
},
realboxLensSearchEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('realboxLensSearch'),
},
realboxShown_: {
type: Boolean,
computed: 'computeRealboxShown_(theme_, showLensUploadDialog_)',
},
logoEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('logoEnabled'),
},
oneGoogleBarEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('oneGoogleBarEnabled'),
},
shortcutsEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('shortcutsEnabled'),
},
modulesFreShown: {
type: Boolean,
reflectToAttribute: true,
},
modulesRedesignedLayoutEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('modulesRedesignedLayoutEnabled'),
reflectToAttribute: true,
},
middleSlotPromoEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('middleSlotPromoEnabled'),
},
modulesEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('modulesEnabled'),
},
modulesRedesignedEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean('modulesRedesignedEnabled'),
reflectToAttribute: true,
},
middleSlotPromoLoaded_: {
type: Boolean,
value: false,
},
modulesLoaded_: {
type: Boolean,
value: false,
},
modulesShownToUser: {
type: Boolean,
reflectToAttribute: true,
},
/**
* In order to avoid flicker, the promo and modules are hidden until both
* are loaded. If modules are disabled, the promo is shown as soon as it
* is loaded.
*/
promoAndModulesLoaded_: {
type: Boolean,
computed: `computePromoAndModulesLoaded_(middleSlotPromoLoaded_,
modulesLoaded_)`,
observer: 'onPromoAndModulesLoadedChange_',
},
removeScrim_: {
type: Boolean,
value: () => loadTimeData.getBoolean('removeScrim'),
reflectToAttribute: true,
},
showLensUploadDialog_: Boolean,
/**
* If true, renders additional elements that were not deemed crucial to
* to show up immediately on load.
*/
lazyRender_: Boolean,
};
}
static get observers() {
return [
'udpateOneGoogleBarAppearance_(oneGoogleBarLoaded_, removeScrim_, showBackgroundImage_, theme_)',
];
}
private oneGoogleBarIframePath_: string;
private oneGoogleBarLoaded_: boolean;
private theme_: Theme;
private showCustomize_: boolean;
private showCustomizeDialog_: boolean;
private selectedCustomizeDialogPage_: string|null;
private showVoiceSearchOverlay_: boolean;
private showBackgroundImage_: boolean;
private backgroundImageAttribution1_: string;
private backgroundImageAttribution2_: string;
private backgroundImageAttributionUrl_: string;
private backgroundColor_: SkColor;
private customizeChromeEnabled_: boolean;
private logoColor_: string;
private singleColoredLogo_: boolean;
private realboxLensSearchEnabled_: boolean;
private realboxShown_: boolean;
private showLensUploadDialog_: boolean = false;
private logoEnabled_: boolean;
private oneGoogleBarEnabled_: boolean;
private shortcutsEnabled_: boolean;
private modulesFreShown: boolean;
private modulesRedesignedLayoutEnabled_: boolean;
private middleSlotPromoEnabled_: boolean;
private modulesEnabled_: boolean;
private modulesRedesignedEnabled_: boolean;
private middleSlotPromoLoaded_: boolean;
private modulesLoaded_: boolean;
private modulesShownToUser: boolean;
private promoAndModulesLoaded_: boolean;
private removeScrim_: boolean;
private lazyRender_: boolean;
private openLensDialog: Function|null;
private callbackRouter_: PageCallbackRouter;
private pageHandler_: PageHandlerRemote;
private backgroundManager_: BackgroundManager;
private setThemeListenerId_: number|null = null;
private setCustomizeChromeSidePanelVisibilityListener_: number|null = null;
private eventTracker_: EventTracker = new EventTracker();
private shouldPrintPerformance_: boolean;
private backgroundImageLoadStartEpoch_: number;
private backgroundImageLoadStart_: number = 0;
constructor() {
performance.mark('app-creation-start');
super();
this.callbackRouter_ = NewTabPageProxy.getInstance().callbackRouter;
this.pageHandler_ = NewTabPageProxy.getInstance().handler;
this.backgroundManager_ = BackgroundManager.getInstance();
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.
*/
this.backgroundImageLoadStartEpoch_ = performance.timeOrigin;
chrome.metricsPrivate.recordValue(
{
metricName: 'NewTabPage.Height',
type: chrome.metricsPrivate.MetricTypeType.HISTOGRAM_LINEAR,
min: 1,
max: 1000,
buckets: 200,
},
Math.floor(document.documentElement.clientHeight));
startColorChangeUpdater();
}
override connectedCallback() {
super.connectedCallback();
this.setThemeListenerId_ =
this.callbackRouter_.setTheme.addListener((theme: Theme) => {
if (!this.theme_) {
this.onThemeLoaded_(theme);
}
performance.measure('theme-set');
this.theme_ = theme;
});
this.setCustomizeChromeSidePanelVisibilityListener_ =
this.callbackRouter_.setCustomizeChromeSidePanelVisibility.addListener(
(visible: boolean) => {
this.showCustomize_ = visible;
});
// Open Customize Chrome if there are Customize Chrome URL params.
if (this.showCustomize_) {
this.setCustomizeChromeSidePanelVisible_(this.showCustomize_);
recordCustomizeChromeOpen(NtpCustomizeChromeEntryPoint.URL);
}
this.eventTracker_.add(window, 'message', (event: MessageEvent) => {
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', this.onWindowKeydown_.bind(this));
this.eventTracker_.add(
window, 'click', this.onWindowClick_.bind(this), /*capture=*/ true);
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);
if (loadTimeData.valueExists('modulesMaxWidthPx')) {
this.updateStyles({
'--ntp-module-max-width':
`${loadTimeData.getInteger('modulesMaxWidthPx')}px`,
});
}
}
override disconnectedCallback() {
super.disconnectedCallback();
this.callbackRouter_.removeListener(this.setThemeListenerId_!);
this.callbackRouter_.removeListener(
this.setCustomizeChromeSidePanelVisibilityListener_!);
this.eventTracker_.removeAll();
}
override ready() {
super.ready();
this.pageHandler_.onAppRendered(WindowProxy.getInstance().now());
// Let the browser breath and then render remaining elements.
WindowProxy.getInstance().waitForLazyRender().then(() => {
ensureLazyLoaded();
this.lazyRender_ = true;
});
this.printPerformance_();
performance.measure('app-creation', 'app-creation-start');
}
// Called to update the OGB of relevant NTP state changes.
private udpateOneGoogleBarAppearance_() {
if (this.oneGoogleBarLoaded_) {
const isNtpDarkTheme =
this.theme_ && (!!this.theme_.backgroundImage || this.theme_.isDark);
$$<IframeElement>(this, '#oneGoogleBar')!.postMessage({
type: 'updateAppearance',
// We should be using a light OGB for dark themes and vice versa.
applyLightTheme: isNtpDarkTheme,
// Only apply background protection if using a custom background in
// combination with a light OGB theme.
applyBackgroundProtection:
this.removeScrim_ && this.showBackgroundImage_ && isNtpDarkTheme,
});
}
}
private computeShowCustomizeDialog_(): boolean {
return !this.customizeChromeEnabled_ && this.showCustomize_;
}
private computeBackgroundImageAttribution1_(): string {
return this.theme_ && this.theme_.backgroundImageAttribution1 || '';
}
private computeBackgroundImageAttribution2_(): string {
return this.theme_ && this.theme_.backgroundImageAttribution2 || '';
}
private computeBackgroundImageAttributionUrl_(): string {
return this.theme_ && this.theme_.backgroundImageAttributionUrl ?
this.theme_.backgroundImageAttributionUrl.url :
'';
}
private computeRealboxShown_(theme: Theme, showLensUploadDialog: boolean):
boolean {
// Do not show the realbox if the upload dialog is showing.
return theme && !showLensUploadDialog;
}
private computePromoAndModulesLoaded_(): boolean {
return (!loadTimeData.getBoolean('middleSlotPromoEnabled') ||
this.middleSlotPromoLoaded_) &&
(!loadTimeData.getBoolean('modulesEnabled') || this.modulesLoaded_);
}
private async onLazyRendered_() {
// Integration tests use this attribute to determine when lazy load has
// completed.
document.documentElement.setAttribute('lazy-loaded', String(true));
this.registerHelpBubble(
CUSTOMIZE_CHROME_BUTTON_ELEMENT_ID, '#customizeButton', {fixed: true});
}
private onOpenVoiceSearch_() {
this.showVoiceSearchOverlay_ = true;
recordVoiceAction(VoiceAction.ACTIVATE_SEARCH_BOX);
}
/**
* Sets the openLensDialog function when the child LensUploadDialogComponent
* is lazily loaded.
*
* We use the connected callback with a custom event dispatch to get around
* bundling the upload dialog component with the primary bundle to call open
* dialog.
*/
private bindOpenLensDialog_(e: CustomEvent<{fn: () => void}>) {
this.openLensDialog = e.detail.fn;
}
private onOpenLensSearch_() {
if (this.openLensDialog) {
this.openLensDialog();
this.showLensUploadDialog_ = true;
}
}
private onCloseLensSearch_() {
this.showLensUploadDialog_ = false;
}
private onCustomizeClick_() {
// Let customize dialog or side panel decide what page or section to show.
this.selectedCustomizeDialogPage_ = null;
if (this.customizeChromeEnabled_) {
this.setCustomizeChromeSidePanelVisible_(!this.showCustomize_);
if (!this.showCustomize_) {
this.pageHandler_.incrementCustomizeChromeButtonOpenCount();
recordCustomizeChromeOpen(
NtpCustomizeChromeEntryPoint.CUSTOMIZE_BUTTON);
}
} else {
this.showCustomize_ = true;
recordCustomizeChromeOpen(NtpCustomizeChromeEntryPoint.CUSTOMIZE_BUTTON);
}
}
private onCustomizeDialogClose_() {
this.showCustomize_ = false;
// Let customize dialog decide what page to show on next open.
this.selectedCustomizeDialogPage_ = null;
}
private onVoiceSearchOverlayClose_() {
this.showVoiceSearchOverlay_ = false;
}
/**
* Handles <CTRL> + <SHIFT> + <.> (also <CMD> + <SHIFT> + <.> on mac) to open
* voice search.
*/
private onWindowKeydown_(e: KeyboardEvent) {
let ctrlKeyPressed = e.ctrlKey;
// <if expr="is_macosx">
ctrlKeyPressed = ctrlKeyPressed || e.metaKey;
// </if>
if (ctrlKeyPressed && e.code === 'Period' && e.shiftKey) {
this.showVoiceSearchOverlay_ = true;
recordVoiceAction(VoiceAction.ACTIVATE_KEYBOARD);
}
}
private rgbaOrInherit_(skColor: SkColor|null): string {
return skColor ? skColorToRgba(skColor) : 'inherit';
}
private computeShowBackgroundImage_(): boolean {
return !!this.theme_ && !!this.theme_.backgroundImage;
}
private onShowBackgroundImageChange_() {
this.backgroundManager_.setShowBackgroundImage(this.showBackgroundImage_);
}
private onThemeChange_() {
if (this.theme_) {
this.backgroundManager_.setBackgroundColor(this.theme_.backgroundColor);
}
this.updateBackgroundImagePath_();
}
private onThemeLoaded_(theme: Theme) {
chrome.metricsPrivate.recordEnumerationValue(
'NewTabPage.BackgroundImageSource',
(theme.backgroundImage ? theme.backgroundImage.imageSource :
NtpBackgroundImageSource.kNoImage),
NtpBackgroundImageSource.MAX_VALUE);
chrome.metricsPrivate.recordSparseValueWithPersistentHash(
'NewTabPage.Collections.IdOnLoad',
theme.backgroundImageCollectionId ?? '');
}
private onPromoAndModulesLoadedChange_() {
if (this.promoAndModulesLoaded_ &&
loadTimeData.getBoolean('modulesEnabled')) {
recordLoadDuration(
'NewTabPage.Modules.ShownTime', WindowProxy.getInstance().now());
}
}
/**
* 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_() {
const backgroundImage = this.theme_ && this.theme_.backgroundImage;
if (backgroundImage) {
this.backgroundManager_.setBackgroundImage(backgroundImage);
}
}
private computeBackgroundColor_(): SkColor|null {
if (this.showBackgroundImage_) {
return null;
}
return this.theme_ && this.theme_.backgroundColor;
}
private computeLogoColor_(): SkColor|null {
return this.theme_ &&
(this.theme_.logoColor ||
(this.theme_.isDark ? hexColorToSkColor('#ffffff') : null));
}
private computeSingleColoredLogo_(): boolean {
return this.theme_ && (!!this.theme_.logoColor || this.theme_.isDark);
}
/**
* Sends the command received from the given source and origin to the browser.
* Relays the browser response to whether or not a promo containing the given
* command can be shown back to the source promo frame. |commandSource| and
* |commandOrigin| are used only to send the response back to the source promo
* frame and should not be used for anything else.
* @param messageData Data received from the source promo frame.
* @param commandSource Source promo frame.
* @param commandOrigin Origin of the source promo frame.
*/
private canShowPromoWithBrowserCommand_(
messageData: CanShowPromoWithBrowserCommandData, commandSource: Window,
commandOrigin: string) {
// Make sure we don't send unsupported commands to the browser.
/** @type {!Command} */
const commandId = Object.values(Command).includes(messageData.commandId) ?
messageData.commandId :
Command.kUnknownCommand;
BrowserCommandProxy.getInstance().handler.canExecuteCommand(commandId).then(
({canExecute}) => {
const response = {
messageType: messageData.messageType,
[messageData.commandId]: canExecute,
};
commandSource.postMessage(response, commandOrigin);
});
}
/**
* 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 Command and mouse click info.
* @param commandSource Source promo frame.
* @param commandOrigin Origin of the source promo frame.
*/
private executePromoBrowserCommand_(
commandData: ExecutePromoBrowserCommandData, commandSource: Window,
commandOrigin: string) {
// Make sure we don't send unsupported commands to the browser.
const commandId = Object.values(Command).includes(commandData.commandId) ?
commandData.commandId :
Command.kUnknownCommand;
BrowserCommandProxy.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.
*/
private handleOneGoogleBarMessage_(event: MessageEvent) {
const data = event.data;
if (data.messageType === 'loaded') {
const oneGoogleBar = $$<IframeElement>(this, '#oneGoogleBar')!;
oneGoogleBar.style.clipPath = 'url(#oneGoogleBarClipPath)';
oneGoogleBar.style.zIndex = '1000';
this.oneGoogleBarLoaded_ = true;
this.pageHandler_.onOneGoogleBarRendered(WindowProxy.getInstance().now());
} else if (data.messageType === 'overlaysUpdated') {
this.$.oneGoogleBarClipPath.querySelectorAll('rect').forEach(el => {
el.remove();
});
const overlayRects = data.data as DOMRect[];
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 === 'can-show-promo-with-browser-command') {
this.canShowPromoWithBrowserCommand_(
data, event.source as Window, event.origin);
} else if (data.messageType === 'execute-browser-command') {
this.executePromoBrowserCommand_(
data.data, event.source as Window, event.origin);
} else if (data.messageType === 'click') {
recordClick(NtpElement.ONE_GOOGLE_BAR);
}
}
private onMiddleSlotPromoLoaded_() {
this.middleSlotPromoLoaded_ = true;
}
private onModulesLoaded_() {
this.modulesLoaded_ = true;
}
private onCustomizeModule_() {
this.showCustomize_ = true;
this.selectedCustomizeDialogPage_ = CustomizeDialogPage.MODULES;
recordCustomizeChromeOpen(NtpCustomizeChromeEntryPoint.MODULE);
this.setCustomizeChromeSidePanelVisible_(this.showCustomize_);
}
private setCustomizeChromeSidePanelVisible_(visible: boolean) {
if (!this.customizeChromeEnabled_) {
return;
}
let section: CustomizeChromeSection = CustomizeChromeSection.kUnspecified;
switch (this.selectedCustomizeDialogPage_) {
case CustomizeDialogPage.BACKGROUNDS:
case CustomizeDialogPage.THEMES:
section = CustomizeChromeSection.kAppearance;
break;
case CustomizeDialogPage.SHORTCUTS:
section = CustomizeChromeSection.kShortcuts;
break;
case CustomizeDialogPage.MODULES:
section = CustomizeChromeSection.kModules;
break;
}
this.pageHandler_.setCustomizeChromeSidePanelVisible(visible, section);
}
private printPerformanceDatum_(
name: string, time: number, auxTime: number = 0) {
if (!this.shouldPrintPerformance_) {
return;
}
console.info(
!auxTime ? `${name}: ${time}` : `${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: PerformanceEntry) => {
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);
});
}
private onWindowClick_(e: Event) {
if (e.composedPath() && e.composedPath()[0] === $$(this, '#content')) {
recordClick(NtpElement.BACKGROUND);
return;
}
for (const target of e.composedPath()) {
switch (target) {
case $$(this, 'ntp-logo'):
recordClick(NtpElement.LOGO);
return;
case $$(this, 'ntp-realbox'):
recordClick(NtpElement.REALBOX);
return;
case $$(this, 'cr-most-visited'):
recordClick(NtpElement.MOST_VISITED);
return;
case $$(this, 'ntp-middle-slot-promo'):
recordClick(NtpElement.MIDDLE_SLOT_PROMO);
return;
case $$(this, 'ntp-modules'):
recordClick(NtpElement.MODULE);
return;
case $$(this, '#customizeButton'):
recordClick(NtpElement.CUSTOMIZE_BUTTON);
return;
case $$(this, 'ntp-customize-dialog'):
recordClick(NtpElement.CUSTOMIZE_DIALOG);
return;
}
}
recordClick(NtpElement.OTHER);
}
}
declare global {
interface HTMLElementTagNameMap {
'ntp-app': AppElement;
}
}
customElements.define(AppElement.is, AppElement);