Keyboard Shortcuts und JWT im iframe

In manchen Kundensituationen wird das jadice web toolkit mittels eines iframes in die umgebende Anwendung integriert. Sobald der Benutzer mit der Maus auf ein Element der umgebenden Anwendung clickt, teilt der Browser der umgebenden Anwendung den Fokus zu. Falls der Benutzer im Anschluss eine Funktion des JWT mittels eines Keyboardshortcuts steuern möchte, schlägt dies fehl, weil der Browser nur diejenige Anwendung über die Tastatureingabe benachrichtigt, die den Fokus besitzt. Um die Shortcuts auch in diesem Fall nutzen zu können, müssen alle Keyboard-Events von der umgebenden Anwendung an den iframe propagiert werden.

In manchen Kundensituation wird außerdem der iframe, in dem das JWT dargestellt wird, von einem anderen Host geladen als die umgebende Anwendung. In diesem Fall erschwert sich die Kommunikation zwischen beiden Anwendungen aufgrund der Same-Origin-Policy.

Eventweiterleitung Same Origin

Dieser Abschnitt beschreibt, wie Keyboard Shortcuts verwendet werden können, wenn der das JWT einbettende iframe vom selben Origin geladen wird, wie die umgebende Anwendung. Hierzu wird in der umgebenden Anwendung ein Event-Listener registriert, der von jedem Keyboard-Event eine Kopie erstellt und diese an das Body-Element des iframes schickt. Die Kopie des Events wird benötigt, da Events nicht zwei mal dispatched werden können. Würde man also im unteren Beispiel iframe.body.dispatchEvent(event); eingeben, so würde der Browser einen Fehler werfen, und das Event würde nicht weitergeschickt werden.

Eventweiterleitung Same Origin
<!DOCTYPE html>
<html>
	<body>
		<script>
			/*
			* Enables a jadice web toolkit integration running inside an iframe to use keyboard shortcuts.
			*
			* Only works if the iframe is loaded from the same origin as the surrounding application. Installs
			* an event listener in the surrounding application that forwards keyboard events to the iframe
			* that contains the jadice web toolkit viewer.
			*/
			window.onload = function() {
				
				function notifyIframe(event) {
					var iframe = document.getElementById("jwt-iframe").contentDocument;
					if (typeof (KeyboardEvent) === 'function') {
						// chrome and firefox
						var keyboardEvent = new KeyboardEvent('keydown', {
							key : event.key,
							code : event.code,
							location : event.location,
							ctrlKey : event.ctrlKey,
							shiftKey : event.shiftKey,
							altKey : event.altKey,
							metaKey : event.metaKey,
							repeat : event.repeat,
							keyCode : event.keyCode
						});
					} else {
						// internet explorer and edge
						// needed because somehow the microsoft developers thought it is unnecessary to support the non 
						// deprecated API (i.e. event constructors) if they support a deprecated one (i.e. initKeyboardEvent method)
						var keyboardEvent = document.createEvent("KeyboardEvent");
						var metaKeys = true === event.ctrlKey ? "Control " : "" + true === event.shiftKey ? "Shift " : ""
							+ true === event.metaKey ? "Meta " : "" + true === event.altKey ? "Alt " : "";
					
						keyboardEvent.initKeyboardEvent('keydown',
							true, // bubbles
							true, // cancelable
							window, // viewArg: should be window
							event.key,
							event.location,
							metaKeys,
							false, // repeat
							event.locale
						);
					}
					console.log(keyboardEvent);
				 
					// send the keyboard event to the body of the iframe
					iframe.body.dispatchEvent(keyboardEvent);
				}
				
				document.body.addEventListener('keydown', notifyIframe);
			}
    	</script>
	</body>
</html>

Zu beachten ist, dass der ActionManager bis einschließlich JWT 5.5.5.0 dafür über ActionManager.initialize(RootPanel.get()); initialisiert werden muss. Da das RootPanel in GWT dem body-Element entspricht, führt dies dazu, dass der ActionManager über alle KeyboardEvents, die an das body-Element geschickt werden, benachrichtigt wird. Ab JWT 5.6.0.0 wird der ActionManager implizit am RootPanel initialisiert, sodass der Aufruf wegfallen kann.

Eventweiterleitung Cross Origin

Dieser Abschnitt beschreibt, wie Keyboard Shortcuts verwendet werden können, wenn der das JWT einbettende iframe von einem anderen Origin geladen wird, als die umgebende Anwendung. Eine Möglichkeit, den Eventversand zwischen beiden Anwendungen zu implementieren, stellt die Methode Window.postMessage dar. Hierzu wird in der umgebenden Anwendung ein EventListener registriert, der bei Eingabe eines beliebigen KeyDownEvents benachrichtigt wird und dann via Window.postMessage eine Nachricht an den eingebetteten iframe schickt. Innerhalb des iframes wird ebenfalls ein EventListener registriert, der immer dann benachrichtigt wird, wenn eine Nachricht via postMessage am iframe ankommt. In diesem EventListener wird dann das zugehörige KeyboardEvent an das body Element im iframe geschickt. Im Anschluss informiert der Browser dann den ActionManager des JWT über das Event, sodass dieser die zugehörige Funktion im Viewer auslösen kann.

