Объявление

В связи с наплывом спама и ботов на форуме, регистрация новых пользователей будет приостановлена. О восстановлении регистрации будет сообщено дополнительно

Administrator

№1730123-03-2025 18:07:35

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Dumby
#window-modal-dialog::backdrop {
   
      background-color: inherit !important;
}
Не тухнет, но здесь не помогло. Стоит modal выпасть ,back кнопка тухнет, а после очистки опять горит, а, ссылок там уже нет. Надо или перезапуск делать или новую открывать...
1.png
Ясен пень, SanitizeDialog  старой FF выпадает окном ,а не modal и не бьет по кнопкам

Отредактировано green25 (23-03-2025 18:50:16)

Отсутствует

 

№1730223-03-2025 19:20:24

T0PMØ3iLLA
Участник
 
Группа: Members
Зарегистрирован: 18-09-2017
Сообщений: 29
UA: Firefox 91.0

Re: Custom Buttons

Так… значит мне ничего нового прописывать уже было не нужно — заработало только либо после очистки startupCache, либо всё-таки после полного удаления расширения и повторной установки (paxmod, ибо с bootstrap`ом мне ещё лет 6 назад пришлось возиться, так и не добившись результата), довелось завести хотя бы "ночной режим"… Сразу определить работоспособность хотя бы кнопки "Ночной режим" не получилось — после очисти startupCache на спех проверялось только в about:* страницах, где, оказалось, она не работает, но после повторного добавления, наконец заработала (несмотря на то, что «затемнена», как и надпись, если добавлять в специальное "»" подменю). А вот с переключалкой настроек проблемы остались — к тому моменту уже было опробован другой код из какого-то сообщения этой темы (то ли за 21-й то ли 23/24г), но над ним мне тогда пришлось полдня повозиться, чтоб предварительно убедиться в том, что убираемые процедуры и переменные из подозрительного кода не используются в остальных его местах — скорее всего где-то тут была "промашка"…

а тот старый, что был до замены…
Пролистывая код испорченнй кнопки в AkelPad`е, сверяю Ctrl+(Shift)+Tab`ом с сохранённой в 2020-м отдельным .txt версией — особых различий в нижней части кода толком не замечаю, и, думая, что в коде ничего не менялось, тупо беру какой-то "новый" попавшийся из этой темы (то ли за 21-й, то ли за 23/24 год), удаляю оттуда кучу ненужных мне пунктов меню, включая ещё какой-то подозрительный код, обращающийся к «файловой системе» (да блин, нужен же переключатель только каких-то заранее заданных параметров — их же легко править!), и заменяю кнопку… Ну… видимо что-то при такой правке кода было что-то "лишнее удалено", раз кнопка не работает…
Добавив CB 0.0.7.0.0.16 ещё раз и вставив тот скопированный URL обратно, замечаю, что заменённая мною кнопка-то действительно не пашет…
Пробую пропихнуть поверх старую версию CB (0.0.7.0.0.11) — о! на удивление, дополнение НЕ ОТКЛЮЧИЛОСЬ! Хах! Так вот как можно "обмануть систему" (т.е. тупо удалить расширение и поставить ещё раз)? Пробую поставить ещё раз ту "сломавшуюся кнопку" (что была сохранена отдельным .txt в 2020-м) — э! Всё равно кнопка "уехала" (сместив остальные панели инструментов вместе с панелью вкладок на "пару пунктов" вниз)… Пфф… значит всё-таки где-то какая-та буква была изменена в верхней части кода, между удалёнными кусками комментариев… Видимо, и тут "промашка"…
Пришлось копировать теперь "изуродованную" кнопку со старого профиля (где она хотя бы как-то работала, но она какая-то слишком навороченная, и, следовательно, глючная)… Видимо, ради того, чтоб продолжить пользоваться классическим вариантом этой кнопки, я в итоге решусь откатиться от 91esr до какой-нибудь 88 (не esr), заодно исправив проблему глючного интерфейса самой лисы, а может ещё и проблему с подтасовкой истории переходов вкладок (предыдущая/следующая страница) вкладок в приватном просмотре… главное не забыть --allow-downgrade
Ну хоть всё равно спасибо, что разъяснили… Не стоило мне беспокоить :/
Консоль по F12 | Ctrl+Shift+I была пустая, это уже потом дошло, что нужна была консоль по Ctrl+Shift+J — после вынужденного использования "инспектора" на многих проблемных страницах (приходилось слишком часто вручную убирать "снегопад" и пр. раздражающие элементы при посещении определённых сайтов) думалось, что там консоль та же, а оказалось нет…

Отсутствует

 

№1730324-03-2025 12:13:26

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Удаляет Историю..А надо Сессии...
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_HISTORY, () => {});

Отредактировано green25 (25-03-2025 10:16:30)

Отсутствует

 

№1730427-03-2025 10:00:36

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

Re: Custom Buttons

green25
В 115 есть отдельный флаг CLEAR_SESSION_HISTORY
А в нынешних версиях уже нет, свалили докучи в CLEAR_HISTORY


В любом случае, для удаления всего,
просто рассылается топик без третьего аргумента:
Services.obs.notifyObservers(null, "browser:purge-session-history");

Отсутствует

 

№1730527-03-2025 12:29:35

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Dumby

Dumby пишет

Services.obs.notifyObservers(null, "browser:purge-session-history");

Класс, тонкий ход...

Отсутствует

 

№1730627-03-2025 21:29:11

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 497
UA: Firefox 136.0

Re: Custom Buttons

Dumby подскажи пожалуйста код скрытия контекстного меню, если код предназначен только для страницы, а не для выделенного текста или медиа и ссылок
Для выделенного текста я использую

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

Выделить код

Код:

addEventListener('popupshowing', e=> {
      if (e.target != e.currentTarget) return;
      var sel = gContextMenu.isTextSelected;
      menu.hidden = !sel;
   }, false, contextMenu);

, для страницы вот такая обвертка на примере кода
скрытый текст

Выделить код

Код:

(this.contextviewpageinfo = {
    
    init(that) {
        var contextMenu = this.contextMenu = document.querySelector("#contentAreaContextMenu");
        if (!contextMenu) return;
        contextMenu.addEventListener("popupshowing", this);
     //   that.unloadlisteners.push("contextviewpageinfo");
    },
   
    handleEvent(e) {
        if (gContextMenu.isTextSelected || gContextMenu.onImage || gContextMenu.onLink || gContextMenu.webExtBrowserType === "popup") return;
        if ( document.getElementById("viewPageInf") ) return; 
        var menuitem = document.createXULElement("menuitem");
        menuitem.setAttribute("id", "viewPageInf")
        menuitem.setAttribute("label", "Информация о странице");
        menuitem.setAttribute("oncommand", "_viewPageInfo();");
        menuitem.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACp0lEQVQ4jV2TPW9cVRCGn3fOuffu9Xo3TnCEkANYQotB2WJpUqWwJSqIUuQPRKKlsAUFpeUmEpXXEv+ACClFitQowmUIMmxASowIkjuQk9jr/fDd7F3fQ7HWdcxI04zmfc47RzPif9G4cXdR0iqy5flL9VYRAoe9rCNpG9nW7v1be2/2qxR+/v0c0vp7C/NrV5euMCkijoYTXucnEALDYcaLwx7DLGsjbTy7d7NbAk7FP12/ttSq1uZ48vyYLA/UZhwg+qMCZ5A6GGVDesODDtLK0x8+69rUh9avX1tqWVzn0dMhWR4A8eDOxzy48xEyEWQcB4O0Tlq73JK5dQBr3Li7+P7C/NpsbY4nf2dTTxKYyhnNG/KGeQfeEVXrRJXqWvP2w0UvafXq0rv8+vz4TCwhJz795s/p62ZlHQkJ4uoFTnqvVj2y5UnwZOP8XNPj75rlTze/3MV5YU4IoQCJS5Bzy3b5rXrraDgBQQEMRgUH/QmNL/4oAXHiiSueJPUkM554JmK2FlGppC1fBBjnBeNJ4HAwQWZEicN7KwFRxeHjaU1mSDCbBIZ9jx0cHXfyyQkvD3NCAHPCnOEid85BUvEkMxGV6jQvzkVk45OOSdp+dTDAOZDpFCCcK3cMnziiiidJp+JaPcZZgZzfNmRbR4MBl2aENE0zIXc2go8cUeKJU0+lGvPhlYh/9kfI3Jbt3r+1N85ftykyUkcJ6Ww2SsCPX7+Nc4aPHY2FiH5vzL8vx+2dzeaeP93Ejfykv+zMtwKzAHzy1V8kaVTOXavFfDAP3f0BP+8cdGS2AWAAz+7d7CKtSL22D11U5MQBqha4GAfeqQYuKOP33/Z59PhFmxBWfvm2cXZMb0bz9sNFma3K+eUkSVs+dozyoiPnt2Vua2ezee6c/wN/E94boB6vcgAAAABJRU5ErkJggg==");
        menuitem.className = "menuitem-iconic";
        menuitem._viewPageInfo = this.viewPageInfo.bind(this);

        addDestructor(function() { contextMenu.removeChild( menuitem ) });

        (this.contextMenu.querySelector("#context-viewsource") || this.contextMenu.lastElementChild).after(menuitem);
        this.handleEvent = () => menuitem.hidden = (gContextMenu.isTextSelected || gContextMenu.onImage || gContextMenu.onLink || gContextMenu.webExtBrowserType === "popup");
    },
    viewPageInfo() {
        BrowserCommands.pageInfo(
            gContextMenu.contentData.docLocation,
            "generalTab",
            gContextMenu.PageInfo,
            null,
            gContextMenu.browser
        );
    },
}).init(this);


Я считаю что в этом коде слишком перемудрено
 
И вот что заметил если использую кнопку Source Editor, которую недавно подправили, в ошибках при редактировании любой кнопки в ошибках показывает такое сообщение -TypeError: this.cssProperties is undefined, хотя оно не на что не влияет
.
Еще один момент. Есть код. Там ошибка, которая тоже не на что не влияет - Uncaught Error: Action with ID 'TranslateBufer1' already added, опять же при редактировании этой кнопки
скрытый текст

Выделить код

Код:

((id, g) => addDestructor(reason => id in g && g[id].destroy(reason)) || id in g || ({
    actions: [{
        title: "Перевод из буфера",
        tooltip: "Перевод из буфера",
        iconURL: gticon,
        id: "TranslateBufer1",
        _insertBeforeActionID: "copyURL",

        onCommand: (e, btn) => ujs_google_translat('auto|ru')
    }],
    init() {
        g[id] = this;
        this.actions = this.actions.map(action => {
            action.extensionID = "custombuttons@xsms.org";
            return g.PageActions.addAction(new g.PageActions.Action(action));
        });
    },
    destroy(reason) {
        if (reason[5] != "e") return;
        delete g[id];
        for(var action of this.actions) action.remove();
    }
}).init())(
    "CBPageActionsMaker", ChromeUtils.importESModule("resource:///modules/PageActions.sys.mjs", {})
);


И еще можно на примере показать как добавлять в новый сайдбар (боковую панель) элементы, на пример самое простое - загрузки

Отредактировано Andrey_Krropotkin (28-03-2025 07:39:48)

Отсутствует

 

№1730730-03-2025 10:49:11

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Чья кнопка ? Перестала менять ,кроме строки адреса...Раньше в любом поиске меняла....

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

Выделить код

Код:

(keybUtils => {
    var btn = this;
    var listener = {
        handleEvent(e) {
            if(e.target != btn)
                return;
            e.preventDefault();
            e.stopPropagation();
            this.switch();
        },
        switch() {
            var br = document.activeElement;
            br && br.localName == "browser" && br.isRemoteBrowser
                ? br.messageManager.loadFrameScript(this.url, false)
                : this.keybUtils.switchSelKeybLayout();
        },
        get url() {
            delete this.url;
            return this.url = `data:;charset=utf-8,(${
                encodeURIComponent(keybUtils)
            }).switchSelKeybLayout()`;
        },
        get keybUtils() {
            delete this.keybUtils;
            var url = "data:;charset=utf-8,this.keybUtils = " + encodeURIComponent(keybUtils);
            Services.scriptloader.loadSubScript(url, this);
            this.keybUtils.button = btn;
            this.keybUtils.getFocusedElement = function(_subCall, _focusFixed) {
                if(
                    !_focusFixed
                    && "closeMenus" in window
                    && document.commandDispatcher.focusedElement == this.button
                ) {
                    closeMenus(this.button);
                    setTimeout(function(_this) {
                        _this.switchSelKeybLayout(_subCall, true);
                    }, 0, this);
                    return;
                }
                return document.commandDispatcher.focusedElement;
            }
            return this.keybUtils;
        }
    };
    if(btn instanceof XULElement && addEventListener.length > 3) {
        addEventListener("command", listener, true, this.parentNode);
    }
    listener.switch();
})(`{
    //== Options
    noSelBehavior: { // Shift+Home
        ctrlKey:  false,
        altKey:   false,
        shiftKey: true,
        metaKey:  false,
        keyCode:  KeyEvent.DOM_VK_HOME,
        charCode: 1
    },
    // 0 - do nothing
    // 1 - convert all text
    // Or use object like following to simulate "keypress" event:
    /*
    noSelBehavior: { // Ctrl+Shift+Left
        ctrlKey:  true,
        altKey:   false,
        shiftKey: true,
        metaKey:  false,
        keyCode:  KeyEvent.DOM_VK_LEFT,
        charCode: 0
    }
    */
    convTableForward: { // ru -> en
        "\\"": "@",
        ":": "^",
        ";": "$",
        "?": "&",
        ",": "?",
        "/": "|",
        ".": "/",
        "э": "'",
        "б": ",",
        "ю": ".",
        "Ж": ":",
        "ж": ";",
        "Б": "<",
        "Ю": ">",
        "Э": "\\"",
        "х": "[",
        "ъ": "]",
        "ё": "\`",
        "Х": "{",
        "Ъ": "}",
        "Ё": "~",
        "№": "#",
        "Ф": "A",
        "ф": "a",
        "И": "B",
        "и": "b",
        "С": "C",
        "с": "c",
        "В": "D",
        "в": "d",
        "У": "E",
        "у": "e",
        "А": "F",
        "а": "f",
        "П": "G",
        "п": "g",
        "Р": "H",
        "р": "h",
        "Ш": "I",
        "ш": "i",
        "О": "J",
        "о": "j",
        "Л": "K",
        "л": "k",
        "Д": "L",
        "д": "l",
        "Ь": "M",
        "ь": "m",
        "Т": "N",
        "т": "n",
        "Щ": "O",
        "щ": "o",
        "З": "P",
        "з": "p",
        "Й": "Q",
        "й": "q",
        "К": "R",
        "к": "r",
        "Ы": "S",
        "ы": "s",
        "Е": "T",
        "е": "t",
        "Г": "U",
        "г": "u",
        "М": "V",
        "м": "v",
        "Ц": "W",
        "ц": "w",
        "Ч": "X",
        "ч": "x",
        "Н": "Y",
        "н": "y",
        "Я": "Z",
        "я": "z",
        __proto__: null
    },
    //== End of options

    get convTableBackward() {
        var ctb = { __proto__: null };
        var ctf = this.convTableForward;
        for(var c in ctf)
            ctb[ctf[c]] = c;
        delete this.convTableBackward;
        return this.convTableBackward = ctb;
    },
    inPrimaryLayout: function(s) {
        for(var i = 0, l = s.length; i < l; ++i) {
            var c = s.charAt(i);
            if(c in this.convTableForward)
                return true;
            if(c in this.convTableBackward)
                return false;
        }
        return false;
    },
    switchKeybLayout: function(s, convTable) {
        var res = "";
        for(var i = 0, l = s.length; i < l; ++i) {
            var c = s.charAt(i);
            res += c in convTable ? convTable[c] : c;
        }
        return res;
    },
    getFocusedElement: function() {
        return Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager)
            .getFocusedElementForWindow(content, true, {});
    },
    switchSelKeybLayout: function(_subCall, _focusFixed) {
        var fe = this.getFocusedElement(_subCall, _focusFixed);
        if(!fe)
            return;
        if(fe instanceof HTMLInputElement || fe instanceof HTMLTextAreaElement) {
            var ta = fe;
            try {
                var val = ta.value;
                var sel = val.substring(ta.selectionStart, ta.selectionEnd);
            }
            catch(e) { // Non-text HTMLInputElement
                return;
            }
            if(!sel && val && this.noSelBehavior && !_subCall) {
                if(this.noSelBehavior == 1) {
                    ta.selectionStart = 0;
                    ta.selectionEnd = val.length;
                    sel = val;
                }
                else {
                    this.handleNoSel(ta);
                    return;
                }
            }
            if(!sel)
                return;
            var res = this.switchKeybLayout(
                sel,
                this.inPrimaryLayout(sel)
                    ? this.convTableForward
                    : this.convTableBackward
            );
            if(res != sel)
                this.insertText(ta, res);
        }
        else if(fe.contentEditable == "true") {
            var doc = fe.ownerDocument;

            var docURI = doc.documentURI;
            if(
                docURI.substr(0, 5) == "data:"
                && docURI.indexOf("chrome://browser/skin/devtools/") != -1
            ) {
                //~ todo: seems like we only can use paste from clipboard here...
                return;
            }

            var sel = doc.defaultView.getSelection();
            var rng = sel.rangeCount && sel.getRangeAt(0);
            var tmpNode;
            if(!rng || rng.collapsed) {
                if(!this.noSelBehavior || _subCall)
                    return;
                if(this.noSelBehavior == 1) {
                    var r = doc.createRange();
                    r.selectNodeContents(fe);
                    sel.removeAllRanges();
                    sel.addRange(r);
                    tmpNode = fe.cloneNode(true);
                }
                else {
                    this.handleNoSel(fe);
                    return;
                }
            }
            else {
                tmpNode = doc.createElementNS("http://www.w3.org/1999/xhtml", "div");
                tmpNode.appendChild(rng.cloneContents());
            }

            var orig = tmpNode.innerHTML;
            var convTable = this.inPrimaryLayout(tmpNode.textContent)
                ? this.convTableForward
                : this.convTableBackward;

            var _this = this;
            var parseChildNodes = function(node) {
                if(node instanceof Element) {
                    var childNodes = node.childNodes;
                    for(var i = childNodes.length - 1; i >= 0; --i)
                        parseChildNodes(childNodes[i]);
                }
                else if(node.nodeType == node.TEXT_NODE) {
                    var text = node.nodeValue;
                    var newText = _this.switchKeybLayout(node.nodeValue, convTable);
                    if(newText != text)
                        node.parentNode.replaceChild(doc.createTextNode(newText), node);
                }
            }
            parseChildNodes(tmpNode);

            var res = tmpNode.innerHTML;
            if(res != orig)
                doc.execCommand("insertHTML", false, res);
        }
    },
    handleNoSel: function(node) {
        this.select(node);
        this.switchSelKeybLayout(true);
    },
    select: function(node) {
        var e = this.noSelBehavior;
        if(!e || typeof e != "object")
            return;
        var evt = node.ownerDocument.createEvent("KeyboardEvent");
        evt.initKeyEvent(
            "keypress", true /*bubbles*/, true /*cancelable*/, node.ownerDocument.defaultView,
            e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
            e.keyCode, e.charCode
        );
        node.dispatchEvent(evt);
    },
    insertText: function(ta, text) {
        //var editor = ta.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor
        var editor = ta.editor
            .QueryInterface(Components.interfaces.nsIPlaintextEditor || Ci.nsIEditor);
        if(editor.flags & editor.eEditorReadonlyMask)
            return;

        var sTop = ta.scrollTop;
        var sHeight = ta.scrollHeight;
        var sLeft = ta.scrollLeft;
        // var sWidth = ta.scrollWidth;

        if(text)
            editor.insertText(text);
        else
            editor.deleteSelection(0, 0);

        ta.scrollTop = sTop + (ta.scrollHeight - sHeight);
        ta.scrollLeft = sLeft; // + (ta.scrollWidth - sWidth);
    }
}`);

Отсутствует

 

№1730831-03-2025 01:41:04

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Dumby
Добавляю закладку !
1.png
Стоит появится этому и все identity-icon-box - пропадает ?

1.png
СНЯТ ВОПРОС  Было:
#urlbar:not(.searchButton) > #urlbar-input-container > #identity-box[pageproxystate="invalid"] {
  pointer-events:  none;
  -moz-user-focus: ignore;
}
Надо:
#urlbar:not(.searchButton) > #urlbar-input-container > #identity-box[pageproxystate="invalid"] {
  pointer-events: inherit;
  -moz-user-focus: ignore;
}

Отредактировано green25 (31-03-2025 08:19:04)

Отсутствует

 

№1730931-03-2025 13:50:49

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

Re: Custom Buttons

Andrey_Krropotkin пишет

Я считаю что в этом коде слишком перемудрено

А можно поподробнее?
По пунктам, где именно ты считаешь перемудрено.

в ошибках при редактировании любой кнопки в ошибках показывает такое сообщение -TypeError: this.cssProperties is undefined

Это, видимо, из-за options.cssInHelp
Либо переключить в false, либо, если прям очень надо,
то попробовать дописать после var optsOvr = options.codeMirror;

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

Выделить код

Код:

//
					if (isCSS) {
						var {generateCssProperties} = require("resource://devtools/server/actors/css-properties.js");
						var {CssProperties, normalizeCssData} = require("resource://devtools/client/fronts/css-properties.js");
						opts.cssProperties = new CssProperties(normalizeCssData({properties: generateCssProperties(document)}));
					}

Uncaught Error: Action with ID 'TranslateBufer1' already added

Ну так ChromeUtils.importESModule()
не возвращает ничего даже близко похожего на g


Можно разделить, PageActions отдельно,
а g, допустим, пусть будет SystemGlobal

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

Выделить код

Код:

((id, g, pa) => addDestructor(reason => id in g && g[id].destroy(reason)) || id in g || ({
    actions: [{
        title: "Перевод из буфера",
        tooltip: "Перевод из буфера",
        iconURL: gticon,
        id: "TranslateBufer1",
        _insertBeforeActionID: "copyURL",

        onCommand: (e, btn) => ujs_google_translat('auto|ru')
    }],
    init() {
        g[id] = this;
        this.actions = this.actions.map(action => {
            action.extensionID = "custombuttons@xsms.org";
            return pa.addAction(new pa.Action(action));
        });
    },
    destroy(reason) {
        if (reason[5] != "e") return;
        delete g[id];
        for(var action of this.actions) action.remove();
    }
}).init())(
    "CBPageActionsMaker", Cu.getGlobalForObject(Cu), PageActions
);

на примере показать как добавлять в новый сайдбар (боковую панель) элементы, на пример самое простое - загрузки

Нет такого «как добавлять».
Как-то насильно запихать разве что.
И, из кнопки это не слишком удобно, возможно что-то типа

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

Выделить код

Код:

(() => {
	var url = "about:downloads";
	var img = "chrome://browser/skin/downloads/downloads.svg";

	var id = "viewDownloadsSidebar";
	var fluentFile = "browser/appmenu.ftl";
	var fluentLabel = "appmenuitem-downloads";

	var sc = SidebarController;

	var sep = document.querySelector("#sidebarMenu-popup > menuseparator");
	if (sep) {
		var switcher = document.createXULElement("menuitem");
		switcher.dataset.l10nId = fluentLabel;
		switcher.id = "sidebar-switcher-downloads";
		switcher.addEventListener("command", () => sc.toggle(id));
		sep.before(switcher);
	}
	var sb = sc.makeSidebar({
		url,
		iconUrl: img,
		elementId: switcher?.id,
		menuL10nId: fluentLabel,
		revampL10nId: fluentLabel,
		menuId: "menu_downloadsSidebar"
	});
	var popup = document.getElementById("viewSidebarMenu");
	var menuitem = sc.createMenuItem(id, sb);
	popup.insertBefore(menuitem,popup.querySelector(".webextension-menuitem"));

	var maybeAddSidebar = sb => {
		var bars = sc.sidebars;
		bars.has(id) || bars.set(id, sb);
	}
	Object.defineProperty(sb, "hasOwnProperty", {value(prop) {
		prop == "extensionId" && setTimeout(maybeAddSidebar, 0, sb);
		return Object.hasOwn(sb, prop);
	}, configurable: true, enumerable: true});

	sc.sidebars.set(id, sb);

	var br = sc.browser;
	var ls = `[href="${fluentFile}"]`;
	try {var tools = toolsNameMap;} catch {tools = defaultTools;}

	Object.defineProperty(tools, id, {get() {
		var doc = br.contentDocument;
		var cust = doc?.querySelector("sidebar-customize");
		if (cust && !doc.head.querySelector(ls)) {
			var link = doc.createElement("link");
			link.rel = "localization";
			link.href = fluentFile;
			doc.head.append(link);
			link.l10nMap = cbu.dbg.ref("l10nMap", cust.constructor, 0).set(id, fluentLabel);
		}
		return "downloads";
	}, configurable: true, enumerable: true});

	var sm = sc.sidebarMain;
	var initSM = setTimeout.bind(null, () => {
		sc._toolsAndExtensions = null;
		sm.fluentStrings.addResourceIds([fluentFile]);
		window.dispatchEvent(new CustomEvent("SidebarItemAdded"));
	});

	if (customElements.get("sidebar-main")) initSM();
	else {
		var resolver = Promise.withResolvers();
		Promise.any([customElements.whenDefined("sidebar-main"), resolver.promise])
			.then(add => add && initSM());
	}

	addDestructor(reason => {
		if (reason[5] != "e") return;

		if (sc.isOpen) {
			var cid = sc.currentID;
			if (cid == id) sc.hide();
			else if (cid == "viewCustomizeSidebar") {
				var link = br.contentDocument.head.querySelector(ls);
				link.l10nMap.delete(id);
				link.remove();
			}
		}
		menuitem.remove();
		switcher?.remove();

		sc.sidebars.delete(id);
		sc.toolsAndExtensions.delete(id);

		resolver?.resolve();
		sm.fluentStrings?.removeResourceIds([fluentFile]);

		window.dispatchEvent(new CustomEvent("SidebarItemRemoved"));		
	});
})();

green25 пишет

Чья кнопка ?

Ничья. Это мод-перемод одной из наследия Infocatcher
Андрей когда-то нечто подобное спрашивал,
но там весьма развесисто в смысле правок.

Отсутствует

 

№1731031-03-2025 18:27:46

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Dumby

Dumby пишет

Андрей когда-то нечто подобное спрашивал,
но там весьма развесисто в смысле правок.

Да,уж..Черт ногу сломит..Придется расстаться

Отсутствует

 

№1731131-03-2025 18:51:41

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

Re: Custom Buttons

Bug 721336 - Ban sync XMLHttpRequest with chrome privileges (Firefox 138+)


Custom Buttons 0.0.7.0.0.36

Отсутствует

 

№1731202-04-2025 10:27:54

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 497
UA: Firefox 137.0

Re: Custom Buttons

Content-Security-Policy: (Политика Report-Only) Настройки страницы блокируют выполнение обработчика события (script-src-attr), поскольку он нарушает следующую директиву: «script-src-attr 'none' 'report-sample' - что это за ошибки, почти во многих кнопках и скриптах

Например:
this.setAttribute("oncommand", "this.toggleEnabled();");
this.setAttribute("oncommand", "this.focusedWindow && this.focusedWindow.focus();");
menuPopup.setAttribute("onclick", "event.stopPropagation()");

var bar = document.getElementById("searchbar");
  if(bar){
        bar._textbox.setAttribute("onmouseover","this.focus()");
        bar._textbox.setAttribute("onmousedown","if(event.button==1){this.showHistoryPopup()}");

         } 
и т.д.

вроде разобрался, надо вместо setAttribute использовать функцию addEventListener Правильно или нет?

Отредактировано Andrey_Krropotkin (02-04-2025 11:53:19)

Отсутствует

 

№1731302-04-2025 12:33:52

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Что делать. В ФФ  после 115 скрипты кастомнык не работают.

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

Выделить код

Код:

try {
    (function() {
        var Cu = Components.utils;
        var {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
        var sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
            wantComponents: true,
            sandboxName: "user_chrome_files"
        });
        sandbox.Services = Services;
        Cu.evalInSandbox(`
            var Ci = Components.interfaces;
            var config = {
                subScript: {},
                observe: function(aSubject, aTopic, aData) {
                    if (aTopic == "domwindowopened" && aSubject instanceof Ci.nsIDOMWindow) {
                        aSubject.addEventListener("DOMContentLoaded", function domLoad() {
                            aSubject.removeEventListener("DOMContentLoaded", domLoad, true);
                            var loc = aSubject.location;
                            if (loc && loc.protocol == "chrome:") {
                                try {
                                    config.subScript.user_chrome.loadIntoWindow(aSubject, loc.href);
                                } catch(ex) { }
                            }
                        }, true);
                    } else if (aTopic == "profile-after-change") {
                        Services.obs.removeObserver(config, "profile-after-change");
                        var file = Services.dirsvc.get("UChrm", Ci.nsIFile);
                        file.append("user_chrome_files");
                        file.append("user_chrome.manifest");
                        if (!file.exists() || !file.isFile()) {
                            this.removeObs();
                            return;
                        }
                        try {
                            var reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
                            reg.autoRegister(file);
                        } catch(ex) {
                            this.removeObs();
                            return;
                        }

                        try {
                            Services.scriptloader.loadSubScript("chrome://user_chrome_files/content/user_chrome.js", this.subScript, "UTF-8");
                        } catch(ex) {
                            this.removeObs();
                        }
                    }
                },
                removeObs: function() {
                    Services.obs.removeObserver(config, "domwindowopened");
                }
            };
            Services.obs.addObserver(config, "profile-after-change", false);
            Services.obs.addObserver(config, "domwindowopened", false);
        `, sandbox);
    })();
} catch(ex) {Cu.reportError(ex);}

Отсутствует

 

№1731402-04-2025 13:04:19

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 497
UA: Firefox 137.0

Re: Custom Buttons

green25
Сразу бросается в глаза. Сейчас такая конструкция для всех resource://gre/modules/
ChromeUtils.importESModule("resource://gre/modules/Services.sys.mjs")
а дальше сам смотри

Отсутствует

 

№1731502-04-2025 13:37:55

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Andrey_Krropotkin

Andrey_Krropotkin пишет

ChromeUtils.importESModule("resource://gre/modules/Services.sys.mjs"

И где его взять ? в 137  его нет...

Отсутствует

 

№1731602-04-2025 13:48:32

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 497
UA: Firefox 137.0

Re: Custom Buttons

green25 Попробуй var {Services} = Components.utils.getGlobalForObject(Components.utils);

Отсутствует

 

№1731702-04-2025 14:13:31

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

Andrey_Krropotkin
У вас как с этим ? Не используется? Эксперименты не работают. Сто раз спрашивал. Есть варианты?

Отсутствует

 

№1731802-04-2025 14:35:09

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 497
UA: Firefox 137.0

Re: Custom Buttons

green25
Больше вариантов нет

Отсутствует

 

№1731902-04-2025 14:49:48

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

Re: Custom Buttons

green25 пишет

Есть варианты?

Есть такой вариант и это называется UCF и  тема отдельная есть


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

Отсутствует

 

№17320Вчера 15:54:39

green25
Участник
 
Группа: Members
Зарегистрирован: 14-12-2024
Сообщений: 52
UA: unknown 0.0

Re: Custom Buttons

в 133 прошло,  137 - нет
config.js

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

Выделить код

Код:

// version, date year-month-day: 2025-1-17
(async () => {
    var file = Services.dirsvc.get("UChrm", Ci.nsIFile), iname;
    file.append("user_chrome_files");
    file.append("user_chrome.manifest");
    if (!file.exists() || !file.isFile())
        return;
    switch (Services.appinfo.ID) {
        case "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": // Firefox or etc.
            iname = "user_chrome.js";
            break;
        case "{3550f703-e582-4d05-9a08-453d09bdfdc6}": // Thunderbird
            iname = "user_chrome_tb.js";
            break;
        default:
            return;
    }
    Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
    .autoRegister(file);
    var sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
        wantComponents: true,
        sandboxName: "UserChromeFiles",
        wantGlobalProperties: ["ChromeUtils"],
    });
    Services.scriptloader.loadSubScript(`chrome://user_chrome_files/content/user_chrome/${iname}`, sandbox);
})();
//
(async xp => {
	var imprt, ids = [
		"custombuttons@xsms.org",
	];
	if (Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).inSafeMode) return;

	if (Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
		var {XPIInternal} = (imprt = url => Cu.import(url, {}))(xp);

	else { // Fx 101+
		var g = Cu.getGlobalForObject(Cu), te = new g.TextEncoder();
		var imp = g.ChromeUtils.import, {XPIInternal} = imp(xp);
		var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
		var rph = ios.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
		imprt = (url, id) => {
			var subst = te.encode(id).join("");
			rph.setSubstitution(subst, ios.newURI(url));
			return imp(`resource://${subst}/`);
		}
	}
	var load = async (file, id) => {
		var rootURI = XPIInternal.getURIForResourceInFile(file, "");
		imprt(rootURI.resolve("startup.jsm"), id).start(rootURI);
	}
	var proto = XPIInternal.BootstrapScope.prototype;
	var func = proto._beforeCallBootstrapMethod;

	proto._beforeCallBootstrapMethod = () => {
		proto._beforeCallBootstrapMethod = func;
		for(var {id, loader, file} of XPIInternal.XPIStates.enabledAddons())
			ids.includes(id) && !loader && load(file, id);
	}
})("resource://gre/modules/addons/XPIProvider.jsm");

