Backed out 6 changesets (bug 1244227) for m(cl) failures

Backed out changeset 0cf1259b7073 (bug 1244227)
Backed out changeset d5866b9dd3d9 (bug 1244227)
Backed out changeset 49b5309e3415 (bug 1244227)
Backed out changeset 33bc49f015a7 (bug 1244227)
Backed out changeset f680f6460f07 (bug 1244227)
Backed out changeset 5a4bb3258978 (bug 1244227)
This commit is contained in:
Wes Kocher 2016-07-22 13:32:37 -07:00
parent 9ab8638548
commit 6517ee3462
26 changed files with 147 additions and 1568 deletions

View File

@ -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]

View File

@ -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);
});

View File

@ -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>

View File

@ -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]

View File

@ -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();
});

View File

@ -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>

View File

@ -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

View File

@ -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") {
if (key == "NetworkMonitor.saveRequestAndResponseBodies" &&
this.networkMonitor) {
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];
}
this.networkMonitorChild.saveRequestAndResponseBodies = this._prefs[key];
}
}
}

View File

@ -15,7 +15,6 @@ DevToolsModules(
'network-monitor.js',
'server-logger-monitor.js',
'server-logger.js',
'throttle.js',
'utils.js',
'worker-utils.js',
)

View File

@ -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);
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 {
this._dispatchActivity(httpActivity, channel, activityType,
activitySubtype, timestamp, extraSizeData,
extraStringData);
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;

View File

@ -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");
});

View File

@ -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]

View File

@ -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;
},
};

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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',
]

View File

@ -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;
};

View File

@ -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
*/

View File

@ -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 },

View File

@ -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

View File

@ -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;

View File

@ -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)) {

View File

@ -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();
}

View File

@ -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");
}

View File

@ -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();
}

View File

@ -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]