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

Заказывай стафф с атрибутикой Mozilla и... пусть все вокруг завидуют тебе! Быть уникальным - быть с Mozilla!

№12612-11-2021 17:26:33

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

Re: UCF-скрипты на этом форуме

Dumby - спасибо, исправил и доработал код.


Вопрос - как сделать перехват кликов на кнопках расширений, которые динамически появляются?
То есть, при старте браузера кнопки Reader View в строке адреса нет, она появляется, когда открывается сайт.
Будет удобнее, если у кнопки режима чтения появится ещё действие - например альтернативный режим просмотра.
То есть, нужно на кнопке Reader View по клику колёсиком включать/выключать режим "Адаптивный дизайн".

Выделить код

Код:

win.document.getElementById("pageAction-panel-_2495d258-41e7-4cd5-bc7d-ac15981f064e_").click()
trg.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // Адаптивный дизайн
id="pageAction-panel-_2495d258-41e7-4cd5-bc7d-ac15981f064e_" Reader View в меню Действия на странице
id="pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_" в строке адреса
actionid="_2495d258-41e7-4cd5-bc7d-ac15981f064e_"

Желательно добавить перехват кликов в тот же UCF-скрипт, который расширяет возможности кнопок Меню и Загрузки:

ucf_hookClicks.js

Выделить код

Код:

