
I’ll talk about my expansion of
Private Tab for Firefox and SeaMonkey: some implementation details - and specifically this, and extensions that do not require restarting (aka
restartless ), in general.
The extension adds private tabs for which history will not be saved and a separate set of cookies will be used.
Unfortunately, attempts to write in a simple way lead to numerous distractions from the main topic, so that the output was a kind of hodgepodge on the subject of writing extensions. On the other hand, completely without distractions it will be clear only to those for whom all this is no longer necessary.
Somewhat messy, but in a logical order. I hope so clearer.
')
Perhaps, we should start with the fact that the extension is just an add-on above the windowed window added to Gecko 20 (or how can we translate per-window?) Private mode.
Without these internal changes, nothing would have happened.
And there were a lot of changes: “
Bug 463027 depends on 1646 bugs ”.
Fortunately, although we received only private windows, a lot of things were done inside based on the possibility of changing not only the entire window’s privacy, but also individual tabs (well, the contents of the individual tabs).
This is if you understand the person. In general, if you can not describe something in your own words, you should question the understanding. So, although the entrails were promised (blood, guts, and so on), but I will start with something simple and (hopefully) accessible.
A simple and understandable thing is that the
nsILoadContext interface (undocumented, hehe - in the sense that there is only source code with comments) is associated with each window *, which now has the property usePrivateBrowsing. And, most importantly, all other code takes into account the value of this property. So all the dirty work was done for us (honestly, unfortunately, not all).
* Windows are actually more than it seems: in this case, the
window object is in mind, well known to all JavaScript programmers. Each browser window has such an object, inside of which there is at least an equally well-known
document with a real DOM tree. And then with each tab is associated
XUL browser , very similar to HTML iframe with its own window, document and DOM-tree.
In general, it’s better to see it once: you can look at this whole structure live using the
DOM Inspector 'a (here
is some instruction in the pictures, or you can put
Custom Buttons and my
Attributes Inspector button, only for Gecko 20 you need an
experimental version of Custom Buttons ).
For example, the XUL browser:

