--- a/toolkit/components/extensions/schemas/web_request.json
+++ b/toolkit/components/extensions/schemas/web_request.json
@@ -68,37 +68,37 @@
{
"id": "OnSendHeadersOptions",
"type": "string",
"enum": ["requestHeaders"]
},
{
"id": "OnHeadersReceivedOptions",
"type": "string",
- "enum": ["blocking", "responseHeaders"]
+ "enum": ["blocking", "responseHeaders", "securityInfo"]
},
{
"id": "OnAuthRequiredOptions",
"type": "string",
- "enum": ["responseHeaders", "blocking", "asyncBlocking"]
+ "enum": ["responseHeaders", "blocking", "asyncBlocking", "securityInfo"]
},
{
"id": "OnResponseStartedOptions",
"type": "string",
- "enum": ["responseHeaders"]
+ "enum": ["responseHeaders", "securityInfo"]
},
{
"id": "OnBeforeRedirectOptions",
"type": "string",
- "enum": ["responseHeaders"]
+ "enum": ["responseHeaders", "securityInfo"]
},
{
"id": "OnCompletedOptions",
"type": "string",
- "enum": ["responseHeaders"]
+ "enum": ["responseHeaders", "securityInfo"]
},
{
"id": "RequestFilter",
"type": "object",
"description": "An object describing filters to apply to webRequest events.",
"properties": {
"urls": {
"type": "array",
@@ -172,16 +172,159 @@
"properties": {
"username": {"type": "string"},
"password": {"type": "string"}
}
}
}
},
{
+ "id": "CertificateInfo",
+ "type": "object",
+ "description": "Contains the certificate properties of the request if it is a secure request.",
+ "properties": {
+ "subject": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "commonName": { "type": "string" },
+ "organization": { "type": "string" },
+ "organizationalUnit": { "type": "string" }
+ }
+ },
+ "issuer": {
+ "type": "object",
+ "properties": {
+ "commonName": { "type": "string" },
+ "organization": { "type": "string" },
+ "organizationalUnit": { "type": "string" }
+ }
+ },
+ "validity": {
+ "type": "object",
+ "description": "Contains start and end dates in GMT.",
+ "properties": {
+ "startGMT": { "type": "string" },
+ "endGMT": { "type": "string" }
+ }
+ },
+ "fingerprint": {
+ "type": "object",
+ "properties": {
+ "sha1": { "type": "string" },
+ "sha256": { "type": "string" }
+ }
+ },
+ "serialNumber": {
+ "type": "string"
+ },
+ "isBuiltInRoot": {
+ "type": "boolean"
+ },
+ "isSelfSigned": {
+ "type": "boolean"
+ },
+ "certType": {
+ "type": "string",
+ "enum": [ "unknown", "ca", "user", "email", "server", "any" ]
+ },
+ "sha256SubjectPublicKeyInfoDigest": {
+ "type": "string"
+ },
+ "keyUsages": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "id": "SecurityInfo",
+ "type": "object",
+ "description": "Contains the security properties of the request (ie. SSL/TLS information).",
+ "properties": {
+ "state": {
+ "type": "string",
+ "enum": [
+ "insecure",
+ "weak",
+ "broken",
+ "secure"
+ ]
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Error message if state is \"broken\"",
+ "optional": true
+ },
+ "protocolVersion": {
+ "type": "string",
+ "description": "Protocol version if state is \"secure\"",
+ "enum": [
+ "TLSv1",
+ "TLSv1.1",
+ "TLSv1.2",
+ "TLSv1.3",
+ "unknown"
+ ],
+ "optional": true
+ },
+ "cipherSuite": {
+ "type": "string",
+ "description": "The cipher suite used in this request if state is \"secure\".",
+ "optional": true
+ },
+ "certificates": {
+ "description": "Certificate data if state is \"secure\".",
+ "type": "array",
+ "items": { "$ref": "CertificateInfo" },
+ "optional": true
+ },
+ "isDomainMismatch": {
+ "type": "boolean",
+ "optional": true
+ },
+ "isExtendedValidation": {
+ "type": "boolean",
+ "optional": true
+ },
+ "isNotValidAtThisTime": {
+ "type": "boolean",
+ "optional": true
+ },
+ "isUntrusted": {
+ "type": "boolean",
+ "optional": true
+ },
+ "certificateTransparencyStatus": {
+ "type": "string",
+ "enum": [
+ "not_applicable",
+ "policy_compliant",
+ "policy_not_enough_scts",
+ "policy_not_diverse_scts"
+ ]
+ },
+ "hsts": {
+ "type": "boolean",
+ "description": "True if host uses Strict Transport Security and state is \"secure\".",
+ "optional": true
+ },
+ "hpkp": {
+ "type": "string",
+ "description": "True if host uses Public Key Pinning and state is \"secure\".",
+ "optional": true
+ },
+ "weaknessReasons": {
+ "type": "array",
+ "items": { "type": "string" },
+ "description": "list of reasons that cause the request to be considered weak, if state is \"weak\"",
+ "optional": true
+ }
+ }
+ },
+ {
"id": "UploadData",
"type": "object",
"properties": {
"bytes": {
"type": "any",
"optional": true,
"description": "An ArrayBuffer with a copy of the data."
},
@@ -397,16 +540,17 @@
"parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
"originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
"documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
"tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
"type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
"timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
"statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line)."},
"responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that have been received with this response."},
+ "securityInfo": {"$ref": "SecurityInfo", "optional": true, "description": "Security information for this request."},
"statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}
}
}
],
"extraParameters": [
{
"$ref": "RequestFilter",
"name": "filter",
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html
@@ -21,31 +21,35 @@ function getExtension() {
}, {urls}, ["blocking"]);
browser.webRequest.onBeforeSendHeaders.addListener(details => {
browser.test.assertEq(expect.shift(), "onBeforeSendHeaders");
}, {urls}, ["blocking", "requestHeaders"]);
browser.webRequest.onSendHeaders.addListener(details => {
browser.test.assertEq(expect.shift(), "onSendHeaders");
}, {urls}, ["requestHeaders"]);
browser.webRequest.onHeadersReceived.addListener(details => {
+ browser.test.log(`**** securityInfo ${details.securityInfo && JSON.stringify(details.securityInfo)}`);
browser.test.assertEq(expect.shift(), "onHeadersReceived");
+ browser.test.assertEq(details.url.startsWith("https"),
+ details.securityInfo && details.securityInfo.state == "secure",
+ "security info reflects https");
let headers = details.responseHeaders || [];
for (let header of headers) {
if (header.name.toLowerCase() === "strict-transport-security") {
return;
}
}
headers.push({
name: "Strict-Transport-Security",
value: "max-age=31536000000",
});
return {responseHeaders: headers};
- }, {urls}, ["blocking", "responseHeaders"]);
+ }, {urls}, ["blocking", "responseHeaders", "securityInfo"]);
browser.webRequest.onBeforeRedirect.addListener(details => {
browser.test.assertEq(expect.shift(), "onBeforeRedirect");
}, {urls});
browser.webRequest.onResponseStarted.addListener(details => {
browser.test.assertEq(expect.shift(), "onResponseStarted");
}, {urls});
browser.webRequest.onCompleted.addListener(details => {
browser.test.assertEq(expect.shift(), "onCompleted");
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/addons/SecurityInfo.jsm
@@ -0,0 +1,349 @@
+/* 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 EXPORTED_SYMBOLS = ["SecurityInfo"];
+
+const {interfaces: Ci, classes: Cc} = Components;
+
+// NOTE: SecurityInfo is largly copied from the devtools NetworkHelper with some
+// minor differences.
+
+const SecurityInfo = {
+ /**
+ * Extracts security information from nsIChannel.securityInfo.
+ *
+ * @param {nsIChannel} channel
+ * If null channel is assumed to be insecure.
+ *
+ * @returns {Object}
+ * Returns an object containing following members:
+ * - state: The security of the connection used to fetch this
+ * request. Has one of following string values:
+ * * "insecure": the connection was not secure (only http)
+ * * "weak": the connection has minor security issues
+ * * "broken": secure connection failed (e.g. expired cert)
+ * * "secure": the connection was properly secured.
+ * If state == broken:
+ * - errorMessage: full error message from
+ * nsITransportSecurityInfo.
+ * If state == secure:
+ * - protocolVersion: one of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3.
+ * - cipherSuite: the cipher suite used in this connection.
+ * - cert: information about certificate used in this connection.
+ * See parseCertificateInfo for the contents.
+ * - hsts: true if host uses Strict Transport Security,
+ * false otherwise
+ * - hpkp: true if host uses Public Key Pinning, false otherwise
+ * If state == weak: Same as state == secure and
+ * - weaknessReasons: list of reasons that cause the request to be
+ * considered weak. See getReasonsForWeakness.
+ */
+ parseSecurityInfo(channel) {
+ const info = {
+ state: "insecure",
+ };
+
+ if (!channel) {
+ return info;
+ }
+
+ /**
+ * Different scenarios to consider here and how they are handled:
+ * - request is HTTP, the connection is not secure
+ * => securityInfo is null
+ * => state === "insecure"
+ *
+ * - request is HTTPS, the connection is secure
+ * => .securityState has STATE_IS_SECURE flag
+ * => state === "secure"
+ *
+ * - request is HTTPS, the connection has security issues
+ * => .securityState has STATE_IS_INSECURE flag
+ * => .errorCode is an NSS error code.
+ * => state === "broken"
+ *
+ * - request is HTTPS, the connection was terminated before the security
+ * could be validated
+ * => .securityState has STATE_IS_INSECURE flag
+ * => .errorCode is NOT an NSS error code.
+ * => .errorMessage is not available.
+ * => state === "insecure"
+ *
+ * - request is HTTPS but it uses a weak cipher or old protocol, see
+ * https://hg.mozilla.org/mozilla-central/annotate/def6ed9d1c1a/
+ * security/manager/ssl/nsNSSCallbacks.cpp#l1233
+ * - request is mixed content (which makes no sense whatsoever)
+ * => .securityState has STATE_IS_BROKEN flag
+ * => .errorCode is NOT an NSS error code
+ * => .errorMessage is not available
+ * => state === "weak"
+ */
+
+
+ let securityInfo = channel.securityInfo;
+ if (!securityInfo) {
+ return info;
+ }
+
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ securityInfo.QueryInterface(Ci.nsISSLStatusProvider);
+
+ const wpl = Ci.nsIWebProgressListener;
+ const NSSErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+ const SSLStatus = securityInfo.SSLStatus;
+ if (!NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
+ const state = securityInfo.securityState;
+
+ let uri = null;
+ if (channel.URI) {
+ uri = channel.URI;
+ }
+ if (uri && !uri.schemeIs("https") && !uri.schemeIs("wss")) {
+ // it is not enough to look at the transport security info -
+ // schemes other than https and wss are subject to
+ // downgrade/etc at the scheme level and should always be
+ // considered insecure
+ info.state = "insecure";
+ } else if (state & wpl.STATE_IS_SECURE) {
+ // The connection is secure if the scheme is sufficient
+ info.state = "secure";
+ } else if (state & wpl.STATE_IS_BROKEN) {
+ // The connection is not secure, there was no error but there's some
+ // minor security issues.
+ info.state = "weak";
+ info.weaknessReasons = this.getReasonsForWeakness(state);
+ } else if (state & wpl.STATE_IS_INSECURE) {
+ // This was most likely an https request that was aborted before
+ // validation. Return info as info.state = insecure.
+ return info;
+ } else {
+ // No known STATE_IS_* flags.
+ return info;
+ }
+
+ // Cipher suite.
+ info.cipherSuite = SSLStatus.cipherName;
+
+ // Key exchange group name.
+ info.keaGroupName = SSLStatus.keaGroupName;
+
+ // Certificate signature scheme.
+ info.signatureSchemeName = SSLStatus.signatureSchemeName;
+
+ info.isDomainMismatch = SSLStatus.isDomainMismatch;
+ info.isExtendedValidation = SSLStatus.isExtendedValidation;
+ info.isNotValidAtThisTime = SSLStatus.isNotValidAtThisTime;
+ info.isUntrusted = SSLStatus.isUntrusted;
+
+ info.certificateTransparencyStatus = this.getTransparencyStatus(SSLStatus.certificateTransparencyStatus);
+
+ // Protocol version.
+ info.protocolVersion =
+ this.formatSecurityProtocol(SSLStatus.protocolVersion);
+
+ let certificates = [];
+ let certChain = SSLStatus.serverCert.getChain();
+ for (let i = 0; i < certChain.length; i++) {
+ let cert = certChain.queryElementAt(i, Ci.nsIX509Cert);
+ certificates.push(this.parseCertificateInfo(cert));
+ }
+ info.certificates = certificates;
+
+ // HSTS and HPKP if available.
+ if (uri && uri.host) {
+ const sss = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+
+ // SiteSecurityService uses different storage if the channel is
+ // private. Thus we must give isSecureURI correct flags or we
+ // might get incorrect results.
+ channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ let flags = (channel.isChannelPrivate) ?
+ Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
+
+ info.hsts = sss.isSecureURI(sss.HEADER_HSTS, uri, flags);
+ info.hpkp = sss.isSecureURI(sss.HEADER_HPKP, uri, flags);
+ } else {
+ info.hsts = false;
+ info.hpkp = false;
+ }
+ } else {
+ // The connection failed.
+ info.state = "broken";
+ info.errorMessage = securityInfo.errorMessage;
+ }
+
+ return info;
+ },
+
+ /**
+ * Takes an nsIX509Cert and returns an object with certificate information.
+ *
+ * @param {nsIX509Cert} cert
+ * The certificate to extract the information from.
+ * @returns {Object}
+ * An object with following format:
+ * {
+ * subject: { commonName, organization, organizationalUnit },
+ * issuer: { commonName, organization, organizationUnit },
+ * validity: { start, end },
+ * fingerprint: { sha1, sha256 }
+ * }
+ */
+ parseCertificateInfo(cert) {
+ if (!cert) {
+ return;
+ }
+
+ let rawDER = cert.getRawDER({});
+
+ return {
+ subject: {
+ name: cert.subjectName,
+ commonName: cert.commonName,
+ organization: cert.organization,
+ organizationalUnit: cert.organizationalUnit,
+ },
+ issuer: {
+ commonName: cert.issuerCommonName,
+ organization: cert.issuerOrganization,
+ organizationUnit: cert.issuerOrganizationUnit,
+ },
+ validity: {
+ startGMT: cert.validity.notBeforeGMT,
+ endGMT: cert.validity.notAfterGMT,
+ },
+ fingerprint: {
+ sha1: cert.sha1Fingerprint,
+ sha256: cert.sha256Fingerprint,
+ },
+ serialNumber: cert.serialNumber,
+ isBuiltInRoot: cert.isBuiltInRoot,
+ certType: this.getCertType(cert.certType),
+ isSelfSigned: cert.isSelfSigned,
+ sha256SubjectPublicKeyInfoDigest: cert.sha256SubjectPublicKeyInfoDigest,
+ keyUsages: cert.keyUsages,
+ ASN1Objects: this.getCertASN1Structure(cert.ASN1Structure),
+ rawDER,
+ };
+ },
+
+ getTransparencyStatus(status) {
+ switch (status) {
+ case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE: return "not_applicable";
+ case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT: return "policy_compliant";
+ case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS: return "policy_not_enough_scts";
+ case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS: return "policy_not_diverse_scts";
+ }
+ return "unknown";
+ },
+
+ getCertType(type) {
+ switch (type) {
+ case Ci.nsIX509Cert.CA_CERT: return "ca";
+ case Ci.nsIX509Cert.USER_CERT: return "user";
+ case Ci.nsIX509Cert.EMAIL_CERT: return "email";
+ case Ci.nsIX509Cert.SERVER_CERT: return "server";
+ case Ci.nsIX509Cert.ANY_CERT: return "any";
+ }
+ return "unknown";
+ },
+
+ /**
+ * Takes protocolVersion of SSLStatus object and returns human readable
+ * description.
+ *
+ * @param {number} version
+ * One of nsISSLStatus version constants.
+ * @returns {string}
+ * One of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 if version
+ * is valid, Unknown otherwise.
+ */
+ formatSecurityProtocol(version) {
+ switch (version) {
+ case Ci.nsISSLStatus.TLS_VERSION_1:
+ return "TLSv1";
+ case Ci.nsISSLStatus.TLS_VERSION_1_1:
+ return "TLSv1.1";
+ case Ci.nsISSLStatus.TLS_VERSION_1_2:
+ return "TLSv1.2";
+ case Ci.nsISSLStatus.TLS_VERSION_1_3:
+ return "TLSv1.3";
+ default:
+ return "unknown";
+ }
+ },
+
+ /**
+ * Takes the securityState bitfield and returns reasons for weak connection
+ * as an array of strings.
+ *
+ * @param {number} state
+ * nsITransportSecurityInfo.securityState.
+ *
+ * @returns {array<string>}
+ * List of weakness reasons. A subset of { cipher } where
+ * * cipher: The cipher suite is consireded to be weak (RC4).
+ */
+ getReasonsForWeakness(state) {
+ const wpl = Ci.nsIWebProgressListener;
+
+ // If there's non-fatal security issues the request has STATE_IS_BROKEN
+ // flag set. See https://hg.mozilla.org/mozilla-central/file/44344099d119
+ // /security/manager/ssl/nsNSSCallbacks.cpp#l1233
+ let reasons = [];
+
+ if (state & wpl.STATE_IS_BROKEN) {
+ let isCipher = state & wpl.STATE_USES_WEAK_CRYPTO;
+
+ if (isCipher) {
+ reasons.push("cipher");
+ }
+ }
+
+ return reasons;
+ },
+
+ /**
+ * Creates array of objects for each element of a certificate.
+ *
+ * @param {nsIASN1Object} sequence
+ *
+ * @returns {Array}
+ * A list of objects with following format:
+ * {
+ * name: displayed name of the element
+ * value: the actual value of the element
+ * children: array of child objects
+ * }
+ */
+ getCertASN1Structure(sequence) {
+ // Get the ASN1Sequence below the current one, bail if it is invalid.
+ try {
+ sequence.QueryInterface(Ci.nsIASN1Sequence);
+ } catch (e) {}
+ if (!sequence || !sequence.isValidContainer) {
+ return [];
+ }
+
+ let ASN1Objects = [];
+ for (let i = 0; i < sequence.ASN1Objects.length; i++) {
+ let element = sequence.ASN1Objects.queryElementAt(i, Ci.nsIASN1Object);
+ let entry = {
+ name: element.displayName,
+ value: element.displayValue,
+ };
+ if (element.isExpanded || element.displayValue == "") {
+ // Push all elements seen deeper in the tree to the list of the current branch
+ // until everything is in the topmost array.
+ entry.children = this.getCertASN1Structure(element);
+ }
+ ASN1Objects.push(entry);
+ }
+ return ASN1Objects;
+ },
+};
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -16,16 +16,18 @@ ChromeUtils.import("resource://gre/modul
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ExtensionUtils",
"resource://gre/modules/ExtensionUtils.jsm");
ChromeUtils.defineModuleGetter(this, "WebRequestCommon",
"resource://gre/modules/WebRequestCommon.jsm");
ChromeUtils.defineModuleGetter(this, "WebRequestUpload",
"resource://gre/modules/WebRequestUpload.jsm");
+ChromeUtils.defineModuleGetter(this, "SecurityInfo",
+ "resource://gre/modules/SecurityInfo.jsm");
XPCOMUtils.defineLazyGetter(this, "ExtensionError", () => ExtensionUtils.ExtensionError);
function runLater(job) {
Services.tm.dispatchToMainThread(job);
}
function parseFilter(filter) {
@@ -175,18 +177,19 @@ class ResponseHeaderChanger extends Head
}
}
const MAYBE_CACHED_EVENTS = new Set([
"onResponseStarted", "onHeadersReceived", "onBeforeRedirect", "onCompleted", "onErrorOccurred",
]);
const OPTIONAL_PROPERTIES = [
- "requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl",
- "requestBody", "scheme", "realm", "isProxy", "challenger", "proxyInfo", "ip", "frameAncestors",
+ "requestHeaders", "responseHeaders", "statusCode", "statusLine", "error",
+ "redirectUrl", "requestBody", "scheme", "securityInfo", "realm", "isProxy",
+ "challenger", "proxyInfo", "ip", "frameAncestors",
];
function serializeRequestData(eventName) {
let data = {
requestId: this.requestId,
url: this.url,
originUrl: this.originUrl,
documentUrl: this.documentUrl,
@@ -741,16 +744,20 @@ HttpObserverManager = {
return;
}
if (!commonData) {
commonData = this.getRequestData(channel, extraData);
if (this.STATUS_TYPES.has(kind)) {
commonData.statusCode = channel.statusCode;
commonData.statusLine = channel.statusLine;
+ // TODO: Should we require a opts.securityInfo before we retreive it?
+ if (opts.securityInfo) {
+ commonData.securityInfo = SecurityInfo.parseSecurityInfo(channel.channel);
+ }
}
}
let data = Object.create(commonData);
if (registerFilter && opts.blocking && opts.extension) {
channel.registerTraceableChannel(opts.extension, opts.tabParent);
}
@@ -945,21 +952,21 @@ HttpEvent.prototype = {
removeListener(callback) {
HttpObserverManager.removeListener(this.internalEvent, callback);
},
};
var onBeforeSendHeaders = new HttpEvent("modify", ["requestHeaders", "blocking"]);
var onSendHeaders = new HttpEvent("afterModify", ["requestHeaders"]);
-var onHeadersReceived = new HttpEvent("headersReceived", ["blocking", "responseHeaders"]);
-var onAuthRequired = new HttpEvent("authRequired", ["blocking", "responseHeaders"]);
-var onBeforeRedirect = new HttpEvent("onRedirect", ["responseHeaders"]);
-var onResponseStarted = new HttpEvent("onStart", ["responseHeaders"]);
-var onCompleted = new HttpEvent("onStop", ["responseHeaders"]);
+var onHeadersReceived = new HttpEvent("headersReceived", ["blocking", "responseHeaders", "securityInfo"]);
+var onAuthRequired = new HttpEvent("authRequired", ["blocking", "responseHeaders", "securityInfo"]);
+var onBeforeRedirect = new HttpEvent("onRedirect", ["responseHeaders", "securityInfo"]);
+var onResponseStarted = new HttpEvent("onStart", ["responseHeaders", "securityInfo"]);
+var onCompleted = new HttpEvent("onStop", ["responseHeaders", "securityInfo"]);
var onErrorOccurred = new HttpEvent("onError");
var WebRequest = {
// http-on-modify observer for HTTP(S), content policy for the other protocols (notably, data:)
onBeforeRequest: onBeforeRequest,
// http-on-modify observer.
onBeforeSendHeaders: onBeforeSendHeaders,
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -162,16 +162,17 @@ TESTING_JS_MODULES += [
SPHINX_TREES['toolkit_modules'] = 'docs'
with Files('docs/**'):
SCHEDULES.exclusive = ['docs']
EXTRA_JS_MODULES += [
'addons/MatchURLFilters.jsm',
+ 'addons/SecurityInfo.jsm',
'addons/WebNavigation.jsm',
'addons/WebNavigationContent.js',
'addons/WebNavigationFrames.jsm',
'addons/WebRequest.jsm',
'addons/WebRequestCommon.jsm',
'addons/WebRequestContent.js',
'addons/WebRequestUpload.jsm',
'AppMenuNotifications.jsm',