@atlaskit/editor-plugin-limited-mode 14.0.0 → 14.1.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/afm-cc/tsconfig.json +9 -0
  3. package/dist/cjs/limitedModePlugin.js +7 -4
  4. package/dist/cjs/pm-plugins/main.js +201 -18
  5. package/dist/cjs/pm-plugins/plugin-key.js +8 -0
  6. package/dist/cjs/pm-plugins/utils/latch-detector-types.js +5 -0
  7. package/dist/cjs/pm-plugins/utils/latch-detector.js +108 -0
  8. package/dist/cjs/pm-plugins/utils/latch-policy-types.js +1 -0
  9. package/dist/cjs/pm-plugins/utils/latch-policy.js +314 -0
  10. package/dist/es2019/limitedModePlugin.js +4 -3
  11. package/dist/es2019/pm-plugins/main.js +195 -16
  12. package/dist/es2019/pm-plugins/plugin-key.js +2 -0
  13. package/dist/es2019/pm-plugins/utils/latch-detector-types.js +1 -0
  14. package/dist/es2019/pm-plugins/utils/latch-detector.js +92 -0
  15. package/dist/es2019/pm-plugins/utils/latch-policy-types.js +0 -0
  16. package/dist/es2019/pm-plugins/utils/latch-policy.js +277 -0
  17. package/dist/esm/limitedModePlugin.js +6 -3
  18. package/dist/esm/pm-plugins/main.js +199 -16
  19. package/dist/esm/pm-plugins/plugin-key.js +2 -0
  20. package/dist/esm/pm-plugins/utils/latch-detector-types.js +1 -0
  21. package/dist/esm/pm-plugins/utils/latch-detector.js +102 -0
  22. package/dist/esm/pm-plugins/utils/latch-policy-types.js +0 -0
  23. package/dist/esm/pm-plugins/utils/latch-policy.js +307 -0
  24. package/dist/types/limitedModePluginType.d.ts +34 -1
  25. package/dist/types/pm-plugins/main.d.ts +12 -4
  26. package/dist/types/pm-plugins/plugin-key.d.ts +2 -0
  27. package/dist/types/pm-plugins/utils/latch-detector-types.d.ts +30 -0
  28. package/dist/types/pm-plugins/utils/latch-detector.d.ts +15 -0
  29. package/dist/types/pm-plugins/utils/latch-policy-types.d.ts +150 -0
  30. package/dist/types/pm-plugins/utils/latch-policy.d.ts +112 -0
  31. package/package.json +7 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # @atlaskit/editor-plugin-limited-mode
2
2
 
3
+ ## 14.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 14.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`aec3fcfe13ff9`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/aec3fcfe13ff9) -
14
+ EDITOR-8303 Add runtime input-latency trigger for limited mode behind
15
+ platform_editor_dynamic_limited_mode. Limited mode can now latch mid-session when sustained slow
16
+ input or repeated browser freezes indicate the device is struggling, in addition to the existing
17
+ document-size decision. The latch is one-way. Nothing is constructed when the experiment is off.
18
+
19
+ How much evidence is needed is a single tunable, `requiredConfirmations`: that many qualifying
20
+ windows, each separated from the last by `confirmationGapMs`. The hardware no longer feeds into
21
+ the decision — `navigator.hardwareConcurrency` and `navigator.deviceMemory` are reported with the
22
+ `limitedModeLatched` event instead, so which devices latch can be answered from the data rather
23
+ than assumed up front.
24
+
25
+ `LimitedModePluginState` gains a derived `enabled` flag, which is now the single thing consumers
26
+ should branch on — the individual reasons (`documentSizeBreachesThreshold`, `latchPolicyBreached`)
27
+ no longer need to be combined at each call site, so a future reason needs no change outside this
28
+ plugin. `sharedState.enabled` reads from it.
29
+
30
+ Under the experiment the document decision is also evaluated on load and on document replacement
31
+ only, rather than on every transaction that changes the document. That check walks the whole
32
+ document, so it was a full-document scan per keystroke on exactly the pages least able to afford
33
+ one. The trade-off is that a document editing its way past the thresholds is not re-judged until
34
+ it next loads.
35
+
36
+ Consumers updated to react to a mid-session change: table sticky headers now subscribe instead of
37
+ reading once at construction, block-controls resets rather than freezes its state on entry, and
38
+ the expand, media and table nodeviews read the derived flag so they no longer miss a runtime
39
+ latch.
40
+
41
+ ### Patch Changes
42
+
43
+ - Updated dependencies
44
+
3
45
  ## 14.0.0
