Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/widget/windows/tests/unit/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 21 kB image not shown  

Quelle  test_windows_alert_service.js   Sprache: JAVA

 

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


/*
 * Test that Windows alert notifications generate expected XML.
 */


var { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

let gProfD = do_get_profile();

// Setup that allows to use the profile service in xpcshell tests,
// lifted from `toolkit/profile/xpcshell/head.js`.
function setupProfileService() {
  let gDataHome = gProfD.clone();
  gDataHome.append("data");
  gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
  let gDataHomeLocal = gProfD.clone();
  gDataHomeLocal.append("local");
  gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);

  let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService(
    Ci.nsIXREDirProvider
  );
  xreDirProvider.setUserDataDirectory(gDataHome, false);
  xreDirProvider.setUserDataDirectory(gDataHomeLocal, true);
}

add_setup(setupProfileService);

function makeAlert(options) {
  var alert = Cc["@mozilla.org/alert-notification;1"].createInstance(
    Ci.nsIAlertNotification
  );
  alert.init(
    options.name,
    options.imageURL,
    options.title,
    options.text,
    options.textClickable,
    options.cookie,
    options.dir,
    options.lang,
    options.data,
    options.principal,
    options.inPrivateBrowsing,
    options.requireInteraction,
    options.silent,
    options.vibrate || []
  );
  if (options.actions) {
    alert.actions = options.actions;
  }
  if (options.opaqueRelaunchData) {
    alert.opaqueRelaunchData = options.opaqueRelaunchData;
  }
  return alert;
}

/**
 * Take a `key1\nvalue1\n...` string encoding as used by the Windows native
 * notification server DLL, and split it into an object, keeping `action\n...`
 * intact.
 *
 * @param {string} t string encoding.
 * @returns {object} an object with keys and values.
 */

function parseOneEncoded(t) {
  var launch = {};

  var lines = t.split("\n");
  while (lines.length) {
    var key = lines.shift();
    var value;
    if (key === "action") {
      value = lines.join("\n");
      lines = [];
    } else {
      value = lines.shift();
    }
    launch[key] = value;
  }

  return launch;
}

/**
 * This complicated-looking function takes a (XML) string representation of a
 * Windows alert (toast notification), parses it into XML, extracts and further
 * parses internal data, and returns a simplified XML representation together
 * with the parsed internals.
 *
 * Doing this lets us compare JSON objects rather than stringified-JSON further
 * encoded as XML strings, which have lots of slashes and `"` characters to
 * contend with.
 *
 * @param {string} s XML string for Windows alert.

 * @returns {Array} a pair of a simplified XML string and an object with
 *                  `launch` and `actions` keys.
 */

function parseLaunchAndActions(s) {
  var document = new DOMParser().parseFromString(s, "text/xml");
  var root = document.documentElement;

  var launchString = root.getAttribute("launch");
  root.setAttribute("launch", "launch");
  var launch = parseOneEncoded(launchString);

  // `actions` is keyed by "content" attribute.
  let actions = {};
  for (var actionElement of root.querySelectorAll("action")) {
    // `activationType="system"` is special.  Leave them alone.
    let systemActivationType =
      actionElement.getAttribute("activationType") === "system";

    let action = {};
    let names = [...actionElement.attributes].map(attribute => attribute.name);

    for (var name of names) {
      let value = actionElement.getAttribute(name);

      // Here is where we parse stringified-JSON to simplify comparisons.
      if (value.startsWith("{")) {
        value = JSON.parse(value);
        if ("opaqueRelaunchData" in value) {
          value.opaqueRelaunchData = JSON.parse(value.opaqueRelaunchData);
        }
      }

      if (name == "arguments" && !systemActivationType) {
        action[name] = parseOneEncoded(value);
      } else {
        action[name] = value;
      }

      if (name != "content" && !systemActivationType) {
        actionElement.removeAttribute(name);
      }
    }

    let actionName = actionElement.getAttribute("content");
    actions[actionName] = action;
  }

  return [new XMLSerializer().serializeToString(document), { launch, actions }];
}

function escape(s) {
  return s
    .replace(/&/g, "&")
    .replace(/"/g, """)
    .replace(/</g, "<")
    .replace(/>/g, ">")
    .replace(/\n/g, " ");
}

