【问题标题】:Firefox SDK Add-on with a sidebar on both the right and left at the same timeFirefox SDK Add-on 左右两侧同时有侧边栏
【发布时间】:2015-12-18 14:13:41
【问题描述】:

我正在编写一个基于 Firefox Add-on SDK 的扩展程序。我需要同时使用左右侧边栏,同时。默认情况下,我可以在左侧显示一个。我已经阅读了有关使用 CSS 在左侧和右侧之间更改 ui/sidebar 的内容:

@namespace url(http://www.mozilla.org/keymaster/gat...re.is.only.xul);
hbox#browser { 
    direction: rtl !important;
}         
hbox#browser > vbox {
    direction: ltr !important; 
}

但是,这似乎是老派的东西,因为我没有定义 .xul 文件。

你知道在浏览器的左右同时有一个侧边栏吗?

【问题讨论】:

  • 您是同时寻找 两个 侧边栏,还是希望您的 一个 侧边栏位于左侧还是对?您希望侧边栏与单个选项卡相关联,还是对所有选项卡都打开?您是否特别想使用ui/sidebar API?您想在侧边栏中添加什么类型的 oc 内容?
  • 您提供的代码是 CSS,而不是 XUL。请澄清为什么“因为我没有定义 .xul 文件”是相关的。
  • @Mayken 因为在 mdn 的高级和低级 api 文档中,有使用没有 xul 的东西的示例(例如侧边栏组件)。那么,如何使用 XUL 编写侧边栏? SDK可以吗?
  • 是的,使用基于 SDK 的插件绝对可以。做什么以及如何实现它在很大程度上取决于您想要的细节。鉴于ui/sidebar 不能满足您的需求,因此有必要直接与构建 Firefox UI 的 XUL 接口(SDK 使您无法直接这样做,但您可以在需要时这样做)。需要更多信息才能准确了解您想要做什么。我会看看我是否可以修改一些我已经拥有的代码,这些代码会创建用于演示的侧边栏。
  • @Mayken 谢谢!我需要同时打开两个侧边栏。我用 require("sdk/ui/sidebar").Sidebar 得到了其中一个,但那个在左侧打开。你能指点我如何操作 XUL 吗?任何关于通过 xul 添加东西的例子都将不胜感激:)

标签: javascript firefox-addon firefox-addon-sdk firefox-sidebar


【解决方案1】:

你的问题不清楚究竟你想要什么。因此,我做了一些假设。

最终,没有官方支持的方式可以一次显示多个“侧边栏”。默认情况下,Firefox 浏览器窗口中只存在一个“侧边栏”。虽然没有支持的 API 为您提供多个侧边栏,但可以通过修改 Firefox 浏览器窗口的 XUL DOM 创建多个侧边栏(界面面板)。

下面的代码在浏览器窗口的顶部、底部、左侧、右侧或单独的窗口中创建用户界面面板(侧边栏、顶部栏、底部栏和窗口)。可以创建界面面板,使其相对于窗口(当您切换选项卡时它们保持可见;如书签/历史侧边栏),或者相对于选项卡(它们仅在它们所在的选项卡时可见)显示已创建;就像开发人员工具一样)。

createInterfacePanelIframe() 方法创建一个包含<iframe>(或<browser>)的界面面板,您可以为其提供 URL 和所有属性。 <iframe><splitter> 的元素将返回给调用者,以便您可以对它们执行其他操作,或者将它们从 Firefox 窗口的 DOM 中删除以删除/隐藏它们。此方法创建<iframe> 并调用createInterfacePanel() 将其插入到Firefox 浏览器的指定位置。

createInterfacePanel() 方法会将您传递给它的 XUL 元素(使用 Document Fragment 传递多个元素)与指定位置的 <splitter> 一起放入 Firefox 浏览器窗口的 DOM(左、右、顶部、底部、窗口以及相对于窗口或选项卡)。您可以指定希望界面面板位于其中的窗口和/或选项卡,或者默认情况下,界面面板插入到当前窗口/选项卡中。如果您指定要插入的界面面板已经存在,则将在现有面板旁边创建另一个面板。您可以创建的数量没有固有限制(例如,如果您愿意,您可以在右侧有 10 个面板)。

