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

Хотите узнать больше о расширениях? Посмотрите ролики, рассказывающие о работе с расширениями Firefox.

№10107-10-2021 15:15:32

Vitaliy V.
Участник
 
Группа: Members
Зарегистрирован: 19-09-2014
Сообщений: 2027
UA: Firefox 94.0

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

kokoss пишет

куда...?

там ниже функция keydown(e) {...
для комбинаций клавиш Ctrl + F
перед gFindBar.close(); добавить gFindBar.clear();

Отредактировано Vitaliy V. (07-10-2021 15:17:46)

Отсутствует

 

№10207-10-2021 15:51:28

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

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

Vitaliy V. пишет

там ниже функция keydown(e) {...
для комбинаций клавиш Ctrl + F
перед gFindBar.close(); добавить gFindBar.clear();

Я так понимаю что если для этого не использую клави..., то можно удалить этот код:

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

Выделить код

Код:

keydown(e) {
                if (e.ctrlKey && e.code == "KeyF" && !e.altKey && !e.shiftKey) {
                    if (this.timer != null) {
                        e.preventDefault();
                        return;
                    }
                    this.timer = setTimeout(() => {
                        this.timer = null;
                    }, 1000);
                    if (window.gFindBarInitialized && !gFindBar.hidden) {
                        e.preventDefault();
			gFindBar.clear();
                        gFindBar.close();
                    }
                }
            },


Win7

Отсутствует

 

№10307-10-2021 16:01:06

Vitaliy V.
Участник
 
Группа: Members
Зарегистрирован: 19-09-2014
Сообщений: 2027
UA: Firefox 94.0

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

kokoss пишет

если для этого не использую клави..., то можно удалить этот код:

Нет, один этот код удалять нельзя, только вместе со слушателем, и удалять код не обязательно, достаточно отключить слушатель.
// window.addEventListener("keydown", this, true);
// window.removeEventListener("keydown", this, true);

Отсутствует

 

№10407-10-2021 16:05:42

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

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

Vitaliy V. пишет

Нет, один этот код удалять нельзя.

Я это уже понял...


Vitaliy V. пишет

и удалять код не обязательно, достаточно отключить слушатель.
// window.addEventListener("keydown", this, true);
// window.removeEventListener("keydown", this, true);

Спасибо!

Отредактировано kokoss (07-10-2021 16:09:18)


Win7

Отсутствует

 

№10510-10-2021 13:54:23

Inko7
Участник
 
Группа: Members
Зарегистрирован: 09-11-2009
Сообщений: 957
UA: Firefox 93.0

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

Скачал комплект UserChromeFiles - 2021-9-23. Обновил/перезаписал все файлы по нужным папкам.
Скрипты подключились и заработали сразу, а вот стили ни один не подключился.
Ранее в файле custom_style_user.css было прописано следующее содержимое и всё работало:

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

Выделить код

Код:

/* Этот файл для правил CSS с правами USER_SHEET */
/* значки папок закладок желтого цвета */
@import url("./css/Colored-folders-91.css");

/* скрыть элементы меню закладок */
@import url("./css/hide_bookmarks_elements.css");

/* скрыть элементы контекстного меню на странице */
@import url("./css/hide_context_elements.css");

/* убрать history-dropmarker из адресной строки */
@import url("./css/history-dropmarker.css");

/* компактная панель поиска сверху справа */
@import url("./css/findbar_compakt.css");

/* панель быстрого поиска такая хе, как и полного поиска */
@import url("./css/findbar_show_full_quickfindbar.css");

/* в приложении GISMETEO - белый шрифт значка погоды */
@import url("./css/gismeteo.css");

/* изменение высоты панели вкладок, компактное меню (Proton) */
@import url("./css/bar_compact_proton.css");


теперь появились три файла: custom_styles_all_user.css , custom_styles_chrome_user.css , custom_styles_content_user.css
пробовал помещать код в них - безрезультатно
Не пойму куда вообще копать?

Отсутствует

 

№10610-10-2021 14:21:38

Vitaliy V.
Участник
 
Группа: Members
Зарегистрирован: 19-09-2014
Сообщений: 2027
UA: Firefox 94.0

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

Inko7
Так все стили по умолчанию отключены в CustomStylesScripts.jsm (зачем включать пустые файлы, они там только для примера)
можете изменить это в styleschrome (стили подключенные в styleschrome работают там же где и userChrome.css)
ну или в stylesall: [ // Для всех документов
И вместо @import можно просто подключить ваши файлы там же в CustomStylesScripts.jsm, например
{ path: "css/Colored-folders-91.css", type: "USER_SHEET", sheet(f) { preloadSheet(this, f); }, },

Отредактировано Vitaliy V. (10-10-2021 14:44:28)

Отсутствует

 

№10710-10-2021 16:28:09

Inko7
Участник
 
Группа: Members
Зарегистрирован: 09-11-2009
Сообщений: 957
UA: Firefox 93.0

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

Vitaliy V.
вот теперь все стили заработали, спасибо!

получается, раз скрипты заработали сразу, то файлы custom_script.js / custom_script_all_win.js / custom_script_win.js обрабатываются изначально и их прописывать дополнительно не нужно?

Отсутствует

 

№10810-10-2021 17:20:57

Vitaliy V.
Участник
 
Группа: Members
Зарегистрирован: 19-09-2014
Сообщений: 2027
UA: Firefox 94.0

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

Inko7 пишет

файлы custom_script.js / custom_script_all_win.js / custom_script_win.js обрабатываются изначально и их прописывать дополнительно не нужно?

custom_script.js добавлен в CustomStylesScripts.jsm, но его можно удалить, переименовать,
а custom_script_all_win.js / custom_script_win.js обрабатываются изначально и их прописывать, удалять, переименовывать нельзя

Отсутствует

 

№10915-10-2021 12:34:54

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

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

Vitaliy V.
Вы можете написать скрипт отключающий отображение пунктов контекстного меню, с определенным в скрипте ID, для разных контекстов. С перечислением исключений для каждого добавленного пользователем ID. Что-то типа:

"#context-copy" this.hidden = gContextMenu.onLink || gContextMenu.onMailtoLink || gContextMenu.onImage || gContextMenu.onCanvas;
"#other-addon" this.hidden = gContextMenu.onTextInput || gContextMenu.isContentSelected;

Например, #context-copy появляется везде при выделенном на странице тексте, но главное здесь расширения, очень часто они добавляют свои пункты без учета контекста.
Второй пример, сепараторы, при переупорядочивании меню некоторые сепараторы надо удалить только для определенных контекстов.
   
И, если будете делать, добавьте примеры, в том числе для контекстов: фрейм, страница, вкладка, адресная строка и textarea (если такой есть отдельно от .onTextInput).
   
ps^ и есть ли контексты в закладках панели - папка, отдельныя закладка? Видел расширение которое добавляло свой пункт и туда и туда, а нужно было только для папок.

Отредактировано _zt (15-10-2021 13:27:40)


Fx 91 esr

Отсутствует

 

№11016-10-2021 00:17:22

Vitaliy V.
Участник
 
Группа: Members
Зарегистрирован: 19-09-2014
Сообщений: 2027
UA: Firefox 94.0

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

_zt
не я пас, не охота это делать, что касается расширений это их проблемы, апи позволяет учитывать контекст, все зависит от разраба расширения.


если не заметили недавно обновил ваши скрипты
https://forum.mozilla-russia.org/viewto … 24#p784824
https://forum.mozilla-russia.org/viewto … 55#p783755

Отсутствует

 

№11116-10-2021 11:23:02

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

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

Vitaliy V.
SidebarTabs обновлял, там беда со сплиттером
2021.1634372082.png
оставил так и выкинул after

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

Выделить код

Код:

#st_splitter {
                -moz-appearance: none !important;
                appearance: none !important;
                background-color: var(--chrome-content-separator-color, rgba(127,127,127,.5)) !important;
                background-clip: content-box !important;
                border-inline: 1px solid transparent !important;
                min-width: 3px !important;
                margin-inline: -1px !important;
                position: relative !important;
                z-index: 2 !important;
                -moz-box-ordinal-group: ${this.ST_RIGHT ? "100" : "0"} !important;
                -moz-box-orient: vertical !important;

2021.1634372264.png
   
Тултипы сейчас обновил.
Я тут подумал, что отображение заголовка перед адресом более удобно, как их местами поменять? У меня не получается, без заголовка остается пустая строка перед адресом.
   
ps^ А как вы svg преобразовываете, например вот это как в скрипт засунуть?
скрытый текст

<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" width="16" height="16"><path d="M4 1C1.804688 1 0 2.800781 0 5L0 15C0 17.195313 1.804688 19 4 19L6.3125 19L8.0625 23.375C8.207031 23.765625 8.582031 24.027344 9 24.027344C9.417969 24.027344 9.792969 23.765625 9.9375 23.375L11.6875 19L22 19C24.195313 19 26 17.195313 26 15L26 5C26 2.800781 24.195313 1 22 1 Z M 4 3L22 3C23.117188 3 24 3.882813 24 5L24 15C24 16.113281 23.113281 17 22 17L11 17C10.589844 16.996094 10.214844 17.242188 10.0625 17.625L9 20.28125L7.9375 17.625C7.785156 17.242188 7.410156 16.996094 7 17L4 17C2.886719 17 2 16.113281 2 15L2 5C2 3.882813 2.882813 3 4 3Z" fill="#D0D0D0" /></svg>

Отредактировано _zt (16-10-2021 11:38:37)


Fx 91 esr

Отсутствует

 

№11216-10-2021 14:02:03

Vitaliy V.
Участник
 
Группа: Members
Зарегистрирован: 19-09-2014
Сообщений: 2027
UA: Firefox 94.0

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

_zt пишет

оставил так и выкинул after

надо только добавил
border: none !important;
background: none !important;

_zt пишет

отображение заголовка перед адресом более удобно, как их местами поменять?

// el.title = title = `${href}${title === "" ? "" : `\nTitle: ${title}`}`;
el.title = title = `${title === "" ? "" : `Title: ${title}\nUrl: `}${href}`;
Но сначала обновите все полностью, я там изменил немного

Отредактировано Vitaliy V. (16-10-2021 14:20:10)

Отсутствует

 

№11316-10-2021 15:26:10

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

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

Vitaliy V.
Теперь нормально. В своем svg заменил fill= на style= из вашего и все заработало.


Fx 91 esr

Отсутствует

 

№11401-11-2021 18:56:05

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

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

Dumby Не могли бы пожалуйста поправить вот этот скрипт для [firefox] 94.0

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

Выделить код

Код:

(async id => ({

	delay: 2e3,

	get limit() {
		var mb = 1024 * 1024;
		delete this.limit;
		return this.limit = this.notMulti
			? 770 * mb // not multiprocess, 770MB
			: 1.50 * 1024 * mb; // multiprocess, 1.50GB
	},
	xul: `
		<hbox
			id="${id}"
			value="ram"
			tooltiptext="ЛКМ: Очистить Память"
		>
			<label id="${id += "-label"}"/>
		</hbox>
	`,
	css: `
		min-height: 17px !important;
		height: 17px !important;
		border-radius: 3px !important;
		padding: 2px 5px 0px !important;
		font-family: Tahoma !important;
		color: SaddleBrown !important;
		font-size: 15px !important;
		margin-bottom: 2px !important;
		margin-right: 1px !important;
		margin-left: 3px !important;
	`,
	launch() {
		var file = Services.dirsvc.get("ProfD", Ci.nsIFile);
		["memreduct", "start.vbs"].forEach(file.append);
		(this.launch = file.launch)();
	},
	val: "",
	init(topic, mm) {
		Services.obs.addObserver(mm = this, topic);
		Services.obs.addObserver(function quit(s, t) {
			this.timer?.cancel();
			Services.obs.removeObserver(mm, topic);
			Services.obs.removeObserver(quit, t);
		}, "quit-application-granted");
	},
	observe(win) {
		var df = win.MozXULElement.parseXULToFragment(this.xul);
		this.click = e => e.button || this.launch();
		this.notMulti = !Services.appinfo.browserTabsRemoteAutostart;
		this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

		(this.observe = async win => {
			this.timer.cancel();
			await new Promise(win.requestAnimationFrame);
			var clone = win.document.importNode(df, true);
			var hbox = clone.firstChild;
			win.document.getElementById("page-action-buttons").append(clone);
			hbox.onclick = this.click;
			hbox.style.cssText = this.css;
			hbox.firstChild.style.setProperty("margin", "0", "important");
			this.notify();
		})(win);
	},
	async notify() {
		var info = await ChromeUtils.requestProcInfo();
		var bytes = info.residentSetSize;
		for(var child of info.children) bytes += child.residentUniqueSize;
		this.timer.initWithCallback(this, this.delay, this.timer.TYPE_ONE_SHOT);

		var prev = this.val;
		if ((this.val = this.mgb(bytes)) != prev)
			for(var win of CustomizableUI.windows)
				win.document.getElementById(id).value = this.val;

		//this.notMulti &&
			bytes > this.limit && this.launch();
	},
	mgb: bytes => bytes < 1073741824
		? String(Math.round(bytes / 1048576))
		: (bytes / 1073741824).toFixed(2)
}).init("browser-delayed-startup-finished"))("ucf-mem-indicator");

Отсутствует

 

№11503-11-2021 20:41:55

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

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

egorsemenov06 пишет

поправить вот этот скрипт для [firefox] 94.0

Заменить residentSetSize и residentUniqueSize на memory


Dobrov пишет

как добавить двойной клик на UCF-кнопку, чтобы вместе с ним не срабатывало событие "click" ?

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

Выделить код

Код:

try {	CustomizableUI.createWidget({
		id: "add-additional-personaltoolbar-button", type: "custom", localized: false, label:
"Панели, Папки", tooltiptext:
`ЛКМ:	★ Закладки\n…+ Alt	Домашняя папка
ПКМ:	⟳ История\n…+ Alt	Папка установки
СКМ:	Папка профиля\n…+ Alt	user_chrome_files`,
		onBuild(doc) {
			var trbn = doc.createXULElement("toolbarbutton");
			trbn.id = this.id;
			trbn.tooltipText = this.tooltiptext;
			trbn.label = this.label;
			trbn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
			trbn.setAttribute("context", false);
			trbn.style.setProperty("list-style-image", `url("chrome://user_chrome_files/content/vertical_top_bottom_bar/svg/bookmark-16.svg")`, "important");
			trbn.addEventListener("dblclick", function(e) {
				e.view.alert("DBL Click");
			}, false);
			trbn.explorer =(dir, subdir = undefined)=> {
				var dirs = Services.dirsvc.get(dir, Ci.nsIFile);
				if (subdir) dirs.append(subdir);
				if (dirs.exists()) dirs.launch();
			};
			trbn.bar =(bar)=> {
				var win = Services.wm.getMostRecentWindow("navigator:browser");
				if ("SidebarUI" in win)
					win.SidebarUI.toggle(bar);
				else if ("toggleSidebar" in win)
					win.toggleSidebar(bar);
			};
			trbn.addEventListener("click", function(e) {
				if (e.button == 0)
					e.altKey ? trbn.explorer("Home") : trbn.bar("viewBookmarksSidebar")
				else if (e.button == 1)
					e.altKey ? trbn.explorer("UChrm", "user_chrome_files") : trbn.explorer("ProfD")
				else if (e.button == 2)
					e.altKey ? trbn.explorer("GreD") : trbn.bar("viewHistorySidebar")
			}, false);
			return trbn;
		},
	});
} catch(e) {}

С помощью таймаута наверно, как же ещё.

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

Выделить код

Код:

(async (bar, exp, tid, self) => CustomizableUI.createWidget(self = {
	label: "Панели, Папки",
	tooltiptext: [
		"ЛКМ:	★ Закладки\n…+ Alt	Домашняя папка",
		"ПКМ:	⟳ История\n…+ Alt	Папка установки",
		"СКМ:	Папка профиля\n…+ Alt	user_chrome_files"
	].join("\n"),
	id: "add-additional-personaltoolbar-button",
	localized: false,
	onCreated(btn) {
		btn.onclick = this.click;
		btn.style.setProperty("list-style-image", "url(chrome://user_chrome_files/content/vertical_top_bottom_bar/svg/bookmark-16.svg)", "important");
	},
	exec(num, win) {
		tid = null;
		self[num](win);
	},
	context: win => win.document.getElementById(self.id)
		.dispatchEvent(new win.MouseEvent("contextmenu", self.a)),
	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];
		}
		n2 || e.preventDefault();

		var win = e.view;
		if (dbl) tid &&= win.clearTimeout(tid), self[num](win);
		else tid = win.setTimeout(self.exec, 300, num, win);
	},
	0:   w => bar(w, "viewBookmarksSidebar"), // ЛКМ
	2:  () => exp("Home"), // Alt+ЛКМ
	16: () => exp("ProfD"), // СКМ
	18: () => exp("UChrm", "user_chrome_files"), // Alt+СКМ
	32:  w => bar(w, "viewHistorySidebar"), // ПКМ
	34: () => exp("GreD"), // Alt+ПКМ

	1(win) { // Double Left Click
		win.alert("DBL Click");
	},
	33(win) { // Double Right Click
		win.alert("DBL Right Click");
	},
	29(win) { // Ctrl + Shift + Double Middle Click
		win.alert("Ctrl + Shift + DBL Middle Click");
	},
}))(
	(win, bar) => win.SidebarUI.toggle(bar),
	(dir, sub) => {
		dir = Services.dirsvc.get(dir, Ci.nsIFile);
		sub && dir.append(sub);
		dir.exists() && dir.launch();
	}
);

