@salve-software/react-native-nitro-jsdom 1.1.0 → 2.0.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 (35) hide show
  1. package/android/CMakeLists.txt +4 -0
  2. package/cpp/HybridHtmlSandbox.cpp +7 -3
  3. package/cpp/lexbor/LexborDocument.cpp +62 -0
  4. package/cpp/lexbor/LexborDocument.hpp +14 -0
  5. package/cpp/quickjs/DOMBindings.cpp +9 -1
  6. package/cpp/quickjs/DOMBindingsInternal.cpp +67 -0
  7. package/cpp/quickjs/DOMBindingsInternal.hpp +39 -0
  8. package/cpp/quickjs/QuickJSRuntime.cpp +13 -0
  9. package/cpp/quickjs/QuickJSRuntime.hpp +28 -0
  10. package/cpp/quickjs/bindings/AbortBindings.cpp +14 -0
  11. package/cpp/quickjs/bindings/BlobBindings.cpp +28 -1
  12. package/cpp/quickjs/bindings/BlobBindings.hpp +3 -3
  13. package/cpp/quickjs/bindings/CSSOMBindings.cpp +46 -0
  14. package/cpp/quickjs/bindings/CSSOMBindings.hpp +3 -1
  15. package/cpp/quickjs/bindings/DOMParserBindings.cpp +299 -0
  16. package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
  17. package/cpp/quickjs/bindings/DocumentBindings.cpp +166 -11
  18. package/cpp/quickjs/bindings/ElementBindings.cpp +296 -20
  19. package/cpp/quickjs/bindings/EventBindings.cpp +277 -5
  20. package/cpp/quickjs/bindings/EventBindings.hpp +3 -0
  21. package/cpp/quickjs/bindings/EventTargetBindings.cpp +75 -0
  22. package/cpp/quickjs/bindings/EventTargetBindings.hpp +24 -0
  23. package/cpp/quickjs/bindings/FetchBindings.cpp +43 -8
  24. package/cpp/quickjs/bindings/FormBindings.cpp +223 -0
  25. package/cpp/quickjs/bindings/FormBindings.hpp +9 -5
  26. package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
  27. package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
  28. package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +45 -0
  29. package/cpp/quickjs/bindings/TimerBindings.cpp +79 -0
  30. package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
  31. package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
  32. package/cpp/quickjs/bindings/UrlBindings.cpp +9 -0
  33. package/cpp/quickjs/bindings/WindowBindings.cpp +286 -4
  34. package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
  35. package/package.json +17 -2
@@ -125,6 +125,229 @@ const char* kFormBootstrapScript = R"JS(
125
125
  // Per spec, submit() bypasses the "submit" event and constraint validation.
126
126
  // There is no real navigation in this sandbox, so this is intentionally inert.
127
127
  };
128
+
129
+ // ── Constraint Validation API (ValidityState, checkValidity, ...) ─────────
130
+ // Covers the subset real-world CMS forms actually hit: required, pattern,
131
+ // min/max/minlength/maxlength, type="email"/"url"/"number" and custom
132
+ // validity. `step` is intentionally not modeled (stepMismatch is always
133
+ // false) — it needs the same "default step base" arithmetic per input type
134
+ // that browsers hardcode, which isn't worth it for this sandbox's scope.
135
+ // reportValidity() has no UI to report against here, so it's just an alias
136
+ // for checkValidity() (same outcome jsdom has, for the same reason).
137
+
138
+ var VALIDITY_KEYS = [
139
+ 'valueMissing', 'typeMismatch', 'patternMismatch', 'tooLong', 'tooShort',
140
+ 'rangeUnderflow', 'rangeOverflow', 'stepMismatch', 'badInput', 'customError',
141
+ ];
142
+
143
+ function ValidityState(flags) {
144
+ this._flags = flags;
145
+ }
146
+ VALIDITY_KEYS.forEach(function(key) {
147
+ Object.defineProperty(ValidityState.prototype, key, {
148
+ enumerable: true,
149
+ get: function() { return !!this._flags[key]; },
150
+ });
151
+ });
152
+ Object.defineProperty(ValidityState.prototype, 'valid', {
153
+ enumerable: true,
154
+ get: function() {
155
+ var flags = this._flags;
156
+ return !VALIDITY_KEYS.some(function(key) { return flags[key]; });
157
+ },
158
+ });
159
+ globalThis.ValidityState = ValidityState;
160
+
161
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
162
+ var URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\/\S+$/;
163
+
164
+ function isFieldElement(el) {
165
+ return el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA';
166
+ }
167
+
168
+ function fieldType(el) {
169
+ if (el.tagName === 'TEXTAREA') return 'textarea';
170
+ if (el.tagName === 'SELECT') return 'select';
171
+ return (el.getAttribute('type') || 'text').toLowerCase();
172
+ }
173
+
174
+ function computeValidityFlags(el) {
175
+ var flags = {};
176
+ if (el._customValidity) flags.customError = true;
177
+
178
+ if (!isFieldElement(el) || el.hasAttribute('disabled')) return flags;
179
+
180
+ var type = fieldType(el);
181
+ if (type === 'hidden') return flags;
182
+
183
+ var required = el.hasAttribute('required');
184
+
185
+ if (type === 'checkbox' || type === 'radio') {
186
+ if (required && !el.checked) flags.valueMissing = true;
187
+ return flags;
188
+ }
189
+
190
+ var value = el.value;
191
+
192
+ if (el.tagName === 'SELECT') {
193
+ if (required && !value) flags.valueMissing = true;
194
+ return flags;
195
+ }
196
+
197
+ if (required && value === '') flags.valueMissing = true;
198
+ if (value === '') return flags;
199
+
200
+ if (type === 'email' && !EMAIL_RE.test(value)) flags.typeMismatch = true;
201
+ if (type === 'url' && !URL_RE.test(value)) flags.typeMismatch = true;
202
+
203
+ var pattern = el.getAttribute('pattern');
204
+ if (pattern) {
205
+ try {
206
+ if (!new RegExp('^(?:' + pattern + ')$').test(value)) flags.patternMismatch = true;
207
+ } catch (e) {
208
+ // Invalid pattern attribute: browsers treat this as never matching,
209
+ // but silently ignoring it is the safer default for a headless sandbox.
210
+ }
211
+ }
212
+
213
+ var maxlength = el.getAttribute('maxlength');
214
+ if (maxlength !== null && value.length > parseInt(maxlength, 10)) flags.tooLong = true;
215
+
216
+ var minlength = el.getAttribute('minlength');
217
+ if (minlength !== null && value.length < parseInt(minlength, 10)) flags.tooShort = true;
218
+
219
+ if (type === 'number' || type === 'range') {
220
+ var num = Number(value);
221
+ if (value.trim() === '' || isNaN(num)) {
222
+ flags.badInput = true;
223
+ } else {
224
+ var min = el.getAttribute('min');
225
+ var max = el.getAttribute('max');
226
+ if (min !== null && num < Number(min)) flags.rangeUnderflow = true;
227
+ if (max !== null && num > Number(max)) flags.rangeOverflow = true;
228
+ }
229
+ }
230
+
231
+ return flags;
232
+ }
233
+
234
+ Object.defineProperty(Element.prototype, 'validity', {
235
+ configurable: true,
236
+ get: function() { return new ValidityState(computeValidityFlags(this)); },
237
+ });
238
+
239
+ Object.defineProperty(Element.prototype, 'willValidate', {
240
+ configurable: true,
241
+ get: function() {
242
+ return isFieldElement(this) && !this.hasAttribute('disabled') && fieldType(this) !== 'hidden';
243
+ },
244
+ });
245
+
246
+ var DEFAULT_MESSAGES = {
247
+ valueMissing: 'Please fill out this field.',
248
+ typeMismatch: 'Please enter a valid value.',
249
+ patternMismatch: 'Please match the requested format.',
250
+ tooLong: 'Please shorten this text.',
251
+ tooShort: 'Please lengthen this text.',
252
+ rangeUnderflow: 'Value must be greater than or equal to the minimum.',
253
+ rangeOverflow: 'Value must be less than or equal to the maximum.',
254
+ badInput: 'Please enter a valid value.',
255
+ };
256
+
257
+ Object.defineProperty(Element.prototype, 'validationMessage', {
258
+ configurable: true,
259
+ get: function() {
260
+ if (this._customValidity) return this._customValidity;
261
+ var flags = computeValidityFlags(this);
262
+ for (var i = 0; i < VALIDITY_KEYS.length; i++) {
263
+ if (flags[VALIDITY_KEYS[i]]) return DEFAULT_MESSAGES[VALIDITY_KEYS[i]] || '';
264
+ }
265
+ return '';
266
+ },
267
+ });
268
+
269
+ Element.prototype.setCustomValidity = function(message) {
270
+ this._customValidity = message == null ? '' : String(message);
271
+ };
272
+
273
+ function fieldCheckValidity(el) {
274
+ var valid = new ValidityState(computeValidityFlags(el)).valid;
275
+ if (!valid) {
276
+ var evt = new Event('invalid', { bubbles: false, cancelable: true });
277
+ el.dispatchEvent(evt);
278
+ }
279
+ return valid;
280
+ }
281
+
282
+ function formFields(form) {
283
+ return Array.prototype.filter.call(form.querySelectorAll('input, select, textarea'), function(el) {
284
+ return !el.hasAttribute('disabled');
285
+ });
286
+ }
287
+
288
+ Element.prototype.checkValidity = function() {
289
+ if (this.tagName === 'FORM') {
290
+ var fields = formFields(this);
291
+ var allValid = true;
292
+ for (var i = 0; i < fields.length; i++) {
293
+ if (!fieldCheckValidity(fields[i])) allValid = false;
294
+ }
295
+ return allValid;
296
+ }
297
+ return fieldCheckValidity(this);
298
+ };
299
+
300
+ Element.prototype.reportValidity = function() {
301
+ return this.checkValidity();
302
+ };
303
+
304
+ // ── element.form / form.elements ────────────────────────────────────────
305
+ var FORM_ASSOCIATED_TAGS = ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON', 'FIELDSET', 'OUTPUT'];
306
+
307
+ function formControls(form) {
308
+ return Array.prototype.slice.call(form.querySelectorAll('input, select, textarea, button, fieldset, output'));
309
+ }
310
+
311
+ Object.defineProperty(Element.prototype, 'form', {
312
+ configurable: true,
313
+ get: function() {
314
+ if (FORM_ASSOCIATED_TAGS.indexOf(this.tagName) === -1) return undefined;
315
+ var formId = this.getAttribute('form');
316
+ if (formId) {
317
+ var byId = document.getElementById(formId);
318
+ if (byId && byId.tagName === 'FORM') return byId;
319
+ }
320
+ return this.closest('form');
321
+ },
322
+ });
323
+
324
+ Object.defineProperty(Element.prototype, 'elements', {
325
+ configurable: true,
326
+ get: function() {
327
+ if (this.tagName !== 'FORM') return undefined;
328
+ return formControls(this);
329
+ },
330
+ });
331
+
332
+ var LABELABLE_TAGS = ['BUTTON', 'INPUT', 'METER', 'OUTPUT', 'PROGRESS', 'SELECT', 'TEXTAREA'];
333
+
334
+ Object.defineProperty(Element.prototype, 'labels', {
335
+ configurable: true,
336
+ get: function() {
337
+ if (LABELABLE_TAGS.indexOf(this.tagName) === -1) return null;
338
+ if (this.tagName === 'INPUT' && (this.getAttribute('type') || '').toLowerCase() === 'hidden') return null;
339
+ var result = [];
340
+ if (this.id) {
341
+ var candidates = this.ownerDocument.querySelectorAll('label[for]');
342
+ for (var i = 0; i < candidates.length; i++) {
343
+ if (candidates[i].getAttribute('for') === this.id) result.push(candidates[i]);
344
+ }
345
+ }
346
+ var wrapping = this.closest('label');
347
+ if (wrapping && result.indexOf(wrapping) === -1) result.push(wrapping);
348
+ return result;
349
+ },
350
+ });
128
351
  })();