Das folgende Skript enthält die Logik für die umgebende Anwendung.

Eventweiterleitung Cross Origin - umgebende Anwendung
<!DOCTYPE html>
<html>
	<body>
		<!-- iframe that contains the JWT -->
		<iframe src="index.html" id="jwt-iframe" style="width: 90%; height: 1000px"></iframe>
		
		<script>
			/*
			 * Enables a jadice web toolkit integration running inside an iframe to use keyboard shortcuts.
			 *   
			 * Installs an event listener in the surrounding application that forwards keyboard events to the
			 * iframe that contains the jadice web toolkit viewer. This is done via postMessage (see
			 * https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage for details). 
			 */
			window.onload = function() {
				
				function notifyIframe(event) {
					
					// origin of the iframe the message will be sent to
					var iframeOrigin = 'http://127.0.0.1:8888';
					
					// get the iframe window that will receive the propagated event. Ensure the iframe tag has an id
					// attribute id="jwt-iframe"
					var receiver = document.getElementById('jwt-iframe').contentWindow;
			
				  	// only standard objects can be transported by postMessage - that's why we cannot send the key down event directly
				  	// we prepare a standard object that contains all information regarding the pressed key
				  	var message = {
			  			key:		event.key,
			  			code:		event.code,
			  			location:	event.location,
			  			ctrlKey:	event.ctrlKey,
			  			shiftKey:	event.shiftKey,
			  			altKey:		event.altKey,
			  			metaKey:	event.metaKey,
			  			repeat:		event.repeat,
			  			keyCode:	event.keyCode,
			  			locale:		event.locale
				  	};
				  
				  	// send the message to the iframe
				  	receiver.postMessage(message, iframeOrigin);
				}
				
				document.body.addEventListener('keydown', notifyIframe);
			}

		</script>
	</body>
</html>

 

Das folgende Skript enthält die Logik für die index.html des im iframe eingebetteten JWT.

Eventweiterleitung Cross Origin - iframe
<!DOCTYPE html>
<html>
	<body>
		
		...

		<script>
			/*
			* Enables a jadice web toolkit integration running inside a cross-origin iframe to use keyboard shortcuts.
			*   
			* Installs an event listener inside the iframe that receives keyboard events from the
			* surrounding application. The surrounding application must propagate keyboard events
			* via postMessage (see https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
			* for details). 
			*/
			window.onload = function() {
				// callback that is invoked when the surrounding application calls postMessage
				function receiveMessage(event) {
			 
					// trusted origin of the surrounding app - only messages from that origin will be processed 
			        var surroundingApplicationOrigin = 'http://127.0.0.1:8888';
			   
			        // check to make sure that this message came from the correct domain
			        if (event.origin !== surroundingApplicationOrigin)
			        	return;
			 
			        // postMessage can transport standard objects only - we create a native key down event from the received data... 
			        if (typeof (KeyboardEvent) === 'function') {
			            // chrome and firefox
			            var keyboardEvent = new KeyboardEvent('keydown', {
			                key:        event.data.key,
			                code:       event.data.code,
			                location:   event.data.location,
			                ctrlKey:    event.data.ctrlKey,
			                shiftKey:   event.data.shiftKey,
			                altKey:     event.data.altKey,
			                metaKey:    event.data.metaKey,
			                repeat:     event.data.repeat,
			                keyCode:    event.data.keyCode
			            });
			        } else {
			            // internet explorer and edge
			            // needed because somehow the microsoft developers thought it is unnecessary to support the non 
			            // deprecated API (i.e. event constructors) if they support a deprecated one (i.e. initKeyboardEvent method)
			            var keyboardEvent = document.createEvent("KeyboardEvent");
			            var metaKeys = true === event.data.ctrlKey ? "Control " : "" + true === event.data.shiftKey ? "Shift " : ""
			                + true === event.data.metaKey ? "Meta " : "" + true === event.data.altKey ? "Alt " : "";
			         
			            keyboardEvent.initKeyboardEvent('keydown',
			                true, // bubbles
			                true, // cancelable
			                window, // viewArg: should be window
			                event.data.key,
			                event.data.location,
			                metaKeys,
			                false, // repeat
			                    event.data.locale
			            );
			        }
			            
			        console.log(keyboardEvent);
			 
			        // ...and send it to the html body in order to let ActionManager catch it
			        document.body.dispatchEvent(keyboardEvent);
			   }
			 
			   // setup an event listener that calls receiveMessage() when the window receives a new MessageEvent
			   window.addEventListener('message', receiveMessage);
			}
		</script>
	</body>
</html>

Zu beachten ist, dass der ActionManager bis einschließlich JWT 5.5.5.0 dafür über ActionManager.initialize(RootPanel.get()); initialisiert werden muss. Da das RootPanel in GWT dem body-Element entspricht, führt dies dazu, dass der ActionManager über alle KeyboardEvents, die an das body-Element geschickt werden, benachrichtigt wird. Ab JWT 5.6.0.0 wird der ActionManager implizit am RootPanel initialisiert, sodass der Aufruf wegfallen kann.