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

Mozilla Россия — свежие версии программ Mozilla, а также масса полезной информации по каждому продукту.

№135114-03-2024 17:10:30

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

Re: UCF - ваши кнопки, скрипты…

Vitaliy V.
Вы делали кнопку

Тултипы с URL

Выделить код

Код:

// Ссылка у курсора в тултипе, кнопка https://forum.mozilla-russia.org/viewtopic.php?pid=783755#p783755
(async () => {
    var id = "ucf-toggle-tooltip-url",
    label = "Тултипы с URL",
    tooltiptext = "Переключить тултипы",
    img = "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' style='fill:context-fill rgb(142, 142, 152);'><path d='M9.618 6.721a2.483 2.483 0 0 0-.39-.317l-.735.734A1.486 1.486 0 0 1 8.91 9.55l-2.12 2.122a1.486 1.486 0 0 1-2.122 0 1.486 1.486 0 0 1 0-2.121l.605-.605a3.53 3.53 0 0 1-.206-1.209L3.961 8.843a2.506 2.506 0 0 0 0 3.535 2.506 2.506 0 0 0 3.535 0l2.122-2.121a2.506 2.506 0 0 0 0-3.536z'/><path d='M6.79 9.55c.12.121.25.226.389.317l.734-.734a1.486 1.486 0 0 1-.417-2.411L9.618 4.6a1.486 1.486 0 0 1 2.121 0 1.486 1.486 0 0 1 0 2.121l-.605.605c.137.391.211.798.206 1.209l1.106-1.107a2.506 2.506 0 0 0 0-3.535 2.506 2.506 0 0 0-3.535 0L6.789 6.014a2.506 2.506 0 0 0 0 3.536z'/><circle style='fill:none;stroke:context-fill rgb(142, 142, 152);stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round' cx='8' cy='8' r='7.4'/></svg>",
    imgcolordisable = "color-mix(in srgb, currentColor 20%, #e31b5d)";

    var branch = "extensions.ucf.", pref = "tooltip_url_enable";
    var tpurl = {
        initialised: false,
        tooltip_url_enable: true,
        get ext_branch() {
            delete this.ext_branch;
            return this.ext_branch = Services.prefs.getBranch(branch);
        },
        init() {
            if (this.initialised) return;
            Services.prefs.getDefaultBranch(branch).setBoolPref(pref, true);
            Services.prefs.addObserver(`${branch}${pref}`, this);
            if (this.tooltip_url_enable = this.ext_branch.getBoolPref(pref))
                this.registerActor();
            this.initialised = true;
        },
        observe(subject, topic, data) {
            var fill = "";
            if ((this.tooltip_url_enable = this.ext_branch.getBoolPref(pref)) === true)
                this.registerActor();
            else {
                fill = imgcolordisable;
                this.unregisterActor();
            }
            this.callWithEachWindow(id, {fill: fill});
        },
        callWithEachWindow(buttonID, atr) {
            var getW = CustomizableUI.getWidget(buttonID);
            if (getW.instances.length)
                for (let {node} of getW.instances) {
                    if (!node) continue;
                    for (let a in atr)
                        node.style.setProperty(a, atr[a]);
                }
            else
                for (let win of CustomizableUI.windows) {
                    let node = getW.forWindow(win).node;
                    if (!node) continue;
                    for (let a in atr)
                        node.style.setProperty(a, atr[a]);
                }
        },
        registerActor() {
            ChromeUtils.registerWindowActor("UcfTooltipUrl", {
                child: {
                    moduleURI: "chrome://user_chrome_files/content/custom_scripts/cs/UcfTooltipUrlChild.jsm",
                    events: {
                        mouseover: { capture: true },
                    },
                },
                allFrames: true,
                matches: ["<all_urls>"],
                messageManagerGroups: ["browsers"],
            });
        },
        unregisterActor() {
            ChromeUtils.unregisterWindowActor("UcfTooltipUrl");
        },
    };
    CustomizableUI.createWidget({
        id: id,
        label: label,
        tooltiptext: tooltiptext,
        localized: false,
        defaultArea: CustomizableUI.AREA_NAVBAR,
        onCreated(btn) {
            tpurl.init();
            btn.style.setProperty("list-style-image", `url("${img}")`, "important");
            if (!tpurl.tooltip_url_enable)
                btn.style.setProperty("fill", imgcolordisable);
        },
        onCommand(e) {
            tpurl.ext_branch.setBoolPref(pref, !tpurl.ext_branch.getBoolPref(pref));
        },
    });
})();


UcfTooltipUrlChild.jsm

Выделить код

Код:

var EXPORTED_SYMBOLS = ["UcfTooltipUrlChild"];
ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
class UcfTooltipUrlChild extends JSWindowActorChild {
    handleEvent(e) {
        timer.cancel();
        timer.initWithCallback(() => {
            var elm = e.isTrusted && e.composedTarget, el, titl;
            if (!elm) return;
            do {
                if (!elm.matches) continue;
                if (elm.matches(":any-link")) {
                    if (elm.matches("[href='#'], [href^='javascript']"))
                        return;
                    el = elm;
                    if (elm.matches("[title]"))
                        titl = elm;
                    else
                        while (elm = elm.flattenedTreeParentNode) {
                            if (!elm.matches) continue;
                            if (elm.matches("[title]")) {
                                titl = elm;
                                break;
                            }
                        }
                    break;
                }
                if (elm.matches("[title]")) {
                    titl = elm;
                    while (elm = elm.flattenedTreeParentNode) {
                        if (!elm.matches) continue;
                        if (elm.matches(":any-link")) {
                            if (elm.matches("[href='#'], [href^='javascript']"))
                                return;
                            el = elm;
                            break;
                        }
                    }
                    break;
                }
            } while (elm = elm.flattenedTreeParentNode);
            if (!el) return;
            var href = el.href;
            if (titl) el = titl;
            titl = (el.title || "");
            var title = titl.trim(), pre = "", path = "";
            try {
                href = Services.io.newURI(href);
                pre = href.displayPrePath;
                path = `\n${href.pathQueryRef}`;
                if (path === "\n/") path = "";
                href = `${pre}${path}`;
            } catch (e) {}
            try {
                href = decodeURIComponent(href);
            } catch (e) {}
            el.title = title = `${href}${title === "" ? "" : `\nTitle: ${title}`}`;
            this.contentWindow.addEventListener("mouseout", () => {
                try {
                    if (!el || title !== el.title) return;
                    if (titl !== "")
                        el.title = titl;
                    else
                        el.removeAttribute("title");
                } catch (e) {}
            }, { once: true });
        }, 100, Ci.nsITimer.TYPE_ONE_SHOT); /* было 400 */
    }
    didDestroy() {
        timer.cancel();
    }
}


Пока работает, но можно переделать на mjs?
P.S. Может, уже переделывали в теме, не уследил...

Отсутствует

 

№135214-03-2024 17:59:39

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

Re: UCF - ваши кнопки, скрипты…

Farby пишет

нашел решение от Vitaliy V

Не, Виталий такую шляпу не пишет, у него всё как-то более академично.
Это моё, наверно. Кстати, ошибку свою заметил.


Настройка "browser.search.hiddenOneOffs" дефолтно существовала (до 116),
а значит, если там ничего не было, то значением возвращалась пустая строка.
А выражение ""?.split(",") возвращает массив с пустой строкой, и это совсем не то, что нужно.


Dumby пишет

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

Ха, оказывается они уже слетели (причём, во всём браузере),
если руками включить настройку browser.search.newSearchConfig.enabled


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


Но ничего, теперь хотя бы понятно, что engine._iconURI для них больше не подходит.
Вобщем вот, небольшая модификация обсуждавшегося кода

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

Выделить код

Код:

try {CustomizableUI.createWidget({
	label: "Переключить текущий поисковик",
	id: "ucf-cbbtn-ToggleCurrentSearchEngine",
	image: "data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA",
	excludeHiddenOneOffs: true,
	gn: () => Services.search.defaultEngine,
	gp: () => Services.search.defaultPrivateEngine,
	sn: val => Services.search.defaultEngine = val,
	sp: val => Services.search.defaultPrivateEngine = val,
	onCreated(btn) {
		btn.type = "menu";
		btn.owner = this;
		btn.setAttribute("image", this.image);

		var win = btn.ownerGlobal;
		var popup = btn.appendChild(win.document.createXULElement("menupopup"));
		var pr = win.PrivateBrowsingUtils.isWindowPrivate(win);
		popup.getDefaultEngine = pr ? this.gp : this.gn;
		popup.setDefaultEngine = pr ? this.sp : this.sn;
		popup.setAttribute("oncommand", "setDefaultEngine(event.target.engine)");
		popup.setAttribute("onpopupshowing", "this.shouldRebuild && owner.rebuild(this, document)");

		this.autoOpenCloseFeature(win, btn);
		this.updButton(btn, win);
	},
	getEngines() {
		var ve = Services.search.getVisibleEngines;
		if (!this.excludeHiddenOneOffs) return (this.getEngines = ve)();

		var arr = [];
		var args = this.fx116
			? [e => !e.hideOneOffButton]
			: Object.defineProperty(
				[function(e) {return !this.includes(e.name);}], "1", {get: () => {
					var str = Services.prefs.getStringPref(this.pref);
					return str ? str.split(",") : arr;
				}}
			);
		return (this.getEngines = async () => (await ve()).filter(...args))();
	},
	async rebuild(popup, doc) {
		popup.textContent = "";
		var df = doc.createDocumentFragment();
		var de = popup.getDefaultEngine().wrappedJSObject, jsde = this.json(de);
		var check = true;
		for(var engine of await this.getEngines()) {
			if (check && engine.name == de.name && this.json(engine) == jsde) {
				check = false; continue;
			}
			var menuitem = df.appendChild(doc.createXULElement("menuitem"));
			menuitem.engine = engine;
			menuitem.label = engine.name;
			menuitem.className = "menuitem-iconic";
			menuitem.image = await this.img(engine);
		}
		popup.append(df);
		delete popup.shouldRebuild;
	},
	async updButton(btn, win) {
		this.updButton = () => {};
		Services.search.isInitialized || await Services.search.init();
		this.fx116 = "hideOneOffButton" in Services.search.defaultEngine;

		var topics = ["browser-search-engine-modified", "quit-application-granted"];
		for(var topic of topics) Services.obs.addObserver(this, topic, false);
		this.observe = (s, topic) => this[topic[0]]();

		var remove = () => topics.forEach(
			topic => Services.obs.removeObserver(this, topic)
		);
		var {id} = this;
		var wins = callback => {
			for(var win of CustomizableUI.windows) {
				var btn = win.document.getElementById(id);
				btn && callback(btn, win);
			}
		}
		if (this.excludeHiddenOneOffs && !this.fx116) {
			var setRebuild = btn => btn.firstChild.shouldRebuild = true;
			var {pref} = this, obs = () => wins(setRebuild);
			Services.prefs.addObserver(pref, obs);
			this.q = () => remove(Services.prefs.removeObserver(pref, obs));
		}
		else this.q = remove;

		var updButton = (btn, win) => {
			var popup = btn.firstChild;
			var engine = popup.getDefaultEngine();
			/*btn.label =*/ btn.tooltipText = engine.name;
			popup.shouldRebuild = true;
			win.requestAnimationFrame(async () => btn.icon.src = await this.img(engine));
		}
		(this.b = () => wins(updButton))();
		this.updButton = updButton;
		btn.tooltipText || updButton(btn, win);
	},
	pref: "browser.search.hiddenOneOffs",
	json: e => JSON.stringify(e.toJSON()),
	img: async e => await e.getIconURL?.() || e.iconURI?.spec || "chrome://browser/skin/search-engine-placeholder.png",

	// https://github.com/Infocatcher/Custom_Buttons/blob/master/code_snippets/autoOpenCloseMenu.js
	// Automatically open menu on mouse over (and hide it on mouse out)
	autoOpenCloseFeature(win, btn, openDelay = 200, closeDelay = 350) {
		var _openTimer = 0;
		var _closeTimer = 0;
		btn.onmouseover = function(e) {
			win.clearTimeout(_closeTimer);
			if(e.target == btn && closeOtherMenus()) {
				btn.open = true;
				return;
			}
			_openTimer = win.setTimeout(function() {
				btn.open = true;
			}, openDelay);
		};
		btn.onmouseout = function(e) {
			win.clearTimeout(_openTimer);
			_closeTimer = win.setTimeout(function() {
					btn.open = false;
			}, closeDelay);
		};
		function closeOtherMenus() {
			return win.Array.prototype.some.call(
				btn.parentNode.getElementsByTagName("*"),
				function(node) {
					if(
						node != btn
						&& win.XULElement.isInstance(node)
						// See https://github.com/Infocatcher/Custom_Buttons/issues/28
						//&& node.boxObject
						//&& node.boxObject instanceof Components.interfaces.nsIMenuBoxObject
						&& "open" in node
						&& node.open
						&& node.getElementsByTagName("menupopup").length
					) {
						node.open = false;
						return true;
					}
					return false;
				}
			);
		}
	}
});} catch(ex) {Cu.reportError(ex);}

Отсутствует

 

№135314-03-2024 18:39:13

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

Re: UCF - ваши кнопки, скрипты…

Dumby пишет

Настройка "browser.search.hiddenOneOffs" дефолтно существовала (до 116),
а значит, если там ничего не было, то значением возвращалась пустая строка.
А выражение ""?.split(",") возвращает массив с пустой строкой, и это совсем не то, что нужно.

Ха, оказывается они уже слетели (причём, во всём браузере),
если руками включить настройку browser.search.newSearchConfig.enabled


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

Получаеться что и эта кнпка не будет работать https://forum.mozilla-russia.org/viewto … 58#p808658 .Поправте и ее пожалуйста

Отсутствует

 

№135414-03-2024 18:55:25

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

Re: UCF - ваши кнопки, скрипты…

Dumby пишет

Вобщем вот, небольшая модификация обсуждавшегося кода

Dumby, в консоль пишет: CustomizableUI: Could not localize property 'ucf-cbbtn-ToggleCurrentSearchEngine.tooltiptext'.


Если в код добавить что-то типа:
tooltiptext: "Переключить текущий поисковик",
то тогда в консоли чисто.


«The Truth Is Out There»

Отсутствует

 

№135514-03-2024 20:06:02

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

Re: UCF - ваши кнопки, скрипты…

egorsemenov06 пишет

Получаеться что и эта кнпка не будет работать

Ну, что значит не будет работать?
В данном случае речь о том, что перестанут подхватываться иконки
встроенных поисковиков (не поисковиков, которые поставил пользователь),
когда включат search-config-v2 (пока что, только в Nightly).


А правка там простая

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

Выделить код

Код:

/*
        node.setAttribute("image", engine._iconURI ? engine._iconURI.spec : engine.iconURI ? engine.iconURI.spec : this.defaultImg);
*/
        node.setAttribute("image", await engine.getIconURL?.() || engine.iconURI?.spec || this.defaultImg);

unter_officer пишет

в консоль пишет: CustomizableUI: Could not localize property 'ucf-cbbtn-ToggleCurrentSearchEngine.tooltiptext'.

Да, есть такое, за всем не уследишь.
Можно добавить, например, второй строкой
localized: false,


xrun1 пишет

переделать на mjs?

Что-то я не вижу там ничего такого,
что могло бы помешать сделать это самому.


Никаких выкрутасов, никаких __URI__ (которого в ESM нет),
никакого использования top-level this (который в ESM undefined),
разве что для Services, но он всё равно уже давно везде определён.


В скрипте меняем

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

Выделить код

Код:

/*
                    moduleURI: "chrome://user_chrome_files/content/custom_scripts/cs/UcfTooltipUrlChild.jsm",
*/
                    esModuleURI: "chrome://user_chrome_files/content/custom_scripts/cs/UcfTooltipUrlChild.mjs",


а в модуле
скрытый текст

Выделить код

Код:

