browser_shadow_dom_and_custom_elements.js (8729B)
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 /** 8 * Test relation defaults via element internals 9 */ 10 addAccessibleTask( 11 ` 12 <div id="dependant1">label</div> 13 <custom-checkbox id="host"></custom-checkbox> 14 <div id="dependant2">label2</div> 15 16 <script> 17 customElements.define("custom-checkbox", 18 class extends HTMLElement { 19 constructor() { 20 super(); 21 this.tabIndex = "0"; 22 this._internals = this.attachInternals(); 23 this._internals.role = "checkbox"; 24 this._internals.ariaChecked = "true"; 25 } 26 get internals() { 27 return this._internals; 28 } 29 } 30 ); 31 </script>`, 32 async function (browser, accDoc) { 33 let host = findAccessibleChildByID(accDoc, "host"); 34 let dependant1 = findAccessibleChildByID(accDoc, "dependant1"); 35 let dependant2 = findAccessibleChildByID(accDoc, "dependant2"); 36 37 function invokeSetInternals(reflectionAttrName, targetIds) { 38 if (targetIds) { 39 Logger.log( 40 `Setting internals reflected ${reflectionAttrName} attribute to ${targetIds} for host` 41 ); 42 } else { 43 Logger.log( 44 `Removing internals reflected ${reflectionAttrName} attribute from node with host` 45 ); 46 } 47 48 return invokeContentTask( 49 browser, 50 [reflectionAttrName, targetIds], 51 (contentAttr, contentTargetIds) => { 52 let internals = content.document.getElementById("host").internals; 53 if (contentTargetIds) { 54 internals[contentAttr] = contentTargetIds.map(targetId => 55 content.document.getElementById(targetId) 56 ); 57 } else { 58 internals[contentAttr] = null; 59 } 60 } 61 ); 62 } 63 64 async function testInternalsRelation( 65 attrName, 66 reflectionAttrName, 67 hostRelation, 68 dependantRelation 69 ) { 70 info(`setting default ${reflectionAttrName}`); 71 await invokeSetInternals(reflectionAttrName, ["dependant1"]); 72 await testCachedRelation(host, hostRelation, [dependant1]); 73 await testCachedRelation(dependant1, dependantRelation, [host]); 74 await testCachedRelation(dependant2, dependantRelation, []); 75 76 info(`setting override ${attrName}`); 77 await invokeSetAttribute(browser, "host", attrName, "dependant2"); 78 await testCachedRelation(host, hostRelation, [dependant2]); 79 await testCachedRelation(dependant2, dependantRelation, [host]); 80 await testCachedRelation(dependant1, dependantRelation, []); 81 82 info(`unsetting default ${reflectionAttrName} and ${attrName} override`); 83 await invokeSetInternals(reflectionAttrName, null); 84 await invokeSetAttribute(browser, "host", attrName, null); 85 await testCachedRelation(host, hostRelation, []); 86 await testCachedRelation(dependant2, dependantRelation, []); 87 await testCachedRelation(dependant1, dependantRelation, []); 88 } 89 90 await testInternalsRelation( 91 "aria-labelledby", 92 "ariaLabelledByElements", 93 RELATION_LABELLED_BY, 94 RELATION_LABEL_FOR 95 ); 96 await testInternalsRelation( 97 "aria-describedby", 98 "ariaDescribedByElements", 99 RELATION_DESCRIBED_BY, 100 RELATION_DESCRIPTION_FOR 101 ); 102 await testInternalsRelation( 103 "aria-controls", 104 "ariaControlsElements", 105 RELATION_CONTROLLER_FOR, 106 RELATION_CONTROLLED_BY 107 ); 108 await testInternalsRelation( 109 "aria-flowto", 110 "ariaFlowToElements", 111 RELATION_FLOWS_TO, 112 RELATION_FLOWS_FROM 113 ); 114 await testInternalsRelation( 115 "aria-details", 116 "ariaDetailsElements", 117 RELATION_DETAILS, 118 RELATION_DETAILS_FOR 119 ); 120 await testInternalsRelation( 121 "aria-errormessage", 122 "ariaErrorMessageElements", 123 RELATION_ERRORMSG, 124 RELATION_ERRORMSG_FOR 125 ); 126 } 127 ); 128 129 /** 130 * Moving explicitly set elements across shadow DOM boundaries. 131 */ 132 addAccessibleTask( 133 ` 134 <div id="describedButtonContainer"> 135 <div id="buttonDescription1">Delicious</div> 136 <div id="buttonDescription2">Nutritious</div> 137 <div id="outerShadowHost"></div> 138 <button id="describedElement">Button</button> 139 </div> 140 141 <script> 142 const buttonDescription1 = document.getElementById("buttonDescription1"); 143 const buttonDescription2 = document.getElementById("buttonDescription2"); 144 const outerShadowRoot = outerShadowHost.attachShadow({mode: "open"}); 145 const innerShadowHost = document.createElement("div"); 146 outerShadowRoot.appendChild(innerShadowHost); 147 const innerShadowRoot = innerShadowHost.attachShadow({mode: "open"}); 148 149 const describedElement = document.getElementById("describedElement"); 150 // Add some attr associated light DOM elements. 151 describedElement.ariaDescribedByElements = [buttonDescription1, buttonDescription2]; 152 </script>`, 153 async function (browser, accDoc) { 154 const waitAndReturnRecreated = acc => { 155 const id = getAccessibleDOMNodeID(acc); 156 return waitForEvents([ 157 [EVENT_HIDE, acc], 158 [EVENT_SHOW, id], 159 ]).then(evts => evts[1].accessible); 160 }; 161 162 let describedAcc = findAccessibleChildByID(accDoc, "describedElement"); 163 let accDescription1 = findAccessibleChildByID(accDoc, "buttonDescription1"); 164 let accDescription2 = findAccessibleChildByID(accDoc, "buttonDescription2"); 165 166 // All elements were in the same scope, so relations are intact. 167 await testCachedRelation(describedAcc, RELATION_DESCRIBED_BY, [ 168 accDescription1, 169 accDescription2, 170 ]); 171 await testCachedRelation(accDescription1, RELATION_DESCRIPTION_FOR, [ 172 describedAcc, 173 ]); 174 await testCachedRelation(accDescription2, RELATION_DESCRIPTION_FOR, [ 175 describedAcc, 176 ]); 177 178 let onRecreated = waitAndReturnRecreated(describedAcc); 179 await invokeContentTask(browser, [], () => { 180 const outerShadowRoot = 181 content.document.getElementById("outerShadowHost").shadowRoot; 182 const describedElement = 183 content.document.getElementById("describedElement"); 184 outerShadowRoot.appendChild(describedElement); 185 }); 186 187 info("Waiting for described accessible to be recreated"); 188 describedAcc = await onRecreated; 189 // Relations should still be intact, we are referencing elements in a lighter scope. 190 await testCachedRelation(describedAcc, RELATION_DESCRIBED_BY, [ 191 accDescription1, 192 accDescription2, 193 ]); 194 await testCachedRelation(accDescription1, RELATION_DESCRIPTION_FOR, [ 195 describedAcc, 196 ]); 197 await testCachedRelation(accDescription2, RELATION_DESCRIPTION_FOR, [ 198 describedAcc, 199 ]); 200 201 // Move the explicitly set elements into a deeper shadow DOM. 202 onRecreated = Promise.all([ 203 waitAndReturnRecreated(accDescription1), 204 waitAndReturnRecreated(accDescription2), 205 ]); 206 await invokeContentTask(browser, [], () => { 207 const buttonDescription1 = 208 content.document.getElementById("buttonDescription1"); 209 const buttonDescription2 = 210 content.document.getElementById("buttonDescription2"); 211 const innerShadowRoot = 212 content.document.getElementById("outerShadowHost").shadowRoot 213 .firstElementChild.shadowRoot; 214 innerShadowRoot.appendChild(buttonDescription1); 215 innerShadowRoot.appendChild(buttonDescription2); 216 }); 217 218 [accDescription1, accDescription2] = await onRecreated; 219 220 // Relation is severed, because relation dependants are no longer in a valid scope. 221 await testCachedRelation(describedAcc, RELATION_DESCRIBED_BY, []); 222 await testCachedRelation(accDescription1, RELATION_DESCRIPTION_FOR, []); 223 await testCachedRelation(accDescription2, RELATION_DESCRIPTION_FOR, []); 224 225 // Move into the same shadow scope as the explicitly set elements. 226 onRecreated = waitAndReturnRecreated(describedAcc); 227 await invokeContentTask(browser, [], () => { 228 const outerShadowRoot = 229 content.document.getElementById("outerShadowHost").shadowRoot; 230 const describedElement = 231 outerShadowRoot.getElementById("describedElement"); 232 const innerShadowRoot = outerShadowRoot.firstElementChild.shadowRoot; 233 innerShadowRoot.appendChild(describedElement); 234 }); 235 236 describedAcc = await onRecreated; 237 // Relation is restored, because target is now in same shadow scope. 238 await testCachedRelation(describedAcc, RELATION_DESCRIBED_BY, [ 239 accDescription1, 240 accDescription2, 241 ]); 242 await testCachedRelation(accDescription1, RELATION_DESCRIPTION_FOR, [ 243 describedAcc, 244 ]); 245 await testCachedRelation(accDescription2, RELATION_DESCRIPTION_FOR, [ 246 describedAcc, 247 ]); 248 } 249 );