@@ -7,6 +7,35 @@ import { STUDIO_MOTION_PATH } from "../components/editor/studioMotion";
77import { shouldHandleTimelineToggleHotkey , isEditableTarget } from "../utils/timelineDiscovery" ;
88import { shouldIgnoreHistoryShortcut } from "../utils/studioHelpers" ;
99
10+ /** Safely resolves contentWindow for a potentially cross-origin iframe. */
11+ function iframeContentWindow ( iframe : HTMLIFrameElement | null ) : Window | null {
12+ try {
13+ return iframe ?. contentWindow ?? null ;
14+ } catch {
15+ return null ;
16+ }
17+ }
18+
19+ /**
20+ * Handles Cmd/Ctrl+Z (undo) and Cmd/Ctrl+Shift+Z / Ctrl+Y (redo) key events.
21+ * Returns true if the event was handled, false otherwise.
22+ */
23+ // fallow-ignore-next-line complexity
24+ function handleUndoRedoKey ( event : KeyboardEvent , onUndo : ( ) => void , onRedo : ( ) => void ) : boolean {
25+ const key = event . key . toLowerCase ( ) ;
26+ if ( key === "z" && ! event . shiftKey ) {
27+ event . preventDefault ( ) ;
28+ onUndo ( ) ;
29+ return true ;
30+ }
31+ if ( ( key === "z" && event . shiftKey ) || ( event . ctrlKey && ! event . metaKey && key === "y" ) ) {
32+ event . preventDefault ( ) ;
33+ onRedo ( ) ;
34+ return true ;
35+ }
36+ return false ;
37+ }
38+
1039// ── Types ──
1140
1241interface EditHistoryHandle {
@@ -177,18 +206,15 @@ export function useAppHotkeys({
177206
178207 // Cmd/Ctrl+Z — undo, Cmd/Ctrl+Shift+Z or Ctrl+Y — redo
179208 if ( event . metaKey || event . ctrlKey ) {
180- if ( ! shouldIgnoreHistoryShortcut ( event . target ) ) {
181- const key = event . key . toLowerCase ( ) ;
182- if ( key === "z" && ! event . shiftKey ) {
183- event . preventDefault ( ) ;
184- void handleUndoRef . current ( ) ;
185- return ;
186- }
187- if ( ( key === "z" && event . shiftKey ) || ( event . ctrlKey && ! event . metaKey && key === "y" ) ) {
188- event . preventDefault ( ) ;
189- void handleRedoRef . current ( ) ;
190- return ;
191- }
209+ if (
210+ ! shouldIgnoreHistoryShortcut ( event . target ) &&
211+ handleUndoRedoKey (
212+ event ,
213+ ( ) => void handleUndoRef . current ( ) ,
214+ ( ) => void handleRedoRef . current ( ) ,
215+ )
216+ ) {
217+ return ;
192218 }
193219
194220 // Cmd/Ctrl+1 — sidebar: Compositions tab
@@ -310,21 +336,33 @@ export function useAppHotkeys({
310336
311337 const syncPreviewTimelineHotkey = useCallback (
312338 ( iframe : HTMLIFrameElement | null ) => {
313- const nextWindow = iframe ?. contentWindow ?? null ;
339+ const nextWindow = iframeContentWindow ( iframe ) ;
314340 if ( previewHotkeyWindowRef . current === nextWindow ) return ;
315341 if ( previewHotkeyWindowRef . current ) {
316- previewHotkeyWindowRef . current . removeEventListener ( "keydown" , previewAppKeyDownHandler ) ;
342+ try {
343+ previewHotkeyWindowRef . current . removeEventListener ( "keydown" , previewAppKeyDownHandler ) ;
344+ } catch {
345+ /* cross-origin iframe */
346+ }
317347 }
318348 previewHotkeyWindowRef . current = nextWindow ;
319- nextWindow ?. addEventListener ( "keydown" , previewAppKeyDownHandler , true ) ;
349+ try {
350+ nextWindow ?. addEventListener ( "keydown" , previewAppKeyDownHandler , true ) ;
351+ } catch {
352+ /* cross-origin iframe */
353+ }
320354 } ,
321355 [ previewAppKeyDownHandler ] ,
322356 ) ;
323357
324358 useEffect (
325359 ( ) => ( ) => {
326360 if ( previewHotkeyWindowRef . current ) {
327- previewHotkeyWindowRef . current . removeEventListener ( "keydown" , previewAppKeyDownHandler ) ;
361+ try {
362+ previewHotkeyWindowRef . current . removeEventListener ( "keydown" , previewAppKeyDownHandler ) ;
363+ } catch {
364+ /* cross-origin iframe */
365+ }
328366 previewHotkeyWindowRef . current = null ;
329367 }
330368 } ,
@@ -336,24 +374,19 @@ export function useAppHotkeys({
336374 const handleHistoryHotkey = useCallback ( ( event : KeyboardEvent ) => {
337375 if ( ! ( event . metaKey || event . ctrlKey ) ) return ;
338376 if ( shouldIgnoreHistoryShortcut ( event . target ) ) return ;
339- const key = event . key . toLowerCase ( ) ;
340- if ( key === "z" && ! event . shiftKey ) {
341- event . preventDefault ( ) ;
342- void handleUndoRef . current ( ) ;
343- return ;
344- }
345- if ( ( key === "z" && event . shiftKey ) || ( event . ctrlKey && ! event . metaKey && key === "y" ) ) {
346- event . preventDefault ( ) ;
347- void handleRedoRef . current ( ) ;
348- }
377+ handleUndoRedoKey (
378+ event ,
379+ ( ) => void handleUndoRef . current ( ) ,
380+ ( ) => void handleRedoRef . current ( ) ,
381+ ) ;
349382 } , [ ] ) ;
350383
351384 const syncPreviewHistoryHotkey = useCallback (
352385 ( iframe : HTMLIFrameElement | null ) => {
353386 previewHistoryHotkeyCleanupRef . current ?.( ) ;
354387 previewHistoryHotkeyCleanupRef . current = null ;
355388
356- const win = iframe ?. contentWindow ?? null ;
389+ const win = iframeContentWindow ( iframe ) ;
357390 let doc : Document | null = null ;
358391 try {
359392 doc = iframe ?. contentDocument ?? null ;
@@ -362,10 +395,18 @@ export function useAppHotkeys({
362395 }
363396 if ( ! win && ! doc ) return ;
364397
365- win ?. addEventListener ( "keydown" , handleHistoryHotkey , true ) ;
398+ try {
399+ win ?. addEventListener ( "keydown" , handleHistoryHotkey , true ) ;
400+ } catch {
401+ /* cross-origin */
402+ }
366403 doc ?. addEventListener ( "keydown" , handleHistoryHotkey , true ) ;
367404 previewHistoryHotkeyCleanupRef . current = ( ) => {
368- win ?. removeEventListener ( "keydown" , handleHistoryHotkey , true ) ;
405+ try {
406+ win ?. removeEventListener ( "keydown" , handleHistoryHotkey , true ) ;
407+ } catch {
408+ /* cross-origin */
409+ }
369410 doc ?. removeEventListener ( "keydown" , handleHistoryHotkey , true ) ;
370411 } ;
371412 } ,
0 commit comments