Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const {Cc, Ci, Cu} = require("chrome");
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
11 loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
12 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
13 loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
14 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
15 loader.lazyImporter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
16 loader.lazyServiceGetter(this, "gActivityDistributor",
17 "@mozilla.org/network/http-activity-distributor;1",
18 "nsIHttpActivityDistributor");
20 ///////////////////////////////////////////////////////////////////////////////
21 // Network logging
22 ///////////////////////////////////////////////////////////////////////////////
24 // The maximum uint32 value.
25 const PR_UINT32_MAX = 4294967295;
27 // HTTP status codes.
28 const HTTP_MOVED_PERMANENTLY = 301;
29 const HTTP_FOUND = 302;
30 const HTTP_SEE_OTHER = 303;
31 const HTTP_TEMPORARY_REDIRECT = 307;
33 // The maximum number of bytes a NetworkResponseListener can hold.
34 const RESPONSE_BODY_LIMIT = 1048576; // 1 MB
36 /**
37 * The network response listener implements the nsIStreamListener and
38 * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature
39 * to get the response body of the request.
40 *
41 * The code is mostly based on code listings from:
42 *
43 * http://www.softwareishard.com/blog/firebug/
44 * nsitraceablechannel-intercept-http-traffic/
45 *
46 * @constructor
47 * @param object aOwner
48 * The response listener owner. This object needs to hold the
49 * |openResponses| object.
50 * @param object aHttpActivity
51 * HttpActivity object associated with this request. See NetworkMonitor
52 * for more information.
53 */
54 function NetworkResponseListener(aOwner, aHttpActivity)
55 {
56 this.owner = aOwner;
57 this.receivedData = "";
58 this.httpActivity = aHttpActivity;
59 this.bodySize = 0;
60 }
61 exports.NetworkResponseListener = NetworkResponseListener;
63 NetworkResponseListener.prototype = {
64 QueryInterface:
65 XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
66 Ci.nsIRequestObserver, Ci.nsISupports]),
68 /**
69 * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
70 * to find the associated uncached headers.
71 * @private
72 */
73 _foundOpenResponse: false,
75 /**
76 * The response listener owner.
77 */
78 owner: null,
80 /**
81 * The response will be written into the outputStream of this nsIPipe.
82 * Both ends of the pipe must be blocking.
83 */
84 sink: null,
86 /**
87 * The HttpActivity object associated with this response.
88 */
89 httpActivity: null,
91 /**
92 * Stores the received data as a string.
93 */
94 receivedData: null,
96 /**
97 * The network response body size.
98 */
99 bodySize: null,
101 /**
102 * The nsIRequest we are started for.
103 */
104 request: null,
106 /**
107 * Set the async listener for the given nsIAsyncInputStream. This allows us to
108 * wait asynchronously for any data coming from the stream.
109 *
110 * @param nsIAsyncInputStream aStream
111 * The input stream from where we are waiting for data to come in.
112 * @param nsIInputStreamCallback aListener
113 * The input stream callback you want. This is an object that must have
114 * the onInputStreamReady() method. If the argument is null, then the
115 * current callback is removed.
116 * @return void
117 */
118 setAsyncListener: function NRL_setAsyncListener(aStream, aListener)
119 {
120 // Asynchronously wait for the stream to be readable or closed.
121 aStream.asyncWait(aListener, 0, 0, Services.tm.mainThread);
122 },
124 /**
125 * Stores the received data, if request/response body logging is enabled. It
126 * also does limit the number of stored bytes, based on the
127 * RESPONSE_BODY_LIMIT constant.
128 *
129 * Learn more about nsIStreamListener at:
130 * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
131 *
132 * @param nsIRequest aRequest
133 * @param nsISupports aContext
134 * @param nsIInputStream aInputStream
135 * @param unsigned long aOffset
136 * @param unsigned long aCount
137 */
138 onDataAvailable:
139 function NRL_onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount)
140 {
141 this._findOpenResponse();
142 let data = NetUtil.readInputStreamToString(aInputStream, aCount);
144 this.bodySize += aCount;
146 if (!this.httpActivity.discardResponseBody &&
147 this.receivedData.length < RESPONSE_BODY_LIMIT) {
148 this.receivedData += NetworkHelper.
149 convertToUnicode(data, aRequest.contentCharset);
150 }
151 },
153 /**
154 * See documentation at
155 * https://developer.mozilla.org/En/NsIRequestObserver
156 *
157 * @param nsIRequest aRequest
158 * @param nsISupports aContext
159 */
160 onStartRequest: function NRL_onStartRequest(aRequest)
161 {
162 this.request = aRequest;
163 this._findOpenResponse();
164 // Asynchronously wait for the data coming from the request.
165 this.setAsyncListener(this.sink.inputStream, this);
166 },
168 /**
169 * Handle the onStopRequest by closing the sink output stream.
170 *
171 * For more documentation about nsIRequestObserver go to:
172 * https://developer.mozilla.org/En/NsIRequestObserver
173 */
174 onStopRequest: function NRL_onStopRequest()
175 {
176 this._findOpenResponse();
177 this.sink.outputStream.close();
178 },
180 /**
181 * Find the open response object associated to the current request. The
182 * NetworkMonitor._httpResponseExaminer() method saves the response headers in
183 * NetworkMonitor.openResponses. This method takes the data from the open
184 * response object and puts it into the HTTP activity object, then sends it to
185 * the remote Web Console instance.
186 *
187 * @private
188 */
189 _findOpenResponse: function NRL__findOpenResponse()
190 {
191 if (!this.owner || this._foundOpenResponse) {
192 return;
193 }
195 let openResponse = null;
197 for each (let item in this.owner.openResponses) {
198 if (item.channel === this.httpActivity.channel) {
199 openResponse = item;
200 break;
201 }
202 }
204 if (!openResponse) {
205 return;
206 }
207 this._foundOpenResponse = true;
209 delete this.owner.openResponses[openResponse.id];
211 this.httpActivity.owner.addResponseHeaders(openResponse.headers);
212 this.httpActivity.owner.addResponseCookies(openResponse.cookies);
213 },
215 /**
216 * Clean up the response listener once the response input stream is closed.
217 * This is called from onStopRequest() or from onInputStreamReady() when the
218 * stream is closed.
219 * @return void
220 */
221 onStreamClose: function NRL_onStreamClose()
222 {
223 if (!this.httpActivity) {
224 return;
225 }
226 // Remove our listener from the request input stream.
227 this.setAsyncListener(this.sink.inputStream, null);
229 this._findOpenResponse();
231 if (!this.httpActivity.discardResponseBody && this.receivedData.length) {
232 this._onComplete(this.receivedData);
233 }
234 else if (!this.httpActivity.discardResponseBody &&
235 this.httpActivity.responseStatus == 304) {
236 // Response is cached, so we load it from cache.
237 let charset = this.request.contentCharset || this.httpActivity.charset;
238 NetworkHelper.loadFromCache(this.httpActivity.url, charset,
239 this._onComplete.bind(this));
240 }
241 else {
242 this._onComplete();
243 }
244 },
246 /**
247 * Handler for when the response completes. This function cleans up the
248 * response listener.
249 *
250 * @param string [aData]
251 * Optional, the received data coming from the response listener or
252 * from the cache.
253 */
254 _onComplete: function NRL__onComplete(aData)
255 {
256 let response = {
257 mimeType: "",
258 text: aData || "",
259 };
261 response.size = response.text.length;
263 try {
264 response.mimeType = this.request.contentType;
265 }
266 catch (ex) { }
268 if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
269 response.encoding = "base64";
270 response.text = btoa(response.text);
271 }
273 if (response.mimeType && this.request.contentCharset) {
274 response.mimeType += "; charset=" + this.request.contentCharset;
275 }
277 this.receivedData = "";
279 this.httpActivity.owner.
280 addResponseContent(response, this.httpActivity.discardResponseBody);
282 this.httpActivity.channel = null;
283 this.httpActivity.owner = null;
284 this.httpActivity = null;
285 this.sink = null;
286 this.inputStream = null;
287 this.request = null;
288 this.owner = null;
289 },
291 /**
292 * The nsIInputStreamCallback for when the request input stream is ready -
293 * either it has more data or it is closed.
294 *
295 * @param nsIAsyncInputStream aStream
296 * The sink input stream from which data is coming.
297 * @returns void
298 */
299 onInputStreamReady: function NRL_onInputStreamReady(aStream)
300 {
301 if (!(aStream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
302 return;
303 }
305 let available = -1;
306 try {
307 // This may throw if the stream is closed normally or due to an error.
308 available = aStream.available();
309 }
310 catch (ex) { }
312 if (available != -1) {
313 if (available != 0) {
314 // Note that passing 0 as the offset here is wrong, but the
315 // onDataAvailable() method does not use the offset, so it does not
316 // matter.
317 this.onDataAvailable(this.request, null, aStream, 0, available);
318 }
319 this.setAsyncListener(aStream, this);
320 }
321 else {
322 this.onStreamClose();
323 }
324 },
325 }; // NetworkResponseListener.prototype
328 /**
329 * The network monitor uses the nsIHttpActivityDistributor to monitor network
330 * requests. The nsIObserverService is also used for monitoring
331 * http-on-examine-response notifications. All network request information is
332 * routed to the remote Web Console.
333 *
334 * @constructor
335 * @param object aFilters
336 * Object with the filters to use for network requests:
337 * - window (nsIDOMWindow): filter network requests by the associated
338 * window object.
339 * - appId (number): filter requests by the appId.
340 * - topFrame (nsIDOMElement): filter requests by their topFrameElement.
341 * Filters are optional. If any of these filters match the request is
342 * logged (OR is applied). If no filter is provided then all requests are
343 * logged.
344 * @param object aOwner
345 * The network monitor owner. This object needs to hold:
346 * - onNetworkEvent(aRequestInfo, aChannel, aNetworkMonitor).
347 * This method is invoked once for every new network request and it is
348 * given the following arguments: the initial network request
349 * information, and the channel. The third argument is the NetworkMonitor
350 * instance.
351 * onNetworkEvent() must return an object which holds several add*()
352 * methods which are used to add further network request/response
353 * information.
354 */
355 function NetworkMonitor(aFilters, aOwner)
356 {
357 if (aFilters) {
358 this.window = aFilters.window;
359 this.appId = aFilters.appId;
360 this.topFrame = aFilters.topFrame;
361 }
362 if (!this.window && !this.appId && !this.topFrame) {
363 this._logEverything = true;
364 }
365 this.owner = aOwner;
366 this.openRequests = {};
367 this.openResponses = {};
368 this._httpResponseExaminer =
369 DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this);
370 }
371 exports.NetworkMonitor = NetworkMonitor;
373 NetworkMonitor.prototype = {
374 _logEverything: false,
375 window: null,
376 appId: null,
377 topFrame: null,
379 httpTransactionCodes: {
380 0x5001: "REQUEST_HEADER",
381 0x5002: "REQUEST_BODY_SENT",
382 0x5003: "RESPONSE_START",
383 0x5004: "RESPONSE_HEADER",
384 0x5005: "RESPONSE_COMPLETE",
385 0x5006: "TRANSACTION_CLOSE",
387 0x804b0003: "STATUS_RESOLVING",
388 0x804b000b: "STATUS_RESOLVED",
389 0x804b0007: "STATUS_CONNECTING_TO",
390 0x804b0004: "STATUS_CONNECTED_TO",
391 0x804b0005: "STATUS_SENDING_TO",
392 0x804b000a: "STATUS_WAITING_FOR",
393 0x804b0006: "STATUS_RECEIVING_FROM"
394 },
396 // Network response bodies are piped through a buffer of the given size (in
397 // bytes).
398 responsePipeSegmentSize: null,
400 owner: null,
402 /**
403 * Whether to save the bodies of network requests and responses. Disabled by
404 * default to save memory.
405 * @type boolean
406 */
407 saveRequestAndResponseBodies: false,
409 /**
410 * Object that holds the HTTP activity objects for ongoing requests.
411 */
412 openRequests: null,
414 /**
415 * Object that holds response headers coming from this._httpResponseExaminer.
416 */
417 openResponses: null,
419 /**
420 * The network monitor initializer.
421 */
422 init: function NM_init()
423 {
424 this.responsePipeSegmentSize = Services.prefs
425 .getIntPref("network.buffer.cache.size");
427 gActivityDistributor.addObserver(this);
429 if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
430 Services.obs.addObserver(this._httpResponseExaminer,
431 "http-on-examine-response", false);
432 }
433 },
435 /**
436 * Observe notifications for the http-on-examine-response topic, coming from
437 * the nsIObserverService.
438 *
439 * @private
440 * @param nsIHttpChannel aSubject
441 * @param string aTopic
442 * @returns void
443 */
444 _httpResponseExaminer: function NM__httpResponseExaminer(aSubject, aTopic)
445 {
446 // The httpResponseExaminer is used to retrieve the uncached response
447 // headers. The data retrieved is stored in openResponses. The
448 // NetworkResponseListener is responsible with updating the httpActivity
449 // object with the data from the new object in openResponses.
451 if (!this.owner || aTopic != "http-on-examine-response" ||
452 !(aSubject instanceof Ci.nsIHttpChannel)) {
453 return;
454 }
456 let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
458 if (!this._matchRequest(channel)) {
459 return;
460 }
462 let response = {
463 id: gSequenceId(),
464 channel: channel,
465 headers: [],
466 cookies: [],
467 };
469 let setCookieHeader = null;
471 channel.visitResponseHeaders({
472 visitHeader: function NM__visitHeader(aName, aValue) {
473 let lowerName = aName.toLowerCase();
474 if (lowerName == "set-cookie") {
475 setCookieHeader = aValue;
476 }
477 response.headers.push({ name: aName, value: aValue });
478 }
479 });
481 if (!response.headers.length) {
482 return; // No need to continue.
483 }
485 if (setCookieHeader) {
486 response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader);
487 }
489 // Determine the HTTP version.
490 let httpVersionMaj = {};
491 let httpVersionMin = {};
493 channel.QueryInterface(Ci.nsIHttpChannelInternal);
494 channel.getResponseVersion(httpVersionMaj, httpVersionMin);
496 response.status = channel.responseStatus;
497 response.statusText = channel.responseStatusText;
498 response.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
499 httpVersionMin.value;
501 this.openResponses[response.id] = response;
502 },
504 /**
505 * Begin observing HTTP traffic that originates inside the current tab.
506 *
507 * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
508 *
509 * @param nsIHttpChannel aChannel
510 * @param number aActivityType
511 * @param number aActivitySubtype
512 * @param number aTimestamp
513 * @param number aExtraSizeData
514 * @param string aExtraStringData
515 */
516 observeActivity: DevToolsUtils.makeInfallible(function NM_observeActivity(aChannel, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData, aExtraStringData)
517 {
518 if (!this.owner ||
519 aActivityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
520 aActivityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
521 return;
522 }
524 if (!(aChannel instanceof Ci.nsIHttpChannel)) {
525 return;
526 }
528 aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
530 if (aActivitySubtype ==
531 gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
532 this._onRequestHeader(aChannel, aTimestamp, aExtraStringData);
533 return;
534 }
536 // Iterate over all currently ongoing requests. If aChannel can't
537 // be found within them, then exit this function.
538 let httpActivity = null;
539 for each (let item in this.openRequests) {
540 if (item.channel === aChannel) {
541 httpActivity = item;
542 break;
543 }
544 }
546 if (!httpActivity) {
547 return;
548 }
550 let transCodes = this.httpTransactionCodes;
552 // Store the time information for this activity subtype.
553 if (aActivitySubtype in transCodes) {
554 let stage = transCodes[aActivitySubtype];
555 if (stage in httpActivity.timings) {
556 httpActivity.timings[stage].last = aTimestamp;
557 }
558 else {
559 httpActivity.timings[stage] = {
560 first: aTimestamp,
561 last: aTimestamp,
562 };
563 }
564 }
566 switch (aActivitySubtype) {
567 case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
568 this._onRequestBodySent(httpActivity);
569 break;
570 case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
571 this._onResponseHeader(httpActivity, aExtraStringData);
572 break;
573 case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
574 this._onTransactionClose(httpActivity);
575 break;
576 default:
577 break;
578 }
579 }),
581 /**
582 * Check if a given network request should be logged by this network monitor
583 * instance based on the current filters.
584 *
585 * @private
586 * @param nsIHttpChannel aChannel
587 * Request to check.
588 * @return boolean
589 * True if the network request should be logged, false otherwise.
590 */
591 _matchRequest: function NM__matchRequest(aChannel)
592 {
593 if (this._logEverything) {
594 return true;
595 }
597 if (this.window) {
598 let win = NetworkHelper.getWindowForRequest(aChannel);
599 if (win && win.top === this.window) {
600 return true;
601 }
602 }
604 if (this.topFrame) {
605 let topFrame = NetworkHelper.getTopFrameForRequest(aChannel);
606 if (topFrame && topFrame === this.topFrame) {
607 return true;
608 }
609 }
611 if (this.appId) {
612 let appId = NetworkHelper.getAppIdForRequest(aChannel);
613 if (appId && appId == this.appId) {
614 return true;
615 }
616 }
618 return false;
619 },
621 /**
622 * Handler for ACTIVITY_SUBTYPE_REQUEST_HEADER. When a request starts the
623 * headers are sent to the server. This method creates the |httpActivity|
624 * object where we store the request and response information that is
625 * collected through its lifetime.
626 *
627 * @private
628 * @param nsIHttpChannel aChannel
629 * @param number aTimestamp
630 * @param string aExtraStringData
631 * @return void
632 */
633 _onRequestHeader:
634 function NM__onRequestHeader(aChannel, aTimestamp, aExtraStringData)
635 {
636 if (!this._matchRequest(aChannel)) {
637 return;
638 }
640 let win = NetworkHelper.getWindowForRequest(aChannel);
641 let httpActivity = this.createActivityObject(aChannel);
643 // see NM__onRequestBodySent()
644 httpActivity.charset = win ? win.document.characterSet : null;
645 httpActivity.private = win ? PrivateBrowsingUtils.isWindowPrivate(win) : false;
647 httpActivity.timings.REQUEST_HEADER = {
648 first: aTimestamp,
649 last: aTimestamp
650 };
652 let httpVersionMaj = {};
653 let httpVersionMin = {};
654 let event = {};
655 event.startedDateTime = new Date(Math.round(aTimestamp / 1000)).toISOString();
656 event.headersSize = aExtraStringData.length;
657 event.method = aChannel.requestMethod;
658 event.url = aChannel.URI.spec;
659 event.private = httpActivity.private;
661 // Determine if this is an XHR request.
662 try {
663 let callbacks = aChannel.notificationCallbacks;
664 let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
665 httpActivity.isXHR = event.isXHR = !!xhrRequest;
666 } catch (e) {
667 httpActivity.isXHR = event.isXHR = false;
668 }
670 // Determine the HTTP version.
671 aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
672 aChannel.getRequestVersion(httpVersionMaj, httpVersionMin);
674 event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
675 httpVersionMin.value;
677 event.discardRequestBody = !this.saveRequestAndResponseBodies;
678 event.discardResponseBody = !this.saveRequestAndResponseBodies;
680 let headers = [];
681 let cookies = [];
682 let cookieHeader = null;
684 // Copy the request header data.
685 aChannel.visitRequestHeaders({
686 visitHeader: function NM__visitHeader(aName, aValue)
687 {
688 if (aName == "Cookie") {
689 cookieHeader = aValue;
690 }
691 headers.push({ name: aName, value: aValue });
692 }
693 });
695 if (cookieHeader) {
696 cookies = NetworkHelper.parseCookieHeader(cookieHeader);
697 }
699 httpActivity.owner = this.owner.onNetworkEvent(event, aChannel, this);
701 this._setupResponseListener(httpActivity);
703 this.openRequests[httpActivity.id] = httpActivity;
705 httpActivity.owner.addRequestHeaders(headers);
706 httpActivity.owner.addRequestCookies(cookies);
707 },
709 /**
710 * Create the empty HTTP activity object. This object is used for storing all
711 * the request and response information.
712 *
713 * This is a HAR-like object. Conformance to the spec is not guaranteed at
714 * this point.
715 *
716 * TODO: Bug 708717 - Add support for network log export to HAR
717 *
718 * @see http://www.softwareishard.com/blog/har-12-spec
719 * @param nsIHttpChannel aChannel
720 * The HTTP channel for which the HTTP activity object is created.
721 * @return object
722 * The new HTTP activity object.
723 */
724 createActivityObject: function NM_createActivityObject(aChannel)
725 {
726 return {
727 id: gSequenceId(),
728 channel: aChannel,
729 charset: null, // see NM__onRequestHeader()
730 url: aChannel.URI.spec,
731 discardRequestBody: !this.saveRequestAndResponseBodies,
732 discardResponseBody: !this.saveRequestAndResponseBodies,
733 timings: {}, // internal timing information, see NM_observeActivity()
734 responseStatus: null, // see NM__onResponseHeader()
735 owner: null, // the activity owner which is notified when changes happen
736 };
737 },
739 /**
740 * Setup the network response listener for the given HTTP activity. The
741 * NetworkResponseListener is responsible for storing the response body.
742 *
743 * @private
744 * @param object aHttpActivity
745 * The HTTP activity object we are tracking.
746 */
747 _setupResponseListener: function NM__setupResponseListener(aHttpActivity)
748 {
749 let channel = aHttpActivity.channel;
750 channel.QueryInterface(Ci.nsITraceableChannel);
752 // The response will be written into the outputStream of this pipe.
753 // This allows us to buffer the data we are receiving and read it
754 // asynchronously.
755 // Both ends of the pipe must be blocking.
756 let sink = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
758 // The streams need to be blocking because this is required by the
759 // stream tee.
760 sink.init(false, false, this.responsePipeSegmentSize, PR_UINT32_MAX, null);
762 // Add listener for the response body.
763 let newListener = new NetworkResponseListener(this, aHttpActivity);
765 // Remember the input stream, so it isn't released by GC.
766 newListener.inputStream = sink.inputStream;
767 newListener.sink = sink;
769 let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
770 createInstance(Ci.nsIStreamListenerTee);
772 let originalListener = channel.setNewListener(tee);
774 tee.init(originalListener, sink.outputStream, newListener);
775 },
777 /**
778 * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. The request body is logged
779 * here.
780 *
781 * @private
782 * @param object aHttpActivity
783 * The HTTP activity object we are working with.
784 */
785 _onRequestBodySent: function NM__onRequestBodySent(aHttpActivity)
786 {
787 if (aHttpActivity.discardRequestBody) {
788 return;
789 }
791 let sentBody = NetworkHelper.
792 readPostTextFromRequest(aHttpActivity.channel,
793 aHttpActivity.charset);
795 if (!sentBody && this.window &&
796 aHttpActivity.url == this.window.location.href) {
797 // If the request URL is the same as the current page URL, then
798 // we can try to get the posted text from the page directly.
799 // This check is necessary as otherwise the
800 // NetworkHelper.readPostTextFromPageViaWebNav()
801 // function is called for image requests as well but these
802 // are not web pages and as such don't store the posted text
803 // in the cache of the webpage.
804 let webNav = this.window.QueryInterface(Ci.nsIInterfaceRequestor).
805 getInterface(Ci.nsIWebNavigation);
806 sentBody = NetworkHelper.
807 readPostTextFromPageViaWebNav(webNav, aHttpActivity.charset);
808 }
810 if (sentBody) {
811 aHttpActivity.owner.addRequestPostData({ text: sentBody });
812 }
813 },
815 /**
816 * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores
817 * information about the response headers.
818 *
819 * @private
820 * @param object aHttpActivity
821 * The HTTP activity object we are working with.
822 * @param string aExtraStringData
823 * The uncached response headers.
824 */
825 _onResponseHeader:
826 function NM__onResponseHeader(aHttpActivity, aExtraStringData)
827 {
828 // aExtraStringData contains the uncached response headers. The first line
829 // contains the response status (e.g. HTTP/1.1 200 OK).
830 //
831 // Note: The response header is not saved here. Calling the
832 // channel.visitResponseHeaders() methood at this point sometimes causes an
833 // NS_ERROR_NOT_AVAILABLE exception.
834 //
835 // We could parse aExtraStringData to get the headers and their values, but
836 // that is not trivial to do in an accurate manner. Hence, we save the
837 // response headers in this._httpResponseExaminer().
839 let headers = aExtraStringData.split(/\r\n|\n|\r/);
840 let statusLine = headers.shift();
841 let statusLineArray = statusLine.split(" ");
843 let response = {};
844 response.httpVersion = statusLineArray.shift();
845 response.status = statusLineArray.shift();
846 response.statusText = statusLineArray.join(" ");
847 response.headersSize = aExtraStringData.length;
849 aHttpActivity.responseStatus = response.status;
851 // Discard the response body for known response statuses.
852 switch (parseInt(response.status)) {
853 case HTTP_MOVED_PERMANENTLY:
854 case HTTP_FOUND:
855 case HTTP_SEE_OTHER:
856 case HTTP_TEMPORARY_REDIRECT:
857 aHttpActivity.discardResponseBody = true;
858 break;
859 }
861 response.discardResponseBody = aHttpActivity.discardResponseBody;
863 aHttpActivity.owner.addResponseStart(response);
864 },
866 /**
867 * Handler for ACTIVITY_SUBTYPE_TRANSACTION_CLOSE. This method updates the HAR
868 * timing information on the HTTP activity object and clears the request
869 * from the list of known open requests.
870 *
871 * @private
872 * @param object aHttpActivity
873 * The HTTP activity object we work with.
874 */
875 _onTransactionClose: function NM__onTransactionClose(aHttpActivity)
876 {
877 let result = this._setupHarTimings(aHttpActivity);
878 aHttpActivity.owner.addEventTimings(result.total, result.timings);
879 delete this.openRequests[aHttpActivity.id];
880 },
882 /**
883 * Update the HTTP activity object to include timing information as in the HAR
884 * spec. The HTTP activity object holds the raw timing information in
885 * |timings| - these are timings stored for each activity notification. The
886 * HAR timing information is constructed based on these lower level data.
887 *
888 * @param object aHttpActivity
889 * The HTTP activity object we are working with.
890 * @return object
891 * This object holds two properties:
892 * - total - the total time for all of the request and response.
893 * - timings - the HAR timings object.
894 */
895 _setupHarTimings: function NM__setupHarTimings(aHttpActivity)
896 {
897 let timings = aHttpActivity.timings;
898 let harTimings = {};
900 // Not clear how we can determine "blocked" time.
901 harTimings.blocked = -1;
903 // DNS timing information is available only in when the DNS record is not
904 // cached.
905 harTimings.dns = timings.STATUS_RESOLVING && timings.STATUS_RESOLVED ?
906 timings.STATUS_RESOLVED.last -
907 timings.STATUS_RESOLVING.first : -1;
909 if (timings.STATUS_CONNECTING_TO && timings.STATUS_CONNECTED_TO) {
910 harTimings.connect = timings.STATUS_CONNECTED_TO.last -
911 timings.STATUS_CONNECTING_TO.first;
912 }
913 else if (timings.STATUS_SENDING_TO) {
914 harTimings.connect = timings.STATUS_SENDING_TO.first -
915 timings.REQUEST_HEADER.first;
916 }
917 else {
918 harTimings.connect = -1;
919 }
921 if ((timings.STATUS_WAITING_FOR || timings.STATUS_RECEIVING_FROM) &&
922 (timings.STATUS_CONNECTED_TO || timings.STATUS_SENDING_TO)) {
923 harTimings.send = (timings.STATUS_WAITING_FOR ||
924 timings.STATUS_RECEIVING_FROM).first -
925 (timings.STATUS_CONNECTED_TO ||
926 timings.STATUS_SENDING_TO).last;
927 }
928 else {
929 harTimings.send = -1;
930 }
932 if (timings.RESPONSE_START) {
933 harTimings.wait = timings.RESPONSE_START.first -
934 (timings.REQUEST_BODY_SENT ||
935 timings.STATUS_SENDING_TO).last;
936 }
937 else {
938 harTimings.wait = -1;
939 }
941 if (timings.RESPONSE_START && timings.RESPONSE_COMPLETE) {
942 harTimings.receive = timings.RESPONSE_COMPLETE.last -
943 timings.RESPONSE_START.first;
944 }
945 else {
946 harTimings.receive = -1;
947 }
949 let totalTime = 0;
950 for (let timing in harTimings) {
951 let time = Math.max(Math.round(harTimings[timing] / 1000), -1);
952 harTimings[timing] = time;
953 if (time > -1) {
954 totalTime += time;
955 }
956 }
958 return {
959 total: totalTime,
960 timings: harTimings,
961 };
962 },
964 /**
965 * Suspend Web Console activity. This is called when all Web Consoles are
966 * closed.
967 */
968 destroy: function NM_destroy()
969 {
970 if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
971 Services.obs.removeObserver(this._httpResponseExaminer,
972 "http-on-examine-response");
973 }
975 gActivityDistributor.removeObserver(this);
977 this.openRequests = {};
978 this.openResponses = {};
979 this.owner = null;
980 this.window = null;
981 this.topFrame = null;
982 },
983 }; // NetworkMonitor.prototype
986 /**
987 * The NetworkMonitorChild is used to proxy all of the network activity of the
988 * child app process from the main process. The child WebConsoleActor creates an
989 * instance of this object.
990 *
991 * Network requests for apps happen in the main process. As such,
992 * a NetworkMonitor instance is used by the WebappsActor in the main process to
993 * log the network requests for this child process.
994 *
995 * The main process creates NetworkEventActorProxy instances per request. These
996 * send the data to this object using the nsIMessageManager. Here we proxy the
997 * data to the WebConsoleActor or to a NetworkEventActor.
998 *
999 * @constructor
1000 * @param number appId
1001 * The web appId of the child process.
1002 * @param nsIMessageManager messageManager
1003 * The nsIMessageManager to use to communicate with the parent process.
1004 * @param string connID
1005 * The connection ID to use for send messages to the parent process.
1006 * @param object owner
1007 * The WebConsoleActor that is listening for the network requests.
1008 */
1009 function NetworkMonitorChild(appId, messageManager, connID, owner) {
1010 this.appId = appId;
1011 this.connID = connID;
1012 this.owner = owner;
1013 this._messageManager = messageManager;
1014 this._onNewEvent = this._onNewEvent.bind(this);
1015 this._onUpdateEvent = this._onUpdateEvent.bind(this);
1016 this._netEvents = new Map();
1017 }
1018 exports.NetworkMonitorChild = NetworkMonitorChild;
1020 NetworkMonitorChild.prototype = {
1021 appId: null,
1022 owner: null,
1023 _netEvents: null,
1024 _saveRequestAndResponseBodies: false,
1026 get saveRequestAndResponseBodies() {
1027 return this._saveRequestAndResponseBodies;
1028 },
1030 set saveRequestAndResponseBodies(val) {
1031 this._saveRequestAndResponseBodies = val;
1033 this._messageManager.sendAsyncMessage("debug:netmonitor:" + this.connID, {
1034 appId: this.appId,
1035 action: "setPreferences",
1036 preferences: {
1037 saveRequestAndResponseBodies: this._saveRequestAndResponseBodies,
1038 },
1039 });
1040 },
1042 init: function() {
1043 let mm = this._messageManager;
1044 mm.addMessageListener("debug:netmonitor:" + this.connID + ":newEvent",
1045 this._onNewEvent);
1046 mm.addMessageListener("debug:netmonitor:" + this.connID + ":updateEvent",
1047 this._onUpdateEvent);
1048 mm.sendAsyncMessage("debug:netmonitor:" + this.connID, {
1049 appId: this.appId,
1050 action: "start",
1051 });
1052 },
1054 _onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) {
1055 let {id, event} = msg.data;
1056 let actor = this.owner.onNetworkEvent(event);
1057 this._netEvents.set(id, Cu.getWeakReference(actor));
1058 }),
1060 _onUpdateEvent: DevToolsUtils.makeInfallible(function _onUpdateEvent(msg) {
1061 let {id, method, args} = msg.data;
1062 let weakActor = this._netEvents.get(id);
1063 let actor = weakActor ? weakActor.get() : null;
1064 if (!actor) {
1065 Cu.reportError("Received debug:netmonitor:updateEvent for unknown event ID: " + id);
1066 return;
1067 }
1068 if (!(method in actor)) {
1069 Cu.reportError("Received debug:netmonitor:updateEvent unsupported method: " + method);
1070 return;
1071 }
1072 actor[method].apply(actor, args);
1073 }),
1075 destroy: function() {
1076 let mm = this._messageManager;
1077 mm.removeMessageListener("debug:netmonitor:" + this.connID + ":newEvent",
1078 this._onNewEvent);
1079 mm.removeMessageListener("debug:netmonitor:" + this.connID + ":updateEvent",
1080 this._onUpdateEvent);
1081 mm.sendAsyncMessage("debug:netmonitor:" + this.connID, {
1082 action: "disconnect",
1083 });
1084 this._netEvents.clear();
1085 this._messageManager = null;
1086 this.owner = null;
1087 },
1088 }; // NetworkMonitorChild.prototype
1090 /**
1091 * The NetworkEventActorProxy is used to send network request information from
1092 * the main process to the child app process. One proxy is used per request.
1093 * Similarly, one NetworkEventActor in the child app process is used per
1094 * request. The client receives all network logs from the child actors.
1095 *
1096 * The child process has a NetworkMonitorChild instance that is listening for
1097 * all network logging from the main process. The net monitor shim is used to
1098 * proxy the data to the WebConsoleActor instance of the child process.
1099 *
1100 * @constructor
1101 * @param nsIMessageManager messageManager
1102 * The message manager for the child app process. This is used for
1103 * communication with the NetworkMonitorChild instance of the process.
1104 * @param string connID
1105 * The connection ID to use to send messages to the child process.
1106 */
1107 function NetworkEventActorProxy(messageManager, connID) {
1108 this.id = gSequenceId();
1109 this.connID = connID;
1110 this.messageManager = messageManager;
1111 }
1112 exports.NetworkEventActorProxy = NetworkEventActorProxy;
1114 NetworkEventActorProxy.methodFactory = function(method) {
1115 return DevToolsUtils.makeInfallible(function() {
1116 let args = Array.slice(arguments);
1117 let mm = this.messageManager;
1118 mm.sendAsyncMessage("debug:netmonitor:" + this.connID + ":updateEvent", {
1119 id: this.id,
1120 method: method,
1121 args: args,
1122 });
1123 }, "NetworkEventActorProxy." + method);
1124 };
1126 NetworkEventActorProxy.prototype = {
1127 /**
1128 * Initialize the network event. This method sends the network request event
1129 * to the content process.
1130 *
1131 * @param object event
1132 * Object describing the network request.
1133 * @return object
1134 * This object.
1135 */
1136 init: DevToolsUtils.makeInfallible(function(event)
1137 {
1138 let mm = this.messageManager;
1139 mm.sendAsyncMessage("debug:netmonitor:" + this.connID + ":newEvent", {
1140 id: this.id,
1141 event: event,
1142 });
1143 return this;
1144 }),
1145 };
1147 (function() {
1148 // Listeners for new network event data coming from the NetworkMonitor.
1149 let methods = ["addRequestHeaders", "addRequestCookies", "addRequestPostData",
1150 "addResponseStart", "addResponseHeaders", "addResponseCookies",
1151 "addResponseContent", "addEventTimings"];
1152 let factory = NetworkEventActorProxy.methodFactory;
1153 for (let method of methods) {
1154 NetworkEventActorProxy.prototype[method] = factory(method);
1155 }
1156 })();
1159 /**
1160 * The NetworkMonitor manager used by the Webapps actor in the main process.
1161 * This object uses the message manager to listen for requests from the child
1162 * process to start/stop the network monitor.
1163 *
1164 * @constructor
1165 * @param nsIDOMElement frame
1166 * The browser frame to work with (mozbrowser).
1167 * @param string id
1168 * Instance identifier to use for messages.
1169 */
1170 function NetworkMonitorManager(frame, id)
1171 {
1172 this.id = id;
1173 let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
1174 this.messageManager = mm;
1175 this.frame = frame;
1176 this.onNetMonitorMessage = this.onNetMonitorMessage.bind(this);
1177 this.onNetworkEvent = this.onNetworkEvent.bind(this);
1179 mm.addMessageListener("debug:netmonitor:" + id, this.onNetMonitorMessage);
1180 }
1181 exports.NetworkMonitorManager = NetworkMonitorManager;
1183 NetworkMonitorManager.prototype = {
1184 netMonitor: null,
1185 frame: null,
1186 messageManager: null,
1188 /**
1189 * Handler for "debug:monitor" messages received through the message manager
1190 * from the content process.
1191 *
1192 * @param object msg
1193 * Message from the content.
1194 */
1195 onNetMonitorMessage: DevToolsUtils.makeInfallible(function _onNetMonitorMessage(msg) {
1196 let { action, appId } = msg.json;
1197 // Pipe network monitor data from parent to child via the message manager.
1198 switch (action) {
1199 case "start":
1200 if (!this.netMonitor) {
1201 this.netMonitor = new NetworkMonitor({
1202 topFrame: this.frame,
1203 appId: appId,
1204 }, this);
1205 this.netMonitor.init();
1206 }
1207 break;
1209 case "setPreferences": {
1210 let {preferences} = msg.json;
1211 for (let key of Object.keys(preferences)) {
1212 if (key == "saveRequestAndResponseBodies" && this.netMonitor) {
1213 this.netMonitor.saveRequestAndResponseBodies = preferences[key];
1214 }
1215 }
1216 break;
1217 }
1219 case "stop":
1220 if (this.netMonitor) {
1221 this.netMonitor.destroy();
1222 this.netMonitor = null;
1223 }
1224 break;
1226 case "disconnect":
1227 this.destroy();
1228 break;
1229 }
1230 }),
1232 /**
1233 * Handler for new network requests. This method is invoked by the current
1234 * NetworkMonitor instance.
1235 *
1236 * @param object event
1237 * Object describing the network request.
1238 * @return object
1239 * A NetworkEventActorProxy instance which is notified when further
1240 * data about the request is available.
1241 */
1242 onNetworkEvent: DevToolsUtils.makeInfallible(function _onNetworkEvent(event) {
1243 return new NetworkEventActorProxy(this.messageManager, this.id).init(event);
1244 }),
1246 destroy: function()
1247 {
1248 if (this.messageManager) {
1249 this.messageManager.removeMessageListener("debug:netmonitor:" + this.id,
1250 this.onNetMonitorMessage);
1251 }
1252 this.messageManager = null;
1253 this.filters = null;
1255 if (this.netMonitor) {
1256 this.netMonitor.destroy();
1257 this.netMonitor = null;
1258 }
1259 },
1260 }; // NetworkMonitorManager.prototype
1263 /**
1264 * A WebProgressListener that listens for location changes.
1265 *
1266 * This progress listener is used to track file loads and other kinds of
1267 * location changes.
1268 *
1269 * @constructor
1270 * @param object aWindow
1271 * The window for which we need to track location changes.
1272 * @param object aOwner
1273 * The listener owner which needs to implement two methods:
1274 * - onFileActivity(aFileURI)
1275 * - onLocationChange(aState, aTabURI, aPageTitle)
1276 */
1277 function ConsoleProgressListener(aWindow, aOwner)
1278 {
1279 this.window = aWindow;
1280 this.owner = aOwner;
1281 }
1282 exports.ConsoleProgressListener = ConsoleProgressListener;
1284 ConsoleProgressListener.prototype = {
1285 /**
1286 * Constant used for startMonitor()/stopMonitor() that tells you want to
1287 * monitor file loads.
1288 */
1289 MONITOR_FILE_ACTIVITY: 1,
1291 /**
1292 * Constant used for startMonitor()/stopMonitor() that tells you want to
1293 * monitor page location changes.
1294 */
1295 MONITOR_LOCATION_CHANGE: 2,
1297 /**
1298 * Tells if you want to monitor file activity.
1299 * @private
1300 * @type boolean
1301 */
1302 _fileActivity: false,
1304 /**
1305 * Tells if you want to monitor location changes.
1306 * @private
1307 * @type boolean
1308 */
1309 _locationChange: false,
1311 /**
1312 * Tells if the console progress listener is initialized or not.
1313 * @private
1314 * @type boolean
1315 */
1316 _initialized: false,
1318 _webProgress: null,
1320 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
1321 Ci.nsISupportsWeakReference]),
1323 /**
1324 * Initialize the ConsoleProgressListener.
1325 * @private
1326 */
1327 _init: function CPL__init()
1328 {
1329 if (this._initialized) {
1330 return;
1331 }
1333 this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
1334 .getInterface(Ci.nsIWebNavigation)
1335 .QueryInterface(Ci.nsIWebProgress);
1336 this._webProgress.addProgressListener(this,
1337 Ci.nsIWebProgress.NOTIFY_STATE_ALL);
1339 this._initialized = true;
1340 },
1342 /**
1343 * Start a monitor/tracker related to the current nsIWebProgressListener
1344 * instance.
1345 *
1346 * @param number aMonitor
1347 * Tells what you want to track. Available constants:
1348 * - this.MONITOR_FILE_ACTIVITY
1349 * Track file loads.
1350 * - this.MONITOR_LOCATION_CHANGE
1351 * Track location changes for the top window.
1352 */
1353 startMonitor: function CPL_startMonitor(aMonitor)
1354 {
1355 switch (aMonitor) {
1356 case this.MONITOR_FILE_ACTIVITY:
1357 this._fileActivity = true;
1358 break;
1359 case this.MONITOR_LOCATION_CHANGE:
1360 this._locationChange = true;
1361 break;
1362 default:
1363 throw new Error("ConsoleProgressListener: unknown monitor type " +
1364 aMonitor + "!");
1365 }
1366 this._init();
1367 },
1369 /**
1370 * Stop a monitor.
1371 *
1372 * @param number aMonitor
1373 * Tells what you want to stop tracking. See this.startMonitor() for
1374 * the list of constants.
1375 */
1376 stopMonitor: function CPL_stopMonitor(aMonitor)
1377 {
1378 switch (aMonitor) {
1379 case this.MONITOR_FILE_ACTIVITY:
1380 this._fileActivity = false;
1381 break;
1382 case this.MONITOR_LOCATION_CHANGE:
1383 this._locationChange = false;
1384 break;
1385 default:
1386 throw new Error("ConsoleProgressListener: unknown monitor type " +
1387 aMonitor + "!");
1388 }
1390 if (!this._fileActivity && !this._locationChange) {
1391 this.destroy();
1392 }
1393 },
1395 onStateChange:
1396 function CPL_onStateChange(aProgress, aRequest, aState, aStatus)
1397 {
1398 if (!this.owner) {
1399 return;
1400 }
1402 if (this._fileActivity) {
1403 this._checkFileActivity(aProgress, aRequest, aState, aStatus);
1404 }
1406 if (this._locationChange) {
1407 this._checkLocationChange(aProgress, aRequest, aState, aStatus);
1408 }
1409 },
1411 /**
1412 * Check if there is any file load, given the arguments of
1413 * nsIWebProgressListener.onStateChange. If the state change tells that a file
1414 * URI has been loaded, then the remote Web Console instance is notified.
1415 * @private
1416 */
1417 _checkFileActivity:
1418 function CPL__checkFileActivity(aProgress, aRequest, aState, aStatus)
1419 {
1420 if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
1421 return;
1422 }
1424 let uri = null;
1425 if (aRequest instanceof Ci.imgIRequest) {
1426 let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest);
1427 uri = imgIRequest.URI;
1428 }
1429 else if (aRequest instanceof Ci.nsIChannel) {
1430 let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel);
1431 uri = nsIChannel.URI;
1432 }
1434 if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
1435 return;
1436 }
1438 this.owner.onFileActivity(uri.spec);
1439 },
1441 /**
1442 * Check if the current window.top location is changing, given the arguments
1443 * of nsIWebProgressListener.onStateChange. If that is the case, the remote
1444 * Web Console instance is notified.
1445 * @private
1446 */
1447 _checkLocationChange:
1448 function CPL__checkLocationChange(aProgress, aRequest, aState, aStatus)
1449 {
1450 let isStart = aState & Ci.nsIWebProgressListener.STATE_START;
1451 let isStop = aState & Ci.nsIWebProgressListener.STATE_STOP;
1452 let isNetwork = aState & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
1453 let isWindow = aState & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
1455 // Skip non-interesting states.
1456 if (!isNetwork || !isWindow || aProgress.DOMWindow != this.window) {
1457 return;
1458 }
1460 if (isStart && aRequest instanceof Ci.nsIChannel) {
1461 this.owner.onLocationChange("start", aRequest.URI.spec, "");
1462 }
1463 else if (isStop) {
1464 this.owner.onLocationChange("stop", this.window.location.href,
1465 this.window.document.title);
1466 }
1467 },
1469 onLocationChange: function() {},
1470 onStatusChange: function() {},
1471 onProgressChange: function() {},
1472 onSecurityChange: function() {},
1474 /**
1475 * Destroy the ConsoleProgressListener.
1476 */
1477 destroy: function CPL_destroy()
1478 {
1479 if (!this._initialized) {
1480 return;
1481 }
1483 this._initialized = false;
1484 this._fileActivity = false;
1485 this._locationChange = false;
1487 try {
1488 this._webProgress.removeProgressListener(this);
1489 }
1490 catch (ex) {
1491 // This can throw during browser shutdown.
1492 }
1494 this._webProgress = null;
1495 this.window = null;
1496 this.owner = null;
1497 },
1498 }; // ConsoleProgressListener.prototype
1500 function gSequenceId() { return gSequenceId.n++; }
1501 gSequenceId.n = 1;