Объявление

В связи с наплывом спама и ботов на форуме, регистрация новых пользователей будет приостановлена. О восстановлении регистрации будет сообщено дополнительно

Administrator

№1732606-04-2025 00:51:22

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 507
UA: Firefox 137.0

Re: Custom Buttons

Dumby еще вопрос. Может к этой теме не относится, но и  к UСF то же. Есть такой проект от одного из разработчиков- aminomancer замена панели unified-extensions-button - extensionOptionsPanel.uc.js

скрытый текст

Выделить код

Код:

// ==UserScript==
// @name           Extension Options Panel
// @version        1.9.2
// @author         aminomancer
// @homepageURL    https://github.com/aminomancer/uc.css.js
// @description    This script creates a toolbar button that opens a popup panel where extensions can be configured, disabled, uninstalled, etc. Each extension gets its own button in the panel. Clicking an extension's button leads to a subview where you can jump to the extension's options, disable or enable the extension, uninstall it, configure automatic updates, disable/enable it in private browsing, view its source code in whatever program is associated with `.xpi` files, open the extension's homepage, or copy the extension's ID. The panel can also be opened from the App Menu, using the built-in "Add-ons and themes" button. Since v1.8, themes will also be listed in the panel. Hovering a theme will show a tooltip with a preview/screenshot of the theme, and clicking the theme will toggle it on or off. There are several translation and configuration options directly below.
// @downloadURL    https://cdn.jsdelivr.net/gh/aminomancer/uc.css.js@master/JS/extensionOptionsPanel.uc.js
// @updateURL      https://cdn.jsdelivr.net/gh/aminomancer/uc.css.js@master/JS/extensionOptionsPanel.uc.js
// @license        This Source Code Form is subject to the terms of the Creative Commons Attribution-NonCommercial-ShareAlike International License, v. 4.0. If a copy of the CC BY-NC-SA 4.0 was not distributed with this file, You can obtain one at http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
// ==/UserScript==

var openAddonsMgr = window.BrowserOpenAddonsMgr || window.BrowserAddonUI.openAddonsMgr; // Firefox
            
             
class ExtensionOptionsWidget {
  // user configuration. some of these are prefs that can be changed in
  // about:config or user.js. as for the others, you can directly edit this file
  // and change the value to the right of the colon.
  
  static config = {
    // this script replaces the "Add-ons & Themes" button in the app menu with
    // an "Extensions" button that opens our new panel instead of opening
    // about:addons. set to false if you want to leave this button alone
    "Replace addons button": Services.prefs.getBoolPref(
      "extensionOptionsPanel.replaceAddonsButton",
      true
    ),

    // set to false if you don't want the "Add-on options" title to be displayed
    // at the top of the panel
    "Show header": Services.prefs.getBoolPref(
      "extensionOptionsPanel.showHeader",
      true
    ),

    // show the addon version next to its name in the list
    "Show version": Services.prefs.getBoolPref(
      "extensionOptionsPanel.showVersion",
      true
    ),

    // about:addons shows you when an addon has a warning or error, e.g. it's
    // unsigned or blocked. if this is set to true, we'll show the same
    // information in the panel
    "Show addon messages": Services.prefs.getBoolPref(
      "extensionOptionsPanel.showAddonMessages",
      true
    ),

    // when hovering a theme in the panel, a preview/screenshot of the theme
    // will be displayed in a tooltip, if possible. this depends on the add-on
    // author.
    "Show theme preview tooltips": Services.prefs.getBoolPref(
      "extensionOptionsPanel.showThemePreviewTooltips",
      true
    ),

    // show system extensions?
    "Show hidden extensions": Services.prefs.getBoolPref(
      "extensionOptionsPanel.showHiddenExtensions",
      false
    ),

    // show extensions that you've disabled?
    "Show disabled extensions": Services.prefs.getBoolPref(
      "extensionOptionsPanel.showDisabledExtensions",
      true
    ),

    // show enabled extensions at the top of the list and disabled extensions at
    // the bottom?
    "Show enabled extensions first": Services.prefs.getBoolPref(
      "extensionOptionsPanel.showEnabledExtensionsFirst",
      true
    ),

    // put addon IDs in this list, separated by commas, to exclude them from the
    // list, e.g. ["screenshots@mozilla.org", "dark-theme@mozilla.org"]
    "Addon ID blacklist": JSON.parse(
      Services.prefs.getCharPref("extensionOptionsPanel.addonIDBlacklist", "[]")
    ),

    // if you want to change the button's icon for some reason, you can replace
    // this string with any URL or data URL that leads to an image.
    "Icon URL": `chrome://mozapps/skin/extensions/extension.svg`,

    // localization strings
    l10n: {
      // what should the button's label be when it's in the overflow panel or
      // customization palette?
      "Button label": "Add-ons and themes",

      // what should the button's tooltip be? I use sentence case since that's
      // the convention.
      "Button tooltip": "Расширения и темы",

      // title shown at the top of the panel (when "Show header" is true)
      "Panel title": "Расширения и темы",

      // label for the button that appears when you have no addons installed.
      "Download addons label": "Установить",

      // label for the about:addons button at the bottom of the panel
      "Addons page label": "Управление дополнениями",

      // labels for the addon subview buttons
      "Addon options label": "Настройки",
      "Manage addon label": "Управление",
      "Enable addon label": "Включить",
      "Disable addon label": "Отключить",
      "Uninstall addon label": "Удалить",
      "View source label": "Файл установки",
      "Manage shortcuts label": "Горячие клавиши",
      "Open homepage label": "Открыть дом. страницу",
      "Copy ID label": "Копировать ID",
      "Copy Name": "Копировать имя",
      "Copy Version": "Копировать версию",
      "Copy NameVersion": "Копировать имя и версию",  
      "Copy Inspector": "Инспектор",
      "Automatic updates label": "Автообновления:",
      "On Pin To Toolbar": "Закрепить на панели инструментов",
      

      // labels for the automatic update radio buttons
      autoUpdate: {
        "Default label": "По умолч.",
        "On label": "Вкл",
        "Off label": "Выкл",
      },

      "Run in private windows label": "В приватных окнах:",

      // labels for the run in private windows radio buttons
      runInPrivate: {
        "Allow label": "Разрешить",

        "Don't allow label": "Не разрешать",
      },

      // labels for addon buttons that have a warning or error, e.g. addon
      // automatically disabled because it's on a blocklist or unsigned
      addonMessages: {
        Blocked: "Blocked",

        "Signature required": "Signature required",

        Incompatible: "Incompatible",

        Unverified: "Unverified",

        Insecure: "Insecure",
      },
    },
  };

  /**
   * create a DOM node with given parameters
   * @param {object} aDoc (which doc to create the element in)
   * @param {string} tag (an HTML tag name, like "button" or "p")
   * @param {object} props (an object containing attribute name/value pairs,
   *                       e.g. class: ".bookmark-item")
   * @param {boolean} isHTML (if true, create an HTML element. if omitted or
   *                         false, create a XUL element. generally avoid HTML
   *                         when modding the UI, most UI elements are actually
   *                         XUL elements.)
   * @returns the created DOM node
   */
  create(aDoc, tag, props, isHTML = false) {
    let el = isHTML ? aDoc.createElement(tag) : aDoc.createXULElement(tag);
    for (let prop in props) el.setAttribute(prop, props[prop]);
    return el;
  }

  /**
   * set or remove multiple attributes for a given node
   * @param {object} el (a DOM node)
   * @param {object} props (an object of attribute name/value pairs)
   * @returns the DOM node
   */
  setAttributes(el, props) {
    for (let [name, value] of Object.entries(props)) {
      if (value) el.setAttribute(name, value);
      else el.removeAttribute(name);
    }
  }

  /**
   * make a valid ID for a DOM node based on an extension's ID.
   * @param {string} id (an extension's ID)
   * @returns an ID with crap removed so it can be used in a DOM node's ID.
   */
  makeWidgetId(id) {
    id = id.toLowerCase();
    return id.replace(/[^a-z0-9_-]/g, "_");
  }

  /**
   * for a given addon ID, get the Extension object from the addon policy
   * @param {string} id (an addon's ID)
   * @returns the Extension object
   */
  extensionForAddonId(id) {
    let policy = WebExtensionPolicy.getByID(id);
    return policy && policy.extension;
  }

  /**
   * find out if an addon has a valid signature
   * @param {object} addon (an Addon object, retrieved by AddonManager.getAddonsByTypes)
   * @returns true if signed, false if unsigned or invalid
   */
  isCorrectlySigned(addon) {
    // Add-ons without an "isCorrectlySigned" property are correctly signed as
    // they aren't the correct type for signing.
    return addon.isCorrectlySigned !== false;
  }

  /**
   * find out if an addon has been automatically disabled from the xpi database
   * because it lacked a valid signature and user had xpinstall.signatures.required = true
   * @param {object} addon (an Addon object)
   * @returns true if the addon was auto-disabled
   */
  isDisabledUnsigned(addon) {
    let signingRequired =
      addon.type == "locale"
        ? this.LANGPACKS_REQUIRE_SIGNING
        : this.REQUIRE_SIGNING;
    return signingRequired && !this.isCorrectlySigned(addon);
  }

  /**
   * find an addon's screenshot url. prefer 680x92.
   * @param {object} addon (an Addon object)
   * @returns {string} url
   */
  getScreenshotUrlForAddon(addon) {
    if (addon.id == "default-theme@mozilla.org") {
      return "chrome://mozapps/content/extensions/default-theme/preview.svg";
    }
    const builtInThemePreview = this.BuiltInThemes.previewForBuiltInThemeId(
      addon.id
    );
    if (builtInThemePreview) return builtInThemePreview;
    let { screenshots } = addon;
    if (!screenshots || !screenshots.length) return null;
    let screenshot = screenshots.find(s => s.width === 680 && s.height === 92);
    if (!screenshot) screenshot = screenshots[0];
    return screenshot.url;
  }

  // where panelviews are hiding when we're not looking
  viewCache(doc) {
    return doc.getElementById("appMenu-viewCache");
  }

