@openreplay/tracker 3.5.17-beta.0 → 3.6.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.
Files changed (90) hide show
  1. package/.eslintignore +1 -1
  2. package/.prettierignore +1 -0
  3. package/cjs/app/guards.d.ts +2 -1
  4. package/cjs/app/guards.js +5 -1
  5. package/cjs/app/index.d.ts +12 -10
  6. package/cjs/app/index.js +38 -44
  7. package/cjs/app/messages.d.ts +52 -0
  8. package/cjs/app/messages.gen.d.ts +57 -0
  9. package/cjs/app/messages.gen.js +494 -0
  10. package/cjs/app/messages.js +235 -0
  11. package/cjs/app/nodes.js +2 -0
  12. package/cjs/app/observer/iframe_observer.js +2 -2
  13. package/cjs/app/observer/observer.d.ts +1 -2
  14. package/cjs/app/observer/observer.js +40 -39
  15. package/cjs/app/observer/shadow_root_observer.js +2 -2
  16. package/cjs/app/observer/top_observer.d.ts +9 -0
  17. package/cjs/app/observer/top_observer.js +40 -12
  18. package/cjs/app/session.d.ts +17 -1
  19. package/cjs/app/session.js +51 -3
  20. package/cjs/common/{webworker.d.ts → interaction.d.ts} +3 -3
  21. package/cjs/common/{types.js → interaction.js} +0 -0
  22. package/cjs/common/messages.gen.d.ts +382 -0
  23. package/cjs/common/messages.gen.js +62 -0
  24. package/cjs/index.d.ts +3 -3
  25. package/cjs/index.js +15 -10
  26. package/cjs/modules/adoptedStyleSheets.d.ts +2 -0
  27. package/cjs/modules/adoptedStyleSheets.js +127 -0
  28. package/cjs/modules/connection.js +2 -2
  29. package/cjs/modules/console.js +6 -20
  30. package/cjs/modules/cssrules.js +16 -12
  31. package/cjs/modules/exception.d.ts +2 -2
  32. package/cjs/modules/exception.js +18 -14
  33. package/cjs/modules/img.js +31 -21
  34. package/cjs/modules/input.js +6 -6
  35. package/cjs/modules/mouse.js +42 -33
  36. package/cjs/modules/performance.js +2 -2
  37. package/cjs/modules/scroll.js +16 -7
  38. package/cjs/modules/timing.js +4 -4
  39. package/cjs/modules/viewport.js +4 -4
  40. package/lib/app/guards.d.ts +2 -1
  41. package/lib/app/guards.js +3 -0
  42. package/lib/app/index.d.ts +12 -10
  43. package/lib/app/index.js +39 -45
  44. package/lib/app/messages.d.ts +52 -0
  45. package/lib/app/messages.gen.d.ts +57 -0
  46. package/lib/app/messages.gen.js +435 -0
  47. package/lib/app/messages.js +182 -0
  48. package/lib/app/nodes.js +2 -0
  49. package/lib/app/observer/iframe_observer.js +1 -1
  50. package/lib/app/observer/observer.d.ts +1 -2
  51. package/lib/app/observer/observer.js +40 -39
  52. package/lib/app/observer/shadow_root_observer.js +1 -1
  53. package/lib/app/observer/top_observer.d.ts +9 -0
  54. package/lib/app/observer/top_observer.js +40 -12
  55. package/lib/app/session.d.ts +17 -1
  56. package/lib/app/session.js +51 -3
  57. package/lib/common/{webworker.d.ts → interaction.d.ts} +3 -3
  58. package/lib/common/{types.js → interaction.js} +0 -0
  59. package/lib/common/messages.gen.d.ts +382 -0
  60. package/lib/common/messages.gen.js +59 -0
  61. package/lib/common/tsconfig.tsbuildinfo +1 -1
  62. package/lib/index.d.ts +3 -3
  63. package/lib/index.js +15 -10
  64. package/lib/modules/adoptedStyleSheets.d.ts +2 -0
  65. package/lib/modules/adoptedStyleSheets.js +124 -0
  66. package/lib/modules/connection.js +2 -2
  67. package/lib/modules/console.js +6 -20
  68. package/lib/modules/cssrules.js +16 -12
  69. package/lib/modules/exception.d.ts +2 -2
  70. package/lib/modules/exception.js +18 -14
  71. package/lib/modules/img.js +31 -21
  72. package/lib/modules/input.js +6 -6
  73. package/lib/modules/mouse.js +43 -34
  74. package/lib/modules/performance.js +2 -2
  75. package/lib/modules/scroll.js +17 -8
  76. package/lib/modules/timing.js +4 -4
  77. package/lib/modules/viewport.js +4 -4
  78. package/package.json +1 -1
  79. package/cjs/common/messages.d.ts +0 -444
  80. package/cjs/common/messages.js +0 -794
  81. package/cjs/common/types.d.ts +0 -9
  82. package/cjs/common/webworker.js +0 -2
  83. package/cjs/modules/longtasks.d.ts +0 -2
  84. package/cjs/modules/longtasks.js +0 -34
  85. package/lib/common/messages.d.ts +0 -444
  86. package/lib/common/messages.js +0 -790
  87. package/lib/common/types.d.ts +0 -9
  88. package/lib/common/webworker.js +0 -1
  89. package/lib/modules/longtasks.d.ts +0 -2
  90. package/lib/modules/longtasks.js +0 -31
