diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 2605293b22a..fab5aa034a2 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -52,13 +52,13 @@ #include "nsCompatibility.h" #include "nsTObserverArray.h" #include "nsNodeInfoManager.h" +#include "nsIStreamListener.h" +#include "nsIObserver.h" class nsIContent; class nsPresContext; class nsIPresShell; class nsIDocShell; -class nsIStreamListener; -class nsIStreamObserver; class nsStyleSet; class nsIStyleSheet; class nsIStyleRule; @@ -77,7 +77,6 @@ class nsIChannel; class nsIPrincipal; class nsIDOMDocument; class nsIDOMDocumentType; -class nsIObserver; class nsScriptLoader; class nsIContentSink; class nsIScriptEventManager; @@ -97,8 +96,8 @@ class nsFrameLoader; // IID for the nsIDocument interface #define NS_IDOCUMENT_IID \ - { 0xd5b1e3c5, 0x85dc, 0x403e, \ - { 0xbb, 0x4a, 0x54, 0x66, 0xdc, 0xbe, 0x15, 0x69 } } +{ 0x189ebc9e, 0x779b, 0x4c49, \ + { 0x90, 0x8b, 0x9a, 0x80, 0x25, 0x9b, 0xaf, 0xa7 } } // Flag for AddStyleSheet(). #define NS_STYLESHEET_FROM_CATALOG (1 << 0) @@ -1004,6 +1003,98 @@ public: virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) = 0; // Returns true if the frame loader of aShell is in the finalization list. virtual PRBool FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell) = 0; + + /** + * Check whether this document is a root document that is not an + * external resource. + */ + PRBool IsRootDisplayDocument() const + { + return !mParentDocument && !mDisplayDocument; + } + + /** + * Get the document for which this document is an external resource. This + * will be null if this document is not an external resource. Otherwise, + * GetDisplayDocument() will return a non-null document, and + * GetDisplayDocument()->GetDisplayDocument() is guaranteed to be null. + */ + nsIDocument* GetDisplayDocument() const + { + return mDisplayDocument; + } + + /** + * Set the display document for this document. aDisplayDocument must not be + * null. + */ + void SetDisplayDocument(nsIDocument* aDisplayDocument) + { + NS_PRECONDITION(!GetPrimaryShell() && + !nsCOMPtr(GetContainer()) && + !GetWindow() && + !GetScriptGlobalObject(), + "Shouldn't set mDisplayDocument on documents that already " + "have a presentation or a docshell or a window"); + NS_PRECONDITION(aDisplayDocument != this, "Should be different document"); + NS_PRECONDITION(!aDisplayDocument->GetDisplayDocument(), + "Display documents should not nest"); + mDisplayDocument = aDisplayDocument; + } + + /** + * A class that represents an external resource load that has begun but + * doesn't have a document yet. Observers can be registered on this object, + * and will be notified after the document is created. Observers registered + * after the document has been created will NOT be notified. When observers + * are notified, the subject will be the newly-created document, the topic + * will be "external-resource-document-created", and the data will be null. + * If document creation fails for some reason, observers will still be + * notified, with a null document pointer. + */ + class ExternalResourceLoad : public nsISupports + { + public: + virtual ~ExternalResourceLoad() {} + + void AddObserver(nsIObserver* aObserver) { + NS_PRECONDITION(aObserver, "Must have observer"); + mObservers.AppendElement(aObserver); + } + + const nsTArray< nsCOMPtr > & Observers() { + return mObservers; + } + protected: + nsAutoTArray< nsCOMPtr, 8 > mObservers; + }; + + /** + * Request an external resource document for aURI. This will return the + * resource document if available. If one is not available yet, it will + * start loading as needed, and the pending load object will be returned in + * aPendingLoad so that the caller can register an observer to wait for the + * load. If this function returns null and doesn't return a pending load, + * that means that there is no resource document for this URI and won't be + * one in the future. + * + * @param aURI the URI to get + * @param aRequestingNode the node making the request + * @param aPendingLoad the pending load for this request, if any + */ + virtual nsIDocument* + RequestExternalResource(nsIURI* aURI, + nsINode* aRequestingNode, + ExternalResourceLoad** aPendingLoad) = 0; + + /** + * Enumerate the external resource documents associated with this document. + * The enumerator callback should return PR_TRUE to continue enumerating, or + * PR_FALSE to stop. + */ + virtual void EnumerateExternalResources(nsSubDocEnumFunc aCallback, + void* aData) = 0; + protected: ~nsIDocument() { @@ -1100,6 +1191,11 @@ protected: nsCOMArray mSubtreeModifiedTargets; PRUint32 mSubtreeModifiedDepth; + // If we're an external resource document, this will be non-null and will + // point to our "display document": the one that all resource lookups should + // go to. + nsCOMPtr mDisplayDocument; + private: // JSObject cache. Only to be used for performance // optimizations. This will be set once this document is touched diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index 4941911c9fd..9980d97e4d5 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -2911,6 +2911,10 @@ nsContentUtils::IsInChromeDocshell(nsIDocument *aDocument) return PR_FALSE; } + if (aDocument->GetDisplayDocument()) { + return IsInChromeDocshell(aDocument->GetDisplayDocument()); + } + nsCOMPtr docContainer = aDocument->GetContainer(); nsCOMPtr docShell(do_QueryInterface(docContainer)); PRInt32 itemType = nsIDocShellTreeItem::typeContent; diff --git a/content/base/src/nsDataDocumentContentPolicy.cpp b/content/base/src/nsDataDocumentContentPolicy.cpp index 4bc8ad4d6d9..4f198cfe6a6 100644 --- a/content/base/src/nsDataDocumentContentPolicy.cpp +++ b/content/base/src/nsDataDocumentContentPolicy.cpp @@ -72,7 +72,28 @@ nsDataDocumentContentPolicy::ShouldLoad(PRUint32 aContentType, doc = do_QueryInterface(domDoc); } } - if (aContentType != nsIContentPolicy::TYPE_DTD && doc && doc->IsLoadedAsData()) { + + // DTDs are always OK to load + if (!doc || aContentType == nsIContentPolicy::TYPE_DTD) { + return NS_OK; + } + + // Nothing else is OK to load for data documents + if (doc->IsLoadedAsData()) { + *aDecision = nsIContentPolicy::REJECT_TYPE; + return NS_OK; + } + + // Allow all loads for non-external-resource documents + if (!doc->GetDisplayDocument()) { + return NS_OK; + } + + // For external resources, blacklist some load types + if (aContentType == nsIContentPolicy::TYPE_OBJECT || + aContentType == nsIContentPolicy::TYPE_DOCUMENT || + aContentType == nsIContentPolicy::TYPE_SUBDOCUMENT || + aContentType == nsIContentPolicy::TYPE_SCRIPT) { *aDecision = nsIContentPolicy::REJECT_TYPE; } diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 8c15fbab17b..7dab88d4bd0 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -157,11 +157,23 @@ static NS_DEFINE_CID(kDOMEventGroupCID, NS_DOMEVENTGROUP_CID); #include "nsCycleCollector.h" #include "nsCCUncollectableMarker.h" #include "nsIContentPolicy.h" +#include "nsContentPolicyUtils.h" +#include "nsICategoryManager.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIContentViewer.h" +#include "nsIXMLContentSink.h" +#include "nsIChannelEventSink.h" +#include "nsContentErrors.h" +#include "nsIXULDocument.h" +#include "nsIProgressEventSink.h" +#include "nsISecurityEventSink.h" +#include "nsIPrompt.h" #include "nsFrameLoader.h" #include "mozAutoDocUpdate.h" + #ifdef MOZ_LOGGING // so we can get logging even in release builds #define FORCE_PR_LOG 1 @@ -718,6 +730,433 @@ nsOnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) return NS_OK; } +// ================================================================== + +nsExternalResourceMap::nsExternalResourceMap() + : mHaveShutDown(PR_FALSE) +{ + mMap.Init(); + mPendingLoads.Init(); +} + +nsIDocument* +nsExternalResourceMap::RequestResource(nsIURI* aURI, + nsINode* aRequestingNode, + nsDocument* aDisplayDocument, + ExternalResourceLoad** aPendingLoad) +{ + // If we ever start allowing non-same-origin loads here, we might need to do + // something interesting with aRequestingPrincipal even for the hashtable + // gets. + NS_PRECONDITION(aURI, "Must have a URI"); + NS_PRECONDITION(aRequestingNode, "Must have a node"); + *aPendingLoad = nsnull; + if (mHaveShutDown) { + return nsnull; + } + + // First, make sure we strip the ref from aURI. + nsCOMPtr clone; + aURI->Clone(getter_AddRefs(clone)); + if (!clone) { + return nsnull; + } + nsCOMPtr url(do_QueryInterface(clone)); + if (url) { + url->SetRef(EmptyCString()); + } + + ExternalResource* resource; + mMap.Get(clone, &resource); + if (resource) { + return resource->mDocument; + } + + nsRefPtr load; + mPendingLoads.Get(clone, getter_AddRefs(load)); + if (load) { + NS_ADDREF(*aPendingLoad = load); + return nsnull; + } + + load = new PendingLoad(aDisplayDocument); + if (!load) { + return nsnull; + } + + if (!mPendingLoads.Put(clone, load)) { + return nsnull; + } + + if (NS_FAILED(load->StartLoad(clone, aRequestingNode))) { + // Make sure we don't thrash things by trying this load again, since + // chances are it failed for good reasons (security check, etc). + AddExternalResource(clone, nsnull, nsnull, aDisplayDocument); + } else { + NS_ADDREF(*aPendingLoad = load); + } + + return nsnull; +} + +struct +nsExternalResourceEnumArgs +{ + nsIDocument::nsSubDocEnumFunc callback; + void *data; +}; + +PR_STATIC_CALLBACK(PLDHashOperator) +ExternalResourceEnumerator(nsIURI* aKey, + nsExternalResourceMap::ExternalResource* aData, + void* aClosure) +{ + nsExternalResourceEnumArgs* args = + static_cast(aClosure); + PRBool next = args->callback(aData->mDocument, args->data); + return next ? PL_DHASH_NEXT : PL_DHASH_STOP; +} + +void +nsExternalResourceMap::EnumerateResources(nsIDocument::nsSubDocEnumFunc aCallback, + void* aData) +{ + nsExternalResourceEnumArgs args = { aCallback, aData }; + mMap.EnumerateRead(ExternalResourceEnumerator, &args); +} + +PR_STATIC_CALLBACK(PLDHashOperator) +ExternalResourceTraverser(nsIURI* aKey, + nsExternalResourceMap::ExternalResource* aData, + void* aClosure) +{ + nsCycleCollectionTraversalCallback *cb = + static_cast(aClosure); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mExternalResourceMap.mMap entry" + "->mDocument"); + cb->NoteXPCOMChild(aData->mDocument); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mExternalResourceMap.mMap entry" + "->mViewer"); + cb->NoteXPCOMChild(aData->mViewer); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mExternalResourceMap.mMap entry" + "->mLoadGroup"); + cb->NoteXPCOMChild(aData->mLoadGroup); + + return PL_DHASH_NEXT; +} + +void +nsExternalResourceMap::Traverse(nsCycleCollectionTraversalCallback* aCallback) const +{ + // mPendingLoads will get cleared out as the requests complete, so + // no need to worry about those here. + mMap.EnumerateRead(ExternalResourceTraverser, aCallback); +} + +nsresult +nsExternalResourceMap::AddExternalResource(nsIURI* aURI, + nsIDocumentViewer* aViewer, + nsILoadGroup* aLoadGroup, + nsIDocument* aDisplayDocument) +{ + NS_PRECONDITION(aURI, "Unexpected call"); + NS_PRECONDITION((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup), + "Must have both or neither"); + + nsRefPtr load; + mPendingLoads.Get(aURI, getter_AddRefs(load)); + mPendingLoads.Remove(aURI); + + nsresult rv = NS_OK; + + nsCOMPtr doc; + if (aViewer) { + aViewer->GetDocument(getter_AddRefs(doc)); + NS_ASSERTION(doc, "Must have a document"); + + nsCOMPtr xulDoc = do_QueryInterface(doc); + if (xulDoc) { + // We don't handle XUL stuff here yet. + rv = NS_ERROR_NOT_AVAILABLE; + } else { + doc->SetDisplayDocument(aDisplayDocument); + + rv = aViewer->Init(nsnull, nsRect(0, 0, 0, 0)); + if (NS_SUCCEEDED(rv)) { + rv = aViewer->Open(nsnull, nsnull); + } + } + + if (NS_FAILED(rv)) { + doc = nsnull; + aViewer = nsnull; + aLoadGroup = nsnull; + } + } + + ExternalResource* newResource = new ExternalResource(); + if (newResource && !mMap.Put(aURI, newResource)) { + delete newResource; + newResource = nsnull; + if (NS_SUCCEEDED(rv)) { + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + + if (newResource) { + newResource->mDocument = doc; + newResource->mViewer = aViewer; + newResource->mLoadGroup = aLoadGroup; + } + + const nsTArray< nsCOMPtr > & obs = load->Observers(); + for (PRUint32 i = 0; i < obs.Length(); ++i) { + obs[i]->Observe(doc, "external-resource-document-created", nsnull); + } + + return rv; +} + +NS_IMPL_ISUPPORTS2(nsExternalResourceMap::PendingLoad, + nsIStreamListener, + nsIRequestObserver) + +NS_IMETHODIMP +nsExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + nsExternalResourceMap& map = mDisplayDocument->ExternalResourceMap(); + if (map.HaveShutDown()) { + return NS_BINDING_ABORTED; + } + + nsCOMPtr viewer; + nsCOMPtr loadGroup; + nsresult rv = SetupViewer(aRequest, getter_AddRefs(viewer), + getter_AddRefs(loadGroup)); + + // Make sure to do this no matter what + nsresult rv2 = map.AddExternalResource(mURI, viewer, loadGroup, + mDisplayDocument); + if (NS_FAILED(rv)) { + return rv; + } + if (NS_FAILED(rv2)) { + mTargetListener = nsnull; + return rv2; + } + + return mTargetListener->OnStartRequest(aRequest, aContext); +} + +nsresult +nsExternalResourceMap::PendingLoad::SetupViewer(nsIRequest* aRequest, + nsIDocumentViewer** aViewer, + nsILoadGroup** aLoadGroup) +{ + NS_PRECONDITION(!mTargetListener, "Unexpected call to OnStartRequest"); + *aViewer = nsnull; + *aLoadGroup = nsnull; + + nsCOMPtr chan(do_QueryInterface(aRequest)); + NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); + + nsCOMPtr httpChannel(do_QueryInterface(aRequest)); + if (httpChannel) { + PRBool requestSucceeded; + if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) || + !requestSucceeded) { + // Bail out on this load, since it looks like we have an HTTP error page + return NS_BINDING_ABORTED; + } + } + + nsCAutoString type; + chan->GetContentType(type); + + nsCOMPtr loadGroup; + chan->GetLoadGroup(getter_AddRefs(loadGroup)); + + // Give this document its own loadgroup + nsCOMPtr newLoadGroup = + do_CreateInstance(NS_LOADGROUP_CONTRACTID); + NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY); + newLoadGroup->SetLoadGroup(loadGroup); + + nsCOMPtr callbacks; + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + + nsCOMPtr newCallbacks = + new LoadgroupCallbacks(callbacks); + newLoadGroup->SetNotificationCallbacks(newCallbacks); + + // This is some serious hackery cribbed from docshell + nsCOMPtr catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE); + nsXPIDLCString contractId; + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", type.get(), + getter_Copies(contractId)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr docLoaderFactory = + do_GetService(contractId); + NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr viewer; + nsCOMPtr listener; + rv = docLoaderFactory->CreateInstance("external-resource", chan, newLoadGroup, + type.get(), nsnull, nsnull, + getter_AddRefs(listener), + getter_AddRefs(viewer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr docViewer = do_QueryInterface(viewer); + NS_ENSURE_TRUE(docViewer, NS_ERROR_UNEXPECTED); + + nsCOMPtr parser = do_QueryInterface(listener); + if (!parser) { + /// We don't want to deal with the various fake documents yet + return NS_ERROR_NOT_IMPLEMENTED; + } + + // We can't handle HTML and other weird things here yet. + nsIContentSink* sink = parser->GetContentSink(); + nsCOMPtr xmlSink = do_QueryInterface(sink); + if (!xmlSink) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + listener.swap(mTargetListener); + docViewer.swap(*aViewer); + newLoadGroup.swap(*aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + PRUint32 aOffset, + PRUint32 aCount) +{ + NS_PRECONDITION(mTargetListener, "Shouldn't be getting called!"); + if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) { + return NS_BINDING_ABORTED; + } + return mTargetListener->OnDataAvailable(aRequest, aContext, aStream, aOffset, + aCount); +} + +NS_IMETHODIMP +nsExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus) +{ + // mTargetListener might be null if SetupViewer or AddExternalResource failed + if (mTargetListener) { + nsCOMPtr listener; + mTargetListener.swap(listener); + return listener->OnStopRequest(aRequest, aContext, aStatus); + } + + return NS_OK; +} + +nsresult +nsExternalResourceMap::PendingLoad::StartLoad(nsIURI* aURI, + nsINode* aRequestingNode) +{ + NS_PRECONDITION(aURI, "Must have a URI"); + NS_PRECONDITION(aRequestingNode, "Must have a node"); + + // Time to start a load. First, the security checks. + + nsIPrincipal* requestingPrincipal = aRequestingNode->NodePrincipal(); + + nsresult rv = nsContentUtils::GetSecurityManager()-> + CheckLoadURIWithPrincipal(requestingPrincipal, aURI, + nsIScriptSecurityManager::STANDARD); + NS_ENSURE_SUCCESS(rv, rv); + + rv = requestingPrincipal->CheckMayLoad(aURI, PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt16 shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_OTHER, + aURI, + requestingPrincipal, + aRequestingNode, + EmptyCString(), //mime guess + nsnull, //extra + &shouldLoad, + nsContentUtils::GetContentPolicy(), + nsContentUtils::GetSecurityManager()); + if (NS_FAILED(rv)) return rv; + if (NS_CP_REJECTED(shouldLoad)) { + // Disallowed by content policy + return NS_ERROR_CONTENT_BLOCKED; + } + + nsIDocument* doc = aRequestingNode->GetOwnerDoc(); + if (!doc) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr loadGroup = doc->GetDocumentLoadGroup(); + nsCOMPtr channel; + rv = NS_NewChannel(getter_AddRefs(channel), aURI, nsnull, loadGroup); + NS_ENSURE_SUCCESS(rv, rv); + + mURI = aURI; + + nsCOMPtr req = nsContentUtils::GetSameOriginChecker(); + NS_ENSURE_TRUE(req, NS_ERROR_OUT_OF_MEMORY); + + channel->SetNotificationCallbacks(req); + return channel->AsyncOpen(this, nsnull); +} + +NS_IMPL_ISUPPORTS1(nsExternalResourceMap::LoadgroupCallbacks, + nsIInterfaceRequestor) + +NS_IMETHODIMP +nsExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID & aIID, + void **aSink) +{ +#define IID_IS(_i) aIID.Equals(NS_GET_IID(_i)) + if (mCallbacks && + (IID_IS(nsIProgressEventSink) || + IID_IS(nsIChannelEventSink) || + IID_IS(nsISecurityEventSink) || + IID_IS(nsIPrompt) || + IID_IS(nsIAuthPrompt) || + IID_IS(nsIAuthPrompt2) || + IID_IS(nsIApplicationCacheContainer) || + // XXXbz evil hack for cookies for now + IID_IS(nsIDOMWindow) || + IID_IS(nsIDocShellTreeItem))) { + return mCallbacks->GetInterface(aIID, aSink); + } +#undef IID_IS + + *aSink = nsnull; + return NS_NOINTERFACE; +} + +nsExternalResourceMap::ExternalResource::~ExternalResource() +{ + if (mViewer) { + mViewer->Close(nsnull); + mViewer->Destroy(); + } +} + // ================================================================== // = // ================================================================== @@ -1194,7 +1633,8 @@ SubDocTraverser(PLDHashTable *table, PLDHashEntryHdr *hdr, PRUint32 number, } PR_STATIC_CALLBACK(PLDHashOperator) -RadioGroupsTraverser(const nsAString& aKey, nsAutoPtr& aData, void* aClosure) +RadioGroupsTraverser(const nsAString& aKey, nsRadioGroupStruct* aData, + void* aClosure) { nsCycleCollectionTraversalCallback *cb = static_cast(aClosure); @@ -1261,6 +1701,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDocument) tmp->mIdentifierMap.EnumerateEntries(IdentifierMapEntryTraverse, &cb); + tmp->mExternalResourceMap.Traverse(&cb); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNodeInfo) // Traverse the mChildren nsAttrAndChildArray. @@ -1276,6 +1718,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mNodeInfoManager, nsNodeInfoManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSecurityInfo) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDisplayDocument) // Traverse all nsDocument nsCOMPtrs. NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mParser) @@ -1284,7 +1727,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDOMStyleSheets) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptLoader) - tmp->mRadioGroups.Enumerate(RadioGroupsTraverser, &cb); + tmp->mRadioGroups.EnumerateRead(RadioGroupsTraverser, &cb); // The boxobject for an element will only exist as long as it's in the // document, so we'll traverse the table here instead of from the element. @@ -1326,6 +1769,9 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument) // from the doc. tmp->DestroyLinkMap(); + // Clear out our external resources + tmp->mExternalResourceMap.Shutdown(); + nsAutoScriptBlocker scriptBlocker; // Unlink the mChildren nsAttrAndChildArray. @@ -1336,6 +1782,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument) } NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCachedRootContent) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDisplayDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK_USERDATA @@ -1711,6 +2158,9 @@ nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, // styles CSSLoader()->SetEnabled(PR_FALSE); // Do not load/process styles when loading as data + } else if (nsCRT::strcmp("external-resource", aCommand) == 0) { + // Allow CSS, but not scripts + ScriptLoader()->SetEnabled(PR_FALSE); } mMayStartLayout = PR_FALSE; @@ -4703,6 +5153,29 @@ nsDocument::FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell) return PR_FALSE; } +nsIDocument* +nsDocument::RequestExternalResource(nsIURI* aURI, + nsINode* aRequestingNode, + ExternalResourceLoad** aPendingLoad) +{ + NS_PRECONDITION(aURI, "Must have a URI"); + NS_PRECONDITION(aRequestingNode, "Must have a node"); + if (mDisplayDocument) { + return mDisplayDocument->RequestExternalResource(aURI, + aRequestingNode, + aPendingLoad); + } + + return mExternalResourceMap.RequestResource(aURI, aRequestingNode, + this, aPendingLoad); +} + +void +nsDocument::EnumerateExternalResources(nsSubDocEnumFunc aCallback, void* aData) +{ + mExternalResourceMap.EnumerateResources(aCallback, aData); +} + struct DirTable { const char* mName; PRUint8 mValue; @@ -5860,20 +6333,20 @@ PRBool nsDocument::IsScriptEnabled() { nsCOMPtr sm(do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID)); - NS_ENSURE_TRUE(sm, PR_TRUE); + NS_ENSURE_TRUE(sm, PR_FALSE); nsIScriptGlobalObject* globalObject = GetScriptGlobalObject(); - NS_ENSURE_TRUE(globalObject, PR_TRUE); + NS_ENSURE_TRUE(globalObject, PR_FALSE); nsIScriptContext *scriptContext = globalObject->GetContext(); - NS_ENSURE_TRUE(scriptContext, PR_TRUE); + NS_ENSURE_TRUE(scriptContext, PR_FALSE); JSContext* cx = (JSContext *) scriptContext->GetNativeContext(); - NS_ENSURE_TRUE(cx, PR_TRUE); + NS_ENSURE_TRUE(cx, PR_FALSE); PRBool enabled; nsresult rv = sm->CanExecuteScripts(cx, NodePrincipal(), &enabled); - NS_ENSURE_SUCCESS(rv, PR_TRUE); + NS_ENSURE_SUCCESS(rv, PR_FALSE); return enabled; } @@ -6396,6 +6869,11 @@ nsDocument::Destroy() nsContentList::OnDocumentDestroy(this); + // Shut down our external resource map. We might not need this for + // leak-fixing if we fix DocumentViewerImpl to do cycle-collection, but + // tearing down all those frame trees right now is the right thing to do. + mExternalResourceMap.Shutdown(); + // XXX We really should let cycle collection do this, but that currently still // leaks (see https://bugzilla.mozilla.org/show_bug.cgi?id=406684). // When we start relying on cycle collection again we should remove the @@ -6437,6 +6915,11 @@ nsDocument::GetLayoutHistoryState() const void nsDocument::BlockOnload() { + if (mDisplayDocument) { + mDisplayDocument->BlockOnload(); + return; + } + // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup // -- it's not ours. if (mOnloadBlockCount == 0 && mScriptGlobalObject) { @@ -6451,6 +6934,11 @@ nsDocument::BlockOnload() void nsDocument::UnblockOnload(PRBool aFireSync) { + if (mDisplayDocument) { + mDisplayDocument->UnblockOnload(aFireSync); + return; + } + if (mOnloadBlockCount == 0) { NS_NOTREACHED("More UnblockOnload() calls than BlockOnload() calls; dropping call"); return; @@ -6498,12 +6986,14 @@ nsDocument::PostUnblockOnloadEvent() void nsDocument::DoUnblockOnload() { - NS_ASSERTION(mOnloadBlockCount != 0, - "Shouldn't have a count of zero here, since we stabilized in " - "PostUnblockOnloadEvent"); + NS_PRECONDITION(!mDisplayDocument, + "Shouldn't get here for resource document"); + NS_PRECONDITION(mOnloadBlockCount != 0, + "Shouldn't have a count of zero here, since we stabilized in " + "PostUnblockOnloadEvent"); --mOnloadBlockCount; - + if (mOnloadBlockCount != 0) { // We blocked again after the last unblock. Nothing to do here. We'll // post a new event when we unblock again. diff --git a/content/base/src/nsDocument.h b/content/base/src/nsDocument.h index 7346d682265..640b3ad73f3 100644 --- a/content/base/src/nsDocument.h +++ b/content/base/src/nsDocument.h @@ -108,6 +108,8 @@ #include "nsPresShellIterator.h" #include "nsContentUtils.h" #include "nsThreadUtils.h" +#include "nsIDocumentViewer.h" +#include "nsIInterfaceRequestor.h" #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0) #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1) @@ -383,6 +385,117 @@ private: ~nsOnloadBlocker() {} }; +class nsExternalResourceMap +{ +public: + typedef nsIDocument::ExternalResourceLoad ExternalResourceLoad; + nsExternalResourceMap(); + + /** + * Request an external resource document. This does exactly what + * nsIDocument::RequestExternalResource is documented to do. + */ + nsIDocument* RequestResource(nsIURI* aURI, + nsINode* aRequestingNode, + nsDocument* aDisplayDocument, + ExternalResourceLoad** aPendingLoad); + + /** + * Enumerate the resource documents. See + * nsIDocument::EnumerateExternalResources. + */ + void EnumerateResources(nsIDocument::nsSubDocEnumFunc aCallback, void* aData); + + /** + * Traverse ourselves for cycle-collection + */ + void Traverse(nsCycleCollectionTraversalCallback* aCallback) const; + + /** + * Shut ourselves down (used for cycle-collection unlink), as well + * as for document destruction. + */ + void Shutdown() + { + mPendingLoads.Clear(); + mMap.Clear(); + mHaveShutDown = PR_TRUE; + } + + PRBool HaveShutDown() const + { + return mHaveShutDown; + } + + // Needs to be public so we can traverse them sanely + struct ExternalResource + { + ~ExternalResource(); + nsCOMPtr mDocument; + nsCOMPtr mViewer; + nsCOMPtr mLoadGroup; + }; + +protected: + class PendingLoad : public ExternalResourceLoad, + public nsIStreamListener + { + public: + PendingLoad(nsDocument* aDisplayDocument) : + mDisplayDocument(aDisplayDocument) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + /** + * Start aURI loading. This will perform the necessary security checks and + * so forth. + */ + nsresult StartLoad(nsIURI* aURI, nsINode* aRequestingNode); + + /** + * Set up an nsIDocumentViewer based on aRequest. This is guaranteed to + * put null in *aViewer and *aLoadGroup on all failures. + */ + nsresult SetupViewer(nsIRequest* aRequest, nsIDocumentViewer** aViewer, + nsILoadGroup** aLoadGroup); + + private: + nsRefPtr mDisplayDocument; + nsCOMPtr mTargetListener; + nsCOMPtr mURI; + }; + friend class PendingLoad; + + class LoadgroupCallbacks : public nsIInterfaceRequestor + { + public: + LoadgroupCallbacks(nsIInterfaceRequestor* aOtherCallbacks) + : mCallbacks(aOtherCallbacks) + {} + NS_DECL_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + private: + nsCOMPtr mCallbacks; + }; + + /** + * Add an ExternalResource for aURI. aViewer and aLoadGroup might be null + * when this is called if the URI didn't result in an XML document. This + * function makes sure to remove the pending load for aURI, if any, from our + * hashtable, and to notify its observers, if any. + */ + nsresult AddExternalResource(nsIURI* aURI, nsIDocumentViewer* aViewer, + nsILoadGroup* aLoadGroup, + nsIDocument* aDisplayDocument); + + nsClassHashtable mMap; + nsRefPtrHashtable mPendingLoads; + PRPackedBool mHaveShutDown; +}; + // Base class for our document implementations. // // Note that this class *implements* nsIDOMXMLDocument, but it's not @@ -802,6 +915,12 @@ public: virtual NS_HIDDEN_(nsresult) FinalizeFrameLoader(nsFrameLoader* aLoader); virtual NS_HIDDEN_(void) TryCancelFrameLoaderInitialization(nsIDocShell* aShell); virtual NS_HIDDEN_(PRBool) FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell); + virtual NS_HIDDEN_(nsIDocument*) + RequestExternalResource(nsIURI* aURI, + nsINode* aRequestingNode, + ExternalResourceLoad** aPendingLoad); + virtual NS_HIDDEN_(void) + EnumerateExternalResources(nsSubDocEnumFunc aCallback, void* aData); NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDocument, nsIDocument) @@ -815,6 +934,11 @@ public: void DoNotifyPossibleTitleChange(); + nsExternalResourceMap& ExternalResourceMap() + { + return mExternalResourceMap; + } + void SetLoadedAsData(PRBool aLoadedAsData) { mLoadedAsData = aLoadedAsData; } nsresult CloneDocHelper(nsDocument* clone) const; @@ -1087,6 +1211,8 @@ private: nsTArray > mFinalizableFrameLoaders; nsRevocableEventPtr > mPendingTitleChangeEvent; + + nsExternalResourceMap mExternalResourceMap; }; #endif /* nsDocument_h___ */ diff --git a/content/base/src/nsFrameLoader.cpp b/content/base/src/nsFrameLoader.cpp index cdb28b0de3e..8194947827d 100644 --- a/content/base/src/nsFrameLoader.cpp +++ b/content/base/src/nsFrameLoader.cpp @@ -727,6 +727,11 @@ nsFrameLoader::EnsureDocShell() return NS_ERROR_UNEXPECTED; } + if (doc->GetDisplayDocument()) { + // Don't allow subframe loads in external reference documents + return NS_ERROR_NOT_AVAILABLE; + } + nsCOMPtr parentAsWebNav = do_GetInterface(doc->GetScriptGlobalObject()); diff --git a/content/svg/document/src/Makefile.in b/content/svg/document/src/Makefile.in index 17f28e5c81d..85e6653d690 100644 --- a/content/svg/document/src/Makefile.in +++ b/content/svg/document/src/Makefile.in @@ -54,6 +54,7 @@ REQUIRES = \ thebes \ dom \ webshell \ + docshell \ htmlparser \ necko \ pref \ diff --git a/content/xul/content/src/nsXULElement.cpp b/content/xul/content/src/nsXULElement.cpp index 49c5100095c..74ddb367b63 100644 --- a/content/xul/content/src/nsXULElement.cpp +++ b/content/xul/content/src/nsXULElement.cpp @@ -2488,7 +2488,7 @@ nsXULElement::HideWindowChrome(PRBool aShouldHide) return NS_ERROR_UNEXPECTED; // only top level chrome documents can hide the window chrome - if (doc->GetParentDocument()) + if (!doc->IsRootDisplayDocument()) return NS_OK; nsIPresShell *shell = doc->GetPrimaryShell(); @@ -2522,7 +2522,7 @@ nsXULElement::SetTitlebarColor(nscolor aColor, PRBool aActive) } // only top level chrome documents can set the titlebar color - if (!doc->GetParentDocument()) { + if (doc->IsRootDisplayDocument()) { nsCOMPtr container = doc->GetContainer(); nsCOMPtr baseWindow = do_QueryInterface(container); if (baseWindow) { diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 273347cd723..620e91bb3be 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -817,7 +817,11 @@ DocumentViewerImpl::InitInternal(nsIWidget* aParentWidget, PRBool makeCX = PR_FALSE; if (aDoCreation) { - if (aParentWidget && !mPresContext) { + // XXXbz this is a nasty hack to do with the fact that we create + // presentations both in Init() and in Show()... Ideally we would only do + // it in one place (Show()) and require that callers call init(), open(), + // show() in that order or something. + if ((aParentWidget || mDocument->GetDisplayDocument()) && !mPresContext) { // Create presentation context if (mIsPageMode) { //Presentation context already created in SetPageMode which is calling this method @@ -1241,12 +1245,12 @@ DocumentViewerImpl::Open(nsISupports *aState, nsISHEntry *aSHEntry) nsRect bounds; mWindow->GetBounds(bounds); - nsresult rv = InitInternal(mParentWidget, aState, bounds, PR_FALSE, PR_FALSE); - NS_ENSURE_SUCCESS(rv, rv); - if (mDocument) mDocument->SetContainer(nsCOMPtr(do_QueryReferent(mContainer))); + nsresult rv = InitInternal(mParentWidget, aState, bounds, PR_FALSE, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + if (mPresShell) mPresShell->SetForwardingContainer(nsnull); @@ -2101,13 +2105,14 @@ DocumentViewerImpl::CreateStyleSet(nsIDocument* aDocument, // The document will fill in the document sheets when we create the presshell // Handle the user sheets. - PRInt32 shellType = nsIDocShellTreeItem::typeContent;; - nsCOMPtr docShell(do_QueryReferent(mContainer)); - if (docShell) { - docShell->GetItemType(&shellType); - } +#ifdef DEBUG + nsCOMPtr debugDocContainer = aDocument->GetContainer(); + nsCOMPtr debugDocShell(do_QueryReferent(mContainer)); + NS_ASSERTION(SameCOMIdentity(debugDocContainer, debugDocShell), + "Unexpected containers"); +#endif nsICSSStyleSheet* sheet = nsnull; - if (shellType == nsIDocShellTreeItem::typeChrome) { + if (nsContentUtils::IsInChromeDocshell(aDocument)) { sheet = nsLayoutStylesheetCache::UserChromeSheet(); } else { @@ -2119,7 +2124,9 @@ DocumentViewerImpl::CreateStyleSet(nsIDocument* aDocument, // Append chrome sheets (scrollbars + forms). PRBool shouldOverride = PR_FALSE; - nsCOMPtr ds(do_QueryInterface(docShell)); + // We don't want a docshell here for external resource docs, so just + // look at mContainer. + nsCOMPtr ds(do_QueryReferent(mContainer)); nsCOMPtr chromeHandler; nsCOMPtr uri; nsCOMPtr csssheet; @@ -2296,6 +2303,23 @@ DocumentViewerImpl::CreateDeviceContext(nsIWidget* aWidget) { NS_PRECONDITION(!mPresShell && !mPresContext && !mWindow, "This will screw up our existing presentation"); + NS_PRECONDITION(mDocument, "Gotta have a document here"); + + nsIDocument* doc = mDocument->GetDisplayDocument(); + if (doc) { + NS_ASSERTION(!aWidget, "Shouldn't have a widget here"); + + // We want to use our display document's device context if possible + nsIPresShell* shell = doc->GetPrimaryShell(); + if (shell) { + nsPresContext* ctx = shell->GetPresContext(); + if (ctx) { + mDeviceContext = ctx->DeviceContext(); + return NS_OK; + } + } + } + // Create a device context even if we already have one, since our widget // might have changed. mDeviceContext = do_CreateInstance(kDeviceContextCID); @@ -2684,6 +2708,38 @@ SetChildFullZoom(nsIMarkupDocumentViewer* aChild, void* aClosure) aChild->SetFullZoom(ZoomInfo->mZoom); } +PR_STATIC_CALLBACK(PRBool) +SetExtResourceTextZoom(nsIDocument* aDocument, void* aClosure) +{ + // Would it be better to enumerate external resource viewers instead? + nsIPresShell* shell = aDocument->GetPrimaryShell(); + if (shell) { + nsPresContext* ctxt = shell->GetPresContext(); + if (ctxt) { + struct ZoomInfo* ZoomInfo = static_cast(aClosure); + ctxt->SetTextZoom(ZoomInfo->mZoom); + } + } + + return PR_TRUE; +} + +PR_STATIC_CALLBACK(PRBool) +SetExtResourceFullZoom(nsIDocument* aDocument, void* aClosure) +{ + // Would it be better to enumerate external resource viewers instead? + nsIPresShell* shell = aDocument->GetPrimaryShell(); + if (shell) { + nsPresContext* ctxt = shell->GetPresContext(); + if (ctxt) { + struct ZoomInfo* ZoomInfo = static_cast(aClosure); + ctxt->SetFullZoom(ZoomInfo->mZoom); + } + } + + return PR_TRUE; +} + NS_IMETHODIMP DocumentViewerImpl::SetTextZoom(float aTextZoom) { @@ -2706,6 +2762,9 @@ DocumentViewerImpl::SetTextZoom(float aTextZoom) pc->SetTextZoom(aTextZoom); } + // And do the external resources + mDocument->EnumerateExternalResources(SetExtResourceTextZoom, &ZoomInfo); + batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC); return NS_OK; @@ -2737,6 +2796,9 @@ DocumentViewerImpl::SetFullZoom(float aFullZoom) pc->SetFullZoom(aFullZoom); } + // And do the external resources + mDocument->EnumerateExternalResources(SetExtResourceFullZoom, &ZoomInfo); + batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC); return NS_OK; diff --git a/layout/generic/nsFrameFrame.cpp b/layout/generic/nsFrameFrame.cpp index c2672e3cb29..1c371252070 100644 --- a/layout/generic/nsFrameFrame.cpp +++ b/layout/generic/nsFrameFrame.cpp @@ -555,9 +555,11 @@ nsSubDocumentFrame::Reflow(nsPresContext* aPresContext, innerSize.height -= aReflowState.mComputedBorderPadding.TopBottom(); } - nsIViewManager* vm = mInnerView->GetViewManager(); - vm->MoveViewTo(mInnerView, offset.x, offset.y); - vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), innerSize), PR_TRUE); + if (mInnerView) { + nsIViewManager* vm = mInnerView->GetViewManager(); + vm->MoveViewTo(mInnerView, offset.x, offset.y); + vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), innerSize), PR_TRUE); + } // Determine if we need to repaint our border, background or outline CheckInvalidateSizeChange(aDesiredSize); diff --git a/layout/generic/nsObjectFrame.cpp b/layout/generic/nsObjectFrame.cpp index 52be4b169c8..ea38bd85a94 100644 --- a/layout/generic/nsObjectFrame.cpp +++ b/layout/generic/nsObjectFrame.cpp @@ -609,7 +609,9 @@ nsObjectFrame::Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow) { - mPreventInstantiation = PR_FALSE; + NS_PRECONDITION(aContent, "How did that happen?"); + mPreventInstantiation = + (aContent->GetCurrentDoc()->GetDisplayDocument() != nsnull); PR_LOG(nsObjectFrameLM, PR_LOG_DEBUG, ("Initializing nsObjectFrame %p for content %p\n", this, aContent)); @@ -620,7 +622,9 @@ nsObjectFrame::Init(nsIContent* aContent, void nsObjectFrame::Destroy() { - NS_ASSERTION(!mPreventInstantiation, "about to crash due to bug 136927"); + NS_ASSERTION(!mPreventInstantiation || + mContent && mContent->GetCurrentDoc()->GetDisplayDocument(), + "about to crash due to bug 136927"); // we need to finish with the plugin before native window is destroyed // doing this in the destructor is too late. @@ -1718,6 +1722,7 @@ nsObjectFrame::Instantiate(const char* aMimeType, nsIURI* aURI) return rv; mInstanceOwner->SetPluginHost(pluginHost); + NS_ASSERTION(!mPreventInstantiation, "Say what?"); mPreventInstantiation = PR_TRUE; rv = InstantiatePlugin(pluginHost, aMimeType, aURI);