  constructor() {
    ChromeUtils.defineESModuleGetters(this, {
      ExtensionPermissions:
        "resource://gre/modules/ExtensionPermissions.sys.mjs",
      BuiltInThemes: "resource:///modules/BuiltInThemes.sys.mjs",
    });
    ChromeUtils.defineLazyGetter(this, "extBundle", function () {
      return Services.strings.createBundle(
        "chrome://global/locale/extensions.properties"
      );
    });
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "REQUIRE_SIGNING",
      "xpinstall.signatures.required",
      false
    );
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "LANGPACKS_REQUIRE_SIGNING",
      "extensions.langpacks.signatures.required",
      false
    );
    this.viewId = "PanelUI-eom";
    this.config = ExtensionOptionsWidget.config;
    let { l10n } = this.config;
    if (
      /^chrome:\/\/browser\/content\/browser.(xul||xhtml)$/i.test(location) &&
      !CustomizableUI.getPlacementOfWidget("eom-button", true)
    ) {
      CustomizableUI.createWidget({
        id: "eom-button",
        viewId: this.viewId,
        type: "view",
        defaultArea: CustomizableUI.AREA_NAVBAR,
        removable: true,
        label: l10n["Button label"],
        tooltiptext: l10n["Button tooltip"],
        // if the button is middle-clicked, open the addons page instead of the panel
        onClick: event => {
          if (event.button == 1) {
            event.target.ownerGlobal.openAddonsMgr(
              "addons://list/extension"
            );
          }
        },
        // create the panelview before the toolbar button
        onBeforeCreated: aDoc => {
          let eop = aDoc.defaultView.extensionOptionsPanel;
          if (!eop) return;
          let view = eop.create(aDoc, "panelview", {
            id: eop.viewId,
            class: "PanelUI-subView cui-widget-panelview",
            flex: "1",
            style: "min-width:30em",
          });
          aDoc.getElementById("appMenu-viewCache").appendChild(view);
          aDoc.defaultView.extensionOptionsPanel.panelview = view;

          if (eop.config["Show header"]) {
            let header = view.appendChild(
              eop.create(aDoc, "vbox", { id: "eom-mainView-panel-header" })
            );
            let heading = header.appendChild(eop.create(aDoc, "label"));
            let label = heading.appendChild(
              eop.create(aDoc, "html:span", {
                id: "eom-mainView-panel-header-span",
                role: "heading",
                "aria-level": "1",
              })
            );
            label.textContent = l10n["Panel title"];
            view.appendChild(aDoc.createXULElement("toolbarseparator"));
          }

          view.appendChild(
            eop.create(aDoc, "vbox", {
              id: `${view.id}-body`,
              class: "panel-subview-body",
            })
          );

          // create the theme preview tooltip
          if (eop.config["Show theme preview tooltips"]) {
            let tooltip = aDoc
              .getElementById("mainPopupSet")
              .appendChild(
                aDoc.defaultView.MozXULElement.parseXULToFragment(
                  `<tooltip id="eom-theme-preview-tooltip" noautohide="true" orient="vertical"><vbox id="eom-theme-preview-box"><html:img id="eom-theme-preview-canvas"></html:img></vbox></tooltip>`
                )
              );
            tooltip.addEventListener("popupshowing", e =>
              eop.onTooltipShowing(e)
            );
          }

          eop.fluentSetup(aDoc).then(() => eop.swapAddonsButton(aDoc));
        },
        // populate the panel before it's shown
        onViewShowing: event => {
          if (
            event.originalTarget ===
            event.target.ownerGlobal.extensionOptionsPanel?.panelview
          ) {
            event.target.ownerGlobal.extensionOptionsPanel.getAddonsAndPopulate(
              event
            );
          }
        },
        // delete the panel if the widget node is destroyed
        onDestroyed: aDoc => {
          let view = aDoc.getElementById(
            aDoc.defaultView.extensionOptionsPanel?.viewId
          );
          if (view) {
            aDoc.defaultView.CustomizableUI.hidePanelForNode(view);
            view.remove();
          }
        },
      });
    }
    this.loadStylesheet(); // load the stylesheet
  }

  // grab localized strings for the extensions button and disabled/enabled extensions headings
  async fluentSetup(aDoc) {
    aDoc.ownerGlobal.MozXULElement.insertFTLIfNeeded(
      "toolkit/about/aboutAddons.ftl"
    );
    let [extensions, themes, enabled, disabled, privateHelp] =
      await aDoc.l10n.formatValues([
        "addon-category-extension",
        "addon-category-theme",
        "extension-enabled-heading",
        "extension-disabled-heading",
        "addon-detail-private-browsing-help",
      ]);
    privateHelp = privateHelp.replace(/\s*\<.*\>$/, "");
    this.aboutAddonsStrings = {
      extensions,
      themes,
      enabled,
      disabled,
      privateHelp,
    };
  }

  /**
   * this script changes the built-in "Add-ons & themes" button in the app menu
   * to open our new panel instead of opening about:addons
   * @param {object} aDoc (the document our widget has been created within)
   */
  swapAddonsButton(aDoc) {
    if (!this.config["Replace addons button"]) return;
    let win = aDoc.defaultView;
    win.PanelUI._initialized ||
      win.PanelUI.init(shouldSuppressPopupNotifications);
    let el = win.PanelUI.mainView.querySelector(
      "#appMenu-extensions-themes-button, #appMenu-addons-button"
    );
    this.setAttributes(el, {
      command: 0,
      key: 0,
      shortcut: 0,
      class: "subviewbutton subviewbutton-nav",
      closemenu: "none",
    });
    el.addEventListener("command", () =>
      win.PanelUI.showSubView("PanelUI-eom", el)
    );
  }

  /**
   * grab all addons and populate the panel with them.
   * @param {object} e (a ViewShowing event)
   */
  async getAddonsAndPopulate(e) {
    let extensions = await AddonManager.getAddonsByTypes(["extension"]);
    let themes = await AddonManager.getAddonsByTypes(["theme"]);
    this.populatePanelBody(e, { extensions, themes });
  }

  /**
   * create everything inside the panel
   * @param {object} e (a ViewShowing event - its target is the panelview node)
   * @param {array} addons (an object containing arrays for different addon
   *                       types e.g. extensions, themes)
   */
  populatePanelBody(e, addons) {
    let prevState;
    let { extensions, themes } = addons;
    let view = e?.target || this.panelview;
    let win = view.ownerGlobal;
    let doc = win.document;
    let body = view.querySelector(".panel-subview-body");
    let { l10n } = this.config;
    let enabledFirst = this.config["Show enabled extensions first"];
    let showVersion = this.config["Show version"];
    let showDisabled = this.config["Show disabled extensions"];
    let blackListArray = this.config["Addon ID blacklist"];

    // clear all the panel items and subviews before rebuilding them.
    while (body.hasChildNodes()) body.firstChild.remove();
    [...this.viewCache(doc).children].forEach(panel => {
      if (panel.id.includes("PanelUI-eom-addon-")) panel.remove();
    });
    let appMenuMultiView = win.PanelMultiView.forNode(PanelUI.multiView);
    if (
      win.PanelMultiView.forNode(view.closest("panelmultiview")) ===
      appMenuMultiView
    ) {
      [...appMenuMultiView._viewStack.children].forEach(panel => {
        if (panel.id !== view.id && panel.id.includes("PanelUI-eom-addon-")) {
          panel.remove();
        }
      });
    }

    // extensions...
    let enabledSubheader = body.appendChild(
      this.create(doc, "h2", { class: "subview-subheader" }, true)
    );
    enabledSubheader.textContent =
      this.aboutAddonsStrings[showDisabled ? "enabled" : "extensions"];
    extensions
      .sort((a, b) => {
        // get sorted by enabled state...
        let ka =
          (enabledFirst ? Number(!a.isActive) : "") + a.name.toLowerCase();
        let kb =
          (enabledFirst ? Number(!b.isActive) : "") + b.name.toLowerCase();
        return ka < kb ? -1 : 1;
      })
      .forEach(addon => {
        // then get excluded if config wills it...
        if (
          !blackListArray.includes(addon.id) &&
          (!addon.hidden || this.config["Show hidden extensions"]) &&
          (!addon.userDisabled || showDisabled)
        ) {
          // then get built into subviewbuttons and corresponding subviews...
          if (
            showDisabled &&
            enabledFirst &&
            prevState &&
            addon.isActive != prevState
          ) {
            body.appendChild(doc.createXULElement("toolbarseparator"));
            let disabledSubheader = body.appendChild(
              this.create(doc, "h2", { class: "subview-subheader" }, true)
            );
            disabledSubheader.textContent = this.aboutAddonsStrings.disabled;
          }
          prevState = addon.isActive;

          let subviewbutton = body.appendChild(
            this.create(doc, "toolbarbutton", {
              label: addon.name + (showVersion ? ` ${addon.version}` : ""),
              class:
                "subviewbutton subviewbutton-iconic subviewbutton-nav eom-addon-button",
              closemenu: "none",
              "addon-type": "extension",
              "data-extensionid": addon.id,
            })
          );
          subviewbutton.addEventListener("command", e =>
            this.showSubView(e, subviewbutton)
          );
          if (!addon.isActive) subviewbutton.classList.add("disabled");
          // set the icon using CSS variables and list-style-image so that user stylesheets can override the icon URL.
          subviewbutton.style.setProperty(
            "--extension-icon",
            `url(${addon.iconURL || this.config["Icon URL"]})`
          );
          subviewbutton._Addon = addon;

          if (this.config["Show addon messages"]) {
            this.setAddonMessage(doc, subviewbutton, addon);
          }
        }
      });

    // themes...
    let themesSeparator = body.appendChild(
      doc.createXULElement("toolbarseparator")
    );
    let themesSubheader = body.appendChild(
      this.create(doc, "h2", { class: "subview-subheader" }, true)
    );
    themesSubheader.textContent = this.aboutAddonsStrings.themes;
    themes.forEach(addon => {
      if (
        !blackListArray.includes(addon.id) &&
        (!addon.hidden || this.config["Show hidden extensions"]) &&
        (!addon.userDisabled || showDisabled)
      ) {
        let subviewbutton = body.appendChild(
          this.create(doc, "toolbarbutton", {
            label: addon.name + (showVersion ? ` ${addon.version}` : ""),
            class: "subviewbutton subviewbutton-iconic eom-addon-button",
            closemenu: "none",
            "addon-type": "theme",
            "data-extensionid": addon.id,
          })
        );
        subviewbutton.addEventListener("command", async e => {
          await addon[addon.userDisabled ? "enable" : "disable"]();
          subviewbutton.parentElement
            .querySelectorAll(`.eom-addon-button[addon-type="theme"]`)
            .forEach(btn => {
              btn.classList[btn._Addon?.isActive ? "remove" : "add"](
                "disabled"
              );
              this.setAddonMessage(doc, btn, btn._Addon);
            });
        });
        if (!addon.isActive) subviewbutton.classList.add("disabled");
        subviewbutton.style.setProperty(
          "--extension-icon",
          `url(${addon.iconURL || this.config["Icon URL"]})`
        );
        subviewbutton._Addon = addon;

        this.setAddonMessage(doc, subviewbutton, addon);
      }
    });

    // if no addons are shown, display a "Download Addons" button that leads to AMO.
    let getAddonsButton = body.appendChild(
      this.create(doc, "toolbarbutton", {
        id: "eom-get-addons-button",
        class: "subviewbutton subviewbutton-iconic",
        label: l10n["Download addons label"],
        image: `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 68 68" style="border-radius:3px"><path fill="context-fill" fill-opacity="context-fill-opacity" d="M0 0v68h68V0H0zm61.8 49H49.5V32.4c0-5.1-1.7-7-5-7-4 0-5.6 2.9-5.6 6.9v10.2h3.9v6.4H30.5V32.4c0-5.1-1.7-7-5-7-4 0-5.6 2.9-5.6 6.9v10.2h5.6v6.4h-18v-6.4h3.9V26H7.5v-6.4h12.3V24c1.8-3.1 4.8-5 8.9-5 4.2 0 8.1 2 9.5 6.3 1.6-3.9 4.9-6.3 9.5-6.3 5.3 0 10.1 3.2 10.1 10.1v13.5h4V49z"/></svg>`,
      })
    );
    getAddonsButton.addEventListener("command", e =>
      win.switchToTabHavingURI(
        Services.urlFormatter.formatURLPref("extensions.getAddons.link.url"),
        true,
        {
          inBackground: false,
          triggeringPrincipal:
            Services.scriptSecurityManager.getSystemPrincipal(),
        }
      )
    );

    let hasExtensions = !!body.querySelector(
      `.eom-addon-button[addon-type="extension"]`
    );
    let hasThemes = !!body.querySelector(
      `.eom-addon-button[addon-type="theme"]`
    );
    getAddonsButton.hidden = hasExtensions || hasThemes;
    if (!hasExtensions) {
      enabledSubheader.remove();
      themesSeparator.remove();
    }
    if (!hasThemes) {
      themesSubheader.remove();
      themesSeparator.remove();
    }

    // make a footer button that leads to about:addons
    if (view.querySelector("#eom-allAddonsButton")) return;
    view.appendChild(doc.createXULElement("toolbarseparator"));
    let footerButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Addons page label"],
        id: "eom-allAddonsButton",
        class: "subviewbutton subviewbutton-iconic panel-subview-footer-button",
        image: this.config["Icon URL"],
        key: "key_openAddons",
        shortcut: win.ShortcutUtils.prettifyShortcut(win.key_openAddons),
      })
    );
    footerButton.addEventListener("command", () =>
      openAddonsMgr("addons://list/extension")
    );
    
    if (view.querySelector("#eom-allAddonsButton1")) return;
     let footerButton1 = view.appendChild (
      this.create(doc, "toolbarbutton", {
        label: "Обновления",
        id: "eom-allAddonsButton1",
        class: "subviewbutton subviewbutton-iconic panel-subview-footer-button",
        image: this.config["Icon URL"],
      })
      );
     footerButton1.addEventListener("command", () =>
     openAddonsMgr("addons://updates/recent")
    ); 
    
    if (view.querySelector("#eom-allAddonsButton2")) return;
     let footerButton2 = view.appendChild (
      this.create(doc, "toolbarbutton", {
        label: "Обновить дополнения",
        id: "ForAddonsUpdates",
        class: "subviewbutton subviewbutton-iconic panel-subview-footer-button",
        image: this.config["Icon URL"],
      })
      );
     footerButton2.addEventListener("command", () =>
     this.checkForAddonsUpdates() 
    ); 
  }


  /**
   * for a given button made for an addon, find out if it has a message
   * (blocked, unverified, etc.) and if so, display it
   * @param {object} doc (the document we're localizing)
   * @param {object} subviewbutton (an addon button in the panel)
   * @param {object} addon (an Addon object)
   */
  async setAddonMessage(doc, subviewbutton, addon) {
    const { l10n } = this.config;
    const { name } = addon;
    const { STATE_BLOCKED, STATE_SOFTBLOCKED } = Ci.nsIBlocklistService;
    const formatString = (type, args) => {
      return new Promise(resolve => {
        doc.l10n
          .formatMessages([{ id: `details-notification-${type}2`, args }])
          .then(msg => resolve(msg[0].value));
      });
    };

    let message = null;
    if (addon.blocklistState === STATE_BLOCKED) {
      message = {
        label: l10n.addonMessages.Blocked,
        detail: await formatString("blocked", { name }),
        type: "error",
      };
    } else if (this.isDisabledUnsigned(addon)) {
      message = {
        label: l10n.addonMessages["Signature Required"],
        detail: await formatString("unsigned-and-disabled", { name }),
        type: "error",
      };
    } else if (
      !addon.isCompatible &&
      (AddonManager.checkCompatibility ||
        addon.blocklistState !== STATE_SOFTBLOCKED)
    ) {
      message = {
        label: l10n.addonMessages.Incompatible,
        detail: await formatString("incompatible", {
          name,
          version: Services.appinfo.version,
        }),
        type: "warning",
      };
    } else if (!this.isCorrectlySigned(addon)) {
      message = {
        label: l10n.addonMessages.Unverified,
        detail: await formatString("unsigned", { name }),
        type: "warning",
      };
    } else if (addon.blocklistState === STATE_SOFTBLOCKED) {
      message = {
        label: l10n.addonMessages.Insecure,
        detail: await formatString("softblocked", { name }),
        type: "warning",
      };
    }
    if (
      this.config["Show theme preview tooltips"] &&
      addon.type === "theme" &&
      (!message || message.type !== "error")
    ) {
      message = message ?? {};
      message.detail = "";
      message.tooltip = "eom-theme-preview-tooltip";
      message.preview = this.getScreenshotUrlForAddon(addon);
      if (addon.isActive) {
        message.label = null;
        message.checked = true;
      }
    }
    if (subviewbutton._addonMessage) {
      subviewbutton.removeAttribute("message-type");
      subviewbutton.removeAttribute("tooltiptext");
      subviewbutton.removeAttribute("tooltip");
      subviewbutton.removeAttribute("enable-checked");
      subviewbutton.querySelector(".eom-message-label")?.remove();
      delete subviewbutton._addonMessage;
    }
    if (message) {
      subviewbutton.setAttribute("message-type", message?.type);
      subviewbutton.setAttribute("tooltiptext", message?.detail);
      if (message.tooltip) {
        subviewbutton.setAttribute("tooltip", message.tooltip);
      }
      if (message.checked) subviewbutton.setAttribute("enable-checked", true);
      if (message.label) {
        subviewbutton.appendChild(
          this.create(document, "h", {
            class: "toolbarbutton-text eom-message-label",
          })
        ).textContent = `(${message.label})`;
      }
    }
    subviewbutton._addonMessage = message;
  }

  /**
   * show the subview for a given extension
   * @param {object} event (a triggering command/click event)
   * @param {object} anchor (the subviewbutton that was clicked —
   *                        dictates the title of the subview)
   */
  showSubView(event, anchor) {
    if (!("_Addon" in anchor)) return;
    this.buildSubView(anchor, anchor._Addon);
    event.target.ownerGlobal.PanelUI?.showSubView(
      `PanelUI-eom-addon-${this.makeWidgetId(anchor._Addon.id)}`,
      anchor,
      event
    );
  }

  /**
   * for a given addon, build a panel subview
   * @param {object} subviewbutton (the button you click to enter the subview,
   *                               corresponding to the addon)
   * @param {object} addon (an addon object provided by the AddonManager,
   *                       with all the data we need)
   */
  buildSubView(subviewbutton, addon) {
    let { l10n } = this.config;
    let win = subviewbutton.ownerGlobal;
    let doc = win.document;
    let view = this.viewCache(doc).appendChild(
      this.create(doc, "panelview", {
        id: `PanelUI-eom-addon-${this.makeWidgetId(addon.id)}`, // turn the extension ID into a DOM node ID
        flex: "1",
        class: "PanelUI-subView cui-widget-panelview",
      })
    );

    // create options button
    let optionsButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Addon options label"],
        class: "subviewbutton",
      })
    );
    optionsButton.addEventListener("command", e =>
      this.openAddonOptions(addon, win)
    );

    // manage button, when no options page exists
    let manageButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Manage addon label"],
        class: "subviewbutton",
      })
    );
    manageButton.addEventListener("command", e =>
      openAddonsMgr(
        `addons://detail/${encodeURIComponent(addon.id)}`
      )
    );

    // disable button
    let disableButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: addon.userDisabled
          ? l10n["Enable addon label"]
          : l10n["Disable addon label"],
        class: "subviewbutton",
        closemenu: "none",
      })
    );
    disableButton.addEventListener("command", async e => {
      if (addon.userDisabled) {
        await addon.enable();
        disableButton.setAttribute("label", l10n["Disable addon label"]);
      } else {
        await addon.disable();
        disableButton.setAttribute("label", l10n["Enable addon label"]);
      }
      this.getAddonsAndPopulate();
    });



