diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/inspector/compatibility/CompatibilityView.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | devtools/client/inspector/compatibility/CompatibilityView.js | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/devtools/client/inspector/compatibility/CompatibilityView.js b/devtools/client/inspector/compatibility/CompatibilityView.js new file mode 100644 index 0000000000..19c3263f00 --- /dev/null +++ b/devtools/client/inspector/compatibility/CompatibilityView.js @@ -0,0 +1,277 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + createFactory, + createElement, +} = require("resource://devtools/client/shared/vendor/react.js"); +const { + Provider, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); + +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const LocalizationProvider = createFactory(FluentReact.LocalizationProvider); + +const compatibilityReducer = require("resource://devtools/client/inspector/compatibility/reducers/compatibility.js"); +const { + appendNode, + clearDestroyedNodes, + initUserSettings, + removeNode, + updateNodes, + updateSelectedNode, + updateTopLevelTarget, + updateNode, +} = require("resource://devtools/client/inspector/compatibility/actions/compatibility.js"); + +const CompatibilityApp = createFactory( + require("resource://devtools/client/inspector/compatibility/components/CompatibilityApp.js") +); + +class CompatibilityView { + constructor(inspector, window) { + this.inspector = inspector; + + this.inspector.store.injectReducer("compatibility", compatibilityReducer); + + this._parseMarkup = this._parseMarkup.bind(this); + this._onChangeAdded = this._onChangeAdded.bind(this); + this._onPanelSelected = this._onPanelSelected.bind(this); + this._onSelectedNodeChanged = this._onSelectedNodeChanged.bind(this); + this._onTopLevelTargetChanged = this._onTopLevelTargetChanged.bind(this); + this._onResourceAvailable = this._onResourceAvailable.bind(this); + this._onMarkupMutation = this._onMarkupMutation.bind(this); + + this._init(); + } + + destroy() { + try { + this.resourceCommand.unwatchResources( + [this.resourceCommand.TYPES.CSS_CHANGE], + { + onAvailable: this._onResourceAvailable, + } + ); + } catch (e) { + // If unwatchResources is called before finishing process of watchResources, + // unwatchResources throws an error during stopping listener. + } + + this.inspector.off("new-root", this._onTopLevelTargetChanged); + this.inspector.off("markupmutation", this._onMarkupMutation); + this.inspector.selection.off("new-node-front", this._onSelectedNodeChanged); + this.inspector.sidebar.off( + "compatibilityview-selected", + this._onPanelSelected + ); + this.inspector = null; + } + + get resourceCommand() { + return this.inspector.toolbox.resourceCommand; + } + + async _init() { + const { setSelectedNode } = this.inspector.getCommonComponentProps(); + const compatibilityApp = new CompatibilityApp({ + setSelectedNode, + }); + + this.provider = createElement( + Provider, + { + id: "compatibilityview", + store: this.inspector.store, + }, + LocalizationProvider( + { + bundles: this.inspector.fluentL10n.getBundles(), + parseMarkup: this._parseMarkup, + }, + compatibilityApp + ) + ); + + await this.inspector.store.dispatch(initUserSettings()); + // awaiting for `initUserSettings` makes us miss the initial "compatibilityview-selected" + // event, so we need to manually call _onPanelSelected to fetch compatibility issues + // for the selected node (and the whole page). + this._onPanelSelected(); + + this.inspector.on("new-root", this._onTopLevelTargetChanged); + this.inspector.on("markupmutation", this._onMarkupMutation); + this.inspector.selection.on("new-node-front", this._onSelectedNodeChanged); + this.inspector.sidebar.on( + "compatibilityview-selected", + this._onPanelSelected + ); + + await this.resourceCommand.watchResources( + [this.resourceCommand.TYPES.CSS_CHANGE], + { + onAvailable: this._onResourceAvailable, + // CSS changes made before opening Compatibility View are already applied to + // corresponding DOM at this point, so existing resources can be ignored here. + ignoreExistingResources: true, + } + ); + + this.inspector.emitForTests("compatibilityview-initialized"); + } + + _isAvailable() { + return ( + this.inspector && + this.inspector.sidebar && + this.inspector.sidebar.getCurrentTabID() === "compatibilityview" && + this.inspector.selection && + this.inspector.selection.isConnected() + ); + } + + _parseMarkup(str) { + // Using a BrowserLoader for the inspector is currently blocked on performance regressions, + // see Bug 1471853. + throw new Error( + "The inspector cannot use tags in ftl strings because it does not run in a BrowserLoader" + ); + } + + _onChangeAdded({ selector }) { + if (!this._isAvailable()) { + // In order to update this panel if a change is added while hiding this panel. + this._isChangeAddedWhileHidden = true; + return; + } + + this._isChangeAddedWhileHidden = false; + + // We need to debounce updating nodes since "add-change" event on changes actor is + // fired for every typed character until fixing bug 1503036. + if (this._previousChangedSelector === selector) { + clearTimeout(this._updateNodesTimeoutId); + } + this._previousChangedSelector = selector; + + this._updateNodesTimeoutId = setTimeout(() => { + // TODO: In case of keyframes changes, the selector given from changes actor is + // keyframe-selector such as "from" and "100%", not selector for node. Thus, + // we need to address this case. + this.inspector.store.dispatch(updateNodes(selector)); + }, 500); + } + + _onMarkupMutation(mutations) { + const attributeMutation = mutations.filter( + mutation => + mutation.type === "attributes" && + (mutation.attributeName === "style" || + mutation.attributeName === "class") + ); + const childListMutation = mutations.filter( + mutation => mutation.type === "childList" + ); + + if (attributeMutation.length === 0 && childListMutation.length === 0) { + return; + } + + if (!this._isAvailable()) { + // In order to update this panel if a change is added while hiding this panel. + this._isChangeAddedWhileHidden = true; + return; + } + + this._isChangeAddedWhileHidden = false; + + // Resource Watcher doesn't respond to programmatic inline CSS + // change. This check can be removed once the following bug is resolved + // https://bugzilla.mozilla.org/show_bug.cgi?id=1506160 + for (const { target } of attributeMutation) { + this.inspector.store.dispatch(updateNode(target)); + } + + // Destroyed nodes can be cleaned up + // once at the end if necessary + let cleanupDestroyedNodes = false; + for (const { removed, target } of childListMutation) { + if (!removed.length) { + this.inspector.store.dispatch(appendNode(target)); + continue; + } + + const retainedNodes = removed.filter(node => node && !node.isDestroyed()); + cleanupDestroyedNodes = + cleanupDestroyedNodes || retainedNodes.length !== removed.length; + + for (const retainedNode of retainedNodes) { + this.inspector.store.dispatch(removeNode(retainedNode)); + } + } + + if (cleanupDestroyedNodes) { + this.inspector.store.dispatch(clearDestroyedNodes()); + } + } + + _onPanelSelected() { + const { selectedNode, topLevelTarget } = + this.inspector.store.getState().compatibility; + + // Update if the selected node is changed or new change is added while the panel was hidden. + if ( + this.inspector.selection.nodeFront !== selectedNode || + this._isChangeAddedWhileHidden + ) { + this._onSelectedNodeChanged(); + } + + // Update if the top target has changed or new change is added while the panel was hidden. + if ( + this.inspector.toolbox.target !== topLevelTarget || + this._isChangeAddedWhileHidden + ) { + this._onTopLevelTargetChanged(); + } + + this._isChangeAddedWhileHidden = false; + } + + _onSelectedNodeChanged() { + if (!this._isAvailable()) { + return; + } + + this.inspector.store.dispatch( + updateSelectedNode(this.inspector.selection.nodeFront) + ); + } + + _onResourceAvailable(resources) { + for (const resource of resources) { + // Style changes applied inline directly to + // the element and its changes are monitored by + // _onMarkupMutation via markupmutation events. + // Hence those changes can be ignored here + if (resource.source?.type !== "element") { + this._onChangeAdded(resource); + } + } + } + + _onTopLevelTargetChanged() { + if (!this._isAvailable()) { + return; + } + + this.inspector.store.dispatch( + updateTopLevelTarget(this.inspector.toolbox.target) + ); + } +} + +module.exports = CompatibilityView; |