129
352
  )JS";
130
353
 
@@ -4,11 +4,15 @@
4
4
 
5
5
  namespace margelo::nitro::nitrojsdom {
6
6
 
7
- // Registers globalThis.FormData and Element.prototype.requestSubmit/submit
8
- // (tag-checked for "form" internally, mirroring ElementBindings' value/checked
9
- // pattern). Pure JS on top of the already-exposed querySelectorAll/Event
10
- // primitives — no native code needed. Must run after ElementBindings (for
11
- // globalThis.Element) and EventBindings (for globalThis.Event).
7
+ // Registers globalThis.FormData, Element.prototype.requestSubmit/submit, and
8
+ // the Constraint Validation API (globalThis.ValidityState,
9
+ // Element.prototype.validity/willValidate/validationMessage/checkValidity()/
10
+ // reportValidity()/setCustomValidity()). Tag-checked for "form"/"input"/
11
+ // "select"/"textarea" internally, mirroring ElementBindings' value/checked
12
+ // pattern. Pure JS on top of the already-exposed getAttribute/value/checked/
13
+ // querySelectorAll/Event primitives — no native code needed. Must run after
14
+ // ElementBindings (for globalThis.Element) and EventBindings (for
15
+ // globalThis.Event).
12
16
  struct FormBindings {
13
17
  static void install(JSContext* ctx);
14
18
  };
@@ -0,0 +1,104 @@
1
+ #include "LayoutStubBindings.hpp"
2
+ #include <cstring>
3
+
4
+ namespace margelo::nitro::nitrojsdom {
5
+
6
+ namespace {
7
+
8
+ const char* kLayoutStubBootstrapScript = R"JS(
9
+ (function() {
10
+ function definePositiveInt(proto, name) {
11
+ Object.defineProperty(proto, name, { get: function() { return 0; }, configurable: true });
12
+ }
13
+
14
+ // ── offset*/client*/scroll* dimensions ────────────────────────────────
15
+ ['offsetWidth', 'offsetHeight', 'offsetTop', 'offsetLeft',
16
+ 'clientWidth', 'clientHeight', 'clientTop', 'clientLeft',
17
+ 'scrollWidth', 'scrollHeight'].forEach(function(name) {
18
+ definePositiveInt(Element.prototype, name);
19
+ });
20
+ Object.defineProperty(Element.prototype, 'offsetParent', {
21
+ get: function() { return null; },
22
+ configurable: true,
23
+ });
24
+
25
+ // scrollTop/scrollLeft are real per-element state (jsdom does the same) —
26
+ // nothing reads them back to affect layout, but round-tripping a value a
27
+ // script just set is more useful than hardcoding 0.
28
+ Object.defineProperty(Element.prototype, 'scrollTop', {
29
+ get: function() { return this._scrollTop || 0; },
30
+ set: function(v) { this._scrollTop = Number(v) || 0; },
31
+ configurable: true,
32
+ });
33
+ Object.defineProperty(Element.prototype, 'scrollLeft', {
34
+ get: function() { return this._scrollLeft || 0; },
35
+ set: function(v) { this._scrollLeft = Number(v) || 0; },
36
+ configurable: true,
37
+ });
38
+
39
+ // ── scrollIntoView / scrollTo / scrollBy / scroll ─────────────────────
40
+ Element.prototype.scrollIntoView = function() {};
41
+ Element.prototype.scrollTo = function() {};
42
+ Element.prototype.scrollBy = function() {};
43
+ Element.prototype.scroll = function() {};
44
+
45
+ globalThis.scrollX = 0;
46
+ globalThis.scrollY = 0;
47
+ globalThis.pageXOffset = 0;
48
+ globalThis.pageYOffset = 0;
49
+ globalThis.scrollTo = function() {};
50
+ globalThis.scrollBy = function() {};
51
+ globalThis.scroll = function() {};
52
+
53
+ // ── document.elementFromPoint / elementsFromPoint ─────────────────────
54
+ document.elementFromPoint = function() { return null; };
55
+ document.elementsFromPoint = function() { return []; };
56
+
57
+ // ── ResizeObserver / IntersectionObserver ─────────────────────────────
58
+ function ResizeObserver(callback) {
59
+ if (typeof callback !== 'function') {
60
+ throw new TypeError("Failed to construct 'ResizeObserver': parameter 1 is not a function.");
61
+ }
62
+ this._callback = callback;
63
+ }
64
+ ResizeObserver.prototype.observe = function() {};
65
+ ResizeObserver.prototype.unobserve = function() {};
66
+ ResizeObserver.prototype.disconnect = function() {};
67
+ globalThis.ResizeObserver = ResizeObserver;
68
+
69
+ function normalizeThreshold(threshold) {
70
+ if (threshold === undefined) return [0];
71
+ var arr = Array.isArray(threshold) ? threshold.slice() : [threshold];
72
+ return arr.length ? arr : [0];
73
+ }
74
+
75
+ function IntersectionObserver(callback, options) {
76
+ if (typeof callback !== 'function') {
77
+ throw new TypeError("Failed to construct 'IntersectionObserver': parameter 1 is not a function.");
78
+ }
79
+ options = options || {};
80
+ this._callback = callback;
81
+ this.root = options.root || null;
82
+ this.rootMargin = options.rootMargin || '0px';
83
+ this.thresholds = normalizeThreshold(options.threshold);
84
+ }
85
+ IntersectionObserver.prototype.observe = function() {};
86
+ IntersectionObserver.prototype.unobserve = function() {};
87
+ IntersectionObserver.prototype.disconnect = function() {};
88
+ IntersectionObserver.prototype.takeRecords = function() { return []; };
89
+ globalThis.IntersectionObserver = IntersectionObserver;
90
+ })();
91
+ )JS";
92
+
93
+ } // namespace
94
+
95
+ void LayoutStubBindings::install(JSContext* ctx) {
96
+ JSValue result = JS_Eval(ctx, kLayoutStubBootstrapScript, strlen(kLayoutStubBootstrapScript),
97
+ "<layout-stub-bootstrap>", JS_EVAL_TYPE_GLOBAL);
98
+ if (JS_IsException(result)) {
99
+ JS_FreeValue(ctx, JS_GetException(ctx));
100
+ }
101
+ JS_FreeValue(ctx, result);
102
+ }
103
+
104
+ } // namespace margelo::nitro::nitrojsdom
@@ -0,0 +1,28 @@
1
+ #pragma once
2
+
3
+ // No layout/rendering engine backs this sandbox, so every geometry- and
4
+ // scroll-related API here is an inert stub rather than a real computation —
5
+ // same stance as CSSOMBindings' getComputedStyle and ElementBindings'
6
+ // getBoundingClientRect (both already all-zero without a cascade/layout
7
+ // pass). This file covers the rest of that surface: scrollIntoView/scrollTo/
8
+ // scrollBy/scroll (no-ops), offset*/client*/scroll* dimensions (0, except
9
+ // scrollTop/scrollLeft which are real per-element state — jsdom does the
10
+ // same), document.elementFromPoint/elementsFromPoint (null/[]), and
11
+ // ResizeObserver/IntersectionObserver (constructible, observe()/unobserve()/
12
+ // disconnect() no-op, callback never fires). jsdom itself doesn't expose
13
+ // Resize/IntersectionObserver at all; this project adds them as defensive
14
+ // "don't throw ReferenceError" stubs instead, matching the precedent set by
15
+ // window.history/window.getSelection().
16
+ //
17
+ // Must run after ElementBindings (globalThis.Element) and DocumentBindings
18
+ // (globalThis.document).
19
+
20
+ #include "quickjs.h"
21
+
22
+ namespace margelo::nitro::nitrojsdom {
23
+
24
+ struct LayoutStubBindings {
25
+ static void install(JSContext* ctx);
26
+ };
27
+
28
+ } // namespace margelo::nitro::nitrojsdom
@@ -138,6 +138,51 @@ const char* kIteratorBootstrapScript = R"JS(
138
138
  }
