| // Copyright 2020 The Chromium Authors |
| // 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.js'; |
| import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js'; |
| import type {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js'; |
| |
| import type {BackgroundImage} from './new_tab_page.mojom-webui.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 { |
| private resolver_: PromiseResolver<number> = new PromiseResolver(); |
| private eventTracker_: EventTracker = new EventTracker(); |
| |
| constructor(url: string) { |
| this.eventTracker_.add(window, 'message', ({data}: MessageEvent) => { |
| if (data.frameType === 'background-image' && |
| data.messageType === 'loaded' && url === data.url) { |
| this.resolve_(data.time); |
| } |
| }); |
| } |
| |
| get promise(): Promise<number> { |
| return this.resolver_.promise; |
| } |
| |
| reject() { |
| this.resolver_.reject(); |
| this.eventTracker_.removeAll(); |
| } |
| |
| private resolve_(loadTime: number) { |
| this.resolver_.resolve(loadTime); |
| this.eventTracker_.removeAll(); |
| } |
| } |
| |
| let instance: BackgroundManager|null = null; |
| |
| export class BackgroundManager { |
| static getInstance(): BackgroundManager { |
| return instance || (instance = new BackgroundManager()); |
| } |
| |
| static setInstance(newInstance: BackgroundManager) { |
| instance = newInstance; |
| } |
| |
| private backgroundImage_: HTMLIFrameElement; |
| private loadTimeResolver_: LoadTimeResolver|null = null; |
| private url_: string; |
| |
| constructor() { |
| this.backgroundImage_ = |
| strictQuery(document.body, '#backgroundImage', HTMLIFrameElement); |
| this.url_ = this.backgroundImage_.src; |
| } |
| |
| /** |
| * Sets whether the background image should be shown. |
| * @param show True, if the background image should be shown. |
| */ |
| setShowBackgroundImage(show: boolean) { |
| document.body.toggleAttribute('show-background-image', show); |
| } |
| |
| /** Sets the background color. */ |
| setBackgroundColor(color: SkColor) { |
| document.body.style.backgroundColor = skColorToRgba(color); |
| } |
| |
| /** Sets the background image. */ |
| setBackgroundImage(image: BackgroundImage) { |
| 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. |
| */ |
| getBackgroundImageLoadTime(): Promise<number> { |
| 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; |
| } |
| } |