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

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

№1560119-05-2021 23:45:40

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

Re: Custom Buttons

Dobrov пишет

Я добавлял события mousedown, click, auxclick - все они не реагируют на клик средней кнопки мыши

Если заменил dblclick на auxclick в child.events,
тогда надо заменить dblclick на auxclick и в само́м class MouseImgSaverChild,
а так же, в handleEvent(), заменить if (e.button) на if (e.button > 1)
autoscroller? Ну, я уже упоминал, и ноль реакции, значит не проблема (выключен?).

Нужно исправить баг: при первом открытии последняя строка меню не с родной иконкой.

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

Выделить код

Код:

/*
			if (!pref.img) img
				? node.setAttribute("image", img)
				:	/этой кнопки/.test(node.getAttribute("label")) ? node.setAttribute("image", "chrome://browser/skin/menu.svg") : node.setAttribute("image", this.UserImg);
				// : node.removeAttribute("image");
*/
			node.nextSibling && node.setAttribute("image", img || this.UserImg);

просьба доработать кнопку переключения параметров Quick Toggle about:config.

Самоотвод.


ВВП пишет

Окно по ПКМ выскакивает и другие коды сработают при отмене. Как бы запретить ?

Вместо Sanitizer.showUI(window);

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

Выделить код

Код:

var cancel = true;
Services.obs.addObserver(function wfp(win, topic) {
	Services.obs.removeObserver(wfp, topic);
	var sd = win.gSanitizePromptDialog, {sanitize} = sd;
	sd.sanitize = e => cancel = sanitize.call(sd, e);
}, "widget-first-paint");

Sanitizer.showUI(window);
if (cancel) return;

Отсутствует

 

№1560220-05-2021 02:52:25

ВВП
Участник
 
Группа: Members
Зарегистрирован: 13-03-2021
Сообщений: 332
UA: Firefox 87.0

Re: Custom Buttons

Dumby

Dumby пишет

Вместо Sanitizer.showUI(window);

Как же сложно... Благодарю !

Отсутствует

 

№1560320-05-2021 06:16:35

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

Re: Custom Buttons

Dumby - Спасибо, я доработал ClickPicSave.jsm-скрипт сохранения картинки.
Создал свой массив pics - опцию "ucf_save.dirs" = "_Web|0|_Images|1", где можно менять папку сохранения картинок.
Но код скрипта создаёт все данные для сохранения картинки только при запуске браузера!
Если изменить опции в about:config, то скрипт по-прежнему сохраняет в старую папку.


Dumby - помоги доработать код, чтобы при каждой записи файла путь брался из опции "ucf_save.dirs"
Если такой опции нет, берётся значение по-умолчанию. Мои изменения в коде: строка 100 и далее…

Выделить код

Код:

var EXPORTED_SYMBOLS = ["MouseImgSaverChild", "MouseImgSaverParent"];

var u = {get it() {
		delete this.it;
		return this.it = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools);
}};
for(let name of ["E10SUtils", "PrivateBrowsingUtils"])
	ChromeUtils.defineModuleGetter(u, name, `resource://gre/modules/${name}.jsm`);

