@appsurify-testmap/rrweb 2.1.0-alpha.7 → 2.1.1-alpha.1

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.
package/dist/rrweb.js CHANGED
@@ -168,6 +168,41 @@ function patch$1(source, name, replacement) {
168
168
  };
169
169
  }
170
170
  }
171
+ function describeNode$1(el) {
172
+ const tag = el.tagName.toLowerCase();
173
+ const id = el.id ? `#${el.id}` : "";
174
+ const classes = el.classList.length ? "." + Array.from(el.classList).join(".") : "";
175
+ return `${tag}${id}${classes}`;
176
+ }
177
+ function getElementVisibility$1(el) {
178
+ var _a2, _b;
179
+ const win = ((_a2 = el.ownerDocument) == null ? void 0 : _a2.defaultView) ?? window;
180
+ const rect = el.getBoundingClientRect();
181
+ const viewportWidth = win.innerWidth || win.document.documentElement.clientWidth || 0;
182
+ const viewportHeight = win.innerHeight || win.document.documentElement.clientHeight || 0;
183
+ const isRectVisible = rect.width > 0 && rect.height > 0 && rect.bottom > 0 && rect.right > 0 && rect.top < viewportHeight && rect.left < viewportWidth;
184
+ const style = (_b = win.getComputedStyle) == null ? void 0 : _b.call(win, el);
185
+ const isStyleVisible2 = !!style && style.display !== "none" && style.visibility !== "hidden" && (parseFloat(style.opacity) || 0) > 0;
186
+ const isVisible = isStyleVisible2 && isRectVisible;
187
+ let ratio = 0;
188
+ if (isVisible) {
189
+ const xOverlap = Math.max(
190
+ 0,
191
+ Math.min(rect.right, viewportWidth) - Math.max(rect.left, 0)
192
+ );
193
+ const yOverlap = Math.max(
194
+ 0,
195
+ Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0)
196
+ );
197
+ const intersectionArea = xOverlap * yOverlap;
198
+ const elementArea = rect.width * rect.height;
199
+ ratio = elementArea > 0 ? intersectionArea / elementArea : 0;
200
+ }
201
+ return {
202
+ isVisible,
203
+ ratio
204
+ };
205
+ }
171
206
  const index$1 = {
172
207
  childNodes: childNodes$1,
173
208
  parentNode: parentNode$1,
@@ -181,7 +216,9 @@ const index$1 = {
181
216
  querySelector: querySelector$1,
182
217
  querySelectorAll: querySelectorAll$1,
183
218
  mutationObserver: mutationObserverCtor$1,
184
- patch: patch$1
219
+ patch: patch$1,
220
+ describeNode: describeNode$1,
221
+ getElementVisibility: getElementVisibility$1
185
222
  };
186
223
  function isElement(n2) {
187
224
  return n2.nodeType === n2.ELEMENT_NODE;
@@ -567,71 +604,95 @@ function splitCssText(cssText, style, _testNoPxNorm = false) {
567
604
  function markCssSplits(cssText, style) {
568
605
  return splitCssText(cssText, style).join("/* rr_split */");
569
606
  }
570
- function getXPath(node2) {
571
- if (node2.nodeType === Node.DOCUMENT_NODE) {
572
- return "/";
607
+ function isSelectorUnique(selector, target) {
608
+ try {
609
+ const matches = document.querySelectorAll(selector);
610
+ return matches.length === 1 && matches[0] === target;
611
+ } catch {
612
+ return false;
573
613
  }
574
- if (node2.nodeType === Node.DOCUMENT_TYPE_NODE) {
575
- return "/html/doctype";
614
+ }
615
+ function buildSelector(node2) {
616
+ if (!(node2 instanceof Element)) return null;
617
+ if (node2.id) {
618
+ return `#${CSS.escape(node2.id)}`;
576
619
  }
577
- if (node2.nodeType === Node.ELEMENT_NODE) {
578
- const element = node2;
579
- if (element.id) {
580
- return `//*[@id="${element.id}"]`;
581
- }
582
- if (element.tagName && element.tagName.toLowerCase() === "html") {
583
- return "/html";
584
- }
585
- if (element === document.head) {
586
- return "/html/head";
587
- }
588
- if (element === document.body) {
589
- return "/html/body";
590
- }
591
- const parentNode2 = element.parentNode;
592
- if (!parentNode2) {
593
- return "";
594
- }
595
- const siblings = Array.from(parentNode2.children).filter(
596
- (sibling) => sibling.tagName === element.tagName
597
- );
598
- const index2 = siblings.length > 1 ? `[${siblings.indexOf(element) + 1}]` : "";
599
- return `${getXPath(parentNode2)}/${element.tagName.toLowerCase()}${index2}`;
620
+ const parts = [];
621
+ const tag = node2.tagName.toLowerCase();
622
+ if (node2.classList.length) {
623
+ parts.push(...Array.from(node2.classList).map((cls) => `.${CSS.escape(cls)}`));
600
624
  }
601
- if (node2.nodeType === Node.TEXT_NODE) {
602
- const parent = node2.parentNode;
603
- if (!parent) {
604
- return "";
625
+ Array.from(node2.attributes).forEach((attr) => {
626
+ if (attr.name.startsWith("data-")) {
627
+ parts.push(`[${attr.name}="${CSS.escape(attr.value)}"]`);
605
628
  }
606
- const textSiblings = Array.from(parent.childNodes).filter(
607
- (sibling) => sibling.nodeType === Node.TEXT_NODE
608
- );
609
- const index2 = textSiblings.length > 1 ? `[${textSiblings.indexOf(node2) + 1}]` : "";
610
- return `${getXPath(parent)}/text()${index2}`;
611
- }
612
- if (node2.nodeType === Node.CDATA_SECTION_NODE) {
613
- const parent = node2.parentNode;
614
- if (!parent) {
615
- return "";
629
+ });
630
+ const shortSelector = `${tag}${parts.join("")}`;
631
+ if (isSelectorUnique(shortSelector, node2)) {
632
+ return shortSelector;
633
+ }
634
+ const pathParts = [];
635
+ let current = node2;
636
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
637
+ const parent = current.parentElement;
638
+ const tagName = current.tagName.toLowerCase();
639
+ let nth = "";
640
+ if (parent) {
641
+ const siblings = Array.from(parent.children).filter(
642
+ (el) => el.tagName.toLowerCase() === tagName
643
+ );
644
+ if (siblings.length > 1) {
645
+ nth = `:nth-of-type(${siblings.indexOf(current) + 1})`;
646
+ }
616
647
  }
617
- const cdataSiblings = Array.from(parent.childNodes).filter(
618
- (sibling) => sibling.nodeType === Node.CDATA_SECTION_NODE
619
- );
620
- const index2 = cdataSiblings.length > 1 ? `[${cdataSiblings.indexOf(node2) + 1}]` : "";
621
- return `${getXPath(parent)}/text()${index2}`;
648
+ pathParts.unshift(`${tagName}${nth}`);
649
+ current = parent;
622
650
  }
623
- if (node2.nodeType === Node.COMMENT_NODE) {
624
- const parent = node2.parentNode;
625
- if (!parent) {
626
- return "";
651
+ return pathParts.join(" > ") || null;
652
+ }
653
+ function buildXPath(node2) {
654
+ switch (node2.nodeType) {
655
+ case Node.DOCUMENT_NODE:
656
+ return "/";
657
+ case Node.DOCUMENT_TYPE_NODE:
658
+ return "/html/doctype";
659
+ case Node.ELEMENT_NODE: {
660
+ const element = node2;
661
+ if (element.id) {
662
+ return `//*[@id="${CSS.escape(element.id)}"]`;
663
+ }
664
+ if (element.tagName.toLowerCase() === "html") return "/html";
665
+ if (element === document.head) return "/html/head";
666
+ if (element === document.body) return "/html/body";
667
+ const parent = element.parentNode;
668
+ if (!parent) return "";
669
+ const tag = element.tagName.toLowerCase();
670
+ const siblings = Array.from(parent.children).filter(
671
+ (el) => el.tagName.toLowerCase() === tag
672
+ );
673
+ const index2 = siblings.length > 1 ? `[${siblings.indexOf(element) + 1}]` : "";
674
+ return `${buildXPath(parent)}/${tag}${index2}`;
675
+ }
676
+ case Node.TEXT_NODE:
677
+ case Node.CDATA_SECTION_NODE:
678
+ case Node.COMMENT_NODE: {
679
+ const parent = node2.parentNode;
680
+ if (!parent) return "";
681
+ const typeMap = {
682
+ [Node.TEXT_NODE]: "text()",
683
+ [Node.CDATA_SECTION_NODE]: "text()",
684
+ // CDATA ≡ text() в XPath
685
+ [Node.COMMENT_NODE]: "comment()"
686
+ };
687
+ const sameTypeSiblings = Array.from(parent.childNodes).filter(
688
+ (sibling) => sibling.nodeType === node2.nodeType
689
+ );
690
+ const index2 = sameTypeSiblings.length > 1 ? `[${sameTypeSiblings.indexOf(node2)}]` : "";
691
+ return `${buildXPath(parent)}/${typeMap[node2.nodeType]}${index2}`;
627
692
  }
628
- const commentSiblings = Array.from(parent.childNodes).filter(
629
- (sibling) => sibling.nodeType === Node.COMMENT_NODE
630
- );
631
- const index2 = commentSiblings.length > 1 ? `[${commentSiblings.indexOf(node2) + 1}]` : "";
632
- return `${getXPath(parent)}/comment()${index2}`;
693
+ default:
694
+ return "";
633
695
  }
634
- return "";
635
696
  }
636
697
  function isTextVisible(n2) {
637
698
  var _a2;
@@ -647,20 +708,9 @@ function isTextVisible(n2) {
647
708
  const textContent2 = (_a2 = n2.textContent) == null ? void 0 : _a2.trim();
648
709
  return textContent2 !== "";
649
710
  }
650
- function isElementVisible(n2) {
651
- var _a2;
652
- const win = ((_a2 = n2.ownerDocument) == null ? void 0 : _a2.defaultView) ?? null;
653
- const style = win ? win.getComputedStyle(n2) : null;
654
- const isStyleVisible = style != null && style.display !== "none" && style.visibility !== "hidden" && parseFloat(style.opacity) !== 0;
655
- const rect = n2.getBoundingClientRect();
656
- const result2 = isStyleVisible && isRectVisible(rect);
657
- return result2;
658
- }
659
- function isRectVisible(rect, win = window) {
660
- var _a2, _b, _c, _d;
661
- const height = (win == null ? void 0 : win.innerHeight) ?? ((_b = (_a2 = win == null ? void 0 : win.document) == null ? void 0 : _a2.documentElement) == null ? void 0 : _b.clientHeight) ?? 0;
662
- const width = (win == null ? void 0 : win.innerWidth) ?? ((_d = (_c = win == null ? void 0 : win.document) == null ? void 0 : _c.documentElement) == null ? void 0 : _d.clientWidth) ?? 0;
663
- return rect.width > 0 && rect.height > 0 && rect.top >= 0 && rect.left >= 0 && rect.bottom <= height && rect.right <= width;
711
+ function isElementVisible(el) {
712
+ const visibility = index$1.getElementVisibility(el);
713
+ return visibility.isVisible;
664
714
  }
665
715
  const interactiveEvents$1 = [
666
716
  "change",
@@ -687,19 +737,6 @@ const interactiveEvents$1 = [
687
737
  "touchend",
688
738
  "touchcancel"
689
739
  ];
690
- const interactiveTags = [
691
- "a",
692
- "button",
693
- "input",
694
- "select",
695
- "textarea",
696
- "label",
697
- "details",
698
- "summary",
699
- "dialog",
700
- "video",
701
- "audio"
702
- ];
703
740
  const inlineEventAttributes$1 = [
704
741
  "onclick",
705
742
  "ondblclick",
@@ -739,25 +776,6 @@ const originalRemoveEventListener$1 = EventTarget.prototype.removeEventListener;
739
776
  EventTarget.prototype.removeEventListener = function(type, listener, options) {
740
777
  originalRemoveEventListener$1.call(this, type, listener, options);
741
778
  };
742
- function hasEventListeners(n2) {
743
- return n2 instanceof Element && interactiveElementsRegistry$1.has(n2);
744
- }
745
- function isElementInteractive(n2) {
746
- if (n2.nodeType === Node.ELEMENT_NODE) {
747
- const element = n2;
748
- const tagName = element.tagName.toLowerCase();
749
- if (interactiveTags.includes(tagName)) {
750
- return true;
751
- }
752
- const hasTabIndex = element.hasAttribute("tabindex") && element.getAttribute("tabindex") !== "-1";
753
- const hasRoleInteractive = ["button", "link", "checkbox", "switch", "menuitem"].includes(
754
- element.getAttribute("role") || ""
755
- );
756
- const result2 = hasEventListeners(element) || hasTabIndex || hasRoleInteractive || element instanceof HTMLAnchorElement && element.hasAttribute("href") || element instanceof HTMLButtonElement && !element.disabled;
757
- return result2;
758
- }
759
- return false;
760
- }
761
779
  function inspectInlineEventHandlers$1() {
762
780
  const allElements = document.querySelectorAll("*");
763
781
  allElements.forEach((el) => {
@@ -893,9 +911,6 @@ function transformAttribute(doc, tagName, name, value) {
893
911
  function ignoreAttribute(tagName, name, _value) {
894
912
  return (tagName === "video" || tagName === "audio") && name === "autoplay";
895
913
  }
896
- function isIncludeAttribute(name, include) {
897
- return typeof include === "string" ? name.includes(include) : include.test(name);
898
- }
899
914
  function isExcludeAttribute(name, exclude) {
900
915
  return typeof exclude === "string" ? name.includes(exclude) : exclude.test(name);
901
916
  }
@@ -1029,7 +1044,6 @@ function serializeNode(n2, options) {
1029
1044
  blockClass,
1030
1045
  blockSelector,
1031
1046
  excludeAttribute,
1032
- includeAttribute,
1033
1047
  needsMask,
1034
1048
  inlineStylesheet,
1035
1049
  maskInputOptions = {},
@@ -1043,22 +1057,19 @@ function serializeNode(n2, options) {
1043
1057
  cssCaptured = false
1044
1058
  } = options;
1045
1059
  const rootId = getRootId(doc, mirror2);
1046
- const xPath = getXPath(n2);
1047
1060
  switch (n2.nodeType) {
1048
1061
  case n2.DOCUMENT_NODE:
1049
1062
  if (n2.compatMode !== "CSS1Compat") {
1050
1063
  return {
1051
1064
  type: NodeType$3.Document,
1052
1065
  childNodes: [],
1053
- xPath,
1054
1066
  compatMode: n2.compatMode
1055
1067
  // probably "BackCompat"
1056
1068
  };
1057
1069
  } else {
1058
1070
  return {
1059
1071
  type: NodeType$3.Document,
1060
- childNodes: [],
1061
- xPath
1072
+ childNodes: []
1062
1073
  };
1063
1074
  }
1064
1075
  case n2.DOCUMENT_TYPE_NODE:
@@ -1067,8 +1078,7 @@ function serializeNode(n2, options) {
1067
1078
  name: n2.name,
1068
1079
  publicId: n2.publicId,
1069
1080
  systemId: n2.systemId,
1070
- rootId,
1071
- xPath
1081
+ rootId
1072
1082
  };
1073
1083
  case n2.ELEMENT_NODE:
1074
1084
  return serializeElementNode(n2, {
@@ -1076,7 +1086,6 @@ function serializeNode(n2, options) {
1076
1086
  blockClass,
1077
1087
  blockSelector,
1078
1088
  excludeAttribute,
1079
- includeAttribute,
1080
1089
  inlineStylesheet,
1081
1090
  maskInputOptions,
1082
1091
  maskInputFn,
@@ -1085,8 +1094,7 @@ function serializeNode(n2, options) {
1085
1094
  recordCanvas,
1086
1095
  keepIframeSrcFn,
1087
1096
  newlyAddedElement,
1088
- rootId,
1089
- xPath
1097
+ rootId
1090
1098
  });
1091
1099
  case n2.TEXT_NODE:
1092
1100
  return serializeTextNode(n2, {
@@ -1094,22 +1102,19 @@ function serializeNode(n2, options) {
1094
1102
  needsMask,
1095
1103
  maskTextFn,
1096
1104
  rootId,
1097
- cssCaptured,
1098
- xPath
1105
+ cssCaptured
1099
1106
  });
1100
1107
  case n2.CDATA_SECTION_NODE:
1101
1108
  return {
1102
1109
  type: NodeType$3.CDATA,
1103
1110
  textContent: "",
1104
- rootId,
1105
- xPath
1111
+ rootId
1106
1112
  };
1107
1113
  case n2.COMMENT_NODE:
1108
1114
  return {
1109
1115
  type: NodeType$3.Comment,
1110
1116
  textContent: index$1.textContent(n2) || "",
1111
- rootId,
1112
- xPath
1117
+ rootId
1113
1118
  };
1114
1119
  default:
1115
1120
  return false;
@@ -1121,7 +1126,7 @@ function getRootId(doc, mirror2) {
1121
1126
  return docId === 1 ? void 0 : docId;
1122
1127
  }
1123
1128
  function serializeTextNode(n2, options) {
1124
- const { needsMask, maskTextFn, rootId, cssCaptured, xPath } = options;
1129
+ const { needsMask, maskTextFn, rootId, cssCaptured } = options;
1125
1130
  const parent = index$1.parentNode(n2);
1126
1131
  const parentTagName = parent && parent.tagName;
1127
1132
  let textContent2 = "";
@@ -1138,15 +1143,10 @@ function serializeTextNode(n2, options) {
1138
1143
  if (!isStyle && !isScript && textContent2 && needsMask) {
1139
1144
  textContent2 = maskTextFn ? maskTextFn(textContent2, index$1.parentElement(n2)) : textContent2.replace(/[\S]/g, "*");
1140
1145
  }
1141
- const isVisible = isTextVisible(n2);
1142
- const isInteractive = isElementInteractive(n2);
1143
1146
  return {
1144
1147
  type: NodeType$3.Text,
1145
1148
  textContent: textContent2 || "",
1146
- rootId,
1147
- isVisible,
1148
- isInteractive,
1149
- xPath
1149
+ rootId
1150
1150
  };
1151
1151
  }
1152
1152
  function serializeElementNode(n2, options) {
@@ -1155,7 +1155,6 @@ function serializeElementNode(n2, options) {
1155
1155
  blockClass,
1156
1156
  blockSelector,
1157
1157
  excludeAttribute,
1158
- includeAttribute,
1159
1158
  inlineStylesheet,
1160
1159
  maskInputOptions = {},
1161
1160
  maskInputFn,
@@ -1164,8 +1163,7 @@ function serializeElementNode(n2, options) {
1164
1163
  recordCanvas,
1165
1164
  keepIframeSrcFn,
1166
1165
  newlyAddedElement = false,
1167
- rootId,
1168
- xPath
1166
+ rootId
1169
1167
  } = options;
1170
1168
  const needBlock = _isBlockedElement(n2, blockClass, blockSelector);
1171
1169
  const tagName = getValidTagName$1(n2);
@@ -1173,7 +1171,7 @@ function serializeElementNode(n2, options) {
1173
1171
  const len = n2.attributes.length;
1174
1172
  for (let i2 = 0; i2 < len; i2++) {
1175
1173
  const attr = n2.attributes[i2];
1176
- if (isExcludeAttribute(attr.name, excludeAttribute) && !isIncludeAttribute(attr.name, includeAttribute)) {
1174
+ if (isExcludeAttribute(attr.name, excludeAttribute)) {
1177
1175
  continue;
1178
1176
  }
1179
1177
  if (!ignoreAttribute(tagName, attr.name, attr.value)) {
@@ -1335,8 +1333,6 @@ function serializeElementNode(n2, options) {
1335
1333
  if (customElements.get(tagName)) isCustomElement = true;
1336
1334
  } catch (e2) {
1337
1335
  }
1338
- const isVisible = isElementVisible(n2);
1339
- const isInteractive = isElementInteractive(n2);
1340
1336
  return {
1341
1337
  type: NodeType$3.Element,
1342
1338
  tagName,
@@ -1345,10 +1341,7 @@ function serializeElementNode(n2, options) {
1345
1341
  isSVG: isSVGElement(n2) || void 0,
1346
1342
  needBlock,
1347
1343
  rootId,
1348
- isCustom: isCustomElement,
1349
- isVisible,
1350
- isInteractive,
1351
- xPath
1344
+ isCustom: isCustomElement
1352
1345
  };
1353
1346
  }
1354
1347
  function lowerIfExists(maybeAttr) {
@@ -1399,7 +1392,6 @@ function serializeNodeWithId(n2, options) {
1399
1392
  maskTextClass,
1400
1393
  maskTextSelector,
1401
1394
  excludeAttribute,
1402
- includeAttribute,
1403
1395
  skipChild = false,
1404
1396
  inlineStylesheet = true,
1405
1397
  maskInputOptions = {},
@@ -1435,7 +1427,6 @@ function serializeNodeWithId(n2, options) {
1435
1427
  blockClass,
1436
1428
  blockSelector,
1437
1429
  excludeAttribute,
1438
- includeAttribute,
1439
1430
  needsMask,
1440
1431
  inlineStylesheet,
1441
1432
  maskInputOptions,
@@ -1461,6 +1452,21 @@ function serializeNodeWithId(n2, options) {
1461
1452
  id = genId();
1462
1453
  }
1463
1454
  const serializedNode = Object.assign(_serializedNode, { id });
1455
+ if (isElement(n2) || n2.nodeType === Node.TEXT_NODE) {
1456
+ serializedNode.xpath = buildXPath(n2);
1457
+ if (isElement(n2)) {
1458
+ const selector = buildSelector(n2);
1459
+ if (selector) {
1460
+ serializedNode.selector = selector;
1461
+ }
1462
+ }
1463
+ if (n2.nodeType === Node.TEXT_NODE) {
1464
+ serializedNode.isVisible = isTextVisible(n2);
1465
+ }
1466
+ if (n2.nodeType === Node.ELEMENT_NODE) {
1467
+ serializedNode.isVisible = isElementVisible(n2);
1468
+ }
1469
+ }
1464
1470
  mirror2.add(n2, serializedNode);
1465
1471
  if (id === IGNORED_NODE) {
1466
1472
  return null;
@@ -1489,7 +1495,6 @@ function serializeNodeWithId(n2, options) {
1489
1495
  maskTextClass,
1490
1496
  maskTextSelector,
1491
1497
  excludeAttribute,
1492
- includeAttribute,
1493
1498
  skipChild,
1494
1499
  inlineStylesheet,
1495
1500
  maskInputOptions,
@@ -1550,7 +1555,6 @@ function serializeNodeWithId(n2, options) {
1550
1555
  maskTextClass,
1551
1556
  maskTextSelector,
1552
1557
  excludeAttribute,
1553
- includeAttribute,
1554
1558
  skipChild: false,
1555
1559
  inlineStylesheet,
1556
1560
  maskInputOptions,
@@ -1593,7 +1597,6 @@ function serializeNodeWithId(n2, options) {
1593
1597
  maskTextClass,
1594
1598
  maskTextSelector,
1595
1599
  excludeAttribute,
1596
- includeAttribute,
1597
1600
  skipChild: false,
1598
1601
  inlineStylesheet,
1599
1602
  maskInputOptions,
@@ -1631,8 +1634,7 @@ function snapshot(n2, options) {
1631
1634
  blockSelector = null,
1632
1635
  maskTextClass = "rr-mask",
1633
1636
  maskTextSelector = null,
1634
- excludeAttribute = /^$a/,
1635
- includeAttribute = /.+/i,
1637
+ excludeAttribute = /.^/,
1636
1638
  inlineStylesheet = true,
1637
1639
  inlineImages = false,
1638
1640
  recordCanvas = false,
@@ -1693,7 +1695,6 @@ function snapshot(n2, options) {
1693
1695
  maskTextClass,
1694
1696
  maskTextSelector,
1695
1697
  excludeAttribute,
1696
- includeAttribute,
1697
1698
  skipChild: false,
1698
1699
  inlineStylesheet,
1699
1700
  maskInputOptions,
@@ -10832,6 +10833,41 @@ function patch(source, name, replacement) {
10832
10833
  };
10833
10834
  }
10834
10835
  }
10836
+ function describeNode(el) {
10837
+ const tag = el.tagName.toLowerCase();
10838
+ const id = el.id ? `#${el.id}` : "";
10839
+ const classes = el.classList.length ? "." + Array.from(el.classList).join(".") : "";
10840
+ return `${tag}${id}${classes}`;
10841
+ }
10842
+ function getElementVisibility(el) {
10843
+ var _a2, _b;
10844
+ const win = ((_a2 = el.ownerDocument) == null ? void 0 : _a2.defaultView) ?? window;
10845
+ const rect = el.getBoundingClientRect();
10846
+ const viewportWidth = win.innerWidth || win.document.documentElement.clientWidth || 0;
10847
+ const viewportHeight = win.innerHeight || win.document.documentElement.clientHeight || 0;
10848
+ const isRectVisible = rect.width > 0 && rect.height > 0 && rect.bottom > 0 && rect.right > 0 && rect.top < viewportHeight && rect.left < viewportWidth;
10849
+ const style = (_b = win.getComputedStyle) == null ? void 0 : _b.call(win, el);
10850
+ const isStyleVisible2 = !!style && style.display !== "none" && style.visibility !== "hidden" && (parseFloat(style.opacity) || 0) > 0;
10851
+ const isVisible = isStyleVisible2 && isRectVisible;
10852
+ let ratio = 0;
10853
+ if (isVisible) {
10854
+ const xOverlap = Math.max(
10855
+ 0,
10856
+ Math.min(rect.right, viewportWidth) - Math.max(rect.left, 0)
10857
+ );
10858
+ const yOverlap = Math.max(
10859
+ 0,
10860
+ Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0)
10861
+ );
10862
+ const intersectionArea = xOverlap * yOverlap;
10863
+ const elementArea = rect.width * rect.height;
10864
+ ratio = elementArea > 0 ? intersectionArea / elementArea : 0;
10865
+ }
10866
+ return {
10867
+ isVisible,
10868
+ ratio
10869
+ };
10870
+ }
10835
10871
  const index = {
10836
10872
  childNodes,
10837
10873
  parentNode,
@@ -10845,7 +10881,9 @@ const index = {
10845
10881
  querySelector,
10846
10882
  querySelectorAll,
10847
10883
  mutationObserver: mutationObserverCtor,
10848
- patch
10884
+ patch,
10885
+ describeNode,
10886
+ getElementVisibility
10849
10887
  };
10850
10888
  function on(type, fn, target = document) {
10851
10889
  const options = { capture: true, passive: true };
@@ -11246,6 +11284,7 @@ var IncrementalSource = /* @__PURE__ */ ((IncrementalSource2) => {
11246
11284
  IncrementalSource2[IncrementalSource2["Selection"] = 14] = "Selection";
11247
11285
  IncrementalSource2[IncrementalSource2["AdoptedStyleSheet"] = 15] = "AdoptedStyleSheet";
11248
11286
  IncrementalSource2[IncrementalSource2["CustomElement"] = 16] = "CustomElement";
11287
+ IncrementalSource2[IncrementalSource2["VisibilityMutation"] = 17] = "VisibilityMutation";
11249
11288
  return IncrementalSource2;
11250
11289
  })(IncrementalSource || {});
11251
11290
  var MouseInteractions = /* @__PURE__ */ ((MouseInteractions2) => {
@@ -11406,11 +11445,11 @@ class MutationBuffer {
11406
11445
  * the browser MutationObserver emits multiple mutations after
11407
11446
  * a delay for performance reasons, making tracing added nodes hard
11408
11447
  * in our `processMutations` callback function.
11409
- * For example, if we append an element el_1 into body, and then append
11448
+ * For example, if we append an element el_1 into body and then append
11410
11449
  * another element el_2 into el_1, these two mutations may be passed to the
11411
11450
  * callback function together when the two operations were done.
11412
- * Generally we need to trace child nodes of newly added nodes, but in this
11413
- * case if we count el_2 as el_1's child node in the first mutation record,
11451
+ * Generally, we need to trace child nodes of newly added nodes, but in this
11452
+ * case, if we count el_2 as el_1's child node in the first mutation record,
11414
11453
  * then we will count el_2 again in the second mutation record which was
11415
11454
  * duplicated.
11416
11455
  * To avoid of duplicate counting added nodes, we use a Set to store
@@ -11429,7 +11468,6 @@ class MutationBuffer {
11429
11468
  __publicField(this, "maskTextClass");
11430
11469
  __publicField(this, "maskTextSelector");
11431
11470
  __publicField(this, "excludeAttribute");
11432
- __publicField(this, "includeAttribute");
11433
11471
  __publicField(this, "inlineStylesheet");
11434
11472
  __publicField(this, "maskInputOptions");
11435
11473
  __publicField(this, "maskTextFn");
@@ -11445,6 +11483,7 @@ class MutationBuffer {
11445
11483
  __publicField(this, "stylesheetManager");
11446
11484
  __publicField(this, "shadowDomManager");
11447
11485
  __publicField(this, "canvasManager");
11486
+ __publicField(this, "visibilityManager");
11448
11487
  __publicField(this, "processedNodeManager");
11449
11488
  __publicField(this, "unattachedDoc");
11450
11489
  __publicField(this, "processMutations", (mutations) => {
@@ -11494,7 +11533,6 @@ class MutationBuffer {
11494
11533
  maskTextClass: this.maskTextClass,
11495
11534
  maskTextSelector: this.maskTextSelector,
11496
11535
  excludeAttribute: this.excludeAttribute,
11497
- includeAttribute: this.includeAttribute,
11498
11536
  skipChild: true,
11499
11537
  newlyAddedElement: true,
11500
11538
  inlineStylesheet: this.inlineStylesheet,
@@ -11540,7 +11578,8 @@ class MutationBuffer {
11540
11578
  this.mirror.removeNodeFromMap(this.mapRemoves.shift());
11541
11579
  }
11542
11580
  for (const n2 of this.movedSet) {
11543
- if (isParentRemoved(this.removesSubTreeCache, n2, this.mirror) && !this.movedSet.has(index.parentNode(n2))) {
11581
+ if (isParentRemoved(this.removesSubTreeCache, n2, this.mirror) && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
11582
+ !this.movedSet.has(index.parentNode(n2))) {
11544
11583
  continue;
11545
11584
  }
11546
11585
  pushAdd(n2);
@@ -11696,12 +11735,60 @@ class MutationBuffer {
11696
11735
  const target = m.target;
11697
11736
  let attributeName = m.attributeName;
11698
11737
  let value = m.target.getAttribute(attributeName);
11699
- const propValue = target[attributeName];
11700
- const isPhantomAttributeMutation = value === null && !target.hasAttribute(attributeName) && m.oldValue !== null && (propValue === "" || propValue === null || typeof propValue === "undefined");
11738
+ const attrKey = attributeName;
11739
+ const propValue = target[attrKey];
11740
+ const isBooleanAttr = typeof propValue === "boolean";
11741
+ const inDOM = document.contains(target);
11742
+ const isVisible = isElementVisible(target);
11743
+ const isExcludeAttributeName = isExcludeAttribute(attributeName, this.excludeAttribute);
11744
+ const isPhantomAttributeMutation = value === null && // текущего атрибута нет
11745
+ !target.hasAttribute(attributeName) && // явно подтверждаем отсутствие
11746
+ m.oldValue !== null && // раньше он был
11747
+ (propValue === "" || // свойство = пустая строка
11748
+ propValue === null || // или null
11749
+ typeof propValue === "undefined");
11701
11750
  if (isPhantomAttributeMutation) {
11751
+ console.debug(
11752
+ `[${nowTimestamp()}] [rrweb:record/mutation] ⛔ phantom attribute mutation ignored`,
11753
+ {
11754
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
11755
+ node: index.describeNode(target),
11756
+ tag: target.tagName,
11757
+ nodeType: target.nodeType,
11758
+ attribute: attributeName,
11759
+ value,
11760
+ oldValue: m.oldValue,
11761
+ excludeAttribute: this.excludeAttribute,
11762
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
11763
+ propValue,
11764
+ isBooleanAttr,
11765
+ inDOM,
11766
+ isVisible,
11767
+ isExcludeAttributeName
11768
+ }
11769
+ );
11702
11770
  return;
11703
11771
  }
11704
11772
  if (isExcludeAttribute(attributeName, this.excludeAttribute)) {
11773
+ console.debug(
11774
+ `[${nowTimestamp()}] [rrweb:record/mutation] ⛔ excluded attribute mutation ignored`,
11775
+ {
11776
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
11777
+ node: index.describeNode(target),
11778
+ tag: target.tagName,
11779
+ nodeType: target.nodeType,
11780
+ attribute: attributeName,
11781
+ value,
11782
+ oldValue: m.oldValue,
11783
+ excludeAttribute: this.excludeAttribute,
11784
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
11785
+ propValue,
11786
+ isBooleanAttr,
11787
+ inDOM,
11788
+ isVisible,
11789
+ isExcludeAttributeName
11790
+ }
11791
+ );
11705
11792
  return;
11706
11793
  }
11707
11794
  if (attributeName === "value") {
@@ -11746,9 +11833,35 @@ class MutationBuffer {
11746
11833
  toLowerCase(attributeName),
11747
11834
  value
11748
11835
  );
11749
- if (value === item.attributes[attributeName]) {
11750
- console.debug("[rrweb-record] A questionable mutation that needs to be investigated in the future.");
11751
- return;
11836
+ const isSuspiciousClassMutation = attributeName !== "class" && (m.oldValue === null || // ранее не было класса
11837
+ value === "" || // класс удалён
11838
+ value !== m.oldValue);
11839
+ if (isSuspiciousClassMutation) {
11840
+ console.debug(
11841
+ `[${nowTimestamp()}] [rrweb:record/mutation] ⚠️ suspicious attribute mutation`,
11842
+ {
11843
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
11844
+ reason: [
11845
+ value === m.oldValue ? "no change in value" : null,
11846
+ value === propValue ? "mirrored in DOM property" : null,
11847
+ value === item.attributes[attributeName] ? "redundant assignment" : null
11848
+ ].filter(Boolean).join(", ") || "uncategorized",
11849
+ node: index.describeNode(target),
11850
+ tag: target.tagName,
11851
+ nodeType: target.nodeType,
11852
+ attribute: attributeName,
11853
+ value,
11854
+ oldValue: m.oldValue,
11855
+ transformedValue: item.attributes[attributeName],
11856
+ excludeAttribute: this.excludeAttribute,
11857
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
11858
+ propValue,
11859
+ isBooleanAttr,
11860
+ inDOM,
11861
+ isVisible,
11862
+ isExcludeAttributeName
11863
+ }
11864
+ );
11752
11865
  }
11753
11866
  if (attributeName === "style") {
11754
11867
  if (!this.unattachedDoc) {
@@ -11866,7 +11979,6 @@ class MutationBuffer {
11866
11979
  "maskTextClass",
11867
11980
  "maskTextSelector",
11868
11981
  "excludeAttribute",
11869
- "includeAttribute",
11870
11982
  "inlineStylesheet",
11871
11983
  "maskInputOptions",
11872
11984
  "maskTextFn",
@@ -11882,6 +11994,7 @@ class MutationBuffer {
11882
11994
  "stylesheetManager",
11883
11995
  "shadowDomManager",
11884
11996
  "canvasManager",
11997
+ "visibilityManager",
11885
11998
  "processedNodeManager"
11886
11999
  ].forEach((key) => {
11887
12000
  this[key] = options[key];
@@ -11890,10 +12003,12 @@ class MutationBuffer {
11890
12003
  freeze() {
11891
12004
  this.frozen = true;
11892
12005
  this.canvasManager.freeze();
12006
+ this.visibilityManager.freeze();
11893
12007
  }
11894
12008
  unfreeze() {
11895
12009
  this.frozen = false;
11896
12010
  this.canvasManager.unfreeze();
12011
+ this.visibilityManager.unfreeze();
11897
12012
  this.emit();
11898
12013
  }
11899
12014
  isFrozen() {
@@ -11902,15 +12017,18 @@ class MutationBuffer {
11902
12017
  lock() {
11903
12018
  this.locked = true;
11904
12019
  this.canvasManager.lock();
12020
+ this.visibilityManager.lock();
11905
12021
  }
11906
12022
  unlock() {
11907
12023
  this.locked = false;
11908
12024
  this.canvasManager.unlock();
12025
+ this.visibilityManager.unlock();
11909
12026
  this.emit();
11910
12027
  }
11911
12028
  reset() {
11912
12029
  this.shadowDomManager.reset();
11913
12030
  this.canvasManager.reset();
12031
+ this.visibilityManager.reset();
11914
12032
  }
11915
12033
  }
11916
12034
  function deepDelete(addsSet, n2) {
@@ -12844,6 +12962,7 @@ function mergeHooks(o2, hooks) {
12844
12962
  styleSheetRuleCb,
12845
12963
  styleDeclarationCb,
12846
12964
  canvasMutationCb,
12965
+ visibilityMutationCb,
12847
12966
  fontCb,
12848
12967
  selectionCb,
12849
12968
  customElementCb
@@ -12908,6 +13027,12 @@ function mergeHooks(o2, hooks) {
12908
13027
  }
12909
13028
  canvasMutationCb(...p);
12910
13029
  };
13030
+ o2.visibilityMutationCb = (...p) => {
13031
+ if (hooks.visibilityMutation) {
13032
+ hooks.visibilityMutation(...p);
13033
+ }
13034
+ visibilityMutationCb(...p);
13035
+ };
12911
13036
  o2.fontCb = (...p) => {
12912
13037
  if (hooks.font) {
12913
13038
  hooks.font(...p);
@@ -14041,11 +14166,249 @@ class ProcessedNodeManager {
14041
14166
  destroy() {
14042
14167
  }
14043
14168
  }
14169
+ function computeVisibility(elements, previous, options) {
14170
+ const root2 = (options == null ? void 0 : options.root) ?? null;
14171
+ const threshold = (options == null ? void 0 : options.threshold) ?? 0.5;
14172
+ const sensitivity = (options == null ? void 0 : options.sensitivity) ?? 0.05;
14173
+ const rootMarginFn = parseRootMargin((options == null ? void 0 : options.rootMargin) ?? "0px");
14174
+ const current = /* @__PURE__ */ new Map();
14175
+ const rootRect = getRootRect(root2);
14176
+ const expandedRoot = expandRootRect(rootRect, rootMarginFn);
14177
+ for (const el of elements) {
14178
+ const elRect = el.getBoundingClientRect();
14179
+ let intersectionRect = emptyRect();
14180
+ let intersectionRatio = 0;
14181
+ if (elRect.width > 0 && elRect.height > 0) {
14182
+ intersectionRect = computeIntersectionRect(elRect, expandedRoot);
14183
+ intersectionRatio = computeIntersectionRatio(elRect, intersectionRect);
14184
+ intersectionRatio = Math.round(intersectionRatio * 100) / 100;
14185
+ }
14186
+ const isStyle = isStyleVisible(el);
14187
+ const old = previous.get(el) ?? null;
14188
+ const prevRatio = (old == null ? void 0 : old.intersectionRatio) ?? 0;
14189
+ const currRatio = intersectionRatio;
14190
+ const wasVisible = (old == null ? void 0 : old.isStyleVisible) && prevRatio > threshold;
14191
+ const nowVisible = isStyle && currRatio > threshold;
14192
+ const changed = !old || wasVisible !== nowVisible || wasVisible !== nowVisible && Math.abs(currRatio - prevRatio) > sensitivity;
14193
+ if (changed) {
14194
+ current.set(el, {
14195
+ target: el,
14196
+ isVisible: nowVisible,
14197
+ isStyleVisible: isStyle,
14198
+ intersectionRatio: currRatio,
14199
+ intersectionRect,
14200
+ oldValue: old
14201
+ });
14202
+ } else {
14203
+ current.set(el, old);
14204
+ }
14205
+ }
14206
+ return current;
14207
+ }
14208
+ function parseRootMargin(marginStr) {
14209
+ const parts = marginStr.trim().split(/\s+/);
14210
+ const getValue = (val, size) => val.endsWith("%") ? parseFloat(val) / 100 * size : parseFloat(val) || 0;
14211
+ return function(rootRect) {
14212
+ const top = getValue(parts[0] || "0px", rootRect.height);
14213
+ const right = getValue(parts[1] || parts[0] || "0px", rootRect.width);
14214
+ const bottom = getValue(parts[2] || parts[0] || "0px", rootRect.height);
14215
+ const left = getValue(parts[3] || parts[1] || parts[0] || "0px", rootRect.width);
14216
+ return { top, right, bottom, left, width: 0, height: 0 };
14217
+ };
14218
+ }
14219
+ function getRootRect(root2) {
14220
+ return root2 ? root2.getBoundingClientRect() : new DOMRect(0, 0, window.innerWidth, window.innerHeight);
14221
+ }
14222
+ function expandRootRect(rect, marginFn) {
14223
+ const margin = marginFn(rect);
14224
+ return new DOMRect(
14225
+ rect.left - margin.left,
14226
+ rect.top - margin.top,
14227
+ rect.width + margin.left + margin.right,
14228
+ rect.height + margin.top + margin.bottom
14229
+ );
14230
+ }
14231
+ function computeIntersectionRect(a2, b) {
14232
+ const top = Math.max(a2.top, b.top);
14233
+ const left = Math.max(a2.left, b.left);
14234
+ const bottom = Math.min(a2.bottom, b.bottom);
14235
+ const right = Math.min(a2.right, b.right);
14236
+ const width = Math.max(0, right - left);
14237
+ const height = Math.max(0, bottom - top);
14238
+ return { top, left, bottom, right, width, height };
14239
+ }
14240
+ function computeIntersectionRatio(elRect, intersectionRect) {
14241
+ const elArea = elRect.width * elRect.height;
14242
+ const intArea = intersectionRect.width * intersectionRect.height;
14243
+ return elArea > 0 ? intArea / elArea : 0;
14244
+ }
14245
+ function emptyRect() {
14246
+ return { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 };
14247
+ }
14248
+ function isStyleVisible(el) {
14249
+ const style = getComputedStyle(el);
14250
+ return style && style.display !== "none" && style.visibility !== "hidden" && parseFloat(style.opacity || "1") > 0;
14251
+ }
14252
+ class VisibilityManager {
14253
+ constructor(options) {
14254
+ __publicField(this, "frozen", false);
14255
+ __publicField(this, "locked", false);
14256
+ __publicField(this, "pending", /* @__PURE__ */ new Map());
14257
+ __publicField(this, "mirror");
14258
+ __publicField(this, "mutationCb");
14259
+ __publicField(this, "rafId", null);
14260
+ __publicField(this, "rafThrottle");
14261
+ __publicField(this, "lastFlushTime", 0);
14262
+ __publicField(this, "elements", /* @__PURE__ */ new Set());
14263
+ __publicField(this, "previousState", /* @__PURE__ */ new Map());
14264
+ __publicField(this, "root", null);
14265
+ __publicField(this, "threshold");
14266
+ __publicField(this, "sensitivity");
14267
+ __publicField(this, "rootMargin");
14268
+ __publicField(this, "hasInitialized", false);
14269
+ __publicField(this, "mode", "none");
14270
+ __publicField(this, "debounce", 50);
14271
+ __publicField(this, "throttle", 100);
14272
+ __publicField(this, "buffer", /* @__PURE__ */ new Map());
14273
+ __publicField(this, "debounceTimer", null);
14274
+ __publicField(this, "lastThrottleTime", 0);
14275
+ __publicField(this, "disabled", false);
14276
+ __publicField(this, "notifyActivity");
14277
+ const { doc, mirror: mirror2, sampling, mutationCb, notifyActivity } = options;
14278
+ this.mirror = mirror2;
14279
+ this.mutationCb = mutationCb;
14280
+ this.notifyActivity = notifyActivity;
14281
+ this.rootMargin = "0px";
14282
+ if (sampling === false) {
14283
+ this.disabled = true;
14284
+ return;
14285
+ }
14286
+ const visibilitySampling = typeof sampling === "object" && sampling !== null ? sampling : {};
14287
+ this.mode = (visibilitySampling == null ? void 0 : visibilitySampling.mode) ?? "none";
14288
+ this.debounce = Number((visibilitySampling == null ? void 0 : visibilitySampling.debounce) ?? 100);
14289
+ this.throttle = Number((visibilitySampling == null ? void 0 : visibilitySampling.throttle) ?? 100);
14290
+ this.threshold = Number((visibilitySampling == null ? void 0 : visibilitySampling.threshold) ?? 0.5);
14291
+ this.sensitivity = Number((visibilitySampling == null ? void 0 : visibilitySampling.sensitivity) ?? 0.05);
14292
+ this.rafThrottle = Number((visibilitySampling == null ? void 0 : visibilitySampling.rafThrottle) ?? 100);
14293
+ doc.querySelectorAll("*").forEach((el) => this.observe(el));
14294
+ const mo = new MutationObserver((mutations) => {
14295
+ mutations.forEach((m) => {
14296
+ m.addedNodes.forEach((n2) => {
14297
+ if (n2.nodeType === Node.ELEMENT_NODE) {
14298
+ this.observe(n2);
14299
+ n2.querySelectorAll("*").forEach((el) => this.observe(el));
14300
+ }
14301
+ });
14302
+ m.removedNodes.forEach((n2) => {
14303
+ if (n2.nodeType === Node.ELEMENT_NODE) {
14304
+ this.unobserve(n2);
14305
+ }
14306
+ });
14307
+ });
14308
+ });
14309
+ mo.observe(doc.body, { childList: true, subtree: true });
14310
+ this.startPendingFlushLoop();
14311
+ }
14312
+ startPendingFlushLoop() {
14313
+ if (this.disabled) return;
14314
+ const loop = (timestamp) => {
14315
+ if (timestamp - this.lastFlushTime >= this.rafThrottle) {
14316
+ this.lastFlushTime = timestamp;
14317
+ this.flushPendingVisibilityMutations();
14318
+ }
14319
+ this.rafId = requestAnimationFrame(loop);
14320
+ };
14321
+ this.rafId = requestAnimationFrame(loop);
14322
+ }
14323
+ flushPendingVisibilityMutations() {
14324
+ if (this.disabled) return;
14325
+ if (this.frozen || this.locked || this.elements.size === 0) return;
14326
+ const state = computeVisibility(this.elements, this.previousState, {
14327
+ root: this.root,
14328
+ threshold: this.threshold,
14329
+ sensitivity: this.sensitivity,
14330
+ rootMargin: this.rootMargin
14331
+ });
14332
+ for (const [el, entry] of state.entries()) {
14333
+ const old = this.previousState.get(el);
14334
+ const changed = !old || old.isVisible !== entry.isVisible || Math.abs(old.intersectionRatio - entry.intersectionRatio) > this.sensitivity;
14335
+ if (changed) {
14336
+ const id = this.mirror.getId(el);
14337
+ if (id !== -1) {
14338
+ this.buffer.set(el, {
14339
+ id,
14340
+ isVisible: entry.isVisible,
14341
+ ratio: entry.intersectionRatio
14342
+ });
14343
+ }
14344
+ this.previousState.set(el, entry);
14345
+ }
14346
+ }
14347
+ this.previousState = state;
14348
+ if (!this.hasInitialized) {
14349
+ this.hasInitialized = true;
14350
+ this.buffer.clear();
14351
+ return;
14352
+ }
14353
+ this.scheduleEmit();
14354
+ }
14355
+ scheduleEmit() {
14356
+ if (this.mode === "debounce") {
14357
+ clearTimeout(this.debounceTimer);
14358
+ this.debounceTimer = setTimeout(() => this.flushBuffer(), this.debounce);
14359
+ } else if (this.mode === "throttle") {
14360
+ const now = performance.now();
14361
+ if (now - this.lastThrottleTime >= this.throttle) {
14362
+ this.lastThrottleTime = now;
14363
+ this.flushBuffer();
14364
+ }
14365
+ } else {
14366
+ this.flushBuffer();
14367
+ }
14368
+ }
14369
+ flushBuffer() {
14370
+ var _a2;
14371
+ if (this.buffer.size === 0) return;
14372
+ (_a2 = this.notifyActivity) == null ? void 0 : _a2.call(this, this.buffer.size);
14373
+ this.mutationCb({ mutations: Array.from(this.buffer.values()) });
14374
+ this.buffer.clear();
14375
+ }
14376
+ observe(el) {
14377
+ if (this.disabled) return;
14378
+ this.elements.add(el);
14379
+ }
14380
+ unobserve(el) {
14381
+ if (this.disabled) return;
14382
+ this.elements.delete(el);
14383
+ this.previousState.delete(el);
14384
+ this.pending.delete(el);
14385
+ }
14386
+ freeze() {
14387
+ this.frozen = true;
14388
+ }
14389
+ unfreeze() {
14390
+ this.frozen = false;
14391
+ }
14392
+ lock() {
14393
+ this.locked = true;
14394
+ }
14395
+ unlock() {
14396
+ this.locked = false;
14397
+ }
14398
+ reset() {
14399
+ this.elements.clear();
14400
+ this.previousState.clear();
14401
+ this.pending.clear();
14402
+ if (this.rafId) cancelAnimationFrame(this.rafId);
14403
+ }
14404
+ }
14044
14405
  let wrappedEmit;
14045
14406
  let takeFullSnapshot$1;
14046
14407
  let canvasManager;
14408
+ let visibilityManager;
14047
14409
  let recording = false;
14048
- const preRecordingCustomEvents = [];
14410
+ const customEventQueue = [];
14411
+ let flushCustomEventQueue;
14049
14412
  try {
14050
14413
  if (Array.from([1], (x2) => x2 * 2)[0] !== 2) {
14051
14414
  const cleanFrame = document.createElement("iframe");
@@ -14062,12 +14425,12 @@ function record(options = {}) {
14062
14425
  emit,
14063
14426
  checkoutEveryNms,
14064
14427
  checkoutEveryNth,
14428
+ checkoutEveryNvm,
14065
14429
  blockClass = "rr-block",
14066
14430
  blockSelector = null,
14067
14431
  ignoreClass = "rr-ignore",
14068
14432
  ignoreSelector = null,
14069
14433
  excludeAttribute: _excludeAttribute,
14070
- includeAttribute: _includeAttribute,
14071
14434
  maskTextClass = "rr-mask",
14072
14435
  maskTextSelector = null,
14073
14436
  inlineStylesheet = true,
@@ -14085,7 +14448,7 @@ function record(options = {}) {
14085
14448
  recordCanvas = false,
14086
14449
  recordCrossOriginIframes = false,
14087
14450
  recordAfter = options.recordAfter === "DOMContentLoaded" ? options.recordAfter : "load",
14088
- flushCustomQueue = options.flushCustomQueue !== void 0 ? options.flushCustomQueue : "after",
14451
+ flushCustomEvent = options.flushCustomEvent !== void 0 ? options.flushCustomEvent : "after",
14089
14452
  userTriggeredOnInput = false,
14090
14453
  collectFonts = false,
14091
14454
  inlineImages = false,
@@ -14117,8 +14480,7 @@ function record(options = {}) {
14117
14480
  sampling.mousemove = mousemoveWait;
14118
14481
  }
14119
14482
  mirror.reset();
14120
- const excludeAttribute = _excludeAttribute === void 0 ? /^$a/ : _excludeAttribute;
14121
- const includeAttribute = _includeAttribute === void 0 ? /.+/i : _includeAttribute;
14483
+ const excludeAttribute = _excludeAttribute === void 0 ? /.^/ : _excludeAttribute;
14122
14484
  const maskInputOptions = maskAllInputs === true ? {
14123
14485
  color: true,
14124
14486
  date: true,
@@ -14155,6 +14517,10 @@ function record(options = {}) {
14155
14517
  polyfill$1();
14156
14518
  let lastFullSnapshotEvent;
14157
14519
  let incrementalSnapshotCount = 0;
14520
+ let recentVisibilityChanges = 0;
14521
+ const onVisibilityActivity = (count) => {
14522
+ recentVisibilityChanges += count;
14523
+ };
14158
14524
  const eventProcessor = (e2) => {
14159
14525
  for (const plugin3 of plugins || []) {
14160
14526
  if (plugin3.eventProcessor) {
@@ -14195,7 +14561,11 @@ function record(options = {}) {
14195
14561
  incrementalSnapshotCount++;
14196
14562
  const exceedCount = checkoutEveryNth && incrementalSnapshotCount >= checkoutEveryNth;
14197
14563
  const exceedTime = checkoutEveryNms && e2.timestamp - lastFullSnapshotEvent.timestamp > checkoutEveryNms;
14198
- if (exceedCount || exceedTime) {
14564
+ const exceedVisibility = checkoutEveryNvm && recentVisibilityChanges >= checkoutEveryNvm;
14565
+ if (exceedCount || exceedTime || exceedVisibility) {
14566
+ if (exceedVisibility) {
14567
+ recentVisibilityChanges = 0;
14568
+ }
14199
14569
  takeFullSnapshot$1(true);
14200
14570
  }
14201
14571
  }
@@ -14223,6 +14593,17 @@ function record(options = {}) {
14223
14593
  ...p
14224
14594
  }
14225
14595
  });
14596
+ const wrappedVisibilityMutationEmit = (p) => {
14597
+ var _a2;
14598
+ (_a2 = hooks == null ? void 0 : hooks.visibilityMutation) == null ? void 0 : _a2.call(hooks, p);
14599
+ wrappedEmit({
14600
+ type: EventType.IncrementalSnapshot,
14601
+ data: {
14602
+ source: IncrementalSource.VisibilityMutation,
14603
+ ...p
14604
+ }
14605
+ });
14606
+ };
14226
14607
  const wrappedAdoptedStyleSheetEmit = (a2) => wrappedEmit({
14227
14608
  type: EventType.IncrementalSnapshot,
14228
14609
  data: {
@@ -14260,6 +14641,13 @@ function record(options = {}) {
14260
14641
  sampling: sampling.canvas,
14261
14642
  dataURLOptions
14262
14643
  });
14644
+ visibilityManager = new VisibilityManager({
14645
+ doc: window.document,
14646
+ mirror,
14647
+ sampling: sampling.visibility,
14648
+ mutationCb: wrappedVisibilityMutationEmit,
14649
+ notifyActivity: onVisibilityActivity
14650
+ });
14263
14651
  const shadowDomManager = new ShadowDomManager({
14264
14652
  mutationCb: wrappedMutationEmit,
14265
14653
  scrollCb: wrappedScrollEmit,
@@ -14269,7 +14657,6 @@ function record(options = {}) {
14269
14657
  maskTextClass,
14270
14658
  maskTextSelector,
14271
14659
  excludeAttribute,
14272
- includeAttribute,
14273
14660
  inlineStylesheet,
14274
14661
  maskInputOptions,
14275
14662
  dataURLOptions,
@@ -14282,6 +14669,7 @@ function record(options = {}) {
14282
14669
  iframeManager,
14283
14670
  stylesheetManager,
14284
14671
  canvasManager,
14672
+ visibilityManager,
14285
14673
  keepIframeSrcFn,
14286
14674
  processedNodeManager
14287
14675
  },
@@ -14312,7 +14700,6 @@ function record(options = {}) {
14312
14700
  maskTextClass,
14313
14701
  maskTextSelector,
14314
14702
  excludeAttribute,
14315
- includeAttribute,
14316
14703
  inlineStylesheet,
14317
14704
  maskAllInputs: maskInputOptions,
14318
14705
  maskTextFn,
@@ -14361,6 +14748,12 @@ function record(options = {}) {
14361
14748
  mirror.getId(document)
14362
14749
  );
14363
14750
  };
14751
+ flushCustomEventQueue = () => {
14752
+ for (const e2 of customEventQueue) {
14753
+ wrappedEmit(e2);
14754
+ }
14755
+ customEventQueue.length = 0;
14756
+ };
14364
14757
  try {
14365
14758
  const handlers = [];
14366
14759
  const observe = (doc) => {
@@ -14419,6 +14812,7 @@ function record(options = {}) {
14419
14812
  }
14420
14813
  }),
14421
14814
  canvasMutationCb: wrappedCanvasMutationEmit,
14815
+ visibilityMutationCb: wrappedVisibilityMutationEmit,
14422
14816
  fontCb: (p) => wrappedEmit({
14423
14817
  type: EventType.IncrementalSnapshot,
14424
14818
  data: {
@@ -14450,7 +14844,6 @@ function record(options = {}) {
14450
14844
  maskTextClass,
14451
14845
  maskTextSelector,
14452
14846
  excludeAttribute,
14453
- includeAttribute,
14454
14847
  maskInputOptions,
14455
14848
  inlineStylesheet,
14456
14849
  sampling,
@@ -14472,6 +14865,7 @@ function record(options = {}) {
14472
14865
  shadowDomManager,
14473
14866
  processedNodeManager,
14474
14867
  canvasManager,
14868
+ visibilityManager,
14475
14869
  ignoreCSSAttributes,
14476
14870
  plugins: ((_a2 = plugins == null ? void 0 : plugins.filter((p) => p.observer)) == null ? void 0 : _a2.map((p) => ({
14477
14871
  observer: p.observer,
@@ -14496,14 +14890,14 @@ function record(options = {}) {
14496
14890
  }
14497
14891
  });
14498
14892
  const init = () => {
14499
- if (flushCustomQueue === "before") {
14500
- flushPreRecordingEvents();
14893
+ if (flushCustomEvent === "before") {
14894
+ flushCustomEventQueue();
14501
14895
  }
14502
14896
  takeFullSnapshot$1();
14503
14897
  handlers.push(observe(document));
14504
14898
  recording = true;
14505
- if (flushCustomQueue === "after") {
14506
- flushPreRecordingEvents();
14899
+ if (flushCustomEvent === "after") {
14900
+ flushCustomEventQueue();
14507
14901
  }
14508
14902
  };
14509
14903
  if (document.readyState === "interactive" || document.readyState === "complete") {
@@ -14533,7 +14927,7 @@ function record(options = {}) {
14533
14927
  );
14534
14928
  }
14535
14929
  return () => {
14536
- flushPreRecordingEvents();
14930
+ flushCustomEventQueue();
14537
14931
  handlers.forEach((h) => h());
14538
14932
  processedNodeManager.destroy();
14539
14933
  recording = false;
@@ -14543,12 +14937,10 @@ function record(options = {}) {
14543
14937
  console.warn(error);
14544
14938
  }
14545
14939
  }
14546
- function flushPreRecordingEvents() {
14547
- for (const e2 of preRecordingCustomEvents) {
14548
- wrappedEmit(e2);
14549
- }
14550
- preRecordingCustomEvents.length = 0;
14551
- }
14940
+ record.flushCustomEventQueue = () => {
14941
+ console.warn(`[rrweb] CustomEvent flushing: ${customEventQueue.length} events`);
14942
+ flushCustomEventQueue();
14943
+ };
14552
14944
  record.addCustomEvent = (tag, payload) => {
14553
14945
  const customEvent = {
14554
14946
  type: EventType.Custom,
@@ -14558,8 +14950,8 @@ record.addCustomEvent = (tag, payload) => {
14558
14950
  }
14559
14951
  };
14560
14952
  if (!recording) {
14561
- console.warn(`[rrweb] CustomEvent buffered before recording start: ${tag}`);
14562
- preRecordingCustomEvents.push(customEvent);
14953
+ console.warn(`[rrweb] CustomEvent buffered before/after recording start: ${tag}`);
14954
+ customEventQueue.push(customEvent);
14563
14955
  return;
14564
14956
  }
14565
14957
  wrappedEmit(customEvent);