Dumby спасибо!
ещё проблемку со скриптом ClickPicSave.jsm не смог исправить…
Сохраняет только в папку Загрузки по-умолчанию, хотя в настройках выбран другой путь, который запоминается в data["browser.download.dir"].set
// by Dumby сохранить картинку колёсиком или перетащив вправо; DBL поиск похожих export {MouseImgSaverChild, MouseImgSaverParent}; // forum.mozilla-russia.org/viewtopic.php?pid=800469#p800469 var u = {get it() { delete this.it; return this.it = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools); }}; ["E10SUtils", "PrivateBrowsingUtils"].forEach(name => Object.defineProperty(u, name, { configurable: true, enumerable: true, get() { var url = `resource://gre/modules/${name}.`; try {var exp = ChromeUtils.importESModule(url + "sys.mjs");} catch {exp = ChromeUtils.import(url + "jsm");} delete this[name]; return this[name] = exp[name]; } })); class MouseImgSaverChild extends JSWindowActorChild { handleEvent(e) { //клики мыши if (e.button > 1) return; //ЛКМ+СКМ var trg = e.explicitOriginalTarget; // dragstart trg.nodeType == Node.ELEMENT_NODE && trg instanceof Ci.nsIImageLoadingContent && this[e.type](trg, e); } handleDragEvent(e) { this[e.type](e); } dragstart(trg, e) { this.trg = trg; this.x = e.screenX; this.y = e.screenY; this.drag("add"); this.handleEvent = this.handleDragEvent; this.checkTextLinkyTool(trg.ownerDocument); } events = ["dragover", "drop", "dragend"]; drag(meth = (delete this.handleEvent, delete this.trg, "remove")) { meth += "EventListener"; var win = this.contentWindow; for(var type of this.events) win[meth](type, this, true); } drop() { this.drag(); } dragover(e) { var {x, y} = this, cx = e.screenX, cy = e.screenY, dx = cx - x, ax = Math.abs(dx), ay = Math.abs(cy - y); if (ax < 10 && ay < 10) return; if (dx < 0 || ax < ay) return this.drag(); this.x = cx; this.y = cy; } dragend(e) { // перетаскивание рисунка var dt = e.dataTransfer, {trg} = this; this.drag(); dt.mozUserCancelled || this.send(trg, e.screenX); // сохранить // dt.mozUserCancelled || this.sendAsyncMessage("dragend", (trg.currentRequestFinalURI || uri).spec); } auxclick(trg) { //СКМ trg.matches(":any-link :scope") || this.send(trg); } dblclick(trg) { // ЛКМ trg.matches(":any-link :scope") || this.sendAsyncMessage("dblclick", (trg.currentRequestFinalURI || uri).spec); } send(trg, sx) { var uri = trg.currentURI; if (!uri) return; var doc = trg.ownerDocument; var cookieJarSettings = u.E10SUtils .serializeCookieJarSettings(doc.cookieJarSettings); var referrerInfo = Cc["@mozilla.org/referrer-info;1"] .createInstance(Ci.nsIReferrerInfo); referrerInfo.initWithElement(trg); referrerInfo = u.E10SUtils.serializeReferrerInfo(referrerInfo); var contentType = null, contentDisposition = null; try { var props = u.it.getImgCacheForDocument(doc).findEntryProperties(uri, doc); var cs = Ci.nsISupportsCString; try {contentType = props.get("type", cs).data;} catch {} try {contentDisposition = props.get("content-disposition", cs).data;} catch {} } catch {} this.sendAsyncMessage("", { title: trg.closest("[title]")?.title, url: (trg.currentRequestFinalURI || uri).spec, contentType, referrerInfo, cookieJarSettings, contentDisposition, sx, isPrivate: u.PrivateBrowsingUtils.isContentWindowPrivate(trg.ownerGlobal) }); } checkTextLinkyTool(doc) { if (doc.title || !doc.documentURI.startsWith("moz-extension:")) return; var lab = doc.querySelector("body > label#lblFrom:first-child")?.textContent; if (lab) doc.title = lab.slice(0, lab.lastIndexOf("(")); } } if (!ChromeUtils.domProcessChild.childID) { var esModuleURI = Components.stack.filename; ChromeUtils.registerWindowActor("MouseImgSaver", { allFrames: true, parent: {esModuleURI}, messageManagerGroups: ["browsers"], child: {esModuleURI, events: {auxclick: {capture: true}, dblclick: {capture: true}, dragstart: {capture: true}}} }); var wref, titles = Object.create(null); var data = Object.assign(Object.create(null), { "browser.download.dir": {type: "String", get set() { var win = wref.get(), {prefs, dirsvc} = win.Services var {DownloadPaths, FileUtils} = win; var map = val => DownloadPaths.sanitize(val); win.Downloads.getList(win.Downloads.ALL).then(list => list.addView({ onDownloadChanged(download) { if (!download.stopped) return; var {url} = download.source, title = titles[url]; if (!title) return; delete titles[url]; if (!download.succeeded) return; var file = FileUtils.File(download.target.path), {leafName} = file; var ext = leafName.slice(leafName.lastIndexOf(".")); var newName = map(title) + ext, {parent} = file; var newFile = parent.clone(); newFile.append(newName); try { newFile.createUnique(file.NORMAL_FILE_TYPE, file.permissions); file.renameTo(parent, newFile.leafName); download.target.path = newFile.path; download.refresh(); } catch {} } })); Object.defineProperty(this, "set", {get() { try {var dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {var dir = dirsvc.get("DfltDwnld", Ci.nsIFile);} var arr = prefs.getStringPref("extensions.user_chrome_files.savedirs", "_Web||_Pics|0").split('|').slice(2, 4); // подпапки в [Загрузках]: нет | папка графики | имя вкладки | домен arr[1] = (arr[1]) ? wref.get().gBrowser.selectedTab.label.slice(0, 64) .replace(/\s+/g,' ').replace(/:/g,'։').replace(/[|<>]+/g,'_').replace(/([\\\/?*\"'`]+| ։։ .*)/g,'').trim() : ""; arr.map(map).forEach(dir.append); try {dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777);} catch{console.error(dir.path)} return dir.path; }}); return this.set; }}, "browser.download.folderList": {type: "Int", set: 2}, "browser.download.useDownloadDir": {type: "Bool", set: true} }); var MouseImgSaverParent = class extends JSWindowActorParent { receiveMessage(msg) { var win = msg.target.browsingContext.topChromeWindow, {name} = msg; if (name) return this[name](win, msg.data); var {url, contentType, contentDisposition, sx, title, isPrivate, referrerInfo, cookieJarSettings} = msg.data; if (sx && sx > win.mozInnerScreenX + win.innerWidth) return; if (title) titles[url] = title; wref = Cu.getWeakReference(win); var p = win.Services.prefs; for(var pref in data) { var obj = data[pref], meth = `et${obj.type}Pref`; obj.val = p.prefHasUserValue(pref) ? p["g" + meth](pref) : null; p["s" + meth](pref, obj.set); } try { var args = [url, null, // document null, // file name contentDisposition, contentType, false, // no cache null, // filepicker title key null, // chosen data u.E10SUtils.deserializeReferrerInfo(referrerInfo), u.E10SUtils.deserializeCookieJarSettings(cookieJarSettings), win.document, // initiating doc true, // skip prompt for where to save null, // cache key isPrivate, win.document.nodePrincipal], {length} = win.internalSave, lfix = length >15; lfix && args.splice(1, 0, null); //FIX FF113+ win.internalSave(...args); } finally { for(var pref in data) data[pref].val === null ? p.clearUserPref(pref) : p[`set${data[pref].type}Pref`](pref, data[pref].val); } } dblclick(win, imgURL) { var gb = win.gBrowser, index = gb.selectedTab._tPos + 1; gb.selectedTab = gb.addTrustedTab('https://yandex.ru/images/search?rpt=imageview&url=' + imgURL, {index}); } } }
Отсутствует
Сохраняет только в папку Загрузки по-умолчанию, хотя в настройках выбран другой путь
Попробовал (на 124) воспроизвести — не получилось.
который запоминается в data["browser.download.dir"].set
В data["browser.download.dir"].set ничего не запоминается.
Это геттер, который каждый раз вычисляет путь заново.
Отсутствует
Попробовал (на 124) воспроизвести — не получилось.
Отбой, картинка запишется в путь Загрузок по-умолчанию, если запись в другую папку не удалась, например, прав на запись не хватило.
Отсутствует
Оптимизировал скрипты ucf_hookClicks SingleHTML.mjs ClickPicSave.mjs в части глобальных функций.
Клики мыши работают на кнопке "Новая вкладка" и пустой панели (Закрыть вкладки слева/справа от активной), при ошибках сохранения страниц/картинок urlbar мигает красным.
В меню пользователя (правый клик на кнопке Дополнения) «Заметки» запускает свой софт для каждой системы (для Linux не вписал, т.к. нет стандарта да и пользователей только 2%)
Отсутствует
Dumby – исчезающая панель не пашет в UCF scriptsbackground [System Principal] (custom_script.js)
но код без win. работает нормально в режиме scriptschrome: document [ChromeOnly]
// ChromeUtils.domProcessChild.childID || ({………………………… getwin(){ //self = Services.obs.addObserver(self = this, topic); //в custom_script.js return this.ownerGlobal || Services.wm.getMostRecentWindow("navigator:browser"); }, ask(head, run, info = "", sec = 3, x, y = 58, win = self.getwin()){ // x < 0 отсчёт смещения справа var doc = win.document, wary = doc.createXULElement("panel"), popupset = doc.getElementById("mainPopupSet"); wary.setAttribute("onpopupshown", "move()"); wary.setAttribute("onpopuphidden", "this.remove()"); var div = wary.appendChild(doc.createElement("div")); var header = div.appendChild(doc.createElement("div")).appendChild(new Text()); var txt = div.appendChild(header.parentNode.cloneNode(true)); var s = run ? [250, 20, 8] : [0, 14, 5]; //без команды вывод сведений: аналог tooltip wary.style.cssText = `filter: sepia(100%) hue-rotate(${s[0]}deg)`; div.style.cssText = `font-size: ${s[1]}px; ${run ? "font-weight: bold;" : ""} margin: 0.${s[2]}em; max-width: 680px; white-space: pre-wrap`; txt.style.cssText = `font-size: 14px; font-style: italic; margin-top: ${info.length > 0 ? "1" : "0"}em`; txt = txt.firstChild; wary.move = (pr = wary.getOuterScreenRect(), x = -win.outerWidth) => { if (x < 0) x = Math.abs(x) - pr.width; wary.moveTo(pr.x + x, pr.y + y); wary.style.opacity = "1"; } header.data = head, txt.data = info, wary.style.opacity = "0"; wary.onclick =()=>{ run(); wary.hidePopup(); }; popupset.append(wary); wary.openPopup(); win.setTimeout(() => { wary.hidePopup(); wary.onclick = null; }, (sec + (head + info).length*0.04)*1000); // время показа зависит от кол-ва букв }
Отредактировано Dobrov (06-04-2024 03:52:52)
Отсутствует
Ошибка на строке: var header = div.appendChild(doc.createElement("div")).appendChild(new Text());
new win.Text()
Но, если панель создаётся каждый раз новая,
то иметь ссылки на текстовые ноды большого смысла нет.
То есть, можно создать header просто как div, и написать header.append(head);
Отсутствует
Dumby =>
Я переделал код, но не получается вставить второй блок текста с другим стилем.
второй текст получается сразу слитно за первым, с тем же размером шрифта и без переноса…
var header = wary.appendChild(document.createElement("div")); header.append(head); var txt = header.appendChild(new Text());
Отредактировано Dobrov (06-04-2024 08:33:52)
Отсутствует
второй текст получается сразу слитно за первым, с тем же размером шрифта и без переноса…
Ну, переносу-то с чего не работать, должен работать.
А то, что с тем же размером шрифта — так ничего удивительного.
Ты в элемент header добавил вторую текстовую ноду,
откуда для неё другой размер шрифта возьмётся?
Это вот header ещё можно не создавать, а просто написать div.append(head);
Но txt должен быть элементом, чтобы на него другой стиль прицепить.
Отсутствует
Dumby спасибо, сделал! Вот ещё хотелка для любителей ярлыков любых прог:
В кнопке «Меню пользователя» создавать последний пункт меню из опции, загружаемой из about:config.
т.е. для последней строки должно быть изменяемое свойство объекта, которое можно править каким либо образом.
Нужно по правому клику открыть диалог правки последней строки меню с её свойствами – т.е. данными в MenuItem{………}.
имя MenuItem не менять (для обращения из другой части кода), а имя строки меню и всё содержимое сделать изменяемым.
Если сложно, то можно не включать функционал upd() – автообновляемая строка меню с renderedOnce.
Но в идеале перед открытием меню из about:config лучше брать весь объект MyMenu, но не представляю, как это сделать…
Пригодиться многим: возможность вбить код, делающий то, что нужно юзеру. После правки строка меню должна измениться.
(async id => { // UserMenu, последний пункт меню брать из extensions.user_chrome_files.MenuItem MyMenu = { //имя курсивом: есть подсказка, обводка: изменяемые строки, alt() клик правой кнопкой Pics: { // Графика сайтов Вкл/Выкл permissions.default.image upd() { // обновлять сроку перед показом меню var s = G.pref(G.v) == 1, i = "chrome://browser/skin/canvas-blocked.svg"; this.label = `Графика страниц ${s ? "загружается" : G.pref(G.v) == 3 ? "кроме сторонних" : "отключена"}`; this.image = s ? i.replace("-blocked","") : i; this.tooltipText = `${G.v} = ${G.pref(G.v)}\nRClick – кроме сторонних`; }, cmd(){ G.pref(G.v, G.pref(G.v) == 2 ? 1 : 2); BrowserReload(); }, }, "SubMenu": { men: 1, //подменю "SubItem": { cmd(){console.log(this.label)}, }, }, MenuItem: { sep: 1, //сперва разделитель lab: "User MenuItem", inf: `Правый клик: изменить данное меню Эту строку и все её значения брать из extensions.user_chrome_files.MenuItem`, cmd(){ console.log("User MenuItem:\n"+ G.pref("extensions.user_chrome_files.MenuItem")) }, }, } CustomizableUI.createWidget({ id: id, label: id, tooltiptext: id, localized: false, defaultArea: CustomizableUI.AREA_NAVBAR, onCreated(btn) { btn.style.setProperty("list-style-image", "url(chrome://branding/content/icon32.png)"); btn.type = "menu"; var doc = btn.ownerDocument, m = nn => doc.createXULElement(nn); var popup = m("menupopup"), menu = m("menuitem"); menu.m = m; menu.fill = this.fill; menu.render = this.render; popup.append(menu); btn.prepend(popup); }, render(){ var popup = this.parentNode; this.remove(); this.fill(MyMenu, popup); }, fill(o, popup) { for (key in o) { if (typeof o[key] != "object") continue; var {lab, inf, img, cmd, alt, sep, men, upd} = o[key]; sep && popup.append(this.m("menuseparator")); var name = men ? "menu" : "menuitem"; var item = this.m(name); item.setAttribute("label", lab || key); // item.alt = alt; //RClick в ucf_hookClicks.js {Mouse… if (inf) item.tooltipText = inf, item.style.setProperty("font-style", "italic", "important"); if(img || /this\.image.*=/.test(upd)) item.className = name + "-iconic", item.setAttribute("image", img || F.nul); men || cmd && item.setAttribute("oncommand", cmd.toString().replace( /cmd\(.*?\){/, "{var trg = event.target || event;")); popup.append(item); if(upd){ //выделить обновляемый пункт меню item.style.filter = "drop-shadow(0.1px 1px 0.1px #c99)"; if(item.renderedOnce) (item.render = upd).call(item); else item.upd = upd, item.render = self.renderSub; } men && this.fill(o[key], item.appendChild(this.m("menupopup"))); } } }); G = { v: "permissions.default.image", pref(key,set){ //или key = [key,default] if (!Array.isArray(key)) key = [key]; var t = Services.prefs.getPrefType(key[0]), m = {b:"Bool",n:"Int",s:"String"}; t = m[t == 128 ? "b" : t == 64 ? "n" : t == 32 ? "s" : ""]; if (set == "get") return t; //тип опции if (!t) t = m[set != undefined ? (typeof set)[0] : (typeof key[1])[0]]; if (t) if (set != undefined) Services.prefs[`set${t}Pref`](key[0],set) else set = Services.prefs[`get${t}Pref`](...key); return set; } } })("ucf_test_menu");
Отредактировано Dobrov (07-04-2024 03:53:33)
Отсутствует
Здравствуйте. Можно как-то переделать код, убрать лишнее сделав его из папки, а не полного пути?
Примерно, чтобы был такой путь, где можно назад по папкам. Может сделать по другому? Есть что-то подобное как GreD, но чтобы открывал на папку ниже?
var p=Services.dirsvc.get('GreD',Ci.nsIFile);p.initWithPath(p.path+"\\..\\..\\ShareX\\ShareX.exe");p.launch();
Обычно запускаю через батник с такими параметрами:
::@echo off|firefox -profile p1
::firefox -no-remote -p ""
::firefox -no-remote -profile .\p1
Кстати, когда добавили перезапуск из-под браузера? Ctrl+Alt+R, похоже перезапускает без кэша. Круто)
upd: dobrov, ок спасибо, посмотрю.
Немного не то, что хотелось, ну сойдет наверно) Из под js, хоткей, как-то удобнее. Файл можно подвязать, но это отдельно торчащий файл, выглядит костыльно. Наверно оставлю код js, просто из-за удобства. Профили проверяю новые, версия особо не имеет значение.
Отредактировано b0ttle (08-04-2024 19:52:45)
Отсутствует
Ну и может совет, как лучше запускать для теста другие профили?
Профили могут требовать и разные версии браузеров.
Set WshShell = CreateObject("WScript.Shell") CurrPath = CreateObject("Scripting.FileSystemObject").GetFile(WScript.ScriptFullName).ParentFolder ProfileDir = CurrPath & "\ProfileDobrov" ProfileUser = "C:\UserTarget\MozillaFirefox\ProfileDobrovRAM" ProfileCD = CurrPath & "\ProfileDobrov" RamDir = "R:\UserTarget\MozillaFirefox\ProfileDobrov" If CreateObject("Scripting.FileSystemObject").FolderExists(RamDir) Then ' CurrPath = FirefoxDir WshShell.Run "cmd /c @echo " & chr(007), 0, false End If If CreateObject("Scripting.FileSystemObject").FolderExists(ProfileUser) Then ProfileDir = ProfileUser End If If CreateObject("Scripting.FileSystemObject").FolderExists(ProfileCD) Then ProfileDir = ProfileCD End If ' result = WshShell.Popup(ProfileDir, 3, "FireFox", vbQuestion) WshShell.Run(Quote(CurrPath & "\firefox.exe") & " -no-remote -profile " & Quote(ProfileDir)) Function Quote(PathFile) 'заключить в кавычки, если путь содержит пробелы If InstrRev(PathFile," ") = 0 Then Quote = PathFile Else Quote = Chr(34) & PathFile & Chr(34) End Function
Отсутствует
заготовка
Да, я понимаю, ты одноокошечник. И я одноокошечник.
Но раз заявлено стремление в сторону «Пригодиться многим»,
то в таких вопросах неплохо, хотя бы, отдавать себе отчёт.
И даже если проверку делать, то виджет, рождённый в каком-то окне,
зависит от существования этого окна. Если его закрыть, то в другом окне
виджет может перестать работать. Прежде всего, всякий promise async await,
но не только, бывает, что и синхронный код перестаёт работать.
Поэтому, создавать виджет в окне, в общем случае, не рекомендуется.
Лучше, чтобы он рождался в чём-то стабильном, существующем всегда
(но, тогда уже без выкрутасов типа F.nul).
Здесь, проще всего, в SystemGlobal, но там CustomizableUI надо импортировать.
Хорошо бы, конечно, в сандбоксе UCF, но в окне нет туда прямых ссылок.
И, если внутренний сандбокс ещё можно достать, например, так
Cu.getGlobalForObject(Cc["@mozilla.org/network/protocol/about;1?what=user-chrome-files"].getService().wrappedJSObject);
то пользовательский сандбокс, разве что только дебаггером всё перебирать.
Кстати, если Виталий это читает, и если будет обновление UCF, то заодно,
возможно, стоит рассмотреть добавить.
Вот есть scope.UcfPrefs = UcfPrefs;
а рядом, наоборот, что-то типа UcfPrefs.userbox = scope;
Но в идеале перед открытием меню из about:config лучше брать весь объект MyMenu
А мне кажется, так чуть проще, чем отдельный MenuItem.
Но, опять же, следует понимать, что если в настройку писать
больше четырёх килобайт — получим Warning в консоль.
А если больше одного мегабайта — будет отказ с ошибкой.
Столько набрать, конечно, нереально, даже с base64 иконками, но мало ли.
И, прикинь как расколбасит строку этой настройки на странице about:config
А если там ткнуть кнопку редактирования (или двойной ЛКМ),
и не одуматься (нажав Esc), а, даже ничего не трогая, сохранить,
то пропадут все переводы строк.
не представляю, как это сделать…
Direct eval() — как же ещё, eval-like стафф всё ещё позволен тем,
у кого заявлено использование конфигурационного файла.
Может дебаггером возможно, но не уверен, не пробовал, и пока не требуется.
BrowserReload();
Кстати, тебе (и всем заинтересованным) здесь рекомендую обратить внимание на
Bug 1880914 - Move Browser* helper functions used from global menubar and similar commands to a single object in a separate file, loaded as-needed
То есть, в 126, вместо BrowserReload(); уже нужен BrowserCommands.reload();
открыть диалог правки
Это ты предлагаешь написать такой диалог мне?
Занятие весьма утомительное и бесконечное,
а главное — всё равно ничего хорошего не выйдет.
И всё только ради того, чтобы редактировать без перезапуска?
Слегка сомнительная полезность.
Нет, ну можно что-то такое подзапилить, чисто для прикола,
может что-нибудь понравится или пригодится.
Вот, вызов — СКМ по самой кнопке.
Примечание: код eval'уатится в круглых скобках (),
а не в точности таким, какой есть.
(async id => { var MyMenuFunc = () => ({ //имя курсивом: есть подсказка, обводка: изменяемые строки, alt() клик правой кнопкой Pics: { // Графика сайтов Вкл/Выкл permissions.default.image upd() { // обновлять сроку перед показом меню var s = G.pref(G.v) == 1, i = "chrome://browser/skin/canvas-blocked.svg"; this.label = `Графика страниц ${s ? "загружается" : G.pref(G.v) == 3 ? "кроме сторонних" : "отключена"}`; this.image = s ? i.replace("-blocked","") : i; this.tooltipText = `${G.v} = ${G.pref(G.v)}\nRClick – кроме сторонних`; }, cmd(){ G.pref(G.v, G.pref(G.v) == 2 ? 1 : 2); BrowserReload(); }, }, "SubMenu": { men: 1, //подменю "SubItem": { cmd(){console.log(this.label)}, }, }, MenuItem: { sep: 1, //сперва разделитель lab: "User MenuItem", inf: `Правый клик: изменить данное меню Эту строку и все её значения брать из extensions.user_chrome_files.MenuItem`, cmd(){ console.log("User MenuItem:\n"+ G.pref("extensions.user_chrome_files.MenuItem")) }, }, }); var widget = CustomizableUI.getWidget(id); if (!widget?.label) { var func = (id, sym, self, widget) => widget = CustomizableUI.createWidget(self = { id, label: id, tooltiptext: id, localized: false, defaultArea: CustomizableUI.AREA_NAVBAR, get disallowSubView() { return (this.f = !this.f) || delete this.f && delete this.disallowSubView && this; }, get ut() { return Services.wm.getMostRecentBrowserWindow().MyMenu[sym]; }, setupMyMenu(data) { var {prefs} = Services, pref = "extensions.user_chrome_files.MyMenuSource"; var utils = class { constructor(data) { Object.assign(this, data); } get pref() { return prefs.getStringPref(pref, ""); } get code() { return this.pref || this.src; } get src() { var proto = this.constructor.prototype; delete proto.src; return proto.src = String(this.mmf).slice(7, -1); } changePref(val) { val ? prefs.setStringPref(pref, val) : prefs.clearUserPref(pref); } reinit() { var {pref} = this; if (pref) try { var mm = this.eval(`(${pref})`); } catch { self.error = true; } (this.win.MyMenu = typeof mm == "object" ? mm : this.mmf())[sym] = this; } }; (this.setupMyMenu = data => (new utils(data)).reinit())(data); }, onCreated(btn) { btn.type = "menu"; btn.style.setProperty("list-style-image", "url(chrome://branding/content/icon32.png)"); var doc = btn.ownerDocument, m = nn => doc.createXULElement(nn); var tmp = btn.tmp = m("menuitem"); var popup = btn.popup = m("menupopup"); popup.toggleAttribute("context"); //popup.setAttribute("oncontextmenu", 'if (event.target.nodeName == "menuitem") this.activateItem(event.target, event);'); tmp.m = m; tmp.fill = this.fill; tmp.render = this.render; popup.append(tmp); btn.prepend(popup); btn.reinit = this.reinit; btn.onauxclick = this.auxclick; }, reinit() { var {popup, tmp} = this; if (popup.firstChild == tmp) return; popup.removeAttribute("hasbeenopened"); popup.textContent = ""; popup.append(tmp); }, auxclick(e) { e.button == 1 && e.target == this && self.edit(e.view); }, render(){ var popup = this.parentNode; this.remove(); this.fill(this.ownerGlobal.MyMenu, popup); }, wt: "UCF:EditMyMenuObj", edit(win) { var w = Services.wm.getMostRecentWindow(this.wt); if (w) return w.focus(); var features = "chrome,centerscreen,resizable,dependent,width=1200,height=750"; if (Services.appinfo.widgetToolkit == "windows") features += ",dialog=no"; var w = win.openDialog("about:blank", "", features, null); var lss = Services.scriptloader.loadSubScript; lss("chrome://global/content/customElements.js", w); lss("chrome://global/content/globalOverlay.js", w); try {lss("chrome://global/content/editMenuOverlay.js", w);} catch {} w.onpageshow = this.editorShown; }, editorShown(e) { var doc = e.target, win = doc.ownerGlobal; doc.title = "Edit MyMenu Object"; doc.documentElement.setAttribute("windowtype", self.wt); var wu = win.windowUtils; for(var sub of ["menu", "popup", "global-shared", "in-content/common-shared"]) wu.loadSheetUsingURIString(`chrome://global/skin/${sub}.css`, wu.AUTHOR_SHEET); var style = doc.head.appendChild(doc.createElement("style")); style.append(` * {border-radius: 0 !important; outline-width: 1px !important;} :root, body {margin: 0; height: 100vh; overflow: hidden;} body {display: grid; grid-template-rows: 1fr auto;} body > * {margin: 8px !important;} textarea {resize: none; margin-bottom: 0 !important; white-space: pre; font-family: monospace !important;} div {display: grid; grid-template-columns: 1fr auto auto; column-gap: 1em; align-items: center;} span {display: flex; align-items: center; justify-content: center; color: light-dark(crimson, tomato); font-family: monospace;} button {border: 1px solid gray !important; margin: 0 !important;} `); var ta = win.ta = doc.body.appendChild(doc.createElement("textarea")); ta.value = self.ut.code; ta.onchange = self.change; ta.onkeypress = self.keypress; var btns = doc.body.appendChild(doc.createElement("div")); win.err = btns.appendChild(doc.createElement("span")).appendChild(new win.Text()); for(var lab of ["Применить", "Готово"]) { var btn = btns.appendChild(doc.createElement("button")); btn.onclick = self.btnClick; btn.append(lab); } doc.getElementById("cmd_selectAll")?.setAttribute( "oncommand", "ta.blur(); Services.focus.setFocus(ta, Services.focus.FLAG_NOSCROLL);" ); if (self.error) return delete self.error, self.btnClick.call(btn.previousSibling); ta.editor.beginningOfDocument(); ta.focus(); }, change() { this.ownerGlobal.newCode = true; }, keypress(e) { if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) return; if (e.key == "Tab") return e.preventDefault(), this.editor.insertText("\t"); if (e.key != "Enter") return; e.preventDefault(); var ss = this.selectionStart; var beg = this.value.slice(0, ss); var ind = beg.lastIndexOf("\n") + 1; var indent = beg.slice(ind).match(/^(?:[ \t]*)?/); this.editor.insertText("\n" + indent); }, btnClick() { var wnd = this.ownerGlobal; var {ta, err, newCode} = wnd, code = ta.value; wnd.newCode = false; var done = this.matches(":last-child"); if (newCode || err.ex) { var some = /\S/.test(code), {ut} = self; if (newCode) err.data = ""; if (some) try { if (newCode) ut.eval(`(${code})`); else throw err.ex; } catch(ex) { var num = ex.lineNumber; var ed = ta.editor, sc = ed.selectionController; ed.beginningOfDocument(); while(num--) sc.lineMove(true, !num); var div = ed.rootElement; var dr = div.getBoundingClientRect(); var dp = dr.top + div.clientHeight / 2.6; var sr = ed.selection.getRangeAt(0).getBoundingClientRect(); var sc = sr.top + sr.height / 2 div.scrollBy(0, sc - dp); if (newCode) err.data = err.ex = ex; return ta.focus(); } err.ex = null; ut.changePref(some && code); if (!some && !done) ta.focus(), ta.value = ut.src; for(var win of Services.wm.getEnumerator("navigator:browser")) if (!win.closed) win.MyMenu?.[sym]?.reinit(), widget.forWindow(win)?.node?.reinit(); } done && wnd.close(); }, renderSub() { delete this.render; this.render(); this.render = self.updSub; this.upd(); }, updSub() { this.parentNode.state.startsWith("c") || this.upd(); }, fill(o, popup) { for(var key in o) { if (typeof o[key] != "object") continue; var {lab, inf, img, cmd, alt, sep, men, upd} = o[key]; sep && popup.append(this.m("menuseparator")); var name = men ? "menu" : "menuitem"; var item = this.m(name); item.setAttribute("label", lab || key); // item.alt = alt; //RClick в ucf_hookClicks.js {Mouse… if (inf) item.tooltipText = inf, item.style.setProperty("font-style", "italic", "important"); if (img || /this\.image.*=/.test(upd)) item.className = name + "-iconic", item.setAttribute("image", img || "chrome://browser/content/robot.ico"); men || cmd && item.setAttribute("oncommand", cmd.toString().replace( /cmd\(.*?\){/, "{var trg = event.target || event;")); popup.append(item); if (upd) { //выделить обновляемый пункт меню item.style.filter = "drop-shadow(0.1px 1px 0.1px #c99)"; if (item.renderedOnce) (item.render = upd).call(item); else item.upd = upd, item.render = self.renderSub; } men && this.fill(o[key], item.appendChild(this.m("menupopup"))); }} }); widget = Cu.evalInSandbox(`(${func})("${id}", Symbol());`, Cu.getGlobalForObject( Cc["@mozilla.org/network/protocol/about;1?what=user-chrome-files"].getService().wrappedJSObject )); } widget.disallowSubView.setupMyMenu({win: window, eval: code => eval(code), mmf: MyMenuFunc}); G = { v: "permissions.default.image", pref(key,set){ //или key = [key,default] if (!Array.isArray(key)) key = [key]; var t = Services.prefs.getPrefType(key[0]), m = {b:"Bool",n:"Int",s:"String"}; t = m[t == 128 ? "b" : t == 64 ? "n" : t == 32 ? "s" : ""]; if (set == "get") return t; //тип опции if (!t) t = m[set != undefined ? (typeof set)[0] : (typeof key[1])[0]]; if (t) if (set != undefined) Services.prefs[`set${t}Pref`](key[0],set) else set = Services.prefs[`get${t}Pref`](...key); return set; } } })("ucf_test_menu");
Отсутствует
следует понимать, что если в настройку писать больше четырёх килобайт — получим Warning в консоль.
Dumby – спасибо за изменяемое пользователем меню, потестирую!
Наверное, код станет проще без диалога настройки, если объект меню хранить в опции в виде base64.
Для изменения меню создавать временный файл через atob(MyMenuSource) и править его в редакторе по-умолчанию.
Только непонятно, как отследить, что редактор сохранил и закрыл файл ??? Затем файл надо перевести в btoa(……) и запомнить новое содержимое в опции.
Т.е когда редактор по-умолчанию является многовкладочным типа SublimeText и при закрытии файла сам не закрывается…
раз заявлено стремление в сторону «Пригодиться многим», то в таких вопросах неплохо, хотя бы, отдавать себе отчёт.
java API браузеров сложна для меня, только чужие примеры переделываю. Не нашёл статей, где можно последовательно изучить API браузеров с нуля, от developer.mozilla.org толку мало.
Отсутствует
Затем файл надо перевести в btoa(……) и запомнить новое содержимое в опции.
То есть, содержимое файла как бинарную строку, а не как текст,
ведь btoa() его есть не будет, попробуй с консоли btoa("меню");
Тогда надо ещё делать метод конвертирования настройки в текст,
в смысле не для создания файла,
и метод перегона текста в бинарную строку для для создания файла,
если настройка не существует.
объект меню хранить в опции в виде base64
А зачем? Он от этого только больше станет.
Если имеется в виду, что он будет одной длинной строкой,
то проще заменять переводы строк на что-нибудь другое, и обратно.
Или JSON.stringify() <—> JSON.parse(), правда тогда ненужных слэшей насыпет.
В base64, возможно, лучше если только в gzip, но не всегда, и это заморочно.
Только непонятно, как отследить, что редактор сохранил и закрыл файл ???
Я не знаю что такое «закрыл файл».
Это типа он берёт его в оборот монопольно,
и других к нему даже не подпустит, пока не «закроет», так что ли?
Или только не даст удалить?
А если сохранил, значит у файла изменится lastModifiedTime
Поэтому, запоминаем его сразу, вешаем в окна листенер на событие "activate".
И обсёрвер на топик "quit-application-granted".
И там сравниваем lastModifiedTime на момент события или топика
с тем, который запомнили.
Можно предусмотреть ручной выход из режима редактирования,
типа когда заказали редактирование, кнопка начинает назойливо мигать,
намекая, что её нужно нажать ещё раз,
чтобы удалить все листенеры, обсёрвер, и временный файл.
Вобщем, можешь пофантазировать на эту тему.
Отсутствует
если будет обновление UCF, то заодно,
возможно, стоит рассмотреть добавить.Вот есть scope.UcfPrefs = UcfPrefs;
а рядом, наоборот, что-то типа UcfPrefs.userbox = scope;
Обновил, добавил UcfPrefs.customSandbox, в случае если отключены в настройках фоновые скрипты
вызов UcfPrefs.customSandbox создает песочницу но при этом скрипты если подключены в CustomStylesScripts.mjs не загружаются.
Для всех:
В этом обновлении CustomStylesScriptsChild.mjs удалён, настройки из него перемещены в CustomStylesScripts.mjs см. Сontent Settings
и подчистил этот файл оставил там только то что нужно для редактирования.
Отредактировано Vitaliy V. (14-04-2024 15:05:19)
Отсутствует
Обновил, добавил UcfPrefs.customSandbox, в случае если отключены в настройках фоновые скрипты
вызов UcfPrefs.customSandbox создает песочницу но при этом скрипты если подключены в CustomStylesScripts.mjs не загружаются.
О, замечательно!
Я-то разогнался, словно сандбокс-скриптинг всегда enabled,
а это, конечно же, может быть и не так.
оставил там только то что нужно для редактирования
Кстати, был, когда-то, вообще ультимативный проект.
Обновлять UCF — это тот ещё головняк, поэтому,
идея была в том, чтобы вырезать весь интерналс в отдельную папку.
То есть, в папке user_chrome_files — только пользовательский стафф.
Включая там сугубый пользовательский user_chrome.manifest, для всяких override директив.
UCF'ские иконки, вроде, тоже рассматривались как пользовательские,
поскольку есть любители ещё старых, тёплых, ламповых svg'шек.
А рядом с этой пользовательской папкой user_chrome_files
другая — (условно) user_chrome_files_internal
Там — весь остальной обеспечительный код, считай почти весь UCF.
Собственно, идея в том, чтобы последний раз переломаться обновить,
но затем,
если какое-то обновление, улучшение, исправление, рефлексия на отвал,
то просто заменяем папку user_chrome_files_internal, ни о чём даже не задумываясь.
Скачал, заменил, перезапустился с очисткой кэша — всё, свободен.
Ну, это некая маниловщина, если бы, да кабы.
Мне такое, скорее всего, не по зубам, но как замысел, возможно, представляет интерес.
И, внезапно меняя тему, я смотрю ATB обновился. Это хорошо.
Но торчат BrowserOpenTab() и BrowserOpenFileWindow()
которых в 126 уже нет.
BrowserOpenAddonsMgr() пока ещё есть, но уже не жилец.
рекомендую обратить внимание на
Bug 1880914 - Move Browser* helper functions used from global menubar and similar commands to a single object in a separate file, loaded as-needed
Отсутствует
Поэтому, создавать виджет в окне, в общем случае, не рекомендуется.
……проще всего, в SystemGlobal, но там CustomizableUI надо импортировать.
Хорошо бы, конечно, в сандбоксе UCF, но в окне нет туда прямых ссылок.
Vitaliy V.
В новой версии можно проще общие объекты подключать к UCF вместо браузера?
У меня пока так: в скрипте scriptsbackground: общий объект для вызова из scriptschrome: кода
globalThis[Symbol.for('UcfGlob')] = this.UcfGlob; //общие функции
И почему CustomStylesScripts.mjs расположен отдельно? Ведь после добавления скриптов его надо править (пока не будет GUI-менеджера подключенных скриптов/стилей)
Вообще, в ReadMe не хватает описания функций UserChromeFiles, т.е. нет авторской рекламы!
И если убраны файлы стилей, то хотя бы сохранить их закомментированными в stylesall: [ // For all documents
{ path: "custom_styles_all_agent.css", type: "AGENT_SHEET"},
Отсутствует
вот убрал 1 иконку
Другое дело.
Но, fp.init(window, в трёх местах торчат,
хотя было сказано, что в 125, уже нужно
fp.init(window.browsingContext,
так что нет, не принимается.
Отсутствует
идея была в том, чтобы вырезать весь интерналс в отдельную папку.
...
А рядом с этой пользовательской папкой user_chrome_files
другая — (условно) user_chrome_files_internal
Т.е. в папке chrome две папки UserChromeFiles ? По моему это излишне...
А так почти все что обновляется и так в одной папке user_chrome,
вот только user_chrome.manifest и папка locales двойного назначения, юзер может редактировать или добавлять файлы локали,
а и ещё остались custom_script_win.js custom_script_all_win.js но они тоже редко обновляются или можно вообще убрать их в следующем обновлении UCF ?
BrowserOpenTab() и BrowserOpenFileWindow()
которых в 126 уже нет.
BrowserOpenAddonsMgr()
Спс, исправил.
В новой версии можно проще общие объекты подключать к UCF вместо браузера?
Например из поста №1440
эту часть кода
Cu.getGlobalForObject(
Cc["@mozilla.org/network/protocol/about;1?what=user-chrome-files"].getService().wrappedJSObject
)
можно заменить на UcfPrefs.customSandbox
У меня пока так: в скрипте scriptsbackground: общий объект для вызова из scriptschrome: кода
globalThis[Symbol.for('UcfGlob')] = this.UcfGlob; //общие функции
Это что, в background скрипт находится?
и каким образом оконный скрипт увидит globalThis background скрипта (если UcfPrefs.customSandbox не использовать)
И почему CustomStylesScripts.mjs расположен отдельно?
Да чтобы не искать его среди других скриптов только для этого
Вообще, в ReadMe не хватает описания функций UserChromeFiles, т.е. нет авторской рекламы!
Да пофиг мне на рекламу, на этом не заработаешь, количество пользователей тоже не волнует
И если убраны файлы стилей, то хотя бы сохранить их закомментированными в stylesall
Для примера что ли, а одного недостаточно? А тип стилей там в описании есть type: style rights AGENT_SHEET, AUTHOR_SHEET or USER_SHEET
Отредактировано Vitaliy V. (16-04-2024 14:55:21)
Отсутствует
и ещё остались custom_script_win.js custom_script_all_win.js но они тоже редко обновляются или можно вообще убрать их в следующем обновлении UCF ?
Не надо убирать, а то совсем запутаемся.
Отсутствует