| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Common from '../../core/common/common.js'; |
| import * as Root from '../../core/root/root.js'; |
| import * as SDK from '../../core/sdk/sdk.js'; |
| |
| let contrastCheckTriggerInstance: ContrastCheckTrigger|null = null; |
| |
| export class ContrastCheckTrigger { |
| #pageLoadListeners = new WeakMap<SDK.ResourceTreeModel.ResourceTreeModel, Common.EventTarget.EventDescriptor>(); |
| #frameAddedListeners = new WeakMap<SDK.ResourceTreeModel.ResourceTreeModel, Common.EventTarget.EventDescriptor>(); |
| |
| constructor() { |
| SDK.TargetManager.TargetManager.instance().observeModels(SDK.ResourceTreeModel.ResourceTreeModel, this); |
| } |
| |
| static instance({forceNew}: {forceNew: boolean} = {forceNew: false}): ContrastCheckTrigger { |
| if (!contrastCheckTriggerInstance || forceNew) { |
| contrastCheckTriggerInstance = new ContrastCheckTrigger(); |
| } |
| |
| return contrastCheckTriggerInstance; |
| } |
| |
| async modelAdded(resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel): Promise<void> { |
| this.#pageLoadListeners.set( |
| resourceTreeModel, |
| resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, this.#pageLoaded, this)); |
| this.#frameAddedListeners.set( |
| resourceTreeModel, |
| resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameAdded, this.#frameAdded, this)); |
| } |
| |
| modelRemoved(resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel): void { |
| const pageLoadListener = this.#pageLoadListeners.get(resourceTreeModel); |
| if (pageLoadListener) { |
| Common.EventTarget.removeEventListeners([pageLoadListener]); |
| } |
| const frameAddedListeners = this.#frameAddedListeners.get(resourceTreeModel); |
| if (frameAddedListeners) { |
| Common.EventTarget.removeEventListeners([frameAddedListeners]); |
| } |
| } |
| |
| #checkContrast(resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel): void { |
| if (!Root.Runtime.experiments.isEnabled('contrast-issues')) { |
| return; |
| } |
| void resourceTreeModel.target().auditsAgent().invoke_checkContrast({}); |
| } |
| |
| #pageLoaded(event: Common.EventTarget |
| .EventTargetEvent<{resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel, loadTime: number}>): |
| void { |
| const {resourceTreeModel} = event.data; |
| this.#checkContrast(resourceTreeModel); |
| } |
| |
| async #frameAdded(event: Common.EventTarget.EventTargetEvent<SDK.ResourceTreeModel.ResourceTreeFrame>): |
| Promise<void> { |
| if (!Root.Runtime.experiments.isEnabled('contrast-issues')) { |
| return; |
| } |
| const frame = event.data; |
| if (!frame.isMainFrame()) { |
| return; |
| } |
| // If the target document finished loading, check the contrast immediately. |
| // Otherwise, it should be triggered when the page load event fires. |
| const response = await frame.resourceTreeModel().target().runtimeAgent().invoke_evaluate( |
| {expression: 'document.readyState', returnByValue: true}); |
| if (response.result?.value === 'complete') { |
| this.#checkContrast(frame.resourceTreeModel()); |
| } |
| } |
| } |