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

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


/* globals AdjustableTitle */

// This is the dialog that is displayed when adding or editing a search engine
// in about:preferences, or when adding a search engine via the context menu of
// an HTML form. Depending on the scenario where it is used, different arguments
// must be supplied in an object in `window.arguments[0]`:
// - `mode`  [required] - The type of dialog: NEW, EDIT or FORM.
// - `title` [optional] - Whether to display a title in the window element.
// - all arguments required by the constructor of the dialog class

/**
 * @import {UserSearchEngine} from "../../../../toolkit/components/search/UserSearchEngine.sys.mjs"
 */


const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  SearchService: "moz-src:///toolkit/components/search/SearchService.sys.mjs",
  SearchUtils: "moz-src:///toolkit/components/search/SearchUtils.sys.mjs",
  UserSearchEngine:
    "moz-src:///toolkit/components/search/UserSearchEngine.sys.mjs",
});

// Set the appropriate l10n id before the dialog's connectedCallback.
if (window.arguments[0].mode == "EDIT") {
  document.l10n.setAttributes(
    document.querySelector("dialog"),
    "edit-engine-dialog"
  );
  document.l10n.setAttributes(
    document.querySelector("window"),
    "edit-engine-window"
  );
else {
  document.l10n.setAttributes(
    document.querySelector("dialog"),
    "add-engine-dialog2"
  );
  document.l10n.setAttributes(
    document.querySelector("window"),
    "add-engine-window"
  );
}

let loadedResolvers = Promise.withResolvers();
document.mozSubdialogReady = loadedResolvers.promise;

/** @type {?EngineDialog} */
let gAddEngineDialog = null;
/** @type {?Map<string, string>} */
let l10nCache = null;

/**
 * The abstract base class for all types of user search engine dialogs.
 * All subclasses must implement the abstract method `onAddEngine`.
 */

class EngineDialog {
  constructor() {
    this._dialog = document.querySelector("dialog");

    this._form = document.getElementById("addEngineForm");
    this._name = document.getElementById("engineName");
    this._alias = document.getElementById("engineAlias");
    this._url = document.getElementById("engineUrl");
    this._postData = document.getElementById("enginePostData");
    this._suggestUrl = document.getElementById("suggestUrl");

    this._form.addEventListener("input", e => this.validateInput(e.target));
    document.addEventListener("dialogaccept"this.onAccept.bind(this));
    document.addEventListener("dialogextra1", () => this.showAdvanced());
  }

  /**
   * Shows the advanced section and hides the advanced button.
   *
   * @param {boolean} [resize]
   *   Whether the resizeDialog should be called. Before `mozSubdialogReady`
   *   is resolved, this should be false to avoid flickering.
   */

  showAdvanced(resize = true) {
    this._dialog.getButton("extra1").hidden = true;
    document.getElementById("advanced-section").hidden = false;
    if (resize) {
      window.resizeDialog();
    }
  }

  onAccept() {
    throw new Error("abstract");
  }

  validateName() {
    let name = this._name.value.trim();
    if (!name) {
      this.setValidity(this._name, "add-engine-no-name");
      return;
    }

    let existingEngine = lazy.SearchService.getEngineByName(name);
    if (existingEngine && !this.allowedNames.includes(name)) {
      this.setValidity(this._name, "add-engine-name-exists");
      return;
    }

    this.setValidity(this._name, null);
  }

  async validateAlias() {
    let alias = this._alias.value.trim();
    if (!alias) {
      this.setValidity(this._alias, null);
      return;
    }

    let existingEngine = await lazy.SearchService.getEngineByAlias(alias);
    if (existingEngine && !this.allowedAliases.includes(alias)) {
      this.setValidity(this._alias, "add-engine-keyword-exists");
      return;
    }

    this.setValidity(this._alias, null);
  }

  validateUrlInput() {
    let urlString = this._url.value.trim();
    if (!urlString) {
      this.setValidity(this._url, "add-engine-no-url");
      return;
    }

    let url = URL.parse(urlString);
    if (!url) {
      this.setValidity(this._url, "add-engine-invalid-url");
      return;
    }

    if (url.protocol != "http:" && url.protocol != "https:") {
      this.setValidity(this._url, "add-engine-invalid-protocol");
      return;
    }

    let postData = this._postData?.value.trim();
    if (!urlString.includes("%s") && !postData) {
      this.setValidity(this._url, "add-engine-missing-terms-url");
      return;
    }
    this.setValidity(this._url, null);
  }

  validatePostDataInput() {
    let postData = this._postData.value.trim();
    if (postData && !postData.includes("%s")) {
      this.setValidity(this._postData, "add-engine-missing-terms-post-data");
      return;
    }
    this.setValidity(this._postData, null);
  }

  validateSuggestUrlInput() {
    let urlString = this._suggestUrl.value.trim();
    if (!urlString) {
      this.setValidity(this._suggestUrl, null);
      return;
    }

    let url = URL.parse(urlString);
    if (!url) {
      this.setValidity(this._suggestUrl, "add-engine-invalid-url");
      return;
    }

    if (url.protocol != "http:" && url.protocol != "https:") {
      this.setValidity(this._suggestUrl, "add-engine-invalid-protocol");
      return;
    }

    if (!urlString.includes("%s")) {
      this.setValidity(this._suggestUrl, "add-engine-missing-terms-url");
      return;
    }

    this.setValidity(this._suggestUrl, null);
  }

  /**
   * Validates the passed input element and updates error messages.
   *
   * @param {HTMLInputElement} input
   *   The input element to validate.
   */

  async validateInput(input) {
    switch (input.id) {
      case this._name.id:
        this.validateName();
        break;
      case this._alias.id:
        await this.validateAlias();
        break;
      case this._postData.id:
      case this._url.id:
        // Since either the url or the post data input could
        // contain %s, we need to update both inputs here.
        this.validateUrlInput();
        this.validatePostDataInput();
        break;
      case this._suggestUrl.id:
        this.validateSuggestUrlInput();
        break;
    }
  }

  async validateAll() {
    for (let input of this._form.elements) {
      await this.validateInput(input);
    }
  }

  /**
   * Sets the validity of the passed input element to the string belonging
   * to the passed l10n id. Also updates the input's error label and
   * the accept button.
   *
   * @param {HTMLInputElement} inputElement
   * @param {string} l10nId
   *   The l10n id of the string to use as validity.
   *   Must be a key of `l10nCache`.
   */

  setValidity(inputElement, l10nId) {
    if (l10nId) {
      inputElement.setCustomValidity(l10nCache.get(l10nId));
    } else {
      inputElement.setCustomValidity("");
    }

    let errorLabel = inputElement.parentElement.querySelector(".error-label");
    let validationMessage = inputElement.validationMessage;

    // If valid, set the error label to "valid" to ensure the layout doesn't shift.
    // The CSS already hides the error label based on the validity of `inputElement`.
    errorLabel.textContent = validationMessage || "valid";

    this._dialog.getButton("accept").disabled = !this._form.checkValidity();
  }

  /**
   * Engine names that always are allowed, even if they are already in use.
   * This is needed for the edit engine dialog.
   *
   * @type {string[]}
   */

  get allowedNames() {
    return [];
  }

  /**
   * Engine aliases that always are allowed, even if they are already in use.
   * This is needed for the edit engine dialog.
   *
   * @type {string[]}
   */

  get allowedAliases() {
    return [];
  }
}

/**
 * This dialog is opened when adding a new search engine in preferences.
 */

class NewEngineDialog extends EngineDialog {
  constructor() {
    super();
    document.l10n.setAttributes(this._name, "add-engine-name-placeholder");
    document.l10n.setAttributes(this._url, "add-engine-url-placeholder");
    document.l10n.setAttributes(this._alias, "add-engine-keyword-placeholder");

    this.validateAll();
  }

  onAccept() {
    let params = new URLSearchParams(
      this._postData.value.trim().replace(/%s/, "{searchTerms}")
    );
    let url = this._url.value.trim().replace(/%s/, "{searchTerms}");

    lazy.SearchService.addUserEngine({
      name: this._name.value.trim(),
      url,
      method: params.size ? "POST" : "GET",
      params,
      suggestUrl: this._suggestUrl.value.trim().replace(/%s/, "{searchTerms}"),
      alias: this._alias.value.trim(),
    });
  }
}

/**
 * This dialog is opened when editing a search engine in preferences.
 *
 * For non-user search engines, it will only allow editing the alias. For user
 * engines it will allow editing any supported field.
 */

class EditEngineDialog extends EngineDialog {
  #engine;
  /**
   * Initializes the dialog with information from a user search engine.
   *
   * @param {object} args
   *   The arguments.
   * @param {SearchEngine} args.engine
   *   The search engine to edit.
   */

  constructor({ engine }) {
    super();
    this.#engine = engine;
    this._name.value = engine.name;
    this._alias.value = engine.alias ?? "";

    let [url, postData] = this.getSubmissionTemplate(
      lazy.SearchUtils.URL_TYPE.SEARCH
    );
    this._url.value = url;
    this._postData.value = postData;

    if (!(this.#engine instanceof lazy.UserSearchEngine)) {
      this._name.closest(".dialogRow").hidden = true;
      this._url.closest(".dialogRow").hidden = true;
      this._dialog.getButton("extra1").hidden = true;

      // For the fields we aren't using, pretend they are all valid so that the
      // form validation can pass.
      for (let input of this._form.elements) {
        if (input === this._alias) {
          continue;
        }
        this.setValidity(input, "");
      }

      this.validateAll();
      return;
    }

    let [suggestUrl] = this.getSubmissionTemplate(
      lazy.SearchUtils.URL_TYPE.SUGGEST_JSON
    );
    if (suggestUrl) {
      this._suggestUrl.value = suggestUrl;
    }

    if (postData || suggestUrl) {
      this.showAdvanced(false);
    }

    this.validateAll();
  }

  async validateAll() {
    // For non-user engines, we only allow the alias to be changed, thus we
    // should only validate the alias field.
    if (this.#engine instanceof lazy.UserSearchEngine) {
      super.validateAll();
    } else {
      await this.validateAlias();
    }
  }

  onAccept() {
    if (this.#engine instanceof lazy.UserSearchEngine) {
      this.#engine.rename(this._name.value.trim());
      this.#engine.alias = this._alias.value.trim();

      let newURL = this._url.value.trim();
      let newPostData = this._postData.value.trim() || null;

      // UserSearchEngine.changeUrl() does not check whether the URL has actually changed.
      let [prevURL, prevPostData] = this.getSubmissionTemplate(
        lazy.SearchUtils.URL_TYPE.SEARCH
      );
      if (newURL != prevURL || prevPostData != newPostData) {
        this.#engine.changeUrl(
          lazy.SearchUtils.URL_TYPE.SEARCH,
          newURL.replace(/%s/, "{searchTerms}"),
          newPostData?.replace(/%s/, "{searchTerms}")
        );
      }

      let newSuggestURL = this._suggestUrl.value.trim() || null;
      let [prevSuggestUrl] = this.getSubmissionTemplate(
        lazy.SearchUtils.URL_TYPE.SUGGEST_JSON
      );
      if (newSuggestURL != prevSuggestUrl) {
        this.#engine.changeUrl(
          lazy.SearchUtils.URL_TYPE.SUGGEST_JSON,
          newSuggestURL?.replace(/%s/, "{searchTerms}"),
          null
        );
      }

      this.#engine.updateFavicon();
    } else {
      let newAlias = this._alias.value;
      this.#engine.alias = newAlias.trim().toLowerCase();
    }
  }

  get allowedAliases() {
    return [this.#engine.alias];
  }

  get allowedNames() {
    return [this.#engine.name];
  }

  /**
   * Returns url and post data templates of the requested type.
   * Both contain %s in place of the search terms.
   *
   * If no url of the requested type exists, both are null.
   * If the url is a GET url, the post data is null.
   *
   * @param {string} urlType
   *   The `SearchUtils.URL_TYPE`.
   * @returns {[?string, ?string]}
   *   Array of the url and post data.
   */

  getSubmissionTemplate(urlType) {
    let submission = this.#engine.getSubmission("searchTerms", urlType);
    if (!submission) {
      return [nullnull];
    }
    let postData = null;
    if (submission.postData) {
      let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
        Ci.nsIBinaryInputStream
      );
      binaryStream.setInputStream(submission.postData.data);

      postData = binaryStream
        .readBytes(binaryStream.available())
        .replace("searchTerms""%s");
    }
    let url = submission.uri.spec.replace("searchTerms""%s");
    return [url, postData];
  }
}

/**
 * This dialog is opened via the context menu of an input and lets the
 * user choose a name and an alias for an engine. Unlike the other two
 * dialogs, it does not add or change an engine in the search service,
 * and instead returns the user input to the caller.
 *
 * The chosen name and alias are returned via `window.arguments[0].engineInfo`.
 * If the user chooses to not save the engine, it's undefined.
 */

class NewEngineFromFormDialog extends EngineDialog {
  /**
   * Initializes the dialog.
   *
   * @param {object} args
   *   The arguments.
   * @param {string} args.nameTemplate
   *   The initial value of the name input.
   */

  constructor({ nameTemplate }) {
    super();
    document.getElementById("engineUrlRow").remove();
    this._url = null;
    document.getElementById("suggestUrlRow").remove();
    this._suggestUrl = null;
    document.getElementById("enginePostDataRow").remove();
    this._postData = null;
    this._dialog.getButton("extra1").hidden = true;

    this._name.value = nameTemplate;
    this.validateAll();
  }

  onAccept() {
    // Return the input to the caller.
    window.arguments[0].engineInfo = {
      name: this._name.value.trim(),
      // Empty string means no alias.
      alias: this._alias.value.trim(),
    };
  }
}

async function initL10nCache() {
  const errorIds = [
    "add-engine-name-exists",
    "add-engine-keyword-exists",
    "add-engine-no-name",
    "add-engine-no-url",
    "add-engine-invalid-protocol",
    "add-engine-invalid-url",
    "add-engine-missing-terms-url",
    "add-engine-missing-terms-post-data",
  ];

  let msgs = await document.l10n.formatValues(errorIds.map(id => ({ id })));
  l10nCache = new Map();

  for (let i = 0; i < errorIds.length; i++) {
    l10nCache.set(errorIds[i], msgs[i]);
  }
}

window.addEventListener("DOMContentLoaded", async () => {
  try {
    if (window.arguments[0].title) {
      document.documentElement.setAttribute(
        "headertitle",
        JSON.stringify({ raw: document.title })
      );
    } else {
      AdjustableTitle.hide();
    }

    await initL10nCache();

    switch (window.arguments[0].mode) {
      case "NEW":
        gAddEngineDialog = new NewEngineDialog();
        break;
      case "EDIT":
        gAddEngineDialog = new EditEngineDialog(window.arguments[0]);
        break;
      case "FORM":
        gAddEngineDialog = new NewEngineFromFormDialog(window.arguments[0]);
        break;
      default:
        throw new Error("Mode not supported for addEngine dialog.");
    }
  } finally {
    loadedResolvers.resolve();
  }
});

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

¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet am  2026-08-26) ¤

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