Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/net/crossdomainrpc.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 Cross domain RPC library using the <a
17
* href="http://go/xd2_design" target="_top">XD2 approach</a>.
18
*
19
* <h5>Protocol</h5>
20
* Client sends a request across domain via a form submission. Server
21
* receives these parameters: "xdpe:request-id", "xdpe:dummy-uri" ("xdpe" for
22
* "cross domain parameter to echo back") and other user parameters prefixed
23
* with "xdp" (for "cross domain parameter"). Headers are passed as parameters
24
* prefixed with "xdh" (for "cross domain header"). Only strings are supported
25
* for parameters and headers. A GET method is mapped to a form GET. All
26
* other methods are mapped to a POST. Server is expected to produce a
27
* HTML response such as the following:
28
* <pre>
29
* &lt;body&gt;
30
* &lt;script type="text/javascript"
31
* src="path-to-crossdomainrpc.js"&gt;&lt;/script&gt;
32
* var currentDirectory = location.href.substring(
33
* 0, location.href.lastIndexOf('/')
34
* );
35
*
36
* // echo all parameters prefixed with "xdpe:"
37
* var echo = {};
38
* echo[goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID] =
39
* &lt;value of parameter "xdpe:request-id"&gt;;
40
* echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI] =
41
* &lt;value of parameter "xdpe:dummy-uri"&gt;;
42
*
43
* goog.net.CrossDomainRpc.sendResponse(
44
* '({"result":"&lt;responseInJSON"})',
45
* true, // is JSON
46
* echo, // parameters to echo back
47
* status, // response status code
48
* headers // response headers
49
* );
50
* &lt;/script&gt;
51
* &lt;/body&gt;
52
* </pre>
53
*
54
* <h5>Server Side</h5>
55
* For an example of the server side, refer to the following files:
56
* <ul>
57
* <li>http://go/xdservletfilter.java</li>
58
* <li>http://go/xdservletrequest.java</li>
59
* <li>http://go/xdservletresponse.java</li>
60
* </ul>
61
*
62
* <h5>System Requirements</h5>
63
* Tested on IE6, IE7, Firefox 2.0 and Safari nightly r23841.
64
*
65
*/
66
67
goog.provide('goog.net.CrossDomainRpc');
68
69
goog.require('goog.Uri');
70
goog.require('goog.dom');
71
goog.require('goog.dom.TagName');
72
goog.require('goog.dom.safe');
73
goog.require('goog.events');
74
goog.require('goog.events.EventTarget');
75
goog.require('goog.events.EventType');
76
goog.require('goog.html.SafeHtml');
77
goog.require('goog.log');
78
goog.require('goog.net.EventType');
79
goog.require('goog.net.HttpStatus');
80
goog.require('goog.string');
81
goog.require('goog.userAgent');
82
83
84
85
/**
86
* Creates a new instance of cross domain RPC.
87
*
88
* @extends {goog.events.EventTarget}
89
* @constructor
90
* @final
91
*/
92
goog.net.CrossDomainRpc = function() {
93
goog.events.EventTarget.call(this);
94
};
95
goog.inherits(goog.net.CrossDomainRpc, goog.events.EventTarget);
96
97
98
/**
99
* Cross-domain response iframe marker.
100
* @type {string}
101
* @private
102
*/
103
goog.net.CrossDomainRpc.RESPONSE_MARKER_ = 'xdrp';
104
105
106
/**
107
* Use a fallback dummy resource if none specified or detected.
108
* @type {boolean}
109
* @private
110
*/
111
goog.net.CrossDomainRpc.useFallBackDummyResource_ = true;
112
113
114
/** @type {Object} */
115
goog.net.CrossDomainRpc.prototype.responseHeaders;
116
117
118
/** @type {string} */
119
goog.net.CrossDomainRpc.prototype.responseText;
120
121
122
/** @type {number} */
123
goog.net.CrossDomainRpc.prototype.status;
124
125
126
/** @type {number} */
127
goog.net.CrossDomainRpc.prototype.timeWaitedAfterResponseReady_;
128
129
130
/** @private {boolean} */
131
goog.net.CrossDomainRpc.prototype.responseTextIsJson_;
132
133
134
/** @private {boolean} */
135
goog.net.CrossDomainRpc.prototype.responseReady_;
136
137
138
/** @private {!HTMLIFrameElement} */
139
goog.net.CrossDomainRpc.prototype.requestFrame_;
140
141
142
/** @private {goog.events.Key} */
143
goog.net.CrossDomainRpc.prototype.loadListenerKey_;
144
145
146
/**
147
* Checks to see if we are executing inside a response iframe. This is the
148
* case when this page is used as a dummy resource to gain caller's domain.
149
* @return {*} True if we are executing inside a response iframe; false
150
* otherwise.
151
* @private
152
*/
153
goog.net.CrossDomainRpc.isInResponseIframe_ = function() {
154
return window.location &&
155
(window.location.hash.indexOf(goog.net.CrossDomainRpc.RESPONSE_MARKER_) ==
156
1 ||
157
window.location.search.indexOf(
158
goog.net.CrossDomainRpc.RESPONSE_MARKER_) == 1);
159
};
160
161
162
/**
163
* Stops execution of the rest of the page if this page is loaded inside a
164
* response iframe.
165
*/
166
if (goog.net.CrossDomainRpc.isInResponseIframe_()) {
167
if (goog.userAgent.EDGE_OR_IE) {
168
document.execCommand('Stop');
169
} else if (goog.userAgent.GECKO) {
170
window.stop();
171
} else {
172
throw Error('stopped');
173
}
174
}
175
176
177
/**
178
* Sets the URI for a dummy resource on caller's domain. This function is
179
* used for specifying a particular resource to use rather than relying on
180
* auto detection.
181
* @param {string} dummyResourceUri URI to dummy resource on the same domain
182
* of caller's page.
183
*/
184
goog.net.CrossDomainRpc.setDummyResourceUri = function(dummyResourceUri) {
185
goog.net.CrossDomainRpc.dummyResourceUri_ = dummyResourceUri;
186
};
187
188
189
/**
190
* Sets whether a fallback dummy resource ("/robots.txt" on Firefox and Safari
191
* and current page on IE) should be used when a suitable dummy resource is
192
* not available.
193
* @param {boolean} useFallBack Whether to use fallback or not.
194
*/
195
goog.net.CrossDomainRpc.setUseFallBackDummyResource = function(useFallBack) {
196
goog.net.CrossDomainRpc.useFallBackDummyResource_ = useFallBack;
197
};
198
199
200
/**
201
* Sends a request across domain.
202
* @param {string} uri Uri to make request to.
203
* @param {Function=} opt_continuation Continuation function to be called
204
* when request is completed. Takes one argument of an event object
205
* whose target has the following properties: "status" is the HTTP
206
* response status code, "responseText" is the response text,
207
* and "headers" is an object with all response headers. The event
208
* target's getResponseJson() method returns a JavaScript object evaluated
209
* from the JSON response or undefined if response is not JSON.
210
* @param {string=} opt_method Method of request. Default is POST.
211
* @param {Object=} opt_params Parameters. Each property is turned into a
212
* request parameter.
213
* @param {Object=} opt_headers Map of headers of the request.
214
*/
215
goog.net.CrossDomainRpc.send = function(
216
uri, opt_continuation, opt_method, opt_params, opt_headers) {
217
var xdrpc = new goog.net.CrossDomainRpc();
218
if (opt_continuation) {
219
goog.events.listen(xdrpc, goog.net.EventType.COMPLETE, opt_continuation);
220
}
221
goog.events.listen(xdrpc, goog.net.EventType.READY, xdrpc.reset);
222
xdrpc.sendRequest(uri, opt_method, opt_params, opt_headers);
223
};
224
225
226
/**
227
* Sets debug mode to true or false. When debug mode is on, response iframes
228
* are visible and left behind after their use is finished.
229
* @param {boolean} flag Flag to indicate intention to turn debug model on
230
* (true) or off (false).
231
*/
232
goog.net.CrossDomainRpc.setDebugMode = function(flag) {
233
goog.net.CrossDomainRpc.debugMode_ = flag;
234
};
235
236
237
/**
238
* Logger for goog.net.CrossDomainRpc
239
* @type {goog.log.Logger}
240
* @private
241
*/
242
goog.net.CrossDomainRpc.logger_ = goog.log.getLogger('goog.net.CrossDomainRpc');
243
244
245
/**
246
* Creates the HTML of an input element
247
* @param {string} name Name of input element.
248
* @param {*} value Value of input element.
249
* @return {!goog.html.SafeHtml} HTML of input element with that name and value.
250
* @private
251
*/
252
goog.net.CrossDomainRpc.createInputHtml_ = function(name, value) {
253
return goog.html.SafeHtml.create('textarea', {'name': name}, String(value));
254
};
255
256
257
/**
258
* Finds a dummy resource that can be used by response to gain domain of
259
* requester's page.
260
* @return {string} URI of the resource to use.
261
* @private
262
*/
263
goog.net.CrossDomainRpc.getDummyResourceUri_ = function() {
264
if (goog.net.CrossDomainRpc.dummyResourceUri_) {
265
return goog.net.CrossDomainRpc.dummyResourceUri_;
266
}
267
268
// find a style sheet if not on IE, which will attempt to save style sheet
269
if (goog.userAgent.GECKO) {
270
var links = goog.dom.getElementsByTagName(goog.dom.TagName.LINK);
271
for (var i = 0; i < links.length; i++) {
272
var link = links[i];
273
// find a link which is on the same domain as this page
274
// cannot use one with '?' or '#' in its URL as it will confuse
275
// goog.net.CrossDomainRpc.getFramePayload_()
276
if (link.rel == 'stylesheet' &&
277
goog.Uri.haveSameDomain(link.href, window.location.href) &&
278
link.href.indexOf('?') < 0) {
279
return goog.net.CrossDomainRpc.removeHash_(link.href);
280
}
281
}
282
}
283
284
var images = goog.dom.getElementsByTagName(goog.dom.TagName.IMG);
285
for (var i = 0; i < images.length; i++) {
286
var image = images[i];
287
// find a link which is on the same domain as this page
288
// cannot use one with '?' or '#' in its URL as it will confuse
289
// goog.net.CrossDomainRpc.getFramePayload_()
290
if (goog.Uri.haveSameDomain(image.src, window.location.href) &&
291
image.src.indexOf('?') < 0) {
292
return goog.net.CrossDomainRpc.removeHash_(image.src);
293
}
294
}
295
296
if (!goog.net.CrossDomainRpc.useFallBackDummyResource_) {
297
throw Error(
298
'No suitable dummy resource specified or detected for this page');
299
}
300
301
if (goog.userAgent.EDGE_OR_IE) {
302
// use this page as the dummy resource; remove hash from URL if any
303
return goog.net.CrossDomainRpc.removeHash_(window.location.href);
304
} else {
305
/**
306
* Try to use "http://<this-domain>/robots.txt" which may exist. Even if
307
* it does not, an error page is returned and is a good dummy resource to
308
* use on Firefox and Safari. An existing resource is faster because it
309
* is cached.
310
*/
311
var locationHref = window.location.href;
312
var rootSlash = locationHref.indexOf('/', locationHref.indexOf('//') + 2);
313
var rootHref = locationHref.substring(0, rootSlash);
314
return rootHref + '/robots.txt';
315
}
316
};
317
318
319
/**
320
* Removes everything at and after hash from URI
321
* @param {string} uri Uri to to remove hash.
322
* @return {string} Uri with its hash and all characters after removed.
323
* @private
324
*/
325
goog.net.CrossDomainRpc.removeHash_ = function(uri) {
326
return uri.split('#')[0];
327
};
328
329
330
// ------------
331
// request side
332
333
334
/**
335
* next request id used to support multiple XD requests at the same time
336
* @type {number}
337
* @private
338
*/
339
goog.net.CrossDomainRpc.nextRequestId_ = 0;
340
341
342
/**
343
* Header prefix.
344
* @type {string}
345
*/
346
goog.net.CrossDomainRpc.HEADER = 'xdh:';
347
348
349
/**
350
* Parameter prefix.
351
* @type {string}
352
*/
353
goog.net.CrossDomainRpc.PARAM = 'xdp:';
354
355
356
/**
357
* Parameter to echo prefix.
358
* @type {string}
359
*/
360
goog.net.CrossDomainRpc.PARAM_ECHO = 'xdpe:';
361
362
363
/**
364
* Parameter to echo: request id
365
* @type {string}
366
*/
367
goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID =
368
goog.net.CrossDomainRpc.PARAM_ECHO + 'request-id';
369
370
371
/**
372
* Parameter to echo: dummy resource URI
373
* @type {string}
374
*/
375
goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI =
376
goog.net.CrossDomainRpc.PARAM_ECHO + 'dummy-uri';
377
378
379
/**
380
* Cross-domain request marker.
381
* @type {string}
382
* @private
383
*/
384
goog.net.CrossDomainRpc.REQUEST_MARKER_ = 'xdrq';
385
386
387
/**
388
* Sends a request across domain.
389
* @param {string} uri Uri to make request to.
390
* @param {string=} opt_method Method of request, 'GET' or 'POST' (uppercase).
391
* Default is 'POST'.
392
* @param {Object=} opt_params Parameters. Each property is turned into a
393
* request parameter.
394
* @param {Object=} opt_headers Map of headers of the request.
395
*/
396
goog.net.CrossDomainRpc.prototype.sendRequest = function(
397
uri, opt_method, opt_params, opt_headers) {
398
// create request frame
399
var requestFrame = this.requestFrame_ =
400
goog.dom.createElement(goog.dom.TagName.IFRAME);
401
var requestId = goog.net.CrossDomainRpc.nextRequestId_++;
402
requestFrame.id = goog.net.CrossDomainRpc.REQUEST_MARKER_ + '-' + requestId;
403
if (!goog.net.CrossDomainRpc.debugMode_) {
404
requestFrame.style.position = 'absolute';
405
requestFrame.style.top = '-5000px';
406
requestFrame.style.left = '-5000px';
407
}
408
document.body.appendChild(requestFrame);
409
410
// build inputs
411
var inputs = [];
412
413
// add request id
414
inputs.push(
415
goog.net.CrossDomainRpc.createInputHtml_(
416
goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID, requestId));
417
418
// add dummy resource uri
419
var dummyUri = goog.net.CrossDomainRpc.getDummyResourceUri_();
420
goog.log.fine(goog.net.CrossDomainRpc.logger_, 'dummyUri: ' + dummyUri);
421
inputs.push(
422
goog.net.CrossDomainRpc.createInputHtml_(
423
goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI, dummyUri));
424
425
// add parameters
426
if (opt_params) {
427
for (var name in opt_params) {
428
var value = opt_params[name];
429
inputs.push(
430
goog.net.CrossDomainRpc.createInputHtml_(
431
goog.net.CrossDomainRpc.PARAM + name, value));
432
}
433
}
434
435
// add headers
436
if (opt_headers) {
437
for (var name in opt_headers) {
438
var value = opt_headers[name];
439
inputs.push(
440
goog.net.CrossDomainRpc.createInputHtml_(
441
goog.net.CrossDomainRpc.HEADER + name, value));
442
}
443
}
444
445
var requestFrameContentHtml = goog.html.SafeHtml.create(
446
'body', {},
447
goog.html.SafeHtml.create(
448
'form',
449
{'method': opt_method == 'GET' ? 'GET' : 'POST', 'action': uri},
450
inputs));
451
var requestFrameDoc = goog.dom.getFrameContentDocument(requestFrame);
452
requestFrameDoc.open();
453
goog.dom.safe.documentWrite(requestFrameDoc, requestFrameContentHtml);
454
requestFrameDoc.close();
455
456
requestFrameDoc.forms[0].submit();
457
requestFrameDoc = null;
458
459
this.loadListenerKey_ =
460
goog.events.listen(requestFrame, goog.events.EventType.LOAD, function() {
461
goog.log.fine(goog.net.CrossDomainRpc.logger_, 'response ready');
462
this.responseReady_ = true;
463
}, false, this);
464
465
this.receiveResponse_();
466
};
467
468
469
/**
470
* period of response polling (ms)
471
* @type {number}
472
* @private
473
*/
474
goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_ = 50;
475
476
477
/**
478
* timeout from response comes back to sendResponse is called (ms)
479
* @type {number}
480
* @private
481
*/
482
goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_ = 500;
483
484
485
/**
486
* Receives response by polling to check readiness of response and then
487
* reads response frames and assembles response data
488
* @private
489
*/
490
goog.net.CrossDomainRpc.prototype.receiveResponse_ = function() {
491
this.timeWaitedAfterResponseReady_ = 0;
492
var responseDetectorHandle = window.setInterval(goog.bind(function() {
493
this.detectResponse_(responseDetectorHandle);
494
}, this), goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_);
495
};
496
497
498
/**
499
* Detects response inside request frame
500
* @param {number} responseDetectorHandle Handle of detector.
501
* @private
502
*/
503
goog.net.CrossDomainRpc.prototype.detectResponse_ = function(
504
responseDetectorHandle) {
505
var requestFrameWindow = this.requestFrame_.contentWindow;
506
var grandChildrenLength = requestFrameWindow.frames.length;
507
var responseInfoFrame = null;
508
if (grandChildrenLength > 0 &&
509
goog.net.CrossDomainRpc.isResponseInfoFrame_(
510
responseInfoFrame =
511
requestFrameWindow.frames[grandChildrenLength - 1])) {
512
goog.log.fine(goog.net.CrossDomainRpc.logger_, 'xd response ready');
513
514
var responseInfoPayload =
515
goog.net.CrossDomainRpc.getFramePayload_(responseInfoFrame)
516
.substring(1);
517
var params = new goog.Uri.QueryData(responseInfoPayload);
518
519
var chunks = [];
520
var numChunks = Number(params.get('n'));
521
goog.log.fine(
522
goog.net.CrossDomainRpc.logger_,
523
'xd response number of chunks: ' + numChunks);
524
for (var i = 0; i < numChunks; i++) {
525
var responseFrame = requestFrameWindow.frames[i];
526
if (!responseFrame || !responseFrame.location ||
527
!responseFrame.location.href) {
528
// On Safari 3.0, it is sometimes the case that the
529
// iframe exists but doesn't have a same domain href yet.
530
goog.log.fine(
531
goog.net.CrossDomainRpc.logger_, 'xd response iframe not ready');
532
return;
533
}
534
var responseChunkPayload =
535
goog.net.CrossDomainRpc.getFramePayload_(responseFrame);
536
// go past "chunk="
537
var chunkIndex =
538
responseChunkPayload.indexOf(goog.net.CrossDomainRpc.PARAM_CHUNK_) +
539
goog.net.CrossDomainRpc.PARAM_CHUNK_.length + 1;
540
var chunk = responseChunkPayload.substring(chunkIndex);
541
chunks.push(chunk);
542
}
543
544
window.clearInterval(responseDetectorHandle);
545
546
var responseData = chunks.join('');
547
// Payload is not encoded to begin with on IE. Decode in other cases only.
548
if (!goog.userAgent.EDGE_OR_IE) {
549
responseData = decodeURIComponent(responseData);
550
}
551
552
this.status = Number(params.get('status'));
553
this.responseText = responseData;
554
this.responseTextIsJson_ = params.get('isDataJson') == 'true';
555
this.responseHeaders = /** @type {?Object} */ (JSON.parse(
556
/** @type {string} */ (params.get('headers'))));
557
558
this.dispatchEvent(goog.net.EventType.READY);
559
this.dispatchEvent(goog.net.EventType.COMPLETE);
560
} else {
561
if (this.responseReady_) {
562
/* The response has come back. But the first response iframe has not
563
* been created yet. If this lasts long enough, it is an error.
564
*/
565
this.timeWaitedAfterResponseReady_ +=
566
goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_;
567
if (this.timeWaitedAfterResponseReady_ >
568
goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_) {
569
goog.log.fine(goog.net.CrossDomainRpc.logger_, 'xd response timed out');
570
window.clearInterval(responseDetectorHandle);
571
572
this.status = goog.net.HttpStatus.INTERNAL_SERVER_ERROR;
573
this.responseText = 'response timed out';
574
575
this.dispatchEvent(goog.net.EventType.READY);
576
this.dispatchEvent(goog.net.EventType.ERROR);
577
this.dispatchEvent(goog.net.EventType.COMPLETE);
578
}
579
}
580
}
581
};
582
583
584
/**
585
* Checks whether a frame is response info frame.
586
* @param {Object} frame Frame to check.
587
* @return {boolean} True if frame is a response info frame; false otherwise.
588
* @private
589
*/
590
goog.net.CrossDomainRpc.isResponseInfoFrame_ = function(frame) {
591
592
try {
593
return goog.net.CrossDomainRpc.getFramePayload_(frame).indexOf(
594
goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_) == 1;
595
} catch (e) {
596
// frame not ready for same-domain access yet
597
return false;
598
}
599
};
600
601
602
/**
603
* Returns the payload of a frame (value after # or ? on the URL). This value
604
* is URL encoded except IE, where the value is not encoded to begin with.
605
* @param {Object} frame Frame.
606
* @return {string} Payload of that frame.
607
* @private
608
*/
609
goog.net.CrossDomainRpc.getFramePayload_ = function(frame) {
610
var href = frame.location.href;
611
var question = href.indexOf('?');
612
var hash = href.indexOf('#');
613
// On IE, beucase the URL is not encoded, we can have a case where ?
614
// is the delimiter before payload and # in payload or # as the delimiter
615
// and ? in payload. So here we treat whoever is the first as the delimiter.
616
var delimiter =
617
question < 0 ? hash : hash < 0 ? question : Math.min(question, hash);
618
return href.substring(delimiter);
619
};
620
621
622
/**
623
* If response is JSON, evaluates it to a JavaScript object and
624
* returns it; otherwise returns undefined.
625
* @return {Object|undefined} JavaScript object if response is in JSON
626
* or undefined.
627
*/
628
goog.net.CrossDomainRpc.prototype.getResponseJson = function() {
629
return this.responseTextIsJson_ ?
630
/** @type {?Object} */ (JSON.parse(this.responseText)) :
631
undefined;
632
};
633
634
635
/**
636
* @return {boolean} Whether the request completed with a success.
637
*/
638
goog.net.CrossDomainRpc.prototype.isSuccess = function() {
639
// Definition similar to goog.net.XhrIo.prototype.isSuccess.
640
switch (this.status) {
641
case goog.net.HttpStatus.OK:
642
case goog.net.HttpStatus.NOT_MODIFIED:
643
return true;
644
645
default:
646
return false;
647
}
648
};
649
650
651
/**
652
* Removes request iframe used.
653
*/
654
goog.net.CrossDomainRpc.prototype.reset = function() {
655
if (!goog.net.CrossDomainRpc.debugMode_) {
656
goog.log.fine(
657
goog.net.CrossDomainRpc.logger_,
658
'request frame removed: ' + this.requestFrame_.id);
659
goog.events.unlistenByKey(this.loadListenerKey_);
660
this.requestFrame_.parentNode.removeChild(this.requestFrame_);
661
}
662
delete this.requestFrame_;
663
};
664
665
666
// -------------
667
// response side
668
669
670
/**
671
* Name of response info iframe.
672
* @type {string}
673
* @private
674
*/
675
goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ =
676
goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '-info';
677
678
679
/**
680
* Maximal chunk size. IE can only handle 4095 bytes on its URL.
681
* 16MB has been tested on Firefox. But 1MB is a practical size.
682
* @type {number}
683
* @private
684
*/
685
goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ =
686
goog.userAgent.EDGE_OR_IE ? 4095 : 1024 * 1024;
687
688
689
/**
690
* Query parameter 'chunk'.
691
* @type {string}
692
* @private
693
*/
694
goog.net.CrossDomainRpc.PARAM_CHUNK_ = 'chunk';
695
696
697
/**
698
* Prefix before data chunk for passing other parameters.
699
* type String
700
* @private
701
*/
702
goog.net.CrossDomainRpc.CHUNK_PREFIX_ =
703
goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '=1&' +
704
goog.net.CrossDomainRpc.PARAM_CHUNK_ + '=';
705
706
707
/**
708
* Makes response available for grandparent (requester)'s receiveResponse
709
* call to pick up by creating a series of iframes pointed to the dummy URI
710
* with a payload (value after either ? or #) carrying a chunk of response
711
* data and a response info iframe that tells the grandparent (requester) the
712
* readiness of response.
713
* @param {string} data Response data (string or JSON string).
714
* @param {boolean} isDataJson true if data is a JSON string; false if just a
715
* string.
716
* @param {Object} echo Parameters to echo back
717
* "xdpe:request-id": Server that produces the response needs to
718
* copy it here to support multiple current XD requests on the same page.
719
* "xdpe:dummy-uri": URI to a dummy resource that response
720
* iframes point to to gain the domain of the client. This can be an
721
* image (IE) or a CSS file (FF) found on the requester's page.
722
* Server should copy value from request parameter "xdpe:dummy-uri".
723
* @param {number} status HTTP response status code.
724
* @param {string} headers Response headers in JSON format.
725
*/
726
goog.net.CrossDomainRpc.sendResponse = function(
727
data, isDataJson, echo, status, headers) {
728
var dummyUri = echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI];
729
730
// since the dummy-uri can be specified by the user, verify that it doesn't
731
// use any other protocols. (Specifically we don't want users to use a
732
// dummy-uri beginning with "javascript:").
733
if (!goog.string.caseInsensitiveStartsWith(dummyUri, 'http://') &&
734
!goog.string.caseInsensitiveStartsWith(dummyUri, 'https://')) {
735
dummyUri = 'http://' + dummyUri;
736
}
737
738
// usable chunk size is max less dummy URI less chunk prefix length
739
// TODO(user): Figure out why we need to do "- 1" below
740
var chunkSize = goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ - dummyUri.length -
741
1 - // payload delimiter ('#' or '?')
742
goog.net.CrossDomainRpc.CHUNK_PREFIX_.length - 1;
743
744
/*
745
* Here we used to do URI encoding of data before we divide it into chunks
746
* and decode on the receiving end. We don't do this any more on IE for the
747
* following reasons.
748
*
749
* 1) On IE, calling decodeURIComponent on a relatively large string is
750
* extremely slow (~22s for 160KB). So even a moderate amount of data
751
* makes this library pretty much useless. Fortunately, we can actually
752
* put unencoded data on IE's URL and get it back reliably. So we are
753
* completely skipping encoding and decoding on IE. When we call
754
* getFrameHash_ to get it back, the value is still intact(*) and unencoded.
755
* 2) On Firefox, we have to call decodeURIComponent because location.hash
756
* does decoding by itself. Fortunately, decodeURIComponent is not slow
757
* on Firefox.
758
* 3) Safari automatically encodes everything you put on URL and it does not
759
* automatically decode when you access it via location.hash or
760
* location.href. So we encode it here and decode it in detectResponse_().
761
*
762
* Note(*): IE actually does encode only space to %20 and decodes that
763
* automatically when you do location.href or location.hash.
764
*/
765
if (!goog.userAgent.EDGE_OR_IE) {
766
data = encodeURIComponent(data);
767
}
768
769
var numChunksToSend = Math.ceil(data.length / chunkSize);
770
if (numChunksToSend == 0) {
771
goog.net.CrossDomainRpc.createResponseInfo_(
772
dummyUri, numChunksToSend, isDataJson, status, headers);
773
} else {
774
var numChunksSent = 0;
775
var checkToCreateResponseInfo_ = function() {
776
if (++numChunksSent == numChunksToSend) {
777
goog.net.CrossDomainRpc.createResponseInfo_(
778
dummyUri, numChunksToSend, isDataJson, status, headers);
779
}
780
};
781
782
for (var i = 0; i < numChunksToSend; i++) {
783
var chunkStart = i * chunkSize;
784
var chunkEnd = chunkStart + chunkSize;
785
var chunk = chunkEnd > data.length ? data.substring(chunkStart) :
786
data.substring(chunkStart, chunkEnd);
787
788
var responseFrame = goog.dom.createElement(goog.dom.TagName.IFRAME);
789
responseFrame.src = dummyUri +
790
goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) +
791
goog.net.CrossDomainRpc.CHUNK_PREFIX_ + chunk;
792
document.body.appendChild(responseFrame);
793
794
// We used to call the function below when handling load event of
795
// responseFrame. But that event does not fire on IE when current
796
// page is used as the dummy resource (because its loading is stopped?).
797
// It also does not fire sometimes on Firefox. So now we call it
798
// directly.
799
checkToCreateResponseInfo_();
800
}
801
}
802
};
803
804
805
/**
806
* Creates a response info iframe to indicate completion of sendResponse
807
* @param {string} dummyUri URI to a dummy resource.
808
* @param {number} numChunks Total number of chunks.
809
* @param {boolean} isDataJson Whether response is a JSON string or just string.
810
* @param {number} status HTTP response status code.
811
* @param {string} headers Response headers in JSON format.
812
* @private
813
*/
814
goog.net.CrossDomainRpc.createResponseInfo_ = function(
815
dummyUri, numChunks, isDataJson, status, headers) {
816
var responseInfoFrame = goog.dom.createElement(goog.dom.TagName.IFRAME);
817
document.body.appendChild(responseInfoFrame);
818
responseInfoFrame.src = dummyUri +
819
goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) +
820
goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ + '=1&n=' + numChunks +
821
'&isDataJson=' + isDataJson + '&status=' + status + '&headers=' +
822
encodeURIComponent(headers);
823
};
824
825
826
/**
827
* Returns payload delimiter, either "#" when caller's page is not used as
828
* the dummy resource or "?" when it is, in which case caching issues prevent
829
* response frames to gain the caller's domain.
830
* @param {string} dummyUri URI to resource being used as dummy resource.
831
* @return {string} Either "?" when caller's page is used as dummy resource or
832
* "#" if it is not.
833
* @private
834
*/
835
goog.net.CrossDomainRpc.getPayloadDelimiter_ = function(dummyUri) {
836
return goog.net.CrossDomainRpc.REFERRER_ == dummyUri ? '?' : '#';
837
};
838
839
840
/**
841
* Removes all parameters (after ? or #) from URI.
842
* @param {string} uri URI to remove parameters from.
843
* @return {string} URI with all parameters removed.
844
* @private
845
*/
846
goog.net.CrossDomainRpc.removeUriParams_ = function(uri) {
847
// remove everything after question mark
848
var question = uri.indexOf('?');
849
if (question > 0) {
850
uri = uri.substring(0, question);
851
}
852
853
// remove everything after hash mark
854
var hash = uri.indexOf('#');
855
if (hash > 0) {
856
uri = uri.substring(0, hash);
857
}
858
859
return uri;
860
};
861
862
863
/**
864
* Gets a response header.
865
* @param {string} name Name of response header.
866
* @return {string|undefined} Value of response header; undefined if not found.
867
*/
868
goog.net.CrossDomainRpc.prototype.getResponseHeader = function(name) {
869
return goog.isObject(this.responseHeaders) ? this.responseHeaders[name] :
870
undefined;
871
};
872
873
874
/**
875
* Referrer of current document with all parameters after "?" and "#" stripped.
876
* @type {string}
877
* @private
878
*/
879
goog.net.CrossDomainRpc.REFERRER_ =
880
goog.net.CrossDomainRpc.removeUriParams_(document.referrer);
881
882