@atlaskit/editor-plugin-interactivity 1.0.0 → 1.2.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/CHANGELOG.md +27 -0
- package/README.md +79 -0
- package/afm-cc/tsconfig.json +3 -0
- package/afm-products/tsconfig.json +3 -0
- package/dist/cjs/collections/bounded-list.js +42 -0
- package/dist/cjs/collections/bounded-map.js +42 -0
- package/dist/cjs/collector/interaction-events.js +0 -7
- package/dist/cjs/collector/interaction-group.js +31 -4
- package/dist/cjs/collector/interaction-tracker.js +210 -68
- package/dist/cjs/collector/interactivity-collector.js +52 -22
- package/dist/cjs/collector/interactivity-session.js +3 -0
- package/dist/cjs/collector/long-animation-frame-observer.js +62 -0
- package/dist/cjs/collector/slow-interaction-list.js +413 -0
- package/dist/es2019/collections/bounded-list.js +24 -0
- package/dist/es2019/collections/bounded-map.js +25 -0
- package/dist/es2019/collector/interaction-events.js +0 -7
- package/dist/es2019/collector/interaction-group.js +28 -5
- package/dist/es2019/collector/interaction-tracker.js +201 -55
- package/dist/es2019/collector/interactivity-collector.js +35 -20
- package/dist/es2019/collector/interactivity-session.js +3 -0
- package/dist/es2019/collector/long-animation-frame-observer.js +42 -0
- package/dist/es2019/collector/slow-interaction-list.js +342 -0
- package/dist/esm/collections/bounded-list.js +35 -0
- package/dist/esm/collections/bounded-map.js +35 -0
- package/dist/esm/collector/interaction-events.js +0 -7
- package/dist/esm/collector/interaction-group.js +31 -5
- package/dist/esm/collector/interaction-tracker.js +210 -68
- package/dist/esm/collector/interactivity-collector.js +52 -22
- package/dist/esm/collector/interactivity-session.js +3 -0
- package/dist/esm/collector/long-animation-frame-observer.js +55 -0
- package/dist/esm/collector/slow-interaction-list.js +407 -0
- package/dist/types/analytics/interactivity-snapshot.d.ts +65 -0
- package/dist/types/collections/bounded-list.d.ts +9 -0
- package/dist/types/collections/bounded-map.d.ts +9 -0
- package/dist/types/collector/interaction-events.d.ts +1 -7
- package/dist/types/collector/interaction-group.d.ts +18 -5
- package/dist/types/collector/interaction-tracker.d.ts +67 -13
- package/dist/types/collector/interactivity-collector.d.ts +3 -0
- package/dist/types/collector/interactivity-session.d.ts +2 -0
- package/dist/types/collector/long-animation-frame-observer.d.ts +28 -0
- package/dist/types/collector/slow-interaction-list.d.ts +64 -0
- package/docs/0-intro.tsx +2 -1
- package/package.json +4 -3
|
@@ -9,12 +9,16 @@ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/cl
|
|
|
9
9
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
10
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
11
|
var _browserApis = require("@atlaskit/browser-apis");
|
|
12
|
+
var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
|
|
12
13
|
var _editorEventObserver = require("./editor-event-observer");
|
|
13
14
|
var _interactionObserver = require("./interaction-observer");
|
|
14
15
|
var _interactivitySession = require("./interactivity-session");
|
|
15
16
|
var _bucketBoundaries = require("./bucket-boundaries");
|
|
16
17
|
var _lifecycleObserver = require("./lifecycle-observer");
|
|
18
|
+
var _longAnimationFrameObserver = require("./long-animation-frame-observer");
|
|
17
19
|
var _snapshotScheduler = require("./snapshot-scheduler");
|
|
20
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
21
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
18
22
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
19
23
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
20
24
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
@@ -43,6 +47,9 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
43
47
|
this.interactionObserver = new _interactionObserver.InteractionObserver(function (entries) {
|
|
44
48
|
return _this.recordEntries(entries);
|
|
45
49
|
});
|
|
50
|
+
this.longAnimationFrameObserver = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_editor_interactivity_slowest') ? new _longAnimationFrameObserver.LongAnimationFrameObserver(function (frames) {
|
|
51
|
+
return _this.recordFrames(frames);
|
|
52
|
+
}) : undefined;
|
|
46
53
|
this.editorEvents = new _editorEventObserver.EditorEventObserver(function (event) {
|
|
47
54
|
return _this.recordEditorEvent(event);
|
|
48
55
|
});
|
|
@@ -75,6 +82,7 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
75
82
|
return (0, _createClass2.default)(InteractivityCollector, [{
|
|
76
83
|
key: "start",
|
|
77
84
|
value: function start() {
|
|
85
|
+
var _this$longAnimationFr;
|
|
78
86
|
// Collecting is over for good once stopped: the session it covered has been reported.
|
|
79
87
|
if (this.stopped) {
|
|
80
88
|
return false;
|
|
@@ -90,6 +98,7 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
90
98
|
// this object was constructed.
|
|
91
99
|
this.session = this.createSession(0);
|
|
92
100
|
this.interactionObserver.start();
|
|
101
|
+
(_this$longAnimationFr = this.longAnimationFrameObserver) === null || _this$longAnimationFr === void 0 || _this$longAnimationFr.start();
|
|
93
102
|
this.editorEvents.observe(this.editorRoot);
|
|
94
103
|
this.snapshotScheduler.start();
|
|
95
104
|
this.lifecycleObserver.start();
|
|
@@ -101,12 +110,14 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
101
110
|
}, {
|
|
102
111
|
key: "stop",
|
|
103
112
|
value: function stop() {
|
|
113
|
+
var _this$longAnimationFr2;
|
|
104
114
|
if (this.stopped) {
|
|
105
115
|
return;
|
|
106
116
|
}
|
|
107
117
|
this.takeSnapshot('unmount');
|
|
108
118
|
this.stopped = true;
|
|
109
119
|
this.interactionObserver.stop();
|
|
120
|
+
(_this$longAnimationFr2 = this.longAnimationFrameObserver) === null || _this$longAnimationFr2 === void 0 || _this$longAnimationFr2.stop();
|
|
110
121
|
this.editorEvents.stop();
|
|
111
122
|
this.snapshotScheduler.stop();
|
|
112
123
|
this.lifecycleObserver.stop();
|
|
@@ -254,33 +265,47 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
254
265
|
this.session[group].countTotal();
|
|
255
266
|
this.session.revision += 1;
|
|
256
267
|
}
|
|
268
|
+
|
|
269
|
+
/** Long Animation Frames say where the latency of a slow interaction went. */
|
|
270
|
+
}, {
|
|
271
|
+
key: "recordFrames",
|
|
272
|
+
value: function recordFrames(frames) {
|
|
273
|
+
var _this$session$slowest;
|
|
274
|
+
if (this.stopped) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if ((_this$session$slowest = this.session.slowest) !== null && _this$session$slowest !== void 0 && _this$session$slowest.trackLongAnimationFrames(frames)) {
|
|
278
|
+
this.session.revision += 1;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
257
281
|
}, {
|
|
258
282
|
key: "recordEntries",
|
|
259
283
|
value: function recordEntries(entries) {
|
|
260
|
-
var _this$session = this.session,
|
|
261
|
-
tracker = _this$session.tracker,
|
|
262
|
-
page = _this$session.page;
|
|
263
284
|
var _iterator = _createForOfIteratorHelper(entries),
|
|
264
285
|
_step;
|
|
265
286
|
try {
|
|
266
287
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
267
288
|
var entry = _step.value;
|
|
268
|
-
var
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
289
|
+
var _iterator2 = _createForOfIteratorHelper(this.session.tracker.merge(entry)),
|
|
290
|
+
_step2;
|
|
291
|
+
try {
|
|
292
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
293
|
+
var _this$session$slowest2;
|
|
294
|
+
var update = _step2.value;
|
|
295
|
+
var groupsChanged = this.session.page.trackInteractionUpdate(update);
|
|
296
|
+
if (update.group) {
|
|
297
|
+
groupsChanged = this.session[update.group].trackInteractionUpdate(update) || groupsChanged;
|
|
298
|
+
}
|
|
299
|
+
var slowestChanged = (_this$session$slowest2 = this.session.slowest) === null || _this$session$slowest2 === void 0 ? void 0 : _this$session$slowest2.trackInteractionUpdate(entry, update);
|
|
300
|
+
if (groupsChanged || slowestChanged) {
|
|
301
|
+
this.session.revision += 1;
|
|
302
|
+
}
|
|
281
303
|
}
|
|
304
|
+
} catch (err) {
|
|
305
|
+
_iterator2.e(err);
|
|
306
|
+
} finally {
|
|
307
|
+
_iterator2.f();
|
|
282
308
|
}
|
|
283
|
-
this.session.revision += 1;
|
|
284
309
|
}
|
|
285
310
|
} catch (err) {
|
|
286
311
|
_iterator.e(err);
|
|
@@ -291,6 +316,7 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
291
316
|
}, {
|
|
292
317
|
key: "takeSnapshot",
|
|
293
318
|
value: function takeSnapshot(reason) {
|
|
319
|
+
var _this$longAnimationFr3, _session$slowest;
|
|
294
320
|
if (this.stopped) {
|
|
295
321
|
return;
|
|
296
322
|
}
|
|
@@ -304,10 +330,11 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
304
330
|
return;
|
|
305
331
|
}
|
|
306
332
|
|
|
307
|
-
// Must run before the change check: the browser may be holding entries that have
|
|
308
|
-
// reached the observer
|
|
309
|
-
// pick them up.
|
|
333
|
+
// Must run before the change check: the browser may be holding entries and frames that have
|
|
334
|
+
// not reached the observer callbacks yet, and on `pagehide` there is no later chance to
|
|
335
|
+
// pick them up. Entries first, so a record the frames answer for exists by then.
|
|
310
336
|
this.interactionObserver.drain();
|
|
337
|
+
(_this$longAnimationFr3 = this.longAnimationFrameObserver) === null || _this$longAnimationFr3 === void 0 || _this$longAnimationFr3.drain();
|
|
311
338
|
if (session.revision === session.emittedRevision) {
|
|
312
339
|
return;
|
|
313
340
|
}
|
|
@@ -319,7 +346,8 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
319
346
|
var now = performance.now();
|
|
320
347
|
var hiddenMs = session.hiddenMs + (session.hiddenSince === undefined ? 0 : now - session.hiddenSince);
|
|
321
348
|
var pageTotalCount = _interactionObserver.InteractionObserver.readPageInteractionCount() - session.interactionCountAtStart;
|
|
322
|
-
|
|
349
|
+
var slowest = (_session$slowest = session.slowest) === null || _session$slowest === void 0 ? void 0 : _session$slowest.snapshot();
|
|
350
|
+
this.emit(_objectSpread({
|
|
323
351
|
schema: _bucketBoundaries.SCHEMA_VERSION,
|
|
324
352
|
interactivitySessionId: session.id,
|
|
325
353
|
objectId: session.objectId,
|
|
@@ -335,7 +363,9 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
335
363
|
editorTyping: session.editorTyping.snapshot(),
|
|
336
364
|
editorPointer: session.editorPointer.snapshot(),
|
|
337
365
|
editorOther: session.editorOther.snapshot()
|
|
338
|
-
}
|
|
366
|
+
}, slowest && {
|
|
367
|
+
slowest: slowest
|
|
368
|
+
}));
|
|
339
369
|
}
|
|
340
370
|
}]);
|
|
341
371
|
}();
|
|
@@ -8,9 +8,11 @@ exports.InteractivitySession = void 0;
|
|
|
8
8
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
9
9
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
10
10
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
|
+
var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
|
|
11
12
|
var _interactionObserver = require("./interaction-observer");
|
|
12
13
|
var _interactionTracker = require("./interaction-tracker");
|
|
13
14
|
var _interactionGroup = require("./interaction-group");
|
|
15
|
+
var _slowInteractionList = require("./slow-interaction-list");
|
|
14
16
|
function createSessionId() {
|
|
15
17
|
if (typeof crypto.randomUUID === 'function') {
|
|
16
18
|
return crypto.randomUUID();
|
|
@@ -37,6 +39,7 @@ var InteractivitySession = exports.InteractivitySession = /*#__PURE__*/(0, _crea
|
|
|
37
39
|
(0, _defineProperty2.default)(this, "editorTyping", new _interactionGroup.InteractionGroup());
|
|
38
40
|
(0, _defineProperty2.default)(this, "editorPointer", new _interactionGroup.InteractionGroup());
|
|
39
41
|
(0, _defineProperty2.default)(this, "editorOther", new _interactionGroup.InteractionGroup());
|
|
42
|
+
(0, _defineProperty2.default)(this, "slowest", (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_editor_interactivity_slowest') ? new _slowInteractionList.SlowInteractionList() : undefined);
|
|
40
43
|
/** Increments per snapshot; a query takes the highest one per session. */
|
|
41
44
|
(0, _defineProperty2.default)(this, "seq", 0);
|
|
42
45
|
(0, _defineProperty2.default)(this, "hiddenMs", 0);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.LongAnimationFrameObserver = void 0;
|
|
8
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
9
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
|
+
/**
|
|
11
|
+
* Reports the frames the browser took longer than 50 ms to render to `onFrames`. They are what says
|
|
12
|
+
* where the time of a slow interaction went — which script ran the longest while the user waited,
|
|
13
|
+
* and how much of the wait was style and layout — which Event Timing cannot answer.
|
|
14
|
+
*
|
|
15
|
+
* What they say about an interaction is `SlowInteractionList`'s to work out; this only observes.
|
|
16
|
+
*/
|
|
17
|
+
var LongAnimationFrameObserver = exports.LongAnimationFrameObserver = /*#__PURE__*/function () {
|
|
18
|
+
function LongAnimationFrameObserver(onFrames) {
|
|
19
|
+
(0, _classCallCheck2.default)(this, LongAnimationFrameObserver);
|
|
20
|
+
this.onFrames = onFrames;
|
|
21
|
+
}
|
|
22
|
+
return (0, _createClass2.default)(LongAnimationFrameObserver, [{
|
|
23
|
+
key: "start",
|
|
24
|
+
value: function start() {
|
|
25
|
+
var _this = this;
|
|
26
|
+
if (this.observer || !LongAnimationFrameObserver.isSupported()) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.observer = new PerformanceObserver(function (list) {
|
|
30
|
+
_this.onFrames(list.getEntries());
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Buffered, as `web-vitals` observes them: a frame reported before the editor mounted can
|
|
34
|
+
// still be the one an interaction right after it ran in.
|
|
35
|
+
this.observer.observe({
|
|
36
|
+
type: 'long-animation-frame',
|
|
37
|
+
buffered: true
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}, {
|
|
41
|
+
key: "drain",
|
|
42
|
+
value: function drain() {
|
|
43
|
+
var _this$observer;
|
|
44
|
+
var frames = (_this$observer = this.observer) === null || _this$observer === void 0 ? void 0 : _this$observer.takeRecords();
|
|
45
|
+
if (frames) {
|
|
46
|
+
this.onFrames(frames);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}, {
|
|
50
|
+
key: "stop",
|
|
51
|
+
value: function stop() {
|
|
52
|
+
var _this$observer2;
|
|
53
|
+
(_this$observer2 = this.observer) === null || _this$observer2 === void 0 || _this$observer2.disconnect();
|
|
54
|
+
this.observer = undefined;
|
|
55
|
+
}
|
|
56
|
+
}], [{
|
|
57
|
+
key: "isSupported",
|
|
58
|
+
value: function isSupported() {
|
|
59
|
+
return typeof PerformanceObserver !== 'undefined' && PerformanceObserver.supportedEntryTypes.includes('long-animation-frame');
|
|
60
|
+
}
|
|
61
|
+
}]);
|
|
62
|
+
}();
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.SlowInteractionList = void 0;
|
|
8
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
9
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
10
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
11
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
12
|
+
var _boundedList = require("../collections/bounded-list");
|
|
13
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
14
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
15
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
16
|
+
/** Fixed by the schema, so the event stays a bounded size. */
|
|
17
|
+
var MAX_RECORDS = 5;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The latency an interaction has to beat to be recorded. 200 ms is the Google INP "good" threshold,
|
|
21
|
+
* so anything below it is an interaction the user was not waiting for.
|
|
22
|
+
*/
|
|
23
|
+
var MIN_LATENCY_MS = 200;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How many frames are kept to attribute records from. An interaction spans one paint, so a few dozen
|
|
27
|
+
* cover even a second of a janky page.
|
|
28
|
+
*/
|
|
29
|
+
var MAX_FRAMES = 64;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The attributes a target may be named by, all of them ours: `data-vc` for visual completion, the
|
|
33
|
+
* test ids for tests. Ids, roles, class names, text and accessibility labels are left out because
|
|
34
|
+
* they can carry what the user wrote.
|
|
35
|
+
*/
|
|
36
|
+
var ALLOWED_TARGET_ATTRIBUTES = ['data-vc', 'data-testid', 'data-test-id'];
|
|
37
|
+
var MAX_TARGET_ELEMENTS = 4;
|
|
38
|
+
var MAX_ATTRIBUTE_VALUE_LENGTH = 32;
|
|
39
|
+
var MAX_TARGET_LENGTH = 120;
|
|
40
|
+
|
|
41
|
+
/** How long a reported script or function name may be. */
|
|
42
|
+
var MAX_NAME_LENGTH = 64;
|
|
43
|
+
var QUERY_OR_HASH = /[#\?]/;
|
|
44
|
+
|
|
45
|
+
/** The part of a record that only the frames the interaction ran in can fill in. */
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether two attributions say the same thing. Shallow, because every field of one is a number or a
|
|
49
|
+
* string; by the field names of both, so that a field going missing counts as a change rather than
|
|
50
|
+
* as nothing to see.
|
|
51
|
+
*/
|
|
52
|
+
function sameAttribution(one, other) {
|
|
53
|
+
var fields = Object.keys(one);
|
|
54
|
+
return fields.length === Object.keys(other).length && fields.every(function (field) {
|
|
55
|
+
return one[field] === other[field];
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One recorded interaction. The reported phases are not among its fields: they are the gaps between
|
|
61
|
+
* the boundaries, worked out when the record is reported, which is also where the latency is
|
|
62
|
+
* rounded into the `durationMs` the event carries.
|
|
63
|
+
*
|
|
64
|
+
* The attribution is kept whole rather than spread across the record, so that a record can never
|
|
65
|
+
* hold half of what one set of frames said and half of what another did.
|
|
66
|
+
*
|
|
67
|
+
* `interactionId` identifies the interaction across the entries measuring it, and `boundaries` is
|
|
68
|
+
* also what its frames are matched to it by. Neither is reported.
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* The slowest interactions of one session, which is what the event's `slowest` records are.
|
|
72
|
+
*
|
|
73
|
+
* Interactions arrive from the tracker and frames from the Long Animation Frame observer, and this
|
|
74
|
+
* is where the two meet: a record says both how long the user waited and where that time went.
|
|
75
|
+
*
|
|
76
|
+
* A record is built from the entry that measured the interaction, as that entry arrives:
|
|
77
|
+
* `entry.target` is `null` once the element has left the document.
|
|
78
|
+
*/
|
|
79
|
+
var SlowInteractionList = exports.SlowInteractionList = /*#__PURE__*/function () {
|
|
80
|
+
function SlowInteractionList() {
|
|
81
|
+
(0, _classCallCheck2.default)(this, SlowInteractionList);
|
|
82
|
+
/** Slowest first. */
|
|
83
|
+
(0, _defineProperty2.default)(this, "records", []);
|
|
84
|
+
(0, _defineProperty2.default)(this, "frames", new _boundedList.BoundedList(MAX_FRAMES));
|
|
85
|
+
}
|
|
86
|
+
return (0, _createClass2.default)(SlowInteractionList, [{
|
|
87
|
+
key: "trackInteractionUpdate",
|
|
88
|
+
value:
|
|
89
|
+
/**
|
|
90
|
+
* Takes in what the tracker now says about an interaction, keeping it when it is one of the
|
|
91
|
+
* slowest of the session.
|
|
92
|
+
*
|
|
93
|
+
* @returns whether that changed what a snapshot would carry.
|
|
94
|
+
*/
|
|
95
|
+
function trackInteractionUpdate(entry, update) {
|
|
96
|
+
var boundaries = update.boundaries,
|
|
97
|
+
interactionId = update.interactionId,
|
|
98
|
+
latencyMs = update.latencyMs;
|
|
99
|
+
var index = this.records.findIndex(function (record) {
|
|
100
|
+
return record.interactionId === interactionId;
|
|
101
|
+
});
|
|
102
|
+
var knownRecord = index === -1 ? undefined : this.records[index];
|
|
103
|
+
if (knownRecord && latencyMs <= knownRecord.latencyMs) {
|
|
104
|
+
var _knownRecord$boundari, _knownRecord$boundari2;
|
|
105
|
+
// The interaction at the latency it already had, so its name and target still come from
|
|
106
|
+
// the entry that measured it at its slowest — and so do `startedAt` and `presentedAt`,
|
|
107
|
+
// which leaves the processing as the only pair that can have moved.
|
|
108
|
+
if (((_knownRecord$boundari = knownRecord.boundaries) === null || _knownRecord$boundari === void 0 ? void 0 : _knownRecord$boundari.processingStartedAt) === (boundaries === null || boundaries === void 0 ? void 0 : boundaries.processingStartedAt) && ((_knownRecord$boundari2 = knownRecord.boundaries) === null || _knownRecord$boundari2 === void 0 ? void 0 : _knownRecord$boundari2.processingEndedAt) === (boundaries === null || boundaries === void 0 ? void 0 : boundaries.processingEndedAt)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
knownRecord.boundaries = boundaries;
|
|
112
|
+
this.attribute(knownRecord);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Everything below builds a record out of `entry`, so it has to be an entry of this
|
|
117
|
+
// interaction. The tracker also reports an interaction whose boundaries moved because of an
|
|
118
|
+
// event that is no interaction of its own, and that event names something else entirely.
|
|
119
|
+
if (entry.interactionId !== interactionId) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (knownRecord) {
|
|
123
|
+
// An interaction measured as slower replaces itself rather than taking a second place.
|
|
124
|
+
this.records[index] = this.toRecord(entry, update);
|
|
125
|
+
} else {
|
|
126
|
+
var toBeatMs = this.records.length === MAX_RECORDS ? this.records[MAX_RECORDS - 1].latencyMs : MIN_LATENCY_MS;
|
|
127
|
+
if (latencyMs <= toBeatMs) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
this.records.push(this.toRecord(entry, update));
|
|
131
|
+
}
|
|
132
|
+
this.records.sort(function (a, b) {
|
|
133
|
+
return b.latencyMs - a.latencyMs;
|
|
134
|
+
});
|
|
135
|
+
this.records.splice(MAX_RECORDS);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Takes in the frames the browser has just reported and works out again what the frames say about
|
|
141
|
+
* every record — again, because the frames of one interaction can be reported in several batches
|
|
142
|
+
* and the first of them may hold neither its longest script nor all of its style and layout.
|
|
143
|
+
*
|
|
144
|
+
* @returns whether that changed what a snapshot would carry.
|
|
145
|
+
*/
|
|
146
|
+
}, {
|
|
147
|
+
key: "trackLongAnimationFrames",
|
|
148
|
+
value: function trackLongAnimationFrames(frames) {
|
|
149
|
+
var _this$frames;
|
|
150
|
+
(_this$frames = this.frames).push.apply(_this$frames, (0, _toConsumableArray2.default)(frames));
|
|
151
|
+
var changed = false;
|
|
152
|
+
var _iterator = _createForOfIteratorHelper(this.records),
|
|
153
|
+
_step;
|
|
154
|
+
try {
|
|
155
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
156
|
+
var record = _step.value;
|
|
157
|
+
changed = this.attribute(record) || changed;
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
_iterator.e(err);
|
|
161
|
+
} finally {
|
|
162
|
+
_iterator.f();
|
|
163
|
+
}
|
|
164
|
+
return changed;
|
|
165
|
+
}
|
|
166
|
+
}, {
|
|
167
|
+
key: "snapshot",
|
|
168
|
+
value: function snapshot() {
|
|
169
|
+
if (this.records.length === 0) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
return this.records.map(function (record) {
|
|
173
|
+
var _record$attribution, _record$attribution2, _record$attribution3, _record$attribution4, _record$attribution5, _record$attribution6, _record$attribution7, _record$attribution8, _record$attribution9;
|
|
174
|
+
var boundaries = record.boundaries;
|
|
175
|
+
return {
|
|
176
|
+
group: record.group,
|
|
177
|
+
name: record.name,
|
|
178
|
+
durationMs: Math.round(record.latencyMs),
|
|
179
|
+
inputDelayMs: boundaries && Math.round(boundaries.processingStartedAt - boundaries.startedAt),
|
|
180
|
+
processingMs: boundaries && Math.round(boundaries.processingEndedAt - boundaries.processingStartedAt),
|
|
181
|
+
presentationDelayMs: boundaries && Math.round(boundaries.presentedAt - boundaries.processingEndedAt),
|
|
182
|
+
target: record.target,
|
|
183
|
+
functionName: (_record$attribution = record.attribution) === null || _record$attribution === void 0 ? void 0 : _record$attribution.functionName,
|
|
184
|
+
invokerType: (_record$attribution2 = record.attribution) === null || _record$attribution2 === void 0 ? void 0 : _record$attribution2.invokerType,
|
|
185
|
+
longestScriptMs: (_record$attribution3 = record.attribution) === null || _record$attribution3 === void 0 ? void 0 : _record$attribution3.longestScriptMs,
|
|
186
|
+
scriptName: (_record$attribution4 = record.attribution) === null || _record$attribution4 === void 0 ? void 0 : _record$attribution4.scriptName,
|
|
187
|
+
scriptSubpart: (_record$attribution5 = record.attribution) === null || _record$attribution5 === void 0 ? void 0 : _record$attribution5.scriptSubpart,
|
|
188
|
+
totalPaintDurationMs: (_record$attribution6 = record.attribution) === null || _record$attribution6 === void 0 ? void 0 : _record$attribution6.totalPaintDurationMs,
|
|
189
|
+
totalScriptDurationMs: (_record$attribution7 = record.attribution) === null || _record$attribution7 === void 0 ? void 0 : _record$attribution7.totalScriptDurationMs,
|
|
190
|
+
totalStyleAndLayoutDurationMs: (_record$attribution8 = record.attribution) === null || _record$attribution8 === void 0 ? void 0 : _record$attribution8.totalStyleAndLayoutDurationMs,
|
|
191
|
+
totalUnattributedDurationMs: (_record$attribution9 = record.attribution) === null || _record$attribution9 === void 0 ? void 0 : _record$attribution9.totalUnattributedDurationMs
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}, {
|
|
196
|
+
key: "toRecord",
|
|
197
|
+
value: function toRecord(entry, update) {
|
|
198
|
+
var _update$group;
|
|
199
|
+
// The target is read only once the interaction has earned a place: naming it walks the DOM,
|
|
200
|
+
// and this runs while the page is already slow.
|
|
201
|
+
var record = {
|
|
202
|
+
attribution: undefined,
|
|
203
|
+
boundaries: update.boundaries,
|
|
204
|
+
interactionId: update.interactionId,
|
|
205
|
+
// An interaction the editor never reported an event for is not the editor's as far as we
|
|
206
|
+
// know.
|
|
207
|
+
group: (_update$group = update.group) !== null && _update$group !== void 0 ? _update$group : 'outsideEditor',
|
|
208
|
+
name: entry.name,
|
|
209
|
+
latencyMs: update.latencyMs,
|
|
210
|
+
target: this.describeTarget(entry.target)
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Frames reported before this entry already answer for it.
|
|
214
|
+
this.attribute(record);
|
|
215
|
+
return record;
|
|
216
|
+
}
|
|
217
|
+
}, {
|
|
218
|
+
key: "attribute",
|
|
219
|
+
value: function attribute(record) {
|
|
220
|
+
var attribution = record.boundaries && this.attributionFor(record.boundaries);
|
|
221
|
+
if (!attribution) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
if (record.attribution && sameAttribution(record.attribution, attribution)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
record.attribution = attribution;
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* What the frames say about an interaction, attributed the way `web-vitals` attributes INP: every
|
|
233
|
+
* frame overlapping the interaction counts, the script that counts is the one with the longest
|
|
234
|
+
* part inside it, and style and layout is summed across those frames.
|
|
235
|
+
*
|
|
236
|
+
* @returns nothing when no frame overlaps the interaction — the browser reports frames above
|
|
237
|
+
* 50 ms only.
|
|
238
|
+
*/
|
|
239
|
+
}, {
|
|
240
|
+
key: "attributionFor",
|
|
241
|
+
value: function attributionFor(boundaries) {
|
|
242
|
+
var _longestScript, _longestScript2, _longestScript3;
|
|
243
|
+
var overlapped = false;
|
|
244
|
+
var lastFrameEndTime = 0;
|
|
245
|
+
var totalScriptDurationMs = 0;
|
|
246
|
+
var totalStyleAndLayoutDurationMs = 0;
|
|
247
|
+
var longestScript;
|
|
248
|
+
var longestScriptMs = 0;
|
|
249
|
+
var _iterator2 = _createForOfIteratorHelper(this.frames),
|
|
250
|
+
_step2;
|
|
251
|
+
try {
|
|
252
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
253
|
+
var _frame$scripts;
|
|
254
|
+
var frame = _step2.value;
|
|
255
|
+
// Frames come in the order they were rendered, so once one starts after the interaction,
|
|
256
|
+
// so does every frame after it.
|
|
257
|
+
if (frame.startTime > boundaries.processingEndedAt) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
var frameEndTime = frame.startTime + frame.duration;
|
|
261
|
+
if (frameEndTime < boundaries.startedAt) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
overlapped = true;
|
|
265
|
+
lastFrameEndTime = frameEndTime;
|
|
266
|
+
totalStyleAndLayoutDurationMs += this.styleAndLayoutOf(frame);
|
|
267
|
+
var _iterator3 = _createForOfIteratorHelper((_frame$scripts = frame.scripts) !== null && _frame$scripts !== void 0 ? _frame$scripts : []),
|
|
268
|
+
_step3;
|
|
269
|
+
try {
|
|
270
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
271
|
+
var _script$forcedStyleAn;
|
|
272
|
+
var script = _step3.value;
|
|
273
|
+
var scriptEndTime = script.startTime + script.duration;
|
|
274
|
+
if (scriptEndTime < boundaries.startedAt) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
var insideInteractionMs = scriptEndTime - Math.max(boundaries.startedAt, script.startTime);
|
|
278
|
+
// `forcedStyleAndLayoutDuration` carries no timestamps, so the part of it inside the
|
|
279
|
+
// interaction is apportioned. It counts as style and layout rather than script time,
|
|
280
|
+
// the same split DevTools shows.
|
|
281
|
+
var forcedInsideMs = script.duration ? insideInteractionMs / script.duration * ((_script$forcedStyleAn = script.forcedStyleAndLayoutDuration) !== null && _script$forcedStyleAn !== void 0 ? _script$forcedStyleAn : 0) : 0;
|
|
282
|
+
totalScriptDurationMs += insideInteractionMs - forcedInsideMs;
|
|
283
|
+
totalStyleAndLayoutDurationMs += forcedInsideMs;
|
|
284
|
+
if (insideInteractionMs > longestScriptMs) {
|
|
285
|
+
longestScript = script;
|
|
286
|
+
longestScriptMs = insideInteractionMs;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch (err) {
|
|
290
|
+
_iterator3.e(err);
|
|
291
|
+
} finally {
|
|
292
|
+
_iterator3.f();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch (err) {
|
|
296
|
+
_iterator2.e(err);
|
|
297
|
+
} finally {
|
|
298
|
+
_iterator2.f();
|
|
299
|
+
}
|
|
300
|
+
if (!overlapped) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// What the browser did after the last frame of the interaction, so it only counts when that
|
|
305
|
+
// frame ended no earlier than the handlers did.
|
|
306
|
+
var totalPaintDurationMs = lastFrameEndTime >= boundaries.processingEndedAt ? Math.max(0, boundaries.presentedAt - lastFrameEndTime) : 0;
|
|
307
|
+
// Every total is brought to what it is reported as before this subtraction, so that the four
|
|
308
|
+
// of them add up to the latency rather than to more than it: a frame whose render phase runs
|
|
309
|
+
// past the interaction would otherwise leave a negative here to be counted twice.
|
|
310
|
+
totalScriptDurationMs = Math.max(0, totalScriptDurationMs);
|
|
311
|
+
totalStyleAndLayoutDurationMs = Math.max(0, totalStyleAndLayoutDurationMs);
|
|
312
|
+
// Whatever is left of the latency: the thread was busy with something the frames attributed
|
|
313
|
+
// to no script, to no style and layout, and to no paint.
|
|
314
|
+
var totalUnattributedDurationMs = Math.max(0, boundaries.presentedAt - boundaries.startedAt - totalScriptDurationMs - totalStyleAndLayoutDurationMs - totalPaintDurationMs);
|
|
315
|
+
return {
|
|
316
|
+
functionName: this.truncated((_longestScript = longestScript) === null || _longestScript === void 0 ? void 0 : _longestScript.sourceFunctionName),
|
|
317
|
+
invokerType: this.truncated((_longestScript2 = longestScript) === null || _longestScript2 === void 0 ? void 0 : _longestScript2.invokerType),
|
|
318
|
+
longestScriptMs: longestScript && Math.round(longestScriptMs),
|
|
319
|
+
scriptName: this.truncated(this.fileName((_longestScript3 = longestScript) === null || _longestScript3 === void 0 ? void 0 : _longestScript3.sourceURL)),
|
|
320
|
+
scriptSubpart: longestScript && this.subpartOf(longestScript, boundaries),
|
|
321
|
+
totalPaintDurationMs: Math.round(totalPaintDurationMs),
|
|
322
|
+
totalScriptDurationMs: Math.round(totalScriptDurationMs),
|
|
323
|
+
totalStyleAndLayoutDurationMs: Math.round(totalStyleAndLayoutDurationMs),
|
|
324
|
+
totalUnattributedDurationMs: Math.round(totalUnattributedDurationMs)
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Style, layout and paint of the frame, which the browser reports as starting at 0 when the
|
|
330
|
+
* frame did none.
|
|
331
|
+
*/
|
|
332
|
+
}, {
|
|
333
|
+
key: "styleAndLayoutOf",
|
|
334
|
+
value: function styleAndLayoutOf(frame) {
|
|
335
|
+
var styleAndLayoutStart = frame.styleAndLayoutStart;
|
|
336
|
+
if (typeof styleAndLayoutStart !== 'number' || styleAndLayoutStart === 0) {
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
var frameEndTime = frame.startTime + frame.duration;
|
|
340
|
+
return Math.max(0, frameEndTime - styleAndLayoutStart);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Which phase of the interaction the script ran in, by where it started. */
|
|
344
|
+
}, {
|
|
345
|
+
key: "subpartOf",
|
|
346
|
+
value: function subpartOf(script, boundaries) {
|
|
347
|
+
if (script.startTime < boundaries.processingStartedAt) {
|
|
348
|
+
return 'inputDelay';
|
|
349
|
+
}
|
|
350
|
+
return script.startTime >= boundaries.processingEndedAt ? 'presentationDelay' : 'processing';
|
|
351
|
+
}
|
|
352
|
+
}, {
|
|
353
|
+
key: "truncated",
|
|
354
|
+
value: function truncated(name) {
|
|
355
|
+
return name ? name.slice(0, MAX_NAME_LENGTH) : undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The file as the browser named it, content hash and all: that is what identifies the artefact
|
|
360
|
+
* and its source map, and a query can be grouped away downstream.
|
|
361
|
+
*/
|
|
362
|
+
}, {
|
|
363
|
+
key: "fileName",
|
|
364
|
+
value: function fileName(sourceURL) {
|
|
365
|
+
if (!sourceURL) {
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
var path = sourceURL.split(QUERY_OR_HASH)[0];
|
|
369
|
+
return path.slice(path.lastIndexOf('/') + 1);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Names the element an interaction happened on — `div[data-vc="x"] > p > span`, outermost first.
|
|
374
|
+
* The path climbs until an element carries an allow-listed attribute, because that is what says
|
|
375
|
+
* which part of the page this was.
|
|
376
|
+
*/
|
|
377
|
+
}, {
|
|
378
|
+
key: "describeTarget",
|
|
379
|
+
value: function describeTarget(node) {
|
|
380
|
+
var _node$parentElement;
|
|
381
|
+
// An event's target can be a text node, and the element around it is the answer for it.
|
|
382
|
+
var element = node instanceof Element ? node : (_node$parentElement = node === null || node === void 0 ? void 0 : node.parentElement) !== null && _node$parentElement !== void 0 ? _node$parentElement : null;
|
|
383
|
+
var path = [];
|
|
384
|
+
for (var climbed = 0; element && climbed < MAX_TARGET_ELEMENTS; climbed += 1) {
|
|
385
|
+
var attribute = this.identifyingAttribute(element);
|
|
386
|
+
path.unshift("".concat(element.localName).concat(attribute !== null && attribute !== void 0 ? attribute : ''));
|
|
387
|
+
if (attribute) {
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
element = element.parentElement;
|
|
391
|
+
}
|
|
392
|
+
if (path.length === 0) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
return path.join(' > ').slice(0, MAX_TARGET_LENGTH);
|
|
396
|
+
}
|
|
397
|
+
}, {
|
|
398
|
+
key: "identifyingAttribute",
|
|
399
|
+
value: function identifyingAttribute(element) {
|
|
400
|
+
for (var _i = 0, _ALLOWED_TARGET_ATTRI = ALLOWED_TARGET_ATTRIBUTES; _i < _ALLOWED_TARGET_ATTRI.length; _i++) {
|
|
401
|
+
var attribute = _ALLOWED_TARGET_ATTRI[_i];
|
|
402
|
+
var value = element.getAttribute(attribute);
|
|
403
|
+
if (value) {
|
|
404
|
+
// Encoded and cut: a value we did not write cannot bring quotes or a paragraph of
|
|
405
|
+
// text into the event.
|
|
406
|
+
var safeValue = encodeURIComponent(value).slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
|
|
407
|
+
return "[".concat(attribute, "=\"").concat(safeValue, "\"]");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
}]);
|
|
413
|
+
}();
|