//
try {
    (function() {
        var Cu = Components.utils;
        var {Services} = Components.utils.getGlobalForObject(Components.utils);
        var sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
            wantComponents: true,
            sandboxName: "user_chrome_files"
        });
        sandbox.Services = Services;
        Cu.evalInSandbox(`
            var Ci = Components.interfaces;
            var config = {
                subScript: {},
                observe: function(aSubject, aTopic, aData) {
                    if (aTopic == "domwindowopened" && aSubject instanceof Ci.nsIDOMWindow) {
                        aSubject.addEventListener("DOMContentLoaded", function domLoad() {
                            aSubject.removeEventListener("DOMContentLoaded", domLoad, true);
                            var loc = aSubject.location;
                            if (loc && loc.protocol == "chrome:") {
                                try {
                                    config.subScript.user_chrome.loadIntoWindow(aSubject, loc.href);
                                } catch(ex) { }
                            }
                        }, true);
                    } else if (aTopic == "profile-after-change") {
                        Services.obs.removeObserver(config, "profile-after-change");
                        var file = Services.dirsvc.get("UChrm", Ci.nsIFile);
                        file.append("user_chrome_files");
                        file.append("user_chrome.manifest");
                        if (!file.exists() || !file.isFile()) {
                            this.removeObs();
                            return;
                        }
                        try {
                            var reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
                            reg.autoRegister(file);
                        } catch(ex) {
                            this.removeObs();
                            return;
                        }

                        try {
                            Services.scriptloader.loadSubScript("chrome://user_chrome_files/content/user_chrome.js", this.subScript, "UTF-8");
                        } catch(ex) {
                            this.removeObs();
                        }
                    }
                },
                removeObs: function() {
                    Services.obs.removeObserver(config, "domwindowopened");
                }
            };
            Services.obs.addObserver(config, "profile-after-change", false);
            Services.obs.addObserver(config, "domwindowopened", false);
        `, sandbox);
    })();
} catch(ex) {Cu.reportError(ex);}

