style-change-tracker.js (3177B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 8 const WalkerEventListener = require("resource://devtools/client/inspector/shared/walker-event-listener.js"); 9 10 /** 11 * The InspectorStyleChangeTracker simply emits an event when it detects any changes in 12 * the page that may cause the current inspector selection to have different style applied 13 * to it. 14 * It currently tracks: 15 * - markup mutations, because they may cause different CSS rules to apply to the current 16 * node. 17 * - window resize, because they may cause media query changes and therefore also 18 * different CSS rules to apply to the current node. 19 */ 20 class InspectorStyleChangeTracker { 21 constructor(inspector) { 22 this.selection = inspector.selection; 23 24 this.onMutations = this.onMutations.bind(this); 25 this.onResized = this.onResized.bind(this); 26 27 this.walkerEventListener = new WalkerEventListener(inspector, { 28 mutations: this.onMutations, 29 resize: this.onResized, 30 }); 31 32 EventEmitter.decorate(this); 33 } 34 35 destroy() { 36 this.walkerEventListener.destroy(); 37 this.walkerEventListener = null; 38 this.selection = null; 39 } 40 41 /** 42 * When markup mutations occur, if an attribute of the selected node, one of its 43 * ancestors or siblings changes, we need to consider this as potentially causing a 44 * style change for the current node. 45 */ 46 onMutations(mutations) { 47 const canMutationImpactCurrentStyles = ({ 48 type, 49 target: mutationTarget, 50 }) => { 51 // Only attributes mutations are interesting here. 52 if (type !== "attributes") { 53 return false; 54 } 55 56 // Is the mutation on the current selected node? 57 const currentNode = this.selection.nodeFront; 58 if (mutationTarget === currentNode) { 59 return true; 60 } 61 62 // Is the mutation on one of the current selected node's siblings? 63 // We can't know the order of nodes on the client-side without calling 64 // walker.children, so don't attempt to check the previous or next element siblings. 65 // It's good enough to know that one sibling changed. 66 let parent = currentNode.parentNode(); 67 const siblings = parent.treeChildren(); 68 if (siblings.includes(mutationTarget)) { 69 return true; 70 } 71 72 // Is the mutation on one of the current selected node's parents? 73 while (parent) { 74 if (mutationTarget === parent) { 75 return true; 76 } 77 parent = parent.parentNode(); 78 } 79 80 return false; 81 }; 82 83 for (const mutation of mutations) { 84 if (canMutationImpactCurrentStyles(mutation)) { 85 this.emit("style-changed"); 86 break; 87 } 88 } 89 } 90 91 /** 92 * When the window gets resized, this may cause media-queries to match, and we therefore 93 * need to consider this as a style change for the current node. 94 */ 95 onResized() { 96 this.emit("style-changed"); 97 } 98 } 99 100 module.exports = InspectorStyleChangeTracker;