gecko-dev/content/xbl/src/nsXBLService.cpp

1158 lines
37 KiB
C++
Raw Normal View History

2000-03-21 13:14:34 +00:00
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape 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 http://www.mozilla.org/NPL/
*
* 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 Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Original Author: David W. Hyatt (hyatt@netscape.com)
*
* Contributor(s): Brendan Eich (brendan@mozilla.org)
2000-03-21 13:14:34 +00:00
*/
#include "nsCOMPtr.h"
2000-05-28 04:10:50 +00:00
#include "nsXBLService.h"
2000-01-12 09:44:18 +00:00
#include "nsIInputStream.h"
#include "nsINameSpace.h"
2000-01-12 09:44:18 +00:00
#include "nsINameSpaceManager.h"
#include "nsHashtable.h"
#include "nsIURI.h"
#include "nsIURL.h"
#include "nsIChannel.h"
2000-08-04 08:45:29 +00:00
#include "nsIHTTPChannel.h"
2000-01-12 09:44:18 +00:00
#include "nsXPIDLString.h"
#include "nsIParser.h"
#include "nsParserCIID.h"
#include "nsNetUtil.h"
#include "plstr.h"
#include "nsIContent.h"
#include "nsIXMLContent.h"
2000-01-15 05:31:45 +00:00
#include "nsIDOMElement.h"
#include "nsIDocument.h"
#include "nsIXMLContentSink.h"
#include "nsLayoutCID.h"
2000-01-12 10:55:56 +00:00
#include "nsXMLDocument.h"
2000-01-13 02:23:54 +00:00
#include "nsHTMLAtoms.h"
#include "nsSupportsArray.h"
#include "nsITextContent.h"
2000-01-13 02:23:54 +00:00
#include "nsIXBLBinding.h"
#include "nsIXBLDocumentInfo.h"
#include "nsIChromeRegistry.h"
#include "nsIPref.h"
#include "nsIPresShell.h"
#include "nsIDocumentObserver.h"
2000-08-05 22:33:29 +00:00
#include "nsIXULContentUtils.h"
#include "nsIXULPrototypeCache.h"
#include "nsIDOMLoadListener.h"
2000-08-05 22:33:29 +00:00
// Static IIDs/CIDs. Try to minimize these.
static NS_DEFINE_CID(kNameSpaceManagerCID, NS_NAMESPACEMANAGER_CID);
static NS_DEFINE_CID(kXMLDocumentCID, NS_XMLDOCUMENT_CID);
static NS_DEFINE_CID(kParserCID, NS_PARSER_IID); // XXX What's up with this???
static NS_DEFINE_CID(kChromeRegistryCID, NS_CHROMEREGISTRY_CID);
2000-01-12 09:44:18 +00:00
static PRBool IsChromeURI(nsIURI* aURI)
{
nsresult rv;
nsXPIDLCString protocol;
rv = aURI->GetScheme(getter_Copies(protocol));
if (NS_SUCCEEDED(rv)) {
if (!PL_strcmp(protocol, "chrome")) {
return PR_TRUE;
}
}
return PR_FALSE;
}
2000-08-05 22:33:29 +00:00
// Individual binding requests.
struct nsXBLBindingRequest
{
2000-08-06 04:57:55 +00:00
nsCString mBindingURL;
nsCOMPtr<nsIContent> mBoundElement;
nsXBLBindingRequest(const nsCString& aURL, nsIContent* aBoundElement)
{
mBindingURL = aURL;
mBoundElement = aBoundElement;
gRefCnt++;
if (gRefCnt == 1) {
nsServiceManager::GetService("component://netscape/xbl",
NS_GET_IID(nsIXBLService),
(nsISupports**) &gXBLService);
}
}
~nsXBLBindingRequest()
{
gRefCnt--;
if (gRefCnt == 0) {
nsServiceManager::ReleaseService("component://netscape/xbl", gXBLService);
gXBLService = nsnull;
}
}
static void* operator new(size_t aSize, nsFixedSizeAllocator& aAllocator) {
return aAllocator.Alloc(aSize);
}
static void operator delete(void* aPtr, size_t aSize) {
nsFixedSizeAllocator::Free(aPtr, aSize);
}
2000-08-06 04:57:55 +00:00
void DocumentLoaded(nsIDocument* aBindingDoc)
{
nsCOMPtr<nsIDocument> doc;
mBoundElement->GetDocument(*getter_AddRefs(doc));
if (!doc)
return;
// Get the binding.
PRBool ready = PR_FALSE;
gXBLService->BindingReady(mBoundElement, mBindingURL, &ready);
2000-08-06 04:57:55 +00:00
if (!ready)
return;
2000-08-06 04:57:55 +00:00
// XXX Deal with layered bindings.
2000-08-06 04:57:55 +00:00
// Now do a ContentInserted notification to cause the frames to get installed finally,
nsCOMPtr<nsIContent> parent;
mBoundElement->GetParent(*getter_AddRefs(parent));
PRInt32 index = 0;
if (parent)
parent->IndexOf(mBoundElement, index);
nsCOMPtr<nsIPresShell> shell = getter_AddRefs(doc->GetShellAt(0));
if (shell) {
nsCOMPtr<nsIDocumentObserver> obs(do_QueryInterface(shell));
obs->ContentRemoved(doc, parent, mBoundElement, index);
obs->ContentInserted(doc, parent, mBoundElement, index);
}
2000-08-06 04:57:55 +00:00
}
static nsIXBLService* gXBLService;
static int gRefCnt;
2000-08-05 22:33:29 +00:00
};
static const size_t kBucketSizes[] = {
sizeof(nsXBLBindingRequest)
};
static const PRInt32 kNumBuckets = sizeof(kBucketSizes)/sizeof(size_t);
static const PRInt32 kNumElements = 64;
static const PRInt32 kInitialSize = (NS_SIZE_IN_HEAP(sizeof(nsXBLBindingRequest))) * kNumElements;
2000-08-06 04:57:55 +00:00
nsIXBLService* nsXBLBindingRequest::gXBLService = nsnull;
int nsXBLBindingRequest::gRefCnt = 0;
2000-08-04 08:45:29 +00:00
// nsXBLStreamListener, a helper class used for
// asynchronous parsing of URLs
/* Header file */
class nsXBLStreamListener : public nsIStreamListener, public nsIDOMLoadListener
2000-08-04 08:45:29 +00:00
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSISTREAMOBSERVER
nsresult Load(nsIDOMEvent* aEvent);
nsresult Unload(nsIDOMEvent* aEvent) { return NS_OK; };
nsresult Abort(nsIDOMEvent* aEvent) { return NS_OK; };
nsresult Error(nsIDOMEvent* aEvent) { return NS_OK; };
nsresult HandleEvent(nsIDOMEvent* aEvent) { return NS_OK; };
static nsIXULPrototypeCache* gXULCache;
static nsIXULContentUtils* gXULUtils;
static PRInt32 gRefCnt;
2000-08-06 04:57:55 +00:00
nsXBLStreamListener(nsIStreamListener* aInner, nsIDocument* aDocument, nsIDocument* aBindingDocument);
2000-08-04 08:45:29 +00:00
virtual ~nsXBLStreamListener();
2000-08-06 04:57:55 +00:00
void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); };
PRBool HasRequest(const nsCString& aURI, nsIContent* aBoundElement);
2000-08-05 22:33:29 +00:00
2000-08-06 04:57:55 +00:00
private:
nsCOMPtr<nsIStreamListener> mInner;
2000-08-05 22:33:29 +00:00
nsVoidArray mBindingRequests;
2000-08-06 04:57:55 +00:00
nsIDocument* mDocument;
2000-08-06 04:57:55 +00:00
nsCOMPtr<nsIDocument> mBindingDocument;
2000-08-04 08:45:29 +00:00
};
nsIXULPrototypeCache* nsXBLStreamListener::gXULCache = nsnull;
nsIXULContentUtils* nsXBLStreamListener::gXULUtils = nsnull;
PRInt32 nsXBLStreamListener::gRefCnt = 0;
2000-08-05 22:33:29 +00:00
2000-08-04 08:45:29 +00:00
/* Implementation file */
NS_IMPL_ISUPPORTS4(nsXBLStreamListener, nsIStreamListener, nsIStreamObserver, nsIDOMLoadListener, nsIDOMEventListener)
2000-08-04 08:45:29 +00:00
2000-08-06 04:57:55 +00:00
nsXBLStreamListener::nsXBLStreamListener(nsIStreamListener* aInner, nsIDocument* aDocument,
nsIDocument* aBindingDocument)
2000-08-04 08:45:29 +00:00
{
NS_INIT_ISUPPORTS();
/* member initializers and constructor code */
mInner = aInner;
2000-08-06 04:57:55 +00:00
mDocument = aDocument;
mBindingDocument = aBindingDocument;
gRefCnt++;
if (gRefCnt == 1) {
nsresult rv = nsServiceManager::GetService("component://netscape/rdf/xul-content-utils",
NS_GET_IID(nsIXULContentUtils),
(nsISupports**) &gXULUtils);
if (NS_FAILED(rv)) return;
rv = nsServiceManager::GetService("component://netscape/rdf/xul-prototype-cache",
NS_GET_IID(nsIXULPrototypeCache),
(nsISupports**) &gXULCache);
if (NS_FAILED(rv)) return;
}
2000-08-04 08:45:29 +00:00
}
nsXBLStreamListener::~nsXBLStreamListener()
{
/* destructor code */
gRefCnt--;
if (gRefCnt == 0) {
if (gXULUtils) {
nsServiceManager::ReleaseService("component://netscape/rdf/xul-content-utils", gXULUtils);
gXULUtils = nsnull;
}
if (gXULCache) {
nsServiceManager::ReleaseService("component://netscape/rdf/xul-prototype-cache", gXULCache);
gXULCache = nsnull;
}
}
2000-08-04 08:45:29 +00:00
}
/* void onDataAvailable (in nsIChannel channel, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long sourceOffset, in unsigned long count); */
NS_IMETHODIMP
nsXBLStreamListener::OnDataAvailable(nsIChannel* aChannel, nsISupports* aCtxt, nsIInputStream* aInStr,
PRUint32 aSourceOffset, PRUint32 aCount)
{
if (mInner)
return mInner->OnDataAvailable(aChannel, aCtxt, aInStr, aSourceOffset, aCount);
return NS_ERROR_FAILURE;
}
/* void onStartRequest (in nsIChannel channel, in nsISupports ctxt); */
NS_IMETHODIMP
nsXBLStreamListener::OnStartRequest(nsIChannel* aChannel, nsISupports* aCtxt)
{
2000-08-06 04:57:55 +00:00
if (mInner)
return mInner->OnStartRequest(aChannel, aCtxt);
2000-08-04 08:45:29 +00:00
return NS_ERROR_FAILURE;
}
/* void onStopRequest (in nsIChannel channel, in nsISupports ctxt, in nsresult status, in wstring statusArg); */
NS_IMETHODIMP
nsXBLStreamListener::OnStopRequest(nsIChannel* aChannel, nsISupports* aCtxt, nsresult aStatus, const PRUnichar* aStatusArg)
{
nsresult rv = NS_OK;
2000-08-06 04:57:55 +00:00
if (mInner) {
rv = mInner->OnStopRequest(aChannel, aCtxt, aStatus, aStatusArg);
}
2000-08-06 04:57:55 +00:00
if (NS_FAILED(rv) || NS_FAILED(aStatus)) {
2000-08-06 04:57:55 +00:00
PRUint32 count = mBindingRequests.Count();
for (PRUint32 i = 0; i < count; i++) {
nsXBLBindingRequest* req = (nsXBLBindingRequest*)mBindingRequests.ElementAt(i);
delete req;
}
mDocument = nsnull;
mBindingDocument = nsnull;
2000-08-06 04:57:55 +00:00
}
return rv;
}
PRBool
nsXBLStreamListener::HasRequest(const nsCString& aURI, nsIContent* aElt)
{
// XXX Could be more efficient.
PRUint32 count = mBindingRequests.Count();
for (PRUint32 i = 0; i < count; i++) {
nsXBLBindingRequest* req = (nsXBLBindingRequest*)mBindingRequests.ElementAt(i);
if (req->mBindingURL.Equals(aURI) && req->mBoundElement.get() == aElt)
return PR_TRUE;
}
return PR_FALSE;
}
nsresult
nsXBLStreamListener::Load(nsIDOMEvent* aEvent)
{
nsresult rv = NS_OK;
// Remove ourselves from the set of pending docs.
nsCOMPtr<nsIBindingManager> bindingManager;
mDocument->GetBindingManager(getter_AddRefs(bindingManager));
nsCOMPtr<nsIURI> uri(mBindingDocument->GetDocumentURL());
nsXPIDLCString str;
uri->GetSpec(getter_Copies(str));
bindingManager->RemoveLoadingDocListener((const char*)str);
PRUint32 count = mBindingRequests.Count();
nsCOMPtr<nsIContent> root = getter_AddRefs(mBindingDocument->GetRootContent());
if (root)
nsXBLService::StripWhitespaceNodes(root);
// Put our doc in the doc table.
nsCOMPtr<nsIXBLDocumentInfo> info;
NS_NewXBLDocumentInfo(mBindingDocument, getter_AddRefs(info));
// If the doc is a chrome URI, then we put it into the XUL cache.
PRBool cached = PR_FALSE;
if (IsChromeURI(uri) && gXULUtils->UseXULCache()) {
cached = PR_TRUE;
gXULCache->PutXBLDocumentInfo(info);
// Cache whether or not this chrome XBL can execute scripts.
nsCOMPtr<nsIChromeRegistry> reg(do_GetService(kChromeRegistryCID, &rv));
if (NS_SUCCEEDED(rv) && reg) {
PRBool allow = PR_TRUE;
reg->AllowScriptsForSkin(uri, &allow);
info->SetScriptAccess(allow);
}
}
if (!cached)
bindingManager->PutXBLDocumentInfo(info);
// Notify all pending requests that their bindings are
// ready and can be installed.
PRUint32 i;
for (i = 0; i < count; i++) {
nsXBLBindingRequest* req = (nsXBLBindingRequest*)mBindingRequests.ElementAt(i);
req->DocumentLoaded(mBindingDocument);
}
for (i = 0; i < count; i++) {
nsXBLBindingRequest* req = (nsXBLBindingRequest*)mBindingRequests.ElementAt(i);
delete req;
}
nsCOMPtr<nsIDOMEventReceiver> rec(do_QueryInterface(mBindingDocument));
rec->RemoveEventListener(NS_ConvertASCIItoUCS2("load"), (nsIDOMLoadListener*)this, PR_FALSE);
mDocument = nsnull;
mBindingDocument = nsnull;
return rv;
2000-08-04 08:45:29 +00:00
}
2000-01-12 09:44:18 +00:00
// nsProxyStream
// A helper class used for synchronous parsing of URLs.
class nsProxyStream : public nsIInputStream
{
private:
const char* mBuffer;
PRUint32 mSize;
PRUint32 mIndex;
public:
nsProxyStream(void) : mBuffer(nsnull)
{
NS_INIT_REFCNT();
}
virtual ~nsProxyStream(void) {
}
// nsISupports
NS_DECL_ISUPPORTS
// nsIBaseStream
NS_IMETHOD Close(void) {
return NS_OK;
}
// nsIInputStream
NS_IMETHOD Available(PRUint32 *aLength) {
*aLength = mSize - mIndex;
return NS_OK;
}
NS_IMETHOD Read(char* aBuf, PRUint32 aCount, PRUint32 *aReadCount) {
PRUint32 readCount = 0;
while (mIndex < mSize && aCount > 0) {
*aBuf = mBuffer[mIndex];
aBuf++;
mIndex++;
readCount++;
aCount--;
}
*aReadCount = readCount;
return NS_OK;
}
// Implementation
void SetBuffer(const char* aBuffer, PRUint32 aSize) {
mBuffer = aBuffer;
mSize = aSize;
mIndex = 0;
}
};
NS_IMPL_ISUPPORTS(nsProxyStream, NS_GET_IID(nsIInputStream));
2000-01-12 09:44:18 +00:00
// Implementation /////////////////////////////////////////////////////////////////
// Static member variable initialization
PRUint32 nsXBLService::gRefCnt = 0;
2000-08-05 22:33:29 +00:00
nsIXULContentUtils* nsXBLService::gXULUtils = nsnull;
nsIXULPrototypeCache* nsXBLService::gXULCache = nsnull;
nsINameSpaceManager* nsXBLService::gNameSpaceManager = nsnull;
2000-05-28 04:10:50 +00:00
nsHashtable* nsXBLService::gClassTable = nsnull;
JSCList nsXBLService::gClassLRUList = JS_INIT_STATIC_CLIST(&nsXBLService::gClassLRUList);
PRUint32 nsXBLService::gClassLRUListLength = 0;
PRUint32 nsXBLService::gClassLRUListQuota = 64;
2000-01-13 02:23:54 +00:00
nsIAtom* nsXBLService::kExtendsAtom = nsnull;
nsIAtom* nsXBLService::kScrollbarAtom = nsnull;
2000-01-13 02:23:54 +00:00
// Enabled by default. Must be over-ridden to disable
PRBool nsXBLService::gDisableChromeCache = PR_FALSE;
static const char kDisableChromeCachePref[] = "nglayout.debug.disable_xul_cache";
PRInt32 nsXBLService::kNameSpaceID_XBL;
2000-01-12 09:44:18 +00:00
// Implement our nsISupports methods
NS_IMPL_ISUPPORTS2(nsXBLService, nsIXBLService, nsIMemoryPressureObserver)
2000-01-12 09:44:18 +00:00
// Constructors/Destructors
nsXBLService::nsXBLService(void)
{
NS_INIT_REFCNT();
mPool.Init("XBL Binding Requests", kBucketSizes, kNumBuckets, kInitialSize);
2000-01-12 09:44:18 +00:00
gRefCnt++;
if (gRefCnt == 1) {
2000-08-05 22:33:29 +00:00
// Register the XBL namespace.
nsresult rv = nsComponentManager::CreateInstance(kNameSpaceManagerCID,
nsnull,
NS_GET_IID(nsINameSpaceManager),
(void**) &gNameSpaceManager);
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create namespace manager");
if (NS_FAILED(rv)) return;
// XXX This is sure to change. Copied from mozilla/layout/xul/content/src/nsXULAtoms.cpp
static const char kXBLNameSpaceURI[]
= "http://www.mozilla.org/xbl";
2000-04-16 11:19:26 +00:00
rv = gNameSpaceManager->RegisterNameSpace(NS_ConvertASCIItoUCS2(kXBLNameSpaceURI), kNameSpaceID_XBL);
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to register XBL namespace");
if (NS_FAILED(rv)) return;
2000-01-13 02:23:54 +00:00
// Create our atoms
kExtendsAtom = NS_NewAtom("extends");
kScrollbarAtom = NS_NewAtom("scrollbar");
// Find out if the XUL cache is on or off
NS_WITH_SERVICE(nsIPref, prefs, NS_PREF_PROGID, &rv);
if (NS_SUCCEEDED(rv))
prefs->GetBoolPref(kDisableChromeCachePref, &gDisableChromeCache);
2000-05-28 04:10:50 +00:00
gClassTable = new nsHashtable();
// Register the first (and only) nsXBLService as a memory pressure observer
// so it can flush the LRU list in low-memory situations.
nsMemory::RegisterObserver(this);
2000-08-05 22:33:29 +00:00
rv = nsServiceManager::GetService("component://netscape/rdf/xul-content-utils",
NS_GET_IID(nsIXULContentUtils),
(nsISupports**) &gXULUtils);
if (NS_FAILED(rv)) return;
rv = nsServiceManager::GetService("component://netscape/rdf/xul-prototype-cache",
NS_GET_IID(nsIXULPrototypeCache),
(nsISupports**) &gXULCache);
if (NS_FAILED(rv)) return;
2000-01-12 09:44:18 +00:00
}
}
nsXBLService::~nsXBLService(void)
{
gRefCnt--;
if (gRefCnt == 0) {
NS_IF_RELEASE(gNameSpaceManager);
2000-01-13 02:23:54 +00:00
// Release our atoms
NS_RELEASE(kExtendsAtom);
NS_RELEASE(kScrollbarAtom);
// Walk the LRU list removing and deleting the nsXBLJSClasses.
FlushMemory(REASON_HEAP_MINIMIZE, 0);
// Any straggling nsXBLJSClass instances held by unfinalized JS objects
// created for bindings will be deleted when those objects are finalized
// (and not put on gClassLRUList, because length >= quota).
gClassLRUListLength = gClassLRUListQuota = 0;
// At this point, the only hash table entries should be for referenced
// XBL class structs held by unfinalized JS binding objects.
2000-05-28 04:10:50 +00:00
delete gClassTable;
gClassTable = nsnull;
nsMemory::UnregisterObserver(this);
2000-08-05 22:33:29 +00:00
if (gXULUtils) {
nsServiceManager::ReleaseService("component://netscape/rdf/xul-content-utils", gXULUtils);
gXULUtils = nsnull;
}
if (gXULCache) {
nsServiceManager::ReleaseService("component://netscape/rdf/xul-prototype-cache", gXULCache);
gXULCache = nsnull;
}
}
2000-01-12 09:44:18 +00:00
}
2000-01-12 09:32:29 +00:00
// This function loads a particular XBL file and installs all of the bindings
// onto the element.
NS_IMETHODIMP
2000-07-28 00:35:02 +00:00
nsXBLService::LoadBindings(nsIContent* aContent, const nsString& aURL, PRBool aAugmentFlag,
nsIXBLBinding** aBinding)
2000-01-12 09:32:29 +00:00
{
2000-07-28 00:35:02 +00:00
*aBinding = nsnull;
nsresult rv;
2000-01-13 02:23:54 +00:00
nsCOMPtr<nsIDocument> document;
aContent->GetDocument(*getter_AddRefs(document));
nsCOMPtr<nsIBindingManager> bindingManager;
document->GetBindingManager(getter_AddRefs(bindingManager));
2000-01-13 02:23:54 +00:00
nsCOMPtr<nsIXBLBinding> binding;
bindingManager->GetBinding(aContent, getter_AddRefs(binding));
2000-06-02 08:13:29 +00:00
if (binding && !aAugmentFlag) {
nsCOMPtr<nsIXBLBinding> styleBinding;
binding->GetFirstStyleBinding(getter_AddRefs(styleBinding));
if (styleBinding) {
PRBool marked = PR_FALSE;
binding->MarkedForDeath(&marked);
if (marked) {
2000-07-28 02:22:59 +00:00
FlushStyleBindings(aContent);
binding = nsnull;
}
else {
// See if the URIs match.
nsCAutoString uri;
styleBinding->GetBindingURI(uri);
if (uri.EqualsWithConversion(aURL))
return NS_OK;
else {
FlushStyleBindings(aContent);
binding = nsnull;
}
}
2000-06-02 08:13:29 +00:00
}
2000-04-27 02:08:35 +00:00
}
2000-06-02 08:13:29 +00:00
nsCOMPtr<nsIXBLBinding> newBinding;
nsCAutoString url; url.AssignWithConversion(aURL);
2000-08-05 22:33:29 +00:00
if (NS_FAILED(rv = GetBinding(aContent, url, getter_AddRefs(newBinding)))) {
return rv;
}
2000-01-12 09:44:18 +00:00
2000-06-02 08:13:29 +00:00
if (!newBinding) {
2000-04-19 08:20:20 +00:00
nsCAutoString str = "Failed to locate XBL binding. XBL is now using id instead of name to reference bindings. Make sure you have switched over. The invalid binding name is: ";
2000-04-16 11:19:26 +00:00
str.AppendWithConversion(aURL);
2000-03-30 03:18:44 +00:00
NS_ERROR(str);
2000-08-04 08:45:29 +00:00
return NS_OK;
2000-03-30 03:18:44 +00:00
}
2000-06-02 08:13:29 +00:00
if (aAugmentFlag) {
nsCOMPtr<nsIXBLBinding> baseBinding;
nsCOMPtr<nsIXBLBinding> nextBinding = newBinding;
do {
baseBinding = nextBinding;
baseBinding->GetBaseBinding(getter_AddRefs(nextBinding));
baseBinding->SetIsStyleBinding(PR_FALSE);
} while (nextBinding);
// XXX Handle adjusting the prototype chain! We need to somehow indicate to
// InstallProperties that the whole chain should just be whacked and rebuilt.
// We are becoming the new binding.
bindingManager->SetBinding(aContent, newBinding);
baseBinding->SetBaseBinding(binding);
}
else {
// We loaded a style binding. It goes on the end.
if (binding) {
// Get the last binding that is in the append layer.
nsCOMPtr<nsIXBLBinding> rootBinding;
binding->GetRootBinding(getter_AddRefs(rootBinding));
rootBinding->SetBaseBinding(newBinding);
}
else {
// Install the binding on the content node.
bindingManager->SetBinding(aContent, newBinding);
}
}
// Set the binding's bound element.
newBinding->SetBoundElement(aContent);
2000-01-13 02:23:54 +00:00
// Tell the binding to build the anonymous content.
2000-06-02 08:13:29 +00:00
newBinding->GenerateAnonymousContent(aContent);
// Tell the binding to install event handlers
2000-07-28 00:35:02 +00:00
newBinding->InstallEventHandlers(aContent, aBinding);
// Set up our properties
2000-06-02 08:13:29 +00:00
newBinding->InstallProperties(aContent);
2000-01-12 09:32:29 +00:00
return NS_OK;
}
// For a given element, returns a flat list of all the anonymous children that need
// frames built.
NS_IMETHODIMP
nsXBLService::GetContentList(nsIContent* aContent, nsISupportsArray** aResult, nsIContent** aParent,
PRBool* aMultipleInsertionPoints)
2000-01-12 09:32:29 +00:00
{
// Iterate over all of the bindings one by one and build up an array
// of anonymous items.
*aResult = nsnull;
*aParent = nsnull;
*aMultipleInsertionPoints = PR_FALSE;
nsCOMPtr<nsIDocument> document;
aContent->GetDocument(*getter_AddRefs(document));
nsCOMPtr<nsIBindingManager> bindingManager;
2000-05-18 19:58:27 +00:00
NS_ASSERTION(document, "no document");
if (!document) return NS_ERROR_FAILURE;
document->GetBindingManager(getter_AddRefs(bindingManager));
nsCOMPtr<nsIXBLBinding> binding;
bindingManager->GetBinding(aContent, getter_AddRefs(binding));
while (binding) {
// Get the anonymous content.
nsCOMPtr<nsIContent> content;
binding->GetAnonymousContent(getter_AddRefs(content));
if (content) {
PRInt32 childCount;
content->ChildCount(childCount);
for (PRInt32 i = 0; i < childCount; i++) {
nsCOMPtr<nsIContent> anonymousChild;
content->ChildAt(i, *getter_AddRefs(anonymousChild));
if (!(*aResult))
NS_NewISupportsArray(aResult); // This call addrefs the array.
2000-01-15 05:31:45 +00:00
(*aResult)->AppendElement(anonymousChild);
}
binding->GetSingleInsertionPoint(aParent, aMultipleInsertionPoints);
2000-03-11 10:36:39 +00:00
return NS_OK;
}
nsCOMPtr<nsIXBLBinding> nextBinding;
binding->GetBaseBinding(getter_AddRefs(nextBinding));
binding = nextBinding;
}
return NS_OK;
}
2000-03-28 00:41:33 +00:00
NS_IMETHODIMP
2000-06-02 08:13:29 +00:00
nsXBLService::FlushStyleBindings(nsIContent* aContent)
2000-03-28 00:41:33 +00:00
{
nsCOMPtr<nsIDocument> document;
aContent->GetDocument(*getter_AddRefs(document));
nsCOMPtr<nsIBindingManager> bindingManager;
document->GetBindingManager(getter_AddRefs(bindingManager));
2000-04-03 07:13:07 +00:00
nsCOMPtr<nsIXBLBinding> binding;
bindingManager->GetBinding(aContent, getter_AddRefs(binding));
2000-04-03 07:13:07 +00:00
if (binding) {
2000-06-02 08:13:29 +00:00
nsCOMPtr<nsIXBLBinding> styleBinding;
binding->GetFirstStyleBinding(getter_AddRefs(styleBinding));
if (styleBinding) {
// Clear out the script references.
nsCOMPtr<nsIDocument> document;
aContent->GetDocument(*getter_AddRefs(document));
2000-06-22 00:36:19 +00:00
styleBinding->UnhookEventHandlers();
2000-06-02 08:13:29 +00:00
styleBinding->ChangeDocument(document, nsnull);
}
if (styleBinding == binding)
bindingManager->SetBinding(aContent, nsnull); // Flush old style bindings
2000-04-03 07:13:07 +00:00
}
2000-06-02 08:13:29 +00:00
2000-03-28 00:41:33 +00:00
return NS_OK;
}
2000-03-11 10:36:39 +00:00
NS_IMETHODIMP
2000-05-24 08:19:10 +00:00
nsXBLService::ResolveTag(nsIContent* aContent, PRInt32* aNameSpaceID, nsIAtom** aResult)
2000-03-11 10:36:39 +00:00
{
nsCOMPtr<nsIDocument> document;
aContent->GetDocument(*getter_AddRefs(document));
if (document) {
nsCOMPtr<nsIBindingManager> bindingManager;
document->GetBindingManager(getter_AddRefs(bindingManager));
if (bindingManager)
2000-05-24 08:19:10 +00:00
return bindingManager->ResolveTag(aContent, aNameSpaceID, aResult);
}
2000-03-11 10:36:39 +00:00
2000-05-24 08:19:10 +00:00
aContent->GetNameSpaceID(*aNameSpaceID);
aContent->GetTag(*aResult); // Addref happens here.
return NS_OK;
2000-03-11 10:36:39 +00:00
}
NS_IMETHODIMP
nsXBLService::GetXBLDocumentInfo(const nsCString& aURLStr, nsIContent* aBoundElement, nsIXBLDocumentInfo** aResult)
{
*aResult = nsnull;
if (gXULUtils->UseXULCache()) {
// The first line of defense is the chrome cache.
// This cache crosses the entire product, so any XBL bindings that are
// part of chrome will be reused across all XUL documents.
gXULCache->GetXBLDocumentInfo(aURLStr, aResult);
}
if (!*aResult) {
// The second line of defense is the binding manager's document table.
nsCOMPtr<nsIDocument> boundDocument;
aBoundElement->GetDocument(*getter_AddRefs(boundDocument));
nsCOMPtr<nsIBindingManager> bindingManager;
boundDocument->GetBindingManager(getter_AddRefs(bindingManager));
bindingManager->GetXBLDocumentInfo(aURLStr, aResult);
}
return NS_OK;
}
NS_IMETHODIMP
nsXBLService::FlushMemory(PRUint32 reason, size_t requestedAmount)
{
while (!JS_CLIST_IS_EMPTY(&gClassLRUList)) {
JSCList* lru = gClassLRUList.next;
nsXBLJSClass* c = NS_STATIC_CAST(nsXBLJSClass*, lru);
JS_REMOVE_AND_INIT_LINK(lru);
delete c;
gClassLRUListLength--;
if (reason == REASON_ALLOC_FAILURE) {
if (requestedAmount <= sizeof(nsXBLJSClass))
break;
requestedAmount -= sizeof(nsXBLJSClass);
}
}
return NS_OK;
}
// Internal helper methods ////////////////////////////////////////////////////////////////
NS_IMETHODIMP nsXBLService::GetBinding(nsIContent* aBoundElement,
const nsCString& aURLStr,
nsIXBLBinding** aResult)
{
PRBool dummy;
return GetBindingInternal(aBoundElement, aURLStr, PR_FALSE, &dummy, aResult);
}
NS_IMETHODIMP nsXBLService::BindingReady(nsIContent* aBoundElement,
const nsCString& aURLStr,
PRBool* aIsReady)
{
return GetBindingInternal(aBoundElement, aURLStr, PR_TRUE, aIsReady, nsnull);
}
NS_IMETHODIMP nsXBLService::GetBindingInternal(nsIContent* aBoundElement,
const nsCString& aURLStr,
PRBool aPeekOnly,
PRBool* aIsReady,
nsIXBLBinding** aResult)
{
if (aResult)
*aResult = nsnull;
2000-01-13 02:23:54 +00:00
if (aURLStr.IsEmpty())
2000-01-13 02:23:54 +00:00
return NS_ERROR_FAILURE;
// XXX Obtain the # marker and remove it from the URL.
nsCAutoString uri(aURLStr);
PRInt32 indx = uri.RFindChar('#');
nsCAutoString ref;
uri.Right(ref, uri.Length() - (indx + 1));
uri.Truncate(indx);
2000-01-13 02:23:54 +00:00
nsCOMPtr<nsIXBLDocumentInfo> docInfo;
GetBindingDocumentInfo(aBoundElement, uri, ref, getter_AddRefs(docInfo));
if (!docInfo)
2000-01-13 02:23:54 +00:00
return NS_ERROR_FAILURE;
// Get our doc info and determine our script access.
nsCOMPtr<nsIDocument> doc;
docInfo->GetDocument(getter_AddRefs(doc));
PRBool allowScripts;
docInfo->GetScriptAccess(&allowScripts);
2000-01-13 02:23:54 +00:00
// We have a doc. Obtain our specific binding element.
// Walk the children looking for the binding that matches the ref
// specified in the URL.
nsCOMPtr<nsIContent> root = getter_AddRefs(doc->GetRootContent());
if (!root)
return NS_ERROR_FAILURE;
2000-04-16 11:19:26 +00:00
nsAutoString bindingName; bindingName.AssignWithConversion( NS_STATIC_CAST(const char*, ref) );
2000-01-13 02:23:54 +00:00
PRInt32 count;
root->ChildCount(count);
2000-01-13 02:23:54 +00:00
for (PRInt32 i = 0; i < count; i++) {
nsCOMPtr<nsIContent> child;
root->ChildAt(i, *getter_AddRefs(child));
nsAutoString value;
2000-04-19 08:20:20 +00:00
child->GetAttribute(kNameSpaceID_None, nsHTMLAtoms::id, value);
2000-01-13 02:23:54 +00:00
// If no ref is specified just use this.
if ((bindingName.IsEmpty()) || (bindingName == value)) {
2000-01-13 02:23:54 +00:00
// Check for the presence of an extends attribute
nsAutoString extends;
nsCOMPtr<nsIXBLBinding> baseBinding;
child->GetAttribute(kNameSpaceID_None, kExtendsAtom, extends);
value = extends;
if (!extends.IsEmpty()) {
nsAutoString prefix;
PRInt32 offset = extends.FindChar(':');
if (-1 != offset) {
extends.Left(prefix, offset);
extends.Cut(0, offset+1);
}
if (prefix.Length() > 0) {
// Look up the prefix.
nsCOMPtr<nsIAtom> prefixAtom = getter_AddRefs(NS_NewAtom(prefix));
nsCOMPtr<nsINameSpace> nameSpace;
nsCOMPtr<nsIXMLContent> xmlContent(do_QueryInterface(child));
if (xmlContent) {
xmlContent->GetContainingNameSpace(*getter_AddRefs(nameSpace));
if (nameSpace) {
nsCOMPtr<nsINameSpace> tagSpace;
nameSpace->FindNameSpace(prefixAtom, *getter_AddRefs(tagSpace));
if (!tagSpace) {
// We have a base class binding. Load it right now.
nsCAutoString urlCString; urlCString.AssignWithConversion(value);
GetBindingInternal(aBoundElement, urlCString, aPeekOnly, aIsReady, getter_AddRefs(baseBinding));
if (!*aIsReady)
return NS_ERROR_FAILURE; // Binding not yet ready or an error occurred.
}
}
}
2000-03-11 10:36:39 +00:00
}
}
2000-01-13 02:23:54 +00:00
*aIsReady = PR_TRUE;
if (!aPeekOnly) {
// Make a new binding
NS_NewXBLBinding(uri, ref, aResult);
// Initialize its bound element.
(*aResult)->SetBindingElement(child);
(*aResult)->SetAllowScripts(allowScripts);
if (baseBinding)
(*aResult)->SetBaseBinding(baseBinding);
}
2000-01-13 02:23:54 +00:00
break;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsXBLService::GetBindingDocumentInfo(nsIContent* aBoundElement, const nsCString& aURLStr, const nsCString& aRef,
nsIXBLDocumentInfo** aResult)
2000-01-13 02:23:54 +00:00
{
nsresult rv;
2000-01-13 02:23:54 +00:00
*aResult = nsnull;
2000-08-05 22:33:29 +00:00
// We've got a file. Check our XBL document cache.
nsCOMPtr<nsIXBLDocumentInfo> info;
2000-08-05 22:33:29 +00:00
if (gXULUtils->UseXULCache()) {
// The first line of defense is the chrome cache.
// This cache crosses the entire product, so that any XBL bindings that are
// part of chrome will be reused across all XUL documents.
gXULCache->GetXBLDocumentInfo(aURLStr, getter_AddRefs(info));
2000-08-05 22:33:29 +00:00
}
if (!info) {
2000-08-05 22:33:29 +00:00
// The second line of defense is the binding manager's document table.
nsCOMPtr<nsIDocument> boundDocument;
aBoundElement->GetDocument(*getter_AddRefs(boundDocument));
nsCOMPtr<nsIBindingManager> bindingManager;
boundDocument->GetBindingManager(getter_AddRefs(bindingManager));
bindingManager->GetXBLDocumentInfo(aURLStr, getter_AddRefs(info));
2000-08-06 04:57:55 +00:00
nsCOMPtr<nsIAtom> tagName;
aBoundElement->GetTag(*getter_AddRefs(tagName));
if (!info && (tagName.get() != kScrollbarAtom)) {
2000-08-06 04:57:55 +00:00
// The third line of defense is to investigate whether or not the
// document is currently being loaded asynchronously. If so, there's no
// document yet, but we need to glom on our request so that it will be
// processed whenever the doc does finish loading.
2000-08-06 05:03:36 +00:00
nsCOMPtr<nsIStreamListener> listener;
2000-08-06 04:57:55 +00:00
bindingManager->GetLoadingDocListener(aURLStr, getter_AddRefs(listener));
if (listener) {
nsIStreamListener* ilist = listener.get();
nsXBLStreamListener* xblListener = NS_STATIC_CAST(nsXBLStreamListener*, ilist);
2000-08-06 04:57:55 +00:00
// Create a new load observer.
nsCAutoString bindingURI(aURLStr);
bindingURI += "#";
bindingURI += aRef;
if (!xblListener->HasRequest(bindingURI, aBoundElement)) {
nsXBLBindingRequest* req = new (mPool) nsXBLBindingRequest(bindingURI, aBoundElement);
xblListener->AddRequest(req);
}
return NS_OK;
2000-08-05 22:33:29 +00:00
}
2000-08-06 04:57:55 +00:00
}
if (!info) {
2000-08-06 04:57:55 +00:00
// Finally, if all lines of defense fail, we go and fetch the binding
// document.
nsCOMPtr<nsIURL> uri;
nsComponentManager::CreateInstance("component://netscape/network/standard-url",
nsnull,
NS_GET_IID(nsIURL),
getter_AddRefs(uri));
uri->SetSpec(aURLStr);
nsCOMPtr<nsIDocument> document;
2000-08-06 04:57:55 +00:00
FetchBindingDocument(aBoundElement, uri, aRef, getter_AddRefs(document));
if (document) {
NS_NewXBLDocumentInfo(document, getter_AddRefs(info));
2000-08-06 04:57:55 +00:00
// If the doc is a chrome URI, then we put it into the XUL cache.
PRBool cached = PR_FALSE;
if (IsChromeURI(uri) && gXULUtils->UseXULCache()) {
2000-08-06 04:57:55 +00:00
cached = PR_TRUE;
gXULCache->PutXBLDocumentInfo(info);
2000-08-06 04:57:55 +00:00
// Cache whether or not this chrome XBL can execute scripts.
nsCOMPtr<nsIChromeRegistry> reg(do_GetService(kChromeRegistryCID, &rv));
if (NS_SUCCEEDED(rv) && reg) {
PRBool allow = PR_TRUE;
2000-08-06 04:57:55 +00:00
reg->AllowScriptsForSkin(uri, &allow);
info->SetScriptAccess(allow);
2000-08-06 04:57:55 +00:00
}
}
if (!cached) {
// Otherwise we put it in our binding manager's document table.
nsCOMPtr<nsIDocument> boundDocument;
aBoundElement->GetDocument(*getter_AddRefs(boundDocument));
nsCOMPtr<nsIBindingManager> bindingManager;
boundDocument->GetBindingManager(getter_AddRefs(bindingManager));
bindingManager->PutXBLDocumentInfo(info);
2000-08-06 04:57:55 +00:00
}
}
}
}
if (!info)
2000-08-06 04:57:55 +00:00
return NS_OK;
*aResult = info;
NS_IF_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP
2000-08-06 04:57:55 +00:00
nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIURI* aURI, const nsCString& aRef, nsIDocument** aResult)
{
// Initialize our out pointer to nsnull
*aResult = nsnull;
// Create the XML document
nsCOMPtr<nsIDocument> doc;
nsresult rv = nsComponentManager::CreateInstance(kXMLDocumentCID, nsnull,
NS_GET_IID(nsIDocument),
getter_AddRefs(doc));
if (NS_FAILED(rv)) return rv;
2000-01-12 10:55:56 +00:00
// XXX This is evil, but we're living in layout, so I'm
// just going to do it.
nsXMLDocument* xmlDoc = (nsXMLDocument*)(doc.get());
// Now we have to synchronously load the binding file.
2000-01-12 10:55:56 +00:00
// Create an XML content sink and a parser.
nsCOMPtr<nsIDocument> boundDoc;
aBoundElement->GetDocument(*getter_AddRefs(boundDoc));
nsCOMPtr<nsILoadGroup> loadGroup;
boundDoc->GetDocumentLoadGroup(getter_AddRefs(loadGroup));
nsCOMPtr<nsIChannel> channel;
rv = NS_OpenURI(getter_AddRefs(channel), aURI, nsnull, loadGroup);
if (NS_FAILED(rv)) return rv;
2000-01-12 10:55:56 +00:00
// Call StartDocumentLoad
nsCOMPtr<nsIStreamListener> listener;
if (NS_FAILED(rv = xmlDoc->StartDocumentLoad("loadAsData", channel,
loadGroup, nsnull, getter_AddRefs(listener)))) {
2000-01-12 10:55:56 +00:00
NS_ERROR("Failure to init XBL doc prior to load.");
return rv;
}
nsCOMPtr<nsIAtom> tagName;
aBoundElement->GetTag(*getter_AddRefs(tagName));
if (tagName != kScrollbarAtom) {
// We can be asynchronous
2000-08-06 04:57:55 +00:00
nsXBLStreamListener* xblListener = new nsXBLStreamListener(listener, boundDoc, doc);
nsCOMPtr<nsIDOMEventReceiver> rec(do_QueryInterface(doc));
rec->AddEventListener(NS_ConvertASCIItoUCS2("load"), (nsIDOMLoadListener*)xblListener, PR_FALSE);
2000-08-06 04:57:55 +00:00
// Add ourselves to the list of loading docs.
nsCOMPtr<nsIDocument> boundDocument;
aBoundElement->GetDocument(*getter_AddRefs(boundDocument));
nsCOMPtr<nsIBindingManager> bindingManager;
boundDocument->GetBindingManager(getter_AddRefs(bindingManager));
nsXPIDLCString uri;
aURI->GetSpec(getter_Copies(uri));
bindingManager->PutLoadingDocListener((const char*)uri, xblListener);
// Add our request.
nsCAutoString bindingURI(uri);
bindingURI += "#";
bindingURI += aRef;
nsXBLBindingRequest* req = new (mPool) nsXBLBindingRequest(bindingURI, aBoundElement);
2000-08-06 04:57:55 +00:00
xblListener->AddRequest(req);
// Now kick off the async read.
channel->AsyncRead(xblListener, nsnull);
2000-08-04 08:45:29 +00:00
return NS_OK;
}
// Now do a blocking synchronous parse of the file.
nsCOMPtr<nsIInputStream> in;
PRUint32 sourceOffset = 0;
rv = channel->OpenInputStream(getter_AddRefs(in));
// If we couldn't open the channel, then just return.
if (NS_FAILED(rv)) return NS_OK;
NS_ASSERTION(in != nsnull, "no input stream");
if (! in) return NS_ERROR_FAILURE;
rv = NS_ERROR_OUT_OF_MEMORY;
nsProxyStream* proxy = new nsProxyStream();
if (! proxy)
return NS_ERROR_FAILURE;
listener->OnStartRequest(channel, nsnull);
while (PR_TRUE) {
char buf[1024];
PRUint32 readCount;
if (NS_FAILED(rv = in->Read(buf, sizeof(buf), &readCount)))
break; // error
if (readCount == 0)
break; // eof
proxy->SetBuffer(buf, readCount);
rv = listener->OnDataAvailable(channel, nsnull, proxy, sourceOffset, readCount);
sourceOffset += readCount;
if (NS_FAILED(rv))
break;
}
listener->OnStopRequest(channel, nsnull, NS_OK, nsnull);
// don't leak proxy!
proxy->Close();
delete proxy;
// The document is parsed. We now have a prototype document.
// Everything worked, so we can just hand this back now.
*aResult = doc;
NS_IF_ADDREF(*aResult);
// The XML content sink produces a ridiculous # of content nodes.
// It generates text nodes even for whitespace. The following
// call walks the generated document tree and trims out these
// nodes.
nsCOMPtr<nsIContent> root = getter_AddRefs(doc->GetRootContent());
if (root)
StripWhitespaceNodes(root);
return NS_OK;
}
nsresult
nsXBLService::StripWhitespaceNodes(nsIContent* aElement)
{
PRInt32 childCount;
aElement->ChildCount(childCount);
for (PRInt32 i = 0; i < childCount; i++) {
nsCOMPtr<nsIContent> child;
aElement->ChildAt(i, *getter_AddRefs(child));
nsCOMPtr<nsITextContent> text = do_QueryInterface(child);
if (text) {
nsAutoString result;
text->CopyText(result);
result.StripWhitespace();
if (result.IsEmpty()) {
// This node contained nothing but whitespace.
// Remove it from the content model.
aElement->RemoveChildAt(i, PR_TRUE);
i--; // Decrement our count, since we just removed this child.
childCount--; // Also decrement our total count.
}
}
else StripWhitespaceNodes(child);
}
2000-01-12 09:32:29 +00:00
return NS_OK;
}
// Creation Routine ///////////////////////////////////////////////////////////////////////
nsresult
NS_NewXBLService(nsIXBLService** aResult)
{
*aResult = new nsXBLService;
if (!*aResult)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult);
return NS_OK;
}