@reticlehq/browser 3.8.1 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { parentAcrossShadowBoundary } from './shadow-registry.js';
2
+ import { stalledFade } from './visibility.js';
2
3
  export function normaliseVisibleText(value) {
3
4
  return value.replace(/\s+/g, ' ').trim().normalize('NFC');
4
5
  }
@@ -81,7 +82,7 @@ export function unreadableText(el) {
81
82
  let opaque = false;
82
83
  for (let node = el; null !== node; node = parentAcrossShadowBoundary(node)) {
83
84
  const style = view.getComputedStyle(node);
84
- const alpha = Number.parseFloat(style.opacity);
85
+ const alpha = stalledFade(node) ? 1 : Number.parseFloat(style.opacity);
85
86
  opacity *= Number.isNaN(alpha) ? 1 : alpha;
86
87
  if (opaque) {
87
88
  continue;
@@ -1,6 +1,7 @@
1
1
  export declare const HTML_DETAILS_TAG = "details";
2
2
  export declare const HTML_DETAILS_OPEN_ATTRIBUTE = "open";
3
3
  export declare const HTML_SUMMARY_TAG = "summary";
4
+ export declare function stalledFade(el: Element): boolean;
4
5
  export declare function isHiddenByAriaOnly(el: Element): boolean;
5
6
  export declare function isRenderedOutsideAriaHidden(el: Element): boolean;
6
7
  export declare function isInViewport(el: Element, memo?: Map<Element, boolean>): boolean;
@@ -1,3 +1,4 @@
1
+ import { RETICLE_OVERLAY_SELECTOR } from '@reticlehq/core';
1
2
  import { isHtmlElement } from './realm.js';
2
3
  import { parentAcrossShadowBoundary } from './shadow-registry.js';
3
4
  import { clippingBox, overflowAxes, scrolledInto, } from './paint-context.js';
@@ -16,15 +17,45 @@ function hiddenInsideClosedDetails(el) {
16
17
  const summary = Array.from(details.children).find((child) => HTML_SUMMARY_TAG === child.localName);
17
18
  return summary === undefined || !summary.contains(el);
18
19
  }
19
- function stalledFade(el) {
20
+ export function stalledFade(el) {
20
21
  if (!el.ownerDocument.hidden || !('getAnimations' in el)) {
21
22
  return false;
22
23
  }
23
24
  return el.getAnimations().some((a) => 'running' === a.playState || a.pending);
24
25
  }
25
26
  const OVERLAY_ARIA_HIDDEN = 'data-aria-hidden';
27
+ const OPEN_LAYER = '[role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"], dialog[open], [aria-modal="true"]';
28
+ const UNPAINTED_TAGS = new Set(['script', 'style', 'template', 'noscript', 'link', 'meta']);
26
29
  function hiddenByAnOverlay(el) {
27
- return el.hasAttribute(OVERLAY_ARIA_HIDDEN);
30
+ if (el.hasAttribute(OVERLAY_ARIA_HIDDEN)) {
31
+ return true;
32
+ }
33
+ return hiddenByAModalManager(el);
34
+ }
35
+ function hiddenByAModalManager(el) {
36
+ const body = el.ownerDocument.body;
37
+ if (el.parentElement !== body) {
38
+ return false;
39
+ }
40
+ const others = Array.from(body.children).filter((child) => child !== el &&
41
+ !UNPAINTED_TAGS.has(child.localName) &&
42
+ !child.matches(RETICLE_OVERLAY_SELECTOR));
43
+ const open = others.filter((child) => 'true' !== child.getAttribute('aria-hidden'));
44
+ const layer = open[0];
45
+ return 1 === open.length && layer !== undefined && holdsOpenLayer(layer);
46
+ }
47
+ function holdsOpenLayer(portal) {
48
+ const layer = portal.matches(OPEN_LAYER) ? portal : portal.querySelector(OPEN_LAYER);
49
+ for (let node = layer; null !== node; node = node.parentElement) {
50
+ const style = node.ownerDocument.defaultView?.getComputedStyle(node) ?? null;
51
+ if (selfCssHidden(node, style)) {
52
+ return false;
53
+ }
54
+ if (node === portal) {
55
+ return true;
56
+ }
57
+ }
58
+ return false;
28
59
  }
29
60
  function selfCssHidden(el, style) {
30
61
  if (isHtmlElement(el) && el.hidden) {
@@ -106,6 +137,56 @@ export function isRenderedOutsideAriaHidden(el) {
106
137
  }
107
138
  return true;
108
139
  }
140
+ const BACKFACE_VISIBILITY_HIDDEN = 'hidden';
141
+ function localTransform(style) {
142
+ if ('undefined' === typeof DOMMatrix) {
143
+ return null;
144
+ }
145
+ try {
146
+ const transform = style?.transform;
147
+ return undefined === transform || '' === transform || 'none' === transform
148
+ ? new DOMMatrix()
149
+ : new DOMMatrix(transform);
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ }
155
+ function backFaceTowardViewer(matrix) {
156
+ return Number.isFinite(matrix.m33) && 0 > matrix.m33;
157
+ }
158
+ function backfaceHidden(el, memo) {
159
+ const chain = [];
160
+ let declares = false;
161
+ for (let current = el; null !== current; current = parentAcrossShadowBoundary(current)) {
162
+ const style = ownVisibilityInfo(current, memo).style;
163
+ if (BACKFACE_VISIBILITY_HIDDEN === style?.backfaceVisibility) {
164
+ declares = true;
165
+ }
166
+ chain.push(current);
167
+ }
168
+ if (!declares) {
169
+ return false;
170
+ }
171
+ let composed = null;
172
+ for (let index = chain.length - 1; index >= 0; index -= 1) {
173
+ const current = chain[index];
174
+ if (undefined === current) {
175
+ return false;
176
+ }
177
+ const style = ownVisibilityInfo(current, memo).style;
178
+ const local = localTransform(style);
179
+ if (null === local) {
180
+ return false;
181
+ }
182
+ composed = null === composed ? local : composed.multiply(local);
183
+ if (BACKFACE_VISIBILITY_HIDDEN === style?.backfaceVisibility &&
184
+ backFaceTowardViewer(composed)) {
185
+ return true;
186
+ }
187
+ }
188
+ return false;
189
+ }
109
190
  const CLIPPING_OVERFLOW = new Set(['hidden', 'clip']);
110
191
  const SCROLLING_OVERFLOW = new Set(['auto', 'scroll']);
111
192
  const NON_CLIPPING_DISPLAY = new Set([
@@ -314,7 +395,9 @@ export function isVisible(el, memo) {
314
395
  return cached;
315
396
  }
316
397
  const info = visibilityInfoMemo(memo);
317
- const result = visibleBounds(el, info, undefined, true) !== null && !collapsed(el, info);
398
+ const result = visibleBounds(el, info, undefined, true) !== null &&
399
+ !collapsed(el, info) &&
400
+ !backfaceHidden(el, info);
318
401
  if (memo !== undefined) {
319
402
  memo.set(el, result);
320
403
  }
@@ -228,6 +228,7 @@ export function installNetwork(emit, opts = {}) {
228
228
  }
229
229
  const owner = {};
230
230
  pageSlots()[PAGE_CAPTURE] = owner;
231
+ const installedAt = performance.now();
231
232
  const captureBodies = true === opts.captureBodies;
232
233
  const bodyCapture = new AbortController();
233
234
  const captureErrorBodies = false !== opts.captureErrorBodies;
@@ -550,7 +551,8 @@ export function installNetwork(emit, opts = {}) {
550
551
  return;
551
552
  }
552
553
  const type = entry.initiatorType || 'other';
553
- if ('fetch' === type || 'xmlhttprequest' === type) {
554
+ const patched = 'fetch' === type || 'xmlhttprequest' === type;
555
+ if (patched && entry.startTime >= installedAt) {
554
556
  continue;
555
557
  }
556
558
  const rawUrl = entry.name;