Отсутствует

 

№11603-11-2021 20:57:33

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

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

Dumby пишет

Заменить residentSetSize и residentUniqueSize на memory

Большое Вам Спасибо!!!

Отсутствует

 

№11705-11-2021 13:10:05

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

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

Vitaliy V.
Подскажите пожалуйста, как в этом скрипте запретить AutoPopup на некоторых кнопках? И спасибо за скрипт!


Add, вроде получилось что хотел, удалил из скрипта часть кода: this.ExtensionParent.WebExtensionPolicy.getByID(id).extension . Правильно ли я это сделал? Нужно было отключить AutoPopup значков расширений.


Add, и ешё вопрос, для чего нужен этот скрипт?

Отредактировано kokoss (12-11-2021 09:54:23)


Win7

Отсутствует

 

№11806-11-2021 01:35:56

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

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

Dumby - Спасибо, скрипт очень компактный и крутой, но в двойной клик на UCF-кнопке правый клик мыши работает неверно.
Вместе с правым кликом в MacOS и вероятно Linux открывается контекстное меню панели, пробовал отключить и не получилось:

Выделить код

Код:

var reset = e => e.target.linkedObject = this;
		var id, lo = {click: e => n2 || reset(e)};
		var lin = /macos|linux/.test(e.view.AppConstants.platform);
		var stop = e => reset(e) && e.preventDefault();
		lo.contextmenu = lin
			? e => e.ctrlKey || e.shiftKey ? 0 : stop(e) : stop;
		n2 || reset(e) && e.preventDefault();
