summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/test/browser_target_parents.js
blob: d66339739cf63250394297b3d1fe803fb048d200 (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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

// Test a given Target's parentFront attribute returns the correct parent front.

const {
  DevToolsClient,
} = require("resource://devtools/client/devtools-client.js");
const {
  DevToolsServer,
} = require("resource://devtools/server/devtools-server.js");
const {
  createCommandsDictionary,
} = require("resource://devtools/shared/commands/index.js");

const TEST_URL = `data:text/html;charset=utf-8,<div id="test"></div>`;

// Test against Tab targets
add_task(async function () {
  const tab = await addTab(TEST_URL);

  const client = await setupDebuggerClient();
  const mainRoot = client.mainRoot;

  const tabDescriptors = await mainRoot.listTabs();

  const concurrentCommands = [];
  for (const descriptor of tabDescriptors) {
    concurrentCommands.push(
      (async () => {
        const commands = await createCommandsDictionary(descriptor);
        // Descriptor's getTarget will only work if the TargetCommand watches for the first top target
        await commands.targetCommand.startListening();
      })()
    );
  }
  info("Instantiate all tab's commands and initialize their TargetCommand");
  await Promise.all(concurrentCommands);

  await testGetTargetWithConcurrentCalls(tabDescriptors, tabTarget => {
    // We only call BrowsingContextTargetFront.attach and not TargetMixin.attachAndInitThread.
    // So very few things are done.
    return !!tabTarget.targetForm?.traits;
  });

  await client.close();
  await removeTab(tab);
});

// Test against Process targets
add_task(async function () {
  const client = await setupDebuggerClient();
  const mainRoot = client.mainRoot;

  const processes = await mainRoot.listProcesses();

  // Assert that concurrent calls to getTarget resolves the same target and that it is already attached
  // With that, we were chasing a precise race, where a second call to ProcessDescriptor.getTarget()
  // happens between the instantiation of ContentProcessTarget and its call to attach() from getTarget
  // function.
  await testGetTargetWithConcurrentCalls(processes, () => {
    // We only call ContentProcessTargetFront.attach and not TargetMixin.attachAndInitThread.
    // So nothing is done for content process targets.
    return true;
  });

  await client.close();
});

// Test against Webextension targets
add_task(async function () {
  const client = await setupDebuggerClient();

  const mainRoot = client.mainRoot;

  const addons = await mainRoot.listAddons();
  await Promise.all(
    // some extensions, such as themes, are not debuggable. Filter those out
    // before trying to connect.
    addons
      .filter(a => a.debuggable)
      .map(async addonDescriptorFront => {
        const addonFront = await addonDescriptorFront.getTarget();
        ok(addonFront, "Got the addon target");
      })
  );

  await client.close();
});

// Test against worker targets on parent process
add_task(async function () {
  const client = await setupDebuggerClient();

  const mainRoot = client.mainRoot;

  const { workers } = await mainRoot.listWorkers();

  ok(!!workers.length, "list workers returned a non-empty list of workers");

  for (const workerDescriptorFront of workers) {
    let targetFront;
    try {
      targetFront = await workerDescriptorFront.getTarget();
    } catch (e) {
      // Ignore race condition where we are trying to connect to a worker
      // related to a previous test which is being destroyed.
      if (
        e.message.includes("nsIWorkerDebugger.initialize") ||
        workerDescriptorFront.isDestroyed() ||
        !workerDescriptorFront.name
      ) {
        info("Failed to connect to " + workerDescriptorFront.url);
        continue;
      }
      throw e;
    }
    // Bug 1767760: name might be null on some worker which are probably initializing or destroying.
    if (!workerDescriptorFront.name) {
      info("Failed to connect to " + workerDescriptorFront.url);
      continue;
    }

    is(
      workerDescriptorFront,
      targetFront,
      "For now, worker descriptors and targets are the same object (see bug 1667404)"
    );
    // Check that accessing descriptor#name getter doesn't throw (See Bug 1714974).
    ok(
      workerDescriptorFront.name.includes(".js") ||
        workerDescriptorFront.name.includes(".mjs"),
      `worker descriptor front holds the worker file name (${workerDescriptorFront.name})`
    );
    is(
      workerDescriptorFront.isWorkerDescriptor,
      true,
      "isWorkerDescriptor is true"
    );
  }

  await client.close();
});

async function setupDebuggerClient() {
  // Instantiate a minimal server
  DevToolsServer.init();
  DevToolsServer.allowChromeProcess = true;
  if (!DevToolsServer.createRootActor) {
    DevToolsServer.registerAllActors();
  }
  const transport = DevToolsServer.connectPipe();
  const client = new DevToolsClient(transport);
  await client.connect();
  return client;
}

async function testGetTargetWithConcurrentCalls(descriptors, isTargetAttached) {
  // Assert that concurrent calls to getTarget resolves the same target and that it is already attached
  await Promise.all(
    descriptors.map(async descriptor => {
      const promises = [];
      const concurrentCalls = 10;
      for (let i = 0; i < concurrentCalls; i++) {
        const targetPromise = descriptor.getTarget();
        // Every odd runs, wait for a tick to introduce some more randomness
        if (i % 2 == 0) {
          await wait(0);
        }
        promises.push(
          targetPromise.then(target => {
            ok(isTargetAttached(target), "The target is attached");
            return target;
          })
        );
      }
      const targets = await Promise.all(promises);
      for (let i = 1; i < concurrentCalls; i++) {
        is(
          targets[0],
          targets[i],
          "All the targets returned by concurrent calls to getTarget are the same"
        );
      }
    })
  );
}