Отсутствует

 

№17321Вчера 18:29:02

Andrey_Krropotkin
Участник
 
Группа: Members
Зарегистрирован: 11-11-2011
Сообщений: 497
UA: Firefox 137.0

Re: Custom Buttons

Dumby Можно ли в этом коде

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

Выделить код

Код:

function createME (sTyp,sLabel,sCommand,sClass,sId) {
    // Создание элемента меню, меню или меню - параметры, не используемые для определенных типов, передаются как 0
    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    var m = document.createElementNS(XUL_NS, sTyp);
    switch (sTyp) {
      case "menuitem":
        m.setAttribute('label', sLabel);
        m.setAttribute('oncommand', sCommand);
         m.setAttribute('class',sClass);
        break;
      case "menu":
        m.setAttribute('label', sLabel);
        m.setAttribute('id', sId);
        break;
      case "menupopup":
        m.setAttribute('id', sId);
        break;
    }
    return m;
  };

Заменить  m.setAttribute('oncommand', sCommand); на что другое чтобы избавиться от oncommand


есть еще старая кнопка, в ней не работает пункт удалить, ссылается на то что popupNode не определен, посмотри пожалуйста

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

Выделить код

Код:

/*Initialization Code*/
(this.type != "menu" && (this.type = "menu") && !this.hasAttribute("is")) || (popup => {
    var inserter = {
        get docShell() {
            delete this.docShell;
            return this.docShell = "docShell" in document
                && document.docShell instanceof Ci.nsIDocShell
                ? document.docShell : window.docShell;
        },
        get insertText() {
            delete this.insertText;
            return this.insertText = text => {
                if (!this.docShell.isCommandEnabled("cmd_insertText")) return;
                var params = "createCommandParams" in Components.utils
                    ? Cu.createCommandParams()
                    : Components.classes["@mozilla.org/embedcomp/command-params;1"]
                        .createInstance(Components.interfaces.nsICommandParams);
                params.setStringValue("state_data", text);
                this.docShell.doCommandWithParams("cmd_insertText", params);
            }
        },
       insert(text) {
            var br = document.activeElement;
            !br || br.localName != "browser" || !br.isRemoteBrowser
            ? this.insertText(text) : br.messageManager.loadFrameScript(
                `data:,(${this.insertText})${encodeURIComponent(text.toSource())}`
            , false, true);
        }
    };
    this.onmousedown = e => {
        if (e.button) return;
        this.onmousedown = null;

        var data, save = () => {
            var link = custombuttons.makeButtonLink("update", _id);
            var params = custombuttons.cbService.getButtonParameters(link).wrappedJSObject;
            params.help = JSON.stringify(data, null, "\t");
            custombuttons.cbService.installButton(params.wrappedJSObject = params);
        }
        popup.setAttribute("context", "");
        //popup.setAttribute("onpopupshowing", "firstChild.disabled = !gClipboard.read();");
        popup.addEventListener("popupshowing", (event) => {this.firstChild.disabled = !gClipboard.read();}, false);
        popup.add = () => save(data.push(gClipboard.read()));

        var menuitem = popup.appendChild(document.createXULElement("menuitem"));
        menuitem.setAttribute("label", "Добавить из буфера");
       // menuitem.setAttribute("oncommand", "parentNode.add();");
       menuitem.onclick = function (event) { this.parentNode.add(); };
        if (!(data = JSON.parse(this.Help || "[]")).length) return;

        popup.insert = ind => inserter.insert(data[ind]);
        popup.delete = ind => save(data.splice(ind, 1));

        var df = document.createDocumentFragment();
        df.append(document.createXULElement("menuseparator"));
 
        var menugroup = df.appendChild(document.createXULElement("menugroup"));
        //menugroup.setAttribute("oncommand", "parentNode.insert(event.target.index);");
        menugroup.onclick = function (event) { this.parentNode.insert(event.target.index); };

        menugroup.setAttribute("orient", "vertical");
        menugroup.setAttribute("context", "_child");
    

        var context = menugroup
            .appendChild(document.createXULElement("menupopup"))
            .appendChild(document.createXULElement("menuitem"));
        context.setAttribute("label", "Удалить элемент?");
        context.setAttribute("oncommand", "event.stopPropagation(); menupopup.delete(popupNode.index);");
        //context.onclick = function (event) { event.stopPropagation();  this.menupopup.delete(popupNode.index); };
       context.menupopup = popup;

        data.forEach((text, ind) => {
            var menuitem = menugroup.appendChild(document.createXULElement("menuitem"));
            menuitem.setAttribute("label", text.trimLeft().replace(/\s+/g, " ").slice(0, 70));
            menuitem.index = ind;
        });
        popup.append(df);
    }
})(this.appendChild(document.createXULElement("menupopup")));