……

Dumby - ещё просьба по твоему скрипту перехвата кликов на кнопках > добавить Двойной клик  (у меня не удалось):
Конкретно - добавить двойной клик на "PanelUI-menu-button" (строка 133) и по возможности на "downloads-button"…


Второе (если это возможно) > изменить способ перехвата кнопок со стандартного addEventListener на такой же «продвинутый», как и в новом скрипте,
то есть способом: var num = 16 * e.button + 8 * e.ctrlKey + 4 * e.shiftKey + 2 * e.altKey + dbl;

ucf_hookClicks.js (скрипт перехвата кликов на downloads-button, PanelUI-menu)

Выделить код

Код:

(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.tooltipText = GetDynamicShortcutTooltipText(btn.id) +`

ПКМ:	Сохранить как единый html
	всё | выделенное на странице
…+ Shift	Обзор папки [Загрузки]\n
Ролик:	Сохранить как файл .txt
…+ Shift	Сайт: графика Вкл/Выкл\n
Колёсико на рисунке: ➜ Сохранить
Двойной клик: найти Похожие фото`,
																PanelUI_help =
`Браузер Firefox, версия ${Services.appinfo.platformVersion}\n
Колёсико:	Развернуть | окно
…+ Alt		Полный экран
Правый клик	⇲ Свернуть
…+ Shift		Закрыть ✕
…+ Alt		Персонализация`;

	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

	listener = e => { var trg = e.target; // Downloads Clicks
		if (e.button == 1) {
			if (e.shiftKey) { // СКМ + Shift
				if ( prefs.getIntPref("permissions.default.image", 1) == 1)
					prefs.setIntPref("permissions.default.image", 2), trg.style.filter = "hue-rotate(180deg) brightness(95%)"
				else
					prefs.setIntPref("permissions.default.image", 1), trg.style.filter = "";
				BrowserReload();
			} else	// СКМ Click
				saveSelectionToTxt(); // сохранить .txt
		} else if (e.button == 2) {
			if (e.shiftKey)
				Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
			else	// ПКМ Click
				save(); // Single HTML
		}
	},
	listener_pui = e => { // PanelUI-menu Clicks
		if (e.button == 1) {
			if (e.altKey)
				window.BrowserFullScreen()
			else
			if( window.windowState != window.STATE_MAXIMIZED )
				window.maximize()
			else
				window.restore();
		} else
			if (e.button == 2) if (e.altKey) return
		else {
			e.stopPropagation();
			(e.shiftKey) ? window.close() : window.minimize();
		}
	}, // end Clicks
	keydown_win = e => { // нажатие клавиш
		if (!(e.keyCode == 83 && e.shiftKey && e.altKey)) return;
		var singlesave = document.getElementById(save_ex); // SingleSave
		singlesave ? singlesave.click() : save(); // имитировать клик по кнопке, используя её ID
	},
	{prefs, dirsvc} = Services, tmax = btn.tooltipText.split("\n")[0].length, save_ex = "_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action";
	btn.setAttribute("context", "event.stopPropagation()");
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	(async () => { // SingleSave - дополнить подсказку
		setTimeout((but = document.getElementById(save_ex))=> {
			if (but)
				btn.tooltipText = btn.tooltipText + '\n\nAlt⇧S	нажатие SingleSave';
			if (!/Закрыть/.test(pui.tooltipText))
				pui.tooltipText = PanelUI_help;
		}, 9000); // после запуска ждать от 3 сек
	})();

	btn.addEventListener("click", listener), pui.addEventListener("click", listener_pui);
		window.addEventListener("keydown", keydown_win);
	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		btn.removeEventListener("click", listener), pui.removeEventListener("click", listener_pui);
		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

В скрипт Quick Toggle about:config тоже добавить Двойной клик на кнопке (в коде есть ещё Долгое нажатие)

ucf_QuickToggle.js (скрипт из моего профиля FF в шапке темы)

Выделить код

Код:

// Quick Toggle Быстрое переключение параметров about:config для custom_script.js

(async (name, id, func) => { // https://forum.mozilla-russia.org/viewtopic.php?pid=789824#p789824
	if (name == "Object") return CustomizableUI.createWidget(func());
	var win = name == "Window", g = Cu.import("resource://gre/modules/Services.jsm", {});
	if (g[id]) {if (win) return;} else g[id] = func(); if (win) return CustomizableUI.createWidget(g[id]);
	addDestructor(r => r[5] == "e" && delete g[id]); g[id].onCreated(this);	// BEGIN QuickToggle…
})(this.constructor.name, "ToggleAboutConfig", () => { var help =

`клик+Alt	Библиотека закладок
…+ Shift	± zoom Текст/страница
։нажать	Антизапрет ⇆ proxy\n
ПКМ	Меню быстрых настроек
։нажать	© Краткая справка
…+ Alt	Опции about:config\n
СКМ	Вкладка «Топ сайтов»
…+ Alt	Восстановить вкладку
…+ Shift	● Захват цвета, Zoom
։нажать	Консоль браузера\n
⟳ Обновить ↯ Перезапуск
ЛкМ: Левый клик, СкМ: Колёсико
нажать: долгий клик мыши ≥1 сек`, // ЛКМ+Alt+Shift: настройки User Chrome Files. свободные hotkeys: СКМ+Alt ЛКМ+Alt+⇧ Нет в подсказке: ЛКМ	панель Журнала, СКМ+Shift – Пипетка

help_ucf = ['chrome://user_chrome_files/content/help.html', 'http://forum.puppyrus.org/index.php?topic=22762'],

// Ctrl+Click или правый клик - сброс параметра по-умолчанию
// клик по параметру с Shift блокирует авто-закрытие меню
// строки с userAlt имеют шрифт italic
//	refresh: false - reload current tab,	true - reload current tab skip cache
//	restart: false - restart browser,		true - restart browser with confirm
// Разделитель: Имя меню "—,⟳,↯" Опция, ⟳ обновить страницу, ↯ перезапуск браузера
// иконки равны ключам: userChoice:зелёный, userAlt:жёлтый, userPro:серый, нет userChoice:серый, ни один:красный

// пути сохранения Html/Pics в [Загрузки]/dir/subdir заданы в ucf_save.dirs="_Html dir|subdir|_Pics dir|subdir" : пусто|0 заголовок|1 домен
// ucf_save.dirs="_Web||_Pics|1" save Html: [Загрузки]/_Web/имя страницы.html, save Pic: [Загрузки]/_Pics/имя вкладки/имя картинки

	{prefs, dirsvc} = Services, db = prefs.getDefaultBranch(""), my_vpn = "https://antizapret.prostovpn.org/proxy.pac", icon_vpn = "hue-rotate(270deg) brightness(95%)", menuactive = (AppConstants.platform == "macosx") ? '#e8e8e8' : '#124', // текст, подсвеченный курсором
	xul_ns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
	fonts = ["Arial","Cantarell","DejaVu Sans","Roboto","PT Serif","Segoe UI","Ubuntu","Cambria","Fira Sans","Georgia","Noto Sans","Calibri","Times","системный"],
	font_pref = (font) => { return font.map(function(name) { // массив с вложениями
		return (name == font[font.length -1]) ? ["", name,,, `prefs.setIntPref("browser.display.use_document_fonts", 1)`] : [name, name,,, `prefs.setIntPref("browser.display.use_document_fonts", 0)`]; });
	},
	fontserif = font_pref(fonts), fontsans = [["PT Sans","PT Sans"], ...fontserif], dw; // путь загрузки
	try {dw = prefs.getComplexValue("browser.download.dir",Ci.nsIFile);} catch {dw = dirsvc.get("DfltDwnld", Ci.nsIFile);};

	var secondary = [{ // pref … [apref, lab, akey, hint, js-code]
			pref: ["dom.disable_open_during_load", "Всплывающие окна"], userChoice: 2, userAlt: true, values: [[true, "Блокировать"], [false, "Разрешить"]]
	},{
			pref: ["browser.safebrowsing.downloads.remote.block_dangerous", "Опасные файлы, сайты",,"browser.safebrowsing.downloads.remote.block_dangerous_host"], userChoice: true, userAlt: false,
			values: [[true, "Отключить",,,`prefs.setBoolPref('browser.safebrowsing.downloads.remote.block_dangerous_host',true)`], [false, "Загружать",,,`prefs.setBoolPref('browser.safebrowsing.downloads.remote.block_dangerous_host',false)`]]
	},{
			pref: ["permissions.default.image", "Загрузка графики"], userChoice: 1, userAlt: 3, refresh: true,
			values: [[1, "Разрешена"], [3, "Только с сайта"], [2, "Отключить"]]
	},{
			pref: ["ucf_save.dirs", `Сайт|Графика`,,`\nПути сохранения страниц | графики\n[Загрузки] — папка по-умолчанию${dw ? ":\n"+ dw.path : ""}`], userChoice: "_Сайты||_Фото|1", userAlt: "", userPro: "_Web||_Images|1",
			values: [
				["", "папка [Загрузки]"], // subdir: пусто | 0 заголовок | 1 домен
				[`_Сайты||_Фото|1`, "_Сайты|_Фото/имя…"], // _Web/host|_Pics/title
				[`_Сайты||_Рисунки|1`, "_Сайты|_Рисунки/…"],
				[`_Web||_Images|`, "_Web|_Images"],
				[`_Web||_Images|1`, "_Web|_Images/имя"],
				[`_Web||_Photo|1`, "_Web|_Photo/имя"],
				[`_Web|1|_Pics|0`, "_Web/сайт|_Pics/…"],
				[`_Web|1|_Pics|`, "_Web/сайт|_Pics"],
				[`_Web|1|_Images|0`, "ввести свои пути"]] // здесь нужно открыть about:config
	},null,{
			pref: ["network.cookie.cookieBehavior", "Получать куки",, "\nПерсональные настройки посещённых сайтов"], userChoice: 3, userAlt: 0, userPro: 4, refresh: false,
			values: [[0, "со всех сайтов"], [3, "кроме не посещённых"], [4, "кроме трекеров"], [1, "кроме сторонних"], [2, "никогда"]]
	},{
			pref: ["network.proxy.autoconfig_url", "Прокси (VPN) URL", "п"],
			userChoice: my_vpn, userAlt: "127.0.0.1", userPro: "", refresh: true,
			values: [
				["", "сброшен", ""],
				[my_vpn, "АнтиЗапрет", "1", "\nНадёжный доступ на заблокированные сайты\n«Режим прокси» меняется на 2", `prefs.setIntPref('network.proxy.type', 2); node.parentNode.parentNode.style.filter = icon_vpn;`],
				["https://git.io/ac-anticensority-pac", "ac-anticensority", "2"],
				// ["localhost", "Tor Browser", "4", "Только для Linux, MacOS\nУстановите сервис: «tor»"],
				[prefs.getStringPref("user.pacfile", "file:///etc/proxy.pac"), "user .pac файл", "3"], // нужен диалог выбора pac-файла
				["127.0.0.1", "local host", "0",, `prefs.setIntPref('network.proxy.type', 0); node.parentNode.parentNode.style.filter = '';`]]
	},{
			pref: ["network.proxy.type", "Режим прокси", "р"], userChoice: 0, userAlt: 2, refresh: true,
			values: [
				[0, "Без прокси", "0", "по-умолчанию"],
				[5, "Системные (из IE)", "5"],
				[2, "Автонастройка", "2", "about:config - user.pacfile"],
				[1, "Ручная настройка", "1", "Используется network.proxy.autoconfig_url"],
				[4, "Автоопределение", "4"] ]
	},{
	// 		pref: ["network.proxy.share_proxy_settings", "Все протоколы через прокси"], userAlt: true, refresh: true,
	// 		values: [[true, "Да", "", "Прокси для всех протоколов при ручной настройке"], [false, "Нет"]]
	// },{
			pref: ["network.trr.mode", "DNS поверх HTTPS",, "\nШифрование DNS-трафика для\nзащиты персональных данных"], userChoice: 1, userAlt: 2, userPro: 5, refresh: true,
			values: [
				[0, "по-умолчанию", "0"], [1, "автоматически", "1", "используется DNS или DoH, в зависимости от того, что быстрее"], [2, "DoH, затем DNS", "2"], [3, "только DoH", "3"], [4, "DNS и DoH", "4"], [5, "отключить DoH", "5"] ]
	},null,{
			pref: ["browser.zoom.full", "Масштабировать"], userChoice: false, userAlt: true,
			values: [[true, "всю страницу"], [false, "только текст"]]
	},{
			pref: ["font.name.sans-serif.x-cyrillic", "Шрифт без засечек ",,"\nТакже влияет на всплывающие подсказки\nСистемный: загрузка шрифтов документа"], userAlt: "", values: fontsans
	},{
			pref: ["font.name.serif.x-cyrillic", "Шрифт с засечками"], userAlt: "", values: fontserif
	},{
			pref: ["image.animation_mode", "Анимация изображений"], userChoice: "none", userAlt: "normal", refresh: true,
			values: [["none", "Выключена"], ["normal", "По циклу"], ["once", "Единожды"]]
	},null,{
			pref: ["media.autoplay.default", "Авто-play аудио/видео"], userChoice: 0, userAlt: 2, userPro: 5, refresh: true,
			values: [
				[0, "Разрешить", "0"], [2, "Спрашивать", "2"], [1, "Запретить", "1"], [5, "Блокировать", "5"]]
	},{
			pref: ["media.autoplay.blocking_policy", "Автозапуск (политика)"], userChoice: 1, userAlt: 2, refresh: true,
			values: [[1, "Временная", "1"], [2, "По действию", "2"], [0, "Постоянная", "0"]]
	},{
			pref: ["gfx.webrender.all", "Аппаратное ускорение графики"], userChoice: true, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["gfx.webrender.force-disabled", "Web render disabled", , "gfx.webrender.compositor.force-enabled\n\nАппаратная отрисовка страниц видеокартой.\nотключите при разных проблемах с графикой"],
			userChoice: false, restart: true, values: [
			[true, "Да",,, `prefs.setBoolPref("gfx.webrender.compositor.force-enabled", false)`],
			[false, "Нет",,, `prefs.setBoolPref("gfx.webrender.compositor.force-enabled", true)`]]
	},null,{
			pref: ["network.http.sendRefererHeader", "Referer: для чего"], userChoice: 2, userAlt: 1,
			values: [[0, "Ни для чего", "0"], [1, "Только ссылки", "1"], [2, "Ссылки, графика", "2"]]
	},{
			pref: ["dom.storage.enabled", "Локальное хранилище",, "\nСохранение персональных данных, по\nкоторым вас можно идентифицировать"],
			userChoice: false, userAlt: true,
			values: [[true, "Разрешить"], [false, "Запретить"]]
	},{
			pref: ["privacy.resistFingerprinting", "Изоляция Firstparty-Fingerprint", ,"privacy.firstparty.isolate\n\nЗащита данных пользователя также\nзапрещает запоминать размер окна"], userChoice: false,
			values: [[true, "Да", , "Защита от слежки",`prefs.setBoolPref('privacy.firstparty.isolate', true);`], [false, "Нет", , "Защита от слежки",`prefs.setBoolPref('privacy.firstparty.isolate', false);`]]
	},{
			pref: ["media.peerconnection.enabled", "WebRTC ваш реальный IP"], userChoice: false,
			values: [[true, "Выдать"], [false, "Скрыть"]]
	},null,{
			pref: ["browser.tabs.remote.force-enable", "Многопоточный режим вкладок",,"\nПо-умолчанию режим разрешён"], userChoice: null, userAlt: true, userPro: false,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["javascript.enabled", "Выполнять скрипты Java",,"\nПоддержка интерактивных сайтов (и рекламы)"], userChoice: true, refresh: true,
			values: [[true, "Да"], [false, "Нет"]]
	},{
			pref: ["browser.cache.disk.capacity", "Кэш браузера",,"browser.cache.memory.enable\n\ncache.memory.max_entry_size:\nдиск и память: 5120\nтолько память: -1"], userChoice: 1048576, userAlt: 0,
			values: [
			[1048576, "Диск и Память",,, `prefs.setBoolPref("browser.cache.memory.enable", true); prefs.setBoolPref("browser.cache.disk.enable", true); prefs.setIntPref('browser.cache.memory.max_entry_size', 5120)`],
			[0, "только Память",,, 		`prefs.setBoolPref("browser.cache.memory.enable", true); prefs.setBoolPref("browser.cache.disk.enable", false); prefs.setIntPref('browser.cache.memory.max_entry_size', -1)`],
			[2097152, "только Диск",,, `prefs.setBoolPref("browser.cache.memory.enable", false); prefs.setBoolPref("browser.cache.disk.enable", true)`]]
	},{
			pref: ["dom.enable_performance", "Статус загрузки страницы",,"\nПередача данных разрешит определять\nфакт использования прокси-сервера"], userAlt: true
	},{
			pref: ["general.useragent.override", "User Agent",,"\nот user-агент зависит вид сайтов"],
			userChoice: null, userAlt: "Mozilla/5.0 (Android 9; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0", userPro: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:68.0) Gecko/20100101 Firefox/68.0", refresh: true,
			values: [
				(arr => {
					var pref = "general.useragent.override";
					var has = prefs.prefHasUserValue(pref);
					if (has) {
						var val = prefs.getStringPref(pref);
						prefs.clearUserPref(pref);
					}
					var ua = Cc["@mozilla.org/network/protocol;1?name=http"]
						.getService(Ci.nsIHttpProtocolHandler).userAgent; // текущий юзерагент
					has && prefs.setStringPref(pref, val);

					var find = node => node.pref && node.pref.pref == pref;
					var redef = (doc, hint) => {
						var popup = doc.getElementById("ToggleAboutConfig-secondaryPopup");
						var menuitem = Array.from(popup.children).find(find).menupopup.firstChild;
						menuitem.tooltipText = hint ? ua + "\n" + hint : ua;
						menuitem.setAttribute("oncommand",
							`event.stopPropagation();
							this.closest("toolbarbutton").linkedObject.contextmenu({
								preventDefault: Boolean,
								target: this.parentNode.parentNode
							});`
						);
					}
					Object.defineProperty(arr, "0", {enumerable: true, get() {
						if (Components.stack.formattedStack.includes("createRadios")) {
							var win = Services.wm.getMostRecentWindow("navigator:browser");
							win.setTimeout(redef, 0, win.document, this[3]);
						}
						else return "";
					}});
					return arr;
				})([null, "По-умолчанию"]),
				["Mozilla/5.0 (Android 9; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0", "Firefox Android9"],
				["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:68.0) Gecko/20100101 Firefox/68.0", "Firefox 68 MacOSX"],
				["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Safari/537.36", "Chrome61 Win10"],
				["Mozilla/5.0 (Windows NT 6.1; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0", "Firefox 56"],
				["Mozilla/5.0 (X11; Linux x86_64; rv:56.0) Gecko/20100101 Firefox/56.0", "Firefox 56 Linux"],
				["Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", "MSIE 6.0 Windows"],
				["Mozilla/5.0 (Linux; Android 7.0; PLUS Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", "Chrome61 Android7"],
				["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", "Safari 6 MacOSX"],
				["Opera/9.80 (Windows NT 6.2; Win64; x64) Presto/2.12 Version/12.16", "Opera12 W8"],
				["Mozilla/5.0 (Linux; Android 5.1.1; SM-G928X Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36", "Samsung Galaxy S6"],
				["Mozilla/5.0 (PlayStation 4 3.11) AppleWebKit/537.73 (KHTML, like Gecko)", "Playstation 4"],
				["Xbox (Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586", "Xbox One (mobile)"],
				["Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586", "Microsoft Lumia 950"],
				["Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; GT-I8350)", "Windows Phone"],
				["Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", "GoogleBot"]]
	},{
			pref: ["browser.cache.disk.free_space_soft_limit", "Неактивные вкладки",, "\nПри запуске загружаются все вкладки,\nэто может замедлить работу браузера.\nОптимально - Выгружать старые табы."], userChoice: 5121, userAlt: 5120, userPro: 5122,
			values: [
			[5120, "Загружать",,, `prefs.setBoolPref("browser.sessionstore.restore_on_demand", true); prefs.setBoolPref("browser.tabs.unloadOnLowMemory", false)`],
			[5121, "Оптимально",, "Выгружать старые вкладки, экономя память", `prefs.setBoolPref("browser.sessionstore.restore_on_demand", true); prefs.setBoolPref("browser.tabs.unloadOnLowMemory", true)`],
			[5122, "Не загружать",,, `prefs.setBoolPref("browser.sessionstore.restore_on_demand", false); prefs.setBoolPref("browser.tabs.unloadOnLowMemory", true)`]]
	}];

	return {
		label: "Quick Toggle Settings",
		id: "ToggleAboutConfig",
		tooltiptext: help, localized: false,
		image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAADhUlEQVR4AaWUIYzsNhCG91WVelKfqoWrpwMLFxQYGhoGBgYGBgYGGhoGBhoGGhoaBhoaGhqcdIFhf+dGt2n09rpq1ZE+2bHl+eyxd0993yVjNIwxq9F6/WjtaNd5nBnq85g2PMeMo8EwDEUI0Z7+jm/EY2it87ZtuAf1EUqAzZZZcuCxnyOlBFoLkuhnkgdBjBG36YqzP+EaTqgmSckyjrGuK8paWGythVLKfv/9t/O/OkEIC6pZoskCfanQuQYLSTm2/ZQkjSil8Pc8z6iqyp/P58tn2l92wWjGnEuG9gO6uUVtaygnMOQaU+nR+hotjfeugwmaHNv9pJzYOYcQAp+kbVv3cBJrbA45QIQLVLyizRI6t5R8gC0aY+nQZYWK5lT4E3nNn2XaiJX5PAmLXl9fuweBz54X97mCoYR21SwYc8+yjsbrKKGCvAu4TDnne3IO7z2oTP1RwCVKJXGJ+rlHaxu0ocZQahLWaO4lmjtov5eI7yDZFnFqWXQUPL1kFzwaV3GpmqQo+cMlc1li9MBCyacbUnC74MePy3NBiAukFRDLBTd/QWUVUk44BJclx5lMPVYvEJ3e7+CZYN+dSw5TmpiQA1/oPTYip4SV5gANRIloa5Ku/LKeCqhljrHmhLzMSIFYPGLw3N/KCMAAuUYeBUkXLDE9vCIW0K45MbXYoW/aFZdiyxYoM9Y0oiwGaxyBjcAElAHFCETTw40T6tvVXP749eUfBe/v78xdlJaAvNwTGmo1YYiRIME6YosGxWnEaUDQbXFt5adazKO6DEcBX97b29tRAq4tl0XzpZKAkzJFEx31iY1bYuANIBtkW6engl1CRD6JAYrh5Mg90QBRAYsgbkAkksS2VJvT0jXqUj0R7HzOrViCQ1kGILZAUAQl9AxLVl/BDTI24tyK1+/8n/SVYOexVDMlaQAvAHdjOHmQLPS6grxe9ZeviDgKuM/jzIZM38kbbE4ekguGBV7CDwLXy1k/vKJtPf4ODmAPFhbf74mLa5DmFltQwCKJCskIiPNpprTi42+bRcaYTIv3Z/oVhcgpUrIOm1NYbIvZWtjJIliSLgpIFdZZohMvgQRqFyilRimFF0I4+QXik7ZR3vRqqSsRpJSO1jEf/bFXGUsNhAq2uWS63eb0X+NjN/uuforb9UXYXjqEBkkLyMupP85/I/53fDzLsZV6am8zCdR9/C+4MZTbZ2zc/gAAAABJRU5ErkJggg==",
		onCreated(btn) {
			btn.setAttribute("image", this.image);
			var doc = btn.ownerDocument;

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

			if (prefs.getIntPref('network.proxy.type') == 2)
				btn.style.filter = icon_vpn; // btn.style.cssText = "background-image: -moz-linear-gradient(#c0c8c0, #c0c8c0, #c0c8c0) !important";

			btn.linkedObject = this;
			for(var type of ["command", "contextmenu", "mousedown", "auxclick"]) // события
				btn.setAttribute("on" + type, `linkedObject.${type}(event)`);
			this.addSheet(btn);
		},
		addSheet(btn) {
			var cb = Array.isArray(btn._destructors);
			var id = cb ? btn.id : "ToggleAboutConfig";
			var css = `#${id} menu[_moz-menuactive] {
				color: ${menuactive} !important;
			}`;
			var args = [
				"data:text/css;charset=utf-8," + encodeURIComponent(css),
				Ci.nsIDOMWindowUtils.USER_SHEET
			];
			if (cb) var destructor = function() {
				this.removeSheetUsingURIString(...args);
			}
			var add = b => b.ownerGlobal.windowUtils.loadSheetUsingURIString(...args);
			(this.addSheet = !cb ? add : btn => {
				add(btn);
				btn._destructors.push({destructor, context: btn.ownerGlobal.windowUtils});
			})(btn);
		},
		createPopup(doc, btn, name, data) {
			var popup = doc.createElementNS(xul_ns, "menupopup");
			var prop = name + "Popup";
			btn.popups.push(btn[prop] = popup);
			popup.id = this.id + "-" + prop;
			for (var type of ["popupshowing", "click"])
				popup.setAttribute("on" + type, `parentNode.linkedObject.${type}(event)`);
			for(var obj of data) popup.append(this.createElement(doc, obj));
			btn.append(popup);
		},
		map: {b: "Bool", n: "Int", s: "String"},
		createElement(doc, obj) {
			if (!obj) return doc.createElementNS(xul_ns, "menuseparator");
			var pref = doc.ownerGlobal.Object.create(null), node, img, bool;
			for(var [key, val] of Object.entries(obj)) {
				if (key == "pref") {
					var [apref, lab, akey, hint] = val;
					pref.pref = apref; pref.lab = lab || apref;
					if (hint) pref.hint = hint;
				}
				else if (key == "image") img = val, pref.img = true;
				else if (key != "values") pref[key] = val;
				else pref.hasVals = true;
			}
			var type = prefs.getPrefType(pref.pref);
			var str = this.map[type == prefs.PREF_INVALID
				? obj.values ? (typeof obj.values[0][0])[0] : "b"
				: type == prefs.PREF_BOOL ? "b" : type == prefs.PREF_INT ? "n" : "s"
			];
			pref.get = prefs[`get${str}Pref`];
			var map, set = prefs[`set${str}Pref`];
			if (pref.hasVals) {
				for(var [val, , , , code] of obj.values)
					code && (map || (map = new Map())).set(val, code);
				if (map) pref.set = (key, val) => {
					set(key, val);
					map.has(val) && eval(map.get(val)); // выполнить код
				}
			}
			if (!map) pref.set = set;

			node = doc.createElementNS(xul_ns, "menu");
			node.className = "menu-iconic";
			node.setAttribute("closemenu", "none");
			img && node.setAttribute("image", img);
			akey && node.setAttribute("accesskey", akey);
			(node.pref = pref).vals = doc.ownerGlobal.Object.create(null);
			this.createRadios(doc,
				str.startsWith("B") && !pref.hasVals ? [[true, "true"], [false, "false"]] : obj.values,
				node.appendChild(doc.createElementNS(xul_ns, "menupopup"))
			);
			if ("userChoice" in obj) pref.noAlt = !("userAlt" in obj);
			return node;
		},
		createCloseMenusOption(doc, btn) {
			var pn = this.closePref = "ToggleAboutConfig.closeMenus";
			var data = [null, {
				pref: [pn, "Закрывать меню этой кнопки"], values: [[true, "Да"], [false, "Нет"]]
			}];
			var setCloseMenus = (e, trg = e.target) => {
				e.stopPropagation();
				var {pref, val} = trg, updPopup = true, clear;
				switch(e.type) {
					case "command": pref = (trg = trg.closest("menu")).pref; updPopup = false; break;
					case "click": if (e.button) return; break;
					case "contextmenu": e.preventDefault(); clear = pref;
				}
				if (!pref) return;
				if (clear) prefs.clearUserPref(pn);
				else if (!updPopup && val === pref.val) return;
				else pref.set(pn, val !== undefined ? val : !pref.val);
				this.upd(trg);
				updPopup && this.popupshowing(null, trg.querySelector("menupopup"));
			}
			(this.createCloseMenusOption = (doc, btn) => {
				for(var obj of data)
					btn.secondaryPopup.append(this.createElement(doc, obj));
				var m = btn.secondaryPopup.lastChild;
				m.style.cssText = "fill: dimgray !important; list-style-image: url(chrome://browser/skin/menu.svg) !important;";
				m.setAttribute("oncommand", "setCloseMenus(event)");
				m.onclick = m.oncontextmenu = m.setCloseMenus = setCloseMenus;
			})(doc, btn);
		},
		UserImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAe1BMVEUAAAB1dXV1dXV1dXV1dXV1dXV1dXV1dXV8fHyRkZGfn5+oqKipqamqqqqysrKzs7O6urq8vLy9vb3ExMTFxcXHx8fNzc3Ozs7Pz8/Q0NDY2NjZ2dna2tre3t7g4ODi4uLj4+Pk5OTl5eXn5+fs7Ozv7+/w8PDx8fHy8vLK4aRZAAAACHRSTlMAGFiAiNDw+LBTincAAACRSURBVBgZBcFBTsMADAAwJ20pqjgM+P8TgQua2Nok2AAAAiIzdfcgkOv2/ub357yaIF8+jwgz969nS7He9pnumf22hrSse1d1V/W+LlIc3d1V3d1HCPsHkWjj+5FUTVdVTdcQtmMNkXrMdT/Dur0KYYy/8wqx7QugHucE+bIFmPPZArkuS+qqqwmIzNTdAwAA/zI6Uy+Vzu5KAAAAAElFTkSuQmCC", // серый
		UserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABGElEQVR4AWKgPgAQSmbgEsNQFB53GHz43GGcioPF4mCxGCwWi8FiMBgMBoPFYOzOPWf2/cv3Z/3P7Wo2W2VQolIJ59zb/grvlfLnO1lWJ7lmwDn2cEbnY3jciF0XSTUqSfKZRCLP4NB9c9tlyrOE4iXUwMAd2OMZHLiPj2O0uUZcWcQXp3jKsQTAuSeODlxm7grEIRmxZZal2EuhOxjEGR24yNwXqHOe5MQsdtVCq72He+DiIfNQYMxGwJRHhdIzPLs4zwViGw4ypF5MGsiYIBLML/t04D49gja7kz4eyEB6yiQCnhG4zDx/xsa30sUL3RMtgXP5jO9+JApt2L+DZ3C+/o2sbv9l5xsNtYBz7B0HS8qUZSZqAwCSaKMMRS9J3wAAAABJRU5ErkJggg==", // зелёный
		notUserChoiceImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABDElEQVR4Aa2TK3CEQBBEt6JwdxKJRJ48GRkZiURGxqt4dV5FnkQikSdXIpHIweE6+8KKyf+7Va92qqe770v493MOoUw8JGJizcSslV+Fm3NRWLxrZUMnzaOAGY0dng/DXVnK+kdpGhKXFI4CZjR2ePC+edu0W3eSYieNPQEPGjvhwUuG7FXuuK+b691uX4Sw2obBmkHbwIOXjC+4qY61D+d5Bq89g5eML6hpxvQd8JLxBXk5uxsM3p05vmBcJhfmnrkzbmaHl4wv6KfL5IKuZHbFecZLxhecxiEuiwVX4nBhPHjJvP0j7QtZe5TaWmqqdG8wo7HD8+m/MWHxUMluDym0wYzGDs/fHqb/Pk9WVkDcHStz1AAAAABJRU5ErkJggg==", // красный
		UserAltImg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABAklEQVR4Aa2TKVDEQBREx3sRiUQi8Wpl5MqVSCQyMhIZGYlciVy5cmQkEhk5runHNQf3kapX9ev36w97hX9/xk3ozGCiScDMjuyr8va6D+syd9LdIOkgYGZHhvNhed4FyxdSmswsaQ/M7MiEg9uWO65r6aV1MCOFCnZkOLh0ygPjMgXpvjc7i5f50HORHRkOLp3yQNSR131uYZMPFbAjw8GlUx5IWk5kLJwiGeQMOzIcoFMfiEEFz+IZMHsHOW8PRO0dHMyxIEK9w8FtX8IYBwe3gPQeOcel0377Vt1w/XNwcOm8/SJtLcxI70OG8+m3kevxKihNucjMjgznbz+m/34eAEg4sZItNtjUAAAAAElFTkSuQmCC", // жёлтый
		regexpRefresh: /^(?:view-source:)?(?:https?|ftp)/,
		upd(node) {
			var {pref} = node, def = false, user = false, val;
			if (prefs.getPrefType(pref.pref) != prefs.PREF_INVALID) {
				var pn = pref.pref;
				try {val = pref.defVal = db[pref.get.name](pn); def = true}
				catch(ex) {def = false;}
				var user = prefs.prefHasUserValue(pn);
				if (user) try {val = pref.get(pn, undefined);} catch(ex) {}
			}
			if (val == pref.val && def == pref.def && user == pref.user) return;
			pref.val = val; pref.def = def; pref.user = user;
			var exists = def || user;

			var hint = exists ? val : "Эта опция не указана";
			if (hint === "") hint = "[ пустая строка ]";
			hint += "\n" + pref.pref;
			if (pref.hint) hint += "\n" + pref.hint;
			node.tooltipText = hint;

			var img, alt = "userAlt" in pref && val == pref.userAlt, pro = "userPro" in pref && val == pref.userPro;
			if (alt) img = this.UserAltImg;
			if (pro) img = this.UserImg;
			if ("userChoice" in pref)
				if (val == pref.userChoice)
					node.style.removeProperty("color"),
					img = this.UserChoiceImg;
				else {
					node.style.setProperty("color", "#804040", "important");
					if (!alt && !pro) img = this.notUserChoiceImg;
				}
			node.nextSibling && node.setAttribute("image", img || this.UserImg); // серый значок, если нет userChoice
			user
				? node.style.setProperty("font-style", "italic", "important")
				: node.style.removeProperty("font-style");

			var {lab} = pref;
			if (exists && pref.hasVals) {
				if (val in pref.vals) var sfx = pref.vals[val] || val;
				else var sfx = user ? "другое" : "стандарт";
				lab += ` ${"restart" in pref ? "↯-" : "refresh" in pref ? "-⟳" : "—"} ${sfx}`;
			}
			lab = exists ? lab : '['+ lab +']'; // имя = [имя] если преф не существует
			node.setAttribute("label", lab);
		},
		createRadios(doc, vals, popup) {
			for(var arr of vals) {
				if (!arr) {
					popup.append(doc.createElementNS(xul_ns, "menuseparator"));
					continue;
				}
				var [val, lab, key, hint] = arr;
				var menuitem = doc.createElementNS(xul_ns, "menuitem");
				menuitem.setAttribute("type", "radio");
				menuitem.setAttribute("closemenu", "none");
				menuitem.style.setProperty("font-style", "italic", "important"),
				menuitem.setAttribute("label", popup.parentNode.pref.vals[val] = lab);
				key && menuitem.setAttribute("accesskey", key);
				var tip = menuitem.val = val;
				if (hint) tip += "\n" + hint;
				menuitem.tooltipText = tip;
				popup.append(menuitem);
			}
		},
		openPopup(popup) {
			var btn = popup.parentNode;
			if (btn.domParent != btn.parentNode) {
				btn.domParent = btn.parentNode;
				if (btn.matches(".widget-overflow-list > :scope"))
					var pos = "after_start";
				else var win = btn.ownerGlobal, {width, height, top, bottom, left, right} =
					btn.closest("toolbar").getBoundingClientRect(), pos = width > height
						? `${win.innerHeight - bottom > top ? "after" : "before"}_start`
						: `${win.innerWidth - right > left ? "end" : "start"}_before`;
				for(var p of btn.popups) p.setAttribute("position", pos);
			}
			popup.openPopup(btn);
		},
		maybeRestart(node, conf) {
			if (conf && !Services.prompt.confirm(null, this.label, "Перезапустить браузер?")) return;
			var cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
			Services.obs.notifyObservers(cancel, "quit-application-requested", "restart");
			return cancel.data ? Services.prompt.alert(null, this.label, "Запрос на выход отменён.") : this.restart();
		},
		async restart() {
			var meth = Services.appinfo.inSafeMode ? "restartInSafeMode" : "quit";
			Services.startup[meth](Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
		},
		maybeRe(node, fe) {
			var {pref} = node;
			if ("restart" in pref) {
				if (this.maybeRestart(node, pref.restart)) return;
			}
			else this.popupshowing(fe, node.parentNode);
			if ("refresh" in pref) {
				var win = node.ownerGlobal;
				if (this.regexpRefresh.test(win.gBrowser.currentURI.spec)) pref.refresh
					? win.BrowserReloadSkipCache() : win.BrowserReload();
			}
		},
		maybeClosePopup(e, trg) {
			!e.shiftKey && prefs.getBoolPref(this.closePref, undefined)
				&& trg.parentNode.hidePopup();
		},
		popupshowing(e, trg = e.target) {
			if (trg.state == "closed") return;
			if (trg.id) {
				for(var node of trg.children) {
					if (node.nodeName.endsWith("r")) continue;
					this.upd(node);
					!e && node.open && this.popupshowing(null, node.querySelector("menupopup"));
				}
				return;
			}
			var {pref} = trg.closest("menu"), findChecked = true;

			var findDef = "defVal" in pref;
			var checked = trg.querySelector("[checked]");
			if (checked) {
				if (checked.val == pref.val) {
					if (findDef) findChecked = false;
					else return;
				}
				else checked.removeAttribute("checked");
			}
			if (findDef) {
				var def = trg.querySelector("menuitem:not([style*=font-style]");
				if (def)
					if (def.val == pref.defVal) {
						if (findChecked) findDef = false;
						else return;
					}
					else def.style.setProperty("font-style", "italic", "important");
			}
			for(var node of trg.children) if ("val" in node) {
				if (findChecked && node.val == pref.val) {
					node.setAttribute("checked", true);
					if (findDef) findChecked = false;
					else break;
				}
				if (findDef && node.val == pref.defVal) {
					node.style.removeProperty("font-style");
					if (findChecked) findDef = false;
					else break;
				}
			}
		},
		click(e) {
			if (e.button) return;
			var trg = e.target, {pref} = trg;
			if (!pref) return;
		},
		command(e) { // нажатия левой кнопки мыши
			var trg = e.target, win = e.view;
			if (trg.btn) { // LMB
				if (e.shiftKey) e.altKey
					? win.openDialog("chrome://user_chrome_files/content/options/prefs_win.xhtml", "user_chrome_prefs:window", "centerscreen,resizable,dialog=no") // Alt+Shift
					: e.view.ZoomManager.toggleZoom(); // Alt Пипетка
				else if (e.altKey)
					e.view.PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar"); // Shift Библиотека Панель закладок
				else {  // LMB Click
					var bar = e.target.ownerDocument.querySelector("#ucf-additional-vertical-bar")
					if (bar) {
						win.setToolbarVisibility(bar, bar.collapsed);
						bar.collapsed ? win.SidebarUI.hide() : win.SidebarUI.show("viewHistorySidebar");
					} else
						win.SidebarUI.toggle("viewHistorySidebar");
				}
				return;
			}
			var menu = trg.closest("menu"), newVal = trg.val;
			this.maybeClosePopup(e, menu);
			if (newVal != menu.pref.val)
				menu.pref.set(menu.pref.pref, newVal),
				this.maybeRe(menu, true);
		},
		auxclick(e) { // CKM
			if (e.button != 1 || !e.target.btn) return;
			if (e.altKey) e.shiftKey
				? this.switchToTab(e, "ya.ru") // Alt+Shift
				: e.view.undoCloseTab()
			else if (e.shiftKey)
				this.eyedropper(e.target)
			else
				this.switchToTab(e, "about:newtab");
		},
		contextmenu(e) { // RMB
			var trg = e.target, win = e.view;
			if (trg.btn) {
				if (e.ctrlKey || e.shiftKey) return;
				if (e.detail == 2) return trg.secondaryPopup.hidePopup(); // меню быстрых настроек
				! e.altKey ? this.openPopup(trg.secondaryPopup) : this.switchToTab(e, "about:config");
			}
			else if ("pref" in trg) {
				this.maybeClosePopup(e, trg);
				if (trg.pref.user)
					prefs.clearUserPref(trg.pref.pref),
					this.maybeRe(trg);
			}
			e.preventDefault();
		},
		mousedown(e) {
			var reset = e => e.target.linkedObject = this;
			var id, lo = {command: reset, mousedown: reset, auxclick: e => e.button != 1 || reset(e)};
			var lin = /macos|linux/.test(e.view.AppConstants.platform);
			var stop = e => reset(e) && e.preventDefault();
			lo.contextmenu = lin
				? e => e.ctrlKey || e.shiftKey ? dsp(e) : stop(e) : stop;
			var context = lin
				? e => e.button == 2 && e.type.endsWith("p") && this.contextmenu(e) : () => {};
			var dsp = (e, timeout) => {
				var trg = e.target;
				trg.onmouseup = trg.onmouseleave = null;
				if (timeout) return this.londPress(e);
				e.view.clearTimeout(id);
				reset(e);
				context(e);
			}
			(this.mousedown = e => {
				var trg = e.target;
				if (!trg.btn) return;
				trg.linkedObject = lo;
				trg.onmouseup = trg.onmouseleave = dsp;
				id = e.view.setTimeout(dsp, 500, e, true);
			})(e);
		},
		londPress(e) { // удержание кнопки мыши. на второй долгий клик при отпускании сработает действие на обычный клик этой кнопки
			var trg = e.target, win = e.view;
			if (e.button == 0) this.switchProxy(e, my_vpn);
			if (e.button == 1) trg.ownerDocument.getElementById("key_browserConsole").doCommand(); // Консоль браузера
			if (e.button == 2) { // RMB Long
				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(e, help_ucf[0]) : this.switchToTab(e, help_ucf[1]);
			}
		},
		switchProxy(e, pac) {
			if (prefs.getIntPref('network.proxy.type') == 2) { // выключить
				prefs.setIntPref('network.proxy.type', 0);
				prefs.setStringPref("network.proxy.autoconfig_url", "127.0.0.1");
				e.target.style.removeProperty("filter");
				this.showInStatusPanel("\u{1F6A6} Настройки сети - работа без прокси"); // символ Светофор
			} else {
				prefs.setIntPref('network.proxy.type', 2);
				prefs.setStringPref("network.proxy.autoconfig_url", pac);
				e.target.style.setProperty("filter", icon_vpn, "important");
				this.showInStatusPanel("\u{1F6A6} Заблокированные сайты через «АнтиЗапрет»");
				// this.Notify('Proxy', 'Работаем через VPN Антизапрет');
			}
		},
		showInStatusPanel(info, ms = 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, ms);
			StatusPanel._label = info;
		},
		eyedropper(trg) { // Пипетка - захват цвета
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm")
				.require("devtools/client/menus").menuitems
				.find(menuitem => menuitem.id == "menu_eyedropper");
			(this.eyedropper = target => obj.oncommand({target}))(trg);
		},
		Notify(title, text, ms = 3000){
			Cc['@mozilla.org/alerts-service;1'].getService(Ci.nsIAlertsService).showAlertNotification(null, title, text, false, '', null, ms);
		},
		switchToTab(e = this, url) { // открыть вкладку | закрыть, если открыта
			for(var tab of e.view.gBrowser.tabs)
				if ( tab.linkedBrowser.currentURI.spec == url ) {e.view.gBrowser.removeTab(tab); return;}; // вкладка найдена, закрыть
			e.view.switchToTabHavingURI(url, true, {relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});
		}
	};
}); // END ToggleAboutConfig

