diff --git a/src/courseware/course/sequence/Unit.jsx b/src/courseware/course/sequence/Unit.jsx index 31a9bf9343..af4f85aa12 100644 --- a/src/courseware/course/sequence/Unit.jsx +++ b/src/courseware/course/sequence/Unit.jsx @@ -96,6 +96,7 @@ function Unit({ const [showError, setShowError] = useState(false); const [modalOptions, setModalOptions] = useState({ open: false }); const [shouldDisplayHonorCode, setShouldDisplayHonorCode] = useState(false); + const [windowTopOffset, setWindowTopOffset] = useState(null); const unit = useModel('units', id); const course = useModel('coursewareMeta', courseId); @@ -123,6 +124,13 @@ function Unit({ } = data; if (type === 'plugin.resize') { setIframeHeight(payload.height); + + // We observe exit from the video xblock full screen mode + // and do page scroll to the previously saved scroll position + if (windowTopOffset !== null) { + window.scrollTo(0, Number(windowTopOffset)); + } + if (!hasLoaded && iframeHeight === 0 && payload.height > 0) { setHasLoaded(true); if (onLoaded) { @@ -132,12 +140,16 @@ function Unit({ } else if (type === 'plugin.modal') { payload.open = true; setModalOptions(payload); + } else if (type === 'plugin.videoFullScreen') { + // We listen for this message from LMS to know when we need to + // save or reset scroll position on toggle video xblock full screen mode. + setWindowTopOffset(payload.open ? window.scrollY : null); } else if (data.offset) { // We listen for this message from LMS to know when the page needs to // be scrolled to another location on the page. window.scrollTo(0, data.offset + document.getElementById('unit-iframe').offsetTop); } - }, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded]); + }, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded, setWindowTopOffset, windowTopOffset]); useEventListener('message', receiveMessage); useEffect(() => { sendUrlHashToFrame(document.getElementById('unit-iframe')); diff --git a/src/courseware/course/sequence/Unit.test.jsx b/src/courseware/course/sequence/Unit.test.jsx index 402c620a88..e7f7e2ad6e 100644 --- a/src/courseware/course/sequence/Unit.test.jsx +++ b/src/courseware/course/sequence/Unit.test.jsx @@ -131,6 +131,21 @@ describe('Unit', () => { expect(window.scrollY === testMessageWithOffset.offset); }); + it('scrolls page on MessageEvent when receiving videoFullScreen state', async () => { + // Set message to contain video full screen data. + const defaultTopOffset = 800; + const testMessageWithOtherHeight = { ...messageEvent, payload: { height: 500 } }; + const testMessageWithFullscreenState = (isOpen) => ({ type: 'plugin.videoFullScreen', payload: { open: isOpen } }); + render(); + Object.defineProperty(window, 'scrollY', { value: defaultTopOffset, writable: true }); + window.postMessage(testMessageWithFullscreenState(true), '*'); + window.postMessage(testMessageWithFullscreenState(false), '*'); + window.postMessage(testMessageWithOtherHeight, '*'); + + await expect(waitFor(() => expect(window.scrollTo()).toHaveBeenCalledTimes(1))); + expect(window.scrollY === defaultTopOffset); + }); + it('ignores MessageEvent with unhandled type', async () => { // Clone message and set different type. const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' };