此外,下面还有一个演示 Firefox Add-on SDK 扩展,它添加了 6 个不同的按钮。前 5 个按钮创建或销毁界面面板。标记指示按钮将影响哪个面板(左、右、顶部、底部和窗口)。绿色徽章表示将创建一个界面面板。红色徽章表示单击将破坏已经存在的面板。第六个按钮在 Tab 相关界面面板和 Window 相关界面面板之间切换其他按钮。如果您创建所有面板,您将拥有 8 个面板(4 个用于窗口,4 个用于当前选项卡)以及两个单独的窗口(每个浏览器窗口)。

以下是演示插件的外观。下图中显示的面板很窄和/或很短,以允许它们显示在此页面上。下面的代码允许您将面板制作成您想要的任何大小,并且用户可以调整它们的大小。

这是创建侧边栏(界面面板)的代码:

sidebars.js

/**
 * createInterfacePanelIframe(location,options)
 *   Creates an <iframe> based panel within the current tab, within the
 *   current window, or opens a window, for use as an user interface
 *   box.  If it is not a window, it is associated with the current
 *   browser tab, or the current browser window depending on the
 *   byWindow option.

 * @param location 
 *   Placement of the panel [right|left|top|bottom|window]
 *   The default location is 'right'.
 * @param options
 *   An Object containing optional parameters. 
 *     height
 *       The height of a top or bottom sidebar
 *       Default is 200.
 *     width
 *       The width of a left or right sidebar
 *       Default is 200.
 *     size
 *       Width if on left or right. Height if top or bottom.
 *       Both width and height if location='window' unless
 *       features is a string. 
 *       Default is 200.
 *     id
 *       The ID to assign to the iframe. Default is
 *       'makyen-interface-panel'
 *       The <splitter> will be assigned the
 *       ID = id + '-splitter'
 *     url
 *       This is the chrome://  URL to use for the contents
 *       of the iframe or the window.
 *       the default is:
 *       'chrome://devtools/content/framework/toolbox.xul'
 *     iframeAttributes
 *       An Object
 *       Contains key/value pairs which are applies as attributes
 *       of the iframe. These will override any defaults or other
 *       attributes derived from other options (e.g. id, height,
 *       width, type, etc.). If the value of the property is null
 *       then that attribute will be removed.
 *     useBrowser
 *       If true, a <browser> element is used instead of an <iframe>.
 *     byWindow
 *       If true then the created sidebar is for the window
 *       not the tab.
 *     tab
 *       The tab for which to create the sidebar. If not
 *       specified, the current tab is used.
 *     window
 *       The window for which to create the sidebar. If not
 *       specified, the current window is used.
 *     features
 *       The features string for the window. See:
 *       https://developer.mozilla.org/en-US/docs/Web/API/Window.open
 *
 * returns [splitterEl, iframeEl]
 *   The elements for the <splitter> and <iframe>.
 *
 * Copyright 2014-2016 by Makyen.
 * Released under the MPL 2.0. http://mozilla.org/MPL/2.0/.
 **/
