добавить подключение стилей по имени в зависимости от OS
Да, но для стилей есть медиа запросы, например:
@media (-moz-platform: macos) {
код для macos
}
@media (-moz-platform: linux) {
код для linux
}
@media (-moz-platform: windows) {
код для windows
}
Скрипты же тем более могут определить OS, я не против добавить но в каких случаях это необходимо?
Отсутствует
Скрипты же тем более могут определить OS, я не против добавить но в каких случаях это необходимо?
Для портабельного или личного профиля, запускаемого и используемого на всяких OS.
В идеале лучше так: грузим стили из custom_styles, затем дополнительные CSS из папки, соответствующий имени операционки.
если браузер на MacOS находит папку custom_styles/macosx, догружаем custom_styles_all_agent.css и прочие CSS из неё.
Примеры стилей обычно только для винды, я пробовал @media (-moz-platform:, но неудобство в том, что придётся править стили при каждом их обновлении.
В стилях от aris-t2 есть совместимость для разных OS, но для Linux и MacOS всё же требуются дополнительные правки.
Проще разные CSS держать, т.к. CSS для MacOS и Windows слишком различаются, да и разные стили для AGENT_SHEET и USER_SHEET увеличивают число файлов.
из-за несовместимости версий Firefox я держу разные папки, например aris-t2-115+ и aris-t2-97…, но это уже другая история
Отредактировано Dobrov (17-03-2024 15:15:09)
Отсутствует
Лоадер применительно к mjs не изменился?
Предполагаю что нет, так как один конвертированный скрипт в нем уже работает.
Не понял. Если сделал как написано в первой строке
конвертированного скрипта — значит изменился.
Неизменившийся у меня не работает, хотя мне казалось, что будет.
Но зачем искать себе приключений?
Для сконвертированных модулей следует использовать метод,
который сейчас в браузере для этого предназначен — ChromeUtils.importESModule("……….mjs");
И, пожалуйста, переделайте в mjs jsm-ки:
Хорошо, попробую.
var clickInterval = 5*60; var intervals = [ 10, 15, 30, 60, 3*60,/* 5*60,*/ 15*60, 30*60, 60*60, ]; var name = "TreeStyleTabAutoReloader"; var addonId = "treestyletab@piro.sakura.ne.jp"; var sfx = "ucf-tst-tab-autoreload", id = `extension:${addonId}:${sfx}`; var sheets = { def(name, css) { Object.defineProperty(this, name, {configurable: true, get() { delete this[name]; return this[name] = this.pre(name, css); }}); }, pre(name, css) { var ios = Services.io; var rph = ios.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler); var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService); var type = "USER_SHEET"; return (this.pre = (name, css) => { var subst = "tst-autoreload-stylesheet-" + name; rph.setSubstitution(subst, ios.newURI("data:text/css," + encodeURIComponent(css))); return [sss.preloadSheet(ios.newURI(`resource://${subst}/`), sss[type]), Ci.nsIDOMWindowUtils[type]]; })(name, css); } }; if (!ChromeUtils.domProcessChild.childID) { var ep = "resource://gre/modules/ExtensionParent.sys.mjs"; var manager = ChromeUtils.importESModule(ep).ExtensionParent.apiManager; var tt = manager.global.tabTracker; var ss = "resource:///modules/sessionstore/SessionStore.sys.mjs"; ss = ChromeUtils.importESModule(ss).SessionStore; var gsec = tab => ss.getCustomTabValue(tab, id); var webExt, addonUUID; var waitAddon = (e, isAppShutdown) => isAppShutdown || ( webExt = null, manager.on("ready", onReady) ); var onReady = (e, addon) => { if (addon.id != addonId) return; manager.off("ready", onReady); addon.once("shutdown", waitAddon); onAddon(addon); } var onAddon = addon => { webExt = addon; if (addonUUID == addon.uuid) return; addonUUID && ChromeUtils.unregisterWindowActor(name); var esModuleURI = Components.stack.filename; ChromeUtils.registerWindowActor(name, { parent: {esModuleURI}, remoteTypes: ["extension"], messageManagerGroups: ["webext-browsers"], child: {esModuleURI, events: {pageshow: {}}}, matches: [`moz-extension://${addonUUID = addon.uuid}/sidebar/sidebar.html?*`] }); } var format = sec => { var map = new Map(); // resource://gre/modules/PluralForm.jsm var f = n => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; var hh = ["", "а", "ов"], ms = ["а", "ы", ""]; return (format = sec => { var res = map.get(sec = +sec); if (!res) { var num, arr = []; if ((num = Math.floor(sec / 3600)) > 0) sec -= num * 3600, arr.push(`${num} час${hh[f(num)]}`); if ((num = Math.floor(sec / 60)) > 0) sec -= num * 60, arr.push(`${num} минут${ms[f(num)]}`); sec > 0 && arr.push(`${sec} секунд${ sec > Math.floor(sec) ? "ы" : ms[f(sec)] }`); map.set(sec, res = arr.join(" ")); } return res; })(sec); } var hasDef = intervals.includes(clickInterval); hasDef || intervals.push(clickInterval); sheets.def("sb", ` #context_autoreloadTab:not([checked]) > .menu-iconic-left,${ hasDef ? "" : "\n\t\t#context_autoreloadTab[checked][def=true] > menupopup > :nth-child(2)," } #context_autoreloadTab:not([checked]) > menupopup > :first-child { fill: gray !important; -moz-context-properties: fill !important; list-style-image: url("chrome://global/skin/icons/reload.svg") !important; } #context_autoreloadTab[checked] > .menu-iconic-left > image { fill: currentColor !important; -moz-context-properties: fill !important; list-style-image: url("chrome://global/skin/icons/check.svg") !important; } `); clickInterval = String(clickInterval); var sym = Symbol(name); var TreeStyleTabAutoReloaderParent = class extends JSWindowActorParent { actorCreated() { var win = this.browsingContext.embedderElement.ownerGlobal; var mo = win[sym]; if (!mo) { win.windowUtils.addSheet(...sheets.sb); mo = win[sym] = new win.MutationObserver(this.handleMutations); var popup = win.document.getElementById("contentAreaContextMenu"); mo.obs = mo.observe.bind(mo, popup, {childList: true}); mo.win = win; } ((mo.actor = this).mo = mo).obs(); } didDestroy() { this.mo.disconnect(); } get menu() { var value = this.mo.menu; if (!value) { value = this.mo.win.MozXULElement.parseXULToFragment( `<menu id="context_autoreloadTab" class="menu-iconic" onclick="if (event.target == this) linkedObject.click(this);" > <menupopup oncommand="parentNode.linkedObject.cmd(event);"/> </menu>` ); (value = this.mo.menu = value.firstChild).remove(); value.linkedObject = this; (value.popup = value.firstChild).initShadowDOM = this.initShadowDOM; } return Object.defineProperty(this, "menu", {value}).menu; } initShadowDOM() { delete this.initShadowDOM; this.initShadowDOM(); var df = this.ownerGlobal.MozXULElement.parseXULToFragment( `<menuitem closemenu="single" label="Не перезагружать" oncommand="event.stopPropagation(); parentNode.parentNode.click();"/> <menuitem label="Другой…" oncommand="event.stopPropagation(); parentNode.parentNode.linkedObject.prompt();"/> <menuseparator/>` ); var doc = this.ownerDocument; for(var sec of intervals) { var menuitem = doc.createXULElement("menuitem"); menuitem.setAttribute("type", "radio"); menuitem.setAttribute("closemenu", "single"); menuitem.setAttribute("value", sec); menuitem.setAttribute("label", format(sec)); df.append(menuitem); } hasDef || df.firstChild.after(df.lastChild); this.append(df); this.parentNode.linkedObject.updMenupopup(this); this.setAttribute("onpopupshowing", "parentNode.linkedObject.updMenupopup(this);"); } handleMutations(muts) { var cm = this.win.gContextMenu; if (cm) for(var mut of muts) for(var node of mut.addedNodes) if (node.id == "treestyletab_piro_sakura_ne_jp-menuitem-_context_duplicateTab") { var {tabId} = cm.contentData.webExtContextData; var tab = tt.getTab(tabId); //if (tab?.linkedBrowser.currentURI.scheme.startsWith("http")) { if (tab) { var {menu} = this.actor; menu.tabId = tabId; node.after(menu); this.actor.maybeSetLabel(tab); webExt.apiManager.global.gMenuBuilder.itemsToCleanUp.add(menu); } break; } } maybeSetLabel(tab) { var sec = gsec(tab); var has = this.menu.hasAttribute("checked"); if (Boolean(sec) ^ has) has = !has, this.menu.toggleAttribute("checked"); var curr = has && sec; curr !== this.menu.sec && this.setLabel(curr); } setLabel(sec) { this.menu.setAttribute("label", (this.menu.sec = sec) ? `Интервал перезагрузки: ${format(sec)}` : "Задать интервал перезагрузки" ); hasDef || this.menu.setAttribute("def", sec == clickInterval); } click(menu) { var {tabId} = menu; var has = menu.toggleAttribute("checked"); has ? this.initTab(tabId, clickInterval) : this.destroyTab(tabId); var w = menu.clientWidth; this.setLabel(has && clickInterval); if (menu.popup.state == "open") this.updMenupopup(menu.popup), menu.clientWidth != w && menu.ownerGlobal.setTimeout(this.move, 50, menu); } cmd(e) { var {value} = e.target; if (value == this.menu.sec) return; var {tabId} = this.menu; this.setLabel(value); if (this.menu.hasAttribute("checked")) this.changeInterval(tt.getTab(tabId), value); else this.menu.toggleAttribute("checked"), this.initTab(tabId, value); } changeInterval(tab, sec) { var win = tab.ownerGlobal; win.clearInterval(tab.getAttribute(sfx)); ss.setCustomTabValue(tab, id, sec); tab.setAttribute(sfx, win.setInterval(bro.reload, sec * 1e3, tab)); } async prompt(val) { var {menu} = this, {sec} = menu, {prompt} = Services; var res = await prompt.asyncPrompt( null, prompt.MODAL_TYPE_WINDOW, val ? "ЕЩЁ РАЗ:" : "Задать интервал обновления", "Введите число секунд авто-обновления", val || sec || clickInterval, null, null ); if (!res.get("ok")) return; var val = res.get("value"); if (!val) return; if (!isFinite(val)) return this.prompt(val); var {tabId} = menu, val = String(Math.round(val) || 1); sec ? this.changeInterval(tt.getTab(tabId), val) : this.initTab(tabId, val); } move(menu) { menu.popup.moveToAnchor(menu, "end_before"); } updMenupopup(popup) { var old = popup.querySelector("[checked=true]"); var {sec} = this.menu; var cur = sec && popup.querySelector(`[value="${sec}"]`); if (old != cur) old?.removeAttribute("checked"), cur && cur.setAttribute("checked", true); } initTab(tabId, sec, skipSet) { bro.initTab(tt.getTab(tabId), sec); this.sendAsyncMessage(tabId, true); } destroyTab(tabId) { bro.destroyTab(tt.getTab(tabId)); this.sendAsyncMessage(tabId); } receiveMessage(msg) { msg.name && bro.destroyTab(tt.getTab(+msg.name)); } } var bro = { async observe(win) { var tc = win.document.getElementById("tabbrowser-tabs"); var tp = win.document.getElementById("tabbrowser-tabpanels") var types = ["EndSwapDocShells", "TabClose", "SSTabRestored"]; var destructor = (meth = "removeEventListener") => types.forEach( (type, ind) => (ind ? tc : tp)[meth](type, this, ind == 0) ); destructor("addEventListener"); win.ucf_custom_script_win[id] = {destructor}; win.ucf_custom_script_win.unloadlisteners.push(id); await ss.promiseAllWindowsRestored; for(var tab of win.gBrowser.tabs) tab.linkedPanel || this.maybeInitTab(tab); }, maybeInitTab(tab) { var sec = gsec(tab); sec && this.initTab(tab, sec, true); }, handleEvent(e) { this[e.type](e); }, reload(tab) { tab.ownerGlobal.gBrowser.reloadTab(tab); }, initTab(tab, sec, skipSet) { skipSet || ss.setCustomTabValue(tab, id, sec); tab.setAttribute(sfx, tab.ownerGlobal.setInterval(this.reload, sec * 1e3, tab)); }, destroyTab(tab) { tab.ownerGlobal.clearInterval(tab.getAttribute(sfx)); ss.deleteCustomTabValue(tab, id); tab.removeAttribute(sfx); }, TabClose(e) { var intervalId = e.target.getAttribute(sfx); if (!intervalId) return; e.target.ownerGlobal.clearInterval(intervalId); var tab = e.detail.adoptedBy; tab && this.initTab(tab, gsec(e.target)); }, SSTabRestored(e) { var tab = e.target; tab.hasAttribute(sfx) || this.maybeInitTab(tab); }, async EndSwapDocShells(e) { var br = e.detail, trg = e.target, win = br.ownerGlobal; await new Promise(win.requestAnimationFrame); if (!win.closed) return; var tab = win.gBrowser.getTabForBrowser(br); if (!tab) return; var sec = gsec(tab); if (sec) tab = trg.ownerGlobal.gBrowser.getTabForBrowser(trg), tab.hasAttribute(sfx) || this.initTab(tab, sec); } }; var topic = "browser-delayed-startup-finished"; var {obs} = Services; obs.addObserver(bro, topic); obs.addObserver(function quit(s, t) { obs.removeObserver(quit, t); obs.removeObserver(bro, topic); }, "quit-application-granted"); var policy = WebExtensionPolicy.getByID(addonId); if (policy) onAddon(policy.extension), policy.extension.once("shutdown", waitAddon); else waitAddon(); } else { sheets.def("tst", ` :root { --ar-ind-width: 22px; } .autoreload-indicator { height: 14px !important; position: relative !important; z-index: var(--tab-ui-z-index) !important; opacity: .6 !important; fill: currentColor !important; -moz-context-properties: fill !important; min-width: var(--ar-ind-width) !important; background: no-repeat center/60% url("chrome://global/skin/icons/reload.svg") !important; } .autoreload-indicator:hover { opacity: 1 !important; } tab-item-substance[autoreload] > .extra-items-container.front { right: calc(var(--tab-label-end-offset) + var(--ar-ind-width)) !important; } tab-item.faviconized .autoreload-indicator { min-width: 12px !important; background-size: 100% !important; } tab-item.faviconized > tab-item-substance[autoreload] { padding-inline-start: 0 !important; } `); var opts = {childList: true}; var TreeStyleTabAutoReloaderChild = class extends JSWindowActorChild { handleEvent(e) { this.sendAsyncMessage(""); this.stopReload = this.stopReload.bind(this); var doc = e.target, win = doc.ownerGlobal; win.windowUtils.addSheet(...sheets.tst); var mos = this.mos = new Set(); for(var div of doc.querySelectorAll( "#pinned-tabs-container, #normal-tabs-container > .virtual-scroll-container" )) { var mo = new win.MutationObserver(this.catchUL); mos.add(mo); mo.actor = this; mo.observe(mo.div = div, opts); } } catchUL() { var ul = this.div.querySelector(":scope > ul"); if (!ul) return; this.disconnect(); this.actor.mos.delete(this); var mo = new this.constructor(this.actor.catchTabItem); (mo.actor = this.actor).mos.add(mo); mo.observe(ul, opts); for(var node of ul.children) mo.actor.check(node); } catchTabItem(muts) { for(var mut of muts) for(var node of mut.addedNodes) this.actor.check(node); } check(node) { node.nodeName == "TAB-ITEM" && this.onTab(node); } async onTab(tab, tabId) { var subs = tab.querySelector("tab-item-substance"); var win = tab.ownerDocument.defaultView.wrappedJSObject; if (!tabId) { if (subs.hasAttribute("autoreload")) subs.removeAttribute("autoreload"), subs.querySelector(".autoreload-indicator")?.remove(); var {tabId} = subs.dataset; var sec = await win.browser.sessions.getTabValue(+tabId, sfx); if (!sec) return; } subs.toggleAttribute("autoreload", true); var ind = win.document.createElement("span"); ind.className = "autoreload-indicator"; ind.title = "Остановить перезагузку"; ind.tabId = tabId; ind.onmousedown = this.stopReload; subs.querySelector("tab-closebox").before(ind); } stopReload(e) { if (e.button) return; e.stopImmediatePropagation(); var trg = e.target.wrappedJSObject; this.sendAsyncMessage(trg.tabId); this.removeIndicator(trg); } removeIndicator(ind) { ind.closest("tab-item-substance").removeAttribute("autoreload"); ind.remove(); } receiveMessage(msg) { var tab = this.contentWindow.document.getElementById("tab-" + msg.name); if (tab) msg.data ? this.onTab(tab, msg.name) : this.removeIndicator(tab.querySelector(".autoreload-indicator")); } didDestroy() { var {mos} = this; if (!mos) return; for(var mo of mos) mo.disconnect(); mos.clear(); } } } export {TreeStyleTabAutoReloaderParent, TreeStyleTabAutoReloaderChild};
var timeout = 500; if (!ChromeUtils.domProcessChild.childID) { var popupWidth = 300; var label = "Some Label"; var tooltiptext = "Some Tooltip Text"; var imgEnabled = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="16"><rect fill="limegreen" width="16" height="16"/></svg>'; var imgDisabled = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="16"><rect fill="orangered" width="16" height="16"/></svg>'; var btnImage, popupPosition, enabled, addonUUID, registeredUUID; var mo = (p, r = "gre") => ChromeUtils.importESModule(`resource://${r}/modules/${p}.sys.mjs`)[p]; //-------[ Addon ]------------------------------------------------------ var addonId = "treestyletab@piro.sakura.ne.jp"; var manager = mo("ExtensionParent").apiManager; var tt = manager.global.tabTracker; var waitAddon = (e, isAppShutdown) => isAppShutdown || ( addonUUID = null, manager.on("ready", onReady) ); var onReady = (e, addon) => { if (addon.id != addonId) return; manager.off("ready", onReady); addon.once("shutdown", waitAddon); addonUUID = addon.uuid; checkRegistration(); } waitAddon(); //-------[ Actor registration ]------------------------------------------------------ var name = "TreeStyleTabPreviewPopup"; var esModuleURI = Components.stack.filename; var reg = () => ChromeUtils.registerWindowActor(name, { parent: {esModuleURI}, remoteTypes: ["extension"], messageManagerGroups: ["webext-browsers"], child: {esModuleURI, events: {mouseover: {}}}, matches: [`moz-extension://${registeredUUID = addonUUID}/sidebar/sidebar.html*`] }); var unreg = () => { registeredUUID = null; ChromeUtils.unregisterWindowActor(name); } var checkRegistration = () => { if (enabled) { if (registeredUUID) { if (registeredUUID == addonUUID) return; addonUUID && unreg(); } addonUUID && reg(); } else if (registeredUUID && addonUUID) unreg(); } //------[ Observer ]------------------------------------------------------ var {prefs, obs} = Services; var pref = "ucf_tst_preview_popup"; var branch = prefs.getBranch("sidebar."); var prefObs = { observe(b, t, data) { this[data]?.(branch.getBoolPref(data, true)); }, position_start: val => popupPosition = val ? "end_before" : "start_before", [pref](val) { btnImage = (enabled = val) ? imgEnabled : imgDisabled; this.setBtnsImg(); checkRegistration(); }, setBtnsImg: () => prefObs.setBtnsImg = () => { var widget = cui.getWidget(btnId); for(var win of cui.windows) widget.forWindow(win).node?.setAttribute("image", btnImage); } }; for (let p of [pref, "position_start"]) prefObs.observe(null, null, p); branch.addObserver("", prefObs); obs.addObserver(function quit(s, topic) { obs.removeObserver(quit, topic); branch.removeObserver("", prefObs); }, "quit-application-granted"); //-------[ Widget ]------------------------------------------------------ var popupId = "ucf-tst-preview-popup"; var btnId = popupId + "-button"; var cui = mo("CustomizableUI", ""); var toggle = () => branch.setBoolPref(pref, !enabled); cui.createWidget({ id: btnId, label, tooltiptext, localized: false, onCreated(btn) { btn._handleClick = toggle; btn.setAttribute("image", btnImage); } }); //-------[ Actor ]------------------------------------------------------ var TreeStyleTabPreviewPopupParent = class extends JSWindowActorParent { actorCreated() { var doc = this.browsingContext.topChromeWindow.document; var popup = doc.getElementById(popupId); if (!popup) { popup = doc.createXULElement("menupopup"); popup.id = popupId; popup.setAttribute("ignorekeys", true); popup.setAttribute("rolluponmousewheel", true); popup.setAttribute("consumeoutsideclicks", "never"); popup.shadowRoot.querySelector("style").append(` :host { padding: 0 !important; -moz-appearance: none !important; } arrowscrollbox::part(scrollbutton-up), arrowscrollbox::part(scrollbutton-down) { display: none !important; } `); (popup.canvas = popup.appendChild(doc.createElement("canvas"))) .width = popupWidth; popup.context = popup.canvas.getContext("2d", {alpha: false}); doc.getElementById("mainPopupSet").append(popup); } this.popup = popup; } receiveMessage(msg) { var id = msg.data; if (!id) return this.popup.hidePopup(); var tab = tt.getTab(+id.slice(4)); if (tab/* && !tab.selected*/) { var cwg = tab.linkedBrowser.browsingContext?.currentWindowGlobal; cwg && this.drawSnapshot(tab.ownerGlobal, cwg, id); } } async drawSnapshot(win, cwg, id) { var {width, height} = await cwg.getActor("Thumbnails") .sendQuery("Browser:Thumbnail:ContentInfo"); if (width < 200) return; var k = popupWidth / width; try {var bitmap = await cwg.drawSnapshot( new DOMRect(0, 0, width, height), k, "white" );} catch {} if (!bitmap) return; var data = await this.sendQuery(id, win.devicePixelRatio); if (!data) return; this.popup.canvas.height = k * height; this.popup.context.drawImage(bitmap, 0, 0); bitmap.close(); this.popup.openPopupAtScreenRect(popupPosition, ...data); } didDestroy() { this.popup.hidePopup(); this.popup = null; } } } export {TreeStyleTabPreviewPopupParent}; export class TreeStyleTabPreviewPopupChild extends JSWindowActorChild { actorCreated() { this.args = ["mouseleave", () => { this.tab = null; this.tid || this.sendAsyncMessage(""); this.tid = this.clearTimeout(); }, {once: true}]; } mult(val) { return this * val; } receiveMessage(msg) { var tab = this.document.getElementById(msg.name); var res = tab?.matches(":hover"); if (res) { var {x, y, width, height} = tab.getBoundingClientRect(); var win = tab.ownerGlobal; res = [ x + win.mozInnerScreenX, y + win.mozInnerScreenY, width, height ]; var z = win.devicePixelRatio; if (z != 1 || msg.data != 1) res = res.map(this.mult, z / msg.data); } return res; } handleEvent(e) { var tab = e.target.closest("tab-item"); if (!tab || tab == this.tab) return; this.clearTimeout(); this.tid = this.contentWindow .setTimeout(this.onTab, timeout, this.tab = tab, this); tab.addEventListener(...this.args); } clearTimeout() { this.tid && this.contentWindow.clearTimeout(this.tid); } onTab(tab, self) { self.tid = null; tab.wrappedJSObject.apiTab.discarded || self.sendAsyncMessage("", tab.id); } didDestroy() { this.tab = null; } }
такой глупый вопрос: JS скрипты будут дóльше поддерживаться в Firefox, чем JSM-ки?
Ну почему, не такой уж глупый, если имеются в виду
ChromeMessageBroadcaster.loadFrameScript()
и ParentProcessMessageManager.loadProcessScript()
Увы, в обозримом прошлом и настоящем,
мне как-то не попадалось ничего об их судьбе, так что сказать нечего.
возможно переделать в JS скрипт сохранения страниц SingleHTML.JSM
А вот это уже глупее.
Вместо простой конвертации в ESM, нахлобучиться затеять такую затею.
Кстати, а чего там win до сих пор торчит? fp.init(win, "", fp.modeSave);
Разве это не видел?
Для Fifefox 125+ вместо win уже нужен win.browsingContext
Vitaliy V.
Просто информационное сообщение, раз уж на глаза попалось.
Bug 1884792 - Consider making -moz-lwtheme a media query.
Удаление псевдокласса :-moz-lwtheme уже пошло в autoland,
а он используется в vertical_top_bottom_bar.css в двух местах.
Отсутствует
Для Fifefox 125+ вместо win уже нужен win.browsingContext (Bug 1878401)
А как сделать, чтоб скрипт SingleHTML.mjs был совместим с FF115+ …… FF125+ ?
не понимаю, как этот win.browsingContext прописывать! SingleHTML править или из других скриптов вызывать иначе ?
1) fp.init(win || win.browsingContext, "", fp.modeSave);
2) Cu.getGlobalForObject(Cu)[Symbol.for("SingleHTML")](to, window.browsingContext)
посмотрел custom_buttons-0.0.7.0.0.33-fx. У тебя в SelfHelper.jsm:
picker(doc) { ……… var win = doc.ownerGlobal;
в SingleHTML (почти весь код твой): async SingleHTML(to, win = this.ownerGlobal)
/* SingleHtml © Лекс, правка Dumby, mod Dobrov вызов: Cu.getGlobalForObject(Cu)[Symbol.for("SingleHTML")](to, window) scriptsbackground [System Principal], «to» пуст: выбор пути */ var self, name = "SingleHTML", EXPORTED_SYMBOLS = [name + "Child"]; var {io, focus, obs, prefs, dirsvc} = globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services; export class SingleHTMLChild extends JSWindowActorChild { //класс = name + Child 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); globalThis[Symbol.for(name)] = this.SingleHTML; //общие функции globalThis[Symbol.for('TitlePath')] = this.TitlePath; }, 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.setAttribute("label", "Всё или выбранное в единый HTML"); var before = "appMenu-save-file-button2", subviewbutton = "subviewbutton"; btn.className = subviewbutton; btn.setAttribute("oncommand", "SingleHTML();"); btn.SingleHTML = this.SingleHTML; popup.querySelector('toolbarbutton[id^="'+ before +'"]').before(btn); }, unload(e) { var win = e.target.ownerGlobal; win.removeEventListener("unload", this); win.document.getElementById("appMenu-popup").removeEventListener("popupshowing", this); }, TitlePath(win, to, f, u, n = 0, h = 99) { //global if(parseInt(to) > 0) [n,to] = [to,n]; if(parseInt(to) < 0) h = Math.abs(to); if (typeof(to) != 'string' || !/.*\|/.test(to)) to = prefs.getStringPref("extensions.user_chrome_files.savedirs","|||0"); to = to.split('|').slice(0 + n, 2 + n); //Dir/Sub|[empty|0 title|1 url] f ||= win.gBrowser.selectedTab.label; f = f.replace(/\s+/g,' ').replace(/[\\\/?*\"'`]+/g,'').replace(/[|<>]+/g,'_').replace(/:/g,'։').slice(0,h).trim(); u ||= decodeURIComponent(win.gURLBar.value); n = f, h = u; u = /^file:\/\//.test(u) ? 'file' : u.replace(/^.*u=|https?:\/\/|www\.|\/.*/g,'').replace(/^ru\.|^m\./,'').replace(/\/.*/,''); to[1] = (to[1] == "0") ? f : (to[1] == "1") ? u : ""; f += "_"+ new Date().toLocaleDateString('ru', {day: 'numeric',month: 'numeric',year: '2-digit'}) +'-'+ new Date().toLocaleTimeString('en-GB').replace(/:/g,"։"); //дата-часы try {var dir = dirsvc.get("DfltDwnld",Ci.nsIFile);} catch {dir = prefs.getComplexValue("browser.download.dir",Ci.nsIFile)} var map = l => win.DownloadPaths.sanitize(l); //FIX имён to.map(map).forEach(dir.append); to = dir.clone(); to.append(f +'.html'); return [dir, to.path, n, f, h, u]; //… имя, +дата, URL, домен }, async Succes(win, dir, dw = true, bg) { var {setTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm"); var d = await win.Downloads.createDownload({source: "about:blank",target: win.FileUtils.File(dir)}); (await win.Downloads.getList(win.Downloads.ALL)).add(d); if (dw) await d.refresh(d.succeeded = true); //flash DWButton d = win.document.getElementById('urlbar-input-container'); d.style.background = dw ? 'rgba(0,200,0,0.3)' : 'rgba(250,0,0,0.2)'; setTimeout(() => {d.style.removeProperty('background-color')}, 350); }, async SingleHTML(to, win = this.ownerGlobal) { var br = win.gBrowser.selectedBrowser, bc = focus.focusedContentBrowsingContext; if (bc?.top.embedderElement != br) bc = br.browsingContext; var actor = bc?.currentWindowGlobal?.getActor(name); actor && self.save(win, ...await actor.sendQuery(""), to); //htmlAndName }, async save(win, data, fname, host, to) { var path = this.TitlePath(win, to, fname, host); //путь в зависимости от опций var dir = path[0], path = path[1]; dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); if (!to) { // диалог выбора папки var fp = Cc['@mozilla.org/filepicker;1'].createInstance(Ci.nsIFilePicker); fp.init(win, "", fp.modeSave); fp.defaultString = path.split(/.*[\/|\\]/)[1]; fp.appendFilters(fp.filterHTML); fp.appendFilters(fp.filterAll); var res = await new Promise(fp.open); if (res == fp.returnOK || res == fp.returnReplace) path = fp.file.path else return; } this.write(path, data); //нужна проверка на ошибки записи await this.Succes(win, path); }, write(path, html) { //без Ff 79-84 в save() IOUtils.writeUTF8 вместо this.write 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 => { //не сохраняет SVG var resolveURL = function (url, base) { try { return io.newURI(url, null, io.newURI(base)).spec;} catch {} }, getSelWin = function (w) { if (w.getSelection().toString()) return w; for (var i = 0, f, r; f = w.frames[i]; i++) { try { if (r = getSelWin(f)) return r;} catch(e) {} } }, encodeImg = function (src, obj) { var canvas, img, ret = src; if (/^https?:\/\//.test(src)) { canvas = doc.createElement('canvas'); if (!obj || obj.nodeName.toLowerCase() != 'img') { img = doc.createElement('img'); img.src = src; } else img = obj; if (img.complete) try { canvas.width = img.width; canvas.height = img.height; canvas.getContext('2d').drawImage(img, 0, 0); ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png')); } catch (e) {}; if (img != obj) img.src = 'about:blank'; }; return ret; }, toSrc = function (obj) { var strToSrc = function (str) { var chr, ret = '', i = 0, meta = {'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','\x22':'\\\x22','\\':'\\\\'}; while (chr = str.charAt(i++)) { ret += meta[chr] || chr; }; return '\x22' + ret + '\x22'; }, arrToSrc = function (arr) { var ret = []; for (var i = 0; i < arr.length; i++) { ret[i] = toSrc(arr[i]) || 'null'; }; return '[' + ret.join(',') + ']'; }, objToSrc = function (obj) { var val, ret = []; for (var prop in obj) { if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val); }; return '{' + ret.join(',') + '}'; }; switch (Object.prototype.toString.call(obj).slice(8, -1)) { case 'Array': return arrToSrc(obj); case 'Boolean': case 'Function': case 'RegExp': return obj.toString(); case 'Date': return 'new Date(' + obj.getTime() + ')'; case 'Math': return 'Math'; case 'Number': return isFinite(obj) ? String(obj) : 'null'; case 'Object': return objToSrc(obj); case 'String': return strToSrc(obj); default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null'; } }, selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location, ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g; if (selWin) { var rng = win.getSelection().getRangeAt(0); pEle = rng.commonAncestorContainer; ele = rng.cloneContents(); } else { pEle = doc.documentElement; ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true); }; while (pEle) { if (pEle.nodeType == 1) { clone = pEle.cloneNode(false); clone.appendChild(ele); ele = clone; }; pEle = pEle.parentNode }; var sel = doc.createElement('div'); sel.appendChild(ele); for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) { el = all[i]; if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) { if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href); return prev + encodeImg(url) + next; }); switch (el.nodeName.toLowerCase()) { case 'link': case 'style': case 'script': el.parentNode.removeChild(el); break; case 'a': case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break; case 'img': case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break; case 'audio': case 'video': case 'embed': case 'frame': case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break; case 'object': if (el.hasAttribute('data')) el.data = el.data; break; case 'form': if (el.hasAttribute('action')) el.action = el.action; break; } }; var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets; 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); }; 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 onlyName = selWin ? win.getSelection().toString() : (title && title.text ? title.text : loc.pathname.split('/').pop()); return [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', onlyName, loc.hostname]; }
Отсутствует
не понимаю, как этот win.browsingContext прописывать!
.browsingContext нужен только для случая Cc['@mozilla.org/filepicker;1'].createInstance(Ci.nsIFilePicker);, посему я придумал такой вариант:
// fp.init(win fp.init((parseInt(Services.appinfo.platformVersion) >= 125 ? win.browsingContext : win)
Жизнь иногда такое выкидывает, что хочется подобрать...
На форуме
Dumby
Пока работает, но если не трудно, переделайте пожалуйста JSM'ку в MJS.
// // Открывать ссылки длинным кликом .......... // Dumby: https://forum.mozilla-russia.org/viewtopic.php?pid=797864#p797864 ..... // var delay = 500; // время удержания в мс var inBackground = false; // открывать в фоновой вкладке var relatedToCurrent = false; // открывать рядом с related вкладкой var name = "LPA", EXPORTED_SYMBOLS = [name + "Child", name + "Parent"]; var u = {get timer() { delete this.timer; return this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); }}; inBackground == null ? Object.defineProperty(u, "bg", { get: Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch) .getBoolPref.bind(null, "browser.tabs.loadInBackground") }) : u.bg = inBackground; if (!ChromeUtils.domProcessChild.childID) { var triggeringPrincipal = Cu.getObjectPrincipal(Cu); var LPAParent = class extends JSWindowActorParent { receiveMessage(msg) { var [link, inBackground, ref] = msg.data; this.manager.browsingContext.top .embedderElement.ownerGlobal.gBrowser.addTab(link, { triggeringPrincipal, relatedToCurrent, inBackground, }); } } ChromeUtils.registerWindowActor(name, { allFrames: true, parent: {moduleURI: __URI__}, messageManagerGroups: ["browsers"], child: {moduleURI: __URI__, events: {mousedown: {}}} }); } class LPAChild extends JSWindowActorChild { handleEvent(e) { if (e.type == "mousedown") { if (e.button || e.shiftKey || e.altKey || e.detail != 1) return; var a = e.originalTarget.closest("a[href]"); if (!a || a.href.startsWith("javascript:")) return; this.ctrl = e.ctrlKey; this.initLongPress(a); } else e.type == "click" && !this.link && e.preventDefault(), this.destroyLongPress(); } timeout() { var bg = this.ctrl != u.bg; var data = [this.link.href, bg]; this.sendAsyncMessage("", data); this.link = null; bg || this.destroyLongPress() //|| this.contentWindow.windowUtils.sendMouseEventToWindow("mouseup", -1, -1, 0, 1, 0); // Linux (?) } initLongPress(a) { this.contentWindow.addEventListener("click", this, true); this.contentWindow.addEventListener("dragstart", this, false); u.timer.initWithCallback(() => this.timeout(), delay, u.timer.TYPE_ONE_SHOT); this.link = a; } destroyLongPress() { this.contentWindow.removeEventListener("click", this, true); this.contentWindow.removeEventListener("dragstart", this, false); this.link && u.timer.cancel(); this.link = null; } didDestroy() { this.link && this.destroyLongPress(); } }
«The Truth Is Out There»
Отсутствует
Не понял. Если сделал как написано в первой строке
конвертированного скрипта — значит изменился.
Сделал давно, и уже забыл.
Хорошо, попробую.
Спасибо, на 115esr работают.
ChromeUtils.importESModule("……….mjs"); - это вариант импорта? Типа так -
(async url => ChromeUtils.importESModule("chrome://user_chrome_files/content/custom_scripts/.....mjs"
));
Отсутствует
.browsingContext нужен только для случая Cc['@mozilla.org/filepicker;1'].createInstance(Ci.nsIFilePicker);
Тут — да.
Но, в общем случае, наверно стоит сказать,
что, теоретически, где-то ещё,
может использоваться функция окна браузера, именуемая makeFilePicker()
// fp.init(win
fp.init((parseInt(Services.appinfo.platformVersion) >= 125 ? win.browsingContext : win)
Да, суть выражена верно.
Разве что заключение выражения
образующего первый аргумент для fp.init()
в круглые скобки здесь представляется избыточным.
А как сделать, чтоб скрипт SingleHTML.mjs был совместим с FF115+
Не вижу какой-то несовместимости с FF115+
Наоборот, для такой совместимости можно кое-что зачистить, например:
Там, где Services, просто сразу пишем Services.
Метод write() подлежит удалению,
тогда, в save(), как и прокомментировано, «IOUtils.writeUTF8 вместо this.write».
Модуль Timer.jsm в 115 уже сконвертирован в Timer.sys.mjs
можно сразу использовать ChromeUtils.importESModule()
Но, нужен ли этот импорт как таковой?
В методе Succes() мы имеем win,
и с него уже берутся win.FileUtils и win.Downloads
Вот точно так же, вместо setTimeout можно написать win.setTimeout
и ничего не импортировать.
переделайте пожалуйста JSM'ку в MJS
Чисто формально, достаточно заменить парочку
moduleURI: __URI__
на
esModuleURI: Components.stack.filename
и перед class LPAChild extends JSWindowActorChild {
добавить
export {LPAParent};
export
Но сам код какой-то неполноценный,
весьма нуждается в некоем переосмыслении.
Типа так -
(async url => ChromeUtils.importESModule("chrome://user_chrome_files/content/custom_scripts/.....mjs"
));
Так ты определил асинхронную функцию импрота модуля.
Вникуда и внизачем.
Но если добавить перед оконечной точкой с запятой «;»
круглые скобки «()», оператор вызова этой функции,
то импорт произойдёт.
url тоже можно заменить на круглые скобки, раз не используется. (не обязательно)
Vitaliy V.
Ну вот, с кода
@supports selector(:-moz-lwtheme) { * { color: green !important; } } @supports not selector(:-moz-lwtheme) { * { color: red !important; } }
Отсутствует
Чисто формально, достаточно заменить парочку
moduleURI: __URI__
на
esModuleURI: Components.stack.filenameи перед class LPAChild extends JSWindowActorChild {
добавить
export {LPAParent};export
Но сам код какой-то неполноценный,
весьма нуждается в некоем переосмыслении.
Dumby, большое спасибо. Всё работает.
«The Truth Is Out There»
Отсутствует
Dumby
У меня сейчас так
(async url => ChromeUtils.importESModule(url))("chrome://user_chrome_files/content/custom_scripts/....mjs"
);
вы выше написали про встроенный метод, вот я и подумал, что это пример импорта.
Вы как то делали переключалку доп панелей, так она теперь по ПКМ еще и меню вызывает, можете поправить?
Отсутствует
Dumby
Вот эту JSM'ку не поможете переделать в MJS?
// // Кнопка сохраняет страницу с картинками или её часть, если она выделена, в html одним файлом .......... // Dumby: https://forum.mozilla-russia.org/viewtopic.php?pid=796993#p796993 ..... // var name = "UCFSaveSnapshotToHTML", EXPORTED_SYMBOLS = [name + "Child"]; if (!ChromeUtils.domProcessChild.childID) { ChromeUtils.import("resource:///modules/CustomizableUI.jsm").CustomizableUI.createWidget({ label: "Cохранить страницу в html", tooltiptext: "Cохранить страницу в html", id: "ucf_SaveSnapshotToHTML", localized: false, onCreated(btn) { btn._handleClick = click; btn.setAttribute("image", "chrome://devtools/skin/images/tool-application.svg"); } }); var click = async function() { var win = this.ownerGlobal; var bc = win.gBrowser.selectedBrowser.browsingContext; var fbc = win.Services.focus.focusedContentBrowsingContext; if (!fbc || fbc.top.id != bc.id) fbc = bc; var cwg = fbc.currentWindowGlobal; var fp = picker(win, "", Ci.nsIFilePicker.modeSave); var [fileContent, fileName] = await cwg.domProcess.getActor(name).sendQuery("", cwg.innerWindowId); fp.defaultString = fileName; fp.appendFilters(fp.filterHTML); fp.appendFilters(fp.filterAll); await new Promise(fp.open) != fp.returnCancel && IOUtils.writeUTF8(fp.file.path, fileContent, {mode: "overwrite"}); } var picker = (...args) => { ChromeUtils.registerProcessActor(name, { includeParent: true, child: {moduleURI: __URI__} }); return (picker = Components.Constructor( "@mozilla.org/filepicker;1", "nsIFilePicker", "init" ))(...args); } } class UCFSaveSnapshotToHTMLChild extends JSProcessActorChild { receiveMessage(msg) { return snap(WindowGlobalChild.getByInnerWindowId(msg.data).browsingContext.window); } } var snap = mainWin => { var resolveURL = function (url, base) { try { return (new URL(url, base)).href; } 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(/[\.]+/ig, ' ').replace(/[\,]+/ig, ' ').replace(/[\']+/ig, ' ').replace(/^\s+|\s+$/g, '').replace(/\s+/g, '_').slice(0, 100); fileName += (function () { var d = new Date(), z = function(n){return (n < 10 ? '0' : '') + n}; return '_' + '[' + z(d.getFullYear()) + '_' + z(d.getMonth()+1) + '_' + z(d.getDate()) + '\u2014' + 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]; }
«The Truth Is Out There»
Отсутствует
unter_officer на предыдущей странице SingleHTML.mjs сохраняет страницу или выделенное.
Ваш код добавляет пункт меню "Всё или выбранное в единый HTML" в "гамбургер".
Я "гамбургером" не пользуюсь, он у меня скрыт. Поэтому мне это не подходит.
«The Truth Is Out There»
Отсутствует
unter_officer - код универсальный, вы прочитали середину, но не увидели команду вызова в начале!
Добавить в любую кнопку: Cu.getGlobalForObject(Cu)[Symbol.for("SingleHTML")](true,window);
Кроме того, SingleHTML.mjs позволяет 1) сохранять страницу с выбором папки или автоматически.
2) сохранять в любые папки/подпапки, используя имя вкладки или домен. Пишем "Сайт||Фото|" в extensions.user_chrome_files.savedirs:
сохранение Html/Pics. [Загрузки]/"_Html/subdir|_Pics/subdir" «subdir: пусто | 0 имя страницы | 1 домен»
3) пути/имена страниц меняются на лету из кнопки настроек скрипта ucf_hookClicks.js или правкой extensions.user_chrome_files.savedirs.
Vitaliy V. может сможешь решить давнюю проблему скрипта SingleHTML.mjs ?
Этот код не сохраняет SVG-графику, но работает быстрее, чем дополнение SingleFile и сохраняет страницу более компактно.
https://meteo7.ru/forecast/59003
Отредактировано Dobrov (21-03-2024 04:31:28)
Отсутствует
Dobrov
Извините, но мне нужна простая кнопка без всяких наворотов, в которой, в случае необходимости, я хоть что-то могу попытаться понять, а не такой комбайн.
И уж тем более без установки дополнительных скриптов в виде ucf_hookClicks.js или подобных.
«The Truth Is Out There»
Отсутствует
Вы как то делали переключалку доп панелей, так она теперь по ПКМ еще и меню вызывает, можете поправить?
Чтобы далеко не ходить, заменил image на "about:logo".
Сунул код в 126.0a1, жму ПКМ — верхний тулбар переключается.
Но никакого «еще и меню» при этом увидеть не сподобился.
Таким образом, ответ — нет, я не могу поправить то, что не сломано.
Извините, но мне нужна простая кнопка
Да, но сам принцип конвертации от этого же не меняется.
Всё тот же esModuleURI: Components.stack.filename вместо moduleURI: __URI__
Всё та же инструкция export перед class
И ChromeUtils.importESModule("resource:///modules/CustomizableUI.sys.mjs")
вместо ChromeUtils.import("resource:///modules/CustomizableUI.jsm")
EXPORTED_SYMBOLS тогда долой (не обязательно).
Ну, и picker(win.browsingContext вместо picker(win если 125+
Отсутствует
Да, но сам принцип конвертации от этого же не меняется.
Dumby, большое спасибо. Всё получилось.
Вы как то делали переключалку доп панелей, так она теперь по ПКМ еще и меню вызывает, можете поправить?
_zt, вы смогли понять, когда начинает появляться контекстное меню?
Дело в том, что я за последние три недели дважды сталкивался с таким поведением этого скрипта, но причину так и не понял.
То есть, всё нормально работает 5-7-10 дней и вдруг, ни с того ни с сего, появляется контекстное меню. Перезапустишь браузер и опять всё нормально работает.
Отредактировано unter_officer (21-03-2024 12:10:06)
«The Truth Is Out There»
Отсутствует
Dumby
А вы потаскайте кнопку по панелям. UCF последний.
unter_officer
Нет не смог, сначала думал, что после перемещения кнопок.
Я вернулся на свой первоначальный вариант, он рабочий - 16-12-2021 02:14:46
Vitaliy V.
У меня есть одна проблема с вертикальной панелью нового UCF. На ней расположено два растягивающихся интервала - 1. сначала кнопки скриптов и расширений, 2. потом интервал, 3. потом кнопки ATB - вверх и вниз страницы через обычный пробел, 4. потом интервал и 5. еще пара кнопок. Так вот, две последние кнопки от расширений, из первого блока кнопок, постоянно сваливаются вниз, в позицию между кнопками ATB. Происходит это после скрытия/показа панели любым из скриптов выше.
Может сделаете переключалку панелей одной кнопкой, без подобных спецэффектов?
Отсутствует
_zt
Я порылся в своих архивах и нашел вот такой вариант, только не помню где я его взял.
Он почти такой же как этот, за исключением пары строчек.
(async () => CustomizableUI.createWidget({ id: "additional-toolbars-button", label: "Панели", tooltiptext: "ЛКМ: Переключить верт. панель\nПКМ: Переключить доп. панель", localized: false, // defaultArea: CustomizableUI.AREA_NAVBAR, onCreated(btn) { btn.setAttribute("context", false); btn.setAttribute("image", "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' height='16' width='16' viewBox='0 0 16 16'><g><rect x='0' y='0' width='16' height='16' rx='1' ry='1' style='fill:rgb(0, 120, 173);'/><path style='fill:white;' d='M 2.5,1 C 1.7,1 1,1.7 1,2.5 V 13.5 C 1,14.3 1.7,15 2.5,15 H 13.5 C 14.3,15 15,14.3 15,13.5 V 2.5 C 15,1.7 14.3,1 13.5,1 Z M 3,2 H 13 C 13.7,2 14,2.3 14,3 V 13 C 14,13.7 13.7,14 13,14 H 3 C 2.3,14 2,13.7 2,13 V 3 C 2,2.3 2.3,2 3,2 Z M 7.3,3.03 C 7.11,3.03 6.95,3.2 6.95,3.4 V 4.09 C 6.6,4.18 6.28,4.34 5.96,4.5 L 5.45,3.99 C 5.39,3.93 5.3,3.9 5.23,3.9 5.14,3.9 5.04,3.93 4.95,3.99 L 3.99,4.98 C 3.84,5.1 3.85,5.33 3.99,5.49 L 4.5,5.96 C 4.31,6.28 4.18,6.6 4.09,6.95 H 3.37 C 3.17,6.95 3.01,7.11 3.01,7.3 V 8.67 C 3.01,8.89 3.17,9.05 3.37,9.05 H 4.09 C 4.18,9.4 4.31,9.72 4.5,10 L 3.99,10.5 C 3.85,10.7 3.84,10.9 3.99,11 L 4.95,12 C 5.1,12.1 5.33,12.1 5.45,12 L 5.96,11.5 C 6.28,11.7 6.6,11.8 6.95,11.9 V 12.6 C 6.95,12.8 7.11,13 7.3,13 H 8.7 C 8.89,13 9.05,12.8 9.05,12.6 V 11.9 C 9.4,11.8 9.72,11.7 10,11.5 L 10.5,12 C 10.7,12.1 10.9,12.1 11.1,12 L 12,11 C 12.2,10.9 12.2,10.7 12,10.5 L 11.5,10 C 11.7,9.72 11.8,9.4 11.9,9.05 H 12.6 C 12.8,9.05 13,8.89 13,8.67 V 7.3 C 13,7.11 12.8,6.95 12.6,6.95 H 11.9 C 11.8,6.6 11.7,6.28 11.5,5.96 L 12,5.49 C 12.2,5.33 12.2,5.1 12,4.98 L 11.1,3.99 C 10.9,3.86 10.7,3.86 10.5,3.99 L 10,4.5 C 9.72,4.34 9.4,4.18 9.05,4.09 V 3.4 C 9.05,3.2 8.89,3.03 8.7,3.03 Z M 8,6.5 C 8.8,6.5 9.5,7.2 9.5,8 9.5,8.8 8.8,9.5 8,9.5 7.2,9.5 6.5,8.8 6.5,8 6.5,7.2 7.2,6.5 8,6.5 Z'/></g></svg>"); }, 0: "ucf-additional-vertical-bar", 2: "ucf-additional-top-bar", onClick(e) { var id = this[e.button]; id && CustomizableUI.setToolbarVisibility(id, e.target.ownerDocument.querySelector('#' + id).collapsed); } }))();
Отредактировано unter_officer (21-03-2024 20:25:24)
«The Truth Is Out There»
Отсутствует
Я вернулся на свой первоначальный вариант, он рабочий
Насколько понимаю, для ЛКМ эти две строки не нужны. Они только для ПКМ, чтобы меню не появлялось.
e.preventDefault();
e.stopPropagation();
Превент может использоваться для предотвращения появления контекстного
меню при ПКМ (Windows)
Это сказано по ссылке в посте №1381 (выше).
Отсутствует
А вы потаскайте кнопку по панелям.
Потаскал. По этим четырём.
#toolbar-menubar, #TabsToolbar-customization-target,
#nav-bar-customization-target, #PersonalToolbar.
И после каждого такого потаскушества, честно жал ПКМ.
Но никакого контекстного меню увидеть так и не смог.
Тем не менее, любая осознанная модификация кода
всегда только приветствуется, будь то
btn.setAttribute("context", false) или btn.setAttribute("context", "")
или, даже пожёстче, btn.setAttribute("oncontextmenu", "return false");
Отсутствует
И после каждого такого потаскушества, честно жал ПКМ.
Но никакого контекстного меню увидеть так и не смог.
Dumby, да тут и правда мистика какая-то.
Я этим скриптом пользуюсь где-то года два и никогда проблем не возникало, от слова совсем.
Первый раз я словил контекстное меню дней 20-25 назад. Причем я никаких кнопок не таскал, просто при очередном открытии доп.панели появилось меню.
Подумал, ну чего с FF не бывает, перезагрузил браузер и забыл про это. А позавчера вдруг опять словил этот глюк.
А тут оказывается и _zt описывает туже проблему, с тем же скриптом.
Странно как-то всё это. И в тоже время хочется понять, что это было.
P.S. В общем посмотрю как в дальнейшем будет работать скрипт, который я выложил чуть выше.
«The Truth Is Out There»
Отсутствует
Может попробуете у себя?
Не стал, так как вперед попробовал предложенное Dumby btn.setAttribute("oncontextmenu", "return false");
проблема исчезла. Значит дело именно в этой строке.
Еще б разобраться со скачками кнопок на вертикальной панели, после ее скрытия.
Отсутствует
Еще б разобраться со скачками кнопок на вертикальной панели, после ее скрытия.
Вот с этим у меня пока проблем нет.
«The Truth Is Out There»
Отсутствует
Вопрос: чем заменить Services.wm.getMostRecentWindow("navigator:browser") на что-то попроще?
Хочу вызывать функцию globalThis[Symbol…… из другого js-кода без аргументов, но выдаёт ошибку win.gBrowser is undefined, если запускать так:
Cu.getGlobalForObject(Cu)[Symbol.for("SingleHTML")]();
пробовал разные варианты, пока в SingleHTML.mjs вписал такой второй аргумент по-умолчанию:
SingleHTML(to = false, win = this.ownerGlobal || Services.wm.getMostRecentWindow("navigator:browser")) {…
Отсутствует