diff options
Diffstat (limited to 'browser/modules/test')
3 files changed, 581 insertions, 0 deletions
diff --git a/browser/modules/test/unit/test_FirefoxBridgeExtensionUtils.js b/browser/modules/test/unit/test_FirefoxBridgeExtensionUtils.js new file mode 100644 index 0000000000..1273ee950b --- /dev/null +++ b/browser/modules/test/unit/test_FirefoxBridgeExtensionUtils.js @@ -0,0 +1,350 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FirefoxBridgeExtensionUtils } = ChromeUtils.importESModule( + "resource:///modules/FirefoxBridgeExtensionUtils.sys.mjs" +); + +const FIREFOX_SHELL_OPEN_COMMAND_PATH = "firefox\\shell\\open\\command"; +const FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH = + "firefox-private\\shell\\open\\command"; + +class StubbedRegistryKey { + #children; + #originalChildren; + #closeCalled; + #deletedChildren; + #openedForRead; + #values; + + constructor(children, values) { + this.#children = children; + this.#values = values; + this.#originalChildren = new Map(children); + + this.#closeCalled = false; + this.#openedForRead = false; + this.#deletedChildren = new Set([]); + } + + get ACCESS_READ() { + return 0; + } + + reset() { + this.#closeCalled = false; + this.#deletedChildren = new Set([]); + this.#children = new Map(this.#originalChildren); + } + + open(accessLevel) { + this.#openedForRead = true; + } + + get wasOpenedForRead() { + return this.#openedForRead; + } + + openChild(path, accessLevel) { + const result = this.#children.get(path); + result?.open(accessLevel); + return result; + } + + hasChild(path) { + return this.#children.has(path); + } + + close() { + this.#closeCalled = true; + } + + removeChild(path) { + this.#deletedChildren.add(path); + + // delete the actual child if it's in there + this.#children.delete(path); + } + + isChildDeleted(path) { + return this.#deletedChildren.has(path); + } + + getChildName(index) { + let i = 0; + for (const [key] of this.#children) { + if (i == index) { + return key; + } + i++; + } + + return undefined; + } + + readStringValue(name) { + return this.#values.get(name); + } + + get childCount() { + return this.#children.size; + } + + getValueType(entryName) { + if (typeof this.readStringValue(entryName) == "string") { + return Ci.nsIWindowsRegKey.TYPE_STRING; + } + + throw new Error(`${entryName} not found in registry`); + } + + get wasCloseCalled() { + return this.#closeCalled; + } + + getValueName(index) { + let i = 0; + for (const [key] of this.#values) { + if (i == index) { + return key; + } + i++; + } + + return undefined; + } + + get valueCount() { + return this.#values.size; + } +} + +class StubbedDeleteBridgeProtocolRegistryEntryHelper { + #applicationPath; + #registryRootKey; + + constructor({ applicationPath, registryRootKey }) { + this.#applicationPath = applicationPath; + this.#registryRootKey = registryRootKey; + } + + getApplicationPath() { + return this.#applicationPath; + } + + openRegistryRoot() { + return this.#registryRootKey; + } + + deleteRegistryTree(root, toDeletePath) { + // simplify this for tests + root.removeChild(toDeletePath); + } +} + +add_task(async function test_DeleteWhenSameFirefoxInstall() { + const applicationPath = "testPath"; + + const firefoxEntries = new Map(); + firefoxEntries.set("", `\"${applicationPath}\" -osint -url \"%1\"`); + + const firefoxProtocolRegKey = new StubbedRegistryKey( + new Map(), + firefoxEntries + ); + + const firefoxPrivateEntries = new Map(); + firefoxPrivateEntries.set( + "", + `\"${applicationPath}\" -osint -private-window \"%1\"` + ); + const firefoxPrivateProtocolRegKey = new StubbedRegistryKey( + new Map(), + firefoxPrivateEntries + ); + + const children = new Map(); + children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey); + children.set( + FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH, + firefoxPrivateProtocolRegKey + ); + + const registryRootKey = new StubbedRegistryKey(children, new Map()); + + const stubbedDeleteBridgeProtocolRegistryHelper = + new StubbedDeleteBridgeProtocolRegistryEntryHelper({ + applicationPath, + registryRootKey, + }); + + FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries( + stubbedDeleteBridgeProtocolRegistryHelper + ); + + ok(registryRootKey.wasCloseCalled, "Root key closed"); + + ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened"); + ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed"); + ok( + registryRootKey.isChildDeleted("firefox"), + "Firefox protocol registry entry deleted" + ); + + ok( + firefoxPrivateProtocolRegKey.wasOpenedForRead, + "Firefox private key opened" + ); + ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed"); + ok( + registryRootKey.isChildDeleted("firefox-private"), + "Firefox private protocol registry entry deleted" + ); +}); + +add_task(async function test_DeleteWhenDifferentFirefoxInstall() { + const applicationPath = "testPath"; + const badApplicationPath = "testPath2"; + + const firefoxEntries = new Map(); + firefoxEntries.set("", `\"${badApplicationPath}\" -osint -url \"%1\"`); + + const firefoxProtocolRegKey = new StubbedRegistryKey( + new Map(), + firefoxEntries + ); + + const firefoxPrivateEntries = new Map(); + firefoxPrivateEntries.set( + "", + `\"${badApplicationPath}\" -osint -private-window \"%1\"` + ); + const firefoxPrivateProtocolRegKey = new StubbedRegistryKey( + new Map(), + firefoxPrivateEntries + ); + + const children = new Map(); + children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey); + children.set( + FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH, + firefoxPrivateProtocolRegKey + ); + + const registryRootKey = new StubbedRegistryKey(children, new Map()); + + const stubbedDeleteBridgeProtocolRegistryHelper = + new StubbedDeleteBridgeProtocolRegistryEntryHelper({ + applicationPath, + registryRootKey, + }); + + FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries( + stubbedDeleteBridgeProtocolRegistryHelper + ); + + ok(registryRootKey.wasCloseCalled, "Root key closed"); + + ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened"); + ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed"); + ok( + !registryRootKey.isChildDeleted("firefox"), + "Firefox protocol registry entry not deleted" + ); + + ok( + firefoxPrivateProtocolRegKey.wasOpenedForRead, + "Firefox private key opened" + ); + ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed"); + ok( + !registryRootKey.isChildDeleted("firefox-private"), + "Firefox private protocol registry entry not deleted" + ); +}); + +add_task(async function test_DeleteWhenNoRegistryEntries() { + const applicationPath = "testPath"; + + const firefoxPrivateEntries = new Map(); + const firefoxPrivateProtocolRegKey = new StubbedRegistryKey( + new Map(), + firefoxPrivateEntries + ); + + const children = new Map(); + children.set( + FIREFOX_PRIVATE_SHELL_OPEN_COMMAND_PATH, + firefoxPrivateProtocolRegKey + ); + + const registryRootKey = new StubbedRegistryKey(children, new Map()); + + const stubbedDeleteBridgeProtocolRegistryHelper = + new StubbedDeleteBridgeProtocolRegistryEntryHelper({ + applicationPath, + registryRootKey, + }); + + FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries( + stubbedDeleteBridgeProtocolRegistryHelper + ); + + ok(registryRootKey.wasCloseCalled, "Root key closed"); + + ok( + firefoxPrivateProtocolRegKey.wasOpenedForRead, + "Firefox private key opened" + ); + ok(firefoxPrivateProtocolRegKey.wasCloseCalled, "Firefox private key closed"); + ok( + !registryRootKey.isChildDeleted("firefox"), + "Firefox protocol registry entry deleted when it shouldn't be" + ); + ok( + !registryRootKey.isChildDeleted("firefox-private"), + "Firefox private protocol registry deleted when it shouldn't be" + ); +}); + +add_task(async function test_DeleteWhenUnexpectedRegistryEntries() { + const applicationPath = "testPath"; + + const firefoxEntries = new Map(); + firefoxEntries.set("", `\"${applicationPath}\" -osint -url \"%1\"`); + firefoxEntries.set("extraEntry", "extraValue"); + const firefoxProtocolRegKey = new StubbedRegistryKey( + new Map(), + firefoxEntries + ); + + const children = new Map(); + children.set(FIREFOX_SHELL_OPEN_COMMAND_PATH, firefoxProtocolRegKey); + + const registryRootKey = new StubbedRegistryKey(children, new Map()); + + const stubbedDeleteBridgeProtocolRegistryHelper = + new StubbedDeleteBridgeProtocolRegistryEntryHelper({ + applicationPath, + registryRootKey, + }); + + FirefoxBridgeExtensionUtils.maybeDeleteBridgeProtocolRegistryEntries( + stubbedDeleteBridgeProtocolRegistryHelper + ); + + ok(registryRootKey.wasCloseCalled, "Root key closed"); + + ok(firefoxProtocolRegKey.wasOpenedForRead, "Firefox key opened"); + ok(firefoxProtocolRegKey.wasCloseCalled, "Firefox key closed"); + ok( + !registryRootKey.isChildDeleted("firefox"), + "Firefox protocol registry entry deleted when it shouldn't be" + ); + ok( + !registryRootKey.isChildDeleted("firefox-private"), + "Firefox private protocol registry deleted when it shouldn't be" + ); +}); diff --git a/browser/modules/test/unit/test_FirefoxBridgeExtensionUtilsNativeManifest.js b/browser/modules/test/unit/test_FirefoxBridgeExtensionUtilsNativeManifest.js new file mode 100644 index 0000000000..cef550d705 --- /dev/null +++ b/browser/modules/test/unit/test_FirefoxBridgeExtensionUtilsNativeManifest.js @@ -0,0 +1,222 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); +const { FirefoxBridgeExtensionUtils } = ChromeUtils.importESModule( + "resource:///modules/FirefoxBridgeExtensionUtils.sys.mjs" +); + +const DUAL_BROWSER_EXTENSION_ORIGIN = ["chrome-extension://fake-origin/"]; +const NATIVE_MESSAGING_HOST_ID = "org.mozilla.firefox_bridge_test"; + +let dir = FileUtils.getDir("TmpD", ["NativeMessagingHostsTest"]); +dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +let userDir = dir.clone(); +userDir.append("user"); +userDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +let appDir = dir.clone(); +appDir.append("app"); +appDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +let dirProvider = { + getFile(property) { + if (property == "Home") { + return userDir.clone(); + } else if (property == "AppData") { + return appDir.clone(); + } + return null; + }, +}; + +try { + Services.dirsvc.undefine("Home"); +} catch (e) {} +try { + Services.dirsvc.undefine("AppData"); +} catch (e) {} +Services.dirsvc.registerProvider(dirProvider); + +registerCleanupFunction(() => { + Services.dirsvc.unregisterProvider(dirProvider); + dir.remove(true); +}); + +const USER_TEST_PATH = PathUtils.join(userDir.path, "manifestDir"); + +let binFile = null; +add_setup(async function () { + binFile = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent.clone(); + if (AppConstants.platform == "win") { + binFile.append("nmhproxy.exe"); + } else if (AppConstants.platform == "macosx") { + binFile.append("nmhproxy"); + } else { + throw new Error("Unsupported platform"); + } +}); + +function getExpectedOutput() { + return { + name: NATIVE_MESSAGING_HOST_ID, + description: "Firefox Native Messaging Host", + path: binFile.path, + type: "stdio", + allowed_origins: DUAL_BROWSER_EXTENSION_ORIGIN, + }; +} + +add_task(async function test_maybeWriteManifestFiles() { + await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles( + USER_TEST_PATH, + NATIVE_MESSAGING_HOST_ID, + DUAL_BROWSER_EXTENSION_ORIGIN + ); + let expectedOutput = JSON.stringify(getExpectedOutput()); + let nmhManifestFilePath = PathUtils.join( + USER_TEST_PATH, + `${NATIVE_MESSAGING_HOST_ID}.json` + ); + let nmhManifestFileContent = await IOUtils.readUTF8(nmhManifestFilePath); + await IOUtils.remove(nmhManifestFilePath); + Assert.equal(nmhManifestFileContent, expectedOutput); +}); + +add_task(async function test_maybeWriteManifestFilesIncorrect() { + let nmhManifestFile = await IOUtils.getFile( + USER_TEST_PATH, + `${NATIVE_MESSAGING_HOST_ID}.json` + ); + + let incorrectInput = { + name: NATIVE_MESSAGING_HOST_ID, + description: "Manifest with unexpected description", + path: binFile.path, + type: "stdio", + allowed_origins: DUAL_BROWSER_EXTENSION_ORIGIN, + }; + await IOUtils.writeJSON(nmhManifestFile.path, incorrectInput); + + // Write correct JSON to the file and check to make sure it matches + // the expected output + await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles( + USER_TEST_PATH, + NATIVE_MESSAGING_HOST_ID, + DUAL_BROWSER_EXTENSION_ORIGIN + ); + let expectedOutput = JSON.stringify(getExpectedOutput()); + + let nmhManifestFilePath = PathUtils.join( + USER_TEST_PATH, + `${NATIVE_MESSAGING_HOST_ID}.json` + ); + let nmhManifestFileContent = await IOUtils.readUTF8(nmhManifestFilePath); + await IOUtils.remove(nmhManifestFilePath); + Assert.equal(nmhManifestFileContent, expectedOutput); +}); + +add_task(async function test_maybeWriteManifestFilesAlreadyExists() { + // Write file and confirm it exists + await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles( + USER_TEST_PATH, + NATIVE_MESSAGING_HOST_ID, + DUAL_BROWSER_EXTENSION_ORIGIN + ); + let nmhManifestFile = await IOUtils.getFile( + USER_TEST_PATH, + `${NATIVE_MESSAGING_HOST_ID}.json` + ); + + // Modify file modificatiomn time to be older than the write time + let oldModificationTime = Date.now() - 1000000; + let setModificationTime = await IOUtils.setModificationTime( + nmhManifestFile.path, + oldModificationTime + ); + Assert.equal(oldModificationTime, setModificationTime); + + // Call function which writes correct JSON to the file and make sure + // the modification time is the same, meaning we haven't written anything + await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles( + USER_TEST_PATH, + NATIVE_MESSAGING_HOST_ID, + DUAL_BROWSER_EXTENSION_ORIGIN + ); + let stat = await IOUtils.stat(nmhManifestFile.path); + await IOUtils.remove(nmhManifestFile.path); + Assert.equal(stat.lastModified, oldModificationTime); +}); + +add_task(async function test_maybeWriteManifestFilesDirDoesNotExist() { + let testDir = dir.clone(); + // This folder does not exist, so we want to make sure it's created + testDir.append("dirDoesNotExist"); + await FirefoxBridgeExtensionUtils.maybeWriteManifestFiles( + testDir.path, + NATIVE_MESSAGING_HOST_ID, + DUAL_BROWSER_EXTENSION_ORIGIN + ); + + ok(await IOUtils.exists(testDir.path)); + ok( + await IOUtils.exists( + PathUtils.join(testDir.path, `${NATIVE_MESSAGING_HOST_ID}.json`) + ) + ); + await IOUtils.remove(testDir.path, { recursive: true }); +}); + +add_task(async function test_ensureRegistered() { + let expectedJSONDirPath = null; + let nativeHostId = "org.mozilla.firefox_bridge_nmh"; + if (AppConstants.NIGHTLY_BUILD) { + nativeHostId = "org.mozilla.firefox_bridge_nmh_nightly"; + } else if (AppConstants.MOZ_DEV_EDITION) { + nativeHostId = "org.mozilla.firefox_bridge_nmh_dev"; + } else if (AppConstants.IS_ESR) { + nativeHostId = "org.mozilla.firefox_bridge_nmh_esr"; + } + + if (AppConstants.platform == "macosx") { + expectedJSONDirPath = PathUtils.joinRelative( + userDir.path, + "Library/Application Support/Google/Chrome/NativeMessagingHosts/" + ); + } else if (AppConstants.platform == "win") { + expectedJSONDirPath = PathUtils.joinRelative( + appDir.path, + "Mozilla\\Firefox" + ); + } else { + throw new Error("Unsupported platform"); + } + + ok(!(await IOUtils.exists(expectedJSONDirPath))); + let expectedJSONPath = PathUtils.join( + expectedJSONDirPath, + `${nativeHostId}.json` + ); + + await FirefoxBridgeExtensionUtils.ensureRegistered(); + let realOutput = { + name: nativeHostId, + description: "Firefox Native Messaging Host", + path: binFile.path, + type: "stdio", + allowed_origins: FirefoxBridgeExtensionUtils.getExtensionOrigins(), + }; + + let expectedOutput = JSON.stringify(realOutput); + let JSONContent = await IOUtils.readUTF8(expectedJSONPath); + await IOUtils.remove(expectedJSONPath); + Assert.equal(JSONContent, expectedOutput); +}); diff --git a/browser/modules/test/unit/xpcshell.toml b/browser/modules/test/unit/xpcshell.toml index 1738e92194..dcc9a98985 100644 --- a/browser/modules/test/unit/xpcshell.toml +++ b/browser/modules/test/unit/xpcshell.toml @@ -5,6 +5,15 @@ skip-if = ["os == 'android'"] # bug 1730213 ["test_E10SUtils_nested_URIs.js"] +["test_FirefoxBridgeExtensionUtils.js"] +run-if = ["os == 'win'"] # Test of a Windows-specific feature + +["test_FirefoxBridgeExtensionUtilsNativeManifest.js"] +run-if = [ + "os == 'win'", + "os == 'mac'", +] + ["test_HomePage.js"] ["test_HomePage_ignore.js"] |