function unescape(s) {
  return s
    .replace(/&/g, "&")
    .replace(/"/g, '"')
    .replace(/</g, "<")
    .replace(/>/g, ">")
    .replace(/ /g, "\n");
}

function testAlert(when, { serverEnabled, profD, isBackgroundTaskMode } = {}) {
  let argumentString = action => {
    //   is "\n".
    let s = ``;
    if (serverEnabled) {
      s += `program ${AppConstants.MOZ_APP_NAME}`;
    } else {
      s += `skipNotificationServer true`;
    }
    if (serverEnabled && profD) {
      s += ` profile ${profD.path}`;
    }
    if (serverEnabled) {
      s += " windowsTag ";
    }
    if (action) {
      s += ` action ${escape(JSON.stringify(action))}`;
    }

    return s;
  };

  let parsedArgumentString = action =>
    parseOneEncoded(unescape(argumentString(action)));

  let settingsAction = isBackgroundTaskMode
    ? ""
    : `<action content="Notification settings"/>`;

  let parsedSettingsAction = origin => {
    if (isBackgroundTaskMode) {
      return [];
    }
    let content = "Notification settings";
    return [
      content,
      {
        content,
        arguments: parsedArgumentString(
          Object.assign(
            {
              action: "settings",
            },
            origin && {
              origin,
            }
          )
        ),
        placement: "contextmenu",
      },
    ];
  };

  let parsedSnoozeAction = (hostport, origin) => {
    let content = `Disable notifications from ${hostport}`;
    return [
      content,
      {
        content,
        arguments: parsedArgumentString(
          Object.assign(
            {
              action: "snooze",
            },
            origin && {
              origin,
            }
          )
        ),
        placement: "contextmenu",
      },
    ];
  };

  let alertsService = Cc["@mozilla.org/system-alerts-service;1"]
    .getService(Ci.nsIAlertsService)
    .QueryInterface(Ci.nsIWindowsAlertsService);

  let name = "name";
  let title = "title";
  let text = "text";
  let imageURL = "file:///image.png";
  let actions = [
    { action: "action1", title: "title1", iconURL: "file:///iconURL1.png" },
    { action: "action2", title: "title2", iconURL: "file:///iconURL2.png" },
  ];
  let opaqueRelaunchData = { foo: 1, bar: "two" };

  let alert = makeAlert({ name, title, text });
  let expected = `<toast launch="launch"><visual><binding template="ToastText03"><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({ action: "" }),
        actions: Object.fromEntries(
          [parsedSettingsAction()].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );

  alert = makeAlert({ name, title, text, imageURL });
  expected = `<toast launch="launch"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({ action: "" }),
        actions: Object.fromEntries(
          [parsedSettingsAction()].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );

  alert = makeAlert({ name, title, text, imageURL, requireInteraction: true });
  expected = `<toast scenario="reminder" launch="launch"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="Dismiss" arguments="dismiss" activationType="system"/></actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({ action: "" }),
        actions: Object.fromEntries(
          [
            parsedSettingsAction(),
            [
              "Dismiss",
              {
                content: "Dismiss",
                arguments: "dismiss",
                activationType: "system",
              },
            ],
          ].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );

  alert = makeAlert({ name, title, text, imageURL, actions });
  expected = `<toast launch="launch"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="title1"/><action content="title2"/></actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({ action: "" }),
        actions: Object.fromEntries(
          [
            parsedSettingsAction(),
            [
              "title1",
              {
                content: "title1",
                arguments: parsedArgumentString({ action: "action1" }),
              },
            ],
            [
              "title2",
              {
                content: "title2",
                arguments: parsedArgumentString({ action: "action2" }),
              },
            ],
          ].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );

  // Chrome privileged alerts can use `windowsSystemActivationType`.
  let systemActions = [
    {
      action: "dismiss",
      title: "dismissTitle",
      windowsSystemActivationType: true,
    },
    {
      action: "snooze",
      title: "snoozeTitle",
      windowsSystemActivationType: true,
    },
  ];
  let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
  alert = makeAlert({
    name,
    title,
    text,
    imageURL,
    principal: systemPrincipal,
    actions: systemActions,
  });
  let parsedSettingsActionWithPrivilegedName = isBackgroundTaskMode
    ? []
    : [
        "Notification settings",
        {
          content: "Notification settings",
          arguments: parsedArgumentString({
            action: "settings",
            privilegedName: name,
          }),
          placement: "contextmenu",
        },
      ];

  expected = `<toast launch="launch"><visual><binding template="ToastGeneric"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="dismissTitle" arguments="dismiss" activationType="system"/><action content="snoozeTitle" arguments="snooze" activationType="system"/></actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({ action: "", privilegedName: name }),
        actions: Object.fromEntries(
          [
            parsedSettingsActionWithPrivilegedName,
            [
              "dismissTitle",
              {
                content: "dismissTitle",
                arguments: "dismiss",
                activationType: "system",
              },
            ],
            [
              "snoozeTitle",
              {
                content: "snoozeTitle",
                arguments: "snooze",
                activationType: "system",
              },
            ],
          ].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );

  // But content unprivileged alerts can't use `windowsSystemActivationType`.
  let path = "https://example.com/foo/bar.html";
  const principaluri = Services.io.newURI(path);
  const principal = Services.scriptSecurityManager.createContentPrincipal(
    principaluri,
    {}
  );

  alert = makeAlert({
    name,
    title,
    text,
    imageURL,
    actions: systemActions,
    principal,
  });
  expected = `<toast launch="launch"><visual><binding template="ToastImageAndText04"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text><text id="3" placement="attribution">via example.com</text></binding></visual><actions><action content="Disable notifications from example.com"/>${settingsAction}<action content="dismissTitle"/><action content="snoozeTitle"/></actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({
          action: "",
          origin: principal.origin,
        }),
        actions: Object.fromEntries(
          [
            parsedSnoozeAction(principal.hostPort, principal.origin),
            parsedSettingsAction(principal.origin),
            [
              "dismissTitle",
              {
                content: "dismissTitle",
                arguments: parsedArgumentString({
                  action: "dismiss",
                  origin: principal.origin,
                }),
              },
            ],
            [
              "snoozeTitle",
              {
                content: "snoozeTitle",
                arguments: parsedArgumentString({
                  action: "snooze",
                  origin: principal.origin,
                }),
              },
            ],
          ].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );

  // Chrome privileged alerts can set `opaqueRelaunchData`.
  alert = makeAlert({
    name,
    title,
    text,
    imageURL,
    principal: systemPrincipal,
    opaqueRelaunchData: JSON.stringify(opaqueRelaunchData),
  });
  expected = `<toast launch="launch"><visual><binding template="ToastGeneric"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({
          action: "",
          opaqueRelaunchData: JSON.stringify(opaqueRelaunchData),
          privilegedName: name,
        }),
        actions: Object.fromEntries(
          [parsedSettingsActionWithPrivilegedName].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );

  // But content unprivileged alerts can't set `opaqueRelaunchData`.
  alert = makeAlert({
    name,
    title,
    text,
    imageURL,
    principal,
    opaqueRelaunchData: JSON.stringify(opaqueRelaunchData),
  });
  expected = `<toast launch="launch"><visual><binding template="ToastImageAndText04"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text><text id="3" placement="attribution">via example.com</text></binding></visual><actions><action content="Disable notifications from example.com"/>${settingsAction}</actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({
          action: "",
          origin: principal.origin,
        }),
        actions: Object.fromEntries(
          [
            parsedSnoozeAction(principal.hostPort, principal.origin),
            parsedSettingsAction(principal.origin),
          ].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );

  // Chrome privileged alerts can set action-specific relaunch parameters.
  let systemRelaunchActions = [
    {
      action: "action1",
      title: "title1",
      opaqueRelaunchData: JSON.stringify({ json: "data1" }),
    },
    {
      action: "action2",
      title: "title2",
      opaqueRelaunchData: JSON.stringify({ json: "data2" }),
    },
  ];
  systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
  alert = makeAlert({
    name,
    title,
    text,
    imageURL,
    principal: systemPrincipal,
    actions: systemRelaunchActions,
  });
  expected = `<toast launch="launch"><visual><binding template="ToastGeneric"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="title1"/><action content="title2"/></actions></toast>`;
  Assert.deepEqual(
    [
      expected.replace("<actions></actions>", "<actions/>"),
      {
        launch: parsedArgumentString({ action: "", privilegedName: name }),
        actions: Object.fromEntries(
          [
            parsedSettingsActionWithPrivilegedName,
            [
              "title1",
              {
                content: "title1",
                arguments: parsedArgumentString(
                  {
                    action: "action1",
                    opaqueRelaunchData: JSON.stringify({ json: "data1" }),
                    privilegedName: name,
                  },
                  null,
                  name
                ),
              },
            ],

            [
              "title2",
              {
                content: "title2",
                arguments: parsedArgumentString(
                  {
                    action: "action2",
                    opaqueRelaunchData: JSON.stringify({ json: "data2" }),
                    privilegedName: name,
                  },
                  null,
                  name
                ),
              },
            ],
          ].filter(x => x.length)
        ),
      },
    ],
    parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
    when
  );
}

add_task(async () => {
  Services.prefs.deleteBranch(
    "alerts.useSystemBackend.windows.notificationserver.enabled"
  );
  testAlert("when notification server pref is unset", {
    profD: gProfD,
  });

  Services.prefs.setBoolPref(
    "alerts.useSystemBackend.windows.notificationserver.enabled",
    false
  );
  testAlert("when notification server pref is false", { profD: gProfD });

  Services.prefs.setBoolPref(
    "alerts.useSystemBackend.windows.notificationserver.enabled",
    true
  );
  testAlert("when notification server pref is true", {
    serverEnabled: true,
    profD: gProfD,
  });
});

let condition = {
  skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
};

add_task(condition, async () => {
  const bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
    Ci.nsIBackgroundTasks
  );

  // Pretend that this is a background task.
  bts.overrideBackgroundTaskNameForTesting("taskname");

  Services.prefs.setBoolPref(
    "alerts.useSystemBackend.windows.notificationserver.enabled",
    true
  );
  testAlert(
    "when notification server pref is true in background task, no default profile",
    { serverEnabled: true, isBackgroundTaskMode: true }
  );

  let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
    Ci.nsIToolkitProfileService
  );

  let profilePath = do_get_profile();
  profilePath.append(`test_windows_alert_service`);
  let profile = profileService.createUniqueProfile(
    profilePath,
    "test_windows_alert_service",
    "tests"
  );

  profileService.defaultProfile = profile;

  testAlert(
    "when notification server pref is true in background task, default profile",
    { serverEnabled: true, isBackgroundTaskMode: true, profD: profilePath }
  );

  // No longer a background task,
  bts.overrideBackgroundTaskNameForTesting("");
});

