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