139
139
  };
140
140
  };
141
+
142
+ proto.forEach = function(callback, thisArg) {
143
+ for (var i = 0; i < this.length; i++) callback.call(thisArg, this[i], i, this);
144
+ };
145
+
146
+ function arrayLikeIterator(self, mapEntry) {
147
+ var i = 0;
148
+ return {
149
+ next: function() {
150
+ if (i >= self.length) return { value: undefined, done: true };
151
+ var value = mapEntry(i, self[i]);
152
+ i++;
153
+ return { value: value, done: false };
154
+ },
155
+ [Symbol.iterator]: function() { return this; },
156
+ };
157
+ }
158
+
159
+ proto.entries = function() {
160
+ return arrayLikeIterator(this, function(i, v) { return [i, v]; });
161
+ };
162
+ proto.keys = function() {
163
+ return arrayLikeIterator(this, function(i) { return i; });
164
+ };
165
+ proto.values = function() {
166
+ return arrayLikeIterator(this, function(i, v) { return v; });
167
+ };
168
+
169
+ proto.item = function(index) {
170
+ return index >= 0 && index < this.length ? this[index] : null;
171
+ };
172
+
173
+ // namedItem() is spec'd on HTMLCollection only (matches by id, then by
174
+ // name), not NodeList — but both share this one LiveCollection class (see
175
+ // the forEach() comment above), so this stays a no-op-safe lookup for
176
+ // NodeLists of non-Elements (text/comment nodes have no getAttribute).
177
+ proto.namedItem = function(name) {
178
+ for (var i = 0; i < this.length; i++) {
179
+ var item = this[i];
180
+ if (typeof item.getAttribute !== 'function') continue;
181
+ if (item.getAttribute('id') === name || item.getAttribute('name') === name) return item;
182
+ }
183
+ return null;
184
+ };
185
+
141
186
  delete globalThis.__LiveCollectionProto;
