编程语言
首页 > 编程语言> > javascript – Firefox SDK附加组件,同时在右侧和左侧都有侧边栏

javascript – Firefox SDK附加组件,同时在右侧和左侧都有侧边栏

作者:互联网

我正在编写一个基于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文件.

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

解决方法:

你的问题并不清楚你到底想要什么.因此,我做了一些假设.

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

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

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

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

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

以下是演示附加组件的外观.下图中显示的面板很窄和/或很短,以便在此页面上显示.下面的代码允许您根据需要制作任何尺寸的面板,并使用户能够调整它们的大小.
Demo Add-on in action

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

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"
    ]
}

数据/ 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浏览器中如何构建侧边栏(界面面板).

标签:javascript,firefox-addon,firefox-addon-sdk,firefox-sidebar
来源: https://codeday.me/bug/20191002/1845175.html