browser_styleeditor_highlight-selector.js (8148B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Test that hovering over a simple selector in the style-editor requests the 7 // highlighting of the corresponding nodes, even in remote iframes. 8 9 const REMOTE_IFRAME_URL = `https://example.org/document-builder.sjs?html= 10 <style>h2{color:cyan}</style> 11 <h2>highlighter test</h2>`; 12 const TOP_LEVEL_URL = `https://example.com/document-builder.sjs?html= 13 <style>h1{color:red}</style> 14 <h1>highlighter test</h1> 15 <iframe src='${REMOTE_IFRAME_URL}'></iframe>`; 16 17 add_task(async function () { 18 const { ui } = await openStyleEditorForURL(TOP_LEVEL_URL); 19 20 info( 21 "Wait until both stylesheet are loaded and ready to handle mouse events" 22 ); 23 await waitFor(() => ui.editors.length == 2); 24 const topLevelStylesheetEditor = ui.editors.find(e => 25 e._resource.nodeHref.startsWith("https://example.com") 26 ); 27 const iframeStylesheetEditor = ui.editors.find(e => 28 e._resource.nodeHref.startsWith("https://example.org") 29 ); 30 31 await ui.selectStyleSheet(topLevelStylesheetEditor.styleSheet); 32 await waitFor(() => topLevelStylesheetEditor.highlighter); 33 34 info("Check that highlighting works on the top-level document"); 35 const topLevelHighlighterTestFront = 36 await topLevelStylesheetEditor._resource.targetFront.getFront( 37 "highlighterTest" 38 ); 39 topLevelHighlighterTestFront.highlighter = 40 topLevelStylesheetEditor.highlighter; 41 42 info("Expecting a node-highlighted event"); 43 let onHighlighted = topLevelStylesheetEditor.once("node-highlighted"); 44 45 info("Simulate a mousemove event on the h1 selector"); 46 // mousemove event listeners is set on editor.sourceEditor, which is not defined right away. 47 await waitFor(() => !!topLevelStylesheetEditor.sourceEditor); 48 let selectorEl = querySelectorCodeMirrorCssRuleSelectorToken( 49 topLevelStylesheetEditor 50 ); 51 EventUtils.synthesizeMouseAtCenter( 52 selectorEl, 53 { type: "mousemove" }, 54 selectorEl.ownerDocument.defaultView 55 ); 56 await onHighlighted; 57 ok( 58 await topLevelHighlighterTestFront.isNodeRectHighlighted( 59 await getElementNodeRectWithinTarget(["h1"]) 60 ), 61 "The highlighter's outline corresponds to the h1 node" 62 ); 63 64 info( 65 "Simulate a mousemove event on the property name to hide the highlighter" 66 ); 67 EventUtils.synthesizeMouseAtCenter( 68 querySelectorCodeMirrorCssPropertyNameToken(topLevelStylesheetEditor), 69 { type: "mousemove" }, 70 selectorEl.ownerDocument.defaultView 71 ); 72 73 await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); 74 let isVisible = await topLevelHighlighterTestFront.isHighlighting(); 75 is(isVisible, false, "The highlighter is now hidden"); 76 77 // It looks like we need to show the same highlighter again to trigger Bug 1847747 78 info("Show and hide the highlighter again"); 79 onHighlighted = topLevelStylesheetEditor.once("node-highlighted"); 80 EventUtils.synthesizeMouseAtCenter( 81 selectorEl, 82 { type: "mousemove" }, 83 selectorEl.ownerDocument.defaultView 84 ); 85 await onHighlighted; 86 EventUtils.synthesizeMouseAtCenter( 87 querySelectorCodeMirrorCssPropertyNameToken(topLevelStylesheetEditor), 88 { type: "mousemove" }, 89 selectorEl.ownerDocument.defaultView 90 ); 91 92 await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); 93 // wait for a bit so the style editor would have had the time to process 94 // any unwanted stylesheets 95 await wait(1000); 96 ok( 97 !ui.editors.find(e => e._resource.href?.includes("highlighters.css")), 98 "highlighters.css isn't displayed in StyleEditor" 99 ); 100 is(ui.editors.length, 2, "No other stylesheet was displayed"); 101 102 info("Check that highlighting works on the iframe document"); 103 await ui.selectStyleSheet(iframeStylesheetEditor.styleSheet); 104 await waitFor(() => iframeStylesheetEditor.highlighter); 105 106 const iframeHighlighterTestFront = 107 await iframeStylesheetEditor._resource.targetFront.getFront( 108 "highlighterTest" 109 ); 110 iframeHighlighterTestFront.highlighter = iframeStylesheetEditor.highlighter; 111 112 info("Expecting a node-highlighted event"); 113 onHighlighted = iframeStylesheetEditor.once("node-highlighted"); 114 115 info("Simulate a mousemove event on the h2 selector"); 116 // mousemove event listeners is set on editor.sourceEditor, which is not defined right away. 117 await waitFor(() => !!iframeStylesheetEditor.sourceEditor); 118 selectorEl = querySelectorCodeMirrorCssRuleSelectorToken( 119 iframeStylesheetEditor 120 ); 121 EventUtils.synthesizeMouseAtCenter( 122 selectorEl, 123 { type: "mousemove" }, 124 selectorEl.ownerDocument.defaultView 125 ); 126 await onHighlighted; 127 128 isVisible = await iframeHighlighterTestFront.isHighlighting(); 129 ok(isVisible, "The highlighter is shown"); 130 ok( 131 await iframeHighlighterTestFront.isNodeRectHighlighted( 132 await getElementNodeRectWithinTarget(["iframe", "h2"]) 133 ), 134 "The highlighter's outline corresponds to the h2 node" 135 ); 136 137 info("Simulate a mousemove event elsewhere in the editor"); 138 EventUtils.synthesizeMouseAtCenter( 139 querySelectorCodeMirrorCssPropertyNameToken(iframeStylesheetEditor), 140 { type: "mousemove" }, 141 selectorEl.ownerDocument.defaultView 142 ); 143 144 await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); 145 146 isVisible = await iframeHighlighterTestFront.isHighlighting(); 147 is(isVisible, false, "The highlighter is now hidden"); 148 }); 149 150 function querySelectorCodeMirrorCssRuleSelectorToken(stylesheetEditor) { 151 // CSS Rules selector (e.g. `h1`) are displayed in a .cm-tag span 152 return querySelectorCodeMirror(stylesheetEditor, ".cm-tag"); 153 } 154 155 function querySelectorCodeMirrorCssPropertyNameToken(stylesheetEditor) { 156 // properties name (e.g. `color`) are displayed in a .cm-property span 157 return querySelectorCodeMirror(stylesheetEditor, ".cm-property"); 158 } 159 160 function querySelectorCodeMirror(stylesheetEditor, selector) { 161 return stylesheetEditor.sourceEditor.codeMirror 162 .getWrapperElement() 163 .querySelector(selector); 164 } 165 166 /** 167 * Return the bounds of the element matching the selector, relatively to the target bounds 168 * (e.g. if Fission is enabled, it's related to the iframe bound, if Fission is disabled, 169 * it's related to the top level document). 170 * 171 * @param {Array<string>} selectors: Arrays of CSS selectors from the root document to the node. 172 * The last CSS selector of the array is for the node in its frame doc. 173 * The before-last CSS selector is for the frame in its parent frame, etc... 174 * Ex: ["frame.first-frame", ..., "frame.last-frame", ".target-node"] 175 * @returns {object} with left/top/width/height properties representing the node bounds 176 */ 177 async function getElementNodeRectWithinTarget(selectors) { 178 // Retrieve the browsing context in which the element is 179 const inBCSelector = selectors.pop(); 180 const frameSelectors = selectors; 181 const bc = frameSelectors.length 182 ? await getBrowsingContextInFrames( 183 gBrowser.selectedBrowser.browsingContext, 184 frameSelectors 185 ) 186 : gBrowser.selectedBrowser.browsingContext; 187 188 // Get the element bounds within the Firefox window 189 const elementBounds = await SpecialPowers.spawn( 190 bc, 191 [inBCSelector], 192 _selector => { 193 const el = content.document.querySelector(_selector); 194 const { left, top, width, height } = el 195 .getBoxQuadsFromWindowOrigin()[0] 196 .getBounds(); 197 return { left, top, width, height }; 198 } 199 ); 200 201 // Then we need to offset the element bounds from a frame bounds 202 // The highlighter is only shown within the iframe bounds. So we only need to 203 // retrieve the element bounds within the iframe. 204 const relativeBrowsingContext = bc; 205 const relativeDocumentBounds = await SpecialPowers.spawn( 206 relativeBrowsingContext, 207 [], 208 () => 209 content.document.documentElement 210 .getBoxQuadsFromWindowOrigin()[0] 211 .getBounds() 212 ); 213 214 // Adjust the element bounds based on the relative document bounds 215 elementBounds.left = elementBounds.left - relativeDocumentBounds.left; 216 elementBounds.top = elementBounds.top - relativeDocumentBounds.top; 217 218 return elementBounds; 219 }