@@ -95,11 +91,11 @@ QUnit.module('Messages', suiteHooks => { const toolContentWrapper = el.find('.tool_content_wrapper') equal(toolContentWrapper.height(), 100) - ltiMessageHandler(postMessageEvent(resizeMessage)) + await ltiMessageHandler(postMessageEvent(resizeMessage)) equal(toolContentWrapper.height(), finalHeight) }) - test('finds and resizes an iframe in embedded content', () => { + test('finds and resizes an iframe in embedded content', async () => { ltiToolWrapperFixture.append(`

LTI resize test

@@ -109,11 +105,11 @@ QUnit.module('Messages', suiteHooks => { const iframe = $('iframe') equal(iframe.height(), 100) - ltiMessageHandler(postMessageEvent(resizeMessage, iframe[0].contentWindow)) + await ltiMessageHandler(postMessageEvent(resizeMessage, iframe[0].contentWindow)) equal(iframe.height(), finalHeight) }) - test('returns the hight and width of the page along with the iframe offset', () => { + test('returns the height and width of the page along with the iframe offset', async () => { ltiToolWrapperFixture.append(`

LTI resize test

@@ -124,11 +120,11 @@ QUnit.module('Messages', suiteHooks => { sinon.spy(iframe[0].contentWindow, 'postMessage') notOk(iframe[0].contentWindow.postMessage.calledOnce) - ltiMessageHandler(postMessageEvent(fetchWindowSize, iframe[0].contentWindow)) + await ltiMessageHandler(postMessageEvent(fetchWindowSize, iframe[0].contentWindow)) ok(iframe[0].contentWindow.postMessage.calledOnce) }) - test('hides the module navigation', () => { + test('hides the module navigation', async () => { ltiToolWrapperFixture.append(`
@@ -137,28 +133,28 @@ QUnit.module('Messages', suiteHooks => { const moduleFooter = $('#module-footer') ok(moduleFooter.is(':visible')) - ltiMessageHandler(postMessageEvent(showMessage(false))) + await ltiMessageHandler(postMessageEvent(showMessage(false))) notOk(moduleFooter.is(':visible')) }) - test('sets the unload message', () => { + test('sets the unload message', async () => { sinon.spy(window, 'addEventListener') notOk(window.addEventListener.calledOnce) - ltiMessageHandler(postMessageEvent(unloadMessage())) + await ltiMessageHandler(postMessageEvent(unloadMessage())) ok(window.addEventListener.calledOnce) }) - test('remove the unload message', () => { - ltiMessageHandler(postMessageEvent(unloadMessage())) + test('remove the unload message', async () => { + await ltiMessageHandler(postMessageEvent(unloadMessage())) sinon.spy(window, 'removeEventListener') notOk(window.removeEventListener.calledOnce) - ltiMessageHandler(postMessageEvent(removeUnloadMessage)) + await ltiMessageHandler(postMessageEvent(removeUnloadMessage)) ok(window.removeEventListener.calledOnce) }) - test('triggers a screen reader alert', () => { + test('triggers a screen reader alert', async () => { sinon.spy($, 'screenReaderFlashMessageExclusive') - ltiMessageHandler(postMessageEvent(alertMessage())) + await ltiMessageHandler(postMessageEvent(alertMessage())) ok($.screenReaderFlashMessageExclusive.calledOnce) }) }) diff --git a/ui/shared/lti/jquery/__tests__/messages.test.js b/ui/shared/lti/jquery/__tests__/messages.test.js index 82002b8a12f..265500d8bd5 100644 --- a/ui/shared/lti/jquery/__tests__/messages.test.js +++ b/ui/shared/lti/jquery/__tests__/messages.test.js @@ -17,7 +17,7 @@ */ import {ltiMessageHandler} from '../messages' -import $ from 'jquery' +import $ from '@canvas/rails-flash-notifications' describe('ltiMessageHander', () => { /* eslint-disable no-console */ @@ -39,30 +39,22 @@ describe('ltiMessageHander', () => { }) /* eslint-enable no-console */ - it('does not log unparseable messages from window.postMessage', () => { - ltiMessageHandler({data: 'abcdef'}) + it('does not log unparseable messages from window.postMessage', async () => { + await ltiMessageHandler({data: 'abcdef'}) expect(logMock).not.toHaveBeenCalled() expect(errorMock).not.toHaveBeenCalled() }) - it('does not log ignored messages from window.postMessage', () => { - ltiMessageHandler({data: JSON.stringify({a: 'b', c: 'd'})}) - ltiMessageHandler({data: {abc: 'def'}}) + it('does not log ignored messages from window.postMessage', async () => { + await ltiMessageHandler({data: JSON.stringify({a: 'b', c: 'd'})}) + await ltiMessageHandler({data: {abc: 'def'}}) expect(logMock).not.toHaveBeenCalled() expect(errorMock).not.toHaveBeenCalled() }) - it('handles parseable messages from window.postMessage', () => { + it('handles parseable messages from window.postMessage', async () => { const flashMessage = jest.spyOn($, 'screenReaderFlashMessageExclusive') - ltiMessageHandler({data: JSON.stringify({subject: 'lti.screenReaderAlert', body: 'Hi'})}) + await ltiMessageHandler({data: JSON.stringify({subject: 'lti.screenReaderAlert', body: 'Hi'})}) expect(flashMessage).toHaveBeenCalledWith('Hi') }) - - it('prevents html from being passed to screenReaderFlashMessageExclusive', () => { - const flashMessage = jest.spyOn($, 'screenReaderFlashMessageExclusive') - ltiMessageHandler({ - data: JSON.stringify({subject: 'lti.screenReaderAlert', body: {html: 'abc'}}) - }) - expect(flashMessage).toHaveBeenCalledWith('{"html":"abc"}') - }) }) diff --git a/ui/shared/lti/jquery/messages.js b/ui/shared/lti/jquery/messages.js index cc2e14fce41..e37a5145935 100644 --- a/ui/shared/lti/jquery/messages.js +++ b/ui/shared/lti/jquery/messages.js @@ -18,28 +18,44 @@ /* eslint no-console: 0 */ -import $ from 'jquery' -import '@canvas/rails-flash-notifications' -import htmlEscape from 'html-escape' -import ToolLaunchResizer from './tool_launch_resizer' -import handleLtiPostMessage from './post_message/handleLtiPostMessage' -import {setUnloadMessage, removeUnloadMessage, findDomForWindow} from './util' +import {findDomForWindow} from './util' +import { + NAVIGATION_MESSAGE as MENTIONS_NAVIGATION_MESSAGE, + INPUT_CHANGE_MESSAGE as MENTIONS_INPUT_CHANGE_MESSAGE, + SELECTION_MESSAGE as MENTIONS_SELECTION_MESSAGE +} from '../../rce/plugins/canvas_mentions/constants' // page-global storage for data relevant to LTI postMessage events const ltiState = {} -export {ltiState} -export function ltiMessageHandler(e) { +const SUBJECT_ALLOW_LIST = [ + 'lti.enableScrollEvents', + 'lti.fetchWindowSize', + 'lti.frameResize', + 'lti.removeUnloadMessage', + 'lti.resourceImported', + 'lti.screenReaderAlert', + 'lti.scrollToTop', + 'lti.setUnloadMessage', + 'lti.showModuleNavigation', + 'requestFullWindowLaunch', + 'toggleCourseNavigationMenu' +] + +// These are handled elsewhere so ignore them +const SUBJECT_IGNORE_LIST = [ + 'A2ExternalContentReady', + 'LtiDeepLinkingResponse', + MENTIONS_NAVIGATION_MESSAGE, + MENTIONS_INPUT_CHANGE_MESSAGE, + MENTIONS_SELECTION_MESSAGE +] + +async function ltiMessageHandler(e) { if (e.data.source && e.data.source.includes('react-devtools')) { return } - if (e.data.messageType) { - handleLtiPostMessage(e) - return - } - - // Legacy post message handlers let message try { message = typeof e.data === 'string' ? JSON.parse(e.data) : e.data @@ -48,109 +64,26 @@ export function ltiMessageHandler(e) { return } + // look at messageType for backwards compatibility + const subject = message.subject || message.messageType + + if (SUBJECT_IGNORE_LIST.includes(subject) || !SUBJECT_ALLOW_LIST.includes(subject)) { + return false + } + try { - switch (message.subject) { - case 'lti.frameResize': { - const toolResizer = new ToolLaunchResizer() - let height = message.height - if (height <= 0) height = 1 - - const container = toolResizer - .tool_content_wrapper(message.token || e.origin) - .data('height_overridden', true) - // If content.length is 0 then jquery didn't the tool wrapper. - if (container.length > 0) { - toolResizer.resize_tool_content_wrapper(height, container) - } else { - // Attempt to find an embedded iframe that matches the event source. - const iframe = findDomForWindow(e.source) - if (iframe) { - if (typeof height === 'number') { - height += 'px' - } - iframe.height = height - iframe.style.height = height - } - } - break - } - case 'lti.fetchWindowSize': { - const iframe = findDomForWindow(e.source) - if (iframe) { - message.height = window.innerHeight - message.width = window.innerWidth - message.offset = $('.tool_content_wrapper').offset() - message.footer = $('#fixed_bottom').height() || 0 - message.scrollY = window.scrollY - const strMessage = JSON.stringify(message) - - iframe.contentWindow.postMessage(strMessage, '*') - } - break - } - - case 'lti.showModuleNavigation': - if (message.show === true || message.show === false) { - $('.module-sequence-footer').toggle(message.show) - } - break - - case 'lti.scrollToTop': - $('html,body').animate( - { - scrollTop: $('.tool_content_wrapper').offset().top - }, - 'fast' - ) - break - - case 'lti.setUnloadMessage': - setUnloadMessage(htmlEscape(message.message)) - break - - case 'lti.removeUnloadMessage': - removeUnloadMessage() - break - - case 'lti.screenReaderAlert': - $.screenReaderFlashMessageExclusive( - typeof message.body === 'string' ? message.body : JSON.stringify(message.body) - ) - break - - case 'lti.enableScrollEvents': { - const iframe = findDomForWindow(e.source) - if (iframe) { - let timeout - window.addEventListener( - 'scroll', - () => { - // requesting animation frames effectively debounces the scroll messages being sent - if (timeout) { - window.cancelAnimationFrame(timeout) - } - - timeout = window.requestAnimationFrame(() => { - const msg = JSON.stringify({ - subject: 'lti.scroll', - scrollY: window.scrollY - }) - iframe.contentWindow.postMessage(msg, '*') - }) - }, - false - ) - } - break - } - } - } catch (err) { - ;(console.error || console.log).call(console, 'invalid message received from') + const handlerModule = await import(`./post_message/${subject}.js`) + handlerModule.default({message, iframe: findDomForWindow(e.source), event: e}) + return true + } catch (error) { + console.error(`Error loading or executing message handler for "${subject}"`, error) } } -export function monitorLtiMessages() { +function monitorLtiMessages() { window.addEventListener('message', e => { if (e.data !== '') ltiMessageHandler(e) }) } + +export {ltiState, SUBJECT_ALLOW_LIST, SUBJECT_IGNORE_LIST, ltiMessageHandler, monitorLtiMessages} diff --git a/ui/shared/lti/jquery/post_message/__tests__/handleLtiPostMessage.test.js b/ui/shared/lti/jquery/post_message/__tests__/handleLtiPostMessage.test.js deleted file mode 100644 index af29accafad..00000000000 --- a/ui/shared/lti/jquery/post_message/__tests__/handleLtiPostMessage.test.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2019 - present Instructure, Inc. - * - * This file is part of Canvas. - * - * Canvas is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3 of the License. - * - * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License along - * with this program. If not, see . - */ - -import handleLtiPostMessage from '../handleLtiPostMessage' -import {ltiState} from '../../messages' - -const requestFullWindowLaunchMessage = { - messageType: 'requestFullWindowLaunch', - data: 'http://localhost/test' -} - -const reactDevToolsBridge = { - data: 'http://localhost/test', - source: 'react-devtools-bridge' -} - -function postMessageEvent(data, origin, source) { - return { - data, - origin, - source - } -} - -function invalidMessageTypeErrorCalls() { - // eslint-disable-next-line no-console - return console.error.mock.calls.filter(x => x.toString().includes('invalid messageType')) -} - -describe('handleLtiPostMessage', () => { - beforeEach(() => { - jest.spyOn(console, 'error') - }) - - afterEach(() => { - // eslint-disable-next-line no-console - console.error.mockRestore() - }) - - describe('when a whitelisted event is processed', () => { - it('attempts to call the message handler', async () => { - ENV.context_asset_string = 'account_1' - const wasCalled = await handleLtiPostMessage(postMessageEvent(requestFullWindowLaunchMessage)) - expect(wasCalled).toBeTruthy() - expect(invalidMessageTypeErrorCalls().length).toBe(0) - }) - }) - - describe('when a non-whitelisted event is processed', () => { - it('does not error nor attempt to call the message handler', async () => { - const wasCalled = await handleLtiPostMessage(postMessageEvent({messageType: 'notSupported'})) - expect(wasCalled).toBeFalsy() - expect(invalidMessageTypeErrorCalls().length).toBe(1) - }) - }) - - describe('when an ignored event is processed', () => { - it('does not attempt to call the message handler', async () => { - const wasCalled = await handleLtiPostMessage( - postMessageEvent({messageType: 'LtiDeepLinkingResponse'}) - ) - expect(wasCalled).toBeFalsy() - expect(invalidMessageTypeErrorCalls().length).toBe(0) - }) - }) - - describe('when source is react-dev-tools', () => { - it('does not attempt to call the message handler', async () => { - const wasCalled = await handleLtiPostMessage(postMessageEvent(reactDevToolsBridge)) - expect(wasCalled).toBeFalsy() - }) - }) -}) - -describe('ltiState', () => { - it('is empty initially', () => { - expect(ltiState).toEqual({}) - }) -}) diff --git a/ui/shared/lti/jquery/post_message/__tests__/lti.screenReaderAlert.test.js b/ui/shared/lti/jquery/post_message/__tests__/lti.screenReaderAlert.test.js new file mode 100644 index 00000000000..7c5abdb2ed8 --- /dev/null +++ b/ui/shared/lti/jquery/post_message/__tests__/lti.screenReaderAlert.test.js @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import handler from '../lti.screenReaderAlert' +import $ from '@canvas/rails-flash-notifications' + +describe('lti.screenReaderAlert handler', () => { + it('prevents html from being passed to screenReaderFlashMessageExclusive', () => { + const flashMessage = jest.spyOn($, 'screenReaderFlashMessageExclusive') + handler({ + message: {body: {html: 'abc'}} + }) + expect(flashMessage).toHaveBeenCalledWith('{"html":"abc"}') + }) +}) diff --git a/ui/shared/lti/jquery/post_message/__tests__/requestFullWindowLaunch.test.js b/ui/shared/lti/jquery/post_message/__tests__/requestFullWindowLaunch.test.js index 3ce8e172547..878c8693193 100644 --- a/ui/shared/lti/jquery/post_message/__tests__/requestFullWindowLaunch.test.js +++ b/ui/shared/lti/jquery/post_message/__tests__/requestFullWindowLaunch.test.js @@ -37,18 +37,18 @@ describe('requestFullWindowLaunch', () => { describe('with string provided', () => { it('uses launch type same_window', () => { - handler('http://localhost/test') + handler({message: {data: 'http://localhost/test'}}) expect(window.location.assign).toHaveBeenCalled() }) it('pulls out client_id if provided', () => { - handler('http://localhost/test?client_id=hello') + handler({message: {data: 'http://localhost/test?client_id=hello'}}) const launch_url = new URL(window.location.assign.mock.calls[0][0]) expect(launch_url.searchParams.get('client_id')).toEqual('hello') }) it('pulls out assignment_id if provided', () => { - handler('http://localhost/test?client_id=hello&assignment_id=50') + handler({message: {data: 'http://localhost/test?client_id=hello&assignment_id=50'}}) const launch_url = new URL(window.location.assign.mock.calls[0][0]) expect(launch_url.searchParams.get('assignment_id')).toEqual('50') }) @@ -56,21 +56,23 @@ describe('requestFullWindowLaunch', () => { describe('with object provided', () => { it('must contain a `url` property', () => { - expect(() => handler({foo: 'bar'})).toThrow('message must contain a `url` property') + expect(() => handler({message: {data: {foo: 'bar'}}})).toThrow( + 'message must contain a `url` property' + ) }) it('uses launch type same_window by default', () => { - handler({url: 'http://localhost/test'}) + handler({message: {data: {url: 'http://localhost/test'}}}) expect(window.location.assign).toHaveBeenCalled() }) it('opens launch type new_window in a new tab', () => { - handler({url: 'http://localhost/test', launchType: 'new_window'}) + handler({message: {data: {url: 'http://localhost/test', launchType: 'new_window'}}}) expect(window.open).toHaveBeenCalled() }) it('opens launch type popup in a popup window', () => { - handler({url: 'http://localhost/test', launchType: 'popup'}) + handler({message: {data: {url: 'http://localhost/test', launchType: 'popup'}}}) expect(window.open).toHaveBeenCalledWith( expect.any(String), 'popupLaunch', @@ -79,13 +81,13 @@ describe('requestFullWindowLaunch', () => { }) it('errors on unknown launch type', () => { - expect(() => handler({url: 'http://localhost/test', launchType: 'fake'})).toThrow( - "unknown launchType, must be 'popup', 'new_window', 'same_window'" - ) + expect(() => + handler({message: {data: {url: 'http://localhost/test', launchType: 'fake'}}}) + ).toThrow("unknown launchType, must be 'popup', 'new_window', 'same_window'") }) it('uses placement to add to launch url', () => { - handler({url: 'http://localhost/test', placement: 'course_navigation'}) + handler({message: {data: {url: 'http://localhost/test', placement: 'course_navigation'}}}) expect(window.location.assign).toHaveBeenCalledWith( expect.stringContaining('&placement=course_navigation') ) @@ -93,9 +95,13 @@ describe('requestFullWindowLaunch', () => { it('uses launchOptions to add width and height to popup', () => { handler({ - url: 'http://localhost/test', - launchType: 'popup', - launchOptions: {width: 420, height: 400} + message: { + data: { + url: 'http://localhost/test', + launchType: 'popup', + launchOptions: {width: 420, height: 400} + } + } }) expect(window.open).toHaveBeenCalledWith( expect.any(String), @@ -105,13 +111,13 @@ describe('requestFullWindowLaunch', () => { }) it('uses display type borderless by default', () => { - handler({url: 'http://localhost/test'}) + handler({message: {data: {url: 'http://localhost/test'}}}) const launch_url = new URL(window.location.assign.mock.calls[0][0]) expect(launch_url.searchParams.get('display')).toEqual('borderless') }) it('allows display type to be overridden', () => { - handler({url: 'http://localhost/test', display: 'full_width_in_context'}) + handler({message: {data: {url: 'http://localhost/test', display: 'full_width_in_context'}}}) const launch_url = new URL(window.location.assign.mock.calls[0][0]) expect(launch_url.searchParams.get('display')).toEqual('full_width_in_context') }) @@ -119,7 +125,7 @@ describe('requestFullWindowLaunch', () => { describe('with anything other than a string or object provided', () => { it('errors', () => { - expect(() => handler(['foo', 'bar'])).toThrow( + expect(() => handler({message: {data: ['foo', 'bar']}})).toThrow( 'message contents must either be a string or an object' ) }) diff --git a/ui/shared/lti/jquery/post_message/handleLtiPostMessage.js b/ui/shared/lti/jquery/post_message/handleLtiPostMessage.js deleted file mode 100644 index 1dd10354cb8..00000000000 --- a/ui/shared/lti/jquery/post_message/handleLtiPostMessage.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2019 - present Instructure, Inc. - * - * This file is part of Canvas. - * - * Canvas is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3 of the License. - * - * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License along - * with this program. If not, see . - */ - -import { - NAVIGATION_MESSAGE as MENTIONS_NAVIGATION_MESSAGE, - INPUT_CHANGE_MESSAGE as MENTIONS_INPUT_CHANGE_MESSAGE, - SELECTION_MESSAGE as MENTIONS_SELECTION_MESSAGE -} from '../../../rce/plugins/canvas_mentions/constants' - -const SUBJECT_ALLOW_LIST = [ - 'requestFullWindowLaunch', - 'lti.resourceImported', - 'toggleCourseNavigationMenu' -] - -// These are handled elsewhere so ignore them -const SUBJECT_IGNORE_LIST = [ - 'A2ExternalContentReady', - 'LtiDeepLinkingResponse', - MENTIONS_NAVIGATION_MESSAGE, - MENTIONS_INPUT_CHANGE_MESSAGE, - MENTIONS_SELECTION_MESSAGE -] - -const handleLtiPostMessage = async e => { - const {messageType, data} = e.data - let handler - - if (SUBJECT_IGNORE_LIST.includes(messageType)) { - // These messages are handled elsewhere - return false - } else if (!SUBJECT_ALLOW_LIST.includes(messageType)) { - // Enforce messageType allowlist -- unknown type - // eslint-disable-next-line no-console - console.error(`invalid messageType: ${messageType}`) - return false - } - - try { - const handlerModule = await import(`./${messageType}.js`) - handler = handlerModule.default - handler(data) - return true - } catch (error) { - // eslint-disable-next-line no-console - console.error(`Error loading or executing message handler for "${messageType}"`, error) - } -} - -export default handleLtiPostMessage diff --git a/ui/shared/lti/jquery/post_message/lti.enableScrollEvents.js b/ui/shared/lti/jquery/post_message/lti.enableScrollEvents.js new file mode 100644 index 00000000000..0ea1692e41a --- /dev/null +++ b/ui/shared/lti/jquery/post_message/lti.enableScrollEvents.js @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +export default function enableScrollEvents({iframe}) { + if (iframe) { + let timeout + window.addEventListener( + 'scroll', + () => { + // requesting animation frames effectively debounces the scroll messages being sent + if (timeout) { + window.cancelAnimationFrame(timeout) + } + + timeout = window.requestAnimationFrame(() => { + const msg = JSON.stringify({ + subject: 'lti.scroll', + scrollY: window.scrollY + }) + iframe.contentWindow.postMessage(msg, '*') + }) + }, + false + ) + } +} diff --git a/ui/shared/lti/jquery/post_message/lti.fetchWindowSize.js b/ui/shared/lti/jquery/post_message/lti.fetchWindowSize.js new file mode 100644 index 00000000000..d52223974f8 --- /dev/null +++ b/ui/shared/lti/jquery/post_message/lti.fetchWindowSize.js @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import $ from 'jquery' + +export default function fetchWindowSize({message, iframe}) { + if (iframe) { + message.height = window.innerHeight + message.width = window.innerWidth + message.offset = $('.tool_content_wrapper').offset() + message.footer = $('#fixed_bottom').height() || 0 + message.scrollY = window.scrollY + const strMessage = JSON.stringify(message) + + iframe.contentWindow.postMessage(strMessage, '*') + } +} diff --git a/ui/shared/lti/jquery/post_message/lti.frameResize.js b/ui/shared/lti/jquery/post_message/lti.frameResize.js new file mode 100644 index 00000000000..8ff09c20daf --- /dev/null +++ b/ui/shared/lti/jquery/post_message/lti.frameResize.js @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import ToolLaunchResizer from '../tool_launch_resizer' + +export default function frameResize({message, iframe, event}) { + const toolResizer = new ToolLaunchResizer() + let height = message.height + if (height <= 0) height = 1 + + const container = toolResizer + .tool_content_wrapper(message.token || event.origin) + .data('height_overridden', true) + // If content.length is 0 then jquery didn't the tool wrapper. + if (container.length > 0) { + toolResizer.resize_tool_content_wrapper(height, container) + } else if (iframe) { + // Attempt to find an embedded iframe that matches the event source. + if (typeof height === 'number') { + height += 'px' + } + iframe.height = height + iframe.style.height = height + } +} diff --git a/ui/shared/lti/jquery/post_message/lti.removeUnloadMessage.js b/ui/shared/lti/jquery/post_message/lti.removeUnloadMessage.js new file mode 100644 index 00000000000..6a8e14eb956 --- /dev/null +++ b/ui/shared/lti/jquery/post_message/lti.removeUnloadMessage.js @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import {removeUnloadMessage} from '../util' + +export default function remove() { + removeUnloadMessage() +} diff --git a/ui/shared/lti/jquery/post_message/lti.resourceImported.js b/ui/shared/lti/jquery/post_message/lti.resourceImported.js index 4d9b2d7cff9..d73588a3099 100644 --- a/ui/shared/lti/jquery/post_message/lti.resourceImported.js +++ b/ui/shared/lti/jquery/post_message/lti.resourceImported.js @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License along * with this program. If not, see . */ + import {ltiState} from '../messages' const handler = () => { diff --git a/ui/shared/lti/jquery/post_message/lti.screenReaderAlert.js b/ui/shared/lti/jquery/post_message/lti.screenReaderAlert.js new file mode 100644 index 00000000000..0e78f753d6a --- /dev/null +++ b/ui/shared/lti/jquery/post_message/lti.screenReaderAlert.js @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import $ from '@canvas/rails-flash-notifications' + +export default function screenReaderAlert({message}) { + $.screenReaderFlashMessageExclusive( + typeof message.body === 'string' ? message.body : JSON.stringify(message.body) + ) +} diff --git a/ui/shared/lti/jquery/post_message/lti.scrollToTop.js b/ui/shared/lti/jquery/post_message/lti.scrollToTop.js new file mode 100644 index 00000000000..443b9b1a243 --- /dev/null +++ b/ui/shared/lti/jquery/post_message/lti.scrollToTop.js @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import $ from 'jquery' + +export default function scrollToTop() { + $('html,body').animate( + { + scrollTop: $('.tool_content_wrapper').offset().top + }, + 'fast' + ) +} diff --git a/ui/shared/lti/jquery/post_message/lti.setUnloadMessage.js b/ui/shared/lti/jquery/post_message/lti.setUnloadMessage.js new file mode 100644 index 00000000000..77cba40ccf8 --- /dev/null +++ b/ui/shared/lti/jquery/post_message/lti.setUnloadMessage.js @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import htmlEscape from 'html-escape' +import {setUnloadMessage} from '../util' + +export default function set({message}) { + setUnloadMessage(htmlEscape(message.message)) +} diff --git a/ui/shared/lti/jquery/post_message/lti.showModuleNavigation.js b/ui/shared/lti/jquery/post_message/lti.showModuleNavigation.js new file mode 100644 index 00000000000..2645cfff2ae --- /dev/null +++ b/ui/shared/lti/jquery/post_message/lti.showModuleNavigation.js @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import $ from 'jquery' + +export default function showModuleNavigation({message}) { + if (message.show === true || message.show === false) { + $('.module-sequence-footer').toggle(message.show) + } +} diff --git a/ui/shared/lti/jquery/post_message/requestFullWindowLaunch.js b/ui/shared/lti/jquery/post_message/requestFullWindowLaunch.js index e2fd0a424ee..31517e3dba8 100644 --- a/ui/shared/lti/jquery/post_message/requestFullWindowLaunch.js +++ b/ui/shared/lti/jquery/post_message/requestFullWindowLaunch.js @@ -64,8 +64,8 @@ const buildLaunchUrl = (messageUrl, placement, display) => { return `${baseUrl}&url=${encodedToolLaunchUrl}${clientIdParam}${placementParam}${assignmentParam}` } -const handler = data => { - const {url, launchType, launchOptions, placement, display} = parseData(data) +const handler = ({message}) => { + const {url, launchType, launchOptions, placement, display} = parseData(message.data) const launchUrl = buildLaunchUrl(url, placement, display) let proxy