function createInterfacePanelIframe(location,options){
    //options
    let size,width,height,id,chromeUrl;
    if(typeof options === 'object'){
        size = options.size;
        width = options.width;
        height = options.height;
        id = options.id;
        chromeUrl = options.url;
    }
    if(!width && !height && size){
        width = size;
        height = size;
    }
    [width,height] = getSizeWithDefaults(location,width,height);

    //defaults
    id = typeof id !== 'string' ? 'makyen-interface-panel' : id;
    chromeUrl = typeof chromeUrl !== 'string'
        ? 'chrome://devtools/content/framework/toolbox.xul'
        : chromeUrl;

    //Create some common variables if they do not exist.
    //This gets the currently active Firefox XUL window.
    //  Add/remove a '/' to comment/un-comment the code appropriate for your add-on type.
    //* Add-on SDK:
    let activeWindow = options.window ?
            options.window : require('sdk/window/utils').getMostRecentBrowserWindow();
    //*/
    /* Overlay and bootstrap (from almost any context/scope):
    Components.utils.import('resource://gre/modules/Services.jsm');//Services
    let activeWindow = options.window ?
            options.window : Services.wm.getMostRecentWindow('navigator:browser');
    //*/
    let mainDocument = activeWindow.document;

    //Create the <iframe> use
    //mainDocument for the XUL namespace.
    let iframeEl;
    if(options.useBrowser){
        iframeEl = mainDocument.createElement('browser');
    } else {
        iframeEl = mainDocument.createElement('iframe');
    }
    iframeEl.id = id;
    iframeEl.setAttribute('src',chromeUrl);
    iframeEl.setAttribute("tooltip", "aHTMLTooltip");
    iframeEl.setAttribute("autocompleteenabled", true);
    iframeEl.setAttribute("autocompletepopup", "PopupAutoComplete");
    iframeEl.setAttribute("disablehistory",true);
    iframeEl.setAttribute('type', 'content');
    if(typeof height === 'number'){
        iframeEl.setAttribute('height', height.toString());
    }
    if(typeof width === 'number'){
        iframeEl.setAttribute('width', width.toString());
    }
    if(typeof options.iframeAttributes === 'object'){
        let attrs = options.iframeAttributes;
        for(let attr in attrs){
            if(attrs.hasOwnProperty(attr)) {
                if(attrs[attr]===null){
                    iframeEl.removeAttribute(attr);
                }else{
                    iframeEl.setAttribute(attr, attrs[attr]);
                }
            }
        }
    }

    //Call createInterfacePanel
    let splitterEl;
    let newOptions = {};
    if(height) {
        newOptions.height = height;
    }
    if(width) {
        newOptions.width = width;
    }
    newOptions.url = chromeUrl;
    if(options.tab){
        newOptions.tab = options.tab;
    }
    if(options.window){
        newOptions.window = options.window;
    }
    if(options.features){
        newOptions.features = options.features;
    }
    if(options.byWindow){
        newOptions.byWindow = options.byWindow;
    }
    newOptions.id = id + '-splitter';
    splitterEl = createInterfacePanel(location, iframeEl, newOptions)
    return [splitterEl, iframeEl];
}