Отредактировано Dobrov (06-11-2021 03:53:55)

Отсутствует

 

№11906-11-2021 19:02:14

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

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

Dobrov пишет

Вместе с правым кликом в MacOS и вероятно Linux открывается контекстное меню панели

Посмотрел на Mint, и точно, так и есть.
Тогда можно попробовать манипуляцию атрибутом "context".

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

Выделить код

Код:

(async (bar, exp, tid, self) => CustomizableUI.createWidget(self = {
	label: "Панели, Папки",
	tooltiptext: [
		"ЛКМ:	★ Закладки\n…+ Alt	Домашняя папка",
		"ПКМ:	⟳ История\n…+ Alt	Папка установки",
		"СКМ:	Папка профиля\n…+ Alt	user_chrome_files"
	].join("\n"),
	id: "add-additional-personaltoolbar-button",
	localized: false,
	onCreated(btn) {
		btn.onclick = this.click;
		btn.toggleAttribute("context");
		btn.style.setProperty("list-style-image", "url(chrome://user_chrome_files/content/vertical_top_bottom_bar/svg/bookmark-16.svg)", "important");
	},
	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:   w => bar(w, "viewBookmarksSidebar"), // ЛКМ
	2:  () => exp("Home"), // Alt+ЛКМ
	16: () => exp("ProfD"), // СКМ
	18: () => exp("UChrm", "user_chrome_files"), // Alt+СКМ
	32:  w => bar(w, "viewHistorySidebar"), // ПКМ
	34: () => exp("GreD"), // Alt+ПКМ

	1(win) { // Double Left Click
		win.alert("DBL Click");
	},
	33(win) { // Double Right Click
		win.alert("DBL Right Click");
	},
	29(win) { // Ctrl + Shift + Double Middle Click
		win.alert("Ctrl + Shift + DBL Middle Click");
	},
}))(
	(win, bar) => win.SidebarUI.toggle(bar),
	(dir, sub) => {
		dir = Services.dirsvc.get(dir, Ci.nsIFile);
		sub && dir.append(sub);
		dir.exists() && dir.launch();
	}
);


