UserChromeFiles загрузчик скриптов/стилей расширяет возможности Firefox 115+ через функцию autoconfig. © Vitaliy V.
Установка: скопировать папку chrome в ваш профиль, всё из папки Firefox в путь его установки. В окне UCF включить нужные опции, после изменений нажать в about:support «Очистить кэш запуска».


Демо-профиль обновляемый на github для FF 78+ кнопки, меню, подсказки, стили, клики, hotkeys, жесты: встроена Справка, сайт в боковую панель, создать единый HTML, градиент загрузки, скрыть 1 вкладку, яркость, быстрая закладка или восстановить удалённые, сохранить фото, перевод сайт/текст, скачать видео, режимы Эксперт|Простой, скрытые настройки, режим Прокси цветом ≡ Меню и многое другое… Лёгкость настройки - всё в ucf_hookClicks.js.

возможности Демо-профиля: для справки жмите кнопку «Печать» секунду
Кнопки — расширяемые кликами кнопок мыши и колёсика, сочетания клавиш и жесты:
новые 14 — Журнал и меню опций, История, Закладки, Папки, Пароли, Просмотр части страницы или Запись в Html, Менеджер сессий, Восстановить удалённые закладки или fav-иконки, Ссылки в подсказках, Дополнения, Инспектор атрибутов, UCF…
стандартные 7 — Загрузки, ≡ Меню, Печать, Звёздочка, Щит защиты, Замок, Обновить/Стоп
расширения 2 — SingleFile (сохранение по Alt+Shift+S), Reader View.
User.js скрипт пользователя: запуск по Alt+X, ucf_aom-button.js управление дополнениями
ucf_hookClicks.js глобальные функции, меню, подсказки, перехват нажатий мыши и клавиатуры
ucf_QuickToggle открытие боковой панели, меню скрытых системных настроек
attrsInspector.js Attributes Inspector, auto_hide_sidebar скрытие боковой панели
menubarVisibilityChance, ucf_autohidetabstoolbar.js автоскрытие панели с одной вкладкой
ucf-loads-favicons восстановить иконки закладок
ucf_BookmarkDir быстрая закладка, перевод сайта/текста
ucf_contextsearch поиск в контекстном меню, ucf_findbarclose.js автоскрытие поиска
ucf_SidebarTabs боковая панель для сайта, журнала, загрузок…
ucf_LocationBarEnhancer градиент загрузки страниц
ucf_pauseResume кнопка Пауза для скачиваемых файлов
ucf_downloads_clear кнопка "Очистить загрузки" на DownloadsPanel
ucf_session сохранение | восстановление сессий
ucf_tab-update автообновление вкладок по таймеру
ucf_undo-tab история закрытых вкладок/окон
ucf_win_contextmenuopenwith можно прописать свои приложения открытия ссылок
ucf_win_mousedrag жесты мыши для ссылок и выделенного текста
ucb_SaveHTML.mjs сохранение страниц в единый Html
ClickPicSave.mjs сохранение картинок, в том числе и недоступных для сохранения обычным способом
TitleChanged.mjs авто-коррекция имён (заголовков) вкладок
PageReadMode.user.js выбор части страницы курсорными клавишами после нажатия Alt+R
undoBookmarks.js восстановить удалённые Закладки в панели закладок
Возможности дополняются пользовательскими скриптами, например Vk Media Downloader

    Скрипты для докум. окна браузера [ChromeOnly]
hookClicks — перехват кликов позволяет «разгрузить» кнопки, исключив код кликов
UCF drag and go жесты мыши
Перезапустить приложение в основном и классическом меню
Автоскрытие панели вкладок
Добавление прокси из контекстного меню
Скрытие панели поиска после клика на странице
Фавикон в адресной строке
Информация о странице, вкладка разрешения
Добавить кнопку "Очистить загрузки" на DownloadsPanel
Кнопка "Меню ссылок" (авт. Dumby)l
Контекстное меню "Открыть ссылку в…"
Sidebar Tabs в боковой панели Сайт, Загрузки, Журнал… Lite-mod, поддержка Закладок

    Скрипты В фоне [System Principal]:
Автоматически добавлять выделенный текст в SearchBar (авт. Dumby)
Тултипы с URL (авт. Dumby)
Кнопка Прокси
Замена фавиконок для сайтов
Кнопка «Сохранить» комбинированная, конвертировать в base64
Меню пользователя (обновляемый текст строк)


Другие менеджеры скриптов: fx-autoconfig MrOtherGuy, Tampermonkey, userChrome.js Endor8

Список полезных постов (т.к. тема захламлена)

del

Vitaliy V.

Удаляйте все загрузчики скриптов из custom_script_all_win.js
добавляйте этот

У меня там только

стандартный загрузчик

Выделить код

Код:

load() {
        if (this.initialized)
            return;
        this.initialized = true;
        /* ************************************************ */

        // Здесь может быть ваш код который сработает по событию "load" не раньше

        /* ************************************************ */
    },


Его нужно заменить на тот, что по ссылке?

harryk
Загрузчики можно добавить где комментарий // Здесь может быть ваш код который сработает по событию "load" не раньше
но добавлять необязательно это для тех кто хочет чтобы были отдельные файлы.
Что касается custom_script_all_win.js у вас же там ничего нет значит и добавлять ничего не надо

Т.е. тот загрузчик нужно вставить вместо или рядом с комментарием?

Да


to all
Добавляйте здесь ссылки на скрипты или сами скрипты кто чем пользуется...

Vitaliy V.
Да, у меня там ничего нет. Пока нет, но я над этим работаю :)
Т.е. тот загрузчик нужно вставить вместо или рядом с комментарием?

Vitaliy V.
Огромное Спасибо за обновлённые загрузчики, скрипты и кнопки.
Еще добавлю здесь ссылку на дропмакер в адресной строке urlbarhistorydropmarker.
В 91+ тоже работает исправно, только иконка там поменялась на arrow-down.svg, сменил у себя в [nightly].
Ещё добавлю переработанные по Вашим советам скрипты классического окна загрузок для custom_script_all_win.js и русифицированные к тому же

ucjsDownloadsManager.uc.js

Выделить код

Код:

if (location.href == "chrome://browser/content/browser.xhtml") {
  Cu.import("resource://gre/modules/Services.jsm");

  window.ucjs_downloadManager = {
    _summary: null,
    _list: null,

    createElement: function(localName, arryAttribute) {
      let elm = document.createXULElement(localName);
      for(let i = 0; i < arryAttribute.length; i++) {
        elm.setAttribute(arryAttribute[i].attr, arryAttribute[i].value);
      }
      return elm;
    },

    init: function() {
      window.addEventListener("unload", this, false);

      let ref = document.getElementById("menu_openDownloads");
      let menu = ref.parentNode.insertBefore(
        this.createElement("menuitem",
          [{attr: "label", value:"Менеджер загрузок"},

           {attr : "oncommand", value: "ucjs_downloadManager.openDownloadManager(true);"}
          ]), ref);

      XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                "resource://gre/modules/Downloads.jsm");
      // Ensure that the DownloadSummary object will be created asynchronously.
      if (!this._summary) {
        this.Downloads.getSummary(this.Downloads.ALL).then(summary => {
          this._summary = summary;
          return this._summary.addView(this);
        }).then(null, Cu.reportError);
      }

      if (!this._list) {
        this.Downloads.getList(this.Downloads.ALL).then(list => {
          this._list = list;
          return this._list.addView(this);
        }).then(null, Cu.reportError);
      }
    },

    uninit: function() {
      window.removeEventListener("unload", this, false);

      if (this._summary) {
        this._summary.removeView(this);
      }
      if (this._list) {
        this._list.removeView(this);
      }
    },

    handleEvent: function(event) {
      switch (event.type) {
        case "unload":
          this.uninit();
          break;
      }
    },

    openDownloadManager: function ucjs_openDownloadManager(aForceFocus) {
      var enumerator = Services.wm.getEnumerator(null);
      while(enumerator.hasMoreElements()) {
        var win = enumerator.getNext();
        if (win.location == "chrome://browser/content/downloads/contentAreaDownloadsView.xhtml"
          && PrivateBrowsingUtils.isWindowPrivate(window) ==
             PrivateBrowsingUtils.isWindowPrivate(win)) {
          if (aForceFocus)
            win.focus();
          return;
        }
      }

      try {
        var height = Math.max(100,Services.prefs.getIntPref("browser.download.manager.size.height"));
        var width  = Math.max(300,Services.prefs.getIntPref("browser.download.manager.size.width"));
        var screenX = Math.min(Math.max(0,Services.prefs.getIntPref("browser.download.manager.size.screenX")), screen.availWidth - width);
        var screenY = Math.min(Math.max(0,Services.prefs.getIntPref("browser.download.manager.size.screenY")), screen.availHeight - height);
      } catch(r){
        height = 300;
        width  = 480;
        screenX = 5;
        screenY = 5;
      }
      var win = window.open("chrome://browser/content/downloads/contentAreaDownloadsView.xhtml",
                            "Download" +
                              (PrivateBrowsingUtils.isWindowPrivate(window) ? " - Private Window"
                                                                            : ""),
                            "outerWidth=" + width + ",outerHeight=" + height +
                            ",left=" + screenX + ",top=" + screenY +
                            ",chrome,toolbar=yes,dialog=no,resizable");
    },

    closeDownloadManager: function ucjs_closeDownloadManager() {
      var enumerator = Services.wm.getEnumerator(null);
      while(enumerator.hasMoreElements()) {
        var win = enumerator.getNext();
        if (win.location == "chrome://browser/content/downloads/contentAreaDownloadsView.xhtml") {
          win.close();
          return;
        }
      }
    },

    onDownloadAdded: function (aDownload) {
      var showWhenStarting = true;
      try {
        showWhenStarting = Services.prefs.getBoolPref("browser.download.manager.showWhenStarting");
      } catch(e) {}
      var numDls = 0;
      if (showWhenStarting) {
        if (this._list) {
          this._list.getAll().then(downloads => {
            for (let download of downloads) {
              if (!download.stopped)
                numDls++;
            }
            if (numDls > 0)
              this.openDownloadManager(false);
          }).then(null, Cu.reportError);
        }
      }
    },

    onDownloadChanged: function (aDownload) {
      if (!this._list)
        return;
      this._list.getAll().then(downloads => {
        var num = 0;
        for (let download of downloads) {
          if (!download.succeeded)
            num++;
        }
        if (num == 0) {
          var closeWhenDone = true;
          try {
            closeWhenDone = Services.prefs.getBoolPref("browser.download.manager.closeWhenDone");
          } catch(e) {}
          if (closeWhenDone) {
            this.closeDownloadManager();
          }
        }
      }).then(null, Cu.reportError);
    }
  };
  ucjs_downloadManager.init();
}

ucjsDownloadsManager2.uc.js

Выделить код

Код:

Cu.import("resource://gre/modules/Services.jsm");
  Cu.import("resource://gre/modules/DownloadIntegration.jsm");

  window.ucjs_downloadManagerMain = {
    originalTitle:"",
    _summary: null,
    _list: null,
    _wait:false,

    createElement: function(localName, arryAttribute) {
      let elm = document.createXULElement(localName);
      for(let i = 0; i < arryAttribute.length; i++) {
        elm.setAttribute(arryAttribute[i].attr, arryAttribute[i].value);
      }
      return elm;
    },

    createElementNS: function(NS, localName, arryAttribute) {
      let elm = document.createElementNS(NS, localName);
      for(let i = 0; i < arryAttribute.length; i++) {
        elm.setAttribute(arryAttribute[i].attr, arryAttribute[i].value);
      }
      return elm;
    },

    init: function() {
      window.addEventListener("unload", this, false);

      // xxx remove in-content css
      var elements = document.childNodes;
      for (var i = 0; i <= elements.length; i++) {
        var element = elements[i];
        if (element.nodeValue.indexOf("chrome://browser/skin/downloads/contentAreaDownloadsView.css") > -1) {
          document.removeChild(element);
          break;
        }
      }

      document.getElementById("downloadsListEmptyDescription").setAttribute("flex", "1");
      let ref = document.documentElement;
      ref = ref.appendChild(this.createElement("hbox", []));
      ref.appendChild(this.createElement("button",
        [{attr: "id", value: "ucjs_clearListButton"},
         {attr: "label", value: "Очистить загрузки"},

        ]));
      ref.appendChild(this.createElement("spacer",
        [{attr: "flex", value: "1"}]));
      ref.appendChild(this.createElementNS("http://www.w3.org/1999/xhtml", "input",
        [{attr: "id", value: "ucjs_downloadManagerMain_input"},
         {attr: "clickSelectsAll", value: "true"},
         {attr: "type", value: "search"},
         {attr: "placeholder", value: "Поиск в загрузках"},
         {attr: "aria-autocomplete", value: "list"}
        ]));

        document.getElementById("ucjs_clearListButton").addEventListener("command", function(event) {
            ucjs_downloadManagerMain.clearDownloads();
          });
        document.getElementById("ucjs_downloadManagerMain_input")
                .addEventListener("input", function(event) {
            ucjs_downloadManagerMain.doSearch(event.target.value);
          });

      this.originalTitle = document.title +
                           (PrivateBrowsingUtils.isWindowPrivate(window) ? " - Private Window"
                                                                         : "");

/*
      // xxx Bug 1279329 "Copy Download Link" of context menu in Library is grayed out
      var listBox = document.getElementById("downloadsRichListBox");
      var placesView = listBox._placesView;
      var place = placesView.place;
      placesView.place= null;
      placesView.place = place;
*/

      setTimeout(function(){this._wait = true}.bind(this), 0);

      // Ensure that the DownloadSummary object will be created asynchronously.
      if (!this._summary) {
        Downloads.getSummary(Downloads.ALL).then(summary => {
          this._summary = summary;
          return this._summary.addView(this);
        }).then(null, Cu.reportError);
      }

      if (!this._list) {
        Downloads.getList(Downloads.ALL).then(list => {
          this._list = list;
          return this._list.addView(this);
        }).then(null, Cu.reportError);
      }

      try {
        var showProgressInTaskButton = Services.prefs.getBoolPref("browser.download.manager.showProgressInTaskButton")
      } catch(ex) {
        showProgressInTaskButton = true; //default
      }
      if (showProgressInTaskButton)
        setTimeout(function() {
          try {
            let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIWebNavigation)
                                  .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
                                  .QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIXULWindow).docShell;
            let gWinTaskbar = Components.classes["@mozilla.org/windows-taskbar;1"]
                                      .getService(Components.interfaces.nsIWinTaskbar);
            this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell);
          } catch(ex) {
            this._taskbarProgress = null;
          }
        }.bind(this), 10);
    },

    uninit: function() {
      window.removeEventListener("unload", this, false);

      this._taskbarProgress = null;
      if (this._wait)
        this.saveSizePosition();

      if (this._summary) {
        this._summary.removeView(this);
      }
      if (this._list) {
        this._list.removeView(this);
      }
    },

    handleEvent: function(event) {
      switch (event.type) {
        case "unload":
          this.uninit();
          break;
      }
    },

    saveSizePosition: function() {
      if (window.windowState == 3) {
        Services.prefs.setIntPref("browser.download.manager.size.height", window.outerHeight);
        Services.prefs.setIntPref("browser.download.manager.size.width", window.outerWidth);
        Services.prefs.setIntPref("browser.download.manager.size.screenX", window.screenX);
        Services.prefs.setIntPref("browser.download.manager.size.screenY", window.screenY);
      }
    },

    onSummaryChanged: function () {
      if (!this._summary)
        return;
      if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) {
        document.title = this.originalTitle;
        if (this._taskbarProgress) {
          this._taskbarProgress.setProgressState(
                                     Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0);
        }
        Cu.import("resource://gre/modules/Services.jsm");
        var enumerator = Services.wm.getEnumerator("navigator:browser");
        while(enumerator.hasMoreElements()) {
          return;
        }

        var closeWhenDone = true;
        try {
          closeWhenDone = Services.prefs.getBoolPref("browser.download.manager.closeWhenDone");
        } catch(e) {}
        if (closeWhenDone) {
          DownloadIntegration._store.save();
          window.close();
        }

      } else {

        // If the last browser window has been closed, we have no indicator any more.
        if (this._taskbarProgress) {
          if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) {
            this._taskbarProgress.setProgressState(
                                     Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0);
          } else {
            // For a brief moment before completion, some download components may
            // report more transferred bytes than the total number of bytes.  Thus,
            // ensure that we never break the expectations of the progress indicator.
            let progressCurrentBytes = Math.min(this._summary.progressTotalBytes,
                                                this._summary.progressCurrentBytes);
            this._taskbarProgress.setProgressState(
                                     Ci.nsITaskbarProgress.STATE_NORMAL,
                                     progressCurrentBytes,
                                     this._summary.progressTotalBytes);
          }
        }

        // Update window title
        var numDls = 0;
        if (!this._list)
          return;
        this._list.getAll().then(downloads => {
          for (let download of downloads) {
            if (download.hasProgress && !download.succeeded)
              numDls++;
          }

          let progressCurrentBytes = Math.min(this._summary.progressTotalBytes,
                                            this._summary.progressCurrentBytes);
          let percent = Math.floor(progressCurrentBytes / this._summary.progressTotalBytes * 100);
          let text = percent + "% из " + numDls + (numDls < 2 ? " файла - " : " файлов - ") ;
          document.title = text + this.originalTitle;
        }).then(null, Cu.reportError);
      }
    },

    clearDownloads: function ucjs_clearDownloads() {
      var DO_NOT_DELETE_HISTORY = true; /* custmizable true or false */
      var richListBox = document.getElementById("downloadsRichListBox");

      var places = [];
      function addPlace(aURI, aTitle, aVisitDate) {
        places.push({
          uri: aURI,
          title: aTitle,
          visits: [{
            visitDate: (aVisitDate || Date.now()) * 1000,
            transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
          }]
        });
      }
      function moveDownloads2History(d) {
        if (DO_NOT_DELETE_HISTORY &&
            !PrivateBrowsingUtils.isWindowPrivate(window)) {
          for (let element of richListBox.childNodes) {
            let download = element._shell.download;
            let aURI = makeURI(download.source.url);
            // let aTitle = document.getAnonymousElementByAttribute(element, "class", "downloadTarget").value
            let aTitle = download.target.path;
            aTitle = aTitle.match( /[^\\]+$/i )[0];
            aTitle = aTitle.match( /[^/]+$/i )[0];

            let aVisitDate = download.endTime || download.startTime;
            addPlace(aURI, aTitle, aVisitDate)
          }
        }

        // Clear List
        richListBox._placesView.doCommand('downloadsCmd_clearDownloads');

        if (DO_NOT_DELETE_HISTORY &&
            !PrivateBrowsingUtils.isWindowPrivate(window)) {
          if (places.length > 0) {
            var asyncHistory = Components.classes["@mozilla.org/browser/history;1"]
                     .getService(Components.interfaces.mozIAsyncHistory);
              asyncHistory.updatePlaces(places);
          }
        }
      }
      var btn = document.getElementById("ucjs_clearListButton");
      moveDownloads2History(0);
    },

    doSearch: function ucjs_doSearch(filterString) {
      var richListBox = document.getElementById("downloadsRichListBox");
      richListBox._placesView.searchTerm = filterString;
    }
  };
  ucjs_downloadManagerMain.init();

Added: Скрипт со стилием и иконками для добавления кнопки паузы в окна загрузок.
Небольшие косметические улучшения
Image_001.png

Выделить код

Код:

@-moz-document url(chrome://browser/content/downloads/contentAreaDownloadsView.xhtml) {
#downloadsListEmptyDescription {
    margin: 0 !important;
    padding: 1px 0 0 6px !important;
    background-color: white !important;
}
#contentAreaDownloadsView > hbox {
    background-color: -moz-Dialog !important;
    padding-top: 2px !important;
    border: 1px solid gainsboro !important;	
}
#ucjs_clearListButton {
    margin: 1px 2px 2px !important;
}
}

Скрипты авторства Dumby, которые использую:
Переключение вкладок наведением указателя на вкладку, и там же, как бы костыль, добавления закладки звёздочкой в адресной строке в меню закладок, а через меню на странице - в "Другие закладки" https://forum.mozilla-russia.org/viewto … 27#p786627
Действие двойного клика на панели вкладок - открыть новую вкладку вместо развернуть окно https://forum.mozilla-russia.org/viewto … 49#p782149
Кнопка "Закрыть другие вкладки" https://forum.mozilla-russia.org/viewto … 81#p788681
Скрипт возврата в адресную строку значка "Копировать ссылку" https://forum.mozilla-russia.org/viewto … 96#p790496
Скрипт возврата пункта "Информация о странице" https://forum.mozilla-russia.org/viewto … 92#p792092
Другой вариант возврата пункта "Информация о странице" с открытием вкладки "Разрешения", код в самом низу поста https://forum.mozilla-russia.org/viewto … 04#p792104
Ещё кнопка авторства Vitaliy V. вкл/откл звука на вкладке/вкладках https://forum.mozilla-russia.org/viewto … 10#p787710, тоже сильно выручает.
Через файл user_chrome.manifest комплекта user_chrome_files можно легко поменять значки интерфейса и так же на служебных страницах, что я и сделал в 89+, использовав значки из [firefox] 78 и свои кое-где

user_chrome.manifest firefox 90
Image_001.png

Выделить код

Код:

content user_chrome_files ./
# Не уверены, не редактируйте этот файл!

override chrome://global/skin/icons/defaultFavicon.svg chrome://user_chrome_files/content/custom_styles/svg/defaultFavicon.svg

override chrome://branding/content/identity-icons-brand.svg chrome://user_chrome_files/content/custom_styles/svg/defaultFavicon.svg

override chrome://mozapps/skin/extensions/extension.svg chrome://user_chrome_files/content/custom_styles/svg/extension.svg

override chrome://global/skin/icons/settings.svg chrome://user_chrome_files/content/custom_styles/svg/settings.svg

override chrome://mozapps/skin/extensions/extensionGeneric.svg chrome://user_chrome_files/content/custom_styles/svg/extension.svg

override chrome://global/skin/icons/help.svg chrome://user_chrome_files/content/custom_styles/svg/help.svg

override chrome://global/skin/icons/plugin.svg chrome://user_chrome_files/content/custom_styles/svg/pluginGeneric.svg

override chrome://mozapps/skin/extensions/category-discover.svg chrome://user_chrome_files/content/custom_styles/svg/category-discover.svg

override chrome://mozapps/skin/extensions/category-extensions.svg chrome://user_chrome_files/content/custom_styles/svg/category-extensions.svg

override chrome://mozapps/skin/extensions/category-themes.svg chrome://user_chrome_files/content/custom_styles/svg/category-themes.svg

override chrome://mozapps/skin/extensions/category-plugins.svg chrome://user_chrome_files/content/custom_styles/svg/category-plugins.svg

override chrome://browser/skin/preferences/category-general.svg chrome://user_chrome_files/content/custom_styles/svg/settings.svg

override chrome://browser/skin/home.svg chrome://user_chrome_files/content/custom_styles/svg/home.svg

override chrome://browser/skin/preferences/category-search.svg chrome://user_chrome_files/content/custom_styles/svg/category-search.svg

override chrome://browser/skin/preferences/category-privacy-security.svg chrome://user_chrome_files/content/custom_styles/svg/category-privacy-security.svg

override chrome://browser/skin/preferences/category-experiments.svg chrome://user_chrome_files/content/custom_styles/svg/category-experiments.svg

override chrome://global/skin/icons/more.svg chrome://user_chrome_files/content/custom_styles/svg/more.svg

override chrome://global/skin/icons/folder.svg chrome://user_chrome_files/content/custom_styles/svg/folder.svg

override chrome://browser/skin/places/folder.svg chrome://user_chrome_files/content/custom_styles/svg/folder.svg

override chrome://global/skin/icons/arrow-dropdown-16.svg chrome://user_chrome_files/content/custom_styles/svg/arrow-dropdown-16.svg

override chrome://browser/skin/history.svg chrome://user_chrome_files/content/custom_styles/svg/history.svg

override chrome://global/skin/icons/chevron.svg chrome://user_chrome_files/content/custom_styles/svg/chevron.svg

override chrome://browser/skin/menu.svg chrome://user_chrome_files/content/custom_styles/svg/menu.svg

override chrome://global/skin/icons/page-portrait.svg chrome://global/skin/icons/info.svg

override chrome://branding/content/icon16.png chrome://branding/content/identity-icons-brand.svg

override chrome://browser/skin/controlcenter/tracking-protection.svg chrome://user_chrome_files/content/custom_styles/svg/tracking-protection.svg

Частично менял стилем там, где не нужно все скопом менять или другие причины
скрытый текст

Выделить код

Код:

/* иконка щита "трекеры не обнаружены" */
#urlbar-input-container[pageproxystate="valid"] > #tracking-protection-icon-container > #tracking-protection-icon-box > #tracking-protection-icon {
	list-style-image: url(./svg/tracking-protection.svg) !important;
	fill-opacity: 0.6 !important;
}
/* иконка щита "блокируются трекеры" */
#urlbar-input-container[pageproxystate="valid"] > #tracking-protection-icon-container > #tracking-protection-icon-box:not([hasException])[active] > #tracking-protection-icon {
	display: block !important;
	background-image: url(./svg/tracking-protection-active.svg) !important;
	transform: translateX(-256px) !important;
	width: 272px !important;
	background-size: auto !important;
	height: 16px !important;
	min-height: 16px !important;
	-moz-context-properties: fill, fill-opacity !important;
}
/* зелёный замок */
#identity-box[pageproxystate="valid"].verifiedDomain #identity-icon, #identity-box[pageproxystate="valid"].mixedActiveBlocked #identity-icon {
	list-style-image:  url(./svg/security.svg) !important;
        fill: #12BC00 !important;
}
/* зелёный замок в "информация о сайте" */
#identity-popup[connection^="secure"] .identity-popup-security-connection {
	background-image:  url(./svg/security.svg) !important;
        fill: #12BC00 !important;
}
/* чёрный замок смешанного содержимого */
#identity-box[pageproxystate="valid"].weakCipher #identity-icon, #identity-box[pageproxystate="valid"].mixedDisplayContent #identity-icon, #identity-box[pageproxystate="valid"].mixedDisplayContentLoadedActiveBlocked #identity-icon, #identity-box[pageproxystate="valid"].certUserOverridden #identity-icon, #identity-box[pageproxystate="valid"].certErrorPage #identity-icon {
	list-style-image: url(./svg/security-warning.svg) !important;
	fill-opacity: 0.6 !important;
}
/* чёрный замок смешанного содержимого в "информация о сайте" */
#identity-popup[ciphers="weak"] .identity-popup-security-connection, #identity-popup[mixedcontent~="passive-loaded"][isbroken] .identity-popup-security-connection {
	background-image: url(./svg/security-warning.svg) !important;
}
/* зачёркнутый замок незащещённого содержимого */
#identity-box[pageproxystate="valid"].notSecure #identity-icon, #identity-box[pageproxystate="valid"].mixedActiveContent #identity-icon, #identity-box[pageproxystate="valid"].httpsOnlyErrorPage #identity-icon {
	list-style-image: url(./svg/security-broken.svg) !important;
	fill-opacity: 0.6 !important;
}
/* зачёркнутый замок незащещённого содержимого в "информация о сайте" */
.identity-popup-security-connection {
	background-image: url(./svg/security-broken.svg) !important;
}
/* значок дополнительных разрешений */
#permissions-granted-icon {
	list-style-image: url(./svg/permissions.svg) !important;
	fill-opacity: 0.6 !important;
}
/* значок запрета автовоспроизведения */
.autoplay-media-icon.blocked-permission-icon {
	list-style-image: url(./svg/autoplay-media-blocked.svg) !important;
	fill-opacity: 0.6 !important;
}
/* значок разрешённого автовоспроизведения */
.autoplay-media-icon {
	list-style-image: url(./svg/autoplay-media.svg) !important;
	fill-opacity: 0.6 !important;
}
/* кнопка "назад" */
#back-button {
	list-style-image: url("./svg/back.svg") !important;
}
/* кнопка "вперёд" */
#forward-button {
	list-style-image: url("./svg/forward.svg") !important;
}
/* значок "v" "показать историю" */
#urlbar .urlbar-history-dropmarker {
	/* list-style-image: url(./svg/arrow-dropdown-16.svg) !important; */
	fill-opacity: 0.6 !important;
}
/* звёздочка в адресной строке */
#pageAction-panel-bookmark, #star-button {
	list-style-image: url("./svg/bookmark-hollow.svg") !important;
	fill-opacity: 0.6 !important;
}
#pageAction-panel-bookmark[starred], #star-button[starred] {
	list-style-image: url("./svg/bookmark.svg") !important;
	fill-opacity: 1 !important;
}
/* прозрачность значка "копироать ссылку" */
#pageAction-urlbar-ucf-copyURL .urlbar-icon {
	fill-opacity: .6 !important;
}

Папка со значками.

Скрипты для custom_script.js:
URL tooltip: https://forum.mozilla-russia.org/viewto … 55#p783755

скрипт

Выделить код

Код:

(async () => {
    var id = "ucf-toggle-tooltip-url",
    label = "Тултипы с URL",
    tooltiptext = "Переключить тултипы",
    img = "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' style='fill:context-fill rgb(142, 142, 152);'><path d='M9.618 6.721a2.483 2.483 0 0 0-.39-.317l-.735.734A1.486 1.486 0 0 1 8.91 9.55l-2.12 2.122a1.486 1.486 0 0 1-2.122 0 1.486 1.486 0 0 1 0-2.121l.605-.605a3.53 3.53 0 0 1-.206-1.209L3.961 8.843a2.506 2.506 0 0 0 0 3.535 2.506 2.506 0 0 0 3.535 0l2.122-2.121a2.506 2.506 0 0 0 0-3.536z'/><path d='M6.79 9.55c.12.121.25.226.389.317l.734-.734a1.486 1.486 0 0 1-.417-2.411L9.618 4.6a1.486 1.486 0 0 1 2.121 0 1.486 1.486 0 0 1 0 2.121l-.605.605c.137.391.211.798.206 1.209l1.106-1.107a2.506 2.506 0 0 0 0-3.535 2.506 2.506 0 0 0-3.535 0L6.789 6.014a2.506 2.506 0 0 0 0 3.536z'/><circle style='fill:none;stroke:context-fill rgb(142, 142, 152);stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round' cx='8' cy='8' r='7.4'/></svg>",
    imgcolordisable = "color-mix(in srgb, currentColor 20%, #e31b5d)";

    var branch = "extensions.ucf.", pref = "tooltip_url_enable";
    var tpurl = {
        initialised: false,
        tooltip_url_enable: true,
        get ext_branch() {
            delete this.ext_branch;
            return this.ext_branch = Services.prefs.getBranch(branch);
        },
        init() {
            if (this.initialised) return;
            Services.prefs.getDefaultBranch(branch).setBoolPref(pref, true);
            Services.prefs.addObserver(`${branch}${pref}`, this);
            if (this.tooltip_url_enable = this.ext_branch.getBoolPref(pref))
                this.registerActor();
            this.initialised = true;
        },
        observe(subject, topic, data) {
            var fill = "";
            if ((this.tooltip_url_enable = this.ext_branch.getBoolPref(pref)) === true)
                this.registerActor();
            else {
                fill = imgcolordisable;
                this.unregisterActor();
            }
            this.callWithEachWindow(id, {fill: fill});
        },
        callWithEachWindow(buttonID, atr) {
            var getW = CustomizableUI.getWidget(buttonID);
            if (getW.instances.length)
                for (let {node} of getW.instances) {
                    if (!node) continue;
                    for (let a in atr)
                        node.style.setProperty(a, atr[a]);
                }
            else
                for (let win of CustomizableUI.windows) {
                    let node = getW.forWindow(win).node;
                    if (!node) continue;
                    for (let a in atr)
                        node.style.setProperty(a, atr[a]);
                }
        },
        registerActor() {
            ChromeUtils.registerWindowActor("UcfTooltipUrl", {
                child: {
                    moduleURI: "chrome://user_chrome_files/content/custom_scripts/Ucf_TooltipUrlChild.jsm",
                    events: {
                        mouseover: { capture: true },
                    },
                },
                allFrames: true,
                matches: ["<all_urls>"],
                messageManagerGroups: ["browsers"],
            });
        },
        unregisterActor() {
            ChromeUtils.unregisterWindowActor("UcfTooltipUrl");
        },
    };
    CustomizableUI.createWidget({
        id: id,
        label: label,
        tooltiptext: tooltiptext,
        localized: false,
        defaultArea: CustomizableUI.AREA_NAVBAR,
        onCreated(btn) {
            tpurl.init();
            btn.style.setProperty("list-style-image", `url("${img}")`, "important");
            if (!tpurl.tooltip_url_enable)
                btn.style.setProperty("fill", imgcolordisable);
        },
        onCommand(e) {
            tpurl.ext_branch.setBoolPref(pref, !tpurl.ext_branch.getBoolPref(pref));
        },
    });
})();

+
ucf_TooltipUrlChild.jsm

Выделить код

Код:

var EXPORTED_SYMBOLS = ["UcfTooltipUrlChild"];
ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
class UcfTooltipUrlChild extends JSWindowActorChild {
    handleEvent(e) {
        timer.cancel();
        timer.initWithCallback(() => {
            var elm = e.isTrusted && e.composedTarget, el, titl;
            if (!elm) return;
            do {
                if (!elm.matches) continue;
                if (elm.matches(":any-link")) {
                    if (elm.matches("[href='#'], [href^='javascript']"))
                        return;
                    el = elm;
                    if (elm.matches("[title]"))
                        titl = elm;
                    else
                        while (elm = elm.flattenedTreeParentNode) {
                            if (!elm.matches) continue;
                            if (elm.matches("[title]")) {
                                titl = elm;
                                break;
                            }
                        }
                    break;
                }
                if (elm.matches("[title]")) {
                    titl = elm;
                    while (elm = elm.flattenedTreeParentNode) {
                        if (!elm.matches) continue;
                        if (elm.matches(":any-link")) {
                            if (elm.matches("[href='#'], [href^='javascript']"))
                                return;
                            el = elm;
                            break;
                        }
                    }
                    break;
                }
            } while (elm = elm.flattenedTreeParentNode);
            if (!el) return;
            var href = el.href;
            if (titl) el = titl;
            titl = (el.title || "");
            var title = titl.trim(), pre = "", path = "";
            try {
                href = Services.io.newURI(href);
                pre = href.displayPrePath;
                path = `\n${href.pathQueryRef}`;
                if (path === "\n/") path = "";
                href = `${pre}${path}`;
            } catch (e) {}
            try {
                href = decodeURIComponent(href);
            } catch (e) {}
            el.title = title = `${href}${title === "" ? "" : `\nTitle: ${title}`}`;
            this.contentWindow.addEventListener("mouseout", () => {
                try {
                    if (!el || title !== el.title) return;
                    if (titl !== "")
                        el.title = titl;
                    else
                        el.removeAttribute("title");
                } catch (e) {}
            }, { once: true });
        }, 400, Ci.nsITimer.TYPE_ONE_SHOT);
    }
    didDestroy() {
        timer.cancel();
    }
}


Proxy: https://forum.mozilla-russia.org/viewto … 94#p782794;


Скрипты для custom_script_win.js:
Скрыть ненужные папки в боковой панели: https://forum.mozilla-russia.org/viewto … 25#p777225;
AutoPopup: https://forum.mozilla-russia.org/viewto … 64#p789264
Скрипты: search_image_contextmenu ... https://forum.mozilla-russia.org/viewto … 29#p788229

UndoBookmarksContextMenu - пункты контекстного меню закладок "вернуть\повторить удаление" (закладки)
Для custom_script_win.js или custom_script_all_win.js, в зависимости где должно работать -
панель закладок, сайдбар, библиотека в окне или библиотека во вкладке.
Строка загрузки - loadscript("subfolder/script_file_name.uc.js", win); или loadscript("script_file_name.uc.js", win);

скрытый текст
// ==UserScript==
// @name          s_UndoBookmarksContextMenu
// @namespace     http://space.geocities.yahoo.co.jp/gl/alice0775
// @include       *
// @compatibility Firefox 60
// @author        alice0775
// @version       2019/11/20 23:00 fix redeclaration error
// @version       2019/07/10 10:00 fix 70 Bug 1558914 - Disable Array generics in Nightly
// @version       2018/10/04 20:00 remove conflict shortcuts key for main window
// @version       2018/10/04 60+
// ==/UserScript==
if (typeof window.undobookmarksmenu == "undefined") {
  window.undobookmarksmenu = {
    popup: null,

    handleEvent: function(event) {
      switch (event.type) {
        case 'unload':
          this.uninit();
          break;
        case 'popupshown':
          this.popupshown(event);
          break;
      }
    },

    init: function() {
      window.addEventListener('unload', this, false);
      this.popup = document.getElementById("placesContext");
      if (!this.popup)
        return;
      this.popup.addEventListener('popupshown', this, false);
      let template = (location.href == "chrome://browser/content/browser.xul")  ?
                [
                  ["menuitem", {id: "undobookmarksmenuUndo",
                                disabled: "true",
                                label: "Вернуть удаленное",
                                key: "key_undo",
                                oncommand: "PlacesTransactions.undo().catch(Cu.reportError);",
                                selection: "any"
                  }],
                  ["menuitem", {id:"undobookmarksmenuRedo",
                                disabled: "true",
                                label: "Повторить удаление",
                                key: "key_redo",
                                oncommand: "PlacesTransactions.redo().catch(Cu.reportError);",
                                selection: "any"
                  }]
                ] : [
                  ["menuitem", {id: "undobookmarksmenuUndo",
                                disabled: "true",
                                label: "Вернуть удаленное",
                                key: "key_undo",
                                oncommand: "PlacesTransactions.undo().catch(Cu.reportError);",
                                acceltext: "Ctrl+Z",
                                selection: "any"
                  }],
                  ["menuitem", {id:"undobookmarksmenuRedo",
                                disabled: "true",
                                label: "Повторить удаление",
                                key: "key_redo",
                                oncommand: "PlacesTransactions.redo().catch(Cu.reportError);",
                                acceltext: "Ctrl+Y",
                                selection: "any"
                  }]
                ];

      let ref = document.getElementById("placesContext_deleteSeparator");
      ref.parentNode.insertBefore(this.jsonToDOM(template, document, {}), ref);
    },

    uninit: function() {
      window.removeEventListener('unload', this, false);
      if (!this.popup)
        return;
      this.popup.removeEventListener('popupshown', this, false);
    },

    popupshown: function(event){
      var menuitem = document.getElementById("undobookmarksmenuUndo");
      if (menuitem)
        menuitem.setAttribute('disabled', PlacesTransactions.topUndoEntry == null);
      menuitem = document.getElementById("undobookmarksmenuRedo");
      if (menuitem)
        menuitem.setAttribute('disabled', PlacesTransactions.topRedoEntry == null);
    },

    jsonToDOM: function(jsonTemplate, doc, nodes) {
      jsonToDOM.namespaces = {
      html: "http://www.w3.org/1999/xhtml",
      xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      };
      jsonToDOM.defaultNamespace = jsonToDOM.namespaces.xul;
      function jsonToDOM(jsonTemplate, doc, nodes) {
        function namespace(name) {
            var reElemNameParts = /^(?:(.*):)?(.*)$/.exec(name);
            return { namespace: jsonToDOM.namespaces[reElemNameParts[1]], shortName: reElemNameParts[2] };
        }

        // Note that 'elemNameOrArray' is: either the full element name (eg. [html:]div) or an array of elements in JSON notation
        function tag(elemNameOrArray, elemAttr) {
          // Array of elements?  Parse each one...
          if (Array.isArray(elemNameOrArray)) {
            var frag = doc.createDocumentFragment();
            Array.prototype.forEach.call(arguments, function(thisElem) {
              frag.appendChild(tag.apply(null, thisElem));
            });
            return frag;
          }

          // Single element? Parse element namespace prefix (if none exists, default to defaultNamespace), and create element
          var elemNs = namespace(elemNameOrArray);
          var elem = doc.createElementNS(elemNs.namespace || jsonToDOM.defaultNamespace, elemNs.shortName);

          // Set element's attributes and/or callback functions (eg. onclick)
          for (var key in elemAttr) {
            var val = elemAttr[key];
            if (nodes && key == "key") {
                nodes[val] = elem;
                continue;
            }

            var attrNs = namespace(key);
            if (typeof val == "function") {
              // Special case for function attributes; don't just add them as 'on...' attributes, but as events, using addEventListener
              elem.addEventListener(key.replace(/^on/, ""), val, false);
            } else {
              // Note that the default namespace for XML attributes is, and should be, blank (ie. they're not in any namespace)
              elem.setAttributeNS(attrNs.namespace || "", attrNs.shortName, val);
            }
          }

          // Create and append this element's children
          var childElems = Array.prototype.slice.call(arguments, 2);
          childElems.forEach(function(childElem) {
            if (childElem != null) {
              elem.appendChild(
                  childElem instanceof doc.defaultView.Node ? childElem :
                      Array.isArray(childElem) ? tag.apply(null, childElem) :
                          doc.createTextNode(childElem));
            }
          });
          return elem;
        }
        return tag.apply(null, jsonTemplate);
      }

      return jsonToDOM(jsonTemplate, doc, nodes);
    }
  }


  window.undobookmarksmenu.init();
}

To be continued... Потом еще десяток выложу.

Кнопка для Attributes_Inspector от Dumby
https://forum.mozilla-russia.org/viewto … 07#p789007
Путь к attrsInspector.js прописать свой.
Сам attrsInspector.js - https://github.com/Infocatcher/Custom_B … _Inspector
Например, chrome://user_chrome_files/content/custom_scripts/custom_js/attrsInspector.js =
пути ФС - .\chrome\user_chrome_files\custom_scripts\custom_js\attrsInspector.js
Для custom_script.js в user_chrome_files
 
Открытие окна "Инструменты браузера" по ПКМ на иконке Attributes Inspector
Создано по шаблонам от Vitaliy V.

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

Выделить код

Код:

// Открытие окна "Инструменты браузера"
// по ПКМ на иконке Attributes Inspector
// Создано по шаблонам, где то здесь -
// https://forum.mozilla-russia.org/viewtopic.php?pid=791976#p791976
(this.opendevtoolsrclick3 = {
    async init(that) {
        await window.delayedStartupPromise;
        var btn = CustomizableUI.getWidget("AttributesInspector")?.forWindow(window).node;
        if (!btn) return;
        btn.setAttribute("context", "");
        btn.tooltipText = `ЛКМ: Attributes Inspector\nПКМ: Инструменты браузера`;
        var listener = e => {
            if (e.button != 2) return;
            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
            var pref = Services.prefs, chr = "devtools.chrome.enabled", rem = "devtools.debugger.remote-enabled";
                if (!pref.getBoolPref(chr) || !pref.getBoolPref(rem)) {
                    pref.setBoolPref(chr, true);
                    pref.setBoolPref(rem, true);
            }
            var { BrowserToolboxLauncher } = ChromeUtils.import("resource://devtools/client/framework/browser-toolbox/Launcher.jsm");
            BrowserToolboxLauncher.init();
        };
        btn.addEventListener("click", listener);
        that.unloadlisteners.push("opendevtoolsrclick3");
        this.destructor = () => {
            btn.removeEventListener("click", listener);
        };
    }
}).init(this);

Для custom_script_win.js
Строка загрузки - loadscript("subfolder/script_file_name.uc.js", this); или loadscript("script_file_name.uc.js", this);

Vitaliy V.
Спрошу в этой теме, возможно это можно сделать тоже только скриптом. Вы мне делали скрипт смены иконки поисковика.
Стилем, по этому шаблону, я сменил ещё иконку на старую на закладке.
Старую иконку взял по этому адресу https://yandex.ru/favicon.ico, странно что сейчас везде отображается не она а буква Я в красном круге, но не суть важно.

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

Выделить код

Код:

.bookmark-item:not([container])[image^='page-icon:https://yandex.ru/'] image {
    object-position: 16px 0px !important;
    list-style-image: none !important;
    background: url("./svg/yandex.ico") transparent center no-repeat !important;
}

Хотел вас спросить, может можно сменить фавикон на вкладке для заданного адреса, т.е. там где он изначально формируется, чтоб на вкладке и в адресной строке была одна и та же иконка.
Я использую этот скрипт для отображения фавикона в строке адреса, но наверно он там же считывает иконку, где и Ваш faviconinurlbar. Может можно что-то придумать.
Ещё поменял на NNM сразу на трёх адресах, там у них вообще странно, если масштаб страницы 100%, то отображается одна бабочка(нормальная), повёрнутая вправо, а если ставлю больше масштаб(133% использую почти везде), то уже другая.
Мелочи это всё конечно, но как-то это нелогично, в slimjet с бабочкой такой проблемы нет, а в [firefox] давненько уже.
скрытый текст
______.PNG
Войдите или зарегистрируйтесь, чтобы увидеть скрытый текст.

Выделить код

Код:

.bookmark-item:not([container]):is([image^='page-icon:https://nnm-club.me/'], [image^='page-icon:https://nnmclub.ro/'], [image^='page-icon:https://nnmclub.to/']) image {
    object-position: 16px 0px !important;
    list-style-image: none !important;
    background: url("./svg/nnmclub.ico") transparent center no-repeat !important;
}
sandro79 пишет

возможно это можно сделать тоже только скриптом

Это скрипты для сайтов, для этого есть Greasemonkey и т.д.

sandro79 пишет

там где он изначально формируется

Естественно со страницы сайта по ссылке например
<link rel="icon" href="favicon.ico">
либо из кеша


Ладно попробую но только потому что в Greasemonkey не сработают некоторые события.
Размеры например sizes="32x32" не учитываются, для всех одна иконка
custom_script.js

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

Выделить код

Код:

ChromeUtils.registerWindowActor("LinkWinActor", {
    child: {
        moduleURI: "chrome://user_chrome_files/content/custom_scripts/LinkWinActorChild.jsm",
        events: {
            DOMLinkAdded: { capture: true },
            DOMLinkChanged: {},
            DOMHeadElementParsed: {},
            pageshow: {},
        },
    },
    messageManagerGroups: ["browsers"],
    matches: [ // адреса где работает скрипт
        "https://yandex.ru/",
        "https://yandex.ru/?*",
        "https://yandex.ru/search/*",
        "https://passport.yandex.ru/*",
        "https://nnmclub.to/*",
        "about:config",
        "about:user-chrome-files",
    ],
});


LinkWinActorChild.jsm
скрытый текст

Выделить код

Код:

var EXPORTED_SYMBOLS = ["LinkWinActorChild"];
ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
const LINK_SELECTOR = "link[href]:is([rel~='icon'],[rel~='apple-touch-icon'],[rel~='apple-touch-icon-precomposed'],[rel~='fluid-icon'],[rel~='mask-icon'])";
const ICONS = { // "домен, или адрес для about|chrome|resource": "иконка",
    "yandex.ru": "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><g><rect rx='2' ry='2' width='16' height='16' style='fill:rgb(231, 43, 90);'/><path d='M 8.56,2 C 6.31,2 4.49,3.5 4.49,5.99 4.49,7.49 5.31,8.48 6.97,9.57 L 4,13.5 V 14 H 5.76 L 8.53,9.57 H 9.47 V 14 H 11 V 2 Z M 9.47,8.48 H 8.67 C 7.36,8.48 6.05,7.98 6.05,5.99 6.05,4 7.26,3 8.47,3 H 9.47 Z' style='fill:white;'/></g></svg>",
    "nnmclub.to": "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><g><path d='M 0.887,0 C 0.54,0.14 0.289,0.279 0.135,0.711 0.051,0.851 0.015,0.991 0,1.27 -0.009,1.71 0.061,2.16 0.325,3.05 0.564,3.88 0.612,4.12 0.612,4.37 0.612,4.48 0.612,4.56 0.588,4.67 0.457,5.43 0.457,6.07 0.588,6.56 0.672,6.87 0.779,7.09 0.947,7.28 1.21,7.57 1.49,7.73 2.05,7.87 L 2.27,7.91 2.11,7.96 C 1.87,8.01 1.71,8.08 1.55,8.19 1.33,8.31 1.22,8.41 1.09,8.59 0.947,8.65 0.887,8.83 0.839,8.99 0.803,9.17 0.792,9.41 0.827,9.65 0.851,9.93 0.851,9.93 0.827,10 0.827,10.1 0.767,10.3 0.708,10.5 0.588,10.9 0.564,11.1 0.564,11.2 0.564,11.5 0.612,11.6 0.875,11.8 1.15,12.2 1.18,12.3 1.15,12.6 1.1,13.2 1.18,13.6 1.39,13.7 1.49,13.7 1.6,13.9 1.75,14 1.93,14 2.05,14.1 2.28,14.4 2.48,14.5 2.55,14.5 2.64,14.5 2.73,14.7 2.73,14.7 2.87,14.7 3.04,14.7 3.04,14.7 3.47,14.5 4.03,14 4.19,13.9 4.44,13.9 4.55,13.7 4.6,13.7 4.68,13.7 4.88,13.6 5.11,13 5.35,11.9 5.39,11.8 5.43,11.6 5.47,11.5 5.65,10.9 5.89,10.4 6.12,10 6.24,9.83 6.48,9.59 6.49,9.59 6.49,9.59 6.49,9.75 6.48,10.1 6.44,10.3 6.43,10.5 6.28,11.1 6.12,11.5 6.08,11.7 6.05,12 6.03,12.4 6.11,12.6 6.21,12.9 6.32,13 6.43,13 6.57,13 6.71,12.9 6.81,12.6 6.91,12.4 7.07,12 7.11,11.7 7.11,11 7.11,10.7 7.13,10.4 7.27,10 7.35,9.75 7.32,9.75 7.37,9.83 7.52,10.1 7.64,10.7 7.69,11.3 7.72,11.6 7.72,12.2 7.69,12.4 7.64,13 7.61,13.6 7.68,14 7.72,14.4 7.76,14.5 8.01,14.5 8.15,14.7 8.31,14.8 8.67,15.5 8.92,15.9 9.03,15.9 9.11,16 9.17,16 9.17,16 9.33,16 9.52,16 9.52,16 9.77,15.9 10.1,15.7 10.1,15.7 10.4,15.7 10.7,15.7 10.7,15.7 10.7,15.7 11,15.6 11.1,15.5 11.4,14.9 11.4,14.9 11.4,14.8 11.5,14.7 11.5,14.5 11.6,14.4 11.7,14.1 12.2,13.9 12.2,13.9 12.2,13.7 12.4,13.6 12.4,13.6 12.4,13 12.4,12.6 12.5,12.4 12.5,12.3 12.5,12.3 12.5,12.2 12.7,12 12.8,11.7 12.8,11.6 12.8,11.5 12.8,11.2 12.8,11.1 12.8,11.1 12.7,10.7 12.4,10.3 12,9.93 11.9,9.83 11.9,9.83 11.9,9.83 12.2,9.93 12.7,9.93 12.9,9.93 13.7,9.75 14,9.08 14.1,7.84 14.1,7.76 14.3,7.59 14.3,7.49 14.3,7.27 14.3,7.19 14.5,7.01 14.5,6.83 14.8,6.6 15.1,6.19 15.7,5.24 15.9,4.85 16,4.41 16,4.31 16,4.25 16,4.03 16,3.84 16,3.76 16,3.69 15.7,3.03 15.2,2.68 14.3,2.75 13.7,2.76 13,2.97 12.2,3.32 12,3.4 11.6,3.63 11.4,3.81 10.2,4.49 9.11,5.55 8.08,6.85 7.95,7 7.85,7.11 7.85,7.11 7.85,7.11 7.84,7.05 7.84,7.04 7.76,6.93 7.72,6.87 7.61,6.81 L 7.53,6.73 7.64,6.47 C 7.92,5.92 8.13,5.51 8.31,5.23 8.35,5.12 8.51,4.91 8.76,4.63 9.43,3.73 9.68,3.47 9.77,3.35 10,3.21 10,3.2 10,3.15 10.1,3.05 10,2.92 10,2.87 9.95,2.87 9.95,2.91 9.84,2.97 9.84,3.11 9.52,3.44 9.11,4.03 8.92,4.19 8.76,4.37 8.76,4.45 8.35,4.95 7.95,5.69 7.61,6.47 7.53,6.6 7.49,6.73 7.47,6.73 7.47,6.73 7.43,6.71 7.37,6.71 L 7.28,6.68 V 6.59 C 7.2,5.69 7.08,5.08 6.91,4.49 6.83,4.16 6.8,4 6.59,3.44 6.33,2.75 6.2,2.32 6.2,2.21 6.17,2.09 6.05,2.08 6,2.2 5.97,2.31 5.99,2.39 6.11,2.55 6.21,2.75 6.32,2.97 6.73,4.15 7,4.96 7.07,5.13 7.2,6.29 7.23,6.68 7.23,6.68 7.2,6.68 7.13,6.68 6.97,6.75 6.89,6.83 6.83,6.87 6.81,6.89 6.81,6.89 6.81,6.89 6.75,6.73 6.68,6.56 6.53,6.07 6.4,5.77 6.2,5.27 5.36,3.43 4.39,2.01 3.36,1.13 2.64,0.571 2.01,0.14 1.49,0 1.32,0 1.02,0 0.887,0 Z' style='fill:rgb(0, 140, 255);stroke:black;stroke-width:0.6;stroke-linejoin:round;stroke-linecap:round;'/></g></svg>",
    "about:config": "chrome://global/skin/icons/settings.svg",
    "about:user-chrome-files": "chrome://global/skin/icons/settings.svg",
};

class LinkWinActorChild extends JSWindowActorChild {
    actorCreated() {
        var docURI = this.document.documentURIObject, host;
        if (!/^(?:about|chrome|resource)$/.test(docURI.scheme))
            try {
                let baseDomain = Services.eTLD.getBaseDomain(docURI);
                host = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService)
                .convertToDisplayIDN(baseDomain, {});
            } catch (e) {
                try {
                    host = docURI.displayHost;
                } catch (e) {
                    host = docURI.specIgnoringRef;
                }
            }
        else
            host = docURI.specIgnoringRef;
        var icon = ICONS[host];
        if (!icon) {
            this.handleEvent = e => {};
            return;
        }
        this._icon = icon;
        this.onHeadParsed(this.document.head);
    }
    onHeadParsed(target) {
        for (let link of target.querySelectorAll(LINK_SELECTOR))
            link.remove();
        var link = this.document.createElement("link");
        link.setAttribute("rel", "icon");
        link.setAttribute("sizes", "any");
        link.setAttribute("href", this._icon);
        target.append(link);
    }
    onLinkEvent(link) {
        if (!link.matches(LINK_SELECTOR) || link.href == this._icon) return;
        link.href = this._icon;
        link.setAttribute("rel", "icon");
        link.setAttribute("sizes", "any");
        if (link.hasAttribute("type"))
            link.removeAttribute("type");
    }
    handleEvent(e) {
        switch (e.type) {
            case "DOMLinkAdded":
            case "DOMLinkChanged":
                this.onLinkEvent(e.target);
                break;
            case "pageshow":
                this.onHeadParsed(e.target.head);
                break;
        }
    }
}


Или вариант поддомен + домен, для тех кому надо устанавливать разные иконки для поддоменов

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

Выделить код

Код:

var EXPORTED_SYMBOLS = ["LinkWinActorChild"];
ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
const LINK_SELECTOR = "link[href]:is([rel~='icon'],[rel~='apple-touch-icon'],[rel~='apple-touch-icon-precomposed'],[rel~='fluid-icon'],[rel~='mask-icon'])";
const ICONS = { // "поддомен + домен, или адрес для about|chrome|resource": "иконка",
    "yandex.ru": "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><g><rect rx='2' ry='2' width='16' height='16' style='fill:rgb(231, 43, 90);'/><path d='M 8.56,2 C 6.31,2 4.49,3.5 4.49,5.99 4.49,7.49 5.31,8.48 6.97,9.57 L 4,13.5 V 14 H 5.76 L 8.53,9.57 H 9.47 V 14 H 11 V 2 Z M 9.47,8.48 H 8.67 C 7.36,8.48 6.05,7.98 6.05,5.99 6.05,4 7.26,3 8.47,3 H 9.47 Z' style='fill:white;'/></g></svg>",
    get "passport.yandex.ru"() { return this["yandex.ru"]; },
    "nnmclub.to": "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><g><path d='M 0.887,0 C 0.54,0.14 0.289,0.279 0.135,0.711 0.051,0.851 0.015,0.991 0,1.27 -0.009,1.71 0.061,2.16 0.325,3.05 0.564,3.88 0.612,4.12 0.612,4.37 0.612,4.48 0.612,4.56 0.588,4.67 0.457,5.43 0.457,6.07 0.588,6.56 0.672,6.87 0.779,7.09 0.947,7.28 1.21,7.57 1.49,7.73 2.05,7.87 L 2.27,7.91 2.11,7.96 C 1.87,8.01 1.71,8.08 1.55,8.19 1.33,8.31 1.22,8.41 1.09,8.59 0.947,8.65 0.887,8.83 0.839,8.99 0.803,9.17 0.792,9.41 0.827,9.65 0.851,9.93 0.851,9.93 0.827,10 0.827,10.1 0.767,10.3 0.708,10.5 0.588,10.9 0.564,11.1 0.564,11.2 0.564,11.5 0.612,11.6 0.875,11.8 1.15,12.2 1.18,12.3 1.15,12.6 1.1,13.2 1.18,13.6 1.39,13.7 1.49,13.7 1.6,13.9 1.75,14 1.93,14 2.05,14.1 2.28,14.4 2.48,14.5 2.55,14.5 2.64,14.5 2.73,14.7 2.73,14.7 2.87,14.7 3.04,14.7 3.04,14.7 3.47,14.5 4.03,14 4.19,13.9 4.44,13.9 4.55,13.7 4.6,13.7 4.68,13.7 4.88,13.6 5.11,13 5.35,11.9 5.39,11.8 5.43,11.6 5.47,11.5 5.65,10.9 5.89,10.4 6.12,10 6.24,9.83 6.48,9.59 6.49,9.59 6.49,9.59 6.49,9.75 6.48,10.1 6.44,10.3 6.43,10.5 6.28,11.1 6.12,11.5 6.08,11.7 6.05,12 6.03,12.4 6.11,12.6 6.21,12.9 6.32,13 6.43,13 6.57,13 6.71,12.9 6.81,12.6 6.91,12.4 7.07,12 7.11,11.7 7.11,11 7.11,10.7 7.13,10.4 7.27,10 7.35,9.75 7.32,9.75 7.37,9.83 7.52,10.1 7.64,10.7 7.69,11.3 7.72,11.6 7.72,12.2 7.69,12.4 7.64,13 7.61,13.6 7.68,14 7.72,14.4 7.76,14.5 8.01,14.5 8.15,14.7 8.31,14.8 8.67,15.5 8.92,15.9 9.03,15.9 9.11,16 9.17,16 9.17,16 9.33,16 9.52,16 9.52,16 9.77,15.9 10.1,15.7 10.1,15.7 10.4,15.7 10.7,15.7 10.7,15.7 10.7,15.7 11,15.6 11.1,15.5 11.4,14.9 11.4,14.9 11.4,14.8 11.5,14.7 11.5,14.5 11.6,14.4 11.7,14.1 12.2,13.9 12.2,13.9 12.2,13.7 12.4,13.6 12.4,13.6 12.4,13 12.4,12.6 12.5,12.4 12.5,12.3 12.5,12.3 12.5,12.2 12.7,12 12.8,11.7 12.8,11.6 12.8,11.5 12.8,11.2 12.8,11.1 12.8,11.1 12.7,10.7 12.4,10.3 12,9.93 11.9,9.83 11.9,9.83 11.9,9.83 12.2,9.93 12.7,9.93 12.9,9.93 13.7,9.75 14,9.08 14.1,7.84 14.1,7.76 14.3,7.59 14.3,7.49 14.3,7.27 14.3,7.19 14.5,7.01 14.5,6.83 14.8,6.6 15.1,6.19 15.7,5.24 15.9,4.85 16,4.41 16,4.31 16,4.25 16,4.03 16,3.84 16,3.76 16,3.69 15.7,3.03 15.2,2.68 14.3,2.75 13.7,2.76 13,2.97 12.2,3.32 12,3.4 11.6,3.63 11.4,3.81 10.2,4.49 9.11,5.55 8.08,6.85 7.95,7 7.85,7.11 7.85,7.11 7.85,7.11 7.84,7.05 7.84,7.04 7.76,6.93 7.72,6.87 7.61,6.81 L 7.53,6.73 7.64,6.47 C 7.92,5.92 8.13,5.51 8.31,5.23 8.35,5.12 8.51,4.91 8.76,4.63 9.43,3.73 9.68,3.47 9.77,3.35 10,3.21 10,3.2 10,3.15 10.1,3.05 10,2.92 10,2.87 9.95,2.87 9.95,2.91 9.84,2.97 9.84,3.11 9.52,3.44 9.11,4.03 8.92,4.19 8.76,4.37 8.76,4.45 8.35,4.95 7.95,5.69 7.61,6.47 7.53,6.6 7.49,6.73 7.47,6.73 7.47,6.73 7.43,6.71 7.37,6.71 L 7.28,6.68 V 6.59 C 7.2,5.69 7.08,5.08 6.91,4.49 6.83,4.16 6.8,4 6.59,3.44 6.33,2.75 6.2,2.32 6.2,2.21 6.17,2.09 6.05,2.08 6,2.2 5.97,2.31 5.99,2.39 6.11,2.55 6.21,2.75 6.32,2.97 6.73,4.15 7,4.96 7.07,5.13 7.2,6.29 7.23,6.68 7.23,6.68 7.2,6.68 7.13,6.68 6.97,6.75 6.89,6.83 6.83,6.87 6.81,6.89 6.81,6.89 6.81,6.89 6.75,6.73 6.68,6.56 6.53,6.07 6.4,5.77 6.2,5.27 5.36,3.43 4.39,2.01 3.36,1.13 2.64,0.571 2.01,0.14 1.49,0 1.32,0 1.02,0 0.887,0 Z' style='fill:rgb(0, 140, 255);stroke:black;stroke-width:0.6;stroke-linejoin:round;stroke-linecap:round;'/></g></svg>",
    "about:config": "chrome://global/skin/icons/settings.svg",
    "about:user-chrome-files": "chrome://global/skin/icons/settings.svg",
};

class LinkWinActorChild extends JSWindowActorChild {
    actorCreated() {
        var docURI = this.document.documentURIObject, host;
        if (!/^(?:about|chrome|resource)$/.test(docURI.scheme))
            try {
                host = docURI.displayHost;
            } catch (e) {
                host = docURI.specIgnoringRef;
            }
        else
            host = docURI.specIgnoringRef;
        var icon = ICONS[host];
        if (!icon) {
            this.handleEvent = e => {};
            return;
        }
        this._icon = icon;
        this.onHeadParsed(this.document.head);
    }
    onHeadParsed(target) {
        for (let link of target.querySelectorAll(LINK_SELECTOR))
            link.remove();
        var link = this.document.createElement("link");
        link.setAttribute("rel", "icon");
        link.setAttribute("sizes", "any");
        link.setAttribute("href", this._icon);
        target.append(link);
    }
    onLinkEvent(link) {
        if (!link.matches(LINK_SELECTOR) || link.href == this._icon) return;
        link.href = this._icon;
        link.setAttribute("rel", "icon");
        link.setAttribute("sizes", "any");
        if (link.hasAttribute("type"))
            link.removeAttribute("type");
    }
    handleEvent(e) {
        switch (e.type) {
            case "DOMLinkAdded":
            case "DOMLinkChanged":
                this.onLinkEvent(e.target);
                break;
            case "pageshow":
                this.onHeadParsed(e.target.head);
                break;
        }
    }
}


sandro79 пишет

Я использую этот скрипт для отображения фавикона в строке адреса

Этот скрипт ужасен, что вы в нем нашли?

Vitaliy V. пишет

Ладно попробую но только потому что в Greasemonkey не сработают некоторые события.
Размеры например sizes="32x32" не учитываются, для всех одна иконка

Виталий, ну Вы Мастер!!! Я думал там попроще будет, как с иконкой поисковика примерно.
Огромное Вам Спасибо! Все с ходу заработало! Прописал остальные адреса, всё отлично!
Правда была проблема когда адреса добавлял, запятые забыл проставить и никак иконки не появлялись, но взял себя в руки и решил эту задачу.

скрытый текст
______2.PNG

Этот скрипт ужасен, что вы в нем нашли?

Да, я вижу его недостатки, скорее всего поверхностно, но плюс в  том, что он мне подходит тем, что на месте скрытой стилем лупы в строке адреса, он ставит значок [firefox] на странице about:newtab, также расположение фавикона мне привычней (ну этим можно и пожертвовать), с тех пор как пользуюсь этим скриптом, с 60 какой-то версии. И плюс ещё, что фавикон в строке адреса при навигации в пределах страницы, обновлении страницы - всегда отображается и не исчезает. Если бы в Вашем скрипте фавикон не менялся на identity-icon пока находишься на странице - обновляешь, по ссылкам ходишь, я бы его использовал конечно, но постоянное обновление фавикона, ну мне не подходит. Я вижу недостатки в используемом мной скрипте, насколько фавикон жёстко привязан к высоте адресной строки, чуть размер строки другой, растягивается или сжимается, подбирать вручную нужно правкой скрипта. Но я настроил под свой режим и пойдёт пока.

Vitaliy V.
Разреши попросить прояснить насчёт
событий "DOMHeadElementParsed" и "pageshow".


Если я правильно понимаю, то лисий LinkHandlerChild
слушает их на предмет того, что если соответствующий <link>
не нарисовался, то запросить дефолтный http://сайт/favicon.ico,
и для предотвращения сохранения фавиконок,
изменённых js-манипуляциями скриптами сайта.


А LinkWinActorChild зачем их слушает?

Dumby
DOMLinkAdded срабатывает не всегда при использовании matches: [ // адреса где работает скрипт ...
Это видно с яндексом если перейти со страницы поиска на главную нажав логотип Яндекс в слева от поиска
А DOMHeadElementParsed да возможно лишний, вариант скрипта не окончательный, есть ещё проблемка с ним

sandro79 пишет

Если бы в Вашем скрипте фавикон не менялся на identity-icon пока находишься на странице - обновляешь, по ссылкам ходишь, я бы его использовал конечно, но постоянное обновление фавикона, ну мне не подходит

Это не постоянное обновление, если говорить об этом то ваш скрипт обновляет её намного чаще, когда это и не нужно.
Это скрытие иконки сделано чтобы при переходе например на другой сайт она не отображалась некоторое время с адресом от другого сайта ,
и вообще то это можно изменить удалив например css селектор , #identity-faviconinurlbar[favbusy="true"]

sandro79 пишет

что на месте скрытой стилем лупы в строке адреса, он ставит значок [firefox] на странице about:newtab

что требуется удалить для этого
#identity-faviconinurlbar[faviconchrome="true"],
,.localResource
, #urlbar:not(.searchButton) #identity-box[pageproxystate="invalid"] #identity-faviconinurlbar
или добавить в конце css кода

Выделить код

Код:

#urlbar:not(.searchButton) #identity-box[pageproxystate="invalid"].localResource #identity-faviconinurlbar[faviconchrome="true"] {
                        display: -moz-inline-box !important;
                    }
Vitaliy V. пишет

DOMLinkAdded срабатывает не всегда при использовании matches

О как! Принято, тогда действительно стоит прогонять ещё раз, на всякий случай.
Я-то вообще был бы склонен к переопределению LinkHandlerParent.prototype.setIconFromLink

sandro79 пишет

но плюс в  том, что он мне подходит тем, что на месте скрытой стилем лупы в строке адреса, он ставит значок [firefox] на странице about:newtab,

Можно не скрывая подставить другую иконку, например так:

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

Выделить код

Код:

#urlbar:not(.searchButton) > #urlbar-input-container > #identity-box[pageproxystate="invalid"] #identity-icon {
    list-style-image: url("chrome://branding/content/about-logo.svg") !important;
}

Vitaliy V. пишет

и вообще то это можно изменить удалив например css селектор , #identity-faviconinurlbar[favbusy="true"]

Да, это сработало.

что требуется удалить для этого

#identity-faviconinurlbar[faviconchrome="true"],

А это не сработало. Добавление в конце css кода тоже нет. Да я может не совсем правильно поставил задачу, не знаю даже. Скрины выложу. Вот на about:newtab у него как и на служебных страницах тоже добавляются значки, может можно так сделать, а так теперь фавикон не мелькает

скрытый текст
______.PNG______2.PNG
Сам скрипт, что я правил, может где ошибся
скрытый текст

Выделить код

Код:

(this.faviconinurlbar = {
            init(that) {
                var identity = document.querySelector("#identity-icon");
                if (!identity)
                    return;
                var iconDefault = "chrome://branding/content/identity-icons-brand.svg"; // или свою иконку
                var style = "data:text/css;charset=utf-8," + encodeURIComponent(`
                    #identity-faviconinurlbar {
                        --v-faviconinurlbar-default: url("${iconDefault}");

                        list-style-image: var(--v-faviconinurlbar, none) !important;
                        pointer-events: none !important;
                        height: 16px !important;
                        width: auto !important;
                        margin-inline-start: 4px !important;
                        -moz-context-properties: fill, fill-opacity;
                        fill: currentColor;
                        fill-opacity: var(--urlbar-icon-fill-opacity, 1);
                    }
                    #identity-faviconinurlbar:not([faviconinurlbar="true"]) {
                        --v-faviconinurlbar: var(--v-faviconinurlbar-default) !important;
                    }
                    #urlbar[actiontype="extension"] #identity-faviconinurlbar,
                    #identity-box:is(.extensionPage,.chromeUI,.localResource) #identity-faviconinurlbar {
                        display: none !important;
                    }
                `);
                windowUtils.loadSheetUsingURIString(style, windowUtils.USER_SHEET);
                var faviconinurlbar = document.createXULElement("image");
                faviconinurlbar.id = "identity-faviconinurlbar";
                identity.after(faviconinurlbar);
                gBrowser.tabContainer.addEventListener("TabAttrModified", this);
                gBrowser.addProgressListener(this);
                that.unloadlisteners.push("faviconinurlbar");
                var {STATE_START, STATE_STOP, STATE_IS_NETWORK} = Ci.nsIWebProgressListener;
                var updatefavicon = image => {
                    if (image) {
                        faviconinurlbar.style.setProperty("--v-faviconinurlbar", `url("${image}")`);
                        faviconinurlbar.setAttribute("faviconinurlbar", "true");
                        faviconinurlbar.setAttribute("faviconchrome", `${image.startsWith("chrome:")}`);
                    } else {
                        faviconinurlbar.setAttribute("faviconinurlbar", "false");
                        faviconinurlbar.style.setProperty("--v-faviconinurlbar", "");
                    }
                };
                this.handleEvent = e => {
                    var tab = e.target, changed;
                    if (!tab.selected || !((changed = e.detail.changed).includes("image") || changed.includes("selected"))) return;
                    updatefavicon(tab.image);
                };
                this.onStateChange = (aWebProgress, aRequest, aStateFlags, aStatus) => {
                    if (aStateFlags & STATE_IS_NETWORK && aWebProgress?.isTopLevel) {
                        if (aStateFlags & STATE_START)
                            faviconinurlbar.setAttribute("favbusy", "true");
                        else if (aStateFlags & STATE_STOP) {
                            faviconinurlbar.setAttribute("favbusy", "false");
                            updatefavicon(gBrowser.selectedTab.image);
                        }
                    }
                };
            },
            destructor() {
                gBrowser.tabContainer.removeEventListener("TabAttrModified", this);
                gBrowser.removeProgressListener(this);
            }
        }).init(this);

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

kokoss пишет

Можно не скрывая подставить другую иконку, например так:

Да, спасибо. Я знаю как подобные иконки менять :D Но как вариант да, сойдет.

sandro79 пишет

Да, спасибо. Я знаю как подобные иконки менять

Я и не сомневался:D, но может кому нибудь ещё пригодится :)

sandro79 пишет

может где ошибся

я ещё писал про ,.localResource
но как я понял вообще скрывать нигде не нужно тогда удалите целиком правило относящееся к display: none !important;

Vitaliy V. пишет

я ещё писал про ,.localResource

Вот же ж, всё-таки недоглядел я, запутался.

но как я понял вообще скрывать нигде не нужно тогда удалите целиком правило относящееся к display: none !important;

Да, я неправильно сформулировал задачу, не упомянув о служебных страницах, извиняюсь.
Да, и это теперь сработало на служебных страницах. Но чтоб сработало на about:newtab, нужно удалить из кода правило, скрывающее лупу

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

Выделить код

Код:

#urlbar:not(.searchButton) > #urlbar-input-container > #identity-box[pageproxystate="invalid"] > #identity-icon-box {
  display: none !important;
}

И получается так. А если скрыть лупу, то лого [firefox] тоже пропадает. Если использовать этот код появляется два лого [firefox].
Ну я понял, тут дело в том как, к чему привязан что ли вафикон в разных скриптах, технически не знаю как сформулировать. Ну тут похоже надо больше переделывать.

kokoss пишет

но может кому нибудь ещё пригодится

Конечно, любой рабочий код будет полезен.

sandro79 пишет

А если скрыть лупу, то лого [firefox] тоже пропадает ... Ну я понял, тут дело в том как, к чему привязан что ли вафикон в разных скриптах

Нет дело не в этом, просто нужно скрывать #identity-icon а не контейнер

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

Выделить код

Код:

#urlbar:not(.searchButton) > #urlbar-input-container > #identity-box[pageproxystate="invalid"] #identity-icon {
  display: none !important;
}

Vitaliy V. пишет

Нет дело не в этом, просто нужно скрывать #identity-icon а не контейнер

Огромное Вам спасибо! Теперь всё отлично!

скрытый текст
______.PNG______2.PNG______3.PNG
Оставил скрипт пока в [nightly], на ней привыкать буду некоторое время к расположению фавикона сайта. У Ариса он перед замком, у Вас после него.
Здесь оставлю коды скрипта и стиля, как резерв
скрытый текст

Выделить код

Код:

(this.faviconinurlbar = {
            init(that) {
                var identity = document.querySelector("#identity-icon");
                if (!identity)
                    return;
                var iconDefault = "chrome://global/skin/icons/info.svg"; // или свою иконку
                var style = "data:text/css;charset=utf-8," + encodeURIComponent(`
                    #identity-faviconinurlbar {
                        --v-faviconinurlbar-default: url("${iconDefault}");

                        list-style-image: var(--v-faviconinurlbar, none) !important;
                        pointer-events: none !important;
                        height: 16px !important;
                        width: auto !important;
                        margin-inline-start: 4px !important;
                        -moz-context-properties: fill, fill-opacity;
                        fill: currentColor;
                        fill-opacity: var(--urlbar-icon-fill-opacity, 1);
                    }
                    #identity-faviconinurlbar:not([faviconinurlbar="true"]) {
                        --v-faviconinurlbar: var(--v-faviconinurlbar-default) !important;
                    }
                `);
                windowUtils.loadSheetUsingURIString(style, windowUtils.USER_SHEET);
                var faviconinurlbar = document.createXULElement("image");
                faviconinurlbar.id = "identity-faviconinurlbar";
                identity.after(faviconinurlbar);
                gBrowser.tabContainer.addEventListener("TabAttrModified", this);
                gBrowser.addProgressListener(this);
                that.unloadlisteners.push("faviconinurlbar");
                var {STATE_START, STATE_STOP, STATE_IS_NETWORK} = Ci.nsIWebProgressListener;
                var updatefavicon = image => {
                    if (image) {
                        faviconinurlbar.style.setProperty("--v-faviconinurlbar", `url("${image}")`);
                        faviconinurlbar.setAttribute("faviconinurlbar", "true");
                        faviconinurlbar.setAttribute("faviconchrome", `${image.startsWith("chrome:")}`);
                    } else {
                        faviconinurlbar.setAttribute("faviconinurlbar", "false");
                        faviconinurlbar.style.setProperty("--v-faviconinurlbar", "");
                    }
                };
                this.handleEvent = e => {
                    var tab = e.target, changed;
                    if (!tab.selected || !((changed = e.detail.changed).includes("image") || changed.includes("selected"))) return;
                    updatefavicon(tab.image);
                };
                this.onStateChange = (aWebProgress, aRequest, aStateFlags, aStatus) => {
                    if (aStateFlags & STATE_IS_NETWORK && aWebProgress?.isTopLevel) {
                        if (aStateFlags & STATE_START)
                            faviconinurlbar.setAttribute("favbusy", "true");
                        else if (aStateFlags & STATE_STOP) {
                            faviconinurlbar.setAttribute("favbusy", "false");
                            updatefavicon(gBrowser.selectedTab.image);
                        }
                    }
                };
            },
            destructor() {
                gBrowser.tabContainer.removeEventListener("TabAttrModified", this);
                gBrowser.removeProgressListener(this);
            }
        }).init(this);

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

Выделить код

Код:

#urlbar:not(.searchButton) > #urlbar-input-container > #identity-box[pageproxystate="invalid"] #identity-icon {
  display: none !important;
}

sandro79 пишет

У Ариса он перед замком, у Вас после него

Не думаете же вы что это сложно изменить, вот даже стилем можно
заменить
margin-inline-start: 4px !important;
на
-moz-box-ordinal-group: 0 !important;
margin-inline: 0 4px !important;


ну или вместо -moz-box-ordinal-group: 0 !important;
//identity.after(faviconinurlbar);
identity.before(faviconinurlbar);

Vitaliy V. пишет

Не думаете же вы что это сложно изменить, вот даже стилем можно

Да, я собирался вообще попробовать, но что-то подумал, что там жёстко у Вас фавикон привязан, и вообще решил не трогать, оставить оригинальное расположение.
Да, сработало стилем. Спасибо за подсказку! Теперь вообще супер!

ну или вместо -moz-box-ordinal-group: 0 !important; //identity.after(faviconinurlbar); identity.before(faviconinurlbar);

А так вообще великолепно! Спасибо! Только замок от фавикона чуть отодвину стилем, это я уже сам.

скрытый текст
______.PNG

Выделить код

Код:

#identity-icon {
    margin-inline-start: 4px !important;
}

Vitaliy V.
Ну всё, настроил я всё окончательно, теперь визуально один в один с Ариса скриптом.
В скрипте ещё для селектора #identity-faviconinurlbar сменил margin-inline-start: 2px !important;
Ниже скрины - верхняя панель 91 с Ариса скриптом, нижняя 92 [nightly] с этим

скрытый текст
Image_001.png
Теперь можно переходить на этот вариант окончательно. Ещё раз Большое Спасибо :beer:


Окончательный вариант скрипта, добавил в скрипт css-коды с предыдущих сообщений

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

Выделить код

Код:

(this.faviconinurlbar = {
            init(that) {
                var identity = document.querySelector("#identity-icon");
                if (!identity)
                    return;
                var iconDefault = "chrome://global/skin/icons/defaultFavicon.svg"; // или свою иконку
                var style = "data:text/css;charset=utf-8," + encodeURIComponent(`
                    #identity-faviconinurlbar {
                        --v-faviconinurlbar-default: url("${iconDefault}");

                        list-style-image: var(--v-faviconinurlbar, none) !important;
                        pointer-events: none !important;
                        height: 16px !important;
                        width: 16px !important;
                        margin-inline-start: 3px !important;
                        -moz-context-properties: fill, fill-opacity;
                        fill: currentColor;
                        fill-opacity: var(--urlbar-icon-fill-opacity, 1);
                    }
                    #identity-faviconinurlbar:not([faviconinurlbar="true"]) {
                        --v-faviconinurlbar: var(--v-faviconinurlbar-default) !important;
                    }
                    #identity-icon {
                        margin-inline-start: 4px !important;
                    }
                    .identity-box-button {
                        padding-inline: 2px !important;
                    }         
                    #urlbar[actiontype="extension"] #identity-faviconinurlbar,
                    #identity-box:is(.extensionPage,.chromeUI,.unknownIdentity:not(.mixedDisplayContent,.mixedDisplayContentLoadedActiveBlocked)) #identity-faviconinurlbar {
                        display: none !important;
                    }
                    #urlbar:not(.searchButton) > #urlbar-input-container > #identity-box[pageproxystate="invalid"] #identity-icon {
                        display: none !important;
                    }
                `);
                windowUtils.loadSheetUsingURIString(style, windowUtils.USER_SHEET);
                var faviconinurlbar = document.createXULElement("image");
                faviconinurlbar.id = "identity-faviconinurlbar";
                identity.before(faviconinurlbar);
                gBrowser.tabContainer.addEventListener("TabAttrModified", this);
                gBrowser.addProgressListener(this);
                that.unloadlisteners.push("faviconinurlbar");
                var {STATE_START, STATE_STOP, STATE_IS_NETWORK} = Ci.nsIWebProgressListener;
                var updatefavicon = image => {
                    if (image) {
                        faviconinurlbar.style.setProperty("--v-faviconinurlbar", `url("${image}")`);
                        faviconinurlbar.setAttribute("faviconinurlbar", "true");
                        faviconinurlbar.setAttribute("faviconchrome", `${image.startsWith("chrome:")}`);
                    } else {
                        faviconinurlbar.setAttribute("faviconinurlbar", "false");
                        faviconinurlbar.style.setProperty("--v-faviconinurlbar", "");
                    }
                };
                this.handleEvent = e => {
                    var tab = e.target, changed;
                    if (!tab.selected || !((changed = e.detail.changed).includes("image") || changed.includes("selected"))) return;
                    updatefavicon(tab.image);
                };
                this.onStateChange = (aWebProgress, aRequest, aStateFlags, aStatus) => {
                    if (aStateFlags & STATE_IS_NETWORK && aWebProgress?.isTopLevel) {
                        if (aStateFlags & STATE_START)
                            faviconinurlbar.setAttribute("favbusy", "true");
                        else if (aStateFlags & STATE_STOP) {
                            faviconinurlbar.setAttribute("favbusy", "false");
                            updatefavicon(gBrowser.selectedTab.image);
                        }
                    }
                };
            },
            destructor() {
                gBrowser.tabContainer.removeEventListener("TabAttrModified", this);
                gBrowser.removeProgressListener(this);
            }
        }).init(this);

скрытый текст
______.PNG

Обновил Замена фавиконок для сайтов
Добавил замену на служебных страницах и там где вообще отсутствует иконка, кстати DOMHeadElementParsed нужен для этого


P.S. Насчет иконок, в идеале лучше использовать svg, если др. форматов то размером от 32x32 px и более

Vitaliy V. пишет

кстати DOMHeadElementParsed нужен для этого

Но в LinkWinActorChild.jsm его нет.
UPD: А, понял, по нему может сам actor создаваться.

Vitaliy V. пишет

Обновил Замена фавиконок для сайтов
Добавил замену на служебных страницах и там где вообще отсутствует иконка, кстати DOMHeadElementParsed нужен для этого


P.S. Насчет иконок, в идеале лучше использовать svg, если др. форматов то размером от 32x32 px и более

Огромное Спасибо за обнову!!! Обновился, всё везде сработало, на закладках иконки тоже обновились, и в топе сайтов.
В about:config и about:user-chrome-files тоже иконка настроек появилась. Всё отлично!

скрытый текст
______.png
Image_001.png

Обновил Добавить кнопку Пауза/Продолжить в загрузки https://forum.mozilla-russia.org/viewto … 50#p776150

Vitaliy V. пишет

Обновил Добавить кнопку Пауза/Продолжить в загрузки https://forum.mozilla-russia.org/viewto … 50#p776150

Спасибо! Тоже обновился, кнопки иконки только старые оставил.

скрытый текст
Image_002.png
Только у меню кнопки круглые, правила из твиков для каждого окна:
скрытый текст

Выделить код

Код:

@-moz-document url("chrome://browser/content/places/places.xhtml") {
.downloadButton > .button-box {
	border-radius: 16px !important;
}
}
Выделить код

Код:

@-moz-document url(chrome://browser/content/downloads/contentAreaDownloadsView.xhtml) {
.downloadButton > .button-box {
	border-radius: 16px !important;
}
}

Блин, можно же добавить перед последней скобкой в стиль правило(без адреса) из кода выше и кнопки везде будут круглыми.

Не знаю, может кому пригодится.
Разбирался сегодня с конвертацией иконок в .svg формат, для подобных скриптов и стилей. С векторным редактором Inkscape не смог разобраться.
Не сразу удалось найти нормальный онлайн-конвертер, попадались все конвертирующие в base64 что ли в обвёртке svg, но не работали в упомянутых кодах, добавлял data:image/svg+xml;utf8, перед третьей строкой, строки 1 и 2 удалял, двойные кавычки менял на одинарные - ошибка синтаксиса и не работает

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

Выделить код

Код:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">  <image id="image0" width="16" height="16" x="0" y="0"
    xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABiVBMVEUAAAAAAQoAF6wAChMA
AREAwN0AAAAAAQoAZXYADC0AEagAUtkAAAAAAAAALnAABCoATcgBDGoBAwUVXdkAKLAAABcAHn4A
U+kABAkAAAAAChwACTsAH3EAfY8AQHYAC0EAN7cEAQIAABcABjQAAEQAAlYANY8AoPMARqIAABMA
F2EAG3gKGVUAu/4AUZAACA4ACBAAHH0ABiAAAAAAAAB/9vswXFYAjY4AgagAAg4AAAEAicsPW1oA
AAAAAgQAlOwASnYAhM8As/gAXbcBQowAAgkAAAAAJ0IABRQAAQIAN+MABHoAfewAh/QAH9IAovUA
Tf4ALtoAwPsAjvcN8/8A+/8cms8+n8wO2v8AIcsAOOMgvP8O/v8BUdEABHQAH84Akf8Ahf8AH+QA
rvcCqvoA3P8By/8AVn8FAzkAGZ8AEJMAJewAP9EAffYAk/oAoP0Auv8AnNsAMJ0Ao/8BqvgLU+EA
uf8A7P8AQaMCVdsA+P8o/f8A/P8U0f5V+v8A8P8Aqf0Aof/////gm21KAAAASnRSTlMASfMnMfQI
Krpm+v0GAZdk8qlA/fJM1/wUDkGDrMSrZuXybnfO+OX+vETNxer+vRhC334UF/6Tq8EwOPyXAxL9
lfD76bYaGl0yB0c23wgAAAABYktHRIKLs/9EAAAAB3RJTUUH5QcYFgUmGgEtiwAAAK5JREFUGNNj
YGBkYmZAASxerGwoAuzePhwoApxcvtw8SHxePj//AH5kAYHAoGBBZC1CwiGhIkh8UbGw8AhxCQZe
SSlpGVk5eQaFyKjomFhFJWUV1bj4hMQkBrXklNS09IzMrOyc3Dx1DU0GrfyCQm2douKS0rJyXT19
BgaDCkMjY5PKqmpTM3MLS5CpxkBsZV1TW2dji3CfnX19Q6MDkt22jk7OLq7IrnNz9/CEsACxmSFY
BCmxVAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNy0yNFQyMjowNTozOCswMzowMJlJCfEAAAAl
dEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDctMjRUMjI6MDU6MzgrMDM6MDDoFLFNAAAAAElFTkSuQmCC" />
</svg>

Ресурс нашёл, ссылка, можно выбрать качество, выше качество - больше размер кода/файла. Размер и качество конечно уступают base64 и другим форматам.
Сконвертировал бабочку из оригинальной .ico-иконки. Ещё, как я понял в ходе экспериментов, чтоб иконка отработала в упомянутых в ссылках выше кодах, нужно заменить все двойные кавычки на одинарные. Получился готовый для использования код с максимальным качеством(Detailed)
14,6 КБ кода

Выделить код

Код:

data:image/svg+xml;utf8,<svg width='16' height='16' version='1.1' xmlns='http://www.w3.org/2000/svg'><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0' d='M 0.5 0 L 1 3.5 L 0 3.5 L 0.5 0 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0' d='M 4.5 0 L 16 0 L 16 7 L 8.5 7 L 7.5 8 L 6 6.5 L 6 3.5 L 4 0.5 L 4.5 0 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0' d='M 0.5 6 L 0.5 7 L 0.5 6 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0' d='M 15.5 10 L 16 16 L 10 15.5 L 11 13 Q 14.25 13.5 15.5 10 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0' d='M 0.5 12 Q 1.5 14 2.5 12 Q 6.25 10.75 5 14.5 L 5.5 16 L 0 16 L 0.5 12 Z '></path><path fill='rgb(0,6,17)' stroke='rgb(0,6,17)' stroke-width='1' opacity='0.17647058823529413' d='M 3.5 0 L 3.5 1 L 3.5 0 Z '></path><path fill='rgb(0,6,17)' stroke='rgb(0,6,17)' stroke-width='1' opacity='0.17647058823529413' d='M 1.5 1 L 2 2.5 L 1 2.5 L 1.5 1 Z '></path><path fill='rgb(0,6,17)' stroke='rgb(0,6,17)' stroke-width='1' opacity='0.17647058823529413' d='M 13.5 11 L 13.5 12 L 13.5 11 Z '></path><path fill='rgb(0,6,17)' stroke='rgb(0,6,17)' stroke-width='1' opacity='0.17647058823529413' d='M 8.5 15 L 8.5 16 L 8.5 15 Z '></path><path fill='rgb(0,131,215)' stroke='rgb(0,131,215)' stroke-width='1' opacity='0.9764705882352941' d='M 6.5 12 L 6.5 13 L 6.5 12 Z '></path><path fill='rgb(0,131,215)' stroke='rgb(0,131,215)' stroke-width='1' opacity='0.9764705882352941' d='M 6.5 14 L 6.5 15 L 6.5 14 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.00392156862745098' d='M 0.5 4 L 1 5.5 L 0 5.5 L 0.5 4 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.00392156862745098' d='M 8.5 7 L 8.5 8 L 8.5 7 Z '></path><path fill='rgb(0,51,115)' stroke='rgb(0,51,115)' stroke-width='1' opacity='0.615686274509804' d='M 1.5 4 L 1.5 5 L 1.5 4 Z '></path><path fill='rgb(0,51,115)' stroke='rgb(0,51,115)' stroke-width='1' opacity='0.615686274509804' d='M 12.5 7 L 12.5 8 L 12.5 7 Z '></path><path fill='rgb(0,51,115)' stroke='rgb(0,51,115)' stroke-width='1' opacity='0.615686274509804' d='M 14.5 7 L 14.5 8 L 14.5 7 Z '></path><path fill='rgb(0,51,115)' stroke='rgb(0,51,115)' stroke-width='1' opacity='0.615686274509804' d='M 10.5 13 L 10.5 14 L 10.5 13 Z '></path><path fill='rgb(0,159,243)' stroke='rgb(0,159,243)' stroke-width='1' opacity='0.996078431372549' d='M 3.5 4 L 3.5 5 L 3.5 4 Z '></path><path fill='rgb(0,159,243)' stroke='rgb(0,159,243)' stroke-width='1' opacity='0.996078431372549' d='M 13.5 9 L 13.5 10 L 13.5 9 Z '></path><path fill='rgb(0,159,243)' stroke='rgb(0,159,243)' stroke-width='1' opacity='0.996078431372549' d='M 1.5 10 L 1.5 11 L 1.5 10 Z '></path><path fill='rgb(0,159,243)' stroke='rgb(0,159,243)' stroke-width='1' opacity='0.996078431372549' d='M 3.5 10 L 3.5 11 L 3.5 10 Z '></path><path fill='rgb(0,159,243)' stroke='rgb(0,159,243)' stroke-width='1' opacity='0.996078431372549' d='M 6.5 13 L 6.5 14 L 6.5 13 Z '></path><path fill='rgb(0,159,243)' stroke='rgb(0,159,243)' stroke-width='1' opacity='0.996078431372549' d='M 9.5 13 L 9.5 14 L 9.5 13 Z '></path><path fill='rgb(0,14,55)' stroke='rgb(0,14,55)' stroke-width='1' opacity='0.4196078431372549' d='M 1.5 3 L 1.5 4 L 1.5 3 Z '></path><path fill='rgb(0,14,55)' stroke='rgb(0,14,55)' stroke-width='1' opacity='0.4196078431372549' d='M 5.5 4 L 5.5 5 L 5.5 4 Z '></path><path fill='rgb(0,14,55)' stroke='rgb(0,14,55)' stroke-width='1' opacity='0.4196078431372549' d='M 11.5 7 L 11.5 8 L 11.5 7 Z '></path><path fill='rgb(0,14,55)' stroke='rgb(0,14,55)' stroke-width='1' opacity='0.4196078431372549' d='M 15.5 7 L 15.5 8 L 15.5 7 Z '></path><path fill='rgb(0,14,55)' stroke='rgb(0,14,55)' stroke-width='1' opacity='0.4196078431372549' d='M 8.5 8 L 8.5 9 L 8.5 8 Z '></path><path fill='rgb(0,14,55)' stroke='rgb(0,14,55)' stroke-width='1' opacity='0.4196078431372549' d='M 7.5 15 L 7.5 16 L 7.5 15 Z '></path><path fill='rgb(1,2,7)' stroke='rgb(1,2,7)' stroke-width='1' opacity='0.25098039215686274' d='M 1.5 0 L 1.5 1 L 1.5 0 Z '></path><path fill='rgb(1,2,7)' stroke='rgb(1,2,7)' stroke-width='1' opacity='0.25098039215686274' d='M 1.5 6 L 1.5 7 L 1.5 6 Z '></path><path fill='rgb(1,2,7)' stroke='rgb(1,2,7)' stroke-width='1' opacity='0.25098039215686274' d='M 5.5 12 L 5.5 13 L 5.5 12 Z '></path><path fill='rgb(23,250,255)' stroke='rgb(23,250,255)' stroke-width='1' opacity='1' d='M 4.5 8 L 4.5 9 L 4.5 8 Z '></path><path fill='rgb(23,250,255)' stroke='rgb(23,250,255)' stroke-width='1' opacity='1' d='M 8.5 11 L 8.5 12 L 8.5 11 Z '></path><path fill='rgb(45,156,205)' stroke='rgb(45,156,205)' stroke-width='1' opacity='1' d='M 2.5 7 L 4 7.5 L 2.5 8 L 2.5 7 Z '></path><path fill='rgb(4,82,221)' stroke='rgb(4,82,221)' stroke-width='1' opacity='0.9882352941176471' d='M 4.5 3 L 5 4.5 L 4 4.5 L 4.5 3 Z '></path><path fill='rgb(4,82,221)' stroke='rgb(4,82,221)' stroke-width='1' opacity='0.9882352941176471' d='M 1.5 5 Q 4 6 2.5 7 Q 0 6 1.5 5 Z '></path><path fill='rgb(4,82,221)' stroke='rgb(4,82,221)' stroke-width='1' opacity='0.9882352941176471' d='M 5.5 7 L 6 8.5 L 5 8.5 L 5.5 7 Z '></path><path fill='rgb(4,82,221)' stroke='rgb(4,82,221)' stroke-width='1' opacity='0.9882352941176471' d='M 9.5 10 L 9.5 11 L 9.5 10 Z '></path><path fill='rgb(4,82,221)' stroke='rgb(4,82,221)' stroke-width='1' opacity='0.9882352941176471' d='M 6.5 11 L 6.5 12 L 6.5 11 Z '></path><path fill='rgb(0,11,25)' stroke='rgb(0,11,25)' stroke-width='1' opacity='0.2549019607843137' d='M 10.5 7 L 10.5 8 L 10.5 7 Z '></path><path fill='rgb(0,11,25)' stroke='rgb(0,11,25)' stroke-width='1' opacity='0.2549019607843137' d='M 0.5 11 L 0.5 12 L 0.5 11 Z '></path><path fill='rgb(0,15,98)' stroke='rgb(0,15,98)' stroke-width='1' opacity='0.7607843137254902' d='M 5.5 5 L 5.5 6 L 5.5 5 Z '></path><path fill='rgb(0,15,98)' stroke='rgb(0,15,98)' stroke-width='1' opacity='0.7607843137254902' d='M 9.5 8 L 9.5 9 L 9.5 8 Z '></path><path fill='rgb(0,15,98)' stroke='rgb(0,15,98)' stroke-width='1' opacity='0.7607843137254902' d='M 0.5 10 L 0.5 11 L 0.5 10 Z '></path><path fill='rgb(0,15,98)' stroke='rgb(0,15,98)' stroke-width='1' opacity='0.7607843137254902' d='M 4.5 10 L 4.5 11 L 4.5 10 Z '></path><path fill='rgb(0,87,137)' stroke='rgb(0,87,137)' stroke-width='1' opacity='0.7098039215686275' d='M 4.5 2 L 4.5 3 L 4.5 2 Z '></path><path fill='rgb(0,87,137)' stroke='rgb(0,87,137)' stroke-width='1' opacity='0.7098039215686275' d='M 14.5 9 Q 16 10 13.5 11 Q 12 10 14.5 9 Z '></path><path fill='rgb(0,87,137)' stroke='rgb(0,87,137)' stroke-width='1' opacity='0.7098039215686275' d='M 11.5 11 L 11.5 12 L 11.5 11 Z '></path><path fill='rgb(0,87,137)' stroke='rgb(0,87,137)' stroke-width='1' opacity='0.7098039215686275' d='M 9.5 14 L 9.5 15 L 9.5 14 Z '></path><path fill='rgb(0,47,180)' stroke='rgb(0,47,180)' stroke-width='1' opacity='0.9215686274509803' d='M 5.5 6 L 5.5 7 L 5.5 6 Z '></path><path fill='rgb(0,47,180)' stroke='rgb(0,47,180)' stroke-width='1' opacity='0.9215686274509803' d='M 0.5 8 L 0.5 9 L 0.5 8 Z '></path><path fill='rgb(0,47,180)' stroke='rgb(0,47,180)' stroke-width='1' opacity='0.9215686274509803' d='M 8.5 14 L 8.5 15 L 8.5 14 Z '></path><path fill='rgb(0,37,131)' stroke='rgb(0,37,131)' stroke-width='1' opacity='0.8705882352941177' d='M 1.5 7 L 1.5 8 L 1.5 7 Z '></path><path fill='rgb(0,37,131)' stroke='rgb(0,37,131)' stroke-width='1' opacity='0.8705882352941177' d='M 0.5 9 L 0.5 10 L 0.5 9 Z '></path><path fill='rgb(0,37,131)' stroke='rgb(0,37,131)' stroke-width='1' opacity='0.8705882352941177' d='M 2.5 11 L 2.5 12 L 2.5 11 Z '></path><path fill='rgb(0,170,250)' stroke='rgb(0,170,250)' stroke-width='1' opacity='0.996078431372549' d='M 1.5 9 L 3 9.5 L 1.5 10 L 1.5 9 Z '></path><path fill='rgb(0,170,250)' stroke='rgb(0,170,250)' stroke-width='1' opacity='0.996078431372549' d='M 7.5 10 L 9 10.5 L 7.5 11 L 7.5 10 Z '></path><path fill='rgb(0,170,250)' stroke='rgb(0,170,250)' stroke-width='1' opacity='0.996078431372549' d='M 8.5 13 Q 10 14 7.5 15 Q 6 14 8.5 13 Z '></path><path fill='rgb(5,213,255)' stroke='rgb(5,213,255)' stroke-width='1' opacity='1' d='M 4.5 7 L 4.5 8 L 4.5 7 Z '></path><path fill='rgb(5,213,255)' stroke='rgb(5,213,255)' stroke-width='1' opacity='1' d='M 3.5 9 L 5 9.5 L 3.5 10 L 3.5 9 Z '></path><path fill='rgb(0,86,127)' stroke='rgb(0,86,127)' stroke-width='1' opacity='1' d='M 5.5 9 L 5.5 10 L 5.5 9 Z '></path><path fill='rgb(0,20,161)' stroke='rgb(0,20,161)' stroke-width='1' opacity='0.9803921568627451' d='M 2.5 0 L 2.5 1 L 2.5 0 Z '></path><path fill='rgb(0,20,161)' stroke='rgb(0,20,161)' stroke-width='1' opacity='0.9803921568627451' d='M 2.5 3 L 2.5 4 L 2.5 3 Z '></path><path fill='rgb(0,20,161)' stroke='rgb(0,20,161)' stroke-width='1' opacity='0.9803921568627451' d='M 7.5 9 L 9 9.5 L 7.5 10 L 7.5 9 Z '></path><path fill='rgb(3,6,77)' stroke='rgb(3,6,77)' stroke-width='1' opacity='0.9725490196078431' d='M 2.5 2 L 2.5 3 L 2.5 2 Z '></path><path fill='rgb(3,6,77)' stroke='rgb(3,6,77)' stroke-width='1' opacity='0.9725490196078431' d='M 6.5 8 L 7 9.5 Q 6 12 5 10.5 L 6.5 8 Z '></path><path fill='rgb(3,6,77)' stroke='rgb(3,6,77)' stroke-width='1' opacity='0.9725490196078431' d='M 10.5 8 L 12 8.5 L 10.5 9 L 10.5 8 Z '></path><path fill='rgb(0,42,218)' stroke='rgb(0,42,218)' stroke-width='1' opacity='1' d='M 2.5 1 L 2.5 2 L 2.5 1 Z '></path><path fill='rgb(0,42,218)' stroke='rgb(0,42,218)' stroke-width='1' opacity='1' d='M 2.5 4 L 3 5.5 L 2 5.5 L 2.5 4 Z '></path><path fill='rgb(0,42,218)' stroke='rgb(0,42,218)' stroke-width='1' opacity='1' d='M 1.5 8 L 3 8.5 L 1.5 9 L 1.5 8 Z '></path><path fill='rgb(0,42,218)' stroke='rgb(0,42,218)' stroke-width='1' opacity='1' d='M 12.5 8 L 12.5 9 L 12.5 8 Z '></path><path fill='rgb(0,42,218)' stroke='rgb(0,42,218)' stroke-width='1' opacity='1' d='M 15.5 8 L 15.5 9 L 15.5 8 Z '></path><path fill='rgb(0,42,218)' stroke='rgb(0,42,218)' stroke-width='1' opacity='1' d='M 9.5 9 L 11 9.5 L 9.5 10 L 9.5 9 Z '></path><path fill='rgb(0,139,247)' stroke='rgb(0,139,247)' stroke-width='1' opacity='0.996078431372549' d='M 3.5 2 L 4 3.5 L 3 3.5 L 3.5 2 Z '></path><path fill='rgb(0,139,247)' stroke='rgb(0,139,247)' stroke-width='1' opacity='0.996078431372549' d='M 4.5 5 L 4.5 6 L 4.5 5 Z '></path><path fill='rgb(0,139,247)' stroke='rgb(0,139,247)' stroke-width='1' opacity='0.996078431372549' d='M 13.5 8 L 15 8.5 L 11.5 10 L 11.5 9 L 13.5 8 Z '></path><path fill='rgb(0,187,253)' stroke='rgb(0,187,253)' stroke-width='1' opacity='0.996078431372549' d='M 3.5 5 L 3.5 6 L 3.5 5 Z '></path><path fill='rgb(0,187,253)' stroke='rgb(0,187,253)' stroke-width='1' opacity='0.996078431372549' d='M 2.5 10 L 2.5 11 L 2.5 10 Z '></path><path fill='rgb(0,187,253)' stroke='rgb(0,187,253)' stroke-width='1' opacity='0.996078431372549' d='M 10.5 10 L 10.5 11 L 10.5 10 Z '></path><path fill='rgb(0,187,253)' stroke='rgb(0,187,253)' stroke-width='1' opacity='0.996078431372549' d='M 12.5 10 L 12.5 11 L 12.5 10 Z '></path><path fill='rgb(0,10,21)' stroke='rgb(0,10,21)' stroke-width='1' opacity='0.09411764705882353' d='M 14.5 10 L 14.5 11 L 14.5 10 Z '></path><path fill='rgb(0,0,22)' stroke='rgb(0,0,22)' stroke-width='1' opacity='0.32941176470588235' d='M 0.5 7 L 0.5 8 L 0.5 7 Z '></path><path fill='rgb(0,0,22)' stroke='rgb(0,0,22)' stroke-width='1' opacity='0.32941176470588235' d='M 7.5 8 L 7.5 9 L 7.5 8 Z '></path><path fill='rgb(0,0,22)' stroke='rgb(0,0,22)' stroke-width='1' opacity='0.32941176470588235' d='M 15.5 9 L 15.5 10 L 15.5 9 Z '></path><path fill='rgb(0,56,160)' stroke='rgb(0,56,160)' stroke-width='1' opacity='1' d='M 6.5 10 L 6.5 11 L 6.5 10 Z '></path><path fill='rgb(0,56,160)' stroke='rgb(0,56,160)' stroke-width='1' opacity='1' d='M 1.5 11 L 1.5 12 L 1.5 11 Z '></path><path fill='rgb(0,6,32)' stroke='rgb(0,6,32)' stroke-width='1' opacity='0.49411764705882355' d='M 3.5 11 L 3.5 12 L 3.5 11 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.08627450980392157' d='M 4.5 11 L 6 11.5 L 4.5 12 L 4.5 11 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.08627450980392157' d='M 1.5 12 L 1.5 13 L 1.5 12 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.08627450980392157' d='M 6.5 15 L 6.5 16 L 6.5 15 Z '></path><path fill='rgb(1,245,255)' stroke='rgb(1,245,255)' stroke-width='1' opacity='1' d='M 3.5 6 L 5 6.5 L 3.5 7 L 3.5 6 Z '></path><path fill='rgb(1,245,255)' stroke='rgb(1,245,255)' stroke-width='1' opacity='1' d='M 11.5 10 L 11.5 11 L 11.5 10 Z '></path><path fill='rgb(1,245,255)' stroke='rgb(1,245,255)' stroke-width='1' opacity='1' d='M 7.5 11 L 8 13.5 L 7 13.5 L 7.5 11 Z '></path><path fill='rgb(106,248,253)' stroke='rgb(106,248,253)' stroke-width='1' opacity='0.996078431372549' d='M 9.5 11 L 10 12.5 L 9 12.5 L 9.5 11 Z '></path><path fill='rgb(26,198,254)' stroke='rgb(26,198,254)' stroke-width='1' opacity='1' d='M 3.5 8 L 3.5 9 L 3.5 8 Z '></path><path fill='rgb(26,198,254)' stroke='rgb(26,198,254)' stroke-width='1' opacity='1' d='M 8.5 12 L 8.5 13 L 8.5 12 Z '></path><path fill='rgb(31,92,88)' stroke='rgb(31,92,88)' stroke-width='1' opacity='0.5843137254901961' d='M 10.5 11 L 11 12.5 L 10 12.5 L 10.5 11 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.03137254901960784' d='M 4.5 1 L 4.5 2 L 4.5 1 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.03137254901960784' d='M 5.5 3 L 5.5 4 L 5.5 3 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.03137254901960784' d='M 9.5 7 L 9.5 8 L 9.5 7 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.03137254901960784' d='M 12.5 12 L 12.5 13 L 12.5 12 Z '></path><path fill='rgb(0,25,25)' stroke='rgb(0,25,25)' stroke-width='1' opacity='0.047058823529411764' d='M 9.5 15 L 9.5 16 L 9.5 15 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.011764705882352941' d='M 11.5 12 L 11.5 13 L 11.5 12 Z '></path><path fill='rgb(0,0,0)' stroke='rgb(0,0,0)' stroke-width='1' opacity='0.011764705882352941' d='M 5.5 14 L 5.5 15 L 5.5 14 Z '></path><path fill='rgb(0,192,221)' stroke='rgb(0,192,221)' stroke-width='1' opacity='0.9568627450980393' d='M 3.5 1 L 3.5 2 L 3.5 1 Z '></path><path fill='rgb(0,116,165)' stroke='rgb(0,116,165)' stroke-width='1' opacity='0.8117647058823529' d='M 13.5 7 L 13.5 8 L 13.5 7 Z '></path><path fill='rgb(0,116,165)' stroke='rgb(0,116,165)' stroke-width='1' opacity='0.8117647058823529' d='M 12.5 11 L 12.5 12 L 12.5 11 Z '></path><path fill='rgb(0,10,10)' stroke='rgb(0,10,10)' stroke-width='1' opacity='0.09019607843137255' d='M 6.5 7 L 6.5 8 L 6.5 7 Z '></path><path fill='rgb(0,10,10)' stroke='rgb(0,10,10)' stroke-width='1' opacity='0.09019607843137255' d='M 5.5 13 L 5.5 14 L 5.5 13 Z '></path><path fill='rgb(0,10,10)' stroke='rgb(0,10,10)' stroke-width='1' opacity='0.09019607843137255' d='M 10.5 14 L 10.5 15 L 10.5 14 Z '></path></svg>

В общем буду использовать иконки в base64.


Ага, у них есть ещё оптимизатор, прогнал через него и файл уменьшился до 3,5КБ, а качество вроде бы и не пострадало. Это уже внушает оптимизм, можно дальше экспериментировать.


Нет, оптимизированный код выдал ошибку

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

Выделить код

Код:

data:image/svg+xml;utf8,<svg width='16' height='16' xmlns='http://www.w3.org/2000/svg'><path fill='#050919' stroke='#050919' opacity='.176' d='M3.5 0v1-1zM1.5 1L2 2.5H1L1.5 1zM13.5 11v1-1zM8.5 15v1-1z'/><path fill='#367BDC' stroke='#367BDC' opacity='.969' d='M6.5 14v1-1z'/><path stroke='#000' opacity='.004' d='M.5 4L1 5.5H0L.5 4zM8.5 7v1-1z'/><path fill='#1B3672' stroke='#1B3672' opacity='.627' d='M1.5 4v1-1zM12.5 7v1-1zM14.5 7v1-1zM10.5 13v1-1z'/><path fill='#3F9CF8' stroke='#3F9CF8' opacity='.996' d='M3.5 4v1-1zM13.5 9v1-1zM1.5 10v1-1zM7.5 10v1-1zM9.5 13v1-1z'/><path fill='#080E3A' stroke='#080E3A' opacity='.42' d='M1.5 3v1-1zM5.5 4v1-1zM11.5 7v1-1zM15.5 7v1-1zM8.5 8v1-1zM3.5 11v1-1zM7.5 15v1-1z'/><path fill='#02030A' stroke='#02030A' opacity='.251' d='M1.5 0v1-1zM1.5 6v1-1zM5.5 12v1-1z'/><path fill='#4DEFFE' stroke='#4DEFFE' d='M3.5 6v1-1zM11.5 10v1-1zM7.5 13v1-1z'/><path fill='#3F94CE' stroke='#3F94CE' opacity='.996' d='M2.5 7l1.5.5-1.5.5V7zM3.5 10v1-1zM6.5 12v1-1z'/><path fill='#3349DC' stroke='#3349DC' opacity='.988' d='M4.5 3L5 4.5H4L4.5 3zM1.5 5q2.5 1 1 2-2.5-1-1-2zM5.5 7L6 8.5H5L5.5 7zM10.5 9q1.5 1-1 2-1.5-1 1-2zM6.5 11v1-1z'/><path fill='#02051F' stroke='#02051F' opacity='.325' d='M.5 7v1-1zM7.5 8v1-1z'/><path fill='#150B62' stroke='#150B62' opacity='.761' d='M5.5 5v1-1zM9.5 8v1-1zM.5 10v1-1zM4.5 10v1-1z'/><path fill='#245A8B' stroke='#245A8B' opacity='.718' d='M4.5 2v1-1zM14.5 9q1.5 1-1 2-1.5-1 1-2zM11.5 11v1-1zM9.5 14v1-1z'/><path fill='#2726B1' stroke='#2726B1' opacity='.922' d='M5.5 6v1-1zM.5 8v1-1zM8.5 14v1-1z'/><path fill='#1D2182' stroke='#1D2182' opacity='.871' d='M1.5 7v1-1zM.5 9v1-1zM2.5 11v1-1z'/><path fill='#41A6F9' stroke='#41A6F9' opacity='.996' d='M1.5 9l1.5.5-1.5.5V9zM8.5 10v1-1zM8.5 13q1.5 1-1 2-1.5-1 1-2z'/><path fill='#4AD9FE' stroke='#4AD9FE' d='M4.5 7v1-1zM3.5 9v1-1z'/><path fill='#21557E' stroke='#21557E' d='M5.5 9v1-1z'/><path fill='#2600B2' stroke='#2600B2' opacity='.988' d='M2.5 0v1-1zM2.5 3L3 4.5H2L2.5 3zM1.5 8v1-1zM12.5 8v1-1zM7.5 9l1.5.5-1.5.5V9z'/><path fill='#15055D' stroke='#15055D' opacity='.976' d='M2.5 2v1-1zM10.5 8l1.5.5-1.5.5V8zM6.5 9q1.5 1-1 2-1.5-1 1-2z'/><path fill='#301ADF' stroke='#301ADF' d='M2.5 1v1-1zM2.5 5v1-1zM2.5 8v1-1zM15.5 8v1-1zM9.5 9v1-1z'/><path fill='#3C85F6' stroke='#3C85F6' opacity='.996' d='M3.5 2L4 3.5H3L3.5 2zM4.5 5v1-1zM13.5 8l1.5.5-3.5 1.5V9l2-1zM6.5 13v1-1z'/><path fill='#45B8FD' stroke='#45B8FD' opacity='.996' d='M3.5 5v1-1zM3.5 8v1-1zM2.5 10v1-1zM10.5 10v1-1zM12.5 10v1-1z'/><path fill='#051C21' stroke='#051C21' opacity='.059' d='M14.5 10v1-1zM9.5 15v1-1z'/><path fill='#030718' stroke='#030718' opacity='.263' d='M10.5 7v1-1zM15.5 9v1-1zM.5 11v1-1z'/><path fill='#24349E' stroke='#24349E' d='M6.5 10v1-1zM1.5 11v1-1z'/><path fill='#050414' stroke='#050414' opacity='.722' d='M6.5 8v1-1z'/><path stroke='#000' opacity='.086' d='M4.5 11l1.5.5-1.5.5v-1zM1.5 12v1-1zM6.5 15v1-1z'/><path fill='#51FCFE' stroke='#51FCFE' d='M4.5 6v1-1zM4.5 8v1-1zM7 11l2 .5q-1 2.5-2 1V11z'/><path fill='#7DF8FC' stroke='#7DF8FC' opacity='.996' d='M9.5 11l.5 1.5H9l.5-1.5z'/><path fill='#49CBFD' stroke='#49CBFD' d='M4.5 9v1-1zM8.5 12v1-1z'/><path fill='#2E5C58' stroke='#2E5C58' opacity='.584' d='M10.5 11l.5 1.5h-1l.5-1.5z'/><path stroke='#000' opacity='.031' d='M4.5 1v1-1zM5.5 3v1-1zM9.5 7v1-1zM12.5 12v1-1z'/><path fill='#0D0D13' stroke='#0D0D13' opacity='.075' d='M6.5 7v1-1zM5.5 13v1-1z'/><path stroke='#000' opacity='.012' d='M11.5 12v1-1zM5.5 14v1-1z'/><path fill='#40BEDB' stroke='#40BEDB' opacity='.957' d='M3.5 1v1-1z'/><path fill='#2B71A2' stroke='#2B71A2' opacity='.812' d='M13.5 7v1-1zM12.5 11v1-1z'/><path fill='#090913' stroke='#090913' opacity='.102' d='M10.5 14v1-1z'/></svg>

sandro79 пишет

Нет, оптимизированный код выдал ошибку

Так у вас повтор здесь ... </svg><svg width='16' height='16' xmlns='http://www.w3.org/2000/svg'> ...
и цвета нужно перевести в rgb(a) или заменить # на %23

А вообще конечно всякие там конверторы из растра в SVG ерунда полная, да и зачем иконок SVG тоже полно.
В крайнем случае можно конвертнуть из др. вектора в SVG, там качество не пострадает ну или не так сильно.

Vitaliy V. пишет

Так у вас повтор здесь ... ...
и цвета нужно перевести в rgb или заменить # на %23

Ну это, тот, что под первым спойлером в оптимизированном варианте, бабочка ихняя. В rgb(a) пока не пробовал, достаточно трудоёмкая для меня задача, заменил пакетно # на %23, всё-равно ошибку даёт. Может после %23 пробел должен быть, тоже пробовал, не прошло.

А вообще конечно всякие там конверторы из растра в SVG ерунда полная, да и зачем иконок SVG тоже полно

Да это верно, но хоть что-то, если не знаешь сам как и что делать. Да хотелось именно оригинальную иконку бабочки перегнать в svg. Зациклило меня на решении этой задачи.

В крайнем случае можно конвертнуть из др. вектора в SVG

Виталий, я тут оставлю результаты кодов svg без моего вмешательства неоптимизированный и оптимизированный, на всякий случай, оба в виде файлов svg в браузере открываются нормально. Вы имеете в виду из первого большого кода конвертнуть в нормальном неонлайн-редакторе? Может глянете, если не сильно муторно, а если муторно, то и не стоит оно того значит

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

Выделить код

Код:

<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0" d="M 0.5 0 L 1 3.5 L 0 3.5 L 0.5 0 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0" d="M 4.5 0 L 16 0 L 16 7 L 8.5 7 L 7.5 8 L 6 6.5 L 6 3.5 L 4 0.5 L 4.5 0 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0" d="M 0.5 6 L 0.5 7 L 0.5 6 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0" d="M 15.5 10 L 16 16 L 10 15.5 L 11 13 Q 14.25 13.5 15.5 10 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0" d="M 0.5 12 Q 1.5 14 2.5 12 Q 6.25 10.75 5 14.5 L 5.5 16 L 0 16 L 0.5 12 Z "></path><path fill="rgb(5,9,25)" stroke="rgb(5,9,25)" stroke-width="1" opacity="0.17647058823529413" d="M 3.5 0 L 3.5 1 L 3.5 0 Z "></path><path fill="rgb(5,9,25)" stroke="rgb(5,9,25)" stroke-width="1" opacity="0.17647058823529413" d="M 1.5 1 L 2 2.5 L 1 2.5 L 1.5 1 Z "></path><path fill="rgb(5,9,25)" stroke="rgb(5,9,25)" stroke-width="1" opacity="0.17647058823529413" d="M 13.5 11 L 13.5 12 L 13.5 11 Z "></path><path fill="rgb(5,9,25)" stroke="rgb(5,9,25)" stroke-width="1" opacity="0.17647058823529413" d="M 8.5 15 L 8.5 16 L 8.5 15 Z "></path><path fill="rgb(54,123,220)" stroke="rgb(54,123,220)" stroke-width="1" opacity="0.9686274509803922" d="M 6.5 14 L 6.5 15 L 6.5 14 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.00392156862745098" d="M 0.5 4 L 1 5.5 L 0 5.5 L 0.5 4 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.00392156862745098" d="M 8.5 7 L 8.5 8 L 8.5 7 Z "></path><path fill="rgb(27,54,114)" stroke="rgb(27,54,114)" stroke-width="1" opacity="0.6274509803921569" d="M 1.5 4 L 1.5 5 L 1.5 4 Z "></path><path fill="rgb(27,54,114)" stroke="rgb(27,54,114)" stroke-width="1" opacity="0.6274509803921569" d="M 12.5 7 L 12.5 8 L 12.5 7 Z "></path><path fill="rgb(27,54,114)" stroke="rgb(27,54,114)" stroke-width="1" opacity="0.6274509803921569" d="M 14.5 7 L 14.5 8 L 14.5 7 Z "></path><path fill="rgb(27,54,114)" stroke="rgb(27,54,114)" stroke-width="1" opacity="0.6274509803921569" d="M 10.5 13 L 10.5 14 L 10.5 13 Z "></path><path fill="rgb(63,156,248)" stroke="rgb(63,156,248)" stroke-width="1" opacity="0.996078431372549" d="M 3.5 4 L 3.5 5 L 3.5 4 Z "></path><path fill="rgb(63,156,248)" stroke="rgb(63,156,248)" stroke-width="1" opacity="0.996078431372549" d="M 13.5 9 L 13.5 10 L 13.5 9 Z "></path><path fill="rgb(63,156,248)" stroke="rgb(63,156,248)" stroke-width="1" opacity="0.996078431372549" d="M 1.5 10 L 1.5 11 L 1.5 10 Z "></path><path fill="rgb(63,156,248)" stroke="rgb(63,156,248)" stroke-width="1" opacity="0.996078431372549" d="M 7.5 10 L 7.5 11 L 7.5 10 Z "></path><path fill="rgb(63,156,248)" stroke="rgb(63,156,248)" stroke-width="1" opacity="0.996078431372549" d="M 9.5 13 L 9.5 14 L 9.5 13 Z "></path><path fill="rgb(8,14,58)" stroke="rgb(8,14,58)" stroke-width="1" opacity="0.4196078431372549" d="M 1.5 3 L 1.5 4 L 1.5 3 Z "></path><path fill="rgb(8,14,58)" stroke="rgb(8,14,58)" stroke-width="1" opacity="0.4196078431372549" d="M 5.5 4 L 5.5 5 L 5.5 4 Z "></path><path fill="rgb(8,14,58)" stroke="rgb(8,14,58)" stroke-width="1" opacity="0.4196078431372549" d="M 11.5 7 L 11.5 8 L 11.5 7 Z "></path><path fill="rgb(8,14,58)" stroke="rgb(8,14,58)" stroke-width="1" opacity="0.4196078431372549" d="M 15.5 7 L 15.5 8 L 15.5 7 Z "></path><path fill="rgb(8,14,58)" stroke="rgb(8,14,58)" stroke-width="1" opacity="0.4196078431372549" d="M 8.5 8 L 8.5 9 L 8.5 8 Z "></path><path fill="rgb(8,14,58)" stroke="rgb(8,14,58)" stroke-width="1" opacity="0.4196078431372549" d="M 3.5 11 L 3.5 12 L 3.5 11 Z "></path><path fill="rgb(8,14,58)" stroke="rgb(8,14,58)" stroke-width="1" opacity="0.4196078431372549" d="M 7.5 15 L 7.5 16 L 7.5 15 Z "></path><path fill="rgb(2,3,10)" stroke="rgb(2,3,10)" stroke-width="1" opacity="0.25098039215686274" d="M 1.5 0 L 1.5 1 L 1.5 0 Z "></path><path fill="rgb(2,3,10)" stroke="rgb(2,3,10)" stroke-width="1" opacity="0.25098039215686274" d="M 1.5 6 L 1.5 7 L 1.5 6 Z "></path><path fill="rgb(2,3,10)" stroke="rgb(2,3,10)" stroke-width="1" opacity="0.25098039215686274" d="M 5.5 12 L 5.5 13 L 5.5 12 Z "></path><path fill="rgb(77,239,254)" stroke="rgb(77,239,254)" stroke-width="1" opacity="1" d="M 3.5 6 L 3.5 7 L 3.5 6 Z "></path><path fill="rgb(77,239,254)" stroke="rgb(77,239,254)" stroke-width="1" opacity="1" d="M 11.5 10 L 11.5 11 L 11.5 10 Z "></path><path fill="rgb(77,239,254)" stroke="rgb(77,239,254)" stroke-width="1" opacity="1" d="M 7.5 13 L 7.5 14 L 7.5 13 Z "></path><path fill="rgb(63,148,206)" stroke="rgb(63,148,206)" stroke-width="1" opacity="0.996078431372549" d="M 2.5 7 L 4 7.5 L 2.5 8 L 2.5 7 Z "></path><path fill="rgb(63,148,206)" stroke="rgb(63,148,206)" stroke-width="1" opacity="0.996078431372549" d="M 3.5 10 L 3.5 11 L 3.5 10 Z "></path><path fill="rgb(63,148,206)" stroke="rgb(63,148,206)" stroke-width="1" opacity="0.996078431372549" d="M 6.5 12 L 6.5 13 L 6.5 12 Z "></path><path fill="rgb(51,73,220)" stroke="rgb(51,73,220)" stroke-width="1" opacity="0.9882352941176471" d="M 4.5 3 L 5 4.5 L 4 4.5 L 4.5 3 Z "></path><path fill="rgb(51,73,220)" stroke="rgb(51,73,220)" stroke-width="1" opacity="0.9882352941176471" d="M 1.5 5 Q 4 6 2.5 7 Q 0 6 1.5 5 Z "></path><path fill="rgb(51,73,220)" stroke="rgb(51,73,220)" stroke-width="1" opacity="0.9882352941176471" d="M 5.5 7 L 6 8.5 L 5 8.5 L 5.5 7 Z "></path><path fill="rgb(51,73,220)" stroke="rgb(51,73,220)" stroke-width="1" opacity="0.9882352941176471" d="M 10.5 9 Q 12 10 9.5 11 Q 8 10 10.5 9 Z "></path><path fill="rgb(51,73,220)" stroke="rgb(51,73,220)" stroke-width="1" opacity="0.9882352941176471" d="M 6.5 11 L 6.5 12 L 6.5 11 Z "></path><path fill="rgb(2,5,31)" stroke="rgb(2,5,31)" stroke-width="1" opacity="0.3254901960784314" d="M 0.5 7 L 0.5 8 L 0.5 7 Z "></path><path fill="rgb(2,5,31)" stroke="rgb(2,5,31)" stroke-width="1" opacity="0.3254901960784314" d="M 7.5 8 L 7.5 9 L 7.5 8 Z "></path><path fill="rgb(21,11,98)" stroke="rgb(21,11,98)" stroke-width="1" opacity="0.7607843137254902" d="M 5.5 5 L 5.5 6 L 5.5 5 Z "></path><path fill="rgb(21,11,98)" stroke="rgb(21,11,98)" stroke-width="1" opacity="0.7607843137254902" d="M 9.5 8 L 9.5 9 L 9.5 8 Z "></path><path fill="rgb(21,11,98)" stroke="rgb(21,11,98)" stroke-width="1" opacity="0.7607843137254902" d="M 0.5 10 L 0.5 11 L 0.5 10 Z "></path><path fill="rgb(21,11,98)" stroke="rgb(21,11,98)" stroke-width="1" opacity="0.7607843137254902" d="M 4.5 10 L 4.5 11 L 4.5 10 Z "></path><path fill="rgb(36,90,139)" stroke="rgb(36,90,139)" stroke-width="1" opacity="0.7176470588235294" d="M 4.5 2 L 4.5 3 L 4.5 2 Z "></path><path fill="rgb(36,90,139)" stroke="rgb(36,90,139)" stroke-width="1" opacity="0.7176470588235294" d="M 14.5 9 Q 16 10 13.5 11 Q 12 10 14.5 9 Z "></path><path fill="rgb(36,90,139)" stroke="rgb(36,90,139)" stroke-width="1" opacity="0.7176470588235294" d="M 11.5 11 L 11.5 12 L 11.5 11 Z "></path><path fill="rgb(36,90,139)" stroke="rgb(36,90,139)" stroke-width="1" opacity="0.7176470588235294" d="M 9.5 14 L 9.5 15 L 9.5 14 Z "></path><path fill="rgb(39,38,177)" stroke="rgb(39,38,177)" stroke-width="1" opacity="0.9215686274509803" d="M 5.5 6 L 5.5 7 L 5.5 6 Z "></path><path fill="rgb(39,38,177)" stroke="rgb(39,38,177)" stroke-width="1" opacity="0.9215686274509803" d="M 0.5 8 L 0.5 9 L 0.5 8 Z "></path><path fill="rgb(39,38,177)" stroke="rgb(39,38,177)" stroke-width="1" opacity="0.9215686274509803" d="M 8.5 14 L 8.5 15 L 8.5 14 Z "></path><path fill="rgb(29,33,130)" stroke="rgb(29,33,130)" stroke-width="1" opacity="0.8705882352941177" d="M 1.5 7 L 1.5 8 L 1.5 7 Z "></path><path fill="rgb(29,33,130)" stroke="rgb(29,33,130)" stroke-width="1" opacity="0.8705882352941177" d="M 0.5 9 L 0.5 10 L 0.5 9 Z "></path><path fill="rgb(29,33,130)" stroke="rgb(29,33,130)" stroke-width="1" opacity="0.8705882352941177" d="M 2.5 11 L 2.5 12 L 2.5 11 Z "></path><path fill="rgb(65,166,249)" stroke="rgb(65,166,249)" stroke-width="1" opacity="0.996078431372549" d="M 1.5 9 L 3 9.5 L 1.5 10 L 1.5 9 Z "></path><path fill="rgb(65,166,249)" stroke="rgb(65,166,249)" stroke-width="1" opacity="0.996078431372549" d="M 8.5 10 L 8.5 11 L 8.5 10 Z "></path><path fill="rgb(65,166,249)" stroke="rgb(65,166,249)" stroke-width="1" opacity="0.996078431372549" d="M 8.5 13 Q 10 14 7.5 15 Q 6 14 8.5 13 Z "></path><path fill="rgb(74,217,254)" stroke="rgb(74,217,254)" stroke-width="1" opacity="1" d="M 4.5 7 L 4.5 8 L 4.5 7 Z "></path><path fill="rgb(74,217,254)" stroke="rgb(74,217,254)" stroke-width="1" opacity="1" d="M 3.5 9 L 3.5 10 L 3.5 9 Z "></path><path fill="rgb(33,85,126)" stroke="rgb(33,85,126)" stroke-width="1" opacity="1" d="M 5.5 9 L 5.5 10 L 5.5 9 Z "></path><path fill="rgb(38,0,178)" stroke="rgb(38,0,178)" stroke-width="1" opacity="0.9882352941176471" d="M 2.5 0 L 2.5 1 L 2.5 0 Z "></path><path fill="rgb(38,0,178)" stroke="rgb(38,0,178)" stroke-width="1" opacity="0.9882352941176471" d="M 2.5 3 L 3 4.5 L 2 4.5 L 2.5 3 Z "></path><path fill="rgb(38,0,178)" stroke="rgb(38,0,178)" stroke-width="1" opacity="0.9882352941176471" d="M 1.5 8 L 1.5 9 L 1.5 8 Z "></path><path fill="rgb(38,0,178)" stroke="rgb(38,0,178)" stroke-width="1" opacity="0.9882352941176471" d="M 12.5 8 L 12.5 9 L 12.5 8 Z "></path><path fill="rgb(38,0,178)" stroke="rgb(38,0,178)" stroke-width="1" opacity="0.9882352941176471" d="M 7.5 9 L 9 9.5 L 7.5 10 L 7.5 9 Z "></path><path fill="rgb(21,5,93)" stroke="rgb(21,5,93)" stroke-width="1" opacity="0.9764705882352941" d="M 2.5 2 L 2.5 3 L 2.5 2 Z "></path><path fill="rgb(21,5,93)" stroke="rgb(21,5,93)" stroke-width="1" opacity="0.9764705882352941" d="M 10.5 8 L 12 8.5 L 10.5 9 L 10.5 8 Z "></path><path fill="rgb(21,5,93)" stroke="rgb(21,5,93)" stroke-width="1" opacity="0.9764705882352941" d="M 6.5 9 Q 8 10 5.5 11 Q 4 10 6.5 9 Z "></path><path fill="rgb(48,26,223)" stroke="rgb(48,26,223)" stroke-width="1" opacity="1" d="M 2.5 1 L 2.5 2 L 2.5 1 Z "></path><path fill="rgb(48,26,223)" stroke="rgb(48,26,223)" stroke-width="1" opacity="1" d="M 2.5 5 L 2.5 6 L 2.5 5 Z "></path><path fill="rgb(48,26,223)" stroke="rgb(48,26,223)" stroke-width="1" opacity="1" d="M 2.5 8 L 2.5 9 L 2.5 8 Z "></path><path fill="rgb(48,26,223)" stroke="rgb(48,26,223)" stroke-width="1" opacity="1" d="M 15.5 8 L 15.5 9 L 15.5 8 Z "></path><path fill="rgb(48,26,223)" stroke="rgb(48,26,223)" stroke-width="1" opacity="1" d="M 9.5 9 L 9.5 10 L 9.5 9 Z "></path><path fill="rgb(60,133,246)" stroke="rgb(60,133,246)" stroke-width="1" opacity="0.996078431372549" d="M 3.5 2 L 4 3.5 L 3 3.5 L 3.5 2 Z "></path><path fill="rgb(60,133,246)" stroke="rgb(60,133,246)" stroke-width="1" opacity="0.996078431372549" d="M 4.5 5 L 4.5 6 L 4.5 5 Z "></path><path fill="rgb(60,133,246)" stroke="rgb(60,133,246)" stroke-width="1" opacity="0.996078431372549" d="M 13.5 8 L 15 8.5 L 11.5 10 L 11.5 9 L 13.5 8 Z "></path><path fill="rgb(60,133,246)" stroke="rgb(60,133,246)" stroke-width="1" opacity="0.996078431372549" d="M 6.5 13 L 6.5 14 L 6.5 13 Z "></path><path fill="rgb(69,184,253)" stroke="rgb(69,184,253)" stroke-width="1" opacity="0.996078431372549" d="M 3.5 5 L 3.5 6 L 3.5 5 Z "></path><path fill="rgb(69,184,253)" stroke="rgb(69,184,253)" stroke-width="1" opacity="0.996078431372549" d="M 3.5 8 L 3.5 9 L 3.5 8 Z "></path><path fill="rgb(69,184,253)" stroke="rgb(69,184,253)" stroke-width="1" opacity="0.996078431372549" d="M 2.5 10 L 2.5 11 L 2.5 10 Z "></path><path fill="rgb(69,184,253)" stroke="rgb(69,184,253)" stroke-width="1" opacity="0.996078431372549" d="M 10.5 10 L 10.5 11 L 10.5 10 Z "></path><path fill="rgb(69,184,253)" stroke="rgb(69,184,253)" stroke-width="1" opacity="0.996078431372549" d="M 12.5 10 L 12.5 11 L 12.5 10 Z "></path><path fill="rgb(5,28,33)" stroke="rgb(5,28,33)" stroke-width="1" opacity="0.058823529411764705" d="M 14.5 10 L 14.5 11 L 14.5 10 Z "></path><path fill="rgb(5,28,33)" stroke="rgb(5,28,33)" stroke-width="1" opacity="0.058823529411764705" d="M 9.5 15 L 9.5 16 L 9.5 15 Z "></path><path fill="rgb(3,7,24)" stroke="rgb(3,7,24)" stroke-width="1" opacity="0.2627450980392157" d="M 10.5 7 L 10.5 8 L 10.5 7 Z "></path><path fill="rgb(3,7,24)" stroke="rgb(3,7,24)" stroke-width="1" opacity="0.2627450980392157" d="M 15.5 9 L 15.5 10 L 15.5 9 Z "></path><path fill="rgb(3,7,24)" stroke="rgb(3,7,24)" stroke-width="1" opacity="0.2627450980392157" d="M 0.5 11 L 0.5 12 L 0.5 11 Z "></path><path fill="rgb(36,52,158)" stroke="rgb(36,52,158)" stroke-width="1" opacity="1" d="M 6.5 10 L 6.5 11 L 6.5 10 Z "></path><path fill="rgb(36,52,158)" stroke="rgb(36,52,158)" stroke-width="1" opacity="1" d="M 1.5 11 L 1.5 12 L 1.5 11 Z "></path><path fill="rgb(5,4,20)" stroke="rgb(5,4,20)" stroke-width="1" opacity="0.7215686274509804" d="M 6.5 8 L 6.5 9 L 6.5 8 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.08627450980392157" d="M 4.5 11 L 6 11.5 L 4.5 12 L 4.5 11 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.08627450980392157" d="M 1.5 12 L 1.5 13 L 1.5 12 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.08627450980392157" d="M 6.5 15 L 6.5 16 L 6.5 15 Z "></path><path fill="rgb(81,252,254)" stroke="rgb(81,252,254)" stroke-width="1" opacity="1" d="M 4.5 6 L 4.5 7 L 4.5 6 Z "></path><path fill="rgb(81,252,254)" stroke="rgb(81,252,254)" stroke-width="1" opacity="1" d="M 4.5 8 L 4.5 9 L 4.5 8 Z "></path><path fill="rgb(81,252,254)" stroke="rgb(81,252,254)" stroke-width="1" opacity="1" d="M 7 11 L 9 11.5 Q 8 14 7 12.5 L 7 11 Z "></path><path fill="rgb(125,248,252)" stroke="rgb(125,248,252)" stroke-width="1" opacity="0.996078431372549" d="M 9.5 11 L 10 12.5 L 9 12.5 L 9.5 11 Z "></path><path fill="rgb(73,203,253)" stroke="rgb(73,203,253)" stroke-width="1" opacity="1" d="M 4.5 9 L 4.5 10 L 4.5 9 Z "></path><path fill="rgb(73,203,253)" stroke="rgb(73,203,253)" stroke-width="1" opacity="1" d="M 8.5 12 L 8.5 13 L 8.5 12 Z "></path><path fill="rgb(46,92,88)" stroke="rgb(46,92,88)" stroke-width="1" opacity="0.5843137254901961" d="M 10.5 11 L 11 12.5 L 10 12.5 L 10.5 11 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.03137254901960784" d="M 4.5 1 L 4.5 2 L 4.5 1 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.03137254901960784" d="M 5.5 3 L 5.5 4 L 5.5 3 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.03137254901960784" d="M 9.5 7 L 9.5 8 L 9.5 7 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.03137254901960784" d="M 12.5 12 L 12.5 13 L 12.5 12 Z "></path><path fill="rgb(13,13,19)" stroke="rgb(13,13,19)" stroke-width="1" opacity="0.07450980392156863" d="M 6.5 7 L 6.5 8 L 6.5 7 Z "></path><path fill="rgb(13,13,19)" stroke="rgb(13,13,19)" stroke-width="1" opacity="0.07450980392156863" d="M 5.5 13 L 5.5 14 L 5.5 13 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.011764705882352941" d="M 11.5 12 L 11.5 13 L 11.5 12 Z "></path><path fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-width="1" opacity="0.011764705882352941" d="M 5.5 14 L 5.5 15 L 5.5 14 Z "></path><path fill="rgb(64,190,219)" stroke="rgb(64,190,219)" stroke-width="1" opacity="0.9568627450980393" d="M 3.5 1 L 3.5 2 L 3.5 1 Z "></path><path fill="rgb(43,113,162)" stroke="rgb(43,113,162)" stroke-width="1" opacity="0.8117647058823529" d="M 13.5 7 L 13.5 8 L 13.5 7 Z "></path><path fill="rgb(43,113,162)" stroke="rgb(43,113,162)" stroke-width="1" opacity="0.8117647058823529" d="M 12.5 11 L 12.5 12 L 12.5 11 Z "></path><path fill="rgb(9,9,19)" stroke="rgb(9,9,19)" stroke-width="1" opacity="0.10196078431372549" d="M 10.5 14 L 10.5 15 L 10.5 14 Z "></path></svg>

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

Выделить код

Код:

<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path fill="#050919" stroke="#050919" opacity=".176" d="M3.5 0v1-1zM1.5 1L2 2.5H1L1.5 1zM13.5 11v1-1zM8.5 15v1-1z"/><path fill="#367BDC" stroke="#367BDC" opacity=".969" d="M6.5 14v1-1z"/><path stroke="#000" opacity=".004" d="M.5 4L1 5.5H0L.5 4zM8.5 7v1-1z"/><path fill="#1B3672" stroke="#1B3672" opacity=".627" d="M1.5 4v1-1zM12.5 7v1-1zM14.5 7v1-1zM10.5 13v1-1z"/><path fill="#3F9CF8" stroke="#3F9CF8" opacity=".996" d="M3.5 4v1-1zM13.5 9v1-1zM1.5 10v1-1zM7.5 10v1-1zM9.5 13v1-1z"/><path fill="#080E3A" stroke="#080E3A" opacity=".42" d="M1.5 3v1-1zM5.5 4v1-1zM11.5 7v1-1zM15.5 7v1-1zM8.5 8v1-1zM3.5 11v1-1zM7.5 15v1-1z"/><path fill="#02030A" stroke="#02030A" opacity=".251" d="M1.5 0v1-1zM1.5 6v1-1zM5.5 12v1-1z"/><path fill="#4DEFFE" stroke="#4DEFFE" d="M3.5 6v1-1zM11.5 10v1-1zM7.5 13v1-1z"/><path fill="#3F94CE" stroke="#3F94CE" opacity=".996" d="M2.5 7l1.5.5-1.5.5V7zM3.5 10v1-1zM6.5 12v1-1z"/><path fill="#3349DC" stroke="#3349DC" opacity=".988" d="M4.5 3L5 4.5H4L4.5 3zM1.5 5q2.5 1 1 2-2.5-1-1-2zM5.5 7L6 8.5H5L5.5 7zM10.5 9q1.5 1-1 2-1.5-1 1-2zM6.5 11v1-1z"/><path fill="#02051F" stroke="#02051F" opacity=".325" d="M.5 7v1-1zM7.5 8v1-1z"/><path fill="#150B62" stroke="#150B62" opacity=".761" d="M5.5 5v1-1zM9.5 8v1-1zM.5 10v1-1zM4.5 10v1-1z"/><path fill="#245A8B" stroke="#245A8B" opacity=".718" d="M4.5 2v1-1zM14.5 9q1.5 1-1 2-1.5-1 1-2zM11.5 11v1-1zM9.5 14v1-1z"/><path fill="#2726B1" stroke="#2726B1" opacity=".922" d="M5.5 6v1-1zM.5 8v1-1zM8.5 14v1-1z"/><path fill="#1D2182" stroke="#1D2182" opacity=".871" d="M1.5 7v1-1zM.5 9v1-1zM2.5 11v1-1z"/><path fill="#41A6F9" stroke="#41A6F9" opacity=".996" d="M1.5 9l1.5.5-1.5.5V9zM8.5 10v1-1zM8.5 13q1.5 1-1 2-1.5-1 1-2z"/><path fill="#4AD9FE" stroke="#4AD9FE" d="M4.5 7v1-1zM3.5 9v1-1z"/><path fill="#21557E" stroke="#21557E" d="M5.5 9v1-1z"/><path fill="#2600B2" stroke="#2600B2" opacity=".988" d="M2.5 0v1-1zM2.5 3L3 4.5H2L2.5 3zM1.5 8v1-1zM12.5 8v1-1zM7.5 9l1.5.5-1.5.5V9z"/><path fill="#15055D" stroke="#15055D" opacity=".976" d="M2.5 2v1-1zM10.5 8l1.5.5-1.5.5V8zM6.5 9q1.5 1-1 2-1.5-1 1-2z"/><path fill="#301ADF" stroke="#301ADF" d="M2.5 1v1-1zM2.5 5v1-1zM2.5 8v1-1zM15.5 8v1-1zM9.5 9v1-1z"/><path fill="#3C85F6" stroke="#3C85F6" opacity=".996" d="M3.5 2L4 3.5H3L3.5 2zM4.5 5v1-1zM13.5 8l1.5.5-3.5 1.5V9l2-1zM6.5 13v1-1z"/><path fill="#45B8FD" stroke="#45B8FD" opacity=".996" d="M3.5 5v1-1zM3.5 8v1-1zM2.5 10v1-1zM10.5 10v1-1zM12.5 10v1-1z"/><path fill="#051C21" stroke="#051C21" opacity=".059" d="M14.5 10v1-1zM9.5 15v1-1z"/><path fill="#030718" stroke="#030718" opacity=".263" d="M10.5 7v1-1zM15.5 9v1-1zM.5 11v1-1z"/><path fill="#24349E" stroke="#24349E" d="M6.5 10v1-1zM1.5 11v1-1z"/><path fill="#050414" stroke="#050414" opacity=".722" d="M6.5 8v1-1z"/><path stroke="#000" opacity=".086" d="M4.5 11l1.5.5-1.5.5v-1zM1.5 12v1-1zM6.5 15v1-1z"/><path fill="#51FCFE" stroke="#51FCFE" d="M4.5 6v1-1zM4.5 8v1-1zM7 11l2 .5q-1 2.5-2 1V11z"/><path fill="#7DF8FC" stroke="#7DF8FC" opacity=".996" d="M9.5 11l.5 1.5H9l.5-1.5z"/><path fill="#49CBFD" stroke="#49CBFD" d="M4.5 9v1-1zM8.5 12v1-1z"/><path fill="#2E5C58" stroke="#2E5C58" opacity=".584" d="M10.5 11l.5 1.5h-1l.5-1.5z"/><path stroke="#000" opacity=".031" d="M4.5 1v1-1zM5.5 3v1-1zM9.5 7v1-1zM12.5 12v1-1z"/><path fill="#0D0D13" stroke="#0D0D13" opacity=".075" d="M6.5 7v1-1zM5.5 13v1-1z"/><path stroke="#000" opacity=".012" d="M11.5 12v1-1zM5.5 14v1-1z"/><path fill="#40BEDB" stroke="#40BEDB" opacity=".957" d="M3.5 1v1-1z"/><path fill="#2B71A2" stroke="#2B71A2" opacity=".812" d="M13.5 7v1-1zM12.5 11v1-1z"/><path fill="#090913" stroke="#090913" opacity=".102" d="M10.5 14v1-1z"/></svg>

sandro79 пишет

Да хотелось именно оригинальную иконку бабочки перегнать в svg. Зациклило меня на решении этой задачи.

Не помню уже откуда взял, но завалялась у меня эта бабочка в PNG, размером 170px на 170px.
Я попробовал поиграться с конвертором, однако без потери качества перегнать не удалось.
Попробуйте, может у вас что-то получится.
nnm-club-02.png

sandro79 пишет

Ну это, тот, что под первым спойлером в оптимизированном варианте

Под первым у вас вообще то png в обертке SVG, а там где 14,6 КБ кода, нет повтора как в последнем
Вот ещё раз, здесь заканчивается svg тег
...<path fill='#000A0A' stroke='#000A0A' opacity='.09' d='M6.5 7v1-1zM5.5 13v1-1zM10.5 14v1-1z'/></svg>
а дальше опять начинается
<svg width='16' height='16' xmlns='http://www.w3.org/2000/svg'>...
ну это видимо вы вставили два раза в этот пост?

sandro79 пишет

Вы имеете в виду из первого большого кода конвертнуть в нормальном неонлайн-редакторе?

Нет никаким редактором не получится нормально конвертировать растровое изображение в векторное SVG
разве что потом вручную подправлять и то если они простые и не слишком фотографические.
Почитайте в чем отличие вектора от растра, в векторе нельзя создать фото, зато его можно масштабировать как угодно без потери качества.
Я имел ввиду можно без проблем перевести векторные изображения (они кроме SVG бывают и в других форматах - EPS, AI и т.д.) в SVG

unter_officer пишет

Попробуйте, может у вас что-то получится.

Спасибо! Тоже пригодится, буду экспериментировать.

Vitaliy V. пишет

Под первым у вас вообще то png в обертке SVG, а там где 14,6 КБ кода, нет повтора как в последнем

Да-да, имелся в виду 14,6 КБ. Про тот забыл уже.

Вот ещё раз, здесь заканчивается svg тег... а дальше опять начинается

Да, точно, вот же ж, два раза вставил.

Нет никаким редактором не получится нормально конвертировать растровое изображение в векторное SVG...

Да-да, я так уже поверхностно понимаю суть, тут ещё вы говорили. Надо будет так, хоть в общем, ознакомится с этой темой. Огромное Спасибо за разъяснение.


Подправил тот код под третьим спойлером, убрал дубляж, хоть и не работает, но так, для порядка.

Vitaliy V.
Ну проясните пожалуйста один момент. Решил обновить кнопку вкл/откл звука на вкладке/вкладках на вариант из add_toolbar_buttons. Взял код этой кнопки из parent.js, внёс правки, добавил код иконки из sound.svg, всё работает, но только иконка не меняет цвет на белый на тёмном фоне, как в add_toolbar_buttons. Нужно внести ещё какие-то правки, но вот какие и возможно ли это в скрипте, не знаю. Можно использовать конечно старую иконку, она всегда белая. Вот как бы эту чёрную заставить менять цвет на тёмном фоне?

Новый код кнопки с новой иконкой
Image_001.png

Выделить код

Код:

try {
	CustomizableUI.createWidget({
		id: "b-sound-muted-all-tabs",
		type: "custom",
		label: "Переключить звук",
		tooltiptext: "ЛКМ: Переключить звук в выделенных вкладках\nСКМ: Закрыть другие вкладки с источником звука\nПКМ: Переключить звук во всех вкладках",
		defaultArea: CustomizableUI.AREA_NAVBAR,
		localized: false,
		onBuild(doc) {
			var trbn = doc.createXULElement("toolbarbutton"),
			win = doc.defaultView;
			trbn.id = "b-sound-muted-all-tabs";
			trbn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
			trbn.setAttribute("label", "Переключить звук");
			trbn.setAttribute("tooltiptext", "ЛКМ: Переключить звук в выделенных вкладках\nСКМ: Закрыть другие вкладки с источником звука\nПКМ: Переключить звук во всех вкладках");
			trbn.setAttribute("context", false);
			trbn.setAttribute("image", "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 48 48'><g style='fill:context-fill;fill-opacity:context-fill-opacity;'><path d='M 22.5,4.49 15,12 H 10.5 C 6,12 3,16.5 3,21 L 3,27 C 3,31.5 5.99,36 10.5,36 H 15 L 22.5,43.5 C 24.9,45.9 27,45 27,43.5 V 4.49 C 27,2.99 24.9,2.09 22.5,4.49 Z'/><path d='M 39,24 C 39,19 35,15 30,15 28,15 28,18 30,18 33.3,18 36,20.7 36,24 36,27.3 33.3,30 30,30 28,30 28,33 30,33 35,33 39,29 39,24 Z'/><path d='M 30,9 C 28,9 28,12 30,12 36.6,12 42,17.4 42,24 42,30.6 36.6,36 30,36 28,36 28,39 30,39 38.4,39 45,32.4 45,24 45,15.6 38.4,9 30,9 Z'/></g></svg>");
			trbn.addEventListener("click", e => {
				if (e.button == 0) {
					win.gBrowser.toggleMuteAudioOnMultiSelectedTabs(win.gBrowser.selectedTab);
				} else if (e.button == 1) {
					for (let tab of win.gBrowser.visibleTabs.filter(tab => !tab.selected && (tab.muted || tab.soundPlaying)))
						win.gBrowser.removeTab(tab);
				} else if (e.button == 2) {
					e.preventDefault();
					e.stopPropagation();
					let tabsToToggle;
					if (win.gBrowser.selectedTab.activeMediaBlocked) {
						tabsToToggle = win.gBrowser.visibleTabs.filter(tab => tab.activeMediaBlocked || tab.linkedBrowser.audioMuted);
					} else {
						let tabMuted = win.gBrowser.selectedTab.linkedBrowser.audioMuted;
						tabsToToggle = win.gBrowser.visibleTabs.filter(tab => (tab.linkedBrowser.audioMuted == tabMuted && !tab.activeMediaBlocked) || (tab.activeMediaBlocked && tabMuted));
					}
					for (let tab of tabsToToggle)
						tab.toggleMuteAudio();
				}
			});
			return trbn;
		},
	});
} catch(e) {}

Вообще думаю использовать иконку, находящуюся по адресу chrome://global/skin/media/audio.svg
скрытый текст

Выделить код

Код:

data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16' fill='context-fill' fill-opacity='context-fill-opacity'><path d='M7.245 1.35 4.117 5 2 5a2 2 0 0 0-2 2l0 2a2 2 0 0 0 2 2l2.117 0 3.128 3.65C7.848 15.353 9 14.927 9 14L9 2c0-.927-1.152-1.353-1.755-.65z'/><path d='M11.764 15a.623.623 0 0 1-.32-1.162 6.783 6.783 0 0 0 3.306-5.805 6.767 6.767 0 0 0-3.409-5.864.624.624 0 1 1 .619-1.085A8.015 8.015 0 0 1 16 8.033a8.038 8.038 0 0 1-3.918 6.879c-.1.06-.21.088-.318.088z'/><path d='M11.434 11.85A4.982 4.982 0 0 0 13.25 8a4.982 4.982 0 0 0-1.819-3.852l-.431 0 0 7.702.434 0z'/></svg>

sandro79 пишет

Вот как бы эту чёрную заставить менять цвет на тёмном фоне?

для иконок загруженных по data: -moz-context-properties не работает
если не включить svg.context-properties.content.enabled - true

sandro79 пишет

Вообще думаю использовать иконку, находящуюся по адресу chrome://global/skin/media/audio.svg

Ну так и впишите этот адрес вместо data: ...
Или как вариант добавить файл .svg в user_chrome_files
chrome://user_chrome_files/content/далее путь к иконке

Vitaliy V. пишет

если не включить svg.context-properties.content.enabled - true

Да, это пробовал. Но почему-то, как и с вариантом chrome://user_chrome_files/content/далее путь к иконке, иконка краснеет.
Ага, всё, разобрался, нашёл причину, так тоже работает нормально.

Ну так и впишите этот адрес вместо data: ...

Да, вот это сработало как надо. Да надо было сразу так сделать и проверить, зациклило меня, чтоб внутрь скрипта иконку встроить.
Спасибо за подсказку, это самый оптимальный вариант. В данном варианте svg.context-properties.content.enabled, как и с вариантом chrome://user_chrome_files/content/ - можно не включать

скрытый текст
Image_002.png

Выделить код

Код:

try {
	CustomizableUI.createWidget({
		id: "b-sound-muted-all-tabs",
		type: "custom",
		label: "Переключить звук",
		tooltiptext: "ЛКМ: Переключить звук в выделенных вкладках\nСКМ: Закрыть другие вкладки с источником звука\nПКМ: Переключить звук во всех вкладках",
		defaultArea: CustomizableUI.AREA_NAVBAR,
		localized: false,
		onBuild(doc) {
			var trbn = doc.createXULElement("toolbarbutton"),
			win = doc.defaultView;
			trbn.id = "b-sound-muted-all-tabs";
			trbn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
			trbn.setAttribute("label", "Переключить звук");
			trbn.setAttribute("tooltiptext", "ЛКМ: Переключить звук в выделенных вкладках\nСКМ: Закрыть другие вкладки с источником звука\nПКМ: Переключить звук во всех вкладках");
			trbn.setAttribute("context", false);
			trbn.setAttribute("image", "chrome://global/skin/media/audio.svg");
			trbn.addEventListener("click", e => {
				if (e.button == 0) {
					win.gBrowser.toggleMuteAudioOnMultiSelectedTabs(win.gBrowser.selectedTab);
				} else if (e.button == 1) {
					for (let tab of win.gBrowser.visibleTabs.filter(tab => !tab.selected && (tab.muted || tab.soundPlaying)))
						win.gBrowser.removeTab(tab);
				} else if (e.button == 2) {
					e.preventDefault();
					e.stopPropagation();
					let tabsToToggle;
					if (win.gBrowser.selectedTab.activeMediaBlocked) {
						tabsToToggle = win.gBrowser.visibleTabs.filter(tab => tab.activeMediaBlocked || tab.linkedBrowser.audioMuted);
					} else {
						let tabMuted = win.gBrowser.selectedTab.linkedBrowser.audioMuted;
						tabsToToggle = win.gBrowser.visibleTabs.filter(tab => (tab.linkedBrowser.audioMuted == tabMuted && !tab.activeMediaBlocked) || (tab.activeMediaBlocked && tabMuted));
					}
					for (let tab of tabsToToggle)
						tab.toggleMuteAudio();
				}
			});
			return trbn;
		},
	});
} catch(e) {}


Наткнулся недавно на вроде бы полезный скрипт. Исправляет контекстное меню журнала.
Можно использовать с 11-ой строки, подключив в custom_script_win.js

Было|Cтало
Image_001.pngImage_002.png

ПАРОЛИ/КУКИ
https://forum.mozilla-russia.org/viewto … 86#p788786

FindBar для custom_script_all_win.js в секцию load
https://forum.mozilla-russia.org/viewto … 27#p777227

Код для CB, но отлично работает в ucf и в новых версиях FF
Если раскомментировать /* */ , то искомое будет на всех вкладках, а не только на исходной.

Туда же горячая клавиша для FindBar

Выделить код

Код:

addEventListener('keydown', e=> {
  if(e.ctrlKey&e.code=="KeyF"&&!gFindBar.hidden) {
    e.preventDefault()+gFindBar.close()
  }
});

Dumby
Поправь, пожалуйста, кнопку HTTP Request Logger для UCF.

rubel

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

Выделить код

Код:

(async self => CustomizableUI.createWidget(({
	label: "Unnamed",
	tooltiptext: "Unnamed",
	fileName: "http-request-log.txt",
	images: {
		true: "data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/AAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/wAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/AAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/wAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/AAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/wAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/AAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/AwMD/AAAA/wAAAP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQ==",
		false: "data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/AwMD/AAAA/wAAAP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AACsQQAArEEAAKxBH/isQR/4rEEf+KxBH/isQR/4rEEf+KxBH/isQR/4rEEf+KxBH/isQQAArEEAAKxBAACsQQ=="
	},
	id: "ucf-httpRequestLogger",
	localized: false,
	init(pref) {
		var topic = "http-on-modify-request";
		this.toggle = () => Services.prefs.setBoolPref(pref, !this.active);
		var prefObs = () => {
			var val = Services.prefs.getBoolPref(pref, false);
			if (this.active ^ (this.active = val))
				Services.obs[`${val ? "add" : "remove"}Observer`](this, topic);
			this.setBtnsState();
		}
		prefObs();
		Services.prefs.addObserver(pref, prefObs);
		Services.obs.addObserver(function quit(s, t) {
			Services.obs.removeObserver(quit, t);
			Services.prefs.removeObserver(pref, prefObs);
			self.active && Services.obs.removeObserver(self, topic);
		}, "quit-application-granted");
		return self = this;
	},
	onCreated(btn) {
		btn._handleClick = this.toggle;
		btn.setAttribute("image", this.images[this.active]);
	},
	setBtnsState() {this.setBtnsState = () => {
		var img = this.images[this.active];
		var widget = CustomizableUI.getWidget(this.id);
		for(var win of CustomizableUI.windows)
			widget.forWindow(win).node?.setAttribute("image", img);
	}},
	log: "",
	observe(channel) {
		if (!(channel instanceof Ci.nsIHttpChannel)) return;
		this.log += `${
			channel.referrerInfo?.originalReferrer?.spec || "(none)"
		} ${
			channel.requestMethod 
		} ${
			channel.URI.spec
		}\r\n`;

		this.busy || this.write();
	},
	write() {
		var file = Services.dirsvc.get("Desk", Ci.nsIFile);
		file.append(this.fileName);
		var {path} = file;

		var {IOUtils} = Cu.getGlobalForObject(Cu);
		var modes = [{mode: "create"}, {mode: "append"}];
		var unbusy = () => {
			this.busy = false;
			this.log && this.write();
		}
		(this.write = () => {
			this.busy = true;
			var {log} = this;
			this.log = "";
			IOUtils.writeUTF8(path, log, modes[+file.exists()])
				.finally(unbusy);
		})();
	}
}).init("ucf.httpRequestLogger.enabled")))();

Dumby пишет

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

Выделить код

Код:

(async self => CustomizableUI.createWidget(({
	label: "Unnamed",
	tooltiptext: "Unnamed",
	fileName: "http-request-log.txt",
	images: {
		true: "data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/AAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/wAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/AAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/wAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/AAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/wAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/Zv+Z/2b/mf9m/5n/AAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/AwMD/AAAA/wAAAP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQ==",
		false: "data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AwMD/AAAA/wAAAP/AwMD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wMDA/wAAAP8AAAD/wMDA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8DAwP8AAAD/AAAA/8DAwP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/AwMD/AAAA/wAAAP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AACsQQAArEEAAKxBH/isQR/4rEEf+KxBH/isQR/4rEEf+KxBH/isQR/4rEEf+KxBH/isQQAArEEAAKxBAACsQQ=="
	},
	id: "ucf-httpRequestLogger",
	localized: false,
	init(pref) {
		var topic = "http-on-modify-request";
		this.toggle = () => Services.prefs.setBoolPref(pref, !this.active);
		var prefObs = () => {
			var val = Services.prefs.getBoolPref(pref, false);
			if (this.active ^ (this.active = val))
				Services.obs[`${val ? "add" : "remove"}Observer`](this, topic);
			this.setBtnsState();
		}
		prefObs();
		Services.prefs.addObserver(pref, prefObs);
		Services.obs.addObserver(function quit(s, t) {
			Services.obs.removeObserver(quit, t);
			Services.prefs.removeObserver(pref, prefObs);
			self.active && Services.obs.removeObserver(self, topic);
		}, "quit-application-granted");
		return self = this;
	},
	onCreated(btn) {
		btn._handleClick = this.toggle;
		btn.setAttribute("image", this.images[this.active]);
	},
	setBtnsState() {this.setBtnsState = () => {
		var img = this.images[this.active];
		var widget = CustomizableUI.getWidget(this.id);
		for(var win of CustomizableUI.windows)
			widget.forWindow(win).node?.setAttribute("image", img);
	}},
	log: "",
	observe(channel) {
		if (!(channel instanceof Ci.nsIHttpChannel)) return;
		this.log += `${
			channel.referrerInfo?.originalReferrer?.spec || "(none)"
		} ${
			channel.requestMethod 
		} ${
			channel.URI.spec
		}\r\n`;

		this.busy || this.write();
	},
	write() {
		var file = Services.dirsvc.get("Desk", Ci.nsIFile);
		file.append(this.fileName);
		var {path} = file;

		var {IOUtils} = Cu.getGlobalForObject(Cu);
		var modes = [{mode: "create"}, {mode: "append"}];
		var unbusy = () => {
			this.busy = false;
			this.log && this.write();
		}
		(this.write = () => {
			this.busy = true;
			var {log} = this;
			this.log = "";
			IOUtils.writeUTF8(path, log, modes[+file.exists()])
				.finally(unbusy);
		})();
	}
}).init("ucf.httpRequestLogger.enabled")))();

Dumby, большое спасибо.

Dumby
Спасибо, прекрасно работает. :)

unter_officer, rubel
Парни, в какой custom_script, win или all_win его прописывать? И название должно быть ucf_httpRequestLogger?

добавлено   чорд возьми, нашел его в персонализации :dumb:

Новая версия UserChromeFiles со многими изменениями в коде, готова к использованию насколько могу судить,
но обновляться нужно осторожно и прочитав обновленную инструкцию,
из известных проблем, слетят ранее добавленные кнопки с панелей UserChromeFiles, если кому лень как мне лезть в персонализацию,
можно попробовать в консоле браузера выполнить код

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

Выделить код

Код:

var uiCustomization = "browser.uiCustomization.state";
Services.prefs.setCharPref(uiCustomization, Services.prefs.getCharPref(uiCustomization, "").replace(/"add-/g, "\"ucf-"));


врядли это затронет другие кнопки хоть и вероятность есть, или при закрытом браузере отредактируйте
настройку "browser.uiCustomization.state" в prefs.js заменив "add- на "ucf-


PS: Да и коды для загрузки доп. файлов скриптов теперь не требуются ...

Vitaliy V.
А с этим как... https://forum.mozilla-russia.org/viewto … 01#p788301 ?

kokoss
Для окна браузера - Включить скрипты: чекбокс "Для докум. окна браузера [ChromeOnly]"
и в CustomStylesScripts.jsm добавить в массив load: [ // По событию load    или domload: [ // По событию DOMContentLoaded
нужные вам скрипты, как видите здесь уже добавленны в закомментированном виде Special Widgets и Auto Hide Sidebar

скрытый текст
    scriptschrome: { // Для докум. окна браузера [ChromeOnly]
        domload: [ // По событию DOMContentLoaded

        ],
        load: [ // По событию load
            // { path: "special_widgets.js", ucfobj: true, }, // <-- Special Widgets
            // { path: "auto_hide_sidebar.js", ucfobj: true, }, // <-- Auto Hide Sidebar
        ],
    },

Да и параметр ucfobj должен быть true для моих скриптов (если не указано), и false или отсутствовать для других (если автором скрипта не указано обратное)

PS: custom_script_win.js и custom_script_all_win.js не нужно подключать в CustomStylesScripts.jsm
только в интерфейсе настроек включить "Для докум. окна браузера [ChromeOnly]" или "Для докум. всех окон [ChromeOnly]" соответственно
эти файлы не переименовывать и не удалять.
А custom_script.js можно раскомментировать в CustomStylesScripts.jsm ну или удалить или добавить с другим именем.


И кстати коды загрузчиков скриптов для custom_script_win.js custom_script_all_win.js также будут работать можете использовать их если нравится

Vitaliy V. пишет

Для окна браузера - Включить скрипты: чекбокс "Для докум. окна браузера [ChromeOnly]"
и в CustomStylesScripts.jsm добавить в массив load: [ // По событию load    или domload: [ // По событию DOMContentLoaded
нужные вам скрипты, как видите здесь уже добавленны в закомментированном виде Special Widgets и Auto Hide Sidebar
скрытый текст

Да и параметр ucfobj должен быть true для моих скриптов (если не указано), и false или отсутствовать для других (если автором скрипта не указано обратное)

PS: custom_script_win.js и custom_script_all_win.js не нужно подключать в CustomStylesScripts.jsm
только в интерфейсе настроек включить "Для докум. окна браузера [ChromeOnly]" или "Для докум. всех окон [ChromeOnly]" соответственно
эти файлы не переименовывать и не удалять.
А custom_script.js можно раскомментировать в CustomStylesScripts.jsm ну или удалить или добавить с другим именем

Пока не ясно как это работает, но надеюсь разберусь :)


Vitaliy V. пишет

И кстати коды загрузчиков скриптов для custom_script_win.js custom_script_all_win.js также будут работать можете использовать их если нравится

Спасибо!

kokoss пишет

Пока не ясно как это работает

А что не ясно, как работает (алгоритм, непонятно объяснил) или это не работает для вас?

Vitaliy V. пишет

или это не работает для вас?

Я же не сказал что не работает, просто многовато изменений.

непонятно объяснил)

да вроде понятно, если что спрошу

kokoss пишет

просто многовато изменений

Возможностей тоже прибавилось и потому что для более новых версий [firefox], 78+ теперь
было для 52+

Vitaliy V.
Спасибо за обновлённый комплект. Почти всё сразу заработало на подопытной 92.
Возникли только проблемы со скриптами для всех окон. ucf_wheretoopenlink.js и скрипты окна загрузок у меня не получилось запустить. В настройках включены все стили и скрипты. startupCache чистил постоянно.
Прописывал в CustomStylesScripts.jsm, поочерёдно в секции: domload: [ // По событию DOMContentLoaded, в load: [ // По событию load, по аналогии { path: "example_places.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, }, с другими именами. Запустить удалось только по-старинке с добавлением загрузчика в custom_script_all_win.js
Не знаю может чего недопонял, неправильно сделал, да вроде и вариантов не много. Хорошо бы без загрузчиков запустить, как я понял из вашего поста, это возможно. Полный код CustomStylesScripts.jsm, как изначально, ну примерно так, прописывал незапустившиеся скрипты. ucjsDownloadsManager.uc.js прописывал тоже выше ucjsDownloadsManager2.uc.js

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

Выделить код

Код:

var EXPORTED_SYMBOLS = ["UcfStylesScripts"];
var UcfStylesScripts = {
    /* ************************▼ Настройки ▼************************ */
    /**
    * Настройки стилей:
    *   path: путь к файлу от папки custom_styles
    *   type: права стиля AGENT_SHEET,  AUTHOR_SHEET или USER_SHEET
    */
    styleschrome: [ // Для докум. всех окон [ChromeOnly]
        // { path: "custom_styles_chrome_author.css", type: "AUTHOR_SHEET", sheet(f) { preloadSheet(this, f); }, },
        { path: "custom_styles_chrome_user.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, },
        { path: "special_widget.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, }, // <-- Special Widgets
        { path: "auto_hide_sidebar.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, }, // <-- Auto Hide Sidebar
    ],
    stylesall: [ // Для всех документов
        // { path: "custom_styles_all_agent.css", type: "AGENT_SHEET", sheet() { registerSheet(this); }, },
        // { path: "custom_styles_all_user.css", type: "USER_SHEET", sheet() { registerSheet(this); }, },
    ],
    /**
    * Настройки скриптов:
    *   path: путь к скрипту от папки custom_scripts
    *   urlregxp: Адрес где работает скрипт в регулярном выражении, только Для докум. всех окон [ChromeOnly]
    *   ucfobj: true - загружать скрипт в специально созданный объект либо в window, для скриптов В фоне [System Principal] не используется
    */
    scriptschrome: { // Для докум. окна браузера [ChromeOnly]
        domload: [ // По событию DOMContentLoaded

        ],
        load: [ // По событию load
            // { path: "special_widgets.js", ucfobj: true, }, // <-- Special Widgets
            // { path: "auto_hide_sidebar.js", ucfobj: true, }, // <-- Auto Hide Sidebar
            { path: "scripts3/favicon_in_urlbar.js", ucfobj: true, },
            { path: "scripts3/restart_item_in_menu.js", ucfobj: true, },
            { path: "scripts3/urlbarhistorydropmarker.js", ucfobj: true, },
            { path: "scripts3/contextmenuopenwith.js", ucfobj: true, }, 
            { path: "scripts3/add_bookmark_to_bookmarks_menu.js", ucfobj: false, },
            { path: "scripts3/pageInfo.js", ucfobj: false, },
            { path: "scripts3/places_addBookmarks.js", ucfobj: false, },
            { path: "scripts3/search_engine_icon.js", ucfobj: false, },
            { path: "scripts3/tabs_focus.js", ucfobj: false, },
            { path: "scripts3/tabstoolbar_doubleclick_opennewtab.js", ucfobj: false, },
            { path: "scripts2/ucjsDownloadsManager.uc.js", ucfobj: false, },
        ],
    },
    scriptsallchrome: { // Для докум. всех окон [ChromeOnly]
        domload: [ // По событию DOMContentLoaded
            // { path: "example_places.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, },
            { path: "scripts2/ucjsDownloadsManager2.uc.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, },
        ],
        load: [ // По событию load
            // { path: "example_places.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, },
        ],
    },
    scriptsbackground: [ // В фоне [System Principal]
        { path: "custom_script.js", },
    ],
};
    /* ************************▲ Настройки ▲************************ */

var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var UcfSSS = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
var preloadSheet = (obj, func) => {
    try {
        let uri = Services.io.newURI(`chrome://user_chrome_files/content/custom_styles/${obj.path}`);
        let type = UcfSSS[obj.type];
        let preload = UcfSSS.preloadSheet(uri, type);
        (obj.sheet = f => {
            try {
                f(preload, type);
            } catch (e) {}
        })(func);
    } catch (e) {
        obj.sheet = () => {};
    }
};
var registerSheet = async obj => {
    try {
        let uri = Services.io.newURI(`chrome://user_chrome_files/content/custom_styles/${obj.path}`);
        let type = UcfSSS[obj.type];
        if (!UcfSSS.sheetRegistered(uri, type))
            UcfSSS.loadAndRegisterSheet(uri, type);
    } catch (e) {}
};

Окно загрузок работает частично при добавлении ucjsDownloadsManager.uc.js в секцию scriptschrome: { // Для докум. окна браузера [ChromeOnly], ну это и понятно.
Добавление второй части скрипта в эту же секцию результата не дало, ну это и было ожидаемо.

sandro79 пишет

ucf_wheretoopenlink.js

Ок добавил ещё параметр где можно указать функцию которая выполнится при загрузке скрипта
func: Функция в виде строки которая выполнится при загрузке скрипта ...


вот так это должно выглядеть для окна библиотеки

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

Выделить код

Код:

load: [ // По событию load
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.places();" },
        ],


и для CustomStylesScriptsChild.jsm если нужно в контенте
скрытый текст

Выделить код

Код:

pageshow: [ // По событию pageshow
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, func: "ucf_where_to_open_link.places();" },
        ],

sandro79 пишет

может чего недопонял, неправильно сделал

Да с чего вы взяли что ваш ucjsDownloadsManager это окно библиотеки? Там все вместе и заклади и загрузки...
Но и я накосячил с для события load, забыл передать url документа в фукцию
Однако по событию DOMContentLoaded у вас бы сработало с этим адресом

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

Выделить код

Код:

/chrome:\/\/browser\/content\/downloads\/contentAreaDownloadsView\.xhtml/

Vitaliy V. пишет

Ок добавил ещё параметр где можно указать функцию которая выполнится при загрузке скрипта

Я так понял, что это было добавлено в нижнюю секцию "Настройки", обновил в обоих файлах и обновил user_chrome.js. В окне библиотеке сработало, для CustomStylesScriptsChild.jsm не добавлял, в контенте не нужно. Но вот почему не работает из боковой панели, значка журнала на панели, журнала из панели меню, ну то есть, как я понимаю в окне браузера "Для докум. окна браузера"? Вот это самое главное, о чём я забыл упомянуть выше, что вообще нигде не работает, кроме как если добавить загрузчик в custom_script_all_win.js, хотя можно и в custom_script_win.js.

Да с чего вы взяли что ваш ucjsDownloadsManager это окно библиотеки?

Да, в ходе экспериментов я это понял, когда оставил в адресе в регулярном выражении только chrome:/ - так кажется и серипт заработал. Сейчас с вашим адресом тоже работает. Только вот с ucf_wheretoopenlink.js не могу до конца разобраться.
Код CustomStylesScripts.jsm на данный момент ниже. Добавил  { path: "scripts2/ucf_wheretoopenlink.js", ucfobj: false,  }, в секцию Для докум. окна браузера [ChromeOnly], не работает, это добавлял func: "ucf_where_to_open_link.places();", ни так ни так не идёт

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

Выделить код

Код:

var EXPORTED_SYMBOLS = ["UcfStylesScripts"];
var UcfStylesScripts = {
    /* ************************▼ Настройки ▼************************ */
    /**
    * Настройки стилей:
    *   path: путь к файлу от папки custom_styles
    *   type: права стиля AGENT_SHEET,  AUTHOR_SHEET или USER_SHEET
    */
    styleschrome: [ // Для докум. всех окон [ChromeOnly]
        // { path: "custom_styles_chrome_author.css", type: "AUTHOR_SHEET", sheet(f) { preloadSheet(this, f); }, },
        { path: "custom_styles_chrome_user.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, },
        { path: "special_widget.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, }, // <-- Special Widgets
        { path: "auto_hide_sidebar.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, }, // <-- Auto Hide Sidebar
    ],
    stylesall: [ // Для всех документов
        { path: "custom_styles_all_agent.css", type: "AGENT_SHEET", sheet() { registerSheet(this); }, },
        // { path: "custom_styles_all_user.css", type: "USER_SHEET", sheet() { registerSheet(this); }, },
    ],
    /**
    * Настройки скриптов:
    *   path: путь к скрипту от папки custom_scripts
    *   urlregxp: Адрес где работает скрипт в регулярном выражении, только Для докум. всех окон [ChromeOnly]
    *   ucfobj: true - загружать скрипт в специально созданный объект либо в window, для скриптов В фоне [System Principal] не используется
    *   func: Функция в виде строки которая выполнится при загрузке скрипта, только Для докум. всех окон [ChromeOnly]
    */
    scriptschrome: { // Для докум. окна браузера [ChromeOnly]
        domload: [ // По событию DOMContentLoaded

        ],
        load: [ // По событию load
            // { path: "special_widgets.js", ucfobj: true, }, // <-- Special Widgets
            // { path: "auto_hide_sidebar.js", ucfobj: true, }, // <-- Auto Hide Sidebar
            { path: "scripts3/favicon_in_urlbar.js", ucfobj: true, },
            { path: "scripts3/restart_item_in_menu.js", ucfobj: true, },
            { path: "scripts3/urlbarhistorydropmarker.js", ucfobj: true, },
            { path: "scripts3/contextmenuopenwith.js", ucfobj: true, }, 
            { path: "scripts3/add_bookmark_to_bookmarks_menu.js", ucfobj: false, },
            { path: "scripts3/pageInfo.js", ucfobj: false, },
            { path: "scripts3/places_addBookmarks.js", ucfobj: false, },
            { path: "scripts3/search_engine_icon.js", ucfobj: false, },
            { path: "scripts3/tabs_focus.js", ucfobj: false, },
            { path: "scripts3/tabstoolbar_doubleclick_opennewtab.js", ucfobj: false, },
            { path: "scripts2/ucjsDownloadsManager.uc.js", ucfobj: false, },
            { path: "scripts2/ucf_wheretoopenlink.js", ucfobj: false,  },
        ],
    },
    scriptsallchrome: { // Для докум. всех окон [ChromeOnly]
        domload: [ // По событию DOMContentLoaded
            // { path: "example_places.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, },
            { path: "scripts2/ucjsDownloadsManager2.uc.js", urlregxp: /chrome:\/\/browser\/content\/downloads\/contentAreaDownloadsView\.xhtml/, ucfobj: false, },
        ],
        load: [ // По событию load
            // { path: "example_places.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.places();" },
        ],
    },
    scriptsbackground: [ // В фоне [System Principal]
        { path: "custom_script.js", },
    ],
};
    /* ************************▲ Настройки ▲************************ */

var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var UcfSSS = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
var preloadSheet = (obj, func) => {
    try {
        let uri = Services.io.newURI(`chrome://user_chrome_files/content/custom_styles/${obj.path}`);
        let type = UcfSSS[obj.type];
        let preload = UcfSSS.preloadSheet(uri, type);
        (obj.sheet = f => {
            try {
                f(preload, type);
            } catch (e) {}
        })(func);
    } catch (e) {
        obj.sheet = () => {};
    }
};
var registerSheet = async obj => {
    try {
        let uri = Services.io.newURI(`chrome://user_chrome_files/content/custom_styles/${obj.path}`);
        let type = UcfSSS[obj.type];
        if (!UcfSSS.sheetRegistered(uri, type))
            UcfSSS.loadAndRegisterSheet(uri, type);
    } catch (e) {}
};

sandro79
Вот так для браузера, но прежде обновите user_chrome.js я там поправил ещё

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

Выделить код

Код:

{ path: "scripts2/ucf_wheretoopenlink.js", ucfobj: false, func: "ucf_where_to_open_link.browser();" },


А это для других докум.
скрытый текст

Выделить код

Код:

{ path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/bookmarksSidebar\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.bookmarksSidebar();" },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/historySidebar\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.historySidebar();" },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.places();" },

Дело в том что нужно разные функции добавлять, а это "ucf_where_to_open_link.places();" только для библиотеки
Иначе этот скрипт пришлось бы делить на 4 части, или в скрипте снова проверять url, а так в каждом документе выполняется разный код при вызове этих функций

Vitaliy V.
Обновил user_chrome.js, пути с функциями прописал, заработало везде. Ну теперь всё в принципе настроено. Для контента тоже добавил, раз есть возможность лишним не будет.
custom_script.js с кнопками и скриптами LinkWinActor и UCFNewTabPage, сменой иконки поиковика, пока по-старинке с загрузчиком оставил. Вечером наверно тоже переподключу по-новому. Огромное Спасибо, буду тестировать.

CustomStylesScripts.jsm

Выделить код

Код:

var EXPORTED_SYMBOLS = ["UcfStylesScripts"];
var UcfStylesScripts = {
    /* ************************▼ Настройки ▼************************ */
    /**
    * Настройки стилей:
    *   path: путь к файлу от папки custom_styles
    *   type: права стиля AGENT_SHEET,  AUTHOR_SHEET или USER_SHEET
    */
    styleschrome: [ // Для докум. всех окон [ChromeOnly]
        // { path: "custom_styles_chrome_author.css", type: "AUTHOR_SHEET", sheet(f) { preloadSheet(this, f); }, },
        { path: "custom_styles_chrome_user.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, },
        { path: "special_widget.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, }, // <-- Special Widgets
        { path: "auto_hide_sidebar.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, }, // <-- Auto Hide Sidebar
    ],
    stylesall: [ // Для всех документов
        { path: "custom_styles_all_agent.css", type: "AGENT_SHEET", sheet() { registerSheet(this); }, },
        // { path: "custom_styles_all_user.css", type: "USER_SHEET", sheet() { registerSheet(this); }, },
    ],
    /**
    * Настройки скриптов:
    *   path: путь к скрипту от папки custom_scripts
    *   urlregxp: Адрес где работает скрипт в регулярном выражении, только Для докум. всех окон [ChromeOnly]
    *   ucfobj: true - загружать скрипт в специально созданный объект либо в window, для скриптов В фоне [System Principal] не используется
    *   func: Функция в виде строки которая выполнится при загрузке скрипта, только Для докум. всех окон [ChromeOnly]
    */
    scriptschrome: { // Для докум. окна браузера [ChromeOnly]
        domload: [ // По событию DOMContentLoaded

        ],
        load: [ // По событию load
            // { path: "special_widgets.js", ucfobj: true, }, // <-- Special Widgets
            // { path: "auto_hide_sidebar.js", ucfobj: true, }, // <-- Auto Hide Sidebar
            { path: "scripts3/favicon_in_urlbar.js", ucfobj: true, },
            { path: "scripts3/restart_item_in_menu.js", ucfobj: true, },
            { path: "scripts3/urlbarhistorydropmarker.js", ucfobj: true, },
            { path: "scripts3/contextmenuopenwith.js", ucfobj: true, }, 
            { path: "scripts3/add_bookmark_to_bookmarks_menu.js", ucfobj: false, },
            { path: "scripts3/pageInfo.js", ucfobj: false, },
            { path: "scripts3/places_addBookmarks.js", ucfobj: false, },
            { path: "scripts3/search_engine_icon.js", ucfobj: false, },
            { path: "scripts3/tabs_focus.js", ucfobj: false, },
            { path: "scripts3/tabstoolbar_doubleclick_opennewtab.js", ucfobj: false, },
            { path: "scripts2/ucjsDownloadsManager.uc.js", ucfobj: false, },
            { path: "scripts2/ucf_wheretoopenlink.js", ucfobj: false, func: "ucf_where_to_open_link.browser();" },
        ],
    },
    scriptsallchrome: { // Для докум. всех окон [ChromeOnly]
        domload: [ // По событию DOMContentLoaded
            // { path: "example_places.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, },
            { path: "scripts2/ucjsDownloadsManager2.uc.js", urlregxp: /chrome:\/\/browser\/content\/downloads\/contentAreaDownloadsView\.xhtml/, ucfobj: false, },
        ],
        load: [ // По событию load
            // { path: "example_places.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.places();" },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/bookmarksSidebar\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.bookmarksSidebar();" },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/historySidebar\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.historySidebar();" },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, ucfobj: false, func: "ucf_where_to_open_link.places();" },
        ],
    },
    scriptsbackground: [ // В фоне [System Principal]
        { path: "scripts/add-sound-realtek-app.js", },
        { path: "scripts/Close-Tabs-button.js", },
        { path: "scripts/downloadPauseResumeButton.js", },
        { path: "scripts/ExtensionOptionsMenu.js", },
        { path: "scripts/LinkWinActor.js", },
        { path: "scripts/PotPlayer.js", },
        { path: "scripts/To_switch_proxy.js", },
        { path: "scripts/ucf-copyURL.js", },
        { path: "scripts/UCFNewTabPage.js", },
        { path: "scripts/undo_closetab_button.js", },
        { path: "scripts/yandex@search.js", },
    ],
};
    /* ************************▲ Настройки ▲************************ */

var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var UcfSSS = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
var preloadSheet = (obj, func) => {
    try {
        let uri = Services.io.newURI(`chrome://user_chrome_files/content/custom_styles/${obj.path}`);
        let type = UcfSSS[obj.type];
        let preload = UcfSSS.preloadSheet(uri, type);
        (obj.sheet = f => {
            try {
                f(preload, type);
            } catch (e) {}
        })(func);
    } catch (e) {
        obj.sheet = () => {};
    }
};
var registerSheet = async obj => {
    try {
        let uri = Services.io.newURI(`chrome://user_chrome_files/content/custom_styles/${obj.path}`);
        let type = UcfSSS[obj.type];
        if (!UcfSSS.sheetRegistered(uri, type))
            UcfSSS.loadAndRegisterSheet(uri, type);
    } catch (e) {}
};

Переподключил скрипты scriptsbackground: [ // В фоне [System Principal] Всё подхватилось и работает. Код под спойлером обновил.


CustomStylesScriptsChild.jsm

Выделить код

Код:

var EXPORTED_SYMBOLS = ["UcfCustomStylesScriptsChild"];
var UcfStylesScripts = {
    /* ************************▼ Настройки ▼************************ */
    /**
    * Настройки стилей:
    *   path: путь к файлу от папки custom_styles
    *   type: права стиля AGENT_SHEET,  AUTHOR_SHEET или USER_SHEET
    */
    stylescontent: [
        // { path: "custom_styles_content_author.css", type: "AUTHOR_SHEET", sheet(f) { preloadSheet(this, f); }, },
        { path: "custom_styles_content_user.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, },
    ],
    /**
    * Настройки скриптов:
    *   path: путь к скрипту от папки custom_scripts
    *   urlregxp: Адрес где работает скрипт, в регулярном выражении
    *   func: Функция в виде строки которая выполнится при загрузке скрипта
    */
    scriptscontent: {
        DOMWindowCreated: [ // По событию DOMWindowCreated
            // { path: "example_all_about.js", urlregxp: /about:.*/, },
        ],
        pageshow: [ // По событию pageshow
            // { path: "example_downloads.js", urlregxp: /about:downloads/, },
            { path: "scripts2/ucf_wheretoopenlink.js", urlregxp: /chrome:\/\/browser\/content\/places\/places\.xhtml/, func: "ucf_where_to_open_link.places();" },
            { path: "scripts2/ucjsDownloadsManager2.uc.js",  urlregxp: /about:downloads/, },
        ],
    },
};
    /* ************************▲ Настройки ▲************************ */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var UcfSSS = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
var preloadSheet = async (obj, func) => {
    try {
        let uri = Services.io.newURI(`chrome://user_chrome_files/content/custom_styles/${obj.path}`);
        let type = UcfSSS[obj.type];
        let preload = await UcfSSS.preloadSheetAsync(uri, type);
        (obj.sheet = f => {
            try {
                f(preload, type);
            } catch (e) {}
        })(func);
    } catch (e) {
        obj.sheet = () => {};
    }
};
class UcfCustomStylesScriptsChild extends JSWindowActorChild {
    actorCreated() {
        var win = this.contentWindow;
        var href = this.href = win?.location.href;
        if (!href) return;
        var { addSheet } = win.windowUtils;
        for (let s of UcfStylesScripts.stylescontent)
            s.sheet(addSheet);
    }
    handleEvent(e) {
        var href = this.href;
        if (!href || href === "about:blank") return;
        for (let s of UcfStylesScripts.scriptscontent[e.type]) {
            try {
                if (s.urlregxp.test(href)) {
                    let win = this.contentWindow;
                    Services.scriptloader.loadSubScript(`chrome://user_chrome_files/content/custom_scripts/${s.path}`, win, "UTF-8");
                    if (s.func)
                        new win.Function(s.func).apply(win, null);
                }
            } catch (e) {}
        }
    }
}

Vitaliy V.
Спасибо за обновление. Уведомления пропали у этого скрипта, как их вернуть? Сейчас подключен в CustomStylesScripts.jsm, в scriptsbackground.

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

Выделить код

Код:

try {
    ((img, preventClearThumbs) => {
        CustomizableUI.createWidget({
            id: "bt-clear-part-history",
            label: "Очистить историю",
            tooltiptext: "Очистить историю",
            defaultArea: CustomizableUI.AREA_NAVBAR,
            onCreated: function(bt) {
                bt.image = img;
            },
            onCommand: function(event) {
                var win = event.target.ownerDocument.defaultView;
                var itemsToClear = [
                    "cookies",
                    "history",
                    "formdata",
                    "sessions",
                    "cache",
                 // "downloads",
                 // "offlineApps",
                 // "openWindows",
                    "pluginData",
                 // "siteSettings",
                ];
                var range = win.Sanitizer.getClearRange(0); // Диапазон очистки, 0 = все, 1,2,3 = часы, 4 = сегодня
                win.Sanitizer.sanitize(itemsToClear, {
                    ignoreTimespan: !range,
                    range,
                }).then(() => {
                    var alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
                    alertsService.showAlertNotification(img, "Данные очищены!", "", false);
                    win.setTimeout(()=> alertsService.closeAlert(), 2000);
                });
            }
        });
    })("data:image/png;base64,i...", null);
} catch(e) {}


В CustomStylesScripts.jsm присутстуют раскомментированные строки:
            { path: "translatetextorpage.js", ucfobj: true, },
            { path: "contextproxy.js", ucfobj: true, },
но таких скриптов в комплекте нет.

_zt пишет

Уведомления пропали у этого скрипта, как их вернуть? ... "data:image/png;base64,i..."

Иконки то нет поэтому и не показывает нет не поэтому, значит у вас ошибка возникает во время очистки, консоль смотрите что пишет

_zt пишет

В CustomStylesScripts.jsm присутстуют раскомментированные строки:

Это я из FullTheme забыл убрать прежде чем отправить


Обновил в FullTheme файлы: common.css, CustomStylesScripts.jsm, CustomStylesScriptsChild.jsm
и в UserChromeFiles файлы: user_chrome.js, CustomStylesScripts.jsm, CustomStylesScriptsChild.jsm

Vitaliy V.

Error: Could not get children of file(...\LocalAppData\Temp\thumbnails) because it does not exist PromiseWorker.jsm:106

На старом то ucf этот скрипт уведомление выводит. Иконку я здесь из кода удалил так как много места занимает.
   
Проверил на старом ucf, уведомление есть. thumbnails отключаю уже много лет и уведомлениям это никогда не мешало, да и с удаленным browser.pagethumbnails.capturing_disabled, в этой версии ucf, уведомлений нет, а папка все равно по тому пути не создается.

_zt пишет

Error: Could not get children of file(...\LocalAppData\Temp\thumbnails) because it does not exist PromiseWorker.jsm:106

На старом то ucf этот скрипт уведомление выводит.

Какое отношение имеет  ucf к глюкам при очистке миниатюр сайтов, у вас же не может установленно на одном и том же профиле одновременно старый и новый ucf, чтобы это утверждать, а если профили разные то и подавно. Проверте есть ли такая ошибка при штатной очистке из браузера, если хотите с  ucf и без

Vitaliy V.
Профиль один и тот же, я только его собрал и тут вы со своим обновлением. :) (шутка) Я его пересобрал, на тех же скриптах, стилях, расширениях и с теми же конфигами, за исключением вашего config.js. Отвалился стиль для адресной строки из userChrome.css, пришлось его перенести в CustomStylesScripts.jsm в секцию styleschrome с правами USER_SHEET, никак по другому он не заработал. Скрипты из custom_script.js я все переподключил в CustomStylesScripts.jsm в секцию scriptsbackground, в том числе и обсуждаемый. Скрипты custom_script_win.js оставил на месте, так как решил не заморачиваться. Скриптов custom_script_all_win.js у меня не оказалось. Т.е. две проблемы стиль и этот скрипт. Ясен пень, что ucf виноват.
   
Но оказалось все проще, смотрите мультики:

Находим виновника

01.1632267720.gif

И отправляем его в ссылку

02.1632267763.gif
А вообще вы молодец, а виноват во всем я. Я же его выпросил. :)

Vitaliy V.
А можно как-то вернуть в новый UCF возможность использования стилей для веб-страниц через файл custom_styles_content_user.css?
Может опцией, если надо может, чтоб включить можно было в CustomStylesScriptsChild.jsm. У меня в старом комплекте в custom_style_user.css добавлено несколько кодов для страниц, в частности, очень нужный для меня :root { text-decoration-skip-ink: none;}, для этого форума стиль.
Можно конечно включить toolkit.legacyUserProfileCustomizations.stylesheets, добавить в папку chrome файл userContent.css и по-старинке использовать, но это не работает в безопасном режиме, да и вообще... Вообще удобно в старом UCF то, что стили и для интерфейса и для страниц можно использовать/подгонять в одном файле css.
Этот код, только с regexp, тоже использую для веб-страниц

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

Выделить код

Код:

@-moz-document regexp("(?:https?|ftp|file):\/\/.*") {
input, textarea {
    /* border: 1px solid rgba(131,137,150,.5); */
    outline: 1px solid transparent;
}
}

В новом комплекте непонятно как всё вышеперечисленное запустить...

sandro79 пишет

в старом комплекте в custom_style_user.css

А в новом в custom_styles_all_user.css

Dumby пишет

А в новом в custom_styles_all_user.css

Да, работает там, в трёх соснах заблудился. Спасибо за подсказку :beer: Вот надо же, так облажаться :dumb:


И chrome и content работает, как и в старом. Всё отлично.

del

Vitaliy V.
Починилось.
   
Нашел еще несколько проблем, давайте по порядку, так как возможно они взаимосвязаны:
2021.1632357561.png
Вот я просто слов без мата не нахожу, что бы описать этот цвет, именно в этом диалоге оно где, в common.css ?

@media (-moz-toolbar-prefers-color-scheme: dark) {
:root, dialog, menu-button, login-filter, login-item, login-intro, login-list, fxaccounts-button, remove-logins-dialog,
    import-error-dialog, import-summary-dialog, confirmation-dialog, info-item, message-bar, addon-updates-message {
>>
    --in-content-primary-button-background: var(--blue-50) !important;

   
Если да, то тогда оно не работает, цвет не меняется. В --blue-50 тоже.
   
После переноса в userChrome.css правило работает.

_zt
Поделись, пожалуйста этой закладкой Старый about:config
У меня даже нет такой папки chrome://user_chrome_files/content/aboutconfig/

rubel
https://forum.mozilla-russia.org/viewto … 75#p789675


Add,

скрин
e0101f473944.png

kokoss
OK, спасибо. все получилось.

_zt пишет

оно где, в common.css ?

Нет в common.css только контент.
В common_win.css я некоторые окна добавил, это туда же url("chrome://browser/content/places/bookmarkProperties.xhtml")
Ну я потом добавлю другие окна где есть <dialog>


Вроде готово, обновил FullTheme
и UserChromeFiles ещё раз обновил надеюсь пока больше не буду трогать если только баг всплывет.


upd: обновил Sidebar Tabs и Тултипы с URL https://forum.mozilla-russia.org/viewto … 02#p792702

Vitaliy V.
Починилось.Спасибо.
   
Можете объяснить?

Прошлая версия
02.1632481807.gif

_zt пишет

Можете объяснить?

А что тут объяснять
Отключили переменную в chrome://global/skin/in-content/common.css
для темной темы
@media (-moz-toolbar-prefers-color-scheme: dark)
Но переменная для светлой темы осталась именно она и дает этот синий цвет
а не от FullTheme. Ну или это вы там что-то наподключали, но такого цвета #2b71e4 в моей теме нет.
Впрочем gif же у вас цвет искажен скорее всего

Vitaliy V.
Искажен. В общем, ничего не понял, но спасибо что исправили. Я там ничего не менял в пространствах имен. В общем, спасибо.

Dumby
Помогите пожалуйста. Использую Ваш скрипт закрытия всех вкладок кроме активной, только со своей иконкой

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

Выделить код

Код:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACNUlEQVQ4jY2TT08aQRjG59LL7mk/gxdvXmw/QD+MX6AePNhk0jThMGljEzLpxio0arQWMGpqomXBP7TQ3YpQ5J8CQf65C7vUKK61Hp4eFhMwBPskc5iZzO9987zPENIV9WyOTkxMPCGPaJIxcdq7NdJ3SJcj4+/jdns+WvMPg1AqC+5vujKjdhrUEx4lhBDyciE0NqPZbU8G8BWAz9pgyCRj4oKqK0t5wJsF5MPr+rS8NkKmOJfc301tMQesl4BQFdhJ9UMolYX1pK5slYGNEuAvAF7VCFJKBUIIIS7OpYW4pX4pA/t14KgJRLMOhDEm7mZ1JaYDB3UgXAXWkkaQUlnoa9HFubSRMrXoOXBsAuVLIFGoBuJFXcn/BtIWkGgBoUxP5YdycS7tnVhqvg3UO0D7D9CygUbHAaoFI0hlefDje3HOpWTZ1Jo2cHMH2HeAeQMkS0Mq94oxJv4qG4p5A1z9ddb5NRDL1XyP5oTKshA71ZXiBVC5dFpv2kDlCshYwHZqCIQxJm4f64pmOGalLUAt6ErqzFKLF0CyBUQagO9wAIRSWViN68rOGRCqOeNSum5zzqUfRVP7aQDhGrBZAuaiPZBJxsS5mK6snDgB2SgBgUT/nDnnUjBnqV8rQKAIfMwCM3tdyOul/Wfuo9vObNqJqEcd7LaLc8mfNLVPp8CHNPBWta0X8qbzH+jq4fM32m3n3cGAhD2AzGuWyuO2RZcj432XU4u7T/9nzi7OpVcrobH7/T9AEdt1RBP8kgAAAABJRU5ErkJggg==

Недавно решил заменить её на svg из допротоновских версий chrome://browser/skin/panel-icon-cancel.svg
Закинул в папку svg комплекта эту иконку, чтоб белой была на тёмном фоне без надобности включения svg.context-properties.content.enabled.
Прописал путь к иконке chrome://user_chrome_files/content/custom_styles/svg/panel-icon-cancel.svg, иконка появилась, всё работает нормально
скрытый текст

Выделить код

Код:

CustomizableUI.createWidget({
	id: "Close-Tabs-button",
	label: "Закрыть другие вкладки",
	tooltiptext: "Закрыть другие вкладки",
	defaultArea: CustomizableUI.AREA_NAVBAR,
	localized: false,
	onCreated(btn) {
		btn._handleClick = this.close;
		btn.setAttribute("image", "chrome://user_chrome_files/content/custom_styles/svg/panel-icon-cancel.svg");
	},
	close() {
		var gb = this.ownerGlobal.gBrowser;
		gb.removeAllTabsBut(gb.selectedTab);
	}
});

Но вот только маленькая она стала, меньше чем та что была. Я её чуть увеличил стилем
скрытый текст
Image_001.png

Выделить код

Код:

#Close-Tabs-button > image {
    padding: 2px !important;
}

Может можно добавить как-то тоже стиль выше в код скрипта, чтоб всё в одном месте было?

sandro79
У меня снова предложение чуть поперёк просьбы.
Там <svg> 32x32, а <path> 20x20 по-центру,
то есть как бы отступ 6px со всех сторон (поэтому выглядит маленькой).


Но можно во viewBox подогнать x, y, width и height.
Допустим, максимально. Меняем в самой svg'ке viewBox="0 0 32 32"
на viewBox="6 6 20 20" и отступа не будет совсем, тогда будет выглядеть крупнее.
Если положительного результата не получится, дай знать, полезу в js-код.

Dumby пишет

Допустим, максимально. Меняем в самой svg'ке viewBox="0 0 32 32"
на viewBox="6 6 20 20" и отступа не будет совсем, тогда будет выглядеть крупнее

6 6 20 20 всё-же крупновато получается. Но попробовал подрегулировать до viewBox="5 5 22 22", почти как с оригинальной svg и стилем, но чуть всё-равно крупнее. А 6 6 23 23 попробовал - размер визуально вроде не отличается, но сдвигается вверх и влево. Никак не получается один к одному подогнать.
Dumby
Ну если можно, добавьте пожалуйста css в js-код, ну почти подогнал как с оригинальной и стилем, но крупновато всё же с viewBox="5 5 22 22".
А за метод правки и полезную информацию Большое Спасибо, а то я пытался только цифры 32 менять в коде svg, теперь буду хоть это знать.

sandro79 пишет

сдвигается вверх и влево

Вот все центрированные варианты

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

Выделить код

Код:

viewBox="0 0 32 32"
viewBox="1 1 30 30"
viewBox="2 2 28 28"
viewBox="3 3 26 26"
viewBox="4 4 24 24"
viewBox="5 5 22 22"
viewBox="6 6 20 20"


Хотя, может можно не целые числа, не проверял.

добавьте пожалуйста css в js-код

Допустим, в атрибут style. Может не сработать,
если user или agent стилями приколочено, тогда снова дай знать.

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

Выделить код

Код:

CustomizableUI.createWidget({
	id: "Close-Tabs-button",
	label: "Закрыть другие вкладки",
	tooltiptext: "Закрыть другие вкладки",
	defaultArea: CustomizableUI.AREA_NAVBAR,
	localized: false,
	onCreated(btn) {
		btn.render = this.render;
		btn._handleClick = this.close;
		btn.setAttribute("image", "chrome://user_chrome_files/content/custom_styles/svg/panel-icon-cancel.svg");
	},
	render() {
		delete this.render;
		this.render();
		this.icon.style.setProperty("padding", "2px", "important");
	},
	close() {
		var gb = this.ownerGlobal.gBrowser;
		gb.removeAllTabsBut(gb.selectedTab);
	}
});

Dumby пишет

Вот все центрированные варианты

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

Допустим, в атрибут style. Может не сработать, если user или agent стилями приколочено, тогда снова дай знать

Отлично всё, сработало! Теперь один в один. Огромное Вам Спасибо за помощь :beer:

sandro79
Всё таки проверил дробные числа. Похоже работает.
Вот с шагом в одну десятую. Ну, это я уже просто так, на интерес.

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

Выделить код

Код:

viewBox="0 0 32 32"
viewBox="0.1 0.1 31.8 31.8"
viewBox="0.2 0.2 31.6 31.6"
viewBox="0.3 0.3 31.4 31.4"
viewBox="0.4 0.4 31.2 31.2"
viewBox="0.5 0.5 31 31"
viewBox="0.6 0.6 30.8 30.8"
viewBox="0.7 0.7 30.6 30.6"
viewBox="0.8 0.8 30.4 30.4"
viewBox="0.9 0.9 30.2 30.2"
viewBox="1 1 30 30"
viewBox="1.1 1.1 29.8 29.8"
viewBox="1.2 1.2 29.6 29.6"
viewBox="1.3 1.3 29.4 29.4"
viewBox="1.4 1.4 29.2 29.2"
viewBox="1.5 1.5 29 29"
viewBox="1.6 1.6 28.8 28.8"
viewBox="1.7 1.7 28.6 28.6"
viewBox="1.8 1.8 28.4 28.4"
viewBox="1.9 1.9 28.2 28.2"
viewBox="2 2 28 28"
viewBox="2.1 2.1 27.8 27.8"
viewBox="2.2 2.2 27.6 27.6"
viewBox="2.3 2.3 27.4 27.4"
viewBox="2.4 2.4 27.2 27.2"
viewBox="2.5 2.5 27 27"
viewBox="2.6 2.6 26.8 26.8"
viewBox="2.7 2.7 26.6 26.6"
viewBox="2.8 2.8 26.4 26.4"
viewBox="2.9 2.9 26.2 26.2"
viewBox="3 3 26 26"
viewBox="3.1 3.1 25.8 25.8"
viewBox="3.2 3.2 25.6 25.6"
viewBox="3.3 3.3 25.4 25.4"
viewBox="3.4 3.4 25.2 25.2"
viewBox="3.5 3.5 25 25"
viewBox="3.6 3.6 24.8 24.8"
viewBox="3.7 3.7 24.6 24.6"
viewBox="3.8 3.8 24.4 24.4"
viewBox="3.9 3.9 24.2 24.2"
viewBox="4 4 24 24"
viewBox="4.1 4.1 23.8 23.8"
viewBox="4.2 4.2 23.6 23.6"
viewBox="4.3 4.3 23.4 23.4"
viewBox="4.4 4.4 23.2 23.2"
viewBox="4.5 4.5 23 23"
viewBox="4.6 4.6 22.8 22.8"
viewBox="4.7 4.7 22.6 22.6"
viewBox="4.8 4.8 22.4 22.4"
viewBox="4.9 4.9 22.2 22.2"
viewBox="5 5 22 22"
viewBox="5.1 5.1 21.8 21.8"
viewBox="5.2 5.2 21.6 21.6"
viewBox="5.3 5.3 21.4 21.4"
viewBox="5.4 5.4 21.2 21.2"
viewBox="5.5 5.5 21 21"
viewBox="5.6 5.6 20.8 20.8"
viewBox="5.7 5.7 20.6 20.6"
viewBox="5.8 5.8 20.4 20.4"
viewBox="5.9 5.9 20.2 20.2"
viewBox="6 6 20 20"

Dumby пишет

Всё таки проверил дробные числа. Похоже работает. Вот с шагом в одну десятую. Ну, это я уже просто так, на интерес

Да, это работает. viewBox="4.7 4.7 22.6 22.6" подошло идеально, ну я не увидел визуально разницы. Спасибо Большое за таблицу. Тоже пригодится в дальнейшем.
Я ещё вчера заменил кнопки для окна загрузок и соответственно библиотеки на эту же(panel-icon-cancel.svg) и panel-icon-retry.svg из 78 [firefox], и там они тоже мелковаты получились, да и в 78 такие же были. panel-icon-cancel.svg для нового скрипта сделаю дубликат с др. именем, а для окон загрузок подберу уже из дробных. Правда, чуть раньше уже пробовал с целыми, с кнопкой отмены нормально всё, а вот кнопку panel-icon-retry.svg начинает коробить - круглая стрелка квадратной становится. Ну буду экспериментировать, не получится, ну там и не столь важно в принципе. Спасибо за помощь!


Почти получилось с кнопкой повтора с viewBox="4.7 4.7 22.6 22.6", округлости чуть снизу не хватает, но попробую ещё с другими числами.


Да так и оставлю в окнах загрузок, нормально.

Vitaliy V.
А как теперь тултипы подключить №10124?

voqabuhe
раскомментируйте эту строку https://github.com/VitaliyVstyle/Vitali … ts.jsm#L16
стиль добавляйте соответственно в custom_styles_all_agent.css

Vitaliy V.
Спасибо, подключились.

Vitaliy V.
Спасибо за обновлённый вариант urlbarhistorydropmarker. Тоже его забрал.
В старом варианте пропал фон наведения в 92+ и значок сдвинулся чуть вправо(но я его вернул на место правда), а тут всё с этим в порядке.
Но скрипт для значка "Копировать ссылку" оставлю, привык к нему за три с лишним года.
Виталий, а нельзя ли ещё добавить в urlbarhistorydropmarker действие "Обновить текущую страницу" по СКМ? Вообще было бы супер - три в одном!

sandro79
Сомнительное удобство обновлять страницу колесом, но добавил
в принципе можно на другую функцию заменить здесь
                    if (e.button === 1) {
                        BrowserReload();
                        return;
                    }

Vitaliy V. пишет

Сомнительное удобство обновлять страницу колесом, но добавил...

Ну да. Но ничего так, вполне удобно. Колесо у меня работает отлично.
Да и хочется чего-то необычного испробовать. Всё работает отлично. Огромное Спасибо :beer:


Добавлю себе в пост, чтоб не потерять, три варианта скрипта: старый с правленным мной под Протон адресом для иконки, новый без обновления по СКМ, и новый с обновлением страницы по СКМ

скрытый текст
browser.urlbar.suggest.history - true
browser.urlbar.suggest.topsites - false
скрытый текст
Допротоновский адрес для иконки chrome://global/skin/icons/arrow-dropdown-16.svg В версиях 92+ этот скрипт лучше не использовать

Выделить код

Код:

(this.urlbarhistorydropmarker = {
            dropmarker: null,
            provider: null,
            get style() {
                delete this.style;
                return this.style = "data:text/css;charset=utf-8," + encodeURIComponent(`
                    #urlbar .urlbar-history-dropmarker {
                        list-style-image: url("chrome://global/skin/icons/arrow-down.svg");
                        transition: opacity 0.15s ease;
                    }
                    #urlbar[switchingtabs] > #urlbar-input-container > .urlbar-history-dropmarker {
                        transition: none;
                    }
                    #urlbar[usertyping] > #urlbar-input-container > .urlbar-history-dropmarker {
                        display: none;
                    }
                    #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container:not(:hover) > #urlbar:not([focused]) > #urlbar-input-container > .urlbar-history-dropmarker {
                        opacity: 0;
                    }
                `);
            },
            init(that) {
                Services.prefs.addObserver("browser.urlbar.suggest.history", this);
                Services.prefs.addObserver("browser.urlbar.suggest.topsites", this);
                that.unloadlisteners.push("urlbarhistorydropmarker");
                var {UrlbarProviderTopSites: provider} = {UrlbarProviderTopSites: this.provider} = ChromeUtils.import("resource:///modules/UrlbarProviderTopSites.jsm");
                if (!provider.orig_PRIORITY) {
                    provider.orig_PRIORITY = provider.ucf_PRIORITY = provider.PRIORITY;
                    delete provider.constructor.prototype.PRIORITY;
                    Object.defineProperty(provider.constructor.prototype, "PRIORITY", {
                        enumerable: true,
                        get() {
                            var priory = this.ucf_PRIORITY;
                            this.ucf_PRIORITY = this.orig_PRIORITY;
                            return priory;
                        },
                        set(val) {
                            this.ucf_PRIORITY = val;
                        },
                    });
                }
                if (Services.prefs.getBoolPref("browser.urlbar.suggest.history", false) && !Services.prefs.getBoolPref("browser.urlbar.suggest.topsites", true))
                   this.createDropmarker();
            },
            createDropmarker() {
                var fragment = MozXULElement.parseXULToFragment(`<image class="urlbar-history-dropmarker urlbar-icon chromeclass-toolbar-additional" role="button" tooltiptext="Показать историю"/>`);
                var dropmarker = this.dropmarker = fragment.firstElementChild;
                document.querySelector("#urlbar #page-action-buttons").before(fragment);
                dropmarker.addEventListener("mousedown", this);
                windowUtils.loadSheetUsingURIString(this.style, windowUtils.USER_SHEET);
            },
            removeDropmarker() {
                this.dropmarker.removeEventListener("mousedown", this);
                this.dropmarker.remove();
                this.dropmarker = null;
                windowUtils.removeSheetUsingURIString(this.style, windowUtils.USER_SHEET);
            },
            destructor() {
                if (this.dropmarker)
                    this.dropmarker.removeEventListener("mousedown", this);
                Services.prefs.removeObserver("browser.urlbar.suggest.history", this);
                Services.prefs.removeObserver("browser.urlbar.suggest.topsites", this);
            },
            observe() {
                if (Services.prefs.getBoolPref("browser.urlbar.suggest.history", false) && !Services.prefs.getBoolPref("browser.urlbar.suggest.topsites", true) && !this.dropmarker)
                    this.createDropmarker();
                else if (this.dropmarker)
                    this.removeDropmarker();
            },
            handleEvent(event) {
                event.preventDefault();
                event.stopPropagation();
                if (gURLBar.view.isOpen)
                    gURLBar.view.close();
                else {
                    this.provider.PRIORITY = 0;
                    gURLBar.focus();
                    gURLBar.startQuery({
                        allowAutofill: false
                    });
                }
            }
        }).init(this);

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

Выделить код

Код:

(this.urlbarhistorydropmarker = {
            // -- Настройки -->
            hidewhenusertyping: false, // скрывать dropmarker при вводе
            copyurlpightclick: true, // копирование URL  по ПКМ
            currentURIlabel: "Адрес текущей страницы в буфере обмена!",
            valueIsTypedlabel: "Содержимое адресной строки в буфере обмена!",
            ЛКМtooltiptext: "Показать историю",
            ПКМtooltiptext: "Копировать URL в буфер обмена",
            // <-- Настройки --

            dropmarker: null,
            provider: null,
            get style() {
                delete this.style;
                return this.style = "data:text/css;charset=utf-8," + encodeURIComponent(`
                    #urlbar .urlbar-history-dropmarker {
                        list-style-image: url("chrome://global/skin/icons/arrow-down.svg") !important;
                        transition: opacity 0.15s ease;
                    }
                    #urlbar[switchingtabs] > #urlbar-input-container > .urlbar-history-dropmarker {
                        transition: none;
                    }
                    ${this.hidewhenusertyping ? `#urlbar[usertyping] > #urlbar-input-container > .urlbar-history-dropmarker {
                        display: none;
                    }` : ""}
                    #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container:not(:hover) > #urlbar:not([focused]) > #urlbar-input-container > .urlbar-history-dropmarker {
                        opacity: 0;
                    }
                `);
            },
            init(that) {
                Services.prefs.addObserver("browser.urlbar.suggest.history", this);
                Services.prefs.addObserver("browser.urlbar.suggest.topsites", this);
                that.unloadlisteners?.push("urlbarhistorydropmarker");
                var { UrlbarProviderTopSites: provider } = { UrlbarProviderTopSites: this.provider } = ChromeUtils.import("resource:///modules/UrlbarProviderTopSites.jsm");
                if (!provider.orig_PRIORITY) {
                    provider.orig_PRIORITY = provider.ucf_PRIORITY = provider.PRIORITY;
                    delete provider.constructor.prototype.PRIORITY;
                    Object.defineProperty(provider.constructor.prototype, "PRIORITY", {
                        enumerable: true,
                        get() {
                            var priory = this.ucf_PRIORITY;
                            this.ucf_PRIORITY = this.orig_PRIORITY;
                            return priory;
                        },
                        set(val) {
                            this.ucf_PRIORITY = val;
                        },
                    });
                }
                if (Services.prefs.getBoolPref("browser.urlbar.suggest.history", false) && !Services.prefs.getBoolPref("browser.urlbar.suggest.topsites", true))
                   this.createDropmarker();
            },
            createDropmarker() {
                var fragment = MozXULElement.parseXULToFragment(`<image class="urlbar-page-action urlbar-history-dropmarker urlbar-icon" tooltiptext="${
                    !this.copyurlpightclick ? `${this.ЛКМtooltiptext}` : `ЛКМ: ${this.ЛКМtooltiptext}&#10;ПКМ: ${this.ПКМtooltiptext}`
                }"/>`);
                var dropmarker = this.dropmarker = fragment.firstElementChild;
                document.querySelector("#urlbar #urlbar-go-button").after(fragment);
                dropmarker.addEventListener("mousedown", this);
                if (this.copyurlpightclick)
                    dropmarker.addEventListener("click", this);
                windowUtils.loadSheetUsingURIString(this.style, windowUtils.USER_SHEET);
            },
            removeDropmarker() {
                this.removeListeners();
                this.dropmarker.remove();
                this.dropmarker = null;
                windowUtils.removeSheetUsingURIString(this.style, windowUtils.USER_SHEET);
            },
            removeListeners() {
                this.dropmarker.removeEventListener("mousedown", this);
                if (this.copyurlpightclick)
                    this.dropmarker.removeEventListener("click", this);
            },
            destructor() {
                if (this.dropmarker)
                    this.removeListeners();
                Services.prefs.removeObserver("browser.urlbar.suggest.history", this);
                Services.prefs.removeObserver("browser.urlbar.suggest.topsites", this);
            },
            observe() {
                if (Services.prefs.getBoolPref("browser.urlbar.suggest.history", false) && !Services.prefs.getBoolPref("browser.urlbar.suggest.topsites", true) && !this.dropmarker)
                    this.createDropmarker();
                else if (this.dropmarker)
                    this.removeDropmarker();
            },
            mousedown(e) {
                if (e.button !== 0) return;
                e.preventDefault();
                e.stopPropagation();
                if (gURLBar.view.isOpen)
                    gURLBar.view.close();
                else {
                    this.provider.PRIORITY = 0;
                    gURLBar.focus();
                    gURLBar.startQuery({
                        allowAutofill: false
                    });
                }
            },
            click(e) {
                if (e.button !== 2) return;
                var currentURI = this.currentURIlabel, valueIsTyped = this.valueIsTypedlabel;
                var gBrowserBundle = {
                    GetStringFromName(str) {
                        return ({
                            "confirmationHint.currentURI.label": currentURI,
                            "confirmationHint.valueIsTyped.label": valueIsTyped,
                        })[str];
                    }
                };
                var show = eval(`(function ${e.view.ConfirmationHint.show})`);
                var helper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
                (this.click = e => {
                    if (e.button !== 2) return;
                    e.preventDefault();
                    e.stopPropagation();
                    var url, mesId;
                    if (!gURLBar.valueIsTyped) {
                        url = gURLBar.makeURIReadable(gBrowser.selectedBrowser.currentURI).displaySpec;
                        mesId = "currentURI";
                    } else {
                        url = gURLBar.untrimmedValue;
                        mesId = "valueIsTyped";
                    }
                    helper.copyString(url);
                    show.call(ConfirmationHint, this.dropmarker, mesId, { hideArrow: true });
                })(e);
            },
            handleEvent(e) {
                this[e.type](e);
            },
        }).init(this);

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

Выделить код

Код:

(this.urlbarhistorydropmarker = {
            // -- Настройки -->
            hidewhenusertyping: false, // скрывать dropmarker при вводе
            reloadpage: true, // обновить текущую страницу по СКМ
            copyurlrightclick: true, // копирование URL по ПКМ
            copyvalueistyped: true, // при вводе копировать содержимое адресной строки вместо URL
            currentURIlabel: "Адрес текущей страницы в буфере обмена!",
            valueIsTypedlabel: "Содержимое адресной строки в буфере обмена!",
            Ltooltiptext: "Показать историю",
            Mtooltiptext: "Обновить текущую страницу",
            Rtooltiptext: "Копировать URL в буфер обмена",
            // <-- Настройки --

            dropmarker: null,
            provider: null,
            get style() {
                delete this.style;
                return this.style = "data:text/css;charset=utf-8," + encodeURIComponent(`
                    #urlbar .urlbar-history-dropmarker {
                        list-style-image: url("chrome://global/skin/icons/arrow-down.svg") !important;
                        transition: opacity 0.15s ease;
                    }
                    #urlbar[switchingtabs] > #urlbar-input-container > .urlbar-history-dropmarker {
                        transition: none;
                    }
                    ${this.hidewhenusertyping ? `#urlbar[usertyping] > #urlbar-input-container > .urlbar-history-dropmarker {
                        display: none;
                    }` : ""}
                    #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container:not(:hover) > #urlbar:not([focused]) > #urlbar-input-container > .urlbar-history-dropmarker {
                        opacity: 0;
                    }
                `);
            },
            init(that) {
                Services.prefs.addObserver("browser.urlbar.suggest.history", this);
                Services.prefs.addObserver("browser.urlbar.suggest.topsites", this);
                that.unloadlisteners?.push("urlbarhistorydropmarker");
                var { UrlbarProviderTopSites: provider } = { UrlbarProviderTopSites: this.provider } = ChromeUtils.import("resource:///modules/UrlbarProviderTopSites.jsm");
                if (!provider.orig_PRIORITY) {
                    provider.orig_PRIORITY = provider.ucf_PRIORITY = provider.PRIORITY;
                    delete provider.constructor.prototype.PRIORITY;
                    Object.defineProperty(provider.constructor.prototype, "PRIORITY", {
                        enumerable: true,
                        get() {
                            var priory = this.ucf_PRIORITY;
                            this.ucf_PRIORITY = this.orig_PRIORITY;
                            return priory;
                        },
                        set(val) {
                            this.ucf_PRIORITY = val;
                        },
                    });
                }
                if (Services.prefs.getBoolPref("browser.urlbar.suggest.history", false) && !Services.prefs.getBoolPref("browser.urlbar.suggest.topsites", true))
                   this.createDropmarker();
            },
            get tooltipText() {
                delete this.tooltipText;
                var l = this.Ltooltiptext, m = "", r = "";
                if (this.reloadpage) l = `ЛКМ: ${this.Ltooltiptext}`, m = `&#10;СКМ: ${this.Mtooltiptext}`;
                if (this.copyurlrightclick) l = `ЛКМ: ${this.Ltooltiptext}`, r = `&#10;ПКМ: ${this.Rtooltiptext}`;
                return this.tooltipText = `${l}${m}${r}`;
            },
            createDropmarker() {
                var fragment = MozXULElement.parseXULToFragment(`<image class="urlbar-page-action urlbar-history-dropmarker urlbar-icon" tooltiptext="${this.tooltipText}"/>`);
                var dropmarker = this.dropmarker = fragment.firstElementChild;
                document.querySelector("#urlbar #urlbar-go-button").after(fragment);
                dropmarker.addEventListener("mousedown", this);
                if (this.copyurlrightclick || this.reloadpage)
                    dropmarker.addEventListener("click", this);
                windowUtils.loadSheetUsingURIString(this.style, windowUtils.USER_SHEET);
            },
            removeDropmarker() {
                this.removeListeners();
                this.dropmarker.remove();
                this.dropmarker = null;
                windowUtils.removeSheetUsingURIString(this.style, windowUtils.USER_SHEET);
            },
            removeListeners() {
                this.dropmarker.removeEventListener("mousedown", this);
                if (this.copyurlrightclick || this.reloadpage)
                    this.dropmarker.removeEventListener("click", this);
            },
            destructor() {
                if (this.dropmarker)
                    this.removeListeners();
                Services.prefs.removeObserver("browser.urlbar.suggest.history", this);
                Services.prefs.removeObserver("browser.urlbar.suggest.topsites", this);
            },
            observe() {
                if (Services.prefs.getBoolPref("browser.urlbar.suggest.history", false) && !Services.prefs.getBoolPref("browser.urlbar.suggest.topsites", true) && !this.dropmarker)
                    this.createDropmarker();
                else if (this.dropmarker)
                    this.removeDropmarker();
            },
            mousedown(e) {
                if (e.button !== 0) return;
                e.preventDefault();
                e.stopPropagation();
                if (gURLBar.view.isOpen)
                    gURLBar.view.close();
                else {
                    this.provider.PRIORITY = 0;
                    gURLBar.focus();
                    gURLBar.startQuery({
                        allowAutofill: false,
                    });
                }
            },
            click(e) {
                if (e.button === 0) return;
                var currentURI = this.currentURIlabel, valueIsTyped = this.valueIsTypedlabel;
                var gBrowserBundle = {
                    GetStringFromName(str) {
                        return ({
                            "confirmationHint.currentURI.label": currentURI,
                            "confirmationHint.valueIsTyped.label": valueIsTyped,
                        })[str];
                    }
                };
                var show = eval(`(function ${e.view.ConfirmationHint.show})`);
                var helper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
                (this.click = e => {
                    if (e.button === 0) return;
                    e.preventDefault();
                    e.stopPropagation();
                    if (e.button === 1) {
                        BrowserReload();
                        return;
                    }
                    var url, mesId;
                    if (!gURLBar.valueIsTyped || !this.copyvalueistyped) url = gURLBar.makeURIReadable(gBrowser.selectedBrowser.currentURI).displaySpec, mesId = "currentURI";
                    else url = gURLBar.untrimmedValue, mesId = "valueIsTyped";
                    helper.copyString(url);
                    show.call(ConfirmationHint, this.dropmarker, mesId, { hideArrow: true });
                })(e);
            },
            handleEvent(e) {
                this[e.type](e);
            },
        }).init(this);

Кто-нибудь может помоч мне с этой проблемой?
Дополню: такая же проблема имеется с этим вариантом скрипта и с кастомной кнопкой с аналогичным функционалом.

Kot DaVinci
Можно после строки, содержащей node.setAttribute("label", label);
добавить строку, содержащую node.setAttribute("closemenu", "none");


Лучше во второй код, потому что там "таблетка от сепараторов" прописана,
ну, если фича "исключения скрытых" не мешает, конечно.

Dumby, спасибо. Работает как надо.

egorsemenov06
Речь про initKeyEvent? Ну, конструктор нам рекоммендуют.

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

Выделить код

Код:

//        var evt = node.ownerDocument.createEvent("KeyboardEvent");
//        evt.initKeyEvent(
//            "keypress", true /*bubbles*/, true /*cancelable*/, node.ownerDocument.defaultView,
//            e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
//            e.keyCode, e.charCode
//        );

        var evt = new node.ownerGlobal.KeyboardEvent(
            "keypress", {bubbles: true, cancelable: true, ...e}
        );


Во второй ничего не смог заметить.

Vitaliy V.
Сделайте пожалуйста, что бы набранный текст удалялся автоматически после скрытия панели поиска.


Add, и если возможно, то объединить с этим скриптом: https://forum.mozilla-russia.org/viewto … 72#p782672

kokoss

скрытый текст
                           // if (!this.findbar.hidden)
                               // this.findbar.close();
                            if (this.findbar.hidden) return;
                            this.findbar.clear();
                            this.findbar.close();

скрытый текст
                        gFindBar.clear();
                        gFindBar.close();

Vitaliy V. пишет

скрытый текст
                           // if (!this.findbar.hidden)
                               // this.findbar.close();
                            if (this.findbar.hidden) return;
                            this.findbar.clear();
                            this.findbar.close();

Благодарю :beer:
А этот код:

gFindBar.clear();
gFindBar.close();

куда...?

kokoss пишет

куда...?

там ниже функция keydown(e) {...
для комбинаций клавиш Ctrl + F
перед gFindBar.close(); добавить gFindBar.clear();

Vitaliy V. пишет

там ниже функция keydown(e) {...
для комбинаций клавиш Ctrl + F
перед gFindBar.close(); добавить gFindBar.clear();

Я так понимаю что если для этого не использую клави..., то можно удалить этот код:

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

Выделить код

Код:

keydown(e) {
                if (e.ctrlKey && e.code == "KeyF" && !e.altKey && !e.shiftKey) {
                    if (this.timer != null) {
                        e.preventDefault();
                        return;
                    }
                    this.timer = setTimeout(() => {
                        this.timer = null;
                    }, 1000);
                    if (window.gFindBarInitialized && !gFindBar.hidden) {
                        e.preventDefault();
			gFindBar.clear();
                        gFindBar.close();
                    }
                }
            },

kokoss пишет

если для этого не использую клави..., то можно удалить этот код:

Нет, один этот код удалять нельзя, только вместе со слушателем, и удалять код не обязательно, достаточно отключить слушатель.
// window.addEventListener("keydown", this, true);
// window.removeEventListener("keydown", this, true);

Vitaliy V. пишет

Нет, один этот код удалять нельзя.

Я это уже понял...


Vitaliy V. пишет

и удалять код не обязательно, достаточно отключить слушатель.
// window.addEventListener("keydown", this, true);
// window.removeEventListener("keydown", this, true);

Спасибо!

Скачал комплект UserChromeFiles - 2021-9-23. Обновил/перезаписал все файлы по нужным папкам.
Скрипты подключились и заработали сразу, а вот стили ни один не подключился.
Ранее в файле custom_style_user.css было прописано следующее содержимое и всё работало:

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

Выделить код

Код:

/* Этот файл для правил CSS с правами USER_SHEET */
/* значки папок закладок желтого цвета */
@import url("./css/Colored-folders-91.css");

/* скрыть элементы меню закладок */
@import url("./css/hide_bookmarks_elements.css");

/* скрыть элементы контекстного меню на странице */
@import url("./css/hide_context_elements.css");

/* убрать history-dropmarker из адресной строки */
@import url("./css/history-dropmarker.css");

/* компактная панель поиска сверху справа */
@import url("./css/findbar_compakt.css");

/* панель быстрого поиска такая хе, как и полного поиска */
@import url("./css/findbar_show_full_quickfindbar.css");

/* в приложении GISMETEO - белый шрифт значка погоды */
@import url("./css/gismeteo.css");

/* изменение высоты панели вкладок, компактное меню (Proton) */
@import url("./css/bar_compact_proton.css");


теперь появились три файла: custom_styles_all_user.css , custom_styles_chrome_user.css , custom_styles_content_user.css
пробовал помещать код в них - безрезультатно
Не пойму куда вообще копать?

Inko7
Так все стили по умолчанию отключены в CustomStylesScripts.jsm (зачем включать пустые файлы, они там только для примера)
можете изменить это в styleschrome (стили подключенные в styleschrome работают там же где и userChrome.css)
ну или в stylesall: [ // Для всех документов
И вместо @import можно просто подключить ваши файлы там же в CustomStylesScripts.jsm, например
{ path: "css/Colored-folders-91.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, },

Vitaliy V.
вот теперь все стили заработали, спасибо!

получается, раз скрипты заработали сразу, то файлы custom_script.js / custom_script_all_win.js / custom_script_win.js обрабатываются изначально и их прописывать дополнительно не нужно?

Inko7 пишет

файлы custom_script.js / custom_script_all_win.js / custom_script_win.js обрабатываются изначально и их прописывать дополнительно не нужно?

custom_script.js добавлен в CustomStylesScripts.jsm, но его можно удалить, переименовать,
а custom_script_all_win.js / custom_script_win.js обрабатываются изначально и их прописывать, удалять, переименовывать нельзя

Vitaliy V.
Вы можете написать скрипт отключающий отображение пунктов контекстного меню, с определенным в скрипте ID, для разных контекстов. С перечислением исключений для каждого добавленного пользователем ID. Что-то типа:

"#context-copy" this.hidden = gContextMenu.onLink || gContextMenu.onMailtoLink || gContextMenu.onImage || gContextMenu.onCanvas;
"#other-addon" this.hidden = gContextMenu.onTextInput || gContextMenu.isContentSelected;

Например, #context-copy появляется везде при выделенном на странице тексте, но главное здесь расширения, очень часто они добавляют свои пункты без учета контекста.
Второй пример, сепараторы, при переупорядочивании меню некоторые сепараторы надо удалить только для определенных контекстов.
   
И, если будете делать, добавьте примеры, в том числе для контекстов: фрейм, страница, вкладка, адресная строка и textarea (если такой есть отдельно от .onTextInput).
   
ps^ и есть ли контексты в закладках панели - папка, отдельныя закладка? Видел расширение которое добавляло свой пункт и туда и туда, а нужно было только для папок.

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


если не заметили недавно обновил ваши скрипты
https://forum.mozilla-russia.org/viewto … 24#p784824
https://forum.mozilla-russia.org/viewto … 55#p783755

Vitaliy V.
SidebarTabs обновлял, там беда со сплиттером
2021.1634372082.png
оставил так и выкинул after

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

Выделить код

Код:

#st_splitter {
                -moz-appearance: none !important;
                appearance: none !important;
                background-color: var(--chrome-content-separator-color, rgba(127,127,127,.5)) !important;
                background-clip: content-box !important;
                border-inline: 1px solid transparent !important;
                min-width: 3px !important;
                margin-inline: -1px !important;
                position: relative !important;
                z-index: 2 !important;
                -moz-box-ordinal-group: ${this.ST_RIGHT ? "100" : "0"} !important;
                -moz-box-orient: vertical !important;

2021.1634372264.png
   
Тултипы сейчас обновил.
Я тут подумал, что отображение заголовка перед адресом более удобно, как их местами поменять? У меня не получается, без заголовка остается пустая строка перед адресом.
   
ps^ А как вы svg преобразовываете, например вот это как в скрипт засунуть?
скрытый текст

<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" width="16" height="16"><path d="M4 1C1.804688 1 0 2.800781 0 5L0 15C0 17.195313 1.804688 19 4 19L6.3125 19L8.0625 23.375C8.207031 23.765625 8.582031 24.027344 9 24.027344C9.417969 24.027344 9.792969 23.765625 9.9375 23.375L11.6875 19L22 19C24.195313 19 26 17.195313 26 15L26 5C26 2.800781 24.195313 1 22 1 Z M 4 3L22 3C23.117188 3 24 3.882813 24 5L24 15C24 16.113281 23.113281 17 22 17L11 17C10.589844 16.996094 10.214844 17.242188 10.0625 17.625L9 20.28125L7.9375 17.625C7.785156 17.242188 7.410156 16.996094 7 17L4 17C2.886719 17 2 16.113281 2 15L2 5C2 3.882813 2.882813 3 4 3Z" fill="#D0D0D0" /></svg>

_zt пишет

оставил так и выкинул after

надо только добавил
border: none !important;
background: none !important;

_zt пишет

отображение заголовка перед адресом более удобно, как их местами поменять?

// el.title = title = `${href}${title === "" ? "" : `\nTitle: ${title}`}`;
el.title = title = `${title === "" ? "" : `Title: ${title}\nUrl: `}${href}`;
Но сначала обновите все полностью, я там изменил немного

Vitaliy V.
Теперь нормально. В своем svg заменил fill= на style= из вашего и все заработало.

egorsemenov06 пишет

поправить вот этот скрипт для [firefox] 94.0

Заменить residentSetSize и residentUniqueSize на memory


Dobrov пишет

как добавить двойной клик на UCF-кнопку, чтобы вместе с ним не срабатывало событие "click" ?

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

Выделить код

Код:

try {	CustomizableUI.createWidget({
		id: "add-additional-personaltoolbar-button", type: "custom", localized: false, label:
"Панели, Папки", tooltiptext:
`ЛКМ:	★ Закладки\n…+ Alt	Домашняя папка
ПКМ:	⟳ История\n…+ Alt	Папка установки
СКМ:	Папка профиля\n…+ Alt	user_chrome_files`,
		onBuild(doc) {
			var trbn = doc.createXULElement("toolbarbutton");
			trbn.id = this.id;
			trbn.tooltipText = this.tooltiptext;
			trbn.label = this.label;
			trbn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
			trbn.setAttribute("context", false);
			trbn.style.setProperty("list-style-image", `url("chrome://user_chrome_files/content/vertical_top_bottom_bar/svg/bookmark-16.svg")`, "important");
			trbn.addEventListener("dblclick", function(e) {
				e.view.alert("DBL Click");
			}, false);
			trbn.explorer =(dir, subdir = undefined)=> {
				var dirs = Services.dirsvc.get(dir, Ci.nsIFile);
				if (subdir) dirs.append(subdir);
				if (dirs.exists()) dirs.launch();
			};
			trbn.bar =(bar)=> {
				var win = Services.wm.getMostRecentWindow("navigator:browser");
				if ("SidebarUI" in win)
					win.SidebarUI.toggle(bar);
				else if ("toggleSidebar" in win)
					win.toggleSidebar(bar);
			};
			trbn.addEventListener("click", function(e) {
				if (e.button == 0)
					e.altKey ? trbn.explorer("Home") : trbn.bar("viewBookmarksSidebar")
				else if (e.button == 1)
					e.altKey ? trbn.explorer("UChrm", "user_chrome_files") : trbn.explorer("ProfD")
				else if (e.button == 2)
					e.altKey ? trbn.explorer("GreD") : trbn.bar("viewHistorySidebar")
			}, false);
			return trbn;
		},
	});
} catch(e) {}

С помощью таймаута наверно, как же ещё.

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

Выделить код

Код:

(async (bar, exp, tid, self) => CustomizableUI.createWidget(self = {
	label: "Панели, Папки",
	tooltiptext: [
		"ЛКМ:	★ Закладки\n…+ Alt	Домашняя папка",
		"ПКМ:	⟳ История\n…+ Alt	Папка установки",
		"СКМ:	Папка профиля\n…+ Alt	user_chrome_files"
	].join("\n"),
	id: "add-additional-personaltoolbar-button",
	localized: false,
	onCreated(btn) {
		btn.onclick = this.click;
		btn.style.setProperty("list-style-image", "url(chrome://user_chrome_files/content/vertical_top_bottom_bar/svg/bookmark-16.svg)", "important");
	},
	exec(num, win) {
		tid = null;
		self[num](win);
	},
	context: win => win.document.getElementById(self.id)
		.dispatchEvent(new win.MouseEvent("contextmenu", self.a)),
	a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

	click(e) {
		if (e.detail > 2) return;
		var n2 = e.button != 2;
		var dbl = e.detail == 2;
		var num = 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;

		if (!self[num]) {
			if (n2) return;
			num = "context";
			for(var p in self.a) self.a[p] = e[p];
		}
		n2 || e.preventDefault();

		var win = e.view;
		if (dbl) tid &&= win.clearTimeout(tid), self[num](win);
		else tid = win.setTimeout(self.exec, 300, num, win);
	},
	0:   w => bar(w, "viewBookmarksSidebar"), // ЛКМ
	2:  () => exp("Home"), // Alt+ЛКМ
	16: () => exp("ProfD"), // СКМ
	18: () => exp("UChrm", "user_chrome_files"), // Alt+СКМ
	32:  w => bar(w, "viewHistorySidebar"), // ПКМ
	34: () => exp("GreD"), // Alt+ПКМ

	1(win) { // Double Left Click
		win.alert("DBL Click");
	},
	33(win) { // Double Right Click
		win.alert("DBL Right Click");
	},
	29(win) { // Ctrl + Shift + Double Middle Click
		win.alert("Ctrl + Shift + DBL Middle Click");
	},
}))(
	(win, bar) => win.SidebarUI.toggle(bar),
	(dir, sub) => {
		dir = Services.dirsvc.get(dir, Ci.nsIFile);
		sub && dir.append(sub);
		dir.exists() && dir.launch();
	}
);

Vitaliy V.
Подскажите пожалуйста, как в этом скрипте запретить AutoPopup на некоторых кнопках? И спасибо за скрипт!


Add, вроде получилось что хотел, удалил из скрипта часть кода: this.ExtensionParent.WebExtensionPolicy.getByID(id).extension . Правильно ли я это сделал? Нужно было отключить AutoPopup значков расширений.


Add, и ешё вопрос, для чего нужен этот скрипт?

Dumby - Спасибо, скрипт очень компактный и крутой, но в двойной клик на UCF-кнопке правый клик мыши работает неверно.
Вместе с правым кликом в MacOS и вероятно Linux открывается контекстное меню панели, пробовал отключить и не получилось:

Выделить код

Код:

var reset = e => e.target.linkedObject = this;
		var id, lo = {click: e => n2 || reset(e)};
		var lin = /macos|linux/.test(e.view.AppConstants.platform);
		var stop = e => reset(e) && e.preventDefault();
		lo.contextmenu = lin
			? e => e.ctrlKey || e.shiftKey ? 0 : stop(e) : stop;
		n2 || reset(e) && e.preventDefault();
……

Dumby - ещё просьба по твоему скрипту перехвата кликов на кнопках > добавить Двойной клик  (у меня не удалось):
Конкретно - добавить двойной клик на "PanelUI-menu-button" (строка 133) и по возможности на "downloads-button"…


Второе (если это возможно) > изменить способ перехвата кнопок со стандартного addEventListener на такой же «продвинутый», как и в новом скрипте,
то есть способом: var num = 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;

ucf_hookClicks.js (скрипт перехвата кликов на downloads-button, PanelUI-menu)

Выделить код

Код:

(async (id, func) => { // дополнительные клики на downloads-button, PanelUI-menu для custom_script_win.js
	await window.delayedStartupPromise;
	var btn = document.getElementById("downloads-button"), pui = document.getElementById("PanelUI-menu-button");
	if (!btn) return; btn.tooltipText = GetDynamicShortcutTooltipText(btn.id) +`

ПКМ:	Сохранить как единый html
	всё | выделенное на странице
…+ Shift	Обзор папки [Загрузки]\n
Ролик:	Сохранить как файл .txt
…+ Shift	Сайт: графика Вкл/Выкл\n
Колёсико на рисунке: ➜ Сохранить
Двойной клик: найти Похожие фото`,
																PanelUI_help =
`Браузер Firefox, версия ${Services.appinfo.platformVersion}\n
Колёсико:	Развернуть | окно
…+ Alt		Полный экран
Правый клик	⇲ Свернуть
…+ Shift		Закрыть ✕
…+ Alt		Персонализация`;

	var addDestructor = nextDestructor => {
		var {destructor} = ucf[id];
		ucf[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	},
	showInStatusPanel = (info, time = 5000) => {
		var win = Services.wm.getMostRecentWindow("navigator:browser"); StatusPanel = win.StatusPanel;
		if (StatusPanel.update.tid)
			clearTimeout(StatusPanel.update.tid)
		else {
			var {update} = StatusPanel;
			StatusPanel.update = () => {};
			StatusPanel.update.ret = () => {
				StatusPanel.update = update;
				StatusPanel.update();
			}
		}
		StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, time);
		StatusPanel._label = info;
	},
	saveSelectionToTxt = async () => { // сохранить страницу или выделенный текст как файл .txt
		var splice = saveURL.length == 10;
		var msgName = id + ":Save:GetSelection";
		var receiver = msg => {
			var title = document.title || gBrowser.selectedTab.label;
			var args = [
				"data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + "\n\n" + msg.data),
				title.replace(/[:\\\/<>?*|"]+/g,'_').replace(/\s+/g,' ').slice(0, 100).trim() + '_' + new Date().toLocaleString('ru').replace(', ','-').replace(/:/g, '։') + '.txt',
				null, false, true, null, window.document
			];
			splice && args.splice(5, 0, null);
			saveURL(...args) && showInStatusPanel("√ текст сохранён: " + title.slice(0, 60));
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));
		var func = fm => {
			var res, fed, win = {}, fe = fm.getFocusedElementForWindow(content, true, win);
			var sel = (win = win.value).getSelection();
			if (sel.isCollapsed) {
				var ed = fe && fe.editor;
				if (ed && ed instanceof Ci.nsIEditor)
					sel = ed.selection, fed = fe;
			}
			if (sel.isCollapsed)
				fed && fed.blur(), docShell.doCommand("cmd_selectAll"),
				res = win.getSelection().toString(), docShell.doCommand("cmd_selectNone"),
				fed && fed.focus();
			res = res || sel.toString();
			/\S/.test(res) && sendAsyncMessage("saveSelectionToTxt", res);
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})`.replace("saveSelectionToTxt", msgName)) + '(Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager));';
		(saveSelectionToTxt = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	}, // end

	save = async () => { // автор: Лекс, правка: Dumby, Dobrov (пути сохранения HTML)
		var msgName = id + "ucfDwnldsBtnSaveSnapshotToHTML";
		if (typeof IOUtils != "object") { // Firefox 78 ESR
			var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
			var PathUtils = {join: (...args) => OS.Path.join(...args)};
			var IOUtils = {writeUTF8: (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt))};
		}
		var write = IOUtils.writeUTF8 ? "writeUTF8" : "writeAtomicUTF8";

		var Title = (type) => { // получить заголовок (без обрезки, если type не указан) или домен (type <0)
			var title = (document.title || gBrowser.selectedTab.label);
			if ( !type ) return title; // заголовок
			if ( type > 0 ) return title.slice(0, type).replace(/ \| Форум Mozilla Россия$| — Mozilla Firefox|[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim(); // ограничить длину имени
			var host = (/^file:\/\//.test(gURLBar.value)) ? '' : gURLBar.value.replace(/^.*url=|https?:\/\/|www\.|\/.*/g,'');
			return host.replace(/^ru\.|^m\.|forum\./,'').replace(/^club\.dns/,'dns');
		}
		var msgListener = async msg => {
			var [fileContent, fileName] = msg.data, dir;
			try {dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}
			var arr = prefs.getStringPref("ucf_save.dirs", "_Web||_Images|0").split('|').slice(0, 2); // Dir/subdir: пусто|0 title|1 домен
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // для ucf_save.dirs = "_Web||_Pics|1" HTML сохранится в папку [Загрузки]/_Web/имя вкладки
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
			var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
			file.initWithPath(dir.path);
			dir.append(fileName);
			await IOUtils[write](dir.path, fileContent) && showInStatusPanel("√ страница записана: " + fileName.slice(0, 60));
			var d = await Downloads.createDownload({ source: "about:blank", target: FileUtils.File(dir.path)}); // Fake download
			(await Downloads.getList(Downloads.ALL)).add(d);
			d.refresh(d.succeeded = true); // кнопка Загрузки мигает
		}
		messageManager.addMessageListener(msgName, msgListener);
		addDestructor(() => messageManager.removeMessageListener(msgName, msgListener));

		var svc = 'globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services';
		var url = "data:;charset=utf8," + encodeURIComponent(`(${func})(${svc});`.replace("%MSG_NAME%", msgName));
		(save = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	}, // end save

	listener = e => { var trg = e.target; // Downloads Clicks
		if (e.button == 1) {
			if (e.shiftKey) { // СКМ + Shift
				if ( prefs.getIntPref("permissions.default.image", 1) == 1)
					prefs.setIntPref("permissions.default.image", 2), trg.style.filter = "hue-rotate(180deg) brightness(95%)"
				else
					prefs.setIntPref("permissions.default.image", 1), trg.style.filter = "";
				BrowserReload();
			} else	// СКМ Click
				saveSelectionToTxt(); // сохранить .txt
		} else if (e.button == 2) {
			if (e.shiftKey)
				Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
			else	// ПКМ Click
				save(); // Single HTML
		}
	},
	listener_pui = e => { // PanelUI-menu Clicks
		if (e.button == 1) {
			if (e.altKey)
				window.BrowserFullScreen()
			else
			if( window.windowState != window.STATE_MAXIMIZED )
				window.maximize()
			else
				window.restore();
		} else
			if (e.button == 2) if (e.altKey) return
		else {
			e.stopPropagation();
			(e.shiftKey) ? window.close() : window.minimize();
		}
	}, // end Clicks
	keydown_win = e => { // нажатие клавиш
		if (!(e.keyCode == 83 && e.shiftKey && e.altKey)) return;
		var singlesave = document.getElementById(save_ex); // SingleSave
		singlesave ? singlesave.click() : save(); // имитировать клик по кнопке, используя её ID
	},
	{prefs, dirsvc} = Services, tmax = btn.tooltipText.split("\n")[0].length, save_ex = "_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action";
	btn.setAttribute("context", "event.stopPropagation()");
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	(async () => { // SingleSave - дополнить подсказку
		setTimeout((but = document.getElementById(save_ex))=> {
			if (but)
				btn.tooltipText = btn.tooltipText + '\n\nAlt⇧S	нажатие SingleSave';
			if (!/Закрыть/.test(pui.tooltipText))
				pui.tooltipText = PanelUI_help;
		}, 9000); // после запуска ждать от 3 сек
	})();

	btn.addEventListener("click", listener), pui.addEventListener("click", listener_pui);
		window.addEventListener("keydown", keydown_win);
	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		btn.removeEventListener("click", listener), pui.removeEventListener("click", listener_pui);
		window.removeEventListener("keydown", keydown_win);
	}};
	ucf.unloadlisteners.push(id);

})("downloads-button-click-listener", ({io, focus}) => { // SingleHTML не сохраняет svg графику

	var resolveURL = function (url, base) {
		try { return io.newURI(url, null, io.newURI(base)).spec;
		} catch {}
	},
	getSelWin = function (w) {
		if (w.getSelection().toString()) return w;
		for (var i = 0, f, r; f = w.frames[i]; i++) {
			try { if (r = getSelWin(f)) return r;
			} catch(e) {}
		}
	},
	encodeImg = function (src, obj) {
		var canvas, img, ret = src;
		if (/^https?:\/\//.test(src)) {
			canvas = doc.createElement('canvas');
			if (!obj || obj.nodeName.toLowerCase() != 'img') {
				img = doc.createElement('img');
				img.src = src;
			} else
				img = obj;
			if (img.complete) try{
				canvas.width = img.width;
				canvas.height = img.height;
				canvas.getContext('2d').drawImage(img, 0, 0);
				ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png'));
			} catch (e) {};
			if (img != obj) img.src = 'about:blank';
		};
		return ret;
	},
	toSrc = function (obj) {
		var strToSrc = function (str) {
			var chr, ret = '', i = 0, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\x22' : '\\\x22', '\\': '\\\\'};
			while (chr = str.charAt(i++)) {
				ret += meta[chr] || chr;
			};
			return '\x22' + ret + '\x22';
		},
		arrToSrc = function (arr) {
			var ret = [];
			for (var i = 0; i < arr.length; i++) {
				ret[i] = toSrc(arr[i]) || 'null';
			};
			return '[' + ret.join(',') + ']';
		},
		objToSrc = function (obj) {
			var val, ret = [];
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val);
			};
			return '{' + ret.join(',') + '}';
		};
		switch (Object.prototype.toString.call(obj).slice(8, -1)) {
			case 'Array': return arrToSrc(obj);
			case 'Boolean':
			case 'Function':
			case 'RegExp': return obj.toString();
			case 'Date': return 'new Date(' + obj.getTime() + ')';
			case 'Math': return 'Math';
			case 'Number': return isFinite(obj) ? String(obj) : 'null';
			case 'Object': return objToSrc(obj);
			case 'String': return strToSrc(obj);
			default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null';
		}
	},
	mainWin = {};
	focus.getFocusedElementForWindow(content, true, mainWin);
	mainWin = mainWin.value;

	var selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location;
	var ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g;

	if (selWin) {
		var rng = win.getSelection().getRangeAt(0);
		pEle = rng.commonAncestorContainer;
		ele = rng.cloneContents();
	} else {
		pEle = doc.documentElement;
		ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true);
	};
	while (pEle) {
		if (pEle.nodeType == 1) {
			clone = pEle.cloneNode(false);
			clone.appendChild(ele);
			ele = clone;
		};
		pEle = pEle.parentNode
	};
	var sel = doc.createElement('div');
	sel.appendChild(ele);

	for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) {
		el = all[i];
		if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) {
			if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href);
			return prev + encodeImg(url) + next;
		});
		switch (el.nodeName.toLowerCase()) {
			case 'link':
			case 'style':
			case 'script': el.parentNode.removeChild(el); break;
			case 'a':
			case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break;
			case 'img':
			case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break;
			case 'audio':
			case 'video':
			case 'embed':
			case 'frame':
			case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break;
			case 'object': if (el.hasAttribute('data')) el.data = el.data; break;
			case 'form': if (el.hasAttribute('action')) el.action = el.action; break;
		}
	};
	var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets, title = doc.getElementsByTagName('title')[0];
	meta.httpEquiv = 'content-type';
	meta.content = 'text/html; charset=utf-8';
	head.appendChild(meta);
	if (title) head.appendChild(title.cloneNode(true));

	head.copyScript = function (unsafeWin) {
		if ('$' in unsafeWin) return;
		var f = doc.createElement('iframe');
		f.src = 'about:blank';
		f.setAttribute('style', 'position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');
		doc.documentElement.appendChild(f);
		var str, script = doc.createElement('script');
		script.type = 'text/javascript';
		for (var name in unsafeWin) {
			if (name in f.contentWindow || !/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) continue;
			try {
				str = toSrc(unsafeWin[name]);
				if (!/\{\s*\[native code\]\s*\}/.test(str)) {
					script.appendChild(doc.createTextNode('var ' + name + ' = ' + str.replace(/<\/(script>)/ig, '<\\/$1') + ';\n'));
				}
			} catch (e) {};
		};
		f.parentNode.removeChild(f);
		if (script.childNodes.length) this.nextSibling.appendChild(script);
	};
	head.copyScript(win.wrappedJSObject || win);

	head.copyStyle = function (s) {
		if (!s) return;
		var style = doc.createElement('style');
		style.type = 'text/css';
		if (s.media && s.media.mediaText) style.media = s.media.mediaText;
		try {
			for (var i = 0, rule; rule = s.cssRules[i]; i++) {
				if (rule.type != 3) {
					if((!rule.selectorText || rule.selectorText.indexOf(':') != -1) || (!sel.querySelector || sel.querySelector(rule.selectorText))) {
						var css = !rule.cssText ? '' : rule.cssText.replace(reUrl, function (a, prev, url, next) {
							if (!/^[a-z]+:/.test(url)) url = resolveURL(url, s.href || loc.href);
							if(rule.type == 1 && rule.style && rule.style.backgroundImage) url = encodeImg(url);
							return prev + url + next;
						});
						style.appendChild(doc.createTextNode(css + '\n'));
					}
				} else {
					this.copyStyle(rule.styleSheet);
				}
			}
		} catch(e) {
			if (s.ownerNode) style = s.ownerNode.cloneNode(false);
		};
		this.appendChild(style);
	};
	for (var j = 0; j < sheets.length; j++) head.copyStyle(sheets[j]);
	head.appendChild(doc.createTextNode('\n'));

	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};

	var fileName = selWin ? win.getSelection().toString() : (title && title.text ? title.text : loc.pathname.split('/').pop());
	fileName = fileName.replace(/[:\\\/<>?*|"]+/g, '_').replace(/\s+/g, ' ').slice(0, 100).trim();
	fileName += "_" + new Date().toLocaleDateString('ru', {day: 'numeric', month: 'numeric', year: '2-digit'}) +'-'+ new Date().toLocaleTimeString().replace(/:/g, "։");
	if (!/\.html?$/.test(fileName)) fileName += '.html';

	sendAsyncMessage("%MSG_NAME%", [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', fileName]);

}); // END hookClicks

В скрипт Quick Toggle about:config тоже добавить Двойной клик на кнопке (в коде есть ещё Долгое нажатие)

ucf_QuickToggle.js (скрипт из моего профиля FF в шапке темы)

Выделить код

Код:

// Quick Toggle Быстрое переключение параметров about:config для custom_script.js

(async (name, id, func) => { // https://forum.mozilla-russia.org/viewtopic.php?pid=789824#p789824
	if (name == "Object") return CustomizableUI.createWidget(func());
	var win = name == "Window", g = Cu.import("resource://gre/modules/Services.jsm", {});
	if (g[id]) {if (win) return;} else g[id] = func(); if (win) return CustomizableUI.createWidget(g[id]);
	addDestructor(r => r[5] == "e" && delete g[id]); g[id].onCreated(this);	// BEGIN QuickToggle…
})(this.constructor.name, "ToggleAboutConfig", () => { var help =

`клик+Alt	Библиотека закладок
…+ Shift	± zoom Текст/страница
։нажать	Антизапрет ⇆ proxy\n
ПКМ	Меню быстрых настроек
։нажать	© Краткая справка
…+ Alt	Опции about:config\n
СКМ	Вкладка «Топ сайтов»
…+ Alt	Восстановить вкладку
…+ Shift	● Захват цвета, Zoom
։нажать	Консоль браузера\n
⟳ Обновить ↯ Перезапуск
ЛкМ: Левый клик, СкМ: Колёсико
нажать: долгий клик мыши ≥1 сек`, // ЛКМ+Alt+Shift: настройки User Chrome Files. свободные hotkeys: СКМ+Alt ЛКМ+Alt+⇧ Нет в подсказке: ЛКМ	панель Журнала, СКМ+Shift – Пипетка

help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'],

// Ctrl+Click или правый клик - сброс параметра по-умолчанию
// клик по параметру с Shift блокирует авто-закрытие меню
// строки с userAlt имеют шрифт italic
//	refresh: false - reload current tab,	true - reload current tab skip cache
//	restart: false - restart browser,		true - restart browser with confirm
// Разделитель: Имя меню "—,⟳,↯" Опция, ⟳ обновить страницу, ↯ перезапуск браузера
// иконки равны ключам: userChoice:зелёный, userAlt:жёлтый, userPro:серый, нет userChoice:серый, ни один:красный

// пути сохранения Html/Pics в [Загрузки]/dir/subdir заданы в ucf_save.dirs="_Html dir|subdir|_Pics dir|subdir" : пусто|0 заголовок|1 домен
// ucf_save.dirs="_Web||_Pics|1" save Html: [Загрузки]/_Web/имя страницы.html, save Pic: [Загрузки]/_Pics/имя вкладки/имя картинки

	{prefs, dirsvc} = Services, db = prefs.getDefaultBranch(""), my_vpn = "https://antizapret.prostovpn.org/proxy.pac", icon_vpn = "hue-rotate(270deg) brightness(95%)", menuactive = (AppConstants.platform == "macosx") ? '#e8e8e8' : '#124', // текст, подсвеченный курсором
	xul_ns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
	fonts = ["Arial","Cantarell","DejaVu Sans","Roboto","PT Serif","Segoe UI","Ubuntu","Cambria","Fira Sans","Georgia","Noto Sans","Calibri","Times","системный"],
	font_pref = (font) => { return font.map(function(name) { // массив с вложениями
		return (name == font[font.length -1]) ? ["", name,,, `prefs.setIntPref("browser.display.use_document_fonts", 1)`] : [name, name,,, `prefs.setIntPref("browser.display.use_document_fonts", 0)`]; });
	},
	fontserif = font_pref(fonts), fontsans = [["PT Sans","PT Sans"], ...fontserif], dw; // путь загрузки
	try {dw = prefs.getComplexValue("browser.download.dir",Ci.nsIFile);} catch {dw = dirsvc.get("DfltDwnld", Ci.nsIFile);};

	var secondary = [{ // pref … [apref, lab, akey, hint, js-code]
			pref: ["dom.disable_open_during_load", "Всплывающие окна"], userChoice: 2, userAlt: true, values: [[true, "Блокировать"], [false, "Разрешить"]]
	},{
			pref: ["browser.safebrowsing.downloads.remote.block_dangerous", "Опасные файлы, сайты",,"browser.safebrowsing.downloads.remote.block_dangerous_host"], userChoice: true, userAlt: false,
			values: [[true, "Отключить",,,`prefs.setBoolPref('browser.safebrowsing.downloads.remote.block_dangerous_host',true)`], [false, "Загружать",,,`prefs.setBoolPref('browser.safebrowsing.downloads.remote.block_dangerous_host',false)`]]
	},{
			pref: ["permissions.default.image", "Загрузка графики"], userChoice: 1, userAlt: 3, refresh: true,
			values: [[1, "Разрешена"], [3, "Только с сайта"], [2, "Отключить"]]
	},{
			pref: ["ucf_save.dirs", `Сайт|Графика`,,`\nПути сохранения страниц | графики\n[Загрузки] — папка по-умолчанию${dw ? ":\n"+ dw.path : ""}`], userChoice: "_Сайты||_Фото|1", userAlt: "", userPro: "_Web||_Images|1",
			values: [
				["", "папка [Загрузки]"], // subdir: пусто | 0 заголовок | 1 домен
				[`_Сайты||_Фото|1`, "_Сайты|_Фото/имя…"], // _Web/host|_Pics/title
				[`_Сайты||_Рисунки|1`, "_Сайты|_Рисунки/…"],
				[`_Web||_Images|`, "_Web|_Images"],
				[`_Web||_Images|1`, "_Web|_Images/имя"],
				[`_Web||_Photo|1`, "_Web|_Photo/имя"],
				[`_Web|1|_Pics|0`, "_Web/сайт|_Pics/…"],
				[`_Web|1|_Pics|`, "_Web/сайт|_Pics"],
				[`_Web|1|_Images|0`, "ввести свои пути"]] // здесь нужно открыть about:config
	},null,{
			pref: ["network.cookie.cookieBehavior", "Получать куки",, "\nПерсональные настройки посещённых сайтов"], userChoice: 3, userAlt: 0, userPro: 4, refresh: false,
			values: [[0, "со всех сайтов"], [3, "кроме не посещённых"], [4, "кроме трекеров"], [1, "кроме сторонних"], [2, "никогда"]]
	},{
			pref: ["network.proxy.autoconfig_url", "Прокси (VPN) URL", "п"],
			userChoice: my_vpn, userAlt: "127.0.0.1", userPro: "", refresh: true,
			values: [
				["", "сброшен", ""],
				[my_vpn, "АнтиЗапрет", "1", "\nНадёжный доступ на заблокированные сайты\n«Режим прокси» меняется на 2", `prefs.setIntPref('network.proxy.type', 2); node.parentNode.parentNode.style.filter = icon_vpn;`],
				["https://git.io/ac-anticensority-pac", "ac-anticensority", "2"],
				// ["localhost", "Tor Browser", "4", "Только для Linux, MacOS\nУстановите сервис: «tor»"],
				[prefs.getStringPref("user.pacfile", "file:///etc/proxy.pac"), "user .pac файл", "3"], // нужен диалог выбора pac-файла
				["127.0.0.1", "local host", "0",, `prefs.setIntPref('network.proxy.type', 0); node.parentNode.parentNode.style.filter = '';`]]
	},{
			pref: ["network.proxy.type", "Режим прокси", "р"], userChoice: 0, userAlt: 2, refresh: true,
			values: [
				[0, "Без прокси", "0", "по-умолчанию"],
				[5, "Системные (из IE)", "5"],
				[2, "Автонастройка", "2", "about:config - user.pacfile"],
				[1, "Ручная настройка", "1", "Используется network.proxy.autoconfig_url"],
				[4, "Автоопределение", "4"] ]
	},{
	// 		pref: ["network.proxy.share_proxy_settings", "Все протоколы через прокси"], userAlt: true, refresh: true,
	// 		values: [[true, "Да", "", "Прокси для всех протоколов при ручной настройке"], [false, "Нет"]]
	// },{
			pref: ["network.trr.mode", "DNS поверх HTTPS",, "\nШифрование DNS-трафика для\nзащиты персональных данных"], userChoice: 1, userAlt: 2, userPro: 5, refresh: true,
			values: [
				[0, "по-умолчанию", "0"], [1, "автоматически", "1", "используется DNS или DoH, в зависимости от того, что быстрее"], [2, "DoH, затем DNS", "2"], [3, "только DoH", "3"], [4, "DNS и DoH", "4"], [5, "отключить DoH", "5"] ]
	},null,{
			pref: ["browser.zoom.full", "Масштабировать"], userChoice: false, userAlt: true,
			values: [[true, "всю страницу"], [false, "только текст"]]
	},{
			pref: ["font.name.sans-serif.x-cyrillic", "Шрифт без засечек ",,"\nТакже влияет на всплывающие подсказки\nСистемный: загрузка шрифтов документа"], userAlt: "", values: fontsans
	},{
			pref: ["font.name.serif.x-cyrillic", "Шрифт с засечками"], userAlt: "", values: fontserif
	},{
			pref: ["image.animation_mode", "Анимация изображений"], userChoice: "none", userAlt: "normal", refresh: true,
			values: [["none", "Выключена"], ["normal", "По циклу"], ["once", "Единожды"]]
	},null,{
			pref: ["media.autoplay.default", "Авто-play аудио/видео"], userChoice: 0, userAlt: 2, userPro: 5, refresh: true,
			values: [
				[0, "Разрешить", "0"], [2, "Спрашивать", "2"], [1, "Запретить", "1"], [5, "Блокировать", "5"]]
	},{
			pref: ["media.autoplay.blocking_policy", "Автозапуск (политика)"], userChoice: 1, userAlt: 2, refresh: true,
			values: [[1, "Временная", "1"], [2, "По действию", "2"], [0, "Постоянная", "0"]]
	},{
			pref: ["gfx.webrender.all", "Аппаратное ускорение графики"], userChoice: true, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["gfx.webrender.force-disabled", "Web render disabled", , "gfx.webrender.compositor.force-enabled\n\nАппаратная отрисовка страниц видеокартой.\nотключите при разных проблемах с графикой"],
			userChoice: false, restart: true, values: [
			[true, "Да",,, `prefs.setBoolPref("gfx.webrender.compositor.force-enabled", false)`],
			[false, "Нет",,, `prefs.setBoolPref("gfx.webrender.compositor.force-enabled", true)`]]
	},null,{
			pref: ["network.http.sendRefererHeader", "Referer: для чего"], userChoice: 2, userAlt: 1,
			values: [[0, "Ни для чего", "0"], [1, "Только ссылки", "1"], [2, "Ссылки, графика", "2"]]
	},{
			pref: ["dom.storage.enabled", "Локальное хранилище",, "\nСохранение персональных данных, по\nкоторым вас можно идентифицировать"],
			userChoice: false, userAlt: true,
			values: [[true, "Разрешить"], [false, "Запретить"]]
	},{
			pref: ["privacy.resistFingerprinting", "Изоляция Firstparty-Fingerprint", ,"privacy.firstparty.isolate\n\nЗащита данных пользователя также\nзапрещает запоминать размер окна"], userChoice: false,
			values: [[true, "Да", , "Защита от слежки",`prefs.setBoolPref('privacy.firstparty.isolate', true);`], [false, "Нет", , "Защита от слежки",`prefs.setBoolPref('privacy.firstparty.isolate', false);`]]
	},{
			pref: ["media.peerconnection.enabled", "WebRTC ваш реальный IP"], userChoice: false,
			values: [[true, "Выдать"], [false, "Скрыть"]]
	},null,{
			pref: ["browser.tabs.remote.force-enable", "Многопоточный режим вкладок",,"\nПо-умолчанию режим разрешён"], userChoice: null, userAlt: true, userPro: false,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["javascript.enabled", "Выполнять скрипты Java",,"\nПоддержка интерактивных сайтов (и рекламы)"], userChoice: true, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["browser.cache.disk.capacity", "Кэш браузера",,"browser.cache.memory.enable\n\ncache.memory.max_entry_size:\nдиск и память: 5120\nтолько память: -1"], userChoice: 1048576, userAlt: 0,
			values: [
			[1048576, "Диск и Память",,, `prefs.setBoolPref("browser.cache.memory.enable", true); prefs.setBoolPref("browser.cache.disk.enable", true); prefs.setIntPref('browser.cache.memory.max_entry_size', 5120)`],
			[0, "только Память",,, 		`prefs.setBoolPref("browser.cache.memory.enable", true); prefs.setBoolPref("browser.cache.disk.enable", false); prefs.setIntPref('browser.cache.memory.max_entry_size', -1)`],
			[2097152, "только Диск",,, `prefs.setBoolPref("browser.cache.memory.enable", false); prefs.setBoolPref("browser.cache.disk.enable", true)`]]
	},{
			pref: ["dom.enable_performance", "Статус загрузки страницы",,"\nПередача данных разрешит определять\nфакт использования прокси-сервера"], userAlt: true
	},{
			pref: ["general.useragent.override", "User Agent",,"\nот user-агент зависит вид сайтов"],
			userChoice: null, userAlt: "Mozilla/5.0 (Android 9; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0", userPro: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:68.0) Gecko/20100101 Firefox/68.0", refresh: true,
			values: [
				(arr => {
					var pref = "general.useragent.override";
					var has = prefs.prefHasUserValue(pref);
					if (has) {
						var val = prefs.getStringPref(pref);
						prefs.clearUserPref(pref);
					}
					var ua = Cc["@mozilla.org/network/protocol;1?name=http"]
						.getService(Ci.nsIHttpProtocolHandler).userAgent; // текущий юзерагент
					has && prefs.setStringPref(pref, val);

					var find = node => node.pref && node.pref.pref == pref;
					var redef = (doc, hint) => {
						var popup = doc.getElementById("ToggleAboutConfig-secondaryPopup");
						var menuitem = Array.from(popup.children).find(find).menupopup.firstChild;
						menuitem.tooltipText = hint ? ua + "\n" + hint : ua;
						menuitem.setAttribute("oncommand",
							`event.stopPropagation();
							this.closest("toolbarbutton").linkedObject.contextmenu({
								preventDefault: Boolean,
								target: this.parentNode.parentNode
							});`
						);
					}
					Object.defineProperty(arr, "0", {enumerable: true, get() {
						if (Components.stack.formattedStack.includes("createRadios")) {
							var win = Services.wm.getMostRecentWindow("navigator:browser");
							win.setTimeout(redef, 0, win.document, this[3]);
						}
						else return "";
					}});
					return arr;
				})([null, "По-умолчанию"]),
				["Mozilla/5.0 (Android 9; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0", "Firefox Android9"],
				["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:68.0) Gecko/20100101 Firefox/68.0", "Firefox 68 MacOSX"],
				["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Safari/537.36", "Chrome61 Win10"],
				["Mozilla/5.0 (Windows NT 6.1; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0", "Firefox 56"],
				["Mozilla/5.0 (X11; Linux x86_64; rv:56.0) Gecko/20100101 Firefox/56.0", "Firefox 56 Linux"],
				["Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", "MSIE 6.0 Windows"],
				["Mozilla/5.0 (Linux; Android 7.0; PLUS Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", "Chrome61 Android7"],
				["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", "Safari 6 MacOSX"],
				["Opera/9.80 (Windows NT 6.2; Win64; x64) Presto/2.12 Version/12.16", "Opera12 W8"],
				["Mozilla/5.0 (Linux; Android 5.1.1; SM-G928X Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36", "Samsung Galaxy S6"],
				["Mozilla/5.0 (PlayStation 4 3.11) AppleWebKit/537.73 (KHTML, like Gecko)", "Playstation 4"],
				["Xbox (Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586", "Xbox One (mobile)"],
				["Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586", "Microsoft Lumia 950"],
				["Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; GT-I8350)", "Windows Phone"],
				["Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", "GoogleBot"]]
	},{
			pref: ["browser.cache.disk.free_space_soft_limit", "Неактивные вкладки",, "\nПри запуске загружаются все вкладки,\nэто может замедлить работу браузера.\nОптимально - Выгружать старые табы."], userChoice: 5121, userAlt: 5120, userPro: 5122,
			values: [
			[5120, "Загружать",,, `prefs.setBoolPref("browser.sessionstore.restore_on_demand", true); prefs.setBoolPref("browser.tabs.unloadOnLowMemory", false)`],
			[5121, "Оптимально",, "Выгружать старые вкладки, экономя память", `prefs.setBoolPref("browser.sessionstore.restore_on_demand", true); prefs.setBoolPref("browser.tabs.unloadOnLowMemory", true)`],
			[5122, "Не загружать",,, `prefs.setBoolPref("browser.sessionstore.restore_on_demand", false); prefs.setBoolPref("browser.tabs.unloadOnLowMemory", true)`]]
	}];

	return {
		label: "Quick Toggle Settings",
		id: "ToggleAboutConfig",
		tooltiptext: help, localized: false,
		image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAADhUlEQVR4AaWUIYzsNhCG91WVelKfqoWrpwMLFxQYGhoGBgYGBgYGGhoGBhoGGhoaBhoaGhqcdIFhf+dGt2n09rpq1ZE+2bHl+eyxd0993yVjNIwxq9F6/WjtaNd5nBnq85g2PMeMo8EwDEUI0Z7+jm/EY2it87ZtuAf1EUqAzZZZcuCxnyOlBFoLkuhnkgdBjBG36YqzP+EaTqgmSckyjrGuK8paWGythVLKfv/9t/O/OkEIC6pZoskCfanQuQYLSTm2/ZQkjSil8Pc8z6iqyp/P58tn2l92wWjGnEuG9gO6uUVtaygnMOQaU+nR+hotjfeugwmaHNv9pJzYOYcQAp+kbVv3cBJrbA45QIQLVLyizRI6t5R8gC0aY+nQZYWK5lT4E3nNn2XaiJX5PAmLXl9fuweBz54X97mCoYR21SwYc8+yjsbrKKGCvAu4TDnne3IO7z2oTP1RwCVKJXGJ+rlHaxu0ocZQahLWaO4lmjtov5eI7yDZFnFqWXQUPL1kFzwaV3GpmqQo+cMlc1li9MBCyacbUnC74MePy3NBiAukFRDLBTd/QWUVUk44BJclx5lMPVYvEJ3e7+CZYN+dSw5TmpiQA1/oPTYip4SV5gANRIloa5Ku/LKeCqhljrHmhLzMSIFYPGLw3N/KCMAAuUYeBUkXLDE9vCIW0K45MbXYoW/aFZdiyxYoM9Y0oiwGaxyBjcAElAHFCETTw40T6tvVXP749eUfBe/v78xdlJaAvNwTGmo1YYiRIME6YosGxWnEaUDQbXFt5adazKO6DEcBX97b29tRAq4tl0XzpZKAkzJFEx31iY1bYuANIBtkW6engl1CRD6JAYrh5Mg90QBRAYsgbkAkksS2VJvT0jXqUj0R7HzOrViCQ1kGILZAUAQl9AxLVl/BDTI24tyK1+/8n/SVYOexVDMlaQAvAHdjOHmQLPS6grxe9ZeviDgKuM/jzIZM38kbbE4ekguGBV7CDwLXy1k/vKJtPf4ODmAPFhbf74mLa5DmFltQwCKJCskIiPNpprTi42+bRcaYTIv3Z/oVhcgpUrIOm1NYbIvZWtjJIliSLgpIFdZZohMvgQRqFyilRimFF0I4+QXik7ZR3vRqqSsRpJSO1jEf/bFXGUsNhAq2uWS63eb0X+NjN/uuforb9UXYXjqEBkkLyMupP85/I/53fDzLsZV6am8zCdR9/C+4MZTbZ2zc/gAAAABJRU5ErkJggg==",
		onCreated(btn) {
			btn.setAttribute("image", this.image);
			var doc = btn.ownerDocument;

			btn.btn = true;
			btn.domParent = null;
			btn.popups = new btn.ownerGlobal.Array();
			this.createPopup(doc, btn, "secondary", secondary);
			this.createCloseMenusOption(doc, btn);

			if (prefs.getIntPref('network.proxy.type') == 2)
				btn.style.filter = icon_vpn; // btn.style.cssText = "background-image: -moz-linear-gradient(#c0c8c0, #c0c8c0, #c0c8c0) !important";

			btn.linkedObject = this;
			for(var type of ["command", "contextmenu", "mousedown", "auxclick"]) // события
				btn.setAttribute("on" + type, `linkedObject.${type}(event)`);
			this.addSheet(btn);
		},
		addSheet(btn) {
			var cb = Array.isArray(btn._destructors);
			var id = cb ? btn.id : "ToggleAboutConfig";
			var css = `#${id} menu[_moz-menuactive] {
				color: ${menuactive} !important;
			}`;
			var args = [
				"data:text/css;charset=utf-8," + encodeURIComponent(css),
				Ci.nsIDOMWindowUtils.USER_SHEET
			];
			if (cb) var destructor = function() {
				this.removeSheetUsingURIString(...args);
			}
			var add = b => b.ownerGlobal.windowUtils.loadSheetUsingURIString(...args);
			(this.addSheet = !cb ? add : btn => {
				add(btn);
				btn._destructors.push({destructor, context: btn.ownerGlobal.windowUtils});
			})(btn);
		},
		createPopup(doc, btn, name, data) {
			var popup = doc.createElementNS(xul_ns, "menupopup");
			var prop = name + "Popup";
			btn.popups.push(btn[prop] = popup);
			popup.id = this.id + "-" + prop;
			for (var type of ["popupshowing", "click"])
				popup.setAttribute("on" + type, `parentNode.linkedObject.${type}(event)`);
			for(var obj of data) popup.append(this.createElement(doc, obj));
			btn.append(popup);
		},
		map: {b: "Bool", n: "Int", s: "String"},
		createElement(doc, obj) {
			if (!obj) return doc.createElementNS(xul_ns, "menuseparator");
			var pref = doc.ownerGlobal.Object.create(null), node, img, bool;
			for(var [key, val] of Object.entries(obj)) {
				if (key == "pref") {
					var [apref, lab, akey, hint] = val;
					pref.pref = apref; pref.lab = lab || apref;
					if (hint) pref.hint = hint;
				}
				else if (key == "image") img = val, pref.img = true;
				else if (key != "values") pref[key] = val;
				else pref.hasVals = true;
			}
			var type = prefs.getPrefType(pref.pref);
			var str = this.map[type == prefs.PREF_INVALID
				? obj.values ? (typeof obj.values[0][0])[0] : "b"
				: type == prefs.PREF_BOOL ? "b" : type == prefs.PREF_INT ? "n" : "s"
			];
			pref.get = prefs[`get${str}Pref`];
			var map, set = prefs[`set${str}Pref`];
			if (pref.hasVals) {
				for(var [val, , , , code] of obj.values)
					code && (map || (map = new Map())).set(val, code);
				if (map) pref.set = (key, val) => {
					set(key, val);
					map.has(val) && eval(map.get(val)); // выполнить код
				}
			}
			if (!map) pref.set = set;

			node = doc.createElementNS(xul_ns, "menu");
			node.className = "menu-iconic";
			node.setAttribute("closemenu", "none");
			img && node.setAttribute("image", img);
			akey && node.setAttribute("accesskey", akey);
			(node.pref = pref).vals = doc.ownerGlobal.Object.create(null);
			this.createRadios(doc,
				str.startsWith("B") && !pref.hasVals ? [[true, "true"], [false, "false"]] : obj.values,
				node.appendChild(doc.createElementNS(xul_ns, "menupopup"))
			);
			if ("userChoice" in obj) pref.noAlt = !("userAlt" in obj);
			return node;
		},
		createCloseMenusOption(doc, btn) {
			var pn = this.closePref = "ToggleAboutConfig.closeMenus";
			var data = [null, {
				pref: [pn, "Закрывать меню этой кнопки"], values: [[true, "Да"], [false, "Нет"]]
			}];
			var setCloseMenus = (e, trg = e.target) => {
				e.stopPropagation();
				var {pref, val} = trg, updPopup = true, clear;
				switch(e.type) {
					case "command": pref = (trg = trg.closest("menu")).pref; updPopup = false; break;
					case "click": if (e.button) return; break;
					case "contextmenu": e.preventDefault(); clear = pref;
				}
				if (!pref) return;
				if (clear) prefs.clearUserPref(pn);
				else if (!updPopup && val === pref.val) return;
				else pref.set(pn, val !== undefined ? val : !pref.val);
				this.upd(trg);
				updPopup && this.popupshowing(null, trg.querySelector("menupopup"));
			}
			(this.createCloseMenusOption = (doc, btn) => {
				for(var obj of data)
					btn.secondaryPopup.append(this.createElement(doc, obj));
				var m = btn.secondaryPopup.lastChild;
				m.style.cssText = "fill: dimgray !important; list-style-image: url(chrome://browser/skin/menu.svg) !important;";
				m.setAttribute("oncommand", "setCloseMenus(event)");
				m.onclick = m.oncontextmenu = m.setCloseMenus = setCloseMenus;
			})(doc, btn);
		},
		UserImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAe1BMVEUAAAB1dXV1dXV1dXV1dXV1dXV1dXV1dXV8fHyRkZGfn5+oqKipqamqqqqysrKzs7O6urq8vLy9vb3ExMTFxcXHx8fNzc3Ozs7Pz8/Q0NDY2NjZ2dna2tre3t7g4ODi4uLj4+Pk5OTl5eXn5+fs7Ozv7+/w8PDx8fHy8vLK4aRZAAAACHRSTlMAGFiAiNDw+LBTincAAACRSURBVBgZBcFBTsMADAAwJ20pqjgM+P8TgQua2Nok2AAAAiIzdfcgkOv2/ub357yaIF8+jwgz969nS7He9pnumf22hrSse1d1V/W+LlIc3d1V3d1HCPsHkWjj+5FUTVdVTdcQtmMNkXrMdT/Dur0KYYy/8wqx7QugHucE+bIFmPPZArkuS+qqqwmIzNTdAwAA/zI6Uy+Vzu5KAAAAAElFTkSuQmCC", // серый
		UserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABGElEQVR4AWKgPgAQSmbgEsNQFB53GHz43GGcioPF4mCxGCwWi8FiMBgMBoPFYOzOPWf2/cv3Z/3P7Wo2W2VQolIJ59zb/grvlfLnO1lWJ7lmwDn2cEbnY3jciF0XSTUqSfKZRCLP4NB9c9tlyrOE4iXUwMAd2OMZHLiPj2O0uUZcWcQXp3jKsQTAuSeODlxm7grEIRmxZZal2EuhOxjEGR24yNwXqHOe5MQsdtVCq72He+DiIfNQYMxGwJRHhdIzPLs4zwViGw4ypF5MGsiYIBLML/t04D49gja7kz4eyEB6yiQCnhG4zDx/xsa30sUL3RMtgXP5jO9+JApt2L+DZ3C+/o2sbv9l5xsNtYBz7B0HS8qUZSZqAwCSaKMMRS9J3wAAAABJRU5ErkJggg==", // зелёный
		notUserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABDElEQVR4Aa2TK3CEQBBEt6JwdxKJRJ48GRkZiURGxqt4dV5FnkQikSdXIpHIweE6+8KKyf+7Va92qqe770v493MOoUw8JGJizcSslV+Fm3NRWLxrZUMnzaOAGY0dng/DXVnK+kdpGhKXFI4CZjR2ePC+edu0W3eSYieNPQEPGjvhwUuG7FXuuK+b691uX4Sw2obBmkHbwIOXjC+4qY61D+d5Bq89g5eML6hpxvQd8JLxBXk5uxsM3p05vmBcJhfmnrkzbmaHl4wv6KfL5IKuZHbFecZLxhecxiEuiwVX4nBhPHjJvP0j7QtZe5TaWmqqdG8wo7HD8+m/MWHxUMluDym0wYzGDs/fHqb/Pk9WVkDcHStz1AAAAABJRU5ErkJggg==", // красный
		UserAltImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABAklEQVR4Aa2TKVDEQBREx3sRiUQi8Wpl5MqVSCQyMhIZGYlciVy5cmQkEhk5runHNQf3kapX9ev36w97hX9/xk3ozGCiScDMjuyr8va6D+syd9LdIOkgYGZHhvNhed4FyxdSmswsaQ/M7MiEg9uWO65r6aV1MCOFCnZkOLh0ygPjMgXpvjc7i5f50HORHRkOLp3yQNSR131uYZMPFbAjw8GlUx5IWk5kLJwiGeQMOzIcoFMfiEEFz+IZMHsHOW8PRO0dHMyxIEK9w8FtX8IYBwe3gPQeOcel0377Vt1w/XNwcOm8/SJtLcxI70OG8+m3kevxKihNucjMjgznbz+m/34eAEg4sZItNtjUAAAAAElFTkSuQmCC", // жёлтый
		regexpRefresh: /^(?:view-source:)?(?:https?|ftp)/,
		upd(node) {
			var {pref} = node, def = false, user = false, val;
			if (prefs.getPrefType(pref.pref) != prefs.PREF_INVALID) {
				var pn = pref.pref;
				try {val = pref.defVal = db[pref.get.name](pn); def = true}
				catch(ex) {def = false;}
				var user = prefs.prefHasUserValue(pn);
				if (user) try {val = pref.get(pn, undefined);} catch(ex) {}
			}
			if (val == pref.val && def == pref.def && user == pref.user) return;
			pref.val = val; pref.def = def; pref.user = user;
			var exists = def || user;

			var hint = exists ? val : "Эта опция не указана";
			if (hint === "") hint = "[ пустая строка ]";
			hint += "\n" + pref.pref;
			if (pref.hint) hint += "\n" + pref.hint;
			node.tooltipText = hint;

			var img, alt = "userAlt" in pref && val == pref.userAlt, pro = "userPro" in pref && val == pref.userPro;
			if (alt) img = this.UserAltImg;
			if (pro) img = this.UserImg;
			if ("userChoice" in pref)
				if (val == pref.userChoice)
					node.style.removeProperty("color"),
					img = this.UserChoiceImg;
				else {
					node.style.setProperty("color", "#804040", "important");
					if (!alt && !pro) img = this.notUserChoiceImg;
				}
			node.nextSibling && node.setAttribute("image", img || this.UserImg); // серый значок, если нет userChoice
			user
				? node.style.setProperty("font-style", "italic", "important")
				: node.style.removeProperty("font-style");

			var {lab} = pref;
			if (exists && pref.hasVals) {
				if (val in pref.vals) var sfx = pref.vals[val] || val;
				else var sfx = user ? "другое" : "стандарт";
				lab += ` ${"restart" in pref ? "↯-" : "refresh" in pref ? "-⟳" : "—"} ${sfx}`;
			}
			lab = exists ? lab : '['+ lab +']'; // имя = [имя] если преф не существует
			node.setAttribute("label", lab);
		},
		createRadios(doc, vals, popup) {
			for(var arr of vals) {
				if (!arr) {
					popup.append(doc.createElementNS(xul_ns, "menuseparator"));
					continue;
				}
				var [val, lab, key, hint] = arr;
				var menuitem = doc.createElementNS(xul_ns, "menuitem");
				menuitem.setAttribute("type", "radio");
				menuitem.setAttribute("closemenu", "none");
				menuitem.style.setProperty("font-style", "italic", "important"),
				menuitem.setAttribute("label", popup.parentNode.pref.vals[val] = lab);
				key && menuitem.setAttribute("accesskey", key);
				var tip = menuitem.val = val;
				if (hint) tip += "\n" + hint;
				menuitem.tooltipText = tip;
				popup.append(menuitem);
			}
		},
		openPopup(popup) {
			var btn = popup.parentNode;
			if (btn.domParent != btn.parentNode) {
				btn.domParent = btn.parentNode;
				if (btn.matches(".widget-overflow-list > :scope"))
					var pos = "after_start";
				else var win = btn.ownerGlobal, {width, height, top, bottom, left, right} =
					btn.closest("toolbar").getBoundingClientRect(), pos = width > height
						? `${win.innerHeight - bottom > top ? "after" : "before"}_start`
						: `${win.innerWidth - right > left ? "end" : "start"}_before`;
				for(var p of btn.popups) p.setAttribute("position", pos);
			}
			popup.openPopup(btn);
		},
		maybeRestart(node, conf) {
			if (conf && !Services.prompt.confirm(null, this.label, "Перезапустить браузер?")) return;
			var cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
			Services.obs.notifyObservers(cancel, "quit-application-requested", "restart");
			return cancel.data ? Services.prompt.alert(null, this.label, "Запрос на выход отменён.") : this.restart();
		},
		async restart() {
			var meth = Services.appinfo.inSafeMode ? "restartInSafeMode" : "quit";
			Services.startup[meth](Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
		},
		maybeRe(node, fe) {
			var {pref} = node;
			if ("restart" in pref) {
				if (this.maybeRestart(node, pref.restart)) return;
			}
			else this.popupshowing(fe, node.parentNode);
			if ("refresh" in pref) {
				var win = node.ownerGlobal;
				if (this.regexpRefresh.test(win.gBrowser.currentURI.spec)) pref.refresh
					? win.BrowserReloadSkipCache() : win.BrowserReload();
			}
		},
		maybeClosePopup(e, trg) {
			!e.shiftKey && prefs.getBoolPref(this.closePref, undefined)
				&& trg.parentNode.hidePopup();
		},
		popupshowing(e, trg = e.target) {
			if (trg.state == "closed") return;
			if (trg.id) {
				for(var node of trg.children) {
					if (node.nodeName.endsWith("r")) continue;
					this.upd(node);
					!e && node.open && this.popupshowing(null, node.querySelector("menupopup"));
				}
				return;
			}
			var {pref} = trg.closest("menu"), findChecked = true;

			var findDef = "defVal" in pref;
			var checked = trg.querySelector("[checked]");
			if (checked) {
				if (checked.val == pref.val) {
					if (findDef) findChecked = false;
					else return;
				}
				else checked.removeAttribute("checked");
			}
			if (findDef) {
				var def = trg.querySelector("menuitem:not([style*=font-style]");
				if (def)
					if (def.val == pref.defVal) {
						if (findChecked) findDef = false;
						else return;
					}
					else def.style.setProperty("font-style", "italic", "important");
			}
			for(var node of trg.children) if ("val" in node) {
				if (findChecked && node.val == pref.val) {
					node.setAttribute("checked", true);
					if (findDef) findChecked = false;
					else break;
				}
				if (findDef && node.val == pref.defVal) {
					node.style.removeProperty("font-style");
					if (findChecked) findDef = false;
					else break;
				}
			}
		},
		click(e) {
			if (e.button) return;
			var trg = e.target, {pref} = trg;
			if (!pref) return;
		},
		command(e) { // нажатия левой кнопки мыши
			var trg = e.target, win = e.view;
			if (trg.btn) { // LMB
				if (e.shiftKey) e.altKey
					? win.openDialog("chrome://user_chrome_files/content/options/prefs_win.xhtml", "user_chrome_prefs:window", "centerscreen,resizable,dialog=no") // Alt+Shift
					: e.view.ZoomManager.toggleZoom(); // Alt Пипетка
				else if (e.altKey)
					e.view.PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar"); // Shift Библиотека Панель закладок
				else {  // LMB Click
					var bar = e.target.ownerDocument.querySelector("#ucf-additional-vertical-bar")
					if (bar) {
						win.setToolbarVisibility(bar, bar.collapsed);
						bar.collapsed ? win.SidebarUI.hide() : win.SidebarUI.show("viewHistorySidebar");
					} else
						win.SidebarUI.toggle("viewHistorySidebar");
				}
				return;
			}
			var menu = trg.closest("menu"), newVal = trg.val;
			this.maybeClosePopup(e, menu);
			if (newVal != menu.pref.val)
				menu.pref.set(menu.pref.pref, newVal),
				this.maybeRe(menu, true);
		},
		auxclick(e) { // CKM
			if (e.button != 1 || !e.target.btn) return;
			if (e.altKey) e.shiftKey
				? this.switchToTab(e, "ya.ru") // Alt+Shift
				: e.view.undoCloseTab()
			else if (e.shiftKey)
				this.eyedropper(e.target)
			else
				this.switchToTab(e, "about:newtab");
		},
		contextmenu(e) { // RMB
			var trg = e.target, win = e.view;
			if (trg.btn) {
				if (e.ctrlKey || e.shiftKey) return;
				if (e.detail == 2) return trg.secondaryPopup.hidePopup(); // меню быстрых настроек
				! e.altKey ? this.openPopup(trg.secondaryPopup) : this.switchToTab(e, "about:config");
			}
			else if ("pref" in trg) {
				this.maybeClosePopup(e, trg);
				if (trg.pref.user)
					prefs.clearUserPref(trg.pref.pref),
					this.maybeRe(trg);
			}
			e.preventDefault();
		},
		mousedown(e) {
			var reset = e => e.target.linkedObject = this;
			var id, lo = {command: reset, mousedown: reset, auxclick: e => e.button != 1 || reset(e)};
			var lin = /macos|linux/.test(e.view.AppConstants.platform);
			var stop = e => reset(e) && e.preventDefault();
			lo.contextmenu = lin
				? e => e.ctrlKey || e.shiftKey ? dsp(e) : stop(e) : stop;
			var context = lin
				? e => e.button == 2 && e.type.endsWith("p") && this.contextmenu(e) : () => {};
			var dsp = (e, timeout) => {
				var trg = e.target;
				trg.onmouseup = trg.onmouseleave = null;
				if (timeout) return this.londPress(e);
				e.view.clearTimeout(id);
				reset(e);
				context(e);
			}
			(this.mousedown = e => {
				var trg = e.target;
				if (!trg.btn) return;
				trg.linkedObject = lo;
				trg.onmouseup = trg.onmouseleave = dsp;
				id = e.view.setTimeout(dsp, 500, e, true);
			})(e);
		},
		londPress(e) { // удержание кнопки мыши. на второй долгий клик при отпускании сработает действие на обычный клик этой кнопки
			var trg = e.target, win = e.view;
			if (e.button == 0) this.switchProxy(e, my_vpn);
			if (e.button == 1) trg.ownerDocument.getElementById("key_browserConsole").doCommand(); // Консоль браузера
			if (e.button == 2) { // RMB Long
				var newURI = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).convertChromeURL(Services.io.newURI(help_ucf[0])); // .spec = file:///
				(newURI.QueryInterface(Ci.nsIFileURL).file.exists()) ? this.switchToTab(e, help_ucf[0]) : this.switchToTab(e, help_ucf[1]);
			}
		},
		switchProxy(e, pac) {
			if (prefs.getIntPref('network.proxy.type') == 2) { // выключить
				prefs.setIntPref('network.proxy.type', 0);
				prefs.setStringPref("network.proxy.autoconfig_url", "127.0.0.1");
				e.target.style.removeProperty("filter");
				this.showInStatusPanel("\u{1F6A6} Настройки сети - работа без прокси"); // символ Светофор
			} else {
				prefs.setIntPref('network.proxy.type', 2);
				prefs.setStringPref("network.proxy.autoconfig_url", pac);
				e.target.style.setProperty("filter", icon_vpn, "important");
				this.showInStatusPanel("\u{1F6A6} Заблокированные сайты через «АнтиЗапрет»");
				// this.Notify('Proxy', 'Работаем через VPN Антизапрет');
			}
		},
		showInStatusPanel(info, ms = 5000) {
			var win = Services.wm.getMostRecentWindow("navigator:browser"); StatusPanel = win.StatusPanel;
			if (StatusPanel.update.tid)
				clearTimeout(StatusPanel.update.tid)
			else {
				var {update} = StatusPanel;
				StatusPanel.update = () => {};
				StatusPanel.update.ret = () => {
					StatusPanel.update = update;
					StatusPanel.update();
				}
			}
			StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, ms);
			StatusPanel._label = info;
		},
		eyedropper(trg) { // Пипетка - захват цвета
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm")
				.require("devtools/client/menus").menuitems
				.find(menuitem => menuitem.id == "menu_eyedropper");
			(this.eyedropper = target => obj.oncommand({target}))(trg);
		},
		Notify(title, text, ms = 3000){
			Cc['@mozilla.org/alerts-service;1'].getService(Ci.nsIAlertsService).showAlertNotification(null, title, text, false, '', null, ms);
		},
		switchToTab(e = this, url) { // открыть вкладку | закрыть, если открыта
			for(var tab of e.view.gBrowser.tabs)
				if ( tab.linkedBrowser.currentURI.spec == url ) {e.view.gBrowser.removeTab(tab); return;}; // вкладка найдена, закрыть
			e.view.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
		}
	};
}); // END ToggleAboutConfig

Dobrov пишет

Вместе с правым кликом в MacOS и вероятно Linux открывается контекстное меню панели

Посмотрел на Mint, и точно, так и есть.
Тогда можно попробовать манипуляцию атрибутом "context".

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

Выделить код

Код:

(async (bar, exp, tid, self) => CustomizableUI.createWidget(self = {
	label: "Панели, Папки",
	tooltiptext: [
		"ЛКМ:	★ Закладки\n…+ Alt	Домашняя папка",
		"ПКМ:	⟳ История\n…+ Alt	Папка установки",
		"СКМ:	Папка профиля\n…+ Alt	user_chrome_files"
	].join("\n"),
	id: "add-additional-personaltoolbar-button",
	localized: false,
	onCreated(btn) {
		btn.onclick = this.click;
		btn.toggleAttribute("context");
		btn.style.setProperty("list-style-image", "url(chrome://user_chrome_files/content/vertical_top_bottom_bar/svg/bookmark-16.svg)", "important");
	},
	exec(num, win) {
		tid = null;
		self[num](win);
	},
	context(win) {
		var btn = win.document.getElementById(this.id);
		btn.removeAttribute("context");
		btn.dispatchEvent(new win.MouseEvent("contextmenu", this.a));
		btn.toggleAttribute("context");
	},
	a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

	click(e) {
		if (e.detail > 2) return;
		var n2 = e.button != 2;
		var dbl = e.detail == 2;
		var num = 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;

		if (!self[num]) {
			if (n2) return;
			num = "context";
			for(var p in self.a) self.a[p] = e[p];
		}

		var win = e.view;
		if (dbl) tid &&= win.clearTimeout(tid), self[num](win);
		else tid = win.setTimeout(self.exec, 300, num, win);
	},
	0:   w => bar(w, "viewBookmarksSidebar"), // ЛКМ
	2:  () => exp("Home"), // Alt+ЛКМ
	16: () => exp("ProfD"), // СКМ
	18: () => exp("UChrm", "user_chrome_files"), // Alt+СКМ
	32:  w => bar(w, "viewHistorySidebar"), // ПКМ
	34: () => exp("GreD"), // Alt+ПКМ

	1(win) { // Double Left Click
		win.alert("DBL Click");
	},
	33(win) { // Double Right Click
		win.alert("DBL Right Click");
	},
	29(win) { // Ctrl + Shift + Double Middle Click
		win.alert("Ctrl + Shift + DBL Middle Click");
	},
}))(
	(win, bar) => win.SidebarUI.toggle(bar),
	(dir, sub) => {
		dir = Services.dirsvc.get(dir, Ci.nsIFile);
		sub && dir.append(sub);
		dir.exists() && dir.launch();
	}
);


А ещё у меня там не работают альт-клики.
Насколько я понял, mousedown занят системой:
альт-левый — перетаскивание окна,
альт-средний — менюшка как по Alt+Пробел,
альт-правый — ресайз окна.
Но у тебя, видимо, такого нет.


Dobrov пишет

скрипт перехвата кликов на downloads-button, PanelUI-menu

Там полную раскликовку сделать сложнее,
потому что панельки из них вываливаются по mousedown.

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

Выделить код

Код:

/*
	listener = e => { var trg = e.target; // Downloads Clicks
		if (e.button == 1) {
			if (e.shiftKey) { // СКМ + Shift
				if ( prefs.getIntPref("permissions.default.image", 1) == 1)
					prefs.setIntPref("permissions.default.image", 2), trg.style.filter = "hue-rotate(180deg) brightness(95%)"
				else
					prefs.setIntPref("permissions.default.image", 1), trg.style.filter = "";
				BrowserReload();
			} else	// СКМ Click
				saveSelectionToTxt(); // сохранить .txt
		} else if (e.button == 2) {
			if (e.shiftKey)
				Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
			else	// ПКМ Click
				save(); // Single HTML
		}
	},
	listener_pui = e => { // PanelUI-menu Clicks
		if (e.button == 1) {
			if (e.altKey)
				window.BrowserFullScreen()
			else
			if( window.windowState != window.STATE_MAXIMIZED )
				window.maximize()
			else
				window.restore();
		} else
			if (e.button == 2) if (e.altKey) return
		else {
			e.stopPropagation();
			(e.shiftKey) ? window.close() : window.minimize();
		}
	}, // end Clicks
*/
	tid, allowMousedown, listener = {
		handleEvent(e) {
			if (e.detail > 2) return;
			var btn = e.target;
			var dbl = e.detail == 2;
			var num = (btn == pui && 100) +
				16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;

			if (!this[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "mousedown";
			}
			if (dbl) tid &&= clearTimeout(tid), this[num](btn);
			else tid = setTimeout(this.exec, 300, num, btn, this);
		},
		exec(num, btn, self) {
			tid = null;
			self[num](btn);
		},
		mousedown(btn) {
			allowMousedown = true;
			btn.dispatchEvent(new MouseEvent("mousedown", {}));
			allowMousedown = false;
		},
		context(btn) {
			btn.removeAttribute("context");
			btn.dispatchEvent(new MouseEvent("contextmenu", this.a));
			btn.toggleAttribute("context");
		},
		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

		/*** ======= Downloads Clicks ======= ***/

		16: saveSelectionToTxt, // СКМ Click (сохранить .txt)
		20(btn) { // СКМ + Shift
			var pref = "permissions.default.image";
			var one = prefs.getIntPref(pref) == 1;
			prefs.setIntPref(pref, one ? 2 : 1);
			btn.style.filter = one ? "hue-rotate(180deg) brightness(95%)" : "";
			BrowserReload();
		},
		32: save, // ПКМ Click (Single HTML)
		36() { // Shift + ПКМ
			Downloads.getSystemDownloadsDirectory()
				.then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
		},

		1() { // Double Left Click
			alert("Double Left Click");
		},
		41() { // Ctrl + Double Right Click
			alert("Ctrl + Double Right Click");
		},
		4() { // Shift + ЛКМ
			alert("Shift + ЛКМ");
		},
		
		/*** ======= PanelUI-menu Clicks ======= ***/

		118: BrowserFullScreen, // Alt + СКМ
		116() { // СКМ
			windowState != STATE_MAXIMIZED ? maximize() : restore();
		},
		132() { // ПКМ
			minimize();
		},
		136: BrowserTryToCloseWindow, // Shift + ПКМ
		134() { // Alt + ПКМ
			gCustomizeMode.enter();
		},

		101() { // Double Left Click
			alert("Double Left Click");
		},
		141() { // Ctrl + Double Right Click
			alert("Ctrl + Double Right Click");
		},
		104() { // Shift + ЛКМ
			alert("Shift + ЛКМ");
		},

	}, // end Clicks
Выделить код

Код:

//	btn.setAttribute("context", "event.stopPropagation()");
Выделить код

Код:

/*
	btn.addEventListener("click", listener), pui.addEventListener("click", listener_pui);
		window.addEventListener("keydown", keydown_win);
	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		btn.removeEventListener("click", listener), pui.removeEventListener("click", listener_pui);
		window.removeEventListener("keydown", keydown_win);
	}};
*/
	var mouseenter = function(e) {
		this.parentNode.addEventListener("mousedown", stop, true);
		this.addEventListener("mouseleave", mouseleave, {once: true});
	}
	var mouseleave = function(e) {
		this.parentNode.removeEventListener("mousedown", stop, true);
	}
	var stop = e => {
		e.button || allowMousedown || e.stopImmediatePropagation();
	}
	var btns = [btn, pui];
	for(var b of btns) {
		b.toggleAttribute("context"),
		b.addEventListener("click", listener, true),
		b.addEventListener("mouseenter", mouseenter);
	}
	window.addEventListener("keydown", keydown_win);

	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		for(var b of btns) {
			b.removeEventListener("click", listener, true);
			b.removeEventListener("mouseenter", mouseenter);
			if (b.matches(":hover"))
				b.removeEventListener("mouseleave", mouseleave),
				b.parentNode.removeEventListener("mousedown", stop, true);
		}
		window.removeEventListener("keydown", keydown_win);
	}};

Dobrov пишет

В скрипт Quick Toggle

Уволь.

Dumby - спасибо, я немного переделал код и обновил профиль-сборку скриптов.

Dumby пишет

А ещё у меня там не работают альт-клики. Насколько я понял, mousedown занят системой

Не системой, а демоном горячих клавиш, которые можно изменить в Настройках из меню.
перетаскивание/Resize окна вместе с Alt - это стандарт, но мета-клавишу можно в настройках поменять на Ctrl или Alt или Win…

egorsemenov06 пишет

присоеденяюсь к просьбе

Какой просьбе? Нет там никакой просьбы.

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

Есть сетование на трудности рихтовки и констатация
отсутствия реакции индикатора на очистку памяти фишкой.


Но индикатор и не должен напрямую реагировать ни на какие фишки.
Пара десятков миллисекунд на получение данных о памяти,
затем delay (две секунды) пауза, и так по кругу, вот и вся деятельность.
Дополнительный пинок только при открытии нового окна браузера.


И код рабочий, так что, если фишка действительно чистит память,
то индикатор это покажет, не прям сразу, но не позднее delay.
И да, если вдруг надо поставить (для теста) второй экземпляр кода,
то следует изменить id (в конце, "ucf-mem-indicator"), а то будут накладки.


И ещё там есть вопрос, можно ли сделать подобный индикатор,
но на другом принципе — использовать nsIMemoryReporterManager.


Когда-то давно я уже отвечал на это, целый трактат написал.
Суть — можно, но тогда результат будет включть только память DOM-процессов,
то есть выпадут процессы gpu, socket, rdd, может ещё какие-то подобные.

Dumby - Помоги исправить код перехода в "Адаптивный дизайн".


код в сложной кнопке QuickToggle.js работает, но если несколько раз включить/выключить Адаптивный дизайн через кнопку, то это переключение перестаёт работать.
и не работает в простой кнопке, в которую ты мне клики добавил:

Выделить код

Код:

responsiveUI(trg) {
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_responsiveUI");
			(this.responsiveUI = target => obj.oncommand({target}))(trg);
		},

строка 155 - ucf_hookClicks.js

Выделить код

Код:

(async (id, func) => { // дополнительные клики на downloads-button, PanelUI-menu для custom_script_win.js
	await window.delayedStartupPromise;
	var btn = document.getElementById("downloads-button"), pui = document.getElementById("PanelUI-menu-button");
	if (!btn) return; btn.tooltipText = GetDynamicShortcutTooltipText(btn.id) +`

Двойной клик:  открыть [Загрузки]
…на картинке: ⧉ найти Похожие\n
Правый клик (Alt+S): Сохранить
   в единый html всё / выделенное
…дважды  Картинки вкл/выкл\n
Ролик:	 Сохранить как файл .txt
Колёсико на рисунке: ➜ Сохранить`,	PanelUI_help =

`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}
…+ дважды	⊠ закрыть браузер
…+ Alt		Обновить без кэша
…+ Shift		⚑ Краткая справка\n
Правый клик	⇲ Свернуть
…+ Alt		Персонализация
Колёсико:	Развернуть | окно
…+ Alt		Полный экран`;

	var addDestructor = nextDestructor => {
		var {destructor} = ucf[id];
		ucf[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	},
	showInStatusPanel = (info, time = 5000) => {
		var win = Services.wm.getMostRecentWindow("navigator:browser"); StatusPanel = win.StatusPanel;
		if (StatusPanel.update.tid)
			clearTimeout(StatusPanel.update.tid)
		else {
			var {update} = StatusPanel;
			StatusPanel.update = () => {};
			StatusPanel.update.ret = () => {
				StatusPanel.update = update;
				StatusPanel.update();
			}
		}
		StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, time);
		StatusPanel._label = info;
	},
	saveSelectionToTxt = async () => { // сохранить страницу или выделенный текст как файл .txt
		var splice = saveURL.length == 10;
		var msgName = id + ":Save:GetSelection";
		var receiver = msg => {
			var title = document.title || gBrowser.selectedTab.label;
			var args = [
				"data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + "\n\n" + msg.data),
				title.replace(/[:\\\/<>?*|"]+/g,'_').replace(/\s+/g,' ').slice(0, 100).trim() + '_' + new Date().toLocaleString('ru').replace(', ','-').replace(/:/g, '։') + '.txt',
				null, false, true, null, window.document
			];
			splice && args.splice(5, 0, null);
			saveURL(...args) && showInStatusPanel("√ текст сохранён: " + title.slice(0, 60));
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));
		var func = fm => {
			var res, fed, win = {}, fe = fm.getFocusedElementForWindow(content, true, win);
			var sel = (win = win.value).getSelection();
			if (sel.isCollapsed) {
				var ed = fe && fe.editor;
				if (ed && ed instanceof Ci.nsIEditor)
					sel = ed.selection, fed = fe;
			}
			if (sel.isCollapsed)
				fed && fed.blur(), docShell.doCommand("cmd_selectAll"),
				res = win.getSelection().toString(), docShell.doCommand("cmd_selectNone"),
				fed && fed.focus();
			res = res || sel.toString();
			/\S/.test(res) && sendAsyncMessage("saveSelectionToTxt", res);
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})`.replace("saveSelectionToTxt", msgName)) + '(Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager));';
		(saveSelectionToTxt = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	}, // end

	save = async () => { // автор: Лекс, правка: Dumby, Dobrov (пути сохранения HTML)
		var msgName = id + "ucfDwnldsBtnSaveSnapshotToHTML";
		if (typeof IOUtils != "object") { // Firefox 78 ESR
			var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
			var PathUtils = {join: (...args) => OS.Path.join(...args)};
			var IOUtils = {writeUTF8: (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt))};
		}
		var write = IOUtils.writeUTF8 ? "writeUTF8" : "writeAtomicUTF8";

		var Title = (type) => { // получить заголовок (без обрезки, если type не указан) или домен (type <0)
			var title = (document.title || gBrowser.selectedTab.label);
			if ( !type ) return title; // заголовок
			if ( type > 0 ) return title.slice(0, type).replace(/ \| Форум Mozilla Россия$| — Mozilla Firefox|[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim(); // ограничить длину имени
			var host = (/^file:\/\//.test(gURLBar.value)) ? '' : gURLBar.value.replace(/^.*url=|https?:\/\/|www\.|\/.*/g,'');
			return host.replace(/^ru\.|^m\.|forum\./,'').replace(/^club\.dns/,'dns');
		}
		var msgListener = async msg => {
			var [fileContent, fileName] = msg.data, dir;
			try {dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}
			var arr = prefs.getStringPref("ucf_save.dirs", "_Web||_Images|0").split('|').slice(0, 2); // Dir/subdir: пусто|0 title|1 домен
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // для ucf_save.dirs = "_Web||_Pics|1" HTML сохранится в папку [Загрузки]/_Web/имя вкладки
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
			var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
			file.initWithPath(dir.path);
			dir.append(fileName);
			await IOUtils[write](dir.path, fileContent) && showInStatusPanel("√ страница записана: " + fileName.slice(0, 60));
			var d = await Downloads.createDownload({ source: "about:blank", target: FileUtils.File(dir.path)}); // Fake download
			(await Downloads.getList(Downloads.ALL)).add(d);
			d.refresh(d.succeeded = true); // кнопка Загрузки мигает
		}
		messageManager.addMessageListener(msgName, msgListener);
		addDestructor(() => messageManager.removeMessageListener(msgName, msgListener));

		var svc = 'globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services';
		var url = "data:;charset=utf8," + encodeURIComponent(`(${func})(${svc});`.replace("%MSG_NAME%", msgName));
		(save = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	}, // end save

	tid, allowMousedown, listener = {
		handleEvent(e) {
			if (e.detail > 2) return;
			var btn = e.target;
			var dbl = e.detail == 2;
			var num = (btn == pui && 100) + 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;

			if (!this[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "mousedown";
			}
			if (dbl) tid &&= clearTimeout(tid), this[num](btn);
			else tid = setTimeout(this.exec, 300, num, btn, this);
		},
		exec(num, btn, self) {
			tid = null;
			self[num](btn);
		},
		mousedown(btn) {
			allowMousedown = true;
			btn.dispatchEvent(new MouseEvent("mousedown", {}));
			allowMousedown = false;
		},
		context(btn) {
			btn.removeAttribute("context");
			btn.dispatchEvent(new MouseEvent("contextmenu", this.a));
			btn.toggleAttribute("context");
		},
		switchToTab(but, url) { // открыть вкладку | закрыть, если открыта
			for(var tab of but.ownerGlobal.gBrowser.tabs)
				if ( tab.linkedBrowser.currentURI.spec == url ) {but.ownerGlobal.gBrowser.removeTab(tab); return;}; // вкладка найдена, закрыть
			but.ownerGlobal.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
		},
		responsiveUI(trg) {
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_responsiveUI");
			(this.responsiveUI = target => obj.oncommand({target}))(trg);
		},

		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

/*** ======= Downloads Clicks ======= ***/

		16: saveSelectionToTxt, // СКМ Click (сохранить .txt)
		1() { // Double Left Click - Обзор папки «Загрузки»
			Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
		},
		32: save, // ПКМ Click (Single HTML)
		33(btn) { // Double Right Click
			var pref = "permissions.default.image";
			var one = prefs.getIntPref(pref) == 1;
			prefs.setIntPref(pref, one ? 2 : 1);
			btn.style.filter = one ? "hue-rotate(180deg) brightness(95%)" : "";
			BrowserReload();
		},
		2() { // Alt + ЛКМ
		},

/*** ======= PanelUI-menu Clicks ======= ***/

		102: BrowserReloadSkipCache, // Alt + Click
		104() { // Shift + ЛКМ
			var help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'];
			var newURI = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).convertChromeURL(Services.io.newURI(help_ucf[0])); // .spec = file:///
			(newURI.QueryInterface(Ci.nsIFileURL).file.exists()) ? this.switchToTab(pui, help_ucf[0]) : this.switchToTab(pui, help_ucf[1]);
		},
		118: BrowserFullScreen, // Alt + СКМ
		116() { // СКМ
			windowState != STATE_MAXIMIZED ? maximize() : restore();
		},
		132() { // ПКМ
			minimize();
		},
		134() { // Alt + ПКМ
			gCustomizeMode.enter();
		},
		101: BrowserTryToCloseWindow, // Double Left Click
		133(pui) { // Double Right Click
			responsiveUI(pui);
		},

	}, // end Clicks

	keydown_win = e => { // нажатие клавиш
		if (e.keyCode == 83 && e.altKey) e.shiftKey // Alt+S [+Shift]
		? singlesave ? singlesave.click() : save() : save(); // имитировать клик по кнопке, используя её ID
	},
	{prefs, dirsvc} = Services, linux = /macos|linux/.test(AppConstants.platform),
	save_ex = "_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action", singlesave;
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	(async () => { setTimeout(()=> { // дополнить подсказки
			singlesave = document.getElementById(save_ex);
			singlesave ? btn.tooltipText = btn.tooltipText + '\n\nAlt⇧S	 ⌨ нажатие SingleSave' : 0;
			if (!/Закрыть/.test(pui.tooltipText)) pui.tooltipText = PanelUI_help;
		}, linux ? 3000 : 9000); // ожидание для Windows больше
	})();
	var mouseenter = function(e) {
		this.parentNode.addEventListener("mousedown", stop, true);
		this.addEventListener("mouseleave", mouseleave, {once: true});
	}
	var mouseleave = function(e) {
		this.parentNode.removeEventListener("mousedown", stop, true);
	}
	var stop = e => {
		e.button || allowMousedown || e.stopImmediatePropagation();
	}
	var btns = [btn, pui];
	for(var b of btns) {
		b.toggleAttribute("context"),
		b.addEventListener("click", listener, true),
		b.addEventListener("mouseenter", mouseenter);
	}
	window.addEventListener("keydown", keydown_win);

	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		for(var b of btns) {
			b.removeEventListener("click", listener, true);
			b.removeEventListener("mouseenter", mouseenter);
			if (b.matches(":hover"))
				b.removeEventListener("mouseleave", mouseleave),
				b.parentNode.removeEventListener("mousedown", stop, true);
		}
		window.removeEventListener("keydown", keydown_win);
	}};

	ucf.unloadlisteners.push(id);

})("downloads-button-click-listener", ({io, focus}) => { // SingleHTML не сохраняет svg графику

	var resolveURL = function (url, base) {
		try { return io.newURI(url, null, io.newURI(base)).spec;
		} catch {}
	},
	getSelWin = function (w) {
		if (w.getSelection().toString()) return w;
		for (var i = 0, f, r; f = w.frames[i]; i++) {
			try { if (r = getSelWin(f)) return r;
			} catch(e) {}
		}
	},
	encodeImg = function (src, obj) {
		var canvas, img, ret = src;
		if (/^https?:\/\//.test(src)) {
			canvas = doc.createElement('canvas');
			if (!obj || obj.nodeName.toLowerCase() != 'img') {
				img = doc.createElement('img');
				img.src = src;
			} else
				img = obj;
			if (img.complete) try{
				canvas.width = img.width;
				canvas.height = img.height;
				canvas.getContext('2d').drawImage(img, 0, 0);
				ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png'));
			} catch (e) {};
			if (img != obj) img.src = 'about:blank';
		};
		return ret;
	},
	toSrc = function (obj) {
		var strToSrc = function (str) {
			var chr, ret = '', i = 0, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\x22' : '\\\x22', '\\': '\\\\'};
			while (chr = str.charAt(i++)) {
				ret += meta[chr] || chr;
			};
			return '\x22' + ret + '\x22';
		},
		arrToSrc = function (arr) {
			var ret = [];
			for (var i = 0; i < arr.length; i++) {
				ret[i] = toSrc(arr[i]) || 'null';
			};
			return '[' + ret.join(',') + ']';
		},
		objToSrc = function (obj) {
			var val, ret = [];
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val);
			};
			return '{' + ret.join(',') + '}';
		};
		switch (Object.prototype.toString.call(obj).slice(8, -1)) {
			case 'Array': return arrToSrc(obj);
			case 'Boolean':
			case 'Function':
			case 'RegExp': return obj.toString();
			case 'Date': return 'new Date(' + obj.getTime() + ')';
			case 'Math': return 'Math';
			case 'Number': return isFinite(obj) ? String(obj) : 'null';
			case 'Object': return objToSrc(obj);
			case 'String': return strToSrc(obj);
			default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null';
		}
	},
	mainWin = {};
	focus.getFocusedElementForWindow(content, true, mainWin);
	mainWin = mainWin.value;

	var selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location;
	var ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g;

	if (selWin) {
		var rng = win.getSelection().getRangeAt(0);
		pEle = rng.commonAncestorContainer;
		ele = rng.cloneContents();
	} else {
		pEle = doc.documentElement;
		ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true);
	};
	while (pEle) {
		if (pEle.nodeType == 1) {
			clone = pEle.cloneNode(false);
			clone.appendChild(ele);
			ele = clone;
		};
		pEle = pEle.parentNode
	};
	var sel = doc.createElement('div');
	sel.appendChild(ele);

	for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) {
		el = all[i];
		if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) {
			if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href);
			return prev + encodeImg(url) + next;
		});
		switch (el.nodeName.toLowerCase()) {
			case 'link':
			case 'style':
			case 'script': el.parentNode.removeChild(el); break;
			case 'a':
			case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break;
			case 'img':
			case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break;
			case 'audio':
			case 'video':
			case 'embed':
			case 'frame':
			case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break;
			case 'object': if (el.hasAttribute('data')) el.data = el.data; break;
			case 'form': if (el.hasAttribute('action')) el.action = el.action; break;
		}
	};
	var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets, title = doc.getElementsByTagName('title')[0];
	meta.httpEquiv = 'content-type';
	meta.content = 'text/html; charset=utf-8';
	head.appendChild(meta);
	if (title) head.appendChild(title.cloneNode(true));

	head.copyScript = function (unsafeWin) {
		if ('$' in unsafeWin) return;
		var f = doc.createElement('iframe');
		f.src = 'about:blank';
		f.setAttribute('style', 'position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');
		doc.documentElement.appendChild(f);
		var str, script = doc.createElement('script');
		script.type = 'text/javascript';
		for (var name in unsafeWin) {
			if (name in f.contentWindow || !/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) continue;
			try {
				str = toSrc(unsafeWin[name]);
				if (!/\{\s*\[native code\]\s*\}/.test(str)) {
					script.appendChild(doc.createTextNode('var ' + name + ' = ' + str.replace(/<\/(script>)/ig, '<\\/$1') + ';\n'));
				}
			} catch (e) {};
		};
		f.parentNode.removeChild(f);
		if (script.childNodes.length) this.nextSibling.appendChild(script);
	};
	head.copyScript(win.wrappedJSObject || win);

	head.copyStyle = function (s) {
		if (!s) return;
		var style = doc.createElement('style');
		style.type = 'text/css';
		if (s.media && s.media.mediaText) style.media = s.media.mediaText;
		try {
			for (var i = 0, rule; rule = s.cssRules[i]; i++) {
				if (rule.type != 3) {
					if((!rule.selectorText || rule.selectorText.indexOf(':') != -1) || (!sel.querySelector || sel.querySelector(rule.selectorText))) {
						var css = !rule.cssText ? '' : rule.cssText.replace(reUrl, function (a, prev, url, next) {
							if (!/^[a-z]+:/.test(url)) url = resolveURL(url, s.href || loc.href);
							if(rule.type == 1 && rule.style && rule.style.backgroundImage) url = encodeImg(url);
							return prev + url + next;
						});
						style.appendChild(doc.createTextNode(css + '\n'));
					}
				} else {
					this.copyStyle(rule.styleSheet);
				}
			}
		} catch(e) {
			if (s.ownerNode) style = s.ownerNode.cloneNode(false);
		};
		this.appendChild(style);
	};
	for (var j = 0; j < sheets.length; j++) head.copyStyle(sheets[j]);
	head.appendChild(doc.createTextNode('\n'));

	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};

	var fileName = selWin ? win.getSelection().toString() : (title && title.text ? title.text : loc.pathname.split('/').pop());
	fileName = fileName.replace(/[:\\\/<>?*|"]+/g, '_').replace(/\s+/g, ' ').slice(0, 100).trim();
	fileName += "_" + new Date().toLocaleDateString('ru', {day: 'numeric', month: 'numeric', year: '2-digit'}) +'-'+ new Date().toLocaleTimeString().replace(/:/g, "։");
	if (!/\.html?$/.test(fileName)) fileName += '.html';

	sendAsyncMessage("%MSG_NAME%", [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', fileName]);

}); // END hookClicks

Ещё вопрос - как имитировать нажатие клавиш, например "послать" переключения в режим "Адаптивный дизайн" - у меня это Option+Command+M (Win+Alt+M)
пробовал, но не пашет:  window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).sendKeyEvent("keypress", 0, "j".charCodeAt(0), 0x08)

Dobrov пишет

не работает
responsiveUI(pui);

this.responsiveUI(pui);


Или (для понимания) без промежуточности

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

Выделить код

Код:

.....
		133(pui) {
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_responsiveUI");
			(this[133] = target => obj.oncommand({target}))(pui);
		},

не пашет
window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).sendKeyEvent

На FF87 у window нет метода QueryInterface(),
и у nsIDOMWindowUtils нет метода sendKeyEvent().

Dumby - спасибо, исправил и доработал код.


Вопрос - как сделать перехват кликов на кнопках расширений, которые динамически появляются?
То есть, при старте браузера кнопки Reader View в строке адреса нет, она появляется, когда открывается сайт.
Будет удобнее, если у кнопки режима чтения появится ещё действие - например альтернативный режим просмотра.
То есть, нужно на кнопке Reader View по клику колёсиком включать/выключать режим "Адаптивный дизайн".

Выделить код

Код:

win.document.getElementById("pageAction-panel-_2495d258-41e7-4cd5-bc7d-ac15981f064e_").click()
trg.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // Адаптивный дизайн
id="pageAction-panel-_2495d258-41e7-4cd5-bc7d-ac15981f064e_" Reader View в меню Действия на странице
id="pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_" в строке адреса
actionid="_2495d258-41e7-4cd5-bc7d-ac15981f064e_"

Желательно добавить перехват кликов в тот же UCF-скрипт, который расширяет возможности кнопок Меню и Загрузки:

ucf_hookClicks.js

Выделить код

Код:

(async (id, func) => { // дополнительные клики на downloads-button, PanelUI-menu для custom_script_win.js
	await window.delayedStartupPromise;
	var btn = document.getElementById("downloads-button"), pui = document.getElementById("PanelUI-menu-button");
	if (!btn) return; btn_help =`

Двойной клик:  открыть [Загрузки]
…на картинке: ⧉ найти Похожие\n
Правый клик (Alt+S):  Сохранить
   в единый html всё / выделенное
…дважды  Картинки вкл/выкл\n
Ролик:	 Сохранить как файл .txt
Колёсико на рисунке: ➜ Сохранить`,	PanelUI_help =

`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}
…+ Shift		⚑ Краткая справка
…+ Alt		Персонализация
Клик дважды	⊠ закрыть браузер \n
Правый клик	⇲ Свернуть
…+ дважды	⤾ Вернуть вкладку
…+ Alt		Диспетчер задач
…+ Shift		Адаптивный дизайн\n
Колёсико:	Развернуть | окно
…+ Alt		Полный экран
…+ дважды	Обновить без кэша`, singlesave_help = `\nAlt⇧S	 ⌨ нажатие SingleSave`;

	var addDestructor = nextDestructor => {
		var {destructor} = ucf[id];
		ucf[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	},
	showInStatusPanel = (info, time = 5000) => {
		var win = Services.wm.getMostRecentWindow("navigator:browser"); StatusPanel = win.StatusPanel;
		if (StatusPanel.update.tid)
			clearTimeout(StatusPanel.update.tid)
		else {
			var {update} = StatusPanel;
			StatusPanel.update = () => {};
			StatusPanel.update.ret = () => {
				StatusPanel.update = update;
				StatusPanel.update();
			}
		}
		StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, time);
		StatusPanel._label = info;
	},
	saveSelectionToTxt = async () => { // сохранить страницу или выделенный текст как файл .txt
		var splice = saveURL.length == 10;
		var msgName = id + ":Save:GetSelection";
		var receiver = msg => {
			var title = document.title || gBrowser.selectedTab.label;
			var args = [
				"data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + "\n\n" + msg.data),
				title.replace(/[:\\\/<>?*|"]+/g,'_').replace(/\s+/g,' ').slice(0, 100).trim() + '_' + new Date().toLocaleString('ru').replace(', ','-').replace(/:/g, '։') + '.txt',
				null, false, true, null, window.document
			];
			splice && args.splice(5, 0, null);
			saveURL(...args) && showInStatusPanel("√ текст сохранён: " + title.slice(0, 60));
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));
		var func = fm => {
			var res, fed, win = {}, fe = fm.getFocusedElementForWindow(content, true, win);
			var sel = (win = win.value).getSelection();
			if (sel.isCollapsed) {
				var ed = fe && fe.editor;
				if (ed && ed instanceof Ci.nsIEditor)
					sel = ed.selection, fed = fe;
			}
			if (sel.isCollapsed)
				fed && fed.blur(), docShell.doCommand("cmd_selectAll"),
				res = win.getSelection().toString(), docShell.doCommand("cmd_selectNone"),
				fed && fed.focus();
			res = res || sel.toString();
			/\S/.test(res) && sendAsyncMessage("saveSelectionToTxt", res);
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})`.replace("saveSelectionToTxt", msgName)) + '(Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager));';
		(saveSelectionToTxt = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	}, // end

	save = async () => { // автор: Лекс, правка: Dumby, Dobrov (пути сохранения HTML)
		var msgName = id + "ucfDwnldsBtnSaveSnapshotToHTML";
		if (typeof IOUtils != "object") { // Firefox 78 ESR
			var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
			var PathUtils = {join: (...args) => OS.Path.join(...args)};
			var IOUtils = {writeUTF8: (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt))};
		}
		var write = IOUtils.writeUTF8 ? "writeUTF8" : "writeAtomicUTF8";

		var Title = (type) => { // получить заголовок (без обрезки, если type не указан) или домен (type <0)
			var title = (document.title || gBrowser.selectedTab.label);
			if ( !type ) return title; // заголовок
			if ( type > 0 ) return title.slice(0, type).replace(/ \| Форум Mozilla Россия$| — Mozilla Firefox|[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim(); // ограничить длину имени
			var host = (/^file:\/\//.test(gURLBar.value)) ? '' : gURLBar.value.replace(/^.*url=|https?:\/\/|www\.|\/.*/g,'');
			return host.replace(/^ru\.|^m\.|forum\./,'').replace(/^club\.dns/,'dns');
		}
		var msgListener = async msg => {
			var [fileContent, fileName] = msg.data, dir;
			try {dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}
			var arr = prefs.getStringPref("ucf_save.dirs", "_Web||_Images|0").split('|').slice(0, 2); // Dir/subdir: пусто|0 title|1 домен
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // для ucf_save.dirs = "_Web||_Pics|1" HTML сохранится в папку [Загрузки]/_Web/имя вкладки
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
			var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
			file.initWithPath(dir.path);
			dir.append(fileName);
			await IOUtils[write](dir.path, fileContent) && showInStatusPanel("√ страница записана: " + fileName.slice(0, 60));
			var d = await Downloads.createDownload({ source: "about:blank", target: FileUtils.File(dir.path)}); // Fake download
			(await Downloads.getList(Downloads.ALL)).add(d);
			d.refresh(d.succeeded = true); // кнопка Загрузки мигает
		}
		messageManager.addMessageListener(msgName, msgListener);
		addDestructor(() => messageManager.removeMessageListener(msgName, msgListener));

		var svc = 'globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services';
		var url = "data:;charset=utf8," + encodeURIComponent(`(${func})(${svc});`.replace("%MSG_NAME%", msgName));
		(save = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	}, // end save

	tid, allowMousedown, listener = {
		handleEvent(e) {
			if (e.detail > 2) return;
			var btn = e.target;
			var dbl = e.detail == 2;
			var num = (btn == pui && 100) + 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;

			if (!this[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "mousedown";
			}
			if (dbl) tid &&= clearTimeout(tid), this[num](btn);
			else tid = setTimeout(this.exec, 300, num, btn, this);
		},
		exec(num, btn, self) {
			tid = null;
			self[num](btn);
		},
		mousedown(btn) {
			allowMousedown = true;
			btn.dispatchEvent(new MouseEvent("mousedown", {}));
			allowMousedown = false;
		},
		context(btn) {
			btn.removeAttribute("context");
			btn.dispatchEvent(new MouseEvent("contextmenu", this.a));
			btn.toggleAttribute("context");
		},
		switchToTab(but, url) { // открыть вкладку | закрыть, если открыта
			for(var tab of but.ownerGlobal.gBrowser.tabs)
				if ( tab.linkedBrowser.currentURI.spec == url ) {but.ownerGlobal.gBrowser.removeTab(tab); return;}; // вкладка найдена, закрыть
			but.ownerGlobal.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
		},

		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

/*** ======= Downloads Clicks ======= ***/

		16: saveSelectionToTxt, // СКМ Click (сохранить .txt)
		1() { // Double Left Click - Обзор папки «Загрузки»
			Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
		},
		32: save, // ПКМ Click (Single HTML)
		33(btn) { // Double Right Click
			var pref = "permissions.default.image";
			var one = prefs.getIntPref(pref) == 1;
			prefs.setIntPref(pref, one ? 2 : 1);
			btn.style.filter = one ? "hue-rotate(180deg) brightness(95%)" : "";
			BrowserReload();
		},
		2() { // Alt + ЛКМ
		},

/*** ======= PanelUI-menu Clicks ======= ***/

		102() { gCustomizeMode.enter(); // ЛКМ + Alt Персонализация
		},
		104() { // Shift + ЛКМ
			var help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'];
			var newURI = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).convertChromeURL(Services.io.newURI(help_ucf[0])); // .spec = file:///
			(newURI.QueryInterface(Ci.nsIFileURL).file.exists()) ? this.switchToTab(pui, help_ucf[0]) : this.switchToTab(pui, help_ucf[1]);
		},
		118: BrowserFullScreen, // Alt + СКМ
		116() { // СКМ
			windowState != STATE_MAXIMIZED ? maximize() : restore();
		},
		117: BrowserReloadSkipCache,  // СКМ Double
		132() { // ПКМ
			minimize();
		},
		134(pui) { // Alt + ПКМ
			this.switchToTab(pui, 'about:performance');
		},
		136(pui) { // Shift + ПКМ
			pui.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // запуск пункта меню с HotKey
		},
		138(pui) { // Shift + Alt + ПКМ
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_devtools_remotedebugging");
			(this[136] = target => obj.oncommand({target}))(pui); // запуск пункта меню, у которого нет HotKey
		},
		101: BrowserTryToCloseWindow, // Double Left Click
		133(pui) { // ПКМ Double Right Click
			pui.ownerGlobal.undoCloseTab();
		},
	}, // end Clicks

	keydown_win = e => { // нажатие клавиш
		if (e.keyCode == 83 && e.altKey) e.shiftKey // Alt+S [+Shift]
		? singlesave ? singlesave.click() : save() : save(); // имитировать клик по кнопке, используя её ID
	},
	{prefs, dirsvc} = Services, linux = /macos|linux/.test(AppConstants.platform), singlesave;
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	var mouseenter = function(e) {
		this.parentNode.addEventListener("mousedown", stop, true);
		this.addEventListener("mouseleave", mouseleave, {once: true});

		if ((e.target == pui) && (!/справка/.test(pui.tooltipText)))
			pui.tooltipText = PanelUI_help; // обновить подсказки кнопок
		if (e.target == btn) {
			var singlesave = document.getElementById("_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action"), dw;
			try {dw = prefs.getComplexValue("browser.download.dir",Ci.nsIFile)} catch {dw = dirsvc.get("DfltDwnld", Ci.nsIFile)};
		// if (!/Двойной/.test(btn.tooltipText))
			btn.tooltipText = GetDynamicShortcutTooltipText(btn.id) + btn_help; // обновлять подсказку при наведении мыши
		if (singlesave){
			if (!/SingleSave/.test(btn.tooltipText)) btn.tooltipText = btn.tooltipText + singlesave_help;
		} else
			btn.tooltipText = btn.tooltipText.replace(singlesave_help,'');
		if (!/выбранная/.test(btn.tooltipText))
			btn.tooltipText = btn.tooltipText + `${dw ? "\n\n[Загрузки] — выбранная папка:\n"+ dw.path.substring(0,33) + `${dw.path.length > 32 ? `…\n…${dw.path.substring(dw.path.length -31, dw.path.length)}`: ""}` : ""}`; // сократить/разбить длинную строку
		}
	}
	var mouseleave = function(e) {
		this.parentNode.removeEventListener("mousedown", stop, true);
	}
	var stop = e => {
		e.button || allowMousedown || e.stopImmediatePropagation();
	}
	var btns = [btn, pui];
	for(var b of btns) {
		b.toggleAttribute("context"),
		b.addEventListener("click", listener, true),
		b.addEventListener("mouseenter", mouseenter);
	}
	window.addEventListener("keydown", keydown_win);

	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		for(var b of btns) {
			b.removeEventListener("click", listener, true);
			b.removeEventListener("mouseenter", mouseenter);
			if (b.matches(":hover"))
				b.removeEventListener("mouseleave", mouseleave),
				b.parentNode.removeEventListener("mousedown", stop, true);
		}
		window.removeEventListener("keydown", keydown_win);
	}};

	ucf.unloadlisteners.push(id);

})("downloads-button-click-listener", ({io, focus}) => { // SingleHTML не сохраняет svg графику

	var resolveURL = function (url, base) {
		try { return io.newURI(url, null, io.newURI(base)).spec;
		} catch {}
	},
	getSelWin = function (w) {
		if (w.getSelection().toString()) return w;
		for (var i = 0, f, r; f = w.frames[i]; i++) {
			try { if (r = getSelWin(f)) return r;
			} catch(e) {}
		}
	},
	encodeImg = function (src, obj) {
		var canvas, img, ret = src;
		if (/^https?:\/\//.test(src)) {
			canvas = doc.createElement('canvas');
			if (!obj || obj.nodeName.toLowerCase() != 'img') {
				img = doc.createElement('img');
				img.src = src;
			} else
				img = obj;
			if (img.complete) try{
				canvas.width = img.width;
				canvas.height = img.height;
				canvas.getContext('2d').drawImage(img, 0, 0);
				ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png'));
			} catch (e) {};
			if (img != obj) img.src = 'about:blank';
		};
		return ret;
	},
	toSrc = function (obj) {
		var strToSrc = function (str) {
			var chr, ret = '', i = 0, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\x22' : '\\\x22', '\\': '\\\\'};
			while (chr = str.charAt(i++)) {
				ret += meta[chr] || chr;
			};
			return '\x22' + ret + '\x22';
		},
		arrToSrc = function (arr) {
			var ret = [];
			for (var i = 0; i < arr.length; i++) {
				ret[i] = toSrc(arr[i]) || 'null';
			};
			return '[' + ret.join(',') + ']';
		},
		objToSrc = function (obj) {
			var val, ret = [];
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val);
			};
			return '{' + ret.join(',') + '}';
		};
		switch (Object.prototype.toString.call(obj).slice(8, -1)) {
			case 'Array': return arrToSrc(obj);
			case 'Boolean':
			case 'Function':
			case 'RegExp': return obj.toString();
			case 'Date': return 'new Date(' + obj.getTime() + ')';
			case 'Math': return 'Math';
			case 'Number': return isFinite(obj) ? String(obj) : 'null';
			case 'Object': return objToSrc(obj);
			case 'String': return strToSrc(obj);
			default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null';
		}
	},
	mainWin = {};
	focus.getFocusedElementForWindow(content, true, mainWin);
	mainWin = mainWin.value;

	var selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location;
	var ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g;

	if (selWin) {
		var rng = win.getSelection().getRangeAt(0);
		pEle = rng.commonAncestorContainer;
		ele = rng.cloneContents();
	} else {
		pEle = doc.documentElement;
		ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true);
	};
	while (pEle) {
		if (pEle.nodeType == 1) {
			clone = pEle.cloneNode(false);
			clone.appendChild(ele);
			ele = clone;
		};
		pEle = pEle.parentNode
	};
	var sel = doc.createElement('div');
	sel.appendChild(ele);

	for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) {
		el = all[i];
		if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) {
			if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href);
			return prev + encodeImg(url) + next;
		});
		switch (el.nodeName.toLowerCase()) {
			case 'link':
			case 'style':
			case 'script': el.parentNode.removeChild(el); break;
			case 'a':
			case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break;
			case 'img':
			case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break;
			case 'audio':
			case 'video':
			case 'embed':
			case 'frame':
			case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break;
			case 'object': if (el.hasAttribute('data')) el.data = el.data; break;
			case 'form': if (el.hasAttribute('action')) el.action = el.action; break;
		}
	};
	var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets, title = doc.getElementsByTagName('title')[0];
	meta.httpEquiv = 'content-type';
	meta.content = 'text/html; charset=utf-8';
	head.appendChild(meta);
	if (title) head.appendChild(title.cloneNode(true));

	head.copyScript = function (unsafeWin) {
		if ('$' in unsafeWin) return;
		var f = doc.createElement('iframe');
		f.src = 'about:blank';
		f.setAttribute('style', 'position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');
		doc.documentElement.appendChild(f);
		var str, script = doc.createElement('script');
		script.type = 'text/javascript';
		for (var name in unsafeWin) {
			if (name in f.contentWindow || !/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) continue;
			try {
				str = toSrc(unsafeWin[name]);
				if (!/\{\s*\[native code\]\s*\}/.test(str)) {
					script.appendChild(doc.createTextNode('var ' + name + ' = ' + str.replace(/<\/(script>)/ig, '<\\/$1') + ';\n'));
				}
			} catch (e) {};
		};
		f.parentNode.removeChild(f);
		if (script.childNodes.length) this.nextSibling.appendChild(script);
	};
	head.copyScript(win.wrappedJSObject || win);

	head.copyStyle = function (s) {
		if (!s) return;
		var style = doc.createElement('style');
		style.type = 'text/css';
		if (s.media && s.media.mediaText) style.media = s.media.mediaText;
		try {
			for (var i = 0, rule; rule = s.cssRules[i]; i++) {
				if (rule.type != 3) {
					if((!rule.selectorText || rule.selectorText.indexOf(':') != -1) || (!sel.querySelector || sel.querySelector(rule.selectorText))) {
						var css = !rule.cssText ? '' : rule.cssText.replace(reUrl, function (a, prev, url, next) {
							if (!/^[a-z]+:/.test(url)) url = resolveURL(url, s.href || loc.href);
							if(rule.type == 1 && rule.style && rule.style.backgroundImage) url = encodeImg(url);
							return prev + url + next;
						});
						style.appendChild(doc.createTextNode(css + '\n'));
					}
				} else {
					this.copyStyle(rule.styleSheet);
				}
			}
		} catch(e) {
			if (s.ownerNode) style = s.ownerNode.cloneNode(false);
		};
		this.appendChild(style);
	};
	for (var j = 0; j < sheets.length; j++) head.copyStyle(sheets[j]);
	head.appendChild(doc.createTextNode('\n'));

	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};

	var fileName = selWin ? win.getSelection().toString() : (title && title.text ? title.text : loc.pathname.split('/').pop());
	fileName = fileName.replace(/[:\\\/<>?*|"]+/g, '_').replace(/\s+/g, ' ').slice(0, 100).trim();
	fileName += "_" + new Date().toLocaleDateString('ru', {day: 'numeric', month: 'numeric', year: '2-digit'}) +'-'+ new Date().toLocaleTimeString().replace(/:/g, "։");
	if (!/\.html?$/.test(fileName)) fileName += '.html';

	sendAsyncMessage("%MSG_NAME%", [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', fileName]);

}); // END hookClicks

Dumby
Возможно ли сделать отдельную кнопочку для Вкл/Выкл "Адаптивного дизайна"?

unter_officer пишет

Возможно ли сделать отдельную кнопочку для Вкл/Выкл "Адаптивного дизайна"?

пример уже есть в этом топике

Выделить код

Код:

(async (bar, exp, tid, self) => CustomizableUI.createWidget(self = { id: "test-button", localized: false, label: 
"Test Button", tooltiptext:
`ЛКМ:	Design View
CKM: folder UCF`,

	onCreated(btn) {
		btn.onclick = this.click;
		btn.toggleAttribute("context");
		btn.setAttribute("image", "chrome://browser/skin/preferences/application.png");
	},
	exec(num, win) {
		tid = null;
		self[num](win);
	},
	context(win) {
		var btn = win.document.getElementById(this.id);
		btn.removeAttribute("context");
		btn.dispatchEvent(new win.MouseEvent("contextmenu", this.a));
		btn.toggleAttribute("context");
	},
	a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

	click(e) {
		if (e.detail > 2) return;
		var n2 = e.button != 2;
		var dbl = e.detail == 2;
		var num = 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;
		if (!self[num]) {
			if (n2) return;
			num = "context";
			for(var p in self.a) self.a[p] = e[p];
		}
		var win = e.view;
		if (dbl) tid &&= win.clearTimeout(tid), self[num](win);
		else tid = win.setTimeout(self.exec, 300, num, win);
	},
	0(win) { // ЛКМ
		var btn = win.document.getElementById(this.id);
		btn.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // запуск пункта меню с HotKey
	},
	1(win) { // Double Left Click
		win.alert("DBL Click");
	},
	16: () => exp("UChrm", "user_chrome_files"), // СКМ
}))(
	(win, bar) => win.SidebarUI.toggle(bar),
	(dir, sub) => {
		dir = Services.dirsvc.get(dir, Ci.nsIFile);
		sub && dir.append(sub); dir.exists() && dir.launch();
	}
);

Dumby - ещё просьба вдобавок к первой:
подскажи код, чтобы выполнять скрипт из файла. То есть, кликом на кнопке выполняем внешний …/user_chrome_files/custom_scripts/user.js, в который должны быть переданы все текущие переменные, функции и т.п. той кнопки, из которой вызван внешний JS.


Может, это пригодиться для отладки скриптов без перезапуска браузера. А может есть более простой способ отладки части кода кнопки?
Наверное, код внешнего JS не должен попадать в startupCache, это верно?

Dobrov
Как пример вызова функции из другого скрипта можно посмотреть здесь, выше и ниже. Скрипт с функцией тоже должен быть загружен. Не знаю, это ли было нужно.
https://forum.mozilla-russia.org/viewtopic.php?pid=796057#p796057

xrun1 - не то. Цитата - Выполняем внешний скрипт. И никаких подгружаемых функций.

Dobrov пишет

при старте браузера кнопки Reader View в строке адреса нет, она появляется, когда открывается сайт

Значит слушать клики на родительском контейнере и проверять id кликнутого.

кликом на кнопке выполняем внешний …/user_chrome_files/custom_scripts/user.js, в который должны быть переданы все текущие переменные, функции и т.п. той кнопки, из которой вызван внешний JS
Наверное, код внешнего JS не должен попадать в startupCache, это верно?

Если scriptloader'ом по протоколу chrome:, то будет кэшироваться.
Можно использовать loadSubScriptWithOptions(),
но «все текущие переменные, функции» идут лесом,
разве что только в объект пробросить.

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

Выделить код

Код:

Services.scriptloader.loadSubScriptWithOptions(
	"chrome://user_chrome_files/content/custom_scripts/user.js",
	{ignoreCache: true, target: {myVariable1, myVariable2, myFunction}}
);


(new Function())(); тоже не подходит:
«Functions created with the Function constructor do not create closures to their creation contexts; they always are created in the global scope.»
Остаётся только direct eval().

То есть, нужно на кнопке Reader View по клику колёсиком включать/выключать режим "Адаптивный дизайн".
Желательно добавить перехват кликов в тот же UCF-скрипт, который расширяет возможности кнопок Меню и Загрузки

Вот, сводный пример. Добавляем после ucf.unloadlisteners.push(id);

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

Выделить код

Код:

.....
	var box = document.getElementById("page-action-buttons");
	var key = document.getElementById("key_responsiveDesignMode");
	var uri = Services.io.newURI("chrome://user_chrome_files/content/custom_scripts/user.js");
	var boxLst = e => eval(Cu.readUTF8URI(uri));
	box.addEventListener("auxclick", boxLst, true);
	addDestructor(() => box.removeEventListener("auxclick", boxLst, true));


user.js

Выделить код

Код:

if (
	e.button == 1 &&
	e.target.id == "pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_"
)
	e.stopImmediatePropagation(),
	key.doCommand();

this[136]

this[138], наверно.


unter_officer пишет

Возможно ли сделать отдельную кнопочку для Вкл/Выкл "Адаптивного дизайна"?

А в чём конкретно затруднение?
Там же просто получаем <key> и вызываем doCommand();

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

Выделить код

Код:

(async id => CustomizableUI.createWidget({
	get label() {
		var l10n = new (ChromeUtils.import("resource://devtools/shared/Loader.jsm")
			.require("devtools/shared/l10n")).MultiLocalizationHelper(
				"devtools/client/locales/startup.properties",
				"devtools/client/locales/menus.properties"
			);
		this.tooltiptext = l10n.getFormatStr(
			"toolbox.buttons.responsive",
			Services.appinfo.OS == "Darwin" ? "Cmd+Opt+M" : "Ctrl+Shift+M"
		);
		delete this.label;
		return this.label = l10n.getStr("responsiveDesignMode.label");
	},
	id: "ucf-responsiveDesignMode-btn",
	localized: false,
	onCreated(btn) {
		btn._handleClick = this.click;
		btn.style.cssText = `
			fill-opacity: 0 !important;
			-moz-context-properties: fill, fill-opacity !important;
			list-style-image: url(chrome://devtools/skin/images/command-responsivemode.svg) !important;
		`;
	},
	click() {
		this.ownerDocument.getElementById(id).doCommand();
	}
}))("key_responsiveDesignMode");

Dumby пишет

А в чём конкретно затруднение?
Там же просто получаем <key> и вызываем doCommand();

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

Выделить код

Код:

(async id => CustomizableUI.createWidget({
	get label() {
		var l10n = new (ChromeUtils.import("resource://devtools/shared/Loader.jsm")
			.require("devtools/shared/l10n")).MultiLocalizationHelper(
				"devtools/client/locales/startup.properties",
				"devtools/client/locales/menus.properties"
			);
		this.tooltiptext = l10n.getFormatStr(
			"toolbox.buttons.responsive",
			Services.appinfo.OS == "Darwin" ? "Cmd+Opt+M" : "Ctrl+Shift+M"
		);
		delete this.label;
		return this.label = l10n.getStr("responsiveDesignMode.label");
	},
	id: "ucf-responsiveDesignMode-btn",
	localized: false,
	onCreated(btn) {
		btn._handleClick = this.click;
		btn.style.cssText = `
			fill-opacity: 0 !important;
			-moz-context-properties: fill, fill-opacity !important;
			list-style-image: url(chrome://devtools/skin/images/command-responsivemode.svg) !important;
		`;
	},
	click() {
		this.ownerDocument.getElementById(id).doCommand();
	}
}))("key_responsiveDesignMode");

Для меня это не так просто. scratch_one-s_head.gif


Dumby, большое спасибо.

Dumby - Спасибо! Ещё проблема: при возвращении из режима "Адаптивный дизайн" в обычный просмотр многие страницы остаются обрезанными, нужно F5 жать.
Как при возвращении в обычный просмотр ещё и обновить страницу? (но не обновлять её для Адаптивного дизайна)


Dumby - а как обновить подсказку для "Reader View" в панели адреса?
У меня только грубый вариант: зарегистрировать mouseenter и постоянно делать: ReaderView.tooltipText = подсказка……

Выделить код

Код:

var box = document.getElementById("page-action-buttons"); // кнопки панели адреса
var boxLst = e => {
	if (e.button == 1 && e.target.id == "pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_") // Reader View
		e.stopImmediatePropagation(),	document.getElementById("key_responsiveDesignMode").doCommand(); // Адаптивный дизайн
}
box.addEventListener("auxclick", boxLst, true);
addDestructor(() => box.removeEventListener("auxclick", boxLst, true));

Dumby - Вопрос: в custom_script.js функция loadscript не грузит одну .JSM-ку, для которой нужна отдельная строка запуска :
ChromeUtils.import(`${scripts}/UCFTitleChangedChild.jsm`, {}).registerUCFTitleChanged(); // исправление заголовка вкладки

Выделить код

Код:

var EXPORTED_SYMBOLS = ["registerUCFTitleChanged", "UCFTitleChangedChild"];
function registerUCFTitleChanged() { // исправление заголовка вкладки …………

Попробуй исправить loadscript, чтобы он был более универсальный, а второй параметр мог быть именем выполняемой функции:
loadscript("UCFTitleChangedChild.jsm", registerUCFTitleChanged()); // так не работает!

Выделить код

Код:

const scripts = 'chrome://user_chrome_files/content/custom_scripts'; (async () => { // загрузка внешних js или jsm-скриптов
	var loadscript = (name, function_register) => {
		try { name.split('.').pop().split("?")[0].split("#")[0].toLowerCase() == "jsm"
			? ChromeUtils.import(`${scripts}/${name}`, {}).function_register
			: Services.scriptloader.loadSubScript(`${scripts}/${name}`,globalThis,"UTF-8");
			return true;
		} catch(e) {}
	};
	loadscript("ucf_eom-button.js"); // нижеследующая строка не работает:
	// loadscript("UCFTitleChangedChild.jsm", registerUCFTitleChanged()); 
})();
Dobrov пишет

но не обновлять её для Адаптивного дизайна

Можно проверять gBrowser.selectedBrowser.browsingContext.inRDMPane

как обновить подсказку для "Reader View" в панели адреса?

Если в смысле установить свою как для аддона, то вот вариант (в custom_script.js).

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

Выделить код

Код:

(async url => {
	var tooltip = "Test Tooltip";

	var m = "2495d258-41e7-4cd5-bc7d-ac15981f064e", id = `{${m}}`, aid = `_${m}_`;
	var manager = ChromeUtils.import(url).ExtensionParent.apiManager;
	var wait = (e, isAppShutdown) => isAppShutdown || manager.on("ready", onReady);
	var onReady = (e, addon) => {
		if (addon.id != id) return;
		manager.off("ready", onReady);
		addon.once("shutdown", wait);
		manager.global.PageActions.actionForID(aid).setTooltip(tooltip);
	}
	manager.on("ready", onReady);

})("resource://gre/modules/ExtensionParent.jsm");

исправить loadscript

Что-то мне не слишком понятны код и задача, может так сойдёт?

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

Выделить код

Код:

(async scripts => {
	var re = /\.jsm$/i;
	var loadscript = name => {
		try {
			var {href, pathname} = new URL(scripts + name);
			if (re.test(pathname))
				return ChromeUtils.import(href);
			Services.scriptloader.loadSubScript(href);
			return true;
		}
		catch(ex) {Cu.reportError(ex);}
	}

	loadscript("ucf_eom-button.js");
	loadscript("UCFTitleChangedChild.jsm")?.registerUCFTitleChanged?.();

})("chrome://user_chrome_files/content/custom_scripts/");


Добавлено: Хотя нет, сам вызов же может завершиться с ошибкой.
Тогда затащим внутрь, как написано, второй параметр — имя выполняемой функции.
скрытый текст

Выделить код

Код:

(async scripts => {
	var re = /\.jsm$/i;
	var loadscript = (name, funcName) => {
		try {
			var {href, pathname} = new URL(scripts + name);
			if (re.test(pathname)) {
				var obj = ChromeUtils.import(href);
				funcName && obj[funcName]();
			} else
				Services.scriptloader.loadSubScript(href);
			return true;
		}
		catch(ex) {Cu.reportError(ex);}
	}

	loadscript("ucf_eom-button.js");
	loadscript("UCFTitleChangedChild.jsm", "registerUCFTitleChanged");

})("chrome://user_chrome_files/content/custom_scripts/");

Dumby - Спасибо! обновление подсказки кнопки также работает из custom_script_win.js.


Dumby - проверь мой новый загрузчик: (сократил, чтобы не повторять строки с loadscript)
Переделал через список массива  js-jsm скриптов. Путь к скриптам используется ещё для подключения [CB]-кодов, поэтому константа.

Выделить код

Код:

const scripts = 'chrome://user_chrome_files/content/custom_scripts/'; (async () => { // ваши скрипты
	[['ucf_QuickToggle.js'], ['UCFTitleChangedChild.jsm', 'registerUCFTitleChanged'], ['Test.jsm']]
	.forEach(function(name) { try { if (/\.jsm$/i.test(name[0])) { // [скрипт js или jsm, инициализация]
				var obj = ChromeUtils.import(scripts + name[0]);
				name[1] && obj[name[1]]();
			} else Services.scriptloader.loadSubScript(scripts + name[0]);
		} catch(ex) {Cu.reportError(ex);}
	});
})();
Dobrov пишет

проверь мой новый загрузчик: (сократил, чтобы не повторять строки с loadscript)

Ну, выглядит нормально.
Но, замысел целиком мне же неизвестен.
Вот зачем тогда функция что-то возвращает, раз это не используется.


Или, в исходнике, name.split('.').pop().split("?")[0].split("#")[0].toLowerCase()
наводило на мысль, что будут присутствовать имена типа "SomeModule.JsM?q=lol#bla",
но ничего подобного пока не видно, хотя, может потом добавятся, а если нет, то зря new URL() создаётся.
Короче — ничего серьёзного.

Dumby - вопрос по коду обновления ToolTip кнопки расширения.
Почему-то подсказка для Video DownloadHelper не обновляется! И как переделать код для замены Tooltip на нескольких кнопках расширений?

Выделить код

Код:

var view_id = "2495d258-41e7-4cd5-bc7d-ac15981f064e"; // Reader View
var vdh_id = "b9db16a4-6edc-47ec-a1f4-b86292ed211d"; // Video DownloadHelper
var manager = ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm").ExtensionParent.apiManager,
wait = (e, isAppShutdown) => isAppShutdown || manager.on("ready", onReady),
onReady = (e, addon) => {
	// if (addon.id != `{${view_id}}`) return;
	if (addon.id == `{${view_id}}`) {
		manager.off("ready", onReady), addon.once("shutdown", wait);
		manager.global.PageActions.actionForID(`_${view_id}_`).setTooltip(`Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n\nКлик мыши	Режим для чтения\nКолёсико	Адаптивный дизайн`); // изменить подсказку
	}
	if (addon.id == `{${vdh_id}}`) {
		manager.off("ready", onReady), addon.once("shutdown", wait);
		manager.global.PageActions.actionForID(`_${vdh_id}_`).setTooltip(`Video DownloadHelper\nСкачивание проигрываемого видео`);
	}
};	manager.on("ready", onReady);
Dobrov пишет

Video DownloadHelper

Совсем разные вещи. У RV pageAction, а у VDH browserAction.

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

Выделить код

Код:

(async url => {
	// Reader View
	var rv = "2495d258-41e7-4cd5-bc7d-ac15981f064e";
	var rv_id = `{${rv}}`, rv_aid = `_${rv}_`;
	var rv_ttt = `Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n\nКлик мыши	Режим для чтения\nКолёсико	Адаптивный дизайн`;

	// Video DownloadHelper
	var vdh_id = "{b9db16a4-6edc-47ec-a1f4-b86292ed211d}";
	var vdh_ttt = "Video DownloadHelper\nСкачивание проигрываемого видео";

	var count = 0;
	var manager = ChromeUtils.import(url).ExtensionParent.apiManager;
	var wait = (e, isAppShutdown) => isAppShutdown || !--count || manager.on("ready", onReady);
	var onReady = (e, addon) => {
		if (addon.id == rv_id)
			manager.global.PageActions.actionForID(rv_aid).setTooltip(rv_ttt);
		else if (addon.id == vdh_id)
			setVDHTooltip(addon);
		else return;

		++count == 2 && manager.off("ready", onReady);
		addon.once("shutdown", wait);
	}
	manager.on("ready", onReady);

	var setVDHTooltip = addon => {
		var vdh_wid = `_${vdh_id.slice(1, -1)}_-browser-action`;
		var {gPalette} = Cu.import("resource:///modules/CustomizableUI.jsm", {});

		var upd = manager.global.browserAction.prototype.updateButton;
		var asgn = eval(`({${upd}})`.replace(/\n^.+"tooltiptext".+$/m, ""));

		(setVDHTooltip = addon => {
			var widget = gPalette.get(vdh_wid);
			widget.tooltiptext = vdh_ttt;

			var {action} = manager.global.browserActionFor(addon);
			Object.assign(action.buttonDelegate, asgn);
			for(var [, node] of widget.instances)
				node.setAttribute("tooltiptext", vdh_ttt);
		})(addon);
	}
})("resource://gre/modules/ExtensionParent.jsm");

Dumby - ты делал перехват кликов для кнопок в панели адреса в скрипте ucf_hookClicks.js.
Получилась обработка кликов двумя дублирующими способами: первый до ucf.unloadlisteners, затем для кнопок на панели адреса.
Второй способ перехватывает клики всех кнопок page-action-buttons, а при обработке проверяется id кнопки.


Возможно ли доработать код, чтобы сразу перехватывать клики кнопок "nav-bar-customization-target" основной панели и "page-action-buttons" панели адреса ?
Выгода этого способа в том, что проще в одном скрипте прописать дополнительные клики нужных кнопок, а не делать кучу скриптов, где каждая кнопка обрабатывается персонально. Также прошу по возможности добавить действие на долгое нажатие кнопки, так как у меня перестал обрабатываться долгий клик в скрипте ToggleAboutConfig, когда я добавил addEventListener("mouseenter" для nav-bar-customization-target в скрипт ucf_hookClicks.


Ещё хотелка - добавить перехват "wheel". Ожидаемый итог работы кода: перехват событий кнопок для двух панелей, разбор такой же, как в твоём коде перехвата кликов: 512: saveSelectionToTxt, // СКМ Click (сохранить .txt) цифра содержит сумму событий: id кнопки, клавиш мыши, мета-клавиш, тип кликов, скролл над кнопками тулбара…
Удобнее сделать изменение яркости скролом над панелью безопасности "identity-box", чем над Звёздочкой. А скролл над кнопками основной панели определять отдельно для каждой, то есть добавить флаги e.scroll+ и e.scroll- так же, как сделано для dbl (дубль-клик).

ucf_hookClicks.js - поменял подсчёт кнопок и клавиш - строка 136

Выделить код

Код:

(async (id, func) => { // для custom_script_win.js: дополнительные клики и подсказки кнопок
	await window.delayedStartupPromise; var
	box = document.getElementById("page-action-buttons"), // кнопки панели адреса
	nav = document.getElementById("nav-bar-customization-target"), // кнопки панели
	btn = document.getElementById("downloads-button"), // 0 Загрузки
	pui = document.getElementById("PanelUI-menu-button"), // 1 меню
	fav = document.getElementById("star-button"), // 2
	prn = document.getElementById("print-button"), // 3
	rv = "pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_", // 4 Reader View
	sgs = "_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action", // SingleSave button
	vdh = "_b9db16a4-6edc-47ec-a1f4-b86292ed211d_-browser-action"; /*Video DownloadHelper*/ if (!btn) return; btn_help =`

Двойной клик: ⬇︎ открыть [Загрузки]
…на картинке: ⧉ найти Похожие\n
Правый клик (Alt+S):  Сохранить
   в единый html всё / выделенное
…дважды  Картинки вкл/выкл\n
Ролик:	 Сохранить как файл .txt
Колёсико на рисунке: ➜ Сохранить`,	PanelUI_help =

`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}
…+ Shift		⚑ Краткая справка
…+ Alt		Персонализация
Клик дважды	⊠ закрыть браузер \n
Правый клик	⇲ Свернуть
…+ дважды	⤾ Вернуть вкладку
…+ Alt		Диспетчер задач
…+ Shift		Адаптивный дизайн\n
Колёсико:	Развернуть | окно
…+ Alt		Полный экран
…+ дважды	Обновить без кэша`, sgs_help = `\nAlt⇧S	 ⌨ нажатие SingleSave`, rv_help =

`Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n
Клик мыши	Режим для чтения
Колёсико	Адаптивный дизайн`; // vdh_help =`Video DownloadHelper\nСкачивание проигрываемого видео`;

	var addDestructor = nextDestructor => {
		var {destructor} = ucf[id];
		ucf[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	},
	showInStatusPanel = (info, time = 5000) => {
		var win = Services.wm.getMostRecentWindow("navigator:browser"); StatusPanel = win.StatusPanel;
		if (StatusPanel.update.tid)
			clearTimeout(StatusPanel.update.tid)
		else {
			var {update} = StatusPanel;
			StatusPanel.update = () => {};
			StatusPanel.update.ret = () => {
				StatusPanel.update = update;
				StatusPanel.update();
			}
		}
		StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, time);
		StatusPanel._label = info;
	},
	Title = (max, title) => { // получить заголовок. без обрезки: max не указан, домен: max <0, + дата: max=0
		if (!title) var title = document.title || gBrowser.selectedTab.label;
		if (max == undefined) return title; // заголовок как есть или ограничить длину, убрать служебные символы
		title = title.replace(/[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim();
		if ( max > 0 ) return title.slice(0, max);
		if ( max == 0) return title.slice(0, 100) +"_"+ new Date().toLocaleDateString('ru', {day: 'numeric', month: 'numeric', year: '2-digit'}) +'-'+ new Date().toLocaleTimeString().replace(/:/g, "։");
		var host = decodeURIComponent(gURLBar.value); // max < 0
		if (!/^file:\/\//.test(host)) host = host.replace(/^.*url=|https?:\/\/|www\.|\/.*/g,'');
		return host.replace(/^ru\.|^m\.|forum\./,'').replace(/^club\.dns/,'dns');
	},
	saveSelectionToTxt = async () => { // сохранить страницу или выделенный текст как файл .txt
		var splice = saveURL.length == 10;
		var msgName = id + ":Save:GetSelection";
		var receiver = msg => {
			var title = Title(0);
			var args = [
				"data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + "\n\n" + msg.data),
				title + '.txt', null, false, true, null, window.document];
			splice && args.splice(5, 0, null);
			saveURL(...args) && showInStatusPanel("√ текст сохранён: " + title.slice(0, 60));
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));
		var func = fm => {
			var res, fed, win = {}, fe = fm.getFocusedElementForWindow(content, true, win);
			var sel = (win = win.value).getSelection();
			if (sel.isCollapsed) {
				var ed = fe && fe.editor;
				if (ed && ed instanceof Ci.nsIEditor)
					sel = ed.selection, fed = fe;
			}
			if (sel.isCollapsed)
				fed && fed.blur(), docShell.doCommand("cmd_selectAll"),
				res = win.getSelection().toString(), docShell.doCommand("cmd_selectNone"),
				fed && fed.focus();
			res = res || sel.toString();
			/\S/.test(res) && sendAsyncMessage("saveSelectionToTxt", res);
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})`.replace("saveSelectionToTxt", msgName)) + '(Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager));';
		(saveSelectionToTxt = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	},
	save = async () => { // SingleHtml by Лекс, правка: Dumby, Dobrov
		var msgName = id + "ucfDwnldsBtnSaveSnapshotToHTML";
		if (typeof IOUtils != "object") { // Firefox 78 ESR
			var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
			var PathUtils = {join: (...args) => OS.Path.join(...args)};
			var IOUtils = {writeUTF8: (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt))};
		}
		var write = IOUtils.writeUTF8 ? "writeUTF8" : "writeAtomicUTF8";
		var msgListener = async msg => {
			var [fileContent, fileName] = msg.data, dir; // fileName: выделенный текст или null
			try {dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}
			var arr = prefs.getStringPref("ucf_save.dirs","_Web||_Images|0").split('|').slice(0,2); //subdir: title|host
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // ucf_save.dirs = "_Web||_Pics|1" HTML сохранится в папку [Загрузки]/_Web/label
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать, если не существует…
			var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
			file.initWithPath(dir.path);
			if (!fileName) fileName = Title(100); // убрать служебные символы
			dir.append(Title(0, fileName) +'.html');
			await IOUtils[write](dir.path, fileContent) && showInStatusPanel("√ страница записана: " + fileName.slice(0, 60));
			var d = await Downloads.createDownload({ source: "about:blank", target: FileUtils.File(dir.path)}); // Fake download
			(await Downloads.getList(Downloads.ALL)).add(d);
			d.refresh(d.succeeded = true); // кнопка Загрузки мигает
		}
		messageManager.addMessageListener(msgName, msgListener);
		addDestructor(() => messageManager.removeMessageListener(msgName, msgListener));

		var svc = 'globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services';
		var url = "data:;charset=utf8," + encodeURIComponent(`(${func})(${svc});`.replace("%MSG_NAME%", msgName));
		(save = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	},
	tid, allowMousedown, listener = { // доп.события для 15 кнопок
		handleEvent(e) {
			if (e.detail > 2) return;
			var btn = e.target;
			var dbl = e.detail == 2;
			var num = e.button *512 + e.metaKey *256 + e.ctrlKey *128 + e.shiftKey *64 + e.altKey *32 + dbl *16 +
			(btn == document.getElementById(rv) && 4) +
			(btn == prn && 3) + (btn == fav && 2) + (btn == pui && 1);

			if (!this[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "mousedown";
			}
			if (dbl) tid &&= clearTimeout(tid), this[num](btn);
			else tid = setTimeout(this.exec, 300, num, btn, this);
		},
		exec(num, btn, self) {
			tid = null;
			self[num](btn);
		},
		mousedown(btn) {
			allowMousedown = true;
			btn.dispatchEvent(new MouseEvent("mousedown", {}));
			allowMousedown = false;
		},
		context(btn) {
			btn.removeAttribute("context");
			btn.dispatchEvent(new MouseEvent("contextmenu", this.a));
			btn.toggleAttribute("context");
		},
		switchToTab(but, url) { // открыть вкладку | закрыть, если открыта
			for(var tab of but.ownerGlobal.gBrowser.tabs)
				if ( tab.linkedBrowser.currentURI.spec == url ) {but.ownerGlobal.gBrowser.removeTab(tab); return;}; // вкладка найдена, закрыть
			but.ownerGlobal.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
		},

		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

/*** ======= Downloads Clicks ======= ***/

		512: saveSelectionToTxt, // СКМ Click (сохранить .txt)
		16() { // Double Left Click - Обзор папки «Загрузки»
			Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
		},
		1024: save, // ПКМ Click (Single HTML)
		1040(btn) { // Double Right Click
			var pref = "permissions.default.image";
			var one = prefs.getIntPref(pref) == 1;
			prefs.setIntPref(pref, one ? 2 : 1);
			btn.style.filter = one ? "hue-rotate(180deg) brightness(95%)" : "";
			BrowserReload();
		},

/*** ======= PanelUI-menu Clicks ======= 100*pui 32*e.button 8*e.ctrlKey 4*e.shiftKey 2*e.altKey dbl btn ***/

		33() { gCustomizeMode.enter(); // ЛКМ + Alt Персонализация
		},
		65() { // Shift + ЛКМ
			var help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'];
			var newURI = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).convertChromeURL(Services.io.newURI(help_ucf[0])); // .spec = file:///
			(newURI.QueryInterface(Ci.nsIFileURL).file.exists()) ? this.switchToTab(pui, help_ucf[0]) : this.switchToTab(pui, help_ucf[1]);
		},
		545: BrowserFullScreen, // Alt + СКМ
		513() { // СКМ
			windowState != STATE_MAXIMIZED ? maximize() : restore();
		},
		529: BrowserReloadSkipCache,  // СКМ Double
		1025() { // ПКМ
			minimize();
		},
		1057(pui) { // Alt + ПКМ
			this.switchToTab(pui, 'about:performance');
		},
		1089(pui) { // Shift + ПКМ
			pui.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // запуск пункта меню с HotKey
			if (gBrowser.selectedBrowser.browsingContext.inRDMPane) BrowserReload();
		},
		1153(pui) { // Shift + Alt + ПКМ
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_devtools_remotedebugging");
			(this[138] = target => obj.oncommand({target}))(pui); // запуск пункта меню, у которого нет HotKey
		},
		17: BrowserTryToCloseWindow, // Double Left Click
		1041(pui) { // ПКМ Double Right Click
			pui.ownerGlobal.undoCloseTab();
		},
	}, // end Clicks

	keydown_win = e => { // нажатие клавиш
		if (e.keyCode == 83 && e.altKey) e.shiftKey // Alt+S [+Shift]
		? singlesave ? singlesave.click() : save() : save(); // имитировать клик по кнопке, используя её ID
		if (e.keyCode == 68 && e.altKey){ // Alt+D отладка - запуск внешнего JS
			// e.target.ownerDocument.getElementById("key_browserConsole").doCommand();
			eval(Cu.readUTF8URI(Services.io.newURI("chrome://user_chrome_files/content/custom_scripts/User.js")));
			console.log("END User.js " + Math.random());
		}
	},
	{prefs, dirsvc} = Services, linux = /macos|linux/.test(AppConstants.platform), singlesave;
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	var hint_upd = function(btn, text, find) { // обновить подсказку
		return;
	}
	var mouseenter = function(e) {
		this.parentNode.addEventListener("mousedown", stop, true);
		this.addEventListener("mouseleave", mouseleave, {once: true});

		var sgs_btn = document.getElementById(sgs), dw;
		try {dw = prefs.getComplexValue("browser.download.dir",Ci.nsIFile)}
			catch {dw = dirsvc.get("DfltDwnld", Ci.nsIFile)};
		var rv_btn = document.getElementById(rv); // Reader View
		if (e.target == rv_btn) {
			rv_btn.tooltipText = rv_help;
		}
		// var vdh_btn = document.getElementById(vdh); // Video DownloadHelper
		// if (e.target == vdh_btn) {
		// 	vdh_btn.tooltipText = vdh_help;
		// }
		if ((e.target == pui) && (!/справка/.test(pui.tooltipText)))
			pui.tooltipText = PanelUI_help; // обновить подсказки кнопок
		if (e.target == btn) {
		// if (!/Двойной/.test(btn.tooltipText))
			btn.tooltipText = GetDynamicShortcutTooltipText(btn.id) + btn_help; // обновлять подсказку при наведении мыши
		if (sgs_btn){
			if (!/SingleSave/.test(btn.tooltipText)) btn.tooltipText = btn.tooltipText + sgs_help;
		} else
			btn.tooltipText = btn.tooltipText.replace(sgs_help,'');
		if (!/выбранная/.test(btn.tooltipText))
			btn.tooltipText = btn.tooltipText + `${dw ? "\n\n[Загрузки] — выбранная папка:\n"+ dw.path.substring(0,33) + `${dw.path.length > 32 ? `…\n…${dw.path.substring(dw.path.length -31, dw.path.length)}`: ""}` : ""}`; // сократить/разбить длинную строку
		}
	}
	var mouseleave = function(e) {
		this.parentNode.removeEventListener("mousedown", stop, true);
	}
	var stop = e => {
		e.button || allowMousedown || e.stopImmediatePropagation();
	}
	var btns = [btn, pui];
	for(var b of btns) {
		b.toggleAttribute("context"),
		b.addEventListener("click", listener, true),
		b.addEventListener("mouseenter", mouseenter);
	}

	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		for(var b of btns) {
			b.removeEventListener("click", listener, true);
			b.removeEventListener("mouseenter", mouseenter);
			if (b.matches(":hover"))
				b.removeEventListener("mouseleave", mouseleave),
				b.parentNode.removeEventListener("mousedown", stop, true);
		}
	}};
	ucf.unloadlisteners.push(id);

	var boxLst = e => {
		console.log('@: '+ e.button);
		if (e.button == 1 && e.target.id == `pageAction-urlbar-_${rv}_`) { // Reader View Button
			e.stopImmediatePropagation(),	document.getElementById("key_responsiveDesignMode").doCommand(); // Адаптивный дизайн
			if (gBrowser.selectedBrowser.browsingContext.inRDMPane)
				BrowserReload();
		}
	}
	box.addEventListener("auxclick", boxLst, true);
	box.addEventListener("mouseenter", mouseenter, true);
	window.addEventListener("keydown", keydown_win);
	addDestructor(() => {
		box.removeEventListener("auxclick", boxLst, true);
		box.removeEventListener("mouseenter", mouseenter, true);
		window.removeEventListener("keydown", keydown_win);
	});

})("downloads-button-click-listener", ({io, focus}) => { // SingleHTML не сохраняет svg графику

	var resolveURL = function (url, base) {
		try { return io.newURI(url, null, io.newURI(base)).spec;} catch {}
	},
	getSelWin = function (w) {
		if (w.getSelection().toString()) return w;
		for (var i = 0, f, r; f = w.frames[i]; i++) {
			try { if (r = getSelWin(f)) return r;} catch(e) {}
		}
	},
	encodeImg = function (src, obj) {
		var canvas, img, ret = src;
		if (/^https?:\/\//.test(src)) {
			canvas = doc.createElement('canvas');
			if (!obj || obj.nodeName.toLowerCase() != 'img') {
				img = doc.createElement('img');
				img.src = src;
			} else
				img = obj;
			if (img.complete) try{
				canvas.width = img.width;
				canvas.height = img.height;
				canvas.getContext('2d').drawImage(img, 0, 0);
				ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png'));
			} catch (e) {};
			if (img != obj) img.src = 'about:blank';
		};
		return ret;
	},
	toSrc = function (obj) {
		var strToSrc = function (str) {
			var chr, ret = '', i = 0, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\x22' : '\\\x22', '\\': '\\\\'};
			while (chr = str.charAt(i++)) {
				ret += meta[chr] || chr;
			};
			return '\x22' + ret + '\x22';
		},
		arrToSrc = function (arr) {
			var ret = [];
			for (var i = 0; i < arr.length; i++) {
				ret[i] = toSrc(arr[i]) || 'null';
			};
			return '[' + ret.join(',') + ']';
		},
		objToSrc = function (obj) {
			var val, ret = [];
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val);
			};
			return '{' + ret.join(',') + '}';
		};
		switch (Object.prototype.toString.call(obj).slice(8, -1)) {
			case 'Array': return arrToSrc(obj);
			case 'Boolean':
			case 'Function':
			case 'RegExp': return obj.toString();
			case 'Date': return 'new Date(' + obj.getTime() + ')';
			case 'Math': return 'Math';
			case 'Number': return isFinite(obj) ? String(obj) : 'null';
			case 'Object': return objToSrc(obj);
			case 'String': return strToSrc(obj);
			default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null';
		}
	},
	mainWin = {};
	focus.getFocusedElementForWindow(content, true, mainWin);
	mainWin = mainWin.value;

	var selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location;
	var ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g;

	if (selWin) {
		var rng = win.getSelection().getRangeAt(0);
		pEle = rng.commonAncestorContainer;
		ele = rng.cloneContents();
	} else {
		pEle = doc.documentElement;
		ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true);
	};
	while (pEle) {
		if (pEle.nodeType == 1) {
			clone = pEle.cloneNode(false);
			clone.appendChild(ele);
			ele = clone;
		};
		pEle = pEle.parentNode
	};
	var sel = doc.createElement('div');
	sel.appendChild(ele);

	for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) {
		el = all[i];
		if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) {
			if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href);
			return prev + encodeImg(url) + next;
		});
		switch (el.nodeName.toLowerCase()) {
			case 'link':
			case 'style':
			case 'script': el.parentNode.removeChild(el); break;
			case 'a':
			case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break;
			case 'img':
			case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break;
			case 'audio':
			case 'video':
			case 'embed':
			case 'frame':
			case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break;
			case 'object': if (el.hasAttribute('data')) el.data = el.data; break;
			case 'form': if (el.hasAttribute('action')) el.action = el.action; break;
		}
	};
	var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets, title = doc.getElementsByTagName('title')[0];
	meta.httpEquiv = 'content-type';
	meta.content = 'text/html; charset=utf-8';
	head.appendChild(meta);
	if (title) head.appendChild(title.cloneNode(true));

	head.copyScript = function (unsafeWin) {
		if ('$' in unsafeWin) return;
		var f = doc.createElement('iframe');
		f.src = 'about:blank';
		f.setAttribute('style', 'position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');
		doc.documentElement.appendChild(f);
		var str, script = doc.createElement('script');
		script.type = 'text/javascript';
		for (var name in unsafeWin) {
			if (name in f.contentWindow || !/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) continue;
			try {
				str = toSrc(unsafeWin[name]);
				if (!/\{\s*\[native code\]\s*\}/.test(str)) {
					script.appendChild(doc.createTextNode('var ' + name + ' = ' + str.replace(/<\/(script>)/ig, '<\\/$1') + ';\n'));
				}
			} catch (e) {};
		};
		f.parentNode.removeChild(f);
		if (script.childNodes.length) this.nextSibling.appendChild(script);
	};
	head.copyScript(win.wrappedJSObject || win);

	head.copyStyle = function (s) {
		if (!s) return;
		var style = doc.createElement('style');
		style.type = 'text/css';
		if (s.media && s.media.mediaText) style.media = s.media.mediaText;
		try {
			for (var i = 0, rule; rule = s.cssRules[i]; i++) {
				if (rule.type != 3) {
					if((!rule.selectorText || rule.selectorText.indexOf(':') != -1) || (!sel.querySelector || sel.querySelector(rule.selectorText))) {
						var css = !rule.cssText ? '' : rule.cssText.replace(reUrl, function (a, prev, url, next) {
							if (!/^[a-z]+:/.test(url)) url = resolveURL(url, s.href || loc.href);
							if(rule.type == 1 && rule.style && rule.style.backgroundImage) url = encodeImg(url);
							return prev + url + next;
						});
						style.appendChild(doc.createTextNode(css + '\n'));
					}
				} else {
					this.copyStyle(rule.styleSheet);
				}
			}
		} catch(e) {
			if (s.ownerNode) style = s.ownerNode.cloneNode(false);
		};
		this.appendChild(style);
	};
	for (var j = 0; j < sheets.length; j++) head.copyStyle(sheets[j]);
	head.appendChild(doc.createTextNode('\n'));
	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};
	var selText = selWin ? win.getSelection().toString().slice(0, 200) : undefined;
	sendAsyncMessage("%MSG_NAME%", [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', selText]); // выделенный текст

}); // END hookClicks

ucf_BookmarkDir.js - только как пример: яркость прокруткой над ★

Выделить код

Код:

(async (id, sel) => { // Клики на Звёздочке, ToolTip: расположение закладки в Избранном, Недавняя папка
	var g = Cu.getGlobalForObject(Cu), stt = g[id]; // https://forum.mozilla-russia.org/viewtopic.php?pid=790890#p790890
	if (!stt) { var {obs, prefs} = Services, {bookmarks: bm, observers: pobs} = PlacesUtils;
		stt = g[id] = { bm, help_star: `

Правый клик:	⤾ Вернуть вкладку
…+ Alt 	Перевод выдел.текст | Сайт 
…+ Shift	Гугл Перевод или поиск\n
Колесико ±	Яркость страниц
…+ клик 	Полная яркость`,

			pref: `ucf.${id}Guid`,
			events: ["bookmark-added"],
			async init() {
				this.handleEvent = e => this[e.type](e);

				if ((this.pbm = typeof PlacesBookmarkMoved == "function"))
					this.events.push("bookmark-moved");
				else
					this.QueryInterface = g.ChromeUtils.generateQI([Ci.nsINavBookmarkObserver]),
					bm.addObserver(this);
				pobs.addListener(this.events, this.added = events => {
					for(var e of events) e.isTagging || this[e.constructor.name](e);
				});
				obs.addObserver(this, "quit-application-granted");
				this.args = [b => this.bguids.add(b.parentGuid), {concurrent: true}];
				var guid = prefs.getStringPref(this.pref, "");
				if (!guid) try {var [guid] = await PlacesUtils.metadata.get(
					PlacesUIUtils.LAST_USED_FOLDERS_META_KEY, []
				)} catch {}
				this.guids.push(guid || await PlacesUIUtils.defaultParentGuid || bm.unfiledGuid);

				var pref = "ucf.tabbrowser-tabpanels.opacity"; // яркость страницы
				var getPref = () => Services.prefs.getIntPref(pref, 100);
				var css = `@-moz-document url(chrome://browser/content/browser.xhtml) {
					:is(${sel})[rst] {filter: grayscale(1%) !important;}
					:root:not([chromehidden*=toolbar]) #tabbrowser-tabbox {background-color: black !important;}
					:root:not([chromehidden*=toolbar]) #tabbrowser-tabpanels {opacity:${getPref()/100} !important;}}`;
				var subst = "ucf-tabbrowser-tabpanels-opacity-style", url = `resource://${subst}/`;
				Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler)
					.setSubstitution(subst, Services.io.newURI("data:text/css," + encodeURIComponent(css)));
				var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
				sss.loadAndRegisterSheet(Services.io.newURI(url), sss.USER_SHEET);

				var st = InspectorUtils.getAllStyleSheets(document).find(s => s.href == url).cssRules[0].cssRules[2].style;

				this.setPref = (e, val = 100) => {
					Services.prefs.setIntPref(pref, val);
					e.target.toggleAttribute("rst");
				}
				this.wheel = e => {
					var val = getPref() + (e.deltaY < 0 ? 5 : -5); // шаг
					val < 25 || val > 100 || this.setPref(e, val);
				}
				var observer = () => st.setProperty("opacity", getPref() / 100, "important");
				Services.prefs.addObserver(pref, observer);
				this.removePrefObs = () => Services.prefs.removeObserver(pref, observer);
			},
			observe() {
				this.pbm || bm.removeObserver(this);
				pobs.removeListener(this.events, this.added);
				obs.removeObserver(this, "quit-application-granted");
				prefs.setStringPref(this.pref, this.guids[0]);
				this.removePrefObs();
			},
			bguids: new g.Set(), guids: new g.Array(),
			skipTags: true,
			tt(win) {
				var list = win.InspectorUtils
					.getChildrenForNode(win.document.documentElement, true);
				return list.item(list.length - 1);
			},
			PlacesBookmarkAddition(e) {
				if (e.itemType == bm.TYPE_BOOKMARK && e.source == bm.SOURCES.DEFAULT)
					this.guids[0] = e.parentGuid;
			},
			PlacesBookmarkMoved(e) {
				e.parentGuid != e.oldParentGuid && this.PlacesBookmarkAddition(e);
			},
			onItemMoved(a, b, c, d, e, itemType, f, oldParentGuid, parentGuid, source) {
				this.PlacesBookmarkMoved({itemType, source, oldParentGuid, parentGuid});
			},
			fetch(win) {
				this.bguids.clear();
				return bm.fetch({url: win.gBrowser.currentURI.spec}, ...this.args);
			},
			addTab: function(win, url, add, params = {relatedToCurrent: true}) { // открыть адрес [add: в новой вкладке]
				params.triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
				return (add) ? win.gBrowser.addTab(url, params) : win.gBrowser.loadURI(url, params);
			},
			translate(browserMM, win, e, go) { // Google-перевод сайта | выделенного текста (go) поиск выдел. текста в Яндекс
				browserMM.addMessageListener('getSelect', function listener(msg) {
					var url = (msg.data) ? (go)
						? "https://yandex.ru/search/?text="+ msg.data +"&src=suggest_Pers&lang=ru" // поиск текста в Яндекс
						: "https://translate.google.com/#view=home&op=translate&sl=auto&tl=ru&text="+ msg.data // Гугл перевод
						: "http://translate.google.com/translate?u="+ gURLBar.value +"&hl=ru&ie=UTF-8&sl=auto&tl=ru"; // Перевод сайта
					if (go && !msg.data) // Перевод сайти в Яндекс. ничего не выделено + go не пуст
						gBrowser.selectedTab = e.addTab(win, "https://translate.yandex.com/translate?url=" + gURLBar.value + "&dir=&ui=ru&lang=auto-ru", 1)
					else
						gBrowser.selectedTab = e.addTab(win, url, 1);
				browserMM.removeMessageListener('getSelect', listener, true);
				});
				browserMM.loadFrameScript('data:,sendAsyncMessage("getSelect", content.document.getSelection().toString())', false);
			},
			auxclick(e) {
				if (e.button == 2) {
					var win = e.view;
					if (e.altKey)
						this.translate(gBrowser.selectedBrowser.messageManager, win, this, 1);
					else if (e.shiftKey)
						this.translate(gBrowser.selectedBrowser.messageManager, win, this);
					else
						win.undoCloseTab();
				} else
					this.setPref(e);
			},
			find: obj => obj.name == "tooltiptext"
		};
		var ps = ["onBeginUpdateBatch", "onEndUpdateBatch", "onItemChanged", "onItemVisited"];
		var noop = () => {}; for(var p of ps) stt[p] = noop; stt.init();

		var func = id => this[id].mouseenter = async function(e) {
			var win = e.view, star = e.target, result = [], starred = star.hasAttribute("starred");
			starred && await this.fetch(win);
			this.help_star = this.help_star.replace(/Яркость страниц.*/, `Яркость страниц ${win.Services.prefs.getIntPref("ucf.tabbrowser-tabpanels.opacity", 100)}%`);

			for(var guid of (starred ? this.bguids : this.guids)) {
				var arr = [], num = 50;
				while(--num) {
					if (!star.matches(":hover")) return;
					var res = await this.bm.fetch(guid);
					if (!res) break;
					if ((guid = res.parentGuid) == this.bm.rootGuid) {
						arr.unshift(this.bm.getLocalizedTitle(res));
						break;
					}
					arr.unshift(res.title || "[Безымянная папка]");
				}
				arr.length && result.push(arr.join("\\"));
			}
			if (!star.matches(":hover")) return;

			var text = (await win.document.l10n.formatMessages([{ // стандартная подсказка
				id: star.getAttribute("data-l10n-id"),
				args: JSON.parse(star.getAttribute("data-l10n-args"))
			}]))[0].attributes.find(this.find).value, txt;

			if (result.length) {
				txt = result.join("\n");
				txt = starred ? `\n\n★ ${result.length > 1 ? "Данные закладки добавлены" : "Данная закладка добавлена"} в:\n${txt}` : "\n\n★ Недавно добавленная папка:\n" + txt;
			}
			win.document.tooltipNode == star ? this.tt(win).label = text + this.help_star + txt : star.tooltipText = text + this.help_star + txt;
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})("${id}")`);
		g.ChromeUtils.compileScript(url).then(ps => ps.executeInGlobal(g));
	}
	await delayedStartupPromise;

	var types = ["auxclick", "mouseenter", "wheel"];
	var stars = Array.from(document.querySelectorAll(sel));

	for(var star of stars) for(var type of types) star.addEventListener(type, stt);
	star.setAttribute("context", "event.stopPropagation()");

	var destructor = () => {
		for(var star of stars) for(var type of types) star.removeEventListener(type, stt);
	}
	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	if (ucf)
		ucf[id] = {destructor}, ucf.unloadlisteners.push(id);
	else
		window.addEventListener("unload", destructor, {once: true});
})("ucfBookmarksStarFTooltipHelper", "#star-button, #context-bookmarkpage");

ucf_QuickToggle.js - здесь код LongPress

Dobrov пишет

доработать код, чтобы сразу перехватывать клики кнопок "nav-bar-customization-target" основной панели и "page-action-buttons"

Так PanelUI-menu-button же торчит в коде,
nav-bar-customization-target ей не родитель. Общим будет nav-bar.

identity-box

Раз предполагаются элементы с видимыми для мыши
дочерними элементами, придётся использовать перебор и closest().
Кстати, с FF90+ таковы pageAction's (в hbox иконки завернули).

добавить действие на долгое нажатие кнопки
добавить перехват "wheel"

И ты думаешь я смогу это всё нормально записать?
Весьма сомнительно. Попробую, так, отдельно, в консоль.

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

Выделить код

Код:

(() => {
	var c = msg => Services.console.logStringMessage("[HC] " + msg);
	var data = {
		"#downloads-button": {
			mousedownTarget: true,

			128() { // СКМ Click 
				c("Downloads Button Middle Click");
			},
			4() { // Double Left Click
				c("Downloads Button Double Left Click");
			},
			256() { // ПКМ Click
				c("Downloads Button Right Click");
			},
			260(btn) { // Double Right Click
				c("Downloads Button Double Right Click");
			},

			1() { // Left Long Press
				c("Downloads Button Left Long Press");
			},
		},
		"#PanelUI-menu-button": {
			mousedownTarget: true,

			8() { // ЛКМ + Alt
				c("PUI Button Alt + Left Click");
			},
			16(btn) { // Shift + ЛКМ
				c("PUI Button Shift + Left Click");
			},
			136(btn) { // Alt + СКМ
				c("PUI Button Alt + Middle Click");
			},
			128() { // СКМ
				c("PUI Button Middle Click");
			},
			132() { // СКМ Double
				c("PUI Button Double Middle Click");
			},
			256() { // ПКМ
				c("PUI Button Right Click");
			},
			264(btn) { // Alt + ПКМ
				c("PUI Button Alt + Right Click");
			},
			272() { // Shift + ПКМ
				c("PUI Button Shift + Right Click");
			},
			280(btn) { // Shift + Alt + ПКМ
				c("PUI Button Shift + Alt + Right Click");
			},
			4() { // Double Left Click
				c("PUI Button Double Left Click");
			},
			260() { // ПКМ Double Right Click
				c("PUI Button Double Right Click");
			},

			145() { // Shift + Middle Long Press
				c("PUI Button Shift + Middle Long Press");
			},
		},
		"#pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_": { // Reader View Button
			128() { // Middle Click
				c("Reader View Middle Click");
			},

			289() { // Ctrl + Right Long Press
				c("Reader View Ctrl + Right Long Press");
			},
			2(trg, forward) { // wheel
				c("Reader View Wheel " + (forward ? "forward" : "backward"));
			},
		},
		"#star-button-box": {
			1() { // Left Long Press
				c("Star Left Long Press");
			},
			4() { // Double Left Click
				c("Star Double Left Click");
			},
			129() { // Middle Long Press
				c("Star Middle Long Press");
			},
		},

		"#identity-permission-box": {
			2(trg, forward) { // wheel
				c("Identity Permisson Box Wheel " + (forward ? "forward" : "backward"));
			}
		},
		"#identity-box": {
			2(trg, forward) { // wheel
				c("Identity Box Wheel " + (forward ? "forward" : "backward"));
			},
			34(trg, forward) { // Ctrl + wheel
				c("Identity Box Ctrl + Wheel " + (forward ? "forward" : "backward"));
			},
		},
		"#identity-icon-box": {
			16() { // Shift + Left Click
				c("Identity Icon Box Shift + Left Click");
			},
		},
	};

	var listener = {
		filter(sel) {
			return this.closest(sel);
		},
		find(sel) {
			return data[sel][this] || data[sel][this + 1];
		},
		handleEvent(e) {
			if (this.skip || e.detail > 2) return;

			var trg = e.target;
			var sels = this.selectors.filter(this.filter, trg);
			var {length} = sels;
			if (!length) return;

			var dbl = e.detail == 2;
			var wh = e.type.startsWith("w");

			var num = e.metaKey *64 + e.ctrlKey *32 + e.shiftKey *16 + e.altKey *8
				+ (wh ? 2 : e.button *128 + dbl *4);

			var obj = data[
				length > 1 && sels.find(this.find, num) || sels[0]
			];

			// wheel
			if (wh) return obj[num]?.(trg, e.deltaY < 0);

			// mousedown
			if (e.type.startsWith("m")) {
				obj.mousedownTarget && this.stop(e);
				if (dbl) return;

				this.longPress = false;
				if (++num in obj)
					this.mousedownTID = setTimeout(this.onLongPress, 640, trg, obj, num);
				if (e.button == 2)
					this.ctx = trg.getAttribute("context"),
					trg.setAttribute("context", "");
				return;
			}

			// click
			obj.mousedownTarget || this.stop(e);
			if (this.longPress) return this.longPress = false;
			dbl
				? this.clickTID &&= clearTimeout(this.clickTID)
				: this.mousedownTID &&= clearTimeout(this.mousedownTID);

			if (!obj[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "dispatch",
					this.mdt = obj.mousedownTarget;
				obj = this;
			}
			dbl
				? obj[num](trg)
				: this.clickTID = setTimeout(this.exec, 300, trg, obj, num);
		},
		get selectors() {
			this.exec = (trg, obj, num) => {
				this.clickTID = null;
				obj[num](trg);
			}
			this.onLongPress = (trg, obj, num) => {
				this.mousedownTID = null;
				this.longPress = true;
				obj[num](trg);
			}
			delete this.selectors;
			return this.selectors = Object.keys(data);
		},
		get mdEvent() {
			delete this.mdEvent;
			return this.mdEvent = new MouseEvent("mousedown", {bubbles: true});
		},
		context(trg) {
			this.ctx
				? trg.setAttribute("context", this.ctx)
				: trg.removeAttribute("context");
			trg.dispatchEvent(new MouseEvent("contextmenu", this.a));
		},
		dispatch(trg) {
			this.skip = true;
			this.mdt ? trg.dispatchEvent(this.mdEvent) : trg.click();
			this.skip = false;
		},
		stop: e => {
			e.preventDefault();
			e.stopImmediatePropagation();
		},
		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0}
	};

	var root = document.getElementById("nav-bar");
	var events = ["click", "mousedown", "wheel"];
	for(var type of events) root.addEventListener(type, listener, true);
	var id = "test-hookClicks";
	ucf_custom_script_win.unloadlisteners.push(id);
	ucf_custom_script_win[id] = {destructor() {
		for(var type of events) root.removeEventListener(type, listener, true);
	}};
})();

Dumby спасибо за отличный код дополнительных кликов. :)
Подключил оба кода в custom_script_win.js, попробую вернуть функции Save HTML и прочие…


Только не понял, как в код обновления tooltips добавить removeEventListener и нужен ли он.

Выделить код

Код:

(() => { // update Tooltips
	………
	addDestructor(() => {
		………
	});
})();

Ещё не обновляются подсказки для 1) tracking-protection-icon-container "На этой странице не обнаружено ни одного известного Firefox трекера" и 2) identity-icon-box "Подтверждено: Let's Encrypt".
Я подключил на них яркость страниц, код работает. А как к этим кнопкам с динамической подсказкой добавить свой текст?
`Колесико ±    Яркость страниц\n…+ клик     Полная яркость
Яркость страниц ${win.Services.prefs.getIntPref("ucf.tabbrowser-tabpanels.opacity", 100)}%`
?

Dobrov пишет

как в код обновления tooltips добавить removeEventListener и нужен ли он

Да так же как и в коде для кликов.
А вот нужен ли он — это я и сам хотел бы знать.
Необходимость вызывает сомнение, но так принято (было),
поэтому, по возможности, оно так и продолжается.


Допустим, тултипский код рядом, сразу после var listener = {.....};
и добавление обработчиков и деструктора в самом конце.

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

Выделить код

Код:

.......
	var str_cut = s => s;

	var dsym = Symbol();
	var j = (...args) => args.join("\n");
	var tooltips = {
		get "PanelUI-menu-button"() {
			delete this["PanelUI-menu-button"];
			return j(
				`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}`,
				"…+ Shift		⚑ Краткая справка",
				"…+ Alt		Персонализация",
				"Клик дважды	⊠ закрыть браузер\n",

				"Правый клик	⇲ Свернуть",
				"…+ дважды	⤾ Вернуть вкладку",
				"…+ Alt		Диспетчер задач",
				"…+ Shift		Адаптивный дизайн\n",

				"Колёсико:	Развернуть | окно",
				"…+ Alt		Полный экран",
				"…+ дважды	Обновить без кэша"
			);
		},

		"pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_": j(
			`Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n`,

			"Клик мыши	Режим для чтения",
			"Колёсико	Адаптивный дизайн"
		),

		"_b9db16a4-6edc-47ec-a1f4-b86292ed211d_-browser-action": j(
			"Video DownloadHelper",
			"Скачивание проигрываемого видео"
		),

		[dsym]: j(
			GetDynamicShortcutTooltipText("downloads-button"),

			"\nДвойной клик: ⬇︎ открыть [Загрузки]",
			"…на картинке: ⧉ найти Похожие\n",

			"Правый клик (Alt+S):  Сохранить",
			"   в единый html всё / выделенное",
			"…дважды  Картинки вкл/выкл\n",

			"Ролик:	 Сохранить как файл .txt",
			"Колёсико на рисунке: ➜ Сохранить"
		),
		get "downloads-button"() {
			var hint = this[dsym];
			if (document.getElementById("_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action"))
				hint += "\nAlt⇧S	 ⌨ нажатие SingleSave";
			try {var dw = Services.prefs.getComplexValue("browser.download.dir", Ci.nsIFile);}
			catch {dw = Services.dirsvc.get("DfltDwnld", Ci.nsIFile);}
			if (dw) hint += "\n\n[Загрузки] — выбранная папка:\n" + str_cut(dw.path, 33);
			return hint;
		},

		get "identity-icon-box"() {
			var ttt = "";
			var trg = window.event.target;
			if (!trg.id.endsWith("x")) {
				if (trg.hasAttribute("tooltiptext"))
					ttt = trg.ttt = trg.tooltipText;
				else
					ttt = trg.ttt;
				if (ttt) ttt += "\n\n";
				trg.removeAttribute("tooltiptext");
			}
			return ttt + "Свой текст";
		},

		get "tracking-protection-icon-container"() {
			var trg = window.event?.target;
			return trg.id.endsWith("r") &&
				trg.textContent + "\n\nСвой текст";
		}
	};

	document.getElementById("tracking-protection-icon-container")
		.removeAttribute("tooltip");

	var onMouseenter = e => {
		var trg = e.target;
		var hint = tooltips[trg.id] || tooltips[(trg = trg.parentNode).id];
		if (hint) trg.tooltipText = hint;
	}
	var root = document.getElementById("nav-bar");
	var events = ["click", "mousedown", "wheel"];

	root.addEventListener("mouseenter", onMouseenter, true);
	for(var type of events) root.addEventListener(type, listener, true);

	var id = "hookClicks-and-tooltips";
	ucf_custom_script_win.unloadlisteners.push(id);
	ucf_custom_script_win[id] = {destructor() {
		root.removeEventListener("mouseenter", onMouseenter, true);
		for(var type of events) root.removeEventListener(type, listener, true);
	}};
})();

Dumby - спасибо! Скрипт hookClicks пригодится многим, он позволит прописывать обработку кликов в одном скрипте и позволит «разгрузить» другие кнопки, не добавлять в них код обработки кликов.
Этот код, расширяющий возможности нескольких кнопок, значительно устарел. Рекомендуется ucf_hookClicks.js

hookClicks дополнительные клики и подсказки кнопок

Выделить код

Код:

(async (id, func) => { // для custom_script_win.js: дополнительные клики и подсказки кнопок © Dumby, mod Dobrov
	var dsym = Symbol(), j = (...args) => args.join("\n"),
	br_val = () => { return ` ${Services.prefs.getIntPref("ucf.tabbrowser-tabpanels.opacity",100)}%`;}, br_txt =

		`Клик ролика	сброс яркости\nКрутить ±	Яркость страниц`, tooltips = {
	get "PanelUI-menu-button"() {	/* delete this["PanelUI-menu-button"]; */ return j(
		`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}`,
		`… держать	⚑ Краткая справка`,
		`…+ Alt		Персонализация`,
		`Клик дважды	⊠ закрыть браузер\n`,
		`Правый клик	⇲ Свернуть`,
		`…+ дважды	⤾ Вернуть вкладку`,
		`…+ Alt		Диспетчер задач\n`,
		`Колёсико:	Развернуть | окно`,
		`…+ Alt		Полный экран`,
		`…+ дважды	Обновить без кэша`
	);},
	[dsym]: j(GetDynamicShortcutTooltipText("downloads-button"),
		`\nДвойной клик: ⬇︎ открыть [Загрузки]`,
		`…на картинке: ⧉ найти Похожие\n`,
		`Правый клик (Alt+S):  Сохранить`,
		`   в единый html всё / выделенное`,
		`…дважды  Картинки вкл/выкл\n`,
		`Ролик:	 Сохранить как файл .txt`,
		`Колёсико на рисунке: ➜ Сохранить`
	),
	get "titlebar-button titlebar-close"() { return j(
		`Закрыть Firefox ${AppConstants.MOZ_APP_VERSION.replace(/-.*/,'')}\n`,
		`◉ колёсико	вернуть вкладку`,
		`◧ держать	краткая Справка`,
		`◨ пр. клик	⇲ Свернуть`);
	},
	get "pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_"() { return j(
		`Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n`,
		`Клик мыши	Режим для чтения`, `Колёсико	Адаптивный дизайн\nКолесико ±	Яркость сайта` + br_val());
	},
	"_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action":
		`Сохранить страницу с помощью SingleFile (Alt+S)`
	,
	"_b9db16a4-6edc-47ec-a1f4-b86292ed211d_-browser-action":
		`Video DownloadHelper\nСкачивание проигрываемого видео`
	,
	get "downloads-button"() {
		var hint = this[dsym];
		if (document.getElementById("_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action"))
			hint += "\nAlt⇧S	 ⌨ нажатие SingleSave";  //убрать/добавить
		try {var dw = Services.prefs.getComplexValue("browser.download.dir", Ci.nsIFile);}
			catch {dw = Services.dirsvc.get("DfltDwnld", Ci.nsIFile);} //отличается от ⇧
		if (dw) hint += "\n\n[Загрузки] — выбранная папка:\n" + str_cut(dw.path, 33);
		return hint;
	},
	get "identity-icon-box"() {
		var trg = window.event.target, ttt = "";
		if (!trg.id.endsWith("x")) {
			if (trg.hasAttribute("tooltiptext"))
				ttt = trg.ttt = trg.tooltipText;
			else
				ttt = trg.ttt;
			if (ttt) ttt += "\n\n";
			trg.removeAttribute("tooltiptext");
		}
		return ttt +`Правый клик	Копировать адрес в буфер\n`+ br_txt + br_val();
	},
	get "tracking-protection-icon-container"() {
		var trg = window.event?.target;
		return trg.id.endsWith("r") && trg.textContent + "\n\n" + br_txt + br_val();
	}
	}; /* end tooltips */ document.getElementById("tracking-protection-icon-container").removeAttribute("tooltip");

	var listener = { // дополнительные клики кнопок и перехват существующих
		filter(sel) {
			return this.closest(sel);
		},
		find(sel) {
			return data[sel][this] || data[sel][this + 1];
		},
		handleEvent(e) {
			if (this.skip || e.detail > 2) return;

			var trg = e.target;
			var sels = this.selectors.filter(this.filter, trg);
			var {length} = sels;
			if (!length) return;

			var dbl = e.detail == 2;
			var wh = e.type.startsWith("w");

			var num = e.metaKey *64 + e.ctrlKey *32 + e.shiftKey *16 + e.altKey *8 + (wh ? 2 : e.button *128 + dbl *4);

			var obj = data[
				length > 1 && sels.find(this.find, num) || sels[0]
			];
// wheel
			if (wh) return obj[num]?.(trg, e.deltaY < 0);
// mousedown
			if (e.type.startsWith("m")) {
				obj.mousedownTarget && this.stop(e);
				if (dbl) return;

				this.longPress = false;
				if (++num in obj)
					this.mousedownTID = setTimeout(this.onLongPress, 640, trg, obj, num);
				if (e.button == 2)
					this.ctx = trg.getAttribute("context"),
					trg.setAttribute("context", "");
				return;
			}
// click
			obj.mousedownTarget || this.stop(e);
			if (this.longPress) return this.longPress = false;
			dbl
				? this.clickTID &&= clearTimeout(this.clickTID)
				: this.mousedownTID &&= clearTimeout(this.mousedownTID);

			if (!obj[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "dispatch",
					this.mdt = obj.mousedownTarget;
				obj = this;
			}
			dbl
				? obj[num](trg)
				: this.clickTID = setTimeout(this.exec, 300, trg, obj, num);
		},
		get selectors() {
			this.exec = (trg, obj, num) => {
				this.clickTID = null;
				obj[num](trg);
			}
			this.onLongPress = (trg, obj, num) => {
				this.mousedownTID = null;
				this.longPress = true;
				obj[num](trg);
			}
			delete this.selectors;
			return this.selectors = Object.keys(data);
		},
		get mdEvent() {
			delete this.mdEvent;
			return this.mdEvent = new MouseEvent("mousedown", {bubbles: true});
		},
		context(trg) {
			this.ctx
				? trg.setAttribute("context", this.ctx)
				: trg.removeAttribute("context");
			trg.dispatchEvent(new MouseEvent("contextmenu", this.a));
		},
		dispatch(trg) {
			this.skip = true;
			this.mdt ? trg.dispatchEvent(this.mdEvent) : trg.click();
			this.skip = false;
		},
		stop: e => {
			e.preventDefault();
			e.stopImmediatePropagation();
		},
		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0}
	};
	var onMouseenter = e => {
		var trg = e.target, id = trg.id || trg.className;
		console.log('id= «'+ id + '» '+ Math.random());
		var hint = tooltips[id] || tooltips[(trg = trg.parentNode).id];
		if (hint) trg.tooltipText = hint;
	}
	var keydown_win = e => { // нажатие клавиш
		if (e.keyCode == 83 && e.altKey) { // Alt+S [+Shift]
			var singlesave = document.getElementById('_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action');
			e.shiftKey ? singlesave ? singlesave.click() : save() : save(); // имитировать клик по кнопке, используя её ID
		}
		if (e.keyCode == 88 && e.altKey){ // Alt+X отладка внешнего JS-кода
			// e.target.ownerDocument.getElementById("key_browserConsole").doCommand();
			eval(Cu.readUTF8URI(Services.io.newURI("chrome://user_chrome_files/content/custom_scripts/User.js")));
			console.log("[END] User.js " + Math.random());
		}
	}
	var root = document.getElementById("navigator-toolbox");
	var events = ["click", "mousedown", "wheel"];
	root.addEventListener("mouseenter", onMouseenter, true);
	for(var type of events) root.addEventListener(type, listener, true);
	window.addEventListener("keydown", keydown_win);
	ucf_custom_script_win.unloadlisteners.push(id);

	ucf_custom_script_win[id] = {destructor() {
		root.removeEventListener("mouseenter", onMouseenter, true);
		for(var type of events) root.removeEventListener(type, listener, true);
		window.removeEventListener("keydown", keydown_win);
	}};
	addDestructor = nextDestructor => {
		var {destructor} = ucf_custom_script_win[id];
		ucf_custom_script_win[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	}; // end Hooks

	var {prefs, dirsvc} = Services, getIntPref = (p) => prefs.getIntPref(p, 100),
	c = msg => Services.console.logStringMessage("[HC] "+ msg), // отладка
	sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService),
	my_br = "ucf.tabbrowser-tabpanels.opacity", // яркость страниц
	css = `@-moz-document url(chrome://browser/content/browser.xhtml) {
		:is(${id})[rst] {filter: grayscale(1%) !important;}
		:root:not([chromehidden*=toolbar]) #tabbrowser-tabbox {background-color: black !important;}
		:root:not([chromehidden*=toolbar]) #tabbrowser-tabpanels {opacity:${getIntPref(my_br)/100} !important;}}`,
	subst = "ucf-tabbrowser-tabpanels-opacity-style", url = `resource://${subst}/`;
	Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler)
		.setSubstitution(subst, Services.io.newURI("data:text/css," + encodeURIComponent(css)));
	sss.loadAndRegisterSheet(Services.io.newURI(url), sss.USER_SHEET);
	var st = InspectorUtils.getAllStyleSheets(document).find(s => s.href == url).cssRules[0].cssRules[2].style;
	var observer = () => st.setProperty("opacity", getIntPref(my_br)/100, "important");
	prefs.addObserver(my_br, observer);
	this.removePrefObs = () => prefs.removeObserver(my_br, observer); // end яркость

	if (typeof IOUtils != "object") { // Firefox 78 ESR
		var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
		var PathUtils = {join: (...args) => OS.Path.join(...args)};
		var IOUtils = {writeUTF8: (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt))};
	};
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	str_cut = (s, cut = 33) => { // сократить/разбить строку
		return s.substring(0,cut) + `${s.length > cut - 1 ? `…\n…${s.substring(s.length -cut + 2, s.length)}`: ''}`;
	},
	url_color = (color = "rgba(240,176,0,0.5)", ms = 300) => { // строка адреса мигает
		var u_alert = document.getElementById("urlbar-input-container");
		u_alert.style.background = color; setTimeout(() => u_alert.style.background = "", ms);
	},
	gClipboard = {
		get ch() { delete this.ch;
			return this.ch = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
		},
		write(str) { this.ch.copyStringToClipboard(str, Services.clipboard.kGlobalClipboard);}
	},
	switchToTab = (url, but = window) => { // открыть вкладку | закрыть, если открыта
		for(var tab of but.ownerGlobal.gBrowser.tabs)
			if (tab.linkedBrowser.currentURI.spec == url) {but.ownerGlobal.gBrowser.removeTab(tab); return;}; // закрыть
		but.ownerGlobal.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
	},
	showInStatusPanel = (info, time = 5000) => {
		StatusPanel = window.StatusPanel;
		if (StatusPanel.update.tid)
			clearTimeout(StatusPanel.update.tid)
		else {
			var {update} = StatusPanel;
			StatusPanel.update = () => {};
			StatusPanel.update.ret = () => {
				StatusPanel.update = update;
				StatusPanel.update();
			}
		}
		StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, time);
		StatusPanel._label = info;
	},
	Title = (max, title) => { // получить заголовок. без обрезки: max не указан, домен: max <0, + дата: max=0
		if (!title) var title = document.title || gBrowser.selectedTab.label;
		if (max == undefined) return title; // заголовок как есть или ограничить длину, убрать служебные символы
		title = title.replace(/[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim();
		if ( max > 0 ) return title.slice(0, max);
		if ( max == 0) return title.slice(0, 100) +"_"+ new Date().toLocaleDateString('ru', {day: 'numeric', month: 'numeric', year: '2-digit'}) +'-'+ new Date().toLocaleTimeString().replace(/:/g, "։");
		var host = decodeURIComponent(gURLBar.value); // max < 0
		if (!/^file:\/\//.test(host)) host = host.replace(/^.*url=|https?:\/\/|www\.|\/.*/g,'');
		return host.replace(/^ru\.|^m\.|forum\./,'').replace(/^club\.dns/,'dns');
	},
	saveSelectionToTxt = async () => { // сохранить выделенный/весь текст страницы как .txt
		var msgName = id + ":Save:GetSelection", splice = saveURL.length == 10;
		var receiver = msg => {
			var args = ["data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + "\n\n" + msg.data),
				Title(0) + '.txt', null, false, true, null, window.document];
			splice && args.splice(5, 0, null);
			saveURL(...args); showInStatusPanel("√ текст сохранён: "+ Title(0).slice(0, 60));
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));
		var func = fm => {
			var res, fed, win = {}, fe = fm.getFocusedElementForWindow(content, true, win);
			var sel = (win = win.value).getSelection();
			if (sel.isCollapsed) {
				var ed = fe && fe.editor;
				if (ed && ed instanceof Ci.nsIEditor)
					sel = ed.selection, fed = fe;
			}
			if (sel.isCollapsed)
				fed && fed.blur(), docShell.doCommand("cmd_selectAll"),
				res = win.getSelection().toString(), docShell.doCommand("cmd_selectNone"),
				fed && fed.focus();
			res = res || sel.toString();
			/\S/.test(res) && sendAsyncMessage("saveSelectionToTxt", res);
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})`.replace("saveSelectionToTxt", msgName)) + '(Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager));';
		(saveSelectionToTxt = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	},
	save = async () => { // SingleHtml by Лекс, правка: Dumby, Dobrov
		var msgName = id + "ucfDwnldsBtnSaveSnapshotToHTML";
		var write = IOUtils.writeUTF8 ? "writeUTF8" : "writeAtomicUTF8";
		var msgListener = async msg => {
			var [fileContent, fileName] = msg.data, dir; // fileName: выделенный текст или null
			try {dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}
			var arr = prefs.getStringPref("ucf_save.dirs","_Web||_Images|0").split('|').slice(0,2); //subdir: title|host
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // ucf_save.dirs = "_Web||_Pics|1" HTML сохранится в папку [Загрузки]/_Web/label
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать, если не существует…
			var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
			file.initWithPath(dir.path);
			if (!fileName) fileName = Title(100); // убрать служебные символы
			dir.append(Title(0, fileName) +'.html');
			await IOUtils[write](dir.path, fileContent) && showInStatusPanel("√ страница записана: " + fileName.slice(0, 60));
			var d = await Downloads.createDownload({ source: "about:blank", target: FileUtils.File(dir.path)}); // Fake download
			(await Downloads.getList(Downloads.ALL)).add(d);
			d.refresh(d.succeeded = true); // кнопка Загрузки мигает
		}
		messageManager.addMessageListener(msgName, msgListener);
		addDestructor(() => messageManager.removeMessageListener(msgName, msgListener));

		var svc = 'globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services';
		var url = "data:;charset=utf8," + encodeURIComponent(`(${func})(${svc});`.replace("%MSG_NAME%", msgName));
		(save = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	},
	bright = (trg, forward, val) => { // wheel
		if (!val) var val = getIntPref(my_br) + (forward ? 5 : -5);
		val = val > 100 ? 100 : val < 20 ? 20 : val;
		prefs.setIntPref(my_br, val), trg.toggleAttribute("rst"), showInStatusPanel("☀ Яркость страниц: "+ val +"%");
	},
	help = (btn) => { // встроенная справка
		var help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'];
		var newURI = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).convertChromeURL(Services.io.newURI(help_ucf[0])); // .spec = file:///
		(newURI.QueryInterface(Ci.nsIFileURL).file.exists()) ? switchToTab(help_ucf[0]) : switchToTab(help_ucf[1]);
	},
	GetSelection = (mM = gBrowser.selectedBrowser.messageManager) => {
		mM.addMessageListener('getSelect', function sel_listener(msg) {
			window.seltxt = msg.data;
			mM.removeMessageListener('getSelect', sel_listener, true);
		});
		mM.loadFrameScript('data:,sendAsyncMessage("getSelect",content.document.getSelection().toString())',false);
	},
	data = {
		"#downloads-button": { mousedownTarget: true,

			4() { // Double Left Click - Обзор папки «Загрузки»
				c("DW Double Left Click");
				Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError);
			},
			128() { saveSelectionToTxt();}, // СКМ Click (сохранить .txt)
			256() { save();}, // ПКМ Click (Single HTML)
			260(btn) { // Double ПКМ Click
				var pref = "permissions.default.image";
				var one = prefs.getIntPref(pref) == 1;
				prefs.setIntPref(pref, one ? 2 : 1);
				btn.style.filter = one ? "hue-rotate(180deg) brightness(95%)" : "";
				BrowserReload();
			},
		},
		"#PanelUI-menu-button": { mousedownTarget: true,

			1(btn) { help(btn);}, // Long Press
			8() { gCustomizeMode.enter();}, //ЛКМ + Alt Персонализация
			16(btn) { help(btn);}, // Shift + ЛКМ
			4() { goQuitApplication();}, // Double Left Click
			128() { windowState != STATE_MAXIMIZED ? maximize() : restore();}, // СКМ
			136(btn) { BrowserFullScreen();}, // Alt + СКМ
			132() { BrowserReloadSkipCache();}, // СКМ Double
			256() { minimize();}, // ПКМ
			260(btn) { btn.ownerGlobal.undoCloseTab();}, // ПКМ Double Right Click
			264(btn) { switchToTab('about:performance');}, // Alt + ПКМ
			280(btn) { // Shift + Alt + ПКМ
				var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_devtools_remotedebugging");
				(this[280] = target => obj.oncommand({target}))(btn); // запуск пункта меню, у которого нет HotKey
			},
		},
		"#pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_": { // Reader View Button
			128(btn) { // СКМ
				btn.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // запуск пункта меню с HotKey
				if (gBrowser.selectedBrowser.browsingContext.inRDMPane) BrowserReload();
			},
			2(trg, forward) { bright(trg, forward);}, // яркость по wheel ±
			264(btn) { // Alt + ПКМ
				translate(gBrowser.selectedBrowser.messageManager, 1);
			},
			1(btn) { // Shift + ПКМ
				translate(gBrowser.selectedBrowser.messageManager);
			},
		},
		"#star-button-box": {
			1() { // Left Long Press
				c("Star Left Long Press");
			},
			2(trg, forward) { bright(trg, forward);}, // яркость по wheel ±
			// 128(btn) { // СКМ
			// 	switchToTab('about:config');
			// },
			256() { // ПКМ
				window.undoCloseTab();
			},
		},
		"#identity-box": { // Замок
			2(trg, forward) { bright(trg, forward);}, // яркость по wheel ±
			128(trg, forward) { bright(trg, forward, 100);}, // СКМ
			256(btn) { // ПКМ
				gClipboard.write(gURLBar.value);
				url_color(), showInStatusPanel("в буфере: "+ gURLBar.value.slice(0, 80));
			},
		},
		"#tracking-protection-icon-container": { // Защита
			2(trg, forward) { bright(trg, forward);}, // яркость по wheel ±
			128(trg, forward) { bright(trg, forward, 100);}, // СКМ
		},
		"#identity-permission-box": {
			2(trg, forward) { // wheel
				c("Identity Permisson Box Wheel " + (forward ? "forward" : "backward"));
			}
		},
		"#identity-icon-box": {
			16() { // Shift + Left Click
				c("Identity Icon Box Shift + Left Click");
			},
		},
	}; // end Clicks, HotKeys ==================================================


})("hookClicks-and-tooltips", ({io, focus}) => { // SingleHTML не сохраняет svg графику

	var resolveURL = function (url, base) {
		try { return io.newURI(url, null, io.newURI(base)).spec;} catch {}
	},
	getSelWin = function (w) {
		if (w.getSelection().toString()) return w;
		for (var i = 0, f, r; f = w.frames[i]; i++) {
			try { if (r = getSelWin(f)) return r;} catch(e) {}
		}
	},
	encodeImg = function (src, obj) {
		var canvas, img, ret = src;
		if (/^https?:\/\//.test(src)) {
			canvas = doc.createElement('canvas');
			if (!obj || obj.nodeName.toLowerCase() != 'img') {
				img = doc.createElement('img');
				img.src = src;
			} else
				img = obj;
			if (img.complete) try{
				canvas.width = img.width;
				canvas.height = img.height;
				canvas.getContext('2d').drawImage(img, 0, 0);
				ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png'));
			} catch (e) {};
			if (img != obj) img.src = 'about:blank';
		};
		return ret;
	},
	toSrc = function (obj) {
		var strToSrc = function (str) {
			var chr, ret = '', i = 0, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\x22' : '\\\x22', '\\': '\\\\'};
			while (chr = str.charAt(i++)) {
				ret += meta[chr] || chr;
			};
			return '\x22' + ret + '\x22';
		},
		arrToSrc = function (arr) {
			var ret = [];
			for (var i = 0; i < arr.length; i++) {
				ret[i] = toSrc(arr[i]) || 'null';
			};
			return '[' + ret.join(',') + ']';
		},
		objToSrc = function (obj) {
			var val, ret = [];
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val);
			};
			return '{' + ret.join(',') + '}';
		};
		switch (Object.prototype.toString.call(obj).slice(8, -1)) {
			case 'Array': return arrToSrc(obj);
			case 'Boolean':
			case 'Function':
			case 'RegExp': return obj.toString();
			case 'Date': return 'new Date(' + obj.getTime() + ')';
			case 'Math': return 'Math';
			case 'Number': return isFinite(obj) ? String(obj) : 'null';
			case 'Object': return objToSrc(obj);
			case 'String': return strToSrc(obj);
			default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null';
		}
	},
	mainWin = {};
	focus.getFocusedElementForWindow(content, true, mainWin);
	mainWin = mainWin.value;

	var selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location;
	var ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g;

	if (selWin) {
		var rng = win.getSelection().getRangeAt(0);
		pEle = rng.commonAncestorContainer;
		ele = rng.cloneContents();
	} else {
		pEle = doc.documentElement;
		ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true);
	};
	while (pEle) {
		if (pEle.nodeType == 1) {
			clone = pEle.cloneNode(false);
			clone.appendChild(ele);
			ele = clone;
		};
		pEle = pEle.parentNode
	};
	var sel = doc.createElement('div');
	sel.appendChild(ele);

	for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) {
		el = all[i];
		if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) {
			if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href);
			return prev + encodeImg(url) + next;
		});
		switch (el.nodeName.toLowerCase()) {
			case 'link':
			case 'style':
			case 'script': el.parentNode.removeChild(el); break;
			case 'a':
			case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break;
			case 'img':
			case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break;
			case 'audio':
			case 'video':
			case 'embed':
			case 'frame':
			case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break;
			case 'object': if (el.hasAttribute('data')) el.data = el.data; break;
			case 'form': if (el.hasAttribute('action')) el.action = el.action; break;
		}
	};
	var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets, title = doc.getElementsByTagName('title')[0];
	meta.httpEquiv = 'content-type';
	meta.content = 'text/html; charset=utf-8';
	head.appendChild(meta);
	if (title) head.appendChild(title.cloneNode(true));

	head.copyScript = function (unsafeWin) {
		if ('$' in unsafeWin) return;
		var f = doc.createElement('iframe');
		f.src = 'about:blank';
		f.setAttribute('style', 'position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');
		doc.documentElement.appendChild(f);
		var str, script = doc.createElement('script');
		script.type = 'text/javascript';
		for (var name in unsafeWin) {
			if (name in f.contentWindow || !/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) continue;
			try {
				str = toSrc(unsafeWin[name]);
				if (!/\{\s*\[native code\]\s*\}/.test(str)) {
					script.appendChild(doc.createTextNode('var ' + name + ' = ' + str.replace(/<\/(script>)/ig, '<\\/$1') + ';\n'));
				}
			} catch (e) {};
		};
		f.parentNode.removeChild(f);
		if (script.childNodes.length) this.nextSibling.appendChild(script);
	};
	head.copyScript(win.wrappedJSObject || win);

	head.copyStyle = function (s) {
		if (!s) return;
		var style = doc.createElement('style');
		style.type = 'text/css';
		if (s.media && s.media.mediaText) style.media = s.media.mediaText;
		try {
			for (var i = 0, rule; rule = s.cssRules[i]; i++) {
				if (rule.type != 3) {
					if((!rule.selectorText || rule.selectorText.indexOf(':') != -1) || (!sel.querySelector || sel.querySelector(rule.selectorText))) {
						var css = !rule.cssText ? '' : rule.cssText.replace(reUrl, function (a, prev, url, next) {
							if (!/^[a-z]+:/.test(url)) url = resolveURL(url, s.href || loc.href);
							if(rule.type == 1 && rule.style && rule.style.backgroundImage) url = encodeImg(url);
							return prev + url + next;
						});
						style.appendChild(doc.createTextNode(css + '\n'));
					}
				} else {
					this.copyStyle(rule.styleSheet);
				}
			}
		} catch(e) {
			if (s.ownerNode) style = s.ownerNode.cloneNode(false);
		};
		this.appendChild(style);
	};
	for (var j = 0; j < sheets.length; j++) head.copyStyle(sheets[j]);
	head.appendChild(doc.createTextNode('\n'));
	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};
	sendAsyncMessage("%MSG_NAME%", [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', selWin ? win.getSelection().toString().slice(0, 200) : undefined]); // выделенный текст
}); // END hookClicks

egorsemenov06 пишет

JSON.parse

/*JSON.parse*/

воможно ли с помощью скрипта https://forum.mozilla-russia.org/viewto … 54#p782454 открывать ссылки и страницы в tor browser? после последнего обновления тор открывается через скрипт, но соединения нет

Dumby
Хочу на базе этого скрипта сделать ещё один, но чтоб он закрывал текущую вкладку, заменить им хочу расширение.
Подскажите пожалуйста, может где подправить можно по мелочи?
Сейчас мой код закрытия "других вкладок" стал немного другим, иконку ещё более подходящую нашёл

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

Выделить код

Код:

CustomizableUI.createWidget({
	id: "Close-Tabs-button",
	label: "Закрыть другие вкладки",
	tooltiptext: "Закрыть другие вкладки",
	defaultArea: CustomizableUI.AREA_NAVBAR,
	localized: false,
	onCreated(btn) {
		btn.render = this.render;
		btn._handleClick = this.close;
		btn.setAttribute("image", "chrome://devtools/skin/images/close.svg");
	},
	render() {
		delete this.render;
		this.render();
		this.icon.style.setProperty("padding", "3px", "important");
	},
	close() {
		var gb = this.ownerGlobal.gBrowser;
		gb.removeAllTabsBut(gb.selectedTab);
	}
});

По-моему, строку gb.removeAllTabsBut(gb.selectedTab); нужно изменить, но вот как...

sandro79 пишет

По-моему, строку gb.removeAllTabsBut(gb.selectedTab); нужно изменить, но вот как...

Верно, эту строку. Как? Ну, обычно,
следует просто посмотреть как это делает сам браузер, и срисовать себе.
Если как пункт контекстного меню вкладок «Закрыть (N) вклад(ку|ки|ок)»,
то получится что-то типа

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

Выделить код

Код:

...
		//gb.removeAllTabsBut(gb.selectedTab);
		var tab = gb.selectedTab;
		tab.multiselected
			? gb.removeMultiSelectedTabs()
			: gb.removeTab(tab, {animate: true});


Но там учитывается вариант несколько-выделенных вкладок.
Если нужно это игнорировать, то есть понимать вопрос
строго буквально «чтоб он закрывал текущую вкладку» и никак иначе,
то, в простейшем случае, можно заменить на
gb.removeTab(gb.selectedTab, {animate: true});

Dumby пишет

следует просто посмотреть как это делает сам браузер, и срисовать себе

С этим я пока не разобрался, хотя не помешало бы такую мелочь самому вычислить.

строго буквально «чтоб он закрывал текущую вкладку» и никак иначе

Да, как раз так и хотел, пункт "Выбрать все вкладки" не использую, и он у меня скрыт стилем. Скрипт собрал, всё работает

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

Выделить код

Код:

CustomizableUI.createWidget({
	id: "Close-Tab",
	label: "Закрыть вкладку",
	tooltiptext: "Закрыть вкладку",
	defaultArea: CustomizableUI.AREA_NAVBAR,
	localized: false,
	onCreated(btn) {
		btn.render = this.render;
		btn._handleClick = this.close;
		btn.setAttribute("image", "chrome://devtools/skin/images/close.svg");
	},
	render() {
		delete this.render;
		this.render();
		this.icon.style.setProperty("padding", "3px", "important");
	},
	close() {
		var gb = this.ownerGlobal.gBrowser;
		gb.removeTab(gb.selectedTab, {animate: true});
	}
});

Благодарю за помощь! :beer:

Dumby
Кнопку  "Закрыть другие вкладки" №82 куда лучше подключить, а то она у меня на [firefox] 95 никак не хочет работать?

У меня кнопка "Закрыть вкладки..." такая, может кому пригодится. Кто-то помогал сделать, закреплённые вкладки, когда удаление слева или другие не удаляет. Я бы сам так не осилил. Работает, ucf у меня старый.

custom_script.js

Выделить код

Код:

// Этот скрипт можно использовать для создания кнопок с помощью CustomizableUI.createWidget
(() => {
    var loadscript = name => {
        try {
            Services.scriptloader.loadSubScript(`chrome://user_chrome_files/content/custom_scripts/${name}`, globalThis, "UTF-8");
        } catch(e) {}
    };
    loadscript("my_buttons.js");
})();


my_buttons.js

Выделить код

Код:

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
var {console} = Cu.import("resource://gre/modules/Console.jsm", {});
try {
    CustomizableUI.createWidget({
        id: "add-select-close-tabs-app",
        type: "custom",
        tooltiptext: [
            "ЛКМ: Закрыть все вкладки",
            "Shift+ЛКМ: Закрыть другие вкладки",
            "Ctrl+ЛКМ: Закрыть слева",
            "Alt+ЛКМ: Закрыть справа",
        ].join("\n"),
        onBuild: function(document) {
            var toolbarbutton_0 = document.createXULElement("toolbarbutton");
            toolbarbutton_0.id = this.id;
            toolbarbutton_0.tooltipText = this.tooltiptext;
            toolbarbutton_0.label = "Закрыть все вкладки";
            toolbarbutton_0.setAttribute("context", false);
            toolbarbutton_0.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPYxg8wD5wy1y7gK1/7AO3/ncI2nq7uOnk3cq20//BuOP07b1Hnt09eurl/6OnXvwB0rOh2hAAphmGU0uOQDRD8ZrN94CaQQZADIFqQwBkzSAcnLTnVkLewWMgnFZy6Ep9z9kr3dMuHoNhqDYEQDeAEIZqQ4Dd937/JwVDtSEANkX4MFQbAmBThA9DtSEANkXIeM/t7/8vz1v9/1590//rE2f8/+pkIQ3VCgHYNMEwSPOLtOT/X1ws/n9xNvv/xRVIO5l9+OxsoQvVjt+AS/PXgDX/vXnt/4/q4v9/b1z9/y0tGmiY6S6odvwGgJwNshmk+f+fP/9/790JcQnQFVDt+A0A+RnsAqDNIM3/Prz//y0lEmTAZah2/OCrvZkM0LnvvqXFgG3+lgzUDAoHZ9N4qBLC4LOriQ7Qxt0gZwMNuQLRzMAAAAEG9BCOlZEaAAAAAElFTkSuQmCC");
            toolbarbutton_0.addEventListener("click", function(event) {
                var win = event.target.ownerDocument.defaultView;
                if (event.button == 0) {
                    if (event.shiftKey) {
                        win.gBrowser.removeAllTabsBut(win.gBrowser.selectedTab);
                    } else if (event.ctrlKey) {
                        var ctab = win.gBrowser.selectedTab, tabs;
                        if (ctab.multiselected)
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned);
                        else
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.pinned);
                        var index = tabs.indexOf(ctab);
                        tabs = tabs.slice(0, (index != -1) ? index : tabs.length);
                        tabs.forEach((tab) => {
                            win.gBrowser.removeTab(tab);
                        });
                    } else if (event.altKey) {
                        var ctab = win.gBrowser.selectedTab, tabs;
                        if (ctab.multiselected)
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned);
                        else
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.pinned);
                        var index = tabs.indexOf(ctab);
                        tabs = tabs.slice((index != -1) ? (index + 1) : 0, tabs.length);
                        tabs.forEach((tab) => {
                            win.gBrowser.removeTab(tab);
                        });
                    }
                else {
                    win.gBrowser.selectAllTabs();
                    win.gBrowser.removeMultiSelectedTabs();
                    }
                }
            }, false);
            toolbarbutton_0.classList.add("toolbarbutton-1");
            toolbarbutton_0.classList.add("chromeclass-toolbar-additional");
            return toolbarbutton_0;
        }
    });
} catch(e) {}


И ещё мне 2 кнопки сделал Vitaliy V. здесь.

voqabuhe пишет

куда лучше подключить

Что значит лучше?
Либо добавить в custom_script.js или подобный, либо отдельным файлом.
Придумываешь название, создаёшь, и прописываешь в CustomStylesScripts.jsm
(это если использовать встроенный загрузчик)

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

Выделить код

Код:

var UcfStylesScripts = {
    /** ************************▼ Настройки ▼************************ */

    .......

    scriptsbackground: [ // В фоне [System Principal]
        .......

        { path: "closeOtherTabs.js" },
    ],
    /** ************************▲ Настройки ▲************************ */
};

никак не хочет работать

Что значит не хочет работать?
Открывает вкладки вместо того, чтобы закрывать?


id'шник проверь, чтоб уникальный был.
Иконки, кстати, такой как там у тебя может не быть, пропиши свою.
Кэш ещё может залипнуть, закрой браузер и удали папку startupCache руками.

xrun1 пишет

my_buttons.js

Выделить код

Код:

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
var {console} = Cu.import("resource://gre/modules/Console.jsm", {});
try {
    CustomizableUI.createWidget({
        id: "add-select-close-tabs-app",
        type: "custom",
        tooltiptext: [
            "ЛКМ: Закрыть все вкладки",
            "Shift+ЛКМ: Закрыть другие вкладки",
            "Ctrl+ЛКМ: Закрыть слева",
            "Alt+ЛКМ: Закрыть справа",
        ].join("\n"),
        onBuild: function(document) {
            var toolbarbutton_0 = document.createXULElement("toolbarbutton");
            toolbarbutton_0.id = this.id;
            toolbarbutton_0.tooltipText = this.tooltiptext;
            toolbarbutton_0.label = "Закрыть все вкладки";
            toolbarbutton_0.setAttribute("context", false);
            toolbarbutton_0.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPYxg8wD5wy1y7gK1/7AO3/ncI2nq7uOnk3cq20//BuOP07b1Hnt09eurl/6OnXvwB0rOh2hAAphmGU0uOQDRD8ZrN94CaQQZADIFqQwBkzSAcnLTnVkLewWMgnFZy6Ep9z9kr3dMuHoNhqDYEQDeAEIZqQ4Dd937/JwVDtSEANkX4MFQbAmBThA9DtSEANkXIeM/t7/8vz1v9/1590//rE2f8/+pkIQ3VCgHYNMEwSPOLtOT/X1ws/n9xNvv/xRVIO5l9+OxsoQvVjt+AS/PXgDX/vXnt/4/q4v9/b1z9/y0tGmiY6S6odvwGgJwNshmk+f+fP/9/790JcQnQFVDt+A0A+RnsAqDNIM3/Prz//y0lEmTAZah2/OCrvZkM0LnvvqXFgG3+lgzUDAoHZ9N4qBLC4LOriQ7Qxt0gZwMNuQLRzMAAAAEG9BCOlZEaAAAAAElFTkSuQmCC");
            toolbarbutton_0.addEventListener("click", function(event) {
                var win = event.target.ownerDocument.defaultView;
                if (event.button == 0) {
                    if (event.shiftKey) {
                        win.gBrowser.removeAllTabsBut(win.gBrowser.selectedTab);
                    } else if (event.ctrlKey) {
                        var ctab = win.gBrowser.selectedTab, tabs;
                        if (ctab.multiselected)
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned);
                        else
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.pinned);
                        var index = tabs.indexOf(ctab);
                        tabs = tabs.slice(0, (index != -1) ? index : tabs.length);
                        tabs.forEach((tab) => {
                            win.gBrowser.removeTab(tab);
                        });
                    } else if (event.altKey) {
                        var ctab = win.gBrowser.selectedTab, tabs;
                        if (ctab.multiselected)
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned);
                        else
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.pinned);
                        var index = tabs.indexOf(ctab);
                        tabs = tabs.slice((index != -1) ? (index + 1) : 0, tabs.length);
                        tabs.forEach((tab) => {
                            win.gBrowser.removeTab(tab);
                        });
                    }
                else {
                    win.gBrowser.selectAllTabs();
                    win.gBrowser.removeMultiSelectedTabs();
                    }
                }
            }, false);
            toolbarbutton_0.classList.add("toolbarbutton-1");
            toolbarbutton_0.classList.add("chromeclass-toolbar-additional");
            return toolbarbutton_0;
        }
    });
} catch(e) {}

Интересная кнопочка.
Только немешало бы в неё добавить "защиту от дурака" на все действия - подтверждение на закрытие вкладок.


Что-то типа такого:

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

untitled-4.png
untitled-2.png
untitled-3.png

xrun1
Спасибо за кнопку"показа/скрытия панели закладок и доп.панели".

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

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

Выделить код

Код:

try {
    CustomizableUI.createWidget({
        id: "additional-toolbars-button",
        type: "custom",
        label: "Доп. панели",
        tooltiptext: [
            "ЛКМ: Переключить верт. панель",
            "ПКМ: Переключить доп. панель"
        ].join("\n"),
        localized: false,
        onBuild(doc) {
            var trbn = doc.createXULElement("toolbarbutton");
            trbn.id = this.id;
            trbn.tooltipText = this.tooltiptext;
            trbn.label = this.label;
            trbn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
            trbn.setAttribute("context", false);
            trbn.setAttribute("image", "chrome://user_chrome_files/content/custom_styles/svg/layer-visible-off.svg");
            trbn.addEventListener("click", function(e) {
                var pref = "browser.add.toolbars.visibility";
                if (e.button == 0) {
                    e.preventDefault();
                    e.stopPropagation();
                    CustomizableUI.setToolbarVisibility("ucf-additional-vertical-bar", doc.querySelector("#ucf-additional-vertical-bar").collapsed);
                } else if (e.button == 2) {
                    e.preventDefault();
                    e.stopPropagation();
                    CustomizableUI.setToolbarVisibility("ucf-additional-top-bar", doc.querySelector("#ucf-additional-top-bar").collapsed);
                }
            }, false);
            return trbn;
        },
    });
} catch(e) {}

unter_officer
Для меня такое сделать нереально. :) Не представляю, как в кнопку прикрутить confirm()... Да и не нужно. Кнопка "Восстановить" из расширения ATB Vitaliy V. восстанавливает все закрытые вкладки моей кнопкой. Если сразу озаботиться, конечно.
kokoss
Я ещё для боковой панели менял кнопку "Закладки" для экономии места, вдруг пригодится.

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

Заменить в user_chrome.js кнопку с id: "add-view-bookmarks-sidebar-button". Ещё раз повторюсь, ucf у меня старый.

Выделить код

Код:

try {
                CustomizableUI.createWidget({
                    id: "add-view-bookmarks-sidebar-button",
                    type: "custom",
                    label: "Закладки Библиотека История",
                    tooltiptext: "ЛКМ: Показать / Скрыть Закладки\nСКМ: Открыть Библиотеки в табе\nПКМ: Показать / Скрыть Историю",
                    localized: false,
                    onBuild: function(doc) {
                        var win = doc.defaultView;
                        var trbn_0 = doc.createElementNS(ns_xul, "toolbarbutton");
                        trbn_0.id = "add-view-bookmarks-sidebar-button";
                        trbn_0.className = "toolbarbutton-1 chromeclass-toolbar-additional";
                        trbn_0.setAttribute("label", "Закладки Библиотека История");
                        trbn_0.setAttribute("context", "false");
                        trbn_0.setAttribute("tooltiptext", "ЛКМ: Показать / Скрыть Закладки\nСКМ: Открыть Библиотеки в табе\nПКМ: Показать / Скрыть Историю");
                        trbn_0.addEventListener("click", function(e) {
                            if (e.button == 0) {
                                if ("SidebarUI" in win)
                                    win.SidebarUI.toggle("viewBookmarksSidebar");
                                else if ("toggleSidebar" in win)
                                    win.toggleSidebar("viewBookmarksSidebar");
                            }
                            else if (e.button == 1) {
                                var url="chrome://browser/content/places/places.xhtml";
                                win.SidebarUI.hide();
                                /* Для CB, открывает "История" в окне "Библиотека"
                                PlacesCommandHook.showPlacesOrganizer('History'); */
                                win.gBrowser.selectedTab = win.gBrowser.addTrustedTab(url);
                            }
                            else if (e.button == 2) {
                                if ("SidebarUI" in win)
                                    win.SidebarUI.toggle("viewHistorySidebar");
                                else if ("toggleSidebar" in win)
                                    win.toggleSidebar("viewHistorySidebar");
                            }
                        });
                        return trbn_0;
                    }
                });
            } catch(e) {}

_zt пишет

Посмотрите пожалуйста, я правильно отредактировал?

Не люблю я вопросы про «правильно», откуда мне знать что есть правильно.
Если работает, и страшных косяков нет (а их нет) — значит правильно.

скрытый текст
И, потом, не всегда понятен замысел, вот смотришь и думаешь,
что здесь имелось в виду? Если судить по себе, то далеко не всё пишется
с каким-то смыслом, иногда просто от балды, первое что в голову придёт.


Вот, например, в коде определяется переменная pref, но нигде не используется.
Или зачем e.preventDefault(); e.stopPropagation(); я же не знаю.
Превент может использоваться для предотвращения появления контекстного
меню при ПКМ (Windows), но там уже решено, что этого контекстного меню
не будет совсем никогда, поскольку установлен атрибут "context".


Или tooltiptext массивом. Когда там целая батарея подсказочных строк, то имеет смысл.
А парочку вполне можно и одной строкой записать. Это не вопрос правильности,
а вопрос предпочтения. Вот там widget создаётся как type: "custom", не знаю,
может это как-то внутренне оптимальнее, но чего не сделаешь, чтоб записать попроще, типа

Выделить код

Код:

(async () => CustomizableUI.createWidget({
	label: "Доп. панели",
	tooltiptext: "ЛКМ: Переключить верт. панель\nПКМ: Переключить доп. панель",

	id: "additional-toolbars-button",
	localized: false,
	onCreated(btn) {
		btn.toggleAttribute("context");
		btn.setAttribute("image", "chrome://user_chrome_files/content/custom_styles/svg/layer-visible-off.svg");
	},
	0: "ucf-additional-vertical-bar",
	2: "ucf-additional-top-bar",
	onClick(e) {
		var id = this[e.button];
		id && CustomizableUI.setToolbarVisibility(id, e.view.document.getElementById(id).collapsed);
	}
}))();

Dumby
По мне судить не надо, судите по Vitaliy V., это его код.
Я в скриптах разбираюсь на уровне  - что нибудь добавить/удалить по доступному примеру. (Сколько раз это повторить надо?) Поэтому и спросил. Спасибо за готовый код, это то что в итоге мне нужно было.

xrun1 пишет

my_buttons.js

Выделить код

Код:

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
var {console} = Cu.import("resource://gre/modules/Console.jsm", {});
try {
    CustomizableUI.createWidget({
        id: "add-select-close-tabs-app",
        type: "custom",
        tooltiptext: [
            "ЛКМ: Закрыть все вкладки",
            "Shift+ЛКМ: Закрыть другие вкладки",
            "Ctrl+ЛКМ: Закрыть слева",
            "Alt+ЛКМ: Закрыть справа",
        ].join("\n"),
        onBuild: function(document) {
            var toolbarbutton_0 = document.createXULElement("toolbarbutton");
            toolbarbutton_0.id = this.id;
            toolbarbutton_0.tooltipText = this.tooltiptext;
            toolbarbutton_0.label = "Закрыть все вкладки";
            toolbarbutton_0.setAttribute("context", false);
            toolbarbutton_0.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPYxg8wD5wy1y7gK1/7AO3/ncI2nq7uOnk3cq20//BuOP07b1Hnt09eurl/6OnXvwB0rOh2hAAphmGU0uOQDRD8ZrN94CaQQZADIFqQwBkzSAcnLTnVkLewWMgnFZy6Ep9z9kr3dMuHoNhqDYEQDeAEIZqQ4Dd937/JwVDtSEANkX4MFQbAmBThA9DtSEANkXIeM/t7/8vz1v9/1590//rE2f8/+pkIQ3VCgHYNMEwSPOLtOT/X1ws/n9xNvv/xRVIO5l9+OxsoQvVjt+AS/PXgDX/vXnt/4/q4v9/b1z9/y0tGmiY6S6odvwGgJwNshmk+f+fP/9/790JcQnQFVDt+A0A+RnsAqDNIM3/Prz//y0lEmTAZah2/OCrvZkM0LnvvqXFgG3+lgzUDAoHZ9N4qBLC4LOriQ7Qxt0gZwMNuQLRzMAAAAEG9BCOlZEaAAAAAElFTkSuQmCC");
            toolbarbutton_0.addEventListener("click", function(event) {
                var win = event.target.ownerDocument.defaultView;
                if (event.button == 0) {
                    if (event.shiftKey) {
                        win.gBrowser.removeAllTabsBut(win.gBrowser.selectedTab);
                    } else if (event.ctrlKey) {
                        var ctab = win.gBrowser.selectedTab, tabs;
                        if (ctab.multiselected)
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned);
                        else
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.pinned);
                        var index = tabs.indexOf(ctab);
                        tabs = tabs.slice(0, (index != -1) ? index : tabs.length);
                        tabs.forEach((tab) => {
                            win.gBrowser.removeTab(tab);
                        });
                    } else if (event.altKey) {
                        var ctab = win.gBrowser.selectedTab, tabs;
                        if (ctab.multiselected)
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.multiselected && !tab.pinned);
                        else
                            tabs = win.gBrowser.visibleTabs.filter(tab => !tab.pinned);
                        var index = tabs.indexOf(ctab);
                        tabs = tabs.slice((index != -1) ? (index + 1) : 0, tabs.length);
                        tabs.forEach((tab) => {
                            win.gBrowser.removeTab(tab);
                        });
                    }
                else {
                    win.gBrowser.selectAllTabs();
                    win.gBrowser.removeMultiSelectedTabs();
                    }
                }
            }, false);
            toolbarbutton_0.classList.add("toolbarbutton-1");
            toolbarbutton_0.classList.add("chromeclass-toolbar-additional");
            return toolbarbutton_0;
        }
    });
} catch(e) {}

Dumby
Возможно ли в эту кнопку, на все действия, прикрутить горячие клавиши для клавиатуры, например Shift+1, Shift+2, Shift+3, Shift+4 ?

Dumby пишет

Что значит лучше?
Либо добавить в custom_script.js или подобный, либо отдельным файлом.
Придумываешь название, создаёшь, и прописываешь в CustomStylesScripts.jsm
(это если использовать встроенный загрузчик)

Типа про это как раз и спрашивал, в какой из них лучше, в чём разница?

Что значит не хочет работать?
Открывает вкладки вместо того, чтобы закрывать?

В смысле, что она ваще не появлялась.

id'шник проверь, чтоб уникальный был.

Ага, это и было причиной, видно с чем-то совпало, после изменения id, кнопка появилась и работает. Спасибо.

xrun1 пишет

Я ещё для боковой панели менял кнопку "Закладки" для экономии места, вдруг пригодится.
скрытый текст

И как это работает? А то у меня в [firefox] 95 не работает!

unter_officer пишет

Возможно ли в эту кнопку, на все действия, прикрутить горячие клавиши для клавиатуры, например Shift+1, Shift+2, Shift+3, Shift+4 ?

Обязательно в кнопку?
Это же надо вмешиваться внутрь или цепляться снаружи.
Может сойдёт какой-нибудь аналог отдельным независимым кодом, например
(это для custom_script_win.js, и, я надеюсь, вопрос был не о Firefox 52)

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

Выделить код

Код:

(async anim => {
	var re = /^(?:Digit|Numpad)(1|2|3|4)$/;
	var funcs = {
		1: () => {
			gBrowser.selectAllTabs();
			gBrowser.removeMultiSelectedTabs();
		},
		2: t => gBrowser.removeAllTabsBut(t),
		3: t => gBrowser.removeTabsToTheStartFrom(t, anim),
		4: t => gBrowser.removeTabsToTheEndFrom(t, anim),
	};
	var args = ["keydown", e => {
		if (
			e.ctrlKey || e.altKey || !re.test(e.code) ||
			e.repeat || docShell.isCommandEnabled("cmd_insertText")
		)
			return;

		var num = RegExp.$1;
		if (e.shiftKey || e.code.startsWith("N") && e.getModifierState("NumLock") && e.key != num)
			e.preventDefault(),
			funcs[num](gBrowser.selectedTab);

	}, true];
	addEventListener(...args);
	var id = Symbol(), ucf = ucf_custom_script_win;
	ucf.unloadlisteners.push(id);
	ucf[id] = {destructor: () => removeEventListener(...args)};
})({animate: true});

kokoss пишет

у меня в [firefox] 95 не работает!

Там ns_xul не определён, вот и не работает.
И иконки нет, но это уже мелочь.

Dumby пишет

Обязательно в кнопку?
Это же надо вмешиваться внутрь или цепляться снаружи.
Может сойдёт какой-нибудь аналог отдельным независимым кодом, например
(это для custom_script_win.js, и, я надеюсь, вопрос был не о Firefox 52)

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

Выделить код

Код:

(async anim => {
	var re = /^(?:Digit|Numpad)(1|2|3|4)$/;
	var funcs = {
		1: () => {
			gBrowser.selectAllTabs();
			gBrowser.removeMultiSelectedTabs();
		},
		2: t => gBrowser.removeAllTabsBut(t),
		3: t => gBrowser.removeTabsToTheStartFrom(t, anim),
		4: t => gBrowser.removeTabsToTheEndFrom(t, anim),
	};
	var args = ["keydown", e => {
		if (
			e.ctrlKey || e.altKey || !re.test(e.code) ||
			e.repeat || docShell.isCommandEnabled("cmd_insertText")
		)
			return;

		var num = RegExp.$1;
		if (e.shiftKey || e.code.startsWith("N") && e.getModifierState("NumLock") && e.key != num)
			e.preventDefault(),
			funcs[num](gBrowser.selectedTab);

	}, true];
	addEventListener(...args);
	var id = Symbol(), ucf = ucf_custom_script_win;
	ucf.unloadlisteners.push(id);
	ucf[id] = {destructor: () => removeEventListener(...args)};
})({animate: true});

Dumby, большое спасибо!


P.S. Вопрос конечно же был не о Firefox 52. :)

Dumby пишет

Там ns_xul не определён, вот и не работает.
И иконки нет, но это уже мелочь.

Иконку то я прикрутил, только не мог понять почему у него работает, а у меня нет, теперь понятно. Спасибо за подсказку!

kokoss
Это была не отдельная кнопка, я правил файл user_chrome.js. А для него иконки уже прописаны в vertical_top_bottom_bar\vertical_top_bottom_bar.css. Из-за этого, наверное, получилась неразбериха.

_zt пишет

По мне судить не надо, судите по Vitaliy V., это его код.

Не, это мой код, он там двумя постами выше. Кнопку делал, когда только-только Vitaliy V. сделал ucf. Делал по аналогии c кнопками, которые уже были в ucf. Поэтому там и type: "custom". А e.preventDefault(); e.stopPropagation(); у меня затесались по старой памяти от CB.:)

Может кому пригодится:

кнопка для custom_script.js

Выделить код

Код:

try {
    CustomizableUI.createWidget({
        id: "add-personalization-button-app",
        type: "custom",
        tooltiptext: [
            "ЛКМ: Персонализация",
            "СКМ: about:about",
            "ПКМ: about:support"
        ].join("\n"),
        onBuild: function(document) {
            var toolbarbutton_0 = document.createXULElement("toolbarbutton");

            toolbarbutton_0.id = this.id;
            toolbarbutton_0.tooltipText = this.tooltiptext;
            toolbarbutton_0.label = "Персонализация about:about about:support";
            toolbarbutton_0.image = "chrome://browser/content/robot.ico";

            toolbarbutton_0.setAttribute("context", false);
            toolbarbutton_0.addEventListener("click", function(event) {
                var win = event.target.ownerDocument.defaultView;
                win.SidebarUI.hide();
                if (event.button == 0) {
                    win.gCustomizeMode.enter();
                }
                if (event.button == 1) {
                    win.gBrowser.selectedTab = win.gBrowser.addTrustedTab('about:about');
                }
                if (event.button == 2) {
		    win.gBrowser.selectedTab = win.gBrowser.addTrustedTab('about:support');
                }
            }, false);
            toolbarbutton_0.classList.add("toolbarbutton-1");
            toolbarbutton_0.classList.add("chromeclass-toolbar-additional");
            return toolbarbutton_0;
        }
    });
} catch(e) {}


в [firefox] 95 вроде работает, немного изменил... эту кнопку

Dumby
Если не сложно, не могли бы вы сделать пример-заготовку для подобной кнопки?
untitled-2.png


Что-то вроде нескольких кнопок в одной.


для FF 91.

Dumby
Уже порядочно долго использую Ваш скрипт для открытия новой вкладки двойным кликом на панели вкладок.
А можно ещё сделать скрипт, чтоб не происходил захват и перетаскивание окна при одиночном клике на панели вкладок?
А то бывает, как-то дрогнет что ли рука и происходит захват и сдвиг окна. Ну такая опция была и сейчас присутствует в новом Tab Mix Plus.
Очень хотелось бы вернуть эту функцию в виде скрипта

скрытый текст
Tab Mix Plus поставил, посмотрел, ну три небольших скрипта и два дополнения она мне компенсировала.
Но в обычном [firefox] нужен ещё скрипт для работы дополнения и bootstrapLoader.xpi по-любому ставить. В общем отказался, в [nightly] только пока оставил


______.PNG

sandro79 пишет

А можно ещё сделать скрипт, чтоб не происходил захват и перетаскивание окна при одиночном клике на панели вкладок?

Возможно я ошибаюсь, но вроде это можно реализовать стилем. Что-то типа такого:
ID_ПАНЕЛИ { -moz-window-dragging: no-drag !important; }

unter_officer пишет

Возможно я ошибаюсь, но вроде это можно реализовать стилем. Что-то типа такого:

Нет, Вы не ошибаетесь, действительно можно стилем. Спасибо за подсказку :beer:
Сделал стилем, добавил ещё панель закладок, работает прекрасно, вопрос закрыт

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

Выделить код

Код:

#TabsToolbar, #PlacesToolbar {
  -moz-window-dragging: no-drag !important;
}

unter_officer пишет

Если не сложно, не могли бы вы сделать пример-заготовку для подобной кнопки?

Ещё как сложно. Вопрос слишком общего характера.
Почти ничего не дано. Какой нужен уровень абстракции, какие там задачи,
что именно не получается, только гадать остаётся. Ладно, набрал простенький вариант.
Насколько далеко, и с какой стороны, это будет от желаемого — неизвестно.
Короче, лучше бы побольше конкретики.

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

Выделить код

Код:

(async () => CustomizableUI.createWidget({
	label: "Some Label",
	tooltiptext: "Some Tooltip Text",

	id: "_some_unique_identifier_",
	localized: false,
	onCreated(btn) {
		btn.setAttribute("type", "menu");
		btn.setAttribute("image", "chrome://global/skin/narrate/headphone-active.svg");

		var doc = btn.ownerDocument;
		var popup = doc.createXULElement("menupopup");
		popup.creator = this;
		popup.toggleAttribute("context");
		popup.setAttribute("oncommand", "creator.cmd(event);");
		popup.setAttribute("oncontextmenu", "hidePopup(); creator.cmd(event);");
		
		for(var item of this.data) {
			if (!item) {
				popup.append(doc.createXULElement("menuseparator"));
				continue;
			}
			var menuitem = popup.appendChild(doc.createXULElement("menuitem"));
			menuitem.linkedItem = item;
			menuitem.setAttribute("label", item.lab || "");
			item.ttt && menuitem.setAttribute("tooltiptext", item.ttt);
			if (item.img)
				menuitem.className = "menuitem-iconic",
				menuitem.setAttribute("image", item.img);
		}
		btn.prepend(popup);
	},
	cmd(e) {
		var it = e.target.linkedItem;
		it && this[it.fnc](e, it.val);
	},
	data: [
		{
			lab: "Пункт 1",
			ttt: "Бла",
			img: "chrome://devtools/skin/images/fox-smiling.svg",
			fnc: "sayBla",
		},
		{
			lab: "Пункт 2",
			ttt: "Трижды Бла",
			img: "chrome://devtools/skin/images/fox-smiling.svg",
			fnc: "sayBla",
			val: 3,
		},
		null, // <= separator
		{
			lab: "Пункт 3",
			fnc: "alertLabel",
		},
		{
			lab: "Пункт 4",
			fnc: "viewImgSource",
			img: "chrome://browser/skin/protections/resolved-breach.svg",
		},
		null,
		{
			lab: "Пункт 5",
			ttt: "Default",
			img: "chrome://browser/skin/preferences/face-sad.svg",
		},
	],
	sayBla(e, val = 1) {
		Services.prompt.alert(null, null, "Бла ".repeat(val));
	},
	alertLabel(e) {
		e.view.alert(e.target.label);
	},
	viewImgSource(e) {
		var gb = e.view.gBrowser;
		gb.selectedTab = gb.addTrustedTab(
			"view-source:" + e.target.image,
			{index: gb.selectedTab._tPos + 1}
		);
	},
	undefined(e) {
		Services.prompt.alert(null, "Method missing!", "event.button = " + e.button);
	}
}))();

Dumby пишет

Короче, лучше бы побольше конкретики.

На многих кнопках вешаются разные действия на клик мыши с модификаторами (Shift+ЛКМ, Ctrl+ПКМ и тому подобное).
Мне тяжеловато запоминать все эти варианты, особенно когда кнопок много, а с моим неважным зрением постоянно вглядываться во всплывающие подсказки тоже тяжело.


Мне проще сделать одну кнопку и добавлять в неё по мере необходимости нужные действия.
Абстрактный пример: "Пункт 1 - Закрыть все вкладки", "Пункт 2 - Восстановить закрытую вкладку", "Пункт 3 - Открыть консоль браузера" и т.д. и т.п.




P.S. Dumby, большое спасибо за кнопку.

Случайно поднял глаза и заметил. ;) В кнопке для адресной строки "Копировать ссылку" в [firefox] 95 сменился значок. Теперь такой iconURL: "chrome://global/skin/icons/link.svg"

Это глюк гугла или что-то в скрипте? Автоматический перевод страницы Ru --> En не переводит. En --> Ru работает. №8287.
Перевод выделенного в окне работает правильно.

скрытый текст
kIqRWWe.png
xJ9T6om.png
LUlmMTi.png

xrun1, здравствуйте.
   
Нужно эту строчку:

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

Выделить код

Код:

var url = "http://translate.google.com/translate?u="+encodeURIComponent(urlt)+"&hl="+lng+"&langpair="+dir+"&tbb=1";
   
заменить на:
   
var url = "https://translate.google.com/translate?sl=auto&tl=ru&u="+encodeURIComponent(urlt)+"&hl="+lng+"&langpair="+dir+"&tbb=1";

Пострел, приветствую!
Попробовал, но у меня без изменений. Английский на русский переводит. Русский на английский не переводит: русский определяется автоматически, а второй язык тоже русский.

Хотел на халяву проскочить. Не получилось, пришлось самому подумать.:D
Пострел, спасибо, что подсказал, в какую сторону копать. Теперь работает, добавил параметр языка '&tl=' + l[1]

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

Выделить код

Код:

var url = "http://translate.google.com/translate?u="+encodeURIComponent(urlt)+"&hl="+lng+"&langpair="+dir+"&tbb=1";
   
заменить на:

var url = "http://translate.google.com/translate?u=" + encodeURIComponent(urlt) + '&tl=' + l[1] + "&hl=" + lng + "&langpair=" + dir + "&tbb=1";

xrun1,
   
Да за, что же спасибо. Пурги вам намёл под спойлер, небрежно прочитав ваш пост.
Просто удача, что вы разобрались и нашли хорошее решение.
Вместо помощи, сам воспользовался вашим исправлением. Спасибо.

Dumby
Не подскажите, как в моём примере добавить возможность вставлять "menuseparator" в тех местах, где мне это понадобится?
Что-то не могу сообразить, как это сделать.

Пример

Выделить код

Код:

try { (this.contextmenubookmark = {
	init(that) {
		var contextMenu = this.contextMenu = document.querySelector("#placesContext");
		if (!contextMenu) return;
		contextMenu.addEventListener("popupshowing", this);
		that.unloadlisteners.push("contextmenubookmark");
		var style = "data:text/css;charset=utf-8," + encodeURIComponent(`
			/* Здесь какой-то стиль ..... */
		`);
		try { windowUtils.loadSheetUsingURIString(style, windowUtils.USER_SHEET); } catch (e) {}
	},
	destructor() {
		this.contextMenu.removeEventListener("popupshowing", this);
	},
	handleEvent(e) {
		var array = [
			["ucf_ID_1", "label_1", "func_1", "data:image/png;base64,....."],
			["ucf_ID_2", "label_2", "func_2", "data:image/png;base64,....."],
			// ["separator"],
			["ucf_ID_3", "label_3", "func_3", "data:image/png;base64,....."],
			// ["separator"],
			["ucf_ID_4", "label_4", "func_4", "data:image/png;base64,....."],
			["ucf_ID_5", "label_5", "func_5", "data:image/png;base64,....."],
			["ucf_ID_6", "label_6", "func_6", "data:image/png;base64,....."],
			// ["separator"],
			["ucf_ID_7", "label_7", "func_7", "data:image/png;base64,....."],
			// ["separator"],
			["ucf_ID_8", "label_8", "func_8", "data:image/png;base64,....."],
		];
		array.forEach(m=> {
			// if (m[0] == "separator") { document.createXULElement("menuseparator"); return };
			var menuitem = document.createXULElement("menuitem");
			menuitem.setAttribute("id", m[0]);
			menuitem.setAttribute("label", m[1]);
			menuitem.className = "menuitem-iconic";
			menuitem.setAttribute("image", m[3]);
			menuitem.setAttribute("oncommand", m[2]);
			(this.contextMenu.lastElementChild).after(menuitem);
			this.handleEvent = () => menuitem.hidden;
		});
	},
}).init(this);
} catch(ex) { Cu.reportError(ex); }

unter_officer пишет

вставлять "menuseparator"

Чтобы вставлять "menuseparator" его нужно... «вставлять»,
а не просто создать и... всё.

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

Выделить код

Код:

…
			// if (m[0] == "separator") { document.createXULElement("menuseparator"); return };
			if (m[0] == "separator") { e.target.append(document.createXULElement("menuseparator")); return; };

Dumby пишет

Чтобы вставлять "menuseparator" его нужно... «вставлять»,
а не просто создать и... всё.

Спасибо за помощь.

Dumby
Помогите пожалуйста по моей коллекционной сборке 69 [firefox]. По мне, так 69 - самая удачная в 60-ой линейке, поэтому решил оставить для коллекции.
Проблема в том, что не получается заставить работать на ней скрипт "Кнопка Дополнения".
Ну в этом наверно нет ничего странного, т.к. скрипт делался уже в бытность 70-ых версий, а в них, кажется с 72, менеджер дополнений был переработан.
Мне пришлось в 69 использовать скрипт extensionOptionsMenu.uc.js от xiaoxiaoflood, он работает как в 69, так и в актуальных версиях, но средствами user_chrome_files запустить его не удалось, поэтому был использован метод загрузки от автора этого скрипта - папка utils и этот код в config.js параллельно с user_chrome_files.
Может можно как-то изменить скрипт авторства Vitaliy V., чтоб он заработал на 69 версии, что предпочтительней, т.к. этот скрипт гораздо лучше, или, в крайнем случае, изменить скрипт от xiaoxiaoflood, чтоб его можно было запустить в user_chrome_files?
Я залил на Яндекс-диск свою настроенную портативку 69, без дополнений, темы, закладок и т.п.
Оба скрипта присутствуют и подключены и были немного изменены: пути до иконки, русификация скрипта xiaoxiaoflood, чекбоксы в скрипте Виталия не цветные, а как были в первом его скрипте, но он точно не будет его подгонять под неактуальную версию, поэтому прошу Вас посмотреть, может можно что-то сделать.
И еще просьба, если не очень сложно, может можно изменить скрипты "Замена фавиконок для сайтов" для работы в 69 [firefox], а то дополнение Favicon Switcher работает хуже чем эти скрипты - заменяет фавикон не сразу, и на Яндексе работает не совсем корректно.

sandro79 пишет

не получается заставить работать на ней скрипт "Кнопка Дополнения"
Может можно как-то изменить скрипт авторства Vitaliy V., чтоб он заработал на 69 версии, что предпочтительней

Что-то у меня там нет ChromeUtils в сандбоксе, наверно версия UCF совсем старая.
Ладно, будем исходить из того что нет. Возьмём из глобального объекта JSM'ок.
Вобщем, так, формально, почистил от операторов опцинольной последовательности,
и импорт модулей записал иначе, и, вроде, завелось.
Но там же дофига всего, так что нужно тестировать.

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

Выделить код

Код:

(async id => {
	label = "Дополнения",
	tooltiptext = "ЛКМ: Меню дополнений\nСКМ: Отладка дополнений\nПКМ: Открыть менеджер дополнений",
	img = "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16' viewBox='0 0 48 48'><g><rect x='0' y='0' width='48' height='48' rx='3' ry='3' style='fill:rgb(0, 120, 173);'/><path style='opacity:0.25;fill:black;' d='M 24,4.5 18,12 3,23.7 12,32.7 3.9,44.1 7.8,48 H 45 C 46.7,48 48,46.7 48,45 V 26.1 L 34.8,12.9 31.8,12.3 Z'/><path style='fill:white;' d='M 19.88,3 C 16.93,3 14.55,4.662 14.55,6.701 14.63,7.474 15.11,8.438 15.37,8.762 16.59,10.41 16.59,11.44 16.29,12.06 H 6.299 C 4.476,12.06 3,13.53 3,15.35 V 23.68 C 3.625,24 4.65,24 6.299,22.77 6.625,22.52 7.587,22.02 8.363,21.94 10.4,21.94 12.06,24.35 12.06,27.29 12.06,30.24 10.4,32.65 8.363,32.65 7.725,32.63 6.774,32.07 6.299,31.82 4.65,30.59 3.625,30.59 3,30.91 V 41.71 C 3,43.53 4.476,45 6.299,45 H 19.58 C 19.88,44.38 19.88,43.35 18.65,41.71 18.4,41.38 17.91,40.42 17.82,39.65 17.82,37.6 20.23,35.94 23.18,35.94 26.14,35.94 28.55,37.6 28.55,39.65 28.53,40.28 27.97,41.23 27.71,41.71 26.47,43.35 26.47,44.38 26.79,45 H 32.65 C 34.47,45 35.96,43.53 35.96,41.71 V 32.55 C 36.56,32.23 37.59,32.23 39.23,33.47 39.72,33.73 40.68,34.29 41.29,34.29 43.35,34.29 45,31.91 45,28.94 45,25.99 43.35,23.59 41.29,23.59 40.54,23.67 39.58,24.17 39.23,24.41 37.59,25.65 36.56,25.65 35.96,25.33 V 15.35 C 35.96,13.53 34.47,12.06 32.65,12.06 H 23.49 C 23.19,11.44 23.19,10.41 24.41,8.762 24.66,8.287 25.22,7.337 25.23,6.713 25.23,4.662 22.85,3 19.88,3' /></g></svg>",
	checked = "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><path d='M 3,7 7,11 13,5' style='fill:none;stroke:white;stroke-width:1;'/></svg>",
	show_version = true,
	show_description = true,
	user_permissions = true,
	show_hidden = true,
	show_disabled = true,
	enabled_first = true,
	exceptions_listset = new Set([

	]),
	exceptions_type_listset = new Set([

	]);

	var imp = Cu.getGlobalForObject(Cu).ChromeUtils.import;
	var {AddonManager} = imp("resource://gre/modules/AddonManager.jsm");
	var {GlobalManager} = imp("resource://gre/modules/ExtensionParent.jsm").ExtensionParent;

	var extensionOptionsMenu = {
		get alertsService() {
			delete this.alertsService;
			return this.alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
		},
		get clipboardHelp() {
			delete this.clipboardHelp;
			return this.clipboardHelp = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
		},
		get exceptions_type_listarr() {
			delete this.exceptions_type_listarr;
			var arr = ["extension", "theme", "locale", "dictionary"];
			if (!exceptions_type_listset.size)
				return this.exceptions_type_listarr = arr;
			return this.exceptions_type_listarr = arr.filter(type => !exceptions_type_listset.has(type));
		},
		async populateMenu(e) {
			var popup = e.target, doc = e.view.document;
			var addons = await AddonManager.getAddonsByTypes(this.exceptions_type_listarr);
			var addonsMap = new WeakMap(),
			setAttributesMenu = (mi, addon, extension) => {
				var permissions, uuid,
				props = {
					label: `${addon.name}${show_version ? ` ${addon.version}` : ""}`,
					class: "menuitem-iconic",

					tooltiptext: `${
						show_description && addon.description ? addon.description + "\n" : ""
					}ID: ${
						addon.id
					}${
						addon.isActive && (uuid = extension && extension.uuid) ? `\nUUID: ${uuid}` : ""
					}${
						user_permissions && (
							permissions = addon.userPermissions && addon.userPermissions.permissions || ""
						).length ? `\nРазрешения: ${permissions.join(", ")}` : ""
					}\n${
						addon.optionsURL ? "\nЛКМ: Настройки" : ""
					}\nCtrl+ЛКМ: Копировать ID${
						uuid ? "\nShift+ЛКМ: Копировать UUID" : ""
					}${
						addon.creator && addon.creator.url ? "\nCtrl+Shift+ЛКМ: Автор" : ""
					}${
						addon.homepageURL ? "\nСКМ: Домашняя страница" : ""
					}${
						!addon.isBuiltin ? "\nCtrl+СКМ: Просмотр источника" : ""
					}\nShift+СКМ: Просмотр источника во вкладке\nПКМ: Включить/Отключить${
						!addon.isSystem && !addon.isBuiltin ? "\nCtrl+ПКМ: Удалить" : ""
					}`
				};
				for (let p in props)
					mi.setAttribute(p, props[p]);
				if (addon.iconURL)
					mi.setAttribute("image", addon.iconURL);
				var cls = mi.classList;
				addon.isActive ? cls.remove("ucf-disabled") : cls.add("ucf-disabled");
				addon.optionsURL ? cls.remove("ucf-notoptions") : cls.add("ucf-notoptions");
				addon.isSystem ? cls.add("ucf-system") : cls.remove("ucf-system");
				cls.add(`ucf-type-${addon.type}`);
			};
			addons.filter(a => !(a.iconURL || "").startsWith("resource://search-extensions/")).sort((a, b) => {
				var ka = `${(enabled_first ? a.isActive ? "0" : "1" : "")}${a.type || ""}${a.name.toLowerCase()}`;
				var kb = `${(enabled_first ? b.isActive ? "0" : "1" : "")}${b.type || ""}${b.name.toLowerCase()}`;
				return (ka < kb) ? -1 : 1;
			}).forEach(addon => {
				if (!exceptions_listset.has(addon.id) &&
					(!addon.hidden || show_hidden) &&
					(!addon.userDisabled || show_disabled)) {
					let extension = GlobalManager.extensionMap.get(addon.id),
					mi = doc.createXULElement("menuitem");
					setAttributesMenu(mi, addon, extension);
					mi._Addon = addon;
					mi._Extension = extension;
					popup.append(mi);
					addonsMap.set(addon, mi);
				}
			});
			var click = e => {
				e.preventDefault();
				e.stopPropagation();
				this.handleClick(e);
			};
			popup.addEventListener("click", click);
			var listener = {
				onEnabled: addon => {
					var mi = addonsMap.get(addon);
					if (mi)
						setAttributesMenu(mi, addon, mi._Extension);
				},
				onDisabled: addon => {
					listener.onEnabled(addon);
				},
				onInstalled: addon => {
					var extension = GlobalManager.extensionMap.get(addon.id),
					mi = doc.createXULElement("menuitem");
					setAttributesMenu(mi, addon, extension);
					mi._Addon = addon;
					mi._Extension = extension;
					popup.prepend(mi);
					addonsMap.set(addon, mi);
				},
				onUninstalled: addon => {
					var mi = addonsMap.get(addon);
					if (mi) {
						mi.remove();
						addonsMap.delete(addon);
					}
				},
			};
			AddonManager.addAddonListener(listener);
			popup.addEventListener("popuphiding", () => {
				AddonManager.removeAddonListener(listener);
				popup.removeEventListener("click", click);
				addonsMap = null;
				for (let item of popup.querySelectorAll("menuitem"))
					item.remove();
			}, { once: true });
		},
		handleClick(e) {
			var win = e.view, mi = e.target;
			if (!("_Addon" in mi) || !("_Extension" in mi))
				return;
			var addon = mi._Addon, extension = mi._Extension;
			switch (e.button) {
				case 0:
					if (e.ctrlKey && e.shiftKey) {
						if (addon.creator && addon.creator.url)
							win.gBrowser.selectedTab = this.addTab(win, addon.creator.url);
					} else if (e.ctrlKey) {
						this.clipboardHelp.copyString(addon.id);
						win.setTimeout(() => {
							this.alertsService.showAlertNotification(`${img}`, "ID в буфере обмена!", addon.id, false);
						}, 100);
					} else if (e.shiftKey) {
						if (extension && extension.uuid) {
							this.clipboardHelp.copyString(extension.uuid);
							win.setTimeout(() => {
								this.alertsService.showAlertNotification(`${img}`, "UUID в буфере обмена!", extension.uuid, false);
							}, 100);
						}
					} else if (addon.isActive && addon.optionsURL)
						this.openAddonOptions(addon, win);
					win.closeMenus(mi);
					break;
				case 1:
					if (e.ctrlKey) {
						if (!addon.isBuiltin)
							this.browseDir(addon);
					} else if (e.shiftKey)
						this.browseDir(addon, win);
					else if (addon.homepageURL)
						win.gBrowser.selectedTab = this.addTab(win, addon.homepageURL);
					win.closeMenus(mi);
					break;
				case 2:
					if (!e.ctrlKey) {
						let endis = addon.userDisabled ? "enable" : "disable";
						if (addon.id == "screenshots@mozilla.org")
							Services.prefs.setBoolPref("extensions.screenshots.disabled", !addon.userDisabled);
						else if (addon.id == "webcompat-reporter@mozilla.org")
							Services.prefs.setBoolPref("extensions.webcompat-reporter.enabled", addon.userDisabled);
						addon[endis]({ allowSystemAddons: true });
					} else if (!addon.isSystem && !addon.isBuiltin) {
						win.closeMenus(mi);
						if (Services.prompt.confirm(win, null, `Удалить ${addon.name}?`))
							addon.uninstall();
					}
				break;
			}
		},
		openAddonOptions(addon, win) {
			switch (addon.optionsType) {
				case 5:
					win.BrowserOpenAddonsMgr(`addons://detail/${encodeURIComponent(addon.id)}/preferences`);
					break;
				case 3:
					win.switchToTabHavingURI(addon.optionsURL, true);
					break;
			}
		},
		browseDir(addon, win) {
			try {
				if (!win) {
					let file = Services.io.getProtocolHandler("file")
					.QueryInterface(Ci.nsIFileProtocolHandler)
					.getFileFromURLSpec(addon.getResourceURI().QueryInterface(Ci.nsIJARURI).JARFile.spec);
					if (file.exists())
						file.launch();
				} else
					win.gBrowser.selectedTab = this.addTab(win, addon.getResourceURI().spec);
			} catch (e) {}
		},
		addTab(win, url, params = {}) {
			params.triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
			params.relatedToCurrent = true;
			return win.gBrowser.addTab(url, params);
		},
	};
	CustomizableUI.createWidget({
		type: "custom",
		id, label, tooltiptext,
		localized: false,
		defaultArea: CustomizableUI.AREA_NAVBAR,
		onBuild(doc) {
			var btn = doc.createXULElement("toolbarbutton"), win = doc.defaultView,
			props = {
				context: "",
				type: "menu",
				id, label, tooltiptext,
				class: "toolbarbutton-1 chromeclass-toolbar-additional",
			};
			for (let p in props)
				btn.setAttribute(p, props[p]);
			btn.addEventListener("click", e => {
				if (e.button == 1)
					e.view.switchToTabHavingURI("about:debugging#/runtime/this-firefox", true, { ignoreFragment: "whenComparing", triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), });
				else if (e.button == 2)
					e.view.BrowserOpenAddonsMgr("addons://list/extension");
			});
			var mp = doc.createXULElement("menupopup");
			mp.id = `${id}-popup`;
			mp.addEventListener("contextmenu", e => {
				e.preventDefault();
				e.stopPropagation();
			});
			mp.addEventListener("popupshowing", e => {
				extensionOptionsMenu.populateMenu(e);
			});
			btn.append(mp);
			var btnstyle = "data:text/css;charset=utf-8," + encodeURIComponent(`
				#${id}, #${id}-popup menuitem {
					list-style-image: url("${img}") !important;
				}
				#${id}-popup menuitem::after {
					display: -moz-box !important;
					content: "" !important;
					height: 16px !important;
					width: 16px !important;
					padding: 0 !important;
					border: 1px solid rgb(0, 116, 232) !important;
					border-radius: 0 !important;
					background-repeat: no-repeat !important;
					background-position: center !important;
					background-size: 16px !important;
					background-color: rgb(0, 116, 232) !important;
					background-image: url("${checked}") !important;
					opacity: 1 !important;
				}
				#${id}-popup menuitem.ucf-disabled::after {
					border-color: currentColor !important;
					background-color: transparent !important;
					background-image: none !important;
					opacity: .6 !important;
				}
				#${id}-popup menuitem.ucf-disabled > label,
				#${id}-popup menuitem.ucf-notoptions > label {
					opacity: .6 !important;
				}
				#${id}-popup menuitem.ucf-system > label {
					text-decoration: underline !important;
					text-decoration-style: dotted !important;
				}
				#${id}-popup menuitem > label {
					margin-inline-end: 0 !important;
				}
				#${id}-popup menuitem > .menu-accel-container {
					display: -moz-box !important;
					padding: 4px !important;
					margin: 0 !important;
					opacity: 1 !important;
				}
				#${id}-popup menuitem > .menu-accel-container .menu-iconic-accel {
					display: -moz-box !important;
					margin: 0 !important;
					height: 8px !important;
					width: 8px !important;
					border-radius: 4px !important;
					background-color: transparent !important;
					opacity: 1 !important;
					font-size: 0 !important;
				}
				#${id}-popup menuitem.ucf-type-dictionary > .menu-accel-container .menu-iconic-accel {
					background-color: rgb(227, 27, 93) !important;
				}
				#${id}-popup menuitem.ucf-type-locale > .menu-accel-container .menu-iconic-accel {
					background-color: rgb(48, 172, 55) !important;
				}
				#${id}-popup menuitem.ucf-type-theme > .menu-accel-container .menu-iconic-accel {
					background-color: rgb(219, 106, 0) !important;
				}
			`);
			try {
				win.windowUtils.loadSheetUsingURIString(btnstyle, win.windowUtils.USER_SHEET);
			} catch (e) {}
			return btn;
		},
	});
})("ucf-aom-button");

Я залил

яд.иск :usch:. Это ты залил для всех, кроме меня.

может можно изменить скрипты "Замена фавиконок для сайтов" для работы в 69

Похоже, в 69 у JSWindowActorChild нет callback'а actorCreated().
И about:config там ещё древесный, и :is(), понятное дело, отсутствует.


Ладно, попробуем. Итак, в первом скрипте меняем первую строку на
Cu.getGlobalForObject(Cu).ChromeUtils.registerWindowActor("LinkWinActor", {


Остальные двое:

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

Выделить код

Код:

const ICONS = { // "домен, или адрес для about|chrome|resource": "иконка",

	"yandex.ru": "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><g><rect rx='2' ry='2' width='16' height='16' style='fill:rgb(231, 43, 90);'/><path d='M 8.56,2 C 6.31,2 4.49,3.5 4.49,5.99 4.49,7.49 5.31,8.48 6.97,9.57 L 4,13.5 V 14 H 5.76 L 8.53,9.57 H 9.47 V 14 H 11 V 2 Z M 9.47,8.48 H 8.67 C 7.36,8.48 6.05,7.98 6.05,5.99 6.05,4 7.26,3 8.47,3 H 9.47 Z' style='fill:white;'/></g></svg>",
	"nnmclub.to": "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><g><path d='M 0.887,0 C 0.54,0.14 0.289,0.279 0.135,0.711 0.051,0.851 0.015,0.991 0,1.27 -0.009,1.71 0.061,2.16 0.325,3.05 0.564,3.88 0.612,4.12 0.612,4.37 0.612,4.48 0.612,4.56 0.588,4.67 0.457,5.43 0.457,6.07 0.588,6.56 0.672,6.87 0.779,7.09 0.947,7.28 1.21,7.57 1.49,7.73 2.05,7.87 L 2.27,7.91 2.11,7.96 C 1.87,8.01 1.71,8.08 1.55,8.19 1.33,8.31 1.22,8.41 1.09,8.59 0.947,8.65 0.887,8.83 0.839,8.99 0.803,9.17 0.792,9.41 0.827,9.65 0.851,9.93 0.851,9.93 0.827,10 0.827,10.1 0.767,10.3 0.708,10.5 0.588,10.9 0.564,11.1 0.564,11.2 0.564,11.5 0.612,11.6 0.875,11.8 1.15,12.2 1.18,12.3 1.15,12.6 1.1,13.2 1.18,13.6 1.39,13.7 1.49,13.7 1.6,13.9 1.75,14 1.93,14 2.05,14.1 2.28,14.4 2.48,14.5 2.55,14.5 2.64,14.5 2.73,14.7 2.73,14.7 2.87,14.7 3.04,14.7 3.04,14.7 3.47,14.5 4.03,14 4.19,13.9 4.44,13.9 4.55,13.7 4.6,13.7 4.68,13.7 4.88,13.6 5.11,13 5.35,11.9 5.39,11.8 5.43,11.6 5.47,11.5 5.65,10.9 5.89,10.4 6.12,10 6.24,9.83 6.48,9.59 6.49,9.59 6.49,9.59 6.49,9.75 6.48,10.1 6.44,10.3 6.43,10.5 6.28,11.1 6.12,11.5 6.08,11.7 6.05,12 6.03,12.4 6.11,12.6 6.21,12.9 6.32,13 6.43,13 6.57,13 6.71,12.9 6.81,12.6 6.91,12.4 7.07,12 7.11,11.7 7.11,11 7.11,10.7 7.13,10.4 7.27,10 7.35,9.75 7.32,9.75 7.37,9.83 7.52,10.1 7.64,10.7 7.69,11.3 7.72,11.6 7.72,12.2 7.69,12.4 7.64,13 7.61,13.6 7.68,14 7.72,14.4 7.76,14.5 8.01,14.5 8.15,14.7 8.31,14.8 8.67,15.5 8.92,15.9 9.03,15.9 9.11,16 9.17,16 9.17,16 9.33,16 9.52,16 9.52,16 9.77,15.9 10.1,15.7 10.1,15.7 10.4,15.7 10.7,15.7 10.7,15.7 10.7,15.7 11,15.6 11.1,15.5 11.4,14.9 11.4,14.9 11.4,14.8 11.5,14.7 11.5,14.5 11.6,14.4 11.7,14.1 12.2,13.9 12.2,13.9 12.2,13.7 12.4,13.6 12.4,13.6 12.4,13 12.4,12.6 12.5,12.4 12.5,12.3 12.5,12.3 12.5,12.2 12.7,12 12.8,11.7 12.8,11.6 12.8,11.5 12.8,11.2 12.8,11.1 12.8,11.1 12.7,10.7 12.4,10.3 12,9.93 11.9,9.83 11.9,9.83 11.9,9.83 12.2,9.93 12.7,9.93 12.9,9.93 13.7,9.75 14,9.08 14.1,7.84 14.1,7.76 14.3,7.59 14.3,7.49 14.3,7.27 14.3,7.19 14.5,7.01 14.5,6.83 14.8,6.6 15.1,6.19 15.7,5.24 15.9,4.85 16,4.41 16,4.31 16,4.25 16,4.03 16,3.84 16,3.76 16,3.69 15.7,3.03 15.2,2.68 14.3,2.75 13.7,2.76 13,2.97 12.2,3.32 12,3.4 11.6,3.63 11.4,3.81 10.2,4.49 9.11,5.55 8.08,6.85 7.95,7 7.85,7.11 7.85,7.11 7.85,7.11 7.84,7.05 7.84,7.04 7.76,6.93 7.72,6.87 7.61,6.81 L 7.53,6.73 7.64,6.47 C 7.92,5.92 8.13,5.51 8.31,5.23 8.35,5.12 8.51,4.91 8.76,4.63 9.43,3.73 9.68,3.47 9.77,3.35 10,3.21 10,3.2 10,3.15 10.1,3.05 10,2.92 10,2.87 9.95,2.87 9.95,2.91 9.84,2.97 9.84,3.11 9.52,3.44 9.11,4.03 8.92,4.19 8.76,4.37 8.76,4.45 8.35,4.95 7.95,5.69 7.61,6.47 7.53,6.6 7.49,6.73 7.47,6.73 7.47,6.73 7.43,6.71 7.37,6.71 L 7.28,6.68 V 6.59 C 7.2,5.69 7.08,5.08 6.91,4.49 6.83,4.16 6.8,4 6.59,3.44 6.33,2.75 6.2,2.32 6.2,2.21 6.17,2.09 6.05,2.08 6,2.2 5.97,2.31 5.99,2.39 6.11,2.55 6.21,2.75 6.32,2.97 6.73,4.15 7,4.96 7.07,5.13 7.2,6.29 7.23,6.68 7.23,6.68 7.2,6.68 7.13,6.68 6.97,6.75 6.89,6.83 6.83,6.87 6.81,6.89 6.81,6.89 6.81,6.89 6.75,6.73 6.68,6.56 6.53,6.07 6.4,5.77 6.2,5.27 5.36,3.43 4.39,2.01 3.36,1.13 2.64,0.571 2.01,0.14 1.49,0 1.32,0 1.02,0 0.887,0 Z' style='fill:rgb(0, 140, 255);stroke:black;stroke-width:0.6;stroke-linejoin:round;stroke-linecap:round;'/></g></svg>",
	
	"about:config": "resource://normandy/skin/shared/heartbeat-icon.svg",
	"about:user-chrome-files": "chrome://browser/skin/accessibility.svg",
};

var EXPORTED_SYMBOLS = ["LinkWinActorChild"];
ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
const LINK_SELECTOR = "link[href]:-moz-any([rel~='icon'],[rel~='apple-touch-icon'],[rel~='apple-touch-icon-precomposed'],[rel~='fluid-icon'],[rel~='mask-icon'])";

var noop = () => {};
var re = /^(?:about|chrome|resource)$/;
var idn = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);

var once = function() {
	delete this.handleEvent;
	var doc = this.document;
	var docURI = doc.documentURIObject, host;
		if (re.test(docURI.scheme)) host = docURI.specIgnoringRef;
		else try {
			host = idn.convertToDisplayIDN(Services.eTLD.getBaseDomain(docURI), {});
		} catch {
			try {host = docURI.displayHost;}
			catch {host = docURI.specIgnoringRef;}
		}
		var icon = ICONS[host];
		if (!icon) return this.handleEvent = noop;

		this._icon = icon;
		this.onHeadParsed(doc.head || doc.documentElement);
}

class LinkWinActorChild extends JSWindowActorChild {
	handleEvent = once;
	onHeadParsed(target) {
		for (let link of target.querySelectorAll(LINK_SELECTOR))
			link.remove();
		var link = this.document.createElementNS("http://www.w3.org/1999/xhtml", "link");
		link.setAttribute("rel", "icon");
		link.setAttribute("sizes", "any");
		link.setAttribute("href", this._icon);
		target.append(link);
	}
	onLinkEvent(link) {
		if (!link.matches(LINK_SELECTOR) || link.href == this._icon) return;
		link.href = this._icon;
		link.setAttribute("rel", "icon");
		link.setAttribute("sizes", "any");
		if (link.hasAttribute("type"))
			link.removeAttribute("type");
	}
	handleEvent(e) {
		switch (e.type) {
			case "DOMLinkAdded":
			case "DOMLinkChanged":
				this.onLinkEvent(e.target);
				break;
			case "pageshow":
				this.onHeadParsed(e.target.head || e.target.documentElement);
				break;
		}
	}
}


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

Выделить код

Код:

const ICONS = { // "поддомен + домен, или адрес для about|chrome|resource": "иконка"

	"yandex.ru": "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><g><rect rx='2' ry='2' width='16' height='16' style='fill:rgb(231, 43, 90);'/><path d='M 8.56,2 C 6.31,2 4.49,3.5 4.49,5.99 4.49,7.49 5.31,8.48 6.97,9.57 L 4,13.5 V 14 H 5.76 L 8.53,9.57 H 9.47 V 14 H 11 V 2 Z M 9.47,8.48 H 8.67 C 7.36,8.48 6.05,7.98 6.05,5.99 6.05,4 7.26,3 8.47,3 H 9.47 Z' style='fill:white;'/></g></svg>",
	get "passport.yandex.ru"() {
		return this["yandex.ru"];
	},
	"nnmclub.to": "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16'><g><path d='M 0.887,0 C 0.54,0.14 0.289,0.279 0.135,0.711 0.051,0.851 0.015,0.991 0,1.27 -0.009,1.71 0.061,2.16 0.325,3.05 0.564,3.88 0.612,4.12 0.612,4.37 0.612,4.48 0.612,4.56 0.588,4.67 0.457,5.43 0.457,6.07 0.588,6.56 0.672,6.87 0.779,7.09 0.947,7.28 1.21,7.57 1.49,7.73 2.05,7.87 L 2.27,7.91 2.11,7.96 C 1.87,8.01 1.71,8.08 1.55,8.19 1.33,8.31 1.22,8.41 1.09,8.59 0.947,8.65 0.887,8.83 0.839,8.99 0.803,9.17 0.792,9.41 0.827,9.65 0.851,9.93 0.851,9.93 0.827,10 0.827,10.1 0.767,10.3 0.708,10.5 0.588,10.9 0.564,11.1 0.564,11.2 0.564,11.5 0.612,11.6 0.875,11.8 1.15,12.2 1.18,12.3 1.15,12.6 1.1,13.2 1.18,13.6 1.39,13.7 1.49,13.7 1.6,13.9 1.75,14 1.93,14 2.05,14.1 2.28,14.4 2.48,14.5 2.55,14.5 2.64,14.5 2.73,14.7 2.73,14.7 2.87,14.7 3.04,14.7 3.04,14.7 3.47,14.5 4.03,14 4.19,13.9 4.44,13.9 4.55,13.7 4.6,13.7 4.68,13.7 4.88,13.6 5.11,13 5.35,11.9 5.39,11.8 5.43,11.6 5.47,11.5 5.65,10.9 5.89,10.4 6.12,10 6.24,9.83 6.48,9.59 6.49,9.59 6.49,9.59 6.49,9.75 6.48,10.1 6.44,10.3 6.43,10.5 6.28,11.1 6.12,11.5 6.08,11.7 6.05,12 6.03,12.4 6.11,12.6 6.21,12.9 6.32,13 6.43,13 6.57,13 6.71,12.9 6.81,12.6 6.91,12.4 7.07,12 7.11,11.7 7.11,11 7.11,10.7 7.13,10.4 7.27,10 7.35,9.75 7.32,9.75 7.37,9.83 7.52,10.1 7.64,10.7 7.69,11.3 7.72,11.6 7.72,12.2 7.69,12.4 7.64,13 7.61,13.6 7.68,14 7.72,14.4 7.76,14.5 8.01,14.5 8.15,14.7 8.31,14.8 8.67,15.5 8.92,15.9 9.03,15.9 9.11,16 9.17,16 9.17,16 9.33,16 9.52,16 9.52,16 9.77,15.9 10.1,15.7 10.1,15.7 10.4,15.7 10.7,15.7 10.7,15.7 10.7,15.7 11,15.6 11.1,15.5 11.4,14.9 11.4,14.9 11.4,14.8 11.5,14.7 11.5,14.5 11.6,14.4 11.7,14.1 12.2,13.9 12.2,13.9 12.2,13.7 12.4,13.6 12.4,13.6 12.4,13 12.4,12.6 12.5,12.4 12.5,12.3 12.5,12.3 12.5,12.2 12.7,12 12.8,11.7 12.8,11.6 12.8,11.5 12.8,11.2 12.8,11.1 12.8,11.1 12.7,10.7 12.4,10.3 12,9.93 11.9,9.83 11.9,9.83 11.9,9.83 12.2,9.93 12.7,9.93 12.9,9.93 13.7,9.75 14,9.08 14.1,7.84 14.1,7.76 14.3,7.59 14.3,7.49 14.3,7.27 14.3,7.19 14.5,7.01 14.5,6.83 14.8,6.6 15.1,6.19 15.7,5.24 15.9,4.85 16,4.41 16,4.31 16,4.25 16,4.03 16,3.84 16,3.76 16,3.69 15.7,3.03 15.2,2.68 14.3,2.75 13.7,2.76 13,2.97 12.2,3.32 12,3.4 11.6,3.63 11.4,3.81 10.2,4.49 9.11,5.55 8.08,6.85 7.95,7 7.85,7.11 7.85,7.11 7.85,7.11 7.84,7.05 7.84,7.04 7.76,6.93 7.72,6.87 7.61,6.81 L 7.53,6.73 7.64,6.47 C 7.92,5.92 8.13,5.51 8.31,5.23 8.35,5.12 8.51,4.91 8.76,4.63 9.43,3.73 9.68,3.47 9.77,3.35 10,3.21 10,3.2 10,3.15 10.1,3.05 10,2.92 10,2.87 9.95,2.87 9.95,2.91 9.84,2.97 9.84,3.11 9.52,3.44 9.11,4.03 8.92,4.19 8.76,4.37 8.76,4.45 8.35,4.95 7.95,5.69 7.61,6.47 7.53,6.6 7.49,6.73 7.47,6.73 7.47,6.73 7.43,6.71 7.37,6.71 L 7.28,6.68 V 6.59 C 7.2,5.69 7.08,5.08 6.91,4.49 6.83,4.16 6.8,4 6.59,3.44 6.33,2.75 6.2,2.32 6.2,2.21 6.17,2.09 6.05,2.08 6,2.2 5.97,2.31 5.99,2.39 6.11,2.55 6.21,2.75 6.32,2.97 6.73,4.15 7,4.96 7.07,5.13 7.2,6.29 7.23,6.68 7.23,6.68 7.2,6.68 7.13,6.68 6.97,6.75 6.89,6.83 6.83,6.87 6.81,6.89 6.81,6.89 6.81,6.89 6.75,6.73 6.68,6.56 6.53,6.07 6.4,5.77 6.2,5.27 5.36,3.43 4.39,2.01 3.36,1.13 2.64,0.571 2.01,0.14 1.49,0 1.32,0 1.02,0 0.887,0 Z' style='fill:rgb(0, 140, 255);stroke:black;stroke-width:0.6;stroke-linejoin:round;stroke-linecap:round;'/></g></svg>",
	
	"about:config": "resource://normandy/skin/shared/heartbeat-icon.svg",
	"about:user-chrome-files": "chrome://browser/skin/accessibility.svg",
};

var EXPORTED_SYMBOLS = ["LinkWinActorChild"];
const LINK_SELECTOR = "link[href]:-moz-any([rel~='icon'],[rel~='apple-touch-icon'],[rel~='apple-touch-icon-precomposed'],[rel~='fluid-icon'],[rel~='mask-icon'])";

var noop = () => {};
var re = /^(?:about|chrome|resource)$/;

var once = function() {
	delete this.handleEvent;
	var doc = this.document;
	var docURI = doc.documentURIObject, host;
		if (re.test(docURI.scheme)) host = docURI.specIgnoringRef;
		else 
			try {host = docURI.displayHost;}
			catch {host = docURI.specIgnoringRef;}

		var icon = ICONS[host];
		if (!icon) return this.handleEvent = noop;

		this._icon = icon;
		this.onHeadParsed(doc.head || doc.documentElement);
}

class LinkWinActorChild extends JSWindowActorChild {
	handleEvent = once;
	onHeadParsed(target) {
		for (let link of target.querySelectorAll(LINK_SELECTOR))
			link.remove();
		var link = this.document.createElementNS("http://www.w3.org/1999/xhtml", "link");
		link.setAttribute("rel", "icon");
		link.setAttribute("sizes", "any");
		link.setAttribute("href", this._icon);
		target.append(link);
	}
	onLinkEvent(link) {
		if (!link.matches(LINK_SELECTOR) || link.href == this._icon) return;
		link.href = this._icon;
		link.setAttribute("rel", "icon");
		link.setAttribute("sizes", "any");
		if (link.hasAttribute("type"))
			link.removeAttribute("type");
	}
	handleEvent(e) {
		switch (e.type) {
			case "DOMLinkAdded":
			case "DOMLinkChanged":
				this.onLinkEvent(e.target);
				break;
			case "pageshow":
				this.onHeadParsed(e.target.head || e.target.documentElement);
				break;
		}
	}
}

Dumby пишет

и импорт модулей записал иначе, и, вроде, завелось.
Но там же дофига всего, так что нужно тестировать

Да, отлично, завелось, работает! Вроде всё в норме.

яд.иск :usch:. Это ты залил для всех, кроме меня

Понял. Тогда буду, как и скриншоты, заливать на https://www.upload.ee/

Ладно, попробуем. Итак, в первом скрипте меняем первую строку на
Cu.getGlobalForObject(Cu).ChromeUtils.registerWindowActor("LinkWinActor", {

Да, тоже всё завелось и отлично работает! Как и в актуальных версиях.
Огромное Вам Спасибо! Всё получилось великолепно!

отчёт :beer:
Image_001.png

Здесь https://forum.mozilla-russia.org/viewto … 08#p792708 скрипт ucjsDownloadsManager.uc.js. Никак не могу добиться его работы через UserChromeFiles от VitaliyV, его user_chrome_files установлен, из него работают дополнительные панели инструментов, их настраивать не пришлось, они заработали сразу. Возможно, не туда прописываю или неправильно прописываю. Пробовал запустить  другие скрипты, тоже не получилось.

shadow_user пишет

Никак не могу добиться его работы через UserChromeFiles от VitaliyV, его user_chrome_files установлен

Вот тут уже рабочий пример подключения скриптов.
Так до сих пор, с несущественными правками, всё и работает вплоть до 98 [nightly]. Единственное ucf_wheretoopenlink.js отвалился с 95 [firefox]
scripts, scripts2, scripts3 в путях - это папки в которые я  положил скрипты.
https://forum.mozilla-russia.org/viewto … 76#p794876
Читать надо тему, а так методом тыка ничего не получится конечно. Этот комплект не этот, куда достаточно просто скрипт закинуть, ничего не прописывая, и даже startupCache не надо чистить.

sandro79 пишет

Вот тут уже рабочий пример подключения скриптов.

Не получилось ничего. Ясное дело, тему читал, startupCache чистил кнопкой в настройках UCF. В умолчальных скриптах разкомментирую скрипт автоскрытия тулбара, и тоже не работает. Менял на ваш CustomStylesScripts.jsm, рядом с ним в папку scripts2 ложил ucjsDownloadsManager2.uc.js, и увы.

shadow_user
Скачайте мой настроенный комплект и разбирайтесь в чём у вас проблема.
startupCache надёжнее чистить вручную, т.к. очень часто ни кнопкой комплекта, ни кнопкой в about:support он почему-то не чистится.

sandro79 пишет

Скачайте мой настроенный комплект и разбирайтесь в чём у вас проблема.

Комплект заработал, вижу новые иконки папок и некоторых кнопок, но... кроме прогресса загрузки :D , упрямо не появляется. Может, и еще что не заработало. В about:profiles вижу два профиля, один default - "по умолчанию, сейчас используется и не может быть удален", второй default -esr - "сейчас используется другим приложением и не может быть удален", что-то тут не так, я сам еще один профиль не создавал.
Попробую с чистым профилем или сделаю новый портабельный.

shadow_user пишет

кроме прогресса загрузки

Вот эти два чекбокса должны быть включены.
Вы наверно в настройки комплекта даже не заходили, раз кроме изменений иконок средствами user_chrome.manifest ничего не заметили.
about:user-chrome-files - открыть настройки во вкладке.

sandro79 пишет

Вот эти два чекбокса должны быть включены.

Искренне благодарен за терпение и помощь! По прочтению темы я помню, что один или два д.б. включены, включал по одному, не помогло. Создал новый профиль, включил два, наконец-то прогресс загрузки показался! Возникла пара вопросов:
1. В наборе два скрипта, ucjsDownloadsManager.uc.js и ucjsDownloadsManager2.uc.js, зачем два и в чем разница? Какой из них у меня срабатывает? Теоретически я не большой спец, мне, может, и одного хватило бы.
2. Как отключить лишний, если он действительно лишний, и какой? Или их однозначно должно быть два?
3. А вот это более важно для меня - окно прогресса закрывается по окончании загрузки, а я привык, чтоб оно оставалось на экране, типа чтобы успеть репу почесать, как это сделать?
4. Табы стали переключаться наведением мышки, но я консерватор. Это работа, видимо, судя по названию, скрипта tabs_focus.js в файле. Для отключения достаточно его удалить\переименовать, или можно где-то закомментировать команду вызова?
5. Какой скрипт или стиль показывает фавиконку в строке адреса?

shadow_user пишет

В наборе два скрипта, ucjsDownloadsManager.uc.js и ucjsDownloadsManager2.uc.js, зачем два и в чем разница?

Это один скрипт разделён на два под особенности комплекта user_chrome_files, нужны оба скрипта и они оба подключены.
Чтоб окно прогресса не закрывалось, нужно в обоих скриптах найти строку var closeWhenDone = true; и вместо true вписать false, и следите чтоб кодировка скрипта оставалась UTF8 без BOM после правки и сохранения.

скрипта tabs_focus.js в файле. Для отключения достаточно его удалить\переименовать, или можно где-то закомментировать команду вызова?

Его можно удалить, ну и строку { path: "scripts3/tabs_focus.js", ucfobj: false, }, в CustomStylesScripts.jsm удалить или // закомментировать.
Так и с другим всем ненужным, я просто всё скинул вам с 91-ой версии, лень было по-новой возится или удалять почти всё.

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

Это скрипт авторства Vitaliy V., только немного изменённый под себя по советам автора. Обсуждение начиная с первой страницы этой темы.
Скрипт исправно работает вплоть до 98 [nightly], но только в user_chrome_files, в классических комплектах его запустить не удалось.

sandro79 пишет

Это один скрипт разделён на два

Все понял и исправил под свои пожелания, кроме: не смог идентифицировать скрипт (или стиль?) для фавиконки, просьба нацелить. Дело в том, что у меня есть скрипт, который показывает фавиконку в размере 22х22, а не 16х16, не скажу, что это лучше или хуже, но мне нужно выбрать один. Так же вроде логично, чтобы фавиконка была не перед замком, а перед урлом, вроде и вы в какой-то теме об этом писали
78.png

shadow_user пишет

не смог идентифицировать скрипт (или стиль?) для фавиконки, просьба нацелить

Скрипт favicon_in_urlbar.js

который показывает фавиконку в размере 22х22, а не 16х16

Ну это тоже можно настроить, см. height и width.
У Виталия в скрипте width: auto !important; прописано, но мне как-то попался

Войдите или зарегистрируйтесь, чтобы увидеть скрытый текст.
со здоровенной иконкой и она не отображалась пока auto не заменил на значение в пикселях как у height.

вроде и вы в какой-то теме об этом писали

Да не, я наоборот у Виталия спрашивал как её перед замком поставить. У Ариса когда-то так было, привык.
identity.before(faviconinurlbar); замените на identity.after(faviconinurlbar);
Вам наверно лучше будет использовать оригинальный авторский скрипт, а не мой правленный.

sandro79 пишет

Скрипт favicon_in_urlbar.js

Ну конечно, нужно быть слепым, чтобы не заметить. На сей момент со всем разобрался, спасибо! :beer:
Почему два скрипта, favicon_in_urlbar.js и favicon_in_urlbar2.js?
Включил остальные птички в UCF, теперь вижу множество кнопок. "Закрыть другие вкладки" это скрипт Close-Tabs-button.js? И какой скрипт кнопки "Показать загрузки"? Просто мечтал об этой кнопке.

shadow_user пишет

Почему два скрипта, favicon_in_urlbar.js и favicon_in_urlbar2.js?

Ну favicon_in_urlbar2.js это Арисовский скрипт, ну просто оставил для потомков :D Он не подключен.

"Закрыть другие вкладки" это скрипт Close-Tabs-button.js?

Ну да. Там два в одном, ещё закрыть текущую.

И какой скрипт кнопки "Показать загрузки"?

To_switch_proxy.js Ну там две кнопки в одном скрипте - переключить прокси и показать загрузки когда-то Виталий по моей просьбе делал.
Если хотите их разделить, то как разделить их я не знаю.
Сейчас в кнопке прокси уже нет кнопки загрузок. Я пока старой, два в одном, пользуюсь.

Dumby
Приветствую! В [firefox] 96 не работает этот скрипт: https://forum.mozilla-russia.org/viewto … 56#p790256, или может что то не так сделал ?

kokoss пишет

В [firefox] 96 не работает этот скрипт

Проверил на 96 — вроде работает.

или может что то не так сделал ?

Да, разумеется, «что то не так сделать» можно всегда.
Но, чтобы попробовать понять что именно не так,
нужно знать что именно сделал, впрочем, об этом и не спрашивалось.

Dumby пишет

Но, чтобы попробовать понять что именно не так,
нужно знать что именно сделал

Добавил скрипт в папку custom_scripts и прописал путь до скрипта в custom_script.js так:

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

Выделить код

Код:

(() => {
    var loadscript = relpath => {
        try {
            Services.scriptloader.loadSubScript(`chrome://user_chrome_files/content/custom_scripts/${relpath}`, globalThis, "UTF-8");
        } catch(e) {}
    };
	loadscript("/ucf-cbbtn-BBCode-Multi.js");
	loadscript("/AutoCopyChild.jsm");
    // loadscript("/Undo_Close_Tabs.js");
    // loadscript("/QuickToggle_AboutConfig.js");
    // и т. д.
})();

kokoss пишет

loadscript("/AutoCopyChild.jsm");

Не-не, JSM'ки scriptloder'ом не грузят.
Их импортируют через ChromeUtils.import();


Можно в CustomStylesScripts.jsm прописать

скриншот

Выделить код

Код:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+EAAAFpCAIAAAB8p74tAAB7qklEQVR42uy9T2wcSZ7vF5zZnvfWeLAlWSQNrJ+t7h4KLv4xeht6fnikW3zi9u5aGhiWCaHgk9uHdlELHUR40PZhZRAEeDDQGIA8NJYkBAN9M2iioMuIl1kKklDCW6PRLYAiyxbV07L3JIloavHWaxtvZ+WMjPwTERm/yIysLFYU6/s5SMWoqIhf/DIy85u//GXG0F//9V8zAAAAAAAAgDcMBRr9L//yL3ttBgAAAAAAACAi0uh/9Vd/1WtLAAAAAAAAAJxUo//5n/95r41ReO+990ZGRl6/fp37b68tBQAAAAAAoEr81eh/8Ad/UESgQ6YDAAAAAIBThr8aHXF0AAAAAAAwmPir0RFHBwAAAAAAg0nFGv3lg7u/+Z6du3Rj/qMzHTblVRz9xdrM2OITpWh69V3r9trMEC8OP7OdhaFrm8E3jfvvNq6y6Ku0+mHr9s/F57hi/EOlSCk39Bu29Itfm+3J/EDYkkNqaqPReDa+0bodNxNZHVUIvt3c3JSHIswOCupbqj3ycHPHqxuZtafyDbqzsCz6b9xfivpuN5e39thU/dbww692X43O3br5yXlRFhD8eYNtB+Xij6Wbn9jbT34YMFVfmq/xD4/Xl0UDbGpq6vXwfF4jRo4erwszhIWdOKESe5LxPhzu1B6bK9Xtwst3mkPXQhc36u82atX3CwAAAPSUKjX626fN7eOPP3//h+bbP+xco/sWRw9k62fs60h4hn+0Qu0YCMqteiRIA+V573qiN5W/1mZmxK+5OmWRKOVSeKueyPGk/aDOyjhvMylM2kraoexRupU7KzAuXv1ZKqNnnn8RtZ+0KcR7LKqDKivPJu+0+B/BN19ebEU9hQMQ7VDj1XrIt6dSuEZnqrYLxS8LJOD5R4EoTIR1u7l+dDkRn/yvfC0bNLXNbqrVwrIboiEuO1+nQv/xevP8zXknmSm3Vg6LPd3GbbzEdhEYtiMAAABwKuhCrsvLB5VodIc4+gs2M8aiwOt9thGM6lrUSPDn9XssjdhOs8MWE5pwYYiFQW92n0UVVg+ZphdlYu14GAhLNbIbqdAvns+MHdyR9LCi0SMJOyaJabWSrrlTwZtpy26PWlVp1jYuQ4Xg8iMckNRiaFj92T1unPw5q9GlURnHywpo9O5BafSRQAKyULHG4VqLRk+D5aNzc2xfKF45gi6+EtrXrKqTqHhMpEGj8ihqnPyVyGhDa3JTaqQ/jZePjo6yiZsWe+hxiUak+wmqpWpcP6wc/HhkdzduSHxJjZe2k9ou5HaMptHjmbFdtnqrdbsLoX0AAACg+/ir0V3j6GszbKvOhBp8scbGtlI5niDXYfwEH6nz++/YWPCTA/Zug7QnzSKZzkR2xXd6uSGO/otfZwRoLFRlbRqLY6Ituz1a1ewvM6S5JaYhPJvenLyjXmpcvxfYfOdgLGh4fGUh0ehSuksUaTcIbkmYGzW61R6No7WZr/RUIP6rucPWJ3aNb9J2cb5GoBW/ejMbS0dKo3MhyyKBmYR6o2qmODpTNKimoU1xZbVjrU5WYT9eX08KuD37E9lrg0z83mCPbVxCvIvK7WaTzScGmewJGp+SGkpdahwvbad5u9DbUUxUaHQAAAD9jb8a3TkffYcNrUS6fGEo0N0sjummYXXGVZ+u0QOBnpuxzWxx9ESfannVSs61SNC2aFZZ4yrZ3HFb5eLoRTS6Mg4pF8Xw8yhazvjQooz8BXMc/dozkTdfQqPb7KmI4jkShEa3Jr0QGl3+PtHQjM79SMtVI1hWE2fi01KE2pgbT9ljHVfGDNKeUKPLgjqo8CgeJDHefDs72Y4AAABAf+GvRi+Rj742ww7uhLkusVgXaTCTsV43xNGZLXYuQ+dgRNnj/DlORVCa1PGLArkupiRyWqP/3GCOS66L7ceM0uhX2c7OztWrV7lLTRq9QG4PUzV6JruH9mFKtXF0M4ouTcV3Rxo91KPtmjWuLPedrZDRxMUS5cOQtCnvPLGnQo2+rl1E5Gn0InbqQKMDAAA4rfir0cu812WHDd3j+eUr47EQD4PoUaQ8/KzH0ctq9ESbynkpqro268siz4zycjXZJVejJ/aoVXOj6MnjqXGjmlKmNHoModHTZgs+Mxr/PM+eiiiu7eQ0Dfmz+iYTOUPEqNHV6rJileWs2gwTcnaWPcwo46wmpt6somjl9NKAtMc2Lsc4+i4zPxdrHC9hZ8nt+GJtfWxx5P67+eqnDgAAAHAiVKnRxYsXU85d+nz+o9KtlXuvi3gSVE5fSZLOWYOtPmPhOwRDXS7lwMjC3YjpHYg8s+V5lDkdZrkklcK/pHcv6qkrtncRxiX854x/pvK8jfZczXyR9/LFzEsfZX2sdTwmpcAH1VjyZ2N19dmiao/buyZTU2l7KsUp/io/Q2l+FtP4gkXlC+1RUvXFiemXmRwPXbXrj6TKP1G7noq1r6HQbo9pXHq/VLnU7/qbEba398rQgWm8hJ3ltmN7Yejh+OHNLj94DAAAAHQRf9cw8ur96OCU0Tc5EnTo2nO0XJcuYd6OO82Z55fxtCgAAIC+xl+N7tv70cFpwrCGkWeUeYLSG6g32FQJ1jACAABwqvFXoyOODgAAAAAABhN/NTri6JVhyl4PyUtWBwAAAAAAvcBfjY44OgAAAAAAGEz81eiIowMAAAAAgMHEX42OODoAAAAAABhM/NXoIo7+F3/xF/Zqf/ZnfwaZDgAAAAAAThMVa/Snzbvf/Mg/nLt0o8OlRkUcfXl5+Ze//CVV51e/+tXS0tIJuElaYujwa/ZZspLPO/vSR+paPckTmunyRo1G49n4Rl4jRpIHQTtf5qcSe5LxSsuEesyLxzNju0/w2j4AAAAAeEnF64x+e1ZIc77kKPv08ysXyreWxNHtGv3E4ujywvXir3wtG+joz1hLrRaWfS0a4gr+WSr012YWXBe+l1srh8WeblNivFXRN2sYAQAAAGAg6Vauy9unze/OzF+5UL4Fhzj6CzYzxqJA8H22EYzqWlQh+PP6PZYuLj/NDltM6NmFIcaLG+w+iyqsHjKL1rVodHmt+1W2JRSvvtp9HHQ3q+rM6xGjoHtUHsXKk78SGW1oTW5KjfSn8fLp6WlWb1nsocclGpHuJ6iWqnH9sHLw48nFxbgh8SU1XtrOaoFGBwAAAIDPdEmjv3zQfHtl/qNOmnCNo6/NsK06E3LuxRob20rleIJch3GhFqnz++/YWPCTA/Zug7SH0uhcyLJIYIbKk6Ua1RRHZ4oG1TS0Ka6sdqzVySrstZmZpIDbs1XPXhtk4vcGe2zjEuJdVN5ZWGAbiUEme4LGG1JDB3diNW4cr0tc/2ht5ivDi9+n5w5bn9jvLECjAwAAAMBnuqDR3z5tbn9ztrNEF6bG0QMtnq0gytN89B02tBLp8oWhQHezSP3tpGF1xlWortEDgV4k3YLQ6NakF0Kjy98nGprRuR9puWoEy2piw3JFSYTamBtP2WMdV8YM0p5Qo6eqPKzwZTxIYrz5dnYIF+ibo6uHN/1PmwcAAADAYFKxRg/1Oev4eVFOiXz0tRl2cCfMdYnFukiDmYz1uiGOzmyxcxlFl6biuyONHurRe9etcWW572yFjCYuligfPtppik8n9lSo0We0i4g8jV7ETgHi6AAAAAA4nVT8zOhvji993lmKS0KZ97rssKF7PL98ZTwW4mEQPYqUh5/1OHphjS6nacif1TeZyBkiRo2uVpcVqyxn1WaYkLN32EpGGWc1MfVmFUUrp5cGpD22cTnG0ReZ+blY43gJOysGGh0AAAAAPlOdRg9D6D9KBR/24r0u4klQOX0lSTpnDbb6jIVPOYa6XMqBkYW7BfkZSvOzmMYXLCpfaI+Sqi9OTL/M5Hjoql1/JFX+idp1I9a+hkK7PaZx6f1S5VK/MweTbHPziaED03gJOysGGh0AAAAAPuPvGkZevR+999Cha8/Rcl08ARodAAAAAD7jr0bHOqOCrj9B2U2oN9j0HqxhBAAAAACP8Vejizh6INPlfwPJLkS5XN5rSwEAAAAAAKgSfzW6iKNrcnx5eXlpaSlb3mtjAQAAAAAAqAx/NTri6AAAAAAAYDDxV6Mjjg4AAAAAAAYTfzU64ugAAAAAAGAw8Vej+xxHf/F4/TN2o/XJ+V47CYBTQ3thuX19aZ6/U399nd24eRu7FzjV4DwCALBTsUZPFzI61+mCox7H0VMxkRAcbb88fzP7Hj+qnMK1HZSjnHFRu7z4Sq/DRufe3fyEGWk3h7b2gv8b9aUNJn3u6Ysog0GN7YbDmKq/m6/SlFz/eLIdUd6n5RTW+obzCAAAyFSp0V8+uPub40iaB2J9l83Nf3SmdGvextH14Ee7vVOrjYlj8fDjhXZtQ3xFlVO4toNylCflR49nvtp9ok4ou+ZW1EMg2du1amWxX1j849V2RHnflVPk1UcQHQCQS7dyXTrX6L7G0TPBjzgkGTK6eiu+R0+Vkw07toNylEvlO83la0kxswbRQ0iNLrU/PXdL1hBpF6Nzq2w/TEcJdoetTaVhbhLbXl58Nbo6N7K4u6fbr0S1p+5HyS2iJPoz6miq/u7yUaSteWSdhX0l7Zvq05cZpH88244o77Nyipz6CKIDAPKpXKPzYPr3rIJcFz/j6MbgB79Hvz8Rq5b8ckvjTu2gHOVpuRoqzk1cSRNLQlI5Hsb/hHQIpPPBbNQO17gsEsHhb1koO9oL60cbNz9J9osklTyU3ZGGDm17cyeW41sTSV/NoYfDQisnPwxHx6ZHZltCcAe/fXS+FQr0Z3O37rzZfn5ZtE/Up6D949d2RHm/lVNY6iOIDgAoQrfi6IFU//bsjVMXRzcHP9bWmxdvzl8NRMA2a0nBS6qcwrUdlKNcLpfj3O/y5psWR585uhwpBrOWjbR4phmbRk/0fdrdsBDcqZjeaa4rmvtNc2iL3b81vJJUi+3RgvpkfRrKP75tR5T3VzkFXR9BdABAIbr3XpeXD5pvr3QQSvcwjo7gB/CaWM4WefqT0OiBeng4Ht+Xl+pQGj1tTds1tHezFNLosxNbW/v1oHcmVYvi6DUlAM/o+hX5B4AugfMIAKAgVWr0p827xx9/fuUC/8xTXtin0R+l8C+OjuAH8B0eKn6dH0RnlEaXY36hop2sp7kuK8OJROZp6ExNF8lo9OVFlloSxRR17Z5Kf5GSHgXLjwwaXW7EVr8i/wDQHXAeAQAUpdo4epyMzk5hPjqCH6AfaO+0a1cLBNFFMrr87kWhd6U89dHGFNvce5VEneU3GKahaCLPO9DiByP856I8zVQx1Y9aFg99JhX0Z0Zr0XN4/InVXXP9fJleyD8AdAmcRwAAxfF3DSPP4uhKDgAAwA7WIQIgA84jAAAH/NXovsXRAQAFSSPuBZ5eBQAAAEAWfzW6Z3F0AAAAAAAATgh/NTri6AAAAAAAYDDxV6Mjjg4AAAAAAAYTfzU64ugAAAAAAGAw8VejI44OAAAAAAAGE381uoijB6LcXk1I9hO2De+4BQAAAAAA3aMbGj1cyejDjhYZZXEcfXl5+Ze//CVV51e/+tXS0tIJuktgWChOWbWxQDkAAAAAAAAU1Wv0p827xx9cOj4+M3/lQiftJHF0WaMHolz78+Tj6HoQvd3eqdXGhBYffrzQrm2Ir6hyAAAAAAAArFSs0V8+uPvt2RvzF142v+tUoxvj6FmNfuJx9EwQvR0tpR4yupqsIUeVAwAAAAAAYKVKjf72aXP7+GOe4hJ86lij+xlHN2aiB4Vj+xOrbF9b/JwqBwAAAAAAwEKVGj3MQ0//PHfpxvxHZ0q3JsfRAy2erSDKTzaObshEZ3zl8+bFm/NXjx7PbLOWtPI5VQ4AAAAAAICF7rzX5ZTG0fE6FwAAAAAAcAL4q9H9y0c3B9EBAAAAAAColuo1epLx0mGui29xdATRAQAAAADAyeDvGkaevR+9vbD8cBzvZgEAAAAAAN3HX43u8zqjAAAAAAAAdA9/NbqIowcyXf43kOxClMvlvbYUAAAAAACAKvFXo4s4uibHl5eXl5aWsuVDQ0O9thcAAAAAAIBq8FejI44OAAAAAAAGE381ulMcvdfGAgAAAAAAUBn+anTE0QEAAAAAwGDir0bHe10AxYvHM5+xr1uf/LxgOQAAAABAf1GxRk8WMGIffvr5lQudNOXZ+9Er48XazNjik+DD9Oph63bFalI03rj/bqNPVkPdWVi+tsk/NO4vFbY5+NG964bqVLlaqTl0bU8pmZ47hKwfEIKruLHdJ3znu9VK1jpIp8To6uHE1pfnW18c8WqN+ruNWg+MlO15d/N2sREVn8bRPodpDwAAnlOpRg8U+rdnP5//KP54qtYZtbA2s3Cx5aaKAzHNI75Va3TR8peu1vQOrheYmxTqPIi+05x5fjmUaO2FmaMvoFQGh+wk4YKY3X83f5XFeldI8+BzINZPXqPr9uzXc2U6c7+DhDtOAADgP1Vq9LdPm9+dmb9yQfz18kHz7ZVQr5fDGEfPavSTjKOvzQyFEXDGpqenWb3Vup1GxWOiGHZUHsXKk7/etaLTrUGjy01JNc39huwsDIkgdFB/lW2J9rhGZ/Vni+IXaUhdNBIY9DX7LOxH/yo1X1gyPd148mQzKLnPrvFu4vq0nYwdrc18tcgcAnTuGr2jIHpU1aTRk3hkQhJo1ELvOQHI9sLQ1qaxnbX1ocVXsZ+FqaHDeK9TgSxjYYCTh3jZdlgzDqNGBowGW3kxKE8NiPpyuQkhbShptGRcmc+rG+FX8k+S+G5cyHUtE8YoTRkp7gfRDl0/rMOkz3nLAOvaNGjn0cXW/FVjTb4f7cb7UexhYUywCfh+tPsk+1VqpzBydLrxKtyP6uyavLEof26zliTKg21xr/YuiutPNTb3NplqDzkuySfR3Jai8tDoAADgP92Kowd6ffubs53ku/gWR5dVNRfHz1J5ao6j7yzMPP8iUeFanaxGX5uZSQq4DN6qi/apfvlnJstmJl0PpJ+VXoSoF8J6Z2GBbQS/DgT6Vj3OugkqrIy/i689Ju+HwivsZiwOz1N2hnRdo1eSiU7F0dNG5HJJNhXsRW5nqH1dxEQDAbc1IWnrh+OpLOMykQvTZ3OHd958JmxbW5+JxLHw0/rzjaB+aEydHVy8yTf82vrCwciz8cv52tSALE+l8WbiykzoPNkeJb7Lt/lWPdbKk3kb090P5vpKnLvw/RB9C9I/lMee/RW/GhGStx3uRzXCzqPYJ/wChgXCeiw22+hPwwSLLQztmRTSPFvN8sPY7QtizpD1AQAAeEbF+eihNP+Rfzp36RI7TqPq7shx9ECLZyuI8hOMo6dhazkIzehcl7Rc1essq9Ez8XipC2O/vMWNluEmuJLrovWbMSObGRNVYdy+Vijlh+5dF7F1XnHMYmc5nzpp9AqC6KwnGj0Tr01tiOOpenhe+okIpnInCWNmD4JmD4dXgl4SLeuMEkpP4rLxxYCtsvYTPWqb06mTH+j62n2PgtnVThqdvAbI/oqyk8WTJ54JUbPDZn+OWTW65ZrEODNTXV6sPgAAAK/o3ntdOs118S2OrpDEm0PIfPRYE2crZOLopOYm+u2RRr9azE4HRzpo9Kpe50JpF7NGV9NgimjBVE4V1OjhV0ogNqoTSvNhKf8hvmDg0e7XPI6btuOKGkcXUd6rlEYny1n8c34Vmf+QpasfrBq9gjh6JrfEsBFZFzR6jb4WMuW66PnxBTV3XE0LojNodAAA6Ae6pdFfPrj7w/sdvdnFt3x0OccjUa76V3ICSvTVwsU7bEVTxqZcl1B+G970QvWr1k87dtPoWvuJ9H9BaXTSzpCu5rpUE0RX9E0qYnjiwcVHcbhai6PHKRzFlI1kj5LrYspdYYq8y2gpXm08yQhnRg0na3Txyo5i6emqrEzaCUThynCyAdPto5anSDkYOyJNxe4fVz9Q9avR6Iwcr5tGZ7bcJJNGJ/1JPTNaRqMLT86ylYy10OgAAOA/VeejR29erODdi77F0aUHK5me4JFmo2QSP3TVLieu6E0Zu7D0K3/ViAW6SEVpRKnkvKvwwdVDrd+0ITXNpiFyz5NWrt8Lfpc8aSp+RZvURY1eSRBdeqpPYur+6utrvHwq1EZxVJiHzGu/TgZU5CV32QdPRftRKF1Kb4hCzsqzkleVROekwTd3hGKTWhBZJcmLK5Mkk8IaPZu7MhXpQs1Lcmhc9V6UmyHGKz0zmnerwdUPpvovSvRLz5bseOn2D2OfZ/yTtXNYGlTtXtBI8qSp+FXWn9GVdebdi8Xt0R8nTe+Q5PsBAACAV/i7htEpeT+6KXQNEgpr9KDiyrgh3ZkqN6MGO0OkUHfnGF7YV7J9EYi96HaLAOQxcNqUuMMwcH4AAIA+xF+N3u/rjJJPmAKJUmsYlafbGr1yx/RqGZ3TinENo1OJ/MJQdRZhDSMAAOgP/NXoIo4eyHT530CyC1Eul/faUgAAAAAAAKrEX40u4uiaHF9eXl5aWsqWDw0N9dpeAAAAAAAAqsFfjY44OgAAAAAAGEz81ehOcfReGwsAAAAAAEBl+KvREUcHAAAAAACDib8avd/f6wKyVLVQKAAAAADA6aakRherFZ27dGP+ozNp6dunze1vfoz+6HQRo1PyfvQMyZJB4dJC1atSqv1q+y31zsSOFgqV3yUnwMvjAAAAAHBaKaPRuRQ//vjz939ovv1DXaN/d2b+yoVKLPNtnVELazMLF1tub/cOFDOPHHdtbSOq/ar6Lb4+aNp1x0H0dN37wmu/AwAAAAD0Ix3kurx80FWNboyjZzX6ScbR12aGosW+p6enWb3Vup1Gp2Oi5Yqi8ihmnfz1rhWv8Z7VynJTUk1zv0xZJCkbGnfU6OE65swhLu2u0TsKokdVTRo9WSY9IYmva6F3W9xdXchdW+NGXpt9lW1FqyDJP4nXbE8KpTXb85bLaUdLu2ftlNaKj29XHEnLy8+z8GYGb59thzVjMyKDubWLQXk68KivsDWb/aHNTPp8fiez5nw6LpudJn8CAAAAIJeqNXp1uS6+xdFldcvl8bNURpvj6DsLM8+/SNSwVierlddmZpICLte36qJ9st+dnZ2rV69Gvx06uKMsZeqbRq8kE52Ko6eNyOXBmLZZK5SehXoJ6j+62BILjkrtcL3L7ouFSMPrASbkprxkKS/fr0syd6sea9zJAk6S7U8WPQ2E79aEpK0fjqftczu5QH82d3jnzWfCJ+oSqjsL6883gvqhE+rs4OJNPjvW1hcORp6NX7bZH3z+8nwrsjn1Q9RgYmry8zw7dX8CAAAAoAiVanT127s/vN+JSpfj6IEWz1YQ5ScYR5cC13G4XEDluqTlql5nWa2cicdLXRD9qj9RDPIu16WCIDo7AY2ehn6TNPtY7Noqaz9Ro9EFgscGjS4LXG3scdf6bQHpJ8F1xb1auHGEE2YPgmYPh1eCXiJNz0j7tfsScS+ERmf5durOAQAAAEABuqbRw6+vzH9U2jLf4ugKgWxeGc+Jo7NUmmcrZLQyr7rRul243/DTYRp3/1LtwCuNXtXrXNbWF0Q8uJBGV+Vm/gOmatx3aIuFspLQ6GQ5i3/Or6oaxTyUhq4LavTwKyWAHdUJpflwenGSXKjwuwGveUJL3A5pPxFHJzZZATtVfwIAAACgEFVq9KfNu7/9IHrTS/Rc6ZULpS3zLR9dzkXhCvne9SRwnX7FQ95MD7HfYStqEJ2ZtLIiunP7DX8fJaaHAfXJjuLoXc11qSaIrujF+LOI5l58FIeNtTh6nGrinOsiac1A3a4MJ45Jx62WSx7mFwaTkb4P01HynCr5Qcl1MeWuqHamFy0srTaeZMwz5WaCdVyK/Q4avZidcr8AAAAAKEIZjS5evJhy7tLncbw8kOlRQrpUWA7f4ujpg5scNbUkzUbRUk6yql1OXNGbMnZB9Sulukw3Gmxz80n4HdW+rd+uavRKgujSU4kSU/dXX1/j5VNhvngcveYh89qvkwEl0XSXZ0bjNjO9y6Fx1SqezjEW9yU9M2rrN/vAa9q1alLUr/LMaDzk5IlP0eCbO5HlaQvac5/TcUq9i/1GezKu0+00+RMAAAAAufi7htEpeT96JhP9dFBYowcVV8YNadlUuRk1WBsihZz7FyVo3dG4RAD7otutCQAAAAD4ir8avd/XGSWfMD0VlFrDqDynVaNXR7RBCmbAAwAAAMBz/NXoIo4eyHT530CyC1Eul/faUgAAAAAAAKrEX40u4uiaHF9eXl5aWsqWDw0N9dpeAAAAAAAAqsFfjY44OgAAAAAAGEz81ehOcfReGwsAAAAAAEBl+KvREUcHAAAAAACDib8a3ef3urx4vP4Zu9H6BCuyAAAAAACA6imp0cUyRucu3ZDXGWXy8kYfftrJIqPM6/ejtxeW29eXlNf+Bar9y/M3s6+9o8oBAAAAAACgKKPR3z5tbh9//Pn7PzTf/qGi0QOF/u1Zsbxo+FFX8E4Y1xnVOMl1RhP0IHq7vVOrjQktPvx4oV3bEF9R5QAAAAAAAFjpINfl5QNNowfa/bsz81cupF9fCfV6OXyNo2eC6O3m0NZe/Mfo6q2b0VI7VDkAAAAAAABWqtTochydx9q/OdtJvoufcXRjJnpQOLY/scr22Q1FiFPlAAAAAAAAWKhUo0fS/Ef+6dylS+w4jaq7I8fRAy2erSDKTzaObshEZ3yl+ubFm/NXjx7PbLPWzU9yywEAAAAAALBQsUbXvu4k18XDODpe5wIAAAAAAE6Abmn0lw/u/vB+R2928S8f3RxEBwAAAAAAoFrKaPT0BYuCc5dEDrryRcfvXvQtjo4gOgAAAAAAOBn8XcPIszh6e2H54TjezQIAAAAAALqPvxrd53VGAQAAAAAA6B7+anQRRw9kuvxvINmFKJfLe20pAAAAAAAAVeKvRhdxdE2OLy8vLy0tZct7bSwAAAAAAACV4a9GRxwdAAAAAAAMJv5qdMTRAQAAAADAYOKvRkccHQAAAAAADCb+anTE0asmXYNpbX2d3cB7JAEAAAAAPKWkRn/avPvNj/zDuUs35KVG3z5tbosvOl7DyM84+ovH61+ev7lRy6vXbg5t7QX/N+pLG0z6nPvDLhs/tvuKf5qqv5vvqSkAAAAAAICm5Dqj354V0pyvLMoSNR5+IdYcDT7+8H5HKt27OHq7vVOrjQmNPvx4oV3bsK45qqj5QLK3a5DFAAAAAACgCJ3murx92vzuzPyVC/zzywfNt38YR9XlL0rhXRw9Do2HjK7mrTlKanSpnem5Wy1J6O80l6+Jb0bnVtl+mI7SXlje2lQa5l2z7eXFV6OrcyOLu3tZe9bWg2/Fx6n7UXKLKIn+jDqaqr+7fDTz1e4TJiLrLOwrad9U33qZYbCfPabbN9ifb6fqB8o/whVZP7Cjx2o72nYUrcWVAQAAAAB6RIcancvyK2HgnGU1+i6bj78qgXdxdJErsj8Rq+cClXdfJX+mcjyMxwsJGIjIg9koAYbrURaJ4PC3LNKg60cbNz8JSj5jN4IWklTyUIDK0vPNnViOb00kfTWHHg6/u/kJk3LQw1Gw6ZHZlhDcwW8fnW+FAvrZ3K07b7afXxbtE/UJCPst7VP2m/vVrnlmji6HY7T5x+iH2J6a2o4AGh0AAAAAXtCBRg9zz89KaefVanTv4uhcPjYv3py/Goi8bdYSgo+G0JR6KDdOUo+0ZqYZmwZN9H3a3bAkQEN2muuK5n7THNpi928NryTVYnu0oD5Z3wxlv6V9k/01sl83jc5IP6j+hxwHAAAAgI+U1OihPmfq86JqDnrHuS4extGdIDXl8sPxOL9CqkNr3Lg1oUGTEu3dLIU0+uzE1tZ+/ZYqYeO4shJ4ZnR9M1aNTrWftb9G9kte8xj9c2TV6FIcnV8JQKYDAAAAwDNKPjP6m+NLn2dj5G+fPnh54Uoo2zt/ZtTDOLoTZk0px+DDgO5kPc11WRlOJCxPumB1Jcyc0ejLi2zuXSyLoxi/rn1T6SySs6Ng9pFBo8uN2OoTkPZb2jfbb+7XTaPTftDGkmr32GzkugAAAACg57hr9PT1ihHyWxaTdzJ2/u7Fvo6jJ8no8rsXhe6U8tRHG1Nsc+9V8k5G6RlH6UWN5twYrkEPRvjPRXmaSWKqH7UsHvo8kp7jVJ4ZrUXPs/InPnfN9a0y3WC/9tu4/UCaG+2n7Dwcfkj5k/KPuVx/ZlQbFDQ6AAAAALzA3zWM+j2O3m36fR2ifrcfAAAAAKB7+KvR+zqO3m3SiPVomi7SR/S7/QAAAAAAXcVfjY44OgAAAAAAGEz81eiIowMAAAAAgMHEX42OODoAAAAAABhM/NXoiKMDAAAAAIDBxF+Njjg6AAAAAAAYTPzV6IijAwAAAACAwaSkRk/WKjp36cZ8uLCogC9B+r1eWA4/4+jKapf+IRZIakgLlAIATgx1tV0AAACgPGU0eiDEvz0rVDjX5CxeUZSvQHr88efv/9B8+4eda3Tv4ujt9k6tNiY0+vDjhXZto9dn4rX15sWb+oqYnl9FAOCKcZ6fAk7ruAAAAFRCp7kugS7/7sz8lQtS0csHlWh07+Lo7WgJ+pDR1VtVrpEZrukzujo3sri7p7cv9Ts9F4foMmvaJ7FzrtHZxLNd8a22rH3hte4z7QuT2Ha49lC88NBOc/nanvjzfNiyXv/2edGjZHnBfqfq7+ZZ+Nt8V0dm8D7nVtk+X76UmdsJ7Tf4OV5TKfJM1GDw28tHRj8k4yrsH9s6TemKTummaRP+dNtevH6x+RPVt/nN5J9527Vgdrv8op3e5xHfyhNDWtlqdJpNtAKn0fPc4DdReXS08eoVn+R1do0PPH+2G/ql5lU8VQKzv2bbwViS9sUtLMayIzLt1wXHpdoDAABgoOhQo3M9fmX+o0zZaYyji9Pw/kRytq6W8MQc6wl+Cn9zR3wO4/dX4zoHs/K53BxHH9tlQgoEnz9jNyRxXFija/H4dnPm6LJoZ219XRp+e2H9aINriOhD0mNaTfptIYKxPzrfCgXis7lbd95sP79s8zYXUiwSi/LYqXYoPycGh1uZTY/MtuI2TX4gx0v4x7bdtyZiVRfo6YfD7+z+dNxe1Pwh65N+M/vHdbtQ/cpzlf/2dXphY5znZr+F2ncy0LssuDJhwVYey7utRPVLzisWX/aIS692c4HNJ+1n9jh6v6b3X8oPAAAABooONDpPbfnmbJzoknJa4+jJOTU40W6zyoNbmv5OpYwaclPjbXm5Lq76uEg7kYbjXwQa4l5N2GPT6NL9B4cQfrHQOy2CiXYoP0cGvwm13a3hlXiMrhqd8A892LiyYKe5Hmri6jQ6MX9y62f8ZvaP63ah56d860CZJ4Z5TvmNxftmMOvatUBhF0j9MvZrvbiidyujRjfv12SuC+kHAAAAA0VJjR7qc2Z+NPT0xtG7iibC4nN5cMJ+OB4H8DTB0RuNnkjPYcO1SlajqDHdSDnZOo7llxIoJbFqdFM7hJ/D8tmJra39ukj5yNHo9Hit/qGMlH67Lt83MLbvsr3I+ZN7DWbwm8k/rtul0PxM7yeIrruq0Y39VqjRzfONFclHV/0AAABgoCj5zOhvji99rqW4SF+f1jh6V+H3xFnm/r4cs0/u46caXY5VsyQvltZAFeS6RO2sH42bcn5yNXr+NYMkv4o8VKe+SYMPkAkXEe2Y/czSPGPe1FFHGt3iH9N2t+XGdKrR6flT5D6J7jeTf1y3Szqi0B4WR+sVP6jXcsZ5bvbbkbNGp/ol5xVzjqMb51uhcRW5pgUAAHBKcdfoYQj9R6ngwzjdRbx4MeUcreMLMIBx9IMRtrkXPQSXZBokz6IxNtqY4hUaklaI00j0B9caUUruHlOSFopq9Lx2wgpvZhX1YM6p0J59zHsAVHlmtKYk/tpclzw7GPdLt2P0c9SCqCz99nD4IekHOgfJ7J/cIWvtWNsvvr2M8+eLI6J+rW302yrbNfrHPkDDdlHGFT1MqWyCCHWKZua52T/DkmG1dvCT5MlOi/cs/Zrs15/lpcqTpqj9mhqXzQ8AAAAGCX/XMBq8OPp6Nx5FrRoRsDy/sNy+3p/qoct+7nv/gGrpk/0aAACAd/ir0Qcqji69bc3z1zhE8cI+XSap+37ub/+Aaumf/RoAAIB3+KvRBy2ODgAAAAAAgMBfjT5QcXQAQLcwLNgkQLY3AAAAf/FXoyOODgAAAAAABhN/NTri6AAAAAAAYDDxV6Mjjg4AAAAAAAYTfzU64ugACIqvZAQAAACA00FJjf60eVesY3Tu0g15SVGqvAR+xtHdlhbvsiViGRplVZS+QVrzBa+lM5IucMPy134CAIBwfdxre0pJf54gQBliVaCcL9IpMTp3OLEfCBixhl2vXhAs25N76nfXOZG0ODXTvoxGf/ng7rdnhQTnS4syaZ1RY3k5vIujt9s7tdqY0OjDjxfatQ0PZkB/R1jDldvvjOw/vwwBqrO2vnwwi5esAwDc2GmuR0fUdnPm6HK/nh2AO1k9wAUxixaEFnpXSPNeRRt1e/YnikToXHVOf+silU5zXd4+bX53Zv7KhaLlxfEujt7NuKa884irzOQqUCyDkqxqrr0wLjsXyfryG+iU69ckpC2tzR6vCS+tFc+HzLaDxqNqJj+Ipoq+0i46TDD1RKL4mRPYU9//iuyXHJdyvb7K9m3LPRpezxd3YbKHW0v3myXcKIT9UvvSpT9fr/T6yG4cElP8LC0Xn7g6uxa9wX4ltGC2n26HxuBnFjc+FRyOWdimbf7EI4qGEzUY/PbyEbFdHO3Ut2/OoGzby+g3Yv6E402rReMK/0ziQ/q8yoRCbfM/6jf8k0mfz5vnSXJwiKZBNDfoHZbwW9TyVHSujQ0eZexVchjR/Waah8LPjSm2uReGyqam2N7eE+Eum5/18UZ+zsyf1ddb5nk176pNbPPNtD8yYvu+MQf5jsz7i6V9C0aN7jTfbOKGOh4a7TyyHAcM+8Uq23WbnxTW/d18XrDu19p50H48LO4Hcvta92sLuh4I2nl0vmWa7fzkyyae7Yohpx626A3dTmHk6Gjj1St+0q+za/LGovy5zVrqOfpebSmK609Nbe7tMdUeclyST6K5LZ2FodETXj5ovr0y/1Hhcge8i6PHl305aq+DxtPr2rZJtoop2G4usPnk8tc8F0315TXJ5evXNF4bznuWHB/5n/v1+BgxGd8XC3dUeT98cyfdndw0+tp68+LNoCbXoxvSfpsOKhhIuyZOqFS/1Lgy8QOWe4yTnZme50h7zP3SgyX8Ft6fSQ6C8rZ4kogJZbzLWxPSOezhcNhv5MPE1NQ8Y/uk/XQ7BKSfo9MDPyE9m7t158228GfudgyNYdMjs624TdN+4WyncroqEF90nW/U/FENU6a6PK9Se6TTmNwgZQ913DDPk7DxOnsT7nd8BzwYeT1+2d1vhnPtun37Guchd87sxNbW/uQUe8ZG2N7ren1i6yFvmfIzPV7z/KHKHSHnG7E/0tuXmnv0/kK1T++S5ji603yzQB2fzXaS4zLtFyXmp9O8pY9X5H5NnAcp3P1grm/TA0U2Dcv5oTz27K9M+oE4ngif8AsYFuz1Y7HZRn8aJlhsYWjPyP34mKZVs/wwdrvQEnT9vqUDjf72aXP7m7PZhBaq3BHv4ujJPMicnyrBrtEte5pRo+v1DXG++FJYGov9ajV2gpKDUf6WmXR8FxfThgsP9Rxg6HeYGFdG9xfcBEU1OuVPunHSb2pTUY5gZo6l41XjIrGdtGal2rf6rbD2pf0cd6GF5Sg/RB29CY/1t4ZX4mFWqdHTIedfRjrON3r+SFrBMs+LaHSj37T4aHqTxzhPWNj47JtgGh8OPwwaT7SCm98yQTJZozvM87iOnnJK71/m8TJy/lDljhDzjVH7I719lTi0NA+N+8uRrX2KHmh0i53EccC8Xxy5z0+neUsdr+zH86PCkWxXP9D1qXledNMIrMrBQW/YjyehlBczwX6cHLNqdMs1iXFmprq8WP0+paRGD3U4yz4XSpWXwMM4elfprka3HJtkLdg2p53IJ1RNDJXW6PqzTdIN6LRNIm4t1aE0YsUaPWOPc/uk/csPx29lOjLdW7BqdGo+FG3f4gorVo0emqoEYOj5k4RUo5BVjkZ3tVOPq4moj/Waymm+2eZPJEGGDdddBs2kylBdg2b8ZvZP3jmV732vFW3t5reMsCPio7nzPIrJHU7sB43U97+K9T3pZ1sc3TR/qPJy5OYSFNHoil6Pj2/m/aWURqe0i9N8sztBPx7atanpOGDeL47c56fTvGWlzhem82B+p0X8YNXoFcTR6XhidzV6rZjekC7Pyhzn42paEJ1Bo/MHQo8vfZ5JZaHKy+FhHL2rpLNKyzlhlWh0vjOsDBuOv/I5VT58SPee2uL2XHrPnaWJX+ruUTzXRd+HpXaCRtrXo/vjaq6LqV9qXGo5N4zlHWGJHdtsD9Uvhdn+I/X8JN1LVU9OqQ2W3AlmP0br7dvsL36MI/18JOukdJJQ21FJlT7qskYvoHVc55vVGL6ZxqUcObERLz6Kw4daXDPeTHqui8me3NwPZZ7QOSpufmMWje42z4Wdv2iHI621k2qUny25Pcb5Q5WLn3Pt5ZKebnr+x+Bn2/alPpP7i21/N2Bqv8R8s3RAHJ8JO4lxGfeLvPnpkktJ7O/U8coy34znQQpXP1D1q9Ho9PHZNSaYczzRNbrtvGx8ZrTccZ57cpatZKwdbI0ehsp/lAo+FGktVHlZBi2OLt1uk57djJ8xSogv5bPPME0lxxFTfY70zAcz3raeHh1lEzfi5LBXTHlWJjrPBfvqwUj0jBfT4y7FjqHySJXn/9Knx9SR8V3a0q95XGp5TghEudeZ3tPMPmiV2GPp1whlv9RFNHxjOkpqv2M52X6B+ZDvN8rPyjNSNTnB0egH5RlE6beHww+peehmZ/Zedp4yc5tvxPxJ4FvhzWzSozLetjSu8FltcX2uZYAY7bHspwb/DJP7HXkTn/Cb5oHG1N7mHosfky08z+fm+CNr0lae5nm6X13b054kTv08Row3fKbWMH+iZ0Yz5dKjroWf/nfZ78jtqx/PtWd/DfuL0zxXPZZuMsUPxeabsX3b8dCeU5cdV3a/OMqdn4U1unV/p84LxeebTfy5+sFU/0WJfmN/mnJC9PHS7b8h9YPleBIMqtYOGkmeNFX2AsmfYqtl371Y3B79PEvcER1sjX5SDFoc3QeKzOz8xN/ODNAzZ+IL9K72W8Ie16Z6Yr+H9IsfKrJTBJzOp6HHHtvTdfrFTqY93ncqMDi/7PHKSHXHw8r2CyBzmrRpIYg7DKfJD/5q9IGLo/eO9Hq30DsEC9XslYV+0u/2D5ofqrMzigV2uFzI4Pmt++jvpDoNdFujV0c1+wXQMK5hdCqRn2dTZxHWMDopEEcHAAAAAACDib8aHXF0AAAAAAAwmPir0RFHBwAAAAAAg4m/Gh1xdAAAAAAAMJj4q9ERRwcAAAAAAIOJvxodcXQAADjtUOtzlaff37zW7/YDAKqipEZ/2rwr1is6d+nG/EdnkvJ0IaNznS446mcc3fCC2J5Cv4EoW836PqbT+CYyAID/pCvjuCz5qRMvzRPSh++e89J+19WUxUj4y+/8f/nmAJNZi6rQfBNioyfvNDS+UzJ90+vUVOP18IaX801bVqyg9+T9roxGf/ng7rdnhTQPPv6GxeuJ8j+OI2keiPVdNifLd1e8i6O32zu12pjQ6MOPF9q1jZ7HOTIrJ1s4gbVFMks9AwDASRCcCw9m+/hl2/1uv0q6pj3wEyXaGFwfPhwuck3Vqzs82X7lEn7x8Lr8NWGXdUtbXqirhAzrNNcl0OLfnZm/csFQ3qFG9y6O3s04h7hMFIHw7NWqvHbuKtsX21iOoIuvkjkqXbqlC+fyyTE7sbUlL7asVc5e5JVfe1lbcD5ZJVheY9k8XrmpLgRjtOva9OaDtH3NxsSODhc8DxoZXZ0bWdzdSwrT1ctjs6NtxLfablBfLIHO25+aYnt7T0Q1Y7+snVkDWVkf2ymYYZg/TF6bmoVtFhhXPBOiBoPfXj4y+kdax1srLzh/HOI65nFlmzLOq8j54bikz8+p8UpLqavby7BMuryWtb69ImNC25j0Wd8lC+x6xPGBkceB/PmvjM62X2TsJz6H8yoNk8cGjzL2SjZP8TONaVxcFF4f2Y0PiZEzlYOkdjAxzocj837hPJ+J+VDf/4qYV8xoPz1eAsf9KGpZ2y6hNxL7s5O8MTW1uben2yN13ajX2cNUo2ftt/TLXCHmLbE/xpJuz1BO4dvx0zAf3I+fukbPXehK9bN80562x3R8K3V+t2v0lCOlr2gOxL1IK6yNTrMJHtykdYvBTlEyOtp49YqLojq7lpw7rI6TNXridqMuuiodN5Jp2aFGf/mg+faKktPCg+nfswpyXbyLowv37U/IZ8FqG5f3mWSFW74/s+hYFm4/lu5+pji6ssC1dH0czgl5P8lkthiW1S2s0aOuietRsXvHknSBzQfDpMYrX2gKh3fznqkU7wnvkyQ6Ro5pqZfs688vJ4dd+ZAU+VO9UI7ajy+Q9ien2DM2wvZe1+sTWw/DbWfuN/ph0nXaLLH6MQU5fwKbH51vhSeYZ3O37rzZLjiucKOw6ZHZVtymaTvS9lNE9tSIqVh0XK7zKmjnXi3a1vJvqfEWmiey/fRq1YSd5v3X1Q/F2lHinbL96TmbGC9lv7k8c6SS9iPCzwTmcYnTZyLu43nLRC/MeJwkjjPm/cJ9PhPzwTxewv6r7vPBbT+SK6vbRR+CNKix3ZFET8TfBmeKh+PRiUnJdSG3l7VfJ8zzltofpalYJCrs2/GTnA+Ox0/1GqbItR8fyFhkz5tkc1P2VHt+N24p86rG6tgTTWKJuxt1i9nOcD+dDHQ8CyQNE9HGvORnOo5u0kXZ8Xag0cPc87NxoouGlA9TEu/i6Mm2dMkwKY59XzX/JmsJfezL6gl9bjmKP9I/2S9MLZPncj2uYD98ZOMN+VcU8uHJfN2sxgkojW72p7QJZP3HTJfIdL82jS7FMwpcxFPzJ+5XCyNR44oMeBMem24Nr8RjrFKjp34oPy7XeZWj0TPjLTRPVI1u3F5anC/aCs7ahfCDtR3z/Ke0DjFes/2WcalBKV2jZ/1MTRLjuDIqXGzK6+3423QIcmzPMB+I/aJqja6N98hs/8awu5Z124+igza/ccSC7XJem05GjW7Yv7QjfDIc2/nI1q8T3dTonh0/GT0fHLe7Hkffem0JvSdm6B9qbcqe/ONAYVNzt5Sm9VMdokxLWSoonRp0C2VnMnnimVbgAUVFoiizhVZcFWj0UJ+zSzYRng2xu+FhHL2rdF+jKycVTzX6SScyJte4chxIdw6h0Ul/RrJvWN06YYzhcGI/aKq+/1UaL6f7ZcZjk6b/cu5RWs8x4VRRAiGMHFdyK6B+SzlVUPES0n4KNQ4kohS2azMnjU7Xt2l0w3iLzRMqji5tL/JaovsaXfm56T6sZCc53tJxdOElKe5onlf5k0QZVzsrLlMz5ONk9Pm8636hb+ICkBrdOK+M9pfT6A77kWhw9mD76PrI/vPaxFZb6a57Gt3erxOmeUv6n6mXkXmJLp4dP4+sGt1lu2ePXRatWU6jV3h+z9vvlONY0p3txr50P8pUjT6ultHokm2qGd3S6PKzoTJPm3ePP5aeH2VEjL0YHsbRu0q6VcJrOCYlz0lP1vNrMiaHfq33cOWpthZFLJK0y27kusixXukYQWj0AuPtOml8Qj6Li/tZJo0u/VDyp76fc7ePqzlRoqNftONDW3IOo/tlBTR6kZwQ8/yRjumy8dS4kvw5TUp2RaMX0CLUuCw5Y8Z5RWt003iJ7SU27sVHcVO0Rs/RsvT+6+qHgu1I1aRzSaJ16PlZnUY3zysKalzqhFHPi/Io4qGRxxliv3Caz5b5QI2Xst91PrjuR/wme5s9G77cOv9o5uHrydmcGAGpvSSHy7kEpP2WfsWtp6Iv+THMW9v+KE3jgrkuXh0/SX86bndVXCqX4uZJFe5Bptwbsz2ux2E7GT+oBmeOG9zts2xFSXpRk0ykazmjbjHb2R8aPX29YsSHqRSPk9HZ6cxH7y7p7ZXooZPkEl9+zEt7FjNFyWXU702Ldy8mzyyanynpWKNLt/WlZ7nUXBRTboltvAXvhbk7OcJ0jz5yFDd1OHOXXNLcByOJP/V4DG/tzayyRabqh8MPk1yXO2++Em/DDFS7od8anQOj+LPQs5WG+aM8Gxc/BxkmIRjHpTzjJf02GVEjStHbY8RtTcuLQY3bpcgZOjuuZCMa7DHNK6Y9eB13vfp6yzhecVDObq/xh5J/2nK/b4zby2ank9+o7cuI+UPM/+wDdskmMI73iyOz/fHzT3p5/Kxk1G5jam9TfmbU5OeCU6VIrhr1mKzhOEPvF07bRdlf2hk/GMfrPq5c56S+ZfafhMHd85mccnU2JM/6M+O8lXO6pqYae3ubdr+Z++Xkvyk4hpq3yv4r74+1dhIPMmceWrambH8Pj5/F9mv7drc8UFvwJ9RrFcodh91zXfLOg5k7CbZODbrF9JNbwyvJRq+1g58kRzzLXik1IhtJ6SLDfufvGkaDFkfvOVg4wwkiIVUEEs6Tl87ecwLv6PSaIq84OHUYM98G0A/AB4xZRkY8nLen+/jpz7sXc+g4cdcf/NXoAxdH7w3SdRtWnSiM+YlyTuTPIuFPD6HHBQAA3aefV9M7zcdPx2dSq8W4hpGRgqs69hH+anTE0QEAAAAAwGDir0ZHHB0AAAAAAAwm/mp0xNEBAAAAAMBg4q9GRxwdAAAAAAAMJv5qdMTRAQAAAADAYOKvRkccHQAAAAAADCYlNfrT5l2xjtG5SzfmPzqjfhmuZPRhR4uMMl/j6AWWleoIv94c1NZXHwAAAAAAACdAGY0eaPBvzwppzuU4U9V4IN+PP7h0fHxmXi51x7s4eru9U6uNCY0+/HihXduoXLbKa1b7ARY2AgAAAAA4eTrNdXn7tPmdpMYj+X7hpVJaCu/i6PJax8XWYHdiR1uWPFwEQby6XwTURQV57e7G1NTmnmEN27Sp0blVti8teyZW2Cm6+q6yXHC4tnC0RkO8QEPUUfDnDRYucBC6hT3OfBY/Z2HvYfkb4czQknb6+Xm0BkRkYdR+vKyx5iJbdF9fGzlvexH11yz2KPNBskduqthKFqbtlV0TmJvEtgN7RlfnRhZ397LjkpYdjjfxkdn/YTukn23+0bbjeaJfZrafqh+WEOOS/Kxt8c7ng7JGd9rg6Gp9YmtLXVs7Z7zE8trh2uPZ/YL2P7P4jdqvO/BDzrxaW3ecJ/oC2tLNQON2LGdP9vjD12A39WuZt5Xsp8yyf7n48/KR0Q/UcYDbT+8XAIBTQ4ca/eWD5tsr8x+JPwK9vn38MQ+qa8q9FN7F0cVpeH9CVb2VYoqjK9k10gq3oSYYEcd6OdrNj/ssOomGdZgk4wpr9OAE8HBYXCSIloNm79X4aU9d67i9sH60ERpM2ckH9eh8KzyBPZu7defN9vPL/OdJg9pvk/ZDb7PpkdmWEASScwpF96N+a7o9jvVJe2QzpAWoZf+ICWM//RPbK3Js0kXSbHialyV4tCafsoB2vPks/reMy+qfbDvGfm32G+2kxiXuX4npGtQ5mI01XyXzIbPH7TTXxbio+Uz6WZ0P8n5KtGP0P+k3cr929INlXMS8cpwnCunxgdyO+uFrPZlXdnv09ql+8+Z/Z/upbb9w8iexXcj5YPEnAODU0IFG55L8m7NSokuYh55+b0pVd8C7ODo/FDYv3py/2r2MFEeNbjmmd2pJ3GBybki7k7SOprPlOGI2VJYNgto0Og+0s/u3hlcSXVVCkzmtXUzUJ+0xanRDXNDedY7IMGpcTd+I5KsZyTAmaR3K/5Zx2f2jtHNE9UvYz0g7zeOq6aHWNC5byXzIBFNzNPoR7Wdaoxv3C8L/1Han9+vqNLrR/87zRB2ysr2M25HW6OR8MB1/cvvV5201+6ll/3Lzp7NGp/0JADg1lNTooT5npAg/pXH0ruOPRs/cs5ZvB0enxmHF2rw4ek0JQNo1+uzE1tZ+/ZYu6eRzcP7tXTVuys+IdplO1LfbE5mdxtFd/Z9TP6u61DhiMY1u8r9lXHb/KO1YNavB/iOLRjeNq9ZeWH44fisVwfIT2xXMh4zATeKgFWp0Mo5O+z+z3W3zxMkPuXF9rZrzPFEINl/7Ot+PcrajSaOb7WHE8Yfol5r/1e2nlv3LxZ/k8ZOaD7Q/AQCnhpLPjP7m+NLncYqLgSo0uodx9K5DaPTo6BwGTlh8DqaO6cEJbGVYOhUtb7E0xOKQj673rsDPWONqzk+uRmfJjYiwCq3R+bVBmtstx9G/2p0Mx+Kc25DRjsXrk/bIOkDKdVH9n491exk1+vIiS6+XEpeSOQCE/+lx5ftHbceWe5B3jZHWN49L3iOkCVDZfGCOGp2wXxRefBRP6UIa3eb/7IjIeeLoB5s95nnlOE9U0vsn1HYkDjKUPYnbx605h0q8mTj+VLOfWvYvF3+6afQjmz8BAKcGd40ehtB/lAq0tywmGS8d5roMWhxdj1snD2al9zSjh5CC4/vXbFtEzvgtTqa/IVFuSr0H6qjR6Xcv8tDdm9l3mQi3bk/0zJz6nOXo3P2RXeUB2XjIq6+30mf45Oe9wmeqmPS8LHN9Ns76oBtVX3mmULLncPih9oCg3IW6KfO9bd5exL3sQHwcjLDNvUzc1FhfeWYu9f8q2zX7udAzo2k7XIJYclFcyqlxSUHi0cYUr8B/Mvy4kvmgbanG1N7mXtoRM+5fJvuVeSLtNdR+Wt//ivQ/ncNgmCdHbn4g99MwiSLrf2r+58+TmCLbMa6vPQBNzPNkINLxh+zXMm8r2U/p9p38mRxPCs430p+Q6QCcLvxdw2gQ4+h9gAgcnk9DyJU1nIai/cdwc/mk7NfuoZ8aTuu4+gXf/E/Y07XjzwnZDwAADvir0Qctjt4nRO8CQ8ymJ6SRvGJvi+sXTuu4+gXf/E/b0x/HH9/8CQDoU/zV6IijAwAAAACAwcRfjY44OgAAAAAAGEz81eiIowMAAAAAgMHEX42OODoAAAAAABhM/NXoiKMDAAAAAIDBxF+N3o9x9EJrqQAAAAAAAGClpEZ/2rwr1jFSFipSljfSljZyxs84unXV5bb/b+0FAAAAAAD+U0ajv3xw99uzQprzRUVZIsYDjf7dmfnoj07xLo7ebu/UamNCow8/XmjXNjJLbyKIDgAAAAAAOqfTXBdFlleq0b2Lo8dLfIcoy1aLrxFEBwAAAAAAldChRn/5oPn2yvxH0V+V5rp4F0cPI+Vj+xOrbD+7yDOC6AAAAAAAoCo60OihIj9LKfGXD+7+8H4nKp2Ko2vVAsl+Ys5aW29evDl/9ejxzDZr6StUI4gOAAAAAACqoaRGD/U5k58XzaCG2N3xMI5OgSA6AAAAAACokJLPjP7m+NLnGf39tHn3tx9Esp2L+OOPuxFH9+/96AiiAwAAAACAKnHX6ErSOUdOPE/eycjOGUS8E/0SR0cQHQAAAAAAVIu/axj1SRy9vbD8cFx/xwsAAAAAAADl8Vej90scHQAAAAAAgGrxV6P3SRwdAAAAAACAivFXoyOODgAAAAAABhN/NTri6AAAAAAAYDDxV6Mjjg4AAAAAAAYTfzU64ugAAAAAAGAw8Vejizh6IMrt1YRk77WxwAtePJ75jH3d+uTnBcuBn2B7AQAAACU1erJW0blL0cKiKXwZ0u/DT/LqRs6IOPry8vIvf/lLqs6vfvWrpaWlXvqvLDsLQyvjh63bJ6RCXqzNjC0+adx/t9Evq6EGMm1s90mj/m6jVvg3OwvL964vZYdIlauVmkPX9pSS6blDyMSTo70wtLUZ/zG9eqt1mtYcENNZG1c65UZXDye2vjzf+uLIfdZXh2zPu5u3i42o+G4S7IbXNrFbAQBAYcpo9ECEf3tWSHOux5mkxMNFSM92Is0Tkji6rNEDUa79OQhx9LWZhYutTtV1INO/7LyVk4Kfz5mbVOk8iL7TnHl+OZRQ7YWZoy+gJE6StfWhg9neaNMTIDsJuSBm99/NX2XqBWnwORDrJ+8H3Z79eq5MZ+53PHCHBAAAitNprksgyr87M3/lQvzHLpuf/6gSy4xx9KxGP8k4+trM0OKT8NP09DSrt1rROWxnYeiaiABOr66yreAcdPvnUeXp1cOv2Wdj/GdRFFuEtMO6aRw9rBz8eHJxMW5IfJnUjpFj4U72cI3O6s8WxS/SZig7lfZFoTBmerrx5MlmUHKfXePdxPVlU6dX37XkE/zR2sxXi8whgOau0TsKokdVTRo9iRcmJIFALfSeEyBU4sRK/UCeLr6K/SxMDR3Ge50KZBMLA5A8BMu2w5pxmDMyYDTYyotBeWpA1FfcmiPF7bnNRHnm83llvAVCpzsL68+vjyzG/owaCS2ZXp2bXNzdlMs1f+qezPgn+FMaVDiwZHIlQ2PpD137zUXXpkGnjy625q8aa/L9dDfeT+MtKOwPeuT76e6T7Ffp9hIjGp1uvAr30zq7Jk8G03h54TZrSaI8cN292rsorj/V2NzbZKo95LikORDtO1JUHhodAACK06FGf/mg+fZKIsqDv35g7PvvqSwYJ3yLowcS9DOhdoUIfhbJUP6ZyTKVpdpbiGUhWHcWFthGcn6TWxOEgljWuwd3Uq1siKO72qN9Vno32RnYs1WPRxKm5vBvw1Ymg+ZZ1M1YHJ5fm5lJmuS1tuqSTO+6Rq8kE52Ko6eNyOWSrCnYi9zOUPu6iFkGAmtrQtLWD8dT2cRlHBfEz+YO77z5TNi2tj7DbiT5ElzabgT1Q2Pq7ODiTb7h19YXDkaejV8uky7iao8S95X9097ZqV1NRGROjFxou6kojhv+eXBnSYwlEKCyvozKqfbN/snOr7hcri/Hj137dZuf9I2aUNeyRONqv+KXB0LytsP9tEZsr9Cfk/VwP91iwSjG4s1kHK9hAscWhvZMCj9kq1l+GE+nBTEnyfoAAAAIOtDombyWsODHSJt3HFOX4+iBFs9WEOUnGEdPw9NSsHlnYeb5Rus29YuZ518Yk86NGj1V5WpqCpHr4maPkuuiGZa1M5MZE1Vh3O5WKOWH7l0XsXVecUyP96sRf1dHL1/bHF09vFk4Xb+CIDrriUbPxFNTG+J4px6slX4igp1cIwpjZg+CZg+HV4JeEg3thrs92n2G9Fs5XivHral+1Tjui1RTKjo4vSSg2jf6J2NqVF9tRHxzP742cOu34KYXWDW6+ZrH+Ctqe7HYn/FMi5odNo93zKrRSXuImZ/q8mL1AQAAGCmp0UM5zrRIOS/87Qefx7r8abPJ5sqH0n2LoyskceUqNfqMXFBAo7vZ012NftXqhzIOdoijV/U6F0pbmDW6qvmKpD2kcqegRg+/UgKlUZ1Qeg5LujbWuDy74zWPs6btOOFuD6Hhgg24Mn4rjdfm5VjrAe8XprivVG5r3+CfbHfRxRsVaC/Tr33T6zkhhG3d1eg1YrxEroueH19Qc8fVtCA6g0YHAAAXSj4z+pvjS5+bYuRPm3ePPxaRdTUNxh3f8tEVDR0rVKa/oUXONHGOoy+yNI1b1uVp11Lzrva4aXT9miGW4C8ojW5/U01Xc12qCaKreRqJyOBC7eKjOByrxdFFOkGtoPKQ7FFyXYjcDEl+ZbQOrzbOttIfGjSWrNHFKzUKpae72mPWcEfq9UPsKJt3dIMjX/GcE5YmNEdd57Sf8Q89zECMrgwbJmapfkmMz4xK/abz3U2jM1vuk0mjk+Olnhkto9GFu2bZSsZaaHQAACiOu0YXGS1SgfqGxYrevOhfHF16gJJpiRzyV/EXciKKrTz5JtDEB5NsczN56vJQzxdX+3WyJ3mesxGlkm/GXRwSduqPqzZE7nnSyvV7we+SJ03Fr2iTuqjRKwmiaw8URkzdX319jZeLPOn4YUEeMq/9OhlQkZfQZR88Fe1HofRs7oTyjOZVJRE5afDNHSl7W7QgntWLXnInPenooNGd7Dmc2BLjakSpz7F/JLcEBY0ptrmX90pBIock0KAHI/znojxxsr19xT/MkNMibyx16ye5LmX6pSByQtJ+k5e6EP48jLep7h+D34aljVW7FzSSPGkqfmUcLzO9e7G4PfrjpO0oD15/igYaHQAACuPvGkan+/3oWbRclwGnsEZXcg8KlJvREhs4Uqi7cwxJESXbF4HSi263CPoZw6axUZl/HPvNYeC0KZFwP3B+AACADvBXow/UOqPSWxS1txYOKmXWMCpPtzV6dUQx8l4tc3PCpBHfAqvqhFTjH/d+czCuYXQqkV9I2tASkLCGEQAAuOCvRhdx9ECmy/8Gkl2Icrm815YCAAAAAABQJf5qdBFH1+T48vLy0tJStnxoaKjX9gIAAAAAAFAN/mp0xNEBAAAAAMBg4q9Gd4qj99pYAAAAAAAAKsNfjY44OgAAAAAAGEz81egD9V4X4BVVLVwKAAAAAFCOkho9XcjonLTgqLK8UYdLGJ3y96NbV+XsCsmSRMrqSHS5pRFlwSTPKfMOx44WLpXfPSfAy+YGB+M7FuW1gQ4ntr483/ri6CTfLKqTXauowIiKT2O8YxEAAKqhjEbnS4keR9I8kOW7bG7+ozNM/PHdmfkrFyqxzLd1RnvI2szCxVY1qjgQ2Z+Zlkqiyo0tfFmVNd2n+Hql6QA7DqKny9oTK7mA00p2knBBzO6LF+3LF4yGZa1OBN2e/XqRF8C73kHCHScAAOicTnNduqfRjXH0rEY/yTi6tNLQ9DSrt+LFhnYWhqJ1sadXV9mW0Lqi8vRquAo3/1kUfTbGrcPKwY8nFxfjhsSXSe0YOYZttie1xhAad9LoxnFxjc7qzxZFz6k51HgVO0WhGNT0dOPJE75A/X12LVx2JqovD1lf0Slc95w5BOjcNXpHQfSoqkmjS2vIRySBRi30nhOAbEdLsmfrS2u8xwuzH0nLws+zMMDJQ7xsO6wZh1EjA0aDrbwYlKcGRH017ruu2akuUG+JK/N5JVaPkn+SxHfjQmkt+vxlgIr7QbRD1w/rMOlz3vJDujYN2nl0sWVaCYtr9GA/2o33o9jDwphgE/D9aPdJ9qvUTmHk6HTjVbgf1dk1eWNR/txmLUmUB9viXu1dFNefamzubTLVHnJckk+iuS1F5aHRAQCgc0prdB5M/551MdfFtzi6rGK5eH0WyUf+mcnykqXCWIhcITR3FhbYRnLey2riUMjKOvXgTqpxDXF0yh62s7Nz9WoijtNWiH6pcmpc2mflV6bxBjZs1WOPhCk+/Nuwlcn7ofAKuxmLw/NrMzNJk7zWVl2S6V3X6JVkolNx9LQRuVySTQV7kdtJFkMNBNzWhKStH46nsozLRC5Mn80d3nnzmbBNXVp1Z2H9+UZQPzSmzg4u3uQbfm194WDk2fjlUktjyvJUGm8mrsyEzpPtUeK7fJtv1WOtPJm3Md39YK6vxLkL3w/RtyD9Q3ns2V/xqxEhedvhflQj7DyKfcIvYFggrMdis43+NEyw2MLQnkkhzbPVLD+M3b4g5gxZHwAAgCOdxtEDqf7t2RtRHF374of3O1Hpchw90OLZCqL8BOPoUoA6DRLvLMw832jdpn4x8/wLYwKJUaPLelpOKSFyXYz26KF3LXe8sEYnx6XkumgDzI43kxkTVWG8v1Yo5YfuXRexdV5xTL9vkBmB0wZbvrY5unp4s3DafwVBdNYTjZ6J16Y2xPFUPTwv/UQEU7keFcbMHgTNHg6vBL0kWtYZJZSexGXjiwFbZe0netQ2p1MnP9D1tfseBbOrnTQ6eQ2Q/RVlJ4snTzwTomaHzf4cs2p0yzWJcWamurxYfQAAAE50/l6Xlw+ab68kofT88qL4FkdXSOLBVWr0GbmggEYn7JEeRs3mjvuu0a9a/VlmQznE0at6nQulXcwaXU2DKaIFUzlVUKOHXymB2KhOKM2HpfyH+IKBR7tf8zhu2o4rahxdRHmvUhqdLGfxz/nVaP5Dlq5+sGr0CuLomdwSw0ZkXdDoNfpayJTroufHF9TccTUtiM6g0QEAoArKaPSnzbvHH0chcp7ywqKslqD8tx9EMXWe9pJUKoVv+eiKho6VJdPf0CJniDjH0RdZmn4t6/K0a6l5sz0vYu3LWJpPUjbXxTguN42uX3vEEvwFpdHtb7zpaq5LNUF0Rd+kIoYnHlx8FIertTh6nMJRTNlI9ii5LqbcFabIu4yW4tXGk4xwZtRwskYXr+wolp6uysqknUAUrgwnGzDdPmp5ipSDsSPSVOz+cfUDVb8ajc7I8bppdGbLTTJpdNKf1DOjZTS68OQsW8lYC40OAACdUy6OHiejMzUfPZTpUUK6Wl4C3+Lo0oOPTEvAkL+Kv5ATUWzlyTeBlj2YZJubydOSh3qet9ovZY+U6jLdaPAGw++ofkl7jONKGm9EqeSbsamHxHhNuTdjUivX7wW/S540Fb+iXd1FjV5JEF16qk9i6v7q62u8fCrURnFUmIfMa79OBlTkJXfZB09F+1EoXUpviELOyrOSV5VE56TBN3eEYpNaEFkl0Uv0pMc0C2v0bO7KVKQLNS/JoXHVe1Fuhhiv9Mxo3q0GVz+Y6r8o0S89W7Ljpds/jH2e8U/WzmFpULV7QSPJk6biV1l/RlfWmXcvFrdHf5w0vUOS7wcAAABO+LuG0el+P3oWLdcFVEJhjR5UXBk3pDtT5WbUYGeIFOruHMML+0q2LwKxF91uEYA8Bk6bEncYBs4PAADQBfzV6AO1zqj0FkXtbYOgM8qsYVSebmv06ohi5L1aRue0YlzD6FQivzBUnUVYwwgAAKrBX40u4uiBTJf/DSS7EOVyea8tBQAAAAAAoEr81egijq7J8eXl5aWlpWz50NBQr+0FAAAAAACgGvzV6IijAwAAAACAwcRfje4UR++1sQAAAAAAAFSGvxodcXQAAAAAADCY+KvRB+q9LqAvqGohUgAAAAAAO51o9HAlow8/lRcT5cuLikWMOl7D6HS/H926mmZHzYYrAlXQcPg6yGqaqppolJ28pDJZnqfYmpnJj0ouRJpZe6jQu/nE6+168g474zsE0zVxphqN1+MbN318Sai2jFRB71GrcrrSeTv0UlZ4pyEAAAwW5TX60+bd4w8uHR+fmb9yQZRwzX4cSfNArO+yufmPzpS2zLjOqMZJrjPaQ9ZmFi62iirJCtdC8nhZpZ2FmecbHWr0YuuPJnQYRNfWWh96OP6ugMbtVYQ+269cwi8eXq8Wsd8IX0D+ZvcWTlIumQxvrO8HcMcGAABASY0eyPFvz96Yv/Cy+V2q0WU61+gextGllYamp1m9FWvEMK4ryldX2ZbQtaJyssp9sqb9i7WZ8E8mx6ijoPXq5OJi3JD4MqkdE7VC28OF9Z361rV4Ufekk9RKpoXHjfazWKP/4teJCaLzpHpksGiNGq9ip+oEuTnRSFTeaGxuburDlVzRuH+frcgaPVwnnTkEGN01evkgemS+ptFzFzZqR0uyR46TjJVCxfFtgGih+DDszaTP59U15EeLCmu7Rk85UvqKYsBxL6mdo+H8DArVBe0Ztda9aEGUjE43Xj3ZDEZaZ9e4Q3LvfChbJHG7MGZ6Lpyfu0+Sdoxx62jlq6nG5l48D6VO0+V7RsP95YY0drWdsNPp1bnJxd14B0vuS0jbNxsah0YHAABQRqPzhJbjj3mKS/BJ1+hhAgyrINfFtzh6IBE/i9Url5XPolwL/plFSjKUkUyXxSIrY2dhgW0k53m5NUEoZBtSQwd3Uo1riKNT9ig5KnI7Ozs7V68mojlp3WK/pNEnZcEd/VYfrXm8Qf2telwnTPERdga//jIZFQ+LfyHqhK1OJlI+HqOcHJTNdem6Ru9cM6npLnlCkw/o4M7SxljU/uHC8sp4KO8C2bc1EQ9Uisdr1wDJCu1yIJnbsF8vHb+XNLek9dXV4JMYuSXuboyjm+0MN+xksKFYoGjZ/dAh6TDJbUvE0YUsFsa3w/kZt2O8JhnbnUx0vDKWYH8Jr6/COkxOB8q2EzpNvpTim5V/bu/s1OL9cX3oYFaZjNDoAAAAymj0WIZHnLt0IxsvjwLtFcXRAy2erSDKTzCOLgWi0/iuNelCkp4aRo2eqnJVwhK5LkZ76HbUkHz8A5v9UfxbVsOh3ckNBH0U2fEqSlypYtHohnKtZdWMUtvSSaN3GkRn2Tj61jNLSnoixfQPtV/PPLrYkgLwO82Z55eVOK4gCs1m4tYFM/DtWlDT+qnmVvR6kvOvd2rQ6JSdQfk2a4WSWtx5eFFIoyf9qiFq9XLCPl7immdnYf25JRHfqNFl/Z02qw65UYdGBwAAoNDZe10McfSElw+ab690EEr3LY6ukMaDK9ToSvJ3AY1utIdqR3lIVWo8R6MfTDY2N5mScCKLY22Ap1SjVyKYNHFp15rlNHoJTek6ZMl/0sVJ3B2ZaK7m35uqEXaW1OjEhVNvNLqSEB83u5PcGDFOBmh0AAAAVWp0/hTpx9FbXnisnSmvfHHFt3x0RfsGivfe9TgQLb+hRc4ccY6jL7I0Yi3r8rRrqXnKHqWdJNdFFrVyNonV/qiLQ2VMcr9KHgsxXvWaIb0kSD2g5swQ2l0aoZrbE9LVXJcKguhM12GKRDMQa9NEkyVST9V8qWSkcl3KvWkkowVVgwPz1EsFLrtn2YqS9KImmUj59+lX7SiD5Splp3caXbNTn0fmXBeWyfNJxsVYms8DjQ4AAECivEZPMl6kXBcpC+bU5aNLDz4y9dFN5as0hyRNRLGVJ98EWvZgkm1uRg1NZ/O81X6N9ohM7UYjaSdtRkp1iSo0Ui2etT/Na7/4ZfKI6GEiqaVHVdOrB9N4qRwbuTx99jR+3jSsxqImo36V1J7wqdI0CaeLGr2qILr67sX8N+iZc1eYOUciqdyIUrc35Z+oryMsl+uiJJAY3h0pqW2BrdP0ccmG7Se3xlfESKfuv6vdC36SPPHZoDed1IhspGZ/kluilwuTxgr7097O1fCC5GCEbe7F+3XciLR9pxtTvELYFNkOsV0AAACcWvxdw8i3OHq38fhFhyTZuwH9QmGNTgW88wLhFeHPuxdzoEPUA061L3+ERgcAgMHBX40+UOuMSm9R7GhpHlhbkFJrGJ0sSrz8pM00rmFkJH0RYebBR2B+E05JsIYRAAAMFv5qdBFHD2S6/G8g2YUol8t7bSkAAAAAAABV4q9GF3F0TY4vLy8vLS1ly3ttLAAAAAAAAJXhr0ZHHB0AAAAAAAwm/mp0xNEBAAAAAMBg4q9GRxwdAAAAAAAMJv5qdMTRAQAAAADAYNKJRg9XLPowu5goVe7GgMbRxUvvTuoldmIdIWVBptK0m8tbe2yqfmv44Ve7r0bnbt38pMsvD/eYwBkPh7vpgaPH61+9mV2az58l1W2X8OV/o6uHN/vwhfgAAABAn1Feoz9t3j3+4NLx8Zn5KxeKlLsymHF0lzXqnVmbWbjY0tV4INO/zJaWgKvGXRZIwPOPAlE4VS+iH914vN48f7PyVntPd8dV6Xbp6vwEAAAAQEJJjf7ywd1vz96Yv/Cy+Z2ixanyEgxmHL2gBgpXEZpeXZ1cXBQLh0+vHsbLfe4sDMWriaelImAutZDEzrlGZ/Vni+JbLaQerqPDii2aEmrBkUACsuby1ms5XCsiuZzRuTm2z27crLWDuq+EXhTfyvHdx+vLu9HKL6OjbOLmzU9E46+k3mStmdYXxaLy6OjUq1c8gFxnW7z7fHVq6JewP7BUVA7MvsG2Q9Oi9hNLMyMKfjyyuxs3JL60jivHSLUL0n56u5QAGh0AAAA4Gcpo9LdPm9vHH/NUluCTpMWp8nIgjm4nlOmxpOYS/OCO+Lyzs3P16tW4Tlwq/jTH0ccWmRDzwWe+0niayuCi0ZP8DqakYXCByyLhmYR0A5kYfH6URI/bzfWjy0I8BuXbQgWL376eW4q1sjHeHAjT/YlYeIYmLMWaPhamvPthuTsTVL+U/fHY+OUFr9luNtl80r7cWmJnfFWiZ6qUjKNLTrP6zbxdygGNDgAAAJwMZTR6mG+e/nnu0o35j85YyssxgHF0p3xfTX+nKStqyFwOjOfnuuwszDz/olVlujEXkvOxyJahNLocttbCygYte6RL76glxhXrzVA6L7drIrb+KEcIG/sl7c+YrY8uq9FlgSzbU4lGt/itSnaaQ9f22Ek9LwEAAAAMLJ2914WKlyOOXhaXOPqMHPKOpfbOwtDKuJThIuea94lGV34dxcVDuqzRjf1WqNHX5YIuaHTSbxWCODoAAABwMvir0Qcwjs5cc13Y6rvW7fjPUH+H2SotURgG1CeVOHos63nOOhPfWDW6U66LGfUNJ2nmSKpi1QwSRcvGCls0lX4lJaCo2jeW1EfOGp3ql7I/7s0hjh4M0ni9YRxX0t0eFRRXe7f4zcKLtfUw06noq1qo+enaDgAAAADslNfoSWaLltNClbuCOLqdQHAfTLLNzSirJXk8VEp1mW40eIU03SV9nLSRCHRRmf/Nom+l508r0OhMfcwx1Zvp45LRw5TiCUi5sp6zkWZzSOXqY5dTIvdclAR/1NrBT5InOy0pIJZ+TfbLiSW28uSbQEO/GWF7e1FDo+aHajUDnTQ67Tca17g4VR/xdQAAAKBa/F3DCHF0O1quC/AcLdelc7KhenfaC0MPx12C38H8XBm/1bp9vsN2AAAAAGDHX40+mHH0gmsYhS91CT9Np+kuwFuktyLOdZYmLsXpO20qfAD0Xs0tiL45df/d/NXO2gEAAABALv5q9MGMowMAAAAAAOCvRh/QODroHpkFg2K69qZCAAAAAIBS+KvREUcHAAAAAACDib8aHXF0AAAAAAAwmPir0RFHBwAAAAAAg4m/Gh1xdABAN6BeW1nF6yzBydHv26vf7e828A8AnWj0cLWiDz/9/MqFqODt0+b2Nz9Gf8hflGFA4+jF3r2YkK5KlKxL1EvCNwNm3gmYvi9wdO7WxP6j8zcvH63bVxTqpn2RIWJVI/FH7ksM1dVGOckDqMpqRB0YprXvA/rapeF6SreGHwYDt49aOMfXR3GpVVgLr8564njrT3nJril1gdyu7i9xJ55ur6rsj46n3h0Y7AZHn+3zwck/9uN29vwS/EZd0y0xiBnfXXsyfjba2Ss/E/6pdWE/rQJu1pvZTnZ26vyVLEnu2lbhdx+H7yweLb0Id3mN/rR59/iDS8fHZ+avXIiKAo3+nfRnZwxmHN1tvcYXazOfsZZP70fXVrxn6vL2stQIPndykCqNujon/2s+tvbxetPVor6O9OSMV5MQ4cZjwaHt/KPgAJWrF3u1fXPp0yC6j/50P71V6OTuba8Sx4ES7RS0v8K1z6oaF0kZuWNv7IZypDYdt6nziz5eef8Jm54d2Zca7LqfLXb2ys+kf7p8MOz6PDRCn79KXOtndY6dTtbhLqnRXz64++3ZG/MXXiqivFKNPphx9OLbUo6gc8LFjALRPrb4RATURYXp1cPW7Z9H5Y3G5qb4jRJzT5uaXl1lW9LypUdrM18tsrnD1icFrwD53B2pK8KOUBb8GzbxeldczKe7jLi+l+Il+lcsqS8OdaOjU69e8QvkOtvisYUp5UAoqishE9OxPvNaxqRXe1zBeCyTViwaHWUT9oOpoX16XEe85dG5uZHd3SQgI59mVP9Y/EmPVx6FErgIfzIShaJey/3K8aE5ti/80avtK1lkPAkWCqLLJ1HRluxo8/Yl7LGMl8I/f1qNVH/ivL/Q/Vr3I8N2NPqNmfaLaPtOTe3t7SnT37JfmOws0w5lv7I6mTzZ1tnsxP5WHDq27u/kvLXaU4HfyswHKTgeHtDUCrp/yOM2feUqtOBwZspFP2JKi931c46dvfEz5R+W2U9tx8OK9wuLc/Sh6ceHGyz3+GY8f9HXPNljv9T1iMM11klrdJ7Scvwxz2TRRHmluS6Io+djiqMHZV9ebEUCfGdh5vkXQnGHMn1SSPPwd5ES5wKdRYo9rMNCVS8ac9bo+r029cgqk1zWiiObcpiIdw2+w7ebTTYv7l3uT8Qth90sxdo63vF4mGI4PhjKcRFea38ivjFaJo5ePPgql7TDo0GRq229Hdu4ltMQjHQH0Owf2p8sJ56R1UDxtmXKbcdMfIhJZ6sebF+pA8Px32k7pmdVadJQ29dmD+F/yu/++dM2a42XoMX9TPVr34+y7VB+o/aLxBO1TGvG/cJip1M7pH/a7XatllxcJTvYY3FZLkZQYH+n5i0rGt8t7zen+ZCOUe7Vtn1Nx236/MIkDZfRdsIP2sG/m36m7eyhnyn/MJfjYbX7hQ3Vh+bjA318o85f8U5gTBEiNbpr7tZJa/QwDz3989ylG/MfnTFU+uH9TlT6AMbRnfOWHDW6qZx/2uhetoxVo1PHOMOvMnGIqAqLj1axpIwqDmeXK4oUW7c1Op0raIM8Cmvjqunh7XS8Rv+cz/6RYjtWFs3/Uxzow/bNsbZwJjptv2n7HlntsSqJPvZn5xrd5jfLfmS6gDT67YjcL9w0Fm2nqyYm7Ne7kBI23PZ3R3uq9JvDfFBrZmaL8SZJCY1uihNLbcvddNfPlJ099XPxODqj5nnF+4UV3YfEcZg4b9rRbxpXzk5z6NoeK/ycoUxn73WxJbe8fNB8e2X+o9KDQhw9H/81uvVo0kXNUSO1jtK2al5FGl3pTIln5/ipoEZf1yoOokant6+Dk63lNvsN29dqj/8avZQ/q4ijF+tX3Y/MF8Zd1eiltgu1fxEX9mlgTm7TdX/vR41uCZFmDVB+bs3MJu8byLkisbrrrp8pO3vqZ6fzHWFPlftFDpajqHyf0EuN3oN89AhVoz9t3v3tB1FMPc2HKcsAxtFZRRo9ymNRM1co7b6zMLQynmS3yJkvrESuSxb1rlB6Z9VNc+jHUCkfkdgnqbtRsvjUhGjahXwDOP5ViXv3hqTKgjkY1ji6/LRKcrwz+4f2p328xR+k8W370n52e51LukXUe8TU9rXZ46DR/fQnQRW5LlS/9H5k3l6U36j9wqohDPuF5Xji1A4ZRE88KWcJaE+nKTkY5LiM87bYuDrym2MORtq+ku9B74/G4zZlPyt2TSIdPwfNz84a3Tje6vYLZn2OiG5EnjIlNToxCDmHsDMoXfdibT3UaLbUifIaPcl4kXNdApkeJaSfu/R5B0F0hjh6HmszQ4tPpL+Tp0DF86H80/Tq6uTi4ub06uHX7DNRxmux6BHR6Vi+y02pL3CsQKMz9bGP5KUu4haZ/DqsMO3rDRHmMN0LHpZaqbXF27PEk3NT0eFPfr2U6bEbbfdLvzDecJfbocrJTqUOzM9rpT8Rb4cixjX8cP3NCNvbS57SMT27k3bcJv1pHq+y1QqGFnzbvsZjvfOdkNQe5Vkry/Y1fWX1f9/4M8dIqbrz/kL4jTTGcucq6zezH2z+jNOCTftF1qRh93Ys8zA2c3Rqiu/gyVYSf4qvUilI5MZQ87bguEr7zXE+qAPmj/zdyD7voUEdt432S4VylrmcgJ0+TDk6+urVq8Hys8E/qul0O9lnTyvYL6TeC2r0zPHhKOf4RkJc84TWVPPCW0rXFdF7/q5hhDg6AFkqfEdYDv3++ml9MNRTQR6+mB5Q9Pv26nf7u4Kky+GfLpKTmek33TOeONFVORUDXbcyfqt1Ww/ULww9HM97/tBfjT6YcXTXNYzAQCG9baqCG3AFujup6wEAwIBxwkezgaWf/ay8kbQrxlsfaaiE8F0gU/ffzV/Vv2gO3avlKj1/NfpgxtEBAAAAAADwV6N7GEf/3Yvvf/LvjQ79k3/Sa98AAPoWwwvLBNXkPg6cnQAAcErxV6N7GEf/3f/+/G9+8V++9y/++c/+9NP3/viPAr3eaycBAAAAAIBTiL8a3cM4esD//d//+f/3v/yv4vPv/ceTgVL/2Z/+8U//o4u99hYAAAAAADg9+KvRPYyjB7w7fvv2X8y++7v/Ry78yT/993/2J5++98dz7/3zf8Z++tNeew4AAAAAAPQ3/mp0P+PoAf/v5v/8dyv/k/GroX/n3/7ZH11570/+6L0rs0O///u9diEAwEDJVWOBZ/T79up3+/sF+Bn0L51o9HAVow8/1RcTTRY3YtnvHPAzjs75+9+9/Zd/8g//11/nWP/JtAiu/2R42KFxx3cv7kTrEXHU5Yd6QvimpMw7kuS1J25N7D86f/Py0Xr+ygLdsi8yRKxuIP7IfalT9l2pyQN18goQnRjm4WuB9bXcwvUlxApL9lEL5/j6aKHbgqM+4K0/5SVPtEWkurq/xJ14ur0Gw/5qXND9457s5+6/y890vqt2G5c4n5baH81nczPakt2lR5U9v5R5N6KL5dG7EUdX895T3ivKa/SnzbvHH1w6Pj4zf+VCUvj2aXP7m7OdSPMEb+PoAf9m9+G//m/+20JVf/ITnrb+p58Gev2nY/lTwG0NoxdrM5+xVuv2CQ/fgrZSPVOX+5WlRrFFeqtHXa1MWRrasjYyRV9HaHLGq0mIZAno84+KLL/Wq+2bS58G0X30p/vps0Ind297lTgOlGjHf/t9o9y4DH4Oi2ZH9qUTQWX9Uue7qqj2fJo3CZXzY3ehzy8lrmWzOsSOz2tHltToLx/c/fbsjfkLL5vfSRo9UOi7bH7+o0os8zeOHvKv/6v/+t88+VdOP/nphf+Qi/U//qPfu/RxoN2NdYrPFTmCzplefde6HYj2scUnIqAuKkyvHrZu/zwqbzQ2N8VvlJh72tT06irbYl+34gvKo7WZrxbZ3GHrk4JXmHzfGKkrwo44cvBv2MTrXRHK1hcGl+LcxjXDpbV/R0enXr3iF+B1tsXDC4Y1kOWQiVmjZ14zl/RqjzcYj3HSmhF88WW7iDG0T4/riLccLcUcDUwySvePxZ/0eOVRKIGR8Ccj0drOr+V+5bjOHNsX/ujV9pUsMp4cCwXR5ZOfaEt2tHn7EvZYxkvhnz+tRqo/cd5f6H6t+5FhOxr9xkz7RbR9p6b29tQ1yS37hcnOMu242F9rO85Da79u84GG2i5G/1Pz0zhPHlPHt4LjKjZPopMSS88E5P7u5E+WcyWdrZ83XtWKYNbdYK7n0072Rz4nHuZodGlQehf6dgmMzzv+GM8vdCwgu09IXY84XBudNo3Og+XHH/NYefBJ1ugvHzR/YOz773/kf5y7dGP+ozPlLSsdR//7b5/+3f+4/Lv/86/L9lyMf/iHd3/7t+V+OnTu7M8+neNp65f/06F//I/lrzqPowdlX15sRQJ8Z2Hm+RdCcYcyfVJI8/B3kRLnAp1Fij2sw0JVLxpz1uj6PTVVEcskl83iGKQcKeJdj58I281mcN1X43vd/kTcctjNUnxOindsHl4Yjg+Scp4Gr7U/IU6r5eLoxYOvckk7PNoUuZrX27GNazkNzUh3GM3+of3JcuJD2XNbvG2ZclszE9dh0nmlB9tX6sCgNpy2Y3o2bCunc+P2tdlD+J/yu3/+tM1a4yVocT9T/dr3o2w7lN+o/SLxRC3TmnG/sNjp1I6r/a7zkLbfbT5Yt7mhX8p++/w3zQfz8Y0al+s8kdpRDv6Un938SZ/vqHlIjtdoQ7nzqcP+KJ/A3TJGNIPN28U238znl4xVapeERnfNoTptGj1NOA9JxHiY6PJj9GfHMfXScfS/+c/+i98dtHvt2EIM/aN/9HufzPzsTz8NJPvQv3vOOS/KUaObyvmnje5ly1iPKdQx0fCrTHwiqsJilRBLyqjicHb5leg42G2NTufo2jBodOO4anp4Ox2v0T/ns3+k2DR60fxC8mZor7ZvjrWFM9Fp+03b98hqD70X9Lc/O9foNr9Z9iPTBaTRb0fkfuGmyWg7XbWdm/2u89Biv9N8sG14Y7/WpAjrWSCr0Q3HN1tMwWmeKGXy187zwejPN8RI6XlYaLxJ7XLn0+L7o/ZDp2Q2vVPiOOk83wyzonp2mkPX9ljh5wBPks7e66LG0blG/+0Hn8e6/GlwwTxXPpReOo7+N3/6n/+u/X/02rHF+OlP3vtP/tl7fzz3sz/59Cf/wT9lXY6j90CjW8/iXdQcNfKcobStmleRRlc6U+LZOX4qqNHXtYqDqNFrZRIlnTai/ZyX2b4lNUpf+7OKOHqxftX9yHxh3FWNXmq7UPuXg/3O85Dot0qNbuy3Qo1uOr7l3/cz+ME4CfUcLWOWSmmNzog9wqbRyfEmlVIDyp1Pe6PRTdvFV41+2uLoKVqui3iQ9GPxxOjLB823V3oRR//7/+2bv/3v/oec9670lKF/6/ffm/3kvT/59Gdz/3LorHIRU4lGj/JY/v/27ia0jfSO4/goiZNrktoxvTlvBTvrrpP6svYmRk4dk0ChCONDoWyhITb4EN96CWwN6akX+1BQginsqWDMsKf4ZuO63lNITLNZQWKI21tqEzksu7TsBnfeNPPMaJ6RZkaynrG+n0OQHo1Ho2f08st/nnnGP3JFlt1XpnIP+9zRLeLIFy3BWJdq/qNO3hHReJkj+N0tjCOXfOZlR7vE8BkIot5TiAduK3+V4Nh9VR2n7jEYkXV08WwY9+s7vH/k/Rn9eus/UUe1/Svv53jTuXh7xH/kXrZ/o7YnRkZXsz8lGjHWRfa88s9R+P6S9ZvscxGZyUI+FxHfJ7HWE3f7474PZc8b9/0gU9/73/+FErOOHvr9Vtfrqv0+Cf5fwl2/rJ/j9Wf892HE63XujmjrviO/8b8f4o118fWgf+xQxHk+8je/uNaEGV3y4RPHUKUjy13bC0UrQ7VyypfkGd0d8eIfeN6gmRfVntfF8O1vfvfDP76K9SfHOjs7xvInb42d+PST3KlTocvUn9EXhnOz4vO7Z4Ha54eat4bm5z+anX08NP/6C+0zu81cSnNOER2qxHdxVf4JHBuQ0TX/aSXuSej2oeN+Z2iaWdmwhpXtSsocwcPN/fZYNnctvSVr7JxzZlK/87U4Jxyf7p8Uv3oqp+j4Pt7eA6EH3MX1yNqlTyo8QeCc9ar12LNPSV5X13px95z24oV79pr/TNPgE5ek/Rn+en17rc7ShWr7N/S3JPaREG97nJO67K6O2L9hD0X2f2b6s8ZGCovH/rxI+k26MRFHrqr7LbwfovqzMlw47HNRvUld8deTYvvrfR+Gb3/890Md+13aP7W+fyK+PyXfb5LXFeN9IvakuVZxJl57GHRIP8foz0pH1P8+1KJfrxZaPUn1/VD78+gbomKdDe39SsbK6FX7ZS/J+83dl9W1AGtrGjNrjix3qVBfV/caRirP6/LD+sa3v/19nQsfv3SxY+zmyVs3T1wb0HK56IVVeE9AZYFjo010pKZvjjjrSMGJ6SGT9f2V9e1vugZ9v2Wmn2u83jiH4FqreXOJSn6IGrmLjdz1sG9m836wUD+VW+9r9bzp6mZ0devoP354nx//8K9/Ry2Ty534xdWT42MdY6PHL5yPsfKY1zBCWxFms2rWBTj8T3dY/x8A0PYO+fut5SJeb5KZB1qg+deEin8RhrisuTr6nxwUbgcf0HNf9rY8iamb0ZWto//3r198/8c/hT6UO3Wq48anHbdu2vO0tLoLAQAAkEnqZnQ16+gH++/3Pxk5+O57sfHYT8523Myb852PXJcNNAcAU8hEfjbF6mVZ2U40FvsdUIa6GV3NOvp3f3jwv78t2bed64aOj524+rF2/HirOwwAAABHhLoZXcE6+odXr9+P/+rExz83ZzQfHzt+6WKrOwkAAABHkLoZXcE6+o///PrYT7uPdXW1um8AAABwlKmb0RWsowMAAACHIE1Gty5XJFypaEtffPrOe9h/baPYlKujr+i5O9YsQ0yMCAAAgGZKntGNRF6+MFguny7keyota+6d/S19VRtNk9HVrKNzjSEAAAA0W8KMvrO2+OzMRKFnR3/uZXSRmNeTUa6ObiGjAwAAoNmSZPT9LX25fM0c4mLcCs3osvY4qKMDAACgPSXJ6NY4dO9u9bjzRkR0aR09sJgR2Q+zv8joAAAAaLZ087pIwnj6gS4adXQAAAC0qyZk9IZU0bM2Hn17oXh5Vpt/PX3/0mFuDgAAAI6g5BndHfESGOvSoIiesTo69XUAAAA0irrXMMpUHb00lVvvo4gOAACARlA3oytXR4+4hpHx0Je9FNEBAADQEOpmdDXr6AAAAECzqZvRlaujAwAAAIdC3YxOHR0AAADtSd2MTh0dAAAA7UndjE4dHQAAAO1J3Yyuch19e6P4mTaxeb2z1Z0EHBmlqbnSrz8v3Na0hWJRm5i+z8cLRxq/IwCipcno1lWMLv7ybr7H32Td8rcnoHAd3QsTLuPb9s+d09WzL8raZeKuh3ba/Uorpd7bNd9vJT23ZE4kem/y80eacLul04caL+ry6lvzVv/kQaFJmxLeP6rtR9qz1S4TuXzI7wgAiJJn9C19sXxhsFz2rim6v6Uvl6/Z0dwI62/Op0rpytbRg8WPUmmlt/ey/V3ctTFV6n1kPyRrl4m7Htppr3pfrehzd/4zejB9vZ63sZcejMhe6m1aLFZISP+oth9pz1a7TK3lKaIDqClhRjci+LMzE4WeHf25L6O794zbq9poYeB04i1TtY5eVfyolCQt3fMzlWP0snbpimOuh3baA++rvY3hv6x+VV9FXJrRhfUPjc5sBv4DYD/SPTqvvbSGoxgfh6XHvhWbm6Qtz82+7Z4fPTe7+qJ6OxeKxqP2zf4nzuAWu8W56zxR/+TBjT37FVmVdc16Lnf9YctH/zcjtH9U24+0Z6tdpsbyFNEB1JYko3v1cjGVm7yhLtrZwbuFgTRbpmYdPbT4YR6jf3mlklpqt0esPNZ6aKddbBczdM1SujewxOLFcav+Z0cHIzp/M+LEWXPlmhOCrb/VrNhRmiruPZq+7n4u3KHkVux2MrQVjncfVOL40hX3ufTcepe9qe4fWq9OGzo3smkHbuNv/965aQX0r0dnHuwuv7phr1+yvJysf1Tbj7Rnq10mYnmK6ADqkSSjC0ncdHZwwqmX76ytafl8j3lzf2ttpyefooyuZh09vPixUNR/Nl24bYSJZW1T+O2XtcvEXQ/ttHvtlSKxrWYpPVBHH9674SSG8PU4WbxqNVEZ3c333tN12YHb27IVvejL3Lt6bkl7MtP10F2ssj2Bor50eRl5/6i1H2nPWruMfHmK6ADqkm5eF38dfWdN379aGd7iu5OEgnV0ih9QllckttUqpUsyupEe1vsqx+WFZWQZ3Vtb4KMRmJulrow+cmVp6eWk8eyasJhTR+/1FeA1+fIN6h+gSfgdAVCnRmZ0373UGV29OjrFD6jKXyS2RZfSwzO6WPOz1vnRpDfW5WGXG5HNYejapK9MXpXR52Y1Lwc7NcVgdveivz0k3SmW74VkdHElUcs3qH+A5uB3BEC9kmd0d8SLN9bFNwom7eSLqtXRKX5AWcJZmAJ5qdgdjC7OvWjnXWGceve9fu3xi7dulhWfxQu4kjEkRhb/5pz553a7N1IlbHlnzfZJn+4CwXNGe53z8MwzVlfDl5fE9Lj9AzQJvyMA6qfuNYwUq6P7xgAAiMZ1iIAq/I4AiEHdjK5aHR1Anby6NbVqAAASUTejK1ZHBwAAAA6JuhmdOjoAAADak7oZnTo6AAAA2pO6GZ06OgAAANqTuhmdOjoAAADak7oZnTo6AAAA2lOajG5dsch/qSLvIkZpL2GUpI6+MjV353H3/Ovp+5da3a8AAABAUskz+pa+WL4wWC6fLuR7nCYjoT87c7cwULnpXn80iWR1dDOma5MHXOMbAAAAmZUwozsRvGdHf+5l9P0t8d7Omr6ft/J6MsnGo0sz+vbG8OVVbX5mkyu8AQAAQG1JMrqRxZfL18yhLP5ULtbRzWWenkkz3qXBdXQyOgAAADIiSUb3Bp1bzg56Y1qsaP7Obh3UxHEwscnq6IHFjMjuu7+i5+680O4x3AUAAABZlW5el0Ad3SftWJfQOrrsX/evGI8OAACArGtWRt9ZW3xzPtXMLqF19OiArskz+vZC8fLsuScHhduH3cMAAABAPMkzujvixRvrIg6CST33YkPr6KWp3HofczICAAAgC9S9hlHiOvrDvqoTQ1f04Vc3OFsUAAAAmaBuRk9QR7euYdTPgBYAAABkmroZPVkdHQAAAMg6dTN6svHoAAAAQNapm9GpowMAAKA9qZvRqaMDAACgPamb0amjAwAAoD2pm9GpowMAAKA9Jcro+1v68tN3zh3fxYq8R1Jfw4g6OgAAANpT0oz+/HQh3xNs31lbfHbmbmHAvvnmfKqUTh0dAAAA7en/BF0at09HDNAAAAAASUVORK5CYII=

Dumby
А с этим кодом работает:

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

Выделить код

Код:

(async () => { // загрузка внешних js или jsm-скриптов
	var loadscript = name => {
		try { name.split('.').pop().split("?")[0].split("#")[0].toLowerCase() != "jsm"
			? Services.scriptloader.loadSubScript(`chrome://user_chrome_files/content/custom_scripts/${name}`,globalThis,"UTF-8")
			: ChromeUtils.import(`chrome://user_chrome_files/content/custom_scripts/${name}`); return true;
		} catch(e) {}
	};
	loadscript("/AutoCopyChild.jsm");
	loadscript("/ucf-cbbtn-BBCode-Multi.js");
})();

код взял у dobrov

kokoss пишет

А с этим кодом работает:

Ну да, можно и так.
Странное, правда, препарирование name,
и третий аргумент в loadSubScript() выпилен аж в Firefox 66


Но это мелочи, удивительно другое:
вот у тебя имена начинаются с ненужного слэша,
таким образом, в адресе получается двойной слэш,
но всё работает.


То есть, например, адрес вида
chrome://user_chrome_files/content/custom_scripts//////////custom_script.js
прекрасно открывается во вкладке.
Ни за что бы не подумал.

shadow_user пишет

Искренне благодарен за терпение и помощь! По прочтению темы я помню, что один или два д.б. включены, включал по одному, не помогло.

А первый пост видели? Именно для начинающих в начале темы есть готовый Демо-профиль, в котором все нужные опции включены и имеется минимально-необходимый набор скриптов.
Подробности в  firefox_profile_dobrov.html.


kokoss пишет

А с этим кодом работает:

kokoss — Вот более новый вариант загрузчика js/jsm скриптов для custom_script_win.js:

Выделить код

Код:

var loadscript = (js, win = this, init) => { try {
		if (/\.jsm$/i.test(js)) { // скрипт js или jsm [инициализация]
				var obj = ChromeUtils.import('chrome://user_chrome_files/content/custom_scripts/'+ js, win);
				init && obj[init]();
			} else
				Services.scriptloader.loadSubScript('chrome://user_chrome_files/content/custom_scripts/'+ js, win);
			return true;
		} catch(e) {} return false;
	}

………………
	// подключить внешние скрипты - сначала глобальные функции
	var jscripts = [["ucf_global_win.js", globalThis], ["ucf_mousedrag.js"], ["ucf_BookmarkDir.js"], ["ucf_hookClicks.js"], ["ucf_autohidetabstoolbar.js"], ["ucf_LocationBarEnhancer.js"], ["ucf_contextsearch.js"], ["ClickPicSave.jsm"], ["UCFTitleChangedChild.jsm", this, "registerUCFTitleChanged"]];
	for (i = 0; i < jscripts.length; i++) loadscript(jscripts[i][0], jscripts[i][1], jscripts[i][2]);

Dobrov
Даже просто наличие этого загрузчика в custom_script_win.js, с закомментированным импортом, как минимум ломает стили окна.

sandro79
Ваш комплект, безусловно, великолепен. Скажите, пожалуйста, какой стиль или скрипт добавляет эти два пункта и как их удалить? Поиском по открыть страницу не нашел. Edge у меня нет, IE не использую.
cont.png
Еще, вроде, если бы кнопка звука имела индикацию нажатия в виде перечеркивания красной линией, или становилась бы красной, было бы визуально весьма привлекательно.

shadow_user пишет

какой стиль или скрипт добавляет эти два пункта и как их удалить?

Это скрипт contextmenuopenwith.js

Еще, вроде, если бы кнопка звука имела индикацию нажатия в виде перечеркивания красной линией, или становилась бы красной, было бы визуально весьма привлекательно

Да, я тоже уже об этом задумывался, но своими силами добавить индикацию конечно не смогу. Не знаю, может Dumby попросить добавить, если можно.
Dumby
А нельзя ли добавить в этот скрипт индикацию активности кнопки? Если конечно не сильно это муторно.
Может перечёркнутый значок chrome://global/skin/media/audio-muted.svg использовать.
Этот скрипт я собирал из кода из add_toolbar_buttons, надо было там id наверно сменить на другой.

sandro79 пишет

индикацию активности кнопки

Несколько раз перечитал эти три слова,
и даже близко не смог понять, что бы они могли означать.
Помоги, может, каким-нибудь объяснением, если конечно не сильно это муторно.

Dumby пишет

индикацию активности кнопки

Слева отжата, звук включен, справа нажата, звук выключен, или вместо перечеркивания иконка меняет цвет на красный. Так будет визуально видно положение кнопки отжата\нажата.
on-off.png

shadow_user пишет

Слева отжата, звук включен, справа нажата, звук выключен

Звук в(ы)ключен где?

будет визуально видно положение кнопки отжата\нажата

То есть будет видно, нажата кнопка чётное количество раз, или нечётное.
Это понятно, но причём здесь звук? И причём здесь «активность кнопки»,
у кнопки нет никакой активности, она просто переключает по клику некий
на тот момент расклад, вот и всё.

Dumby пишет

Это понятно, но причём здесь звук?

Кнопка управляет звуком активной вкладки, если несколько вкладок, то для каждой вкладки кнопку можно применить индивидуально, в одной вкладке звук включен (белая иконка), в другой можно выключить (перечеркнутая или красная иконка).

shadow_user пишет

Кнопка управляет звуком активной вкладки

Управляет, но не монопольно же. Звук может быть переключён как угодно,
от через контекстное меню вкладки, и до любым другим сторонним кодом.
То есть звук и кнопка не имеют жёсткой связи.


Ладно, вместо «добавить в этот скрипт индикацию активности кнопки»,
сформулируем так: вывести на кнопку индикацию muted-состояния активной вкладки,
если muted, то иконка перечёркнутая, иначе неперечёркнутая. Это можно попробовать.

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

Выделить код

Код:

(async self => CustomizableUI.createWidget(self = {
	label: "Переключить звук",
	tooltiptext:
		"ЛКМ: Переключить звук в выделенных вкладках\n" +
		//"СКМ: Закрыть другие вкладки с источником звука\n" +
		"СКМ: Закрыть другие вкладки «ВОСПРОИЗВОДИТСЯ» и «БЕЗ ЗВУКА»\n" +
		"ПКМ: Переключить звук во всех вкладках",
	imgs: [
		"url(chrome://global/skin/media/audio.svg)",
		"url(chrome://global/skin/media/audio-muted.svg)"
	],
	id: "b-sound-muted-all-tabs",
	defaultArea: CustomizableUI.AREA_NAVBAR,
	localized: false,
	onCreated(btn) {
		btn.onclick = this.click;
		btn.toggleAttribute("context");

		var win = btn.ownerGlobal, gb = win.gBrowser;
		btn.muted = gb.selectedTab.muted;
		this.setImg(btn);

		var tc = gb.tabContainer;
		var args = ["TabAttrModified", e => this.tam(e, btn)];

		tc.addEventListener(...args);
		win.addEventListener("unload",
			() => tc.removeEventListener(...args)
		, {once: true});
	},
	setImg: btn => btn.style.setProperty(
		"list-style-image", self.imgs[+btn.muted], "important"
	),
	tam(e, btn) {
		if (e.target.selected) {
			var arr = e.detail.changed;
			if (arr.includes("selected") || arr.includes("muted"))
				btn.muted ^ (btn.muted = e.target.muted) && this.setImg(btn);
		}
	},
	click(e) {
		var gb = this.ownerGlobal.gBrowser;
		if (e.button == 0)
			gb.toggleMuteAudioOnMultiSelectedTabs(gb.selectedTab);
		else if (e.button == 1)
			gb.visibleTabs.filter(self.f1).forEach(gb.removeTab, gb);
		else if (e.button == 2)
			for(var tab of gb.selectedTab.activeMediaBlocked
				? gb.visibleTabs.filter(self.f2)
				: gb.visibleTabs.filter(self.f3, gb.selectedTab.linkedBrowser.audioMuted)
			)
				tab.toggleMuteAudio();
	},
	f1: tab => !tab.selected && (tab.muted || tab.soundPlaying),
	f2: tab => tab.activeMediaBlocked || tab.linkedBrowser.audioMuted,
	f3(tab) {
		return tab.linkedBrowser.audioMuted == this && !tab.activeMediaBlocked
			|| tab.activeMediaBlocked && this
	}
}))();

Как в скрипте Контекстный поиск переместить меню «Искать в…» выше? (например перед строкой «Добавить в Заладки»)


Firefox.png


_zt пишет

Даже просто наличие этого загрузчика в custom_script_win.js, с закомментированным импортом, как минимум ломает стили окна.

Как это происходит? Мне не удалось воспроизвести: «как минимум ломает стили окна», но всё-же поправил код  в своём сообщении.

Dobrov пишет

переместить меню «Искать в…» выше? (например перед строкой «Добавить в Заладки»)

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

Выделить код

Код:

…
                //searchSelect.before(menu);
                document.getElementById("context-bookmarklink").before(menu);

Как это происходит? всё-же поправил код

Уж не знаю что поправил, но запятая в конце первой части кода всё ещё торчит.
А происходит это так: парсер видит, что после этой запятой идёт совсем не то,
что ожидалось, выдаёт «SyntaxError: missing variable name», и весь код встаёт враскоряку.

Dumby пишет

сформулируем так: вывести на кнопку индикацию muted-состояния активной вкладки,
если muted, то иконка перечёркнутая, иначе неперечёркнутая. Это можно попробовать.

Спасибо, самое оно!

Dobrov пишет

Как это происходит?

Будет явно заметно с VitaliyVstyle full_theme + Windows 7, но уверен, что и на 10-11 имеется смещение рамки окна. Что  приведет к ошибкам при правке стилей.
   
"Поправка" ничего не исправила.

Dumby - спасибо за помощь с меню поиска!

_zt - не знаю, на каком custom_script_win.js вы проверяете, но я брал загрузчик от Виталия, где фрагмент кода var loadscript также завершался запятой!

Выделить код

Код:

// Загрузчик для custom_script_win.js https://forum.mozilla-russia.org/viewtopic.php?pid=788301#p788301

	var loadscript = (js, win = this, init) => { try {
		if (/\.jsm$/i.test(js)) { // скрипт js или jsm [инициализация]
				var obj = ChromeUtils.import('chrome://user_chrome_files/content/custom_scripts/'+ js, win);
				init && obj[init]();
			} else
				Services.scriptloader.loadSubScript('chrome://user_chrome_files/content/custom_scripts/'+ js, win);
			return true;
		} catch(e) {} return false;
	},

	load_scripts_by_url = {
		browser: win => {
			//>>>>>>>>>>| Этот блок требуется для боковой панели и др., очистите строку ниже если он нужен |>>>>>>>>>>
			/*
			var box = document.querySelector("#browser") || window;
			var listener = e => {
				var doc = e.target || ({});
				load_scripts_by_url[doc.documentURI]?.(doc.defaultView);
			};
			box.addEventListener("pageshow", listener);
			this.loadscriptswinandsidebar = {
				destructor() {
					box.removeEventListener("pageshow", listener);
				}
			};
			this.unloadlisteners.push("loadscriptswinandsidebar");
			/* <<<<<<<<<<<<<<<<<<<< */

			setTimeout(() => { //>>>>>>>>>>| Загрузка скриптов для browser.xhtml |>>>>>>>>>>

			// подключить внешние скрипты - сначала глобальные функции
			var jscripts = [["ucf_global_win.js", globalThis], ["ucf_win_contextmenuopenwith.js"], ["ucf_mousedrag.js"], ["ucf_QuickToggle.js"], ["ucf_BookmarkDir.js"], ["ucf_hookClicks.js"], ["ucf_autohidetabstoolbar.js"], ["ucf_LocationBarEnhancer.js"], ["ucf_contextsearch.js"], ["ucf_findbarclose.js"], ["ucf_tab-update.js"], ["ucf_cooks-pass.js"], ["ClickPicSave.jsm"], ["UCFTitleChangedChild.jsm", this, "registerUCFTitleChanged"]]; // auto_hide_sidebar.js
			for (i = 0; i < jscripts.length; i++)
				loadscript(jscripts[i][0], jscripts[i][1], jscripts[i][2]);

			//<<<<<<<<<<<<<<<<<<<<
			}, 0);

		},
		//>>>>>>>>>>| Загрузка скриптов для др. документов |>>>>>>>>>>
		"chrome://browser/content/places/bookmarksSidebar.xhtml": win => {
			// боковая панель закладок
		},
		"chrome://browser/content/places/historySidebar.xhtml": win => {
			// боковая панель истории

		},
		//<<<<<<<<<<<<<<<<<<<<
	};
	load_scripts_by_url.browser(window);

// END Загрузчик для custom_script_win.js

Dobrov

Dobrov пишет

не знаю, на каком custom_script_win.js вы проверяете

На актуальном.
   

Dobrov пишет

завершался запятой!

Я ничего не говорил про запятые.
   

Dobrov пишет

брал загрузчик от Виталия

Однако его загрузчик работает без подобных ошибок. Хотя, у меня давно уже весь импорт в CustomStylesScripts.jsm и CustomStylesScriptsChild.jsm, но его загрузчик до сих пор в custom_script_win.js присутствовал.
   
ps^ Теперь нормально. Правда проверил только на одном скрипте.
ps2^ Нефига не нормально, точки ошибку вызывают. Зачем вы их туда добавили? И запятая, если ее вернуть, тоже окно перекашивает.

Dobrov пишет

фрагмент кода var loadscript также завершался запятой!

Нет. Фрагмент кода начинается инструкцией var, которая завершается точкой с запятой;
А фрагмент loadscript завершается скобкой } которой заканчивается стрелочная функция.
И уже только затем идёт запятая, которая означает, что далее будет определена ещё одна переменная,
в данном случае load_scripts_by_url


_zt пишет

точки ошибку вызывают. Зачем вы их туда добавили?

Это не точки, а троеточия ("\u2026", HORIZONTAL ELLIPSIS).
И если, например, увидеть, что запятая в код попала по недоразумению,
вполне себе может представляться затруднительным, то


то, что последовательность троеточий не является частью кода,
а, видимо, просто служит визуальным разделителем его частей,
представляется очевидным.

Dumby
Вот эта очевидность и ломает стили окна.

Есть кнопка для СВ, отображающая расход оперативной памяти, индикатор расположен в адресной строке (код во вкладке "Инициализация")

Memory Indicator

Выделить код

Код:

(async id => ({

	delay: 2e3,

	val: "",
	init(topic, mm) {
		Services.obs.addObserver(mm = this, topic);
		Services.obs.addObserver(function quit(s, t) {
			this.timer?.cancel();
			Services.obs.removeObserver(mm, topic);
			Services.obs.removeObserver(quit, t);
		}, "quit-application-granted");
	},
	observe(win) {
		var df = win.MozXULElement.parseXULToFragment(
			`<hbox id="${id}" align="center"><label id="${id += "-label"}"/></hbox>`
		);
		this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

		// Bug 1665318 - In about:processes refresh, ResidentUniqueDistinguishedAmount is slow (Firefox 94+)
		// https://bugzilla.mozilla.org/show_bug.cgi?id=1665318
		parseInt(Services.appinfo.platformVersion) < 94 && Object.assign(this, eval(
			`({${this.notify}})`.replace("memory", "residentSetSize").replace("memory", "residentUniqueSize")
		));

		(this.observe = async win => {
			this.timer.cancel();
			await new Promise(ChromeUtils.idleDispatch);
			var ind = win.document.importNode(df, true);
			win.document.getElementById("star-button-box").after(ind);
			this.notify();
		})(win);
	},
	async notify() {
		var info = await ChromeUtils.requestProcInfo();
		var bytes = info.memory;
		for(var child of info.children) bytes += child.memory;
		this.timer.initWithCallback(this, this.delay, this.timer.TYPE_ONE_SHOT);

		var prev = this.val;
		if ((this.val = this.mgb(bytes)) != prev)
			for(var win of CustomizableUI.windows) {
				var lab = win.document.getElementById(id);
				if (lab) lab.value = this.val;
			}
	},
	mgb: bytes => bytes < 1073741824
		? Math.round(bytes / 1048576) + "MB"
		: (bytes / 1073741824).toFixed(2) + "GB"
}).init("browser-delayed-startup-finished"))("ucf-mem-indicator");


Работает крайне нестабильно. Индикатор то отображается, то пропадает после перезапуска браузера, через несколько сессий опять появляется...
Dumby, если можно, перепишите, пожалуйста, под UCF. Спасибо большое :beer:

Помогите, пожалуйста, сделать пункты «открыть ссылку в {другой программе}».

Скачал user_chrome_files.
Распаковал, что и куда там сказано.
Нашёл contextmenuopenwith.js, но нашёл только здесь, я не знаю, правильно ли. Но других вариантов не нашёл.
В общем, тот код вставил в contextmenuopenwith.js, кинул файл в профиль\chrome\user_chrome_files\custom_scripts.
В CustomStylesScripts.jsm добавил  { path: "contextmenuopenwith.js", ucfobj, true, }, в секцию     scriptschrome: { load: [
Зашёл в настройки about:user-chrome-files, включил там стили и скрипты, проставил все галочки в стилях-скриптах.
Перезапустил лиса с очисткой startup cache.

Сами UCF работают, например, есть три ненужных мне панели, да и в about:user-chrome-files иначе было бы не зайти. Но меню открытия в других программах нету.

Wave
Я вам все расписал уже.
   
Если не работает, значит что-то сделали не правильно.
   
Скрипт вы нашли правильный.

_zt, я пошагово написал, что и как я делаю. Полностью совпадает с вашей инструкцией.

Wave
UCF для тех кто хотя бы может правильно отредактировать JavaScript и CSS,
а не с ошибками синтаксиса как у вас

Wave пишет

В CustomStylesScripts.jsm добавил  { path: "contextmenuopenwith.js", ucfobj, true, },

ucfobj: true

Vitaliy V., спасибо, заработало.

Vitaliy V. пишет

UCF для тех кто хотя бы может правильно отредактировать JavaScript и CSS

В теме, в которой мне посоветовали UCF, я писал:

Окей, по поводу UCF перехожу в ту тему. К слову, мне не надо «очень много полезного», я ищу одну конкретную функцию открытия ссылок в других браузерах или видеоплеере. В XUL-фоксе это можно было сделать одним аддоном, в первых WE-лисах двумя или тремя файликами, брошенными в /chrome, но тогда я не выдержал и откатился на XUL, а сейчас то решение не работает, не знаю почему. Может, что-то потерял. А вот это вот UCF-решение, оно громоздкое, избыточное (распаковывать файлы не только в chrome, но и в firefox, редактировать их и так далее), — так ещё и пока что не получилось заставить его работать.
Либо ставить расширение, ставить питон, ставить питоновский скрипт. Тоже избыточно, блин.

Я же не виноват, что простейшее и конкретное действие в квантуме можно организовать только будучи изрядно красноглазым. Дайте мне простой аддон, который можно поставить, залезть в его настройки и всё — и я не буду пользоваться UCF.

Wave пишет

А вот это вот UCF-решение, оно громоздкое, избыточное

Я не согласен с этим, что там такого громоздкого, а то что лишнее отключается в настройках и на работу браузера никак не влияет.

Распаковывать файлы по разным местам, редактировать конфиги вручную, вручную добавлять скрипты, которые или где-то находить, или писать самому, чистить startup cache, что там ещё. Вы сами сказали — это не для всех.
И вот я всё это худо-бедно сделал. Окей. Через год, когда придёт пора обновлять esr, надо будет либо вспоминать, что это я делал, либо искать (опять) все эти инструкции. Хорошо, если в бэкапе оно всё будет и легко на него наткнусь. Но ведь несколько лет назад, когда только вышел квантум, я попробовал на него перейти, неудачно — откатился, тогда тоже нашлось простенькое решение из двух или трёх файлов, которое надо было распаковать в каталог chrome, и оно работало — я ж это решение тоже тогда закатал в бэкап. Ну так с тех пор у меня ноут сменился, и то решение больше не работает, а я не знаю, почему, то ли потому, что девяностый огнелис этого уже не позволяет, то ли я что-то пропустил, потерял где-то. А сами те файлики в сети уже тю-тю, 404.
Я очень люблю программы-комбайны, которые можно как угодно настраивать, но очень не люблю для какой-то простейшей функции ставить комбайн, у которого ничего кроме этой функции не использовать.

Wave пишет

но очень не люблю для какой-то простейшей функции ставить комбайн

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

Vitaliy V. пишет

Wave пишетно очень не люблю для какой-то простейшей функции ставить комбайнНу так это не такая уж простейшая функция вызов внешних приложений, в расширениях этого теперь точно не будет без ещё куда более громоздкого и не универсального решения в виде установки приложения для ОС и оно ещё должно в фоне постоянно работать.

async_run_applications.2021.9.7.xpi умеет вызывать внешние программы и передавать им текущий адрес или адрес из буфера обмена. Правда, тоже требует некоторых телодвижений в каталоге установленного фаерфокса.
https://addons.mozilla.org/ru/firefox/addon/potplayer-youtube-shortcut/ — вот это, почему-то, вызывает внешнее приложение, правда, всего одно — PotPlayer. Не требует установки в систему чего-то типа питона. Находится на AMO. Работает в [firefox] 91 esr. Не требует даже отключать проверку подписей расширений.
Как так?
.
К слову, примерно на втором месте после скрапбука мне из xul-расширений жаль It'sAllText, которое передавало содержимое textarea во внешний редактор, а по сохранению файла обновляло содержимое textarea. WE-аналоги нынче требуют сервер, оформленный в виде плагина для вима, саблайма или любого другого редактора, и чтобы точно так же он был запущен. Почему нельзя сделать по аналогии с potplayer-youtube-shortcut? Или хотя бы как скрипт к UCF (но это надо чтобы кто-то сделал, сам я вряд ли смогу)?

Wave пишет

async_run_applications.2021.9.7.xpi умеет вызывать внешние программы и передавать им текущий адрес или адрес из буфера обмена

Ну да мое расширение может но оно WebExtensions Experiments, для ознакомления https://firefox-source-docs.mozilla.org … xperiments
И его нельзя подписать или выложить на АМО и конечно такое апи не добавят в [firefox]

Wave пишет

https://addons.mozilla.org/ru/firefox/addon/potplayer-youtube-shortcut/ — вот это, почему-то, вызывает внешнее приложение, правда, всего одно — PotPlayer

Не пользуюсь [windows] и PotPlayer, но судя по коду расширения оно добавляет ссылку в виде
potplayer://ссылка, т.е. видимо сам PotPlayer регистрирует новый протокол potplayer: в [windows]
Короче опять же не универсальное решение.

Wave
"Открыть страницу в..." у меня заработало без дополнительных правок, на предыдущей странице я спрашивал, как его убрать https://forum.mozilla-russia.org/viewto … 68#p797568

Очевидно, что ты скачал где-то в другом месте набор, в котором этот скрипт уже был и был подключен. Я же сначала нагуглил скрипт, потом по цепочке выяснил, что подключить его можно через UCF, потом сам пакет UCF, потом выяснил, где и как в нём этот скрипт включить. И шёл я от темы про userChrome.css, потому что несколько лет назад было выяснил, что данную функцию можно задействовать через chrome. И что такое UCF вообще, не сразу выяснил.

Wave
Любезно поделился https://forum.mozilla-russia.org/viewto … 31#p797431 уважаемый мастер sandro79

Viatcheslav пишет

Есть кнопка для СВ

Это что ещё за фантазии?
Приведённый код как раз именно для UCF (для custom_script.js).

Dumby пишет

Приведённый код как раз именно для UCF (для custom_script.js)

Благодарю за просветление :sick: Вроде, нашёл его в теме для СВ, ну да ладно :blush:

Fx 91.4.1 ESR

Можно ли с помощью UCF переместить findbar в нижнюю панель (#browser-bottombox) и
сделать так, чтобы он занял её полностью и отображался постоянно, даже после перезапуска браузера?
 
Дело в том, что если:
privacy.resistFingerprinting;true
privacy.resistFingerprinting.letterboxing;true
то, при появлении findbar, уменьшается viewport — появляются серые полосы сверху и снизу.

viewport.1642889108.png
Для уведомлений помогает стиль: display:block и position:fixed,
а с findbar’ом не получается. Т.е., он отображается поверх содержимого страницы,
но серые полосы всё-равно появляются.
Если подвинуть findbar с помощью margin, то нижнюю серую полосу удаётся убрать, но верхняя полоса остаётся.
 
P.S. Из «Настройки внешнего вида…» меня изгнали, стало быть нужен именно скрипт.

negodnik пишет

переместить findbar

Ты так говоришь, как будто он один на всё окно,
а не для каждой вкладки свой, отдельный.

уменьшается viewport — появляются серые полосы

Если не нравятся серые полосы зачем тогда включать letterboxing :/
Вот этот стиль не образует ресайз, вдруг подойдёт.

изгнали, стало быть нужен именно скрипт

Не слишком ли надумано?
Написано «UCF», а для него есть отдельная тема.
Вот и всё, ничего более.

Ищу скрипты для custom_script.js- «Показать весь журнал» и «открыть about:config»

doud пишет

«Показать весь журнал» и «открыть about:config»

user_chrome_files

kokoss
Разве там есть такие кнопки?

Dumby
> Ты так говоришь, как будто он один на всё окно
Это просто моя хотелка. Готов умерить аппетиты. Просто переместить его в нижнюю панель.
Пускай отображается не постоянно и не занимает всю панель. Есть же кнопка.
Если она не сможет работать в нижней панели — ничего страшного.
> Если не нравятся серые полосы зачем тогда включать letterboxing
Надо.

Стиль видел.
Спасибо.

voqabuhe

скрин
09cf7513771d.png

kokoss,СКМ открывать не удобно, надо чтобы открывала по ЛКМ

doud пишет

Ищу скрипты для custom_script.js- «Показать весь журнал» и «открыть about:config»

Два в одном подойдёт?

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

Выделить код

Код:

try {
CustomizableUI.createWidget({
	id: "ucf_ShowHistory_AboutConfig",
	type: "custom",
	label: "Показать журнал / about:config",
	tooltiptext: [
		"ЛКМ: Показать журнал",
		"ПКМ: about:config"
	].join("\n"),
	// defaultArea: CustomizableUI.AREA_NAVBAR,
	localized: false,
	onBuild(doc) {
		var win = doc.defaultView;
		var trbn = doc.createXULElement("toolbarbutton");
		trbn.id = this.id;
		trbn.tooltipText = this.tooltiptext;
		trbn.label = this.label;
		trbn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
		trbn.setAttribute("context", false);
		trbn.setAttribute("image", "chrome://browser/skin/history.svg");
		trbn.addEventListener("click", function(e) {
			if (e.button == 0) {
					e.preventDefault();
					e.stopPropagation();
					win.SidebarUI.toggle("viewHistorySidebar");
			}
			else if (e.button == 2) {
				win.switchToTabHavingURI("about:config", true, {
					relatedToCurrent: true,
					triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()
				});;
			}
		}, false);
		return trbn;
	},
});
} catch(e) {}

По ЛКМ покзывает журнал в боковой панели,а хотелось бы в новом окне или в новой вкладке

doud пишет

хотелось бы в новом окне или в новой вкладке

Подтянутся гуру и может подскажут, как это реализовать.


А пока попробуйте в коде заменить строку:
win.SidebarUI.toggle("viewHistorySidebar");

на строку:
win.PlacesCommandHook.showPlacesOrganizer("History");


Возможно, что вам такой вариант подойдёт.

Спасибо, отлично получилось открывает в новом окне

negodnik пишет

его

их


Вообще, вроде можно там findbar-склад устроить.
Надо смотреть, не заглючит ли чего.
Код для custom_script_win.js

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

Выделить код

Код:

(async uriStr => {
	var attr = "current";
	await delayedStartupPromise;
	gBrowser.browserBottomBox = document.getElementById("browser-bottombox");
	Object.assign(gBrowser, eval(`({${gBrowser._createFindBar}})`
		.replace(/\/\/.+?\);/s, "this.browserBottomBox.append(findBar);")
		.replace("return f", `aTab.selected && findBar.toggleAttribute("${attr}");\n      $&`)
	));
	windowUtils.loadSheetUsingURIString(uriStr.replace("A", attr), windowUtils.USER_SHEET);

	var arr = [["TabSelect", e => {
		e.target._findBar?.toggleAttribute(attr);
		e.detail.previousTab._findBar?.removeAttribute(attr);
	}], ["TabClose", e => e.target._findBar?.remove()]];

	var tc = gBrowser.tabContainer;
	for(var args of arr) tc.addEventListener(...args);
	var id = Symbol(), ucf = ucf_custom_script_win;
	ucf.unloadlisteners.push(id);
	ucf[id] = {destructor: () => arr.forEach(args => tc.removeEventListener(...args))};

})("data:text/css,%23browser-bottombox>findbar:not([A]){display:none!important;}");

findbar-склад устроился, но и полосы не пропали. Странный этот letterboxing.
Полосы появляются, даже если изменить высоту панелей, и не пропадают после перезапуска.
И с вышеупомянутым стилем Vitaliy V. не пропадают, даже если оставить в userChrome только этот стиль.
В общем, если нужную высоту панелей не угадаю, буду жить с полосами.
Спасибо за помощь.

del

Farby пишет

После подклучкния user_chrome_files у меня пропала способность открывать Menu bar путем нажатия клавиши Alt, можно ли включить это обратно?

Если бы это было так то уже бы другие пользователи об этом сообщили, и у мненя на [linux] это работает. Скорее всего проблема у вас в другом стиле или скрипте.

Farby пишет

передачи URL из контекстного меню в приложение путём расширение async_run_applications

Да я собирался сделать, хотя мне не нужны пункты для вызова приложений в контекстном меню особенно когда их много,
обычно когда мне нужно скачать по ссылке я копирую ее адрес в буфер обмена
и вызываю пункт меню кнопки async_run_applications для вызова с аргументом %OpenClipboardURI из буфера обмена.
Ну или есть скрипт https://forum.mozilla-russia.org/viewto … 54#p782454

del.

Vitaliy V. или Dumby - проблема с скриптом Контекстный поиск - при клике или выборе строк ничего не происходит.


Не работает на версии Firefox 91.5 и выше на МакОС, при этом на Linux скрипт работает без проблем на версиях от 80 до новейшей.
На Firefox 84.0.2 под МакОС работает, на версии Firefox 91.5 в контекстном меню создаётся подменю поиска, но при клике на любой из строк ничего не происходит. В консоли также никаких ошибок не появляется, но страница поиска не открывается. Подключал или в custom_script_win.js или в CustomStylesScripts.jsm, меню поиска в контекстном меню создаётся, но пункты меню не работают:

Выделить код

Код:

scriptschrome: { // Для докум. окна браузера [ChromeOnly]
		domload: [ // По событию "DOMContentLoaded"
      { path: "ucf_contextsearch.js", ucfobj: true, },

Как исправить работу скрипта Контекстный поиск для Firefox 90+ ??? (т. к. некоторые скрипты Dumby делал только под новый Firefox)

Dobrov
https://forum.mozilla-russia.org/viewto … 54#p782454

Dobrov
Там же по умолчанию включено нативное контекстное меню, отключи
widget.macos.native-context-menus - false
так хоть стиль для меню можно использовать, мой кстати работает на первый взгляд.
А иначе только на ...addEventListener("command", ... реагирует и только на пункты в подменю menuitem
на menu не срабатывает там где дефолтный поиск

Кстати, заметил что там небольшой кусочек замысла
потерялся (возвращаемое значение), пустяк конечно, но всё же.

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

Выделить код

Код:

/*
                this.handler = ev => {
                    if (ev.target != popup) return;
                    menu.hidden = searchSelect.hidden;
                };
                this.handlerRebuild = () => this.handler(e) || this.rebuild(menu);
*/
                this.handler = e => e.target != popup || (menu.hidden = searchSelect.hidden);
                this.handlerRebuild = e => this.handler(e) || this.rebuild(menu);


И ещё случайно наткнулся на такой момент:
searchSelect.collapsed = true; — скрывает пункт,
но в клавиатурной навигации он продолжает участвовать.


То есть, когда searchSelect не hidden, и стрелками клавиатуры
перемещаешься по пунктам #contentAreaContextMenu вверх-вниз,
то оно на нём как-бы запинается, пробуксовывает.


Если написать searchSelect.style.setProperty("display", "none", "important");
то нормально.

Dumby пишет

кусочек замысла
потерялся (возвращаемое значение), пустяк конечно, но всё же.

Ок, поправил это похоже после последней правки упустил когда проверку e.target != popup добавлял
которой кстати нет в первоначальном варианте

Dumby - доработал твой код, исправил неудобство кнопки «Быстрое переключение параметров about:config», которое есть во всех примерах форума:
Флажок строки под-меню не выбирается, если параметр сброшен Правым кликом по строке меню (отсутствует в настройках).


Теперь поведение выбора строк подменю одинаковое для параметров по-умолчанию, независимо от того, есть они в about:config или нет (сброшены). Как пример, в коде «Автовыбор значений по-умолчанию» включен в опциях: Загрузки, Многопоточный режим вкладок, User Agent.
Например, к строке ЮзерАгент добавляется всего лишь такой код: [ua, "встроенный"].


Скрипт брать из демо-профиля шапки темы, так как код скрипта сокращён и зависит от двух других: win_global.js и ucf_hookClicks.js.
ранее Dumby делал «Автовыбор значений по-умолчанию», но способ тащил за собой костыль в 30 строк кода на каждый пункт меню.

Dobrov пишет

Флажок строки под-меню не выбирается, если параметр сброшен Правым кликом по строке меню

Всё вроде выбирается. Код кнопки у меня отсюда.
+ изменения раз, два.

xrun1 пишет

Всё вроде выбирается. Код кнопки у меня отсюда.
+ изменения раз, два.

Такая же фигня на 91 esr.
 
Dobrov
Просто вы понакрутили дров в своем скрипте, так что он работает не так как ожидалось. Или использовали на 78esr те фиксы которые для версий выше предназначались.
Оригинальный же, от Dumby, даже с правками, так себя не ведет. Ссылки на использованные фиксы искать лень, но что-то брал и из вашего с ним обсуждения.

Vitaliy V. — насколько я понял, в CustomStylesScripts.jsm нет возможности подключать jsm-скрипты ?
Сделайте в новой версии UserChromeFiles подключение не только js но и jsm-скриптов. Пока поправил user_chrome.js так:

Выделить код

Код:

scriptsbackground: [ // В фоне [System Principal]
		{ path: "custom_script.js", },
………………
		{ path: "ClickPicSave.jsm", },

if (s.path) // правка в user_chrome.js
	if (/\.jsm$/i.test(s.path))
		ChromeUtils.import(`chrome://user_chrome_files/content/custom_scripts/${s.path}`)
	else
		Services.scriptloader.loadSubScript(`chrome://user_chrome_files/content/custom_scripts/${s.path}`, scope);
_zt пишет

Просто вы понакрутили дров в своем скрипте, так что он работает не так как ожидалось.
Оригинальный же, от Dumby, даже с правками, так себя не ведет.

Не накрутил, а оптимизировал и по возможности сократил код, исключил клики и подсказки и повысил удобство использования.


xrun1 пишет

Всё вроде выбирается.

_zt и xrun1 - Неверно! Я тоже тестировал все версии кода! Вот код xrun1 со всеми правками:

но сброшенная опция "Многопоточный режим вкладок" не выбирается!

Выделить код

Код:

// Быстрое переключение параметров about:config
(async (name, id, func) => {
	if (name == "Object") return CustomizableUI.createWidget(func());
	var win = name == "Window", g = Components.utils.import("resource://gre/modules/Services.jsm", {});
	if (g[id]) {if (win) return;} else g[id] = func();
	if (win) return CustomizableUI.createWidget(g[id]);
	addDestructor(r => r[5] == "e" && delete g[id]);
	g[id].onCreated(this);
})(this.constructor.name, "QuickToggleAboutConfigSettings", () => {

	var {prefs} = Services, db = prefs.getDefaultBranch("");
	var pv = parseInt(Services.appinfo.platformVersion);
	var xul_ns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

//=====================================================================================

	// refresh:
	//	false - reload current tab
	//	true - reload current tab skip cache
	//
	// restart:
	//	false - restart browser
	//	true - restart browser with confirm

	var primary = [{

			pref: ["network.proxy.type", "Настройки прокси"],
			userChoice: 5, userAlt: 1, refresh: true,
			values: [
				[0, "Не проксировать", "0"], [5, "Системные (из IE)", "5"], [2, "Авто (pacfile)", "2"],
				[1, "Прописанные", "1"], [4, "Автоопределение", "4"]
	]},
			null,
	{
			pref: ["permissions.default.image", "Загружать графику"],
			userChoice: 1, refresh: true,
			values: [[1, "Да"], [3, "С сайта"], [2, "Нет"]]
	},{
			pref: ["browser.display.use_document_fonts", "Загружать web-шрифты"],
			userChoice: 1, refresh: true,
			values: [[1, "Да"], [0, "Нет"]]
	},{
			pref: ["javascript.enabled", "Выполнять скрипты Java"],
			userChoice: true, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["media.autoplay.default", "Автозапуск медиа"],
			userChoice: 5, refresh: true,
			values: [
				[5, "Блокировать все", "5"],
				[1, "Блокировать не приглушенное", "1"],
				[0, "Разрешить все", "0"]
	]},{
			pref: ["media.autoplay.blocking_policy", "Автозапуск (политика)"],
			userChoice: 1, userAlt: 2, refresh: true,
			values: [
				[1, "Временная", "1"],
				[2, "По действию", "2"],
				[0, "Постоянная", "0"]
	]},{
			pref: ["network.cookie.cookieBehavior", "Cookies"],
			userChoice: 1, userAlt: 3, refresh: false,
			values: [
				[1, "Не принимать сторонние"], [3, "Не принимать с не посещенных"], [4, "Не принимать от трекеров"],
				[2, "Не принимать со всех"], [0, "Принимать со всех"]
	]},
			null,
	{
			pref: ["dom.storage.enabled", "Локальное хранилище"],
			userChoice: true
	},{
			pref: ["browser.tabs.remote.force-enable", "Многопоточный режим вкладок"],
			userChoice: true, userAlt: false,
			values: [[true, "Да"], [false, "Нет"]]
	}
];

//=====================================================================================

	var secondary = [{

			pref: ["dom.serviceWorkers.enabled", "Видео dom.serviceWorkers"],
			userChoice: false
	},{
			pref: ["dom.enable_performance", "Статус загрузки страницы"],
			userChoice: false
	},
			null,
	{
			pref: ["browser.cache.memory.enable", "Кэш в оперативной памяти"],
			userChoice: true
	},
			null,
	{
			pref: ["intl.accept_languages", "Язык для веб-страниц"],
			userChoice: "en-US, en",
			values: [["en-US, en", "en-US, en"], ["en-US, en, ru-RU, ru", "en-US, en, ru-RU, ru"]]
	},{
			pref: ["browser.display.document_color_use", "Использовать цвета сайтов"],
			userChoice: 0,
			values: [[0, "Авто", "0"], [1, "Всегда", "1"], [2, "Никогда", "2"]]
	},
			null,
	{
			pref: ["network.http.sendRefererHeader", "Referer - для чего"],
			userChoice: 1,
			values: [[0, "Ни для чего", "0"], [1, "Только ссылки", "1"], [2, "Ссылки и изобр.", "2"]]
	},{
			pref: ["network.http.referer.trimmingPolicy", "Referer - что"],
			userChoice: 0,
			values: [[0, "Полный URL", "0"], [1, "scheme+host+port+path", "1"], [2, "scheme+host+port", "2"]]
	},{
			pref: ["network.http.referer.XOriginPolicy", "RefererXO - когда"],
			userChoice: 0,
			values: [[0, "В любом случае", "0"], [1, "При совп. баз. домена", "1"], [2, "При совпадении адреса", "2"]]
	},{
			pref: ["network.http.referer.XOriginTrimmingPolicy", "RefererXO - что"],
			userChoice: 0,
			values: [[0, "Полный URL", "0"], [1, "scheme+host+port+path", "1"], [2, "scheme+host+port", "2"]]
	},{
			pref: ["network.http.referer.spoofSource", "Referer - корень сайта"],
			userChoice: false
	},
			null,
	{
			pref: ["media.peerconnection.enabled", "WebRTC утечка IP"],
			userChoice: false
	}
	];

	return {
		label: "Quick toggle",
		id: "QuickToggleAboutConfigSettings",
		localized: false,
		image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFPSURBVDhPnVPNSsNAEJ5tamnBk+A7BMEcikohp/gi9i18nRafQoT00kK16tnn8CJo4vp9m5m41eSgH0zmd3d+diJ/gfdedvl0QW7k1PcLwem+3TjowZagK+Nn6yc3gNCJ+LCCh9LzzTMdqeoNbkQSFZmpJLGCW5Gx2hZaQahMbZ52VuDSfHpnAVJXOb4FKzianbzCXiLrHLYl5C3t5NTRwtxaKJxUTZ/JcOTr6q2Rxwf0aVb2/hLs4KqL3Isckz/mmSdRRksjyg+z7IM6LzDCBfuvAEOJmIKBOqAWmo1YodxLlQNsyO0rsIV/AZM+JGc2a4GgzhZ2p9mFmrpboCMqNYBDdMlwompoTWN6F2nFT5hBXb0PEpmo/Gk+oH+RrqNZINPeIlnZViV1jQuLFJQuWGB0gJdwgShvqdPXZv6J+F/QS7oXqQ9xBTFZ5oa8fAGs9fed5YhPSwAAAABJRU5ErkJggg==",
		onCreated(btn) {
			btn.setAttribute("image", this.image);
			var doc = btn.ownerDocument;

			btn.btn = true;
			btn.domParent = null;
			btn.popups = new btn.ownerGlobal.Array();
			this.createPopup(doc, btn, "primary", primary);
			this.createPopup(doc, btn, "secondary", secondary);
			this.createCloseMenusOption(doc, btn);

			btn.linkedObject = this;
			for(var type of ["command", "contextmenu"])
				btn.setAttribute("on" + type, `linkedObject.${type}(event)`);
		},
		createPopup(doc, btn, name, data) {
			var popup = doc.createElementNS(xul_ns, "menupopup");
			var prop = name + "Popup";
			btn.popups.push(btn[prop] = popup);
			popup.id = this.id + "-" + prop;
			for (var type of ["popupshowing", "click"])
				popup.setAttribute("on" + type, `parentNode.linkedObject.${type}(event)`);
			for(var obj of data) popup.append(this.createElement(doc, obj));
			btn.append(popup);
		},
		map: {b: "Bool", n: "Int", s: "String"},
		createElement(doc, obj) {
			if (!obj) return doc.createElementNS(xul_ns, "menuseparator");
			var pref = doc.ownerGlobal.Object.create(null), node, img, bool;
			for(var [key, val] of Object.entries(obj)) {
				if (key == "pref") {
					var [apref, lab, akey, ttt] = val;
					pref.pref = apref; pref.lab = lab || apref;
					if (ttt) pref.ttt = ttt;
				}
				else if (key == "image") img = val, pref.img = true;
				else if (key != "values") pref[key] = val;
				else pref.hasVals = true;
			}
			var type = prefs.getPrefType(pref.pref);
			var str = this.map[type == prefs.PREF_INVALID
				? obj.values ? (typeof obj.values[0][0])[0] : "b"
				: type == prefs.PREF_BOOL ? "b" : type == prefs.PREF_INT ? "n" : "s"
			];
			pref.get = prefs[`get${str}Pref`];
			pref.set = prefs[`set${str}Pref`];

			node = doc.createElementNS(xul_ns, "menu");
			node.className = "menu-iconic";
			node.setAttribute("closemenu", "none");
			img && node.setAttribute("image", img);
			akey && node.setAttribute("accesskey", akey);
			(node.pref = pref).vals = doc.ownerGlobal.Object.create(null);
			this.createRadios(doc,
				str.startsWith("B") && !pref.hasVals ? [[true, "true"], [false, "false"]] : obj.values,
				node.appendChild(doc.createElementNS(xul_ns, "menupopup"))
			);
			if ("userChoice" in obj) pref.noAlt = !("userAlt" in obj);
			return node;
		},
		createCloseMenusOption(doc, btn) {
			var pn = this.closePref = "QuickToggleAboutConfigSettings.closeMenus";
			var data = [null, {
				pref: [pn, "Закрывать меню этой кнопки"], values: [[true, "Да"], [false, "Нет"]]
			}];
			var setCloseMenus = e => {
				e.stopPropagation();
				var trg = e.target, {pref, val} = trg, updPopup = true, clear;
				switch(e.type) {
					case "command": pref = (trg = trg.closest("menu")).pref; updPopup = false; break;
					case "click": if (e.button) return; break;
					case "contextmenu": e.preventDefault(); clear = pref;
				}
				if (!pref) return;
				if (clear) prefs.clearUserPref(pn);
				else if (!updPopup && val === pref.val) return;
				else pref.set(pn, val !== undefined ? val : !pref.val);
				this.upd(trg);
				updPopup && this.popupshowing(null, trg.querySelector("menupopup"));
			}
			(this.createCloseMenusOption = (doc, btn) => {
				for(var obj of data)
					btn.secondaryPopup.append(this.createElement(doc, obj));
				var m = btn.secondaryPopup.lastChild;
				m.style.cssText = "fill: lightblue !important; list-style-image: url(chrome://browser/skin/menu.svg) !important;";
				m.setAttribute("oncommand", "setCloseMenus(event)");
				m.onclick = m.oncontextmenu = m.setCloseMenus = setCloseMenus;
			})(doc, btn);
		},
		UserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACRUlEQVR42qWTS2gTQRjH/zPJZo2BDfgkB62vYg5CjQdvRUE8WB8HKyahYotIQQOeFKGCF7EgvQlBqOCjWFKlevBRDyL4uHloLXhYqWJroaEqlETSptndGb+ZPDH0ojN8O8vA98vv+/Itw38u1nSRYRE6UhKyg4FF1R292/Q+xhhLi4TIrgjgIzwe4MbgmR2nrZMtxxEN63zYORuPZp7gzpehvCu9XjfuPmwCUHIiEtw4fK89zVvD2+FHAD56quXBpV3CVO4ret6nxHzxZxdBRuqADCKmz7RHD9y2tlmbYbBVfwE8DXBkEd/y39H5+mze8ZyoSIpsFXC9u/VE3/ldpyg5AAMEoJMTQi2hANKBgyKdy0h/eoChqcf9MimvVAHjd/fdiG0NRwgQpDAp1agBpDZwdHKJLKZzc+h5e3kCSexh5c5j8fmhW0HGpNZXFkqfsQpAKgcFKGmAlAxHXp5bkkmsrhosjh4cCIJ5pG5qfQXgtMslqO1qgEMWTPrR+eriEhoA4wN7L8TWh0KVXzfgY2RQAUjVBenWLH4UCrj04Wa9BNXEo5va+w63xCiJ6WRVP2MVgNQIgjgEA17MTODZ7Lt+AtSaGDG4377a1m2FAkKrc11/dUwIID1dSKHEcW3yft4RbpR6kK1PYgaJtaY1nNp5jIeMsjKNbs1AWRUcjvTnp2Kh9LtLJGTDINUhcTIZ3L+hzdq9ZgvWmSF9/Wu5gI8L03gzP5l3hddL/3/zKDdA9MdE0UERrdzaFGMUaap75Y/pX9Yfap76EQYaCeEAAAAASUVORK5CYII=",
		notUserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACNUlEQVR42qXTT2jTUBwH8G9e0ta2ks5ND0Ww/qX1oGyevEwQnOBA6EHXwLwoUh3eRGRs501RPE6xgp6KrexQECZY8DBPXmbRS4Z/K0gRtmmDa2yb5PlLX7p2Gwpi4EfCy8sn3/dPwn9e0saGnCRF6XYFnA9DkhKtRs51ep6jmtEcp/JHIM9Yivl9mf0XNDV2Ngn1YLzVxdB1lJ8U8P5hzuCWnR6xrPwmIM8kLRjdkT326BaLHNgLKAFA9ouXdhOw6qi++4j589cd89vSaMqyc2sAPUXlgF8fyt9WI3t2Av4w4NsCMJ8AHAKav6hqqH76iuLINcNuNhOawyttYCp+7tRE/6XT9GFQAG4CpniATQkIaNQIMVG6/xSL2WfTGueTbWDh5L2rA9t29QGBrQQEPUDuAuoEmFSr+P5lGc8v33mtAUfaQO3M4/GgDOoYCIkUin8D0BDDoBQ2ZMxqN0wCQh0gMxaUnYb4uzt+dwIZ8wBHTGQbYAHMpu+uAxaGxpMDvSr3AHcFaAIlD+BtoN6ag5WqhOLNwrohTMWPH5roP7pdLIwb3wW6EziWGAZhpVdLWHzxdpqAyc4yKrJ+4uKg2oMV+lgRK9CdwAVsG1WpF8UH84Zt2QkCKmsbiRAtFAllB5OHWQ9fpr81OwlcgBL9kPrwsvDGMQ1zNMV5ZyN1ISlZYZl9/TE1tjuMSFgA1VWO8uef+FAqG47tpFMcm7dyFyIOEzBMlfCadao5qhk39l9P479evwFbiOcRSXKueAAAAABJRU5ErkJggg==",
		UserAltImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIfSURBVDhPpVM9b9NQFD02cSKKFVGKIFFREFsmFiCA1AEQQrQjC0gssLCkC0jJj0iFWFoGdiT4Ay0SEmJBhPI5FBokikL4SCC0Te2UxInt13Mdu1CVrVc6713fe86x/T6w09DCeTNKJ7Q0pzyUmoCmZYOiUhXms8RMcd6vB7UwthhM5fTLWix2L3PmYjKdOwdz9DDFGtr1KuovnqD29JGlfO9Goew9DCV/DaZy2pXE8PD9Y/mbupk6xE6cVWPQhEujHo2+4fXdO76ztnq1UPYfSCcwKB1HWjeMyunCraSZOsDqbkBPsBOTNoMGvkOTLto/f+F56bble262OK/qesiYzIydTJojFLjrgEe4bcIOEdXWYe6LITN2KgmFSRFGBuPpo0eAngX0SexTJHD5LOgLpEZTcgKuUuMijAyy5l7+r0NSb21g5LQI5gGYSy2AjYBLjQyRAUniTnJXsBoayBzm3RBBndwwIoOK3fgNdIREQYCV/0DqLdiNZqCRITKYayx85RfQuUNiZ3k7IgO+vfGeXGpkiAyma++WLHtZhZ8o5H/FEVpos1V7u8TFwLQId8nw+Afa51Oq2vyycmlk9KCW0GXFZeu63IUO8z88Bx5s28SbuUXfddzrxVcobxpI0GTh7H734/dPzQt9d0/CGBpCPB7nbhmwbAPVxR4+PPtsuU7/WuElth/lKORUcsoTE8TgMg0WbJaY4Zu3XKYdBrABARUk+ls4DfQAAAAASUVORK5CYII=",
		upd(node) {
			var {pref} = node, def = false, user = false, val;
			if (prefs.getPrefType(pref.pref) != prefs.PREF_INVALID) {
				var pn = pref.pref;
				try {val = pref.defVal = db[pref.get.name](pn); def = true}
				catch(ex) {def = false;}
				var user = prefs.prefHasUserValue(pn);
				if (user) try {val = pref.get(pn, undefined);} catch(ex) {}
			}
			if (val == pref.val && def == pref.def && user == pref.user) return;
			pref.val = val; pref.def = def; pref.user = user;
			var exists = def || user;

			var ttt = exists ? val : "Этого префа не существует";
			if (ttt === "") ttt = "[ empty_string ]";
			ttt += "\n" + pref.pref;
			if (pref.ttt) ttt += "\n" + pref.ttt;
			node.tooltipText = ttt;

			var img, alt = "userAlt" in pref && val == pref.userAlt;
			if (alt) img = this.UserAltImg;
			if ("userChoice" in pref)
				if (val == pref.userChoice)
					//node.style.removeProperty("color"),
					img = this.UserChoiceImg;
				else {
					//node.style.setProperty("color", "maroon", "important");
					if (!alt) img = this.notUserChoiceImg;
				}
			if (!pref.img) img
				? node.setAttribute("image", img)
				: node.removeAttribute("image");
			user
				? node.style.setProperty("font-style", "italic", "important")
				: node.style.removeProperty("font-style");

			var {lab} = pref;
			if (exists && pref.hasVals) {
				if (val in pref.vals) var sfx = pref.vals[val] || val;
				else var sfx = user ? "Другое" : "По умолчанию";
				lab += ` — "${sfx}"`;
			}
			node.setAttribute("label", lab);
		},
		createRadios(doc, vals, popup) {
			for(var arr of vals) {
				if (!arr) {
					popup.append(doc.createElementNS(xul_ns, "menuseparator"));
					continue;
				}
				var [val, lab, key, ttt] = arr;
				var menuitem = doc.createElementNS(xul_ns, "menuitem");
				menuitem.setAttribute("type", "radio");
				menuitem.setAttribute("closemenu", "none");
				menuitem.style.setProperty("font-style", "italic", "important"),
				menuitem.setAttribute("label", popup.parentNode.pref.vals[val] = lab);
				key && menuitem.setAttribute("accesskey", key);
				var tip = menuitem.val = val;
				if (ttt) tip += "\n" + ttt;
				menuitem.tooltipText = tip;
				popup.append(menuitem);
			}
		},
		openPopup(popup) {
			var btn = popup.parentNode;
			if (btn.domParent != btn.parentNode) {
				btn.domParent = btn.parentNode;
				var pos;
				if (btn.matches(".widget-overflow-list > :scope"))
					pos = "after_start";
				else var win = btn.ownerGlobal, {width, height, top, bottom, left, right} =
					btn.closest("toolbar").getBoundingClientRect(), pos = width > height
						? `${win.innerHeight - bottom > top ? "after" : "before"}_start`
						: `${win.innerWidth - right > left ? "end" : "start"}_before`;
				for(var p of btn.popups) p.setAttribute("position", pos);
			}
			popup.openPopup(btn);
		},
		maybeRestart(node, conf) {
			if (conf && !Services.prompt.confirm(null, this.label, "Перезапустить браузер?")) return;

			var cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
			Services.obs.notifyObservers(cancel, "quit-application-requested", "restart");
			return cancel.data ? Services.prompt.alert(null, this.label, "Запрос на выход отменен.") : this.restart();
		},
		async restart() {
			var meth = Services.appinfo.inSafeMode ? "restartInSafeMode" : "quit";
			Services.startup[meth](Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
		},
		regexpRefresh: /^(?:view-source:)?(?:https?|ftp)/,
		maybeRe(node, fe) {
			var {pref} = node;
			if ("restart" in pref) {
				if (this.maybeRestart(node, pref.restart)) return;
			}
			else this.popupshowing(fe, node.parentNode);
			if ("refresh" in pref) {
				var win = node.ownerGlobal;
				if (this.regexpRefresh.test(win.gBrowser.currentURI.spec)) pref.refresh
					? win.BrowserReloadSkipCache() : win.BrowserReload();
			}
		},
		maybeClosePopup(e, trg) {
			!e.ctrlKey && prefs.getBoolPref(this.closePref, undefined)
				&& trg.parentNode.hidePopup();
		},
		command(e) {
			var trg = e.target;
			if (trg.btn) return this.openPopup(trg.primaryPopup);

			var menu = trg.closest("menu"), newVal = trg.val;
			this.maybeClosePopup(e, menu);
			if (newVal != menu.pref.val)
				menu.pref.set(menu.pref.pref, newVal),
				this.maybeRe(menu, true);
		},
		popupshowing(e, trg = e.target) {
			if (trg.state == "closed") return;
			if (trg.id) {
				for(var node of trg.children) {
					if (node.nodeName.endsWith("r")) continue;
					this.upd(node);
					!e && node.open && this.popupshowing(null, node.querySelector("menupopup"));
				}
				return;
			}
			var {pref} = trg.closest("menu"), findChecked = true;

			var findDef = "defVal" in pref;
			var checked = trg.querySelector("[checked]");
			if (checked) {
				if (checked.val == pref.val) {
					if (findDef) findChecked = false;
					else return;
				}
				else checked.removeAttribute("checked");
			}
			if (findDef) {
				var def = trg.querySelector("menuitem:not([style*=font-style]");
				if (def)
					if (def.val == pref.defVal) {
						if (findChecked) findDef = false;
						else return;
					}
					else def.style.setProperty("font-style", "italic", "important");
			}
			for(var node of trg.children) if ("val" in node) {
				if (findChecked && node.val == pref.val) {
					node.setAttribute("checked", true);
					if (findDef) findChecked = false;
					else break;
				}
				if (findDef && node.val == pref.defVal) {
					node.style.removeProperty("font-style");
					if (findChecked) findDef = false;
					else break;
				}
			}
		},
		contextmenu(e) {
			var trg = e.target;
			if (trg.btn) {
				if (e.ctrlKey || e.shiftKey) return;
				if (e.detail == 2) return trg.secondaryPopup.hidePopup();
				this.openPopup(trg.secondaryPopup);
			}
			else if ("pref" in trg) {
				this.maybeClosePopup(e, trg);
				if (trg.pref.user)
					prefs.clearUserPref(trg.pref.pref),
					this.maybeRe(trg);
			}
			e.preventDefault();
		},
		click(e) {
			if (e.button) return;
			var trg = e.target, {pref} = trg;
			if (!pref) return;

			this.maybeClosePopup(e, trg);
			if (!("noAlt" in pref)) return;

			if (pref.val == pref.userChoice)
				if (pref.noAlt) return;
				else  pref.set(pref.pref, pref.userAlt);
			else
				pref.set(pref.pref, pref.userChoice);
			this.maybeRe(trg);
		}
	};
});

Dobrov пишет

насколько я понял, в CustomStylesScripts.jsm нет возможности подключать jsm-скрипты ?

Выделить код

Код:

scriptsbackground: [ // В фоне [System Principal]
		{ func: 'ChromeUtils.import("chrome://user_chrome_files/content/custom_scripts/xxxxxxxx.jsm");', },
Dobrov пишет

но сброшенная опция "Многопоточный режим вкладок" не выбирается!

Что я понимаю не так? [firefox] 97.0 Добавил опцию в свой файл, т.к. не пользуюсь и сделал видео (лежит на ЯД). ЛКМ - красный, ПКМ - синий.

Перелопатил форум на предмет скрита auto_hide_sidebar.uc.js, оказывается для userChromeJS его не так-то просто подключить.
Собрал до кучи из примеров, теперь он автономен и можно просто положит в папку crome с именем auto_hide_sidebar.uc.js или подключить в user_chrome_files, auto_hide_sidebar.css больше отдельно не нужен.

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

Выделить код

Код:

data:application/x-javascript;base64,Ly8gPT1Vc2VyU2NyaXB0PT0KLy8gQG5hbWUJQXV0byBIaWRlIFNpZGViYXIKLy8gQGF1dGhvcglWaXRhbGl5IFYuCi8vIEBpbmNsdWRlCW1haW4KLy8gQHNodXRkb3duCXRoaXMuYXV0b2hpZGVzaWRlYmFyLmRlc3RydWN0b3IoKTsKLy8gQG5vdGUJaHR0cHM6Ly9naXRodWIuY29tL1ZpdGFsaXlWc3R5bGUvVml0YWxpeVZzdHlsZS5naXRodWIuaW8vdHJlZS9tYXN0ZXIvc3R5bGVzZmYvdXNlcl9jaHJvbWVfZmlsZXMvCi8vIEBvbmx5b25jZQovLyA9PS9Vc2VyU2NyaXB0PT0KCih0aGlzLmF1dG9oaWRlc2lkZWJhciA9IHsKCXNpZGViYXI6IG51bGwsCgl0aW1lcjogbnVsbCwKCWRlbGF5OiBudWxsLAoJbGlzdGVuZXI6IG51bGwsCglldmVudHM6IFsiZHJhZ2VudGVyIiwgImRyb3AiLCAiZHJhZ2V4aXQiLCAiTW96TGF5ZXJUcmVlUmVhZHkiLCAibW91c2VlbnRlciIsICJtb3VzZWxlYXZlIl0sCglnZXQgdW5sb2FkbGlzdGVuZXJzKCkgewoJCWRlbGV0ZSB0aGlzLnVubG9hZGxpc3RlbmVyczsKCQl3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigidW5sb2FkIiwgdGhpcywgeyBvbmNlOiB0cnVlIH0pOwoJCXJldHVybiB0aGlzLnVubG9hZGxpc3RlbmVycyA9IFtdOwoJfSwKCWluaXQoKSB7CgkJdmFyIHNpZGViYXIgPSB0aGlzLnNpZGViYXIgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCIjc2lkZWJhci1ib3giKTsKCQlpZiAoIXNpZGViYXIpIHJldHVybjsKCQlmb3IgKGxldCB0eXBlIG9mIHRoaXMuZXZlbnRzKQoJCQlzaWRlYmFyLmFkZEV2ZW50TGlzdGVuZXIodHlwZSwgdGhpcyk7CgkJdGhpcy51bmxvYWRsaXN0ZW5lcnMucHVzaCgiYXV0b2hpZGVzaWRlYmFyIik7CgkJdmFyIHBvcHVwID0gdGhpcy5wb3B1cCA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoIiNzaWRlYmFyTWVudS1wb3B1cCIpOwoJCWlmICghcG9wdXApIHJldHVybjsKCQlwb3B1cC5hZGRFdmVudExpc3RlbmVyKCJwb3B1cHNob3dpbmciLCB0aGlzKTsKCQlsZXQgc3R5bGUgPSB0aGlzLnN0eWxlCgkJdmFyIHNzcyA9IENjWydAbW96aWxsYS5vcmcvY29udGVudC9zdHlsZS1zaGVldC1zZXJ2aWNlOzEnXS5nZXRTZXJ2aWNlKENpLm5zSVN0eWxlU2hlZXRTZXJ2aWNlKTsKCQlsZXQgbmV3VVJJUGFyYW0gPSB7CgkJCWFVUkw6ICdkYXRhOnRleHQvY3NzLCcgKyBlbmNvZGVVUklDb21wb25lbnQoc3R5bGUpLAoJCQlhT3JpZ2luQ2hhcnNldDogbnVsbCwKCQkJYUJhc2VVUkk6IG51bGwKCQl9CgkJbGV0IGNzc1VyaSA9IFNlcnZpY2VzLmlvLm5ld1VSSShuZXdVUklQYXJhbS5hVVJMLAoJCQkJCQluZXdVUklQYXJhbS5hT3JpZ2luQ2hhcnNldCwKCQkJCQkJbmV3VVJJUGFyYW0uYUJhc2VVUkkpOwoJCXNzcy5sb2FkQW5kUmVnaXN0ZXJTaGVldChjc3NVcmksIHNzcy5BR0VOVF9TSEVFVCk7Cgl9LAoJZGVzdHJ1Y3RvcigpIHsKCQl2YXIgc2lkZWJhciA9IHRoaXMuc2lkZWJhcjsKCQlmb3IgKGxldCB0eXBlIG9mIHRoaXMuZXZlbnRzKQoJCQlzaWRlYmFyLnJlbW92ZUV2ZW50TGlzdGVuZXIodHlwZSwgdGhpcyk7CgkJaWYgKCF0aGlzLnBvcHVwKSByZXR1cm47CgkJdGhpcy5wb3B1cC5yZW1vdmVFdmVudExpc3RlbmVyKCJwb3B1cHNob3dpbmciLCB0aGlzKTsKCX0sCgloYW5kbGVFdmVudChlKSB7CgkJdGhpc1tlLnR5cGVdKGUpOwoJfSwKCU1vekxheWVyVHJlZVJlYWR5KGUpIHsKCQlpZiAoZS5vcmlnaW5hbFRhcmdldD8uaWQgPT0gIndlYmV4dC1wYW5lbHMtYnJvd3NlciIgJiYgIXRoaXMuc2lkZWJhci5oYXNBdHRyaWJ1dGUoInNpZGViYXJkcmFnIikpIHsKCQkJd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoIm1vdXNlZG93biIsICgpID0+IHsKCQkJCXRoaXMuZHJvcCgpOwoJCQl9LCB7IG9uY2U6IHRydWUgfSk7CgkJCXRoaXMuZHJhZ2VudGVyKCk7CgkJfQoJfSwKCWNsaWNrOiBmdW5jdGlvbihldmVudCkgewoJCXZhciBzaWRlYmFyID0gdGhpcy5zaWRlYmFyOwoJCXZhciBib3hPYmogPSBzaWRlYmFyLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLCBib3hTY3JuID0gIXNpZGViYXIuYm94T2JqZWN0ID8gc2lkZWJhciA6IHNpZGViYXIuYm94T2JqZWN0OwoJCWlmIChldmVudC5zY3JlZW5ZIDwgYm94U2Nybi5zY3JlZW5ZIHx8IGV2ZW50LnNjcmVlblk+IGJveFNjcm4uc2NyZWVuWSArIGJveE9iai5oZWlnaHQgfHwgZXZlbnQuc2NyZWVuWCA8IGJveFNjcm4uc2NyZWVuWHx8IGV2ZW50LnNjcmVlblggPiBib3hTY3JuLnNjcmVlblggKyBib3hPYmoud2lkdGgpIHsKCQkJd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIoImNsaWNrIiwgdGhpcywgZmFsc2UpOwoJCQl0aGlzLmxpc3RlbmVyID0gZmFsc2U7CgkJCWlmIChzaWRlYmFyLmhhc0F0dHJpYnV0ZSgic2lkZWJhcmRyYWciKSkKCQkJCXNpZGViYXIucmVtb3ZlQXR0cmlidXRlKCJzaWRlYmFyZHJhZyIpOwoJCX0KCX0sCgltb3VzZWVudGVyOiBmdW5jdGlvbigpIHsKCQl2YXIgc2lkZWJhciA9IHRoaXMuc2lkZWJhcjsKCQlpZiAoIXNpZGViYXIuaGFzQXR0cmlidXRlKCJzaWRlYmFyZHJhZyIpICYmICF0aGlzLmxpc3RlbmVyKSB7CgkJCWNsZWFyVGltZW91dCh0aGlzLnRpbWVyKTsKCQkJdmFyIGRlbGF5ID0gdGhpcy5kZWxheSB8fCAodGhpcy5kZWxheSA9ICtnZXRDb21wdXRlZFN0eWxlKHNpZGViYXIpLmdldFByb3BlcnR5VmFsdWUoInRyYW5zaXRpb24tZGVsYXkiKS5yZXBsYWNlKC9bXjAtOVwuXS9nLCAiIikgKiAxMDAwKTsKCQkJdGhpcy50aW1lciA9IHNldFRpbWVvdXQoKCkgPT4gewoJCQkJc2lkZWJhci5zZXRBdHRyaWJ1dGUoInNpZGViYXJkcmFnIiwgInRydWUiKTsKCQkJCXRoaXMubGlzdGVuZXIgPSB0cnVlOwoJCQkJd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgdGhpcywgZmFsc2UpOwoJCQl9LCBkZWxheSk7CgkJfQoJfSwKCW1vdXNlbGVhdmU6IGZ1bmN0aW9uKCkgewoJCWNsZWFyVGltZW91dCh0aGlzLnRpbWVyKTsKCX0sCglwb3B1cHNob3dpbmcoKSB7CgkJdGhpcy5wb3B1cC5hZGRFdmVudExpc3RlbmVyKCJwb3B1cGhpZGRlbiIsICgpID0+IHsKCQkJdGhpcy5kcm9wKCk7CgkJfSwgeyBvbmNlOiB0cnVlIH0pOwoJCXRoaXMuZHJhZ2VudGVyKCk7Cgl9LAoJZHJhZ2VudGVyKCkgewoJCWlmICghdGhpcy5zaWRlYmFyLmhhc0F0dHJpYnV0ZSgic2lkZWJhcmRyYWciKSkKCQkJdGhpcy5zaWRlYmFyLnNldEF0dHJpYnV0ZSgic2lkZWJhcmRyYWciLCAidHJ1ZSIpOwoJfSwKCWRyb3AoKSB7CgkJaWYgKHRoaXMuc2lkZWJhci5oYXNBdHRyaWJ1dGUoInNpZGViYXJkcmFnIikpCgkJCXRoaXMuc2lkZWJhci5yZW1vdmVBdHRyaWJ1dGUoInNpZGViYXJkcmFnIik7Cgl9LAoJZHJhZ2V4aXQoZSkgewoJCXZhciBzaWRlYmFyID0gdGhpcy5zaWRlYmFyOwoJCXZhciBib3hPYmogPSBzaWRlYmFyLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLCBib3hTY3JuID0gIXNpZGViYXIuYm94T2JqZWN0ID8gc2lkZWJhciA6IHNpZGViYXIuYm94T2JqZWN0OwoJCWlmICgoIWUucmVsYXRlZFRhcmdldCB8fCBlLnNjcmVlblkgPD0gKGJveFNjcm4uc2NyZWVuWSArIDUpIHx8IGUuc2NyZWVuWT49IChib3hTY3JuLnNjcmVlblkgKyBib3hPYmouaGVpZ2h0IC0gNSkKCQkJfHwgZS5zY3JlZW5YIDw9IChib3hTY3JuLnNjcmVlblggKyA1KSB8fCBlLnNjcmVlblggPj0gKGJveFNjcm4uc2NyZWVuWCArIGJveE9iai53aWR0aCAtIDUpKQoJCQkmJiBzaWRlYmFyLmhhc0F0dHJpYnV0ZSgic2lkZWJhcmRyYWciKSkKCQkJc2lkZWJhci5yZW1vdmVBdHRyaWJ1dGUoInNpZGViYXJkcmFnIik7Cgl9LAoJc3R5bGU6IGBALW1vei1kb2N1bWVudCB1cmwoImNocm9tZTovL2Jyb3dzZXIvY29udGVudC9icm93c2VyLnhodG1sIikgewoJCQkjc2lkZWJhci1ib3ggewoJCQkJLS12LXNpZGViYXItbWluLXdpZHRoOiAycHg7CgkJCQktLXYtc2lkZWJhci1taW4td2lkdGgtbm9ybWFsOiA1cHg7CgkJCQktLXYtc2lkZWJhci1tYXgtd2lkdGg6IDMwZW07CgkJCQktLXYtc2lkZWJhci10cmFuc2l0aW9uLWRlbGF5LXNob3c6IC4zczsKCQkJCS0tdi1zaWRlYmFyLXRyYW5zaXRpb24tZGVsYXktaGlkZTogLjZzOwoJCQkJLS12LXNpZGViYXItdHJhbnNpdGlvbi1kdXJhdGlvbjogLjNzOwoJCQkgLyogKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiAqLwoKCQkJIHBvc2l0aW9uOiByZWxhdGl2ZSAhaW1wb3J0YW50OwoJCQkgei1pbmRleDogMiAhaW1wb3J0YW50OwoJCQkgbWluLXdpZHRoOiB2YXIoLS12LXNpZGViYXItbWF4LXdpZHRoKSAhaW1wb3J0YW50OwoJCQkgd2lkdGg6IHZhcigtLXYtc2lkZWJhci1tYXgtd2lkdGgpICFpbXBvcnRhbnQ7CgkJCSBtYXgtd2lkdGg6IHZhcigtLXYtc2lkZWJhci1tYXgtd2lkdGgpICFpbXBvcnRhbnQ7CgkJCSBvdmVyZmxvdzogaGlkZGVuICFpbXBvcnRhbnQ7CgkJCSBvcGFjaXR5OiAwICFpbXBvcnRhbnQ7CgkJCSAtLXYtc2lkZWJhci1tYXJnaW4tbWF4LXdpZHRoOiBjYWxjKC0xICogdmFyKC0tdi1zaWRlYmFyLW1heC13aWR0aCkpOwoJCQkgLS12LXNpZGViYXItdHJhbnNmb3JtLWxvY2FsZS1kaXI6IC0xOwoJCQkgLS12LXNpZGViYXItdHJhbnNmb3JtLWxvY2FsZS1kaXItdmlzaWJsZTogMTsKCQkJIG1hcmdpbi1pbmxpbmUtc3RhcnQ6IDAgIWltcG9ydGFudDsKCQkJIG1hcmdpbi1pbmxpbmUtZW5kOiB2YXIoLS12LXNpZGViYXItbWFyZ2luLW1heC13aWR0aCkgIWltcG9ydGFudDsKCQkJIGJvcmRlcjogbm9uZSAhaW1wb3J0YW50OwoJCQkgYm9yZGVyLWlubGluZS1lbmQ6IDFweCBzb2xpZCB2YXIoLS1zaWRlYmFyLWJvcmRlci1jb2xvciwgVGhyZWVEU2hhZG93KSAhaW1wb3J0YW50OwoJCQkgdHJhbnNmb3JtOiB0cmFuc2xhdGVYKGNhbGModmFyKC0tdi1zaWRlYmFyLXRyYW5zZm9ybS1sb2NhbGUtZGlyKSAqICh2YXIoLS12LXNpZGViYXItbWF4LXdpZHRoKSAtIHZhcigtLXYtc2lkZWJhci1taW4td2lkdGgpKSkpICFpbXBvcnRhbnQ7CgkJCSB0cmFuc2l0aW9uLXRpbWluZy1mdW5jdGlvbjogbGluZWFyLCBzdGVwLXN0YXJ0ICFpbXBvcnRhbnQ7CgkJCSB0cmFuc2l0aW9uLWRlbGF5OiB2YXIoLS12LXNpZGViYXItdHJhbnNpdGlvbi1kZWxheS1oaWRlKSwgY2FsYyh2YXIoLS12LXNpZGViYXItdHJhbnNpdGlvbi1kZWxheS1oaWRlKSArIHZhcigtLXYtc2lkZWJhci10cmFuc2l0aW9uLWR1cmF0aW9uKSkgIWltcG9ydGFudDsKCQkJIHRyYW5zaXRpb24tZHVyYXRpb246IHZhcigtLXYtc2lkZWJhci10cmFuc2l0aW9uLWR1cmF0aW9uKSwgMHMgIWltcG9ydGFudDsKCQkJIHRyYW5zaXRpb24tcHJvcGVydHk6IHRyYW5zZm9ybSwgb3BhY2l0eSAhaW1wb3J0YW50OwoJCSB9CgkJICNzaWRlYmFyLWJveFtwb3NpdGlvbmVuZD0idHJ1ZSJdIHsKCQkJIG1hcmdpbi1pbmxpbmUtc3RhcnQ6IHZhcigtLXYtc2lkZWJhci1tYXJnaW4tbWF4LXdpZHRoKSAhaW1wb3J0YW50OwoJCQkgbWFyZ2luLWlubGluZS1lbmQ6IDAgIWltcG9ydGFudDsKCQkJIGJvcmRlci1pbmxpbmUtc3RhcnQ6IDFweCBzb2xpZCB2YXIoLS1zaWRlYmFyLWJvcmRlci1jb2xvciwgVGhyZWVEU2hhZG93KSAhaW1wb3J0YW50OwoJCQkgYm9yZGVyLWlubGluZS1lbmQ6IG5vbmUgIWltcG9ydGFudDsKCQkgfQoJCSAjc2lkZWJhci1ib3hbcG9zaXRpb25lbmQ9InRydWUiXTotbW96LWxvY2FsZS1kaXIobHRyKSwKCQkgI3NpZGViYXItYm94Om5vdChbcG9zaXRpb25lbmQ9InRydWUiXSk6LW1vei1sb2NhbGUtZGlyKHJ0bCkgewoJCQkgLS12LXNpZGViYXItdHJhbnNmb3JtLWxvY2FsZS1kaXI6IDE7CgkJCSAtLXYtc2lkZWJhci10cmFuc2Zvcm0tbG9jYWxlLWRpci12aXNpYmxlOiAtMTsKCQkgfQoJCSA6cm9vdFt2X3ZlcnRpY2FsX2Jhcl9hdXRvaGlkZV1bdl92ZXJ0aWNhbF9iYXJfc2lkZWJhcj0idHJ1ZSJdICNzaWRlYmFyLWJveDpub3QoOmhvdmVyLFtzaWRlYmFyZHJhZ10pLAoJCSA6cm9vdFt2X3ZlcnRpY2FsX2Jhcl9hdXRvaGlkZV06bm90KFt2X3ZlcnRpY2FsX2Jhcl9zaWRlYmFyXSkgI3NpZGViYXItYm94Om5vdCg6aG92ZXIsW3NpZGViYXJkcmFnXSkgewoJCQkgdHJhbnNpdGlvbi1kZWxheTogMHMsIHZhcigtLXYtc2lkZWJhci10cmFuc2l0aW9uLWR1cmF0aW9uKSAhaW1wb3J0YW50OwoJCSB9CgkJIDpyb290W3NpemVtb2RlPSJub3JtYWwiXSAjc2lkZWJhci1ib3ggewoJCQkgLS12LXNpZGViYXItbWluLXdpZHRoOiB2YXIoLS12LXNpZGViYXItbWluLXdpZHRoLW5vcm1hbCkgIWltcG9ydGFudDsKCQkgfQoJCSAjc2lkZWJhci1ib3g6aG92ZXIsCgkJICNzaWRlYmFyLWJveFtzaWRlYmFyZHJhZ10gewoJCQkJCQl0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoMHB4KSAhaW1wb3J0YW50OwoJCQkgb3BhY2l0eTogMSAhaW1wb3J0YW50OwoJCQkgdHJhbnNpdGlvbi1kZWxheTogdmFyKC0tdi1zaWRlYmFyLXRyYW5zaXRpb24tZGVsYXktc2hvdykgIWltcG9ydGFudDsKCQkgfQoJCSA6cm9vdFt2X3ZlcnRpY2FsX2Jhcl92aXNpYmxlXVt2X3ZlcnRpY2FsX2Jhcl9zdGFydD0idHJ1ZSJdICNzaWRlYmFyLWJveDpub3QoW3Bvc2l0aW9uZW5kPSJ0cnVlIl0pLAoJCSA6cm9vdFt2X3ZlcnRpY2FsX2Jhcl92aXNpYmxlXVt2X3ZlcnRpY2FsX2Jhcl9zdGFydD0iZmFsc2UiXSAjc2lkZWJhci1ib3hbcG9zaXRpb25lbmQ9InRydWUiXSB7CgkJCSB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoY2FsYyh2YXIoLS12LXNpZGViYXItdHJhbnNmb3JtLWxvY2FsZS1kaXItdmlzaWJsZSkgKiB2YXIoLS12LXZlcnRpY2FsX2Jhcl93aWR0aCwgMHB4KSkpICFpbXBvcnRhbnQ7CgkJCSBvcGFjaXR5OiAxICFpbXBvcnRhbnQ7CgkJCSB0cmFuc2l0aW9uLWRlbGF5OiAwcyAhaW1wb3J0YW50OwoJCSB9CgkJICNicm93c2VyID4gI3NpZGViYXItc3BsaXR0ZXIgewoJCQkgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OwoJCSB9CgkJICNzaWRlYmFyLWJveCA+ICNzaWRlYmFyIHsKCQkJIG1pbi13aWR0aDogMCAhaW1wb3J0YW50OwoJCQkgd2lkdGg6IGF1dG8gIWltcG9ydGFudDsKCQkJIG1heC13aWR0aDogbm9uZSAhaW1wb3J0YW50OwoJCQkgLW1vei1ib3gtZmxleDogMSAhaW1wb3J0YW50OwoJCSB9CgkJIH1gCn0pLmluaXQodGhpcyk7Cg==


ЗЫ: может кому сгодиться, тестил на [firefox] 91esr и [firefox] 97

Farby пишет

для userChromeJS

скрипт ваш неправильный во первых он скорее всего не работает в таком виде в userChromeJS разве что там есть массив unloadlisteners, я не курсе
ну и loadAndRegisterSheet не для использования в оконных скриптах тем более без проверки загружен уже стиль или нет, лучше заменить хотя бы на windowUtils.loadSheetUsingURIString(string, type);
понятно что в ваших .uc.js скриптах loadAndRegisterSheet часто присутствует но это не значит что надо тоже такое городить.

Farby пишет

или подключить в user_chrome_files

вот это совсем лишнее учитывая что он там уже присутствует по умолчанию и с нормальной (кешированной) загрузкой стиля

17-02-2022 20:17:21

Dobrov пишет

насколько я понял, в CustomStylesScripts.jsm нет возможности подключать jsm-скрипты ?

Как вариант добавить перед строкой var UcfStylesScripts = {
эту
var jsmImport = path => `ChromeUtils.import("chrome://user_chrome_files/content/custom_scripts/${path}.jsm")`;
и далее добавлять названия скриптов или путь/название
scriptsbackground: [ // В фоне [System Principal]

        { func: jsmImport("path/scriptName"), },

Dobrov
Ну тут бабка надвое сказала, мне например, ваши комбайны, с кучей кликов, совсем не кажутся удобными и жесты мне неудобны. Но это не значит, что я против, я вижу что многим жесты нравятся. А вот массив кликов, ну это как в играх, например в Devil May Cry я ни при каком раскладе играть не буду, а ваши решения напоминают боевку этой игры. :)
 

Dobrov пишет

насколько я понял, в CustomStylesScripts.jsm нет возможности подключать jsm-скрипты

Dumby же давал загрузчик:

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

Выделить код

Код:

(async url => ChromeUtils.import(url))(
    "chrome://user_chrome_files/content/custom_scripts/scriptName"
);
(async url => ChromeUtils.import(url))(
    "chrome://user_chrome_files/content/custom_scripts/scriptName"
);


Добавляем его в стандартный импорт CustomStylesScripts.jsm:
скрытый текст

Выделить код

Код:

scriptsbackground: [ // В фоне [System Principal]
 ...
        { path: "custom_js/name.js", },

Vitaliy V. пишет

Как вариант добавить var jsmImport = path =>

Не работает на скрипте "Замена текста в имени вкладки", у которого надо грузить функцию, я попробовал перенести код, но не получилось:
Как исправить код, чтобы он грузил обычный jsm и jsm с функцией, которая выполнится при загрузке скрипта ?

Выделить код

Код:

var jsmImport = (name, funcName) => ` // подключить jsm [выполнить функцию]
	var {href, pathname} = new URL("chrome://user_chrome_files/content/custom_scripts/${name}");
	var obj = ChromeUtils.import(href); 
	funcName && obj[funcName]();
`;
{ func: jsmImport("UCFTitleChangedChild.jsm", "registerUCFTitleChanged"), },

UCFTitleChangedChild.jsm — Замена текста в имени вкладки

Выделить код

Код:

var EXPORTED_SYMBOLS = ["registerUCFTitleChanged", "UCFTitleChangedChild"];

var reg = /-\sПоиск\sв\sGoogle$| \| Форум Mozilla Россия$|^Смотреть дораму |^Смотреть бесплатно дораму |^Сериал \| Фильм | - DoramaTV/;
var hosts = ["https://www.google.com/search?*", "https://www.google.ru/search?*", "https://doramatv.live/*", "https://forum.mozilla-russia.org"];

function registerUCFTitleChanged() { // исправление заголовка вкладки
	ChromeUtils.registerWindowActor("UCFTitleChanged", {
		child: {
			moduleURI: "chrome://user_chrome_files/content/custom_scripts/UCFTitleChangedChild.jsm",
			events: {
				DOMTitleChanged: { capture: true },
			},
		},
			matches: hosts,
			messageManagerGroups: ["browsers"],
	});
}
class UCFTitleChangedChild extends JSWindowActorChild {
	handleEvent(e) {
		if (reg.test(this.document.title))
			this.document.title = this.document.title.replace(reg, "");
	}
}

Но работает в custom_script_win.js (а желательно подключать в CustomStylesScripts.jsm?)

Выделить код

Код:

var loadscript = (name, funcName) => {
  try { var {href, pathname} = new URL(`chrome://user_chrome_files/content/custom_scripts/${name}`);
    if (/\.jsm$/i.test(pathname)) {
      var obj = ChromeUtils.import(href);
      funcName && obj[funcName]();
    } else
      Services.scriptloader.loadSubScript(href);
    return true;
  }
  catch(ex) {Cu.reportError(ex);}
}
loadscript("ucf_aom-button.js");
loadscript("UCFTitleChangedChild.jsm", "registerUCFTitleChanged");

Dumby - Просьба убрать longPress из скрипта перехвата кликов - я пробовал, но срабатывает и обычный и двойной клик сразу…
После длительного юзания неудобство в том, что при нажатии кнопок действие происходит с задержкой в пол-секунды. Можно ли переделать код на стандартные события click, doubleclick, wheel — чтобы действие при нажатии кнопок выполнялось сразу?
Преимущество твоего кода в том, что легко можно прописать клики для любых кнопок панели инструментов, а в более простом примере на форуме нет разбора по #id нажатых кнопок и нет скролла…


Можешь набросать только часть кода для перехвата кликов/$id кнопок ? (я сам переделаю скрипт по ссылке)
Нужно обычный и двойной клик кнопок мыши, колёсико и разбор событий по кнопка, как в вышеуказанном коде:

Выделить код

Код:

data = {
	"#downloads-button": { mousedownTarget: true,
		2(trg, forward) {
			bright(trg, forward); // яркость по wheel ±
		},
		128() { saveSelectionToTxt();}, // СКМ Click
		260(btn) { // Double ПКМ Click
Dobrov пишет

Не работает на скрипте "Замена текста в имени вкладки", у которого надо грузить функцию

и в чем проблема, не надо там ничего переделывать с функцией jsmImport
{ func: `${jsmImport("UCFTitleChangedChild")}.registerUCFTitleChanged();`, },

18-02-2022 13:55:47

Dobrov пишет

Но работает в custom_script_win.js

это же опечатка или действительно в win запускаешь?

Vitaliy V. пишет

это же опечатка или действительно в win запускаешь?

Спасибо! Но в будущих версиях UCF возможно сделать подключение jsm-скриптов попроще? Без всяких `` кавычек, так, как для js сделано ?


Было так и имена вкладок менялись, но сейчас прописал правильно в CustomStylesScripts.jsm:
scriptsbackground: [ // В фоне [System Principal]

del

Dumby - В скрипте "Simple Session Manager" одна иконка прописана 2 раза. Как сократить код и передать её как переменную в regStyle() ?

Выделить код

Код:

onCreated(btn) {
…………
	btn.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8…………
regStyle() {
…………
	#${pid} > menu {
	list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8……………

Кроме того, восстановление сессий в новые вкладки с кликом средней кнопки + Ctrl не очень удобно, добавил для этого пункт меню: "Восстановить в новых вкладках"
А вообще для восстановления удобнее сразу кликать по имени сохранённой сесии (и восстанавливать в новых вкладках при клике с нажатым Ctrl), а в подменю оставить только: Переименовать и Удалить.

Simple Session Manager mod

Выделить код

Код:

// https://forum.mozilla-russia.org/viewtopic.php?pid=798085#p798085
(async (pid, mp) => CustomizableUI.createWidget(({ id: "797321",
	label: "Simple Session Manager", tooltiptext: "Управление сессиями", localized: false,
	init() {
		this.handleEvent = e => this[e.type](e);
		this.onTimeout = () => this.saveSession() || this.save();
		Services.obs.addObserver(this, "quit-application");
		var {openMenu} = this;
		this.render = function() {
			this.openMenu = openMenu;
			this.constructor.prototype.render.call(this);
		}
		delete this.init;
		return this;
	},
	onCreated(btn) {
		btn.type = "menu";
		btn.phTimestamp = 0;
		btn.render = this.render;
		btn._handleClick = this.click;
		// btn.setAttribute("image", "chrome://user_chrome_files/content/custom_styles/svg/download-resume.svg");
		btn.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEnQAABJ0BfDRroQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGxSURBVDiNnZE/T1NRGMZ/7zn3lmKLkIKpaUxZFINEJZ0YHXRwcnBxMQ6aOJi4+FH8Dm5OOjA4+AEYGCTRook1JlVIVUrbW/rnPg63UtReIr7bOXmf5/ye59jDlanHS3O+wn/Mu+/DF8HFeVe5f+vaPTdfPpF4UNvk6frmhwAvXOEsvrR0IoN47xM4CHCADmDQBkAHbdTrTBRZdgYLs6NTD/MiMC+gC8MO6ke8ffWsXv3S3JpksHouv7Z4/UEeM8z6IwKfEGjYRt0G1d3m1p3n0Y1JBi/vTtfKw1Yec2CHBDFmXYjboAgL4tTc5pXsmcNcDxIDUFQHuqgfYT69OAuE9qqAoX4jIcAL7b9H+0deOYYg3tkYXxx2EAo7dQYEBF/TEULD5oow7KHut18RhGVD3GwxMZ36YWn6IJd37vQCSMSNBpYQCMs43PQMAJXlwtr6k7CGfo9iznN1ebGY7Ak1NSJwYERYJgM+Q+nClVzp/OXc3wWMwdT+jAVKCMwLC2PUeoMrrEI4m94BQq0a6mxjoca/YKFgUCfeqR8jPhoHcIAHe3ST2+UFLv2T8o/5uMvrnx9Wn65p5nMEAAAAAElFTkSuQmCC");
		var popup = btn.ownerDocument.createXULElement("menupopup");
		popup.filler = this;
		popup.id = pid;
		popup.setAttribute("onpopupshowing", "event.defaultPrevented || filler.fill(event)");
		btn.prepend(popup);

		btn.addEventListener("mousedown", this);
		popup.addEventListener("command", this);
		btn.ownerGlobal.addEventListener("unload", () => {
			btn.removeEventListener("mousedown", this);
			popup.removeEventListener("command", this);
			if (popup.filler != this)
				popup.removeEventListener("dragstart", this),
				popup.removeEventListener("popuphidden", this);
		}, {once: true});
	},
	openMenu(arg) {
		var pos;
		if (this.matches(".widget-overflow-list > :scope"))
			pos = "after_start";
		else var win = this.ownerGlobal, {width, height, top, bottom, left, right} =
			this.closest("toolbar").getBoundingClientRect(), pos = width > height
				? `${win.innerHeight - bottom > top ? "after" : "before"}_start`
				: `${win.innerWidth - right > left ? "end" : "start"}_before`;
		this.firstChild.setAttribute("position", pos);
		delete this.openMenu;
		this.openMenu(arg);
	},

	mousedown(e) {
		if (e.button) return;
		var trg = e.target;
		if (trg.nodeName.startsWith("t")) {
			trg.mdTimestamp = Cu.now();
			trg.tid = e.view.setTimeout(this.onTimeout, 500);
			return e.preventDefault();
		}
		e.detail == 2 && trg.nodeName == "menu" && this.boot(trg);
	},
	boot(trg) {
		var popup = trg.parentNode;
		var old = popup.querySelector("[boot]");
		if (old != trg) old?.removeAttribute("boot");
		trg.toggleAttribute("boot");
		popup.dblMD = true;
	},
	click() {
		var win = this.ownerGlobal;
		if (win.event.target != this) return;
		win.clearTimeout(this.tid);
		if (this.mdTimestamp - this.phTimestamp > 50) this.open = true;
	},
	command(e) {
		var arg, trg = e.target, cmd = trg.value;
		if (cmd.startsWith("r")) {
			arg = trg.parentNode.parentNode.label;
			if (cmd.startsWith("res"))
				return this[cmd](arg, (trg.label.indexOf("вклад")>0) && e.view); // Восстановить в новых вкладках
		}
		this[cmd](arg) || this.save();
	},

	dragstart(e) {
		var trg = e.target;
		if (trg.nodeName != "menu") return;

		var popup = trg.parentNode;
		this.dragData = {trg, mouse: true};
		trg.menupopup.hidePopup();

		var win = trg.ownerGlobal;
		win.setCursor("grabbing");
		var {width} = trg.getBoundingClientRect();
		trg.setAttribute("maxwidth", width);

		win.addEventListener("mouseup", this);
		popup.addEventListener("mousemove", this);
	},
	mousemove(e) {
		var trg = e.target, dtrg = this.dragData.trg;
		if (trg == dtrg || trg.nodeName != "menu") return;

		e.movementY > 0
			? trg.nextSibling != dtrg && trg.after(dtrg)
			: trg.previousSibling != dtrg && trg.before(dtrg);
	},
	mouseup(arg) {
		if (arg.constructor.isInstance?.(arg)) {
			arg.preventDefault();
			var {trg} = this.dragData;
			this.dragData.mouse = false;
		}
		else var trg = arg;

		trg.removeAttribute("maxwidth");
		trg.ownerGlobal.setCursor("auto");
		trg.ownerGlobal.removeEventListener("mouseup", this);
		trg.parentNode.removeEventListener("mousemove", this);
	},

	popuphidden(e) {
		if (!e.target.id) return;
		e.view.removeEventListener("keydown", this, true);
		var popup = e.target;
		popup.parentNode.phTimestamp = Cu.now();
		if (!this.dragData && !popup.dblMD) return;

		var save;
		if (this.dragData) {
			var {trg, mouse} = this.dragData;
			mouse && this.mouseup(trg);

			delete this.dragData;
			var order = Array.from(popup.getElementsByTagName("menu"), m => m.label);
			if (order.toString() != this.meta.order.toString()) {
				var hasBoot = this.meta.boot != null;
				if (hasBoot) var bootName = this.meta.order[this.meta.boot];
				this.meta.order = order;
				if (hasBoot) this.meta.boot = this.meta.order.indexOf(bootName);
				save = true;
			}
		}
		if (popup.dblMD) {
			delete popup.dblMD;
			var {boot} = this.meta;
			var bootNode = e.target.querySelector("[boot]");
			var ind = bootNode && this.meta.order.indexOf(bootNode.label);
			if (ind != boot)
				this.meta.boot = ind,
				save = true;
		}
		save && this.save(e.view);
	},

	sku: `#${pid} > menu[maxwidth]`,
	skd: `#${pid} > menu:is([maxwidth],[_moz-menuactive]):not([open])`,
	keydown(e) {
		if (e.repeat && e.key == "Shift" || !e.shiftKey) return;
		var func = this.keyHandlers[e.key];
		if (!func) return;

		var menu = e.view.windowRoot
			.ownerGlobal.document.querySelector(this.skd);
		if (menu)
			e.stopImmediatePropagation(),
			func.call(this, menu);
	},
	keyup(e) {
		if (e.key != "Shift") return;
		var win = e.view.windowRoot.ownerGlobal;
		win.removeEventListener("keyup", this, true);
		win.document.querySelector(this.skd)?.removeAttribute("maxwidth");
	},
	keyHandlers: {
		Enter(menu) {
			this.boot(menu);
		},
		ArrowDown(menu) {
			var ns = menu.nextSibling;
			if (ns) ns.after(menu), this.arrow(menu);
		},
		ArrowUp(menu) {
			var ps = menu.previousSibling;
			if (ps.nodeName == "menu") ps.before(menu), this.arrow(menu);
		}
	},
	arrow(menu) {
		if (menu.hasAttribute("maxwidth")) return;
		menu.setAttribute("maxwidth", menu.getBoundingClientRect().width);
		menu.ownerGlobal.addEventListener("keyup", this, true);
		this.dragData = {trg: menu};
	},

	fill(e) {
		var mxe = e.view.MozXULElement;

		var initFrag = mxe.parseXULToFragment(`
			<menuitem value="saveSession" class="menuitem-iconic" label="Сохранить сессию"/>
			<menuitem value="deleteAllSessions" class="menuitem-iconic" label="Удалить все сессии"/>
			<menuseparator/>
		`);
		this.menuFrag = mxe.parseXULToFragment(`<menu class="menu-iconic"><menupopup>
			<menuitem label="Восстановить"
				class="menuitem-iconic" value="restoreSession"/>
			<menuitem label="Восстановить в новых вкладках"
				class="menuitem-iconic" value="restoreSession"/>
			<menuitem label="Переименовать"
				class="menuitem-iconic" value="renameSession"/>
			<menuitem label="Удалить"
				class="menuitem-iconic" value="removeSession"/>
		</menupopup></menu>`);

		this.regStyle();

		var filler = {fill: e => e.target.id
			? e.view.addEventListener("keydown", this, true)
				|| e.target.fillFlag || this.fillSessions(e.target)
			: this.dragData?.mouse && e.preventDefault()
		};

		(this.fill = e => {
			var trg = e.target;
			trg.setAttribute("context", "");
			trg.append(trg.ownerDocument.importNode(initFrag, true));
			(trg.filler = filler).fill(e);
			trg.addEventListener("dragstart", this);
			trg.addEventListener("popuphidden", this);
		})(e);
	},
	fillSessions(popup) {
		while(popup.lastChild.nodeName == "menu") popup.lastChild.remove();
		var ind = 0, {boot} = this.meta;
		for(var name of this.meta.order) {
			var df = popup.ownerDocument.importNode(this.menuFrag, true);
			df.firstChild.setAttribute("label", name);
			if (ind++ == boot) df.firstChild.toggleAttribute("boot");
			popup.append(df);
		}
		popup.fillFlag = true;
	},
	regStyle() {
		delete this.regStyle;
		var subst = "ucf-ssm-style-resurl";
		Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler).setSubstitution(
			subst, Services.io.newURI("data:text/css;charset=utf-8," + encodeURIComponent(`
			@-moz-document url-prefix(chrome://browser/content/browser.xhtml) {
				#${pid} > menu {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEnQAABJ0BfDRroQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGxSURBVDiNnZE/T1NRGMZ/7zn3lmKLkIKpaUxZFINEJZ0YHXRwcnBxMQ6aOJi4+FH8Dm5OOjA4+AEYGCTRook1JlVIVUrbW/rnPg63UtReIr7bOXmf5/ye59jDlanHS3O+wn/Mu+/DF8HFeVe5f+vaPTdfPpF4UNvk6frmhwAvXOEsvrR0IoN47xM4CHCADmDQBkAHbdTrTBRZdgYLs6NTD/MiMC+gC8MO6ke8ffWsXv3S3JpksHouv7Z4/UEeM8z6IwKfEGjYRt0G1d3m1p3n0Y1JBi/vTtfKw1Yec2CHBDFmXYjboAgL4tTc5pXsmcNcDxIDUFQHuqgfYT69OAuE9qqAoX4jIcAL7b9H+0deOYYg3tkYXxx2EAo7dQYEBF/TEULD5oow7KHut18RhGVD3GwxMZ36YWn6IJd37vQCSMSNBpYQCMs43PQMAJXlwtr6k7CGfo9iznN1ebGY7Ak1NSJwYERYJgM+Q+nClVzp/OXc3wWMwdT+jAVKCMwLC2PUeoMrrEI4m94BQq0a6mxjoca/YKFgUCfeqR8jPhoHcIAHe3ST2+UFLv2T8o/5uMvrnx9Wn65p5nMEAAAAAElFTkSuQmCC");
				}
				#${pid} > [value=saveSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKVSURBVHjajFNNTBNREJ6+7na73bLbQn8I0BKNCYWKGr0oYoiJPxeNh6oHozcOxgSVi4le1JNHQRPigRsewURNPKiJMQicSISUACelRVJ2tz/bdktpy9adBzT8xTjJ5L35ezNv5hsLHEAv7noEntguChx7EuV8oTydKRhfno8kCnt9LTuFZ7f87mAz/8rnl86GOoOBBq+LQX1S1ioLs0txWdEmsulS38Phlcy+BwZ6W0L+JsfolcjpsFDnBKNcgQ193bRUwSrwQFgr6Dkd3o9NzilqMdI/FF+sPYCZ245I4zdunw9DtQr6LxlK6bVdpdrcPAiHfGBYLDD69ltUXsmfw0oIGoPNjkHMjMHa3J9asDyRpoyEOrQR0+dq5MxR0c29Rj3Bhvn8YjeWjZmN9cpWcApSkzpkptYg8T1FdWjTfysg1Ang9YtdT+80OogTbJc7OlsD+OftzOqsBrMflqHe6QKXIEL00zLV0UpSBbM/G9DeEQy6OXKJsfPsCbdXYjb0Yu2/nmMS9LSGYGlYpXLPkxCwkq1mR98Gn8Q4XexxYhhgYKf3TPTfRDaPqklkvViaSSpaxSpw/x1v5e0UG8W10k+iW8qfF2ZiccIydFQ7ScsXKO8ep4NiYj4ai6k5+Eoev1F1Wcn8QJDgnAnHbGYRWTh800cZ77Ryzmr6eCGXzYGiZCcQ2vQ32XT5PiIMTJBI4Waw1ZtZzLu7XaSMd1s9b9paKJA+vpuKqols3y4ov7wXaPN67GPXIl1hnDOOansyVicHhGHMzHkzeDKqqPnr/UOJxX3LNNDb5BJd3KC3Uexu7wgEGxq3limRqcxH47FVWRtPreYePBpJagdu4zYhwjx1cMFus59CuVgqTmPDDlrnvwIMAGS8IFCLagHpAAAAAElFTkSuQmCC");
				}
				#${pid} [value=restoreSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAACzElEQVQ4jX2QPWwbZRjHf+/5vfPdObZjO3Ga2pVpGtxCP4QIDKgqDCDUNQwgJCS2qFI3hBCRCvXSCcTAAu0GY0Fi4kuq+IjUCpWAgCTq4IpS3Di2G8c+x/bZ57t7mSJFMfBf/3p+z+95BPvy3pufxqKmcVY3ZFFKmQtFGIReuNnrDze6lv5TqfSyx4EIgA/e+fzkZML6MDmVnD80mz1kxW3DMAxQCs/z6Hb6g3q1Xt1ttdfa7dbFN668vrkHkADJhPntufNnc0LTDi7AjFkkUknzcGF2LvCDuZVvfiwCj+/1GoAmhL3x6x2cZhsVqjFIGIZs15ts/LJOJCLt/Z0EiE3Y9eLpR1ONaoMH9zbHAGiC9FSKkwunWF1Z3RoDABhRg/zRPPmj+XHA/0QDaLectFLj6gcT+AHdTm9mzMC27e766lrWtCwy2Qz2hIUeNVDAaDCku9ujWW/ij3wsO9oaMzCiunf66TMU5gt4wyGbf1Upr5W5u15mq1JDhYqZQo6HIuqtueLw4vLXzwC89NZXeQFw/ePv7zx5buHEf6nf3qgMVipOGE0ko6EKhoQi3NnZ+c51BwsaQLOxM9N1uv86vNtz+aHSDn0pH9S2tl6r/Pn3s/VG/WYunzs/NZ2OSID0dKpRrzZS98v3kYbEsi1004BQcbtc9Q07bdYb229/dvmFL5aWVvVts9mq1Wo/e54vJIAQqGOPzQHgj3zcvos39BAIBshRqJQYjYZ/AFy79tQIeHVx+ctiIDRTA+g43Xud9q4PIHVJPBknk82QzqY5VZg2UUrEYhOX9s5aXL5RnMykb8Rj9kcC4OrSVZ1Hpt+PJ5PPz+SzR5KpyYRpGgRKMei7fHLr7lBmZ1XHcX4LgsCxLPt4JBLRm82Hr4j9DyuVSprdO/ZELGafkXpkXtPA94OK03Vv/R4/csEw5XOGbuieN6y6bv/S9XdfvPkPTgcoDlpQJpwAAAAASUVORK5CYII=");
				}
				#${pid} [value=renameSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPFJREFUeNrEU0sOgkAMbc1IIHoQT+KeY7hkBW5ccwLdsOICxI2X8peIGhggI21gggpCwsImM01f+tpOO0WlFIwR0efgeR5nsG0boihizPd91A5UwZDjuq5qwycwUkQYhttSr4Y4l76fDdtBEAQqz3OVZdnPQ09o2sQhrpBSQgnAen/qzLxZzljHcawxy7KAuCJNUyiKAm53SS0FBGQNlUW3lFO2iVCLYRhAXJEkCQPn65NGAswpL/4fWAebt1ZGXB3geHlw9maXsKqjS94CHNxF7xRM0/wKgI7jDBojvbnZAz3Gv/9E7NvGvmXCsev8EmAAWocA9ofpaRIAAAAASUVORK5CYII=");
				}
				#${pid} :is([value=removeSession], [value=deleteAllSessions]) {
					list-style-image: url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABt0lEQVQ4ja2RT2sTURTFz8tk0nntFAQrVLBQxIUg/tkkMoLyIEVECHZhNrrxI+hOP5Mb6eBCcEipYIjZ2LpvhFasUtBSkwzOve+6mE6YSScuxLt67753fudyLvA/a7T+qCHmiTfrfWCMd3j9diPfq2SHX+uPnzJx71gP33w3bX9avBcE2j+SkK30Di7ffJ71VebMxD1hC2GGEEfH41prpftynIm92A0t2aYQQYgBThrndz/2KwCgf9Z2LNtImFOAtc356ijcCwJdJrZEUWzPfJpMAAAStPWR/h2Ktc10CoIQRUKEabFNvNbKfndcAKSjtrVfHW5YorWTzxDKYAxLtOWM+P7yt51hIYPpsDTNTyB/EwNAtWxdMuV8GCfbgxjxIviUYSV/SQOrbVjitbx4N8alBeXcdZR+3Tl3xS8FlIkt0dbnWH3xlbPgKgUo3HEq+tX7C4EuAAbGeOmqCuLIJt69s3PeQ1epKGfapCQJO6vGmwAWf/C1Wau6td8dV123BaAAUfHw6gSwtP3ugxA/y6X9INszAOQgbwFAIC/MQb9/Kv2vF2/UByejlVVn1Xiby/X6rPd/qj/1ak71UYKuwQAAAABJRU5ErkJggg==");
				}
				#${pid} > menuseparator:last-child,
				#${pid} > menu[maxwidth] > .menu-right,
				#${pid} > [value=deleteAllSessions]:nth-last-child(2) {
					display: none;
				}
				#${pid} > menu[boot] {
					color: red;
					font-weight: bold;
				}
				#${pid} > menu[maxwidth] {
					color: blue;
					font-weight: bold;
					outline-offset: -2px;
					outline: 2px solid orangered;
				}
			}
		`.replace(/;$/gm, " !important;"))));
		var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
		sss.loadAndRegisterSheet(Services.io.newURI("resource://" + subst), sss.USER_SHEET);
	},

	get gs() {
		delete this.gs;
		return this.gs = Cu.import("resource:///modules/sessionstore/SessionStore.jsm", {});
	},
	getState() {
		return JSON.parse(this.gs.SessionStore.getBrowserState());
	},
	splice(name, newName) {
		var ind = this.meta.order.indexOf(name);
		if (ind == -1) return;
		var args = [ind, 1];

		if (1 in arguments) args.push(newName);
		else {
			if (ind == this.meta.boot) this.meta.boot = null;
			else if (ind < this.meta.boot) this.meta.boot--;
		}
		this.meta.order.splice(...args);
	},

	get meta() {
		var file = Services.dirsvc.get("UChrm", Ci.nsIFile);
		file.append("simple_session_manager.json");
		this.path = file.path;
		try {
			this.data = JSON.parse(Cu.readUTF8File(file));
		} catch {
			this.pp = file.parent.path;
			this.data = Object.create(null);
		}
		var meta = this.data[mp];
		if (!meta) {
			var order = Object.keys(this.data);
			meta = this.data[mp] = {order, boot: null};
		}
		delete this.meta;
		return this.meta = meta;
	},
	async save(excWin) {
		var io = Cu.getGlobalForObject(Cu).IOUtils;
		if (this.pp)
			await io.makeDirectory(this.pp), delete this.pp;
		(this.save = excWin => {
			this.meta.order.length
				? io.writeJSON(this.path, this.data)
				: io.remove(this.path, {ignoreAbsent: true});
			for(var win of CustomizableUI.windows) {
				if (win == excWin) continue;
				var popup = win.document.getElementById(pid);
				if (popup) popup.fillFlag = false;
			}
		})(excWin);
	},

	get prompter() {
		var {prompt} = Services;
		var p = {}, args = [null, null, "UCF Simple Session Manager"];
		p.alert = prompt.alert.bind(...args);
		p.confirm = prompt.confirm.bind(...args);
		var pr = prompt.prompt.bind(...args);
		p.prompt = (msg, value) => {
			var res = {value};
			return pr(msg, res, null, {}) ? res.value : null;
		}
		delete this.prompter;
		return this.prompter = p;
	},
	observe(s, t, data) {
		Services.obs.removeObserver(this, "quit-application");
		if (data.includes("restart")) return;

		var {boot} = this.meta;
		if (boot == null) return;

		var state = this.data[this.meta.order[boot]];
		//this.gs.SessionStoreInternal.getCurrentState = () => state;
		var ssi = this.gs.SessionStoreInternal;
		ssi.getCurrentState = () => state;
		Services.obs.removeObserver(ssi, "browser:purge-session-history");

		Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
	},

	get bwt() {
		delete this.bwt;
		var url = "resource:///modules/BrowserWindowTracker.jsm";
		return this.bwt = ChromeUtils.import(url).BrowserWindowTracker;
	},
	getName(state) {
		var wl = state.windows.length, tl = 0;
		for(var w of state.windows) tl += w.tabs.length;
		return `${
			this.bwt.getTopWindow().gBrowser.selectedTab.label.slice(0, 70)
		} ${wl}/${tl} [${
			new Date().toLocaleString("mn").replace(" ", "-")
		}]`;
	},
	exists(name) {
		this.meta;
		return (this.exists = name => name in this.data &&
			!this.prompter.alert("Сессия с тем же именем уже существует!"))(name);
	},
	saveSession(state = this.getState(), name = this.getName(state)) {
		var name = this.prompter.prompt("Сохранить:", name);
		if (name == null) return true;

		if (this.exists(name)) return this.saveSession(state, name);

		this.data[name] = state;

		this.meta.order.push(name);
		//this.meta.order.unshift(name);
		//if (this.meta.boot != null) this.meta.boot++;
	},
	restoreSession(name, win) {
		var ss = this.gs.SessionStore;
		var state = JSON.stringify(this.data[name]);
		win ? ss.setWindowState(win, state) : ss.setBrowserState(state);
	},
	renameSession(name, newName = name) {
		var newName = this.prompter.prompt(`Переименовать "${name}" в:`, newName);
		if (newName == null || newName == name) return true;

		if (this.exists(newName)) return this.renameSession(name, newName);

		var {data} = this;
		this.splice(name, newName);
		data[newName] = data[name];
		delete data[name];
	},
	removeSession(name) {
		if (!this.prompter.confirm(`Вы уверены, что хотите удалить ${name} ?`))
			return true;
		delete this.data[name];
		this.splice(name);
	},
	deleteAllSessions() {
		if (!this.prompter.confirm(`Вы уверены, что хотите удалить все сессии?`))
			return true;
		delete this.dragData;
		delete this.bwt.getTopWindow().document.getElementById(pid).dblMD;
		this.meta = (this.data = Object.create(null))[mp] = {order: [], boot: null};
	}
}).init()))("ucf-ssm-menupopup", "{07cae4f5-18b0-487b-80eb-973304af9528}");

Dobrov пишет

одна иконка прописана 2 раза. Как сократить код

Например, задать её как свойство объекта, и далее использовать, типа this.image

А вообще для восстановления удобнее сразу кликать по имени сохранённой сесии (и восстанавливать в новых вкладках при клике с нажатым Ctrl), а в подменю оставить только: Переименовать и Удалить.

Там же двойной левый mousedown устанавливает
или снимает загрузочную сессию. С этим что делать?
Ладно, допустим перенесём на клик ПКМ.

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

Выделить код

Код:

(async (pid, mp, self) => CustomizableUI.createWidget((self = { id: "797321",
	label: "Simple Session Manager", tooltiptext: "Управление сессиями", localized: false,
	init() {
		this.handleEvent = e => this[e.type](e);
		this.onTimeout = () => this.saveSession() || this.save();
		Services.obs.addObserver(this, "quit-application");
		var {openMenu} = this;
		this.render = function() {
			this.openMenu = openMenu;
			this.constructor.prototype.render.call(this);
		}
		delete this.init;
		return this;
	},
	image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEnQAABJ0BfDRroQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGxSURBVDiNnZE/T1NRGMZ/7zn3lmKLkIKpaUxZFINEJZ0YHXRwcnBxMQ6aOJi4+FH8Dm5OOjA4+AEYGCTRook1JlVIVUrbW/rnPg63UtReIr7bOXmf5/ye59jDlanHS3O+wn/Mu+/DF8HFeVe5f+vaPTdfPpF4UNvk6frmhwAvXOEsvrR0IoN47xM4CHCADmDQBkAHbdTrTBRZdgYLs6NTD/MiMC+gC8MO6ke8ffWsXv3S3JpksHouv7Z4/UEeM8z6IwKfEGjYRt0G1d3m1p3n0Y1JBi/vTtfKw1Yec2CHBDFmXYjboAgL4tTc5pXsmcNcDxIDUFQHuqgfYT69OAuE9qqAoX4jIcAL7b9H+0deOYYg3tkYXxx2EAo7dQYEBF/TEULD5oow7KHut18RhGVD3GwxMZ36YWn6IJd37vQCSMSNBpYQCMs43PQMAJXlwtr6k7CGfo9iznN1ebGY7Ak1NSJwYERYJgM+Q+nClVzp/OXc3wWMwdT+jAVKCMwLC2PUeoMrrEI4m94BQq0a6mxjoca/YKFgUCfeqR8jPhoHcIAHe3ST2+UFLv2T8o/5uMvrnx9Wn65p5nMEAAAAAElFTkSuQmCC",
	onCreated(btn) {
		btn.type = "menu";
		btn.phTimestamp = 0;
		btn.render = this.render;
		btn.onclick = this.click;
		// btn.setAttribute("image", "chrome://user_chrome_files/content/custom_styles/svg/download-resume.svg");
		btn.setAttribute("image", this.image);
		var popup = btn.ownerDocument.createXULElement("menupopup");
		popup.filler = this;
		popup.id = pid;
		popup.setAttribute("onpopupshowing", "event.defaultPrevented || filler.fill(event)");
		btn.prepend(popup);

		btn.addEventListener("mousedown", this);
		popup.addEventListener("command", this);
		btn.ownerGlobal.addEventListener("unload", () => {
			btn.removeEventListener("mousedown", this);
			popup.removeEventListener("command", this);
			if (popup.filler != this)
				popup.removeEventListener("dragstart", this),
				popup.removeEventListener("popuphidden", this);
		}, {once: true});
	},
	openMenu(arg) {
		var pos;
		if (this.matches(".widget-overflow-list > :scope"))
			pos = "after_start";
		else var win = this.ownerGlobal, {width, height, top, bottom, left, right} =
			this.closest("toolbar").getBoundingClientRect(), pos = width > height
				? `${win.innerHeight - bottom > top ? "after" : "before"}_start`
				: `${win.innerWidth - right > left ? "end" : "start"}_before`;
		this.firstChild.setAttribute("position", pos);
		delete this.openMenu;
		this.openMenu(arg);
	},

	mousedown(e) {
		if (e.button) return;
		var trg = e.target;
		if (trg.nodeName.startsWith("t"))
			trg.mdTimestamp = Cu.now(),
			trg.tid = e.view.setTimeout(this.onTimeout, 500),
			e.preventDefault();
	},
	boot(trg) {
		var popup = trg.parentNode;
		var old = popup.querySelector("[boot]");
		if (old != trg) old?.removeAttribute("boot");
		trg.toggleAttribute("boot");
		popup.bootChanged = true;
	},
	muTimestamp: 0,
	click(e) {
		var trg = e.target;
		if (trg.nodeName == "menu") {
			if (e.button > 1) self.boot(trg);
			else if (Cu.now() - self.muTimestamp > 50)
				e.view.closeMenus(trg.menupopup),
				self.restoreSession(trg.label, (e.button || e.ctrlKey) && e.view);
		}
		else if (trg != this || e.button) return;
		e.view.clearTimeout(this.tid);
		if (this.mdTimestamp - this.phTimestamp > 50) this.open = true;
	},
	command(e) {
		var arg, trg = e.target, cmd = trg.value;
		if (cmd.startsWith("r"))
			arg = trg.parentNode.parentNode.label;
		this[cmd](arg) || this.save();
	},

	dragstart(e) {
		var trg = e.target;
		if (trg.nodeName != "menu") return;

		var popup = trg.parentNode;
		this.dragData = {trg, mouse: true};
		trg.menupopup.hidePopup();

		var win = trg.ownerGlobal;
		win.setCursor("grabbing");
		var {width} = trg.getBoundingClientRect();
		trg.setAttribute("maxwidth", width);

		win.addEventListener("mouseup", this);
		popup.addEventListener("mousemove", this);
	},
	mousemove(e) {
		var trg = e.target, dtrg = this.dragData.trg;
		if (trg == dtrg || trg.nodeName != "menu") return;

		e.movementY > 0
			? trg.nextSibling != dtrg && trg.after(dtrg)
			: trg.previousSibling != dtrg && trg.before(dtrg);
	},
	mouseup(arg) {
		if (arg.constructor.isInstance?.(arg)) {
			arg.preventDefault();
			var {trg} = this.dragData;
			this.dragData.mouse = false;
			this.muTimestamp = Cu.now();
		}
		else var trg = arg;

		trg.removeAttribute("maxwidth");
		trg.ownerGlobal.setCursor("auto");
		trg.ownerGlobal.removeEventListener("mouseup", this);
		trg.parentNode.removeEventListener("mousemove", this);
	},

	popuphidden(e) {
		if (!e.target.id) return;
		e.view.removeEventListener("keydown", this, true);
		var popup = e.target;
		popup.parentNode.phTimestamp = Cu.now();

		var save;
		if (this.dragData) {
			var {trg, mouse} = this.dragData;
			mouse && this.mouseup(trg);

			delete this.dragData;
			var order = Array.from(popup.getElementsByTagName("menu"), m => m.label);
			if (order.toString() != this.meta.order.toString()) {
				var hasBoot = this.meta.boot != null;
				if (hasBoot) var bootName = this.meta.order[this.meta.boot];
				this.meta.order = order;
				if (hasBoot) this.meta.boot = this.meta.order.indexOf(bootName);
				save = true;
			}
		}
		if (popup.bootChanged) {
			delete popup.bootChanged;
			var {boot} = this.meta;
			var bootNode = e.target.querySelector("[boot]");
			var ind = bootNode && this.meta.order.indexOf(bootNode.label);
			if (ind != boot)
				this.meta.boot = ind,
				save = true;
		}
		save && this.save(e.view);
	},

	sku: `#${pid} > menu[maxwidth]`,
	skd: `#${pid} > menu:is([maxwidth],[_moz-menuactive]):not([open])`,
	keydown(e) {
		if (e.repeat && e.key == "Shift" || !e.shiftKey && e.key != " ") return;
		var func = this.keyHandlers[e.key];
		if (!func) return;

		var menu = e.view.windowRoot
			.ownerGlobal.document.querySelector(this.skd);
		if (menu)
			e.stopImmediatePropagation(),
			func.call(this, menu, e);
	},
	keyup(e) {
		if (e.key != "Shift") return;
		var win = e.view.windowRoot.ownerGlobal;
		win.removeEventListener("keyup", this, true);
		win.document.querySelector(this.skd)?.removeAttribute("maxwidth");
	},
	keyHandlers: {
		Enter(menu) {
			this.boot(menu);
		},
		ArrowDown(menu) {
			var ns = menu.nextSibling;
			if (ns) ns.after(menu), this.arrow(menu);
		},
		ArrowUp(menu) {
			var ps = menu.previousSibling;
			if (ps.nodeName == "menu") ps.before(menu), this.arrow(menu);
		},
		" "(menu, e) {
			e.preventDefault();
			menu.ownerGlobal.closeMenus(menu.parentNode);
			this.restoreSession(menu.label, e.ctrlKey && menu.ownerGlobal);
		}
	},
	arrow(menu) {
		if (menu.hasAttribute("maxwidth")) return;
		menu.setAttribute("maxwidth", menu.getBoundingClientRect().width);
		menu.ownerGlobal.addEventListener("keyup", this, true);
		this.dragData = {trg: menu};
	},

	fill(e) {
		var mxe = e.view.MozXULElement;

		var initFrag = mxe.parseXULToFragment(`
			<menuitem value="saveSession" class="menuitem-iconic" label="Сохранить сессию"/>
			<menuitem value="deleteAllSessions" class="menuitem-iconic" label="Удалить все сессии"/>
			<menuseparator/>
		`);
		this.menuFrag = mxe.parseXULToFragment(`<menu class="menu-iconic"><menupopup>
			<menuitem label="Переименовать"
				class="menuitem-iconic" value="renameSession"/>
			<menuitem label="Удалить"
				class="menuitem-iconic" value="removeSession"/>
		</menupopup></menu>`);

		this.regStyle();

		var filler = {fill: e => e.target.id
			? e.view.addEventListener("keydown", this, true)
				|| e.target.fillFlag || this.fillSessions(e.target)
			: this.dragData?.mouse && e.preventDefault()
		};

		(this.fill = e => {
			var trg = e.target;
			trg.setAttribute("context", "");
			trg.append(trg.ownerDocument.importNode(initFrag, true));
			(trg.filler = filler).fill(e);
			trg.addEventListener("dragstart", this);
			trg.addEventListener("popuphidden", this);
		})(e);
	},
	fillSessions(popup) {
		while(popup.lastChild.nodeName == "menu") popup.lastChild.remove();
		var ind = 0, {boot} = this.meta;
		for(var name of this.meta.order) {
			var df = popup.ownerDocument.importNode(this.menuFrag, true);
			df.firstChild.setAttribute("label", name);
			if (ind++ == boot) df.firstChild.toggleAttribute("boot");
			popup.append(df);
		}
		popup.fillFlag = true;
	},
	regStyle() {
		delete this.regStyle;
		var subst = "ucf-ssm-style-resurl";
		Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler).setSubstitution(
			subst, Services.io.newURI("data:text/css;charset=utf-8," + encodeURIComponent(`
			@-moz-document url-prefix(chrome://browser/content/browser.xhtml) {
				#${pid} > menu {
					list-style-image: url("${this.image}");
				}
				#${pid} > [value=saveSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKVSURBVHjajFNNTBNREJ6+7na73bLbQn8I0BKNCYWKGr0oYoiJPxeNh6oHozcOxgSVi4le1JNHQRPigRsewURNPKiJMQicSISUACelRVJ2tz/bdktpy9adBzT8xTjJ5L35ezNv5hsLHEAv7noEntguChx7EuV8oTydKRhfno8kCnt9LTuFZ7f87mAz/8rnl86GOoOBBq+LQX1S1ioLs0txWdEmsulS38Phlcy+BwZ6W0L+JsfolcjpsFDnBKNcgQ193bRUwSrwQFgr6Dkd3o9NzilqMdI/FF+sPYCZ245I4zdunw9DtQr6LxlK6bVdpdrcPAiHfGBYLDD69ltUXsmfw0oIGoPNjkHMjMHa3J9asDyRpoyEOrQR0+dq5MxR0c29Rj3Bhvn8YjeWjZmN9cpWcApSkzpkptYg8T1FdWjTfysg1Ang9YtdT+80OogTbJc7OlsD+OftzOqsBrMflqHe6QKXIEL00zLV0UpSBbM/G9DeEQy6OXKJsfPsCbdXYjb0Yu2/nmMS9LSGYGlYpXLPkxCwkq1mR98Gn8Q4XexxYhhgYKf3TPTfRDaPqklkvViaSSpaxSpw/x1v5e0UG8W10k+iW8qfF2ZiccIydFQ7ScsXKO8ep4NiYj4ai6k5+Eoev1F1Wcn8QJDgnAnHbGYRWTh800cZ77Ryzmr6eCGXzYGiZCcQ2vQ32XT5PiIMTJBI4Waw1ZtZzLu7XaSMd1s9b9paKJA+vpuKqols3y4ov7wXaPN67GPXIl1hnDOOansyVicHhGHMzHkzeDKqqPnr/UOJxX3LNNDb5BJd3KC3Uexu7wgEGxq3limRqcxH47FVWRtPreYePBpJagdu4zYhwjx1cMFus59CuVgqTmPDDlrnvwIMAGS8IFCLagHpAAAAAElFTkSuQmCC");
				}
				#${pid} [value=restoreSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAACzElEQVQ4jX2QPWwbZRjHf+/5vfPdObZjO3Ga2pVpGtxCP4QIDKgqDCDUNQwgJCS2qFI3hBCRCvXSCcTAAu0GY0Fi4kuq+IjUCpWAgCTq4IpS3Di2G8c+x/bZ57t7mSJFMfBf/3p+z+95BPvy3pufxqKmcVY3ZFFKmQtFGIReuNnrDze6lv5TqfSyx4EIgA/e+fzkZML6MDmVnD80mz1kxW3DMAxQCs/z6Hb6g3q1Xt1ttdfa7dbFN668vrkHkADJhPntufNnc0LTDi7AjFkkUknzcGF2LvCDuZVvfiwCj+/1GoAmhL3x6x2cZhsVqjFIGIZs15ts/LJOJCLt/Z0EiE3Y9eLpR1ONaoMH9zbHAGiC9FSKkwunWF1Z3RoDABhRg/zRPPmj+XHA/0QDaLectFLj6gcT+AHdTm9mzMC27e766lrWtCwy2Qz2hIUeNVDAaDCku9ujWW/ij3wsO9oaMzCiunf66TMU5gt4wyGbf1Upr5W5u15mq1JDhYqZQo6HIuqtueLw4vLXzwC89NZXeQFw/ePv7zx5buHEf6nf3qgMVipOGE0ko6EKhoQi3NnZ+c51BwsaQLOxM9N1uv86vNtz+aHSDn0pH9S2tl6r/Pn3s/VG/WYunzs/NZ2OSID0dKpRrzZS98v3kYbEsi1004BQcbtc9Q07bdYb229/dvmFL5aWVvVts9mq1Wo/e54vJIAQqGOPzQHgj3zcvos39BAIBshRqJQYjYZ/AFy79tQIeHVx+ctiIDRTA+g43Xud9q4PIHVJPBknk82QzqY5VZg2UUrEYhOX9s5aXL5RnMykb8Rj9kcC4OrSVZ1Hpt+PJ5PPz+SzR5KpyYRpGgRKMei7fHLr7lBmZ1XHcX4LgsCxLPt4JBLRm82Hr4j9DyuVSprdO/ZELGafkXpkXtPA94OK03Vv/R4/csEw5XOGbuieN6y6bv/S9XdfvPkPTgcoDlpQJpwAAAAASUVORK5CYII=");
				}
				#${pid} [value=renameSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPFJREFUeNrEU0sOgkAMbc1IIHoQT+KeY7hkBW5ccwLdsOICxI2X8peIGhggI21gggpCwsImM01f+tpOO0WlFIwR0efgeR5nsG0boihizPd91A5UwZDjuq5qwycwUkQYhttSr4Y4l76fDdtBEAQqz3OVZdnPQ09o2sQhrpBSQgnAen/qzLxZzljHcawxy7KAuCJNUyiKAm53SS0FBGQNlUW3lFO2iVCLYRhAXJEkCQPn65NGAswpL/4fWAebt1ZGXB3geHlw9maXsKqjS94CHNxF7xRM0/wKgI7jDBojvbnZAz3Gv/9E7NvGvmXCsev8EmAAWocA9ofpaRIAAAAASUVORK5CYII=");
				}
				#${pid} :is([value=removeSession], [value=deleteAllSessions]) {
					list-style-image: url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABt0lEQVQ4ja2RT2sTURTFz8tk0nntFAQrVLBQxIUg/tkkMoLyIEVECHZhNrrxI+hOP5Mb6eBCcEipYIjZ2LpvhFasUtBSkwzOve+6mE6YSScuxLt67753fudyLvA/a7T+qCHmiTfrfWCMd3j9diPfq2SHX+uPnzJx71gP33w3bX9avBcE2j+SkK30Di7ffJ71VebMxD1hC2GGEEfH41prpftynIm92A0t2aYQQYgBThrndz/2KwCgf9Z2LNtImFOAtc356ijcCwJdJrZEUWzPfJpMAAAStPWR/h2Ktc10CoIQRUKEabFNvNbKfndcAKSjtrVfHW5YorWTzxDKYAxLtOWM+P7yt51hIYPpsDTNTyB/EwNAtWxdMuV8GCfbgxjxIviUYSV/SQOrbVjitbx4N8alBeXcdZR+3Tl3xS8FlIkt0dbnWH3xlbPgKgUo3HEq+tX7C4EuAAbGeOmqCuLIJt69s3PeQ1epKGfapCQJO6vGmwAWf/C1Wau6td8dV123BaAAUfHw6gSwtP3ugxA/y6X9INszAOQgbwFAIC/MQb9/Kv2vF2/UByejlVVn1Xiby/X6rPd/qj/1ak71UYKuwQAAAABJRU5ErkJggg==");
				}
				#${pid} > menuseparator:last-child,
				#${pid} > menu[maxwidth] > .menu-right,
				#${pid} > [value=deleteAllSessions]:nth-last-child(2) {
					display: none;
				}
				#${pid} > menu[boot] {
					color: red;
					font-weight: bold;
				}
				#${pid} > menu[maxwidth] {
					color: blue;
					font-weight: bold;
					outline-offset: -2px;
					outline: 2px solid orangered;
				}
			}
		`.replace(/;$/gm, " !important;"))));
		var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
		sss.loadAndRegisterSheet(Services.io.newURI("resource://" + subst), sss.USER_SHEET);
	},

	get gs() {
		delete this.gs;
		return this.gs = Cu.import("resource:///modules/sessionstore/SessionStore.jsm", {});
	},
	getState() {
		return JSON.parse(this.gs.SessionStore.getBrowserState());
	},
	splice(name, newName) {
		var ind = this.meta.order.indexOf(name);
		if (ind == -1) return;
		var args = [ind, 1];

		if (1 in arguments) args.push(newName);
		else {
			if (ind == this.meta.boot) this.meta.boot = null;
			else if (ind < this.meta.boot) this.meta.boot--;
		}
		this.meta.order.splice(...args);
	},

	get meta() {
		var file = Services.dirsvc.get("UChrm", Ci.nsIFile);
		file.append("simple_session_manager.json");
		this.path = file.path;
		try {
			this.data = JSON.parse(Cu.readUTF8File(file));
		} catch {
			this.pp = file.parent.path;
			this.data = Object.create(null);
		}
		var meta = this.data[mp];
		if (!meta) {
			var order = Object.keys(this.data);
			meta = this.data[mp] = {order, boot: null};
		}
		delete this.meta;
		return this.meta = meta;
	},
	async save(excWin) {
		var io = Cu.getGlobalForObject(Cu).IOUtils;
		if (this.pp)
			await io.makeDirectory(this.pp), delete this.pp;
		(this.save = excWin => {
			this.meta.order.length
				? io.writeJSON(this.path, this.data)
				: io.remove(this.path, {ignoreAbsent: true});
			for(var win of CustomizableUI.windows) {
				if (win == excWin) continue;
				var popup = win.document.getElementById(pid);
				if (popup) popup.fillFlag = false;
			}
		})(excWin);
	},

	get prompter() {
		var {prompt} = Services;
		var p = {}, args = [null, null, "UCF Simple Session Manager"];
		p.alert = prompt.alert.bind(...args);
		p.confirm = prompt.confirm.bind(...args);
		var pr = prompt.prompt.bind(...args);
		p.prompt = (msg, value) => {
			var res = {value};
			return pr(msg, res, null, {}) ? res.value : null;
		}
		delete this.prompter;
		return this.prompter = p;
	},
	observe(s, t, data) {
		Services.obs.removeObserver(this, "quit-application");
		if (data.includes("restart")) return;

		var {boot} = this.meta;
		if (boot == null) return;

		var state = this.data[this.meta.order[boot]];
		//this.gs.SessionStoreInternal.getCurrentState = () => state;
		var ssi = this.gs.SessionStoreInternal;
		ssi.getCurrentState = () => state;
		Services.obs.removeObserver(ssi, "browser:purge-session-history");

		Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
	},

	get bwt() {
		delete this.bwt;
		var url = "resource:///modules/BrowserWindowTracker.jsm";
		return this.bwt = ChromeUtils.import(url).BrowserWindowTracker;
	},
	getName(state) {
		var wl = state.windows.length, tl = 0;
		for(var w of state.windows) tl += w.tabs.length;
		return `${
			this.bwt.getTopWindow().gBrowser.selectedTab.label.slice(0, 70)
		} ${wl}/${tl} [${
			new Date().toLocaleString("mn").replace(" ", "-")
		}]`;
	},
	exists(name) {
		this.meta;
		return (this.exists = name => name in this.data &&
			!this.prompter.alert("Сессия с тем же именем уже существует!"))(name);
	},
	saveSession(state = this.getState(), name = this.getName(state)) {
		var name = this.prompter.prompt("Сохранить:", name);
		if (name == null) return true;

		if (this.exists(name)) return this.saveSession(state, name);

		this.data[name] = state;

		this.meta.order.push(name);
		//this.meta.order.unshift(name);
		//if (this.meta.boot != null) this.meta.boot++;
	},
	restoreSession(name, win) {
		var ss = this.gs.SessionStore;
		var state = JSON.stringify(this.data[name]);
		win ? ss.setWindowState(win, state) : ss.setBrowserState(state);
	},
	renameSession(name, newName = name) {
		var newName = this.prompter.prompt(`Переименовать "${name}" в:`, newName);
		if (newName == null || newName == name) return true;

		if (this.exists(newName)) return this.renameSession(name, newName);

		var {data} = this;
		this.splice(name, newName);
		data[newName] = data[name];
		delete data[name];
	},
	removeSession(name) {
		if (!this.prompter.confirm(`Вы уверены, что хотите удалить ${name} ?`))
			return true;
		delete this.data[name];
		this.splice(name);
	},
	deleteAllSessions() {
		if (!this.prompter.confirm(`Вы уверены, что хотите удалить все сессии?`))
			return true;
		delete this.dragData;
		delete this.bwt.getTopWindow().document.getElementById(pid).bootChanged;
		this.meta = (this.data = Object.create(null))[mp] = {order: [], boot: null};
	}
}).init()))("ucf-ssm-menupopup", "{07cae4f5-18b0-487b-80eb-973304af9528}");

Vitaliy V.
Dumby
Что необходимо изменить или добавить в этом скрипте, что бы всплывающее окно автоматически закрывалось после перемещения курсора за пределы окна ?

Dumby пишет

Там же двойной левый mousedown устанавливает или снимает загрузочную сессию.

Вот этого в предыдущем коде не понял. Желательно в подсказку добавлять справку по действиям, как в большинстве примеров:
Долгое нажатие Сохранить сессию, Колёсико или Клик + Ctrl Открыть сессию в новых вкладках, Правый клик Выделить и Открывать при запуске???


Ещё проблема - сохраняю пару сессий по две или три вкладки. При клике по имени сохранённой сессии (не boot) восстанавливается только последняя вкладка, первые пустые. (Firefox 97.0.1)


А что должно происходить, если имя сессии правым кликом выделено Красным? Оставляю одну пустую вкладку, при перезапуске браузера выделенная сессия не загружается…
Раскомментировал эту строку - тоже ничего не автозагружается: this.gs.SessionStoreInternal.getCurrentState = () => state;

О, а скажите, можно ли посредством сабжа организовать такую штуку?
В доквантумном фоксе было расширение itsalltext. Становишься в textarea, жмёшь хоткей или кликаешь иконку (всплывала в уголке textarea) — и содержимое оной сохранялось в файле на диске и открывалось в текстовом редакторе, в каком настроено. А когда в редакторе сохранялся файл, содержимое textarea обновлялось. Можно было закрыть огнелиса, закрыть текстовый редактор, потом запустить их обратно и продолжить редактировать текст.
В квантуме же расширение больше не работает. Альтернативы требуют предварительно запустить текстовый редактор вручную, а в нём запустить плагин-сервер (есть для вима, для саблайма, ещё чего-то), и только тогда textarea обменивается текстом с редактором. Схема не намного удобней, чем просто запустить редактор и копипастить. Я б даже сказал, вообще шило на мыло.
А вот через UCF можно запускать сторонние приложения и значит — можно теоретически реализовать поведение старого аддона.

Wave пишет

Становишься в textarea, жмёшь хоткей или кликаешь иконку (всплывала в уголке textarea) — и содержимое оной сохранялось в файле на диске и открывалось в текстовом редакторе

В шапке темы скрипт hookClicks сохраняет выделенный текст или всю страницу в текстовый файл – правый клик по кнопке Загрузки. К имени файла добавляется заголовок вкладки и дата, но имя можно фиксированное прописать…

Dobrov пишет
Wave пишет

Становишься в textarea, жмёшь хоткей или кликаешь иконку (всплывала в уголке textarea) — и содержимое оной сохранялось в файле на диске и открывалось в текстовом редакторе

В шапке темы скрипт hookClicks сохраняет выделенный текст или всю страницу в текстовый файл – правый клик по кнопке Загрузки. К имени файла добавляется заголовок вкладки и дата, но имя можно фиксированное прописать…

Остаётся чтобы этот скрипт а) автоматически запускал текстовый редактор с этим файлом, б) по сохранению файла «снаружи» загружал его и обновлял содержимое страницы.

Wave
С внешним редактором не знаю, а вот для ucf в [firefox] есть такая кнопка. Что она делает - смотрите там мою просьбу выше.
Для Вашего пожелания можно было бы сделать следующее:
1. По ПКМ в textarea в меню появляется строчка, например, "Открыть Notepad". Текст выделяется, копируется в буфер, в [firefox] открывается вкладка "Notepad" и текст вставляется.
2. При закрытии вкладки текст выделяется, копируется в буфер и во вкладке, из которой был открыт в  textarea вставляется.
Дело за малым - устраивает это Вас? И если устраивает найти того, кто это напишет. Кнопку сделал Dumby.
UPD: можно и без строки меню, хоткеем. Невнимательно прочитал Ваш пост.

27-02-2022 14:54:56
UPD2: Можно поставить расширение "Управление историей форм". Будет сохраняться история редактирования.

xrun1
Вы распакуйте 52 версию, какую нить портативку, да и установите расширение, что б понимать о чем речь. Расширение шикарное, тоже его очень не хватает. То что вы предлагаете не совсем то... я через буфер обмена быстрее и проще все сделаю.

_zt
Если длинный пост всегда сначала пишу в редакторе, чтобы проверить орфографию. Поэтому расширением никогда не пользовался по причине ненужности лично для меня.
У меня портабельная есть 56-я, но ставить расширение лень.;) Предложил исходя из формулировки вопроса, что можно придумать на сей день. Нет так нет.

xrun1 пишет

UPD2: Можно поставить расширение "Управление историей форм". Будет сохраняться история редактирования.

У меня аналогичное расширение стоит, Textarea Cache. Оно, конечно, спасает от случайного перехода на другую страницу, закрытия вкладки и прочих неприятностей (на самом деле нет, потому что за всё время, что оно у меня стоит, так ни разу и не пригодилось).

xrun1 пишет

Если длинный пост всегда сначала пишу в редакторе, чтобы проверить орфографию. Поэтому расширением никогда не пользовался по причине ненужности лично для меня.

Ну вот как поступаете вы. Придя куда-то, где нужно написать длинный пост, вы, вероятно, копируете в буфер обмена содержимое textarea (например, если там уже есть цитаты), запускаете редактор, вставляете из буфера обмена текст, пишете пост, копируете в буфер обмена текст, закрываете редактор, предварительно сказав «не сохранять» или введя имя файла и выбрав путь, и вставляете текст туда, куда писали.
.
Как поступал я, когда сидел на фф56-. Придя куда-то, где нужно писать длинный пост, нажимал хоткей, автоматом запускался редактор, где уже было то, что  до этого было в textarea, писал пост, жал Ctrl-S, Alt-F4, переключался в браузер (если он не на переднем плане после закрытия редактора) и жал кнопку «отправить», т.к. в textarea текст уже тот, что я редактировал. Всё. Меньше действий — отсутствует ручной запуск редактора, два копирования-вставки в буфер, вопрос про то, что текст несохранён, с каким именем вы хотите его сохранить или вообще не хотите. Удобней (по крайней мере, мне).
.
Зы. Сами файлы расширение чистило по прошествии какого-то времени.

kokoss пишет

Что необходимо изменить или добавить в этом скрипте, что бы всплывающее окно автоматически закрывалось после перемещения курсора за пределы окна ?

Да уж, мягко говоря, не слишком понятный вопрос.
Может попробуй добавить это, только удалить
затем не забудь, если Виталий что-то предложит.

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

Выделить код

Код:

(async bmrk => {
	await delayedStartupPromise;

	var popupshown = e => {
		var trg = e.target;
		if (trg.nodeName.startsWith("t")) return;

		var {curid, curbut} = autopopup;
		if (curid && trg.id == curid || curbut && (
			curbut.className == "bookmark-item" && trg.matches(bmrk) ||
			curbut.open && curbut.contains(trg.anchorNode || trg)
				&& (curbut.type != "menu" || curbut.menupopup)
		))
			trg.addEventListener("mouseleave", mouseleave),
			trg.addEventListener("popuphidden", popuphidden);
	}
	var popuphidden = function(e) {
		if (e.target == this)
			this.removeEventListener("mouseleave", mouseleave),
			this.removeEventListener("popuphidden", popuphidden);
	}
	var tid;
	var mouseleave = e => {
		tid && clearTimeout(tid);
		tid = setTimeout(check, 350, e.target);
	}
	var check = popup => {
		tid = null;
		popup.closest(":is(menupopup,panel):hover") || autopopup.curbut?.matches(":hover")
			|| (popup.nodeName.startsWith("m") ? closeMenus(popup) : popup.hidePopup());
	}
	var autopopup = ucf_custom_script_win.mouseoveropentoolbarbutton;
	var {destructor} = autopopup;
	autopopup.destructor = () => {
		destructor.call(autopopup);
		removeEventListener("popupshown", popupshown);
	}
	addEventListener("popupshown", popupshown);
})("toolbarbutton.bookmark-item :scope");


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

Вот этого в предыдущем коде не понял.

История у него есть, не вдруг появился, а из даденого,
и обсуждалось, и хотелки добавлялись.
А если выдернуть из контекста, и смотреть по коду,
то конечно трудно понять, он же довольно большой.

Желательно в подсказку добавлять

Вот и займись этим. Если туда еще добавить про двиганье мышью,
и все клавиатурные аналоги мышиных действий, будет капитально.

при перезапуске браузера выделенная сессия не загружается

И не должна, перезапуск есть перезапуск, это действие определённого характера.
А вот если закрыть Firefox, то затем, при запуске, должна загружаться.

восстанавливается только последняя вкладка, первые пустые

Прямо в реальном времени это не работает.
Нужно подождать, а не сохранять сразу после открытия вкладок.


Если очень надо, то можно попробовать так:
задать небольшое значение настройки browser.sessionstore.interval
подождать секунду, вернуть настройку, и тогда сохранять.
Вроде работает, на первый взгляд.

Выделить код

Код:

(async (pid, mp, self) => CustomizableUI.createWidget((self = { id: "797321",
	label: "Simple Session Manager", tooltiptext: "Управление сессиями", localized: false,
	init() {
		this.handleEvent = e => this[e.type](e);
		this.onTimeout = async () => await this.saveSession() || this.save();
		Services.obs.addObserver(this, "quit-application");
		var {openMenu} = this;
		this.render = function() {
			this.openMenu = openMenu;
			this.constructor.prototype.render.call(this);
		}
		delete this.init;
		return this;
	},
	image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEnQAABJ0BfDRroQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGxSURBVDiNnZE/T1NRGMZ/7zn3lmKLkIKpaUxZFINEJZ0YHXRwcnBxMQ6aOJi4+FH8Dm5OOjA4+AEYGCTRook1JlVIVUrbW/rnPg63UtReIr7bOXmf5/ye59jDlanHS3O+wn/Mu+/DF8HFeVe5f+vaPTdfPpF4UNvk6frmhwAvXOEsvrR0IoN47xM4CHCADmDQBkAHbdTrTBRZdgYLs6NTD/MiMC+gC8MO6ke8ffWsXv3S3JpksHouv7Z4/UEeM8z6IwKfEGjYRt0G1d3m1p3n0Y1JBi/vTtfKw1Yec2CHBDFmXYjboAgL4tTc5pXsmcNcDxIDUFQHuqgfYT69OAuE9qqAoX4jIcAL7b9H+0deOYYg3tkYXxx2EAo7dQYEBF/TEULD5oow7KHut18RhGVD3GwxMZ36YWn6IJd37vQCSMSNBpYQCMs43PQMAJXlwtr6k7CGfo9iznN1ebGY7Ak1NSJwYERYJgM+Q+nClVzp/OXc3wWMwdT+jAVKCMwLC2PUeoMrrEI4m94BQq0a6mxjoca/YKFgUCfeqR8jPhoHcIAHe3ST2+UFLv2T8o/5uMvrnx9Wn65p5nMEAAAAAElFTkSuQmCC",
	onCreated(btn) {
		btn.type = "menu";
		btn.phTimestamp = 0;
		btn.render = this.render;
		btn.onclick = this.click;
		// btn.setAttribute("image", "chrome://user_chrome_files/content/custom_styles/svg/download-resume.svg");
		btn.setAttribute("image", this.image);
		var popup = btn.ownerDocument.createXULElement("menupopup");
		popup.filler = this;
		popup.id = pid;
		popup.setAttribute("onpopupshowing", "event.defaultPrevented || filler.fill(event)");
		btn.prepend(popup);

		btn.addEventListener("mousedown", this);
		popup.addEventListener("command", this);
		btn.ownerGlobal.addEventListener("unload", () => {
			btn.removeEventListener("mousedown", this);
			popup.removeEventListener("command", this);
			if (popup.filler != this)
				popup.removeEventListener("dragstart", this),
				popup.removeEventListener("popuphidden", this);
		}, {once: true});
	},
	openMenu(arg) {
		var pos;
		if (this.matches(".widget-overflow-list > :scope"))
			pos = "after_start";
		else var win = this.ownerGlobal, {width, height, top, bottom, left, right} =
			this.closest("toolbar").getBoundingClientRect(), pos = width > height
				? `${win.innerHeight - bottom > top ? "after" : "before"}_start`
				: `${win.innerWidth - right > left ? "end" : "start"}_before`;
		this.firstChild.setAttribute("position", pos);
		delete this.openMenu;
		this.openMenu(arg);
	},

	mousedown(e) {
		if (e.button) return;
		var trg = e.target;
		if (trg.nodeName.startsWith("t"))
			trg.mdTimestamp = Cu.now(),
			trg.tid = e.view.setTimeout(this.onTimeout, 500),
			e.preventDefault();
	},
	boot(trg) {
		var popup = trg.parentNode;
		var old = popup.querySelector("[boot]");
		if (old != trg) old?.removeAttribute("boot");
		trg.toggleAttribute("boot");
		popup.bootChanged = true;
	},
	muTimestamp: 0,
	click(e) {
		var trg = e.target;
		if (trg.nodeName == "menu") {
			if (e.button > 1) self.boot(trg);
			else if (Cu.now() - self.muTimestamp > 50)
				e.view.closeMenus(trg.menupopup),
				self.restoreSession(trg.label, (e.button || e.ctrlKey) && e.view);
		}
		else if (trg != this || e.button) return;
		e.view.clearTimeout(this.tid);
		if (this.mdTimestamp - this.phTimestamp > 50) this.open = true;
	},
	async command(e) {
		var arg, trg = e.target, cmd = trg.value;
		if (cmd.startsWith("r"))
			arg = trg.parentNode.parentNode.label;
		await this[cmd](arg) || this.save();
	},

	dragstart(e) {
		var trg = e.target;
		if (trg.nodeName != "menu") return;

		var popup = trg.parentNode;
		this.dragData = {trg, mouse: true};
		trg.menupopup.hidePopup();

		var win = trg.ownerGlobal;
		win.setCursor("grabbing");
		var {width} = trg.getBoundingClientRect();
		trg.setAttribute("maxwidth", width);

		win.addEventListener("mouseup", this);
		popup.addEventListener("mousemove", this);
	},
	mousemove(e) {
		var trg = e.target, dtrg = this.dragData.trg;
		if (trg == dtrg || trg.nodeName != "menu") return;

		e.movementY > 0
			? trg.nextSibling != dtrg && trg.after(dtrg)
			: trg.previousSibling != dtrg && trg.before(dtrg);
	},
	mouseup(arg) {
		if (arg.constructor.isInstance?.(arg)) {
			arg.preventDefault();
			var {trg} = this.dragData;
			this.dragData.mouse = false;
			this.muTimestamp = Cu.now();
		}
		else var trg = arg;

		trg.removeAttribute("maxwidth");
		trg.ownerGlobal.setCursor("auto");
		trg.ownerGlobal.removeEventListener("mouseup", this);
		trg.parentNode.removeEventListener("mousemove", this);
	},

	popuphidden(e) {
		if (!e.target.id) return;
		e.view.removeEventListener("keydown", this, true);
		var popup = e.target;
		popup.parentNode.phTimestamp = Cu.now();

		var save;
		if (this.dragData) {
			var {trg, mouse} = this.dragData;
			mouse && this.mouseup(trg);

			delete this.dragData;
			var order = Array.from(popup.getElementsByTagName("menu"), m => m.label);
			if (order.toString() != this.meta.order.toString()) {
				var hasBoot = this.meta.boot != null;
				if (hasBoot) var bootName = this.meta.order[this.meta.boot];
				this.meta.order = order;
				if (hasBoot) this.meta.boot = this.meta.order.indexOf(bootName);
				save = true;
			}
		}
		if (popup.bootChanged) {
			delete popup.bootChanged;
			var {boot} = this.meta;
			var bootNode = e.target.querySelector("[boot]");
			var ind = bootNode && this.meta.order.indexOf(bootNode.label);
			if (ind != boot)
				this.meta.boot = ind,
				save = true;
		}
		save && this.save(e.view);
	},

	sku: `#${pid} > menu[maxwidth]`,
	skd: `#${pid} > menu:is([maxwidth],[_moz-menuactive]):not([open])`,
	keydown(e) {
		if (e.repeat && e.key == "Shift" || !e.shiftKey && e.key != " ") return;
		var func = this.keyHandlers[e.key];
		if (!func) return;

		var menu = e.view.windowRoot
			.ownerGlobal.document.querySelector(this.skd);
		if (menu)
			e.stopImmediatePropagation(),
			func.call(this, menu, e);
	},
	keyup(e) {
		if (e.key != "Shift") return;
		var win = e.view.windowRoot.ownerGlobal;
		win.removeEventListener("keyup", this, true);
		win.document.querySelector(this.skd)?.removeAttribute("maxwidth");
	},
	keyHandlers: {
		Enter(menu) {
			this.boot(menu);
		},
		ArrowDown(menu) {
			var ns = menu.nextSibling;
			if (ns) ns.after(menu), this.arrow(menu);
		},
		ArrowUp(menu) {
			var ps = menu.previousSibling;
			if (ps.nodeName == "menu") ps.before(menu), this.arrow(menu);
		},
		" "(menu, e) {
			e.preventDefault();
			menu.ownerGlobal.closeMenus(menu.parentNode);
			this.restoreSession(menu.label, e.ctrlKey && menu.ownerGlobal);
		}
	},
	arrow(menu) {
		if (menu.hasAttribute("maxwidth")) return;
		menu.setAttribute("maxwidth", menu.getBoundingClientRect().width);
		menu.ownerGlobal.addEventListener("keyup", this, true);
		this.dragData = {trg: menu};
	},

	fill(e) {
		var mxe = e.view.MozXULElement;

		var initFrag = mxe.parseXULToFragment(`
			<menuitem value="saveSession" class="menuitem-iconic" label="Сохранить сессию"/>
			<menuitem value="deleteAllSessions" class="menuitem-iconic" label="Удалить все сессии"/>
			<menuseparator/>
		`);
		this.menuFrag = mxe.parseXULToFragment(`<menu class="menu-iconic"><menupopup>
			<menuitem label="Переименовать"
				class="menuitem-iconic" value="renameSession"/>
			<menuitem label="Удалить"
				class="menuitem-iconic" value="removeSession"/>
		</menupopup></menu>`);

		this.regStyle();

		var filler = {fill: e => e.target.id
			? e.view.addEventListener("keydown", this, true)
				|| e.target.fillFlag || this.fillSessions(e.target)
			: this.dragData?.mouse && e.preventDefault()
		};

		(this.fill = e => {
			var trg = e.target;
			trg.setAttribute("context", "");
			trg.append(trg.ownerDocument.importNode(initFrag, true));
			(trg.filler = filler).fill(e);
			trg.addEventListener("dragstart", this);
			trg.addEventListener("popuphidden", this);
		})(e);
	},
	fillSessions(popup) {
		while(popup.lastChild.nodeName == "menu") popup.lastChild.remove();
		var ind = 0, {boot} = this.meta;
		for(var name of this.meta.order) {
			var df = popup.ownerDocument.importNode(this.menuFrag, true);
			df.firstChild.setAttribute("label", name);
			if (ind++ == boot) df.firstChild.toggleAttribute("boot");
			popup.append(df);
		}
		popup.fillFlag = true;
	},
	regStyle() {
		delete this.regStyle;
		var subst = "ucf-ssm-style-resurl";
		Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler).setSubstitution(
			subst, Services.io.newURI("data:text/css;charset=utf-8," + encodeURIComponent(`
			@-moz-document url-prefix(chrome://browser/content/browser.xhtml) {
				#${pid} > menu {
					list-style-image: url("${this.image}");
				}
				#${pid} > [value=saveSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKVSURBVHjajFNNTBNREJ6+7na73bLbQn8I0BKNCYWKGr0oYoiJPxeNh6oHozcOxgSVi4le1JNHQRPigRsewURNPKiJMQicSISUACelRVJ2tz/bdktpy9adBzT8xTjJ5L35ezNv5hsLHEAv7noEntguChx7EuV8oTydKRhfno8kCnt9LTuFZ7f87mAz/8rnl86GOoOBBq+LQX1S1ioLs0txWdEmsulS38Phlcy+BwZ6W0L+JsfolcjpsFDnBKNcgQ193bRUwSrwQFgr6Dkd3o9NzilqMdI/FF+sPYCZ245I4zdunw9DtQr6LxlK6bVdpdrcPAiHfGBYLDD69ltUXsmfw0oIGoPNjkHMjMHa3J9asDyRpoyEOrQR0+dq5MxR0c29Rj3Bhvn8YjeWjZmN9cpWcApSkzpkptYg8T1FdWjTfysg1Ang9YtdT+80OogTbJc7OlsD+OftzOqsBrMflqHe6QKXIEL00zLV0UpSBbM/G9DeEQy6OXKJsfPsCbdXYjb0Yu2/nmMS9LSGYGlYpXLPkxCwkq1mR98Gn8Q4XexxYhhgYKf3TPTfRDaPqklkvViaSSpaxSpw/x1v5e0UG8W10k+iW8qfF2ZiccIydFQ7ScsXKO8ep4NiYj4ai6k5+Eoev1F1Wcn8QJDgnAnHbGYRWTh800cZ77Ryzmr6eCGXzYGiZCcQ2vQ32XT5PiIMTJBI4Waw1ZtZzLu7XaSMd1s9b9paKJA+vpuKqols3y4ov7wXaPN67GPXIl1hnDOOansyVicHhGHMzHkzeDKqqPnr/UOJxX3LNNDb5BJd3KC3Uexu7wgEGxq3limRqcxH47FVWRtPreYePBpJagdu4zYhwjx1cMFus59CuVgqTmPDDlrnvwIMAGS8IFCLagHpAAAAAElFTkSuQmCC");
				}
				#${pid} [value=restoreSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAACzElEQVQ4jX2QPWwbZRjHf+/5vfPdObZjO3Ga2pVpGtxCP4QIDKgqDCDUNQwgJCS2qFI3hBCRCvXSCcTAAu0GY0Fi4kuq+IjUCpWAgCTq4IpS3Di2G8c+x/bZ57t7mSJFMfBf/3p+z+95BPvy3pufxqKmcVY3ZFFKmQtFGIReuNnrDze6lv5TqfSyx4EIgA/e+fzkZML6MDmVnD80mz1kxW3DMAxQCs/z6Hb6g3q1Xt1ttdfa7dbFN668vrkHkADJhPntufNnc0LTDi7AjFkkUknzcGF2LvCDuZVvfiwCj+/1GoAmhL3x6x2cZhsVqjFIGIZs15ts/LJOJCLt/Z0EiE3Y9eLpR1ONaoMH9zbHAGiC9FSKkwunWF1Z3RoDABhRg/zRPPmj+XHA/0QDaLectFLj6gcT+AHdTm9mzMC27e766lrWtCwy2Qz2hIUeNVDAaDCku9ujWW/ij3wsO9oaMzCiunf66TMU5gt4wyGbf1Upr5W5u15mq1JDhYqZQo6HIuqtueLw4vLXzwC89NZXeQFw/ePv7zx5buHEf6nf3qgMVipOGE0ko6EKhoQi3NnZ+c51BwsaQLOxM9N1uv86vNtz+aHSDn0pH9S2tl6r/Pn3s/VG/WYunzs/NZ2OSID0dKpRrzZS98v3kYbEsi1004BQcbtc9Q07bdYb229/dvmFL5aWVvVts9mq1Wo/e54vJIAQqGOPzQHgj3zcvos39BAIBshRqJQYjYZ/AFy79tQIeHVx+ctiIDRTA+g43Xud9q4PIHVJPBknk82QzqY5VZg2UUrEYhOX9s5aXL5RnMykb8Rj9kcC4OrSVZ1Hpt+PJ5PPz+SzR5KpyYRpGgRKMei7fHLr7lBmZ1XHcX4LgsCxLPt4JBLRm82Hr4j9DyuVSprdO/ZELGafkXpkXtPA94OK03Vv/R4/csEw5XOGbuieN6y6bv/S9XdfvPkPTgcoDlpQJpwAAAAASUVORK5CYII=");
				}
				#${pid} [value=renameSession] {
					list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPFJREFUeNrEU0sOgkAMbc1IIHoQT+KeY7hkBW5ccwLdsOICxI2X8peIGhggI21gggpCwsImM01f+tpOO0WlFIwR0efgeR5nsG0boihizPd91A5UwZDjuq5qwycwUkQYhttSr4Y4l76fDdtBEAQqz3OVZdnPQ09o2sQhrpBSQgnAen/qzLxZzljHcawxy7KAuCJNUyiKAm53SS0FBGQNlUW3lFO2iVCLYRhAXJEkCQPn65NGAswpL/4fWAebt1ZGXB3geHlw9maXsKqjS94CHNxF7xRM0/wKgI7jDBojvbnZAz3Gv/9E7NvGvmXCsev8EmAAWocA9ofpaRIAAAAASUVORK5CYII=");
				}
				#${pid} :is([value=removeSession], [value=deleteAllSessions]) {
					list-style-image: url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABt0lEQVQ4ja2RT2sTURTFz8tk0nntFAQrVLBQxIUg/tkkMoLyIEVECHZhNrrxI+hOP5Mb6eBCcEipYIjZ2LpvhFasUtBSkwzOve+6mE6YSScuxLt67753fudyLvA/a7T+qCHmiTfrfWCMd3j9diPfq2SHX+uPnzJx71gP33w3bX9avBcE2j+SkK30Di7ffJ71VebMxD1hC2GGEEfH41prpftynIm92A0t2aYQQYgBThrndz/2KwCgf9Z2LNtImFOAtc356ijcCwJdJrZEUWzPfJpMAAAStPWR/h2Ktc10CoIQRUKEabFNvNbKfndcAKSjtrVfHW5YorWTzxDKYAxLtOWM+P7yt51hIYPpsDTNTyB/EwNAtWxdMuV8GCfbgxjxIviUYSV/SQOrbVjitbx4N8alBeXcdZR+3Tl3xS8FlIkt0dbnWH3xlbPgKgUo3HEq+tX7C4EuAAbGeOmqCuLIJt69s3PeQ1epKGfapCQJO6vGmwAWf/C1Wau6td8dV123BaAAUfHw6gSwtP3ugxA/y6X9INszAOQgbwFAIC/MQb9/Kv2vF2/UByejlVVn1Xiby/X6rPd/qj/1ak71UYKuwQAAAABJRU5ErkJggg==");
				}
				#${pid} > menuseparator:last-child,
				#${pid} > menu[maxwidth] > .menu-right,
				#${pid} > [value=deleteAllSessions]:nth-last-child(2) {
					display: none;
				}
				#${pid} > menu[boot] {
					color: red;
					font-weight: bold;
				}
				#${pid} > menu[maxwidth] {
					color: blue;
					font-weight: bold;
					outline-offset: -2px;
					outline: 2px solid orangered;
				}
			}
		`.replace(/;$/gm, " !important;"))));
		var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
		sss.loadAndRegisterSheet(Services.io.newURI("resource://" + subst), sss.USER_SHEET);
	},

	get gs() {
		delete this.gs;
		return this.gs = Cu.import("resource:///modules/sessionstore/SessionStore.jsm", {});
	},
	splice(name, newName) {
		var ind = this.meta.order.indexOf(name);
		if (ind == -1) return;
		var args = [ind, 1];

		if (1 in arguments) args.push(newName);
		else {
			if (ind == this.meta.boot) this.meta.boot = null;
			else if (ind < this.meta.boot) this.meta.boot--;
		}
		this.meta.order.splice(...args);
	},

	get meta() {
		var file = Services.dirsvc.get("UChrm", Ci.nsIFile);
		file.append("simple_session_manager.json");
		this.path = file.path;
		try {
			this.data = JSON.parse(Cu.readUTF8File(file));
		} catch {
			this.pp = file.parent.path;
			this.data = Object.create(null);
		}
		var meta = this.data[mp];
		if (!meta) {
			var order = Object.keys(this.data);
			meta = this.data[mp] = {order, boot: null};
		}
		delete this.meta;
		return this.meta = meta;
	},
	async save(excWin) {
		var io = Cu.getGlobalForObject(Cu).IOUtils;
		if (this.pp)
			await io.makeDirectory(this.pp), delete this.pp;
		(this.save = excWin => {
			this.meta.order.length
				? io.writeJSON(this.path, this.data)
				: io.remove(this.path, {ignoreAbsent: true});
			for(var win of CustomizableUI.windows) {
				if (win == excWin) continue;
				var popup = win.document.getElementById(pid);
				if (popup) popup.fillFlag = false;
			}
		})(excWin);
	},

	get prompter() {
		var {prompt} = Services;
		var p = {}, args = [null, null, "UCF Simple Session Manager"];
		p.alert = prompt.alert.bind(...args);
		p.confirm = prompt.confirm.bind(...args);
		var pr = prompt.prompt.bind(...args);
		p.prompt = (msg, value) => {
			var res = {value};
			return pr(msg, res, null, {}) ? res.value : null;
		}
		delete this.prompter;
		return this.prompter = p;
	},
	observe(s, t, data) {
		Services.obs.removeObserver(this, "quit-application");
		if (data.includes("restart")) return;

		var {boot} = this.meta;
		if (boot == null) return;

		var state = this.data[this.meta.order[boot]];
		var ssi = this.gs.SessionStoreInternal;
		ssi.getCurrentState = () => state;
		Services.obs.removeObserver(ssi, "browser:purge-session-history");

		Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
	},

	get bwt() {
		delete this.bwt;
		var url = "resource:///modules/BrowserWindowTracker.jsm";
		return this.bwt = ChromeUtils.import(url).BrowserWindowTracker;
	},
	getName(state) {
		var wl = state.windows.length, tl = 0;
		for(var w of state.windows) tl += w.tabs.length;
		return `${
			this.bwt.getTopWindow().gBrowser.selectedTab.label.slice(0, 70)
		} ${wl}/${tl} [${
			new Date().toLocaleString("mn").replace(" ", "-")
		}]`;
	},
	exists(name) {
		this.meta;
		return (this.exists = name => name in this.data &&
			!this.prompter.alert("Сессия с тем же именем уже существует!"))(name);
	},
	getState() {
		return JSON.parse(this.gs.SessionStore.getBrowserState());
	},
	get spref() {
		var pref = "browser.sessionstore.interval";
		var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
		var wait = cb => timer.initWithCallback(cb, 1e3, timer.TYPE_ONE_SHOT);
		delete this.spref;
		return this.spref = async cb => {
			var val = Services.prefs.getIntPref(pref);
			Services.prefs.setIntPref(pref, 100);
			await new Promise(wait);
			Services.prefs.setIntPref(pref, val);
		}
	},
	async saveSession(name = this.getName(this.getState())) {
		var name = this.prompter.prompt("Сохранить:", name);
		if (name == null) return true;

		if (this.exists(name)) return this.saveSession(name);

		await this.spref();
		this.data[name] = this.getState();

		this.meta.order.push(name);
		//this.meta.order.unshift(name);
		//if (this.meta.boot != null) this.meta.boot++;
	},
	restoreSession(name, win) {
		var ss = this.gs.SessionStore;
		var state = JSON.stringify(this.data[name]);
		win ? ss.setWindowState(win, state) : ss.setBrowserState(state);
	},
	renameSession(name, newName = name) {
		var newName = this.prompter.prompt(`Переименовать "${name}" в:`, newName);
		if (newName == null || newName == name) return true;

		if (this.exists(newName)) return this.renameSession(name, newName);

		var {data} = this;
		this.splice(name, newName);
		data[newName] = data[name];
		delete data[name];
	},
	removeSession(name) {
		if (!this.prompter.confirm(`Вы уверены, что хотите удалить ${name} ?`))
			return true;
		delete this.data[name];
		this.splice(name);
	},
	deleteAllSessions() {
		if (!this.prompter.confirm(`Вы уверены, что хотите удалить все сессии?`))
			return true;
		delete this.dragData;
		delete this.bwt.getTopWindow().document.getElementById(pid).bootChanged;
		this.meta = (this.data = Object.create(null))[mp] = {order: [], boot: null};
	}
}).init()))("ucf-ssm-menupopup", "{07cae4f5-18b0-487b-80eb-973304af9528}");

Dumby пишет

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

Спасибо!

Dumby пишет

Вот и займись этим. Если туда еще добавить про двиганье мышью,
и все клавиатурные аналоги мышиных действий, будет капитально.

Доработал код Менеджера сессий, добавил описание: (про двиганье мышью записал только сортирровку перетаскиванием)
Сделал обновление времени browser.sessionstore.interval в подсказке и нашёл такие возможности, может что-то пропустил:

Выделить код

Код:

tooltiptext: `Менеджер сессий: Браузер каждые 0.25 мин
сохраняет сессии, это снижает ресурс SSD\n
Правый клик на Имени сессии:
	Выделить и Открывать при запуске
Колёсико или Клик + Ctrl:
	Открыть сессию в новых вкладках
Сортировка: тащите строки мышью
или курсором, удерживая Shift`,
………
		btn.addEventListener("mouseenter", this);
………
			btn.removeEventListener("mouseenter", this);
………
	mouseenter(e) { // обновить время сохранения сессии браузера
		e.view.document.getElementById(self.id).tooltipText = self.tooltiptext.replace(new RegExp(/ые .* ми/,''),'ые '+ Services.prefs.getIntPref('browser.sessionstore.interval',15e3)/60e3 + ' ми');
	},

Всем привет! Значительно переработал расширенный профиль Firefox (в шапке темы), добавил скриптов и сократил объём архива до 3,3 Мб:


Визуальное представление: 4 режима Прокси разным фоном ≡ Меню, запрет графики фоном кнопки ⤓ «Загрузки», различные сообщения статуса при наведении на кнопки, например показ пути к папке Загрузок, масштаб шрифта, переключение прокси, предупреждение о включенном HTTPS и прочее…


На любых кнопках разрешены все клики мыши ⦺, градиентный Прогресс загрузки страниц, Четыре режима чтения, Меню переключения скрытых настроек (долгое нажатие 0.5 сек на пункте меню откроет эту опцию в about:config), Управления дополнениями, расширенный Инспектор атрибутов, Добавление закладки без запроса, Перевод сайта/текста, Проиграть/скачать видео или ссылку программой из контекстного меню, Поиск похожих фото, Сохранение картинок, которые нельзя сохранить обычным способом, Жесты мыши например перетаскивание ссылки вправо копирует её в буфер, Расположение страниц в Закладках показывается в подсказке Звёздочки, Авто-коррекция имён вкладок, Яркость колёсиком ± на Замке, показ Ссылок и прочее… — читайте подсказки кнопкок при наведении мыши и встроенную Справку (долгий клик по кнопке «Печать»).

Возможности кнопок вкратце: ◧ левая кнопка мыши, ◨ правая, ⦿ средняя
1) ⤓ кнопка Загрузки добавлены клики мыши, в списке есть Пауза\Старт
    Правый клик: сохранить Страницу | Выделен. текст в единый .html
    ◨ клик + Shift Графика вкл/откл, Левый клик + Alt папка Загрузки
    Колёсико ⦿ клик мыши: Сохранить | Выделенный текст как файл .txt
    Alt+⇧+S сохранить Страницу в единый html расширением SingleFile
    единый html - файл, содержащий все данные: графику, текст, стили…

2) ≡ стандартная кнопка Меню — вне курсора составной значок
    ◧ Левый+Alt или Колёсико: Развернуть | Восстановить окно
    ⩉ Ролик вверх: Полный экран, ◧ Держать кнопку: Закрыть Firefox
    ◨ правый Свернуть, + Shift Вернуть вкладку, ◨ + Alt Диспетчер задач
    В меню ≡ Диалог сохранения "Страница / Выбранное в единый HTML"

3) Избранное + боковая панель с кнопками, Колёсико ± Масштаб
    Клик мыши: открыть слева Журнал, ◧ + Shift - zoom Текст/страница
    ◧ левый клик мыши +Alt: Заладки, ◧ держать: Вкл/Выкл Антизапрет
    ⦿ Колёсико – «Топ сайтов», ◨ правый клик Меню настроек

4) Менеджер сессий — сохранить вкладки и положение страниц в базу
    ◨ клик на имени: Выделить и Открывать эту сессию при запуске
    ⦿ колёсико или Клик + Ctrl: Открыть сессию в новых вкладках

удалите папку startupCache перед запуском, рекомендуется Firefox 90+
Установите Demo-профиль согласно структуре папок. Запуск firefox -P user

Dumby пишет

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

А можно ещё сделать что бы работал на кнопках CB
Add, и ещё кнопку для перезапуска [firefox] с удалением папки startupCache

kokoss пишет

А можно ещё сделать что бы работал на кнопках CB

Не требуется, «скрытый текст» и так работает на кнопках CB.

ещё кнопку для перезапуска [firefox] с удалением папки startupCache

Ууу, сам бы от такой не отказался.
Папка в лисьем использовании, и её удаление, на первый взгляд, не представляется возможным.

Dumby пишет

Не требуется, ... и так работает на кнопках CB.

Проверял в предыдущей версии UCF. В актуальной версии UCF у меня тоже не работает, походу не туда запихнул, пока не до конца разобрался куда добавлять...

Папка в лисьем использовании, и её удаление, на первый взгляд, не представляется возможным.

Надеюсь такая возможность со временем появится!

kokoss пишет

и ещё кнопку для перезапуска [firefox] с удалением папки startupCache

Есть перезапуск для меню или горячая клавиша Ctrl+Alt+Q. В меню гамбургера не показывает иконку. https://forum.mozilla-russia.org/viewto … 07#p785107

xrun1 пишет

В меню гамбургера не показывает иконку. https://forum.mozilla-russia.org/viewto … 07#p785107

Это не то, таких кнопок у меня несколько


Add, просто что бы заработали некоторые скрипты, приходится удалять эту папку

Dumby пишет

Папка в лисьем использовании, и её удаление, на первый взгляд, не представляется возможным.

Я сейчас проверил. Удаляется. Там ещё файл появляется .startup-incomplete, но он или само-удаляется при удалении папки, либо просто исчезает через некоторое время после запуска [firefox].

xrun1 пишет

проверил

Увы, это не то, что можно «проверить».


Services.appinfo.invalidateCachesOnRestart(), разумеется, в основном, работает.
Иначе был бы соответствующий баг, STR, и всё такое.


Дело в том, что работает это не всегда.
Иногда, в некоторых случаях, совершенно рандомно,
без какой-либо закономерности, механизм даёт сбой, и кэш не очищается.


Очень неприятный глюк, особенно при возне с кодом,
когда перезапуски идут многими десятками.

egorsemenov06
Спасибо. А то был непорядок.
Dumby
Можно удалить ведь и системной командой cmd /c rd /s /q "путь к папке\startupCache" > nul 2>&1 перед перезапуском. Не элегантное решение (окно cmd мелькает), зато результативное.
Но Вам виднее.

DEL.

Dumby
В full_theme есть скрипт setattributechromemargin.js, он отвечает за смещение области chrome относительно рамки окна ОС, в том числе с учетом Aero в Win 7.
На [firefox] 100 скрипт перестал работать правильно, как минимум для Win 7.
Нижние углы окна [firefox] 100 :
1ae053102af052fbabdcbc6ff8506d92.png 279614fa4ac6aecfafd962f9885d72fb.png
левая и верхняя кромки в порядке.
   
Вы можете поправить это? Изменение значений результата не дает.
full_theme_220116.zip  user_chrome_files_211113.zip

_zt пишет

На [firefox] 100 скрипт перестал работать правильно

Надеюсь это только
Bug 1754547 - Provide a @media query to target major platform/toolkits (Firefox 99+)
То есть, поменяли -moz-os-version на -moz-platform

Вы можете поправить это?

Тут бы хорошо что-то более персонализированное говорить.


Если не собираешься использовать скрипт на других осях
(где он проведёт проверку, увидит, что это не Win7(8), и ничего не сделает),
то и проверка не нужна, просто удали её (третья строка в setattributechromemargin.js).


Иначе, если обратная совместимость не нужна,
то просто замени -moz-os-version на -moz-platform (в двух местах).


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

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

Выделить код

Код:

(async tit => {
	if (AppConstants.platform != "win") return;
	var key = parseInt(Services.appinfo.platformVersion) >= 99 ? "platform" : "os-version";
	if (!matchMedia(`(-moz-${key}: windows-win7), (-moz-${key}: windows-win8)`).matches) return;

	Object.assign(tit, eval(`({${tit._update}})`.replace('"0,2,2,2"', "this.margin")));
	var glass = matchMedia("(-moz-windows-glass)");
	(glass.onchange = () => {
		tit.margin = glass.matches ? "0,7,7,7" : "0,0,0,0";
		tit.enabled && document.documentElement.setAttribute("chromemargin", tit.margin);
	})();
})(window.TabsInTitlebar);

Dumby
Спасибо. И второй и третий варианты на Win 7 работают.

Dumby
Вы можете этот скрипт изменить, что бы он открывал библиотеку в текущей вкладке, если вызван из библиотеки открытой во вкладке?
userChrome.js/openLibraryContextMenu.uc.js · alice0775 · GitHub
   
Что бы скрипт заработал во вкладке, надо удалить в нем строку
if (location.href == "chrome://browser/content/places/places.xhtml") return;
или поставить ! после скобки.
Автору бестолку писать.
   
Вообще, хорошо бы еще и пункт скрывать при пустой строке поиска.
А в идеале, написать новый скрипт, что бы он работал везде и переходил в папку в том документе из которого вызван: библиотека, библиотека во вкладке,
сайдбар, Sidebar Tabs.

_zt
Так есть же. Разве нет?


Или сугубо под 91?
Вроде нашёлся какой-то древний код (для custom_script.js), может подойдёт?
Если нет, то подкинь ещё подробностей.
Это я к тому, что сейчас времени нет, но если появится, то могут пригодиться.

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

Выделить код

Код:

try {({
	run(func) {
		var topics = ["quit-application-granted", "chrome-document-loaded"];
		var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
		for(var t of topics) obs.addObserver(this, t, false);
		this.observe = (subj, topic) => this[topic[0]](subj);
		this.q = () => topics.forEach(t => obs.removeObserver(this, t));

		this.run = async doc => {
			var code = `(${func})(document.getElementById("placesContext_editSeparator"));`;
			var ps = await doc.ownerGlobal.ChromeUtils
				.compileScript("data:charset=utf-8," + encodeURIComponent(code));
			(this.run = ps.executeInGlobal.bind(ps))(doc);
		}
		var re = /\/(?:places|bookmarksSidebar)\.xhtml$/;
		this.c = doc => re.test(doc.documentURI) && this.run(doc);
	}
}).run(sep => {
	var label = "\u041F\u0430\u043F\u043A\u0430 \u0437\u0430\u043A\u043B\u0430\u0434\u043A\u0438";
	var popup = sep.parentNode, listener = {
		handleEvent() {
			if (this.shouldHide) return;

			var menuitem = document.createXULElement("menuitem");
			menuitem.setAttribute("label", label);
			menuitem.setAttribute("oncommand", "creator.goParentFolder();");
			menuitem.creator = this;
			sep.before(menuitem);

			this.handleEvent = e => {
				if (e.target != popup) return;
				var sh = this.shouldHide;
				if (Boolean(menuitem.clientHeight) ^ sh) return;
				if ((menuitem.hidden = sh)) return;
				menuitem.disabled = false;
			}
		},
		get shouldHide() {
			var node = popup._view.selectedNodes.length == 1
				&& popup._view.selectedNode;
			return !(node && PlacesUtils.nodeIsBookmark(node)
				&& node.parent.type == node.RESULT_TYPE_QUERY);
		},
		get goParentFolder() {
			var tree = popup._view;
			if (tree.id.startsWith("b")) {
				delete this.library;
				var func = () => this.sidebar(tree);
			} else {
				delete this.sidebar;
				var list = document.getElementById("placesList");
				var func = () => this.library(popup._view, list);
			}
			delete this.goParentFolder;
			return this.goParentFolder = func;
		},
		sidebar(tree) {
			var {bookmarkGuid} = tree.selectedNode;
			if (tree.result.root.uri.startsWith("place:terms="))
				tree.place = tree.place;
			tree.selectItems([bookmarkGuid]);
			this.scroll(tree);
		},
		async library(tree, list) {
			var {bookmarkGuid} = tree.selectedNode;
			var {parentGuid} = await PlacesUtils.bookmarks.fetch(bookmarkGuid);

			if (PlacesUtils.getConcreteItemGuid(list.selectedNode) == parentGuid)
				list.selectItems([PlacesUtils.virtualAllBookmarksGuid]);
			else {
				var rows = list.view._rows, lastRow = rows[rows.length - 1];
				if (lastRow.bookmarkGuid == PlacesUtils.virtualAllBookmarksGuid)
					lastRow.containerOpen = true;
			}
			list.selectItems([parentGuid]);
			this.scroll(list);

			tree.selectItems([bookmarkGuid]);
			await new Promise(requestAnimationFrame);
			this.scroll(tree);
		},
		scroll(tree) {
			var pos = .4, visibleRows = tree.getPageLength();
			var ind = tree.view.selection.currentIndex;
			var first = tree.getFirstVisibleRow();
			var newFirst = ind - pos*visibleRows + 1;
			tree.scrollByLines(Math.round(newFirst - first));
		}
	};
	popup.addEventListener("popupshowing", listener);
	addEventListener("unload", () =>
		popup.removeEventListener("popupshowing", listener)
	, {once: true});
});} catch(ex) {Cu.reportError(ex);}

Dumby
Получается только для 91 нужен, им еще долго пользоваться. В 100 функция работает идеально, открывает папку там где нажат пункт.
Тот код что вы выложили только в окне работает. В окне у меня работает и тот код что по ссылке, да и вообще везде, вот только, вместо перехода на месте, из вкладки переходит в окно, а из Sidebar Tabs в нативный сайдбар.
В общем, как минимум, нужен код для Sidebar Tabs, как максимум аналог функции из [firefox]100.

_zt пишет

код что вы выложили только в окне работает

Быть этого не может.

скришотцы с 91

Выделить код

Код:

data:image/png;base64,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

Dumby
Да, работает. Спасибо. Или с кодировкой что-то не то было или очки пора купить. :) Вопрос закрыт.

Dumby
Посмотрите кнопку Save.
Что хотелось бы пофиксить:
1. Через меню кнопки сохраняет pdf и html не в папку загрузок назначенную в браузере, а в папку загрузок в системном профиле пользователя.
2. Через меню кнопки не сохраняет выбранный текст в файл txt.
3. Через контекстное меню сохраняет выбранный текст в файл txt на рабочий стол.
Хотелось бы пути сохранения перенаправить в назначенную папку, а п.2 починить.
   
У меня ссылки на мод не сохранилось. Последнюю правку делал вроде по этим постам.

js

Выделить код

Код:

// var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
// var {console} = Cu.import("resource://gre/modules/Console.jsm", {});

try {CustomizableUI.createWidget({
	id: "ucf-cbbtn-Save",
	tooltiptext: "Сохранить страницу\n/ часть / выбранное",
	localized: false,
	get initCode() {
		delete this.initCode;
		return this.initCode = Cu.readUTF8URI(Services.io.newURI(
			"chrome://user_chrome_files/content/custom_scripts/custom_js/Save_Script.jsm"
		));
	},
	cbu: {
		types: {
			128: "Bool", boolean: "Bool",
			64: "Int", number: "Int",
			32: "String", string: "String"
		},
		getPrefs(pref) {
			try {
				return Services.prefs[`get${
					this.types[Services.prefs.getPrefType(pref)]
				}Pref`](pref);
			}
			catch {return null;}
		},
		setPrefs(pref, val) {
			Services.prefs[`set${this.types[typeof val]}Pref`](pref, val);
		}
	},
	gClipboard: {
		get ch() {
			delete this.ch;
			return this.ch = Cc["@mozilla.org/widget/clipboardhelper;1"]
				.getService(Ci.nsIClipboardHelper);
		},
		write(str) {
			this.ch.copyStringToClipboard(str, Services.clipboard.kGlobalClipboard);
		}
	},
	custombuttonsUtils: {
		writeFile(path, data) {
			try {
				if (path.includes(":\\")) path = path.replace(/\//g, "\\");
				var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
				file.initWithPath(path);
				file.exists() && file.remove(false);

				var strm = Cc["@mozilla.org/network/file-output-stream;1"]
					.createInstance(Ci.nsIFileOutputStream);
				strm.init(file, 0x04 | 0x08, 420, 0);
				strm.write(data, data.length);
				strm.flush();
				strm.close();
			} catch(ex) {
				Cu.reportError("Custom Buttons: " + [path, "---", ex, ex.stack].join("\n"));
			}
		}
	},
	addDestructor(destructor, context) {
		this._destructors.push({destructor, context});
	},
	addEventListener(...args) {
		var trg = args[3];
		if (!trg) trg = args[3] = this.ownerGlobal;
		trg.addEventListener(...args);
		this._handlers.push(args);
	},

	onCreated(btn) {
		var win = btn.ownerGlobal;
		btn._handlers = new win.Array();
		btn._destructors = new win.Array();
		win.addEventListener("unload", this, {once: true});
		new win.Function(
			"self,_id,cbu,xhtmlns,addDestructor,addEventListener,gClipboard,custombuttonsUtils",
			this.initCode
		).call(
			btn, btn, this.id, this.cbu,
			"http://www.w3.org/1999/xhtml",
			this.addDestructor.bind(btn),
			this.addEventListener.bind(btn),
			this.gClipboard, this.custombuttonsUtils
		);
	},
	handleEvent(e) {
		var btn = e.target.getElementById(this.id);
		for(var args of btn._handlers)
			args.pop().removeEventListener(...args);
		delete btn._handlers;
		for(var {destructor, context} of btn._destructors)
			try {destructor.call(context, "destructor");}
			catch(ex) {Cu.reportError(ex);}
		delete btn._destructors;
	}
});} catch(ex) {Cu.reportError(ex);}

jsm

Выделить код

Код:

self.label = "Save";
self._handleClick =()=> menuPopup.openPopup(this, "after_start");
self.image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABGklEQVQ4EaWSvQ4BQRSFd/3EUouGksIDiNBLvMAqtVoaiShUHkItIRGd1iN4A0ql2l8wvruxw67FJuR87p05d85OFsP482PKeaVUjWpDDKpwgyAd2GyZprmiPkXAWFbUIvSld2EdgQ5UYApzKLl+xG2+VAuvDjZcoAldQspUI0yAzMXlS+D6e6qEtKlvAVc2o/CqI4sJTMF5Z4Qc6OU2hrw0eq01XZLrjah+ldhYgEeeAJIVbg9CyxMgp3j6kFqAIK15SP/V8ARwOI95ZqhBfRP+QCmVx9+4pv9XiGNc4JPEkxnt+wO0EbbRAVwtwyELfsl6zDpzOoBVFlLwSzIjs86c88cgscbKhgSkYQtByrG5gxPMeJlL6n+6A9I+WoE5cj6LAAAAAElFTkSuQmCC";


var folderpath="E:\Download";         // папка для сохранения иконок для ярлыков и ярлыков сайтов

// Создать меню для кнопки .............
var array = [
   { label: "Сохранить favicon сайта", func: "saveFavicon()", image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAACPVBMVEX09ff////C3L+Uq8+Vq8+Uqs+Zr9CZrtCZr9Gfu+ear8+mt9JRf8ORxl3t8vfF06+Twojs8/d9otl8o9s+aquZrs/X9KLp8Pft8fZhisf//+DBzN2hveihv+pii8hti9pgicl6oNlojs1zncNsi836/P2duebx8/eYyWqBp+Gn0IKBvlKHsm9qmaVuk8zt7/FEbauEv1Tp7/JdhL9oi9Pl8e2LwlmdsdD7/P76+/3H7ofo8+peh8eHwFaSteZ0pkp2gl7q8/Ohy5OApt2by2eZuOqbuOWaezWuvtd7nN2HvWxul9Ty9feQxV5ljcqBp+JEcLCVtOOo0nR7odx5n9suX6Z1mtBzmtSXyGPv9PewzfOzx+O6zu/s8fd9o95Xfrthi8lYhMN5oNnw9ffw9Pjw9Pf8/f6ewO/m8O9zmdE6aapsjdyUwouPxWPDzd6XteOSs9B5nNVpnpqHt7h/s6F6n9d7ntSTttGHwVh4qp+Ev1HH7ox6qk5wj+Hm8e3t9fOm0IKAtqOBpNrx+P9ljcyhs9FpkM2hv+/u8/fF0eOLu4N+vFKgzX3p9OSFqN13qExekIl4n9j7/P3x9PhxmNDm8e9Vg8Zfkozr8veq0YTX9qL//92AtamOwnHFz96Fot1diMh+pd13ntmatu+YyW/3+/+Tqs5UgcShzJNbhsdTf8GHs7bo8PaXtuqMr+Ty8/SZt+SUqs7r7Ox3ndb9/f7t8feZyXGYyWWCpNbz9PRuiteNtNDn7/V4ntjx8fGo3JqNAAAAv3RSTlP/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AEVuhDkAAAD+SURBVBhXY5jHzcUMAqxAICq9bx8D96adDFAgaGQOFOBaH7h7zoqZDTlFyptncAAFWBjyi52CXCI0unRLxcECPhsatbbzmlXMnS60hg0kkOxW0uNrq93tNaFpD1ggUm21QK532ZQdSm1hmXKdDCwdnOWVOi1RjNGMQCCrwMDMJ8NZ4LAynVGPkXFp8zpJBubYmn579wXtqhZb0iwn9a1iWLaViYmJ3891obOwYtLEvcYMGyWAAkwJdv6accEhi8LjGVr11SenpC5f61g3NcO0vjCAIc+DjZ2dnWexddWSbYa9nlkM+8BgWsxsK7FZ1VLzRaACNokmtdnyu1QMQgF7Rlh4zWWTAwAAAABJRU5ErkJggg=="},
   { label: "Копировать favicon в base64", func: "copyFaviconData()", image: "data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAI2bv/9RVpf/AAAAAAAAAAAAAAAAAAAA/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/AAAAAAAAAACIkvD/Jia6/ywpq/8AAAAAAAAAAAAAAAAAAAD/AAAA/wbwAf90qpv/Ymic/1RWqP9OUKr/W2Ch/2dumf9YYKT/Ly/B/xQP3/8MB9P/JCGb/wAAAAAAAAAAAAAAAAAAAP8G8AH/U5ea/ycr8f8VIP3/HiP4/ywo8v8sIvb/LCL2/ywi9v8KBOj/BQDe/wQAtv8tK4P/AAAAAAAAAAAAAAD/BvAB/3Sqm/9iaJz/Tim3/0UuuP9GPrT/R0ex/zk8uf8gIMz/FRDe/xEMzv8jIJz/AAAAAAAAAAAAAAAAAAAA/wbwAf8G8AH/BvAB/wAAAAAAAAAAAAAAAAAAAP8AAAD/SqOR/yImvP8sLKj/AAAAAAAAAAAAAAAAAAAAAAAAAP8G8AH/BvAB/wbwAf8AAAD/AAAA/wAAAP8AAAD/BvAB/3Sqm/9KW5r/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8G8AH/BvAB/wbwAf8AAAAAAAAAAAAAAAAAAAAABvAB/wbwAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/BvAB/wbwAf8G8AH/AAAAAAAAAAAAAAAAAAAAAAAAAAAG8AH/AAAAAAAAAP8G8AH/AAAAAAAAAAAAAAAAAAAA/wbwAf8G8AH/BvAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8G8AH/BvAB/wAAAAAAAAAAAAAAAAAAAP8G8AH/BvAB/wbwAf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/BvAB/wbwAf8AAAAAAAAA/wAAAP8G8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/AAAAAAAAAAAG8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wbwAf8G8AH/BvAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOesQQBjrEGAAaxBwACsQcABrEHDg6xBwAesQcAPrEHAD6xBw8+sQcPprEHD8axBwAGsQQABrEGAAaxB//+sQQ=="},  
   { separator: ''},
   { label: "Сохранить ярлык страницы как…", func: "saveShortcuts()", image: "data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADzqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA/5XLDv/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA/5XLDv8E/yT/lcsO//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA/5XLDv8E/yT/BP8k/wT/JP+Vyw7/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA/5XLDv8E/yT/BP8k/wT/JP8E/yT/BP8k/5XLDv/zqgD/86oA//I1///yNf//86oA//OqAP/zqgD/86oA//OqAP+Vyw7/lcsO/wT/JP8E/yT/BP8k/5XLDv+Vyw7/86oA//OqAP/yNf//8jX///OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP+Vyw7/BP8k/5XLDv/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/lcsO/wT/JP+Vyw7/86oA//OqAP/zqgD/86oA//02AP/9NgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA/5XLDv8E/yT/lcsO//OqAP/zqgD/86oA//OqAP/9NgD//TYA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP+Vyw7/BP8k/5XLDv/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/lcsO/wT/JP+Vyw7/86oA//OqAP/zqgD/86oA/wA31v8AN9b/86oA//9If///SH//86oA//OqAP/zqgD/86oA/5XLDv8E/yT/lcsO//OqAP/zqgD/86oA//OqAP8AN9b/ADfW//OqAP//SH///0h///OqAP/zqgD/86oA//OqAP+Vyw7/BP8k/5XLDv/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/lcsO/5XLDv+Vyw7/86oA//OqAP/zqgD/86oA/0CA//9AgP//86oA/07+9f9O/vX/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP9AgP//QID///OqAP9O/vX/Tv71//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/86oA//OqAP/zqgD/AACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQAArEEAAKxBAACsQQ=="},
   { separator: ''},  
   { label: "Копировать изображение / текст в base64", func: "copyFaviconbase()", image: "data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AIAQ/wCAEf8AgA//AIAR/wCAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AgBX/AIAVAAAAAAAAAAD/AIAo/wCA//8AgP//AID//wCA//8AgP//AIAoAAAAAAAAAAAAAAAAAAAAAP8AgBL/AID//wCA//8AgA3/AIAL/wCA//8AgP//AID//wCA//8AgP//AID//wCA//8AgBAAAAAAAAAAAAAAAAD/AIAR/wCA//8AgP//AIAK/wCACv8AgP//AID//wCAIf8AgAX/AIAh/wCA//8AgP//AIAQAAAAAAAAAAAAAAAA/wCACv8AgP//AID//wCAB/8AgAf/AID//wCA//8AgAUAAAAA/wCABf8AgP//AID//wCACgAAAAD/AIAQ/wCADP8AgCH/AID//wCA//8AgAf/AIAH/wCA//8AgP//AIAh/wCABf8AgCH/AID//wCA//8AgAv/AIAh/wCA//8AgP//AID//wCA//8AgP//AIAH/wCAB/8AgP//AID//wCA//8AgP//AID//wCA//8AgP//AIAg/wCA//8AgP//AID//wCA//8AgP//AID//wCAB/8AgAf/AID//wCA//8AgP//AID//wCA//8AgP//AIAh/wCAC/8AgP//AID//wCAHP8AgBz/AID//wCA//8AgAf/AIAH/wCA//8AgP//AIAh/wCACf8AgA7/AIAMAAAAAP8AgAj/AID//wCA//8AgAP/AIAD/wCA//8AgP//AIAH/wCAB/8AgP//AID//wCABQAAAAAAAAAA/wCADf8AgAr/AIAL/wCA//8AgP//AIAH/wCAB/8AgP//AID//wCAB/8AgAr/AID//wCA//8AgCH/AIAH/wCAJf8AgP//AID//wCAI/8AgP//AID//wCAB/8AgAf/AID//wCA//8AgAf/AIAL/wCA//8AgP//AID//wCA//8AgP//AID//wCA//8AgCT/AID//wCA//8AgAr/AIAK/wCA//8AgP//AIAKAAAAAP8AgCj/AID//wCA//8AgP//AID//wCA//8AgCP/AIAM/wCA//8AgP//AIAN/wCADf8AgP//AID//wCADQAAAAAAAAAA/wCAEP8AgBH/AIAP/wCAEf8AgBAAAAAAAAAAAP8AgBT/AIAVAAAAAAAAAAD/AIAV/wCAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+sQcH5rEGA8KxBAHCsQQBwrEEIQKxBAACsQQAArEEAAKxBAQCsQQwArEEAAKxBAACsQYAArEHBmaxB//+sQQ=="},
   { separator: ''},
   { label: "Сохранить страницу как PDF", func: "savePageToPDF()", image: "data:image/x-icon;base64,AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAyAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYUw4pJt3V/+Rb1D8lnFP/55zTf+VcVH/lXJS/5VyUv+VclL/lXJS/5VyUv+VclL/k3BR/J56Wv9cRzSkAAAAAJNvUKTto2L/4ppe/uehZf/Pmmr/noZv/9Scav/Wl17/1plh/9eZYf/XmWH/15lh/9eZYf/WmGD/2Jlg/suRXP9mRiuk8rWA/397dP1akKn/rqqi/3LF3f8Mntj/4dLJ///+9f/48u3/9e7n//bt5v/47+f/+O/n//jv5//rvZP/1ZJX/a9/VP9+lrD8AIvz/xOt+f8Douv/ALb6/wC28/9tmar/z8jJ//Lq5/////7//v////n9///6/P3//////+Ta0P/PjVP/pnpT/IWds/+aiXj/5efl/8Px//951/3/LMz//wCx8/8GltL/NIu3/4ycqf/l29L////+//n6+//8/v//3tXM/8+OVP+oe1P/6K57/86QWP/r6Ob////////++v/r9/z/oOb9/zDL//8Arf//AI/r/ydysv+hpqr//PTr///////d1Mz/0I5U/6h7U//osH//wo5g/+fm5P/7/f//9vf5//z7+////vv/9/r8/5Dc/P8Oqv7/AJf//wF02v9ffZ7/8Ojh/+Ha1P/NjFL/qHtT/+ewf//Ej1//7O3r///////+/v7//v7+//f5+v/6+vv////8/8Tq/f8hp/7/AJH//wB18/8/bqj/1MGu/9mXXv+leVH/569+/8iSYv+/tKn/wLew/8O5sf/P0M////////7+/v/3+fv////7/9Hv/v8hoP3/AIn//wB4/v84ZqL/w4NH/619VP/nsYD/x45c/9W5of/bv6j/0Jxt/6J/YP+spqD/2N3i//7////6+/3///76/8vr/v8Slv7/AIb+/wBz//83VH7/nW1B/+ewfv/Fjl7/7Ozr///////9+/r/9d7K/9Ghdv+jd1D/pJ6Y/+jt8f/9///////7/6fb/v8Ahv7/AYD//wRp6f95YlT/57B//8SOXv/n5uT//v7///r7/P/8/v////////XRsv/DhEv/loBu/9DX3P/9/v/////7/2O6/f8Afv//FnTU/5JtTf/nsH//xY9e/+jn5f/+/f//+fj5//n4+P/5+Pn/+/////Xbxf/Wj1D/nX1h/+Ll6P////7/3+/3/w+V//8tcbP/qHJB/+WuffzCjV7/5efn///////+/v7//////////////v7///////XRsP/TnW3/8fT3//////////z/ddD//0Nxl/+zdkH88LmH/9CRWP2+qJT/0M7N/8/Jxv/Pysf/z8rH/8/Kxv/Py8n/zsO6/7+pmP/PzMv/zsnG/9bNyP+nsK7/h4R3/b2BT/+Sb1Ck7qxw/9GSW/69hlb/wYhX/8CIV//AiFf/wIhX/8CIVv/BiVj/xI1d/8CIVv/BiFf/vYZW/9STW/7ppmv/bE80pAAAAACUd16k+sui/+7AmPzwwpr/8MKa//DCmv/wwpr/8MKa//DCmf/vwZj/8MKa//DCmv/uwJj8+cui/5N3XaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="},
   { label: "Сохранить страницу / выбор как HTML", func: "savePageToHTML()", image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACzElEQVQ4jV2STW8bZRDHf/Psrr11nZhgVKcKaSmUiArRKBRVIMgH4BKJ7xAp5MSFiA+Rc28ckBAoipC4koJQLyAaVFChIlQllDRWmjRev8Qvu14/+wwHx6ZlpNFcZn7zn79GVJW1tbXc6urqijHGqKoAPF3TgcVaq8owXJbphx99fOOnWzcHoqqsr68/t7y83BARVBXnHHraXo3a3HoUU8gFAHRTS2Bg/7C2Zx7vXPIBwjA0p3BU9Zns9i3vvVrhr6MWzlpee+EsRqAbHc3+/Pee7wPkcrkx4P+g0IMvb/5Iv5/iXEaWZQS+By578MaVy9YH8H3fE5FnANZajqMmgcAnH7yN73mICMYMd21sbLy7srKiPkA+n3/qBLhzv8p+EuOfTUjShPbODvOVWa5fnRsvKBaLAcAIMFbw+bc/EBVn8At7SPcQ60LSiVm++P0fGu0e7y++yalvHoABCILAiAj7hzU+vfcVT6zPVGmR3x4/xJoLSH6BVu4cN777nnQwQETI5XL/AYwxRkS4ff8BEk6Q2pBfq3dxUuHMmWt0kgFGStTDY/7YrTLybQwYPU2cpgzsCd1OgmSzWHeOR7VjWicxaT9lxn+HfjoYmWnGHgAiIsydf5HKnUXiThehQDG4Tr3e5KR7hN8XilnGKxfOoyrDgZECwIgIb125xIxxTGVdgjRG4pR2+zbG/sIkD7l2cYJyaYLMOZxzjBV4nueLCI1GnaWFab65t4OXt4gUuFqYJ8gGuPYBCxdLHNciys9PkaapjAHGGM8YQ7lcZv71OaYmC/xZPaJHTN4cUgpDXrr8MtOVaUqTRXzfY3t7u7O0tDR82a2trfkkSTSOY+31etrpdLTZbGoURRpFkdbrTY3qTW00mhrHse7u7t4tl8uhqg4VxHHcPDg4+ExVHeBU1YmIU1WnisucZtamLszn01ar9WRzc/PrWq2WAPwLJ7l2ULfXOAMAAAAASUVORK5CYII="},
   { label: "Сохранить выбранный текст как TXT", func: "saveSelectionToTxt()", image: "data:image/x-icon;base64,AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAyAQAAAAAAAAAAAAAAAAAAAAAAAAAAQE6AAAAZAAAAGgAAAJmAAACZgAAAGYAAABmAAAAZgAAAGYAAABmAAAAZgAAAGYAAABlAAAAaQABAmYAAQEjAAAAADlVkOdVcKHxVXKi8kdklPJLZpfyU3Cg8lJvn/JRbZ7yUW+g8lFun/JRbp/yUG6e8lVyofFVdKjzLkV45wAAAFABAQEAaIzF/3y34v9wsuL/cJe0/0lpgv9bjLP/dLLh/2+v3v9sq9v/cK3d/3Gv3v9sqtv/b7Df/4G77P9VcqT2AAAAVAMDAQBnir/+ZqfU/pDB4/7a3uD+j46N/kxYXv6To6/+1er4/tXp+P7J3/D+1ej4/svg8v6Gut/9aqzd/lhxnvAAAABSAwIBAGaIvv9pptX/ocfj//f4/P/P0tX/g4CA/1xZWf+Woqr/2uz8/9Hl+f/W5fT/2Of3/5fC4v5tq93/Vm+e8QAAAFIDAgEAaYm+/3mw2/+iyeX/9Pn8/+z0+//IzNL/d3h5/0tMTv+Mkpn/xdvs/9Hp/f/O4PL/msXk/nmz4/9XcJ/xAAAAUgMDAQBti7//k7/h/6fL5v/v9fn/4u73/9Dk9P+8wMT/YGpx/zJJWv94iJf/ztzo/9Pp/P+ZwuD+gLfk/1dwnvEAAABSAwMBAHOPwf+myub/sNHp//j7/P/6/P3/8fr///r39P+sxdP/IXWq/xJJcv+NjZD/0+Lu/6TN7f6Ft+L/WXGf8QAAAFIDAwEAd5LD/7PR6/+ZxOP/0ePx/97r9f/Y5/L/3e32/8Tc7f9gseT/CHK3/zBYdv+HiY7/lL/f/pLE7/9bcJ3xAAAAUgMDAQB4k8P/xNvx/5/F5f+kyOX/qMrn/6TH5f+kyOX/sM/q/5e51P9Mm83/GHm3/xxIav9fdYf+pMvs/1x1pfIAAABTAwMBAHmSxf/O4/T/y9/y/8Hb9P/C3PX/wNv0/7vW7/+93ff/utDm/42fsf9Gjbn/EXS2/ytSbv6Fj5r/WnGc8gAAAFICAwEBepPE/tHk9f/S5PX/vMjV/7fCzP+3w8//uMPP/7XBzP+6ytf/qa+4/3h/hv9Ghaz/JoO+/jNXdP82PVnyAAAAVgACAgCBmcb/2+r3/dHh8fyPkpX/kI6N/5yam/+dnJ3/paWk/6enpf+sr6//mpSR/3Bubf9SjbD8H4C+/QsrSvcCAAB/AAAAA3yVx//j8///3u/7/52gpf6pqKf+uLm5/rq7u/7Ly8v+ycnI/paWlf6LjY/+np2f/Xh+g/5gnL7/MX+y/xcgJ80BAgNNN1OUs6W84fDA1O73mJyj/ainpv+2trb/t7e4/8fHyP/Fxsb/kZGQ/4eGhP+vucT/j6G+/W53k/NlkbnwNoOv/AgiNb8CCBsQDRo/YwsaO3B0d33arKyp9q2trfWvr6/2u7u79ru7vPawr7H1sbCt9nJ2gOQIGD6bFCFEYB0qPE1Bf6SpEz9cggAAAAMCAQEBAQAAADIyMmlDQ0ONQUFBhUFBQYVCQkGFQkJBhUhISIRNTU2OKysrZAEAAAUBAQAAAgEAAAkEAAEDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="},
   { separator: ''},
   { label: "(Меню ПКМ) Сохранить текст в файл", value: "Save.SelectionToFile" },
   { label: "(Меню ПКМ) Открыть текст в редакторе", value: "Save.TextToEditor"},
];

var menuPopup = self.appendChild(document.createXULElement("menupopup"));
array.forEach((m,i)=> {
   if ("separator" in m) { menuPopup.appendChild(document.createXULElement("menuseparator")); return };
   var mItem = menuPopup.appendChild(document.createXULElement("menuitem"));
   mItem.setAttribute("label", m.label);
   mItem.setAttribute("class", "menuitem-iconic");
   if ("image" in m) mItem.setAttribute("image", m.image || array[i-1].image); 
   if ("value" in m) { 
       mItem.setAttribute('type', 'checkbox');
       mItem.setAttribute('checked', cbu.getPrefs(m.value) );
       mItem.onclick =()=> cbu.setPrefs(m.value, !cbu.getPrefs(m.value));
       }
   if ("func" in m) mItem.addEventListener("command", ()=> eval(m.func.toString()));
});
menuPopup.setAttribute("onclick", "event.stopPropagation()");


function aDate() {
 var t=new Date();
 var y=1900+t.getYear();
 var min=t.getMinutes(); if (min<10){min="0"+min};
 var h=t.getHours();
 var m=t.getMonth();switch(m){case 0: m="января";break;case 1: m="февраля";break;case 2: m="марта";break;case 3: m="апреля";break;case 4: m="мая";break;case 5: m="июня";break;case 6: m="июля";break;case 7: m="августа";break;case 8: m="сентября";break;case 9: m="октября";break;case 10: m="ноября";break;default: m="декабря";}
 var d=t.getDate();
 var curdate=d+" "+m+" "+y+" "+"г";
 var myfilename=curdate;
 return myfilename;
}
 

function WebScreenShotonImage(image) {
      var canvas = document.createElementNS(xhtmlns, 'canvas');
      canvas.width = image.naturalWidth;
      canvas.height = image.naturalHeight;
      var ctx = canvas.getContext('2d');
      ctx.drawImage(image, 0, 0);
      var base64 = canvas.toDataURL();
      gClipboard.write(base64);
   
      // стиль для изображение в сплывающей подсказке ....
      var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
      var uri = makeURI('data:text/css,'+ encodeURIComponent('#alertImage { height: 25px !important; width: 25px !important; }'));
      sss.loadAndRegisterSheet(uri, 0);
      
     // alertsService.showAlertNotification(base64, self.label, "Запомнил изображение как base64", false, "", (s, t)=> { 
     Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService).showAlertNotification(base64, self.label, "Изображение копировано как base64", false, "", (s, t)=> {
         if (t == 'alertfinished')
             sss.unregisterSheet(uri, 0); // удалить стиль когда подсказка закрывается
      }, "");
};


var saveToFile = function (fileContent, fileName) {
    var uc = Components.classes['@mozilla.org/intl/scriptableunicodeconverter'].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
    uc.charset = 'utf-8';
    fileContent = uc.ConvertFromUnicode(fileContent);

    var nsIFilePicker = Components.interfaces.nsIFilePicker;
    var fp = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
    fp.init(window, '', fp.modeSave);
    fp.defaultString = fileName;
    fp.appendFilters(fp.filterHTML);
    fp.appendFilters(fp.filterAll);
    fp.open(function (rv) {
  if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) {
    var stream = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream);
    stream.init(fp.file, 0x02|0x20|0x08, 0666, 0);
    stream.write(fileContent, fileContent.length);
    stream.close();
  }
});
};


function savePageToHTML() {
var vert=`javascript:(function(){var getSelWin=function(w){if(w.getSelection().toString())return w;for(var i=0,f,r;f=w.frames[i];i++){try{if(r=getSelWin(f))return r}catch(e){}}};var selWin=getSelWin(window),win=selWin||window,doc=win.document,loc=win.location;var qualifyURL=function(url,base){if(!url||/^([a-z]+:|%23)/.test(url))return url;var a=doc.createElement('a');if(base){a.href=base;a.href=a.protocol+(url.charAt(0)=='/'%3F(url.charAt(1)=='/'%3F'':'//'+a.host):'//'+a.host+a.pathname.slice(0,(url.charAt(0)!='%3F'&&a.pathname.lastIndexOf('/')+1)||a.pathname.length))+url}else{a.href=url};return a.href};var encodeImg=function(src,obj){var canvas,img,ret=src;if(/^https%3F:%5C/%5C//.test(src)){canvas=doc.createElement('canvas');if(!obj||obj.nodeName.toLowerCase()!='img'){img=doc.createElement('img');img.src=src}else{img=obj};if(img.complete)try{canvas.width=img.width;canvas.height=img.height;canvas.getContext('2d').drawImage(img,0,0);ret=canvas.toDataURL((/%5C.jpe%3Fg/i.test(src)%3F'image/jpeg':'image/png'))}catch(e){};if(img!=obj)img.src='about:blank'};return ret};var toSrc=function(obj){var strToSrc=function(str){var chr,ret='',i=0,meta={'%5Cb':'%5C%5Cb','%5Ct':'%5C%5Ct','%5Cn':'%5C%5Cn','%5Cf':'%5C%5Cf','%5Cr':'%5C%5Cr','%5Cx22':'%5C%5C%5Cx22','%5C%5C':'%5C%5C%5C%5C'};while(chr=str.charAt(i++)){ret+=meta[chr]||chr};return'%5Cx22'+ret+'%5Cx22'},arrToSrc=function(arr){var ret=[];for(var i=0;i<arr.length;i++){ret[i]=toSrc(arr[i])||'null'};return'['+ret.join(',')+']'},objToSrc=function(obj){var val,ret=[];for(var prop in obj){if(Object.prototype.hasOwnProperty.call(obj,prop)&&(val=toSrc(obj[prop])))ret.push(strToSrc(prop)+': '+val)};return'{'+ret.join(',')+'}'};switch(Object.prototype.toString.call(obj).slice(8,-1)){case'Array':return arrToSrc(obj);case'Boolean':case'Function':case'RegExp':return obj.toString();case'Date':return'new Date('+obj.getTime()+')';case'Math':return'Math';case'Number':return isFinite(obj)%3FString(obj):'null';case'Object':return objToSrc(obj);case'String':return strToSrc(obj);default:return obj%3F(obj.nodeType==1&&obj.id%3F'document.getElementById('+strToSrc(obj.id)+')':'{}'):'null'}};var ele,pEle,clone,reUrl=/(url%5C(%5Cx22%3F)(.+%3F)(%5Cx22%3F%5C))/g;if(selWin){var rng=win.getSelection().getRangeAt(0);pEle=rng.commonAncestorContainer;ele=rng.cloneContents()}else{pEle=doc.documentElement;ele=(doc.body||doc.getElementsByTagName('body')[0]).cloneNode(true)};while(pEle){if(pEle.nodeType==1){clone=pEle.cloneNode(false);clone.appendChild(ele);ele=clone};pEle=pEle.parentNode};var sel=doc.createElement('div');sel.appendChild(ele);for(var el,all=sel.getElementsByTagName('*'),i=all.length;i--;){el=all[i];if(el.style&&el.style.backgroundImage)el.style.backgroundImage=el.style.backgroundImage.replace(reUrl,function(a,b,c,d){return b+encodeImg(qualifyURL(c))+d});switch(el.nodeName.toLowerCase()){case'link':case'style':case'script':el.parentNode.removeChild(el);break;case'a':case'area':if(el.hasAttribute('href')&&el.getAttribute('href').charAt(0)!='%23')el.href=el.href;break;case'img':case'input':if(el.hasAttribute('src'))el.src=encodeImg(el.src,el);break;case'audio':case'video':case'embed':case'frame':case'iframe':if(el.hasAttribute('src'))el.src=el.src;break;case'object':if(el.hasAttribute('data'))el.data=el.data;break;case'form':if(el.hasAttribute('action'))el.action=el.action;break}};var head=ele.insertBefore(doc.createElement('head'),ele.firstChild);var meta=doc.createElement('meta');meta.httpEquiv='content-type';meta.content='text/html; charset=utf-8';head.appendChild(meta);var title=doc.getElementsByTagName('title')[0];if(title)head.appendChild(title.cloneNode(true));head.copyScript=function(){if('$'in win)return;var f=doc.createElement('iframe');f.src='about:blank';f.setAttribute('style','position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');doc.documentElement.appendChild(f);var str,script=doc.createElement('script');script.type='text/javascript';for(var name in win){if(name in f.contentWindow||!/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name))continue;try{str=toSrc(win[name]);if(!/%5C{%5Cs*%5C[native code%5C]%5Cs*%5C}/.test(str)){script.appendChild(doc.createTextNode('var '+name+' = '+str.replace(/<%5C/(script>)/ig,'<%5C%5C/$1')+';%5Cn'))}}catch(e){}};f.parentNode.removeChild(f);if(script.childNodes.length)this.nextSibling.appendChild(script)};head.copyScript();head.copyStyle=function(s){if(!s)return;var style=doc.createElement('style');style.type='text/css';if(s.media&&s.media.mediaText)style.media=s.media.mediaText;try{for(var i=0,rule;rule=s.cssRules[i];i++){if(rule.type!=3){if((!rule.selectorText||rule.selectorText.indexOf(':')!=-1)||(!sel.querySelector||sel.querySelector(rule.selectorText))){style.appendChild(doc.createTextNode(rule.cssText.replace(reUrl,function(a,b,c,d){var url=qualifyURL(c,s.href);if(rule.type==1&&rule.style&&rule.style.backgroundImage)url=encodeImg(url);return b+url+d})+'%5Cn'))}}else{this.copyStyle(rule.styleSheet)}}}catch(e){if(s.ownerNode)style=s.ownerNode.cloneNode(false)};this.appendChild(style)};var sheets=doc.styleSheets;for(var j=0;j<sheets.length;j++)head.copyStyle(sheets[j]);head.appendChild(doc.createTextNode('%5Cn'));var doctype='',dt=doc.doctype;if(dt&&dt.name){doctype+='<!DOCTYPE '+dt.name;if(dt.publicId)doctype+=' PUBLIC %5Cx22'+dt.publicId+'%5Cx22';if(dt.systemId)doctype+=' %5Cx22'+dt.systemId+'%5Cx22';doctype+='>%5Cn'};var href = 'data:text/html;charset=utf-8,' + encodeURIComponent(doctype + sel.innerHTML + '\n<!-- This document saved from ' + (loc.protocol != 'data:' ? loc.href : 'data:uri') + ' -->');var a = document.documentElement.appendChild(document.createElement("a"));a.setAttribute("href", href);var name = selWin ? win.getSelection().toString() : (title && title.text ? title.text : loc.pathname.split('/').pop());name=name.replace(/[:\\\/<>?*|"]+/g, '_').replace(/\s+/g, ' ').slice(0, 100).replace(/^\s+|\s+$/g, '');name += (function () {var d = new Date(), z=function(n){return '_' + (n < 10 ? '0' : '') + n};return z(d.getHours()) + z(d.getMinutes()) + z(d.getSeconds());})();a.setAttribute("download", name + ".html");a.click();a.remove();})();`;
gBrowser. loadURI(vert, {triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
};


function saveShortcuts() {
var file = Components.classes["@mozilla.org/file/local;1"].
           createInstance(Components.interfaces.nsIFile);
file.initWithPath(folderpath);

if( !file.exists() || !file.isDirectory() ) {   file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0x1B6);}

var savetodir=folderpath+"\\"; 
var urllink=gBrowser.currentURI.spec;
var out=getTabLabel();
var filename=savetodir+out+'.url';
var data="[InternetShortcut]\r\nURL="+urllink+"\r\n";

saveToFile(data, filename);
 // стиль для изображения во всплывающей подсказке ....
      var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
      var uri = makeURI('data:text/css,'+ encodeURIComponent('#alertImage { height: 25px !important; width: 25px !important; }'));
      sss.loadAndRegisterSheet(uri, 0);
   // подсказка
   var notific = 'Сохранил в: ' + folderpath;
   var image = gBrowser.selectedBrowser.mIconURL;
   Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService).showAlertNotification(image, filename, notific);
};

// Кодировать изображение или текстовой файл в base64 .............
function copyFaviconbase(){
var fp = window.makeFilePicker();
fp.init(window, "Открыть файл", fp.modeOpen);
fp.appendFilter("Text and images", "*.txt; *.text; *.css; *.js; *.ini; *.rdf; *.xml; *.html; *.htm; *.shtml; *.xhtml; *.jpe; *.jpg; *.jpeg;\
                                    *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw");
  fp.open(re=> { 
  if ( re != fp.returnOK ) return;
   var file = fp.file;
   var inputStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
   inputStream.init(file, 0x01, 0600, 0);
   var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
   stream.setInputStream(inputStream);
   var encoded = btoa(stream.readBytes(stream.available()));
   var contentType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService).getTypeFromFile(file);
   var dataURI = "data:" + contentType + ";charset=utf-8;base64," + encoded;
   gClipboard.write(dataURI);
   //Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService).showAlertNotification(dataURI, self.label, "Текст скопирован как  base64");
    // стиль для изображение в сплывающей подсказке ....
      var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
      var uri = makeURI('data:text/css,'+ encodeURIComponent('#alertImage { height: 25px !important; width: 25px !important; }'));
      sss.loadAndRegisterSheet(uri, 0);
      
     // alertsService.showAlertNotification(base64, self.label, "Изображение скопировано как base64", false, "", (s, t)=> { 
     Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService).showAlertNotification(dataURI, self.label, "Изображение скопировано как base64", false, "", (s, t)=> {
         if (t == 'alertfinished')
             sss.unregisterSheet(uri, 0); // удалить стиль когда подсказка закрывается
      }, "");
});
};

// Сохранить страницу как PDF файл через сервис 'pdfmyurl.com' .............
function savePageToPDF() {
      var loc = gBrowser.currentURI.spec;
   var vert = "http://pdfmyurl.com?url=" + loc;
  
   gBrowser. loadURI(vert, {
   triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()
   });
}; 

if (typeof window.saveImageURL != "function") var saveImageURL = internalSave.length == 15
	? (url, name, a3, a4, a5, a6, a7, type, a9, priv, prin) =>
		internalSave(url, null, name, a9, type, a4, a3, null, a6, null, a7, a5, null, priv, prin)
	: (url, name, a3, a4, a5, a6, a7, type, a9, priv, prin) =>
		internalSave(url, null, name, a9, type, a4, a3, null, a6, a7, a5, null, priv, prin);

// Сохранить иконку текущего сайта с диалогом сохранения .............
function saveFavicon() {
       var uri = gBrowser.currentURI;
       function getSiteName() {
                  try { var domain = uri.host.split('.') } catch(e) { return "" };
                   domain = (domain.length == 2) ? domain[0] : domain[1]
                   return domain.charAt(0).toUpperCase() + domain.slice(1).split('.')[0] + " ";  
            };
    var url = gBrowser.selectedTab.image;
    url && saveImageURL(
        url, getSiteName(), null, false, false, null, null,
        /^data:(image\/[^;,]+)/i.test(url) ? RegExp.$1.toLowerCase() : Cc["@mozilla.org/mime;1"]
            .getService(Ci.nsIMIMEService).getTypeFromURI(Services.io.newURI(url)),
        null, PrivateBrowsingUtils.isContentWindowPrivate(content || window), document.nodePrincipal
    );
};


// Копировать иконку текущего сайта в base64 .............
function copyFaviconData() {
   var img = new Image();
   img.src = gBrowser.selectedTab.image;
   WebScreenShotonImage(img);
};


// Сохранить выделенный текст или весь текст на странице как txt файл .............
function saveSelectionToTxt() {

let browserMM = gBrowser.selectedBrowser.messageManager;
        browserMM.addMessageListener('getSelection', function listener(message) {
        var sel = message.data;
       !sel && document.getElementById("cmd_selectAll").doCommand(); 
     
   // создать название файла из заголовка страницы и текущего времени и сохранить текст ....
   var fileTitle = getTabLabel() + '  ' + aDate().replace(/:/g, ".");
   saveURL("data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + ("\r\n\r\n" + sel)), 
                                fileTitle + ".txt", null, false, false, null, window.document);
   !sel && goDoCommand("cmd_selectNone"); 
 browserMM.removeMessageListener('getSelection', listener, true);
});
        browserMM.loadFrameScript('data:,sendAsyncMessage("getSelection", content.document.getSelection().toString())', false);
};


// Добавляем в контекстного меню страницы новые пункты .............
((contextMenu, el)=> {

   // в контекстного меню выделенного текста ....
   var saveItem = contextMenu.insertBefore(document.createXULElement("menuitem"), el);
   saveIt