[フレーム]
Last Updated: February 25, 2016
·
3.682K
· kfwerf

CORS Iframe messaging with DOM access and callback

Sometimes you can't go around using iframes, but you still want your own design and have the iframe do your commands but be invisible. I made a small little utility that uses the postmessage to send and receive json parsed DOM data.

Now i haven't set this all up to be 100% safe yet, it's origin calls are wildcarded which on production would be ill advised. Anyway it works and you could send flow through the iframe and pick off errors by checking at which page it is or what the dom is doing. It's not pretty but iframes aren't pretty..

Place this on the iframe side to enable some dom manipulation via postmessage packages

# DOM Manipulation via PostMessage
#
do ( dom = window ) ->

 #
 # Abstract API for doing small DOM manipulations
 #
 class DomManipulator
 parseHTML: ( strHtml ) ->
 elConverted = document.implementation.createHTMLDocument()
 elConverted.body.innerHTML = strHTML
 elConverted.body.children
 stringifyHTML: ( elTarget ) ->
 if typeof elTarget is 'object' then elTarget.outerHTML else false
 getElement: ( strElement ) ->
 elTarget = document.body.querySelector( strElement )
 elTarget
 getText: ( strElement ) ->
 elTarget = @getElement strElement
 elTarget.textContent
 getProperty: ( strElement, strProperty ) ->
 elTarget = @getElement strElement
 if elTarget then elTarget.getAttribute( strProperty ) else false
 setProperty: ( strElement, strProperty, strValue ) ->
 elTarget = @getElement strElement
 if elTarget then elTarget.setAttribute strProperty, strValue #elTarget[strProperty] = strValue
 @getProperty strElement, strProperty
 doMouseEvent: ( strElement, strEvent ) ->
 elTarget = @getElement strElement
 if elTarget
 evtMouseAny = document.createEvent('MouseEvents')
 evtMouseAny.initMouseEvent strEvent, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null
 elTarget.dispatchEvent evtMouseAny
 true
 false

 #
 # Extends DOM Manipulator and transforms the manipulations to be compatible with PostMessage
 #
 class PostMessageDomManipulator extends DomManipulator
 constructor: () ->

 #
 # Public PostMessage DomManipulator API
 #
 @objApi =
 getElement: (( objOptions, objJsonData ) -> 
 strElement = objOptions['strElement']
 if strElement then objJsonData.strReturn = @stringifyHTML @getElement(strElement)
 ).bind @
 getText: (( objOptions, objJsonData ) ->
 strElement = objOptions['strElement']
 if strElement then objJsonData.strReturn = @getText strElement
 ).bind @
 getProperty: (( objOptions, objJsonData ) ->
 strElement = objOptions['strElement']
 strProperty = objOptions['strProperty']
 if strElement and strProperty then objJsonData.strReturn = @getProperty strElement, strProperty
 ).bind @
 setProperty: (( objOptions, objJsonData ) -> 
 strElement = objOptions['strElement']
 strProperty = objOptions['strProperty']
 strValue = objOptions['strValue']
 if strElement and strProperty and strValue then objJsonData.strReturn = @setProperty strElement, strProperty, strValue
 ).bind @
 doMouseEvent: (( objOptions, objJsonData ) ->
 strElement = objOptions['strElement']
 strEvent = objOptions['strEvent']
 if strElement and strEvent then objJsonData.strReturn = @doMouseEvent strElement, strEvent
 ).bind @

 # Give a debug message that can be read
 console.debug 'Initializing postMessage protocol'

 # Bind the postMessage to onReceiveData. That will transform the call for the Public API
 dom.addEventListener 'message', @onReceiveData.bind( @ )
 @doSendData.bind(@) boolInitialized: true, strId: 'intitialize'

 #
 # For receiving messages via PostMessage to DOM Manipulation
 #
 onReceiveData: ( e ) ->
 strOrigin = e.origin
 objJsonData = JSON.parse e.data
 strLocation = objJsonData['strLocation']
 strAction = objJsonData['strAction']
 objParameters = objJsonData['objParameters']

 # Parent always needs to say what he expects, else we might end up going out of sync
 boolExpected = strLocation is window.location.href or strLocation is '*'
 boolOrigin = true
 boolAction = strAction
 boolParameters = objParameters
 boolPassing = boolExpected and boolOrigin and boolAction and boolParameters

 if not boolPassing then return @doSendData strErrorMessage: 'Origin not allowed or you were expecting someone else.'

 # Try to execute the request by testing if the action exists and can be called
 try
 objJsonData['strReturn'] = false
 if @objApi[strAction] then @objApi[strAction]( objParameters, objJsonData )
 @doSendData objJsonData
 catch
 @doSendData { strErrorMessage: 'Could not pass action onto manipulator. Something malformed..' } 

 #
 # For sending messages via PostMessage back to the parent
 #
 doSendData: ( objData = {} ) ->
 objData.objLocation = window.location
 objData.strId = objData.strId or 'keep-alive'
 strOrgin = '*' # Needs to be set for safety, only send messages to allowed domain
 strJsonData = JSON.stringify objData
 if parent
 parent.postMessage strJsonData, strOrgin
 #
 # on DOMContentLoaded initialize the PostMessageDomManipulator
 #
 onLoad = () ->
 dom.pmdm = new PostMessageDomManipulator()
 dom.addEventListener 'DOMContentLoaded', onLoad

