Ctrl K
ComunidadGuías…

electron/chrome-rail.node-test.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import { readFileSync } from "node:fs";
import test from "node:test";
import vm from "node:vm";

import { CHROME_RAIL_PATH, CHROME_RAIL_WIDTH, RAIL_OFFER_GRACE_MS, createChromeRail } from "./chrome-rail.mjs";
import { TRAFFIC_LIGHTS } from "./window-chrome.mjs";

const RAIL = "http://127.0.0.1:8799/rail";

/** The app's window: its page (what it is told, and the rail's state once it asked) and the views laid over it. */
function appWindow() {
  const page = Object.assign(new EventEmitter(), { mainFrame: { id: "page-frame" }, told: [], states: [], destroyed: false,
    send(channel, value) { if (channel === "rail:beside") this.told.push(value); if (channel === "rail:state") this.states.push(value); }, isDestroyed() { return this.destroyed; } });
  const children = [];
  const win = Object.assign(new EventEmitter(), { webContents: page, size: [1440, 900], destroyed: false, lights: [],
    getContentSize() { return this.size; }, isDestroyed() { return this.destroyed; }, setWindowButtonPosition(point) { this.lights.push(point); },
    contentView: { addChildView: (view) => children.push(view), removeChildView: (view) => children.splice(children.indexOf(view), 1) } });
  return { win, page, children };
}

function viewMaker() {
  const made = [];
  const createView = (options) => {
    const contents = Object.assign(new EventEmitter(), { mainFrame: { id: "rail-frame" }, loaded: [], sent: [], focused: 0, closed: false, opener: null,
      loadURL(url) { this.loaded.push(url); return Promise.resolve(); }, setWindowOpenHandler(handler) { this.opener = handler; },
      send(channel, ...values) { this.sent.push([channel, ...values]); }, focus() { this.focused++; }, isDestroyed() { return this.closed; }, close() { this.closed = true; } });
    const view = { options, webContents: contents, bounds: null, background: null, setBounds(bounds) { this.bounds = bounds; }, setBackgroundColor(color) { this.background = color; } };
    made.push(view);
    return view;
  };
  return { made, createView };
}

function world({ wanted = true, hidden = false, frame = true } = {}) {
  const { win, page, children } = appWindow();
  const { made, createView } = viewMaker();
  const state = { wanted, hidden, changes: 0 };
  const prefs = { railHidden: () => state.hidden, setRailHidden: (value) => { state.hidden = value; }, windowFrame: () => frame };
  const rail = createChromeRail({ createView, preload: "/app/preload.cjs", args: () => ["--omb-local-origin=http://127.0.0.1:8799", "--omb-company-desktop=1"], url: () => RAIL,
    wanted: () => state.wanted, prefs: () => prefs, changed: () => { state.changes++; } });
  const handlers = new Map();
  rail.install({ handle: (channel, handler) => handlers.set(channel, handler) });
  /** The page beside the rail asks, from its main frame. */
  const ask = (channel, ...values) => handlers.get(channel)({ sender: page, senderFrame: page.mainFrame }, ...values);
  return { rail, win, page, children, made, state, handlers, ask, settle: () => new Promise((resolve) => setImmediate(resolve)) };
}

test("two places or more: the rail's own page in a view at the window's left edge, and the page beside it moves over", async () => {
  const w = world();
  w.rail.attach(w.win);
  await w.settle();
  assert.equal(w.made.length, 1);
  const [view] = w.made;
  assert.deepEqual(w.children, [view]);
  assert.deepEqual(view.bounds, { x: 0, y: 0, width: CHROME_RAIL_WIDTH, height: 900 });
  assert.equal(view.background, "#00000000", "transparent where the rail draws nothing");
  assert.deepEqual(view.webContents.loaded, [RAIL]);
  assert.deepEqual(view.options.webPreferences, { contextIsolation: true, sandbox: true, nodeIntegration: false, preload: "/app/preload.cjs",
    additionalArguments: ["--omb-local-origin=http://127.0.0.1:8799", "--omb-company-desktop=1"] });
  assert.deepEqual(w.page.told, [CHROME_RAIL_WIDTH], "the page beside it is told how wide the rail is");
  w.win.size = [1000, 700];
  w.win.emit("resize");
  assert.deepEqual(view.bounds, { x: 0, y: 0, width: CHROME_RAIL_WIDTH, height: 700 }, "the rail follows the window's height");
  assert.equal(w.rail.shown(), true);
});