@@ -0,0 +1,124 @@
1
+ import { TechnicalInfo, AdoptedSSReplaceURLBased, AdoptedSSInsertRuleURLBased, AdoptedSSDeleteRule, AdoptedSSAddOwner, AdoptedSSRemoveOwner, } from '../app/messages.gen.js';
2
+ import { isRootNode } from '../app/guards.js';
3
+ function hasAdoptedSS(node) {
4
+ return (isRootNode(node) &&
5
+ // @ts-ignore
6
+ typeof node.adoptedStyleSheets !== 'undefined');
7
+ }
8
+ export default function (app) {
9
+ if (app === null) {
10
+ return;
11
+ }
12
+ if (!hasAdoptedSS(document)) {
13
+ app.attachStartCallback(() => {
14
+ // MBTODO: pre-start sendQueue app
15
+ app.send(TechnicalInfo('no_adopted_stylesheets', ''));
16
+ });
17
+ return;
18
+ }
19
+ let nextID = 0xf;
20
+ const styleSheetIDMap = new Map();
21
+ const adoptedStyleSheetsOwnings = new Map();
22
+ const updateAdoptedStyleSheets = (root) => {
23
+ let nodeID = app.nodes.getID(root);
24
+ if (root === document) {
25
+ nodeID = 0; // main document doesn't have nodeID. ID count starts from the documentElement
26
+ }
27
+ if (!nodeID) {
28
+ return;
29
+ }
30
+ let pastOwning = adoptedStyleSheetsOwnings.get(nodeID);
31
+ if (!pastOwning) {
32
+ pastOwning = [];
33
+ }
34
+ const nowOwning = [];
35
+ const styleSheets = root.adoptedStyleSheets;
36
+ for (const s of styleSheets) {
37
+ let sheetID = styleSheetIDMap.get(s);
38
+ const init = !sheetID;
39
+ if (!sheetID) {
40
+ sheetID = ++nextID;
41
+ }
42
+ nowOwning.push(sheetID);
43
+ if (!pastOwning.includes(sheetID)) {
44
+ app.send(AdoptedSSAddOwner(sheetID, nodeID));
45
+ }
46
+ if (init) {
47
+ const rules = s.cssRules;
48
+ for (let i = 0; i < rules.length; i++) {
49
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, rules[i].cssText, i, app.getBaseHref()));
50
+ }
51
+ }
52
+ }
53
+ for (const sheetID of pastOwning) {
54
+ if (!nowOwning.includes(sheetID)) {
55
+ app.send(AdoptedSSRemoveOwner(sheetID, nodeID));
56
+ }
57
+ }
58
+ adoptedStyleSheetsOwnings.set(nodeID, nowOwning);
59
+ };
60
+ function patchAdoptedStyleSheets(prototype) {
61
+ const nativeAdoptedStyleSheetsDescriptor = Object.getOwnPropertyDescriptor(prototype, 'adoptedStyleSheets');
62
+ if (nativeAdoptedStyleSheetsDescriptor) {
63
+ Object.defineProperty(prototype, 'adoptedStyleSheets', Object.assign(Object.assign({}, nativeAdoptedStyleSheetsDescriptor), { set: function (value) {
64
+ // @ts-ignore
65
+ const retVal = nativeAdoptedStyleSheetsDescriptor.set.call(this, value);
66
+ updateAdoptedStyleSheets(this);
67
+ return retVal;
68
+ } }));
69
+ }
70
+ }
71
+ const patchContext = (context) => {
72
+ patchAdoptedStyleSheets(context.Document.prototype);
73
+ patchAdoptedStyleSheets(context.ShadowRoot.prototype);
74
+ //@ts-ignore TODO: configure ts (use necessary lib)
75
+ const { insertRule, deleteRule, replace, replaceSync } = context.CSSStyleSheet.prototype;
76
+ //@ts-ignore
77
+ context.CSSStyleSheet.prototype.replace = function (text) {
78
+ return replace.call(this, text).then((sheet) => {
79
+ const sheetID = styleSheetIDMap.get(this);
80
+ if (sheetID) {
81
+ app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
82
+ }
83
+ return sheet;
84
+ });
85
+ };
86
+ //@ts-ignore
87
+ context.CSSStyleSheet.prototype.replaceSync = function (text) {
88
+ const sheetID = styleSheetIDMap.get(this);
89
+ if (sheetID) {
90
+ app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
91
+ }
92
+ return replaceSync.call(this, text);
93
+ };
94
+ context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
95
+ const sheetID = styleSheetIDMap.get(this);
96
+ if (sheetID) {
97
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, rule, index, app.getBaseHref()));
98
+ }
99
+ return insertRule.call(this, rule, index);
100
+ };
101
+ context.CSSStyleSheet.prototype.deleteRule = function (index) {
102
+ const sheetID = styleSheetIDMap.get(this);
103
+ if (sheetID) {
104
+ app.send(AdoptedSSDeleteRule(sheetID, index));
105
+ }
106
+ return deleteRule.call(this, index);
107
+ };
108
+ };
109
+ patchContext(window);
110
+ app.observer.attachContextCallback(patchContext);
111
+ app.attachStopCallback(() => {
112
+ styleSheetIDMap.clear();
113
+ adoptedStyleSheetsOwnings.clear();
114
+ });
115
+ // So far main Document is not triggered with nodeCallbacks
116
+ app.attachStartCallback(() => {
117
+ updateAdoptedStyleSheets(document);
118
+ });
119
+ app.nodes.attachNodeCallback((node) => {
120
+ if (hasAdoptedSS(node)) {
121
+ updateAdoptedStyleSheets(node);
122
+ }
123
+ });
124
+ }
@@ -1,4 +1,4 @@
1
- import { ConnectionInformation } from '../common/messages.js';
1
+ import { ConnectionInformation } from '../app/messages.gen.js';
2
2
  export default function (app) {
3
3
  const connection = navigator.connection ||
4
4
  navigator.mozConnection ||
@@ -6,7 +6,7 @@ export default function (app) {
6
6
  if (connection === undefined) {
7
7
  return;
8
8
  }
9
- const sendConnectionInformation = () => app.send(new ConnectionInformation(Math.round(connection.downlink * 1000), connection.type || 'unknown'));
9
+ const sendConnectionInformation = () => app.send(ConnectionInformation(Math.round(connection.downlink * 1000), connection.type || 'unknown'));
10
10
  sendConnectionInformation();
11
11
  connection.addEventListener('change', sendConnectionInformation);
12
12
  }
@@ -1,6 +1,5 @@
1
- import { hasTag } from '../app/guards.js';
2
1
  import { IN_BROWSER } from '../utils.js';
3
- import { ConsoleLog } from '../common/messages.js';
2
+ import { ConsoleLog } from '../app/messages.gen.js';
4
3
  const printError = IN_BROWSER && 'InstallTrigger' in window // detect Firefox
5
4
  ? (e) => e.message + '\n' + e.stack
6
5
  : (e) => e.stack || e.message;
@@ -90,7 +89,7 @@ export default function (app, opts) {
90
89
  if (!Array.isArray(options.consoleMethods) || options.consoleMethods.length === 0) {
91
90
  return;
92
91
  }
93
- const sendConsoleLog = app.safe((level, args) => app.send(new ConsoleLog(level, printf(args))));
92
+ const sendConsoleLog = app.safe((level, args) => app.send(ConsoleLog(level, printf(args))));
94
93
  let n;
95
94
  const reset = () => {
96
95
  n = 0;
@@ -99,7 +98,7 @@ export default function (app, opts) {
99
98
  app.ticker.attach(reset, 33, false);
100
99
  const patchConsole = (console) => options.consoleMethods.forEach((method) => {
101
100
  if (consoleMethods.indexOf(method) === -1) {
102
- console.error(`OpenReplay: unsupported console method "${method}"`);
101
+ app.debug.error(`OpenReplay: unsupported console method "${method}"`);
103
102
  return;
104
103
  }
105
104
  const fn = console[method];
@@ -111,20 +110,7 @@ export default function (app, opts) {
111
110
  sendConsoleLog(method, args);
112
111
  };
113
112
  });
114
- patchConsole(window.console);
115
- app.nodes.attachNodeCallback(app.safe((node) => {
116
- if (hasTag(node, 'IFRAME')) {
117
- // TODO: newContextCallback
118
- let context = node.contentWindow;
119
- if (context) {
120
- patchConsole(context.console);
121
- }
122
- app.attachEventListener(node, 'load', () => {
123
- if (node.contentWindow !== context) {
124
- context = node.contentWindow;
125
- patchConsole(context.console);
126
- }
127
- });
128
- }
129
- }));
113
+ const patchContext = app.safe((context) => patchConsole(context.console));
114
+ patchContext(window);
115
+ app.observer.attachContextCallback(patchContext);
130
116
  }
@@ -1,17 +1,17 @@
1
- import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from '../common/messages.js';
1
+ import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from '../app/messages.gen.js';
2
2
  import { hasTag } from '../app/guards.js';
3
3
  export default function (app) {
4
4
  if (app === null) {
5
5
  return;
6
6
  }
7
7
  if (!window.CSSStyleSheet) {
8
- app.send(new TechnicalInfo('no_stylesheet_prototype_in_window', ''));
8
+ app.send(TechnicalInfo('no_stylesheet_prototype_in_window', ''));
9
9
  return;
10
10
  }
11
11
  const processOperation = app.safe((stylesheet, index, rule) => {
12
12
  const sendMessage = typeof rule === 'string'
13
- ? (nodeID) => app.send(new CSSInsertRuleURLBased(nodeID, rule, index, app.getBaseHref()))
14
- : (nodeID) => app.send(new CSSDeleteRule(nodeID, index));
13
+ ? (nodeID) => app.send(CSSInsertRuleURLBased(nodeID, rule, index, app.getBaseHref()))
14
+ : (nodeID) => app.send(CSSDeleteRule(nodeID, index));
15
15
  // TODO: Extend messages to maintain nested rules (CSSGroupingRule prototype, as well as CSSKeyframesRule)
16
16
  if (stylesheet.ownerNode == null) {
17
17
  throw new Error('Owner Node not found');
@@ -21,15 +21,19 @@ export default function (app) {
21
21
  sendMessage(nodeID);
22
22
  } // else error?
23
23
  });
24
- const { insertRule, deleteRule } = CSSStyleSheet.prototype;
25
- CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
26
- processOperation(this, index, rule);
27
- return insertRule.call(this, rule, index);
28
- };
29
- CSSStyleSheet.prototype.deleteRule = function (index) {
30
- processOperation(this, index);
31
- return deleteRule.call(this, index);
24
+ const patchContext = (context) => {
25
+ const { insertRule, deleteRule } = context.CSSStyleSheet.prototype;
26
+ context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
27
+ processOperation(this, index, rule);
28
+ return insertRule.call(this, rule, index);
29
+ };
30
+ context.CSSStyleSheet.prototype.deleteRule = function (index) {
31
+ processOperation(this, index);
32
+ return deleteRule.call(this, index);
33
+ };
32
34
  };
35
+ patchContext(window);
36
+ app.observer.attachContextCallback(patchContext);
33
37
  app.nodes.attachNodeCallback((node) => {
34
38
  if (!hasTag(node, 'STYLE') || !node.sheet) {
35
39
  return;
@@ -1,5 +1,5 @@
1
1
  import type App from '../app/index.js';
2
- import type Message from '../common/messages.js';
2
+ import type Message from '../app/messages.gen.js';
3
3
  export interface Options {
4
4
  captureExceptions: boolean;
5
5
  }
@@ -11,6 +11,6 @@ interface StackFrame {
11
11
  source?: string;
12
12
  }
13
13
  export declare function getExceptionMessage(error: Error, fallbackStack: Array<StackFrame>): Message;
14
- export declare function getExceptionMessageFromEvent(e: ErrorEvent | PromiseRejectionEvent): Message | null;
14
+ export declare function getExceptionMessageFromEvent(e: ErrorEvent | PromiseRejectionEvent, context?: typeof globalThis): Message | null;
15
15
  export default function (app: App, opts: Partial<Options>): void;
16
16
  export {};
@@ -1,4 +1,4 @@
1
- import { JSException } from '../common/messages.js';
1
+ import { JSException } from '../app/messages.gen.js';
2
2
  import ErrorStackParser from 'error-stack-parser';
3
3
  function getDefaultStack(e) {
4
4
  return [
@@ -17,9 +17,9 @@ export function getExceptionMessage(error, fallbackStack) {
17
17
  stack = ErrorStackParser.parse(error);
18
18
  }
19
19
  catch (e) { }
20
- return new JSException(error.name, error.message, JSON.stringify(stack));
20
+ return JSException(error.name, error.message, JSON.stringify(stack));
21
21
  }
22
- export function getExceptionMessageFromEvent(e) {
22
+ export function getExceptionMessageFromEvent(e, context = window) {
23
23
  if (e instanceof ErrorEvent) {
24
24
  if (e.error instanceof Error) {
25
25
  return getExceptionMessage(e.error, getDefaultStack(e));
@@ -30,10 +30,10 @@ export function getExceptionMessageFromEvent(e) {
30
30
  name = 'Error';
31
31
  message = e.message;
32
32
  }
33
- return new JSException(name, message, JSON.stringify(getDefaultStack(e)));
33
+ return JSException(name, message, JSON.stringify(getDefaultStack(e)));
34
34
  }
35
35
  }
36
- else if ('PromiseRejectionEvent' in window && e instanceof PromiseRejectionEvent) {
36
+ else if ('PromiseRejectionEvent' in context && e instanceof context.PromiseRejectionEvent) {
37
37
  if (e.reason instanceof Error) {
38
38
  return getExceptionMessage(e.reason, []);
39
39
  }
@@ -45,7 +45,7 @@ export function getExceptionMessageFromEvent(e) {
45
45
  catch (_) {
46
46
  message = String(e.reason);
47
47
  }
48
- return new JSException('Unhandled Promise Rejection', message, '[]');
48
+ return JSException('Unhandled Promise Rejection', message, '[]');
49
49
  }
50
50
  }
51
51
  return null;
@@ -55,13 +55,17 @@ export default function (app, opts) {
55
55
  captureExceptions: true,
56
56
  }, opts);
57
57
  if (options.captureExceptions) {
58
- const handler = (e) => {
59
- const msg = getExceptionMessageFromEvent(e);
60
- if (msg != null) {
61
- app.send(msg);
62
- }
63
- };
64
- app.attachEventListener(window, 'unhandledrejection', (e) => handler(e));
65
- app.attachEventListener(window, 'error', (e) => handler(e));
58
+ function patchContext(context) {
59
+ const handler = (e) => {
60
+ const msg = getExceptionMessageFromEvent(e, context);
61
+ if (msg != null) {
62
+ app.send(msg);
63
+ }
64
+ };
65
+ app.attachEventListener(context, 'unhandledrejection', handler);
66
+ app.attachEventListener(context, 'error', handler);
67
+ }
68
+ app.observer.attachContextCallback(patchContext);
69
+ patchContext(window);
66
70
  }
67
71
  }
@@ -1,5 +1,5 @@
1
1
  import { timestamp, isURL } from '../utils.js';
2
- import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute, } from '../common/messages.js';
2
+ import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from '../app/messages.gen.js';
3
3
  import { hasTag } from '../app/guards.js';
4
4
  function resolveURL(url, location = document.location) {
5
5
  url = url.trim();
@@ -19,16 +19,31 @@ function resolveURL(url, location = document.location) {
19
19
  const PLACEHOLDER_SRC = 'https://static.openreplay.com/tracker/placeholder.jpeg';
20
20
  export default function (app) {
21
21
  function sendPlaceholder(id, node) {
22
- app.send(new SetNodeAttribute(id, 'src', PLACEHOLDER_SRC));
22
+ app.send(SetNodeAttribute(id, 'src', PLACEHOLDER_SRC));
23
23
  const { width, height } = node.getBoundingClientRect();
24
24
  if (!node.hasAttribute('width')) {
25
- app.send(new SetNodeAttribute(id, 'width', String(width)));
25
+ app.send(SetNodeAttribute(id, 'width', String(width)));
26
26
  }
27
27
  if (!node.hasAttribute('height')) {
28
- app.send(new SetNodeAttribute(id, 'height', String(height)));
28
+ app.send(SetNodeAttribute(id, 'height', String(height)));
29
29
  }
30
30
  }
31
- const sendImgSrc = app.safe(function () {
31
+ const sendSrcset = function (id, img) {
32
+ const { srcset } = img;
33
+ if (!srcset) {
34
+ return;
35
+ }
36
+ const resolvedSrcset = srcset
37
+ .split(',')
38
+ .map((str) => resolveURL(str))
39
+ .join(',');
40
+ app.send(SetNodeAttribute(id, 'srcset', resolvedSrcset));
41
+ };
42
+ const sendSrc = function (id, img) {
43
+ const src = img.src;
44
+ app.send(SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
45
+ };
46
+ const sendImgAttrs = app.safe(function () {
32
47
  const id = app.nodes.getID(this);
33
48
  if (id === undefined) {
34
49
  return;
@@ -40,21 +55,15 @@ export default function (app) {
40
55
  const resolvedSrc = resolveURL(src || ''); // Src type is null sometimes. - is it true?
41
56
  if (naturalWidth === 0 && naturalHeight === 0) {
42
57
  if (isURL(resolvedSrc)) {
43
- app.send(new ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
58
+ app.send(ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
44
59
  }
45
60
  }
46
61
  else if (resolvedSrc.length >= 1e5 || app.sanitizer.isMasked(id)) {
47
62
  sendPlaceholder(id, this);
48
63
  }
49
64
  else {
50
- app.send(new SetNodeAttribute(id, 'src', resolvedSrc));
51
- if (srcset) {
52
- const resolvedSrcset = srcset
53
- .split(',')
54
- .map((str) => resolveURL(str))
55
- .join(',');
56
- app.send(new SetNodeAttribute(id, 'srcset', resolvedSrcset));
57
- }
65
+ sendSrc(id, this);
66
+ sendSrcset(id, this);
58
67
  }
59
68
  });
60
69
  const observer = new MutationObserver((mutations) => {
@@ -66,23 +75,24 @@ export default function (app) {
66
75
  return;
67
76
  }
68
77
  if (mutation.attributeName === 'src') {
69
- const src = target.src;
70
- app.send(new SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
78
+ sendSrc(id, target);
71
79
  }
72
80
  if (mutation.attributeName === 'srcset') {
73
- const srcset = target.srcset;
74
- app.send(new SetNodeAttribute(id, 'srcset', srcset));
81
+ sendSrcset(id, target);
75
82
  }
76
83
  }
77
84
  }
78
85
  });
86
+ app.attachStopCallback(() => {
87
+ observer.disconnect();
88
+ });
79
89
  app.nodes.attachNodeCallback((node) => {
80
90
  if (!hasTag(node, 'IMG')) {
81
91
  return;
82
92
  }
83
- app.nodes.attachElementListener('error', node, sendImgSrc);
84
- app.nodes.attachElementListener('load', node, sendImgSrc);
85
- sendImgSrc.call(node);
93
+ app.nodes.attachElementListener('error', node, sendImgAttrs.bind(node));
94
+ app.nodes.attachElementListener('load', node, sendImgAttrs.bind(node));
95
+ sendImgAttrs.call(node);
86
96
  observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
87
97
  });
88
98
  }
@@ -1,6 +1,6 @@
1
1
  import { normSpaces, IN_BROWSER, getLabelAttribute, hasOpenreplayAttribute } from '../utils.js';
2
2
  import { hasTag } from '../app/guards.js';
3
- import { SetInputTarget, SetInputValue, SetInputChecked } from '../common/messages.js';
3
+ import { SetInputTarget, SetInputValue, SetInputChecked } from '../app/messages.gen.js';
4
4
  const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date'];
5
5
  function isTextEditable(node) {
6
6
  if (hasTag(node, 'TEXTAREA')) {
@@ -40,7 +40,7 @@ const labelElementFor = IN_BROWSER && 'labels' in HTMLInputElement.prototype
40
40
  }
41
41
  const id = node.id;
42
42
  if (id) {
43
- const labels = document.querySelectorAll('label[for="' + id + '"]');
43
+ const labels = node.ownerDocument.querySelectorAll('label[for="' + id + '"]');
44
44
  if (labels !== null && labels.length === 1) {
45
45
  return labels[0];
46
46
  }
@@ -70,7 +70,7 @@ export default function (app, opts) {
70
70
  function sendInputTarget(id, node) {
71
71
  const label = getInputLabel(node);
72
72
  if (label !== '') {
73
- app.send(new SetInputTarget(id, label));
73
+ app.send(SetInputTarget(id, label));
74
74
  }
75
75
  }
76
76
  function sendInputValue(id, node) {
@@ -97,7 +97,7 @@ export default function (app, opts) {
97
97
  value = '';
98
98
  break;
99
99
  }
100
- app.send(new SetInputValue(id, value, mask));
100
+ app.send(SetInputValue(id, value, mask));
101
101
  }
102
102
  const inputValues = new Map();
103
103
  const checkableValues = new Map();
@@ -135,7 +135,7 @@ export default function (app, opts) {
135
135
  }
136
136
  if (checked !== node.checked) {
137
137
  checkableValues.set(id, node.checked);
138
- app.send(new SetInputChecked(id, node.checked));
138
+ app.send(SetInputChecked(id, node.checked));
139
139
  }
140
140
  });
141
141
  });
@@ -159,7 +159,7 @@ export default function (app, opts) {
159
159
  }
160
160
  if (isCheckable(node)) {
161
161
  checkableValues.set(id, node.checked);
162
- app.send(new SetInputChecked(id, node.checked));
162
+ app.send(SetInputChecked(id, node.checked));
163
163
  return;
164
164
  }
165
165
  }));
@@ -1,8 +1,8 @@
1
- import { hasTag, isSVGElement } from '../app/guards.js';
1
+ import { hasTag, isSVGElement, isDocument } from '../app/guards.js';
2
2
  import { normSpaces, hasOpenreplayAttribute, getLabelAttribute } from '../utils.js';
3
- import { MouseMove, MouseClick } from '../common/messages.js';
3
+ import { MouseMove, MouseClick } from '../app/messages.gen.js';
4
4
  import { getInputLabel } from './input.js';
5
- function _getSelector(target) {
5
+ function _getSelector(target, document) {
6
6
  let el = target;
7
7
  let selector = null;
8
8
  do {
@@ -32,16 +32,16 @@ function isClickable(element) {
32
32
  element.onclick != null ||
33
33
  element.getAttribute('role') === 'button');
34
34
  //|| element.className.includes("btn")
35
- // MBTODO: intersect addEventListener
35
+ // MBTODO: intersept addEventListener
36
36
  }
37
- //TODO: fix (typescript doesn't allow work when the guard is inside the function)
38
- function getTarget(target) {
37
+ //TODO: fix (typescript is not sure about target variable after assignation of svg)
38
+ function getTarget(target, document) {
39
39
  if (target instanceof Element) {
40
- return _getTarget(target);
40
+ return _getTarget(target, document);
41
41
  }
42
42
  return null;
43
43
  }
44
- function _getTarget(target) {
44
+ function _getTarget(target, document) {
45
45
  let element = target;
46
46
  while (element !== null && element !== document.documentElement) {
47
47
  if (hasOpenreplayAttribute(element, 'masked')) {
@@ -104,37 +104,46 @@ export default function (app) {
104
104
  });
105
105
  const sendMouseMove = () => {
106
106
  if (mousePositionChanged) {
107
- app.send(new MouseMove(mousePositionX, mousePositionY));
107
+ app.send(MouseMove(mousePositionX, mousePositionY));
108
108
  mousePositionChanged = false;
109
109
  }
110
110
  };
111
- const selectorMap = {};
112
- function getSelector(id, target) {
113
- return (selectorMap[id] = selectorMap[id] || _getSelector(target));
114
- }
115
- app.attachEventListener(document.documentElement, 'mouseover', (e) => {
116
- const target = getTarget(e.target);
117
- if (target !== mouseTarget) {
118
- mouseTarget = target;
119
- mouseTargetTime = performance.now();
120
- }
121
- });
122
- app.attachEventListener(document, 'mousemove', (e) => {
123
- mousePositionX = e.clientX;
124
- mousePositionY = e.clientY;
125
- mousePositionChanged = true;
126
- }, false);
127
- app.attachEventListener(document, 'click', (e) => {
128
- const target = getTarget(e.target);
129
- if ((!e.clientX && !e.clientY) || target === null) {
130
- return;
111
+ const patchDocument = (document) => {
112
+ const selectorMap = {};
113
+ function getSelector(id, target) {
114
+ return (selectorMap[id] = selectorMap[id] || _getSelector(target, document));
131
115
  }
132
- const id = app.nodes.getID(target);
133
- if (id !== undefined) {
134
- sendMouseMove();
135
- app.send(new MouseClick(id, mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0, getTargetLabel(target), getSelector(id, target)), true);
116
+ app.attachEventListener(document.documentElement, 'mouseover', (e) => {
117
+ const target = getTarget(e.target, document);
118
+ if (target !== mouseTarget) {
119
+ mouseTarget = target;
120
+ mouseTargetTime = performance.now();
121
+ }
122
+ });
123
+ app.attachEventListener(document, 'mousemove', (e) => {
124
+ const { top, left } = app.observer.getDocumentOffset(document);
125
+ mousePositionX = e.clientX + left;
126
+ mousePositionY = e.clientY + top;
127
+ mousePositionChanged = true;
128
+ }, false);
129
+ app.attachEventListener(document, 'click', (e) => {
130
+ const target = getTarget(e.target, document);
131
+ if ((!e.clientX && !e.clientY) || target === null) {
132
+ return;
133
+ }
134
+ const id = app.nodes.getID(target);
135
+ if (id !== undefined) {
136
+ sendMouseMove();
137
+ app.send(MouseClick(id, mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0, getTargetLabel(target), getSelector(id, target)), true);
138
+ }
139
+ mouseTarget = null;
140
+ });
141
+ };
142
+ app.nodes.attachNodeCallback((node) => {
143
+ if (isDocument(node)) {
144
+ patchDocument(node);
136
145
  }
137
- mouseTarget = null;
138
146
  });
147
+ patchDocument(document);
139
148
  app.ticker.attach(sendMouseMove, 10);
140
149
  }
@@ -1,5 +1,5 @@
1
1
  import { IN_BROWSER } from '../utils.js';
2
- import { PerformanceTrack } from '../common/messages.js';
2
+ import { PerformanceTrack } from '../app/messages.gen.js';
3
3
  const perf = IN_BROWSER && 'performance' in window && 'memory' in performance // works in Chrome only
4
4
  ? performance
5
5
  : { memory: {} };
@@ -31,7 +31,7 @@ export default function (app, opts) {
31
31
  if (frames === undefined || ticks === undefined) {
32
32
  return;
33
33
  }
34
- app.send(new PerformanceTrack(frames, ticks, perf.memory.totalJSHeapSize || 0, perf.memory.usedJSHeapSize || 0));
34
+ app.send(PerformanceTrack(frames, ticks, perf.memory.totalJSHeapSize || 0, perf.memory.usedJSHeapSize || 0));
35
35
  ticks = frames = document.hidden ? -1 : 0;
36
36
  };
37
37
  app.attachStartCallback(() => {
@@ -1,9 +1,14 @@
1
- import { SetViewportScroll, SetNodeScroll } from '../common/messages.js';
2
- import { isElementNode } from '../app/guards.js';
1
+ import { SetViewportScroll, SetNodeScroll } from '../app/messages.gen.js';
2
+ import { isElementNode, isRootNode } from '../app/guards.js';
3
3
  export default function (app) {
4
4
  let documentScroll = false;
5
5
  const nodeScroll = new Map();
6
- const sendSetViewportScroll = app.safe(() => app.send(new SetViewportScroll(window.pageXOffset ||
6
+ function setNodeScroll(target) {
7
+ if (target instanceof Element) {
8
+ nodeScroll.set(target, [target.scrollLeft, target.scrollTop]);
9
+ }
10
+ }
11
+ const sendSetViewportScroll = app.safe(() => app.send(SetViewportScroll(window.pageXOffset ||
7
12
  (document.documentElement && document.documentElement.scrollLeft) ||
8
13
  (document.body && document.body.scrollLeft) ||
9
14
  0, window.pageYOffset ||
@@ -13,7 +18,7 @@ export default function (app) {
13
18
  const sendSetNodeScroll = app.safe((s, node) => {
14
19
  const id = app.nodes.getID(node);
15
20
  if (id !== undefined) {
16
- app.send(new SetNodeScroll(id, s[0], s[1]));
21
+ app.send(SetNodeScroll(id, s[0], s[1]));
17
22
  }
18
23
  });
19
24
  app.attachStartCallback(sendSetViewportScroll);
@@ -25,16 +30,20 @@ export default function (app) {
25
30
  if (isStart && isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
26
31
  nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
27
32
  }
33
+ else if (isRootNode(node)) {
34
+ // scroll is not-composed event (https://javascript.info/shadow-dom-events)
35
+ app.attachEventListener(node, 'scroll', (e) => {
36
+ setNodeScroll(e.target);
37
+ });
38
+ }
28
39
  });
29
- app.attachEventListener(window, 'scroll', (e) => {
40
+ app.attachEventListener(document, 'scroll', (e) => {
30
41
  const target = e.target;
31
42
  if (target === document) {
32
43
  documentScroll = true;
33
44
  return;
34
45
  }
35
- if (target instanceof Element) {
36
- nodeScroll.set(target, [target.scrollLeft, target.scrollTop]);
37
- }
46
+ setNodeScroll(target);
38
47
  });
39
48
  app.ticker.attach(() => {
40
49
  if (documentScroll) {