Полезная информация

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

№1502628-08-2020 17:44:21

solombala
Забанен
 
Группа: Members
Зарегистрирован: 20-07-2019
Сообщений: 652
UA: Firefox 77.0

Re: Custom Buttons

Dumby
С tmemutil.dll  и MOZ_FORCE_DISABLE_E10S=81.0 мультирежим прибит...

Отсутствует

 

№1502707-09-2020 15:34:11

momo2000
Участник
 
Группа: Members
Зарегистрирован: 03-09-2015
Сообщений: 228
UA: Firefox 68.0

Re: Custom Buttons

Dobrov
Выложите последний вариант QuickSettings, а то непонятно, вроде всё время что то обсуждали, а что в итоге получилось?

Отсутствует

 

№1502809-09-2020 01:41:51

Dobrov
Участник
 
Группа: Members
Зарегистрирован: 04-10-2011
Сообщений: 397
UA: Firefox 56.0

Re: Custom Buttons

momo2000 пишет

Выложите последний вариант QuickSettings, а то непонятно, вроде всё время что то обсуждали, а что в итоге получилось?

Это надо у Dumby или _zt спрашивать…

Отсутствует

 

№1502909-09-2020 15:21:47

xrun1
Участник
 
Группа: Members
Зарегистрирован: 12-12-2013
Сообщений: 1160
UA: Firefox 80.0

Re: Custom Buttons

momo2000
Могу выложить свой, он отличается пунктами от тех, что нужны были _zt или Dobrov.

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

Выделить код

Код:

// Quick Toggle https://forum.mozilla-russia.org/viewtopic.php?pid=784139#p784139
// https://forum.mozilla-russia.org/viewtopic.php?pid=784165#p784165
// Быстрое переключение параметров about:config
(async (name, id, func) => {
	if (name == "Object") return CustomizableUI.createWidget(func());
	var win = name == "Window", g = Components.utils.import("resource://gre/modules/Services.jsm", {});
	if (g[id]) {if (win) return;} else g[id] = func();
	if (win) return CustomizableUI.createWidget(g[id]);
	addDestructor(r => r[5] == "e" && delete g[id]);
	g[id].onCreated(this);
})(this.constructor.name, "QuickToggleAboutConfigSettings", () => {

	var {prefs} = Services, db = prefs.getDefaultBranch("");
	var pv = parseInt(Services.appinfo.platformVersion);
	var xul_ns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

//=====================================================================================

	// refresh:
	//	false - reload current tab
	//	true - reload current tab skip cache
	//
	// restart:
	//	false - restart browser
	//	true - restart browser with confirm

	var primary = [{

			pref: ["network.proxy.share_proxy_settings", "Прокси для всех протоколов при ручной настройке"],
			userChoice: true, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["network.proxy.type", "Настройки прокси"],
			userChoice: 0, userAlt: 2, refresh: true,
			values: [
				[0, "Без прокси", "0"], [5, "Системные (из IE)", "5"], [2, "Автонастройка с URL (pacfile)", "2"],
				[1, "Ручная настройка", "1"], [4, "Автоопределение", "4"]
	]},{
			pref: ["network.trr.mode", "DNS через HTTPS"],
			userChoice: 2, userAlt: 0, refresh: true,
			values: [
				[0, "Выключен", "0"], [2, "DNS через HTTPS TRR + мой", "2"], [3, "DNS через HTTPS только TRR", "3"]
	]},
			null,
	{
			pref: ["permissions.default.image", "Разрешить загрузку изображений"],
			userChoice: 1, userAlt: 3, refresh: true,
			values: [[1, "Да"], [3, "Только с сайта"], [2, "Нет"]]
	},{
			pref: ["image.animation_mode", "Анимация изображений"],
			userChoice: "none", refresh: true,
			values: [["none", "Выключена"], ["normal", "Включена"], ["once", "Единожды"]]
	},{
			pref: ["browser.display.use_document_fonts", "Загружать web-шрифты"],
			userChoice: 1, refresh: true,
			values: [[1, "Да"], [0, "Нет"]]
	},
	    null,
	{
			pref: ["gfx.webrender.all", "WebRender для всего"],
			userChoice: false, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["media.webm.enabled", "Декодер WebM VP8"],
			userChoice: false, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},
	    null,
	{
			pref: ["media.autoplay.default", "Автовоспроизведение аудио и видео"],
			userChoice: 5, refresh: true,
			values: [
				[0, "Разрешить автовоспроизведение", "0"],
				[1, "Не разрешать автовоспроизведение", "1"],
				[2, "Всегда спрашивать", "2"],
				[5, "Блокировать аудио и видео", "5"]
	]},{
			pref: ["media.autoplay.blocking_policy", "Автозапуск (политика)"],
			userChoice: 1, userAlt: 2, refresh: true,
			values: [
				[1, "Временная", "1"],
				[2, "По действию", "2"],
				[0, "Постоянная", "0"]
	]},{
			pref: ["plugin.state.flash", "Flash-plugin"],
			userChoice: 2, refresh: true,
			values: [
				[2, "Всегда включать", "2"],
				[1, "Включать по запросу", "1"],
				[0, "Никогда не включать", "0"]
	]},
	    null,
	{
			pref: ["network.cookie.cookieBehavior", "Cookies"],
			userChoice: 1, userAlt: 3, refresh: false,
			values: [
				[1, "Не принимать сторонние"], [3, "Не принимать с не посещенных"], [4, "Не принимать от трекеров"],
				[2, "Не принимать со всех"], [0, "Принимать со всех"]
	]},
			null,
	{
			pref: ["dom.storage.enabled", "Локальное хранилище"],
			userChoice: true
	}
];

//=====================================================================================

	var secondary = [{

			pref: ["dom.enable_performance", "Статус загрузки страницы"],
			userChoice: false
	},{
			pref: ["javascript.enabled", "Выполнять скрипты Java"],
			userChoice: true, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},
			null,
	{
			pref: ["intl.accept_languages", "Язык для веб-страниц"],
			userChoice: "ru-RU, ru, en-US, en",
			values: [["en-US, en, ru-RU, ru", "Английская локаль"], ["ru-RU, ru, en-US, en", "Русская локаль"]]
	},{
			pref: ["browser.display.document_color_use", "Использовать цвета сайтов"],
			userChoice: 0,
			values: [[0, "Авто", "0"], [1, "Всегда", "1"], [2, "Никогда", "2"]]
	},
			null,
	{
			pref: ["network.http.sendRefererHeader", "Referer - для чего"],
			userChoice: 1,
			values: [[0, "Ни для чего", "0"], [1, "Только ссылки", "1"], [2, "Ссылки и изобр.", "2"]]
  },{
			pref: ["media.peerconnection.enabled", "WebRTC утечка IP"],
			userChoice: false
	}
	];

	return {
		label: "Quick toggle",
		id: "QuickToggleAboutConfigSettings",
		tooltiptext: "Quick toggle about preferences",
		localized: false,
		image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFPSURBVDhPnVPNSsNAEJ5tamnBk+A7BMEcikohp/gi9i18nRafQoT00kK16tnn8CJo4vp9m5m41eSgH0zmd3d+diJ/gfdedvl0QW7k1PcLwem+3TjowZagK+Nn6yc3gNCJ+LCCh9LzzTMdqeoNbkQSFZmpJLGCW5Gx2hZaQahMbZ52VuDSfHpnAVJXOb4FKzianbzCXiLrHLYl5C3t5NTRwtxaKJxUTZ/JcOTr6q2Rxwf0aVb2/hLs4KqL3Isckz/mmSdRRksjyg+z7IM6LzDCBfuvAEOJmIKBOqAWmo1YodxLlQNsyO0rsIV/AZM+JGc2a4GgzhZ2p9mFmrpboCMqNYBDdMlwompoTWN6F2nFT5hBXb0PEpmo/Gk+oH+RrqNZINPeIlnZViV1jQuLFJQuWGB0gJdwgShvqdPXZv6J+F/QS7oXqQ9xBTFZ5oa8fAGs9fed5YhPSwAAAABJRU5ErkJggg==",
		onCreated(btn) {
			btn.setAttribute("image", this.image);
			var doc = btn.ownerDocument;

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

			btn.linkedObject = this;
			for(var type of ["command", "contextmenu"])
				btn.setAttribute("on" + type, `linkedObject.${type}(event)`);
		},
		createPopup(doc, btn, name, data) {
			var popup = doc.createElementNS(xul_ns, "menupopup");
			var prop = name + "Popup";
			btn.popups.push(btn[prop] = popup);
			popup.id = this.id + "-" + prop;
			for (var type of ["popupshowing", "click"])
				popup.setAttribute("on" + type, `parentNode.linkedObject.${type}(event)`);
			for(var obj of data) popup.append(this.createElement(doc, obj));
			btn.append(popup);
		},
		map: {b: "Bool", n: "Int", s: "String"},
		createElement(doc, obj) {
			if (!obj) return doc.createElementNS(xul_ns, "menuseparator");
			var pref = doc.ownerGlobal.Object.create(null), node, img, bool;
			for(var [key, val] of Object.entries(obj)) {
				if (key == "pref") {
					var [apref, lab, akey, ttt] = val;
					pref.pref = apref; pref.lab = lab || apref;
					if (ttt) pref.ttt = ttt;
				}
				else if (key == "image") img = val, pref.img = true;
				else if (key != "values") pref[key] = val;
				else pref.hasVals = true;
			}
			var type = prefs.getPrefType(pref.pref);
			var str = this.map[type == prefs.PREF_INVALID
				? obj.values ? (typeof obj.values[0][0])[0] : "b"
				: type == prefs.PREF_BOOL ? "b" : type == prefs.PREF_INT ? "n" : "s"
			];
			pref.get = prefs[`get${str}Pref`];
			pref.set = prefs[`set${str}Pref`];

			node = doc.createElementNS(xul_ns, "menu");
			node.className = "menu-iconic";
			node.setAttribute("closemenu", "none");
			img && node.setAttribute("image", img);
			akey && node.setAttribute("accesskey", akey);
			(node.pref = pref).vals = doc.ownerGlobal.Object.create(null);
			this.createRadios(doc,
				str.startsWith("B") && !pref.hasVals ? [[true, "true"], [false, "false"]] : obj.values,
				node.appendChild(doc.createElementNS(xul_ns, "menupopup"))
			);
			if ("userChoice" in obj) pref.noAlt = !("userAlt" in obj);
			return node;
		},
		createCloseMenusOption(doc, btn) {
			var pn = this.closePref = "QuickToggleAboutConfigSettings.closeMenus";
			var data = [null, {
				pref: [pn, "Закрывать меню этой кнопки"], values: [[true, "Да"], [false, "Нет"]]
			}];
			var setCloseMenus = e => {
				e.stopPropagation();
				var trg = e.target, {pref, val} = trg, updPopup = true, clear;
				switch(e.type) {
					case "command": pref = (trg = trg.closest("menu")).pref; updPopup = false; break;
					case "click": if (e.button) return; break;
					case "contextmenu": e.preventDefault(); clear = pref;
				}
				if (!pref) return;
				if (clear) prefs.clearUserPref(pn);
				else if (!updPopup && val === pref.val) return;
				else pref.set(pn, val !== undefined ? val : !pref.val);
				this.upd(trg);
				updPopup && this.popupshowing(null, trg.querySelector("menupopup"));
			}
			(this.createCloseMenusOption = (doc, btn) => {
				for(var obj of data)
					btn.secondaryPopup.append(this.createElement(doc, obj));
				var m = btn.secondaryPopup.lastChild;
				m.style.cssText = "fill: lightblue !important; list-style-image: url(chrome://browser/skin/menu.svg) !important;";
				m.setAttribute("oncommand", "setCloseMenus(event)");
				m.onclick = m.oncontextmenu = m.setCloseMenus = setCloseMenus;
			})(doc, btn);
		},
		UserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACRUlEQVR42qWTS2gTQRjH/zPJZo2BDfgkB62vYg5CjQdvRUE8WB8HKyahYotIQQOeFKGCF7EgvQlBqOCjWFKlevBRDyL4uHloLXhYqWJroaEqlETSptndGb+ZPDH0ojN8O8vA98vv+/Itw38u1nSRYRE6UhKyg4FF1R292/Q+xhhLi4TIrgjgIzwe4MbgmR2nrZMtxxEN63zYORuPZp7gzpehvCu9XjfuPmwCUHIiEtw4fK89zVvD2+FHAD56quXBpV3CVO4ret6nxHzxZxdBRuqADCKmz7RHD9y2tlmbYbBVfwE8DXBkEd/y39H5+mze8ZyoSIpsFXC9u/VE3/ldpyg5AAMEoJMTQi2hANKBgyKdy0h/eoChqcf9MimvVAHjd/fdiG0NRwgQpDAp1agBpDZwdHKJLKZzc+h5e3kCSexh5c5j8fmhW0HGpNZXFkqfsQpAKgcFKGmAlAxHXp5bkkmsrhosjh4cCIJ5pG5qfQXgtMslqO1qgEMWTPrR+eriEhoA4wN7L8TWh0KVXzfgY2RQAUjVBenWLH4UCrj04Wa9BNXEo5va+w63xCiJ6WRVP2MVgNQIgjgEA17MTODZ7Lt+AtSaGDG4377a1m2FAkKrc11/dUwIID1dSKHEcW3yft4RbpR6kK1PYgaJtaY1nNp5jIeMsjKNbs1AWRUcjvTnp2Kh9LtLJGTDINUhcTIZ3L+hzdq9ZgvWmSF9/Wu5gI8L03gzP5l3hddL/3/zKDdA9MdE0UERrdzaFGMUaap75Y/pX9Yfap76EQYaCeEAAAAASUVORK5CYII=",
		notUserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACNUlEQVR42qXTT2jTUBwH8G9e0ta2ks5ND0Ww/qX1oGyevEwQnOBA6EHXwLwoUh3eRGRs501RPE6xgp6KrexQECZY8DBPXmbRS4Z/K0gRtmmDa2yb5PlLX7p2Gwpi4EfCy8sn3/dPwn9e0saGnCRF6XYFnA9DkhKtRs51ep6jmtEcp/JHIM9Yivl9mf0XNDV2Ngn1YLzVxdB1lJ8U8P5hzuCWnR6xrPwmIM8kLRjdkT326BaLHNgLKAFA9ouXdhOw6qi++4j589cd89vSaMqyc2sAPUXlgF8fyt9WI3t2Av4w4NsCMJ8AHAKav6hqqH76iuLINcNuNhOawyttYCp+7tRE/6XT9GFQAG4CpniATQkIaNQIMVG6/xSL2WfTGueTbWDh5L2rA9t29QGBrQQEPUDuAuoEmFSr+P5lGc8v33mtAUfaQO3M4/GgDOoYCIkUin8D0BDDoBQ2ZMxqN0wCQh0gMxaUnYb4uzt+dwIZ8wBHTGQbYAHMpu+uAxaGxpMDvSr3AHcFaAIlD+BtoN6ag5WqhOLNwrohTMWPH5roP7pdLIwb3wW6EziWGAZhpVdLWHzxdpqAyc4yKrJ+4uKg2oMV+lgRK9CdwAVsG1WpF8UH84Zt2QkCKmsbiRAtFAllB5OHWQ9fpr81OwlcgBL9kPrwsvDGMQ1zNMV5ZyN1ISlZYZl9/TE1tjuMSFgA1VWO8uef+FAqG47tpFMcm7dyFyIOEzBMlfCadao5qhk39l9P479evwFbiOcRSXKueAAAAABJRU5ErkJggg==",
		UserAltImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIfSURBVDhPpVM9b9NQFD02cSKKFVGKIFFREFsmFiCA1AEQQrQjC0gssLCkC0jJj0iFWFoGdiT4Ay0SEmJBhPI5FBokikL4SCC0Te2UxInt13Mdu1CVrVc6713fe86x/T6w09DCeTNKJ7Q0pzyUmoCmZYOiUhXms8RMcd6vB7UwthhM5fTLWix2L3PmYjKdOwdz9DDFGtr1KuovnqD29JGlfO9Goew9DCV/DaZy2pXE8PD9Y/mbupk6xE6cVWPQhEujHo2+4fXdO76ztnq1UPYfSCcwKB1HWjeMyunCraSZOsDqbkBPsBOTNoMGvkOTLto/f+F56bble262OK/qesiYzIydTJojFLjrgEe4bcIOEdXWYe6LITN2KgmFSRFGBuPpo0eAngX0SexTJHD5LOgLpEZTcgKuUuMijAyy5l7+r0NSb21g5LQI5gGYSy2AjYBLjQyRAUniTnJXsBoayBzm3RBBndwwIoOK3fgNdIREQYCV/0DqLdiNZqCRITKYayx85RfQuUNiZ3k7IgO+vfGeXGpkiAyma++WLHtZhZ8o5H/FEVpos1V7u8TFwLQId8nw+Afa51Oq2vyycmlk9KCW0GXFZeu63IUO8z88Bx5s28SbuUXfddzrxVcobxpI0GTh7H734/dPzQt9d0/CGBpCPB7nbhmwbAPVxR4+PPtsuU7/WuElth/lKORUcsoTE8TgMg0WbJaY4Zu3XKYdBrABARUk+ls4DfQAAAAASUVORK5CYII=",
		upd(node) {
			var {pref} = node, def = false, user = false, val;
			if (prefs.getPrefType(pref.pref) != prefs.PREF_INVALID) {
				var pn = pref.pref;
				try {val = pref.defVal = db[pref.get.name](pn); def = true}
				catch(ex) {def = false;}
				var user = prefs.prefHasUserValue(pn);
				if (user) try {val = pref.get(pn, undefined);} catch(ex) {}
			}
			if (val == pref.val && def == pref.def && user == pref.user) return;
			pref.val = val; pref.def = def; pref.user = user;
			var exists = def || user;

			var ttt = exists ? val : "Этого префа не существует";
			if (ttt === "") ttt = "[ empty_string ]";
			ttt += "\n" + pref.pref;
			if (pref.ttt) ttt += "\n" + pref.ttt;
			node.tooltipText = ttt;

			var img, alt = "userAlt" in pref && val == pref.userAlt;
			if (alt) img = this.UserAltImg;
			if ("userChoice" in pref)
				if (val == pref.userChoice)
					//node.style.removeProperty("color"),
					img = this.UserChoiceImg;
				else {
					//node.style.setProperty("color", "maroon", "important");
					if (!alt) img = this.notUserChoiceImg;
				}
			if (!pref.img) img
				? node.setAttribute("image", img)
				: node.removeAttribute("image");
			user
				? node.style.setProperty("font-style", "italic", "important")
				: node.style.removeProperty("font-style");

			var {lab} = pref;
			if (exists && pref.hasVals) {
				if (val in pref.vals) var sfx = pref.vals[val] || val;
				else var sfx = user ? "Другое" : "По умолчанию";
				lab += ` — "${sfx}"`;
			}
			node.setAttribute("label", lab);
		},
		createRadios(doc, vals, popup) {
			for(var arr of vals) {
				if (!arr) {
					popup.append(doc.createElementNS(xul_ns, "menuseparator"));
					continue;
				}
				var [val, lab, key, ttt] = arr;
				var menuitem = doc.createElementNS(xul_ns, "menuitem");
				menuitem.setAttribute("type", "radio");
				menuitem.setAttribute("closemenu", "none");
				menuitem.style.setProperty("font-style", "italic", "important"),
				menuitem.setAttribute("label", popup.parentNode.pref.vals[val] = lab);
				key && menuitem.setAttribute("accesskey", key);
				var tip = menuitem.val = val;
				if (ttt) tip += "\n" + ttt;
				menuitem.tooltipText = tip;
				popup.append(menuitem);
			}
		},
		openPopup(popup) {
			var btn = popup.parentNode;
			if (btn.domParent != btn.parentNode) {
				btn.domParent = btn.parentNode;
				var pos;
				if (btn.matches(".widget-overflow-list > :scope"))
					pos = "after_start";
				else var win = btn.ownerGlobal, {width, height, top, bottom, left, right} =
					btn.closest("toolbar").getBoundingClientRect(), pos = width > height
						? `${win.innerHeight - bottom > top ? "after" : "before"}_start`
						: `${win.innerWidth - right > left ? "end" : "start"}_before`;
				for(var p of btn.popups) p.setAttribute("position", pos);
			}
			popup.openPopup(btn);
		},
		maybeRestart(node, conf) {
			var msgRest = "Перезапустить браузер?", msgAbort = "Запрос на выход отменен.";
			if (pv >= 77) {
				var title = node.closest("toolbarbutton").label;
				var pp = domWin => Services.prompt.wrappedJSObject.pickPrompter({
					domWin, modalType: Ci.nsIPrompt.MODAL_TYPE_WINDOW
				});
				var confirm = win => pp(win).confirm(title, msgRest);
				var alert = win => pp(win).alert(title, msgAbort);
			} else {
				var confirm = win => win.confirm(msgRest);
				var alert = win => win.alert(msgAbort);
			}
			return (this.mayBeRestart = (node, conf) => {
				var win = node.ownerGlobal;
				if (conf && !confirm(win)) return;
				if (win.BrowserUtils.restartApplication() === false) alert(win);
				else return true;
			})(node, conf);
		},
		regexpRefresh: /^(?:view-source:)?(?:https?|ftp)/,
		maybeRe(node, fe) {
			var {pref} = node;
			if ("restart" in pref) {
				if (this.maybeRestart(node, pref.restart)) return;
			}
			else this.popupshowing(fe, node.parentNode);
			if ("refresh" in pref) {
				var win = node.ownerGlobal;
				if (this.regexpRefresh.test(win.gBrowser.currentURI.spec)) pref.refresh
					? win.BrowserReloadSkipCache() : win.BrowserReload();
			}
		},
		maybeClosePopup(e, trg) {
			!e.ctrlKey && prefs.getBoolPref(this.closePref, undefined)
				&& trg.parentNode.hidePopup();
		},
		command(e) {
			var trg = e.target;
			if (trg.btn) return this.openPopup(trg.primaryPopup);

			var menu = trg.closest("menu"), newVal = trg.val;
			this.maybeClosePopup(e, menu);
			if (newVal != menu.pref.val)
				menu.pref.set(menu.pref.pref, newVal),
				this.maybeRe(menu, true);
		},
		popupshowing(e, trg = e.target) {
			if (trg.state == "closed") return;
			if (trg.id) {
				for(var node of trg.children) {
					if (node.nodeName.endsWith("r")) continue;
					this.upd(node);
					!e && node.open && this.popupshowing(null, node.querySelector("menupopup"));
				}
				return;
			}
			var {pref} = trg.closest("menu"), findChecked = true;

			var findDef = "defVal" in pref;
			var checked = trg.querySelector("[checked]");
			if (checked) {
				if (checked.val == pref.val) {
					if (findDef) findChecked = false;
					else return;
				}
				else checked.removeAttribute("checked");
			}
			if (findDef) {
				var def = trg.querySelector("menuitem:not([style*=font-style]");
				if (def)
					if (def.val == pref.defVal) {
						if (findChecked) findDef = false;
						else return;
					}
					else def.style.setProperty("font-style", "italic", "important");
			}
			for(var node of trg.children) if ("val" in node) {
				if (findChecked && node.val == pref.val) {
					node.setAttribute("checked", true);
					if (findDef) findChecked = false;
					else break;
				}
				if (findDef && node.val == pref.defVal) {
					node.style.removeProperty("font-style");
					if (findChecked) findDef = false;
					else break;
				}
			}
		},
		contextmenu(e) {
			var trg = e.target;
			if (trg.btn) {
				if (e.ctrlKey || e.shiftKey) return;
				if (e.detail == 2) return trg.secondaryPopup.hidePopup();
				this.openPopup(trg.secondaryPopup);
			}
			else if ("pref" in trg) {
				this.maybeClosePopup(e, trg);
				if (trg.pref.user)
					prefs.clearUserPref(trg.pref.pref),
					this.maybeRe(trg);
			}
			e.preventDefault();
		},
		click(e) {
			if (e.button) return;
			var trg = e.target, {pref} = trg;
			if (!pref) return;

			this.maybeClosePopup(e, trg);
			if (!("noAlt" in pref)) return;

			if (pref.val == pref.userChoice)
				if (pref.noAlt) return;
				else  pref.set(pref.pref, pref.userAlt);
			else
				pref.set(pref.pref, pref.userChoice);
			this.maybeRe(trg);
		}
	};
});

