Firefox SDK 附加组件,左右两侧同时有侧边栏
Firefox SDK Add-on with a sidebar on both the right and left at the same time
我正在使用 CSS:
在左侧和右侧编程 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
@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,这个插件很好地利用了它 - https://addons.mozilla.org/en-US/firefox/addon/whatsapp-web-messenger/
每个chrome window中似乎有一个window.SidebarUI
对象。
您的问题不完全您想要什么。因此,我做了一些假设。
最终,官方不支持一次显示多个 "sidebar" 的方法。默认情况下,Firefox 浏览器 windows 中只存在一个 "sidebar"。虽然不支持为您提供多个侧边栏的 API,但可以通过修改 Firefox 浏览器 windows 的 XUL DOM 创建多个侧边栏(界面面板)。 =26=]
下面的代码创建了位于浏览器顶部、底部、左侧、右侧的用户界面面板(侧栏、顶部栏、底部栏和 windows)window,或在单独的 window 中。可以创建界面面板,使其相对于 window(当您切换选项卡时它们保持可见;就像 bookmarks/history 侧边栏),或者相对于选项卡(它们仅在可见当显示创建它们的选项卡时;就像开发人员工具一样)。
createInterfacePanelIframe()
方法创建一个包含 <iframe>
(或 <browser>
)的界面面板,您可以为其提供 URL 和所有属性。 <iframe>
和 <splitter>
的元素返回给调用者,因此您可以对它们执行其他操作,或者将它们从 Firefox window 的 DOM 中删除到 delete/hide他们。此方法创建 <iframe>
并调用 createInterfacePanel()
将其插入到 Firefox 浏览器中指定的位置。
createInterfacePanel()
方法会将您传递给它的 XUL 元素(使用 Document Fragment 传递多个元素)放入 Firefox 浏览器 window 的 DOM,以及指定位置(左、右、上、下、window 和相对于 window 或选项卡的 <splitter>
。您可以指定您希望界面面板所在的 window and/or 选项卡,或者默认情况下,界面面板插入当前 window/tab。如果您指定要插入的界面面板已经存在,则将在现有面板的旁边创建另一个面板。您可以创建的数量没有固有的限制(例如,如果您愿意,您可以在右侧设置 10 个面板)。
此外,下面是一个演示 Firefox Add-on SDK 扩展,它添加了 6 个不同的按钮。前 5 个按钮创建或销毁界面面板。徽章指示按钮将影响哪个面板(左、右、上、下和 window)。绿色徽章表示将创建一个界面面板。红色徽章表示单击将破坏已经存在的面板。第六个按钮在 Tab 相关界面面板和 Window 相关界面面板之间切换其他按钮。如果您创建所有面板,您将有 8 个面板(4 个用于 Window,4 个用于当前选项卡)以及两个单独的 windows(每个浏览器 Window)。
以下是演示插件的样子。下图中显示的面板较窄 and/or 较短,以允许它们显示在此页面上。下面的代码允许您将面板制作成任何您想要的大小,并且用户可以调整它们的大小。
这是创建侧边栏(界面面板)的代码:
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 浏览器中的结构。
我正在使用 CSS:
在左侧和右侧编程 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@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,这个插件很好地利用了它 - https://addons.mozilla.org/en-US/firefox/addon/whatsapp-web-messenger/
每个chrome window中似乎有一个window.SidebarUI
对象。
您的问题不完全您想要什么。因此,我做了一些假设。
最终,官方不支持一次显示多个 "sidebar" 的方法。默认情况下,Firefox 浏览器 windows 中只存在一个 "sidebar"。虽然不支持为您提供多个侧边栏的 API,但可以通过修改 Firefox 浏览器 windows 的 XUL DOM 创建多个侧边栏(界面面板)。 =26=]
下面的代码创建了位于浏览器顶部、底部、左侧、右侧的用户界面面板(侧栏、顶部栏、底部栏和 windows)window,或在单独的 window 中。可以创建界面面板,使其相对于 window(当您切换选项卡时它们保持可见;就像 bookmarks/history 侧边栏),或者相对于选项卡(它们仅在可见当显示创建它们的选项卡时;就像开发人员工具一样)。
createInterfacePanelIframe()
方法创建一个包含 <iframe>
(或 <browser>
)的界面面板,您可以为其提供 URL 和所有属性。 <iframe>
和 <splitter>
的元素返回给调用者,因此您可以对它们执行其他操作,或者将它们从 Firefox window 的 DOM 中删除到 delete/hide他们。此方法创建 <iframe>
并调用 createInterfacePanel()
将其插入到 Firefox 浏览器中指定的位置。
createInterfacePanel()
方法会将您传递给它的 XUL 元素(使用 Document Fragment 传递多个元素)放入 Firefox 浏览器 window 的 DOM,以及指定位置(左、右、上、下、window 和相对于 window 或选项卡的 <splitter>
。您可以指定您希望界面面板所在的 window and/or 选项卡,或者默认情况下,界面面板插入当前 window/tab。如果您指定要插入的界面面板已经存在,则将在现有面板的旁边创建另一个面板。您可以创建的数量没有固有的限制(例如,如果您愿意,您可以在右侧设置 10 个面板)。
此外,下面是一个演示 Firefox Add-on SDK 扩展,它添加了 6 个不同的按钮。前 5 个按钮创建或销毁界面面板。徽章指示按钮将影响哪个面板(左、右、上、下和 window)。绿色徽章表示将创建一个界面面板。红色徽章表示单击将破坏已经存在的面板。第六个按钮在 Tab 相关界面面板和 Window 相关界面面板之间切换其他按钮。如果您创建所有面板,您将有 8 个面板(4 个用于 Window,4 个用于当前选项卡)以及两个单独的 windows(每个浏览器 Window)。
以下是演示插件的样子。下图中显示的面板较窄 and/or 较短,以允许它们显示在此页面上。下面的代码允许您将面板制作成任何您想要的大小,并且用户可以调整它们的大小。
这是创建侧边栏(界面面板)的代码:
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 浏览器中的结构。