forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1268 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1268 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
 | 
						|
// Delay loading until createAppInfo is called and setup.
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "AddonManager",
 | 
						|
  "resource://gre/modules/AddonManager.jsm"
 | 
						|
);
 | 
						|
 | 
						|
const { ExtensionAPI } = ExtensionCommon;
 | 
						|
 | 
						|
// The code in this class does not actually run in this test scope, it is
 | 
						|
// serialized into a string which is later loaded by the WebExtensions
 | 
						|
// framework in the same context as other extension APIs.  By writing it
 | 
						|
// this way rather than as a big string constant we get lint coverage.
 | 
						|
// But eslint doesn't understand that this code runs in a different context
 | 
						|
// where the EventManager class is available so just tell it here:
 | 
						|
/* global EventManager */
 | 
						|
const API = class extends ExtensionAPI {
 | 
						|
  static namespace = undefined;
 | 
						|
  primeListener(event, fire, params, isInStartup) {
 | 
						|
    if (isInStartup && event == "nonBlockingEvent") {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // eslint-disable-next-line no-undef
 | 
						|
    let { eventName, throwError, ignoreListener } =
 | 
						|
      this.constructor.testOptions || {};
 | 
						|
    let { namespace } = this.constructor;
 | 
						|
 | 
						|
    if (eventName == event) {
 | 
						|
      if (throwError) {
 | 
						|
        throw new Error(throwError);
 | 
						|
      }
 | 
						|
      if (ignoreListener) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    Services.obs.notifyObservers(
 | 
						|
      { namespace, event, fire, params },
 | 
						|
      "prime-event-listener"
 | 
						|
    );
 | 
						|
 | 
						|
    const FIRE_TOPIC = `fire-${namespace}.${event}`;
 | 
						|
 | 
						|
    async function listener(subject, topic, data) {
 | 
						|
      try {
 | 
						|
        if (subject.wrappedJSObject.waitForBackground) {
 | 
						|
          await fire.wakeup();
 | 
						|
        }
 | 
						|
        await fire.async(subject.wrappedJSObject.listenerArgs);
 | 
						|
      } catch (err) {
 | 
						|
        let errSubject = { namespace, event, errorMessage: err.toString() };
 | 
						|
        Services.obs.notifyObservers(errSubject, "listener-callback-exception");
 | 
						|
      }
 | 
						|
    }
 | 
						|
    Services.obs.addObserver(listener, FIRE_TOPIC);
 | 
						|
 | 
						|
    return {
 | 
						|
      unregister() {
 | 
						|
        Services.obs.notifyObservers(
 | 
						|
          { namespace, event, params },
 | 
						|
          "unregister-primed-listener"
 | 
						|
        );
 | 
						|
        Services.obs.removeObserver(listener, FIRE_TOPIC);
 | 
						|
      },
 | 
						|
      convert(_fire) {
 | 
						|
        Services.obs.notifyObservers(
 | 
						|
          { namespace, event, params },
 | 
						|
          "convert-event-listener"
 | 
						|
        );
 | 
						|
        fire = _fire;
 | 
						|
      },
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  getAPI(context) {
 | 
						|
    let self = this;
 | 
						|
    let { namespace } = this.constructor;
 | 
						|
    return {
 | 
						|
      [namespace]: {
 | 
						|
        testOptions(options) {
 | 
						|
          // We want to be able to test errors on startup.
 | 
						|
          // We use a global here because we test restarting AOM,
 | 
						|
          // which causes the instance of this class to be destroyed.
 | 
						|
          // eslint-disable-next-line no-undef
 | 
						|
          self.constructor.testOptions = options;
 | 
						|
        },
 | 
						|
        onEvent1: new EventManager({
 | 
						|
          context,
 | 
						|
          module: namespace,
 | 
						|
          event: "onEvent1",
 | 
						|
          register: (fire, ...params) => {
 | 
						|
            let data = { namespace, event: "onEvent1", params };
 | 
						|
            Services.obs.notifyObservers(data, "register-event-listener");
 | 
						|
            return () => {
 | 
						|
              Services.obs.notifyObservers(data, "unregister-event-listener");
 | 
						|
            };
 | 
						|
          },
 | 
						|
        }).api(),
 | 
						|
 | 
						|
        onEvent2: new EventManager({
 | 
						|
          context,
 | 
						|
          module: namespace,
 | 
						|
          event: "onEvent2",
 | 
						|
          register: (fire, ...params) => {
 | 
						|
            let data = { namespace, event: "onEvent2", params };
 | 
						|
            Services.obs.notifyObservers(data, "register-event-listener");
 | 
						|
            return () => {
 | 
						|
              Services.obs.notifyObservers(data, "unregister-event-listener");
 | 
						|
            };
 | 
						|
          },
 | 
						|
        }).api(),
 | 
						|
 | 
						|
        nonBlockingEvent: new EventManager({
 | 
						|
          context,
 | 
						|
          module: namespace,
 | 
						|
          event: "nonBlockingEvent",
 | 
						|
          register: (fire, ...params) => {
 | 
						|
            let data = { namespace, event: "nonBlockingEvent", params };
 | 
						|
            Services.obs.notifyObservers(data, "register-event-listener");
 | 
						|
            return () => {
 | 
						|
              Services.obs.notifyObservers(data, "unregister-event-listener");
 | 
						|
            };
 | 
						|
          },
 | 
						|
        }).api(),
 | 
						|
      },
 | 
						|
    };
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
function makeModule(namespace, options = {}) {
 | 
						|
  const SCHEMA = [
 | 
						|
    {
 | 
						|
      namespace,
 | 
						|
      functions: [
 | 
						|
        {
 | 
						|
          name: "testOptions",
 | 
						|
          type: "function",
 | 
						|
          async: true,
 | 
						|
          parameters: [
 | 
						|
            {
 | 
						|
              name: "options",
 | 
						|
              type: "object",
 | 
						|
              additionalProperties: {
 | 
						|
                type: "any",
 | 
						|
              },
 | 
						|
            },
 | 
						|
          ],
 | 
						|
        },
 | 
						|
      ],
 | 
						|
      events: [
 | 
						|
        {
 | 
						|
          name: "onEvent1",
 | 
						|
          type: "function",
 | 
						|
          extraParameters: [{ type: "any", optional: true }],
 | 
						|
        },
 | 
						|
        {
 | 
						|
          name: "onEvent2",
 | 
						|
          type: "function",
 | 
						|
          extraParameters: [{ type: "any", optional: true }],
 | 
						|
        },
 | 
						|
        {
 | 
						|
          name: "nonBlockingEvent",
 | 
						|
          type: "function",
 | 
						|
          extraParameters: [{ type: "any", optional: true }],
 | 
						|
        },
 | 
						|
      ],
 | 
						|
    },
 | 
						|
  ];
 | 
						|
 | 
						|
  const API_SCRIPT = `
 | 
						|
    this.${namespace} = ${API.toString()};
 | 
						|
    this.${namespace}.namespace = "${namespace}";
 | 
						|
  `;
 | 
						|
 | 
						|
  // MODULE_INFO for registerModules
 | 
						|
  let { startupBlocking } = options;
 | 
						|
  return {
 | 
						|
    schema: `data:,${JSON.stringify(SCHEMA)}`,
 | 
						|
    scopes: ["addon_parent"],
 | 
						|
    paths: [[namespace]],
 | 
						|
    startupBlocking,
 | 
						|
    url: URL.createObjectURL(new Blob([API_SCRIPT])),
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
// Two modules, primary test module is startupBlocking
 | 
						|
const MODULE_INFO = {
 | 
						|
  startupBlocking: makeModule("startupBlocking", { startupBlocking: true }),
 | 
						|
  nonStartupBlocking: makeModule("nonStartupBlocking"),
 | 
						|
};
 | 
						|
 | 
						|
const global = this;
 | 
						|
 | 
						|
// Wait for the given event (topic) to occur a specific number of times
 | 
						|
// (count).  If fn is not supplied, the Promise returned from this function
 | 
						|
// resolves as soon as that many instances of the event have been observed.
 | 
						|
// If fn is supplied, this function also waits for the Promise that fn()
 | 
						|
// returns to complete and ensures that the given event does not occur more
 | 
						|
// than `count` times before then.  On success, resolves with an array
 | 
						|
// of the subjects from each of the observed events.
 | 
						|
async function promiseObservable(topic, count, fn = null) {
 | 
						|
  let _countResolve;
 | 
						|
  let results = [];
 | 
						|
  function listener(subject, _topic, data) {
 | 
						|
    const eventDetails = subject.wrappedJSObject;
 | 
						|
    results.push(eventDetails);
 | 
						|
    if (results.length > count) {
 | 
						|
      ok(
 | 
						|
        false,
 | 
						|
        `Got unexpected ${topic} event with ${JSON.stringify(eventDetails)}`
 | 
						|
      );
 | 
						|
    } else if (results.length == count) {
 | 
						|
      _countResolve();
 | 
						|
    }
 | 
						|
  }
 | 
						|
  Services.obs.addObserver(listener, topic);
 | 
						|
 | 
						|
  try {
 | 
						|
    await Promise.all([
 | 
						|
      new Promise(resolve => {
 | 
						|
        _countResolve = resolve;
 | 
						|
      }),
 | 
						|
      fn && fn(),
 | 
						|
    ]);
 | 
						|
  } finally {
 | 
						|
    Services.obs.removeObserver(listener, topic);
 | 
						|
  }
 | 
						|
 | 
						|
  return results;
 | 
						|
}
 | 
						|
 | 
						|
function trackEvents(wrapper) {
 | 
						|
  let events = new Map();
 | 
						|
  for (let event of ["background-script-event", "start-background-script"]) {
 | 
						|
    events.set(event, false);
 | 
						|
    wrapper.extension.once(event, () => events.set(event, true));
 | 
						|
  }
 | 
						|
  return events;
 | 
						|
}
 | 
						|
 | 
						|
const server = createHttpServer({ hosts: ["example.com"] });
 | 
						|
server.registerDirectory("/data/", do_get_file("data"));
 | 
						|
 | 
						|
add_task(async function setup() {
 | 
						|
  // The blob:-URL registered above in MODULE_INFO gets loaded at
 | 
						|
  // https://searchfox.org/mozilla-central/rev/0fec57c05d3996cc00c55a66f20dd5793a9bfb5d/toolkit/components/extensions/ExtensionCommon.jsm#1649
 | 
						|
  Services.prefs.setBoolPref(
 | 
						|
    "security.allow_parent_unrestricted_js_loads",
 | 
						|
    true
 | 
						|
  );
 | 
						|
  registerCleanupFunction(() => {
 | 
						|
    Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
 | 
						|
  });
 | 
						|
 | 
						|
  AddonTestUtils.init(global);
 | 
						|
  AddonTestUtils.overrideCertDB();
 | 
						|
  AddonTestUtils.createAppInfo(
 | 
						|
    "xpcshell@tests.mozilla.org",
 | 
						|
    "XPCShell",
 | 
						|
    "42",
 | 
						|
    "42"
 | 
						|
  );
 | 
						|
 | 
						|
  ExtensionParent.apiManager.registerModules(MODULE_INFO);
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_persistent_events() {
 | 
						|
  await AddonTestUtils.promiseStartupManager();
 | 
						|
 | 
						|
  let extension = ExtensionTestUtils.loadExtension({
 | 
						|
    useAddonManager: "permanent",
 | 
						|
    background() {
 | 
						|
      let register1 = true,
 | 
						|
        register2 = true;
 | 
						|
      if (localStorage.getItem("skip1")) {
 | 
						|
        register1 = false;
 | 
						|
      }
 | 
						|
      if (localStorage.getItem("skip2")) {
 | 
						|
        register2 = false;
 | 
						|
      }
 | 
						|
 | 
						|
      let listener1 = arg => browser.test.sendMessage("listener1", arg);
 | 
						|
      let listener2 = arg => browser.test.sendMessage("listener2", arg);
 | 
						|
      let listener3 = arg => browser.test.sendMessage("listener3", arg);
 | 
						|
 | 
						|
      if (register1) {
 | 
						|
        browser.startupBlocking.onEvent1.addListener(listener1, "listener1");
 | 
						|
      }
 | 
						|
      if (register2) {
 | 
						|
        browser.startupBlocking.onEvent1.addListener(listener2, "listener2");
 | 
						|
        browser.startupBlocking.onEvent2.addListener(listener3, "listener3");
 | 
						|
      }
 | 
						|
 | 
						|
      browser.test.onMessage.addListener(msg => {
 | 
						|
        if (msg == "unregister2") {
 | 
						|
          browser.startupBlocking.onEvent2.removeListener(listener3);
 | 
						|
          localStorage.setItem("skip2", true);
 | 
						|
        } else if (msg == "unregister1") {
 | 
						|
          localStorage.setItem("skip1", true);
 | 
						|
          browser.test.sendMessage("unregistered");
 | 
						|
        }
 | 
						|
      });
 | 
						|
 | 
						|
      browser.test.sendMessage("ready");
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
  function check(
 | 
						|
    info,
 | 
						|
    what,
 | 
						|
    { listener1 = true, listener2 = true, listener3 = true } = {}
 | 
						|
  ) {
 | 
						|
    let count = (listener1 ? 1 : 0) + (listener2 ? 1 : 0) + (listener3 ? 1 : 0);
 | 
						|
    equal(info.length, count, `Got ${count} ${what} events`);
 | 
						|
 | 
						|
    let i = 0;
 | 
						|
    if (listener1) {
 | 
						|
      equal(info[i].event, "onEvent1", `Got ${what} on event1 for listener 1`);
 | 
						|
      deepEqual(
 | 
						|
        info[i].params,
 | 
						|
        ["listener1"],
 | 
						|
        `Got event1 ${what} args for listener 1`
 | 
						|
      );
 | 
						|
      ++i;
 | 
						|
    }
 | 
						|
 | 
						|
    if (listener2) {
 | 
						|
      equal(info[i].event, "onEvent1", `Got ${what} on event1 for listener 2`);
 | 
						|
      deepEqual(
 | 
						|
        info[i].params,
 | 
						|
        ["listener2"],
 | 
						|
        `Got event1 ${what} args for listener 2`
 | 
						|
      );
 | 
						|
      ++i;
 | 
						|
    }
 | 
						|
 | 
						|
    if (listener3) {
 | 
						|
      equal(info[i].event, "onEvent2", `Got ${what} on event2 for listener 3`);
 | 
						|
      deepEqual(
 | 
						|
        info[i].params,
 | 
						|
        ["listener3"],
 | 
						|
        `Got event2 ${what} args for listener 3`
 | 
						|
      );
 | 
						|
      ++i;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Check that the regular event registration process occurs when
 | 
						|
  // the extension is installed.
 | 
						|
  let [observed] = await Promise.all([
 | 
						|
    promiseObservable("register-event-listener", 3),
 | 
						|
    extension.startup(),
 | 
						|
  ]);
 | 
						|
  check(observed, "register");
 | 
						|
 | 
						|
  await extension.awaitMessage("ready");
 | 
						|
 | 
						|
  // Check that the regular unregister process occurs when
 | 
						|
  // the browser shuts down.
 | 
						|
  [observed] = await Promise.all([
 | 
						|
    promiseObservable("unregister-event-listener", 3),
 | 
						|
    new Promise(resolve => extension.extension.once("shutdown", resolve)),
 | 
						|
    AddonTestUtils.promiseShutdownManager(),
 | 
						|
  ]);
 | 
						|
  check(observed, "unregister");
 | 
						|
 | 
						|
  // Check that listeners are primed at the next browser startup.
 | 
						|
  [observed] = await Promise.all([
 | 
						|
    promiseObservable("prime-event-listener", 3),
 | 
						|
    AddonTestUtils.promiseStartupManager(),
 | 
						|
  ]);
 | 
						|
  check(observed, "prime");
 | 
						|
 | 
						|
  // Check that primed listeners are converted to regular listeners
 | 
						|
  // when the background page is started after browser startup.
 | 
						|
  let p = promiseObservable("convert-event-listener", 3);
 | 
						|
  AddonTestUtils.notifyLateStartup();
 | 
						|
  observed = await p;
 | 
						|
 | 
						|
  check(observed, "convert");
 | 
						|
 | 
						|
  await extension.awaitMessage("ready");
 | 
						|
 | 
						|
  // Check that when the event is triggered, all the plumbing worked
 | 
						|
  // correctly for the primed-then-converted listener.
 | 
						|
  let listenerArgs = { test: "kaboom" };
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs },
 | 
						|
    "fire-startupBlocking.onEvent1"
 | 
						|
  );
 | 
						|
 | 
						|
  let details = await extension.awaitMessage("listener1");
 | 
						|
  deepEqual(details, listenerArgs, "Listener 1 fired");
 | 
						|
  details = await extension.awaitMessage("listener2");
 | 
						|
  deepEqual(details, listenerArgs, "Listener 2 fired");
 | 
						|
 | 
						|
  // Check that the converted listener is properly unregistered at
 | 
						|
  // browser shutdown.
 | 
						|
  [observed] = await Promise.all([
 | 
						|
    promiseObservable("unregister-primed-listener", 3),
 | 
						|
    AddonTestUtils.promiseShutdownManager(),
 | 
						|
  ]);
 | 
						|
  check(observed, "unregister");
 | 
						|
 | 
						|
  // Start up again, listener should be primed
 | 
						|
  [observed] = await Promise.all([
 | 
						|
    promiseObservable("prime-event-listener", 3),
 | 
						|
    AddonTestUtils.promiseStartupManager(),
 | 
						|
  ]);
 | 
						|
  check(observed, "prime");
 | 
						|
 | 
						|
  // Check that triggering the event before the listener has been converted
 | 
						|
  // causes the background page to be loaded and the listener to be converted,
 | 
						|
  // and the listener is invoked.
 | 
						|
  p = promiseObservable("convert-event-listener", 3);
 | 
						|
  listenerArgs.test = "startup event";
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs },
 | 
						|
    "fire-startupBlocking.onEvent2"
 | 
						|
  );
 | 
						|
  observed = await p;
 | 
						|
 | 
						|
  check(observed, "convert");
 | 
						|
 | 
						|
  details = await extension.awaitMessage("listener3");
 | 
						|
  deepEqual(details, listenerArgs, "Listener 3 fired for event during startup");
 | 
						|
 | 
						|
  await extension.awaitMessage("ready");
 | 
						|
 | 
						|
  // Check that the unregister process works when we manually remove
 | 
						|
  // a listener.
 | 
						|
  p = promiseObservable("unregister-primed-listener", 1);
 | 
						|
  extension.sendMessage("unregister2");
 | 
						|
  observed = await p;
 | 
						|
  check(observed, "unregister", { listener1: false, listener2: false });
 | 
						|
 | 
						|
  // Check that we only get unregisters for the remaining events after
 | 
						|
  // one listener has been removed.
 | 
						|
  observed = await promiseObservable("unregister-primed-listener", 2, () =>
 | 
						|
    AddonTestUtils.promiseShutdownManager()
 | 
						|
  );
 | 
						|
  check(observed, "unregister", { listener3: false });
 | 
						|
 | 
						|
  // Check that after restart, only listeners that were present at
 | 
						|
  // the end of the last session are primed.
 | 
						|
  observed = await promiseObservable("prime-event-listener", 2, () =>
 | 
						|
    AddonTestUtils.promiseStartupManager()
 | 
						|
  );
 | 
						|
  check(observed, "prime", { listener3: false });
 | 
						|
 | 
						|
  // Check that if the background script does not re-register listeners,
 | 
						|
  // the primed listeners are unregistered after the background page
 | 
						|
  // starts up.
 | 
						|
  p = promiseObservable("unregister-primed-listener", 1, () =>
 | 
						|
    extension.awaitMessage("ready")
 | 
						|
  );
 | 
						|
 | 
						|
  AddonTestUtils.notifyLateStartup();
 | 
						|
  observed = await p;
 | 
						|
  check(observed, "unregister", { listener1: false, listener3: false });
 | 
						|
 | 
						|
  // Just listener1 should be registered now, fire event1 to confirm.
 | 
						|
  listenerArgs.test = "third time";
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs },
 | 
						|
    "fire-startupBlocking.onEvent1"
 | 
						|
  );
 | 
						|
  details = await extension.awaitMessage("listener1");
 | 
						|
  deepEqual(details, listenerArgs, "Listener 1 fired");
 | 
						|
 | 
						|
  // Tell the extension not to re-register listener1 on the next startup
 | 
						|
  extension.sendMessage("unregister1");
 | 
						|
  await extension.awaitMessage("unregistered");
 | 
						|
 | 
						|
  // Shut down, start up
 | 
						|
  observed = await promiseObservable("unregister-primed-listener", 1, () =>
 | 
						|
    AddonTestUtils.promiseShutdownManager()
 | 
						|
  );
 | 
						|
  check(observed, "unregister", { listener2: false, listener3: false });
 | 
						|
 | 
						|
  observed = await promiseObservable("prime-event-listener", 1, () =>
 | 
						|
    AddonTestUtils.promiseStartupManager()
 | 
						|
  );
 | 
						|
  check(observed, "register", { listener2: false, listener3: false });
 | 
						|
 | 
						|
  // Check that firing event1 causes the listener fire callback to
 | 
						|
  // reject.
 | 
						|
  p = promiseObservable("listener-callback-exception", 1);
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs, waitForBackground: true },
 | 
						|
    "fire-startupBlocking.onEvent1"
 | 
						|
  );
 | 
						|
  equal(
 | 
						|
    (await p)[0].errorMessage,
 | 
						|
    "Error: primed listener startupBlocking.onEvent1 not re-registered",
 | 
						|
    "Primed listener that was not re-registered received an error when event was triggered during startup"
 | 
						|
  );
 | 
						|
 | 
						|
  await extension.awaitMessage("ready");
 | 
						|
 | 
						|
  await extension.unload();
 | 
						|
 | 
						|
  await AddonTestUtils.promiseShutdownManager();
 | 
						|
});
 | 
						|
 | 
						|
// This test checks whether primed listeners are correctly unregistered when
 | 
						|
// a background page load is interrupted. In particular, it verifies that the
 | 
						|
// fire.wakeup() and fire.async() promises settle eventually.
 | 
						|
add_task(async function test_shutdown_before_background_loaded() {
 | 
						|
  await AddonTestUtils.promiseStartupManager();
 | 
						|
 | 
						|
  let extension = ExtensionTestUtils.loadExtension({
 | 
						|
    useAddonManager: "permanent",
 | 
						|
    background() {
 | 
						|
      let listener = arg => browser.test.sendMessage("triggered", arg);
 | 
						|
      browser.startupBlocking.onEvent1.addListener(listener, "triggered");
 | 
						|
      browser.test.sendMessage("bg_started");
 | 
						|
    },
 | 
						|
  });
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("register-event-listener", 1),
 | 
						|
    extension.startup(),
 | 
						|
  ]);
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("unregister-event-listener", 1),
 | 
						|
    new Promise(resolve => extension.extension.once("shutdown", resolve)),
 | 
						|
    AddonTestUtils.promiseShutdownManager(),
 | 
						|
  ]);
 | 
						|
 | 
						|
  let primeListenerPromise = promiseObservable("prime-event-listener", 1);
 | 
						|
  let fire;
 | 
						|
  let fireWakeupBeforeBgFail;
 | 
						|
  let fireAsyncBeforeBgFail;
 | 
						|
 | 
						|
  let bgAbortedPromise = new Promise(resolve => {
 | 
						|
    let Management = ExtensionParent.apiManager;
 | 
						|
    Management.once("extension-browser-inserted", (eventName, browser) => {
 | 
						|
      browser.loadURI = async () => {
 | 
						|
        // The fire.wakeup/fire.async promises created while loading the
 | 
						|
        // background page should settle when the page fails to load.
 | 
						|
        fire = (await primeListenerPromise)[0].fire;
 | 
						|
        fireWakeupBeforeBgFail = fire.wakeup();
 | 
						|
        fireAsyncBeforeBgFail = fire.async();
 | 
						|
 | 
						|
        extension.extension.once("background-script-aborted", resolve);
 | 
						|
        info("Forcing the background load to fail");
 | 
						|
        browser.remove();
 | 
						|
      };
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  let unregisterPromise = promiseObservable("unregister-primed-listener", 1);
 | 
						|
 | 
						|
  await Promise.all([
 | 
						|
    primeListenerPromise,
 | 
						|
    AddonTestUtils.promiseStartupManager(),
 | 
						|
  ]);
 | 
						|
  await bgAbortedPromise;
 | 
						|
  info("Loaded extension and aborted load of background page");
 | 
						|
 | 
						|
  await unregisterPromise;
 | 
						|
  info("Primed listener has been unregistered");
 | 
						|
 | 
						|
  await fireWakeupBeforeBgFail;
 | 
						|
  info("fire.wakeup() before background load failure should settle");
 | 
						|
 | 
						|
  await Assert.rejects(
 | 
						|
    fireAsyncBeforeBgFail,
 | 
						|
    /Error: listener not re-registered/,
 | 
						|
    "fire.async before background load failure should be rejected"
 | 
						|
  );
 | 
						|
 | 
						|
  await fire.wakeup();
 | 
						|
  info("fire.wakeup() after background load failure should settle");
 | 
						|
 | 
						|
  await Assert.rejects(
 | 
						|
    fire.async(),
 | 
						|
    /Error: primed listener startupBlocking.onEvent1 not re-registered/,
 | 
						|
    "fire.async after background load failure should be rejected"
 | 
						|
  );
 | 
						|
 | 
						|
  await AddonTestUtils.promiseShutdownManager();
 | 
						|
 | 
						|
  // End of the abnormal shutdown test. Now restart the extension to verify
 | 
						|
  // that the persistent listeners have not been unregistered.
 | 
						|
 | 
						|
  // Suppress background page start until an explicit notification.
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("prime-event-listener", 1),
 | 
						|
    AddonTestUtils.promiseStartupManager({ earlyStartup: false }),
 | 
						|
  ]);
 | 
						|
  info("Triggering persistent event to force the background page to start");
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs: 123 },
 | 
						|
    "fire-startupBlocking.onEvent1"
 | 
						|
  );
 | 
						|
  AddonTestUtils.notifyEarlyStartup();
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
  equal(await extension.awaitMessage("triggered"), 123, "triggered event");
 | 
						|
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("unregister-primed-listener", 1),
 | 
						|
    AddonTestUtils.promiseShutdownManager(),
 | 
						|
  ]);
 | 
						|
 | 
						|
  // And lastly, verify that a primed listener is correctly removed when the
 | 
						|
  // extension unloads normally before the delayed background page can load.
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("prime-event-listener", 1),
 | 
						|
    AddonTestUtils.promiseStartupManager({ earlyStartup: false }),
 | 
						|
  ]);
 | 
						|
 | 
						|
  info("Unloading extension before background page has loaded");
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("unregister-primed-listener", 1),
 | 
						|
    extension.unload(),
 | 
						|
  ]);
 | 
						|
 | 
						|
  await AddonTestUtils.promiseShutdownManager();
 | 
						|
});
 | 
						|
 | 
						|
