summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/head_native_messaging.js
blob: 32b6948033c9f165695d2753ae25a0abab9d77af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

/* globals AppConstants, FileUtils */
/* exported getSubprocessCount, setupHosts, waitForSubprocessExit */

ChromeUtils.defineESModuleGetters(this, {
  MockRegistry: "resource://testing-common/MockRegistry.sys.mjs",
});
if (AppConstants.platform == "win") {
  ChromeUtils.defineESModuleGetters(this, {
    SubprocessImpl: "resource://gre/modules/subprocess/subprocess_win.sys.mjs",
  });
} else {
  ChromeUtils.defineESModuleGetters(this, {
    SubprocessImpl: "resource://gre/modules/subprocess/subprocess_unix.sys.mjs",
  });
}

const { Subprocess } = ChromeUtils.importESModule(
  "resource://gre/modules/Subprocess.sys.mjs"
);

// It's important that we use a space in this directory name to make sure we
// correctly handle executing batch files with spaces in their path.
let tmpDir = FileUtils.getDir("TmpD", ["Native Messaging"]);
tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

const TYPE_SLUG =
  AppConstants.platform === "linux"
    ? "native-messaging-hosts"
    : "NativeMessagingHosts";

add_setup(async function setup() {
  await IOUtils.makeDirectory(PathUtils.join(tmpDir.path, TYPE_SLUG));
});

registerCleanupFunction(async () => {
  await IOUtils.remove(tmpDir.path, { recursive: true });
});

function getPath(filename) {
  return PathUtils.join(tmpDir.path, TYPE_SLUG, filename);
}

const ID = "native@tests.mozilla.org";

async function setupHosts(scripts) {
  const pythonPath = await Subprocess.pathSearch(Services.env.get("PYTHON"));

  async function writeManifest(script, scriptPath, path) {
    let body = `#!${pythonPath} -u\n${script.script}`;

    await IOUtils.writeUTF8(scriptPath, body);
    await IOUtils.setPermissions(scriptPath, 0o755);

    let manifest = {
      name: script.name,
      description: script.description,
      path,
      type: "stdio",
      allowed_extensions: [ID],
    };

    // Optionally, allow the test to change the manifest before writing.
    script._hookModifyManifest?.(manifest);

    let manifestPath = getPath(`${script.name}.json`);
    await IOUtils.writeJSON(manifestPath, manifest);

    return manifestPath;
  }

  switch (AppConstants.platform) {
    case "macosx":
    case "linux":
      let dirProvider = {
        getFile(property) {
          if (property == "XREUserNativeManifests") {
            return tmpDir.clone();
          } else if (property == "XRESysNativeManifests") {
            return tmpDir.clone();
          }
          return null;
        },
      };

      Services.dirsvc.registerProvider(dirProvider);
      registerCleanupFunction(() => {
        Services.dirsvc.unregisterProvider(dirProvider);
      });

      for (let script of scripts) {
        let path = getPath(`${script.name}.py`);

        await writeManifest(script, path, path);
      }
      break;

    case "win":
      const REGKEY = String.raw`Software\Mozilla\NativeMessagingHosts`;

      let registry = new MockRegistry();
      registerCleanupFunction(() => {
        registry.shutdown();
      });

      for (let script of scripts) {
        let { scriptExtension = "bat" } = script;

        // It's important that we use a space in this filename. See directory
        // name comment above.
        let batPath = getPath(`batch ${script.name}.${scriptExtension}`);
        let scriptPath = getPath(`${script.name}.py`);

        let batBody = `@ECHO OFF\n${pythonPath} -u "${scriptPath}" %*\n`;
        await IOUtils.writeUTF8(batPath, batBody);

        let manifestPath = await writeManifest(script, scriptPath, batPath);

        registry.setValue(
          Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
          `${REGKEY}\\${script.name}`,
          "",
          manifestPath
        );
      }
      break;

    default:
      ok(
        false,
        `Native messaging is not supported on ${AppConstants.platform}`
      );
  }
}

function getSubprocessCount() {
  return SubprocessImpl.Process.getWorker()
    .call("getProcesses", [])
    .then(result => result.size);
}
function waitForSubprocessExit() {
  return SubprocessImpl.Process.getWorker()
    .call("waitForNoProcesses", [])
    .then(() => {
      // Return to the main event loop to give IO handlers enough time to consume
      // their remaining buffered input.
      return new Promise(resolve => setTimeout(resolve, 0));
    });
}