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

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

№1572621-07-2021 16:25:37

Alex_one
Участник
 
Группа: Members
Зарегистрирован: 27-09-2015
Сообщений: 151
UA: Firefox 90.0

Re: Custom Buttons

Dumby пишет

когда не надо очищать

Да вот я уже и думаю, может оставить всё как сейчас есть.
Выбрал поисковую систему, строка не очистилась, тут же можно выбрать другой поиск.
Ну и на худой конец можно колёсиком мышки почистить :D

Отсутствует

 

№1572722-07-2021 19:27:49

Garalf
Участник
 
Группа: Members
Зарегистрирован: 19-09-2017
Сообщений: 316
UA: Firefox 91.0

Re: Custom Buttons

Dumby

Dumby пишет

xrun1 пишет

    Можно сделать тоже самое для ucf, тоько для ПКМ сделать эмуляцию, как СКМ с вставкой из буфера?

А можно сделать, чтобы текст из буфера в AkelPad открывался?

Отредактировано Garalf (22-07-2021 19:28:44)

Отсутствует

 

№1572822-07-2021 21:50:47

sandro79
Участник
 
Группа: Members
Зарегистрирован: 15-11-2017
Сообщений: 1750
UA: Firefox 91.0

Re: Custom Buttons

Dumby
А нельзя ли добавить как-то в этот скрипт вот этот код(второй спойлер)? Добавьте пожалуйста, если возможно.

Отсутствует

 

№1572922-07-2021 23:14:24

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

Re: Custom Buttons

Garalf
Вроде есть такое в документации. Демо-кнопка.

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

Выделить код

Код:

(async self => CustomizableUI.createWidget(self = {
	label: "AkelPad",
	tooltiptext: "ЛКМ: AkelPad\nПКМ: AkelPad + вставка из буфера",
	akelpadPath: "C:\\Program Files\\AkelPad\\AkelPad.exe",
	image: "data:image/x-icon;base64,AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAyAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAgAAAAApKSlRRkZGh0BAQHxBQUF+QUFBfkFBQX5BQUF+QEBAfEZGRogaGho1AAAAAAABAAECAgICAgICAgQEBAUAAAAAMTEwWeLi4f//////+vr6//r6+v/6+vr//Pz8//v7+//5+fn++/v7/7CwsPdNTU2hAQEBAgAAAAAAAAAAAwMDBAAAAABvbm+x8vHy/+Df4Pfi4+P76ubr++Lh4vvk5OT7/f39+/////v////77/Dv/aysrP8BEAElKngnxDCDLdEvgSzOLX8qy0GTPu1Zq1b/WatW/lasU/9op2f/xcPF/8PEw//i5+L/8vfx//H27//p6+n/qqqq/ESaQOS67a7/teip/rbpqv+36qv/suSm/6zfoP+s36D/teOo/4bNfP93s3b/39bf/9XY1f/5/Pj/+Pv2/+vs6/+qqar/T6RK8bvjsP6t2qT8sNym/LDcpvyx3af+st6o/7LeqP+w3ab/vuaz/1mqVP+2w7b/3dvd//D07//4+/f/6+zr/6qqqv8BNwFvndiU/8Djt/y34a//ueGw/7nhsP+54bD/ueGw/7jgr//B5Lj/mtSS/3q1ev/WzNb/4ufh//f79f/p6+n/qqqq/wAAAANHk0Pbzu/F/73gtP3A47f/wOO3/8Djt//A47f/wOO4/73itf/D5rn/Y7Je/7/MwP/k4eP/+f35/+vs6/+qqqr/AAAAAAAzAGap4KP/zebG+8Tkvv/G5b//xuS//8blv//F5b7/vuK2/8LkuP+U0Yv/ZaBl/97W3v/u9e3/6+3r/6qqqv8DAQMAAAAABE6XS+Db8dT/yuPD/czmxv/N5sf/zObG/8blv//A47j/t+Cu/7vjsP9nuWP/uMW5/+zr6//s7+z/qamq/wACAAQAAAAAATQBZrTir//a6dX70ufN/9Pozv/N5sf/xuS//8Djt/+44K//u+Ow/4zNgv9uqW7/6+Tr/+ru6v+qqar/AAAAAQMBAwAAAAAHVJhS2Or25v/W59L91OjP/8zmxv/G5b//wOO3/7rhsP+w3ab/tuSp/1isU/+vva//6+rq/6qqqv8AAAAAAQIBAwAAAAAENQRkv+S7/+Pr3vvR58z/zObG/8bkv/+/4rf/ueCw/7Hep/+z4Kf/h818/3Kucv/r4uv/qKqo/gAAAAAAAAABAwEDAAAAAApVl1TU7Pbo/9HlzP3P58j/xuS//8Ljuf+64bH/s9+p/6vcoP+t4KD/Wq5U/8bSx/23tLf9AAAAAAAAAAABAgEDAAAAAAk3CWaq36b/1unR+cLjvPzF5r78t+Cv/LXgrP2w36b8odmW/Kzgn/1yxGf7gLKA/52Sne0AAAAAAAAAAAAAAAECAQIAAAEADUOCQcdwt23/Zati82GrXvVjrWD4XKlY9FqpVvZaqlX3UqZN81OnTvxYbFndGxcbNAAAAAAAAAAAAAAAAAABAQEAAAAAFBAUHTAzMGcqKipRJSclUS8wL14nJyZNKCspVy4uLlsiJCJKNjU2aQQABAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",

	id: "ucf-AkelPad",
	localized: false,
	onCreated(btn) {
		btn.setAttribute("image", this.image);
		btn._handleClick = btn.oncontextmenu = this.akelpad;
	},
	akelpad(e) {
		if (e && (e.ctrlKey || e.shiftKey)) return;

		var akelpad = Components.Constructor(
			"@mozilla.org/file/local;1", "nsIFile", "initWithPath"
		)(self.akelpadPath);

		if (!e) return akelpad.launch();

		e.preventDefault();

		var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
		process.init(akelpad);
		var args = [
			//"/NewInstance",	// Форсировать создание нового экземпляра программы
			//"/Command(4157)",	// Выделить всё
			"/Command(4155)",	// Вставить
		];
		process.runwAsync(args, args.length);
	}
}))();

