Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  browser_midi_permission_gated.js

  Sprache: JAVA
 

/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */


const EXAMPLE_COM_URL =
  "https://example.com/document-builder.sjs?html=<h1>Test midi permission with synthetic site permission addon</h1>";
const PAGE_WITH_IFRAMES_URL = `https://example.org/document-builder.sjs?html=
  <h1>Test midi permission with synthetic site permission addon in iframes</h1>
  <iframe id=sameOrigin src="${encodeURIComponent(
    'https://example.org/document-builder.sjs?html=SameOrigin"'
  )}"></iframe>
  <iframe id=crossOrigin  src="${encodeURIComponent(
    'https://example.net/document-builder.sjs?html=CrossOrigin"'
  )}"></iframe>`;
const USE_COUNTER_URL =
  "https://example.net/document-builder.sjs?html=<h1>Test midi use counter</h1>";

const l10n = new Localization(
  [
    "browser/addonNotifications.ftl",
    "toolkit/global/extensions.ftl",
    "toolkit/global/extensionPermissions.ftl",
    "branding/brand.ftl",
  ],
  true
);

const { HttpServer } = ChromeUtils.importESModule(
  "resource://testing-common/httpd.sys.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
  AddonTestUtils: "resource://testing-common/AddonTestUtils.sys.mjs",
});

/* import-globals-from ../../../toolkit/mozapps/extensions/test/xpinstall/helpers_addons_install_dialogs.js */
Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/dom/midi/tests/helpers_addons_install_dialogs.js",
  this
);

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [["midi.prompt.testing"false]],
  });

  AddonTestUtils.initMochitest(this);
  AddonTestUtils.hookAMTelemetryEvents();

  // Once the addon is installed, a dialog is displayed as a confirmation.
  // This could interfere with tests running after this one, so we set up a listener
  // that will always accept post install dialogs so we don't have  to deal with them in
  // the test.
  alwaysAcceptAddonPostInstallDialogs();

  registerCleanupFunction(async () => {
    // Remove the permission.
    await SpecialPowers.removePermission("midi-sysex", {
      url: EXAMPLE_COM_URL,
    });
    await SpecialPowers.removePermission("midi-sysex", {
      url: PAGE_WITH_IFRAMES_URL,
    });
    await SpecialPowers.removePermission("midi", {
      url: EXAMPLE_COM_URL,
    });
    await SpecialPowers.removePermission("midi", {
      url: PAGE_WITH_IFRAMES_URL,
    });
    await SpecialPowers.removePermission("install", {
      url: EXAMPLE_COM_URL,
    });
    await SpecialPowers.removePermission("midi-sysex", {
      url: USE_COUNTER_URL,
    });
    await SpecialPowers.removePermission("midi", {
      url: USE_COUNTER_URL,
    });
    await SpecialPowers.removePermission("install", {
      url: USE_COUNTER_URL,
    });

    while (gBrowser.tabs.length > 1) {
      BrowserTestUtils.removeTab(gBrowser.selectedTab);
    }
  });
});

