I'm taking a foray into Firefox extension development for the first time, and so far it's been pretty comfortable going, but I'm running into a problem; one of the things I need to do overwriting the built-in nsIPromptService and replacing it with something of my own instead.
I walked through the basic XPCOM component creation tutorial here and got the hello world one working:
https://developer.mozilla.org/en/creating_xpcom_components
And everything in that seems to work fine, but nothing I've been able to find or research shows how I can overwrite an interface from javascript. I've seen things in C++ and Java that seem to be able to overwrite the built-in components, but I can't find anything about doing this from javascript, and just trying to change the contract ID didn't work; when I try to get the service from the contract ID (as below), it just returns the original, built-in component version.
var myComponent = Components.classes['#mozilla.org/embedcomp/prompt-service;1']
.getService(Components.interfaces.nsIPromptService);
Is there something really obvious here that I'm missing? Is this the wrong way to go about overriding components (I can't seem to find anything anywhere, so I'm not really sure what I should be doing..).
Neil, thanks for the suggestion. That's what I thought I was doing (and I was), but if you're actually overriding a contract (instead of defining a new one), it looks like the answer is that you have to go to the nsIComponentRegistrar and actually register your factory (rather than relying on the chrome.manifest to handle it for you). An example of this would be:
Components.manager.nsIComponentRegistrar.registerFactory(CLASS_ID, CLASS_NAME, CONTRACT_ID, MyPromptServiceFactory);
Where the constans were:
const CLASS_ID = Components.ID("{a2112d6a-0e28-421f-b46a-25c0b308cbd0}");
// description
const CLASS_NAME = "My Prompt Service";
// textual unique identifier
const CONTRACT_ID = "#mozilla.org/embedcomp/prompt-service;1";
Where the CLASS_ID/CONTRACT_ID were the IDs for the pre-existing service.
You need to register your component using the contract id of the service that you want to override.
Related
I might be approaching this completely incorrectly, so any advice is appreciated. I'm currently trying to dig in deep to Typescript, and have decided to simultaneously use Sql.js (the JS version of SQLite) at the same time...
My first instinct to use Sql.js was to search for a .d.ts set of bindings around Sql.js so that I could easily start using it with TS. I've come up with no bindings so far (I don't think one exists yet), but figured I could just start "define"-ing in the stuff that I need from the library...
Starting with one of the simple examples from the "sql.js" docs, you have something like this:
var sql = window.SQL;
var db = new sql.Database();
Moving to the typescript side, I wanted to let TS know that after the sql.js library is included, the window object now has a property called SQL, which is essentially the hook to the rest of the library. I figured I needed to do this because, of course, when I type "window." (window-dot) in Visual Studio in my TS file, the Intellisense presented doesn't know about the SQL property now hanging off of "window". So... I dug around Stack, and concluded that I could solve this with a "declare" which I basically see as a way to tell TS just enough about the libraries that I don't have binding files for (.d.ts files).
However, in doing this, I can't seem to construct the syntax for such a declaration. I've tried:
declare var window.SQL : any;
declare var window.SQL;
declare var SQL = window.SQL;
declare window.SQL;
None of these work, of course.
So, the question is, how can I let TS know about new properties introduced by JS libraries on standard objects like "window", and the follow up question is, of course, is this even the right way to be approaching this?
Thanks for any insight.
Now that we have type definitions for SQL.js, and as of Typescript 2.0, the ability to install them easily, you can just do this:
npm install #types/sql.js
Then, that typings file will automatically be picked up by the compiler, and it will know that there is a SQL object. Here is the full definition:
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/sql.js/sql.js.d.ts
window is declared to be of type interface Window (in lib.d.ts). So you need to add to that if you want to use window.SQL :
interface Window{
SQL:any;
}
var sql = window.SQL;
var db = new sql.Database();
Recommend:
Personally I would recommend not using it off of window and just do
declare var SQL:any;
var db = new SQL.Database();
By default the variable access in the browser is on window.
OK, I think I've figured it out, although I'm still not sure if this is the preferred method.
In inspecting lib.d.ts, which is pretty much the heart of the sun as far as declarations go, I found the declaration of the interface for Window more than once. That led me to the conclusion that TS interface declarations (and likely other declarations) can essentially be "partials". It appears that the definition of any interface can be extended by simply adding extra items in a brand new declaration...
So, currently, my angry red squiggly line under "window.SQL" has gone away by simply adding the following:
interface Window {
SQL: any;
}
This seems to work because in lib.d.ts, the "window" variable is defined as a "Window" like this:
declare var window: Window;
... on line 9867 of the file in case others are looking for it. So, the net effect seems to be, I extended the definition of "Window" based on knowing that sql.js would make a new property called "SQL" on it.
HTH, in case anyone else is spelunking the same concepts.
I am trying to over ride one the notification popups of browser using the following code:
var branch = Components.classes["#mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
But in Mozilla Firefox, I get the error Component.classes is undefined.
And in Chrome Browser, I get the error Component is undefined.
Well I have realised I need to include something in my website. But I am unable to find exactly what is required.
Please anybody help. I googled about it a lot, but I have never used this thing before(the Classes) and I am unable to search what will help me out. i dont even have any idea that what will be the tags for this thing. I have never used Component or its classes
My website is in ZF2.
Components Object is non-standard feature. See https://developer.mozilla.org/en/docs/Components_object.
It also says
Warning: This object is only intended for code running with chrome
privileges. Exposing the object to regular web code was a mistake.
In my add-on, I'm using XUL to display dialog windows because I can customize their appearance to suit the add-on's general style (like a custom titlebar).
Using the migration guide, I'm able to do this easily. The thing is, I would like to call certain functions in the add-on's main module from the XUL dialog.
After a bit of searching I found the loader module, which seems to be able to do exactly what I want. But, I'm experiencing trouble in using it to access the main module.
First, I tried the obvious approach as mentioned in the documentation;
xul_dialog.js:
let {Loader} = Components.utils.import('resource://gre/modules/commonjs/toolkit/loader.js');
let loader = Loader.Loader({
paths: {
'toolkit/': 'resource://gre/modules/commonjs/toolkit/',
'': 'resource:///modules/',
'./': 'resource://<my-addon-name>/root/'
}
});
let main = Loader.main(loader, './main');
I got an error that './main' was not found at resource://<my-addon-name>/root/. Figuring that I was using the incorrect paths, I experimented a bit until I could remove all path associated errors.
xul_dialog.js:
let {Loader} = Components.utils.import('resource://gre/modules/commonjs/toolkit/loader.js');
let loader = Loader.Loader({
paths: {
'toolkit/': 'resource://gre/modules/commonjs/toolkit/',
'': 'resource://gre/modules/commonjs/',
'./': 'resource://<my-addon-id>-at-jetpack/<my-addon-name>/lib/'
}
});
let main = Loader.main(loader, './main');
This time I got a rather confusing error at loader.js, line 279.
Components is not available in this context.
Functionality provided by Components may be available in an SDK
module: https://jetpack.mozillalabs.com/sdk/latest/docs/
However, if you still need to import Components, you may use the
`chrome` module's properties for shortcuts to Component properties:
Shortcuts:
Cc = Components.classes
Ci = Components.interfaces
Cu = Components.utils
CC = Components.Constructor
Example:
let { Cc, Ci } = require('chrome');
I get the same error when I use Loader.Require(loader, {id: './main'}) instead of Loader.main. I even tried passing Components as globals when instantiating the loader, but without much luck.
I'm fairly certain that I'm doing a lot of things wrong. I don't understand why I'm getting the error, even after spending quite a bit of time in loader.js. Plus, I also think that there would be a better alternative than having to use the add-on id for the path to main.js; hard-coding it like that doesn't seem right.
Any help would be really appreciated.
What you have to do is to find a specific instance of Loader, not create a new one.
At main.js
const { id, name, prefixURI } = require("#loader/options");
//pass these to the XUL dialog
At xul.js (or whatever is the name of the xul dialog script)
Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
var extensionscope = XPIProvider.bootstrapScopes[id];
var mainjssandbox = extensionscope.loader.sandboxes[prefixURI + name + "/lib/main.js"];
Assuming there is a foo function at main.js, you can call it like
mainjssandbox.foo();
Of course don't expect things to work as if XUL and Add-on SDK actually blended into one thing.
If it is your XUL dialog that should interact with your add-on, then please don't use the Loader stuff and in particular do not go the XPIProvider.bootstrapScopes #paa suggested. While this might work (for now), it should be noted that it relies on tons of implementation details that are subject to change at any point making this solution extremely fragile.
Instead there are a couple of other options (not an exhaustive list):
If the SDK part opens the windows, you may use .openDialog which supports passing arguments to the created window, and these arguments can even be objects and functions. Also, you can have the window dispatch (custom) events, and which your SDK part can listen to by calling addEventListener on the window the .openDialog call returns.
If the window is created from somewhere else (e.g. from the AddonManager because of em:optionsURL) then the nsIObserverService is another way to communicate. The window could e.g. .notifyObservers on DOMContentLoaded containing a reference to itself. The SDK parts would just have to observe such notifications by addObserver.
Another way, a bit hacky but working, is the SDK part listening to new windows via nsIWindowWatcher.registerNotification and then injecting some API into e.g. browser.xul windows by XPCNativeWrapper.unwrap(subject.QueryInterface(Ci.nsIDOMWindow)).myAddonAPI = something.
Be sure to handle unloading or your add-on well - it is still restartless -, i.e. reverse any changes you've done and remove any observers and so on again.
If you want to interact with SDK addons not created by you, then the XPIProvider route might be the only feasible. But still, it might be worth contacting the add-on author first asking for the addition of some public API instead of hacking deep into the AddonManager and SDK loader internals.
PS:
Considering passing require or the global scope to your window via openDialog if you want to. Get the global scope by placing this into your main.js:
const globalScope = this;
Some of you may or may not (probably not) know about my framework. It's name is Ally, and I absolutely love using it.
Lately I've been doing a little bit of stuff in Node.js. Today I decided I was going to use it as my HTTP server, so that I could do server-side JS (in a PHP kind of way).
To do this, I started a project I'm calling Trailer . While working on it, I found myself needing one of Ally's functions, Object:deploy. What it does is pretty much this:
var a = { a: 'a' };
a.deploy({ b: 'b' });
a.a; // 'a'
a.b; // 'b'
So I loaded it in..
var Ally = require('./Ally.js');
..but when I tried using it, it said it was undefined.
After a bit of digging I discovered that Object:deploy is defined in the Ally.js file, but the changes it makes to the global constructors don't stay.
How do I make the changes to global variables in the Ally.js file apply to the global variables in the file that required it?
Note: Ally is linked to above if looking through the source could help, and Trailer is linked to in case anyone wants to use it when I get a usable version out.
Is this discussion relevant? The key points here seem to be:
require won't extend global objects if you're working in the shell
It also won't work when NODE_MODULE_CONTEXTS = 1, though this doesn't seem to be the default for a script.
So if you're trying to run this in an interactive shell, that might be the issue. See also this SO question.
I tried to create custom widget for my site. when I loaded page it says:
mixin #0 is not a callable constructor.
clsInfo.cls.prototype is undefined
I can't find any information about clsInfo, so I don't know what is it. maybe the problem that I use dojo from google:
and my own script is located on localhost. so when my dojo on page initializes something goes wrong with my script. I can't find any good info on dojo, maybe I search in wrong places?
please help me to resolve my problem
I ran into this when I was trying to override a dijit.Dialog so I could bind events to controls within it. We've yet to see if the binding part will work, but if you look at the source, this happens when one of the bases passed in as the second argument fails to resolve to an "[Object function]". In my case, I was passing a String in.
dojo.declare takes 3 arguments:
The name of the custom object "class" you're building
An array of base classes, parents to provide functionality (not the string names of those classes)
A hash of functions and declarations
So if I want to override dijit.Dialog, I have to do:
dojo.declare("myDialogType", [dijit.Dialog], {
function1() {/*Code*/},
function2() {/*Code*/}
}
I had ["dijit.Dialog"] as my second argument and that was the problem.
I strongly recommend using Web Inspector or Firebug with uncompressed local copies of the Dojo library rather than the CDN to figure out what's going on and debug these types of problems. Dojo's documentation is extensive but not complete in some areas and some behaviors have to be figured out by looking at what the code expects. That's not intended as a slight to the authors; once you get it going it's a pretty awesome product, and any documentation for volunteer work is appreciated.
Are you sure Dojo is loading? Did you put your code in a dojo.addOnLoad()? When using a CDN you sometimes run into issues with execution times. dojo.addOnLoad() will not only trigger when the DOM is loaded, it gets called when dojo resources have downloaded, such as dijit._Widget.
I've run into this problem when I screw up the order of my requires which makes _WidgetBase not what _WidgetBase really is. Seems like a simple spot to screw up.