Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/browser/components/sidebar/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 8 kB image not shown  

Quelle  sidebar-page.mjs   Sprache: unbekannt

 
Spracherkennung für: .mjs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

/* 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/. */

const lazy = {};

import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
import { html } from "chrome://global/content/vendor/lit.all.mjs";
// eslint-disable-next-line mozilla/reject-import-system-module-from-non-system
import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://browser/content/sidebar/sidebar-panel-header.mjs";

ChromeUtils.defineESModuleGetters(lazy, {
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
  PlacesUIUtils: "moz-src:///browser/components/places/PlacesUIUtils.sys.mjs",
});

export class SidebarPage extends MozLitElement {
  constructor() {
    super();
    this.clearDocument = this.clearDocument.bind(this);
  }

  connectedCallback() {
    super.connectedCallback();
    this.documentGlobal.addEventListener("beforeunload", this.clearDocument);
    this.documentGlobal.addEventListener("unload", this.clearDocument);

    this._contextMenu = this.topWindow.SidebarController.currentContextMenu;
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.documentGlobal.removeEventListener("beforeunload", this.clearDocument);
    this.documentGlobal.removeEventListener("unload", this.clearDocument);
  }

  get topWindow() {
    return this.documentGlobal.top;
  }

  get sidebarController() {
    return this.topWindow.SidebarController;
  }

  /**
   * Routes keydown events to the tree view controller.
   */
  get keydownHandler() {
    if (!this._keydownHandler) {
      this._keydownHandler = e => {
        this.treeView?.handleKeydown(e);
        if (e.defaultPrevented) {
          e.stopPropagation();
        }
      };
    }
    return this._keydownHandler;
  }

  /**
   * Visually ordered nodes for keyboard navigation. Walks the page's shadow
   * DOM, emitting moz-card summaries and tab-list rows.
   *
   * @returns {TreeViewNode[]}
   */
  getNodesInOrder() {
    const nodes = [];
    this.#collectNodes(this.renderRoot, nodes);
    return nodes;
  }

