I have a FF extension that I want notified when the lightweight theme list is changed. Here's the code:
var PesonaSwitcherObserver = {
register: function() {
PersonaSwitcher.log ("in register");
var observerService =
Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(PesonaSwitcherObserver,
"lightweight-theme-list-change", false);
},
observe: function (subject, topic, data) {
PersonaSwitcher.log ("in observe");
switch (topic) {
case 'lightweight-theme-list-change':
PersonaSwitcher.subMenu();
break;
}
},
unregister: function() {
PersonaSwitcher.log ("in unregister");
var observerService =
Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(PesonaSwitcherObserver,
"lightweight-theme-list-change");
}
}
window.addEventListener("load", PesonaSwitcherObserver.register, false);
window.addEventListener("unload", PesonaSwitcherObserver.unregister, false);
The log receives the "in register", but no "in observes" when I add or remove personas. I've even looked at LightweightThemeManager.jsm and
function _updateUsedThemes(aList) {
calls
Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null);
Anyone know why or have a hint?
I guess that the call to observerService.addObserver() fails - please check the Error Console (press Ctrl-Shift-J to open it). Your observer doesn't implement QueryInterface function and the observer service will explicitly check whether nsIObserver is implemented. This function is easiest to implement using XPCOMUtils.jsm. If you don't want to import it into the global scope (you probably don't since your code seems to run from an overlay) you can do it like this:
var PesonaSwitcherObserver = {
QueryInterface: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", null)
.XPCOMUtils
.generateQI([Components.interfaces.nsIObserver]),
...
Related
I was re-reading this post here: https://stackoverflow.com/a/24473888/1828637
And got concerned about if I did things correctly. This is how I do unloading:
So I set up some stuff per window. And unload them on shutdown. (i dont unload on window close, i havent found a need to yet, as when it closes, everything i added to it goes with with the close [such as my mutation observers]).
All code below is theoretical, the mutation stuff is example, so there might be typos or bugs in it. I was wondering if the idea behind it is appropriate:
var unloadersOnShutdown = [];
var unloadersOnClose = [];
function startup() {
let DOMWindows = Services.wm.getEnumerator(null);
while (DOMWindows.hasMoreElements()) {
let aDOMWindow = DOMWindows.getNext();
var worker = new winWorker(aDOMWindow);
unloadersOnShutdown.push({DOMWindow: aDOMWindow, fn: worker.destroy});
}
}
function shutdown() {
Array.forEach.call(unloadersOnShutdown, function(obj) {
//should probably test if obj.DOMWindow exists/is open, but just put it in try-ctach
try {
obj.fn();
} catch(ex) {
//window was probably closed
console.warn('on shutdown unlaoder:', ex);
}
});
}
function winWorker(aDOMWindow) {
this.DOMWindow = aDOMWindow;
this.init();
}
winWorker.prototype = {
init: function() {
this.gMutationObserver = new this.DOMWindow.MutationObserver(gMutationFunc.bind(this));
this.myElement = this.DOMWindow.querySelector('#myXulEl');
this.gMutationObserver.observe(this.myElement, gMutationConfig);
if (this.DOMWindow.gBrowser && this.DOMWindow.gBrowser.tabContainer) {
this.onTabSelectBinded = this.onTabSelect.bind(this);
this.gBrowser.tabContainer.addEventListener('TabSelect', this.onTabSelectBinded, false);
}
},
destroy: function() {
this.gMutationObserver.disconnect();
if (this.onTabSelectBinded) {
this.gBrowser.tabContainer.removeEventListener('TabSelect', this.onTabSelectBinded, false);
}
},
onTabSelect: function() {
console.log('tab selected = ', thisDOMWindow.gBrowser.selectedTab);
}
};
var windowListener = {
onOpenWindow: function (aXULWindow) {},
onCloseWindow: function (aXULWindow) {
var DOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
for (var i=0; i<unloadersOnClose.length; i++) {
if (unloadersOnClose.DOMWindow == DOMWindow) {
try {
unloadersOnClose.fn();
} catch(ex) {
console.warn('on close unloader:', ex);
}
unloadersOnClose.splice(i, 1);
i--;
}
}
},
onWindowTitleChange: function (aXULWindow, aNewTitle) {},
}
I think one problem is me not using weak references with DOMWindows but I'm not sure.
The idea around unloaders in general seems to be OK, but very limited (to windows only).
The implementation is lacking. E.g. there is a big, fat bug:
unloadersOnShutdown.push({DOMWindow: aDOMWindow, fn: worker.destroy});
// and
obj.fn();
// or
unloadersOnClose.fn();
This will call winWorker.prototype.destroy with the wrong this.
The i++/i-- loop also looks, um... "interesting"?!
Also, keep in mind that there can be subtle leaks, so you should mind and test for Zombie compartments.
Not only can a window leak parts of your add-on (e.g. bootstrap.js) but it is also possible to leak closed windows by keeping references in your add-on. And of course, it's not just windows you need to care about, but also e.g. observers, other types of (XPCOM) listeners etc.
Are there any events fired by an element to check whether a css3 transition has started or end?
W3C CSS Transitions Draft
The completion of a CSS Transition generates a corresponding DOM Event. An event is fired for each property that undergoes a transition. This allows a content developer to perform actions that synchronize with the completion of a transition.
Webkit
To determine when a transition completes, set a JavaScript event listener function for the DOM event that is sent at the end of a transition. The event is an instance of WebKitTransitionEvent, and its type is webkitTransitionEnd.
box.addEventListener( 'webkitTransitionEnd',
function( event ) { alert( "Finished transition!" ); }, false );
Mozilla
There is a single event that is fired when transitions complete. In Firefox, the event is transitionend, in Opera, oTransitionEnd, and in WebKit it is webkitTransitionEnd.
Opera
There is one type of transition event
available. The oTransitionEnd event
occurs at the completion of the
transition.
Internet Explorer
The transitionend event occurs at the completion of the transition. If the transition is removed before completion, the event will not fire.
Stack Overflow: How do I normalize CSS3 Transition functions across browsers?
Update
All modern browsers now support the unprefixed event:
element.addEventListener('transitionend', callback, false);
https://caniuse.com/#feat=css-transitions
I was using the approach given by Pete, however I have now started using the following
$(".myClass").one('transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd',
function() {
//do something
});
Alternatively if you use bootstrap then you can simply do
$(".myClass").one($.support.transition.end,
function() {
//do something
});
This is becuase they include the following in bootstrap.js
+function ($) {
'use strict';
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap')
var transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd otransitionend',
'transition' : 'transitionend'
}
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] }
}
}
return false // explicit for ie8 ( ._.)
}
$(function () {
$.support.transition = transitionEnd()
})
}(jQuery);
Note they also include an emulateTransitionEnd function which may be needed to ensure a callback always occurs.
// http://blog.alexmaccaw.com/css-transitions
$.fn.emulateTransitionEnd = function (duration) {
var called = false, $el = this
$(this).one($.support.transition.end, function () { called = true })
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
setTimeout(callback, duration)
return this
}
Be aware that sometimes this event doesn’t fire, usually in the case
when properties don’t change or a paint isn’t triggered. To ensure we
always get a callback, let’s set a timeout that’ll trigger the event
manually.
http://blog.alexmaccaw.com/css-transitions
All modern browsers now support the unprefixed event:
element.addEventListener('transitionend', callback, false);
Works in the latest versions of Chrome, Firefox and Safari. Even IE10+.
In Opera 12 when you bind using the plain JavaScript, 'oTransitionEnd' will work:
document.addEventListener("oTransitionEnd", function(){
alert("Transition Ended");
});
however if you bind through jQuery, you need to use 'otransitionend'
$(document).bind("otransitionend", function(){
alert("Transition Ended");
});
In case you are using Modernizr or bootstrap-transition.js you can simply do a change:
var transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd otransitionend',
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
},
transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];
You can find some info here as well http://www.ianlunn.co.uk/blog/articles/opera-12-otransitionend-bugs-and-workarounds/
Just for fun, don't do this!
$.fn.transitiondone = function () {
return this.each(function () {
var $this = $(this);
setTimeout(function () {
$this.trigger('transitiondone');
}, (parseFloat($this.css('transitionDelay')) + parseFloat($this.css('transitionDuration'))) * 1000);
});
};
$('div').on('mousedown', function (e) {
$(this).addClass('bounce').transitiondone();
});
$('div').on('transitiondone', function () {
$(this).removeClass('bounce');
});
If you simply want to detect only a single transition end, without using any JS framework here's a little convenient utility function:
function once = function(object,event,callback){
var handle={};
var eventNames=event.split(" ");
var cbWrapper=function(){
eventNames.forEach(function(e){
object.removeEventListener(e,cbWrapper, false );
});
callback.apply(this,arguments);
};
eventNames.forEach(function(e){
object.addEventListener(e,cbWrapper,false);
});
handle.cancel=function(){
eventNames.forEach(function(e){
object.removeEventListener(e,cbWrapper, false );
});
};
return handle;
};
Usage:
var handler = once(document.querySelector('#myElement'), 'transitionend', function(){
//do something
});
then if you wish to cancel at some point you can still do it with
handler.cancel();
It's good for other event usages as well :)
I am building a firefox addon that loads javascript at every page load. I'm using progress listener function I found on this page: https://developer.mozilla.org/en/Code_snippets/Progress_Listeners
My problem is that the code seems to execute to early before the page is fully loaded which causes my script to not run. Here is my code.
var PageLoad = {
browser: null,
domain: null,
oldURL: null,
init: function() {
gBrowser.addProgressListener(urlBarListener,Components.interfaces.nsIWebProgress.NOTIFY_LOCATION);
},
uninit: function() {
gBrowser.removeProgressListener(urlBarListener);
},
processNewURL: function(aURI) {
//if (aURI.spec == this.oldURL)
//return;
MyObject.function();
this.oldURL = aURI.spec;
}
};
var urlBarListener = {
locChange: false,
QueryInterface: function(aIID) {
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
},
onLocationChange: function(aProgress, aRequest, aURI) {
PageLoad.processNewURL(aURI);
},
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus) {},
onProgressChange: function(a, b, c, d, e, f) {},
onStatusChange: function(a, b, c, d) {},
onSecurityChange: function(a, b, c) {}
};
window.addEventListener("load",
function() {
PageLoad.init()
}, false);
var MyObject = {
function : function() {
var script = PageLoad.browser.createElement('script');
script.src = 'url_to_script.js';
PageLoad.browser.getElementsByTagName('head')[0].appendChild(script);
}
};
With this code I get this error message on the console:
PageLoad.browser.getElementByTagName("head")[0] is undefined
If I add a timeout then the script does work intermittently but if the page loads slow I get the same error, here's what works sometimes setTimeout(MyObject.function, 1000);
I need a more reliable way of making sure it's executing the script after the page is loaded.
Unrelated, and it doesn't seem to cause any problems but I also see this error message:
gBrowser.addProgressListener was called with a second argument, which is not supported. See bug 608628.
If you want to load javascript at every page load - the best way is subscribing to DOMContentLoaded event:
var MyObject = {
processDOMContentLoaded: function(doc) {
var script = doc.createElement('script');
script.src = 'url_to_script.js';
script.type = 'text/javascript';
doc.getElementsByTagName('head')[0].appendChild(script);
}
};
window.addEventListener('load', function() {
var appcontent = document.getElementById('appcontent');
if(appcontent != null) {
appcontent.addEventListener('DOMContentLoaded', function(event) {
var doc = event.originalTarget;
if(doc instanceof HTMLDocument) {
MyObject.processDOMContentLoaded(doc);
}
}, true);
}
}, false);
Have not tested, but should work.
You are using onLocationChange method - but if you look at how the browser behaves, the location in the address bar changes as soon as a connection with the server is established. You should look at state changes instead:
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus)
{
if ((aFlag & Components.interfaces.nsIWebProgressListener.STATE_STOP) &&
(aFlag & Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW))
{
// A window finished loading
PageLoad.windowLoaded(aWebProgress.DOMWindow);
}
},
Note that the window that finished loading is explicitly passed to PageLoad.windowLoaded() - you will be receiving notifications from different tabs and you cannot assume that the notification comes from the foreground tab.
As to the warning you are getting, just leave out the second parameter in the call to gBrowser.addProgressListener():
gBrowser.addProgressListener(urlBarListener);
tabbrowser.addProgressListener() only accepts one parameter, unlike nsIWebProgress.addProgressListener() which has a second parameter.
Actually its a great question.
You should use event listener, but carefully, because if you trigger for every page load its can trigger you more than one time (in the worst case about dozens of times).
So how you can do that?
window.addEventListener("load", function load(event){
window.removeEventListener("load", load, false); //remove listener, no longer needed
myExtension.init();
},false);
var myExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent){
appcontent.addEventListener("DOMContentLoaded", myExtension.onPageLoad, true);
}
},
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget; // doc is document that triggered the event
var win = doc.defaultView; // win is the window for the doc
if (doc.nodeName != "#document") return;
if (win != win.top) return;
if (win.frameElement) return;
alert("the main page has been loaded");
},
};
get notice that we check for the type of the trigger every pageload triggering to prevent multi load.
The answers that were provided were acceptable but I found yet another solution that works perfectly.
var PageLoad = {
init: function() {
if(gBrowser) gBrowser.addEventListener("DOMContentLoaded", this.onPageLoad, false);
},
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget; // doc is document that triggered the event
var win = doc.defaultView; // win is the window for the doc
if (doc.nodeName != "#document") return;
if (win != win.top) return;
if (win.frameElement) return;
MyAddon.function();
}
}
window.addEventListener("load", function load(event){
window.removeEventListener("load", load, false); //remove listener, no longer needed
PageLoad.init();
},false);
I am trying using code
// This is an active module of the goelvivek (8) Add-on
exports.main = function() {
var httpRequestObserver =
{
observe: function(subject, topic, data)
{
if (topic == "http-on-examine-response") {
if(console)
console.log(data);
}
}
};
var {Cc, Ci, Cr} = require("chrome");
var observer = require("observer-service");
observerService = Components.classes["#mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.addObserver(httpRequestObserver, "http-on-examine-response", false);
};
but line console.log(data); is not printing any thing in console log. why ?
In addition to the issue noted by Nickolay, an observer needs to implement a QueryInterface() function (typically by means of XPCOMUtils.generateQI()). Here is how one would do it with the Add-on SDK:
var {Cc, Ci, Cr, Cu} = require("chrome");
var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var httpRequestObserver =
{
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
...
};
However, since you already require observer-service package, it would be easier to use it:
var observer = require("observer-service");
observer.add("http-on-examine-response", onHttpRequest);
function onHttpRequest(subject, data)
{
...
}
The downside of this approach is that observer-service is an internal package and its API might change in future Add-on SDK versions.
Is it the real snippet? You should see an error about Components being undefined in the Error Console. Either get it from require('chrome') or use the object from require("observer-service").
I want to open the link when the user uninstalls the addon, so for this what i have to code and under which event.
If anybody know about this then please help me out.
Currently this is what I am doing at the time of uninstall. But gBrowser.addTab(Website + 'uninstalled=true&token=' + uniqueguid); is not working over here.
var UninstallObserver = {
_uninstall : false,
observe : function(subject, topic, data) {
//===Write Code here for Delete File Uninsatll Time
//alert("Uninstall Time Delete File");
var Filename = "webmail";
// Delete all template file.
try{
var pref = Components.classes["#mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
var finished = "";
pref.setBoolPref("myextension.install.just_installed", false);
}
catch(e) {}
gBrowser.addTab(Website + 'uninstalled=true&token=' + uniqueguid);
var file = Components.classes["#mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(Components.classes["#mozilla.org/file/directory_service;1"].getService( Components.interfaces.nsIProperties).get("ProfD", Components.interfaces.nsIFile).path+"\\DefaultTemplate.txt");
if ( file.exists() == true )
{
var aFile = Components.classes["#mozilla.org/file/local;1"].createInstance();
if (aFile instanceof Components.interfaces.nsILocalFile)
{
aFile.initWithPath(Components.classes["#mozilla.org/file/directory_service;1"].getService( Components.interfaces.nsIProperties).get("ProfD", Components.interfaces.nsIFile).path + "\\DefaultTemplate.txt");
aFile.remove(false);
}
}
//=======
if (topic == "em-action-requested") {
subject.QueryInterface(Components.interfaces.nsIUpdateItem);
if (subject.id == MY_EXTENSION_UUID)
{
if (data == "item-uninstalled")
{
//==Delete File Whenever Uninstall
//alert("When Uninatall");
//===========
data = "item-cancel-action";
this._uninstall = true;
}
if (data == "disabled")
{
// alert("You are not allow to disable SysLocker.");
this._uninstall = true;
}
else if (data == "item-cancel-action")
{
this._uninstall = false;
}
}
}
else if (topic == "quit-application-granted")
{
data = "item-cancel-action";
if (this._uninstall)
{
//Code here to delete registry
}
this.unregister();
}
},
register : function() {
var observerService =
Components.classes["#mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "em-action-requested", false);
observerService.addObserver(this, "quit-application-granted", false);
},
unregister : function() {
var observerService =
Components.classes["#mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this,"em-action-requested");
observerService.removeObserver(this,"quit-application-granted");
}
}
Thanks
0) What kind of extension is this? I assume it's a regular extension requiring restart; bootstrapped (restartless) extensions have their own uninstall notification.
1) Per the MDC docs, the em-action-requested notification was replaced with a different notification in Firefox 4+, are you testing with Firefox 4 or 3.6?
2) How exactly is gBrowser.addTab "not working over here"? Does the code get to that point? Do you get any messages in the Error Console (see that page for set up tips)? If you put your code in an XPCOM component (which is correct), you'll first have to get a reference to a browser window. See Working with windows in chrome code.
I don't think that the em-action-requested topic is posted to observers until the extension is actually uninstalled, which happens on restart (assuming it is not a restartless extension). When are you expecting the new tab to appear? I would try setting a pref when the uninstall topic is triggered and checking for that pref on startup. If it is there, you can display your tab and remove the pref.