@qooxdoo/framework
Version:
The JS Framework for Coders
452 lines (394 loc) • 11.7 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Sebastian Werner (wpbasti)
* Andreas Ecker (ecker)
* Til Schneider (til132)
* Jonathan Weiß (jonathan_rass)
************************************************************************ */
/**
* Container widget for internal frames (iframes).
* An iframe can display any HTML page inside the widget.
*
* *Example*
*
* Here is a little example of how to use the widget.
*
* <pre class='javascript'>
* var document = this.getRoot();
* var iframe = new qx.ui.embed.Iframe("http://www.qooxdoo.org");
* document.add(iframe);
* </pre>
*
*
* *External Documentation*
*
* <a href='http://qooxdoo.org/docs/#desktop/widget/iframe.md' target='_blank'>
* Documentation of this widget in the qooxdoo manual.</a>
*
*
* *Notes*
* When modifying this file, note that the test qx.test.ui.embed.Iframe.testSyncSourceAfterDOMMove
* has been disabled under Chrome because of problems with Travis and Github. Changes to this file
* should be tested manually against that test.
*/
qx.Class.define("qx.ui.embed.Iframe", {
extend: qx.ui.embed.AbstractIframe,
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @ignore(MutationObserver)
* @param source {String} URL which should initially set.
*/
construct(source) {
if (source != null) {
this.__source = source;
}
super(source);
qx.event.Registration.addListener(
document.body,
"pointerdown",
this.block,
this,
true
);
qx.event.Registration.addListener(
document.body,
"pointerup",
this.release,
this,
true
);
qx.event.Registration.addListener(
document.body,
"losecapture",
this.release,
this,
true
);
this.__blockerElement = this._createBlockerElement();
if (qx.core.Environment.get("ecmascript.mutationobserver")) {
this.addListenerOnce("appear", () => {
var element = this.getContentElement().getDomElement();
// Mutation record check callback
var isDOMNodeInserted = function (mutationRecord) {
var i;
// 'our' iframe was either added...
if (mutationRecord.addedNodes) {
for (i = mutationRecord.addedNodes.length; i >= 0; --i) {
if (mutationRecord.addedNodes[i] == element) {
return true;
}
}
}
// ...or removed
if (mutationRecord.removedNodes) {
for (i = mutationRecord.removedNodes.length; i >= 0; --i) {
if (mutationRecord.removedNodes[i] == element) {
return true;
}
}
}
return false;
};
var observer = new MutationObserver(
function (mutationRecords) {
if (mutationRecords.some(isDOMNodeInserted)) {
this._syncSourceAfterDOMMove();
}
}.bind(this)
);
// Observe parent element
var parent = this.getLayoutParent().getContentElement().getDomElement();
observer.observe(parent, { childList: true, subtree: true });
});
}
// !qx.core.Environment.get("ecmascript.mutationobserver")
else {
this.addListenerOnce("appear", () => {
var element = this.getContentElement().getDomElement();
qx.bom.Event.addNativeListener(
element,
"DOMNodeInserted",
this._onDOMNodeInserted
);
});
this._onDOMNodeInserted = qx.lang.Function.listener(
this._syncSourceAfterDOMMove,
this
);
}
},
properties: {
// overridden
appearance: {
refine: true,
init: "iframe"
},
/**
* Whether to show the frame's native context menu.
*
* Note: This only works if the iframe source is served from the same domain
* as the main application.
*/
nativeContextMenu: {
refine: true,
init: false
},
/**
* If the user presses F1 in IE by default the onhelp event is fired and
* IE’s help window is opened. Setting this property to <code>false</code>
* prevents this behavior.
*
* Note: This only works if the iframe source is served from the same domain
* as the main application.
*/
nativeHelp: {
check: "Boolean",
init: false,
apply: "_applyNativeHelp"
},
/**
* Whether the widget should have scrollbars.
*/
scrollbar: {
check: ["auto", "no", "yes"],
nullable: true,
themeable: true,
apply: "_applyScrollbar"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
__source: null,
__blockerElement: null,
// overridden
renderLayout(left, top, width, height) {
super.renderLayout(left, top, width, height);
var pixel = "px";
var insets = this.getInsets();
this.__blockerElement.setStyles({
left: left + insets.left + pixel,
top: top + insets.top + pixel,
width: width - insets.left - insets.right + pixel,
height: height - insets.top - insets.bottom + pixel
});
},
// overridden
_createContentElement() {
var iframe = new qx.html.Iframe(this.__source);
iframe.addListener("load", this._onIframeLoad, this);
return iframe;
},
// overridden
_getIframeElement() {
return this.getContentElement();
},
/**
* Creates <div> element which is aligned over iframe node to avoid losing pointer events.
*
* @return {Object} Blocker element node
*/
_createBlockerElement() {
var el = new qx.html.Blocker();
el.setStyles({
zIndex: 20,
display: "none"
});
return el;
},
/**
* Reacts on native load event and redirects it to the widget.
*
* @param e {qx.event.type.Event} Native load event
*/
_onIframeLoad(e) {
this._applyNativeContextMenu(this.getNativeContextMenu(), null);
this._applyNativeHelp(this.getNativeHelp(), null);
this.fireNonBubblingEvent("load");
},
/*
---------------------------------------------------------------------------
METHODS
---------------------------------------------------------------------------
*/
/**
* Cover the iframe with a transparent blocker div element. This prevents
* pointer or key events to be handled by the iframe. To release the blocker
* use {@link #release}.
*
*/
block() {
this.__blockerElement.setStyle("display", "block");
},
/**
* Release the blocker set by {@link #block}.
*
*/
release() {
this.__blockerElement.setStyle("display", "none");
},
/*
---------------------------------------------------------------------------
EVENT HANDLER
---------------------------------------------------------------------------
*/
// property apply
_applyNativeContextMenu(value, old) {
if (value !== false && old !== false) {
return;
}
var doc = this.getDocument();
if (!doc) {
return;
}
try {
var documentElement = doc.documentElement;
} catch (e) {
// this may fail due to security restrictions
return;
}
if (old === false) {
qx.event.Registration.removeListener(
documentElement,
"contextmenu",
this._onNativeContextMenu,
this,
true
);
}
if (value === false) {
qx.event.Registration.addListener(
documentElement,
"contextmenu",
this._onNativeContextMenu,
this,
true
);
}
},
/**
* Stops the <code>contextmenu</code> event from showing the native context menu
*
* @param e {qx.event.type.Mouse} The event object
*/
_onNativeContextMenu(e) {
e.preventDefault();
},
// property apply
_applyNativeHelp(value, old) {
if (qx.core.Environment.get("event.help")) {
var document = this.getDocument();
if (!document) {
return;
}
try {
if (old === false) {
qx.bom.Event.removeNativeListener(document, "help", function () {
return false;
});
}
if (value === false) {
qx.bom.Event.addNativeListener(document, "help", function () {
return false;
});
}
} catch (e) {
if (qx.core.Environment.get("qx.debug")) {
this.warn(
"Unable to set 'nativeHelp' property, possibly due to security restrictions"
);
}
}
}
},
/**
* Checks if the iframe element is out of sync. This can happen in Firefox
* if the iframe is moved around and the source is changed right after.
* The root cause is that Firefox is reloading the iframe when its position
* in DOM has changed.
*/
_syncSourceAfterDOMMove() {
var iframeDomElement =
this.getContentElement() && this.getContentElement().getDomElement();
if (!iframeDomElement) {
return;
}
var iframeSource = iframeDomElement.src;
// remove trailing "/"
if (iframeSource.charAt(iframeSource.length - 1) == "/") {
iframeSource = iframeSource.substring(0, iframeSource.length - 1);
}
if (iframeSource != this.getSource()) {
if (
qx.core.Environment.get("browser.name") != "edge" &&
qx.core.Environment.get("browser.name") != "ie"
) {
qx.bom.Iframe.getWindow(iframeDomElement).stop();
}
iframeDomElement.src = this.getSource();
}
},
// property apply
_applyScrollbar(value) {
this.getContentElement().setAttribute("scrolling", value);
},
// overridden
setLayoutParent(parent) {
if (parent === null) {
this.getLayoutParent()
.getContentElement()
.remove(this.__blockerElement);
}
super.setLayoutParent(parent);
if (parent) {
this.getLayoutParent().getContentElement().add(this.__blockerElement);
}
}
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct() {
if (this.getLayoutParent() && this.__blockerElement.getParent()) {
this.getLayoutParent().getContentElement().remove(this.__blockerElement);
}
this._disposeObjects("__blockerElement");
qx.event.Registration.removeListener(
document.body,
"pointerdown",
this.block,
this,
true
);
qx.event.Registration.removeListener(
document.body,
"pointerup",
this.release,
this,
true
);
qx.event.Registration.removeListener(
document.body,
"losecapture",
this.release,
this,
true
);
}
});