/*
var EXPORTED_SYMBOLS = ["UcfTooltipUrlChild"];
ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
class UcfTooltipUrlChild extends JSWindowActorChild {
*/
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
export class UcfTooltipUrlChild extends JSWindowActorChild {


Вот и всё.

Отсутствует

 

№135614-03-2024 20:24:16

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

Re: UCF - ваши кнопки, скрипты…

Dumby пишет

Да, есть такое, за всем не уследишь.
Можно добавить, например, второй строкой
localized: false,

Спасибо, теперь всё нормально.


«The Truth Is Out There»

Отсутствует

 

№135714-03-2024 20:57:42

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

Re: UCF - ваши кнопки, скрипты…

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

Получаеться что и эта кнпка не будет работать

Ну, что значит не будет работать?
В данном случае речь о том, что перестанут подхватываться иконки
встроенных поисковиков (не поисковиков, которые поставил пользователь),
когда включат search-config-v2 (пока что, только в Nightly).


А правка там простая

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

Выделить код

Код:

/*
        node.setAttribute("image", engine._iconURI ? engine._iconURI.spec : engine.iconURI ? engine.iconURI.spec : this.defaultImg);
*/
        node.setAttribute("image", await engine.getIconURL?.() || engine.iconURI?.spec || this.defaultImg);

Спасибо!!! Чё-то я впереди паровоза побежал

Отредактировано egorsemenov06 (14-03-2024 20:58:25)

Отсутствует

 

№135814-03-2024 22:46:44

Farby
Участник
 
Группа: Members
Зарегистрирован: 21-11-2012
Сообщений: 250
UA: Google 2.1

Re: UCF - ваши кнопки, скрипты…

Dumby пишет

Не, Виталий такую шляпу не пишет, у него всё как-то более академично.

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


Жизнь иногда такое выкидывает, что хочется подобрать...

Отсутствует

 

№135914-03-2024 23:43:16

Farby
Участник
 
Группа: Members
Зарегистрирован: 21-11-2012
Сообщений: 250
UA: Google 2.1

Re: UCF - ваши кнопки, скрипты…

Dumby пишет

Вобщем вот, небольшая модификация обсуждавшегося кода

ой а без директивы localized: false вы сразу отшибаете таких пользователей как например я..


Жизнь иногда такое выкидывает, что хочется подобрать...

Отсутствует

 

№136015-03-2024 00:15:46

Farby
Участник
 
Группа: Members
Зарегистрирован: 21-11-2012
Сообщений: 250
UA: Google 2.1

Re: UCF - ваши кнопки, скрипты…

Dumby
Спасибо за урок, есть много нюансов которые не знал где почерпнуть (async await)


Жизнь иногда такое выкидывает, что хочется подобрать...

Отсутствует

 

№136115-03-2024 01:08:46

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

Re: UCF - ваши кнопки, скрипты…

Dumby
Вы когда-то помогли мне переделать кнопку CB в UCF:

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

Выделить код

Код:

(async initCode => CustomizableUI.createWidget({
    id: "ucf_UpDownCenterPage",
    label: "Up/Down/Center Page",
    // defaultArea: CustomizableUI.AREA_NAVBAR,
    localized: false,
    onCreated(btn) {
        btn.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAFo9M/3AAAACXBIWXMAAAAAAAAAAAHqZRakAAACvElEQVQ4y3VTX0jTURT+rrsqmm3gwm02dZOZOk3RFCTMp4ggEQIT9GUD7VV8mDNF2NaD4qY2X8PBBpGkDwX64EP6YILGilzl3B/dnI5UMHSUWHO/3+1h/pva93S595zvfN8551IcgyYcZMYbHhpV17MU4xShABBV17MU9xR5xjSMAsCIe34ZAKb7F86S00aJ77CN3aJNUpVbnpYhX2pNvj7b5gQVJaceEEJWZ4mzEgBolHGrHPicwg5FxGtdF51ydCjKI9Z1lwgXQAGgr7CWJQkEGKh+yLqc00Tdlb+TdCTA92G/hAJAj3eenGQUmPKYsDsNAKBKzY3r1sgLJ+wlDxr1vg+TFsMS8RsulDiPfE3OWKg0fLvgh3Lf8yJQeykg4NhsBgAPAmcMnP0t48w2pLiniFIjn7hrL238rPdOeizBBjpYXMcslhHwACw1j9ioyO1y6da2wRMFAFDdyhxheffZoUqB9JlRgsUr+kBC7wlC8YuaaAkDgHZBJ1oE2vi8RiH1hcXXeOPPtaLFlGXymrOzFoGWnDK0VtUVoKQCRkc3AODk8co+XAWlRj6htMsaZRAjBxK8089NeizBhksEHYryyJDRKBzoM6PHt3BahYDEFmyf/NE9RLMOxFQUE2YmKDDllbFekwmc2QbObIOOiqFT1zPD7xD6N76RgGOzGY54whZ2sYXdxC4ZQl+JQfv4vxZUT3Od6S9JVfpRBm5yUrh7g19WhgKVCRbaFWVLaYTKjijhwZDE87Eda8BVVj2oZnyUAzVT8AwI89uQZIkrsi1ZbKZzkZwSRGJ//SPSonKIJYA0G/q5Nx8BwKlzk9wmmTv7VWYxOZ7f807TL89wUJigwBH2PnGEvVda2BjfUm+Mb6GwQxFpsWpFlzYlvvNFY/fC+3f28Cd6IBbTfVHGrjXgqj0f7LWuX/py/wDHfA2DzTMorQAAAABJRU5ErkJggg==");
        new btn.ownerGlobal.Function(initCode).call(btn);
    }
}))(`(u => {
    var id, lfs = url => gBrowser.selectedBrowser.messageManager.loadFrameScript(url, false);
    var max = () => {
        var url = u([
            "var args = [scroller.scrollHeight, 0];",
            "scroller.scrollTop != 0 || args.reverse();",
            "content.scrollTo(...args);"
        ].join("\\n\\t"));
        (max = () => lfs(url))();
    }
    var mid = () => {
        var url = u("content.scrollTo(0, (scroller.scrollHeight - scroller.clientHeight) / 2);");
        (mid = () => id = lfs(url))();
    }
    var obj = {
        mousedown: () => id = setTimeout(mid, 500),
        mouseup: () => id && max(id = clearTimeout(id))
    };
    this.onmousedown = this.onmouseup = e => e.button || obj[e.type]();
    this.tooltipText = "ЛКМ:   Вверх/Вниз по странице \\nдЛКМ: Центрирование страницы";
})
(code => "data:," + encodeURIComponent(\`(doc => {
    var root = doc.documentElement;
    var body = doc.body || root;
    var scroller = body.scrollHeight > root.scrollHeight ? body : root;
    \${code}
})(content.document)\`));`);

Я этой кнопкой пользуюсь нечасто, поэтому как-то сразу не заметил один нюанс.
Если в исходном коде страницы сайта отсутствует элемент <!DOCTYPE>, то кнопка начинает работать неправильно. Проверить можно, например, на forum.ru-board.com
Это возможно как-то поправить?


«The Truth Is Out There»

Отсутствует

 

№136215-03-2024 05:20:16

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

Re: UCF - ваши кнопки, скрипты…

Dumby - поправь код менюшки! Не работает upd(){… для подменю!
Одинаковый код обновления не пашет в подменю, но работает в первой и последней строках меню.
screen-2024-03-15-10-11-19.png

Выделить код

Код:

(async id => { // forum.mozilla-russia.org/viewtopic.php?pid=808738#p808738

MyMenu = { //массив команд пользователя, alt() клик правой кнопкой
	Pics: { // Графика сайтов Вкл/Выкл permissions.default.image

		upd() { // обновлять сроку перед показом меню
			// var {G} = this.ownerGlobal;
			var val = G.pref(G.v), s = val == 1, i = G.pdi; s ? i = i.replace("-blocked","") : 0;
			this.label = `Графика сайтов ${s ? "загружается" : val == 3 ? "кроме сторонних" : "отключена"}`;
			this.image = i || G.chk;
			this.tooltipText = G.v +" "+ val +"\nRClick – кроме сторонних";
		},
		cmd(){
			G.pref(G.v, G.pref(G.v) == 2 ? 1 : 2);
			BrowserReload();
		},
		// alt(){ //для RClick нужен ucf_hookClicks.js
		// 	G.pref(G.v, 3); BrowserReload();
		// },
	},
	"SubMenu": { men: 1, //подменю
		"SubItem": {
			lab: "SubItem Mod",
			// img: G.opt,
			upd() { // обновлять сроку перед показом меню
				var val = G.pref(G.v), s = val == 1, i = G.pdi; s ? i = i.replace("-blocked","") : 0;
				this.label = `SubItem: Графика ${s ? "загружается" : val == 3 ? "кроме сторонних" : "отключена"}`;
				this.image = i || G.chk;
				this.tooltipText = G.v +" "+ val +"\nRClick – кроме сторонних";
			},
			cmd(){console.log(this.label)},
		},
	},
	"End Menu": { sep: 1, //сперва разделитель
		upd() { //обновить иконку
			var val = G.pref(G.v), s = val == 1, i = G.pdi; s ? i = i.replace("-blocked","") : 0;
			this.image = i;
		},
	},
}

CustomizableUI.createWidget({
		id: id, label: id, tooltiptext: id, localized: false,
		defaultArea: CustomizableUI.AREA_NAVBAR,
		onCreated(btn) {
			btn.style.setProperty("list-style-image", "url(chrome://branding/content/icon32.png)");
			btn.type = "menu";
			var doc = btn.ownerDocument, m = nn => doc.createXULElement(nn);
			var popup = m("menupopup"), menu = m("menuitem");
			menu.m = m;
			menu.fill = this.fill;
			menu.render = this.render;
			popup.append(menu);
			btn.prepend(popup);
		},
		render(){
			var popup = this.parentNode;
			this.remove();
			this.fill(MyMenu, popup);
		},

	fill(o, popup) {
		for (key in o) {
			var val = o[key];
			if (typeof val != "object") continue;

			var {lab, inf, img, cmd, alt, sep, men, upd} = val;

			sep && popup.append(this.m("menuseparator"));
			var name = men ? "menu" : "menuitem";
			var item = this.m(name);

			item.setAttribute("label", lab || key);
			// item.alt = alt; //RClick в ucf_hookClicks.js {Mouse…
			if (inf)
				item.tooltipText = inf;
			if (img || /this\.image.*=/.test(upd))
				item.className = name + "-iconic", item.setAttribute("image", img || G.nul);
			men || cmd && item.setAttribute("oncommand", cmd.toString().replace(
				/cmd\(.*?\){/, "{var trg = event.target || event;"
			));
			popup.append(item);
			upd && (item.render = upd).call(item);
			men && this.fill(val, item.appendChild(this.m("menupopup")));
		}
	},
});

var {prefs} = Services;
G = {
	pref(key,set){ //или key = [key,default]
		if (!Array.isArray(key)) key = [key];
		var t = prefs.getPrefType(key[0]), m = {b:"Bool",n:"Int",s:"String"};
		t = m[t == 128 ? "b" : t == 64 ? "n" : t == 32 ? "s" : ""];
		if (set == "get") return t; //тип опции
		if (!t) t = m[set != undefined ? (typeof set)[0] : (typeof key[1])[0]];
		if (t) if (set != undefined)
			prefs[`set${t}Pref`](key[0],set)
		else
			set = prefs[`get${t}Pref`](...key);
		return set;
	},
	v: "permissions.default.image",
	pdi: "chrome://browser/skin/canvas-blocked.svg",
	chk: "chrome://devtools/skin/images/check.svg",
	nul: "chrome://devtools/skin/images/blocked.svg",
}

})("ucf_test_menu");

Отсутствует

 

№136315-03-2024 14:46:57

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

Re: UCF - ваши кнопки, скрипты…

unter_officer пишет

Это возможно как-то поправить?

Даже не знаю, может так попробовать

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

Выделить код

Код:

/*
    var root = doc.documentElement;
    var body = doc.body || root;
    var scroller = body.scrollHeight > root.scrollHeight ? body : root;
*/
    var scroller = doc.scrollingElement;

Dobrov пишет

код обновления не пашет в подменю

Разве? Код-то как раз пашет, но пункт не render'ится.


Когда добавляешь пункт в основное меню он render'ится сразу,
но когда в субменю, тогда нет. А когда будет следующий вызов render()
метод уже переопределён и ничего не render'ит.


Можно сразу принудительно отрендерить, но лучше, наверно, отложить.
Плюс, upd() вызывается для пунктов субменю даже просто при открытии основного меню,
а это слегка нехорошо. Возможно, стоит проверять, что субменю открыто.


Вобщем, так поменял фрагмент кода создания виджета

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

Выделить код

Код:

CustomizableUI.getWidget(id)?.label || (self => CustomizableUI.createWidget(self = {
		id, label: id, tooltiptext: id, localized: false,
		defaultArea: CustomizableUI.AREA_NAVBAR,
		onCreated(btn) {
			btn.style.setProperty("list-style-image", "url(chrome://branding/content/icon32.png)");
			btn.type = "menu";
			var doc = btn.ownerDocument, m = nn => doc.createXULElement(nn);
			var popup = m("menupopup"), menu = m("menuitem");
			menu.m = m;
			menu.fill = this.fill;
			menu.render = this.render;
			popup.append(menu);
			btn.prepend(popup);
		},
		render(){
			var popup = this.parentNode;
			this.remove();
			this.fill(MyMenu, popup);
		},
		fill(o, popup) {
			for (key in o) {
				var val = o[key];
				if (typeof val != "object") continue;

				var {lab, inf, img, cmd, alt, sep, men, upd} = val;

				sep && popup.append(this.m("menuseparator"));
				var name = men ? "menu" : "menuitem";
				var item = this.m(name);

				item.setAttribute("label", lab || key);
				// item.alt = alt; //RClick в ucf_hookClicks.js {Mouse…
				if (inf)
					item.tooltipText = inf;
				if (img || /this\.image.*=/.test(upd))
					item.className = name + "-iconic", item.setAttribute("image", img || G.nul);
				men || cmd && item.setAttribute("oncommand", cmd.toString().replace(
					/cmd\(.*?\){/, "{var trg = event.target || event;"
				));
				popup.append(item);

				if (upd)
					if (item.renderedOnce) (item.render = upd).call(item);
					else item.upd = upd, item.render = self.renderSub;

				men && this.fill(val, item.appendChild(this.m("menupopup")));
			}
		},
		renderSub() {
			delete this.render;
			this.render();
			this.render = self.updSub;
			this.upd();
		},
		updSub() {
			this.parentNode.state.startsWith("c") || this.upd();
		}
}))();

Отсутствует

 

№136415-03-2024 17:16:39

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

Re: UCF - ваши кнопки, скрипты…

Dumby пишет

Даже не знаю, может так попробовать

Dumby, большое спасибо. Как всегда всё супер!


«The Truth Is Out There»

Отсутствует

 

№136516-03-2024 16:59:51

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

Re: UCF - ваши кнопки, скрипты…

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


Обновил меню в ucf_hookClicks.js и исправил совместимость с UCF 2024.
Firefox/config.js может грузить старую и новую версию UCF.

Отсутствует

 

№136616-03-2024 18:41:23

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

Re: UCF - ваши кнопки, скрипты…

Dobrov пишет

исправил совместимость с UCF 2024.
Firefox/config.js может грузить старую и новую версию UCF

Немного запоздало я как раз недавно обновил несколько файлов config.js user_chrome.js vertical_top_bottom_bar.css
https://github.com/VitaliyVstyle/Vitali … /config.js

Отсутствует

 

№136716-03-2024 18:56:23

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

Re: UCF - ваши кнопки, скрипты…

Vitaliy V. - спасибо!
забываю проверять обновления UCF…
и теперь новый упрощённый config.js не загружает user_chrome.js от старого UCF

Отсутствует

 

№136816-03-2024 22:42:39

b0ttle
Участник
 
Группа: Members
Зарегистрирован: 22-10-2020
Сообщений: 182
UA: Firefox 123.0

Re: UCF - ваши кнопки, скрипты…

Dobrov
Спасибо за обновы, особенно за ucf_hookClicks.js) Удобная штука. Все что хотел раньше от скриптов, все в одном флаконе, под любые хотелки. Кстати, а что за сборка 116? esr?

Отсутствует

 

№136916-03-2024 23:32:57

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

Re: UCF - ваши кнопки, скрипты…

Vitaliy V.
Спасибо за обновления. Рад вас видеть.
   
Dumby
Приветствую.
Лоадер применительно к mjs не изменился?
Предполагаю что нет, так как один конвертированный скрипт в нем уже работает.
   
И, пожалуйста, переделайте в mjs jsm-ки:
1. TST Reload Tab Interval
2. TST TabPreview
   
TST

Отредактировано _zt (16-03-2024 23:42:36)

Отсутствует

 

№137017-03-2024 04:13:31

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

Re: UCF - ваши кнопки, скрипты…

Dumby :beer: такой глупый вопрос: JS скрипты будут дóльше поддерживаться в Firefox, чем JSM-ки?


Если это так, то возможно переделать в JS скрипт сохранения страниц SingleHTML.JSM (в custom_scripts UCF 2024 тоже JS-скрипты)
Его функции используют два скрипта (а могут и другие): ClickPicSave.jsm и ucf_hookClicks.js


b0ttle пишет

ucf_hookClicks.js Удобная штука. Все что хотел раньше от скриптов, все в одном флаконе
а что за сборка 116? esr?

в ucf_hookClicks.js код слишком компактный, чтоб легко подстроить «под себя» строки меню/нажатия клавиш/клики мыши в первых блоках. Позже отформатирую по правилам, разграничив блоки разных назначений.
А браузер обычный LibreWolf 116 arm64 на Apple Silicon Mac M3, не вижу смысла постоянно обновлять (поставил в игнор для менеджера пакетов brew)

Отсутствует

 

№137117-03-2024 07:08:48

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

Re: UCF - ваши кнопки, скрипты…

Vitaliy V. - пожелание:
добавить подключение стилей по имени в зависимости от OS – иногда нужно использовать один профиль на разных системах, для которых CSS не совпадают.
вот так работает в CustomStylesScripts.mjs (на MacOS подключается custom_styles_all_user_macosx.css):

Выделить код

Код:

const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
var os = name => `${name.replace(/\.[^.$]+$/,'')}_${AppConstants.platform}${name.lastIndexOf('.') > 0 ? "."+ name.split('.').pop() : ""}`;
…………
	stylesall: [ // Для всех документов
		{ path: "custom_styles_all_user.css", type: "USER_SHEET", sheet() { registerSheet(this); }, },
	// стиль для вашей операционной системы: *_macosx.css, *_linux.css, *_win.css
		{ path: os("custom_styles_all_user.css"), type: "USER_SHEET", sheet() { registerSheet(this); }, },
	],

Отредактировано Dobrov (17-03-2024 07:15:48)

Отсутствует

 

№137217-03-2024 13:24:36

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

Re: UCF - ваши кнопки, скрипты…

Dobrov пишет

добавить подключение стилей по имени в зависимости от OS

Да, но для стилей есть медиа запросы, например:
@media (-moz-platform: macos) {
   код для macos
}
@media (-moz-platform: linux) {
   код для linux
}
@media (-moz-platform: windows) {
   код для windows
}
Скрипты же тем более могут определить OS, я не против добавить но в каких случаях это необходимо?

Отсутствует

 

№137317-03-2024 13:57:49

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

Re: UCF - ваши кнопки, скрипты…

Vitaliy V. пишет

Скрипты же тем более могут определить OS, я не против добавить но в каких случаях это необходимо?

Для портабельного или личного профиля, запускаемого и используемого на всяких OS.
В идеале лучше так: грузим стили из custom_styles, затем дополнительные CSS из папки, соответствующий имени операционки.
если браузер на MacOS находит папку custom_styles/macosx, догружаем custom_styles_all_agent.css и прочие CSS из неё.


Примеры стилей обычно только для винды, я пробовал @media (-moz-platform:, но неудобство в том, что придётся править стили при каждом их обновлении.
В стилях от aris-t2 есть совместимость для разных OS, но для Linux и MacOS всё же требуются дополнительные правки.
Проще разные CSS держать, т.к. CSS для MacOS и Windows слишком различаются, да и разные стили для AGENT_SHEET и USER_SHEET увеличивают число файлов.
из-за несовместимости версий Firefox я держу разные папки, например aris-t2-115+ и aris-t2-97…, но это уже другая история

Отредактировано Dobrov (17-03-2024 15:15:09)

Отсутствует

 

№137418-03-2024 20:30:25

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

Re: UCF - ваши кнопки, скрипты…

_zt пишет

Лоадер применительно к mjs не изменился?
Предполагаю что нет, так как один конвертированный скрипт в нем уже работает.

Не понял. Если сделал как написано в первой строке
конвертированного скрипта — значит изменился.


Неизменившийся у меня не работает, хотя мне казалось, что будет.
Но зачем искать себе приключений?
Для сконвертированных модулей следует использовать метод,
который сейчас в браузере для этого предназначен — ChromeUtils.importESModule("……….mjs");

И, пожалуйста, переделайте в mjs jsm-ки:

Хорошо, попробую.

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

Выделить код

Код:

var clickInterval = 5*60;
var intervals = [
	10, 15, 30, 60, 3*60,/* 5*60,*/ 15*60, 30*60, 60*60,
];
var name = "TreeStyleTabAutoReloader";
var addonId = "treestyletab@piro.sakura.ne.jp";
var sfx = "ucf-tst-tab-autoreload", id = `extension:${addonId}:${sfx}`;
var sheets = {
	def(name, css) {
		Object.defineProperty(this, name, {configurable: true, get() {
			delete this[name]; return this[name] = this.pre(name, css);
		}});
	},
	pre(name, css) {
		var ios = Services.io;
		var rph = ios.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
		var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
		var type = "USER_SHEET";
		return (this.pre = (name, css) => {
			var subst = "tst-autoreload-stylesheet-" + name;
			rph.setSubstitution(subst, ios.newURI("data:text/css," + encodeURIComponent(css)));
			return [sss.preloadSheet(ios.newURI(`resource://${subst}/`), sss[type]), Ci.nsIDOMWindowUtils[type]];
		})(name, css);
	}
};
if (!ChromeUtils.domProcessChild.childID) {

	var ep = "resource://gre/modules/ExtensionParent.sys.mjs";
	var manager = ChromeUtils.importESModule(ep).ExtensionParent.apiManager;
	var tt = manager.global.tabTracker;
	var ss = "resource:///modules/sessionstore/SessionStore.sys.mjs";
	ss = ChromeUtils.importESModule(ss).SessionStore;
	var gsec = tab => ss.getCustomTabValue(tab, id);

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

		addonUUID && ChromeUtils.unregisterWindowActor(name);

		var esModuleURI = Components.stack.filename;
		ChromeUtils.registerWindowActor(name, {
			parent: {esModuleURI},
			remoteTypes: ["extension"],
			messageManagerGroups: ["webext-browsers"],
			child: {esModuleURI, events: {pageshow: {}}},
			matches: [`moz-extension://${addonUUID = addon.uuid}/sidebar/sidebar.html?*`]
		});
	}
	var format = sec => {
		var map = new Map();
		// resource://gre/modules/PluralForm.jsm
		var f = n => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;
		var hh = ["", "а", "ов"], ms = ["а", "ы", ""];
		return (format = sec => {
			var res = map.get(sec = +sec);
			if (!res) {
				var num, arr = [];
				if ((num = Math.floor(sec / 3600)) > 0)
					sec -= num * 3600,
					arr.push(`${num} час${hh[f(num)]}`);
				if ((num = Math.floor(sec / 60)) > 0)
					sec -= num * 60,
					arr.push(`${num} минут${ms[f(num)]}`);
				sec > 0 && arr.push(`${sec} секунд${
					sec > Math.floor(sec) ? "ы" : ms[f(sec)]
				}`);
				map.set(sec, res = arr.join(" "));
			}
			return res;
		})(sec);
	}
	var hasDef = intervals.includes(clickInterval);
	hasDef || intervals.push(clickInterval);
	sheets.def("sb", `
		#context_autoreloadTab:not([checked]) > .menu-iconic-left,${
			hasDef ? "" : "\n\t\t#context_autoreloadTab[checked][def=true] > menupopup > :nth-child(2),"
		}
		#context_autoreloadTab:not([checked]) > menupopup > :first-child {
			fill: gray !important;
			-moz-context-properties: fill !important;
			list-style-image: url("chrome://global/skin/icons/reload.svg") !important;
		}
		#context_autoreloadTab[checked] > .menu-iconic-left > image {
			fill: currentColor !important;
			-moz-context-properties: fill !important;
			list-style-image: url("chrome://global/skin/icons/check.svg") !important;
		}
	`);
	clickInterval = String(clickInterval);

	var sym = Symbol(name);
	var TreeStyleTabAutoReloaderParent = class extends JSWindowActorParent {
		actorCreated() {
			var win = this.browsingContext.embedderElement.ownerGlobal;
			var mo = win[sym];
			if (!mo) {
				win.windowUtils.addSheet(...sheets.sb);
				mo = win[sym] = new win.MutationObserver(this.handleMutations);
				var popup = win.document.getElementById("contentAreaContextMenu");
				mo.obs = mo.observe.bind(mo, popup, {childList: true});
				mo.win = win;
			}
			((mo.actor = this).mo = mo).obs();
		}
		didDestroy() {
			this.mo.disconnect();
		}
		get menu() {
			var value = this.mo.menu;
			if (!value) {
				value = this.mo.win.MozXULElement.parseXULToFragment(
					`<menu id="context_autoreloadTab"
						class="menu-iconic"
						onclick="if (event.target == this) linkedObject.click(this);"
					>
						<menupopup oncommand="parentNode.linkedObject.cmd(event);"/>
					</menu>`
				);
				(value = this.mo.menu = value.firstChild).remove();
				value.linkedObject = this;
				(value.popup = value.firstChild).initShadowDOM = this.initShadowDOM;
			}
			return Object.defineProperty(this, "menu", {value}).menu;
		}
		initShadowDOM() {
			delete this.initShadowDOM;
			this.initShadowDOM();

			var df = this.ownerGlobal.MozXULElement.parseXULToFragment(
				`<menuitem closemenu="single" label="Не перезагружать"
					oncommand="event.stopPropagation(); parentNode.parentNode.click();"/>
				<menuitem label="Другой…"
					oncommand="event.stopPropagation(); parentNode.parentNode.linkedObject.prompt();"/>
				<menuseparator/>`
			);
			var doc = this.ownerDocument;

			for(var sec of intervals) {
				var menuitem = doc.createXULElement("menuitem");
				menuitem.setAttribute("type", "radio");
				menuitem.setAttribute("closemenu", "single");
				menuitem.setAttribute("value", sec);
				menuitem.setAttribute("label", format(sec));
				df.append(menuitem);
			}
			hasDef || df.firstChild.after(df.lastChild);
			this.append(df);
			this.parentNode.linkedObject.updMenupopup(this);
			this.setAttribute("onpopupshowing", "parentNode.linkedObject.updMenupopup(this);");
		}
		handleMutations(muts) {
			var cm = this.win.gContextMenu;
			if (cm) for(var mut of muts) for(var node of mut.addedNodes)
				if (node.id == "treestyletab_piro_sakura_ne_jp-menuitem-_context_duplicateTab") {
					var {tabId} = cm.contentData.webExtContextData;
					var tab = tt.getTab(tabId);
					//if (tab?.linkedBrowser.currentURI.scheme.startsWith("http")) {
					if (tab) {
						var {menu} = this.actor;
						menu.tabId = tabId;
						node.after(menu);
						this.actor.maybeSetLabel(tab);
						webExt.apiManager.global.gMenuBuilder.itemsToCleanUp.add(menu);
					}
					break;
				}
		}
		maybeSetLabel(tab) {
			var sec = gsec(tab);
			var has = this.menu.hasAttribute("checked");
			if (Boolean(sec) ^ has)
				has = !has, this.menu.toggleAttribute("checked");

			var curr = has && sec;
			curr !== this.menu.sec && this.setLabel(curr);
		}
		setLabel(sec) {
			this.menu.setAttribute("label", (this.menu.sec = sec)
				? `Интервал перезагрузки:   ${format(sec)}`
				: "Задать интервал перезагрузки"
			);
			hasDef || this.menu.setAttribute("def", sec == clickInterval);
		}
		click(menu) {
			var {tabId} = menu;
			var has = menu.toggleAttribute("checked");
			has
				? this.initTab(tabId, clickInterval)
				: this.destroyTab(tabId);

			var w = menu.clientWidth;
			this.setLabel(has && clickInterval);

			if (menu.popup.state == "open")
				this.updMenupopup(menu.popup),
				menu.clientWidth != w && menu.ownerGlobal.setTimeout(this.move, 50, menu);
		}
		cmd(e) {
			var {value} = e.target;
			if (value == this.menu.sec) return;

			var {tabId} = this.menu;
			this.setLabel(value);

			if (this.menu.hasAttribute("checked"))
				this.changeInterval(tt.getTab(tabId), value);
			else
				this.menu.toggleAttribute("checked"),
				this.initTab(tabId, value);
		}
		changeInterval(tab, sec) {
			var win = tab.ownerGlobal;
			win.clearInterval(tab.getAttribute(sfx));
			ss.setCustomTabValue(tab, id, sec);
			tab.setAttribute(sfx, win.setInterval(bro.reload, sec * 1e3, tab));
		}
		async prompt(val) {
			var {menu} = this, {sec} = menu, {prompt} = Services;
			var res = await prompt.asyncPrompt(
				null, prompt.MODAL_TYPE_WINDOW,
				val ? "ЕЩЁ РАЗ:" : "Задать интервал обновления",
				"Введите число секунд авто-обновления",
				val || sec || clickInterval, null, null
			);
			if (!res.get("ok")) return;

			var val = res.get("value");
			if (!val) return;
			if (!isFinite(val)) return this.prompt(val);

			var {tabId} = menu, val = String(Math.round(val) || 1);
			sec ? this.changeInterval(tt.getTab(tabId), val) : this.initTab(tabId, val);
		}
		move(menu) {
			menu.popup.moveToAnchor(menu, "end_before");
		}
		updMenupopup(popup) {
			var old = popup.querySelector("[checked=true]");
			var {sec} = this.menu;
			var cur = sec && popup.querySelector(`[value="${sec}"]`);
			if (old != cur)
				old?.removeAttribute("checked"),
				cur && cur.setAttribute("checked", true);
		}
		initTab(tabId, sec, skipSet) {
			bro.initTab(tt.getTab(tabId), sec);
			this.sendAsyncMessage(tabId, true);
		}
		destroyTab(tabId) {
			bro.destroyTab(tt.getTab(tabId));
			this.sendAsyncMessage(tabId);
		}
		receiveMessage(msg) {
			msg.name && bro.destroyTab(tt.getTab(+msg.name));
		}
	}
	var bro = {
		async observe(win) {
			var tc = win.document.getElementById("tabbrowser-tabs");
			var tp = win.document.getElementById("tabbrowser-tabpanels")
			var types = ["EndSwapDocShells", "TabClose", "SSTabRestored"];

			var destructor = (meth = "removeEventListener") => types.forEach(
				(type, ind) => (ind ? tc : tp)[meth](type, this, ind == 0)
			);
			destructor("addEventListener");

			win.ucf_custom_script_win[id] = {destructor};
			win.ucf_custom_script_win.unloadlisteners.push(id);

			await ss.promiseAllWindowsRestored;
			for(var tab of win.gBrowser.tabs)
				tab.linkedPanel || this.maybeInitTab(tab);
		},
		maybeInitTab(tab) {
			var sec = gsec(tab);
			sec && this.initTab(tab, sec, true);
		},
		handleEvent(e) {
			this[e.type](e);
		},
		reload(tab) {
			tab.ownerGlobal.gBrowser.reloadTab(tab);
		},
		initTab(tab, sec, skipSet) {
			skipSet || ss.setCustomTabValue(tab, id, sec);
			tab.setAttribute(sfx, tab.ownerGlobal.setInterval(this.reload, sec * 1e3, tab));
		},
		destroyTab(tab) {
			tab.ownerGlobal.clearInterval(tab.getAttribute(sfx));
			ss.deleteCustomTabValue(tab, id);
			tab.removeAttribute(sfx);
		},
		TabClose(e) {
			var intervalId = e.target.getAttribute(sfx);
			if (!intervalId) return;
			e.target.ownerGlobal.clearInterval(intervalId);

			var tab = e.detail.adoptedBy;
			tab && this.initTab(tab, gsec(e.target));
		},
		SSTabRestored(e) {
			var tab = e.target;
			tab.hasAttribute(sfx) || this.maybeInitTab(tab);
		},
		async EndSwapDocShells(e) {
			var br = e.detail, trg = e.target, win = br.ownerGlobal;
			await new Promise(win.requestAnimationFrame);

			if (!win.closed) return;
			var tab = win.gBrowser.getTabForBrowser(br);
			if (!tab) return;

			var sec = gsec(tab);
			if (sec)
				tab = trg.ownerGlobal.gBrowser.getTabForBrowser(trg),
				tab.hasAttribute(sfx) || this.initTab(tab, sec);
		}
		
	};
	var topic = "browser-delayed-startup-finished";
	var {obs} = Services;

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

	var policy = WebExtensionPolicy.getByID(addonId);
	if (policy)
		onAddon(policy.extension),
		policy.extension.once("shutdown", waitAddon);
	else
		waitAddon();

} else {
	sheets.def("tst", `
		:root {
			--ar-ind-width: 22px;
		}
		.autoreload-indicator {
			height: 14px !important;
			position: relative !important;
			z-index: var(--tab-ui-z-index) !important;

			opacity: .6 !important;
			fill: currentColor !important;
			-moz-context-properties: fill !important;
			min-width: var(--ar-ind-width) !important;
			background: no-repeat center/60% url("chrome://global/skin/icons/reload.svg") !important;
		}
		.autoreload-indicator:hover {
			opacity: 1 !important;
		}
		tab-item-substance[autoreload] > .extra-items-container.front {
			right: calc(var(--tab-label-end-offset) + var(--ar-ind-width)) !important;
		}
		tab-item.faviconized .autoreload-indicator {
			min-width: 12px !important;
			background-size: 100% !important;
		}
		tab-item.faviconized > tab-item-substance[autoreload] {
			padding-inline-start: 0 !important;
		}
	`);
	var opts = {childList: true};

	var TreeStyleTabAutoReloaderChild = class extends JSWindowActorChild {
		handleEvent(e) {
			this.sendAsyncMessage("");
			this.stopReload = this.stopReload.bind(this);

			var doc = e.target, win = doc.ownerGlobal;
			win.windowUtils.addSheet(...sheets.tst);
			var mos = this.mos = new Set();

			for(var div of doc.querySelectorAll(
				"#pinned-tabs-container, #normal-tabs-container > .virtual-scroll-container"
			)) {
				var mo = new win.MutationObserver(this.catchUL);
				mos.add(mo);
				mo.actor = this;
				mo.observe(mo.div = div, opts);
			}
		}
		catchUL() {
			var ul = this.div.querySelector(":scope > ul");
			if (!ul) return;
			this.disconnect();
			this.actor.mos.delete(this);

			var mo = new this.constructor(this.actor.catchTabItem);
			(mo.actor = this.actor).mos.add(mo);
			mo.observe(ul, opts);
			for(var node of ul.children) mo.actor.check(node);
		}
		catchTabItem(muts) {
			for(var mut of muts) for(var node of mut.addedNodes) this.actor.check(node);
		}
		check(node) {
			node.nodeName == "TAB-ITEM" && this.onTab(node);
		}
		async onTab(tab, tabId) {
			var subs = tab.querySelector("tab-item-substance");
			var win = tab.ownerDocument.defaultView.wrappedJSObject;
			if (!tabId) {
				if (subs.hasAttribute("autoreload"))
					subs.removeAttribute("autoreload"),
					subs.querySelector(".autoreload-indicator")?.remove();

				var {tabId} = subs.dataset;
				var sec = await win.browser.sessions.getTabValue(+tabId, sfx);
				if (!sec) return;
			}
			subs.toggleAttribute("autoreload", true);
			var ind = win.document.createElement("span");
			ind.className = "autoreload-indicator";
			ind.title = "Остановить перезагузку";
			ind.tabId = tabId;
			ind.onmousedown = this.stopReload;
			subs.querySelector("tab-closebox").before(ind);
		}
		stopReload(e) {
			if (e.button) return;
			e.stopImmediatePropagation();
			var trg = e.target.wrappedJSObject;
			this.sendAsyncMessage(trg.tabId);
			this.removeIndicator(trg);
		}
		removeIndicator(ind) {
			ind.closest("tab-item-substance").removeAttribute("autoreload");
			ind.remove();
		}
		receiveMessage(msg) {
			var tab = this.contentWindow.document.getElementById("tab-" + msg.name);
			if (tab) msg.data
				? this.onTab(tab, msg.name)
				: this.removeIndicator(tab.querySelector(".autoreload-indicator"));
		}
		didDestroy() {
			var {mos} = this;
			if (!mos) return;
			for(var mo of mos) mo.disconnect();
			mos.clear();
		}
	}
}
export {TreeStyleTabAutoReloaderParent, TreeStyleTabAutoReloaderChild};

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

Выделить код

Код:

var timeout = 500;

if (!ChromeUtils.domProcessChild.childID) {

	var popupWidth = 300;

	var label = "Some Label";
	var tooltiptext = "Some Tooltip Text";
	var imgEnabled = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="16"><rect fill="limegreen" width="16" height="16"/></svg>';
	var imgDisabled = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="16"><rect fill="orangered" width="16" height="16"/></svg>';


	var btnImage, popupPosition, enabled, addonUUID, registeredUUID;
	var mo = (p, r = "gre") => ChromeUtils.importESModule(`resource://${r}/modules/${p}.sys.mjs`)[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 esModuleURI = Components.stack.filename;
	var reg = () => ChromeUtils.registerWindowActor(name, {
		parent: {esModuleURI},
		remoteTypes: ["extension"],
		messageManagerGroups: ["webext-browsers"],
		child: {esModuleURI, 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} = 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._handleClick = toggle;
			btn.setAttribute("image", btnImage);
		}
	});

	//-------[ 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.ownerGlobal, cwg, id);
			}
		}
		async drawSnapshot(win, 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) return;
			var data = await this.sendQuery(id, win.devicePixelRatio);
			if (!data) return;

			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;
		}
	}
}
export {TreeStyleTabPreviewPopupParent};