Отсутствует

 

№1503009-09-2020 16:18:15

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1421
UA: Firefox 78.0

Re: Custom Buttons

QuickToggleAboutConfigSettings автор Dumby
   
Как это использовать для CB я не знаю.
При использовании в UCF подключать в custom_script.js.
   
Создает кнопку с двумя меню вызываемыми щелчками ЛКМ иил ПКМ.
Каждый пункт прописывается по существующим в скрипте шаблонам.
   
Основные характеристики:
   
Каждый пункт имеет подменю с прописанными в скрипте значениями, в котором также можно переключать значения щелчком ЛКМ.
Каждый пункт имеет подсказку с текущим значением и заголовком параметра.
   
userChoice: value, userAlt: value,
    ЛКМ по пункту переключет его значение на userChoice (создает параметр, если его не существует), а с userChoice на userAlt.
    ПКМ по пункту сбрасывает его значение на по умолчанию, либо удаляет его, если такого параметра по умолчанию в Firefox не существует.
   
refresh: true, restart: true,
    Назначает обновление страницы, перезагрузку браузера, при изменении значения.
   
Индикация иконкой
     Каждый пункт имеет иконку
        зеленая = userChoice
        оранжевая = userAlt
        красная = все остальное
   
Индикация шрифтом (работает также и в подменю)
    норамльный = значение браузера по умолчанию
    курсив = значение отличное от по умолчанию браузера
   
Хоткеи
    Хоткеи доступны как в основных меню, так и в подменю
    Для логических, похоже, в основных меню не отображаются.

Пример

Выделить код

Код:

{
			pref: ["javascript.enabled", "Выполнять скрипты Java", "хоткей"],
			userChoice: true, refresh: true,
			values: [[true, "Да", "хоткей"], [false, "Нет", "хоткей"]]
	},


   
В меню ПКМ кнопки доступна настройка скрытия меню при переключении значения параметра.
   
Скрин
QuickToggle.1599656583.png

   
Сам скрипт

Выделить код

Код:

