mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
Backed out 6 changesets (bug 1244227
) for m(cl) failures
Backed out changeset 0cf1259b7073 (bug1244227
) Backed out changeset d5866b9dd3d9 (bug1244227
) Backed out changeset 49b5309e3415 (bug1244227
) Backed out changeset 33bc49f015a7 (bug1244227
) Backed out changeset f680f6460f07 (bug1244227
) Backed out changeset 5a4bb3258978 (bug1244227
)
This commit is contained in:
parent
9ab8638548
commit
6517ee3462
@ -9,4 +9,3 @@ support-files =
|
||||
|
||||
[browser_net_har_copy_all_as_har.js]
|
||||
[browser_net_har_post_data.js]
|
||||
[browser_net_har_throttle_upload.js]
|
||||
|
@ -1,60 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test timing of upload when throttling.
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
let [ , debuggee, monitor ] = yield initNetMonitor(
|
||||
HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
|
||||
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
const size = 4096;
|
||||
const request = {
|
||||
"NetworkMonitor.throttleData": {
|
||||
roundTripTimeMean: 0,
|
||||
roundTripTimeMax: 0,
|
||||
downloadBPSMean: 200000,
|
||||
downloadBPSMax: 200000,
|
||||
uploadBPSMean: size / 3,
|
||||
uploadBPSMax: size / 3,
|
||||
},
|
||||
};
|
||||
let client = monitor._controller.webConsoleClient;
|
||||
|
||||
info("sending throttle request");
|
||||
let deferred = promise.defer();
|
||||
client.setPreferences(request, response => {
|
||||
deferred.resolve(response);
|
||||
});
|
||||
yield deferred.promise;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
// Execute one POST request on the page and wait till its done.
|
||||
debuggee.executeTest2(size);
|
||||
yield waitForNetworkEvents(monitor, 0, 1);
|
||||
|
||||
// Copy HAR into the clipboard (asynchronous).
|
||||
let jsonString = yield RequestsMenu.copyAllAsHar();
|
||||
let har = JSON.parse(jsonString);
|
||||
|
||||
// Check out the HAR log.
|
||||
isnot(har.log, null, "The HAR log must exist");
|
||||
is(har.log.pages.length, 1, "There must be one page");
|
||||
is(har.log.entries.length, 1, "There must be one request");
|
||||
|
||||
let entry = har.log.entries[0];
|
||||
is(entry.request.postData.text, "x".repeat(size),
|
||||
"Check post data payload");
|
||||
|
||||
ok(entry.timings.send >= 2000, "upload should have taken more than 2 seconds");
|
||||
|
||||
// Clean up
|
||||
teardown(monitor).then(finish);
|
||||
});
|
@ -27,12 +27,6 @@
|
||||
var data = "{'first': 'John', 'last': 'Doe'}";
|
||||
post(url, data);
|
||||
}
|
||||
|
||||
function executeTest2(size) {
|
||||
var url = "html_har_post-data-test-page.html";
|
||||
var data = "x".repeat(size);
|
||||
post(url, data);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
@ -138,7 +138,6 @@ skip-if = (e10s && debug && os == 'mac') # Bug 1253037
|
||||
[browser_net_statistics-03.js]
|
||||
[browser_net_status-codes.js]
|
||||
[browser_net_streaming-response.js]
|
||||
[browser_net_throttle.js]
|
||||
[browser_net_timeline_ticks.js]
|
||||
[browser_net_timing-division.js]
|
||||
[browser_net_persistent_logs.js]
|
||||
|
@ -1,53 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Network throttling integration test.
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let [, , monitor] = yield initNetMonitor(SIMPLE_URL);
|
||||
const {ACTIVITY_TYPE, NetMonitorController, NetMonitorView} =
|
||||
monitor.panelWin;
|
||||
|
||||
info("Starting test... ");
|
||||
|
||||
const request = {
|
||||
"NetworkMonitor.throttleData": {
|
||||
roundTripTimeMean: 0,
|
||||
roundTripTimeMax: 0,
|
||||
// Must be smaller than the length of the content of SIMPLE_URL
|
||||
// in bytes.
|
||||
downloadBPSMean: 200,
|
||||
downloadBPSMax: 200,
|
||||
uploadBPSMean: 10000,
|
||||
uploadBPSMax: 10000,
|
||||
},
|
||||
};
|
||||
let client = monitor._controller.webConsoleClient;
|
||||
|
||||
info("sending throttle request");
|
||||
let deferred = promise.defer();
|
||||
client.setPreferences(request, response => {
|
||||
deferred.resolve(response);
|
||||
});
|
||||
yield deferred.promise;
|
||||
|
||||
const startTime = Date.now();
|
||||
let eventPromise =
|
||||
monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_EVENT_TIMINGS);
|
||||
yield NetMonitorController
|
||||
.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED);
|
||||
const endTime = Date.now();
|
||||
ok(endTime - startTime > 1000, "download took more than one second");
|
||||
|
||||
yield eventPromise;
|
||||
let requestItem = NetMonitorView.RequestsMenu.getItemAtIndex(0);
|
||||
ok(requestItem.attachment.eventTimings.timings.receive > 1000,
|
||||
"download reported as taking more than one second");
|
||||
|
||||
yield teardown(monitor);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<title>Network Monitor Test Page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>HAR POST data test</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
function post(aAddress, aData) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", aAddress, true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(aData);
|
||||
}
|
||||
|
||||
function executeTest() {
|
||||
var url = "sjs_simple-test-server.sjs";
|
||||
var data = "{'first': 'John', 'last': 'Doe'}";
|
||||
post(url, data);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -387,7 +387,6 @@ WebConsoleFrame.prototype = {
|
||||
_destroyer: null,
|
||||
|
||||
_saveRequestAndResponseBodies: true,
|
||||
_throttleData: null,
|
||||
|
||||
// Chevron width at the starting of Web Console's input box.
|
||||
_chevronWidth: 0,
|
||||
@ -425,36 +424,6 @@ WebConsoleFrame.prototype = {
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Setter for throttling data.
|
||||
*
|
||||
* @param boolean value
|
||||
* The new value you want to set; @see NetworkThrottleManager.
|
||||
*/
|
||||
setThrottleData: function(value) {
|
||||
if (!this.webConsoleClient) {
|
||||
// Don't continue if the webconsole disconnected.
|
||||
return promise.resolve(null);
|
||||
}
|
||||
|
||||
let deferred = promise.defer();
|
||||
let toSet = {
|
||||
"NetworkMonitor.throttleData": value,
|
||||
};
|
||||
|
||||
// Make sure the web console client connection is established first.
|
||||
this.webConsoleClient.setPreferences(toSet, response => {
|
||||
if (!response.error) {
|
||||
this._throttleData = value;
|
||||
deferred.resolve(response);
|
||||
} else {
|
||||
deferred.reject(response.error);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the persistent logging preference.
|
||||
* @type boolean
|
||||
|
@ -1054,18 +1054,11 @@ WebConsoleActor.prototype =
|
||||
for (let key in aRequest.preferences) {
|
||||
this._prefs[key] = aRequest.preferences[key];
|
||||
|
||||
if (this.networkMonitor) {
|
||||
if (key == "NetworkMonitor.saveRequestAndResponseBodies") {
|
||||
this.networkMonitor.saveRequestAndResponseBodies = this._prefs[key];
|
||||
if (this.networkMonitorChild) {
|
||||
this.networkMonitorChild.saveRequestAndResponseBodies =
|
||||
this._prefs[key];
|
||||
}
|
||||
} else if (key == "NetworkMonitor.throttleData") {
|
||||
this.networkMonitor.throttleData = this._prefs[key];
|
||||
if (this.networkMonitorChild) {
|
||||
this.networkMonitorChild.throttleData = this._prefs[key];
|
||||
}
|
||||
if (key == "NetworkMonitor.saveRequestAndResponseBodies" &&
|
||||
this.networkMonitor) {
|
||||
this.networkMonitor.saveRequestAndResponseBodies = this._prefs[key];
|
||||
if (this.networkMonitorChild) {
|
||||
this.networkMonitorChild.saveRequestAndResponseBodies = this._prefs[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ DevToolsModules(
|
||||
'network-monitor.js',
|
||||
'server-logger-monitor.js',
|
||||
'server-logger.js',
|
||||
'throttle.js',
|
||||
'utils.js',
|
||||
'worker-utils.js',
|
||||
)
|
||||
|
@ -18,7 +18,6 @@ loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
|
||||
loader.lazyServiceGetter(this, "gActivityDistributor",
|
||||
"@mozilla.org/network/http-activity-distributor;1",
|
||||
"nsIHttpActivityDistributor");
|
||||
const {NetworkThrottleManager} = require("devtools/shared/webconsole/throttle");
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Network logging
|
||||
@ -303,12 +302,50 @@ function NetworkResponseListener(owner, httpActivity) {
|
||||
this.receivedData = "";
|
||||
this.httpActivity = httpActivity;
|
||||
this.bodySize = 0;
|
||||
let channel = this.httpActivity.channel;
|
||||
this._wrappedNotificationCallbacks = channel.notificationCallbacks;
|
||||
channel.notificationCallbacks = this;
|
||||
}
|
||||
|
||||
NetworkResponseListener.prototype = {
|
||||
QueryInterface:
|
||||
XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
|
||||
Ci.nsIRequestObserver, Ci.nsISupports]),
|
||||
Ci.nsIRequestObserver, Ci.nsIInterfaceRequestor,
|
||||
Ci.nsISupports]),
|
||||
|
||||
// nsIInterfaceRequestor implementation
|
||||
|
||||
/**
|
||||
* This object implements nsIProgressEventSink, but also needs to forward
|
||||
* interface requests to the notification callbacks of other objects.
|
||||
*/
|
||||
getInterface(iid) {
|
||||
if (iid.equals(Ci.nsIProgressEventSink)) {
|
||||
return this;
|
||||
}
|
||||
if (this._wrappedNotificationCallbacks) {
|
||||
return this._wrappedNotificationCallbacks.getInterface(iid);
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
/**
|
||||
* Forward notifications for interfaces this object implements, in case other
|
||||
* objects also implemented them.
|
||||
*/
|
||||
_forwardNotification(iid, method, args) {
|
||||
if (!this._wrappedNotificationCallbacks) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let impl = this._wrappedNotificationCallbacks.getInterface(iid);
|
||||
impl[method].apply(impl, args);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_NO_INTERFACE) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This NetworkResponseListener tracks the NetworkMonitor.openResponses object
|
||||
@ -317,6 +354,12 @@ NetworkResponseListener.prototype = {
|
||||
*/
|
||||
_foundOpenResponse: false,
|
||||
|
||||
/**
|
||||
* If the channel already had notificationCallbacks, hold them here internally
|
||||
* so that we can forward getInterface requests to that object.
|
||||
*/
|
||||
_wrappedNotificationCallbacks: null,
|
||||
|
||||
/**
|
||||
* The response listener owner.
|
||||
*/
|
||||
@ -413,6 +456,9 @@ NetworkResponseListener.prototype = {
|
||||
this.request = request;
|
||||
this._getSecurityInfo();
|
||||
this._findOpenResponse();
|
||||
// We need to track the offset for the onDataAvailable calls where
|
||||
// we pass the data from our pipe to the coverter.
|
||||
this.offset = 0;
|
||||
|
||||
// In the multi-process mode, the conversion happens on the child
|
||||
// side while we can only monitor the channel on the parent
|
||||
@ -473,6 +519,23 @@ NetworkResponseListener.prototype = {
|
||||
this.sink.outputStream.close();
|
||||
},
|
||||
|
||||
// nsIProgressEventSink implementation
|
||||
|
||||
/**
|
||||
* Handle progress event as data is transferred. This is used to record the
|
||||
* size on the wire, which may be compressed / encoded.
|
||||
*/
|
||||
onProgress: function (request, context, progress, progressMax) {
|
||||
this.transferredSize = progress;
|
||||
// Need to forward as well to keep things like Download Manager's progress
|
||||
// bar working properly.
|
||||
this._forwardNotification(Ci.nsIProgressEventSink, "onProgress", arguments);
|
||||
},
|
||||
|
||||
onStatus: function () {
|
||||
this._forwardNotification(Ci.nsIProgressEventSink, "onStatus", arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the open response object associated to the current request. The
|
||||
* NetworkMonitor._httpResponseExaminer() method saves the response headers in
|
||||
@ -580,6 +643,9 @@ NetworkResponseListener.prototype = {
|
||||
this.httpActivity.discardResponseBody
|
||||
);
|
||||
|
||||
this._wrappedNotificationCallbacks = null;
|
||||
this.httpActivity.channel = null;
|
||||
this.httpActivity.owner = null;
|
||||
this.httpActivity = null;
|
||||
this.sink = null;
|
||||
this.inputStream = null;
|
||||
@ -610,23 +676,20 @@ NetworkResponseListener.prototype = {
|
||||
}
|
||||
|
||||
if (available != -1) {
|
||||
if (this.transferredSize === null) {
|
||||
this.transferredSize = 0;
|
||||
}
|
||||
|
||||
if (available != 0) {
|
||||
if (this.converter) {
|
||||
this.converter.onDataAvailable(this.request, null, stream,
|
||||
this.transferredSize, available);
|
||||
this.offset, available);
|
||||
} else {
|
||||
this.onDataAvailable(this.request, null, stream, this.transferredSize,
|
||||
this.onDataAvailable(this.request, null, stream, this.offset,
|
||||
available);
|
||||
}
|
||||
}
|
||||
this.transferredSize += available;
|
||||
this.offset += available;
|
||||
this.setAsyncListener(stream, this);
|
||||
} else {
|
||||
this.onStreamClose();
|
||||
this.offset = 0;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -666,11 +729,7 @@ function NetworkMonitor(filters, owner) {
|
||||
this.openResponses = {};
|
||||
this._httpResponseExaminer =
|
||||
DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this);
|
||||
this._httpModifyExaminer =
|
||||
DevToolsUtils.makeInfallible(this._httpModifyExaminer).bind(this);
|
||||
this._serviceWorkerRequest = this._serviceWorkerRequest.bind(this);
|
||||
this.throttleData = null;
|
||||
this._throttler = null;
|
||||
}
|
||||
|
||||
exports.NetworkMonitor = NetworkMonitor;
|
||||
@ -695,13 +754,6 @@ NetworkMonitor.prototype = {
|
||||
0x804b0006: "STATUS_RECEIVING_FROM"
|
||||
},
|
||||
|
||||
httpDownloadActivities: [
|
||||
gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START,
|
||||
gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
|
||||
gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
|
||||
gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
|
||||
],
|
||||
|
||||
// Network response bodies are piped through a buffer of the given size (in
|
||||
// bytes).
|
||||
responsePipeSegmentSize: null,
|
||||
@ -738,8 +790,6 @@ NetworkMonitor.prototype = {
|
||||
"http-on-examine-response", false);
|
||||
Services.obs.addObserver(this._httpResponseExaminer,
|
||||
"http-on-examine-cached-response", false);
|
||||
Services.obs.addObserver(this._httpModifyExaminer,
|
||||
"http-on-modify-request", false);
|
||||
}
|
||||
// In child processes, only watch for service worker requests
|
||||
// everything else only happens in the parent process
|
||||
@ -747,13 +797,6 @@ NetworkMonitor.prototype = {
|
||||
"service-worker-synthesized-response", false);
|
||||
},
|
||||
|
||||
_getThrottler: function () {
|
||||
if (this.throttleData !== null && this._throttler === null) {
|
||||
this._throttler = new NetworkThrottleManager(this.throttleData);
|
||||
}
|
||||
return this._throttler;
|
||||
},
|
||||
|
||||
_serviceWorkerRequest: function (subject, topic, data) {
|
||||
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
|
||||
|
||||
@ -868,62 +911,6 @@ NetworkMonitor.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Observe notifications for the http-on-modify-request topic, coming from
|
||||
* the nsIObserverService.
|
||||
*
|
||||
* @private
|
||||
* @param nsIHttpChannel aSubject
|
||||
* @returns void
|
||||
*/
|
||||
_httpModifyExaminer: function (subject) {
|
||||
let throttler = this._getThrottler();
|
||||
if (throttler) {
|
||||
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
|
||||
if (matchRequest(channel, this.filters)) {
|
||||
throttler.manageUpload(channel);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A helper function for observeActivity. This does whatever work
|
||||
* is required by a particular http activity event. Arguments are
|
||||
* the same as for observeActivity.
|
||||
*/
|
||||
_dispatchActivity: function (httpActivity, channel, activityType,
|
||||
activitySubtype, timestamp, extraSizeData,
|
||||
extraStringData) {
|
||||
let transCodes = this.httpTransactionCodes;
|
||||
|
||||
// Store the time information for this activity subtype.
|
||||
if (activitySubtype in transCodes) {
|
||||
let stage = transCodes[activitySubtype];
|
||||
if (stage in httpActivity.timings) {
|
||||
httpActivity.timings[stage].last = timestamp;
|
||||
} else {
|
||||
httpActivity.timings[stage] = {
|
||||
first: timestamp,
|
||||
last: timestamp,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
switch (activitySubtype) {
|
||||
case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
|
||||
this._onRequestBodySent(httpActivity);
|
||||
break;
|
||||
case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
|
||||
this._onResponseHeader(httpActivity, extraStringData);
|
||||
break;
|
||||
case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
|
||||
this._onTransactionClose(httpActivity);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Begin observing HTTP traffic that originates inside the current tab.
|
||||
*
|
||||
@ -973,20 +960,33 @@ NetworkMonitor.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're throttling, we must not report events as they arrive
|
||||
// from platform, but instead let the throttler emit the events
|
||||
// after some time has elapsed.
|
||||
if (httpActivity.downloadThrottle &&
|
||||
this.httpDownloadActivities.indexOf(activitySubtype) >= 0) {
|
||||
let callback = this._dispatchActivity.bind(this);
|
||||
httpActivity.downloadThrottle
|
||||
.addActivityCallback(callback, httpActivity, channel, activityType,
|
||||
activitySubtype, timestamp, extraSizeData,
|
||||
extraStringData);
|
||||
} else {
|
||||
this._dispatchActivity(httpActivity, channel, activityType,
|
||||
activitySubtype, timestamp, extraSizeData,
|
||||
extraStringData);
|
||||
let transCodes = this.httpTransactionCodes;
|
||||
|
||||
// Store the time information for this activity subtype.
|
||||
if (activitySubtype in transCodes) {
|
||||
let stage = transCodes[activitySubtype];
|
||||
if (stage in httpActivity.timings) {
|
||||
httpActivity.timings[stage].last = timestamp;
|
||||
} else {
|
||||
httpActivity.timings[stage] = {
|
||||
first: timestamp,
|
||||
last: timestamp,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
switch (activitySubtype) {
|
||||
case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
|
||||
this._onRequestBodySent(httpActivity);
|
||||
break;
|
||||
case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
|
||||
this._onResponseHeader(httpActivity, extraStringData);
|
||||
break;
|
||||
case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
|
||||
this._onTransactionClose(httpActivity);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}),
|
||||
|
||||
@ -1081,7 +1081,7 @@ NetworkMonitor.prototype = {
|
||||
|
||||
httpActivity.owner = this.owner.onNetworkEvent(event, channel);
|
||||
|
||||
this._setupResponseListener(httpActivity, fromCache);
|
||||
this._setupResponseListener(httpActivity);
|
||||
|
||||
httpActivity.owner.addRequestHeaders(headers, extraStringData);
|
||||
httpActivity.owner.addRequestCookies(cookies);
|
||||
@ -1151,17 +1151,10 @@ NetworkMonitor.prototype = {
|
||||
* @param object httpActivity
|
||||
* The HTTP activity object we are tracking.
|
||||
*/
|
||||
_setupResponseListener: function (httpActivity, fromCache) {
|
||||
_setupResponseListener: function (httpActivity) {
|
||||
let channel = httpActivity.channel;
|
||||
channel.QueryInterface(Ci.nsITraceableChannel);
|
||||
|
||||
if (!fromCache) {
|
||||
let throttler = this._getThrottler();
|
||||
if (throttler) {
|
||||
httpActivity.downloadThrottle = throttler.manage(channel);
|
||||
}
|
||||
}
|
||||
|
||||
// The response will be written into the outputStream of this pipe.
|
||||
// This allows us to buffer the data we are receiving and read it
|
||||
// asynchronously.
|
||||
@ -1344,10 +1337,12 @@ NetworkMonitor.prototype = {
|
||||
harTimings.connect = -1;
|
||||
}
|
||||
|
||||
if (timings.STATUS_SENDING_TO) {
|
||||
harTimings.send = timings.STATUS_SENDING_TO.last - timings.STATUS_SENDING_TO.first;
|
||||
} else if (timings.REQUEST_HEADER && timings.REQUEST_BODY_SENT) {
|
||||
harTimings.send = timings.REQUEST_BODY_SENT.last - timings.REQUEST_HEADER.first;
|
||||
if ((timings.STATUS_WAITING_FOR || timings.STATUS_RECEIVING_FROM) &&
|
||||
(timings.STATUS_CONNECTED_TO || timings.STATUS_SENDING_TO)) {
|
||||
harTimings.send = (timings.STATUS_WAITING_FOR ||
|
||||
timings.STATUS_RECEIVING_FROM).first -
|
||||
(timings.STATUS_CONNECTED_TO ||
|
||||
timings.STATUS_SENDING_TO).last;
|
||||
} else {
|
||||
harTimings.send = -1;
|
||||
}
|
||||
@ -1393,8 +1388,6 @@ NetworkMonitor.prototype = {
|
||||
"http-on-examine-response");
|
||||
Services.obs.removeObserver(this._httpResponseExaminer,
|
||||
"http-on-examine-cached-response");
|
||||
Services.obs.removeObserver(this._httpModifyExaminer,
|
||||
"http-on-modify-request", false);
|
||||
}
|
||||
|
||||
Services.obs.removeObserver(this._serviceWorkerRequest,
|
||||
@ -1405,7 +1398,6 @@ NetworkMonitor.prototype = {
|
||||
this.openResponses = {};
|
||||
this.owner = null;
|
||||
this.filters = null;
|
||||
this._throttler = null;
|
||||
},
|
||||
};
|
||||
|
||||
@ -1449,7 +1441,6 @@ NetworkMonitorChild.prototype = {
|
||||
owner: null,
|
||||
_netEvents: null,
|
||||
_saveRequestAndResponseBodies: true,
|
||||
_throttleData: null,
|
||||
|
||||
get saveRequestAndResponseBodies() {
|
||||
return this._saveRequestAndResponseBodies;
|
||||
@ -1466,22 +1457,6 @@ NetworkMonitorChild.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
get throttleData() {
|
||||
return this._throttleData;
|
||||
},
|
||||
|
||||
set throttleData(val) {
|
||||
this._throttleData = val;
|
||||
|
||||
this._messageManager.sendAsyncMessage("debug:netmonitor:" + this.connID, {
|
||||
appId: this.appId,
|
||||
action: "setPreferences",
|
||||
preferences: {
|
||||
throttleData: this._throttleData,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
init: function () {
|
||||
let mm = this._messageManager;
|
||||
mm.addMessageListener("debug:netmonitor:" + this.connID + ":newEvent",
|
||||
@ -1666,9 +1641,8 @@ NetworkMonitorManager.prototype = {
|
||||
case "setPreferences": {
|
||||
let {preferences} = msg.json;
|
||||
for (let key of Object.keys(preferences)) {
|
||||
if ((key == "saveRequestAndResponseBodies" ||
|
||||
key == "throttleData") && this.netMonitor) {
|
||||
this.netMonitor[key] = preferences[key];
|
||||
if (key == "saveRequestAndResponseBodies" && this.netMonitor) {
|
||||
this.netMonitor.saveRequestAndResponseBodies = preferences[key];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -1,140 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const promise = require("promise");
|
||||
const { NetworkThrottleManager } =
|
||||
require("devtools/shared/webconsole/throttle");
|
||||
const nsIScriptableInputStream = Ci.nsIScriptableInputStream;
|
||||
|
||||
function TestStreamListener() {
|
||||
this.state = "initial";
|
||||
}
|
||||
TestStreamListener.prototype = {
|
||||
onStartRequest: function() {
|
||||
this.setState("start");
|
||||
},
|
||||
|
||||
onStopRequest: function() {
|
||||
this.setState("stop");
|
||||
},
|
||||
|
||||
onDataAvailable: function(request, context, inputStream, offset, count) {
|
||||
const sin = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(nsIScriptableInputStream);
|
||||
sin.init(inputStream);
|
||||
this.data = sin.read(count);
|
||||
this.setState("data");
|
||||
},
|
||||
|
||||
setState: function(state) {
|
||||
this.state = state;
|
||||
if (this._deferred) {
|
||||
this._deferred.resolve(state);
|
||||
this._deferred = null;
|
||||
}
|
||||
},
|
||||
|
||||
onStateChanged: function() {
|
||||
if (!this._deferred) {
|
||||
this._deferred = promise.defer();
|
||||
}
|
||||
return this._deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
function TestChannel() {
|
||||
this.state = "initial";
|
||||
this.testListener = new TestStreamListener();
|
||||
this._throttleQueue = null;
|
||||
}
|
||||
TestChannel.prototype = {
|
||||
QueryInterface: function() {
|
||||
return this;
|
||||
},
|
||||
|
||||
get throttleQueue() {
|
||||
return this._throttleQueue;
|
||||
},
|
||||
|
||||
set throttleQueue(q) {
|
||||
this._throttleQueue = q;
|
||||
this.state = "throttled";
|
||||
},
|
||||
|
||||
setNewListener: function(listener) {
|
||||
this.listener = listener;
|
||||
this.state = "listener";
|
||||
return this.testListener;
|
||||
},
|
||||
};
|
||||
|
||||
add_task(function*() {
|
||||
let throttler = new NetworkThrottleManager({
|
||||
roundTripTimeMean: 1,
|
||||
roundTripTimeMax: 1,
|
||||
downloadBPSMean: 500,
|
||||
downloadBPSMax: 500,
|
||||
uploadBPSMean: 500,
|
||||
uploadBPSMax: 500,
|
||||
});
|
||||
|
||||
let uploadChannel = new TestChannel();
|
||||
throttler.manageUpload(uploadChannel);
|
||||
equal(uploadChannel.state, "throttled",
|
||||
"NetworkThrottleManager set throttleQueue");
|
||||
|
||||
let downloadChannel = new TestChannel();
|
||||
let testListener = downloadChannel.testListener;
|
||||
|
||||
let listener = throttler.manage(downloadChannel);
|
||||
equal(downloadChannel.state, "listener",
|
||||
"NetworkThrottleManager called setNewListener");
|
||||
|
||||
equal(testListener.state, "initial", "test listener in initial state");
|
||||
|
||||
// This method must be passed through immediately.
|
||||
listener.onStartRequest(null, null);
|
||||
equal(testListener.state, "start", "test listener started");
|
||||
|
||||
const TEST_INPUT = "hi bob";
|
||||
|
||||
let testStream = Cc["@mozilla.org/storagestream;1"]
|
||||
.createInstance(Ci.nsIStorageStream);
|
||||
testStream.init(512, 512);
|
||||
let out = testStream.getOutputStream(0);
|
||||
out.write(TEST_INPUT, TEST_INPUT.length);
|
||||
out.close();
|
||||
let testInputStream = testStream.newInputStream(0);
|
||||
|
||||
let activityDistributor =
|
||||
Cc["@mozilla.org/network/http-activity-distributor;1"]
|
||||
.getService(Ci.nsIHttpActivityDistributor);
|
||||
let activitySeen = false;
|
||||
listener.addActivityCallback(() => activitySeen = true, null, null, null,
|
||||
activityDistributor
|
||||
.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
|
||||
null, TEST_INPUT.length, null);
|
||||
|
||||
// onDataAvailable is required to immediately read the data.
|
||||
listener.onDataAvailable(null, null, testInputStream, 0, 6);
|
||||
equal(testInputStream.available(), 0, "no more data should be available");
|
||||
equal(testListener.state, "start",
|
||||
"test listener should not have received data");
|
||||
equal(activitySeen, false, "activity not distributed yet");
|
||||
|
||||
let newState = yield testListener.onStateChanged();
|
||||
equal(newState, "data", "test listener received data");
|
||||
equal(testListener.data, TEST_INPUT, "test listener received all the data");
|
||||
equal(activitySeen, true, "activity has been distributed");
|
||||
|
||||
let onChange = testListener.onStateChanged();
|
||||
listener.onStopRequest(null, null, null);
|
||||
newState = yield onChange;
|
||||
equal(newState, "stop", "onStateChanged reported");
|
||||
});
|
@ -14,4 +14,3 @@ support-files =
|
||||
[test_security-info-state.js]
|
||||
[test_security-info-static-hpkp.js]
|
||||
[test_security-info-weakness-reasons.js]
|
||||
[test_throttle.js]
|
||||
|
@ -1,400 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {CC, Ci, Cu, Cc} = require("chrome");
|
||||
|
||||
const ArrayBufferInputStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
|
||||
"nsIArrayBufferInputStream");
|
||||
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
|
||||
"nsIBinaryInputStream", "setInputStream");
|
||||
|
||||
loader.lazyServiceGetter(this, "gActivityDistributor",
|
||||
"@mozilla.org/network/http-activity-distributor;1",
|
||||
"nsIHttpActivityDistributor");
|
||||
|
||||
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
|
||||
|
||||
/**
|
||||
* Construct a new nsIStreamListener that buffers data and provides a
|
||||
* method to notify another listener when data is available. This is
|
||||
* used to throttle network data on a per-channel basis.
|
||||
*
|
||||
* After construction, @see setOriginalListener must be called on the
|
||||
* new object.
|
||||
*
|
||||
* @param {NetworkThrottleQueue} queue the NetworkThrottleQueue to
|
||||
* which status changes should be reported
|
||||
*/
|
||||
function NetworkThrottleListener(queue) {
|
||||
this.queue = queue;
|
||||
this.pendingData = [];
|
||||
this.pendingException = null;
|
||||
this.offset = 0;
|
||||
this.responseStarted = false;
|
||||
this.activities = {};
|
||||
}
|
||||
|
||||
NetworkThrottleListener.prototype = {
|
||||
QueryInterface:
|
||||
XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInterfaceRequestor,
|
||||
Ci.nsISupports]),
|
||||
|
||||
/**
|
||||
* Set the original listener for this object. The original listener
|
||||
* will receive requests from this object when the queue allows data
|
||||
* through.
|
||||
*
|
||||
* @param {nsIStreamListener} originalListener the original listener
|
||||
* for the channel, to which all requests will be sent
|
||||
*/
|
||||
setOriginalListener: function (originalListener) {
|
||||
this.originalListener = originalListener;
|
||||
},
|
||||
|
||||
/**
|
||||
* @see nsIStreamListener.onStartRequest.
|
||||
*/
|
||||
onStartRequest: function (request, context) {
|
||||
this.originalListener.onStartRequest(request, context);
|
||||
this.queue.start(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @see nsIStreamListener.onStopRequest.
|
||||
*/
|
||||
onStopRequest: function (request, context, statusCode) {
|
||||
this.pendingData.push({request, context, statusCode});
|
||||
this.queue.dataAvailable(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @see nsIStreamListener.onDataAvailable.
|
||||
*/
|
||||
onDataAvailable: function (request, context, inputStream, offset, count) {
|
||||
if (this.pendingException) {
|
||||
throw this.pendingException;
|
||||
}
|
||||
|
||||
const bin = new BinaryInputStream(inputStream);
|
||||
const bytes = new ArrayBuffer(count);
|
||||
bin.readArrayBuffer(count, bytes);
|
||||
|
||||
const stream = new ArrayBufferInputStream();
|
||||
stream.setData(bytes, 0, count);
|
||||
|
||||
this.pendingData.push({request, context, stream, count});
|
||||
this.queue.dataAvailable(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Allow some buffered data from this object to be forwarded to this
|
||||
* object's originalListener.
|
||||
*
|
||||
* @param {Number} bytesPermitted The maximum number of bytes
|
||||
* permitted to be sent.
|
||||
* @return {Object} an object of the form {length, done}, where
|
||||
* |length| is the number of bytes actually forwarded, and
|
||||
* |done| is a boolean indicating whether this particular
|
||||
* request has been completed. (A NetworkThrottleListener
|
||||
* may be queued multiple times, so this does not mean that
|
||||
* all available data has been sent.)
|
||||
*/
|
||||
sendSomeData: function (bytesPermitted) {
|
||||
if (this.pendingData.length === 0) {
|
||||
// Shouldn't happen.
|
||||
return {length: 0, done: true};
|
||||
}
|
||||
|
||||
const {request, context, stream, count, statusCode} = this.pendingData[0];
|
||||
|
||||
if (statusCode !== undefined) {
|
||||
this.pendingData.shift();
|
||||
this.originalListener.onStopRequest(request, context, statusCode);
|
||||
return {length: 0, done: true};
|
||||
}
|
||||
|
||||
if (bytesPermitted > count) {
|
||||
bytesPermitted = count;
|
||||
}
|
||||
|
||||
try {
|
||||
this.originalListener.onDataAvailable(request, context, stream,
|
||||
this.offset, bytesPermitted);
|
||||
} catch (e) {
|
||||
this.pendingException = e;
|
||||
}
|
||||
|
||||
let done = false;
|
||||
if (bytesPermitted === count) {
|
||||
this.pendingData.shift();
|
||||
done = true;
|
||||
} else {
|
||||
this.pendingData[0].count -= bytesPermitted;
|
||||
}
|
||||
|
||||
this.offset += bytesPermitted;
|
||||
// Maybe our state has changed enough to emit an event.
|
||||
this.maybeEmitEvents();
|
||||
|
||||
return {length: bytesPermitted, done};
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the number of pending data requests available for this
|
||||
* listener.
|
||||
*/
|
||||
pendingCount: function () {
|
||||
return this.pendingData.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is called when an http activity event is delivered. This
|
||||
* object delays the event until the appropriate moment.
|
||||
*/
|
||||
addActivityCallback: function (callback, httpActivity, channel, activityType,
|
||||
activitySubtype, timestamp, extraSizeData,
|
||||
extraStringData) {
|
||||
let datum = {callback, httpActivity, channel, activityType,
|
||||
activitySubtype, extraSizeData,
|
||||
extraStringData};
|
||||
this.activities[activitySubtype] = datum;
|
||||
|
||||
if (activitySubtype ===
|
||||
gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE) {
|
||||
this.totalSize = extraSizeData;
|
||||
}
|
||||
|
||||
this.maybeEmitEvents();
|
||||
},
|
||||
|
||||
/**
|
||||
* This is called for a download throttler when the latency timeout
|
||||
* has ended.
|
||||
*/
|
||||
responseStart: function () {
|
||||
this.responseStarted = true;
|
||||
this.maybeEmitEvents();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check our internal state and emit any http activity events as
|
||||
* needed. Note that we wait until both our internal state has
|
||||
* changed and we've received the real http activity event from
|
||||
* platform. This approach ensures we can both pass on the correct
|
||||
* data from the original event, and update the reported time to be
|
||||
* consistent with the delay we're introducing.
|
||||
*/
|
||||
maybeEmitEvents: function () {
|
||||
if (this.responseStarted) {
|
||||
this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START);
|
||||
this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER);
|
||||
}
|
||||
|
||||
if (this.totalSize !== undefined && this.offset >= this.totalSize) {
|
||||
this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE);
|
||||
this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Emit an event for |code|, if the appropriate entry in
|
||||
* |activities| is defined.
|
||||
*/
|
||||
maybeEmit: function (code) {
|
||||
if (this.activities[code] !== undefined) {
|
||||
let {callback, httpActivity, channel, activityType,
|
||||
activitySubtype, extraSizeData,
|
||||
extraStringData} = this.activities[code];
|
||||
let now = Date.now() * 1000;
|
||||
callback(httpActivity, channel, activityType, activitySubtype,
|
||||
now, extraSizeData, extraStringData);
|
||||
this.activities[code] = undefined;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a new queue that can be used to throttle the network for
|
||||
* a group of related network requests.
|
||||
*
|
||||
* meanBPS {Number} Mean bytes per second.
|
||||
* maxBPS {Number} Maximum bytes per second.
|
||||
* roundTripTimeMean {Number} Mean round trip time in milliseconds.
|
||||
* roundTripTimeMax {Number} Maximum round trip time in milliseconds.
|
||||
*/
|
||||
function NetworkThrottleQueue(meanBPS, maxBPS,
|
||||
roundTripTimeMean, roundTripTimeMax) {
|
||||
this.meanBPS = meanBPS;
|
||||
this.maxBPS = maxBPS;
|
||||
this.roundTripTimeMean = roundTripTimeMean;
|
||||
this.roundTripTimeMax = roundTripTimeMax;
|
||||
|
||||
this.pendingRequests = new Set();
|
||||
this.downloadQueue = [];
|
||||
this.previousReads = [];
|
||||
|
||||
this.pumping = false;
|
||||
}
|
||||
|
||||
NetworkThrottleQueue.prototype = {
|
||||
/**
|
||||
* A helper function that, given a mean and a maximum, returns a
|
||||
* random integer between (mean - (max - mean)) and max.
|
||||
*/
|
||||
random: function (mean, max) {
|
||||
return mean - (max - mean) + Math.floor(2 * (max - mean) * Math.random());
|
||||
},
|
||||
|
||||
/**
|
||||
* A helper function that lets the indicating listener start sending
|
||||
* data. This is called after the initial round trip time for the
|
||||
* listener has elapsed.
|
||||
*/
|
||||
allowDataFrom: function (throttleListener) {
|
||||
throttleListener.responseStart();
|
||||
this.pendingRequests.delete(throttleListener);
|
||||
const count = throttleListener.pendingCount();
|
||||
for (let i = 0; i < count; ++i) {
|
||||
this.downloadQueue.push(throttleListener);
|
||||
}
|
||||
this.pump();
|
||||
},
|
||||
|
||||
/**
|
||||
* Notice a new listener object. This is called by the
|
||||
* NetworkThrottleListener when the request has started. Initially
|
||||
* a new listener object is put into a "pending" state, until the
|
||||
* round-trip time has elapsed. This is used to simulate latency.
|
||||
*
|
||||
* @param {NetworkThrottleListener} throttleListener the new listener
|
||||
*/
|
||||
start: function (throttleListener) {
|
||||
this.pendingRequests.add(throttleListener);
|
||||
let delay = this.random(this.roundTripTimeMean, this.roundTripTimeMax);
|
||||
if (delay > 0) {
|
||||
setTimeout(() => this.allowDataFrom(throttleListener), delay);
|
||||
} else {
|
||||
this.allowDataFrom(throttleListener);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Note that new data is available for a given listener. Each time
|
||||
* data is available, the listener will be re-queued.
|
||||
*
|
||||
* @param {NetworkThrottleListener} throttleListener the listener
|
||||
* which has data available.
|
||||
*/
|
||||
dataAvailable: function (throttleListener) {
|
||||
if (!this.pendingRequests.has(throttleListener)) {
|
||||
this.downloadQueue.push(throttleListener);
|
||||
this.pump();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* An internal function that permits individual listeners to send
|
||||
* data.
|
||||
*/
|
||||
pump: function () {
|
||||
// A redirect will cause two NetworkThrottleListeners to be on a
|
||||
// listener chain. In this case, we might recursively call into
|
||||
// this method. Avoid infinite recursion here.
|
||||
if (this.pumping) {
|
||||
return;
|
||||
}
|
||||
this.pumping = true;
|
||||
|
||||
const now = Date.now();
|
||||
const oneSecondAgo = now - 1000;
|
||||
|
||||
while (this.previousReads.length &&
|
||||
this.previousReads[0].when < oneSecondAgo) {
|
||||
this.previousReads.shift();
|
||||
}
|
||||
|
||||
const totalBytes = this.previousReads.reduce((sum, elt) => {
|
||||
return sum + elt.numBytes;
|
||||
}, 0);
|
||||
|
||||
let thisSliceBytes = this.random(this.meanBPS, this.maxBPS);
|
||||
if (totalBytes < thisSliceBytes) {
|
||||
thisSliceBytes -= totalBytes;
|
||||
let readThisTime = 0;
|
||||
while (thisSliceBytes > 0 && this.downloadQueue.length) {
|
||||
let {length, done} = this.downloadQueue[0].sendSomeData(thisSliceBytes);
|
||||
thisSliceBytes -= length;
|
||||
readThisTime += length;
|
||||
if (done) {
|
||||
this.downloadQueue.shift();
|
||||
}
|
||||
}
|
||||
this.previousReads.push({when: now, numBytes: readThisTime});
|
||||
}
|
||||
|
||||
// If there is more data to download, then schedule ourselves for
|
||||
// one second after the oldest previous read.
|
||||
if (this.downloadQueue.length) {
|
||||
const when = this.previousReads[0].when + 1000;
|
||||
setTimeout(this.pump.bind(this), when - now);
|
||||
}
|
||||
|
||||
this.pumping = false;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a new object that can be used to throttle the network for
|
||||
* a group of related network requests.
|
||||
*
|
||||
* @param {Object} An object with the following attributes:
|
||||
* roundTripTimeMean {Number} Mean round trip time in milliseconds.
|
||||
* roundTripTimeMax {Number} Maximum round trip time in milliseconds.
|
||||
* downloadBPSMean {Number} Mean bytes per second for downloads.
|
||||
* downloadBPSMax {Number} Maximum bytes per second for downloads.
|
||||
* uploadBPSMean {Number} Mean bytes per second for uploads.
|
||||
* uploadBPSMax {Number} Maximum bytes per second for uploads.
|
||||
*/
|
||||
function NetworkThrottleManager({roundTripTimeMean, roundTripTimeMax,
|
||||
downloadBPSMean, downloadBPSMax,
|
||||
uploadBPSMean, uploadBPSMax}) {
|
||||
this.downloadQueue =
|
||||
new NetworkThrottleQueue(downloadBPSMean, downloadBPSMax,
|
||||
roundTripTimeMean, roundTripTimeMax);
|
||||
this.uploadQueue = Cc["@mozilla.org/network/throttlequeue;1"]
|
||||
.createInstance(Ci.nsIInputChannelThrottleQueue);
|
||||
this.uploadQueue.init(uploadBPSMean, uploadBPSMax);
|
||||
}
|
||||
exports.NetworkThrottleManager = NetworkThrottleManager;
|
||||
|
||||
NetworkThrottleManager.prototype = {
|
||||
/**
|
||||
* Create a new NetworkThrottleListener for a given channel and
|
||||
* install it using |setNewListener|.
|
||||
*
|
||||
* @param {nsITraceableChannel} channel the channel to manage
|
||||
* @return {NetworkThrottleListener} the new listener
|
||||
*/
|
||||
manage: function (channel) {
|
||||
let listener = new NetworkThrottleListener(this.downloadQueue);
|
||||
let originalListener = channel.setNewListener(listener);
|
||||
listener.setOriginalListener(originalListener);
|
||||
return listener;
|
||||
},
|
||||
|
||||
/**
|
||||
* Throttle uploads taking place on the given channel.
|
||||
*
|
||||
* @param {nsITraceableChannel} channel the channel to manage
|
||||
*/
|
||||
manageUpload: function (channel) {
|
||||
channel = channel.QueryInterface(Ci.nsIThrottledInputChannel);
|
||||
channel.throttleQueue = this.uploadQueue;
|
||||
},
|
||||
};
|
@ -1,392 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ThrottleQueue.h"
|
||||
#include "nsISeekableStream.h"
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsNetUtil.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class ThrottleInputStream final
|
||||
: public nsIAsyncInputStream
|
||||
, public nsISeekableStream
|
||||
{
|
||||
public:
|
||||
|
||||
ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue);
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIINPUTSTREAM
|
||||
NS_DECL_NSISEEKABLESTREAM
|
||||
NS_DECL_NSIASYNCINPUTSTREAM
|
||||
|
||||
void AllowInput();
|
||||
|
||||
private:
|
||||
|
||||
~ThrottleInputStream();
|
||||
|
||||
nsCOMPtr<nsIInputStream> mStream;
|
||||
RefPtr<ThrottleQueue> mQueue;
|
||||
nsresult mClosedStatus;
|
||||
|
||||
nsCOMPtr<nsIInputStreamCallback> mCallback;
|
||||
nsCOMPtr<nsIEventTarget> mEventTarget;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream, nsISeekableStream)
|
||||
|
||||
ThrottleInputStream::ThrottleInputStream(nsIInputStream *aStream, ThrottleQueue* aQueue)
|
||||
: mStream(aStream)
|
||||
, mQueue(aQueue)
|
||||
, mClosedStatus(NS_OK)
|
||||
{
|
||||
MOZ_ASSERT(aQueue != nullptr);
|
||||
}
|
||||
|
||||
ThrottleInputStream::~ThrottleInputStream()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::Close()
|
||||
{
|
||||
if (NS_FAILED(mClosedStatus)) {
|
||||
return mClosedStatus;
|
||||
}
|
||||
|
||||
if (mQueue) {
|
||||
mQueue->DequeueStream(this);
|
||||
mQueue = nullptr;
|
||||
mClosedStatus = NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
return mStream->Close();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::Available(uint64_t* aResult)
|
||||
{
|
||||
if (NS_FAILED(mClosedStatus)) {
|
||||
return mClosedStatus;
|
||||
}
|
||||
|
||||
return mStream->Available(aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
|
||||
{
|
||||
if (NS_FAILED(mClosedStatus)) {
|
||||
return mClosedStatus;
|
||||
}
|
||||
|
||||
uint32_t realCount;
|
||||
nsresult rv = mQueue->Available(aCount, &realCount);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (realCount == 0) {
|
||||
return NS_BASE_STREAM_WOULD_BLOCK;
|
||||
}
|
||||
|
||||
rv = mStream->Read(aBuf, realCount, aResult);
|
||||
if (NS_SUCCEEDED(rv) && *aResult > 0) {
|
||||
mQueue->RecordRead(*aResult);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
|
||||
uint32_t aCount, uint32_t* aResult)
|
||||
{
|
||||
if (NS_FAILED(mClosedStatus)) {
|
||||
return mClosedStatus;
|
||||
}
|
||||
|
||||
uint32_t realCount;
|
||||
nsresult rv = mQueue->Available(aCount, &realCount);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (realCount == 0) {
|
||||
return NS_BASE_STREAM_WOULD_BLOCK;
|
||||
}
|
||||
|
||||
rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult);
|
||||
if (NS_SUCCEEDED(rv) && *aResult > 0) {
|
||||
mQueue->RecordRead(*aResult);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::IsNonBlocking(bool* aNonBlocking)
|
||||
{
|
||||
*aNonBlocking = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset)
|
||||
{
|
||||
if (NS_FAILED(mClosedStatus)) {
|
||||
return mClosedStatus;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
|
||||
if (!sstream) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return sstream->Seek(aWhence, aOffset);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::Tell(int64_t* aResult)
|
||||
{
|
||||
if (NS_FAILED(mClosedStatus)) {
|
||||
return mClosedStatus;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
|
||||
if (!sstream) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return sstream->Tell(aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::SetEOF()
|
||||
{
|
||||
if (NS_FAILED(mClosedStatus)) {
|
||||
return mClosedStatus;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
|
||||
if (!sstream) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return sstream->SetEOF();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::CloseWithStatus(nsresult aStatus)
|
||||
{
|
||||
if (NS_FAILED(mClosedStatus)) {
|
||||
// Already closed, ignore.
|
||||
return NS_OK;
|
||||
}
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
aStatus = NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
mClosedStatus = Close();
|
||||
if (NS_SUCCEEDED(mClosedStatus)) {
|
||||
mClosedStatus = aStatus;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleInputStream::AsyncWait(nsIInputStreamCallback *aCallback,
|
||||
uint32_t aFlags,
|
||||
uint32_t aRequestedCount,
|
||||
nsIEventTarget *aEventTarget)
|
||||
{
|
||||
if (aFlags != 0) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
mCallback = aCallback;
|
||||
mEventTarget = aEventTarget;
|
||||
if (mCallback) {
|
||||
mQueue->QueueStream(this);
|
||||
} else {
|
||||
mQueue->DequeueStream(this);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
ThrottleInputStream::AllowInput()
|
||||
{
|
||||
MOZ_ASSERT(mCallback);
|
||||
nsCOMPtr<nsIInputStreamCallback> callbackEvent =
|
||||
NS_NewInputStreamReadyEvent(mCallback, mEventTarget);
|
||||
mCallback = nullptr;
|
||||
mEventTarget = nullptr;
|
||||
callbackEvent->OnInputStreamReady(this);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback)
|
||||
|
||||
ThrottleQueue::ThrottleQueue()
|
||||
: mMeanBytesPerSecond(0)
|
||||
, mMaxBytesPerSecond(0)
|
||||
, mBytesProcessed(0)
|
||||
, mTimerArmed(false)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIEventTarget> sts;
|
||||
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
mTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
if (mTimer)
|
||||
mTimer->SetTarget(sts);
|
||||
}
|
||||
|
||||
ThrottleQueue::~ThrottleQueue()
|
||||
{
|
||||
if (mTimer && mTimerArmed) {
|
||||
mTimer->Cancel();
|
||||
}
|
||||
mTimer = nullptr;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleQueue::RecordRead(uint32_t aBytesRead)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
ThrottleEntry entry;
|
||||
entry.mTime = TimeStamp::Now();
|
||||
entry.mBytesRead = aBytesRead;
|
||||
mReadEvents.AppendElement(entry);
|
||||
mBytesProcessed += aBytesRead;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleQueue::Available(uint32_t aRemaining, uint32_t* aAvailable)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
TimeStamp oneSecondAgo = now - TimeDuration::FromSeconds(1);
|
||||
size_t i;
|
||||
|
||||
// Remove all stale events.
|
||||
for (i = 0; i < mReadEvents.Length(); ++i) {
|
||||
if (mReadEvents[i].mTime >= oneSecondAgo) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mReadEvents.RemoveElementsAt(0, i);
|
||||
|
||||
uint32_t totalBytes = 0;
|
||||
for (i = 0; i < mReadEvents.Length(); ++i) {
|
||||
totalBytes += mReadEvents[i].mBytesRead;
|
||||
}
|
||||
|
||||
uint32_t spread = mMaxBytesPerSecond - mMeanBytesPerSecond;
|
||||
double prob = static_cast<double>(rand()) / RAND_MAX;
|
||||
uint32_t thisSliceBytes = mMeanBytesPerSecond - spread +
|
||||
static_cast<uint32_t>(2 * spread * prob);
|
||||
|
||||
if (totalBytes >= thisSliceBytes) {
|
||||
*aAvailable = 0;
|
||||
} else {
|
||||
*aAvailable = thisSliceBytes;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond)
|
||||
{
|
||||
// Can be called on any thread.
|
||||
if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 || aMaxBytesPerSecond < aMeanBytesPerSecond) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
mMeanBytesPerSecond = aMeanBytesPerSecond;
|
||||
mMaxBytesPerSecond = aMaxBytesPerSecond;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleQueue::BytesProcessed(uint64_t* aResult)
|
||||
{
|
||||
*aResult = mBytesProcessed;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleQueue::WrapStream(nsIInputStream* aInputStream, nsIAsyncInputStream** aResult)
|
||||
{
|
||||
nsCOMPtr<nsIAsyncInputStream> result = new ThrottleInputStream(aInputStream, this);
|
||||
result.forget(aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ThrottleQueue::Notify(nsITimer* aTimer)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
// A notified reader may need to push itself back on the queue.
|
||||
// Swap out the list of readers so that this works properly.
|
||||
nsTArray<RefPtr<ThrottleInputStream>> events;
|
||||
events.SwapElements(mAsyncEvents);
|
||||
|
||||
// Optimistically notify all the waiting readers, and then let them
|
||||
// requeue if there isn't enough bandwidth.
|
||||
for (size_t i = 0; i < events.Length(); ++i) {
|
||||
events[i]->AllowInput();
|
||||
}
|
||||
|
||||
mTimerArmed = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
ThrottleQueue::QueueStream(ThrottleInputStream* aStream)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
if (mAsyncEvents.IndexOf(aStream) == mAsyncEvents.NoIndex) {
|
||||
mAsyncEvents.AppendElement(aStream);
|
||||
|
||||
if (!mTimerArmed) {
|
||||
uint32_t ms = 1000;
|
||||
if (mReadEvents.Length() > 0) {
|
||||
TimeStamp t = mReadEvents[0].mTime + TimeDuration::FromSeconds(1);
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
if (t > now) {
|
||||
ms = static_cast<uint32_t>((t - now).ToMilliseconds());
|
||||
} else {
|
||||
ms = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) {
|
||||
mTimerArmed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ThrottleQueue::DequeueStream(ThrottleInputStream* aStream)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
mAsyncEvents.RemoveElement(aStream);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_net_ThrottleQueue_h
|
||||
#define mozilla_net_ThrottleQueue_h
|
||||
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsIThrottledInputChannel.h"
|
||||
#include "nsITimer.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class ThrottleInputStream;
|
||||
|
||||
/**
|
||||
* An implementation of nsIInputChannelThrottleQueue that can be used
|
||||
* to throttle uploads. This class is not thread-safe.
|
||||
* Initialization and calls to WrapStream may be done on any thread;
|
||||
* but otherwise, after creation, it can only be used on the socket
|
||||
* thread. It currently throttles with a one second granularity, so
|
||||
* may be a bit choppy.
|
||||
*/
|
||||
|
||||
class ThrottleQueue final
|
||||
: public nsIInputChannelThrottleQueue
|
||||
, public nsITimerCallback
|
||||
{
|
||||
public:
|
||||
|
||||
ThrottleQueue();
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
|
||||
void QueueStream(ThrottleInputStream* aStream);
|
||||
void DequeueStream(ThrottleInputStream* aStream);
|
||||
|
||||
private:
|
||||
|
||||
~ThrottleQueue();
|
||||
|
||||
struct ThrottleEntry {
|
||||
TimeStamp mTime;
|
||||
uint32_t mBytesRead;
|
||||
};
|
||||
|
||||
nsTArray<ThrottleEntry> mReadEvents;
|
||||
uint32_t mMeanBytesPerSecond;
|
||||
uint32_t mMaxBytesPerSecond;
|
||||
uint64_t mBytesProcessed;
|
||||
|
||||
nsTArray<RefPtr<ThrottleInputStream>> mAsyncEvents;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
bool mTimerArmed;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // mozilla_net_ThrottleQueue_h
|
@ -125,7 +125,6 @@ XPIDL_SOURCES += [
|
||||
'nsISystemProxySettings.idl',
|
||||
'nsIThreadRetargetableRequest.idl',
|
||||
'nsIThreadRetargetableStreamListener.idl',
|
||||
'nsIThrottledInputChannel.idl',
|
||||
'nsITimedChannel.idl',
|
||||
'nsITLSServerSocket.idl',
|
||||
'nsITraceableChannel.idl',
|
||||
@ -261,7 +260,6 @@ UNIFIED_SOURCES += [
|
||||
'RequestContextService.cpp',
|
||||
'SimpleBuffer.cpp',
|
||||
'StreamingProtocolService.cpp',
|
||||
'ThrottleQueue.cpp',
|
||||
'Tickler.cpp',
|
||||
'TLSServerSocket.cpp',
|
||||
]
|
||||
|
@ -1,80 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIInputStream;
|
||||
interface nsIAsyncInputStream;
|
||||
|
||||
/**
|
||||
* An instance of this interface can be used to throttle the uploads
|
||||
* of a group of associated channels.
|
||||
*/
|
||||
[scriptable, uuid(6b4b96fe-3c67-4587-af7b-58b6b17da411)]
|
||||
interface nsIInputChannelThrottleQueue : nsISupports
|
||||
{
|
||||
/**
|
||||
* Initialize this object with the mean and maximum bytes per
|
||||
* second that will be allowed. Neither value may be zero, and
|
||||
* the maximum must not be less than the mean.
|
||||
*
|
||||
* @param aMeanBytesPerSecond
|
||||
* Mean number of bytes per second.
|
||||
* @param aMaxBytesPerSecond
|
||||
* Maximum number of bytes per second.
|
||||
*/
|
||||
void init(in unsigned long aMeanBytesPerSecond, in unsigned long aMaxBytesPerSecond);
|
||||
|
||||
/**
|
||||
* Return the number of bytes that are available to the caller in
|
||||
* this time slice.
|
||||
*
|
||||
* @param aRemaining
|
||||
* The number of bytes available to be processed
|
||||
* @return the number of bytes allowed to be processed during this
|
||||
* time slice; this will never be greater than aRemaining.
|
||||
*/
|
||||
unsigned long available(in unsigned long aRemaining);
|
||||
|
||||
/**
|
||||
* Record a successful read.
|
||||
*
|
||||
* @param aBytesRead
|
||||
* The number of bytes actually read.
|
||||
*/
|
||||
void recordRead(in unsigned long aBytesRead);
|
||||
|
||||
/**
|
||||
* Return the number of bytes allowed through this queue. This is
|
||||
* the sum of all the values passed to recordRead. This method is
|
||||
* primarily useful for testing.
|
||||
*/
|
||||
unsigned long long bytesProcessed();
|
||||
|
||||
/**
|
||||
* Wrap the given input stream in a new input stream which
|
||||
* throttles the incoming data.
|
||||
*
|
||||
* @param aInputStream the input stream to wrap
|
||||
* @return a new input stream that throttles the data.
|
||||
*/
|
||||
nsIAsyncInputStream wrapStream(in nsIInputStream aInputStream);
|
||||
};
|
||||
|
||||
/**
|
||||
* A throttled input channel can be managed by an
|
||||
* nsIInputChannelThrottleQueue to limit how much data is sent during
|
||||
* a given time slice.
|
||||
*/
|
||||
[scriptable, uuid(0a32a100-c031-45b6-9e8b-0444c7d4a143)]
|
||||
interface nsIThrottledInputChannel : nsISupports
|
||||
{
|
||||
/**
|
||||
* The queue that manages this channel. Multiple channels can
|
||||
* share a single queue. A null value means that no throttling
|
||||
* will be done.
|
||||
*/
|
||||
attribute nsIInputChannelThrottleQueue throttleQueue;
|
||||
};
|
@ -625,16 +625,6 @@
|
||||
{0x96, 0x1f, 0x65, 0x53, 0xcd, 0x60, 0xb1, 0xa2} \
|
||||
}
|
||||
|
||||
#define NS_THROTTLEQUEUE_CONTRACTID \
|
||||
"@mozilla.org/network/throttlequeue;1"
|
||||
#define NS_THROTTLEQUEUE_CID \
|
||||
{ /* 4c39159c-cd90-4dd3-97a7-06af5e6d84c4 */ \
|
||||
0x4c39159c, \
|
||||
0xcd90, \
|
||||
0x4dd3, \
|
||||
{0x97, 0xa7, 0x06, 0xaf, 0x5e, 0x6d, 0x84, 0xc4} \
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* netwerk/protocol/ftp/ classes
|
||||
*/
|
||||
|
@ -269,7 +269,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsFtpProtocolHandler, Init)
|
||||
#include "nsHttpDigestAuth.h"
|
||||
#include "nsHttpNTLMAuth.h"
|
||||
#include "nsHttpActivityDistributor.h"
|
||||
#include "ThrottleQueue.h"
|
||||
#undef LOG
|
||||
#undef LOG_ENABLED
|
||||
namespace mozilla {
|
||||
@ -282,7 +281,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpChannelAuthProvider)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpActivityDistributor)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpBasicAuth)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDigestAuth)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(ThrottleQueue)
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
#endif // !NECKO_PROTOCOL_http
|
||||
@ -796,7 +794,6 @@ NS_DEFINE_NAMED_CID(NS_HTTPNTLMAUTH_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_HTTPAUTHMANAGER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_HTTPCHANNELAUTHPROVIDER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_HTTPACTIVITYDISTRIBUTOR_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_THROTTLEQUEUE_CID);
|
||||
#endif // !NECKO_PROTOCOL_http
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
NS_DEFINE_NAMED_CID(NS_FTPPROTOCOLHANDLER_CID);
|
||||
@ -947,7 +944,6 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
|
||||
{ &kNS_HTTPAUTHMANAGER_CID, false, nullptr, mozilla::net::nsHttpAuthManagerConstructor },
|
||||
{ &kNS_HTTPCHANNELAUTHPROVIDER_CID, false, nullptr, mozilla::net::nsHttpChannelAuthProviderConstructor },
|
||||
{ &kNS_HTTPACTIVITYDISTRIBUTOR_CID, false, nullptr, mozilla::net::nsHttpActivityDistributorConstructor },
|
||||
{ &kNS_THROTTLEQUEUE_CID, false, nullptr, mozilla::net::ThrottleQueueConstructor },
|
||||
#endif // !NECKO_PROTOCOL_http
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
{ &kNS_FTPPROTOCOLHANDLER_CID, false, nullptr, nsFtpProtocolHandlerConstructor },
|
||||
@ -1109,7 +1105,6 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
|
||||
{ NS_HTTPAUTHMANAGER_CONTRACTID, &kNS_HTTPAUTHMANAGER_CID },
|
||||
{ NS_HTTPCHANNELAUTHPROVIDER_CONTRACTID, &kNS_HTTPCHANNELAUTHPROVIDER_CID },
|
||||
{ NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &kNS_HTTPACTIVITYDISTRIBUTOR_CID },
|
||||
{ NS_THROTTLEQUEUE_CONTRACTID, &kNS_THROTTLEQUEUE_CID },
|
||||
#endif // !NECKO_PROTOCOL_http
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
{ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &kNS_FTPPROTOCOLHANDLER_CID },
|
||||
|
@ -227,7 +227,6 @@ NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
|
||||
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -3442,28 +3441,6 @@ HttpBaseChannel::GetInnerDOMWindow()
|
||||
return innerWindow;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// HttpBaseChannel::nsIThrottledInputChannel
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
HttpBaseChannel::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue)
|
||||
{
|
||||
if (!XRE_IsParentProcess()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mThrottleQueue = aQueue;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HttpBaseChannel::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue)
|
||||
{
|
||||
*aQueue = mThrottleQueue;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
|
@ -43,7 +43,6 @@
|
||||
#include "nsISecurityConsoleMessage.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "mozilla/net/ChannelEventQueue.h"
|
||||
#include "nsIThrottledInputChannel.h"
|
||||
|
||||
class nsISecurityConsoleMessage;
|
||||
class nsIPrincipal;
|
||||
@ -80,7 +79,6 @@ class HttpBaseChannel : public nsHashPropertyBag
|
||||
, public nsITimedChannel
|
||||
, public nsIForcePendingChannel
|
||||
, public nsIConsoleReportCollector
|
||||
, public nsIThrottledInputChannel
|
||||
{
|
||||
protected:
|
||||
virtual ~HttpBaseChannel();
|
||||
@ -92,7 +90,6 @@ public:
|
||||
NS_DECL_NSIUPLOADCHANNEL2
|
||||
NS_DECL_NSITRACEABLECHANNEL
|
||||
NS_DECL_NSITIMEDCHANNEL
|
||||
NS_DECL_NSITHROTTLEDINPUTCHANNEL
|
||||
|
||||
HttpBaseChannel();
|
||||
|
||||
@ -387,8 +384,6 @@ protected:
|
||||
nsCOMPtr<nsIStreamListener> mCompressListener;
|
||||
|
||||
nsHttpRequestHead mRequestHead;
|
||||
// Upload throttling.
|
||||
nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
|
||||
nsCOMPtr<nsIInputStream> mUploadStream;
|
||||
nsCOMPtr<nsIRunnable> mUploadCloneableCallback;
|
||||
nsAutoPtr<nsHttpResponseHead> mResponseHead;
|
||||
|
@ -33,7 +33,6 @@
|
||||
#include "nsIEventTarget.h"
|
||||
#include "nsIHttpChannelInternal.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIThrottledInputChannel.h"
|
||||
#include "nsITransport.h"
|
||||
#include "nsIOService.h"
|
||||
#include "nsIRequestContext.h"
|
||||
@ -233,7 +232,6 @@ nsHttpTransaction::Init(uint32_t caps,
|
||||
MOZ_ASSERT(cinfo);
|
||||
MOZ_ASSERT(requestHead);
|
||||
MOZ_ASSERT(target);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
@ -381,25 +379,6 @@ nsHttpTransaction::Init(uint32_t caps,
|
||||
else
|
||||
mRequestStream = headers;
|
||||
|
||||
nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mChannel);
|
||||
nsIInputChannelThrottleQueue* queue;
|
||||
if (throttled) {
|
||||
rv = throttled->GetThrottleQueue(&queue);
|
||||
// In case of failure, just carry on without throttling.
|
||||
if (NS_SUCCEEDED(rv) && queue) {
|
||||
nsCOMPtr<nsIAsyncInputStream> wrappedStream;
|
||||
rv = queue->WrapStream(mRequestStream, getter_AddRefs(wrappedStream));
|
||||
// Failure to throttle isn't sufficient reason to fail
|
||||
// initialization
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
MOZ_ASSERT(wrappedStream != nullptr);
|
||||
LOG(("nsHttpTransaction::Init %p wrapping input stream using throttle queue %p\n",
|
||||
this, queue));
|
||||
mRequestStream = do_QueryInterface(wrappedStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t size_u64;
|
||||
rv = mRequestStream->Available(&size_u64);
|
||||
if (NS_FAILED(rv)) {
|
||||
|
@ -1,41 +0,0 @@
|
||||
// Test nsIThrottledInputChannel interface.
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
function test_handler(metadata, response) {
|
||||
const originalBody = "the response";
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(originalBody, originalBody.length);
|
||||
}
|
||||
|
||||
function make_channel(url) {
|
||||
return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
|
||||
.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let httpserver = new HttpServer();
|
||||
httpserver.start(-1);
|
||||
const PORT = httpserver.identity.primaryPort;
|
||||
|
||||
httpserver.registerPathHandler("/testdir", test_handler);
|
||||
|
||||
let channel = make_channel("http://localhost:" + PORT + "/testdir");
|
||||
|
||||
let tq = Cc["@mozilla.org/network/throttlequeue;1"]
|
||||
.createInstance(Ci.nsIInputChannelThrottleQueue);
|
||||
tq.init(1000, 1000);
|
||||
|
||||
let tic = channel.QueryInterface(Ci.nsIThrottledInputChannel);
|
||||
tic.throttleQueue = tq;
|
||||
|
||||
channel.asyncOpen2(new ChannelListener(() => {
|
||||
ok(tq.bytesProcessed() > 0, "throttled queue processed some bytes");
|
||||
|
||||
httpserver.stop(do_test_finished);
|
||||
}));
|
||||
|
||||
do_test_pending();
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
// Test ThrottleQueue initialization.
|
||||
|
||||
function init(tq, mean, max) {
|
||||
let threw = false;
|
||||
try {
|
||||
tq.init(mean, max);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
return !threw;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let tq = Cc["@mozilla.org/network/throttlequeue;1"]
|
||||
.createInstance(Ci.nsIInputChannelThrottleQueue);
|
||||
|
||||
ok(!init(tq, 0, 50), "mean bytes cannot be 0");
|
||||
ok(!init(tq, 50, 0), "max bytes cannot be 0");
|
||||
ok(!init(tq, 0, 0), "mean and max bytes cannot be 0");
|
||||
ok(!init(tq, 70, 20), "max cannot be less than mean");
|
||||
|
||||
ok(init(tq, 2, 2), "valid initialization");
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
// Test nsIThrottledInputChannel interface.
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
function test_handler(metadata, response) {
|
||||
const originalBody = "the response";
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(originalBody, originalBody.length);
|
||||
}
|
||||
|
||||
function make_channel(url) {
|
||||
return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
|
||||
.QueryInterface(Ci.nsIHttpChannel);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let httpserver = new HttpServer();
|
||||
httpserver.registerPathHandler("/testdir", test_handler);
|
||||
httpserver.start(-1);
|
||||
|
||||
const PORT = httpserver.identity.primaryPort;
|
||||
const size = 4096;
|
||||
|
||||
let sstream = Cc["@mozilla.org/io/string-input-stream;1"].
|
||||
createInstance(Ci.nsIStringInputStream);
|
||||
sstream.data = 'x'.repeat(size);
|
||||
|
||||
let mime = Cc["@mozilla.org/network/mime-input-stream;1"].
|
||||
createInstance(Ci.nsIMIMEInputStream);
|
||||
mime.addHeader("Content-Type", "multipart/form-data; boundary=zzzzz");
|
||||
mime.setData(sstream);
|
||||
mime.addContentLength = true;
|
||||
|
||||
let tq = Cc["@mozilla.org/network/throttlequeue;1"]
|
||||
.createInstance(Ci.nsIInputChannelThrottleQueue);
|
||||
// Make sure the request takes more than one read.
|
||||
tq.init(100 + size / 2, 100 + size / 2);
|
||||
|
||||
let channel = make_channel("http://localhost:" + PORT + "/testdir");
|
||||
channel.QueryInterface(Ci.nsIUploadChannel)
|
||||
.setUploadStream(mime, "", mime.available());
|
||||
channel.requestMethod = "POST";
|
||||
|
||||
let tic = channel.QueryInterface(Ci.nsIThrottledInputChannel);
|
||||
tic.throttleQueue = tq;
|
||||
|
||||
let startTime = Date.now();
|
||||
channel.asyncOpen2(new ChannelListener(() => {
|
||||
ok(Date.now() - startTime > 1000, "request took more than one second");
|
||||
|
||||
httpserver.stop(do_test_finished);
|
||||
}));
|
||||
|
||||
do_test_pending();
|
||||
}
|
@ -359,6 +359,3 @@ skip-if = os == "android"
|
||||
[test_bug464591.js]
|
||||
[test_cache-control_request.js]
|
||||
[test_bug1279246.js]
|
||||
[test_throttlequeue.js]
|
||||
[test_throttlechannel.js]
|
||||
[test_throttling.js]
|
||||
|
Loading…
Reference in New Issue
Block a user