@editframe/elements 0.30.2-beta.0 → 0.31.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.d.ts +5 -0
- package/dist/EF_FRAMEGEN.js +20 -4
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_INTERACTIVE.js.map +1 -1
- package/dist/_virtual/rolldown_runtime.js +27 -0
- package/dist/canvas/EFCanvas.d.ts +311 -0
- package/dist/canvas/EFCanvas.js +1089 -0
- package/dist/canvas/EFCanvas.js.map +1 -0
- package/dist/canvas/EFCanvasItem.d.ts +55 -0
- package/dist/canvas/EFCanvasItem.js +72 -0
- package/dist/canvas/EFCanvasItem.js.map +1 -0
- package/dist/canvas/api/CanvasAPI.d.ts +115 -0
- package/dist/canvas/api/CanvasAPI.js +182 -0
- package/dist/canvas/api/CanvasAPI.js.map +1 -0
- package/dist/canvas/api/types.d.ts +42 -0
- package/dist/canvas/coordinateTransform.js +90 -0
- package/dist/canvas/coordinateTransform.js.map +1 -0
- package/dist/canvas/getElementBounds.js +40 -0
- package/dist/canvas/getElementBounds.js.map +1 -0
- package/dist/canvas/overlays/SelectionOverlay.js +265 -0
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -0
- package/dist/canvas/overlays/overlayState.js +153 -0
- package/dist/canvas/overlays/overlayState.js.map +1 -0
- package/dist/canvas/selection/SelectionController.js +105 -0
- package/dist/canvas/selection/SelectionController.js.map +1 -0
- package/dist/canvas/selection/SelectionModel.d.ts +98 -0
- package/dist/canvas/selection/SelectionModel.js +229 -0
- package/dist/canvas/selection/SelectionModel.js.map +1 -0
- package/dist/canvas/selection/selectionContext.d.ts +31 -0
- package/dist/canvas/selection/selectionContext.js +12 -0
- package/dist/canvas/selection/selectionContext.js.map +1 -0
- package/dist/elements/ContainerInfo.d.ts +29 -0
- package/dist/elements/ContainerInfo.js +30 -0
- package/dist/elements/ContainerInfo.js.map +1 -0
- package/dist/elements/EFAudio.d.ts +13 -3
- package/dist/elements/EFAudio.js +64 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +18 -16
- package/dist/elements/EFCaptions.js +110 -19
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +16 -6
- package/dist/elements/EFImage.js +79 -9
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +51 -4
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +125 -52
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +24 -6
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -8
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +46 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +98 -73
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +18 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +8 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +31 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +97 -72
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +25 -14
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +47 -16
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +37 -19
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +65 -21
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +8 -3
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +32 -9
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +33 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +23 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +34 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +31 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +31 -114
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +44 -8
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +18 -7
- package/dist/elements/EFMedia.js +23 -3
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +96 -0
- package/dist/elements/EFPanZoom.js +290 -0
- package/dist/elements/EFPanZoom.js.map +1 -0
- package/dist/elements/EFSourceMixin.js +7 -6
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +6 -6
- package/dist/elements/EFSurface.js +7 -2
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +2 -1
- package/dist/elements/EFTemporal.js +192 -71
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +5 -4
- package/dist/elements/EFText.js +102 -13
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +32 -6
- package/dist/elements/EFTextSegment.js +53 -15
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFThumbnailStrip.d.ts +129 -56
- package/dist/elements/EFThumbnailStrip.js +605 -359
- package/dist/elements/EFThumbnailStrip.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +233 -25
- package/dist/elements/EFTimegroup.js +865 -144
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +42 -5
- package/dist/elements/EFVideo.js +165 -11
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +6 -6
- package/dist/elements/EFWaveform.js +2 -1
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.d.ts +35 -0
- package/dist/elements/ElementPositionInfo.js +49 -0
- package/dist/elements/ElementPositionInfo.js.map +1 -0
- package/dist/elements/FetchMixin.js +16 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/SessionThumbnailCache.js +154 -0
- package/dist/elements/SessionThumbnailCache.js.map +1 -0
- package/dist/elements/TargetController.js +3 -1
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js +9 -3
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/findRootTemporal.js +30 -0
- package/dist/elements/findRootTemporal.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +18 -5
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/updateAnimations.js +171 -28
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/ContextMixin.js +4 -2
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +74 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +50 -0
- package/dist/gui/EFActiveRootTemporal.js +94 -0
- package/dist/gui/EFActiveRootTemporal.js.map +1 -0
- package/dist/gui/EFConfiguration.d.ts +7 -1
- package/dist/gui/EFConfiguration.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +109 -13
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +11 -214
- package/dist/gui/EFFilmstrip.js +53 -1152
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +3 -3
- package/dist/gui/EFFitScale.js +39 -12
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFOverlayItem.d.ts +48 -0
- package/dist/gui/EFOverlayItem.js +97 -0
- package/dist/gui/EFOverlayItem.js.map +1 -0
- package/dist/gui/EFOverlayLayer.d.ts +70 -0
- package/dist/gui/EFOverlayLayer.js +104 -0
- package/dist/gui/EFOverlayLayer.js.map +1 -0
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFResizableBox.d.ts +12 -16
- package/dist/gui/EFResizableBox.js +109 -451
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +30 -5
- package/dist/gui/EFScrubber.js +224 -31
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.js +4 -1
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +71 -0
- package/dist/gui/EFTimelineRuler.js +320 -0
- package/dist/gui/EFTimelineRuler.js.map +1 -0
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTransformHandles.d.ts +91 -0
- package/dist/gui/EFTransformHandles.js +393 -0
- package/dist/gui/EFTransformHandles.js.map +1 -0
- package/dist/gui/EFWorkbench.d.ts +178 -0
- package/dist/gui/EFWorkbench.js +2067 -22
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.d.ts +31 -0
- package/dist/gui/FitScaleHelpers.js +41 -0
- package/dist/gui/FitScaleHelpers.js.map +1 -0
- package/dist/gui/PlaybackController.d.ts +2 -1
- package/dist/gui/PlaybackController.js +46 -15
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchy.d.ts +65 -0
- package/dist/gui/hierarchy/EFHierarchy.js +338 -0
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -0
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +118 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js +551 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -0
- package/dist/gui/hierarchy/hierarchyContext.d.ts +38 -0
- package/dist/gui/hierarchy/hierarchyContext.js +8 -0
- package/dist/gui/hierarchy/hierarchyContext.js.map +1 -0
- package/dist/gui/icons.js +34 -0
- package/dist/gui/icons.js.map +1 -0
- package/dist/gui/panZoomTransformContext.js +12 -0
- package/dist/gui/panZoomTransformContext.js.map +1 -0
- package/dist/gui/previewSettingsContext.js +12 -0
- package/dist/gui/previewSettingsContext.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +270 -0
- package/dist/gui/timeline/EFTimeline.js +1369 -0
- package/dist/gui/timeline/EFTimeline.js.map +1 -0
- package/dist/gui/timeline/EFTimelineRow.js +374 -0
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -0
- package/dist/gui/timeline/TrimHandles.d.ts +36 -0
- package/dist/gui/timeline/TrimHandles.js +204 -0
- package/dist/gui/timeline/TrimHandles.js.map +1 -0
- package/dist/gui/timeline/flattenHierarchy.js +31 -0
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.d.ts +26 -0
- package/dist/gui/timeline/timelineStateContext.js +42 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -0
- package/dist/gui/timeline/tracks/AudioTrack.js +264 -0
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js +595 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js +19 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ImageTrack.js +53 -0
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TextTrack.js +250 -0
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js +143 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TrackItem.js +269 -0
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -0
- package/dist/gui/timeline/tracks/VideoTrack.js +265 -0
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js +19 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ensureTrackItemInit.js +1 -0
- package/dist/gui/timeline/tracks/preloadTracks.js +9 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js +119 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -0
- package/dist/gui/timeline/tracks/waveformUtils.js +80 -0
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -0
- package/dist/gui/transformCalculations.js +217 -0
- package/dist/gui/transformCalculations.js.map +1 -0
- package/dist/gui/transformUtils.d.ts +37 -0
- package/dist/gui/transformUtils.js +77 -0
- package/dist/gui/transformUtils.js.map +1 -0
- package/dist/gui/tree/EFTree.d.ts +59 -0
- package/dist/gui/tree/EFTree.js +174 -0
- package/dist/gui/tree/EFTree.js.map +1 -0
- package/dist/gui/tree/EFTreeItem.d.ts +38 -0
- package/dist/gui/tree/EFTreeItem.js +146 -0
- package/dist/gui/tree/EFTreeItem.js.map +1 -0
- package/dist/gui/tree/treeContext.d.ts +60 -0
- package/dist/gui/tree/treeContext.js +23 -0
- package/dist/gui/tree/treeContext.js.map +1 -0
- package/dist/index.d.ts +32 -8
- package/dist/index.js +30 -6
- package/dist/index.js.map +1 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +688 -0
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
- package/dist/node_modules/react/cjs/react.development.js +1521 -0
- package/dist/node_modules/react/cjs/react.development.js.map +1 -0
- package/dist/node_modules/react/index.js +13 -0
- package/dist/node_modules/react/index.js.map +1 -0
- package/dist/node_modules/react/jsx-runtime.js +13 -0
- package/dist/node_modules/react/jsx-runtime.js.map +1 -0
- package/dist/preview/AdaptiveResolutionTracker.js +228 -0
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -0
- package/dist/preview/RenderProfiler.js +135 -0
- package/dist/preview/RenderProfiler.js.map +1 -0
- package/dist/preview/previewSettings.js +131 -0
- package/dist/preview/previewSettings.js.map +1 -0
- package/dist/preview/previewTypes.js +64 -0
- package/dist/preview/previewTypes.js.map +1 -0
- package/dist/preview/renderTimegroupPreview.js +656 -0
- package/dist/preview/renderTimegroupPreview.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.d.ts +37 -0
- package/dist/preview/renderTimegroupToCanvas.js +833 -0
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +39 -0
- package/dist/preview/renderTimegroupToVideo.js +274 -0
- package/dist/preview/renderTimegroupToVideo.js.map +1 -0
- package/dist/preview/renderers.js +16 -0
- package/dist/preview/renderers.js.map +1 -0
- package/dist/preview/statsTrackingStrategy.js +201 -0
- package/dist/preview/statsTrackingStrategy.js.map +1 -0
- package/dist/preview/thumbnailCacheSettings.js +52 -0
- package/dist/preview/thumbnailCacheSettings.js.map +1 -0
- package/dist/preview/workers/WorkerPool.js +178 -0
- package/dist/preview/workers/WorkerPool.js.map +1 -0
- package/dist/preview/workers/encoderWorkerInline.js +103 -0
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -0
- package/dist/sandbox/PlaybackControls.js +10 -0
- package/dist/sandbox/PlaybackControls.js.map +1 -0
- package/dist/sandbox/ScenarioRunner.js +1 -0
- package/dist/sandbox/index.js +2 -0
- package/dist/style.css +71 -67
- package/dist/transcoding/types/index.d.ts +2 -1
- package/dist/transcoding/utils/UrlGenerator.d.ts +6 -1
- package/dist/transcoding/utils/UrlGenerator.js +12 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -375
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +14 -0
- package/dist/utils/frameTime.js.map +1 -0
- package/package.json +3 -3
- package/test/profilingPlugin.ts +223 -0
- package/test/recordReplayProxyPlugin.js +22 -27
- package/test/thumbnail-performance-test.html +116 -0
- package/test/visualRegressionUtils.ts +286 -0
- package/types.json +1 -1
- package/dist/elements/TimegroupController.d.ts +0 -18
- package/dist/msToTimeCode.js +0 -17
- package/dist/msToTimeCode.js.map +0 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest Browser Profiling Plugin
|
|
3
|
+
*
|
|
4
|
+
* Captures Chrome DevTools CPU profiles during browser tests.
|
|
5
|
+
* Enable with VITEST_PROFILE=1 environment variable.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* VITEST_PROFILE=1 ./scripts/browsertest <test-file>
|
|
9
|
+
* ./scripts/browsertest --profile <test-file>
|
|
10
|
+
*
|
|
11
|
+
* Output:
|
|
12
|
+
* Creates ./browsertest-profile.cpuprofile in the elements directory
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Plugin } from "vite";
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
|
|
19
|
+
interface ProfileNode {
|
|
20
|
+
id: number;
|
|
21
|
+
callFrame: {
|
|
22
|
+
functionName: string;
|
|
23
|
+
scriptId: string;
|
|
24
|
+
url: string;
|
|
25
|
+
lineNumber: number;
|
|
26
|
+
columnNumber: number;
|
|
27
|
+
};
|
|
28
|
+
hitCount?: number;
|
|
29
|
+
children?: number[];
|
|
30
|
+
positionTicks?: { line: number; ticks: number }[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface CPUProfile {
|
|
34
|
+
nodes: ProfileNode[];
|
|
35
|
+
startTime: number;
|
|
36
|
+
endTime: number;
|
|
37
|
+
samples: number[];
|
|
38
|
+
timeDeltas: number[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Global state for CDP session and profile
|
|
42
|
+
let cdpSession: any = null;
|
|
43
|
+
let profilingStartTime: number = 0;
|
|
44
|
+
|
|
45
|
+
export function profilingPlugin(): Plugin {
|
|
46
|
+
const isProfilingEnabled = process.env.VITEST_PROFILE === "1";
|
|
47
|
+
|
|
48
|
+
if (!isProfilingEnabled) {
|
|
49
|
+
return { name: "vitest-profiling-disabled" };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log("\n🔬 CPU Profiling enabled for browser tests\n");
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: "vitest-browser-profiling",
|
|
56
|
+
|
|
57
|
+
// Hook into server configuration to get access to the browser
|
|
58
|
+
configureServer(server) {
|
|
59
|
+
// We need to hook into the Vitest browser lifecycle
|
|
60
|
+
// This is tricky because Vitest manages the browser connection
|
|
61
|
+
|
|
62
|
+
// Add an endpoint that tests can call to start/stop profiling
|
|
63
|
+
server.middlewares.use("/__vitest_profile__/start", async (_req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
// Get the CDP session from the connected browser
|
|
66
|
+
// This requires access to the Playwright page, which Vitest manages
|
|
67
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
68
|
+
res.end(JSON.stringify({ status: "profiling_start_requested" }));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
71
|
+
res.end(JSON.stringify({ error: String(error) }));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
server.middlewares.use("/__vitest_profile__/stop", async (_req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
78
|
+
res.end(JSON.stringify({ status: "profiling_stop_requested" }));
|
|
79
|
+
} catch (error) {
|
|
80
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
81
|
+
res.end(JSON.stringify({ error: String(error) }));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Start CDP profiling on a Playwright page
|
|
90
|
+
* Call this from test setup or beforeAll
|
|
91
|
+
*/
|
|
92
|
+
export async function startProfiling(page: any): Promise<void> {
|
|
93
|
+
if (process.env.VITEST_PROFILE !== "1") return;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Get CDP session from Playwright page
|
|
97
|
+
// In Playwright, we access CDP via page.context().newCDPSession(page)
|
|
98
|
+
const context = page.context();
|
|
99
|
+
cdpSession = await context.newCDPSession(page);
|
|
100
|
+
|
|
101
|
+
await cdpSession.send("Profiler.enable");
|
|
102
|
+
await cdpSession.send("Profiler.setSamplingInterval", { interval: 100 }); // 100µs
|
|
103
|
+
await cdpSession.send("Profiler.start");
|
|
104
|
+
|
|
105
|
+
profilingStartTime = Date.now();
|
|
106
|
+
console.log("🎬 CPU profiling started");
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error("Failed to start profiling:", error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Stop CDP profiling and save the profile
|
|
114
|
+
* Call this from test teardown or afterAll
|
|
115
|
+
*/
|
|
116
|
+
export async function stopProfiling(
|
|
117
|
+
outputPath?: string,
|
|
118
|
+
): Promise<CPUProfile | null> {
|
|
119
|
+
if (process.env.VITEST_PROFILE !== "1" || !cdpSession) return null;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const { profile } = (await cdpSession.send("Profiler.stop")) as {
|
|
123
|
+
profile: CPUProfile;
|
|
124
|
+
};
|
|
125
|
+
await cdpSession.send("Profiler.disable");
|
|
126
|
+
|
|
127
|
+
const duration = Date.now() - profilingStartTime;
|
|
128
|
+
console.log(`⏱️ CPU profiling stopped after ${duration}ms`);
|
|
129
|
+
|
|
130
|
+
// Save profile
|
|
131
|
+
const finalPath = outputPath || "./browsertest-profile.cpuprofile";
|
|
132
|
+
const profileJson = JSON.stringify(profile, null, 2);
|
|
133
|
+
fs.writeFileSync(finalPath, profileJson);
|
|
134
|
+
console.log(`💾 Profile saved to: ${finalPath}`);
|
|
135
|
+
console.log(` Load in Chrome DevTools → Performance → Load profile`);
|
|
136
|
+
|
|
137
|
+
// Print summary
|
|
138
|
+
printProfileSummary(profile, duration);
|
|
139
|
+
|
|
140
|
+
cdpSession = null;
|
|
141
|
+
return profile;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error("Failed to stop profiling:", error);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function printProfileSummary(profile: CPUProfile, wallClockMs: number): void {
|
|
149
|
+
const hitCounts = new Map<number, number>();
|
|
150
|
+
for (const sample of profile.samples) {
|
|
151
|
+
hitCounts.set(sample, (hitCounts.get(sample) || 0) + 1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const sampleIntervalUs =
|
|
155
|
+
profile.timeDeltas.length > 0
|
|
156
|
+
? profile.timeDeltas.reduce((a, b) => a + b, 0) /
|
|
157
|
+
profile.timeDeltas.length
|
|
158
|
+
: 1000;
|
|
159
|
+
|
|
160
|
+
const totalSamples = profile.samples.length;
|
|
161
|
+
const profileTimeMs = (totalSamples * sampleIntervalUs) / 1000;
|
|
162
|
+
|
|
163
|
+
// Build hotspots
|
|
164
|
+
const hotspots: { name: string; file: string; timeMs: number }[] = [];
|
|
165
|
+
for (const node of profile.nodes) {
|
|
166
|
+
const hitCount = hitCounts.get(node.id) || 0;
|
|
167
|
+
if (hitCount === 0) continue;
|
|
168
|
+
|
|
169
|
+
const selfTimeMs = (hitCount * sampleIntervalUs) / 1000;
|
|
170
|
+
const file =
|
|
171
|
+
node.callFrame.url?.split("/").slice(-1)[0]?.split("?")[0] || "(native)";
|
|
172
|
+
|
|
173
|
+
hotspots.push({
|
|
174
|
+
name: node.callFrame.functionName || "(anonymous)",
|
|
175
|
+
file,
|
|
176
|
+
timeMs: selfTimeMs,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
hotspots.sort((a, b) => b.timeMs - a.timeMs);
|
|
181
|
+
|
|
182
|
+
// Group by file
|
|
183
|
+
const byFile = new Map<string, number>();
|
|
184
|
+
for (const h of hotspots) {
|
|
185
|
+
byFile.set(h.file, (byFile.get(h.file) || 0) + h.timeMs);
|
|
186
|
+
}
|
|
187
|
+
const sortedFiles = Array.from(byFile.entries()).sort((a, b) => b[1] - a[1]);
|
|
188
|
+
|
|
189
|
+
console.log(
|
|
190
|
+
`\n📊 Profile Summary (${wallClockMs}ms wall clock, ${profileTimeMs.toFixed(1)}ms profile time)`,
|
|
191
|
+
);
|
|
192
|
+
console.log(`\n Top files:`);
|
|
193
|
+
for (const [file, time] of sortedFiles.slice(0, 10)) {
|
|
194
|
+
const pct = ((time / profileTimeMs) * 100).toFixed(1);
|
|
195
|
+
console.log(
|
|
196
|
+
` ${time.toFixed(1).padStart(8)}ms (${pct.padStart(5)}%) ${file}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Our code
|
|
201
|
+
const ourCode = hotspots.filter(
|
|
202
|
+
(h) =>
|
|
203
|
+
h.file.includes(".ts") &&
|
|
204
|
+
!h.file.includes("node_modules") &&
|
|
205
|
+
(h.file.includes("render") ||
|
|
206
|
+
h.file.includes("preview") ||
|
|
207
|
+
h.file.includes("element") ||
|
|
208
|
+
h.file.includes("Timegroup") ||
|
|
209
|
+
h.file.includes("clone") ||
|
|
210
|
+
h.file.includes("sync")),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (ourCode.length > 0) {
|
|
214
|
+
console.log(`\n Top functions in our code:`);
|
|
215
|
+
for (const h of ourCode.slice(0, 10)) {
|
|
216
|
+
console.log(
|
|
217
|
+
` ${h.timeMs.toFixed(1).padStart(8)}ms ${h.name.slice(0, 40).padEnd(40)} ${h.file}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log();
|
|
223
|
+
}
|
|
@@ -3,8 +3,11 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import debug from "debug";
|
|
6
7
|
import { TEST_SERVER_PORT } from "./constants.js";
|
|
7
8
|
|
|
9
|
+
const log = debug("ef:recordReplayProxyPlugin");
|
|
10
|
+
|
|
8
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
12
|
const CACHE_DIR = join(__dirname, "__cache__");
|
|
10
13
|
const TARGET_HOST = "host.docker.internal";
|
|
@@ -43,7 +46,7 @@ export function recordReplayProxyPlugin() {
|
|
|
43
46
|
name: "record-replay-proxy",
|
|
44
47
|
|
|
45
48
|
configureServer(server) {
|
|
46
|
-
|
|
49
|
+
log(
|
|
47
50
|
`[Proxy Plugin] Configuring record-replay proxy middleware... ${CACHE_ONLY_MODE ? "(CACHE-ONLY MODE)" : ""}`,
|
|
48
51
|
);
|
|
49
52
|
|
|
@@ -60,10 +63,10 @@ export function recordReplayProxyPlugin() {
|
|
|
60
63
|
await handleProxyRequest(req, res, next);
|
|
61
64
|
});
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
log("[Proxy Plugin] Proxy middleware configured");
|
|
67
|
+
log(`[Proxy Plugin] Cache directory: ${CACHE_DIR}`);
|
|
65
68
|
if (CACHE_ONLY_MODE) {
|
|
66
|
-
|
|
69
|
+
log(
|
|
67
70
|
"[Proxy Plugin] ⚠️ Running in CACHE-ONLY mode - no remote fetching",
|
|
68
71
|
);
|
|
69
72
|
}
|
|
@@ -122,9 +125,7 @@ export function recordReplayProxyPlugin() {
|
|
|
122
125
|
responseHeaders["content-length"] = body.length.toString();
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
`[Proxy] ✓ Rewrote cached URLs: ${originalHost} → ${proxyHost}`,
|
|
127
|
-
);
|
|
128
|
+
log(`[Proxy] ✓ Rewrote cached URLs: ${originalHost} → ${proxyHost}`);
|
|
128
129
|
} catch (error) {
|
|
129
130
|
console.warn(
|
|
130
131
|
`[Proxy] Failed to rewrite cached URLs: ${error.message}`,
|
|
@@ -199,7 +200,7 @@ export function recordReplayProxyPlugin() {
|
|
|
199
200
|
const dataFile = join(cacheDir, "data.bin");
|
|
200
201
|
await writeFile(dataFile, body); // Write raw binary data
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
log("[Proxy] ✓ Cached response");
|
|
203
204
|
} catch (error) {
|
|
204
205
|
console.warn(`[Proxy] Failed to cache: ${error.message}`);
|
|
205
206
|
}
|
|
@@ -226,9 +227,9 @@ export function recordReplayProxyPlugin() {
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
const fullPath = apiPath;
|
|
229
|
-
|
|
230
|
+
log(`[Proxy] → ${req.method} ${fullPath}`);
|
|
230
231
|
if (req.headers.range) {
|
|
231
|
-
|
|
232
|
+
log(`[Proxy] Range: ${req.headers.range}`);
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
// Set CORS headers
|
|
@@ -257,9 +258,7 @@ export function recordReplayProxyPlugin() {
|
|
|
257
258
|
try {
|
|
258
259
|
const metadataFile = join(cacheDir, "metadata.json");
|
|
259
260
|
if (existsSync(metadataFile)) {
|
|
260
|
-
|
|
261
|
-
`[Proxy] ✓ CACHE-ONLY: Serving from cache: ${cacheKey}`,
|
|
262
|
-
);
|
|
261
|
+
log(`[Proxy] ✓ CACHE-ONLY: Serving from cache: ${cacheKey}`);
|
|
263
262
|
await serveCachedResponse(res, cacheDir, req);
|
|
264
263
|
return;
|
|
265
264
|
}
|
|
@@ -268,7 +267,7 @@ export function recordReplayProxyPlugin() {
|
|
|
268
267
|
}
|
|
269
268
|
}
|
|
270
269
|
|
|
271
|
-
|
|
270
|
+
log(`[Proxy] ✗ CACHE-ONLY: No cache available for ${cacheKey}`);
|
|
272
271
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
273
272
|
res.end(
|
|
274
273
|
JSON.stringify({
|
|
@@ -339,9 +338,7 @@ export function recordReplayProxyPlugin() {
|
|
|
339
338
|
responseHeaders["content-length"] = body.length.toString();
|
|
340
339
|
}
|
|
341
340
|
|
|
342
|
-
|
|
343
|
-
`[Proxy] ✓ Rewrote URLs: ${originalHost} → ${proxyHost}`,
|
|
344
|
-
);
|
|
341
|
+
log(`[Proxy] ✓ Rewrote URLs: ${originalHost} → ${proxyHost}`);
|
|
345
342
|
} catch (error) {
|
|
346
343
|
console.warn(`[Proxy] Failed to rewrite URLs: ${error.message}`);
|
|
347
344
|
// Continue with original body on error
|
|
@@ -350,8 +347,8 @@ export function recordReplayProxyPlugin() {
|
|
|
350
347
|
|
|
351
348
|
// If we get a 404, try to serve from cache first
|
|
352
349
|
if (response.status === 404) {
|
|
353
|
-
|
|
354
|
-
|
|
350
|
+
log("[Proxy] ✗ Target server returned 404");
|
|
351
|
+
log(`[Proxy] Checking cache: ${cacheKey}`);
|
|
355
352
|
|
|
356
353
|
if (existsSync(cacheDir)) {
|
|
357
354
|
try {
|
|
@@ -360,7 +357,7 @@ export function recordReplayProxyPlugin() {
|
|
|
360
357
|
const metadata = JSON.parse(
|
|
361
358
|
await readFile(metadataFile, "utf-8"),
|
|
362
359
|
);
|
|
363
|
-
|
|
360
|
+
log(
|
|
364
361
|
`[Proxy] ✓ Serving from cache instead of 404 (${metadata.timestamp})`,
|
|
365
362
|
);
|
|
366
363
|
await serveCachedResponse(res, cacheDir, req);
|
|
@@ -373,7 +370,7 @@ export function recordReplayProxyPlugin() {
|
|
|
373
370
|
}
|
|
374
371
|
}
|
|
375
372
|
|
|
376
|
-
|
|
373
|
+
log("[Proxy] ✗ No cache available, passing through 404");
|
|
377
374
|
}
|
|
378
375
|
|
|
379
376
|
res.writeHead(response.status, responseHeaders);
|
|
@@ -391,8 +388,8 @@ export function recordReplayProxyPlugin() {
|
|
|
391
388
|
);
|
|
392
389
|
}
|
|
393
390
|
} catch (err) {
|
|
394
|
-
|
|
395
|
-
|
|
391
|
+
log(`[Proxy] ✗ Real server failed: ${err.message}`);
|
|
392
|
+
log(`[Proxy] Checking cache: ${cacheKey}`);
|
|
396
393
|
|
|
397
394
|
if (existsSync(cacheDir)) {
|
|
398
395
|
try {
|
|
@@ -401,9 +398,7 @@ export function recordReplayProxyPlugin() {
|
|
|
401
398
|
const metadata = JSON.parse(
|
|
402
399
|
await readFile(metadataFile, "utf-8"),
|
|
403
400
|
);
|
|
404
|
-
|
|
405
|
-
`[Proxy] ✓ Serving from cache (${metadata.timestamp})`,
|
|
406
|
-
);
|
|
401
|
+
log(`[Proxy] ✓ Serving from cache (${metadata.timestamp})`);
|
|
407
402
|
await serveCachedResponse(res, cacheDir, req);
|
|
408
403
|
return;
|
|
409
404
|
}
|
|
@@ -414,7 +409,7 @@ export function recordReplayProxyPlugin() {
|
|
|
414
409
|
}
|
|
415
410
|
}
|
|
416
411
|
|
|
417
|
-
|
|
412
|
+
log("[Proxy] ✗ No cache available, failing request");
|
|
418
413
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
419
414
|
res.end(
|
|
420
415
|
JSON.stringify({
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Thumbnail Performance Test</title>
|
|
5
|
+
<style>
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 20px;
|
|
9
|
+
font-family: system-ui;
|
|
10
|
+
}
|
|
11
|
+
ef-timegroup {
|
|
12
|
+
width: 1920px;
|
|
13
|
+
height: 1080px;
|
|
14
|
+
background: #000;
|
|
15
|
+
}
|
|
16
|
+
#results {
|
|
17
|
+
margin-top: 20px;
|
|
18
|
+
font-family: monospace;
|
|
19
|
+
white-space: pre-wrap;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<h1>Thumbnail Capture Performance Test</h1>
|
|
25
|
+
<p>Testing captureBatch with yield between captures and size limits</p>
|
|
26
|
+
|
|
27
|
+
<ef-timegroup id="test-timegroup" mode="contain" fps="30" duration="10000">
|
|
28
|
+
<ef-video src="test-assets/bars-n-tone.mp4" start-time="0" duration="10000"></ef-video>
|
|
29
|
+
</ef-timegroup>
|
|
30
|
+
|
|
31
|
+
<div id="results"></div>
|
|
32
|
+
|
|
33
|
+
<script type="module">
|
|
34
|
+
import '/packages/elements/src/elements/EFTimegroup.js';
|
|
35
|
+
import '/packages/elements/src/elements/EFVideo.js';
|
|
36
|
+
|
|
37
|
+
const timegroup = document.querySelector('ef-timegroup');
|
|
38
|
+
const results = document.getElementById('results');
|
|
39
|
+
|
|
40
|
+
async function runTest() {
|
|
41
|
+
results.textContent = 'Waiting for timegroup to initialize...\n';
|
|
42
|
+
|
|
43
|
+
// Wait for media to load
|
|
44
|
+
await timegroup.updateComplete;
|
|
45
|
+
if (timegroup.waitForMediaDurations) {
|
|
46
|
+
await timegroup.waitForMediaDurations();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Generate timestamps for 15 thumbnails
|
|
50
|
+
const duration = timegroup.durationMs;
|
|
51
|
+
const timestamps = [];
|
|
52
|
+
for (let i = 0; i < 15; i++) {
|
|
53
|
+
timestamps.push((i * duration) / 14);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
results.textContent += `Testing captureBatch with ${timestamps.length} thumbnails...\n`;
|
|
57
|
+
results.textContent += `Duration: ${duration}ms\n`;
|
|
58
|
+
results.textContent += `Timestamps: ${timestamps.map(t => Math.round(t)).join(', ')}ms\n\n`;
|
|
59
|
+
|
|
60
|
+
// Measure captureBatch performance
|
|
61
|
+
const startTime = performance.now();
|
|
62
|
+
const canvases = await timegroup.captureBatch(timestamps, {
|
|
63
|
+
scale: 0.25,
|
|
64
|
+
contentReadyMode: 'immediate'
|
|
65
|
+
});
|
|
66
|
+
const endTime = performance.now();
|
|
67
|
+
const elapsed = endTime - startTime;
|
|
68
|
+
|
|
69
|
+
results.textContent += `\n✅ Capture completed!\n`;
|
|
70
|
+
results.textContent += `Total time: ${elapsed.toFixed(2)}ms\n`;
|
|
71
|
+
results.textContent += `Average per thumbnail: ${(elapsed / timestamps.length).toFixed(2)}ms\n`;
|
|
72
|
+
results.textContent += `Canvases captured: ${canvases.length}\n`;
|
|
73
|
+
|
|
74
|
+
// Check canvas dimensions
|
|
75
|
+
if (canvases.length > 0) {
|
|
76
|
+
const firstCanvas = canvases[0];
|
|
77
|
+
results.textContent += `\nCanvas dimensions:\n`;
|
|
78
|
+
results.textContent += ` Width: ${firstCanvas.width}px (logical: ${firstCanvas.style.width})\n`;
|
|
79
|
+
results.textContent += ` Height: ${firstCanvas.height}px (logical: ${firstCanvas.style.height})\n`;
|
|
80
|
+
|
|
81
|
+
// Verify max size limit (480px max width)
|
|
82
|
+
const logicalWidth = parseInt(firstCanvas.style.width);
|
|
83
|
+
if (logicalWidth <= 480) {
|
|
84
|
+
results.textContent += ` ✅ Size limit enforced (${logicalWidth}px <= 480px)\n`;
|
|
85
|
+
} else {
|
|
86
|
+
results.textContent += ` ⚠️ Size limit NOT enforced (${logicalWidth}px > 480px)\n`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Test responsiveness - try to interact during capture
|
|
91
|
+
results.textContent += `\nTesting responsiveness during capture...\n`;
|
|
92
|
+
let interactionCount = 0;
|
|
93
|
+
const interactionInterval = setInterval(() => {
|
|
94
|
+
interactionCount++;
|
|
95
|
+
results.textContent += ` Interaction ${interactionCount} processed\n`;
|
|
96
|
+
}, 10);
|
|
97
|
+
|
|
98
|
+
const startTime2 = performance.now();
|
|
99
|
+
const canvases2 = await timegroup.captureBatch(timestamps.slice(0, 5), {
|
|
100
|
+
scale: 0.25,
|
|
101
|
+
contentReadyMode: 'immediate'
|
|
102
|
+
});
|
|
103
|
+
const endTime2 = performance.now();
|
|
104
|
+
clearInterval(interactionInterval);
|
|
105
|
+
|
|
106
|
+
results.textContent += `\n✅ Responsiveness test completed!\n`;
|
|
107
|
+
results.textContent += `Capture time: ${(endTime2 - startTime2).toFixed(2)}ms\n`;
|
|
108
|
+
results.textContent += `Interactions processed: ${interactionCount}\n`;
|
|
109
|
+
results.textContent += ` (Higher is better - shows browser stayed responsive)\n`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Run test after a short delay to ensure everything is loaded
|
|
113
|
+
setTimeout(runTest, 1000);
|
|
114
|
+
</script>
|
|
115
|
+
</body>
|
|
116
|
+
</html>
|