예제 #1
0
nsresult
nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding,
                                      nsXBLBinding* aBinding)
{
  // This function is called to install a concrete implementation on a bound element using
  // this prototype implementation as a guide.  The prototype implementation is compiled lazily,
  // so for the first bound element that needs a concrete implementation, we also build the
  // prototype implementation.
  if (!mMembers && !mFields)  // Constructor and destructor also live in mMembers
    return NS_OK; // Nothing to do, so let's not waste time.

  // If the way this gets the script context changes, fix
  // nsXBLProtoImplAnonymousMethod::Execute
  nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc();

  nsCOMPtr<nsIScriptGlobalObject> global =  do_QueryInterface(document->GetScopeObject());
  if (!global) return NS_OK;

  nsCOMPtr<nsIScriptContext> context = global->GetContext();
  if (!context) return NS_OK;
  JSContext* cx = context->GetNativeContext();
  AutoCxPusher pusher(cx);

  // InitTarget objects gives us back the JS object that represents the bound element and the
  // class object in the bound document that represents the concrete version of this implementation.
  // This function also has the side effect of building up the prototype implementation if it has
  // not been built already.
  JS::Rooted<JSObject*> targetClassObject(cx, nullptr);
  bool targetObjectIsNew = false;
  nsresult rv = InitTargetObjects(aPrototypeBinding,
                                  aBinding->GetBoundElement(),
                                  &targetClassObject,
                                  &targetObjectIsNew);
  NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
  MOZ_ASSERT(targetClassObject);

  // Stash a strong reference to the JSClass in the binding.
  aBinding->SetJSClass(nsXBLJSClass::fromJSClass(JS_GetClass(targetClassObject)));

  // If the prototype already existed, we don't need to install anything. return early.
  if (!targetObjectIsNew)
    return NS_OK;

  // We want to define the canonical set of members in a safe place. If we're
  // using a separate XBL scope, we want to define them there first (so that
  // they'll be available for Xray lookups, among other things), and then copy
  // the properties to the content-side prototype as needed. We don't need to
  // bother about the field accessors here, since we don't use/support those
  // for in-content bindings.

  // First, start by entering the compartment of the XBL scope. This may or may
  // not be the same compartment as globalObject.
  JS::Rooted<JSObject*> globalObject(cx,
    GetGlobalForObjectCrossCompartment(targetClassObject));
  JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject));
  NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
  JSAutoCompartment ac(cx, scopeObject);

  // If they're different, create our safe holder object in the XBL scope.
  JS::Rooted<JSObject*> propertyHolder(cx);
  if (scopeObject != globalObject) {

    // This is just a property holder, so it doesn't need any special JSClass.
    propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scopeObject);
    NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY);

    // Define it as a property on the scopeObject, using the same name used on
    // the content side.
    bool ok = JS_DefineProperty(cx, scopeObject,
                                js::GetObjectClass(targetClassObject)->name,
                                JS::ObjectValue(*propertyHolder), JS_PropertyStub,
                                JS_StrictPropertyStub,
                                JSPROP_PERMANENT | JSPROP_READONLY);
    NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
  } else {
    propertyHolder = targetClassObject;
  }

  // Walk our member list and install each one in turn on the XBL scope object.
  for (nsXBLProtoImplMember* curr = mMembers;
       curr;
       curr = curr->GetNext())
    curr->InstallMember(cx, propertyHolder);

  // Now, if we're using a separate XBL scope, enter the compartment of the
  // bound node and copy exposable properties to the prototype there. This
  // rewraps them appropriately, which should result in cross-compartment
  // function wrappers.
  if (propertyHolder != targetClassObject) {
    AssertSameCompartment(propertyHolder, scopeObject);
    AssertSameCompartment(targetClassObject, globalObject);
    for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
      if (curr->ShouldExposeToUntrustedContent()) {
        JS::Rooted<jsid> id(cx);
        JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName()));
        bool ok = JS_CharsToId(cx, chars, &id);
        NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
        JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder);
        NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
      }
    }
  }

  // From here on out, work in the scope of the bound element.
  JSAutoCompartment ac2(cx, targetClassObject);

  // Install all of our field accessors.
  for (nsXBLProtoImplField* curr = mFields;
       curr;
       curr = curr->GetNext())
    curr->InstallAccessors(cx, targetClassObject);

  return NS_OK;
}
예제 #2
0
nsresult
nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding,
                                      nsXBLBinding* aBinding)
{
  // This function is called to install a concrete implementation on a bound element using
  // this prototype implementation as a guide.  The prototype implementation is compiled lazily,
  // so for the first bound element that needs a concrete implementation, we also build the
  // prototype implementation.
  if (!mMembers && !mFields)  // Constructor and destructor also live in mMembers
    return NS_OK; // Nothing to do, so let's not waste time.

  // If the way this gets the script context changes, fix
  // nsXBLProtoImplAnonymousMethod::Execute
  nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc();

  // This sometimes gets called when we have no outer window and if we don't
  // catch this, we get leaks during crashtests and reftests.
  if (NS_WARN_IF(!document->GetWindow())) {
    return NS_OK;
  }

  // |propertyHolder| (below) can be an existing object, so in theory we might
  // hit something that could end up running script. We never want that to
  // happen here, so we use an AutoJSAPI instead of an AutoEntryScript.
  dom::AutoJSAPI jsapi;
  if (NS_WARN_IF(!jsapi.Init(document->GetScopeObject()))) {
    return NS_OK;
  }
  JSContext* cx = jsapi.cx();

  // InitTarget objects gives us back the JS object that represents the bound element and the
  // class object in the bound document that represents the concrete version of this implementation.
  // This function also has the side effect of building up the prototype implementation if it has
  // not been built already.
  JS::Rooted<JSObject*> targetClassObject(cx, nullptr);
  bool targetObjectIsNew = false;
  nsresult rv = InitTargetObjects(aPrototypeBinding,
                                  aBinding->GetBoundElement(),
                                  &targetClassObject,
                                  &targetObjectIsNew);
  NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
  MOZ_ASSERT(targetClassObject);

  // If the prototype already existed, we don't need to install anything. return early.
  if (!targetObjectIsNew)
    return NS_OK;

  // We want to define the canonical set of members in a safe place. If we're
  // using a separate XBL scope, we want to define them there first (so that
  // they'll be available for Xray lookups, among other things), and then copy
  // the properties to the content-side prototype as needed. We don't need to
  // bother about the field accessors here, since we don't use/support those
  // for in-content bindings.

  // First, start by entering the compartment of the XBL scope. This may or may
  // not be the same compartment as globalObject.
  JSAddonId* addonId = MapURIToAddonID(aPrototypeBinding->BindingURI());
  JS::Rooted<JSObject*> globalObject(cx,
    GetGlobalForObjectCrossCompartment(targetClassObject));
  JS::Rooted<JSObject*> scopeObject(cx, xpc::GetScopeForXBLExecution(cx, globalObject, addonId));
  NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
  JSAutoCompartment ac(cx, scopeObject);

  // Determine the appropriate property holder.
  //
  // Note: If |targetIsNew| is false, we'll early-return above. However, that only
  // tells us if the content-side object is new, which may be the case even if
  // we've already set up the binding on the XBL side. For example, if we apply
  // a binding #foo to a <span> when we've already applied it to a <div>, we'll
  // end up with a different content prototype, but we'll already have a property
  // holder called |foo| in the XBL scope. Check for that to avoid wasteful and
  // weird property holder duplication.
  const char* className = aPrototypeBinding->ClassName().get();
  JS::Rooted<JSObject*> propertyHolder(cx);
  JS::Rooted<JSPropertyDescriptor> existingHolder(cx);
  if (scopeObject != globalObject &&
      !JS_GetOwnPropertyDescriptor(cx, scopeObject, className, &existingHolder)) {
    return NS_ERROR_FAILURE;
  }
  bool propertyHolderIsNew = !existingHolder.object() || !existingHolder.value().isObject();

  if (!propertyHolderIsNew) {
    propertyHolder = &existingHolder.value().toObject();
  } else if (scopeObject != globalObject) {

    // This is just a property holder, so it doesn't need any special JSClass.
    propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scopeObject);
    NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY);

    // Define it as a property on the scopeObject, using the same name used on
    // the content side.
    bool ok = JS_DefineProperty(cx, scopeObject, className, propertyHolder,
                                JSPROP_PERMANENT | JSPROP_READONLY,
                                JS_PropertyStub, JS_StrictPropertyStub);
    NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
  } else {
    propertyHolder = targetClassObject;
  }

  // Walk our member list and install each one in turn on the XBL scope object.
  if (propertyHolderIsNew) {
    for (nsXBLProtoImplMember* curr = mMembers;
         curr;
         curr = curr->GetNext())
      curr->InstallMember(cx, propertyHolder);
  }

  // Now, if we're using a separate XBL scope, enter the compartment of the
  // bound node and copy exposable properties to the prototype there. This
  // rewraps them appropriately, which should result in cross-compartment
  // function wrappers.
  if (propertyHolder != targetClassObject) {
    AssertSameCompartment(propertyHolder, scopeObject);
    AssertSameCompartment(targetClassObject, globalObject);
    bool inContentXBLScope = xpc::IsInContentXBLScope(scopeObject);
    for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
      if (!inContentXBLScope || curr->ShouldExposeToUntrustedContent()) {
        JS::Rooted<jsid> id(cx);
        JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName()));
        bool ok = JS_CharsToId(cx, chars, &id);
        NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);

        bool found;
        ok = JS_HasPropertyById(cx, propertyHolder, id, &found);
        NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
        if (!found) {
          // Some members don't install anything in InstallMember (e.g.,
          // nsXBLProtoImplAnonymousMethod). We need to skip copying in
          // those cases.
          continue;
        }

        ok = JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder);
        NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
      }
    }
  }

  // From here on out, work in the scope of the bound element.
  JSAutoCompartment ac2(cx, targetClassObject);

  // Install all of our field accessors.
  for (nsXBLProtoImplField* curr = mFields;
       curr;
       curr = curr->GetNext())
    curr->InstallAccessors(cx, targetClassObject);

  return NS_OK;
}