Mon, 10 Sep 2007 15:01:06 -0700
changeset 5843 79e8c861a0dd70389b8ea77092fbff989b24cd2f
parent 1833 6aa0d1c707e2cb1efaf319ebe3d4918235178697
child 6328 3047af24748824c921413eacf7c47f94a16ba327
permissions -rw-r--r--
Bug 395466 Checking in patch by Ben_H for QA Extension

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 * The Original Code is Mozilla Communicator client code.
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 * Contributor(s):
 *   David Hyatt <> (Original Author)
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * ***** END LICENSE BLOCK ***** */

#include "nsXBLProtoImpl.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsContentUtils.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptGlobalObjectOwner.h"
#include "nsIScriptContext.h"
#include "nsIXPConnect.h"
#include "nsIServiceManager.h"
#include "nsIXBLDocumentInfo.h"
#include "nsIDOMNode.h"

nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIContent* aBoundElement)
  // 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)  // 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 = aBoundElement->GetOwnerDoc();
  if (!document) return NS_OK;

  nsIScriptGlobalObject *global = document->GetScriptGlobalObject();
  if (!global) return NS_OK;

  nsCOMPtr<nsIScriptContext> context = global->GetContext();
  if (!context) return NS_OK;

  // 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.
  nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
  void * targetClassObject = nsnull;
  nsresult rv = InitTargetObjects(aBinding, context, aBoundElement,
                                  getter_AddRefs(holder), &targetClassObject);
  NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects

  JSObject * targetScriptObject;

  // Walk our member list and install each one in turn.
  for (nsXBLProtoImplMember* curr = mMembers;
       curr = curr->GetNext())
    curr->InstallMember(context, aBoundElement, targetScriptObject,
                        targetClassObject, mClassName);
  return NS_OK;

nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
                                  nsIScriptContext* aContext, 
                                  nsIContent* aBoundElement, 
                                  nsIXPConnectJSObjectHolder** aScriptObjectHolder, 
                                  void** aTargetClassObject)
  nsresult rv = NS_OK;
  *aScriptObjectHolder = nsnull;
  if (!mClassObject) {
    rv = CompilePrototypeMembers(aBinding); // This is the first time we've ever installed this binding on an element.
                                 // We need to go ahead and compile all methods and properties on a class
                                 // in our prototype binding.
    if (NS_FAILED(rv))
      return rv;

    if (!mClassObject)
      return NS_OK; // This can be ok, if all we've got are fields (and no methods/properties).

  nsIDocument *ownerDoc = aBoundElement->GetOwnerDoc();
  nsIScriptGlobalObject *sgo;

  if (!ownerDoc || !(sgo = ownerDoc->GetScriptGlobalObject())) {
    NS_ERROR("Can't find global object for bound content!");


  // Because our prototype implementation has a class, we need to build up a corresponding
  // class for the concrete implementation in the bound document.
  JSContext* jscontext = (JSContext*)aContext->GetNativeContext();
  JSObject* global = sgo->GetGlobalJSObject();
  nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
  rv = nsContentUtils::XPConnect()->WrapNative(jscontext, global,
  JSObject * object = nsnull;
  rv = wrapper->GetJSObject(&object);

  // All of the above code was just obtaining the bound element's script object and its immediate
  // concrete base class.  We need to alter the object so that our concrete class is interposed
  // between the object and its base class.  We become the new base class of the object, and the
  // object's old base class becomes the new class' base class.
  rv = aBinding->InitClass(mClassName, jscontext, global, object,
  if (NS_FAILED(rv))
    return rv;

  // Root ourselves in the document.
  nsIDocument* doc = aBoundElement->GetOwnerDoc();
  if (doc) {
    nsCOMPtr<nsIXPConnectWrappedNative> nativeWrapper(do_QueryInterface(wrapper));
    if (nativeWrapper)
      doc->AddReference(aBoundElement, nativeWrapper);

  return rv;

nsXBLProtoImpl::CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding)
  // We want to pre-compile our implementation's members against a "prototype context". Then when we actually 
  // bind the prototype to a real xbl instance, we'll clone the pre-compiled JS into the real instance's 
  // context.
  nsCOMPtr<nsIScriptGlobalObjectOwner> globalOwner(
  nsIScriptGlobalObject* globalObject = globalOwner->GetScriptGlobalObject();

  nsIScriptContext *context = globalObject->GetContext();
  JSObject *global = globalObject->GetGlobalJSObject();

  void* classObject;
  nsresult rv = aBinding->InitClass(mClassName,
                                    (JSContext *)context->GetNativeContext(),
                                    global, global,
  if (NS_FAILED(rv))
    return rv;

  mClassObject = (JSObject*) classObject;
  if (!mClassObject)
    return NS_ERROR_FAILURE;

  // Now that we have a class object installed, we walk our member list and compile each of our
  // properties and methods in turn.
  for (nsXBLProtoImplMember* curr = mMembers;
       curr = curr->GetNext()) {
    nsresult rv = curr->CompileMember(context, mClassName, mClassObject);
    if (NS_FAILED(rv)) {
      return rv;
  return NS_OK;

nsXBLProtoImpl::Traverse(nsCycleCollectionTraversalCallback &cb) const
  // If we don't have a class object then we either didn't compile members
  // or we only have fields, in both cases there are no cycles through our
  // members.
  if (!mClassObject) {

  nsXBLProtoImplMember *member;
  for (member = mMembers; member; member = member->GetNext()) {

nsXBLProtoImpl::DestroyMembers(nsXBLProtoImplMember* aBrokenMember)
  NS_ASSERTION(mClassObject, "This should never be called when there is no class object");
  PRBool compiled = PR_TRUE;
  for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
    if (curr == aBrokenMember) {
      compiled = PR_FALSE;

  // Now clear out mMembers so we don't try to call Destroy() on them again
  delete mMembers;
  mMembers = nsnull;
  mConstructor = nsnull;
  mDestructor = nsnull;

NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding, 
                   const PRUnichar* aClassName, 
                   nsXBLProtoImpl** aResult)
  nsXBLProtoImpl* impl = new nsXBLProtoImpl();
  if (!impl)
  if (aClassName)
  *aResult = impl;

  return NS_OK;