export 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.devicePixelRatio;
			if (z != 1 || msg.data != 1)
				res = res.map(this.mult, z / msg.data);
		}
		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;
	}
}

Dobrov пишет

такой глупый вопрос: JS скрипты будут дóльше поддерживаться в Firefox, чем JSM-ки?

Ну почему, не такой уж глупый, если имеются в виду
ChromeMessageBroadcaster.loadFrameScript()
и ParentProcessMessageManager.loadProcessScript()


Увы, в обозримом прошлом и настоящем,
мне как-то не попадалось ничего об их судьбе, так что сказать нечего.

возможно переделать в JS скрипт сохранения страниц SingleHTML.JSM

А вот это уже глупее.
Вместо простой конвертации в ESM, нахлобучиться затеять такую затею.


Кстати, а чего там win до сих пор торчит? fp.init(win, "", fp.modeSave);
Разве это не видел?
Для Fifefox 125+ вместо win уже нужен win.browsingContext


Vitaliy V.
Просто информационное сообщение, раз уж на глаза попалось.
Bug 1884792 - Consider making -moz-lwtheme a media query.


Удаление псевдокласса :-moz-lwtheme уже пошло в autoland,
а он используется в vertical_top_bottom_bar.css в двух местах.

Отсутствует

 

№137519-03-2024 01:47:00

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