(async (id, func) => { // дополнительные клики на downloads-button, PanelUI-menu для custom_script_win.js
	await window.delayedStartupPromise;
	var btn = document.getElementById("downloads-button"), pui = document.getElementById("PanelUI-menu-button");
	if (!btn) return; btn_help =`

Двойной клик:  открыть [Загрузки]
…на картинке: ⧉ найти Похожие\n
Правый клик (Alt+S):  Сохранить
   в единый html всё / выделенное
…дважды  Картинки вкл/выкл\n
Ролик:	 Сохранить как файл .txt
Колёсико на рисунке: ➜ Сохранить`,	PanelUI_help =

`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}
…+ Shift		⚑ Краткая справка
…+ Alt		Персонализация
Клик дважды	⊠ закрыть браузер \n
Правый клик	⇲ Свернуть
…+ дважды	⤾ Вернуть вкладку
…+ Alt		Диспетчер задач
…+ Shift		Адаптивный дизайн\n
Колёсико:	Развернуть | окно
…+ Alt		Полный экран
…+ дважды	Обновить без кэша`, singlesave_help = `\nAlt⇧S	 ⌨ нажатие SingleSave`;

	var addDestructor = nextDestructor => {
		var {destructor} = ucf[id];
		ucf[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	},
	showInStatusPanel = (info, time = 5000) => {
		var win = Services.wm.getMostRecentWindow("navigator:browser"); StatusPanel = win.StatusPanel;
		if (StatusPanel.update.tid)
			clearTimeout(StatusPanel.update.tid)
		else {
			var {update} = StatusPanel;
			StatusPanel.update = () => {};
			StatusPanel.update.ret = () => {
				StatusPanel.update = update;
				StatusPanel.update();
			}
		}
		StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, time);
		StatusPanel._label = info;
	},
	saveSelectionToTxt = async () => { // сохранить страницу или выделенный текст как файл .txt
		var splice = saveURL.length == 10;
		var msgName = id + ":Save:GetSelection";
		var receiver = msg => {
			var title = document.title || gBrowser.selectedTab.label;
			var args = [
				"data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + "\n\n" + msg.data),
				title.replace(/[:\\\/<>?*|"]+/g,'_').replace(/\s+/g,' ').slice(0, 100).trim() + '_' + new Date().toLocaleString('ru').replace(', ','-').replace(/:/g, '։') + '.txt',
				null, false, true, null, window.document
			];
			splice && args.splice(5, 0, null);
			saveURL(...args) && showInStatusPanel("√ текст сохранён: " + title.slice(0, 60));
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));
		var func = fm => {
			var res, fed, win = {}, fe = fm.getFocusedElementForWindow(content, true, win);
			var sel = (win = win.value).getSelection();
			if (sel.isCollapsed) {
				var ed = fe && fe.editor;
				if (ed && ed instanceof Ci.nsIEditor)
					sel = ed.selection, fed = fe;
			}
			if (sel.isCollapsed)
				fed && fed.blur(), docShell.doCommand("cmd_selectAll"),
				res = win.getSelection().toString(), docShell.doCommand("cmd_selectNone"),
				fed && fed.focus();
			res = res || sel.toString();
			/\S/.test(res) && sendAsyncMessage("saveSelectionToTxt", res);
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})`.replace("saveSelectionToTxt", msgName)) + '(Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager));';
		(saveSelectionToTxt = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	}, // end

	save = async () => { // автор: Лекс, правка: Dumby, Dobrov (пути сохранения HTML)
		var msgName = id + "ucfDwnldsBtnSaveSnapshotToHTML";
		if (typeof IOUtils != "object") { // Firefox 78 ESR
			var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
			var PathUtils = {join: (...args) => OS.Path.join(...args)};
			var IOUtils = {writeUTF8: (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt))};
		}
		var write = IOUtils.writeUTF8 ? "writeUTF8" : "writeAtomicUTF8";

		var Title = (type) => { // получить заголовок (без обрезки, если type не указан) или домен (type <0)
			var title = (document.title || gBrowser.selectedTab.label);
			if ( !type ) return title; // заголовок
			if ( type > 0 ) return title.slice(0, type).replace(/ \| Форум Mozilla Россия$| — Mozilla Firefox|[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim(); // ограничить длину имени
			var host = (/^file:\/\//.test(gURLBar.value)) ? '' : gURLBar.value.replace(/^.*url=|https?:\/\/|www\.|\/.*/g,'');
			return host.replace(/^ru\.|^m\.|forum\./,'').replace(/^club\.dns/,'dns');
		}
		var msgListener = async msg => {
			var [fileContent, fileName] = msg.data, dir;
			try {dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}
			var arr = prefs.getStringPref("ucf_save.dirs", "_Web||_Images|0").split('|').slice(0, 2); // Dir/subdir: пусто|0 title|1 домен
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // для ucf_save.dirs = "_Web||_Pics|1" HTML сохранится в папку [Загрузки]/_Web/имя вкладки
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
			var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
			file.initWithPath(dir.path);
			dir.append(fileName);
			await IOUtils[write](dir.path, fileContent) && showInStatusPanel("√ страница записана: " + fileName.slice(0, 60));
			var d = await Downloads.createDownload({ source: "about:blank", target: FileUtils.File(dir.path)}); // Fake download
			(await Downloads.getList(Downloads.ALL)).add(d);
			d.refresh(d.succeeded = true); // кнопка Загрузки мигает
		}
		messageManager.addMessageListener(msgName, msgListener);
		addDestructor(() => messageManager.removeMessageListener(msgName, msgListener));

		var svc = 'globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services';
		var url = "data:;charset=utf8," + encodeURIComponent(`(${func})(${svc});`.replace("%MSG_NAME%", msgName));
		(save = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	}, // end save

	tid, allowMousedown, listener = {
		handleEvent(e) {
			if (e.detail > 2) return;
			var btn = e.target;
			var dbl = e.detail == 2;
			var num = (btn == pui && 100) + 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;

			if (!this[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "mousedown";
			}
			if (dbl) tid &&= clearTimeout(tid), this[num](btn);
			else tid = setTimeout(this.exec, 300, num, btn, this);
		},
		exec(num, btn, self) {
			tid = null;
			self[num](btn);
		},
		mousedown(btn) {
			allowMousedown = true;
			btn.dispatchEvent(new MouseEvent("mousedown", {}));
			allowMousedown = false;
		},
		context(btn) {
			btn.removeAttribute("context");
			btn.dispatchEvent(new MouseEvent("contextmenu", this.a));
			btn.toggleAttribute("context");
		},
		switchToTab(but, url) { // открыть вкладку | закрыть, если открыта
			for(var tab of but.ownerGlobal.gBrowser.tabs)
				if ( tab.linkedBrowser.currentURI.spec == url ) {but.ownerGlobal.gBrowser.removeTab(tab); return;}; // вкладка найдена, закрыть
			but.ownerGlobal.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
		},

		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

/*** ======= Downloads Clicks ======= ***/

		16: saveSelectionToTxt, // СКМ Click (сохранить .txt)
		1() { // Double Left Click - Обзор папки «Загрузки»
			Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
		},
		32: save, // ПКМ Click (Single HTML)
		33(btn) { // Double Right Click
			var pref = "permissions.default.image";
			var one = prefs.getIntPref(pref) == 1;
			prefs.setIntPref(pref, one ? 2 : 1);
			btn.style.filter = one ? "hue-rotate(180deg) brightness(95%)" : "";
			BrowserReload();
		},
		2() { // Alt + ЛКМ
		},

/*** ======= PanelUI-menu Clicks ======= ***/

		102() { gCustomizeMode.enter(); // ЛКМ + Alt Персонализация
		},
		104() { // Shift + ЛКМ
			var help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'];
			var newURI = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).convertChromeURL(Services.io.newURI(help_ucf[0])); // .spec = file:///
			(newURI.QueryInterface(Ci.nsIFileURL).file.exists()) ? this.switchToTab(pui, help_ucf[0]) : this.switchToTab(pui, help_ucf[1]);
		},
		118: BrowserFullScreen, // Alt + СКМ
		116() { // СКМ
			windowState != STATE_MAXIMIZED ? maximize() : restore();
		},
		117: BrowserReloadSkipCache,  // СКМ Double
		132() { // ПКМ
			minimize();
		},
		134(pui) { // Alt + ПКМ
			this.switchToTab(pui, 'about:performance');
		},
		136(pui) { // Shift + ПКМ
			pui.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // запуск пункта меню с HotKey
		},
		138(pui) { // Shift + Alt + ПКМ
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_devtools_remotedebugging");
			(this[136] = target => obj.oncommand({target}))(pui); // запуск пункта меню, у которого нет HotKey
		},
		101: BrowserTryToCloseWindow, // Double Left Click
		133(pui) { // ПКМ Double Right Click
			pui.ownerGlobal.undoCloseTab();
		},
	}, // end Clicks

	keydown_win = e => { // нажатие клавиш
		if (e.keyCode == 83 && e.altKey) e.shiftKey // Alt+S [+Shift]
		? singlesave ? singlesave.click() : save() : save(); // имитировать клик по кнопке, используя её ID
	},
	{prefs, dirsvc} = Services, linux = /macos|linux/.test(AppConstants.platform), singlesave;
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	var mouseenter = function(e) {
		this.parentNode.addEventListener("mousedown", stop, true);
		this.addEventListener("mouseleave", mouseleave, {once: true});

		if ((e.target == pui) && (!/справка/.test(pui.tooltipText)))
			pui.tooltipText = PanelUI_help; // обновить подсказки кнопок
		if (e.target == btn) {
			var singlesave = document.getElementById("_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action"), dw;
			try {dw = prefs.getComplexValue("browser.download.dir",Ci.nsIFile)} catch {dw = dirsvc.get("DfltDwnld", Ci.nsIFile)};
		// if (!/Двойной/.test(btn.tooltipText))
			btn.tooltipText = GetDynamicShortcutTooltipText(btn.id) + btn_help; // обновлять подсказку при наведении мыши
		if (singlesave){
			if (!/SingleSave/.test(btn.tooltipText)) btn.tooltipText = btn.tooltipText + singlesave_help;
		} else
			btn.tooltipText = btn.tooltipText.replace(singlesave_help,'');
		if (!/выбранная/.test(btn.tooltipText))
			btn.tooltipText = btn.tooltipText + `${dw ? "\n\n[Загрузки] — выбранная папка:\n"+ dw.path.substring(0,33) + `${dw.path.length > 32 ? `…\n…${dw.path.substring(dw.path.length -31, dw.path.length)}`: ""}` : ""}`; // сократить/разбить длинную строку
		}
	}
	var mouseleave = function(e) {
		this.parentNode.removeEventListener("mousedown", stop, true);
	}
	var stop = e => {
		e.button || allowMousedown || e.stopImmediatePropagation();
	}
	var btns = [btn, pui];
	for(var b of btns) {
		b.toggleAttribute("context"),
		b.addEventListener("click", listener, true),
		b.addEventListener("mouseenter", mouseenter);
	}
	window.addEventListener("keydown", keydown_win);

	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		for(var b of btns) {
			b.removeEventListener("click", listener, true);
			b.removeEventListener("mouseenter", mouseenter);
			if (b.matches(":hover"))
				b.removeEventListener("mouseleave", mouseleave),
				b.parentNode.removeEventListener("mousedown", stop, true);
		}
		window.removeEventListener("keydown", keydown_win);
	}};

	ucf.unloadlisteners.push(id);

})("downloads-button-click-listener", ({io, focus}) => { // SingleHTML не сохраняет svg графику

	var resolveURL = function (url, base) {
		try { return io.newURI(url, null, io.newURI(base)).spec;
		} catch {}
	},
	getSelWin = function (w) {
		if (w.getSelection().toString()) return w;
		for (var i = 0, f, r; f = w.frames[i]; i++) {
			try { if (r = getSelWin(f)) return r;
			} catch(e) {}
		}
	},
	encodeImg = function (src, obj) {
		var canvas, img, ret = src;
		if (/^https?:\/\//.test(src)) {
			canvas = doc.createElement('canvas');
			if (!obj || obj.nodeName.toLowerCase() != 'img') {
				img = doc.createElement('img');
				img.src = src;
			} else
				img = obj;
			if (img.complete) try{
				canvas.width = img.width;
				canvas.height = img.height;
				canvas.getContext('2d').drawImage(img, 0, 0);
				ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png'));
			} catch (e) {};
			if (img != obj) img.src = 'about:blank';
		};
		return ret;
	},
	toSrc = function (obj) {
		var strToSrc = function (str) {
			var chr, ret = '', i = 0, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\x22' : '\\\x22', '\\': '\\\\'};
			while (chr = str.charAt(i++)) {
				ret += meta[chr] || chr;
			};
			return '\x22' + ret + '\x22';
		},
		arrToSrc = function (arr) {
			var ret = [];
			for (var i = 0; i < arr.length; i++) {
				ret[i] = toSrc(arr[i]) || 'null';
			};
			return '[' + ret.join(',') + ']';
		},
		objToSrc = function (obj) {
			var val, ret = [];
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val);
			};
			return '{' + ret.join(',') + '}';
		};
		switch (Object.prototype.toString.call(obj).slice(8, -1)) {
			case 'Array': return arrToSrc(obj);
			case 'Boolean':
			case 'Function':
			case 'RegExp': return obj.toString();
			case 'Date': return 'new Date(' + obj.getTime() + ')';
			case 'Math': return 'Math';
			case 'Number': return isFinite(obj) ? String(obj) : 'null';
			case 'Object': return objToSrc(obj);
			case 'String': return strToSrc(obj);
			default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null';
		}
	},
	mainWin = {};
	focus.getFocusedElementForWindow(content, true, mainWin);
	mainWin = mainWin.value;

	var selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location;
	var ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g;

	if (selWin) {
		var rng = win.getSelection().getRangeAt(0);
		pEle = rng.commonAncestorContainer;
		ele = rng.cloneContents();
	} else {
		pEle = doc.documentElement;
		ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true);
	};
	while (pEle) {
		if (pEle.nodeType == 1) {
			clone = pEle.cloneNode(false);
			clone.appendChild(ele);
			ele = clone;
		};
		pEle = pEle.parentNode
	};
	var sel = doc.createElement('div');
	sel.appendChild(ele);

	for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) {
		el = all[i];
		if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) {
			if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href);
			return prev + encodeImg(url) + next;
		});
		switch (el.nodeName.toLowerCase()) {
			case 'link':
			case 'style':
			case 'script': el.parentNode.removeChild(el); break;
			case 'a':
			case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break;
			case 'img':
			case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break;
			case 'audio':
			case 'video':
			case 'embed':
			case 'frame':
			case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break;
			case 'object': if (el.hasAttribute('data')) el.data = el.data; break;
			case 'form': if (el.hasAttribute('action')) el.action = el.action; break;
		}
	};
	var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets, title = doc.getElementsByTagName('title')[0];
	meta.httpEquiv = 'content-type';
	meta.content = 'text/html; charset=utf-8';
	head.appendChild(meta);
	if (title) head.appendChild(title.cloneNode(true));

	head.copyScript = function (unsafeWin) {
		if ('$' in unsafeWin) return;
		var f = doc.createElement('iframe');
		f.src = 'about:blank';
		f.setAttribute('style', 'position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');
		doc.documentElement.appendChild(f);
		var str, script = doc.createElement('script');
		script.type = 'text/javascript';
		for (var name in unsafeWin) {
			if (name in f.contentWindow || !/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) continue;
			try {
				str = toSrc(unsafeWin[name]);
				if (!/\{\s*\[native code\]\s*\}/.test(str)) {
					script.appendChild(doc.createTextNode('var ' + name + ' = ' + str.replace(/<\/(script>)/ig, '<\\/$1') + ';\n'));
				}
			} catch (e) {};
		};
		f.parentNode.removeChild(f);
		if (script.childNodes.length) this.nextSibling.appendChild(script);
	};
	head.copyScript(win.wrappedJSObject || win);

	head.copyStyle = function (s) {
		if (!s) return;
		var style = doc.createElement('style');
		style.type = 'text/css';
		if (s.media && s.media.mediaText) style.media = s.media.mediaText;
		try {
			for (var i = 0, rule; rule = s.cssRules[i]; i++) {
				if (rule.type != 3) {
					if((!rule.selectorText || rule.selectorText.indexOf(':') != -1) || (!sel.querySelector || sel.querySelector(rule.selectorText))) {
						var css = !rule.cssText ? '' : rule.cssText.replace(reUrl, function (a, prev, url, next) {
							if (!/^[a-z]+:/.test(url)) url = resolveURL(url, s.href || loc.href);
							if(rule.type == 1 && rule.style && rule.style.backgroundImage) url = encodeImg(url);
							return prev + url + next;
						});
						style.appendChild(doc.createTextNode(css + '\n'));
					}
				} else {
					this.copyStyle(rule.styleSheet);
				}
			}
		} catch(e) {
			if (s.ownerNode) style = s.ownerNode.cloneNode(false);
		};
		this.appendChild(style);
	};
	for (var j = 0; j < sheets.length; j++) head.copyStyle(sheets[j]);
	head.appendChild(doc.createTextNode('\n'));

	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};

	var fileName = selWin ? win.getSelection().toString() : (title && title.text ? title.text : loc.pathname.split('/').pop());
	fileName = fileName.replace(/[:\\\/<>?*|"]+/g, '_').replace(/\s+/g, ' ').slice(0, 100).trim();
	fileName += "_" + new Date().toLocaleDateString('ru', {day: 'numeric', month: 'numeric', year: '2-digit'}) +'-'+ new Date().toLocaleTimeString().replace(/:/g, "։");
	if (!/\.html?$/.test(fileName)) fileName += '.html';

	sendAsyncMessage("%MSG_NAME%", [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', fileName]);

}); // END hookClicks

Отсутствует

 

№12713-11-2021 00:15:26

unter_officer
Участник
 
Группа: Members
Откуда: Санкт-Петербург
Зарегистрирован: 27-03-2011
Сообщений: 254
UA: Firefox 91.0

Re: UCF-скрипты на этом форуме

Dumby
Возможно ли сделать отдельную кнопочку для Вкл/Выкл "Адаптивного дизайна"?


«The Truth Is Out There»

Отсутствует

 

№12813-11-2021 02:00:44

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

Re: UCF-скрипты на этом форуме

unter_officer пишет

Возможно ли сделать отдельную кнопочку для Вкл/Выкл "Адаптивного дизайна"?

пример уже есть в этом топике

Выделить код

Код:

(async (bar, exp, tid, self) => CustomizableUI.createWidget(self = { id: "test-button", localized: false, label: 
"Test Button", tooltiptext:
`ЛКМ:	Design View
CKM: folder UCF`,

	onCreated(btn) {
		btn.onclick = this.click;
		btn.toggleAttribute("context");
		btn.setAttribute("image", "chrome://browser/skin/preferences/application.png");
	},
	exec(num, win) {
		tid = null;
		self[num](win);
	},
	context(win) {
		var btn = win.document.getElementById(this.id);
		btn.removeAttribute("context");
		btn.dispatchEvent(new win.MouseEvent("contextmenu", this.a));
		btn.toggleAttribute("context");
	},
	a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

	click(e) {
		if (e.detail > 2) return;
		var n2 = e.button != 2;
		var dbl = e.detail == 2;
		var num = 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;
		if (!self[num]) {
			if (n2) return;
			num = "context";
			for(var p in self.a) self.a[p] = e[p];
		}
		var win = e.view;
		if (dbl) tid &&= win.clearTimeout(tid), self[num](win);
		else tid = win.setTimeout(self.exec, 300, num, win);
	},
	0(win) { // ЛКМ
		var btn = win.document.getElementById(this.id);
		btn.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // запуск пункта меню с HotKey
	},
	1(win) { // Double Left Click
		win.alert("DBL Click");
	},
	16: () => exp("UChrm", "user_chrome_files"), // СКМ
}))(
	(win, bar) => win.SidebarUI.toggle(bar),
	(dir, sub) => {
		dir = Services.dirsvc.get(dir, Ci.nsIFile);
		sub && dir.append(sub); dir.exists() && dir.launch();
	}
);

Отсутствует

 

№12913-11-2021 06:39:10

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

Re: UCF-скрипты на этом форуме

Dumby - ещё просьба вдобавок к первой:
подскажи код, чтобы выполнять скрипт из файла. То есть, кликом на кнопке выполняем внешний …/user_chrome_files/custom_scripts/user.js, в который должны быть переданы все текущие переменные, функции и т.п. той кнопки, из которой вызван внешний JS.


Может, это пригодиться для отладки скриптов без перезапуска браузера. А может есть более простой способ отладки части кода кнопки?
Наверное, код внешнего JS не должен попадать в startupCache, это верно?

Отсутствует

 

№13013-11-2021 10:36:01

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

Re: UCF-скрипты на этом форуме

Dobrov
Как пример вызова функции из другого скрипта можно посмотреть здесь, выше и ниже. Скрипт с функцией тоже должен быть загружен. Не знаю, это ли было нужно.
https://forum.mozilla-russia.org/viewtopic.php?pid=796057#p796057

Отсутствует

 

№13113-11-2021 13:46:13

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

Re: UCF-скрипты на этом форуме

xrun1 - не то. Цитата - Выполняем внешний скрипт. И никаких подгружаемых функций.

Отсутствует

 

№13213-11-2021 20:59:05

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

Re: UCF-скрипты на этом форуме

Dobrov пишет

при старте браузера кнопки Reader View в строке адреса нет, она появляется, когда открывается сайт

Значит слушать клики на родительском контейнере и проверять id кликнутого.

кликом на кнопке выполняем внешний …/user_chrome_files/custom_scripts/user.js, в который должны быть переданы все текущие переменные, функции и т.п. той кнопки, из которой вызван внешний JS
Наверное, код внешнего JS не должен попадать в startupCache, это верно?

Если scriptloader'ом по протоколу chrome:, то будет кэшироваться.
Можно использовать loadSubScriptWithOptions(),
но «все текущие переменные, функции» идут лесом,
разве что только в объект пробросить.

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

Выделить код

Код:

Services.scriptloader.loadSubScriptWithOptions(
	"chrome://user_chrome_files/content/custom_scripts/user.js",
	{ignoreCache: true, target: {myVariable1, myVariable2, myFunction}}
);


(new Function())(); тоже не подходит:
«Functions created with the Function constructor do not create closures to their creation contexts; they always are created in the global scope.»
Остаётся только direct eval().

То есть, нужно на кнопке Reader View по клику колёсиком включать/выключать режим "Адаптивный дизайн".
Желательно добавить перехват кликов в тот же UCF-скрипт, который расширяет возможности кнопок Меню и Загрузки

Вот, сводный пример. Добавляем после ucf.unloadlisteners.push(id);

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

Выделить код

Код:

.....
	var box = document.getElementById("page-action-buttons");
	var key = document.getElementById("key_responsiveDesignMode");
	var uri = Services.io.newURI("chrome://user_chrome_files/content/custom_scripts/user.js");
	var boxLst = e => eval(Cu.readUTF8URI(uri));
	box.addEventListener("auxclick", boxLst, true);
	addDestructor(() => box.removeEventListener("auxclick", boxLst, true));


user.js

Выделить код

Код:

if (
	e.button == 1 &&
	e.target.id == "pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_"
)
	e.stopImmediatePropagation(),
	key.doCommand();

this[136]

this[138], наверно.


unter_officer пишет

Возможно ли сделать отдельную кнопочку для Вкл/Выкл "Адаптивного дизайна"?

А в чём конкретно затруднение?
Там же просто получаем <key> и вызываем doCommand();

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

Выделить код

Код:

(async id => CustomizableUI.createWidget({
	get label() {
		var l10n = new (ChromeUtils.import("resource://devtools/shared/Loader.jsm")
			.require("devtools/shared/l10n")).MultiLocalizationHelper(
				"devtools/client/locales/startup.properties",
				"devtools/client/locales/menus.properties"
			);
		this.tooltiptext = l10n.getFormatStr(
			"toolbox.buttons.responsive",
			Services.appinfo.OS == "Darwin" ? "Cmd+Opt+M" : "Ctrl+Shift+M"
		);
		delete this.label;
		return this.label = l10n.getStr("responsiveDesignMode.label");
	},
	id: "ucf-responsiveDesignMode-btn",
	localized: false,
	onCreated(btn) {
		btn._handleClick = this.click;
		btn.style.cssText = `
			fill-opacity: 0 !important;
			-moz-context-properties: fill, fill-opacity !important;
			list-style-image: url(chrome://devtools/skin/images/command-responsivemode.svg) !important;
		`;
	},
	click() {
		this.ownerDocument.getElementById(id).doCommand();
	}
}))("key_responsiveDesignMode");

Отсутствует

 

№13314-11-2021 01:00:53

unter_officer
Участник
 
Группа: Members
Откуда: Санкт-Петербург
Зарегистрирован: 27-03-2011
Сообщений: 254
UA: Firefox 52.0

Re: UCF-скрипты на этом форуме

Dumby пишет

А в чём конкретно затруднение?
Там же просто получаем <key> и вызываем doCommand();

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

Выделить код

Код:

(async id => CustomizableUI.createWidget({
	get label() {
		var l10n = new (ChromeUtils.import("resource://devtools/shared/Loader.jsm")
			.require("devtools/shared/l10n")).MultiLocalizationHelper(
				"devtools/client/locales/startup.properties",
				"devtools/client/locales/menus.properties"
			);
		this.tooltiptext = l10n.getFormatStr(
			"toolbox.buttons.responsive",
			Services.appinfo.OS == "Darwin" ? "Cmd+Opt+M" : "Ctrl+Shift+M"
		);
		delete this.label;
		return this.label = l10n.getStr("responsiveDesignMode.label");
	},
	id: "ucf-responsiveDesignMode-btn",
	localized: false,
	onCreated(btn) {
		btn._handleClick = this.click;
		btn.style.cssText = `
			fill-opacity: 0 !important;
			-moz-context-properties: fill, fill-opacity !important;
			list-style-image: url(chrome://devtools/skin/images/command-responsivemode.svg) !important;
		`;
	},
	click() {
		this.ownerDocument.getElementById(id).doCommand();
	}
}))("key_responsiveDesignMode");

Для меня это не так просто. scratch_one-s_head.gif


Dumby, большое спасибо.


«The Truth Is Out There»

Отсутствует

 

№13414-11-2021 02:19:52

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

Re: UCF-скрипты на этом форуме

Dumby - Спасибо! Ещё проблема: при возвращении из режима "Адаптивный дизайн" в обычный просмотр многие страницы остаются обрезанными, нужно F5 жать.
Как при возвращении в обычный просмотр ещё и обновить страницу? (но не обновлять её для Адаптивного дизайна)


Dumby - а как обновить подсказку для "Reader View" в панели адреса?
У меня только грубый вариант: зарегистрировать mouseenter и постоянно делать: ReaderView.tooltipText = подсказка……

Выделить код

Код:

var box = document.getElementById("page-action-buttons"); // кнопки панели адреса
var boxLst = e => {
	if (e.button == 1 && e.target.id == "pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_") // Reader View
		e.stopImmediatePropagation(),	document.getElementById("key_responsiveDesignMode").doCommand(); // Адаптивный дизайн
}
box.addEventListener("auxclick", boxLst, true);
addDestructor(() => box.removeEventListener("auxclick", boxLst, true));

Dumby - Вопрос: в custom_script.js функция loadscript не грузит одну .JSM-ку, для которой нужна отдельная строка запуска :
ChromeUtils.import(`${scripts}/UCFTitleChangedChild.jsm`, {}).registerUCFTitleChanged(); // исправление заголовка вкладки

Выделить код

Код:

var EXPORTED_SYMBOLS = ["registerUCFTitleChanged", "UCFTitleChangedChild"];
function registerUCFTitleChanged() { // исправление заголовка вкладки …………

Попробуй исправить loadscript, чтобы он был более универсальный, а второй параметр мог быть именем выполняемой функции:
loadscript("UCFTitleChangedChild.jsm", registerUCFTitleChanged()); // так не работает!

Выделить код

Код:

const scripts = 'chrome://user_chrome_files/content/custom_scripts'; (async () => { // загрузка внешних js или jsm-скриптов
	var loadscript = (name, function_register) => {
		try { name.split('.').pop().split("?")[0].split("#")[0].toLowerCase() == "jsm"
			? ChromeUtils.import(`${scripts}/${name}`, {}).function_register
			: Services.scriptloader.loadSubScript(`${scripts}/${name}`,globalThis,"UTF-8");
			return true;
		} catch(e) {}
	};
	loadscript("ucf_eom-button.js"); // нижеследующая строка не работает:
	// loadscript("UCFTitleChangedChild.jsm", registerUCFTitleChanged()); 
})();

Отредактировано Dobrov (14-11-2021 04:37:50)

Отсутствует

 

№13514-11-2021 20:10:47

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

Re: UCF-скрипты на этом форуме

Dobrov пишет

но не обновлять её для Адаптивного дизайна

Можно проверять gBrowser.selectedBrowser.browsingContext.inRDMPane

как обновить подсказку для "Reader View" в панели адреса?

Если в смысле установить свою как для аддона, то вот вариант (в custom_script.js).

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

Выделить код

Код:

(async url => {
	var tooltip = "Test Tooltip";

	var m = "2495d258-41e7-4cd5-bc7d-ac15981f064e", id = `{${m}}`, aid = `_${m}_`;
	var manager = ChromeUtils.import(url).ExtensionParent.apiManager;
	var wait = (e, isAppShutdown) => isAppShutdown || manager.on("ready", onReady);
	var onReady = (e, addon) => {
		if (addon.id != id) return;
		manager.off("ready", onReady);
		addon.once("shutdown", wait);
		manager.global.PageActions.actionForID(aid).setTooltip(tooltip);
	}
	manager.on("ready", onReady);

})("resource://gre/modules/ExtensionParent.jsm");

исправить loadscript

Что-то мне не слишком понятны код и задача, может так сойдёт?

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

Выделить код

Код:

(async scripts => {
	var re = /\.jsm$/i;
	var loadscript = name => {
		try {
			var {href, pathname} = new URL(scripts + name);
			if (re.test(pathname))
				return ChromeUtils.import(href);
			Services.scriptloader.loadSubScript(href);
			return true;
		}
		catch(ex) {Cu.reportError(ex);}
	}

	loadscript("ucf_eom-button.js");
	loadscript("UCFTitleChangedChild.jsm")?.registerUCFTitleChanged?.();

})("chrome://user_chrome_files/content/custom_scripts/");


Добавлено: Хотя нет, сам вызов же может завершиться с ошибкой.
Тогда затащим внутрь, как написано, второй параметр — имя выполняемой функции.
скрытый текст

Выделить код

Код:

(async scripts => {
	var re = /\.jsm$/i;
	var loadscript = (name, funcName) => {
		try {
			var {href, pathname} = new URL(scripts + name);
			if (re.test(pathname)) {
				var obj = ChromeUtils.import(href);
				funcName && obj[funcName]();
			} else
				Services.scriptloader.loadSubScript(href);
			return true;
		}
		catch(ex) {Cu.reportError(ex);}
	}

	loadscript("ucf_eom-button.js");
	loadscript("UCFTitleChangedChild.jsm", "registerUCFTitleChanged");

})("chrome://user_chrome_files/content/custom_scripts/");

Отредактировано Dumby (14-11-2021 21:07:11)

Отсутствует

 

№13615-11-2021 00:13:02

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

Re: UCF-скрипты на этом форуме

Dumby - Спасибо! обновление подсказки кнопки также работает из custom_script_win.js.


Dumby - проверь мой новый загрузчик: (сократил, чтобы не повторять строки с loadscript)
Переделал через список массива  js-jsm скриптов. Путь к скриптам используется ещё для подключения [CB]-кодов, поэтому константа.

Выделить код

Код:

const scripts = 'chrome://user_chrome_files/content/custom_scripts/'; (async () => { // ваши скрипты
	[['ucf_QuickToggle.js'], ['UCFTitleChangedChild.jsm', 'registerUCFTitleChanged'], ['Test.jsm']]
	.forEach(function(name) { try { if (/\.jsm$/i.test(name[0])) { // [скрипт js или jsm, инициализация]
				var obj = ChromeUtils.import(scripts + name[0]);
				name[1] && obj[name[1]]();
			} else Services.scriptloader.loadSubScript(scripts + name[0]);
		} catch(ex) {Cu.reportError(ex);}
	});
})();

Отредактировано Dobrov (16-11-2021 02:10:34)

Отсутствует

 

№13715-11-2021 13:39:52

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

Re: UCF-скрипты на этом форуме

Dobrov пишет

проверь мой новый загрузчик: (сократил, чтобы не повторять строки с loadscript)

Ну, выглядит нормально.
Но, замысел целиком мне же неизвестен.
Вот зачем тогда функция что-то возвращает, раз это не используется.


Или, в исходнике, name.split('.').pop().split("?")[0].split("#")[0].toLowerCase()
наводило на мысль, что будут присутствовать имена типа "SomeModule.JsM?q=lol#bla",
но ничего подобного пока не видно, хотя, может потом добавятся, а если нет, то зря new URL() создаётся.
Короче — ничего серьёзного.

Отсутствует

 

№13816-11-2021 16:02:55

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

Re: UCF-скрипты на этом форуме

Dumby - вопрос по коду обновления ToolTip кнопки расширения.
Почему-то подсказка для Video DownloadHelper не обновляется! И как переделать код для замены Tooltip на нескольких кнопках расширений?

Выделить код

Код:

var view_id = "2495d258-41e7-4cd5-bc7d-ac15981f064e"; // Reader View
var vdh_id = "b9db16a4-6edc-47ec-a1f4-b86292ed211d"; // Video DownloadHelper
var manager = ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm").ExtensionParent.apiManager,
wait = (e, isAppShutdown) => isAppShutdown || manager.on("ready", onReady),
onReady = (e, addon) => {
	// if (addon.id != `{${view_id}}`) return;
	if (addon.id == `{${view_id}}`) {
		manager.off("ready", onReady), addon.once("shutdown", wait);
		manager.global.PageActions.actionForID(`_${view_id}_`).setTooltip(`Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n\nКлик мыши	Режим для чтения\nКолёсико	Адаптивный дизайн`); // изменить подсказку
	}
	if (addon.id == `{${vdh_id}}`) {
		manager.off("ready", onReady), addon.once("shutdown", wait);
		manager.global.PageActions.actionForID(`_${vdh_id}_`).setTooltip(`Video DownloadHelper\nСкачивание проигрываемого видео`);
	}
};	manager.on("ready", onReady);

Отсутствует

 

№13917-11-2021 14:33:12

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

Re: UCF-скрипты на этом форуме

Dobrov пишет

Video DownloadHelper

Совсем разные вещи. У RV pageAction, а у VDH browserAction.

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

Выделить код

Код:

(async url => {
	// Reader View
	var rv = "2495d258-41e7-4cd5-bc7d-ac15981f064e";
	var rv_id = `{${rv}}`, rv_aid = `_${rv}_`;
	var rv_ttt = `Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n\nКлик мыши	Режим для чтения\nКолёсико	Адаптивный дизайн`;

	// Video DownloadHelper
	var vdh_id = "{b9db16a4-6edc-47ec-a1f4-b86292ed211d}";
	var vdh_ttt = "Video DownloadHelper\nСкачивание проигрываемого видео";

	var count = 0;
	var manager = ChromeUtils.import(url).ExtensionParent.apiManager;
	var wait = (e, isAppShutdown) => isAppShutdown || !--count || manager.on("ready", onReady);
	var onReady = (e, addon) => {
		if (addon.id == rv_id)
			manager.global.PageActions.actionForID(rv_aid).setTooltip(rv_ttt);
		else if (addon.id == vdh_id)
			setVDHTooltip(addon);
		else return;

		++count == 2 && manager.off("ready", onReady);
		addon.once("shutdown", wait);
	}
	manager.on("ready", onReady);

	var setVDHTooltip = addon => {
		var vdh_wid = `_${vdh_id.slice(1, -1)}_-browser-action`;
		var {gPalette} = Cu.import("resource:///modules/CustomizableUI.jsm", {});

		var upd = manager.global.browserAction.prototype.updateButton;
		var asgn = eval(`({${upd}})`.replace(/\n^.+"tooltiptext".+$/m, ""));

		(setVDHTooltip = addon => {
			var widget = gPalette.get(vdh_wid);
			widget.tooltiptext = vdh_ttt;

			var {action} = manager.global.browserActionFor(addon);
			Object.assign(action.buttonDelegate, asgn);
			for(var [, node] of widget.instances)
				node.setAttribute("tooltiptext", vdh_ttt);
		})(addon);
	}
})("resource://gre/modules/ExtensionParent.jsm");

Отсутствует

 

№14018-11-2021 18:33:26

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

Re: UCF-скрипты на этом форуме

Dumby - ты делал перехват кликов для кнопок в панели адреса в скрипте ucf_hookClicks.js.
Получилась обработка кликов двумя дублирующими способами: первый до ucf.unloadlisteners, затем для кнопок на панели адреса.
Второй способ перехватывает клики всех кнопок page-action-buttons, а при обработке проверяется id кнопки.


Возможно ли доработать код, чтобы сразу перехватывать клики кнопок "nav-bar-customization-target" основной панели и "page-action-buttons" панели адреса ?
Выгода этого способа в том, что проще в одном скрипте прописать дополнительные клики нужных кнопок, а не делать кучу скриптов, где каждая кнопка обрабатывается персонально. Также прошу по возможности добавить действие на долгое нажатие кнопки, так как у меня перестал обрабатываться долгий клик в скрипте ToggleAboutConfig, когда я добавил addEventListener("mouseenter" для nav-bar-customization-target в скрипт ucf_hookClicks.


Ещё хотелка - добавить перехват "wheel". Ожидаемый итог работы кода: перехват событий кнопок для двух панелей, разбор такой же, как в твоём коде перехвата кликов: 512: saveSelectionToTxt, // СКМ Click (сохранить .txt) цифра содержит сумму событий: id кнопки, клавиш мыши, мета-клавиш, тип кликов, скролл над кнопками тулбара…
Удобнее сделать изменение яркости скролом над панелью безопасности "identity-box", чем над Звёздочкой. А скролл над кнопками основной панели определять отдельно для каждой, то есть добавить флаги e.scroll+ и e.scroll- так же, как сделано для dbl (дубль-клик).

ucf_hookClicks.js - поменял подсчёт кнопок и клавиш - строка 136

Выделить код

Код:

(async (id, func) => { // для custom_script_win.js: дополнительные клики и подсказки кнопок
	await window.delayedStartupPromise; var
	box = document.getElementById("page-action-buttons"), // кнопки панели адреса
	nav = document.getElementById("nav-bar-customization-target"), // кнопки панели
	btn = document.getElementById("downloads-button"), // 0 Загрузки
	pui = document.getElementById("PanelUI-menu-button"), // 1 меню
	fav = document.getElementById("star-button"), // 2
	prn = document.getElementById("print-button"), // 3
	rv = "pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_", // 4 Reader View
	sgs = "_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action", // SingleSave button
	vdh = "_b9db16a4-6edc-47ec-a1f4-b86292ed211d_-browser-action"; /*Video DownloadHelper*/ if (!btn) return; btn_help =`

Двойной клик: ⬇︎ открыть [Загрузки]
…на картинке: ⧉ найти Похожие\n
Правый клик (Alt+S):  Сохранить
   в единый html всё / выделенное
…дважды  Картинки вкл/выкл\n
Ролик:	 Сохранить как файл .txt
Колёсико на рисунке: ➜ Сохранить`,	PanelUI_help =

`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}
…+ Shift		⚑ Краткая справка
…+ Alt		Персонализация
Клик дважды	⊠ закрыть браузер \n
Правый клик	⇲ Свернуть
…+ дважды	⤾ Вернуть вкладку
…+ Alt		Диспетчер задач
…+ Shift		Адаптивный дизайн\n
Колёсико:	Развернуть | окно
…+ Alt		Полный экран
…+ дважды	Обновить без кэша`, sgs_help = `\nAlt⇧S	 ⌨ нажатие SingleSave`, rv_help =

`Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n
Клик мыши	Режим для чтения
Колёсико	Адаптивный дизайн`; // vdh_help =`Video DownloadHelper\nСкачивание проигрываемого видео`;

	var addDestructor = nextDestructor => {
		var {destructor} = ucf[id];
		ucf[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	},
	showInStatusPanel = (info, time = 5000) => {
		var win = Services.wm.getMostRecentWindow("navigator:browser"); StatusPanel = win.StatusPanel;
		if (StatusPanel.update.tid)
			clearTimeout(StatusPanel.update.tid)
		else {
			var {update} = StatusPanel;
			StatusPanel.update = () => {};
			StatusPanel.update.ret = () => {
				StatusPanel.update = update;
				StatusPanel.update();
			}
		}
		StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, time);
		StatusPanel._label = info;
	},
	Title = (max, title) => { // получить заголовок. без обрезки: max не указан, домен: max <0, + дата: max=0
		if (!title) var title = document.title || gBrowser.selectedTab.label;
		if (max == undefined) return title; // заголовок как есть или ограничить длину, убрать служебные символы
		title = title.replace(/[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim();
		if ( max > 0 ) return title.slice(0, max);
		if ( max == 0) return title.slice(0, 100) +"_"+ new Date().toLocaleDateString('ru', {day: 'numeric', month: 'numeric', year: '2-digit'}) +'-'+ new Date().toLocaleTimeString().replace(/:/g, "։");
		var host = decodeURIComponent(gURLBar.value); // max < 0
		if (!/^file:\/\//.test(host)) host = host.replace(/^.*url=|https?:\/\/|www\.|\/.*/g,'');
		return host.replace(/^ru\.|^m\.|forum\./,'').replace(/^club\.dns/,'dns');
	},
	saveSelectionToTxt = async () => { // сохранить страницу или выделенный текст как файл .txt
		var splice = saveURL.length == 10;
		var msgName = id + ":Save:GetSelection";
		var receiver = msg => {
			var title = Title(0);
			var args = [
				"data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + "\n\n" + msg.data),
				title + '.txt', null, false, true, null, window.document];
			splice && args.splice(5, 0, null);
			saveURL(...args) && showInStatusPanel("√ текст сохранён: " + title.slice(0, 60));
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));
		var func = fm => {
			var res, fed, win = {}, fe = fm.getFocusedElementForWindow(content, true, win);
			var sel = (win = win.value).getSelection();
			if (sel.isCollapsed) {
				var ed = fe && fe.editor;
				if (ed && ed instanceof Ci.nsIEditor)
					sel = ed.selection, fed = fe;
			}
			if (sel.isCollapsed)
				fed && fed.blur(), docShell.doCommand("cmd_selectAll"),
				res = win.getSelection().toString(), docShell.doCommand("cmd_selectNone"),
				fed && fed.focus();
			res = res || sel.toString();
			/\S/.test(res) && sendAsyncMessage("saveSelectionToTxt", res);
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})`.replace("saveSelectionToTxt", msgName)) + '(Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager));';
		(saveSelectionToTxt = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	},
	save = async () => { // SingleHtml by Лекс, правка: Dumby, Dobrov
		var msgName = id + "ucfDwnldsBtnSaveSnapshotToHTML";
		if (typeof IOUtils != "object") { // Firefox 78 ESR
			var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
			var PathUtils = {join: (...args) => OS.Path.join(...args)};
			var IOUtils = {writeUTF8: (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt))};
		}
		var write = IOUtils.writeUTF8 ? "writeUTF8" : "writeAtomicUTF8";
		var msgListener = async msg => {
			var [fileContent, fileName] = msg.data, dir; // fileName: выделенный текст или null
			try {dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}
			var arr = prefs.getStringPref("ucf_save.dirs","_Web||_Images|0").split('|').slice(0,2); //subdir: title|host
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // ucf_save.dirs = "_Web||_Pics|1" HTML сохранится в папку [Загрузки]/_Web/label
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать, если не существует…
			var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
			file.initWithPath(dir.path);
			if (!fileName) fileName = Title(100); // убрать служебные символы
			dir.append(Title(0, fileName) +'.html');
			await IOUtils[write](dir.path, fileContent) && showInStatusPanel("√ страница записана: " + fileName.slice(0, 60));
			var d = await Downloads.createDownload({ source: "about:blank", target: FileUtils.File(dir.path)}); // Fake download
			(await Downloads.getList(Downloads.ALL)).add(d);
			d.refresh(d.succeeded = true); // кнопка Загрузки мигает
		}
		messageManager.addMessageListener(msgName, msgListener);
		addDestructor(() => messageManager.removeMessageListener(msgName, msgListener));

		var svc = 'globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services';
		var url = "data:;charset=utf8," + encodeURIComponent(`(${func})(${svc});`.replace("%MSG_NAME%", msgName));
		(save = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	},
	tid, allowMousedown, listener = { // доп.события для 15 кнопок
		handleEvent(e) {
			if (e.detail > 2) return;
			var btn = e.target;
			var dbl = e.detail == 2;
			var num = e.button *512 + e.metaKey *256 + e.ctrlKey *128 + e.shiftKey *64 + e.altKey *32 + dbl *16 +
			(btn == document.getElementById(rv) && 4) +
			(btn == prn && 3) + (btn == fav && 2) + (btn == pui && 1);

			if (!this[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "mousedown";
			}
			if (dbl) tid &&= clearTimeout(tid), this[num](btn);
			else tid = setTimeout(this.exec, 300, num, btn, this);
		},
		exec(num, btn, self) {
			tid = null;
			self[num](btn);
		},
		mousedown(btn) {
			allowMousedown = true;
			btn.dispatchEvent(new MouseEvent("mousedown", {}));
			allowMousedown = false;
		},
		context(btn) {
			btn.removeAttribute("context");
			btn.dispatchEvent(new MouseEvent("contextmenu", this.a));
			btn.toggleAttribute("context");
		},
		switchToTab(but, url) { // открыть вкладку | закрыть, если открыта
			for(var tab of but.ownerGlobal.gBrowser.tabs)
				if ( tab.linkedBrowser.currentURI.spec == url ) {but.ownerGlobal.gBrowser.removeTab(tab); return;}; // вкладка найдена, закрыть
			but.ownerGlobal.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
		},

		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

/*** ======= Downloads Clicks ======= ***/

		512: saveSelectionToTxt, // СКМ Click (сохранить .txt)
		16() { // Double Left Click - Обзор папки «Загрузки»
			Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
		},
		1024: save, // ПКМ Click (Single HTML)
		1040(btn) { // Double Right Click
			var pref = "permissions.default.image";
			var one = prefs.getIntPref(pref) == 1;
			prefs.setIntPref(pref, one ? 2 : 1);
			btn.style.filter = one ? "hue-rotate(180deg) brightness(95%)" : "";
			BrowserReload();
		},

/*** ======= PanelUI-menu Clicks ======= 100*pui 32*e.button 8*e.ctrlKey 4*e.shiftKey 2*e.altKey dbl btn ***/

		33() { gCustomizeMode.enter(); // ЛКМ + Alt Персонализация
		},
		65() { // Shift + ЛКМ
			var help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'];
			var newURI = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).convertChromeURL(Services.io.newURI(help_ucf[0])); // .spec = file:///
			(newURI.QueryInterface(Ci.nsIFileURL).file.exists()) ? this.switchToTab(pui, help_ucf[0]) : this.switchToTab(pui, help_ucf[1]);
		},
		545: BrowserFullScreen, // Alt + СКМ
		513() { // СКМ
			windowState != STATE_MAXIMIZED ? maximize() : restore();
		},
		529: BrowserReloadSkipCache,  // СКМ Double
		1025() { // ПКМ
			minimize();
		},
		1057(pui) { // Alt + ПКМ
			this.switchToTab(pui, 'about:performance');
		},
		1089(pui) { // Shift + ПКМ
			pui.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // запуск пункта меню с HotKey
			if (gBrowser.selectedBrowser.browsingContext.inRDMPane) BrowserReload();
		},
		1153(pui) { // Shift + Alt + ПКМ
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_devtools_remotedebugging");
			(this[138] = target => obj.oncommand({target}))(pui); // запуск пункта меню, у которого нет HotKey
		},
		17: BrowserTryToCloseWindow, // Double Left Click
		1041(pui) { // ПКМ Double Right Click
			pui.ownerGlobal.undoCloseTab();
		},
	}, // end Clicks

	keydown_win = e => { // нажатие клавиш
		if (e.keyCode == 83 && e.altKey) e.shiftKey // Alt+S [+Shift]
		? singlesave ? singlesave.click() : save() : save(); // имитировать клик по кнопке, используя её ID
		if (e.keyCode == 68 && e.altKey){ // Alt+D отладка - запуск внешнего JS
			// e.target.ownerDocument.getElementById("key_browserConsole").doCommand();
			eval(Cu.readUTF8URI(Services.io.newURI("chrome://user_chrome_files/content/custom_scripts/User.js")));
			console.log("END User.js " + Math.random());
		}
	},
	{prefs, dirsvc} = Services, linux = /macos|linux/.test(AppConstants.platform), singlesave;
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	var hint_upd = function(btn, text, find) { // обновить подсказку
		return;
	}
	var mouseenter = function(e) {
		this.parentNode.addEventListener("mousedown", stop, true);
		this.addEventListener("mouseleave", mouseleave, {once: true});

		var sgs_btn = document.getElementById(sgs), dw;
		try {dw = prefs.getComplexValue("browser.download.dir",Ci.nsIFile)}
			catch {dw = dirsvc.get("DfltDwnld", Ci.nsIFile)};
		var rv_btn = document.getElementById(rv); // Reader View
		if (e.target == rv_btn) {
			rv_btn.tooltipText = rv_help;
		}
		// var vdh_btn = document.getElementById(vdh); // Video DownloadHelper
		// if (e.target == vdh_btn) {
		// 	vdh_btn.tooltipText = vdh_help;
		// }
		if ((e.target == pui) && (!/справка/.test(pui.tooltipText)))
			pui.tooltipText = PanelUI_help; // обновить подсказки кнопок
		if (e.target == btn) {
		// if (!/Двойной/.test(btn.tooltipText))
			btn.tooltipText = GetDynamicShortcutTooltipText(btn.id) + btn_help; // обновлять подсказку при наведении мыши
		if (sgs_btn){
			if (!/SingleSave/.test(btn.tooltipText)) btn.tooltipText = btn.tooltipText + sgs_help;
		} else
			btn.tooltipText = btn.tooltipText.replace(sgs_help,'');
		if (!/выбранная/.test(btn.tooltipText))
			btn.tooltipText = btn.tooltipText + `${dw ? "\n\n[Загрузки] — выбранная папка:\n"+ dw.path.substring(0,33) + `${dw.path.length > 32 ? `…\n…${dw.path.substring(dw.path.length -31, dw.path.length)}`: ""}` : ""}`; // сократить/разбить длинную строку
		}
	}
	var mouseleave = function(e) {
		this.parentNode.removeEventListener("mousedown", stop, true);
	}
	var stop = e => {
		e.button || allowMousedown || e.stopImmediatePropagation();
	}
	var btns = [btn, pui];
	for(var b of btns) {
		b.toggleAttribute("context"),
		b.addEventListener("click", listener, true),
		b.addEventListener("mouseenter", mouseenter);
	}

	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		for(var b of btns) {
			b.removeEventListener("click", listener, true);
			b.removeEventListener("mouseenter", mouseenter);
			if (b.matches(":hover"))
				b.removeEventListener("mouseleave", mouseleave),
				b.parentNode.removeEventListener("mousedown", stop, true);
		}
	}};
	ucf.unloadlisteners.push(id);

	var boxLst = e => {
		console.log('@: '+ e.button);
		if (e.button == 1 && e.target.id == `pageAction-urlbar-_${rv}_`) { // Reader View Button
			e.stopImmediatePropagation(),	document.getElementById("key_responsiveDesignMode").doCommand(); // Адаптивный дизайн
			if (gBrowser.selectedBrowser.browsingContext.inRDMPane)
				BrowserReload();
		}
	}
	box.addEventListener("auxclick", boxLst, true);
	box.addEventListener("mouseenter", mouseenter, true);
	window.addEventListener("keydown", keydown_win);
	addDestructor(() => {
		box.removeEventListener("auxclick", boxLst, true);
		box.removeEventListener("mouseenter", mouseenter, true);
		window.removeEventListener("keydown", keydown_win);
	});

})("downloads-button-click-listener", ({io, focus}) => { // SingleHTML не сохраняет svg графику

	var resolveURL = function (url, base) {
		try { return io.newURI(url, null, io.newURI(base)).spec;} catch {}
	},
	getSelWin = function (w) {
		if (w.getSelection().toString()) return w;
		for (var i = 0, f, r; f = w.frames[i]; i++) {
			try { if (r = getSelWin(f)) return r;} catch(e) {}
		}
	},
	encodeImg = function (src, obj) {
		var canvas, img, ret = src;
		if (/^https?:\/\//.test(src)) {
			canvas = doc.createElement('canvas');
			if (!obj || obj.nodeName.toLowerCase() != 'img') {
				img = doc.createElement('img');
				img.src = src;
			} else
				img = obj;
			if (img.complete) try{
				canvas.width = img.width;
				canvas.height = img.height;
				canvas.getContext('2d').drawImage(img, 0, 0);
				ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png'));
			} catch (e) {};
			if (img != obj) img.src = 'about:blank';
		};
		return ret;
	},
	toSrc = function (obj) {
		var strToSrc = function (str) {
			var chr, ret = '', i = 0, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\x22' : '\\\x22', '\\': '\\\\'};
			while (chr = str.charAt(i++)) {
				ret += meta[chr] || chr;
			};
			return '\x22' + ret + '\x22';
		},
		arrToSrc = function (arr) {
			var ret = [];
			for (var i = 0; i < arr.length; i++) {
				ret[i] = toSrc(arr[i]) || 'null';
			};
			return '[' + ret.join(',') + ']';
		},
		objToSrc = function (obj) {
			var val, ret = [];
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val);
			};
			return '{' + ret.join(',') + '}';
		};
		switch (Object.prototype.toString.call(obj).slice(8, -1)) {
			case 'Array': return arrToSrc(obj);
			case 'Boolean':
			case 'Function':
			case 'RegExp': return obj.toString();
			case 'Date': return 'new Date(' + obj.getTime() + ')';
			case 'Math': return 'Math';
			case 'Number': return isFinite(obj) ? String(obj) : 'null';
			case 'Object': return objToSrc(obj);
			case 'String': return strToSrc(obj);
			default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null';
		}
	},
	mainWin = {};
	focus.getFocusedElementForWindow(content, true, mainWin);
	mainWin = mainWin.value;

	var selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location;
	var ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g;

	if (selWin) {
		var rng = win.getSelection().getRangeAt(0);
		pEle = rng.commonAncestorContainer;
		ele = rng.cloneContents();
	} else {
		pEle = doc.documentElement;
		ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true);
	};
	while (pEle) {
		if (pEle.nodeType == 1) {
			clone = pEle.cloneNode(false);
			clone.appendChild(ele);
			ele = clone;
		};
		pEle = pEle.parentNode
	};
	var sel = doc.createElement('div');
	sel.appendChild(ele);

	for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) {
		el = all[i];
		if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) {
			if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href);
			return prev + encodeImg(url) + next;
		});
		switch (el.nodeName.toLowerCase()) {
			case 'link':
			case 'style':
			case 'script': el.parentNode.removeChild(el); break;
			case 'a':
			case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break;
			case 'img':
			case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break;
			case 'audio':
			case 'video':
			case 'embed':
			case 'frame':
			case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break;
			case 'object': if (el.hasAttribute('data')) el.data = el.data; break;
			case 'form': if (el.hasAttribute('action')) el.action = el.action; break;
		}
	};
	var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets, title = doc.getElementsByTagName('title')[0];
	meta.httpEquiv = 'content-type';
	meta.content = 'text/html; charset=utf-8';
	head.appendChild(meta);
	if (title) head.appendChild(title.cloneNode(true));

	head.copyScript = function (unsafeWin) {
		if ('$' in unsafeWin) return;
		var f = doc.createElement('iframe');
		f.src = 'about:blank';
		f.setAttribute('style', 'position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');
		doc.documentElement.appendChild(f);
		var str, script = doc.createElement('script');
		script.type = 'text/javascript';
		for (var name in unsafeWin) {
			if (name in f.contentWindow || !/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) continue;
			try {
				str = toSrc(unsafeWin[name]);
				if (!/\{\s*\[native code\]\s*\}/.test(str)) {
					script.appendChild(doc.createTextNode('var ' + name + ' = ' + str.replace(/<\/(script>)/ig, '<\\/$1') + ';\n'));
				}
			} catch (e) {};
		};
		f.parentNode.removeChild(f);
		if (script.childNodes.length) this.nextSibling.appendChild(script);
	};
	head.copyScript(win.wrappedJSObject || win);

	head.copyStyle = function (s) {
		if (!s) return;
		var style = doc.createElement('style');
		style.type = 'text/css';
		if (s.media && s.media.mediaText) style.media = s.media.mediaText;
		try {
			for (var i = 0, rule; rule = s.cssRules[i]; i++) {
				if (rule.type != 3) {
					if((!rule.selectorText || rule.selectorText.indexOf(':') != -1) || (!sel.querySelector || sel.querySelector(rule.selectorText))) {
						var css = !rule.cssText ? '' : rule.cssText.replace(reUrl, function (a, prev, url, next) {
							if (!/^[a-z]+:/.test(url)) url = resolveURL(url, s.href || loc.href);
							if(rule.type == 1 && rule.style && rule.style.backgroundImage) url = encodeImg(url);
							return prev + url + next;
						});
						style.appendChild(doc.createTextNode(css + '\n'));
					}
				} else {
					this.copyStyle(rule.styleSheet);
				}
			}
		} catch(e) {
			if (s.ownerNode) style = s.ownerNode.cloneNode(false);
		};
		this.appendChild(style);
	};
	for (var j = 0; j < sheets.length; j++) head.copyStyle(sheets[j]);
	head.appendChild(doc.createTextNode('\n'));
	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};
	var selText = selWin ? win.getSelection().toString().slice(0, 200) : undefined;
	sendAsyncMessage("%MSG_NAME%", [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', selText]); // выделенный текст

}); // END hookClicks

