@openreplay/tracker 16.2.1 → 16.3.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/entry.js CHANGED
@@ -3564,7 +3564,7 @@ function isNodeStillActive(node) {
3564
3564
  return [false, e];
3565
3565
  }
3566
3566
  }
3567
- const defaults = {
3567
+ const defaults$1 = {
3568
3568
  interval: SECOND * 30,
3569
3569
  batchSize: 2500,
3570
3570
  enabled: true,
@@ -3592,7 +3592,7 @@ class Maintainer {
3592
3592
  clearInterval(this.interval);
3593
3593
  }
3594
3594
  };
3595
- this.options = { ...defaults, ...options };
3595
+ this.options = { ...defaults$1, ...options };
3596
3596
  }
3597
3597
  }
3598
3598
 
@@ -5206,7 +5206,7 @@ class App {
5206
5206
  this.stopCallbacks = [];
5207
5207
  this.commitCallbacks = [];
5208
5208
  this.activityState = ActivityState.NotActive;
5209
- this.version = '16.2.1'; // TODO: version compatability check inside each plugin.
5209
+ this.version = '16.3.0-beta.0'; // TODO: version compatability check inside each plugin.
5210
5210
  this.socketMode = false;
5211
5211
  this.compressionThreshold = 24 * 1000;
5212
5212
  this.bc = null;
@@ -8041,60 +8041,137 @@ function Viewport (app) {
8041
8041
  app.ticker.attach(sendSetViewportSize, 5, false);
8042
8042
  }
8043
8043
 
8044
- function CSSRules (app) {
8045
- if (app === null) {
8044
+ const defaults = {
8045
+ checkCssInterval: 200,
8046
+ scanInMemoryCSS: false,
8047
+ checkLimit: undefined,
8048
+ };
8049
+ function CSSRules (app, opts) {
8050
+ if (app === null)
8046
8051
  return;
8047
- }
8048
8052
  if (!window.CSSStyleSheet) {
8049
8053
  app.send(TechnicalInfo('no_stylesheet_prototype_in_window', ''));
8050
8054
  return;
8051
8055
  }
8056
+ const options = { ...defaults, ...opts };
8057
+ // sheetID:index -> ruleText
8058
+ const ruleSnapshots = new Map();
8059
+ let checkInterval = null;
8060
+ const trackedSheets = new Set();
8061
+ const checkIntervalMs = options.checkCssInterval || 200;
8062
+ let checkIterations = {};
8063
+ function checkRuleChanges() {
8064
+ if (!options.scanInMemoryCSS)
8065
+ return;
8066
+ const allSheets = trackedSheets.values();
8067
+ for (const sheet of allSheets) {
8068
+ try {
8069
+ const sheetID = styleSheetIDMap.get(sheet);
8070
+ if (!sheetID)
8071
+ continue;
8072
+ if (options.checkLimit) {
8073
+ if (!checkIterations[sheetID]) {
8074
+ checkIterations[sheetID] = 0;
8075
+ }
8076
+ else {
8077
+ checkIterations[sheetID]++;
8078
+ }
8079
+ if (checkIterations[sheetID] > options.checkLimit) {
8080
+ trackedSheets.delete(sheet);
8081
+ return;
8082
+ }
8083
+ }
8084
+ for (let j = 0; j < sheet.cssRules.length; j++) {
8085
+ try {
8086
+ const rule = sheet.cssRules[j];
8087
+ const key = `${sheetID}:${j}`;
8088
+ const oldText = ruleSnapshots.get(key);
8089
+ const newText = rule.cssText;
8090
+ if (oldText !== newText) {
8091
+ if (oldText !== undefined) {
8092
+ // Rule is changed
8093
+ app.send(AdoptedSSDeleteRule(sheetID, j));
8094
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, newText, j, app.getBaseHref()));
8095
+ }
8096
+ else {
8097
+ // Rule added
8098
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, newText, j, app.getBaseHref()));
8099
+ }
8100
+ ruleSnapshots.set(key, newText);
8101
+ }
8102
+ }
8103
+ catch (e) {
8104
+ /* Skip inaccessible rules */
8105
+ }
8106
+ }
8107
+ const keysToCheck = Array.from(ruleSnapshots.keys()).filter((key) => key.startsWith(`${sheetID}:`));
8108
+ for (const key of keysToCheck) {
8109
+ const index = parseInt(key.split(':')[1], 10);
8110
+ if (index >= sheet.cssRules.length) {
8111
+ ruleSnapshots.delete(key);
8112
+ }
8113
+ }
8114
+ }
8115
+ catch (e) {
8116
+ /* Skip inaccessible sheets */
8117
+ trackedSheets.delete(sheet);
8118
+ }
8119
+ }
8120
+ }
8121
+ const emptyRuleReg = /{\s*}/;
8122
+ function isRuleEmpty(rule) {
8123
+ return emptyRuleReg.test(rule);
8124
+ }
8052
8125
  const sendInsertDeleteRule = app.safe((sheet, index, rule) => {
8053
8126
  const sheetID = styleSheetIDMap.get(sheet);
8054
- if (!sheetID) {
8055
- // OK-case. Sheet haven't been registered yet. Rules will be sent on registration.
8127
+ if (!sheetID)
8056
8128
  return;
8057
- }
8058
8129
  if (typeof rule === 'string') {
8059
8130
  app.send(AdoptedSSInsertRuleURLBased(sheetID, rule, index, app.getBaseHref()));
8131
+ if (isRuleEmpty(rule)) {
8132
+ ruleSnapshots.set(`${sheetID}:${index}`, rule);
8133
+ trackedSheets.add(sheet);
8134
+ }
8060
8135
  }
8061
8136
  else {
8062
8137
  app.send(AdoptedSSDeleteRule(sheetID, index));
8138
+ if (ruleSnapshots.has(`${sheetID}:${index}`)) {
8139
+ ruleSnapshots.delete(`${sheetID}:${index}`);
8140
+ }
8063
8141
  }
8064
8142
  });
