Bug 1944416: Restore individual tabs from closed groups in closed windows r=dao,sessi...
[gecko.git] / browser / components / sessionstore / TabStateFlusher.sys.mjs
blobed7953e41e8d61695c04cd9fc40f9a9d40d56d77
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /**
6  * A module that enables async flushes. Updates from frame scripts are
7  * throttled to be sent only once per second. If an action wants a tab's latest
8  * state without waiting for a second then it can request an async flush and
9  * wait until the frame scripts reported back. At this point the parent has the
10  * latest data and the action can continue.
11  */
12 export var TabStateFlusher = Object.freeze({
13   /**
14    * Requests an async flush for the given browser. Returns a promise that will
15    * resolve when we heard back from the content process and the parent has
16    * all the latest data.
17    */
18   flush(browser) {
19     return TabStateFlusherInternal.flush(browser);
20   },
22   /**
23    * Requests an async flush for all browsers of a given window. Returns a Promise
24    * that will resolve when we've heard back from all browsers.
25    */
26   flushWindow(window) {
27     return TabStateFlusherInternal.flushWindow(window);
28   },
30   /**
31    * Resolves all active flush requests for a given browser. This should be
32    * used when the content process crashed or the final update message was
33    * seen. In those cases we can't guarantee to ever hear back from the frame
34    * script so we just resolve all requests instead of discarding them.
35    *
36    * @param browser (<xul:browser>)
37    *        The browser for which all flushes are being resolved.
38    * @param success (bool, optional)
39    *        Whether or not the flushes succeeded.
40    * @param message (string, optional)
41    *        An error message that will be sent to the Console in the
42    *        event that the flushes failed.
43    */
44   resolveAll(browser, success = true, message = "") {
45     TabStateFlusherInternal.resolveAll(browser, success, message);
46   },
47 });
49 var TabStateFlusherInternal = {
50   // A map storing all active requests per browser. A request is a
51   // triple of a map containing all flush requests, a promise that
52   // resolve when a request for a browser is canceled, and the
53   // function to call to cancel a reqeust.
54   _requests: new WeakMap(),
56   initEntry(entry) {
57     entry.cancelPromise = new Promise(resolve => {
58       entry.cancel = resolve;
59     }).then(result => {
60       TabStateFlusherInternal.initEntry(entry);
61       return result;
62     });
64     return entry;
65   },
67   /**
68    * Requests an async flush for the given browser. Returns a promise that will
69    * resolve when we heard back from the content process and the parent has
70    * all the latest data.
71    */
72   flush(browser) {
73     let nativePromise = Promise.resolve();
74     if (browser && browser.frameLoader) {
75       /*
76         Request native listener to flush the tabState.
77         Resolves when flush is complete.
78       */
79       nativePromise = browser.frameLoader.requestTabStateFlush();
80     }
82     // Retrieve active requests for given browser.
83     let permanentKey = browser.permanentKey;
84     let request = this._requests.get(permanentKey);
85     if (!request) {
86       // If we don't have any requests for this browser, create a new
87       // entry for browser.
88       request = this.initEntry({});
89       this._requests.set(permanentKey, request);
90     }
92     // It's fine to resolve the request immediately after the native promise
93     // resolves, since SessionStore will have processed all updates from this
94     // browser by that point.
95     return Promise.race([nativePromise, request.cancelPromise]);
96   },
98   /**
99    * Requests an async flush for all non-lazy browsers of a given window.
100    * Returns a Promise that will resolve when we've heard back from all browsers.
101    */
102   flushWindow(window) {
103     let promises = [];
104     for (let browser of window.gBrowser.browsers) {
105       if (window.gBrowser.getTabForBrowser(browser).linkedPanel) {
106         promises.push(this.flush(browser));
107       }
108     }
109     return Promise.all(promises);
110   },
112   /**
113    * Resolves all active flush requests for a given browser. This should be
114    * used when the content process crashed or the final update message was
115    * seen. In those cases we can't guarantee to ever hear back from the frame
116    * script so we just resolve all requests instead of discarding them.
117    *
118    * @param browser (<xul:browser>)
119    *        The browser for which all flushes are being resolved.
120    * @param success (bool, optional)
121    *        Whether or not the flushes succeeded.
122    * @param message (string, optional)
123    *        An error message that will be sent to the Console in the
124    *        event that the flushes failed.
125    */
126   resolveAll(browser, success = true, message = "") {
127     // Nothing to do if there are no pending flushes for the given browser.
128     if (!this._requests.has(browser.permanentKey)) {
129       return;
130     }
132     // Retrieve the cancel function for a given browser.
133     let { cancel } = this._requests.get(browser.permanentKey);
135     if (!success) {
136       console.error("Failed to flush browser: ", message);
137     }
139     // Resolve all requests.
140     cancel(success);
141   },