Re: UCF - ваши кнопки, скрипты…

Dumby пишет

Для Fifefox 125+ вместо win уже нужен win.browsingContext (Bug 1878401)

А как сделать, чтоб скрипт SingleHTML.mjs был совместим с FF115+ …… FF125+ ?
не понимаю, как этот win.browsingContext прописывать! SingleHTML править или из других скриптов вызывать иначе ?
1) fp.init(win || win.browsingContext, "", fp.modeSave);
2) Cu.getGlobalForObject(Cu)[Symbol.for("SingleHTML")](to, window.browsingContext)


посмотрел custom_buttons-0.0.7.0.0.33-fx. У тебя в SelfHelper.jsm:
picker(doc) { ……… var win = doc.ownerGlobal;
в SingleHTML (почти весь код твой): async SingleHTML(to, win = this.ownerGlobal)

SingleHTML.mjs (или JSM, если child: {moduleURI: __URI__})

Выделить код

Код:

/* SingleHtml © Лекс, правка Dumby, mod Dobrov
вызов: Cu.getGlobalForObject(Cu)[Symbol.for("SingleHTML")](to, window)
scriptsbackground [System Principal], «to» пуст: выбор пути */

var self, name = "SingleHTML", EXPORTED_SYMBOLS = [name + "Child"];
var {io, focus, obs, prefs, dirsvc} = globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services;

export class SingleHTMLChild extends JSWindowActorChild { //класс = name + Child
	receiveMessage() { return htmlAndName(this.contentWindow);}
}
ChromeUtils.domProcessChild.childID || ({
	init(topic) {
		ChromeUtils.registerWindowActor(name, {
			allFrames: true,
			child: {esModuleURI: Components.stack.filename},
			messageManagerGroups: ["browsers"]
		});
		obs.addObserver(self = this, topic);
		obs.addObserver(function quit(s, t) {
			obs.removeObserver(quit, t);
			obs.removeObserver(self, topic);
		}, "quit-application-granted");
		this.handleEvent = e => this[e.type](e);
		globalThis[Symbol.for(name)] = this.SingleHTML; //общие функции
		globalThis[Symbol.for('TitlePath')] = this.TitlePath;
	},
	observe(win) {
		win.document.getElementById("appMenu-popup").addEventListener("popupshowing", this);
		win.addEventListener("unload", this);
	},
	popupshowing(e) {
		this.unload(e);
		var popup = e.target;
		var btn = popup.ownerDocument.createXULElement("toolbarbutton");
		btn.id = "appMenu-ucf-save-html-button";
		btn.setAttribute("label", "Всё или выбранное в единый HTML");
		var before = "appMenu-save-file-button2", subviewbutton = "subviewbutton";
		btn.className = subviewbutton;
		btn.setAttribute("oncommand", "SingleHTML();");
		btn.SingleHTML = this.SingleHTML;
		popup.querySelector('toolbarbutton[id^="'+ before +'"]').before(btn);
	},
	unload(e) {
		var win = e.target.ownerGlobal;
		win.removeEventListener("unload", this);
		win.document.getElementById("appMenu-popup").removeEventListener("popupshowing", this);
	},
	TitlePath(win, to, f, u, n = 0, h = 99) { //global
		if(parseInt(to) > 0) [n,to] = [to,n]; if(parseInt(to) < 0) h = Math.abs(to);
		if (typeof(to) != 'string' || !/.*\|/.test(to)) to = prefs.getStringPref("extensions.user_chrome_files.savedirs","|||0");
		to = to.split('|').slice(0 + n, 2 + n); //Dir/Sub|[empty|0 title|1 url]
		f ||= win.gBrowser.selectedTab.label;
		f = f.replace(/\s+/g,' ').replace(/[\\\/?*\"'`]+/g,'').replace(/[|<>]+/g,'_').replace(/:/g,'։').slice(0,h).trim();
		u ||= decodeURIComponent(win.gURLBar.value); n = f, h = u;
		u = /^file:\/\//.test(u) ? 'file' : u.replace(/^.*u=|https?:\/\/|www\.|\/.*/g,'').replace(/^ru\.|^m\./,'').replace(/\/.*/,'');
		to[1] = (to[1] == "0") ? f : (to[1] == "1") ? u : "";
		f += "_"+ new Date().toLocaleDateString('ru', {day: 'numeric',month: 'numeric',year: '2-digit'}) +'-'+ new Date().toLocaleTimeString('en-GB').replace(/:/g,"։"); //дата-часы
		try {var dir = dirsvc.get("DfltDwnld",Ci.nsIFile);} catch {dir = prefs.getComplexValue("browser.download.dir",Ci.nsIFile)}
		var map = l => win.DownloadPaths.sanitize(l); //FIX имён
		to.map(map).forEach(dir.append);
		to = dir.clone(); to.append(f +'.html');
		return [dir, to.path, n, f, h, u]; //… имя, +дата, URL, домен
	},
	async Succes(win, dir, dw = true, bg) {
		var {setTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
		var d = await win.Downloads.createDownload({source: "about:blank",target: win.FileUtils.File(dir)});
		(await win.Downloads.getList(win.Downloads.ALL)).add(d);
		if (dw) await d.refresh(d.succeeded = true); //flash DWButton
		d = win.document.getElementById('urlbar-input-container');
		d.style.background = dw ? 'rgba(0,200,0,0.3)' : 'rgba(250,0,0,0.2)';
		setTimeout(() => {d.style.removeProperty('background-color')}, 350);
	},
	async SingleHTML(to, win = this.ownerGlobal) {
		var br = win.gBrowser.selectedBrowser, bc = focus.focusedContentBrowsingContext;
		if (bc?.top.embedderElement != br) bc = br.browsingContext;
		var actor = bc?.currentWindowGlobal?.getActor(name);
		actor && self.save(win, ...await actor.sendQuery(""), to); //htmlAndName
		},
	async save(win, data, fname, host, to) {
		var path = this.TitlePath(win, to, fname, host); //путь в зависимости от опций
		var dir = path[0], path = path[1];
		dir.exists() && dir.isDirectory() || dir.create(dir.DIRECTORY_TYPE, 0o777);
		if (!to) { // диалог выбора папки
			var fp = Cc['@mozilla.org/filepicker;1'].createInstance(Ci.nsIFilePicker);
			fp.init(win, "", fp.modeSave);
			fp.defaultString = path.split(/.*[\/|\\]/)[1];
			fp.appendFilters(fp.filterHTML); fp.appendFilters(fp.filterAll);
			var res = await new Promise(fp.open);
			if (res == fp.returnOK || res == fp.returnReplace)
				path = fp.file.path
			else return;
		}
		this.write(path, data); //нужна проверка на ошибки записи
		await this.Succes(win, path);
	},
	write(path, html) { //без Ff 79-84 в save() IOUtils.writeUTF8 вместо this.write
		if (typeof IOUtils == "object")
			var write = IOUtils.writeUTF8 || IOUtils.writeAtomicUTF8; // Fx 85+ || 82-84
		if (!write) { // Fx 79-81
			var {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
			write = (path, txt) => OS.File.writeAtomic(path, new TextEncoder().encode(txt));
		}
		(this.write = write)(path, html);
	}
}).init("browser-delayed-startup-finished");

var htmlAndName = async mainWin => { //не сохраняет SVG

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

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

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

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

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

	var doctype = '', dt = doc.doctype;
	if (dt && dt.name) {
		doctype += '<!DOCTYPE ' + dt.name;
		if (dt.publicId) doctype += ' PUBLIC \x22' + dt.publicId + '\x22';
		if (dt.systemId) doctype += ' \x22' + dt.systemId + '\x22';
		doctype += '>\n';
	};
	var onlyName = selWin ? win.getSelection().toString() : (title && title.text ? title.text : loc.pathname.split('/').pop());
	return [doctype + sel.innerHTML +'\n<a href='+ (loc.protocol != 'data:' ? loc.href : 'data:uri') +'><small><blockquote>источник: '+ new Date().toLocaleString("ru") +'</blockquote></small></a>', onlyName, loc.hostname];
}

Отсутствует

 

Board footer

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