8065
- // TODO: proper rule insertion/removal (how?)
8066
8143
  const sendReplaceGroupingRule = app.safe((rule) => {
8067
8144
  let topmostRule = rule;
8068
- while (topmostRule.parentRule) {
8145
+ while (topmostRule.parentRule)
8069
8146
  topmostRule = topmostRule.parentRule;
8070
- }
8071
8147
  const sheet = topmostRule.parentStyleSheet;
8072
- if (!sheet) {
8073
- app.debug.warn('No parent StyleSheet found for', topmostRule, rule);
8148
+ if (!sheet)
8074
8149
  return;
8075
- }
8076
8150
  const sheetID = styleSheetIDMap.get(sheet);
8077
- if (!sheetID) {
8078
- app.debug.warn('No sheedID found for', sheet, styleSheetIDMap);
8151
+ if (!sheetID)
8079
8152
  return;
8080
- }
8081
8153
  const cssText = topmostRule.cssText;
8082
- const ruleList = sheet.cssRules;
8083
- const idx = Array.from(ruleList).indexOf(topmostRule);
8154
+ const idx = Array.from(sheet.cssRules).indexOf(topmostRule);
8084
8155
  if (idx >= 0) {
8085
8156
  app.send(AdoptedSSInsertRuleURLBased(sheetID, cssText, idx, app.getBaseHref()));
8086
- app.send(AdoptedSSDeleteRule(sheetID, idx + 1)); // Remove previous clone
8087
- }
8088
- else {
8089
- app.debug.warn('Rule index not found in', sheet, topmostRule);
8157
+ app.send(AdoptedSSDeleteRule(sheetID, idx + 1));
8158
+ if (isRuleEmpty(cssText)) {
8159
+ ruleSnapshots.set(`${sheetID}:${idx}`, cssText);
8160
+ trackedSheets.add(sheet);
8161
+ }
8090
8162
  }
8091
8163
  });
