@eclipse-scout/core
Version:
Eclipse Scout runtime
195 lines (170 loc) • 6.58 kB
text/typescript
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {Device, events, HtmlComponent, IFrameEventMap, IFrameKeyStrokeContext, IFrameModel, KeyStrokeContext, scout, Widget} from '../index';
export class IFrame extends Widget implements IFrameModel {
declare model: IFrameModel;
declare eventMap: IFrameEventMap;
declare self: IFrame;
location: string;
sandboxEnabled: boolean;
sandboxPermissions: string;
scrollBarEnabled: boolean;
trackLocation: boolean;
/**
* Iframe on iOS is always as big as its content. Workaround it by using a wrapper div with overflow: auto
* Don't wrap it when running in the chrome emulator (in that case isIosPlatform returns false)
*/
wrapIframe: boolean;
$iframe: JQuery<HTMLIFrameElement>;
constructor() {
super();
this.location = null;
this.sandboxEnabled = true;
this.sandboxPermissions = null;
this.scrollBarEnabled = true;
this.trackLocation = false;
this.wrapIframe = Device.get().isIosPlatform();
this.$iframe = null;
}
protected override _createKeyStrokeContext(): KeyStrokeContext {
return new IFrameKeyStrokeContext();
}
protected override _render() {
let cssClass = 'iframe ' + Device.get().cssClassForIphone();
// Inserting an IFrame starts the processing of the micro task queue in Safari.
// This must not happen during rendering because it could trigger render again for elements being rendered (some layouts render parts of the widget, e.g. widgets with virtual scrolling)
this.session.layoutValidator.suppressValidate();
if (this.wrapIframe) {
this.$container = this.$parent.appendDiv('iframe-wrapper');
this.$iframe = this.$container.appendElement('<iframe>', cssClass) as JQuery<HTMLIFrameElement>;
} else {
this.$iframe = this.$parent.appendElement('<iframe>', cssClass) as JQuery<HTMLIFrameElement>;
this.$container = this.$iframe;
}
this.session.layoutValidator.unsuppressValidate();
this.htmlComp = HtmlComponent.install(this.$container, this.session);
this.$iframe.on('load', this._onLoad.bind(this));
}
protected override _renderProperties() {
super._renderProperties();
this._renderScrollBarEnabled();
this._renderSandboxEnabled(); // includes _renderSandboxPermissions()
this._renderLocation(); // Needs to be after _renderScrollBarEnabled and _renderSandboxEnabled, see comment in _renderScrollBarEnabled
}
setLocation(location: string) {
this.setProperty('location', location);
}
protected _renderLocation() {
// Convert empty locations to 'about:blank', because in Firefox (maybe others, too?),
// empty locations simply remove the src attribute but don't remove the old content.
let location = this.location || 'about:blank';
this.$iframe.attr('src', location);
}
setTrackLocation(trackLocation: boolean) {
this.setProperty('trackLocation', trackLocation);
}
protected _contentDocument(): Document {
if (this.$iframe && this.$iframe[0]) {
return this.$iframe[0].contentDocument;
}
return null;
}
protected _onLoad(event: JQuery.TriggeredEvent) {
if (!this.rendered) { // check needed, because this is an async callback
return;
}
if (this.trackLocation) {
this._updateLocation();
}
this._propagateKeyEvents();
}
protected _updateLocation() {
let doc = this._contentDocument();
if (!doc) {
// Doc can be null if website cannot be loaded or if website is not from same origin
return;
}
let location = doc.location.href;
if (location === 'about:blank') {
location = null;
}
this._setProperty('location', location);
}
/**
* Make keystrokes work even if pressed in the iframe
*/
protected _propagateKeyEvents() {
let source = this._contentDocument();
if (!source) {
return;
}
let target = this.$iframe[0];
if (!target) {
return;
}
events.addPropagationListener(source, target, ['keydown', 'keyup', 'keypress']);
}
setScrollBarEnabled(scrollBarEnabled: boolean) {
this.setProperty('scrollBarEnabled', scrollBarEnabled);
}
protected _renderScrollBarEnabled() {
this.$container.toggleClass('no-scrolling', !this.scrollBarEnabled);
// According to http://stackoverflow.com/a/18470016, setting 'overflow: hidden' via
// CSS should be enough. However, if the inner page sets 'overflow' to another value,
// scroll bars are shown again. Therefore, we add the legacy 'scrolling' attribute,
// which is deprecated in HTML5, but seems to do the trick.
this.$iframe.attr('scrolling', (this.scrollBarEnabled ? 'yes' : 'no'));
// re-render location otherwise the attribute change would have no effect, see
// https://html.spec.whatwg.org/multipage/embedded-content.html#attr-iframe-sandbox
if (this.rendered) {
this._renderLocation();
}
}
setSandboxEnabled(sandboxEnabled: boolean) {
this.setProperty('sandboxEnabled', sandboxEnabled);
}
protected _renderSandboxEnabled() {
if (this.sandboxEnabled) {
this._renderSandboxPermissions();
} else {
this.$iframe.removeAttr('sandbox');
this.$iframe.removeAttr('security');
}
// re-render location otherwise the attribute change would have no effect, see
// https://html.spec.whatwg.org/multipage/embedded-content.html#attr-iframe-sandbox
if (this.rendered) {
this._renderLocation();
}
}
/**
* @param sandboxPermissions Permission names separated by space.
* @see IFrame.sandboxPermissions
*/
setSandboxPermissions(sandboxPermissions: string) {
this.setProperty('sandboxPermissions', sandboxPermissions);
}
protected _renderSandboxPermissions() {
if (!this.sandboxEnabled) {
return;
}
this.$iframe.attr('sandbox', scout.nvl(this.sandboxPermissions, ''));
// re-render location otherwise the attribute change would have no effect, see
// https://html.spec.whatwg.org/multipage/embedded-content.html#attr-iframe-sandbox
if (this.rendered) {
this._renderLocation();
}
}
postMessage(message: any, targetOrigin: string, transfer?: Transferable[]) {
if (!this.rendered) {
return;
}
this.$iframe[0].contentWindow.postMessage(message, targetOrigin, transfer);
}
}