// This test checks whether primed listeners are correctly primed to
 | 
						|
// restart the background once the background has been shutdown or
 | 
						|
// put to sleep.
 | 
						|
add_task(async function test_background_restarted() {
 | 
						|
  await AddonTestUtils.promiseStartupManager();
 | 
						|
 | 
						|
  let extension = ExtensionTestUtils.loadExtension({
 | 
						|
    useAddonManager: "permanent",
 | 
						|
    background() {
 | 
						|
      let listener = arg => browser.test.sendMessage("triggered", arg);
 | 
						|
      browser.startupBlocking.onEvent1.addListener(listener, "triggered");
 | 
						|
      browser.test.sendMessage("bg_started");
 | 
						|
    },
 | 
						|
  });
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("register-event-listener", 1),
 | 
						|
    extension.startup(),
 | 
						|
  ]);
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    primed: false,
 | 
						|
  });
 | 
						|
 | 
						|
  // Shutdown the background page
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("unregister-event-listener", 1),
 | 
						|
    extension.terminateBackground(),
 | 
						|
  ]);
 | 
						|
  // When sleeping the background, its events should become persisted
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    primed: true,
 | 
						|
  });
 | 
						|
 | 
						|
  info("Triggering persistent event to force the background page to start");
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs: 123 },
 | 
						|
    "fire-startupBlocking.onEvent1"
 | 
						|
  );
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
  equal(await extension.awaitMessage("triggered"), 123, "triggered event");
 | 
						|
 | 
						|
  await extension.unload();
 | 
						|
  await AddonTestUtils.promiseShutdownManager();
 | 
						|
});
 | 
						|
 | 
						|
