From 85ecd44b7c9049cc2bd27231b5655b61ed5c7b26 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: Sat, 12 Apr 2025 23:03:20 +0300 Subject: [PATCH 01/19] v1.2.0 --- deno.json | 4 ++-- deno.lock | 22 +++++++++++----------- manifest.json | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/deno.json b/deno.json index a90c42b..baa12a2 100644 --- a/deno.json +++ b/deno.json @@ -37,8 +37,8 @@ "esbuild-svelte": "npm:esbuild-svelte@0.9.0", "svelte-preprocess": "npm:svelte-preprocess@6.0.3", - "@std/expect": "jsr:@std/expect@1.0.14", - "@std/testing": "jsr:@std/testing@1.0.10", + "@std/expect": "jsr:@std/expect@^1.0.15", + "@std/testing": "jsr:@std/testing@^1.0.11", "happy-dom": "npm:happy-dom@17.4.4" } } diff --git a/deno.lock b/deno.lock index 7055d4e..f65fd09 100644 --- a/deno.lock +++ b/deno.lock @@ -7,12 +7,12 @@ "jsr:@std/bytes@^1.0.2": "1.0.4", "jsr:@std/data-structures@^1.0.6": "1.0.6", "jsr:@std/encoding@^1.0.5": "1.0.6", - "jsr:@std/expect@1.0.14": "1.0.14", - "jsr:@std/fs@^1.0.15": "1.0.15", + "jsr:@std/expect@^1.0.15": "1.0.15", + "jsr:@std/fs@^1.0.16": "1.0.16", "jsr:@std/internal@^1.0.6": "1.0.6", "jsr:@std/path@^1.0.6": "1.0.8", "jsr:@std/path@^1.0.8": "1.0.8", - "jsr:@std/testing@1.0.10": "1.0.10", + "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", @@ -54,15 +54,15 @@ "@std/encoding@1.0.6": { "integrity": "ca87122c196e8831737d9547acf001766618e78cd8c33920776c7f5885546069" }, - "@std/expect@1.0.14": { - "integrity": "27b8200267c97206e38050aff18badc82e73b3072c1a6bdd59393ddea6f69183", + "@std/expect@1.0.15": { + "integrity": "eca360007b5a7f13dbfa1294224baee7fb98dcd460d8461fe64eeae302902945", "dependencies": [ "jsr:@std/assert", "jsr:@std/internal" ] }, - "@std/fs@1.0.15": { - "integrity": "c083fb479889d6440d768e498195c3fc499d426fbf9a6592f98f53884d1d3f41", + "@std/fs@1.0.16": { + "integrity": "81878f62b6eeda0bf546197fc3daa5327c132fee1273f6113f940784a468b036", "dependencies": [ "jsr:@std/path@^1.0.8" ] @@ -73,8 +73,8 @@ "@std/path@1.0.8": { "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" }, - "@std/testing@1.0.10": { - "integrity": "8997bd0b0df020b81bf5eae103c66622918adeff7e45e96291c92a29dbf82cc1", + "@std/testing@1.0.11": { + "integrity": "12b3db12d34f0f385a26248933bde766c0f8c5ad8b6ab34d4d38f528ab852f48", "dependencies": [ "jsr:@std/assert", "jsr:@std/async", @@ -526,8 +526,8 @@ "workspace": { "dependencies": [ "jsr:@luca/esbuild-deno-loader@0.11.1", - "jsr:@std/expect@1.0.14", - "jsr:@std/testing@1.0.10", + "jsr:@std/expect@^1.0.15", + "jsr:@std/testing@^1.0.11", "npm:@types/chrome@*", "npm:esbuild-svelte@0.9.0", "npm:happy-dom@17.4.4", diff --git a/manifest.json b/manifest.json index e21d07d..491c2e8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.1.0", + "version": "1.2.0", "name": "API Monitor", "manifest_version": 3, "description": "Show active intervals, scheduled timeouts, animation frames, idle callbacks, eval invocations, media events and properties", From ade51e01d3bfd7a21d10958caf556c85a2ce676d 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: Sat, 12 Apr 2025 23:05:15 +0300 Subject: [PATCH 02/19] use `__development__` instead of `IS_DEV = __development__` --- src/api-monitor-cs-main.ts | 3 +-- src/api/env.ts | 1 - src/view/App.svelte | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/api-monitor-cs-main.ts b/src/api-monitor-cs-main.ts index e0d6439..499edc5 100644 --- a/src/api-monitor-cs-main.ts +++ b/src/api-monitor-cs-main.ts @@ -1,5 +1,4 @@ import { EMsg, windowListen, windowPost } from './api/communication.ts'; -import { IS_DEV } from './api/env.ts'; import { TELEMETRY_FREQUENCY_1PS } from './api/const.ts'; import { adjustTelemetryDelay, Timer } from './api/time.ts'; import { @@ -76,4 +75,4 @@ windowListen((o) => { } }); -IS_DEV && console.debug('cs-main.ts'); +__development__ && console.debug('cs-main.ts'); diff --git a/src/api/env.ts b/src/api/env.ts index 6e46e9c..af39508 100644 --- a/src/api/env.ts +++ b/src/api/env.ts @@ -1,4 +1,3 @@ -export const IS_DEV = __development__; export const APPLICATION_VERSION = __app_version__; export const APPLICATION_NAME = __app_name__; export const APPLICATION_HOME_PAGE = __home_page__; diff --git a/src/view/App.svelte b/src/view/App.svelte index 4feeb92..48abe4a 100644 --- a/src/view/App.svelte +++ b/src/view/App.svelte @@ -1,6 +1,5 @@ + + + {Fact.getTags(facts, factsMap)} + + + diff --git a/src/view/components/IdleCallbackCancelHistory.svelte b/src/view/components/IdleCallbackCancelHistory.svelte index 4ebdcf7..4423132 100644 --- a/src/view/components/IdleCallbackCancelHistory.svelte +++ b/src/view/components/IdleCallbackCancelHistory.svelte @@ -13,6 +13,9 @@ import SortableColumn from './SortableColumn.svelte'; import TraceBreakpoint from './TraceBreakpoint.svelte'; import TraceBypass from './TraceBypass.svelte'; + import { CafFact } from '../../wrapper/AnimationWrapper.ts'; + import type { TFactsMap } from '../../wrapper/Fact.ts'; + import FactsCell from './FactsCell.svelte'; let { cicHistory, @@ -24,6 +27,13 @@ let sortedMetrics = $derived.by(() => cicHistory.toSorted(compareByFieldOrder(sortField, sortOrder)) ); + const CicFacts: TFactsMap = new Map([ + [CafFact.NOT_FOUND, { tag: 'I', details: 'Idle Callback not found' }], + [CafFact.BAD_HANDLER, { + tag: 'H', + details: 'Handler is not a positive number', + }], + ]); getSettings().then((settings) => { sortField = settings.sortCancelIdleCallback.field; @@ -50,6 +60,14 @@ Callstack + + + + + + diff --git a/src/view/components/IdleCallbackRequestHistory.svelte b/src/view/components/IdleCallbackRequestHistory.svelte index 7289048..8e1a6dc 100644 --- a/src/view/components/IdleCallbackRequestHistory.svelte +++ b/src/view/components/IdleCallbackRequestHistory.svelte @@ -1,7 +1,8 @@ {@render children?.()} {#if field === currentField} diff --git a/src/view/components/TimersClearHistory.svelte b/src/view/components/TimersClearHistory.svelte index a3e1c2c..0a3e6d1 100644 --- a/src/view/components/TimersClearHistory.svelte +++ b/src/view/components/TimersClearHistory.svelte @@ -47,6 +47,14 @@ Callstack + + + - import type { TClearTimerHistory } from '../../wrapper/TimerWrapper.ts'; + import { + ClearTimerFact, + type TClearTimerHistory, + } from '../../wrapper/TimerWrapper.ts'; import { msToHms } from '../../api/time.ts'; import Trace from './Trace.svelte'; import TraceDomain from './TraceDomain.svelte'; import Variable from './Variable.svelte'; import TraceBreakpoint from './TraceBreakpoint.svelte'; import TraceBypass from './TraceBypass.svelte'; + import type { TFactsMap } from '../../wrapper/Fact.ts'; + import FactsCell from './FactsCell.svelte'; let { metric }: { metric: TClearTimerHistory } = $props(); + const ClearTimerFacts: TFactsMap = new Map([ + [ClearTimerFact.NOT_FOUND, { tag: 'T', details: 'Timer not found' }], + [ClearTimerFact.BAD_HANDLER, { + tag: 'H', + details: 'Handler is not a positive number', + }], + ]); @@ -15,6 +27,9 @@ + + + {metric.delay} diff --git a/src/view/components/TimersSetHistory.svelte b/src/view/components/TimersSetHistory.svelte index 7093200..48d9b07 100644 --- a/src/view/components/TimersSetHistory.svelte +++ b/src/view/components/TimersSetHistory.svelte @@ -64,6 +64,14 @@ eventChangeSorting={onChangeSort} >Self + + + - 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 @@ - - + - + - + + + {#each metrics as metric (metric.handler)} - + - + {/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} -
Delay + {caption} Callstack [] + HandlerCallstackDelay
{metric.delay} + + + {metric.handler} @@ -45,10 +49,7 @@ onclick={() => void onRemoveHandler(metric)} > - - - {metric.delay}
- - + - + + + {#each sortedMetrics as metric (metric.traceId)}
- {caption} -
Callstack + {caption} Callstack [] +
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 @@ - - + - + + + {#each sortedMetrics as metric (metric.traceId)}
- {caption} -
Callstack + {caption} Callstack [] +
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} - - + - + @@ -38,6 +37,9 @@ + + + {#each evalHistory as metric (metric.traceId)}
- Eval History -
Callstack + Eval History Callstack [] + Self Called Code
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 @@ - - + - + + + {#each sortedMetrics as metric (metric.traceId)}
- {caption} -
Callstack + {caption} Callstack [] +
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 @@ - - + - + + + {#each sortedMetrics as metric (metric.traceId)} - {#each panels as panel, index (panel.key)} - + {#each config?.panels || [] as panel, index (panel.key)} + - {#if !NON_WRAPPABLE.includes(panel.key)} + {#if panel.wrap !== null} 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 @@ + + +
+
{upsValue} u/s
+ +
+ + 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 @@ @@ -102,7 +95,7 @@ - + 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 @@ @@ -116,24 +109,24 @@ @@ -161,7 +154,7 @@ onClick={onFindRegressors} /> - + @@ -100,7 +95,7 @@ - + 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 @@ @@ -190,7 +183,7 @@ onClick={onFindRegressors} /> - + 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 @@ 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 @@
-
{upsValue} u/s
- +
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;
- {caption} -
Callstack + {caption} Callstack [] + didTimeout
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 @@ - - + - + + + {#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} -
Callstack + {caption} Callstack [] +
- - + - + + + {#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 @@ - - + + @@ -35,8 +34,10 @@ {#each metrics as metric (metric.handler)} 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 = 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 }); + }
- {caption} -
Callstack + {caption} Callstack [] +
{caption} Callstack [] HandlerDelayHandlerDelay
- - + {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}
- - - + + {/each} + + + + +
@@ -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 __development__} - +
{/if} - -
- +
- +
- -
- {#if telemetry} - - {/if} -
- - {#if !paused} -
- - {/if} - + +
+
- {#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 @@
Called Handler {metric.handler}
Self Called Handler Set {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 {metric.handler}
Self Called Handler Delay Set {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 Self Called Handler Delay Set