test("a sheet of the rail's covers the whole window until it closes; only the rail's page may ask", async () => {
  const w = world();
  const handlers = w.handlers;
  w.rail.attach(w.win);
  const [view] = w.made;
  const fromRail = { sender: view.webContents, senderFrame: view.webContents.mainFrame };
  handlers.get("rail:expand")(fromRail, true);
  assert.deepEqual(view.bounds, { x: 0, y: 0, width: 1440, height: 900 });
  assert.equal(view.webContents.focused, 1, "the sheet takes the keyboard");
  handlers.get("rail:expand")(fromRail, false);
  assert.deepEqual(view.bounds, { x: 0, y: 0, width: CHROME_RAIL_WIDTH, height: 900 });
  for (const event of [{ sender: w.page, senderFrame: w.page.mainFrame }, { sender: view.webContents, senderFrame: { id: "an-iframe" } }, {}]) {
    assert.throws(() => handlers.get("rail:expand")(event, true), /only available to the window's rail/);
  }
  assert.equal(w.rail.owns(fromRail), true);
  assert.equal(w.rail.owns({ sender: w.page, senderFrame: w.page.mainFrame }), false, "a place's page is never the rail");
});

test("each new document beside the rail is told again; with fewer than two places the rail goes and the page has the window", async () => {
  const w = world();
  w.rail.attach(w.win);
  w.page.emit("dom-ready");
  assert.deepEqual(w.page.told, [CHROME_RAIL_WIDTH, CHROME_RAIL_WIDTH], "the next server's page moves over too");
  w.state.wanted = false;
  w.rail.refresh();
  assert.deepEqual(w.children, []);
  assert.deepEqual(w.page.told.at(-1), 0, "the room is given back");
  w.page.emit("dom-ready");
  assert.equal(w.page.told.length, 3, "a page with no rail beside it is told nothing more");
  w.rail.expand(true);
  assert.equal(w.rail.expanded(), false, "nothing to cover while there is no rail");
  w.state.wanted = true;
  w.rail.refresh();
  assert.equal(w.made.length, 1, "the same page comes back");
  assert.deepEqual(w.children, [w.made[0]]);
});

test("the rail's page stays this app's rail: no other page, no new window; the window closing closes it", async () => {
  const w = world();
  w.rail.attach(w.win);
  const contents = w.made[0].webContents;
  for (const [target, kept] of [[RAIL, true], ["https://evil.example.test/", false], ["http://127.0.0.1:8799/", false]]) {
    let prevented = false;
    contents.emit("will-navigate", { preventDefault: () => { prevented = true; } }, target);
    assert.equal(prevented, !kept, target);
  }
  assert.deepEqual(contents.opener({ url: "https://evil.example.test/" }), { action: "deny" });
  w.win.emit("closed");
  assert.equal(contents.closed, true);
  assert.equal(w.rail.shown(), false);
});

test("nothing to show: no view is made at all", () => {
  const w = world({ wanted: false });
  w.rail.attach(w.win);
  assert.deepEqual([w.made.length, w.children.length, w.page.told.length], [0, 0, 0]);
});

test("the page beside the rail offers the button: hiding gives the page the whole window, and this desktop keeps the choice", () => {
  const w = world();
  w.rail.attach(w.win);
  const [view] = w.made;
  assert.deepEqual(w.ask("rail:offer"), { available: true, hidden: false }, "whether there is a rail to hide, never its places");
  assert.deepEqual(w.ask("rail:hide", true), { available: true, hidden: true });
  assert.equal(w.state.hidden, true, "kept with this desktop's own preferences");
  assert.deepEqual([w.children, w.page.told.at(-1), w.rail.shown(), w.state.changes], [[], 0, false, 2], "the page has the whole window, and pages hear the rail went");
  assert.deepEqual(w.ask("rail:hide", "yes"), { available: true, hidden: false }, "only true hides it");
  assert.deepEqual([w.children, w.page.told.at(-1), w.made.length], [[view], CHROME_RAIL_WIDTH, 1], "the same page comes back beside it");
  w.ask("rail:hide", true);
  w.state.wanted = false;
  w.rail.refresh();
  assert.deepEqual(w.page.states, [{ available: false, hidden: true }], "told when there is no rail to show any more, and never what it was just answered");
});

test("only the page beside the rail, in its main frame, offers the button: never the rail's own page, a frame inside the page or another window", () => {
  const w = world();
  w.rail.attach(w.win);
  const [view] = w.made, other = appWindow().page;
  for (const event of [{ sender: view.webContents, senderFrame: view.webContents.mainFrame }, { sender: w.page, senderFrame: { id: "an-iframe" } },
    { sender: other, senderFrame: other.mainFrame }, {}]) {
    assert.throws(() => w.handlers.get("rail:offer")(event), /only available to the page beside the window's rail/);
    assert.throws(() => w.handlers.get("rail:hide")(event, true), /only available to the page beside the window's rail/);
  }
  assert.deepEqual([w.state.hidden, w.rail.shown()], [false, true], "nothing hidden, nothing kept");
});

test("after the page moves on, a hidden rail waits a few seconds for the new page's button: this app's page keeps it hidden, an older one gets it back", (t) => {
  t.mock.timers.enable({ apis: ["setTimeout"] });
  const w = world({ hidden: true });
  w.rail.attach(w.win);
  assert.deepEqual([w.rail.shown(), w.made.length], [false, 0], "hidden while the window's first page loads");
  w.page.emit("did-navigate");
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS - 1);
  w.ask("rail:offer");
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS);
  assert.deepEqual([w.rail.shown(), w.made.length, w.page.told], [false, 0, []], "the page offered its button in time: no flicker");
  w.page.emit("did-navigate");
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS - 1);
  assert.equal(w.rail.shown(), false, "still time for the next page");
  t.mock.timers.tick(1);
  assert.deepEqual([w.rail.shown(), w.page.told.at(-1)], [true, CHROME_RAIL_WIDTH], "an older server's page with no button: the rail comes back");
  assert.equal(w.state.hidden, true, "and the choice is kept");
  w.page.emit("did-navigate");
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS);
  assert.equal(w.rail.shown(), true, "a rail back beside a page with no button stays while the next one loads");
  w.ask("rail:offer");
  assert.deepEqual([w.rail.shown(), w.page.told.at(-1)], [false, 0], "until that one offers its button");
});