А ещё у меня там не работают альт-клики.
Насколько я понял, mousedown занят системой:
альт-левый — перетаскивание окна,
альт-средний — менюшка как по Alt+Пробел,
альт-правый — ресайз окна.
Но у тебя, видимо, такого нет.


Dobrov пишет

скрипт перехвата кликов на downloads-button, PanelUI-menu

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

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

Выделить код

Код:

/*
	listener = e => { var trg = e.target; // Downloads Clicks
		if (e.button == 1) {
			if (e.shiftKey) { // СКМ + Shift
				if ( prefs.getIntPref("permissions.default.image", 1) == 1)
					prefs.setIntPref("permissions.default.image", 2), trg.style.filter = "hue-rotate(180deg) brightness(95%)"
				else
					prefs.setIntPref("permissions.default.image", 1), trg.style.filter = "";
				BrowserReload();
			} else	// СКМ Click
				saveSelectionToTxt(); // сохранить .txt
		} else if (e.button == 2) {
			if (e.shiftKey)
				Downloads.getSystemDownloadsDirectory().then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
			else	// ПКМ Click
				save(); // Single HTML
		}
	},
	listener_pui = e => { // PanelUI-menu Clicks
		if (e.button == 1) {
			if (e.altKey)
				window.BrowserFullScreen()
			else
			if( window.windowState != window.STATE_MAXIMIZED )
				window.maximize()
			else
				window.restore();
		} else
			if (e.button == 2) if (e.altKey) return
		else {
			e.stopPropagation();
			(e.shiftKey) ? window.close() : window.minimize();
		}
	}, // end Clicks
*/
	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");
		},
		a: {__proto__: null, bubbles: true, screenX: 0, screenY: 0},

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

		16: saveSelectionToTxt, // СКМ Click (сохранить .txt)
		20(btn) { // СКМ + Shift
			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();
		},
		32: save, // ПКМ Click (Single HTML)
		36() { // Shift + ПКМ
			Downloads.getSystemDownloadsDirectory()
				.then(path => FileUtils.File(path).launch(), Cu.reportError) // Обзор папки «Загрузки»
		},

		1() { // Double Left Click
			alert("Double Left Click");
		},
		41() { // Ctrl + Double Right Click
			alert("Ctrl + Double Right Click");
		},
		4() { // Shift + ЛКМ
			alert("Shift + ЛКМ");
		},
		
		/*** ======= PanelUI-menu Clicks ======= ***/

		118: BrowserFullScreen, // Alt + СКМ
		116() { // СКМ
			windowState != STATE_MAXIMIZED ? maximize() : restore();
		},
		132() { // ПКМ
			minimize();
		},
		136: BrowserTryToCloseWindow, // Shift + ПКМ
		134() { // Alt + ПКМ
			gCustomizeMode.enter();
		},

		101() { // Double Left Click
			alert("Double Left Click");
		},
		141() { // Ctrl + Double Right Click
			alert("Ctrl + Double Right Click");
		},
		104() { // Shift + ЛКМ
			alert("Shift + ЛКМ");
		},

	}, // end Clicks