sandro79 пишет

Добавьте пожалуйста

Давай сначала что-то более простецкое попробуем,
если не сработает, тогда уже USER_SHEET и всё такое.


Вот, добавил строку, задающую для ноды
CSS-переменную --urlbar-icon-fill-opacity (в атрибут style пропишется).

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

Выделить код

Код:

(async (url, pa = ChromeUtils.import(url).PageActions) => pa.addAction(new pa.Action({

	title: "Копировать ссылку",
	tooltip: "Копировать ссылку",
	iconURL: "chrome://browser/skin/link.svg",

	id: "ucf-copyURL",
	pinnedToUrlbar: true,		
	onCommand(e) {
		var gBrowserBundle = {
			GetStringFromName: () => "Скопировано в буфер обмена!"
		};
		var show = eval(`(function ${e.view.ConfirmationHint.show})`);
		var helper = Cc["@mozilla.org/widget/clipboardhelper;1"]
			.getService(Ci.nsIClipboardHelper);

		(this.onCommand = e => {
			var win = e.view;
			var uri = win.gBrowser.selectedBrowser.currentURI;
			helper.copyString(win.gURLBar.makeURIReadable(uri).displaySpec);

			var anchor = win.BrowserPageActions.panelAnchorNodeForAction(this, e);
			show.call(win.ConfirmationHint, anchor, "", {event: e, hideArrow: true});
		})(e);
	},
	onPlacedInUrlbar: node => node.style.setProperty("--urlbar-icon-fill-opacity", ".6", "important")
})))("resource:///modules/PageActions.jsm");

Отсутствует

 

№1573022-07-2021 23:23:25

sandro79
Участник
 
Группа: Members
Зарегистрирован: 15-11-2017
Сообщений: 1750
UA: Firefox 91.0

Re: Custom Buttons

Dumby пишет

Давай сначала что-то более простецкое попробуем

Да, это сработало. Отлично! Благодарю!

Отсутствует

 

№1573131-07-2021 20:34:12

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

Re: Custom Buttons

Dumby
Здравствуйте, вы бы не могли помочь, если можно так сделать, чтобы левый клик по крестику закрытия окна выполнял функцию выхода с браузера (ctrl+shift+q) а правый клик стандартно закрывал окно?

Отредактировано Stkvsky (31-07-2021 21:08:50)

Отсутствует

 

№1573201-08-2021 22:58:25

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

Re: Custom Buttons

Stkvsky пишет

клик по крестику закрытия окна

Смотря по какому. Если по одному из трёх лисьих, то вроде так работает.
А если по тому, который в нативном titlebar'е окна Windows,
то что-то не получается придумать ничего хорошего.

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

Выделить код

Код:

(sel => {
	var lst = e => {
		if (e.button == 1 || e.ctrlKey || e.shiftKey) return;
		e.preventDefault();
		e.button ? BrowserTryToCloseWindow(e) : goQuitApplication(e);
	}
	for(var btn of gNavToolbox.querySelectorAll(sel))
		addEventListener("click", lst, false, btn);

})("#close-button, hbox.titlebar-buttonbox-container toolbarbutton.titlebar-close");

Отсутствует

 

№1573302-08-2021 11:28:49

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

Re: Custom Buttons

Dumby пишет

то что-то не получается придумать ничего хорошего.

Так работает жеж) Спасибо большое:):)

Отсутствует

 

№1573402-08-2021 11:33:12

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

Re: Custom Buttons

Dumby
Есть проблемка и по ходу оне не решатся.. Label  - отрегулировать . ЭТИ СПОСОБЫ НЕ ПОДХОДЯТ

скрытый текст
#PlacesToolbarItems > toolbarbutton > label{
  Подходит , но не очень. Поэтому имя закладки начинаю через пробел. Вот бы так на автомате сделать ?
celu8gp7.jpg
Теперь ровно при hover:
скрытый текст

Выделить код

Код:

.bookmark-item:hover {
  background-image: url( chrome://browser/skin/tabbrowser/tab-bg-active.png);
  background-repeat: repeat-x !important;
  padding: 2px 0px 2px 2px !important;
  background-position: left center !important;
  border-radius: 3px !important;
  background-color: #FFFF80 !important; 
  color: blue !important; 
  
}

Отсутствует

 

№1573502-08-2021 12:31:19

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

Re: Custom Buttons

Dumby
Можно еще вас попросить изменить скрипт, который вы делали для открытия папки закладок в контейнере (ПКМ по папке закладок - Открыть все в контейнере) (после открытия папка закладок удаляется)

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

Выделить код

Код:

(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.meaybeRemove);
	},
	meaybeRemove() {
		var ids = Array.from(self.closed);
		self.closed.clear();
		for(var id of ids) self.meaybeRemoveById(id);
	},
	meaybeRemoveById(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")
*/
				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 = this.parentNode._view;
		var node = win.document.popupNode;
		node = node._placesView && node._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) => {
			this.openURLs(gbw(win), list || win.PlacesUtils.getURLsForContainerNode(node));
			node.bookmarkGuid && this.pu.doCommand(win, "placesCmd_delete");
		})(win, node, list);
	},
	async openURLs(win, urls) {
		var userContextId = this.newUsercontext();
		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"
);

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

Отредактировано Stkvsky (02-08-2021 12:32:51)

Отсутствует

 

№1573602-08-2021 21:04:45

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

Re: Custom Buttons

ВВП пишет

Есть проблемка и по ходу оне не решатся..

Ну да. Скажем так: вряд ли такое описание поспособствует решению.
Тут нужен весь стиль, экстракт всех правил,
которые применяются к проблемным элементам,
чтобы накатить на нестилизованый профиль и увидеть проблему.


Вообще интересно, зачем по :hover задавать padding, в чём замысел?


Stkvsky пишет

чтобы названия для контейнера бралось с названия папки закладок

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

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

Выделить код

Код:

(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"
);

Отсутствует

 

№1573702-08-2021 22:47:04

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

Re: Custom Buttons

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

Добавлено 02-08-2021 22:53:30
Dumby
Еще можете пожалуйста изменить процесс добавления контейнера в папку закладок так же?
Вот ваш код для custom buttons который добавляет в меню вкладки (ПКМ по вкладке - Добавить контейнер в закладки)

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

Выделить код

Код:

// Добавить контейнер в закладки....................
(async id => {
	var menuitem = document.createXULElement("menuitem");
	document.getElementById(id).after(menuitem);
	typeof addDestructor == "function"
		&& addDestructor(() => menuitem.remove());
	menuitem.render = function() {
		this.id = "context_bookmarkContainer";
		this.label = "Добавить контейнер в закладки";
		this.setAttribute("oncommand", "bookmark()");

		var bm = PlacesUtils.bookmarks, attr = "usercontextid";
		var {toolbarGuid: parentGuid, TYPE_FOLDER: type} = bm;
		this.bookmark = async () => {
			var tab = TabContextMenu.contextTab;
			var title = tab.label, id = tab.getAttribute(attr);
			var {guid} = await bm.insert({title, parentGuid, type});
			var tabs = [];
			for(tab of gBrowser.visibleTabs)
				tab.getAttribute(attr) == id
				&& tabs.unshift(tab) && await bm.insert({
					parentGuid: guid, title: tab.label,
					url: tab.linkedBrowser.currentURI.spec
				});
			gBrowser.removeTabs(tabs);
		}
		var raf = () => menuitem.hidden =
			!TabContextMenu.contextTab.hasAttribute(attr);
		var {render} = this.constructor.prototype;
		(this.render = () => {
			requestAnimationFrame(raf);
			render.call(menuitem);
		})();
	}
})("context_reopenInContainer");


Вы бы не могли изменить чтобы названия для папки закладок бралось с названия контейнера?
(сейчас оно берется с названия вкладки по которой кликается ПКМ)

Отредактировано Stkvsky (03-08-2021 01:39:06)

Отсутствует

 

№1573803-08-2021 08:35:35

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

Re: Custom Buttons

Stkvsky

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

Выделить код

Код:

/*
			var title = tab.label, id = tab.getAttribute(attr);
*/
			var id = tab.getAttribute(attr);
			var title = ContextualIdentityService.getUserContextLabel(id);

Отсутствует

 

№1573903-08-2021 10:28:31

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

Re: Custom Buttons

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

Отсутствует

 

№1574004-08-2021 14:09:47

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

Re: Custom Buttons

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

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

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

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

Отсутствует

 

№1574106-08-2021 08:52:51

Dumby
Участник
 
Группа: Members
Зарегистрирован: 12-08-2012
Сообщений: 2249
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");

Отсутствует

 

№1574206-08-2021 10:12:59

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

Re: Custom Buttons

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

Отсутствует

 

№1574307-08-2021 16:58:19

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

Re: Custom Buttons

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

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

Отсутствует

 

№1574407-08-2021 21:10:28

_zt
Участник
 
Группа: Members
Зарегистрирован: 10-11-2014
Сообщений: 1644
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)

Отсутствует

 

№1574509-08-2021 19:45:08

Dumby
Участник
 
Группа: Members
Зарегистрирован: 12-08-2012
Сообщений: 2249
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).

Отсутствует

 

№1574609-08-2021 20:06:13

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

Re: Custom Buttons

Dumby пишет

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

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

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

Отсутствует

 

№1574709-08-2021 22:19:21

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

Re: Custom Buttons

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

Отсутствует

 

№1574810-08-2021 12:41:54

dezhnev
Участник
 
Группа: Members
Зарегистрирован: 21-04-2016
Сообщений: 72
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

Отсутствует

 

№1574910-08-2021 13:53:04

Stkvsky
Участник
 
Группа: Members
Зарегистрирован: 26-06-2012
Сообщений: 1700
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)

Отсутствует

 

№1575010-08-2021 19:08:19

Dumby
Участник
 
Группа: Members
Зарегистрирован: 12-08-2012
Сообщений: 2249
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);

Отсутствует

 

Board footer

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