messages: handle all subjects in top level

refs INTEROP-7086
flag=none

why:
* since all postMessages should be using subject and not messageType,
handle them all in the same place
* reduces complexity, since before some subjects were handled by a
switch statement
* prepare for further refactoring like adding lower level handler tests
back, and renaming post_message folder

* move all subjects to their own file
* remove lower-level handler that used to do subjects by file, and move
that logic to the top level
* remove switch statement that used to handle "legacy" subjects,
and add file for each subject from that

test plan:
* specs pass
* sending each of these postMessage subjects works like they should

Change-Id: Ia2b0552b6df895757581c5735f89d62158fa5a41
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/273933
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Mysti Lilla <mysti@instructure.com>
QA-Review: Xander Moffatt <xmoffatt@instructure.com>
Product-Review: Xander Moffatt <xmoffatt@instructure.com>
This commit is contained in:
Xander Moffatt 2021-09-20 14:37:16 -06:00
parent 5438d8f86e
commit 2001fcb357
17 changed files with 362 additions and 325 deletions

View File

@ -35,10 +35,6 @@ const fetchWindowSize = {
subject: 'lti.fetchWindowSize'
}
const scrollMessage = {
subject: 'lti.scrollToTop'
}
const removeUnloadMessage = {
subject: 'lti.removeUnloadMessage'
}
@ -81,7 +77,7 @@ QUnit.module('Messages', suiteHooks => {
ltiToolWrapperFixture.empty()
})
test('finds and resizes the tool content wrapper', () => {
test('finds and resizes the tool content wrapper', async () => {
ltiToolWrapperFixture.append(`
<div id="content-wrapper" class="ic-Layout-contentWrapper">
<div id="content" class="ic-Layout-contentMain" role="main">
@ -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(`
<div>
<h1 class="page-title">LTI resize test</h1>
@ -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(`
<div>
<h1 class="page-title">LTI resize test</h1>
@ -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(`
<div>
<div id="module-footer" class="module-sequence-footer">Next</div>
@ -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)
})
})

View File

@ -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"}')
})
})

View File

@ -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}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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({})
})
})

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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"}')
})
})

View File

@ -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({
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'
)
})

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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
)
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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, '*')
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
import {removeUnloadMessage} from '../util'
export default function remove() {
removeUnloadMessage()
}

View File

@ -15,6 +15,7 @@
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {ltiState} from '../messages'
const handler = () => {

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
import $ from '@canvas/rails-flash-notifications'
export default function screenReaderAlert({message}) {
$.screenReaderFlashMessageExclusive(
typeof message.body === 'string' ? message.body : JSON.stringify(message.body)
)
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
import $ from 'jquery'
export default function scrollToTop() {
$('html,body').animate(
{
scrollTop: $('.tool_content_wrapper').offset().top
},
'fast'
)
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
import htmlEscape from 'html-escape'
import {setUnloadMessage} from '../util'
export default function set({message}) {
setUnloadMessage(htmlEscape(message.message))
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
import $ from 'jquery'
export default function showModuleNavigation({message}) {
if (message.show === true || message.show === false) {
$('.module-sequence-footer').toggle(message.show)
}
}

View File

@ -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