// This test checks whether primed listeners are correctly primed to
 | 
						|
// restart the background once the background has been shutdown or
 | 
						|
// put to sleep.
 | 
						|
add_task(
 | 
						|
  { pref_set: [["extensions.eventPages.enabled", true]] },
 | 
						|
  async function test_eventpage_startup() {
 | 
						|
    await AddonTestUtils.promiseStartupManager();
 | 
						|
 | 
						|
    let extension = ExtensionTestUtils.loadExtension({
 | 
						|
      useAddonManager: "permanent",
 | 
						|
      manifest: {
 | 
						|
        applications: { gecko: { id: "eventpage@test" } },
 | 
						|
        background: { persistent: false },
 | 
						|
      },
 | 
						|
      background() {
 | 
						|
        let listener = arg => browser.test.sendMessage("triggered", arg);
 | 
						|
        browser.startupBlocking.onEvent1.addListener(listener, "triggered");
 | 
						|
        let listenerNs = arg => browser.test.sendMessage("triggered-et2", arg);
 | 
						|
        browser.nonStartupBlocking.onEvent1.addListener(
 | 
						|
          listenerNs,
 | 
						|
          "triggered-et2"
 | 
						|
        );
 | 
						|
        browser.test.onMessage.addListener(() => {
 | 
						|
          let listener = arg => browser.test.sendMessage("triggered2", arg);
 | 
						|
          browser.startupBlocking.onEvent2.addListener(listener, "triggered2");
 | 
						|
          browser.test.sendMessage("async-registered-listener");
 | 
						|
        });
 | 
						|
        browser.test.sendMessage("bg_started");
 | 
						|
      },
 | 
						|
    });
 | 
						|
    await Promise.all([
 | 
						|
      promiseObservable("register-event-listener", 2),
 | 
						|
      extension.startup(),
 | 
						|
    ]);
 | 
						|
    await extension.awaitMessage("bg_started");
 | 
						|
    extension.sendMessage("async-register-listener");
 | 
						|
    await extension.awaitMessage("async-registered-listener");
 | 
						|
 | 
						|
    async function testAfterRestart() {
 | 
						|
      assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
        primed: true,
 | 
						|
      });
 | 
						|
      // async registration should not be primed or persisted
 | 
						|
      assertPersistentListeners(extension, "startupBlocking", "onEvent2", {
 | 
						|
        primed: false,
 | 
						|
        persisted: false,
 | 
						|
      });
 | 
						|
 | 
						|
      let events = trackEvents(extension);
 | 
						|
      ok(
 | 
						|
        !events.get("background-script-event"),
 | 
						|
        "Should not have received a background script event"
 | 
						|
      );
 | 
						|
      ok(
 | 
						|
        !events.get("start-background-script"),
 | 
						|
        "Background script should not be started"
 | 
						|
      );
 | 
						|
 | 
						|
      info("Triggering persistent event to force the background page to start");
 | 
						|
      let converted = promiseObservable("convert-event-listener", 1);
 | 
						|
      Services.obs.notifyObservers(
 | 
						|
        { listenerArgs: 123 },
 | 
						|
        "fire-startupBlocking.onEvent1"
 | 
						|
      );
 | 
						|
      await extension.awaitMessage("bg_started");
 | 
						|
      await converted;
 | 
						|
      equal(await extension.awaitMessage("triggered"), 123, "triggered event");
 | 
						|
      ok(
 | 
						|
        events.get("background-script-event"),
 | 
						|
        "Should have received a background script event"
 | 
						|
      );
 | 
						|
      ok(
 | 
						|
        events.get("start-background-script"),
 | 
						|
        "Background script should be started"
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    // Shutdown the background page
 | 
						|
    await Promise.all([
 | 
						|
      promiseObservable("unregister-event-listener", 3),
 | 
						|
      new Promise(resolve => extension.extension.once("shutdown", resolve)),
 | 
						|
      AddonTestUtils.promiseShutdownManager(),
 | 
						|
    ]);
 | 
						|
    await AddonTestUtils.promiseStartupManager({ lateStartup: false });
 | 
						|
    await extension.awaitStartup();
 | 
						|
    assertPersistentListeners(extension, "nonStartupBlocking", "onEvent1", {
 | 
						|
      primed: false,
 | 
						|
      persisted: true,
 | 
						|
    });
 | 
						|
    await testAfterRestart();
 | 
						|
 | 
						|
    extension.sendMessage("async-register-listener");
 | 
						|
    await extension.awaitMessage("async-registered-listener");
 | 
						|
 | 
						|
    // We sleep twice to ensure startup and shutdown work correctly
 | 
						|
    info("test event listener registration during termination");
 | 
						|
    let registrationEvents = Promise.all([
 | 
						|
      promiseObservable("unregister-event-listener", 2),
 | 
						|
      promiseObservable("unregister-primed-listener", 1),
 | 
						|
      promiseObservable("prime-event-listener", 2),
 | 
						|
    ]);
 | 
						|
    await extension.terminateBackground();
 | 
						|
    await registrationEvents;
 | 
						|
 | 
						|
    assertPersistentListeners(extension, "nonStartupBlocking", "onEvent1", {
 | 
						|
      primed: true,
 | 
						|
      persisted: true,
 | 
						|
    });
 | 
						|
 | 
						|
    // Ensure onEvent2 does not fire, testAfterRestart will fail otherwise.
 | 
						|
    Services.obs.notifyObservers(
 | 
						|
      { listenerArgs: 123 },
 | 
						|
      "fire-startupBlocking.onEvent2"
 | 
						|
    );
 | 
						|
    await testAfterRestart();
 | 
						|
 | 
						|
    registrationEvents = Promise.all([
 | 
						|
      promiseObservable("unregister-primed-listener", 2),
 | 
						|
      promiseObservable("prime-event-listener", 2),
 | 
						|
    ]);
 | 
						|
    await extension.terminateBackground();
 | 
						|
    await registrationEvents;
 | 
						|
    await testAfterRestart();
 | 
						|
 | 
						|
    await extension.unload();
 | 
						|
    await AddonTestUtils.promiseShutdownManager();
 | 
						|
  }
 | 
						|
);
 | 
						|
 | 
						|
