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