@qbix/q 1.0.5

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +678 -0
  3. package/dist/Metrics.js +2873 -0
  4. package/dist/Metrics.min.js +95 -0
  5. package/dist/Q.js +15677 -0
  6. package/dist/Q.min.js +385 -0
  7. package/dist/Q.minimal.js +11594 -0
  8. package/dist/Q.minimal.min.js +286 -0
  9. package/dist/handlebars-v4.0.10.min.js +29 -0
  10. package/dist/handlebars.minimal.min.js +1 -0
  11. package/dist/img/hints/rotate-left.gif +0 -0
  12. package/dist/img/hints/swipe-down.gif +0 -0
  13. package/dist/img/hints/swipe-up.gif +0 -0
  14. package/dist/img/hints/tap.gif +0 -0
  15. package/dist/img/throbbers/loading.gif +0 -0
  16. package/dist/jquery.minimal.min.js +18 -0
  17. package/dist/methods/Q/Audio/load.js +29 -0
  18. package/dist/methods/Q/Audio/loadVoices.js +36 -0
  19. package/dist/methods/Q/Audio/play.js +47 -0
  20. package/dist/methods/Q/Audio/speak.js +149 -0
  21. package/dist/methods/Q/Crypto/delegate.js +186 -0
  22. package/dist/methods/Q/Crypto/internalKeypair.js +170 -0
  23. package/dist/methods/Q/Crypto/sign.js +200 -0
  24. package/dist/methods/Q/Crypto/verify.js +212 -0
  25. package/dist/methods/Q/Crypto/verifyDelegated.js +214 -0
  26. package/dist/methods/Q/Data/Bloom/_internal.js +163 -0
  27. package/dist/methods/Q/Data/Bloom/create.js +23 -0
  28. package/dist/methods/Q/Data/Bloom/fromBase64.js +21 -0
  29. package/dist/methods/Q/Data/Bloom/fromBytes.js +15 -0
  30. package/dist/methods/Q/Data/Bloom/fromElements.js +33 -0
  31. package/dist/methods/Q/Data/Merkle/_internal.js +68 -0
  32. package/dist/methods/Q/Data/Merkle/build.js +29 -0
  33. package/dist/methods/Q/Data/Merkle/proof.js +50 -0
  34. package/dist/methods/Q/Data/Merkle/verify.js +32 -0
  35. package/dist/methods/Q/Data/Prolly/_internal.js +190 -0
  36. package/dist/methods/Q/Data/Prolly/build.js +28 -0
  37. package/dist/methods/Q/Data/Prolly/delete.js +28 -0
  38. package/dist/methods/Q/Data/Prolly/diff.js +70 -0
  39. package/dist/methods/Q/Data/Prolly/get.js +38 -0
  40. package/dist/methods/Q/Data/Prolly/set.js +32 -0
  41. package/dist/methods/Q/Data/compress.js +45 -0
  42. package/dist/methods/Q/Data/decompress.js +35 -0
  43. package/dist/methods/Q/Data/decrypt.js +55 -0
  44. package/dist/methods/Q/Data/derive.js +76 -0
  45. package/dist/methods/Q/Data/digest.js +29 -0
  46. package/dist/methods/Q/Data/encrypt.js +61 -0
  47. package/dist/methods/Q/Data/generateKey.js +40 -0
  48. package/dist/methods/Q/Data/hkdf.js +44 -0
  49. package/dist/methods/Q/Data/importKey.js +34 -0
  50. package/dist/methods/Q/Data/sign.js +42 -0
  51. package/dist/methods/Q/Data/verify.js +45 -0
  52. package/dist/methods/Q/Onboarding/handle.js +50 -0
  53. package/dist/methods/Q/Onboarding/start.js +165 -0
  54. package/dist/methods/Q/Onboarding/stop.js +21 -0
  55. package/dist/methods/Q/Sandbox/run.js +392 -0
  56. package/dist/methods/Q/Tool/define/component.js +218 -0
  57. package/dist/methods/Q/globalMemoryWalk.js +82 -0
  58. package/dist/methods/Q/leaves.js +34 -0
  59. package/dist/methods/Q/registerWebComponent.js +0 -0
  60. package/dist/methods/Q/sanitize.js +142 -0
  61. package/dist/test.html +6 -0
  62. package/dist/tools/Q/lazyload.js +433 -0
  63. package/package.json +26 -0
