Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/shared/commands/inspector/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 16 kB image not shown  

Quelle  inspector-command.js   Sprache: JAVA

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


"use strict";

loader.lazyRequireGetter(
  this,
  "getTargetBrowsers",
  "resource://devtools/shared/compatibility/compatibility-user-settings.js",
  true
);
loader.lazyRequireGetter(
  this,
  "TARGET_BROWSER_PREF",
  "resource://devtools/shared/compatibility/constants.js",
  true
);

class InspectorCommand {
  constructor({ commands }) {
    this.commands = commands;
  }

  #cssDeclarationBlockIssuesQueuedDomRulesDeclarations = [];
  #cssDeclarationBlockIssuesPendingTimeoutPromise;
  #cssDeclarationBlockIssuesTargetBrowsersPromise;

  /**
   * Return the list of all current target's inspector fronts
   *
   * @return {Promise<Array<InspectorFront>>}
   */

  async getAllInspectorFronts() {
    return this.commands.targetCommand.getAllFronts(
      [this.commands.targetCommand.TYPES.FRAME],
      "inspector"
    );
  }

  /**
   * Search the document for the given string and return all the results.
   *
   * @param {Object} walkerFront
   * @param {String} query
   *        The string to search for.
   * @param {Object} options
   *        {Boolean} options.reverse - search backwards
   * @returns {Array} The list of search results
   */

  async walkerSearch(walkerFront, query, options = {}) {
    const result = await walkerFront.search(query, options);
    return result.list.items();
  }

  /**
   * Incrementally search the top-level document and sub frames for a given string.
   * Only one result is sent back at a time. Calling the
   * method again with the same query will send the next result.
   * If a new query which does not match the current one all is reset and new search
   * is kicked off.
   *
   * @param {String} query
   *         The string / selector searched for
   * @param {Object} options
   *        {Boolean} reverse - determines if the search is done backwards
   * @returns {Object} res
   *          {String} res.type
   *          {String} res.query - The string / selector searched for
   *          {Object} res.node - the current node
   *          {Number} res.resultsIndex - The index of the current node
   *          {Number} res.resultsLength - The total number of results found.
   */

  async findNextNode(query, { reverse } = {}) {
    const inspectors = await this.getAllInspectorFronts();
    const nodes = await Promise.all(
      inspectors.map(({ walker }) =>
        this.walkerSearch(walker, query, { reverse })
      )
    );
    const results = nodes.flat();

    // If the search query changes
    if (this._searchQuery !== query) {
      this._searchQuery = query;
      this._currentIndex = -1;
    }

    if (!results.length) {
      return null;
    }

    this._currentIndex = reverse
      ? this._currentIndex - 1
      : this._currentIndex + 1;

    if (this._currentIndex >= results.length) {
      this._currentIndex = 0;
    }
    if (this._currentIndex < 0) {
      this._currentIndex = results.length - 1;
    }

    return {
      node: results[this._currentIndex],
      resultsIndex: this._currentIndex,
      resultsLength: results.length,
    };
  }

  /**
   * Returns a list of matching results for CSS selector autocompletion.
   *
   * @param {String} query
   *        The selector query being completed
   * @param {String} firstPart
   *        The exact token being completed out of the query
   * @param {String} state
   *        One of "pseudo", "id", "tag", "class", "null"
   * @return {Array<string>} suggestions
   *        The list of suggested CSS selectors
   */

  async getSuggestionsForQuery(query, firstPart, state) {
    // Get all inspectors where we want suggestions from.
    const inspectors = await this.getAllInspectorFronts();

    const mergedSuggestions = [];
    // Get all of the suggestions.
    await Promise.all(
      inspectors.map(async ({ walker }) => {
        const { suggestions } = await walker.getSuggestionsForQuery(
          query,
          firstPart,
          state
        );
        for (const [suggestion, count, type] of suggestions) {
          // Merge any already existing suggestion with the new one, by incrementing the count
          // which is the second element of the array.
          const existing = mergedSuggestions.find(
            ([s, , t]) => s == suggestion && t == type
          );
          if (existing) {
            existing[1] += count;
          } else {
            mergedSuggestions.push([suggestion, count, type]);
          }
        }
      })
    );

    // Descending sort the list by count, i.e. second element of the arrays
    return sortSuggestions(mergedSuggestions);
  }

