/* -*- 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. * * Contributor(s): * Pierre Phaneuf */ #include "nsCOMPtr.h" #include "nsXMLContentSink.h" #include "nsIElementFactory.h" #include "nsIParser.h" #include "nsIUnicharInputStream.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsIDOMDocumentType.h" #include "nsIDOMDOMImplementation.h" #include "nsIXMLDocument.h" #include "nsIXMLContent.h" #include "nsIScriptGlobalObject.h" #include "nsIURL.h" #include "nsIRefreshURI.h" #include "nsNetUtil.h" #include "nsIWebShell.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIContent.h" #include "nsITextContent.h" #include "nsIStyleSheetLinkingElement.h" #include "nsIPresContext.h" #include "nsIPresShell.h" #include "nsIViewManager.h" #include "nsIDOMComment.h" #include "nsIDOMCDATASection.h" #include "nsDOMDocumentType.h" #include "nsIHTMLContent.h" #include "nsHTMLEntities.h" #include "nsHTMLParts.h" #include "nsVoidArray.h" #include "nsCRT.h" #include "nsICSSLoader.h" #include "nsICSSStyleSheet.h" #include "nsIHTMLContentContainer.h" #include "nsHTMLAtoms.h" #include "nsContentUtils.h" #include "nsLayoutAtoms.h" #include "nsContentCID.h" #include "nsIScriptContext.h" #include "nsINameSpace.h" #include "nsINameSpaceManager.h" #include "nsIServiceManager.h" #include "nsIScriptSecurityManager.h" #include "nsIContentViewer.h" #include "jsapi.h" // for JSVERSION_* and JS_VersionToString #include "prtime.h" #include "prlog.h" #include "prmem.h" #include "nsXSLContentSink.h" #include "nsParserCIID.h" #include "nsParserUtils.h" #include "nsIDocumentViewer.h" #include "nsIScrollable.h" #include "nsGenericElement.h" #include "nsIWebNavigation.h" #include "nsIScriptElement.h" #include "nsStyleLinkElement.h" // XXX misnamed header file, but oh well #include "nsHTMLTokens.h" static char kNameSpaceSeparator = ':'; static char kNameSpaceDef[] = "xmlns"; static char kStyleSheetPI[] = "xml-stylesheet"; static char kXSLType[] = "text/xsl"; static NS_DEFINE_CID(kNameSpaceManagerCID, NS_NAMESPACEMANAGER_CID); static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID); nsINameSpaceManager* nsXMLContentSink::gNameSpaceManager = nsnull; PRUint32 nsXMLContentSink::gRefCnt = 0; // XXX Open Issues: // 1) what's not allowed - We need to figure out which HTML tags // (prefixed with a HTML namespace qualifier) are explicitly not // allowed (if any). // 2) factoring code with nsHTMLContentSink - There's some amount of // common code between this and the HTML content sink. This will // increase as we support more and more HTML elements. How can code // from the code be factored? nsresult NS_NewXMLContentSink(nsIXMLContentSink** aResult, nsIDocument* aDoc, nsIURI* aURL, nsIWebShell* aWebShell) { NS_PRECONDITION(nsnull != aResult, "null ptr"); if (nsnull == aResult) { return NS_ERROR_NULL_POINTER; } nsXMLContentSink* it; NS_NEWXPCOM(it, nsXMLContentSink); if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } nsresult rv = it->Init(aDoc, aURL, aWebShell); if (NS_OK != rv) { delete it; return rv; } return it->QueryInterface(NS_GET_IID(nsIXMLContentSink), (void **)aResult); } nsXMLContentSink::nsXMLContentSink() { NS_INIT_REFCNT(); gRefCnt++; if (gRefCnt == 1) { nsresult rv = nsServiceManager::GetService(kNameSpaceManagerCID, NS_GET_IID(nsINameSpaceManager), (nsISupports**) &gNameSpaceManager); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get namespace manager"); } mDocument = nsnull; mDocumentURL = nsnull; mDocumentBaseURL = nsnull; mWebShell = nsnull; mParser = nsnull; mRootElement = nsnull; mDocElement = nsnull; mContentStack = nsnull; mNameSpaceStack = nsnull; mText = nsnull; mTextLength = 0; mTextSize = 0; mConstrainSize = PR_TRUE; mInScript = PR_FALSE; mInTitle = PR_FALSE; mStyleSheetCount = 0; mCSSLoader = nsnull; mXSLTransformMediator = nsnull; mNeedToBlockParser = PR_FALSE; } nsXMLContentSink::~nsXMLContentSink() { gRefCnt--; if (gRefCnt == 0) { NS_IF_RELEASE(gNameSpaceManager); } NS_IF_RELEASE(mDocument); NS_IF_RELEASE(mDocumentURL); NS_IF_RELEASE(mDocumentBaseURL); NS_IF_RELEASE(mWebShell); NS_IF_RELEASE(mParser); NS_IF_RELEASE(mRootElement); NS_IF_RELEASE(mDocElement); if (nsnull != mNameSpaceStack) { // There shouldn't be any here except in an error condition PRInt32 index = mNameSpaceStack->Count(); while (0 < index--) { nsINameSpace* nameSpace = (nsINameSpace*)mNameSpaceStack->ElementAt(index); NS_RELEASE(nameSpace); } delete mNameSpaceStack; } if (nsnull != mText) { PR_FREEIF(mText); } NS_IF_RELEASE(mCSSLoader); } nsresult nsXMLContentSink::Init(nsIDocument* aDoc, nsIURI* aURL, nsIWebShell* aContainer) { NS_PRECONDITION(nsnull != aDoc, "null ptr"); NS_PRECONDITION(nsnull != aURL, "null ptr"); if ((nsnull == aDoc) || (nsnull == aURL)) { return NS_ERROR_NULL_POINTER; } mDocument = aDoc; NS_ADDREF(aDoc); mDocumentURL = aURL; NS_ADDREF(aURL); mDocumentBaseURL = aURL; NS_ADDREF(aURL); mWebShell = aContainer; NS_IF_ADDREF(aContainer); nsCOMPtr loader; nsresult rv = mDocument->GetScriptLoader(getter_AddRefs(loader)); NS_ENSURE_SUCCESS(rv, rv); loader->AddObserver(this); mState = eXMLContentSinkState_InProlog; mDocElement = nsnull; mRootElement = nsnull; nsIHTMLContentContainer* htmlContainer = nsnull; if (NS_SUCCEEDED(aDoc->QueryInterface(NS_GET_IID(nsIHTMLContentContainer), (void**)&htmlContainer))) { htmlContainer->GetCSSLoader(mCSSLoader); NS_RELEASE(htmlContainer); } return aDoc->GetNodeInfoManager(*getter_AddRefs(mNodeInfoManager)); } NS_IMPL_THREADSAFE_ADDREF(nsXMLContentSink) NS_IMPL_THREADSAFE_RELEASE(nsXMLContentSink) NS_INTERFACE_MAP_BEGIN(nsXMLContentSink) NS_INTERFACE_MAP_ENTRY(nsIXMLContentSink) NS_INTERFACE_MAP_ENTRY(nsIContentSink) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIScriptLoaderObserver) NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXMLContentSink) NS_INTERFACE_MAP_END // nsIContentSink NS_IMETHODIMP nsXMLContentSink::WillBuildModel(void) { // Notify document that the load is beginning mDocument->BeginLoad(); return NS_OK; } void nsXMLContentSink::ScrollToRef() { if (mRef.Length() > 0) { PRInt32 i, ns = mDocument->GetNumberOfShells(); for (i = 0; i < ns; i++) { nsCOMPtr shell; mDocument->GetShellAt(i, getter_AddRefs(shell)); if (shell) { shell->FlushPendingNotifications(); // Scroll to the anchor shell->GoToAnchor(mRef); } } } } NS_IMETHODIMP nsXMLContentSink::DidBuildModel(PRInt32 aQualityLevel) { // XXX this is silly; who cares? PRInt32 i, ns = mDocument->GetNumberOfShells(); for (i = 0; i < ns; i++) { nsCOMPtr shell; mDocument->GetShellAt(i, getter_AddRefs(shell)); if (shell) { nsCOMPtr vm; shell->GetViewManager(getter_AddRefs(vm)); if(vm) { vm->SetQuality(nsContentQuality(aQualityLevel)); } } } mDocument->SetRootContent(mDocElement); nsresult rv = NS_OK; if (mXSLTransformMediator) { rv = SetupTransformMediator(); } nsCOMPtr loader; mDocument->GetScriptLoader(getter_AddRefs(loader)); if (loader) { loader->RemoveObserver(this); } if (!mXSLTransformMediator || NS_FAILED(rv)) { StartLayout(); ScrollToRef(); mDocument->EndLoad(); } // Drop our reference to the parser to get rid of a circular // reference. NS_IF_RELEASE(mParser); return NS_OK; } // The observe method is called on completion of the transform. The nsISupports argument is an // nsIDOMElement interface to the root node of the output content model. NS_IMETHODIMP nsXMLContentSink::Observe(nsISupports *aSubject, const PRUnichar *aTopic, const PRUnichar *someData) { nsresult rv = NS_OK; nsAutoString topic(aTopic); if (topic.Equals(NS_LITERAL_STRING("xslt-done"))) { nsCOMPtr content; // Set the output content model on the document content = do_QueryInterface(aSubject, &rv); if (NS_SUCCEEDED(rv)) { nsCOMPtr resultDOMDoc; mXSLTransformMediator->GetResultDocument(getter_AddRefs(resultDOMDoc)); nsCOMPtr resultDoc = do_QueryInterface(resultDOMDoc); nsCOMPtr sourceDoc = mDocument; NS_RELEASE(mDocument); mDocument = resultDoc; NS_ADDREF(mDocument); mDocument->SetRootContent(content); // Reset the observer on the transform mediator mXSLTransformMediator->SetTransformObserver(nsnull); // Start the layout process StartLayout(); sourceDoc->EndLoad(); nsCOMPtr docShell(do_QueryInterface(mWebShell)); nsCOMPtr contentViewer; rv = docShell->GetContentViewer(getter_AddRefs(contentViewer)); if (NS_SUCCEEDED(rv) && contentViewer) { contentViewer->LoadComplete(NS_OK); } } else { // Transform failed nsCOMPtr docShell(do_QueryInterface(mWebShell)); nsCOMPtr contentViewer; rv = docShell->GetContentViewer(getter_AddRefs(contentViewer)); nsCOMPtr documentViewer(do_QueryInterface(contentViewer)); if (documentViewer) { documentViewer->SetTransformMediator(nsnull); } mXSLTransformMediator = nsnull; mDocument->SetRootContent(mDocElement); // Start the layout process StartLayout(); mDocument->EndLoad(); } } return rv; } // Provide the transform mediator with the source document's content // model and the output document, and register the XML content sink // as the transform observer. The transform mediator will call // the nsIObserver::Observe() method on the transform observer once // the transform is completed. The nsISupports pointer to the Observe // method will be an nsIDOMElement pointer to the root node of the output // content model. nsresult nsXMLContentSink::SetupTransformMediator() { nsresult rv = NS_OK; nsCOMPtr currentDOMDoc(do_QueryInterface(mDocument)); mXSLTransformMediator->SetSourceContentModel(currentDOMDoc); // Create the result document nsCOMPtr resultDOMDoc; nsCOMPtr url; mDocument->GetBaseURL(*getter_AddRefs(url)); nsAutoString emptyStr; rv = NS_NewDOMDocument(getter_AddRefs(resultDOMDoc), emptyStr, emptyStr, nsnull, url); if (NS_FAILED(rv)) return rv; nsCOMPtr resultXMLDoc(do_QueryInterface(resultDOMDoc)); resultXMLDoc->SetDefaultStylesheets(url); nsCOMPtr docShell(do_QueryInterface(mWebShell)); nsCOMPtr contentViewer; rv = docShell->GetContentViewer(getter_AddRefs(contentViewer)); if (NS_SUCCEEDED(rv) && contentViewer) { contentViewer->SetDOMDocument(resultDOMDoc); } mXSLTransformMediator->SetResultDocument(resultDOMDoc); mXSLTransformMediator->SetTransformObserver(this); return rv; } NS_IMETHODIMP nsXMLContentSink::WillInterrupt(void) { return NS_OK; } NS_IMETHODIMP nsXMLContentSink::WillResume(void) { return NS_OK; } NS_IMETHODIMP nsXMLContentSink::SetParser(nsIParser* aParser) { NS_IF_RELEASE(mParser); mParser = aParser; NS_IF_ADDREF(mParser); return NS_OK; } // XXX Code copied from nsHTMLContentSink. It should be shared. nsresult nsXMLContentSink::AddAttributes(const nsIParserNode& aNode, nsIContent* aContent, PRBool aIsHTML) { // Add tag attributes to the content attributes nsAutoString name; PRInt32 ac = aNode.GetAttributeCount(); for (PRInt32 i = 0; i < ac; i++) { // Get upper-cased key const nsAReadableString& key = aNode.GetKeyAt(i); name.Assign(key); nsCOMPtr nameSpacePrefix(dont_AddRef(CutNameSpacePrefix(name))); nsCOMPtr nameAtom(dont_AddRef(NS_NewAtom(name))); PRInt32 nameSpaceID; if (nameSpacePrefix) { nameSpaceID = GetNameSpaceId(nameSpacePrefix); } else { if (nameAtom.get() == nsLayoutAtoms::xmlnsNameSpace) nameSpaceID = kNameSpaceID_XMLNS; else nameSpaceID = kNameSpaceID_None; } if (kNameSpaceID_Unknown == nameSpaceID) { nameSpaceID = kNameSpaceID_None; nameAtom = dont_AddRef(NS_NewAtom(key)); nameSpacePrefix = nsnull; } else if ((kNameSpaceID_XMLNS == nameSpaceID) && aIsHTML) { name.InsertWithConversion("xmlns:", 0); nameAtom = dont_AddRef(NS_NewAtom(name)); nameSpaceID = kNameSpaceID_HTML; // XXX this is wrong, but necessary until HTML can store other namespaces for attrs } nsCOMPtr ni; mNodeInfoManager->GetNodeInfo(nameAtom, nameSpacePrefix, nameSpaceID, *getter_AddRefs(ni)); NS_ENSURE_TRUE(ni, NS_ERROR_FAILURE); // Add attribute to content aContent->SetAttribute(ni, aNode.GetValueAt(i), PR_FALSE); } // Give autoloading links a chance to fire if (mWebShell) { nsCOMPtr xmlcontent(do_QueryInterface(aContent)); if (xmlcontent) { nsresult rv = xmlcontent->MaybeTriggerAutoLink(mWebShell); if (rv == NS_XML_AUTOLINK_REPLACE || rv == NS_XML_AUTOLINK_UNDEFINED) { // If we do not terminate the parse, we just keep generating link trigger // events. We want to parse only up to the first replace link, and stop. mParser->Terminate(); } } } return NS_OK; } void nsXMLContentSink::PushNameSpacesFrom(const nsIParserNode& aNode) { nsAutoString k, prefix; PRInt32 ac = aNode.GetAttributeCount(); PRInt32 offset; nsINameSpace* nameSpace = nsnull; if ((nsnull != mNameSpaceStack) && (0 < mNameSpaceStack->Count())) { nameSpace = (nsINameSpace*)mNameSpaceStack->ElementAt(mNameSpaceStack->Count() - 1); NS_ADDREF(nameSpace); } else { nsINameSpaceManager* manager = nsnull; mDocument->GetNameSpaceManager(manager); NS_ASSERTION(nsnull != manager, "no name space manager in document"); if (nsnull != manager) { manager->CreateRootNameSpace(nameSpace); NS_RELEASE(manager); } } if (nsnull != nameSpace) { for (PRInt32 i = 0; i < ac; i++) { const nsAReadableString& key = aNode.GetKeyAt(i); k.Assign(key); // Look for "xmlns" at the start of the attribute name offset = k.Find(kNameSpaceDef); if (0 == offset) { if (k.Length() == (sizeof(kNameSpaceDef)-1)) { // If there's nothing left, this is a default namespace prefix.Truncate(); } else { PRUnichar next = k.CharAt(sizeof(kNameSpaceDef)-1); // If the next character is a :, there is a namespace prefix if (':' == next) { prefix.Truncate(); k.Right(prefix, k.Length()-sizeof(kNameSpaceDef)); } else { continue; } } // Open a local namespace nsIAtom* prefixAtom = ((0 < prefix.Length()) ? NS_NewAtom(prefix) : nsnull); nsINameSpace* child = nsnull; nameSpace->CreateChildNameSpace(prefixAtom, aNode.GetValueAt(i), child); if (nsnull != child) { NS_RELEASE(nameSpace); nameSpace = child; } NS_IF_RELEASE(prefixAtom); } } if (nsnull == mNameSpaceStack) { mNameSpaceStack = new nsVoidArray(); } mNameSpaceStack->AppendElement(nameSpace); } } nsIAtom* nsXMLContentSink::CutNameSpacePrefix(nsString& aString) { nsAutoString prefix; PRInt32 nsoffset = aString.FindChar(kNameSpaceSeparator); if (-1 != nsoffset) { aString.Left(prefix, nsoffset); aString.Cut(0, nsoffset+1); } if (0 < prefix.Length()) { return NS_NewAtom(prefix); } return nsnull; } NS_IMETHODIMP nsXMLContentSink::OpenContainer(const nsIParserNode& aNode) { nsresult result = NS_OK; nsAutoString tag; nsCOMPtr nameSpacePrefix; PRBool isHTML = PR_FALSE; PRBool pushContent = PR_TRUE; PRBool appendContent = PR_TRUE; nsCOMPtr content; // XXX Hopefully the parser will flag this before we get // here. If we're in the epilog, there should be no // new elements PR_ASSERT(eXMLContentSinkState_InEpilog != mState); FlushText(); mState = eXMLContentSinkState_InDocumentElement; tag.Assign(aNode.GetText()); nameSpacePrefix = getter_AddRefs(CutNameSpacePrefix(tag)); nsCOMPtr tagAtom(dont_AddRef(NS_NewAtom(tag))); // We must register namespace declarations found in the attribute list // of an element before creating the element. This is because the // namespace prefix for an element might be declared within the attribute // list. PushNameSpacesFrom(aNode); PRInt32 nameSpaceID = GetNameSpaceId(nameSpacePrefix); nsCOMPtr nodeInfo; mNodeInfoManager->GetNodeInfo(tagAtom, nameSpacePrefix, nameSpaceID, *getter_AddRefs(nodeInfo)); isHTML = IsHTMLNameSpace(nameSpaceID); if (isHTML) { if (tagAtom.get() == nsHTMLAtoms::script) { result = ProcessStartSCRIPTTag(aNode); // Don't append the content to the tree until we're all // done collecting its contents appendContent = PR_FALSE; } else if (tagAtom.get() == nsHTMLAtoms::title) { if (mTitleText.IsEmpty()) mInTitle = PR_TRUE; // The first title wins } nsCOMPtr htmlContent; result = NS_CreateHTMLElement(getter_AddRefs(htmlContent), nodeInfo, PR_TRUE); content = do_QueryInterface(htmlContent); if (tagAtom.get() == nsHTMLAtoms::textarea) { mTextAreaElement = do_QueryInterface(htmlContent); } else if (tagAtom.get() == nsHTMLAtoms::style) { mStyleElement = htmlContent; } else if (tagAtom.get() == nsHTMLAtoms::base) { if (!mBaseElement) { mBaseElement = htmlContent; // The first base wins } } else if (tagAtom.get() == nsHTMLAtoms::meta) { if (!mMetaElement) { mMetaElement = htmlContent; } } } else { // The first step here is to see if someone has provided their // own content element implementation (e.g., XUL or MathML). // This is done based off a contractid/namespace scheme. nsCOMPtr elementFactory; // This should *not* be done for every node, only when we find // a new namespace!!! -- jst GetElementFactory(nameSpaceID, getter_AddRefs(elementFactory)); if (elementFactory) { // Create the content element using the element factory. elementFactory->CreateInstanceByTag(nodeInfo, getter_AddRefs(content)); } else { nsCOMPtr xmlContent; result = NS_NewXMLElement(getter_AddRefs(xmlContent), nodeInfo); content = do_QueryInterface(xmlContent); } } if (NS_OK == result) { PRInt32 id; mDocument->GetAndIncrementContentID(&id); content->SetContentID(id); nsCOMPtr ssle(do_QueryInterface(content)); if (ssle) { // We stopped supporting