summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/browser/browser_inspector-retain.js
blob: 43d156675e90e06c204add473417f3efa155ac7a (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
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js",
  this
);

// Retain a node, and a second-order child (in another document, for kicks)
// Release the parent of the top item, which should cause one retained orphan.

// Then unretain the top node, which should retain the orphan.

// Then change the source of the iframe, which should kill that orphan.

add_task(async function testRetain() {
  // The test does not make sense when EFT is enabled, as different documents will have
  // different walkers.
  if (isEveryFrameTargetEnabled()) {
    return;
  }

  const { walker } = await initInspectorFront(
    MAIN_DOMAIN + "inspector-traversal-data.html"
  );

  // Get the toplevel body element and retain it.
  const bodyFront = await walker.querySelector(walker.rootNode, "body");
  await walker.retainNode(bodyFront);
  // Get an element in the child frame and retain it.
  const frame = await walker.querySelector(walker.rootNode, "#childFrame");
  const children = await walker.children(frame, { maxNodes: 1 });
  const childDoc = children.nodes[0];
  const childListFront = await walker.querySelector(childDoc, "#longlist");
  const originalOwnershipSize = await assertOwnershipTrees(walker);
  // and retain it.
  await walker.retainNode(childListFront);
  // OK, try releasing the parent of the first retained.
  await walker.releaseNode(bodyFront.parentNode());
  const clientTree = clientOwnershipTree(walker);

  // That request should have freed the parent of the first retained
  // but moved the rest into the retained orphaned tree.
  is(
    ownershipTreeSize(clientTree.root) +
      ownershipTreeSize(clientTree.retained[0]) +
      1,
    originalOwnershipSize,
    "Should have only lost one item overall."
  );
  is(walker._retainedOrphans.size, 1, "Should have retained one orphan");
  ok(
    walker._retainedOrphans.has(bodyFront),
    "Should have retained the expected node."
  );
  // Unretain the body, which should promote the childListFront to a retained orphan.
  await walker.unretainNode(bodyFront);
  await assertOwnershipTrees(walker);

  is(
    walker._retainedOrphans.size,
    1,
    "Should still only have one retained orphan."
  );
  ok(
    !walker._retainedOrphans.has(bodyFront),
    "Should have dropped the body node."
  );
  ok(
    walker._retainedOrphans.has(childListFront),
    "Should have retained the child node."
  );

  // Change the source of the iframe, which should kill the retained orphan.
  const onMutations = waitForMutation(walker, isUnretained);
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    content.document.querySelector("#childFrame").src =
      "data:text/html,<html>new child</html>";
  });
  await onMutations;

  await assertOwnershipTrees(walker);
  is(walker._retainedOrphans.size, 0, "Should have no more retained orphans.");
});

// Get a hold of a node, remove it from the doc and retain it at the same time.
// We should always win that race (even though the mutation happens before the
// retain request), because we haven't issued `getMutations` yet.
add_task(async function testWinRace() {
  const { walker } = await initInspectorFront(
    MAIN_DOMAIN + "inspector-traversal-data.html"
  );

  const front = await walker.querySelector(walker.rootNode, "#a");
  const onMutation = waitForMutation(walker, isChildList);
  SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    const contentNode = content.document.querySelector("#a");
    contentNode.remove();
  });
  // Now wait for that mutation and retain response to come in.
  await walker.retainNode(front);
  await onMutation;

  await assertOwnershipTrees(walker);
  is(walker._retainedOrphans.size, 1, "Should have a retained orphan.");
  ok(
    walker._retainedOrphans.has(front),
    "Should have retained our expected node."
  );
  await walker.unretainNode(front);

  // Make sure we're clear for the next test.
  await assertOwnershipTrees(walker);
  is(walker._retainedOrphans.size, 0, "Should have no more retained orphans.");
});

// Same as above, but issue the request right after the 'new-mutations' event, so that
// we *lose* the race.
add_task(async function testLoseRace() {
  const { walker } = await initInspectorFront(
    MAIN_DOMAIN + "inspector-traversal-data.html"
  );

  const front = await walker.querySelector(walker.rootNode, "#z");
  const onMutation = walker.once("new-mutations");
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    const contentNode = content.document.querySelector("#z");
    contentNode.remove();
  });
  await onMutation;

  // Verify that we have an outstanding request (no good way to tell that it's a
  // getMutations request, but there's nothing else it would be).
  is(walker._requests.length, 1, "Should have an outstanding request.");
  try {
    await walker.retainNode(front);
    ok(false, "Request should not have succeeded!");
  } catch (err) {
    // XXX: Switched to from ok() to todo_is() in Bug 1467712. Follow up in
    // 1500960
    // This is throwing because of
    // `gInspectee.querySelector("#z").parentNode = null;` two blocks above...
    // Even if you fix that, the test is still failing because "#a" was removed
    // by the previous test. I am switching this to "#z" because I think that
    // was the original intent. Still not failing with the expected error message
    // Needs more work.
    // ok(err, "noSuchActor", "Should have lost the race.");
    is(
      walker._retainedOrphans.size,
      0,
      "Should have no more retained orphans."
    );
    // Don't re-throw the error.
  }
});