Выделить код

Код:

//	btn.setAttribute("context", "event.stopPropagation()");
Выделить код

Код:

/*
	btn.addEventListener("click", listener), pui.addEventListener("click", listener_pui);
		window.addEventListener("keydown", keydown_win);
	var ucf = window.ucf_custom_script_win || window.ucf_custom_script_all_win;
	ucf[id] = {destructor() {
		btn.removeEventListener("click", listener), pui.removeEventListener("click", listener_pui);
		window.removeEventListener("keydown", keydown_win);
	}};
*/
	var mouseenter = function(e) {
		this.parentNode.addEventListener("mousedown", stop, true);
		this.addEventListener("mouseleave", mouseleave, {once: true});
	}
	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);
	}};

Dobrov пишет

В скрипт Quick Toggle

Уволь.

Отсутствует

 

№12007-11-2021 00:51:18

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

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

Dumby присоеденяюсь к просьбе ВВП https://forum.mozilla-russia.org/viewto … 45#p795945 на счет скрипта памяти

Отсутствует

 

№12107-11-2021 05:32:38

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

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

Dumby - спасибо, я немного переделал код и обновил профиль-сборку скриптов.

Dumby пишет

А ещё у меня там не работают альт-клики. Насколько я понял, mousedown занят системой

Не системой, а демоном горячих клавиш, которые можно изменить в Настройках из меню.
перетаскивание/Resize окна вместе с Alt - это стандарт, но мета-клавишу можно в настройках поменять на Ctrl или Alt или Win…

