Сложные скрипты все починили, вам же надо хотелку. Тоже самое, но с перламутровыми пуговицами.
Мне надо исправить то, что есть.
Но те, кто может исправить, это делать по каким-то своим внутренним убеждениям не хочет. Другие же предлагают альтернативу, которая по тем или иным причинам мне не подходит, или не работает.
Вот и остается, что дербанить Save, и вместо одного фала скрипта, получить два. Да, пока работает, но это извращение какое-то.
«The Truth Is Out There»
Отсутствует
Строка меню в гамбургере "Сохранить страницу | выбранное как HTML"
на замену
// в custom_script.js (async url => ChromeUtils.importESModule(url))( "chrome://user_chrome_files/content/custom_scripts/Actors/AppMenuTbbSaveHTMLChild.mjs"); // или в scriptsbackground: [ // In the background [System Principal] // { func: 'ChromeUtils.importESModule("chrome://user_chrome_files/content/custom_scripts/Actors/AppMenuTbbSaveHTMLChild.mjs");' }, var self, name = "AppMenuTbbSaveHTML"; var {io, focus, obs} = globalThis.Services; export class AppMenuTbbSaveHTMLChild extends JSWindowActorChild { receiveMessage() { return htmlAndName(this.contentWindow); } } ChromeUtils.domProcessChild.childID || ({ init(topic) { ChromeUtils.registerWindowActor(name, { allFrames: true, child: {esModuleURI: Components.stack.filename}, messageManagerGroups: ["browsers"] }); obs.addObserver(self = this, topic); obs.addObserver(function quit(s, t) { obs.removeObserver(quit, t); obs.removeObserver(self, topic); }, "quit-application-granted"); this.handleEvent = e => this[e.type](e); }, observe(win) { win.document.getElementById("appMenu-popup") .addEventListener("popupshowing", this); win.addEventListener("unload", this); }, popupshowing(e) { this.unload(e); var popup = e.target; var btn = popup.ownerDocument.createXULElement("toolbarbutton"); btn.id = "appMenu-ucf-save-html-button"; btn.className = "subviewbutton subviewbutton-iconic"; btn.setAttribute("label", "Страница | выбранное в единый HTML"); btn.setAttribute("image", "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='context-fill' fill-opacity='context-fill-opacity'><path d='M12.225.01H2.656v5.993l.955-.125L3.569.677 11 .76l-.042 4.375h4.275l.041 10.125-12.575.042-.042.708h13.281V3.737L12.225.01zm-.272 1.613 2.384 2.394h-2.384V1.623zm2.93 10.318.062 1.333-2.992.063V8.003h1.328v4l1.601-.063zM4.647 8.002h-.664v1.334h.664v4h1.329v-4h.664V8.003H4.648zm5.313 0-.664 1.074-.664-1.074H7.305v5.334h1.328V10.53l.664 1.073.664-1.073v2.807h1.328V8.003H9.961zm-7.969 2h-.664v-2H0v5.334h1.328v-2h.664v2H3.32V8.003H1.992v2z'/></svg>"); btn.saveHTML = this.saveHTML; popup.querySelector('toolbarbutton[id^="appMenu-print-button"]').before(btn); btn.addEventListener("command", (e) => {e.target.saveHTML();}); }, unload(e) { var win = e.target.ownerGlobal; win.removeEventListener("unload", this); win.document.getElementById("appMenu-popup").removeEventListener("popupshowing", this); win.document.getElementById("appMenu-popup").removeEventListener("command", (e) => {e.target.saveHTML();}); }, async saveHTML() { var win = this.ownerGlobal; var br = win.gBrowser.selectedBrowser; var bc = focus.focusedContentBrowsingContext; if (bc?.top.embedderElement != br) bc = br.browsingContext; var actor = bc?.currentWindowGlobal?.getActor(name); actor && self.save(win, ...await actor.sendQuery("")); }, async save(win, fileContent, fileName) { var fp = Cc['@mozilla.org/filepicker;1'].createInstance(Ci.nsIFilePicker); fp.init( !("inIsolatedMozBrowser" in win.browsingContext.originAttributes) ? win.browsingContext : win, "", fp.modeSave); fp.defaultString = fileName; fp.appendFilters(fp.filterHTML); fp.appendFilters(fp.filterAll); var res = await new Promise(fp.open); if (res == fp.returnOK || res == fp.returnReplace) this.write(fp.file.path, fileContent); }, write(path, html) { if (typeof IOUtils == "object") var write = IOUtils.writeUTF8 || IOUtils.writeAtomicUTF8; // Fx 85+ || 82-84 if (!write) { // Fx 79-81 var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm"); write = (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt)); } (this.write = write)(path, html); } }).init("browser-delayed-startup-finished"); var htmlAndName = async mainWin => { var resolveURL = function (url, base) { try { return io.newURI(url, null, io.newURI(base)).spec; } catch {} }; 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 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; }; var 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'; } }; 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); 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 (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); }; var sheets = doc.styleSheets; 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).replace(/^\s+|\s+$/g, ''); fileName += (function () { var d = new Date(), z = function(n){return '_' + (n < 10 ? '0' : '') + n}; return z(d.getHours()) + z(d.getMinutes()) + z(d.getSeconds()); })(); if(!/\.html?$/.test(fileName))fileName += '.html'; return [doctype + sel.innerHTML + '\n<!-- This document saved from ' + (loc.protocol != 'data:' ? loc.href : 'data:uri') + ' -->', fileName]; }
Жизнь иногда такое выкидывает, что хочется подобрать...
Отсутствует
contextviewpageinfo.js
Там, где другому пользователю "с большим скрипом, но все же помогли исправить", этот скрипт есть в двух вариантах, на панель и в контекст, работает в 139.
Отсутствует
Виталий выложил новую версию UCF
styleschrome - CssChrome stylesall - CssAllFrame stylescontent - CssContent scriptsbackground - JsBackground scriptschrome - JsChrome scriptsallchrome - JsAllChrome scriptscontent - JsContent domload - DOMContentLoaded типа JsChrome.DOMContentLoaded JsAllChrome.DOMContentLoaded ну и остальное JsChrome.load JsAllChrome.load JsContent.DOMWindowCreated JsContent.DOMContentLoaded JsContent.pageshow
Чтобы перевести рег. выражения из CustomStylesScripts.mjs в строку для записи в prefs.json (новый файл настроек) - откройте консоль, введите RegExp и в конце .source
Например, вводим:
получаем строку
prefs.json вручную править не надо, все делается через страницу настроек.
Добавлено 21-05-2025 19:09:24
И самое главное забыл. Если в начале файла стиля/скрипта добавить строки, например
/** @UCF @param {"prop":"JsAllChrome.load","ucfobj":false,"urlregxp":"^chrome:\\/\\/browser\\/content\\/(?:browser|places\\/(?:bookmarksSidebar|places))\\.xhtml"} @UCF @UCF @param {"prop":"JsContent.pageshow","ucfobj":false,"urlregxp":"^chrome:\\/\\/browser\\/content\\/(?:browser|places\\/(?:bookmarksSidebar|places))\\.xhtml"} @UCF */
то он сразу пропишется на странице настроек в нужном разделе (в данном случае в двух разделах), но что бы его включить все равно мышкой тыкнуть придется и перезагрузится с пересозданием кеша.
Отредактировано _zt (21-05-2025 19:10:39)
Отсутствует
Там, где другому пользователю
Я там устал читать. Теряю изначальную мысль обсуждения к окончанию.
На предыдущей странице получил рабочий код.
unter_officer, Farby
Спасибо.
Отредактировано xrun1 (21-05-2025 20:31:26)
Отсутствует
выложил выше полностью рабочий вариант Save
Функции, вроде, работают (те, что проверил). А вот внешний вид не комильфо.
Системная тема. Слева мой вариант сейчас и то, что Вы выложили. https://imgsh.net/i/2c45617ddf
В тёмной теме значки видны. https://imgsh.net/i/95027066e7
Внешний вид, лично меня, пугает. Может, что-то сделал не так? У меня выглядит одинаково и на чистом профиле, и со стилями.
Отсутствует
xrun1
При чем тут иконки, главное что скрипт рабочий, а иконки свои сделайте, я же сделал для себя когда меня те что были в оригинале не устроили.
Можете сравнением перенести измененные строки из моего варианта в старый и будут у вас привычные иконки. В скрипте ссылки на предыдущие версии есть.
А насчет блока с PNG - я хз почему так, может потому что скрипт на 138 не тестировался или потому что вы с лопуховатыми меню сидите. Можно из fulltheme взять menu_max138.css (в 139 menu.css). Все что после /* icons in menu --> */ можете удалить или оставить, но тогда и папка svg понадобится. У меня так выглядит: https://s1.directupload.eu/images/250521/3qgv8tge.png
ps^ все равно все ваши стили для меню в 139 отвалятся, так что можете уже сейчас начинать их выкидывать.
Добавлено 21-05-2025 22:56:02
ps2^ папка svg с пятью иконками понадобится даже если удалить блок начиная с /* icons in menu --> */ А именно: menu-right-icon.svg, checkbox-icon.svg, checkbox-icon-checked.svg, radio-icon.svg и radio-icon-checked.svg
Отредактировано _zt (21-05-2025 22:57:54)
Отсутствует
_zt
Всё нормально. Это был чистый скрипт. Кривенько вышла группа PNG, но это я забыл о стилях. Приношу извинения. Со стилями всё выровнялось, иконки заменяются (на скрине заменил 2 первые). Чек и радио buttons не потребовались.
https://imgsh.net/i/a1b6791b71
все равно все ваши стили для меню в 139 отвалятся, так что можете уже сейчас начинать их выкидывать
Чёй-то отвалятся? А хоть и отвалятся - починю, первый раз что ли?
Отсутствует
Чек и радио buttons нужны для меню панелей и др.
https://s1.directupload.eu/images/250522/jrdqbq4a.png
Ну а menu-right-icon.svg это стрелка в конце пункта menu
Отредактировано _zt (Вчера 03:02:11)
Отсутствует
fuchsfan
У меня в консоли такого нет. 138.0.3 и UCF 2025.05.02 23:38:18
_zt
Чек и радио buttons прописаны в каком-то стиле, т.е. картинки как-бы есть. Поэтому в скрипте квадратик с галочкой или без есть.
Поправили мне поиск по картинкам. В custom_script_win.js
// Добавить подменю "Поиск изображения в" в контекстном меню изображений (this.searchimagecontextmenu = { handleEvent(evt) { if (evt.target != this.contextMenu || !gContextMenu?.imageInfo?.currentSrc) return; var array = [ ['Яндекс', 'chrome://activity-stream/content/data/content/tippytop/favicons/yandex-ru.png', 'https://yandex.ru/images/search?rpt=imageview&url='], ['Bing', 'chrome://activity-stream/content/data/content/tippytop/favicons/bing-com.ico', 'https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIHMP&sbisrc=UrlPaste&q=imgurl:'], ['Tineye', 'https://tineye.com/favicon.ico', 'https://tineye.com/search?pluginver=bookmark_1.0&url='], // нерабочий ['Google', 'https://www.google.lv/favicon.ico', 'https://www.google.com/searchbyimage?&image_url='], ['Google1', 'chrome://activity-stream/content/data/content/tippytop/favicons/google-com.ico', 'https://lens.google.com/v3/upload?url='], ['Google2', 'chrome://activity-stream/content/data/content/tippytop/favicons/google-com.ico', 'https://www.google.com/searchbyimage?sbisrc=cr_1_5_2&image_url='], ]; var menu = document.createXULElement("menu"); menu.setAttribute("label", "Поиск изображения в ..."); menu.setAttribute("class", "menu-iconic"); menu.setAttribute("image", array[0][1]); // menu.setAttribute("onclick", "_searcclick(event);"); // menu._searcclick = function(e) { menu.onclick = function(e) { if (e.target != this) return; // gBrowser.selectedTab = gBrowser.addTrustedTab(this._searcharg[2] + encodeURIComponent(gContextMenu.imageInfo.currentSrc), { index: gBrowser.selectedTab._tPos + 1 } ); gBrowser.selectedTab = gBrowser.addTrustedTab(array[0][2] + encodeURIComponent(gContextMenu.imageInfo.currentSrc), { index: gBrowser.selectedTab._tPos + 1 } ); this.parentNode.hidePopup(); } // menu._searcharg = array[0]; var menuPopup = document.createXULElement("menupopup"); menu.append(menuPopup); array.forEach(m=> { var mItem = document.createXULElement("menuitem"); mItem.setAttribute("label", m[0]); mItem.setAttribute("image", m[1]); mItem.setAttribute("class", "menuitem-iconic"); // mItem.setAttribute("oncommand", "gBrowser.selectedTab = gBrowser.addTrustedTab(_searcharg[2] + encodeURIComponent(gContextMenu.imageInfo.currentSrc), { index: gBrowser.selectedTab._tPos + 1 } );"); // mItem._searcharg = m; mItem._searchimg = e => gBrowser.selectedTab = gBrowser.addTrustedTab(m[2] + encodeURIComponent(gContextMenu.imageInfo.currentSrc), { index: gBrowser.selectedTab._tPos + 1 } ); mItem.addEventListener("command", mItem._searchimg); menuPopup.append(mItem); }); var mItem = document.createXULElement("menuitem"); mItem.setAttribute("label", 'Искать во всех поисковиках'); // mItem.setAttribute("oncommand", "_searcharg.forEach(m => { gBrowser.selectedTab = gBrowser.addTrustedTab(m[2] + encodeURIComponent(gContextMenu.imageInfo.currentSrc), { index: gBrowser.selectedTab._tPos + 1 } );});"); // mItem._searcharg = array; mItem._searchimg = e => array.forEach(m => { gBrowser.selectedTab = gBrowser.addTrustedTab(m[2] + encodeURIComponent(gContextMenu.imageInfo.currentSrc), { index: gBrowser.selectedTab._tPos + 1 } );}); mItem.addEventListener("command", mItem._searchimg); menuPopup.append(mItem); this.contextMenu.querySelector("#context-copyimage-contents")?.before(menu); this.popupshowing = e => { if (e.target != this.contextMenu) return; menu.hidden = !gContextMenu?.imageInfo?.currentSrc; }; this.popuphiding = e => { if (e.target != this.contextMenu) return; menu.hidden = true; }; this.contextMenu.addEventListener("popuphiding", this); this.handleEvent = e => { this[e.type](e); }; }, init(that) { var contextMenu = this.contextMenu = document.querySelector("#contentAreaContextMenu"); if (!contextMenu) return; contextMenu.addEventListener("popupshowing", this); // that.unloadlisteners.push("searchimagecontextmenu"); setUnloadMap(Symbol("searchimagecontextmenu"), this.destructor, this); }, destructor() { this.contextMenu.removeEventListener("popupshowing", this); this.contextMenu.removeEventListener("popuphiding", this); }, }).init(this);
Отредактировано xrun1 (Вчера 22:30:42)
Отсутствует
для 138-139+ может кому надо
(async id => CustomizableUI.createWidget({ label: "Открыть страницу в другом браузере", get image() { var img = `${this.id.toLowerCase()}-img`; Services.io.getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) .setSubstitution(img, Services.io.newURI( "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path style='fill:context-fill rgb(142, 142, 152);fill-opacity:context-fill-opacity' d='M7.94.062h.085a7.42 7.42 0 0 1 3.868 1.082l-.035-.02a7.77 7.77 0 0 1 3.033 3.25l.02.043-6.44-.344a4.18 4.18 0 0 0-2.57.672l.016-.01a3.826 3.826 0 0 0-1.6 2.006l-.008.026L1.913 3.01A7.657 7.657 0 0 1 4.569.856l.046-.02A7.54 7.54 0 0 1 7.938.06h.002zM1.455 3.649l2.926 5.875a4.317 4.317 0 0 0 1.81 1.91l.022.012a3.669 3.669 0 0 0 1.81.471c.26 0 .512-.026.756-.077l-.024.004-1.997 3.995a7.788 7.788 0 0 1-5.686-4.153l-.02-.043a7.818 7.818 0 0 1-.864-3.6v-.048.003-.046c0-1.603.474-3.092 1.285-4.332l-.017.029zm13.769 1.497c.32.815.51 1.76.518 2.75v.002l.001.114c0 .93-.154 1.823-.439 2.654l.017-.057a7.898 7.898 0 0 1-1.339 2.423l.01-.013a7.845 7.845 0 0 1-2.098 1.834l-.036.02a7.46 7.46 0 0 1-4.345 1.053l.022.002 3.516-5.519a4.262 4.262 0 0 0 .716-2.582v.01a3.902 3.902 0 0 0-.929-2.464l.004.005 4.382-.232zm-7.256.178c1.447 0 2.62 1.198 2.622 2.675-.001 1.477-1.175 2.675-2.622 2.675-1.448 0-2.621-1.198-2.622-2.675 0-1.477 1.174-2.675 2.622-2.675z'/></svg>" )); delete this.image; return this.image = `resource://${img}`; }, tooltiptext: [ "С: Добавить в меню новый браузер", "\nФункции кликов мыши для меню:", "\tЛ: Открыть страницу", "\tС: Добавить разделитель", "\tП: Удалить пункт меню или разделитель", "\tCtrl+П: Изменить название пункта меню", "Перетаскиванием можно передвигать пункты меню или разделители" ].join("\n"), id, localized: false, onCreated(btn) { btn.owner = this; btn.type = "menu"; btn.setAttribute("image", this.image); btn.openPopup = btn.openMenu; btn.openMenu = this.openMenu; const popup = btn.appendChild(btn.ownerDocument.createXULElement("menupopup")); popup.setAttribute("context", ""); popup.shouldRebuild = true; popup.addEventListener("command", event => this.command(event)); popup.addEventListener("mousedown", e => { this._lastClickWasMiddle = (e.button === 1); }); popup.addEventListener("popupshowing", () => { if (popup.shouldRebuild) this.rebuild(popup); }); popup.addEventListener("dragstart", this.dragstart.bind(btn)); btn.onauxclick = this.auxclick; const { openDelay, closeDelay } = this; this.autoOpenCloseFeature(btn.ownerGlobal, btn, openDelay, closeDelay); }, file: Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile), openMenu(...args) { if (this.parentNode != this.domParent) { this.domParent = this.parentNode; this.owner.setPopupPosition(this); } this.openPopup(...args); }, setPopupPosition(node) { if (node.matches(".widget-overflow-list > :scope")) var pos = "after_start"; else { var win = node.ownerGlobal, { width, height, top, bottom, left, right } = node.closest("toolbar").getBoundingClientRect(); pos = width > height ? `${win.innerHeight - bottom > top ? "after" : "before"}_start` : `${win.innerWidth - right > left ? "end" : "start"}_before`; } node.firstChild.setAttribute("position", pos); }, autoOpenCloseFeature(win, btn, openDelay = 200, closeDelay = 350) { var _openTimer = 0; var _closeTimer = 0; btn.onmouseover = function (e) { win.clearTimeout(_closeTimer); if (e.target == btn && closeOtherMenus()) { btn.open = true; return; } _openTimer = win.setTimeout(() => btn.open = true, openDelay); }; btn.onmouseout = function (e) { win.clearTimeout(_openTimer); _closeTimer = win.setTimeout(() => btn.open = false, closeDelay); }; function closeOtherMenus() { return win.Array.prototype.some.call( btn.parentNode.getElementsByTagName("*"), function (node) { if ( node != btn && win.XULElement.isInstance(node) && "open" in node && node.open && node.getElementsByTagName("menupopup").length ) { node.open = false; return true; } return false; } ); } }, get markup() { try { var data = Cu.readUTF8URI(Services.io.newURI( `chrome://user_chrome_files/content/custom_scripts/${id}-data.txt` )).split("\n").filter(line => /\S/.test(line)); } catch { var data = []; } delete this.markup; return this.markup = this.dataToMarkup(data); }, setMarkup(popup) { this.markup = popup.innerHTML; for (var { node } of CustomizableUI.getWidget(id).instances) if (node.firstChild != popup) node.firstChild.shouldRebuild = true; this.write(Array.from(popup.children, node => node.hasAttribute("value") ? node.tooltipText + (node.value == "true" ? ">" + node.label : "") : "separator" ).join("\n")); }, dataToMarkup(data) { var markup = ""; for (var str of data) markup += str == "separator" ? "<menuseparator/>" : this.strToMenuitem(str); return markup; }, repl: [/^./, c => c.toUpperCase()], strToMenuitem(str, ind = str.lastIndexOf(">")) { var name, val, path = str; if ((val = ind != -1)) path = str.slice(0, ind), name = str.slice(ind + 1); else this.file.initWithPath(path), name = this.file.leafName.split(".") .shift().replace(...this.repl); return `<menuitem label="${name}" tooltiptext="${path}" value="${val}" class="menuitem-iconic" image="moz-icon://file://${path}"/>`; }, append(popup, xul = this.markup) { popup.append(popup.ownerGlobal.MozXULElement.parseXULToFragment(xul)); }, rebuild(popup) { popup.textContent = ""; this.append(popup); delete popup.shouldRebuild; }, auxclick(e) { var trg = e.target, popup = this.firstChild; if (trg == this && e.button == 1) return this.owner.addMenuitem(popup); else if (trg.parentNode != popup) return; if (e.button == 1) { var up = e.screenY < trg.screenY + trg.clientHeight / 2; up = up ? trg.previousSibling : !trg.nextSibling; trg[up ? "before" : "after"]( trg.ownerDocument.createXULElement("menuseparator") ); } else { if (e.ctrlKey) { if (trg.nodeName.endsWith("r")) return; var name = this.owner.prompt( "Введите другое название пункта", trg.label, trg.ownerGlobal ); if (name && name != trg.label) trg.label = name, trg.value = true; } else trg.remove(); } this.owner.changeMarkup(popup); }, prompt(msg, value, domWin) { var res = { value }; return Services.prompt.wrappedJSObject.pickPrompter({ domWin, modalType: Ci.nsIPrompt.MODAL_TYPE_WINDOW }).nsIPrompt_prompt(this.label, msg, res, null, {}) ? res.value : null; }, addMenuitem(popup) { var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); fp.appendFilters(fp.filterApps); fp.init(popup.ownerGlobal.browsingContext, "Укажите путь к программе", fp.modeOpen); fp.open(res => { if (res == fp.returnOK) this.append(popup, this.strToMenuitem(fp.file.path, -1)), this.setMarkup(popup); }); }, changeMarkup(popup) { popup.state == "open" ? popup.addEventListener("popuphidden", this, { once: true }) : this.setMarkup(popup); }, handleEvent(e) { this[e.type](e); }, popuphidden(e) { this.setMarkup(e.target); }, dragstart(e) { var trg = e.target; if (trg.parentNode.nodeName != "menupopup") return; var owner = (this && this.owner) || (this.parentNode && this.parentNode.owner); if (!owner) return; var pn = trg.flattenedTreeParentNode; owner.dragData = { trg, pn, ns: trg.nextSibling }; trg.style.cssText = "font-weight: bold; color: red;" + "outline: 2px solid red; outline-offset: -2px;" .replace(/;/g, " !important;"); var win = trg.ownerGlobal; win.setCursor("grabbing"); pn.addEventListener("mousemove", owner); win.addEventListener("mouseup", owner, { once: true }); }, mousemove(e) { var trg = e.target, dtrg = this.dragData.trg; if (trg == dtrg) return; e.movementY > 0 ? trg.nextSibling != dtrg && trg.after(dtrg) : trg.previousSibling != dtrg && trg.before(dtrg); }, mouseup(e) { e.preventDefault(); var { trg, pn, ns } = this.dragData; delete this.dragData; trg.removeAttribute("style"); trg.ownerGlobal.setCursor("auto"); pn.removeEventListener("mousemove", this); trg.nextSibling != ns && this.changeMarkup(trg.parentNode); }, command(e) { if (this._lastClickWasMiddle) { this._lastClickWasMiddle = false; return; } this.file.initWithPath(e.target.tooltipText); if (this.file.exists()) { var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); process.init(this.file); return process.run(false, [e.view.gBrowser.currentURI.spec], 1); } Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService) .showAlertNotification(this.image, this.label, "Файл не существует"); }, write(txt) { var file = Services.dirsvc.get("UChrm", Ci.nsIFile), CC = Components.Constructor; ["user_chrome_files", "custom_scripts", id + "-data.txt"].forEach(file.append); var te = new (Cu.getGlobalForObject(Cu).TextEncoder)(); var fos = CC("@mozilla.org/network/file-output-stream;1", "nsIFileOutputStream", "init") .bind(null, file, 0x02 | 0x08 | 0x20, 0o644, 0); var bos = CC("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream", "setOutputStream"); (this.write = txt => { var stream = new fos(); try { new bos(stream).writeByteArray(te.encode(txt)); } catch (ex) { Cu.reportError(ex); } finally { stream.close(); } })(txt); } }))("ucf-cbbtn-OpenPageInOtherBrowser");
(async widget => widget = CustomizableUI.createWidget({ maxtimeout: 30, // Длительность до прерывания запроса в секундах maxrequests: 50, // Максимальное количество параллельных запросов alertnotification: true, // Уведомление о завершении поиска фавиконок для закладок image: "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path style='fill:none;stroke:context-fill rgb(142, 142, 152);stroke-opacity:context-fill-opacity;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;' d='M3.6.6v14.8L8 11l4.4 4.4V.6z'/></svg>", id: "ucf-loads-favicons", label: "Восстановить фавиконки", tooltiptext: "Восстановить фавиконки закладок", defaultArea: CustomizableUI.AREA_NAVBAR, localized: false, onCreated(btn) { this.setFill(btn); btn.style.setProperty("list-style-image", this.lsi, "important"); btn._handleClick = this.favSearchStart; }, get lsi() { this.favSearchStart = this.favSearchStart.bind(this); ChromeUtils.defineESModuleGetters(this, { NetUtil: "resource://gre/modules/NetUtil.sys.mjs" }); var subst = this.id + "-img"; Services.io.getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) .setSubstitution(subst, Services.io.newURI(this.image)); delete this.lsi; return this.lsi = `url(\"${this.image = "resource://" + subst}\")`; }, setFill(btn) { this.favrunning ? btn.style.setProperty("fill", "color-mix(in srgb, currentColor 20%, #e31b5d)") : btn.style.removeProperty("fill"); }, setBtnsFill() { for (var win of CustomizableUI.windows) { var btn = widget.forWindow(win).node; btn && this.setFill(btn); } }, get showAlert() { delete this.showAlert; return this.showAlert = Cc["@mozilla.org/alerts-service;1"] .getService(Ci.nsIAlertsService).showAlertNotification.bind(null, this.image); }, favSearchStart() { if (this.favrunning) return; this.favrunning = true; this.setBtnsFill(); console.log("favSearchStart: Получаем дерево закладок..."); PlacesUtils.promiseBookmarksTree(PlacesUtils.bookmarks.rootGuid).then(root => { console.log("favSearchStart: дерево закладок получено: ", root); var urlsList = []; var convert = (node, url) => { if (node.children) node.children.map(convert); else if ((url = node.uri) && /^(?:https?|ftp|file):/.test(url)) urlsList.push(url); }; convert(root); console.log("favSearchStart: найдено URL для проверки фавиконок:", urlsList.length, urlsList); var favForPage = async (siteURL) => { let siteURI; try { siteURI = Services.io.newURI(siteURL); } catch { console.warn("\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439 URI:", siteURL); return null; } try { let faviconURI = await PlacesUtils.promiseFaviconLinkForPage(siteURI); console.log("Favicon \u043d\u0430\u0439\u0434\u0435\u043d:", siteURL, faviconURI?.spec); return faviconURI ? null : siteURI; } catch (e) { console.log("\u041e\u0448\u0438\u0431\u043aа \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u0444\u0430\u0432\u0438\u043a\u043e\u043d\u043a\u0438:", siteURL, e); return siteURI; } }; Promise.all(urlsList.map(favForPage)).then(results => { console.log("\u0412\u0441\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b favForPage:", results); this.favSearchResults(results.filter(url => url !== null)); }); }); }, favComplete(favsuccesslength, favmaxlength) { this.favrunning = false; this.setBtnsFill(); this.alertnotification && this.showAlert( "Поиск фавиконок", `Успешно обработано - ${favsuccesslength}, не удалось обработать - ${favmaxlength - favsuccesslength}` ); }, favSearchResults(results) { console.log("favSearchResults: обработка результатов", results); var favmaxlength = _favmaxlength = results.length; var favsuccesslength = 0; if (!favmaxlength) { this.favComplete(0, 0); return; } var { maxrequests } = this; var favmaxtimeout = this.maxtimeout * 1000; var setFaviconForPage = "setAndFetchFaviconForPage" in PlacesUtils.favicons ? async (siteURI, favURI) => { var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); var request = PlacesUtils.favicons.setAndFetchFaviconForPage(siteURI, favURI, false, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, { onComplete() { ++favsuccesslength; timer.cancel(); timer = request = null; }, }, Services.scriptSecurityManager.getSystemPrincipal()); if (!request) { timer = null; return; } timer.initWithCallback(() => { try { request.cancel(); } catch { } timer = request = null; }, favmaxtimeout, timer.TYPE_ONE_SHOT); } : async (siteURI, uri, type) => { var resolver = Promise.withResolvers(); if (uri.schemeIs("data")) resolver.resolve(uri); else { let { NetUtil } = this; let channel = NetUtil.newChannel({ uri, loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT | Ci.nsILoadInfo.SEC_COOKIES_INCLUDE | Ci.nsILoadInfo.SEC_ALLOW_CHROME | Ci.nsILoadInfo.SEC_DISALLOW_SCRIPT, contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON, }); NetUtil.asyncFetch(channel, async (input, status, request) => { if (!Components.isSuccessCode(status)) { resolver.reject(status); return; } try { let data = NetUtil.readInputStream(input, input.available()); let { contentType } = request.QueryInterface(Ci.nsIChannel); input.close(); let buffer = new Uint8ClampedArray(data); let blob = new Blob([buffer], { type: type || contentType }); let dataURL = await new Promise((resolve, reject) => { let reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = e => reject(e); reader.readAsDataURL(blob); }); resolver.resolve(Services.io.newURI(dataURL)); } catch (e) { resolver.reject(e); } }); } try { PlacesUtils.favicons.setFaviconForPage(siteURI, uri, await resolver.promise); ++favsuccesslength; } catch { } }; var favSearchPage = siteURI => { new Promise(resolve => { let req = new XMLHttpRequest({ mozAnon: false }); req.mozBackgroundRequest = true; req.open("GET", siteURI.spec, true); req.responseType = "document"; req.overrideMimeType("text/html"); req.timeout = favmaxtimeout; req.onload = async () => { try { let doc = req.responseXML, favURI, favType; if (doc) { let lastlink, is16, is32, isany; for (let link of doc.head.querySelectorAll("link[href][rel~='icon']")) { if (link.sizes.length === 1) { let size = link.sizes[0]; if (/any/i.test(size)) isany = link; else if (/32x32/i.test(size)) is32 = link; else if (/16x16/i.test(size)) is16 = link; } lastlink = link; } let icon = (isany || is32 || is16 || lastlink); favURI = icon?.href; favType = icon?.type; } if (!favURI) { favURI = `${req.responseURL ? Services.io.newURI(req.responseURL).prePath : siteURI.prePath}/favicon.ico`; favType = "image/x-icon"; } setFaviconForPage(siteURI, Services.io.newURI(favURI), favType); } catch { } resolve(); }; req.onabort = () => resolve(); req.onerror = req.ontimeout = () => { resolve(); req.abort(); }; req.send(null); }).then(() => { if (!(--_favmaxlength)) { this.favComplete(favsuccesslength, favmaxlength); return; } if (results.length) favSearchPage(results.shift()); }); }; results.splice(0, maxrequests).map(favSearchPage); } }))();
(async () => CustomizableUI.createWidget(({ label: "Вкладки в контейнере", get image() { var img = `${this.id.toLowerCase()}-img`; Services.io.getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) .setSubstitution(img, Services.io.newURI("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><g style='fill:context-fill rgb(142, 142, 152);fill-opacity:context-fill-opacity'><rect width='6' height='6' x='1' y='1' rx='1'/><path d='M14.75 3H13V1.25a.25.25 0 0 0-.25-.25h-1.5a.25.25 0 0 0-.25.25V3H9.25a.25.25 0 0 0-.25.25v1.5a.25.25 0 0 0 .25.25H11v1.75a.25.25 0 0 0 .25.25h1.5a.25.25 0 0 0 .25-.25V5h1.75a.25.25 0 0 0 .25-.25v-1.5a.25.25 0 0 0-.25-.25'/><rect width='6' height='6' x='1' y='9' rx='1'/><rect width='6' height='6' x='9' y='9' rx='1'/></g></svg>")); delete this.image; return this.image = `resource://${img}`; }, get defaultFavicon() { var img = `${this.id.toLowerCase()}-default-favicon-img`; Services.io.getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) .setSubstitution(img, Services.io.newURI("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path style='fill:context-fill rgb(142, 142, 152);fill-opacity:context-fill-opacity;' d='M13.384 3.408c.535.276 1.22 1.152 1.556 1.963a8 8 0 0 1 .503 3.897l-.009.077-.026.224A7.758 7.758 0 0 1 .006 8.257v-.04q.025-.545.114-1.082c.01-.074.075-.42.09-.489l.01-.051a6.6 6.6 0 0 1 1.041-2.35q.327-.465.725-.87.35-.358.758-.65a1.5 1.5 0 0 1 .26-.137c-.018.268-.04 1.553.268 1.943h.003a5.7 5.7 0 0 1 1.868-1.443 3.6 3.6 0 0 0 .021 1.896q.105.07.2.152c.107.09.226.207.454.433l.068.066.009.009a2 2 0 0 0 .213.18c.383.287.943.563 1.306.741.201.1.342.168.359.193l.004.008c-.012.193-.695.858-.933.858-2.206 0-2.564 1.335-2.564 1.335.087.997.714 1.839 1.517 2.357a4 4 0 0 0 .439.241q.114.05.228.094c.325.115.665.18 1.01.194 3.043.143 4.155-2.804 3.129-4.745v-.001a3 3 0 0 0-.731-.9 3 3 0 0 0-.571-.37l-.003-.002a2.68 2.68 0 0 1 1.87.454 3.92 3.92 0 0 0-3.396-1.983q-.116.001-.23.01l-.042.003V4.31h-.002a4 4 0 0 0-.8.14 7 7 0 0 0-.333-.314 2 2 0 0 0-.2-.152 4 4 0 0 1-.088-.383 5 5 0 0 1 1.352-.289l.05-.003c.052-.004.125-.01.205-.012C7.996 2.212 8.733.843 10.17.002l-.003.005.003-.001.002-.002h.002l.002-.002h.015a.02.02 0 0 1 .012.007 2.4 2.4 0 0 0 .206.48q.09.153.183.297c.49.774 1.023 1.379 1.543 1.968.771.874 1.512 1.715 2.036 3.02l-.001-.013a8 8 0 0 0-.786-2.353'/> </svg>")); delete this.defaultFavicon; return this.defaultFavicon = `resource://${img}`; }, id: "ucf-cbbtn-LnkCreator", localized: false, onCreated(btn) { btn.owner = this; btn.tooltipText = this.label; btn.setAttribute("image", this.image); btn.addEventListener("command", event => this.createLnk(event.target)); }, init() { this.widget.parent = this; this.widget.contextmenu.destroy = id => { CustomizableUI.destroyWidget(id); delete this.data[id.slice(8)]; this.save(); } try {this.data = JSON.parse(Cu.readUTF8URI(Services.io.newURI( `chrome://user_chrome_files/content/custom_scripts/${this.id}-data.json` )))} catch {this.data = {}; return this;} for (var [id, inf] of Object.entries(this.data)) this.createWidget(id, inf.url, inf.name); return this; }, createLnk(btn) { var id = Date.now(); var gb = btn.ownerGlobal.gBrowser; var uri = gb.currentURI; var label = gb.contentTitle.slice(0, 75); var widget = this.createWidget(id, uri, label); var { area, position } = CustomizableUI.getPlacementOfWidget(this.id); CustomizableUI.addWidgetToArea(widget.id, area, position + 1); this.data[id] = { name: label, url: uri.spec }; this.save(); }, createWidget(id, url, label) { var obj = { uri: url.spec ? url : Services.io.newURI(url), id: "ucf-lnk-" + id, label, ...this.widget }; var widget = obj.widget = CustomizableUI.createWidget(obj); // Попробовать получить favicon let faviconUrl = obj.uri.prePath + "/favicon.ico"; fetch(faviconUrl).then(res => { if (!res.ok) throw new Error("Favicon not found"); return res.blob(); }).then(blob => { let reader = new FileReader(); reader.onloadend = () => { obj.image = reader.result; for (let { node } of widget.instances || []) node.setAttribute("image", obj.image); }; reader.readAsDataURL(blob); }).catch(() => { obj.image = this.defaultFavicon; for (let { node } of widget.instances || []) node.setAttribute("image", obj.image); }); return widget; }, tip: "\nShift+ПКМ - Удалить кнопку", widget: { localized: false, onCreated(btn) { btn.uri = this.uri; btn.addTab = this.addTab; btn.oncontextmenu = this.contextmenu; btn.addEventListener("command", event => this.addTab(event.target)); btn.tooltipText = this.label + this.parent.tip; this.image && btn.setAttribute("image", this.image); }, addTab(btn) { var gb = btn.ownerGlobal.gBrowser; gb.selectedTab = gb.addTrustedTab(btn.uri.spec, { userContextId: 1 }); }, contextmenu: function checkShift(e) { if (e.shiftKey) e.preventDefault(), checkShift.destroy(e.target.id); } }, save() { var file = Services.dirsvc.get("UChrm", Ci.nsIFile); var CC = Components.Constructor; ["user_chrome_files", "custom_scripts", this.id + "-data.json"].forEach(file.append); var te = new (Cu.getGlobalForObject(Cu).TextEncoder)(); var fos = CC("@mozilla.org/network/file-output-stream;1", "nsIFileOutputStream", "init") .bind(null, file, 0x02 | 0x08 | 0x20, 0o644, 0); var bos = CC("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream", "setOutputStream"); (this.save = () => { var stream = new fos(); try { new bos(stream).writeByteArray(te.encode(JSON.stringify(this.data))); } catch (ex) { Cu.reportError(ex); } finally { stream.close(); } })(); } }).init()))();
//Кнопка дополнения....... (async () => { const id = "ucf-toggle-restartless-addons"; const label = "Дополнения"; const tooltiptext = "СКМ – открыть страницу дополнений\n" + "ПКМ – проверить обновления\n" + "В меню:\n" + "ЛКМ – включить/выключить дополнение без закрытия меню\n" + "СКМ – Удалить\n" + "ПКМ – открыть настройки дополнения (если есть)"; const show_hidden = false; const show_version = true; const { AddonManager, ExtensionParent, Services, Ci } = globalThis; const imgData = `<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'>` + `<path style='fill:none;stroke:context-fill rgb(142, 142, 152);stroke-opacity:context-fill-opacity;` + `stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;' ` + `d='M12.9 15.3H3.2c-.88 0-1.6-.6-1.6-1.4v-2.7c0-.4.33-.6.74-.6h1.72c.7 0 1.25-.64 1.25-1.2 0-.64-.55-1.15-1.25-1.15H2.34c-.41 0-.74-.32-.74-.68V5.84c0-.81.72-1.48 1.6-1.48h2.36V3.13c0-1.21.93-2.297 2.21-2.419C9.23.57 10.5 1.62 10.5 2.98v1.38h2.4c.9 0 1.5.67 1.5 1.48v8.06c0 .8-.6 1.4-1.5 1.4z'/>` + `</svg>`; const dataURI = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(imgData); Services.io .getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) .setSubstitution("ucf-button", Services.io.newURI(dataURI)); const img = "resource://ucf-button/"; function openAddonOptions(addon, win) { if (addon.optionsType === 5) { const viewID = `addons://detail/${encodeURIComponent(addon.id)}/preferences`; if (win.BrowserAddonUI?.openAddonsMgr) { win.BrowserAddonUI.openAddonsMgr(viewID); } else { win.BrowserOpenAddonsMgr(viewID); } } else if (addon.optionsURL) { win.switchToTabHavingURI(addon.optionsURL, true); } } function handleItemAction(addon, win, button, ctrlKey) { if (button === 0) { addon[addon.userDisabled ? "enable" : "disable"](); } else if (button === 1) { if (!addon.isSystem && !addon.isBuiltin) { if (Services.prompt.confirm(win, null, `Удалить ${addon.name}?`)) { addon.uninstall(); } } } else if (button === 2) { openAddonOptions(addon, win); } } // ======================================= // Блок: checkForAddonsUpdates для Firefox // ======================================= async function checkForAddonsUpdates(btn) { if (btn._cb_disabled) return; btn._cb_disabled = true; const win = Services.wm.getMostRecentWindow("navigator:browser"); const icon = new ProgressIcon(btn); const manager = new TabManager(win); const prevTab = win.gBrowser.selectedTab; const { tab, wasExisting } = manager.findOrCreateTab(); const browser = tab.linkedBrowser; const proc = new UpdateProcessor(btn, tab, browser, icon, prevTab, wasExisting); function waitForLoad(browser, callback) { if (browser.webProgress.isLoadingDocument) { browser.addEventListener("load", function onLoad() { browser.removeEventListener("load", onLoad, true); callback(); }, true); } else { callback(); } } waitForLoad(browser, () => proc.start()); } class ProgressIcon { constructor(btn) { this.btn = btn; this.icon = btn.icon; this.origIcon = this.icon.src; this.box = btn.ownerDocument.createXULElement("hbox"); this.box.toggleAttribute("busy"); this.box.toggleAttribute("fadein"); this.box.className = "tab-throbber"; const s = btn.style; const r = btn.getBoundingClientRect(); s.setProperty("min-width", `${r.width}px`, "important"); s.setProperty("min-height", `${r.height}px`, "important"); Object.defineProperty(btn, "open", { configurable: true }); this.icon.replaceWith(this.box); setTimeout(() => { this.box.replaceWith(this.icon); this.icon.src = "chrome://global/skin/icons/loading.svg"; }, 300); } loading() { setTimeout(() => { this.icon.src = this.origIcon; const s = this.btn.style; s.removeProperty("min-width"); s.removeProperty("min-height"); delete this.btn.open; }, 700); } restore() { this.icon.src = this.origIcon; delete this.btn.open; } } class TabManager { constructor(win) { this.win = win; this.gBrowser = win.gBrowser; } findOrCreateTab() { for (let tab of this.gBrowser.tabs) { if (!tab.closing && tab.linkedBrowser.currentURI.spec === "about:addons") { this.gBrowser.selectedTab = tab; return { tab, wasExisting: true }; } } const insertBefore = this.gBrowser.selectedTab; const tab = this.gBrowser.addTab("about:addons", { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal() }); const index = Array.prototype.indexOf.call(this.gBrowser.tabs, insertBefore); this.gBrowser.moveTabTo(tab, index); tab.setAttribute("hidden", "true"); return { tab, wasExisting: false }; } } class UpdateProcessor { constructor(btn, tab, browser, icon, prevTab, wasExisting) { this.btn = btn; this.tab = tab; this.browser = browser; this.icon = icon; this.prevTab = prevTab; this.wasExisting = wasExisting; this.origTip = btn.getAttribute("tooltiptext"); } start() { this.btn.setAttribute("tooltiptext", `Processing about:addons…`); this.icon.loading(); const doc = this.browser.contentDocument; const origIcon = doc.querySelector('link[rel="shortcut icon"]')?.href || this.tab.image; this.tab.setAttribute("_cb_origIcon", origIcon); this.tab.image = this.btn.getAttribute("image"); (doc.getElementById("cmd_findAllUpdates") || doc.querySelector('[action="check-for-updates"]'))?.click(); this._watch(doc); } _watch(doc) { this.timer = setInterval(async () => { const msg = doc.getElementById("updates-message"); if (!msg) return; const state = msg.getAttribute("state"); if (state !== "updating") { clearInterval(this.timer); await this._finish(doc, state); } }, 50); } async _finish(doc, state) { this.icon.restore(); this.btn.setAttribute("tooltiptext", this.origTip); this.tab.image = this.tab.getAttribute("_cb_origIcon"); this.tab.removeAttribute("_cb_origIcon"); delete this.btn._cb_disabled; const keyMap = { "none-found": "addon-updates-none-found", "installed": "addon-updates-installed" }; const msgKey = keyMap[state] || `addon-updates-${state}`; const message = doc.l10n ? await doc.l10n.formatValue(msgKey) : msgKey; Components.classes["@mozilla.org/alerts-service;1"] .getService(Ci.nsIAlertsService) .showAlertNotification( "chrome://mozapps/skin/extensions/extensionGeneric.svg", this.btn.label, message, false, "", null ); const gBrowser = this.browser.ownerGlobal.gBrowser; if (state === "none-found") { if (!this.wasExisting) { gBrowser.removeTab(this.tab, { animate: false }); if (this.prevTab && !this.prevTab.closing && gBrowser.tabs.includes(this.prevTab)) { gBrowser.selectedTab = this.prevTab; } } } else { this.tab.removeAttribute("hidden"); this.tab.setAttribute("closable", "true"); gBrowser.selectedTab = this.tab; } } } // ======================================= CustomizableUI.createWidget({ id, type: "custom", label, tooltiptext, defaultArea: CustomizableUI.AREA_NAVBAR, onBuild(doc) { const win = doc.defaultView; const btn = doc.createXULElement("toolbarbutton"); btn.setAttribute("id", id); btn.setAttribute("label", label); btn.setAttribute("type", "menu"); btn.setAttribute("tooltiptext", tooltiptext); btn.setAttribute("image", img); btn.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional toolbarbutton-iconic"); const popup = doc.createXULElement("menupopup"); popup.id = id + "-popup"; btn.appendChild(popup); popup.addEventListener("popupshown", () => { const rect = btn.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const evt = new win.MouseEvent("mousemove", { bubbles: true, cancelable: true, view: win, clientX: x, clientY: y, screenX: win.mozInnerScreenX + x, screenY: win.mozInnerScreenY + y, }); btn.dispatchEvent(evt); }); btn.addEventListener("mousedown", e => { if (e.button === 1) { e.preventDefault(); const g = win.gBrowser; let t = [...g.tabs].find(t => t.linkedBrowser.currentURI.spec === "about:addons"); if (t) { g.selectedTab = t; t.linkedBrowser.reload(); } else { g.selectedTab = g.addTab("about:addons", { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal() }); } } }); btn.addEventListener("click", async e => { if (e.button !== 0) return; popup.textContent = ""; try { const all = await AddonManager.getAddonsByTypes(["extension", "theme", "plugin", "locale"]); const visible = show_hidden ? all : all.filter(a => !a.hidden); const { extensionMap } = ExtensionParent.GlobalManager; const sortGroup = type => [ ...visible.filter(a => a.type === type && a.isActive), ...visible.filter(a => a.type === type && !a.isActive) ]; function createAddonItems(groupType, defaultIcon) { for (let addon of sortGroup(groupType)) { const item = doc.createXULElement("menuitem"); item.className = "menuitem-iconic"; const label = show_version && addon.version ? `${addon.name} (${addon.version})` : addon.name; item.setAttribute("label", label); item.setAttribute("tooltiptext", addon.description || ""); const icon = extensionMap.get(addon.id)?.iconURL || addon.iconURL || defaultIcon; item.setAttribute("image", icon); if (!addon.isActive) item.style.opacity = 0.5; item.setAttribute("type", "checkbox"); item.setAttribute("checked", addon.isActive); item.addEventListener("mousedown", async ev => { const win = doc.defaultView; ev.preventDefault(); ev.stopPropagation(); if (ev.button === 0) { item.setAttribute("closemenu", "none"); if (addon.userDisabled) { await addon.enable(); } else { await addon.disable(); } item.setAttribute("checked", addon.isActive); item.style.opacity = addon.isActive ? "1" : "0.5"; } else { item.removeAttribute("closemenu"); handleItemAction(addon, win, ev.button, ev.ctrlKey); popup.hidePopup(); } }); popup.appendChild(item); } } createAddonItems("extension", "chrome://mozapps/skin/extensions/extension.svg"); if (visible.some(a => a.type === "theme")) { popup.appendChild(doc.createXULElement("menuseparator")); createAddonItems("theme", "chrome://mozapps/skin/extensions/theme.svg"); } if (visible.some(a => a.type === "plugin")) { popup.appendChild(doc.createXULElement("menuseparator")); createAddonItems("plugin", "chrome://global/skin/icons/plugin.svg"); } if (visible.some(a => a.type === "locale")) { popup.appendChild(doc.createXULElement("menuseparator")); createAddonItems("locale", "chrome://mozapps/skin/extensions/category-languages.svg"); } const toolbar = btn.closest("toolbar"); const rect = toolbar.getBoundingClientRect(); const pos = (rect.width > rect.height) ? (win.innerHeight - rect.bottom > rect.top ? "after_start" : "before_start") : (win.innerWidth - rect.right > rect.left ? "end_before" : "start_before"); popup.setAttribute("position", pos); if (popup.state !== "open") popup.openPopup(btn); } catch (err) { console.error("Ошибка при построении меню дополнений:", err); } }); btn.addEventListener("contextmenu", e => { if (e.target !== btn) return; e.preventDefault(); checkForAddonsUpdates(btn); }); return btn; } }); })();
// Атрибут инспектор (async () => { CustomizableUI.createWidget({ id: "AttributesInspector", label: "Attributes Inspector", get image() { const img = `${this.id.toLowerCase()}-img`; Services.io.getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) .setSubstitution( img, Services.io.newURI( "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path style='fill:context-fill rgb(142, 142, 152);fill-opacity:context-fill-opacity;' d='M 3.5 0 C 2.67157 0 2 0.67157 2 1.5 L 2 5.4160156 C 2.3175 5.2772956 2.6522 5.1702094 3 5.0996094 L 3 1.5 C 3 1.22386 3.22386 1 3.5 1 L 8 1 L 8 4.5 C 8 5.32843 8.6716 6 9.5 6 L 13 6 L 13 14.5 C 13 14.7761 12.910861 15 12.634766 15 L 11.076172 15 C 11.139982 15.380805 11.123616 15.698627 11.103516 16 L 12.5 16 C 13.3284 16 14 15.3284 14 14.5 L 14 5.4140625 C 14 5.0162425 13.841847 4.6348256 13.560547 4.3535156 L 9.6464844 0.43945312 C 9.3651844 0.15815313 8.9837375 0 8.5859375 0 L 3.5 0 z M 9 1.2070312 L 12.792969 5 L 9.5 5 C 9.2239 5 9 4.77614 9 4.5 L 9 1.2070312 z M 4 6 C 1.79086 6 0 7.79086 0 10 C 0 12.2091 1.79086 14 4 14 C 4.92432 14 5.775795 13.686656 6.453125 13.160156 L 9.1445312 15.851562 C 9.3398312 16.046863 9.6562625 16.046862 9.8515625 15.851562 C 10.046862 15.656363 10.046862 15.339831 9.8515625 15.144531 L 7.1601562 12.453125 C 7.6867262 11.775725 8 10.9244 8 10 C 8 7.79086 6.20914 6 4 6 z M 4 7 C 5.65685 7 7 8.3431 7 10 C 7 11.6569 5.65685 13 4 13 C 2.34315 13 1 11.6569 1 10 C 1 8.3431 2.34315 7 4 7 z'/></svg>" ) ); delete this.image; return (this.image = `resource://${img}`); }, localized: false, onCreated(btn) { btn.setAttribute("image", this.image); btn.setAttribute("tooltiptext", this.label); btn.onmouseenter = btn.onmouseleave = this.onmouse; btn.addEventListener("command", this.handleCommand); }, get handleCommand() { delete this.handleCommand; return (this.handleCommand = async function () { this.focusedWindow?.focus(); Services.scriptloader.loadSubScript( "chrome://user_chrome_files/content/custom_scripts/custom_script/ucf-attributesInspector.js", this, "UTF-8" ); }); }, onmouse: (e) => (e.target.focusedWindow = e.type.endsWith("r") ? Services.wm.getMostRecentWindow(null) : null), }); })();
Отредактировано egorsemenov06 (Сегодня 20:21:07)
Отсутствует
У меня в консоли такого нет.
138.0.3 и UCF 2025.05.02 23:38:18
В 139, которая через 4 дня зарелизится. Запуск в консоли не привязан к способу активации, или я неправ?
Отсутствует
Который в консоли выдает ошибку Упомянутые мною, которые там, не ошибаются.
У меня в 139 версии никаких ошибок в консоли нет. Что я делаю не так?
«The Truth Is Out There»
Отсутствует
xrun1
А какой поиск нужен, этот?
Отредактировано _zt (Вчера 12:34:46)
Отсутствует
У меня в 139 версии никаких ошибок в консоли нет. Что я делаю не так?
139, 138, деактивирую все скрипты оптом (переименовал папку), все равно ошибка
CSP и то, что возле него, не влияет.
Отсутствует
fuchsfan
Вам же дали ссылку, как правильно подключать скрипты с setUnloadMap. Вы сделали так, как там написано? И повторюсь, у меня в 139 ошибок нет.
ADD.
Вместо того, чтобы доказывать, что скрипт кривой, взяли бы и исправили скрипт, а потом выложили сюда.
Мы бы сказали вам большое спасибо и начали бы пользоваться вашим вариантом скрипта.
Отредактировано unter_officer (Вчера 17:59:08)
«The Truth Is Out There»
Отсутствует
/** @UCF @param {"prop":"JsChrome.load","ucfobj":true} @UCF */ // Сохранять изображение без запроса в указанную папку, из контекстного меню (async ( id = "ucf_contextsaveimg", path = "E:\\Download", label = `Сохранить без подтверждения`, // в ${path}`, с путем папки сохранения в меню beforeSelector = "#context-sendimage", ) => ({ init() { var popup = this.popup = document.querySelector("#contentAreaContextMenu"); if (!popup) return; setUnloadMap(Symbol(id), this.destructor, this); popup.addEventListener("popupshowing", this); }, handleEvent(e) { this[e.type](e); }, get data() { delete this.data; return this.data = Object.assign(Object.create(null), { "browser.download.folderList": { type: "Int", set: 2 }, "browser.download.useDownloadDir": { type: "Bool", set: true }, "browser.download.dir": { type: "String", set: path } }); }, command(e) { var {prefs} = Services, {data} = this; for (let pref in data) { let obj = data[pref], meth = `et${obj.type}Pref`; obj.val = prefs.prefHasUserValue(pref) ? prefs["g" + meth](pref) : null; prefs["s" + meth](pref, obj.set); } try { let {window: win, browser, mediaURL, principal, contentData: {referrerInfo, cookieJarSettings, contentDisposition, contentType}} = gContextMenu; win.urlSecurityCheck(mediaURL, principal); win.internalSave( mediaURL, null, // originalURL null, // document null, // file name; we'll take it from the URL contentDisposition, contentType, false, // do not bypass the cache "SaveImageTitle", null, // chosen data referrerInfo, cookieJarSettings, null, // initiating doc true, // skip prompt for where to save null, // cache key PrivateBrowsingUtils.isBrowserPrivate(browser), principal ); } catch {} for (let pref in data) data[pref].val === null ? prefs.clearUserPref(pref) : prefs[`set${data[pref].type}Pref`](pref, data[pref].val); }, popupshowing(e) { if (!gContextMenu.onImage || gContextMenu.webExtBrowserType === "popup") return; this.popupshowing = this.showing; var mitem = this.mitem = document.createXULElement("menuitem"); mitem.id = id; mitem.setAttribute("label", label); mitem.addEventListener("command", this); (this.popup.querySelector(beforeSelector) || this.popup.lastElementChild).after(mitem); this.popup.addEventListener("popuphiding", this); }, showing({target}) { if (target != this.popup || !gContextMenu.onImage || gContextMenu.webExtBrowserType === "popup") return; this.mitem.hidden = false; }, popuphiding({target}) { if (target != this.popup) return; this.mitem.hidden = true; }, destructor() { this.popup.removeEventListener("popupshowing", this); this.popup.removeEventListener("popuphiding", this); }, }).init())();
Отсутствует
/** @UCF @param {"prop":"JsChrome.load","ucfobj":false} @UCF */ // Пункт контекстного меню "Информация о странице" ..... // location == "chrome://browser/content/browser.xhtml" && (async (id, id2, img) => { var menuitem = document.createXULElement("menuitem"); document.getElementById(id2).after(menuitem); var hidden = (d = nsContextMenu.contentData, {context: c, browser: b} = d) => c.onImage || c.onLink || b.className.startsWith("webext"); menuitem.render = () => { if (menuitem.hidden = hidden()) return; menuitem.id = id; menuitem.label = "Информация о странице"; menuitem.className = "menuitem-iconic"; menuitem.onclick = e => { var win = event.target.ownerDocument.defaultView; win.BrowserCommands.pageInfo(null, "generalTab"); } delete menuitem.render; menuitem.render(); menuitem.firstChild.src = img; menuitem.render = () => menuitem.hidden = hidden(); } })( "context-viewpageinfo", "context-viewsource", "data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAFo9M/3AAAACXBIWXMAAAAAAAAAAAHqZRakAAACIUlEQVQ4y4WSsU9TURTGf7d9TiqDg0kHRRdCAjG+IhEVI0waIwuGpMHNGGyUaHApC2GAKDiITkJijJNBTRgcFKIBS0MTwNZ/QAguzgrYvr533z0Or320JdGznJtzvvvd893vWCICgAVg27ZYYSWd3ZDW041UKmLbNl29qQD7IbMJwIP710NEGNbE9LwMJ68wNDbL1EhCWU7RYXHlO+c7zgSIX793cIzCq1wRESZnFiR1+3LIMzQ2S3tzA/19V5UF4BSL5L5t4WrhzkAvI0/m6O44vifBcVwKJY3rBwye1rimSuPOn23efUwD0NkzSGY5zZf0Es/HB/brqA+rnGtQo1PvSSbaicViyqpu2LbN6nqO7d0Cj1+kmRpJhAx8zW8B8Cn7gwudZ1leXKx5gl3HLyvwMSaCQWoBERUUjBA0xdQx6AgAWhRawK/+h3ujLwHhYs8gy+klBCEaje55ATA5syAAnqcBcCvZDXLJ9bjW1US89SSvj/xUd6WldgSAakNFhHg8DsDqeo7Uo7ccO9HM01efaXoTEWhRNQSOU6pxQClFPp/HL+sZT/Xhej43+y/xbHqOG3VOhb9SkSQCbW3BBNm1HAJEolEONxzC0/4+q0OtK6sbCKDN3jQLmc1g0yKK7nONSNVmhASFohNI0cHimKrt8YwEhKLQRhDzD4JTdmPYnM+sIWU5RkAbYdc1+GL2E8xM3FIARw8e4H8x/TAZnv8CSRYQGWupptQAAAAASUVORK5CYII=" );
«The Truth Is Out There»
Отсутствует
unter_officer
Работает в 138.0.3, уже говорил выше об этом.
И кто может объяснить разницу, между "ucfobj":true и "ucfobj":false? Например, скрипт выше работает и так и сяк. У меня есть ещё похожие скрипты. В том числе такие, где поменял
//that.unloadlisteners.push("xxxxxxxxxxxxxx");
на
setUnloadMap(Symbol("xxxxxxxxxxxxxx"), this.destructor, this);
а false на true забыл. Скрипты работают. Так для чего этот параметр?
Отсутствует
И кто может объяснить разницу, между "ucfobj":true и "ucfobj":false?
В исходниках UCF написано:
* @param {Boolean} ucfobj: (optional) * if true, load the script into a specially created object, not for scripts in the background [System Principal].
На форуме
Работает в 138.0.3, уже говорил выше об этом.
Вы какой вариант скрипта имеете ввиду?
Если крайний, без setUnloadMap, то вроде не должен в 138 версии работать.
Однако, я не проверял. У меня 138 нет, а скачивать ради проверки одного скрипта лень. Но, если работает, то хорошо.
«The Truth Is Out There»
Отсутствует