  /**
   * Find a nodeFront from an array of selectors. The last item of the array is the selector
   * for the element in its owner document, and the previous items are selectors to iframes
   * that lead to the frame where the searched node lives in.
   *
   * For example, with the following markup
   * <html>
   *  <iframe id="level-1" src="…">
   *    <iframe id="level-2" src="…">
   *      <h1>Waldo</h1>
   *    </iframe>
   *  </iframe>
   *
   * If you want to retrieve the `<h1>` nodeFront, `selectors` would be:
   * [
   *   "#level-1",
   *   "#level-2",
   *   "h1",
   * ]
   *
   * @param {Array} selectors
   *        An array of CSS selectors to find the target accessible object.
   *        Several selectors can be needed if the element is nested in frames
   *        and not directly in the root document.
   * @param {Integer} timeoutInMs
   *        The maximum number of ms the function should run (defaults to 5000).
   *        If it exceeds this, the returned promise will resolve with `null`.
   * @return {Promise<NodeFront|null>} a promise that resolves when the node front is found
   *        for selection using inspector tools. It resolves with the deepest frame document
   *        that could be retrieved when the "final" nodeFront couldn't be found in the page.
   *        It resolves with `null` when the function runs for more than timeoutInMs.
   */

  async findNodeFrontFromSelectors(nodeSelectors, timeoutInMs = 5000) {
    if (
      !nodeSelectors ||
      !Array.isArray(nodeSelectors) ||
      nodeSelectors.length === 0
    ) {
      console.warn(
        "findNodeFrontFromSelectors expect a non-empty array but got",
        nodeSelectors
      );
      return null;
    }

    const { walker } =
      await this.commands.targetCommand.targetFront.getFront("inspector");
    // Copy the array as we will mutate it
    nodeSelectors = [...nodeSelectors];
    const querySelectors = async nodeFront => {
      const selector = nodeSelectors.shift();
      if (!selector) {
        return nodeFront;
      }
      nodeFront = await nodeFront.walkerFront.querySelector(
        nodeFront,
        selector
      );
      // It's possible the containing iframe isn't available by the time
      // walkerFront.querySelector is called, which causes the re-selected node to be
      // unavailable. There also isn't a way for us to know when all iframes on the page
      // have been created after a reload. Because of this, we should should bail here.
      if (!nodeFront) {
        return null;
      }

      if (nodeSelectors.length) {
        if (!nodeFront.isShadowHost) {
          await this.#waitForFrameLoad(nodeFront);
        }

        const { nodes } = await walker.children(nodeFront);

        // If there are remaining selectors to process, they will target a document or a
        // document-fragment under the current node. Whether the element is a frame or
        // a web component, it can only contain one document/document-fragment, so just
        // select the first one available.
        nodeFront = nodes.find(node => {
          const { nodeType } = node;
          return (
            nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
            nodeType === Node.DOCUMENT_NODE
          );
        });

        // The iframe selector might have matched an element which is not an
        // iframe in the new page (or an iframe with no document?). In this
        // case, bail out and fallback to the root body element.
        if (!nodeFront) {
          return null;
        }
      }
      const childrenNodeFront = await querySelectors(nodeFront);
      return childrenNodeFront || nodeFront;
    };
    const rootNodeFront = await walker.getRootNode();

    // Since this is only used for re-setting a selection after a page reloads, we can
    // put a timeout, in case there's an iframe that would take too much time to load,
    // and prevent the markup view to be populated.
    const onTimeout = new Promise(res => setTimeout(res, timeoutInMs)).then(
      () => null
    );
    const onQuerySelectors = querySelectors(rootNodeFront);
    return Promise.race([onTimeout, onQuerySelectors]);
  }

  /**
   * Wait for the given NodeFront child document to be loaded.
   *
   * @param {NodeFront} A nodeFront representing a frame
   */

