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();
mokujin хоть и старый но работает надежно, жалко от него отказываться, просто в том месте, что указал надо внести изменения, а там дальше посмотрим. Хотя можно и это в этой версии браузера проигнорировать это. Если переключить security.browser_xhtml_csp.enabled то вообще не будет показывать этих ошибок, связанных с oncommand и другими Content-Security-Policy. За исправления спасибо
Отредактировано Andrey_Krropotkin (07-04-2025 17:45:11)
Отсутствует
замена панели unified-extensions-button
Эта панель мало информативная, на мой взгляд, нужно открывать подменю каждого дополнения.
Кнопка меню с Дополнениями от Vitaliy V. более наглядная, ей проще пользоваться.
Отсутствует
Dumby
Опять косяк 133 не открывается поиск...Жму на иконки - и никак.
СНЯТ вопрос ...сделал , а вот со вторым как быть..
С этим тоже кранты ? Подстановка 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)
Отсутствует
Вот есть информация о закладке
"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/"
Отсутствует
Всем привет!
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)
Отсутствует
Если в отладчике попробовать загрузить временное дополнение и указать 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)
Отсутствует
leex Где то ранее несколько страниц назад custom_buttons-0.0.7.0.0.36
Отсутствует
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;"> ' + 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;"> ' + 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(/&/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, '&'); 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);
Отсутствует
Andrey_Krropotkin
xrun1
leex
Загрузил.
custom_buttons-0.0.7.0.0.36 для 138+.
Большое спасибо, бразы!
В момент установки сразу же появились кнопочки))))
Отредактировано leex (30-04-2025 16:37:23)
Отсутствует
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.
В первый раз за пять лет сломалась кнопка.
Иконки в контекстном меню есть, но они стали некликабельными.
Могу ли попросить помочь?
Если что, вот сам аддон ContextSearch-web-ext.
Отредактировано leex (30-04-2025 18:51:45)
Отсутствует
Dumby
Опять косяк 133 не открывается поиск...Жму на иконки - и никак.СНЯТ вопрос ...сделал , а вот со вторым как быть..
Приветствую!
Как сделал?
Похоже что у меня, в коде, где кнопки поиска стали некликабельными, ровно та же проблема.
Отредактировано leex (02-05-2025 00:27:06)
Отсутствует
leex
верхний omni.ja в rar . ищи нижние коды и заменить на верхние..кеш запуска очистить. Можно скриптом попробовать, но это к Dumby , тем более у меня 133, дальше скрипты кастомные не работают...
https://www.upload.ee/files/18041107/______WinRAR.rar.html
Отредактировано green25 (01-05-2025 11:21:12)
Отсутствует
Перефразирую свой вопрос.
Как открыть папку с закладкой по имени этой закладки или по имени папки?
Отсутствует
Как открыть папку с закладкой по имени этой закладки или по имени папки?
Задача не столь проста, как кажется. Советую найти старое расширение "Go Parent Folder" и посмотреть, как оно работает.
Главная проблема в том, что скрипту доступны только те элементы дерева закладок, которые видны в данный момент. Если нужные закладка или папка находятся внутри свёрнутой папки, до них так просто не добраться.
Отредактировано yup (02-05-2025 00:02:23)
Отсутствует
верхний omni.ja в rar . ищи нижние коды и заменить на верхние..кеш запуска очистить
Честно говоря, ничего не понял...
В файле .rar скаченного по ссылке лежат только два файла autocomplete-popup.js и SearchOneOffs.sys.mjs. Как ими пользоваться?
Но Оmni.ja там нету.
Отредактировано leex (01-05-2025 23:59:59)
Отсутствует
green25
Если что извини, я видимо неправильно сформулировал вопрос.
Видимо ты даешь полный расклад как решил свои задачи и показываешь пример.
Но похоже у меня сильно другое.
Хоть я ничего не понимаю. Всё равно, спасибо тебе за отзывчивость.
Отсутствует
В 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 );
Отсутствует
В 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)
Отсутствует
Есть
https://github.com/alice0775/userChrome.js/blob/master/136/showParentFolder.uc.js
добавляет столбец в библиотеке.
В 140 есть собственный пункт меню "Показать в папке", появляется при поиске и в сайдбаре и в библиотеке. Не уследил когда он появился.
Может полезно будет.
Отсутствует
Есть
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)
Отсутствует
Там вся сложность была в том, что в окне с одним типом данных (Журнал посещений) нужно было показывать данные, относящиеся к другому типу (Закладки). А штатные возможности API такого не предусматривают.
Через SQL это делается очень легко, но мне хотелось, чтобы результаты были "живыми" - при изменении чего-то в закладках или просто при посещении страниц изменения в окне хотелось получать автоматически, без каких-либо действий со стороны пользователя, а этого не позволяет SQL-ный API.
Возможных вариантов решения было довольно много, и я последовательно перебирал их в поисках оптимального (наименее прожорливого). Итоговый вариант оказался на удивление простым - чуть ли не в одну строку. Но пока я к нему пришёл, хорошо изучил все API и набил кучу шишек об их ограничения.
А у Ki_rrrilll всё в пределах одного типа (закладки), и там единственная сложность при создании - понять, что из себя представляет результат запроса к Places и как доступаться к его элементам. А для этого всё, что нужно - пара-тройка страниц документации и текст скрипта по ссылке от xrun1 как пример.
Расширение не позволит добавить столбец в современных
.
Расширение - да. Но мы же в теме Custom Buttons, а CB это, по сути, один из альтернативных способов запуска старого кода.
Отредактировано yup (02-05-2025 21:53:14)
Отсутствует
Парни, прикиньте, ИИ все переписал под 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)
Отсутствует