142
187
  })();
143
188
  )JS";
@@ -107,6 +107,85 @@ const char* kTimerBootstrapScript = R"JS(
107
107
  globalThis.queueMicrotask = function(callback) {
108
108
  Promise.resolve().then(function() { callback(); });
109
109
  };
110
+
111
+ globalThis.requestIdleCallback = function(callback, options) {
112
+ var timeout = options && options.timeout;
113
+ return setTimeout(function() {
114
+ callback({
115
+ didTimeout: false,
116
+ timeRemaining: function() { return 50; },
117
+ });
118
+ }, typeof timeout === 'number' ? Math.min(timeout, 1) : 1);
119
+ };
120
+ globalThis.cancelIdleCallback = function(id) {
121
+ clearTimeout(id);
122
+ };
123
+
124
+ var __perfEntries = [];
125
+ function __perfFindMark(name) {
126
+ for (var i = __perfEntries.length - 1; i >= 0; i--) {
127
+ if (__perfEntries[i].name === name && __perfEntries[i].entryType === 'mark') return __perfEntries[i];
128
+ }
129
+ return null;
130
+ }
131
+ function __perfRequireMark(name) {
132
+ var entry = __perfFindMark(name);
133
+ if (!entry) {
134
+ throw new DOMException(
135
+ "Failed to execute 'measure' on 'Performance': The mark '" + name + "' does not exist.", 'SyntaxError');
136
+ }
137
+ return entry.startTime;
138
+ }
139
+ performance.mark = function(name, options) {
140
+ var entry = {
141
+ name: String(name),
142
+ entryType: 'mark',
143
+ startTime: (options && options.startTime !== undefined) ? options.startTime : performance.now(),
144
+ duration: 0,
145
+ detail: (options && options.detail !== undefined) ? options.detail : null,
146
+ };
147
+ __perfEntries.push(entry);
148
+ return entry;
149
+ };
150
+ performance.measure = function(name, startOrOptions, endMark) {
151
+ var startTime = 0;
152
+ var endTime = performance.now();
153
+ var detail = null;
154
+ if (typeof startOrOptions === 'string') {
155
+ startTime = __perfRequireMark(startOrOptions);
156
+ if (endMark !== undefined) endTime = __perfRequireMark(endMark);
157
+ } else if (startOrOptions && typeof startOrOptions === 'object') {
158
+ if (startOrOptions.detail !== undefined) detail = startOrOptions.detail;
159
+ if (startOrOptions.start !== undefined) {
160
+ startTime = typeof startOrOptions.start === 'string' ? __perfRequireMark(startOrOptions.start) : startOrOptions.start;
161
+ }
162
+ if (startOrOptions.end !== undefined) {
163
+ endTime = typeof startOrOptions.end === 'string' ? __perfRequireMark(startOrOptions.end) : startOrOptions.end;
164
+ } else if (startOrOptions.duration !== undefined) {
165
+ endTime = startTime + startOrOptions.duration;
166
+ }
167
+ }
168
+ var entry = { name: String(name), entryType: 'measure', startTime: startTime, duration: endTime - startTime, detail: detail };
169
+ __perfEntries.push(entry);
170
+ return entry;
171
+ };
172
+ performance.getEntries = function() { return __perfEntries.slice(); };
173
+ performance.getEntriesByType = function(type) {
174
+ return __perfEntries.filter(function(e) { return e.entryType === type; });
175
+ };
176
+ performance.getEntriesByName = function(name, type) {
177
+ return __perfEntries.filter(function(e) { return e.name === name && (!type || e.entryType === type); });
178
+ };
179
+ performance.clearMarks = function(name) {
180
+ __perfEntries = __perfEntries.filter(function(e) {
181
+ return e.entryType !== 'mark' || (name !== undefined && e.name !== name);
182
+ });
183
+ };
184
+ performance.clearMeasures = function(name) {
185
+ __perfEntries = __perfEntries.filter(function(e) {
186
+ return e.entryType !== 'measure' || (name !== undefined && e.name !== name);
187
+ });
188
+ };
110
189
  })();
111
190
  )JS";
112
191