/* -*- 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.org 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 * Peter Annema * Daniel Glazman * * * 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 "nsReadableUtils.h" #include "nsUnicharUtils.h" #include "nsXPIDLString.h" #include "nsIHTMLContentSink.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIParser.h" #include "nsParserUtils.h" #include "nsICSSStyleSheet.h" #include "nsICSSLoader.h" #include "nsICSSLoaderObserver.h" #include "nsIScriptLoader.h" #include "nsIScriptLoaderObserver.h" #include "nsIHTMLContent.h" #include "nsIHTMLContentContainer.h" #include "nsIUnicharInputStream.h" #include "nsIURL.h" #include "nsNetUtil.h" #include "nsIPresShell.h" #include "nsIPresContext.h" #include "nsIViewManager.h" #include "nsIWidget.h" #include "nsIContentViewer.h" #include "nsIMarkupDocumentViewer.h" #include "nsINodeInfo.h" #include "nsHTMLTokens.h" #include "nsCRT.h" #include "nsSupportsArray.h" #include "jsapi.h" // for JSVERSION_* and JS_VersionToString #include "prtime.h" #include "prlog.h" #include "nsGenericHTMLElement.h" #include "nsIElementFactory.h" #include "nsITextContent.h" #include "nsIDOMText.h" #include "nsIDOMComment.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMDOMImplementation.h" #include "nsIDOMDocumentType.h" #include "nsIDOMHTMLScriptElement.h" #include "nsIScriptElement.h" #include "nsIDOMHTMLFormElement.h" #include "nsIDOMHTMLTextAreaElement.h" #include "nsIDOMHTMLOptionElement.h" #include "nsIFormControl.h" #include "nsIForm.h" #include "nsIComponentManager.h" #include "nsIServiceManager.h" #include "nsIScrollableView.h" #include "nsHTMLAtoms.h" #include "nsContentUtils.h" #include "nsIFrame.h" #include "nsICharsetConverterManager.h" #include "nsICharsetConverterManager2.h" #include "nsIUnicodeDecoder.h" #include "nsICharsetAlias.h" #include "nsIChannel.h" #include "nsIHttpChannel.h" #include "nsCPrefetchService.h" #include "nsIWebShell.h" #include "nsIDocShell.h" #include "nsIWebNavigation.h" #include "nsIDocument.h" #include "nsIDocumentObserver.h" #include "nsIHTMLDocument.h" #include "nsStyleConsts.h" #include "nsINameSpaceManager.h" #include "nsIDOMHTMLMapElement.h" #include "nsIRefreshURI.h" #include "nsICookieService.h" #include "nsVoidArray.h" #include "nsIScriptSecurityManager.h" #include "nsIPrincipal.h" #include "nsICodebasePrincipal.h" #include "nsIAggregatePrincipal.h" #include "nsTextFragment.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptGlobalObjectOwner.h" #include "nsIParserService.h" #include "nsISelectElement.h" #include "nsITextAreaElement.h" #include "nsIPref.h" // XXX Go through a factory for this one #include "nsICSSParser.h" #include "nsIStyleSheetLinkingElement.h" #include "nsIDOMHTMLTitleElement.h" #include "nsTimer.h" #include "nsITimer.h" #include "nsDOMError.h" #include "nsIScrollable.h" #include "nsContentPolicyUtils.h" #include "nsIScriptContext.h" #include "nsStyleLinkElement.h" #include "nsReadableUtils.h" #include "nsWeakReference.h" // nsHTMLElementFactory supports weak references #include "nsIPrompt.h" #include "nsIDOMWindowInternal.h" #include "nsLayoutCID.h" #include "nsIFrameManager.h" #include "nsILayoutHistoryState.h" #include "nsIDocShellTreeItem.h" #include "plevent.h" #include "nsEscape.h" #include "nsIElementObserver.h" static NS_DEFINE_CID(kLayoutHistoryStateCID, NS_LAYOUT_HISTORY_STATE_CID); #ifdef ALLOW_ASYNCH_STYLE_SHEETS const PRBool kBlockByDefault = PR_FALSE; #else const PRBool kBlockByDefault = PR_TRUE; #endif //---------------------------------------------------------------------- #ifdef NS_DEBUG static PRLogModuleInfo* gSinkLogModuleInfo; #define SINK_TRACE_CALLS 0x1 #define SINK_TRACE_REFLOW 0x2 #define SINK_ALWAYS_REFLOW 0x4 #define SINK_LOG_TEST(_lm, _bit) (PRIntn((_lm)->level) & (_bit)) #define SINK_TRACE(_bit, _args) \ PR_BEGIN_MACRO \ if (SINK_LOG_TEST(gSinkLogModuleInfo, _bit)) { \ PR_LogPrint _args; \ } \ PR_END_MACRO #define SINK_TRACE_NODE(_bit, _msg, _tag, _sp, _obj) \ _obj->SinkTraceNode(_bit, _msg, _tag, _sp, this) #else #define SINK_TRACE(_bit, _args) #define SINK_TRACE_NODE(_bit, _msg, _tag, _sp, _obj) #endif #undef SINK_NO_INCREMENTAL //---------------------------------------------------------------------- #define NS_SINK_FLAG_SCRIPT_ENABLED 0x00000008 #define NS_SINK_FLAG_FRAMES_ENABLED 0x00000010 // Interrupt parsing when mMaxTokenProcessingTime is exceeded #define NS_SINK_FLAG_CAN_INTERRUPT_PARSER 0x00000020 // Lower the value for mNotificationInterval and // mMaxTokenProcessingTime #define NS_SINK_FLAG_DYNAMIC_LOWER_VALUE 0x00000040 #define NS_SINK_FLAG_FORM_ON_STACK 0x00000080 // 1/2 second fudge factor for window creation #define NS_DELAY_FOR_WINDOW_CREATION 500000 // 200 determined empirically to provide good user response without // sampling the clock too often. #define NS_MAX_TOKENS_DEFLECTED_IN_LOW_FREQ_MODE 200 class SinkContext; class HTMLContentSink : public nsIHTMLContentSink, public nsIScriptLoaderObserver, public nsITimerCallback, public nsICSSLoaderObserver, #ifdef DEBUG public nsIDebugDumpContent, #endif public nsIDocumentObserver { public: HTMLContentSink(); virtual ~HTMLContentSink(); NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW nsresult Init(nsIDocument* aDoc, nsIURI* aURL, nsIWebShell* aContainer, nsIChannel* aChannel); // nsISupports NS_DECL_ISUPPORTS NS_DECL_NSISCRIPTLOADEROBSERVER // nsIContentSink NS_IMETHOD WillBuildModel(void); NS_IMETHOD DidBuildModel(PRInt32 aQualityLevel); NS_IMETHOD WillInterrupt(void); NS_IMETHOD WillResume(void); NS_IMETHOD SetParser(nsIParser* aParser); NS_IMETHOD FlushPendingNotifications(); NS_IMETHOD SetDocumentCharset(nsAString& aCharset); // nsIHTMLContentSink NS_IMETHOD OpenContainer(const nsIParserNode& aNode); NS_IMETHOD CloseContainer(const nsHTMLTag aTag); NS_IMETHOD AddLeaf(const nsIParserNode& aNode); NS_IMETHOD AddComment(const nsIParserNode& aNode); NS_IMETHOD AddProcessingInstruction(const nsIParserNode& aNode); NS_IMETHOD AddDocTypeDecl(const nsIParserNode& aNode); NS_IMETHOD WillProcessTokens(void); NS_IMETHOD DidProcessTokens(void); NS_IMETHOD WillProcessAToken(void); NS_IMETHOD DidProcessAToken(void); NS_IMETHOD NotifyTagObservers(nsIParserNode* aNode); NS_IMETHOD BeginContext(PRInt32 aID); NS_IMETHOD EndContext(PRInt32 aID); NS_IMETHOD SetTitle(const nsString& aValue); NS_IMETHOD OpenHTML(const nsIParserNode& aNode); NS_IMETHOD CloseHTML(); NS_IMETHOD OpenHead(const nsIParserNode& aNode); NS_IMETHOD CloseHead(); NS_IMETHOD OpenBody(const nsIParserNode& aNode); NS_IMETHOD CloseBody(); NS_IMETHOD OpenForm(const nsIParserNode& aNode); NS_IMETHOD CloseForm(); NS_IMETHOD OpenFrameset(const nsIParserNode& aNode); NS_IMETHOD CloseFrameset(); NS_IMETHOD OpenMap(const nsIParserNode& aNode); NS_IMETHOD CloseMap(); NS_IMETHOD GetPref(PRInt32 aTag, PRBool& aPref); NS_IMETHOD_(PRBool) IsFormOnStack(); NS_IMETHOD DoFragment(PRBool aFlag); // nsITimerCallback NS_DECL_NSITIMERCALLBACK // nsICSSLoaderObserver NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet* aSheet, PRBool aNotify) { return NS_OK; } // nsIDocumentObserver NS_IMETHOD BeginUpdate(nsIDocument *aDocument); NS_IMETHOD EndUpdate(nsIDocument *aDocument); NS_IMETHOD BeginLoad(nsIDocument *aDocument) { return NS_OK; } NS_IMETHOD EndLoad(nsIDocument *aDocument) { return NS_OK; } NS_IMETHOD BeginReflow(nsIDocument *aDocument, nsIPresShell* aShell) { return NS_OK; } NS_IMETHOD EndReflow(nsIDocument *aDocument, nsIPresShell* aShell) { return NS_OK; } NS_IMETHOD ContentChanged(nsIDocument *aDocument, nsIContent* aContent, nsISupports* aSubContent) { return NS_OK; } NS_IMETHOD ContentStatesChanged(nsIDocument* aDocument, nsIContent* aContent1, nsIContent* aContent2, PRInt32 aStateMask) { return NS_OK; } NS_IMETHOD AttributeChanged(nsIDocument *aDocument, nsIContent *aContent, PRInt32 aNameSpaceID, nsIAtom *aAttribute, PRInt32 aModType, nsChangeHint aHint) { return NS_OK; } NS_IMETHOD ContentAppended(nsIDocument *aDocument, nsIContent *aContainer, PRInt32 aNewIndexInContainer) { return NS_OK; } NS_IMETHOD ContentInserted(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aChild, PRInt32 aIndexInContainer) { return NS_OK; } NS_IMETHOD ContentReplaced(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aOldChild, nsIContent* aNewChild, PRInt32 aIndexInContainer) { return NS_OK; } NS_IMETHOD ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aChild, PRInt32 aIndexInContainer) { return NS_OK; } NS_IMETHOD StyleSheetAdded(nsIDocument *aDocument, nsIStyleSheet* aStyleSheet); NS_IMETHOD StyleSheetRemoved(nsIDocument *aDocument, nsIStyleSheet* aStyleSheet); NS_IMETHOD StyleSheetDisabledStateChanged(nsIDocument *aDocument, nsIStyleSheet* aStyleSheet, PRBool aDisabled); NS_IMETHOD StyleRuleChanged(nsIDocument *aDocument, nsIStyleSheet* aStyleSheet, nsIStyleRule* aStyleRule, nsChangeHint aHint) { return NS_OK; } NS_IMETHOD StyleRuleAdded(nsIDocument *aDocument, nsIStyleSheet* aStyleSheet, nsIStyleRule* aStyleRule) { return NS_OK; } NS_IMETHOD StyleRuleRemoved(nsIDocument *aDocument, nsIStyleSheet* aStyleSheet, nsIStyleRule* aStyleRule) { return NS_OK; } NS_IMETHOD DocumentWillBeDestroyed(nsIDocument *aDocument) { return NS_OK; } #ifdef DEBUG // nsIDebugDumpContent NS_IMETHOD DumpContentModel(); #endif PRBool IsTimeToNotify(); PRBool IsInScript(); nsresult AddAttributes(const nsIParserNode& aNode, nsIHTMLContent* aContent, PRBool aNotify = PR_FALSE); nsresult CreateContentObject(const nsIParserNode& aNode, nsHTMLTag aNodeType, nsIDOMHTMLFormElement* aForm, nsIWebShell* aWebShell, nsIHTMLContent** aResult); inline PRInt32 GetNotificationInterval() { if (mFlags & NS_SINK_FLAG_DYNAMIC_LOWER_VALUE) { return 1000; } return mNotificationInterval; } inline PRInt32 GetMaxTokenProcessingTime() { if (mFlags & NS_SINK_FLAG_DYNAMIC_LOWER_VALUE) { return 3000; } return mMaxTokenProcessingTime; } #ifdef NS_DEBUG void SinkTraceNode(PRUint32 aBit, const char* aMsg, const nsHTMLTag aTag, PRInt32 aStackPos, void* aThis); #endif nsIDocument* mDocument; nsIHTMLDocument* mHTMLDocument; nsCOMPtr mNodeInfoManager; nsIURI* mDocumentURI; nsIURI* mDocumentBaseURL; nsIWebShell* mWebShell; nsIParser* mParser; // back off timer notification after count PRInt32 mBackoffCount; // Notification interval in microseconds PRInt32 mNotificationInterval; // Time of last notification PRTime mLastNotificationTime; // Timer used for notification nsCOMPtr mNotificationTimer; // The maximum length of a text run PRInt32 mMaxTextRun; nsIHTMLContent* mRoot; nsIHTMLContent* mBody; nsIHTMLContent* mFrameset; nsIHTMLContent* mHead; nsString mTitle; nsString mUnicodeXferBuf; // Do we notify based on time? PRPackedBool mNotifyOnTimer; PRPackedBool mLayoutStarted; PRPackedBool mScrolledToRefAlready; PRPackedBool mNeedToBlockParser; PRInt32 mInScript; PRInt32 mInNotification; nsCOMPtr mCurrentForm; nsCOMPtr mCurrentMap; nsAutoVoidArray mContextStack; SinkContext* mCurrentContext; SinkContext* mHeadContext; PRInt32 mNumOpenIFRAMES; nsCOMPtr mScriptElements; nsCOMPtr mDummyParserRequest; nsCString mRef; nsString mBaseHREF; nsString mBaseTarget; PRInt32 mStyleSheetCount; nsICSSLoader *mCSSLoader; PRInt32 mInsideNoXXXTag; PRInt32 mInMonolithicContainer; PRUint32 mFlags; // -- Can interrupt parsing members -- PRUint32 mDelayTimerStart; // Interrupt parsing during token procesing after # of microseconds PRInt32 mMaxTokenProcessingTime; // Switch between intervals when time is exceeded PRInt32 mDynamicIntervalSwitchThreshold; PRInt32 mBeginLoadTime; // Last mouse event or keyboard event time sampled by the content // sink PRUint32 mLastSampledUserEventTime; // The number of tokens that have been processed while in the low // frequency parser interrupt mode without falling through to the // logic which decides whether to switch to the high frequency // parser interrupt mode. PRUint8 mDeflectedCount; nsCOMPtr mObservers; void StartLayout(); void ScrollToRef(); void TryToScrollToRef(); void AddBaseTagInfo(nsIHTMLContent* aContent); nsresult ProcessLinkHeader(nsIHTMLContent* aElement, const nsAString& aLinkData); nsresult ProcessLink(nsIHTMLContent* aElement, const nsString& aHref, const nsString& aRel, const nsString& aTitle, const nsString& aType, const nsString& aMedia); nsresult ProcessStyleLink(nsIHTMLContent* aElement, const nsString& aHref, const nsStringArray& aLinkTypes, const nsString& aTitle, const nsString& aType, const nsString& aMedia); void PrefetchHref(const nsAString &aHref); void ProcessBaseHref(const nsAString& aBaseHref); void ProcessBaseTarget(const nsAString& aBaseTarget); nsresult RefreshIfEnabled(nsIViewManager* vm); // Routines for tags that require special handling nsresult ProcessATag(const nsIParserNode& aNode, nsIHTMLContent* aContent); nsresult ProcessAREATag(const nsIParserNode& aNode); nsresult ProcessBASETag(const nsIParserNode& aNode); nsresult ProcessLINKTag(const nsIParserNode& aNode); nsresult ProcessMAPTag(const nsIParserNode& aNode, nsIHTMLContent* aContent); nsresult ProcessMETATag(const nsIParserNode& aNode); nsresult ProcessSCRIPTTag(const nsIParserNode& aNode); nsresult ProcessSTYLETag(const nsIParserNode& aNode); nsresult ProcessHeaderData(nsIAtom* aHeader, const nsAString& aValue, nsIHTMLContent* aContent = nsnull); nsresult ProcessHTTPHeaders(nsIChannel* aChannel); // Script processing related routines nsresult ResumeParsing(); PRBool PreEvaluateScript(); void PostEvaluateScript(); void UpdateAllContexts(); void NotifyAppend(nsIContent* aContent, PRInt32 aStartIndex); void NotifyInsert(nsIContent* aContent, nsIContent* aChildContent, PRInt32 aIndexInContainer); PRBool IsMonolithicContainer(nsHTMLTag aTag); // CanInterrupt parsing related routines nsresult AddDummyParserRequest(void); nsresult RemoveDummyParserRequest(void); #ifdef NS_DEBUG void ForceReflow(); #endif // Measures content model creation time for current document MOZ_TIMER_DECLARE(mWatch) }; //---------------------------------------------------------------------- // // DummyParserRequest // // This is a dummy request implementation that we add to the document's load // group. It ensures that EndDocumentLoad() in the docshell doesn't fire // before we've finished all of parsing and tokenizing of the document. // class DummyParserRequest : public nsIChannel { protected: DummyParserRequest(nsIHTMLContentSink* aSink); virtual ~DummyParserRequest(); static PRInt32 gRefCnt; static nsIURI* gURI; nsCOMPtr mLoadGroup; nsIHTMLContentSink* mSink; // Weak reference public: static nsresult Create(nsIRequest** aResult, nsIHTMLContentSink* aSink); NS_DECL_ISUPPORTS // nsIRequest NS_IMETHOD GetName(nsACString &result) { result = NS_LITERAL_CSTRING("about:layout-dummy-request"); return NS_OK; } NS_IMETHOD IsPending(PRBool *_retval) { *_retval = PR_TRUE; return NS_OK; } NS_IMETHOD GetStatus(nsresult *status) { *status = NS_OK; return NS_OK; } NS_IMETHOD Cancel(nsresult status); NS_IMETHOD Suspend(void) { return NS_OK; } NS_IMETHOD Resume(void) { return NS_OK; } NS_IMETHOD GetLoadGroup(nsILoadGroup **aLoadGroup) { *aLoadGroup = mLoadGroup; NS_IF_ADDREF(*aLoadGroup); return NS_OK; } NS_IMETHOD SetLoadGroup(nsILoadGroup * aLoadGroup) { mLoadGroup = aLoadGroup; return NS_OK; } NS_IMETHOD GetLoadFlags(nsLoadFlags *aLoadFlags) { *aLoadFlags = nsIRequest::LOAD_NORMAL; return NS_OK; } NS_IMETHOD SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } // nsIChannel NS_IMETHOD GetOriginalURI(nsIURI **aOriginalURI) { *aOriginalURI = gURI; NS_ADDREF(*aOriginalURI); return NS_OK; } NS_IMETHOD SetOriginalURI(nsIURI* aOriginalURI) { gURI = aOriginalURI; NS_ADDREF(gURI); return NS_OK; } NS_IMETHOD GetURI(nsIURI **aURI) { *aURI = gURI; NS_ADDREF(*aURI); return NS_OK; } NS_IMETHOD SetURI(nsIURI* aURI) { gURI = aURI; NS_ADDREF(gURI); return NS_OK; } NS_IMETHOD Open(nsIInputStream **_retval) { *_retval = nsnull; return NS_OK; } NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) { return NS_OK; } NS_IMETHOD GetOwner(nsISupports **aOwner) { *aOwner = nsnull; return NS_OK; } NS_IMETHOD SetOwner(nsISupports *aOwner) { return NS_OK; } NS_IMETHOD GetNotificationCallbacks(nsIInterfaceRequestor **aNotifCallbacks) { *aNotifCallbacks = nsnull; return NS_OK; } NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor *aNotifCallbacks) { return NS_OK; } NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo) { *aSecurityInfo = nsnull; return NS_OK; } NS_IMETHOD GetContentType(nsACString &aContentType) { aContentType.Truncate(); return NS_OK; } NS_IMETHOD SetContentType(const nsACString &aContentType) { return NS_OK; } NS_IMETHOD GetContentCharset(nsACString &aContentCharset) { aContentCharset.Truncate(); return NS_OK; } NS_IMETHOD SetContentCharset(const nsACString &aContentCharset) { return NS_OK; } NS_IMETHOD GetContentLength(PRInt32 *aContentLength) { return NS_OK; } NS_IMETHOD SetContentLength(PRInt32 aContentLength) { return NS_OK; } }; PRInt32 DummyParserRequest::gRefCnt; nsIURI* DummyParserRequest::gURI; NS_IMPL_ADDREF(DummyParserRequest); NS_IMPL_RELEASE(DummyParserRequest); NS_IMPL_QUERY_INTERFACE2(DummyParserRequest, nsIRequest, nsIChannel); nsresult DummyParserRequest::Create(nsIRequest** aResult, nsIHTMLContentSink* aSink) { *aResult = new DummyParserRequest(aSink); if (!*aResult) { return NS_ERROR_OUT_OF_MEMORY; } NS_ADDREF(*aResult); return NS_OK; } DummyParserRequest::DummyParserRequest(nsIHTMLContentSink* aSink) { NS_INIT_ISUPPORTS(); if (gRefCnt++ == 0) { #ifdef DEBUG nsresult rv = #endif NS_NewURI(&gURI, NS_LITERAL_CSTRING("about:parser-dummy-request")); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create about:parser-dummy-request"); } mSink = aSink; } DummyParserRequest::~DummyParserRequest() { if (--gRefCnt == 0) { NS_IF_RELEASE(gURI); } } NS_IMETHODIMP DummyParserRequest::Cancel(nsresult status) { // Cancel parser nsresult rv = NS_OK; HTMLContentSink* sink = NS_STATIC_CAST(HTMLContentSink*, mSink); if ((sink) && (sink->mParser)) { sink->mParser->CancelParsingEvents(); } return rv; } class SinkContext { public: SinkContext(HTMLContentSink* aSink); ~SinkContext(); // Normally when OpenContainer's are done the container is not // appended to it's parent until the container is closed. By setting // pre-append to true, the container will be appended when it is // created. void SetPreAppend(PRBool aPreAppend) { mPreAppend = aPreAppend; } nsresult Begin(nsHTMLTag aNodeType, nsIHTMLContent* aRoot, PRInt32 aNumFlushed, PRInt32 aInsertionPoint); nsresult OpenContainer(const nsIParserNode& aNode); nsresult CloseContainer(const nsHTMLTag aTag); nsresult AddLeaf(const nsIParserNode& aNode); nsresult AddLeaf(nsIHTMLContent* aContent); nsresult AddComment(const nsIParserNode& aNode); nsresult End(); nsresult GrowStack(); nsresult AddText(const nsAString& aText); nsresult FlushText(PRBool* aDidFlush = nsnull, PRBool aReleaseLast = PR_FALSE); nsresult FlushTextAndRelease(PRBool* aDidFlush = nsnull) { return FlushText(aDidFlush, PR_TRUE); } nsresult FlushTags(PRBool aNotify = PR_TRUE); PRBool IsCurrentContainer(nsHTMLTag mType); PRBool IsAncestorContainer(nsHTMLTag mType); nsIHTMLContent* GetCurrentContainer(); void DidAddContent(nsIContent* aContent, PRBool aDidNotify = PR_FALSE); void UpdateChildCounts(); HTMLContentSink* mSink; PRBool mPreAppend; PRInt32 mNotifyLevel; nsIContent* mLastTextNode; PRInt32 mLastTextNodeSize; struct Node { nsHTMLTag mType; nsIHTMLContent* mContent; PRUint32 mFlags; PRInt32 mNumFlushed; PRInt32 mInsertionPoint; }; // Node.mFlags #define APPENDED 0x1 Node* mStack; PRInt32 mStackSize; PRInt32 mStackPos; PRUnichar* mText; PRInt32 mTextLength; PRInt32 mTextSize; }; //---------------------------------------------------------------------- #ifdef NS_DEBUG void HTMLContentSink::SinkTraceNode(PRUint32 aBit, const char* aMsg, const nsHTMLTag aTag, PRInt32 aStackPos, void* aThis) { if (SINK_LOG_TEST(gSinkLogModuleInfo, aBit)) { nsCOMPtr dtd; if (mParser) { mParser->GetDTD(getter_AddRefs(dtd)); if (!dtd) return; } const char* cp = NS_ConvertUCS2toUTF8(dtd->IntTagToStringTag(aTag)).get(); PR_LogPrint("%s: this=%p node='%s' stackPos=%d", aMsg, aThis, cp, aStackPos); } } #endif nsresult HTMLContentSink::AddAttributes(const nsIParserNode& aNode, nsIHTMLContent* aContent, PRBool aNotify) { // Add tag attributes to the content attributes PRInt32 ac = aNode.GetAttributeCount(); if (ac == 0) { // No attributes, nothing to do. Do an early return to avoid // constructing the nsAutoString object for nothing. return NS_OK; } nsAutoString k; nsHTMLTag nodeType = nsHTMLTag(aNode.GetNodeType()); for (PRInt32 i = 0; i < ac; i++) { // Get upper-cased key const nsAString& key = aNode.GetKeyAt(i); k.Assign(key); ToLowerCase(k); nsCOMPtr keyAtom(dont_AddRef(NS_NewAtom(k))); if (!aContent->HasAttr(kNameSpaceID_None, keyAtom)) { // Get value and remove mandatory quotes static const char* kWhitespace = "\n\r\t\b"; const nsAString& v = nsContentUtils::TrimCharsInSet(kWhitespace, aNode.GetValueAt(i)); if (nodeType == eHTMLTag_a && keyAtom == nsHTMLAtoms::name) { NS_ConvertUCS2toUTF8 cname(v); NS_ConvertUTF8toUCS2 uv(nsUnescape(NS_CONST_CAST(char *, cname.get()))); // Add attribute to content aContent->SetAttr(kNameSpaceID_None, keyAtom, uv, aNotify); } else { // Add attribute to content aContent->SetAttr(kNameSpaceID_None, keyAtom, v, aNotify); } } } return NS_OK; } static void SetForm(nsIHTMLContent* aContent, nsIDOMHTMLFormElement* aForm) { nsCOMPtr formControl(do_QueryInterface(aContent)); if (formControl) { formControl->SetForm(aForm); } } static nsresult MakeContentObject(nsHTMLTag aNodeType, nsINodeInfo *aNodeInfo, nsIDOMHTMLFormElement* aForm, nsIWebShell* aWebShell, nsIHTMLContent** aResult, const nsAString* aSkippedContent, PRBool aInsideNoXXXTag, PRBool aFromParser); /** * Factory subroutine to create all of the html content objects. */ nsresult HTMLContentSink::CreateContentObject(const nsIParserNode& aNode, nsHTMLTag aNodeType, nsIDOMHTMLFormElement* aForm, nsIWebShell* aWebShell, nsIHTMLContent** aResult) { nsresult rv = NS_OK; // Find/create atom for the tag name nsCOMPtr nodeInfo; if (aNodeType == eHTMLTag_userdefined) { nsAutoString tmp; tmp.Append(aNode.GetText()); ToLowerCase(tmp); rv = mNodeInfoManager->GetNodeInfo(tmp, nsnull, kNameSpaceID_None, *getter_AddRefs(nodeInfo)); } else { nsCOMPtr dtd; rv = mParser->GetDTD(getter_AddRefs(dtd)); if (NS_SUCCEEDED(rv)) { nsDependentString tag(dtd->IntTagToStringTag(aNodeType)); rv = mNodeInfoManager->GetNodeInfo(tag, nsnull, kNameSpaceID_None, *getter_AddRefs(nodeInfo)); } } NS_ENSURE_SUCCESS(rv, rv); // XXX if the parser treated the text in a textarea like a normal // textnode we wouldn't need to do this. nsAutoString skippedContent; if (aNodeType == eHTMLTag_textarea) { nsCOMPtr dtd; mParser->GetDTD(getter_AddRefs(dtd)); NS_ENSURE_TRUE(dtd, NS_ERROR_FAILURE); PRInt32 lineNo = 0; dtd->CollectSkippedContent(eHTMLTag_textarea, skippedContent, lineNo); } // Make the content object rv = MakeContentObject(aNodeType, nodeInfo, aForm, aWebShell, aResult, &skippedContent, !!mInsideNoXXXTag, PR_TRUE); PRInt32 id; mDocument->GetAndIncrementContentID(&id); (*aResult)->SetContentID(id); return rv; } nsresult NS_CreateHTMLElement(nsIHTMLContent** aResult, nsINodeInfo *aNodeInfo, PRBool aCaseSensitive) { nsresult rv = NS_OK; nsIParserService* parserService = nsContentUtils::GetParserServiceWeakRef(); if (!parserService) return NS_ERROR_OUT_OF_MEMORY; nsCOMPtr name; rv = aNodeInfo->GetNameAtom(*getter_AddRefs(name)); NS_ENSURE_SUCCESS(rv, rv); // Find tag in tag table PRInt32 id; if (aCaseSensitive) { parserService->HTMLCaseSensitiveAtomTagToId(name, &id); } else { parserService->HTMLAtomTagToId(name, &id); } if (aCaseSensitive) { rv = MakeContentObject(nsHTMLTag(id), aNodeInfo, nsnull, nsnull, aResult, nsnull, PR_FALSE, PR_FALSE); } else { // Revese map id to name to get the correct character case in // the tag name. nsCOMPtr kungFuDeathGrip; nsINodeInfo *nodeInfo = aNodeInfo; if (nsHTMLTag(id) != eHTMLTag_userdefined) { const PRUnichar *tag = nsnull; parserService->HTMLIdToStringTag(id, &tag); NS_ASSERTION(tag, "What? Reverse mapping of id to string broken!!!"); const PRUnichar *name_str = nsnull; name->GetUnicode(&name_str); NS_ASSERTION(name_str, "What? No string in atom?!?"); if (nsCRT::strcmp(tag, name_str) != 0) { nsCOMPtr atom(dont_AddRef(NS_NewAtom(tag))); rv = aNodeInfo->NameChanged(atom, *getter_AddRefs(kungFuDeathGrip)); NS_ENSURE_SUCCESS(rv, rv); nodeInfo = kungFuDeathGrip; } } rv = MakeContentObject(nsHTMLTag(id), nodeInfo, nsnull, nsnull, aResult, nsnull, PR_FALSE, PR_FALSE); } return rv; } //---------------------------------------------------------------------- class nsHTMLElementFactory : public nsIElementFactory, public nsSupportsWeakReference { public: nsHTMLElementFactory(); virtual ~nsHTMLElementFactory(); NS_DECL_ISUPPORTS NS_IMETHOD CreateInstanceByTag(nsINodeInfo *aNodeInfo, nsIContent** aResult); }; nsresult NS_NewHTMLElementFactory(nsIElementFactory** aInstancePtrResult) { *aInstancePtrResult = new nsHTMLElementFactory(); if (!*aInstancePtrResult) { return NS_ERROR_OUT_OF_MEMORY; } NS_ADDREF(*aInstancePtrResult); return NS_OK; } nsHTMLElementFactory::nsHTMLElementFactory() { NS_INIT_ISUPPORTS(); } nsHTMLElementFactory::~nsHTMLElementFactory() { } NS_IMPL_ISUPPORTS2(nsHTMLElementFactory, nsIElementFactory, nsISupportsWeakReference); NS_IMETHODIMP nsHTMLElementFactory::CreateInstanceByTag(nsINodeInfo *aNodeInfo, nsIContent** aResult) { NS_ENSURE_ARG_POINTER(aResult); NS_ENSURE_ARG_POINTER(aNodeInfo); nsresult rv; nsCOMPtr htmlContent; rv = NS_CreateHTMLElement(getter_AddRefs(htmlContent), aNodeInfo, aNodeInfo->NamespaceEquals(kNameSpaceID_XHTML)); *aResult = htmlContent; NS_IF_ADDREF(*aResult); return rv; } // XXX compare switch statement against nsHTMLTags.h's list nsresult MakeContentObject(nsHTMLTag aNodeType, nsINodeInfo *aNodeInfo, nsIDOMHTMLFormElement* aForm, nsIWebShell* aWebShell, nsIHTMLContent** aResult, const nsAString* aSkippedContent, PRBool aInsideNoXXXTag, PRBool aFromParser) { nsresult rv = NS_OK; switch (aNodeType) { case eHTMLTag_a: rv = NS_NewHTMLAnchorElement(aResult, aNodeInfo); break; case eHTMLTag_applet: rv = NS_NewHTMLAppletElement(aResult, aNodeInfo); break; case eHTMLTag_area: rv = NS_NewHTMLAreaElement(aResult, aNodeInfo); break; case eHTMLTag_base: rv = NS_NewHTMLBaseElement(aResult, aNodeInfo); break; case eHTMLTag_basefont: rv = NS_NewHTMLBaseFontElement(aResult, aNodeInfo); break; case eHTMLTag_blockquote: rv = NS_NewHTMLQuoteElement(aResult, aNodeInfo); break; case eHTMLTag_body: rv = NS_NewHTMLBodyElement(aResult, aNodeInfo); break; case eHTMLTag_br: rv = NS_NewHTMLBRElement(aResult, aNodeInfo); break; case eHTMLTag_button: rv = NS_NewHTMLButtonElement(aResult, aNodeInfo); if (!aInsideNoXXXTag) { SetForm(*aResult, aForm); } break; case eHTMLTag_caption: rv = NS_NewHTMLTableCaptionElement(aResult, aNodeInfo); break; case eHTMLTag_col: rv = NS_NewHTMLTableColElement(aResult, aNodeInfo); break; case eHTMLTag_colgroup: rv = NS_NewHTMLTableColGroupElement(aResult, aNodeInfo); break; case eHTMLTag_dir: rv = NS_NewHTMLDirectoryElement(aResult, aNodeInfo); break; case eHTMLTag_del: rv = NS_NewHTMLDelElement(aResult, aNodeInfo); break; case eHTMLTag_div: case eHTMLTag_marquee: case eHTMLTag_noembed: case eHTMLTag_noframes: case eHTMLTag_noscript: case eHTMLTag_parsererror: case eHTMLTag_sourcetext: rv = NS_NewHTMLDivElement(aResult, aNodeInfo); break; case eHTMLTag_dl: rv = NS_NewHTMLDListElement(aResult, aNodeInfo); break; case eHTMLTag_embed: rv = NS_NewHTMLEmbedElement(aResult, aNodeInfo); break; case eHTMLTag_fieldset: rv = NS_NewHTMLFieldSetElement(aResult, aNodeInfo); if (!aInsideNoXXXTag) { SetForm(*aResult, aForm); } break; case eHTMLTag_font: rv = NS_NewHTMLFontElement(aResult, aNodeInfo); break; case eHTMLTag_form: // the form was already created if (aForm) { rv = CallQueryInterface(aForm, aResult); } else { rv = NS_NewHTMLFormElement(aResult, aNodeInfo); } break; case eHTMLTag_frame: rv = NS_NewHTMLFrameElement(aResult, aNodeInfo); break; case eHTMLTag_frameset: rv = NS_NewHTMLFrameSetElement(aResult, aNodeInfo); break; case eHTMLTag_h1: case eHTMLTag_h2: case eHTMLTag_h3: case eHTMLTag_h4: case eHTMLTag_h5: case eHTMLTag_h6: rv = NS_NewHTMLHeadingElement(aResult, aNodeInfo); break; case eHTMLTag_head: rv = NS_NewHTMLHeadElement(aResult, aNodeInfo); break; case eHTMLTag_hr: rv = NS_NewHTMLHRElement(aResult, aNodeInfo); break; case eHTMLTag_html: rv = NS_NewHTMLHtmlElement(aResult, aNodeInfo); break; case eHTMLTag_iframe: rv = NS_NewHTMLIFrameElement(aResult, aNodeInfo); break; case eHTMLTag_img: rv = NS_NewHTMLImageElement(aResult, aNodeInfo); break; case eHTMLTag_input: rv = NS_NewHTMLInputElement(aResult, aNodeInfo, aFromParser); if (!aInsideNoXXXTag) { SetForm(*aResult, aForm); } break; case eHTMLTag_ins: rv = NS_NewHTMLInsElement(aResult, aNodeInfo); break; case eHTMLTag_isindex: rv = NS_NewHTMLIsIndexElement(aResult, aNodeInfo); break; case eHTMLTag_label: rv = NS_NewHTMLLabelElement(aResult, aNodeInfo); if (!aInsideNoXXXTag) { SetForm(*aResult, aForm); } break; case eHTMLTag_legend: rv = NS_NewHTMLLegendElement(aResult, aNodeInfo); if (!aInsideNoXXXTag) { SetForm(*aResult, aForm); } break; case eHTMLTag_li: rv = NS_NewHTMLLIElement(aResult, aNodeInfo); break; case eHTMLTag_link: rv = NS_NewHTMLLinkElement(aResult, aNodeInfo); break; case eHTMLTag_map: rv = NS_NewHTMLMapElement(aResult, aNodeInfo); break; case eHTMLTag_menu: rv = NS_NewHTMLMenuElement(aResult, aNodeInfo); break; case eHTMLTag_meta: rv = NS_NewHTMLMetaElement(aResult, aNodeInfo); break; case eHTMLTag_object: rv = NS_NewHTMLObjectElement(aResult, aNodeInfo); if (!aInsideNoXXXTag) { SetForm(*aResult, aForm); } break; case eHTMLTag_ol: rv = NS_NewHTMLOListElement(aResult, aNodeInfo); break; case eHTMLTag_optgroup: rv = NS_NewHTMLOptGroupElement(aResult, aNodeInfo); break; case eHTMLTag_option: rv = NS_NewHTMLOptionElement(aResult, aNodeInfo); break; case eHTMLTag_p: rv = NS_NewHTMLParagraphElement(aResult, aNodeInfo); break; case eHTMLTag_pre: rv = NS_NewHTMLPreElement(aResult, aNodeInfo); break; case eHTMLTag_param: rv = NS_NewHTMLParamElement(aResult, aNodeInfo); break; case eHTMLTag_q: rv = NS_NewHTMLQuoteElement(aResult, aNodeInfo); break; case eHTMLTag_script: rv = NS_NewHTMLScriptElement(aResult, aNodeInfo); break; case eHTMLTag_select: rv = NS_NewHTMLSelectElement(aResult, aNodeInfo, aFromParser); if (!aInsideNoXXXTag) { SetForm(*aResult, aForm); } break; case eHTMLTag_spacer: rv = NS_NewHTMLSpacerElement(aResult, aNodeInfo); break; case eHTMLTag_style: rv = NS_NewHTMLStyleElement(aResult, aNodeInfo); break; case eHTMLTag_table: rv = NS_NewHTMLTableElement(aResult, aNodeInfo); break; case eHTMLTag_tbody: case eHTMLTag_thead: case eHTMLTag_tfoot: rv = NS_NewHTMLTableSectionElement(aResult, aNodeInfo); break; case eHTMLTag_td: case eHTMLTag_th: rv = NS_NewHTMLTableCellElement(aResult, aNodeInfo); break; case eHTMLTag_textarea: rv = NS_NewHTMLTextAreaElement(aResult, aNodeInfo); // XXX: if the parser treated the text in a textarea like a normal // textnode we wouldn't need to do this. // If the text area has some content, set it if (aSkippedContent && (!aSkippedContent->IsEmpty())) { // Strip only one leading newline if there is one (bug 40394) nsString::const_iterator start, end; aSkippedContent->BeginReading(start); aSkippedContent->EndReading(end); if (*start == nsCRT::CR) { ++start; if (start != end && *start == nsCRT::LF) { ++start; } } else if (*start == nsCRT::LF) { ++start; } nsCOMPtr ta(do_QueryInterface(*aResult)); if (ta) { ta->SetDefaultValue(Substring(start, end)); } } if (!aInsideNoXXXTag) { SetForm(*aResult, aForm); } break; case eHTMLTag_title: rv = NS_NewHTMLTitleElement(aResult, aNodeInfo); break; case eHTMLTag_tr: rv = NS_NewHTMLTableRowElement(aResult, aNodeInfo); break; case eHTMLTag_ul: rv = NS_NewHTMLUListElement(aResult, aNodeInfo); break; case eHTMLTag_wbr: rv = NS_NewHTMLWBRElement(aResult, aNodeInfo); break; case eHTMLTag_unknown: case eHTMLTag_userdefined: rv = NS_NewHTMLUnknownElement(aResult, aNodeInfo); break; default: rv = NS_NewHTMLSpanElement(aResult, aNodeInfo); break; } return rv; } //---------------------------------------------------------------------- MOZ_DECL_CTOR_COUNTER(SinkContext) SinkContext::SinkContext(HTMLContentSink* aSink) : mSink(aSink), mPreAppend(PR_FALSE), mNotifyLevel(0), mLastTextNode(nsnull), mLastTextNodeSize(0), mStack(nsnull), mStackSize(0), mStackPos(0), mText(nsnull), mTextLength(0), mTextSize(0) { MOZ_COUNT_CTOR(SinkContext); } SinkContext::~SinkContext() { MOZ_COUNT_DTOR(SinkContext); if (mStack) { for (PRInt32 i = 0; i < mStackPos; i++) { NS_RELEASE(mStack[i].mContent); } delete [] mStack; } delete [] mText; NS_IF_RELEASE(mLastTextNode); } nsresult SinkContext::Begin(nsHTMLTag aNodeType, nsIHTMLContent* aRoot, PRInt32 aNumFlushed, PRInt32 aInsertionPoint) { if (mStackSize < 1) { nsresult rv = GrowStack(); if (NS_FAILED(rv)) { return rv; } } mStack[0].mType = aNodeType; mStack[0].mContent = aRoot; mStack[0].mFlags = APPENDED; mStack[0].mNumFlushed = aNumFlushed; mStack[0].mInsertionPoint = aInsertionPoint; NS_ADDREF(aRoot); mStackPos = 1; mTextLength = 0; return NS_OK; } PRBool SinkContext::IsCurrentContainer(nsHTMLTag aTag) { if (aTag == mStack[mStackPos - 1].mType) { return PR_TRUE; } return PR_FALSE; } PRBool SinkContext::IsAncestorContainer(nsHTMLTag aTag) { PRInt32 stackPos = mStackPos - 1; while (stackPos >= 0) { if (aTag == mStack[stackPos].mType) { return PR_TRUE; } stackPos--; } return PR_FALSE; } nsIHTMLContent* SinkContext::GetCurrentContainer() { nsIHTMLContent* content = mStack[mStackPos - 1].mContent; NS_ADDREF(content); return content; } void SinkContext::DidAddContent(nsIContent* aContent, PRBool aDidNotify) { PRInt32 childCount; // If there was a notification done for this content, update the // parent's notification count. if (aDidNotify && (0 < mStackPos)) { nsIContent* parent = mStack[mStackPos - 1].mContent; parent->ChildCount(childCount); mStack[mStackPos - 1].mNumFlushed = childCount; } if ((mStackPos == 2) && (mSink->mBody == mStack[1].mContent)) { // We just finished adding something to the body mNotifyLevel = 0; } // If we just added content to a node for which // an insertion happen, we need to do an immediate // notification for that insertion. if (!aDidNotify && (0 < mStackPos) && (mStack[mStackPos - 1].mInsertionPoint != -1)) { nsIContent* parent = mStack[mStackPos - 1].mContent; #ifdef NS_DEBUG // Tracing code nsCOMPtr dtd; mSink->mParser->GetDTD(getter_AddRefs(dtd)); nsHTMLTag tag = nsHTMLTag(mStack[mStackPos - 1].mType); nsDependentString str(dtd->IntTagToStringTag(tag)); SINK_TRACE(SINK_TRACE_REFLOW, ("SinkContext::DidAddContent: Insertion notification for " "parent=%s at position=%d and stackPos=%d", NS_LossyConvertUCS2toASCII(str).get(), mStack[mStackPos - 1].mInsertionPoint - 1, mStackPos - 1)); #endif mSink->NotifyInsert(parent, aContent, mStack[mStackPos - 1].mInsertionPoint - 1); parent->ChildCount(childCount); mStack[mStackPos - 1].mNumFlushed = childCount; } else if (!aDidNotify && mSink->IsTimeToNotify()) { SINK_TRACE(SINK_TRACE_REFLOW, ("SinkContext::DidAddContent: Notification as a result of the " "interval expiring; backoff count: %d", mSink->mBackoffCount)); FlushTags(PR_TRUE); } } nsresult SinkContext::OpenContainer(const nsIParserNode& aNode) { FlushTextAndRelease(); SINK_TRACE_NODE(SINK_TRACE_CALLS, "SinkContext::OpenContainer", nsHTMLTag(aNode.GetNodeType()), mStackPos, mSink); nsresult rv; if (mStackPos + 1 > mStackSize) { rv = GrowStack(); if (NS_FAILED(rv)) { return rv; } } // Create new container content object nsHTMLTag nodeType = nsHTMLTag(aNode.GetNodeType()); nsIHTMLContent* content; rv = mSink->CreateContentObject(aNode, nodeType, mSink->mCurrentForm, mSink->mFrameset ? mSink->mWebShell : nsnull, &content); NS_ENSURE_SUCCESS(rv, rv); mStack[mStackPos].mType = nodeType; mStack[mStackPos].mContent = content; mStack[mStackPos].mFlags = 0; mStack[mStackPos].mNumFlushed = 0; mStack[mStackPos].mInsertionPoint = -1; content->SetDocument(mSink->mDocument, PR_FALSE, PR_TRUE); rv = mSink->AddAttributes(aNode, content); if (mPreAppend) { if (mStackPos <= 0) { NS_ERROR("container w/o parent"); return NS_ERROR_FAILURE; } nsIHTMLContent* parent = mStack[mStackPos - 1].mContent; if (mStack[mStackPos - 1].mInsertionPoint != -1) { parent->InsertChildAt(content, mStack[mStackPos - 1].mInsertionPoint++, PR_FALSE, PR_FALSE); } else { parent->AppendChildTo(content, PR_FALSE, PR_FALSE); } mStack[mStackPos].mFlags |= APPENDED; } mStackPos++; NS_ENSURE_SUCCESS(rv, rv); if (mSink->IsMonolithicContainer(nodeType)) { mSink->mInMonolithicContainer++; } // Special handling for certain tags switch (nodeType) { case eHTMLTag_noembed: case eHTMLTag_noframes: mSink->mInsideNoXXXTag++; break; case eHTMLTag_a: mSink->ProcessATag(aNode, content); break; case eHTMLTag_form: case eHTMLTag_table: case eHTMLTag_thead: case eHTMLTag_tbody: case eHTMLTag_tfoot: case eHTMLTag_tr: case eHTMLTag_td: case eHTMLTag_th: // XXX if navigator_quirks_mode (only body in html supports background) mSink->AddBaseTagInfo(content); break; case eHTMLTag_map: mSink->ProcessMAPTag(aNode, content); break; case eHTMLTag_iframe: mSink->mNumOpenIFRAMES++; break; default: break; } return NS_OK; } nsresult SinkContext::CloseContainer(const nsHTMLTag aTag) { nsresult result = NS_OK; // Flush any collected text content. Release the last text // node to indicate that no more should be added to it. FlushTextAndRelease(); SINK_TRACE_NODE(SINK_TRACE_CALLS, "SinkContext::CloseContainer", aTag, mStackPos - 1, mSink); NS_WARN_IF_FALSE(mStackPos > 0, "stack out of bounds. wrong context probably!"); if (mStackPos <= 0) { return NS_OK; // Fix crash - Ref. bug 45975 or 45007 } --mStackPos; nsHTMLTag nodeType = mStack[mStackPos].mType; nsIHTMLContent* content = mStack[mStackPos].mContent; content->Compact(); // Add container to its parent if we haven't already done it if ((mStack[mStackPos].mFlags & APPENDED) == 0) { NS_ASSERTION(mStackPos > 0, "container w/o parent"); if (mStackPos <= 0) { return NS_ERROR_FAILURE; } nsIHTMLContent* parent = mStack[mStackPos - 1].mContent; // If the parent has an insertion point, insert rather than // append. if (mStack[mStackPos - 1].mInsertionPoint != -1) { result = parent->InsertChildAt(content, mStack[mStackPos - 1].mInsertionPoint++, PR_FALSE, PR_FALSE); } else { result = parent->AppendChildTo(content, PR_FALSE, PR_FALSE); } } // If we're in a state where we do append notifications as // we go up the tree, and we're at the level where the next // notification needs to be done, do the notification. if (mNotifyLevel >= mStackPos) { PRInt32 childCount; // Check to see if new content has been added after our last // notification content->ChildCount(childCount); if (mStack[mStackPos].mNumFlushed < childCount) { #ifdef NS_DEBUG // Tracing code nsCOMPtr tag; mStack[mStackPos].mContent->GetTag(*getter_AddRefs(tag)); const PRUnichar* tagChar; tag->GetUnicode(&tagChar); nsDependentString str(tagChar); SINK_TRACE(SINK_TRACE_REFLOW, ("SinkContext::CloseContainer: reflow on notifyImmediate " "tag=%s newIndex=%d stackPos=%d", NS_LossyConvertUCS2toASCII(str).get(), mStack[mStackPos].mNumFlushed, mStackPos)); #endif mSink->NotifyAppend(content, mStack[mStackPos].mNumFlushed); } // Indicate that notification has now happened at this level mNotifyLevel = mStackPos - 1; } if (mSink->IsMonolithicContainer(nodeType)) { --mSink->mInMonolithicContainer; } DidAddContent(content, PR_FALSE); // Special handling for certain tags switch (nodeType) { case eHTMLTag_noembed: case eHTMLTag_noframes: // Fix bug 40216 NS_ASSERTION((mSink->mInsideNoXXXTag > 0), "mInsideNoXXXTag underflow"); if (mSink->mInsideNoXXXTag > 0) { mSink->mInsideNoXXXTag--; } break; case eHTMLTag_form: { mSink->mFlags &= ~NS_SINK_FLAG_FORM_ON_STACK; // If there's a FORM on the stack, but this close tag doesn't // close the form, then close out the form *and* close out the // next container up. This is since the parser doesn't do fix up // of invalid form nesting. When the end FORM tag comes through, // we'll ignore it. if (aTag != nodeType) { result = CloseContainer(aTag); } } break; case eHTMLTag_iframe: mSink->mNumOpenIFRAMES--; break; case eHTMLTag_select: { nsCOMPtr select = do_QueryInterface(content); if (select) { result = select->DoneAddingChildren(); } } break; default: break; } NS_IF_RELEASE(content); #ifdef DEBUG if (mPreAppend && SINK_LOG_TEST(gSinkLogModuleInfo, SINK_ALWAYS_REFLOW)) { mSink->ForceReflow(); } #endif return result; } nsresult SinkContext::AddLeaf(const nsIParserNode& aNode) { SINK_TRACE_NODE(SINK_TRACE_CALLS, "SinkContext::AddLeaf", nsHTMLTag(aNode.GetNodeType()), mStackPos, mSink); nsresult rv = NS_OK; switch (aNode.GetTokenType()) { case eToken_start: { FlushTextAndRelease(); // Create new leaf content object nsHTMLTag nodeType = nsHTMLTag(aNode.GetNodeType()); nsCOMPtr content; rv = mSink->CreateContentObject(aNode, nodeType, mSink->mCurrentForm, mSink->mWebShell, getter_AddRefs(content)); NS_ENSURE_SUCCESS(rv, rv); // Set the content's document content->SetDocument(mSink->mDocument, PR_FALSE, PR_TRUE); rv = mSink->AddAttributes(aNode, content); NS_ENSURE_SUCCESS(rv, rv); switch (nodeType) { case eHTMLTag_img: // elements with 'SRC=' case eHTMLTag_frame: case eHTMLTag_input: mSink->AddBaseTagInfo(content); break; default: break; } // Add new leaf to its parent AddLeaf(content); // Notify input and button that they are now fully created switch (nodeType) { case eHTMLTag_input: case eHTMLTag_button: content->DoneCreatingElement(); break; case eHTMLTag_textarea: { // XXX textarea deserves to be treated like the container it is. nsCOMPtr textarea(do_QueryInterface(content)); if (textarea) { textarea->DoneAddingChildren(); } break; } default: break; } } break; case eToken_text: case eToken_whitespace: case eToken_newline: rv = AddText(aNode.GetText()); break; case eToken_entity: { nsAutoString tmp; PRInt32 unicode = aNode.TranslateToUnicodeStr(tmp); if (unicode < 0) { rv = AddText(aNode.GetText()); } else { // Map carriage returns to newlines if (!tmp.IsEmpty()) { if (tmp.CharAt(0) == '\r') { tmp.Assign((PRUnichar)'\n'); } rv = AddText(tmp); } } } break; default: break; } return rv; } nsresult SinkContext::AddLeaf(nsIHTMLContent* aContent) { NS_ASSERTION(mStackPos > 0, "leaf w/o container"); if (mStackPos <= 0) { return NS_ERROR_FAILURE; } nsIHTMLContent* parent = mStack[mStackPos - 1].mContent; // If the parent has an insertion point, insert rather than append. if (mStack[mStackPos - 1].mInsertionPoint != -1) { parent->InsertChildAt(aContent, mStack[mStackPos - 1].mInsertionPoint++, PR_FALSE, PR_FALSE); } else { parent->AppendChildTo(aContent, PR_FALSE, PR_FALSE); } DidAddContent(aContent, PR_FALSE); #ifdef DEBUG if (mPreAppend && SINK_LOG_TEST(gSinkLogModuleInfo, SINK_ALWAYS_REFLOW)) { mSink->ForceReflow(); } #endif return NS_OK; } nsresult SinkContext::AddComment(const nsIParserNode& aNode) { SINK_TRACE_NODE(SINK_TRACE_CALLS, "SinkContext::AddLeaf", nsHTMLTag(aNode.GetNodeType()), mStackPos, mSink); FlushTextAndRelease(); nsCOMPtr comment; nsresult rv = NS_NewCommentNode(getter_AddRefs(comment)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr domComment(do_QueryInterface(comment)); NS_ENSURE_TRUE(domComment, NS_ERROR_UNEXPECTED); domComment->AppendData(aNode.GetText()); comment->SetDocument(mSink->mDocument, PR_FALSE, PR_TRUE); nsIHTMLContent* parent; if (!mSink->mBody && mSink->mHead) { parent = mSink->mHead; } else { parent = mStack[mStackPos - 1].mContent; } // If the parent has an insertion point, insert rather than append. if (mStack[mStackPos - 1].mInsertionPoint != -1) { parent->InsertChildAt(comment, mStack[mStackPos - 1].mInsertionPoint++, PR_FALSE, PR_FALSE); } else { parent->AppendChildTo(comment, PR_FALSE, PR_FALSE); } DidAddContent(comment, PR_FALSE); #ifdef DEBUG if (mPreAppend && SINK_LOG_TEST(gSinkLogModuleInfo, SINK_ALWAYS_REFLOW)) { mSink->ForceReflow(); } #endif return rv; } nsresult SinkContext::End() { for (PRInt32 i = 0; i < mStackPos; i++) { NS_RELEASE(mStack[i].mContent); } mStackPos = 0; mTextLength = 0; return NS_OK; } nsresult SinkContext::GrowStack() { PRInt32 newSize = mStackSize * 2; if (newSize == 0) { newSize = 32; } Node* stack = new Node[newSize]; if (!stack) { return NS_ERROR_OUT_OF_MEMORY; } if (mStackPos != 0) { memcpy(stack, mStack, sizeof(Node) * mStackPos); delete [] mStack; } mStack = stack; mStackSize = newSize; return NS_OK; } /** * Add textual content to the current running text buffer. If the text buffer * overflows, flush out the text by creating a text content object and adding * it to the content tree. */ // XXX If we get a giant string grow the buffer instead of chopping it // up??? nsresult SinkContext::AddText(const nsAString& aText) { PRInt32 addLen = aText.Length(); if (addLen == 0) { return NS_OK; } // Create buffer when we first need it if (mTextSize == 0) { mText = new PRUnichar[4096]; if (!mText) { return NS_ERROR_OUT_OF_MEMORY; } mTextSize = 4096; } // Copy data from string into our buffer; flush buffer when it fills up PRInt32 offset = 0; PRBool isLastCharCR = PR_FALSE; while (addLen != 0) { PRInt32 amount = mTextSize - mTextLength; if (amount > addLen) { amount = addLen; } if (amount == 0) { // Don't release last text node so we can add to it again nsresult rv = FlushText(); if (NS_FAILED(rv)) { return rv; } } mTextLength += nsContentUtils::CopyNewlineNormalizedUnicodeTo(aText, offset, &mText[mTextLength], amount, isLastCharCR); offset += amount; addLen -= amount; } return NS_OK; } /** * Flush all elements that have been seen so far such that * they are visible in the tree. Specifically, make sure * that they are all added to their respective parents. * Also, do notification at the top for all content that * has been newly added so that the frame tree is complete. */ nsresult SinkContext::FlushTags(PRBool aNotify) { nsresult result = NS_OK; // Don't release last text node in case we need to add to it again FlushText(); PRInt32 childCount; nsIHTMLContent* content; // Start from the top of the stack (growing upwards) and append // all content that hasn't been previously appended to the tree PRInt32 stackPos = mStackPos - 1; while ((stackPos > 0) && ((mStack[stackPos].mFlags & APPENDED) == 0)) { content = mStack[stackPos].mContent; nsIHTMLContent* parent = mStack[stackPos - 1].mContent; mStack[stackPos].mFlags |= APPENDED; // If the parent has an insertion point, insert rather than // append. if (mStack[mStackPos - 1].mInsertionPoint != -1) { parent->InsertChildAt(content, mStack[mStackPos - 1].mInsertionPoint++, PR_FALSE, PR_FALSE); } else { parent->AppendChildTo(content, PR_FALSE, PR_FALSE); } stackPos--; } if (aNotify) { // Start from the base of the stack (growing upward) and do // a notification from the node that is closest to the root of // tree for any content that has been added. stackPos = 1; PRBool flushed = PR_FALSE; while (stackPos < mStackPos) { content = mStack[stackPos].mContent; content->ChildCount(childCount); if (!flushed && (mStack[stackPos].mNumFlushed < childCount)) { #ifdef NS_DEBUG // Tracing code nsCOMPtr tag; mStack[stackPos].mContent->GetTag(*getter_AddRefs(tag)); const PRUnichar* tagChar; tag->GetUnicode(&tagChar); nsDependentString str(tagChar); SINK_TRACE(SINK_TRACE_REFLOW, ("SinkContext::FlushTags: tag=%s from newindex=%d at " "stackPos=%d", NS_LossyConvertUCS2toASCII(str).get(), mStack[stackPos].mNumFlushed, stackPos)); #endif if ((mStack[stackPos].mInsertionPoint != -1) && (mStackPos > (stackPos + 1))) { nsIContent* child = mStack[stackPos + 1].mContent; mSink->NotifyInsert(content, child, mStack[stackPos].mInsertionPoint); } else { mSink->NotifyAppend(content, mStack[stackPos].mNumFlushed); } flushed = PR_TRUE; } mStack[stackPos].mNumFlushed = childCount; stackPos++; } mNotifyLevel = mStackPos - 1; } return result; } void SinkContext::UpdateChildCounts() { PRInt32 childCount; nsIHTMLContent* content; // Start from the top of the stack (growing upwards) and see if any // new content has been appended. If so, we recognize that reflows // have been generated for it and we should make sure that no // further reflows occur. PRInt32 stackPos = mStackPos - 1; while (stackPos > 0) { if (mStack[stackPos].mFlags & APPENDED) { content = mStack[stackPos].mContent; content->ChildCount(childCount); mStack[stackPos].mNumFlushed = childCount; } stackPos--; } mNotifyLevel = mStackPos - 1; } /** * Flush any buffered text out by creating a text content object and * adding it to the content. */ nsresult SinkContext::FlushText(PRBool* aDidFlush, PRBool aReleaseLast) { nsresult rv = NS_OK; PRBool didFlush = PR_FALSE; if (mTextLength != 0) { if (mLastTextNode) { if ((mLastTextNodeSize + mTextLength) > mSink->mMaxTextRun) { mLastTextNodeSize = 0; NS_RELEASE(mLastTextNode); FlushText(aDidFlush, aReleaseLast); } else { nsCOMPtr cdata(do_QueryInterface(mLastTextNode)); if (cdata) { rv = cdata->AppendData(Substring(mText, mText + mTextLength)); mLastTextNodeSize += mTextLength; mTextLength = 0; didFlush = PR_TRUE; } } } else { nsIContent* content; rv = NS_NewTextNode(&content); NS_ENSURE_SUCCESS(rv, rv); // Set the content's document content->SetDocument(mSink->mDocument, PR_FALSE, PR_TRUE); // Set the text in the text node nsCOMPtr text(do_QueryInterface(content)); text->SetText(mText, mTextLength, PR_FALSE); // Eat up the rest of the text up in state. mLastTextNode = content; mLastTextNodeSize += mTextLength; mTextLength = 0; // Add text to its parent NS_ASSERTION(mStackPos > 0, "leaf w/o container"); if (mStackPos <= 0) { return NS_ERROR_FAILURE; } nsIHTMLContent* parent = mStack[mStackPos - 1].mContent; if (mStack[mStackPos - 1].mInsertionPoint != -1) { parent->InsertChildAt(content, mStack[mStackPos - 1].mInsertionPoint++, PR_FALSE, PR_FALSE); } else { parent->AppendChildTo(content, PR_FALSE, PR_FALSE); } didFlush = PR_TRUE; DidAddContent(content, PR_FALSE); } } if (aDidFlush) { *aDidFlush = didFlush; } if (aReleaseLast && mLastTextNode) { mLastTextNodeSize = 0; NS_RELEASE(mLastTextNode); } #ifdef DEBUG if (mPreAppend && didFlush && SINK_LOG_TEST(gSinkLogModuleInfo, SINK_ALWAYS_REFLOW)) { mSink->ForceReflow(); } #endif return rv; } nsresult NS_NewHTMLContentSink(nsIHTMLContentSink** aResult, nsIDocument* aDoc, nsIURI* aURL, nsIWebShell* aWebShell, nsIChannel* aChannel) { NS_ENSURE_ARG_POINTER(aResult); HTMLContentSink* it; NS_NEWXPCOM(it, HTMLContentSink); if (!it) { return NS_ERROR_OUT_OF_MEMORY; } nsresult rv = it->Init(aDoc, aURL, aWebShell, aChannel); if (NS_FAILED(rv)) { delete it; return rv; } *aResult = it; NS_ADDREF(*aResult); return NS_OK; } HTMLContentSink::HTMLContentSink() { // Note: operator new zeros our memory NS_INIT_ISUPPORTS(); #ifdef NS_DEBUG if (!gSinkLogModuleInfo) { gSinkLogModuleInfo = PR_NewLogModule("htmlcontentsink"); } #endif } HTMLContentSink::~HTMLContentSink() { NS_IF_RELEASE(mHead); NS_IF_RELEASE(mBody); NS_IF_RELEASE(mFrameset); NS_IF_RELEASE(mRoot); if (mDocument) { mDocument->RemoveObserver(this); NS_RELEASE(mDocument); } NS_IF_RELEASE(mHTMLDocument); NS_IF_RELEASE(mDocumentURI); NS_IF_RELEASE(mDocumentBaseURL); NS_IF_RELEASE(mWebShell); NS_IF_RELEASE(mParser); NS_IF_RELEASE(mCSSLoader); if (mNotificationTimer) { mNotificationTimer->Cancel(); } PRInt32 numContexts = mContextStack.Count(); if (mCurrentContext == mHeadContext) { // Pop off the second html context if it's not done earlier mContextStack.RemoveElementAt(--numContexts); } for (PRInt32 i = 0; i < numContexts; i++) { SinkContext* sc = (SinkContext*)mContextStack.ElementAt(i); if (sc) { sc->End(); if (sc == mCurrentContext) { mCurrentContext = nsnull; } delete sc; } } if (mCurrentContext == mHeadContext) { mCurrentContext = nsnull; } delete mCurrentContext; delete mHeadContext; } #ifdef DEBUG NS_IMPL_ISUPPORTS7(HTMLContentSink, nsIHTMLContentSink, nsIContentSink, nsIScriptLoaderObserver, nsITimerCallback, nsICSSLoaderObserver, nsIDocumentObserver, nsIDebugDumpContent) #else NS_IMPL_ISUPPORTS6(HTMLContentSink, nsIHTMLContentSink, nsIContentSink, nsIScriptLoaderObserver, nsITimerCallback, nsICSSLoaderObserver, nsIDocumentObserver) #endif static PRBool IsScriptEnabled(nsIDocument *aDoc, nsIWebShell *aContainer) { NS_ENSURE_TRUE(aDoc && aContainer, PR_TRUE); nsCOMPtr securityManager = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); NS_ENSURE_TRUE(securityManager, PR_TRUE); nsCOMPtr principal; aDoc->GetPrincipal(getter_AddRefs(principal)); NS_ENSURE_TRUE(principal, PR_TRUE); nsCOMPtr globalObject; aDoc->GetScriptGlobalObject(getter_AddRefs(globalObject)); // Getting context is tricky if the document hasn't had it's // GlobalObject set yet if (!globalObject) { nsCOMPtr requestor(do_QueryInterface(aContainer)); NS_ENSURE_TRUE(requestor, PR_TRUE); nsCOMPtr owner; requestor->GetInterface(NS_GET_IID(nsIScriptGlobalObjectOwner), getter_AddRefs(owner)); NS_ENSURE_TRUE(owner, PR_TRUE); owner->GetScriptGlobalObject(getter_AddRefs(globalObject)); NS_ENSURE_TRUE(globalObject, PR_TRUE); } nsCOMPtr scriptContext; globalObject->GetContext(getter_AddRefs(scriptContext)); NS_ENSURE_TRUE(scriptContext, PR_TRUE); JSContext* cx = (JSContext *) scriptContext->GetNativeContext(); NS_ENSURE_TRUE(cx, PR_TRUE); PRBool enabled = PR_TRUE; securityManager->CanExecuteScripts(cx, principal, &enabled); return enabled; } nsresult HTMLContentSink::Init(nsIDocument* aDoc, nsIURI* aURL, nsIWebShell* aContainer, nsIChannel* aChannel) { MOZ_TIMER_DEBUGLOG(("Reset and start: nsHTMLContentSink::Init(), this=%p\n", this)); MOZ_TIMER_RESET(mWatch); MOZ_TIMER_START(mWatch); if (!aDoc || !aURL || !aContainer) { NS_ERROR("Null ptr!"); MOZ_TIMER_DEBUGLOG(("Stop: nsHTMLContentSink::Init()\n")); MOZ_TIMER_STOP(mWatch); return NS_ERROR_NULL_POINTER; } nsresult rv; rv = NS_NewISupportsArray(getter_AddRefs(mScriptElements)); if (NS_FAILED(rv)) return rv; mDocument = aDoc; NS_ADDREF(aDoc); aDoc->AddObserver(this); aDoc->QueryInterface(NS_GET_IID(nsIHTMLDocument), (void**)&mHTMLDocument); rv = mDocument->GetNodeInfoManager(*getter_AddRefs(mNodeInfoManager)); NS_ENSURE_SUCCESS(rv, rv); mDocumentURI = aURL; NS_ADDREF(aURL); mDocumentBaseURL = aURL; NS_ADDREF(aURL); mWebShell = aContainer; NS_ADDREF(aContainer); mObservers = nsnull; nsIParserService* service = nsContentUtils::GetParserServiceWeakRef(); if (!service) { return NS_ERROR_OUT_OF_MEMORY; } service->GetTopicObservers(NS_LITERAL_STRING("text/html"), getter_AddRefs(mObservers)); nsCOMPtr loader; rv = mDocument->GetScriptLoader(getter_AddRefs(loader)); NS_ENSURE_SUCCESS(rv, rv); loader->AddObserver(this); PRBool enabled = PR_TRUE; nsCOMPtr docShell(do_QueryInterface(mWebShell)); NS_ASSERTION(docShell, "oops no docshell!"); if (docShell) { docShell->GetAllowSubframes(&enabled); if (enabled) { mFlags |= NS_SINK_FLAG_FRAMES_ENABLED; } } // Find out if scripts are enabled, if not, show