iframeio.js 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423
  1. // Copyright 2006 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 for managing requests via iFrames. Supports a number of
  16. * methods of transfer.
  17. *
  18. * Gets and Posts can be performed and the resultant page read in as text,
  19. * JSON, or from the HTML DOM.
  20. *
  21. * Using an iframe causes the throbber to spin, this is good for providing
  22. * feedback to the user that an action has occurred.
  23. *
  24. * Requests do not affect the history stack, see goog.History if you require
  25. * this behavior.
  26. *
  27. * The responseText and responseJson methods assume the response is plain,
  28. * text. You can access the Iframe's DOM through responseXml if you need
  29. * access to the raw HTML.
  30. *
  31. * Tested:
  32. * + FF2.0 (Win Linux)
  33. * + IE6, IE7
  34. * + Opera 9.1,
  35. * + Chrome
  36. * - Opera 8.5 fails because of no textContent and buggy innerText support
  37. *
  38. * NOTE: Safari doesn't fire the onload handler when loading plain text files
  39. *
  40. * This has been tested with Drip in IE to ensure memory usage is as constant
  41. * as possible. When making making thousands of requests, memory usage stays
  42. * constant for a while but then starts increasing (<500k for 2000
  43. * requests) -- this hasn't yet been tracked down yet, though it is cleared up
  44. * after a refresh.
  45. *
  46. *
  47. * BACKGROUND FILE UPLOAD:
  48. * By posting an arbitrary form through an IframeIo object, it is possible to
  49. * implement background file uploads. Here's how to do it:
  50. *
  51. * - Create a form:
  52. * <pre>
  53. * &lt;form id="form" enctype="multipart/form-data" method="POST"&gt;
  54. * &lt;input name="userfile" type="file" /&gt;
  55. * &lt;/form&gt;
  56. * </pre>
  57. *
  58. * - Have the user click the file input
  59. * - Create an IframeIo instance
  60. * <pre>
  61. * var io = new goog.net.IframeIo;
  62. * goog.events.listen(io, goog.net.EventType.COMPLETE,
  63. * function() { alert('Sent'); });
  64. * io.sendFromForm(document.getElementById('form'));
  65. * </pre>
  66. *
  67. *
  68. * INCREMENTAL LOADING:
  69. * Gmail sends down multiple script blocks which get executed as they are
  70. * received by the client. This allows incremental rendering of the thread
  71. * list and conversations.
  72. *
  73. * This requires collaboration with the server that is sending the requested
  74. * page back. To set incremental loading up, you should:
  75. *
  76. * A) In the application code there should be an externed reference to
  77. * <code>handleIncrementalData()</code>. e.g.
  78. * goog.exportSymbol('GG_iframeFn', goog.net.IframeIo.handleIncrementalData);
  79. *
  80. * B) The response page should them call this method directly, an example
  81. * response would look something like this:
  82. * <pre>
  83. * &lt;html&gt;
  84. * &lt;head&gt;
  85. * &lt;meta content="text/html;charset=UTF-8" http-equiv="content-type"&gt;
  86. * &lt;/head&gt;
  87. * &lt;body&gt;
  88. * &lt;script&gt;
  89. * D = top.P ? function(d) { top.GG_iframeFn(window, d) } : function() {};
  90. * &lt;/script&gt;
  91. *
  92. * &lt;script&gt;D([1, 2, 3, 4, 5]);&lt;/script&gt;
  93. * &lt;script&gt;D([6, 7, 8, 9, 10]);&lt;/script&gt;
  94. * &lt;script&gt;D([11, 12, 13, 14, 15]);&lt;/script&gt;
  95. * &lt;/body&gt;
  96. * &lt;/html&gt;
  97. * </pre>
  98. *
  99. * Your application should then listen, on the IframeIo instance, to the event
  100. * goog.net.EventType.INCREMENTAL_DATA. The event object contains a
  101. * 'data' member which is the content from the D() calls above.
  102. *
  103. * NOTE: There can be problems if you save a reference to the data object in IE.
  104. * If you save an array, and the iframe is dispose, then the array looses its
  105. * prototype and thus array methods like .join(). You can get around this by
  106. * creating arrays using the parent window's Array constructor, or you can
  107. * clone the array.
  108. *
  109. *
  110. * EVENT MODEL:
  111. * The various send methods work asynchronously. You can be notified about
  112. * the current status of the request (completed, success or error) by
  113. * listening for events on the IframeIo object itself. The following events
  114. * will be sent:
  115. * - goog.net.EventType.COMPLETE: when the request is completed
  116. * (either successfully or unsuccessfully). You can find out about the result
  117. * using the isSuccess() and getLastError
  118. * methods.
  119. * - goog.net.EventType.SUCCESS</code>: when the request was completed
  120. * successfully
  121. * - goog.net.EventType.ERROR: when the request failed
  122. * - goog.net.EventType.ABORT: when the request has been aborted
  123. *
  124. * Example:
  125. * <pre>
  126. * var io = new goog.net.IframeIo();
  127. * goog.events.listen(io, goog.net.EventType.COMPLETE,
  128. * function() { alert('request complete'); });
  129. * io.sendFromForm(...);
  130. * </pre>
  131. *
  132. */
  133. goog.provide('goog.net.IframeIo');
  134. goog.provide('goog.net.IframeIo.IncrementalDataEvent');
  135. goog.require('goog.Timer');
  136. goog.require('goog.Uri');
  137. goog.require('goog.array');
  138. goog.require('goog.asserts');
  139. goog.require('goog.debug.HtmlFormatter');
  140. goog.require('goog.dom');
  141. goog.require('goog.dom.InputType');
  142. goog.require('goog.dom.TagName');
  143. goog.require('goog.dom.safe');
  144. goog.require('goog.events');
  145. goog.require('goog.events.Event');
  146. goog.require('goog.events.EventTarget');
  147. goog.require('goog.events.EventType');
  148. goog.require('goog.html.uncheckedconversions');
  149. goog.require('goog.json');
  150. goog.require('goog.log');
  151. goog.require('goog.log.Level');
  152. goog.require('goog.net.ErrorCode');
  153. goog.require('goog.net.EventType');
  154. goog.require('goog.reflect');
  155. goog.require('goog.string');
  156. goog.require('goog.string.Const');
  157. goog.require('goog.structs');
  158. goog.require('goog.userAgent');
  159. /**
  160. * Class for managing requests via iFrames.
  161. * @constructor
  162. * @extends {goog.events.EventTarget}
  163. */
  164. goog.net.IframeIo = function() {
  165. goog.net.IframeIo.base(this, 'constructor');
  166. /**
  167. * Name for this IframeIo and frame
  168. * @type {string}
  169. * @private
  170. */
  171. this.name_ = goog.net.IframeIo.getNextName_();
  172. /**
  173. * An array of iframes that have been finished with. We need them to be
  174. * disposed async, so we don't confuse the browser (see below).
  175. * @type {Array<Element>}
  176. * @private
  177. */
  178. this.iframesForDisposal_ = [];
  179. // Create a lookup from names to instances of IframeIo. This is a helper
  180. // function to be used in conjunction with goog.net.IframeIo.getInstanceByName
  181. // to find the IframeIo object associated with a particular iframe. Used in
  182. // incremental scripts etc.
  183. goog.net.IframeIo.instances_[this.name_] = this;
  184. };
  185. goog.inherits(goog.net.IframeIo, goog.events.EventTarget);
  186. /**
  187. * Object used as a map to lookup instances of IframeIo objects by name.
  188. * @type {Object}
  189. * @private
  190. */
  191. goog.net.IframeIo.instances_ = {};
  192. /**
  193. * Prefix for frame names
  194. * @type {string}
  195. */
  196. goog.net.IframeIo.FRAME_NAME_PREFIX = 'closure_frame';
  197. /**
  198. * Suffix that is added to inner frames used for sending requests in non-IE
  199. * browsers
  200. * @type {string}
  201. */
  202. goog.net.IframeIo.INNER_FRAME_SUFFIX = '_inner';
  203. /**
  204. * The number of milliseconds after a request is completed to dispose the
  205. * iframes. This can be done lazily so we wait long enough for any processing
  206. * that occurred as a result of the response to finish.
  207. * @type {number}
  208. */
  209. goog.net.IframeIo.IFRAME_DISPOSE_DELAY_MS = 2000;
  210. /**
  211. * Counter used when creating iframes
  212. * @type {number}
  213. * @private
  214. */
  215. goog.net.IframeIo.counter_ = 0;
  216. /**
  217. * Form element to post to.
  218. * @type {HTMLFormElement}
  219. * @private
  220. */
  221. goog.net.IframeIo.form_;
  222. /**
  223. * Static send that creates a short lived instance of IframeIo to send the
  224. * request.
  225. * @param {goog.Uri|string} uri Uri of the request, it is up the caller to
  226. * manage query string params.
  227. * @param {Function=} opt_callback Event handler for when request is completed.
  228. * @param {string=} opt_method Default is GET, POST uses a form to submit the
  229. * request.
  230. * @param {boolean=} opt_noCache Append a timestamp to the request to avoid
  231. * caching.
  232. * @param {Object|goog.structs.Map=} opt_data Map of key-value pairs that
  233. * will be posted to the server via the iframe's form.
  234. */
  235. goog.net.IframeIo.send = function(
  236. uri, opt_callback, opt_method, opt_noCache, opt_data) {
  237. var io = new goog.net.IframeIo();
  238. goog.events.listen(io, goog.net.EventType.READY, io.dispose, false, io);
  239. if (opt_callback) {
  240. goog.events.listen(io, goog.net.EventType.COMPLETE, opt_callback);
  241. }
  242. io.send(uri, opt_method, opt_noCache, opt_data);
  243. };
  244. /**
  245. * Find an iframe by name (assumes the context is goog.global since that is
  246. * where IframeIo's iframes are kept).
  247. * @param {string} fname The name to find.
  248. * @return {HTMLIFrameElement} The iframe element with that name.
  249. */
  250. goog.net.IframeIo.getIframeByName = function(fname) {
  251. return window.frames[fname];
  252. };
  253. /**
  254. * Find an instance of the IframeIo object by name.
  255. * @param {string} fname The name to find.
  256. * @return {goog.net.IframeIo} The instance of IframeIo.
  257. */
  258. goog.net.IframeIo.getInstanceByName = function(fname) {
  259. return goog.net.IframeIo.instances_[fname];
  260. };
  261. /**
  262. * Handles incremental data and routes it to the correct iframeIo instance.
  263. * The HTML page requested by the IframeIo instance should contain script blocks
  264. * that call an externed reference to this method.
  265. * @param {Window} win The window object.
  266. * @param {Object} data The data object.
  267. */
  268. goog.net.IframeIo.handleIncrementalData = function(win, data) {
  269. // If this is the inner-frame, then we need to use the parent instead.
  270. var iframeName =
  271. goog.string.endsWith(win.name, goog.net.IframeIo.INNER_FRAME_SUFFIX) ?
  272. win.parent.name :
  273. win.name;
  274. var iframeIoName = iframeName.substring(0, iframeName.lastIndexOf('_'));
  275. var iframeIo = goog.net.IframeIo.getInstanceByName(iframeIoName);
  276. if (iframeIo && iframeName == iframeIo.iframeName_) {
  277. iframeIo.handleIncrementalData_(data);
  278. } else {
  279. var logger = goog.log.getLogger('goog.net.IframeIo');
  280. goog.log.info(logger, 'Incremental iframe data routed for unknown iframe');
  281. }
  282. };
  283. /**
  284. * @return {string} The next iframe name.
  285. * @private
  286. */
  287. goog.net.IframeIo.getNextName_ = function() {
  288. return goog.net.IframeIo.FRAME_NAME_PREFIX + goog.net.IframeIo.counter_++;
  289. };
  290. /**
  291. * Gets a static form, one for all instances of IframeIo since IE6 leaks form
  292. * nodes that are created/removed from the document.
  293. * @return {!HTMLFormElement} The static form.
  294. * @private
  295. */
  296. goog.net.IframeIo.getForm_ = function() {
  297. if (!goog.net.IframeIo.form_) {
  298. goog.net.IframeIo.form_ = goog.dom.createDom(goog.dom.TagName.FORM);
  299. goog.net.IframeIo.form_.acceptCharset = 'utf-8';
  300. // Hide the form and move it off screen
  301. var s = goog.net.IframeIo.form_.style;
  302. s.position = 'absolute';
  303. s.visibility = 'hidden';
  304. s.top = s.left = '-10px';
  305. s.width = s.height = '10px';
  306. s.overflow = 'hidden';
  307. goog.dom.getDocument().body.appendChild(goog.net.IframeIo.form_);
  308. }
  309. return goog.net.IframeIo.form_;
  310. };
  311. /**
  312. * Adds the key value pairs from a map like data structure to a form
  313. * @param {HTMLFormElement} form The form to add to.
  314. * @param {Object|goog.structs.Map|goog.Uri.QueryData} data The data to add.
  315. * @private
  316. */
  317. goog.net.IframeIo.addFormInputs_ = function(form, data) {
  318. var helper = goog.dom.getDomHelper(form);
  319. goog.structs.forEach(data, function(value, key) {
  320. if (!goog.isArray(value)) {
  321. value = [value];
  322. }
  323. goog.array.forEach(value, function(value) {
  324. var inp = helper.createDom(
  325. goog.dom.TagName.INPUT,
  326. {'type': goog.dom.InputType.HIDDEN, 'name': key, 'value': value});
  327. form.appendChild(inp);
  328. });
  329. });
  330. };
  331. /**
  332. * @return {boolean} Whether we can use readyState to monitor iframe loading.
  333. * @private
  334. */
  335. goog.net.IframeIo.useIeReadyStateCodePath_ = function() {
  336. // ReadyState is only available on iframes up to IE10.
  337. return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('11');
  338. };
  339. /**
  340. * Reference to a logger for the IframeIo objects
  341. * @type {goog.log.Logger}
  342. * @private
  343. */
  344. goog.net.IframeIo.prototype.logger_ = goog.log.getLogger('goog.net.IframeIo');
  345. /**
  346. * Reference to form element that gets reused for requests to the iframe.
  347. * @type {HTMLFormElement}
  348. * @private
  349. */
  350. goog.net.IframeIo.prototype.form_ = null;
  351. /**
  352. * Reference to the iframe being used for the current request, or null if no
  353. * request is currently active.
  354. * @type {HTMLIFrameElement}
  355. * @private
  356. */
  357. goog.net.IframeIo.prototype.iframe_ = null;
  358. /**
  359. * Name of the iframe being used for the current request, or null if no
  360. * request is currently active.
  361. * @type {?string}
  362. * @private
  363. */
  364. goog.net.IframeIo.prototype.iframeName_ = null;
  365. /**
  366. * Next id so that iframe names are unique.
  367. * @type {number}
  368. * @private
  369. */
  370. goog.net.IframeIo.prototype.nextIframeId_ = 0;
  371. /**
  372. * Whether the object is currently active with a request.
  373. * @type {boolean}
  374. * @private
  375. */
  376. goog.net.IframeIo.prototype.active_ = false;
  377. /**
  378. * Whether the last request is complete.
  379. * @type {boolean}
  380. * @private
  381. */
  382. goog.net.IframeIo.prototype.complete_ = false;
  383. /**
  384. * Whether the last request was a success.
  385. * @type {boolean}
  386. * @private
  387. */
  388. goog.net.IframeIo.prototype.success_ = false;
  389. /**
  390. * The URI for the last request.
  391. * @type {goog.Uri}
  392. * @private
  393. */
  394. goog.net.IframeIo.prototype.lastUri_ = null;
  395. /**
  396. * The text content of the last request.
  397. * @type {?string}
  398. * @private
  399. */
  400. goog.net.IframeIo.prototype.lastContent_ = null;
  401. /**
  402. * Last error code
  403. * @type {goog.net.ErrorCode}
  404. * @private
  405. */
  406. goog.net.IframeIo.prototype.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
  407. /**
  408. * Window timeout ID used to detect when firefox silently fails.
  409. * @type {?number}
  410. * @private
  411. */
  412. goog.net.IframeIo.prototype.firefoxSilentErrorTimeout_ = null;
  413. /**
  414. * Window timeout ID used by the timer that disposes the iframes.
  415. * @type {?number}
  416. * @private
  417. */
  418. goog.net.IframeIo.prototype.iframeDisposalTimer_ = null;
  419. /**
  420. * This is used to ensure that we don't handle errors twice for the same error.
  421. * We can reach the {@link #handleError_} method twice in IE if the form is
  422. * submitted while IE is offline and the URL is not available.
  423. * @type {boolean}
  424. * @private
  425. */
  426. goog.net.IframeIo.prototype.errorHandled_;
  427. /**
  428. * Whether to suppress the listeners that determine when the iframe loads.
  429. * @type {boolean}
  430. * @private
  431. */
  432. goog.net.IframeIo.prototype.ignoreResponse_ = false;
  433. /** @private {Function} */
  434. goog.net.IframeIo.prototype.errorChecker_;
  435. /** @private {Object} */
  436. goog.net.IframeIo.prototype.lastCustomError_;
  437. /** @private {?string} */
  438. goog.net.IframeIo.prototype.lastContentHtml_;
  439. /**
  440. * Sends a request via an iframe.
  441. *
  442. * A HTML form is used and submitted to the iframe, this simplifies the
  443. * difference between GET and POST requests. The iframe needs to be created and
  444. * destroyed for each request otherwise the request will contribute to the
  445. * history stack.
  446. *
  447. * sendFromForm does some clever trickery (thanks jlim) in non-IE browsers to
  448. * stop a history entry being added for POST requests.
  449. *
  450. * @param {goog.Uri|string} uri Uri of the request.
  451. * @param {string=} opt_method Default is GET, POST uses a form to submit the
  452. * request.
  453. * @param {boolean=} opt_noCache Append a timestamp to the request to avoid
  454. * caching.
  455. * @param {Object|goog.structs.Map=} opt_data Map of key-value pairs.
  456. */
  457. goog.net.IframeIo.prototype.send = function(
  458. uri, opt_method, opt_noCache, opt_data) {
  459. if (this.active_) {
  460. throw Error('[goog.net.IframeIo] Unable to send, already active.');
  461. }
  462. var uriObj = new goog.Uri(uri);
  463. this.lastUri_ = uriObj;
  464. var method = opt_method ? opt_method.toUpperCase() : 'GET';
  465. if (opt_noCache) {
  466. uriObj.makeUnique();
  467. }
  468. goog.log.info(
  469. this.logger_, 'Sending iframe request: ' + uriObj + ' [' + method + ']');
  470. // Build a form for this request
  471. this.form_ = goog.net.IframeIo.getForm_();
  472. if (method == 'GET') {
  473. // For GET requests, we assume that the caller didn't want the queryparams
  474. // already specified in the URI to be clobbered by the form, so we add the
  475. // params here.
  476. goog.net.IframeIo.addFormInputs_(this.form_, uriObj.getQueryData());
  477. }
  478. if (opt_data) {
  479. // Create form fields for each of the data values
  480. goog.net.IframeIo.addFormInputs_(this.form_, opt_data);
  481. }
  482. // Set the URI that the form will be posted
  483. this.form_.action = uriObj.toString();
  484. this.form_.method = method;
  485. this.sendFormInternal_();
  486. this.clearForm_();
  487. };
  488. /**
  489. * Sends the data stored in an existing form to the server. The HTTP method
  490. * should be specified on the form, the action can also be specified but can
  491. * be overridden by the optional URI param.
  492. *
  493. * This can be used in conjunction will a file-upload input to upload a file in
  494. * the background without affecting history.
  495. *
  496. * Example form:
  497. * <pre>
  498. * &lt;form action="/server/" enctype="multipart/form-data" method="POST"&gt;
  499. * &lt;input name="userfile" type="file"&gt;
  500. * &lt;/form&gt;
  501. * </pre>
  502. *
  503. * @param {HTMLFormElement} form Form element used to send the request to the
  504. * server.
  505. * @param {string=} opt_uri Uri to set for the destination of the request, by
  506. * default the uri will come from the form.
  507. * @param {boolean=} opt_noCache Append a timestamp to the request to avoid
  508. * caching.
  509. */
  510. goog.net.IframeIo.prototype.sendFromForm = function(
  511. form, opt_uri, opt_noCache) {
  512. if (this.active_) {
  513. throw Error('[goog.net.IframeIo] Unable to send, already active.');
  514. }
  515. var uri = new goog.Uri(opt_uri || form.action);
  516. if (opt_noCache) {
  517. uri.makeUnique();
  518. }
  519. goog.log.info(this.logger_, 'Sending iframe request from form: ' + uri);
  520. this.lastUri_ = uri;
  521. this.form_ = form;
  522. this.form_.action = uri.toString();
  523. this.sendFormInternal_();
  524. };
  525. /**
  526. * Abort the current Iframe request
  527. * @param {goog.net.ErrorCode=} opt_failureCode Optional error code to use -
  528. * defaults to ABORT.
  529. */
  530. goog.net.IframeIo.prototype.abort = function(opt_failureCode) {
  531. if (this.active_) {
  532. goog.log.info(this.logger_, 'Request aborted');
  533. var requestIframe = this.getRequestIframe();
  534. goog.asserts.assert(requestIframe);
  535. goog.events.removeAll(requestIframe);
  536. this.complete_ = false;
  537. this.active_ = false;
  538. this.success_ = false;
  539. this.lastErrorCode_ = opt_failureCode || goog.net.ErrorCode.ABORT;
  540. this.dispatchEvent(goog.net.EventType.ABORT);
  541. this.makeReady_();
  542. }
  543. };
  544. /** @override */
  545. goog.net.IframeIo.prototype.disposeInternal = function() {
  546. goog.log.fine(this.logger_, 'Disposing iframeIo instance');
  547. // If there is an active request, abort it
  548. if (this.active_) {
  549. goog.log.fine(this.logger_, 'Aborting active request');
  550. this.abort();
  551. }
  552. // Call super-classes implementation (remove listeners)
  553. goog.net.IframeIo.superClass_.disposeInternal.call(this);
  554. // Add the current iframe to the list of iframes for disposal.
  555. if (this.iframe_) {
  556. this.scheduleIframeDisposal_();
  557. }
  558. // Disposes of the form
  559. this.disposeForm_();
  560. // Nullify anything that might cause problems and clear state
  561. delete this.errorChecker_;
  562. this.form_ = null;
  563. this.lastCustomError_ = this.lastContent_ = this.lastContentHtml_ = null;
  564. this.lastUri_ = null;
  565. this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
  566. delete goog.net.IframeIo.instances_[this.name_];
  567. };
  568. /**
  569. * @return {boolean} True if transfer is complete.
  570. */
  571. goog.net.IframeIo.prototype.isComplete = function() {
  572. return this.complete_;
  573. };
  574. /**
  575. * @return {boolean} True if transfer was successful.
  576. */
  577. goog.net.IframeIo.prototype.isSuccess = function() {
  578. return this.success_;
  579. };
  580. /**
  581. * @return {boolean} True if a transfer is in progress.
  582. */
  583. goog.net.IframeIo.prototype.isActive = function() {
  584. return this.active_;
  585. };
  586. /**
  587. * Returns the last response text (i.e. the text content of the iframe).
  588. * Assumes plain text!
  589. * @return {?string} Result from the server.
  590. */
  591. goog.net.IframeIo.prototype.getResponseText = function() {
  592. return this.lastContent_;
  593. };
  594. /**
  595. * Returns the last response html (i.e. the innerHtml of the iframe).
  596. * @return {?string} Result from the server.
  597. */
  598. goog.net.IframeIo.prototype.getResponseHtml = function() {
  599. return this.lastContentHtml_;
  600. };
  601. /**
  602. * Parses the content as JSON. This is a legacy method for browsers without
  603. * JSON.parse or for responses that are not valid JSON (e.g. containing NaN).
  604. * Use JSON.parse(this.getResponseText()) in the other cases.
  605. * @return {Object} The parsed content.
  606. */
  607. goog.net.IframeIo.prototype.getResponseJson = function() {
  608. return goog.json.parse(this.lastContent_);
  609. };
  610. /**
  611. * Returns the document object from the last request. Not truly XML, but
  612. * used to mirror the XhrIo interface.
  613. * @return {HTMLDocument} The document object from the last request.
  614. */
  615. goog.net.IframeIo.prototype.getResponseXml = function() {
  616. if (!this.iframe_) return null;
  617. return this.getContentDocument_();
  618. };
  619. /**
  620. * Get the uri of the last request.
  621. * @return {goog.Uri} Uri of last request.
  622. */
  623. goog.net.IframeIo.prototype.getLastUri = function() {
  624. return this.lastUri_;
  625. };
  626. /**
  627. * Gets the last error code.
  628. * @return {goog.net.ErrorCode} Last error code.
  629. */
  630. goog.net.IframeIo.prototype.getLastErrorCode = function() {
  631. return this.lastErrorCode_;
  632. };
  633. /**
  634. * Gets the last error message.
  635. * @return {string} Last error message.
  636. */
  637. goog.net.IframeIo.prototype.getLastError = function() {
  638. return goog.net.ErrorCode.getDebugMessage(this.lastErrorCode_);
  639. };
  640. /**
  641. * Gets the last custom error.
  642. * @return {Object} Last custom error.
  643. */
  644. goog.net.IframeIo.prototype.getLastCustomError = function() {
  645. return this.lastCustomError_;
  646. };
  647. /**
  648. * Sets the callback function used to check if a loaded IFrame is in an error
  649. * state.
  650. * @param {Function} fn Callback that expects a document object as it's single
  651. * argument.
  652. */
  653. goog.net.IframeIo.prototype.setErrorChecker = function(fn) {
  654. this.errorChecker_ = fn;
  655. };
  656. /**
  657. * Gets the callback function used to check if a loaded IFrame is in an error
  658. * state.
  659. * @return {Function} A callback that expects a document object as it's single
  660. * argument.
  661. */
  662. goog.net.IframeIo.prototype.getErrorChecker = function() {
  663. return this.errorChecker_;
  664. };
  665. /**
  666. * @return {boolean} Whether the server response is being ignored.
  667. */
  668. goog.net.IframeIo.prototype.isIgnoringResponse = function() {
  669. return this.ignoreResponse_;
  670. };
  671. /**
  672. * Sets whether to ignore the response from the server by not adding any event
  673. * handlers to fire when the iframe loads. This is necessary when using IframeIo
  674. * to submit to a server on another domain, to avoid same-origin violations when
  675. * trying to access the response. If this is set to true, the IframeIo instance
  676. * will be a single-use instance that is only usable for one request. It will
  677. * only clean up its resources (iframes and forms) when it is disposed.
  678. * @param {boolean} ignore Whether to ignore the server response.
  679. */
  680. goog.net.IframeIo.prototype.setIgnoreResponse = function(ignore) {
  681. this.ignoreResponse_ = ignore;
  682. };
  683. /**
  684. * Submits the internal form to the iframe.
  685. * @private
  686. */
  687. goog.net.IframeIo.prototype.sendFormInternal_ = function() {
  688. this.active_ = true;
  689. this.complete_ = false;
  690. this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
  691. // Make Iframe
  692. this.createIframe_();
  693. if (goog.net.IframeIo.useIeReadyStateCodePath_()) {
  694. // In IE<11 we simply create the frame, wait until it is ready, then post
  695. // the form to the iframe and wait for the readystate to change to
  696. // 'complete'
  697. // Set the target to the iframe's name
  698. this.form_.target = this.iframeName_ || '';
  699. this.appendIframe_();
  700. if (!this.ignoreResponse_) {
  701. goog.events.listen(
  702. this.iframe_, goog.events.EventType.READYSTATECHANGE,
  703. this.onIeReadyStateChange_, false, this);
  704. }
  705. try {
  706. this.errorHandled_ = false;
  707. this.form_.submit();
  708. } catch (e) {
  709. // If submit threw an exception then it probably means the page that the
  710. // code is running on the local file system and the form's action was
  711. // pointing to a file that doesn't exist, causing the browser to fire an
  712. // exception. IE also throws an exception when it is working offline and
  713. // the URL is not available.
  714. if (!this.ignoreResponse_) {
  715. goog.events.unlisten(
  716. this.iframe_, goog.events.EventType.READYSTATECHANGE,
  717. this.onIeReadyStateChange_, false, this);
  718. }
  719. this.handleError_(goog.net.ErrorCode.ACCESS_DENIED);
  720. }
  721. } else {
  722. // For all other browsers we do some trickery to ensure that there is no
  723. // entry on the history stack. Thanks go to jlim for the prototype for this
  724. goog.log.fine(this.logger_, 'Setting up iframes and cloning form');
  725. this.appendIframe_();
  726. var innerFrameName =
  727. this.iframeName_ + goog.net.IframeIo.INNER_FRAME_SUFFIX;
  728. // Open and document.write another iframe into the iframe
  729. var doc = goog.dom.getFrameContentDocument(this.iframe_);
  730. var html;
  731. if (document.baseURI) {
  732. // On Safari 4 and 5 the new iframe doesn't inherit the current baseURI.
  733. html = goog.net.IframeIo.createIframeHtmlWithBaseUri_(innerFrameName);
  734. } else {
  735. html = goog.net.IframeIo.createIframeHtml_(innerFrameName);
  736. }
  737. if (goog.userAgent.OPERA && !goog.userAgent.WEBKIT) {
  738. // Presto based Opera adds a history entry when document.write is used.
  739. // Change the innerHTML of the page instead.
  740. goog.dom.safe.setInnerHtml(doc.documentElement, html);
  741. } else {
  742. goog.dom.safe.documentWrite(doc, html);
  743. }
  744. // Listen for the iframe's load
  745. if (!this.ignoreResponse_) {
  746. goog.events.listen(
  747. doc.getElementById(innerFrameName), goog.events.EventType.LOAD,
  748. this.onIframeLoaded_, false, this);
  749. }
  750. // Fix text areas, since importNode won't clone changes to the value
  751. var textareas = goog.dom.getElementsByTagName(
  752. goog.dom.TagName.TEXTAREA, goog.asserts.assert(this.form_));
  753. for (var i = 0, n = textareas.length; i < n; i++) {
  754. // The childnodes represent the initial child nodes for the text area
  755. // appending a text node essentially resets the initial value ready for
  756. // it to be clones - while maintaining HTML escaping.
  757. var value = textareas[i].value;
  758. if (goog.dom.getRawTextContent(textareas[i]) != value) {
  759. goog.dom.setTextContent(textareas[i], value);
  760. textareas[i].value = value;
  761. }
  762. }
  763. // Append a cloned form to the iframe
  764. var clone = doc.importNode(this.form_, true);
  765. clone.target = innerFrameName;
  766. // Work around crbug.com/66987
  767. clone.action = this.form_.action;
  768. doc.body.appendChild(clone);
  769. // Fix select boxes, importNode won't override the default value
  770. var selects = goog.dom.getElementsByTagName(
  771. goog.dom.TagName.SELECT, goog.asserts.assert(this.form_));
  772. var clones = goog.dom.getElementsByTagName(
  773. goog.dom.TagName.SELECT, /** @type {!Element} */ (clone));
  774. for (var i = 0, n = selects.length; i < n; i++) {
  775. var selectsOptions =
  776. goog.dom.getElementsByTagName(goog.dom.TagName.OPTION, selects[i]);
  777. var clonesOptions =
  778. goog.dom.getElementsByTagName(goog.dom.TagName.OPTION, clones[i]);
  779. for (var j = 0, m = selectsOptions.length; j < m; j++) {
  780. clonesOptions[j].selected = selectsOptions[j].selected;
  781. }
  782. }
  783. // IE and some versions of Firefox (1.5 - 1.5.07?) fail to clone the value
  784. // attribute for <input type="file"> nodes, which results in an empty
  785. // upload if the clone is submitted. Check, and if the clone failed, submit
  786. // using the original form instead.
  787. var inputs = goog.dom.getElementsByTagName(
  788. goog.dom.TagName.INPUT, goog.asserts.assert(this.form_));
  789. var inputClones = goog.dom.getElementsByTagName(
  790. goog.dom.TagName.INPUT, /** @type {!Element} */ (clone));
  791. for (var i = 0, n = inputs.length; i < n; i++) {
  792. if (inputs[i].type == goog.dom.InputType.FILE) {
  793. if (inputs[i].value != inputClones[i].value) {
  794. goog.log.fine(
  795. this.logger_, 'File input value not cloned properly. Will ' +
  796. 'submit using original form.');
  797. this.form_.target = innerFrameName;
  798. clone = this.form_;
  799. break;
  800. }
  801. }
  802. }
  803. goog.log.fine(this.logger_, 'Submitting form');
  804. try {
  805. this.errorHandled_ = false;
  806. clone.submit();
  807. doc.close();
  808. if (goog.userAgent.GECKO) {
  809. // This tests if firefox silently fails, this can happen, for example,
  810. // when the server resets the connection because of a large file upload
  811. this.firefoxSilentErrorTimeout_ =
  812. goog.Timer.callOnce(this.testForFirefoxSilentError_, 250, this);
  813. }
  814. } catch (e) {
  815. // If submit threw an exception then it probably means the page that the
  816. // code is running on the local file system and the form's action was
  817. // pointing to a file that doesn't exist, causing the browser to fire an
  818. // exception.
  819. goog.log.error(
  820. this.logger_,
  821. 'Error when submitting form: ' +
  822. goog.debug.HtmlFormatter.exposeException(e));
  823. if (!this.ignoreResponse_) {
  824. goog.events.unlisten(
  825. doc.getElementById(innerFrameName), goog.events.EventType.LOAD,
  826. this.onIframeLoaded_, false, this);
  827. }
  828. doc.close();
  829. this.handleError_(goog.net.ErrorCode.FILE_NOT_FOUND);
  830. }
  831. }
  832. };
  833. /**
  834. * @param {string} innerFrameName
  835. * @return {!goog.html.SafeHtml}
  836. * @private
  837. */
  838. goog.net.IframeIo.createIframeHtml_ = function(innerFrameName) {
  839. var innerFrameNameEscaped = goog.string.htmlEscape(innerFrameName);
  840. return goog.html.uncheckedconversions
  841. .safeHtmlFromStringKnownToSatisfyTypeContract(
  842. goog.string.Const.from(
  843. 'Short HTML snippet, input escaped, for performance'),
  844. '<body><iframe id="' + innerFrameNameEscaped + '" name="' +
  845. innerFrameNameEscaped + '"></iframe>');
  846. };
  847. /**
  848. * @param {string} innerFrameName
  849. * @return {!goog.html.SafeHtml}
  850. * @private
  851. */
  852. goog.net.IframeIo.createIframeHtmlWithBaseUri_ = function(innerFrameName) {
  853. var innerFrameNameEscaped = goog.string.htmlEscape(innerFrameName);
  854. return goog.html.uncheckedconversions
  855. .safeHtmlFromStringKnownToSatisfyTypeContract(
  856. goog.string.Const.from(
  857. 'Short HTML snippet, input escaped, safe URL, for performance'),
  858. '<head><base href="' +
  859. goog.string.htmlEscape(/** @type {string} */ (document.baseURI)) +
  860. '"></head>' +
  861. '<body><iframe id="' + innerFrameNameEscaped + '" name="' +
  862. innerFrameNameEscaped + '"></iframe>');
  863. };
  864. /**
  865. * Handles the load event of the iframe for IE, determines if the request was
  866. * successful or not, handles clean up and dispatching of appropriate events.
  867. * @param {goog.events.BrowserEvent} e The browser event.
  868. * @private
  869. */
  870. goog.net.IframeIo.prototype.onIeReadyStateChange_ = function(e) {
  871. if (this.iframe_.readyState == 'complete') {
  872. goog.events.unlisten(
  873. this.iframe_, goog.events.EventType.READYSTATECHANGE,
  874. this.onIeReadyStateChange_, false, this);
  875. var doc;
  876. try {
  877. doc = goog.dom.getFrameContentDocument(this.iframe_);
  878. // IE serves about:blank when it cannot load the resource while offline.
  879. if (goog.userAgent.IE && doc.location == 'about:blank' &&
  880. !navigator.onLine) {
  881. this.handleError_(goog.net.ErrorCode.OFFLINE);
  882. return;
  883. }
  884. } catch (ex) {
  885. this.handleError_(goog.net.ErrorCode.ACCESS_DENIED);
  886. return;
  887. }
  888. this.handleLoad_(/** @type {!HTMLDocument} */ (doc));
  889. }
  890. };
  891. /**
  892. * Handles the load event of the iframe for non-IE browsers.
  893. * @param {goog.events.BrowserEvent} e The browser event.
  894. * @private
  895. */
  896. goog.net.IframeIo.prototype.onIframeLoaded_ = function(e) {
  897. // In Presto based Opera, the default "about:blank" page of iframes fires an
  898. // onload event that we'd like to ignore.
  899. if (goog.userAgent.OPERA && !goog.userAgent.WEBKIT &&
  900. this.getContentDocument_().location == 'about:blank') {
  901. return;
  902. }
  903. goog.events.unlisten(
  904. this.getRequestIframe(), goog.events.EventType.LOAD, this.onIframeLoaded_,
  905. false, this);
  906. try {
  907. this.handleLoad_(this.getContentDocument_());
  908. } catch (ex) {
  909. this.handleError_(goog.net.ErrorCode.ACCESS_DENIED);
  910. }
  911. };
  912. /**
  913. * Handles generic post-load
  914. * @param {HTMLDocument} contentDocument The frame's document.
  915. * @private
  916. */
  917. goog.net.IframeIo.prototype.handleLoad_ = function(contentDocument) {
  918. goog.log.fine(this.logger_, 'Iframe loaded');
  919. this.complete_ = true;
  920. this.active_ = false;
  921. var errorCode;
  922. // Try to get the innerHTML. If this fails then it can be an access denied
  923. // error or the document may just not have a body, typical case is if there
  924. // is an IE's default 404.
  925. try {
  926. var body = contentDocument.body;
  927. this.lastContent_ = body.textContent || body.innerText;
  928. this.lastContentHtml_ = body.innerHTML;
  929. } catch (ex) {
  930. errorCode = goog.net.ErrorCode.ACCESS_DENIED;
  931. }
  932. // Use a callback function, defined by the application, to analyse the
  933. // contentDocument and determine if it is an error page. Applications
  934. // may send down markers in the document, define JS vars, or some other test.
  935. var customError;
  936. if (!errorCode && typeof this.errorChecker_ == 'function') {
  937. customError = this.errorChecker_(contentDocument);
  938. if (customError) {
  939. errorCode = goog.net.ErrorCode.CUSTOM_ERROR;
  940. }
  941. }
  942. goog.log.log(
  943. this.logger_, goog.log.Level.FINER, 'Last content: ' + this.lastContent_);
  944. goog.log.log(
  945. this.logger_, goog.log.Level.FINER, 'Last uri: ' + this.lastUri_);
  946. if (errorCode) {
  947. goog.log.fine(this.logger_, 'Load event occurred but failed');
  948. this.handleError_(errorCode, customError);
  949. } else {
  950. goog.log.fine(this.logger_, 'Load succeeded');
  951. this.success_ = true;
  952. this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
  953. this.dispatchEvent(goog.net.EventType.COMPLETE);
  954. this.dispatchEvent(goog.net.EventType.SUCCESS);
  955. this.makeReady_();
  956. }
  957. };
  958. /**
  959. * Handles errors.
  960. * @param {goog.net.ErrorCode} errorCode Error code.
  961. * @param {Object=} opt_customError If error is CUSTOM_ERROR, this is the
  962. * client-provided custom error.
  963. * @private
  964. */
  965. goog.net.IframeIo.prototype.handleError_ = function(
  966. errorCode, opt_customError) {
  967. if (!this.errorHandled_) {
  968. this.success_ = false;
  969. this.active_ = false;
  970. this.complete_ = true;
  971. this.lastErrorCode_ = errorCode;
  972. if (errorCode == goog.net.ErrorCode.CUSTOM_ERROR) {
  973. goog.asserts.assert(goog.isDef(opt_customError));
  974. this.lastCustomError_ = opt_customError;
  975. }
  976. this.dispatchEvent(goog.net.EventType.COMPLETE);
  977. this.dispatchEvent(goog.net.EventType.ERROR);
  978. this.makeReady_();
  979. this.errorHandled_ = true;
  980. }
  981. };
  982. /**
  983. * Dispatches an event indicating that the IframeIo instance has received a data
  984. * packet via incremental loading. The event object has a 'data' member.
  985. * @param {Object} data Data.
  986. * @private
  987. */
  988. goog.net.IframeIo.prototype.handleIncrementalData_ = function(data) {
  989. this.dispatchEvent(new goog.net.IframeIo.IncrementalDataEvent(data));
  990. };
  991. /**
  992. * Finalizes the request, schedules the iframe for disposal, and maybe disposes
  993. * the form.
  994. * @private
  995. */
  996. goog.net.IframeIo.prototype.makeReady_ = function() {
  997. goog.log.info(this.logger_, 'Ready for new requests');
  998. this.scheduleIframeDisposal_();
  999. this.disposeForm_();
  1000. this.dispatchEvent(goog.net.EventType.READY);
  1001. };
  1002. /**
  1003. * Creates an iframe to be used with a request. We use a new iframe for each
  1004. * request so that requests don't create history entries.
  1005. * @private
  1006. */
  1007. goog.net.IframeIo.prototype.createIframe_ = function() {
  1008. goog.log.fine(this.logger_, 'Creating iframe');
  1009. this.iframeName_ = this.name_ + '_' + (this.nextIframeId_++).toString(36);
  1010. var dom = goog.dom.getDomHelper(this.form_);
  1011. this.iframe_ = dom.createDom(
  1012. goog.dom.TagName.IFRAME,
  1013. {'name': this.iframeName_, 'id': this.iframeName_});
  1014. // Setting the source to javascript:"" is a fix to remove IE6 mixed content
  1015. // warnings when being used in an https page.
  1016. if (goog.userAgent.IE && Number(goog.userAgent.VERSION) < 7) {
  1017. this.iframe_.src = 'javascript:""';
  1018. }
  1019. var s = this.iframe_.style;
  1020. s.visibility = 'hidden';
  1021. s.width = s.height = '10px';
  1022. // Chrome sometimes shows scrollbars when visibility is hidden, but not when
  1023. // display is none.
  1024. s.display = 'none';
  1025. // There are reports that safari 2.0.3 has a bug where absolutely positioned
  1026. // iframes can't have their src set.
  1027. if (!goog.userAgent.WEBKIT) {
  1028. s.position = 'absolute';
  1029. s.top = s.left = '-10px';
  1030. } else {
  1031. s.marginTop = s.marginLeft = '-10px';
  1032. }
  1033. };
  1034. /**
  1035. * Appends the Iframe to the document body.
  1036. * @private
  1037. */
  1038. goog.net.IframeIo.prototype.appendIframe_ = function() {
  1039. goog.dom.getDomHelper(this.form_)
  1040. .getDocument()
  1041. .body.appendChild(this.iframe_);
  1042. };
  1043. /**
  1044. * Schedules an iframe for disposal, async. We can't remove the iframes in the
  1045. * same execution context as the response, otherwise some versions of Firefox
  1046. * will not detect that the response has correctly finished and the loading bar
  1047. * will stay active forever.
  1048. * @private
  1049. */
  1050. goog.net.IframeIo.prototype.scheduleIframeDisposal_ = function() {
  1051. var iframe = this.iframe_;
  1052. // There shouldn't be a case where the iframe is null and we get to this
  1053. // stage, but the error reports in http://b/909448 indicate it is possible.
  1054. if (iframe) {
  1055. // NOTE(user): Stops Internet Explorer leaking the iframe object. This
  1056. // shouldn't be needed, since the events have all been removed, which
  1057. // should in theory clean up references. Oh well...
  1058. iframe.onreadystatechange = null;
  1059. iframe.onload = null;
  1060. iframe.onerror = null;
  1061. this.iframesForDisposal_.push(iframe);
  1062. }
  1063. if (this.iframeDisposalTimer_) {
  1064. goog.Timer.clear(this.iframeDisposalTimer_);
  1065. this.iframeDisposalTimer_ = null;
  1066. }
  1067. if (goog.userAgent.GECKO ||
  1068. (goog.userAgent.OPERA && !goog.userAgent.WEBKIT)) {
  1069. // For FF and Presto Opera, we must dispose the iframe async,
  1070. // but it doesn't need to be done as soon as possible.
  1071. // We therefore schedule it for 2s out, so as not to
  1072. // affect any other actions that may have been triggered by the request.
  1073. this.iframeDisposalTimer_ = goog.Timer.callOnce(
  1074. this.disposeIframes_, goog.net.IframeIo.IFRAME_DISPOSE_DELAY_MS, this);
  1075. } else {
  1076. // For non-Gecko browsers we dispose straight away.
  1077. this.disposeIframes_();
  1078. }
  1079. // Nullify reference
  1080. this.iframe_ = null;
  1081. this.iframeName_ = null;
  1082. };
  1083. /**
  1084. * Disposes any iframes.
  1085. * @private
  1086. */
  1087. goog.net.IframeIo.prototype.disposeIframes_ = function() {
  1088. if (this.iframeDisposalTimer_) {
  1089. // Clear the timer
  1090. goog.Timer.clear(this.iframeDisposalTimer_);
  1091. this.iframeDisposalTimer_ = null;
  1092. }
  1093. while (this.iframesForDisposal_.length != 0) {
  1094. var iframe = this.iframesForDisposal_.pop();
  1095. goog.log.info(this.logger_, 'Disposing iframe');
  1096. goog.dom.removeNode(iframe);
  1097. }
  1098. };
  1099. /**
  1100. * Removes all the child nodes from the static form so it can be reused again.
  1101. * This should happen right after sending a request. Otherwise, there can be
  1102. * issues when another iframe uses this form right after the first iframe.
  1103. * @private
  1104. */
  1105. goog.net.IframeIo.prototype.clearForm_ = function() {
  1106. if (this.form_ && this.form_ == goog.net.IframeIo.form_) {
  1107. goog.dom.removeChildren(this.form_);
  1108. }
  1109. };
  1110. /**
  1111. * Disposes of the Form. Since IE6 leaks form nodes, this just cleans up the
  1112. * DOM and nullifies the instances reference so the form can be used for another
  1113. * request.
  1114. * @private
  1115. */
  1116. goog.net.IframeIo.prototype.disposeForm_ = function() {
  1117. this.clearForm_();
  1118. this.form_ = null;
  1119. };
  1120. /**
  1121. * @return {HTMLDocument} The appropriate content document.
  1122. * @private
  1123. */
  1124. goog.net.IframeIo.prototype.getContentDocument_ = function() {
  1125. if (this.iframe_) {
  1126. return /** @type {!HTMLDocument} */ (
  1127. goog.dom.getFrameContentDocument(this.getRequestIframe()));
  1128. }
  1129. return null;
  1130. };
  1131. /**
  1132. * @return {HTMLIFrameElement} The appropriate iframe to use for requests
  1133. * (created in sendForm_).
  1134. */
  1135. goog.net.IframeIo.prototype.getRequestIframe = function() {
  1136. if (this.iframe_) {
  1137. return /** @type {HTMLIFrameElement} */ (
  1138. goog.net.IframeIo.useIeReadyStateCodePath_() ?
  1139. this.iframe_ :
  1140. goog.dom.getFrameContentDocument(this.iframe_)
  1141. .getElementById(
  1142. this.iframeName_ + goog.net.IframeIo.INNER_FRAME_SUFFIX));
  1143. }
  1144. return null;
  1145. };
  1146. /**
  1147. * Tests for a silent failure by firefox that can occur when the connection is
  1148. * reset by the server or is made to an illegal URL.
  1149. * @private
  1150. */
  1151. goog.net.IframeIo.prototype.testForFirefoxSilentError_ = function() {
  1152. if (this.active_) {
  1153. var doc = this.getContentDocument_();
  1154. // This is a hack to test of the document has loaded with a page that
  1155. // we can't access, such as a network error, that won't report onload
  1156. // or onerror events.
  1157. if (doc && !goog.reflect.canAccessProperty(doc, 'documentUri')) {
  1158. if (!this.ignoreResponse_) {
  1159. goog.events.unlisten(
  1160. this.getRequestIframe(), goog.events.EventType.LOAD,
  1161. this.onIframeLoaded_, false, this);
  1162. }
  1163. if (navigator.onLine) {
  1164. goog.log.warning(this.logger_, 'Silent Firefox error detected');
  1165. this.handleError_(goog.net.ErrorCode.FF_SILENT_ERROR);
  1166. } else {
  1167. goog.log.warning(
  1168. this.logger_, 'Firefox is offline so report offline error ' +
  1169. 'instead of silent error');
  1170. this.handleError_(goog.net.ErrorCode.OFFLINE);
  1171. }
  1172. return;
  1173. }
  1174. this.firefoxSilentErrorTimeout_ =
  1175. goog.Timer.callOnce(this.testForFirefoxSilentError_, 250, this);
  1176. }
  1177. };
  1178. /**
  1179. * Class for representing incremental data events.
  1180. * @param {Object} data The data associated with the event.
  1181. * @extends {goog.events.Event}
  1182. * @constructor
  1183. * @final
  1184. */
  1185. goog.net.IframeIo.IncrementalDataEvent = function(data) {
  1186. goog.events.Event.call(this, goog.net.EventType.INCREMENTAL_DATA);
  1187. /**
  1188. * The data associated with the event.
  1189. * @type {Object}
  1190. */
  1191. this.data = data;
  1192. };
  1193. goog.inherits(goog.net.IframeIo.IncrementalDataEvent, goog.events.Event);