ucf_BookmarkDir.js - только как пример: яркость прокруткой над ★

Выделить код

Код:

(async (id, sel) => { // Клики на Звёздочке, ToolTip: расположение закладки в Избранном, Недавняя папка
	var g = Cu.getGlobalForObject(Cu), stt = g[id]; // https://forum.mozilla-russia.org/viewtopic.php?pid=790890#p790890
	if (!stt) { var {obs, prefs} = Services, {bookmarks: bm, observers: pobs} = PlacesUtils;
		stt = g[id] = { bm, help_star: `

Правый клик:	⤾ Вернуть вкладку
…+ Alt 	Перевод выдел.текст | Сайт 
…+ Shift	Гугл Перевод или поиск\n
Колесико ±	Яркость страниц
…+ клик 	Полная яркость`,

			pref: `ucf.${id}Guid`,
			events: ["bookmark-added"],
			async init() {
				this.handleEvent = e => this[e.type](e);

				if ((this.pbm = typeof PlacesBookmarkMoved == "function"))
					this.events.push("bookmark-moved");
				else
					this.QueryInterface = g.ChromeUtils.generateQI([Ci.nsINavBookmarkObserver]),
					bm.addObserver(this);
				pobs.addListener(this.events, this.added = events => {
					for(var e of events) e.isTagging || this[e.constructor.name](e);
				});
				obs.addObserver(this, "quit-application-granted");
				this.args = [b => this.bguids.add(b.parentGuid), {concurrent: true}];
				var guid = prefs.getStringPref(this.pref, "");
				if (!guid) try {var [guid] = await PlacesUtils.metadata.get(
					PlacesUIUtils.LAST_USED_FOLDERS_META_KEY, []
				)} catch {}
				this.guids.push(guid || await PlacesUIUtils.defaultParentGuid || bm.unfiledGuid);

				var pref = "ucf.tabbrowser-tabpanels.opacity"; // яркость страницы
				var getPref = () => Services.prefs.getIntPref(pref, 100);
				var css = `@-moz-document url(chrome://browser/content/browser.xhtml) {
					:is(${sel})[rst] {filter: grayscale(1%) !important;}
					:root:not([chromehidden*=toolbar]) #tabbrowser-tabbox {background-color: black !important;}
					:root:not([chromehidden*=toolbar]) #tabbrowser-tabpanels {opacity:${getPref()/100} !important;}}`;
				var subst = "ucf-tabbrowser-tabpanels-opacity-style", url = `resource://${subst}/`;
				Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler)
					.setSubstitution(subst, Services.io.newURI("data:text/css," + encodeURIComponent(css)));
				var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
				sss.loadAndRegisterSheet(Services.io.newURI(url), sss.USER_SHEET);

				var st = InspectorUtils.getAllStyleSheets(document).find(s => s.href == url).cssRules[0].cssRules[2].style;

				this.setPref = (e, val = 100) => {
					Services.prefs.setIntPref(pref, val);
					e.target.toggleAttribute("rst");
				}
				this.wheel = e => {
					var val = getPref() + (e.deltaY < 0 ? 5 : -5); // шаг
					val < 25 || val > 100 || this.setPref(e, val);
				}
				var observer = () => st.setProperty("opacity", getPref() / 100, "important");
				Services.prefs.addObserver(pref, observer);
				this.removePrefObs = () => Services.prefs.removeObserver(pref, observer);
			},
			observe() {
				this.pbm || bm.removeObserver(this);
				pobs.removeListener(this.events, this.added);
				obs.removeObserver(this, "quit-application-granted");
				prefs.setStringPref(this.pref, this.guids[0]);
				this.removePrefObs();
			},
			bguids: new g.Set(), guids: new g.Array(),
			skipTags: true,
			tt(win) {
				var list = win.InspectorUtils
					.getChildrenForNode(win.document.documentElement, true);
				return list.item(list.length - 1);
			},
			PlacesBookmarkAddition(e) {
				if (e.itemType == bm.TYPE_BOOKMARK && e.source == bm.SOURCES.DEFAULT)
					this.guids[0] = e.parentGuid;
			},
			PlacesBookmarkMoved(e) {
				e.parentGuid != e.oldParentGuid && this.PlacesBookmarkAddition(e);
			},
			onItemMoved(a, b, c, d, e, itemType, f, oldParentGuid, parentGuid, source) {
				this.PlacesBookmarkMoved({itemType, source, oldParentGuid, parentGuid});
			},
			fetch(win) {
				this.bguids.clear();
				return bm.fetch({url: win.gBrowser.currentURI.spec}, ...this.args);
			},
			addTab: function(win, url, add, params = {relatedToCurrent: true}) { // открыть адрес [add: в новой вкладке]
				params.triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
				return (add) ? win.gBrowser.addTab(url, params) : win.gBrowser.loadURI(url, params);
			},
			translate(browserMM, win, e, go) { // Google-перевод сайта | выделенного текста (go) поиск выдел. текста в Яндекс
				browserMM.addMessageListener('getSelect', function listener(msg) {
					var url = (msg.data) ? (go)
						? "https://yandex.ru/search/?text="+ msg.data +"&src=suggest_Pers&lang=ru" // поиск текста в Яндекс
						: "https://translate.google.com/#view=home&op=translate&sl=auto&tl=ru&text="+ msg.data // Гугл перевод
						: "http://translate.google.com/translate?u="+ gURLBar.value +"&hl=ru&ie=UTF-8&sl=auto&tl=ru"; // Перевод сайта
					if (go && !msg.data) // Перевод сайти в Яндекс. ничего не выделено + go не пуст
						gBrowser.selectedTab = e.addTab(win, "https://translate.yandex.com/translate?url=" + gURLBar.value + "&dir=&ui=ru&lang=auto-ru", 1)
					else
						gBrowser.selectedTab = e.addTab(win, url, 1);
				browserMM.removeMessageListener('getSelect', listener, true);
				});
				browserMM.loadFrameScript('data:,sendAsyncMessage("getSelect", content.document.getSelection().toString())', false);
			},
			auxclick(e) {
				if (e.button == 2) {
					var win = e.view;
					if (e.altKey)
						this.translate(gBrowser.selectedBrowser.messageManager, win, this, 1);
					else if (e.shiftKey)
						this.translate(gBrowser.selectedBrowser.messageManager, win, this);
					else
						win.undoCloseTab();
				} else
					this.setPref(e);
			},
			find: obj => obj.name == "tooltiptext"
		};
		var ps = ["onBeginUpdateBatch", "onEndUpdateBatch", "onItemChanged", "onItemVisited"];
		var noop = () => {}; for(var p of ps) stt[p] = noop; stt.init();

		var func = id => this[id].mouseenter = async function(e) {
			var win = e.view, star = e.target, result = [], starred = star.hasAttribute("starred");
			starred && await this.fetch(win);
			this.help_star = this.help_star.replace(/Яркость страниц.*/, `Яркость страниц ${win.Services.prefs.getIntPref("ucf.tabbrowser-tabpanels.opacity", 100)}%`);

			for(var guid of (starred ? this.bguids : this.guids)) {
				var arr = [], num = 50;
				while(--num) {
					if (!star.matches(":hover")) return;
					var res = await this.bm.fetch(guid);
					if (!res) break;
					if ((guid = res.parentGuid) == this.bm.rootGuid) {
						arr.unshift(this.bm.getLocalizedTitle(res));
						break;
					}
					arr.unshift(res.title || "[Безымянная папка]");
				}
				arr.length && result.push(arr.join("\\"));
			}
			if (!star.matches(":hover")) return;

			var text = (await win.document.l10n.formatMessages([{ // стандартная подсказка
				id: star.getAttribute("data-l10n-id"),
				args: JSON.parse(star.getAttribute("data-l10n-args"))
			}]))[0].attributes.find(this.find).value, txt;

			if (result.length) {
				txt = result.join("\n");
				txt = starred ? `\n\n★ ${result.length > 1 ? "Данные закладки добавлены" : "Данная закладка добавлена"} в:\n${txt}` : "\n\n★ Недавно добавленная папка:\n" + txt;
			}
			win.document.tooltipNode == star ? this.tt(win).label = text + this.help_star + txt : star.tooltipText = text + this.help_star + txt;
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})("${id}")`);
		g.ChromeUtils.compileScript(url).then(ps => ps.executeInGlobal(g));
	}
	await delayedStartupPromise;

	var types = ["auxclick", "mouseenter", "wheel"];
	var stars = Array.from(document.querySelectorAll(sel));

	for(var star of stars) for(var type of types) star.addEventListener(type, stt);
	star.setAttribute("context", "event.stopPropagation()");

	var destructor = () => {
		for(var star of stars) for(var type of types) star.removeEventListener(type, stt);
	}
	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	if (ucf)
		ucf[id] = {destructor}, ucf.unloadlisteners.push(id);
	else
		window.addEventListener("unload", destructor, {once: true});
})("ucfBookmarksStarFTooltipHelper", "#star-button, #context-bookmarkpage");

ucf_QuickToggle.js - здесь код LongPress

Отредактировано Dobrov (20-11-2021 01:29:28)

Отсутствует

 

№14122-11-2021 21:25:38

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

Re: UCF-скрипты на этом форуме

Dobrov пишет

доработать код, чтобы сразу перехватывать клики кнопок "nav-bar-customization-target" основной панели и "page-action-buttons"

Так PanelUI-menu-button же торчит в коде,
nav-bar-customization-target ей не родитель. Общим будет nav-bar.

identity-box

Раз предполагаются элементы с видимыми для мыши
дочерними элементами, придётся использовать перебор и closest().
Кстати, с FF90+ таковы pageAction's (в hbox иконки завернули).

добавить действие на долгое нажатие кнопки
добавить перехват "wheel"

И ты думаешь я смогу это всё нормально записать?
Весьма сомнительно. Попробую, так, отдельно, в консоль.

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

Выделить код

Код:

(() => {
	var c = msg => Services.console.logStringMessage("[HC] " + msg);
	var data = {
		"#downloads-button": {
			mousedownTarget: true,

			128() { // СКМ Click 
				c("Downloads Button Middle Click");
			},
			4() { // Double Left Click
				c("Downloads Button Double Left Click");
			},
			256() { // ПКМ Click
				c("Downloads Button Right Click");
			},
			260(btn) { // Double Right Click
				c("Downloads Button Double Right Click");
			},

			1() { // Left Long Press
				c("Downloads Button Left Long Press");
			},
		},
		"#PanelUI-menu-button": {
			mousedownTarget: true,

			8() { // ЛКМ + Alt
				c("PUI Button Alt + Left Click");
			},
			16(btn) { // Shift + ЛКМ
				c("PUI Button Shift + Left Click");
			},
			136(btn) { // Alt + СКМ
				c("PUI Button Alt + Middle Click");
			},
			128() { // СКМ
				c("PUI Button Middle Click");
			},
			132() { // СКМ Double
				c("PUI Button Double Middle Click");
			},
			256() { // ПКМ
				c("PUI Button Right Click");
			},
			264(btn) { // Alt + ПКМ
				c("PUI Button Alt + Right Click");
			},
			272() { // Shift + ПКМ
				c("PUI Button Shift + Right Click");
			},
			280(btn) { // Shift + Alt + ПКМ
				c("PUI Button Shift + Alt + Right Click");
			},
			4() { // Double Left Click
				c("PUI Button Double Left Click");
			},
			260() { // ПКМ Double Right Click
				c("PUI Button Double Right Click");
			},

			145() { // Shift + Middle Long Press
				c("PUI Button Shift + Middle Long Press");
			},
		},
		"#pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_": { // Reader View Button
			128() { // Middle Click
				c("Reader View Middle Click");
			},

			289() { // Ctrl + Right Long Press
				c("Reader View Ctrl + Right Long Press");
			},
			2(trg, forward) { // wheel
				c("Reader View Wheel " + (forward ? "forward" : "backward"));
			},
		},
		"#star-button-box": {
			1() { // Left Long Press
				c("Star Left Long Press");
			},
			4() { // Double Left Click
				c("Star Double Left Click");
			},
			129() { // Middle Long Press
				c("Star Middle Long Press");
			},
		},

		"#identity-permission-box": {
			2(trg, forward) { // wheel
				c("Identity Permisson Box Wheel " + (forward ? "forward" : "backward"));
			}
		},
		"#identity-box": {
			2(trg, forward) { // wheel
				c("Identity Box Wheel " + (forward ? "forward" : "backward"));
			},
			34(trg, forward) { // Ctrl + wheel
				c("Identity Box Ctrl + Wheel " + (forward ? "forward" : "backward"));
			},
		},
		"#identity-icon-box": {
			16() { // Shift + Left Click
				c("Identity Icon Box Shift + Left Click");
			},
		},
	};

	var listener = {
		filter(sel) {
			return this.closest(sel);
		},
		find(sel) {
			return data[sel][this] || data[sel][this + 1];
		},
		handleEvent(e) {
			if (this.skip || e.detail > 2) return;

			var trg = e.target;
			var sels = this.selectors.filter(this.filter, trg);
			var {length} = sels;
			if (!length) return;

			var dbl = e.detail == 2;
			var wh = e.type.startsWith("w");

			var num = e.metaKey *64 + e.ctrlKey *32 + e.shiftKey *16 + e.altKey *8
				+ (wh ? 2 : e.button *128 + dbl *4);

			var obj = data[
				length > 1 && sels.find(this.find, num) || sels[0]
			];

			// wheel
			if (wh) return obj[num]?.(trg, e.deltaY < 0);

			// mousedown
			if (e.type.startsWith("m")) {
				obj.mousedownTarget && this.stop(e);
				if (dbl) return;

				this.longPress = false;
				if (++num in obj)
					this.mousedownTID = setTimeout(this.onLongPress, 640, trg, obj, num);
				if (e.button == 2)
					this.ctx = trg.getAttribute("context"),
					trg.setAttribute("context", "");
				return;
			}

			// click
			obj.mousedownTarget || this.stop(e);
			if (this.longPress) return this.longPress = false;
			dbl
				? this.clickTID &&= clearTimeout(this.clickTID)
				: this.mousedownTID &&= clearTimeout(this.mousedownTID);

			if (!obj[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "dispatch",
					this.mdt = obj.mousedownTarget;
				obj = this;
			}
			dbl
				? obj[num](trg)
				: this.clickTID = setTimeout(this.exec, 300, trg, obj, num);
		},
		get selectors() {
			this.exec = (trg, obj, num) => {
				this.clickTID = null;
				obj[num](trg);
			}
			this.onLongPress = (trg, obj, num) => {
				this.mousedownTID = null;
				this.longPress = true;
				obj[num](trg);
			}
			delete this.selectors;
			return this.selectors = Object.keys(data);
		},
		get mdEvent() {
			delete this.mdEvent;
			return this.mdEvent = new MouseEvent("mousedown", {bubbles: true});
		},
		context(trg) {
			this.ctx
				? trg.setAttribute("context", this.ctx)
				: trg.removeAttribute("context");
			trg.dispatchEvent(new MouseEvent("contextmenu", this.a));
		},
		dispatch(trg) {
			this.skip = true;
			this.mdt ? trg.dispatchEvent(this.mdEvent) : trg.click();
			this.skip = false;
		},
		stop: e => {
			e.preventDefault();
			e.stopImmediatePropagation();
		},
		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0}
	};

	var root = document.getElementById("nav-bar");
	var events = ["click", "mousedown", "wheel"];
	for(var type of events) root.addEventListener(type, listener, true);
	var id = "test-hookClicks";
	ucf_custom_script_win.unloadlisteners.push(id);
	ucf_custom_script_win[id] = {destructor() {
		for(var type of events) root.removeEventListener(type, listener, true);
	}};
})();

Отсутствует

 

№14223-11-2021 02:02:40

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

Re: UCF-скрипты на этом форуме

Dumby спасибо за отличный код дополнительных кликов. :)
Подключил оба кода в custom_script_win.js, попробую вернуть функции Save HTML и прочие…


Только не понял, как в код обновления tooltips добавить removeEventListener и нужен ли он.

Выделить код

Код:

(() => { // update Tooltips
	………
	addDestructor(() => {
		………
	});
})();

Ещё не обновляются подсказки для 1) tracking-protection-icon-container "На этой странице не обнаружено ни одного известного Firefox трекера" и 2) identity-icon-box "Подтверждено: Let's Encrypt".
Я подключил на них яркость страниц, код работает. А как к этим кнопкам с динамической подсказкой добавить свой текст?
`Колесико ±    Яркость страниц\n…+ клик     Полная яркость
Яркость страниц ${win.Services.prefs.getIntPref("ucf.tabbrowser-tabpanels.opacity", 100)}%`
?

Отредактировано Dobrov (23-11-2021 10:45:15)

Отсутствует

 

№14323-11-2021 22:34:40

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

Re: UCF-скрипты на этом форуме

Dobrov пишет

как в код обновления tooltips добавить removeEventListener и нужен ли он

Да так же как и в коде для кликов.
А вот нужен ли он — это я и сам хотел бы знать.
Необходимость вызывает сомнение, но так принято (было),
поэтому, по возможности, оно так и продолжается.


Допустим, тултипский код рядом, сразу после var listener = {.....};
и добавление обработчиков и деструктора в самом конце.

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

Выделить код

Код:

.......
	var str_cut = s => s;

	var dsym = Symbol();
	var j = (...args) => args.join("\n");
	var tooltips = {
		get "PanelUI-menu-button"() {
			delete this["PanelUI-menu-button"];
			return j(
				`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}`,
				"…+ Shift		⚑ Краткая справка",
				"…+ Alt		Персонализация",
				"Клик дважды	⊠ закрыть браузер\n",

				"Правый клик	⇲ Свернуть",
				"…+ дважды	⤾ Вернуть вкладку",
				"…+ Alt		Диспетчер задач",
				"…+ Shift		Адаптивный дизайн\n",

				"Колёсико:	Развернуть | окно",
				"…+ Alt		Полный экран",
				"…+ дважды	Обновить без кэша"
			);
		},

		"pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_": j(
			`Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n`,

			"Клик мыши	Режим для чтения",
			"Колёсико	Адаптивный дизайн"
		),

		"_b9db16a4-6edc-47ec-a1f4-b86292ed211d_-browser-action": j(
			"Video DownloadHelper",
			"Скачивание проигрываемого видео"
		),

		[dsym]: j(
			GetDynamicShortcutTooltipText("downloads-button"),

			"\nДвойной клик: ⬇︎ открыть [Загрузки]",
			"…на картинке: ⧉ найти Похожие\n",

			"Правый клик (Alt+S):  Сохранить",
			"   в единый html всё / выделенное",
			"…дважды  Картинки вкл/выкл\n",

			"Ролик:	 Сохранить как файл .txt",
			"Колёсико на рисунке: ➜ Сохранить"
		),
		get "downloads-button"() {
			var hint = this[dsym];
			if (document.getElementById("_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action"))
				hint += "\nAlt⇧S	 ⌨ нажатие SingleSave";
			try {var dw = Services.prefs.getComplexValue("browser.download.dir", Ci.nsIFile);}
			catch {dw = Services.dirsvc.get("DfltDwnld", Ci.nsIFile);}
			if (dw) hint += "\n\n[Загрузки] — выбранная папка:\n" + str_cut(dw.path, 33);
			return hint;
		},

		get "identity-icon-box"() {
			var ttt = "";
			var trg = window.event.target;
			if (!trg.id.endsWith("x")) {
				if (trg.hasAttribute("tooltiptext"))
					ttt = trg.ttt = trg.tooltipText;
				else
					ttt = trg.ttt;
				if (ttt) ttt += "\n\n";
				trg.removeAttribute("tooltiptext");
			}
			return ttt + "Свой текст";
		},

		get "tracking-protection-icon-container"() {
			var trg = window.event?.target;
			return trg.id.endsWith("r") &&
				trg.textContent + "\n\nСвой текст";
		}
	};

	document.getElementById("tracking-protection-icon-container")
		.removeAttribute("tooltip");

	var onMouseenter = e => {
		var trg = e.target;
		var hint = tooltips[trg.id] || tooltips[(trg = trg.parentNode).id];
		if (hint) trg.tooltipText = hint;
	}
	var root = document.getElementById("nav-bar");
	var events = ["click", "mousedown", "wheel"];

	root.addEventListener("mouseenter", onMouseenter, true);
	for(var type of events) root.addEventListener(type, listener, true);

	var id = "hookClicks-and-tooltips";
	ucf_custom_script_win.unloadlisteners.push(id);
	ucf_custom_script_win[id] = {destructor() {
		root.removeEventListener("mouseenter", onMouseenter, true);
		for(var type of events) root.removeEventListener(type, listener, true);
	}};
})();

Отсутствует

 

№14430-11-2021 07:08:17

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

Re: UCF-скрипты на этом форуме

Dumby - спасибо! Скрипт hookClicks пригодится многим, он позволит прописывать обработку кликов в одном скрипте и позволит «разгрузить» другие кнопки, не добавлять в них код обработки кликов.
Сделал финальный демо-скрипт, расширяющий возможности нескольких кнопок. Dumby, проверь, может я где-то накосячил или что-то можно сделать проще! :)

hookClicks дополнительные клики и подсказки кнопок

Выделить код

Код:

(async (id, func) => { // для custom_script_win.js: дополнительные клики и подсказки кнопок © Dumby, mod Dobrov
	var dsym = Symbol(), j = (...args) => args.join("\n"),
	br_val = () => { return ` ${Services.prefs.getIntPref("ucf.tabbrowser-tabpanels.opacity",100)}%`;}, br_txt =

		`Клик ролика	сброс яркости\nКрутить ±	Яркость страниц`, tooltips = {
	get "PanelUI-menu-button"() {	/* delete this["PanelUI-menu-button"]; */ return j(
		`Клик мыши:	меню Firefox ${Services.appinfo.platformVersion}`,
		`… держать	⚑ Краткая справка`,
		`…+ Alt		Персонализация`,
		`Клик дважды	⊠ закрыть браузер\n`,
		`Правый клик	⇲ Свернуть`,
		`…+ дважды	⤾ Вернуть вкладку`,
		`…+ Alt		Диспетчер задач\n`,
		`Колёсико:	Развернуть | окно`,
		`…+ Alt		Полный экран`,
		`…+ дважды	Обновить без кэша`
	);},
	[dsym]: j(GetDynamicShortcutTooltipText("downloads-button"),
		`\nДвойной клик: ⬇︎ открыть [Загрузки]`,
		`…на картинке: ⧉ найти Похожие\n`,
		`Правый клик (Alt+S):  Сохранить`,
		`   в единый html всё / выделенное`,
		`…дважды  Картинки вкл/выкл\n`,
		`Ролик:	 Сохранить как файл .txt`,
		`Колёсико на рисунке: ➜ Сохранить`
	),
	get "pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_"() { return j(
		`Reader View	${Services.appinfo.OS == "Darwin" ? "⌥⌘M" : "Ctrl+⇧+M"}\n`,
		`Клик мыши	Режим для чтения`, `Колёсико	Адаптивный дизайн\nКолесико ±	Яркость сайта` + br_val());
	},
	"_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action":
		`Сохранить страницу с помощью SingleFile (Alt+S)`
	,
	"_b9db16a4-6edc-47ec-a1f4-b86292ed211d_-browser-action":
		`Video DownloadHelper\nСкачивание проигрываемого видео`
	,
	get "downloads-button"() {
		var hint = this[dsym];
		if (document.getElementById("_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action"))
			hint += "\nAlt⇧S	 ⌨ нажатие SingleSave";  //убрать/добавить
		try {var dw = Services.prefs.getComplexValue("browser.download.dir", Ci.nsIFile);}
			catch {dw = Services.dirsvc.get("DfltDwnld", Ci.nsIFile);} //отличается от ⇧
		if (dw) hint += "\n\n[Загрузки] — выбранная папка:\n" + str_cut(dw.path, 33);
		return hint;
	},
	get "identity-icon-box"() {
		var trg = window.event.target, ttt = "";
		if (!trg.id.endsWith("x")) {
			if (trg.hasAttribute("tooltiptext"))
				ttt = trg.ttt = trg.tooltipText;
			else
				ttt = trg.ttt;
			if (ttt) ttt += "\n\n";
			trg.removeAttribute("tooltiptext");
		}
		return ttt +`Правый клик	Копировать адрес в буфер\n`+ br_txt + br_val();
	},
	get "tracking-protection-icon-container"() {
		var trg = window.event?.target;
		return trg.id.endsWith("r") && trg.textContent + "\n\n" + br_txt + br_val();
	}
	}; /* end tooltips */ document.getElementById("tracking-protection-icon-container").removeAttribute("tooltip");

	var listener = { // дополнительные клики кнопок и перехват существующих
		filter(sel) {
			return this.closest(sel);
		},
		find(sel) {
			return data[sel][this] || data[sel][this + 1];
		},
		handleEvent(e) {
			if (this.skip || e.detail > 2) return;

			var trg = e.target;
			var sels = this.selectors.filter(this.filter, trg);
			var {length} = sels;
			if (!length) return;

			var dbl = e.detail == 2;
			var wh = e.type.startsWith("w");

			var num = e.metaKey *64 + e.ctrlKey *32 + e.shiftKey *16 + e.altKey *8 + (wh ? 2 : e.button *128 + dbl *4);

			var obj = data[
				length > 1 && sels.find(this.find, num) || sels[0]
			];
// wheel
			if (wh) return obj[num]?.(trg, e.deltaY < 0);
// mousedown
			if (e.type.startsWith("m")) {
				obj.mousedownTarget && this.stop(e);
				if (dbl) return;

				this.longPress = false;
				if (++num in obj)
					this.mousedownTID = setTimeout(this.onLongPress, 640, trg, obj, num);
				if (e.button == 2)
					this.ctx = trg.getAttribute("context"),
					trg.setAttribute("context", "");
				return;
			}
// click
			obj.mousedownTarget || this.stop(e);
			if (this.longPress) return this.longPress = false;
			dbl
				? this.clickTID &&= clearTimeout(this.clickTID)
				: this.mousedownTID &&= clearTimeout(this.mousedownTID);

			if (!obj[num]) {
				if (e.button == 1) return;
				if (e.button) {
					num = "context";
					for(var p in this.a) this.a[p] = e[p];
				} else
					num = "dispatch",
					this.mdt = obj.mousedownTarget;
				obj = this;
			}
			dbl
				? obj[num](trg)
				: this.clickTID = setTimeout(this.exec, 300, trg, obj, num);
		},
		get selectors() {
			this.exec = (trg, obj, num) => {
				this.clickTID = null;
				obj[num](trg);
			}
			this.onLongPress = (trg, obj, num) => {
				this.mousedownTID = null;
				this.longPress = true;
				obj[num](trg);
			}
			delete this.selectors;
			return this.selectors = Object.keys(data);
		},
		get mdEvent() {
			delete this.mdEvent;
			return this.mdEvent = new MouseEvent("mousedown", {bubbles: true});
		},
		context(trg) {
			this.ctx
				? trg.setAttribute("context", this.ctx)
				: trg.removeAttribute("context");
			trg.dispatchEvent(new MouseEvent("contextmenu", this.a));
		},
		dispatch(trg) {
			this.skip = true;
			this.mdt ? trg.dispatchEvent(this.mdEvent) : trg.click();
			this.skip = false;
		},
		stop: e => {
			e.preventDefault();
			e.stopImmediatePropagation();
		},
		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0}
	};
	var onMouseenter = e => {
		var trg = e.target;
		var hint = tooltips[trg.id] || tooltips[(trg = trg.parentNode).id];
		if (hint) trg.tooltipText = hint;
	}
	var keydown_win = e => { // нажатие клавиш
		if (e.keyCode == 83 && e.altKey) { // Alt+S [+Shift]
			var singlesave = document.getElementById('_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action');
			e.shiftKey ? singlesave ? singlesave.click() : save() : save(); // имитировать клик по кнопке, используя её ID
		}
		if (e.keyCode == 88 && e.altKey){ // Alt+X отладка внешнего JS-кода
			// e.target.ownerDocument.getElementById("key_browserConsole").doCommand();
			eval(Cu.readUTF8URI(Services.io.newURI("chrome://user_chrome_files/content/custom_scripts/User.js")));
			console.log("[END] User.js " + Math.random());
		}
	}
	var root = document.getElementById("nav-bar");
	var events = ["click", "mousedown", "wheel"];
	root.addEventListener("mouseenter", onMouseenter, true);
	for(var type of events) root.addEventListener(type, listener, true);
	window.addEventListener("keydown", keydown_win);
	ucf_custom_script_win.unloadlisteners.push(id);

	ucf_custom_script_win[id] = {destructor() {
		root.removeEventListener("mouseenter", onMouseenter, true);
		for(var type of events) root.removeEventListener(type, listener, true);
		window.removeEventListener("keydown", keydown_win);
	}};
	addDestructor = nextDestructor => {
		var {destructor} = ucf_custom_script_win[id];
		ucf_custom_script_win[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	}; // end Hooks

	var {prefs, dirsvc} = Services, getIntPref = (p) => prefs.getIntPref(p, 100),
	c = msg => Services.console.logStringMessage("[HC] "+ msg), // отладка
	sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService),
	my_br = "ucf.tabbrowser-tabpanels.opacity", // яркость страниц
	css = `@-moz-document url(chrome://browser/content/browser.xhtml) {
		:is(${id})[rst] {filter: grayscale(1%) !important;}
		:root:not([chromehidden*=toolbar]) #tabbrowser-tabbox {background-color: black !important;}
		:root:not([chromehidden*=toolbar]) #tabbrowser-tabpanels {opacity:${getIntPref(my_br)/100} !important;}}`,
	subst = "ucf-tabbrowser-tabpanels-opacity-style", url = `resource://${subst}/`;
	Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler)
		.setSubstitution(subst, Services.io.newURI("data:text/css," + encodeURIComponent(css)));
	sss.loadAndRegisterSheet(Services.io.newURI(url), sss.USER_SHEET);
	var st = InspectorUtils.getAllStyleSheets(document).find(s => s.href == url).cssRules[0].cssRules[2].style;
	var observer = () => st.setProperty("opacity", getIntPref(my_br)/100, "important");
	prefs.addObserver(my_br, observer);
	this.removePrefObs = () => prefs.removeObserver(my_br, observer); // end яркость

	if (typeof IOUtils != "object") { // Firefox 78 ESR
		var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
		var PathUtils = {join: (...args) => OS.Path.join(...args)};
		var IOUtils = {writeUTF8: (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt))};
	};
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	str_cut = (s, cut = 33) => { // сократить/разбить строку
		return s.substring(0,cut) + `${s.length > cut - 1 ? `…\n…${s.substring(s.length -cut + 2, s.length)}`: ''}`;
	},
	url_color = (color = "rgba(240,176,0,0.5)", ms = 300) => { // строка адреса мигает
		var u_alert = document.getElementById("urlbar-input-container");
		u_alert.style.background = color; setTimeout(() => u_alert.style.background = "", ms);
	},
	gClipboard = {
		get ch() { delete this.ch;
			return this.ch = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
		},
		write(str) { this.ch.copyStringToClipboard(str, Services.clipboard.kGlobalClipboard);}
	},
	switchToTab = (url, but = window) => { // открыть вкладку | закрыть, если открыта
		for(var tab of but.ownerGlobal.gBrowser.tabs)
			if (tab.linkedBrowser.currentURI.spec == url) {but.ownerGlobal.gBrowser.removeTab(tab); return;}; // закрыть
		but.ownerGlobal.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
	},
	showInStatusPanel = (info, time = 5000) => {
		StatusPanel = window.StatusPanel;
		if (StatusPanel.update.tid)
			clearTimeout(StatusPanel.update.tid)
		else {
			var {update} = StatusPanel;
			StatusPanel.update = () => {};
			StatusPanel.update.ret = () => {
				StatusPanel.update = update;
				StatusPanel.update();
			}
		}
		StatusPanel.update.tid = setTimeout(StatusPanel.update.ret, time);
		StatusPanel._label = info;
	},
	Title = (max, title) => { // получить заголовок. без обрезки: max не указан, домен: max <0, + дата: max=0
		if (!title) var title = document.title || gBrowser.selectedTab.label;
		if (max == undefined) return title; // заголовок как есть или ограничить длину, убрать служебные символы
		title = title.replace(/[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim();
		if ( max > 0 ) return title.slice(0, max);
		if ( max == 0) return title.slice(0, 100) +"_"+ new Date().toLocaleDateString('ru', {day: 'numeric', month: 'numeric', year: '2-digit'}) +'-'+ new Date().toLocaleTimeString().replace(/:/g, "։");
		var host = decodeURIComponent(gURLBar.value); // max < 0
		if (!/^file:\/\//.test(host)) host = host.replace(/^.*url=|https?:\/\/|www\.|\/.*/g,'');
		return host.replace(/^ru\.|^m\.|forum\./,'').replace(/^club\.dns/,'dns');
	},
	saveSelectionToTxt = async () => { // сохранить выделенный/весь текст страницы как .txt
		var msgName = id + ":Save:GetSelection", splice = saveURL.length == 10;
		var receiver = msg => {
			var args = ["data:text/plain," + encodeURIComponent(gBrowser.currentURI.spec + "\n\n" + msg.data),
				Title(0) + '.txt', null, false, true, null, window.document];
			splice && args.splice(5, 0, null);
			saveURL(...args); showInStatusPanel("√ текст сохранён: "+ Title(0).slice(0, 60));
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));
		var func = fm => {
			var res, fed, win = {}, fe = fm.getFocusedElementForWindow(content, true, win);
			var sel = (win = win.value).getSelection();
			if (sel.isCollapsed) {
				var ed = fe && fe.editor;
				if (ed && ed instanceof Ci.nsIEditor)
					sel = ed.selection, fed = fe;
			}
			if (sel.isCollapsed)
				fed && fed.blur(), docShell.doCommand("cmd_selectAll"),
				res = win.getSelection().toString(), docShell.doCommand("cmd_selectNone"),
				fed && fed.focus();
			res = res || sel.toString();
			/\S/.test(res) && sendAsyncMessage("saveSelectionToTxt", res);
		}
		var url = "data:;charset=utf-8," + encodeURIComponent(`(${func})`.replace("saveSelectionToTxt", msgName)) + '(Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager));';
		(saveSelectionToTxt = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	},
	save = async () => { // SingleHtml by Лекс, правка: Dumby, Dobrov
		var msgName = id + "ucfDwnldsBtnSaveSnapshotToHTML";
		var write = IOUtils.writeUTF8 ? "writeUTF8" : "writeAtomicUTF8";
		var msgListener = async msg => {
			var [fileContent, fileName] = msg.data, dir; // fileName: выделенный текст или null
			try {dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}
			var arr = prefs.getStringPref("ucf_save.dirs","_Web||_Images|0").split('|').slice(0,2); //subdir: title|host
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // ucf_save.dirs = "_Web||_Pics|1" HTML сохранится в папку [Загрузки]/_Web/label
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать, если не существует…
			var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
			file.initWithPath(dir.path);
			if (!fileName) fileName = Title(100); // убрать служебные символы
			dir.append(Title(0, fileName) +'.html');
			await IOUtils[write](dir.path, fileContent) && showInStatusPanel("√ страница записана: " + fileName.slice(0, 60));
			var d = await Downloads.createDownload({ source: "about:blank", target: FileUtils.File(dir.path)}); // Fake download
			(await Downloads.getList(Downloads.ALL)).add(d);
			d.refresh(d.succeeded = true); // кнопка Загрузки мигает
		}
		messageManager.addMessageListener(msgName, msgListener);
		addDestructor(() => messageManager.removeMessageListener(msgName, msgListener));

		var svc = 'globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services';
		var url = "data:;charset=utf8," + encodeURIComponent(`(${func})(${svc});`.replace("%MSG_NAME%", msgName));
		(save = () => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false))();
	},
	bright = (trg, forward, val) => { // wheel
		if (!val) var val = getIntPref(my_br) + (forward ? 5 : -5);
		val = val > 100 ? 100 : val < 20 ? 20 : val;
		prefs.setIntPref(my_br, val), trg.toggleAttribute("rst"), showInStatusPanel("☀ Яркость страниц: "+ val +"%");
	},
	help = (btn) => { // встроенная справка
		var help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'];
		var newURI = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).convertChromeURL(Services.io.newURI(help_ucf[0])); // .spec = file:///
		(newURI.QueryInterface(Ci.nsIFileURL).file.exists()) ? switchToTab(help_ucf[0]) : switchToTab(help_ucf[1]);
	},
	GetSelection = (mM = gBrowser.selectedBrowser.messageManager) => {
		mM.addMessageListener('getSelect', function sel_listener(msg) {
			window.seltxt = msg.data;
			mM.removeMessageListener('getSelect', sel_listener, true);
		});
		mM.loadFrameScript('data:,sendAsyncMessage("getSelect",content.document.getSelection().toString())',false);
	},
	data = {
		"#downloads-button": { mousedownTarget: true,

			4() { // Double Left Click - Обзор папки «Загрузки»
				c("DW Double Left Click");
				Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError);
			},
			128() { saveSelectionToTxt();}, // СКМ Click (сохранить .txt)
			256() { save();}, // ПКМ Click (Single HTML)
			260(btn) { // Double ПКМ Click
				var pref = "permissions.default.image";
				var one = prefs.getIntPref(pref) == 1;
				prefs.setIntPref(pref, one ? 2 : 1);
				btn.style.filter = one ? "hue-rotate(180deg) brightness(95%)" : "";
				BrowserReload();
			},
		},
		"#PanelUI-menu-button": { mousedownTarget: true,

			1(btn) { help(btn);}, // Long Press
			8() { gCustomizeMode.enter();}, //ЛКМ + Alt Персонализация
			16(btn) { help(btn);}, // Shift + ЛКМ
			4() { goQuitApplication();}, // Double Left Click
			128() { windowState != STATE_MAXIMIZED ? maximize() : restore();}, // СКМ
			136(btn) { BrowserFullScreen();}, // Alt + СКМ
			132() { BrowserReloadSkipCache();}, // СКМ Double
			256() { minimize();}, // ПКМ
			260(btn) { btn.ownerGlobal.undoCloseTab();}, // ПКМ Double Right Click
			264(btn) { switchToTab('about:performance');}, // Alt + ПКМ
			280(btn) { // Shift + Alt + ПКМ
				var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_devtools_remotedebugging");
				(this[280] = target => obj.oncommand({target}))(btn); // запуск пункта меню, у которого нет HotKey
			},
		},
		"#pageAction-urlbar-_2495d258-41e7-4cd5-bc7d-ac15981f064e_": { // Reader View Button
			128(btn) { // СКМ
				btn.ownerDocument.getElementById("key_responsiveDesignMode").doCommand(); // запуск пункта меню с HotKey
				if (gBrowser.selectedBrowser.browsingContext.inRDMPane) BrowserReload();
			},
			2(trg, forward) { bright(trg, forward);}, // яркость по wheel ±
			264(btn) { // Alt + ПКМ
				translate(gBrowser.selectedBrowser.messageManager, 1);
			},
			1(btn) { // Shift + ПКМ
				translate(gBrowser.selectedBrowser.messageManager);
			},
		},
		"#star-button-box": {
			1() { // Left Long Press
				c("Star Left Long Press");
			},
			2(trg, forward) { bright(trg, forward);}, // яркость по wheel ±
			// 128(btn) { // СКМ
			// 	switchToTab('about:config');
			// },
			256() { // ПКМ
				window.undoCloseTab();
			},
		},
		"#identity-box": { // Замок
			2(trg, forward) { bright(trg, forward);}, // яркость по wheel ±
			128(trg, forward) { bright(trg, forward, 100);}, // СКМ
			256(btn) { // ПКМ
				gClipboard.write(gURLBar.value);
				url_color(), showInStatusPanel("в буфере: "+ gURLBar.value.slice(0, 80));
			},
		},
		"#tracking-protection-icon-container": { // Защита
			2(trg, forward) { bright(trg, forward);}, // яркость по wheel ±
			128(trg, forward) { bright(trg, forward, 100);}, // СКМ
		},
		"#identity-permission-box": {
			2(trg, forward) { // wheel
				c("Identity Permisson Box Wheel " + (forward ? "forward" : "backward"));
			}
		},
		"#identity-icon-box": {
			16() { // Shift + Left Click
				c("Identity Icon Box Shift + Left Click");
			},
		},
	}; // end Clicks, HotKeys ==================================================


})("hookClicks-and-tooltips", ({io, focus}) => { // SingleHTML не сохраняет svg графику

	var resolveURL = function (url, base) {
		try { return io.newURI(url, null, io.newURI(base)).spec;} catch {}
	},
	getSelWin = function (w) {
		if (w.getSelection().toString()) return w;
		for (var i = 0, f, r; f = w.frames[i]; i++) {
			try { if (r = getSelWin(f)) return r;} catch(e) {}
		}
	},
	encodeImg = function (src, obj) {
		var canvas, img, ret = src;
		if (/^https?:\/\//.test(src)) {
			canvas = doc.createElement('canvas');
			if (!obj || obj.nodeName.toLowerCase() != 'img') {
				img = doc.createElement('img');
				img.src = src;
			} else
				img = obj;
			if (img.complete) try{
				canvas.width = img.width;
				canvas.height = img.height;
				canvas.getContext('2d').drawImage(img, 0, 0);
				ret = canvas.toDataURL((/\.jpe?g/i.test(src) ? 'image/jpeg' : 'image/png'));
			} catch (e) {};
			if (img != obj) img.src = 'about:blank';
		};
		return ret;
	},
	toSrc = function (obj) {
		var strToSrc = function (str) {
			var chr, ret = '', i = 0, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\x22' : '\\\x22', '\\': '\\\\'};
			while (chr = str.charAt(i++)) {
				ret += meta[chr] || chr;
			};
			return '\x22' + ret + '\x22';
		},
		arrToSrc = function (arr) {
			var ret = [];
			for (var i = 0; i < arr.length; i++) {
				ret[i] = toSrc(arr[i]) || 'null';
			};
			return '[' + ret.join(',') + ']';
		},
		objToSrc = function (obj) {
			var val, ret = [];
			for (var prop in obj) {
				if (obj.hasOwnProperty(prop) && (val = toSrc(obj[prop]))) ret.push(strToSrc(prop) + ': ' + val);
			};
			return '{' + ret.join(',') + '}';
		};
		switch (Object.prototype.toString.call(obj).slice(8, -1)) {
			case 'Array': return arrToSrc(obj);
			case 'Boolean':
			case 'Function':
			case 'RegExp': return obj.toString();
			case 'Date': return 'new Date(' + obj.getTime() + ')';
			case 'Math': return 'Math';
			case 'Number': return isFinite(obj) ? String(obj) : 'null';
			case 'Object': return objToSrc(obj);
			case 'String': return strToSrc(obj);
			default: return obj ? (obj.nodeType == 1 && obj.id ? 'document.getElementById(' + strToSrc(obj.id) + ')' : '{}') : 'null';
		}
	},
	mainWin = {};
	focus.getFocusedElementForWindow(content, true, mainWin);
	mainWin = mainWin.value;

	var selWin = getSelWin(mainWin), win = selWin || mainWin, doc = win.document, loc = win.location;
	var ele, pEle, clone, reUrl = /(url\(\x22)(.+?)(\x22\))/g;

	if (selWin) {
		var rng = win.getSelection().getRangeAt(0);
		pEle = rng.commonAncestorContainer;
		ele = rng.cloneContents();
	} else {
		pEle = doc.documentElement;
		ele = (doc.body || doc.getElementsByTagName('body')[0]).cloneNode(true);
	};
	while (pEle) {
		if (pEle.nodeType == 1) {
			clone = pEle.cloneNode(false);
			clone.appendChild(ele);
			ele = clone;
		};
		pEle = pEle.parentNode
	};
	var sel = doc.createElement('div');
	sel.appendChild(ele);

	for (var el, all = sel.getElementsByTagName('*'), i = all.length; i--;) {
		el = all[i];
		if (el.style && el.style.backgroundImage) el.style.backgroundImage = el.style.backgroundImage.replace(reUrl, function (a, prev, url, next) {
			if (!/^[a-z]+:/.test(url)) url = resolveURL(url, loc.href);
			return prev + encodeImg(url) + next;
		});
		switch (el.nodeName.toLowerCase()) {
			case 'link':
			case 'style':
			case 'script': el.parentNode.removeChild(el); break;
			case 'a':
			case 'area': if (el.hasAttribute('href') && el.getAttribute('href').charAt(0) != '#') el.href = el.href; break;
			case 'img':
			case 'input': if (el.hasAttribute('src')) el.src = encodeImg(el.src, el); break;
			case 'audio':
			case 'video':
			case 'embed':
			case 'frame':
			case 'iframe': if (el.hasAttribute('src')) el.src = el.src; break;
			case 'object': if (el.hasAttribute('data')) el.data = el.data; break;
			case 'form': if (el.hasAttribute('action')) el.action = el.action; break;
		}
	};
	var head = ele.insertBefore(doc.createElement('head'), ele.firstChild), meta = doc.createElement('meta'), sheets = doc.styleSheets, title = doc.getElementsByTagName('title')[0];
	meta.httpEquiv = 'content-type';
	meta.content = 'text/html; charset=utf-8';
	head.appendChild(meta);
	if (title) head.appendChild(title.cloneNode(true));

	head.copyScript = function (unsafeWin) {
		if ('$' in unsafeWin) return;
		var f = doc.createElement('iframe');
		f.src = 'about:blank';
		f.setAttribute('style', 'position:fixed;left:0;top:0;visibility:hidden;width:0;height:0;');
		doc.documentElement.appendChild(f);
		var str, script = doc.createElement('script');
		script.type = 'text/javascript';
		for (var name in unsafeWin) {
			if (name in f.contentWindow || !/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) continue;
			try {
				str = toSrc(unsafeWin[name]);
				if (!/\{\s*\[native code\]\s*\}/.test(str)) {
					script.appendChild(doc.createTextNode('var ' + name + ' = ' + str.replace(/<\/(script>)/ig, '<\\/$1') + ';\n'));
				}
			} catch (e) {};
		};
		f.parentNode.removeChild(f);
		if (script.childNodes.length) this.nextSibling.appendChild(script);
	};
	head.copyScript(win.wrappedJSObject || win);

	head.copyStyle = function (s) {
		if (!s) return;
		var style = doc.createElement('style');
		style.type = 'text/css';
		if (s.media && s.media.mediaText) style.media = s.media.mediaText;
		try {
			for (var i = 0, rule; rule = s.cssRules[i]; i++) {
				if (rule.type != 3) {
					if((!rule.selectorText || rule.selectorText.indexOf(':') != -1) || (!sel.querySelector || sel.querySelector(rule.selectorText))) {
						var css = !rule.cssText ? '' : rule.cssText.replace(reUrl, function (a, prev, url, next) {
							if (!/^[a-z]+:/.test(url)) url = resolveURL(url, s.href || loc.href);
							if(rule.type == 1 && rule.style && rule.style.backgroundImage) url = encodeImg(url);
							return prev + url + next;
						});
						style.appendChild(doc.createTextNode(css + '\n'));
					}
				} else {
					this.copyStyle(rule.styleSheet);
				}
			}
		} catch(e) {
			if (s.ownerNode) style = s.ownerNode.cloneNode(false);
		};
		this.appendChild(style);
	};
	for (var j = 0; j < sheets.length; j++) head.copyStyle(sheets[j]);
	head.appendChild(doc.createTextNode('\n'));
	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};
	sendAsyncMessage("%MSG_NAME%", [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', selWin ? win.getSelection().toString().slice(0, 200) : undefined]); // выделенный текст
}); // END hookClicks

Отсутствует

 

№14506-12-2021 17:33:36

egorsemenov06
Участник
 
Группа: Members
Зарегистрирован: 12-06-2018
Сообщений: 260
UA: Firefox 95.0

Re: UCF-скрипты на этом форуме

Dumby посмотрите пожалуйста кнопку toggleRestartlessAddons в ней при ПКМ не появляется сообщение в правом нижнем углу а в консоле появляеться ошибка
Uncaught SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data
    forgetClosedTab chrome://user_chrome_files/content/custom_scripts/custom_script.js line 237 > Function:794
    removeTab chrome://user_chrome_files/content/custom_scripts/custom_script.js line 237 > Function:805
    waitTimer chrome://user_chrome_files/content/custom_scripts/custom_script.js line 237 > Function:809

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

Выделить код

Код:

// http://infocatcher.ucoz.net/js/cb/toggleRestartlessAddons.js
// https://forum.mozilla-russia.org/viewtopic.php?id=57948
// https://github.com/Infocatcher/Custom_Buttons/tree/master/Toggle_Restartless_Add-ons

// Toggle Restartless Add-ons button for Custom Buttons
// (code for "initialization" section)
// Also the code can be used from main window context (as Mouse Gestures code, for example)

// Also you can check for add-ons updates using right-click:
// copy all code from
// https://github.com/Infocatcher/Custom_Buttons/blob/master/Check_for_Addons_Updates/checkForAddonsUpdates.js
// after "//== Check for Addons Updates begin"

// See "var style = " to modify styles for specific add-ons

// (c) Infocatcher 2013-2019
// version 0.1.3pre4 - 2020-01-01

var options = {
	addonTypes: ["extension", "plugin"],
	// Possible values: "extension", "plugin"
	// From extensions: "userstyle" (Stylish), "greasemonkey-user-script" (Greasemonkey), "userscript" (Scriptish)
	// (swap to reorder in the menu)
	showVersions: 0,
	// 0 - don't show versions
	// 1 - show after name: "Addon Name 1.2"
	// 2 - show as "acceltext" (in place for hotkey text)
	showHidden: 1,
	// 0  - don't show hidden add-ons
	// -1 - show only enabled hidden add-ons (e.g. to track new items)
	// 1  - show all hidden add-ons
	sort: {
		enabled:     0,
		clickToPlay: 0,
		disabled:    0
		// Sort order:
		// 0, 0, 0 - sort add-ons of each type alphabetically
		// 0, 0, 1 - show enabled add-ons (of each type) first
		// 0, 1, 2 - enabled add-ons, then click-to-play and then disabled
	},
	closeMenu: false, // Close menu after left-click
	closeMenuClickToPlay: false // Close menu after left-click, for click to play plugins
	// Use Shift+click to invert closeMenu* behavior
};

var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

var mp = document.createElementNS(xulns, "menupopup");
mp.setAttribute("onpopupshowing", "this.updateMenu();");
mp.setAttribute("oncommand", "if(!event.button) this.handleEvent(event);"); // Ignore middle-click in Firefox 89+
mp.setAttribute("onmousedown", "if(event.button == 0) this.handleEvent(event);");
mp.setAttribute("onclick", "if(event.button > 0) this.handleEvent(event);");
mp.setAttribute("oncontextmenu", "return false;");
mp.setAttribute("onpopuphidden", "this.destroyMenu();");

var tb = this.parentNode;
if(tb && tb.getAttribute("orient") == "vertical") {
	// https://addons.mozilla.org/firefox/addon/vertical-toolbar/
	var isRight = tb.parentNode.getAttribute("placement") == "right";
	mp.setAttribute("position", isRight ? "start_before" : "end_before");
}

var cleanupTimer = 0;
mp.updateMenu = function() {
	clearTimeout(cleanupTimer);
	addStyle();
	getRestartlessAddons(options.addonTypes, function(addons) {
		var df = document.createDocumentFragment();
		var prevType;
		function sortPosition(addon) {
			if("STATE_ASK_TO_ACTIVATE" in AddonManager && addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE)
				return options.sort.clickToPlay;
			if(addon.isActive)
				return options.sort.enabled;
			return options.sort.disabled;
		}
		function key(addon) {
			return options.addonTypes.indexOf(addon.type)
				+ "\n" + sortPosition(addon)
				+ "\n" + addon.name.toLowerCase();
		}
		addons.sort(function(a, b) {
			var ka = key(a);
			var kb = key(b);
			return ka == kb ? 0 : ka < kb ? -1 : 1;
		}).forEach(function(addon) {
			var type = addon.type;
			if(prevType && type != prevType)
				df.appendChild(document.createElementNS(xulns, "menuseparator"));
			prevType = type;
			var icon = addon.iconURL || addon.icon64URL;
			var mi = document.createElementNS(xulns, "menuitem");
			mi.className = "menuitem-iconic";
			var label = addon.name;
			if(options.showVersions == 1)
				label += " " + addon.version;
			else if(options.showVersions == 2)
				mi.setAttribute("acceltext", addon.version);
			mi.setAttribute("label", label);
			mi.setAttribute("image", icon || mp.icons[type] || "");
			if(!icon && mp.icons.useSVG)
				mi.style.fill = "#15c";
			var tip = addon.description || "";
			var delay = "delayedStartupAddons" in Services
				&& Services.delayedStartupAddons[addon.id] || null;
			var isDelayed = delay !== null;
			mi.classList.toggle("toggleRestartlessAddons-isDelayed", isDelayed);
			if(isDelayed)
				tip = "[Delayed Startup: " + delay.toLocaleString() + "]" + (tip ? "\n" + tip : "");
			tip && mi.setAttribute("tooltiptext", tip);
			mi.classList.toggle("toggleRestartlessAddons-isHidden", addon.hidden || false);
			setDisabled(mi, addon.userDisabled);
			mi._cbAddon = addon;
			df.appendChild(mi);
		});
		mp.textContent = "";
		mp.appendChild(df);
	});
};
mp.handleEvent = function(e) {
	var mi = e.target;
	if(!("_cbAddon" in mi))
		return;
	var addon = mi._cbAddon;
	if(e.type == "mousedown") {
		var closeMenu = isAskToActivateAddon(addon)
			? options.closeMenuClickToPlay
			: options.closeMenu;
		if(e.shiftKey)
			closeMenu = !closeMenu;
		mi.setAttribute("closemenu", closeMenu ? "auto" : "none");
		return;
	}
	var hasMdf = hasModifier(e);
	if(e.type == "command" && (!hasMdf || e.shiftKey)) {
		let newDis = setNewDisabled(addon);
		setDisabled(mi, newDis);
	}
	else if(e.type == "command" && hasMdf || e.type == "click" && e.button == 1) {
		openAddonPage(addon);
		closeMenus(mi);
	}
	else if(e.type == "click" && e.button == 2) {
		if(openAddonOptions(addon))
			closeMenus(mi);
	}
};
mp.destroyMenu = function() {
	removeStyle();
	clearTimeout(cleanupTimer);
	cleanupTimer = setTimeout(function() {
		mp.textContent = "";
	}, 5000);
};
mp.icons = {
	get platformVersion() {
		delete this.platformVersion;
		return this.platformVersion = parseFloat(Services.appinfo.platformVersion);
	},
	get useSVG() {
		delete this.useSVG;
		return this.useSVG = Services.appinfo.name == "Firefox" && this.platformVersion >= 57;
	},
	get plugin() {
		delete this.plugin;
		return this.plugin = this.useSVG
			? this.platformVersion >= 65
				? "chrome://global/skin/plugins/pluginGeneric.svg"
				: "chrome://mozapps/skin/plugins/pluginGeneric.svg"
			: "chrome://mozapps/skin/plugins/pluginGeneric-16.png";
	},
	get extension() {
		delete this.extension;
		return this.extension = this.useSVG
			? this.platformVersion >= 76
				? "chrome://mozapps/skin/extensions/extensionGeneric.svg" // Or chrome://mozapps/skin/extensions/extension.svg
				: "chrome://mozapps/skin/extensions/extensionGeneric-16.svg"
			: "chrome://mozapps/skin/extensions/extensionGeneric-16.png";
	}
};
function isAskToActivateAddon(addon) {
	return addon.type == "plugin"
		&& "STATE_ASK_TO_ACTIVATE" in AddonManager
		&& Services.prefs.getBoolPref("plugins.click_to_play", true);
}
function setNewDisabled(addon) {
	var newDis = getNewDisabled(addon);
	var oldDis = addon.userDisabled;
	try {
		addon.userDisabled = newDis;
	}
	catch(e) { // Error: Cannot disable hidden add-on firefox@getpocket.com
		_log("Can't set addon.userDisabled to " + newDis + ", error:\n" + e);
		if(addon.hidden)
			setNewDisabledRaw(addon, newDis);
	}
	var realDis = addon.userDisabled;
	if(realDis != newDis && addon.type == "extension") { // Firefox 62+? Weird things happens
		setNewDisabledRaw(addon, newDis);
		realDis = addon.userDisabled;
	}
	if(realDis != newDis) { // We can't enable vulnerable plugins
		let err = "Can't set addon.userDisabled to " + newDis + ", real value: " + realDis;
		if(newDis) {
			_log(err + "\nSTATE_ASK_TO_ACTIVATE not supported?");
			newDis = false;
		}
		else {
			_log(err + "\nVulnerable plugin?");
			if(oldDis == AddonManager.STATE_ASK_TO_ACTIVATE)
				newDis = true;
			else
				newDis = AddonManager.STATE_ASK_TO_ACTIVATE;
		}
		addon.userDisabled = newDis;
	}
	ensureSpecialDisabled(addon, newDis);
	return addon.userDisabled;
}
function getNewDisabled(addon) {
	// disabled -> STATE_ASK_TO_ACTIVATE -> enabled -> ...
	var curDis = addon.userDisabled;
	var newDis;
	if("STATE_ASK_TO_ACTIVATE" in AddonManager && curDis == AddonManager.STATE_ASK_TO_ACTIVATE)
		newDis = false;
	else if(!curDis)
		newDis = true;
	else {
		if(isAskToActivateAddon(addon))
			newDis = AddonManager.STATE_ASK_TO_ACTIVATE;
		else
			newDis = false;
	}
	return newDis;
}
function setNewDisabledRaw(addon, newDis) {
	_log("Let's try set addon.userDisabled using raw hack");
	let g = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm", {});
	if("XPIDatabase" in g && "updateAddonDisabledState" in g.XPIDatabase) { // Firefox 61+
		let rawAddon = g.XPIDatabase.getAddons().find(function(rawAddon) {
			return rawAddon.id == addon.id;
		});
		g.XPIDatabase.updateAddonDisabledState(
			rawAddon,
			g.XPIDatabase.updateAddonDisabledState.length == 1 // Firefox 74+
				? { userDisabled: newDis }
				: newDis
		);
	}
	else if("eval" in g) { // See "set userDisabled(val)"
		let addonFor = g.eval("addonFor");
		let rawAddon = addonFor(addon);
		//rawAddon.userDisabled = newDis;
		g.XPIProvider.updateAddonDisabledState(rawAddon, newDis);
	}
	else { // Firefox 57+? See https://forum.mozilla-russia.org/viewtopic.php?pid=745272#p745272
		updateAddonDisabledState(addon, newDis);
	}
}
function updateAddonDisabledState(addon, newDis) {
	var nsvo = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm", {});
	var key = "_cbToggleRestartlessAddonsData";
	var url = URL.createObjectURL(new Blob([
		"XPIProvider.updateAddonDisabledState(addonFor(this." + key + "[0]), this." + key + "[1]); delete this." + key + ";"
	]));
	addDestructor(function() {
		URL.revokeObjectURL(url);
	});
	(updateAddonDisabledState = function(addon, newDis) {
		nsvo[key] = [addon, newDis];
		Services.scriptloader.loadSubScript(url, nsvo);
	})(addon, newDis);
}
function setDisabled(mi, disabled) {
	var askToActivate = "STATE_ASK_TO_ACTIVATE" in AddonManager && disabled == AddonManager.STATE_ASK_TO_ACTIVATE;
	var cl = mi.classList;
	cl.toggle("toggleRestartlessAddons-askToActivate", askToActivate);
	cl.toggle("toggleRestartlessAddons-disabled", disabled && !askToActivate);
}
function ensureSpecialDisabled(addon, newDis) {
	if(addon.id == "screenshots@mozilla.org")
		Services.prefs.setBoolPref("extensions.screenshots.disabled", newDis);
}

if(
	this instanceof XULElement // Custom Buttons
	&& typeof event == "object"
	&& !("type" in event) && typeof _phase == "string" && _phase == "init" // Initialization
) {
	this.type = "menu";
	this.orient = "horizontal";
	this.appendChild(mp);

	this.onmouseover = function(e) {
		if(e.target != this)
			return;
		Array.prototype.some.call(
			this.parentNode.getElementsByTagName("*"),
			function(node) {
				if(
					node != this
					&& node.namespaceURI == xulns
					// See https://github.com/Infocatcher/Custom_Buttons/issues/28
					//&& node.boxObject
					//&& node.boxObject instanceof Components.interfaces.nsIMenuBoxObject
					&& "open" in node
					&& node.open
					&& node.getElementsByTagName("menupopup").length
				) {
					node.open = false;
					this.open = true;
					return true;
				}
				return false;
			},
			this
		);
	};
	this.onmousedown = function(e) {
		if(e.target == this && e.button == 0 && hasModifier(e))
			e.preventDefault();
	};
	this.oncontextmenu = function(e) {
		if(e.target == this && !hasModifier(e) && hasUpdater())
			e.preventDefault();
	};
	this.onclick = function(e) {
		if(e.target != this)
			return;
		if(e.button == 0 && hasModifier(e) || e.button == 1)
			openAddonsManager();
		else if(e.button == 2 && !hasModifier(e) && hasUpdater())
			checkForAddonsUpdates.call(this);
	};
}
else { // Mouse gestures or something other...
	let e;
	if(typeof event == "object" && event instanceof Event && "screenX" in event) // FireGestures
		e = event;
	else if(
		this instanceof Components.interfaces.nsIDOMChromeWindow
		&& "mgGestureState" in window && "endEvent" in mgGestureState // Mouse Gestures Redox
	)
		e = mgGestureState.endEvent;
	else {
		let anchor = this instanceof XULElement && this
			|| window.gBrowser && gBrowser.selectedBrowser
			|| document.documentElement;
		if("boxObject" in anchor) {
			let bo = anchor.boxObject;
			e = {
				screenX: bo.screenX,
				screenY: bo.screenY
			};
			if(this instanceof XULElement)
				e.screenY += bo.height;
		}
	}
	if(!e || !("screenX" in e))
		throw new Error("[Toggle Restartless Add-ons]: Can't get event object");
	document.documentElement.appendChild(mp);
	mp.addEventListener("popuphidden", function destroy(e) {
		mp.removeEventListener(e.type, destroy, false);
		setTimeout(function() {
			mp.destroyMenu();
			mp.parentNode.removeChild(mp);
		}, 0);
	}, false);
	mp.openPopupAtScreen(e.screenX, e.screenY);
}

function getRestartlessAddons(addonTypes, callback, context) {
	if(!("AddonManager" in window))
		Components.utils.import("resource://gre/modules/AddonManager.jsm");
	if(!("Services" in window))
		Components.utils.import("resource://gre/modules/Services.jsm");
	var then, promise = AddonManager.getAddonsByTypes(addonTypes, then = function(addons) {
		callback.call(context, addons.filter(function(addon) {
			var ops = addon.operationsRequiringRestart;
			return !addon.appDisabled
				&& !(ops & AddonManager.OP_NEEDS_RESTART_ENABLE || ops & AddonManager.OP_NEEDS_RESTART_DISABLE)
				&& (
					!addon.hidden
					|| options.showHidden > 0
					|| options.showHidden == -1 && !addon.userDisabled
				)
				&& (addon.iconURL || "").substr(0, 29) != "resource://search-extensions/";
		}));
	});
	promise && typeof promise.then == "function" && promise.then(then, Components.utils.reportError); // Firefox 61+
}
function openAddonOptions(addon) {
	// Based on code from chrome://mozapps/content/extensions/extensions.js
	// Firefox 21.0a1 (2013-01-27)
	var optionsURL = addon.optionsURL;
	if(!addon.isActive || !optionsURL)
		return false;
	if(addon.type == "plugin") // No options for now!
		return false;
	if(
		addon.optionsType == (AddonManager.OPTIONS_TYPE_INLINE || NaN)
		|| addon.optionsType == (AddonManager.OPTIONS_TYPE_INLINE_INFO || NaN)
		|| addon.optionsType == (AddonManager.OPTIONS_TYPE_INLINE_BROWSER || NaN)
	)
		openAddonPage(addon, true);
	else if(addon.optionsType == AddonManager.OPTIONS_TYPE_TAB && "switchToTabHavingURI" in window)
		switchToTabHavingURI(optionsURL, true);
	else {
		let windows = Services.wm.getEnumerator(null);
		while(windows.hasMoreElements()) {
			let win = windows.getNext();
			if(win.document.documentURI == optionsURL) {
				win.focus();
				return true;
			}
		}
		// Note: original code checks browser.preferences.instantApply and may open modal windows
		window.openDialog(optionsURL, "", "chrome,titlebar,toolbar,centerscreen,dialog=no");
	}
	return true;
}
function openAddonsManager(view) {
	var openAddonsMgr = window.BrowserOpenAddonsMgr // Firefox
		|| window.openAddonsMgr // Thunderbird
		|| window.toEM; // SeaMonkey
	openAddonsMgr(view);
}
function openAddonPage(addon, scrollToPreferences) {
	var platformVersion = parseFloat(
		Services.appinfo.name == "Pale Moon"
			? Services.appinfo.version
			: Services.appinfo.platformVersion
	);
	scrollToPreferences = scrollToPreferences && platformVersion >= 12
		? "/preferences"
		: "";
	openAddonsManager("addons://detail/" + encodeURIComponent(addon.id) + scrollToPreferences);
}

function hasModifier(e) {
	return e.ctrlKey || e.shiftKey || e.altKey || e.metaKey;
}

function addStyle() {
	if(addStyle.hasOwnProperty("_style"))
		return;
	var style = '\
		.toggleRestartlessAddons-isDelayed > .menu-iconic-text {\n\
			opacity: 0.75;\n\
			color: #070;\n\
		}\n\
		.toggleRestartlessAddons-isHidden > .menu-iconic-text {\n\
			color: #609;\n\
		}\n\
		.toggleRestartlessAddons-disabled > .menu-iconic-left {\n\
			opacity: 0.4;\n\
		}\n\
		.toggleRestartlessAddons-disabled > .menu-iconic-text,\n\
		.toggleRestartlessAddons-disabled > .menu-accel-container {\n\
			opacity: 0.5;\n\
		}\n\
		.toggleRestartlessAddons-askToActivate {\n\
			color: -moz-nativehyperlinktext;\n\
		}';
	addStyle._style = document.insertBefore(
		document.createProcessingInstruction(
			"xml-stylesheet",
			'href="' + "data:text/css,"
				+ encodeURIComponent(style) + '" type="text/css"'
		),
		document.documentElement
	);
}
function removeStyle() {
	if(!addStyle.hasOwnProperty("_style"))
		return;
	var s = addStyle._style;
	s.parentNode.removeChild(s);
	delete addStyle._style;
}
function closeMenus(node) {
	// Based on function closeMenus from chrome://browser/content/utilityOverlay.js
	for(; node && "tagName" in node; node = node.parentNode) {
		if(
			node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
			&& (node.localName == "menupopup" || node.localName == "popup")
		)
			node.hidePopup();
	}
}
function _log(s) {
	if(typeof LOG == "function") // Custom Buttons
		LOG(s);
	else // Or something else
		Services.console.logStringMessage("Toggle Restartless Add-ons: " + s);
}

function hasUpdater() {
	var has = checkForAddonsUpdates.toString().indexOf("about:addons") != -1;
	hasUpdater = function() {
		return has;
	};
	return has;
}
function checkForAddonsUpdates() {
//== Check for Addons Updates begin
// http://infocatcher.ucoz.net/js/cb/checkForAddonsUpdates.js
// https://forum.mozilla-russia.org/viewtopic.php?id=57958
// https://github.com/Infocatcher/Custom_Buttons/tree/master/Check_for_Addons_Updates

// Check for Addons Updates button for Custom Buttons
// (code for "code" section)

// (c) Infocatcher 2012-2021
// version 0.1.6pre4 - 2021-03-28

// Button just open hidden tab with about:addons and trigger built-in "Check for Updates" function.
// And show tab, if found updates.

(function() {
var btn = this instanceof XULElement
	? this
	: { // Launched not from custom button
		image: "", // Base64-encoded icon (if empty, will be used "imgLoading")
		label: "Check for Addons Updates",
		tooltipText: ""
	};
if("_cb_disabled" in btn)
	return;
btn._cb_disabled = true;

if(!("Services" in window))
	Components.utils.import("resource://gre/modules/Services.jsm");
var app = Services.appinfo.name;
var pv = parseFloat(Services.appinfo.platformVersion);

var ADDONS_URL = "about:addons";

var progressIcon = new ProgressIcon(btn);
var image = btn.image || progressIcon.imgLoading;
var tip = btn.tooltipText;
btn.tooltipText = "Open " + ADDONS_URL + "…";

var tab, browser, gBrowser;
var tbTabInfo, tbTab;

var trgWindow = Services.wm.getMostRecentWindow("navigator:browser")
	|| app == "Thunderbird" && Services.wm.getMostRecentWindow("mail:3pane")
	|| window;
var trgDocument = trgWindow.document;
var tabmail = trgDocument.getElementById("tabmail");

if(tabmail && app == "Thunderbird") { // Note: SeaMonkey doesn't support content tabs in mail window
	let addonsWin;
	let receivePong = function(subject, topic, data) {
		addonsWin = subject;
	};
	Services.obs.addObserver(receivePong, "EM-pong", false);
	Services.obs.notifyObservers(null, "EM-ping", "");
	Services.obs.removeObserver(receivePong, "EM-pong");
	if(addonsWin) {
		let rootWindow = addonsWin
			.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
			.getInterface(Components.interfaces.nsIWebNavigation)
			.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
			.rootTreeItem
			.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
			.getInterface(Components.interfaces.nsIDOMWindow);
		tabmail = rootWindow.document.getElementById("tabmail");
		tbTabInfo = tabmail.getBrowserForDocument(addonsWin);
		tbTab = tab = tbTabInfo.tabNode;
		processAddonsTab(addonsWin);
	}
	else {
		Services.obs.addObserver(function observer(subject, topic, data) {
			Services.obs.removeObserver(observer, topic);
			if(subject.document.readyState == "complete")
				processAddonsTab(subject);
			else {
				subject.addEventListener("load", function onLoad(e) {
					subject.removeEventListener(e.type, onLoad, false);
					processAddonsTab(subject);
				}, false);
			}
		}, "EM-loaded", false);
		// See openAddonsMgr() -> openContentTab()
		tbTabInfo = tabmail.openTab("contentTab", {
			contentPage: ADDONS_URL,
			clickHandler: "specialTabs.siteClickHandler(event, /addons\.mozilla\.org/);",
			background: true
		});
		tbTab = tab = tbTabInfo.tabNode;
		tbTab.collapsed = true;
		// Note: dontSelectHiddenTab() not implemented
	}
}
else if("gBrowser" in trgWindow && trgWindow.gBrowser.tabs) {
	let isPending = false;
	let ws = Services.wm.getEnumerator("navigator:browser");
	windowsLoop:
	while(ws.hasMoreElements()) {
		let w = ws.getNext();
		let tabs = w.gBrowser.tabs;
		for(let i = 0, l = tabs.length; i < l; ++i) {
			let t = tabs[i];
			if(
				!t.closing
				&& t.linkedBrowser
				&& t.linkedBrowser.currentURI.spec == ADDONS_URL
			) {
				tab = t;
				break windowsLoop;
			}
		}
	}

	gBrowser = trgWindow.gBrowser;
	if(!tab) {
		tab = gBrowser.addTab(ADDONS_URL, {
			triggeringPrincipal: "Services" in window // Firefox 63+
				&& Services.scriptSecurityManager
				&& Services.scriptSecurityManager.getSystemPrincipal()
		});
		tab.collapsed = true;
		tab.closing = true; // See "visibleTabs" getter in chrome://browser/content/tabbrowser.xml
		trgWindow.addEventListener("TabSelect", dontSelectHiddenTab, false);
	}
	else if(
		tab.getAttribute("pending") == "true" // Gecko >= 9.0
		|| tab.linkedBrowser.contentDocument.readyState == "uninitialized"
		// || tab.linkedBrowser.__SS_restoreState == 1
	)
		isPending = true;

	browser = tab.linkedBrowser;
	if(
		isPending
		|| browser.webProgress.isLoadingDocument
		|| browser.currentURI.spec == "about:blank" // Firefox 79+
	) {
		browser.addEventListener("load", processAddonsTab, true);
		if(isPending) {
			if(pv >= 41) {
				// Workaround to correctly restore pending tab
				// See https://github.com/Infocatcher/Custom_Buttons/issues/39
				let selTab = gBrowser.selectedTab;
				gBrowser.selectedTab = tab;
				gBrowser.selectedTab = selTab;
			}
			else {
				browser.reload();
			}
		}
	}
	else {
		processAddonsTab();
	}
}
else {
	progressIcon.restore();
	btn.tooltipText = tip;
	delete btn._cb_disabled;
	Services.prompt.alert(window, btn.label, "Error: Can't find supported window!");
	return;
}

function processAddonsTab(e, again) {
	var doc;
	if(e && e instanceof Components.interfaces.nsIDOMWindow) {
		doc = e.document;
	}
	else if(e) {
		doc = e.target;
		if(doc.location != ADDONS_URL)
			return;
		browser.removeEventListener(e.type, processAddonsTab, true);
	}
	else {
		doc = browser.contentDocument;
	}

	btn.tooltipText = "Process " + ADDONS_URL + "…";
	progressIcon.loading();

	var origAttr = "_cb_checkForAddonsUpdates_origImage";
	if(!tab.hasAttribute(origAttr)) {
		var link = doc.querySelector('link[rel="shortcut icon"]'); // Not loaded yet?
		tab.setAttribute(origAttr, link && link.href || tab.image);
	}
	tab.image = image;

	var fu = $("cmd_findAllUpdates");
	if(!fu) { // Firefox 72+
		var win = doc.defaultView;
		var vb = doc.getElementById("html-view-browser");
		if(!vb) {
			if(!HTMLHtmlElement.isInstance(doc.documentElement)) { // Firefox 87+
				win.setTimeout(processAddonsTab, 20, win);
				return;
			}
			vb = browser;
		}
		if(!again) { // Strange errors happens
			// chrome://mozapps/content/extensions/aboutaddons.js
			// getTelemetryViewName() -> el.closest(...) is null
			win.setTimeout(processAddonsTab, 20, win, true);
			return;
		}
		var vbDoc = vb.contentDocument;
		fu = vbDoc.querySelector('[action="check-for-updates"]');
		var um = vbDoc.getElementById("updates-message");
	}

	var notFound = $("updates-noneFound") || {
		get hidden() { return um.getAttribute("state") != "none-found"; }
	};
	var updated = $("updates-installed") || {
		get hidden() { return um.getAttribute("state") != "installed"; }
	};
	// Avoid getting false results from the past update check (may not be required for "noneFound")
	if(um) { // Firefox 72+
		um.hidden = true;
		um.removeAttribute("state");
	}
	else {
		notFound.hidden = updated.hidden = true;
	}

	//fu.doCommand();
	fu.click();

	function localize(node, key, callback) {
		if(um) { // Firefox 72+
			doc.l10n.formatValue(key).then(function(s) {
				callback(s || key);
			}, Components.utils.reportError);
			return;
		}
		callback(node.getAttribute("value") || key);
	}

	var inProgress = $("updates-progress") || {
		get hidden() { return um.getAttribute("state") != "updating"; }
	};
	localize(inProgress, "addon-updates-updating", function(s) {
		btn.tooltipText = s;
	});

	var waitTimer = setInterval(function() {
		if(!doc.defaultView || doc.defaultView.closed) {
			stopWait();
			notify("Tab with add-ons manager was closed!");
			return;
		}
		if(!inProgress.hidden)
			return;
		var autoUpdate = $("utils-autoUpdateDefault")
			|| vbDoc.querySelector('[action="set-update-automatically"]');
		var autoUpdateChecked = autoUpdate.getAttribute("checked") == "true"
			|| autoUpdate.checked;

		var found = $("updates-manualUpdatesFound-btn") || {
			get hidden() { return um.getAttribute("state") != "manual-updates-found"; }
		};
		if(
			autoUpdateChecked
				? notFound.hidden && updated.hidden
				: notFound.hidden && found.hidden
		) // Too early?
			return;

		stopWait();
		if(!tbTab)
			tab.closing = false;
		function removeTab() {
			if(!tab.collapsed)
				return;
			if(tbTab) {
				tabmail.closeTab(tbTabInfo, true /*aNoUndo*/);
				return;
			}
			gBrowser.removeTab(tab);
			(function forgetClosedTab(isSecondTry) {
				var ss = "nsISessionStore" in Components.interfaces
					? (
						Components.classes["@mozilla.org/browser/sessionstore;1"]
						|| Components.classes["@mozilla.org/suite/sessionstore;1"]
					).getService(Components.interfaces.nsISessionStore)
					: trgWindow.SessionStore; // Firefox 61+ https://bugzilla.mozilla.org/show_bug.cgi?id=1450559
				if(!("forgetClosedTab" in ss))
					return;
				var closedTabs = JSON.parse(ss.getClosedTabData(window));
				for(let i = 0, l = closedTabs.length; i < l; ++i) {
					let closedTab = closedTabs[i];
					let state = closedTab.state;
					if(state.entries[state.index - 1].url == ADDONS_URL) {
						ss.forgetClosedTab(window, i);
						return;
					}
				}
				if(!isSecondTry) // May be needed in SeaMonkey
					setTimeout(forgetClosedTab, 0, true);
			})();
		}

		if(!notFound.hidden) {
			removeTab();
			localize(notFound, "addon-updates-none-found", function(s) {
				notify(s);
			});
			return;
		}
		if(autoUpdateChecked) {
			removeTab();
			localize(updated, "addon-updates-installed", function(s) {
				notify(s);
			});
			return;
		}

		tab.collapsed = false;

		var cats = $("categories");
		var upds = $("category-availableUpdates");
		if(cats && upds) {
			if(vb && cats.selectedItem == upds) // Only for Firefox 72+
				cats.selectedItem = $("category-extension"); // Trick to force update
			cats.selectedItem = upds;
		}
		else { // Firefox 76+ ?
			vbDoc.querySelector('.category[name="available-updates"]').click();
		}

		var tabWin = tab.ownerDocument.defaultView;
		if(tbTab)
			tabmail.switchToTab(tbTabInfo);
		else
			tabWin.gBrowser.selectedTab = tab;
		setTimeout(function() {
			tabWin.focus();
			doc.defaultView.focus();
			var al = $("addon-list") || vb;
			al.focus();
		}, 0);
	}, 50);
	function $(id) {
		return doc.getElementById(id);
	}
	function stopWait() {
		clearInterval(waitTimer);
		progressIcon.restore();
		btn.tooltipText = tip;
		if(tab.image == image)
			tab.image = tab.getAttribute(origAttr);
		tab.removeAttribute(origAttr);
		trgWindow.removeEventListener("TabSelect", dontSelectHiddenTab, false);
		setTimeout(function() {
			delete btn._cb_disabled;
		}, 500);
	}
	function notify(msg) {
		Components.classes["@mozilla.org/alerts-service;1"]
			.getService(Components.interfaces.nsIAlertsService)
			.showAlertNotification(
				app == "Firefox" && pv >= 57
					? "chrome://mozapps/skin/extensions/extensionGeneric.svg"
					: "chrome://mozapps/skin/extensions/extensionGeneric.png",
				btn.label,
				msg, false, "", null
			);
	}
}
function dontSelectHiddenTab(e) {
	// <tab /><tab collapsed="true" />
	// Close first tab: collapsed tab becomes selected
	var trgTab = e.originalTarget || e.target;
	if(trgTab != tab)
		return;

	if(/\n(?:BrowserOpenAddonsMgr|toEM)@chrome:\/\//.test(new Error().stack)) {
		// User open Add-ons Manager, show tab
		trgWindow.removeEventListener("TabSelect", dontSelectHiddenTab, false);
		setTimeout(function() { // Hidden tab can't be selected, so select it manually...
			tab.collapsed = tab.closing = false;
			gBrowser.selectedTab = tab;
		}, 0);
	}

	function done(t) {
		if(!t.hidden && !t.closing) {
			e.preventDefault();
			e.stopPropagation();
			return gBrowser.selectedTab = t;
		}
		return false;
	}
	for(var t = tab.nextSibling; t; t = t.nextSibling)
		if(done(t))
			return;
	for(var t = tab.previousSibling; t; t = t.previousSibling)
		if(done(t))
			return;
}
function ProgressIcon(btn) {
	var app = Services.appinfo.name;
	var pv = parseFloat(Services.appinfo.platformVersion);
	if(app == "SeaMonkey")
		this.imgConnecting = this.imgLoading = "chrome://communicator/skin/icons/loading.gif";
	else if(app == "Thunderbird") {
		this.imgConnecting = "chrome://messenger/skin/icons/connecting.png";
		this.imgLoading = "chrome://messenger/skin/icons/loading.png";
	}
	else {
		this.imgConnecting = app == "Firefox" && pv >= 58
			? "chrome://browser/skin/tabbrowser/tab-connecting.png"
			: "chrome://browser/skin/tabbrowser/connecting.png";
		this.imgLoading = app == "Firefox" && pv >= 48
			? "chrome://global/skin/icons/loading.png"
			: "chrome://browser/skin/tabbrowser/loading.png";
	}
	if(!(btn instanceof XULElement)) {
		this.loading = this.restore = function() {};
		return;
	}
	var useAnimation = app == "Firefox" && pv >= 32 && pv < 48;
	var btnIcon = btn.icon
		|| btn.ownerDocument.getAnonymousElementByAttribute(btn, "class", "toolbarbutton-icon");
	var origIcon = btnIcon.src;
	btnIcon.src = this.imgConnecting;
	if(useAnimation) {
		let cs = btnIcon.ownerDocument.defaultView.getComputedStyle(btnIcon, null);
		let s = btnIcon.style;
		s.margin = [cs.marginTop, cs.marginRight, cs.marginBottom, cs.marginLeft].join(" ");
		s.padding = [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].join(" ");
		s.width = cs.width;
		s.height = cs.height;
		s.boxShadow = "none";
		s.borderColor = s.background = "transparent";
		btnIcon.setAttribute("fadein", "true");
		btnIcon.setAttribute("busy", "true");
		btnIcon.classList.add("tab-throbber");
		btnIcon._restore = function() {
			delete btnIcon._restore;
			btnIcon.removeAttribute("busy");
			btnIcon.removeAttribute("progress");
			setTimeout(function() {
				btnIcon.classList.remove("tab-throbber");
				btnIcon.removeAttribute("style");
				btnIcon.removeAttribute("fadein");
			}, 0);
		};
	}
	this.loading = function() {
		btnIcon.src = this.imgLoading;
		if(useAnimation)
			btnIcon.setAttribute("progress", "true");
	};
	this.restore = function() {
		btnIcon.src = origIcon;
		if(useAnimation)
			btnIcon._restore();
	};
}
}).call(this);
//== Check for Addons Updates end
}              

this.tooltipText = "Переключатель джетпаков" 
                   + "\n\nУправление:\nЛКМ – открыть меню" 
                   + "\nПКМ – проверить обновления"
                   + "\nСКМ – открыть страницу дополнений"
                   + "\nShift+ПКМ – меню кнопки"
                   + "\n\nВ меню: \nЛКМ – включить/выключить дополнение без закрытия меню"
                   + "\nShift+ЛКМ – включить/выключить дополнение"   
                   + "\nСКМ – открыть страницу дополнения в управлении дополнениями"                    
                   + "\nПКМ – открыть настройки дополнения (если есть)";     
// Autoopen/close feature
var openDelay = 200;
var closeDelay = 350;

var _openTimer = 0;
var _closeTimer = 0;
this.onmouseover = function(e) {
	clearTimeout(_closeTimer);
	if(e.target == this && closeOtherMenus()) {
		this.open = true;
		return;
	}
	_openTimer = setTimeout(function() {
		self.open = true;
	}, openDelay);
};
this.onmouseout = function(e) {
	clearTimeout(_openTimer);
	_closeTimer = setTimeout(function() {
		if(!isContextOpened())
			self.open = false;
	}, closeDelay);
};
function closeOtherMenus() {
	return Array.prototype.some.call(
		self.parentNode.getElementsByTagName("*"),
		function(node) {
			if(
				node != self
				&& node.namespaceURI == xulns
				// See https://github.com/Infocatcher/Custom_Buttons/issues/28
				//&& node.boxObject
				//&& node.boxObject instanceof Components.interfaces.nsIMenuBoxObject
				&& "open" in node
				&& node.open
				&& node.getElementsByTagName("menupopup").length
			) {
				node.open = false;
				return true;
			}
			return false;
		}
	);
}
function isContextOpened() {
	return inBtn(document.popupNode);
}
function inBtn(node) {
	for(; node; node = node.parentNode)
		if(node == self)
			return true;
	return false;
}

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

Выделить код

Код:

try {CustomizableUI.createWidget({
	label: "Дополнения",
	id: "ucf-cbbtn-ToggleRestartlessAddons",
	localized: false,
	get initCode() {
		this.event = Object.create(null);
		delete this.initCode;
		return this.initCode = Cu.readUTF8URI(Services.io.newURI(
			"chrome://user_chrome_files/content/custom_scripts/custom_script/toggleRestartlessAddons.js"
		));
	},
	onCreated(btn) {
		btn.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAt0lEQVR42mP4//8/AzK269D+zxDPAMYgNro8OsYUsGf4L5IrAMYgNskGPHrzkAHmAhCbZAPAglFAA6II2w5WC/ZzGEQDDMO9gCQGUmNYr4dhKANPBgdcAyEMMgSrAWA/R0H8jdcALN5iQHYWLABJMgBbABJjACiKweFHigHwgIW6Eux1UryA1QD0QISzsQQqVi+gKEBLfeguwhqIBEM5ihQD4rEoQHaBLwEDQOGBHKggNiyl4sreAEpLK4gRQN5TAAAAAElFTkSuQmCC");
		new btn.ownerGlobal.Function("self,event,_phase", this.initCode)
			.call(btn, btn, this.event, "init");
	}
});} catch(ex) {Cu.reportError(ex);}

[firefox] 95.0

Отредактировано egorsemenov06 (06-12-2021 17:36:51)

Отсутствует

 

№14606-12-2021 18:57:00

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

Re: UCF-скрипты на этом форуме

egorsemenov06 пишет

JSON.parse

/*JSON.parse*/

Отсутствует

 

№14706-12-2021 19:08:31

egorsemenov06
Участник
 
Группа: Members
Зарегистрирован: 12-06-2018
Сообщений: 260
UA: Firefox 95.0

Re: UCF-скрипты на этом форуме

Dumby пишет
egorsemenov06 пишет

JSON.parse

/*JSON.parse*/

Огромное Спасибо!!!!!

Отсутствует

 

№148Вчера 20:37:15

sachka
Участник
 
Группа: Members
Зарегистрирован: 11-04-2018
Сообщений: 21
UA: Firefox 95.0

Re: UCF-скрипты на этом форуме

воможно ли с помощью скрипта https://forum.mozilla-russia.org/viewto … 54#p782454 открывать ссылки и страницы в tor browser? после последнего обновления тор открывается через скрипт, но соединения нет

На форуме

 

Board footer

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