|  | // Copyright 2020 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 {skColorToRgba} from 'chrome://resources/js/color_utils.js'; | 
|  | import {EventTracker} from 'chrome://resources/js/event_tracker.m.js'; | 
|  | import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js'; | 
|  |  | 
|  | import {strictQuery} from './utils.js'; | 
|  | import {WindowProxy} from './window_proxy.js'; | 
|  |  | 
|  | /** | 
|  | * @fileoverview The background manager brokers access to background related | 
|  | * DOM elements. The reason for this abstraction is that the these elements are | 
|  | * not owned by any custom elements (this is done so that the aforementioned DOM | 
|  | * elements load faster at startup). | 
|  | * | 
|  | * The background manager expects an iframe with ID 'backgroundImage' to be | 
|  | * present in the DOM. It will use that element to set the background image URL. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * Installs a listener for background image load times and manages a | 
|  | * |PromiseResolver| that resolves to the captured load time. | 
|  | */ | 
|  | class LoadTimeResolver { | 
|  | /** @param {string} url */ | 
|  | constructor(url) { | 
|  | /** @private {!PromiseResolver<number>} */ | 
|  | this.resolver_ = new PromiseResolver(); | 
|  | /** @private {!EventTracker} */ | 
|  | this.eventTracker_ = new EventTracker(); | 
|  | this.eventTracker_.add(window, 'message', ({data}) => { | 
|  | if (data.frameType === 'background-image' && | 
|  | data.messageType === 'loaded' && url === data.url) { | 
|  | this.resolve_(data.time); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | /** @return {!Promise<number>} */ | 
|  | get promise() { | 
|  | return this.resolver_.promise; | 
|  | } | 
|  |  | 
|  | reject() { | 
|  | this.resolver_.reject(); | 
|  | this.eventTracker_.removeAll(); | 
|  | } | 
|  |  | 
|  | /** @param {number} loadTime */ | 
|  | resolve_(loadTime) { | 
|  | this.resolver_.resolve(loadTime); | 
|  | this.eventTracker_.removeAll(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @type {BackgroundManager} */ | 
|  | let instance = null; | 
|  |  | 
|  | export class BackgroundManager { | 
|  | /** @return {!BackgroundManager} */ | 
|  | static getInstance() { | 
|  | return instance || (instance = new BackgroundManager()); | 
|  | } | 
|  |  | 
|  | /** @param {BackgroundManager} newInstance */ | 
|  | static setInstance(newInstance) { | 
|  | instance = newInstance; | 
|  | } | 
|  |  | 
|  | constructor() { | 
|  | /** @private {!HTMLIFrameElement} */ | 
|  | this.backgroundImage_ = | 
|  | strictQuery(document.body, '#backgroundImage', HTMLIFrameElement); | 
|  | /** @private {LoadTimeResolver} */ | 
|  | this.loadTimeResolver_ = null; | 
|  | /** @private {string} */ | 
|  | this.url_ = this.backgroundImage_.src; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets whether the background image should be shown. | 
|  | * @param {boolean} show True, if the background image should be shown. | 
|  | */ | 
|  | setShowBackgroundImage(show) { | 
|  | document.body.toggleAttribute('show-background-image', show); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the background color. | 
|  | * @param {skia.mojom.SkColor} color The background color. | 
|  | */ | 
|  | setBackgroundColor(color) { | 
|  | document.body.style.backgroundColor = skColorToRgba(color); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the background image. | 
|  | * @param {!newTabPage.mojom.BackgroundImage} image The background image. | 
|  | */ | 
|  | setBackgroundImage(image) { | 
|  | const url = | 
|  | new URL('chrome-untrusted://new-tab-page/custom_background_image'); | 
|  | url.searchParams.append('url', image.url.url); | 
|  | if (image.url2x) { | 
|  | url.searchParams.append('url2x', image.url2x.url); | 
|  | } | 
|  | if (image.size) { | 
|  | url.searchParams.append('size', image.size); | 
|  | } | 
|  | if (image.repeatX) { | 
|  | url.searchParams.append('repeatX', image.repeatX); | 
|  | } | 
|  | if (image.repeatY) { | 
|  | url.searchParams.append('repeatY', image.repeatY); | 
|  | } | 
|  | if (image.positionX) { | 
|  | url.searchParams.append('positionX', image.positionX); | 
|  | } | 
|  | if (image.positionY) { | 
|  | url.searchParams.append('positionY', image.positionY); | 
|  | } | 
|  | if (url.href === this.url_) { | 
|  | return; | 
|  | } | 
|  | if (this.loadTimeResolver_) { | 
|  | this.loadTimeResolver_.reject(); | 
|  | this.loadTimeResolver_ = null; | 
|  | } | 
|  | // We use |contentWindow.location.replace| because reloading the iframe by | 
|  | // setting its |src| adds a history entry. | 
|  | this.backgroundImage_.contentWindow.location.replace(url.href); | 
|  | // We track the URL separately because |contentWindow.location.replace| does | 
|  | // not update the iframe's src attribute. | 
|  | this.url_ = url.href; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns promise that resolves with the background image load time. | 
|  | * | 
|  | * The background image iframe proactively sends the load time as soon as it | 
|  | * has loaded. However, this could be before we have installed the message | 
|  | * listener in LoadTimeResolver. Therefore, we request the background image | 
|  | * iframe to resend the load time in case it has already loaded. With that | 
|  | * setup we ensure that the load time is (re)sent _after_ both the NTP top | 
|  | * frame and the background image iframe have installed the required message | 
|  | * listeners. | 
|  | * @return {!Promise<number>} | 
|  | */ | 
|  | getBackgroundImageLoadTime() { | 
|  | if (!this.loadTimeResolver_) { | 
|  | this.loadTimeResolver_ = new LoadTimeResolver(this.backgroundImage_.src); | 
|  | WindowProxy.getInstance().postMessage( | 
|  | this.backgroundImage_, 'sendLoadTime', | 
|  | 'chrome-untrusted://new-tab-page'); | 
|  | } | 
|  | return this.loadTimeResolver_.promise; | 
|  | } | 
|  | } |