test("the traffic lights sit in the frame's top bar beside a page that draws it, and go back where an older release left room for them", (t) => {
  t.mock.timers.enable({ apis: ["setTimeout"] });
  const w = world();
  w.rail.attach(w.win);
  const framed = () => w.made[0].webContents.sent.filter(([channel]) => channel === "rail:framed").map(([, value]) => value);
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS - 1);
  w.ask("rail:draws-frame");
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS);
  assert.deepEqual([w.rail.framed(), w.win.lights, framed()], [true, [], []], "this app's page draws the frame in time: the window stays as it opened");
  w.page.emit("did-navigate");
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS - 1);
  assert.equal(w.rail.framed(), true, "still time for the next page");
  t.mock.timers.tick(1);
  assert.deepEqual([w.rail.framed(), w.win.lights, framed()], [false, [TRAFFIC_LIGHTS.unframed], [false]],
    "an older Cloud's page, which never says so: the lights go back to its row, and the rail's places under them");
  w.made[0].webContents.emit("did-finish-load");
  assert.deepEqual(framed(), [false, false], "the rail's page, loaded again, hears it too");
  w.page.emit("did-navigate");
  w.ask("rail:draws-frame");
  assert.deepEqual([w.rail.framed(), w.win.lights.at(-1), framed().at(-1)], [true, TRAFFIC_LIGHTS.framed, true], "the next page draws it: back in the bar at once");
  const [view] = w.made;
  for (const event of [{ sender: view.webContents, senderFrame: view.webContents.mainFrame }, { sender: w.page, senderFrame: { id: "an-iframe" } }, {}]) {
    assert.throws(() => w.handlers.get("rail:draws-frame")(event), /only available to the page beside the window's rail/);
  }
});