// Быстрое переключение параметров about:config, автор Dumby
// готовые варианты - https://forum.mozilla-russia.org/viewtopic.php?id=9591&p=602
(async (name, id, func) => {
	if (name == "Object") return CustomizableUI.createWidget(func());
	var win = name == "Window", g = Components.utils.import("resource://gre/modules/Services.jsm", {});
	if (g[id]) {if (win) return;} else g[id] = func();
	if (win) return CustomizableUI.createWidget(g[id]);
	addDestructor(r => r[5] == "e" && delete g[id]);
	g[id].onCreated(this);
})(this.constructor.name, "QuickToggleAboutConfigSettings", () => {

	var {prefs} = Services, db = prefs.getDefaultBranch("");
	var pv = parseInt(Services.appinfo.platformVersion);
	var xul_ns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

//=============================================================================

	// refresh:
	//	false - reload current tab
	//	true - reload current tab skip cache
	//
	// restart:
	//	false - restart browser
	//	true - restart browser with confirm

	var primary = [{

			pref: ["network.proxy.type", "Настройки прокси"],
			userChoice: 5, userAlt: 0, refresh: true,
			values: [
				[0, "Не проксировать", "0"],
				[5, "Системные (IE)", "5"],
				[2, "Авто (pacfile)", "2"],
				[1, "Прописанные", "1"],
				[4, "Автоопределение", "4"]
	]},
			null,
	{
			pref: ["permissions.default.image", "Загружать графику"],
			userChoice: 1, refresh: true,
			values: [
				[1, "Да"],
				[3, "С сайта"],
				[2, "Нет"]
	]},{
			pref: ["browser.display.use_document_fonts", "Загружать web-шрифты"],
			userChoice: 1, refresh: true,
			values: [[1, "Да"], [0, "Нет"]]
	},{
			pref: ["browser.display.document_color_use", "Загружать цвета сайтов"],
			userChoice: 0, refresh: true,
			values: [
				[0, "Авто", "0"],
				[1, "Всегда", "1"],
				[2, "Никогда", "2"]
	]},{
			pref: ["javascript.enabled", "Выполнять скрипты Java", "j"],
			userChoice: true, refresh: true,
			values: [[true, "Да", "y"], [false, "Нет", "n"]]
	},{
			pref: ["intl.accept_languages", "Язык для веб-страниц"],
			userChoice: "en-US, en", userAlt: "en-US, en, ru-RU, ru", refresh: true,
			values: [
				["en-US, en", "EN"],
				["en-US, en, ru-RU, ru", "EN, RU"]
	]},{
			pref: ["privacy.spoof_english", "Язык для веб-страниц с RFP"],
			userChoice: 2, refresh: true,
			values: [
				["2", "EN"]
	]},
			null,
	{
			pref: ["userChromeJS.enabled", "userChromeJS.enabled"],
			userChoice: true, userAlt: false, restart: true,
			values: [[true, "Да"], [false, "Нет"]]
	}
];
//=============================================================================

	var secondary = [{

			pref: ["ui.prefersReducedMotion", "Анимация chrome"],
			userChoice: 1, userAlt: 2, restart: true,
			values: [[1, "Отключена"], [0, "Включена"]]
	},{
			pref: ["gfx.webrender.enabled", "Web render"],
			userChoice: false, restart: true,
			values: [[true, "Включен"], [false, "Отключен"]]
	},{
			pref: ["gfx.webrender.force-disabled", "Web render (force-disabled)"],
			userChoice: true, userAlt: false, restart: true,
			values: [[true, "Да"], [false, "Нет"]]
	},
			null,
	{
			pref: ["network.predictor.enabled", "Предзагрузка ссылок"],
			userChoice: false, refresh: true,
			values: [[true, "Включена"], [false, "Отключена"]]
	},{
			pref: ["network.predictor.enable-prefetch", "Предвыборка ссылок"],
			userChoice: false, refresh: true,
			values: [[true, "Включена"], [false, "Отключена"]]
	},{
			pref: ["browser.send_pings", "Аудит гиперссылок"],
			userChoice: false, refresh: true,
			values: [[true, "Включен"], [false, "Отключен"]]
	},{
			pref: ["media.peerconnection.enabled", "WebRTC"],
			userChoice: false, refresh: true,
			values: [[true, "Включен"], [false, "Отключен"]]
	}
	];

	return {
		id: "QuickToggleAboutConfigSettings",
		label: "Quick Toggle Settings",
		tooltiptext: "Quick Toggle Settings\n	ЛКМ	ПКМ",
		localized: false,
		image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAKdSURBVDhPjZNLTxNRFMenM9OWTulTEx4KkSg+0mCCujExNTyiLKiQAUofIcR+AOPOlStdGDd+AzdGDQvF+IhBBY0ro8aNUkAXxFhKERRaYOhjOuP/j0NidONNfj2Puef0nHvPFUzT/C9EQQiC5p/JoXeQJ8Ax2jZ+/HtJNpsDwqiapm7ZnmoqWchq2qNGRYl8yRdetPq83dLN20zyezEIiNwMsxZ4obuAm/aitvVQkeX9CBoIOp1N6bX8K/g92+W1u1x+ZAqwJDOVNAsj0TfQW+EPQbbk4uoD+qGfAz2gn/a02vtSsEuSj0Za7b2BD8f10eGKtfnMcmLgg6WfnlUjk7DnGLySGJydUXunoPeIhmG4C+XK2yP+2ovo870kybKuV8rQJ3bX1LRPZXO3UGrwkN/bmdsq5uAfXy4Ws4f9vg74FSERCJxCppNmKmYS6IldkhSlXh0d1mHH6LMYYiWQbIPthG0sk/+EbAIOKA5RBuu08W/PKLnwLQaxBTYAr457SuJOMLbzZye4ZLG9MptaGqIINIsVkAerwgGHI45SBnlYVgvdLI221YIKoqAPdH6N9j+B5O20gr02/HQ87+k639lYP4KM26ta1Us4TCf1hU1tunls/Craufs5X5g86PN2za9vTLR4as+iraOIF1YYjEH5CMcQgnVJsjk5ME8z2bE9biXERJ9W114HnY4m+Pu8dvu+ubXCFNwBVlCviGKdZhh+OPyZmHqlQXG1YWOyTpYrS7peTquRC7xG+HjIPCM7r5PTyAQcXR+zAb4BjjHLNwDfAg/WPjMQucwK6u7cu/Q9MXiNsxC6//i6iAfDjDzRBbBkyXnwDWQs3zJ7/1EsZ/4ZpD+eqwPIwAX4dInHshtAGwhz/iE5SLgdIfwLnaFD0PXeqXgAAAAASUVORK5CYII=",
		onCreated(btn) {
			btn.setAttribute("image", this.image);
			var doc = btn.ownerDocument;

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

			btn.linkedObject = this;
			for(var type of ["command", "contextmenu"])
				btn.setAttribute("on" + type, `linkedObject.${type}(event)`);
		},
		createPopup(doc, btn, name, data) {
			var popup = doc.createElementNS(xul_ns, "menupopup");
			var prop = name + "Popup";
			btn.popups.push(btn[prop] = popup);
			popup.id = this.id + "-" + prop;
			for (var type of ["popupshowing", "click"])
				popup.setAttribute("on" + type, `parentNode.linkedObject.${type}(event)`);
			for(var obj of data) popup.append(this.createElement(doc, obj));
			btn.append(popup);
		},
		map: {b: "Bool", n: "Int", s: "String"},
		createElement(doc, obj) {
			if (!obj) return doc.createElementNS(xul_ns, "menuseparator");
			var pref = doc.ownerGlobal.Object.create(null), node, img, bool;
			for(var [key, val] of Object.entries(obj)) {
				if (key == "pref") {
					var [apref, lab, akey, ttt] = val;
					pref.pref = apref; pref.lab = lab || apref;
					if (ttt) pref.ttt = ttt;
				}
				else if (key == "image") img = val, pref.img = true;
				else if (key != "values") pref[key] = val;
				else pref.hasVals = true;
			}
			var type = prefs.getPrefType(pref.pref);
			var str = this.map[type == prefs.PREF_INVALID
				? obj.values ? (typeof obj.values[0][0])[0] : "b"
				: type == prefs.PREF_BOOL ? "b" : type == prefs.PREF_INT ? "n" : "s"
			];
			pref.get = prefs[`get${str}Pref`];
			pref.set = prefs[`set${str}Pref`];

			node = doc.createElementNS(xul_ns, "menu");
			node.className = "menu-iconic";
			node.setAttribute("closemenu", "none");
			img && node.setAttribute("image", img);
			akey && node.setAttribute("accesskey", akey);
			(node.pref = pref).vals = doc.ownerGlobal.Object.create(null);
			this.createRadios(doc,
				str.startsWith("B") && !pref.hasVals ? [[true, "true"], [false, "false"]] : obj.values,
				node.appendChild(doc.createElementNS(xul_ns, "menupopup"))
			);
			if ("userChoice" in obj) pref.noAlt = !("userAlt" in obj);
			return node;
		},
		createCloseMenusOption(doc, btn) {
			var pn = this.closePref = "QuickToggleAboutConfigSettings.closeMenus";
			var data = [null, {
				pref: [pn, "Автозакрытие этого меню"], values: [[true, "Да"], [false, "Нет"]]
			}];
			var setCloseMenus = e => {
				e.stopPropagation();
				var trg = e.target, {pref, val} = trg, updPopup = true, clear;
				switch(e.type) {
					case "command": pref = (trg = trg.closest("menu")).pref; updPopup = false; break;
					case "click": if (e.button) return; break;
					case "contextmenu": e.preventDefault(); clear = pref;
				}
				if (!pref) return;
				if (clear) prefs.clearUserPref(pn);
				else if (!updPopup && val === pref.val) return;
				else pref.set(pn, val !== undefined ? val : !pref.val);
				this.upd(trg);
				updPopup && this.popupshowing(null, trg.querySelector("menupopup"));
			}
			(this.createCloseMenusOption = (doc, btn) => {
				for(var obj of data)
					btn.secondaryPopup.append(this.createElement(doc, obj));
				var m = btn.secondaryPopup.lastChild;
				m.style.cssText = "fill: lightblue !important; list-style-image: url(chrome://browser/skin/menu.svg) !important;";
				m.setAttribute("oncommand", "setCloseMenus(event)");
				m.onclick = m.oncontextmenu = m.setCloseMenus = setCloseMenus;
			})(doc, btn);
		},
		UserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHqSURBVDhPpVJBTxNBGH1LLakxVEJQQIkhlZCAJDYR/BGScNCDwZMcuEji2ZP+BD14MCGciDEaEw8e1At3QGLlwAUOFtm6tut2uwtb2t3p8GbZ0iXlVF/y5ttkv+/Nmzej5XI5/A+6otoxTh1ks9mwhniJHvRiBAHXLjRIEy7yeIqjqAPNuXYHS5jGVby7PTT27eHo7Or9zL3Va/19Xyj5FsuYjLpOcdbBG9xFGh+fZRaGJ5O3tB7tMrcXcGQZP+pb8tXuyjpdPcA89HYHynYazxdvzg5nkgOapFsPNleXP+uY6L6hzYxMT+EC5vCVa4SWAM880Ju4cyVxUXNkERUeuyJLIR1+O6wTqcEEUngEA5eiqZgAAxtMpfur0sIBLA7+hS2NkK5UGVpsqgEJjLMkT4biAkzbE27D5u7/ZAFWSB1l0iRtUokyEj+aCBEXMHcOA70Y/IElfsMUeyiJPIpBHiZZEvv4Vcsrge9Mi1ZO0BJQ98yw9w4tWT4yYNV1ki78AuyggHLNwLZjC1Txnpl60VRMQD2SBl5sGsHaT88XJd+DEzioCAd6rYpN1xf7Jj5T4BMe00eE9pf4GteZ8Ry6mbYKDDyzxAblP4TDT1BUbc2585+yumd1Vc201ZmV7djObQKdopVBRwCOAVyl9qcQtNEaAAAAAElFTkSuQmCC",
		notUserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHqSURBVDhPpVJNSxtRFD1PJ9GMMVpaKyWhRGwRFy7Utv+gVFyUgoWif8Ct+APMsvQX+BuUWhdBcOvSDwi1XVhK0WgmTr4mmphoJvPRe5NnxhLdpAcO9828d869794nXNfF/6BLxo7RqiAcDjciYx3oDwFRBxikT4ey5CtAcg64aZ4ANE1rxDaDLeB176Aae/zx/ZR/NDqEuu1UD39oxsZ2wnIRmwV+8rl7DeLAGzXg+/pidSWivBwVCPQDvH91AfPot/tn6fOedW3NzTT0TYNWD7hsVfWtjH1ZjCjPggImFV25AKpE+xr+6CMxsvzpVXePmE8AipR5Bnzn4bcT0yJoCZSyQDkPXOrEDDHXYG8k0B18/nSBdvukzDPghqlB/xOUi4BBgiIJDUleF0lmVaEo7ngd8EnZP2N0uiolBwXKmksDegrISubovvyPWauR3kPLgBb5q+O8hnMyOD8jJoknTaZPKZLRSQp2+eaA6q9JmWcwTHPWv2cT0E0XGcqos4lkhgz4X7Jql3O1NZNaK2WewSQ9EtqIHcWPd5FybWQNoECNNApkdgmcwv61k47TbDbf0VykrP0hfaPlADAfCmFBERiHQN2sY79UwTqLP1BX+NyDL5HBc+ZR3Xab78xl383cZtAp7o6xAwB/AT+Y7GFJS/NjAAAAAElFTkSuQmCC",
		UserAltImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAH7SURBVDhPpVJLa1NBFP5mJolNcrWhtiq9Betjk0VFfNS1O7ciXbQguBDXxR9g3Lnown8g3SqCi24K/gCxVbqogiBaxdyY5nFrkt5H5j7GmZvxphDdxA8+Dgzn+86Zcw4RQuB/QHUcG2kHpmkmUWHtEo6DY55mUCIhYkHQOlXA97vv4esUWJaVxBGDp2Vcz54klakbN69kZ8wZRHHsVT9b7e2tHdFH5eEuPqi8vxqsLWDxmIGX5+8/mMufPkdA8zIBiMMuvNo3sbf+bCt2cWd1V+kHBukMVNuFE3h08d7SXHF6glC4oHFXGnTBmA9j1iBnb9+6xvJYfryIjJYdGaL889TlC1cLkyFhQQek3wThjYSUt8GCX9Iky/JniitGF0WtGhpQgdLEJJvO8AMpUgIbtK/JbflmI0cc2Y0oy/JZLRsaEIKYcCemXgvUlVXdOpj/c0Bnf0BXdhXxQEsSpAZqVU7NtkRPJh5aoAmrCYnzA9SpIbDr4G74Lsihr2VDA7Xn5kdvx2mGgnr7soNaSubXQb0GelUv8lt4Pj8rJ6yRGqgjERyVT68P3rarJIo6HojrJOS2h8Yejb6+OdyIe3i1tIlIy0YP6ckCTCOH5WIJKxmGsnwKeB/bbgcvlHj1Cxoq75+XqKD2rFb1Z9rqz6rto5VHDMZFOoPxAPwGQ077rbKVfhEAAAAASUVORK5CYII=",
		upd(node) {
			var {pref} = node, def = false, user = false, val;
			if (prefs.getPrefType(pref.pref) != prefs.PREF_INVALID) {
				var pn = pref.pref;
				try {val = pref.defVal = db[pref.get.name](pn); def = true}
				catch(ex) {def = false;}
				var user = prefs.prefHasUserValue(pn);
				if (user) try {val = pref.get(pn, undefined);} catch(ex) {}
			}
			if (val == pref.val && def == pref.def && user == pref.user) return;
			pref.val = val; pref.def = def; pref.user = user;
			var exists = def || user;

			var ttt = exists ? val : "Этот преф не существует";
			if (ttt === "") ttt = "[ empty_string ]";
			ttt += "\n" + pref.pref;
			if (pref.ttt) ttt += "\n" + pref.ttt;
			node.tooltipText = ttt;

			var img, alt = "userAlt" in pref && val == pref.userAlt;
			if (alt) img = this.UserAltImg;
			if ("userChoice" in pref)
				if (val == pref.userChoice)
					img = this.UserChoiceImg;
				else {
					if (!alt) img = this.notUserChoiceImg;
				}
			if (!pref.img) img
				? node.setAttribute("image", img)
				: node.removeAttribute("image");
			user
				? node.style.setProperty("font-style", "italic", "important")
				: node.style.removeProperty("font-style");

			var {lab} = pref;
			if (exists && pref.hasVals) {
				if (val in pref.vals) var sfx = pref.vals[val] || val;
				else var sfx = user ? "Другое" : "По умолчанию";
				lab += ` — "${sfx}"`;
			}
			node.setAttribute("label", lab);
		},
		createRadios(doc, vals, popup) {
			for(var arr of vals) {
				if (!arr) {
					popup.append(doc.createElementNS(xul_ns, "menuseparator"));
					continue;
				}
				var [val, lab, key, ttt] = arr;
				var menuitem = doc.createElementNS(xul_ns, "menuitem");
				menuitem.setAttribute("type", "radio");
				menuitem.setAttribute("closemenu", "none");
				menuitem.style.setProperty("font-style", "italic", "important"),
				menuitem.setAttribute("label", popup.parentNode.pref.vals[val] = lab);
				key && menuitem.setAttribute("accesskey", key);
				var tip = menuitem.val = val;
				if (ttt) tip += "\n" + ttt;
				menuitem.tooltipText = tip;
				popup.append(menuitem);
			}
		},
		openPopup(popup) {
			var btn = popup.parentNode;
			if (btn.domParent != btn.parentNode) {
				btn.domParent = btn.parentNode;
				var pos;
				if (btn.matches(".widget-overflow-list > :scope"))
					pos = "after_start";
				else var win = btn.ownerGlobal, {width, height, top, bottom, left, right} =
					btn.closest("toolbar").getBoundingClientRect(), pos = width > height
						? `${win.innerHeight - bottom > top ? "after" : "before"}_start`
						: `${win.innerWidth - right > left ? "end" : "start"}_before`;
				for(var p of btn.popups) p.setAttribute("position", pos);
			}
			popup.openPopup(btn);
		},
		maybeRestart(node, conf) {
			var msgRest = "Перезапустить браузер?", msgAbort = "Запрос на выход отменен.";
			if (pv >= 77) {
				var title = node.closest("toolbarbutton").label;
				var pp = domWin => Services.prompt.wrappedJSObject.pickPrompter({
					domWin, modalType: Ci.nsIPrompt.MODAL_TYPE_WINDOW
				});
				var confirm = win => pp(win).confirm(title, msgRest);
				var alert = win => pp(win).alert(title, msgAbort);
			} else {
				var confirm = win => win.confirm(msgRest);
				var alert = win => win.alert(msgAbort);
			}
			return (this.mayBeRestart = (node, conf) => {
				var win = node.ownerGlobal;
				if (conf && !confirm(win)) return;
				if (win.BrowserUtils.restartApplication() === false) alert(win);
				else return true;
			})(node, conf);
		},
		regexpRefresh: /^(?:view-source:)?(?:https?|ftp)/,
		maybeRe(node, fe) {
			var {pref} = node;
			if ("restart" in pref) {
				if (this.maybeRestart(node, pref.restart)) return;
			}
			else this.popupshowing(fe, node.parentNode);
			if ("refresh" in pref) {
				var win = node.ownerGlobal;
				if (this.regexpRefresh.test(win.gBrowser.currentURI.spec)) pref.refresh
					? win.BrowserReloadSkipCache() : win.BrowserReload();
			}
		},
		maybeClosePopup(e, trg) {
			!e.ctrlKey && prefs.getBoolPref(this.closePref, undefined)
				&& trg.parentNode.hidePopup();
		},
		command(e) {
			var trg = e.target;
			if (trg.btn) return this.openPopup(trg.primaryPopup);

			var menu = trg.closest("menu"), newVal = trg.val;
			this.maybeClosePopup(e, menu);
			if (newVal != menu.pref.val)
				menu.pref.set(menu.pref.pref, newVal),
				this.maybeRe(menu, true);
		},
		popupshowing(e, trg = e.target) {
			if (trg.state == "closed") return;
			if (trg.id) {
				for(var node of trg.children) {
					if (node.nodeName.endsWith("r")) continue;
					this.upd(node);
					!e && node.open && this.popupshowing(null, node.querySelector("menupopup"));
				}
				return;
			}
			var {pref} = trg.closest("menu"), findChecked = true;

			var findDef = "defVal" in pref;
			var checked = trg.querySelector("[checked]");
			if (checked) {
				if (checked.val == pref.val) {
					if (findDef) findChecked = false;
					else return;
				}
				else checked.removeAttribute("checked");
			}
			if (findDef) {
				var def = trg.querySelector("menuitem:not([style*=font-style]");
				if (def)
					if (def.val == pref.defVal) {
						if (findChecked) findDef = false;
						else return;
					}
					else def.style.setProperty("font-style", "italic", "important");
			}
			for(var node of trg.children) if ("val" in node) {
				if (findChecked && node.val == pref.val) {
					node.setAttribute("checked", true);
					if (findDef) findChecked = false;
					else break;
				}
				if (findDef && node.val == pref.defVal) {
					node.style.removeProperty("font-style");
					if (findChecked) findDef = false;
					else break;
				}
			}
		},
		contextmenu(e) {
			var trg = e.target;
			if (trg.btn) {
				if (e.ctrlKey || e.shiftKey) return;
				if (e.detail == 2) return trg.secondaryPopup.hidePopup();
				this.openPopup(trg.secondaryPopup);
			}
			else if ("pref" in trg) {
				this.maybeClosePopup(e, trg);
				if (trg.pref.user)
					prefs.clearUserPref(trg.pref.pref),
					this.maybeRe(trg);
			}
			e.preventDefault();
		},
		click(e) {
			if (e.button) return;
			var trg = e.target, {pref} = trg;
			if (!pref) return;

			this.maybeClosePopup(e, trg);
			if (!("noAlt" in pref)) return;

			if (pref.val == pref.userChoice)
				if (pref.noAlt) return;
				else  pref.set(pref.pref, pref.userAlt);
			else
				pref.set(pref.pref, pref.userChoice);
			this.maybeRe(trg);
		}
	};
});


Или постом выше с другими иконками.
Параметры прописываете СВОИ, готовые добавлены в качестве примера.

Отредактировано _zt (10-09-2020 19:48:58)

Отсутствует

 

№1503110-09-2020 18:05:55

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1421
UA: Firefox 78.0

Re: Custom Buttons

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

Отредактировано _zt (10-09-2020 18:19:57)

Отсутствует

 

№1503210-09-2020 18:41:03

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1421
UA: Firefox 78.0

Re: Custom Buttons

Question
Я специально для вас последнюю строку в свой пост добавил.

Параметры прописываете СВОИ, готовые добавлены в качестве примера.

   
Кстати, и хоткеи должен работать, например pref: ["network.proxy.type", "Настройки прокси", "хоткей"], Dumby вроде не вырезал их. И их, как бы это не было странно, тоже придется вам добавлять самостоятельно.
   
Еще раз для непонятливых:

userChoice: value, userAlt: value,
    ЛКМ по пункту переключет его значение на userChoice (создает параметр, если его не существует), а с userChoice на userAlt.
    ПКМ по пункту сбрасывает его значение на по умолчанию, либо удаляет его, если такого параметра по умолчанию в Firefox не существует.

Т.е., если в пункте не прописаны userChoice и userAlt, то клики ЛКМ работать не будут.
Если прописан только userChoice, то клик ЛКМ будет работать только, если текущее значение не равно userChoice.
Если прописаны оба, то клик ЛКМ будет переключать значение между ними (или на userChoice, если текущее значение не равно ни userChoice, ни userAlt).
   
Клик ПКМ тоже работать не будет, если текущее значение уже равно значению по умолчанию. :)
   
Добавлено 10-09-2020 18:44:08
Question
Да нет, я то все понял, а вот ты двух написанных слов понять не можешь.

вообще кто придумал такое?

Я придумал. Не нравится, пользуйся старым. :dumb:

Отредактировано bunda1 (19-09-2020 10:25:45)

Отсутствует

 

№1503310-09-2020 20:00:00

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1421
UA: Firefox 78.0

Re: Custom Buttons

Question

Какой-то не доделок получился.

Да, есть такое, но только не в скрипте. :)
   

(userChoice: true, userAlt: false) переключать по ЛКМ стало удобнее, но цвет не красный.

С какого препугу там красный должен быть? Возвращайтесь назад и читайте все заново, до просветления.
   
Исправил основной пост выше. Просвещайтесь. Хоткеи еще и к значениям добавлять надо.

Отсутствует

 

№1503410-09-2020 20:23:37

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1421
UA: Firefox 78.0

Re: Custom Buttons

Question
Попап параметр показывает, значение там прицепом.
Хоткеи работают, я проверил прежде чем писать.

Отредактировано _zt (11-09-2020 11:59:22)

Отсутствует

 

№1503512-09-2020 11:29:52

Sergeys
Administrator
 
Группа: Administrators
Откуда: Moscow, Russia
Зарегистрирован: 23-01-2005
Сообщений: 14008
UA: Firefox 80.0
Веб-сайт

Re: Custom Buttons

пишем только по теме


Через сомнения приходим к истине. Цицерон

Отсутствует

 

№1503614-09-2020 16:29:06

danxak70
Участник
 
Группа: Members
Зарегистрирован: 13-06-2020
Сообщений: 1
UA: Firefox 77.0

Re: Custom Buttons

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

На данный момент у меня установлены Firefox 77 и CustomBattons 16 (последняя)
Работает уже часть кнопок которые вы здесь обсуждаете...
Но из старых кнопок, которые стояли в Firefox 56 ещё, не работает ни одна.
А к некоторым кнопкам я как бы уже настолько привык, что без них не вижу даже смысла использовать дальше CustomButtons.
Подскажите в этой связи, какую кнопку установить, чтобы реализовать функции быстрого сохранения открытых вкладок в отдельную папку закладок на панели закладок одним нажатием кнопки...?! Папка при этом должна создаваться автоматом, скажем, с указанием даты...
Кто-то из вас испульзует сейчас такие кнопки...?! Может быть старые модифицировал уже кто-то под себя???

Неплохо будет, если у такой кнопки будут и дополнительные функции... например добавление в уже созданную папку одной отдельной вкладки через ПКМ и т.д.
Ну вобщем чем круче будет такая кнопка, и чем больше у неё будет реализовано полезных функций, тем лучше.
Очень надо бы такую кнопку...! Раньше их было много... на выбор и на любой вкус... А как быть сейчас?!


И ещё просьба в этой связи...
У кого-нибудь есть понятный и подробный мануал по работе с DOM-инспектором...?!
Вообще... чем вы отлаживаете кнопки, которые не работают на новых версиях Firefox и CustomButtons...?!

Заранее благодарен всем, кто возможно откликнется на этот мой пост...!

Отсутствует

 

№1503715-09-2020 03:21:56

Dobrov
Участник
 
Группа: Members
Зарегистрирован: 04-10-2011
Сообщений: 397
UA: Firefox 56.0

Re: Custom Buttons

Всем экспертам — просьба от начинающих пользователей, желающих расширить функционал Firefox скриптами:
Необходимо создать тему, где объясняется, как подключить запуск скриптов, работающих на «системном» уровне.

То есть нужно доступно рассказать, как добавить расширение CustomButtons и UCF (user_chrome_files от VitaliyV), к разным версиям Firefox.
Это может быть отдельная тема или один пост, в котором собраны наиболее информативные сообщения.
На данный момент очень сложно с нуля подключить CustomButtons или user_chrome_files скрипты, к разным версиям Firefox.
То есть, даже опытный админ должен перечитать множество страниц данного форума, потратив значительное время.
Даже опытный пользователь ПК может быть не знаком с Firefox на таком серьёзном уровне, как подключение CustomButtons или UCF скриптов.
Невозможно знать всё! Поэтому и необходима отдельная статья/тема, в которой это объясняется!

Отсутствует

 

№1503815-09-2020 20:21:49

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1421
UA: Firefox 78.0

Re: Custom Buttons

