mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			234 lines
		
	
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | 
						|
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
ChromeUtils.defineESModuleGetters(lazy, {
 | 
						|
  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
/**
 | 
						|
 * A module that enables async flushes. Updates from frame scripts are
 | 
						|
 * throttled to be sent only once per second. If an action wants a tab's latest
 | 
						|
 * state without waiting for a second then it can request an async flush and
 | 
						|
 * wait until the frame scripts reported back. At this point the parent has the
 | 
						|
 * latest data and the action can continue.
 | 
						|
 */
 | 
						|
export var TabStateFlusher = Object.freeze({
 | 
						|
  /**
 | 
						|
   * Requests an async flush for the given browser. Returns a promise that will
 | 
						|
   * resolve when we heard back from the content process and the parent has
 | 
						|
   * all the latest data.
 | 
						|
   */
 | 
						|
  flush(browser) {
 | 
						|
    return TabStateFlusherInternal.flush(browser);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Requests an async flush for all browsers of a given window. Returns a Promise
 | 
						|
   * that will resolve when we've heard back from all browsers.
 | 
						|
   */
 | 
						|
  flushWindow(window) {
 | 
						|
    return TabStateFlusherInternal.flushWindow(window);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Resolves the flush request with the given flush ID.
 | 
						|
   *
 | 
						|
   * @param browser (<xul:browser>)
 | 
						|
   *        The browser for which the flush is being resolved.
 | 
						|
   * @param flushID (int)
 | 
						|
   *        The ID of the flush that was sent to the browser.
 | 
						|
   * @param success (bool, optional)
 | 
						|
   *        Whether or not the flush succeeded.
 | 
						|
   * @param message (string, optional)
 | 
						|
   *        An error message that will be sent to the Console in the
 | 
						|
   *        event that a flush failed.
 | 
						|
   */
 | 
						|
  resolve(browser, flushID, success = true, message = "") {
 | 
						|
    TabStateFlusherInternal.resolve(browser, flushID, success, message);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Resolves all active flush requests for a given browser. This should be
 | 
						|
   * used when the content process crashed or the final update message was
 | 
						|
   * seen. In those cases we can't guarantee to ever hear back from the frame
 | 
						|
   * script so we just resolve all requests instead of discarding them.
 | 
						|
   *
 | 
						|
   * @param browser (<xul:browser>)
 | 
						|
   *        The browser for which all flushes are being resolved.
 | 
						|
   * @param success (bool, optional)
 | 
						|
   *        Whether or not the flushes succeeded.
 | 
						|
   * @param message (string, optional)
 | 
						|
   *        An error message that will be sent to the Console in the
 | 
						|
   *        event that the flushes failed.
 | 
						|
   */
 | 
						|
  resolveAll(browser, success = true, message = "") {
 | 
						|
    TabStateFlusherInternal.resolveAll(browser, success, message);
 | 
						|
  },
 | 
						|
});
 | 
						|
 | 
						|
var TabStateFlusherInternal = {
 | 
						|
  // Stores the last request ID.
 | 
						|
  _lastRequestID: 0,
 | 
						|
 | 
						|
  // A map storing all active requests per browser. A request is a
 | 
						|
  // triple of a map containing all flush requests, a promise that
 | 
						|
  // resolve when a request for a browser is canceled, and the
 | 
						|
  // function to call to cancel a reqeust.
 | 
						|
  _requests: new WeakMap(),
 | 
						|
 | 
						|
  initEntry(entry) {
 | 
						|
    entry.perBrowserRequests = new Map();
 | 
						|
    entry.cancelPromise = new Promise(resolve => {
 | 
						|
      entry.cancel = resolve;
 | 
						|
    }).then(result => {
 | 
						|
      TabStateFlusherInternal.initEntry(entry);
 | 
						|
      return result;
 | 
						|
    });
 | 
						|
 | 
						|
    return entry;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Requests an async flush for the given browser. Returns a promise that will
 | 
						|
   * resolve when we heard back from the content process and the parent has
 | 
						|
   * all the latest data.
 | 
						|
   */
 | 
						|
  flush(browser) {
 | 
						|
    let id = ++this._lastRequestID;
 | 
						|
    let nativePromise = Promise.resolve();
 | 
						|
    if (browser && browser.frameLoader) {
 | 
						|
      /*
 | 
						|
        Request native listener to flush the tabState.
 | 
						|
        Resolves when flush is complete.
 | 
						|
      */
 | 
						|
      nativePromise = browser.frameLoader.requestTabStateFlush();
 | 
						|
    }
 | 
						|
 | 
						|
    if (!Services.appinfo.sessionHistoryInParent) {
 | 
						|
      /*
 | 
						|
        In the event that we have to trigger a process switch and thus change
 | 
						|
        browser remoteness, session store needs to register and track the new
 | 
						|
        browser window loaded and to have message manager listener registered
 | 
						|
        ** before ** TabStateFlusher send "SessionStore:flush" message. This fixes
 | 
						|
        the race where we send the message before the message listener is
 | 
						|
        registered for it.
 | 
						|
        */
 | 
						|
      lazy.SessionStore.ensureInitialized(browser.ownerGlobal);
 | 
						|
 | 
						|
      let mm = browser.messageManager;
 | 
						|
      mm.sendAsyncMessage("SessionStore:flush", {
 | 
						|
        id,
 | 
						|
        epoch: lazy.SessionStore.getCurrentEpoch(browser),
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    // Retrieve active requests for given browser.
 | 
						|
    let permanentKey = browser.permanentKey;
 | 
						|
    let request = this._requests.get(permanentKey);
 | 
						|
    if (!request) {
 | 
						|
      // If we don't have any requests for this browser, create a new
 | 
						|
      // entry for browser.
 | 
						|
      request = this.initEntry({});
 | 
						|
      this._requests.set(permanentKey, request);
 | 
						|
    }
 | 
						|
 | 
						|
    // Non-SHIP flushes resolve this after the "SessionStore:update" message. We
 | 
						|
    // don't use that message for SHIP, so it's fine to resolve the request
 | 
						|
    // immediately after the native promise resolves, since SessionStore will
 | 
						|
    // have processed all updates from this browser by that point.
 | 
						|
    let requestPromise = Promise.resolve();
 | 
						|
    if (!Services.appinfo.sessionHistoryInParent) {
 | 
						|
      requestPromise = new Promise(resolve => {
 | 
						|
        // Store resolve() so that we can resolve the promise later.
 | 
						|
        request.perBrowserRequests.set(id, resolve);
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    return Promise.race([
 | 
						|
      nativePromise.then(_ => requestPromise),
 | 
						|
      request.cancelPromise,
 | 
						|
    ]);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Requests an async flush for all non-lazy browsers of a given window.
 | 
						|
   * Returns a Promise that will resolve when we've heard back from all browsers.
 | 
						|
   */
 | 
						|
  flushWindow(window) {
 | 
						|
    let promises = [];
 | 
						|
    for (let browser of window.gBrowser.browsers) {
 | 
						|
      if (window.gBrowser.getTabForBrowser(browser).linkedPanel) {
 | 
						|
        promises.push(this.flush(browser));
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return Promise.all(promises);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Resolves the flush request with the given flush ID.
 | 
						|
   *
 | 
						|
   * @param browser (<xul:browser>)
 | 
						|
   *        The browser for which the flush is being resolved.
 | 
						|
   * @param flushID (int)
 | 
						|
   *        The ID of the flush that was sent to the browser.
 | 
						|
   * @param success (bool, optional)
 | 
						|
   *        Whether or not the flush succeeded.
 | 
						|
   * @param message (string, optional)
 | 
						|
   *        An error message that will be sent to the Console in the
 | 
						|
   *        event that a flush failed.
 | 
						|
   */
 | 
						|
  resolve(browser, flushID, success = true, message = "") {
 | 
						|
    // Nothing to do if there are no pending flushes for the given browser.
 | 
						|
    if (!this._requests.has(browser.permanentKey)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Retrieve active requests for given browser.
 | 
						|
    let { perBrowserRequests } = this._requests.get(browser.permanentKey);
 | 
						|
    if (!perBrowserRequests.has(flushID)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!success) {
 | 
						|
      console.error("Failed to flush browser: ", message);
 | 
						|
    }
 | 
						|
 | 
						|
    // Resolve the request with the given id.
 | 
						|
    let resolve = perBrowserRequests.get(flushID);
 | 
						|
    perBrowserRequests.delete(flushID);
 | 
						|
    resolve(success);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Resolves all active flush requests for a given browser. This should be
 | 
						|
   * used when the content process crashed or the final update message was
 | 
						|
   * seen. In those cases we can't guarantee to ever hear back from the frame
 | 
						|
   * script so we just resolve all requests instead of discarding them.
 | 
						|
   *
 | 
						|
   * @param browser (<xul:browser>)
 | 
						|
   *        The browser for which all flushes are being resolved.
 | 
						|
   * @param success (bool, optional)
 | 
						|
   *        Whether or not the flushes succeeded.
 | 
						|
   * @param message (string, optional)
 | 
						|
   *        An error message that will be sent to the Console in the
 | 
						|
   *        event that the flushes failed.
 | 
						|
   */
 | 
						|
  resolveAll(browser, success = true, message = "") {
 | 
						|
    // Nothing to do if there are no pending flushes for the given browser.
 | 
						|
    if (!this._requests.has(browser.permanentKey)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Retrieve the cancel function for a given browser.
 | 
						|
    let { cancel } = this._requests.get(browser.permanentKey);
 | 
						|
 | 
						|
    if (!success) {
 | 
						|
      console.error("Failed to flush browser: ", message);
 | 
						|
    }
 | 
						|
 | 
						|
    // Resolve all requests.
 | 
						|
    cancel(success);
 | 
						|
  },
 | 
						|
};
 |