// This test verifies primeListener behavior for errors or ignored listeners.
 | 
						|
add_task(async function test_background_primeListener_errors() {
 | 
						|
  await AddonTestUtils.promiseStartupManager();
 | 
						|
 | 
						|
  // The internal APIs to shutdown the background work with any
 | 
						|
  // background, and in the shutdown case, events will be persisted
 | 
						|
  // and primed for a restart.
 | 
						|
  let extension = ExtensionTestUtils.loadExtension({
 | 
						|
    useAddonManager: "permanent",
 | 
						|
    background() {
 | 
						|
      // Listen for options being set so a restart will have them.
 | 
						|
      browser.test.onMessage.addListener(async (message, options) => {
 | 
						|
        if (message == "set-options") {
 | 
						|
          await browser.startupBlocking.testOptions(options);
 | 
						|
          browser.test.sendMessage("set-options:done");
 | 
						|
        }
 | 
						|
      });
 | 
						|
      let listener = arg => browser.test.sendMessage("triggered", arg);
 | 
						|
      browser.startupBlocking.onEvent1.addListener(listener, "triggered");
 | 
						|
      let listener2 = arg => browser.test.sendMessage("triggered", arg);
 | 
						|
      browser.startupBlocking.onEvent2.addListener(listener2, "triggered");
 | 
						|
      browser.test.sendMessage("bg_started");
 | 
						|
    },
 | 
						|
  });
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("register-event-listener", 1),
 | 
						|
    extension.startup(),
 | 
						|
  ]);
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    primed: false,
 | 
						|
  });
 | 
						|
 | 
						|
  // If an event is removed from an api, a permission is removed,
 | 
						|
  // or some other option prevents priming, ensure that
 | 
						|
  // primelistener works correctly.
 | 
						|
  // In this scenario we are testing that an event is not renewed
 | 
						|
  // on startup because the API does not re-prime it.  The result
 | 
						|
  // is that the event is also not persisted.  However the other
 | 
						|
  // events that are renewed should still be primed and persisted.
 | 
						|
  extension.sendMessage("set-options", {
 | 
						|
    eventName: "onEvent1",
 | 
						|
    ignoreListener: true,
 | 
						|
  });
 | 
						|
  await extension.awaitMessage("set-options:done");
 | 
						|
 | 
						|
  // Shutdown the background page
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("unregister-event-listener", 2),
 | 
						|
    extension.terminateBackground(),
 | 
						|
  ]);
 | 
						|
  // startupBlocking.onEvent1 was not re-primed and should not be persisted, but
 | 
						|
  // onEvent2 should still be primed and persisted.
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    primed: false,
 | 
						|
    persisted: false,
 | 
						|
  });
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent2", {
 | 
						|
    primed: true,
 | 
						|
  });
 | 
						|
 | 
						|
  info("Triggering persistent event to force the background page to start");
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs: 123 },
 | 
						|
    "fire-startupBlocking.onEvent2"
 | 
						|
  );
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
  equal(await extension.awaitMessage("triggered"), 123, "triggered event");
 | 
						|
 | 
						|
  // On restart, test an exception, it should not be re-primed.
 | 
						|
  extension.sendMessage("set-options", {
 | 
						|
    eventName: "onEvent1",
 | 
						|
    throwError: "error",
 | 
						|
  });
 | 
						|
  await extension.awaitMessage("set-options:done");
 | 
						|
 | 
						|
  // Shutdown the background page
 | 
						|
  await Promise.all([
 | 
						|
    promiseObservable("unregister-event-listener", 1),
 | 
						|
    extension.terminateBackground(),
 | 
						|
  ]);
 | 
						|
  // startupBlocking.onEvent1 failed and should not be persisted
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    primed: false,
 | 
						|
    persisted: false,
 | 
						|
  });
 | 
						|
 | 
						|
  info("Triggering event to verify background starts after prior error");
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs: 123 },
 | 
						|
    "fire-startupBlocking.onEvent2"
 | 
						|
  );
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
  equal(await extension.awaitMessage("triggered"), 123, "triggered event");
 | 
						|
 | 
						|
  info("reset options for next test");
 | 
						|
  extension.sendMessage("set-options", {});
 | 
						|
  await extension.awaitMessage("set-options:done");
 | 
						|
 | 
						|
  // Test errors on app restart
 | 
						|
  info("Test errors during app startup");
 | 
						|
  await AddonTestUtils.promiseRestartManager();
 | 
						|
  await extension.awaitStartup();
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
 | 
						|
  info("restart AOM and verify primed listener");
 | 
						|
  await AddonTestUtils.promiseRestartManager({ earlyStartup: false });
 | 
						|
  await extension.awaitStartup();
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    primed: true,
 | 
						|
    persisted: true,
 | 
						|
  });
 | 
						|
  AddonTestUtils.notifyEarlyStartup();
 | 
						|
 | 
						|
  Services.obs.notifyObservers(
 | 
						|
    { listenerArgs: 123 },
 | 
						|
    "fire-startupBlocking.onEvent1"
 | 
						|
  );
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
  equal(await extension.awaitMessage("triggered"), 123, "triggered event");
 | 
						|
 | 
						|
  // Test that an exception happening during priming clears the
 | 
						|
  // event from being persisted when restarting the browser, and that
 | 
						|
  // the background correctly starts.
 | 
						|
  info("test exception during primeListener on startup");
 | 
						|
  extension.sendMessage("set-options", {
 | 
						|
    eventName: "onEvent1",
 | 
						|
    throwError: "error",
 | 
						|
  });
 | 
						|
  await extension.awaitMessage("set-options:done");
 | 
						|
 | 
						|
  await AddonTestUtils.promiseRestartManager({ earlyStartup: false });
 | 
						|
  await extension.awaitStartup();
 | 
						|
  AddonTestUtils.notifyEarlyStartup();
 | 
						|
 | 
						|
  // At this point, the exception results in the persisted entry
 | 
						|
  // being cleared.
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    primed: false,
 | 
						|
    persisted: false,
 | 
						|
  });
 | 
						|
 | 
						|
  AddonTestUtils.notifyLateStartup();
 | 
						|
 | 
						|
  await extension.awaitMessage("bg_started");
 | 
						|
 | 
						|
  // The background added the listener back during top level execution,
 | 
						|
  // verify it is in the persisted list.
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    primed: false,
 | 
						|
    persisted: true,
 | 
						|
  });
 | 
						|
 | 
						|
  // reset options
 | 
						|
  extension.sendMessage("set-options", {});
 | 
						|
  await extension.awaitMessage("set-options:done");
 | 
						|
  await extension.unload();
 | 
						|
  await AddonTestUtils.promiseShutdownManager();
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_non_background_context_listener_not_persisted() {
 | 
						|
  await AddonTestUtils.promiseStartupManager();
 | 
						|
 | 
						|
  let extension = ExtensionTestUtils.loadExtension({
 | 
						|
    useAddonManager: "permanent",
 | 
						|
    background() {
 | 
						|
      let listener = arg => browser.test.sendMessage("triggered", arg);
 | 
						|
      browser.startupBlocking.onEvent1.addListener(listener, "triggered");
 | 
						|
      browser.test.sendMessage(
 | 
						|
        "bg_started",
 | 
						|
        browser.runtime.getURL("extpage.html")
 | 
						|
      );
 | 
						|
    },
 | 
						|
    files: {
 | 
						|
      "extpage.html": `<script src="extpage.js"></script>`,
 | 
						|
      "extpage.js": function() {
 | 
						|
        let listener = arg =>
 | 
						|
          browser.test.sendMessage("extpage-triggered", arg);
 | 
						|
        browser.startupBlocking.onEvent2.addListener(
 | 
						|
          listener,
 | 
						|
          "extpage-triggered"
 | 
						|
        );
 | 
						|
        // Send a message to signal the extpage has registered the listener,
 | 
						|
        // after calling an async method and wait it to be resolved to make sure
 | 
						|
        // the addListener call to have been handled in the parent process by
 | 
						|
        // the time we will assert the persisted listeners.
 | 
						|
        browser.runtime.getPlatformInfo().then(() => {
 | 
						|
          browser.test.sendMessage("extpage_started");
 | 
						|
        });
 | 
						|
      },
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
  await extension.startup();
 | 
						|
  const extpage_url = await extension.awaitMessage("bg_started");
 | 
						|
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
 | 
						|
    persisted: true,
 | 
						|
    primed: false,
 | 
						|
  });
 | 
						|
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent2", {
 | 
						|
    persisted: false,
 | 
						|
  });
 | 
						|
 | 
						|
  const page = await ExtensionTestUtils.loadContentPage(extpage_url);
 | 
						|
  await extension.awaitMessage("extpage_started");
 | 
						|
 | 
						|
  // Expect the onEvent2 listener subscribed by the extpage to not be persisted.
 | 
						|
  assertPersistentListeners(extension, "startupBlocking", "onEvent2", {
 | 
						|
    persisted: false,
 | 
						|
  });
 | 
						|
 | 
						|
  await page.close();
 | 
						|
  await extension.unload();
 | 
						|
  await AddonTestUtils.promiseShutdownManager();
 | 
						|
});
 | 
						|
 | 
						|