test("a page with the frame turned off says it draws none: the traffic lights go back at once, and the window opens that way next time", (t) => {
  t.mock.timers.enable({ apis: ["setTimeout"] });
  const w = world();
  w.rail.attach(w.win);
  w.ask("rail:draws-frame", true);
  w.ask("rail:draws-frame", false);
  assert.deepEqual([w.rail.framed(), w.win.lights], [false, [TRAFFIC_LIGHTS.unframed]], "no grace to wait out: the page said so");
  w.ask("rail:draws-frame", true);
  assert.deepEqual([w.rail.framed(), w.win.lights.at(-1)], [true, TRAFFIC_LIGHTS.framed], "and back in the bar when it is turned on again");
  const off = world({ frame: false });
  off.rail.attach(off.win);
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS);
  assert.deepEqual([off.rail.framed(), off.win.lights], [false, []], "a window made with the frame off opened its lights where they stay (windowChromeOptions)");
});

test("a page that stops responding, crashes or gives way to an error page gets the rail back", (t) => {
  t.mock.timers.enable({ apis: ["setTimeout"] });
  const w = world({ hidden: true });
  w.rail.attach(w.win);
  w.ask("rail:offer");
  w.page.emit("unresponsive");
  assert.equal(w.rail.shown(), true, "a hung page can't bring it back");
  w.page.emit("responsive");
  assert.equal(w.rail.shown(), false, "hidden again once it answers");
  w.page.emit("render-process-gone");
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS);
  assert.equal(w.rail.shown(), true, "its page crashed, and nothing offered the button since");
  w.ask("rail:offer");
  w.page.emit("did-fail-load", {}, -3, "ERR_ABORTED", "https://cloud.example.test/", true);
  w.page.emit("did-fail-load", {}, -105, "ERR_NAME_NOT_RESOLVED", "https://cdn.example.test/", false);
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS);
  assert.equal(w.rail.shown(), false, "a load cut short by another, or a frame inside the page that failed, leaves the page as it was");
  w.page.emit("did-fail-load", {}, -105, "ERR_NAME_NOT_RESOLVED", "https://cloud.example.test/", true);
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS);
  assert.equal(w.rail.shown(), true, "an error page in its place has no button");
  w.win.emit("closed");
  t.mock.timers.tick(RAIL_OFFER_GRACE_MS);
});

test("main's own wiring: the choice is this desktop's, and the pages hear the rail come and go", () => {
  const main = readFileSync(new URL("./main.mjs", import.meta.url), "utf8").replace(/\r\n/g, "\n");
  const start = main.indexOf("const chromeRail = createChromeRail("), end = main.indexOf("chromeRail.install(ipcMain);", start);
  assert.ok(start >= 0 && end > start, "main makes the window's rail");
  const { made, createView } = viewMaker();
  const kept = { hidden: false }, signals = [], handlers = new Map();
  const context = vm.createContext({ createChromeRail, CHROME_RAIL_PATH, WebContentsView: function WebContentsView(options) { return createView(options); },
    path: { join: (...parts) => parts.join("/") }, __dirname: "/app", slog: () => {}, desktopCompanionRendererArguments: () => [],
    app: { isPackaged: true, getVersion: () => "0.1.133" }, desktopRemoteAccess: null, rendererOrigin: () => "http://127.0.0.1:8799", serverReady: true,
    railList: () => [{ id: "local" }, { id: "home" }], desktopPrefs: { railHidden: () => kept.hidden, setRailHidden: (hidden) => { kept.hidden = hidden; } },
    rail: { changed: () => signals.push("changed") }, ipcMain: { handle: (channel, handler) => handlers.set(channel, handler) } });
  const chromeRail = vm.runInContext(`${main.slice(start, end)}chromeRail.install(ipcMain); chromeRail`, context);
  const { win, page } = appWindow();
  chromeRail.attach(win);
  assert.deepEqual(made.map((view) => view.webContents.loaded), [["http://127.0.0.1:8799/rail"]]);
  handlers.get("rail:hide")({ sender: page, senderFrame: page.mainFrame }, true);
  assert.deepEqual([kept.hidden, chromeRail.shown(), signals], [true, false, ["changed", "changed"]]);
});