// on Pin To Toolbar
    let onPinToTool = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["On Pin To Toolbar"],
        closemenu: "none",
        class: "subviewbutton",
        type: "checkbox",
        checked: "false",
          })
    );
    onPinToTool.addEventListener("command", async ( event) => {
          const extension = this.extensionForAddonId(addon.id);
          const browserAction = extension.apiManager.global.browserActionFor(extension);
          const checkbox = event.target;

     if (browserAction) {
         gUnifiedExtensions.pinToToolbar(
        browserAction.widget?.id, checkbox.getAttribute("checked") == "true"
       );
}
     
    });


    // uninstall button, and so on...
    let uninstallButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Uninstall addon label"],
        class: "subviewbutton",
      })
    );
    uninstallButton.addEventListener("command", e => {
      if (
        win.Services.prompt.confirm(
          null,
          null,
          `Delete ${addon.name} permanently?`
        )
      ) {
        addon.pendingOperations & win.AddonManager.PENDING_UNINSTALL
          ? addon.cancelUninstall()
          : addon.uninstall();
      }
    });
    
   let themesSeparator1 = view.appendChild(
      doc.createXULElement("toolbarseparator")
    );
    
    // allow automatic updates radio group
    let updates = view.appendChild(
      this.create(doc, "hbox", {
        id: "eom-allow-auto-updates",
        class: "subviewbutton eom-radio-hbox",
        align: "center",
      })
    );
    let updatesLabel = updates.appendChild(
      this.create(doc, "label", {
        id: "eom-allow-auto-updates-label",
        class: "toolbarbutton-text eom-radio-label",
        flex: 1,
        wrap: true,
      })
    );
    updatesLabel.textContent = l10n["Automatic updates label"];
    let updatesGroup = updates.appendChild(
      this.create(doc, "radiogroup", {
        id: "eom-allow-auto-updates-group",
        class: "eom-radio-group",
        value: addon.applyBackgroundUpdates,
        closemenu: "none",
        orient: "horizontal",
        "aria-labelledby": "eom-allow-auto-updates-label",
      })
    );
    updatesGroup.addEventListener(
      "command",
      e => (addon.applyBackgroundUpdates = e.target.value)
    );
    updatesGroup.appendChild(
      this.create(doc, "radio", {
        label: l10n.autoUpdate["Default label"],
        class: "subviewradio",
        value: 1,
      })
    );
    updatesGroup.appendChild(
      this.create(doc, "radio", {
        label: l10n.autoUpdate["On label"],
        class: "subviewradio",
        value: 2,
      })
    );
    updatesGroup.appendChild(
      this.create(doc, "radio", {
        label: l10n.autoUpdate["Off label"],
        class: "subviewradio",
        value: 0,
      })
    );

    // run in private windows radio group
    let setPrivateState = async (addon, node) => {
      let perms = await this.ExtensionPermissions.get(addon.id);
      let isAllowed = perms.permissions.includes(
        "internal:privateBrowsingAllowed"
      );
      node.permState = isAllowed;
      node.value = isAllowed ? 1 : 0;
    };
    let privateWindows = view.appendChild(
      this.create(doc, "hbox", {
        id: "eom-run-in-private",
        class: "subviewbutton eom-radio-hbox",
        align: "center",
      })
    );
    let privateLabel = privateWindows.appendChild(
      this.create(doc, "label", {
        id: "eom-run-in-private-label",
        class: "toolbarbutton-text eom-radio-label",
        flex: 1,
        wrap: true,
        tooltiptext: this.aboutAddonsStrings.privateHelp,
      })
    );
    privateLabel.textContent = l10n["Run in private windows label"];
    let privateGroup = privateWindows.appendChild(
      this.create(doc, "radiogroup", {
        id: "eom-run-in-private-group",
        class: "eom-radio-group",
        closemenu: "none",
        orient: "horizontal",
        "aria-labelledby": "eom-run-in-private-label",
      })
    );
    privateGroup.addEventListener("command", async () => {
      let extension = this.extensionForAddonId(addon.id);
      await this.ExtensionPermissions[
        privateGroup.permState ? "remove" : "add"
      ](
        addon.id,
        {
          permissions: ["internal:privateBrowsingAllowed"],
          origins: [],
        },
        extension
      );
      setPrivateState(addon, privateGroup);
    });
    privateGroup.appendChild(
      this.create(doc, "radio", {
        label: l10n.runInPrivate["Allow label"],
        class: "subviewradio",
        value: 1,
      })
    );
    privateGroup.appendChild(
      this.create(doc, "radio", {
        label: l10n.runInPrivate["Don't allow label"],
        class: "subviewradio",
        value: 0,
      })
    );
    setPrivateState(addon, privateGroup);


    let themesSeparator2 = view.appendChild(
      doc.createXULElement("toolbarseparator")
    );
    // manage shortcuts
    let shortcutsButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Manage shortcuts label"],
        class: "subviewbutton",
      })
    );
    shortcutsButton.addEventListener("command", () =>
      openAddonsMgr("addons://shortcuts/shortcuts")
    );

    let viewSrcButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["View source label"],
        class: "subviewbutton",
      })
    );
    viewSrcButton.addEventListener("command", () => this.openArchive(addon));

   let viewSrcButton1 = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: "Папка установки",
        class: "subviewbutton",
      })
    );
    viewSrcButton1.addEventListener("command", () => this.openDir(addon));

    let homePageButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Open homepage label"],
        class: "subviewbutton",
      })
    );
    homePageButton.addEventListener("command", () => {
      win.switchToTabHavingURI(addon.homepageURL || addon.supportURL, true, {
        inBackground: false,
        triggeringPrincipal:
          win.Services.scriptSecurityManager.getSystemPrincipal(),
      });
    });

    let copyIdButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Copy ID label"],
        class: "subviewbutton",
      })
    );
    copyIdButton.addEventListener("command", () => {
      win.Cc["@mozilla.org/widget/clipboardhelper;1"]
        .getService(win.Ci.nsIClipboardHelper)
        .copyString(addon.id);
      let PMV =
        view.panelMultiView && win.PanelMultiView.forNode(view.panelMultiView);
      if (PMV) {
        let panel = PMV._panel;
        if (panel && PMV._getBoundsWithoutFlushing(panel.anchorNode)?.width) {
          win.CustomHint?.show(panel.anchorNode, "Copied");
        }
      }
    });
    
    let copyNameButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Copy Name"],
        class: "subviewbutton",
      })
    );
    copyNameButton.addEventListener("command", () => {
      win.Cc["@mozilla.org/widget/clipboardhelper;1"]
        .getService(win.Ci.nsIClipboardHelper)
        .copyString(addon.name);
      
    });
    
    let copyVersionButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Copy Version"],
        class: "subviewbutton",
      })
    );
    copyVersionButton.addEventListener("command", () => {
      win.Cc["@mozilla.org/widget/clipboardhelper;1"]
        .getService(win.Ci.nsIClipboardHelper)
        .copyString(addon.version);
    });

     let copyNameVersionButton1 = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Copy NameVersion"],
        class: "subviewbutton ",
      })
    );
    copyNameVersionButton1.addEventListener("command", () => {
      win.Cc["@mozilla.org/widget/clipboardhelper;1"]
        .getService(win.Ci.nsIClipboardHelper)
        .copyString(addon.name + " " + addon.version);
    });
    
    let InspectorButton = view.appendChild(
      this.create(doc, "toolbarbutton", {
        label: l10n["Copy Inspector"],
        class: "subviewbutton ",
      })
    );
      InspectorButton.addEventListener("command", () => {
      var  url3 = `about:devtools-toolbox?id=${encodeURIComponent(   addon.id    )}&type=extension`;
     gBrowser.fixupAndLoadURIString(url3, { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal() }); 

    });

    view.addEventListener("ViewShowing", () => {
      optionsButton.hidden = !addon.optionsURL;
      manageButton.hidden = !!addon.optionsURL;
      updates.hidden = !(addon.permissions & win.AddonManager.PERM_CAN_UPGRADE);
      updatesGroup.setAttribute("value", addon.applyBackgroundUpdates);
      privateWindows.hidden = !(
        addon.incognito != "not_allowed" &&
        !!(
          addon.permissions &
          win.AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
        )
      );
      setPrivateState(addon, privateGroup);
      shortcutsButton.hidden = !this.extensionForAddonId(addon.id)?.shortcuts
        ?.manifestCommands?.size;
      disableButton.setAttribute(
        "label",
        addon.userDisabled
          ? l10n["Enable addon label"]
          : l10n["Disable addon label"]
      );
      uninstallButton.hidden = viewSrcButton.hidden =
        addon.isSystem || addon.isBuiltin || addon.temporarilyInstalled;
      homePageButton.hidden = !(addon.homepageURL || addon.supportURL);
    });
  }

  /**
   * open a given addon's options page
   * @param {object} addon (an addon object)
   * @param {object} win (the window from which this was invoked)
   */
  openAddonOptions(addon, win) {
    if (!addon.isActive || !addon.optionsURL) return;

    switch (Number(addon.optionsType)) {
      case 5:
        win.BrowserOpenAddonsMgr(
          `addons://detail/${win.encodeURIComponent(addon.id)}/preferences`
        );
        break;
      case 3:
        win.switchToTabHavingURI(addon.optionsURL, true);
        break;
      case 1: {
        let windows = win.Services.wm.getEnumerator(null);
        while (windows.hasMoreElements()) {
          let win2 = windows.getNext();
          if (win2.closed) continue;
          if (win2.document.documentURI == addon.optionsURL) {
            win2.focus();
            return;
          }
        }
        let features = "chrome,titlebar,toolbar,centerscreen";
        if (
          win.Services.prefs.getBoolPref("browser.preferences.instantApply")
        ) {
          features += ",dialog=no";
        }
        win.openDialog(addon.optionsURL, addon.id, features);
      }
    }
  }

  /**
   * open a given addon's source xpi file in the user's associated program, e.g. 7-zip
   * @param {object} addon (an addon object)
   */
  openArchive(addon) {
    let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
    dir.append("extensions");
    dir.append(`${addon.id}.xpi`);
    dir.launch();
  }

  openDir(addon) {
    let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
    dir.append("extensions");
    dir.append(`${addon.id}.xpi`);
    dir.reveal();
  }


  onTooltipShowing(e) {
    let anchor = e.target.triggerNode
      ? e.target.triggerNode.closest(".eom-addon-button")
      : null;
    let message = anchor._addonMessage;
    let img = e.target.querySelector("#eom-theme-preview-canvas");
    img.src = message?.preview || "";
    if (!anchor || !message?.preview) {
      e.preventDefault();
      return;
    }
    e.target.setAttribute("position", "after_start");
    e.target.moveToAnchor(anchor, "after_start");
  }

  // generate and load a stylesheet
  loadStylesheet() {
    let sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(
      Ci.nsIStyleSheetService
    );
    let css = /*css*/ `#eom-button {
  list-style-image: url('${this.config["Icon URL"]}');
}
#eom-mainView-panel-header {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--arrowpanel-menuitem-margin-inline);
  min-height: var(--arrowpanel-header-min-height);
}
#eom-mainView-panel-header-span {
  font-weight: 600;
  display: inline-block;
  text-align: center;
  overflow-wrap: break-word;
}
.panel-header ~ #eom-mainView-panel-header,
.panel-header ~ #eom-mainView-panel-header + toolbarseparator {
  display: none;
}
.eom-addon-button {
  list-style-image: var(--extension-icon);
}
#${this.viewId} {
  min-height: 400px;
  min-width: 27em;
}
#${this.viewId} .disabled label {
  opacity: 0.6;
  font-style: italic;
}
#${this.viewId} .eom-message-label {
  opacity: 0.6;
  margin-inline-start: 8px;
  font-style: italic;
}
.eom-addon-button:is(toolbarbutton.subviewbutton) {
  &[message-type="warning"] {
    background-color: var(--eom-warning-bg, hsla(48, 100%, 66%, 0.15));

    &:not([disabled], [open], :active):is(:hover) {
      background-color: var(
        --eom-warning-bg-hover,
        color-mix(in srgb, currentColor 8%, hsla(48, 100%, 66%, 0.18))
      );
    }
    &:not([disabled]):is([open], :hover:active) {
      background-color: var(
        --eom-warning-bg-active,
        color-mix(in srgb, currentColor 15%, hsla(48, 100%, 66%, 0.2))
      );
    }
  }
  &[message-type="error"] {
    background-color: var(--eom-error-bg, hsla(2, 100%, 66%, 0.15));

    &:not([disabled], [open], :active):is(:hover) {
      background-color: var(
        --eom-error-bg-hover,
        color-mix(in srgb, currentColor 8%, hsla(2, 100%, 66%, 0.18))
      );
    }
    &:not([disabled]):is([open], :hover:active) {
      background-color: var(
        --eom-error-bg-active,
        color-mix(in srgb, currentColor 15%, hsla(2, 100%, 66%, 0.2))
      );
    }
  }
}
.eom-radio-hbox {
  padding-block: 4px;
}
.eom-radio-hbox .radio-check {
  margin-block: 0;
}
.eom-radio-hbox label {
  padding-bottom: 1px;
}
.eom-radio-label {
  margin-inline-end: 8px;
}
.eom-radio-hbox .subviewradio {
  margin: 0;
  margin-inline: 2px;
  padding: 0;
  background: none !important;
}
.eom-radio-hbox .radio-label-box {
  margin: 0;
  padding: 0;
}
.eom-radio-label[tooltiptext] {
  cursor: help;
}
.eom-addon-button[enable-checked]::after {
  -moz-context-properties: fill, fill-opacity;
  content: url(chrome://global/skin/icons/check.svg);
  fill: currentColor;
  fill-opacity: 0.6;
  display: block;
  margin-inline-start: 10px;
}
#eom-theme-preview-tooltip {
  appearance: none;
  padding: 0;
  border-radius: var(--arrowpanel-border-radius, 8px);
  overflow: hidden;
  border: 1px solid var(--arrowpanel-border-color);
  background: var(--arrowpanel-border-color);
}
`;
    let uri = makeURI(`data:text/css;charset=UTF=8,${encodeURIComponent(css)}`);
    if (sss.sheetRegistered(uri, sss.AUTHOR_SHEET)) return;
    sss.loadAndRegisterSheet(uri, sss.AUTHOR_SHEET);
  }
}

