Firefox SDK插件同时在右侧和左侧都有侧边栏 [英] Firefox SDK Add-on with a sidebar on both the right and left at the same time

查看:172
本文介绍了Firefox SDK插件同时在右侧和左侧都有侧边栏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编制基于



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

sidebars.js

/ **
* createInterfacePanelIframe(location,options)
* < iframe>在
*当前窗口中的当前选项卡内的基于窗口的面板,或者打开一个窗口,用作用户界面
*框。如果它不是一个窗口,它将与当前的
*浏览器标签或当前的浏览器窗口相关联,具体取决于
* byWindow选项。

* @param location
*放置面板[right | left | top | bottom | window]
*默认位置是'right'。
* @param选项
*包含可选参数的对象。
* height
*顶部或底部边栏的高度
*默认为200.
* width
*左边或右边栏的宽度
*默认值为200.
* size
*如果在左边或右边的宽度。如果顶部或底部高度。
*如果位置=窗口,则宽度和高度都不相同,除非
* features是一个字符串。
*默认为200.
* id
*要分配给iframe的ID。默认是
*'makyen-interface-panel'
*< splitter>将分配
* ID = id +'-splitter'
* url
*这是用于iframe内容
*的chrome:// URL窗户。
*缺省值是:
*'chrome://devtools/content/framework/toolbox.xul'
* iframeAttributes
*一个对象
*包含键/值对应用于iframe的属性
*。这些将覆盖从其他选项(例如id,height,
* width,type等)派生的任何默认值或其他
*属性。如果该属性的值为空
*,那么该属性将被删除。
* useBrowser
*如果为true,则< browser>元素被用来代替< iframe> ;.
* byWindow
*如果为true,那么创建的侧边栏是窗口
*而不是标签。
* tab
*要为其创建边栏的选项卡。如果未指定
*,则使用当前选项卡。
*窗口
*要为其创建边栏的窗口。如果未指定
*,则使用当前窗口。
*特征
*窗口的特征字符串。请参阅:
* https://developer.mozilla.org/en-US/docs/Web/API/Window.open
*
*返回[splitterEl,iframeEl]
*<分隔符>的元素和< iframe> ;.
*
*版权所有2014-2016由Makyen。
*在MPL 2.0下发布。 http://mozilla.org/MPL/2.0/。
** /
函数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);

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

//创建一些公共变量,如果它们不存在的话。
//获取当前活动的Firefox XUL窗口。
//添加/删除'/'以评论/取消注释适合您的加载项类型的代码。
// *附加SDK:
让activeWindow = options.window?
options.window:require('sdk / window / utils')。getMostRecentBrowserWindow();
// * /
/ *覆盖和引导(来自几乎所有的上下文/范围):
Components.utils.import('resource://gre/modules/Services.jsm') ; //服务
让activeWindow = options.window?
options.window:Services.wm.getMostRecentWindow('navigator:browser');
// * /
let mainDocument = activeWindow.document;

