Is there any chance to pass some data to my server through install.rdf when my Firefox add-on check server for update?
Example:
...
<em:updateURL>http://www.site.com/update.php?var=myData</em:updateURL>
...
where "myData" is saved in options.xul or in another place like simple-storage.
Yes, but it is quite nasty. The AddonManager will replace a bunch of predefined and dynamic properties in the URL:
Register a new component implementing nsIPropertyBag2 (or use an existing implementation, such as ["#mozilla.org/hash-property-bag;1"]).
Register your component in the nsICategoryManager under the "extension-update-params" category.
Since you mentioned simple-storage: restartless add-ons must also unregister their stuff when being unloaded.
There is a unit test demonstrating how this stuff works. You of course need to adapt it a bit (if alone for require("chrome").
I found one "simple solution" but I dont know if that is also good practice ...
var origLink = "http://www.site.net/update.php?var=myData";
var newsLink = "http://www.site.net/update.php?var=" + simplePref.prefs.myData;
const {Cc,Ci,Cu} = require("chrome");
var observer = {
QueryInterface: function(iid) {
if (iid.equals(Ci.nsIObserver) || iid.equals(Ci.nsISupports)) return this;
},
observe: function(subject, topic, data){
if (topic == "http-on-modify-request"){
var channel = subject.QueryInterface(Ci.nsIChannel);
if (channel.originalURI.spec == origLink) {
channel.originalURI.spec = newsLink;
}
}
}
};
var ObsService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
ObsService.addObserver(observer, "http-on-modify-request", false);
Related
I'm creating a restartless (bootstrapped) addon for Thunderbird to replicate the functionality of an overlay-based addon I made years ago. The desire is to manipulate some of the text in the new email's body. In the old overlay version, it was pretty straightforward: I could just use the gMsgCompose object. How do I get the gMsgCompose object for a compose window in a restartless addon?
Here's my code right now. For a while I thought that edElem was nearly what I wanted, which is why I'm test-dumping edElem.editortype (which should be either "textmail" or "htmlmail" when I really do have the right object).
'use strict';
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
function startup(data, reason)
{
Services.wm.addListener(MyAddon.windowListener);
}
function shutdown(data, reason)
{
Services.wm.removeListener(MyAddon.windowListener);
}
function install(aData, aReason) { }
function uninstall(aData, aReason) { }
if(!MyAddon) var MyAddon = {};
MyAddon.windowListener = {
onOpenWindow: function(aXULWindow) {
let aDOMWindow = aXULWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
if (!aDOMWindow) return;
aDOMWindow.addEventListener("load", function _cl_load(event){
aDOMWindow.removeEventListener("load", _cl_load, false);
MyAddon.handleOpenWindow(aDOMWindow, event.target);
}, false);
}
}; // MyAddon.windowListener
MyAddon.handleOpenWindow = function(aDOMWindow, DOMdocument) {
if(DOMdocument.documentURI != "chrome://messenger/content/messengercompose/messengercompose.xul")
return;
let edWindow = DOMdocument.documentElement;
// Just to be sure
if(edWindow.id != 'msgcomposeWindow')
return;
let edElem = DOMdocument.getElementById("content-frame");
/* I thought edElem was the element I want, but the following
* returns an empty result, which I take to mean the element isn't
* fully loaded yet.
*/
dump("XXX handleOpenWindow: edElem.editortype="+edElem.editortype+"\n");
/* Adding a listener to this element doesn't get the behaviour
* I want either. I've tried hooking several events
* in the following fashion, and the event handler code doesn't
* execute for any of them, so maybe edElem isn't what I want.
*/
[ "load",
"unload",
"compose-window-init",
"compose-window-close",
"compose-fields-ready",
"compose-send-message",
].forEach(function(eventName) {
edElem.addEventListener(eventName, function _eE_FIXME(event){
dump("XXX "+eventName+": edElem.editortype="+edElem.editortype+"\n");
}, false);
});
/* So, how do I get control of the editor,
* so I can read and play with its content?
*/
}; // MyAddon.handleOpenWindow
The only question I found on stackoverflow that's close to what I want is Get sender and recipients in Thunderbird extension upon sending message. I've tried what they do there, but all it seems to do is give me an alternate way of getting the compose window as a whole, which I can already get.
I've made an ugly stub with a timer in the onOpenWindow:
domWindow.setTimeout( function() { uploadHook( domWindow ); }, 500 );
So then (in uploadHook) I can get editor with:
var editorNode = domWindow.document.getElementById("content-frame");
// GetCurrentEditor() equivalent:
nsIEditor = editorNode.getEditor(editorNode.contentWindow)
But looking for a better solution.
I'm having trouble adding an observer to watch for changes in firefox's search engines. I read the nsIBrowserSearchService page on the Mozilla Developer Site which suggests to use the init() method of the Services.search object.
void init([optional] in nsIBrowserSearchInitObserver observer);
I tried that and I managed to get it to execute the function once on start up but it never calls it again when I add or remove or reorder the search engines. So I'm doing something wrong.
I have experience with observers but only with using general preferences and I generally use add them and remove them using the code below. I'm not sure how to do it with the nsIBrowserSearchService . I would like to observe the nsIBrowserSearchService in the same or a similar way but I'm not sure what I would put for
branch = Services.prefs.getBranch("preferenceNameHere");
I'm not observing Services.prefs but Services.search I assume, and that has no getBranch method as far as I can tell.
This is how I normally add and remove the observer in Chrome.js
const {Ci, Cu} = require("chrome");
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
branch = Services.prefs.getBranch("preferenceNameHere");
branch.addObserver("", observe, false);
function observe(subject, topic, data) {
// instanceof actually also "casts" subject
if (!(subject instanceof Ci.nsIPrefBranch)) {
return;
}
//do stuff here
};
exports.onUnload = function(reason) {
// Need to remove our observer again! This isn't automatic and will leak
// otherwise.
branch.removeObserver("", observe);
if(reason == "disable" || reason == "uninstall"){
//restore changes made by addon
}
};
Can anyone advise me on how to do this so I can observe for changes and ensure that I remove the observer properly with the search object. Thanks
What you are trying above is trying to use pref observer on non-prefs, this is not possible. You have to use the regular observer service like this.
This notifies you when the user changes the engine.
Cu.import('resource://gre/modules/Services.jsm');
var observers = {
'browser-search-engine-modified': {
aTopic: 'browser-search-engine-modified',
observe: function (aSubject, aTopic, aData) {
if (aData == 'engine-current') {
console.log('current engine was changed!');
//console.log('aSubject on change:', aSubject.name, 'same as Services.search.currentEngine.name:', Services.search.currentEngine.name); //aSubject is the engine
//console.log('aTopic on change:', aTopic); //aTopic is obviously `browser-search-engine-modified`
}
},
reg: function () {
Services.obs.addObserver(observers[this.aTopic], this.aTopic, false);
},
unreg: function () {
Services.obs.removeObserver(observers[this.aTopic], this.aTopic);
}
}
};
To start listening do this:
for (var o in observers) {
observers[o].reg();
}
To stop listening do this:
for (var o in observers) {
observers[o].unreg();
}
I'm not sure what happens when user adds a new engine but doenst select it. Or if he removes a engine. Please let me know what those messages are when user does that.
I have a Single Page Application that is working pretty well so far but I have run into an issue I am unable to figure out. I am using breeze to populate a list of projects to be displayed in a table. There is way more info than what I actually need so I am doing a projection on the data. I want to add a knockout computed onto the entity. So to accomplish this I registered and entity constructor like so...
metadataStore.registerEntityTypeCtor(entityNames.project, function () { this.isPartial = false; }, initializeProject);
The initializeProject function uses some of the values in the project to determine what the values should be for the computed. For example if the Project.Type == "P" then the rowClass should = "Red".
The problem I am having is that all the properties of Project are null except for the ProjNum which happens to be the key. I believe the issue is because I am doing the projection because I have registered other initializers for other types and they work just fine. Is there a way to make this work?
EDIT: I thought I would just add a little more detail for clarification. The values of all the properties are set to knockout observables, when I interrogate the properties using the javascript debugger in Chrome the _latestValue of any of the properties is null. The only property that is set is the ProjNum which is also the entity key.
EDIT2: Here is the client side code that does the projection
var getProjectPartials = function (projectObservable, username, forceRemote) {
var p1 = new breeze.Predicate("ProjManager", "==", username);
var p2 = new breeze.Predicate("ApprovalStatus", "!=", "X");
var p3 = new breeze.Predicate("ApprovalStatus", "!=", "C");
var select = 'ProjNum,Title,Type,ApprovalStatus,CurrentStep,StartDate,ProjTargetDate,CurTargDate';
var isQaUser = cookies.getCookie("IsQaUser");
if (isQaUser == "True") {
p1 = new breeze.Predicate("QAManager", "==", username);
select = select + ',QAManager';
} else {
select = select + ',ProjManager';
}
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select);
if (!forceRemote) {
var p = getLocal(query);
if (p.length > 1) {
projectObservable(p);
return Q.resolve();
}
}
return manager.executeQuery(query).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
var list = partialMapper.mapDtosToEntities(
manager,
data.results,
model.entityNames.project,
'ProjNum'
);
if (projectObservable) {
projectObservable(list);
}
log('Retrieved projects using breeze', data, true);
}
};
and the code for the partialMapper.mapDtosToEntities function.
var defaultExtension = { isPartial: true };
function mapDtosToEntities(manager,dtos,entityName,keyName,extendWith) {
return dtos.map(dtoToEntityMapper);
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
extendWith = $.extend({}, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
function mapToEntity(entity, dto) {
for (var prop in dto) {
if (dto.hasOwnProperty(prop)) {
entity[prop](dto[prop]);
}
}
return entity;
}
}
EDIT3: Looks like it was my mistake. I found the error when I looked closer at initializeProject. Below is what the function looked like before i fixed it.
function initializeProject(project) {
project.rowClass = ko.computed(function() {
if (project.Type == "R") {
return "project-list-item info";
} else if (project.Type == "P") {
return "project-list-item error";
}
return "project-list-item";
});
}
the issue was with project.Type I should have used project.Type() since it is an observable. It is a silly mistake that I have made too many times since starting this project.
EDIT4: Inside initializeProject some parts are working and others aren't. When I try to access project.ProjTargetDate() I get null, same with project.StartDate(). Because of the Null value I get an error thrown from the moment library as I am working with these dates to determine when a project is late. I tried removing the select from the client query and the call to the partial entity mapper and when I did that everything worked fine.
You seem to be getting closer. I think a few more guard clauses in your initializeProject method would help and, when working with Knockout, one is constantly battling the issue of parentheses.
Btw, I highly recommend the Knockout Context Debugger plugin for Chrome for diagnosing binding problems.
Try toType()
You're working very hard with your DTO mapping, following along with John's code from his course. Since then there's a new way to get projection data into an entity: add toType(...) to the end of the query like this:
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select)
.toType('Project'); // cast to Project
It won't solve everything but you may be able to do away with the dto mapping.
Consider DTOs on the server
I should have pointed this out first. If you're always cutting this data down to size, why not define the client-facing model to suit your client. Create DTO classes of the right shape(s) and project into them on the server before sending data over the wire.
You can also build metadata to match those DTOs so that Project on the client has exactly the properties it should have there ... and no more.
I'm writing about this now. Should have a page on it in a week or so.
I've been reading and hacking around with https://developer.mozilla.org/en/XUL_School/Intercepting_Page_Loads but can seem to do what I need.
I'm working on Chromeless, trying to prevent the main xulbrowser element from ever being navigated away from, e.g., links should not work, neither should window.location.href="http://www.example.com/".
I'm assuming I can do this via browser.webProgress.addProgressListener and then listen to onProgressChange but I can't figure out how to differentiate between a resource request and the browser changing locations (it seems that onLocationChange is too late as the document is already being unloaded).
browser.webProgress.addProgressListener({
onLocationChange: function(){},
onStatusChange: function(){},
onStateChange: function(){},
onSecurityChange: function(){},
onProgressChange: function(){
aRequest.QueryInterface(Components.interfaces.nsIHttpChannel)
if( /* need to check if the object triggering the event is the xulbrowser */ ){
aRequest.cancel(Components.results.NS_BINDING_ABORTED);
}
},
QueryInterface: xpcom.utils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference])
}, wo._browser.webProgress.NOTIFY_ALL);
Another option that sounds promising is the nsIContentPolicy.shouldLoad() method but I really have no clue how to "create an XPCOM component that extends nsIContentPolicy and register it to the "content-policy" category using the nsICategoryManager."
Any Ideas?
I got help on this from the mozilla's #xulrunner irc channel.
Resulting solution follows.
Note: this is a module for use in Mozilla Chromeless, the require("chrome") and require("xpcom") bits will NOT be available under normal circumstances.
const {Cc, Ci, Cu, Cm, Cr} = require("chrome");
const xpcom = require("xpcom");
/***********************************************************
class definition
***********************************************************/
var description = "Chromeless Policy XPCOM Component";
/* UID generated by http://www.famkruithof.net/uuid/uuidgen */
var classID = Components.ID("{2e946f14-72d5-42f3-95b7-4907c676cf2b}");
// I just made this up. Don't know if I'm supposed to do that.
var contractID = "#mozilla.org/chromeless-policy;1";
//class constructor
function ChromelessPolicy() {
//this.wrappedJSObject = this;
}
// class definition
var ChromelessPolicy = {
// properties required for XPCOM registration:
classDescription: description,
classID: classID,
contractID: contractID,
xpcom_categories: ["content-policy"],
// QueryInterface implementation
QueryInterface: xpcom.utils.generateQI([Ci.nsIContentPolicy,
Ci.nsIFactory, Ci.nsISupportsWeakReference]),
// ...component implementation...
shouldLoad : function(aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aExtra) {
let result = Ci.nsIContentPolicy.ACCEPT;
// only filter DOCUMENTs (not SUB_DOCUMENTs, like iframes)
if( aContentType === Ci.nsIContentPolicy["TYPE_DOCUMENT"]
// block http(s) protocols...
&& /^http(s):/.test(aContentLocation.spec) ){
// make sure we deny the request now
result = Ci.nsIContentPolicy.REJECT_REQUEST;
}
// continue loading...
return result;
},
createInstance: function(outer, iid) {
if (outer)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
}
};
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
try
{
Cm.nsIComponentRegistrar.registerFactory(classID, description, contractID, ChromelessPolicy);
}
catch (e) {
// Don't stop on errors - the factory might already be registered
Cu.reportError(e);
}
const categoryManager = Cc["#mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
for each (let category in ChromelessPolicy.xpcom_categories) {
categoryManager.addCategoryEntry(category, ChromelessPolicy.classDescription, ChromelessPolicy.contractID, false, true);
}
Pull Request on github for those that are interested: https://github.com/mozilla/chromeless/pull/114
Thanks to everyone in advance -
I need to load a preference before any windows are loaded at startup. Below is some /component code I have been working with. The SetPreference method seems to fail when it is called (nothing executes afterwords either) - I am assuming because the resources that it needs are not available at the time of execution...or I am doing something wrong. Any suggestions with this code or another approach to setting a preference at startup?
Thanks again,
Sam
For some reason the code formatting for SO is not working properly - here is a link to the code as well - http://samingrassia.com/_FILES/startup.js
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
const Cc = Components.classes;
const Ci = Components.interfaces;
const ObserverService = Cc['#mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
function MyStartupService() {};
MyStartupService.prototype = {
observe : function(aSubject, aTopic, aData) {
switch (aTopic) {
case 'xpcom-startup':
this.SetPreference("my.extension.is_running", "false");
break;
case 'app-startup':
this.SetPreference("my.extension.is_running", "false");
ObserverService.addObserver(this, 'final-ui-startup', false);
break;
case 'final-ui-startup':
//make sure is_running is set to false
this.SetPreference("my.extension.is_running", "false");
ObserverService.removeObserver(this, 'final-ui-startup');
const WindowWatcher = Cc['#mozilla.org/embedcomp/window-watcher;1'].getService(Ci.nsIWindowWatcher);
WindowWatcher.registerNotification(this);
break;
case 'domwindowopened':
this.initWindow(aSubject);
break;
}
},
SetPreference : function(Token, Value) {
var prefs = Components.classes["#mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
var str = Components.classes["#mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
str.data = Value;
prefs.setComplexValue(Token, Components.interfaces.nsISupportsString, str);
//save preferences
var prefService = Components.classes["#mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
prefService.savePrefFile(null);
},
initWindow : function(aWindow) {
if (aWindow != '[object ChromeWindow]') return;
aWindow.addEventListener('load', function() {
aWindow.removeEventListener('load', arguments.callee, false);
aWindow.document.title = 'domwindowopened!';
// for browser windows
var root = aWindow.document.documentElement;
root.setAttribute('title', aWindow.document.title);
root.setAttribute('titlemodifier', aWindow.document.title);
}, false);
},
classDescription : 'My Startup Service',
contractID : '#mystartupservice.com/startup;1',
classID : Components.ID('{770825e7-b39c-4654-94bc-008e5d6d57b7}'),
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver]),
_xpcom_categories : [{ category : 'app-startup', service : true }]
};
function NSGetModule(aCompMgr, aFileSpec) {
return XPCOMUtils.generateModule([MyStartupService]);
}
To answer your real question, which is
I have code that loads on every window load and I need to make sure that only gets executed once every time firefox starts up.
..you should just use a module, in the load handler that you wish to execute once, check a flag on the object exported from (i.e. "living in") the module, then after running the code you need, set the flag.
Since the module is shared across all windows, the flag will remain set until you close Firefox.
As for your intermediate problem, I'd suggest wrapping the code inside observe() in a try { ... } catch(e) {dump(e)} (you'll need to set a pref and run Firefox in a special way in order to see the output) and check the error returned.
I guess xpcom-startup and app-startup is too early to mess with preferences (I think you need a profile for that), note that you don't register to get xpcom-startup notification anyway. You probably want to register for profile-after-change instead.