window.extensionOptionsPanel = new ExtensionOptionsWidget();


Не плохой вариант на замену кнопки от infocatcher. Как выражается автор - это его видения в будущем этой панели, которое придет не скоро. Я обратился к нему . но на письма он никому не отвечет больше 2-х месяцев.
Недостатки:
1. Она не заменяет полностью основную панель - выражается это в том, что если спрятать основную, в какую нибудь панель, которая закрывается, то невозможно установить дополнения, т.к. это системная кнопка. Хотелось чтобы, когда я спрятал основную, расширения устанавливались через эту, если возможно.
2. Я дополнил эту панель несколькоми пунктами, один из них - закрепить на панели инструментов, но надо щелкнуть мышью не один раз, а 2 раза, в чем причина не понятно
3. Не хватает пункта- проверить обновления. Для меня достаточно - проверить на обновления, если есть, то открыть вкладку-последние обновления, если нет: алерт - не найдено.
Плюсы
1. Простота кода и  добавления своих пунктов каких душе угодно
2. Современный вид
Dumby если не сложно, можешь посмотреть пункты недостатки и предложить свое решение


mokujin хоть и старый но работает надежно, жалко от него отказываться, просто в том месте, что указал надо внести изменения, а там дальше посмотрим. Хотя  можно и это в этой версии браузера проигнорировать это. Если переключить security.browser_xhtml_csp.enabled то вообще не будет показывать этих ошибок, связанных с oncommand и другими Content-Security-Policy. За исправления спасибо