add_task(async function testRequestMIDIAccess() {
  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, EXAMPLE_COM_URL);
  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
  const testPageHost = gBrowser.selectedTab.linkedBrowser.documentURI.host;
  Services.fog.testResetFOG();

  info("Check that midi-sysex isn't set");
  ok(
    await SpecialPowers.testPermission(
      "midi-sysex",
      SpecialPowers.Services.perms.UNKNOWN_ACTION,
      { url: EXAMPLE_COM_URL }
    ),
    "midi-sysex value should have UNKNOWN permission"
  );

  info("Request midi-sysex access");
  let onAddonInstallBlockedNotification = waitForNotification(
    "addon-install-blocked"
  );
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    content.midiAccessRequestPromise = content.navigator.requestMIDIAccess({
      sysex: true,
    });
  });

  info("Deny site permission addon install in first popup");
  let addonInstallPanel = await onAddonInstallBlockedNotification;
  const [installPopupHeader, installPopupMessage] =
    addonInstallPanel.querySelectorAll(
      "description.popup-notification-description"
    );
  is(
    installPopupHeader.textContent,
    l10n.formatValueSync("site-permission-install-first-prompt-midi-header"),
    "First popup has expected header text"
  );
  is(
    installPopupMessage.textContent,
    l10n.formatValueSync("site-permission-install-first-prompt-midi-message"),
    "First popup has expected message"
  );

  let notification = addonInstallPanel.childNodes[0];
  // secondaryButton is the "Don't allow" button
  notification.secondaryButton.click();

  let rejectionMessage = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      let errorMessage;
      try {
        await content.midiAccessRequestPromise;
      } catch (e) {
        errorMessage = `${e.name}: ${e.message}`;
      }

      delete content.midiAccessRequestPromise;
      return errorMessage;
    }
  );
  is(
    rejectionMessage,
    "SecurityError: WebMIDI requires a site permission add-on to activate"
  );

  AddonTestUtils.assertInstallTelemetryEvents(
    ["site_warning""cancelled"],
    "sitepermission"
  );

  info("Deny site permission addon install in second popup");
  onAddonInstallBlockedNotification = waitForNotification(
    "addon-install-blocked"
  );
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    content.midiAccessRequestPromise = content.navigator.requestMIDIAccess({
      sysex: true,
    });
  });
  addonInstallPanel = await onAddonInstallBlockedNotification;
  notification = addonInstallPanel.childNodes[0];
  let dialogPromise = waitForInstallDialog();
  notification.button.click();
  let installDialog = await dialogPromise;
  is(
    installDialog.querySelector(".popup-notification-description").textContent,
    l10n.formatValueSync(
      "webext-site-perms-header-with-gated-perms-midi-sysex",
      { hostname: testPageHost }
    ),
    "Install dialog has expected header text"
  );
  is(
    installDialog.querySelector("popupnotificationcontent description")
      .textContent,
    l10n.formatValueSync("webext-site-perms-description-gated-perms-midi"),
    "Install dialog has expected description"
  );

  // secondaryButton is the "Cancel" button
  installDialog.secondaryButton.click();

  rejectionMessage = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      let errorMessage;
      try {
        await content.midiAccessRequestPromise;
      } catch (e) {
        errorMessage = `${e.name}: ${e.message}`;
      }

      delete content.midiAccessRequestPromise;
      return errorMessage;
    }
  );
  is(
    rejectionMessage,
    "SecurityError: WebMIDI requires a site permission add-on to activate"
  );

  AddonTestUtils.assertInstallTelemetryEvents(
    ["site_warning""permissions_prompt""cancelled"],
    "sitepermission"
  );

  info("Request midi-sysex access again");
  onAddonInstallBlockedNotification = waitForNotification(
    "addon-install-blocked"
  );
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    content.midiAccessRequestPromise = content.navigator.requestMIDIAccess({
      sysex: true,
    });
  });

  info("Accept site permission addon install");
  addonInstallPanel = await onAddonInstallBlockedNotification;
  notification = addonInstallPanel.childNodes[0];
  dialogPromise = waitForInstallDialog();
  notification.button.click();
  installDialog = await dialogPromise;
  installDialog.button.click();

  info("Wait for the midi-sysex access request promise to resolve");
  let accessGranted = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      try {
        await content.midiAccessRequestPromise;
        return true;
      } catch (e) {}

      delete content.midiAccessRequestPromise;
      return false;
    }
  );
  ok(accessGranted, "requestMIDIAccess resolved");

  info("Check that midi-sysex is now set");
  ok(
    await SpecialPowers.testPermission(
      "midi-sysex",
      SpecialPowers.Services.perms.ALLOW_ACTION,
      { url: EXAMPLE_COM_URL }
    ),
    "midi-sysex value should have ALLOW permission"
  );
  ok(
    await SpecialPowers.testPermission(
      "midi",
      SpecialPowers.Services.perms.UNKNOWN_ACTION,
      { url: EXAMPLE_COM_URL }
    ),
    "but midi should have UNKNOWN permission"
  );

  info("Check that we don't prompt user again once they installed the addon");
  const accessPromiseState = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      return content.navigator
        .requestMIDIAccess({ sysex: true })
        .then(() => "resolved");
    }
  );
  is(
    accessPromiseState,
    "resolved",
    "requestMIDIAccess resolved without user prompt"
  );

  AddonTestUtils.assertInstallTelemetryEvents(
    ["site_warning""permissions_prompt""completed"],
    "sitepermission"
  );

  info("Request midi access without sysex");
  onAddonInstallBlockedNotification = waitForNotification(
    "addon-install-blocked"
  );
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    content.midiNoSysexAccessRequestPromise =
      content.navigator.requestMIDIAccess();
  });

  info("Accept site permission addon install");
  addonInstallPanel = await onAddonInstallBlockedNotification;
  notification = addonInstallPanel.childNodes[0];

  is(
    notification
      .querySelector("#addon-install-blocked-info")
      .getAttribute("href"),
    Services.urlFormatter.formatURLPref("app.support.baseURL") +
      "site-permission-addons",
    "Got the expected SUMO page as a learn more link in the addon-install-blocked panel"
  );

  dialogPromise = waitForInstallDialog();
  notification.button.click();
  installDialog = await dialogPromise;

  is(
    installDialog.querySelector(".popup-notification-description").textContent,
    l10n.formatValueSync("webext-site-perms-header-with-gated-perms-midi", {
      hostname: testPageHost,
    }),
    "Install dialog has expected header text"
  );
  is(
    installDialog.querySelector("popupnotificationcontent description")
      .textContent,
    l10n.formatValueSync("webext-site-perms-description-gated-perms-midi"),
    "Install dialog has expected description"
  );

  installDialog.button.click();

  info("Wait for the midi access request promise to resolve");
  accessGranted = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      try {
        await content.midiNoSysexAccessRequestPromise;
        return true;
      } catch (e) {}

      delete content.midiNoSysexAccessRequestPromise;
      return false;
    }
  );
  ok(accessGranted, "requestMIDIAccess resolved");

  info("Check that both midi-sysex and midi are now set");
  ok(
    await SpecialPowers.testPermission(
      "midi-sysex",
      SpecialPowers.Services.perms.ALLOW_ACTION,
      { url: EXAMPLE_COM_URL }
    ),
    "midi-sysex value should have ALLOW permission"
  );
  ok(
    await SpecialPowers.testPermission(
      "midi",
      SpecialPowers.Services.perms.ALLOW_ACTION,
      { url: EXAMPLE_COM_URL }
    ),
    "and midi value should also have ALLOW permission"
  );

  AddonTestUtils.assertInstallTelemetryEvents(
    ["site_warning""permissions_prompt""completed"],
    "sitepermission"
  );

  info("Check that we don't prompt user again when they perm denied");
  // remove permission to have a clean state
  await SpecialPowers.removePermission("midi-sysex", {
    url: EXAMPLE_COM_URL,
  });

  onAddonInstallBlockedNotification = waitForNotification(
    "addon-install-blocked"
  );
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    content.midiAccessRequestPromise = content.navigator.requestMIDIAccess({
      sysex: true,
    });
  });

  info("Perm-deny site permission addon install");
  addonInstallPanel = await onAddonInstallBlockedNotification;
  // Click the "Report Suspicious Site" menuitem, which has the same effect as
  // "Never Allow" and also submits a telemetry event (which we check below).
  notification.menupopup.querySelectorAll("menuitem")[1].click();

  rejectionMessage = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      let errorMessage;
      try {
        await content.midiAccessRequestPromise;
      } catch (e) {
        errorMessage = e.name;
      }

      delete content.midiAccessRequestPromise;
      return errorMessage;
    }
  );
  is(rejectionMessage, "SecurityError""requestMIDIAccess was rejected");

  info("Request midi-sysex access again");
  let denyIntervalStart = performance.now();
  rejectionMessage = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      let errorMessage;
      try {
        await content.navigator.requestMIDIAccess({
          sysex: true,
        });
      } catch (e) {
        errorMessage = e.name;
      }
      return errorMessage;
    }
  );
  is(
    rejectionMessage,
    "SecurityError",
    "requestMIDIAccess was rejected without user prompt"
  );
  let denyIntervalElapsed = performance.now() - denyIntervalStart;
  Assert.greaterOrEqual(
    denyIntervalElapsed,
    3000,
    `Rejection should be delayed by a randomized interval no less than 3 seconds (got ${
      denyIntervalElapsed / 1000
    } seconds)`
  );

  Assert.deepEqual(
    [{ suspicious_site: "example.com", permission_type: "midi-sysex" }],
    AddonTestUtils.getAMGleanEvents("reportSuspiciousSite"),
    "Expected Glean event recorded."
  );

  AddonTestUtils.assertInstallTelemetryEvents(
    ["site_warning""cancelled"],
    "sitepermission"
  );

  Assert.deepEqual(
    Array.from(
      new Set(
        AddonTestUtils.getAMGleanEvents("install").map(
          evt => evt.site_permission
        )
      )
    ).sort(),
    ["midi""midi-sysex"],
    "Install telemetry events distinguish the midi and midi-sysex site permissions"
  );
});