/**
 * createInterfacePanel(location,objectEl,options)
 *   Creates a panel within the current tab, or opens a window, for use as a
 *   user interface box. If not a window, it is associated with the current
 *   browser tab.
 * @param location 
 *   Placement of the panel [right|left|top|bottom|window]
 *   The default location is 'right'.
 * @param objectEl
 *   The element of an XUL object that will be inserted into
 *   the DOM such that it is within the current tab.
 *   Some examples of possible objects are <iframe>,
 *   <browser>, <box>, <hbox>, <vbox>, etc.
 *   If the location='window' and features is not a string
 *   and this is a number then it is used as the width of the
 *   window.
 * @param options
 *   An Object containing optional parameters. 
 *     height
 *       The height of a top or bottom sidebar
 *       Default is 200.
 *     width
 *       The width of a left or right sidebar
 *       Default is 200.
 *     size
 *       Width if on left or right. Height if top or bottom.
 *       Both width and height if location='window' unless
 *       features is a string. 
 *       Default is 200.
 *       If none of height, width or size is specified, then the
 *       size of the sidebar should be specified within the XUL
 *       elements referenced by objectEl.
 *     sizeEl
 *       The element that is to contain attributes of 'width' and 
 *       'height'. If location=='left'|'right' then the 
 *       'height' attribute is removed prior to the objectEl
 *       being inserted into the DOM.
 *       This is an optional spearate reference for the size element
 *       in case the objectEl is a documentFragment containing
 *       multiple elements. However, normal usage is for
 *       objectEl === sizeEl (which is default if unspecified)
 *       when location != 'window'.
 *       When location == 'window' and features is not a string,
 *       and sizeEl is a number then it is used as the height
 *       of the window.
 *       If features is a string, it is assumed the height is
 *       set in that, or elsewhere (e.g. in the XUL).
 *     id
 *       The ID to assign to the <splitter>. The default is:
 *       'makyen-interface-panel-splitter'.
 *     url
 *       This is the chrome://  URL to use for the contents
 *       of the window.
 *       the default is:
 *       'chrome://devtools/content/framework/toolbox.xul'
 *     byWindow
 *       If true then the created sidebar is for the window
 *       not the tab.
 *     tab
 *       The tab for which to create the sidebar. If not
 *       specified, the current tab is used.
 *     window
 *       The window for which to create the sidebar. If not
 *       specified, the current window is used.
 *     features
 *       The features string for the window. See:
 *       https://developer.mozilla.org/en-US/docs/Web/API/Window.open
 *       If features is a string, it is assumed the width is
 *       set in that, or elsewhere (e.g. in the XUL).
 *
 * returns
 *   if location != 'window':
 *     splitterEl, The element for the <splitter>.
 *   if location == 'window':
 *     The windowObjectReference returned by window.open().
 *
 * Copyright 2014-2016 by Makyen.
 * Released under the MPL 2.0. http://mozilla.org/MPL/2.0/.
 **/
