browser_markup_search_01.js (12051B)
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 searching for nodes using the selector-search input expands and 7 // selects the right nodes in the markup-view, even when those nodes are deeply 8 // nested (and therefore not attached yet when the markup-view is initialized). 9 10 const TEST_URL = URL_ROOT + "doc_markup_search.html"; 11 const DEVTOOLS_SEARCH_HIGHLIGHT_NAME = "devtools-search"; 12 13 add_task(async function () { 14 const { inspector } = await openInspectorForURL(TEST_URL); 15 16 let container = await getContainerForSelector("em", inspector, true); 17 ok(!container, "The <em> tag isn't present yet in the markup-view"); 18 19 // Searching for the innermost element first makes sure that the inspector 20 // back-end is able to attach the resulting node to the tree it knows at the 21 // moment. When the inspector is started, the <body> is the default selected 22 // node, and only the parents up to the ROOT are known, and its direct 23 // children. 24 info("searching for the innermost child: <em>"); 25 await searchInMarkupView(inspector, "em"); 26 27 container = await getContainerForSelector("em", inspector); 28 ok(container, "The <em> tag is now imported in the markup-view"); 29 30 let nodeFront = await getNodeFront("em", inspector); 31 is( 32 inspector.selection.nodeFront, 33 nodeFront, 34 "The <em> tag is the currently selected node" 35 ); 36 ok( 37 inspector.markup.win.CSS.highlights.has(DEVTOOLS_SEARCH_HIGHLIGHT_NAME), 38 `"${DEVTOOLS_SEARCH_HIGHLIGHT_NAME}" CSS highlight does exist` 39 ); 40 41 checkHighlightedSearchResults(inspector, [ 42 // Opening tag 43 "em", 44 // Closing tag 45 "em", 46 ]); 47 48 info("searching for other nodes too"); 49 for (const node of ["span", "li", "ul"]) { 50 await searchInMarkupView(inspector, node); 51 52 nodeFront = await getNodeFront(node, inspector); 53 is( 54 inspector.selection.nodeFront, 55 nodeFront, 56 "The <" + node + "> tag is the currently selected node" 57 ); 58 // We still get 2 Ranges on those items: even if only the opening tag is visible (because 59 // the elements are expanded to show their children), a closing tag is actually 60 // rendered and hidden in CSS. 61 checkHighlightedSearchResults(inspector, [node, node]); 62 } 63 64 await searchInMarkupView(inspector, "BUTT"); 65 is( 66 inspector.selection.nodeFront, 67 await getNodeFront(".Buttons", inspector), 68 "The section.Buttons element is selected" 69 ); 70 // Selected node markup: <section class="Buttons"> 71 checkHighlightedSearchResults(inspector, ["Butt"]); 72 73 await searchInMarkupView(inspector, "BUT"); 74 is( 75 inspector.selection.nodeFront, 76 await getNodeFront(".Buttons", inspector), 77 "The section.Buttons element is selected" 78 ); 79 checkHighlightedSearchResults(inspector, ["But"]); 80 81 let onSearchResult = inspector.search.once("search-result"); 82 inspector.searchNextButton.click(); 83 info("Waiting for results"); 84 await onSearchResult; 85 86 is( 87 inspector.selection.nodeFront, 88 await getNodeFront(`button[type="button"]`, inspector), 89 `The button[type="button"] element is selected` 90 ); 91 // Selected node markup: <button type="button" class="Button">OK</button> 92 checkHighlightedSearchResults(inspector, [ 93 // opening tag (`<button`) 94 "but", 95 // class attribute (`class="Button"`) 96 // Attributes are re-ordered in the markup view, that's wy this is coming before 97 // the result for the type attribute. 98 "But", 99 // type attribute (`type="button"`) 100 "but", 101 // closing tag (`</button`) 102 "but", 103 ]); 104 105 onSearchResult = inspector.search.once("search-result"); 106 inspector.searchNextButton.click(); 107 info("Waiting for results"); 108 await onSearchResult; 109 110 is( 111 inspector.selection.nodeFront, 112 await getNodeFront(`section.Buttons > p`, inspector), 113 `The p element is selected` 114 ); 115 // Selected node markup: <p>Click the button</p> 116 checkHighlightedSearchResults(inspector, ["but"]); 117 118 const onSearchCleared = inspector.once("search-cleared"); 119 inspector.searchClearButton.click(); 120 info("Waiting for search to clear"); 121 await onSearchCleared; 122 123 checkHighlightedSearchResults(inspector, []); 124 125 await searchInMarkupView(inspector, "TALLTOPMATCH"); 126 const talltopNodeFront = await getNodeFront("section.talltop", inspector); 127 const talltopNodeFrontChildren = 128 await inspector.walker.children(talltopNodeFront); 129 is( 130 inspector.selection.nodeFront, 131 talltopNodeFrontChildren.nodes[0], 132 `The section.talltop text node is selected` 133 ); 134 checkHighlightedSearchResults(inspector, ["TALLTOPMATCH"]); 135 136 await searchInMarkupView(inspector, "TALLBOTTOMMATCH"); 137 const tallbottomNodeFront = await getNodeFront( 138 "section.tallbottom", 139 inspector 140 ); 141 const tallbottomNodeFrontChildren = 142 await inspector.walker.children(tallbottomNodeFront); 143 is( 144 inspector.selection.nodeFront, 145 tallbottomNodeFrontChildren.nodes[0], 146 `The section.tallbottom text node is selected` 147 ); 148 checkHighlightedSearchResults(inspector, ["TALLBOTTOMMATCH"]); 149 150 await searchInMarkupView(inspector, "OVERFLOWSMATCH"); 151 const overflowsNodeFront = await getNodeFront("section.overflows", inspector); 152 const overflowsNodeFrontChildren = 153 await inspector.walker.children(overflowsNodeFront); 154 is( 155 inspector.selection.nodeFront, 156 overflowsNodeFrontChildren.nodes[0], 157 "The section.overflows text node is selected" 158 ); 159 checkHighlightedSearchResults(inspector, ["OVERFLOWSMATCH"]); 160 161 info( 162 "Check that matching node with non-visible search result are still being scrolled to" 163 ); 164 // Scroll to top to make sure the node isn't in view at first 165 const markupViewContainer = inspector.markup.win.document.documentElement; 166 markupViewContainer.scrollTop = 0; 167 markupViewContainer.scrollLeft = 0; 168 169 const croppedAttributeContainer = await getContainerForSelector( 170 "section#cropped-attribute", 171 inspector 172 ); 173 let croppedAttributeContainerRect = 174 croppedAttributeContainer.elt.getBoundingClientRect(); 175 176 ok( 177 croppedAttributeContainerRect.y < 0 || 178 croppedAttributeContainerRect.y > markupViewContainer.clientHeight, 179 "section#cropped-attribute container is not into view before searching for a match in its attributes" 180 ); 181 182 await searchInMarkupView(inspector, "croppedvalue"); 183 is( 184 inspector.selection.nodeFront, 185 await getNodeFront("section#cropped-attribute", inspector), 186 "The section#cropped-attribute element is selected" 187 ); 188 checkHighlightedSearchResults(inspector, []); 189 // Check that node visible after it was selected 190 croppedAttributeContainerRect = 191 croppedAttributeContainer.elt.getBoundingClientRect(); 192 193 Assert.greaterOrEqual( 194 croppedAttributeContainerRect.y, 195 0, 196 `Node with cropped attributes is not above visible viewport` 197 ); 198 Assert.less( 199 croppedAttributeContainerRect.y, 200 markupViewContainer.clientHeight, 201 `Node with cropped attributes is not below visible viewport` 202 ); 203 204 // Sanity check to make sure the markup view does overflow in both axes. We need to 205 // wait after the search is done as their text node is only revealed when cycling through 206 // search results. 207 Assert.greater( 208 markupViewContainer.scrollHeight, 209 markupViewContainer.clientHeight, 210 "Markup view overflows vertically" 211 ); 212 Assert.greater( 213 markupViewContainer.scrollWidth, 214 markupViewContainer.clientWidth, 215 "Markup view overflows horizontally" 216 ); 217 218 info("Search for pseudo elements"); 219 220 await searchInMarkupView(inspector, "::before"); 221 is( 222 inspector.selection.nodeFront.displayName, 223 "::before", 224 "The ::before element is selected" 225 ); 226 checkHighlightedSearchResults(inspector, ["::before"]); 227 228 await searchInMarkupView(inspector, "::after"); 229 is( 230 inspector.selection.nodeFront.displayName, 231 "::after", 232 "The ::after element is selected" 233 ); 234 checkHighlightedSearchResults(inspector, ["::after"]); 235 236 await searchInMarkupView(inspector, "::marker"); 237 is( 238 inspector.selection.nodeFront.displayName, 239 "::marker", 240 "The ::marker element is selected" 241 ); 242 checkHighlightedSearchResults(inspector, ["::marker"]); 243 244 await searchInMarkupView(inspector, "::backdrop"); 245 is( 246 inspector.selection.nodeFront.displayName, 247 "::backdrop", 248 "The ::backdrop element is selected" 249 ); 250 checkHighlightedSearchResults(inspector, ["::backdrop"]); 251 252 // Search by the `content` declaration of the ::before and ::after pseudo elements 253 await searchInMarkupView(inspector, "my_before_text"); 254 is( 255 inspector.selection.nodeFront.displayName, 256 "::before", 257 "The ::before element is selected" 258 ); 259 // no highlighting as the `content` text isn't displayed in the markup view 260 checkHighlightedSearchResults(inspector, []); 261 262 await searchInMarkupView(inspector, "my_after_text"); 263 is( 264 inspector.selection.nodeFront.displayName, 265 "::after", 266 "The ::after element is selected" 267 ); 268 // no highlighting as the `content` text isn't displayed in the markup view 269 checkHighlightedSearchResults(inspector, []); 270 271 info("Search for view-transition pseudo elements"); 272 // Trigger the view transition 273 const onMarkupMutation = inspector.once("markupmutation"); 274 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { 275 const document = content.document; 276 content.testTransition = document.startViewTransition(() => { 277 document.querySelector(".pseudos").replaceChildren("updated"); 278 }); 279 await content.testTransition.ready; 280 await content.testTransition.updateCallbackDone; 281 }); 282 await onMarkupMutation; 283 284 await searchInMarkupView(inspector, "::view-transition"); 285 is( 286 inspector.selection.nodeFront.displayName, 287 "::view-transition", 288 "The ::view-transition element is selected" 289 ); 290 checkHighlightedSearchResults(inspector, ["::view-transition"]); 291 292 await searchInMarkupView(inspector, "::view-transition-old(root)"); 293 is( 294 inspector.selection.nodeFront.displayName, 295 "::view-transition-old(root)", 296 "The ::view-transition-old(root) element is selected" 297 ); 298 checkHighlightedSearchResults(inspector, ["::view-transition-old(root)"]); 299 300 await searchInMarkupView(inspector, "::view-transition-new(custom)"); 301 is( 302 inspector.selection.nodeFront.displayName, 303 "::view-transition-new(custom)", 304 "The ::view-transition-new(custom) element is selected" 305 ); 306 checkHighlightedSearchResults(inspector, ["::view-transition-new(custom)"]); 307 308 // Cancel transition 309 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { 310 content.testTransition.skipTransition(); 311 delete content.testTransition; 312 }); 313 }); 314 315 function checkHighlightedSearchResults(inspector, expectedHighlights) { 316 const searchInputValue = getMarkupViewSearchInput(inspector).value; 317 318 info(`Checking highlights for "${searchInputValue}" search`); 319 const devtoolsHighlights = [ 320 ...inspector.markup.win.CSS.highlights 321 .get(DEVTOOLS_SEARCH_HIGHLIGHT_NAME) 322 .values(), 323 ]; 324 Assert.deepEqual( 325 devtoolsHighlights.map(range => range.toString()), 326 expectedHighlights, 327 `Got expected highlights for "${searchInputValue}"` 328 ); 329 330 if (expectedHighlights.length) { 331 const markupViewContainer = inspector.markup.win.document.documentElement; 332 info( 333 `Check that we scrolled so the first highlighted range for "${searchInputValue}" is visible` 334 ); 335 const [rect] = devtoolsHighlights[0].getClientRects(); 336 const { x, y } = rect; 337 338 Assert.greaterOrEqual( 339 y, 340 0, 341 `First "${searchInputValue}" match not above visible viewport` 342 ); 343 Assert.less( 344 y, 345 markupViewContainer.clientHeight, 346 `First "${searchInputValue}" match not below visible viewport` 347 ); 348 Assert.greaterOrEqual( 349 x, 350 0, 351 `First "${searchInputValue}" match not before the "left border" of the visible viewport` 352 ); 353 Assert.less( 354 x, 355 markupViewContainer.clientWidth, 356 `First "${searchInputValue}" match not after the "right border" of the visible viewport` 357 ); 358 } 359 }