Win 7
115.1.0esr (64-разрядный), с portableapps.
Антиподписячий код от 2022-10-19 Это ведь последний?
Больше никакого кода в mozilla.cfg нет. Кодировка не менялась.
«Ошибка при чтении файла настроек. Обратитесь к системному администратору.»
Что со мной не так, почему у остальных работает?
UPD: Вот я тормоз — это FoxReplace некстати сработал, код заменил!
Отредактировано negodnik (04-08-2023 16:25:07)
Отсутствует
Подскажите какой-либо приём сократить несколько команд с одним идентификатором аналогично VBS ? (то есть, не писать gBrowser)
With gBrowser
.selectAllTabs() : .reloadMultiSelectedTabs() : .clearMultiSelectedTabs()
End With
gBrowser.selectAllTabs(); gBrowser.reloadMultiSelectedTabs(); gBrowser.clearMultiSelectedTabs(); // этот вариант НЕ нужен: var g = gBrowser; g.selectAllTabs(); g.reloadMultiSelectedTabs(); g.clearMultiSelectedTabs();
Отсутствует
Dumby - Спасибо, слона и не заметил!
Вопрос по закрытию вкладки под указателем мыши (т.е. не обязательно текущая вкладка).
Имеется trg = объект под мышью. Сделал перебором на уровень выше, до tabbrowser-tab. Посоветуйте способ попроще?
"#tabbrowser-tabs": { 128(trg){ // СКМ for (let i = 0; i < 9; i++) // вкладка под курсором мыши if (trg.className == "tabbrowser-tab") { gBrowser.removeTab(trg); break;} else trg = trg.parentNode; }, }, // перебор tab-close-button, tab-text tab-label, tab-label-container и прочих…
Отсутствует
Dumby - вопрос по твоему старому коду перехвата кликов мыши: я запутался в this.selectors.filter(…
В объекте кликов имена ключей начинаются с «#», этот символ мешает, если вместо имени использовать переменную: [variable]: свойство
Просьба убрать символ решётки из имён ключей, но учитывая, что кроме .id ещё нужно имя класса, например:
".titlebar-button.titlebar-close": { // так работает, а без решётки нет: "downloads-button": {………
1(){ Help()},………
var trg = e.target, id = trg.id || trg.className || trg.tagName; // дополнил строку из твоего кода
Твой код при наведении мыши на объекты возвращает имена, не содержащие решётки или точки: .id .className .tagName
Удобнее вбить эти же имена в data ( …… чтобы не писать кучу повторных строк с # решёткой, а юзать переменные: [variable]: свойство
// https://github.com/VicDobrov/UserChromeFiles/blob/main/profile_ucf_dobrov/chrome/user_chrome_files/custom_scripts/ucf_hookClicks.js Tag = { // Hints "titlebar-button titlebar-close": `Закрыть Firefox ◉ колёсико вернуть вкладку ◧ держать краткая Справка`, }; Mouse = { // Clicks "#urlbar-input": { // здесь решётка, а в объекте mouse Over таких префиктов нет 2(trg, forward){ trg.value = ""}, // clear urlbar }, ".titlebar-button.titlebar-close": { 1(){ Help()}, }, }; Over = { // при наведении мыши обновить Hints и строку статуса get "urlbar-input"() { glob.toStatus(Tag["#"].split('|')[0], 2500); }, "titlebar-button titlebar-close": Tag["title-close"], }; listener = { // Действия мыши и перехват существующих, если CapsLock выключен. без doubleclick find(sel) { return Mouse[sel][this] || Mouse[sel][this + 1]; }, filter(sel) { return this.closest(sel); }, handleEvent(e) { if (this.skip || e.detail > 1 || e.getModifierState("CapsLock")) return; var trg = e.target, id = trg.id || trg.className || trg.tagName; if (e.type == "mouseenter") { var hint = Over[id] || Over[(trg = trg.parentNode).id]; if (hint) trg.tooltipText = hint; return; //обновить подсказку } var sels = this.selectors.filter(this.filter, trg); var {length} = sels; if (!length) return; var wheel = e.type.startsWith("w"); var num = e.metaKey*64 + e.ctrlKey*32 + e.shiftKey*16 + e.altKey*8 + (wheel ? 2 : e.button*128); //dbl*4 var obj = Mouse[ length > 1 && sels.find(this.find, num) || sels[0] ]; if (wheel) return obj[num]?.(trg, e.deltaY < 0); // mousedown if (e.type.startsWith("m")) { obj.mousedownTarget && this.stop(e); this.longPress = false; //+ задержка при обычном клике if (++num in obj) this.mousedownTID = setTimeout(this.onLongPress, 640, trg, obj, num); if (e.button == 2) this.ctx = trg.getAttribute("context"), trg.setAttribute("context",""); return; } obj.mousedownTarget || this.stop(e); //click if (this.longPress) return this.longPress = false; this.mousedownTID &&= clearTimeout(this.mousedownTID); if (!obj[num]) { if (e.button == 1) return; if (e.button) { num = "context"; for(var p in this.a) this.a[p] = e[p]; } else num = "dispatch", this.mdt = obj.mousedownTarget; obj = this; } obj[num](trg); //run }, get selectors() { this.onLongPress = (trg, obj, num) => { this.mousedownTID = null; this.longPress = true; obj[num](trg); } delete this.selectors; return this.selectors = Object.keys(Mouse); }, get mdEvent() { delete this.mdEvent; return this.mdEvent = new MouseEvent("mousedown", {bubbles: true}); }, context(trg) { this.ctx ? trg.setAttribute("context", this.ctx) : trg.removeAttribute("context"); trg.dispatchEvent(new MouseEvent("contextmenu", this.a)); }, dispatch(trg) { this.skip = true; this.mdt ? trg.dispatchEvent(this.mdEvent) : trg.click(); this.skip = false; }, stop: e => { e.preventDefault(); e.stopImmediatePropagation(); }, a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0} }
Отредактировано Dobrov (12-08-2023 08:21:54)
Отсутствует
чтобы не писать кучу повторных строк с # решёткой, а юзать переменные: [variable]: свойство
Ну, если это прям так принципиально, то решётку можно и дописать,
типа завести функцию, которая будет возвращать
trg.id && "#" + trg.id || trg.className && "." + [...trg.classList].join(".") || trg.tagName
и вызывать её внутри блока if (e.type == "mouseenter") {…}
Кстати, заканчиваться код блока должен инструкцией return;
ведь исполнять дальнейший код по "mouseenter" не нужно.
Отсутствует
если это прям так принципиально, то решётку можно и дописать
Не пойдёт! Надо решётку убрать из всех ключей, а не дописывать!
Сделал иначе, и работает, НО не понял, когда выполняется функция find(sel) ?
Подскажи, как вызвать find(sel) чтоб её отладить ? Или можно лишь sels[0] оставить ?
Ещё в моём варианте прокрутка вкладок колёсиком над вкладками не пашет, а только на пустой области #tabbrowser-tabs. В прежнем коде работает везде!
// var sels = this.selectors.filter(this.filter, trg); ВМЕСТО этой строки 4 следующих: var sels = this.selectors.filter( (sel, p) => { // виртуальное добавление # или . return trg.closest((trg.id && "#" || trg.className && "." || "") + sel); }); find(sel) { // при каких условиях выполняется ? return data[sel][this] || data[sel][this + 1]; }, handleEvent(e) { …………… var obj = data[ // length > 1 когда выполняется ??? length > 1 && sels.find(this.find, num) || sels[0]];
Отредактировано Dobrov (13-08-2023 06:57:58)
Отсутствует
Надо решётку убрать из всех ключей, а не дописывать!
Тогда, наверно, вместо closest() надо будет использовать
что-нибудь самописное, которое так и будет проверять от trg вверх
безрешёточный id, затем бесточечное класс-добро, затем имя тэга.
когда выполняется функция find(sel) ?
this.selectors — это массив всех прописанных селекторов.
Через его метод filter() из него создаётся новый массив sels,
в котором остаются только те селекторы, под которые подпадает
сам trg или какой-либо из его родительских элементов.
Если таких селекторов в массиве sels получится больше одного,
тогда и выполняется функция find(sel)
Подскажи, как вызвать find(sel)
Как-то так: this.find.call(myThis, myArg);
только на пустой области #tabbrowser-tabs
Ну правильно,
trg.closest((trg.id && "#" || trg.className && "." || "") + sel)
это именно точное соответствие trg и псевдо-селектора sel,
а не поиск от trg вверх.
Не говоря уже о том, что просто прицепить точку к началу className
не прокатит, если классов несколько, а не один.
Отсутствует
Тогда, наверно, вместо closest() надо будет использовать
что-нибудь самописное, которое так и будет проверять от trg вверх
безрешёточный id, затем бесточечное класс-добро, затем имя тэга.
А можешь что-то конкретное решение предложить ? Я изменил только filter(sel) {… и всё развалилось!
Убрал решётку на входе функции и в ней пробовал авто-подставить "#" и "." - по идее всё должно работать также, но не пашет…
var sels = this.selectors.filter((sel) => { var d = trg.id && "#" || trg.className && "." || sel; // здесь по твоим советам различные варианты пробовал… return trg.closest(d + sel); });
Ещё запасной вариант: берём твой код и из data {} удаляем решётки, затем к каждому ключу добавляем "#" и "."
Выгода в том, что много id-ключей берутся из массива подсказок, экономя на каждый несколько повторов строк.
Отредактировано Dobrov (13-08-2023 13:21:26)
Отсутствует
А можешь что-то конкретное решение предложить ?
Нет, не могу.
Я просто не понимаю конечный смысл.
А перевести на JS то, что процитировано, могу попробовать
Вот, например, изменил метод filter()
/* filter(sel) { return this.closest(sel); }, */ filter(sel) { var node = this; do { if (node.id == sel || node.className == sel) return true; } while(node = node.parentNode /*node.flattenedTreeParentNode*/); },
Отсутствует
Dumby - Спасибо!
Свой способ сделал - объём кода поменьше (как выше писал), но и твоя разработка пригодиться!
Mouse = {}; Object.keys(data).forEach((k) =>{Mouse["#"+ k] = Mouse["."+ k] = data[k]});
А c кнопкой "Закрыть" в заголовке окна titlebar-button.titlebar-close мой способ работает, твой нет…
Я просто не понимаю конечный смысл.
Имена кликабельных объектов используются без "#" и ".", поэтому можно сократить код!
Tag { "ToggleButton": "подсказка кнопки…"}; var Bt = Object.keys(Tag); // список часто используемых имён (async (id) => CustomizableUI.createWidget({ label: id, id: id, tooltiptext: Tag[id], ……… создание кнопки }))(Bt[0]); Over = { //подсказка при наведении указателя мыши, инфа в строке статуса… get [Bt[0]]() {return Tag[Bt[0]]} Mouse = { [Bt[0]]: { …… // обработка кликов
Отредактировано Dobrov (15-08-2023 13:22:25)
Отсутствует
Dumby Я прочитал про Services.jsm, но не пойму чем заменить в этой кнопке "global", так как она завязана на "storage"
// https://forum.mozilla-russia.org/viewtopic.php?id=56040 // http://infocatcher.ucoz.net/js/cb/cbEditorToggleOnTop.js // https://github.com/Infocatcher/Custom_Buttons/tree/master/CB_Editor_Toggle_on_Top // Custom Buttons Editor: Toggle on Top button for Custom Buttons // (code for "initialization" section) // (c) Infocatcher 2012-2015 // version 0.1.11 - 2015-06-04 // Hotkey: Ctrl+T const watcherId = "customButtonsToggleOnTop_" + this.id; var {Components} = window; // Prevent garbage collection in Firefox 3.6 and older var storage = (function() { if(!("Services" in window)) // Firefox 3.6 and older return Application.storage; // Simple replacement for Application.storage // See https://bugzilla.mozilla.org/show_bug.cgi?id=1090880 //var global = Components.utils.getGlobalForObject(Services); // Ensure, that we have global object (because window.Services may be overwritten) var global = Components.utils.import("resource://gre/modules/Services.jsm", {}); var ns = "_cbEditorToggleOnTopStorage"; // Note: Firefox 57+ returns NonSyntacticVariablesObject w/o .Object property var storage = global[ns] || (global[ns] = Components.utils.getGlobalForObject(global).Object.create(null)); return { get: function(key, defaultVal) { if(key in storage) return storage[key]; return defaultVal; }, set: function(key, val) { if(key === null) delete storage[key]; else storage[key] = val; } }; })(); var watcher = storage.get(watcherId, null); if(!watcher) { watcher = { btnPos: 0, // 0 - at top right window corner, 1 - at end of tabs, 2 - before dialog buttons spacer btnStyle: "button", // "button" or "toolbarbutton" btnChecked: true, // use "checked" style: true or false // Fogue icons, http://p.yusukekamiyamane.com/ // http://www.iconfinder.com/icondetails/12276/16/gps_location_pin_icon icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AgNBjAx4SevkwAAAjxJREFUKM+FUk1oE0EU/mZnRU0U6cFLIT1KKkkNpIcSWwUvVty2l142hZpDoQcPgYQKQkUEz6V6sHgR/KGQQwleQg/xEKRRsIEmga4NoZa2oRGKpJaWmp2Z52UX12Dxg4838+D73ps3jzUYQycIgDc7l0oNAkgqpZ4n5uc/NgHsEEHDKZB/X5OJRGJcSplemJ6+6SZPFStvJ0Ta4eEhTNMcE0I8fDsxcQsAdPwHc6nUoBRCOz4+Rr1eR39//+18Pn/h/cjIGea+eS6VegLgKoBfRPRdKbXGOf8GIBmPx8cty0Kz2YTf74ff70cmkynpNSIAwP7+/t2ZmZnowcGBPDo6+rm1tdWo1Wo7pmnesSwLtm2jWCx+EkJwKSUnoln2wWnv9eTkMBE9TafT0b29PbTbbXDO0Wq1YNs2lpaWVpVSj8ZyuWXhaPg953CtXK6v9fVtr6ys9A4NDXU3Gg24JoVC4fPJycnjsVxu2TtMzgCUAVQAUKVSrwDtzc3NQCQS6d7d3XU7EK1W62twY+ML84i1N4zB5TvgYrVaPTs1NRXNZDKlrq4u+Hw+BAKBy1LK6zYA2xE+6/gqzhgbXl9fnw+FQrPBYPCHlBKjo6NRIjpPRD3kEdbcJWGMgTF2Y3Fx8UE4HE4yxl70WtYCEc1ms9kS51wDcM4r/FOS87BhGK90XTc0TeNuPgggFosNG4axOjAw8PJKxwIxAD26rt9XShWVUtvOWosO2h1RAJAMwCUAPseInGG6UTlm6l/8DZ+nFuAjSdH0AAAAAElFTkSuQmCC", iconPinned: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AgNBjIpwH1VRwAAAgdJREFUKM+FkkFoU0EQhr/dtxFSquLBS8CAICISQci1oBWLKaaINL300psXDxH0ItSD4L21UKgUxV4aWnKyiIGaQwio2IQ0PQQpBaEhoaEk0dSQNsnb9WAepg+LPwz/7DD/7O7MiJIQuGEAV3QIiNowV4f0HlA0BskJsI8fo75YLHIEjw3ccIInivVxX1IqcXFu7l4Lnhq4BaD4P4Y6INsHB+zlcpwfH7+zG48PavCIvj8/B64CRxoqXdg8Bd+BqG9xMVJNpaiUy5zxehkcGCC2uppV28Y44rs3V1aCFIt2p15v/MzlSj/S6eKlmZnRairFoW2zmUx+boHVBsvAtEj+fV5Iw4vbS0tBe2uLw04H2+ulUy5zaNu8X17OdOGZgkTX3TADCRumP0xNZY3fz6/9fSr5PNVGg6/x+BfdE/Y3wxJAHtj6wzt5aHsSiQv+4WFfbWcHu9VCeDzdRq32DdgQfZMQom9JjDGngcn19fWF2shI9vrYWNBqNjFCND8mk+8MTDq5s65RWUKIUKFQmA0EAtP3pawdra0xGokE0dqrwe8kvgS2e1cjpURKORyLxTaUUg+UUuciUvIWQq8hU5uYMPOQmQcuu7fAsqxr4XD4jVIqLKW0nPgV4AmEFiDzCF65hQLwK6Ueaq0/aa13e2vddVnHxV3AFsBZYKBXyPSa6bDuFdP/st/BfOD54p3eIQAAAABJRU5ErkJggg==", boxId: "cbToggleOnTopBox", btnId: "cbToggleOnTopButton", onTopAttr: "cbOnTop", naAttr: "cbOnTopNA", styleId: "cbToggleOnTopStyle", get btnTip() { var locale = (function() { if("Services" in window && "locale" in Services) { var locales = Services.locale.requestedLocales // Firefox 64+ || Services.locale.getRequestedLocales && Services.locale.getRequestedLocales(); if(locales) return locales[0]; } var prefs = "Services" in window && Services.prefs || Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); function pref(name, type) { return prefs.getPrefType(name) != prefs.PREF_INVALID ? prefs["get" + type + "Pref"](name) : undefined; } if(!pref("intl.locale.matchOS", "Bool")) { // Also see https://bugzilla.mozilla.org/show_bug.cgi?id=1414390 var locale = pref("general.useragent.locale", "Char"); if(locale && locale.substr(0, 9) != "chrome://") return locale; } return Components.classes["@mozilla.org/chrome/chrome-registry;1"] .getService(Components.interfaces.nsIXULChromeRegistry) .getSelectedLocale("global"); })().match(/^[a-z]*/)[0]; if(locale == "ru") return "Поверх всех окон (Ctrl+T)"; return "Always on top (Ctrl+T)"; }, REASON_STARTUP: 1, REASON_SHUTDOWN: 2, REASON_WINDOW_LOADED: 3, REASON_WINDOW_CLOSED: 4, get obs() { delete this.obs; return this.obs = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); }, get ww() { delete this.ww; return this.ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] .getService(Components.interfaces.nsIWindowWatcher); }, get wm() { delete this.wm; return this.wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); }, init: function(reason) { this.obs.addObserver(this, "quit-application-granted", false); var ws = this.wm.getEnumerator(null); while(ws.hasMoreElements()) this.initWindow(ws.getNext(), reason); this.ww.registerNotification(this); }, destroy: function(reason) { this.obs.removeObserver(this, "quit-application-granted"); var ws = this.wm.getEnumerator(null); while(ws.hasMoreElements()) this.destroyWindow(ws.getNext(), reason); this.ww.unregisterNotification(this); }, checkOnTopAttr: function(window) { if(this.version < 71) return this.checkOnTopAttr = function() {} var attr = this.onTopAttr; var id = window.document.documentElement.id; var xs = Cu.import("resource://gre/modules/Services.jsm", {}) .Services.xulStore; (this.checkOnTopAttr = function(window) { var de = window.document.documentElement; if(de.hasAttribute(attr)) return; var url = window.location.href; if(xs.hasValue(url, id, attr)) de.setAttribute(attr, xs.getValue(url, id, attr)); })(window); }, initWindow: function(window, reason) { if(!this.isTargetWindow(window)) return; window.addEventListener("keypress", this, true); if(this.hasSizeModeChangeEvent) window.addEventListener("sizemodechange", this, false); else { window.addEventListener("resize", this, false); // Can detect only maximize/restore this.legacySizeModeChange(window); } this.checkOnTopAttr(window); var document = window.document; this.removeStyle(document); this.addStyle(document); var box = document.getElementById(this.boxId); box && box.parentNode.removeChild(box); box = document.createElementNS(xulns, "hbox"); box.id = this.boxId; var btn = document.createElementNS(xulns, this.btnStyle); btn.id = this.btnId; if(this.btnChecked) { btn.setAttribute("type", "checkbox"); btn.setAttribute("autoCheck", "false"); } btn.tooltipText = this.btnTip; btn.addEventListener("command", this, false); box.appendChild(btn); switch(this.btnPos) { default: box.setAttribute("cbOnTopFloat", "true"); document.documentElement.appendChild(box); break; case 1: if(this.platformVersion >= 72) box.setAttribute("pack", "end"); else box.setAttribute("align", "right"); let tabbox = document.getElementById("custombuttons-editbutton-tabbox"); let tabs = tabbox.getElementsByTagName("tabs")[0]; tabs.parentNode.insertBefore(box, tabs); box.style.marginBottom = -(btn.boxObject || btn.getBoundingClientRect()).height + "px"; break; case 2: box.setAttribute("align", "center"); let btnBox = document.documentElement.getButton("accept").parentNode; let insPos = btnBox.firstChild; for(let node = insPos; node; node = node.nextSibling) { if(node.localName == "spacer") { insPos = node; break; } } btnBox.insertBefore(box, insPos); } this.checkWindowStatus(window, box); //this.setOnTop(btn); }, destroyWindow: function(window, reason) { if(reason == this.REASON_WINDOW_CLOSED) window.removeEventListener("DOMContentLoaded", this, false); // Window can be closed before DOMContentLoaded if(!this.isTargetWindow(window)) return; window.removeEventListener("keypress", this, true); if(this.hasSizeModeChangeEvent) window.removeEventListener("sizemodechange", this, false); else window.removeEventListener("resize", this, false); var document = window.document; var btn = this.shadow(document).getElementById(this.btnId); btn.removeEventListener("command", this, false); if(reason == this.REASON_SHUTDOWN) { let box = btn.parentNode; box.parentNode.removeChild(box); this.removeStyle(document); let xulWin = this.getXulWin(window); xulWin.zLevel = xulWin.normalZ; } }, isTargetWindow: function(window) { return window.location.href.substr(0, 41) == "chrome://custombuttons/content/editor.xul"; }, observe: function(subject, topic, data) { if(topic == "quit-application-granted") this.destroy(); else if(topic == "domwindowopened") subject.addEventListener("DOMContentLoaded", this, false); else if(topic == "domwindowclosed") this.destroyWindow(subject, this.REASON_WINDOW_CLOSED); }, handleEvent: function(e) { var trg = e.originalTarget || e.target; var window; switch(e.type) { case "DOMContentLoaded": window = trg.defaultView; window.removeEventListener("DOMContentLoaded", this, false); this.initWindow(window, this.REASON_WINDOW_LOADED); break; case "keypress": if( !( (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && String.fromCharCode(e.charCode).toLowerCase() == "t" ) ) break; e.preventDefault(); e.stopPropagation(); case "command": window = trg.ownerDocument.defaultView.top; this.toggleOnTop(window); break; case "sizemodechange": case "resize": window = trg; this.checkWindowStatus(window); } }, version: 0, get hasSizeModeChangeEvent() { var appinfo = "Services" in window && Services.appinfo; delete this.hasSizeModeChangeEvent; return this.hasSizeModeChangeEvent = appinfo && ( appinfo.name == "Pale Moon" || (this.version = parseFloat(appinfo.platformVersion)) >= 8 ); }, legacySizeModeChange: function(window) { var lastState = window.windowState; window.setInterval(function(window, _this) { var state = window.windowState; if(state != lastState) _this.checkWindowStatus(window); lastState = state; }, 150, window, this); }, checkWindowStatus: function(window, box) { if(!box) box = this.shadow(window.document).getElementById(this.boxId); var na = String(window.windowState != window.STATE_NORMAL); if(box.getAttribute(this.naAttr) == na) return; box.setAttribute(this.naAttr, na); //LOG("Set n/a: " + na); this.setOnTop(box.firstChild); }, addStyle: function(document) { var style = '\ %box%[cbOnTopFloat] {\n\ display: block !important;\n\ position: fixed !important;\n\ top: 0 !important;\n\ right: 0 !important;\n\ }\n\ %btn%, %btn% * {\n\ margin: 0 !important;\n\ padding: 0 !important;\n\ }\n\ %btn% > .button-box > .button-icon {\n\ margin: -3px -1px !important;\n\ }\n\ toolbarbutton%btn% {\n\ padding: 0 2px !important;\n\ }\n\ %btn% {\n\ position: relative !important;\n\ z-index: 2147483647 !important;\n\ list-style-image: url("%icon%") !important;\n\ -moz-user-focus: ignore !important;\n\ min-height: 0 !important;\n\ min-width: 0 !important;\n\ }\n\ %btn%[%onTopAttr%="true"] {\n\ list-style-image: url("%iconPinned%") !important;\n\ }\n\ %box%[%naAttr%="true"] image {\n\ opacity: 0.75 !important;\n\ }' .replace(/%box%/g, "#" + this.boxId) .replace(/%btn%/g, "#" + this.btnId) .replace(/%onTopAttr%/g, this.onTopAttr) .replace(/%icon%/g, this.icon) .replace(/%iconPinned%/g, this.iconPinned) .replace(/%naAttr%/g, this.naAttr); if(this.shadow(document, style)) return; document.insertBefore(document.createProcessingInstruction( "xml-stylesheet", 'id="' + this.styleId + '" href="' + "data:text/css," + encodeURIComponent(style) + '" type="text/css"' ), document.documentElement); }, removeStyle: function(document) { if(this.shadow(document, false)) return; var mark = 'id="' + this.styleId + '"'; for(var child = document.documentElement; child = child.previousSibling; ) { if( child.nodeType == child.PROCESSING_INSTRUCTION_NODE && child.data.substr(0, mark.length) == mark ) { document.removeChild(child); break; } } }, shadow: function(document, arg) { var sr = document.documentElement.shadowRoot; if(this.btnPos != 2 || !sr) return (this.shadow = function(document, arg) { return arg === undefined ? document : false; })(document, arg); if(arg === undefined) return sr; var st = sr.querySelector("style"), id = this.styleId; if(arg) { st.append(arg); st.lastChild[id] = true; } else { var tn = Array.from(st.childNodes).find(function(node) { return id in node; }); tn && tn.remove(); } return true; }, getXulWin: function(window) { return ( window.docShell && window.docShell instanceof Components.interfaces.nsIDocShell ? window.docShell : window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIWebNavigation) .QueryInterface(Components.interfaces.nsIDocShellTreeItem) ) .treeOwner .QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIXULWindow || Ci.nsIAppWindow); }, setOnTop: function(btn, toggle) { var document = btn.ownerDocument; var root = document.documentElement; var onTop = root.getAttribute(this.onTopAttr) == "true"; if(toggle) { onTop = !onTop; root.setAttribute(this.onTopAttr, onTop); if(root.id) { if("persist" in document) document.persist(root.id, this.onTopAttr); else // Firefox 63+ Services.xulStore.persist(root, this.onTopAttr); } } else if(!onTop) // Just opened or restored window always have zLevel == normalZ return; var window = document.defaultView; var state = window.windowState; // Strange glitches with minimized "raisedZ" window... var restore = onTop && state == window.STATE_MINIMIZED; if(restore || state == window.STATE_NORMAL) { if(restore) onTop = false; let xulWin = this.getXulWin(window); xulWin.zLevel = onTop ? xulWin.raisedZ : xulWin.normalZ; //LOG("Set on top: " + onTop); } this.checkButton(btn, onTop); }, toggleOnTop: function(window) { this.setOnTop(this.shadow(window.document).getElementById(this.btnId), true); }, checkButton: function(btn, onTop) { btn.setAttribute(this.onTopAttr, onTop); this.btnChecked && btn.setAttribute("checked", onTop); } }; storage.set(watcherId, watcher); watcher.init(watcher.REASON_STARTUP); } function destructor(reason) { if(reason == "update" || reason == "delete") { watcher.destroy(watcher.REASON_SHUTDOWN); storage.set(watcherId, null); } } if( typeof addDestructor == "function" // Custom Buttons 0.0.5.6pre4+ && addDestructor != ("addDestructor" in window && window.addDestructor) ) addDestructor(destructor, this); else this.onDestroy = destructor;
Отсутствует
После перехода на 117 перестали работать расширения custom_buttons и add_toolbar_buttons. Подскажите, что делать? Или я не в ту тему попал?
Windows 10 LTSC
Отсутствует
про Custom Buttons 0.0.7.0.0.32 надо прочитать.
Жизнь иногда такое выкидывает, что хочется подобрать...
Отсутствует
про Custom Buttons 0.0.7.0.0.32 надо прочитать.
Дык оно не устанавливается так как оно "не может быть проверено". Опять config.js надо редактировать? Он у меня вот отсюда, но с правками из четырех стёртых строчек.
Windows 10 LTSC
Отсутствует
"не может быть проверено"
Антиподписячий код надо брать здесь
Добавлено 30-08-2023 11:47:39
И запускатор здесь, для paxmod если что.
Добавлено 30-08-2023 11:56:40
ну или
try {(ios => { var subst = "bootstrap-loader-config-script"; ios.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler).setSubstitution(subst, ios.newURI(String.raw` data:,/%2A%2A%2A RDFDataSource.jsm %2A%2A%2A/%0A%0Aconst NS_XML = "http://www.w3.org/XML/1998/namespace";%0Aconst NS_XMLNS = "http://www.w3.org/2000/xmlns/";%0Aconst NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns%23";%0Aconst NS_NC = "http://home.netscape.com/NC-rdf%23";%0A%0Afunction isElement(obj) {%0A return Element.isInstance(obj);%0A}%0Afunction isText(obj) {%0A return obj && typeof obj == "object" && ChromeUtils.getClassName(obj) == "Text";%0A}%0A%0A/%2A%2A%0A %2A Returns either an rdf namespaced attribute or an un-namespaced attribute%0A %2A value. Returns null if neither exists,%0A %2A/%0Afunction getRDFAttribute(element, name) {%0A if (element.hasAttributeNS(NS_RDF, name))%0A return element.getAttributeNS(NS_RDF, name);%0A if (element.hasAttribute(name))%0A return element.getAttribute(name);%0A return undefined;%0A}%0A%0A/%2A%2A%0A %2A Represents an assertion in the datasource%0A %2A/%0Aclass RDFAssertion {%0A constructor(subject, predicate, object) {%0A // The subject on this assertion, an RDFSubject%0A this._subject = subject;%0A // The predicate, a string%0A this._predicate = predicate;%0A // The object, an RDFNode%0A this._object = object;%0A // The datasource this assertion exists in%0A this._ds = this._subject._ds;%0A // Marks that _DOMnode is the subject's element%0A this._isSubjectElement = false;%0A // The DOM node that represents this assertion. Could be a property element,%0A // a property attribute or the subject's element for rdf:type%0A this._DOMNode = null;%0A }%0A%0A getPredicate() {%0A return this._predicate;%0A }%0A%0A getObject() {%0A return this._object;%0A }%0A}%0A%0Aclass RDFNode {%0A equals(rdfnode) {%0A return (rdfnode.constructor === this.constructor &&%0A rdfnode._value == this._value);%0A }%0A}%0A%0A/%2A%2A%0A %2A A simple literal value%0A %2A/%0Aclass RDFLiteral extends RDFNode {%0A constructor(value) {%0A super();%0A this._value = value;%0A }%0A%0A getValue() {%0A return this._value;%0A }%0A}%0A%0A/%2A%2A%0A %2A This is an RDF node that can be a subject so a resource or a blank node%0A %2A/%0Aclass RDFSubject extends RDFNode {%0A constructor(ds) {%0A super();%0A // A lookup of the assertions with this as the subject. Keyed on predicate%0A this._assertions = {};%0A // A lookup of the assertions with this as the object. Keyed on predicate%0A this._backwards = {};%0A // The datasource this subject belongs to%0A this._ds = ds;%0A // The DOM elements in the document that represent this subject. Array of Element%0A this._elements = [];%0A }%0A%0A /%2A%2A%0A %2A Parses the given Element from the DOM document%0A %2A/%0A /%2A eslint-disable complexity %2A/%0A _parseElement(element) {%0A this._elements.push(element);%0A%0A // There might be an inferred rdf:type assertion in the element name%0A if (element.namespaceURI != NS_RDF ||%0A element.localName != "Description") {%0A var assertion = new RDFAssertion(this, RDF_R("type"),%0A this._ds.getResource(element.namespaceURI + element.localName));%0A assertion._DOMnode = element;%0A assertion._isSubjectElement = true;%0A this._addAssertion(assertion);%0A }%0A%0A // Certain attributes can be literal properties%0A for (let attr of element.attributes) {%0A if (attr.namespaceURI == NS_XML || attr.namespaceURI == NS_XMLNS ||%0A attr.nodeName == "xmlns")%0A continue;%0A if ((attr.namespaceURI == NS_RDF || !attr.namespaceURI) &&%0A (["nodeID", "about", "resource", "ID", "parseType"].includes(attr.localName)))%0A continue;%0A var object = null;%0A if (attr.namespaceURI == NS_RDF) {%0A if (attr.localName == "type")%0A object = this._ds.getResource(attr.nodeValue);%0A }%0A if (!object)%0A object = new RDFLiteral(attr.nodeValue);%0A assertion = new RDFAssertion(this, attr.namespaceURI + attr.localName, object);%0A assertion._DOMnode = attr;%0A this._addAssertion(assertion);%0A }%0A%0A var child = element.firstChild;%0A element.listCounter = 1;%0A while (child) {%0A if (isElement(child)) {%0A object = null;%0A var predicate = child.namespaceURI + child.localName;%0A if (child.namespaceURI == NS_RDF) {%0A if (child.localName == "li") {%0A predicate = RDF_R(%60_%24{element.listCounter}%60);%0A element.listCounter++;%0A }%0A }%0A%0A // Check for and bail out on unknown attributes on the property element%0A for (let attr of child.attributes) {%0A // Ignore XML namespaced attributes%0A if (attr.namespaceURI == NS_XML)%0A continue;%0A // These are reserved by XML for future use%0A if (attr.localName.substring(0, 3).toLowerCase() == "xml")%0A continue;%0A // We can handle these RDF attributes%0A if ((!attr.namespaceURI || attr.namespaceURI == NS_RDF) &&%0A ["resource", "nodeID"].includes(attr.localName))%0A continue;%0A // This is a special attribute we handle for compatibility with Mozilla RDF%0A if (attr.namespaceURI == NS_NC &&%0A attr.localName == "parseType")%0A continue;%0A }%0A%0A var parseType = child.getAttributeNS(NS_NC, "parseType");%0A%0A var resource = getRDFAttribute(child, "resource");%0A var nodeID = getRDFAttribute(child, "nodeID");%0A%0A if (resource !== undefined) {%0A var base = Services.io.newURI(element.baseURI);%0A object = this._ds.getResource(base.resolve(resource));%0A } else if (nodeID !== undefined) {%0A object = this._ds.getBlankNode(nodeID);%0A } else {%0A var hasText = false;%0A var childElement = null;%0A var subchild = child.firstChild;%0A while (subchild) {%0A if (isText(subchild) && /\S/.test(subchild.nodeValue)) {%0A hasText = true;%0A } else if (isElement(subchild)) {%0A childElement = subchild;%0A }%0A subchild = subchild.nextSibling;%0A }%0A%0A if (childElement) {%0A object = this._ds._getSubjectForElement(childElement);%0A object._parseElement(childElement);%0A } else%0A object = new RDFLiteral(child.textContent);%0A }%0A%0A assertion = new RDFAssertion(this, predicate, object);%0A this._addAssertion(assertion);%0A assertion._DOMnode = child;%0A }%0A child = child.nextSibling;%0A }%0A }%0A /%2A eslint-enable complexity %2A/%0A%0A /%2A%2A%0A %2A Adds a new assertion to the internal hashes. Should be called for every%0A %2A new assertion parsed or created programmatically.%0A %2A/%0A _addAssertion(assertion) {%0A var predicate = assertion.getPredicate();%0A if (predicate in this._assertions)%0A this._assertions[predicate].push(assertion);%0A else%0A this._assertions[predicate] = [ assertion ];%0A%0A var object = assertion.getObject();%0A if (object instanceof RDFSubject) {%0A // Create reverse assertion%0A if (predicate in object._backwards)%0A object._backwards[predicate].push(assertion);%0A else%0A object._backwards[predicate] = [ assertion ];%0A }%0A }%0A%0A /%2A%2A%0A %2A Returns all objects in assertions with this subject and the given predicate.%0A %2A/%0A getObjects(predicate) {%0A if (predicate in this._assertions)%0A return Array.from(this._assertions[predicate],%0A i => i.getObject());%0A%0A return [];%0A }%0A%0A /%2A%2A%0A %2A Retrieves the first property value for the given predicate.%0A %2A/%0A getProperty(predicate) {%0A if (predicate in this._assertions)%0A return this._assertions[predicate][0].getObject();%0A return null;%0A }%0A}%0A%0A/%2A%2A%0A %2A Creates a new RDFResource for the datasource. Private.%0A %2A/%0Aclass RDFResource extends RDFSubject {%0A constructor(ds, uri) {%0A super(ds);%0A // This is the uri that the resource represents.%0A this._uri = uri;%0A }%0A}%0A%0A/%2A%2A%0A %2A Creates a new blank node. Private.%0A %2A/%0Aclass RDFBlankNode extends RDFSubject {%0A constructor(ds, nodeID) {%0A super(ds);%0A // The nodeID of this node. May be null if there is no ID.%0A this._nodeID = nodeID;%0A }%0A%0A /%2A%2A%0A %2A Sets attributes on the DOM element to mark it as representing this node%0A %2A/%0A _applyToElement(element) {%0A if (!this._nodeID)%0A return;%0A if (USE_RDFNS_ATTR) {%0A var prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));%0A element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);%0A } else {%0A element.setAttribute("nodeID", this._nodeID);%0A }%0A }%0A%0A /%2A%2A%0A %2A Creates a new Element in the document for holding assertions about this%0A %2A subject. The URI controls what tagname to use.%0A %2A/%0A _createNewElement(uri) {%0A // If there are already nodes representing this in the document then we need%0A // a nodeID to match them%0A if (!this._nodeID && this._elements.length > 0) {%0A this._ds._createNodeID(this);%0A for (let element of this._elements)%0A this._applyToElement(element);%0A }%0A%0A return super._createNewElement.call(uri);%0A }%0A%0A /%2A%2A%0A %2A Adds a reference to this node to the given property Element.%0A %2A/%0A _addReferenceToElement(element) {%0A if (this._elements.length > 0 && !this._nodeID) {%0A // In document elsewhere already%0A // Create a node ID and update the other nodes referencing%0A this._ds._createNodeID(this);%0A for (let element of this._elements)%0A this._applyToElement(element);%0A }%0A%0A if (this._nodeID) {%0A if (USE_RDFNS_ATTR) {%0A let prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));%0A element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);%0A } else {%0A element.setAttribute("nodeID", this._nodeID);%0A }%0A } else {%0A // Add the empty blank node, this is generally right since further%0A // assertions will be added to fill this out%0A var newelement = this._ds._addElement(element, RDF_R("Description"));%0A newelement.listCounter = 1;%0A this._elements.push(newelement);%0A }%0A }%0A%0A /%2A%2A%0A %2A Removes any reference to this node from the given property Element.%0A %2A/%0A _removeReferenceFromElement(element) {%0A if (element.hasAttributeNS(NS_RDF, "nodeID"))%0A element.removeAttributeNS(NS_RDF, "nodeID");%0A if (element.hasAttribute("nodeID"))%0A element.removeAttribute("nodeID");%0A }%0A%0A getNodeID() {%0A return this._nodeID;%0A }%0A}%0A%0A/%2A%2A%0A %2A Creates a new RDFDataSource from the given document. The document will be%0A %2A changed as assertions are added and removed to the RDF. Pass a null document%0A %2A to start with an empty graph.%0A %2A/%0Aclass RDFDataSource {%0A constructor(document) {%0A // All known resources, indexed on URI%0A this._resources = {};%0A // All blank nodes%0A this._allBlankNodes = [];%0A%0A // The underlying DOM document for this datasource%0A this._document = document;%0A this._parseDocument();%0A }%0A%0A static loadFromString(text) {%0A let parser = new DOMParser();%0A let document = parser.parseFromString(text, "application/xml");%0A%0A return new this(document);%0A }%0A%0A /%2A%2A%0A %2A Returns an rdf subject for the given DOM Element. If the subject has not%0A %2A been seen before a new one is created.%0A %2A/%0A _getSubjectForElement(element) {%0A var about = getRDFAttribute(element, "about");%0A%0A if (about !== undefined) {%0A let base = Services.io.newURI(element.baseURI);%0A return this.getResource(base.resolve(about));%0A }%0A return this.getBlankNode(null);%0A }%0A%0A /%2A%2A%0A %2A Parses the document for subjects at the top level.%0A %2A/%0A _parseDocument() {%0A var domnode = this._document.documentElement.firstChild;%0A while (domnode) {%0A if (isElement(domnode)) {%0A var subject = this._getSubjectForElement(domnode);%0A subject._parseElement(domnode);%0A }%0A domnode = domnode.nextSibling;%0A }%0A }%0A%0A /%2A%2A%0A %2A Gets a blank node. nodeID may be null and if so a new blank node is created.%0A %2A If a nodeID is given then the blank node with that ID is returned or created.%0A %2A/%0A getBlankNode(nodeID) {%0A var rdfnode = new RDFBlankNode(this, nodeID);%0A this._allBlankNodes.push(rdfnode);%0A return rdfnode;%0A }%0A%0A /%2A%2A%0A %2A Gets the resource for the URI. The resource is created if it has not been%0A %2A used already.%0A %2A/%0A getResource(uri) {%0A if (uri in this._resources)%0A return this._resources[uri];%0A%0A var resource = new RDFResource(this, uri);%0A this._resources[uri] = resource;%0A return resource;%0A }%0A}%0A%0A%0A/%2A%2A%2A RDFManifestConverter.jsm %2A%2A%2A/%0A%0Aconst RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";%0A%0Afunction EM_R(aProperty) {%0A return %60http://www.mozilla.org/2004/em-rdf%23%24{aProperty}%60;%0A}%0A%0Afunction getValue(literal) {%0A return literal && literal.getValue();%0A}%0A%0Afunction getProperty(resource, property) {%0A return getValue(resource.getProperty(EM_R(property)));%0A}%0A%0Aclass Manifest {%0A constructor(ds) {%0A this.ds = ds;%0A }%0A%0A static loadFromString(text) {%0A return new this(RDFDataSource.loadFromString(text));%0A }%0A}%0A%0Aclass InstallRDF extends Manifest {%0A _readProps(source, obj, props) {%0A for (let prop of props) {%0A let val = getProperty(source, prop);%0A if (val != null) {%0A obj[prop] = val;%0A }%0A }%0A }%0A%0A _readArrayProp(source, obj, prop, target, decode = getValue) {%0A let result = Array.from(source.getObjects(EM_R(prop)),%0A target => decode(target));%0A if (result.length) {%0A obj[target] = result;%0A }%0A }%0A%0A _readArrayProps(source, obj, props, decode = getValue) {%0A for (let [prop, target] of Object.entries(props)) {%0A this._readArrayProp(source, obj, prop, target, decode);%0A }%0A }%0A%0A _readLocaleStrings(source, obj) {%0A this._readProps(source, obj, ["name", "description", "creator", "homepageURL"]);%0A this._readArrayProps(source, obj, {%0A locale: "locales",%0A developer: "developers",%0A translator: "translators",%0A contributor: "contributors",%0A });%0A }%0A%0A decode() {%0A let root = this.ds.getResource(RDFURI_INSTALL_MANIFEST_ROOT);%0A let result = {};%0A%0A let props = ["id", "version", "type", "updateURL", "optionsURL",%0A "optionsType", "aboutURL", "iconURL",%0A "bootstrap", "unpack", "strictCompatibility"];%0A this._readProps(root, result, props);%0A%0A let decodeTargetApplication = source => {%0A let app = {};%0A this._readProps(source, app, ["id", "minVersion", "maxVersion"]);%0A return app;%0A };%0A%0A let decodeLocale = source => {%0A let localized = {};%0A this._readLocaleStrings(source, localized);%0A return localized;%0A };%0A%0A this._readLocaleStrings(root, result);%0A%0A this._readArrayProps(root, result, {"targetPlatform": "targetPlatforms"});%0A this._readArrayProps(root, result, {"targetApplication": "targetApplications"},%0A decodeTargetApplication);%0A this._readArrayProps(root, result, {"localized": "localized"},%0A decodeLocale);%0A this._readArrayProps(root, result, {"dependency": "dependencies"},%0A source => getProperty(source, "id"));%0A%0A return result;%0A }%0A}%0A%0A%0A/%2A%2A%2A BootstrapLoader.jsm %2A%2A%2A/%0A%0Avar Services = globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services;%0Avar XPCOMUtils = globalThis.XPCOMUtils || ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;%0A%0AXPCOMUtils.defineLazyModuleGetters(this, {%0A ConsoleAPI: "resource://gre/modules/Console.jsm",%0A Blocklist: "resource://gre/modules/Blocklist.jsm",%0A XPIProvider: "resource://gre/modules/addons/XPIProvider.jsm"%0A});%0A%0Avar OPTIONS_TYPE_DIALOG = 1;%0A%0AServices.obs.addObserver(doc => {%0A if (doc.location.protocol + doc.location.pathname === 'about:addons' ||%0A doc.location.protocol + doc.location.pathname === 'chrome://mozapps/content/extensions/aboutaddons.html') {%0A const win = doc.defaultView;%0A let handleEvent_orig = win.customElements.get('addon-card').prototype.handleEvent;%0A win.customElements.get('addon-card').prototype.handleEvent = function (e) {%0A if (e.type === 'click' &&%0A e.target.getAttribute('action') === 'preferences' &&%0A this.addon.optionsType == OPTIONS_TYPE_DIALOG) {%0A var windows = Services.wm.getEnumerator(null);%0A while (windows.hasMoreElements()) {%0A var win2 = windows.getNext();%0A if (win2.closed) {%0A continue;%0A }%0A if (win2.document.documentURI == this.addon.optionsURL) {%0A win2.focus();%0A return;%0A }%0A }%0A var features = 'chrome,titlebar,toolbar,centerscreen';%0A win.docShell.rootTreeItem.domWindow.openDialog(this.addon.optionsURL, this.addon.id, features); %0A } else {%0A handleEvent_orig.apply(this, arguments);%0A }%0A }%0A let update_orig = win.customElements.get('addon-options').prototype.update;%0A win.customElements.get('addon-options').prototype.update = function (card, addon) {%0A update_orig.apply(this, arguments);%0A if (addon.optionsType == OPTIONS_TYPE_DIALOG)%0A this.querySelector('panel-item[data-l10n-id="preferences-addon-button"]').hidden = false;%0A }%0A }%0A}, 'chrome-document-loaded');%0A%0Aconst {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");%0Aconst {XPIDatabase, AddonInternal} = ChromeUtils.import("resource://gre/modules/addons/XPIDatabase.jsm");%0A%0ACu.import("resource://gre/modules/addons/XPIDatabase.jsm", {}).defineAddonWrapperProperty("optionsType", function optionsType() {%0A if (!this.isActive) {%0A return null;%0A }%0A%0A let addon = this.__AddonInternal__;%0A let hasOptionsURL = !!this.optionsURL;%0A%0A if (addon.optionsType) {%0A switch (parseInt(addon.optionsType, 10)) {%0A case OPTIONS_TYPE_DIALOG:%0A case AddonManager.OPTIONS_TYPE_TAB:%0A case AddonManager.OPTIONS_TYPE_INLINE_BROWSER:%0A return hasOptionsURL ? addon.optionsType : null;%0A }%0A return null;%0A }%0A%0A return null;%0A});%0A%0AXPIDatabase.isDisabledLegacy = () => false;%0A%0AXPCOMUtils.defineLazyGetter(this, 'BOOTSTRAP_REASONS', () => XPIProvider.BOOTSTRAP_REASONS);%0A%0Aconst {Log} = ChromeUtils.import('resource://gre/modules/Log.jsm');%0Avar logger = Log.repository.getLogger('addons.bootstrap');%0A%0A/%2A%2A%0A %2A Valid IDs fit this pattern.%0A %2A/%0Avar gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]%2A\@[a-z0-9-\._]+)$/i;%0A%0A// Properties that exist in the install manifest%0Aconst PROP_METADATA = ['id', 'version', 'type', 'internalName', 'updateURL',%0A 'optionsURL', 'optionsType', 'aboutURL', 'iconURL'];%0Aconst PROP_LOCALE_SINGLE = ['name', 'description', 'creator', 'homepageURL'];%0Aconst PROP_LOCALE_MULTI = ['developers', 'translators', 'contributors'];%0A%0A// Map new string type identifiers to old style nsIUpdateItem types.%0A// Retired values:%0A// 32 = multipackage xpi file%0A// 8 = locale%0A// 256 = apiextension%0A// 128 = experiment%0A// theme = 4%0Aconst TYPES = {%0A extension: 2,%0A dictionary: 64,%0A};%0A%0Aconst COMPATIBLE_BY_DEFAULT_TYPES = {%0A extension: true,%0A dictionary: true,%0A};%0A%0Aconst hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);%0A%0Afunction isXPI(filename) {%0A let ext = filename.slice(-4).toLowerCase();%0A return ext === '.xpi' || ext === '.zip';%0A}%0A%0A/%2A%2A%0A %2A Gets an nsIURI for a file within another file, either a directory or an XPI%0A %2A file. If aFile is a directory then this will return a file: URI, if it is an%0A %2A XPI file then it will return a jar: URI.%0A %2A%0A %2A @param {nsIFile} aFile%0A %2A The file containing the resources, must be either a directory or an%0A %2A XPI file%0A %2A @param {string} aPath%0A %2A The path to find the resource at, '/' separated. If aPath is empty%0A %2A then the uri to the root of the contained files will be returned%0A %2A @returns {nsIURI}%0A %2A An nsIURI pointing at the resource%0A %2A/%0Afunction getURIForResourceInFile(aFile, aPath) {%0A if (!isXPI(aFile.leafName)) {%0A let resource = aFile.clone();%0A if (aPath)%0A aPath.split('/').forEach(part => resource.append(part));%0A%0A return Services.io.newFileURI(resource);%0A }%0A%0A return buildJarURI(aFile, aPath);%0A}%0A%0A/%2A%2A%0A %2A Creates a jar: URI for a file inside a ZIP file.%0A %2A%0A %2A @param {nsIFile} aJarfile%0A %2A The ZIP file as an nsIFile%0A %2A @param {string} aPath%0A %2A The path inside the ZIP file%0A %2A @returns {nsIURI}%0A %2A An nsIURI for the file%0A %2A/%0Afunction buildJarURI(aJarfile, aPath) {%0A let uri = Services.io.newFileURI(aJarfile);%0A uri = 'jar:' + uri.spec + '!/' + aPath;%0A return Services.io.newURI(uri);%0A}%0A%0Avar BootstrapLoader = {%0A name: 'bootstrap',%0A manifestFile: 'install.rdf',%0A async loadManifest(pkg) {%0A /%2A%2A%0A %2A Reads locale properties from either the main install manifest root or%0A %2A an em:localized section in the install manifest.%0A %2A%0A %2A @param {Object} aSource%0A %2A The resource to read the properties from.%0A %2A @param {boolean} isDefault%0A %2A True if the locale is to be read from the main install manifest%0A %2A root%0A %2A @param {string[]} aSeenLocales%0A %2A An array of locale names already seen for this install manifest.%0A %2A Any locale names seen as a part of this function will be added to%0A %2A this array%0A %2A @returns {Object}%0A %2A an object containing the locale properties%0A %2A/%0A function readLocale(aSource, isDefault, aSeenLocales) {%0A let locale = {};%0A if (!isDefault) {%0A locale.locales = [];%0A for (let localeName of aSource.locales || []) {%0A if (!localeName) {%0A logger.warn('Ignoring empty locale in localized properties');%0A continue;%0A }%0A if (aSeenLocales.includes(localeName)) {%0A logger.warn('Ignoring duplicate locale in localized properties');%0A continue;%0A }%0A aSeenLocales.push(localeName);%0A locale.locales.push(localeName);%0A }%0A%0A if (locale.locales.length == 0) {%0A logger.warn('Ignoring localized properties with no listed locales');%0A return null;%0A }%0A }%0A%0A for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {%0A if (hasOwnProperty(aSource, prop)) {%0A locale[prop] = aSource[prop];%0A }%0A }%0A%0A return locale;%0A }%0A%0A let manifestData = await pkg.readString('install.rdf');%0A let manifest = InstallRDF.loadFromString(manifestData).decode();%0A%0A let addon = new AddonInternal();%0A for (let prop of PROP_METADATA) {%0A if (hasOwnProperty(manifest, prop)) {%0A addon[prop] = manifest[prop];%0A }%0A }%0A%0A if (!addon.type) {%0A addon.type = 'extension';%0A } else {%0A let type = addon.type;%0A addon.type = null;%0A for (let name in TYPES) {%0A if (TYPES[name] == type) {%0A addon.type = name;%0A break;%0A }%0A }%0A }%0A%0A if (!(addon.type in TYPES))%0A throw new Error('Install manifest specifies unknown type: ' + addon.type);%0A%0A if (!addon.id)%0A throw new Error('No ID in install manifest');%0A if (!gIDTest.test(addon.id))%0A throw new Error('Illegal add-on ID ' + addon.id);%0A if (!addon.version)%0A throw new Error('No version in install manifest');%0A%0A addon.strictCompatibility = (!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||%0A manifest.strictCompatibility == 'true');%0A%0A // Only read these properties for extensions.%0A if (addon.type == 'extension') {%0A if (manifest.bootstrap != 'true') {%0A throw new Error('Non-restartless extensions no longer supported');%0A }%0A%0A if (addon.optionsType &&%0A addon.optionsType != OPTIONS_TYPE_DIALOG &&%0A addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER &&%0A addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) {%0A throw new Error('Install manifest specifies unknown optionsType: ' + addon.optionsType);%0A }%0A%0A if (addon.optionsType)%0A addon.optionsType = parseInt(addon.optionsType);%0A }%0A%0A addon.defaultLocale = readLocale(manifest, true);%0A%0A let seenLocales = [];%0A addon.locales = [];%0A for (let localeData of manifest.localized || []) {%0A let locale = readLocale(localeData, false, seenLocales);%0A if (locale)%0A addon.locales.push(locale);%0A }%0A%0A let dependencies = new Set(manifest.dependencies);%0A addon.dependencies = Object.freeze(Array.from(dependencies));%0A%0A let seenApplications = [];%0A addon.targetApplications = [];%0A for (let targetApp of manifest.targetApplications || []) {%0A if (!targetApp.id || !targetApp.minVersion ||%0A !targetApp.maxVersion) {%0A logger.warn('Ignoring invalid targetApplication entry in install manifest');%0A continue;%0A }%0A if (seenApplications.includes(targetApp.id)) {%0A logger.warn('Ignoring duplicate targetApplication entry for ' + targetApp.id +%0A ' in install manifest');%0A continue;%0A }%0A seenApplications.push(targetApp.id);%0A addon.targetApplications.push(targetApp);%0A }%0A%0A // Note that we don't need to check for duplicate targetPlatform entries since%0A // the RDF service coalesces them for us.%0A addon.targetPlatforms = [];%0A for (let targetPlatform of manifest.targetPlatforms || []) {%0A let platform = {%0A os: null,%0A abi: null,%0A };%0A%0A let pos = targetPlatform.indexOf('_');%0A if (pos != -1) {%0A platform.os = targetPlatform.substring(0, pos);%0A platform.abi = targetPlatform.substring(pos + 1);%0A } else {%0A platform.os = targetPlatform;%0A }%0A%0A addon.targetPlatforms.push(platform);%0A }%0A%0A addon.userDisabled = false;%0A addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;%0A addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;%0A%0A addon.userPermissions = null;%0A%0A addon.icons = {};%0A if (await pkg.hasResource('icon.png')) {%0A addon.icons[32] = 'icon.png';%0A addon.icons[48] = 'icon.png';%0A }%0A%0A if (await pkg.hasResource('icon64.png')) {%0A addon.icons[64] = 'icon64.png';%0A }%0A%0A return addon;%0A },%0A%0A loadScope(addon) {%0A let file = addon.file || addon._sourceBundle;%0A let uri = getURIForResourceInFile(file, 'bootstrap.js').spec;%0A let principal = Services.scriptSecurityManager.getSystemPrincipal();%0A%0A let sandbox = new Cu.Sandbox(principal, {%0A sandboxName: uri,%0A addonId: addon.id,%0A wantGlobalProperties: ['ChromeUtils'],%0A metadata: { addonID: addon.id, URI: uri },%0A });%0A%0A try {%0A Object.assign(sandbox, BOOTSTRAP_REASONS);%0A%0A XPCOMUtils.defineLazyGetter(sandbox, 'console', () =>%0A new ConsoleAPI({ consoleID: %60addon/%24{addon.id}%60 }));%0A%0A Services.scriptloader.loadSubScript(uri, sandbox);%0A } catch (e) {%0A logger.warn(%60Error loading bootstrap.js for %24{addon.id}%60, e);%0A }%0A%0A function findMethod(name) {%0A if (sandbox[name]) {%0A return sandbox[name];%0A }%0A%0A try {%0A let method = Cu.evalInSandbox(name, sandbox);%0A return method;%0A } catch (err) { }%0A%0A return () => {%0A logger.warn(%60Add-on %24{addon.id} is missing bootstrap method %24{name}%60);%0A };%0A }%0A%0A let install = findMethod('install');%0A let uninstall = findMethod('uninstall');%0A let startup = findMethod('startup');%0A let shutdown = findMethod('shutdown');%0A%0A return {%0A install(...args) {%0A install(...args);%0A // Forget any cached files we might've had from this extension.%0A Services.obs.notifyObservers(null, 'startupcache-invalidate');%0A },%0A%0A uninstall(...args) {%0A uninstall(...args);%0A // Forget any cached files we might've had from this extension.%0A Services.obs.notifyObservers(null, 'startupcache-invalidate');%0A },%0A%0A startup(...args) {%0A if (addon.type == 'extension') {%0A logger.debug(%60Registering manifest for %24{file.path}\n%60);%0A Components.manager.addBootstrappedManifestLocation(file);%0A }%0A return startup(...args);%0A },%0A%0A shutdown(data, reason) {%0A try {%0A return shutdown(data, reason);%0A } catch (err) {%0A throw err;%0A } finally {%0A if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {%0A logger.debug(%60Removing manifest for %24{file.path}\n%60);%0A Components.manager.removeBootstrappedManifestLocation(file);%0A }%0A }%0A },%0A };%0A },%0A};%0A%0AAddonManager.addExternalExtensionLoader(BootstrapLoader);%0A%0Aif (AddonManager.isReady) {%0A AddonManager.getAllAddons().then(addons => {%0A addons.forEach(addon => {%0A if (addon.type == 'extension' && !addon.isWebExtension && !addon.userDisabled) {%0A addon.reload();%0A };%0A });%0A });%0A};%0A%0AObject.defineProperty(%0A AddonInternal.prototype,%0A "providesUpdatesSecurely",%0A {enumerable: true, value: true}%0A);%0A `.trim())); Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) .loadSubScript("resource://" + subst, new Cu.Sandbox(Cu.getObjectPrincipal(this), {wantGlobalProperties: ["ChromeUtils", "DOMParser", "Element", "fetch"]})); })(Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService));} catch(ex) {Cu.reportError(ex);}
Отредактировано Farby (30-08-2023 11:56:40)
Жизнь иногда такое выкидывает, что хочется подобрать...
Отсутствует
Антиподписячий код надо брать здесь
Добавлено Сегодня 13:47:39
И запускатор здесь, для paxmod если что.
Ага, custom_buttons у меня заработал, спасибо. И add_toolbar_buttons я вернул, но его дополнительных кнопок почему-то нигде нет.
Windows 10 LTSC
Отсутствует
не пойму чем заменить в этой кнопке "global"
Чем-нибудь глобальным, в смысле независимым от окон,
куда можно добавить ссылку на storage и она там останется, пока сам не удалишь.
Например, js-объект рождённый в модуле, только нормальный,
а не какой-нибудь неподходящий, типа замороженный как AppConstants или ещё что.
Ну или, собственно, сам SystemGlobal. Вот там же две строки с var global = …
Можно первую раскомментировать, а вторую закомментировать,
если конечно window.Services у тебя не «overwritten».
add_toolbar_buttons я вернул, но его дополнительных кнопок почему-то нигде нет.
А зачистка Services.jsm в нём проведена?
Отсутствует
А зачистка Services.jsm в нём проведена?
config.js я конечно чистил, а вот где это делать для add_toolbar_buttons я не знаю.
Отредактировано Black_Monk (30-08-2023 12:39:48)
Windows 10 LTSC
Отсутствует
А зачистка Services.jsm в нём проведена?
Там два файла с этим Services.jsm
parent.js со строкой
Эту можно закомментировать, как я понимаю, или удалить. А что делать со второй в файле ATBWinActorChild.jsm?
ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
Отсутствует
Farby
Ещё я здесь спросил.
Спасибо.
да всё так же, var const let на выбор в зависимости от случая, правда с этим сам ещё до конца не понял...
// ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); var Services = globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
Жизнь иногда такое выкидывает, что хочется подобрать...
Отсутствует
CB (custom_buttons-0.0.7.0.0.32-fx-bootstrap) отключился, при попытке переустановить: "не может быть проверено".
// try {(jsval => { var dbg, gref, genv = func => { var sandbox = new Cu.Sandbox(g, {freshCompartment: true}); Cc["@mozilla.org/jsdebugger;1"].createInstance(Ci.IJSDebugger).addClass(sandbox); (dbg = new sandbox.Debugger()).addDebuggee(g); gref = dbg.makeGlobalObjectReference(g); return (genv = func => func && gref.makeDebuggeeValue(func).environment)(func); } var g = Cu.getGlobalForObject(jsval), o = g.Object, {freeze} = o, disleg; var lexp = () => lockPref("extensions.experiments.enabled", true); var MRS = "MOZ_REQUIRE_SIGNING", AC = "AppConstants", uac = `resource://gre/modules/${AC}.`; if (o.isFrozen(o)) { // Fx 102.0b7+ lexp(); disleg = true; genv(); dbg.onEnterFrame = frame => { var {script} = frame; try {if (!script.url.startsWith(uac)) return;} catch {return;} dbg.onEnterFrame = undefined; if (script.isModule) { // ESM, Fx 108+ var env = frame.environment; frame.onPop = () => env.setVariable(AC, gref.makeDebuggeeValue(freeze( o.assign(new o(), env.getVariable(AC).unsafeDereference(), {[MRS]: false}) ))); } else { // JSM var nsvo = frame.this.unsafeDereference(); nsvo.Object = {freeze(ac) { ac[MRS] = false; delete nsvo.Object; return freeze(ac); }}; } } } else o.freeze = obj => { if (!Components.stack.caller.filename.startsWith(uac)) return freeze(obj); obj[MRS] = false; if ((disleg = "MOZ_ALLOW_ADDON_SIDELOAD" in obj)) lexp(); else obj.MOZ_ALLOW_LEGACY_EXTENSIONS = true, lockPref("extensions.legacy.enabled", true); return (o.freeze = freeze)(obj); } lockPref("xpinstall.signatures.required", false); lockPref("extensions.langpacks.signatures.required", false); var useDbg = true, xpii = "resource://gre/modules/addons/XPIInstall."; if (Ci.nsINativeFileWatcherService) { // Fx < 100 jsval = Cu.import(xpii + "jsm", {}); var shouldVerify = jsval.shouldVerifySignedState; if (shouldVerify.length == 1) useDbg = false, jsval.shouldVerifySignedState = addon => !addon.id && shouldVerify(addon); } if (useDbg) { // Fx 99+ try {var exp = ChromeUtils.importESModule(xpii + "sys.mjs");} catch {exp = g.ChromeUtils.import(xpii + "jsm");} jsval = o.assign({}, exp); var env = genv(jsval.XPIInstall.installTemporaryAddon); var ref = name => {try {return env.find(name).getVariable(name).unsafeDereference();} catch {}} jsval.XPIDatabase = (ref("lazy") || {}).XPIDatabase || ref("XPIDatabase"); var proto = ref("Package").prototype; var verify = proto.verifySignedState; proto.verifySignedState = function(id) { return id ? {cert: null, signedState: undefined} : verify.apply(this, arguments); } dbg.removeAllDebuggees(); } if (disleg) jsval.XPIDatabase.isDisabledLegacy = () => false; })( "permitCPOWsInScope" in Cu ? Cu.import("resource://gre/modules/WebRequestCommon.jsm", {}) : Cu );} catch(ex) {Cu.reportError(ex);} // bootstrap-loader.js https://forum.mozilla-russia.org/viewtopic.php?pid=795196#p795196 try {(ios => { var subst = "bootstrap-loader-config-script"; ios.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler).setSubstitution(subst, ios.newURI(String.raw` data:,/%2A%2A%2A RDFDataSource.jsm %2A%2A%2A/%0A%0Aconst NS_XML = "http://www.w3.org/XML/1998/namespace";%0Aconst NS_XMLNS = "http://www.w3.org/2000/xmlns/";%0Aconst NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns%23";%0Aconst NS_NC = "http://home.netscape.com/NC-rdf%23";%0A%0Afunction isElement(obj) {%0A return Element.isInstance(obj);%0A}%0Afunction isText(obj) {%0A return obj && typeof obj == "object" && ChromeUtils.getClassName(obj) == "Text";%0A}%0A%0A/%2A%2A%0A %2A Returns either an rdf namespaced attribute or an un-namespaced attribute%0A %2A value. Returns null if neither exists,%0A %2A/%0Afunction getRDFAttribute(element, name) {%0A if (element.hasAttributeNS(NS_RDF, name))%0A return element.getAttributeNS(NS_RDF, name);%0A if (element.hasAttribute(name))%0A return element.getAttribute(name);%0A return undefined;%0A}%0A%0A/%2A%2A%0A %2A Represents an assertion in the datasource%0A %2A/%0Aclass RDFAssertion {%0A constructor(subject, predicate, object) {%0A // The subject on this assertion, an RDFSubject%0A this._subject = subject;%0A // The predicate, a string%0A this._predicate = predicate;%0A // The object, an RDFNode%0A this._object = object;%0A // The datasource this assertion exists in%0A this._ds = this._subject._ds;%0A // Marks that _DOMnode is the subject's element%0A this._isSubjectElement = false;%0A // The DOM node that represents this assertion. Could be a property element,%0A // a property attribute or the subject's element for rdf:type%0A this._DOMNode = null;%0A }%0A%0A getPredicate() {%0A return this._predicate;%0A }%0A%0A getObject() {%0A return this._object;%0A }%0A}%0A%0Aclass RDFNode {%0A equals(rdfnode) {%0A return (rdfnode.constructor === this.constructor &&%0A rdfnode._value == this._value);%0A }%0A}%0A%0A/%2A%2A%0A %2A A simple literal value%0A %2A/%0Aclass RDFLiteral extends RDFNode {%0A constructor(value) {%0A super();%0A this._value = value;%0A }%0A%0A getValue() {%0A return this._value;%0A }%0A}%0A%0A/%2A%2A%0A %2A This is an RDF node that can be a subject so a resource or a blank node%0A %2A/%0Aclass RDFSubject extends RDFNode {%0A constructor(ds) {%0A super();%0A // A lookup of the assertions with this as the subject. Keyed on predicate%0A this._assertions = {};%0A // A lookup of the assertions with this as the object. Keyed on predicate%0A this._backwards = {};%0A // The datasource this subject belongs to%0A this._ds = ds;%0A // The DOM elements in the document that represent this subject. Array of Element%0A this._elements = [];%0A }%0A%0A /%2A%2A%0A %2A Parses the given Element from the DOM document%0A %2A/%0A /%2A eslint-disable complexity %2A/%0A _parseElement(element) {%0A this._elements.push(element);%0A%0A // There might be an inferred rdf:type assertion in the element name%0A if (element.namespaceURI != NS_RDF ||%0A element.localName != "Description") {%0A var assertion = new RDFAssertion(this, RDF_R("type"),%0A this._ds.getResource(element.namespaceURI + element.localName));%0A assertion._DOMnode = element;%0A assertion._isSubjectElement = true;%0A this._addAssertion(assertion);%0A }%0A%0A // Certain attributes can be literal properties%0A for (let attr of element.attributes) {%0A if (attr.namespaceURI == NS_XML || attr.namespaceURI == NS_XMLNS ||%0A attr.nodeName == "xmlns")%0A continue;%0A if ((attr.namespaceURI == NS_RDF || !attr.namespaceURI) &&%0A (["nodeID", "about", "resource", "ID", "parseType"].includes(attr.localName)))%0A continue;%0A var object = null;%0A if (attr.namespaceURI == NS_RDF) {%0A if (attr.localName == "type")%0A object = this._ds.getResource(attr.nodeValue);%0A }%0A if (!object)%0A object = new RDFLiteral(attr.nodeValue);%0A assertion = new RDFAssertion(this, attr.namespaceURI + attr.localName, object);%0A assertion._DOMnode = attr;%0A this._addAssertion(assertion);%0A }%0A%0A var child = element.firstChild;%0A element.listCounter = 1;%0A while (child) {%0A if (isElement(child)) {%0A object = null;%0A var predicate = child.namespaceURI + child.localName;%0A if (child.namespaceURI == NS_RDF) {%0A if (child.localName == "li") {%0A predicate = RDF_R(%60_%24{element.listCounter}%60);%0A element.listCounter++;%0A }%0A }%0A%0A // Check for and bail out on unknown attributes on the property element%0A for (let attr of child.attributes) {%0A // Ignore XML namespaced attributes%0A if (attr.namespaceURI == NS_XML)%0A continue;%0A // These are reserved by XML for future use%0A if (attr.localName.substring(0, 3).toLowerCase() == "xml")%0A continue;%0A // We can handle these RDF attributes%0A if ((!attr.namespaceURI || attr.namespaceURI == NS_RDF) &&%0A ["resource", "nodeID"].includes(attr.localName))%0A continue;%0A // This is a special attribute we handle for compatibility with Mozilla RDF%0A if (attr.namespaceURI == NS_NC &&%0A attr.localName == "parseType")%0A continue;%0A }%0A%0A var parseType = child.getAttributeNS(NS_NC, "parseType");%0A%0A var resource = getRDFAttribute(child, "resource");%0A var nodeID = getRDFAttribute(child, "nodeID");%0A%0A if (resource !== undefined) {%0A var base = Services.io.newURI(element.baseURI);%0A object = this._ds.getResource(base.resolve(resource));%0A } else if (nodeID !== undefined) {%0A object = this._ds.getBlankNode(nodeID);%0A } else {%0A var hasText = false;%0A var childElement = null;%0A var subchild = child.firstChild;%0A while (subchild) {%0A if (isText(subchild) && /\S/.test(subchild.nodeValue)) {%0A hasText = true;%0A } else if (isElement(subchild)) {%0A childElement = subchild;%0A }%0A subchild = subchild.nextSibling;%0A }%0A%0A if (childElement) {%0A object = this._ds._getSubjectForElement(childElement);%0A object._parseElement(childElement);%0A } else%0A object = new RDFLiteral(child.textContent);%0A }%0A%0A assertion = new RDFAssertion(this, predicate, object);%0A this._addAssertion(assertion);%0A assertion._DOMnode = child;%0A }%0A child = child.nextSibling;%0A }%0A }%0A /%2A eslint-enable complexity %2A/%0A%0A /%2A%2A%0A %2A Adds a new assertion to the internal hashes. Should be called for every%0A %2A new assertion parsed or created programmatically.%0A %2A/%0A _addAssertion(assertion) {%0A var predicate = assertion.getPredicate();%0A if (predicate in this._assertions)%0A this._assertions[predicate].push(assertion);%0A else%0A this._assertions[predicate] = [ assertion ];%0A%0A var object = assertion.getObject();%0A if (object instanceof RDFSubject) {%0A // Create reverse assertion%0A if (predicate in object._backwards)%0A object._backwards[predicate].push(assertion);%0A else%0A object._backwards[predicate] = [ assertion ];%0A }%0A }%0A%0A /%2A%2A%0A %2A Returns all objects in assertions with this subject and the given predicate.%0A %2A/%0A getObjects(predicate) {%0A if (predicate in this._assertions)%0A return Array.from(this._assertions[predicate],%0A i => i.getObject());%0A%0A return [];%0A }%0A%0A /%2A%2A%0A %2A Retrieves the first property value for the given predicate.%0A %2A/%0A getProperty(predicate) {%0A if (predicate in this._assertions)%0A return this._assertions[predicate][0].getObject();%0A return null;%0A }%0A}%0A%0A/%2A%2A%0A %2A Creates a new RDFResource for the datasource. Private.%0A %2A/%0Aclass RDFResource extends RDFSubject {%0A constructor(ds, uri) {%0A super(ds);%0A // This is the uri that the resource represents.%0A this._uri = uri;%0A }%0A}%0A%0A/%2A%2A%0A %2A Creates a new blank node. Private.%0A %2A/%0Aclass RDFBlankNode extends RDFSubject {%0A constructor(ds, nodeID) {%0A super(ds);%0A // The nodeID of this node. May be null if there is no ID.%0A this._nodeID = nodeID;%0A }%0A%0A /%2A%2A%0A %2A Sets attributes on the DOM element to mark it as representing this node%0A %2A/%0A _applyToElement(element) {%0A if (!this._nodeID)%0A return;%0A if (USE_RDFNS_ATTR) {%0A var prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));%0A element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);%0A } else {%0A element.setAttribute("nodeID", this._nodeID);%0A }%0A }%0A%0A /%2A%2A%0A %2A Creates a new Element in the document for holding assertions about this%0A %2A subject. The URI controls what tagname to use.%0A %2A/%0A _createNewElement(uri) {%0A // If there are already nodes representing this in the document then we need%0A // a nodeID to match them%0A if (!this._nodeID && this._elements.length > 0) {%0A this._ds._createNodeID(this);%0A for (let element of this._elements)%0A this._applyToElement(element);%0A }%0A%0A return super._createNewElement.call(uri);%0A }%0A%0A /%2A%2A%0A %2A Adds a reference to this node to the given property Element.%0A %2A/%0A _addReferenceToElement(element) {%0A if (this._elements.length > 0 && !this._nodeID) {%0A // In document elsewhere already%0A // Create a node ID and update the other nodes referencing%0A this._ds._createNodeID(this);%0A for (let element of this._elements)%0A this._applyToElement(element);%0A }%0A%0A if (this._nodeID) {%0A if (USE_RDFNS_ATTR) {%0A let prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));%0A element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);%0A } else {%0A element.setAttribute("nodeID", this._nodeID);%0A }%0A } else {%0A // Add the empty blank node, this is generally right since further%0A // assertions will be added to fill this out%0A var newelement = this._ds._addElement(element, RDF_R("Description"));%0A newelement.listCounter = 1;%0A this._elements.push(newelement);%0A }%0A }%0A%0A /%2A%2A%0A %2A Removes any reference to this node from the given property Element.%0A %2A/%0A _removeReferenceFromElement(element) {%0A if (element.hasAttributeNS(NS_RDF, "nodeID"))%0A element.removeAttributeNS(NS_RDF, "nodeID");%0A if (element.hasAttribute("nodeID"))%0A element.removeAttribute("nodeID");%0A }%0A%0A getNodeID() {%0A return this._nodeID;%0A }%0A}%0A%0A/%2A%2A%0A %2A Creates a new RDFDataSource from the given document. The document will be%0A %2A changed as assertions are added and removed to the RDF. Pass a null document%0A %2A to start with an empty graph.%0A %2A/%0Aclass RDFDataSource {%0A constructor(document) {%0A // All known resources, indexed on URI%0A this._resources = {};%0A // All blank nodes%0A this._allBlankNodes = [];%0A%0A // The underlying DOM document for this datasource%0A this._document = document;%0A this._parseDocument();%0A }%0A%0A static loadFromString(text) {%0A let parser = new DOMParser();%0A let document = parser.parseFromString(text, "application/xml");%0A%0A return new this(document);%0A }%0A%0A /%2A%2A%0A %2A Returns an rdf subject for the given DOM Element. If the subject has not%0A %2A been seen before a new one is created.%0A %2A/%0A _getSubjectForElement(element) {%0A var about = getRDFAttribute(element, "about");%0A%0A if (about !== undefined) {%0A let base = Services.io.newURI(element.baseURI);%0A return this.getResource(base.resolve(about));%0A }%0A return this.getBlankNode(null);%0A }%0A%0A /%2A%2A%0A %2A Parses the document for subjects at the top level.%0A %2A/%0A _parseDocument() {%0A var domnode = this._document.documentElement.firstChild;%0A while (domnode) {%0A if (isElement(domnode)) {%0A var subject = this._getSubjectForElement(domnode);%0A subject._parseElement(domnode);%0A }%0A domnode = domnode.nextSibling;%0A }%0A }%0A%0A /%2A%2A%0A %2A Gets a blank node. nodeID may be null and if so a new blank node is created.%0A %2A If a nodeID is given then the blank node with that ID is returned or created.%0A %2A/%0A getBlankNode(nodeID) {%0A var rdfnode = new RDFBlankNode(this, nodeID);%0A this._allBlankNodes.push(rdfnode);%0A return rdfnode;%0A }%0A%0A /%2A%2A%0A %2A Gets the resource for the URI. The resource is created if it has not been%0A %2A used already.%0A %2A/%0A getResource(uri) {%0A if (uri in this._resources)%0A return this._resources[uri];%0A%0A var resource = new RDFResource(this, uri);%0A this._resources[uri] = resource;%0A return resource;%0A }%0A}%0A%0A%0A/%2A%2A%2A RDFManifestConverter.jsm %2A%2A%2A/%0A%0Aconst RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";%0A%0Afunction EM_R(aProperty) {%0A return %60http://www.mozilla.org/2004/em-rdf%23%24{aProperty}%60;%0A}%0A%0Afunction getValue(literal) {%0A return literal && literal.getValue();%0A}%0A%0Afunction getProperty(resource, property) {%0A return getValue(resource.getProperty(EM_R(property)));%0A}%0A%0Aclass Manifest {%0A constructor(ds) {%0A this.ds = ds;%0A }%0A%0A static loadFromString(text) {%0A return new this(RDFDataSource.loadFromString(text));%0A }%0A}%0A%0Aclass InstallRDF extends Manifest {%0A _readProps(source, obj, props) {%0A for (let prop of props) {%0A let val = getProperty(source, prop);%0A if (val != null) {%0A obj[prop] = val;%0A }%0A }%0A }%0A%0A _readArrayProp(source, obj, prop, target, decode = getValue) {%0A let result = Array.from(source.getObjects(EM_R(prop)),%0A target => decode(target));%0A if (result.length) {%0A obj[target] = result;%0A }%0A }%0A%0A _readArrayProps(source, obj, props, decode = getValue) {%0A for (let [prop, target] of Object.entries(props)) {%0A this._readArrayProp(source, obj, prop, target, decode);%0A }%0A }%0A%0A _readLocaleStrings(source, obj) {%0A this._readProps(source, obj, ["name", "description", "creator", "homepageURL"]);%0A this._readArrayProps(source, obj, {%0A locale: "locales",%0A developer: "developers",%0A translator: "translators",%0A contributor: "contributors",%0A });%0A }%0A%0A decode() {%0A let root = this.ds.getResource(RDFURI_INSTALL_MANIFEST_ROOT);%0A let result = {};%0A%0A let props = ["id", "version", "type", "updateURL", "optionsURL",%0A "optionsType", "aboutURL", "iconURL",%0A "bootstrap", "unpack", "strictCompatibility"];%0A this._readProps(root, result, props);%0A%0A let decodeTargetApplication = source => {%0A let app = {};%0A this._readProps(source, app, ["id", "minVersion", "maxVersion"]);%0A return app;%0A };%0A%0A let decodeLocale = source => {%0A let localized = {};%0A this._readLocaleStrings(source, localized);%0A return localized;%0A };%0A%0A this._readLocaleStrings(root, result);%0A%0A this._readArrayProps(root, result, {"targetPlatform": "targetPlatforms"});%0A this._readArrayProps(root, result, {"targetApplication": "targetApplications"},%0A decodeTargetApplication);%0A this._readArrayProps(root, result, {"localized": "localized"},%0A decodeLocale);%0A this._readArrayProps(root, result, {"dependency": "dependencies"},%0A source => getProperty(source, "id"));%0A%0A return result;%0A }%0A}%0A%0A%0A/%2A%2A%2A BootstrapLoader.jsm %2A%2A%2A/%0A%0Avar {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");%0Avar {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");%0A%0AXPCOMUtils.defineLazyModuleGetters(this, {%0A ConsoleAPI: "resource://gre/modules/Console.jsm",%0A Blocklist: "resource://gre/modules/Blocklist.jsm",%0A XPIProvider: "resource://gre/modules/addons/XPIProvider.jsm"%0A});%0A%0Avar OPTIONS_TYPE_DIALOG = 1;%0A%0AServices.obs.addObserver(doc => {%0A if (doc.location.protocol + doc.location.pathname === 'about:addons' ||%0A doc.location.protocol + doc.location.pathname === 'chrome:/content/extensions/aboutaddons.html') {%0A const win = doc.defaultView;%0A let handleEvent_orig = win.customElements.get('addon-card').prototype.handleEvent;%0A win.customElements.get('addon-card').prototype.handleEvent = function (e) {%0A if (e.type === 'click' &&%0A e.target.getAttribute('action') === 'preferences' &&%0A this.addon.optionsType == OPTIONS_TYPE_DIALOG) {%0A var windows = Services.wm.getEnumerator(null);%0A while (windows.hasMoreElements()) {%0A var win2 = windows.getNext();%0A if (win2.closed) {%0A continue;%0A }%0A if (win2.document.documentURI == this.addon.optionsURL) {%0A win2.focus();%0A return;%0A }%0A }%0A var features = 'chrome,titlebar,toolbar,centerscreen';%0A var instantApply = Services.prefs.getBoolPref('browser.preferences.instantApply');%0A features += instantApply ? ',dialog=no' : '';%0A win.docShell.rootTreeItem.domWindow.openDialog(this.addon.optionsURL, this.addon.id, features); %0A } else {%0A handleEvent_orig.apply(this, arguments);%0A }%0A }%0A let update_orig = win.customElements.get('addon-options').prototype.update;%0A win.customElements.get('addon-options').prototype.update = function (card, addon) {%0A update_orig.apply(this, arguments);%0A if (addon.optionsType == OPTIONS_TYPE_DIALOG)%0A this.querySelector('panel-item[data-l10n-id="preferences-addon-button"]').hidden = false;%0A }%0A }%0A}, 'chrome-document-loaded');%0A%0Aconst {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");%0Aconst {XPIDatabase, AddonInternal} = ChromeUtils.import("resource://gre/modules/addons/XPIDatabase.jsm");%0A%0ACu.import("resource://gre/modules/addons/XPIDatabase.jsm", {}).defineAddonWrapperProperty("optionsType", function optionsType() {%0A if (!this.isActive) {%0A return null;%0A }%0A%0A let addon = this.__AddonInternal__;%0A let hasOptionsURL = !!this.optionsURL;%0A%0A if (addon.optionsType) {%0A switch (parseInt(addon.optionsType, 10)) {%0A case OPTIONS_TYPE_DIALOG:%0A case AddonManager.OPTIONS_TYPE_TAB:%0A case AddonManager.OPTIONS_TYPE_INLINE_BROWSER:%0A return hasOptionsURL ? addon.optionsType : null;%0A }%0A return null;%0A }%0A%0A return null;%0A});%0A%0AXPIDatabase.isDisabledLegacy = () => false;%0A%0AXPCOMUtils.defineLazyGetter(this, 'BOOTSTRAP_REASONS', () => XPIProvider.BOOTSTRAP_REASONS);%0A%0Aconst {Log} = ChromeUtils.import('resource://gre/modules/Log.jsm');%0Avar logger = Log.repository.getLogger('addons.bootstrap');%0A%0A/%2A%2A%0A %2A Valid IDs fit this pattern.%0A %2A/%0Avar gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]%2A\@[a-z0-9-\._]+)$/i;%0A%0A// Properties that exist in the install manifest%0Aconst PROP_METADATA = ['id', 'version', 'type', 'internalName', 'updateURL',%0A 'optionsURL', 'optionsType', 'aboutURL', 'iconURL'];%0Aconst PROP_LOCALE_SINGLE = ['name', 'description', 'creator', 'homepageURL'];%0Aconst PROP_LOCALE_MULTI = ['developers', 'translators', 'contributors'];%0A%0A// Map new string type identifiers to old style nsIUpdateItem types.%0A// Retired values:%0A// 32 = multipackage xpi file%0A// 8 = locale%0A// 256 = apiextension%0A// 128 = experiment%0A// theme = 4%0Aconst TYPES = {%0A extension: 2,%0A dictionary: 64,%0A};%0A%0Aconst COMPATIBLE_BY_DEFAULT_TYPES = {%0A extension: true,%0A dictionary: true,%0A};%0A%0Aconst hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);%0A%0Afunction isXPI(filename) {%0A let ext = filename.slice(-4).toLowerCase();%0A return ext === '.xpi' || ext === '.zip';%0A}%0A%0A/%2A%2A%0A %2A Gets an nsIURI for a file within another file, either a directory or an XPI%0A %2A file. If aFile is a directory then this will return a file: URI, if it is an%0A %2A XPI file then it will return a jar: URI.%0A %2A%0A %2A @param {nsIFile} aFile%0A %2A The file containing the resources, must be either a directory or an%0A %2A XPI file%0A %2A @param {string} aPath%0A %2A The path to find the resource at, '/' separated. If aPath is empty%0A %2A then the uri to the root of the contained files will be returned%0A %2A @returns {nsIURI}%0A %2A An nsIURI pointing at the resource%0A %2A/%0Afunction getURIForResourceInFile(aFile, aPath) {%0A if (!isXPI(aFile.leafName)) {%0A let resource = aFile.clone();%0A if (aPath)%0A aPath.split('/').forEach(part => resource.append(part));%0A%0A return Services.io.newFileURI(resource);%0A }%0A%0A return buildJarURI(aFile, aPath);%0A}%0A%0A/%2A%2A%0A %2A Creates a jar: URI for a file inside a ZIP file.%0A %2A%0A %2A @param {nsIFile} aJarfile%0A %2A The ZIP file as an nsIFile%0A %2A @param {string} aPath%0A %2A The path inside the ZIP file%0A %2A @returns {nsIURI}%0A %2A An nsIURI for the file%0A %2A/%0Afunction buildJarURI(aJarfile, aPath) {%0A let uri = Services.io.newFileURI(aJarfile);%0A uri = 'jar:' + uri.spec + '!/' + aPath;%0A return Services.io.newURI(uri);%0A}%0A%0Avar BootstrapLoader = {%0A name: 'bootstrap',%0A manifestFile: 'install.rdf',%0A async loadManifest(pkg) {%0A /%2A%2A%0A %2A Reads locale properties from either the main install manifest root or%0A %2A an em:localized section in the install manifest.%0A %2A%0A %2A @param {Object} aSource%0A %2A The resource to read the properties from.%0A %2A @param {boolean} isDefault%0A %2A True if the locale is to be read from the main install manifest%0A %2A root%0A %2A @param {string[]} aSeenLocales%0A %2A An array of locale names already seen for this install manifest.%0A %2A Any locale names seen as a part of this function will be added to%0A %2A this array%0A %2A @returns {Object}%0A %2A an object containing the locale properties%0A %2A/%0A function readLocale(aSource, isDefault, aSeenLocales) {%0A let locale = {};%0A if (!isDefault) {%0A locale.locales = [];%0A for (let localeName of aSource.locales || []) {%0A if (!localeName) {%0A logger.warn('Ignoring empty locale in localized properties');%0A continue;%0A }%0A if (aSeenLocales.includes(localeName)) {%0A logger.warn('Ignoring duplicate locale in localized properties');%0A continue;%0A }%0A aSeenLocales.push(localeName);%0A locale.locales.push(localeName);%0A }%0A%0A if (locale.locales.length == 0) {%0A logger.warn('Ignoring localized properties with no listed locales');%0A return null;%0A }%0A }%0A%0A for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {%0A if (hasOwnProperty(aSource, prop)) {%0A locale[prop] = aSource[prop];%0A }%0A }%0A%0A return locale;%0A }%0A%0A let manifestData = await pkg.readString('install.rdf');%0A let manifest = InstallRDF.loadFromString(manifestData).decode();%0A%0A let addon = new AddonInternal();%0A for (let prop of PROP_METADATA) {%0A if (hasOwnProperty(manifest, prop)) {%0A addon[prop] = manifest[prop];%0A }%0A }%0A%0A if (!addon.type) {%0A addon.type = 'extension';%0A } else {%0A let type = addon.type;%0A addon.type = null;%0A for (let name in TYPES) {%0A if (TYPES[name] == type) {%0A addon.type = name;%0A break;%0A }%0A }%0A }%0A%0A if (!(addon.type in TYPES))%0A throw new Error('Install manifest specifies unknown type: ' + addon.type);%0A%0A if (!addon.id)%0A throw new Error('No ID in install manifest');%0A if (!gIDTest.test(addon.id))%0A throw new Error('Illegal add-on ID ' + addon.id);%0A if (!addon.version)%0A throw new Error('No version in install manifest');%0A%0A addon.strictCompatibility = (!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||%0A manifest.strictCompatibility == 'true');%0A%0A // Only read these properties for extensions.%0A if (addon.type == 'extension') {%0A if (manifest.bootstrap != 'true') {%0A throw new Error('Non-restartless extensions no longer supported');%0A }%0A%0A if (addon.optionsType &&%0A addon.optionsType != OPTIONS_TYPE_DIALOG &&%0A addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER &&%0A addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) {%0A throw new Error('Install manifest specifies unknown optionsType: ' + addon.optionsType);%0A }%0A%0A if (addon.optionsType)%0A addon.optionsType = parseInt(addon.optionsType);%0A }%0A%0A addon.defaultLocale = readLocale(manifest, true);%0A%0A let seenLocales = [];%0A addon.locales = [];%0A for (let localeData of manifest.localized || []) {%0A let locale = readLocale(localeData, false, seenLocales);%0A if (locale)%0A addon.locales.push(locale);%0A }%0A%0A let dependencies = new Set(manifest.dependencies);%0A addon.dependencies = Object.freeze(Array.from(dependencies));%0A%0A let seenApplications = [];%0A addon.targetApplications = [];%0A for (let targetApp of manifest.targetApplications || []) {%0A if (!targetApp.id || !targetApp.minVersion ||%0A !targetApp.maxVersion) {%0A logger.warn('Ignoring invalid targetApplication entry in install manifest');%0A continue;%0A }%0A if (seenApplications.includes(targetApp.id)) {%0A logger.warn('Ignoring duplicate targetApplication entry for ' + targetApp.id +%0A ' in install manifest');%0A continue;%0A }%0A seenApplications.push(targetApp.id);%0A addon.targetApplications.push(targetApp);%0A }%0A%0A // Note that we don't need to check for duplicate targetPlatform entries since%0A // the RDF service coalesces them for us.%0A addon.targetPlatforms = [];%0A for (let targetPlatform of manifest.targetPlatforms || []) {%0A let platform = {%0A os: null,%0A abi: null,%0A };%0A%0A let pos = targetPlatform.indexOf('_');%0A if (pos != -1) {%0A platform.os = targetPlatform.substring(0, pos);%0A platform.abi = targetPlatform.substring(pos + 1);%0A } else {%0A platform.os = targetPlatform;%0A }%0A%0A addon.targetPlatforms.push(platform);%0A }%0A%0A addon.userDisabled = false;%0A addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;%0A addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;%0A%0A addon.userPermissions = null;%0A%0A addon.icons = {};%0A if (await pkg.hasResource('icon.png')) {%0A addon.icons[32] = 'icon.png';%0A addon.icons[48] = 'icon.png';%0A }%0A%0A if (await pkg.hasResource('icon64.png')) {%0A addon.icons[64] = 'icon64.png';%0A }%0A%0A return addon;%0A },%0A%0A loadScope(addon) {%0A let file = addon.file || addon._sourceBundle;%0A let uri = getURIForResourceInFile(file, 'bootstrap.js').spec;%0A let principal = Services.scriptSecurityManager.getSystemPrincipal();%0A%0A let sandbox = new Cu.Sandbox(principal, {%0A sandboxName: uri,%0A addonId: addon.id,%0A wantGlobalProperties: ['ChromeUtils'],%0A metadata: { addonID: addon.id, URI: uri },%0A });%0A%0A try {%0A Object.assign(sandbox, BOOTSTRAP_REASONS);%0A%0A XPCOMUtils.defineLazyGetter(sandbox, 'console', () =>%0A new ConsoleAPI({ consoleID: %60addon/%24{addon.id}%60 }));%0A%0A Services.scriptloader.loadSubScript(uri, sandbox);%0A } catch (e) {%0A logger.warn(%60Error loading bootstrap.js for %24{addon.id}%60, e);%0A }%0A%0A function findMethod(name) {%0A if (sandbox[name]) {%0A return sandbox[name];%0A }%0A%0A try {%0A let method = Cu.evalInSandbox(name, sandbox);%0A return method;%0A } catch (err) { }%0A%0A return () => {%0A logger.warn(%60Add-on %24{addon.id} is missing bootstrap method %24{name}%60);%0A };%0A }%0A%0A let install = findMethod('install');%0A let uninstall = findMethod('uninstall');%0A let startup = findMethod('startup');%0A let shutdown = findMethod('shutdown');%0A%0A return {%0A install(...args) {%0A install(...args);%0A // Forget any cached files we might've had from this extension.%0A Services.obs.notifyObservers(null, 'startupcache-invalidate');%0A },%0A%0A uninstall(...args) {%0A uninstall(...args);%0A // Forget any cached files we might've had from this extension.%0A Services.obs.notifyObservers(null, 'startupcache-invalidate');%0A },%0A%0A startup(...args) {%0A if (addon.type == 'extension') {%0A logger.debug(%60Registering manifest for %24{file.path}\n%60);%0A Components.manager.addBootstrappedManifestLocation(file);%0A }%0A return startup(...args);%0A },%0A%0A shutdown(data, reason) {%0A try {%0A return shutdown(data, reason);%0A } catch (err) {%0A throw err;%0A } finally {%0A if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {%0A logger.debug(%60Removing manifest for %24{file.path}\n%60);%0A Components.manager.removeBootstrappedManifestLocation(file);%0A }%0A }%0A },%0A };%0A },%0A};%0A%0AAddonManager.addExternalExtensionLoader(BootstrapLoader);%0A%0Aif (AddonManager.isReady) {%0A AddonManager.getAllAddons().then(addons => {%0A addons.forEach(addon => {%0A if (addon.type == 'extension' && !addon.isWebExtension && !addon.userDisabled) {%0A addon.reload();%0A };%0A });%0A });%0A}%0A%0AObject.defineProperty(%0A AddonInternal.prototype,%0A "providesUpdatesSecurely",%0A {enumerable: true, value: true}%0A);%0A `.trim())); Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) .loadSubScript("resource://" + subst, new Cu.Sandbox(Cu.getObjectPrincipal(this), {wantGlobalProperties: ["ChromeUtils", "DOMParser", "Element", "fetch"]})); })(Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService));} catch(ex) {Cu.reportError(ex);} // https://forum.mozilla-russia.org/viewtopic.php?pid=780458#p780458 //(async xp => { // var imprt, ids = [ // "custombuttons@xsms.org", // ]; // if (Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).inSafeMode) return; // // if (Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) // var {XPIInternal} = (imprt = url => Cu.import(url, {}))(xp); // else { // Fx 101+ // var g = Cu.getGlobalForObject(Cu), te = new g.TextEncoder(); // var imp = g.ChromeUtils.import, {XPIInternal} = imp(xp); // var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); // var rph = ios.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler); // imprt = (url, id) => { // var subst = te.encode(id).join(""); // rph.setSubstitution(subst, ios.newURI(url)); // return imp(`resource://${subst}/`); // } // } // var load = async (file, id) => { // var rootURI = XPIInternal.getURIForResourceInFile(file, ""); // imprt(rootURI.resolve("startup.jsm"), id).start(rootURI); // } // var proto = XPIInternal.BootstrapScope.prototype; // var func = proto._beforeCallBootstrapMethod; // proto._beforeCallBootstrapMethod = () => { // proto._beforeCallBootstrapMethod = func; // for(var {id, loader, file} of XPIInternal.XPIStates.enabledAddons()) // ids.includes(id) && !loader && load(file, id); // } //})("resource://gre/modules/addons/XPIProvider.jsm");//
Отсутствует
Как исправить?
Жизнь иногда такое выкидывает, что хочется подобрать...
Отсутствует
bootstrap
bootstrap-loader.js не зачищен от Services.jsm
Но он и не слишком-то дружественнен к правке по своей природе.
Лучше разворачивать что-то гитхабское.
Или вот, на пробу (замену), shitty-simple
// bootstrap-loader-mini.js Cu.evalInSandbox( '(async () => {\n\tvar {console, DOMParser} = Cu.getGlobalForObject(Cu);\n\n\tvar imp = sub => {\n\t\tvar url = `resource://gre/modules/${sub}.`;\n\t\ttry {return ChromeUtils.importESModule(url + "sys.mjs");}\n\t\tcatch {return ChromeUtils.import(url + "jsm");}\n\t}\n\tvar {AddonManager} = imp("AddonManager");\n\tvar {XPIProvider} = imp("addons/XPIProvider");\n\tvar {XPIDatabase, AddonInternal} = imp("addons/XPIDatabase");\n\n\tXPIDatabase.isDisabledLegacy = () => false;\n\n\tvar OPTIONS_TYPE_DIALOG = 1; // AddonManager.OPTIONS_TYPE_DIALOG\n\tvar aboutaddons = "chrome://mozapps/content/extensions/aboutaddons.js";\n\n\tvar optionsTypeDescriptor = {\n\t\tenumerable: true,\n\t\tconfigurable: true, // extensions may has its own def\n\t\tget() {\n\t\t\tif (!this.isActive) return null;\n\n\t\t\tvar {caller} = Components.stack.caller;\n\t\t\tif (caller?.filename.startsWith(aboutaddons)) switch (caller.name) {\n\t\t\t\tcase "setElementState": return AddonManager.OPTIONS_TYPE_TAB; // show pref button\n\t\t\t\tcase "handleEvent": this.lastOpenOptionsCall -\n\t\t\t\t\t(this.lastOpenOptionsCall = Cu.now()) < -500 && openOptionsDialog(this);\n\t\t\t\tdefault: return null;\n\t\t\t}\n\t\t\treturn OPTIONS_TYPE_DIALOG;\n\t\t}\n\t};\n\n\tvar openOptionsDialog = async addon => {\n\t\tvar {optionsURL} = addon;\n\t\tfor(var win of Services.wm.getEnumerator(null))\n\t\t\tif (win.document?.documentURI == optionsURL && !win.closed)\n\t\t\t\treturn win.focus();\n\n\t\tServices.wm.getMostRecentWindow(null).openDialog(\n\t\t\toptionsURL, addon.id, "chrome,titlebar,toolbar,centerscreen"\n\t\t); \n\t}\n\n\tvar BootstrapLoader = {\n\t\tname: "bootstrap",\n\t\tmanifestFile: "install.rdf",\n\t\tasync loadManifest(pkg) {\n\n\t\t\tvar addon = new AddonInternal();\n\t\t\tvar xml = await pkg.readString("install.rdf");\n\t\t\tvar doc = new DOMParser().parseFromString(xml, "application/xml");\n\n\t\t\tfor(var prop of [\n\t\t\t\t"id", "version", "name", "description", "creator",\n\t\t\t\t"homepageURL", "updateURL", "optionsURL", "optionsType", "iconURL"\n\t\t\t])\n\t\t\t\taddon[prop] = doc.querySelector(":root > Description > " + prop)?.textContent;\n\n\t\t\tif (addon.optionsType) addon.optionsType = +addon.optionsType;\n\n\t\t\taddon.defaultLocale = {};\n\t\t\tvar locProps = ["name", "description", "creator"];\n\t\t\tfor(var p of locProps) addon.defaultLocale[p] = addon[p];\n\n\t\t\taddon.locales = [];\n\t\t\tfor(var desc of doc.querySelectorAll("localized > Description")) {\n\n\t\t\t\tvar locales = desc.querySelectorAll(":scope > locale");\n\t\t\t\tvar locale = {locales: Array.from(locales, n => n.textContent)};\n\t\t\t\t\n\t\t\t\tfor(var p of locProps)\n\t\t\t\t\tlocale[p] = desc.querySelector(":scope > " + p)?.textContent || addon[p];\n\n\t\t\t\taddon.locales.push(locale);\n\t\t\t}\n\t\t\taddon.type = "extension";\n\t\t\taddon.targetPlatforms = [];\n\t\t\taddon.targetApplications = [{id: "toolkit@mozilla.org"}];\n\n\t\t\taddon.icons = {};\n\t\t\tif (await pkg.hasResource("icon.png"))\n\t\t\t\taddon.icons[32] = addon.icons[48] = "icon.png";\n\t\t\tif (await pkg.hasResource("icon64.png"))\n\t\t\t\taddon.icons[64] = "icon64.png";\n\n\t\t\treturn addon;\n\t\t},\n\n\t\tpatch(wrapper) {\n\t\t\tvar addon = wrapper.__AddonInternal__;\n\t\t\tif (\n\t\t\t\taddon.optionsType == OPTIONS_TYPE_DIALOG\n\t\t\t\t&& addon.optionsURL && !wrapper.lastOpenOptionsCall\n\t\t\t)\n\t\t\t\twrapper.lastOpenOptionsCall = 1,\n\t\t\t\tObject.defineProperty(wrapper, "optionsType", optionsTypeDescriptor);\n\n\t\t\taddon.providesUpdatesSecurely ||\n\t\t\t\tObject.defineProperty(addon, "providesUpdatesSecurely", {enumerable: true, value: true});\n\t\t},\n\n\t\tloadScope(addon) {\n\t\t\tAddonManager.getAddonByID(addon.id).then(this.patch);\n\n\t\t\tvar file = addon.file || addon._sourceBundle;\n\n\t\t\tvar uri = Services.io.newFileURI(file).spec;\n\t\t\tif (file.isFile()) uri = `jar:${uri}!/`;\n\t\t\turi += "bootstrap.js";\n\n\t\t\tvar principal = Services.scriptSecurityManager.getSystemPrincipal();\n\n\t\t\tvar sandbox = new Cu.Sandbox(principal, {\n\t\t\t\tsandboxName: uri,\n\t\t\t\taddonId: addon.id,\n\t\t\t\twantGlobalProperties: ["ChromeUtils"],\n\t\t\t\tmetadata: {addonID: addon.id, URI: uri},\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tObject.assign(sandbox, XPIProvider.BOOTSTRAP_REASONS);\n\t\t\t\tObject.defineProperty(sandbox, "console", {\n\t\t\t\t\tconfigurable: true, enumerable: true, get() {\n\t\t\t\t\t\tvar exp = imp("Console");\n\t\t\t\t\t\tdelete sandbox.console;\n\t\t\t\t\t\treturn sandbox.console = new exp.ConsoleAPI({consoleID: `addon/${addon.id}`});\n\t\t\t\t}});\n\t\t\t\tServices.scriptloader.loadSubScript(uri, sandbox);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.warn(`Error loading bootstrap.js for ${addon.id}`, e);\n\t\t\t}\n\n\t\t\tfunction findMethod(name) {\n\t\t\t\tif (sandbox[name]) return sandbox[name];\n\t\t\t\ttry {return Cu.evalInSandbox(name, sandbox);} catch {}\n\t\t\t\treturn () => console.warn(`Add-on ${addon.id} is missing bootstrap method ${name}`);\n\t\t\t}\n\n\t\t\tvar install = findMethod("install");\n\t\t\tvar uninstall = findMethod("uninstall");\n\t\t\tvar startup = findMethod("startup");\n\t\t\tvar shutdown = findMethod("shutdown");\n\n\t\t\treturn {\n\t\t\t\tinstall(...args) {\n\t\t\t\t\tinstall(...args);\n\t\t\t\t\t// Forget any cached files we might\'ve had from this extension.\n\t\t\t\t\tServices.obs.notifyObservers(null, "startupcache-invalidate");\n\t\t\t\t},\n\t\t\t\tuninstall(...args) {\n\t\t\t\t\tuninstall(...args);\n\t\t\t\t\t// Forget any cached files we might\'ve had from this extension.\n\t\t\t\t\tServices.obs.notifyObservers(null, "startupcache-invalidate");\n\t\t\t\t},\n\t\t\t\tstartup(...args) {\n\t\t\t\t\tComponents.manager.addBootstrappedManifestLocation(file);\n\t\t\t\t\treturn startup(...args);\n\t\t\t\t},\n\t\t\t\tshutdown(data, reason) {\n\t\t\t\t\ttry {return shutdown(data, reason);}\n\t\t\t\t\tcatch (ex) {throw ex;}\n\t\t\t\t\tfinally {\n\t\t\t\t\t\tif (reason != XPIProvider.BOOTSTRAP_REASONS.APP_SHUTDOWN)\n\t\t\t\t\t\t\tComponents.manager.removeBootstrappedManifestLocation(file);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n\tAddonManager.addExternalExtensionLoader(BootstrapLoader);\n})();' , this);
Отсутствует