summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/ServiceRequest.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/modules/ServiceRequest.sys.mjs')
-rw-r--r--toolkit/modules/ServiceRequest.sys.mjs180
1 files changed, 180 insertions, 0 deletions
diff --git a/toolkit/modules/ServiceRequest.sys.mjs b/toolkit/modules/ServiceRequest.sys.mjs
new file mode 100644
index 0000000000..a88ba9c324
--- /dev/null
+++ b/toolkit/modules/ServiceRequest.sys.mjs
@@ -0,0 +1,180 @@
+/* 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/. */
+
+/**
+ * This module consolidates various code and data update requests, so flags
+ * can be set, Telemetry collected, etc. in a central place.
+ */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "ProxyService",
+ "@mozilla.org/network/protocol-proxy-service;1",
+ "nsIProtocolProxyService"
+);
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ ExtensionPreferencesManager:
+ "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
+});
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "CaptivePortalService",
+ "@mozilla.org/network/captive-portal-service;1",
+ "nsICaptivePortalService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "gNetworkLinkService",
+ "@mozilla.org/network/network-link-service;1",
+ "nsINetworkLinkService"
+);
+
+const PROXY_CONFIG_TYPES = [
+ "direct",
+ "manual",
+ "pac",
+ "unused", // nsIProtocolProxyService.idl skips index 3.
+ "wpad",
+ "system",
+];
+
+function recordEvent(service, source = {}) {
+ try {
+ Services.telemetry.setEventRecordingEnabled("service_request", true);
+ Services.telemetry.recordEvent(
+ "service_request",
+ "bypass",
+ "proxy_info",
+ service,
+ source
+ );
+ } catch (err) {
+ // If the telemetry throws just log the error so it doesn't break any
+ // functionality.
+ console.error(err);
+ }
+}
+
+// If proxy.settings is used to change the proxy, an extension will
+// be "in control". This returns the id of that extension.
+async function getControllingExtension() {
+ if (
+ !WebExtensionPolicy.getActiveExtensions().some(p =>
+ p.permissions.includes("proxy")
+ )
+ ) {
+ return undefined;
+ }
+ // Is this proxied by an extension that set proxy prefs?
+ let setting = await lazy.ExtensionPreferencesManager.getSetting(
+ "proxy.settings"
+ );
+ return setting?.id;
+}
+
+async function getProxySource(proxyInfo) {
+ // sourceId is set when using proxy.onRequest
+ if (proxyInfo.sourceId) {
+ return {
+ source: proxyInfo.sourceId,
+ type: "api",
+ };
+ }
+ let type = PROXY_CONFIG_TYPES[lazy.ProxyService.proxyConfigType] || "unknown";
+
+ // If we have a policy it will have set the prefs.
+ if (
+ Services.policies &&
+ Services.policies.status === Services.policies.ACTIVE
+ ) {
+ let policies = Services.policies.getActivePolicies()?.filter(p => p.Proxy);
+ if (policies?.length) {
+ return {
+ source: "policy",
+ type,
+ };
+ }
+ }
+
+ let source = await getControllingExtension();
+ return {
+ source: source || "prefs",
+ type,
+ };
+}
+
+/**
+ * ServiceRequest is intended to be a drop-in replacement for current users
+ * of XMLHttpRequest.
+ *
+ * @param {Object} options - Options for underlying XHR, e.g. { mozAnon: bool }
+ */
+export class ServiceRequest extends XMLHttpRequest {
+ constructor(options) {
+ super(options);
+ }
+ /**
+ * Opens an XMLHttpRequest, and sets the NSS "beConservative" flag.
+ * Requests are always async.
+ *
+ * @param {String} method - HTTP method to use, e.g. "GET".
+ * @param {String} url - URL to open.
+ * @param {Object} options - Additional options { bypassProxy: bool }.
+ */
+ open(method, url, options) {
+ super.open(method, url, true);
+
+ if (super.channel instanceof Ci.nsIHttpChannelInternal) {
+ let internal = super.channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ // Disable cutting edge features, like TLS 1.3, where middleboxes might brick us
+ internal.beConservative = true;
+ // Disable use of proxy for this request if necessary.
+ if (options?.bypassProxy && this.bypassProxyEnabled) {
+ internal.bypassProxy = true;
+ }
+ }
+ }
+
+ get bypassProxy() {
+ let { channel } = this;
+ return channel.QueryInterface(Ci.nsIHttpChannelInternal).bypassProxy;
+ }
+
+ get isProxied() {
+ let { channel } = this;
+ return !!(channel instanceof Ci.nsIProxiedChannel && channel.proxyInfo);
+ }
+
+ get bypassProxyEnabled() {
+ return Services.prefs.getBoolPref("network.proxy.allow_bypass", true);
+ }
+
+ static async logProxySource(channel, service) {
+ if (channel.proxyInfo) {
+ let source = await getProxySource(channel.proxyInfo);
+ recordEvent(service, source);
+ }
+ }
+
+ static get isOffline() {
+ try {
+ return (
+ Services.io.offline ||
+ lazy.CaptivePortalService.state ==
+ lazy.CaptivePortalService.LOCKED_PORTAL ||
+ !lazy.gNetworkLinkService.isLinkUp
+ );
+ } catch (ex) {
+ // we cannot get state, assume the best.
+ }
+ return false;
+ }
+}