Firefox Extension, Window related sidebar - firefox

I'm writing an extension for Firefox, and I need the UI of this extension to be on a sidebar, I followed some mozilla tutorials, but sidebars are not related to just one window.
I need a sidebar like UI, that will save navigation data from the same window, and need it to be related to just that window, something like firebug.
What I did so far is just creating a menu, and an item, I need that a click on this item will toggle my sidebar.
I took a look at firebug source, I didn't find any overlay of sidebar in its XUL, the scripts are complicated for me, so i didn't know how they can add their UI to the window.
Any ideas, or sources I can read about this ?

When talking about a Sidebar one needs to be careful about terminology. The specific term that Mozilla uses within Firefox for "Sidebar" refers to a content box that is on the side of the UI. The Sidebar, if open, is a constant part of the UI which is shown independent of the tab selected. Only one is provided for which can be chosen to be on the left or the right. It is routinely used for content that does not change from tab to tab (e.g. Bookmarks, or History).
The UI that is used for the Firefox devtools (and placement was adopted for use by FireBug) is a sub-panel within the current tab. It is shown only within the tab in which it was invoked. It is implemented within an <iframe>. It can also be opened as a separate window.
When you have a known working example, one way to figure out how this type of thing is implemented in the DOM (the entire browser window is a DOM) is to install the add-on DOM Inspector and use it to investigate what the contents of the DOM looks like. You probably also want, the Element Inspector add-on which is a very useful addition to the DOM Inspector (shift-right-click opens the DOM Inspector to the element clicked). You might also find Stacked Inspector helpful.
Another way to figure out how it is being done is to look at the source code. For the devtools the interface is actually created in the function SH_create within resource:///modules/devtools/framework/toolbox-hosts.js
When placed at the bottom location, the UI is placed as a child of the <notificationbox> which exists for each tab. You can find the <notificationbox> for a tab by using the gBrowser.getNotificationBox( browserForTab ) method. The elements inserted are a <splitter> and an <iframe>. When placed in the other locations within the browser tab those two elements are inserted at locations in the Browser DOM either as children of the <notificationbox>, or as children of its child <hbox> that has class="browserSidebarContainer".
As an example, the following functions will, depending on the [location] parameter, create a panel on the left, right, top, or bottom of the current tab or as a separate window. The default is that the panel is composed of an <iframe> separated from the browser content by a <splitter>. The createInterfacePanel() function is more generic and will accept any element or DOM object as the second parameter which is inserted into the DOM at the appropriate place based on [location] and separated by a form the content. Such Object is expect to be either a Document Fragment or element.
/**
* Creates an <iframe> based panel within the current tab,
* 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.
* #param location
* Placement of the panel [right|left|top|bottom|window]
* The default location is "right".
* #param 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 400.
* #param id
* The ID to assign to the iframe. Default is
* "makyen-interface-panel"
* The <splitter> will be assigned the
* ID = id + "-splitter"
* #param chromeUrl
* This is the chrome:// URL to use for the contents
* of the iframe or the window.
* the default is:
* "chrome://browser/content/devtools/framework/toolbox.xul"
* #param 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 by Makyen.
* Released under the MPL 2.0. http://mozilla.org/MPL/2.0/.
**/
function createInterfacePanelIframe(location,size,id,chromeUrl,features) {
//defaults
size = ( (typeof size !== "number") || size<1) ? 400 : size;
id = typeof id !== "string" ? "makyen-interface-panel" : id;
chromeUrl = typeof chromeUrl !== "string"
? "chrome://browser/content/devtools/framework/toolbox.xul"
: chromeUrl;
//Create some common variables if they do not exist.
// This should work from any Firefox context.
// Depending on the context in which the function is being run,
// this could be simplified.
if (typeof window === "undefined") {
//If there is no window defined, get the most recent.
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
}
if (typeof document === "undefined") {
//If there is no document defined, get it
var document = window.content.document;
}
if (typeof gBrowser === "undefined") {
//If there is no gBrowser defined, get it
var gBrowser = window.gBrowser;
}
//Get the current tab & notification box (container for tab UI).
let tab = gBrowser.selectedTab;
let browserForTab = gBrowser.getBrowserForTab( tab );
let notificationBox = gBrowser.getNotificationBox( browserForTab );
let ownerDocument = gBrowser.ownerDocument;
//Create the <iframe> use
//ownerDocument for the XUL namespace.
let iframeEl = ownerDocument.createElement("iframe");
iframeEl.id = id;
iframeEl.setAttribute("src",chromeUrl);
iframeEl.setAttribute("height", size.toString());
iframeEl.setAttribute("width", size.toString());
//Call createInterfacePanel, pass the size if it is to be a window.
let splitterEl;
if(location == "window" ) {
splitterEl = createInterfacePanel(location, size, size
,id + "-splitter", chromeUrl, features);
return [splitterEl, null];
} else {
splitterEl = createInterfacePanel(location, iframeEl, iframeEl
,id + "-splitter", chromeUrl, features);
}
return [splitterEl, iframeEl];
}
/**
* 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.
* If features is a string, it is assumed the width is
* set in that, or elsewhere (e.g. in the XUL).
* #param sizeEl
* The element that contains attributes of "width" and
* "height". If location=="left"|"right" then the
* "height" attribute is removed prior to the objectEl
* being inserted into the DOM.
* A spearate reference for the size element in case the
* objectEl is a documentFragment containing multiple elements.
* However, normal usage is for objectEl === sizeEl 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).
* #param id
* The ID to assign to the <splitter>. The default is:
* "makyen-interface-panel-splitter".
* #param chromeUrl
* This is the chrome:// URL to use for the contents
* of the window.
* the default is:
* "chrome://browser/content/devtools/framework/toolbox.xul"
* #param features
* The features string for the window. See:
* https://developer.mozilla.org/en-US/docs/Web/API/Window.open
* returns
* if location != "window":
* splitterEl, The element for the <splitter>.
* if location == "window":
* The windowObjectReference returned by window.open().
*
* Copyright 2014 by Makyen.
* Released under the MPL 2.0. http://mozilla.org/MPL/2.0/.
**/
function createInterfacePanel(location,objectEl,sizeEl,id,chromeUrl,features) {
//Set location default:
location = typeof location !== "string" ? "right" : location;
if(location == "window") {
if(typeof features !== "string") {
let width = "";
let height = "";
if(typeof objectEl == "number") {
width = "width=" + objectEl.toString() + ",";
}
if(typeof sizeEl == "number") {
height = "height=" + sizeEl.toString() + ",";
}
features = width + height
+ "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://browser/content/devtools/framework/toolbox.xul"
: chromeUrl;
//Create some common variables if they do not exist.
// This should work from any Firefox context.
// Depending on the context in which the function is being run,
// this could be simplified.
if (typeof window === "undefined") {
//If there is no window defined, get the most recent.
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
}
if (typeof document === "undefined") {
//If there is no document defined, get it
var document = window.content.document;
}
if (typeof gBrowser === "undefined") {
//If there is no gBrowser defined, get it
var gBrowser = window.gBrowser;
}
//Get the current tab & notification box (container for tab UI).
let 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").contains("browserSidebarContainer")
) {
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 toReturn = null;
switch(location) {
case "window" :
return window.open(chromeUrl,"_blank",features);
break;
case "top" :
if(sizeEl) {
sizeEl.removeAttribute("width");
}
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.
toReturn = notificationBox.insertBefore(docFrag,theChild);
break;
case "bottom" :
if(sizeEl) {
sizeEl.removeAttribute("width");
}
docFrag.appendChild(splitterEl);
docFrag.appendChild(objectEl);
//Insert the interface just after theChild.
toReturn = notificationBox.insertBefore(docFrag,theChild.nextSibling);
break;
case "left" :
if(sizeEl) {
sizeEl.removeAttribute("height");
}
docFrag.appendChild(objectEl);
//Splitter is second in this orientaiton.
docFrag.appendChild(splitterEl);
//Insert the interface as the first child of theChild.
toReturn = theChild.insertBefore(docFrag,theChild.firstChild);
break;
case "right" :
default :
//Right orientaiton, the default.
if(sizeEl) {
sizeEl.removeAttribute("height");
}
docFrag.appendChild(splitterEl);
docFrag.appendChild(objectEl);
//Insert the interface as the last child of theChild.
toReturn = theChild.appendChild(docFrag);
break;
}
return splitterEl;
}
Update:
The code in this answer was significantly enhanced for my answer to "Firefox SDK Add-on with a sidebar on both the right and left at the same time". You are probably much better off using the code contained in that answer rather than the code found here.

Related

React onKeyDown/onKeyUp events on non-input elements

I need to capture cmd button up and down events, in order to choose, whether to use concatenation or not in setState. How do i get these events, for example, on table element?
You have to capture the keypress then in body/window level. Table element doesn't have input focus so you can't capture the keys from table (without input element).
var cmdDown = false;
document.body.addEventListener('keydown', function(event) {
var key = event.keyCode || event.charCode || 0;
if ([91,93,224,17].indexOf(key) !== -1) {
cmdDown = true;
}
console.log('CMD DOWN: ' + cmdDown.toString());
});
document.body.addEventListener('keyup', function(event) {
var key = event.keyCode || event.charCode || 0;
if ([91,93,224,17].indexOf(key) !== -1) {
cmdDown = false;
}
console.log('CMD DOWN: ' + cmdDown.toString());
});
Simple Example
I believe best practice here is to add an event listener to document and modify your element (e.x. table) accordingly. Extending u/Hardy's answer with a full React component:
class MyComponent extends React.Component {
// Using an arrow function. Alternatively, could bind() this
// function in the component's constructor
keydownHandler = (event) => {
// Add logic, e.x. check event.keyCode to see
// if CMD is pressed and then update state
}
componentDidMount() {
document.addEventListener("keydown", this.keydownHandler);
}
componentWillUnmount() {
this.removeEventListener("keydown", this.keydownHandler);
}
render() {
<div>Hello World</div>
}
}
Alternative
As others have noted, there are challenges in using keyboard events that are bound to non-input elements, as browsers require those elements to be in focus in order to invoke the bound callbacks. Another approach that seems to work is to set the tabindex property of the component to bring the element in focus. From u/burak's answer:
render() {
<div onKeyDown={this.keydownHandler} tabindex={-1}>Example</div>
}
This has the benefit of connecting into React's Synthetic Events, that is "a cross-browser wrapper around the browser’s native event." However, when modifying the tabindex property it's important to be mindful that this could change the order of focusable elements, and affect a user's ability to navigate your site with their keyboard or accessibility software. Some users report that setting tabindex to -1 resolves this issue (see comments in previous link).

Is it possible to get the window object from the event in handleEvent?

As in the question... how to get the window object from an event fired in the window scope for example:
handleEvent: function(event) {
// is window object available here and can we get it from event
}
I can get the window object from other APIs. I was wondering if it was possible to get it from the fired event.
Reference:
handleEvent
Code Snippet using handleEvent
I found out the answer ... any of these will get the window object from the event
event.view event.view
event.target.ownerDocument.defaultView event.target
event.originalTarget.ownerGlobal event.originalTarget (Non-standard)
It depends on the event. But most usually yes you can. Do a console.log on the event then you might something like targetChromeWindow or something, this one I can't remember i came across it once though while doing something.
Most usually though, get event.target or relatedTarget or originalTarget (theres one more target i forgot what it is) and do ownerDocument.defaultView
If you want the chrome window from that you can get that by doing this:
var DOMWin = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
The following will populate the window and document variables if they do not already exist. It should work from any scope/context:
if (typeof window === "undefined") {
//If there is no window defined, get the most recent.
var window = Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
}
if (typeof document === "undefined") {
//If there is no document defined, get it
var document = window.content.document;
}
Here are some additional variables which might be useful to have available, depending on what you are doing:
if (typeof gBrowser === "undefined") {
//If there is no gBrowser defined, get it
var gBrowser = window.gBrowser;
}
var tab = gBrowser.selectedTab;
var browserForTab = gBrowser.getBrowserForTab( tab );
var notificationBox = gBrowser.getNotificationBox( browserForTab );
var ownerDocument = gBrowser.ownerDocument;

Tablesorter Filter with Quicksearch Plugin Issue for Showing Corresponding Contents

I am trying to make the table shows the corresponding contents when I pick a category. I have that successfully implemented. However, if I search in the search box (using the quicksearch plugin) after I pick a category, it will search all the rows in the table rather than only searching for corresponding contents. How do I make it so, it will only search the corresponding items?
Here is the demo
http://jsfiddle.net/azsuA/
UPDATED Question
Now I have one child row under Coke in the table. I am wondering why is it being counted as "filteredRows"? How do I make "filteredRows" not including the child rows in the table? And for some reason, if I pick "Uncategorized", it'll say "1 - 1/ 1 (1) / 14" where it should be "- / (14)"
Another demo
http://jsfiddle.net/azsuA/4/
You'll need to disable, then re-enable quicksearch on the new categories (updated demo). First set up quicksearch like this:
/**
* Filter the table.
* Resource: https://github.com/riklomas/quicksearch
*/
var quickSearchOptions = {
show: function () {
$(this).show().removeClass('filtered');
$('table').trigger('pageSet'); // reset to page 1 & update display
},
hide: function () {
$(this).hide().addClass('filtered');
$('table').trigger('pageSet'); // reset to page 1 & update display
},
onAfter: function () {
$('table').trigger('update.pager');
}
};
$('.search').quicksearch('table tbody tr', quickSearchOptions);
Then, when you select a cateogy, disable, then re-enable quicksearch:
/**
* Show the corresponding contents when a category is clicked.
* Resource: http://tinyurl.com/jvneeey
*/
$('ul').on('click', 'li', function () {
var filters = [],
$t = $(this),
col = $t.data('filter-column'), // zero-based index
txt = $t.data('filter-text') || $t.text(); // text to add to filter
filters[col] = txt;
// using "table.hasFilters" here to make sure we aren't targetting a sticky header
$.tablesorter.setFilters($('table.hasFilters'), filters, true); // new v2.9
// disable previous quicksearch
$('.search')
.off('keyup')
.quicksearch('table tbody tr:not(.filtered)', quickSearchOptions);
});

why an addon for firefox is only avaliable though the view menu toolbar?

I've developed an add-on for Firefox and is installs ok in windows Firefox but in linux mint i must go to the View menu then toolbars and select personalize to put the created add-on button to the toolbar near my firebug (i mean where other add-ons coexist)
To put a toolbarbutton on the nav-bar automatically, it isn't enough to create the button. You have to add it to the "current set" of icons in the toolbar. If you don't do this, it will only be added to the
My guess is your code is not working on windows either. You may have added it to the toolbar manually some time ago and it is there since.(Try installing your addon in a blank profile).
To make it "persistent" automatically you can add it to the current set on the first time you run your addon, with the following:
/**
* Installs the toolbar button with the given ID into the given
* toolbar, if it is not already present in the document.
*
* #param {string} toolbarId The ID of the toolbar to install to.
* #param {string} id The ID of the button to install.
* #param {string} afterId The ID of the element to insert after. #optional
*/
function installButton(toolbarId, id, afterId) {
if (!document.getElementById(id)) {
var toolbar = document.getElementById(toolbarId);
// If no afterId is given, then append the item to the toolbar
var before = null;
if (afterId) {
let elem = document.getElementById(afterId);
if (elem && elem.parentNode == toolbar)
before = elem.nextElementSibling;
}
toolbar.insertItem(id, before);
toolbar.setAttribute("currentset", toolbar.currentSet);
document.persist(toolbar.id, "currentset");
if (toolbarId == "addon-bar")
toolbar.collapsed = false;
}
}
if (firstRun) {
installButton("nav-bar", "my-extension-navbar-button");
// The "addon-bar" is available since Firefox 4
installButton("addon-bar", "my-extension-addon-bar-button");
}
Reference: Toolbar - Code snippets

Reusing a tab in Firefox, TabOpen event, and beyond

I followed a tutorial on reusing tabs inside Firefox, and right now my extension does it well. However, I also need to reuse tabs that are opened from outside (from some application, start menu etc.). How do I do this?
I tried adding an event listener for TabOpen event, but when I log the
event.target.linkedBrowser.currentURI.spec
it's value is "about:blank". I expected the actual address that I typed into the address bar (automatically opens in a new tab), or the address that I open from some other application, so I can close that tab immediately, and the focus the right one. What am I doing wrong?
Thanks in advance.
Just in case, here's the code that reuses the tab when a new tab is requested from an extension
function openAndReuseOneTabPerURL(url) {
var wm = Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var browserEnumerator = wm.getEnumerator("navigator:browser");
// Check each browser instance for our URL
var found = false;
while (!found && browserEnumerator.hasMoreElements()) {
var browserWin = browserEnumerator.getNext();
var tabbrowser = browserWin.getBrowser();
// Check each tab of this browser instance
var numTabs = tabbrowser.browsers.length;
for(var index=0; index<numTabs; index++) {
var currentBrowser = tabbrowser.getBrowserAtIndex(index);
if (url == currentBrowser.currentURI.spec) {
// The URL is already opened. Select this tab.
tabbrowser.selectedTab = tabbrowser.mTabs[index];
// Focus *this* browser-window
browserWin.focus();
found = true;
break;
}
}
}
// Our URL isn't open. Open it now.
if (!found) {
var recentWindow = wm.getMostRecentWindow("navigator:browser");
if (recentWindow) {
// Use an existing browser window
recentWindow.delayedOpenTab(url, null, null, null, null);
}
else {
// No browser windows are open, so open a new one.
window.open(url);
}
}
}
When you receive the TabOpen event, it's too early for the page content to be loaded still. When you receive the TabOpen event, however, you should register for load or DOMContentLoaded. When you receive that event, you should be able to access the URI.
I guess you could extract the wanted behaviour from the implementation in the Tab Mix Plus extension

Resources