class MouseImgSaverChild extends JSWindowActorChild {
	handleEvent(e) { // клики мышью
		if (e.button > 1) return; // только ЛКМ, СКМ
		var trg = e.explicitOriginalTarget;
		trg.nodeType == Node.ELEMENT_NODE
			&& trg instanceof Ci.nsIImageLoadingContent
			&& this[e.type](trg, e);
	}
	handleDragEvent(e) {
		this[e.type](e);
	}
	dragstart(trg, e) {
		this.trg = trg;
		this.x = e.screenX; this.y = e.screenY;
		this.drag("add");
		this.handleEvent = this.handleDragEvent;
		this.checkTextLinkyTool(trg.ownerDocument);
	}
	events = ["dragover", "drop", "dragend"];
	drag(meth = (delete this.handleEvent, delete this.trg, "remove")) {
		meth += "EventListener";
		var win = this.contentWindow;
		for(var type of this.events) win[meth](type, this, true);
	}
	drop() {
		this.drag();
	}
	dragover(e) {
		var {x, y} = this,
			cx = e.screenX, cy = e.screenY,
			dx = cx - x,
			ax = Math.abs(dx), ay = Math.abs(cy - y);

		if (ax < 10 && ay < 10) return;
		if (dx < 0 || ax < ay) return this.drag();
		this.x = cx; this.y = cy;
	}
	dragend(e) {
		var dt = e.dataTransfer, {trg} = this;
		this.drag();
		dt.mozUserCancelled || this.send(trg, e.screenX);
	}
	auxclick(trg) { // клик СКМ
		this.dblclick(trg);
	}
	dblclick(trg) { // ЛКМ
		trg.matches(":any-link :scope") || this.send(trg);
	}
	send(trg, sx) {
		var uri = trg.currentURI;
		if (!uri) return;

		var doc = trg.ownerDocument;
		var cookieJarSettings = u.E10SUtils
			.serializeCookieJarSettings(doc.cookieJarSettings);

		var referrerInfo = Cc["@mozilla.org/referrer-info;1"]
			.createInstance(Ci.nsIReferrerInfo);
		referrerInfo.initWithElement(trg);
		referrerInfo = u.E10SUtils.serializeReferrerInfo(referrerInfo);

		var contentType = null, contentDisposition = null;
		try {
			var props = u.it.getImgCacheForDocument(doc).findEntryProperties(uri, doc);
			var cs = Ci.nsISupportsCString;
			try {contentType = props.get("type", cs).data;} catch {}
			try {contentDisposition = props.get("content-disposition", cs).data;} catch {}
		} catch {}

		this.sendAsyncMessage("", {
			url: (trg.currentRequestFinalURI || uri).spec,
			contentType, referrerInfo, cookieJarSettings, contentDisposition, sx,
			isPrivate: u.PrivateBrowsingUtils.isContentWindowPrivate(trg.ownerGlobal)
		});
	}
	checkTextLinkyTool(doc) {
		if (doc.title || !doc.documentURI.startsWith("moz-extension:")) return;
		var lab = doc.querySelector("body > label#lblFrom:first-child")?.textContent;
		if (lab) doc.title = lab.slice(0, lab.lastIndexOf("("));
	}
}
if (!ChromeUtils.domProcessChild.childID) {
	ChromeUtils.registerWindowActor("MouseImgSaver", {
		allFrames: true,
		parent: {moduleURI: __URI__},
		messageManagerGroups: ["browsers"],
		child: {moduleURI: __URI__, events: {auxclick: {capture: true}, dblclick: {capture: true}, dragstart: {capture: true}}}
	});
	var wref, data = Object.assign(Object.create(null), {
		"browser.download.dir": {type: "String", get set() {

			try {var dir = wref.get().Services.prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {var dir = wref.get().Services.dirsvc.get("DfltDwnld", Ci.nsIFile);};
			var pics = wref.get().Services.prefs.getStringPref("ucf_save.dirs", "_Web||_Images|1").split('|');
			dir.append(pics[2]);
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
			var {path} = dir; // папка Загрузки:/_Images/[title]/файл
			Object.defineProperty(this, "set", {get() {
				var win = wref.get();
				var title = pics[3] ? win.gBrowser.selectedTab.label.slice(0, 100) : ''; // папка = имя вкладки
				return PathUtils.join(path, win.DownloadPaths.sanitize(title));
			}});
			return this.set;
		}},
		"browser.download.folderList": {type: "Int", set: 2},
		"browser.download.useDownloadDir": {type: "Bool", set: true}
	});
	var MouseImgSaverParent = class extends JSWindowActorParent {
		receiveMessage(msg) {
			var {url, contentType, contentDisposition, sx,
				isPrivate, referrerInfo, cookieJarSettings} = msg.data;

			var win = msg.target.browsingContext.topChromeWindow;
			if (sx && sx > win.mozInnerScreenX + win.innerWidth) return;

			wref = Cu.getWeakReference(win);
			var p = win.Services.prefs;

			for(var pref in data) {
				var obj = data[pref], meth = `et${obj.type}Pref`;
				obj.val = p.prefHasUserValue(pref) ? p["g" + meth](pref) : null;
				p["s" + meth](pref, obj.set);
			}
			try {win.internalSave(
				url,
				null, // document
				null, // file name
				contentDisposition,
				contentType,
				false, // do not bypass the cache
				null, // filepicker title key
				null, // chosen data
				u.E10SUtils.deserializeReferrerInfo(referrerInfo),
				u.E10SUtils.deserializeCookieJarSettings(cookieJarSettings),
				win.document, // initiating doc
				true, // skip prompt for where to save
				null, // cache key
				isPrivate,
				win.document.nodePrincipal
			);}
			finally {
				for(var pref in data) data[pref].val === null
					? p.clearUserPref(pref)
					: p[`set${data[pref].type}Pref`](pref, data[pref].val);
			}
		}
	}
}

Отсутствует

 

№1560420-05-2021 11:34:35

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

Re: Custom Buttons

Dumby пишет

Думаю да. Добавил в 78 в custom_script_win.js — выглядит рабочим.
Только исключение тэгов для "bookmark-added" потерялось. Можно вписать.

Спасибо. Я его в custom_script.js пихал, вот он и не работал. Теперь нормально все.

Отсутствует

 

№1560520-05-2021 19:03:31

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

Re: Custom Buttons

Dobrov пишет

Если изменить опции в about:config, то скрипт по-прежнему сохраняет в старую папку.


Dumby - помоги доработать код, чтобы при каждой записи файла путь брался из опции "ucf_save.dirs"
Если такой опции нет, берётся значение по-умолчанию. Мои изменения в коде: строка 100 и далее…

Во навернул идею. Хорошо бы поподробнее. Может так :/

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

Выделить код

Код:

/*
			try {var dir = wref.get().Services.prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {var dir = wref.get().Services.dirsvc.get("DfltDwnld", Ci.nsIFile);};
			var pics = wref.get().Services.prefs.getStringPref("ucf_save.dirs", "_Web||_Images|1").split('|');
			dir.append(pics[2]);
			dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
			var {path} = dir; // папка Загрузки:/_Images/[title]/файл
			Object.defineProperty(this, "set", {get() {
				var win = wref.get();
				var title = pics[3] ? win.gBrowser.selectedTab.label.slice(0, 100) : ''; // папка = имя вкладки
				return PathUtils.join(path, win.DownloadPaths.sanitize(title));
			}});
			return this.set;
*/
			var win = wref.get();
			var {DownloadPaths} = win, {prefs, dirsvc} = win.Services;
			var map = val => DownloadPaths.sanitize(val);

			Object.defineProperty(this, "set", {get() {
				try {var dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);}
				catch {var dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}

				var arr = prefs.getStringPref("ucf_save.dirs", "||_Images|1").split('|').slice(2, 4);
				arr[0] ??= ""; // Hmm...
				arr[1] ??= wref.get().gBrowser.selectedTab.label.slice(0, 100); // имя вкладки

				arr.map(map).forEach(dir.append);
				dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
				return dir.path;
			}});
			return this.set;

Отредактировано Dumby (20-05-2021 22:38:05)

Отсутствует

 

№1560621-05-2021 05:22:38

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

Re: Custom Buttons

Dumby - Спасибо! А у меня ещё актуальная хотелка по скрипту сохранения картинок перетаскиванием:
На некоторых картинках есть всплывающая подсказка-описание 1) Если tooltip картики есть, то нужно сохранять картинку с именем всплывающей подсказки.
2) на двойной клик мыши надо "повесить" другую полезную функцию на твоё усмотрение. Желательно поиск дубликата этой картинки в Яндекс. ну или перезагрузку картинки (то есть обновить её).

ClickPicSave.jsm если save-опций нет, ошибки не будет

Выделить код

Код:

var EXPORTED_SYMBOLS = ["MouseImgSaverChild", "MouseImgSaverParent"]; // сохранить картинку — перетащив вправо; двойным кликом; колёсиком

var u = {get it() {
		delete this.it;
		return this.it = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools);
}};
for(let name of ["E10SUtils", "PrivateBrowsingUtils"])
	ChromeUtils.defineModuleGetter(u, name, `resource://gre/modules/${name}.jsm`);