That is, our task is to solicit the nsILoadContext interface from something related to <browser> 'in the tab and switch usePrivateBrowsing to false.
Two other interfaces are responsible for begging:
nsISupports.QueryInterface () and
nsIInterfaceRequestor.getInterface () .
But in this case, we have already been taken care of, and there is a helpful article
Supporting per-window private browsing , from which you can learn about the ready-made
resource module : //gre/modules/PrivateBrowsingUtils.jsm , in which the method we need is already implemented:
privacyContextFromWindow: function pbu_privacyContextFromWindow(aWindow) { return aWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsILoadContext); },
There is, however, another subtlety: when the value of the usePrivateBrowsing property changes, it writes to the error console
Warning: Only use the passwordPrivateBrowsing attribute
This is
Bug 800193 - Make the nsILoadContext.usePrivateBrowsing property read-only . Fortunately, as a result, so far the matter is limited to this warning.
So now we need to get the window object bound to the tab.
Generally, there is
gBrowser.getBrowserForTab (tab) , but we
not looking for easy ways we want to make a full-fledged restartless extension (there is still a simplified version: connecting a script to each window that will be executed for each window again, it’s easier as a first approximation to port “traditional” extensions, but it will require more memory), so tab.ownerDocument .defaultView.gBrowser.getBrowserForTab (tab) looks somehow scary. Moreover, in the view-source: chrome: //browser/content/tabbrowser.xml (this link is such, it opens, of course, only in Firefox) there is only
<method name="getBrowserForTab"> <parameter name="aTab"/> <body> <![CDATA[ return aTab.linkedBrowser; ]]> </body> </method>
Moreover, this approach is used a lot where, although it seems to be not documented.
So now we have (honestly, we don’t have a tab yet, but more on that later) a link to the
XUL browser tab, which, in turn, has a
contentWindow property that refers to the DOM window we need in the browser .
So it turns out something like this:
var tab = ...
Now, when we can find out if a private tab or not and switch the privacy mode for it, let's look at the tabs.
To begin, open the private tab.
To open tabs, there is
gBrowser.addTab () , but there is a subtlety: if you open a tab first and then enable private mode for it, the browser in the tab will have time to be initialized, and although the link will not be saved in your browsing history, cookies will not be used privately.
To understand why this happens, you need to look at the source code:
<method name="addTab"> <parameter name="aURI"/> <parameter name="aReferrerURI"/> <parameter name="aCharset"/> <parameter name="aPostData"/> <parameter name="aOwner"/> <parameter name="aAllowThirdPartyFixup"/> <body> <![CDATA[ ... // Dispatch a new tab notification. We do this once we're // entirely done, so that things are in a consistent state // even if the event listener opens or closes tabs. var evt = document.createEvent("Events"); evt.initEvent("TabOpen", true, false); t.dispatchEvent(evt); if (uriIsNotAboutBlank) { ... try { b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData); } catch (ex) { Cu.reportError(ex); } }
Fortunately, not only the reason is visible (start of loading by b.loadURIWithFlags ()), but also the solution: you can use the generated
TabOpen event - the notification comes just before the start of the download.
And here the code is not as transparent as we would like. The basic idea is:
window.addEventListener("TabOpen", function waitForTab(e) { window.removeEventListener(e.type, waitForTab, false); var tab = e.originalTarget;
At the same time,
event.originalTarget differs from
event.target only in that, unlike the latter, it can refer to anonymous nodes created using
XBL (and in SeaMonkey it’s just its own
<tabbrowser> implementation).
Well, now we have a lonely private tab.
It needs to be visually separated from others. To do this, I add the tab attribute privateTab-isPrivate = "true" and change its appearance styles.
It is important to note here that potential collisions with other extensions should be avoided - all you need to do is to add to all classes, attributes and other entities a distinctive prefix, in this case “privateTab-”.
In order not to be distracted, briefly about styles: use
nsIStyleSheetService.loadAndRegisterSheet () . At the same time, styles are applied to all windows in general, so it is better to limit them using
@ -moz-document .
But from the private tab, you can open a new tab or even a window. These new tabs and windows should remain private!
There are two ways.
The first is to redefine all or some of the functions that open tabs in one way or another. You can make a wrapper (a good way), and you can apply an eval-patch (there are much more possibilities, but it is more difficult to roll it back later, and this method is not recommended).
In general, with the abolition of patches and wrappers, everything is not so simple: another extension may impose a patch between our switching on and off (we are restartless!). And if this extension makes an eval-patch, then our wrapper will break it - the code has changed.
Here you can go to the trick: override the toString () and toSource () methods of the wrapper function (that is, to make the wrapper look like a modified original function), then the third-party patch will most likely work fine. But now we are disconnected and we must return everything as it was. And it was without a patch! And, rolling back on the memorized value of the function, we will break all the extensions that have applied their patches after us.
But even here you can cheat: when modifying a function, add to our code a call to our global (relative to the window that owns the function requiring modification) function, then when detecting third-party edits, you can arrange
memory leak change our global function, which was engaged in modifications, to do nothing. This is better than breaking the unsuspecting user’s extensions.
Here is an intricate shamanism with patches. It sounds incomprehensible, and the
code is no better.
In the simplest case, it turns out this:
The main code (remembering the original and overriding toString () / toSource () is omitted to demonstrate the essence):
var orig = someObject.someMethod; var patch = window["privateTabMod::someObject.someMethod"] = function() {
And yes, it is also important to create global properties in the window with names that can be used elsewhere.
At the same time, toString () / toSource () are redefined and return this:
function wrapped() { if(window["privateTabMod::someObject.someMethod"].apply(this, arguments)) return;
Then, when we need to roll back our patch, we compare the stored modified function with the current value of someObject.someMethod. If they match, we simply change someObject.someMethod to the memorized original function, and if they differ, we change the window ["privateTabMod :: someObject.someMethod"] to an empty function.
Such a long retreat.
And yes, there is nothing fundamentally bad in eval-patches, sometimes there is simply no other way.
But we must understand that when you can do with a wrapper, you need to do with a wrapper. This is also better optimized by the interpreter, and only third-party code making eval-patches can break it.
Here, of course, it's time to grab your head and run away, waving your arms, but it is better to wait and read this first:
Wladimir Palant
Some reasons why I ’m not really dangerous than other hacks and
why I ’m using eval () instead of others? by
YUKI "Piro" HiroshiIn short, wrappers break eval patches (which sometimes can’t be done without), and neat eval patches do not break either other patches or wrappers. Yes, it is potentially much more dangerous, but dangerous is not unacceptable at all.
And when you can do without patches in general, you must choose this path - it is the least conflicting.
If, of course, there is no desire to then deal with compatibility with a bunch of different extensions. And after all, not all extensions are equally "straight."
In this case, there is a very simple way, albeit with some not quite logical effects: earlier we already used the listener of the “TabOpen” event to get the not-yet-initialized tab. Here you can do the same: when opening a tab, check the privacy of the
current tab, and if it is private, then make a new tab also private.
With windows, in general, it is similar (only you need to manually get a replacement for window.opener, because after opening a link in a new window, the opener property is not set) and check the privacy of the active tab of the parent window.
But we still have an extension about tabs, so we can do without details, especially since a couple of words will be said about tracking opening windows in restartless extensions.
So, we can already open a private tab and everything that opens from it is also private.
The main work is over, and the pleasant little things begin, from which usability is built. And sometimes these little things are much more than the main code.
Firstly, there is saving sessions and other restarts. Tabs should remain private.
For this, there is an
SSTabRestoring event to track the moment of recovery and the
nsISStationStore interface, for which we will need the
persistTabAttribute () method.
And then everything is simple: when you restore the tab, you need to check for the presence of the previously installed privateTab-isPrivate attribute. If it is, make the tab private.
This, of course, is far from everything - “everything” has already stretched over 2100 lines, and there are more trifles than it seems - both changing the title of the window and changing the application button (which is red, App button) if the menu is hidden (the button, however , it changes Firefox itself for the presence of the privatebrowsingmode attribute at the root node of the DOM tree of the main window), and adding items to different menus, handling shortcuts (and the built-in
XUL key is never restartless), and dragging (you can also pull the link from private tab!), and the impossibility of dragging tabs between the private and ordinary windows (and here are the patches!), and ... just do not remember.
But we value our time, the time of our readers.
and healthy sleep . :) No, we also appreciate the dream, but it does not always remain what to appreciate.
Now that we’ve dealt with the tabs (and this can be done without creating an extension using the Custom Buttons extension already mentioned, or using built-in tools, setting
devtools.chrome.enabled = true to about: config and launching Web Development — Simple JavaScript Editor aka Scratchpad, Shift + F4 - Surroundings - Browser), it's time to thrust the developments into the extension.
But 2100 lines is a bad example. And because it could be cut into modules (maybe cut, but for now it turns out that cutting leads to a bunch of additional code), and because the minimum of lines needed to just run something simple is lost.
So let's take a simpler example:
Tree Style Tab Tweaker (just in case the link leads to a specific version, otherwise it will also swell over time).
In general, “Tweaker” is too loud. This is a
Tree Style Tab thing that implements
this “fix” (slightly improved) for
issues # 384 . The bottom line is that when you close parental tabs, the closed tab will be replaced with a dummy tab to save the tab hierarchy.
Anyway, it's easier to keep track of the main points.
First, you need the classic
install.rdf , without which nothing will work out, but with the
flag "
<em: bootstrap> true </ em: bootstrap> ".
And the
bootstrap.js file next to install.rdf.
Further, all this is packaged in a regular ZIP-archive, but with the extension xpi. And that's it! Well, all is when the necessary code is already written.
The bootstrap.js file must necessarily define
special functions that the add-ons manager will invoke when it is turned on, turned off, started up, and so on.
And then the expansion should do everything itself: it should catch windows, and do something with them, and clean up after itself, too.
This, by the way, is very sad, because there is no API for simplifying these manipulations (not yet, I hope).
Here, however, a reservation is needed: there is still
Jetpack aka Add-on SDK . Why not him? Personally, it's easier for me. It’s much clearer to mess around with more or less familiar interfaces and other APIs than to learn new ones (and, obviously, more limited ones). There is also a lot of abstractions in the source code, so that you can work normally only through documentation (and sometimes it is late, it's open source). In addition, something not to see complex extensions on the Jetpack, which also suggests. But if there is no experience with writing extensions, it’s probably easier to start with the Add-on SDK.
But back to the manipulation.
We will only use the
startup () and
shutdown () functions.
As you might guess from the name, startup () is called when the extension is launched — whether it is launching the browser or setting / enabling, the reason is passed by the second argument.
When enabled, there may be already open windows that should be processed. For this there is
nsIWindowMediator.getEnumerator () , there is also an
example of use .
The result is simple:
The “navigator: browser” is the value of the
windowtype attribute of the root node (
XUL window ) of the main browser window. You can see it with the help of the same DOM Inspector.
Accordingly, initWindow () receives a link to the window and the reason for the inclusion.
But these are only already open windows - you also need to keep track of the creation of new windows and the closure of existing ones (in order to clean up after themselves and not arrange a flood).
To do this, we use the
nsIWindowWatcher.registerNotification () method.
In this case, both the function and the object implementing the
nsIObserver interface can
receive alerts . The latter is more convenient: we get the correct this, referring to our object-namespace.
Again, use the Services.jsm module:
Services.ww.registerNotification(this);
Now the
observe (subject, topic, data) method of our object will receive notifications about opening (“domwindowopened” in the topic) and closing (“domwindowclosed”) windows.
However, at the time of receiving the “domwindowopened” alert, it is impossible to know what kind of window it is - in window.location.href will be about: blank, so you need to wait for the window to load:
observe: function(subject, topic, data) { if(topic == "domwindowopened")
Here we will also use the trick to pass the correct this: event listener can be an object that implements the
EventListener interface, so that when an event occurs, the
handleEvent (event) method of the transferred object will be called.
And you can already find out from the booted window both the location and the same windowtype that we used to go through only the necessary windows.
Thus, we handle the
opening and
closing of windows with the required windowtype.
And then you just need to make the inclusion when opening and cleaning when closing.
In this case, we only need to
add a listener to the
TabClose event:
window.addEventListener("TabClose", this, false);
When an event occurs, handleEvent () ->
tabCloseHandler () will work , but with the tabs we have already worked.
Further, when shutting down, deleting, updating or closing the browser, the shutdown () function will be called, and
windowsObserver.destroy () will work, with the already familiar to us enumeration of all open windows and disabling alerts.
So it's pretty simple. Difficulties begin when a strange desire arises. For example, there is also no API for adding custom buttons, so regular
cycling in each restartless extension is obtained.
The main, perhaps, complexity is that a global object is not a window at all, so either we need to create a window handler (everything is not as bad as it seems at first glance: there are prototypes), or, as done in Private Tab , get the window from the object of the event being processed.
Here, perhaps, that's all. Hopefully this thread without headers can be read. But the headlines, alas, did not want to be placed in any way, I had to provide the text to myself.