и если не лень посмотри на предмет oncommand в кнопке
скрытый текст

Выделить код

Код:

// http://infocatcher.ucoz.net/js/cb/undoCloseTabs.js
// https://forum.mozilla-russia.org/viewtopic.php?id=56267
// https://github.com/Infocatcher/Custom_Buttons/tree/master/Undo_Close_Tabs

// Undo Close Tabs button for Custom Buttons
// (code for "initialization" section)

// (c) Infocatcher 2009-2021
// version 0.3.3.3 - 2021-09-04

var options = {
	menuTemplate: [
		"closedWindows",
		"separator",
		"restoreClosedWindows",
		"clearClosedWindows",
		"separator",
		"closedTabs",
		"separator",
		"restoreClosedTabs",
		"clearClosedTabs",
		"separator",
		"clearAll",
		"separator",
		"restoreLastSession",
		"separator",
		"buttonMenu"
	],
	showInTabContextMenu: false,
	/*
	menuTemplateTabContext: [ // like menuTemplate
		"closedTabs",
		"separator",
		"restoreClosedTabs",
		"clearClosedTabs"
	],
	*/
	windowItemTemplate: "(%count) %title",
	windowSelectedTabPrefix: "*",
	buttonTipTemplate: ["header", "title", "url", "closedAt"],
	itemTipTemplate: ["title", "url", "closedAt"],
	hideRestoreAllForSingleEntry: false,
	allowDeleteEntries: true,
	accesskeys: { // Empty string ("") to disable or string with possible values ("0123...", "abcd...")
		closedTabs: "",
		closedWindows: ""
	},
	accesskeySeparator: " ", // <accesskey><separator><label>
	openMenuOnMouseover: false,
	useMenu: false,
	rightClickToUndoCloseTab: false // Useful with "useMenu: true"
};

function _localize(sid) {
	var strings = {
		en: {
			restoreTab: "Restore the most recently closed tab",

			restoreAllTabs: "Restore all tabs",
			restoreAllTabsAccesskey: "t",
			clearTabsHistory: "Clear history of closed tabs",
			clearTabsHistoryAccesskey: "b",

			restoreAllWindows: "Restore all windows",
			restoreAllWindowsAccesskey: "w",
			clearWindowsHistory: "Clear history of closed windows",
			clearWindowsHistoryAccesskey: "d",

			clearAllHistory: "Clear all history",
			clearAllHistoryAccesskey: "C",

			restoreLastSession: "Restore last session",
			restoreLastSessionAccesskey: "s",

			deleteUndoEntry: "Delete",

			buttonMenu: "Button menu",
			buttonMenuAccesskey: "m",

			tabContextMenu: "Recently Closed Tabs",
			tabContextMenuAccesskey: "y",

			itemTip: "%ago ago, %date",
			day: "d"
		},
		ru: {
			restoreTab: "Восстановить последнюю закрытую вкладку",

			restoreAllTabs: "Восстановить все вкладки",
			restoreAllTabsAccesskey: "л",
			clearTabsHistory: "Очистить историю закрытых вкладок",
			clearTabsHistoryAccesskey: "д",

			restoreAllWindows: "Восстановить все окна",
			restoreAllWindowsAccesskey: "о",
			clearWindowsHistory: "Очистить историю закрытых окон",
			clearWindowsHistoryAccesskey: "н",

			clearAllHistory: "Очистить всю историю",
			clearAllHistoryAccesskey: "ч",

			restoreLastSession: "Восстановить последнюю сессию",
			restoreLastSessionAccesskey: "с",

			deleteUndoEntry: "Удалить",

			buttonMenu: "Меню кнопки",
			buttonMenuAccesskey: "М",

			tabContextMenu: "Недавно закрытые вкладки",
			tabContextMenuAccesskey: "о",

			itemTip: "%ago назад, %date",
			day: "д"
		}
	};
	var locale = (function() {
		if("Services" in window && "locale" in Services) {
			var locales = Services.locale.requestedLocales // Firefox 64+
				|| Services.locale.getRequestedLocales && Services.locale.getRequestedLocales();
			if(locales)
				return locales[0];
		}
		var prefs = "Services" in window && Services.prefs
			|| Components.classes["@mozilla.org/preferences-service;1"]
				.getService(Components.interfaces.nsIPrefBranch);
		function pref(name, type) {
			return prefs.getPrefType(name) != prefs.PREF_INVALID ? prefs["get" + type + "Pref"](name) : undefined;
		}
		if(!pref("intl.locale.matchOS", "Bool")) { // Also see https://bugzilla.mozilla.org/show_bug.cgi?id=1414390
			var locale = pref("general.useragent.locale", "Char");
			if(locale && locale.substr(0, 9) != "chrome://")
				return locale;
		}
		return Components.classes["@mozilla.org/chrome/chrome-registry;1"]
			.getService(Components.interfaces.nsIXULChromeRegistry)
			.getSelectedLocale("global");
	})().match(/^[a-z]*/)[0];
	_localize = function(sid) {
		return strings[locale] && strings[locale][sid] || strings.en[sid] || sid;
	};
	return _localize.apply(this, arguments);
}

var JSON = "JSON" in window
	? {
		parse: function(arg) {
			return typeof arg == "string"
				? (JSON = window.JSON).parse(arg)
				: (this.parse = function(obj) {
					return obj;
				}) && arg;
		}
	}
	: "nsIJSON" in Components.interfaces
		? {
			parse: function(s) {
				return Components.classes["@mozilla.org/dom/json;1"]
					.createInstance(Components.interfaces.nsIJSON)
					.decode(s);
			}
		}
		: {
			parse: function(s) {
				return Components.utils.evalInSandbox("(" + s + ")", new Components.utils.Sandbox("about:blank"));
			}
		};

this.onclick = function(e) {
	if(e.target != this)
		return;
	if(e.button == 1 || e.button == 0 && (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey))
		this.undoCloseTabsList.clearAllLists();
	else if(
		e.button == 0
		|| e.button == 2 && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
			&& this.undoCloseTabsList.options.rightClickToUndoCloseTab
	) {
		if(
			e.button == 0 && !this.undoCloseTabsList.options.useMenu
			|| e.button == 2 && this.undoCloseTabsList.options.rightClickToUndoCloseTab
		) {
			if(this.undoCloseTabsList.closedTabCount)
				this.undoCloseTabsList.undoCloseTab();
			else
				this.undoCloseTabsList.drawUndoList() && this.undoCloseTabsList.showMenu(e);
		}
		// Allow use "command" section only from hotkey:
		e.preventDefault();
		e.stopPropagation();
	}
};
if(!this.hasOwnProperty("defaultContextId"))
	this.defaultContextId = this.getAttribute("context") || "custombuttons-contextpopup";
