/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * 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 the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Pierre Phaneuf * * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #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 "nsIDOMNSDocument.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 "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 "nsRect.h" #include "nsGenericElement.h" #include "nsIWebNavigation.h" #include "nsIScriptElement.h" #include "nsStyleLinkElement.h" #include "nsEscape.h" #include "nsICharsetConverterManager.h" #include "nsICharsetConverterManager2.h" // XXX misnamed header file, but oh well #include "nsHTMLTokens.h" static char kNameSpaceSeparator = ':'; 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; 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_ENSURE_TRUE(gNameSpaceManager, NS_ERROR_OUT_OF_MEMORY); 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; } // This function's implementation is in nsHTMLContentSink.cpp nsresult CharsetConvRef(const nsString& aDocCharset, const nsCString& aRefInDocCharset, nsString& aRefInUnicode); void nsXMLContentSink::ScrollToRef() { // XXX Duplicate code in nsHTMLContentSink. // XXX Be sure to change both places if you make changes here. if (!mRef.IsEmpty()) { char* tmpstr = ToNewCString(mRef); if(! tmpstr) return; nsUnescape(tmpstr); nsCAutoString unescapedRef; unescapedRef.Assign(tmpstr); nsMemory::Free(tmpstr); nsresult rv = NS_ERROR_FAILURE; // We assume that the bytes are in UTF-8, as it says in the spec: // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1 nsAutoString ref = NS_ConvertUTF8toUCS2(unescapedRef); PRInt32 i, ns = mDocument->GetNumberOfShells(); for (i = 0; i < ns; i++) { nsCOMPtr shell; mDocument->GetShellAt(i, getter_AddRefs(shell)); if (shell) { // Scroll to the anchor shell->FlushPendingNotifications(PR_FALSE); // Check an empty string which might be caused by the UTF-8 conversion if (!ref.IsEmpty()) rv = shell->GoToAnchor(ref); else rv = NS_ERROR_FAILURE; // If UTF-8 URL failed then try to assume the string as a // document's charset. if (NS_FAILED(rv)) { nsAutoString docCharset; rv = mDocument->GetDocumentCharacterSet(docCharset); if (NS_SUCCEEDED(rv)) { rv = CharsetConvRef(docCharset, unescapedRef, ref); if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) rv = shell->GoToAnchor(ref); } } } } } } 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(); } // Ref. Bug 49115 // Do this hack to make sure that the parser // doesn't get destroyed, accidently, before // the circularity, between sink & parser, is // actually borken. nsCOMPtr kungFuDeathGrip(mParser); // 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 char *aTopic, const PRUnichar *someData) { nsresult rv = NS_OK; if (!nsCRT::strcmp(aTopic, "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 nsCOMPtr nameSpacePrefix, nameAtom; PRInt32 ac = aNode.GetAttributeCount(); for (PRInt32 i = 0; i < ac; i++) { // Get upper-cased key const nsAReadableString& key = aNode.GetKeyAt(i); SplitXMLName(key, getter_AddRefs(nameSpacePrefix), getter_AddRefs(nameAtom)); 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) { // Ooh, what a nice little hack we have here :-) nsAutoString name; nameAtom->ToString(name); 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->SetAttr(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; } nsresult nsXMLContentSink::PushNameSpacesFrom(const nsIParserNode& aNode) { PRInt32 ac = aNode.GetAttributeCount(); nsCOMPtr nameSpace; nsresult rv = NS_OK; if (mNameSpaceStack && (0 < mNameSpaceStack->Count())) { nameSpace = (nsINameSpace*)mNameSpaceStack->ElementAt(mNameSpaceStack->Count() - 1); } else { nsCOMPtr manager; mDocument->GetNameSpaceManager(*getter_AddRefs(manager)); NS_ASSERTION(manager, "no name space manager in document"); if (manager) { rv = manager->CreateRootNameSpace(*getter_AddRefs(nameSpace)); NS_ENSURE_SUCCESS(rv, rv); } } NS_ENSURE_TRUE(nameSpace, NS_ERROR_UNEXPECTED); static const NS_NAMED_LITERAL_STRING(kNameSpaceDef, "xmlns"); static const PRUint32 xmlns_len = kNameSpaceDef.Length(); for (PRInt32 i = 0; i < ac; i++) { const nsAReadableString& key = aNode.GetKeyAt(i); // Look for "xmlns" at the start of the attribute name PRUint32 key_len = key.Length(); if (key_len >= xmlns_len && nsDependentSubstring(key, 0, xmlns_len).Equals(kNameSpaceDef)) { nsCOMPtr prefixAtom; // If key_len > xmlns_len we have a xmlns:foo type attribute, // extract the prefix. If not, we have a xmlns attribute in // which case there is no prefix. if (key_len > xmlns_len) { nsReadingIterator start, end; key.BeginReading(start); key.EndReading(end); start.advance(xmlns_len); if (*start == ':') { ++start; prefixAtom = dont_AddRef(NS_NewAtom(nsDependentSubstring(start, end))); } } nsCOMPtr child; rv = nameSpace->CreateChildNameSpace(prefixAtom, aNode.GetValueAt(i), *getter_AddRefs(child)); NS_ENSURE_SUCCESS(rv, rv); nameSpace = child; } } if (!mNameSpaceStack) { mNameSpaceStack = new nsAutoVoidArray(); if (!mNameSpaceStack) { return NS_ERROR_OUT_OF_MEMORY; } } nsINameSpace *tmp = nameSpace; mNameSpaceStack->AppendElement(tmp); NS_ADDREF(tmp); return NS_OK; } // static void nsXMLContentSink::SplitXMLName(nsAReadableString& aString, nsIAtom **aPrefix, nsIAtom **aLocalName) { nsReadingIterator iter, end; aString.BeginReading(iter); aString.EndReading(end); FindCharInReadable(kNameSpaceSeparator, iter, end); if (iter != end) { nsReadingIterator start; aString.BeginReading(start); *aPrefix = NS_NewAtom(nsDependentSubstring(start, iter)); ++iter; *aLocalName = NS_NewAtom(nsDependentSubstring(iter, end)); return; } *aPrefix = nsnull; *aLocalName = NS_NewAtom(aString); } NS_IMETHODIMP nsXMLContentSink::OpenContainer(const nsIParserNode& aNode) { nsresult result = NS_OK; PRBool isHTML = PR_FALSE; 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; nsCOMPtr nameSpacePrefix, tagAtom; SplitXMLName(aNode.GetText(), getter_AddRefs(nameSpacePrefix), getter_AddRefs(tagAtom)); // 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. result = PushNameSpacesFrom(aNode); NS_ENSURE_SUCCESS(result, result); 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; 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