  async #waitForFrameLoad(nodeFront) {
    const domLoadingPromises = [];

    // if the flag isn't true, we don't know for sure if the iframe will be remote
    // or not; when the nodeFront was created, the iframe might still have been loading
    // and in such case, its associated window can be an initial document.
    // Luckily, once EFT is enabled everywhere we can remove this call and only wait
    // for the associated target.
    if (!nodeFront.useChildTargetToFetchChildren) {
      domLoadingPromises.push(nodeFront.waitForFrameLoad());
    }

    const { onResource: onDomInteractiveResource } =
      await this.commands.resourceCommand.waitForNextResource(
        this.commands.resourceCommand.TYPES.DOCUMENT_EVENT,
        {
          // We might be in a case where the children document is already loaded (i.e. we
          // would already have received the dom-interactive resource), so it's important
          // to _not_ ignore existing resource.
          predicate: resource =>
            resource.name == "dom-interactive" &&
            resource.targetFront !== nodeFront.targetFront &&
            resource.targetFront.browsingContextID ==
              nodeFront.browsingContextID,
        }
      );
    const newTargetResolveValue = Symbol();
    domLoadingPromises.push(
      onDomInteractiveResource.then(() => newTargetResolveValue)
    );

    // Here we wait for any promise to resolve first. `waitForFrameLoad` might throw
    // (if the iframe does end up being remote), so we don't want to use `Promise.race`.
    const loadResult = await Promise.any(domLoadingPromises);

