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


Quelle  AccountsGlue.sys.mjs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.sys.mjs",
  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
  BrowserUIUtils: "resource:///modules/BrowserUIUtils.sys.mjs",
  ClientID: "resource://gre/modules/ClientID.sys.mjs",
  CloseRemoteTab: "resource://gre/modules/FxAccountsCommands.sys.mjs",
  FxAccounts: "resource://gre/modules/FxAccounts.sys.mjs",
  UIState: "resource://services-sync/UIState.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "CLIENT_ASSOCIATION_PING_ENABLED",
  "identity.fxaccounts.telemetry.clientAssociationPing.enabled",
  false
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "AlertsService",
  "@mozilla.org/alerts-service;1",
  "nsIAlertsService"
);

ChromeUtils.defineLazyGetter(
  lazy,
  "accountsL10n",
  () => new Localization(["browser/accounts.ftl", "branding/brand.ftl"], true)
);

/**
 * Manages Mozilla Account and Sync related functionality
 * needed at startup. It mainly handles various account-related events and notifications.
 *
 * This module was sliced off of BrowserGlue and designed to centralize
 * account-related events/notifications to prevent crowding BrowserGlue
 */
export const AccountsGlue = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  init() {
    let os = Services.obs;
    [
      "fxaccounts:onverified",
      "fxaccounts:device_connected",
      "fxaccounts:verify_login",
      "fxaccounts:device_disconnected",
      "fxaccounts:commands:open-uri",
      "fxaccounts:commands:close-uri",
      "sync-ui-state:update",
    ].forEach(topic => os.addObserver(this, topic, true));
  },

  observe(subject, topic, data) {
    switch (topic) {
      case "fxaccounts:onverified":
        this._onThisDeviceConnected();
        break;
      case "fxaccounts:device_connected":
        this._onDeviceConnected(data);
        break;
      case "fxaccounts:verify_login":
        this._onVerifyLoginNotification(JSON.parse(data));
        break;
      case "fxaccounts:device_disconnected":
        data = JSON.parse(data);
        if (data.isLocalDevice) {
          this._onDeviceDisconnected();
        }
        break;
      case "fxaccounts:commands:open-uri":
        this._onDisplaySyncURIs(subject);
        break;
      case "fxaccounts:commands:close-uri":
        this._onIncomingCloseTabCommand(subject);
        break;
      case "sync-ui-state:update": {
        this._updateFxaBadges(lazy.BrowserWindowTracker.getTopWindow());

        if (lazy.CLIENT_ASSOCIATION_PING_ENABLED) {
          let fxaState = lazy.UIState.get();
          if (fxaState.status == lazy.UIState.STATUS_SIGNED_IN) {
            Glean.clientAssociation.uid.set(fxaState.uid);
            Glean.clientAssociation.legacyClientId.set(
              lazy.ClientID.getCachedClientID()
            );
          }
        }
        break;
      }
      case "browser-glue-test": // used by tests
        if (data == "mock-alerts-service") {
          // eslint-disable-next-line mozilla/valid-lazy
          Object.defineProperty(lazy, "AlertsService", {
            value: subject.wrappedJSObject,
          });
        }
        break;
    }
  },

  _onThisDeviceConnected() {
    const [title, body] = lazy.accountsL10n.formatValuesSync([
      "account-connection-title-2",
      "account-connection-connected",
    ]);

    let clickCallback = (subject, topic) => {
      if (topic != "alertclickcallback") {
        return;
      }
      this._openPreferences("sync");
    };
    lazy.AlertsService.showAlertNotification(
      null,
      title,
      body,
      true,
      null,
      clickCallback
    );
  },

  _openURLInNewWindow(url) {
    let urlString = Cc["@mozilla.org/supports-string;1"].createInstance(
      Ci.nsISupportsString
    );
    urlString.data = url;
    return new Promise(resolve => {
      let win = Services.ww.openWindow(
        null,
        AppConstants.BROWSER_CHROME_URL,
        "_blank",
        "chrome,all,dialog=no",
        urlString
      );
      win.addEventListener(
        "load",
        () => {
          resolve(win);
        },
        { once: true }
      );
    });
  },

  /**
   * Called as an observer when Sync's "display URIs" notification is fired.
   * We open the received URIs in background tabs.
   *
   * @param {object} data
   *        The data passed to the observer notification, which contains
   *        a wrappedJSObject with the URIs to open.
   */
  async _onDisplaySyncURIs(data) {
    try {
      // The payload is wrapped weirdly because of how Sync does notifications.
      const URIs = data.wrappedJSObject.object;

      // win can be null, but it's ok, we'll assign it later in openTab()
      let win = lazy.BrowserWindowTracker.getTopWindow({ private: false });

      const openTab = async URI => {
        let tab;
        if (!win) {
          win = await this._openURLInNewWindow(URI.uri);
          let tabs = win.gBrowser.tabs;
          tab = tabs[tabs.length - 1];
        } else {
          tab = win.gBrowser.addWebTab(URI.uri);
        }
        tab.attention = true;
        return tab;
      };

      const firstTab = await openTab(URIs[0]);
      await Promise.all(URIs.slice(1).map(URI => openTab(URI)));

      const deviceName = URIs[0].sender && URIs[0].sender.name;
      let titleL10nId, body;
      if (URIs.length == 1) {
        // Due to bug 1305895, tabs from iOS may not have device information, so
        // we have separate strings to handle those cases. (See Also
        // unnamedTabsArrivingNotificationNoDevice.body below)
        titleL10nId = deviceName
          ? {
              id: "account-single-tab-arriving-from-device-title",
              args: { deviceName },
            }
          : { id: "account-single-tab-arriving-title" };
        // Use the page URL as the body. We strip the fragment and query (after
        // the `?` and `#` respectively) to reduce size, and also format it the
        // same way that the url bar would.
        let url = URIs[0].uri.replace(/([?#]).*$/, "$1");
        const wasTruncated = url.length < URIs[0].uri.length;
        url = lazy.BrowserUIUtils.trimURL(url);
        if (wasTruncated) {
          body = await lazy.accountsL10n.formatValue(
            "account-single-tab-arriving-truncated-url",
            { url }
          );
        } else {
          body = url;
        }
      } else {
        titleL10nId = { id: "account-multiple-tabs-arriving-title" };
        const allKnownSender = URIs.every(URI => URI.sender != null);
        const allSameDevice =
          allKnownSender &&
          URIs.every(URI => URI.sender.id == URIs[0].sender.id);
        let bodyL10nId;
        if (allSameDevice) {
          bodyL10nId = deviceName
            ? "account-multiple-tabs-arriving-from-single-device"
            : "account-multiple-tabs-arriving-from-unknown-device";
        } else {
          bodyL10nId = "account-multiple-tabs-arriving-from-multiple-devices";
        }

        body = await lazy.accountsL10n.formatValue(bodyL10nId, {
          deviceName,
          tabCount: URIs.length,
        });
      }
      const title = await lazy.accountsL10n.formatValue(titleL10nId);

      const clickCallback = (obsSubject, obsTopic) => {
        if (obsTopic == "alertclickcallback") {
          win.gBrowser.selectedTab = firstTab;
        }
      };

      // Specify an icon because on Windows no icon is shown at the moment
      let imageURL;
      if (AppConstants.platform == "win") {
        imageURL = "chrome://branding/content/icon64.png";
      }
      lazy.AlertsService.showAlertNotification(
        imageURL,
        title,
        body,
        true,
        null,
        clickCallback
      );
    } catch (ex) {
      console.error("Error displaying tab(s) received by Sync: ", ex);
    }
  },

  async _onIncomingCloseTabCommand(data) {
    // The payload is wrapped weirdly because of how Sync does notifications.
    const wrappedObj = data.wrappedJSObject.object;
    let { urls } = wrappedObj[0];
    let urisToClose = [];
    urls.forEach(urlString => {
      try {
        urisToClose.push(Services.io.newURI(urlString));
      } catch (ex) {
        // The url was invalid so we ignore
        console.error(ex);
      }
    });
    // We want to keep track of the tabs we closed for the notification
    // given that there could be duplicates we also closed
    let totalClosedTabs = 0;
    const windows = lazy.BrowserWindowTracker.orderedWindows;

    async function closeTabsInWindows() {
      for (const win of windows) {
        if (!win.gBrowser) {
          continue;
        }
        try {
          const closedInWindow = await win.gBrowser.closeTabsByURI(urisToClose);
          totalClosedTabs += closedInWindow;
        } catch (ex) {
          this.log.error("Error closing tabs in window:", ex);
        }
      }
    }

    await closeTabsInWindows();

    let clickCallback = async (subject, topic) => {
      if (topic == "alertshow") {
        // Keep track of the fact that we showed the notification to
        // the user at least once
        lazy.CloseRemoteTab.hasPendingCloseTabNotification = true;
      }

      // The notification is either turned off or dismissed by user
      if (topic == "alertfinished") {
        // Reset the notification pending flag
        lazy.CloseRemoteTab.hasPendingCloseTabNotification = false;
      }

      if (topic != "alertclickcallback") {
        return;
      }
      let win =
        lazy.BrowserWindowTracker.getTopWindow({ private: false }) ??
        (await lazy.BrowserWindowTracker.promiseOpenWindow());
      // We don't want to open a new tab, instead use the handler
      // to switch to the existing view
      if (win) {
        win.FirefoxViewHandler.openTab("recentlyclosed");
      }
    };

    let imageURL;
    if (AppConstants.platform == "win") {
      imageURL = "chrome://branding/content/icon64.png";
    }

    // Reset the count only if there are no pending notifications
    if (!lazy.CloseRemoteTab.hasPendingCloseTabNotification) {
      lazy.CloseRemoteTab.closeTabNotificationCount = 0;
    }
    lazy.CloseRemoteTab.closeTabNotificationCount += totalClosedTabs;
    const [title, body] = await lazy.accountsL10n.formatValues([
      {
        id: "account-tabs-closed-remotely",
        args: { closedCount: lazy.CloseRemoteTab.closeTabNotificationCount },
      },
      { id: "account-view-recently-closed-tabs" },
    ]);

    try {
      lazy.AlertsService.showAlertNotification(
        imageURL,
        title,
        body,
        true,
        null,
        clickCallback,
        "closed-tab-notification"
      );
    } catch (ex) {
      console.error("Error notifying user of closed tab(s) ", ex);
    }
  },

  async _onVerifyLoginNotification({ body, title, url }) {
    let tab;
    let imageURL;
    if (AppConstants.platform == "win") {
      imageURL = "chrome://branding/content/icon64.png";
    }
    let win = lazy.BrowserWindowTracker.getTopWindow({ private: false });
    if (!win) {
      win = await this._openURLInNewWindow(url);
      let tabs = win.gBrowser.tabs;
      tab = tabs[tabs.length - 1];
    } else {
      tab = win.gBrowser.addWebTab(url);
    }
    tab.attention = true;
    let clickCallback = (subject, topic) => {
      if (topic != "alertclickcallback") {
        return;
      }
      win.gBrowser.selectedTab = tab;
    };

    try {
      lazy.AlertsService.showAlertNotification(
        imageURL,
        title,
        body,
        true,
        null,
        clickCallback
      );
    } catch (ex) {
      console.error("Error notifying of a verify login event: ", ex);
    }
  },

  _onDeviceConnected(deviceName) {
    const [title, body] = lazy.accountsL10n.formatValuesSync([
      { id: "account-connection-title-2" },
      deviceName
        ? { id: "account-connection-connected-with", args: { deviceName } }
        : { id: "account-connection-connected-with-noname" },
    ]);

    let clickCallback = async (subject, topic) => {
      if (topic != "alertclickcallback") {
        return;
      }
      let url = await lazy.FxAccounts.config.promiseManageDevicesURI(
        "device-connected-notification"
      );
      let win = lazy.BrowserWindowTracker.getTopWindow({ private: false });
      if (!win) {
        this._openURLInNewWindow(url);
      } else {
        win.gBrowser.addWebTab(url);
      }
    };

    try {
      lazy.AlertsService.showAlertNotification(
        null,
        title,
        body,
        true,
        null,
        clickCallback
      );
    } catch (ex) {
      console.error("Error notifying of a new Sync device: ", ex);
    }
  },

  _onDeviceDisconnected() {
    const [title, body] = lazy.accountsL10n.formatValuesSync([
      "account-connection-title-2",
      "account-connection-disconnected",
    ]);

    let clickCallback = (subject, topic) => {
      if (topic != "alertclickcallback") {
        return;
      }
      this._openPreferences("sync");
    };
    lazy.AlertsService.showAlertNotification(
      null,
      title,
      body,
      true,
      null,
      clickCallback
    );
  },

  _updateFxaBadges(win) {
    let fxaButton = win.document.getElementById("fxa-toolbar-menu-button");
    let badge = fxaButton?.querySelector(".toolbarbutton-badge");

    let state = lazy.UIState.get();
    if (
      state.status == lazy.UIState.STATUS_LOGIN_FAILED ||
      state.status == lazy.UIState.STATUS_NOT_VERIFIED
    ) {
      // If the fxa toolbar button is in the toolbox, we display the notification
      // on the fxa button instead of the app menu.
      let navToolbox = win.document.getElementById("navigator-toolbox");
      let isFxAButtonShown = navToolbox.contains(fxaButton);
      if (isFxAButtonShown) {
        state.status == lazy.UIState.STATUS_LOGIN_FAILED
          ? fxaButton?.setAttribute("badge-status", state.status)
          : badge?.classList.add("feature-callout");
      } else {
        lazy.AppMenuNotifications.showBadgeOnlyNotification(
          "fxa-needs-authentication"
        );
      }
    } else {
      fxaButton?.removeAttribute("badge-status");
      badge?.classList.remove("feature-callout");
      lazy.AppMenuNotifications.removeNotification("fxa-needs-authentication");
    }
  },

  // Open preferences even if there are no open windows.
  _openPreferences(...args) {
    let chromeWindow = lazy.BrowserWindowTracker.getTopWindow();
    if (chromeWindow) {
      chromeWindow.openPreferences(...args);
      return;
    }

    if (AppConstants.platform == "macosx") {
      Services.appShell.hiddenDOMWindow.openPreferences(...args);
    }
  },
};

[ Dauer der Verarbeitung: 0.27 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge