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

Список ответов на каверзные вопросы можно получить в FAQ-разделе форума.

№1580103-08-2021 10:28:31

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

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

Отсутствует

 

№1580204-08-2021 14:09:47

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
А можно вас еще попросить, вот когда нажимаешь "звездочку" добавить в закладки, которая в адресной строке
Открывается меню добавления в закладки и там есть миниатюра эскиз изображения страницы

скрытый текст
nBhbZXz.png

Вы бы не могли пожалуйста сделать, если можно, чтобы при наведении курсора поверх вкладки это изображение эскиза страницы показывалось под вкладкой?

Отредактировано Stkvsky (04-08-2021 14:10:25)

Отсутствует

 

№1580306-08-2021 08:52:51

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

Re: Custom Buttons

Stkvsky пишет

чтобы при наведении курсора поверх вкладки это изображение эскиза страницы показывалось под вкладкой

Именно это неохота, а какое-нибудь можно попробовать.

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

Выделить код

Код:

(async query => {
	var width = 300;

	var tid, once = {once: true};
	var nh = tab => !tab.matches(":hover");

	await delayedStartupPromise;
	var slot = gBrowser.selectedTab.flattenedTreeParentNode;
	var lst = e => {
		var tab = e.target;
		if (
			tab.nodeName == "tab"
			//&& !tab.selected
			&& tab.linkedPanel
		)
			tid && clearTimeout(tid),
			tid = setTimeout(onTab, 200, tab);
	}
	var args = ["mouseenter", lst, true], cb;
	if (addEventListener != window.addEventListener)
		cb = true,
		addEventListener(...args, slot);
	else
		slot.addEventListener(...args),
		addEventListener("unload", () => 
			slot.removeEventListener(...args)
		, once);

	var onTab = tab => {
		tid = 0;
		if (nh(tab)) return;
		var br = tab.linkedBrowser;
		var cwg = br.browsingContext?.currentWindowGlobal;
		cwg && showPanel(tab, br, cwg);
	}
	var showPanel = (...args) => {
		var popup = (cb ? this : mainPopupSet)
			.appendChild(document.createXULElement("panel"));
		popup.setAttribute("noautofocus", true);
		popup.setAttribute("consumeoutsideclicks", "never");
		var hide = () => popup.hidePopup();

		var canvas = popup.appendChild(document.createElement("canvas"));
		canvas.width = width;
		var context = canvas.getContext("2d", {alpha: false});

		if (parseInt(Services.appinfo.platformVersion) >= 81) {
			var wp = "width", hp = "height";
			query += "Info";
		} else {
			var wp = 0, hp = 1;
			query += "Size";
		}
		(showPanel = async (tab, br, cwg) => {
			var res = await cwg.getActor("Thumbnails").sendQuery(query);
			if (nh(tab)) return;

			var w = res[wp];
			if (w < width) return;

			var h = res[hp], k = width / w;
			try {var bitmap = await cwg.drawSnapshot(
				new DOMRect(0, 0, w, h), k, "white"
			);} catch {return;}

			if (nh(tab) || !bitmap) return;

			canvas.height = k * h;
			context.drawImage(bitmap, 0, 0);
			bitmap.close();
			popup.openPopup(tab, "after_start");
			tab.addEventListener("mouseleave", hide, once);
		})(...args);
	}
})("Browser:Thumbnail:Content");

Отсутствует

 

№1580406-08-2021 10:12:59

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
Ааа:), класс, супер, благодарю, фантастика)

Отсутствует

 

№1580507-08-2021 16:58:19

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
Еще такой вопрос
Сейчас если вкладка не загружена, например после перезапуска браузера, то миниатюра страницы не отображается
А можно ли сделать чтобы эти миниатюры куда нибудь сохранялись и отображались вне зависимости от того загружена вкладка или нет?

Отредактировано Stkvsky (07-08-2021 20:25:48)

Отсутствует

 

№1580607-08-2021 21:10:28

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

Re: Custom Buttons

Dumby пишет

Именно это неохота, а какое-нибудь можно попробовать.

А для TST такое же можете сделать? :)
GitHub - AMO
   
Добавлено 07-08-2021 21:12:29
Stkvsky
Расширение для [firefox]52 просто тупо загружало вкладку и настроить это было нельзя, поэтому выкинул его еще тогда.

Отредактировано _zt (07-08-2021 21:12:44)


Fx 91 esr

Отсутствует

 

№1580709-08-2021 19:45:08

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

Re: Custom Buttons

Stkvsky пишет

сделать чтобы эти миниатюры куда нибудь сохранялись

Эти, это которые уже срисовывались по наведению? Ну да, какие же ещё «эти».
Самое простое, записывать в .json (при выходе). Жуть, конечно.

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

Выделить код

Код:

(async query => {
	var width = 300;

	var tid, once = {once: true};
	var nh = tab => !tab.matches(":hover");

	await delayedStartupPromise;
	var slot = gBrowser.selectedTab.flattenedTreeParentNode;
	var lst = e => {
		var tab = e.target;
		if (
			tab.nodeName == "tab"
			//&& !tab.selected
		)
			tid && clearTimeout(tid),
			tid = setTimeout(onTab, 200, tab);
	}
	var args = ["mouseenter", lst, true], cb = false;
	if (addEventListener != window.addEventListener)
		cb = true,
		addEventListener(...args, slot);
	else
		slot.addEventListener(...args),
		addEventListener("unload", () => 
			slot.removeEventListener(...args)
		, once);

	var g = Cu.getGlobalForObject(Cu);
	var key = "TabHoverSnapshotsStorage";
	var storage = g[key];
	if (!storage) {
		var func = (key, cb) => (this[key] = {get data() {
			var mo = p => ChromeUtils.import(`resource://gre/modules/${p}.jsm`)[p];

			var file = mo("Services").dirsvc.get("ProfD", Ci.nsIFile);
			file.append(key + ".json");

			var data = Object.create(null);
			try {Object.assign(data, JSON.parse(Cu.readUTF8File(file)));} catch {}

			var skip = true;
			var blocker = () => {
				if (skip) return Promise.resolve();
				var fu = mo("FileUtils");
				var sfs = fu.openSafeFileOutputStream(file);
				var sis = Cc["@mozilla.org/io/string-input-stream;1"]
					.createInstance(Ci.nsISupportsCString);
				sis.data = JSON.stringify(data, null, "\t");

				return new Promise(resolve => mo("NetUtil").asyncCopy(
					sis, sfs, () => resolve(fu.closeSafeFileOutputStream(sfs))
				));
			}
			var pbc = mo("AsyncShutdown").profileBeforeChange;
			pbc.addBlocker(key, blocker);
			if (cb) this.destroy = save => {
				delete globalThis[key];
				pbc.removeBlocker(blocker);
				save && blocker();
			}
			this.images = Object.create(null);
			this.set = (...args) => {
				skip = false;
				(this.set = (url, src) => data[url] = src)(...args);
			}
			delete this.data;
			return this.data = data;
		}});
		storage = g.eval(`(${func})("${key}", ${cb});`);
	}
	cb && addDestructor(r => r[5] == "e" && g[key]?.destroy(r[0] == "u"));

	var onTab = tab => {
		tid = 0;
		if (nh(tab)) return;
		var br = tab.linkedBrowser;
		var cwg = br.browsingContext?.currentWindowGlobal;
		openPopup(tab, br, cwg);
	}
	var openPopup = (...args) => {
		var popup = (cb ? this : mainPopupSet)
			.appendChild(document.createXULElement("menupopup"));

		popup.setAttribute("ignorekeys", true);
		popup.setAttribute("consumeoutsideclicks", "never");
		popup.shadowRoot.querySelector("style").append(`
			:host {
				padding: 0 !important;
				margin-top: 1px !important;
				-moz-appearance: none !important;
			}
			arrowscrollbox::part(scrollbutton-up),
			arrowscrollbox::part(scrollbutton-down) {
				display: none !important;
			}
		`);
		var hide = () => popup.hidePopup();

		var canvas = popup.appendChild(document.createElement("canvas"));
		canvas.width = width;
		var context = canvas.getContext("2d", {alpha: false});
		
		if (parseInt(Services.appinfo.platformVersion) >= 81) {
			var wp = "width", hp = "height";
			query += "Info";
		} else {
			var wp = 0, hp = 1;
			query += "Size";
		}
		(openPopup = async (tab, br, cwg) => {
			var url = br.currentURI.specIgnoringRef;
			var store = url.length < 640;
			if (tab.linkedPanel) {
				if (!cwg) return;
				var res = await cwg.getActor("Thumbnails").sendQuery(query);
				if (nh(tab)) return;

				var w = res[wp];
				if (w < width) return;

				var h = res[hp], k = width / w;
				try {var bitmap = await cwg.drawSnapshot(
					new DOMRect(0, 0, w, h), k, "white"
				);} catch {}

				if (nh(tab) || !bitmap) return;

				canvas.height = k * h;
				context.drawImage(bitmap, 0, 0);
				bitmap.close();
				if (store) {
					var src = canvas.toDataURL();
					if (src != storage.data[url])
						storage.set(url, src),
						delete storage.images[url];
				}
			} else {
				if (!store) return;
				var img = storage.images?.[url];
				if (!img) {
					var src = storage.data[url];
					if (!src) return;
					img = storage.images[url] = new Image();
					img.src = src;
					await new Promise(resolve => img.onloadend = resolve);
					if (nh(tab)) return;
				}
				context.drawImage(img, 0, 0, width, canvas.height =
					img.naturalHeight * width / img.naturalWidth
				);
			}
			popup.openPopup(tab, "after_start");
			tab.addEventListener("mouseleave", hide, once);
		})(...args);
	}
})("Browser:Thumbnail:Content");

_zt пишет

А для TST такое же можете сделать?

Попробовал JSM'кой, вроде чего-то показывает.
Импортировать из custom_script.js, примерно так:
(async url => ChromeUtils.import(url))(
    "chrome://user_chrome_files/content/custom_scripts/TreeStyleTabPreviewPopup.jsm"
);

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

Выделить код

Код:

var EXPORTED_SYMBOLS = ["TreeStyleTabPreviewPopupChild", "TreeStyleTabPreviewPopupParent"];
if (!ChromeUtils.domProcessChild.childID) {

	var popupWidth = 300;

	var name = "TreeStyleTabPreviewPopup";
	var host, tstId = "treestyletab@piro.sakura.ne.jp";

	var manager = ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm")
		.ExtensionParent.apiManager;
	var tt = manager.global.tabTracker;

	var wait = (e, isAppShutdown) => isAppShutdown || manager.on("ready", onReady);
	var onReady = (e, addon) => {
		if (addon.id != tstId) return;
		manager.off("ready", onReady);
		addon.once("shutdown", wait);
		init(addon.uuid);
	}
	wait();

	var init = uuid => {
		if (host == uuid) return;
		host && ChromeUtils.unregisterWindowActor(name);

		ChromeUtils.registerWindowActor(name, {
			parent: {moduleURI: __URI__},
			messageManagerGroups: ["webext-browsers"],
			child: {moduleURI: __URI__, events: {mouseover: {}}},
			matches: [`moz-extension://${host = uuid}/sidebar/sidebar.html*`]
		});
	}
	var popupId = "ucf-tst-preview-popup";

	var TreeStyleTabPreviewPopupParent = class extends JSWindowActorParent {
		actorCreated() {
			var doc = this.browsingContext.topChromeWindow.document;
			var popup = doc.getElementById(popupId);
			if (!popup) {
				popup = doc.createXULElement("menupopup");
				popup.id = popupId;
				popup.setAttribute("ignorekeys", true);
				popup.setAttribute("consumeoutsideclicks", "never");
				popup.shadowRoot.querySelector("style").append(`
					:host {
						padding: 0 !important;
						-moz-appearance: none !important;
					}
					arrowscrollbox::part(scrollbutton-up),
					arrowscrollbox::part(scrollbutton-down) {
						display: none !important;
					}
				`);
				(popup.canvas = popup.appendChild(doc.createElement("canvas")))
					.width = popupWidth;
				popup.context = popup.canvas.getContext("2d", {alpha: false});
				doc.getElementById("mainPopupSet").append(popup);
			}
			this.popup = popup;
		}
		receiveMessage(msg) {
			var id = msg.data;
			if (!id) return this.popup.hidePopup();

			var tab = tt.getTab(+id.slice(4));
			if (tab/* && !tab.selected*/) {
				var cwg = tab.linkedBrowser.browsingContext?.currentWindowGlobal;
				cwg && this.drawSnapshot(tab.ownerDocument, cwg, id);
			}
		}
		async drawSnapshot(doc, cwg, id) {
			var {width, height} = await cwg.getActor("Thumbnails")
				.sendQuery("Browser:Thumbnail:ContentInfo");
			if (width < 200) return;

			var k = popupWidth / width;
			try {var bitmap = await cwg.drawSnapshot(
				new DOMRect(0, 0, width, height), k, "white"
			);} catch {}

			if (bitmap) {
				var data = await this.sendQuery(id);
				if (data) {
					this.popup.canvas.height = k * height;
					this.popup.context.drawImage(bitmap, 0, 0);
					bitmap.close();
					this.popup.openPopupAtScreenRect("after_start", ...data);
				}
			}
		}
		didDestroy() {
			this.popup = null;
		}
	}
}

class TreeStyleTabPreviewPopupChild extends JSWindowActorChild {
	actorCreated() {
		this.args = ["mouseleave", () => {
			this.tab = null;
			this.tid || this.sendAsyncMessage("");
			this.tid = this.clearTimeout();
		}, {once: true}];
	}
	mult(val) {
		return this * val;
	}
	receiveMessage(msg) {
		var tab = this.document.getElementById(msg.name);
		var res = tab?.matches(":hover");
		if (res) {
			var {x, y, width, height} = tab.getBoundingClientRect();
			var win = tab.ownerGlobal;
			res = [
				x + win.mozInnerScreenX,
				y + win.mozInnerScreenY,
				width, height
			];
			var z = win.windowUtils.screenPixelsPerCSSPixel;
			if (z != 1) res = res.map(this.mult, z);
		}
		return res;
	}
	handleEvent(e) {
		var tab = e.target.closest("tab-item");
		if (!tab || tab == this.tab) return;
		this.clearTimeout();
		this.tid = this.contentWindow
			.setTimeout(this.onTab, 200, this.tab = tab, this);
		tab.addEventListener(...this.args);
	}
	clearTimeout() {
		this.tid && this.contentWindow.clearTimeout(this.tid);
	}
	onTab(tab, self) {
		self.tid = null;
		tab.wrappedJSObject.apiTab.discarded
			|| self.sendAsyncMessage("", tab.id);
	}
	didDestroy() {
		this.tab = null;
	}
}


Custom Buttons 0.0.7.0.0.19, paxmod и bootstrap в zip-папке.
Плюс, добавлен Custom Buttons 91.0 для Thunderbird 91.0
(только для TB, и только для TB 91).

Отсутствует

 

№1580809-08-2021 20:06:13

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby пишет

Эти, это которые уже срисовывались по наведению? Ну да, какие же ещё «эти».
Самое простое, записывать в .json (при выходе). Жуть, конечно.

Да эти:), извиняюсь за французский) работает, класс, благодарю

Отредактировано Stkvsky (09-08-2021 20:11:13)

Отсутствует

 

№1580909-08-2021 22:19:21

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

Re: Custom Buttons

Dumby
Шикарно, спасибо.
Можете, пожалуйста, добавить:
    1. Тайм-аут появления миниатюры.
    2. Авто-расположение -
        слева от tab-item, если панель справа,
        справа от tab-item, если панель слева.
       С выравниванием верхнего края миниатюры по верхнему краю tab-item.
    3. Кнопки включения отключения скрипта, примерно как это сделано здесь.


Fx 91 esr

Отсутствует

 

№1581010-08-2021 12:41:54

dezhnev
Участник
 
Группа: Members
Зарегистрирован: 21-04-2016
Сообщений: 71
UA: Firefox 78.0

Re: Custom Buttons

Попробовал JSM'кой, вроде чего-то показывает.

Ох какая годнота, спасибо Dumby !

А можно попросить для TST сделать что-то подобное, чтобы отображалось "host | title", без переписывания тайтла страниц?
https://forum.mozilla-russia.org/viewtopic.php?pid=788775#p788775
начало тут
https://forum.mozilla-russia.org/viewtopic.php?pid=788701#p788701

Отсутствует

 

№1581110-08-2021 13:53:04

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
ВЫ бы не могли пожалуйста подправить, сейчас когда отображается миниатюра вкладки при наведении - не работает скролл колесом

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

Выделить код

Код:

(async query => {
	var width = 300;

	var tid, once = {once: true};
	var nh = tab => !tab.matches(":hover");

	await delayedStartupPromise;
	var slot = gBrowser.selectedTab.flattenedTreeParentNode;
	var lst = e => {
		var tab = e.target;
		if (
			tab.nodeName == "tab"
			//&& !tab.selected
		)
			tid && clearTimeout(tid),
			tid = setTimeout(onTab, 200, tab);
	}
	var args = ["mouseenter", lst, true], cb = false;
	if (addEventListener != window.addEventListener)
		cb = true,
		addEventListener(...args, slot);
	else
		slot.addEventListener(...args),
		addEventListener("unload", () => 
			slot.removeEventListener(...args)
		, once);

	var g = Cu.getGlobalForObject(Cu);
	var key = "TabHoverSnapshotsStorage";
	var storage = g[key];
	if (!storage) {
		var func = (key, cb) => (this[key] = {get data() {
			var mo = p => ChromeUtils.import(`resource://gre/modules/${p}.jsm`)[p];

			var file = mo("Services").dirsvc.get("ProfD", Ci.nsIFile);
			file.append(key + ".json");

			var data = Object.create(null);
			try {Object.assign(data, JSON.parse(Cu.readUTF8File(file)));} catch {}

			var skip = true;
			var blocker = () => {
				if (skip) return Promise.resolve();
				var fu = mo("FileUtils");
				var sfs = fu.openSafeFileOutputStream(file);
				var sis = Cc["@mozilla.org/io/string-input-stream;1"]
					.createInstance(Ci.nsISupportsCString);
				sis.data = JSON.stringify(data, null, "\t");

				return new Promise(resolve => mo("NetUtil").asyncCopy(
					sis, sfs, () => resolve(fu.closeSafeFileOutputStream(sfs))
				));
			}
			var pbc = mo("AsyncShutdown").profileBeforeChange;
			pbc.addBlocker(key, blocker);
			if (cb) this.destroy = save => {
				delete globalThis[key];
				pbc.removeBlocker(blocker);
				save && blocker();
			}
			this.images = Object.create(null);
			this.set = (...args) => {
				skip = false;
				(this.set = (url, src) => data[url] = src)(...args);
			}
			delete this.data;
			return this.data = data;
		}});
		storage = g.eval(`(${func})("${key}", ${cb});`);
	}
	cb && addDestructor(r => r[5] == "e" && g[key]?.destroy(r[0] == "u"));

	var onTab = tab => {
		tid = 0;
		if (nh(tab)) return;
		var br = tab.linkedBrowser;
		var cwg = br.browsingContext?.currentWindowGlobal;
		openPopup(tab, br, cwg);
	}
	var openPopup = (...args) => {
		var popup = (cb ? this : mainPopupSet)
			.appendChild(document.createXULElement("menupopup"));

		popup.setAttribute("ignorekeys", true);
		popup.setAttribute("consumeoutsideclicks", "never");
		popup.shadowRoot.querySelector("style").append(`
			:host {
				padding: 0 !important;
				margin-top: 1px !important;
				-moz-appearance: none !important;
			}
			arrowscrollbox::part(scrollbutton-up),
			arrowscrollbox::part(scrollbutton-down) {
				display: none !important;
			}
		`);
		var hide = () => popup.hidePopup();

		var canvas = popup.appendChild(document.createElement("canvas"));
		canvas.width = width;
		var context = canvas.getContext("2d", {alpha: false});
		
		if (parseInt(Services.appinfo.platformVersion) >= 81) {
			var wp = "width", hp = "height";
			query += "Info";
		} else {
			var wp = 0, hp = 1;
			query += "Size";
		}
		(openPopup = async (tab, br, cwg) => {
			var url = br.currentURI.specIgnoringRef;
			var store = url.length < 640;
			if (tab.linkedPanel) {
				if (!cwg) return;
				var res = await cwg.getActor("Thumbnails").sendQuery(query);
				if (nh(tab)) return;

				var w = res[wp];
				if (w < width) return;

				var h = res[hp], k = width / w;
				try {var bitmap = await cwg.drawSnapshot(
					new DOMRect(0, 0, w, h), k, "white"
				);} catch {}

				if (nh(tab) || !bitmap) return;

				canvas.height = k * h;
				context.drawImage(bitmap, 0, 0);
				bitmap.close();
				if (store) {
					var src = canvas.toDataURL();
					if (src != storage.data[url])
						storage.set(url, src),
						delete storage.images[url];
				}
			} else {
				if (!store) return;
				var img = storage.images?.[url];
				if (!img) {
					var src = storage.data[url];
					if (!src) return;
					img = storage.images[url] = new Image();
					img.src = src;
					await new Promise(resolve => img.onloadend = resolve);
					if (nh(tab)) return;
				}
				context.drawImage(img, 0, 0, width, canvas.height =
					img.naturalHeight * width / img.naturalWidth
				);
			}
			popup.openPopup(tab, "after_start");
			tab.addEventListener("mouseleave", hide, once);
		})(...args);
	}
})("Browser:Thumbnail:Content");

Отредактировано Stkvsky (10-08-2021 14:04:17)

Отсутствует

 

№1581210-08-2021 19:08:19

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

Re: Custom Buttons

_zt пишет

Можете, пожалуйста, добавить

Попробую

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

Выделить код

Код:

var timeout = 500;

if (!ChromeUtils.domProcessChild.childID) {

	var popupWidth = 300;

	var label = "Some Label";
	var tooltiptext = "Some Tooltip Text";
	var imgEnabled = "chrome://browser/skin/preferences/face-smile.svg";
	var imgDisabled = "chrome://browser/skin/preferences/face-sad.svg";


	var btnImage, popupPosition, enabled, addonUUID, registeredUUID;
	var mo = (p, r = "gre") => ChromeUtils.import(`resource://${r}/modules/${p}.jsm`)[p];

	//-------[ Addon ]------------------------------------------------------

	var addonId = "treestyletab@piro.sakura.ne.jp";
	var manager = mo("ExtensionParent").apiManager;
	var tt = manager.global.tabTracker;

	var waitAddon = (e, isAppShutdown) => isAppShutdown || (
		addonUUID = null, manager.on("ready", onReady)
	);
	var onReady = (e, addon) => {
		if (addon.id != addonId) return;
		manager.off("ready", onReady);
		addon.once("shutdown", waitAddon);
		addonUUID = addon.uuid;
		checkRegistration();
	}
	waitAddon();

	//-------[ Actor registration ]------------------------------------------------------

	var name = "TreeStyleTabPreviewPopup";

	var reg = () => ChromeUtils.registerWindowActor(name, {
		parent: {moduleURI: __URI__},
		messageManagerGroups: ["webext-browsers"],
		child: {moduleURI: __URI__, events: {mouseover: {}}},
		matches: [`moz-extension://${registeredUUID = addonUUID}/sidebar/sidebar.html*`]
	});
	var unreg = () => {
		registeredUUID = null;
		ChromeUtils.unregisterWindowActor(name);
	}
	var checkRegistration = () => {
		if (enabled) {
			if (registeredUUID) {
				if (registeredUUID == addonUUID) return;
				addonUUID && unreg();
			}
			addonUUID && reg();
		}
		else if (registeredUUID && addonUUID) unreg();
	}

	//------[ Observer ]------------------------------------------------------

	var {prefs, obs} = mo("Services");
	var pref = "ucf_tst_preview_popup";
	var branch = prefs.getBranch("sidebar.");

	var prefObs = {
		observe(b, t, data) {
			this[data]?.(branch.getBoolPref(data, true));
		},
		position_start: val => popupPosition = val ? "end_before" : "start_before",
		[pref](val) {
			btnImage = (enabled = val) ? imgEnabled : imgDisabled;
			this.setBtnsImg();
			checkRegistration();
		},
		setBtnsImg: () => prefObs.setBtnsImg = () => {
			var widget = cui.getWidget(btnId);
			for(var win of cui.windows)
				widget.forWindow(win).node?.setAttribute("image", btnImage);
		}
	};
	for (let p of [pref, "position_start"]) prefObs.observe(null, null, p);

	branch.addObserver("", prefObs);
	obs.addObserver(function quit(s, topic) {
		obs.removeObserver(quit, topic);
		branch.removeObserver("", prefObs);
	}, "quit-application-granted");

	//-------[ Widget ]------------------------------------------------------

	var popupId = "ucf-tst-preview-popup";
	var btnId = popupId + "-button";
	var cui = mo("CustomizableUI", "");
	var toggle = () => branch.setBoolPref(pref, !enabled);

	cui.createWidget({
		id: btnId, label, tooltiptext, localized: false,
		onCreated(btn) {
			btn.render = this.render;
			btn._handleClick = toggle;
			btn.setAttribute("image", btnImage);
		},
		render() {
			delete this.render;
			this.render();
			this.firstChild.style.setProperty("min-height", "16px", "important");
		}
	});

	//-------[ Actor ]------------------------------------------------------

	var TreeStyleTabPreviewPopupParent = class extends JSWindowActorParent {
		actorCreated() {
			var doc = this.browsingContext.topChromeWindow.document;
			var popup = doc.getElementById(popupId);
			if (!popup) {
				popup = doc.createXULElement("menupopup");
				popup.id = popupId;
				popup.setAttribute("ignorekeys", true);
				popup.setAttribute("rolluponmousewheel", true);
				popup.setAttribute("consumeoutsideclicks", "never");
				popup.shadowRoot.querySelector("style").append(`
					:host {
						padding: 0 !important;
						-moz-appearance: none !important;
					}
					arrowscrollbox::part(scrollbutton-up),
					arrowscrollbox::part(scrollbutton-down) {
						display: none !important;
					}
				`);
				(popup.canvas = popup.appendChild(doc.createElement("canvas")))
					.width = popupWidth;
				popup.context = popup.canvas.getContext("2d", {alpha: false});
				doc.getElementById("mainPopupSet").append(popup);
			}
			this.popup = popup;
		}
		receiveMessage(msg) {
			var id = msg.data;
			if (!id) return this.popup.hidePopup();

			var tab = tt.getTab(+id.slice(4));
			if (tab/* && !tab.selected*/) {
				var cwg = tab.linkedBrowser.browsingContext?.currentWindowGlobal;
				cwg && this.drawSnapshot(tab.ownerDocument, cwg, id);
			}
		}
		async drawSnapshot(doc, cwg, id) {
			var {width, height} = await cwg.getActor("Thumbnails")
				.sendQuery("Browser:Thumbnail:ContentInfo");
			if (width < 200) return;

			var k = popupWidth / width;
			try {var bitmap = await cwg.drawSnapshot(
				new DOMRect(0, 0, width, height), k, "white"
			);} catch {}

			if (bitmap) {
				var data = await this.sendQuery(id);
				if (data) {
					this.popup.canvas.height = k * height;
					this.popup.context.drawImage(bitmap, 0, 0);
					bitmap.close();
					this.popup.openPopupAtScreenRect(popupPosition, ...data);
				}
			}
		}
		didDestroy() {
			this.popup.hidePopup();
			this.popup = null;
		}
	}
}

class TreeStyleTabPreviewPopupChild extends JSWindowActorChild {
	actorCreated() {
		this.args = ["mouseleave", () => {
			this.tab = null;
			this.tid || this.sendAsyncMessage("");
			this.tid = this.clearTimeout();
		}, {once: true}];
	}
	mult(val) {
		return this * val;
	}
	receiveMessage(msg) {
		var tab = this.document.getElementById(msg.name);
		var res = tab?.matches(":hover");
		if (res) {
			var {x, y, width, height} = tab.getBoundingClientRect();
			var win = tab.ownerGlobal;
			res = [
				x + win.mozInnerScreenX,
				y + win.mozInnerScreenY,
				width, height
			];
			var z = win.windowUtils.screenPixelsPerCSSPixel;
			if (z != 1) res = res.map(this.mult, z);
		}
		return res;
	}
	handleEvent(e) {
		var tab = e.target.closest("tab-item");
		if (!tab || tab == this.tab) return;
		this.clearTimeout();
		this.tid = this.contentWindow
			.setTimeout(this.onTab, timeout, this.tab = tab, this);
		tab.addEventListener(...this.args);
	}
	clearTimeout() {
		this.tid && this.contentWindow.clearTimeout(this.tid);
	}
	onTab(tab, self) {
		self.tid = null;
		tab.wrappedJSObject.apiTab.discarded
			|| self.sendAsyncMessage("", tab.id);
	}
	didDestroy() {
		this.tab = null;
	}
}
var EXPORTED_SYMBOLS = ["TreeStyleTabPreviewPopupChild", "TreeStyleTabPreviewPopupParent"];

dezhnev пишет

чтобы отображалось "host | title", без переписывания тайтла страниц

Дописал некое вмешательство в местную кустомэлементщину.
Ненадёжно это, наверно.

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

Выделить код

Код:

var EXPORTED_SYMBOLS = ["TreeStyleTabPreviewPopupChild", "TreeStyleTabPreviewPopupParent"];
if (!ChromeUtils.domProcessChild.childID) {

	var popupWidth = 300;

	var name = "TreeStyleTabPreviewPopup";
	var host, tstId = "treestyletab@piro.sakura.ne.jp";

	var manager = ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm")
		.ExtensionParent.apiManager;
	var tt = manager.global.tabTracker;

	var wait = (e, isAppShutdown) => isAppShutdown || manager.on("ready", onReady);
	var onReady = (e, addon) => {
		if (addon.id != tstId) return;
		manager.off("ready", onReady);
		addon.once("shutdown", wait);
		init(addon.uuid);
	}
	wait();

	var init = uuid => {
		if (host == uuid) return;
		host && ChromeUtils.unregisterWindowActor(name);

		ChromeUtils.registerWindowActor(name, {
			parent: {moduleURI: __URI__},
			messageManagerGroups: ["webext-browsers"],
			child: {moduleURI: __URI__, events: {DOMDocElementInserted: {}, mouseover: {}}},
			matches: [`moz-extension://${host = uuid}/sidebar/sidebar.html*`]
		});
	}
	var popupId = "ucf-tst-preview-popup";

	var TreeStyleTabPreviewPopupParent = class extends JSWindowActorParent {
		actorCreated() {
			var doc = this.browsingContext.topChromeWindow.document;
			var popup = doc.getElementById(popupId);
			if (!popup) {
				popup = doc.createXULElement("menupopup");
				popup.id = popupId;
				popup.setAttribute("ignorekeys", true);
				popup.setAttribute("rolluponmousewheel", true);
				popup.setAttribute("consumeoutsideclicks", "never");
				popup.shadowRoot.querySelector("style").append(`
					:host {
						padding: 0 !important;
						-moz-appearance: none !important;
					}
					arrowscrollbox::part(scrollbutton-up),
					arrowscrollbox::part(scrollbutton-down) {
						display: none !important;
					}
				`);
				(popup.canvas = popup.appendChild(doc.createElement("canvas")))
					.width = popupWidth;
				popup.context = popup.canvas.getContext("2d", {alpha: false});
				doc.getElementById("mainPopupSet").append(popup);
			}
			this.popup = popup;
		}
		receiveMessage(msg) {
			var id = msg.data;
			if (!id) return this.popup.hidePopup();

			var tab = tt.getTab(+id.slice(4));
			if (tab/* && !tab.selected*/) {
				var cwg = tab.linkedBrowser.browsingContext?.currentWindowGlobal;
				cwg && this.drawSnapshot(tab.ownerDocument, cwg, id);
			}
		}
		async drawSnapshot(doc, cwg, id) {
			var {width, height} = await cwg.getActor("Thumbnails")
				.sendQuery("Browser:Thumbnail:ContentInfo");
			if (width < 200) return;

			var k = popupWidth / width;
			try {var bitmap = await cwg.drawSnapshot(
				new DOMRect(0, 0, width, height), k, "white"
			);} catch {}

			if (bitmap) {
				var data = await this.sendQuery(id);
				if (data) {
					this.popup.canvas.height = k * height;
					this.popup.context.drawImage(bitmap, 0, 0);
					bitmap.close();
					this.popup.openPopupAtScreenRect("after_start", ...data);
				}
			}
		}
		didDestroy() {
			this.popup.hidePopup();
			this.popup = null;
		}
	}
}

function updateTextContent() {
	var span = this.firstElementChild;
	if (!span) return;

	var val = this.getAttribute("value");
	var url = this.parentNode.dataset.currentUri;
	if (url?.startsWith("http")) try {
		var {hostname} = new URL(url);
		if (hostname) val = `${hostname} | ${val}`;
	} catch {}

	span.textContent = val || "";
}

class TreeStyleTabPreviewPopupChild extends JSWindowActorChild {
	actorCreated() {
		this.args = ["mouseleave", () => {
			this.tab = null;
			this.tid || this.sendAsyncMessage("");
			this.tid = this.clearTimeout();
		}, {once: true}];
	}
	mult(val) {
		return this * val;
	}
	receiveMessage(msg) {
		var tab = this.document.getElementById(msg.name);
		var res = tab?.matches(":hover");
		if (res) {
			var {x, y, width, height} = tab.getBoundingClientRect();
			var win = tab.ownerGlobal;
			res = [
				x + win.mozInnerScreenX,
				y + win.mozInnerScreenY,
				width, height
			];
			var z = win.windowUtils.screenPixelsPerCSSPixel;
			if (z != 1) res = res.map(this.mult, z);
		}
		return res;
	}
	labDefined(lab) {
		lab.wrappedJSObject.prototype
			.updateTextContent = updateTextContent;
	}
	handleEvent(e) {
		e.target.ownerGlobal.customElements
			.whenDefined("tab-label").then(this.labDefined);
		this.handleEvent = this.mouseover;
	}
	mouseover(e) {
		var tab = e.target.closest("tab-item");
		if (!tab || tab == this.tab) return;
		this.clearTimeout();
		this.tid = this.contentWindow
			.setTimeout(this.onTab, 200, this.tab = tab, this);
		tab.addEventListener(...this.args);
	}
	clearTimeout() {
		this.tid && this.contentWindow.clearTimeout(this.tid);
	}
	onTab(tab, self) {
		self.tid = null;
		tab.wrappedJSObject.apiTab.discarded
			|| self.sendAsyncMessage("", tab.id);
	}
	didDestroy() {
		this.tab = null;
	}
}

Stkvsky пишет

когда отображается миниатюра вкладки при наведении - не работает скролл колесом

Да, вижу. Вроде помогает, если
после
popup.setAttribute("ignorekeys", true);
добавить
popup.setAttribute("rolluponmousewheel", true);

Отсутствует

 

№1581311-08-2021 06:29:13

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

Re: Custom Buttons

Dumby
Спасибо. Еще один шедевр. :)


Fx 91 esr

Отсутствует

 

№1581411-08-2021 11:01:03

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
Класс, спасибо:)

Отсутствует

 

№1581513-08-2021 12:05:41

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

Re: Custom Buttons

Dumby
При исп.  скрипта миниатюр вкладок в профиле появляется TabHoverSnapshotsStorage.json. Так и должно быть?  Или  это кто-то попросил ? Можно избавиться?
Сделал по наитию ,но вря дли это кошерно... var key = "TabHoverSnapshotsStorage"; , на var key = "";

Отредактировано ВВП (16-08-2021 20:45:09)

Отсутствует

 

№1581613-08-2021 14:07:39

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

Re: Custom Buttons

ВВП пишет

это кто-то попросил ? Можно избавиться?

Ну да, Stkvsky попросил:
«А можно ли сделать чтобы эти миниатюры куда нибудь сохранялись …».


Если хочешь, могу вырезать

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

Выделить код

Код:

(async query => {
	var width = 300;
	var timeout = 400;

	var tid, once = {once: true};
	var nh = tab => !tab.matches(":hover");

	await delayedStartupPromise;
	var slot = gBrowser.selectedTab.flattenedTreeParentNode;
	var lst = e => {
		var tab = e.target;
		if (
			tab.nodeName == "tab"
			//&& !tab.selected
			&& tab.linkedPanel
		)
			tid && clearTimeout(tid),
			tid = setTimeout(onTab, timeout, tab);
	}
	var args = ["mouseenter", lst, true], cb;
	if (addEventListener != window.addEventListener)
		cb = true,
		addEventListener(...args, slot);
	else
		slot.addEventListener(...args),
		addEventListener("unload", () => 
			slot.removeEventListener(...args)
		, once);

	var onTab = tab => {
		tid = 0;
		if (nh(tab)) return;
		var cwg = tab.linkedBrowser
			.browsingContext?.currentWindowGlobal;
		cwg && openPopup(tab, cwg);
	}
	var openPopup = (...args) => {
		var popup = (cb ? this : mainPopupSet)
			.appendChild(document.createXULElement("menupopup"));

		popup.setAttribute("ignorekeys", true);
		popup.setAttribute("rolluponmousewheel", true);
		popup.setAttribute("consumeoutsideclicks", "never");
		popup.shadowRoot.querySelector("style").append(`
			:host {
				padding: 0 !important;
				margin-top: 1px !important;
				-moz-appearance: none !important;
			}
			arrowscrollbox::part(scrollbutton-up),
			arrowscrollbox::part(scrollbutton-down) {
				display: none !important;
			}
		`);
		var hide = () => popup.hidePopup();

		var canvas = popup.appendChild(document.createElement("canvas"));
		canvas.width = width;
		var context = canvas.getContext("2d", {alpha: false});

		if (parseInt(Services.appinfo.platformVersion) >= 81) {
			var wp = "width", hp = "height";
			query += "Info";
		} else {
			var wp = 0, hp = 1;
			query += "Size";
		}
		(openPopup = async (tab, cwg) => {
			var res = await cwg.getActor("Thumbnails").sendQuery(query);
			if (nh(tab)) return;

			var w = res[wp];
			if (w < width) return;

			var h = res[hp], k = width / w;
			try {var bitmap = await cwg.drawSnapshot(
				new DOMRect(0, 0, w, h), k, "white"
			);} catch {}

			if (nh(tab) || !bitmap) return;

			canvas.height = k * h;
			context.drawImage(bitmap, 0, 0);
			bitmap.close();

			popup.openPopup(tab, "after_start");
			tab.addEventListener("mouseleave", hide, once);
		})(...args);
	}
})("Browser:Thumbnail:Content");

Отсутствует

 

№1581713-08-2021 14:40:53

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

Re: Custom Buttons

Dumby

Dumby пишет

Если хочешь, могу вырезать

Тонкий ход ! Еще бы на пустые вкладки не наводилось, типа about:blank , newtab, home, Впрочем итак классно...

Отсутствует

 

№1581814-08-2021 01:28:16

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
Вы бы не могли пожалуйста добавить в этот код еще одну функцию?
Чтобы при открытии со строки поиска вкладки с результатами поиска, автоматически открывалась еще пустая вкладка(about:blank)

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

Выделить код

Код:

(async (sel, self) => ({

	icon: "circle",
	colors: [
		"#FF9800",
		"#03A9F4",
		"#FFC107",
		"#00BCD4",
		"#FFEB3B",
		"#009688",
		"#CDDC39",
		"#4CAF50",
		"#8BC34A",
		"#D32F2F",
		"#4949ff",
		"#C2185B",
		"#607D8B",
		"#7B1FA2",
		"#9E9E9E",
		"#673AB7",
		"#795548",
		"#3F51B5",
		"#FF5722",
		"#2196F3",
	],

	initColors() {
		var colorName = "ucf-gen";
		var css = "@-moz-document url(about:preferences#containers),"
			+ " url-prefix(chrome://browser/content/browser.x) {\n";
		this.colors.forEach((color, ind) => {
			var [ic, tc] = color.split(/\s*\|\s*/);
			css += `\t.identity-color-${colorName}${ind} {\n`
				+ `\t\t--identity-tab-color: ${tc || ic};\n`
				+ `\t\t--identity-icon-color: ${ic};\n\t}\n`
		});
		var url = "data:text/css;charset=utf-8," + encodeURIComponent(css + "}");
		var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
			.getService(Ci.nsIStyleSheetService);
		sss.loadAndRegisterSheet(Services.io.newURI(url), sss.USER_SHEET);

		var len = this.colors.length;
		var pref = "ucf.openInGeneratedContainer.lastColor";
		var ind = Math.min(Services.prefs.getIntPref(pref, -1), len - 1);
		this.nextColor = () => {
			var next = ind + 1;
			Services.prefs.setIntPref(pref, ind = next == len ? 0 : next);
			return colorName + ind;
		}
	},
	quit: false,
	init(topic) {
		Services.obs.addObserver(self = this, topic);

		var lt = "browser-lastwindow-close-granted";
		var lw = () => this.quit = true;
		Services.obs.addObserver(lw, lt);

		Services.obs.addObserver(function quit(s, t) {
			self.quit = true;
			Services.obs.removeObserver(self, topic);
			Services.obs.removeObserver(lw, lt);
			Services.obs.removeObserver(quit, t);
		}, "quit-application-granted");
		this.initColors();
		this.newUsercontext = name => {
			var id = this.cis.create(
				name || `[ ${this.cis._lastUserContextId + 1} ]`, this.icon, this.nextColor()
			).userContextId;
			this.saveGens(this.gens.add(id));
			return id;
		}
		var cpref = "ucf.openInGeneratedContainer.containers";
		var arr = Services.prefs.getStringPref(cpref, "").split(",").map(Number).filter(Boolean);
		if (arr.length) {
			var ids = this.cis.getPublicIdentities().map(i => i.userContextId);
			arr = arr.filter(id => ids.includes(id));
		}
		this.gens = new Set(arr);
		(this.saveGens = () => Services.prefs.setStringPref(cpref, Array.from(this.gens).join(",")))();
	},
	observe(doc) {
		var list = doc.querySelectorAll(sel);
		if (!list.length) return;

		var menuitem = doc.createXULElement("menuitem");
		for(var args of Object.entries({
			selectiontype: "single",
			oncommand: "cmd(window)",
			nodetype: "folder|query",
			selection: "folder|query",
			label: "Открыть всё в контейнере",
			id: "placesContext_openContainer:tabs:newUsercontext"
		}))
			menuitem.setAttribute(...args);
		menuitem.cmd = this.cmd;
		menuitem.rnd = menuitem.constructor.prototype.render;
		menuitem.render = this.render;
		var [m1, m2] = menuitem.list = Array.from(list);
		(m2 || m1).after(menuitem);

		if (doc.documentElement.getAttribute("windowtype") != "navigator:browser") return;

		for(var btn of [
			doc.getElementById("tabs-newtab-button"),
			doc.getElementById("new-tab-button") ||
				doc.ownerGlobal.gNavToolbox.palette.querySelector("#new-tab-button")
		])
			if (btn) btn.checkForMiddleClick = this.click;

		var win = doc.ownerGlobal;
		this.redefDoSearch(win, win.customElements.get("searchbar").prototype);

		win.gBrowser.tabContainer.addEventListener("TabClose", this.tabClose);
		win.addEventListener("unload", this.winUnload, {once: true});
		this.quit = false;
	},
	winUnload(e) {
		var win = e.target.ownerGlobal;
		win.removeEventListener("TabClose", self.tabClose);
		if (self.quit) return;
		var gb = win.gBrowser;
		if (gb) for(var tab of gb.tabs) self.tabClose(null, tab);
	},
	closed: new Set(),
	cis: ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm")
		.ContextualIdentityService,
	tabClose(e, tab = e.target) {
		var id = +tab.getAttribute("usercontextid");
		id && self.gens.has(id) && self.closed.add(id);
		self.closed.size == 1 && ChromeUtils.idleDispatch(self.maybeRemove);
	},
	maybeRemove() {
		var ids = Array.from(self.closed);
		self.closed.clear();
		for(var id of ids) self.maybeRemoveById(id);
	},
	maybeRemoveById(id) {
		for(var win of CustomizableUI.windows)
			if (win.document.querySelector(`tab.tabbrowser-tab[usercontextid="${id}"]`))
				return;
		this.saveGens(this.gens.delete(id));
		this.cis.remove(id);
	},
	redefDoSearch(win, proto) {
		var code = `(openTrustedLinkIn => [
			{${proto.doSearch}}, openTrustedLinkIn
		])(
			function otl(url, where, params) {
				if (where != "current") params.resolveOnNewTabCreated =
					br => gBrowser.moveTabTo(
						gBrowser.getTabForBrowser(br), Infinity
					),
					params.userContextId = otl.newUsercontext(
						document.getElementById("searchbar").value
					);
				openTrustedLinkIn(url, where, params);
			}
		);`;
		(this.redefDoSearch = (win, proto) => {
			var [obj, func] = win.eval(code);
			Object.assign(proto, obj);
			func.newUsercontext = this.newUsercontext;
		})(win, proto);
	},
	click(btn, e) {
		if (!(e.button != 2 || e.ctrlKey || e.shiftKey)) {
			var txt = e.view.readFromClipboard();
			if (txt) {
				var urls = txt.split("\n").map(self.map).filter(Boolean);
				if (urls.length) return e.preventDefault(),
					self.openFromClipboard(e.view, urls);
			}
		}
		e.view.checkForMiddleClick(btn, e);
	},
	eo: Object.create(null),
	map(str) {
		str = str.trim();
		try {
			var scheme = Services.io.extractScheme(str);
			var ph = Services.io.getProtocolHandler(scheme);
			if (ph.scheme == scheme)
				return Services.io.newURI(str) && {uri: str};
		} catch {}
	},
	openFromClipboard(win, urls) {
		if (win.OpenInTabsUtils.confirmOpenInTabs(urls.length, win))
			urls.load = true,
			this.open(win, this.eo, urls);
	},
	async render() {
		this.rnd();
		await new Promise(this.ownerGlobal.requestAnimationFrame);
		this.hidden || (this.hidden = this.list.every(self.every));
	},
	every: node => node.disabled || node.hidden,
	cmd(win) {
		var {_view, triggerNode} = this.parentNode;
		var node = triggerNode._placesView && triggerNode._placesView.result.root;
		self.open(win, node || _view.selectedNode || _view.result.root);
	},
	open(win, node, list) {
		var gbw = Cu.import("resource:///modules/PlacesUIUtils.jsm", {}).getBrowserWindow;
		var w = gbw(win);
		this.pu = w.PlacesUIUtils;
		this.fs = w.PlacesUtils.favicons;
		this.sysp = w.E10SUtils.SERIALIZED_SYSTEMPRINCIPAL;

		(this.open = (win, node, list) => {
			var {bookmarkGuid: guid, title} = node;
			if (guid && title) title = win.PlacesUtils.bookmarks.getLocalizedTitle({guid, title});
			this.openURLs(gbw(win), list || win.PlacesUtils.getURLsForContainerNode(node), title);
			guid && this.pu.doCommand(win, "placesCmd_delete");
		})(win, node, list);
	},
	async openURLs(win, urls, title) {
		var userContextId = this.newUsercontext(title);
		var mark = !win.PrivateBrowsingUtils.isWindowPrivate(win);
		var {load} = urls, gb = win.gBrowser, pos = gb.selectedTab._tPos;

		for(var {uri, title, isBookmark} of urls) try {
			if (mark) isBookmark
				? this.pu.markPageAsFollowedBookmark(uri)
				: this.pu.markPageAsTyped(uri);

			if (load) {
				gb.addTrustedTab(uri, {index: ++pos, userContextId});
				continue;
			}
			var state = {userContextId, entries: [{
				url: uri,
				title: title || uri,
				triggeringPrincipal_base64: this.sysp
			}]};
			var [,, data, mime] = await new Promise(
				resolve => this.fs.getFaviconDataForPage(
					Services.io.newURI(uri), (...args) => resolve(args), 16
				)
			);
			if (data.length) state.image = `data:${
				mime || "image/x-icon"
			};base64,${
				btoa(String.fromCharCode(...data))
			}`;
			var tab = gb.addTrustedTab(null, {index: ++pos, userContextId});
			win.SessionStore.setTabState(tab, state);
		} catch {};
	}
}).init("chrome-document-loaded"))(
	"#placesContext_openBookmarkContainer\\:tabs,#placesContext_openContainer\\:tabs"
);

Отсутствует

 

№1581914-08-2021 09:30:39

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

Re: Custom Buttons

Stkvsky пишет

автоматически открывалась еще пустая вкладка(about:blank)

В этом же контейнере?

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

Выделить код

Код:

/*
				if (where != "current") params.resolveOnNewTabCreated =
					br => gBrowser.moveTabTo(
						gBrowser.getTabForBrowser(br), Infinity
					),
*/
				if (where != "current")
					params.resolveOnNewTabCreated = br => {
						var tab = gBrowser.getTabForBrowser(br);
						gBrowser.moveTabTo(tab, Infinity);
						gBrowser.addTrustedTab("about:blank", {
							index: tab._tPos + 1,
							userContextId: tab.userContextId
						});
					},

