ust 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;
  }
}
PK