this.onmousedown = function(e) {
	if(e.target != this)
		return;
	if(this.undoCloseTabsList.options.useMenu) {
		if(e.button == 0)
			this.undoCloseTabsList.drawUndoList();
	}
	else if(e.button == 2) {
		var showCbMenu = e.ctrlKey || e.shiftKey || e.altKey || e.metaKey || !this.undoCloseTabsList.drawUndoList();
		this.setAttribute(
			"context",
			showCbMenu
				? this.defaultContextId
				: this.undoCloseTabsList.mpId
		);
	}
};
this.onmouseover = function(e) {
	if(e.target != this)
		return;
	if(!this.disabled)
		this.undoCloseTabsList.updUI();
	this.undoCloseTabsList.options.useMenu && Array.prototype.some.call(
		this.parentNode.getElementsByTagName("*"),
		function(node) {
			if(
				node != this
				&& node.namespaceURI == xulns
				// 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
				&& this.undoCloseTabsList.drawUndoList()
			) {
				node.open = false;
				this.open = true;
				return true;
			}
			return false;
		},
		this
	);
	if(
		this.undoCloseTabsList.options.openMenuOnMouseover
		&& this.undoCloseTabsList.drawUndoList()
	)
		this.undoCloseTabsList.openMenu();
};

this.undoCloseTabsList = {
	button: this,
	options: options,
	mpId: this.id + "-context",
	cmId: this.id + "-contextSub",
	tcmId: this.id + "-tabContextMenu",
	tipId: this.id + "-tooltip",
	errPrefix: "[Custom Buttons :: Undo Close Tabs List]: ",
	get mp() {
		var btn = this.button;
		var mp = btn.getElementsByTagName("menupopup");
		mp = mp.length && mp[0];
		mp && mp.parentNode.removeChild(mp);
		mp = this.createElement("menupopup", {
			id: this.mpId,
			onclick: "this.parentNode.undoCloseTabsList.checkForMiddleClick(event);",
			onpopupshowing: "if(event.target == this) document.popupNode = this.parentNode;",
			onpopuphidden: "if(event.target == this) document.popupNode = null;"
		});
		if(this.cm)
			mp.setAttribute("context", this.cmId);
		var tb = btn.parentNode;
		if(
			this.options.useMenu
			&& tb.getAttribute("orient") == "vertical"
		) {
			// https://addons.mozilla.org/firefox/addon/vertical-toolbar/
			var isRight = tb.parentNode.getAttribute("placement") == "right";
			mp.setAttribute("position", isRight ? "start_before" : "end_before");
		}
		delete this.mp;
		return this.mp = btn.appendChild(mp);
	},
	get useCentextMenu() {
		delete this.useCentextMenu;
		return this.useCentextMenu = this.options.allowDeleteEntries
			&& ("forgetClosedTab" in this.ss || "forgetClosedWindow" in this.ss);
	},
	get cm() {
		delete this.cm;
		if(!this.useCentextMenu)
			return this.cm = null;
		var cm = document.getElementById(this.cmId);
		cm && cm.parentNode.removeChild(cm);
		cm = this.createElement("menupopup", {
			id: this.cmId,
			onpopupshowing: "return this.undoCloseTabsList.canDeleteUndoEntry(this.triggerNode || document.popupNode);"
		});
		var mi = this.createElement("menuitem", {
			oncommand: "this.parentNode.undoCloseTabsList.deleteUndoEntry(this.parentNode.triggerNode || document.popupNode);",
			label: _localize("deleteUndoEntry"),
			closemenu: "single"
		});
		cm.appendChild(mi);
		cm.undoCloseTabsList = this;
		return this.cm = document.getElementById("mainPopupSet").appendChild(cm);
	},
	get cbMenu() {
		var cbPopup = document.getElementById(this.button.defaultContextId);
		if(!cbPopup) {
			Components.utils.reportError(this.errPrefix + "cb menu not found");
			return this.cbMenu = null;
		}
		cbPopup = cbPopup.cloneNode(true);
		var id = "-" + this.button.id.match(/\d*$/)[0] + "-cloned";
		cbPopup.id += id;
		Array.prototype.slice.call(cbPopup.getElementsByAttribute("id", "*")).forEach(function(node) {
			node.id += id;
		});
		var menu = this.createElement("menu", {
			label: _localize("buttonMenu"),
			accesskey: _localize("buttonMenuAccesskey")
		});
		menu.appendChild(cbPopup);
		cbPopup.setAttribute(
			"onpopupshowing",
			'\
			var btn = document.popupNode = this.parentNode.parentNode.parentNode\n\
				.undoCloseTabsList.button;\n\
			custombutton.setContextMenuVisibility(btn);'
		);
		delete this.cbMenu;
		return this.cbMenu = menu;
	},
	get ss() {
		delete this.ss;
		return this.ss = "nsISessionStore" in Components.interfaces
			? (
				Components.classes["@mozilla.org/browser/sessionstore;1"]
				|| Components.classes["@mozilla.org/suite/sessionstore;1"]
			).getService(Components.interfaces.nsISessionStore)
			: SessionStore; // Firefox 61+ https://bugzilla.mozilla.org/show_bug.cgi?id=1450559
	},
	get appInfo() {
		delete this.appInfo;
		return this.appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
			.getService(Components.interfaces.nsIXULAppInfo);
	},
	get appVersion() {
		delete this.appVersion;
		return this.appVersion = parseFloat(this.appInfo.version);
	},
	get platformVersion() {
		delete this.platformVersion;
		return this.platformVersion = parseFloat(this.appInfo.platformVersion);
	},
	get appName() {
		delete this.appName;
		return this.appName = this.appInfo.name;
	},

	init: function() {
		window.addEventListener("TabClose",       this, false);
		window.addEventListener("SSTabRestoring", this, false);
		window.addEventListener("unload",         this, false);
		if(this.appName == "SeaMonkey") // No SSTab* events in SeaMonkey
			window.addEventListener("TabOpen", this, false);
		setTimeout(function(_this) {
			_this.mp.addEventListener("DOMMenuItemActive",   _this, false);
			_this.mp.addEventListener("DOMMenuItemInactive", _this, false);
			_this.initTooltip();
		}, 50, this);
		this.addPbExitObserver(true);
		this.updUIGlobal();
		if(this.options.showInTabContextMenu) setTimeout(function(_this) {
			_this.initTabContext();
		}, 100, this);
	},
	initTabContext: function() {
		var origMi = this.tabContextUndoClose;
		if(!origMi) {
			LOG("Can't find \"Undo Close Tab\" item in tab context menu");
			return;
		}
		var menu = document.getElementById(this.tcmId);
		menu && menu.parentNode.removeChild(menu); // For SeaMonkey
		menu = this.createElement("menu", {
			id: this.tcmId,
			label: _localize("tabContextMenu"),
			accesskey: _localize("tabContextMenuAccesskey"),
			tooltip: this.tipId,
			popupsinherittooltip: "true"
		});
		menu.undoCloseTabsList = this;
		menu.onclick = function(e) {
			if(e.target != this)
				return;
			if(e.button == 1 || e.button == 0 && (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey)) {
				if(this.undoCloseTabsList.closedTabCount) {
					this.undoCloseTabsList.undoCloseTab();
					closeMenus(this);
				}
			}
		};
		var origMp = this.mp;
		var mp = origMp.cloneNode(true);
		mp.id = this.button.id + "-tabContext";
		var _this = this;
		function drawUndoList() {
			var ok = false;
			var opts = _this.options;
			var origTemplate = opts.menuTemplate;
			opts.menuTemplate = opts.menuTemplateTabContext || origTemplate;
			_this.mp = mp;
			try {
				ok = _this.drawUndoList();
			}
			catch(e) {
				Components.utils.reportError(e);
			}
			opts.menuTemplate = origTemplate;
			_this.mp = origMp;
			return ok;
		}
		function updMenu() {
			if(drawUndoList())
				menu.removeAttribute("disabled");
			else
				menu.setAttribute("disabled", "true");
		}
		mp._updatePopup = function(e) {
			if(e.target != this)
				return;
			document.popupNode = _this.button;
			drawUndoList();
		};
		mp.setAttribute("onpopupshowing", "this._updatePopup(event);");
		mp.onclick = function(e) {
			_this.checkForMiddleClick(e, updMenu);
		};
		menu.appendChild(mp);
		addEventListener("popupshown", function(e) {
			if(e.target == e.currentTarget)
				setTimeout(updMenu, 0); // Pseudo async
		}, false, origMi.parentNode);
		addEventListener("DOMMenuItemActive",   this, false, mp);
		addEventListener("DOMMenuItemInactive", this, false, mp);
		origMi.parentNode.insertBefore(menu, origMi.nextSibling);
		origMi.setAttribute("hidden", "true");
	},
	initTooltip: function() {
		var tip = document.getElementById(this.tipId);
		tip && tip.parentNode.removeChild(tip);
		tip = this.tip = this.createElement("tooltip", {
			id: this.tipId,
			orient: "vertical",
			onpopupshowing: "return this.undoCloseTabsList.updTooltip(this, this.triggerNode || document.tooltipNode);",
			onpopuphiding: "this.cancelUpdateTimer();"
		});
		tip.undoCloseTabsList = this;
		tip._updateTimer = 0;
		tip.initUpdateTimer = function(fn, context) {
			if(this._updateTimer)
				clearInterval(this._updateTimer);
			this._updateTimer = setInterval(function() {
				fn.call(context);
			}, 1000);
		};
		tip.cancelUpdateTimer = function() {
			if(this._updateTimer) {
				clearInterval(this._updateTimer);
				this._updateTimer = 0;
			}
		};
		var btn = this.button;
		btn.removeAttribute("tooltiptext");
		btn.setAttribute("tooltip", this.tipId);
		btn.setAttribute("popupsinherittooltip", "true");
		document.getElementById("mainPopupSet").appendChild(tip);
		if(this.appVersion >= 61 && "getAnonymousElementByAttribute" in document) {
			var label = document.getAnonymousElementByAttribute(tip, "class", "tooltip-label");
			label && label.remove();
		}
	},
	_hasPbExitObserver: false,
	addPbExitObserver: function(add) {
		if(add == this._hasPbExitObserver || !("Services" in window))
			return;
		this._hasPbExitObserver = add;
		if(add)
			Services.obs.addObserver(this, "last-pb-context-exited", false);
		else
			Services.obs.removeObserver(this, "last-pb-context-exited");
	},
	destroy: function() {
		window.removeEventListener("TabClose",       this, false);
		window.removeEventListener("SSTabRestoring", this, false);
		window.removeEventListener("unload",         this, false);
		if(this.appName == "SeaMonkey")
			window.removeEventListener("TabOpen", this, false);
		this.mp.removeEventListener("DOMMenuItemActive",   this, false);
		this.mp.removeEventListener("DOMMenuItemInactive", this, false);
		this.addPbExitObserver(false);
		var menu = document.getElementById(this.tcmId);
		if(menu) {
			menu.parentNode.removeChild(menu);
			this.tabContextUndoClose.removeAttribute("hidden");
		}
		var tip = this.tip;
		tip && tip.parentNode && tip.parentNode.removeChild(tip);
	},
	handleEvent: function(e) {
		switch(e.type) {
			case "TabClose":
			case "SSTabRestoring":
			case "TabOpen":
				setTimeout(function(_this) {
					_this.updUI();
				}, 0, this);
			break;
			case "DOMMenuItemActive":
			case "DOMMenuItemInactive":
				if(!("XULBrowserWindow" in window))
					break;
				XULBrowserWindow.setOverLink(
					e.type == "DOMMenuItemActive"
						? (e.target.getAttribute("cb_urlDecoded") || "")
							.replace(/ \n/g, ", ")
						: "",
					null
				);
			break;
			case "unload":
				this.updUIGlobal();
				this.destroy();
		}
	},
	observe: function(subject, topic, data) {
		if(topic == "last-pb-context-exited") {
			setTimeout(function(_this) {
				_this.updUI();
			}, 25, this);
		}
	},

	createElement: function(name, attrs) {
		var node = document.createElementNS(xulns, name);
		if(attrs) for(var attrName in attrs) if(attrs.hasOwnProperty(attrName))
			node.setAttribute(attrName, attrs[attrName]);
		return node;
	},
	get tabContextUndoClose() {
		return document.getElementById("context_undoCloseTab")
			|| document.getElementById("tabContextUndoCloseTab") // Firefox 2.0
			|| document.getAnonymousElementByAttribute(gBrowser, "tbattr", "tabbrowser-undoclosetab"); // SeaMonkey
	},
	get closedWindowCount() {
		if(!("getClosedWindowCount" in this.ss)) {
			delete this.closedWindowCount;
			return this.closedWindowCount = 0;
		}
		this.__defineGetter__("closedWindowCount", function() {
			return this.ss.getClosedWindowCount();
		});
		return this.closedWindowCount;
	},
	get closedTabCount() {
		return this.ss.getClosedTabCountForWindow(window);
	},
	undoCloseTab: function(i) {
		if("undoCloseTab" in window) // Firefox 2.0+
			undoCloseTab(i);
		else // SeaMonkey
			gBrowser.undoCloseTab(i);
	},
	clearUndoTabsList: function() {
		var closedTabCount = this.closedTabCount;
		if(!closedTabCount)
			return;
		if("forgetClosedTab" in this.ss) // Gecko 1.9.2+
			while(closedTabCount--)
				this.ss.forgetClosedTab(window, 0);
		else {
			// Doesn't work in SeaMonkey
			const pName = "browser.sessionstore.max_tabs_undo";
			let val = cbu.getPrefs(pName);
			cbu.setPrefs(pName, 0);
			cbu.setPrefs(pName, val);
		}
		this.updUIGlobal();
	},
	clearUndoWindowsList: function() {
		var closedWindowCount = this.closedWindowCount;
		if(!closedWindowCount)
			return;
		if("forgetClosedWindow" in this.ss) // Gecko 1.9.2+
			while(closedWindowCount--)
				this.ss.forgetClosedWindow(0);
		else
			this.ss.setWindowState(window, '{"windows":[{}],"_closedWindows":[]}', false);
		this.updUIGlobal();
	},
	clearAllLists: function() {
		this.clearUndoTabsList();
		this.clearUndoWindowsList();
	},
	canDeleteUndoEntry: function(mi) {
		switch(mi.getAttribute("cb_type")) {
			case "tab":    return "forgetClosedTab"    in this.ss;
			case "window": return "forgetClosedWindow" in this.ss;
		}
		return false;
	},
	deleteUndoEntry: function(mi) {
		var i = +mi.getAttribute("cb_index");
		if(mi.getAttribute("cb_type") == "window") {
			this.ss.forgetClosedWindow(i);
			this.updUIGlobal();
		}
		else {
			this.ss.forgetClosedTab(window, i);
			this.updUI();
		}
		this.drawUndoList();
	},
	showMenu: function(e, isContext, mp) {
		var btn = this.button;
		document.popupNode = btn.ownerDocument.popupNode = btn;
		if(!mp)
			mp = this.mp;
		if("openPopupAtScreen" in mp)
			mp.openPopupAtScreen(e.screenX, e.screenY, isContext);
		else
			mp.showPopup(btn, e.screenX, e.screenY, isContext ? "context" : "popup", null, null);
	},
	openMenu: function() {
		var mp = this.mp;
		if("openPopup" in mp)
			mp.openPopup(this.button, "after_start");
		else
			mp.showPopup(this.button, -1, -1, "popup", "bottomleft", "topleft");
	},
	drawUndoList: function() {
		var mp = this.mp;

		var wc = this.closedWindowCount;
		var tc = this.closedTabCount;
		var ss = this.ss;
		var canRestoreLastSession = "restoreLastSession" in ss && ss.canRestoreLastSession
		if(!wc && !tc && !canRestoreLastSession) {
			mp.textContent = "";
			mp.hidePopup();
			return false;
		}

		this._undoWindowItems = wc && JSON.parse(ss.getClosedWindowData());
		this._undoTabItems    = tc && JSON.parse(ss.getClosedTabDataForWindow(window));
		var df = document.createDocumentFragment();

		this.options.menuTemplate.forEach(function(sid, indx, arr) {
			switch(sid) {
				case "closedWindows":
					wc && this.addUndoWindowsList(df);
				break;
				case "restoreClosedWindows":
					wc > this.options.hideRestoreAllForSingleEntry
					&& df.appendChild(this.createElement("menuitem", {
						label: _localize("restoreAllWindows"),
						accesskey: _localize("restoreAllWindowsAccesskey"),
						oncommand: "for(var i = 0; i < " + this._undoWindowItems.length + "; ++i) undoCloseWindow();"
					}));
				break;
				case "clearClosedWindows":
					wc && df.appendChild(this.createElement("menuitem", {
						label: _localize("clearWindowsHistory"),
						accesskey: _localize("clearWindowsHistoryAccesskey"),
						oncommand: "this.parentNode.parentNode.undoCloseTabsList.clearUndoWindowsList();"
					}));
				break;
				case "closedTabs":
					tc && this.addUndoTabsList(df);
				break;
				case "restoreClosedTabs":
					tc > this.options.hideRestoreAllForSingleEntry
					&& df.appendChild(this.createElement("menuitem", {
						label: _localize("restoreAllTabs"),
						accesskey: _localize("restoreAllTabsAccesskey"),
						oncommand: "for(var i = 0; i < " + this._undoTabItems.length + "; ++i) this.parentNode.parentNode.undoCloseTabsList.undoCloseTab();"
					}));
				break;
				case "clearClosedTabs":
					tc && df.appendChild(this.createElement("menuitem", {
						label: _localize("clearTabsHistory"),
						accesskey: _localize("clearTabsHistoryAccesskey"),
						oncommand: "this.parentNode.parentNode.undoCloseTabsList.clearUndoTabsList();"
					}));
				break;
				case "clearAll":
					(
						wc && tc
						|| wc && arr.indexOf("clearClosedWindows") == -1
						|| tc && arr.indexOf("clearClosedTabs") == -1
					)
					&& df.appendChild(this.createElement("menuitem", {
						label: _localize("clearAllHistory"),
						accesskey: _localize("clearAllHistoryAccesskey"),
						oncommand: "this.parentNode.parentNode.undoCloseTabsList.clearAllLists();"
					}));
				break;
				case "restoreLastSession": // Gecko 2.0+
					canRestoreLastSession && df.appendChild(this.createElement("menuitem", {
						label: _localize("restoreLastSession"),
						accesskey: _localize("restoreLastSessionAccesskey"),
						oncommand: "this.parentNode.parentNode.undoCloseTabsList.ss.restoreLastSession();"
					}));
				break;
				case "buttonMenu":
					let cbMenu = this.cbMenu;
					if(cbMenu)
						df.appendChild(cbMenu);
				break;
				case "separator":
					if(df.hasChildNodes() && df.lastChild.localName != "menuseparator")
						df.appendChild(document.createElementNS(xulns, "menuseparator"));
				break;
				default:
					Components.utils.reportError(this.errPrefix + 'Invalid template entry: "' + sid + '"');
			}
		}, this);

		while(df.hasChildNodes() && df.lastChild.localName == "menuseparator")
			df.removeChild(df.lastChild);

		this._undoWindowItems = this._undoTabItems = null;

		mp.textContent = "";
		if(!df.hasChildNodes()) {
			mp.hidePopup();
			return false;
		}
		mp.appendChild(df);
		return true;
	},
	addUndoWindowsList: function(undoPopup) {
		// Based on code from chrome://browser/content/browser.js
		// Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.3a1pre) Gecko/20090824 Minefield/3.7a1pre

		var keys = this.options.accesskeys.closedWindows;
		this._undoWindowItems.forEach(function(undoItem, i) {
			var tabs = undoItem.tabs;
			var [key, keyPrefix] = this.getKey(keys, i);
			var title = undoItem.title;
			var selected = undoItem.selected;
			var selectedTab = tabs[selected && selected - 1];
			var urls = [];
			tabs.forEach(function(tab) {
				if(!tab.entries || !tab.entries.length) // Can be [] for about:blank
					return;
				var url = this.convertURI(tab.entries[tab.index - 1].url, 120);
				var selectedPrefix = tab == selectedTab && tabs.length > 1
					? this.options.windowSelectedTabPrefix
					: "";
				urls.push(selectedPrefix + url);
			}, this);
			var url = urls.join(" \n");
			var mi = this.createElement("menuitem", {
				label: keyPrefix + this.options.windowItemTemplate
					.replace("%title", title)
					.replace("%count", tabs.length),
				accesskey: key,
				"class": "menuitem-iconic bookmark-item menuitem-with-favicon",
				oncommand: "undoCloseWindow(" + i + ");",
				cb_url: url,
				cb_urlDecoded: this.convertURI(url),
				cb_closedAt: undoItem.closedAt || 0,
				cb_index: i,
				cb_type: "window"
			});
			if(this.cm)
				mi.setAttribute("context", this.cmId);
			var icon = selectedTab.image || selectedTab.attributes && selectedTab.attributes.image;
			if(icon)
				mi.setAttribute("image", this.cachedIcon(icon));
			if(i == 0)
				mi.setAttribute("key", "key_undoCloseWindow");
			undoPopup.appendChild(mi);
		}, this);
	},
	addUndoTabsList: function(undoPopup) {
		// Based on code from chrome://browser/content/browser.js
		// Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.3a1pre) Gecko/20090824 Minefield/3.7a1pre

		var keys = this.options.accesskeys.closedTabs;
		this._undoTabItems.forEach(function(undoItem, i) {
			var state = undoItem.state;
			var [key, keyPrefix] = this.getKey(keys, i);
			var title = undoItem.title;
			var url = state && state.entries && state.entries[state.index - 1].url || "";
			var mi = this.createElement("menuitem", {
				label: keyPrefix + title,
				accesskey: key,
				class: "menuitem-iconic bookmark-item menuitem-with-favicon",
				oncommand: "this.parentNode.parentNode.undoCloseTabsList.undoCloseTab(" + i + ");",
				cb_url: url,
				cb_urlDecoded: this.convertURI(url),
				cb_closedAt: undoItem.closedAt || 0,
				cb_index: i,
				cb_type: "tab"
			});
			if(
				state
				&& "attributes" in state
				&& "privateTab-isPrivate" in state.attributes
			) // https://addons.mozilla.org/addon/private-tab/
				mi.setAttribute("privateTab-isPrivate", "true");
			if(this.cm)
				mi.setAttribute("context", this.cmId);
			var image = undoItem.image // Firefox
				|| state && state.attributes && state.attributes.image // SeaMonkey
				|| state && state.xultab
					&& /(?:^| )image=(\S+)/.test(state.xultab)
					&& decodeURI(RegExp.$1); // Only Firefox 2.0 ?
			if(image)
				mi.setAttribute("image", this.cachedIcon(image));
			if(i == 0)
				mi.setAttribute("key", "key_undoCloseTab");
			undoPopup.appendChild(mi);
		}, this);
	},
	getKey: function(keys, i) {
		var key = keys && keys.charAt(i % keys.length);
		var keyPrefix = keys && (key + this.options.accesskeySeparator);
		return [key, keyPrefix];
	},
	checkForMiddleClick: function(e, upd) {
		var mi = e.target;
		if(
			"doCommand" in mi
			&& e.button == 1
			&& mi.parentNode == e.currentTarget
		) {
			mi.doCommand();
			if(upd)
				upd();
			else
				this.drawUndoList();
		}
	},
	crop: function(s, crop) {
		if(crop == undefined)
			crop = 500;
		if(s.length <= crop)
			return s;
		var start = Math.round(crop*0.6);
		return s.substr(0, start) + "…" + s.substr(start - crop);
	},
	convertURI: function(uri, crop) {
		if(!uri || uri.indexOf("\n") != -1)
			return uri;
		uri = this.losslessDecodeURI(uri);
		return this.crop(uri, crop);
	},
	losslessDecodeURI: function(uri) {
		if(uri) try {
			return this._losslessDecodeURI(uri);
		}
		catch(e) {
			Components.utils.reportError(e);
		}
		return uri;
	},
	get _losslessDecodeURI() {
		var ldu;
		if("losslessDecodeURI" in window)
			ldu = losslessDecodeURI;
		else if("UrlbarInput" in window) // Firefox 75+
			ldu = ChromeUtils.importESModule("resource:///modules/UrlbarInput.sys.mjs", {}).losslessDecodeURI;
		delete this._losslessDecodeURI;
		return this._losslessDecodeURI = ldu
			? function(uri) {
				return ldu(makeURI(uri));
			}
			: decodeURI;
	},
	cachedIcon: function(src) {
		src = src.replace(/[&#]-moz-resolution=\d+,\d+$/, ""); // Firefox 22+
		if(
			!/^https?:/.test(src)
			// IDN, see https://bugzilla.mozilla.org/show_bug.cgi?id=311045
			|| /^https?:\/\/[^.:\/]+\.[^a-z0-9-]+(?:\/|$)/.test(src) && this.platformVersion < 46
			|| this.appName == "SeaMonkey" && this.appVersion <= 2
			|| this.appName == "Firefox"   && this.appVersion <= 3.5
		)
			return src;
		return "moz-anno:favicon:" + src; // https://bugzilla.mozilla.org/show_bug.cgi?id=467828
	},
	updUI: function() {
		var tabsCount = this.closedTabCount;
		var dis = !tabsCount && !this.closedWindowCount;
		if(
			dis
			&& this.options.useMenu
			&& this.options.menuTemplate.indexOf("restoreLastSession") != -1
			&& "restoreLastSession" in this.ss && this.ss.canRestoreLastSession
		)
			dis = false;
		this.button.disabled = dis;
	},
	updTooltip: function(tip, tn) {
		var template, header, title, url, closedAt;
		if(tn == this.button) {
			template = this.options.buttonTipTemplate;
			header = _localize("restoreTab");
			let undoTabItems = JSON.parse(this.ss.getClosedTabDataForWindow(window));
			if(undoTabItems.length) {
				let lastItem = undoTabItems[0];
				title = lastItem.title;
				url = lastItem.state && lastItem.state.entries
					&& lastItem.state.entries[lastItem.state.index - 1].url;
				closedAt = lastItem.closedAt || 0;
			}
		}
		else if(tn.hasAttribute("cb_index")) {
			template = this.options.itemTipTemplate;
			title = tn.getAttribute("label");
			url = tn.getAttribute("cb_url");
			closedAt = +tn.getAttribute("cb_closedAt");
		}
		else {
			return false;
		}

		var tipData = this.getTooltipData(template, header, title, url, closedAt);
		tip.textContent = "";
		tip.appendChild(tipData);
		if(closedAt && template.indexOf("closedAt") != -1) {
			tip.initUpdateTimer(function() {
				var tipData = this.getTooltipData(template, header, title, url, closedAt);
				if(tipData.textContent != tip.textContent) {
					tip.textContent = "";
					tip.appendChild(tipData);
				}
			}, this);
		}
		return tip.hasChildNodes();
	},
	getTooltipData: function(template, header, title, url, closedAt) {
		var df = document.createDocumentFragment();
		var hasHeader = header && template.indexOf("header") != -1;
		function item(key, val) {
			var lbl = document.createElementNS(xulns, "label");
			lbl.className = "cb-" + key + " tooltip-label";
			lbl.textContent = val;
			lbl.setAttribute("maxwidth", "450"); // Trick to restore right border for long lines
			if(key == "closedAt" || hasHeader && key != "header")
				lbl.style.color = "grayText";
			return df.appendChild(lbl);
		}
		template.forEach(function(key) {
			switch(key) {
				case "header":
					if(header)
						item(key, header);
				break;
				case "title":
					if(title && title != url)
						item(key, title);
				break;
				case "url":
					if(url)
						item(key, this.convertURI(url));
				break;
				case "closedAt":
					if(!closedAt)
						break;
					let dt = Math.round(Math.max(0, Date.now() - closedAt)/1000);
					let days = Math.floor(dt/24/3600);
					dt -= days*24*3600;
					let d = new Date((dt + new Date(dt).getTimezoneOffset()*60)*1000);
					let m = d.getMinutes();
					let ts = d.getHours() + ":" + (m > 9 ? m : "0" + m);
					if(days)
						ts = days + _localize("day") + " " + ts;
					let tsTip = _localize("itemTip")
						.replace("%ago", ts)
						.replace("%date", new Date(closedAt).toLocaleString());
					item(key, tsTip);
			}
		}, this);
		return df;
	},
	get wm() {
		delete this.wm;
		return this.wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
			.getService(Components.interfaces.nsIWindowMediator);
	},
	updUIGlobal: function() {
		var isSeaMonkey = this.appName == "SeaMonkey";
		var ws = this.wm.getEnumerator(isSeaMonkey ? null : "navigator:browser");
		const id = this.button.id;
		while(ws.hasMoreElements()) {
			let win = ws.getNext();
			if(isSeaMonkey && !this.isBrowserWindow(win))
				continue;
			let btn = win.document.getElementById(id);
			if(btn && "undoCloseTabsList" in btn) {
				let ucl = btn.undoCloseTabsList;
				ucl.ensureSessionsInitialized(ucl.updUI, ucl);
			}
		}
	},
	isBrowserWindow: function(win) {
		var loc = window.location.href;
		return loc == "chrome://browser/content/browser.xul"
			|| loc == "chrome://navigator/content/navigator.xul";
	},
	ensureSessionsInitialized: function(callback, context) {
		var _this = this;
		var stopTime = Date.now() + 3e3;
		(function ensureInitialized() {
			try {
				_this.ss.getClosedTabCountForWindow(window);
				callback.call(context);
				return;
			}
			catch(e) {
				if(Date.now() > stopTime) {
					Components.utils.reportError(
						_this.errPrefix
						+ "Can't initialize: nsISessionStore.getClosedTabCountForWindow() failed"
					);
					Components.utils.reportError(e);
					return;
				}
			}
			setTimeout(ensureInitialized, 50);
		})();
	}
};

if(!this.undoCloseTabsList.options.useMenu && this.undoCloseTabsList.useCentextMenu) {
	this.oncontextmenu = function(e) {
		if(
			e.target != this
			|| e.ctrlKey || e.shiftKey || e.altKey || e.metaKey
			|| !this.undoCloseTabsList.mp.hasChildNodes()
		)
			return;
		e.preventDefault();
		this.undoCloseTabsList.showMenu(e); // Show menu without "context" flag
	};
}
if(this.undoCloseTabsList.options.rightClickToUndoCloseTab) {
	this.oncontextmenu = function(e) {
		if(e.target == this && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey)
			e.preventDefault();
	};
}

this.disabled = true;
setTimeout(function(_this) {
	_this.undoCloseTabsList.init();
}, 0, this);



//===================
// Styles
// Used icons from Undo Closed Tabs Button extension

// Styles can't override hardcoded icon

/*
if( // Remove icon only if nsIStyleSheetService works on-the-fly (Firefox 3.0+)
	!Components.ID("{41d979dc-ea03-4235-86ff-1e3c090c5630}")
		.equals(Components.interfaces.nsIStyleSheetService)
) {
	let icon = this.icon
		|| this.ownerDocument.getAnonymousElementByAttribute(this, "class", "toolbarbutton-icon");
	if(icon)
		icon.src = "";
	else
		this.image = "";
}
*/
var cssStr = '\
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");\n\
	@-moz-document url("%windowURL%") {\n\
		%button% {\n\
			list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAEEUlEQVRIS9XVbWxTVRgA4Pfce3tv27t2bbNuXbfCBuyzAZ2MEdkkLH5gJvEH8TOZEo0JFdgmbgjTgEHUGQXGMJEMmDJMiNvQjS9BJhH8MY3IfsB0rnTraCcbm13b2+/1fni2xAVuxOHAH57+adNz3uee9z3vuQj+44FmG79VksglAIoMgBhCSLpdnFkBkiQxl2OQgRdbJhjoKkQofM+ALklSGSNwf1iAjbXNZ3MdI+MB+7vPF989IEnoZwBVQhCWBgVhW13b+WX5KXpFR7fDe/ntZ8wYiONUiXLojlKEU4LGANgRDpZHBH7r9iPnilZaM4jKFTlQ/P5XvHV+6o5si7HzuWUL+hzngSstRfxf0DSwa9cHa3GgWgmQTv4UNMMggyWTMuYtYepOXCKfKMiGZ4tyQEMDjPrDcGVoDI780As3/AGhJIVs2vTk6pqkJAhOFn8KaG1tpV1u53h5+RpGr7s1/uTxCIgk6g/EiaqmM2i5NRseL7SCgwMg8Wo9A5CsBLjPADDki8CbbRdgQiI631n/2NOFANwUUF9frxPECVf1628gn893Sx55IIhxBavcffIi1fO7H15atRJiAoD8XBI4UmYCwOIkCdY3fyuyrGrHZ+UldXJAwoDn5hThQESYpNXXQpJuw4HTiuzM+fBAfu7UFIQ/0wN/nfydrEKwSCdARfMZftVSa6kcEPx+v0NeA7wlKowYXX+AT604+DWTmT4XcufhFsN6r9MJ/gAHiVotpBmNoNNoIctAw8Xf7FKMjx2TA7zX6/3p7860SBCKEGJSBrj4PIyozMkm0KhZsDv7hTUP5Xt+HfFpuvqGVBZTKizEuOePEYiL0V/kQJzjuG9u1zS8RDAhRFv6/FFr5YFTaopicJGRt932yDmJQLQ3JpY9+lEHVZCTB6PecTAmEMNyIIZr8MU/3U8iSSo5RGfZPZGFFftPaU0a9dgxW+mXBEGQtJJ5xbq1hUo3mdENzxjYyoq65UAUA5/MdAGKiFQHSGbB1WEPDZGIq3COYRQD1I/XPJttn39PKRlWYpUgfvraU2/JgXAwGHxvRkAUEd6JAteYJAUhioNLwaigLN1z8sNQLA5mQyK//YWHr8zRaV9EjY2NikDAuwkQ2oL7IIRH9UzATf9PPuBUS4gIKY92u/Zf5zhixaKsAa1BXzOhhrNoz96d21JNaavT0tO9xQ+W5GDg5X8BTE/lCSIBUcqW3kGX3XW1Jz7sdtVXrn31EGpo2Nm0bl1VHt7maTw7Eo1GL8wGwPcOiRSKMpIgUpzOAd2JjqOdNTW1B1HDx7ttlRs2mnCDHZ5NYPkalmXnut3uxe3tbZeqqzd/h/bt21tgs1WocYP13AtAr9cnDg4Omo8fb7leVbXFdUfvg7uB///An8idyhRIYoVPAAAAAElFTkSuQmCC") !important;\n\
		}\n\
		%button%:hover {\n\
			list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAECUlEQVRIS9XVe2xTVRgA8O/ce3tv310fa8P6AOoapwznYtXFzchQJNEYNTHEODSaYLK4jsEaGcQnEpAQu8r+WLJMMmOMIQXnNgeKYTJjfBGzMEGTztVubcGmD9q1t+263t7r2Qxka8DhwD88//SP2/P97vd95zsXwX+80ErjC4JAjE4BvWEN5BFCwvXirAjAwWkfgIVjwczK4Sc7QtlbBpwRBLG+ADVcDnbu+fjUHZMX4+mJA00NNw8IAvoZQKIEsLOp4pv7PaMP2jRKanjclzi/99kKDBSuVaobKhEuCYoCyKIZaMjOcW+89clIXe1qA9H80Hp4uqu/UGszvlNl1J/eVF/pjY9CurERcVcyugq4XAdf5gXowP2SL00XAUkzSGteSxuq7LIDw2Miu9UET9y7DhgSwHcpAhN/RuHrC34QeJ57wED17nryqQ6dDtj5jBYAj8dDB4L+WNNzz0uUStWS+PPHI4so5EtzRNuRL9Hday1QX22D6RkOSLxbShGgZAgwKSiYjsTgo2/GQCGTfbXP8egWO0BqAXC73WVFfi7gbN+Fkskkv1jggCAui2Ri1/BZ6pfpGGy+7x7IFnjA2f69rvziSDopBUY5Cd1ffMtXmfX7+rY2vFsKCBiILwbwfiJL0tIpVihz9J4UlWvL4TaTaWnwRZaMJkFOzMFnP5zjmhprG0uB4szMzGTpkcMImQFG7csUV7X2nmA0ZRow6w2Amw/BSARmZ3MgFotBi8srk0iAERHweygkVBoUg6UAl0gkzl7rTPMEIcogxuBPcVbHByckKrkKJIwYwtFw8cUN1bFfLyaU33uDEp1KDQatDkLRCNy+SnGhFCikUqlT1xsaTiCYDKLNE+n8ndt7Tkp5QKhcKkoMtTwyUgREx7LcY5tdg5TNtBoSbBos5ZJLpUAe9+DoP91PHEmKWUTbJpP59Y7uIYVBKY0NNm/8lCAIkqBE22re9lB6rR7FUwl45fH7x0qBWQx0L3cB8oiUpkmm0h9JiIRsNlBrUkcxQI14w7udx34kxIwE9wLxfW3P7CkFsizL7l8W4HnEk6RovvlksTiLgwvJXEG6sXP4YJ4rQoVGxe3d+vC41ax8AfX09IhS7OXXEBDteA4yeDmXAxY9n3/BhUnApvjoOX9vPJODhnXWKbVWsyMlhdOo8/1Dr1tMa7YYjRWpurp6KwZe+hfA1b/iAyAHmvZ4g0Ff0PtbLhzwH25pbulDne5DPa2OnXfhNAeLON1CofDdSgB875Akw2zCKZn8f0xKhz4fGH21veMIch9+b9uO7U4LHrAPVxK4dI9MJrMGAoHqgYHj405nxxnU1eWqaW1tV+ABO38rALVaXRYK+Q39/Z5wW9vuwA19D24G/v8DfwHJNM8naEZRUgAAAABJRU5ErkJggg==") !important;\n\
		}\n\
		%button%[disabled="true"] {\n\
			list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAADCklEQVRIS9WVz0sbQRTHZ3Y32awxYkOIMaQQjBJ026zVVLCeCpX2H2jpqaceSjGnUpBcKj0IpVA81UtLT4VCoYdepJcapBYKGjfNbwNqTGJsbGKi5pdmd/s2tEVCjBrroQMLw8x77/Pem+/MYnTOA59zfHQmwPj4+AWCIG5OTk6+OyrRpgFOp7MTgn6pVCpdLS0t9MTExH49SFMACNaFMZ7v6enRRSKRiiRJnbCWbQgAI8LjWbyBMaFtdC7r67GLwWD4CQRnLBYL4XK5yoIgbKpUqkB7e9u3oSHrlMViz/2J8bcCnl/8TJCUmVJQW0cB0ls/W91u3gqDNBqNVTNoEcrlcmhnZwdtJDdEiqSEUql0zeFwLMj7VYDf728t7xdTV/oHFeBQrAdIJpPkzMwMA5ljnU5XNwfoAgI7tLKyUhJF0T42NuavApaWltolJEQBQGWz2Xw971gsRvM8r+nr68NKpbKhulOplBSNRldBYWwtgNze3o4d5b28vKxdW1vTQRWEQqFoCIEqCtC26VoAARV4GnkCxJRIJExms5mEgeLxuJDP5wmKokSNRkNotVosw/f29hDYLdQCMABcx91ukKYV2mAxGAykDBgZGflUKBQYCHoVMlebTCYMypLh8VoA2t3d/XAcQN4Ph8P2TCbTC1NxeHj4rbwGFYzOuma1jIphoCKUTqdjtQAJAK9PApBtoF3Xi8WimeO4N78Bt+fm5vRqtZqGivIHBwePawEiaPr5SQFVncPNBHmK8hza4giFQx0KSlGB4D/0en13LUAAgPM0gMO2cKhTcA40yNMN79Moy7IZ7PV6OyrC/iuMkZ2zDSgB8KBZADwXz4KhgIAkKQECe2SzDS5ij8c9beg03tK0tpUZhtHANb93BsAU+KvyhXwlHl+f7+cG7uPvPv7FZZazQv9eQi+Fcrm82SyApmk9nIkK/B8GAr6vNlv/U+wL+O6yvawR1PC+2cCH/QBAwlNyJxTyhViW+4gjEa+lu/sSA0ar/wIgCwu+rmAwmIN3K9rUD+c0ifz/gF/LJ4uAUJQHTwAAAABJRU5ErkJggg==") !important;\n\
		}\n\
		toolbar[iconsize="small"] %button% {\n\
			list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACPElEQVQ4T2NkIBL8//+fEQj+oytnJEY/UDPXzPOPTdINZQ+jG0LQgPvv/wvsvf9k54FzN432X39S9rQ3oR/ZUrgBvb29akxMf7mQJcWVNYTeCWvOfvrijVK9nymDRevqKwqsfxdYiLDNqUgP+whSCzZg4sS+dkYmxlx2NrYfMAM4efiYXonr8Z27/4q52NeGQZiLlYGd4Q/D9iuPGDafu/3/zZevvocrwraCDZgwoWd/QECwtbi4xD8QHxRS178xsPq2LmfKC/FlEBAUYfgDFORlZWCQ52FgUOP5zVCz9sjfiw/eKMENCAwMMeXl5XsMc8FPJhbOjv235A5fe8zoZWvLwMTIxPD45UsGPm5uBiVxQQZN7u8MnVtPXUN2gbGAgOAJpDBg/MrIKtCy/bLRsVtPmXi4uBj+/fn2+9vPvyycHJyMLgZaDAcvXPoFN8A/IMhIUEBoLUoIA+Ps4382qfotZ112nL3JVOyieyjdQYfXuGWtgbCgCOOnzx//IrvAgJ9fYDKWdMH4mYlduX/HOa0AHamD3Jyc5v5TtptzsrMzeBmrXWKcNGkS399/Pw8GBoTIA71QgSNhwaL7/9zjdzN6d10wdDdR/5XrbWbNOHvOjA18fHwmTo6ubECQQChlcnBzR5y9/yT4wcVTb1+/fW7DuGjR3EXR0QlfgMn16N+/fy8TMoCJiUkCiK0PHNqvduXSuTrGNWtWugcHh737+PHjHUKaYfL8/Pyip04dV7tw4dBhgnmBkKEUGwAAgK/XtUYSNDQAAAAASUVORK5CYII=") !important;\n\
		}\n\
		toolbar[iconsize="small"] %button%:hover {\n\
			list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACRElEQVQ4T2NkIBr8Z2RgYPyPrhwoSBj8//+fbfXt11phqqIXGRhRDSFoAFAzz8KLzzdvP3nZ+titFwWPe+OnIVsJN6BnSo88ww8GTmRJBRUV/jeSuvOOXL6nVeJuxBA3Z+cZWfY/M0x1BZc3+Pp+A6kFGzBxYm8T0GnFrKwsP2AGsHPyMH2RNebdfukxc6KLOYMAFzvDq3fvGC49fMFw+dGL/z9+/nQ7UBm+B2zAhIk9+3x9A2ylJKX/gfigkLr9jZHVt305Y4i9JQMbJy/Dn3//GbhZmRiEuVgYWP9+ZVh19MLfHz9/y0MMmNCz39cvwEJQQOgBzAU/mdg4m3dflTty7TGjkYYm2KmvP3xg4OLgYBDm42H48/09w/UnLy8hG2AuLCRyFCkMGL8ysAo0brtoeOzmEyZ2NjYGFsY/v7///sfCzMTCKCEiyvDo+dNfcAP8/AJNhYSEV6GEMCMj0BA2yaqNZ1x3nr/FlO+kfSTbSY/bqHmtAQ8PH+OPH9/+ohggKCjUhyVVMH5hYlfs3XFeO1BH8hAHB4d50NTt5pzs7AyuhioXGWfO7OD/9p35EDAQFcVExYuxJat///6BLWJiYvo/59jtjN5dF418zDV/ZbpZWDDOnTtzI7+AoLGDvSM3KytbFKF0ycHNHXnx/tPgW+dPvnn99qk948LF8+bFRMV/Aqa4Y9+/f79G0AAODjGgUywOHdivefHipUbGVetWOYUGhr778OEDPAoJGSIgICB87Ngh5bNnL54gmBcIGUaxAQAqY9puYCzN/wAAAABJRU5ErkJggg==") !important;\n\
		}\n\
		toolbar[iconsize="small"] %button%[disabled="true"] {\n\
			list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB70lEQVQ4T2NkoBAwEqu/vLzcjpOT81pDQ8MbZD1EGVBZWZn579+/qUxMTBXt7e1dGAbcv3+f4/2n9z6M//7zoLvo7NlzXh8+fPQTFxdnfwEEvPy8R+RlpWd4evruBakFu+DCpfNLONg4/NjZ2X4jG3D16jWup0+fsRsZGTGysLAwvH79muH9+/cMQHMY2NjY8jMzMyfBDNivrKhs+f8/wy+YAb9+/WJcs2YNj66uLgMvLy+Kw75//85w6dKl74yMjNZwAxQVlEz//f13D1nlgwcPBG/evCmloqLCBFQMth1oMwMfHx/Dx48f/z99+vQssgHGDP8ZjqCHwd27d+UfP36szs7ODvLGO2BAcgNdwCEhIcH46NGjt8gGGP3/938VtmgFBrIB0N8mcnJyB1VVVbkOHjxgzMnJxfT169cnCAPklQyApk/AlS6ePXumJSQk9ACoxuHKlStmQPonUG0e4+XLl8X//v29V0lJWQoYKaWEEtaXL1/SgX43AQbJWhMT01DGq9ev7ODm4tKWEJdi+/PnTxwhA4CpMfz161e+79+/e/f7919Pxtu3b85RUlJ5+////8PAwLlPyAAODg5BoPONXr16YfDmzbtWxseP79vLyCgAY+j9Q0KaYfJAVwgCA1DpzZs3p4nKC/gMptgAAO1G2FJifkXnAAAAAElFTkSuQmCC") !important;\n\
		}\n\
	}'
	.replace(/%windowURL%/g, window.location.href)
	.replace(/%button%/g, "#" + this.id);
	
	
var cssURI = this.cssURI = Components.classes["@mozilla.org/network/io-service;1"]
	.getService(Components.interfaces.nsIIOService)
	.newURI("data:text/css," + encodeURIComponent(cssStr), null, null);
var sss = this.sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
	.getService(Components.interfaces.nsIStyleSheetService);
if(!sss.sheetRegistered(cssURI, sss.USER_SHEET))
	sss.loadAndRegisterSheet(cssURI, sss.USER_SHEET);


	
		

this.onDestroy = function(reason) {
	this.undoCloseTabsList.destroy();
	if(reason == "destructor") // May happens before "unload"
		this.undoCloseTabsList.updUIGlobal();
	if(reason == "update" || reason == "delete") {
		let sss = this.sss;
		let cssURI = this.cssURI;
		if(sss.sheetRegistered(cssURI, sss.USER_SHEET))
			sss.unregisterSheet(cssURI, sss.USER_SHEET);
	}
};
if(this.undoCloseTabsList.options.useMenu) {
	this.type = "menu";
	this.orient = "horizontal";
}

Отсутствует

 

Board footer

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