IPProtectionUsage.sys.mjs (3141B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Service Class to observe and record IP protection usage. 7 * 8 * When started it will observe all HTTP requests and record the 9 * transfer sizes of requests and responses that are proxied through 10 * the IP protection proxy. 11 * 12 * It should be started when the IP protection proxy is active. 13 * It should be stopped when we know all proxy requests have been completed. 14 * 15 * It will record all Proxied Requests that match the isolation keys. 16 * So after a connection is established, the isolation key should be added. 17 */ 18 export class IPProtectionUsage { 19 constructor() { 20 ChromeUtils.generateQI([Ci.nsIObserver]); 21 } 22 23 start() { 24 if (this.#active) { 25 return; 26 } 27 Services.obs.addObserver(this, "http-on-stop-request"); 28 this.#active = true; 29 } 30 stop() { 31 if (!this.#active) { 32 return; 33 } 34 this.#active = false; 35 this.#isolationKeys.clear(); 36 Services.obs.removeObserver(this, "http-on-stop-request"); 37 } 38 39 addIsolationKey(key) { 40 if (typeof key !== "string" || !key) { 41 throw new Error("Isolation key must be a non-empty string"); 42 } 43 this.#isolationKeys.add(key); 44 } 45 46 observe(subject, topic) { 47 if (topic != "http-on-stop-request") { 48 return; 49 } 50 try { 51 const chan = subject.QueryInterface(Ci.nsIHttpChannel); 52 if (this.shouldCountChannel(chan)) { 53 IPProtectionUsage.countChannel(chan); 54 } 55 } catch (err) { 56 // If the channel is not an nsIHttpChannel 57 } 58 } 59 /** 60 * Checks if a channel should be counted. 61 * 62 * @param {nsIHttpChannel} channel 63 * @returns {boolean} true if the channel should be counted. 64 */ 65 shouldCountChannel(channel) { 66 try { 67 const proxiedChannel = channel.QueryInterface(Ci.nsIProxiedChannel); 68 const proxyInfo = proxiedChannel.proxyInfo; 69 if (!proxyInfo) { 70 // No proxy info, nothing to do. 71 return false; 72 } 73 const isolationKey = proxyInfo.connectionIsolationKey; 74 return isolationKey && this.#isolationKeys.has(isolationKey); 75 } catch (err) { 76 // If the channel is not an nsIHttpChannel or nsIProxiedChannel, as it's irrelevant 77 // for this class. 78 } 79 return false; 80 } 81 82 /** 83 * Checks a completed channel and records the transfer sizes to glean. 84 * 85 * @param {nsIHttpChannel} chan - A completed Channel to check. 86 */ 87 static countChannel(chan) { 88 try { 89 const cacheInfo = chan.QueryInterface(Ci.nsICacheInfoChannel); 90 if (cacheInfo.isFromCache()) { 91 return; 92 } 93 } catch (_) { 94 /* not all channels support it */ 95 } 96 97 if (chan.transferSize > 0) { 98 Glean.ipprotection.usageRx.accumulate(chan.transferSize); 99 } 100 if (chan.requestSize > 0) { 101 Glean.ipprotection.usageTx.accumulate(chan.requestSize); 102 } 103 } 104 105 #active = false; 106 #isolationKeys = new Set(); 107 } 108 109 IPProtectionUsage.prototype.QueryInterface = ChromeUtils.generateQI([ 110 Ci.nsIObserver, 111 ]);