this.__proto__ = Ci[].createInstance and QueryInterface calling problem

Axel Hecht <[email protected]> Mon, 04 Feb 2008 18:53:44 +0100
Newsgroups gmane.comp.mozilla.devel.rdf
Message-ID <[email protected]>
Asking js-engine for some xpconnect help.

I left all the context in, I guess it's best to read from the bottom up 
for those in js-engine.

Axel

mcben wrote:
> On 4 fév, 15:47, Axel Hecht <[email protected]> wrote:
>> mcben wrote:
>>> On 4 fév, 14:52, Axel Hecht <[email protected]> wrote:
>>>> mcben wrote:
>>>>> On 4 fév, 09:27, Axel Hecht <[email protected]> wrote:
>>>>>> mcben wrote:
>>>>>>> Hi folks,
>>>>>>> Here is quite a long post about a problem with a own datasource
>>>>>>> implementation based on a xml-datasource.
>>>>>>> I'm looking for any clues or links that could help me to achieve my
>>>>>>> first goal : a custom datasource that i could fill from a CSV file.
>>>>>>> Thanks in advance for reading me :)
>>>>>>> I'm creating a javascript component that fetches a CSV file with
>>>>>>> XMLHttpRequest, parses it, then adds lines as RDF resources in a RDF
>>>>>>> datasource.
>>>>>>> Its contractid is like "@mozilla.org/rdf/datasource;1?name=mydatasource"
>>>>>>> so as to get it in the chrome with the "rdf:mydatasource" keyword.
>>>>>>> I started this work by using an in-memory-datasource as the __proto__
>>>>>>> properties of my javascript prototype.
>>>>>>> I could fetch my file and appends data in the datasource : the result
>>>>>>> was shown in a rdf tree.
>>>>>>> The first problem with this design was that i was not able to overwrite
>>>>>>> some nsIRDFDataSource methods (not a real problem in fact), nor extends
>>>>>>> the in-memory-datasource component with other interfaces : the QI method
>>>>>>> of my component was never called, so i got a NS_ERROR_NO_INTERFACE
>>>>>>> error, when i QI another interface (eg: nsIDOMListener to adds
>>>>>>> XMLHttpRequest callback in my chrome).
>>>>>>> Then i took a look a the rdf:localstore C++ implementation
>>>>>>> (nsLocalStore.cpp) and i'm quite happy with its design : it works like a
>>>>>>> wrapper around a xml-datasource. It "justs" load data from
>>>>>>> localstore.rdf in the profile directory or creates it if it does not exist.
>>>>>>> Then with that, I could even read and store a cached version of my CSV
>>>>>>> file on disk.
>>>>>>> So, I've made the same design in my javascript component, all
>>>>>>> nsIRDFDataSource related methods are forwarded to a "mInner"
>>>>>>> xml-datasource property.
>>>>>>> But when i try to call RegisterDataSource to register my datasource in
>>>>>>> the constructor of my component, it throws a NS_ERROR_FAILURE :
>>>>>>> Here is the code :
>>>>>>> this._loadLocalData();
>>>>>>> RDF.RegisterDataSource(this, false);
>>>>>>> this._loadRemoteData();
>>>>>>> If i log "this" the console service returns me that is an Object.
>>>>>>> That's the problem ?
>>>>>>> In fact I get from the chrome : [xpconnect wrapped (nsISupports,
>>>>>>> nsIRDFDataSource, nsIRDFRemoteDataSource)] after the registration, even
>>>>>>> if the registration fails.
>>>>>>> But, the nsIRDFRemoteDatasource Init method is called at every calls
>>>>>>> from chrome.
>>>>>>> Maybe the real problem comes from the xml-datasource initialization.
>>>>>>> The following code is called by the constructor of my component :
>>>>>>> this.mInner = Components
>>>>>>>        .classes["@mozilla.org/rdf/datasource;1?name=xml-datasource"]
>>>>>>>        .createInstance(Components.interfaces.nsIRDFDataSource);
>>>>>>> var remote = this.mInner
>>>>>>>        .QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
>>>>>>> remote.Init(filespec);
>>>>>>> The "Init" call throws an exception with the following code
>>>>>>> "0x804b000a". Its related with nsISocketTransport and means
>>>>>>> STATUS_WAITING_FOR. Ok, why not.
>>>>>>> Then remote.loaded returns false, and calling remote.Refresh(false)
>>>>>>> throws a NS_ERROR_FAILURE
>>>>>>> What is wrong with that ?
>>>>>>> So i catch this Init exception and get my remote datasource with the RDF
>>>>>>> Service like this :
>>>>>>> remote = RDF.GetDataSource(filespec)
>>>>>>>           .QueryInterface(Components.interfaces.nsIRDFRemoteDataSource)
>>>>>>> this.mInner = remote
>>>>>>>           .QueryInterface(Components.interfaces.nsIRDFDataSource);
>>>>>>> In that case no problem with remote ds, but the RegisterDataSource go on
>>>>>>> failing.
>>>>>>> And later, in the chrome, when i call a method or property that is
>>>>>>> forwarded to the inner, i got back a undefined result.
>>>>>>> And i cant look any result in my xul tree.
>>>>>>> I can post the full source code of the component if needed.
>>>>>> I suspect that it has something to do with your move from an in-memory
>>>>>> datasource as inner to an rdf/xml as inner. I didn't get why you made
>>>>>> that move, was that intentional?
>>>>> Hi Axel,
>>>>> Thank you for your answer.
>>>>> To begin, you can grab my component source code here, with the "second
>>>>> design" implementation (with inner):
>>>>> http://pastebin.mozilla.org/319272
>>>>> And the xul tree that should show my results look quite simple :
>>>>> <tree id="itemtypes-tree" flex="1"
>>>>>         datasources="rdf:bi-itemtypes" ref="bi:itemtypes-list">
>>>>>   <treecols>
>>>>>     <treecol id="col-name" primary="true" label="Name" flex="1"/>
>>>>>   </treecols>
>>>>>   <template>
>>>>>     <treechildren>
>>>>>       <treeitem uri="rdf:*">
>>>>>         <treerow>
>>>>>           <treecell label="rdf:*" />
>>>>>         </treerow>
>>>>>       </treeitem>
>>>>>     </treechildren>
>>>>>   </template>
>>>>> </tree>
>>>>> Hum. yes, the move was partially intentional :
>>>>>  * My first try with an in-memory datasource was with the instance set
>>>>> as __proto__ property in the prototype of my component. That is to
>>>>> say, that it was not with the "inner" design.
>>>>> The main problem with this design was that i could not extends my
>>>>> component by implementing other public interface : the QI function of
>>>>> my component was never called, nor any method that i though they
>>>>> overrode existing methods of the in-mem. The only thing that i could
>>>>> improve in this design was the init phase of my component (by filling
>>>>> in-mem with XMLHttpRequest) nothing else.
>>>>> Is it normal that with the following code, the QueryInterface method
>>>>> is never called, that's why i cant implement any other interfaces ? (i
>>>>> think that the __proto__ QI is called instead).
>>>>> function myComponentImplementation() {
>>>>> }
>>>>> myComponentImplementation.prototype {
>>>>>     __proto__ : Components.classes["@mozilla.org/rdf/datasource;1?
>>>>> name=in-memory-datasource"]
>>>>>                          .createInstance(Components.interfaces.nsIRDFDataSource),
>>>>>     QueryInterface : function(iid) {
>>>>>         if (!iid.equals(C_i.nsIDOMEventListener) &&
>>>>>             !iid.equals(C_i.nsIRDFDataSource) &&
>>>>>             !iid.equals(C_i.nsIRDFRemoteDataSource) &&
>>>>>             !iid.equals(C_i.nsISupports))
>>>>>             throw Components.results.NS_ERROR_NO_INTERFACE;
>>>>>         ConsoleService.logStringMessage("QI called:" + iid);
>>>>>         return this;
>>>>>     }
>>>>> }
>>>>>  * With the "inner" design, i immediatly use a xml-datasource, just
>>>>> like the nsLocalStore implementation. With that, i could load a cached
>>>>> version of the rdf file from disk, and then update it with
>>>>> XMLHttpRequest only if it really needed.
>>>>> Since, you are thinking that the problem can come from the xml-
>>>>> datasource, i took the same component, and just replaced the xml-
>>>>> datasource inner, with an in-memory-datasource.
>>>>> Indeed, it seems there is no major failure with initialization, i can
>>>>> register the datasource, but the content that i downloaded with
>>>>> XMLHttpRequest and that i put in the ds is not reflected in my chrome
>>>>> xul tree, nor i can query my datasource for any target that sould
>>>>> exists.
>>>>> More precisely, about any errors, i had to create the container (the
>>>>> one to wich I append downloaded resources) with this.mInner (the in-
>>>>> mem ds) and not my component. Is it normal ?
>>>>> Look:
>>>>> this._container = RDFContainer.MakeSeq(this.mInner,
>>>>> RDF.GetResource(this._struct.ref));
>>>>> Even though my component should implement nsIRDFDataSource, that's why
>>>>> i thought i could do that :
>>>>> this._container = RDFContainer.MakeSeq(this,
>>>>> RDF.GetResource(this._struct.ref));
>>>>> But it seems that append does not work anyway.
>>>>> * Finally, why I made the move to xml-datasource ?
>>>>> (I thought) I could parse a local rdf file at init, and flushes the
>>>>> modified results at destruction. I even could observe the download
>>>>> progress from chrome (eg: with a progressbar), etc.
>>>>> So any clue ?
>>>>> What do you think is the best implementation ?
>>>>> The first design could be perfect if i could override and extends
>>>>> methods and interfaces of the __proto__ instance.
>>>>> Thank you for reading me.
>>>> I suspect that something in your implementation is broken. There ishttp://developer.mozilla.org/en/docs/Aggregating_the_In-Memory_Dataso...,
>>>> but that hasn't been maintained in years, same goes for its sibling
>>>> documents.
>>> Yes, i know this document, and tried some months ago to agregate a xml-
>>> datasource (for another project) and gave up when i saw that xml-
>>> datasource cant be aggregated ;-)
>>> Maybe i should give another try with the in-mem datasource.
>>> But can you confirm me that i could try that with a javascript
>>> implementation ?
>>>> Sorry, no better idea, maybe if you post parts of your code there's
>>>> better chances.
>>> Héhé. The whole component is too long / time consuming for you to take
>>> a look ?
>>> I understand.
>>> So first question, that i previously asked. WIth a component
>>> implementation below, is it normal that I cant override public methods
>>> of the __proto__. In fact, I can only add private method, that cant be
>>> accessed by the chrome ?
>>> function myComponentImplementation() {
>>> }
>>> myComponentImplementation.prototype {
>>>     __proto__ : Components.classes["@mozilla.org/rdf/datasource;1?
>>> name=in-memory-datasource"]
>>>                          .createInstance(Components.interfaces.nsIRDFDataSource),
>>>     QueryInterface : function(iid) {
>>>         if (!iid.equals(C_i.nsIDOMEventListener) &&
>>>             !iid.equals(C_i.nsIRDFDataSource) &&
>>>             !iid.equals(C_i.nsIRDFRemoteDataSource) &&
>>>             !iid.equals(C_i.nsISupports))
>>>             throw Components.results.NS_ERROR_NO_INTERFACE;
>>>         ConsoleService.logStringMessage("QI called:" + iid);
>>>         return this;
>>>     }
>>> }
>> That is more of a js/xpconnect question. Trying
>>
>> function A(){this.bar=3;}
>> function B(){this.__proto__ = new A();this.foo=6};
>> b=new B()
>> [object Object]
>> props(b)
>> Fields: foo
>> Fields of prototype: bar
>> Methods of prototype of prototype of prototype: extend
>> b.bar
>> 3
>> b.foo
>> 6
>>
>> makes me think that you should set the __proto__ to a new instance of
>> the in-mem ds in the Constructor. Which makes sense, as you don't want
>> to share the in-mem ds across all instances of
>> myComponentImplementation, right?
> 
> Good point.
> 
> I just tried that :
> 
> function myDataSourceImplementation(struct)
> {
>     this.__proto__ = Components.classes["@mozilla.org/rdf/datasource;1?
> name=in-memory-datasource"]
>                          .createInstance(Components.interfaces.nsIRDFDataSource);
> 
>     this.init = function() {
>        // load data with XMLHttpRequest
>     };
> 
>    this.QueryInterface = function(iid) {
>         ConsoleService.logStringMessage("QI called:" + iid);
>         if (!iid.equals(Components.interfaces.nsIDOMEventListener) &&
>             !iid.equals(Components.interfaces.nsIRDFDataSource) &&
>             !iid.equals(Components.interfaces.nsIRDFRemoteDataSource)
> &&
>             !iid.equals(Components.interfaces.nsISupports))
>             throw Components.results.NS_ERROR_NO_INTERFACE;
>         return this;
>     };
> 
>     this.init();
> 
> }
> 
> As you can see, i'm compelled to binds new methods in the constructor
> since, the prototype is overriden by "this.__proto__". Not a real
> problem.
> Code works (i see the data in the chrome).
> 
> But my new QI is never called, like before, and prevents me from
> implementing other interfaces :-(
> 
>> But I guess for real questions on this, you should try .js-engine.
> 
> Maybe.
> This evening, I'll try XPCOM aggregation (like in "Aggregating In
> Memory Datasource"), and will ask .js-engine for any clues.
> I'll post back here any progress about this subject.
> 
> Thanks for your time.