class MouseImgSaverChild extends JSWindowActorChild {
	handleEvent(e) { // клики мышью
		if (e.button > 1) return; // только ЛКМ, СКМ
		var trg = e.explicitOriginalTarget;
		trg.nodeType == Node.ELEMENT_NODE
			&& trg instanceof Ci.nsIImageLoadingContent
			&& this[e.type](trg, e);
	}
	handleDragEvent(e) {
		this[e.type](e);
	}
	dragstart(trg, e) {
		this.trg = trg;
		this.x = e.screenX; this.y = e.screenY;
		this.drag("add");
		this.handleEvent = this.handleDragEvent;
		this.checkTextLinkyTool(trg.ownerDocument);
	}
	events = ["dragover", "drop", "dragend"];
	drag(meth = (delete this.handleEvent, delete this.trg, "remove")) {
		meth += "EventListener";
		var win = this.contentWindow;
		for(var type of this.events) win[meth](type, this, true);
	}
	drop() {
		this.drag();
	}
	dragover(e) {
		var {x, y} = this,
			cx = e.screenX, cy = e.screenY,
			dx = cx - x,
			ax = Math.abs(dx), ay = Math.abs(cy - y);

		if (ax < 10 && ay < 10) return;
		if (dx < 0 || ax < ay) return this.drag();
		this.x = cx; this.y = cy;
	}
	dragend(e) {
		var dt = e.dataTransfer, {trg} = this;
		this.drag();
		dt.mozUserCancelled || this.send(trg, e.screenX);
	}
	auxclick(trg) { // клик СКМ
		trg.matches(":any-link :scope") || this.send(trg);
	}
	dblclick(trg) { // ЛКМ
		trg.matches(":any-link :scope") || this.send(trg);
	}
	send(trg, sx) {
		var uri = trg.currentURI;
		if (!uri) return;

		var doc = trg.ownerDocument;
		var cookieJarSettings = u.E10SUtils
			.serializeCookieJarSettings(doc.cookieJarSettings);

		var referrerInfo = Cc["@mozilla.org/referrer-info;1"]
			.createInstance(Ci.nsIReferrerInfo);
		referrerInfo.initWithElement(trg);
		referrerInfo = u.E10SUtils.serializeReferrerInfo(referrerInfo);

		var contentType = null, contentDisposition = null;
		try {
			var props = u.it.getImgCacheForDocument(doc).findEntryProperties(uri, doc);
			var cs = Ci.nsISupportsCString;
			try {contentType = props.get("type", cs).data;} catch {}
			try {contentDisposition = props.get("content-disposition", cs).data;} catch {}
		} catch {}

		this.sendAsyncMessage("", {
			url: (trg.currentRequestFinalURI || uri).spec,
			contentType, referrerInfo, cookieJarSettings, contentDisposition, sx,
			isPrivate: u.PrivateBrowsingUtils.isContentWindowPrivate(trg.ownerGlobal)
		});
	}
	checkTextLinkyTool(doc) {
		if (doc.title || !doc.documentURI.startsWith("moz-extension:")) return;
		var lab = doc.querySelector("body > label#lblFrom:first-child")?.textContent;
		if (lab) doc.title = lab.slice(0, lab.lastIndexOf("("));
	}
}
if (!ChromeUtils.domProcessChild.childID) {
	ChromeUtils.registerWindowActor("MouseImgSaver", {
		allFrames: true,
		parent: {moduleURI: __URI__},
		messageManagerGroups: ["browsers"],
		child: {moduleURI: __URI__, events: {auxclick: {capture: true}, dblclick: {capture: true}, dragstart: {capture: true}}}
	});
	var wref, data = Object.assign(Object.create(null), {
		"browser.download.dir": {type: "String", get set() {

			var win = wref.get();
			var {DownloadPaths} = win, {prefs, dirsvc} = win.Services;
			var map = val => DownloadPaths.sanitize(val);

			Object.defineProperty(this, "set", {get() {
				try {var dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {var dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}

				var arr = prefs.getStringPref("ucf_save.dirs", "_Web||_Images|0").split('|').slice(2, 4); // [Загрузки]/папки ucf_save/файл
				arr[1] = (arr[1]) ? wref.get().gBrowser.selectedTab.label.slice(0, 64).replace(/ \| Форум Mozilla Россия$| — Mozilla Firefox|[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim() : ""; // имя вкладки
				arr.map(map).forEach(dir.append); // ucf_save.dirs: путь для html|имя или домен|папка графики|имя вкладки
				dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
				return dir.path;
			}});
			return this.set;
		}},
		"browser.download.folderList": {type: "Int", set: 2},
		"browser.download.useDownloadDir": {type: "Bool", set: true}
	});
	var MouseImgSaverParent = class extends JSWindowActorParent {
		receiveMessage(msg) {
			var {url, contentType, contentDisposition, sx,
				isPrivate, referrerInfo, cookieJarSettings} = msg.data;

			var win = msg.target.browsingContext.topChromeWindow;
			if (sx && sx > win.mozInnerScreenX + win.innerWidth) return;

			wref = Cu.getWeakReference(win);
			var p = win.Services.prefs;

			for(var pref in data) {
				var obj = data[pref], meth = `et${obj.type}Pref`;
				obj.val = p.prefHasUserValue(pref) ? p["g" + meth](pref) : null;
				p["s" + meth](pref, obj.set);
			}
			try {win.internalSave(
				url,
				null, // document
				null, // file name
				contentDisposition,
				contentType,
				false, // do not bypass the cache
				null, // filepicker title key
				null, // chosen data
				u.E10SUtils.deserializeReferrerInfo(referrerInfo),
				u.E10SUtils.deserializeCookieJarSettings(cookieJarSettings),
				win.document, // initiating doc
				true, // skip prompt for where to save
				null, // cache key
				isPrivate,
				win.document.nodePrincipal
			);}
			finally {
				for(var pref in data) data[pref].val === null
					? p.clearUserPref(pref)
					: p[`set${data[pref].type}Pref`](pref, data[pref].val);
			}
		}
	}
}

Dumby пишет

…чтобы при каждой записи файла путь брался из опции "ucf_save.dirs"
Во навернул идею. Хорошо бы поподробнее.

Логика такая: мои скрипты берут из опций браузера пути сохранения страниц/картинок/(медиа).
Пользователь может указать в опциях кнопки Quick Toggle свои папки для сохранения файлов.
Например, имена картинок часто безсмысленные, поэтому лучше создавать папку с именем вкладки.

Синтаксис массива опции выбрал такой: папка|subdir: папка с именем вкладки или домена.
subdir [пусто|0 имя вкладки|1 домен т.е. dns.ru]. Если элемент массива пуст, папка не создаётся.
пример: "_Web|1|_Pics|0" для html запись в [Загрузки]/_Web/домен. Картинки в [Загрузки]/_Pics/имя вкладки.

Выделить код

Код:

var arr = prefs.getStringPref("ucf_save.dirs", "_Web||_Images|0").split('|').slice(2, 4); // [Загрузки]/папки ucf_save/файл
arr[1] = (arr[1]) ? wref.get().gBrowser.selectedTab.label.slice(0, 64).replace(/ \| Форум Mozilla Россия$| — Mozilla Firefox|[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim() : ""; // имя вкладки
arr.map(map).forEach(dir.append); // ucf_save.dirs: путь для html|имя или домен|папка графики|имя вкладки
return dir.path;

hookClicks для downloads-button, PanelUI берёт save-путь из опций

Выделить код

Код:

(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	Обзор папки [Загрузки]
Ролик:	Сохранить как файл .txt
…Shift	Сайт: графика Вкл/Выкл
СКМ или Тащить рисунок вправо:
…cохранить в [Загрузки]/_Images
Alt⇧S	нажать кнопку SingleSave` // нет SingleSave - выполнить save()

pui.tooltipText =
`ЛКМ:	меню приложения
ПКМ:	⇲ Свернуть окно
…Alt	Персонализация
Ролик:	Закрыть браузер`

	var addDestructor = nextDestructor => {
		var {destructor} = ucf[id];
		ucf[id].destructor = () => {
			try {destructor();} catch(ex) {Cu.reportError(ex);}
			nextDestructor();
		}
	},
	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);
		}
		messageManager.addMessageListener(msgName, receiver);
		addDestructor(() => messageManager.removeMessageListener(msgName, receiver));

		var func = fm => {
			var res, fed, win = {};
			var 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
		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); // [Загрузки]/папки ucf_save/файл
			arr[1] = (arr[1] == "0") ? Title(100) : (arr[1] == "1") ? Title(-1) : ""; // имя вкладки или домен
			arr.forEach(dir.append); // ucf_save.dirs: путь для html|имя или домен|папка графики|имя вкладки
			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);
			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) e.shiftKey // middle
			? null : close()
		else if (e.button == 2) if (e.altKey) return
		else event.stopPropagation(), window.minimize();
	}, // end Clicks

	keydown_win = e => { // нажатие клавиш
		if (!(e.keyCode == 83 && e.shiftKey && e.altKey)) return;
		var singlesave = document.getElementById("_531906d3-e22f-4a6c-a102-8057b88a1a63_-browser-action"); // SingleSave
		singlesave ? singlesave.click() : save(); // имитировать клик по кнопке, используя её ID
	},
	{prefs, dirsvc} = Services, tmax = btn.tooltipText.split("\n")[0].length, dw;
	btn.setAttribute("context", "event.stopPropagation()");
	prefs.setBoolPref("browser.download.autohideButton", false); // не скрывать кнопку Загрузки

	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}) => {

	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

Dumby ты делал запуск js-кода в строках меню Quick Toggle. Так не пашет: this, node.parentNode.parentNode, …
Подскажи, как открыть вкладку с адресом? Я получаю ошибку: e.view is undefined

Выделить код

Код:

// строка 54 кнопки https://forum.mozilla-russia.org/viewtopic.php?pid=789824#p789824
[`_Web|1|_Images|0`, "ввести свои пути",,,`this.switchToTab("about:config", Services.wm.getMostRecentWindow("navigator:browser"))`]]
… … …
switchToTab(url, e = this) { // открыть вкладку | закрыть, если открыта
	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()});

Отредактировано Dobrov (21-05-2021 16:35:33)

Отсутствует

 

№1560721-05-2021 14:39:27

Cytrus
Участник
 
Группа: Members
Зарегистрирован: 30-04-2021
Сообщений: 10
UA: Firefox 78.0

Re: Custom Buttons

Подскажите, почему я не могу обратиться к DOM-элементам страницы из кнопки или из Browser Console (CTRL+SHIFT+J)?
Пишет, что селектор null.
А из WEB Console (CTRL+SHIFT+K)  команда прекрасно работает.

Вот, допустим, на этой же страницы ввести что-нибудь в "Быстрый ответ".


Выделить код

Код:

alert(document.querySelector('textarea[name="req_message"]').value);

Как можно свободно обращаться к элементам DOM?

Отсутствует

 

№1560821-05-2021 16:33:31

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

Re: Custom Buttons

Dumby – ещё хотелка - нужна Кнопка, которая отвечает за вид страницы, то есть переключает 1) режим чтения вкл/выкл 2) стиль страницы обычный/зелёный 3) масштаб с отображением процентов на кнопке 4) прочие функции, например переключить показ картинок, отключить стили страницы, гугл-перевод сайта/выделенного текста и т.п.
Режимы должны отображаться на иконке - 0) Обычный режим - родная иконка 1) Режим чтения - к иконке применён/отключен красный фильтр  "filter: hue-rotate(90deg)" 2) если переключен стиль страницы, к иконке добавляется/отключается фон: background-image
Переключение вида иконки также должно происходить при переходе по вкладкам - то есть, если переходим на вкладку about:reader, то иконка кнопки должна смениться - к ней должен добавиться красный фильтр.
Эта моя кнопка из старого профиля для Basilisk, но адаптировать её сложно.

стиль, включающий зелёный фон всех страниц

Выделить код

Код:

@-moz-document url-prefix("http"), url-prefix("https"), url-prefix("ftp"), url-prefix("file") {
html *, html {
background: #ccd1d1 !important; text-shadow: none !important;
color: #000000 !important;
-moz-border-top-colors: #8FBC8F !important; -moz-border-bottom-colors: #8FBC8F!important; -moz-border-left-colors: #8FBC8F !important; -moz-border-right-colors: #8FBC8F !important;
}
code, pre, input, samp, kbd, var, dfn,.postright {
-moz-appearance: none !important;
background-color: #BADBAD !important;
}
input, select, textarea, button {
-moz-appearance: none !important;
border: 1px solid #BADBAD !important;
background-color: #BADBAD !important;
}
.codebox, BLOCKQUOTE, TEXTAREA, .spoiler { -moz-border-top-colors: #444  !important; -moz-border-bottom-colors: #444 !important;
-moz-border-left-colors: #444 !important; -moz-border-right-colors: #444 !important; }
a:link { color: #300090 !important;
}
a:visited { color: #489412 !important; }
a:active { color: #d81e1e !important; }
a:hover { color: #d81e1e !important; }
}

набросок применения фильтров к иконке кнопки

Выделить код

Код:

const greenbg = "background-image: -moz-linear-gradient(#c0c8c0, #c0c8c0, #c0c8c0);";
function icon_color( icon, image, num, bg, setAttr) { bg = bg || ''; setAttr = setAttr || true; // запомнить настройки
	if (num == 1) { // зелёный + фильтр (если указан)
		icon.style.cssText = "background-image: none; filter: unset;" + bg;
	} else if (num == 2) {	// красный
		icon.style.cssText = "filter: hue-rotate(90deg);" + bg;
	} else if (num == 3)	{ // серый
		icon.style.cssText = "filter: grayscale(100%) invert(40%) contrast(300%);";
	} else if (num == 4)	{ // добавить фон
			icon.style.cssText = iconbutton.getAttribute('style').toString() +' '+ greenbg;
	} else if (num == 5) {	// зелёный <> красный переключить цвет
		if ( /hue-rotate\(90deg\)/.test(icon.style.cssText) ) // красный
			icon_color( iconbutton, self.image, 1, iconbutton.getAttribute('bg').toString(), false)
		else
			icon_color( iconbutton, self.image, 2, iconbutton.getAttribute('bg').toString(), false);
	} else {
			self.image = this.image; // сброс
			icon_color( iconbutton, self.image, 1)	// return;
	} if (!setAttr) return; // режим уже запомнен
	iconbutton.setAttribute('color', num); // запомнить режим
	iconbutton.setAttribute('style', icon.style.cssText); // запомнить режим
	iconbutton.setAttribute('bg', /linear-gradient/.test(icon.style.cssText)); // false: нет фона, true: есть
};

Заготовка: Кнопка переключения режима чтения, стиля страниц и т.п.

Выделить код

Код:

try {	CustomizableUI.createWidget({ tooltiptext: `ЛКМ:	Reader`,
		type: "custom", id: "ucf_Reader", label: "Reader", localized: false,
		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("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACTElEQVR4Ad1VIbCjMBBFIpHISCQSiUQiK5FIJBJZiaxEVpxAIpFI5JdfVpyoO1Ry76XZmcz8tP/uy2ZmmzD73tvNZpNG7zfquk4x1iRJjFJqwohf4YEZgT/IIffbAABOmIwYBPoX4p2PJffb7OM4vmN5gHwlid+hzKqqIvbmkpjJCWO9kWVZQwLKsxljYs4gmbIss0CADD5idwxiV3KdRngANPtlgbCCVc/wRVFUElzKBY3lWXkUtwg7hPQ/gxxwpUwqdGBSnjX64ZAyQasNORc68zwfmqbJ2rZN/1W467qUHHKDZcKB2fJI18hW0XYzfE/LRR87SDi+BjXD/UyLY1kzmxvqmwdqnsP3GeLQqPmldqlSurtMetx2PcyLzopSS9viRicoQUHjGmOjjxhiySGXGt5ZutPHJeHTQNB8GD3fjeE8fd61ynPtbunO2V/TR4zPoQa14H90o3RP1bQW+Ov3YcT43Y4XGyBk9BFzvT3wnPkNLUmqiXD6dqsdwXcJ8McasxrXTQIcKE9D4xpGHznAegGg0bmk0Il7hJ/tkc2IbPwAFmzOy+rX1D8z+r4G8HZNbZboxI+yPrlaWqCATXMepe6jBHBr6yNG8ORSozydbABq85DliTDDddaLd2CX/YNdYdwFLCWAWxv6iJFDJpca0PJfYWakBkx8Pk3d9boHqDmf/ZabA/8bswtCrOWQSw2X/eBf9RiEq9ch2rtoG15N/9mQXaT0hTjQmvq+j0P/By0if3CLmG+wAcGTF29QQgyx7H3wd2pEbzX+AiHuFSjoQ3KFAAAAAElFTkSuQmCC")', "important");
			trbn.addEventListener("click", function(e) {
				var win = e.view, dirs;
				if (e.button == 0) {
					var green = `
@-moz-document url-prefix("http"), url-prefix("https"), url-prefix("ftp"), url-prefix("file") {
html *, html {
	color: #800000 !important;
}}`;

		var sss = this.sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
		var uri = this.uri = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newURI("data:text/css,"+ encodeURIComponent(green),null,null);

					function setStyleForPages( reason ) {
						var style = sss.sheetRegistered(uri, sss.AGENT_SHEET);
					style
						? sss.unregisterSheet(uri, sss.AGENT_SHEET)
						: sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
					};
					setStyleForPages();
					return;
				}
			}, false);
			return trbn;
		},
	});
} catch(e) {}

:rolleyes: Вопрос по ucf-коду - как проверить наличие файла?  chrome://user_chrome_files/content/Help.html
Сделал открытие/закрытие вкладки с этим адресом, но при отсутствии файла нужно открывать сайт, например https://ru.wikibooks.org/wiki


2) Как показать текст в строке статуса в течении 5 секунд ? (строка, которая появляется при наведении на ссылку)

Отредактировано Dobrov (22-05-2021 02:05:31)

Отсутствует

 

№1560922-05-2021 07:26:20

Viatcheslav
Участник
 
Группа: Members
Откуда: г. Бобруйск, Беларусь
Зарегистрирован: 23-11-2016
Сообщений: 312
UA: Firefox 88.0

Re: Custom Buttons

Переложите, пожалуйста, custom_buttons-0.0.7.0.0.17-fx-paxmod.xpi. Спасибо :)

Отсутствует

 

№1561022-05-2021 07:43:58

voqabuhe
Участник
 
Группа: Members
Зарегистрирован: 06-12-2011
Сообщений: 3231
UA: Firefox 88.0

Re: Custom Buttons

Viatcheslav
№15514

Отсутствует

 

№1561122-05-2021 10:46:34

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

Re: Custom Buttons

Dumby делаю GetSelectionText для loadFrameScript - функция получает выделенный текст для перевода в гугле, яндексе и т.п.
Но пока нужные команды обработки текста приходится вставлять в ту же функцию, которая получает текст.


1) как без использования add…Listener получить данные от FrameScript-функции, например так: GoogleTranslate(GetSelectionText())
2) из FrameScript не работает gBrowser :( - должна открыться вкладка Перевода выделенного текста

Выделить код

Код:

translate(browserMM, win, e, go) { // вариант 
	browserMM.addMessageListener('getSelect', function listener(msg) {
		= команды обработки msg.data =
	browserMM.removeMessageListener('getSelect', listener, true);
	});
	browserMM.loadFrameScript('data:,sendAsyncMessage("getSelect", content.document.getSelection().toString())', false);
},
this.translate(gBrowser.selectedBrowser.messageManager, win, this, 1)

Отредактировано Dobrov (22-05-2021 16:52:33)

Отсутствует

 

№1561222-05-2021 12:24:30

ВВП
Участник
 
Группа: Members
Зарегистрирован: 13-03-2021
Сообщений: 332
UA: Firefox 87.0

Re: Custom Buttons

Dumby
Переключиться на последнюю используемую вкладку?  Это не то
gBrowser.tabContainer.advanceSelectedTab(-1, true);
Можно и обойтись,но
Есть аддон, но как в код предупереждение запихать?
} else if (event.button == 1) {
                           [...gtBrowser.tabs].forEach((tab)=> gtBrowser.removeTab(tab));

Отредактировано ВВП (22-05-2021 20:01:32)

Отсутствует

 

№1561322-05-2021 23:06:25

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

Re: Custom Buttons

Dobrov пишет

1) Если tooltip картики есть, то нужно сохранять картинку с именем всплывающей подсказки.

Без понятия как в этой концепции сохранения такое сделать.
Подсунуть имя в internalSave() — не прокатит.
Ну, вот, разве что отслеживать загрузки и переименовывать.

2) на двойной клик мыши надо "повесить" другую полезную функцию на твоё усмотрение. Желательно поиск дубликата этой картинки в Яндекс.

Это типа надо какую-то вкладку открыть?
Добавил такое, там, в конце, dblclick(win, imgURL) {}
А какой адрес открывать — сам смотри.

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

Выделить код

Код:

var EXPORTED_SYMBOLS = ["MouseImgSaverChild", "MouseImgSaverParent"]; // сохранить картинку — перетащив вправо; колёсиком

var u = {get it() {
	delete this.it;
	return this.it = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools);
}};
for(let name of ["E10SUtils", "PrivateBrowsingUtils"])
	ChromeUtils.defineModuleGetter(u, name, `resource://gre/modules/${name}.jsm`);

class MouseImgSaverChild extends JSWindowActorChild {
	handleEvent(e) { // клики мышью
		if (e.button > 1) return; // только ЛКМ, СКМ
		var trg = e.explicitOriginalTarget;
		trg.nodeType == Node.ELEMENT_NODE
			&& trg instanceof Ci.nsIImageLoadingContent
			&& this[e.type](trg, e);
	}
	handleDragEvent(e) {
		this[e.type](e);
	}
	dragstart(trg, e) {
		this.trg = trg;
		this.x = e.screenX; this.y = e.screenY;
		this.drag("add");
		this.handleEvent = this.handleDragEvent;
		this.checkTextLinkyTool(trg.ownerDocument);
	}
	events = ["dragover", "drop", "dragend"];
	drag(meth = (delete this.handleEvent, delete this.trg, "remove")) {
		meth += "EventListener";
		var win = this.contentWindow;
		for(var type of this.events) win[meth](type, this, true);
	}
	drop() {
		this.drag();
	}
	dragover(e) {
		var {x, y} = this,
			cx = e.screenX, cy = e.screenY,
			dx = cx - x,
			ax = Math.abs(dx), ay = Math.abs(cy - y);

		if (ax < 10 && ay < 10) return;
		if (dx < 0 || ax < ay) return this.drag();
		this.x = cx; this.y = cy;
	}
	dragend(e) {
		var dt = e.dataTransfer, {trg} = this;
		this.drag();
		dt.mozUserCancelled || this.send(trg, e.screenX);
	}
	auxclick(trg) { // клик СКМ
		trg.matches(":any-link :scope") || this.send(trg);
	}
	dblclick(trg) { // ЛКМ
		trg.matches(":any-link :scope")
			|| this.sendAsyncMessage("dblclick", (trg.currentRequestFinalURI || uri).spec);
	}
	send(trg, sx) {
		var uri = trg.currentURI;
		if (!uri) return;

		var doc = trg.ownerDocument;
		var cookieJarSettings = u.E10SUtils
			.serializeCookieJarSettings(doc.cookieJarSettings);

		var referrerInfo = Cc["@mozilla.org/referrer-info;1"]
			.createInstance(Ci.nsIReferrerInfo);
		referrerInfo.initWithElement(trg);
		referrerInfo = u.E10SUtils.serializeReferrerInfo(referrerInfo);

		var contentType = null, contentDisposition = null;
		try {
			var props = u.it.getImgCacheForDocument(doc).findEntryProperties(uri, doc);
			var cs = Ci.nsISupportsCString;
			try {contentType = props.get("type", cs).data;} catch {}
			try {contentDisposition = props.get("content-disposition", cs).data;} catch {}
		} catch {}

		this.sendAsyncMessage("", {
			title: trg.closest("[title]")?.title,
			url: (trg.currentRequestFinalURI || uri).spec,
			contentType, referrerInfo, cookieJarSettings, contentDisposition, sx,
			isPrivate: u.PrivateBrowsingUtils.isContentWindowPrivate(trg.ownerGlobal)
		});
	}
	checkTextLinkyTool(doc) {
		if (doc.title || !doc.documentURI.startsWith("moz-extension:")) return;
		var lab = doc.querySelector("body > label#lblFrom:first-child")?.textContent;
		if (lab) doc.title = lab.slice(0, lab.lastIndexOf("("));
	}
}
if (!ChromeUtils.domProcessChild.childID) {
	ChromeUtils.registerWindowActor("MouseImgSaver", {
		allFrames: true,
		parent: {moduleURI: __URI__},
		messageManagerGroups: ["browsers"],
		child: {moduleURI: __URI__, events: {auxclick: {capture: true}, dblclick: {capture: true}, dragstart: {capture: true}}}
	});
	var wref, titles = Object.create(null);
	var data = Object.assign(Object.create(null), {
		"browser.download.dir": {type: "String", get set() {

			var win = wref.get(), {prefs, dirsvc} = win.Services
			var {DownloadPaths, FileUtils} = win;
			var map = val => DownloadPaths.sanitize(val);

			win.Downloads.getList(win.Downloads.ALL).then(list => list.addView({
				onDownloadChanged(download) {
					if (!download.stopped) return;
					var {url} = download.source, title = titles[url];
					if (!title) return;
					delete titles[url];
					if (!download.succeeded) return;

					var file = FileUtils.File(download.target.path), {leafName} = file;
					var ext = leafName.slice(leafName.lastIndexOf("."));
					var newName = map(title) + ext, {parent} = file;
					var newFile = parent.clone();
					newFile.append(newName);
					try {
						newFile.createUnique(file.NORMAL_FILE_TYPE, file.permissions);
						file.renameTo(parent, newFile.leafName);
						download.target.path = newFile.path;
						download.refresh();
					} catch {}
				}
			}));
			Object.defineProperty(this, "set", {get() {
				try {var dir = prefs.getComplexValue("browser.download.dir", Ci.nsIFile);} catch {var dir = dirsvc.get("DfltDwnld", Ci.nsIFile);}

				var arr = prefs.getStringPref("ucf_save.dirs", "_Web||_Images|0").split('|').slice(2, 4); // [Загрузки]/папки ucf_save/файл
				arr[1] = (arr[1]) ? wref.get().gBrowser.selectedTab.label.slice(0, 64).replace(/ \| Форум Mozilla Россия$| — Mozilla Firefox|[\\\/?*\"'`]+/g,'').replace(/\s+/g,' ').replace(/[|<>]+/g,'_').replace(/:/g,'։').trim() : ""; // имя вкладки
				arr.map(map).forEach(dir.append); // ucf_save.dirs: путь для html|имя или домен|папка графики|имя вкладки
				dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777); // создать папку, если не существует…
				return dir.path;
			}});
			return this.set;
		}},
		"browser.download.folderList": {type: "Int", set: 2},
		"browser.download.useDownloadDir": {type: "Bool", set: true}
	});
	var MouseImgSaverParent = class extends JSWindowActorParent {
		receiveMessage(msg) {
			var win = msg.target.browsingContext.topChromeWindow;
			var {name} = msg;
			if (name) return this[name](win, msg.data);

			var {url, contentType, contentDisposition, sx, title,
				isPrivate, referrerInfo, cookieJarSettings} = msg.data;
			if (sx && sx > win.mozInnerScreenX + win.innerWidth) return;

			if (title) titles[url] = title;

			wref = Cu.getWeakReference(win);
			var p = win.Services.prefs;

			for(var pref in data) {
				var obj = data[pref], meth = `et${obj.type}Pref`;
				obj.val = p.prefHasUserValue(pref) ? p["g" + meth](pref) : null;
				p["s" + meth](pref, obj.set);
			}
			try {win.internalSave(
				url,
				null, // document
				null, // file name
				contentDisposition,
				contentType,
				false, // do not bypass the cache
				null, // filepicker title key
				null, // chosen data
				u.E10SUtils.deserializeReferrerInfo(referrerInfo),
				u.E10SUtils.deserializeCookieJarSettings(cookieJarSettings),
				win.document, // initiating doc
				true, // skip prompt for where to save
				null, // cache key
				isPrivate,
				win.document.nodePrincipal
			);}
			finally {
				for(var pref in data) data[pref].val === null
					? p.clearUserPref(pref)
					: p[`set${data[pref].type}Pref`](pref, data[pref].val);
			}
		}
		dblclick(win, imgURL) {
			var url = "data:text/plain;charset=utf-8," + encodeURIComponent(
				"Поиск дубликата этой картинки в Яндекс.\n\n" + imgURL
			);
			var gb = win.gBrowser;
			var index = gb.selectedTab._tPos + 1;
			gb.selectedTab = gb.addTrustedTab(url, {index});
		}
	}
}

Я получаю ошибку: e.view is undefined

Ну так Services.wm.getMostRecentWindow("navigator:browser")
это и есть окно (window), назови win и используй вместо e.view

как проверить наличие файла?  chrome://user_chrome_files/content/Help.html

Можно "HEAD" запрос отправить, или попытаться прочитать,
или не как url, а напрямую как файл в профиле, или так

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

Выделить код

Код:

var url = "chrome://user_chrome_files/content/Help.html";
var exists = Cc["@mozilla.org/chrome/chrome-registry;1"]
	.getService(Ci.nsIXULChromeRegistry)
	.convertChromeURL(Services.io.newURI(url))
	.QueryInterface(Ci.nsIFileURL).file.exists();
	
alert(exists);

Как показать текст в строке статуса в течении 5 секунд ? (строка, которая появляется при наведении на ссылку)

Уже спрашивал. Может имеется в виду приколотить,
чтобы за эти 5 секунд браузер там ничего другого не показывал?

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

Выделить код

Код:

function showInStatusPanel(str) {
	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, 5000);
	StatusPanel._label = str;
}
showInStatusPanel(Math.random());

как без использования add…Listener получить данные от FrameScript-функции

В большинстве случаев FrameScript исполняется в другом процессе, так что — никак.
Даже если что-нибудь придумать, то это будет ни сколько не лучше, чем с использованием add…Listener.

из FrameScript не работает gBrowser

FrameScript  исполняется в объекте ContentFrameMessageManager,
глобальный объект для него — тот же, что и у JSM'ок процесса.
Никакого gBrowser'а там нет, gBrowser — это объект окна browser.xhtml,
и, если в этом процессе его ещё можно там получить, то в другом — получать просто нечего.


Выделенный текст, кстати, можно через clipboard взять, если содержимое буфера
не большая ценность, а текст, если в нём был, затем назад вернуть.


Cytrus пишет

alert(document.querySelector('textarea[name="req_message"]').value);

alert(document.location);

Как можно свободно обращаться к элементам DOM?

В многопроцессном Firefox — никак.


ВВП пишет

Переключиться на последнюю используемую вкладку?

Не уверен, что есть «последняя используемая вкладка».
Но вот, наверно, всё, к чему можно прицепиться без специального отслеживающего кода.

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

Выделить код

Код:

gBrowser.selectedTab = Array.from(gBrowser.tabs).reduce((prev, curr) =>
	curr.closing || curr.selected
	|| curr._notselectedsinceload !== false
	|| curr.lastAccessed < prev.lastAccessed
		? prev : curr
, {});

Есть аддон, но как в код предупереждение запихать?

Это Flip Close Tab? Services.prompt.confirm() чем не угодил?

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

Выделить код

Код:

…
                    } else if (event.button == 1) {
                        if (Services.prompt.confirm(null, "Title", "Message"))
                            [...gtBrowser.tabs].forEach((tab)=> gtBrowser.removeTab(tab));

Отсутствует

 

№1561422-05-2021 23:35:19

ВВП
Участник
 
Группа: Members
Зарегистрирован: 13-03-2021
Сообщений: 332
UA: Firefox 87.0

Re: Custom Buttons

Dumby
Класс!

Dumby пишет

Это Flip Close Tab?

Ну,да. Так а на черта оно, если кнопка теперь так же работает.

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

Выделить код

Код:

/*Initialization Code*/


this.onclick = this.oncontextmenu = function(event) {
if (event.button == 1) {

 if (custombuttons.confirmBox(null, "Вниманиее ! Закрыть все вкладки!", "Да", "Отмена") ) {	
                [...gBrowser.tabs].forEach((tab)=> gBrowser.removeTab(tab)); 
}
}

 if(event.button == 0 && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey){
  gBrowser.removeTab(gBrowser.selectedTab);
}
     
if(event.button == 2 && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey){
gBrowser.selectedTab = Array.from(gBrowser.tabs).reduce((prev, curr) =>
	curr.closing || curr.selected
	|| curr._notselectedsinceload !== false
	|| curr.lastAccessed < prev.lastAccessed
		? prev : curr
, {});
          
}      
};

this.oncontextmenu =e=> { e.button && !e.ctrlKey && e.preventDefault() };
 
this.tooltipText = "ЛКМ: Закрыть вкладку\nСКМ: Закрыть все вкладки \nПКМ: Переключиться на послед. вкладку";

А , вот :hover на кнопку стилем можно делать? Чтоб при этом иконка кнопки другая стала...

Отредактировано ВВП (23-05-2021 00:23:44)

Отсутствует

 

№1561523-05-2021 06:06:20

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

Re: Custom Buttons

Dumby - спасибо, доработал всё, что мог! :) и свёл в демо-профиль все наработки - тестируйте!


Есть проблема - делаю СКМ LongClick и при отпускании кнопки ошибка: Uncaught TypeError: linkedObject.auxclick is not a function
Dumby - проверь пожалуйста скрипт переключения настроек либо отдельно, либо скачав расширенный профиль.
эта ошибка не влияет на работу кнопки, но я её не смог устранить ! :(

Описание расширенного профиля Firefox
В строке адреса наглядно виден прогресс загрузки страниц в виде заполняющего градиента.
Сохранение страниц из режима Read View правым кликом по кнопке Загрузки.
Панель поиска и Поиск на странице Быстрого доступа сделаны компактными.
Быстрое переключение более тридцати скрытых настроек браузера.
Открыть ссылку из контекстного меню в программах, указанных в ucf_win_contextmenuopenwith.js.
Расположение закладки в Избранном показывается в подсказке Звёздочки ★.
Перетаскивание или клик колёсиком на картинке сохраняет её в [Загрузки]/_Images
Панель вкладок будет скрыта, если вкладка только одна.
Внешний вид браузера настроен стилями от VitaliyV и aris-t2

8 кнопок (из них 4 в боковой панели) упрощают использование браузера:
Некоторые возможности кнопок (наведите мышь на кнопку для подсказки):
1) стандартная кнопка Меню:
    Правый клик:    ⇲ Свернуть окно
    …Alt                Персонализация\n
    Колёсико:        Закрыть браузер`
2) штатная Кнопка «Загрузки»
    Колёсико: Сохранить (выделенный текст) как файл .txt
        …Shift    Сайт: графика Вкл/Выкл
    ПКМ: Сохранить (выделенный текст) как единый .html
        …Shift    Обзор папки «Загрузки»
    Alt⇧S сохранить страницу как единый .html (встроенным кодом или расширением SingleFile)
3) Избранное - боковые панели, управление опциями…
    ПКМ меню переключения скрытых опций, …держать секунду - Справка, …+Alt    Опции about:config
    ЛКМ - Боковая панель: Журнал, …держать секунду - Вкл/Выкл proxy Антизапрет,
    …+Shift - ★ Библиотека закладки, …+Alt - Пипетка: захват цвета
    Колёсико: Zoom Текст/Страница, …держать: Консоль браузера
    Значки в строках меню опций: ⟳ Обновить страницу, ↯ запрос Перезапуска браузера
4) Восстановить закрытые вкладки/Окна
5) Настройка расширений позволяет отключить скрытые дополнения.
6) ★ в строке адреса - Колёсико: Плитки быстрого доступа,
    Правый клик - восстановить закрытую вкладку
    …+Alt Перевод выделенного текста | Сайта, …+Shift Яндекс: поиск выдел.текста | Перевод сайта
…) ещё 8 кнопок упрощают использование и настройку браузера.

Отсутствует

 

№1561623-05-2021 15:35:08

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

Re: Custom Buttons

Dobrov пишет

не смог устранить

Это надо какой-нибудь auxclick() сюда вписать

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

Выделить код

Код:

…
			//var id, lo = {command: reset, mousedown: reset};
			var id, lo = {command: reset, mousedown: reset, auxclick: e => e.button != 1 || reset(e)};

ВВП пишет

А , вот :hover на кнопку стилем можно делать? Чтоб при этом иконка кнопки другая стала...

Вспоминай.

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

Выделить код

Код:

#custombuttons-button25:hover > image {

	list-style-image: none !important;
	object-position: 16px 0px !important;
	background-position: center !important;
	background-repeat: no-repeat !important;

	background-image: url(chrome://browser/content/robot.ico) !important;
}

Отсутствует

 

№1561723-05-2021 17:36:03

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

Re: Custom Buttons

Dumby пишет

Это надо какой-нибудь auxclick() сюда вписать

Спасибо! Отличная и оперативная помощь! Осталась хотелка по кнопке Чтения…


Будет время, посмотри мой демо-профиль браузера, расширенный необходимым (по моему мнению за 3 месяца отладки) набором функций.
Dumby - может сделаешь обзор возможностей твоего профиля Firefox - какие там есть навороты за столько лет (с 2012 года на форуме) ?

Отсутствует

 

№1561823-05-2021 18:44:01

ВВП
Участник
 
Группа: Members
Зарегистрирован: 13-03-2021
Сообщений: 332
UA: Firefox 87.0

Re: Custom Buttons

Dumby
Облажался ... Теперь класс ! Стиль на "справку" и все. Класс!

скрытый текст
#id:hover > image {

    list-style-image: none !important;
    object-position: 20px 0px !important;
    background-position: center !important;
    background-repeat: no-repeat !important;

    background-image: url("chrome://global/skin/icons/index.png") !important;}
}

Отсутствует

 

№1561924-05-2021 01:03:15

Ferguss114
Участник
 
Группа: Members
Зарегистрирован: 31-03-2012
Сообщений: 207
UA: Chrome 90.0

Re: Custom Buttons

Как открыть обычное окно с заданным адресом и размерами?

Отсутствует

 

№1562024-05-2021 15:11:36

Deriax
Участник
 
Группа: Members
Зарегистрирован: 27-03-2021
Сообщений: 37
UA: Chrome 90.0

Re: Custom Buttons

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

Отсутствует

 

№1562126-05-2021 01:11:20

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

Re: Custom Buttons

Нужно перетаскиванием влево/вправо сохранить в «Загрузки» без запроса рисунок или ссылку на файл.


Расширения не работают из режима чтения. Поискал и не нашёл такой скрипт для user_chrome_files. Наверняка такой код на форуме есть?
Dumby - подскажи, как сохранить ссылку на файл перетаскиванием скриптом UCF?

Отсутствует

 

№1562228-05-2021 22:01:41

Deriax
Участник
 
Группа: Members
Зарегистрирован: 27-03-2021
Сообщений: 37
UA: Chrome 90.0

Re: Custom Buttons

Нужна помощь! Подскажете где можно изучить xpcom, желательно разжёванный материал...

Отредактировано Deriax (29-05-2021 22:40:16)

Отсутствует

 

№1562331-05-2021 20:23:21

ВВП
Участник
 
Группа: Members
Зарегистрирован: 13-03-2021
Сообщений: 332
UA: Firefox 87.0

Re: Custom Buttons

Dumby
Есть код. масштаб на все.  на конкретную страницу никак? К, примеру , на about:addons

скрытый текст
(function(val) {   
    var cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(Ci.nsIContentPrefService2);
    cps2.setGlobal(window.FullZoom.name, val, Cu.createLoadContext());
})(1.15);

Отсутствует

 

№1562431-05-2021 23:18:49

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

Re: Custom Buttons

Dobrov пишет

3) масштаб с отображением процентов на кнопке

Отображение масштаба на бэйдже кнопки что ли,
типа как у кнопок, которые создаёт Firefox для WebExtensions?
«Очки» — эпический комбайн, неудобовместительный в голове,
приступить можно, но как оно сложится — непонятно вообще.


ВВП пишет

Есть код. масштаб на все.  на конкретную страницу никак? К, примеру , на about:addons

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

Выделить код

Код:

((url, val) => FullZoom._cps2.set(
	FullZoom._cps2.extractDomain(url),
	FullZoom.name, val, Cu.createLoadContext()
))("about:addons", 2.4);

Отсутствует

 

№1562501-06-2021 01:06:46

ВВП
Участник
 
Группа: Members
Зарегистрирован: 13-03-2021
Сообщений: 332
UA: Firefox 87.0

Re: Custom Buttons

Dumby
Хорошо, но не очень. Сброс или регулировка масштаба и все, только перезапуск нужен. А Стилем никак? Или тот код подрихтовать?
Нельзя ли в настройках запретить запоминать zoom для кажной страницы?
Поставим вопрос по другому: onclick="FullZoom.reset(); FullZoom.resetScalingZoom();"  - это клик  по масштабу в browser.xhtml , а мне надо такой код:

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

Выделить код

Код:

FullZoom.reset();
(function(val) {    
    var cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(Ci.nsIContentPrefService2);
    cps2.setGlobal(window.FullZoom.name, val, Cu.createLoadContext());
})(1.15);

((url, val) => FullZoom._cps2.set(
	FullZoom._cps2.extractDomain(url),
	FullZoom.name, val, Cu.createLoadContext()
))("about:addons", 1.09);

((url, val) => FullZoom._cps2.set(
	FullZoom._cps2.extractDomain(url),
	FullZoom.name, val, Cu.createLoadContext()
))("https://hdrezka-ag.com/", 1.21);
((url, val) => FullZoom._cps2.set(
	FullZoom._cps2.extractDomain(url),
	FullZoom.name, val, Cu.createLoadContext()
))("https://kinokrad.co/", 1.21);
((url, val) => FullZoom._cps2.set(
	FullZoom._cps2.extractDomain(url),
	FullZoom.name, val, Cu.createLoadContext()
))("https://bigcinema.tv/", 1.21);
((url, val) => FullZoom._cps2.set(
	FullZoom._cps2.extractDomain(url),
	FullZoom.name, val, Cu.createLoadContext()
))("https://filmix.ac/", 1.21);

Рихтовать сам browser.xhtml  - не прошло , как решить проблему ?

Отредактировано ВВП (02-06-2021 11:37:26)

Отсутствует

 

Board footer

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