Skip to content

Commit 9157443

Browse files
Add test for cross-origin iframes
1 parent 7db564d commit 9157443

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

shepherd.js/test/unit/components/shepherd-modal.spec.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)