- import type {
- TClearTimerHistory,
- TSetTimerHistory,
+ import {
+ SetTimerFact,
+ type TClearTimerHistory,
+ type TSetTimerHistory,
} from '../../wrapper/TimerWrapper.ts';
import { msToHms } from '../../api/time.ts';
import FrameSensitiveTime from './FrameSensitiveTime.svelte';
@@ -14,6 +15,8 @@
import Alert from './Alert.svelte';
import TimersClearHistory from './TimersClearHistory.svelte';
import CancelableCallMetric from './CancelableCallMetric.svelte';
+ import type { TFactsMap } from '../../wrapper/Fact.ts';
+ import FactsCell from './FactsCell.svelte';
let {
metric,
@@ -27,6 +30,16 @@
let dialogEl: Dialog | null = null;
let alertEl: Alert | null = null;
let clearTimerHistoryMetrics: TClearTimerHistory[] = $state([]);
+ const SetTimerFacts: TFactsMap = new Map([
+ [SetTimerFact.NOT_A_FUNCTION, {
+ tag: 'C',
+ details: 'Callback is not a function',
+ }],
+ [SetTimerFact.BAD_DELAY, {
+ tag: 'D',
+ details: 'Delay is not a positive number or undefined',
+ }],
+ ]);
function onFindRegressors(regressors: string[] | null) {
if (!dialogEl || !alertEl || !regressors?.length) {
@@ -83,6 +96,9 @@
|
|
+
+
+ |
0;
+ let rafTraceId;
+
+ if (validHandler(handler)) {
+ rafTraceId = this.onlineAnimationFrameLookup.get(handler);
- if (hasError) {
- handler = TAG_EXCEPTION(handler);
+ if (rafTraceId) {
+ this.onlineAnimationFrameLookup.delete(handler);
+ } else {
+ facts = Fact.assign(facts, CafFact.NOT_FOUND);
+ }
+ } else {
+ handler = TAG_BAD_HANDLER(handler);
+ facts = Fact.assign(facts, CafFact.BAD_HANDLER);
}
if (existing) {
existing.calls++;
existing.handler = handler;
+
+ if (facts) {
+ existing.facts = Fact.assign(existing.facts, facts);
+ }
} else {
this.cafHistory.set(callstack.traceId, {
traceId: callstack.traceId,
trace: callstack.trace,
traceDomain: this.traceUtil.getTraceDomain(callstack.trace[0]),
+ facts,
calls: 1,
handler,
});
}
- const rafTraceId = this.onlineAnimationFrameLookup.get(Number(handler));
const rafRecord = rafTraceId && this.rafHistory.get(rafTraceId);
if (rafRecord) {
- this.onlineAnimationFrameLookup.delete(Number(handler));
-
rafRecord.online--;
+ rafRecord.canceledCounter++;
if (rafRecord.canceledByTraceIds === null) {
rafRecord.canceledByTraceIds = [callstack.traceId];
} else if (!rafRecord.canceledByTraceIds.includes(callstack.traceId)) {
rafRecord.canceledByTraceIds.push(callstack.traceId);
}
- rafRecord.canceledCounter++;
}
}
diff --git a/src/wrapper/Fact.ts b/src/wrapper/Fact.ts
index 5d21961..f0d8a21 100644
--- a/src/wrapper/Fact.ts
+++ b/src/wrapper/Fact.ts
@@ -1,8 +1,11 @@
import type { Brand } from '../api/generics.ts';
-type TFact = Brand;
-type TFactDetail = string;
-type TFactsMap = Map;
+export type TFact = Brand;
+interface IFactDescriptor {
+ tag: string;
+ details: string;
+}
+export type TFactsMap = Map;
export class Fact {
/**
@@ -20,15 +23,28 @@ export class Fact {
return (data | fact) as TFact;
}
- static check(data: number, fact: TFact): TFact {
- return (data & fact) as TFact;
+ static check(data: number, fact: TFact): boolean {
+ return !!(data & fact);
+ }
+
+ static getDetails(data: number, factsMap: TFactsMap): string {
+ const rv: string[] = [];
+
+ for (const [fact, descriptor] of factsMap) {
+ Fact.check(data, fact) &&
+ rv.push(`${descriptor.tag}: ${descriptor.details}`);
+ }
+
+ return rv.join('\n');
}
- static getDetails(data: number, factsMap: TFactsMap): TFactDetail[] {
- const rv = [];
+ static getTags(data: number, factsMap: TFactsMap): string {
+ let rv = '';
- for (const [fact, detail] of factsMap) {
- Fact.check(data, fact) && rv.push(detail);
+ for (const [fact, descriptor] of factsMap) {
+ if (Fact.check(data, fact)) {
+ rv += descriptor.tag;
+ }
}
return rv;
diff --git a/src/wrapper/IdleWrapper.ts b/src/wrapper/IdleWrapper.ts
index 34f3761..4cb482c 100644
--- a/src/wrapper/IdleWrapper.ts
+++ b/src/wrapper/IdleWrapper.ts
@@ -1,5 +1,4 @@
import type { TSettingsPanel } from '../api/settings.ts';
-import { TAG_EXCEPTION } from '../api/clone.ts';
import { trim2microsecond } from '../api/time.ts';
import {
type ETraceDomain,
@@ -8,11 +7,14 @@ import {
type TTrace,
} from './TraceUtil.ts';
import { validHandler, validTimerDelay } from './util.ts';
+import { Fact, type TFact } from './Fact.ts';
+import { TAG_BAD_DELAY, TAG_BAD_HANDLER } from '../api/const.ts';
export type TRequestIdleCallbackHistory = {
traceId: string;
trace: TTrace[];
traceDomain: ETraceDomain;
+ facts: TFact;
calls: number;
handler: number | undefined | string;
delay: number | undefined | string;
@@ -26,6 +28,7 @@ export type TCancelIdleCallbackHistory = {
traceId: string;
trace: TTrace[];
traceDomain: ETraceDomain;
+ facts: TFact;
calls: number;
handler: number | undefined | string;
};
@@ -36,6 +39,13 @@ const requestIdleCallback = /*@__PURE__*/ globalThis.requestIdleCallback.bind(
const cancelIdleCallback = /*@__PURE__*/ globalThis.cancelIdleCallback.bind(
globalThis,
);
+export const RicFact = {
+ BAD_DELAY: Fact.define(1 << 0),
+} as const;
+export const CicFact = {
+ NOT_FOUND: Fact.define(1 << 0),
+ BAD_HANDLER: Fact.define(1 << 1),
+} as const;
export class IdleWrapper {
traceUtil: TraceUtil;
@@ -82,8 +92,14 @@ export class IdleWrapper {
callstack: TCallstack,
) {
const existing = this.ricHistory.get(callstack.traceId);
- const hasError = !validTimerDelay(delay);
- delay = hasError ? TAG_EXCEPTION(delay) : trim2microsecond(delay);
+ let facts = 0;
+
+ if (validTimerDelay(delay)) {
+ delay = trim2microsecond(delay);
+ } else {
+ delay = TAG_BAD_DELAY(delay);
+ facts = Fact.assign(facts, RicFact.BAD_DELAY);
+ }
if (existing) {
existing.calls++;
@@ -91,11 +107,16 @@ export class IdleWrapper {
existing.didTimeout = undefined;
existing.delay = delay;
existing.online++;
+
+ if (facts) {
+ existing.facts = Fact.assign(existing.facts, facts);
+ }
} else {
this.ricHistory.set(callstack.traceId, {
traceId: callstack.traceId,
trace: callstack.trace,
traceDomain: this.traceUtil.getTraceDomain(callstack.trace[0]),
+ facts,
calls: 1,
handler,
didTimeout: undefined,
@@ -112,39 +133,51 @@ export class IdleWrapper {
#updateCicHistory(handler: number | string, callstack: TCallstack) {
const existing = this.cicHistory.get(callstack.traceId);
- const hasError = !validHandler(handler);
+ let facts = 0;
+ let ricTraceId;
+
+ if (validHandler(handler)) {
+ ricTraceId = this.onlineIdleCallbackLookup.get(handler);
- if (hasError) {
- handler = TAG_EXCEPTION(handler);
+ if (ricTraceId) {
+ this.onlineIdleCallbackLookup.delete(handler);
+ } else {
+ facts = Fact.assign(facts, CicFact.NOT_FOUND);
+ }
+ } else {
+ handler = TAG_BAD_HANDLER(handler);
+ facts = Fact.assign(facts, CicFact.BAD_HANDLER);
}
if (existing) {
existing.calls++;
existing.handler = handler;
+
+ if (facts) {
+ existing.facts = Fact.assign(existing.facts, facts);
+ }
} else {
this.cicHistory.set(callstack.traceId, {
traceId: callstack.traceId,
trace: callstack.trace,
traceDomain: this.traceUtil.getTraceDomain(callstack.trace[0]),
+ facts,
calls: 1,
handler,
});
}
- const ricTraceId = this.onlineIdleCallbackLookup.get(Number(handler));
const ricRecord = ricTraceId && this.ricHistory.get(ricTraceId);
if (ricRecord) {
- this.onlineIdleCallbackLookup.delete(Number(handler));
-
ricRecord.online--;
ricRecord.didTimeout = undefined;
+ ricRecord.canceledCounter++;
if (ricRecord.canceledByTraceIds === null) {
ricRecord.canceledByTraceIds = [callstack.traceId];
} else if (!ricRecord.canceledByTraceIds.includes(callstack.traceId)) {
ricRecord.canceledByTraceIds.push(callstack.traceId);
}
- ricRecord.canceledCounter++;
}
}
diff --git a/src/wrapper/TimerWrapper.ts b/src/wrapper/TimerWrapper.ts
index d5291f7..c99476e 100644
--- a/src/wrapper/TimerWrapper.ts
+++ b/src/wrapper/TimerWrapper.ts
@@ -9,15 +9,17 @@ import {
clearTimeout,
setInterval,
setTimeout,
+ TAG_BAD_DELAY,
+ TAG_BAD_HANDLER,
+ TAG_DELAY_NOT_FOUND,
TAG_EVAL_RETURN_SET_INTERVAL,
TAG_EVAL_RETURN_SET_TIMEOUT,
- TAG_MISFORTUNE,
} from '../api/const.ts';
import type { TSettingsPanel } from '../api/settings.ts';
import type { EvalWrapper } from './EvalWrapper.ts';
-import { TAG_EXCEPTION } from '../api/clone.ts';
import { validHandler, validTimerDelay } from './util.ts';
import { trim2microsecond } from '../api/time.ts';
+import { Fact, type TFact } from './Fact.ts';
export enum ETimerType {
TIMEOUT,
@@ -30,16 +32,15 @@ export type TOnlineTimerMetrics = {
type: ETimerType;
delay: number | undefined | string;
handler: number;
- isEval: boolean;
};
export type TSetTimerHistory = {
traceId: string;
trace: TTrace[];
traceDomain: ETraceDomain;
+ facts: TFact;
calls: number;
handler: number | string;
delay: number | undefined | string;
- isEval: boolean | undefined;
online: number;
canceledCounter: number;
canceledByTraceIds: string[] | null;
@@ -49,11 +50,21 @@ export type TClearTimerHistory = {
traceId: string;
trace: TTrace[];
traceDomain: ETraceDomain;
+ facts: TFact;
calls: number;
handler: number | string;
delay: number | undefined | string;
};
+export const SetTimerFact = /*@__PURE__*/ {
+ NOT_A_FUNCTION: Fact.define(1 << 0),
+ BAD_DELAY: Fact.define(1 << 1),
+} as const;
+export const ClearTimerFact = /*@__PURE__*/ {
+ NOT_FOUND: Fact.define(1 << 0),
+ BAD_HANDLER: Fact.define(1 << 1),
+} as const;
+
export class TimerWrapper {
traceUtil: TraceUtil;
apiEval: EvalWrapper;
@@ -85,17 +96,15 @@ export class TimerWrapper {
handler: number,
delay: number | undefined | string,
callstack: TCallstack,
- isEval: boolean,
) {
delay = validTimerDelay(delay)
? trim2microsecond(delay)
- : TAG_EXCEPTION(delay);
+ : TAG_BAD_DELAY(delay);
this.onlineTimers.set(handler, {
type,
handler,
delay,
- isEval,
traceId: callstack.traceId,
trace: callstack.trace,
traceDomain: this.traceUtil.getTraceDomain(callstack.trace[0]),
@@ -145,26 +154,39 @@ export class TimerWrapper {
callstack: TCallstack,
isEval: boolean,
) {
+ let facts = 0;
const existing = history.get(callstack.traceId);
- const hasError = !validTimerDelay(delay);
- delay = hasError ? TAG_EXCEPTION(delay) : trim2microsecond(delay);
+
+ if (validTimerDelay(delay)) {
+ delay = trim2microsecond(delay);
+ } else {
+ delay = TAG_BAD_DELAY(delay);
+ facts = Fact.assign(facts, SetTimerFact.BAD_DELAY);
+ }
+
+ if (isEval) {
+ facts = Fact.assign(facts, SetTimerFact.NOT_A_FUNCTION);
+ }
if (existing) {
existing.handler = handler;
existing.delay = delay;
existing.calls++;
- existing.isEval = isEval;
existing.online++;
+
+ if (facts) {
+ existing.facts = Fact.assign(existing.facts, facts);
+ }
} else {
history.set(callstack.traceId, {
handler,
calls: 1,
delay,
- isEval,
online: 1,
traceId: callstack.traceId,
trace: callstack.trace,
traceDomain: this.traceUtil.getTraceDomain(callstack.trace[0]),
+ facts,
canceledCounter: 0,
canceledByTraceIds: null,
selfTime: null,
@@ -178,22 +200,30 @@ export class TimerWrapper {
callstack: TCallstack,
) {
const existing = history.get(callstack.traceId);
- const hasError = !validHandler(handler);
- const onlineTimer = hasError
- ? null
- : this.onlineTimers.get( handler);
- const handlerDelay: string | number | undefined = onlineTimer
- ? onlineTimer.delay
- : TAG_MISFORTUNE;
-
- if (hasError) {
- handler = TAG_EXCEPTION(handler);
+ let handlerDelay: string | number | undefined = TAG_DELAY_NOT_FOUND;
+ let facts = 0;
+
+ if (validHandler(handler)) {
+ const onlineTimer = this.onlineTimers.get(handler);
+
+ if (onlineTimer) {
+ handlerDelay = onlineTimer.delay;
+ } else {
+ facts = Fact.assign(facts, ClearTimerFact.NOT_FOUND);
+ }
+ } else {
+ handler = TAG_BAD_HANDLER(handler);
+ facts = Fact.assign(facts, ClearTimerFact.BAD_HANDLER);
}
if (existing) {
existing.handler = handler;
existing.delay = handlerDelay;
existing.calls++;
+
+ if (facts) {
+ existing.facts = Fact.assign(existing.facts, facts);
+ }
} else {
history.set(callstack.traceId, {
handler: handler,
@@ -202,6 +232,7 @@ export class TimerWrapper {
traceId: callstack.traceId,
trace: callstack.trace,
traceDomain: this.traceUtil.getTraceDomain(callstack.trace[0]),
+ facts,
});
}
}
@@ -227,7 +258,7 @@ export class TimerWrapper {
) {
const err = new Error(TraceUtil.SIGNATURE);
const callstack = this.traceUtil.getCallstack(err, code);
- const isEval = typeof code === 'string';
+ const isEval = typeof code !== 'function';
this.callCounter.setTimeout++;
const handler = this.native.setTimeout(
@@ -266,7 +297,7 @@ export class TimerWrapper {
...args,
);
- this.#timerOnline(ETimerType.TIMEOUT, handler, delay, callstack, isEval);
+ this.#timerOnline(ETimerType.TIMEOUT, handler, delay, callstack);
this.#updateSetTimersHistory(
this.setTimeoutHistory,
handler,
@@ -326,7 +357,7 @@ export class TimerWrapper {
) {
const err = new Error(TraceUtil.SIGNATURE);
const callstack = this.traceUtil.getCallstack(err, code);
- const isEval = typeof code === 'string';
+ const isEval = typeof code !== 'function';
this.callCounter.setInterval++;
@@ -365,7 +396,7 @@ export class TimerWrapper {
...args,
);
- this.#timerOnline(ETimerType.INTERVAL, handler, delay, callstack, isEval);
+ this.#timerOnline(ETimerType.INTERVAL, handler, delay, callstack);
this.#updateSetTimersHistory(
this.setIntervalHistory,
handler,
diff --git a/src/wrapper/util.ts b/src/wrapper/util.ts
index ffca4eb..cc840d3 100644
--- a/src/wrapper/util.ts
+++ b/src/wrapper/util.ts
@@ -1,5 +1,5 @@
export function validHandler(handler: unknown): handler is number {
- return Number.isFinite(handler) && handler > 0;
+ return Number.isInteger(handler) && handler > 0;
}
export function validTimerDelay(delay: unknown): delay is number {
diff --git a/tests/AnimationWrapper_test.ts b/tests/AnimationWrapper_test.ts
index e1c4281..5271044 100644
--- a/tests/AnimationWrapper_test.ts
+++ b/tests/AnimationWrapper_test.ts
@@ -2,9 +2,10 @@ import './browserPolyfill.ts';
import { wait } from './util.ts';
import { afterEach, beforeEach, describe, test } from '@std/testing/bdd';
import { expect } from '@std/expect';
-import { AnimationWrapper } from '../src/wrapper/AnimationWrapper.ts';
+import { AnimationWrapper, CafFact } from '../src/wrapper/AnimationWrapper.ts';
import { TraceUtil } from '../src/wrapper/TraceUtil.ts';
-import { TAG_EXCEPTION } from '../src/api/clone.ts';
+import { TAG_BAD_HANDLER } from '../src/api/const.ts';
+import { Fact } from '../src/wrapper/Fact.ts';
describe('AnimationWrapper', () => {
const traceUtil = new TraceUtil();
@@ -70,8 +71,17 @@ describe('AnimationWrapper', () => {
const rec = Array.from(apiAnimation.cafHistory?.values())[0];
- expect(rec.handler).toBe(TAG_EXCEPTION(0));
+ expect(rec.handler).toBe(TAG_BAD_HANDLER(0));
+ expect(Fact.check(rec.facts, CafFact.BAD_HANDLER)).toBe(true);
+ });
+
+ test('cafHistory - raf not found', () => {
+ cancelAnimationFrame(404);
+
+ const rec = Array.from(apiAnimation.cafHistory?.values())[0];
+
+ expect(Fact.check(rec.facts, CafFact.NOT_FOUND)).toBe(true);
});
});
-await wait(1e3);
+await wait(1e1);
diff --git a/tests/EvalWrapper_test.ts b/tests/EvalWrapper_test.ts
index 584b365..dc5547c 100644
--- a/tests/EvalWrapper_test.ts
+++ b/tests/EvalWrapper_test.ts
@@ -3,13 +3,14 @@ import { wait } from './util.ts';
import { afterEach, beforeEach, describe, test } from '@std/testing/bdd';
import { expect } from '@std/expect';
import { EvalWrapper } from '../src/wrapper/EvalWrapper.ts';
-import { TimerWrapper } from '../src/wrapper/TimerWrapper.ts';
+import { SetTimerFact, TimerWrapper } from '../src/wrapper/TimerWrapper.ts';
import { TraceUtil } from '../src/wrapper/TraceUtil.ts';
import { TAG_UNDEFINED } from '../src/api/clone.ts';
import {
TAG_EVAL_RETURN_SET_INTERVAL,
TAG_EVAL_RETURN_SET_TIMEOUT,
} from '../src/api/const.ts';
+import { Fact } from '../src/wrapper/Fact.ts';
describe('EvalWrapper', () => {
const traceUtil = new TraceUtil();
@@ -69,7 +70,7 @@ describe('EvalWrapper', () => {
const timerRec = Array.from(apiTimer.setTimeoutHistory.values())[0];
const evalRec = Array.from(apiEval.evalHistory.values())[0];
- expect(timerRec.isEval).toBe(true);
+ expect(Fact.check(timerRec.facts, SetTimerFact.NOT_A_FUNCTION)).toBe(true);
expect(evalRec.code).toBe(CODE);
expect(evalRec.returnedValue).toBe(TAG_EVAL_RETURN_SET_TIMEOUT);
});
@@ -80,7 +81,7 @@ describe('EvalWrapper', () => {
const timerRec = Array.from(apiTimer.setIntervalHistory.values())[0];
const evalRec = Array.from(apiEval.evalHistory.values())[0];
- expect(timerRec.isEval).toBe(true);
+ expect(Fact.check(timerRec.facts, SetTimerFact.NOT_A_FUNCTION)).toBe(true);
expect(evalRec.code).toBe(CODE);
expect(evalRec.returnedValue).toBe(TAG_EVAL_RETURN_SET_INTERVAL);
@@ -88,4 +89,4 @@ describe('EvalWrapper', () => {
});
});
-await wait(1e3);
+await wait(1e1);
diff --git a/tests/Facts_test.ts b/tests/Facts_test.ts
index b42280e..ccde7ae 100644
--- a/tests/Facts_test.ts
+++ b/tests/Facts_test.ts
@@ -30,7 +30,10 @@ describe('Facts', () => {
test('getDetails', () => {
let data = 0;
- const factsMap = new Map([[fact_1, 'fact_1'], [fact_2, 'fact_2']]);
+ const factsMap = new Map([
+ [fact_1, { tag: '1', details: 'fact_1' }],
+ [fact_2, { tag: '2', details: 'fact_2' }],
+ ]);
for (const [fact, _detail] of factsMap) {
data = Fact.assign(data, fact);
@@ -38,6 +41,6 @@ describe('Facts', () => {
const details = Fact.getDetails(data, factsMap);
- expect(details.length).toBe(factsMap.size);
+ expect(details).toBe(`1: fact_1\n2: fact_2`);
});
});
diff --git a/tests/IdleWrapper_test.ts b/tests/IdleWrapper_test.ts
new file mode 100644
index 0000000..2388a21
--- /dev/null
+++ b/tests/IdleWrapper_test.ts
@@ -0,0 +1,99 @@
+import './browserPolyfill.ts';
+import { wait } from './util.ts';
+import { afterEach, beforeEach, describe, test } from '@std/testing/bdd';
+import { expect } from '@std/expect';
+import { CicFact, IdleWrapper, RicFact } from '../src/wrapper/IdleWrapper.ts';
+import { TraceUtil } from '../src/wrapper/TraceUtil.ts';
+import { TAG_BAD_DELAY, TAG_BAD_HANDLER } from '../src/api/const.ts';
+import { Fact } from '../src/wrapper/Fact.ts';
+
+describe('IdleWrapper', () => {
+ const traceUtil = new TraceUtil();
+ let apiIdle: IdleWrapper;
+
+ beforeEach(() => {
+ apiIdle = new IdleWrapper(traceUtil);
+ apiIdle.wrapRequestIdleCallback();
+ apiIdle.wrapCancelIdleCallback();
+ });
+
+ afterEach(() => {
+ apiIdle.unwrapRequestIdleCallback();
+ apiIdle.unwrapCancelIdleCallback();
+ });
+
+ test('ricHistory - recorded', async () => {
+ let typeOfArgument = '';
+ const handler = await new Promise((resolve) => {
+ const handler = requestIdleCallback((o) => {
+ typeOfArgument = typeof o.didTimeout;
+ resolve(handler);
+ });
+ });
+ const rec = Array.from(apiIdle.ricHistory.values())[0];
+
+ expect(typeOfArgument).toBe('boolean');
+ expect(apiIdle.ricHistory.size).toBe(1);
+ expect(rec.handler).toBe(handler);
+ expect(rec.calls).toBe(1);
+ expect(rec.trace.length).toBeGreaterThan(1);
+ expect(rec.traceId.length).toBeGreaterThan(1);
+ expect(rec.selfTime).not.toBeNull();
+ expect(apiIdle.callCounter.requestIdleCallback).toBe(1);
+ });
+
+ test('cicHistory - recorded', () => {
+ const unchanged = 0,
+ changed = 1;
+ let changeable = unchanged;
+ const handler = requestIdleCallback(() => {
+ changeable = changed;
+ });
+ cancelIdleCallback(handler);
+
+ const ricRec = Array.from(apiIdle.ricHistory.values())[0];
+ const cicRec = Array.from(apiIdle.cicHistory.values())[0];
+
+ expect(changeable).toBe(unchanged);
+ expect(apiIdle.ricHistory.size).toBe(1);
+ expect(apiIdle.cicHistory.size).toBe(1);
+ expect(cicRec.handler).toBe(handler);
+ expect(cicRec.calls).toBe(1);
+ expect(cicRec.trace.length).toBeGreaterThan(1);
+ expect(cicRec.traceId.length).toBeGreaterThan(1);
+ expect(apiIdle.callCounter.cancelIdleCallback).toBe(1);
+ expect(ricRec.canceledByTraceIds?.length).toBe(1);
+ expect(ricRec.canceledCounter).toBe(1);
+ });
+
+ test('ricHistory - invalid delay', () => {
+ const BAD_DELAY = -1;
+ const handler = requestIdleCallback(() => {}, { timeout: BAD_DELAY });
+ cancelIdleCallback(handler);
+
+ const rec = Array.from(apiIdle.ricHistory?.values())[0];
+
+ expect(rec.delay).toBe(TAG_BAD_DELAY(BAD_DELAY));
+ expect(Fact.check(rec.facts, RicFact.BAD_DELAY)).toBe(true);
+ });
+
+ test('cicHistory - invalid handler', () => {
+ const BAD_HANDLER = 0;
+ cancelIdleCallback(BAD_HANDLER);
+
+ const rec = Array.from(apiIdle.cicHistory?.values())[0];
+
+ expect(rec.handler).toBe(TAG_BAD_HANDLER(BAD_HANDLER));
+ expect(Fact.check(rec.facts, CicFact.BAD_HANDLER)).toBe(true);
+ });
+
+ test('cicHistory - ric not found', () => {
+ cancelIdleCallback(404);
+
+ const rec = Array.from(apiIdle.cicHistory?.values())[0];
+
+ expect(Fact.check(rec.facts, CicFact.NOT_FOUND)).toBe(true);
+ });
+});
+
+await wait(1e1);
diff --git a/tests/TimerWrapper_test.ts b/tests/TimerWrapper_test.ts
index 5d1fc85..cdb746b 100644
--- a/tests/TimerWrapper_test.ts
+++ b/tests/TimerWrapper_test.ts
@@ -2,11 +2,19 @@ import './browserPolyfill.ts';
import { wait } from './util.ts';
import { afterEach, beforeEach, describe, test } from '@std/testing/bdd';
import { expect } from '@std/expect';
-import { TAG_EXCEPTION } from '../src/api/clone.ts';
-import { TAG_MISFORTUNE } from '../src/api/const.ts';
+import {
+ TAG_BAD_DELAY,
+ TAG_BAD_HANDLER,
+ TAG_DELAY_NOT_FOUND,
+} from '../src/api/const.ts';
import { TraceUtil } from '../src/wrapper/TraceUtil.ts';
-import { TimerWrapper } from '../src/wrapper/TimerWrapper.ts';
+import {
+ ClearTimerFact,
+ SetTimerFact,
+ TimerWrapper,
+} from '../src/wrapper/TimerWrapper.ts';
import { EvalWrapper } from '../src/wrapper/EvalWrapper.ts';
+import { Fact } from '../src/wrapper/Fact.ts';
describe('wrappers', () => {
const traceUtil = new TraceUtil();
@@ -114,22 +122,24 @@ describe('wrappers', () => {
expect(rec.calls).toBe(1);
expect(rec.delay).toBe(DELAY);
- expect(rec.isEval).toBe(false);
expect(rec.trace.length).toBeGreaterThan(1);
expect(rec.traceId.length).toBeGreaterThan(0);
+ expect(Fact.check(rec.facts, SetTimerFact.BAD_DELAY)).toBe(false);
+ expect(Fact.check(rec.facts, SetTimerFact.NOT_A_FUNCTION)).toBe(false);
globalThis.clearTimeout(handler);
expect(rec.selfTime).toBeNull();
});
test('setTimeoutHistory - invalid delay', () => {
- globalThis.setTimeout(() => {}, -1);
+ const BAD_DELAY = -1;
+ globalThis.setTimeout(() => {}, BAD_DELAY);
const rec = Array.from(apiTimer.setTimeoutHistory.values())[0];
expect(rec.calls).toBe(1);
- expect(rec.delay).toBe(TAG_EXCEPTION('-1'));
- expect(rec.isEval).toBe(false);
+ expect(rec.delay).toBe(TAG_BAD_DELAY(BAD_DELAY));
+ expect(Fact.check(rec.facts, SetTimerFact.BAD_DELAY)).toBe(true);
});
test('setTimeout - poling registers selfTime', async () => {
@@ -166,6 +176,8 @@ describe('wrappers', () => {
expect(rec.handler).toBe(handler);
expect(rec.delay).toBe(1e3);
+ expect(Fact.check(rec.facts, ClearTimerFact.BAD_HANDLER)).toBe(false);
+ expect(Fact.check(rec.facts, ClearTimerFact.NOT_FOUND)).toBe(false);
});
test('clearTimeoutHistory - non existent handler', () => {
@@ -173,7 +185,9 @@ describe('wrappers', () => {
const rec = Array.from(apiTimer.clearTimeoutHistory.values())[0];
- expect(rec.delay).toBe(TAG_MISFORTUNE);
+ expect(rec.delay).toBe(TAG_DELAY_NOT_FOUND);
+ expect(Fact.check(rec.facts, ClearTimerFact.BAD_HANDLER)).toBe(false);
+ expect(Fact.check(rec.facts, ClearTimerFact.NOT_FOUND)).toBe(true);
});
test('clearTimeoutHistory - invalid handler', () => {
@@ -181,8 +195,10 @@ describe('wrappers', () => {
const rec = Array.from(apiTimer.clearTimeoutHistory.values())[0];
- expect(rec.delay).toBe(TAG_MISFORTUNE);
- expect(rec.handler).toBe(TAG_EXCEPTION(0));
+ expect(rec.delay).toBe(TAG_DELAY_NOT_FOUND);
+ expect(rec.handler).toBe(TAG_BAD_HANDLER(0));
+ expect(Fact.check(rec.facts, ClearTimerFact.BAD_HANDLER)).toBe(true);
+ expect(Fact.check(rec.facts, ClearTimerFact.NOT_FOUND)).toBe(false);
});
test('setIntervalHistory & clearIntervalHistory - recorded', () => {
@@ -219,10 +235,11 @@ describe('wrappers', () => {
expect(rec.calls).toBe(1);
expect(rec.delay).toBe(DELAY);
- expect(rec.isEval).toBe(false);
expect(rec.trace.length).toBeGreaterThan(1);
expect(rec.traceId.length).toBeGreaterThan(0);
expect(rec.selfTime).not.toBeNull();
+ expect(Fact.check(rec.facts, SetTimerFact.BAD_DELAY)).toBe(false);
+ expect(Fact.check(rec.facts, SetTimerFact.NOT_A_FUNCTION)).toBe(false);
globalThis.clearInterval(handler);
});
@@ -232,8 +249,9 @@ describe('wrappers', () => {
const rec = Array.from(apiTimer.setIntervalHistory.values())[0];
expect(rec.calls).toBe(1);
- expect(rec.delay).toBe(TAG_EXCEPTION('-1'));
- expect(rec.isEval).toBe(false);
+ expect(rec.delay).toBe(TAG_BAD_DELAY('-1'));
+ expect(Fact.check(rec.facts, SetTimerFact.BAD_DELAY)).toBe(true);
+ expect(Fact.check(rec.facts, SetTimerFact.NOT_A_FUNCTION)).toBe(false);
globalThis.clearInterval(handler);
});
@@ -245,6 +263,7 @@ describe('wrappers', () => {
const rec = Array.from(apiTimer.clearIntervalHistory.values())[0];
expect(rec.delay).toBe(1e3);
+ expect(Fact.check(rec.facts, ClearTimerFact.BAD_HANDLER)).toBe(false);
});
test('clearIntervalHistory - non existent handler', () => {
@@ -252,7 +271,9 @@ describe('wrappers', () => {
const rec = Array.from(apiTimer.clearIntervalHistory.values())[0];
- expect(rec.delay).toBe(TAG_MISFORTUNE);
+ expect(rec.delay).toBe(TAG_DELAY_NOT_FOUND);
+ expect(Fact.check(rec.facts, ClearTimerFact.BAD_HANDLER)).toBe(false);
+ expect(Fact.check(rec.facts, ClearTimerFact.NOT_FOUND)).toBe(true);
});
test('clearIntervalHistory - invalid handler', () => {
@@ -260,8 +281,10 @@ describe('wrappers', () => {
const rec = Array.from(apiTimer.clearIntervalHistory.values())[0];
- expect(rec.delay).toBe(TAG_MISFORTUNE);
+ expect(rec.delay).toBe(TAG_DELAY_NOT_FOUND);
+ expect(Fact.check(rec.facts, ClearTimerFact.BAD_HANDLER)).toBe(true);
+ expect(Fact.check(rec.facts, ClearTimerFact.NOT_FOUND)).toBe(false);
});
});
-await wait(1e3);
+await wait(1e1);
diff --git a/tests/TraceUtil_test.ts b/tests/TraceUtil_test.ts
index be187d7..dbea887 100644
--- a/tests/TraceUtil_test.ts
+++ b/tests/TraceUtil_test.ts
@@ -75,4 +75,4 @@ describe('TraceUtil', () => {
});
});
-await wait(1e3);
+await wait(1e1);
diff --git a/tests/time_test.ts b/tests/time_test.ts
index 19f039e..26cd4ce 100644
--- a/tests/time_test.ts
+++ b/tests/time_test.ts
@@ -164,4 +164,4 @@ describe('callingOnce', () => {
});
});
-await wait(2e3);
+await wait(1e3);
From 0372daca93fa50260d35524bf807b36d7db07eeb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Wed, 16 Apr 2025 09:14:07 +0300
Subject: [PATCH 06/19] add sticky header
---
deno.json | 2 ++
public/api-monitor-devtools-panel.html | 2 +-
public/api-monitor-devtools.html | 5 ++--
public/global.css | 19 +++++++++++++--
src/view/components/ActiveTimers.svelte | 23 ++++++++++---------
.../components/AnimationCancelHistory.svelte | 11 +++++----
.../components/AnimationRequestHistory.svelte | 11 +++++----
src/view/components/EvalMetrics.svelte | 12 ++++++----
.../IdleCallbackCancelHistory.svelte | 11 +++++----
.../IdleCallbackRequestHistory.svelte | 11 +++++----
src/view/components/Media.svelte | 4 ++--
src/view/components/TimersClearHistory.svelte | 11 +++++----
src/view/components/TimersSetHistory.svelte | 11 +++++----
13 files changed, 80 insertions(+), 53 deletions(-)
diff --git a/deno.json b/deno.json
index baa12a2..a0f87dc 100644
--- a/deno.json
+++ b/deno.json
@@ -27,6 +27,8 @@
"include": [
"src/",
"tests/",
+ "public/*.html",
+ "public/*.css",
"build.ts",
"*.json"
]
diff --git a/public/api-monitor-devtools-panel.html b/public/api-monitor-devtools-panel.html
index 1b18575..05bbf58 100644
--- a/public/api-monitor-devtools-panel.html
+++ b/public/api-monitor-devtools-panel.html
@@ -1,4 +1,4 @@
-
+
diff --git a/public/api-monitor-devtools.html b/public/api-monitor-devtools.html
index 0dcde82..9283322 100644
--- a/public/api-monitor-devtools.html
+++ b/public/api-monitor-devtools.html
@@ -1,7 +1,8 @@
-
-
+
+
+ DevtoolsTab
diff --git a/public/global.css b/public/global.css
index 947aa9e..19770aa 100644
--- a/public/global.css
+++ b/public/global.css
@@ -30,8 +30,15 @@ body {
padding: 0;
box-sizing: border-box;
font-family:
- -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu,
- Cantarell, 'Helvetica Neue', sans-serif;
+ -apple-system,
+ BlinkMacSystemFont,
+ 'Segoe UI',
+ Roboto,
+ Oxygen-Sans,
+ Ubuntu,
+ Cantarell,
+ 'Helvetica Neue',
+ sans-serif;
}
a {
@@ -103,6 +110,14 @@ th,
margin-right: 0;
}
+.sticky-header {
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ height: 1rem;
+ vertical-align: middle;
+}
+
.icon {
display: inline-block;
width: 1rem;
diff --git a/src/view/components/ActiveTimers.svelte b/src/view/components/ActiveTimers.svelte
index 1a41802..04bd149 100644
--- a/src/view/components/ActiveTimers.svelte
+++ b/src/view/components/ActiveTimers.svelte
@@ -21,19 +21,23 @@
-
- {caption}
-
-
+
+
{#each metrics as metric (metric.handler)}
- | {metric.delay} |
+
+
+
+ |
{metric.handler}
@@ -45,10 +49,7 @@
onclick={() => void onRemoveHandler(metric)}
>
|
-
-
-
- |
+ {metric.delay} |
{/each}
diff --git a/src/view/components/AnimationCancelHistory.svelte b/src/view/components/AnimationCancelHistory.svelte
index 644205f..98f432c 100644
--- a/src/view/components/AnimationCancelHistory.svelte
+++ b/src/view/components/AnimationCancelHistory.svelte
@@ -56,12 +56,11 @@
-
- {caption}
-
-
+
+
{#each sortedMetrics as metric (metric.traceId)}
diff --git a/src/view/components/AnimationRequestHistory.svelte b/src/view/components/AnimationRequestHistory.svelte
index 60e9fd2..bcf4689 100644
--- a/src/view/components/AnimationRequestHistory.svelte
+++ b/src/view/components/AnimationRequestHistory.svelte
@@ -100,12 +100,11 @@
-
- {caption}
-
-
+
+
{#each sortedMetrics as metric (metric.traceId)}
diff --git a/src/view/components/EvalMetrics.svelte b/src/view/components/EvalMetrics.svelte
index 3d44c37..98ae84d 100644
--- a/src/view/components/EvalMetrics.svelte
+++ b/src/view/components/EvalMetrics.svelte
@@ -24,12 +24,11 @@
{#if evalHistory?.length}
-
- Eval History
-
-
+
+
+
{#each evalHistory as metric (metric.traceId)}
diff --git a/src/view/components/IdleCallbackCancelHistory.svelte b/src/view/components/IdleCallbackCancelHistory.svelte
index 4423132..efb38a9 100644
--- a/src/view/components/IdleCallbackCancelHistory.svelte
+++ b/src/view/components/IdleCallbackCancelHistory.svelte
@@ -54,12 +54,11 @@
-
- {caption}
-
-
+
+
{#each sortedMetrics as metric (metric.traceId)}
diff --git a/src/view/components/IdleCallbackRequestHistory.svelte b/src/view/components/IdleCallbackRequestHistory.svelte
index 8e1a6dc..7ac99fd 100644
--- a/src/view/components/IdleCallbackRequestHistory.svelte
+++ b/src/view/components/IdleCallbackRequestHistory.svelte
@@ -110,12 +110,11 @@
-
- {caption}
-
-
+
+
{#each sortedMetrics as metric (metric.traceId)}
diff --git a/src/view/components/Media.svelte b/src/view/components/Media.svelte
index dbce50c..89af0cf 100644
--- a/src/view/components/Media.svelte
+++ b/src/view/components/Media.svelte
@@ -19,7 +19,7 @@
{#if media.collection.length}
{#if videos.length}
-
+
@@ -32,7 +32,7 @@
{#if audios.length}
-
+
diff --git a/src/view/components/TimersClearHistory.svelte b/src/view/components/TimersClearHistory.svelte
index 0a3e6d1..a8984f8 100644
--- a/src/view/components/TimersClearHistory.svelte
+++ b/src/view/components/TimersClearHistory.svelte
@@ -41,12 +41,11 @@
-
- {caption}
-
-
+
+
{#each sortedMetrics as metric (metric.traceId)}
{/each}
diff --git a/src/view/components/TimersSetHistory.svelte b/src/view/components/TimersSetHistory.svelte
index 48d9b07..c8086c0 100644
--- a/src/view/components/TimersSetHistory.svelte
+++ b/src/view/components/TimersSetHistory.svelte
@@ -50,12 +50,11 @@
-
- {caption}
-
-
+
+
{#each sortedMetrics as metric (metric.traceId)}
Date: Wed, 16 Apr 2025 19:22:37 +0300
Subject: [PATCH 07/19] refactor `` & `` to
``
---
src/view/components/ActiveTimers.svelte | 13 +++++++------
.../components/AnimationCancelHistory.svelte | 9 +++++----
.../components/AnimationRequestHistory.svelte | 9 +++++----
...{TraceDomain.svelte => CallstackCell.svelte} | 17 +++++++++++++++--
src/view/components/EvalMetrics.svelte | 9 +++++----
.../components/IdleCallbackCancelHistory.svelte | 9 +++++----
.../IdleCallbackRequestHistory.svelte | 9 +++++----
.../components/TimersClearHistoryMetric.svelte | 9 +++++----
.../components/TimersSetHistoryMetric.svelte | 9 +++++----
src/view/components/Trace.svelte | 12 ------------
10 files changed, 57 insertions(+), 48 deletions(-)
rename src/view/components/{TraceDomain.svelte => CallstackCell.svelte} (55%)
delete mode 100644 src/view/components/Trace.svelte
diff --git a/src/view/components/ActiveTimers.svelte b/src/view/components/ActiveTimers.svelte
index 04bd149..b6dabc9 100644
--- a/src/view/components/ActiveTimers.svelte
+++ b/src/view/components/ActiveTimers.svelte
@@ -3,8 +3,7 @@
import { EMsg, portPost } from '../../api/communication.ts';
import { msToHms } from '../../api/time.ts';
import Variable from './Variable.svelte';
- import Trace from './Trace.svelte';
- import TraceDomain from './TraceDomain.svelte';
+ import CallstackCell from './CallstackCell.svelte';
let {
metrics,
@@ -26,8 +25,8 @@
|
{caption} Callstack []
|
- Handler |
- Delay |
+ Handler |
+ Delay |
@@ -35,8 +34,10 @@
{#each metrics as metric (metric.handler)}
|
-
-
+
|
{metric.handler}
diff --git a/src/view/components/AnimationCancelHistory.svelte b/src/view/components/AnimationCancelHistory.svelte
index 98f432c..c1a5e62 100644
--- a/src/view/components/AnimationCancelHistory.svelte
+++ b/src/view/components/AnimationCancelHistory.svelte
@@ -11,13 +11,12 @@
} from '../../api/settings.ts';
import { compareByFieldOrder } from '../../api/comparator.ts';
import Variable from './Variable.svelte';
- import Trace from './Trace.svelte';
- import TraceDomain from './TraceDomain.svelte';
import SortableColumn from './SortableColumn.svelte';
import TraceBreakpoint from './TraceBreakpoint.svelte';
import TraceBypass from './TraceBypass.svelte';
import type { TFactsMap } from '../../wrapper/Fact.ts';
import FactsCell from './FactsCell.svelte';
+ import CallstackCell from './CallstackCell.svelte';
let {
cafHistory,
@@ -94,8 +93,10 @@
{#each sortedMetrics as metric (metric.traceId)}
|
|
-
-
+
|
diff --git a/src/view/components/AnimationRequestHistory.svelte b/src/view/components/AnimationRequestHistory.svelte
index bcf4689..6a203dc 100644
--- a/src/view/components/AnimationRequestHistory.svelte
+++ b/src/view/components/AnimationRequestHistory.svelte
@@ -11,8 +11,6 @@
} from '../../api/settings.ts';
import { compareByFieldOrder } from '../../api/comparator.ts';
import Variable from './Variable.svelte';
- import Trace from './Trace.svelte';
- import TraceDomain from './TraceDomain.svelte';
import SortableColumn from './SortableColumn.svelte';
import FrameSensitiveTime from './FrameSensitiveTime.svelte';
import TraceBreakpoint from './TraceBreakpoint.svelte';
@@ -21,6 +19,7 @@
import AnimationCancelHistory from './AnimationCancelHistory.svelte';
import TraceBypass from './TraceBypass.svelte';
import CancelableCallMetric from './CancelableCallMetric.svelte';
+ import CallstackCell from './CallstackCell.svelte';
let {
rafHistory,
@@ -147,8 +146,10 @@
{#each sortedMetrics as metric (metric.traceId)}
|
|
-
-
+
|
|
{metric.cps || undefined} |
diff --git a/src/view/components/TraceDomain.svelte b/src/view/components/CallstackCell.svelte
similarity index 55%
rename from src/view/components/TraceDomain.svelte
rename to src/view/components/CallstackCell.svelte
index 8be9741..8d9b56a 100644
--- a/src/view/components/TraceDomain.svelte
+++ b/src/view/components/CallstackCell.svelte
@@ -1,7 +1,14 @@
{#if traceDomain === ETraceDomain.SAME}
@@ -13,3 +20,9 @@
{:else if traceDomain === ETraceDomain.UNKNOWN}
❓
{/if}
+
+{#each trace as { link, name }, index (index)}
+ {@const isLast = index === trace.length - 1}
+
+ {#if !isLast}• {/if}
+{/each}
diff --git a/src/view/components/EvalMetrics.svelte b/src/view/components/EvalMetrics.svelte
index 98ae84d..9763b2e 100644
--- a/src/view/components/EvalMetrics.svelte
+++ b/src/view/components/EvalMetrics.svelte
@@ -1,11 +1,10 @@
-
-{#each trace as { link, name }, index (index)}
- {@const isLast = index === trace.length - 1}
-
- {#if !isLast}• {/if}
-{/each}
From dc457ba3a4e1b1195d209c43297fd82ea5c538ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Wed, 16 Apr 2025 20:54:07 +0300
Subject: [PATCH 08/19] refactor `src/view` file structure
---
src/view/App.svelte | 28 +++++++++----------
src/view/{components => menu}/InfoBar.svelte | 0
.../{components => menu}/InfoBarItem.svelte | 2 +-
.../{components => menu}/TickSpinner.svelte | 0
.../{components => menu}/TogglePanels.svelte | 2 +-
src/view/{components => menu}/Version.svelte | 0
.../AnimationCancelHistory.svelte | 12 ++++----
.../AnimationRequestHistory.svelte | 18 ++++++------
.../EvalMetrics.svelte => panel/Eval.svelte} | 10 +++----
.../IdleCallbackCancelHistory.svelte | 12 ++++----
.../IdleCallbackRequestHistory.svelte | 20 ++++++-------
src/view/{components => panel}/Media.svelte | 4 +--
.../{components => panel}/OnlineTimers.svelte | 2 +-
.../TimersClearHistory.svelte | 6 ++--
.../TimersSetHistory.svelte | 6 ++--
.../components/ActiveTimers.svelte | 8 +++---
.../components/CallstackCell.svelte | 2 +-
.../components/CancelableCallMetric.svelte | 2 +-
.../{ => panel}/components/FactsCell.svelte | 6 +++-
.../components/FrameSensitiveTime.svelte | 4 +--
.../components/MediaCommands.svelte | 2 +-
.../components/MediaMetrics.svelte | 6 ++--
.../components/SortableColumn.svelte | 2 +-
.../TimersClearHistoryMetric.svelte | 8 +++---
.../components/TimersSetHistoryMetric.svelte | 14 +++++-----
.../components/TraceBreakpoint.svelte | 2 +-
.../{ => panel}/components/TraceBypass.svelte | 2 +-
.../{ => panel}/components/TraceLink.svelte | 2 +-
28 files changed, 93 insertions(+), 89 deletions(-)
rename src/view/{components => menu}/InfoBar.svelte (100%)
rename src/view/{components => menu}/InfoBarItem.svelte (96%)
rename src/view/{components => menu}/TickSpinner.svelte (100%)
rename src/view/{components => menu}/TogglePanels.svelte (98%)
rename src/view/{components => menu}/Version.svelte (100%)
rename src/view/{components => panel}/AnimationCancelHistory.svelte (89%)
rename src/view/{components => panel}/AnimationRequestHistory.svelte (89%)
rename src/view/{components/EvalMetrics.svelte => panel/Eval.svelte} (88%)
rename src/view/{components => panel}/IdleCallbackCancelHistory.svelte (89%)
rename src/view/{components => panel}/IdleCallbackRequestHistory.svelte (90%)
rename src/view/{components => panel}/Media.svelte (91%)
rename src/view/{components => panel}/OnlineTimers.svelte (94%)
rename src/view/{components => panel}/TimersClearHistory.svelte (92%)
rename src/view/{components => panel}/TimersSetHistory.svelte (94%)
rename src/view/{ => panel}/components/ActiveTimers.svelte (86%)
rename src/view/{ => panel}/components/CallstackCell.svelte (91%)
rename src/view/{ => panel}/components/CancelableCallMetric.svelte (91%)
rename src/view/{ => panel}/components/FactsCell.svelte (76%)
rename src/view/{ => panel}/components/FrameSensitiveTime.svelte (75%)
rename src/view/{ => panel}/components/MediaCommands.svelte (97%)
rename src/view/{ => panel}/components/MediaMetrics.svelte (94%)
rename src/view/{ => panel}/components/SortableColumn.svelte (95%)
rename src/view/{ => panel}/components/TimersClearHistoryMetric.svelte (84%)
rename src/view/{ => panel}/components/TimersSetHistoryMetric.svelte (89%)
rename src/view/{ => panel}/components/TraceBreakpoint.svelte (96%)
rename src/view/{ => panel}/components/TraceBypass.svelte (96%)
rename src/view/{ => panel}/components/TraceLink.svelte (97%)
diff --git a/src/view/App.svelte b/src/view/App.svelte
index 48abe4a..779f077 100644
--- a/src/view/App.svelte
+++ b/src/view/App.svelte
@@ -4,19 +4,19 @@
import diff from '../api/diff.ts';
import type { TTelemetry } from '../wrapper/Wrapper.ts';
import { onMount } from 'svelte';
- import Media from './components/Media.svelte';
- import EvalMetrics from './components/EvalMetrics.svelte';
- import Version from './components/Version.svelte';
- import TogglePanels from './components/TogglePanels.svelte';
- import InfoBar from './components/InfoBar.svelte';
- import TickSpinner from './components/TickSpinner.svelte';
- import OnlineTimers from './components/OnlineTimers.svelte';
- import IdleCallbackRequestHistory from './components/IdleCallbackRequestHistory.svelte';
- import IdleCallbackCancelHistory from './components/IdleCallbackCancelHistory.svelte';
- import AnimationRequestHistory from './components/AnimationRequestHistory.svelte';
- import AnimationCancelHistory from './components/AnimationCancelHistory.svelte';
- import TimersSetHistory from './components/TimersSetHistory.svelte';
- import TimersClearHistory from './components/TimersClearHistory.svelte';
+ import Media from './panel/Media.svelte';
+ import Eval from './panel/Eval.svelte';
+ import Version from './menu/Version.svelte';
+ import TogglePanels from './menu/TogglePanels.svelte';
+ import InfoBar from './menu/InfoBar.svelte';
+ import TickSpinner from './menu/TickSpinner.svelte';
+ import OnlineTimers from './panel/OnlineTimers.svelte';
+ import IdleCallbackRequestHistory from './panel/IdleCallbackRequestHistory.svelte';
+ import IdleCallbackCancelHistory from './panel/IdleCallbackCancelHistory.svelte';
+ import AnimationRequestHistory from './panel/AnimationRequestHistory.svelte';
+ import AnimationCancelHistory from './panel/AnimationCancelHistory.svelte';
+ import TimersSetHistory from './panel/TimersSetHistory.svelte';
+ import TimersClearHistory from './panel/TimersClearHistory.svelte';
let spinnerEl: TickSpinner | null = $state.raw(null);
let paused = $state.raw(false);
@@ -131,7 +131,7 @@
{#if telemetry}
-
+
diff --git a/src/view/components/InfoBar.svelte b/src/view/menu/InfoBar.svelte
similarity index 100%
rename from src/view/components/InfoBar.svelte
rename to src/view/menu/InfoBar.svelte
diff --git a/src/view/components/InfoBarItem.svelte b/src/view/menu/InfoBarItem.svelte
similarity index 96%
rename from src/view/components/InfoBarItem.svelte
rename to src/view/menu/InfoBarItem.svelte
index 58fcaaa..77c059d 100644
--- a/src/view/components/InfoBarItem.svelte
+++ b/src/view/menu/InfoBarItem.svelte
@@ -1,6 +1,6 @@
diff --git a/src/view/components/FrameSensitiveTime.svelte b/src/view/panel/components/FrameSensitiveTime.svelte
similarity index 75%
rename from src/view/components/FrameSensitiveTime.svelte
rename to src/view/panel/components/FrameSensitiveTime.svelte
index 1397ea5..e7995b4 100644
--- a/src/view/components/FrameSensitiveTime.svelte
+++ b/src/view/panel/components/FrameSensitiveTime.svelte
@@ -1,6 +1,6 @@
diff --git a/src/view/panel/components/TraceBreakpoint.svelte b/src/view/panel/components/TraceBreakpoint.svelte
index 60f4abd..8115598 100644
--- a/src/view/panel/components/TraceBreakpoint.svelte
+++ b/src/view/panel/components/TraceBreakpoint.svelte
@@ -1,36 +1,24 @@
diff --git a/src/view/panel/components/TraceBypass.svelte b/src/view/panel/components/TraceBypass.svelte
index 9081472..ea21a6c 100644
--- a/src/view/panel/components/TraceBypass.svelte
+++ b/src/view/panel/components/TraceBypass.svelte
@@ -1,36 +1,24 @@
diff --git a/src/view/store/session.store.svelte.ts b/src/view/store/session.store.svelte.ts
new file mode 100644
index 0000000..bafff7b
--- /dev/null
+++ b/src/view/store/session.store.svelte.ts
@@ -0,0 +1,53 @@
+import { getSession, setSession } from '../../api/session.ts';
+import { SvelteSet } from 'svelte/reactivity';
+import { HASH_STRING_LENGTH } from '../../api/hash.ts';
+
+const SIZE_LIMIT = chrome.storage.session.QUOTA_BYTES - HASH_STRING_LENGTH;
+export const sessionStore = $state({
+ bypass: > new SvelteSet(),
+ debug: > new SvelteSet(),
+});
+
+getSession().then((session) => {
+ session.bypass.forEach((traceId) => {
+ sessionStore.bypass.add(traceId);
+ });
+
+ session.debug.forEach((traceId) => {
+ sessionStore.debug.add(traceId);
+ });
+});
+
+export async function toggleBypass(traceId: string) {
+ const sessionStorageSize = await chrome.storage.session.getBytesInUse();
+ let dirty = false;
+
+ if (sessionStore.bypass.has(traceId)) {
+ sessionStore.bypass.delete(traceId);
+ dirty = true;
+ } else if (sessionStorageSize < SIZE_LIMIT) {
+ sessionStore.bypass.add(traceId);
+ dirty = true;
+ }
+
+ dirty && await setSession({
+ bypass: Array.from(sessionStore.bypass.values()),
+ });
+}
+
+export async function toggleDebug(traceId: string) {
+ const sessionStorageSize = await chrome.storage.session.getBytesInUse();
+ let dirty = false;
+
+ if (sessionStore.debug.has(traceId)) {
+ sessionStore.debug.delete(traceId);
+ dirty = true;
+ } else if (sessionStorageSize < SIZE_LIMIT) {
+ sessionStore.debug.add(traceId);
+ dirty = true;
+ }
+
+ dirty && await setSession({
+ debug: Array.from(sessionStore.debug.values()),
+ });
+}
diff --git a/src/wrapper/TraceUtil.ts b/src/wrapper/TraceUtil.ts
index 634e557..d20f07a 100644
--- a/src/wrapper/TraceUtil.ts
+++ b/src/wrapper/TraceUtil.ts
@@ -37,8 +37,8 @@ const REGEX_STACKTRACE_LINK_PROTOCOL = /*@__PURE__*/ new RegExp(
export class TraceUtil {
selfTraceLink = '';
callstackType: EWrapperCallstackType = EWrapperCallstackType.FULL;
- trace4Debug: string | null = null;
- trace4Bypass: string | null = null;
+ debug: Set = new Set();
+ bypass: Set = new Set();
#fullCallstackCacheTrace: Map*traceId*/ string, TTrace[]> = new Map();
static readonly SIGNATURE = 'browser-api-monitor';
@@ -67,11 +67,11 @@ export class TraceUtil {
}
shouldPass(traceId: string) {
- return this.trace4Bypass !== traceId;
+ return !this.bypass.has(traceId);
}
shouldPause(traceId: string) {
- return this.trace4Debug === traceId;
+ return this.debug.has(traceId);
}
#getSelfTraceLink() {
diff --git a/src/wrapper/Wrapper.ts b/src/wrapper/Wrapper.ts
index 0891cae..3a97954 100644
--- a/src/wrapper/Wrapper.ts
+++ b/src/wrapper/Wrapper.ts
@@ -24,6 +24,7 @@ import {
type TRequestIdleCallbackHistory,
} from './IdleWrapper.ts';
import { MediaWrapper, type TMediaTelemetry } from './MediaWrapper.ts';
+import type { TSession } from '../api/session.ts';
export type TTelemetry = {
media: TMediaTelemetry;
@@ -77,12 +78,15 @@ const wrapApis = callingOnce(() => {
export function setSettings(settings: TSettings) {
panels = panelsArray2Map(settings.panels);
- traceUtil.trace4Debug = settings.trace4Debug;
- traceUtil.trace4Bypass = settings.trace4Bypass;
setCallstackType(settings.wrapperCallstackType);
wrapApis();
}
+export function setTracePoints(session: TSession) {
+ traceUtil.debug = new Set(session.debug);
+ traceUtil.bypass = new Set(session.bypass);
+}
+
export function onEachSecond() {
apiMedia.meetMedia();
if (
From 6ec02c4c906faafdc92f4d35af3f425ee2f4b516 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Sun, 20 Apr 2025 08:41:46 +0300
Subject: [PATCH 10/19] tune MARGINAL_SIZE
---
src/view/App.svelte | 1 +
src/view/store/session.store.svelte.ts | 51 +++++++++++++-------------
2 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/src/view/App.svelte b/src/view/App.svelte
index 779f077..c0bda6a 100644
--- a/src/view/App.svelte
+++ b/src/view/App.svelte
@@ -78,6 +78,7 @@
function onDevReload() {
console.clear();
chrome.storage.local.clear();
+ chrome.storage.session.clear();
location.reload();
}
diff --git a/src/view/store/session.store.svelte.ts b/src/view/store/session.store.svelte.ts
index bafff7b..8b9a459 100644
--- a/src/view/store/session.store.svelte.ts
+++ b/src/view/store/session.store.svelte.ts
@@ -1,8 +1,6 @@
import { getSession, setSession } from '../../api/session.ts';
import { SvelteSet } from 'svelte/reactivity';
-import { HASH_STRING_LENGTH } from '../../api/hash.ts';
-const SIZE_LIMIT = chrome.storage.session.QUOTA_BYTES - HASH_STRING_LENGTH;
export const sessionStore = $state({
bypass: > new SvelteSet(),
debug: > new SvelteSet(),
@@ -19,35 +17,36 @@ getSession().then((session) => {
});
export async function toggleBypass(traceId: string) {
- const sessionStorageSize = await chrome.storage.session.getBytesInUse();
- let dirty = false;
-
- if (sessionStore.bypass.has(traceId)) {
- sessionStore.bypass.delete(traceId);
- dirty = true;
- } else if (sessionStorageSize < SIZE_LIMIT) {
- sessionStore.bypass.add(traceId);
- dirty = true;
+ if (await toggleSet(sessionStore.bypass, traceId)) {
+ await setSession({
+ bypass: Array.from(sessionStore.bypass.values()),
+ });
}
-
- dirty && await setSession({
- bypass: Array.from(sessionStore.bypass.values()),
- });
}
export async function toggleDebug(traceId: string) {
- const sessionStorageSize = await chrome.storage.session.getBytesInUse();
- let dirty = false;
+ if (await toggleSet(sessionStore.debug, traceId)) {
+ await setSession({
+ debug: Array.from(sessionStore.debug.values()),
+ });
+ }
+}
- if (sessionStore.debug.has(traceId)) {
- sessionStore.debug.delete(traceId);
- dirty = true;
- } else if (sessionStorageSize < SIZE_LIMIT) {
- sessionStore.debug.add(traceId);
- dirty = true;
+const QUOTA_THRESHOLD = chrome.storage.session.QUOTA_BYTES;
+const MARGINAL_SIZE = 40; // for ASCII string in an array
+async function toggleSet(set: Set, traceId: string): Promise {
+ if (set.has(traceId)) {
+ set.delete(traceId);
+ return true;
}
- dirty && await setSession({
- debug: Array.from(sessionStore.debug.values()),
- });
+ const freeSpace = QUOTA_THRESHOLD -
+ await chrome.storage.session.getBytesInUse();
+
+ if (freeSpace - traceId.length - MARGINAL_SIZE >= 0) {
+ set.add(traceId);
+ return true;
+ }
+
+ return false;
}
From f51da002f6598dd10fcc6ffc38cc4e0512d16922 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Sun, 20 Apr 2025 16:45:21 +0300
Subject: [PATCH 11/19] add `keepAwake` option - useful for uninterrupted
monitoring
---
manifest.json | 2 +-
src/api/settings.ts | 1 +
src/view/menu/TogglePanels.svelte | 69 ++++++++++++++++++++++----
src/view/store/session.store.svelte.ts | 2 +-
4 files changed, 62 insertions(+), 12 deletions(-)
diff --git a/manifest.json b/manifest.json
index 491c2e8..0d15a73 100644
--- a/manifest.json
+++ b/manifest.json
@@ -5,7 +5,7 @@
"description": "Show active intervals, scheduled timeouts, animation frames, idle callbacks, eval invocations, media events and properties",
"minimum_chrome_version": "135.0",
"homepage_url": "https://github.com/zendive/browser-api-monitor",
- "permissions": ["storage"],
+ "permissions": ["storage", "power"],
"host_permissions": ["*://*/*"],
"devtools_page": "public/api-monitor-devtools.html",
"icons": {
diff --git a/src/api/settings.ts b/src/api/settings.ts
index 576fd54..b72c311 100644
--- a/src/api/settings.ts
+++ b/src/api/settings.ts
@@ -130,6 +130,7 @@ export const DEFAULT_SETTINGS = {
paused: false,
devtoolsPanelShown: false,
wrapperCallstackType: EWrapperCallstackType.SHORT,
+ keepAwake: false,
};
export function panelsArray2Map(panels: TSettingsPanel[]) {
diff --git a/src/view/menu/TogglePanels.svelte b/src/view/menu/TogglePanels.svelte
index fea5ed1..6878fea 100644
--- a/src/view/menu/TogglePanels.svelte
+++ b/src/view/menu/TogglePanels.svelte
@@ -14,10 +14,16 @@
let wrapperCallstackType = $state(DEFAULT_SETTINGS.wrapperCallstackType);
let reloadMessageEl: Alert | null = null;
let selfEl: HTMLElement | null = null;
+ let keepAwake = $state.raw(false);
getSettings().then((state) => {
panels = state.panels;
wrapperCallstackType = state.wrapperCallstackType;
+ keepAwake = state.keepAwake;
+
+ if (keepAwake) {
+ chrome.power.requestKeepAwake('display');
+ }
});
runtimeListen(async (o) => {
@@ -48,6 +54,18 @@
});
reloadMessageEl?.show();
}
+
+ function onToggleKeepAwake() {
+ keepAwake = !keepAwake;
+
+ if (keepAwake) {
+ chrome.power.requestKeepAwake('display');
+ } else {
+ chrome.power.releaseKeepAwake();
+ }
+
+ setSettings({ keepAwake });
+ }
@@ -134,13 +166,31 @@
padding: 0 0.375rem;
.menu-content {
+ margin: 0.2rem 0;
+
.menu-item {
- line-height: 1.4rem;
+ td {
+ line-height: 1rem;
+ padding: 0.1rem 0 0.1rem 0;
- &.-dash {
+ &.-left {
+ max-width: 12rem;
+ }
+ &.-right {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+
+ &.-dash-bottom {
border-bottom: 1px solid var(--border);
}
+ &.-dash-top {
+ border-top: 1px solid var(--border);
+ }
+
.toggle-visibility {
color: var(--text);
text-wrap: nowrap;
@@ -152,9 +202,8 @@
.btn-toggle {
color: var(--text);
- border-left: 1px solid var(--border);
- border-right: none;
margin-left: 0.375rem;
+ font-weight: bold;
}
}
}
diff --git a/src/view/store/session.store.svelte.ts b/src/view/store/session.store.svelte.ts
index 8b9a459..c675ce0 100644
--- a/src/view/store/session.store.svelte.ts
+++ b/src/view/store/session.store.svelte.ts
@@ -33,7 +33,7 @@ export async function toggleDebug(traceId: string) {
}
const QUOTA_THRESHOLD = chrome.storage.session.QUOTA_BYTES;
-const MARGINAL_SIZE = 40; // for ASCII string in an array
+const MARGINAL_SIZE = 40; // for ASCII string in an array
async function toggleSet(set: Set, traceId: string): Promise {
if (set.has(traceId)) {
set.delete(traceId);
From 0c859e1c2e3619478a2305f00ced9d124efef7b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Sun, 20 Apr 2025 20:30:58 +0300
Subject: [PATCH 12/19] adjust trigger points for `keepAwake`
---
src/api-monitor-devtools.ts | 4 ++++
src/view/App.svelte | 1 +
src/view/menu/TogglePanels.svelte | 5 +----
3 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/api-monitor-devtools.ts b/src/api-monitor-devtools.ts
index b367f08..7782535 100644
--- a/src/api-monitor-devtools.ts
+++ b/src/api-monitor-devtools.ts
@@ -14,9 +14,13 @@ if (chrome.devtools.inspectedWindow.tabId !== null) {
if (!settings.paused) {
portPost({ msg: EMsg.START_OBSERVE });
}
+ if (settings.keepAwake) {
+ chrome.power.requestKeepAwake('display');
+ }
setSettings({ devtoolsPanelShown: true });
});
panel.onHidden.addListener(() => {
+ chrome.power.releaseKeepAwake();
portPost({ msg: EMsg.STOP_OBSERVE });
setSettings({ devtoolsPanelShown: false });
});
diff --git a/src/view/App.svelte b/src/view/App.svelte
index c0bda6a..8f80bf0 100644
--- a/src/view/App.svelte
+++ b/src/view/App.svelte
@@ -47,6 +47,7 @@
});
globalThis.addEventListener('beforeunload', () => {
+ chrome.power.releaseKeepAwake();
portPost({ msg: EMsg.STOP_OBSERVE });
});
});
diff --git a/src/view/menu/TogglePanels.svelte b/src/view/menu/TogglePanels.svelte
index 6878fea..d93b611 100644
--- a/src/view/menu/TogglePanels.svelte
+++ b/src/view/menu/TogglePanels.svelte
@@ -20,10 +20,6 @@
panels = state.panels;
wrapperCallstackType = state.wrapperCallstackType;
keepAwake = state.keepAwake;
-
- if (keepAwake) {
- chrome.power.requestKeepAwake('display');
- }
});
runtimeListen(async (o) => {
@@ -49,6 +45,7 @@
wrapperCallstackType === EWrapperCallstackType.FULL
? EWrapperCallstackType.SHORT
: EWrapperCallstackType.FULL;
+
setSettings({
wrapperCallstackType: $state.snapshot(wrapperCallstackType),
});
From 1f5f37656e1b7ad2790db70a20df50aa413dd442 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Sun, 20 Apr 2025 21:19:39 +0300
Subject: [PATCH 13/19] fix concurrency between acquiring local and session
storage
---
src/api-monitor-cs-isolated.ts | 20 ++++++++------------
src/api-monitor-cs-main.ts | 2 +-
2 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/src/api-monitor-cs-isolated.ts b/src/api-monitor-cs-isolated.ts
index 36e5550..21d52e3 100644
--- a/src/api-monitor-cs-isolated.ts
+++ b/src/api-monitor-cs-isolated.ts
@@ -8,25 +8,21 @@ import {
import { getSettings, onSettingsChange } from './api/settings.ts';
import { getSession, onSessionChange } from './api/session.ts';
-getSettings().then((settings) => {
+Promise.all([getSettings(), getSession()]).then(([settings, session]) => {
windowPost({ msg: EMsg.SETTINGS, settings });
+ windowPost({ msg: EMsg.SESSION, session });
+
+ portListen(windowPost);
+ windowListen(runtimePost);
onSettingsChange((newValue) => {
windowPost({ msg: EMsg.SETTINGS, settings: newValue });
});
-});
-
-getSession().then((session) => {
- windowPost({ msg: EMsg.SESSION, session });
-
onSessionChange((newValue) => {
windowPost({ msg: EMsg.SESSION, session: newValue });
});
-});
-portListen(windowPost);
-windowListen(runtimePost);
+ runtimePost({ msg: EMsg.CONTENT_SCRIPT_LOADED });
-runtimePost({ msg: EMsg.CONTENT_SCRIPT_LOADED });
-
-__development__ && console.log('api-monitor-cs-isolated.ts');
+ __development__ && console.log('api-monitor-cs-isolated.ts', performance.now());
+});
diff --git a/src/api-monitor-cs-main.ts b/src/api-monitor-cs-main.ts
index 60d35bb..c02b553 100644
--- a/src/api-monitor-cs-main.ts
+++ b/src/api-monitor-cs-main.ts
@@ -78,4 +78,4 @@ windowListen((o) => {
}
});
-__development__ && console.debug('api-monitor-cs-main.ts');
+__development__ && console.debug('api-monitor-cs-main.ts', performance.now());
From d93254c8aaadb9a37a454846bdaea4cf56969ca1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Fri, 25 Apr 2025 14:06:25 +0300
Subject: [PATCH 14/19] code review
---
public/global.css | 15 +-
public/img/breakpoint.svg | 3 +
public/img/trace-webpack.svg | 5 +
src/api-monitor-cs-isolated.ts | 42 ++--
src/api-monitor-cs-main.ts | 12 +-
src/api-monitor-devtools-panel.ts | 7 +-
src/api-monitor-devtools.ts | 20 +-
src/api/communication.ts | 14 +-
src/api/comparator.ts | 2 +-
src/api/const.ts | 2 +-
src/api/{settings.ts => storage.local.ts} | 58 ++---
src/api/{session.ts => storage.session.ts} | 10 +-
src/devtoolsPanelUtil.ts | 13 ++
src/state/app.state.svelte.ts | 7 +
src/state/config.state.svelte.ts | 61 ++++++
.../session.state.svelte.ts} | 25 ++-
src/state/telemetry.state.svelte.ts | 36 +++
src/view/App.svelte | 206 ++----------------
src/view/menu/DevReload.svelte | 13 ++
src/view/menu/InfoBar.svelte | 112 ----------
src/view/menu/ResetHistory.svelte | 15 ++
src/view/menu/SummaryBar.svelte | 104 +++++++++
...foBarItem.svelte => SummaryBarItem.svelte} | 6 +-
src/view/menu/TickSpinner.svelte | 28 ---
src/view/menu/TogglePanels.svelte | 79 ++-----
src/view/menu/TogglePause.svelte | 16 ++
src/view/menu/UpdatePace.svelte | 35 +++
src/view/menu/UpdatePaceAnime.svelte | 32 +++
src/view/panel/AnimationCancelHistory.svelte | 51 ++---
src/view/panel/AnimationRequestHistory.svelte | 53 ++---
src/view/panel/Eval.svelte | 51 +++--
.../panel/IdleCallbackCancelHistory.svelte | 49 ++---
.../panel/IdleCallbackRequestHistory.svelte | 63 +++---
src/view/panel/TimersClearHistory.svelte | 48 ++--
src/view/panel/TimersSetHistory.svelte | 59 ++---
src/view/panel/components/AppPanels.svelte | 78 +++++++
.../panel/components/CallstackCell.svelte | 2 +
src/view/panel/components/FactsCell.svelte | 4 +-
.../components/FrameSensitiveTime.svelte | 2 +-
.../panel/components/SortableColumn.svelte | 2 +-
.../TimersClearHistoryMetric.svelte | 2 +-
.../components/TimersSetHistoryMetric.svelte | 2 +-
.../panel/components/TraceBreakpoint.svelte | 6 +-
src/view/panel/components/TraceBypass.svelte | 6 +-
src/view/panel/components/TraceLink.svelte | 6 +-
src/wrapper/AnimationWrapper.ts | 4 +-
src/wrapper/EvalWrapper.ts | 23 +-
src/wrapper/Fact.ts | 7 +-
src/wrapper/IdleWrapper.ts | 4 +-
src/wrapper/TimerWrapper.ts | 12 +-
src/wrapper/TraceUtil.ts | 5 +-
src/wrapper/Wrapper.ts | 38 ++--
tests/EvalWrapper_test.ts | 8 +-
tests/Facts_test.ts | 2 +-
tests/TraceUtil_test.ts | 2 +-
55 files changed, 831 insertions(+), 736 deletions(-)
create mode 100644 public/img/trace-webpack.svg
rename src/api/{settings.ts => storage.local.ts} (73%)
rename src/api/{session.ts => storage.session.ts} (80%)
create mode 100644 src/devtoolsPanelUtil.ts
create mode 100644 src/state/app.state.svelte.ts
create mode 100644 src/state/config.state.svelte.ts
rename src/{view/store/session.store.svelte.ts => state/session.state.svelte.ts} (61%)
create mode 100644 src/state/telemetry.state.svelte.ts
create mode 100644 src/view/menu/DevReload.svelte
delete mode 100644 src/view/menu/InfoBar.svelte
create mode 100644 src/view/menu/ResetHistory.svelte
create mode 100644 src/view/menu/SummaryBar.svelte
rename src/view/menu/{InfoBarItem.svelte => SummaryBarItem.svelte} (91%)
delete mode 100644 src/view/menu/TickSpinner.svelte
create mode 100644 src/view/menu/TogglePause.svelte
create mode 100644 src/view/menu/UpdatePace.svelte
create mode 100644 src/view/menu/UpdatePaceAnime.svelte
create mode 100644 src/view/panel/components/AppPanels.svelte
diff --git a/public/global.css b/public/global.css
index 19770aa..fcef669 100644
--- a/public/global.css
+++ b/public/global.css
@@ -11,7 +11,7 @@
--text-trace: light-dark(rgb(30% 30% 30%), rgb(70% 70% 70%));
--link: light-dark(rgb(0% 0% 0%), rgb(100% 100% 100%));
--link-visited-bg: rgb(79 189 36 / 0.24);
- --error: rgb(100% 0% 0%);
+ --attention: rgb(100% 0% 0%);
--small-icon-size: 0.6875rem;
}
@@ -80,6 +80,9 @@ th,
.ta-r {
text-align: right;
}
+.tc-attention {
+ color: var(--attention);
+}
.t-zebra:where(:nth-child(even)) {
background-color: var(--bg-table-even);
}
@@ -111,9 +114,10 @@ th,
}
.sticky-header {
- position: sticky;
- top: 0;
- z-index: 1;
+ /* @NOTE: unstable in Chrome v135 */
+ /*position: sticky;*/
+ /*top: 0;*/
+ /*z-index: 1;*/
height: 1rem;
vertical-align: middle;
}
@@ -188,6 +192,9 @@ th .icon {
.icon.-trace-extension {
mask-image: url(img/trace-extension.svg);
}
+.icon.-trace-webpack {
+ mask-image: url(img/trace-webpack.svg);
+}
.icon.-breakpoint {
mask-image: url(img/breakpoint.svg);
}
diff --git a/public/img/breakpoint.svg b/public/img/breakpoint.svg
index 312a1a2..9a2dbf1 100644
--- a/public/img/breakpoint.svg
+++ b/public/img/breakpoint.svg
@@ -1,4 +1,7 @@
diff --git a/public/img/trace-webpack.svg b/public/img/trace-webpack.svg
new file mode 100644
index 0000000..6ac0da0
--- /dev/null
+++ b/public/img/trace-webpack.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/api-monitor-cs-isolated.ts b/src/api-monitor-cs-isolated.ts
index 21d52e3..ef18b12 100644
--- a/src/api-monitor-cs-isolated.ts
+++ b/src/api-monitor-cs-isolated.ts
@@ -5,24 +5,34 @@ import {
windowListen,
windowPost,
} from './api/communication.ts';
-import { getSettings, onSettingsChange } from './api/settings.ts';
-import { getSession, onSessionChange } from './api/session.ts';
+import { loadLocalStorage, onLocalStorageChange } from './api/storage.local.ts';
+import {
+ loadSessionStorage,
+ onSessionStorageChange,
+} from './api/storage.session.ts';
+
+Promise.all([loadLocalStorage(), loadSessionStorage()]).then(
+ ([config, session]) => {
+ windowPost({ msg: EMsg.CONFIG, config });
+ windowPost({ msg: EMsg.SESSION, session });
-Promise.all([getSettings(), getSession()]).then(([settings, session]) => {
- windowPost({ msg: EMsg.SETTINGS, settings });
- windowPost({ msg: EMsg.SESSION, session });
+ if (config.devtoolsPanelShown && !config.paused) {
+ windowPost({ msg: EMsg.START_OBSERVE });
+ }
- portListen(windowPost);
- windowListen(runtimePost);
+ portListen(windowPost);
+ windowListen(runtimePost);
- onSettingsChange((newValue) => {
- windowPost({ msg: EMsg.SETTINGS, settings: newValue });
- });
- onSessionChange((newValue) => {
- windowPost({ msg: EMsg.SESSION, session: newValue });
- });
+ onLocalStorageChange((newValue) => {
+ windowPost({ msg: EMsg.CONFIG, config: newValue });
+ });
+ onSessionStorageChange((newValue) => {
+ windowPost({ msg: EMsg.SESSION, session: newValue });
+ });
- runtimePost({ msg: EMsg.CONTENT_SCRIPT_LOADED });
+ runtimePost({ msg: EMsg.CONTENT_SCRIPT_LOADED });
- __development__ && console.log('api-monitor-cs-isolated.ts', performance.now());
-});
+ __development__ &&
+ console.log('api-monitor-cs-isolated.ts', performance.now());
+ },
+);
diff --git a/src/api-monitor-cs-main.ts b/src/api-monitor-cs-main.ts
index c02b553..e9079ed 100644
--- a/src/api-monitor-cs-main.ts
+++ b/src/api-monitor-cs-main.ts
@@ -2,13 +2,13 @@ import { EMsg, windowListen, windowPost } from './api/communication.ts';
import { TELEMETRY_FREQUENCY_1PS } from './api/const.ts';
import { adjustTelemetryDelay, Timer } from './api/time.ts';
import {
+ applyConfig,
+ applySession,
cleanHistory,
collectMetrics,
onEachSecond,
runMediaCommand,
runTimerCommand,
- setSettings,
- setTracePoints,
type TTelemetry,
} from './wrapper/Wrapper.ts';
import diff from './api/diff.ts';
@@ -52,11 +52,9 @@ windowListen((o) => {
originalMetrics = currentMetrics;
eachSecond.isPending() && tick.start();
} else if (
- o.msg === EMsg.SETTINGS &&
- o.settings &&
- typeof o.settings === 'object'
+ o.msg === EMsg.CONFIG && o.config && typeof o.config === 'object'
) {
- setSettings(o.settings);
+ applyConfig(o.config);
} else if (o.msg === EMsg.START_OBSERVE) {
originalMetrics = currentMetrics = null;
tick.trigger();
@@ -74,7 +72,7 @@ windowListen((o) => {
} else if (o.msg === EMsg.MEDIA_COMMAND) {
runMediaCommand(o.mediaId, o.cmd, o.property);
} else if (o.msg === EMsg.SESSION) {
- setTracePoints(o.session);
+ applySession(o.session);
}
});
diff --git a/src/api-monitor-devtools-panel.ts b/src/api-monitor-devtools-panel.ts
index 9daf707..d951c71 100644
--- a/src/api-monitor-devtools-panel.ts
+++ b/src/api-monitor-devtools-panel.ts
@@ -1,4 +1,9 @@
import { mount } from 'svelte';
import App from './view/App.svelte';
+import { initConfigState } from './state/config.state.svelte.ts';
+import { onHidePanel } from './devtoolsPanelUtil.ts';
-mount(App, { target: document.body });
+initConfigState().then(() => {
+ mount(App, { target: document.body });
+ globalThis.addEventListener('beforeunload', onHidePanel);
+});
diff --git a/src/api-monitor-devtools.ts b/src/api-monitor-devtools.ts
index 7782535..bc94884 100644
--- a/src/api-monitor-devtools.ts
+++ b/src/api-monitor-devtools.ts
@@ -1,6 +1,7 @@
import { EMsg, portPost } from './api/communication.ts';
-import { getSettings, setSettings } from './api/settings.ts';
-import { enableSessionInContentScript } from './api/session.ts';
+import { loadLocalStorage, saveLocalStorage } from './api/storage.local.ts';
+import { enableSessionInContentScript } from './api/storage.session.ts';
+import { onHidePanel } from './devtoolsPanelUtil.ts';
// tabId may be null if user opened the devtools of the devtools
if (chrome.devtools.inspectedWindow.tabId !== null) {
@@ -10,20 +11,17 @@ if (chrome.devtools.inspectedWindow.tabId !== null) {
'/public/api-monitor-devtools-panel.html',
(panel) => {
panel.onShown.addListener(async () => {
- const settings = await getSettings();
- if (!settings.paused) {
+ const config = await loadLocalStorage();
+ if (!config.paused) {
portPost({ msg: EMsg.START_OBSERVE });
}
- if (settings.keepAwake) {
+ if (config.keepAwake) {
chrome.power.requestKeepAwake('display');
}
- setSettings({ devtoolsPanelShown: true });
- });
- panel.onHidden.addListener(() => {
- chrome.power.releaseKeepAwake();
- portPost({ msg: EMsg.STOP_OBSERVE });
- setSettings({ devtoolsPanelShown: false });
+ await saveLocalStorage({ devtoolsPanelShown: true });
});
+
+ panel.onHidden.addListener(onHidePanel);
},
);
diff --git a/src/api/communication.ts b/src/api/communication.ts
index 4468750..6dcc477 100644
--- a/src/api/communication.ts
+++ b/src/api/communication.ts
@@ -14,10 +14,10 @@ import { APPLICATION_NAME } from './env.ts';
import { ERRORS_IGNORED } from './const.ts';
import { ETimerType } from '../wrapper/TimerWrapper.ts';
import type { TTelemetry } from '../wrapper/Wrapper.ts';
-import type { TSettings } from './settings.ts';
+import type { TConfig } from './storage.local.ts';
import type { TMediaCommand } from '../wrapper/MediaWrapper.ts';
import type { Delta } from 'jsondiffpatch';
-import type { TSession } from './session.ts';
+import type { TSession } from './storage.session.ts';
let port: chrome.runtime.Port | null = null;
export function portPost(payload: TMsgOptions) {
@@ -92,7 +92,7 @@ function handleRuntimeMessageResponse(): void {
}
export enum EMsg {
- SETTINGS,
+ CONFIG,
CONTENT_SCRIPT_LOADED,
START_OBSERVE,
STOP_OBSERVE,
@@ -136,9 +136,9 @@ export interface IMsgTelemetryAcknowledged {
msg: EMsg.TELEMETRY_ACKNOWLEDGED;
timeOfCollection: number;
}
-export interface IMsgSettings {
- msg: EMsg.SETTINGS;
- settings: TSettings;
+export interface IMsgConfig {
+ msg: EMsg.CONFIG;
+ config: TConfig;
}
export interface IMsgMediaCommand {
msg: EMsg.MEDIA_COMMAND;
@@ -159,6 +159,6 @@ export type TMsgOptions =
| IMsgLoaded
| IMsgResetHistory
| IMsgTimerCommand
- | IMsgSettings
+ | IMsgConfig
| IMsgMediaCommand
| IMsgSession;
diff --git a/src/api/comparator.ts b/src/api/comparator.ts
index 88ce717..8ecbda2 100644
--- a/src/api/comparator.ts
+++ b/src/api/comparator.ts
@@ -1,5 +1,5 @@
import type { TOnlineTimerMetrics } from '../wrapper/TimerWrapper.ts';
-import { ESortOrder } from './settings.ts';
+import { ESortOrder } from './storage.local.ts';
const SEMISORTING_FIELDS = ['calls', 'delay', 'online'];
diff --git a/src/api/const.ts b/src/api/const.ts
index 7661418..19e95d9 100644
--- a/src/api/const.ts
+++ b/src/api/const.ts
@@ -10,7 +10,7 @@ export const FRAME_1of60 = 0.0166666666667; // ms
export const VARIABLE_ANIMATION_THROTTLE = 3500; // eye blinking average frequency
export const SELF_TIME_MAX_GOOD = 13.333333333333332; // ms
-// store native functions
+// state native functions
export const setTimeout = /*@__PURE__*/ globalThis.setTimeout.bind(globalThis);
export const clearTimeout = /*@__PURE__*/ globalThis.clearTimeout.bind(
globalThis,
diff --git a/src/api/settings.ts b/src/api/storage.local.ts
similarity index 73%
rename from src/api/settings.ts
rename to src/api/storage.local.ts
index b72c311..35f49ab 100644
--- a/src/api/settings.ts
+++ b/src/api/storage.local.ts
@@ -12,6 +12,7 @@ import type {
} from '../wrapper/TimerWrapper.ts';
type TPanelKey =
+ | 'callsSummary'
| 'eval'
| 'media'
| 'activeTimers'
@@ -24,19 +25,20 @@ type TPanelKey =
| 'requestIdleCallback'
| 'cancelIdleCallback';
export type TPanelMap = {
- [K in TPanelKey]: TSettingsPanel;
+ [K in TPanelKey]: TPanel;
};
-export type TSettingsPanel = {
+export type TPanel = {
key: TPanelKey;
label: string;
visible: boolean;
wrap: boolean | null;
};
-export type TSettings = typeof DEFAULT_SETTINGS;
-export type TSettingsProperty = Partial;
+export type TConfig = typeof DEFAULT_CONFIG;
+export type TConfigField = Partial;
-const SETTINGS_VERSION = '1.2.0';
-export const DEFAULT_PANELS: TSettingsPanel[] = [
+const CONFIG_VERSION = '2025-04-25';
+export const DEFAULT_PANELS: TPanel[] = [
+ { key: 'callsSummary', label: 'Calls Summary', visible: false, wrap: null },
{ key: 'media', label: 'Media', visible: true, wrap: null },
{ key: 'activeTimers', label: 'Active Timers', visible: true, wrap: null },
{ key: 'eval', label: 'eval', visible: true, wrap: false },
@@ -97,29 +99,29 @@ export enum ESortOrder {
export const DEFAULT_SORT_SET_TIMERS = {
field: 'calls',
order: ESortOrder.DESCENDING,
-} as const;
+};
export const DEFAULT_SORT_CLEAR_TIMERS = {
field: 'calls',
order: ESortOrder.DESCENDING,
-} as const;
+};
export const DEFAULT_SORT_RAF = {
field: 'calls',
order: ESortOrder.DESCENDING,
-} as const;
+};
export const DEFAULT_SORT_CAF = {
field: 'calls',
order: ESortOrder.DESCENDING,
-} as const;
+};
export const DEFAULT_SORT_RIC = {
field: 'calls',
order: ESortOrder.DESCENDING,
-} as const;
+};
export const DEFAULT_SORT_CIC = {
field: 'calls',
order: ESortOrder.DESCENDING,
-} as const;
+};
-export const DEFAULT_SETTINGS = {
+export const DEFAULT_CONFIG = {
panels: DEFAULT_PANELS,
sortSetTimers: DEFAULT_SORT_SET_TIMERS,
sortClearTimers: DEFAULT_SORT_CLEAR_TIMERS,
@@ -133,44 +135,44 @@ export const DEFAULT_SETTINGS = {
keepAwake: false,
};
-export function panelsArray2Map(panels: TSettingsPanel[]) {
+export function panelsArray2Map(panels: TPanel[]) {
return panels.reduce(
(acc, o) => Object.assign(acc, { [o.key]: o }),
{} as TPanelMap,
);
}
-export async function getSettings(): Promise {
- let store = await chrome.storage.local.get([SETTINGS_VERSION]);
+export async function loadLocalStorage(): Promise {
+ let store = await chrome.storage.local.get([CONFIG_VERSION]);
const isEmpty = !Object.keys(store).length;
if (isEmpty) {
- await chrome.storage.local.clear(); // rid off previous version settings
- await chrome.storage.local.set({ [SETTINGS_VERSION]: DEFAULT_SETTINGS });
- store = await chrome.storage.local.get([SETTINGS_VERSION]);
+ await chrome.storage.local.clear(); // reset previous version
+ await chrome.storage.local.set({ [CONFIG_VERSION]: DEFAULT_CONFIG });
+ store = await chrome.storage.local.get([CONFIG_VERSION]);
}
- return store[SETTINGS_VERSION];
+ return store[CONFIG_VERSION];
}
-export async function setSettings(value: TSettingsProperty) {
- const store = await chrome.storage.local.get([SETTINGS_VERSION]);
+export async function saveLocalStorage(value: TConfigField) {
+ const store = await chrome.storage.local.get([CONFIG_VERSION]);
- Object.assign(store[SETTINGS_VERSION], value);
+ Object.assign(store[CONFIG_VERSION], value);
return await chrome.storage.local.set(store);
}
-export function onSettingsChange(
- callback: (newValue: TSettings, oldValue: TSettings) => void,
+export function onLocalStorageChange(
+ callback: (newValue: TConfig, oldValue: TConfig) => void,
) {
chrome.storage.local.onChanged.addListener((change) => {
if (
- change && change[SETTINGS_VERSION] && change[SETTINGS_VERSION].newValue
+ change && change[CONFIG_VERSION] && change[CONFIG_VERSION].newValue
) {
callback(
- change[SETTINGS_VERSION].newValue,
- change[SETTINGS_VERSION].oldValue,
+ change[CONFIG_VERSION].newValue,
+ change[CONFIG_VERSION].oldValue,
);
}
});
diff --git a/src/api/session.ts b/src/api/storage.session.ts
similarity index 80%
rename from src/api/session.ts
rename to src/api/storage.session.ts
index 1aaf590..8a74cce 100644
--- a/src/api/session.ts
+++ b/src/api/storage.session.ts
@@ -1,4 +1,4 @@
-const SESSION_VERSION = '1.2.0';
+const SESSION_VERSION = '2025-04-25';
export type TSession = typeof DEFAULT_SESSION;
type TSessionProperty = Partial;
@@ -13,12 +13,12 @@ export function enableSessionInContentScript() {
});
}
-export async function getSession(): Promise {
+export async function loadSessionStorage(): Promise {
let store = await chrome.storage.session.get([SESSION_VERSION]);
const isEmpty = !Object.keys(store).length;
if (isEmpty) {
- await chrome.storage.session.clear(); // rid off previous version settings
+ await chrome.storage.session.clear(); // reset previous version
await chrome.storage.session.set({ [SESSION_VERSION]: DEFAULT_SESSION });
store = await chrome.storage.session.get([SESSION_VERSION]);
}
@@ -26,7 +26,7 @@ export async function getSession(): Promise {
return store[SESSION_VERSION];
}
-export async function setSession(value: TSessionProperty) {
+export async function saveSessionStorage(value: TSessionProperty) {
const store = await chrome.storage.session.get([SESSION_VERSION]);
Object.assign(store[SESSION_VERSION], value);
@@ -34,7 +34,7 @@ export async function setSession(value: TSessionProperty) {
return await chrome.storage.session.set(store);
}
-export function onSessionChange(
+export function onSessionStorageChange(
callback: (newValue: TSession, oldValue: TSession) => void,
) {
chrome.storage.session.onChanged.addListener((change) => {
diff --git a/src/devtoolsPanelUtil.ts b/src/devtoolsPanelUtil.ts
new file mode 100644
index 0000000..3e23cb9
--- /dev/null
+++ b/src/devtoolsPanelUtil.ts
@@ -0,0 +1,13 @@
+/**
+ * module for functions common for devtools-panel
+ * as well as for svelte component, BUT doesn't require svelte
+ * as a dependency
+ */
+import { EMsg, portPost } from './api/communication.ts';
+import { saveLocalStorage } from './api/storage.local.ts';
+
+export async function onHidePanel() {
+ chrome.power.releaseKeepAwake();
+ portPost({ msg: EMsg.STOP_OBSERVE });
+ await saveLocalStorage({ devtoolsPanelShown: false });
+}
diff --git a/src/state/app.state.svelte.ts b/src/state/app.state.svelte.ts
new file mode 100644
index 0000000..41e8d00
--- /dev/null
+++ b/src/state/app.state.svelte.ts
@@ -0,0 +1,7 @@
+const appState = $state({
+ lastTelemetryUpdate: 0,
+});
+
+export function useAppState() {
+ return appState;
+}
diff --git a/src/state/config.state.svelte.ts b/src/state/config.state.svelte.ts
new file mode 100644
index 0000000..a9e5ec8
--- /dev/null
+++ b/src/state/config.state.svelte.ts
@@ -0,0 +1,61 @@
+import { EMsg, portPost } from '../api/communication.ts';
+import {
+ DEFAULT_CONFIG,
+ EWrapperCallstackType,
+ loadLocalStorage,
+ saveLocalStorage,
+ type TConfig,
+} from '../api/storage.local.ts';
+
+let config: TConfig = $state(DEFAULT_CONFIG);
+
+export function useConfigState() {
+ return config;
+}
+
+export async function initConfigState() {
+ config = await loadLocalStorage();
+}
+
+export async function togglePause() {
+ config.paused = !config.paused;
+ await saveLocalStorage({ paused: $state.snapshot(config.paused) });
+
+ if (config.paused) {
+ portPost({ msg: EMsg.STOP_OBSERVE });
+ } else {
+ portPost({ msg: EMsg.START_OBSERVE });
+ }
+}
+
+export async function toggleKeepAwake() {
+ config.keepAwake = !config.keepAwake;
+ await saveLocalStorage({ keepAwake: $state.snapshot(config.keepAwake) });
+
+ if (config.keepAwake) {
+ chrome.power.requestKeepAwake('display');
+ } else {
+ chrome.power.releaseKeepAwake();
+ }
+}
+
+export async function toggleWrapperCallstackType() {
+ config.wrapperCallstackType =
+ config.wrapperCallstackType === EWrapperCallstackType.FULL
+ ? EWrapperCallstackType.SHORT
+ : EWrapperCallstackType.FULL;
+
+ await saveLocalStorage({
+ wrapperCallstackType: $state.snapshot(config.wrapperCallstackType),
+ });
+}
+
+export async function togglePanelWrap(index: number) {
+ config.panels[index].wrap = !config.panels[index].wrap;
+ await saveLocalStorage({ panels: $state.snapshot(config.panels) });
+}
+
+export async function togglePanelVisibility(index: number) {
+ config.panels[index].visible = !config.panels[index].visible;
+ await saveLocalStorage({ panels: $state.snapshot(config.panels) });
+}
diff --git a/src/view/store/session.store.svelte.ts b/src/state/session.state.svelte.ts
similarity index 61%
rename from src/view/store/session.store.svelte.ts
rename to src/state/session.state.svelte.ts
index c675ce0..fc2c186 100644
--- a/src/view/store/session.store.svelte.ts
+++ b/src/state/session.state.svelte.ts
@@ -1,33 +1,36 @@
-import { getSession, setSession } from '../../api/session.ts';
+import {
+ loadSessionStorage,
+ saveSessionStorage,
+} from '../api/storage.session.ts';
import { SvelteSet } from 'svelte/reactivity';
-export const sessionStore = $state({
+export const sessionState = $state({
bypass: > new SvelteSet(),
debug: > new SvelteSet(),
});
-getSession().then((session) => {
+loadSessionStorage().then((session) => {
session.bypass.forEach((traceId) => {
- sessionStore.bypass.add(traceId);
+ sessionState.bypass.add(traceId);
});
session.debug.forEach((traceId) => {
- sessionStore.debug.add(traceId);
+ sessionState.debug.add(traceId);
});
});
export async function toggleBypass(traceId: string) {
- if (await toggleSet(sessionStore.bypass, traceId)) {
- await setSession({
- bypass: Array.from(sessionStore.bypass.values()),
+ if (await toggleSet(sessionState.bypass, traceId)) {
+ await saveSessionStorage({
+ bypass: Array.from(sessionState.bypass.values()),
});
}
}
export async function toggleDebug(traceId: string) {
- if (await toggleSet(sessionStore.debug, traceId)) {
- await setSession({
- debug: Array.from(sessionStore.debug.values()),
+ if (await toggleSet(sessionState.debug, traceId)) {
+ await saveSessionStorage({
+ debug: Array.from(sessionState.debug.values()),
});
}
}
diff --git a/src/state/telemetry.state.svelte.ts b/src/state/telemetry.state.svelte.ts
new file mode 100644
index 0000000..c9da58a
--- /dev/null
+++ b/src/state/telemetry.state.svelte.ts
@@ -0,0 +1,36 @@
+import type { TTelemetry } from '../wrapper/Wrapper.ts';
+import { EMsg, portPost, runtimeListen } from '../api/communication.ts';
+import diff from '../api/diff.ts';
+import { type Writable, writable } from 'svelte/store';
+
+class TelemetryState {
+ telemetry: TTelemetry | null = $state.raw(null);
+ timeOfCollection: Writable = writable(0);
+}
+const state = new TelemetryState();
+let telemetryProgressive: TTelemetry | null = null;
+
+export function useTelemetryState() {
+ return state;
+}
+
+runtimeListen((o) => {
+ if (o.msg === EMsg.TELEMETRY) {
+ telemetryProgressive = structuredClone(o.telemetry);
+ state.telemetry = o.telemetry;
+ acknowledgeTelemetry(o.timeOfCollection);
+ } else if (o.msg === EMsg.TELEMETRY_DELTA) {
+ diff.patch(telemetryProgressive, o.telemetryDelta);
+ state.telemetry = structuredClone(telemetryProgressive);
+ acknowledgeTelemetry(o.timeOfCollection);
+ }
+});
+
+function acknowledgeTelemetry(timeOfCollection: number) {
+ portPost({
+ msg: EMsg.TELEMETRY_ACKNOWLEDGED,
+ timeOfCollection,
+ });
+
+ state.timeOfCollection.set(timeOfCollection);
+}
diff --git a/src/view/App.svelte b/src/view/App.svelte
index 8f80bf0..44d80e8 100644
--- a/src/view/App.svelte
+++ b/src/view/App.svelte
@@ -1,205 +1,41 @@
-
+
- {#if telemetry}
-
-
-
-
- {#if telemetry.setTimeoutHistory?.length}
-
- {/if}
- {#if telemetry.clearTimeoutHistory?.length}
-
- {/if}
-
- {#if telemetry.setIntervalHistory?.length}
-
- {/if}
- {#if telemetry.clearIntervalHistory?.length}
-
- {/if}
-
- {#if telemetry.rafHistory?.length}
-
- {/if}
- {#if telemetry.cafHistory?.length}
-
- {/if}
-
- {#if telemetry.ricHistory?.length}
-
- {/if}
- {#if telemetry.cicHistory?.length}
-
- {/if}
- {/if}
+
diff --git a/src/view/menu/InfoBarItem.svelte b/src/view/menu/SummaryBarItem.svelte
similarity index 91%
rename from src/view/menu/InfoBarItem.svelte
rename to src/view/menu/SummaryBarItem.svelte
index 77c059d..ad10212 100644
--- a/src/view/menu/InfoBarItem.svelte
+++ b/src/view/menu/SummaryBarItem.svelte
@@ -1,5 +1,5 @@
-
-
- {fpsValue}fps
- {frame}
-
-
-
diff --git a/src/view/menu/TogglePanels.svelte b/src/view/menu/TogglePanels.svelte
index d93b611..5e7bacc 100644
--- a/src/view/menu/TogglePanels.svelte
+++ b/src/view/menu/TogglePanels.svelte
@@ -1,75 +1,42 @@
@@ -87,7 +54,7 @@
>
{
`${
- wrapperCallstackType ===
+ config.wrapperCallstackType ===
EWrapperCallstackType.FULL
? 'full'
: 'short'
@@ -97,8 +64,8 @@
- {#each panels as panel, index (panel.key)}
-
diff --git a/src/view/menu/TogglePause.svelte b/src/view/menu/TogglePause.svelte
new file mode 100644
index 0000000..75d8049
--- /dev/null
+++ b/src/view/menu/TogglePause.svelte
@@ -0,0 +1,16 @@
+
+
+
diff --git a/src/view/menu/UpdatePace.svelte b/src/view/menu/UpdatePace.svelte
new file mode 100644
index 0000000..5d807e2
--- /dev/null
+++ b/src/view/menu/UpdatePace.svelte
@@ -0,0 +1,35 @@
+
+
+
+
+
diff --git a/src/view/menu/UpdatePaceAnime.svelte b/src/view/menu/UpdatePaceAnime.svelte
new file mode 100644
index 0000000..ef3db9b
--- /dev/null
+++ b/src/view/menu/UpdatePaceAnime.svelte
@@ -0,0 +1,32 @@
+
+
+
diff --git a/src/view/panel/AnimationCancelHistory.svelte b/src/view/panel/AnimationCancelHistory.svelte
index 038cf7f..e331a7b 100644
--- a/src/view/panel/AnimationCancelHistory.svelte
+++ b/src/view/panel/AnimationCancelHistory.svelte
@@ -3,12 +3,7 @@
CafFact,
type TCancelAnimationFrameHistory,
} from '../../wrapper/AnimationWrapper.ts';
- import {
- DEFAULT_SORT_CAF,
- ESortOrder,
- getSettings,
- setSettings,
- } from '../../api/settings.ts';
+ import { ESortOrder, saveLocalStorage } from '../../api/storage.local.ts';
import { compareByFieldOrder } from '../../api/comparator.ts';
import Variable from '../components/Variable.svelte';
import SortableColumn from './components/SortableColumn.svelte';
@@ -17,16 +12,21 @@
import type { TFactsMap } from '../../wrapper/Fact.ts';
import FactsCell from './components/FactsCell.svelte';
import CallstackCell from './components/CallstackCell.svelte';
+ import { useConfigState } from '../../state/config.state.svelte.ts';
let {
cafHistory,
caption = '',
}: { cafHistory: TCancelAnimationFrameHistory[]; caption?: string } =
$props();
- let sortField = $state(DEFAULT_SORT_CAF.field);
- let sortOrder = $state(DEFAULT_SORT_CAF.order);
+ const { sortCancelAnimationFrame } = useConfigState();
let sortedMetrics = $derived.by(() =>
- cafHistory.toSorted(compareByFieldOrder(sortField, sortOrder))
+ cafHistory.toSorted(
+ compareByFieldOrder(
+ sortCancelAnimationFrame.field,
+ sortCancelAnimationFrame.order,
+ ),
+ )
);
const CafFacts: TFactsMap = new Map([
[CafFact.NOT_FOUND, { tag: 'A', details: 'Animation not found' }],
@@ -36,20 +36,13 @@
}],
]);
- getSettings().then((settings) => {
- sortField = settings.sortCancelAnimationFrame.field;
- sortOrder = settings.sortCancelAnimationFrame.order;
- });
-
- function onChangeSort(_field: string, _order: ESortOrder) {
- sortField = _field;
- sortOrder = _order;
+ function onChangeSort(field: string, order: ESortOrder) {
+ sortCancelAnimationFrame.field =
+ field;
+ sortCancelAnimationFrame.order = order;
- setSettings({
- sortCancelAnimationFrame: {
- field: $state.snapshot(sortField),
- order: $state.snapshot(sortOrder),
- },
+ saveLocalStorage({
+ sortCancelAnimationFrame: $state.snapshot(sortCancelAnimationFrame),
});
}
@@ -63,24 +56,24 @@
|
Called
|
Handler
|
@@ -102,7 +95,7 @@
|
- |
+ {metric.handler} |
|
|
diff --git a/src/view/panel/AnimationRequestHistory.svelte b/src/view/panel/AnimationRequestHistory.svelte
index b3825e7..46a8dc5 100644
--- a/src/view/panel/AnimationRequestHistory.svelte
+++ b/src/view/panel/AnimationRequestHistory.svelte
@@ -3,12 +3,7 @@
TCancelAnimationFrameHistory,
TRequestAnimationFrameHistory,
} from '../../wrapper/AnimationWrapper.ts';
- import {
- DEFAULT_SORT_RAF,
- ESortOrder,
- getSettings,
- setSettings,
- } from '../../api/settings.ts';
+ import { ESortOrder, saveLocalStorage } from '../../api/storage.local.ts';
import { compareByFieldOrder } from '../../api/comparator.ts';
import Variable from '../components/Variable.svelte';
import SortableColumn from './components/SortableColumn.svelte';
@@ -20,6 +15,7 @@
import TraceBypass from './components/TraceBypass.svelte';
import CancelableCallMetric from './components/CancelableCallMetric.svelte';
import CallstackCell from './components/CallstackCell.svelte';
+ import { useConfigState } from '../../state/config.state.svelte.ts';
let {
rafHistory,
@@ -30,28 +26,25 @@
cafHistory: TCancelAnimationFrameHistory[] | null;
caption: string;
} = $props();
- let sortField = $state(DEFAULT_SORT_RAF.field);
- let sortOrder = $state(DEFAULT_SORT_RAF.order);
+ const { sortRequestAnimationFrame } = useConfigState();
let dialogEl: Dialog | null = null;
let alertEl: Alert | null = null;
let sortedMetrics = $derived.by(() =>
- rafHistory.toSorted(compareByFieldOrder(sortField, sortOrder))
+ rafHistory.toSorted(
+ compareByFieldOrder(
+ sortRequestAnimationFrame.field,
+ sortRequestAnimationFrame.order,
+ ),
+ )
);
- getSettings().then((settings) => {
- sortField = settings.sortRequestAnimationFrame.field;
- sortOrder = settings.sortRequestAnimationFrame.order;
- });
-
function onChangeSort(_field: string, _order: ESortOrder) {
- sortField = _field;
- sortOrder = _order;
+ sortRequestAnimationFrame.field =
+ _field;
+ sortRequestAnimationFrame.order = _order;
- setSettings({
- sortRequestAnimationFrame: {
- field: $state.snapshot(sortField),
- order: $state.snapshot(sortOrder),
- },
+ saveLocalStorage({
+ sortRequestAnimationFrame: $state.snapshot(sortRequestAnimationFrame),
});
}
@@ -107,8 +100,8 @@
Self
|
@@ -116,24 +109,24 @@
Called
|
Handler
|
Set
|
@@ -161,7 +154,7 @@
onClick={onFindRegressors}
/>
- |
+ {metric.handler} |
{#if metric.online}
diff --git a/src/view/panel/Eval.svelte b/src/view/panel/Eval.svelte
index ee4087f..18803f4 100644
--- a/src/view/panel/Eval.svelte
+++ b/src/view/panel/Eval.svelte
@@ -1,12 +1,35 @@
@@ -61,24 +56,24 @@
|
|
Called
|
Handler
|
@@ -100,7 +95,7 @@
|
- |
+ {metric.handler} |
|
|
diff --git a/src/view/panel/IdleCallbackRequestHistory.svelte b/src/view/panel/IdleCallbackRequestHistory.svelte
index 8d0aa38..b8c65a6 100644
--- a/src/view/panel/IdleCallbackRequestHistory.svelte
+++ b/src/view/panel/IdleCallbackRequestHistory.svelte
@@ -4,12 +4,7 @@
type TCancelIdleCallbackHistory,
type TRequestIdleCallbackHistory,
} from '../../wrapper/IdleWrapper.ts';
- import {
- DEFAULT_SORT_RIC,
- ESortOrder,
- getSettings,
- setSettings,
- } from '../../api/settings.ts';
+ import { ESortOrder, saveLocalStorage } from '../../api/storage.local.ts';
import { compareByFieldOrder } from '../../api/comparator.ts';
import { msToHms } from '../../api/time.ts';
import Variable from '../components/Variable.svelte';
@@ -24,6 +19,7 @@
import type { TFactsMap } from '../../wrapper/Fact.ts';
import FactsCell from './components/FactsCell.svelte';
import CallstackCell from './components/CallstackCell.svelte';
+ import { useConfigState } from '../../state/config.state.svelte.ts';
let {
ricHistory,
@@ -34,12 +30,16 @@
cicHistory: TCancelIdleCallbackHistory[] | null;
caption: string;
} = $props();
- let sortField = $state(DEFAULT_SORT_RIC.field);
- let sortOrder = $state(DEFAULT_SORT_RIC.order);
let dialogEl: Dialog | null = null;
let alertEl: Alert | null = null;
+ const { sortRequestIdleCallback } = useConfigState();
let sortedMetrics = $derived.by(() =>
- ricHistory.toSorted(compareByFieldOrder(sortField, sortOrder))
+ ricHistory.toSorted(
+ compareByFieldOrder(
+ sortRequestIdleCallback.field,
+ sortRequestIdleCallback.order,
+ ),
+ )
);
const RicFacts: TFactsMap = new Map([
[RicFact.BAD_DELAY, {
@@ -48,20 +48,13 @@
}],
]);
- getSettings().then((settings) => {
- sortField = settings.sortRequestIdleCallback.field;
- sortOrder = settings.sortRequestIdleCallback.order;
- });
-
- function onChangeSort(_field: string, _order: ESortOrder) {
- sortField = _field;
- sortOrder = _order;
+ function onChangeSort(field: string, order: ESortOrder) {
+ sortRequestIdleCallback.field =
+ field;
+ sortRequestIdleCallback.order = order;
- setSettings({
- sortRequestIdleCallback: {
- field: $state.snapshot(sortField),
- order: $state.snapshot(sortOrder),
- },
+ saveLocalStorage({
+ sortRequestIdleCallback: $state.snapshot(sortRequestIdleCallback),
});
}
@@ -118,48 +111,48 @@
Self
|
|
Called
|
Handler
|
Delay
|
Set
|
@@ -190,7 +183,7 @@
onClick={onFindRegressors}
/>
- |
+ {metric.handler} |
{metric.delay} |
{#if metric.online}
diff --git a/src/view/panel/TimersClearHistory.svelte b/src/view/panel/TimersClearHistory.svelte
index d02ac9b..5fd68f7 100644
--- a/src/view/panel/TimersClearHistory.svelte
+++ b/src/view/panel/TimersClearHistory.svelte
@@ -1,41 +1,33 @@
@@ -49,32 +41,32 @@
|
|
Called
|
Handler
|
Delay
|
diff --git a/src/view/panel/TimersSetHistory.svelte b/src/view/panel/TimersSetHistory.svelte
index 4932cb9..b8db023 100644
--- a/src/view/panel/TimersSetHistory.svelte
+++ b/src/view/panel/TimersSetHistory.svelte
@@ -3,16 +3,12 @@
TClearTimerHistory,
TSetTimerHistory,
} from '../../wrapper/TimerWrapper.ts';
- import {
- DEFAULT_SORT_SET_TIMERS,
- ESortOrder,
- getSettings,
- setSettings,
- } from '../../api/settings.ts';
+ import { ESortOrder, saveLocalStorage } from '../../api/storage.local.ts';
import { compareByFieldOrder } from '../../api/comparator.ts';
import Variable from '../components/Variable.svelte';
import SortableColumn from './components/SortableColumn.svelte';
import TimersSetHistoryMetric from './components/TimersSetHistoryMetric.svelte';
+ import { useConfigState } from '../../state/config.state.svelte.ts';
let {
setTimerHistory,
@@ -25,26 +21,19 @@
clearIntervalHistory: TClearTimerHistory[] | null;
caption?: string;
} = $props();
- let sortField = $state(DEFAULT_SORT_SET_TIMERS.field);
- let sortOrder = $state(DEFAULT_SORT_SET_TIMERS.order);
- let sortedMetrics = $derived.by(() =>
- setTimerHistory.toSorted(compareByFieldOrder(sortField, sortOrder))
+ let { sortSetTimers } = useConfigState();
+ const sortedMetrics = $derived.by(() =>
+ setTimerHistory.toSorted(
+ compareByFieldOrder(sortSetTimers.field, sortSetTimers.order),
+ )
);
- getSettings().then((settings) => {
- sortField = settings.sortSetTimers.field;
- sortOrder = settings.sortSetTimers.order;
- });
+ function onChangeSort(field: string, order: ESortOrder) {
+ sortSetTimers.field = field;
+ sortSetTimers.order = order;
- function onChangeSort(_field: string, _order: ESortOrder) {
- sortField = _field;
- sortOrder = _order;
-
- setSettings({
- sortSetTimers: {
- field: $state.snapshot(sortField),
- order: $state.snapshot(sortOrder),
- },
+ saveLocalStorage({
+ sortSetTimers: $state.snapshot(sortSetTimers),
});
}
@@ -58,48 +47,48 @@
Self
|
|
Called
|
Handler
|
Delay
|
Set
|
diff --git a/src/view/panel/components/AppPanels.svelte b/src/view/panel/components/AppPanels.svelte
new file mode 100644
index 0000000..0f3b75a
--- /dev/null
+++ b/src/view/panel/components/AppPanels.svelte
@@ -0,0 +1,78 @@
+
+
+{#if ts.telemetry}
+
+
+
+
+ {#if ts.telemetry.setTimeoutHistory?.length}
+
+ {/if}
+ {#if ts.telemetry.clearTimeoutHistory?.length}
+
+ {/if}
+
+ {#if ts.telemetry.setIntervalHistory?.length}
+
+ {/if}
+ {#if ts.telemetry.clearIntervalHistory?.length}
+
+ {/if}
+
+ {#if ts.telemetry.rafHistory?.length}
+
+ {/if}
+ {#if ts.telemetry.cafHistory?.length}
+
+ {/if}
+
+ {#if ts.telemetry.ricHistory?.length}
+
+ {/if}
+ {#if ts.telemetry.cicHistory?.length}
+
+ {/if}
+{/if}
diff --git a/src/view/panel/components/CallstackCell.svelte b/src/view/panel/components/CallstackCell.svelte
index 486061e..3c74216 100644
--- a/src/view/panel/components/CallstackCell.svelte
+++ b/src/view/panel/components/CallstackCell.svelte
@@ -17,6 +17,8 @@
{:else if traceDomain === ETraceDomain.EXTENSION}
+{:else if traceDomain === ETraceDomain.WEBPACK}
+
{:else if traceDomain === ETraceDomain.UNKNOWN}
❓
{/if}
diff --git a/src/view/panel/components/FactsCell.svelte b/src/view/panel/components/FactsCell.svelte
index 319244a..190de74 100644
--- a/src/view/panel/components/FactsCell.svelte
+++ b/src/view/panel/components/FactsCell.svelte
@@ -10,7 +10,7 @@
{Fact.getTags(facts, factsMap)}
@@ -18,6 +18,6 @@
diff --git a/src/view/panel/components/FrameSensitiveTime.svelte b/src/view/panel/components/FrameSensitiveTime.svelte
index e7995b4..ed9e220 100644
--- a/src/view/panel/components/FrameSensitiveTime.svelte
+++ b/src/view/panel/components/FrameSensitiveTime.svelte
@@ -14,7 +14,7 @@
diff --git a/src/view/panel/components/SortableColumn.svelte b/src/view/panel/components/SortableColumn.svelte
index db27bb1..4053dbb 100644
--- a/src/view/panel/components/SortableColumn.svelte
+++ b/src/view/panel/components/SortableColumn.svelte
@@ -1,6 +1,6 @@
diff --git a/src/view/menu/UpdatePaceAnime.svelte b/src/view/menu/UpdatePaceAnime.svelte
deleted file mode 100644
index ef3db9b..0000000
--- a/src/view/menu/UpdatePaceAnime.svelte
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
diff --git a/src/view/menu/UpdatePaceTimeMap.ts b/src/view/menu/UpdatePaceTimeMap.ts
new file mode 100644
index 0000000..da17780
--- /dev/null
+++ b/src/view/menu/UpdatePaceTimeMap.ts
@@ -0,0 +1,116 @@
+import { deg2rad, Point, Vector } from '../../api/canvas.ts';
+
+interface IMemo {
+ when: number;
+ vector: Vector;
+ age: number;
+}
+
+let raf = 0;
+let ctx: CanvasRenderingContext2D;
+const R = 40;
+const D = 2 * R;
+const pRotationAxis = new Point(R, R);
+const WHITE = 'rgb(100% 100% 100%)';
+const BLACK = 'rgb(0% 0% 0%)';
+const ANIMATION_DURATION = 2e3;
+let primaryColour: string = BLACK;
+let shadowColour: string = WHITE;
+const memory: IMemo[] = [];
+
+export function startAnimation(ctx: CanvasRenderingContext2D) {
+ initContext(ctx);
+ raf = requestAnimationFrame(draw);
+
+ return function stopAnimation() {
+ if (raf) {
+ cancelAnimationFrame(raf);
+ raf = 0;
+ }
+ };
+}
+
+export function update() {
+ const when = Date.now();
+ const angle = -(when % 1000) * 360 / 1000;
+ const vector = new Vector(R, 0).rotate(deg2rad(angle));
+
+ memory.unshift({
+ when,
+ vector,
+ age: 0,
+ });
+}
+
+function initContext(_ctx: CanvasRenderingContext2D) {
+ ctx = _ctx;
+ ctx.canvas.width = D;
+ ctx.canvas.height = D;
+ ctx.lineCap = 'round';
+ ctx.lineWidth = 4;
+ ctx.shadowBlur = 4;
+
+ onColourSchemeChange((scheme) => {
+ primaryColour = scheme === 'dark' ? WHITE : BLACK;
+ shadowColour = scheme === 'dark' ? BLACK : WHITE;
+ ctx.strokeStyle = primaryColour;
+ ctx.shadowColor = shadowColour;
+ });
+}
+
+function draw() {
+ const drawTime = Date.now();
+ ctx.clearRect(0, 0, D, D);
+
+ for (let n = 0, N = memory.length; n < N; n++) {
+ const memo = memory[n];
+ memo.age = drawTime - memo.when;
+ drawLine(memo);
+ }
+
+ let n = memory.length;
+ while (n--) {
+ if (memory[n].age >= ANIMATION_DURATION) {
+ memory.pop();
+ } else {
+ break;
+ }
+ }
+
+ raf = requestAnimationFrame(draw);
+}
+
+function drawLine(memo: IMemo) {
+ const length = R - memo.age * R / ANIMATION_DURATION;
+ const p = memo.vector.setLength(length).toPoint(pRotationAxis);
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(pRotationAxis.x, pRotationAxis.y);
+ ctx.lineTo(p.x, p.y);
+ ctx.stroke();
+ ctx.closePath();
+ ctx.restore();
+}
+
+type TColourScheme = 'light' | 'dark';
+
+/**
+ * NOTE: if OS is dark but devtools is default - then scheme is dark
+ */
+export function onColourSchemeChange(
+ callback: (scheme: TColourScheme) => void,
+) {
+ const devtoolsScheme = chrome.devtools.panels.themeName;
+ const osDarkScheme = globalThis.matchMedia('(prefers-color-scheme: dark)');
+
+ if (devtoolsScheme === 'dark' || osDarkScheme.matches) {
+ callback('dark');
+ } else {
+ callback('light');
+ }
+
+ osDarkScheme.addEventListener('change', (e: MediaQueryListEvent) => {
+ callback(e.matches ? 'dark' : 'light');
+ });
+}
diff --git a/tests/canvas_test.ts b/tests/canvas_test.ts
new file mode 100644
index 0000000..a1126a6
--- /dev/null
+++ b/tests/canvas_test.ts
@@ -0,0 +1,119 @@
+import { describe, test } from '@std/testing/bdd';
+import { expect } from '@std/expect';
+import {
+ Box,
+ deg2rad,
+ fround,
+ PId2,
+ Point,
+ rad2deg,
+ Vector,
+} from '../src/api/canvas.ts';
+
+describe('Point', () => {
+ test('toVector', () => {
+ const pBase = new Point(6, 3);
+ const pDirection = new Point(8, 1);
+ const v = pBase.toVector(pDirection);
+
+ expect(v.clone().equalXY(2, 2)).toBe(true);
+ });
+
+ test('rotate', () => {
+ const p = new Point(2, 2);
+ const base = new Point(4, 4);
+ const rotated = p.rotate(PId2, base);
+
+ expect(rotated.clone().equalXY(2, 6)).toBe(true);
+ });
+});
+
+describe('Vector', () => {
+ test('toPoint', () => {
+ const v = new Vector(2, -2);
+ const pBase = new Point(4, 4);
+ const p = v.toPoint(pBase);
+
+ expect(p.equalXY(6, 6)).toBe(true);
+ });
+
+ test('setLength', () => {
+ const v = new Vector(-2, -2);
+ const v2 = v.setLength(5);
+
+ // same angle
+ expect(fround(v.xAxisAngle() - v2.xAxisAngle())).toBe(0);
+ // same length
+ expect(fround(v2.clone().length())).toBe(5);
+ });
+
+ test('half', () => {
+ const v = new Vector(2, 4);
+
+ expect(v.half().length()).toBe(v.length() / 2);
+ });
+
+ test('angleOX', () => {
+ const v = new Vector(1, -1);
+ const angle = v.xAxisAngle();
+
+ expect(angle).toBe(5.497787143782138);
+ expect(rad2deg(angle)).toBe(315);
+ });
+
+ test('angle', () => {
+ const v1 = new Vector(1, 1);
+ const v2 = new Vector(-1, -1);
+ const angle = v1.angle(v2);
+
+ expect(fround(angle)).toBe(fround(Math.PI));
+ });
+
+ describe('mirror', () => {
+ const v1 = new Vector(-5, -1);
+ const vAxis = new Vector(-4, 4);
+ const v2 = v1.mirror(vAxis).round();
+
+ test('clockwise', () => {
+ expect(v2.equalXY(1, 5)).toBe(true);
+ });
+
+ test('counterclockwise', () => {
+ const v3 = v2.mirror(vAxis).round();
+
+ expect(v3.equal(v1)).toBe(true);
+ });
+ });
+});
+
+describe('Box', () => {
+ const box = new Box(new Point(0, 0), 10, 10);
+
+ test('proximity', () => {
+ expect(box.clone().c.proximity(new Point(20, 5))).toBe(15);
+ });
+
+ test('contains', () => {
+ expect(box.contains(new Point(5, 5))).toBe(true);
+ });
+});
+
+describe('module exports', () => {
+ test('fround', () => {
+ expect(fround(1.33333333333)).toBe(1.333333);
+ expect(fround(1.33333333333, 1e3)).toBe(1.333);
+ });
+
+ test('rad2deg', () => {
+ const v = new Vector(0, 0);
+
+ expect(rad2deg(v.set(1, 1).xAxisAngle())).toBe(45);
+ expect(rad2deg(v.set(-1, 1).xAxisAngle())).toBe(135);
+ expect(rad2deg(v.set(-1, -1).xAxisAngle())).toBe(225);
+ expect(rad2deg(v.set(1, -1).xAxisAngle())).toBe(315);
+ });
+
+ test('deg2rad', () => {
+ expect(rad2deg(deg2rad(81))).toBe(81);
+ });
+});
From 5486ed4d07f581f6c79374ea38906908ccdc1247 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Sun, 27 Apr 2025 22:04:23 +0300
Subject: [PATCH 16/19] update deps
---
README.md | 4 +-
deno.lock | 166 +++++++++++++++++++++++++--------------------------
package.json | 12 ++--
3 files changed, 91 insertions(+), 91 deletions(-)
diff --git a/README.md b/README.md
index ad5db55..fb96f2b 100644
--- a/README.md
+++ b/README.md
@@ -48,8 +48,8 @@ If you're web developer and want to assess implementation correctness - this too
### Build requirements
- OS: Linux
-- Node: 22.12.0 (LTS)
-- [Deno](https://docs.deno.com/runtime/getting_started/installation/) 2.2.8
+- Node: 22.14.0 (LTS)
+- [Deno](https://docs.deno.com/runtime/getting_started/installation/) 2.2.12
### Build instructions
diff --git a/deno.lock b/deno.lock
index 0e679d9..f0fdc3a 100644
--- a/deno.lock
+++ b/deno.lock
@@ -13,18 +13,18 @@
"jsr:@std/path@^1.0.6": "1.0.8",
"jsr:@std/path@^1.0.8": "1.0.8",
"jsr:@std/testing@^1.0.11": "1.0.11",
- "npm:@noble/hashes@1.7.1": "1.7.1",
- "npm:@types/chrome@*": "0.0.313",
- "npm:@types/chrome@0.0.313": "0.0.313",
+ "npm:@noble/hashes@1.8.0": "1.8.0",
+ "npm:@types/chrome@*": "0.0.317",
+ "npm:@types/chrome@0.0.317": "0.0.317",
"npm:@types/deno@2.2.0": "2.2.0",
- "npm:esbuild-svelte@0.9.2": "0.9.2_esbuild@0.25.2_svelte@5.25.7__acorn@8.14.1",
+ "npm:esbuild-svelte@0.9.2": "0.9.2_esbuild@0.25.3_svelte@5.28.2__acorn@8.14.1",
"npm:happy-dom@17.4.4": "17.4.4",
"npm:jsondiffpatch@0.7.3": "0.7.3",
- "npm:sass@1.86.3": "1.86.3",
- "npm:sv@0.8.0": "0.8.0",
- "npm:svelte-check@4.1.5": "4.1.5_svelte@5.25.7__acorn@8.14.1_typescript@5.8.3",
- "npm:svelte-preprocess@6.0.3": "6.0.3_sass@1.86.3_svelte@5.25.7__acorn@8.14.1_typescript@5.8.3",
- "npm:svelte@5.25.7": "5.25.7_acorn@8.14.1",
+ "npm:sass@1.87.0": "1.87.0",
+ "npm:sv@0.8.3": "0.8.3",
+ "npm:svelte-check@4.1.6": "4.1.6_svelte@5.28.2__acorn@8.14.1_typescript@5.8.3",
+ "npm:svelte-preprocess@6.0.3": "6.0.3_sass@1.87.0_svelte@5.28.2__acorn@8.14.1_typescript@5.8.3",
+ "npm:svelte@5.28.2": "5.28.2_acorn@8.14.1",
"npm:typescript@5.8.3": "5.8.3"
},
"jsr": {
@@ -96,80 +96,80 @@
"@dmsnell/diff-match-patch@1.1.0": {
"integrity": "sha512-yejLPmM5pjsGvxS9gXablUSbInW7H976c/FJ4iQxWIm7/38xBySRemTPDe34lhg1gVLbJntX0+sH0jYfU+PN9A=="
},
- "@esbuild/aix-ppc64@0.25.2": {
- "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="
+ "@esbuild/aix-ppc64@0.25.3": {
+ "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ=="
},
- "@esbuild/android-arm64@0.25.2": {
- "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="
+ "@esbuild/android-arm64@0.25.3": {
+ "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ=="
},
- "@esbuild/android-arm@0.25.2": {
- "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="
+ "@esbuild/android-arm@0.25.3": {
+ "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A=="
},
- "@esbuild/android-x64@0.25.2": {
- "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="
+ "@esbuild/android-x64@0.25.3": {
+ "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ=="
},
- "@esbuild/darwin-arm64@0.25.2": {
- "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="
+ "@esbuild/darwin-arm64@0.25.3": {
+ "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w=="
},
- "@esbuild/darwin-x64@0.25.2": {
- "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="
+ "@esbuild/darwin-x64@0.25.3": {
+ "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A=="
},
- "@esbuild/freebsd-arm64@0.25.2": {
- "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="
+ "@esbuild/freebsd-arm64@0.25.3": {
+ "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw=="
},
- "@esbuild/freebsd-x64@0.25.2": {
- "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="
+ "@esbuild/freebsd-x64@0.25.3": {
+ "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q=="
},
- "@esbuild/linux-arm64@0.25.2": {
- "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="
+ "@esbuild/linux-arm64@0.25.3": {
+ "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A=="
},
- "@esbuild/linux-arm@0.25.2": {
- "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="
+ "@esbuild/linux-arm@0.25.3": {
+ "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ=="
},
- "@esbuild/linux-ia32@0.25.2": {
- "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="
+ "@esbuild/linux-ia32@0.25.3": {
+ "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw=="
},
- "@esbuild/linux-loong64@0.25.2": {
- "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="
+ "@esbuild/linux-loong64@0.25.3": {
+ "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g=="
},
- "@esbuild/linux-mips64el@0.25.2": {
- "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="
+ "@esbuild/linux-mips64el@0.25.3": {
+ "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag=="
},
- "@esbuild/linux-ppc64@0.25.2": {
- "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="
+ "@esbuild/linux-ppc64@0.25.3": {
+ "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg=="
},
- "@esbuild/linux-riscv64@0.25.2": {
- "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="
+ "@esbuild/linux-riscv64@0.25.3": {
+ "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA=="
},
- "@esbuild/linux-s390x@0.25.2": {
- "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="
+ "@esbuild/linux-s390x@0.25.3": {
+ "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ=="
},
- "@esbuild/linux-x64@0.25.2": {
- "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="
+ "@esbuild/linux-x64@0.25.3": {
+ "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA=="
},
- "@esbuild/netbsd-arm64@0.25.2": {
- "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="
+ "@esbuild/netbsd-arm64@0.25.3": {
+ "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA=="
},
- "@esbuild/netbsd-x64@0.25.2": {
- "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="
+ "@esbuild/netbsd-x64@0.25.3": {
+ "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g=="
},
- "@esbuild/openbsd-arm64@0.25.2": {
- "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="
+ "@esbuild/openbsd-arm64@0.25.3": {
+ "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ=="
},
- "@esbuild/openbsd-x64@0.25.2": {
- "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="
+ "@esbuild/openbsd-x64@0.25.3": {
+ "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w=="
},
- "@esbuild/sunos-x64@0.25.2": {
- "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="
+ "@esbuild/sunos-x64@0.25.3": {
+ "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA=="
},
- "@esbuild/win32-arm64@0.25.2": {
- "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="
+ "@esbuild/win32-arm64@0.25.3": {
+ "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ=="
},
- "@esbuild/win32-ia32@0.25.2": {
- "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="
+ "@esbuild/win32-ia32@0.25.3": {
+ "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew=="
},
- "@esbuild/win32-x64@0.25.2": {
- "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="
+ "@esbuild/win32-x64@0.25.3": {
+ "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg=="
},
"@jridgewell/gen-mapping@0.3.8": {
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
@@ -195,8 +195,8 @@
"@jridgewell/sourcemap-codec"
]
},
- "@noble/hashes@1.7.1": {
- "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="
+ "@noble/hashes@1.8.0": {
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="
},
"@parcel/watcher-android-arm64@2.5.1": {
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="
@@ -265,8 +265,8 @@
"acorn"
]
},
- "@types/chrome@0.0.313": {
- "integrity": "sha512-9R5T7gTaYZhkxlu+Ho4wk9FL+y/werWQY2yjGWSqCuiTsqS7nL/BE5UMTP6rU7J+oIG2FRKqrEycHhJATeltVA==",
+ "@types/chrome@0.0.317": {
+ "integrity": "sha512-ibKycbXX8ZZToFshjgWg98BTvFUSvQht8m53Xc+87ye3Z6ZoHJubLjoiDsil8rtW+noWE+Z0+7y0nwLxArU+CQ==",
"dependencies": [
"@types/filesystem",
"@types/har-format"
@@ -317,7 +317,7 @@
"detect-libc@1.0.3": {
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="
},
- "esbuild-svelte@0.9.2_esbuild@0.25.2_svelte@5.25.7__acorn@8.14.1": {
+ "esbuild-svelte@0.9.2_esbuild@0.25.3_svelte@5.28.2__acorn@8.14.1": {
"integrity": "sha512-8Jq6+rh+g1E2mkBOZKdYZ8JtlbtDq2Fydwvn+/cBvUX9S0cdKv6AISZcEbErKQ0TpLC/Cv04l1vKaqXOBO8+VQ==",
"dependencies": [
"@jridgewell/trace-mapping",
@@ -325,8 +325,8 @@
"svelte"
]
},
- "esbuild@0.25.2": {
- "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
+ "esbuild@0.25.3": {
+ "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
"dependencies": [
"@esbuild/aix-ppc64",
"@esbuild/android-arm",
@@ -364,8 +364,8 @@
"@jridgewell/sourcemap-codec"
]
},
- "fdir@6.4.3": {
- "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="
+ "fdir@6.4.4": {
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="
},
"fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
@@ -444,8 +444,8 @@
"mri"
]
},
- "sass@1.86.3": {
- "integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==",
+ "sass@1.87.0": {
+ "integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==",
"dependencies": [
"@parcel/watcher",
"chokidar",
@@ -456,11 +456,11 @@
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
- "sv@0.8.0": {
- "integrity": "sha512-nWTFtF3Q8hDrQJB1wko50z1dwUgmjhXgyJFTV0Z6dkzGjSPTHM56evKoJaHar0VfF+ljCj5a8kpSC2uwxNT/Jw=="
+ "sv@0.8.3": {
+ "integrity": "sha512-y/RIbFUowsykbShu8rJnxILieNtV1yduSN6dPhmCqNa+oMDSroRtjXBWyZe8MoZetAPN+m1tpvW1aA/CJNmIMw=="
},
- "svelte-check@4.1.5_svelte@5.25.7__acorn@8.14.1_typescript@5.8.3": {
- "integrity": "sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==",
+ "svelte-check@4.1.6_svelte@5.28.2__acorn@8.14.1_typescript@5.8.3": {
+ "integrity": "sha512-P7w/6tdSfk3zEVvfsgrp3h3DFC75jCdZjTQvgGJtjPORs1n7/v2VMPIoty3PWv7jnfEm3x0G/p9wH4pecTb0Wg==",
"dependencies": [
"@jridgewell/trace-mapping",
"chokidar",
@@ -471,7 +471,7 @@
"typescript"
]
},
- "svelte-preprocess@6.0.3_sass@1.86.3_svelte@5.25.7__acorn@8.14.1_typescript@5.8.3": {
+ "svelte-preprocess@6.0.3_sass@1.87.0_svelte@5.28.2__acorn@8.14.1_typescript@5.8.3": {
"integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==",
"dependencies": [
"sass",
@@ -479,8 +479,8 @@
"typescript"
]
},
- "svelte@5.25.7_acorn@8.14.1": {
- "integrity": "sha512-0fzXbXaKfSvFUs6Wxev2h4CoEhexZotbTF9EJ4+Cg7MHW64ZnZ9+xUedZyEpgj0Tt9HrYGv9aASHkqjn9b/cPw==",
+ "svelte@5.28.2_acorn@8.14.1": {
+ "integrity": "sha512-FbWBxgWOpQfhKvoGJv/TFwzqb4EhJbwCD17dB0tEpQiw1XyUEKZJtgm4nA4xq3LLsMo7hu5UY/BOFmroAxKTMg==",
"dependencies": [
"@ampproject/remapping",
"@jridgewell/sourcemap-codec",
@@ -535,14 +535,14 @@
],
"packageJson": {
"dependencies": [
- "npm:@noble/hashes@1.7.1",
- "npm:@types/chrome@0.0.313",
+ "npm:@noble/hashes@1.8.0",
+ "npm:@types/chrome@0.0.317",
"npm:@types/deno@2.2.0",
"npm:jsondiffpatch@0.7.3",
- "npm:sass@1.86.3",
- "npm:sv@0.8.0",
- "npm:svelte-check@4.1.5",
- "npm:svelte@5.25.7",
+ "npm:sass@1.87.0",
+ "npm:sv@0.8.3",
+ "npm:svelte-check@4.1.6",
+ "npm:svelte@5.28.2",
"npm:typescript@5.8.3"
]
}
diff --git a/package.json b/package.json
index f12f5a6..0297f21 100644
--- a/package.json
+++ b/package.json
@@ -1,16 +1,16 @@
{
"type": "module",
"devDependencies": {
- "@types/chrome": "0.0.313",
+ "@types/chrome": "0.0.317",
"@types/deno": "2.2.0",
- "sass": "1.86.3",
- "sv": "0.8.0",
- "svelte": "5.25.7",
- "svelte-check": "4.1.5",
+ "sass": "1.87.0",
+ "sv": "0.8.3",
+ "svelte": "5.28.2",
+ "svelte-check": "4.1.6",
"typescript": "5.8.3"
},
"dependencies": {
- "@noble/hashes": "1.7.1",
+ "@noble/hashes": "1.8.0",
"jsondiffpatch": "0.7.3"
}
}
From 505c35eb209a342a512e879ce77bde0f316f8029 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Sun, 27 Apr 2025 22:53:53 +0300
Subject: [PATCH 17/19] optimize scaling factor of canvas animation
---
src/view/menu/UpdatePace.svelte | 4 +--
src/view/menu/UpdatePaceTimeMap.ts | 48 +++++++++++++++++++-----------
2 files changed, 32 insertions(+), 20 deletions(-)
diff --git a/src/view/menu/UpdatePace.svelte b/src/view/menu/UpdatePace.svelte
index 436564e..0081ab3 100644
--- a/src/view/menu/UpdatePace.svelte
+++ b/src/view/menu/UpdatePace.svelte
@@ -34,8 +34,8 @@
canvas {
border-radius: 50%;
// downscaled
- width: 1.5rem;
- height: 1.5rem;
+ width: 20px;
+ height: 20px;
}
}
diff --git a/src/view/menu/UpdatePaceTimeMap.ts b/src/view/menu/UpdatePaceTimeMap.ts
index da17780..40b4bd3 100644
--- a/src/view/menu/UpdatePaceTimeMap.ts
+++ b/src/view/menu/UpdatePaceTimeMap.ts
@@ -1,4 +1,4 @@
-import { deg2rad, Point, Vector } from '../../api/canvas.ts';
+import { deg2rad, PI2, Point, Vector } from '../../api/canvas.ts';
interface IMemo {
when: number;
@@ -8,8 +8,10 @@ interface IMemo {
let raf = 0;
let ctx: CanvasRenderingContext2D;
-const R = 40;
+const R = 20;
const D = 2 * R;
+const LINE_WIDTH = 4;
+const SHADOW_WIDTH = 4;
const pRotationAxis = new Point(R, R);
const WHITE = 'rgb(100% 100% 100%)';
const BLACK = 'rgb(0% 0% 0%)';
@@ -47,8 +49,8 @@ function initContext(_ctx: CanvasRenderingContext2D) {
ctx.canvas.width = D;
ctx.canvas.height = D;
ctx.lineCap = 'round';
- ctx.lineWidth = 4;
- ctx.shadowBlur = 4;
+ ctx.lineWidth = LINE_WIDTH;
+ ctx.shadowBlur = SHADOW_WIDTH;
onColourSchemeChange((scheme) => {
primaryColour = scheme === 'dark' ? WHITE : BLACK;
@@ -62,24 +64,37 @@ function draw() {
const drawTime = Date.now();
ctx.clearRect(0, 0, D, D);
- for (let n = 0, N = memory.length; n < N; n++) {
- const memo = memory[n];
- memo.age = drawTime - memo.when;
- drawLine(memo);
- }
+ if (memory.length) {
+ for (let n = 0, N = memory.length; n < N; n++) {
+ const memo = memory[n];
+ memo.age = drawTime - memo.when;
+ drawLine(memo);
+ }
- let n = memory.length;
- while (n--) {
- if (memory[n].age >= ANIMATION_DURATION) {
- memory.pop();
- } else {
- break;
+ let n = memory.length;
+ while (n--) {
+ if (memory[n].age >= ANIMATION_DURATION) {
+ memory.pop();
+ } else {
+ break;
+ }
}
+ } else {
+ drawCenter();
}
raf = requestAnimationFrame(draw);
}
+function drawCenter() {
+ ctx.save();
+ ctx.beginPath();
+ ctx.arc(pRotationAxis.x, pRotationAxis.y, 0.5, 0, PI2);
+ ctx.stroke();
+ ctx.closePath();
+ ctx.restore();
+}
+
function drawLine(memo: IMemo) {
const length = R - memo.age * R / ANIMATION_DURATION;
const p = memo.vector.setLength(length).toPoint(pRotationAxis);
@@ -95,9 +110,6 @@ function drawLine(memo: IMemo) {
type TColourScheme = 'light' | 'dark';
-/**
- * NOTE: if OS is dark but devtools is default - then scheme is dark
- */
export function onColourSchemeChange(
callback: (scheme: TColourScheme) => void,
) {
From 5568a0935cd5aa7d4e974380d773de4e61486cba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Tue, 29 Apr 2025 19:05:19 +0300
Subject: [PATCH 18/19] fix vector math
---
src/api/canvas.ts | 146 +++++++++--------------
src/view/menu/UpdatePace.svelte | 4 +-
src/view/menu/UpdatePaceTimeMap.ts | 62 +++++-----
tests/canvas_test.ts | 185 ++++++++++++++++++++---------
4 files changed, 221 insertions(+), 176 deletions(-)
diff --git a/src/api/canvas.ts b/src/api/canvas.ts
index 22b7a35..1e950c7 100644
--- a/src/api/canvas.ts
+++ b/src/api/canvas.ts
@@ -1,3 +1,8 @@
+/**
+ * Module assumption:
+ * - Coordinate system of canvas html element
+ */
+
export const PI = Math.PI;
export const PI2 = 2 * PI;
export const PId2 = PI / 2;
@@ -24,7 +29,7 @@ export function rad2deg(rad: number) {
return ((rad % PI2) / PI2) * 360;
}
-class Coordinate {
+class XY {
x: number = 0;
y: number = 0;
@@ -38,6 +43,10 @@ class Coordinate {
return this;
}
+ clone() {
+ return new XY(this.x, this.y);
+ }
+
toString() {
return `{${this.x}, ${this.y}}`;
}
@@ -58,12 +67,12 @@ class Coordinate {
return this;
}
- equalXY(x: number, y: number) {
+ hasSameXY(x: number, y: number) {
return (x === this.x && y === this.y);
}
- equal(c: Coordinate) {
- return this.equalXY(c.x, c.y);
+ isEqualTo(xy: XY) {
+ return this.hasSameXY(xy.x, xy.y);
}
round(precision?: number) {
@@ -74,10 +83,7 @@ class Coordinate {
}
}
-/**
- * Point {x,y}
- */
-export class Point extends Coordinate {
+export class Point extends XY {
constructor(x: number, y: number) {
super(x, y);
}
@@ -90,7 +96,7 @@ export class Point extends Coordinate {
* Get distance between two points
*/
proximity(p: Point) {
- return this.toVector(p).length();
+ return this.vectorTo(p).length;
}
/**
@@ -98,25 +104,24 @@ export class Point extends Coordinate {
* Assuming this(x,y) is a v(0,0)
* @note this(0,0) is at top left corner
*/
- toVector(to: Point) {
- return new Vector(to.x - this.x, this.y - to.y);
+ vectorTo(to: XY) {
+ return new Vector(to.x - this.x, to.y - this.y);
}
/**
* Rotate point over center `axis` point by an angle
- * @note: positive angle value means counterclockwise
+ * @note: positive `radAngle` means counterclockwise
*/
- rotate(angle: number, axis: Point) {
- return axis.toVector(this).rotate(angle).toPoint(axis);
+ rotate(radAngle: number, axis: Point) {
+ const p = axis.vectorTo(this).rotate(radAngle).atBase(axis);
+ return this.set(p.x, p.y);
}
}
/**
- * Vector with virtual base of {0,0}
- * and assumed position at top-left corner
- * that points to {this.x, this.y}
+ * Vector with virtual base of {0,0} that points to {x,y}
*/
-export class Vector extends Coordinate {
+export class Vector extends XY {
constructor(x: number, y: number) {
super(x, y);
}
@@ -126,121 +131,85 @@ export class Vector extends Coordinate {
}
/**
- * Convert vector to point
- * Assuming pBase(x,y) refers to v(0,0)
- * @note: base(0,0) is at top left corner
+ * Get point where vector points from `base` point of view
+ * Assuming base(x,y) refers to v(0,0) of `this` vector
*/
- toPoint(base: Point) {
- return new Point(base.x + this.x, base.y - this.y);
+ atBase(base: Point) {
+ return new Point(base.x + this.x, base.y + this.y);
}
/**
- * Create vector rotated on specific angle
+ * Rotate at specific angle
* produced vector may contain float epsilon errors
- * @note: Positive angle means counterclockwise
+ * @note: positive `radAngle` means counterclockwise
*/
- rotate(angle: number) {
- const cos = Math.cos(angle);
- const sin = Math.sin(angle);
- return new Vector(
+ rotate(radAngle: number) {
+ const cos = Math.cos(-radAngle);
+ const sin = Math.sin(-radAngle);
+
+ return this.set(
this.x * cos - this.y * sin,
this.x * sin + this.y * cos,
);
}
- /**
- * Create vector rotated to left
- */
rotateLeft() {
- return new Vector(-this.y, this.x);
+ return this.set(-this.y, this.x);
}
- /**
- * Create vector rotated to right
- */
rotateRight() {
- return new Vector(this.y, -this.x);
+ return this.set(this.y, -this.x);
}
- /**
- * Create vector rotated backwards
- */
rotateBack() {
- return new Vector(-this.x, -this.y);
+ return this.set(-this.x, -this.y);
}
- /**
- * Return new vector mirrored over another vector interpreted as rotation axis
- */
- mirror(axis: Vector) {
- const delta = this.xAxisAngle() - axis.xAxisAngle();
+ mirrorOver(axis: Vector) {
+ const delta = this.angleWithX - axis.angleWithX;
const k = delta >= 0 ? -2 : 2;
return this.rotate(k * this.angle(axis));
}
- length() {
+ get length() {
return Math.sqrt(this.dot(this));
}
- /**
- * Return new vector with new vector length
- */
setLength(newLength: number) {
- return (new Vector(newLength, 0)).rotate(this.xAxisAngle());
+ const v = new Vector(newLength, 0).rotate(this.angleWithX);
+ return this.set(v.x, v.y);
}
- /**
- * Return new vector halved in length
- */
half() {
- return new Vector(this.x / 2, this.y / 2);
+ return this.set(this.x / 2, this.y / 2);
}
/**
- * Vector normalization
+ * Get angle of vector relative to X axis in range [0 ... α ... PI2] counterclockwise
*/
- normalize() {
- const length = this.length();
- return new Vector(this.x / length, this.y / length);
- }
+ get angleWithX() {
+ const angle = Math.atan2(-this.y, this.x);
- /**
- * Dot product of two vectors
- */
- dot(v: Vector) {
- return (this.x * v.x + this.y * v.y);
+ return (angle < 0) ? angle + PI2 : angle;
}
angle(v: Vector) {
return Math.acos(this.normalize().dot(v.normalize()));
}
- angleWithNorth() {
- return this.angle(new Vector(0, 1));
- }
-
- angleWithEast() {
- return this.angle(new Vector(1, 0));
- }
-
- angleWithSought() {
- return this.angle(new Vector(0, -1));
- }
-
- angleWithWest() {
- return this.angle(new Vector(-1, 0));
+ /**
+ * Return normalized vector
+ */
+ normalize() {
+ const length = this.length;
+ return new Vector(this.x / length, this.y / length);
}
/**
- * Get angle of vector relative to OX in range [0 ... α ... XY.PI2]
- * @note produced angle will be always positive radian
+ * Dot product of two vectors
*/
- xAxisAngle() {
- let angle = Math.atan2(this.y, this.x);
- if (angle < 0) {
- angle += PI2;
- }
- return angle;
+ dot(v: Vector) {
+ return (this.x * v.x + this.y * v.y);
}
}
@@ -280,9 +249,10 @@ export class Box {
toString() {
return JSON.stringify({
- tl: this.tl.toString(),
w: this.w,
h: this.h,
+ tl: this.tl.toString(),
+ c: this.c.toString(),
});
}
diff --git a/src/view/menu/UpdatePace.svelte b/src/view/menu/UpdatePace.svelte
index 0081ab3..8c89294 100644
--- a/src/view/menu/UpdatePace.svelte
+++ b/src/view/menu/UpdatePace.svelte
@@ -12,8 +12,8 @@
return ctx && startAnimation(ctx);
});
- ts.timeOfCollection.subscribe(() => {
- update();
+ ts.timeOfCollection.subscribe((v) => {
+ update(v);
});
diff --git a/src/view/menu/UpdatePaceTimeMap.ts b/src/view/menu/UpdatePaceTimeMap.ts
index 40b4bd3..19e7ac8 100644
--- a/src/view/menu/UpdatePaceTimeMap.ts
+++ b/src/view/menu/UpdatePaceTimeMap.ts
@@ -1,9 +1,11 @@
import { deg2rad, PI2, Point, Vector } from '../../api/canvas.ts';
interface IMemo {
- when: number;
- vector: Vector;
+ whenOccurred: number;
+ whenAdded: number;
age: number;
+ timePoint: Point;
+ vector: Vector;
}
let raf = 0;
@@ -15,7 +17,8 @@ const SHADOW_WIDTH = 4;
const pRotationAxis = new Point(R, R);
const WHITE = 'rgb(100% 100% 100%)';
const BLACK = 'rgb(0% 0% 0%)';
-const ANIMATION_DURATION = 2e3;
+const ANIMATION_DURATION = 1.5e3;
+const ANIMATION_DELTA_PX = R / ANIMATION_DURATION;
let primaryColour: string = BLACK;
let shadowColour: string = WHITE;
const memory: IMemo[] = [];
@@ -32,15 +35,21 @@ export function startAnimation(ctx: CanvasRenderingContext2D) {
};
}
-export function update() {
- const when = Date.now();
- const angle = -(when % 1000) * 360 / 1000;
- const vector = new Vector(R, 0).rotate(deg2rad(angle));
+export function update(timeOfCollection: number) {
+ const whenAdded = Date.now();
+ const angle = (timeOfCollection % 1000) * 360 / 1000;
+ const vector = new Vector(R, 0)
+ .rotate(deg2rad(-angle))
+ // rotate left to adjust zero angle to point at {0,-1} (north)
+ .rotateLeft();
+ const timePoint = vector.atBase(pRotationAxis);
memory.unshift({
- when,
- vector,
+ whenOccurred: timeOfCollection,
+ whenAdded,
age: 0,
+ timePoint,
+ vector: vector.rotateBack(),
});
}
@@ -63,24 +72,21 @@ function initContext(_ctx: CanvasRenderingContext2D) {
function draw() {
const drawTime = Date.now();
ctx.clearRect(0, 0, D, D);
+ drawCenter();
- if (memory.length) {
- for (let n = 0, N = memory.length; n < N; n++) {
- const memo = memory[n];
- memo.age = drawTime - memo.when;
- drawLine(memo);
- }
+ for (let n = 0, N = memory.length; n < N; n++) {
+ const memo = memory[n];
+ memo.age = drawTime - memo.whenAdded;
+ drawLine(memo);
+ }
- let n = memory.length;
- while (n--) {
- if (memory[n].age >= ANIMATION_DURATION) {
- memory.pop();
- } else {
- break;
- }
+ let n = memory.length;
+ while (n--) {
+ if (memory[n].age >= ANIMATION_DURATION) {
+ memory.pop();
+ } else {
+ break;
}
- } else {
- drawCenter();
}
raf = requestAnimationFrame(draw);
@@ -96,13 +102,13 @@ function drawCenter() {
}
function drawLine(memo: IMemo) {
- const length = R - memo.age * R / ANIMATION_DURATION;
- const p = memo.vector.setLength(length).toPoint(pRotationAxis);
+ const length = R - memo.age * ANIMATION_DELTA_PX;
+ const p = memo.vector.setLength(length).atBase(memo.timePoint);
ctx.save();
ctx.beginPath();
- ctx.moveTo(pRotationAxis.x, pRotationAxis.y);
- ctx.lineTo(p.x, p.y);
+ ctx.moveTo(p.x, p.y);
+ ctx.lineTo(memo.timePoint.x, memo.timePoint.y);
ctx.stroke();
ctx.closePath();
ctx.restore();
diff --git a/tests/canvas_test.ts b/tests/canvas_test.ts
index a1126a6..1eda19f 100644
--- a/tests/canvas_test.ts
+++ b/tests/canvas_test.ts
@@ -4,85 +4,172 @@ import {
Box,
deg2rad,
fround,
+ PI,
+ PI2,
PId2,
Point,
rad2deg,
Vector,
} from '../src/api/canvas.ts';
+describe('module exports', () => {
+ test('fround', () => {
+ expect(fround(1.33333333333)).toBe(1.333333);
+ expect(fround(1.33333333333, 1e3)).toBe(1.333);
+ });
+
+ test('rad2deg', () => {
+ expect(rad2deg(0)).toBe(0);
+ expect(rad2deg(PId2)).toBe(90);
+ expect(rad2deg(PI)).toBe(180);
+ expect(rad2deg(PI + PId2)).toBe(270);
+ expect(rad2deg(PI2)).toBe(0);
+ });
+
+ test('deg2rad', () => {
+ expect(deg2rad(0)).toBe(0);
+ expect(deg2rad(90)).toBe(PId2);
+ expect(deg2rad(180)).toBe(PI);
+ expect(deg2rad(270)).toBe(PI + PId2);
+ expect(deg2rad(360)).toBe(0);
+ });
+});
+
describe('Point', () => {
- test('toVector', () => {
- const pBase = new Point(6, 3);
- const pDirection = new Point(8, 1);
- const v = pBase.toVector(pDirection);
+ test('clone', () => {
+ const v = new Point(Infinity, -Infinity);
+ const clone = v.clone();
- expect(v.clone().equalXY(2, 2)).toBe(true);
+ expect(clone).not.toBe(v);
+ expect(clone.isEqualTo(v)).toBe(true);
+ });
+
+ test('proximity', () => {
+ const v = new Point(0, 0);
+
+ expect(v.proximity(new Point(0, 0))).toBe(0);
+ expect(v.proximity(new Point(1, 0))).toBe(1);
+ expect(v.proximity(new Point(0, 1))).toBe(1);
+ });
+
+ test('vectorTo', () => {
+ const from = new Point(0, 0);
+ const to = new Point(0, 0);
+
+ let v = from.set(6, 3).vectorTo(to.set(8, 1));
+ expect(v.x).toBe(2);
+ expect(v.y).toBe(-2);
+
+ v = from.set(8, 1).vectorTo(to.set(6, 3));
+ expect(v.x).toBe(-2);
+ expect(v.y).toBe(2);
+
+ v = from.set(4, 4).vectorTo(to.set(2, 2));
+ expect(v.x).toBe(-2);
+ expect(v.y).toBe(-2);
+
+ v = from.set(2, 2).vectorTo(to.set(4, 4));
+ expect(v.x).toBe(2);
+ expect(v.y).toBe(2);
});
test('rotate', () => {
const p = new Point(2, 2);
const base = new Point(4, 4);
- const rotated = p.rotate(PId2, base);
- expect(rotated.clone().equalXY(2, 6)).toBe(true);
+ p.rotate(PId2, base);
+ expect(p.x).toBe(2);
+ expect(p.y).toBe(6);
});
});
describe('Vector', () => {
- test('toPoint', () => {
+ test('rotate', () => {
+ const p = new Point(2, 2);
+ const base = new Point(4, 4);
+ const v = base.vectorTo(p);
+
+ expect(v.x).toBe(-2);
+ expect(v.y).toBe(-2);
+
+ v.rotate(PId2).round();
+ expect(v.x).toBe(-2);
+ expect(v.y).toBe(2);
+
+ v.rotate(-PI).round();
+ expect(v.x).toBe(2);
+ expect(v.y).toBe(-2);
+ });
+
+ test('atBase', () => {
const v = new Vector(2, -2);
const pBase = new Point(4, 4);
- const p = v.toPoint(pBase);
+ const p = v.atBase(pBase);
- expect(p.equalXY(6, 6)).toBe(true);
+ expect(p.hasSameXY(6, 2)).toBe(true);
});
- test('setLength', () => {
- const v = new Vector(-2, -2);
- const v2 = v.setLength(5);
+ test('length / setLength', () => {
+ const size = 2;
+ const v = new Vector(size, size);
+ const vAngleWithX = v.angleWithX;
+ const v2 = v.clone().setLength(5);
+ const v2AngleWithX = v2.angleWithX;
- // same angle
- expect(fround(v.xAxisAngle() - v2.xAxisAngle())).toBe(0);
- // same length
- expect(fround(v2.clone().length())).toBe(5);
+ expect(v.length).toBe(Math.sqrt(2 * size * size));
+ expect(fround(v2.length)).toBe(5);
+ expect(vAngleWithX).toEqual(v2AngleWithX);
});
test('half', () => {
const v = new Vector(2, 4);
- expect(v.half().length()).toBe(v.length() / 2);
+ expect(v.clone().half().length).toBe(v.length / 2);
});
- test('angleOX', () => {
+ test('xAxisAngle', () => {
const v = new Vector(1, -1);
- const angle = v.xAxisAngle();
- expect(angle).toBe(5.497787143782138);
- expect(rad2deg(angle)).toBe(315);
+ expect(rad2deg(v.set(1, -1).angleWithX)).toBe(45);
+ expect(rad2deg(v.set(-1, -1).angleWithX)).toBe(135);
+ expect(rad2deg(v.set(-1, 1).angleWithX)).toBe(225);
+ expect(rad2deg(v.set(1, 1).angleWithX)).toBe(315);
});
test('angle', () => {
- const v1 = new Vector(1, 1);
- const v2 = new Vector(-1, -1);
- const angle = v1.angle(v2);
-
- expect(fround(angle)).toBe(fround(Math.PI));
+ const v = new Vector(0, 0);
+ const ox = new Vector(1, 0); // 0-right
+ const oy = new Vector(0, 1); // 0-down
+
+ expect(Math.round(rad2deg(new Vector(1, 1).angle(new Vector(-1, -1)))))
+ .toBe(
+ 180,
+ );
+ expect(Math.round(rad2deg(v.set(1, -1).angle(ox)))).toBe(45);
+ expect(Math.round(rad2deg(v.set(-1, -1).angle(ox)))).toBe(135);
+ expect(Math.round(rad2deg(v.set(-1, 1).angle(ox)))).toBe(135);
+ expect(Math.round(rad2deg(v.set(1, 1).angle(ox)))).toBe(45);
+ expect(Math.round(rad2deg(ox.angle(v.set(1, 1))))).toBe(45);
+
+ expect(Math.round(rad2deg(oy.angle(v.set(1, -1))))).toBe(135);
+ expect(Math.round(rad2deg(oy.angle(v.set(-1, -1))))).toBe(135);
+ expect(Math.round(rad2deg(oy.angle(v.set(-1, 0))))).toBe(90);
+ expect(Math.round(rad2deg(oy.angle(v.set(0, 1))))).toBe(0);
+ expect(Math.round(rad2deg(oy.angle(v.set(1, 1))))).toBe(45);
});
- describe('mirror', () => {
+ test('mirror', () => {
const v1 = new Vector(-5, -1);
const vAxis = new Vector(-4, 4);
- const v2 = v1.mirror(vAxis).round();
-
- test('clockwise', () => {
- expect(v2.equalXY(1, 5)).toBe(true);
- });
+ const v2 = v1.mirrorOver(vAxis).round();
- test('counterclockwise', () => {
- const v3 = v2.mirror(vAxis).round();
+ expect(rad2deg(vAxis.angleWithX)).toBe(225);
+ expect(v2.x).toBe(1);
+ expect(v2.y).toBe(5);
- expect(v3.equal(v1)).toBe(true);
- });
+ const v3 = v2.mirrorOver(vAxis).round();
+ expect(v3.x).toBe(v1.x);
+ expect(v3.y).toBe(v1.y);
});
});
@@ -90,30 +177,12 @@ describe('Box', () => {
const box = new Box(new Point(0, 0), 10, 10);
test('proximity', () => {
- expect(box.clone().c.proximity(new Point(20, 5))).toBe(15);
+ expect(box.c.proximity(new Point(20, 5))).toBe(15);
});
test('contains', () => {
+ expect(box.contains(new Point(0, 0))).toBe(true);
expect(box.contains(new Point(5, 5))).toBe(true);
- });
-});
-
-describe('module exports', () => {
- test('fround', () => {
- expect(fround(1.33333333333)).toBe(1.333333);
- expect(fround(1.33333333333, 1e3)).toBe(1.333);
- });
-
- test('rad2deg', () => {
- const v = new Vector(0, 0);
-
- expect(rad2deg(v.set(1, 1).xAxisAngle())).toBe(45);
- expect(rad2deg(v.set(-1, 1).xAxisAngle())).toBe(135);
- expect(rad2deg(v.set(-1, -1).xAxisAngle())).toBe(225);
- expect(rad2deg(v.set(1, -1).xAxisAngle())).toBe(315);
- });
-
- test('deg2rad', () => {
- expect(rad2deg(deg2rad(81))).toBe(81);
+ expect(box.contains(new Point(-1, 0))).toBe(false);
});
});
From 74e9fc01f9bf9a5c97f548d4b94743a12e02bd44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=F0=9F=95=B7=EF=B8=8F?=
<3756473+zendive@users.noreply.github.com>
Date: Tue, 29 Apr 2025 19:17:30 +0300
Subject: [PATCH 19/19] 2s animation makes it beautiful
---
src/view/menu/UpdatePaceTimeMap.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/view/menu/UpdatePaceTimeMap.ts b/src/view/menu/UpdatePaceTimeMap.ts
index 19e7ac8..2713b1d 100644
--- a/src/view/menu/UpdatePaceTimeMap.ts
+++ b/src/view/menu/UpdatePaceTimeMap.ts
@@ -17,7 +17,7 @@ const SHADOW_WIDTH = 4;
const pRotationAxis = new Point(R, R);
const WHITE = 'rgb(100% 100% 100%)';
const BLACK = 'rgb(0% 0% 0%)';
-const ANIMATION_DURATION = 1.5e3;
+const ANIMATION_DURATION = 2e3;
const ANIMATION_DELTA_PX = R / ANIMATION_DURATION;
let primaryColour: string = BLACK;
let shadowColour: string = WHITE;
| | | | | |