The Problem
In this short note we illustrate how one can update an XPCOM module in order for it to work in both Firefox 2 and Firefox 3, even if the interfaces have changed in the interim.
In the extension that prompted this note, I needed to obtain the HWND
of the document (yes its on Windows) in order to identify
each particular extension instance. To do this I used the accessibility framework:
HWND getHwnd(nsIDOMNode *node){ HWND self = NULL; nsresult rv; nsCOMPtr<nsIAccessibleRetrieval> refp; refp = do_CreateInstance( "@mozilla.org/accessibleRetrieval;1", &rv); if (NS_FAILED(rv)){ return self; } //line 6. nsCOMPtr<nsIAccessible> accnode; rv = refp->GetAccessibleFor(node, getter_AddRefs(accnode)); if(NS_FAILED(rv)){ return self; } void *wh = NULL; nsCOMPtr<nsIAccessibleDocument> accdocnode; accdocnode = do_QueryInterface(accnode, &rv); if(NS_FAILED(rv)){ return self; } rv = accdocnode->GetWindowHandle(&wh); if(NS_SUCCEEDED(rv)){ self = static_cast<HWND>(wh); } return self; }
This approach worked, as is, for versions as early as Firefox 1.5.
The problem arises when one tries to run an extension built with the latest
SDK in an older version of Firefox, say Firefox 2. What happens is that the call to do_CreateInstance
fails with nsresult NS_ERROR_NO_INTERFACE
.
This is because the call to
do_CreateInstance(aCID, aOuter, error);
will eventually evolve into a request for an object supporting the interface
with IID NS_GET_IID(nsIAccessibleRetrieval)
. Unfortunately we compiled
this in the latest SDK, and so this magic number happens to be:
"244e4c67-a1d3-44f2-9cab-cdaa31b68046"
whereas, inside Firefox 2, the IID it happens to know about is:
"663ca4a8-d219-4000-925d-d8f66406b626".
and this explains our NS_ERROR_NO_INTERFACE
.
Fortunately there is a plan of action that we can follow to rectify this. We build construct our XPCOM component so that first tries to get the interface by it's new IID, then if that fails, attempts plan B. Plan B is simply trying to get the interface by it's older IID.
For expository purposes we will do this in two stages; buoyed by the fact that it is the actual route we took.
The Optimist's Solution
The first thing we do is replace line 6 above by our plan B:
if (NS_FAILED(rv)){ return getHwndB(node); } //new line 6.
and then implement plan B. We first dredge out the old interface identifiers from our yea olde Firefox 1.5 SDK:
static const nsIID IAR_IID_OLD = { 0x663ca4a8, 0xd219, 0x4000, { 0x92, 0x5d, 0xd8, 0xf6, 0x64, 0x06, 0xb6, 0x26 }}; static const nsIID IAD_IID_OLD = {0x8781fc88, 0x355f, 0x4439, { 0x88, 0x1f, 0x65, 0x04, 0xa0, 0xa1, 0xce, 0xb6 }};
then follow the recipe.
HWND getHwndB(nsIDOMNode *node){ HWND self = NULL; nsresult rv; nsCOMPtr<nsIComponentManager> compMgr; rv = NS_GetComponentManager(getter_AddRefs(compMgr)); if (NS_FAILED(rv)){ return self; } nsCOMPtr<nsIAccessibleRetrieval> refp; rv = compMgr->CreateInstanceByContractID(accRetCID, 0, IAR_IID_OLD, getter_AddRefs(refp)); if (NS_FAILED(rv)){ return self; } nsCOMPtr<nsIAccessible> accnode; rv = refp->GetAccessibleFor(node, getter_AddRefs(accnode)); if(NS_FAILED(rv)){ return self; } void *wh = NULL; nsCOMPtr<nsIAccessibleDocument> accdocnode; rv = accnode->QueryInterface(IAD_IID_OLD, getter_AddRefs(accdocnode)); if(NS_FAILED(rv)){ return self; } rv = accdocnode->GetWindowHandle(&wh); if(NS_SUCCEEDED(rv)){ self = static_cast<HWND>(wh); } return self; }
There is good news and bad news. First the good news. Plan B inside Firefox 2
happily runs to completion. All the XPCOM calls succeed, or at least think
they succeed. The bad news is that the thing we get back is not a valid HWND
.
The problem now is not just interface identifier mismatch, but class declaration
mismatch. GetWindowHandle
is the tenth method declared in the
nsIAccessibleDocument.h
that Firefox 2 was built with, but actually the eighth method in the SDK
that I used to build my extension (and hence XPCOM component). Since
these classes don't use vtables this means I'm probably, no I can be more positive, definitely calling the wrong method. If I were
a betting man, I'd hedge a bet on GetAccessibleInParentChain
,
that being the tenth method in the interface declaration in the new SDK.
In actual fact we were lucky we actually got as far as we did, since
the very first interface we made use of has changed substantially. If
there is any truth to the story I weave here, then this is because
GetAccessibleFor
is still the very first method declared in
nsIAccessibleRetrieval.h
. So if you are lucky enough with your
API's this technique may work without further ado.
We are not so lucky.
The Realist's Solution
To make sure we call the right methods of the right interfaces we
need to have two versions of both the nsIAccessibleDocument
and
nsIAccessibleRetrieval
interfaces at our fingertips. I called my
old ones: nsIAccessibleRetrieval_old.h
and nsIAccessibleDocument_old.h
.
Mine date back to August of 2006, which is when I first built the lizard. To get these four files to co-exist together peacefully I had to resort to some preprocessor magic and an ugly hack. Maybe the ugly hack can be replaced by even more preprocessor magic, but not today. Lets do the magic first, then describe the ugly hack.
To include both retrieval interfaces (and remember the old IID without having to cut and paste) I followed the kind advice of Mike Shaver and did:
#define nsIAccessibleRetrieval nsIAccessibleRetrieval_old #include "accessibility/nsIAccessibleRetrieval_old.h" static const nsIID NS_IACCESSIBLERETRIEVAL_IID_OLD = NS_IACCESSIBLERETRIEVAL_IID; #undef nsIAccessibleRetrieval #undef __gen_nsIAccessibleRetrieval_h__ #include "accessibility/nsIAccessibleRetrieval.h"
and following the identical principle for the document interface:
#define nsIAccessibleDocument nsIAccessibleDocument_old #include "accessibility/nsIAccessibleDocument_old.h" static const nsIID NS_IACCESSIBLEDOCUMENT_IID_OLD = NS_IACCESSIBLEDOCUMENT_IID; #undef nsIAccessibleDocument #undef __gen_nsIAccessibleDocument_h__ #include "accessibility/nsIAccessibleDocument.h"
I even silenced my friend the compiler by enclosing both incantations within a compiler pragma:
#pragma warning(push) #pragma warning(disableĀ : 4005) ... #pragma warning(pop)
So now I have to own up to the ugly hack. I did have to delve into my old versions and change:
NS_DEFINE_STATIC_IID_ACCESSOR(...)
to
NS_DECLARE_STATIC_IID_ACCESSOR(...)
This ugliness aside, my plan B routine now looks like:
HWND getHwndB(nsIDOMNode *node){ HWND self = NULL; nsresult rv; nsCOMPtr<nsIComponentManager> compMgr; rv = NS_GetComponentManager(getter_AddRefs(compMgr)); if (NS_FAILED(rv)){ return self; } nsCOMPtr<nsIAccessibleRetrieval_old> refp; //N.B. _old rv = compMgr->CreateInstanceByContractID(accRetCID, 0, NS_IACCESSIBLERETRIEVAL_IID_OLD, getter_AddRefs(refp)); if (NS_FAILED(rv)){ return self; } nsCOMPtr<nsIAccessible> accnode; rv = refp->GetAccessibleFor(node, getter_AddRefs(accnode)); if(NS_FAILED(rv)){ return self; } void *wh = NULL; nsCOMPtr<nsIAccessibleDocument_old> accdocnode; //N.B. _old rv = accnode->QueryInterface(NS_IACCESSIBLEDOCUMENT_IID_OLD, getter_AddRefs(accdocnode)); if(NS_FAILED(rv)){ return self; } rv = accdocnode->GetWindowHandle(&wh); if(NS_SUCCEEDED(rv)){ self = static_cast<HWND>(wh); } return self; }