summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/head_native_messaging.js
blob: f7c619b76af06be8c1141669be01a5b2888449b6 (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
/* -*- 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 */

XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry",
                                  "resource://testing-common/MockRegistry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                  "resource://gre/modules/Timer.jsm");

let {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm");


// 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);

do_register_cleanup(() => {
  tmpDir.remove(true);
});

function getPath(filename) {
  return OS.Path.join(tmpDir.path, filename);
}

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


function* setupHosts(scripts) {
  const PERMS = {unixMode: 0o755};

  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
  const pythonPath = yield Subprocess.pathSearch(env.get("PYTHON"));

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

    yield OS.File.writeAtomic(scriptPath, body);
    yield OS.File.setPermissions(scriptPath, PERMS);

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

    let manifestPath = getPath(`${script.name}.json`);
    yield OS.File.writeAtomic(manifestPath, JSON.stringify(manifest));

    return manifestPath;
  }

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

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

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

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

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

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

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

        let batBody = `@ECHO OFF\n${pythonPath} -u "${scriptPath}" %*\n`;
        yield OS.File.writeAtomic(batPath, batBody);

        // Create absolute and relative path versions of the entry.
        for (let [name, path] of [[script.name, batPath],
                                  [`relative.${script.name}`, OS.Path.basename(batPath)]]) {
          script.name = name;
          let manifestPath = yield writeManifest(script, scriptPath, path);

          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));
  });
}