On the parent place something like

<!doctype html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Document</title>
</head>
<body>

 <script>
 window.onload = function() {

 var onClickDemo = function() {
 console.info('Trying to post a message to the target');
 var objData = {
 strId: '',
 strLocation: '*',
 strAction: 'setProperty',
 objParameters: {
 strElement: '#amount',
 strProperty: 'value',
 strValue: '100'
 }
 }

 document.getElementsByTagName('iframe')[0].contentWindow.postMessage(JSON.stringify(objData), '*');
 },
 onClickTriggerDemo = function() {
 console.info('Trying to trigger the mouse event');

 var objData = {
 strId: 'ontoNextPage',
 strLocation: '*',
 strAction: 'doMouseEvent',
 objParameters: {
 strElement: '#continueButton',
 strEvent: 'click'
 }
 }
 document.getElementsByTagName('iframe')[0].contentWindow.postMessage(JSON.stringify(objData), '*');
 },
 onGetErrorMessage = function() {
 console.info('Trying to get the error');

 var objData = {
 strLocation: '*',
 strAction: 'getElement',
 objParameters: {
 strElement: '#textWithdrawalDeclined'
 }
 }
 document.getElementsByTagName('iframe')[0].contentWindow.postMessage(JSON.stringify(objData), '*');
 }
 onMessageReceived = function( e ) {
 console.debug( 'data;', JSON.parse(e.data));
 }
 elIframe = document.createElement('iframe');
 elIframe.width = '100%';
 elIframe.height = '800';

 document.body.querySelectorAll( '.try' )[0].addEventListener('click', onClickDemo);
 document.body.querySelectorAll( '.do' )[0].addEventListener('click', onClickTriggerDemo);

 window.addEventListener( 'message', onMessageReceived )
 document.body.appendChild(elIframe);
 }
 </script>

 <input type="submit" value="Try a postMessage" class="try">
 <input type="submit" value="Try a trigger" class="do">
</body>
</html>

Basically you throw packages over the line JSON stringified:

var objData = {
 strId: '',
 strLocation: '*',
 strAction: 'setProperty',
 objParameters: {
 strElement: '#amount',
 strProperty: 'value',
 strValue: '100'
 }
}

document.getElementsByTagName('iframe')[0].contentWindow.postMessage(JSON.stringify(objData), '*');

PostMessage DOM i have made very small, you have the following:

  • getElement
  • getText
  • getProperty
  • setProperty
  • doMouseEvent

You can check them and add to them in the DomManipulator

Cheers!

AltStyle によって変換されたページ (->オリジナル) /