|
270 | 270 | return result; |
271 | 271 | } |
272 | 272 |
|
| 273 | + /** |
| 274 | + * Get list of shadow DOM roots, recursively. |
| 275 | + * |
| 276 | + * @since 3.12.0 |
| 277 | + * |
| 278 | + * @param DOMElement parent Parent element to query |
| 279 | + * @return array List of shadow root elements plus parent |
| 280 | + */ |
| 281 | + function queryShadowRoots(parent) { |
| 282 | + return [parent, ...parent.querySelectorAll("*")] |
| 283 | + .filter((e) => e.shadowRoot) |
| 284 | + .flatMap((e) => [e.shadowRoot, ...queryShadowRoots(e.shadowRoot)]); |
| 285 | + } |
| 286 | + |
273 | 287 | /** |
274 | 288 | * Query all visible elements |
275 | 289 | * |
|
282 | 296 | */ |
283 | 297 | function queryAllVisible(parent, field, form) { |
284 | 298 | const result = []; |
285 | | - for (let i = 0; i < field.selectors.length; i++) { |
286 | | - let elems = parent.querySelectorAll(field.selectors[i]); |
287 | | - for (let j = 0; j < elems.length; j++) { |
288 | | - let elem = elems[j]; |
289 | | - // Select only elements from specified form |
290 | | - if (form && form != elem.form) { |
291 | | - continue; |
292 | | - } |
293 | | - // Ignore disabled fields |
294 | | - if (elem.disabled) { |
295 | | - continue; |
296 | | - } |
297 | | - // Elem or its parent has a style 'display: none', |
298 | | - // or it is just too narrow to be a real field (a trap for spammers?). |
299 | | - if (elem.offsetWidth < 30 || elem.offsetHeight < 10) { |
300 | | - continue; |
301 | | - } |
302 | | - // We may have a whitelist of acceptable field types. If so, skip elements of a different type. |
303 | | - if (field.types && field.types.indexOf(elem.type.toLowerCase()) < 0) { |
304 | | - continue; |
305 | | - } |
306 | | - // Elem takes space on the screen, but it or its parent is hidden with a visibility style. |
307 | | - let style = window.getComputedStyle(elem); |
308 | | - if (style.visibility == "hidden") { |
309 | | - continue; |
310 | | - } |
311 | | - // Elem is outside of the boundaries of the visible viewport. |
312 | | - let rect = elem.getBoundingClientRect(); |
313 | | - if ( |
314 | | - rect.x + rect.width < 0 || |
315 | | - rect.y + rect.height < 0 || |
316 | | - rect.x > window.innerWidth || |
317 | | - rect.y > window.innerHeight |
318 | | - ) { |
319 | | - continue; |
320 | | - } |
321 | | - // Elem is hidden by its or or its parent's opacity rules |
322 | | - const OPACITY_LIMIT = 0.1; |
323 | | - let opacity = 1; |
324 | | - for ( |
325 | | - let testElem = elem; |
326 | | - opacity >= OPACITY_LIMIT && testElem && testElem.nodeType === Node.ELEMENT_NODE; |
327 | | - testElem = testElem.parentNode |
328 | | - ) { |
329 | | - let style = window.getComputedStyle(testElem); |
330 | | - if (style.opacity) { |
331 | | - opacity *= parseFloat(style.opacity); |
| 299 | + for (let root of queryShadowRoots(parent)) { |
| 300 | + for (let i = 0; i < field.selectors.length; i++) { |
| 301 | + let elems = root.querySelectorAll(field.selectors[i]); |
| 302 | + for (let j = 0; j < elems.length; j++) { |
| 303 | + let elem = elems[j]; |
| 304 | + // Select only elements from specified form |
| 305 | + if (form && form != elem.form) { |
| 306 | + continue; |
332 | 307 | } |
| 308 | + // Ignore disabled fields |
| 309 | + if (elem.disabled) { |
| 310 | + continue; |
| 311 | + } |
| 312 | + // Elem or its parent has a style 'display: none', |
| 313 | + // or it is just too narrow to be a real field (a trap for spammers?). |
| 314 | + if (elem.offsetWidth < 30 || elem.offsetHeight < 10) { |
| 315 | + continue; |
| 316 | + } |
| 317 | + // We may have a whitelist of acceptable field types. If so, skip elements of a different type. |
| 318 | + if (field.types && field.types.indexOf(elem.type.toLowerCase()) < 0) { |
| 319 | + continue; |
| 320 | + } |
| 321 | + // Elem takes space on the screen, but it or its parent is hidden with a visibility style. |
| 322 | + let style = window.getComputedStyle(elem); |
| 323 | + if (style.visibility == "hidden") { |
| 324 | + continue; |
| 325 | + } |
| 326 | + // Elem is outside of the boundaries of the visible viewport. |
| 327 | + let rect = elem.getBoundingClientRect(); |
| 328 | + if ( |
| 329 | + rect.x + rect.width < 0 || |
| 330 | + rect.y + rect.height < 0 || |
| 331 | + rect.x > window.innerWidth || |
| 332 | + rect.y > window.innerHeight |
| 333 | + ) { |
| 334 | + continue; |
| 335 | + } |
| 336 | + // Elem is hidden by its or or its parent's opacity rules |
| 337 | + const OPACITY_LIMIT = 0.1; |
| 338 | + let opacity = 1; |
| 339 | + for ( |
| 340 | + let testElem = elem; |
| 341 | + opacity >= OPACITY_LIMIT && |
| 342 | + testElem && |
| 343 | + testElem.nodeType === Node.ELEMENT_NODE; |
| 344 | + testElem = testElem.parentNode |
| 345 | + ) { |
| 346 | + let style = window.getComputedStyle(testElem); |
| 347 | + if (style.opacity) { |
| 348 | + opacity *= parseFloat(style.opacity); |
| 349 | + } |
| 350 | + } |
| 351 | + if (opacity < OPACITY_LIMIT) { |
| 352 | + continue; |
| 353 | + } |
| 354 | + // This element is visible, will use it. |
| 355 | + result.push(elem); |
333 | 356 | } |
334 | | - if (opacity < OPACITY_LIMIT) { |
335 | | - continue; |
336 | | - } |
337 | | - // This element is visible, will use it. |
338 | | - result.push(elem); |
339 | 357 | } |
340 | 358 | } |
341 | 359 | return result; |
|
0 commit comments