Skip to content

Commit 5896a31

Browse files
committed
Bug 1876344 - Added idle startup task to register native messaging host JSON files on macOS r=mhughes,robwu,mossop
Differential Revision: https://phabricator.services.mozilla.com/D201773
1 parent b1ee07c commit 5896a31

File tree

5 files changed

+312
-0
lines changed

5 files changed

+312
-0
lines changed

browser/app/profile/firefox.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2915,6 +2915,13 @@ pref("svg.context-properties.content.allowed-domains", "profile.accounts.firefox
29152915
pref("extensions.translations.disabled", true);
29162916
#endif
29172917

2918+
#if defined(XP_MACOSX) || defined(XP_WIN)
2919+
pref("browser.firefoxbridge.enabled", false);
2920+
pref("browser.firefoxbridge.extensionOrigins",
2921+
"chrome-extension://gkcbmfjnnjoambnfmihmnkneakghogca/"
2922+
);
2923+
#endif
2924+
29182925
// Turn on interaction measurements
29192926
pref("browser.places.interactions.enabled", true);
29202927

browser/components/BrowserGlue.sys.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2654,6 +2654,16 @@ BrowserGlue.prototype = {
26542654
},
26552655
},
26562656

2657+
{
2658+
name: "firefoxBridgeNativeMessaging",
2659+
condition:
2660+
AppConstants.platform == "macosx" &&
2661+
Services.prefs.getBoolPref("browser.firefoxbridge.enabled", false),
2662+
task: async () => {
2663+
await lazy.FirefoxBridgeExtensionUtils.ensureRegistered();
2664+
},
2665+
},
2666+
26572667
// Ensure a Private Browsing Shortcut exists. This is needed in case
26582668
// a user tries to use Windows functionality to pin our Private Browsing
26592669
// mode icon to the Taskbar (eg: the "Pin to Taskbar" context menu item).

