dom/xbl/nsXBLProtoImpl.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "mozilla/DebugOnly.h"
     8 #include "nsXBLProtoImpl.h"
     9 #include "nsIContent.h"
    10 #include "nsIDocument.h"
    11 #include "nsContentUtils.h"
    12 #include "nsCxPusher.h"
    13 #include "nsIScriptGlobalObject.h"
    14 #include "nsIScriptContext.h"
    15 #include "nsIXPConnect.h"
    16 #include "nsIServiceManager.h"
    17 #include "nsIDOMNode.h"
    18 #include "nsXBLPrototypeBinding.h"
    19 #include "nsXBLProtoImplProperty.h"
    20 #include "nsIURI.h"
    21 #include "mozilla/dom/XULElementBinding.h"
    22 #include "xpcpublic.h"
    23 #include "js/CharacterEncoding.h"
    25 using namespace mozilla;
    26 using js::GetGlobalForObjectCrossCompartment;
    27 using js::AssertSameCompartment;
    29 nsresult
    30 nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding,
    31                                       nsXBLBinding* aBinding)
    32 {
    33   // This function is called to install a concrete implementation on a bound element using
    34   // this prototype implementation as a guide.  The prototype implementation is compiled lazily,
    35   // so for the first bound element that needs a concrete implementation, we also build the
    36   // prototype implementation.
    37   if (!mMembers && !mFields)  // Constructor and destructor also live in mMembers
    38     return NS_OK; // Nothing to do, so let's not waste time.
    40   // If the way this gets the script context changes, fix
    41   // nsXBLProtoImplAnonymousMethod::Execute
    42   nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc();
    44   nsCOMPtr<nsIScriptGlobalObject> global =  do_QueryInterface(document->GetScopeObject());
    45   if (!global) return NS_OK;
    47   nsCOMPtr<nsIScriptContext> context = global->GetContext();
    48   if (!context) return NS_OK;
    49   JSContext* cx = context->GetNativeContext();
    50   AutoCxPusher pusher(cx);
    52   // InitTarget objects gives us back the JS object that represents the bound element and the
    53   // class object in the bound document that represents the concrete version of this implementation.
    54   // This function also has the side effect of building up the prototype implementation if it has
    55   // not been built already.
    56   JS::Rooted<JSObject*> targetClassObject(cx, nullptr);
    57   bool targetObjectIsNew = false;
    58   nsresult rv = InitTargetObjects(aPrototypeBinding,
    59                                   aBinding->GetBoundElement(),
    60                                   &targetClassObject,
    61                                   &targetObjectIsNew);
    62   NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
    63   MOZ_ASSERT(targetClassObject);
    65   // If the prototype already existed, we don't need to install anything. return early.
    66   if (!targetObjectIsNew)
    67     return NS_OK;
    69   // We want to define the canonical set of members in a safe place. If we're
    70   // using a separate XBL scope, we want to define them there first (so that
    71   // they'll be available for Xray lookups, among other things), and then copy
    72   // the properties to the content-side prototype as needed. We don't need to
    73   // bother about the field accessors here, since we don't use/support those
    74   // for in-content bindings.
    76   // First, start by entering the compartment of the XBL scope. This may or may
    77   // not be the same compartment as globalObject.
    78   JS::Rooted<JSObject*> globalObject(cx,
    79     GetGlobalForObjectCrossCompartment(targetClassObject));
    80   JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject));
    81   NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
    82   JSAutoCompartment ac(cx, scopeObject);
    84   // If they're different, create our safe holder object in the XBL scope.
    85   JS::Rooted<JSObject*> propertyHolder(cx);
    86   if (scopeObject != globalObject) {
    88     // This is just a property holder, so it doesn't need any special JSClass.
    89     propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scopeObject);
    90     NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY);
    92     // Define it as a property on the scopeObject, using the same name used on
    93     // the content side.
    94     bool ok = JS_DefineProperty(cx, scopeObject, aPrototypeBinding->ClassName().get(),
    95                                 propertyHolder, JSPROP_PERMANENT | JSPROP_READONLY,
    96                                 JS_PropertyStub, JS_StrictPropertyStub);
    97     NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
    98   } else {
    99     propertyHolder = targetClassObject;
   100   }
   102   // Walk our member list and install each one in turn on the XBL scope object.
   103   for (nsXBLProtoImplMember* curr = mMembers;
   104        curr;
   105        curr = curr->GetNext())
   106     curr->InstallMember(cx, propertyHolder);
   108   // Now, if we're using a separate XBL scope, enter the compartment of the
   109   // bound node and copy exposable properties to the prototype there. This
   110   // rewraps them appropriately, which should result in cross-compartment
   111   // function wrappers.
   112   if (propertyHolder != targetClassObject) {
   113     AssertSameCompartment(propertyHolder, scopeObject);
   114     AssertSameCompartment(targetClassObject, globalObject);
   115     for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
   116       if (curr->ShouldExposeToUntrustedContent()) {
   117         JS::Rooted<jsid> id(cx);
   118         JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName()));
   119         bool ok = JS_CharsToId(cx, chars, &id);
   120         NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
   121         JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder);
   122         NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
   123       }
   124     }
   125   }
   127   // From here on out, work in the scope of the bound element.
   128   JSAutoCompartment ac2(cx, targetClassObject);
   130   // Install all of our field accessors.
   131   for (nsXBLProtoImplField* curr = mFields;
   132        curr;
   133        curr = curr->GetNext())
   134     curr->InstallAccessors(cx, targetClassObject);
   136   return NS_OK;
   137 }
   139 nsresult 
   140 nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
   141                                   nsIContent* aBoundElement, 
   142                                   JS::MutableHandle<JSObject*> aTargetClassObject,
   143                                   bool* aTargetIsNew)
   144 {
   145   nsresult rv = NS_OK;
   147   if (!mPrecompiledMemberHolder) {
   148     rv = CompilePrototypeMembers(aBinding); // This is the first time we've ever installed this binding on an element.
   149                                  // We need to go ahead and compile all methods and properties on a class
   150                                  // in our prototype binding.
   151     if (NS_FAILED(rv))
   152       return rv;
   154     MOZ_ASSERT(mPrecompiledMemberHolder);
   155   }
   157   nsIDocument *ownerDoc = aBoundElement->OwnerDoc();
   158   nsIGlobalObject *sgo;
   160   if (!(sgo = ownerDoc->GetScopeObject())) {
   161     return NS_ERROR_UNEXPECTED;
   162   }
   164   // Because our prototype implementation has a class, we need to build up a corresponding
   165   // class for the concrete implementation in the bound document.
   166   AutoJSContext cx;
   167   JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject());
   168   JS::Rooted<JS::Value> v(cx);
   170   JSAutoCompartment ac(cx, global);
   171   // Make sure the interface object is created before the prototype object
   172   // so that XULElement is hidden from content. See bug 909340.
   173   bool defineOnGlobal = dom::XULElementBinding::ConstructorEnabled(cx, global);
   174   dom::XULElementBinding::GetConstructorObject(cx, global, defineOnGlobal);
   176   rv = nsContentUtils::WrapNative(cx, aBoundElement, &v,
   177                                   /* aAllowWrapping = */ false);
   178   NS_ENSURE_SUCCESS(rv, rv);
   180   JS::Rooted<JSObject*> value(cx, &v.toObject());
   181   JSAutoCompartment ac2(cx, value);
   183   // All of the above code was just obtaining the bound element's script object and its immediate
   184   // concrete base class.  We need to alter the object so that our concrete class is interposed
   185   // between the object and its base class.  We become the new base class of the object, and the
   186   // object's old base class becomes the new class' base class.
   187   rv = aBinding->InitClass(mClassName, cx, value, aTargetClassObject, aTargetIsNew);
   188   if (NS_FAILED(rv)) {
   189     return rv;
   190   }
   192   aBoundElement->PreserveWrapper(aBoundElement);
   194   return rv;
   195 }
   197 nsresult
   198 nsXBLProtoImpl::CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding)
   199 {
   200   // We want to pre-compile our implementation's members against a "prototype context". Then when we actually 
   201   // bind the prototype to a real xbl instance, we'll clone the pre-compiled JS into the real instance's 
   202   // context.
   203   AutoSafeJSContext cx;
   204   JS::Rooted<JSObject*> compilationGlobal(cx, xpc::GetCompilationScope());
   205   NS_ENSURE_TRUE(compilationGlobal, NS_ERROR_UNEXPECTED);
   206   JSAutoCompartment ac(cx, compilationGlobal);
   208   mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), compilationGlobal);
   209   if (!mPrecompiledMemberHolder)
   210     return NS_ERROR_OUT_OF_MEMORY;
   212   // Now that we have a class object installed, we walk our member list and compile each of our
   213   // properties and methods in turn.
   214   JS::Rooted<JSObject*> rootedHolder(cx, mPrecompiledMemberHolder);
   215   for (nsXBLProtoImplMember* curr = mMembers;
   216        curr;
   217        curr = curr->GetNext()) {
   218     nsresult rv = curr->CompileMember(mClassName, rootedHolder);
   219     if (NS_FAILED(rv)) {
   220       DestroyMembers();
   221       return rv;
   222     }
   223   }
   225   return NS_OK;
   226 }
   228 bool
   229 nsXBLProtoImpl::LookupMember(JSContext* aCx, nsString& aName,
   230                              JS::Handle<jsid> aNameAsId,
   231                              JS::MutableHandle<JSPropertyDescriptor> aDesc,
   232                              JS::Handle<JSObject*> aClassObject)
   233 {
   234   for (nsXBLProtoImplMember* m = mMembers; m; m = m->GetNext()) {
   235     if (aName.Equals(m->GetName())) {
   236       return JS_GetPropertyDescriptorById(aCx, aClassObject, aNameAsId, aDesc);
   237     }
   238   }
   239   return true;
   240 }
   242 void
   243 nsXBLProtoImpl::Trace(const TraceCallbacks& aCallbacks, void *aClosure)
   244 {
   245   // If we don't have a class object then we either didn't compile members
   246   // or we only have fields, in both cases there are no cycles through our
   247   // members.
   248   if (!mPrecompiledMemberHolder) {
   249     return;
   250   }
   252   nsXBLProtoImplMember *member;
   253   for (member = mMembers; member; member = member->GetNext()) {
   254     member->Trace(aCallbacks, aClosure);
   255   }
   256 }
   258 void
   259 nsXBLProtoImpl::UnlinkJSObjects()
   260 {
   261   if (mPrecompiledMemberHolder) {
   262     DestroyMembers();
   263   }
   264 }
   266 nsXBLProtoImplField*
   267 nsXBLProtoImpl::FindField(const nsString& aFieldName) const
   268 {
   269   for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
   270     if (aFieldName.Equals(f->GetName())) {
   271       return f;
   272     }
   273   }
   275   return nullptr;
   276 }
   278 bool
   279 nsXBLProtoImpl::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const
   280 {
   281   for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
   282     // Using OBJ_LOOKUP_PROPERTY is a pain, since what we have is a
   283     // char16_t* for the property name.  Let's just use the public API and
   284     // all.
   285     nsDependentString name(f->GetName());
   286     JS::Rooted<JS::Value> dummy(cx);
   287     if (!::JS_LookupUCProperty(cx, obj, name.get(), name.Length(), &dummy)) {
   288       return false;
   289     }
   290   }
   292   return true;
   293 }
   295 void
   296 nsXBLProtoImpl::UndefineFields(JSContext *cx, JS::Handle<JSObject*> obj) const
   297 {
   298   JSAutoRequest ar(cx);
   299   for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
   300     nsDependentString name(f->GetName());
   302     const jschar* s = name.get();
   303     bool hasProp;
   304     if (::JS_AlreadyHasOwnUCProperty(cx, obj, s, name.Length(), &hasProp) &&
   305         hasProp) {
   306       bool dummy;
   307       ::JS_DeleteUCProperty2(cx, obj, s, name.Length(), &dummy);
   308     }
   309   }
   310 }
   312 void
   313 nsXBLProtoImpl::DestroyMembers()
   314 {
   315   MOZ_ASSERT(mPrecompiledMemberHolder);
   317   delete mMembers;
   318   mMembers = nullptr;
   319   mConstructor = nullptr;
   320   mDestructor = nullptr;
   321 }
   323 nsresult
   324 nsXBLProtoImpl::Read(nsIObjectInputStream* aStream,
   325                      nsXBLPrototypeBinding* aBinding)
   326 {
   327   AssertInCompilationScope();
   328   AutoJSContext cx;
   329   // Set up a class object first so that deserialization is possible
   330   JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
   331   mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), global);
   332   if (!mPrecompiledMemberHolder)
   333     return NS_ERROR_OUT_OF_MEMORY;
   335   nsXBLProtoImplField* previousField = nullptr;
   336   nsXBLProtoImplMember* previousMember = nullptr;
   338   do {
   339     XBLBindingSerializeDetails type;
   340     nsresult rv = aStream->Read8(&type);
   341     NS_ENSURE_SUCCESS(rv, rv);
   342     if (type == XBLBinding_Serialize_NoMoreItems)
   343       break;
   345     switch (type & XBLBinding_Serialize_Mask) {
   346       case XBLBinding_Serialize_Field:
   347       {
   348         nsXBLProtoImplField* field =
   349           new nsXBLProtoImplField(type & XBLBinding_Serialize_ReadOnly);
   350         rv = field->Read(aStream);
   351         if (NS_FAILED(rv)) {
   352           delete field;
   353           return rv;
   354         }
   356         if (previousField) {
   357           previousField->SetNext(field);
   358         }
   359         else {
   360           mFields = field;
   361         }
   362         previousField = field;
   364         break;
   365       }
   366       case XBLBinding_Serialize_GetterProperty:
   367       case XBLBinding_Serialize_SetterProperty:
   368       case XBLBinding_Serialize_GetterSetterProperty:
   369       {
   370         nsAutoString name;
   371         nsresult rv = aStream->ReadString(name);
   372         NS_ENSURE_SUCCESS(rv, rv);
   374         nsXBLProtoImplProperty* prop =
   375           new nsXBLProtoImplProperty(name.get(), type & XBLBinding_Serialize_ReadOnly);
   376         rv = prop->Read(aStream, type & XBLBinding_Serialize_Mask);
   377         if (NS_FAILED(rv)) {
   378           delete prop;
   379           return rv;
   380         }
   382         previousMember = AddMember(prop, previousMember);
   383         break;
   384       }
   385       case XBLBinding_Serialize_Method:
   386       {
   387         nsAutoString name;
   388         rv = aStream->ReadString(name);
   389         NS_ENSURE_SUCCESS(rv, rv);
   391         nsXBLProtoImplMethod* method = new nsXBLProtoImplMethod(name.get());
   392         rv = method->Read(aStream);
   393         if (NS_FAILED(rv)) {
   394           delete method;
   395           return rv;
   396         }
   398         previousMember = AddMember(method, previousMember);
   399         break;
   400       }
   401       case XBLBinding_Serialize_Constructor:
   402       {
   403         nsAutoString name;
   404         rv = aStream->ReadString(name);
   405         NS_ENSURE_SUCCESS(rv, rv);
   407         mConstructor = new nsXBLProtoImplAnonymousMethod(name.get());
   408         rv = mConstructor->Read(aStream);
   409         if (NS_FAILED(rv)) {
   410           delete mConstructor;
   411           mConstructor = nullptr;
   412           return rv;
   413         }
   415         previousMember = AddMember(mConstructor, previousMember);
   416         break;
   417       }
   418       case XBLBinding_Serialize_Destructor:
   419       {
   420         nsAutoString name;
   421         rv = aStream->ReadString(name);
   422         NS_ENSURE_SUCCESS(rv, rv);
   424         mDestructor = new nsXBLProtoImplAnonymousMethod(name.get());
   425         rv = mDestructor->Read(aStream);
   426         if (NS_FAILED(rv)) {
   427           delete mDestructor;
   428           mDestructor = nullptr;
   429           return rv;
   430         }
   432         previousMember = AddMember(mDestructor, previousMember);
   433         break;
   434       }
   435       default:
   436         NS_ERROR("Unexpected binding member type");
   437         break;
   438     }
   439   } while (1);
   441   return NS_OK;
   442 }
   444 nsresult
   445 nsXBLProtoImpl::Write(nsIObjectOutputStream* aStream,
   446                       nsXBLPrototypeBinding* aBinding)
   447 {
   448   nsresult rv;
   450   if (!mPrecompiledMemberHolder) {
   451     rv = CompilePrototypeMembers(aBinding);
   452     NS_ENSURE_SUCCESS(rv, rv);
   453   }
   455   rv = aStream->WriteStringZ(mClassName.get());
   456   NS_ENSURE_SUCCESS(rv, rv);
   458   for (nsXBLProtoImplField* curr = mFields; curr; curr = curr->GetNext()) {
   459     rv = curr->Write(aStream);
   460     NS_ENSURE_SUCCESS(rv, rv);
   461   }
   462   for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
   463     if (curr == mConstructor) {
   464       rv = mConstructor->Write(aStream, XBLBinding_Serialize_Constructor);
   465     }
   466     else if (curr == mDestructor) {
   467       rv = mDestructor->Write(aStream, XBLBinding_Serialize_Destructor);
   468     }
   469     else {
   470       rv = curr->Write(aStream);
   471     }
   472     NS_ENSURE_SUCCESS(rv, rv);
   473   }
   475   return aStream->Write8(XBLBinding_Serialize_NoMoreItems);
   476 }
   478 nsresult
   479 NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding, 
   480                    const char16_t* aClassName, 
   481                    nsXBLProtoImpl** aResult)
   482 {
   483   nsXBLProtoImpl* impl = new nsXBLProtoImpl();
   484   if (!impl)
   485     return NS_ERROR_OUT_OF_MEMORY;
   486   if (aClassName)
   487     impl->mClassName.AssignWithConversion(aClassName);
   488   else
   489     aBinding->BindingURI()->GetSpec(impl->mClassName);
   490   aBinding->SetImplementation(impl);
   491   *aResult = impl;
   493   return NS_OK;
   494 }

mercurial