4
46
 
5
47
  ### Patch Changes
@@ -17,9 +17,18 @@
17
17
  "../src/**/examples.*"
18
18
  ],
19
19
  "references": [
20
+ {
21
+ "path": "../../editor-plugin-analytics/afm-cc/tsconfig.json"
22
+ },
20
23
  {
21
24
  "path": "../../editor-prosemirror/afm-cc/tsconfig.json"
22
25
  },
26
+ {
27
+ "path": "../../../measurement/feature-gate-js-client/afm-cc/tsconfig.json"
28
+ },
29
+ {
30
+ "path": "../../../platform/feature-experiments/afm-cc/tsconfig.json"
31
+ },
23
32
  {
24
33
  "path": "../../../platform/feature-flags/afm-cc/tsconfig.json"
25
34
  },
@@ -7,6 +7,7 @@ exports.limitedModePlugin = void 0;
7
7
  var _nodeAnchor = require("@atlaskit/editor-common/node-anchor");
8
8
  var _usePluginStateEffect = require("@atlaskit/editor-common/use-plugin-state-effect");
9
9
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
10
+ var _pluginKey = require("./pm-plugins/plugin-key");
10
11
  var _main = require("./pm-plugins/main");
11
12
  var limitedModePlugin = exports.limitedModePlugin = function limitedModePlugin(_ref) {
12
13
  var api = _ref.api;
@@ -15,7 +16,9 @@ var limitedModePlugin = exports.limitedModePlugin = function limitedModePlugin(_
15
16
  pmPlugins: function pmPlugins() {
16
17
  return [{
17
18
  name: 'limitedModePlugin',
18
- plugin: _main.createPlugin
19
+ plugin: function plugin() {
20
+ return (0, _main.createPlugin)(api);
21
+ }
19
22
  }];
20
23
  },
21
24
  getSharedState: function getSharedState(editorState) {
@@ -23,14 +26,14 @@ var limitedModePlugin = exports.limitedModePlugin = function limitedModePlugin(_
23
26
  return {
24
27
  get enabled() {
25
28
  var _limitedModePluginKey, _limitedModePluginKey2;
26
- return (_limitedModePluginKey = (_limitedModePluginKey2 = _main.limitedModePluginKey.getState(editorState)) === null || _limitedModePluginKey2 === void 0 ? void 0 : _limitedModePluginKey2.documentSizeBreachesThreshold) !== null && _limitedModePluginKey !== void 0 ? _limitedModePluginKey : false;
29
+ return (_limitedModePluginKey = (_limitedModePluginKey2 = _pluginKey.limitedModePluginKey.getState(editorState)) === null || _limitedModePluginKey2 === void 0 ? void 0 : _limitedModePluginKey2.enabled) !== null && _limitedModePluginKey !== void 0 ? _limitedModePluginKey : false;
27
30
  },
28
- limitedModePluginKey: _main.limitedModePluginKey
31
+ limitedModePluginKey: _pluginKey.limitedModePluginKey
29
32
  };
30
33
  }
31
34
  return {
32
35
  enabled: false,
33
- limitedModePluginKey: _main.limitedModePluginKey
36
+ limitedModePluginKey: _pluginKey.limitedModePluginKey
34
37
  };
35
38
  },
36
39
  usePluginHook: function usePluginHook(_ref2) {
@@ -1,35 +1,218 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
- exports.limitedModePluginKey = exports.createPlugin = void 0;
7
+ exports.createPlugin = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _analytics = require("@atlaskit/editor-common/analytics");
7
10
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
8
11
  var _shouldEnableLimitedMode = require("@atlaskit/editor-common/should-enable-limited-mode");
9
- var _state = require("@atlaskit/editor-prosemirror/state");
10
- var limitedModePluginKey = exports.limitedModePluginKey = new _state.PluginKey('limitedModePlugin');
11
- var createPlugin = exports.createPlugin = function createPlugin() {
12
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
13
+ var _expVal = require("@atlaskit/platform-feature-experiments/exp-val");
14
+ var _pluginKey = require("./plugin-key");
15
+ var _latchDetector = require("./utils/latch-detector");
16
+ var _latchPolicy = require("./utils/latch-policy");
17
+ 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; }
18
+ 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; }
19
+ /**
20
+ * Meta shape used to latch limited mode at runtime. Dispatching this is the entire delivery
21
+ * mechanism: the transaction flows through `SharedStateAPI.notifyListeners`, which diffs the plugin's
22
+ * shared state and notifies every consumer. No plugin re-registration and no schema rebuild.
23
+ */
24
+
25
+ /**
26
+ * Hardware hints, reported with a latch so it can be correlated with device class.
27
+ *
28
+ * Telemetry only — the policy no longer takes the hardware into account when deciding, so this
29
+ * exists to answer "which devices are latching" from the data rather than by assumption. Both hints
30
+ * are optional (`deviceMemory` is Chromium-only) and are simply absent where unsupported.
31
+ */
32
+ var getDeviceHints = function getDeviceHints() {
33
+ if (typeof navigator === 'undefined') {
34
+ return {};
35
+ }
36
+ var _ref = navigator,
37
+ hardwareConcurrency = _ref.hardwareConcurrency,
38
+ deviceMemory = _ref.deviceMemory;
39
+ return {
40
+ hardwareConcurrency: hardwareConcurrency,
41
+ deviceMemoryGb: deviceMemory
42
+ };
43
+ };
44
+
45
+ /** Control-arm state: the document decision alone, exactly as before the experiment. */
46
+ var documentOnlyState = function documentOnlyState(doc) {
47
+ var documentSizeBreachesThreshold = (0, _shouldEnableLimitedMode.shouldEnableLimitedModeForDocument)(doc);
48
+ return {
49
+ documentSizeBreachesThreshold: documentSizeBreachesThreshold,
50
+ latchPolicyBreached: false,
51
+ enabled: documentSizeBreachesThreshold
52
+ };
53
+ };
54
+
55
+ /** Treatment-arm state: whatever the policy says, for both of its reasons. */
56
+ var policyState = function policyState(policy) {
57
+ return {
58
+ documentSizeBreachesThreshold: policy.isDocumentBreached(),
59
+ latchPolicyBreached: policy.isBreached(),
60
+ enabled: policy.isBreached()
61
+ };
62
+ };
63
+ var createPlugin = exports.createPlugin = function createPlugin(api, injectedPolicy) {
64
+ var detector;
65
+ var policy;
66
+ if (injectedPolicy) {
67
+ policy = injectedPolicy;
68
+ } else if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_dynamic_limited_mode')) {
69
+ var config = (0, _expVal.expVal)('platform_editor_dynamic_limited_mode', 'policyConfig', {
70
+ warmUpMs: 10000,
71
+ slowInputMs: 100,
72
+ latencyWindowSize: 12,
73
+ latencySlowSamplesRequired: 6,
74
+ freezeTaskMs: 600,
75
+ freezeTasksRequired: 3,
76
+ freezeWindowMs: 30000,
77
+ requiredConfirmations: 2,
78
+ confirmationGapMs: 30000,
79
+ bulkChangeNodeSize: 100,
80
+ bulkChangeSuppressionMs: 2000,
81
+ docSizeThreshold: 750000,
82
+ nodeCountThreshold: 5000
83
+ });
84
+
85
+ // Resolved once per editor rather than per transaction. When off, no policy is built and the
86
+ // document decision runs inline exactly as it did before this experiment.
87
+ policy = new _latchPolicy.LatchPolicy({
88
+ now: function now() {
89
+ return performance.now();
90
+ },
91
+ config: config
92
+ });
93
+ }
12
94
  return new _safePlugin.SafePlugin({
13
- key: limitedModePluginKey,
14
- view: function view(_view) {
15
- return {};
95
+ key: _pluginKey.limitedModePluginKey,
96
+ props: {
97
+ handleTextInput: function handleTextInput() {
98
+ var _detector;
99
+ (_detector = detector) === null || _detector === void 0 || _detector.measureInput();
100
+
101
+ // Never handle the input — this is measurement only.
102
+ return false;
103
+ }
104
+ },
105
+ view: function view(editorView) {
106
+ // No policy means the experiment is off: no observers, no per-keystroke measurement.
107
+ if (!policy) {
108
+ return {};
109
+ }
110
+ var startedAt = performance.now();
111
+ detector = (0, _latchDetector.createLatchDetector)({
112
+ policy: policy,
113
+ onLatchCriteriaMet: function onLatchCriteriaMet(details) {
114
+ var _api$analytics;
115
+ // Treatment arm only — the detector does not exist in control. See LimitedModeLatchedAEP.
116
+ //
117
+ // `details` is the policy's own snapshot of what it latched on, taken before it cleared
118
+ // its evidence buffers, so it reports the closing window rather than an empty one.
119
+ api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
120
+ action: _analytics.ACTION.LIMITED_MODE_LATCHED,
121
+ actionSubject: _analytics.ACTION_SUBJECT.EDITOR,
122
+ eventType: _analytics.EVENT_TYPE.OPERATIONAL,
123
+ attributes: _objectSpread(_objectSpread({
124
+ latched: true,
125
+ reason: details.reason,
126
+ firstWindowReason: details.firstWindowReason,
127
+ requiredConfirmations: details.requiredConfirmations,
128
+ documentAlreadyBreached: details.documentAlreadyBreached,
129
+ msFromFirstWindow: details.msFromFirstWindow,
130
+ latencyMedianMs: details.latencyMedianMs,
131
+ totalInputSamples: details.totalInputSamples,
132
+ totalSlowInputs: details.totalSlowInputs,
133
+ totalFreezes: details.totalFreezes,
134
+ nodeSize: editorView.state.doc.nodeSize
135
+ }, getDeviceHints()), {}, {
136
+ // Measured from the plugin view starting, which is a little later than the policy's
137
+ // own `timeToLatchMs` (it starts at plugin construction).
138
+ timeToLatch: performance.now() - startedAt
139
+ })
140
+ });
141
+
142
+ // The policy already holds the latch; this transaction only prompts the plugin to
143
+ // re-read it, which is what notifies every consumer through shared state.
144
+ editorView.dispatch(editorView.state.tr.setMeta(_pluginKey.limitedModePluginKey, {
145
+ latchPolicyBreached: true
146
+ }));
147
+ }
148
+ });
149
+ return {
150
+ destroy: function destroy() {
151
+ var _detector2;
152
+ (_detector2 = detector) === null || _detector2 === void 0 || _detector2.destroy();
153
+ detector = undefined;
154
+ }
155
+ };
16
156
  },
17
157
  state: {
18
158
  init: function init(_config, editorState) {
19
- return {
20
- documentSizeBreachesThreshold: (0, _shouldEnableLimitedMode.shouldEnableLimitedModeForDocument)(editorState.doc)
21
- };
159
+ if (!policy) {
160
+ return documentOnlyState(editorState.doc);
161
+ }
162
+ policy.evaluateDocument(editorState.doc);
163
+ return policyState(policy);
22
164
  },
23
- apply: function apply(tr, currentPluginState, _oldState, _newState) {
24
- // Don't check the document size if we're already in limited mode.
25
- // We ALWAYS want to re-check the document size if we're replacing the document (e.g. live-to-live page navigation).
165
+ apply: function apply(tr, currentPluginState, oldState, _newState) {
166
+ var _tr$getMeta;
167
+ var documentReplaced = Boolean(tr.getMeta('replaceDocument'));
168
+ if (!policy) {
169
+ // Control arm, unchanged: skip the traversal once already breached, but always re-check
170
+ // when the document is replaced (e.g. live-to-live page navigation).
171
+ if (currentPluginState.documentSizeBreachesThreshold && !documentReplaced) {
172
+ return currentPluginState;
173
+ }
174
+ return documentOnlyState(tr.doc);
175
+ }
26
176
 
27
- if (currentPluginState.documentSizeBreachesThreshold && !tr.getMeta('replaceDocument')) {
28
- return currentPluginState;
177
+ // The detector's latch arrives as a transaction so that plugin state stays a function of
178
+ // the transaction stream rather than of when `apply` happens to read the policy. Dev
179
+ // tooling dispatches the same meta to force limited mode by hand.
180
+ if ((_tr$getMeta = tr.getMeta(_pluginKey.limitedModePluginKey)) !== null && _tr$getMeta !== void 0 && _tr$getMeta.latchPolicyBreached) {
181
+ policy.latch();
29
182
  }
30
- return {
31
- documentSizeBreachesThreshold: (0, _shouldEnableLimitedMode.shouldEnableLimitedModeForDocument)(tr.doc)
32
- };
183
+
184
+ // Only on replacement, never on an ordinary edit: the check walks the whole document, so
185
+ // running it per transaction is a full-document scan on every keystroke. The trade-off is
186
+ // that a document editing its way past the thresholds is not noticed until it next loads.
187
+ //
188
+ // Deliberately not skipped when limited mode is already on. Replacement is the one moment
189
+ // the document verdict can go *down* — live-to-live navigation onto a smaller page — so
190
+ // skipping it there is what would strand limited mode on forever. It costs one walk per
191
+ // page navigation, which is nothing next to the navigation itself.
192
+ if (documentReplaced) {
193
+ policy.evaluateDocument(tr.doc);
194
+ }
195
+
196
+ // Report bulk work so the policy can discount it. The policy decides what counts as bulk;
197
+ // this only supplies the facts.
198
+ //
199
+ // Known gap: operations that are expensive but barely change document size — table
200
+ // resize, drag-and-drop moves (delete + insert nets to ~0), type-ahead — are not
201
+ // suppressed. `@atlaskit/insm` already tracks exactly these via `startHeavyTask`, but its
202
+ // public facade does not expose `runningHeavyTasks`, so there is no way to read them from
203
+ // here today. Closing that gap needs an accessor on the insm package.
204
+ if (tr.docChanged) {
205
+ var _detector3;
206
+ (_detector3 = detector) === null || _detector3 === void 0 || _detector3.noteDocumentChange({
207
+ nodeSizeDelta: tr.doc.nodeSize - oldState.doc.nodeSize,
208
+ isDocumentReplaced: documentReplaced
209
+ });
210
+ }
211
+ var next = policyState(policy);
212
+
213
+ // Keep the previous object when nothing changed, so shared-state diffing stays cheap and
214
+ // consumers are not notified for no reason.
215
+ return next.enabled === currentPluginState.enabled && next.documentSizeBreachesThreshold === currentPluginState.documentSizeBreachesThreshold ? currentPluginState : next;
33
216
  }
34
217
  }
35
218
  });
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.limitedModePluginKey = void 0;
7
+ var _state = require("@atlaskit/editor-prosemirror/state");
8
+ var limitedModePluginKey = exports.limitedModePluginKey = new _state.PluginKey('limitedModePlugin');
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createLatchDetector = void 0;
7
+ var _isPerformanceApiAvailable = require("@atlaskit/editor-common/is-performance-api-available");
8
+ 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; } } }; }
9
+ 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; } }
10
+ 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; }
11
+ /**
12
+ * Wires browser performance signals into the supplied {@link LatchPolicy}.
13
+ *
14
+ * Holds no configuration and makes no decisions: it measures, and forwards. Everything tunable lives
15
+ * on the policy, including the clock it reads.
16
+ *
17
+ * The measurement mirrors `editor-plugin-base/src/pm-plugins/frozen-editor.ts`, which already runs an
18
+ * identical `longtask` observer and per-keystroke rAF measurement for every session to feed
19
+ * `ACTION.SLOW_INPUT` / `ACTION.BROWSER_FREEZE`. It is duplicated rather than shared so that this
20
+ * experiment stays self-contained inside the limited-mode plugin — no new cross-plugin dependency,
21
+ * and deleting it is a single-directory revert if the experiment does not ship. The marginal cost is
22
+ * one `performance.now()` and one `requestAnimationFrame` per keystroke.
23
+ */
24
+ var createLatchDetector = exports.createLatchDetector = function createLatchDetector(_ref) {
25
+ var onLatchCriteriaMet = _ref.onLatchCriteriaMet,
26
+ policy = _ref.policy;
27
+ // One clock for the whole subsystem, so measured durations and the policy's windows agree.
28
+ var now = policy.now;
29
+ var observer;
30
+ var destroyed = false;
31
+ var handleEvaluation = function handleEvaluation(evaluation) {
32
+ if (evaluation !== 'latched') {
33
+ return;
34
+ }
35
+
36
+ // One-way latch: the policy will never evaluate again, so stop paying for the observers.
37
+ teardownObservers();
38
+ var details = policy.getLatchDetails();
39
+ if (details) {
40
+ onLatchCriteriaMet(details);
41
+ }
42
+ };
43
+ function teardownObservers() {
44
+ var _observer;
45
+ (_observer = observer) === null || _observer === void 0 || _observer.disconnect();
46
+ observer = undefined;
47
+ }
48
+ if ((0, _isPerformanceApiAvailable.isPerformanceObserverLongTaskAvailable)()) {
49
+ try {
50
+ observer = new PerformanceObserver(function (list) {
51
+ var _iterator = _createForOfIteratorHelper(list.getEntries()),
52
+ _step;
53
+ try {
54
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
55
+ var entry = _step.value;
56
+ if (policy.isLatched()) {
57
+ return;
58
+ }
59
+ handleEvaluation(policy.recordLongTask(entry.duration));
60
+ }
61
+ } catch (err) {
62
+ _iterator.e(err);
63
+ } finally {
64
+ _iterator.f();
65
+ }
66
+ });
67
+ observer.observe({
68
+ entryTypes: ['longtask']
69
+ });
70
+ } catch (_unused) {
71
+ // `longtask` is unsupported in some browsers even when PerformanceObserver exists. The
72
+ // latency criterion alone is still a valid trigger, so carry on without freeze detection.
73
+ observer = undefined;
74
+ }
75
+ }
76
+ return {
77
+ noteDocumentChange: function noteDocumentChange(_ref2) {
78
+ var nodeSizeDelta = _ref2.nodeSizeDelta,
79
+ isDocumentReplaced = _ref2.isDocumentReplaced;
80
+ if (destroyed || policy.isLatched()) {
81
+ return;
82
+ }
83
+ if (isDocumentReplaced || policy.isBulkChange(nodeSizeDelta)) {
84
+ policy.suppress();
85
+ }
86
+ },
87
+ measureInput: function measureInput() {
88
+ if (destroyed || policy.isLatched() || !(0, _isPerformanceApiAvailable.isPerformanceAPIAvailable)()) {
89
+ return;
90
+ }
91
+ var start = now();
92
+
93
+ // Runs after every handleTextInput and all resulting plugin work, but before paint — the
94
+ // same measurement point frozen-editor uses, so the numbers are comparable to existing
95
+ // SLOW_INPUT telemetry.
96
+ requestAnimationFrame(function () {
97
+ if (destroyed || policy.isLatched()) {
98
+ return;
99
+ }
100
+ handleEvaluation(policy.recordInputLatency(now() - start));
101
+ });
102
+ },
103
+ destroy: function destroy() {
104
+ destroyed = true;
105
+ teardownObservers();
106
+ }
107
+ };
108
+ };
@@ -0,0 +1 @@
1
+ "use strict";