Backed out changeset 8aaffdf63d09 (bug 1920575) for causing bc failures on browser_si...
[gecko.git] / browser / modules / FirefoxBridgeExtensionUtils.sys.mjs
blobe1222db6e0b3b16186f2970e7beb68422eb37aad
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 file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
7 const lazy = {};
8 ChromeUtils.defineESModuleGetters(lazy, {
9   ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
10 });
12 /**
13  * Default implementation of the helper class to assist in deleting the firefox protocols.
14  * See maybeDeleteBridgeProtocolRegistryEntries for more info.
15  */
16 class DeleteBridgeProtocolRegistryEntryHelperImplementation {
17   getApplicationPath() {
18     return Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
19   }
21   openRegistryRoot() {
22     const wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
23       Ci.nsIWindowsRegKey
24     );
26     wrk.open(wrk.ROOT_KEY_CURRENT_USER, "Software\\Classes", wrk.ACCESS_ALL);
28     return wrk;
29   }
31   deleteChildren(start) {
32     // Recursively delete all of the children of the children
33     // Go through the list in reverse order, so that shrinking
34     // the list doesn't rearrange things while iterating
35     for (let i = start.childCount; i > 0; i--) {
36       const childName = start.getChildName(i - 1);
37       const child = start.openChild(childName, start.ACCESS_ALL);
38       this.deleteChildren(child);
39       child.close();
41       start.removeChild(childName);
42     }
43   }
45   deleteRegistryTree(root, toDeletePath) {
46     var start = root.openChild(toDeletePath, root.ACCESS_ALL);
47     this.deleteChildren(start);
48     start.close();
50     root.removeChild(toDeletePath);
51   }
54 export const FirefoxBridgeExtensionUtils = {
55   /**
56    * In Firefox 122, we enabled the firefox and firefox-private protocols.
57    * We switched over to using firefox-bridge and firefox-private-bridge,
58    *
59    * In Firefox 126, we deleted the above firefox-bridge and
60    * firefox-private-bridge protocols in favor of using native
61    * messaging so we are only keeping the deletion code.
62    *
63    * but we want to clean up the use of the other protocols.
64    *
65    * deleteBridgeProtocolRegistryEntryHelper handles everything outside of the logic needed for
66    * this method so that the logic in maybeDeleteBridgeProtocolRegistryEntries can be unit tested
67    *
68    * We only delete the entries for the firefox and firefox-private protocols if
69    * they were set up to use this install and in the format that Firefox installed
70    * them with. If the entries are changed in any way, it is assumed that the user
71    * mucked with them manually and knows what they are doing.
72    */
74   PUBLIC_PROTOCOL: "firefox-bridge",
75   PRIVATE_PROTOCOL: "firefox-private-bridge",
76   OLD_PUBLIC_PROTOCOL: "firefox",
77   OLD_PRIVATE_PROTOCOL: "firefox-private",
79   maybeDeleteBridgeProtocolRegistryEntries(
80     publicProtocol = this.PUBLIC_PROTOCOL,
81     privateProtocol = this.PRIVATE_PROTOCOL,
82     deleteBridgeProtocolRegistryEntryHelper = new DeleteBridgeProtocolRegistryEntryHelperImplementation()
83   ) {
84     try {
85       var wrk = deleteBridgeProtocolRegistryEntryHelper.openRegistryRoot();
86       const path = deleteBridgeProtocolRegistryEntryHelper.getApplicationPath();
88       const maybeDeleteRegistryKey = (protocol, protocolCommand) => {
89         const openCommandPath = protocol + "\\shell\\open\\command";
90         if (wrk.hasChild(openCommandPath)) {
91           let deleteProtocolEntry = false;
93           try {
94             var openCommandKey = wrk.openChild(
95               openCommandPath,
96               wrk.ACCESS_READ
97             );
98             if (openCommandKey.valueCount == 1) {
99               const defaultKeyName = "";
100               if (openCommandKey.getValueName(0) == defaultKeyName) {
101                 if (
102                   openCommandKey.getValueType(defaultKeyName) ==
103                   Ci.nsIWindowsRegKey.TYPE_STRING
104                 ) {
105                   const val = openCommandKey.readStringValue(defaultKeyName);
106                   if (val == protocolCommand) {
107                     deleteProtocolEntry = true;
108                   }
109                 }
110               }
111             }
112           } finally {
113             openCommandKey.close();
114           }
116           if (deleteProtocolEntry) {
117             deleteBridgeProtocolRegistryEntryHelper.deleteRegistryTree(
118               wrk,
119               protocol
120             );
121           }
122         }
123       };
125       maybeDeleteRegistryKey(publicProtocol, `\"${path}\" -osint -url \"%1\"`);
126       maybeDeleteRegistryKey(
127         privateProtocol,
128         `\"${path}\" -osint -private-window \"%1\"`
129       );
130     } catch (err) {
131       console.error(err);
132     } finally {
133       wrk.close();
134     }
135   },
137   getNativeMessagingHostId() {
138     let nativeMessagingHostId = "org.mozilla.firefox_bridge_nmh";
139     if (AppConstants.NIGHTLY_BUILD) {
140       nativeMessagingHostId += "_nightly";
141     } else if (AppConstants.MOZ_DEV_EDITION) {
142       nativeMessagingHostId += "_dev";
143     } else if (AppConstants.IS_ESR) {
144       nativeMessagingHostId += "_esr";
145     }
146     return nativeMessagingHostId;
147   },
149   getExtensionOrigins() {
150     return Services.prefs
151       .getStringPref("browser.firefoxbridge.extensionOrigins", "")
152       .split(",");
153   },
155   async maybeWriteManifestFiles(
156     nmhManifestFolder,
157     nativeMessagingHostId,
158     dualBrowserExtensionOrigins
159   ) {
160     try {
161       let binFile = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent;
162       if (AppConstants.platform == "win") {
163         binFile.append("nmhproxy.exe");
164       } else if (AppConstants.platform == "macosx") {
165         binFile.append("nmhproxy");
166       } else {
167         throw new Error("Unsupported platform");
168       }
170       let jsonContent = {
171         name: nativeMessagingHostId,
172         description: "Firefox Native Messaging Host",
173         path: binFile.path,
174         type: "stdio",
175         allowed_origins: dualBrowserExtensionOrigins,
176       };
177       let nmhManifestFile = await IOUtils.getFile(
178         nmhManifestFolder,
179         `${nativeMessagingHostId}.json`
180       );
182       // This throws an error if the JSON file doesn't exist
183       // or if it's corrupt.
184       let correctFileExists = true;
185       try {
186         correctFileExists = lazy.ObjectUtils.deepEqual(
187           await IOUtils.readJSON(nmhManifestFile.path),
188           jsonContent
189         );
190       } catch (e) {
191         correctFileExists = false;
192       }
193       if (!correctFileExists) {
194         await IOUtils.writeJSON(nmhManifestFile.path, jsonContent);
195       }
196     } catch (e) {
197       console.error(e);
198     }
199   },
201   async ensureRegistered() {
202     let nmhManifestFolder = null;
203     if (AppConstants.platform == "win") {
204       // We don't have permission to write to the application install directory
205       // so instead write to %AppData%\Mozilla\Firefox.
206       nmhManifestFolder = PathUtils.join(
207         Services.dirsvc.get("AppData", Ci.nsIFile).path,
208         "Mozilla",
209         "Firefox"
210       );
211     } else if (AppConstants.platform == "macosx") {
212       nmhManifestFolder =
213         "~/Library/Application Support/Google/Chrome/NativeMessagingHosts/";
214     } else {
215       throw new Error("Unsupported platform");
216     }
217     await this.maybeWriteManifestFiles(
218       nmhManifestFolder,
219       this.getNativeMessagingHostId(),
220       this.getExtensionOrigins()
221     );
222     if (AppConstants.platform == "win") {
223       this.maybeWriteNativeMessagingRegKeys(
224         "Software\\Google\\Chrome\\NativeMessagingHosts",
225         nmhManifestFolder,
226         this.getNativeMessagingHostId()
227       );
228     }
229   },
231   maybeWriteNativeMessagingRegKeys(
232     regPath,
233     nmhManifestFolder,
234     NATIVE_MESSAGING_HOST_ID
235   ) {
236     let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
237       Ci.nsIWindowsRegKey
238     );
239     try {
240       let expectedValue = PathUtils.join(
241         nmhManifestFolder,
242         `${NATIVE_MESSAGING_HOST_ID}.json`
243       );
244       try {
245         // If the key already exists it will just be opened
246         wrk.create(
247           wrk.ROOT_KEY_CURRENT_USER,
248           regPath + `\\${NATIVE_MESSAGING_HOST_ID}`,
249           wrk.ACCESS_ALL
250         );
251         if (wrk.readStringValue("") == expectedValue) {
252           return;
253         }
254       } catch (e) {
255         // The key either doesn't have a value or doesn't exist
256         // In either case we need to write it.
257       }
258       wrk.writeStringValue("", expectedValue);
259     } catch (e) {
260       // The method fails if we can't access the key
261       // which means it doesn't exist. That's a normal situation.
262       // We don't need to do anything here.
263     } finally {
264       wrk.close();
265     }
266   },