/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Iframe shims, to protect controls on the underlying page
* from bleeding through popups.
*/
goog.provide('goog.ui.IframeMask');
goog.require('goog.Disposable');
goog.require('goog.Timer');
goog.require('goog.dom');
goog.require('goog.dom.iframe');
goog.require('goog.events.EventHandler');
goog.require('goog.structs.Pool');
goog.require('goog.style');
goog.requireType('goog.events.EventTarget');
/**
* Controller for an iframe mask. The mask is only valid in the current
* document, or else the document of the given DOM helper.
*
* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper for the relevant
* document.
* @param {goog.structs.Pool=} opt_iframePool An optional source of iframes.
* Iframes will be grabbed from the pool when they're needed and returned
* to the pool (but still attached to the DOM) when they're done.
* @constructor
* @extends {goog.Disposable}
*/
goog.ui.IframeMask = function(opt_domHelper, opt_iframePool) {
'use strict';
goog.Disposable.call(this);
/**
* The DOM helper for this document.
* @type {goog.dom.DomHelper}
* @private
*/
this.dom_ = opt_domHelper || goog.dom.getDomHelper();
/**
* An Element to snap the mask to. If none is given, defaults to
* a full-screen iframe mask.
* @type {Element}
* @private
*/
this.snapElement_ = this.dom_.getDocument().documentElement;
/**
* An event handler for listening to popups and the like.
* @type {goog.events.EventHandler<!goog.ui.IframeMask>}
* @private
*/
this.handler_ = new goog.events.EventHandler(this);
/**
* An iframe pool.
* @type {goog.structs.Pool|undefined}
* @private
*/
this.iframePool_ = opt_iframePool;
};
goog.inherits(goog.ui.IframeMask, goog.Disposable);
/**
* An iframe.
* @type {HTMLIFrameElement}
* @private
*/
goog.ui.IframeMask.prototype.iframe_;
/**
* The z-index of the iframe mask.
* @type {number}
* @private
*/
goog.ui.IframeMask.prototype.zIndex_ = 1;
/**
* The opacity of the iframe mask, expressed as a value between 0 and 1, with
* 1 being totally opaque.
* @type {number}
* @private
*/
goog.ui.IframeMask.prototype.opacity_ = 0;
/**
* Removes the iframe from the DOM.
* @override
* @protected
*/
goog.ui.IframeMask.prototype.disposeInternal = function() {
'use strict';
if (this.iframePool_) {
this.iframePool_.releaseObject(
/** @type {HTMLIFrameElement} */ (this.iframe_));
} else {
goog.dom.removeNode(this.iframe_);
}
this.iframe_ = null;
this.handler_.dispose();
this.handler_ = null;
goog.ui.IframeMask.superClass_.disposeInternal.call(this);
};
/**
* CSS for a hidden iframe.
* @type {string}
* @private
*/
goog.ui.IframeMask.HIDDEN_CSS_TEXT_ =
'position:absolute;display:none;z-index:1';
/**
* Removes the mask from the screen.
*/
goog.ui.IframeMask.prototype.hideMask = function() {
'use strict';
if (this.iframe_) {
this.iframe_.style.cssText = goog.ui.IframeMask.HIDDEN_CSS_TEXT_;
if (this.iframePool_) {
this.iframePool_.releaseObject(this.iframe_);
this.iframe_ = null;
}
}
};
/**
* Gets the iframe to use as a mask. Creates a new one if one has not been
* created yet.
* @return {!HTMLIFrameElement} The iframe.
* @private
*/
goog.ui.IframeMask.prototype.getIframe_ = function() {
'use strict';
if (!this.iframe_) {
this.iframe_ = this.iframePool_ ?
/** @type {HTMLIFrameElement} */ (this.iframePool_.getObject()) :
goog.dom.iframe.createBlank(this.dom_);
this.iframe_.style.cssText = goog.ui.IframeMask.HIDDEN_CSS_TEXT_;
this.dom_.getDocument().body.appendChild(this.iframe_);
}
return this.iframe_;
};
/**
* Applies the iframe mask to the screen.
*/
goog.ui.IframeMask.prototype.applyMask = function() {
'use strict';
var iframe = this.getIframe_();
var bounds = goog.style.getBounds(this.snapElement_);
iframe.style.cssText = 'position:absolute;' +
'left:' + bounds.left + 'px;' +
'top:' + bounds.top + 'px;' +
'width:' + bounds.width + 'px;' +
'height:' + bounds.height + 'px;' +
'z-index:' + this.zIndex_;
goog.style.setOpacity(iframe, this.opacity_);
iframe.style.display = 'block';
};
/**
* Sets the opacity of the mask. Will take effect the next time the mask
* is applied.
* @param {number} opacity A value between 0 and 1, with 1 being
* totally opaque.
*/
goog.ui.IframeMask.prototype.setOpacity = function(opacity) {
'use strict';
this.opacity_ = opacity;
};
/**
* Sets the z-index of the mask. Will take effect the next time the mask
* is applied.
* @param {number} zIndex A z-index value.
*/
goog.ui.IframeMask.prototype.setZIndex = function(zIndex) {
'use strict';
this.zIndex_ = zIndex;
};
/**
* Sets the element to use as the bounds of the mask. Takes effect immediately.
* @param {Element} snapElement The snap element, which the iframe will be
* "snapped" around.
*/
goog.ui.IframeMask.prototype.setSnapElement = function(snapElement) {
'use strict';
this.snapElement_ = snapElement;
if (this.iframe_ && goog.style.isElementShown(this.iframe_)) {
this.applyMask();
}
};
/**
* Listens on the specified target, hiding and showing the iframe mask
* when the given event types are dispatched.
* @param {goog.events.EventTarget} target The event target to listen on.
* @param {string} showEvent When this event fires, the mask will be applied.
* @param {string} hideEvent When this event fires, the mask will be hidden.
* @param {Element=} opt_snapElement When the mask is applied, it will
* automatically snap to this element. If no element is specified, it will
* use the default snap element.
*/
goog.ui.IframeMask.prototype.listenOnTarget = function(
target, showEvent, hideEvent, opt_snapElement) {
'use strict';
var timerKey;
this.handler_.listen(target, showEvent, function() {
'use strict';
if (opt_snapElement) {
this.setSnapElement(opt_snapElement);
}
// Check out the iframe asynchronously, so we don't block the SHOW
// event and cause a bounce.
timerKey = goog.Timer.callOnce(this.applyMask, 0, this);
});
this.handler_.listen(target, hideEvent, function() {
'use strict';
if (timerKey) {
goog.Timer.clear(timerKey);
timerKey = null;
}
this.hideMask();
});
};
/**
* Removes all handlers attached by listenOnTarget.
*/
goog.ui.IframeMask.prototype.removeHandlers = function() {
'use strict';
this.handler_.removeAll();
};