@@ -719,5 +719,71 @@ describe('components/ShepherdModal', () => {
719719
720720 rafSpy . mockRestore ( ) ;
721721 } ) ;
722+
723+ it ( 'handles cross-origin iframe SecurityError gracefully' , ( ) => {
724+ // Regression test for https://github.com/shipshapecode/shepherd/issues/3087
725+ // When Shepherd is loaded in a nested cross-origin iframe, accessing
726+ // window.frameElement throws a SecurityError due to Same-Origin Policy.
727+ // This test ensures the error is caught and handled gracefully.
728+ const modal = createShepherdModal ( container ) ;
729+ const rafSpy = vi
730+ . spyOn ( window , 'requestAnimationFrame' )
731+ . mockImplementation ( ( ) => 1 ) ;
732+
733+ const targetEl = document . createElement ( 'div' ) ;
734+ container . appendChild ( targetEl ) ;
735+
736+ // Simulate a cross-origin iframe by making frameElement access throw SecurityError
737+ const fakeChildWindow = {
738+ get frameElement ( ) {
739+ // Simulate browser's SecurityError when accessing cross-origin frameElement
740+ const error = new Error (
741+ 'Blocked a frame with origin "https://example.com" from accessing a cross-origin frame.'
742+ ) ;
743+ error . name = 'SecurityError' ;
744+ throw error ;
745+ } ,
746+ parent : window
747+ } ;
748+
749+ const origDescriptor = Object . getOwnPropertyDescriptor (
750+ targetEl . ownerDocument ,
751+ 'defaultView'
752+ ) ;
753+ Object . defineProperty ( targetEl . ownerDocument , 'defaultView' , {
754+ value : fakeChildWindow ,
755+ configurable : true
756+ } ) ;
757+
758+ const tour = new Tour ( { useModalOverlay : true } ) ;
759+ const step = new Step ( tour , {
760+ attachTo : { element : targetEl , on : 'bottom' }
761+ } ) ;
762+ step . _resolveAttachToOptions ( ) ;
763+ step . target = targetEl ;
764+
765+ // This should NOT throw an error, even though frameElement access throws SecurityError
766+ expect ( ( ) => {
767+ modal . setupForStep ( step ) ;
768+ } ) . not . toThrow ( ) ;
769+
770+ // Restore defaultView before any assertions
771+ if ( origDescriptor ) {
772+ Object . defineProperty (
773+ targetEl . ownerDocument ,
774+ 'defaultView' ,
775+ origDescriptor
776+ ) ;
777+ } else {
778+ Object . defineProperty ( targetEl . ownerDocument , 'defaultView' , {
779+ value : window ,
780+ configurable : true
781+ } ) ;
782+ }
783+
784+ expect ( modal . getElement ( ) ) . toHaveClass ( 'shepherd-modal-is-visible' ) ;
785+
786+ rafSpy . mockRestore ( ) ;
787+ } ) ;
722788 } ) ;
723789} ) ;
0 commit comments