Dobrov
UCF подключается одинаково ко всем версиям, кроме < 60, и все подробно расписано в самом архиве UCF.
Скрипты, в зависимости от функций применения, подключаются в трех разных файлах UCF и для каждого скрипта (которые, в массе своей, выкладываются в теме CSS, а не здесь), как правило, следует инструкция по подключению. Для начала подключите сам UCF, а когда возникнет вопрос по подключению определенного скрипта, задайте его в соответствующей теме и вам ответят. Или воспользуйтесь поиском по версии для печати темы по CSS.
   
Если коротко и упрощенно, то:
скрипты создающие кнопки, подключаются в custom_script.js,
скрипты работающие в главном окне, подключаются в custom_script_win.js,
скрипты для других окон и сайдбара подключаются, в custom_script_all_win.js.
   
Для всего существуют исключения, например, есть варианты скриптов состоящие из двух частей - кнопка для custom_script.js и основная часть для *_win.js или *_all_win.js. Также, скрипты для *_all_win.js, определенным образом (описанном далее в теме CSS), можно подключить в *_win.js. Еще есть варианты импорта скриптов из внешних файлов, их было много, но актуален последний в теме.
   
Поскольку VitaliyV все постоянно улучшает, то статический пост с единой инструкцией вряд ли получится. Ну и, если человек не может осилить инструкцию по подключению самомго UCF, то ему никакие описания не помогут, пример смотрите выше, на этой странице.

Отредактировано _zt (15-09-2020 20:23:45)

Отсутствует

 

№1503915-09-2020 21:26:54

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 476
UA: Firefox 80.0

Re: Custom Buttons

Dobrov пишет

очень сложно с нуля подключить CustomButtons

А тут в теме только один Dumby поддерживает исправления и  на нем все держится, если не он, темы не было. Уйдет и не будет больше CustomButtons, а больше энтузиастов нет и модераторы не горят желанием здесь поработать, так что поиск в помощь. Раньше больше было людей кто творил, а сейчас одни потребители. Никто здесь ни кому не обязан.

Отсутствует

 

№1504023-09-2020 10:23:20

solombala
Забанен
 
Группа: Members
Зарегистрирован: 20-07-2019
Сообщений: 652
UA: Firefox 81.0

Re: Custom Buttons

Dumby
В новых крякнула кнопка или код "Поиск Seasonvar"

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

Выделить код

Код:

Services.search.addEngine("data:text/xml," + encodeURIComponent(`

    <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
        <ShortName>Seasonvar.ru</ShortName>
        <Description>Сериалы ТУТ! Сериалы онлайн смотреть бесплатно. Смотреть онлайн.</Description>
        <InputEncoding>UTF-8</InputEncoding>
        <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADvklEQVRYhb1XTYgcRRR+JtNVr0ail0gUPLkehBwEVwgakMHNdr1XKwQ8RFQwF39uOXnxtkIwieAhetWLeoh7VVBE3IPgYY1gCIFA0OzOdL0aMYoH4y5xZsrDdjUzk91sz8BsQTFU09Pfz3uvXhXAjEax2FjoWP34MsCBWWHsDm7VKWGMwrh+7SiofScQnAlC+j9xeHvfwQvSbwnpO4FxIKTveKs+2TcXCsKF4EwMjINqOhODM1Fs9tRMwX9fgCPCGIX0oLvUjNV0ZhCciZ7wh5stwJkR6OTq1aR+hMBSs3LEW/3tzAh4q04OExj+DYwDYYxi8fWZgLcXG7kwbg6rT2S2y1H3hPRXMwEHABBnLgeH/cA46DozSLEvc+LrmQGn4UlfGY99cuD6cTg0U/B23nhOGO9OvtKB4PD7iT/qc/PsXu8I45vC2E3gaQ470HVmIKT/XgE4WAt4I89OF6S2PKnPA+ON3xbgyPg7geGhwPidMMauM4NU5+Oll1zwpLc6z4Cpp5zwlif9MwDAagsaweEXI6pz9YQn1R9Rm5Juh7ldBbp3JYf7axEorLoa2HwEALACcLCwahMA7gMA2DiBjw3bvRvoeAg84a3aIbg8D9ly2b9XW9DwrP8FAFi38Ig47EsqtaGNZjflVRISnq4FPj6uH4dDnvESAIA4c7FUFMO9Yj629QpjmAocAKCTq/eFtF0GODCJ9SkptxuQvjg1AWGMNxgeEMazeyneKfbBmbg+bQsubPakt7oLACCEa7XVjzlwswUPT6ee8LOCcAEAILCRWolXKq+2YWeitODw5ODz0PSkN9M6LDX/qhuC6gRUdsGC4dEp1OszwmatWrP5sxaBdPKx+K6w7gVnYnsxe3oKAihC+oO09ozFXvYPh6G92MiFUIQxetZvTwTeZj3nScci1y8MEVqrcuAeTlSll6uXOqReFMZYkFqZiMC2fRglz+bTs8KqT9PH99r3gzOxsOoUAIA4vO1J/zIZAdIfCmP0uV5Mzzp5dqzqfLspT1sv6UEnz44BAHjGS5701kQEpLTO8+jptTxY9HcsvSo/TN9b9U/6T5vUy9v3AfXKZC6w/rK81/306wl4sHTm4+FsHwF3JnaXTF8YY/t5mFttQWO5bGjCGAurrso8NKHsqjWd0Gc84x/pRBsc9kZuPCknqqOX7gVnup5xQxi3POGPJfFvSjEjeVVrXDsKqs16zlt1smOzN4TUO8J41hOe86wveNYXvFXnPalzBeF7aXqL54s8q1qwkLaFzV5Lbo6P/wEF7JeDr5SEggAAAABJRU5ErkJggg==</Image>
        <Url type="text/html" method="GET" template="http://seasonvar.ru/search">
            <Param name="q" value="{searchTerms}"/>
        </Url>
        <SearchForm>http://seasonvar.ru/</SearchForm>
    </SearchPlugin>

`), null, null);

Отсутствует

 

№1504123-09-2020 11:16:04

Dumby
Участник
 
Группа: Members
Зарегистрирован: 12-08-2012
Сообщений: 2130
UA: Firefox 52.0

Re: Custom Buttons

solombala
Bug 1632448 - Remove window.external.AddSearchProvider code

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

Выделить код

Код:

//Services.search.addEngine("data:text/xml," + encodeURIComponent(`
(Services.search.addOpenSearchEngine || Services.search.addEngine)("data:text/xml," + encodeURIComponent(`

Отсутствует

 

№1504223-09-2020 12:04:40

solombala
Забанен
 
Группа: Members
Зарегистрирован: 20-07-2019
Сообщений: 652
UA: Firefox 81.0

Re: Custom Buttons

Dumby
Не слабо...Эти жуки-баги плодятся , как котята...Еще, вот окно при запуске - белая вспышка, наверное, от dll портабла зависит?

Отсутствует

 

№1504325-09-2020 11:09:05

KOMMEHTATOP
Участник
 
Группа: Members
Зарегистрирован: 13-10-2015
Сообщений: 53
UA: Firefox 42.0

Re: Custom Buttons

Доброго Времени!,не судите строго
Нашел в прошлой теме ,что в Firefox 60 и выше отвалились кнопки и есть решение 
Ссылка

изменив в скрипте chrome://custombuttons/content/overlay.js 

var custombuttons = {  на  var cbu custombuttons = {

Кто пробывал, и если работает не пойму как изменить.
то есть перехожу по ссылке  нажимаю F12 -дальше не как ,не поиму что делать : толи консуль толи отладчик.


Distance Subordinatio!

Отсутствует

 

№1504425-09-2020 17:55:18

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 476
UA: Firefox 81.0

Re: Custom Buttons

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

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

Выделить код

Код:

// Добавить новый пункт "Перезапуск" в главном меню
(()=> {
    var menuItem = document.createXULElement("toolbarbutton");
    menuItem.setAttribute("id", "restart_firefox");               
    menuItem.setAttribute("label", "Перезапуск");
    menuItem.setAttribute("class", "subviewbutton subviewbutton-iconic");
    menuItem.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAuZJREFUeNqkU0lPU1EUPvcNHaCUWlpsERGooIhxwQPFSNUgKpEYTRQXhpi44g+48W8YFy7cmOgC4hRjDEGjosEQpQRRKQQoFmgZSqGldHrDvZ7XohJWJr7kyx3e/b57vnPOJYwx+J9POHpnJj8RRRAEATietxFCvLh1CtGEKERMIQbxsqeU0jDP81249r67Wdot7BCsFjh2rarMfKS81FxnLRSqcc+aSKp1C6uZlpn55BXK2KCmqu2/CdwfKmNug8C8HcedrspSs9VAiFGRKegQCSnY5zCVn29ynFaR3HnGLeGYj5xqGmDIBgJa3blGl3U9mgGNAzoRjIfmllIR3UKFq9DWULvLE19X4LLXJW3Esygs5wU0RdHHkkqX1RJdSQLlCXnzOTyOoh84QXil4sFAMHs3EFxrvtpWKS0vxMFiNYKazeYF5PzEYi8QDHJWod/nY4uo3k8IjKiplB5dF6L5eketlIhlQDTorhnIvwX0G9C/YjbyEVXVVoNz0YRGtZG8NQ5EUfTqpX7wZMy3PdsomlsL6WRSX9x+/vqHtLPGlKk+JHdj2SB3ESFSq/dAkYil6ns7HvwbAYDUfrFJSmXymS0pNsKzx0M+FP6Idc+RRYOhYXeZ/Ww0QxNUTi8r2Ww4J6BseVnMAKwmGFQ4TND7aEAPbwjJDzW0gg12AfNy0uHZa46CkEyFF2XkbW5PIsR5A9icJhiLKVBzqVVa+TJmi8yGhvFXsniPQ3bW16TDnCngLhLAPzW/qSlqdLsFMFpM8Olej6/yRqc0LzPgpUaP+5jeybCRUVkoRFnAVciBr+flhpJM+7Hlc0TCOQ8C33JreCtvfcRoPOE4XLtf9FTZid1eoAuw6FpAnp7xx/zTY8rCt146+SJAVyZAr45AIxPlsOwfh2x8lPruD3Clh94v/WxrgZKaeuBE/S1sgpr5Cqv+UTbdP8nWZ02450SsITSiPxaEqNtB8P/wgvXWTSHSGAH9JcAAMrRspwHKXNQAAAAASUVORK5CYII=");
    menuItem.onclick =()=> setTimeout(()=> Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart), 500);    
    addDestructor(()=> menuItem.remove());
    var it = document.getElementById("appMenu-quit-button");
    it.parentNode.insertBefore(menuItem, it);
// или it.insertBefore(menuItem, it);
})();

и кнопка накрылась Custom Buttons: Source Editor

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

Выделить код

Код:

if(true) return; // Disabled by Disable Initialization button

// https://github.com/Infocatcher/Custom_Buttons/tree/master/CB_Source_Editor
// http://infocatcher.ucoz.net/js/cb/cbSourceEditor.js

// Source Editor (formerly Orion Editor) button for Custom Buttons
// (code for "initialization" section)

// (c) Infocatcher 2012-2019
// version 0.1.0a10 - 2019-12-25

var options = {
	cssInHelp: true,
	codeMirror: {
		lineNumbers: true,
		enableCodeFolding: true,
		showTrailingSpace: true,
		lineWrapping: true,
		autocomplete: true,
		fontSize: 12
	},
	orion: {
		lineNumbers: true
	}
};
// Also see devtools.editor.* preferences in about:config

const watcherId = "customButtonsSourceEditor_" + 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 = "_cbSourceEditorStorage";
	// 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 = {
		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);
		},
		get platformVersion() {
			delete this.platformVersion;
			return this.platformVersion = parseFloat(Services.appinfo.platformVersion);
		},
		get hasCodeMirror() {
			delete this.hasCodeMirror;
			return this.hasCodeMirror = Services.appinfo.name == "Pale Moon" //~ todo: test
				|| this.platformVersion >= 27;
		},
		init: function(reason) {
			if(!this.hasCodeMirror) {
				this.isBrowserWindow = function() {
					return false;
				};
			}
			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);
		},
		initWindow: function(window, reason, isFrame) {
			if(this.isBrowserWindow(window)) {
				this.initBrowserWindow(window, reason);
				return;
			}
			if(!this.isEditorWindow(window))
				return;
			_log("initWindow(): isFrame: " + isFrame);
			var document = window.document;
			if(isFrame)
				window.addEventListener("unload", this, false);

			Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
				.getService(Components.interfaces.mozIJSSubScriptLoader)
				.loadSubScript("chrome://global/content/globalOverlay.js", window);

			var isCodeMirror = false;
			try { // See chrome://browser/content/devtools/scratchpad.js
				Components.utils.import("resource:///modules/source-editor.jsm", window);
			}
			catch(e) {
				var loader = this.platformVersion >= 44 // See https://bugzilla.mozilla.org/show_bug.cgi?id=912121
					? "resource://devtools/shared/Loader.jsm"
					: "resource://gre/modules/devtools/Loader.jsm";
				var g = Components.utils.import(loader, {});
				var require = (g.devtools || g).require;
				[
					"devtools/sourceeditor/editor",
					"devtools/client/sourceeditor/editor", // Firefox 44+
					"devtools/client/shared/sourceeditor/editor" // Firefox 68+
				].some(function(path) {
					try {
						return window.SourceEditor = require(path);
					}
					catch(e) {
					}
					return null;
				});
				isCodeMirror = true;
			}
			var SourceEditor = window.SourceEditor;

			// See view-source:chrome://browser/content/devtools/scratchpad.xul
			// + view-source:chrome://browser/content/devtools/source-editor-overlay.xul
			var psXUL = (isCodeMirror
			? '<!DOCTYPE popupset [\
				<!ENTITY % editMenuStrings SYSTEM "chrome://global/locale/editMenuOverlay.dtd">\
				%editMenuStrings;\
				<!ENTITY % sourceEditorStrings SYSTEM "' + (
					Services.appinfo.name == "Pale Moon" || Services.appinfo.name == "Basilisk"
						? this.platformVersion >= 4.1
							? "chrome://devtools/locale/sourceeditor.dtd"
							: "chrome://global/locale/devtools/sourceeditor.dtd"
						: this.platformVersion >= 45
							? "chrome://devtools/locale/sourceeditor.dtd"
							: "chrome://browser/locale/devtools/sourceeditor.dtd"
				) + '">\
				%sourceEditorStrings;\
			]>\
			<popupset id="sourceEditorPopupset"\
				xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">\
				<menupopup id="sourceEditorContext"\
					onpopupshowing="goUpdateSourceEditorMenuItems()">\
					<menuitem id="menu_undo" label="&undoCmd.label;" accesskey="&undoCmd.accesskey;"\
						oncommand="goDoCommand(\'cmd_undo\')" />\
					<menuitem id="menu_redo" label="&redoCmd.label;" accesskey="&redoCmd.accesskey;"\
						oncommand="goDoCommand(\'cmd_redo\')" />\
					<menuseparator/>\
					<menuitem id="menu_cut" label="&cutCmd.label;" accesskey="&cutCmd.accesskey;"\
						oncommand="goDoCommand(\'cmd_cut\')" />\
					<menuitem id="menu_copy" label="&copyCmd.label;" accesskey="&copyCmd.accesskey;"\
						oncommand="goDoCommand(\'cmd_copy\')" />\
					<menuitem id="menu_paste" label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;"\
						oncommand="goDoCommand(\'cmd_paste\')" />\
					<menuitem id="menu_delete" label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;"\
						oncommand="goDoCommand(\'cmd_delete\')" />\
					<menuseparator/>\
					<menuitem id="menu_selectAll" label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;"\
						oncommand="goDoCommand(\'cmd_selectAll\')" />\
					<menuseparator/>\
					<menuitem id="menu_find" label="&findCmd.label;" accesskey="&findCmd.accesskey;" />\
					<menuitem id="menu_findAgain" label="&findAgainCmd.label;" accesskey="&findAgainCmd.accesskey;" />\
					<menuseparator/>\
					<menuitem id="se-menu-gotoLine"\
						label="&gotoLineCmd.label;"\
						accesskey="&gotoLineCmd.accesskey;"\
						key="key_gotoLine"\
						oncommand="goDoCommand(\'cmd_gotoLine\')"/>\
				</menupopup>\
			</popupset>'
			: '<popupset id="sourceEditorPopupset"\
				xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">\
				<menupopup id="sourceEditorContext"\
					onpopupshowing="goUpdateSourceEditorMenuItems()">\
					<menuitem id="se-menu-undo"/>\
					<menuitem id="se-menu-redo"/>\
					<menuseparator/>\
					<menuitem id="se-menu-cut"/>\
					<menuitem id="se-menu-copy"/>\
					<menuitem id="se-menu-paste"/>\
					<menuitem id="se-menu-delete"/>\
					<menuseparator/>\
					<menuitem id="se-menu-selectAll"/>\
					<menuseparator/>\
					<menuitem id="se-menu-find"/>\
					<menuitem id="se-menu-findAgain"/>\
					<menuseparator/>\
					<menuitem id="se-menu-gotoLine"/>\
				</menupopup>\
			</popupset>'
			).replace(/>\s+</g, "><");

			var ps = this.parseXULFromString(psXUL);
            // "Edit Custom Button in Tab" button, Firefox 71+
            if(isFrame && "parseFromSafeString" in window.DOMParser.prototype)
                ps = window.MozXULElement.parseXULToFragment(ps.outerHTML);
			document.documentElement.appendChild(ps);

			window.setTimeout(function() {
				function appendNode(nodeName, id) {
					var node = document.createElementNS(xulns, nodeName);
					node.id = id;
					document.documentElement.appendChild(node);
				}
				appendNode("commandset", "editMenuCommands");
				appendNode("commandset", "sourceEditorCommands");
				appendNode("keyset", "sourceEditorKeys");
				appendNode("keyset", "editMenuKeys");

				this.loadOverlays(
					window,
					function done() {
						window.setTimeout(function() {
							var mp = document.getElementById("sourceEditorContext");
							if(mp.state == "closed")
								return;
							Array.prototype.forEach.call(
								mp.getElementsByAttribute("command", "*"),
								function(mi) {
									var cmd = mi.getAttribute("command");
									var controller = document.commandDispatcher
										.getControllerForCommand(cmd);
									if(controller && !controller.isCommandEnabled(cmd))
										mi.setAttribute("disabled", "true");
								}
							);
						}, 0);
						if(!isCodeMirror)
							return;
						// See view-source:chrome://browser/content/devtools/scratchpad.xul in Firefox 27.0a1
						window.goUpdateSourceEditorMenuItems = function() {
							goUpdateGlobalEditMenuItems();
							var commands = ["cmd_undo", "cmd_redo", "cmd_cut", "cmd_paste", "cmd_delete"];
							commands.forEach(goUpdateCommand);
						};
						var cmdsMap = {
							"se-menu-undo":   "cmd_undo",
							"se-menu-redo":   "cmd_redo",
							"se-menu-cut":    "cmd_cut",
							"se-menu-copy":   "cmd_copy",
							"se-menu-paste":  "cmd_paste",
							"se-menu-delete": "cmd_delete",
							__proto__: null
						};
						for(var id in cmdsMap) {
							var mi = document.getElementById(id);
							mi && mi.setAttribute("command", cmdsMap[id]);
						}
						// We can't use command="cmd_selectAll", menuitem will be wrongly disabled sometimes
						var enabledCmdsMap = {
							"se-menu-selectAll": "cmd_selectAll",
							"se-menu-findAgain": "cmd_findAgain",
							__proto__: null
						};
						for(var id in enabledCmdsMap) {
							var mi = document.getElementById(id);
							if(mi) {
								mi.removeAttribute("command");
								mi.removeAttribute("disabled");
								mi.setAttribute("oncommand", "goDoCommand('" + enabledCmdsMap[id] + "');");
							}
						}
						// Workaround: emulate keyboard shortcut
						var keyCmdsMap = {
							"menu_find":      { keyCode: KeyboardEvent.DOM_VK_F, charCode: "f".charCodeAt(0), ctrlKey: true },
							"menu_findAgain": { keyCode: KeyboardEvent.DOM_VK_G, charCode: "g".charCodeAt(0), ctrlKey: true },
							__proto__: null
						};
						var _key = function() {
							var e = this._keyData;
							var evt = document.createEvent("KeyboardEvent");
							evt.initKeyEvent(
								"keydown", true /*bubbles*/, true /*cancelable*/, window,
								e.ctrlKey || false, e.altKey || false, e.shiftKey || false, e.metaKey || false,
								e.keyCode || 0, e.charCode || 0
							);
							document.commandDispatcher.focusedElement.dispatchEvent(evt);
						};
						for(var id in keyCmdsMap) {
							var mi = document.getElementById(id);
							if(mi) {
								mi.removeAttribute("command");
								mi.removeAttribute("disabled");
								mi.setAttribute("oncommand", "this._key();");
								mi._keyData = keyCmdsMap[id];
								mi._key = _key;
							}
						}
						// Fix styles for autocomplete tooltip
						function css(uri) {
							document.insertBefore(document.createProcessingInstruction(
								"xml-stylesheet",
								'href="' + uri + '" type="text/css"'
							), document.documentElement);
						}
						css("resource://devtools/client/themes/variables.css");
						css("resource://devtools/client/themes/common.css");
						css("chrome://devtools/skin/tooltips.css");
						if(this.platformVersion >= 68) window.setTimeout(function fixSelection() {
							var sheets = document.styleSheets;
							for(var i = sheets.length - 1; i >= 0; --i) {
								var sheet = sheets[i];
								if(sheet.href != "resource://devtools/client/themes/common.css")
									continue;
								try {
									var rules = sheet.cssRules;
								}
								catch(e) {
									// InvalidAccessError:
									// A parameter or an operation is not supported by the underlying object
									return window.setTimeout(fixSelection, 10);
								}
								for(var j = 0, len = rules.length; j < len; ++j)
									if(rules[j].selectorText == "::selection")
										return !sheet.deleteRule(j);
								break;
							}
							return false;
						}, 10);
					}.bind(this),
					["chrome://global/content/editMenuOverlay.xul", function check(window) {
						return window.document.getElementById("editMenuCommands").hasChildNodes();
					}],
					["chrome://browser/content/devtools/source-editor-overlay.xul", function check(window) {
						return window.document.getElementById("sourceEditorCommands").hasChildNodes();
					}]
				);
			}.bind(this), 500); // We should wait to not break other extensions with document.loadOverlay()

			var tabs = document.getElementById("custombuttons-editbutton-tabbox");
			var selectedPanel = tabs.selectedPanel;
			Array.prototype.slice.call(document.getElementsByTagName("cbeditor")).forEach(function(cbEditor) {
				if("__sourceEditor" in cbEditor)
					return;
				var code = cbEditor.value;
				var isCSS = options.cssInHelp && cbEditor.id == "help";
				if(isCodeMirror) {
					var opts = {
						mode: isCSS
							? SourceEditor.modes.css
							: SourceEditor.modes.js,
						value: code,
						lineNumbers: true,
						enableCodeFolding: true,
						showTrailingSpace: true,
						autocomplete: true,
						contextMenu: "sourceEditorContext"
					};
					var optsOvr = options.codeMirror;
					for(var opt in optsOvr) if(optsOvr.hasOwnProperty(opt))
						opts[opt] = optsOvr[opt];
					var se = new SourceEditor(opts);
					if("codeMirror" in se) window.setTimeout(function() {
						if("insertCommandsController" in se)
							se.insertCommandsController(); // Pale Moon and Basilisk
						else
							this.insertCommandsController(se);
					}.bind(this), 200);
				}
				else {
					var se = new SourceEditor();
				}
				se.__isCodeMirror = isCodeMirror;
				var seElt = document.createElementNS(xulns, "hbox");
				if(cbEditor.id)
					seElt.id = "sourceEditor-" + cbEditor.id;
				seElt.className = "sourceEditor";
				seElt.setAttribute("flex", 1);
				seElt.__sourceEditor = se;
				cbEditor.parentNode.insertBefore(seElt, cbEditor);
				//cbEditor.setAttribute("hidden", "true");
				cbEditor.setAttribute("collapsed", "true");
				cbEditor.parentNode.appendChild(cbEditor);
				cbEditor.__sourceEditor = se;
				cbEditor.__sourceEditorElt = seElt;
				cbEditor.__defineGetter__("value", function() {
					if("__sourceEditor" in this) {
						var se = this.__sourceEditor;
						if(!se.__initialized)
							return se.__value;
						return se.getText().replace(/\r\n?|\n\r?/g, "\n");
					}
					return this.textbox.value;
				});
				cbEditor.__defineSetter__("value", function(v) {
					if("__sourceEditor" in this) {
						var se = this.__sourceEditor;
						if(!se.__initialized) {
							var _this = this;
							se.__onLoadCallbacks.push(function() {
								_this.value = v;
							});
							return se.__value = v;
						}
						return se.setText(v.replace(/\r\n?|\n\r?/g, "\n"));
					}
					return this.textbox.value = v;
				});
				cbEditor.selectLine = function(lineNumber) {
					if("__sourceEditor" in this) {
						var se = this.__sourceEditor;
						if(!se.__initialized) {
							var _this = this, args = arguments;
							se.__onLoadCallbacks.push(function() {
								_this.selectLine.apply(_this, args);
							});
							return undefined;
						}
						if(se.__isCodeMirror) {
							//se.focus();
							//se.setCursor({ line: lineNumber - 1, ch: 0 });
							//~ todo: optimize
							var val = this.value;
							var lines = val.split("\n");
							var line = Math.min(lineNumber - 1, lines.length);
							var ch = lines[line].length;
							se.focus();
							return se.setSelection({ line: line, ch: 0 }, { line: line, ch: ch });
						}
						else {
							var selStart = se.getLineStart(lineNumber - 1);
							var selEnd = se.getLineEnd(lineNumber - 1, false);
							se.focus();
							return se.setSelection(selStart, selEnd);
						}
					}
					return this.__proto__.selectLine.apply(this, arguments);
				};

				// For edit_button() from chrome://custombuttons/content/editExternal.js
				seElt.__cbEditor = cbEditor;
				seElt.__defineGetter__("localName", function() {
					return "cbeditor";
				});
				seElt.__defineGetter__("value", function() {
					return this.__cbEditor.value;
				});
				seElt.__defineSetter__("value", function(val) {
					this.__cbEditor.value = val;
				});

				se.__initialized = false;
				se.__onLoadCallbacks = [];
				se.__value = code;
				var onTextChanged = se.__onTextChanged = function() {
					window.editor.changed = true;
				};
				var isLoaded = reason == this.REASON_WINDOW_LOADED;
				function done() {
					se.__initialized = true;
					se.__onLoadCallbacks.forEach(function(fn) {
						try {
							fn();
						}
						catch(e) {
							Components.utils.reportError(e);
						}
					});
					delete se.__onLoadCallbacks;
					delete se.__value;
				}
				if(isCodeMirror) {
					se.appendTo(seElt).then(function() {
						try {
							se.setupAutoCompletion();
						}
						catch(e) {
							Components.utils.reportError(e);
						}
						if("setFontSize" in se) try {
							se.setFontSize(options.codeMirror.fontSize);
						}
						catch(e) {
							Components.utils.reportError(e);
						}
						window.setTimeout(function() {
							window.editor.changed = false; // Strange...
							window.setTimeout(function() { // Workaround for unexpected onTextChanged() calls
								if(window.editor.changed && cbEditor.value == code)
									window.editor.changed = false;
							}, 100);
							se.on("change", onTextChanged);
							if(isLoaded) {
								if("clearHistory" in se)
									se.clearHistory();
								else {
									var seGlobal = Components.utils.getGlobalForObject(SourceEditor.prototype);
									// Note: this is resource://app/modules/devtools/gDevTools.jsm scope in Firefox 34+
									var cm = seGlobal.editors.get(se);
									cm.clearHistory();
								}
							}
						}, isFrame ? 50 : 15); // Oh, magic delays...
						done();

						// See resource:///modules/devtools/sourceeditor/editor.js
						// doc.defaultView.controllers.insertControllerAt(0, controller(this, doc.defaultView));
						var controllers = window.controllers; // nsIControllers
						var controller = se.__cmdController = controllers.getControllerAt(0);
						if("__cmdControllers" in tabs)
							tabs.__cmdControllers.push(controller);
						else {
							tabs.__cmdControllers = [controller];
							var onSelect = tabs.__onSelect = function() {
								var seElt = tabs.selectedPanel;
								if(!seElt || !("__sourceEditor" in seElt))
									return;
								var se = seElt.__sourceEditor;
								var curController = se.__cmdController;
								tabs.__cmdControllers.forEach(function(controller) {
									try {
										if(controller == curController)
											controllers.insertControllerAt(0, controller);
										else
											controllers.removeController(controller);
									}
									catch(e) {
									}
								});
							};
							tabs.addEventListener("select", onSelect, false);
							window.setTimeout(onSelect, 0); // Activate controller from selected tab
						}
					});
				}
				else {
					var opts = {
						mode: isCSS
							? SourceEditor.MODES.CSS
							: SourceEditor.MODES.JAVASCRIPT,
						showLineNumbers: true,
						initialText: code,
						placeholderText: code, // For backward compatibility
						contextMenu: "sourceEditorContext"
					};
					var optsOvr = options.orion;
					for(var opt in optsOvr) if(optsOvr.hasOwnProperty(opt))
						opts[opt] = optsOvr[opt];
					se.init(seElt, opts, function callback() {
						done();
						isLoaded && se.resetUndo && se.resetUndo();
						se.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onTextChanged);

						// Hack to use selected editor
						var controller = se.ui._controller;
						controller.__defineGetter__("_editor", function() {
							var seElt = tabs.selectedPanel;
							var se = seElt && seElt.__sourceEditor
								|| document.getElementsByTagName("cbeditor")[0].__sourceEditor;
							return se;
						});
						controller.__defineSetter__("_editor", function() {});
					});
				}
			}, this);
			// Trick to select correct tab (especially if was selected "Button settings" tab)
			tabs.tabs.advanceSelectedTab(1, true);
			tabs.tabs.advanceSelectedTab(-1, true);

			var origExecCmd = window.editor.execute_oncommand_code;
			window.editor.execute_oncommand_code = function() {
				var cd = document.commandDispatcher;
				var cdFake = {
					__proto__: cd,
					get focusedElement() {
						var selectedTab = tabs.selectedTab;
						if(selectedTab && selectedTab.id == "code-tab")
							return document.getElementById("code").textbox.inputField;
						return cd.focusedElement;
					}
				};
				document.__defineGetter__("commandDispatcher", function() {
					return cdFake;
				});
				try {
					var ret = origExecCmd.apply(this, arguments);
				}
				catch(e) {
					Components.utils.reportError(e);
				}
				// document.hasOwnProperty("commandDispatcher") == false, so we cat just delete our fake property
				delete document.commandDispatcher;
				return ret;
			};

			window.addEventListener("load", function ensureObserversAdded() {
				window.removeEventListener("load", ensureObserversAdded, false);
				window.setTimeout(function() { window.editor.removeObservers(); }, 0);
				window.setTimeout(function() { window.editor.addObservers();    }, 0);
			}, false);
			// Fix for Ctrl+S hotkey (catched by CodeMirror)
			var hke = this.handleKeyEvent;
			window.addEventListener("keydown",  hke, true);
			window.addEventListener("keypress", hke, true);
			window.addEventListener("keyup",    hke, true);
		},
		insertCommandsController: function(se) {
			this.insertCommandsController = insertCommandsController;
			return insertCommandsController(se);
			// devtools/client/sourceeditor/editor-commands-controller in Pale Moon/Basilisk
			function createController(ed) {
				return {
					supportsCommand: function (cmd) {
						switch (cmd) {
							case "cmd_find":
							case "cmd_findAgain":
							case "cmd_gotoLine":
							case "cmd_undo":
							case "cmd_redo":
							case "cmd_delete":
							case "cmd_selectAll":
								return true;
						}

						return false;
					},

					isCommandEnabled: function (cmd) {
						let cm = ed.codeMirror;

						switch (cmd) {
							case "cmd_find":
							case "cmd_gotoLine":
							case "cmd_selectAll":
								return true;
							case "cmd_findAgain":
								return cm.state.search != null && cm.state.search.query != null;
							case "cmd_undo":
								return ed.canUndo();
							case "cmd_redo":
								return ed.canRedo();
							case "cmd_delete":
								return ed.somethingSelected();
						}

						return false;
					},

					doCommand: function (cmd) {
						let cm = ed.codeMirror;

						let map = {
							"cmd_selectAll": "selectAll",
							"cmd_find": "find",
							"cmd_undo": "undo",
							"cmd_redo": "redo",
							"cmd_delete": "delCharAfter",
							"cmd_findAgain": "findNext"
						};

						if (map[cmd]) {
							cm.execCommand(map[cmd]);
							return;
						}

						if (cmd == "cmd_gotoLine") {
							ed.jumpToLine();
						}
					},

					onEvent: function () {}
				};
			}
			function insertCommandsController(sourceEditor) {
				let input = sourceEditor.codeMirror.getInputField();
				let controller = createController(sourceEditor);
				input.controllers.insertControllerAt(0, controller);
			}
		},
		destroyWindow: function(window, reason, isFrame) {
			if(reason == this.REASON_WINDOW_CLOSED)
				window.removeEventListener(this.loadEvent, this, false); // Window can be closed before DOMContentLoaded
			if(this.isBrowserWindow(window)) {
				this.destroyBrowserWindow(window, reason);
				return;
			}
			if(!this.isEditorWindow(window) || !("SourceEditor" in window))
				return;
			_log("destroyWindow(): isFrame: " + isFrame);
			var document = window.document;
			if(isFrame)
				window.removeEventListener("unload", this, false);

			var tabs = document.getElementById("custombuttons-editbutton-tabbox");
			if("__onSelect" in tabs) {
				tabs.removeEventListener("select", tabs.__onSelect, false);
				delete tabs.__onSelect;
				delete tabs.__cmdControllers;
			}

			Array.prototype.slice.call(document.getElementsByTagName("cbeditor")).forEach(function(cbEditor) {
				if(!("__sourceEditor" in cbEditor))
					return;
				var se = cbEditor.__sourceEditor;
				var isCodeMirror = se.__isCodeMirror;
				if(isCodeMirror)
					se.off("change", se.__onTextChanged);
				else
					se.removeEventListener(window.SourceEditor.EVENTS.TEXT_CHANGED, se.__onTextChanged);
				delete se.__onTextChanged;
				if(reason == this.REASON_SHUTDOWN) {
					var val = cbEditor.value;
					delete cbEditor.value;
					delete cbEditor.selectLine;

					var seElt = cbEditor.__sourceEditorElt;
					seElt.parentNode.insertBefore(cbEditor, seElt);
					seElt.parentNode.removeChild(seElt);
					delete cbEditor.__sourceEditorElt;
					delete cbEditor.__sourceEditor;
					delete seElt.__sourceEditor;
					delete seElt.__cbEditor;

					cbEditor.value = val;
					window.setTimeout(function() {
						cbEditor.removeAttribute("collapsed");
					}, 0);
				}
				se.destroy();
				if("__cmdController" in se) {
					try {
						window.controllers.removeController(se.__cmdController);
					}
					catch(e) {
					}
					delete se.__cmdController;
				}
			}, this);

			if(reason == this.REASON_SHUTDOWN) {
				delete window.editor.execute_oncommand_code;
				[
					"sourceEditorPopupset",
					"editMenuCommands",
					"sourceEditorCommands",
					"sourceEditorKeys",
					"editMenuKeys"
				].forEach(function(id) {
					var node = document.getElementById(id);
					node && node.parentNode.removeChild(node);
				});
				[
					// chrome://global/content/globalOverlay.js
					"closeWindow", "canQuitApplication", "goQuitApplication", "goUpdateCommand", "goDoCommand",
					"goSetCommandEnabled", "goSetMenuValue", "goSetAccessKey", "goOnEvent", "visitLink",
					"setTooltipText", "NS_ASSERT",
					// chrome://global/content/editMenuOverlay.xul => view-source:chrome://global/content/editMenuOverlay.js
					"goUpdateGlobalEditMenuItems", "goUpdateUndoEditMenuItems", "goUpdatePasteMenuItems"
				].forEach(function(p) {
					delete window[p];
				});
				for(var child = document.documentElement; child = child.previousSibling; ) {
					if(
						child.nodeType == child.PROCESSING_INSTRUCTION_NODE
						&& child.data.indexOf("://devtools/") != -1
					) {
						setTimeout(function(child) {
							child.parentNode.removeChild(child);
						}, 0, child);
					}
				}
				delete window.SourceEditor;
			}
			var hke = this.handleKeyEvent;
			window.removeEventListener("keydown",  hke, true);
			window.removeEventListener("keypress", hke, true);
			window.removeEventListener("keyup",    hke, true);
			//~ todo: we have one not removed controller!
			//LOG("getControllerCount(): " + window.controllers.getControllerCount());
		},
		initBrowserWindow: function(window, reason) {
			_log("initBrowserWindow()");
			window.addEventListener("DOMContentLoaded", this, false);
			Array.prototype.forEach.call(window.frames, function(frame) {
				this.initWindow(frame, reason, true);
			}, this);
		},
		destroyBrowserWindow: function(window, reason) {
			_log("destroyBrowserWindow()");
			window.removeEventListener("DOMContentLoaded", this, false);
			Array.prototype.forEach.call(window.frames, function(frame) {
				this.destroyWindow(frame, reason, true);
			}, this);
		},
		isEditorWindow: function(window) {
			return window.location.href.substr(0, 41) == "chrome://custombuttons/content/editor.xul";
		},
		isBrowserWindow: function(window) {
			var loc = window.location.href;
			return loc == "chrome://browser/content/browser.xul"
				|| loc == "chrome://browser/content/browser.xhtml" // Firefox 69+
				|| loc == "chrome://navigator/content/navigator.xul";
		},
		get loadEvent() { // "DOMContentLoaded" -> initWindow() may hang editor window (and browser)
			delete this.loadEvent;
			return this.loadEvent = this.platformVersion >= 73 ? "load" : "DOMContentLoaded";
		},
		observe: function(subject, topic, data) {
			if(topic == "quit-application-granted")
				this.destroy();
			else if(topic == "domwindowopened")
				subject.addEventListener(this.loadEvent, this, false);
			else if(topic == "domwindowclosed")
				this.destroyWindow(subject, this.REASON_WINDOW_CLOSED);
		},
		handleEvent: function(e) {
			switch(e.type) {
				case "DOMContentLoaded":
				case "load":
					//var window = e.currentTarget;
					var window = e.target.defaultView || e.target;
					window.removeEventListener(e.type, this, false);
					var isFrame = window != e.currentTarget;
					this.initWindow(window, this.REASON_WINDOW_LOADED, isFrame);
				break;
				case "unload":
					//var window = e.currentTarget;
					var window = e.target.defaultView || e.target;
					window.removeEventListener(e.type, this, false);
					this.destroyWindow(window, this.REASON_WINDOW_CLOSED, true);
			}
		},
		handleKeyEvent: function(e) {
			if(
				(e.keyCode == e.DOM_VK_S || String.fromCharCode(e.charCode).toUpperCase() == "S")
				&& e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey
			) {
				e.preventDefault();
				e.stopPropagation();
				if(e.type == "keydown") {
					var window = e.currentTarget;
					window.editor.updateButton();
				}
			}
		},
		loadOverlays: function() {
			this.runGenerator(this.loadOverlaysGen, this, arguments);
		},

		get loadOverlaysGen() {
			var fn = this._loadOverlaysGen.toString()
				.replace(/__yield/g, "yield");
			try {
				new Function("function test() { yield 0; }");
			}
			catch(e) { // Firefox 58+: SyntaxError: yield expression is only valid in generators
				fn = fn.replace("function", "function*"); // Firefox 26+
			}
			delete this.loadOverlaysGen;
			return this.loadOverlaysGen = eval("(" + fn + ")");
		},
		_loadOverlaysGen: function loadOverlaysGen(window, callback/*, overlayData1, ...*/) {
			var gen = loadOverlaysGen.__generator;
			for(var i = 2, l = arguments.length; i < l; ++i) {
				var overlayData = arguments[i];
				this.loadOverlay(window, overlayData[0], overlayData[1], function() {
					gen.next();
				});
				__yield(0);
			}
			callback();
			__yield(0);
		},
		loadOverlay: function(window, uri, check, callback) {
			var document = window.document;
			var stopWait = Date.now() + 4500;
			window.setTimeout(function load() {
				_log("loadOverlay(): " + uri);
				var tryAgain = Date.now() + 800;
				try {
					document.loadOverlay(uri, null);
				}
				catch(e) {
					window.setTimeout(callback, 0);
					return;
				}
				window.setTimeout(function ensureLoaded() {
					if(check(window))
						window.setTimeout(callback, 0);
					else if(Date.now() > stopWait)
						return;
					else if(Date.now() > tryAgain)
						window.setTimeout(load, 0);
					else
						window.setTimeout(ensureLoaded, 50);
				}, 50);
			}, 0);
		},
		runGenerator: function(genFunc, context, args) {
			var gen = genFunc.apply(context, args);
			genFunc.__generator = gen;
			gen.next();
		},
		parseXULFromString: function(xul) {
			xul = xul.replace(/>\s+</g, "><");
			try {
				return new DOMParser().parseFromString(xul, "application/xml").documentElement;
			}
			catch(e) {
				// See http://custombuttons.sourceforge.net/forum/viewtopic.php?f=5&t=3720
				// + https://forum.mozilla-russia.org/viewtopic.php?pid=732243#p732243
				var dummy = document.createElement("dummy");
				dummy.innerHTML = xul.trimLeft();
				return dummy.firstChild;
			}
		}
	};
	storage.set(watcherId, watcher);
	setTimeout(function() {
		watcher.init(watcher.REASON_STARTUP);
	}, 50);
}
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;

function ts() {
	var d = new Date();
	var ms = d.getMilliseconds();
	return d.toTimeString().replace(/^.*\d+:(\d+:\d+).*$/, "$1") + ":" + "000".substr(("" + ms).length) + ms + " ";
}
function _log(s) {
	Services.console.logStringMessage("[Custom Buttons :: Source Editor] " + ts() + s);
}


пишет ошибка синтактического анализа XML

Отредактировано Andrey_Krropotkin (25-09-2020 18:06:36)

Отсутствует

 

№1504525-09-2020 18:05:30

solombala
Забанен
 
Группа: Members
Зарегистрирован: 20-07-2019
Сообщений: 652
UA: Firefox 81.0

Re: Custom Buttons

Dumby
этот код сдох?
try {
    Components.interfaces.nsIUDPSocketChild ||
    Cc["@mozilla.org/process/environment;81.0"].getService(Ci.nsIEnvironment).set(
        "MOZ_FORCE_DISABLE_E10S",
        Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch)
            .getBoolPref("browser.tabs.remote.autostart", true) ? "" : "81.0"
    );
} catch(ex) {}
Зачем? Теперь есть аддоны, что ни черта не настраиваются без мультирежима...

Отредактировано solombala (26-09-2020 11:09:37)

Отсутствует

 

№1504626-09-2020 16:24:06

KOMMEHTATOP
Участник
 
Группа: Members
Зарегистрирован: 13-10-2015
Сообщений: 53
UA: Firefox 42.0

Re: Custom Buttons

Доброго времени.

Может кто подправить под Firefox 81.0 ?
Autocopy+3 Автоматически копирует выделенный текст


Distance Subordinatio!

Отсутствует

 

№1504726-09-2020 17:52:30

kokoss
Участник
 
Группа: Members
Зарегистрирован: 15-02-2018
Сообщений: 1728
UA: Firefox 52.0

Re: Custom Buttons

KOMMEHTATOP пишет

Может кто подправить под Firefox 81.0 ?
Autocopy+3 Автоматически копирует выделенный текст

https://forum.mozilla-russia.org/viewto … 95#p783695, и не ленитесь использовать поиск, частенько выручает!

Отредактировано kokoss (26-09-2020 17:53:57)


Win7

Отсутствует

 

№1504827-09-2020 15:23:29

Dumby
Участник
 
Группа: Members
Зарегистрирован: 12-08-2012
Сообщений: 2130
UA: Firefox 52.0

Re: Custom Buttons

Ходьба по кругу какая-то.

Andrey_Krropotkin пишет

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

Я же писал, что будут всё больше совать в <html:template>
А добро внутри него не является частью документа. Дошло время и до гамбургера.
Да вот совсем недавно подобное обсуждалось, неужели не прояснительно.


Можно записать похуже, но попроще, и можно даже без getElementById,
но тогда обратная совместимость пропадёт.

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

Выделить код

Код:

...
    //var it = document.getElementById("appMenu-quit-button");
    var it = document.getElementById("appMenu-quit-button") || PanelMultiView.getViewNode(document, "appMenu-quit-button");

ошибка синтактического анализа XML

Опять же, писал про это, и даже какой-то патч пробовал набросать.
Ошибки не было потому, что в ru локали выпиленные <!ENTITY> оставались.
Вот теперь добрались и зачистили. Вобщем, возвращаю код с теми правками,
но они не одобрены и не интегрированы.

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

Выделить код

Код:

// https://github.com/Infocatcher/Custom_Buttons/tree/master/CB_Source_Editor
// http://infocatcher.ucoz.net/js/cb/cbSourceEditor.js

// Source Editor (formerly Orion Editor) button for Custom Buttons
// (code for "initialization" section)

// (c) Infocatcher 2012-2019
// version 0.1.0a10 - 2019-12-25

var options = {
	cssInHelp: true,
	codeMirror: {
		lineNumbers: true,
		enableCodeFolding: true,
		showTrailingSpace: true,
		lineWrapping: true,
		autocomplete: true,
		fontSize: 12
	},
	orion: {
		lineNumbers: true
	}
};
// Also see devtools.editor.* preferences in about:config

const watcherId = "customButtonsSourceEditor_" + 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 = "_cbSourceEditorStorage";
	// 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 = {
		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);
		},
		get platformVersion() {
			delete this.platformVersion;
			return this.platformVersion = parseFloat(Services.appinfo.platformVersion);
		},
		get hasCodeMirror() {
			delete this.hasCodeMirror;
			return this.hasCodeMirror = Services.appinfo.name == "Pale Moon" //~ todo: test
				|| this.platformVersion >= 27;
		},
		init: function(reason) {
			if(!this.hasCodeMirror) {
				this.isBrowserWindow = function() {
					return false;
				};
			}
			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);
		},
		initWindow: function(window, reason, isFrame) {
			if(this.isBrowserWindow(window)) {
				this.initBrowserWindow(window, reason);
				return;
			}
			if(!this.isEditorWindow(window))
				return;
			_log("initWindow(): isFrame: " + isFrame);
			var document = window.document;
			if(isFrame)
				window.addEventListener("unload", this, false);

			Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
				.getService(Components.interfaces.mozIJSSubScriptLoader)
				.loadSubScript("chrome://global/content/globalOverlay.js", window);

			var isCodeMirror = false;
			try { // See chrome://browser/content/devtools/scratchpad.js
				Components.utils.import("resource:///modules/source-editor.jsm", window);
			}
			catch(e) {
				var loader = this.platformVersion >= 44 // See https://bugzilla.mozilla.org/show_bug.cgi?id=912121
					? "resource://devtools/shared/Loader.jsm"
					: "resource://gre/modules/devtools/Loader.jsm";
				var g = Components.utils.import(loader, {});
				var require = (g.devtools || g).require;
				[
					"devtools/sourceeditor/editor",
					"devtools/client/sourceeditor/editor", // Firefox 44+
					"devtools/client/shared/sourceeditor/editor" // Firefox 68+
				].some(function(path) {
					try {
						return window.SourceEditor = require(path);
					}
					catch(e) {
					}
					return null;
				});
				isCodeMirror = true;
			}
			var SourceEditor = window.SourceEditor;

			if(this.platformVersion >= 73) {
				var psXUL = '<popupset id="sourceEditorPopupset">\
					<linkset>\
						<html:link rel="localization" href="toolkit/global/textActions.ftl" />\
					</linkset>\
					<menupopup id="sourceEditorContext"\
						onpopupshowing="popupHandler(event.target)"\
						oncommand="popupHandler(event.target)">\
\
						<menuitem id="menu_undo" data-l10n-id="text-action-undo" />\
						<menuitem id="menu_redo" data-l10n-id="text-action-redo" />\
						<menuseparator/>\
						<menuitem id="menu_cut" data-l10n-id="text-action-cut" />\
						<menuitem id="menu_copy" data-l10n-id="text-action-copy" />\
						<menuitem id="menu_paste" data-l10n-id="text-action-paste" />\
						<menuitem id="menu_delete" data-l10n-id="text-action-delete" />\
						<menuseparator/>\
						<menuitem id="menu_selectAll" data-l10n-id="text-action-select-all" />\
						<menuseparator/>\
						<menuitem id="menu_find" label="&findCmd.label;" accesskey="&findCmd.accesskey;" />\
						<menuitem id="menu_findAgain" label="&findAgainCmd.label;" accesskey="&findAgainCmd.accesskey;" />\
						<menuseparator/>\
						<menuitem id="se-menu-gotoLine" label="&gotoLineCmd.label;" accesskey="&gotoLineCmd.accesskey;" />\
					</menupopup>\
				</popupset>';
				var ps = window.MozXULElement.parseXULToFragment(psXUL, [
					"chrome://global/locale/editMenuOverlay.dtd",
					"chrome://devtools/locale/sourceeditor.dtd"
				]);
				if(!this.popupHandler) {
					var commands = {module: {}};
					ps.querySelectorAll("menuitem").forEach(function(menuitem) {
						commands[menuitem.id] = menuitem.id.replace(/^(se-)?menu./, "cmd_");
					});
					Services.scriptloader.loadSubScript(
						"resource://devtools/client/shared/sourceeditor/editor-commands-controller.js", commands
					);
					var createController = function(editor) {
						var res = editor.popupCmdController = commands.createController(editor);
						res.docShell = editor.container.contentWindow.docShell;
						return res;
					}
					var getObj = function(cmd, controller) {
						return controller.supportsCommand(cmd) ? controller: controller.docShell;
					}
					var setDisabled = function(menuitem) {
						var cmd = commands[menuitem.id];
						menuitem.disabled = !getObj(cmd, this).isCommandEnabled(cmd);
					}
					this.popupHandler = function(node) {
						var ed = node.ownerDocument.activeElement.contentWindow.editor;
						var controller = ed.popupCmdController || createController(ed);
						var cmd = commands[node.id];
						if(cmd) getObj(cmd, controller).doCommand(cmd);
						else node.menuitems.forEach(setDisabled, controller);
					}
				}
				var popup = ps.querySelector("menupopup");
				popup.menuitems = popup.querySelectorAll("menuitem");
				popup.popupHandler = this.popupHandler;

			} else {

				// See view-source:chrome://browser/content/devtools/scratchpad.xul
				// + view-source:chrome://browser/content/devtools/source-editor-overlay.xul
				var psXUL = (isCodeMirror
				? '<!DOCTYPE popupset [\
					<!ENTITY % editMenuStrings SYSTEM "chrome://global/locale/editMenuOverlay.dtd">\
					%editMenuStrings;\
					<!ENTITY % sourceEditorStrings SYSTEM "' + (
						Services.appinfo.name == "Pale Moon" || Services.appinfo.name == "Basilisk"
							? this.platformVersion >= 4.1
								? "chrome://devtools/locale/sourceeditor.dtd"
								: "chrome://global/locale/devtools/sourceeditor.dtd"
							: this.platformVersion >= 45
								? "chrome://devtools/locale/sourceeditor.dtd"
								: "chrome://browser/locale/devtools/sourceeditor.dtd"
					) + '">\
					%sourceEditorStrings;\
				]>\
				<popupset id="sourceEditorPopupset"\
					xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">\
					<menupopup id="sourceEditorContext"\
						onpopupshowing="goUpdateSourceEditorMenuItems()">\
						<menuitem id="menu_undo" label="&undoCmd.label;" accesskey="&undoCmd.accesskey;"\
							oncommand="goDoCommand(\'cmd_undo\')" />\
						<menuitem id="menu_redo" label="&redoCmd.label;" accesskey="&redoCmd.accesskey;"\
							oncommand="goDoCommand(\'cmd_redo\')" />\
						<menuseparator/>\
						<menuitem id="menu_cut" label="&cutCmd.label;" accesskey="&cutCmd.accesskey;"\
							oncommand="goDoCommand(\'cmd_cut\')" />\
						<menuitem id="menu_copy" label="&copyCmd.label;" accesskey="&copyCmd.accesskey;"\
							oncommand="goDoCommand(\'cmd_copy\')" />\
						<menuitem id="menu_paste" label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;"\
							oncommand="goDoCommand(\'cmd_paste\')" />\
						<menuitem id="menu_delete" label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;"\
							oncommand="goDoCommand(\'cmd_delete\')" />\
						<menuseparator/>\
						<menuitem id="menu_selectAll" label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;"\
							oncommand="goDoCommand(\'cmd_selectAll\')" />\
						<menuseparator/>\
						<menuitem id="menu_find" label="&findCmd.label;" accesskey="&findCmd.accesskey;" />\
						<menuitem id="menu_findAgain" label="&findAgainCmd.label;" accesskey="&findAgainCmd.accesskey;" />\
						<menuseparator/>\
						<menuitem id="se-menu-gotoLine"\
							label="&gotoLineCmd.label;"\
							accesskey="&gotoLineCmd.accesskey;"\
							key="key_gotoLine"\
							oncommand="goDoCommand(\'cmd_gotoLine\')"/>\
					</menupopup>\
				</popupset>'
				: '<popupset id="sourceEditorPopupset"\
					xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">\
					<menupopup id="sourceEditorContext"\
						onpopupshowing="goUpdateSourceEditorMenuItems()">\
						<menuitem id="se-menu-undo"/>\
						<menuitem id="se-menu-redo"/>\
						<menuseparator/>\
						<menuitem id="se-menu-cut"/>\
						<menuitem id="se-menu-copy"/>\
						<menuitem id="se-menu-paste"/>\
						<menuitem id="se-menu-delete"/>\
						<menuseparator/>\
						<menuitem id="se-menu-selectAll"/>\
						<menuseparator/>\
						<menuitem id="se-menu-find"/>\
						<menuitem id="se-menu-findAgain"/>\
						<menuseparator/>\
						<menuitem id="se-menu-gotoLine"/>\
					</menupopup>\
				</popupset>'
				).replace(/>\s+</g, "><");
	
				var ps = this.parseXULFromString(psXUL);
				// "Edit Custom Button in Tab" button, Firefox 71+
				if(isFrame && "parseFromSafeString" in window.DOMParser.prototype)
				ps = window.MozXULElement.parseXULToFragment(ps.outerHTML);
			}
			document.documentElement.appendChild(ps);

			window.setTimeout(function() {
				function appendNode(nodeName, id) {
					var node = document.createElementNS(xulns, nodeName);
					node.id = id;
					document.documentElement.appendChild(node);
				}
				appendNode("commandset", "editMenuCommands");
				appendNode("commandset", "sourceEditorCommands");
				appendNode("keyset", "sourceEditorKeys");
				appendNode("keyset", "editMenuKeys");

				this.loadOverlays(
					window,
					function done() {
						window.setTimeout(function() {
							var mp = document.getElementById("sourceEditorContext");
							if(mp.state == "closed")
								return;
							Array.prototype.forEach.call(
								mp.getElementsByAttribute("command", "*"),
								function(mi) {
									var cmd = mi.getAttribute("command");
									var controller = document.commandDispatcher
										.getControllerForCommand(cmd);
									if(controller && !controller.isCommandEnabled(cmd))
										mi.setAttribute("disabled", "true");
								}
							);
						}, 0);
						if(!isCodeMirror)
							return;
						// See view-source:chrome://browser/content/devtools/scratchpad.xul in Firefox 27.0a1
						window.goUpdateSourceEditorMenuItems = function() {
							goUpdateGlobalEditMenuItems();
							var commands = ["cmd_undo", "cmd_redo", "cmd_cut", "cmd_paste", "cmd_delete"];
							commands.forEach(goUpdateCommand);
						};
						var cmdsMap = {
							"se-menu-undo":   "cmd_undo",
							"se-menu-redo":   "cmd_redo",
							"se-menu-cut":    "cmd_cut",
							"se-menu-copy":   "cmd_copy",
							"se-menu-paste":  "cmd_paste",
							"se-menu-delete": "cmd_delete",
							__proto__: null
						};
						for(var id in cmdsMap) {
							var mi = document.getElementById(id);
							mi && mi.setAttribute("command", cmdsMap[id]);
						}
						// We can't use command="cmd_selectAll", menuitem will be wrongly disabled sometimes
						var enabledCmdsMap = {
							"se-menu-selectAll": "cmd_selectAll",
							"se-menu-findAgain": "cmd_findAgain",
							__proto__: null
						};
						for(var id in enabledCmdsMap) {
							var mi = document.getElementById(id);
							if(mi) {
								mi.removeAttribute("command");
								mi.removeAttribute("disabled");
								mi.setAttribute("oncommand", "goDoCommand('" + enabledCmdsMap[id] + "');");
							}
						}
						// Workaround: emulate keyboard shortcut
						var keyCmdsMap = {
							"menu_find":      { keyCode: KeyboardEvent.DOM_VK_F, charCode: "f".charCodeAt(0), ctrlKey: true },
							"menu_findAgain": { keyCode: KeyboardEvent.DOM_VK_G, charCode: "g".charCodeAt(0), ctrlKey: true },
							__proto__: null
						};
						var _key = function() {
							var e = this._keyData;
							var evt = document.createEvent("KeyboardEvent");
							evt.initKeyEvent(
								"keydown", true /*bubbles*/, true /*cancelable*/, window,
								e.ctrlKey || false, e.altKey || false, e.shiftKey || false, e.metaKey || false,
								e.keyCode || 0, e.charCode || 0
							);
							document.commandDispatcher.focusedElement.dispatchEvent(evt);
						};
						for(var id in keyCmdsMap) {
							var mi = document.getElementById(id);
							if(mi) {
								mi.removeAttribute("command");
								mi.removeAttribute("disabled");
								mi.setAttribute("oncommand", "this._key();");
								mi._keyData = keyCmdsMap[id];
								mi._key = _key;
							}
						}
						// Fix styles for autocomplete tooltip
						function css(uri) {
							document.insertBefore(document.createProcessingInstruction(
								"xml-stylesheet",
								'href="' + uri + '" type="text/css"'
							), document.documentElement);
						}
						css("resource://devtools/client/themes/variables.css");
						css("resource://devtools/client/themes/common.css");
						css("chrome://devtools/skin/tooltips.css");
						if(this.platformVersion >= 68) window.setTimeout(function fixSelection() {
							var sheets = document.styleSheets;
							for(var i = sheets.length - 1; i >= 0; --i) {
								var sheet = sheets[i];
								if(sheet.href != "resource://devtools/client/themes/common.css")
									continue;
								try {
									var rules = sheet.cssRules;
								}
								catch(e) {
									// InvalidAccessError:
									// A parameter or an operation is not supported by the underlying object
									return window.setTimeout(fixSelection, 10);
								}
								for(var j = 0, len = rules.length; j < len; ++j)
									if(rules[j].selectorText == "::selection")
										return !sheet.deleteRule(j);
								break;
							}
							return false;
						}, 10);
					}.bind(this),
					["chrome://global/content/editMenuOverlay.xul", function check(window) {
						return window.document.getElementById("editMenuCommands").hasChildNodes();
					}],
					["chrome://browser/content/devtools/source-editor-overlay.xul", function check(window) {
						return window.document.getElementById("sourceEditorCommands").hasChildNodes();
					}]
				);
			}.bind(this), 500); // We should wait to not break other extensions with document.loadOverlay()

			var tabs = document.getElementById("custombuttons-editbutton-tabbox");
			var selectedPanel = tabs.selectedPanel;
			Array.prototype.slice.call(document.getElementsByTagName("cbeditor")).forEach(function(cbEditor) {
				if("__sourceEditor" in cbEditor)
					return;
				var code = cbEditor.value;
				var isCSS = options.cssInHelp && cbEditor.id == "help";
				if(isCodeMirror) {
					var opts = {
						mode: isCSS
							? SourceEditor.modes.css
							: SourceEditor.modes.js,
						value: code,
						lineNumbers: true,
						enableCodeFolding: true,
						showTrailingSpace: true,
						autocomplete: true,
						contextMenu: "sourceEditorContext"
					};
					var optsOvr = options.codeMirror;
					for(var opt in optsOvr) if(optsOvr.hasOwnProperty(opt))
						opts[opt] = optsOvr[opt];
					var se = new SourceEditor(opts);
					if("codeMirror" in se) window.setTimeout(function() {
						if("insertCommandsController" in se)
							se.insertCommandsController(); // Pale Moon and Basilisk
						else
							this.insertCommandsController(se);
					}.bind(this), 200);
				}
				else {
					var se = new SourceEditor();
				}
				se.__isCodeMirror = isCodeMirror;
				var seElt = document.createElementNS(xulns, "hbox");
				if(cbEditor.id)
					seElt.id = "sourceEditor-" + cbEditor.id;
				seElt.className = "sourceEditor";
				seElt.setAttribute("flex", 1);
				seElt.__sourceEditor = se;
				cbEditor.parentNode.insertBefore(seElt, cbEditor);
				//cbEditor.setAttribute("hidden", "true");
				cbEditor.setAttribute("collapsed", "true");
				cbEditor.parentNode.appendChild(cbEditor);
				cbEditor.__sourceEditor = se;
				cbEditor.__sourceEditorElt = seElt;
				cbEditor.__defineGetter__("value", function() {
					if("__sourceEditor" in this) {
						var se = this.__sourceEditor;
						if(!se.__initialized)
							return se.__value;
						return se.getText().replace(/\r\n?|\n\r?/g, "\n");
					}
					return this.textbox.value;
				});
				cbEditor.__defineSetter__("value", function(v) {
					if("__sourceEditor" in this) {
						var se = this.__sourceEditor;
						if(!se.__initialized) {
							var _this = this;
							se.__onLoadCallbacks.push(function() {
								_this.value = v;
							});
							return se.__value = v;
						}
						return se.setText(v.replace(/\r\n?|\n\r?/g, "\n"));
					}
					return this.textbox.value = v;
				});
				cbEditor.selectLine = function(lineNumber) {
					if("__sourceEditor" in this) {
						var se = this.__sourceEditor;
						if(!se.__initialized) {
							var _this = this, args = arguments;
							se.__onLoadCallbacks.push(function() {
								_this.selectLine.apply(_this, args);
							});
							return undefined;
						}
						if(se.__isCodeMirror) {
							//se.focus();
							//se.setCursor({ line: lineNumber - 1, ch: 0 });
							//~ todo: optimize
							var val = this.value;
							var lines = val.split("\n");
							var line = Math.min(lineNumber - 1, lines.length);
							var ch = lines[line].length;
							se.focus();
							return se.setSelection({ line: line, ch: 0 }, { line: line, ch: ch });
						}
						else {
							var selStart = se.getLineStart(lineNumber - 1);
							var selEnd = se.getLineEnd(lineNumber - 1, false);
							se.focus();
							return se.setSelection(selStart, selEnd);
						}
					}
					return this.__proto__.selectLine.apply(this, arguments);
				};

				// For edit_button() from chrome://custombuttons/content/editExternal.js
				seElt.__cbEditor = cbEditor;
				seElt.__defineGetter__("localName", function() {
					return "cbeditor";
				});
				seElt.__defineGetter__("value", function() {
					return this.__cbEditor.value;
				});
				seElt.__defineSetter__("value", function(val) {
					this.__cbEditor.value = val;
				});

				se.__initialized = false;
				se.__onLoadCallbacks = [];
				se.__value = code;
				var onTextChanged = se.__onTextChanged = function() {
					window.editor.changed = true;
				};
				var isLoaded = reason == this.REASON_WINDOW_LOADED;
				function done() {
					se.__initialized = true;
					se.__onLoadCallbacks.forEach(function(fn) {
						try {
							fn();
						}
						catch(e) {
							Components.utils.reportError(e);
						}
					});
					delete se.__onLoadCallbacks;
					delete se.__value;
				}
				if(isCodeMirror) {
					se.appendTo(seElt).then(function() {
						try {
							se.setupAutoCompletion();
						}
						catch(e) {
							Components.utils.reportError(e);
						}
						if("setFontSize" in se) try {
							se.setFontSize(options.codeMirror.fontSize);
						}
						catch(e) {
							Components.utils.reportError(e);
						}
						window.setTimeout(function() {
							window.editor.changed = false; // Strange...
							window.setTimeout(function() { // Workaround for unexpected onTextChanged() calls
								if(window.editor.changed && cbEditor.value == code)
									window.editor.changed = false;
							}, 100);
							se.on("change", onTextChanged);
							if(isLoaded) {
								if("clearHistory" in se)
									se.clearHistory();
								else {
									var seGlobal = Components.utils.getGlobalForObject(SourceEditor.prototype);
									// Note: this is resource://app/modules/devtools/gDevTools.jsm scope in Firefox 34+
									var cm = seGlobal.editors.get(se);
									cm.clearHistory();
								}
							}
						}, isFrame ? 50 : 15); // Oh, magic delays...
						done();

						// See resource:///modules/devtools/sourceeditor/editor.js
						// doc.defaultView.controllers.insertControllerAt(0, controller(this, doc.defaultView));
						var controllers = window.controllers; // nsIControllers
						var controller = se.__cmdController = controllers.getControllerAt(0);
						if("__cmdControllers" in tabs)
							tabs.__cmdControllers.push(controller);
						else {
							tabs.__cmdControllers = [controller];
							var onSelect = tabs.__onSelect = function() {
								var seElt = tabs.selectedPanel;
								if(!seElt || !("__sourceEditor" in seElt))
									return;
								var se = seElt.__sourceEditor;
								var curController = se.__cmdController;
								tabs.__cmdControllers.forEach(function(controller) {
									try {
										if(controller == curController)
											controllers.insertControllerAt(0, controller);
										else
											controllers.removeController(controller);
									}
									catch(e) {
									}
								});
							};
							tabs.addEventListener("select", onSelect, false);
							window.setTimeout(onSelect, 0); // Activate controller from selected tab
						}
					});
				}
				else {
					var opts = {
						mode: isCSS
							? SourceEditor.MODES.CSS
							: SourceEditor.MODES.JAVASCRIPT,
						showLineNumbers: true,
						initialText: code,
						placeholderText: code, // For backward compatibility
						contextMenu: "sourceEditorContext"
					};
					var optsOvr = options.orion;
					for(var opt in optsOvr) if(optsOvr.hasOwnProperty(opt))
						opts[opt] = optsOvr[opt];
					se.init(seElt, opts, function callback() {
						done();
						isLoaded && se.resetUndo && se.resetUndo();
						se.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onTextChanged);

						// Hack to use selected editor
						var controller = se.ui._controller;
						controller.__defineGetter__("_editor", function() {
							var seElt = tabs.selectedPanel;
							var se = seElt && seElt.__sourceEditor
								|| document.getElementsByTagName("cbeditor")[0].__sourceEditor;
							return se;
						});
						controller.__defineSetter__("_editor", function() {});
					});
				}
			}, this);
			// Trick to select correct tab (especially if was selected "Button settings" tab)
			tabs.tabs.advanceSelectedTab(1, true);
			tabs.tabs.advanceSelectedTab(-1, true);

			var origExecCmd = window.editor.execute_oncommand_code;
			window.editor.execute_oncommand_code = function() {
				var cd = document.commandDispatcher;
				var cdFake = {
					__proto__: cd,
					get focusedElement() {
						var selectedTab = tabs.selectedTab;
						if(selectedTab && selectedTab.id == "code-tab")
							return document.getElementById("code").textbox.inputField;
						return cd.focusedElement;
					}
				};
				document.__defineGetter__("commandDispatcher", function() {
					return cdFake;
				});
				try {
					var ret = origExecCmd.apply(this, arguments);
				}
				catch(e) {
					Components.utils.reportError(e);
				}
				// document.hasOwnProperty("commandDispatcher") == false, so we cat just delete our fake property
				delete document.commandDispatcher;
				return ret;
			};

			window.addEventListener("load", function ensureObserversAdded() {
				window.removeEventListener("load", ensureObserversAdded, false);
				window.setTimeout(function() { window.editor.removeObservers(); }, 0);
				window.setTimeout(function() { window.editor.addObservers();    }, 0);
			}, false);
			// Fix for Ctrl+S hotkey (catched by CodeMirror)
			var hke = this.handleKeyEvent;
			window.addEventListener("keydown",  hke, true);
			window.addEventListener("keypress", hke, true);
			window.addEventListener("keyup",    hke, true);
		},
		insertCommandsController: function(se) {
			this.insertCommandsController = insertCommandsController;
			return insertCommandsController(se);
			// devtools/client/sourceeditor/editor-commands-controller in Pale Moon/Basilisk
			function createController(ed) {
				return {
					supportsCommand: function (cmd) {
						switch (cmd) {
							case "cmd_find":
							case "cmd_findAgain":
							case "cmd_gotoLine":
							case "cmd_undo":
							case "cmd_redo":
							case "cmd_delete":
							case "cmd_selectAll":
								return true;
						}

						return false;
					},

					isCommandEnabled: function (cmd) {
						let cm = ed.codeMirror;

						switch (cmd) {
							case "cmd_find":
							case "cmd_gotoLine":
							case "cmd_selectAll":
								return true;
							case "cmd_findAgain":
								return cm.state.search != null && cm.state.search.query != null;
							case "cmd_undo":
								return ed.canUndo();
							case "cmd_redo":
								return ed.canRedo();
							case "cmd_delete":
								return ed.somethingSelected();
						}

						return false;
					},

					doCommand: function (cmd) {
						let cm = ed.codeMirror;

						let map = {
							"cmd_selectAll": "selectAll",
							"cmd_find": "find",
							"cmd_undo": "undo",
							"cmd_redo": "redo",
							"cmd_delete": "delCharAfter",
							"cmd_findAgain": "findNext"
						};

						if (map[cmd]) {
							cm.execCommand(map[cmd]);
							return;
						}

						if (cmd == "cmd_gotoLine") {
							ed.jumpToLine();
						}
					},

					onEvent: function () {}
				};
			}
			function insertCommandsController(sourceEditor) {
				let input = sourceEditor.codeMirror.getInputField();
				let controller = createController(sourceEditor);
				input.controllers.insertControllerAt(0, controller);
			}
		},
		destroyWindow: function(window, reason, isFrame) {
			if(reason == this.REASON_WINDOW_CLOSED)
				window.removeEventListener(this.loadEvent, this, false); // Window can be closed before DOMContentLoaded
			if(this.isBrowserWindow(window)) {
				this.destroyBrowserWindow(window, reason);
				return;
			}
			if(!this.isEditorWindow(window) || !("SourceEditor" in window))
				return;
			_log("destroyWindow(): isFrame: " + isFrame);
			var document = window.document;
			if(isFrame)
				window.removeEventListener("unload", this, false);

			var tabs = document.getElementById("custombuttons-editbutton-tabbox");
			if("__onSelect" in tabs) {
				tabs.removeEventListener("select", tabs.__onSelect, false);
				delete tabs.__onSelect;
				delete tabs.__cmdControllers;
			}

			Array.prototype.slice.call(document.getElementsByTagName("cbeditor")).forEach(function(cbEditor) {
				if(!("__sourceEditor" in cbEditor))
					return;
				var se = cbEditor.__sourceEditor;
				var isCodeMirror = se.__isCodeMirror;
				if(isCodeMirror)
					se.off("change", se.__onTextChanged);
				else
					se.removeEventListener(window.SourceEditor.EVENTS.TEXT_CHANGED, se.__onTextChanged);
				delete se.__onTextChanged;
				if(reason == this.REASON_SHUTDOWN) {
					var val = cbEditor.value;
					delete cbEditor.value;
					delete cbEditor.selectLine;

					var seElt = cbEditor.__sourceEditorElt;
					seElt.parentNode.insertBefore(cbEditor, seElt);
					seElt.parentNode.removeChild(seElt);
					delete cbEditor.__sourceEditorElt;
					delete cbEditor.__sourceEditor;
					delete seElt.__sourceEditor;
					delete seElt.__cbEditor;

					cbEditor.value = val;
					window.setTimeout(function() {
						cbEditor.removeAttribute("collapsed");
					}, 0);
				}
				se.destroy();
				if("__cmdController" in se) {
					try {
						window.controllers.removeController(se.__cmdController);
					}
					catch(e) {
					}
					delete se.__cmdController;
				}
			}, this);

			if(reason == this.REASON_SHUTDOWN) {
				delete window.editor.execute_oncommand_code;
				[
					"sourceEditorPopupset",
					"editMenuCommands",
					"sourceEditorCommands",
					"sourceEditorKeys",
					"editMenuKeys"
				].forEach(function(id) {
					var node = document.getElementById(id);
					node && node.parentNode.removeChild(node);
				});
				[
					// chrome://global/content/globalOverlay.js
					"closeWindow", "canQuitApplication", "goQuitApplication", "goUpdateCommand", "goDoCommand",
					"goSetCommandEnabled", "goSetMenuValue", "goSetAccessKey", "goOnEvent", "visitLink",
					"setTooltipText", "NS_ASSERT",
					// chrome://global/content/editMenuOverlay.xul => view-source:chrome://global/content/editMenuOverlay.js
					"goUpdateGlobalEditMenuItems", "goUpdateUndoEditMenuItems", "goUpdatePasteMenuItems"
				].forEach(function(p) {
					delete window[p];
				});
				for(var child = document.documentElement; child = child.previousSibling; ) {
					if(
						child.nodeType == child.PROCESSING_INSTRUCTION_NODE
						&& child.data.indexOf("://devtools/") != -1
					) {
						setTimeout(function(child) {
							child.parentNode.removeChild(child);
						}, 0, child);
					}
				}
				delete window.SourceEditor;
			}
			var hke = this.handleKeyEvent;
			window.removeEventListener("keydown",  hke, true);
			window.removeEventListener("keypress", hke, true);
			window.removeEventListener("keyup",    hke, true);
			//~ todo: we have one not removed controller!
			//LOG("getControllerCount(): " + window.controllers.getControllerCount());
		},
		initBrowserWindow: function(window, reason) {
			_log("initBrowserWindow()");
			window.addEventListener("DOMContentLoaded", this, false);
			Array.prototype.forEach.call(window.frames, function(frame) {
				this.initWindow(frame, reason, true);
			}, this);
		},
		destroyBrowserWindow: function(window, reason) {
			_log("destroyBrowserWindow()");
			window.removeEventListener("DOMContentLoaded", this, false);
			Array.prototype.forEach.call(window.frames, function(frame) {
				this.destroyWindow(frame, reason, true);
			}, this);
		},
		isEditorWindow: function(window) {
			return window.location.href.substr(0, 41) == "chrome://custombuttons/content/editor.xul";
		},
		isBrowserWindow: function(window) {
			var loc = window.location.href;
			return loc == "chrome://browser/content/browser.xul"
				|| loc == "chrome://browser/content/browser.xhtml" // Firefox 69+
				|| loc == "chrome://navigator/content/navigator.xul";
		},
		get loadEvent() { // "DOMContentLoaded" -> initWindow() may hang editor window (and browser)
			delete this.loadEvent;
			return this.loadEvent = this.platformVersion >= 73 ? "load" : "DOMContentLoaded";
		},
		observe: function(subject, topic, data) {
			if(topic == "quit-application-granted")
				this.destroy();
			else if(topic == "domwindowopened")
				subject.addEventListener(this.loadEvent, this, false);
			else if(topic == "domwindowclosed")
				this.destroyWindow(subject, this.REASON_WINDOW_CLOSED);
		},
		handleEvent: function(e) {
			switch(e.type) {
				case "DOMContentLoaded":
				case "load":
					//var window = e.currentTarget;
					var window = e.target.defaultView || e.target;
					window.removeEventListener(e.type, this, false);
					var isFrame = window != e.currentTarget;
					this.initWindow(window, this.REASON_WINDOW_LOADED, isFrame);
				break;
				case "unload":
					//var window = e.currentTarget;
					var window = e.target.defaultView || e.target;
					window.removeEventListener(e.type, this, false);
					this.destroyWindow(window, this.REASON_WINDOW_CLOSED, true);
			}
		},
		handleKeyEvent: function(e) {
			if(
				(e.keyCode == e.DOM_VK_S || String.fromCharCode(e.charCode).toUpperCase() == "S")
				&& e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey
			) {
				e.preventDefault();
				e.stopPropagation();
				if(e.type == "keydown") {
					var window = e.currentTarget;
					window.editor.updateButton();
				}
			}
		},
		loadOverlays: function() {
			this.runGenerator(this.loadOverlaysGen, this, arguments);
		},

		get loadOverlaysGen() {
			var fn = this._loadOverlaysGen.toString()
				.replace(/__yield/g, "yield");
			try {
				new Function("function test() { yield 0; }");
			}
			catch(e) { // Firefox 58+: SyntaxError: yield expression is only valid in generators
				fn = fn.replace("function", "function*"); // Firefox 26+
			}
			delete this.loadOverlaysGen;
			return this.loadOverlaysGen = eval("(" + fn + ")");
		},
		_loadOverlaysGen: function loadOverlaysGen(window, callback/*, overlayData1, ...*/) {
			var gen = loadOverlaysGen.__generator;
			for(var i = 2, l = arguments.length; i < l; ++i) {
				var overlayData = arguments[i];
				this.loadOverlay(window, overlayData[0], overlayData[1], function() {
					gen.next();
				});
				__yield(0);
			}
			callback();
			__yield(0);
		},
		loadOverlay: function(window, uri, check, callback) {
			var document = window.document;
			var stopWait = Date.now() + 4500;
			window.setTimeout(function load() {
				_log("loadOverlay(): " + uri);
				var tryAgain = Date.now() + 800;
				try {
					document.loadOverlay(uri, null);
				}
				catch(e) {
					window.setTimeout(callback, 0);
					return;
				}
				window.setTimeout(function ensureLoaded() {
					if(check(window))
						window.setTimeout(callback, 0);
					else if(Date.now() > stopWait)
						return;
					else if(Date.now() > tryAgain)
						window.setTimeout(load, 0);
					else
						window.setTimeout(ensureLoaded, 50);
				}, 50);
			}, 0);
		},
		runGenerator: function(genFunc, context, args) {
			var gen = genFunc.apply(context, args);
			genFunc.__generator = gen;
			gen.next();
		},
		parseXULFromString: function(xul) {
			xul = xul.replace(/>\s+</g, "><");
			try {
				return new DOMParser().parseFromString(xul, "application/xml").documentElement;
			}
			catch(e) {
				// See http://custombuttons.sourceforge.net/forum/viewtopic.php?f=5&t=3720
				// + https://forum.mozilla-russia.org/viewtopic.php?pid=732243#p732243
				var dummy = document.createElement("dummy");
				dummy.innerHTML = xul.trimLeft();
				return dummy.firstChild;
			}
		}
	};
	storage.set(watcherId, watcher);
	setTimeout(function() {
		watcher.init(watcher.REASON_STARTUP);
	}, 50);
}
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;

function ts() {
	var d = new Date();
	var ms = d.getMilliseconds();
	return d.toTimeString().replace(/^.*\d+:(\d+:\d+).*$/, "$1") + ":" + "000".substr(("" + ms).length) + ms + " ";
}
function _log(s) {
	Services.console.logStringMessage("[Custom Buttons :: Source Editor] " + ts() + s);
}


А консольская кнопка у меня совсем уже никакая, редко пользуюсь,
слишком сложно это для меня, но раз «покажи»...
скрытый текст

Выделить код

Код:

({
	title: "Консоль браузера",
	url: "chrome://devtools/content/webconsole/index.html",
	icon: "chrome://devtools/skin/images/tool-webconsole.svg",
	init() {
		var trg = document.getElementById("browser");
		trg && addEventListener("DOMContentLoaded", this, false, trg);
		var id = "viewBrowserConsoleSidebar";

		var menuitem = this.element("menuitem", {
			type: "checkbox",
			label: this.title,
			id: "menu_browserConsoleSidebar",
			oncommand: `SidebarUI.toggle("${id}");`
		}, document.getElementById("viewSidebarMenu"));

		var btn = this.element("toolbarbutton", {
			type: "checkbox",
			label: this.title,
			id: "sidebar-switcher-browserConsole",
			oncommand: `SidebarUI.show("${id}");`,
			class: "subviewbutton subviewbutton-iconic"
		});
		document.querySelector(
			'toolbarbutton[id^="sidebar-switcher-"] + toolbarseparator'
		).before(btn);

		SidebarUI.sidebars.set(id, {
			url: this.url,
			buttonId: btn.id,
			title: this.title,
			menuId: menuitem.id
		});
		SidebarUI.isOpen && SidebarUI.currentID == id && SidebarUI.selectMenuItem(id);

		var popupset = this.popupset = this.element("popupset", {
			id: `CB${_id.slice(20)}-browserConsole-popupset`
		}, document.documentElement);

		var css = `\
			#${btn.id} > .toolbarbutton-icon,
			#sidebar-box[sidebarcommand="${id}"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
				list-style-image: url(${this.icon});
			}`;
		var str = (cbu.cb || "") + "data:text/css," + encodeURIComponent(css), type = windowUtils.USER_SHEET;
		windowUtils.loadSheetUsingURIString(str, type);

		addDestructor(() => {
			SidebarUI.sidebars.delete(id);
			btn.remove(); menuitem.remove(); popupset.remove();
			windowUtils.removeSheetUsingURIString(str, type);
		});
		parseInt(Services.appinfo.platformVersion) < 73 
			&& "insertFTLIfNeeded" in MozXULElement
			&& MozXULElement.insertFTLIfNeeded("toolkit/main-window/editmenu.ftl");

		self.onclick = e => {
			if (e.button == 2) return;
			if (!e.button && !e.shiftKey) return SidebarUI.toggle(id);
			var st = gBrowser.selectedTab, tab;
			if (!e.ctrlKey) tab = gBrowser.visibleTabs.find(tab => {
				var br = gBrowser.getBrowserForTab(tab);
				return br.currentURI.spec == this.url || (
					"_cachedCurrentURI" in br
					&& br._cachedCurrentURI.spec == this.url
				)
			});
			if (tab == st) return;
			if (!tab) tab = gBrowser.addTrustedTab(this.url);
			gBrowser.moveTabTo(tab, st._tPos + 1);
			gBrowser.selectedTab = tab;
		}
		for(var br of gBrowser.browsers) {
			if (br.currentURI.spec != this.url) continue;
			var doc = br.contentDocument;
			if (doc && (
				doc.readyState == "complete" ||
				doc.readyState == "interactive"
			))
				doc.querySelector(
					"main#app-wrapper,div#output-container"
				).childElementCount
					? this.defineDocPopupset(doc)
					: this.handleEvent({target: doc});
		}
		if (!btn.hasAttribute("checked")) return;
		var doc = SidebarUI.browser.contentDocument;
		if (doc.documentURI != this.url) btn.doCommand();
		else if (doc.readyState == "complete") this.defineDocPopupset(doc);
	},
	defineDocPopupset(doc) {
		this.definePopupset(
			doc.querySelector("popupset") ||
			doc.documentElement.appendChild(doc.createXULElement("popupset"))
		);
	},
	get definePopupset() {
		var append = customElements.get("menuitem")
			? popup => {
				this.popupset.appendChild(popup);
				popup.setAttribute("oncommand", "event.target.cmd()");
				for(var node of [...popup.querySelectorAll("menuitem")]) {
					var menuitem = document.importNode(node, true);
					menuitem.cmd = Services.els.getListenerInfoFor(node)
						.find(inf => inf.type == "command").listenerObject;
					popup.replaceChild(menuitem, node);
				}
				return popup;
			}
			: this.popupset.appendChild.bind(this.popupset);

		delete this.definePopupset;
		return this.definePopupset = popupset => popupset.appendChild = append;
	},
	lss: Services.scriptloader.loadSubScript,
	async handleEvent({target: doc}) {
		if (!doc || doc.documentURI != this.url) return;

		var win = doc.defaultView;
		if (
			win.docShell.name == "toolbox-panel-iframe-webconsole" ||
			doc.DOMContentLoadedEventHandled
		)
			return;
		doc.DOMContentLoadedEventHandled = true;
		"custombuttonsConsole" in win || this.lss(
			"chrome://custombuttons/content/consoleOverlay.js", win
		);
		var cw = win.isChromeWindow, bc;
		if (!cw) {
			if (doc.visibilityState == "hidden") {
				var {focus} = win;
				win.focus = () => win.focus = focus;
			}
			doc.title = this.title;
			var link = doc.createElement("link");
			link.setAttribute("rel", "shortcut icon");
			link.setAttribute("href", this.icon);
			doc.head.prepend(link);

			var br = win.docShell.chromeEventHandler;
			var cmAttr = br.getAttribute("contextmenu");
			cmAttr && br.removeAttribute("contextmenu");
			win.onbeforeunload = () => {
				if (bc) bc.chromeWindow = {close() {}};
				cmAttr && br.setAttribute("contextmenu", cmAttr);
			}
		}
		bc = await this.console(win);
	},
	get console() {
		// Bug 1579090 - WebConsole should handle ObjectFront when needed (for non-primitive Console API args + Evaluation results) (Firefox 73+)
		// https://bugzilla.mozilla.org/show_bug.cgi?id=1579090
		var vers = parseInt(Services.appinfo.platformVersion);
		this.bug1579090 = vers > 73 || (vers == 73 && !(
			"_setCurrentURI" in gBrowser.selectedBrowser // https://bugzil.la/1431214
		));
		delete this.console;
		return this.console = this.bug1579090 ? async win => {
			await this.loader.bcm._browserConsoleInitializing;
			var key = "CBBrowserConsolePromise", {wins} = this.loader;
			win[key] = win.Object.create(null);
			win[key].promise = new win.Promise(resolve => win[key].resolve = resolve);
			win[key].destroy = () => {
				win[key].resolve();
				delete win[key];
				wins.splice(wins.indexOf(win), 1);
			}
			wins.unshift(win);
			wins.length > 1 && await wins[1][key].promise;

			var bc = await new this.loader.console(win).toggleBrowserConsole();
			win[key].destroy();
			return bc;

		} : async win => {
			this.loader.Services.ww.wins.push(win);
			return await new this.loader.HUDService().toggleBrowserConsole();
		}
	},
	get loader() {
		delete this.loader;
		var url = "resource://devtools/shared/Loader.jsm";
		if (this.bug1579090) {
			var g = Cu.import(url, {}), key = "CBBrowserConsoleLoader";
			addDestructor(reason => reason[5] == e && key in g && g[key].destroy());
			if (key in g) return this.loader = g[key];
			var {BrowserConsoleManager} = g.require(
				"devtools/client/webconsole/browser-console-manager"
			);
			return this.loader = g[key] = {
				wins: [],
				bcm: BrowserConsoleManager,
				console: class extends BrowserConsoleManager.constructor {
					constructor(win) {
						super();
						this.win = win;
					}
					openWindow() {
						var {win} = this;
						win.addEventListener("unload", () => {
							win.CBBrowserConsolePromise &&
								win.CBBrowserConsolePromise.destroy();
							this.closeBrowserConsole.call(this);
						}, {once: true});
						delete this.win;
						return win;
					}
				},
				destroy() {
					this.wins = null;
					delete g[key];
				}
			};
		}
		var id = _id + "-browser-console";
		url += "?" + id;
		var loader = {exports: {}}, nsvo = Cu.import(url, loader);
		addDestructor(reason => reason[5] == "e" && Cu.unload(url));

		if (id in nsvo) return this.loader = nsvo[id];

		var dir = "resource://devtools/client/webconsole/";
		try {
			this.lss(dir + "hudservice.js", loader);
		} catch(ex) {
			// Bug 1570320 - Rename hudservice.js into browser-console-manager.js (Firefox 70+)
			// https://bugzilla.mozilla.org/show_bug.cgi?id=1570320
			this.lss(dir + "browser-console-manager.js", loader);
			this.lss("data:,this.HUDService=BrowserConsoleManager", loader);
		}
		var e = new CustomEvent("DOMContentLoaded", {bubbles: false}), ww = loader.Services.ww;
		loader.Services.ww = Cu.getGlobalForObject(nsvo).Object.create(ww, {
			wins: {value: []},
			openWindow: {value: function() {
				var win = this.wins.shift();
				win.setTimeout(() => win.dispatchEvent(e), 0);
				return win;
			}}
		});
		return this.loader = nsvo[id] = loader;
	},
	element(name, attrs, parent) {
		var node = document.createXULElement(name);
		for(var attr in attrs) node.setAttribute(attr, attrs[attr]);
		parent && parent.append(node);
		return node;
	}
}).init();

solombala пишет

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

Да, сдох, уже давно, что неоднократо перетёрто.
И да, видимо стоит повторить, что конфигурация отсутствия отдельных XPCOM процессов
заявлена мазилой как нетестируемая и как неподдерживающаяся. Или, проще говоря, «вас нет».

Отсутствует

 

№1504929-09-2020 10:18:10

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 476
UA: Firefox 81.0

Re: Custom Buttons

Dumby спасибо

Отсутствует

 

№1505002-10-2020 01:57:53

Dobrov
Участник
 
Группа: Members
Зарегистрирован: 04-10-2011
Сообщений: 397
UA: Firefox 56.0

Re: Custom Buttons

Можно сделать скрипт, который до открытия страницы включает/выключает прокси в зависимости от открываемого URL ?

Хотелось бы автоматически менять настройки прокси и затем открывать конкретные url (список заблокированных провайдером) http://flibusta.is https://rutracker.org https://nnm-club.me …
network.proxy.type = 0 или 2
network.proxy.autoconfig_url = https://antizapret.prostovpn.org/proxy.pac (или пусто)


Dumby - а можно пост «Merge Date» перенести в постоянное место (или тему) ? Так новичкам будет проще искать.
И в этот пост только дописывать конфиги, указывая версию браузера. То есть примерно так:
«Merge Date» Firefox 68
    custom_buttons-0.0.7.0.0.6-fx-paxmod.xpi
    custom_buttons-0.0.7.0.0.6-fx-bootstrap.xpi
    скрипты bootstrap-loader.js и config.js

«Merge Date» Firefox 79+
    тоже самое…

Отсутствует

 

Board footer

Powered by PunBB
Modified by Mozilla Russia
Copyright © 2004–2020 Mozilla Russia GitHub mark
Язык отображения форума: [Русский] [English]