dom/xbl/nsXBLProtoImplField.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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 "nsIAtom.h"
     7 #include "nsIContent.h"
     8 #include "nsString.h"
     9 #include "nsJSUtils.h"
    10 #include "jsapi.h"
    11 #include "js/CharacterEncoding.h"
    12 #include "nsUnicharUtils.h"
    13 #include "nsReadableUtils.h"
    14 #include "nsXBLProtoImplField.h"
    15 #include "nsIScriptContext.h"
    16 #include "nsIURI.h"
    17 #include "nsXBLSerialize.h"
    18 #include "nsXBLPrototypeBinding.h"
    19 #include "mozilla/dom/BindingUtils.h"
    20 #include "mozilla/dom/ScriptSettings.h"
    21 #include "nsGlobalWindow.h"
    22 #include "xpcpublic.h"
    23 #include "WrapperFactory.h"
    25 using namespace mozilla;
    26 using namespace mozilla::dom;
    28 nsXBLProtoImplField::nsXBLProtoImplField(const char16_t* aName, const char16_t* aReadOnly)
    29   : mNext(nullptr),
    30     mFieldText(nullptr),
    31     mFieldTextLength(0),
    32     mLineNumber(0)
    33 {
    34   MOZ_COUNT_CTOR(nsXBLProtoImplField);
    35   mName = NS_strdup(aName);  // XXXbz make more sense to use a stringbuffer?
    37   mJSAttributes = JSPROP_ENUMERATE;
    38   if (aReadOnly) {
    39     nsAutoString readOnly; readOnly.Assign(aReadOnly);
    40     if (readOnly.LowerCaseEqualsLiteral("true"))
    41       mJSAttributes |= JSPROP_READONLY;
    42   }
    43 }
    46 nsXBLProtoImplField::nsXBLProtoImplField(const bool aIsReadOnly)
    47   : mNext(nullptr),
    48     mFieldText(nullptr),
    49     mFieldTextLength(0),
    50     mLineNumber(0)
    51 {
    52   MOZ_COUNT_CTOR(nsXBLProtoImplField);
    54   mJSAttributes = JSPROP_ENUMERATE;
    55   if (aIsReadOnly)
    56     mJSAttributes |= JSPROP_READONLY;
    57 }
    59 nsXBLProtoImplField::~nsXBLProtoImplField()
    60 {
    61   MOZ_COUNT_DTOR(nsXBLProtoImplField);
    62   if (mFieldText)
    63     nsMemory::Free(mFieldText);
    64   NS_Free(mName);
    65   NS_CONTENT_DELETE_LIST_MEMBER(nsXBLProtoImplField, this, mNext);
    66 }
    68 void 
    69 nsXBLProtoImplField::AppendFieldText(const nsAString& aText)
    70 {
    71   if (mFieldText) {
    72     nsDependentString fieldTextStr(mFieldText, mFieldTextLength);
    73     nsAutoString newFieldText = fieldTextStr + aText;
    74     char16_t* temp = mFieldText;
    75     mFieldText = ToNewUnicode(newFieldText);
    76     mFieldTextLength = newFieldText.Length();
    77     nsMemory::Free(temp);
    78   }
    79   else {
    80     mFieldText = ToNewUnicode(aText);
    81     mFieldTextLength = aText.Length();
    82   }
    83 }
    85 // XBL fields are represented on elements inheriting that field a bit trickily.
    86 // When setting up the XBL prototype object, we install accessors for the fields
    87 // on the prototype object. Those accessors, when used, will then (via
    88 // InstallXBLField below) reify a property for the field onto the actual XBL-backed
    89 // element.
    90 //
    91 // The accessor property is a plain old property backed by a getter function and
    92 // a setter function.  These properties are backed by the FieldGetter and
    93 // FieldSetter natives; they're created by InstallAccessors.  The precise field to be
    94 // reified is identified using two extra slots on the getter/setter functions.
    95 // XBLPROTO_SLOT stores the XBL prototype object that provides the field.
    96 // FIELD_SLOT stores the name of the field, i.e. its JavaScript property name.
    97 //
    98 // This two-step field installation process -- creating an accessor on the
    99 // prototype, then have that reify an own property on the actual element -- is
   100 // admittedly convoluted.  Better would be for XBL-backed elements to be proxies
   101 // that could resolve fields onto themselves.  But given that XBL bindings are
   102 // associated with elements mutably -- you can add/remove/change -moz-binding
   103 // whenever you want, alas -- doing so would require all elements to be proxies,
   104 // which isn't performant now.  So we do this two-step instead.
   105 static const uint32_t XBLPROTO_SLOT = 0;
   106 static const uint32_t FIELD_SLOT = 1;
   108 bool
   109 ValueHasISupportsPrivate(JS::Handle<JS::Value> v)
   110 {
   111   if (!v.isObject()) {
   112     return false;
   113   }
   115   const DOMClass* domClass = GetDOMClass(&v.toObject());
   116   if (domClass) {
   117     return domClass->mDOMObjectIsISupports;
   118   }
   120   const JSClass* clasp = ::JS_GetClass(&v.toObject());
   121   const uint32_t HAS_PRIVATE_NSISUPPORTS =
   122     JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS;
   123   return (clasp->flags & HAS_PRIVATE_NSISUPPORTS) == HAS_PRIVATE_NSISUPPORTS;
   124 }
   126 #ifdef DEBUG
   127 static bool
   128 ValueHasISupportsPrivate(JSContext* cx, const JS::Value& aVal)
   129 {
   130     JS::Rooted<JS::Value> v(cx, aVal);
   131     return ValueHasISupportsPrivate(v);
   132 }
   133 #endif
   135 // Define a shadowing property on |this| for the XBL field defined by the
   136 // contents of the callee's reserved slots.  If the property was defined,
   137 // *installed will be true, and idp will be set to the property name that was
   138 // defined.
   139 static bool
   140 InstallXBLField(JSContext* cx,
   141                 JS::Handle<JSObject*> callee, JS::Handle<JSObject*> thisObj,
   142                 JS::MutableHandle<jsid> idp, bool* installed)
   143 {
   144   *installed = false;
   146   // First ensure |this| is a reasonable XBL bound node.
   147   //
   148   // FieldAccessorGuard already determined whether |thisObj| was acceptable as
   149   // |this| in terms of not throwing a TypeError.  Assert this for good measure.
   150   MOZ_ASSERT(ValueHasISupportsPrivate(cx, JS::ObjectValue(*thisObj)));
   152   // But there are some cases where we must accept |thisObj| but not install a
   153   // property on it, or otherwise touch it.  Hence this split of |this|-vetting
   154   // duties.
   155   nsISupports* native =
   156     nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, thisObj);
   157   if (!native) {
   158     // Looks like whatever |thisObj| is it's not our nsIContent.  It might well
   159     // be the proto our binding installed, however, where the private is the
   160     // nsXBLDocumentInfo, so just baul out quietly.  Do NOT throw an exception
   161     // here.
   162     //
   163     // We could make this stricter by checking the class maybe, but whatever.
   164     return true;
   165   }
   167   nsCOMPtr<nsIContent> xblNode = do_QueryInterface(native);
   168   if (!xblNode) {
   169     xpc::Throw(cx, NS_ERROR_UNEXPECTED);
   170     return false;
   171   }
   173   // Now that |this| is okay, actually install the field.
   175   // Because of the possibility (due to XBL binding inheritance, because each
   176   // XBL binding lives in its own global object) that |this| might be in a
   177   // different compartment from the callee (not to mention that this method can
   178   // be called with an arbitrary |this| regardless of how insane XBL is), and
   179   // because in this method we've entered |this|'s compartment (see in
   180   // Field[GS]etter where we attempt a cross-compartment call), we must enter
   181   // the callee's compartment to access its reserved slots.
   182   nsXBLPrototypeBinding* protoBinding;
   183   nsDependentJSString fieldName;
   184   {
   185     JSAutoCompartment ac(cx, callee);
   187     JS::Rooted<JSObject*> xblProto(cx);
   188     xblProto = &js::GetFunctionNativeReserved(callee, XBLPROTO_SLOT).toObject();
   190     JS::Rooted<JS::Value> name(cx, js::GetFunctionNativeReserved(callee, FIELD_SLOT));
   191     JSFlatString* fieldStr = JS_ASSERT_STRING_IS_FLAT(name.toString());
   192     fieldName.init(fieldStr);
   194     MOZ_ALWAYS_TRUE(JS_ValueToId(cx, name, idp));
   196     // If a separate XBL scope is being used, the callee is not same-compartment
   197     // with the xbl prototype, and the object is a cross-compartment wrapper.
   198     xblProto = js::UncheckedUnwrap(xblProto);
   199     JSAutoCompartment ac2(cx, xblProto);
   200     JS::Value slotVal = ::JS_GetReservedSlot(xblProto, 0);
   201     protoBinding = static_cast<nsXBLPrototypeBinding*>(slotVal.toPrivate());
   202     MOZ_ASSERT(protoBinding);
   203   }
   205   nsXBLProtoImplField* field = protoBinding->FindField(fieldName);
   206   MOZ_ASSERT(field);
   208   nsresult rv = field->InstallField(thisObj, protoBinding->DocURI(), installed);
   209   if (NS_SUCCEEDED(rv)) {
   210     return true;
   211   }
   213   if (!::JS_IsExceptionPending(cx)) {
   214     xpc::Throw(cx, rv);
   215   }
   216   return false;
   217 }
   219 bool
   220 FieldGetterImpl(JSContext *cx, JS::CallArgs args)
   221 {
   222   JS::Handle<JS::Value> thisv = args.thisv();
   223   MOZ_ASSERT(ValueHasISupportsPrivate(thisv));
   225   JS::Rooted<JSObject*> thisObj(cx, &thisv.toObject());
   227   // We should be in the compartment of |this|. If we got here via nativeCall,
   228   // |this| is not same-compartment with |callee|, and it's possible via
   229   // asymmetric security semantics that |args.calleev()| is actually a security
   230   // wrapper. In this case, we know we want to do an unsafe unwrap, and
   231   // InstallXBLField knows how to handle cross-compartment pointers.
   232   bool installed = false;
   233   JS::Rooted<JSObject*> callee(cx, js::UncheckedUnwrap(&args.calleev().toObject()));
   234   JS::Rooted<jsid> id(cx);
   235   if (!InstallXBLField(cx, callee, thisObj, &id, &installed)) {
   236     return false;
   237   }
   239   if (!installed) {
   240     args.rval().setUndefined();
   241     return true;
   242   }
   244   JS::Rooted<JS::Value> v(cx);
   245   if (!JS_GetPropertyById(cx, thisObj, id, &v)) {
   246     return false;
   247   }
   248   args.rval().set(v);
   249   return true;
   250 }
   252 static bool
   253 FieldGetter(JSContext *cx, unsigned argc, JS::Value *vp)
   254 {
   255   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   256   return JS::CallNonGenericMethod<ValueHasISupportsPrivate, FieldGetterImpl>
   257                                  (cx, args);
   258 }
   260 bool
   261 FieldSetterImpl(JSContext *cx, JS::CallArgs args)
   262 {
   263   JS::Handle<JS::Value> thisv = args.thisv();
   264   MOZ_ASSERT(ValueHasISupportsPrivate(thisv));
   266   JS::Rooted<JSObject*> thisObj(cx, &thisv.toObject());
   268   // We should be in the compartment of |this|. If we got here via nativeCall,
   269   // |this| is not same-compartment with |callee|, and it's possible via
   270   // asymmetric security semantics that |args.calleev()| is actually a security
   271   // wrapper. In this case, we know we want to do an unsafe unwrap, and
   272   // InstallXBLField knows how to handle cross-compartment pointers.
   273   bool installed = false;
   274   JS::Rooted<JSObject*> callee(cx, js::UncheckedUnwrap(&args.calleev().toObject()));
   275   JS::Rooted<jsid> id(cx);
   276   if (!InstallXBLField(cx, callee, thisObj, &id, &installed)) {
   277     return false;
   278   }
   280   if (installed) {
   281     if (!::JS_SetPropertyById(cx, thisObj, id, args.get(0))) {
   282       return false;
   283     }
   284   }
   285   args.rval().setUndefined();
   286   return true;
   287 }
   289 static bool
   290 FieldSetter(JSContext *cx, unsigned argc, JS::Value *vp)
   291 {
   292   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   293   return JS::CallNonGenericMethod<ValueHasISupportsPrivate, FieldSetterImpl>
   294                                  (cx, args);
   295 }
   297 nsresult
   298 nsXBLProtoImplField::InstallAccessors(JSContext* aCx,
   299                                       JS::Handle<JSObject*> aTargetClassObject)
   300 {
   301   MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx));
   302   JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject));
   303   JS::Rooted<JSObject*> scopeObject(aCx, xpc::GetXBLScopeOrGlobal(aCx, globalObject));
   304   NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
   306   // Don't install it if the field is empty; see also InstallField which also must
   307   // implement the not-empty requirement.
   308   if (IsEmpty()) {
   309     return NS_OK;
   310   }
   312   // Install a getter/setter pair which will resolve the field onto the actual
   313   // object, when invoked.
   315   // Get the field name as an id.
   316   JS::Rooted<jsid> id(aCx);
   317   JS::TwoByteChars chars(mName, NS_strlen(mName));
   318   if (!JS_CharsToId(aCx, chars, &id))
   319     return NS_ERROR_OUT_OF_MEMORY;
   321   // Properties/Methods have historically taken precendence over fields. We
   322   // install members first, so just bounce here if the property is already
   323   // defined.
   324   bool found = false;
   325   if (!JS_AlreadyHasOwnPropertyById(aCx, aTargetClassObject, id, &found))
   326     return NS_ERROR_FAILURE;
   327   if (found)
   328     return NS_OK;
   330   // FieldGetter and FieldSetter need to run in the XBL scope so that they can
   331   // see through any SOWs on their targets.
   333   // First, enter the XBL scope, and compile the functions there.
   334   JSAutoCompartment ac(aCx, scopeObject);
   335   JS::Rooted<JS::Value> wrappedClassObj(aCx, JS::ObjectValue(*aTargetClassObject));
   336   if (!JS_WrapValue(aCx, &wrappedClassObj) || !JS_WrapId(aCx, &id))
   337     return NS_ERROR_OUT_OF_MEMORY;
   339   JS::Rooted<JSObject*> get(aCx,
   340     JS_GetFunctionObject(js::NewFunctionByIdWithReserved(aCx, FieldGetter,
   341                                                          0, 0, scopeObject, id)));
   342   if (!get) {
   343     return NS_ERROR_OUT_OF_MEMORY;
   344   }
   345   js::SetFunctionNativeReserved(get, XBLPROTO_SLOT, wrappedClassObj);
   346   js::SetFunctionNativeReserved(get, FIELD_SLOT,
   347                                 JS::StringValue(JSID_TO_STRING(id)));
   349   JS::Rooted<JSObject*> set(aCx,
   350     JS_GetFunctionObject(js::NewFunctionByIdWithReserved(aCx, FieldSetter,
   351                                                           1, 0, scopeObject, id)));
   352   if (!set) {
   353     return NS_ERROR_OUT_OF_MEMORY;
   354   }
   355   js::SetFunctionNativeReserved(set, XBLPROTO_SLOT, wrappedClassObj);
   356   js::SetFunctionNativeReserved(set, FIELD_SLOT,
   357                                 JS::StringValue(JSID_TO_STRING(id)));
   359   // Now, re-enter the class object's scope, wrap the getters/setters, and define
   360   // them there.
   361   JSAutoCompartment ac2(aCx, aTargetClassObject);
   362   if (!JS_WrapObject(aCx, &get) || !JS_WrapObject(aCx, &set) ||
   363       !JS_WrapId(aCx, &id))
   364   {
   365     return NS_ERROR_OUT_OF_MEMORY;
   366   }
   368   if (!::JS_DefinePropertyById(aCx, aTargetClassObject, id, JS::UndefinedValue(),
   369                                JS_DATA_TO_FUNC_PTR(JSPropertyOp, get.get()),
   370                                JS_DATA_TO_FUNC_PTR(JSStrictPropertyOp, set.get()),
   371                                AccessorAttributes())) {
   372     return NS_ERROR_OUT_OF_MEMORY;
   373   }
   375   return NS_OK;
   376 }
   378 nsresult
   379 nsXBLProtoImplField::InstallField(JS::Handle<JSObject*> aBoundNode,
   380                                   nsIURI* aBindingDocURI,
   381                                   bool* aDidInstall) const
   382 {
   383   NS_PRECONDITION(aBoundNode,
   384                   "uh-oh, bound node should NOT be null or bad things will "
   385                   "happen");
   387   *aDidInstall = false;
   389   // Empty fields are treated as not actually present.
   390   if (IsEmpty()) {
   391     return NS_OK;
   392   }
   394   nsAutoMicroTask mt;
   396   // EvaluateString and JS_DefineUCProperty can both trigger GC, so
   397   // protect |result| here.
   398   nsresult rv;
   400   nsAutoCString uriSpec;
   401   aBindingDocURI->GetSpec(uriSpec);
   403   nsIGlobalObject* globalObject = xpc::WindowGlobalOrNull(aBoundNode);
   404   if (!globalObject) {
   405     return NS_OK;
   406   }
   408   // We are going to run script via EvaluateString, so we need a script entry
   409   // point, but as this is XBL related it does not appear in the HTML spec.
   410   AutoEntryScript entryScript(globalObject, true);
   411   JSContext* cx = entryScript.cx();
   413   NS_ASSERTION(!::JS_IsExceptionPending(cx),
   414                "Shouldn't get here when an exception is pending!");
   416   // First, enter the xbl scope, wrap the node, and use that as the scope for
   417   // the evaluation.
   418   JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, aBoundNode));
   419   NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
   420   JSAutoCompartment ac(cx, scopeObject);
   422   JS::Rooted<JSObject*> wrappedNode(cx, aBoundNode);
   423   if (!JS_WrapObject(cx, &wrappedNode))
   424       return NS_ERROR_OUT_OF_MEMORY;
   426   JS::Rooted<JS::Value> result(cx);
   427   JS::CompileOptions options(cx);
   428   options.setFileAndLine(uriSpec.get(), mLineNumber)
   429          .setVersion(JSVERSION_LATEST);
   430   nsJSUtils::EvaluateOptions evalOptions;
   431   rv = nsJSUtils::EvaluateString(cx, nsDependentString(mFieldText,
   432                                                        mFieldTextLength),
   433                                  wrappedNode, options, evalOptions,
   434                                  &result);
   435   if (NS_FAILED(rv)) {
   436     return rv;
   437   }
   440   // Now, enter the node's compartment, wrap the eval result, and define it on
   441   // the bound node.
   442   JSAutoCompartment ac2(cx, aBoundNode);
   443   nsDependentString name(mName);
   444   if (!JS_WrapValue(cx, &result) ||
   445       !::JS_DefineUCProperty(cx, aBoundNode,
   446                              reinterpret_cast<const jschar*>(mName),
   447                              name.Length(), result, nullptr, nullptr,
   448                              mJSAttributes)) {
   449     return NS_ERROR_OUT_OF_MEMORY;
   450   }
   452   *aDidInstall = true;
   453   return NS_OK;
   454 }
   456 nsresult
   457 nsXBLProtoImplField::Read(nsIObjectInputStream* aStream)
   458 {
   459   nsAutoString name;
   460   nsresult rv = aStream->ReadString(name);
   461   NS_ENSURE_SUCCESS(rv, rv);
   462   mName = ToNewUnicode(name);
   464   rv = aStream->Read32(&mLineNumber);
   465   NS_ENSURE_SUCCESS(rv, rv);
   467   nsAutoString fieldText;
   468   rv = aStream->ReadString(fieldText);
   469   NS_ENSURE_SUCCESS(rv, rv);
   470   mFieldTextLength = fieldText.Length();
   471   if (mFieldTextLength)
   472     mFieldText = ToNewUnicode(fieldText);
   474   return NS_OK;
   475 }
   477 nsresult
   478 nsXBLProtoImplField::Write(nsIObjectOutputStream* aStream)
   479 {
   480   XBLBindingSerializeDetails type = XBLBinding_Serialize_Field;
   482   if (mJSAttributes & JSPROP_READONLY) {
   483     type |= XBLBinding_Serialize_ReadOnly;
   484   }
   486   nsresult rv = aStream->Write8(type);
   487   NS_ENSURE_SUCCESS(rv, rv);
   488   rv = aStream->WriteWStringZ(mName);
   489   NS_ENSURE_SUCCESS(rv, rv);
   490   rv = aStream->Write32(mLineNumber);
   491   NS_ENSURE_SUCCESS(rv, rv);
   493   return aStream->WriteWStringZ(mFieldText ? mFieldText : MOZ_UTF16(""));
   494 }

mercurial