// This test checks whether `nsIWindowsalert.imagePathUnchecked` is used
// in the generated XML used to display notifications.
add_task(async function test_image_unchecked_xml() {
  let alertsService = Cc["@mozilla.org/system-alerts-service;1"].getService(
    Ci.nsIWindowsAlertsService
  );

  let name = "name";
  let title = "title";
  let text = "text";
  let imageURL = "chrome://branding/content/icon32.gif";
  let alert = makeAlert({ name, title, text, imageURL });

  let capturedXML = alertsService.getXmlStringForWindowsAlert(alert);
  Assert.ok(capturedXML, "Should have captured toast XML");

  const xmlDoc = new DOMParser().parseFromString(
    capturedXML,
    "application/xml"
  );
  const [img] = xmlDoc.getElementsByTagNameNS("*", "image");
  const src = img?.getAttribute("src");

  Assert.deepEqual(
    src,
    imageURL,
    "Check if image path is set in alert.imagePathUnchecked"
  );
});

// This test makes sure that we can still get a notification xml without
// images in it.
add_task(async function test_not_gif_xml() {
  let alertsService = Cc["@mozilla.org/system-alerts-service;1"].getService(
    Ci.nsIWindowsAlertsService
  );

  let name = "name";
  let title = "title";
  let text = "text";

  let alert = makeAlert({ name, title, text });

  let capturedXML = alertsService.getXmlStringForWindowsAlert(alert);
  Assert.ok(capturedXML, "Should have captured toast XML");

  const xmlDoc = new DOMParser().parseFromString(
    capturedXML,
    "application/xml"
  );
  const [img] = xmlDoc.getElementsByTagNameNS("*", "image");
  const src = img?.getAttribute("src");

  Assert.ok(
    !src,
    "Ensure that imagePathUnchecked is not set since there is no image."
  );
});

Messung V0.5 in Prozent
C=96 H=94 G=94

¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet am  2026-09-30) ¤

*© Formatika GbR, Deutschland






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.