출처: http://www.mozilla.org/projects/plugins/bi-directional-plugin-scripting.html
Bi-directional Plugin Scripting
July 26, 2002
Introduction
Background
Sample Code
Introduction
Mozilla has supported plugin scripting since the 0.9.4 milestone, and has been documented by a previous article. However, that article only discusses how to make a plugin callable from JavaScript. This article will show how to call JavaScript from a plugin.
Background
The traditional plugin API already provides a mechanism by which a plugin instance can call into JavaScript and access information from the embedding web page. A plugin instance can simply call NPN_GetURL()
passing in a "javascript:" URL. Here's a simple example:
NPN_GetURL(instance, "javascript:alert('hello world.')", NULL);
This simply tells JavaScript to display an alert window with the message "hello world." However, this happens asynchronously, i.e. the alert doesn't come up until some time after the call has been issued. This makes showing alert rather inconvenient to use as a debugging tool. In addition, what if what we really want to do is read some value, rather than display a message?
If we are willing to wait for the result to come at some later time, then we have a couple of options. One is to use NPN_GetURLNotify()
instead, which will call the plugin back with the result of evaluating the JavaScript URL. Alternatively, since you are presumably writing a scriptable plugin, we can deliver the results to the scriptable plugin through one of its methods. For the sake of exposition, let's define a hypothetical scriptable plugin interface:
interface acmeScriptablePlugin : nsISupports { attribute string location; }
This interface defines an attribute, location, which can be both read and written from script. If the plugin is instantiated on a web page, all by itself, then a simple script that reads and writes this attribute would look like this:
<SCRIPT> var plugin = document.embeds[0]; plugin.location = document.location; // tell the plugin the URL of this document. alert('location = ' + plugin.location); // read back the document's location </SCRIPT>
This is an example of a script calling into a plugin's scriptable interface. Things get more interesting if we define an attribute with a more complex value. Let's define an interface that represents a simple JavaScript object. It has properties which can be read and written:
interface acmeJSObject : nsISupports { string getProperty(in string name); void setProperty(in string name, in string value); }
If we modify our plugin's scriptable interface to use this interface, we can provide a way for the plugin itself to read properties of the web page's document object:
interface acmeScriptablePlugin : nsISupports { attribute acmeJSObject document; }
Here's JavaScript code that would write to the document attribute:
<SCRIPT> var plugin = document.embeds[0]; var docWrapper = { doc : document, getProperty : function(name) { return this.doc[name]; } setPropety : function(name, value) { this.doc[name] = value; } }; plugin.document = docWrapper; // give plugin wrapper to this document. </SCRIPT>
Finally, here's some C++ code reads document.location:
nsIMemory* allocator = GetMemoryAllocator(); acmeJSObject* document = GetJSDocument(); char* location; if (NS_SUCCEEDED(document->GetProperty("location", &location))) { printf("location = %s\n", location); allocator->Free(location); }
How does all of this work? The Mozilla browser contains a bridging layer which wraps JavaScript objects in C++ objects that implement XPCOM interfaces. All that a script object has to do to implement an XPCOM interface is to define function properties that correspond to the IDL declaration of that interface.
Sample Code
Here's a more fully fleshed out example. Here's an interface that will be implemented by JavaScript code that the plugin injects into a web page:
/* acmeIScriptObject.idl */ #include "nsISupports.idl" [scriptable, uuid(f78d64e0-1dd1-11b2-a9b4-ae998c529d3e)] interface acmeIScriptObject : nsISupports { acmeIScriptObject getProperty(in string name); void setProperty(in string name, in acmeIScriptObject value); /** * Conversions. */ string toString(); double toNumber(); /** * Constructors. */ acmeIScriptObject fromString(in string value); acmeIScriptObject fromNumber(in double value); acmeIScriptObject call(in string name, in PRUint32 count, [array, size_is(count)] in acmeIScriptObject argArray); };
Here's a JavaScript implementation of acmeIScriptObject
:
function jsScriptObject(obj) { // implementation detail, to allow unwrapping. this.wrappedJSObject = obj; } jsScriptObject.prototype = { QueryInterface : function(iid) { try { if (iid.equals(Components.interfaces.acmeIScriptObject) || iid.equals(Components.interfaces.nsIClassInfo) || iid.equals(Components.interfaces.nsISecurityCheckedComponent) || iid.equals(Components.interfaces.nsISupports)) { alert("QI good."); return this; } throw Components.results.NS_ERROR_NO_INTERFACE; } catch (se) { // older browsers don't let us use iid.equals, wah. return this; } } // acmeIScriptObject implementation. getProperty : function(name) { return new jsScriptObject(this.wrappedJSObject[name]); } setProperty : function(name, value) { alert('setProperty: name = ' + name + ', value = ' + value.toString() + '\n'); this.wrappedJSObject[name] = value.toString(); } toString : function() { return this.wrappedJSObject.toString(); } toNumber : function() { return this.wrappedJSObject.valueOf(); } fromString : function(value) { return new jsScriptObject(value); } fromNumber : function(value) { return new jsScriptObject(value); } call : function(name, argArray) { // TBD } };
Finally, here's some C++ code that uses the acmeIScriptObject
interface:
NS_IMETHODIMP nsScriptablePeer::SetWindow(acmeIScriptObject *window) { NS_IF_ADDREF(window); NS_IF_RELEASE(mWindow); mWindow = window; acmeIScriptObject* location = nsnull; nsresult rv = window->GetProperty("location", &location); if (NS_SUCCEEDED(rv) && location) { char* locationStr = NULL; rv = location->ToString(&locationStr); if (NS_SUCCEEDED(rv) && locationStr) { NPN_MemFree(locationStr); } NS_RELEASE(location); } acmeIScriptObject* newLocation = nsnull; rv = window->FromString("http://www.mozilla.org", &newLocation); if (NS_SUCCEEDED(rv) && newLocation) { window->SetProperty("location", newLocation); NS_RELEASE(newLocation); } return NS_OK; }
July 25th, 2007 at 2:49 pm
thanks for the tip, that’s really smart. I’m now able to trig a js function from my c++ xpcom component. The only bug is that the parameter given to the callback are not correctly handled, ie they appears as ‘undefined’ in the js function. Any idea?
February 16th, 2008 at 2:30 am
Do you have all the code?
February 16th, 2008 at 2:39 am
You can check out the Firetray’s source code from google code.
Or browse it online via http://code.google.com/p/firetray/source/browse
February 20th, 2008 at 7:54 am
The information all works upto the point of calling JS with parameters.
The parameter is coming out “undefined”.
The info in this thread is great with that caveat. How to make it work?
February 21st, 2008 at 5:06 am
I got the solution to my own post. I’ll post it asap.
February 21st, 2008 at 5:53 am
You have to call from javascript to XPCOM once the callback is called. That is the only way I can think so far.
March 5th, 2008 at 11:31 pm
Hi, nice article, but I am not able to pass the argument to the JavaScript callback.
I just get an alert window saying “undefined”. Does anybody knows how to solve this issue?
Santa, can you explain further your workaround? Do you mean store the data in a variable in XPCom component and then in the call back recover that info from JavaScript?
March 6th, 2008 at 12:02 am
Hey I ‘ve discovered something!!
I was wondering why JSCallback interface should define a method named “call” and not any other name.
I found that in JavaScript, if you define a function you create an object with a call() method!, so i decided to test this. In the JavaScript example above i called “test.call()”. After some more test I’ve discovered that test.call(0,3) makes an alert(3)!!.
The last step was to do the same in the XPCom, so modify the idl for the callback:
boolean call(in PRUint32 bogus, in PRUint32 aData);
And the C++ code
aCallback->Call(0, first+second, &ret);
And I got it.
Hope this helps any one.
One final request. I don’t know how to use nsCOMPtr. Help linking (aditional libs) would be very appreciated.
March 7th, 2008 at 2:37 am
Do this:
http://www.mail-archive.com/dev-tech-xpcom@lists.mozilla.org/msg00531.html
if you want parameters.