add_task(async function testIframeRequestMIDIAccess() {
  gBrowser.selectedTab = BrowserTestUtils.addTab(
    gBrowser,
    PAGE_WITH_IFRAMES_URL
  );
  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);

  info("Check that midi-sysex isn't set");
  ok(
    await SpecialPowers.testPermission(
      "midi-sysex",
      SpecialPowers.Services.perms.UNKNOWN_ACTION,
      { url: PAGE_WITH_IFRAMES_URL }
    ),
    "midi-sysex value should have UNKNOWN permission"
  );

  info("Request midi-sysex access from the same-origin iframe");
  const sameOriginIframeBrowsingContext = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      return content.document.getElementById("sameOrigin").browsingContext;
    }
  );

  let onAddonInstallBlockedNotification = waitForNotification(
    "addon-install-blocked"
  );
  await SpecialPowers.spawn(sameOriginIframeBrowsingContext, [], () => {
    content.midiAccessRequestPromise = content.navigator.requestMIDIAccess({
      sysex: true,
    });
  });

  info("Accept site permission addon install");
  const addonInstallPanel = await onAddonInstallBlockedNotification;
  const notification = addonInstallPanel.childNodes[0];
  const dialogPromise = waitForInstallDialog();
  notification.button.click();
  let installDialog = await dialogPromise;
  installDialog.button.click();

  info("Wait for the midi-sysex access request promise to resolve");
  const accessGranted = await SpecialPowers.spawn(
    sameOriginIframeBrowsingContext,
    [],
    async () => {
      try {
        await content.midiAccessRequestPromise;
        return true;
      } catch (e) {}

      delete content.midiAccessRequestPromise;
      return false;
    }
  );
  ok(accessGranted, "requestMIDIAccess resolved");

  info("Check that midi-sysex is now set");
  ok(
    await SpecialPowers.testPermission(
      "midi-sysex",
      SpecialPowers.Services.perms.ALLOW_ACTION,
      { url: PAGE_WITH_IFRAMES_URL }
    ),
    "midi-sysex value should have ALLOW permission"
  );

  info(
    "Check that we don't prompt user again once they installed the addon from the same-origin iframe"
  );
  const accessPromiseState = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      return content.navigator
        .requestMIDIAccess({ sysex: true })
        .then(() => "resolved");
    }
  );
  is(
    accessPromiseState,
    "resolved",
    "requestMIDIAccess resolved without user prompt"
  );

  AddonTestUtils.assertInstallTelemetryEvents(
    ["site_warning""permissions_prompt""completed"],
    "sitepermission"
  );

  info("Check that request is rejected when done from a cross-origin iframe");
  const crossOriginIframeBrowsingContext = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      return content.document.getElementById("crossOrigin").browsingContext;
    }
  );

  const onConsoleErrorMessage = new Promise(resolve => {
    const errorListener = {
      observe(error) {
        if (error.message.includes("WebMIDI access request was denied")) {
          resolve(error);
          Services.console.unregisterListener(errorListener);
        }
      },
    };
    Services.console.registerListener(errorListener);
  });

  const rejectionMessage = await SpecialPowers.spawn(
    crossOriginIframeBrowsingContext,
    [],
    async () => {
      let errorName;
      try {
        await content.navigator.requestMIDIAccess({
          sysex: true,
        });
      } catch (e) {
        errorName = e.name;
      }
      return errorName;
    }
  );

  is(
    rejectionMessage,
    "SecurityError",
    "requestMIDIAccess from the remote iframe was rejected"
  );

  const consoleErrorMessage = await onConsoleErrorMessage;
  ok(
    consoleErrorMessage.message.includes(
      `WebMIDI access request was denied: ❝SitePermsAddons can't be installed from cross origin subframes❞`,
      "an error message is sent to the console"
    )
  );
  AddonTestUtils.assertInstallTelemetryEvents([], "sitepermission");
});

