summaryrefslogtreecommitdiffstats
path: root/docshell/test/unit/test_subframe_stop_after_parent_error.js
blob: 2f80d18ebb986ab014c9ac7e00ca73222e83c5ae (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
"use strict";
// Tests that pending subframe requests for an initial about:blank
// document do not delay showing load errors (and possibly result in a
// crash at docShell destruction) for failed document loads.

const { PromiseTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PromiseTestUtils.sys.mjs"
);
PromiseTestUtils.allowMatchingRejectionsGlobally(/undefined/);

const { XPCShellContentUtils } = ChromeUtils.importESModule(
  "resource://testing-common/XPCShellContentUtils.sys.mjs"
);

XPCShellContentUtils.init(this);

const server = XPCShellContentUtils.createHttpServer({
  hosts: ["example.com"],
});

// Registers a URL with the HTTP server which will not return a response
// until we're ready to.
function registerSlowPage(path) {
  let result = {
    url: `http://example.com/${path}`,
  };

  let finishedPromise = new Promise(resolve => {
    result.finish = resolve;
  });

  server.registerPathHandler(`/${path}`, async (request, response) => {
    response.processAsync();

    response.setHeader("Content-Type", "text/html");
    response.write("<html><body>Hello.</body></html>");

    await finishedPromise;

    response.finish();
  });

  return result;
}

let topFrameRequest = registerSlowPage("top.html");
let subFrameRequest = registerSlowPage("frame.html");

let thunks = new Set();
function promiseStateStop(webProgress) {
  return new Promise(resolve => {
    let listener = {
      onStateChange(aWebProgress, request, stateFlags, status) {
        if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
          webProgress.removeProgressListener(listener);

          thunks.delete(listener);
          resolve();
        }
      },

      QueryInterface: ChromeUtils.generateQI([
        "nsIWebProgressListener",
        "nsISupportsWeakReference",
      ]),
    };

    // Keep the listener alive, since it's stored as a weak reference.
    thunks.add(listener);
    webProgress.addProgressListener(
      listener,
      Ci.nsIWebProgress.NOTIFY_STATE_REQUEST
    );
  });
}

async function runTest(waitForErrorPage) {
  let page = await XPCShellContentUtils.loadContentPage("about:blank", {
    remote: false,
  });
  let { browser } = page;

  // Watch for the HTTP request for the top frame so that we can cancel
  // it with an error.
  let requestPromise = TestUtils.topicObserved(
    "http-on-modify-request",
    subject => subject.QueryInterface(Ci.nsIRequest).name == topFrameRequest.url
  );

  // Create a frame with a source URL which will not return a response
  // before we cancel it with an error.
  let doc = browser.contentDocument;
  let frame = doc.createElement("iframe");
  frame.src = topFrameRequest.url;
  doc.body.appendChild(frame);

  // Create a subframe in the initial about:blank document for the above
  // frame which will not return a response before we cancel the
  // document request.
  let frameDoc = frame.contentDocument;
  let subframe = frameDoc.createElement("iframe");
  subframe.src = subFrameRequest.url;
  frameDoc.body.appendChild(subframe);

  let [req] = await requestPromise;

  info("Cancel request for parent frame");
  req.cancel(Cr.NS_ERROR_PROXY_CONNECTION_REFUSED);

  // Request cancelation is not synchronous, so wait for the STATE_STOP
  // event to fire.
  await promiseStateStop(
    browser.docShell.nsIInterfaceRequestor.getInterface(Ci.nsIWebProgress)
  );

  // And make a trip through the event loop to give the DocLoader a
  // chance to update its state.
  await new Promise(executeSoon);

  if (waitForErrorPage) {
    // Make sure that canceling the request with an error code actually
    // shows an error page without waiting for a subframe response.
    await TestUtils.waitForCondition(() =>
      frame.contentDocument.documentURI.startsWith("about:neterror?")
    );
  }

  info("Remove frame");
  frame.remove();

  await page.close();
}

add_task(async function testRemoveFrameImmediately() {
  await runTest(false);
});

add_task(async function testRemoveFrameAfterErrorPage() {
  await runTest(true);
});

add_task(async function () {
  // Allow the document requests for the frames to complete.
  topFrameRequest.finish();
  subFrameRequest.finish();
});