  #collectNodes({ children }, nodes) {
    for (const el of children) {
      const isCard = el.localName === "moz-card" && el.type === "accordion";
      const isTabList = !!el.tabItems;

      if (isCard) {
        nodes.push({
          card: el,
          type: "card-summary",
          get domNode() {
            return el.summaryEl;
          },
        });
        if (el.expanded) {
          this.#collectNodes(el, nodes);
        }
      } else if (isTabList) {
        for (const item of el.tabItems) {
          nodes.push({
            list: el,
            item,
            type: "row",
            get domNode() {
              return el.shadowRoot.querySelector(
                `[data-guid="${CSS.escape(item.guid)}"]`
              );
            },
          });
        }
      } else {
        this.#collectNodes(el, nodes);
      }
    }
  }

  /**
   * Toggle the expanded state of a node, if it's expandable. Returns true if
   * the call had an effect (the node is expandable AND its state changed),
   * false otherwise.
   *
   * @param {TreeViewNode} node
   * @param {boolean} expanded
   * @returns {boolean}
   */
  setExpanded(node, expanded) {
    if (node.type === "card-summary" && node.card?.expanded !== expanded) {
      node.card.expanded = expanded;
      return true;
    }
    return false;
  }

  addContextMenuListeners() {
    this.addEventListener("contextmenu", this);
    this._contextMenu.addEventListener("command", this);
    this._contextMenu.addEventListener(
      "popupshowing",
      this.placesContextShowing
    );
    this._contextMenu.addEventListener("popuphiding", this.placesContextHiding);
  }

  removeContextMenuListeners() {
    this.removeEventListener("contextmenu", this);
    this._contextMenu.removeEventListener("command", this);
    this._contextMenu.removeEventListener(
      "popupshowing",
      this.placesContextShowing
    );
    this._contextMenu.removeEventListener(
      "popuphiding",
      this.placesContextHiding
    );
  }

  addSidebarFocusedListeners() {
    this.topWindow.addEventListener("SidebarFocused", this);
  }

  removeSidebarFocusedListeners() {
    this.topWindow.removeEventListener("SidebarFocused", this);
  }

  handleEvent(e) {
    switch (e.type) {
      case "contextmenu":
        this.handleContextMenuEvent?.(e);
        break;
      case "command":
        this.handleCommandEvent?.(e);
        break;
      case "SidebarFocused":
        this.handleSidebarFocusedEvent?.(e);
        break;
    }
  }

  placesContextShowing(e) {
    lazy.PlacesUIUtils.placesContextShowing(e);
  }

  placesContextHiding(e) {
    lazy.PlacesUIUtils.placesContextHiding(e);
  }

  /**
   * Check if this event comes from an element of the specified type. If it
   * does, return that element.
   *
   * @param {Event} e
   *   The event to check.
   * @param {string} localName
   *   The name of the element to match.
   * @returns {Element | null}
   *   The matching element, or `null` if no match is found.
   */
  findTriggerNode(e, localName) {
    let elements = [
      e.explicitOriginalTarget,
      e.originalTarget.flattenedTreeParentNode,
      // Event might be in shadow DOM, check the host element.
      e.explicitOriginalTarget.flattenedTreeParentNode.getRootNode().host,
      e.originalTarget.flattenedTreeParentNode.getRootNode().host,
    ];
    for (let el of elements) {
      if (el?.localName == localName) {
        return el;
      }
    }
    return null;
  }

  /**
   * Handle a command if it is a common one that is used in multiple pages.
   * Commands specific to a page should be handled in a subclass.
   *
   * @param {Event} e
   *   The event to handle.
   */
  handleCommandEvent(e) {
    let promise;
    switch (e.target.id) {
      case "sidebar-history-context-open-in-tab":
        this.topWindow.openTrustedLinkIn(this.triggerNode.url, "tab", {
          inBackground: Services.prefs.getBoolPref(
            "browser.tabs.loadInBackground"
          ),
        });
        break;
      case "sidebar-synced-tabs-context-open-in-tab":
        this.topWindow.openTrustedLinkIn(this.triggerNode.url, "tab");
        break;
      case "sidebar-history-context-forget-site":
        this.forgetAboutThisSite().catch(console.error);
        break;
      case "sidebar-history-context-open-in-window":
      case "sidebar-synced-tabs-context-open-in-window":
        this.topWindow.openTrustedLinkIn(this.triggerNode.url, "window", {
          private: false,
        });
        break;
      case "sidebar-history-context-open-in-private-window":
      case "sidebar-synced-tabs-context-open-in-private-window":
        this.topWindow.openTrustedLinkIn(this.triggerNode.url, "window", {
          private: true,
        });
        break;
      case "sidebar-history-context-copy-link":
      case "sidebar-synced-tabs-context-copy-link":
        lazy.BrowserUtils.copyLink(
          this.triggerNode.url,
          this.triggerNode.title
        );
        break;
      case "sidebar-synced-tabs-context-bookmark-tab":
      case "sidebar-history-context-bookmark-page":
        promise = this.topWindow.PlacesCommandHook.bookmarkLink(
          this.triggerNode.url,
          this.triggerNode.title
        );
        break;
    }
    return promise;
  }

  /**
   * Show the "Clear data for site" dialog.
   *
   * @returns {"accept" | "cancel"}
   *   The dialog's closing button.
   */
  async forgetAboutThisSite() {
    let host;
    if (PlacesUtils.nodeIsHost(this.triggerNode)) {
      host = this.triggerNode.query.domain;
    } else {
      host = Services.io.newURI(this.triggerNode.url).host;
    }
    let baseDomain;
    try {
      baseDomain = Services.eTLD.getBaseDomainFromHost(host);
    } catch (e) {
      // If there is no baseDomain we fall back to host
    }
    let deferred = Promise.withResolvers();
    await this.topWindow.gDialogBox.open(
      "chrome://browser/content/places/clearDataForSite.xhtml",
      {
        host,
        hostOrBaseDomain: baseDomain ?? host,
        onAccept: () => deferred.resolve("accept"),
        onCancel: () => deferred.resolve("cancel"),
      }
    );
    return deferred.promise;
  }

  /**
   * Clear out the document so the disconnectedCallback() will trigger properly
   * and all of the custom elements can cleanup.
   */
  clearDocument() {
    this.documentGlobal.document.body.textContent = "";
  }

  /**
   * The common stylesheet for all sidebar pages.
   *
   * @returns {TemplateResult}
   */
  stylesheet() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://browser/content/sidebar/sidebar.css"
      />
    `;
  }
}

[Dauer der Verarbeitung: 0.69 Sekunden]