function createInterfacePanel(location,objectEl,options) {
//function createInterfacePanel(location,objectEl,sizeEl,id,chromeUrl,features) {
    //options
    let size,width,height,sizeEl,id,chromeUrl,byWindow,features;
    if(typeof options === 'object'){
        size = options.size;
        width = options.width;
        height = options.height;
        //If a separate sizeEl is not specified, then use the ObjectEl for sizeEl.
        //  This is so we could pass a document fragment with multiple elements,
        //  But only one which should have a specified size.
        sizeEl = options.sizeEl? options.sizeEl:objectEl;
        id = options.id;
        chromeUrl = options.url;
        byWindow = options.byWindow;
        features = options.features;
    }
    if(!width && !height && size){
        width = size;
        height = size;
    }
    [width,height] = getSizeWithDefaults(location,width,height);

    //Set location default:
    location = typeof location !== 'string' ? 'right' : location;
    if(location == 'window') {
        if(typeof features !== 'string') {
            let widthText =  'width=' + width.toString() + ',';
            let heightText = 'height=' + height.toString() + ',';
            features = widthText + heightText
                       + 'menubar=no,toolbar=no,location=no,personalbar=no'
                       + ',status=no,chrome=yes,resizable,centerscreen';
        }
    }
    id = typeof id !== 'string' ? 'makyen-interface-panel-splitter' : id;
    chromeUrl = typeof chromeUrl !== 'string'
        ? 'chrome://devtools/content/framework/toolbox.xul'
        : chromeUrl;

    //Create some common variables if they do not exist.
    //This gets the currently active Firefox XUL window.
    //  Add/remove a '/' to comment/un-comment the code appropriate for your add-on type.
    //* Add-on SDK:
    let activeWindow = options.window ?
            options.window : require('sdk/window/utils').getMostRecentBrowserWindow();
    //*/
    /* Overlay and bootstrap (from almost any context/scope):
    Components.utils.import('resource://gre/modules/Services.jsm');//Services
    let activeWindow = options.window ?
            options.window : Services.wm.getMostRecentWindow('navigator:browser');
    //*/
    if (typeof gBrowser === 'undefined') {
        //If there is no gBrowser defined, get it
        var gBrowser = activeWindow.gBrowser;
    }

    //Get the tab & notification box (container for tab UI).
    let tab = options.tab?options.tab:gBrowser.selectedTab;
    let browserForTab = gBrowser.getBrowserForTab( tab );
    let notificationBox = gBrowser.getNotificationBox( browserForTab );
    let ownerDocument = gBrowser.ownerDocument;

    //Create a Document Fragment.
    //If doing multiple DOM additions, we should be in the habit
    //  of doing things in a way which causes the least number of reflows.
    //  We know that we are going to add more than one thing, so use a
    //  document fragment.
    let docFrag = ownerDocument.createDocumentFragment();

    //ownerDocument must be used here in order to have the XUL namespace
    //  or the splitter causes problems.
    //  createElementNS() does not work here.
    let splitterEl = ownerDocument.createElement('splitter');
    splitterEl.id =  id ;

    //Look for the child element with class='browserSidebarContainer'.
    //It is the element in procimity to which the <splitter>
    //and objectEl will be placed.
    let theChild = notificationBox.firstChild;
    while (!theChild.hasAttribute('class')
        || (theChild.getAttribute('class').indexOf('browserSidebarContainer') === -1)
    ) {
        theChild = theChild.nextSibling;
        if(!theChild) {
            //We failed to find the correct node.
            //This implies that the structure Firefox
            //  uses has changed significantly and it should 
            //  be assumed that the extension is no longer compatible.
            return null;
        }
    }
    let tabBrowser = ownerDocument.getElementById('content');
    let heightAttr='height';
    let widthAttr='width';
    if(byWindow) {
        notificationBox = ownerDocument.getElementById('browser');
        theChild = ownerDocument.getElementById('appcontent');
        //When Window referenced, where we need to put the elements is
        //  slightly different, but works out to just being a swapping
        //  of 'location' values.
        //Swap the width and height attributes and options.
        heightAttr='width';
        widthAttr='height';
        let foo = width;
        width = height;
        height = foo;
        foo = options.width;
        options.width = options.height;
        options.height = foo;
        switch(location) {
            case 'window'  :
                //no change
                break;
            case 'top'    :
                location = 'left'
                break;
            case 'bottom' :
                location = 'right'
                break;
            case 'left'   :
                location = 'top'
                break;
            case 'right'  :
            default       :
                location = 'bottom'
                break;
        }
    }

    switch(location) {
        case 'window'    :
            return activeWindow.open(chromeUrl,'_blank',features);
            break;
        case 'top'    :
            if(options.size || options.height) {
                //Don't mess with the height/size unless it was specified
                sizeEl.removeAttribute(widthAttr);
                sizeEl.setAttribute(heightAttr,height);
            }
            docFrag.appendChild(objectEl);
            docFrag.appendChild(splitterEl);
            //Inserting the document fragment results in the same
            //  DOM structure as if you Inserted each child of the
            //  fragment separately. (i.e. the document fragment
            //  is just a temporary container).
            //Insert the interface prior to theChild.
            notificationBox.insertBefore(docFrag,theChild);
            break;
        case 'bottom' :
            if(options.size || options.height) {
                //Don't mess with the height/size unless it was specified
                sizeEl.removeAttribute(widthAttr);
                sizeEl.setAttribute(heightAttr,height);
            }
            docFrag.appendChild(splitterEl);
            docFrag.appendChild(objectEl);
            //Insert the interface just after theChild.
            notificationBox.insertBefore(docFrag,theChild.nextSibling);
            break;
        case 'left'   :
            if(options.size || options.width) {
                //Don't mess with the height/size unless it was specified
                sizeEl.removeAttribute(heightAttr);
                sizeEl.setAttribute(widthAttr,width);
            }
            docFrag.appendChild(objectEl);
            //Splitter is second in this orientaiton.
            docFrag.appendChild(splitterEl);
            if(byWindow) {
                //Insert the interface prior to the tabbrowser to put
                //  global notifications above the top sidebar.
                theChild.insertBefore(docFrag,tabBrowser);
            }else{
                //Insert the interface as the first child of theChild.
                theChild.insertBefore(docFrag,theChild.firstChild);
            }
            break;
        case 'right'  :
        default       :
            //Right orientaiton, the default.
            if(options.size || options.width) {
                //Don't mess with the height/size unless it was specified
                sizeEl.removeAttribute(heightAttr);
                sizeEl.setAttribute(widthAttr,width);
            }
            docFrag.appendChild(splitterEl);
            docFrag.appendChild(objectEl);
            //Insert the interface as the last child of theChild.
            theChild.appendChild(docFrag);
            break;
    }
    return splitterEl;
}