    // The Node may have `useChildTargetToFetchChildren` set to false because the
    // child document was still loading when fetching its form. But it may happen that
    // the Node ends up being a remote iframe.
    // When this happen we will try to call `waitForFrameLoad` which will throw, but
    // we will be notified about the new target.
    // This is the special edge case we are trying to handle here.
    // We want WalkerFront.children to consider this as an iframe with a dedicated target.
    if (loadResult == newTargetResolveValue) {
      nodeFront._form.useChildTargetToFetchChildren = true;
    }
  }

  /**
   * Get the full array of selectors from the topmost document, going through
   * iframes.
   * For example, given the following markup:
   *
   * <html>
   *   <body>
   *     <iframe src="...">
   *       <html>
   *         <body>
   *           <h1 id="sub-document-title">Title of sub document</h1>
   *         </body>
   *       </html>
   *     </iframe>
   *   </body>
   * </html>
   *
   * If this function is called with the NodeFront for the h1#sub-document-title element,
   * it will return something like: ["body > iframe", "#sub-document-title"]
   *
   * @param {NodeFront} nodeFront: The nodefront to get the selectors for
   * @returns {Promise<Array<String>>} A promise that resolves with an array of selectors (strings)
   */

  async getNodeFrontSelectorsFromTopDocument(nodeFront) {
    const selectors = [];

    let currentNode = nodeFront;
    while (currentNode) {
      // Get the selector for the node inside its document
      const selector = await currentNode.getUniqueSelector();
      selectors.unshift(selector);

      // Retrieve the node's document/shadowRoot nodeFront so we can get its parent
      // (so if we're in an iframe, we'll get the <iframe> node front, and if we're in a
      // shadow dom document, we'll get the host).
      const rootNode = currentNode.getOwnerRootNodeFront();
      currentNode = rootNode?.parentOrHost();
    }

    return selectors;
  }

  #updateTargetBrowsersCache = async () => {
    this.#cssDeclarationBlockIssuesTargetBrowsersPromise = getTargetBrowsers();
  };

  /**
   *  Get compatibility issues for given domRule declarations
   *
   * @param {Array<Object>} domRuleDeclarations
   * @param {string} domRuleDeclarations[].name: Declaration name
   * @param {string} domRuleDeclarations[].value: Declaration value
   * @returns {Promise<Array<Object>>}
   */

  async getCSSDeclarationBlockIssues(domRuleDeclarations) {
    // Filter out custom property declarations as we can't have issue with those and
    // they're already ignored on the server.
    const nonCustomPropertyDeclarations = domRuleDeclarations.filter(
      decl => !decl.isCustomProperty
    );
    const resultIndex =
      this.#cssDeclarationBlockIssuesQueuedDomRulesDeclarations.length;
    this.#cssDeclarationBlockIssuesQueuedDomRulesDeclarations.push(
      nonCustomPropertyDeclarations
    );

    // We're getting the target browsers from RemoteSettings, which can take some time.
    // We cache the target browsers to avoid bad performance.
    if (!this.#cssDeclarationBlockIssuesTargetBrowsersPromise) {
      this.#updateTargetBrowsersCache();
      // Update the target browsers cache when the pref in which we store the compat
      // panel settings is updated.
      Services.prefs.addObserver(
        TARGET_BROWSER_PREF,
        this.#updateTargetBrowsersCache
      );
    }

    // This can be a hot path if the rules view has a lot of rules displayed.
    // Here we wait before sending the RDP request so we can collect all the domRule declarations
    // of "concurrent" calls, and only send a single RDP request.
    if (!this.#cssDeclarationBlockIssuesPendingTimeoutPromise) {
      // Wait before sending the RDP request so all "concurrent" calls can be handle
      // in a single RDP request.
      this.#cssDeclarationBlockIssuesPendingTimeoutPromise = new Promise(
        resolve => {
          setTimeout(() => {
            this.#cssDeclarationBlockIssuesPendingTimeoutPromise = null;
            this.#batchedGetCSSDeclarationBlockIssues().then(data =>
              resolve(data)
            );
          }, 50);
        }
      );
    }

    const results = await this.#cssDeclarationBlockIssuesPendingTimeoutPromise;
    return results?.[resultIndex] || [];
  }

  /**
   * Get compatibility issues for all queued domRules declarations
   * @returns {Promise<Array<Array<Object>>>}
   */

  #batchedGetCSSDeclarationBlockIssues = async () => {
    const declarations =
      this.#cssDeclarationBlockIssuesQueuedDomRulesDeclarations;
    this.#cssDeclarationBlockIssuesQueuedDomRulesDeclarations = [];

    const { targetFront } = this.commands.targetCommand;
    try {
      // The server method isn't dependent on the target (it computes the values from the
      // declarations we send, which are just property names and values), so we can always
      // use the top-level target front.
      const inspectorFront = await targetFront.getFront("inspector");

      const [compatibilityFront, targetBrowsers] = await Promise.all([
        inspectorFront.getCompatibilityFront(),
        this.#cssDeclarationBlockIssuesTargetBrowsersPromise,
      ]);

      const data = await compatibilityFront.getCSSDeclarationBlockIssues(
        declarations,
        targetBrowsers
      );
      return data;
    } catch (e) {
      if (this.destroyed || targetFront.isDestroyed()) {
        return [];
      }
      throw e;
    }
  };

  destroy() {
    Services.prefs.removeObserver(
      TARGET_BROWSER_PREF,
      this.#updateTargetBrowsersCache
    );
    this.destroyed = true;
  }
}

// This is a fork of the server sort:
// https://searchfox.org/mozilla-central/rev/46a67b8656ac12b5c180e47bc4055f713d73983b/devtools/server/actors/inspector/walker.js#1447
function sortSuggestions(suggestions) {
  const sorted = suggestions.sort((a, b) => {
    // Computed a sortable string with first the inverted count, then the name
    let sortA = 10000 - a[1] + a[0];
    let sortB = 10000 - b[1] + b[0];

    // Prefixing ids, classes and tags, to group results
    const firstA = a[0].substring(0, 1);
    const firstB = b[0].substring(0, 1);

    const getSortKeyPrefix = firstLetter => {
      if (firstLetter === "#") {
        return "2";
      }
      if (firstLetter === ".") {
        return "1";
      }
      return "0";
    };

    sortA = getSortKeyPrefix(firstA) + sortA;
    sortB = getSortKeyPrefix(firstB) + sortB;

    // String compare
    return sortA.localeCompare(sortB);
  });
  return sorted.slice(0, 25);
}

module.exports = InspectorCommand;

95%


¤ Dauer der Verarbeitung: 0.6 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

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 ist noch experimentell.