summaryrefslogtreecommitdiffstats
path: root/browser/components/sessionstore/test/SessionStoreTestUtils.sys.mjs
blob: dd2885cee4ace5b06dcc9032a6aba8f1f661754d (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
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  BrowserTestUtils: "resource://testing-common/BrowserTestUtils.sys.mjs",
  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
  TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs",
  TestUtils: "resource://testing-common/TestUtils.sys.mjs",
});

export var SessionStoreTestUtils = {
  /**
   * Running this init allows helpers to access test scope helpers, like Assert
   * and SimpleTest.
   * Tests should call this init() before using the helpers which rely on properties assign here.
   *
   * @param {object} scope The global scope where tests are being run.
   * @param {DOmWindow} scope The global window object, for acessing gBrowser etc.
   */
  init(scope, windowGlobal) {
    if (!scope) {
      throw new Error(
        "Must initialize SessionStoreTestUtils with a test scope"
      );
    }
    if (!windowGlobal) {
      throw new Error("this.windowGlobal must be defined when we init");
    }
    this.info = scope.info;
    this.registerCleanupFunction = scope.registerCleanupFunction;
    this.windowGlobal = windowGlobal;
  },

  async closeTab(tab) {
    await lazy.TabStateFlusher.flush(tab.linkedBrowser);
    let sessionUpdatePromise =
      lazy.BrowserTestUtils.waitForSessionStoreUpdate(tab);
    lazy.BrowserTestUtils.removeTab(tab);
    await sessionUpdatePromise;
  },

  async openAndCloseTab(window, url) {
    let { updatePromise } = await lazy.BrowserTestUtils.withNewTab(
      { url, gBrowser: window.gBrowser },
      async browser => {
        return {
          updatePromise: lazy.BrowserTestUtils.waitForSessionStoreUpdate({
            linkedBrowser: browser,
          }),
        };
      }
    );
    await updatePromise;
    return lazy.TestUtils.topicObserved("sessionstore-closed-objects-changed");
  },

  // This assumes that tests will at least have some state/entries
  waitForBrowserState(aState, aSetStateCallback) {
    if (typeof aState == "string") {
      aState = JSON.parse(aState);
    }
    if (typeof aState != "object") {
      throw new TypeError(
        "Argument must be an object or a JSON representation of an object"
      );
    }
    if (!this.windowGlobal) {
      throw new Error(
        "no windowGlobal defined, please call init() first with the scope and window object"
      );
    }
    let windows = [this.windowGlobal];
    let tabsRestored = 0;
    let expectedTabsRestored = 0;
    let expectedWindows = aState.windows.length;
    let windowsOpen = 1;
    let listening = false;
    let windowObserving = false;
    let restoreHiddenTabs = Services.prefs.getBoolPref(
      "browser.sessionstore.restore_hidden_tabs"
    );
    // This should match the |restoreTabsLazily| value that
    // SessionStore.restoreWindow() uses.
    let restoreTabsLazily =
      Services.prefs.getBoolPref("browser.sessionstore.restore_on_demand") &&
      Services.prefs.getBoolPref("browser.sessionstore.restore_tabs_lazily");

    aState.windows.forEach(function (winState) {
      winState.tabs.forEach(function (tabState) {
        if (!restoreTabsLazily && (restoreHiddenTabs || !tabState.hidden)) {
          expectedTabsRestored++;
        }
      });
    });

    // If there are only hidden tabs and restoreHiddenTabs = false, we still
    // expect one of them to be restored because it gets shown automatically.
    // Otherwise if lazy tab restore there will only be one tab restored per window.
    if (!expectedTabsRestored) {
      expectedTabsRestored = 1;
    } else if (restoreTabsLazily) {
      expectedTabsRestored = aState.windows.length;
    }

    function onSSTabRestored(aEvent) {
      if (++tabsRestored == expectedTabsRestored) {
        // Remove the event listener from each window
        windows.forEach(function (win) {
          win.gBrowser.tabContainer.removeEventListener(
            "SSTabRestored",
            onSSTabRestored,
            true
          );
        });
        listening = false;
        SessionStoreTestUtils.info("running " + aSetStateCallback.name);
        lazy.TestUtils.executeSoon(aSetStateCallback);
      }
    }

    // Used to add our listener to further windows so we can catch SSTabRestored
    // coming from them when creating a multi-window state.
    function windowObserver(aSubject, aTopic, aData) {
      if (aTopic == "domwindowopened") {
        let newWindow = aSubject;
        newWindow.addEventListener(
          "load",
          function () {
            if (++windowsOpen == expectedWindows) {
              Services.ww.unregisterNotification(windowObserver);
              windowObserving = false;
            }

            // Track this window so we can remove the progress listener later
            windows.push(newWindow);
            // Add the progress listener
            newWindow.gBrowser.tabContainer.addEventListener(
              "SSTabRestored",
              onSSTabRestored,
              true
            );
          },
          { once: true }
        );
      }
    }

    // We only want to register the notification if we expect more than 1 window
    if (expectedWindows > 1) {
      this.registerCleanupFunction(function () {
        if (windowObserving) {
          Services.ww.unregisterNotification(windowObserver);
        }
      });
      windowObserving = true;
      Services.ww.registerNotification(windowObserver);
    }

    this.registerCleanupFunction(function () {
      if (listening) {
        windows.forEach(function (win) {
          win.gBrowser.tabContainer.removeEventListener(
            "SSTabRestored",
            onSSTabRestored,
            true
          );
        });
      }
    });
    // Add the event listener for this window as well.
    listening = true;
    this.windowGlobal.gBrowser.tabContainer.addEventListener(
      "SSTabRestored",
      onSSTabRestored,
      true
    );

    // Ensure setBrowserState() doesn't remove the initial tab.
    this.windowGlobal.gBrowser.selectedTab = this.windowGlobal.gBrowser.tabs[0];

    // Finally, call setBrowserState
    lazy.SessionStore.setBrowserState(JSON.stringify(aState));
  },

  promiseBrowserState(aState) {
    return new Promise(resolve => this.waitForBrowserState(aState, resolve));
  },
};