function getSizeWithDefaults(location,width,height){
    let defaultSize = 200;
    switch(location) {
        case 'window'    :
            width = ( (typeof width !== 'number') || width<1) ? defaultSize : width; 
            height = ( (typeof height !== 'number') || height<1) ? defaultSize : height; 
            break;
        case 'top'    :
        case 'bottom' :
            width = null;
            height = ( (typeof height !== 'number') || height<1) ? defaultSize : height; 
            break;
        case 'left'   :
        case 'right'  :
        default       :
            width = ( (typeof width !== 'number') || width<1) ? defaultSize : width; 
            height = null;
            break;
    }
    return [width,height];
}

exports.createInterfacePanel=createInterfacePanel;
exports.createInterfacePanelIframe=createInterfacePanelIframe;

演示 Firefox Add-on SDK 扩展是:

package.json

{
    "title": "Demo Sidebars",
    "name": "demo-sidebars",
    "version": "0.0.1",
    "description": "Demo creating Window related sidebars",
    "main": "index.js",
    "author": "Makyen",
    "permissions": {"private-browsing": true},
    "engines": {
        "firefox": ">=38.0a1",
        "fennec": ">=38.0a1"
    },
    "license": "MIT",
    "keywords": [
        "jetpack"
    ]
}

data/sidebar.html

<html>
<head>
    <meta charset="utf-8">
</head>
<body style="background-color:white;">
    This is a Window.
</body>
</html>

index.js

var utils = require('sdk/window/utils');
var tabs = require('sdk/tabs');
var tabsUtils = require('sdk/tabs/utils');
var self = require('sdk/self');

//For testing: Open the Browser Console
var activeWin = utils.getMostRecentBrowserWindow();
activeWin.document.getElementById('menu_browserConsole').doCommand();

var mySidebars = require('./sidebars.js');
var sidebarSize = 100;  //Width & height to use

var sidebarByWindow = false;
var sidebars = {};
//The buttons
var buttons = {
    '◀': {where:'Left'},
    '▶': {where:'Right'},
    '▲': {where:'Top'},
    '▼': {where:'Bottom'},
    '☐': {where:'Window'}
};

//Create Buttons
var sdkActionButtons     = require('sdk/ui/button/action');

for(let badge in buttons){
    buttons[badge].button = sdkActionButtons.ActionButton({
        id: 'openSidebar' + buttons[badge].where,
        label: 'Open ' + buttons[badge].where + ' Sidebar',
        badge: badge,
        badgeColor: 'green',
        icon: './icons/Aurora-icon64.png',
        onClick: handleButtonClick
    });
}

function handleButtonClick(state){
    let where = buttons[state.badge].where.toLowerCase();
    let stateType = getSidebarByWindowText();
    let sidebarId = getSidebarId(state.badge,sidebarByWindow);
    //With this state being kept by window and tab, the checked property does
    //  not accurately track what we need to be doing, so use badgeColor and
    //  action buttons.
    if(sidebars[sidebarId]){
        //If we have a sidebar for this combo, then
        let elements = sidebars[sidebarId];
        if(elements){
            if(where==='window'){
                try{
                    elements[0].close();
                }catch(e){
                    //Do nothing. We should be tracking the state of the window so
                    //  users can use the close button. We are not, so trying to
                    //  close an already closed window could throw an error.
                }
            } else {
                elements.forEach(el => {el.remove();});
            }
        }
        delete sidebars[sidebarId];
    }else{
        //Create the sidebar and keep track of it so it can be removed.
        sidebars[sidebarId] = mySidebars.createInterfacePanelIframe(where,{
            url:self.data.url('sidebar.html'),
            byWindow:sidebarByWindow,
            size:sidebarSize,
            id:'makyen-interface-panel-' + stateType + '- ' + where
        });
        //Make the text reflect the sidebar
        if(where !== 'window'){
            setBodyText(sidebarId, 'This is a ' +stateType + ' ' + where + ' Sidebar.');
            sidebars[sidebarId][1].addEventListener('load', setBodyText.bind(null
                ,sidebarId
                ,'This is a ' + stateType.toUpperCase() + ' ' + where + ' Sidebar.'),true);
        }
    }
    updateButtonBadgeColors();
}