//创建< iframe>使用
// mainDocument作为XUL命名空间。
让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(attr inrs){
if(attrs.hasOwnProperty(attr)){
if(attrs [attr] === null){
iframeEl.removeAttribute ATTR);
} else {
iframeEl.setAttribute(attr,attrs [attr]);




$ b //调用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];

$ b / **
* createInterfacePanel(location,objectEl,options)
*在当前标签中创建一个面板,或者打开一个窗口,用作
*用户界面框。如果不是窗口,则与当前的
*浏览器选项卡关联。
* @param location
*放置面板[right | left | top | bottom | window]
*默认位置是'right'。
* @param objectEl
* XUL对象的元素,将插入到
* DOM中,使其位于当前选项卡内。
*可能对象的一些示例是< iframe>,
*< browser>< box>,< hbox>,< vbox>等。位置='窗口'和功能不是一个字符串
*,这是一个数字,那么它被用作
*窗口的宽度。
* @param选项
*包含可选参数的对象。
* height
*顶部或底部边栏的高度
*默认为200.
* width
*左边或右边栏的宽度
*默认值为200.
* size
*如果在左边或右边的宽度。如果顶部或底部高度。
*如果位置=窗口,则宽度和高度都不相同,除非
* features是一个字符串。
*默认为200.
*如果没有指定height,width或size,那么边栏的
*大小应该在引用的XUL
*元素中指定objectEl。
* sizeEl
*包含'width'和
*'height'属性的元素。如果location =='left'|'right',那么
*'height'属性会在objectEl
*被插入到DOM之前被删除。
*如果objectEl是包含
*多个元素的documentFragment,则这是size元素
*的可选特殊引用。但是,正常的用法是
* objectEl === sizeEl(如果未指定,这是默认的)
*当location!='window'时。
*当位置=='窗口'和要素不是字符串时,
*和sizeEl是一个数字,那么它被用作窗口的高度
*。
*如果特征是一个字符串,则假定高度为
*,或者在其他位置(例如在XUL中)设置。
* id
*分配给< splitter>的ID。默认值是:
*'makyen-interface-panel-splitter'。
* url
*这是用于窗口内容
*的chrome:// URL。
*默认是:
*'chrome://devtools/content/framework/toolbox.xul'
* byWindow
*如果为true,则创建的侧边栏是窗口
*不是标签。
* tab
*要为其创建边栏的选项卡。如果未指定
*,则使用当前选项卡。
*窗口
*要为其创建边栏的窗口。如果未指定
*,则使用当前窗口。
*特征
*窗口的特征字符串。请参阅:
* https://developer.mozilla.org/en-US/docs/Web/API/Window.open
*如果功能是字符串,则假定宽度为
*设置在其中或其他地方(例如在XUL中)。
*
*返回
* if location!='window':
* splitterEl,< splitter>的元素。
* if location =='window':
* window.open()返回的windowObjectReference。
*
*版权所有2014-2016由Makyen。
*在MPL 2.0下发布。 http://mozilla.org/MPL/2.0/。
** /
函数createInterfacePanel(location,objectEl,options){
//函数createInterfacePanel(location,objectEl,sizeEl,id,chromeUrl,features){
// options
让size,width,height,sizeEl,id,chromeUrl,byWindow,features;
if(typeof options ==='object'){
size = options.size;
width = options.width;
height = options.height;
//如果没有指定单独的sizeEl,则使用ObjectEl作为sizeEl。
//这就是我们可以传递一个具有多个元素的文档片段
//但是只有一个应该有一个指定的大小。
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);

//设置位置默认值:
位置= typeof位置!=='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;

//创建一些公共变量,如果它们不存在的话。
//获取当前活动的Firefox XUL窗口。
//添加/删除'/'以评论/取消注释适合您的加载项类型的代码。
// *附加SDK:
让activeWindow = options.window?
options.window:require('sdk / window / utils')。getMostRecentBrowserWindow();
// * /
/ *覆盖和引导(来自几乎所有的上下文/范围):
Components.utils.import('resource://gre/modules/Services.jsm') ; //服务
让activeWindow = options.window?
options.window:Services.wm.getMostRecentWindow('navigator:browser');
// * /
if(typeof gBrowser ==='undefined'){
//如果没有定义gBrowser,可以获取
var gBrowser = activeWindow.gBrowser;
}

//获取标签&通知框(标签UI的容器)。
让tab = options.tab ?options.tab:gBrowser.selectedTab;
let browserForTab = gBrowser.getBrowserForTab(tab);
let notificationBox = gBrowser.getNotificationBox(browserForTab);
让ownerDocument = gBrowser.ownerDocument;

//创建一个文档片段。
//如果做多个DOM添加,我们应该习惯
//做一些导致回流次数最少的方法。
//我们知道我们要添加多个东西,所以使用
//文档片段。
let docFrag = ownerDocument.createDocumentFragment();

//为了让XUL命名空间
// // splitter会导致问题,必须在这里使用ownerDocument。
// createElementNS()在这里不起作用。
让splitterEl = ownerDocument.createElement('splitter');
splitterEl.id = id;

//查找class ='browserSidebarContainer'的子元素。
//这是< splitter><
//和objectEl将被放置。
let theChild = notificationBox.firstChild;
while(!theChild.hasAttribute('class')
||(theChild.getAttribute('class')。indexOf('browserSidebarContainer')=== -1)
){
theChild = theChild.nextSibling;
if(!theChild){
//我们找不到正确的节点。
//这意味着Firefox
//使用的结构已经发生了很大的变化,应该假设
//被认为是扩展名不再兼容。
返回null;
}
}
let tabBrowser = ownerDocument.getElementById('content');
let heightAttr ='height';
let widthAttr ='width';
if(byWindow){
notificationBox = ownerDocument.getElementById('browser');
theChild = ownerDocument.getElementById('appcontent');
//当Window引用的时候,我们需要放置元素
//略有不同,但是只需要交换
//'location'值。
//交换宽度和高度属性和选项。
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':
//无变化
break;
case'top':
location ='left'
break;
'bottom':
位置='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){
//除非指定
sizeEl,否则不要乱用height / size。的removeAttribute(widthAttr);
sizeEl.setAttribute(heightAttr,height);
}
docFrag.appendChild(objectEl);
docFrag.appendChild(splitterEl);
//插入文档片段会得到相同的
// DOM结构,就像分别插入
//片段的每个子元素一样。 (即文档片段
//只是一个临时容器)。
//在儿童之前插入接口。
notificationBox.insertBefore(docFrag,theChild);
break;
case'bottom':
if(options.size || options.height){
//除非指定
sizeEl,否则不要乱用height / size。的removeAttribute(widthAttr);
sizeEl.setAttribute(heightAttr,height);
}
docFrag.appendChild(splitterEl);
docFrag.appendChild(objectEl);
//在theChild之后插入接口。
notificationBox.insertBefore(docFrag,theChild.nextSibling);
break;
case'left':
if(options.size || options.width){
//除非指定
sizeEl,否则不要乱用height / size。的removeAttribute(heightAttr);
sizeEl.setAttribute(widthAttr,width);
}
docFrag.appendChild(objectEl);
//分配器在这方面排名第二。
docFrag.appendChild(splitterEl);
if(byWindow){
//在tabbrowser之前插入接口,将
//全局通知放在顶部边栏之上。
theChild.insertBefore(docFrag,tabBrowser);
} else {
//将接口作为孩子的第一个孩子插入。
theChild.insertBefore(docFrag,theChild.firstChild);
}
break;
案例'权利':
默认:
//正确的方向,默认。
if(options.size || options.width){
//除非指定
sizeEl.removeAttribute(heightAttr);否则不要乱用高度/大小。
sizeEl.setAttribute(widthAttr,width);
}
docFrag.appendChild(splitterEl);
docFrag.appendChild(objectEl);
//将接口作为孩子的最后一个孩子插入。
theChild.appendChild(docFrag);
break;
}
return splitterEl;


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;
'top':
'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附加SDK扩展是:

package.json
$ b

  {
title:Demo Sidebars,
name:demo-sidebars,
version:0.0.1,
description:演示创建窗口相关侧边栏,
main:index.js,
author:Makyen,
permissions:{private-browsing:true},
engines:{
firefox:> = 38.0a1,
fennec:> = 38.0a1
},
:MIT,
关键字:[
jetpack
]
}


$ b data / sidebar.html :

 < HTML> 
< head>
< meta charset =utf-8>
< / head>
< body style =background-color:white;>
这是一个窗口。
< / 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');

//用于测试:打开浏览器控制台
var activeWin = utils.getMostRecentBrowserWindow();
activeWin.document.getElementById('menu_browserConsole')。doCommand();

var mySidebars = require('./ sidebars.js');
var sidebarSize = 100; //宽度&高度使用

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

//创建按钮
var sdkActionButtons = require('sdk / ui / button / action');

(让按钮中的徽章){
buttons [badge] .button = sdkActionButtons.ActionButton({
id:'openSidebar'+ buttons [badge] .where,
标签:打开+按钮[徽章]。其他+侧边栏,
徽章:徽章,
badgeColor:'绿色',
图标:'./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);
//这个状态由窗口和标签保存,被检查的属性是
//不准确地跟踪我们需要做什么,所以使用badgeColor和
//动作按钮。
if(sidebars [sidebarId]){
//如果我们有这个组合的边栏,那么
让元素=边栏[sidebarId];
if(elements){
if(where ==='window'){
try {
elements [0] .close();
} catch(e){
//什么也不做。我们应该跟踪窗口的状态,所以
//用户可以使用关闭按钮。我们不是,所以试图
//关闭一个已经关闭的窗口可能会抛出一个错误。
}
} else {
elements.forEach(el => {el.remove();});
}
}
删除侧边栏[sidebarId];
} else {
//创建边栏并对其进行跟踪,以便将其移除。
sidebars [sidebarId] = mySidebars.createInterfacePanelIframe(其中,{
url:self.data.url('sidebar.html'),
byWindow:sidebarByWindow,
size:sidebarSize ,
id:'makyen-interface-panel-'+ stateType +' - '+ where
});
//让文字反映边栏
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();


函数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);
//这应该从任何窗口获取标签ID,而不仅仅是活动窗口。
let tabId = tabsUtils.getTabId(tabsUtils.getActiveTab(domWin));
让id = sidebarByWindow?winId:tabId;
return stateType + id + where;


函数getSidebarByWindowText(){
return sidebarByWindow?'window':'tab';


function updateButtonBadgeColors(){
//更新所有窗口中的徽章颜色是否有
// // current类型的边栏窗口/标签。
let allWindows = utils.windows('navigator:browser',{includePrivate:true});
(让allWindows获胜){
(让按钮中的徽章){
let sidebarId = getSidebarId(badge,sidebarByWindow,win);
buttons [badge] .button.state(win,{
badgeColor:sidebars [sidebarId]?'red':'green'
});
}
}
}
//每当活动标签更改时更新徽章颜色。
tabs.on('activate',updateButtonBadgeColors);

// var sdkToggleButtons = require('sdk / ui / button / toggle');
var windowTabLabelText ='侧边栏与'关联';
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();
}

The code in this answer was adapted and significantly expanded from my answer to \"Firefox Extension, Window related sidebar\". That answer has significant additional information as how sidebars (interface panels) are structured within the Firefox Browser.


I'm programming a Firefox Add-on SDK based extension. I need to use both a left and a right-side sidebar, at the same time. By default, I can display one on the left side. I've already read about changing between having the ui/sidebar on the left and the right by using the CSS:

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

But, that seems to be something of the old-school, because I didn't define .xul files.

Do you know any to have a sidebar on both the left and right of the browser at the same time?

解决方案

Your question is not clear as to exactly what you want. Thus, I have made some assumptions.

Ultimately, there is no officially supported way to have more than one "sidebar" displayed at a time. By default, only a single "sidebar" exist within the Firefox Browser windows. While there is no supported API which provides you with more than one sidebar, it is possible to create multiple sidebars (interface panels) by modifying the XUL DOM for the Firefox Browser windows.

The code below creates user interface panels (side-bars, top-bars, bottom-bars and windows) which are on the top, bottom, left, right, of the browser window, or in a separate window. The interface panels can be created such that they are with respect to the window (they stay visible when you switch tabs; like a bookmarks/history sidebar), or are with respect to the tab (they on only visible when the tab in which they were created is displayed; like the developer tools).

The createInterfacePanelIframe() method creates an interface panel containing an <iframe> (or <browser>) for which you can supply the URL and all attributes. The elements for the <iframe> and the <splitter> are returned to the caller so you can perform other operations on them, or remove them from the Firefox window's DOM to delete/hide them. This method creates <iframe> and calls createInterfacePanel() to insert it into the Firefox browser in the location specified.

The createInterfacePanel() method will put the XUL element that you pass to it (use a Document Fragment to pass multiple elements) into the Firefox Browser window's DOM, along with a <splitter> in the location specified (left, right, top, bottom, window and relative to either the window or the tab). You can specify the window and/or tab you desire the interface panel to be within, or, by default, the interface panel is inserted in the current window/tab. If an interface panel already exists where you are specifying one to be inserted, another panel will be created adjacent to the existing one. There is no inherent limit to the number you can create (e.g. you could have 10 panels on the right, if you wanted).

In addition, a demo Firefox Add-on SDK extension is below which adds 6 different buttons. The first 5 buttons create or destroy interface panels. The badge indicates which panel the button will affect (left, right, top, bottom, and window). A green badge indicates an interface panel will be created. A red badge indicates clicking will destroy the already existing panel. The sixth button toggles the other buttons between Tab relative interface panels, and Window relative interface panels. If you create all the panels your will have 8 panels (4 for the Window and 4 for the current tab) along with two separate windows (per Browser Window).

The following is what the demo add-on looks like. The panels shown in the image below are narrow and/or short to allow them to be displayed on this page. The code below allows you to make the panels whatever size you desire and for the user to be able to resize them.

This is the code which creates the sidebars (interface panels):

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;

The demo Firefox Add-on SDK extension is:

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

The code in this answer was adapted and significantly expanded from my answer to "Firefox Extension, Window related sidebar". That answer has significant additional information as how sidebars (interface panels) are structured within the Firefox Browser.

这篇关于Firefox SDK插件同时在右侧和左侧都有侧边栏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