/** The app's preload in a page of the window, with what the room needs of the page: its document, size and zoom. */
function pageWithPreload(origin = "https://bots.example.test", pathname = "/", answer = () => undefined) {
  const listeners = new Map(), handlers = new Map(), invoked = [];
  const head = { children: [], append(node) { this.children.push(node); node.isConnected = true; } };
  const document = { documentElement: { style: { zoom: "" } }, head, createElement: () => ({ textContent: "", isConnected: false, remove() { head.children.splice(head.children.indexOf(this), 1); this.isConnected = false; } }) };
  const frame = { zoom: 1 }, navigator = { userActivation: { isActive: false } };
  let bridge = null;
  vm.runInNewContext(readFileSync(new URL("./preload.cjs", import.meta.url), "utf8"), {
    process: { platform: "darwin", argv: ["--omb-local-origin=http://127.0.0.1:8799", "--omb-company-desktop=1"] },
    location: { origin, pathname }, navigator, TextEncoder, localStorage: { getItem: () => null }, queueMicrotask, document,
    window: { addEventListener: (type, listener) => listeners.set(type, listener) },
    require: () => ({ webUtils: {}, webFrame: { getZoomFactor: () => frame.zoom }, contextBridge: { exposeInMainWorld: (_name, value) => { bridge = value; } },
      ipcRenderer: { on: (channel, handler) => handlers.set(channel, handler), removeListener: (channel, handler) => { if (handlers.get(channel) === handler) handlers.delete(channel); },
        send() {}, sendSync: () => null, invoke: (channel, ...values) => { invoked.push([channel, ...values]); return Promise.resolve(answer(channel, ...values)); } } }),
  });
  return { head, frame, root: document.documentElement, bridge, invoked, navigator, tell: (width) => handlers.get("rail:beside")({}, width), resize: () => listeners.get("resize")(),
    hear: (channel, ...values) => handlers.get(channel)?.({}, ...values) };
}

test("every page beside the rail, from any server, moves over by the rail's width at any zoom, and back when it goes", () => {
  const page = pageWithPreload();
  page.tell(CHROME_RAIL_WIDTH);
  assert.equal(page.head.children.length, 1);
  assert.match(page.head.children[0].textContent, /padding-left: 36px !important; --rail-beside: 36px;/, "and the page can read how far it moved");
  page.frame.zoom = 1.25;
  page.resize();
  assert.match(page.head.children[0].textContent, /padding-left: 28\.8px !important; --rail-beside: 28\.8px;/, "zoomed in, the same 36 points on screen");
  page.tell("9999");
  assert.match(page.head.children[0].textContent, /padding-left: 160px/, "never more than 200 points, whatever is sent");
  page.tell(0);
  assert.equal(page.head.children.length, 0, "the page has the whole window again");
});

test("the rail's own page keeps its size at any zoom of this app's page (Chromium shares a host's zoom); no other page is touched", () => {
  const rail = pageWithPreload("http://127.0.0.1:8799", "/rail");
  rail.frame.zoom = 1.25;
  rail.resize();
  assert.equal(rail.root.style.zoom, "0.8", "zoomed in, the rail takes the zoom back out");
  rail.frame.zoom = 1;
  rail.resize();
  assert.equal(rail.root.style.zoom, "");
  for (const [origin, pathname] of [["http://127.0.0.1:8799", "/"], ["https://bots.example.test", "/rail"]]) {
    const page = pageWithPreload(origin, pathname);
    page.frame.zoom = 1.25;
    page.resize();
    assert.equal(page.root.style.zoom, "", `${origin}${pathname} keeps its zoom`);
  }
});