Отсутствует

 

№1582014-08-2021 12:29:57

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
Спасибо большое, супер, да, в этом же контейнере
Забыл уточнить, извиняюсь, если можно добавить, чтобы пустая вкладка открывалась перед вкладкой с поиском
(но фокус оставался на вкладке с поиском)

Отредактировано Stkvsky (14-08-2021 12:35:47)

Отсутствует

 

№1582114-08-2021 13:30:08

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

Re: Custom Buttons

Stkvsky
Тогда удалить из правки « + 1».
А «фокус» на неё переходить и так не должен.

Отсутствует

 

№1582214-08-2021 13:47:31

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
Работает, класс, благодарю

Отсутствует

 

№1582314-08-2021 19:27:13

Пострел
Участник
 
Группа: Members
Зарегистрирован: 08-04-2021
Сообщений: 34
UA: Firefox 91.0

Re: Custom Buttons

Dumby, здравствуйте.
В кнопке "Управление закладками", автор bunda1,
https://forum.mozilla-russia.org/viewto … pid=724500
есть пункт: "Левый длинный клик добавляет текущую вкладку в закладки под нажатой закладкой, в боковой панели закладок".

Пожалуйста адаптируйте этот кусок кода для Firefox 91.

Код кнопки

Выделить код

Код:

// Управление закладками, от 25.10.2014.

(function func() {
    // страницы где левый длинный клик открывает закладку в текущей странице
    var current = ["about:newtab", "about:blank", "chrome://browser/content/bookmarks/bookmarksPanel.xul"];

    const background = false;      // открывать закладку в фоновой вкладке
    const nextToCurrent = true;    // открывать закладку рядом с текущей вкладкой
    const menuAutoClose = true;    // автоматически закрыть меню закладок при уходе курсора

    function handleClick(e) {
        var target = e.originalTarget;
        var sidebar = target.ownerDocument.defaultView.top !== content
                   && target.id == 'bookmarks-view-children';

        if ( !sidebar && !target._placesNode ) return; // стоп, если не закладка, папка, разделитель

        // получить закладку, адрес, папку и id закладки, другие переменные
        if ( sidebar ) {
            var tree = target.parentNode;
            var box = tree.treeBoxObject;
            var row = box.getRowAt(e.clientX, e.clientY); 
            var node = tree.view.nodeForTreeIndex(row);
        }
        var node = sidebar ? node : target._placesNode,
        id = node.itemId, uri = node.uri, button = e.button, type = e.type,
        isFolder = PlacesUtils.nodeIsContainer(node), isURI = PlacesUtils.nodeIsURI(node);

        // двойной клик добавляет закладку или удаляет закладку, разделитель 
        if ( button !== 1 && type == 'mouseup' ) {
            if ( button == 0 && func.flag == true && isFolder ) {
                addBookmark(node, false);
                sidebar && tree.view.toggleOpenState(row);
            }

            if ( button == 2 && func.flag == true && !isFolder ) {
                setTimeout(function() {
                    target.ownerDocument.getElementById("placesContext").hidePopup()
                    try { PlacesUtils.bookmarks.removeItem(id) } catch(e) {};
                }, 0);
            }

            func.flag = true;
            setTimeout(function() func.flag = false, 400 );
        }

        if ( isFolder ) return;                                // стоп, если папка закладок
        if ( button == 0 ) window.clearTimeout( func.timer );  // сбросить таймер длинного клика

        // блокировать действие по умолчанию для клика и отжатия 
        if ( button !== 2 && /click|mouseup/.test(type) ) {
            e.preventDefault(); e.stopPropagation();
        }

        // левый длинный клик добавляет текущую вкладку в закладки под нажатой закладкой 
        if ( button == 0 && type == 'mousedown' ) {
            func.loadBook = true;
            func.timer = setTimeout(function() {
                func.loadBook = false;
                addBookmark(node.parent, id);
            }, 400 );
        }

        // левый клик открывает закладку в новой или текущей вкладке 
        if ( button == 0 && type == 'mouseup' && func.loadBook && isURI ) {
            ( uri.startsWith("javascript") || current.indexOf(content.location.href) !== -1 || e.ctrlKey )
            ? gBrowser.loadURI( uri )
            : gBrowser.loadOneTab( uri, {relatedToCurrent: nextToCurrent, inBackground: background,
                                         referrerURI: null } );
        }

        // средний клик открывает закладку в текущей вкладке 
        if ( button == 1 && type == 'click' && isURI ) {
            gBrowser.loadOneTab( uri, {relatedToCurrent: nextToCurrent, inBackground: true, referrerURI: null } );
        }

        // автоматически закрыть все меню закладок при уходе курсора 
        var menu = target.parentNode;
        if ( !menuAutoClose || !menu || menu.localName !== 'menupopup' || type !== 'click' || button == 2 ) return;

        menu.onmouseover = function() menu.f = true;
        menu.onmouseleave = function() {
            menu.f = false;
            setTimeout(function() {
                if ( menu.f ) return;
                for ( var node = menu; node; node = node.parentNode )
                node.nodeName == 'menupopup' && node.hidePopup();
                menu.onmouseleave = null;
            }, 500);
        };
    };

    ["click", "dragstart", "mouseup", "mousedown"]
    .forEach(function(type) addEventListener(type, handleClick, true) );

    // добавить страницу в закладки в указанной папке 
    function addBookmark(folder, id) {
        var title = gBrowser.mCurrentTab.label;
        var folderId = PlacesUtils.getConcreteItemId(folder);
        var index = id ? PlacesUtils.bookmarks.getItemIndex(id) + 1 : 0;
        var readOnlyFolder = PlacesUtils.nodeIsQuery(folder) ||
                             PlacesUtils.annotations.itemHasAnnotation(folderId, PlacesUtils.LMANNO_FEEDURI);
        if ( !readOnlyFolder )
        PlacesUtils.bookmarks.insertBookmark(folderId, gBrowser.currentURI, index, title);

        // всплывающая подсказка
        if ( id ) return;
        var al = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
        if ( readOnlyFolder ) {
            al.showAlertNotification("chrome://global/skin/icons/error-16.png", folder.title, "Не поддерживается");
            return;
        }
        var icon = gBrowser.mCurrentTab.image || "chrome://global/skin/icons/information-16.png";
        al.showAlertNotification(icon, "Добавил в папку " + (folder.title || "(без заголовка)") + ":", title.slice(0, 100));
    };
})();

Отсутствует

 

№1582415-08-2021 12:54:16

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1578
UA: Firefox 78.0

Re: Custom Buttons

Dumby
Можно вас еще попросить сделать чтобы внешние ссылки открывались в том же контейнере что и активная вкладка?(в конце списка вкладок)
И открытие новой вкладки (кнопка + на панели вкладок(ctrl+t)) так же было в активном контейнере

Отредактировано Stkvsky (16-08-2021 13:41:22)

Отсутствует

 

№1582516-08-2021 03:21:16

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

Re: Custom Buttons

Dumby
Возможно ли что-то придумать, что бы при закреплении вкладки она оставалась на своей позиции, а не перемещалась влево?


«The Truth Is Out There»

Отсутствует

 

Board footer

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