1// Intercept attachShadow to capture EqualWeb's shadow root 2const nativeAttachShadow = Element.prototype.attachShadow; 3let equalWebShadowRoot; 4 5Element.prototype.attachShadow = function (init) { 6 const root = nativeAttachShadow.call(this, init); 7 8 if (this.id === 'INDShadowRootHost') { 9 equalWebShadowRoot = root; 10 11 // OBR-8339: Inject CSS synchronously to hide the default button 12 // before it is even added to the shadow DOM â prevents any flash 13 const style = document.createElement('style'); 14 style.textContent = '#INDmenu-btn { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; }'; 15 root.appendChild(style); 16 17 // Restore original method after capturing 18 Element.prototype.attachShadow = nativeAttachShadow; 19 } 20 21 return root; 22}; 23 24// OBR-9001: Dispatch custom event on URL change so equal-web-button can show/hide by path 25const dispatchUrlChange = () => window.dispatchEvent(new CustomEvent('equalweb-urlchange')); 26const originalPushState = history.pushState; 27const originalReplaceState = history.replaceState; 28history.pushState = function (...args) { 29 originalPushState.apply(this, args); 30 dispatchUrlChange(); 31}; 32history.replaceState = function (...args) { 33 originalReplaceState.apply(this, args); 34 dispatchUrlChange(); 35}; 36window.addEventListener('popstate', dispatchUrlChange); 37 38const hideEqualWebButton = () => { 39 const button = equalWebShadowRoot?.querySelector('#INDmenu-btn'); 40 41 if (!button) { 42 return; 43 } 44 45 button.style.setProperty('display', 'none', 'important'); 46 button.style.setProperty('visibility', 'hidden', 'important'); 47 button.style.setProperty('opacity', '0', 'important'); 48 button.style.setProperty('pointer-events', 'none', 'important'); 49}; 50 51customElements.define( 52 'equal-web-button', 53 class extends HTMLElement { 54 constructor() { 55 super(); 56 57 // Keep hidden until _init() makes it visible 58 this.style.setProperty('display', 'none', 'important'); 59 60 if (window.interdeal) { 61 this._init(); 62 } else { 63 this._waitForInterdeal(); 64 } 65 } 66 67 _waitForInterdeal() { 68 const self = this; 69 let resolved = false; 70 71 const resolve = () => { 72 if (resolved) return; 73 resolved = true; 74 clearInterval(pollTimer); 75 // Restore normal property after intercepting 76 try { 77 Object.defineProperty(window, 'interdeal', { 78 configurable: true, 79 writable: true, 80 value: window.interdeal, 81 }); 82 } catch (_) {} 83 self._init(); 84 }; 85 86 try { 87 Object.defineProperty(window, 'interdeal', { 88 configurable: true, 89 get() { return undefined; }, 90 set(value) { 91 Object.defineProperty(window, 'interdeal', { 92 configurable: true, 93 writable: true, 94 value, 95 }); 96 resolve(); 97 }, 98 }); 99 } catch (_) {} 100 101 // Fallback: poll in case setter is bypassed (e.g. Object.assign) 102 const pollTimer = setInterval(() => { 103 if (window.interdeal) resolve(); 104 }, 200); 105 106 // Give up after 15s if EqualWeb never loads 107 setTimeout(() => { 108 clearInterval(pollTimer); 109 }, 15000); 110 } 111 112 _init() { 113 const supportsDeclarative = HTMLElement.prototype.hasOwnProperty('attachInternals'); 114 const internals = supportsDeclarative ? this.attachInternals() : undefined; 115 116 const close = () => { 117 // set cookie on close button click 118 document.cookie = 'OBRhideAccBtn=true';
119 120 // hide the button 121 this.style.display = 'none'; 122 this.style.visibility = 'hidden'; 123 this.style.opacity = '0'; 124 this.style.pointerEvents = 'none'; 125 }; 126 127 const openMenu = () => { 128 window.interdeal.a11y.openMenu(); 129 }; 130 131 // check for a Declarative Shadow Root. 132 let shadow = internals?.shadowRoot; 133 134 if (!shadow) { 135 // there wasn't one. create a new Shadow Root: 136 shadow = this.attachShadow({ 137 mode: 'open' 138 }); 139 shadow.innerHTML = ` 140 <style> 141 :host { 142 position: fixed; 143 left: 0; 144 top: 50%; 145 transform: translateY(-50%); 146 z-index: 1000; 147 display: flex; 148 align-items: center; 149 justify-content: center; 150 gap: 0; 151 background-color: var(--atomic-background-color-12); 152 color: #fff; 153 border: 2px solid #fff; 154 border-top-right-radius: 56px; 155 border-bottom-right-radius: 56px; 156 height: 52px; 157 min-width: 76px; 158 padding: 8px 4px 8px 2px; 159 } 160 161 :host:has(._accessibility:not([style*='overflow: hidden'])) { 162 display: none; 163 } 164 165 button { 166 background: transparent; 167 border: none; 168 cursor: pointer; 169 padding: 4px; 170 } 171 172 button#INDMenu-close { 173 width: 32px; 174 height: 32px; 175 } 176 177 button#INDMenu-close img { 178 width: 24px; 179 height: 24px; 180 } 181 182 button#INDmenu-btn { 183 width: 35px; 184 height: 35px; 185 } 186 187 button#INDmenu-btn img { 188 width: 27px; 189 height: 27px; 190 } 191 </style> 192 <button type="button" aria-label="Hide accessibility button" id="INDMenu-close"> 193 <img src="/assets/equal-web/close-24.svg" 194 alt="Close"> 195 </button> 196 <button aria-label="Open accessibility options menu" 197 id="INDmenu-btn"> 198 <img src="/assets/equal-web/accessibility.svg" 199 alt="Accessibility"> 200 </button> 201 `; 202 } 203 204 // add event listeners to the buttons 205 shadow.querySelector('#INDMenu-close').addEventListener('click', close); 206 shadow.querySelector('#INDmenu-btn').addEventListener('click', openMenu); 207 208 // OBR-9001: On /accessibility always show button and hide close button; otherwise respect cookie 209 const updateForUrl = () => { 210 const onAccessibility = window.location.pathname.includes('/accessibility'); 211 const closeBtn = shadow.querySelector('#INDMenu-close'); 212 if (onAccessibility) { 213 this.style.display = ''; 214 this.style.visibility = ''; 215 this.style.opacity = ''; 216 this.style.pointerEvents = ''; 217 closeBtn.style.display = 'none'; 218 } else { 219 closeBtn.style.display = ''; 220 if (document.cookie.includes('OBRhideAccBtn=true')) { 221 close(); 222 } else { 223 this.style.display = ''; 224 this.style.visibility = ''; 225 this.style.opacity = ''; 226 this.style.pointerEvents = ''; 227 } 228 } 229 }; 230 231 updateForUrl(); 232 this._urlChangeHandler = () => updateForUrl(); 233 window.addEventListener('equalweb-urlchange', this._urlChangeHandler); 234 235 // Additional fallback: try to hide EqualWeb button if it appears later 236 const hideDefaultEqualWebButton = () => { 237 if (equalWebShadowRoot) { 238 hideEqualWebButton(); 239 return; 240 } 241 }; 242 243 hideDefaultEqualWebButton(); 244 245 const observer = new MutationObserver(hideDefaultEqualWebButton); 246 observer.observe(document.body, { childList: true, subtree: true }); 247 } 248 249 disconnectedCallback() { 250 if (this._urlChangeHandler) { 251 window.removeEventListener('equalweb-urlchange', this._urlChangeHandler); 252 } 253 } 254 } 255);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.