Отсутствует

 

№12207-11-2021 09:14:37

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

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

egorsemenov06 пишет

присоеденяюсь к просьбе

Какой просьбе? Нет там никакой просьбы.

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

Есть сетование на трудности рихтовки и констатация
отсутствия реакции индикатора на очистку памяти фишкой.


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


И код рабочий, так что, если фишка действительно чистит память,
то индикатор это покажет, не прям сразу, но не позднее delay.
И да, если вдруг надо поставить (для теста) второй экземпляр кода,
то следует изменить id (в конце, "ucf-mem-indicator"), а то будут накладки.


И ещё там есть вопрос, можно ли сделать подобный индикатор,
но на другом принципе — использовать nsIMemoryReporterManager.


Когда-то давно я уже отвечал на это, целый трактат написал.
Суть — можно, но тогда результат будет включть только память DOM-процессов,
то есть выпадут процессы gpu, socket, rdd, может ещё какие-то подобные.

Отсутствует

 

№12307-11-2021 09:59:42

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

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

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

присоеденяюсь к просьбе

Какой просьбе? Нет там никакой просьбы.

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

Есть сетование на трудности рихтовки и констатация
отсутствия реакции индикатора на очистку памяти фишкой.


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


И код рабочий, так что, если фишка действительно чистит память,
то индикатор это покажет, не прям сразу, но не позднее delay.
И да, если вдруг надо поставить (для теста) второй экземпляр кода,
то следует изменить id (в конце, "ucf-mem-indicator"), а то будут накладки.