Отредактировано Andrey_Krropotkin (07-04-2025 17:45:11)

Отсутствует

 

№1732706-04-2025 01:49:59

Dobrov
Участник
 
Группа: Members
Зарегистрирован: 04-10-2011
Сообщений: 492
UA: Firefox 128.0

Re: Custom Buttons

Andrey_Krropotkin пишет

замена панели unified-extensions-button

Эта панель мало информативная, на мой взгляд, нужно открывать подменю каждого дополнения.
Кнопка меню с Дополнениями от Vitaliy V. более наглядная, ей проще пользоваться.

Отсутствует

 

№1732809-04-2025 15:58:38

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 57
UA: unknown 0.0

Re: Custom Buttons

Dumby
Опять косяк  133 не открывается поиск...Жму на иконки - и никак.
1.png
СНЯТ вопрос ...сделал , а вот со вторым как быть..

С этим тоже кранты ? Подстановка XPIDatabase.sys.mjs ,кнопка падает...

скрытый текст

Выделить код

Код:

((id, g) => {
    addDestructor(r => r[5] == "e" && id in g && g[id].destroy());                                         
    if (g[id]) return;
    var topic = "quit-application-granted", {obs} = Services;
    obs.addObserver(g[id] = {
        // true - disable, false - enable
        states: {
            "{97d566da-42c5-4ef4-a03b-5a2e5f7cbcb2}": true,
            "jid1-s2tSKqH4h0BHUw@jetpack": false,
            "mozilla_cc3@internetdownloadmanager.com": true,
            "{74145f27-f039-47ce-a470-a662b129930a}": true,
            "{acf99872-d701-4863-adc2-cdda1163aa34}": true,
            "{8f4bbf79-5514-4d04-a901-d5fabfe91d73}": true, 
            
        },
        filter(addon) {
            var state = this.states[addon.id];
            if (
                state !== undefined && addon.userDisabled != state
                && addon.type.endsWith("extension")
                && addon.location.name != "app-builtin"
            ) {
                addon.active = addon.location.get(addon.id).enabled
                    = !(addon.userDisabled = state);
                return true;
            }
        },
        observe() {
            this.destroy();
            if (g.XPIDatabase.getAddons().filter(this.filter, this).length)
                g.XPIDatabase.saveChanges(), g.XPIStates.save();
        },
        destroy() {
            delete g[id];
            obs.removeObserver(this, topic);
        }
    }, topic, false);
})(
    "CBQuitApplicationExtensionsSwitcher",
    Cu.import("resource://gre/modules/addons/XPIDatabase.jsm", {})
);

Отредактировано green25 (14-04-2025 00:37:42)

Отсутствует

 

№1732917-04-2025 14:16:16

Ki_rrrilll
Участник
 
Группа: Members
Зарегистрирован: 22-11-2013
Сообщений: 136
UA: Firefox 115.0

Re: Custom Buttons

Вот есть информация о закладке

скрытый текст

Выделить код

Код:

"guid":"aAhaKL-EVpzr",
"title":"Regex tester and debugger",
"index":2,
"dateAdded":1504880196000000,
"lastModified":1717321063820000,
"id":41,
"typeCode":1,
"type":"text/x-moz-place",
"uri":"https://regex101.com/"


Можно ли, используя эту информацию, программно открыть менеджер закладок на этой закладке?

Отсутствует

 

№1733030-04-2025 07:22:43

leex
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 331
UA: Firefox 138.0

Re: Custom Buttons