test("every page beside the rail, from any server, gets the button's bridge, learns only whether there is a rail to hide, and says when it draws the frame; the rail's own channels stay this app's", async () => {
  const remote = pageWithPreload("https://bots.example.test", "/", (channel) => (channel === "rail:offer" ? { available: "yes", hidden: 1, places: [{ id: "home" }] } : { available: true, hidden: true }));
  assert.deepEqual(Object.keys(remote.bridge.rail), ["state", "drawsFrame", "setHidden", "onState"]);
  /** A value made in the page's own context, as this one sees it. */
  const plain = (value) => JSON.parse(JSON.stringify(value));
  assert.deepEqual(plain(await remote.bridge.rail.state()), { available: false, hidden: false }, "two plain answers, nothing else main might send");
  await assert.rejects(remote.bridge.rail.setHidden(true), /Choose to hide or show/, "a server's page hides it only from the person's click");
  remote.navigator.userActivation.isActive = true;
  assert.deepEqual(plain(await remote.bridge.rail.setHidden(true)), { available: true, hidden: true });
  await remote.bridge.rail.drawsFrame();
  await remote.bridge.rail.drawsFrame(false);
  assert.deepEqual(plain(remote.invoked), [["rail:offer"], ["rail:hide", true], ["rail:draws-frame", true], ["rail:draws-frame", false]]);
  const heard = [];
  const stop = remote.bridge.rail.onState((state) => heard.push(state));
  remote.hear("rail:state", { available: true, hidden: false, places: ["home"] });
  stop();
  remote.hear("rail:state", { available: true, hidden: true });
  assert.deepEqual(plain(heard), [{ available: true, hidden: false }]);
  const local = pageWithPreload("http://127.0.0.1:8799", "/");
  assert.deepEqual(Object.keys(local.bridge.rail), ["state", "drawsFrame", "setHidden", "onState", "expand", "onSkin", "onFramed"]);
  const framed = [];
  const unheard = local.bridge.rail.onFramed((value) => framed.push(value));
  local.hear("rail:framed", false);
  local.hear("rail:framed", "yes");
  unheard();
  local.hear("rail:framed", true);
  assert.deepEqual(framed, [false, false], "only true is framed");
  await local.bridge.rail.setHidden(false);
  assert.deepEqual(plain(local.invoked), [["rail:hide", false]], "this app's own page, as for its other choices");
});

test("the accounts sheet holds the rail's page up, even for someone who hid the rail or has one place, and letting go puts things back", () => {
  const w = world({ wanted: false, hidden: true });
  w.rail.attach(w.win);
  assert.equal(w.rail.shown(), false);
  w.rail.holdOpen(true);
  assert.equal(w.rail.shown(), true);
  w.rail.expand(true);
  assert.equal(w.made[0].bounds.width, 1440, "the sheet has the whole window");
  w.rail.expand(false);
  w.rail.holdOpen(false);
  assert.equal(w.rail.shown(), false);
  w.state.hidden = false; w.state.wanted = true;
  w.rail.refresh();
  w.rail.holdOpen(true); w.rail.holdOpen(false);
  assert.equal(w.rail.shown(), true, "a rail that was there stays");
});

test("the rail's page that couldn't load (this app's server was restarting) loads again at the next refresh, so it isn't left a blank strip", () => {
  const w = world();
  w.rail.attach(w.win);
  const contents = w.made[0].webContents, loads = () => contents.loaded.length;
  assert.equal(loads(), 1);
  w.rail.refresh();
  assert.equal(loads(), 1, "loaded or on its way: not loaded again");
  contents.emit("did-fail-load", {}, -102, "ERR_CONNECTION_REFUSED", "http://127.0.0.1:8799/rail", true);
  assert.equal(loads(), 1, "nothing retries until the server says it is ready");
  w.rail.refresh();
  assert.equal(loads(), 2, "the next refresh loads it");
  w.rail.refresh();
  assert.equal(loads(), 2, "once");
  contents.emit("did-fail-load", {}, -3, "ERR_ABORTED", "http://127.0.0.1:8799/rail", true);
  w.rail.refresh();
  assert.equal(loads(), 2, "a load cut short by another isn't a failure");
  contents.emit("did-fail-load", {}, -105, "ERR_NAME_NOT_RESOLVED", "https://cdn.example.test/", false);
  w.rail.refresh();
  assert.equal(loads(), 2, "nor a frame inside it");
  contents.emit("did-fail-load", {}, -102, "ERR_CONNECTION_REFUSED", "http://127.0.0.1:8799/rail", true);
  contents.emit("did-finish-load");
  w.rail.refresh();
  assert.equal(loads(), 3, "the error page that replaced it finishing loading isn't the rail's page loading (seen in the real app)");
  contents.emit("did-finish-load");
  w.rail.refresh();
  assert.equal(loads(), 3, "and the load that worked stays");
});