head.js (11191B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Import the inspector's head.js first (which itself imports shared-head.js). 7 Services.scriptloader.loadSubScript( 8 "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js", 9 this 10 ); 11 12 /** 13 * Dispatch the copy event on the given element 14 */ 15 function fireCopyEvent(element) { 16 const evt = element.ownerDocument.createEvent("Event"); 17 evt.initEvent("copy", true, true); 18 element.dispatchEvent(evt); 19 } 20 21 /** 22 * Return all the computed items in the computed view 23 * 24 * @param {CssComputedView} view 25 * The instance of the computed view panel 26 * @returns {Array<Element>} 27 */ 28 function getComputedViewProperties(view) { 29 return Array.from( 30 view.styleDocument.querySelectorAll( 31 "#computed-container .computed-property-view" 32 ) 33 ); 34 } 35 36 /** 37 * Get references to the name and value span nodes corresponding to a given 38 * property name in the computed-view 39 * 40 * @param {CssComputedView} view 41 * The instance of the computed view panel 42 * @param {string} name 43 * The name of the property to retrieve 44 * @return an object {nameSpan, valueSpan} 45 */ 46 function getComputedViewProperty(view, name) { 47 let prop; 48 for (const property of getComputedViewProperties(view)) { 49 const nameSpan = property.querySelector(".computed-property-name"); 50 const valueSpan = property.querySelector(".computed-property-value"); 51 52 if (nameSpan.firstChild.textContent === name) { 53 prop = { nameSpan, valueSpan }; 54 break; 55 } 56 } 57 return prop; 58 } 59 60 /** 61 * Get an instance of PropertyView from the computed-view. 62 * 63 * @param {CssComputedView} view 64 * The instance of the computed view panel 65 * @param {string} name 66 * The name of the property to retrieve 67 * @return {PropertyView} 68 */ 69 function getComputedViewPropertyView(view, name) { 70 return view.propertyViews.find(propertyView => propertyView.name === name); 71 } 72 73 /** 74 * Get a reference to the matched rules element for a given property name in 75 * the computed-view. 76 * A matched rule element is inside the property element (<li>) itself 77 * and is only shown when the twisty icon is expanded on the property. 78 * It contains matched rules, with selectors, properties, values and stylesheet links. 79 * 80 * @param {CssComputedView} view 81 * The instance of the computed view panel 82 * @param {string} name 83 * The name of the property to retrieve 84 * @return {Promise} A promise that resolves to the property matched rules 85 * container 86 */ 87 var getComputedViewMatchedRules = async function (view, name) { 88 let expander; 89 let matchedRulesEl; 90 for (const property of view.styleDocument.querySelectorAll( 91 "#computed-container .computed-property-view" 92 )) { 93 const nameSpan = property.querySelector(".computed-property-name"); 94 if (nameSpan.firstChild.textContent === name) { 95 expander = property.querySelector(".computed-expandable"); 96 matchedRulesEl = property.querySelector(".matchedselectors"); 97 98 break; 99 } 100 } 101 102 if (!expander.hasAttribute("open")) { 103 // Need to expand the property 104 const onExpand = view.inspector.once("computed-view-property-expanded"); 105 expander.click(); 106 await onExpand; 107 108 await waitFor(() => expander.hasAttribute("open")); 109 } 110 111 return matchedRulesEl; 112 }; 113 114 /** 115 * Get the text value of the property corresponding to a given name in the 116 * computed-view 117 * 118 * @param {CssComputedView} view 119 * The instance of the computed view panel 120 * @param {string} name 121 * The name of the property to retrieve 122 * @return {string} The property value 123 */ 124 function getComputedViewPropertyValue(view, name) { 125 return getComputedViewProperty(view, name).valueSpan.textContent; 126 } 127 128 /** 129 * Expand a given property, given its index in the current property list of 130 * the computed view 131 * 132 * @param {CssComputedView} view 133 * The instance of the computed view panel 134 * @param {number} index 135 * The index of the property to be expanded 136 * @return a promise that resolves when the property has been expanded, or 137 * rejects if the property was not found 138 */ 139 function expandComputedViewPropertyByIndex(view, index) { 140 info("Expanding property " + index + " in the computed view"); 141 const expandos = view.styleDocument.querySelectorAll(".computed-expandable"); 142 if (!expandos.length || !expandos[index]) { 143 return Promise.reject(); 144 } 145 146 const onExpand = view.inspector.once("computed-view-property-expanded"); 147 expandos[index].click(); 148 return onExpand; 149 } 150 151 /** 152 * Get a rule-link from the computed-view given its index 153 * 154 * @param {CssComputedView} view 155 * The instance of the computed view panel 156 * @param {number} index 157 * The index of the matched selector element 158 * @return {DOMNode} The link at the given index, if one exists, null otherwise 159 */ 160 function getComputedViewLinkByIndex(view, index) { 161 const matchedSelectors = view.styleDocument.querySelectorAll( 162 ".matchedselectors > p" 163 ); 164 const matchedSelector = matchedSelectors[index]; 165 if (!matchedSelector) { 166 return null; 167 } 168 169 return matchedSelector.querySelector(`.rule-link .computed-link`); 170 } 171 172 /** 173 * Trigger the select all action in the computed view. 174 * 175 * @param {CssComputedView} view 176 * The instance of the computed view panel 177 */ 178 function selectAllText(view) { 179 info("Selecting all the text"); 180 view.contextMenu._onSelectAll(); 181 } 182 183 /** 184 * Select all the text, copy it, and check the content in the clipboard. 185 * 186 * @param {CssComputedView} view 187 * The instance of the computed view panel 188 * @param {string} expectedPattern 189 * A regular expression used to check the content of the clipboard 190 */ 191 async function copyAllAndCheckClipboard(view, expectedPattern) { 192 selectAllText(view); 193 const contentDoc = view.styleDocument; 194 const prop = contentDoc.querySelector( 195 "#computed-container .computed-property-view" 196 ); 197 198 try { 199 info("Trigger a copy event and wait for the clipboard content"); 200 await waitForClipboardPromise( 201 () => fireCopyEvent(prop), 202 () => checkClipboard(expectedPattern) 203 ); 204 } catch (e) { 205 failClipboardCheck(expectedPattern); 206 } 207 } 208 209 /** 210 * Select some text, copy it, and check the content in the clipboard. 211 * 212 * @param {CssComputedView} view 213 * The instance of the computed view panel 214 * @param {object} positions 215 * The start and end positions of the text to be selected. This must be an object 216 * like this: 217 * { start: {prop: 1, offset: 0}, end: {prop: 3, offset: 5} } 218 * @param {string} expectedPattern 219 * A regular expression used to check the content of the clipboard 220 */ 221 async function copySomeTextAndCheckClipboard(view, positions, expectedPattern) { 222 info("Testing selection copy"); 223 224 const contentDocument = view.styleDocument; 225 const props = contentDocument.querySelectorAll( 226 "#computed-container .computed-property-view" 227 ); 228 229 info("Create the text selection range"); 230 const range = contentDocument.createRange(); 231 range.setStart(props[positions.start.prop], positions.start.offset); 232 range.setEnd(props[positions.end.prop], positions.end.offset); 233 contentDocument.defaultView.getSelection().addRange(range); 234 235 try { 236 info("Trigger a copy event and wait for the clipboard content"); 237 await waitForClipboardPromise( 238 () => fireCopyEvent(props[0]), 239 () => checkClipboard(expectedPattern) 240 ); 241 } catch (e) { 242 failClipboardCheck(expectedPattern); 243 } 244 } 245 246 function checkClipboard(expectedPattern) { 247 const actual = SpecialPowers.getClipboardData("text/plain"); 248 const expectedRegExp = new RegExp(expectedPattern, "g"); 249 return expectedRegExp.test(actual); 250 } 251 252 function failClipboardCheck(expectedPattern) { 253 // Format expected text for comparison 254 const terminator = Services.appinfo.OS == "WINNT" ? "\r\n" : "\n"; 255 expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator); 256 expectedPattern = expectedPattern.replace(/\\\(/g, "("); 257 expectedPattern = expectedPattern.replace(/\\\)/g, ")"); 258 259 let actual = SpecialPowers.getClipboardData("text/plain"); 260 261 // Trim the right hand side of our strings. This is because expectedPattern 262 // accounts for windows sometimes adding a newline to our copied data. 263 expectedPattern = expectedPattern.trimRight(); 264 actual = actual.trimRight(); 265 266 dump( 267 "TEST-UNEXPECTED-FAIL | Clipboard text does not match expected ... " + 268 "results (escaped for accurate comparison):\n" 269 ); 270 info("Actual: " + escape(actual)); 271 info("Expected: " + escape(expectedPattern)); 272 } 273 274 /** 275 * Check that the given property has the expected value and the expected matched selectors 276 * 277 * @param {CssComputedView} view 278 * The instance of the computed view panel 279 * @param {object} options 280 * @param {string} options.property 281 * The property name to check 282 * @param {string} options.expectedComputedValue 283 * The expected value displayed for the property 284 * @param {object[]} options.expectedMatchedSelectors 285 * An array of objects describing the expected matched selectors 286 * @param {string} options.expectedMatchedSelectors[].selector 287 * The selector that should be displayed at this index 288 * @param {string} options.expectedMatchedSelectors[].value 289 * The value that should be displayed at this index 290 * @param {boolean} options.expectedMatchedSelectors[].match 291 * Whether the selector should match the currently selected element. Defaults to true. 292 */ 293 async function checkMatchedSelectorForProperty( 294 view, 295 { property, expectedComputedValue, expectedMatchedSelectors } 296 ) { 297 const propertyView = getComputedViewPropertyView(view, property); 298 ok(propertyView, `found PropertyView for "${property}"`); 299 const { valueNode } = propertyView; 300 is( 301 valueNode.textContent, 302 expectedComputedValue, 303 `Expected displayed computed value for "${property}"` 304 ); 305 306 is(propertyView.hasMatchedSelectors, true, "hasMatchedSelectors is true"); 307 308 info("Expanding the matched selectors"); 309 propertyView.matchedExpanded = true; 310 await propertyView.refreshMatchedSelectors(); 311 312 const selectorsEl = 313 propertyView.matchedSelectorsContainer.querySelectorAll(".rule-text"); 314 is( 315 selectorsEl.length, 316 expectedMatchedSelectors.length, 317 "Expected number of selectors are displayed" 318 ); 319 320 selectorsEl.forEach((selectorEl, index) => { 321 is( 322 selectorEl.querySelector(".fix-get-selection").innerText, 323 expectedMatchedSelectors[index].selector, 324 `Selector #${index} is the expected one` 325 ); 326 is( 327 selectorEl.querySelector(".computed-other-property-value").innerText, 328 expectedMatchedSelectors[index].value, 329 `Selector #${index} ("${expectedMatchedSelectors[index].selector}") has the expected "${property}"` 330 ); 331 const classToMatch = index === 0 ? "bestmatch" : "matched"; 332 const expectedMatch = expectedMatchedSelectors[index].match ?? true; 333 is( 334 selectorEl.classList.contains(classToMatch), 335 expectedMatch, 336 `Selector #${index} ("${expectedMatchedSelectors[index].selector}") element does ${expectedMatch ? "" : "not "}have a matching class` 337 ); 338 }); 339 }