1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * David Hyatt <hyatt@netscape.com> (Original Author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsXBLProtoImpl.h"
40 : #include "nsIContent.h"
41 : #include "nsIDocument.h"
42 : #include "nsContentUtils.h"
43 : #include "nsIScriptGlobalObject.h"
44 : #include "nsIScriptGlobalObjectOwner.h"
45 : #include "nsIScriptContext.h"
46 : #include "nsIXPConnect.h"
47 : #include "nsIServiceManager.h"
48 : #include "nsIDOMNode.h"
49 : #include "nsXBLPrototypeBinding.h"
50 : #include "nsXBLProtoImplProperty.h"
51 :
52 : // Checks that the version is not modified in a given scope.
53 : class AutoVersionChecker
54 : {
55 : JSContext * const cx;
56 : JSVersion versionBefore;
57 :
58 : public:
59 0 : explicit AutoVersionChecker(JSContext *cx) : cx(cx) {
60 : #ifdef DEBUG
61 0 : versionBefore = JS_GetVersion(cx);
62 : #endif
63 0 : }
64 :
65 0 : ~AutoVersionChecker() {
66 : #ifdef DEBUG
67 0 : JSVersion versionAfter = JS_GetVersion(cx);
68 0 : NS_ABORT_IF_FALSE(versionAfter == versionBefore, "version must not change");
69 : #endif
70 0 : }
71 : };
72 :
73 : nsresult
74 0 : nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIContent* aBoundElement)
75 : {
76 : // This function is called to install a concrete implementation on a bound element using
77 : // this prototype implementation as a guide. The prototype implementation is compiled lazily,
78 : // so for the first bound element that needs a concrete implementation, we also build the
79 : // prototype implementation.
80 0 : if (!mMembers && !mFields) // Constructor and destructor also live in mMembers
81 0 : return NS_OK; // Nothing to do, so let's not waste time.
82 :
83 : // If the way this gets the script context changes, fix
84 : // nsXBLProtoImplAnonymousMethod::Execute
85 0 : nsIDocument* document = aBoundElement->OwnerDoc();
86 :
87 0 : nsIScriptGlobalObject *global = document->GetScopeObject();
88 0 : if (!global) return NS_OK;
89 :
90 0 : nsCOMPtr<nsIScriptContext> context = global->GetContext();
91 0 : if (!context) return NS_OK;
92 :
93 : // InitTarget objects gives us back the JS object that represents the bound element and the
94 : // class object in the bound document that represents the concrete version of this implementation.
95 : // This function also has the side effect of building up the prototype implementation if it has
96 : // not been built already.
97 0 : nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
98 0 : JSObject* targetClassObject = nsnull;
99 : nsresult rv = InitTargetObjects(aBinding, context, aBoundElement,
100 0 : getter_AddRefs(holder), &targetClassObject);
101 0 : NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
102 :
103 : JSObject * targetScriptObject;
104 0 : holder->GetJSObject(&targetScriptObject);
105 :
106 0 : JSContext *cx = context->GetNativeContext();
107 :
108 0 : AutoVersionChecker avc(cx);
109 :
110 : // Walk our member list and install each one in turn.
111 0 : for (nsXBLProtoImplMember* curr = mMembers;
112 : curr;
113 : curr = curr->GetNext())
114 : curr->InstallMember(context, aBoundElement, targetScriptObject,
115 0 : targetClassObject, mClassName);
116 :
117 0 : return NS_OK;
118 : }
119 :
120 : nsresult
121 0 : nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
122 : nsIScriptContext* aContext,
123 : nsIContent* aBoundElement,
124 : nsIXPConnectJSObjectHolder** aScriptObjectHolder,
125 : JSObject** aTargetClassObject)
126 : {
127 0 : nsresult rv = NS_OK;
128 0 : *aScriptObjectHolder = nsnull;
129 :
130 0 : if (!mClassObject) {
131 0 : rv = CompilePrototypeMembers(aBinding); // This is the first time we've ever installed this binding on an element.
132 : // We need to go ahead and compile all methods and properties on a class
133 : // in our prototype binding.
134 0 : if (NS_FAILED(rv))
135 0 : return rv;
136 :
137 0 : if (!mClassObject)
138 0 : return NS_OK; // This can be ok, if all we've got are fields (and no methods/properties).
139 : }
140 :
141 0 : nsIDocument *ownerDoc = aBoundElement->OwnerDoc();
142 : nsIScriptGlobalObject *sgo;
143 :
144 0 : if (!(sgo = ownerDoc->GetScopeObject())) {
145 0 : return NS_ERROR_UNEXPECTED;
146 : }
147 :
148 : // Because our prototype implementation has a class, we need to build up a corresponding
149 : // class for the concrete implementation in the bound document.
150 0 : JSContext* jscontext = aContext->GetNativeContext();
151 0 : JSObject* global = sgo->GetGlobalJSObject();
152 0 : nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
153 : jsval v;
154 : rv = nsContentUtils::WrapNative(jscontext, global, aBoundElement, &v,
155 0 : getter_AddRefs(wrapper));
156 0 : NS_ENSURE_SUCCESS(rv, rv);
157 :
158 : // All of the above code was just obtaining the bound element's script object and its immediate
159 : // concrete base class. We need to alter the object so that our concrete class is interposed
160 : // between the object and its base class. We become the new base class of the object, and the
161 : // object's old base class becomes the new class' base class.
162 : rv = aBinding->InitClass(mClassName, jscontext, global, JSVAL_TO_OBJECT(v),
163 0 : aTargetClassObject);
164 0 : if (NS_FAILED(rv)) {
165 0 : return rv;
166 : }
167 :
168 0 : nsContentUtils::PreserveWrapper(aBoundElement, aBoundElement);
169 :
170 0 : wrapper.swap(*aScriptObjectHolder);
171 :
172 0 : return rv;
173 : }
174 :
175 : nsresult
176 0 : nsXBLProtoImpl::CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding)
177 : {
178 : // We want to pre-compile our implementation's members against a "prototype context". Then when we actually
179 : // bind the prototype to a real xbl instance, we'll clone the pre-compiled JS into the real instance's
180 : // context.
181 : nsCOMPtr<nsIScriptGlobalObjectOwner> globalOwner(
182 0 : do_QueryObject(aBinding->XBLDocumentInfo()));
183 0 : nsIScriptGlobalObject* globalObject = globalOwner->GetScriptGlobalObject();
184 0 : NS_ENSURE_TRUE(globalObject, NS_ERROR_UNEXPECTED);
185 :
186 0 : nsIScriptContext *context = globalObject->GetContext();
187 0 : NS_ENSURE_TRUE(context, NS_ERROR_OUT_OF_MEMORY);
188 :
189 0 : JSContext *cx = context->GetNativeContext();
190 0 : JSObject *global = globalObject->GetGlobalJSObject();
191 :
192 :
193 : JSObject* classObject;
194 : nsresult rv = aBinding->InitClass(mClassName, cx, global, global,
195 0 : &classObject);
196 0 : if (NS_FAILED(rv))
197 0 : return rv;
198 :
199 0 : mClassObject = classObject;
200 0 : if (!mClassObject)
201 0 : return NS_ERROR_FAILURE;
202 :
203 0 : AutoVersionChecker avc(cx);
204 :
205 : // Now that we have a class object installed, we walk our member list and compile each of our
206 : // properties and methods in turn.
207 0 : for (nsXBLProtoImplMember* curr = mMembers;
208 : curr;
209 : curr = curr->GetNext()) {
210 0 : nsresult rv = curr->CompileMember(context, mClassName, mClassObject);
211 0 : if (NS_FAILED(rv)) {
212 0 : DestroyMembers();
213 0 : return rv;
214 : }
215 : }
216 :
217 0 : return NS_OK;
218 : }
219 :
220 : void
221 0 : nsXBLProtoImpl::Trace(TraceCallback aCallback, void *aClosure) const
222 : {
223 : // If we don't have a class object then we either didn't compile members
224 : // or we only have fields, in both cases there are no cycles through our
225 : // members.
226 0 : if (!mClassObject) {
227 0 : return;
228 : }
229 :
230 : nsXBLProtoImplMember *member;
231 0 : for (member = mMembers; member; member = member->GetNext()) {
232 0 : member->Trace(aCallback, aClosure);
233 : }
234 : }
235 :
236 : void
237 0 : nsXBLProtoImpl::UnlinkJSObjects()
238 : {
239 0 : if (mClassObject) {
240 0 : DestroyMembers();
241 : }
242 0 : }
243 :
244 : nsXBLProtoImplField*
245 0 : nsXBLProtoImpl::FindField(const nsString& aFieldName) const
246 : {
247 0 : for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
248 0 : if (aFieldName.Equals(f->GetName())) {
249 0 : return f;
250 : }
251 : }
252 :
253 0 : return nsnull;
254 : }
255 :
256 : bool
257 0 : nsXBLProtoImpl::ResolveAllFields(JSContext *cx, JSObject *obj) const
258 : {
259 0 : AutoVersionChecker avc(cx);
260 0 : for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
261 : // Using OBJ_LOOKUP_PROPERTY is a pain, since what we have is a
262 : // PRUnichar* for the property name. Let's just use the public API and
263 : // all.
264 0 : nsDependentString name(f->GetName());
265 : jsval dummy;
266 0 : if (!::JS_LookupUCProperty(cx, obj,
267 0 : reinterpret_cast<const jschar*>(name.get()),
268 0 : name.Length(), &dummy)) {
269 0 : return false;
270 : }
271 : }
272 :
273 0 : return true;
274 : }
275 :
276 : void
277 0 : nsXBLProtoImpl::UndefineFields(JSContext *cx, JSObject *obj) const
278 : {
279 0 : JSAutoRequest ar(cx);
280 0 : for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
281 0 : nsDependentString name(f->GetName());
282 :
283 0 : const jschar* s = reinterpret_cast<const jschar*>(name.get());
284 : JSBool hasProp;
285 0 : if (::JS_AlreadyHasOwnUCProperty(cx, obj, s, name.Length(), &hasProp) &&
286 : hasProp) {
287 : jsval dummy;
288 0 : ::JS_DeleteUCProperty2(cx, obj, s, name.Length(), &dummy);
289 : }
290 : }
291 0 : }
292 :
293 : void
294 0 : nsXBLProtoImpl::DestroyMembers()
295 : {
296 0 : NS_ASSERTION(mClassObject, "This should never be called when there is no class object");
297 :
298 0 : delete mMembers;
299 0 : mMembers = nsnull;
300 0 : mConstructor = nsnull;
301 0 : mDestructor = nsnull;
302 0 : }
303 :
304 : nsresult
305 0 : nsXBLProtoImpl::Read(nsIScriptContext* aContext,
306 : nsIObjectInputStream* aStream,
307 : nsXBLPrototypeBinding* aBinding,
308 : nsIScriptGlobalObject* aGlobal)
309 : {
310 : // Set up a class object first so that deserialization is possible
311 0 : JSContext *cx = aContext->GetNativeContext();
312 0 : JSObject *global = aGlobal->GetGlobalJSObject();
313 :
314 : JSObject* classObject;
315 0 : nsresult rv = aBinding->InitClass(mClassName, cx, global, global, &classObject);
316 0 : NS_ENSURE_SUCCESS(rv, rv);
317 0 : NS_ENSURE_TRUE(classObject, NS_ERROR_FAILURE);
318 :
319 0 : mClassObject = classObject;
320 :
321 0 : nsXBLProtoImplField* previousField = nsnull;
322 0 : nsXBLProtoImplMember* previousMember = nsnull;
323 :
324 0 : do {
325 : XBLBindingSerializeDetails type;
326 0 : rv = aStream->Read8(&type);
327 0 : NS_ENSURE_SUCCESS(rv, rv);
328 0 : if (type == XBLBinding_Serialize_NoMoreItems)
329 : break;
330 :
331 0 : switch (type & XBLBinding_Serialize_Mask) {
332 : case XBLBinding_Serialize_Field:
333 : {
334 : nsXBLProtoImplField* field =
335 0 : new nsXBLProtoImplField(type & XBLBinding_Serialize_ReadOnly);
336 0 : rv = field->Read(aContext, aStream);
337 0 : if (NS_FAILED(rv)) {
338 0 : delete field;
339 0 : return rv;
340 : }
341 :
342 0 : if (previousField) {
343 0 : previousField->SetNext(field);
344 : }
345 : else {
346 0 : mFields = field;
347 : }
348 0 : previousField = field;
349 :
350 0 : break;
351 : }
352 : case XBLBinding_Serialize_GetterProperty:
353 : case XBLBinding_Serialize_SetterProperty:
354 : case XBLBinding_Serialize_GetterSetterProperty:
355 : {
356 0 : nsAutoString name;
357 0 : nsresult rv = aStream->ReadString(name);
358 0 : NS_ENSURE_SUCCESS(rv, rv);
359 :
360 : nsXBLProtoImplProperty* prop =
361 0 : new nsXBLProtoImplProperty(name.get(), type & XBLBinding_Serialize_ReadOnly);
362 0 : rv = prop->Read(aContext, aStream, type & XBLBinding_Serialize_Mask);
363 0 : if (NS_FAILED(rv)) {
364 0 : delete prop;
365 0 : return rv;
366 : }
367 :
368 0 : previousMember = AddMember(prop, previousMember);
369 0 : break;
370 : }
371 : case XBLBinding_Serialize_Method:
372 : {
373 0 : nsAutoString name;
374 0 : rv = aStream->ReadString(name);
375 0 : NS_ENSURE_SUCCESS(rv, rv);
376 :
377 0 : nsXBLProtoImplMethod* method = new nsXBLProtoImplMethod(name.get());
378 0 : rv = method->Read(aContext, aStream);
379 0 : if (NS_FAILED(rv)) {
380 0 : delete method;
381 0 : return rv;
382 : }
383 :
384 0 : previousMember = AddMember(method, previousMember);
385 0 : break;
386 : }
387 : case XBLBinding_Serialize_Constructor:
388 : {
389 0 : mConstructor = new nsXBLProtoImplAnonymousMethod();
390 0 : rv = mConstructor->Read(aContext, aStream);
391 0 : if (NS_FAILED(rv)) {
392 0 : delete mConstructor;
393 0 : mConstructor = nsnull;
394 0 : return rv;
395 : }
396 :
397 0 : previousMember = AddMember(mConstructor, previousMember);
398 0 : break;
399 : }
400 : case XBLBinding_Serialize_Destructor:
401 : {
402 0 : mDestructor = new nsXBLProtoImplAnonymousMethod();
403 0 : rv = mDestructor->Read(aContext, aStream);
404 0 : if (NS_FAILED(rv)) {
405 0 : delete mDestructor;
406 0 : mDestructor = nsnull;
407 0 : return rv;
408 : }
409 :
410 0 : previousMember = AddMember(mDestructor, previousMember);
411 0 : break;
412 : }
413 : default:
414 0 : NS_ERROR("Unexpected binding member type");
415 0 : break;
416 : }
417 : } while (1);
418 :
419 0 : return NS_OK;
420 : }
421 :
422 : nsresult
423 0 : nsXBLProtoImpl::Write(nsIScriptContext* aContext,
424 : nsIObjectOutputStream* aStream,
425 : nsXBLPrototypeBinding* aBinding)
426 : {
427 : nsresult rv;
428 :
429 0 : if (!mClassObject) {
430 0 : rv = CompilePrototypeMembers(aBinding);
431 0 : NS_ENSURE_SUCCESS(rv, rv);
432 : }
433 :
434 0 : rv = aStream->WriteStringZ(mClassName.get());
435 0 : NS_ENSURE_SUCCESS(rv, rv);
436 :
437 0 : for (nsXBLProtoImplField* curr = mFields; curr; curr = curr->GetNext()) {
438 0 : rv = curr->Write(aContext, aStream);
439 0 : NS_ENSURE_SUCCESS(rv, rv);
440 : }
441 0 : for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
442 0 : if (curr == mConstructor) {
443 0 : rv = mConstructor->Write(aContext, aStream, XBLBinding_Serialize_Constructor);
444 : }
445 0 : else if (curr == mDestructor) {
446 0 : rv = mDestructor->Write(aContext, aStream, XBLBinding_Serialize_Destructor);
447 : }
448 : else {
449 0 : rv = curr->Write(aContext, aStream);
450 : }
451 0 : NS_ENSURE_SUCCESS(rv, rv);
452 : }
453 :
454 0 : return aStream->Write8(XBLBinding_Serialize_NoMoreItems);
455 : }
456 :
457 : nsresult
458 0 : NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding,
459 : const PRUnichar* aClassName,
460 : nsXBLProtoImpl** aResult)
461 : {
462 0 : nsXBLProtoImpl* impl = new nsXBLProtoImpl();
463 0 : if (!impl)
464 0 : return NS_ERROR_OUT_OF_MEMORY;
465 0 : if (aClassName)
466 0 : impl->mClassName.AssignWithConversion(aClassName);
467 : else
468 0 : aBinding->BindingURI()->GetSpec(impl->mClassName);
469 0 : aBinding->SetImplementation(impl);
470 0 : *aResult = impl;
471 :
472 0 : return NS_OK;
473 : }
474 :
|