// Test support for event page tests
 | 
						|
const background = async function() {
 | 
						|
  let listener2 = () =>
 | 
						|
    browser.test.sendMessage("triggered:non-startupblocking");
 | 
						|
  browser.startupBlocking.onEvent1.addListener(() => {});
 | 
						|
  browser.startupBlocking.nonBlockingEvent.addListener(() => {});
 | 
						|
  browser.nonStartupBlocking.onEvent2.addListener(listener2);
 | 
						|
  browser.test.sendMessage("bg_started");
 | 
						|
};
 | 
						|
 | 
						|
const background_update = async function() {
 | 
						|
  browser.startupBlocking.onEvent1.addListener(() => {});
 | 
						|
  browser.nonStartupBlocking.onEvent2.addListener(() => {});
 | 
						|
  browser.test.sendMessage("updated_bg_started");
 | 
						|
};
 | 
						|
 | 
						|
function testPersistentListeners(extension, expect) {
 | 
						|
  for (let [ns, event, persisted, primed] of expect) {
 | 
						|
    assertPersistentListeners(extension, ns, event, {
 | 
						|
      persisted,
 | 
						|
      primed,
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
add_task(
 | 
						|
  { pref_set: [["extensions.eventPages.enabled", true]] },
 | 
						|
  async function test_startupblocking_behavior() {
 | 
						|
    await AddonTestUtils.promiseStartupManager();
 | 
						|
 | 
						|
    let extension = ExtensionTestUtils.loadExtension({
 | 
						|
      useAddonManager: "permanent",
 | 
						|
      manifest: {
 | 
						|
        background: { persistent: false },
 | 
						|
      },
 | 
						|
      background,
 | 
						|
    });
 | 
						|
 | 
						|
    await extension.startup();
 | 
						|
    await extension.awaitMessage("bg_started");
 | 
						|
 | 
						|
    // All are persisted on startup
 | 
						|
    testPersistentListeners(extension, [
 | 
						|
      ["startupBlocking", "onEvent1", true, false],
 | 
						|
      ["startupBlocking", "nonBlockingEvent", true, false],
 | 
						|
      ["nonStartupBlocking", "onEvent2", true, false],
 | 
						|
    ]);
 | 
						|
 | 
						|
    info("Test after mocked browser restart");
 | 
						|
    await Promise.all([
 | 
						|
      new Promise(resolve => extension.extension.once("shutdown", resolve)),
 | 
						|
      AddonTestUtils.promiseShutdownManager(),
 | 
						|
    ]);
 | 
						|
    await AddonTestUtils.promiseStartupManager({ lateStartup: false });
 | 
						|
    await extension.awaitStartup();
 | 
						|
 | 
						|
    testPersistentListeners(extension, [
 | 
						|
      // Startup blocking event is expected to be persisted and primed.
 | 
						|
      ["startupBlocking", "onEvent1", true, true],
 | 
						|
      // A non-startup-blocking event shouldn't be primed yet.
 | 
						|
      ["startupBlocking", "nonBlockingEvent", true, false],
 | 
						|
      // Non "Startup blocking" event is expected to be persisted but not primed yet.
 | 
						|
      ["nonStartupBlocking", "onEvent2", true, false],
 | 
						|
    ]);
 | 
						|
 | 
						|
    // Complete the browser startup and fire the startup blocking event
 | 
						|
    // to let the backgrund script to run.
 | 
						|
    AddonTestUtils.notifyLateStartup();
 | 
						|
    Services.obs.notifyObservers({}, "fire-startupBlocking.onEvent1");
 | 
						|
    await extension.awaitMessage("bg_started");
 | 
						|
 | 
						|
    info("Test after terminate background script");
 | 
						|
    await extension.terminateBackground();
 | 
						|
 | 
						|
    // After the background is terminated, all are persisted and primed.
 | 
						|
    testPersistentListeners(extension, [
 | 
						|
      ["startupBlocking", "onEvent1", true, true],
 | 
						|
      ["startupBlocking", "nonBlockingEvent", true, true],
 | 
						|
      ["nonStartupBlocking", "onEvent2", true, true],
 | 
						|
    ]);
 | 
						|
 | 
						|
    info("Notify event for the non-startupBlocking API event");
 | 
						|
    Services.obs.notifyObservers({}, "fire-nonStartupBlocking.onEvent2");
 | 
						|
    await extension.awaitMessage("bg_started");
 | 
						|
    await extension.awaitMessage("triggered:non-startupblocking");
 | 
						|
 | 
						|
    await extension.unload();
 | 
						|
    await AddonTestUtils.promiseShutdownManager();
 | 
						|
  }
 | 
						|
);
 | 
						|
 | 
						|
add_task(
 | 
						|
  { pref_set: [["extensions.eventPages.enabled", true]] },
 | 
						|
  async function test_startupblocking_behavior_upgrade() {
 | 
						|
    let id = "persistent-upgrade@test";
 | 
						|
    await AddonTestUtils.promiseStartupManager();
 | 
						|
 | 
						|
    let extensionData = {
 | 
						|
      useAddonManager: "permanent",
 | 
						|
      manifest: {
 | 
						|
        version: "1.0",
 | 
						|
        applications: {
 | 
						|
          gecko: { id },
 | 
						|
        },
 | 
						|
        background: { persistent: false },
 | 
						|
      },
 | 
						|
      background,
 | 
						|
    };
 | 
						|
    let extension = ExtensionTestUtils.loadExtension(extensionData);
 | 
						|
    await extension.startup();
 | 
						|
    await extension.awaitMessage("bg_started");
 | 
						|
 | 
						|
    // All are persisted on startup
 | 
						|
    testPersistentListeners(extension, [
 | 
						|
      ["startupBlocking", "onEvent1", true, false],
 | 
						|
      ["startupBlocking", "nonBlockingEvent", true, false],
 | 
						|
      ["nonStartupBlocking", "onEvent2", true, false],
 | 
						|
    ]);
 | 
						|
 | 
						|
    // Prepare the extension that will be updated.
 | 
						|
    extensionData.manifest.version = "2.0";
 | 
						|
    extensionData.background = background_update;
 | 
						|
 | 
						|
    info("Test after a upgrade");
 | 
						|
    await extension.upgrade(extensionData);
 | 
						|
    // upgrade should start the background
 | 
						|
    await extension.awaitMessage("updated_bg_started");
 | 
						|
 | 
						|
    // Nothing should be primed at this point after the background
 | 
						|
    // has started.  We look specifically for nonBlockingEvent to
 | 
						|
    // no longer be a part of the persisted listeners.
 | 
						|
    testPersistentListeners(extension, [
 | 
						|
      ["startupBlocking", "onEvent1", true, false],
 | 
						|
      ["startupBlocking", "nonBlockingEvent", false, false],
 | 
						|
      ["nonStartupBlocking", "onEvent2", true, false],
 | 
						|
    ]);
 | 
						|
 | 
						|
    await extension.unload();
 | 
						|
    await AddonTestUtils.promiseShutdownManager();
 | 
						|
  }
 | 
						|
);
 | 
						|
 | 
						|
add_task(
 | 
						|
  { pref_set: [["extensions.eventPages.enabled", true]] },
 | 
						|
  async function test_startupblocking_behavior_staged_upgrade() {
 | 
						|
    AddonManager.checkUpdateSecurity = false;
 | 
						|
    let id = "persistent-staged-upgrade@test";
 | 
						|
 | 
						|
    // register an update file.
 | 
						|
    AddonTestUtils.registerJSON(server, "/test_update.json", {
 | 
						|
      addons: {
 | 
						|
        [id]: {
 | 
						|
          updates: [
 | 
						|
            {
 | 
						|
              version: "2.0",
 | 
						|
              update_link:
 | 
						|
                "http://example.com/addons/test_settings_staged_restart.xpi",
 | 
						|
            },
 | 
						|
          ],
 | 
						|
        },
 | 
						|
      },
 | 
						|
    });
 | 
						|
 | 
						|
    let extensionData = {
 | 
						|
      useAddonManager: "permanent",
 | 
						|
      manifest: {
 | 
						|
        version: "2.0",
 | 
						|
        applications: {
 | 
						|
          gecko: { id, update_url: `http://example.com/test_update.json` },
 | 
						|
        },
 | 
						|
        background: { persistent: false },
 | 
						|
      },
 | 
						|
      background: background_update,
 | 
						|
    };
 | 
						|
 | 
						|
    // Prepare the update first.
 | 
						|
    server.registerFile(
 | 
						|
      `/addons/test_settings_staged_restart.xpi`,
 | 
						|
      AddonTestUtils.createTempWebExtensionFile(extensionData)
 | 
						|
    );
 | 
						|
 | 
						|
    // Prepare the extension that will be updated.
 | 
						|
    extensionData.manifest.version = "1.0";
 | 
						|
    extensionData.background = async function() {
 | 
						|
      // we're testing persistence, not operation, so no action in listeners.
 | 
						|
      browser.startupBlocking.onEvent1.addListener(() => {});
 | 
						|
      // nonBlockingEvent will be removed on upgrade
 | 
						|
      browser.startupBlocking.nonBlockingEvent.addListener(() => {});
 | 
						|
      browser.nonStartupBlocking.onEvent2.addListener(() => {});
 | 
						|
 | 
						|
      // Force a staged updated.
 | 
						|
      browser.runtime.onUpdateAvailable.addListener(async details => {
 | 
						|
        // This should be the version of the pending update.
 | 
						|
        browser.test.assertEq("2.0", details.version, "correct version");
 | 
						|
        browser.test.sendMessage("delay");
 | 
						|
      });
 | 
						|
 | 
						|
      browser.test.sendMessage("bg_started");
 | 
						|
    };
 | 
						|
 | 
						|
    await AddonTestUtils.promiseStartupManager();
 | 
						|
    let extension = ExtensionTestUtils.loadExtension(extensionData);
 | 
						|
 | 
						|
    await extension.startup();
 | 
						|
    await extension.awaitMessage("bg_started");
 | 
						|
 | 
						|
    // All are persisted but not primed on startup
 | 
						|
    testPersistentListeners(extension, [
 | 
						|
      ["startupBlocking", "onEvent1", true, false],
 | 
						|
      ["startupBlocking", "nonBlockingEvent", true, false],
 | 
						|
      ["nonStartupBlocking", "onEvent2", true, false],
 | 
						|
    ]);
 | 
						|
 | 
						|
    info("Test after a staged update");
 | 
						|
    // first, deal with getting and staging an upgrade
 | 
						|
    let addon = await AddonManager.getAddonByID(id);
 | 
						|
    Assert.equal(addon.version, "1.0", "1.0 is loaded");
 | 
						|
 | 
						|
    let update = await AddonTestUtils.promiseFindAddonUpdates(addon);
 | 
						|
    let install = update.updateAvailable;
 | 
						|
    Assert.ok(install, `install is available ${update.error}`);
 | 
						|
 | 
						|
    await AddonTestUtils.promiseCompleteAllInstalls([install]);
 | 
						|
 | 
						|
    Assert.equal(
 | 
						|
      install.state,
 | 
						|
      AddonManager.STATE_POSTPONED,
 | 
						|
      "update is staged for install"
 | 
						|
    );
 | 
						|
    await extension.awaitMessage("delay");
 | 
						|
 | 
						|
    await AddonTestUtils.promiseShutdownManager();
 | 
						|
 | 
						|
    // restarting allows upgrade to proceed
 | 
						|
    await AddonTestUtils.promiseStartupManager();
 | 
						|
    // upgrade should always start the background
 | 
						|
    await extension.awaitMessage("updated_bg_started");
 | 
						|
 | 
						|
    // Since this is an upgraded addon, the background will have started
 | 
						|
    // and we no longer have primed listeners.  Check only the persisted
 | 
						|
    // values, and that nonBlockingEvent is not persisted.
 | 
						|
    testPersistentListeners(extension, [
 | 
						|
      ["startupBlocking", "onEvent1", true, false],
 | 
						|
      ["startupBlocking", "nonBlockingEvent", false, false],
 | 
						|
      ["nonStartupBlocking", "onEvent2", true, false],
 | 
						|
    ]);
 | 
						|
 | 
						|
    await extension.unload();
 | 
						|
    await AddonTestUtils.promiseShutdownManager();
 | 
						|
  }
 | 
						|
);
 |