forked from mirrors/gecko-dev
Bug 1552325 - Implement Network.requestWillBeSent. r=remote-protocol-reviewers,ato
Differential Revision: https://phabricator.services.mozilla.com/D36008 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
49180a060f
commit
7c4f974fe1
10 changed files with 274 additions and 65 deletions
|
|
@ -15,7 +15,6 @@ XPCOMUtils.defineLazyModuleGetters(ContentProcessDomains, {
|
|||
Emulation: "chrome://remote/content/domains/content/Emulation.jsm",
|
||||
Input: "chrome://remote/content/domains/content/Input.jsm",
|
||||
Log: "chrome://remote/content/domains/content/Log.jsm",
|
||||
Network: "chrome://remote/content/domains/content/Network.jsm",
|
||||
Page: "chrome://remote/content/domains/content/Page.jsm",
|
||||
Performance: "chrome://remote/content/domains/content/Performance.jsm",
|
||||
Runtime: "chrome://remote/content/domains/content/Runtime.jsm",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const ParentProcessDomains = {};
|
|||
XPCOMUtils.defineLazyModuleGetters(ParentProcessDomains, {
|
||||
Browser: "chrome://remote/content/domains/parent/Browser.jsm",
|
||||
Input: "chrome://remote/content/domains/parent/Input.jsm",
|
||||
Network: "chrome://remote/content/domains/parent/Network.jsm",
|
||||
Page: "chrome://remote/content/domains/parent/Page.jsm",
|
||||
Target: "chrome://remote/content/domains/parent/Target.jsm",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["Network"];
|
||||
|
||||
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm");
|
||||
|
||||
class Network extends ContentProcessDomain {
|
||||
constructor(session) {
|
||||
super(session);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
destructor() {
|
||||
this.disable();
|
||||
|
||||
super.destructor();
|
||||
}
|
||||
|
||||
// commands
|
||||
|
||||
async enable() {
|
||||
if (!this.enabled) {
|
||||
this.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (this.enabled) {
|
||||
this.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
132
remote/domains/parent/Network.jsm
Normal file
132
remote/domains/parent/Network.jsm
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/* 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";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["Network"];
|
||||
|
||||
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm");
|
||||
const {NetworkObserver} = ChromeUtils.import("chrome://remote/content/domains/parent/network/NetworkObserver.jsm");
|
||||
|
||||
const LOAD_CAUSE_STRINGS = {
|
||||
[Ci.nsIContentPolicy.TYPE_INVALID]: "Invalid",
|
||||
[Ci.nsIContentPolicy.TYPE_OTHER]: "Other",
|
||||
[Ci.nsIContentPolicy.TYPE_SCRIPT]: "Script",
|
||||
[Ci.nsIContentPolicy.TYPE_IMAGE]: "Img",
|
||||
[Ci.nsIContentPolicy.TYPE_STYLESHEET]: "Stylesheet",
|
||||
[Ci.nsIContentPolicy.TYPE_OBJECT]: "Object",
|
||||
[Ci.nsIContentPolicy.TYPE_DOCUMENT]: "Document",
|
||||
[Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "Subdocument",
|
||||
[Ci.nsIContentPolicy.TYPE_REFRESH]: "Refresh",
|
||||
[Ci.nsIContentPolicy.TYPE_XBL]: "Xbl",
|
||||
[Ci.nsIContentPolicy.TYPE_PING]: "Ping",
|
||||
[Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "Xhr",
|
||||
[Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "ObjectSubdoc",
|
||||
[Ci.nsIContentPolicy.TYPE_DTD]: "Dtd",
|
||||
[Ci.nsIContentPolicy.TYPE_FONT]: "Font",
|
||||
[Ci.nsIContentPolicy.TYPE_MEDIA]: "Media",
|
||||
[Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "Websocket",
|
||||
[Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "Csp",
|
||||
[Ci.nsIContentPolicy.TYPE_XSLT]: "Xslt",
|
||||
[Ci.nsIContentPolicy.TYPE_BEACON]: "Beacon",
|
||||
[Ci.nsIContentPolicy.TYPE_FETCH]: "Fetch",
|
||||
[Ci.nsIContentPolicy.TYPE_IMAGESET]: "Imageset",
|
||||
[Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "WebManifest",
|
||||
};
|
||||
|
||||
class Network extends Domain {
|
||||
constructor(session) {
|
||||
super(session);
|
||||
this.enabled = false;
|
||||
|
||||
this._onRequest = this._onRequest.bind(this);
|
||||
}
|
||||
|
||||
destructor() {
|
||||
this.disable();
|
||||
|
||||
super.destructor();
|
||||
}
|
||||
|
||||
enable() {
|
||||
if (this.enabled) {
|
||||
return;
|
||||
}
|
||||
this.enabled = true;
|
||||
this._networkObserver = new NetworkObserver();
|
||||
const { browser } = this.session.target;
|
||||
this._networkObserver.startTrackingBrowserNetwork(browser);
|
||||
this._networkObserver.on("request", this._onRequest);
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
const { browser } = this.session.target;
|
||||
this._networkObserver.stopTrackingBrowserNetwork(browser);
|
||||
this._networkObserver.off("request", this._onRequest);
|
||||
this._networkObserver.dispose();
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
_onRequest(eventName, httpChannel, data) {
|
||||
const topFrame = getLoadContext(httpChannel).topFrameElement;
|
||||
const request = {
|
||||
url: httpChannel.URI.spec,
|
||||
urlFragment: undefined,
|
||||
method: httpChannel.requestMethod,
|
||||
headers: [],
|
||||
postData: undefined,
|
||||
hasPostData: false,
|
||||
mixedContentType: undefined,
|
||||
initialPriority: undefined,
|
||||
referrerPolicy: undefined,
|
||||
isLinkPreload: false,
|
||||
};
|
||||
let loaderId = undefined;
|
||||
let causeType = Ci.nsIContentPolicy.TYPE_OTHER;
|
||||
let causeUri = topFrame.currentURI.spec;
|
||||
if (httpChannel.loadInfo) {
|
||||
causeType = httpChannel.loadInfo.externalContentPolicyType;
|
||||
const { loadingPrincipal } = httpChannel.loadInfo;
|
||||
if (loadingPrincipal && loadingPrincipal.URI) {
|
||||
causeUri = loadingPrincipal.URI.spec;
|
||||
}
|
||||
if (causeType == Ci.nsIContentPolicy.TYPE_DOCUMENT) {
|
||||
// Puppeteer expect this specialy of CDP where loaderId = requestId
|
||||
// for the toplevel document request
|
||||
loaderId = String(httpChannel.channelId);
|
||||
}
|
||||
}
|
||||
this.emit("Network.requestWillBeSent", {
|
||||
requestId: String(httpChannel.channelId),
|
||||
loaderId,
|
||||
documentURL: causeUri,
|
||||
request,
|
||||
timestamp: undefined,
|
||||
wallTime: undefined,
|
||||
initiator: undefined,
|
||||
redirectResponse: undefined,
|
||||
type: LOAD_CAUSE_STRINGS[causeType] || "unknown",
|
||||
frameId: topFrame.outerWindowID,
|
||||
hasUserGesture: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getLoadContext(httpChannel) {
|
||||
let loadContext = null;
|
||||
try {
|
||||
if (httpChannel.notificationCallbacks) {
|
||||
loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (!loadContext && httpChannel.loadGroup) {
|
||||
loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
||||
}
|
||||
} catch (e) { }
|
||||
return loadContext;
|
||||
}
|
||||
|
|
@ -5,14 +5,12 @@
|
|||
"use strict";
|
||||
|
||||
const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
|
||||
const {Helper} = ChromeUtils.import("chrome://juggler/content/Helper.js");
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
|
||||
const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js");
|
||||
|
||||
const Cm = Components.manager;
|
||||
const CC = Components.Constructor;
|
||||
const helper = new Helper();
|
||||
|
||||
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream");
|
||||
const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream", "setOutputStream");
|
||||
|
|
@ -35,17 +33,6 @@ const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
|
|||
const SINK_CATEGORY_NAME = "net-channel-event-sinks";
|
||||
|
||||
class NetworkObserver {
|
||||
static instance() {
|
||||
return NetworkObserver._instance || null;
|
||||
}
|
||||
|
||||
static initialize() {
|
||||
if (NetworkObserver._instance) {
|
||||
return;
|
||||
}
|
||||
NetworkObserver._instance = new NetworkObserver();
|
||||
}
|
||||
|
||||
constructor() {
|
||||
EventEmitter.decorate(this);
|
||||
this._browserSessionCount = new Map();
|
||||
|
|
@ -74,12 +61,13 @@ class NetworkObserver {
|
|||
this._extraHTTPHeaders = new Map();
|
||||
this._browserResponseStorages = new Map();
|
||||
|
||||
this._eventListeners = [
|
||||
helper.addObserver(this._onRequest.bind(this), "http-on-modify-request"),
|
||||
helper.addObserver(this._onResponse.bind(this, false /* fromCache */), "http-on-examine-response"),
|
||||
helper.addObserver(this._onResponse.bind(this, true /* fromCache */), "http-on-examine-cached-response"),
|
||||
helper.addObserver(this._onResponse.bind(this, true /* fromCache */), "http-on-examine-merged-response"),
|
||||
];
|
||||
this._onRequest = this._onRequest.bind(this);
|
||||
this._onExamineResponse = this._onResponse.bind(this, false /* fromCache */);
|
||||
this._onCachedResponse = this._onResponse.bind(this, true /* fromCache */);
|
||||
Services.obs.addObserver(this._onRequest, "http-on-modify-request");
|
||||
Services.obs.addObserver(this._onExamineResponse, "http-on-examine-response");
|
||||
Services.obs.addObserver(this._onCachedResponse, "http-on-examine-cached-response");
|
||||
Services.obs.addObserver(this._onCachedResponse, "http-on-examine-merged-response");
|
||||
}
|
||||
|
||||
setExtraHTTPHeaders(browser, headers) {
|
||||
|
|
@ -152,7 +140,7 @@ class NetworkObserver {
|
|||
httpChannel.resume();
|
||||
this.emit("requestfailed", httpChannel, {
|
||||
requestId: requestId(httpChannel),
|
||||
errorCode: helper.getNetworkErrorStatusText(httpChannel.status),
|
||||
errorCode: getNetworkErrorStatusText(httpChannel.status),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -294,7 +282,10 @@ class NetworkObserver {
|
|||
const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.unregisterFactory(SINK_CLASS_ID, this._channelSinkFactory);
|
||||
Services.catMan.deleteCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID, false);
|
||||
helper.removeListeners(this._eventListeners);
|
||||
Services.obs.removeObserver(this._onRequest, "http-on-modify-request");
|
||||
Services.obs.removeObserver(this._onExamineResponse, "http-on-examine-response");
|
||||
Services.obs.removeObserver(this._onCachedResponse, "http-on-examine-cached-response");
|
||||
Services.obs.removeObserver(this._onCachedResponse, "http-on-examine-merged-response");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -459,7 +450,7 @@ class ResponseBodyListener {
|
|||
this.originalListener = httpChannel.setNewListener(this);
|
||||
}
|
||||
|
||||
onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) {
|
||||
onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
|
||||
const iStream = new BinaryInputStream(aInputStream);
|
||||
const sStream = new StorageStream(8192, aCount, null);
|
||||
const oStream = new BinaryOutputStream(sStream.getOutputStream(0));
|
||||
|
|
@ -469,20 +460,75 @@ class ResponseBodyListener {
|
|||
this._chunks.push(data);
|
||||
|
||||
oStream.writeBytes(data, aCount);
|
||||
this.originalListener.onDataAvailable(aRequest, aContext, sStream.newInputStream(0), aOffset, aCount);
|
||||
this.originalListener.onDataAvailable(aRequest, sStream.newInputStream(0), aOffset, aCount);
|
||||
}
|
||||
|
||||
onStartRequest(aRequest, aContext) {
|
||||
this.originalListener.onStartRequest(aRequest, aContext);
|
||||
onStartRequest(aRequest) {
|
||||
this.originalListener.onStartRequest(aRequest);
|
||||
}
|
||||
|
||||
onStopRequest(aRequest, aContext, aStatusCode) {
|
||||
this.originalListener.onStopRequest(aRequest, aContext, aStatusCode);
|
||||
onStopRequest(aRequest, aStatusCode) {
|
||||
this.originalListener.onStopRequest(aRequest, aStatusCode);
|
||||
const body = this._chunks.join("");
|
||||
delete this._chunks;
|
||||
this._networkObserver._onResponseFinished(this._browser, this._httpChannel, body);
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkErrorStatusText(status) {
|
||||
if (!status)
|
||||
return null;
|
||||
for (const key of Object.keys(Cr)) {
|
||||
if (Cr[key] === status)
|
||||
return key;
|
||||
}
|
||||
// Security module. The following is taken from
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/How_to_check_the_secruity_state_of_an_XMLHTTPRequest_over_SSL
|
||||
if ((status & 0xff0000) === 0x5a0000) {
|
||||
// NSS_SEC errors (happen below the base value because of negative vals)
|
||||
if ((status & 0xffff) < Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
|
||||
// The bases are actually negative, so in our positive numeric space, we
|
||||
// need to subtract the base off our value.
|
||||
const nssErr = Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (status & 0xffff);
|
||||
switch (nssErr) {
|
||||
case 11:
|
||||
return "SEC_ERROR_EXPIRED_CERTIFICATE";
|
||||
case 12:
|
||||
return "SEC_ERROR_REVOKED_CERTIFICATE";
|
||||
case 13:
|
||||
return "SEC_ERROR_UNKNOWN_ISSUER";
|
||||
case 20:
|
||||
return "SEC_ERROR_UNTRUSTED_ISSUER";
|
||||
case 21:
|
||||
return "SEC_ERROR_UNTRUSTED_CERT";
|
||||
case 36:
|
||||
return "SEC_ERROR_CA_CERT_INVALID";
|
||||
case 90:
|
||||
return "SEC_ERROR_INADEQUATE_KEY_USAGE";
|
||||
case 176:
|
||||
return "SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED";
|
||||
default:
|
||||
return "SEC_ERROR_UNKNOWN";
|
||||
}
|
||||
}
|
||||
const sslErr = Math.abs(Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (status & 0xffff);
|
||||
switch (sslErr) {
|
||||
case 3:
|
||||
return "SSL_ERROR_NO_CERTIFICATE";
|
||||
case 4:
|
||||
return "SSL_ERROR_BAD_CERTIFICATE";
|
||||
case 8:
|
||||
return "SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE";
|
||||
case 9:
|
||||
return "SSL_ERROR_UNSUPPORTED_VERSION";
|
||||
case 12:
|
||||
return "SSL_ERROR_BAD_CERT_DOMAIN";
|
||||
default:
|
||||
return "SSL_ERROR_UNKNOWN";
|
||||
}
|
||||
}
|
||||
return "<unknown error>";
|
||||
}
|
||||
|
||||
var EXPORTED_SYMBOLS = ["NetworkObserver"];
|
||||
this.NetworkObserver = NetworkObserver;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ remote.jar:
|
|||
content/domains/content/Emulation.jsm (domains/content/Emulation.jsm)
|
||||
content/domains/content/Input.jsm (domains/content/Input.jsm)
|
||||
content/domains/content/Log.jsm (domains/content/Log.jsm)
|
||||
content/domains/content/Network.jsm (domains/content/Network.jsm)
|
||||
content/domains/content/Page.jsm (domains/content/Page.jsm)
|
||||
content/domains/content/Performance.jsm (domains/content/Performance.jsm)
|
||||
content/domains/content/Runtime.jsm (domains/content/Runtime.jsm)
|
||||
|
|
@ -47,6 +46,8 @@ remote.jar:
|
|||
content/domains/content/Security.jsm (domains/content/Security.jsm)
|
||||
content/domains/parent/Browser.jsm (domains/parent/Browser.jsm)
|
||||
content/domains/parent/Input.jsm (domains/parent/Input.jsm)
|
||||
content/domains/parent/Network.jsm (domains/parent/Network.jsm)
|
||||
content/domains/parent/network/NetworkObserver.jsm (domains/parent/network/NetworkObserver.jsm)
|
||||
content/domains/parent/Page.jsm (domains/parent/Page.jsm)
|
||||
content/domains/parent/Target.jsm (domains/parent/Target.jsm)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@ subsuite = remote
|
|||
prefs = remote.enabled=true
|
||||
support-files =
|
||||
chrome-remote-interface.js
|
||||
doc_network_requestWillBeSent.html
|
||||
doc_page_frameNavigated.html
|
||||
file_network_requestWillBeSent.js
|
||||
head.js
|
||||
|
||||
[browser_cdp.js]
|
||||
[browser_input_dispatchKeyEvent.js]
|
||||
[browser_main_target.js]
|
||||
[browser_network_requestWillBeSent.js]
|
||||
[browser_page_bringToFront.js]
|
||||
[browser_page_frameNavigated.js]
|
||||
[browser_page_runtime_events.js]
|
||||
|
|
|
|||
52
remote/test/browser/browser_network_requestWillBeSent.js
Normal file
52
remote/test/browser/browser_network_requestWillBeSent.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the Network.requestWillBeSent event
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
|
||||
const PAGE_URI = "http://example.com/browser/remote/test/browser/doc_network_requestWillBeSent.html";
|
||||
const JS_URI = "http://example.com/browser/remote/test/browser/file_network_requestWillBeSent.js";
|
||||
|
||||
add_task(async function() {
|
||||
const {client} = await setupTestForUri(TEST_URI);
|
||||
|
||||
const {Page, Network} = client;
|
||||
|
||||
await Network.enable();
|
||||
ok(true, "Network domain has been enabled");
|
||||
|
||||
let requests = 0;
|
||||
const onRequests = new Promise(resolve => {
|
||||
Network.requestWillBeSent(event => {
|
||||
ok(true, "Received a request");
|
||||
switch (++requests) {
|
||||
case 1:
|
||||
is(event.request.url, PAGE_URI, "Got the page request");
|
||||
is(event.type, "Document", "The page request has 'Document' type");
|
||||
is(event.requestId, event.loaderId, "The page request has requestId = loaderId (puppeteer assumes that to detect the page start request)");
|
||||
break;
|
||||
case 2:
|
||||
is(event.request.url, JS_URI, "Got the JS request");
|
||||
resolve();
|
||||
break;
|
||||
case 3:
|
||||
ok(false, "Expect only two requests");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const { frameId } = await Page.navigate({ url: PAGE_URI });
|
||||
ok(frameId, "Page.navigate returned a frameId");
|
||||
|
||||
info("Wait for Network.requestWillBeSent events");
|
||||
await onRequests;
|
||||
|
||||
await client.close();
|
||||
ok(true, "The client is closed");
|
||||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
await RemoteAgent.close();
|
||||
});
|
||||
9
remote/test/browser/doc_network_requestWillBeSent.html
Normal file
9
remote/test/browser/doc_network_requestWillBeSent.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test page for requestWillBeSent</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="file_network_requestWillBeSent.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
2
remote/test/browser/file_network_requestWillBeSent.js
Normal file
2
remote/test/browser/file_network_requestWillBeSent.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// Test file to emit Network.requestWillBeSent events.
|
||||
var foo = true;
|
||||
Loading…
Reference in a new issue