browser/modules/FirefoxBridgeExtensionUtils.sys.mjs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
33
* You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5+
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
6+
7+
const lazy = {};
8+
ChromeUtils.defineESModuleGetters(lazy, {
9+
ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
10+
});
11+
512
/**
613
* Default implementation of the helper class to assist in deleting the firefox protocols.
714
* See maybeDeleteBridgeProtocolRegistryEntries for more info.
@@ -219,4 +226,81 @@ export const FirefoxBridgeExtensionUtils = {
219226
wrk.close();
220227
}
221228
},
229+
230+
getNativeMessagingHostId() {
231+
let nativeMessagingHostId = "org.mozilla.firefox_bridge_nmh";
232+
if (AppConstants.NIGHTLY_BUILD) {
233+
nativeMessagingHostId += "_nightly";
234+
} else if (AppConstants.MOZ_DEV_EDITION) {
235+
nativeMessagingHostId += "_dev";
236+
} else if (AppConstants.IS_ESR) {
237+
nativeMessagingHostId += "_esr";
238+
}
239+
return nativeMessagingHostId;
240+
},
241+
242+
getExtensionOrigins() {
243+
return Services.prefs
244+
.getStringPref("browser.firefoxbridge.extensionOrigins", "")
245+
.split(",");
246+
},
247+
248+
async maybeWriteManifestFiles(
249+
nmhManifestFolder,
250+
nativeMessagingHostId,
251+
dualBrowserExtensionOrigins
252+
) {
253+
try {
254+
let binFile = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent;
255+
if (AppConstants.platform == "macosx") {
256+
binFile.append("nmhproxy");
257+
} else {
258+
throw new Error("Unsupported platform");
259+
}
260+
261+
let jsonContent = {
262+
name: nativeMessagingHostId,
263+
description: "Firefox Native Messaging Host",
264+
path: binFile.path,
265+
type: "stdio",
266+
allowed_origins: dualBrowserExtensionOrigins,
267+
};
268+
let nmhManifestFile = await IOUtils.getFile(
269+
nmhManifestFolder,
270+
`${nativeMessagingHostId}.json`
271+
);
272+
273+
// This throws an error if the JSON file doesn't exist
274+
// or if it's corrupt.
275+
let correctFileExists = true;
276+
try {
277+
correctFileExists = lazy.ObjectUtils.deepEqual(
278+
await IOUtils.readJSON(nmhManifestFile.path),
279+
jsonContent
280+
);
281+
} catch (e) {
282+
correctFileExists = false;
283+
}
284+
if (!correctFileExists) {
285+
await IOUtils.writeJSON(nmhManifestFile.path, jsonContent);
286+
}
287+
} catch (e) {
288+
console.error(e);
289+
}
290+
},
291+
292+
async ensureRegistered() {
293+
let nmhManifestFolder = null;
294+
if (AppConstants.platform == "macosx") {
295+
nmhManifestFolder =
296+
"~/Library/Application Support/Google/Chrome/NativeMessagingHosts/";
297+
} else {
298+
throw new Error("Unsupported platform");
299+
}
300+
await this.maybeWriteManifestFiles(
301+
nmhManifestFolder,
302+
this.getNativeMessagingHostId(),
303+
this.getExtensionOrigins()
304+
);
305+
},
222306
};
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/* Any copyright is dedicated to the Public Domain.
2+
http://creativecommons.org/publicdomain/zero/1.0/ */
3+
4+
"use strict";
5+
6+
const { AppConstants } = ChromeUtils.importESModule(
7+
"resource://gre/modules/AppConstants.sys.mjs"
8+
);
9+
const { FileUtils } = ChromeUtils.importESModule(
10+
"resource://gre/modules/FileUtils.sys.mjs"
11+
);
12+
const { FirefoxBridgeExtensionUtils } = ChromeUtils.importESModule(
13+
"resource:///modules/FirefoxBridgeExtensionUtils.sys.mjs"
14+
);
15+
16+
const DUAL_BROWSER_EXTENSION_ORIGIN = ["chrome-extension://fake-origin/"];
17+
const NATIVE_MESSAGING_HOST_ID = "org.mozilla.firefox_bridge_test";
18+
19+
let dir = FileUtils.getDir("TmpD", ["NativeMessagingHostsTest"]);
20+
dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
21+
22+
let userDir = dir.clone();
23+
userDir.append("user");
24+
userDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
25+
26+
let dirProvider = {
27+
getFile(property) {
28+
if (property == "Home") {
29+
return userDir.clone();
30+
}
31+
return null;
32+
},
33+
};
34+
35+
try {
36+
Services.dirsvc.undefine("Home");
37+
} catch (e) {}
38+
Services.dirsvc.registerProvider(dirProvider);
39+
40+
registerCleanupFunction(() => {
41+
Services.dirsvc.unregisterProvider(dirProvider);
42+
dir.remove(true);
43+
});
44+
45+
const USER_TEST_PATH = PathUtils.join(userDir.path, "manifestDir");
46+
47+
let binFile = null;
48+
add_setup(async function () {
49+
binFile = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent.clone();
50+
if (AppConstants.platform == "macosx") {
51+
binFile.append("nmhproxy");
52+
} else {
53+
throw new Error("Unsupported platform");
54+
}
55+
});
56+
57+
function getExpectedOutput() {
58+
return {
59+
name: NATIVE_MESSAGING_HOST_ID,
60+
description: "Firefox Native Messaging Host",
61+
path: binFile.path,
62+
type: "stdio",
63+
allowed_origins: DUAL_BROWSER_EXTENSION_ORIGIN,
64+
};
65+
}
66+
67+
add_task(async function test_maybeWriteManifestFiles() {
68+
await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles(
69+
USER_TEST_PATH,
70+
NATIVE_MESSAGING_HOST_ID,
71+
DUAL_BROWSER_EXTENSION_ORIGIN
72+
);
73+
let expectedOutput = JSON.stringify(getExpectedOutput());
74+
let nmhManifestFilePath = PathUtils.join(
75+
USER_TEST_PATH,
76+
`${NATIVE_MESSAGING_HOST_ID}.json`
77+
);
78+
let nmhManifestFileContent = await IOUtils.readUTF8(nmhManifestFilePath);
79+
await IOUtils.remove(nmhManifestFilePath);
80+
Assert.equal(nmhManifestFileContent, expectedOutput);
81+
});
82+
83+
add_task(async function test_maybeWriteManifestFilesIncorrect() {
84+
let nmhManifestFile = await IOUtils.getFile(
85+
USER_TEST_PATH,
86+
`${NATIVE_MESSAGING_HOST_ID}.json`
87+
);
88+
89+
let incorrectInput = {
90+
name: NATIVE_MESSAGING_HOST_ID,
91+
description: "Manifest with unexpected description",
92+
path: binFile.path,
93+
type: "stdio",
94+
allowed_origins: DUAL_BROWSER_EXTENSION_ORIGIN,
95+
};
96+
await IOUtils.writeJSON(nmhManifestFile.path, incorrectInput);
97+
98+
// Write correct JSON to the file and check to make sure it matches
99+
// the expected output
100+
await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles(
101+
USER_TEST_PATH,
102+
NATIVE_MESSAGING_HOST_ID,
103+
DUAL_BROWSER_EXTENSION_ORIGIN
104+
);
105+
let expectedOutput = JSON.stringify(getExpectedOutput());
106+
107+
let nmhManifestFilePath = PathUtils.join(
108+
USER_TEST_PATH,
109+
`${NATIVE_MESSAGING_HOST_ID}.json`
110+
);
111+
let nmhManifestFileContent = await IOUtils.readUTF8(nmhManifestFilePath);
112+
await IOUtils.remove(nmhManifestFilePath);
113+
Assert.equal(nmhManifestFileContent, expectedOutput);
114+
});
115+
116+
add_task(async function test_maybeWriteManifestFilesAlreadyExists() {
117+
// Write file and confirm it exists
118+
await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles(
119+
USER_TEST_PATH,
120+
NATIVE_MESSAGING_HOST_ID,
121+
DUAL_BROWSER_EXTENSION_ORIGIN
122+
);
123+
let nmhManifestFile = await IOUtils.getFile(
124+
USER_TEST_PATH,
125+
`${NATIVE_MESSAGING_HOST_ID}.json`
126+
);
127+
128+
// Modify file modificatiomn time to be older than the write time
129+
let oldModificationTime = Date.now() - 1000000;
130+
let setModificationTime = await IOUtils.setModificationTime(
131+
nmhManifestFile.path,
132+
oldModificationTime
133+
);
134+
Assert.equal(oldModificationTime, setModificationTime);
135+
136+
// Call function which writes correct JSON to the file and make sure
137+
// the modification time is the same, meaning we haven't written anything
138+
await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles(
139+
USER_TEST_PATH,
140+
NATIVE_MESSAGING_HOST_ID,
141+
DUAL_BROWSER_EXTENSION_ORIGIN
142+
);
143+
let stat = await IOUtils.stat(nmhManifestFile.path);
144+
await IOUtils.remove(nmhManifestFile.path);
145+
Assert.equal(stat.lastModified, oldModificationTime);
146+
});
147+
148+
add_task(async function test_maybeWriteManifestFilesDirDoesNotExist() {
149+
let testDir = dir.clone();
150+
// This folder does not exist, so we want to make sure it's created
151+
testDir.append("dirDoesNotExist");
152+
await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles(
153+
testDir.path,
154+
NATIVE_MESSAGING_HOST_ID,
155+
DUAL_BROWSER_EXTENSION_ORIGIN
156+
);
157+
158+
ok(await IOUtils.exists(testDir.path));
159+
ok(
160+
await IOUtils.exists(
161+
PathUtils.join(testDir.path, `${NATIVE_MESSAGING_HOST_ID}.json`)
162+
)
163+
);
164+
await IOUtils.remove(testDir.path, { recursive: true });
165+
});
166+
167+
add_task(async function test_ensureRegistered() {
168+
let expectedJSONDirPath = null;
169+
let nativeHostId = "org.mozilla.firefox_bridge_nmh";
170+
if (AppConstants.NIGHTLY_BUILD) {
171+
nativeHostId = "org.mozilla.firefox_bridge_nmh_nightly";
172+
} else if (AppConstants.MOZ_DEV_EDITION) {
173+
nativeHostId = "org.mozilla.firefox_bridge_nmh_dev";
174+
} else if (AppConstants.IS_ESR) {
175+
nativeHostId = "org.mozilla.firefox_bridge_nmh_esr";
176+
}
177+
178+
if (AppConstants.platform == "macosx") {
179+
expectedJSONDirPath = PathUtils.joinRelative(
180+
userDir.path,
181+
"Library/Application Support/Google/Chrome/NativeMessagingHosts/"
182+
);
183+
} else {
184+
throw new Error("Unsupported platform");
185+
}
186+
187+
ok(!(await IOUtils.exists(expectedJSONDirPath)));
188+
let expectedJSONPath = PathUtils.join(
189+
expectedJSONDirPath,
190+
`${nativeHostId}.json`
191+
);
192+
193+
await FirefoxBridgeExtensionUtils.ensureRegistered();
194+
let realOutput = {
195+
name: nativeHostId,
196+
description: "Firefox Native Messaging Host",
197+
path: binFile.path,
198+
type: "stdio",
199+
allowed_origins: FirefoxBridgeExtensionUtils.getExtensionOrigins(),
200+
};
201+
202+
let expectedOutput = JSON.stringify(realOutput);
203+
let JSONContent = await IOUtils.readUTF8(expectedJSONPath);
204+
await IOUtils.remove(expectedJSONPath);
205+
Assert.equal(JSONContent, expectedOutput);
206+
});

browser/modules/test/unit/xpcshell.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ skip-if = ["os == 'android'"] # bug 1730213
88
["test_FirefoxBridgeExtensionUtils.js"]
99
run-if = ["os == 'win'"] # Test of a Windows-specific feature
1010

11+
["test_FirefoxBridgeExtensionUtilsNativeManifest.js"]
12+
run-if = [
13+
"os == 'mac'",
14+
]
15+
1116
["test_HomePage.js"]
1217

1318
["test_HomePage_ignore.js"]

0 commit comments

Comments
 (0)