@@ -0,0 +1,392 @@
1
+ Q.exports(function (Q) {
2
+ /**
3
+ * Q plugin's front-end code
4
+ * @module Q
5
+ * @class Q.Sandbox
6
+ */
7
+
8
+ /**
9
+ * Runs code safely inside a sandboxed Web Worker.
10
+ * If `options.name` is provided, a persistent worker is reused.
11
+ *
12
+ * @static
13
+ * @method run
14
+ * @param {String} code JavaScript source to execute
15
+ * @param {Object} [context] Variables accessible inside the sandbox
16
+ * @param {Object} [methods] Async RPC methods exposed as stubs
17
+ * @param {Object} [options] Additional sandbox configuration
18
+ * @param {String} [options.name] Reuse a persistent sandbox worker under this name
19
+ * @param {Number} [options.timeout=2000] Timeout in milliseconds before aborting execution
20
+ * @param {Boolean} [options.db=false] Whether to expose indexedDB inside sandbox
21
+ * @param {Boolean|Object} [options.deterministic=false] Set to true or object to make the code run deterministically
22
+ * @param {Number} [options.deterministic.seed=1] Seed for deterministic RNG
23
+ * @return {Q.Promise} Resolves with result or rejects on error
24
+ */
25
+ return function Q_Sandbox_run(code, context, methods, options) {
26
+ context = context || {};
27
+ methods = methods || {};
28
+ options = options || {};
29
+
30
+ if (!Q.Sandbox._runners) Q.Sandbox._runners = {};
31
+
32
+ function SandboxRunner(defaults) {
33
+ this.defaults = {
34
+ timeout: (defaults && defaults.timeout) || 2000,
35
+ db: !!(defaults && defaults.db)
36
+ };
37
+ this.worker = null;
38
+ this.url = null;
39
+ }
40
+
41
+ SandboxRunner.prototype.createWorker = function () {
42
+ const allowDB = !!this.defaults.db;
43
+ const indexedDBExpr = allowDB ? 'indexedDB' : 'undefined';
44
+
45
+ const script = `
46
+ // --- Hard-disable network & import capabilities ---
47
+ self.fetch = undefined;
48
+ self.XMLHttpRequest = undefined;
49
+ self.WebSocket = undefined;
50
+ self.EventSource = undefined;
51
+ self.importScripts = undefined;
52
+
53
+ // --- Safe stubs instead of deleting env ---
54
+ try {
55
+ Object.defineProperty(self, "navigator", {
56
+ value: { userAgent: "sandbox", language: "en-US" },
57
+ configurable: false
58
+ });
59
+ } catch {}
60
+
61
+ self.location = undefined;
62
+ self.caches = undefined;
63
+
64
+ // Optional DB
65
+ if (!${allowDB}) {
66
+ self.indexedDB = undefined;
67
+ }
68
+
69
+ // --- Block prototype mutation entry points (Safari-safe) ---
70
+ try {
71
+ Object.defineProperty(Object.prototype, "__defineSetter__", { value: undefined });
72
+ Object.defineProperty(Object.prototype, "__defineGetter__", { value: undefined });
73
+ Object.defineProperty(Object.prototype, "__lookupGetter__", { value: undefined });
74
+ Object.defineProperty(Object.prototype, "__lookupSetter__", { value: undefined });
75
+ } catch {}
76
+
77
+ let rpcCounter = 0;
78
+ const pending = {};
79
+
80
+ function call(method, args) {
81
+ return new Promise((resolve, reject) => {
82
+ const id = ++rpcCounter;
83
+ pending[id] = { resolve, reject };
84
+ self.postMessage({ type: "rpc", id, method, args });
85
+ });
86
+ }
87
+
88
+ self.onmessage = async function (e) {
89
+ const msg = e.data;
90
+
91
+ if (msg && msg.type === "rpcResult") {
92
+ const p = pending[msg.id];
93
+ if (!p) return;
94
+ delete pending[msg.id];
95
+ msg.ok ? p.resolve(msg.result) : p.reject(msg.error);
96
+ return;
97
+ }
98
+
99
+ try {
100
+ const { code, context, methodNames, deterministic } = msg;
101
+
102
+ let __seed = 1;
103
+ if (deterministic && typeof deterministic === "object" && deterministic.seed !== undefined) {
104
+ __seed = deterministic.seed >>> 0;
105
+ }
106
+
107
+ const __timers = [];
108
+ let __timerGuard = 1000;
109
+
110
+ // --- Deterministic runtime injected only if requested ---
111
+ if (deterministic) {
112
+ let __randSeed = (__seed >>> 0) || 1;
113
+
114
+ function __rand(){
115
+ __randSeed = (__randSeed * 1664525 + 1013904223) >>> 0;
116
+ return __randSeed / 4294967296;
117
+ }
118
+
119
+ Object.defineProperty(self, "__deterministicSeed", {
120
+ value: __randSeed,
121
+ writable: false,
122
+ configurable: false
123
+ });
124
+
125
+ Math.random = __rand;
126
+ Object.defineProperty(Math, "random", {
127
+ value: __rand,
128
+ writable: false,
129
+ configurable: false
130
+ });
131
+
132
+ const __start = 0;
133
+
134
+ Date.now = function(){ return __start };
135
+
136
+ if (typeof performance !== "undefined") {
137
+ performance.now = function(){ return 0 };
138
+ }
139
+
140
+ const __RealDate = Date;
141
+
142
+ function DeterministicDate(...args) {
143
+ if (!(this instanceof DeterministicDate)) {
144
+ return new __RealDate(__start).toString();
145
+ }
146
+ if (args.length === 0) {
147
+ return new __RealDate(__start);
148
+ }
149
+ return new __RealDate(...args);
150
+ }
151
+
152
+ DeterministicDate.UTC = __RealDate.UTC;
153
+ DeterministicDate.parse = __RealDate.parse;
154
+ DeterministicDate.prototype = __RealDate.prototype;
155
+ DeterministicDate.prototype.constructor = DeterministicDate;
156
+
157
+ Date = DeterministicDate;
158
+
159
+ setTimeout = function(fn){ __timers.push(fn); return __timers.length };
160
+ setInterval = function(fn){ __timers.push(fn); return __timers.length };
161
+
162
+ clearTimeout = function(){};
163
+ clearInterval = function(){};
164
+
165
+ if (typeof crypto !== "undefined") {
166
+ crypto.getRandomValues = function(arr){
167
+ for (let i=0;i<arr.length;i++){
168
+ arr[i] = Math.floor(__rand()*256);
169
+ }
170
+ return arr;
171
+ };
172
+
173
+ crypto.randomUUID = function(){
174
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){
175
+ const r = Math.floor(__rand()*16);
176
+ return (c==='x'?r:(r&0x3|0x8)).toString(16);
177
+ });
178
+ };
179
+ }
180
+
181
+ self.fetch = undefined;
182
+ self.XMLHttpRequest = undefined;
183
+ self.WebSocket = undefined;
184
+ self.EventSource = undefined;
185
+ self.navigator = undefined;
186
+
187
+ try {
188
+ Object.freeze(Math);
189
+ Object.freeze(Date);
190
+ } catch {}
191
+ }
192
+
193
+ const methods = {};
194
+ for (const name of methodNames) {
195
+ methods[name] = (...args) => call(name, args);
196
+ }
197
+
198
+ const keys = Object.keys(context || {}).concat("methods");
199
+ const values = Object.values(context || {}).concat(methods);
200
+
201
+ const AsyncFunction =
202
+ Object.getPrototypeOf(async function () {}).constructor;
203
+
204
+ const fn = new AsyncFunction(
205
+ ...keys,
206
+ '"use strict";\\n' +
207
+ 'const fetch = undefined;\\n' +
208
+ 'const XMLHttpRequest = undefined;\\n' +
209
+ 'const WebSocket = undefined;\\n' +
210
+ 'const EventSource = undefined;\\n' +
211
+ 'const importScripts = undefined;\\n' +
212
+ 'const indexedDB = ${allowDB ? 'self.indexedDB' : 'undefined'};\\n' +
213
+ 'const IDBFactory = undefined;\\n' +
214
+ 'const IDBDatabase = undefined;\\n' +
215
+ 'const IDBObjectStore = undefined;\\n' +
216
+ 'const __user = async function(){\\n' +
217
+ code + '\\n' +
218
+ '};\\n' +
219
+ 'return __user();'
220
+ );
221
+
222
+ const result = await fn(...values);
223
+
224
+ // run deterministic timers
225
+ while (__timers.length && __timerGuard--) {
226
+ try { __timers.shift()(); } catch {}
227
+ }
228
+ __timers.length = 0;
229
+
230
+ self.postMessage({ type: "done", ok: true, result });
231
+
232
+ } catch (err) {
233
+ self.postMessage({
234
+ type: "done",
235
+ ok: false,
236
+ error: String(err && err.message || err)
237
+ });
238
+ }
239
+ };
240
+ `;
241
+
242
+ const blob = new Blob([script], { type: "application/javascript" });
243
+ this.url = URL.createObjectURL(blob);
244
+ this.worker = new Worker(this.url);
245
+ return this.worker;
246
+ };
247
+
248
+ SandboxRunner.prototype.run = function (code, ctx, methods, opts) {
249
+ opts = opts || {};
250
+ const worker = this.worker || this.createWorker();
251
+ const timeoutMs = opts.timeout || this.defaults.timeout;
252
+
253
+ let safeCtx;
254
+ try {
255
+ safeCtx = JSON.parse(JSON.stringify(ctx));
256
+ } catch {
257
+ safeCtx = {};
258
+ }
259
+
260
+ const methodNames = Object.keys(methods);
261
+
262
+ return new Q.Promise(function (resolve, reject) {
263
+ let timer;
264
+
265
+ const runner = this;
266
+ const cleanup = () => {
267
+ clearTimeout(timer);
268
+ if (!opts.name) {
269
+ try {
270
+ URL.revokeObjectURL(runner.url);
271
+ worker.terminate();
272
+ } catch {}
273
+ }
274
+ };
275
+
276
+ const rpcLog = [];
277
+ let finished = false;
278
+
279
+ worker.onmessage = function (e) {
280
+ const msg = e.data;
281
+
282
+ if (msg && msg.type === "rpc") {
283
+ const fn = methods[msg.method];
284
+ if (!fn) {
285
+ worker.postMessage({
286
+ type: "rpcResult",
287
+ id: msg.id,
288
+ ok: false,
289
+ error: "Unknown method: " + msg.method
290
+ });
291
+ return;
292
+ }
293
+
294
+ Promise.resolve()
295
+ .then(() => fn(...msg.args))
296
+ .then(result => {
297
+ rpcLog.push({
298
+ method: msg.method,
299
+ args: msg.args,
300
+ result
301
+ });
302
+ worker.postMessage({
303
+ type: "rpcResult",
304
+ id: msg.id,
305
+ ok: true,
306
+ result
307
+ });
308
+ })
309
+ .catch(err => {
310
+ rpcLog.push({
311
+ method: msg.method,
312
+ args: msg.args,
313
+ error: String(err && err.message || err)
314
+ });
315
+
316
+ worker.postMessage({
317
+ type: "rpcResult",
318
+ id: msg.id,
319
+ ok: false,
320
+ error: String(err && err.message || err)
321
+ });
322
+ });
323
+ return;
324
+ }
325
+
326
+ if (msg && msg.type === "done") {
327
+ if (finished) return;
328
+ finished = true;
329
+
330
+ const execution = {
331
+ code,
332
+ context: safeCtx,
333
+ seed: (opts.deterministic && typeof opts.deterministic === "object")
334
+ ? opts.deterministic.seed
335
+ : (opts.deterministic ? 1 : undefined),
336
+ rpc: rpcLog,
337
+ ok: !!msg.ok,
338
+ result: msg.ok ? msg.result : undefined,
339
+ error: msg.ok ? undefined : msg.error
340
+ };
341
+
342
+ Q.Data.digest("SHA-256", JSON.stringify(execution))
343
+ .then(function (bytes) {
344
+ var hash = Q.Data.toHex(bytes);
345
+ cleanup();
346
+ if (msg.ok) {
347
+ resolve({
348
+ result: msg.result,
349
+ hash
350
+ });
351
+ } else {
352
+ const err = new Error(msg.error || "Sandbox error");
353
+ err.hash = hash;
354
+ reject(err);
355
+ }
356
+ });
357
+ }
358
+ };
359
+
360
+ worker.onerror = function (err) {
361
+ cleanup();
362
+ reject(err.message || String(err));
363
+ };
364
+
365
+ timer = setTimeout(function () {
366
+ cleanup();
367
+ reject(new Error("Worker timeout / infinite loop"));
368
+ }, timeoutMs);
369
+
370
+ worker.postMessage({
371
+ code,
372
+ context: safeCtx,
373
+ methodNames,
374
+ deterministic: opts.deterministic || false
375
+ });
376
+ }.bind(this));
377
+ };
378
+
379
+ let runner;
380
+ if (options.name) {
381
+ runner = Q.Sandbox._runners[options.name];
382
+ if (!runner) {
383
+ runner = new SandboxRunner(options);
384
+ Q.Sandbox._runners[options.name] = runner;
385
+ }
386
+ } else {
387
+ runner = new SandboxRunner(options);
388
+ }
389
+
390
+ return runner.run(code, context, methods, options);
391
+ };
392
+ });
@@ -0,0 +1,218 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * Registers a Custom Element (Web Component) for a Q tool.
5
+ * Only active when Q.Tool.define.components === true.
6
+ *
7
+ * Equivalent to writing:
8
+ * <div class="Q_tool Streams_chat_tool" data-streams-chat='{"publisherId":"NYU"}'></div>
9
+ * and having Q.activate() find it. The web component syntax is purely a
10
+ * translation shim — connectedCallback dresses the element and calls Q.activate(this).
11
+ *
12
+ * Attribute → option mapping:
13
+ * - foo-bar="2" → nested: options.foo.bar = 2
14
+ * - fooBar="2" → flat if schema declares it, else options.fooBar = 2
15
+ * - baz → true (bare attribute, no value)
16
+ *
17
+ * Schema is declared as the last element of stateKeys if it's a plain object:
18
+ * stateKeys = ["editable", { count: Q.Types.Integer, visible: Q.Types.Boolean }]
19
+ * Leaf nodes have a .from(string) method. Branch nodes are plain objects without .from().
20
+ * Without a schema, automagic inference handles bool/int/float/JSON/string.
21
+ *
22
+ * @method Q.Tool.define.component
23
+ * @param {String} name Tool name e.g. "Streams/chat"
24
+ * @param {Function} ctor Tool constructor (already registered)
25
+ */
26
+ return function Q_Tool_define_component(name, ctor) {
27
+ if (typeof customElements === 'undefined') {
28
+ return;
29
+ }
30
+
31
+ // "Streams/chat" -> "streams-chat"
32
+ var tagName = name.toLowerCase().replace(/[/_]/g, '-');
33
+
34
+ if (customElements.get(tagName)) {
35
+ return;
36
+ }
37
+
38
+ // Extract schema from last element of stateKeys if it's a plain object
39
+ var stateKeys = ctor.stateKeys;
40
+ var schema = null;
41
+ if (Array.isArray(stateKeys) && stateKeys.length) {
42
+ var last = stateKeys[stateKeys.length - 1];
43
+ if (Q.isPlainObject(last)) {
44
+ schema = last;
45
+ }
46
+ }
47
+
48
+ // attrTypeMap: full-hyphenated-path -> Q.Types.X descriptor
49
+ // attrNameMap: full-hyphenated-path -> key path array into options
50
+ var attrTypeMap = {};
51
+ var attrNameMap = {};
52
+ if (schema) {
53
+ _flattenSchema(schema, [], attrTypeMap, attrNameMap);
54
+ }
55
+
56
+ /**
57
+ * Recursively walk schema. Leaf nodes have .from(), branch nodes don't.
58
+ * Keys into the maps are the FULL hyphenated path e.g. "foo-bar-baz"
59
+ * to avoid collisions between keys at different nesting levels.
60
+ */
61
+ function _flattenSchema(node, path, typeMap, nameMap) {
62
+ for (var k in node) {
63
+ if (!node.hasOwnProperty(k)) continue;
64
+ var val = node[k];
65
+ var newPath = path.concat([k]);
66
+ var hyphenPath = newPath.map(_camelToHyphen).join('-');
67
+ if (val && typeof val.from === 'function') {
68
+ typeMap[hyphenPath] = val;
69
+ nameMap[hyphenPath] = newPath;
70
+ } else if (Q.isPlainObject(val)) {
71
+ _flattenSchema(val, newPath, typeMap, nameMap);
72
+ }
73
+ }
74
+ }
75
+
76
+ function _camelToHyphen(str) {
77
+ return str.replace(/([A-Z])/g, function(c) {
78
+ return '-' + c.toLowerCase();
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Resolve one attribute name+value to { path, value }.
84
+ * Resolution order:
85
+ * 1. Full hyphenated path match in schema (e.g. "foo-bar" -> ["foo","bar"])
86
+ * 2. Hyphen-split into nested path
87
+ * 3. Single segment: flat key preserving original casing
88
+ * Bare attribute (no value) -> true, or type.from('') if schema declares it.
89
+ */
90
+ function _resolveAttr(attrName, attrValue) {
91
+ var lower = attrName.toLowerCase();
92
+ var type = attrTypeMap[lower] || null;
93
+ var path = attrNameMap[lower] || null;
94
+
95
+ if (!path) {
96
+ var parts = lower.split('-');
97
+ path = parts.length > 1 ? parts : [attrName];
98
+ }
99
+
100
+ var converted;
101
+ if (attrValue === null) {
102
+ // bare attribute
103
+ converted = type ? type.from('') : true;
104
+ } else if (type) {
105
+ converted = type.from(attrValue);
106
+ } else {
107
+ converted = _infer(attrValue);
108
+ }
109
+
110
+ return { path: path, value: converted };
111
+ }
112
+
113
+ /**
114
+ * Automagic inference when no schema type is declared.
115
+ * Order: "true"/"false" -> Boolean, integer, float, JSON, String.
116
+ */
117
+ function _infer(str) {
118
+ if (str === 'true') return true;
119
+ if (str === 'false') return false;
120
+ if (str === 'null') return null;
121
+ if (str === '') return true;
122
+ if (/^-?\d+$/.test(str)) return parseInt(str, 10);
123
+ if (/^-?\d*\.\d+$/.test(str)) return parseFloat(str);
124
+ if (str[0] === '{' || str[0] === '[') {
125
+ try { return JSON.parse(str); } catch(e) {}
126
+ }
127
+ return str;
128
+ }
129
+
130
+ /**
131
+ * Build options object from all non-standard attributes on the element.
132
+ * Skips: id, class, style, slot, and all data-* except the tool's own.
133
+ * The tool's own data-* attribute (e.g. data-streams-chat) is parsed as
134
+ * a raw JSON blob and merged as the base, with individual attrs on top.
135
+ */
136
+ function _attrsToOptions(element) {
137
+ var options = {};
138
+ var skip = { id: 1, 'class': 1, style: 1, slot: 1 };
139
+ var ownDataAttr = 'data-' + tagName;
140
+ var attrs = element.attributes;
141
+
142
+ for (var i = 0; i < attrs.length; i++) {
143
+ var attr = attrs[i];
144
+ var aName = attr.name;
145
+
146
+ if (skip[aName]) continue;
147
+
148
+ // Legacy JSON blob on the tool's own data- attr: merge as base
149
+ if (aName === ownDataAttr) {
150
+ try {
151
+ var blob = JSON.parse(attr.value);
152
+ if (Q.isPlainObject(blob)) {
153
+ Q.extend(options, blob);
154
+ }
155
+ } catch(e) {}
156
+ continue;
157
+ }
158
+
159
+ // Skip other data-* passthrough attributes
160
+ if (aName.slice(0, 5) === 'data-') continue;
161
+
162
+ var resolved = _resolveAttr(aName, attr.value === '' ? null : attr.value);
163
+ Q.setObject(resolved.path, resolved.value, options);
164
+ }
165
+
166
+ return options;
167
+ }
168
+
169
+ // observedAttributes: only schema-declared attrs trigger attributeChangedCallback.
170
+ // Undeclared attrs are still read at connectedCallback time via _attrsToOptions.
171
+ var observedAttrNames = Object.keys(attrTypeMap);
172
+
173
+ var ntt = name.split('/').join('_');
174
+
175
+ class ToolElement extends HTMLElement {
176
+
177
+ connectedCallback() {
178
+ this.classList.add('Q_tool', ntt + '_tool');
179
+ var options = _attrsToOptions(this);
180
+ if (!Q.isEmpty(options)) {
181
+ this.setAttribute(
182
+ 'data-' + tagName,
183
+ JSON.stringify(options)
184
+ );
185
+ }
186
+ Q.activate(this);
187
+ }
188
+
189
+ disconnectedCallback() {
190
+ if (this.getAttribute('data-Q-retain') !== null) return;
191
+ Q.Tool.remove(this);
192
+ }
193
+
194
+ attributeChangedCallback(attrName, oldVal, newVal) {
195
+ if (oldVal === newVal) return;
196
+ // Fires only for schema-declared attributes after initial connection.
197
+ // Before connection, the change will be picked up by connectedCallback.
198
+ var tool = Q.Tool.from(this, name);
199
+ if (!tool) return;
200
+ var resolved = _resolveAttr(attrName, newVal === '' ? null : newVal);
201
+ var update = {};
202
+ Q.setObject(resolved.path, resolved.value, update);
203
+ tool.setState(update);
204
+ }
205
+
206
+ static get observedAttributes() {
207
+ return observedAttrNames;
208
+ }
209
+ }
210
+
211
+ try {
212
+ customElements.define(tagName, ToolElement);
213
+ } catch(e) {
214
+ console.warn('Q.Tool: could not register <' + tagName + '>:', e);
215
+ }
216
+ };
217
+
218
+ });
@@ -0,0 +1,82 @@
1
+ Q.globalMemoryWalk = function (filterFn, options) {
2
+ options = options || {};
3
+ var seen = new WeakSet();
4
+ var found = new Set();
5
+ var pathMap = new WeakMap();
6
+
7
+ var maxDepth = options.maxDepth || 5;
8
+ var includeStack = options.includeStack || false;
9
+ var logEvery = options.logEvery || 100;
10
+ var startingKeys = Q.globalNamesAdded
11
+ ? Q.globalNamesAdded()
12
+ : Object.keys(window);
13
+
14
+ let totalChecked = 0;
15
+ let matchesFound = 0;
16
+
17
+ function walk(obj, path = 'window', depth = 0) {
18
+ if (!obj || typeof obj !== 'object') return;
19
+ if (seen.has(obj)) return;
20
+ seen.add(obj);
21
+
22
+ totalChecked++;
23
+ if (totalChecked % logEvery === 0) {
24
+ console.log(`Checked ${totalChecked} objects, found ${matchesFound}`);
25
+ }
26
+
27
+ if (filterFn(obj)) {
28
+ found.add(obj);
29
+ matchesFound++;
30
+ if (includeStack) {
31
+ pathMap.set(obj, path);
32
+ }
33
+ }
34
+
35
+ if (depth >= maxDepth) return;
36
+
37
+ var skipKeys = obj instanceof HTMLElement
38
+ ? new Set([
39
+ 'parentNode', 'parentElement', 'nextSibling', 'previousSibling',
40
+ 'firstChild', 'lastChild', 'children', 'childNodes',
41
+ 'ownerDocument', 'style', 'classList', 'dataset',
42
+ 'attributes', 'innerHTML', 'outerHTML',
43
+ 'nextElementSibling', 'previousElementSibling'
44
+ ])
45
+ : null;
46
+
47
+ for (var key in obj) {
48
+ if (skipKeys && skipKeys.has(key)) continue;
49
+ try {
50
+ walk(obj[key], path + '.' + key, depth + 1);
51
+ } catch (e) {}
52
+ }
53
+ }
54
+
55
+ let i = 0;
56
+ function nextBatch() {
57
+ var batchSize = 10;
58
+ var end = Math.min(i + batchSize, startingKeys.length);
59
+
60
+ for (; i < end; i++) {
61
+ try {
62
+ walk(window[startingKeys[i]], 'window.' + startingKeys[i], 0);
63
+ } catch (e) {}
64
+ }
65
+
66
+ if (i < startingKeys.length) {
67
+ setTimeout(nextBatch, 0); // Schedule next batch
68
+ } else {
69
+ console.log(`Done. Found ${matchesFound} retained objects.`);
70
+ if (includeStack) {
71
+ console.log([...found].map(obj => ({
72
+ object: obj,
73
+ path: pathMap.get(obj)
74
+ })));
75
+ } else {
76
+ console.log([...found]);
77
+ }
78
+ }
79
+ }
80
+
81
+ nextBatch();
82
+ };