function setBodyText(sidebarId,text){
    let doc = sidebars[sidebarId][1].contentDocument; 
    doc.body.textContent = text;
}

function getSidebarId(badge,sidebarByWindow,domWin){
    let where = buttons[badge].where.toLowerCase();
    let stateType = getSidebarByWindowText();
    domWin = domWin?domWin:utils.getMostRecentBrowserWindow();
    let winId = utils.getOuterId(domWin);
    //This should get the tab ID from any window, not just the active window.
    let tabId = tabsUtils.getTabId(tabsUtils.getActiveTab(domWin));
    let id = sidebarByWindow?winId:tabId;
    return stateType+id+where;
}

function getSidebarByWindowText(){
    return sidebarByWindow?'window':'tab';
}

function updateButtonBadgeColors(){
    //Update the badge colors in all windows based on if there is a sidebar of the
    //  current type for the window/tab.
    let allWindows = utils.windows('navigator:browser',{includePrivate:true});
    for(let win of allWindows){
        for(let badge in buttons){
            let sidebarId = getSidebarId(badge,sidebarByWindow,win);
            buttons[badge].button.state(win,{
                badgeColor : sidebars[sidebarId]?'red':'green'
            });
        }
    }
}
//update badge colors each time the active tab changes.
tabs.on('activate',updateButtonBadgeColors);

//var sdkToggleButtons     = require('sdk/ui/button/toggle');
var windowTabLabelText = 'Sidebars are associated with ';
var windowTabToggleButton = sdkActionButtons.ActionButton({
    id: 'windowTabToggleButton',
    label: windowTabLabelText + getSidebarByWindowText(),
    icon: './icons/Aurora-icon64.png',
    onClick: handlewindowTabToggle
});

function handlewindowTabToggle(state){
    if(!state.badge){
        windowTabToggleButton.badge= '☐';
        windowTabToggleButton.badgeColor= 'blue';
        sidebarByWindow = true;
    } else {
        windowTabToggleButton.badge= '';
        sidebarByWindow = false;
    }
    windowTabToggleButton.label = windowTabLabelText + getSidebarByWindowText();
    updateButtonBadgeColors();
}

此答案中的代码已从我对“Firefox Extension, Window related sidebar”的答案进行了改编并显着扩展。该答案包含重要的附加信息,例如侧边栏(界面面板)在 Firefox 浏览器中的结构。

【讨论】:

  • 非常感谢!这真的很好,不仅仅是代码,还有这个和你引用的问题中的解释:)
  • @gal007。我很高兴这很有帮助。我会在此处添加更多解释(而不是将其留在较旧的答案中),但我已经不得不删除一些内容并重新格式化,以达到答案的 30k 字符限制。
【解决方案2】:

firefox 中有一个侧边栏 API,这个插件很好地利用了它 - https://addons.mozilla.org/en-US/firefox/addon/whatsapp-web-messenger/

每个 chrome 窗口中似乎都有一个 window.SidebarUI 对象。

【讨论】:

  • 我使用的是同一个侧边栏,但它在左侧,而不是右侧。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-04
  • 2014-10-15
  • 1970-01-01
  • 2021-11-29
  • 1970-01-01
  • 2020-12-28
  • 1970-01-01
相关资源
最近更新 更多