add_task(async function testRequestMIDIAccessLocalhost() {
  const httpServer = new HttpServer();
  httpServer.start(-1);
  httpServer.registerPathHandler(`/test`, function (request, response) {
    response.setStatusLine(request.httpVersion, 200"OK");
    response.write(`
      <!DOCTYPE html>
      <meta charset=utf8>
      <h1>Test requestMIDIAccess on lcoalhost</h1>`);
  });
  const localHostTestUrl = `http://localhost:${httpServer.identity.primaryPort}/test`;

  registerCleanupFunction(async function cleanup() {
    await new Promise(resolve => httpServer.stop(resolve));
  });

  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, localHostTestUrl);
  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);

  info("Check that midi-sysex isn't set");
  ok(
    await SpecialPowers.testPermission(
      "midi-sysex",
      SpecialPowers.Services.perms.UNKNOWN_ACTION,
      { url: localHostTestUrl }
    ),
    "midi-sysex value should have UNKNOWN permission"
  );

  info(
    "Request midi-sysex access should not prompt for addon install on locahost, but for permission"
  );
  let popupShown = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    content.midiAccessRequestPromise = content.navigator.requestMIDIAccess({
      sysex: true,
    });
  });
  await popupShown;
  is(
    PopupNotifications.panel.querySelector("popupnotification").id,
    "midi-notification",
    "midi notification was displayed"
  );

  info("Accept permission");
  PopupNotifications.panel
    .querySelector(".popup-notification-primary-button")
    .click();

  info("Wait for the midi-sysex access request promise to resolve");
  let accessGranted = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      try {
        await content.midiAccessRequestPromise;
        return true;
      } catch (e) {}

      delete content.midiAccessRequestPromise;
      return false;
    }
  );
  ok(accessGranted, "requestMIDIAccess resolved");

  // We're remembering permission grants temporarily on the tab since Bug 1754005.
  info(
    "Check that a new request is automatically granted because we granted before in the same tab."
  );

  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
    content.navigator.requestMIDIAccess({ sysex: true });
  });

  accessGranted = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    async () => {
      try {
        await content.midiAccessRequestPromise;
        return true;
      } catch (e) {}

      delete content.midiAccessRequestPromise;
      return false;
    }
  );
  ok(accessGranted, "requestMIDIAccess resolved");

  AddonTestUtils.assertInstallTelemetryEvents([], "sitepermission");
});

