UNPKG

@eclipse-scout/core

Version:
195 lines (170 loc) 6.58 kB
/* * 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); } }