Всем привет!
FF обновился до 138, и СB 0.32 перестал работать.
Закрыл FF, удалил папку startupCache из Локального каталога (нашёл в about:profiles), но CB так и не заработал.
Удалил СB 0.32, установил custom_buttons-0.0.7.0.0.34-fx-paxmod (залил отсюда https://forum.mozilla-russia.org/viewtopic.php?pid=811227#p811227 ) и результата снова нет.
А вот custom_buttons-0.0.7.0.0.34-fx-bootstrap не устанавливается.
Пишет, что «Это дополнение не может быть установлено, так как оно, по-видимому, повреждено».


Помогите запустить СВ, пожалуйста.
Windows 10 (64-bit)

Отсутствует

 

№1733130-04-2025 08:06:09

leex
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 331
UA: Firefox 138.0

Re: Custom Buttons

Если в отладчике попробовать загрузить временное дополнение и указать custom_buttons-0.0.7.0.0.34-fx-paxmod, то на желтом фоне получаю такое предупреждение:
Сведения о предупреждении
Reading manifest: Warning processing version: version must be a version string consisting of at most 4 integers of at most 9 digits without leading zeros, and separated with dots

--

Если попробовать custom_buttons-0.0.7.0.0.34-fx-bootstrap, то на красном фоне показывает ошибку:
При установке временного дополнения произошла ошибка.
Сведения об ошибке
File C:\Users\Desktop\custom_buttons-0.0.7.0.0.34-fx-bootstrap.xpi does not contain a valid manifest

Отредактировано leex (30-04-2025 08:07:16)

Отсутствует

 

№1733230-04-2025 08:24:00

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 507
UA: Firefox 137.0

Re: Custom Buttons

leex Где то ранее несколько страниц назад  custom_buttons-0.0.7.0.0.36

Отсутствует

 

№1733330-04-2025 10:32:12

xrun1
Участник
 
Группа: Members
Зарегистрирован: 12-12-2013
Сообщений: 1291
UA: Firefox 138.0

Re: Custom Buttons

Отсутствует

 

№1733430-04-2025 14:54:36

Garalf
Участник
 
Группа: Members
Зарегистрирован: 19-09-2017
Сообщений: 348
UA: Firefox 138.0

Re: Custom Buttons

Dumby
Посмотри, пожалуйста, кнопку
Перестала работать в 138

скрытый текст

Выделить код

Код:

(func => {
	var sysPlayerName = "Pot Player";
        
	var path = "c:\\INstaLL\\PotPlayer\\PotPlayerMini64.exe";
	var videoMoved = "Видео перенесено в " + sysPlayerName;
	var noFound = "Не найдено видео на странице, доступное для переноса в " + sysPlayerName;
        
	
	this.label = "Открыть видео в " + sysPlayerName;
	this.tooltipText = "Л: Видео в плеер\nП: Видео из Clipboard";

	this._handleClick = () => {
		var msgName = _id + ":Player";
		var listener = ({data}) => data ? run([data]) : notify();
                var listener = ({data}) => data ? run([data], true) : notify();
		messageManager.addMessageListener(msgName, listener);
		addDestructor(() => messageManager.removeMessageListener(msgName, listener));

		var url = "data:charset=utf-8," + encodeURIComponent(
			`(${func})()`.replace("MSG_NAME", msgName)
				.replace("VIDEO_MOVED", encodeURIComponent(videoMoved))
				.replace("CONFIRM", encodeURIComponent("Открыть ссылку в плеере ?"))
		);
		(this._handleClick = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
                
	}
	this.onclick = e => e.button != 1 || gShowPopup(this); 
	this.oncontextmenu = e => {
		if (e.ctrlKey || e.shiftKey || e.altKey) return;
		e.preventDefault();
		custombuttons.confirmBox(null, "Запустить плеер из буфера обмена ?", "Да", "Нет")
                 && run([gClipboard.read(), "/play"]);
			
	}
	var popup = document.getElementById("contentAreaContextMenu");
	addEventListener("popupshowing", {
		get hidden() {
			return !(gContextMenu.onLink || gContextMenu.onVideo || gContextMenu.onPlainTextLink);
		},
		handleEvent() {
			if (this.hidden) return;
			var menuitem = document.createXULElement("menuitem");
			for(var args of Object.entries({
				image: self.image,
				oncommand: "play();",
				class: "menuitem-iconic",
				label: "Открыть в " + sysPlayerName
			}))
				menuitem.setAttribute(...args);
			menuitem.play = () => play(gContextMenu.linkURL || gContextMenu.mediaURL);
			document.getElementById("context-savelink").before(menuitem);
			addDestructor(() => menuitem.remove());
			this.handleEvent = e => {
				if (e.target == popup) menuitem.hidden = this.hidden;
			}
		}
	}, false, popup || 1);

	var play = link => custombuttons.confirmBox(null, "Открыть ссылку в плеере ?", "Да", "Отмена") && run([link]);
	/*
	var run = args => {
		var file = FileUtils.File(path);
		(run = args => {
			if (!file.exists()) return custombuttons.alertBox("File not exists!", path);
			var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
			process.init(file);
                        args.push("/play");
			process.runwAsync(args, args.length);
                        quit && window.close();
		})(args);
	}
*/
	var run = (...a) => {
		var file = FileUtils.File(path);
		(run = (args, quit) => {
			if (!file.exists()) return custombuttons.alertBox("File not exists!", path);
			var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
			gBrowser.selectedBrowser.browsingContext.mediaController.pause();
			process.init(file);
                        args.push("/play");
			process.runwAsync(args, args.length);
			
		})(...a);
	}
	var notify = () => {
		var name = _id + "-noFound";
		var as = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
		(notify = () => setTimeout(as.closeAlert, 1150, name, as.showAlertNotification(
			"chrome://global/skin/icons/question-48.png", "", noFound, false, "", null, name
		)))();
	}

})(() => {

	var found, videoMoved, SEND = msg => {
		found = true;
		if (!msg || Cc["@mozilla.org/embedcomp/prompt-service;1"]
			.getService(Ci.nsIPromptService)
			.confirm(content, null, decodeURIComponent("CONFIRM"))
		) {
			if (msg) videoMoved = decodeURIComponent("VIDEO_MOVED");
			sendAsyncMessage("MSG_NAME", msg);
		}
		else return true;
	}

	var YoutubeID = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})(?:\W|$)/;

	var tmp = '',
	tmpp = '',
	innerA = '<div style="display:block!important;color:#00ff00!important;width:250px!important;font:bold 16px serif!important;z-index:999!important;opacity:1!important;visibility: visible!important;',
	innerB = 'left:5px!important;position:absolute!important;height:auto!important;box-sizing:border-box!important;padding:5px!important;margin:5px!important;',
	//stopPl = "javascript:(function(){v=document.getElementById('movie_player');if(v){v.stopVideo()}else{v=document.getElementsByTagName('video');if(v){v[0].src='';try{v[0].load()}catch(e){}};}})();",
	ytIMGouter = function (ytID) {
		return '<div width="100%"><br /><a target="_blank" href="https://www.youtube.com/watch?v=' + ytID + '"><img src="https://i.ytimg.com/vi/' + ytID + '/hqdefault.jpg"></a><br />' + innerA + 'background-color:black!important;position:relative!important;bottom:20px!important;">&nbsp;&nbsp;' + videoMoved + '</div><br /></div><br />'
	},
	handlWin = function (currentWin) {
		tmp = '';
		var elem = currentWin.document.getElementsByTagName('video'),
		currLoc = currentWin.location;
		if (elem.length > 0) {
			if (currLoc.hostname.indexOf('youtu') != -1 && (tmp = currLoc.toString().match(YoutubeID)) && tmp[1].length == 11) {

				if (SEND('https://www.youtube.com/watch?v=' + tmp[1])) return;

				videoMovedbox = currentWin.document.createElement('videoMoved');
				videoMovedbox.innerHTML = innerA + innerB + 'top:-15px!important;"><b>' + videoMoved + '</b></div>';

				//loadURI(stopPl);
				(function(d){var v=d.getElementById('movie_player');if(v){try{v.stopVideo()}catch{}}
					else{v=d.getElementsByTagName('video');if(v[0]){v[0].src='';try{v[0].load()}catch{}};}})(currentWin.document);

				currentWin.document.getElementById('eow-title').appendChild(videoMovedbox);
				return true;
			};
			for (i = 0; i < elem.length; i++) {
				if (((tmp = getSrc(elem[i].parentNode, currLoc)) && tmp.length > 2) || (i == 0 && currentWin.document.body.innerHTML.substring(0, 7) == '<video ' && (tmp = currLoc.toString()))) {

					if (SEND(tmp)) return;

					videoMovedbox = currentWin.document.createElement('videoMoved');
					videoMovedbox.innerHTML = innerA + innerB + 'top:20px!important;background-color:black!important;">' + videoMoved + '</div>';

					if (currLoc.hostname == 'www.youtube.com') {
						elem[i].parentNode.parentNode.appendChild(videoMovedbox);
					} else {
						elem[i].parentNode.appendChild(videoMovedbox);
					};
					elem[i].src = '';
					try {
						elem[i].load()
					} catch (e) {};
					return true;
				}
			}
		};

		currentWin._elems = currentWin.document.getElementsByTagName('iframe');
		if (currentWin._elems.length > 0) {
			for (currentWin._iCounter = 0; currentWin._iCounter < currentWin._elems.length; currentWin._iCounter++) {
				if ((currentWin._elems[currentWin._iCounter].src.indexOf('youtube.com') > -1) && (tmp = currentWin._elems[currentWin._iCounter].src.match(YoutubeID)) && (tmp[1].length == 11)) {

				if (SEND('https://www.youtube.com/watch?v=' + tmp[1])) return;

				currentWin._elems[currentWin._iCounter].outerHTML = ytIMGouter(tmp[1]);
					return true;
				};
				if (currentWin._elems[currentWin._iCounter].clientWidth > 80 && currentWin._elems[currentWin._iCounter].clientHeight > 40 && handlWin(currentWin._elems[currentWin._iCounter].contentWindow))
					return true;
			}
		};

		elem = currentWin.document.getElementsByTagName('object');
		currLoc = currentWin.location;
		if (elem.length == 0) {
			elem = currentWin.document.getElementsByTagName('embed')
		};
		if (elem.length > 0) {
			for (i = 0; i < elem.length; i++) {
				if (elem[i].innerHTML.indexOf('youtu') != -1 && (tmp = elem[i].innerHTML.match(YoutubeID)) && tmp[1].length == 11) {

					if (SEND('https://www.youtube.com/watch?v=' + tmp[1])) return;

					elem[i].outerHTML = ytIMGouter(tmp[1]);
					return true;
				} else {
					if (elem[i].clientWidth > 80 && elem[i].clientHeight > 40) {
						if (((tmp = getSrc(elem[i].parentNode, currLoc)) || (tmp = getLink(elem[i], currLoc))) && tmp.length > 2) {

							if (SEND(tmp)) return;

							elem[i].outerHTML = innerA + 'background-color:black!important;bottom:20px!important;">&nbsp;&nbsp;' + videoMoved + '</div>';
							return true;
						};
					};
				}
			};
		};
		return false;
	};

	function restProtHost(lnkR, curLoc) {
		if (lnkR.length == 0)
			return '';
		let tr = lnkR.replace(/^:\/\//, curLoc.protocol + "//");
		if (!tr.match(/^https?:\/\//i)) {
			lnkR = tr.replace(/^\/+/, '');
			if (lnkR.split('/')[0].split('?')[0].split('#')[0].toLowerCase().match(/^(?:[-a-z\d]+\.)+[a-z\d]{2,6}$/)) {
				tr = curLoc.protocol + '//' + lnkR;
			} else {
				tr = curLoc.protocol + '//' + curLoc.host + "/" + lnkR;
			}
		};
		return tr;
	};

	function getSrc(vobj, currentLoc) {
		var t = '',
		tt = '';
		if ((((t = vobj.innerHTML.match(/<video.*?\ssrc=(?:(?:'([^']*)')|(?:"([^"]*)")|([^\s]*))/i)) && (t) && (tt = t[1] || t[2] || t[3]) && tt.indexOf('blob:') == -1) || ((t = vobj.innerHTML.match(/<source.*?\ssrc=(?:(?:'([^']*)')|(?:"([^"]*)")|([^\s]*)).*?\stype=['"]?video\//i)) && (t) && (tt = t[1] || t[2] || t[3]))) && tt.length > 2 && tt.indexOf('blob:') == -1) {
			if (tt.indexOf(".mp4/?") == -1) {
				tt = tt.replace(/&amp;/g, "&")
			};
			t = restProtHost(tt, currentLoc);
			return t;
		};
		return '';
	};

	function getLink(obj, curLocation) {

		if (!obj || !obj.tagName)
			return '';
		q = obj.tagName.toLowerCase();

		var getParam = function (e, n) {
			var v = '',
			r = new RegExp('^(' + n + ')$', 'i'),
			param = e.getElementsByTagName('param');
			for (var igp = 0, p; p = param[igp]; igp++) {
				if (p.hasAttribute('name') && p.getAttribute('name').match(r)) {
					v = p.getAttribute('value');
					break
				};
			};
			return v;
		};

		var restPath = function (f, s) {
			return (f.substring(0, 4) == 'http') ? f : s.replace(/[#?].*$/, '').replace(/[^\/]*$/, f)
		};

		function videoLinkExtract(fl) {
			alert(fl);
			var linkArr = [],
			outLinks = [],
			jj = 0,
			lba = '',
			lbb = '',
			decodeURL = gBrowser.currentURI.spec; {
				try {
					return decodeURIComponent(s)
				} catch (e) {
					return unescape(s)
				}
			};

			for (var ij = 0; ij < 3; ij++) {
				lba = lba + String.fromCharCode(parseInt((Math.random() * 15 + 1) + '', 10));
				lbb = lbb + String.fromCharCode(parseInt((Math.random() * 15 + 16) + '', 10));
			};

			function pushWithMerit(lnk) {

				var merit = -11;
				if (lnk.match(/^https?:\/\//i))
					merit = merit + 40;
				if (outLinks.length == 0)
					merit = merit + 1;
				if (lnk.match(/^\//))
					merit = merit + 7;
				if (lnk.match(/^\/\//))
					merit = merit + 30;
				if (lnk.match(/240p([^a-z]|$)/i))
					merit = merit + 1;
				if (lnk.match(/[^a-z]240([^a-z0-9]|$)/i))
					merit = merit + 1;
				if (lnk.match(/360p([^a-z]|$)/i))
					merit = merit + 3;
				if (lnk.match(/[^a-z]360([^a-z0-9]|$)/i))
					merit = merit + 3;
				if (lnk.match(/480p([^a-z]|$)/i))
					merit = merit + 5;
				if (lnk.match(/[^a-z]480([^a-z0-9]|$)/i))
					merit = merit + 5;
				if (lnk.match(/720p([^a-z]|$)/i))
					merit = merit + 7;
				if (lnk.match(/[^a-z]720([^a-z0-9]|$)/i))
					merit = merit + 7;
				if (lnk.match(/\.mp4([^a-z]|$)/i))
					merit = merit + 8;
				if (lnk.match(/_hd([^a-z]|$)/i))
					merit = merit + 6;
				if (lnk.match(/\.(jpg|xml)([^a-z]|$)/i))
					merit = merit - 40;
				if (merit > 0)
					outLinks.push(merit + lba + lnk);
				Services.console.logStringMessage('merit:' + merit + ' lnk->' + lnk);
			};

			linkArr.push(fl);
			while (linkArr.length > jj && jj < 30) {

				var testPaths = [];
				testPaths = linkArr[jj].split(/(\.(?:flv|mp4|m3u8))/i);
				if (testPaths[testPaths.length - 1] == '')
					testPaths.pop();

				for (k = 1; k < testPaths.length; k = k + 2) {

					if (testPaths[k - 1].indexOf(lba) > -1) {
						pref = testPaths[k - 1];
					} else {
						var testAboutDom = testPaths[k - 1].toLowerCase().split(/(https?:\/\/)/);
						if (testAboutDom[testAboutDom.length - 1] == '')
							testAboutDom.pop();
						var pTest = testAboutDom[testAboutDom.length - 1].split(/(\?[^\?]*?&)/);
						if (pTest.length > 2) {
							pTest.pop();
							pTest.pop();
						};
						testAboutDom[testAboutDom.length - 1] = pTest.join('');
						pref = testPaths[k - 1].substring(testAboutDom.join('').lastIndexOf("&") + 1);
					};

					t2 = pref.lastIndexOf(lbb);
					if (t2 > -1) {
						pref = pref.substring(t2 + 3);
					} else {

						t2 = pref.lastIndexOf('{"');
						if (t2 > -1)
							pref = pref.substring(t2 + 2);
						t2 = pref.lastIndexOf('["');
						if (t2 > -1)
							pref = pref.substring(t2 + 2);
						t2 = pref.lastIndexOf(',"');
						if (t2 > -1)
							pref = pref.substring(t2 + 2);
						t2 = pref.toLowerCase().lastIndexOf('"http://');
						if (t2 > -1)
							pref = pref.substring(t2 + 1);
						t2 = pref.toLowerCase().lastIndexOf('"https://');
						if (t2 > -1)
							pref = pref.substring(t2 + 1);
						t2 = pref.toLowerCase().lastIndexOf(',http://');
						if (t2 > -1)
							pref = pref.substring(t2 + 1);
						t2 = pref.toLowerCase().lastIndexOf(',https://');
						if (t2 > -1)
							pref = pref.substring(t2 + 1);
						t2 = pref.toLowerCase().lastIndexOf(';http');
						if (t2 > -1)
							pref = pref.substring(t2 + 1);
						t2 = pref.toLowerCase().lastIndexOf('*https://');
						if (t2 > -1)
							pref = pref.substring(t2 + 1);
						t2 = pref.toLowerCase().lastIndexOf(' or ');
						if (t2 > -1)
							pref = pref.substring(t2 + 4);

						pref = pref.substring(pref.split('/')[0].toLowerCase().split('%2f')[0].lastIndexOf('=') + 1);

					}

					if (pref.length > 0) {

						if (pref.split('?')[0].toLowerCase().match(/%[2-3][0-9a-f]/)) {

							t2 = pref.indexOf('"')
								if (t2 > -1)
									pref = pref.substring(t2 + 1);
								suff = testPaths[k + 1] ? testPaths[k + 1].split('&')[0].split('"')[0].split(';')[0].split(/,http/i)[0] : '';
							if ((suff != testPaths[k + 1]) || (testPaths.length < k + 3)) {
								if (testPaths.length > k + 1) {
									testPaths[k + 1] = ((pref == testPaths[k - 1]) ? '' : '&') + testPaths[k + 1].substr(suff.length)
								};
								t2 = pref.lastIndexOf(lba);
								if (t2 > -1)
									pref = pref.substring(t2 + 3)
										linkArr.push(decodeURL(pref + testPaths[k] + suff));

							} else {
								testPaths[k + 1] = (pref == testPaths[k - 1] ? '' : lbb) + pref + testPaths[k] + suff
							}
						} else {
							suff = testPaths[k + 1] ? testPaths[k + 1].split(';')[0].split('"]')[0].split('"}')[0].split('",')[0].split(/,https?:\/\//i)[0].split('*https://')[0].split(' or ')[0] : '';
							t2 = suff.indexOf('&');
							if ((t2 > -1) && (pref != testPaths[k - 1])) {
								if (t2 == 0)
									suff = '';
								if (suff.charAt(0) != '?')
									suff = suff.split(/(&[^&]+=https?:\/\/)/i)[0];
							};
							if ((suff != testPaths[k + 1]) || (testPaths.length < k + 3)) {
								if (testPaths.length > k + 1) {
									testPaths[k + 1] = ((pref == testPaths[k - 1]) ? '' : '&') + testPaths[k + 1].substr(suff.length)
								};
								t2 = pref.lastIndexOf(lba);
								if (t2 > -1)
									pref = pref.substring(t2 + 3);
								pushWithMerit(pref + testPaths[k] + suff);

							} else {
								testPaths[k + 1] = lba + (pref == testPaths[k - 1] ? '' : lbb) + pref + testPaths[k] + suff
							}
						}
					}
				};
				jj = jj + 1;
			};

			if (outLinks.length == 0)
				return '';
			function srt(a, b) {
				a = parseInt(a.substr(0, a.indexOf(lba)), 10);
				b = parseInt(b.substr(0, b.indexOf(lba)), 10);
				if (a < b)
					return 1;
				if (a > b)
					return -1;
				return 0
			};
			outLinks.sort(srt);
			outLinks[0] = outLinks[0].substr(outLinks[0].indexOf(lba) + 3)
				if (outLinks[0].indexOf('_hq.mp4/?time=') > 0)
					outLinks[0] = outLinks[0].replace(/&/g, '&amp;');
				return outLinks[0];
		};

		if (!ol)
			return '';
		//ol = ol.replace(/^:?\/\//, curLocation.protocol + "//");
		//return restPath(ol, src);
		return restProtHost(ol, curLocation);
	};

	try {handlWin(content);} finally {found || SEND();}
});

 var style = custombutton.buttonGetHelp(self).replace(/id/g, _id);
var uri = makeURI('data:text/css,'+ encodeURIComponent(style));
var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
sss.loadAndRegisterSheet(uri, 0);

Отсутствует

 

№1733530-04-2025 16:30:10

leex
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 331
UA: Firefox 138.0

Re: Custom Buttons

Andrey_Krropotkin
xrun1

xrun1 пишет

Большое спасибо, бразы!
В момент установки сразу же появились кнопочки))))


:beer::beer::beer::beer::beer::beer:

Отредактировано leex (30-04-2025 16:37:23)

Отсутствует

 

№1733630-04-2025 18:46:24

leex
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 331
UA: Firefox 138.0

Re: Custom Buttons

Dumby пишет
leex пишет

А можно ли попросить убрать из контекстного меню фразу
"добавить краткое имя для данного поиска"

Убрать — нет, а скрыть — вписал, надеюсь.

скрытый текст

Выделить код

Код:

(popup => {
	var id = "cswem-menugroup";
	var mid = "_5dd73bb9-e728-4d1e-990b-c77d8e03670f_-menuitem-_root_menu";
	var css = `
		#${id} {
			padding-left: 30px;

			display: grid;
			grid-template-columns: repeat(auto-fill, 32px);
			grid-auto-rows: 26px;
		}
		#${id} > menuitem {
			-moz-box-pack: center;
		}
		#${mid},
		#${id}:empty,
		#context-searchselect,
		#context-keywordfield,
		#${id} > menuitem > :not(.menu-iconic-left) {
			display: none;
		}
		/*
		#${id} > menuitem > .menu-iconic-left > .menu-iconic-icon {
			margin-inline: 2px -3px;
		}
		*/

	`.replace(/;$/gm, " !important;");
	var url = "data:text/css," + encodeURIComponent(css), type = windowUtils.USER_SHEET;
	windowUtils.loadSheetUsingURIString(url, type);

	var menugroup = document.createXULElement("menugroup");
	menugroup.id = id;
	menugroup.hidden = true;

	var find = info => info.type == "command";
	menugroup.setAttribute("oncommand", "handleCommand(event);");
	menugroup.handleCommand = e => {
		var target = e.target.linkedMenuitem;
		var {button, ctrlKey, shiftKey, altKey, metaKey} = e;
		Services.els.getListenerInfoFor(target).find(find).listenerObject({
			button, target, currentTarget: target,
			ctrlKey, shiftKey, altKey, metaKey
		});
	}
	document.getElementById("context-searchselect").after(menugroup);

	var kwf = document.getElementById("context-keywordfield");
	Object.defineProperty(kwf, "hidden", {
		configurable: true, enumerable: true, get: () => true, set() {}
	});
	addEventListener("popuphidden", e => {
		if (menugroup.firstChild && e.target == popup) menugroup.textContent = "";
	}, false, popup);

	var fillMenugroup = menu => {
		var p = menu.querySelector(":scope > menupopup");
		for(var menuitem of menu.getElementsByTagName("menuitem")) {

			var m = document.createXULElement("menuitem");
			m.className = "menuitem-iconic";
			m.setAttribute("image", menuitem.getAttribute("image"));

			var tt = menuitem.getAttribute("label");
			if (menuitem.closest("menupopup") != p)
				tt = menuitem.closest("menu").getAttribute("label") + "\n" + tt;
			m.setAttribute("tooltiptext", tt);

			m.linkedMenuitem = menuitem;
			menugroup.append(m);
		}
		var lc = menugroup.lastChild;
		menugroup.style.setProperty(
			"height", (lc.screenY + lc.scrollHeight - menugroup.screenY) + "px", "important"
		);
	}
	var mo = new MutationObserver(muts => {
		for(var mut of muts) for(var node of mut.addedNodes) 
			if (node.id == mid) return fillMenugroup(node);
	});
	mo.observe(popup, {childList: true});
	addDestructor(() => {
		mo.disconnect();
		delete kwf.hidden;
		menugroup.remove();
		windowUtils.removeSheetUsingURIString(url, type);
	});
})(document.getElementById("contentAreaContextMenu"));


Приветствую, Dumby.

Обновился ФФ и обновил СB.
В первый раз за пять лет сломалась кнопка.
Иконки в контекстном меню есть, но они стали некликабельными.
image.jpg

Могу ли попросить помочь?

Если что, вот сам аддон ContextSearch-web-ext.

Отредактировано leex (30-04-2025 18:51:45)

Отсутствует

 

№1733701-05-2025 06:14:14

leex
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 331
UA: Firefox 138.0

Re: Custom Buttons

green25 пишет

Dumby
Опять косяк  133 не открывается поиск...Жму на иконки - и никак.

СНЯТ вопрос ...сделал , а вот со вторым как быть..

Приветствую!
Как сделал?
Похоже что у меня, в коде, где кнопки поиска стали некликабельными, ровно та же проблема.

Отредактировано leex (02-05-2025 00:27:06)

Отсутствует

 

№1733801-05-2025 11:19:31

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 57
UA: Chrome 135.0

Re: Custom Buttons

leex
верхний  omni.ja  в rar . ищи нижние коды и заменить на верхние..кеш запуска очистить. Можно скриптом попробовать, но это к Dumby , тем более у меня 133, дальше скрипты кастомные не работают...
https://www.upload.ee/files/18041107/______WinRAR.rar.html

Отредактировано green25 (01-05-2025 11:21:12)

Отсутствует

 

№1733901-05-2025 16:36:49

Ki_rrrilll
Участник
 
Группа: Members
Зарегистрирован: 22-11-2013
Сообщений: 136
UA: Firefox 115.0

Re: Custom Buttons

Перефразирую свой вопрос.
Как открыть папку с закладкой по имени этой закладки или по имени папки?

Отсутствует

 

№1734001-05-2025 22:48:20

yup
Участник
 
Группа: Members
Зарегистрирован: 15-04-2016
Сообщений: 1154
UA: Seamonkey 2.49

Re: Custom Buttons

Ki_rrrilll пишет

Как открыть папку с закладкой по имени этой закладки или по имени папки?

Задача не столь проста, как кажется. Советую найти старое расширение "Go Parent Folder" и посмотреть, как оно работает.


Главная проблема в том, что скрипту доступны только те элементы дерева закладок, которые видны в данный момент. Если нужные закладка или папка находятся внутри  свёрнутой папки, до них так просто не добраться.

Отредактировано yup (02-05-2025 00:02:23)

Отсутствует

 

№1734101-05-2025 23:57:24

leex
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 331
UA: Firefox 138.0

Re: Custom Buttons

green25 пишет

верхний  omni.ja  в rar . ищи нижние коды и заменить на верхние..кеш запуска очистить

Честно говоря, ничего не понял...

В файле .rar скаченного по ссылке лежат только два файла autocomplete-popup.js и SearchOneOffs.sys.mjs. Как ими пользоваться?
Но Оmni.ja там нету.

Отредактировано leex (01-05-2025 23:59:59)

Отсутствует

 

№1734202-05-2025 02:31:38

leex
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 331
UA: Firefox 138.0

Re: Custom Buttons

green25
Если что извини, я видимо неправильно сформулировал вопрос.
Видимо ты даешь полный расклад как решил свои задачи и показываешь пример.
Но похоже у меня сильно другое.
Хоть я ничего не понимаю. Всё равно, спасибо тебе за отзывчивость.

Отсутствует

 

№1734302-05-2025 05:56:19

vitalii201
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 683
UA: Firefox 138.0

Re: Custom Buttons

В [firefox] 138.0.1 (138.0.0 (?)) перестал работать код в "Инициализация":

Открывать новую вкладку справа от текущей

Выделить код

Код:

// Открывать любую новую вкладку справа от текущей, от 25.11.2015. ................................ 
addEventListener("TabOpen", (e)=> {
   var newTab = e.target,
       tabpos = gBrowser.selectedTab._tPos,
       err = new Error().stack; 

   // если восстановление сессии или открыть всё во вкладках из папки закладок 
   if ( /ssi_restore|openContainer/.test(err) ) return;  

   /undoCloseTab/.test(err)
   ? setTimeout(()=> gBrowser.moveTabTo(newTab, tabpos + 1), 50) // если восстановление вкладки
   : gBrowser.moveTabTo(newTab, tabpos + 1);

}, false, gBrowser.tabContainer );


Как исправить?

Отсутствует

 

№1734402-05-2025 10:30:06

xrun1
Участник
 
Группа: Members
Зарегистрирован: 12-12-2013
Сообщений: 1291
UA: Firefox 138.0

Re: Custom Buttons

Ki_rrrilll
Для CB не подскажу. В UCF работает скрипт, аналогичный упоминавшемуся yup старому расширению "Go Parent Folder".

Отсутствует

 

№1734502-05-2025 11:43:19

yup
Участник
 
Группа: Members
Зарегистрирован: 15-04-2016
Сообщений: 1154
UA: Seamonkey 2.49

Re: Custom Buttons

xrun1 пишет

В UCF работает скрипт, аналогичный упоминавшемуся yup старому расширению "Go Parent Folder".

Кстати, ссылка ведёт на репозиторий как раз того человека, который является автором расширения "Go Parent Folder". Может, там в каком-то из этих номерных каталогов и само "Go Parent Folder", перепиленное под userChrome.js, лежит.
Но сам поиск методом рекурсивного прохода по дереву каталогов у них, насколько я понимаю, идентичен.


Однако я бы делал по-другому - сначала средствами Bookmarks API найти все элементы, имеющие нужное имя или URL, из них по GUID выбрать нужный элемент, потом цепочкой запросов построить его каталог, а потом уже целенаправленно открыть именно этот каталог.


Но есть и третий способ - работать не через Bookmarks API, а самому отправлять SQL-запросы в places.sqlite.

Отредактировано yup (02-05-2025 12:01:17)

Отсутствует

 

№1734602-05-2025 18:48:55

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1812
UA: Firefox 128.0

Re: Custom Buttons

Есть
https://github.com/alice0775/userChrome.js/blob/master/136/showParentFolder.uc.js
добавляет столбец в библиотеке.
   
В 140 есть собственный пункт меню "Показать в папке", появляется при поиске и в сайдбаре и в библиотеке. Не уследил когда он появился.
   
Может полезно будет.

Отсутствует

 

№1734702-05-2025 19:56:29

yup
Участник
 
Группа: Members
Зарегистрирован: 15-04-2016
Сообщений: 1154
UA: Seamonkey 2.49

Re: Custom Buttons

_zt пишет

Есть
https://github.com/alice0775/userChrome.js/blob/master/136/showParentFolder.uc.js
добавляет столбец в библиотеке.

"Show Parent Folder" это ещё одно расширение того же автора. Но показать в дополнительном столбце имя родительской папки закладки - задача попроще, потому что в этот момент и сама закладка видна, и папку, которая вполне может находиться внутри другой закрытой папки, на белый свет вытаскивать не нужно.


Поэтому для задачи, которую Ki_rrrilll сформулировал, скрипт, лежащий по ссылке, приведённой xrun1, в качестве образца и "источника вдохновения" изрядно полезнее будет.


Я совсем недавно сделал расширение, добавляющее в Закладки и Журнал посещений столбец с нужной мне информацией, и за время работы над ним с Bookmarks и History API наигрался преизрядно.
Если нужно, ссылки на страницы с документацией дам.

Отредактировано yup (02-05-2025 20:03:21)

Отсутствует

 

№1734802-05-2025 20:21:55

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1812
UA: Firefox 128.0

Re: Custom Buttons

yup
Я не осилю, ваш пост видел и хотелось бы такой скрипт для [firefox] 140+, для ucf или userChrome.js.
Расширение не позволит добавить столбец в современных [firefox] .

Отсутствует

 

№1734902-05-2025 21:48:15

yup
Участник
 
Группа: Members
Зарегистрирован: 15-04-2016
Сообщений: 1154
UA: Seamonkey 2.49

Re: Custom Buttons

Там вся сложность была в том, что в окне с одним типом данных (Журнал посещений) нужно было показывать данные, относящиеся к другому типу (Закладки). А штатные возможности API такого не предусматривают.
Через SQL это делается очень легко, но мне хотелось, чтобы результаты были "живыми" - при изменении чего-то в закладках или просто при посещении страниц изменения в окне хотелось получать автоматически, без каких-либо действий со стороны пользователя, а этого не позволяет SQL-ный API.


Возможных вариантов решения было довольно много, и я последовательно перебирал их в поисках оптимального (наименее прожорливого). Итоговый вариант оказался на удивление простым - чуть ли не в одну строку. Но пока я к нему пришёл, хорошо изучил все API и набил кучу шишек об их ограничения.


А у Ki_rrrilll всё в пределах одного типа (закладки), и там единственная сложность при создании - понять, что из себя представляет результат запроса к Places и как доступаться к его элементам. А для этого всё, что нужно - пара-тройка страниц документации и текст скрипта по ссылке от xrun1 как пример.


_zt пишет

Расширение не позволит добавить столбец в современных [firefox] .

Расширение - да. Но мы же в теме Custom Buttons, а CB это, по сути, один из альтернативных способов запуска старого кода.

Отредактировано yup (02-05-2025 21:53:14)

Отсутствует

 

№1735003-05-2025 19:12:11

leex
Участник
 
Группа: Members
Зарегистрирован: 24-03-2011
Сообщений: 331
UA: Firefox 137.0

Re: Custom Buttons

Парни, прикиньте, ИИ все переписал под 138.
Все работает. Была путаница у него с расположением кнопок, и показывал кнопки от других расширений в том же блоке.
Еще дублировал дважды. Я ему писал что надо исправить и с третей попытки он все переписал.

Я мягкоговоря "вафиге"

скрытый текст

Выделить код

Код:

(async function() {
    try {
        // Стиль (оставляем без изменений)
        const style = document.createElement('style');
        style.id = 'cswem-styles';
        style.textContent = `
            #cswem-menugroup {
                padding-left: 30px;
                display: grid;
                grid-template-columns: repeat(auto-fill, 32px);
                grid-auto-rows: 26px;
                gap: 2px;
            }
            #cswem-menugroup > menuitem {
                -moz-box-pack: center;
                list-style-image: var(--image) !important;
            }
            #context-searchselect,
            #context-keywordfield {
                display: none !important;
            }
        `;
        document.head.appendChild(style);

        // Контейнер (теперь в правильном месте)
        const menugroup = document.createXULElement('menugroup');
        menugroup.id = 'cswem-menugroup';
        
        // ★ Важное изменение: вставляем ПОСЛЕ поискового блока ★
        const searchSelect = document.getElementById('context-searchselect');
        if (searchSelect) {
            searchSelect.after(menugroup);
        } else {
            // Фолбек на случай изменений в Firefox
            document.getElementById('contentAreaContextMenu').appendChild(menugroup);
        }

        // Фильтрация (без изменений)
        const isNativeItem = (item) => {
            const forbiddenClasses = ['menuitem-iconic-webext', 'extension-menu-item'];
            const forbiddenIds = ['ublock', 'adguard', 'grammarly', 'ext-', 'extension-'];
            
            return (
                item.tagName === 'menuitem' &&
                !item.hidden &&
                item.getAttribute('image') &&
                item.getAttribute('label') &&
                !item.id?.includes('_separator') &&
                !forbiddenClasses.some(c => item.classList.contains(c)) &&
                !forbiddenIds.some(id => item.id?.includes(id))
            );
        };

        // Обновление меню (без изменений)
        const refreshMenu = () => {
            menugroup.innerHTML = '';
            const validItems = Array.from(document.querySelectorAll('#contentAreaContextMenu menuitem'))
                                .filter(isNativeItem);
            
            validItems.forEach(item => {
                const clone = item.cloneNode(true);
                clone.id = '';
                clone.addEventListener('command', () => item.click());
                menugroup.appendChild(clone);
            });
            
            menugroup.hidden = menugroup.children.length === 0;
        };

        const contextMenu = document.getElementById('contentAreaContextMenu');
        contextMenu.addEventListener('popupshowing', refreshMenu);

        window.addEventListener('unload', () => {
            contextMenu.removeEventListener('popupshowing', refreshMenu);
            menugroup.remove();
            style.remove();
        });

    } catch (error) {
        console.error('Custom Buttons Error:', error);
    }
})();

Отредактировано leex (03-05-2025 19:12:55)

Отсутствует

 

Board footer

Powered by PunBB
Modified by Mozilla Russia
Copyright © 2004–2020 Mozilla Russia GitHub mark
Язык отображения форума: [Русский] [English]