// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Iframe shims, to protect controls on the underlying page
 * from bleeding through popups.
 *
 * @author gboyer@google.com (Garrett Boyer)
 * @author nicksantos@google.com (Nick Santos) (Ported to Closure)
 */


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');



/**
 * 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) {
  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);
goog.tagUnsealableClass(goog.ui.IframeMask);


/**
 * 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() {
  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() {
  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() {
  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() {
  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) {
  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) {
  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) {
  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) {
  var timerKey;
  this.handler_.listen(target, showEvent, function() {
    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() {
    if (timerKey) {
      goog.Timer.clear(timerKey);
      timerKey = null;
    }
    this.hideMask();
  });
};


/**
 * Removes all handlers attached by listenOnTarget.
 */
goog.ui.IframeMask.prototype.removeHandlers = function() {
  this.handler_.removeAll();
};