И ещё там есть вопрос, можно ли сделать подобный индикатор,
но на другом принципе — использовать nsIMemoryReporterManager.


Когда-то давно я уже отвечал на это, целый трактат написал.
Суть — можно, но тогда результат будет включть только память DOM-процессов,
то есть выпадут процессы gpu, socket, rdd, может ещё какие-то подобные.

Спасибо! понятно что ни чего не понятно.

Отсутствует

 

№12411-11-2021 18:05:36

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

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

Dumby - Помоги исправить код перехода в "Адаптивный дизайн".


код в сложной кнопке QuickToggle.js работает, но если несколько раз включить/выключить Адаптивный дизайн через кнопку, то это переключение перестаёт работать.
и не работает в простой кнопке, в которую ты мне клики добавил:

Выделить код

Код:

responsiveUI(trg) {
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_responsiveUI");
			(this.responsiveUI = target => obj.oncommand({target}))(trg);
		},

строка 155 - 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.tooltipText = GetDynamicShortcutTooltipText(btn.id) +`

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

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

	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()});
		},
		responsiveUI(trg) {
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_responsiveUI");
			(this.responsiveUI = target => obj.oncommand({target}))(trg);
		},

		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: BrowserReloadSkipCache, // Alt + Click
		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();
		},
		132() { // ПКМ
			minimize();
		},
		134() { // Alt + ПКМ
			gCustomizeMode.enter();
		},
		101: BrowserTryToCloseWindow, // Double Left Click
		133(pui) { // Double Right Click
			responsiveUI(pui);
		},

	}, // 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),
	save_ex = "_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action", singlesave;
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	(async () => { setTimeout(()=> { // дополнить подсказки
			singlesave = document.getElementById(save_ex);
			singlesave ? btn.tooltipText = btn.tooltipText + '\n\nAlt⇧S	 ⌨ нажатие SingleSave' : 0;
			if (!/Закрыть/.test(pui.tooltipText)) pui.tooltipText = PanelUI_help;
		}, linux ? 3000 : 9000); // ожидание для Windows больше
	})();
	var mouseenter = function(e) {
		this.parentNode.addEventListener("mousedown", stop, true);
		this.addEventListener("mouseleave", mouseleave, {once: true});
	}
	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

Ещё вопрос - как имитировать нажатие клавиш, например "послать" переключения в режим "Адаптивный дизайн" - у меня это Option+Command+M (Win+Alt+M)
пробовал, но не пашет:  window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).sendKeyEvent("keypress", 0, "j".charCodeAt(0), 0x08)

Отредактировано Dobrov (11-11-2021 18:07:09)

Отсутствует

 

№12511-11-2021 23:09:22

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

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

Dobrov пишет

не работает
responsiveUI(pui);

this.responsiveUI(pui);


Или (для понимания) без промежуточности

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

Выделить код

Код:

.....
		133(pui) {
			var obj = ChromeUtils.import("resource://devtools/shared/Loader.jsm").require("devtools/client/menus").menuitems.find(menuitem => menuitem.id == "menu_responsiveUI");
			(this[133] = target => obj.oncommand({target}))(pui);
		},

не пашет
window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).sendKeyEvent

На FF87 у window нет метода QueryInterface(),
и у nsIDOMWindowUtils нет метода sendKeyEvent().

Отсутствует

 

Board footer

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