iframeloadmonitor.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Class that can be used to determine when an iframe is loaded.
  16. */
  17. goog.provide('goog.net.IframeLoadMonitor');
  18. goog.require('goog.dom');
  19. goog.require('goog.events');
  20. goog.require('goog.events.EventTarget');
  21. goog.require('goog.events.EventType');
  22. goog.require('goog.userAgent');
  23. /**
  24. * The correct way to determine whether a same-domain iframe has completed
  25. * loading is different in IE and Firefox. This class abstracts above these
  26. * differences, providing a consistent interface for:
  27. * <ol>
  28. * <li> Determing if an iframe is currently loaded
  29. * <li> Listening for an iframe that is not currently loaded, to finish loading
  30. * </ol>
  31. *
  32. * @param {HTMLIFrameElement} iframe An iframe.
  33. * @param {boolean=} opt_hasContent Whether to wait for the loaded iframe to
  34. * have content in its document body.
  35. * @extends {goog.events.EventTarget}
  36. * @constructor
  37. * @final
  38. */
  39. goog.net.IframeLoadMonitor = function(iframe, opt_hasContent) {
  40. goog.net.IframeLoadMonitor.base(this, 'constructor');
  41. /**
  42. * Iframe whose load state is monitored by this IframeLoadMonitor
  43. * @type {HTMLIFrameElement}
  44. * @private
  45. */
  46. this.iframe_ = iframe;
  47. /**
  48. * Whether to wait for the loaded iframe to have content in its document body.
  49. * @type {boolean}
  50. * @private
  51. */
  52. this.hasContent_ = !!opt_hasContent;
  53. /**
  54. * Whether or not the iframe is loaded.
  55. * @type {boolean}
  56. * @private
  57. */
  58. this.isLoaded_ = this.isLoadedHelper_();
  59. if (!this.isLoaded_) {
  60. // IE 6 (and lower?) does not reliably fire load events, so listen to
  61. // readystatechange.
  62. // IE 7 does not reliably fire readystatechange events but listening on load
  63. // seems to work just fine.
  64. var isIe6OrLess =
  65. goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('7');
  66. var loadEvtType = isIe6OrLess ? goog.events.EventType.READYSTATECHANGE :
  67. goog.events.EventType.LOAD;
  68. this.onloadListenerKey_ = goog.events.listen(
  69. this.iframe_, loadEvtType, this.handleLoad_, false, this);
  70. // Sometimes we still don't get the event callback, so we'll poll just to
  71. // be safe.
  72. this.intervalId_ = window.setInterval(
  73. goog.bind(this.handleLoad_, this),
  74. goog.net.IframeLoadMonitor.POLL_INTERVAL_MS_);
  75. }
  76. };
  77. goog.inherits(goog.net.IframeLoadMonitor, goog.events.EventTarget);
  78. /**
  79. * Event type dispatched by a goog.net.IframeLoadMonitor when it internal iframe
  80. * finishes loading for the first time after construction of the
  81. * goog.net.IframeLoadMonitor
  82. * @type {string}
  83. */
  84. goog.net.IframeLoadMonitor.LOAD_EVENT = 'ifload';
  85. /**
  86. * Poll interval for polling iframe load states in milliseconds.
  87. * @type {number}
  88. * @private
  89. */
  90. goog.net.IframeLoadMonitor.POLL_INTERVAL_MS_ = 100;
  91. /**
  92. * Key for iframe load listener, or null if not currently listening on the
  93. * iframe for a load event.
  94. * @type {goog.events.Key}
  95. * @private
  96. */
  97. goog.net.IframeLoadMonitor.prototype.onloadListenerKey_ = null;
  98. /**
  99. * Returns whether or not the iframe is loaded.
  100. * @return {boolean} whether or not the iframe is loaded.
  101. */
  102. goog.net.IframeLoadMonitor.prototype.isLoaded = function() {
  103. return this.isLoaded_;
  104. };
  105. /**
  106. * Stops the poll timer if this IframeLoadMonitor is currently polling.
  107. * @private
  108. */
  109. goog.net.IframeLoadMonitor.prototype.maybeStopTimer_ = function() {
  110. if (this.intervalId_) {
  111. window.clearInterval(this.intervalId_);
  112. this.intervalId_ = null;
  113. }
  114. };
  115. /**
  116. * Returns the iframe whose load state this IframeLoader monitors.
  117. * @return {HTMLIFrameElement} the iframe whose load state this IframeLoader
  118. * monitors.
  119. */
  120. goog.net.IframeLoadMonitor.prototype.getIframe = function() {
  121. return this.iframe_;
  122. };
  123. /** @override */
  124. goog.net.IframeLoadMonitor.prototype.disposeInternal = function() {
  125. delete this.iframe_;
  126. this.maybeStopTimer_();
  127. goog.events.unlistenByKey(this.onloadListenerKey_);
  128. goog.net.IframeLoadMonitor.superClass_.disposeInternal.call(this);
  129. };
  130. /**
  131. * Returns whether or not the iframe is loaded. Determines this by inspecting
  132. * browser dependent properties of the iframe.
  133. * @return {boolean} whether or not the iframe is loaded.
  134. * @private
  135. */
  136. goog.net.IframeLoadMonitor.prototype.isLoadedHelper_ = function() {
  137. var isLoaded = false;
  138. try {
  139. if (!this.hasContent_ && goog.userAgent.IE &&
  140. !goog.userAgent.isVersionOrHigher('11')) {
  141. // IE versions before IE11 will reliably have readyState set to complete
  142. // if the iframe is loaded.
  143. isLoaded = this.iframe_.readyState == 'complete';
  144. } else {
  145. // For other browsers, check whether the document body exists to determine
  146. // whether the iframe has loaded. Older versions of Firefox may fire the
  147. // LOAD event early for an empty frame and then, a few hundred
  148. // milliseconds later, replace the contentDocument. If the hasContent
  149. // check is requested, the iframe is considered loaded only once there is
  150. // content in the body.
  151. var body = goog.dom.getFrameContentDocument(this.iframe_).body;
  152. isLoaded = this.hasContent_ ? !!body && !!body.firstChild : !!body;
  153. }
  154. } catch (e) {
  155. // Ignore these errors. This just means that the iframe is not loaded
  156. // IE will throw error reading readyState if the iframe is not appended
  157. // to the dom yet.
  158. // Firefox will throw error getting the iframe body if the iframe is not
  159. // fully loaded.
  160. }
  161. return isLoaded;
  162. };
  163. /**
  164. * Handles an event indicating that the loading status of the iframe has
  165. * changed. In Firefox this is a goog.events.EventType.LOAD event, in IE
  166. * this is a goog.events.EventType.READYSTATECHANGED
  167. * @private
  168. */
  169. goog.net.IframeLoadMonitor.prototype.handleLoad_ = function() {
  170. // Only do the handler if the iframe is loaded.
  171. if (this.isLoadedHelper_()) {
  172. this.maybeStopTimer_();
  173. goog.events.unlistenByKey(this.onloadListenerKey_);
  174. this.onloadListenerKey_ = null;
  175. this.isLoaded_ = true;
  176. this.dispatchEvent(goog.net.IframeLoadMonitor.LOAD_EVENT);
  177. }
  178. };