add_task(async function testDisabledRequestMIDIAccessFile() {
  let dir = getChromeDir(getResolvedURI(gTestPath));
  dir.append("blank.html");
  const fileSchemeTestUri = Services.io.newFileURI(dir).spec;

  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, fileSchemeTestUri);
  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);

  info("Check that requestMIDIAccess isn't set on navigator on file scheme");
  const isRequestMIDIAccessDefined = await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [],
    () => {
      return "requestMIDIAccess" in content.wrappedJSObject.navigator;
    }
  );
  is(
    isRequestMIDIAccessDefined,
    false,
    "navigator.requestMIDIAccess is not defined on file scheme"
  );
});

add_task(async function testMIDIAccessGrantedUseCounter() {
  // Verify the MIDIAccessGranted use counter is set on the document when
  // requestMIDIAccess succeeds, and not set when permission is denied.
  // Use counters are reported when the document is destroyed (tab close),
  // so we open/close tabs and snapshot counters before and after.
  //
  // navigator_requestmidiaccess is a [UseCounter] on the method itself
  // (fires whether or not the call resolves), so we use it as a sentinel to
  // know the destroyed document's counters have been flushed before reading
  // midiaccess_granted.

  async function snapshotCounters() {
    await Services.fog.testFlushAllChildren();
    return {
      grantedPage: Glean.useCounterPage.midiaccessGranted.testGetValue() ?? 0,
      grantedDoc: Glean.useCounterDoc.midiaccessGranted.testGetValue() ?? 0,
      requestPage:
        Glean.useCounterPage.navigatorRequestmidiaccess.testGetValue() ?? 0,
    };
  }

  async function waitForFlush(prevRequestPage) {
    await BrowserTestUtils.waitForCondition(async () => {
      await Services.fog.testFlushAllChildren();
      return (
        (Glean.useCounterPage.navigatorRequestmidiaccess.testGetValue() ?? 0) >
        prevRequestPage
      );
    }, "Wait for use counter ping from destroyed document");
  }

  // ===== Deny phase =====
  info("Deny phase: open tab on a fresh origin and reject addon install");
  let before = await snapshotCounters();

  let denyTab = BrowserTestUtils.addTab(gBrowser, USE_COUNTER_URL);
  gBrowser.selectedTab = denyTab;
  await BrowserTestUtils.browserLoaded(denyTab.linkedBrowser);

  let onAddonInstallBlockedNotification = waitForNotification(
    "addon-install-blocked"
  );
  await SpecialPowers.spawn(denyTab.linkedBrowser, [], () => {
    content.midiAccessRequestPromise = content.navigator.requestMIDIAccess({
      sysex: true,
    });
  });

  let addonInstallPanel = await onAddonInstallBlockedNotification;
  // secondaryButton is the "Don't allow" button
  addonInstallPanel.childNodes[0].secondaryButton.click();

  let denyRejected = await SpecialPowers.spawn(
    denyTab.linkedBrowser,
    [],
    async () => {
      try {
        await content.midiAccessRequestPromise;
      } catch (e) {
        return true;
      } finally {
        delete content.midiAccessRequestPromise;
      }
      return false;
    }
  );
  ok(denyRejected, "requestMIDIAccess was rejected in deny phase");

  await BrowserTestUtils.removeTab(denyTab);
  await waitForFlush(before.requestPage);

  let afterDeny = await snapshotCounters();
  is(
    afterDeny.grantedPage,
    before.grantedPage,
    "midiaccess_granted page counter unchanged when permission denied"
  );
  is(
    afterDeny.grantedDoc,
    before.grantedDoc,
    "midiaccess_granted document counter unchanged when permission denied"
  );

  // ===== Allow phase =====
  info("Allow phase: open tab on same origin and accept addon install");
  let allowTab = BrowserTestUtils.addTab(gBrowser, USE_COUNTER_URL);
  gBrowser.selectedTab = allowTab;
  await BrowserTestUtils.browserLoaded(allowTab.linkedBrowser);

  onAddonInstallBlockedNotification = waitForNotification(
    "addon-install-blocked"
  );
  await SpecialPowers.spawn(allowTab.linkedBrowser, [], () => {
    content.midiAccessRequestPromise = content.navigator.requestMIDIAccess({
      sysex: true,
    });
  });

  addonInstallPanel = await onAddonInstallBlockedNotification;
  let dialogPromise = waitForInstallDialog();
  addonInstallPanel.childNodes[0].button.click();
  let installDialog = await dialogPromise;
  installDialog.button.click();

  let allowResolved = await SpecialPowers.spawn(
    allowTab.linkedBrowser,
    [],
    async () => {
      try {
        await content.midiAccessRequestPromise;
        return true;
      } catch (e) {
      } finally {
        delete content.midiAccessRequestPromise;
      }
      return false;
    }
  );
  ok(allowResolved, "requestMIDIAccess resolved in allow phase");

  await BrowserTestUtils.removeTab(allowTab);
  await waitForFlush(afterDeny.requestPage);

  let afterAllow = await snapshotCounters();
  is(
    afterAllow.grantedPage,
    afterDeny.grantedPage + 1,
    "midiaccess_granted page counter incremented when permission granted"
  );
  is(
    afterAllow.grantedDoc,
    afterDeny.grantedDoc + 1,
    "midiaccess_granted document counter incremented when permission granted"
  );
});

// Ignore any additional telemetry events collected in this file.
// Unfortunately it doesn't work to have this in a cleanup function.
// Keep this as the last task done.
add_task(function teardown_telemetry_events() {
  AddonTestUtils.getAMTelemetryEvents();
});

// See helpers_addons_install_dialogs.js for shared helpers. If needed, update the shared
// helpers defined there instead of re-introducing local copies.

Messung V0.5 in Prozent
C=96 H=99 G=97

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.20Bemerkung:  (vorverarbeitet am  2026-08-25) ¤

*Bot Zugriff






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002