8164
+ // Patch prototype methods
8092
8165
  const patchContext = app.safe((context) => {
8166
+ if (context.__css_tracking_patched__)
8167
+ return;
8168
+ context.__css_tracking_patched__ = true;
8093
8169
  const { insertRule, deleteRule } = context.CSSStyleSheet.prototype;
8094
8170
  const { insertRule: groupInsertRule, deleteRule: groupDeleteRule } = context.CSSGroupingRule.prototype;
8095
8171
  context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
8096
- sendInsertDeleteRule(this, index, rule);
8097
- return insertRule.call(this, rule, index);
8172
+ const result = insertRule.call(this, rule, index);
8173
+ sendInsertDeleteRule(this, result, rule);
8174
+ return result;
8098
8175
  };
8099
8176
  context.CSSStyleSheet.prototype.deleteRule = function (index) {
8100
8177
  sendInsertDeleteRule(this, index);
@@ -8105,7 +8182,7 @@ function CSSRules (app) {
8105
8182
  sendReplaceGroupingRule(this);
8106
8183
  return result;
8107
8184
  };
8108
- context.CSSGroupingRule.prototype.deleteRule = function (index = 0) {
8185
+ context.CSSGroupingRule.prototype.deleteRule = function (index) {
8109
8186
  const result = groupDeleteRule.call(this, index);
8110
8187
  sendReplaceGroupingRule(this);
8111
8188
  return result;
@@ -8114,24 +8191,38 @@ function CSSRules (app) {
8114
8191
  patchContext(window);
8115
8192
  app.observer.attachContextCallback(patchContext);
8116
8193
  app.nodes.attachNodeCallback((node) => {
8117
- if (!hasTag(node, 'style') || !node.sheet) {
8194
+ if (!hasTag(node, 'style') || !node.sheet)
8195
+ return;
8196
+ if (node.textContent !== null && node.textContent.trim().length > 0)
8118
8197
  return;
8119
- }
8120
- if (node.textContent !== null && node.textContent.trim().length > 0) {
8121
- return; // Non-virtual styles captured by the observer as a text
8122
- }
8123
8198
  const nodeID = app.nodes.getID(node);
8124
- if (!nodeID) {
8199
+ if (!nodeID)
8125
8200
  return;
8126
- }
8127
8201
  const sheet = node.sheet;
8128
8202
  const sheetID = nextID();
8129
8203
  styleSheetIDMap.set(sheet, sheetID);
8130
8204
  app.send(AdoptedSSAddOwner(sheetID, nodeID));
8131
- const rules = sheet.cssRules;
8132
- for (let i = 0; i < rules.length; i++) {
8133
- sendInsertDeleteRule(sheet, i, rules[i].cssText);
8205
+ for (let i = 0; i < sheet.cssRules.length; i++) {
8206
+ try {
8207
+ sendInsertDeleteRule(sheet, i, sheet.cssRules[i].cssText);
8208
+ }
8209
+ catch (e) {
8210
+ // Skip inaccessible rules
8211
+ }
8212
+ }
8213
+ });
8214
+ function startChecking() {
8215
+ if (checkInterval || !options.scanInMemoryCSS)
8216
+ return;
8217
+ checkInterval = window.setInterval(checkRuleChanges, checkIntervalMs);
8218
+ }
8219
+ setTimeout(startChecking, 50);
8220
+ app.attachStopCallback(() => {
8221
+ if (checkInterval) {
8222
+ clearInterval(checkInterval);
8223
+ checkInterval = null;
8134
8224
  }
8225
+ ruleSnapshots.clear();
8135
8226
  });
8136
8227
  }
8137
8228
 
@@ -9696,7 +9787,7 @@ class API {
9696
9787
  this.signalStartIssue = (reason, missingApi) => {
9697
9788
  const doNotTrack = this.checkDoNotTrack();
9698
9789
  console.log("Tracker couldn't start due to:", JSON.stringify({
9699
- trackerVersion: '16.2.1',
9790
+ trackerVersion: '16.3.0-beta.0',
9700
9791
  projectKey: this.options.projectKey,
9701
9792
  doNotTrack,
9702
9793
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9795,7 +9886,7 @@ class API {
9795
9886
  Mouse(app, options.mouse);
9796
9887
  // inside iframe, we ignore viewport scroll
9797
9888
  Scroll(app, this.crossdomainMode);
9798
- CSSRules(app);
9889
+ CSSRules(app, options.css);
9799
9890
  ConstructedStyleSheets(app);
9800
9891
  Console(app, options);
9801
9892
  Exception(app, options);