Commit aa7c4e88 authored by Gijs Kruitbosch's avatar Gijs Kruitbosch
Browse files

Bug 1831259 - prevent initialization re-entrancy for preference panes, r=mconley

This should fix the issue experienced by the reporter. It also pushes the
re-entrancy guard into the the 'init' method we define on gCategoryInits
objects, and removes the asynchronicity which was only there for fluent's sake.

Instead of blocking the insertion on fluent, which meant that for the in-page
find functionality we do N initializations and fluent passes, we make each of
the 2 consumers responsible for checking translation has completed. This means
find in page now just has 1 fluent pass, instead of N passes for N categories.
This should speed up the find in page initialization, and means initialization
of a category is now sync instead of async.

Differential Revision: https://phabricator.services.mozilla.com/D178232
parent c757e62a
Loading
Loading
Loading
Loading
+7 −4
Original line number Diff line number Diff line
@@ -105,10 +105,13 @@ var gSearchResultsPane = {
    if (!this.categoriesInitialized) {
      this.categoriesInitialized = true;
      // Each element of gCategoryInits is a name
      for (let [, /* name */ category] of gCategoryInits) {
        if (!category.inited) {
          await category.init();
      for (let category of gCategoryInits.values()) {
        category.init();
      }
      if (document.hasPendingL10nMutations) {
        await new Promise(r =>
          document.addEventListener("L10nMutationsFinished", r, { once: true })
        );
      }
    }
  },
+26 −41
Original line number Diff line number Diff line
@@ -155,43 +155,24 @@ var gLastCategory = { category: undefined, subcategory: undefined };
const gXULDOMParser = new DOMParser();
var gCategoryModules = new Map();
var gCategoryInits = new Map();
function init_category_if_required(category) {
  let categoryInfo = gCategoryInits.get(category);
  if (!categoryInfo) {
    throw new Error(
      "Unknown in-content prefs category! Can't init " + category
    );
  }
  if (categoryInfo.inited) {
    return null;
  }
  return categoryInfo.init();
}

function register_module(categoryName, categoryObject) {
  gCategoryModules.set(categoryName, categoryObject);
  gCategoryInits.set(categoryName, {
    inited: false,
    async init() {
    _initted: false,
    init() {
      let startTime = performance.now();
      if (this._initted) {
        return;
      }
      this._initted = true;
      let template = document.getElementById("template-" + categoryName);
      if (template) {
        // Replace the template element with the nodes inside of it.
        let frag = template.content;
        await document.l10n.translateFragment(frag);

        // Actually insert them into the DOM.
        document.l10n.pauseObserving();
        template.replaceWith(frag);
        document.l10n.resumeObserving();

        // We need to queue an update again because the previous update might
        // have happened while we awaited on translateFragment.
        Preferences.queueUpdateOfAllElements();
        template.replaceWith(template.content);
      }

      categoryObject.init();
      this.inited = true;
      ChromeUtils.addProfilerMarker(
        "Preferences",
        { startTime },
@@ -386,17 +367,20 @@ async function gotoPref(
  }
  window.history.replaceState(category, document.title);

  try {
    await init_category_if_required(category);
  } catch (ex) {
    console.error(
      new Error(
        "Error initializing preference category " + category + ": " + ex
      )
  let categoryInfo = gCategoryInits.get(category);
  if (!categoryInfo) {
    let err = new Error(
      "Unknown in-content prefs category! Can't init " + category
    );
    throw ex;
    console.error(err);
    throw err;
  }
  categoryInfo.init();

  if (document.hasPendingL10nMutations) {
    await new Promise(r =>
      document.addEventListener("L10nMutationsFinished", r, { once: true })
    );
    // Bail out of this goToPref if the category
    // or subcategory changed during async operation.
    if (
@@ -405,6 +389,7 @@ async function gotoPref(
    ) {
      return;
    }
  }

  search(category, "data-category");

+12 −2
Original line number Diff line number Diff line
@@ -143,17 +143,27 @@ add_task(async function testFilterFeatures() {
      );
    }

    // Check that switching to a non-find-in-page category changes item
    // visibility appropriately.
    EventUtils.synthesizeMouseAtCenter(
      doc.getElementById(category),
      {},
      gBrowser.contentWindow
    );

    // Ensure that async passes of localization and any code waiting for
    // those passes have finished running.
    await new Promise(r =>
      requestAnimationFrame(() => requestAnimationFrame(r))
    );
    let shouldShow = category == "category-experimental";
    for (let definition of definitions) {
      checkVisibility(
        doc.getElementById(definition.id),
        true,
        `${definition.id} should be visible after category change to ${category}`
        shouldShow,
        `${definition.id} should be ${
          shouldShow ? "visible" : "hidden"
        } after category change to ${category}`
      );
    }
  }
+5 −2
Original line number Diff line number Diff line
@@ -689,9 +689,12 @@ add_task(async function mainMenu() {
});

add_task(async function preferences() {
  let initialized = BrowserTestUtils.waitForEvent(gBrowser, "Initialized");
  let finalPaneEvent = Services.prefs.getBoolPref("identity.fxaccounts.enabled")
    ? "sync-pane-loaded"
    : "privacy-pane-loaded";
  let finalPrefPaneLoaded = TestUtils.topicObserved(finalPaneEvent, () => true);
  await BrowserTestUtils.withNewTab("about:preferences", async browser => {
    await initialized;
    await finalPrefPaneLoaded;

    Services.telemetry.getSnapshotForKeyedScalars("main", true);