mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
9397418412
r=timeless
3044 lines
101 KiB
C++
3044 lines
101 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 ts=2 et tw=78: */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla 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/MPL/
|
|
*
|
|
* 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):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of 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 MPL, 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 MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#include "nsDebug.h"
|
|
#include "nsIAtom.h"
|
|
#include "CNavDTD.h"
|
|
#include "nsHTMLTokens.h"
|
|
#include "nsCRT.h"
|
|
#include "nsParser.h"
|
|
#include "nsIHTMLContentSink.h"
|
|
#include "nsScanner.h"
|
|
#include "prenv.h"
|
|
#include "prtypes.h"
|
|
#include "prio.h"
|
|
#include "plstr.h"
|
|
#include "nsDTDUtils.h"
|
|
#include "nsHTMLTokenizer.h"
|
|
#include "nsTime.h"
|
|
#include "nsParserNode.h"
|
|
#include "nsHTMLEntities.h"
|
|
#include "nsLinebreakConverter.h"
|
|
#include "nsIFormProcessor.h"
|
|
#include "nsTArray.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsUnicharUtils.h"
|
|
#include "prmem.h"
|
|
#include "nsIServiceManager.h"
|
|
|
|
#ifdef NS_DEBUG
|
|
#include "nsLoggingSink.h"
|
|
#endif
|
|
|
|
/*
|
|
* Ignore kFontStyle and kPhrase tags when the stack is deep, bug 58917.
|
|
*/
|
|
#define FONTSTYLE_IGNORE_DEPTH (MAX_REFLOW_DEPTH * 80 / 100)
|
|
#define PHRASE_IGNORE_DEPTH (MAX_REFLOW_DEPTH * 90 / 100)
|
|
|
|
static NS_DEFINE_CID(kFormProcessorCID, NS_FORMPROCESSOR_CID);
|
|
|
|
#ifdef DEBUG
|
|
static const char kNullToken[] = "Error: Null token given";
|
|
static const char kInvalidTagStackPos[] = "Error: invalid tag stack position";
|
|
#endif
|
|
|
|
#include "nsElementTable.h"
|
|
|
|
// Some flags for use by the DTD.
|
|
#define NS_DTD_FLAG_NONE 0x00000000
|
|
#define NS_DTD_FLAG_HAS_OPEN_HEAD 0x00000001
|
|
#define NS_DTD_FLAG_HAS_OPEN_BODY 0x00000002
|
|
#define NS_DTD_FLAG_HAS_OPEN_FORM 0x00000004
|
|
#define NS_DTD_FLAG_HAS_EXPLICIT_HEAD 0x00000008
|
|
#define NS_DTD_FLAG_HAD_BODY 0x00000010
|
|
#define NS_DTD_FLAG_HAD_FRAMESET 0x00000020
|
|
#define NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE 0x00000040
|
|
#define NS_DTD_FLAG_ALTERNATE_CONTENT 0x00000080 // NOFRAMES, NOSCRIPT
|
|
#define NS_DTD_FLAG_MISPLACED_CONTENT 0x00000100
|
|
#define NS_DTD_FLAG_IN_MISPLACED_CONTENT 0x00000200
|
|
#define NS_DTD_FLAG_STOP_PARSING 0x00000400
|
|
|
|
#define NS_DTD_FLAG_HAS_MAIN_CONTAINER (NS_DTD_FLAG_HAD_BODY | \
|
|
NS_DTD_FLAG_HAD_FRAMESET)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CNavDTD)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDTD)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDTD)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(CNavDTD)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(CNavDTD)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_1(CNavDTD, mSink)
|
|
|
|
CNavDTD::CNavDTD()
|
|
: mMisplacedContent(0),
|
|
mTokenAllocator(0),
|
|
mBodyContext(new nsDTDContext()),
|
|
mTempContext(0),
|
|
mCountLines(PR_TRUE),
|
|
mTokenizer(0),
|
|
mDTDMode(eDTDMode_quirks),
|
|
mDocType(eHTML_Quirks),
|
|
mParserCommand(eViewNormal),
|
|
mLineNumber(1),
|
|
mOpenMapCount(0),
|
|
mHeadContainerPosition(-1),
|
|
mFlags(NS_DTD_FLAG_NONE)
|
|
{
|
|
}
|
|
|
|
#ifdef NS_DEBUG
|
|
|
|
static nsLoggingSink*
|
|
GetLoggingSink()
|
|
{
|
|
// This returns a content sink that is useful for following what calls the DTD
|
|
// makes to the content sink.
|
|
|
|
static PRBool checkForPath = PR_TRUE;
|
|
static nsLoggingSink *theSink = nsnull;
|
|
static const char* gLogPath = nsnull;
|
|
|
|
if (checkForPath) {
|
|
// Only check once per run.
|
|
gLogPath = PR_GetEnv("PARSE_LOGFILE");
|
|
checkForPath = PR_FALSE;
|
|
}
|
|
|
|
|
|
if (gLogPath && !theSink) {
|
|
static nsLoggingSink gLoggingSink;
|
|
|
|
PRIntn theFlags = PR_CREATE_FILE | PR_RDWR;
|
|
|
|
// Open the record file.
|
|
PRFileDesc *theLogFile = PR_Open(gLogPath, theFlags, 0);
|
|
gLoggingSink.SetOutputStream(theLogFile, PR_TRUE);
|
|
theSink = &gLoggingSink;
|
|
}
|
|
|
|
return theSink;
|
|
}
|
|
|
|
#endif
|
|
|
|
CNavDTD::~CNavDTD()
|
|
{
|
|
delete mBodyContext;
|
|
delete mTempContext;
|
|
|
|
#ifdef NS_DEBUG
|
|
if (mSink) {
|
|
nsLoggingSink *theLogSink = GetLoggingSink();
|
|
if (mSink == theLogSink) {
|
|
theLogSink->ReleaseProxySink();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
CNavDTD::WillBuildModel(const CParserContext& aParserContext,
|
|
nsITokenizer* aTokenizer,
|
|
nsIContentSink* aSink)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
mFilename = aParserContext.mScanner->GetFilename();
|
|
mFlags = NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE;
|
|
mLineNumber = 1;
|
|
mDTDMode = aParserContext.mDTDMode;
|
|
mParserCommand = aParserContext.mParserCommand;
|
|
mMimeType = aParserContext.mMimeType;
|
|
mDocType = aParserContext.mDocType;
|
|
mTokenizer = aTokenizer;
|
|
mBodyContext->SetNodeAllocator(&mNodeAllocator);
|
|
|
|
if (!aParserContext.mPrevContext && aSink) {
|
|
|
|
if (!mSink) {
|
|
mSink = do_QueryInterface(aSink, &result);
|
|
if (NS_FAILED(result)) {
|
|
mFlags |= NS_DTD_FLAG_STOP_PARSING;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Let's see if the environment is set up for us to write output to
|
|
// a logging sink. If so, then we'll create one, and make it the
|
|
// proxy for the real sink we're given from the parser.
|
|
#ifdef NS_DEBUG
|
|
nsLoggingSink *theLogSink = GetLoggingSink();
|
|
if (theLogSink) {
|
|
theLogSink->SetProxySink(mSink);
|
|
mSink = theLogSink;
|
|
}
|
|
#endif
|
|
|
|
mFlags |= nsHTMLTokenizer::GetFlags(aSink);
|
|
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
CNavDTD::BuildModel(nsITokenizer* aTokenizer,
|
|
PRBool aCanInterrupt,
|
|
PRBool aCountLines,
|
|
const nsCString*)
|
|
{
|
|
NS_PRECONDITION(mBodyContext != nsnull,
|
|
"Create a context before calling build model");
|
|
|
|
nsresult result = NS_OK;
|
|
|
|
if (!aTokenizer) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsITokenizer* const oldTokenizer = mTokenizer;
|
|
|
|
mCountLines = aCountLines;
|
|
mTokenizer = aTokenizer;
|
|
mTokenAllocator = mTokenizer->GetTokenAllocator();
|
|
|
|
if (!mSink) {
|
|
return (mFlags & NS_DTD_FLAG_STOP_PARSING)
|
|
? NS_ERROR_HTMLPARSER_STOPPARSING
|
|
: result;
|
|
}
|
|
|
|
if (mBodyContext->GetCount() == 0) {
|
|
CToken* tempToken;
|
|
if (ePlainText == mDocType) {
|
|
tempToken =
|
|
mTokenAllocator->CreateTokenOfType(eToken_start, eHTMLTag_pre);
|
|
if (tempToken) {
|
|
mTokenizer->PushTokenFront(tempToken);
|
|
}
|
|
}
|
|
|
|
// Always open a body if frames are disabled.
|
|
if (!(mFlags & NS_IPARSER_FLAG_FRAMES_ENABLED)) {
|
|
tempToken =
|
|
mTokenAllocator->CreateTokenOfType(eToken_start,
|
|
eHTMLTag_body,
|
|
NS_LITERAL_STRING("body"));
|
|
if (tempToken) {
|
|
mTokenizer->PushTokenFront(tempToken);
|
|
}
|
|
}
|
|
|
|
// If the content model is empty, then begin by opening <html>.
|
|
CStartToken* theToken = (CStartToken*)mTokenizer->GetTokenAt(0);
|
|
if (theToken) {
|
|
eHTMLTags theTag = (eHTMLTags)theToken->GetTypeID();
|
|
eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType());
|
|
if (theTag != eHTMLTag_html || theType != eToken_start) {
|
|
tempToken =
|
|
mTokenAllocator->CreateTokenOfType(eToken_start,
|
|
eHTMLTag_html,
|
|
NS_LITERAL_STRING("html"));
|
|
if (tempToken) {
|
|
mTokenizer->PushTokenFront(tempToken);
|
|
}
|
|
}
|
|
} else {
|
|
tempToken =
|
|
mTokenAllocator->CreateTokenOfType(eToken_start,
|
|
eHTMLTag_html,
|
|
NS_LITERAL_STRING("html"));
|
|
if (tempToken) {
|
|
mTokenizer->PushTokenFront(tempToken);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (NS_SUCCEEDED(result)) {
|
|
if (!(mFlags & NS_DTD_FLAG_STOP_PARSING)) {
|
|
CToken* theToken = mTokenizer->PopToken();
|
|
if (!theToken) {
|
|
break;
|
|
}
|
|
result = HandleToken(theToken);
|
|
} else {
|
|
result = NS_ERROR_HTMLPARSER_STOPPARSING;
|
|
break;
|
|
}
|
|
|
|
if (NS_ERROR_HTMLPARSER_INTERRUPTED == mSink->DidProcessAToken()) {
|
|
// The content sink has requested that DTD interrupt processing tokens
|
|
// So we need to make sure the parser is in a state where it can be
|
|
// interrupted (e.g., not in a document.write).
|
|
// We also need to make sure that an interruption does not override
|
|
// a request to block the parser.
|
|
if (aCanInterrupt && NS_SUCCEEDED(result)) {
|
|
result = NS_ERROR_HTMLPARSER_INTERRUPTED;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
mTokenizer = oldTokenizer;
|
|
return result;
|
|
}
|
|
|
|
nsresult
|
|
CNavDTD::BuildNeglectedTarget(eHTMLTags aTarget,
|
|
eHTMLTokenTypes aType)
|
|
{
|
|
NS_ASSERTION(mTokenizer, "tokenizer is null! unable to build target.");
|
|
NS_ASSERTION(mTokenAllocator, "unable to create tokens without an allocator.");
|
|
if (!mTokenizer || !mTokenAllocator) {
|
|
return NS_OK;
|
|
}
|
|
|
|
CToken* target = mTokenAllocator->CreateTokenOfType(aType, aTarget);
|
|
NS_ENSURE_TRUE(target, NS_ERROR_OUT_OF_MEMORY);
|
|
mTokenizer->PushTokenFront(target);
|
|
// Always safe to disallow interruptions, so it doesn't matter that we've
|
|
// forgotten the aCanInterrupt parameter to BuildModel. Also, BuildModel
|
|
// doesn't seem to care about the charset, and at this point we have no idea
|
|
// what the charset was, so 0 can and must suffice. If either of these
|
|
// values mattered, we'd want to store them as data members in BuildModel.
|
|
return BuildModel(mTokenizer, PR_FALSE, mCountLines, 0);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
CNavDTD::DidBuildModel(nsresult anErrorCode)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
if (mSink) {
|
|
if (NS_OK == anErrorCode) {
|
|
if (!(mFlags & NS_DTD_FLAG_HAS_MAIN_CONTAINER)) {
|
|
// This document is not a frameset document, however, it did not contain
|
|
// a body tag either. So, make one!. Note: Body tag is optional per spec..
|
|
// Also note: We ignore the return value of BuildNeglectedTarget, we
|
|
// can't reasonably respond to errors (or requests to block) at this
|
|
// point in the parsing process.
|
|
BuildNeglectedTarget(eHTMLTag_body, eToken_start);
|
|
}
|
|
if (mFlags & NS_DTD_FLAG_MISPLACED_CONTENT) {
|
|
// Looks like the misplaced contents are not processed yet.
|
|
// Here is our last chance to handle the misplaced content.
|
|
|
|
// Keep track of the top index.
|
|
PRInt32 topIndex = mBodyContext->mContextTopIndex;
|
|
|
|
// Loop until we've really consumed all of our misplaced content.
|
|
do {
|
|
mFlags &= ~NS_DTD_FLAG_MISPLACED_CONTENT;
|
|
|
|
// mContextTopIndex refers to the misplaced content's legal parent index.
|
|
result = HandleSavedTokens(mBodyContext->mContextTopIndex);
|
|
if (NS_FAILED(result)) {
|
|
NS_ERROR("Bug in the DTD");
|
|
break;
|
|
}
|
|
|
|
// If we start handling misplaced content while handling misplaced
|
|
// content, mContextTopIndex gets modified. However, this new index
|
|
// necessarily points to the middle of a closed tag (since we close
|
|
// new tags after handling the misplaced content). So we restore the
|
|
// insertion point after every iteration.
|
|
mBodyContext->mContextTopIndex = topIndex;
|
|
} while (mFlags & NS_DTD_FLAG_MISPLACED_CONTENT);
|
|
|
|
mBodyContext->mContextTopIndex = -1;
|
|
}
|
|
|
|
// Now let's disable style handling to save time when closing remaining
|
|
// stack members.
|
|
mFlags &= ~NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE;
|
|
while (mBodyContext->GetCount() > 0) {
|
|
result = CloseContainersTo(mBodyContext->Last(), PR_FALSE);
|
|
NS_ENSURE_SUCCESS(result, result);
|
|
}
|
|
} else {
|
|
// If you're here, then an error occurred, but we still have nodes on the stack.
|
|
// At a minimum, we should grab the nodes and recycle them.
|
|
// Just to be correct, we'll also recycle the nodes.
|
|
while (mBodyContext->GetCount() > 0) {
|
|
nsEntryStack* theChildStyles = 0;
|
|
nsCParserNode* theNode = mBodyContext->Pop(theChildStyles);
|
|
IF_DELETE(theChildStyles, &mNodeAllocator);
|
|
IF_FREE(theNode, &mNodeAllocator);
|
|
}
|
|
}
|
|
|
|
// Now make sure the misplaced content list is empty,
|
|
// by forcefully recycling any tokens we might find there.
|
|
CToken* theToken = 0;
|
|
while ((theToken = (CToken*)mMisplacedContent.Pop())) {
|
|
IF_FREE(theToken, mTokenAllocator);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP_(void)
|
|
CNavDTD::Terminate()
|
|
{
|
|
mFlags |= NS_DTD_FLAG_STOP_PARSING;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP_(PRInt32)
|
|
CNavDTD::GetType()
|
|
{
|
|
return NS_IPARSER_FLAG_HTML;
|
|
}
|
|
|
|
NS_IMETHODIMP_(nsDTDMode)
|
|
CNavDTD::GetMode() const
|
|
{
|
|
return mDTDMode;
|
|
}
|
|
|
|
/**
|
|
* Text and some tags require a body when they're added, this function returns
|
|
* true for those tags.
|
|
*
|
|
* @param aToken The current token that we care about.
|
|
* @param aTokenizer A tokenizer that we can get the tags attributes off of.
|
|
* @return PR_TRUE if aToken does indeed force the body to open.
|
|
*/
|
|
static PRBool
|
|
DoesRequireBody(CToken* aToken, nsITokenizer* aTokenizer)
|
|
{
|
|
PRBool result = PR_FALSE;
|
|
|
|
if (aToken) {
|
|
eHTMLTags theTag = (eHTMLTags)aToken->GetTypeID();
|
|
if (gHTMLElements[theTag].HasSpecialProperty(kRequiresBody)) {
|
|
if (theTag == eHTMLTag_input) {
|
|
// IE & Nav4x opens up a body for type=text - Bug 66985
|
|
// XXXbz but we don't want to open one for <input> with no
|
|
// type attribute? That's pretty whack.
|
|
PRInt32 ac = aToken->GetAttributeCount();
|
|
for(PRInt32 i = 0; i < ac; ++i) {
|
|
CAttributeToken* attr = static_cast<CAttributeToken*>
|
|
(aTokenizer->GetTokenAt(i));
|
|
const nsSubstring& name = attr->GetKey();
|
|
const nsAString& value = attr->GetValue();
|
|
// XXXbz note that this stupid case-sensitive comparison is
|
|
// actually depended on by sites...
|
|
if ((name.EqualsLiteral("type") ||
|
|
name.EqualsLiteral("TYPE"))
|
|
&&
|
|
!(value.EqualsLiteral("hidden") ||
|
|
value.EqualsLiteral("HIDDEN"))) {
|
|
result = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
result = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static PRBool
|
|
ValueIsHidden(const nsAString& aValue)
|
|
{
|
|
// Having to deal with whitespace here sucks, but we have to match
|
|
// what the content sink does.
|
|
nsAutoString str(aValue);
|
|
str.Trim("\n\r\t\b");
|
|
return str.LowerCaseEqualsLiteral("hidden");
|
|
}
|
|
|
|
// Check whether aToken corresponds to a <input type="hidden"> tag. The token
|
|
// must be a start tag token for an <input>. This must be called at a point
|
|
// when all the attributes for the input are still in the tokenizer.
|
|
static PRBool
|
|
IsHiddenInput(CToken* aToken, nsITokenizer* aTokenizer)
|
|
{
|
|
NS_PRECONDITION(eHTMLTokenTypes(aToken->GetTokenType()) == eToken_start,
|
|
"Must be start token");
|
|
NS_PRECONDITION(eHTMLTags(aToken->GetTypeID()) == eHTMLTag_input,
|
|
"Must be <input> tag");
|
|
|
|
PRInt32 ac = aToken->GetAttributeCount();
|
|
NS_ASSERTION(ac <= aTokenizer->GetCount(),
|
|
"Not enough tokens in the tokenizer");
|
|
// But we don't really trust ourselves to get that right
|
|
ac = NS_MIN(ac, aTokenizer->GetCount());
|
|
|
|
for (PRInt32 i = 0; i < ac; ++i) {
|
|
NS_ASSERTION(eHTMLTokenTypes(aTokenizer->GetTokenAt(i)->GetTokenType()) ==
|
|
eToken_attribute, "Unexpected token type");
|
|
// Again, we're not sure we actually manage to guarantee that
|
|
if (eHTMLTokenTypes(aTokenizer->GetTokenAt(i)->GetTokenType()) !=
|
|
eToken_attribute) {
|
|
break;
|
|
}
|
|
|
|
CAttributeToken* attrToken =
|
|
static_cast<CAttributeToken*>(aTokenizer->GetTokenAt(i));
|
|
if (!attrToken->GetKey().LowerCaseEqualsLiteral("type")) {
|
|
continue;
|
|
}
|
|
|
|
return ValueIsHidden(attrToken->GetValue());
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not there is a tag of type aType open on aContext.
|
|
*/
|
|
static PRBool
|
|
HasOpenTagOfType(PRInt32 aType, const nsDTDContext& aContext)
|
|
{
|
|
PRInt32 count = aContext.GetCount();
|
|
|
|
while (--count >= 0) {
|
|
if (gHTMLElements[aContext.TagAt(count)].IsMemberOf(aType)) {
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
nsresult
|
|
CNavDTD::HandleToken(CToken* aToken)
|
|
{
|
|
if (!aToken) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult result = NS_OK;
|
|
CHTMLToken* theToken = static_cast<CHTMLToken*>(aToken);
|
|
eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType());
|
|
eHTMLTags theTag = (eHTMLTags)theToken->GetTypeID();
|
|
|
|
aToken->SetLineNumber(mLineNumber);
|
|
|
|
if (mCountLines) {
|
|
mLineNumber += aToken->GetNewlineCount();
|
|
}
|
|
|
|
if (mFlags & NS_DTD_FLAG_MISPLACED_CONTENT) {
|
|
// Included TD & TH to fix Bug# 20797
|
|
static eHTMLTags gLegalElements[] = {
|
|
eHTMLTag_table, eHTMLTag_thead, eHTMLTag_tbody,
|
|
eHTMLTag_tr, eHTMLTag_td, eHTMLTag_th, eHTMLTag_tfoot
|
|
};
|
|
// Don't even try processing misplaced tokens if we're already
|
|
// handling misplaced content. See bug 269095
|
|
if (mFlags & NS_DTD_FLAG_IN_MISPLACED_CONTENT) {
|
|
PushIntoMisplacedStack(theToken);
|
|
return NS_OK;
|
|
}
|
|
|
|
eHTMLTags theParentTag = mBodyContext->Last();
|
|
if (FindTagInSet(theTag, gLegalElements,
|
|
NS_ARRAY_LENGTH(gLegalElements)) ||
|
|
(gHTMLElements[theParentTag].CanContain(theTag, mDTDMode) &&
|
|
// Here's a problem. If theTag is legal in here, we don't move it
|
|
// out. So if we're moving stuff out of here, the parent of theTag
|
|
// gets closed at this point. But some things are legal
|
|
// _everywhere_ and hence would effectively close out misplaced
|
|
// content in tables. This is undesirable, so treat them as
|
|
// illegal here so they'll be shipped out with their parents and
|
|
// siblings. See bug 40855 for an explanation (that bug was for
|
|
// comments, but the same issues arise with whitespace, newlines,
|
|
// noscript, etc). Script is special, though. Shipping it out
|
|
// breaks document.write stuff. See bug 243064.
|
|
(!gHTMLElements[theTag].HasSpecialProperty(kLegalOpen) ||
|
|
theTag == eHTMLTag_script)) ||
|
|
(theTag == eHTMLTag_input && theType == eToken_start &&
|
|
FindTagInSet(theParentTag, gLegalElements,
|
|
NS_ARRAY_LENGTH(gLegalElements)) &&
|
|
IsHiddenInput(theToken, mTokenizer))) {
|
|
// Reset the state since all the misplaced tokens are about to get
|
|
// handled.
|
|
mFlags &= ~NS_DTD_FLAG_MISPLACED_CONTENT;
|
|
|
|
result = HandleSavedTokens(mBodyContext->mContextTopIndex);
|
|
NS_ENSURE_SUCCESS(result, result);
|
|
|
|
mBodyContext->mContextTopIndex = -1;
|
|
} else {
|
|
PushIntoMisplacedStack(theToken);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This section of code is used to "move" misplaced content from one location
|
|
* in our document model to another. (Consider what would happen if we found a
|
|
* <P> tag in the head.) To move content, we throw it onto the
|
|
* misplacedcontent deque until we can deal with it.
|
|
*/
|
|
switch(theTag) {
|
|
case eHTMLTag_html:
|
|
case eHTMLTag_noframes:
|
|
case eHTMLTag_script:
|
|
case eHTMLTag_doctypeDecl:
|
|
case eHTMLTag_instruction:
|
|
break;
|
|
|
|
default:
|
|
if (!gHTMLElements[eHTMLTag_html].SectionContains(theTag, PR_FALSE)) {
|
|
if (!(mFlags & (NS_DTD_FLAG_HAS_MAIN_CONTAINER |
|
|
NS_DTD_FLAG_ALTERNATE_CONTENT))) {
|
|
// For bug examples from this code, see bugs: 18928, 20989.
|
|
// At this point we know the body/frameset aren't open.
|
|
// If the child belongs in the head, then handle it (which may open
|
|
// the head); otherwise, push it onto the misplaced stack.
|
|
|
|
PRBool isExclusive = PR_FALSE;
|
|
PRBool theChildBelongsInHead =
|
|
gHTMLElements[eHTMLTag_head].IsChildOfHead(theTag, isExclusive);
|
|
if (theChildBelongsInHead &&
|
|
!isExclusive &&
|
|
!gHTMLElements[theTag].HasSpecialProperty(kPreferHead)) {
|
|
if (mMisplacedContent.GetSize() == 0 &&
|
|
(!gHTMLElements[theTag].HasSpecialProperty(kPreferBody) ||
|
|
(mFlags & NS_DTD_FLAG_HAS_EXPLICIT_HEAD))) {
|
|
// This tag can either be in the body or the head. Since
|
|
// there is no indication that the body should be open,
|
|
// put this token in the head.
|
|
break;
|
|
}
|
|
|
|
// Otherwise, we have received some indication that the body is
|
|
// "open", so push this token onto the misplaced content stack.
|
|
theChildBelongsInHead = PR_FALSE;
|
|
}
|
|
|
|
if (!theChildBelongsInHead) {
|
|
eHTMLTags top = mBodyContext->Last();
|
|
NS_ASSERTION(top != eHTMLTag_userdefined,
|
|
"Userdefined tags should act as leaves in the head");
|
|
if (top != eHTMLTag_html && top != eHTMLTag_head &&
|
|
gHTMLElements[top].CanContain(theTag, mDTDMode)) {
|
|
// Some tags (such as <object> and <script>) are opened in the
|
|
// head and allow other non-head content to be children.
|
|
// Note: Userdefined tags in the head act like leaves.
|
|
break;
|
|
}
|
|
|
|
// If you're here then we found a child of the body that was out of
|
|
// place. We're going to move it to the body by storing it
|
|
// temporarily on the misplaced stack. However, in quirks mode, a
|
|
// few tags request, ambiguosly, for a BODY. - Bugs 18928, 24204.-
|
|
PushIntoMisplacedStack(aToken);
|
|
|
|
if (IsAlternateTag(theTag)) {
|
|
// These tags' contents are consumed as CDATA. If we simply
|
|
// pushed them on the misplaced content stack, the CDATA
|
|
// contents would force us to open a body, which could be
|
|
// wrong. So we collect the whole tag as misplaced in one
|
|
// gulp. Note that the tokenizer guarantees that there will
|
|
// be an end tag.
|
|
CToken *current = aToken;
|
|
while (current->GetTokenType() != eToken_end ||
|
|
current->GetTypeID() != theTag) {
|
|
current = static_cast<CToken *>(mTokenizer->PopToken());
|
|
NS_ASSERTION(current, "The tokenizer is not creating good "
|
|
"alternate tags");
|
|
PushIntoMisplacedStack(current);
|
|
}
|
|
|
|
// XXX Add code to also collect incorrect attributes on the
|
|
// end tag.
|
|
}
|
|
|
|
if (DoesRequireBody(aToken, mTokenizer)) {
|
|
CToken* theBodyToken =
|
|
mTokenAllocator->CreateTokenOfType(eToken_start,
|
|
eHTMLTag_body,
|
|
NS_LITERAL_STRING("body"));
|
|
result = HandleToken(theBodyToken);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (theToken) {
|
|
switch (theType) {
|
|
case eToken_text:
|
|
case eToken_start:
|
|
case eToken_whitespace:
|
|
case eToken_newline:
|
|
result = HandleStartToken(theToken);
|
|
break;
|
|
|
|
case eToken_end:
|
|
result = HandleEndToken(theToken);
|
|
break;
|
|
|
|
case eToken_cdatasection:
|
|
case eToken_comment:
|
|
case eToken_markupDecl:
|
|
result = HandleCommentToken(theToken);
|
|
break;
|
|
|
|
case eToken_entity:
|
|
result = HandleEntityToken(theToken);
|
|
break;
|
|
|
|
case eToken_attribute:
|
|
result = HandleAttributeToken(theToken);
|
|
break;
|
|
|
|
case eToken_instruction:
|
|
result = HandleProcessingInstructionToken(theToken);
|
|
break;
|
|
|
|
case eToken_doctypeDecl:
|
|
result = HandleDocTypeDeclToken(theToken);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
IF_FREE(theToken, mTokenAllocator);
|
|
if (result == NS_ERROR_HTMLPARSER_STOPPARSING) {
|
|
mFlags |= NS_DTD_FLAG_STOP_PARSING;
|
|
} else if (NS_FAILED(result) && result != NS_ERROR_HTMLPARSER_BLOCK) {
|
|
result = NS_OK;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
nsresult
|
|
CNavDTD::DidHandleStartTag(nsIParserNode& aNode, eHTMLTags aChildTag)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
switch (aChildTag) {
|
|
case eHTMLTag_pre:
|
|
case eHTMLTag_listing:
|
|
{
|
|
// Skip the 1st newline inside PRE and LISTING unless this is a
|
|
// plain text doc (for which we pushed a PRE in CNavDTD::BuildModel).
|
|
|
|
// XXX This code is incorrect in the face of misplaced <pre> and
|
|
// <listing> tags (as direct children of <table>).
|
|
CToken* theNextToken = mTokenizer->PeekToken();
|
|
if (ePlainText != mDocType && theNextToken) {
|
|
eHTMLTokenTypes theType = eHTMLTokenTypes(theNextToken->GetTokenType());
|
|
if (eToken_newline == theType) {
|
|
if (mCountLines) {
|
|
mLineNumber += theNextToken->GetNewlineCount();
|
|
}
|
|
theNextToken = mTokenizer->PopToken();
|
|
IF_FREE(theNextToken, mTokenAllocator); // fix for Bug 29379
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
PRInt32
|
|
CNavDTD::LastOf(eHTMLTags aTagSet[], PRInt32 aCount) const
|
|
{
|
|
for (PRInt32 theIndex = mBodyContext->GetCount() - 1; theIndex >= 0;
|
|
--theIndex) {
|
|
if (FindTagInSet((*mBodyContext)[theIndex], aTagSet, aCount)) {
|
|
return theIndex;
|
|
}
|
|
}
|
|
|
|
return kNotFound;
|
|
}
|
|
|
|
static PRBool
|
|
CanBeContained(eHTMLTags aChildTag, nsDTDContext& aContext)
|
|
{
|
|
/* # Interesting test cases: Result:
|
|
* 1. <UL><LI>..<B>..<LI> inner <LI> closes outer <LI>
|
|
* 2. <CENTER><DL><DT><A><CENTER> allow nested <CENTER>
|
|
* 3. <TABLE><TR><TD><TABLE>... allow nested <TABLE>
|
|
* 4. <FRAMESET> ... <FRAMESET>
|
|
*/
|
|
PRBool result = PR_TRUE;
|
|
PRInt32 theCount = aContext.GetCount();
|
|
|
|
if (0 < theCount) {
|
|
const TagList* theRootTags = gHTMLElements[aChildTag].GetRootTags();
|
|
const TagList* theSpecialParents =
|
|
gHTMLElements[aChildTag].GetSpecialParents();
|
|
|
|
if (theRootTags) {
|
|
PRInt32 theRootIndex = LastOf(aContext, *theRootTags);
|
|
PRInt32 theSPIndex = theSpecialParents
|
|
? LastOf(aContext, *theSpecialParents)
|
|
: kNotFound;
|
|
PRInt32 theChildIndex =
|
|
nsHTMLElement::GetIndexOfChildOrSynonym(aContext, aChildTag);
|
|
PRInt32 theTargetIndex = (theRootIndex > theSPIndex)
|
|
? theRootIndex
|
|
: theSPIndex;
|
|
|
|
if (theTargetIndex == theCount-1 ||
|
|
(theTargetIndex == theChildIndex &&
|
|
gHTMLElements[aChildTag].CanContainSelf())) {
|
|
result = PR_TRUE;
|
|
} else {
|
|
result = PR_FALSE;
|
|
|
|
static eHTMLTags gTableElements[] = { eHTMLTag_td, eHTMLTag_th };
|
|
|
|
PRInt32 theIndex = theCount - 1;
|
|
while (theChildIndex < theIndex) {
|
|
eHTMLTags theParentTag = aContext.TagAt(theIndex--);
|
|
if (gHTMLElements[theParentTag].IsMemberOf(kBlockEntity) ||
|
|
gHTMLElements[theParentTag].IsMemberOf(kHeading) ||
|
|
gHTMLElements[theParentTag].IsMemberOf(kPreformatted) ||
|
|
gHTMLElements[theParentTag].IsMemberOf(kFormControl) || //fixes bug 44479
|
|
gHTMLElements[theParentTag].IsMemberOf(kList)) {
|
|
if (!HasOptionalEndTag(theParentTag)) {
|
|
result = PR_TRUE;
|
|
break;
|
|
}
|
|
} else if (FindTagInSet(theParentTag, gTableElements,
|
|
NS_ARRAY_LENGTH(gTableElements))) {
|
|
// Added this to catch a case we missed; bug 57173.
|
|
result = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
enum eProcessRule { eNormalOp, eLetInlineContainBlock };
|
|
|
|
nsresult
|
|
CNavDTD::HandleDefaultStartToken(CToken* aToken, eHTMLTags aChildTag,
|
|
nsCParserNode *aNode)
|
|
{
|
|
NS_PRECONDITION(nsnull != aToken, kNullToken);
|
|
|
|
nsresult result = NS_OK;
|
|
PRBool theChildIsContainer = nsHTMLElement::IsContainer(aChildTag);
|
|
|
|
// Client of parser is spefically trying to parse a fragment that
|
|
// may lack required context. Suspend containment rules if so.
|
|
if (mParserCommand != eViewFragment) {
|
|
PRBool theChildAgrees = PR_TRUE;
|
|
PRInt32 theIndex = mBodyContext->GetCount();
|
|
PRBool theParentContains = PR_FALSE;
|
|
|
|
do {
|
|
eHTMLTags theParentTag = mBodyContext->TagAt(--theIndex);
|
|
if (theParentTag == eHTMLTag_userdefined) {
|
|
continue;
|
|
}
|
|
|
|
// Figure out whether this is a hidden input inside a
|
|
// table/tbody/thead/tfoot/tr
|
|
static eHTMLTags sTableElements[] = {
|
|
eHTMLTag_table, eHTMLTag_thead, eHTMLTag_tbody,
|
|
eHTMLTag_tr, eHTMLTag_tfoot
|
|
};
|
|
|
|
PRBool isHiddenInputInsideTableElement = PR_FALSE;
|
|
if (aChildTag == eHTMLTag_input &&
|
|
FindTagInSet(theParentTag, sTableElements,
|
|
NS_ARRAY_LENGTH(sTableElements))) {
|
|
PRInt32 attrCount = aNode->GetAttributeCount();
|
|
for (PRInt32 attrIndex = 0; attrIndex < attrCount; ++attrIndex) {
|
|
const nsAString& key = aNode->GetKeyAt(attrIndex);
|
|
if (key.LowerCaseEqualsLiteral("type")) {
|
|
isHiddenInputInsideTableElement =
|
|
ValueIsHidden(aNode->GetValueAt(attrIndex));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Precompute containment, and pass it to CanOmit()...
|
|
theParentContains =
|
|
isHiddenInputInsideTableElement || CanContain(theParentTag, aChildTag);
|
|
if (!isHiddenInputInsideTableElement &&
|
|
CanOmit(theParentTag, aChildTag, theParentContains)) {
|
|
HandleOmittedTag(aToken, aChildTag, theParentTag, aNode);
|
|
return NS_OK;
|
|
}
|
|
|
|
eProcessRule theRule = eNormalOp;
|
|
|
|
if (!theParentContains &&
|
|
(IsBlockElement(aChildTag, theParentTag) &&
|
|
IsInlineElement(theParentTag, theParentTag))) {
|
|
// Don't test for table to fix 57554.
|
|
if (eHTMLTag_li != aChildTag) {
|
|
nsCParserNode* theParentNode = mBodyContext->PeekNode();
|
|
if (theParentNode && theParentNode->mToken->IsWellFormed()) {
|
|
theRule = eLetInlineContainBlock;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (theRule) {
|
|
case eNormalOp:
|
|
theChildAgrees = PR_TRUE;
|
|
if (theParentContains) {
|
|
eHTMLTags theAncestor = gHTMLElements[aChildTag].mRequiredAncestor;
|
|
if (eHTMLTag_unknown != theAncestor) {
|
|
theChildAgrees = HasOpenContainer(theAncestor);
|
|
}
|
|
|
|
if (theChildAgrees && theChildIsContainer) {
|
|
if (theParentTag != aChildTag) {
|
|
// Double check the power structure
|
|
// Note: The bit is currently set on tags such as <A> and <LI>.
|
|
if (gHTMLElements[aChildTag].ShouldVerifyHierarchy()) {
|
|
PRInt32 theChildIndex =
|
|
nsHTMLElement::GetIndexOfChildOrSynonym(*mBodyContext,
|
|
aChildTag);
|
|
|
|
if (kNotFound < theChildIndex && theChildIndex < theIndex) {
|
|
/*
|
|
* 1 Here's a tricky case from bug 22596: <h5><li><h5>
|
|
* How do we know that the 2nd <h5> should close the <LI>
|
|
* rather than nest inside the <LI>? (Afterall, the <h5>
|
|
* is a legal child of the <LI>).
|
|
*
|
|
* The way you know is that there is no root between the
|
|
* two, so the <h5> binds more tightly to the 1st <h5>
|
|
* than to the <LI>.
|
|
*
|
|
* 2 Also, bug 6148 shows this case: <SPAN><DIV><SPAN>
|
|
* From this case we learned not to execute this logic if
|
|
* the parent is a block.
|
|
*
|
|
* 3 Fix for 26583:
|
|
* <A href=foo.html><B>foo<A href-bar.html>bar</A></B></A>
|
|
* In the above example clicking on "foo" or "bar" should
|
|
* link to foo.html or bar.html respectively. That is,
|
|
* the inner <A> should be informed about the presence of
|
|
* an open <A> above <B>..so that the inner <A> can close
|
|
* out the outer <A>. The following code does it for us.
|
|
*
|
|
* 4 Fix for 27865 [ similer to 22596 ]. Ex:
|
|
* <DL><DD><LI>one<DD><LI>two
|
|
*/
|
|
theChildAgrees = CanBeContained(aChildTag, *mBodyContext);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(theParentContains && theChildAgrees)) {
|
|
if (!CanPropagate(theParentTag, aChildTag, theParentContains)) {
|
|
if (theChildIsContainer || !theParentContains) {
|
|
if (!theChildAgrees &&
|
|
!gHTMLElements[aChildTag].CanAutoCloseTag(*mBodyContext,
|
|
theIndex,
|
|
aChildTag)) {
|
|
// Closing the tags above might cause non-compatible results.
|
|
// Ex. <TABLE><TR><TD><TBODY>Text</TD></TR></TABLE>.
|
|
// In the example above <TBODY> is badly misplaced, but
|
|
// we should not attempt to close the tags above it,
|
|
// The safest thing to do is to discard this tag.
|
|
// XXX We get the above example wrong anyway because of
|
|
// CanBeContained.
|
|
return result;
|
|
} else if (mBodyContext->mContextTopIndex > 0 &&
|
|
theIndex <= mBodyContext->mContextTopIndex) {
|
|
// Looks like the parent tag does not want to contain the
|
|
// current tag ( aChildTag ). However, we have to force the
|
|
// containment, when handling misplaced content, to avoid data
|
|
// loss. Ref. bug 138577.
|
|
theParentContains = PR_TRUE;
|
|
} else {
|
|
CloseContainersTo(theIndex, aChildTag, PR_TRUE);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
CreateContextStackFor(theParentTag, aChildTag);
|
|
theIndex = mBodyContext->GetCount();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case eLetInlineContainBlock:
|
|
// Break out of this loop and open the block.
|
|
theParentContains = theChildAgrees = PR_TRUE;
|
|
break;
|
|
|
|
default:
|
|
NS_NOTREACHED("Invalid rule detected");
|
|
}
|
|
} while (!(theParentContains && theChildAgrees));
|
|
}
|
|
|
|
if (theChildIsContainer) {
|
|
result = OpenContainer(aNode, aChildTag);
|
|
} else {
|
|
result = AddLeaf(aNode);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
nsresult
|
|
CNavDTD::WillHandleStartTag(CToken* aToken, eHTMLTags aTag,
|
|
nsIParserNode& aNode)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
PRInt32 stackDepth = mBodyContext->GetCount();
|
|
if (stackDepth >= FONTSTYLE_IGNORE_DEPTH &&
|
|
gHTMLElements[aTag].IsMemberOf(kFontStyle)) {
|
|
// Prevent bug 58917 by tossing the new kFontStyle start tag
|
|
return kHierarchyTooDeep;
|
|
}
|
|
|
|
if (stackDepth >= PHRASE_IGNORE_DEPTH &&
|
|
gHTMLElements[aTag].IsMemberOf(kPhrase)) {
|
|
// Prevent bug 58917 by tossing the new kPhrase start tag
|
|
return kHierarchyTooDeep;
|
|
}
|
|
|
|
/*
|
|
* Now a little code to deal with bug #49687 (crash when layout stack gets
|
|
* too deep) I've also opened this up to any container (not just inlines):
|
|
* re bug 55095 Improved to handle bug 55980 (infinite loop caused when
|
|
* DEPTH is exceeded and </P> is encountered by itself (<P>) is continuously
|
|
* produced.
|
|
*/
|
|
if (stackDepth > MAX_REFLOW_DEPTH) {
|
|
if (nsHTMLElement::IsContainer(aTag) &&
|
|
!gHTMLElements[aTag].HasSpecialProperty(kHandleStrayTag)) {
|
|
// Ref. bug 98261,49678,55095,55980
|
|
// Instead of throwing away the current tag close it's parent
|
|
// such that the stack level does not go beyond the max_reflow_depth.
|
|
// This would allow leaf tags, that follow the current tag, to find
|
|
// the correct node.
|
|
while (stackDepth != MAX_REFLOW_DEPTH && NS_SUCCEEDED(result)) {
|
|
result = CloseContainersTo(mBodyContext->Last(), PR_FALSE);
|
|
--stackDepth;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aTag <= NS_HTML_TAG_MAX) {
|
|
result = mSink->NotifyTagObservers(&aNode);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
PushMisplacedAttributes(nsIParserNode& aNode, nsDeque& aDeque)
|
|
{
|
|
nsCParserNode& theAttrNode = static_cast<nsCParserNode &>(aNode);
|
|
|
|
for (PRInt32 count = aNode.GetAttributeCount(); count > 0; --count) {
|
|
CToken* theAttrToken = theAttrNode.PopAttributeTokenFront();
|
|
if (theAttrToken) {
|
|
theAttrToken->SetNewlineCount(0);
|
|
aDeque.Push(theAttrToken);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CNavDTD::HandleOmittedTag(CToken* aToken, eHTMLTags aChildTag,
|
|
eHTMLTags aParent, nsIParserNode* aNode)
|
|
{
|
|
NS_PRECONDITION(mBodyContext != nsnull, "need a context to work with");
|
|
|
|
// The trick here is to see if the parent can contain the child, but prefers
|
|
// not to. Only if the parent CANNOT contain the child should we look to see
|
|
// if it's potentially a child of another section. If it is, the cache it for
|
|
// later.
|
|
PRInt32 theTagCount = mBodyContext->GetCount();
|
|
PRBool pushToken = PR_FALSE;
|
|
|
|
if (gHTMLElements[aParent].HasSpecialProperty(kBadContentWatch) &&
|
|
!nsHTMLElement::IsWhitespaceTag(aChildTag)) {
|
|
eHTMLTags theTag = eHTMLTag_unknown;
|
|
|
|
// Don't bother saving misplaced stuff in the head. This can happen in
|
|
// cases like |<head><noscript><table>foo|. See bug 401169.
|
|
if (mFlags & NS_DTD_FLAG_HAS_OPEN_HEAD) {
|
|
NS_ASSERTION(!(mFlags & NS_DTD_FLAG_HAS_MAIN_CONTAINER),
|
|
"Bad state");
|
|
return;
|
|
}
|
|
|
|
// Determine the insertion point
|
|
while (theTagCount > 0) {
|
|
theTag = mBodyContext->TagAt(--theTagCount);
|
|
if (!gHTMLElements[theTag].HasSpecialProperty(kBadContentWatch)) {
|
|
// This is our insertion point.
|
|
mBodyContext->mContextTopIndex = theTagCount;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mBodyContext->mContextTopIndex > -1) {
|
|
pushToken = PR_TRUE;
|
|
|
|
// Remember that we've stashed some misplaced content.
|
|
mFlags |= NS_DTD_FLAG_MISPLACED_CONTENT;
|
|
}
|
|
}
|
|
|
|
if (aChildTag != aParent &&
|
|
gHTMLElements[aParent].HasSpecialProperty(kSaveMisplaced)) {
|
|
NS_ASSERTION(!pushToken, "A strange element has both kBadContentWatch "
|
|
"and kSaveMisplaced");
|
|
pushToken = PR_TRUE;
|
|
}
|
|
|
|
if (pushToken) {
|
|
// Hold on to this token for later use. Ref Bug. 53695
|
|
IF_HOLD(aToken);
|
|
PushIntoMisplacedStack(aToken);
|
|
|
|
// If the token is attributed then save those attributes too.
|
|
PushMisplacedAttributes(*aNode, mMisplacedContent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method gets called when a kegen token is found.
|
|
*
|
|
* @update harishd 05/02/00
|
|
* @param aNode -- CParserNode representing keygen
|
|
* @return NS_OK if all went well; ERROR if error occurred
|
|
*/
|
|
nsresult
|
|
CNavDTD::HandleKeyGen(nsIParserNode* aNode)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
nsCOMPtr<nsIFormProcessor> theFormProcessor =
|
|
do_GetService(kFormProcessorCID, &result);
|
|
if (NS_FAILED(result)) {
|
|
return result;
|
|
}
|
|
|
|
PRInt32 theAttrCount = aNode->GetAttributeCount();
|
|
nsTArray<nsString> theContent;
|
|
nsAutoString theAttribute;
|
|
nsAutoString theFormType;
|
|
CToken* theToken = nsnull;
|
|
|
|
theFormType.AssignLiteral("select");
|
|
|
|
result = theFormProcessor->ProvideContent(theFormType, theContent,
|
|
theAttribute);
|
|
if (NS_FAILED(result)) {
|
|
return result;
|
|
}
|
|
PRInt32 theIndex = nsnull;
|
|
|
|
// Populate the tokenizer with the fabricated elements in the reverse
|
|
// order such that <SELECT> is on the top fo the tokenizer followed by
|
|
// <OPTION>s and </SELECT>.
|
|
theToken = mTokenAllocator->CreateTokenOfType(eToken_end,
|
|
eHTMLTag_select);
|
|
NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY);
|
|
mTokenizer->PushTokenFront(theToken);
|
|
|
|
for (theIndex = theContent.Length()-1; theIndex > -1; --theIndex) {
|
|
theToken = mTokenAllocator->CreateTokenOfType(eToken_text,
|
|
eHTMLTag_text,
|
|
theContent[theIndex]);
|
|
NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY);
|
|
mTokenizer->PushTokenFront(theToken);
|
|
|
|
theToken = mTokenAllocator->CreateTokenOfType(eToken_start,
|
|
eHTMLTag_option);
|
|
NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY);
|
|
mTokenizer->PushTokenFront(theToken);
|
|
}
|
|
|
|
// The attribute ( provided by the form processor ) should be a part
|
|
// of the SELECT. Placing the attribute token on the tokenizer to get
|
|
// picked up by the SELECT.
|
|
theToken = mTokenAllocator->CreateTokenOfType(eToken_attribute,
|
|
eHTMLTag_unknown,
|
|
theAttribute);
|
|
NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
((CAttributeToken*)theToken)->SetKey(NS_LITERAL_STRING("_moz-type"));
|
|
mTokenizer->PushTokenFront(theToken);
|
|
|
|
// Pop out NAME and CHALLENGE attributes ( from the keygen NODE ) and
|
|
// place it in the tokenizer such that the attribtues get sucked into
|
|
// SELECT node.
|
|
for (theIndex = theAttrCount; theIndex > 0; --theIndex) {
|
|
mTokenizer->PushTokenFront(((nsCParserNode*)aNode)->PopAttributeToken());
|
|
}
|
|
|
|
theToken = mTokenAllocator->CreateTokenOfType(eToken_start,
|
|
eHTMLTag_select);
|
|
NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
// Increment the count because of the additional attribute from the form processor.
|
|
theToken->SetAttributeCount(theAttrCount + 1);
|
|
mTokenizer->PushTokenFront(theToken);
|
|
|
|
return result;
|
|
}
|
|
|
|
PRBool
|
|
CNavDTD::IsAlternateTag(eHTMLTags aTag)
|
|
{
|
|
switch (aTag) {
|
|
case eHTMLTag_noembed:
|
|
return PR_TRUE;
|
|
|
|
case eHTMLTag_noscript:
|
|
return (mFlags & NS_IPARSER_FLAG_SCRIPT_ENABLED) != 0;
|
|
|
|
case eHTMLTag_iframe:
|
|
case eHTMLTag_noframes:
|
|
return (mFlags & NS_IPARSER_FLAG_FRAMES_ENABLED) != 0;
|
|
|
|
default:
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
CNavDTD::HandleStartToken(CToken* aToken)
|
|
{
|
|
NS_PRECONDITION(nsnull != aToken, kNullToken);
|
|
|
|
nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken, mTokenAllocator);
|
|
NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
eHTMLTags theChildTag = (eHTMLTags)aToken->GetTypeID();
|
|
PRInt16 attrCount = aToken->GetAttributeCount();
|
|
eHTMLTags theParent = mBodyContext->Last();
|
|
nsresult result = NS_OK;
|
|
|
|
if (attrCount > 0) {
|
|
result = CollectAttributes(theNode, theChildTag, attrCount);
|
|
}
|
|
|
|
if (NS_OK == result) {
|
|
result = WillHandleStartTag(aToken, theChildTag, *theNode);
|
|
if (NS_OK == result) {
|
|
PRBool isTokenHandled = PR_FALSE;
|
|
PRBool theHeadIsParent = PR_FALSE;
|
|
|
|
if (nsHTMLElement::IsSectionTag(theChildTag)) {
|
|
switch (theChildTag) {
|
|
case eHTMLTag_html:
|
|
if (mBodyContext->GetCount() > 0) {
|
|
result = OpenContainer(theNode, theChildTag);
|
|
isTokenHandled = PR_TRUE;
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_body:
|
|
if (mFlags & NS_DTD_FLAG_HAS_OPEN_BODY) {
|
|
result = OpenContainer(theNode, theChildTag);
|
|
isTokenHandled=PR_TRUE;
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_head:
|
|
mFlags |= NS_DTD_FLAG_HAS_EXPLICIT_HEAD;
|
|
|
|
if (mFlags & NS_DTD_FLAG_HAS_MAIN_CONTAINER) {
|
|
HandleOmittedTag(aToken, theChildTag, theParent, theNode);
|
|
isTokenHandled = PR_TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
PRBool isExclusive = PR_FALSE;
|
|
theHeadIsParent = nsHTMLElement::IsChildOfHead(theChildTag, isExclusive);
|
|
|
|
switch (theChildTag) {
|
|
case eHTMLTag_area:
|
|
if (!mOpenMapCount) {
|
|
isTokenHandled = PR_TRUE;
|
|
}
|
|
|
|
if (mOpenMapCount > 0 && mSink) {
|
|
result = mSink->AddLeaf(*theNode);
|
|
isTokenHandled = PR_TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case eHTMLTag_image:
|
|
aToken->SetTypeID(theChildTag = eHTMLTag_img);
|
|
break;
|
|
|
|
case eHTMLTag_keygen:
|
|
result = HandleKeyGen(theNode);
|
|
isTokenHandled = PR_TRUE;
|
|
break;
|
|
|
|
case eHTMLTag_script:
|
|
// Script isn't really exclusively in the head. However, we treat it
|
|
// as such to make sure that we don't pull scripts outside the head
|
|
// into the body.
|
|
// XXX Where does the script go in a frameset document?
|
|
isExclusive = !(mFlags & NS_DTD_FLAG_HAD_BODY);
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
|
|
if (!isTokenHandled) {
|
|
PRBool prefersBody =
|
|
gHTMLElements[theChildTag].HasSpecialProperty(kPreferBody);
|
|
|
|
// If this tag prefers to be in the head (when neither the head nor the
|
|
// body have been explicitly opened) then check that we haven't seen the
|
|
// body (true until the <body> tag has really been seen). Otherwise,
|
|
// check if the head has been explicitly opened. See bug 307122.
|
|
theHeadIsParent = theHeadIsParent &&
|
|
(isExclusive ||
|
|
(prefersBody
|
|
? (mFlags & NS_DTD_FLAG_HAS_EXPLICIT_HEAD) &&
|
|
(mFlags & NS_DTD_FLAG_HAS_OPEN_HEAD)
|
|
: !(mFlags & NS_DTD_FLAG_HAS_MAIN_CONTAINER)));
|
|
|
|
if (theHeadIsParent) {
|
|
// These tokens prefer to be in the head.
|
|
result = AddHeadContent(theNode);
|
|
} else {
|
|
result = HandleDefaultStartToken(aToken, theChildTag, theNode);
|
|
}
|
|
}
|
|
|
|
// Now do any post processing necessary on the tag...
|
|
if (NS_OK == result) {
|
|
DidHandleStartTag(*theNode, theChildTag);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (kHierarchyTooDeep == result) {
|
|
// Reset this error to ok; all that happens here is that given inline tag
|
|
// gets dropped because the stack is too deep. Don't terminate parsing.
|
|
result = NS_OK;
|
|
}
|
|
|
|
IF_FREE(theNode, &mNodeAllocator);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Call this to see if you have a closeable peer on the stack that
|
|
* is ABOVE one of its root tags.
|
|
*
|
|
* @update gess 4/11/99
|
|
* @param aRootTagList -- list of root tags for aTag
|
|
* @param aTag -- tag to test for containership
|
|
* @return PR_TRUE if given tag can contain other tags
|
|
*/
|
|
static PRBool
|
|
HasCloseablePeerAboveRoot(const TagList& aRootTagList, nsDTDContext& aContext,
|
|
eHTMLTags aTag, PRBool anEndTag)
|
|
{
|
|
PRInt32 theRootIndex = LastOf(aContext, aRootTagList);
|
|
const TagList* theCloseTags = anEndTag
|
|
? gHTMLElements[aTag].GetAutoCloseEndTags()
|
|
: gHTMLElements[aTag].GetAutoCloseStartTags();
|
|
PRInt32 theChildIndex = -1;
|
|
|
|
if (theCloseTags) {
|
|
theChildIndex=LastOf(aContext, *theCloseTags);
|
|
} else if (anEndTag || !gHTMLElements[aTag].CanContainSelf()) {
|
|
theChildIndex = aContext.LastOf(aTag);
|
|
}
|
|
|
|
// I changed this to theRootIndex<=theChildIndex so to handle this case:
|
|
// <SELECT><OPTGROUP>...</OPTGROUP>
|
|
return theRootIndex<=theChildIndex;
|
|
}
|
|
|
|
|
|
/**
|
|
* This method is called to determine whether or not an END tag
|
|
* can be autoclosed. This means that based on the current
|
|
* context, the stack should be closed to the nearest matching
|
|
* tag.
|
|
*
|
|
* @param aTag -- tag enum of child to be tested
|
|
* @return PR_TRUE if autoclosure should occur
|
|
*/
|
|
static eHTMLTags
|
|
FindAutoCloseTargetForEndTag(eHTMLTags aCurrentTag, nsDTDContext& aContext,
|
|
nsDTDMode aMode)
|
|
{
|
|
int theTopIndex = aContext.GetCount();
|
|
eHTMLTags thePrevTag = aContext.Last();
|
|
|
|
if (nsHTMLElement::IsContainer(aCurrentTag)) {
|
|
PRInt32 theChildIndex =
|
|
nsHTMLElement::GetIndexOfChildOrSynonym(aContext, aCurrentTag);
|
|
|
|
if (kNotFound < theChildIndex) {
|
|
if (thePrevTag == aContext[theChildIndex]) {
|
|
return aContext[theChildIndex];
|
|
}
|
|
|
|
if (nsHTMLElement::IsBlockCloser(aCurrentTag)) {
|
|
/*
|
|
* Here's the algorithm:
|
|
* Our here is sitting at aChildIndex. There are other tags above it
|
|
* on the stack. We have to try to close them out, but we may encounter
|
|
* one that can block us. The way to tell is by comparing each tag on
|
|
* the stack against our closeTag and rootTag list.
|
|
*
|
|
* For each tag above our hero on the stack, ask 3 questions:
|
|
* 1. Is it in the closeTag list? If so, the we can skip over it
|
|
* 2. Is it in the rootTag list? If so, then we're gated by it
|
|
* 3. Otherwise its non-specified and we simply presume we can close it.
|
|
*/
|
|
const TagList* theCloseTags =
|
|
gHTMLElements[aCurrentTag].GetAutoCloseEndTags();
|
|
const TagList* theRootTags =
|
|
gHTMLElements[aCurrentTag].GetEndRootTags();
|
|
|
|
if (theCloseTags) {
|
|
// At a mininimum, this code is needed for H1..H6
|
|
while (theChildIndex < --theTopIndex) {
|
|
eHTMLTags theNextTag = aContext[theTopIndex];
|
|
if (!FindTagInSet(theNextTag, theCloseTags->mTags,
|
|
theCloseTags->mCount) &&
|
|
FindTagInSet(theNextTag, theRootTags->mTags,
|
|
theRootTags->mCount)) {
|
|
// We encountered a tag in root list so fail (we're gated).
|
|
return eHTMLTag_unknown;
|
|
}
|
|
|
|
// Otherwise, presume it's something we can simply ignore and
|
|
// continue searching.
|
|
}
|
|
|
|
eHTMLTags theTarget = aContext.TagAt(theChildIndex);
|
|
if (aCurrentTag != theTarget) {
|
|
aCurrentTag = theTarget;
|
|
}
|
|
// If you make it here, we're ungated and found a target!
|
|
return aCurrentTag;
|
|
} else if (theRootTags) {
|
|
// Since we didn't find any close tags, see if there is an instance of
|
|
// aCurrentTag above the stack from the roottag.
|
|
if (HasCloseablePeerAboveRoot(*theRootTags, aContext, aCurrentTag,
|
|
PR_TRUE)) {
|
|
return aCurrentTag;
|
|
} else {
|
|
return eHTMLTag_unknown;
|
|
}
|
|
}
|
|
} else {
|
|
// Ok, a much more sensible approach for non-block closers; use the tag
|
|
// group to determine closure: For example: %phrasal closes %phrasal,
|
|
// %fontstyle and %special
|
|
return gHTMLElements[aCurrentTag].GetCloseTargetForEndTag(aContext,
|
|
theChildIndex,
|
|
aMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return eHTMLTag_unknown;
|
|
}
|
|
|
|
static void
|
|
StripWSFollowingTag(eHTMLTags aChildTag, nsITokenizer* aTokenizer,
|
|
nsTokenAllocator* aTokenAllocator, PRInt32* aNewlineCount)
|
|
{
|
|
if (!aTokenizer || !aTokenAllocator) {
|
|
return;
|
|
}
|
|
|
|
CToken* theToken = aTokenizer->PeekToken();
|
|
|
|
PRInt32 newlineCount = 0;
|
|
while (theToken) {
|
|
eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType());
|
|
|
|
switch(theType) {
|
|
case eToken_newline:
|
|
case eToken_whitespace:
|
|
theToken = aTokenizer->PopToken();
|
|
newlineCount += theToken->GetNewlineCount();
|
|
IF_FREE(theToken, aTokenAllocator);
|
|
|
|
theToken = aTokenizer->PeekToken();
|
|
break;
|
|
|
|
default:
|
|
theToken = nsnull;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (aNewlineCount) {
|
|
*aNewlineCount += newlineCount;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method gets called when an end token has been
|
|
* encountered in the parse process. If the end tag matches
|
|
* the start tag on the stack, then simply close it. Otherwise,
|
|
* we have a erroneous state condition. This can be because we
|
|
* have a close tag with no prior open tag (user error) or because
|
|
* we screwed something up in the parse process. I'm not sure
|
|
* yet how to tell the difference.
|
|
*
|
|
* @param aToken -- next (start) token to be handled
|
|
* @return PR_TRUE if all went well; PR_FALSE if error occurred
|
|
*/
|
|
nsresult
|
|
CNavDTD::HandleEndToken(CToken* aToken)
|
|
{
|
|
NS_PRECONDITION(nsnull != aToken, kNullToken);
|
|
|
|
nsresult result = NS_OK;
|
|
eHTMLTags theChildTag = (eHTMLTags)aToken->GetTypeID();
|
|
|
|
// Begin by dumping any attributes (bug 143512)
|
|
CollectAttributes(nsnull, theChildTag, aToken->GetAttributeCount());
|
|
|
|
switch (theChildTag) {
|
|
case eHTMLTag_link:
|
|
case eHTMLTag_meta:
|
|
break;
|
|
|
|
case eHTMLTag_head:
|
|
StripWSFollowingTag(theChildTag, mTokenizer, mTokenAllocator,
|
|
!mCountLines ? nsnull : &mLineNumber);
|
|
if (mBodyContext->LastOf(eHTMLTag_head) != kNotFound) {
|
|
result = CloseContainersTo(eHTMLTag_head, PR_FALSE);
|
|
}
|
|
mFlags &= ~NS_DTD_FLAG_HAS_EXPLICIT_HEAD;
|
|
break;
|
|
|
|
case eHTMLTag_form:
|
|
result = CloseContainer(eHTMLTag_form, PR_FALSE);
|
|
break;
|
|
|
|
case eHTMLTag_br:
|
|
{
|
|
// This is special NAV-QUIRKS code that allows users to use </BR>, even
|
|
// though that isn't a legitimate tag.
|
|
if (eDTDMode_quirks == mDTDMode) {
|
|
// Use recycler and pass the token thro' HandleToken() to fix bugs
|
|
// like 32782.
|
|
CToken* theToken = mTokenAllocator->CreateTokenOfType(eToken_start,
|
|
theChildTag);
|
|
result = HandleToken(theToken);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_body:
|
|
case eHTMLTag_html:
|
|
StripWSFollowingTag(theChildTag, mTokenizer, mTokenAllocator,
|
|
!mCountLines ? nsnull : &mLineNumber);
|
|
break;
|
|
|
|
case eHTMLTag_script:
|
|
// Note: we don't fall through to the default case because
|
|
// CloseContainersTo() has the bad habit of closing tags that are opened
|
|
// by document.write(). Fortunately, the tokenizer guarantees that no
|
|
// actual tags appear between <script> and </script> so we won't be
|
|
// closing the wrong tag.
|
|
if (mBodyContext->Last() != eHTMLTag_script) {
|
|
// Except if we're here, then there's probably a stray script tag.
|
|
NS_ASSERTION(mBodyContext->LastOf(eHTMLTag_script) == kNotFound,
|
|
"Mishandling scripts in CNavDTD");
|
|
break;
|
|
}
|
|
|
|
mBodyContext->Pop();
|
|
result = CloseContainer(eHTMLTag_script, aToken->IsInError());
|
|
break;
|
|
|
|
default:
|
|
{
|
|
// Now check to see if this token should be omitted, or
|
|
// if it's gated from closing by the presence of another tag.
|
|
if (gHTMLElements[theChildTag].CanOmitEndTag()) {
|
|
PopStyle(theChildTag);
|
|
} else {
|
|
eHTMLTags theParentTag = mBodyContext->Last();
|
|
|
|
// First open transient styles, so that we see any autoclosed style
|
|
// tags.
|
|
if (nsHTMLElement::IsResidualStyleTag(theChildTag)) {
|
|
result = OpenTransientStyles(theChildTag);
|
|
if (NS_FAILED(result)) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (kNotFound ==
|
|
nsHTMLElement::GetIndexOfChildOrSynonym(*mBodyContext,
|
|
theChildTag)) {
|
|
// Ref: bug 30487
|
|
// Make sure that we don't cross boundaries, of certain elements,
|
|
// to close stylistic information.
|
|
// <font face="helvetica"><table><tr><td></font></td></tr></table> some text...
|
|
// In the above ex. the orphaned FONT tag, inside TD, should cross
|
|
// TD boundary to close the FONT tag above TABLE.
|
|
static eHTMLTags gBarriers[] = {
|
|
eHTMLTag_thead, eHTMLTag_tbody, eHTMLTag_tfoot, eHTMLTag_table
|
|
};
|
|
|
|
if (!FindTagInSet(theParentTag, gBarriers,
|
|
NS_ARRAY_LENGTH(gBarriers)) &&
|
|
nsHTMLElement::IsResidualStyleTag(theChildTag)) {
|
|
// Fix bug 77746
|
|
mBodyContext->RemoveStyle(theChildTag);
|
|
}
|
|
|
|
// If the bit kHandleStrayTag is set then we automatically open up a
|
|
// matching start tag (compatibility). Currently this bit is set on
|
|
// P tag. This also fixes Bug: 22623
|
|
if (gHTMLElements[theChildTag].HasSpecialProperty(kHandleStrayTag) &&
|
|
mDTDMode != eDTDMode_full_standards &&
|
|
mDTDMode != eDTDMode_almost_standards) {
|
|
// Oh boy!! we found a "stray" tag. Nav4.x and IE introduce line
|
|
// break in such cases. So, let's simulate that effect for
|
|
// compatibility.
|
|
// Ex. <html><body>Hello</P>There</body></html>
|
|
PRInt32 theParentContains = -1;
|
|
if (!CanOmit(theParentTag, theChildTag, theParentContains)) {
|
|
CToken* theStartToken =
|
|
mTokenAllocator->CreateTokenOfType(eToken_start, theChildTag);
|
|
NS_ENSURE_TRUE(theStartToken, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
// This check for NS_DTD_FLAG_IN_MISPLACED_CONTENT was added
|
|
// to fix bug 142965.
|
|
if (!(mFlags & NS_DTD_FLAG_IN_MISPLACED_CONTENT)) {
|
|
// We're not handling misplaced content right now, just push
|
|
// these new tokens back on the stack and handle them in the
|
|
// regular flow of HandleToken.
|
|
IF_HOLD(aToken);
|
|
mTokenizer->PushTokenFront(aToken);
|
|
mTokenizer->PushTokenFront(theStartToken);
|
|
} else {
|
|
// Oops, we're in misplaced content. Handle these tokens
|
|
// directly instead of trying to push them onto the tokenizer
|
|
// stack.
|
|
result = HandleToken(theStartToken);
|
|
NS_ENSURE_SUCCESS(result, result);
|
|
|
|
IF_HOLD(aToken);
|
|
result = HandleToken(aToken);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
if (result == NS_OK) {
|
|
eHTMLTags theTarget =
|
|
FindAutoCloseTargetForEndTag(theChildTag, *mBodyContext,
|
|
mDTDMode);
|
|
if (eHTMLTag_unknown != theTarget) {
|
|
result = CloseContainersTo(theTarget, PR_FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method will be triggered when the end of a table is
|
|
* encountered. Its primary purpose is to process all the
|
|
* bad-contents pertaining a particular table. The position
|
|
* of the table is the token bank ID.
|
|
*
|
|
* @update harishd 03/24/99
|
|
* @param aTag - This ought to be a table tag
|
|
*
|
|
*/
|
|
nsresult
|
|
CNavDTD::HandleSavedTokens(PRInt32 anIndex)
|
|
{
|
|
NS_PRECONDITION(mBodyContext != nsnull && mBodyContext->GetCount() > 0, "invalid context");
|
|
|
|
nsresult result = NS_OK;
|
|
|
|
if (mSink && (anIndex > kNotFound)) {
|
|
PRInt32 theBadTokenCount = mMisplacedContent.GetSize();
|
|
|
|
if (theBadTokenCount > 0) {
|
|
mFlags |= NS_DTD_FLAG_IN_MISPLACED_CONTENT;
|
|
|
|
if (!mTempContext && !(mTempContext = new nsDTDContext())) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
CToken* theToken;
|
|
eHTMLTags theTag;
|
|
PRInt32 attrCount;
|
|
PRInt32 theTopIndex = anIndex + 1;
|
|
PRInt32 theTagCount = mBodyContext->GetCount();
|
|
PRBool formWasOnStack = mSink->IsFormOnStack();
|
|
|
|
if (formWasOnStack) {
|
|
// Do this to synchronize dtd stack and the sink stack.
|
|
// Note: FORM is never on the dtd stack because its always
|
|
// considered as a leaf. However, in the sink FORM can either
|
|
// be a container or a leaf. Therefore, we have to check
|
|
// with the sink -- Ref: Bug 20087.
|
|
++anIndex;
|
|
}
|
|
|
|
// Pause the main context and switch to the new context.
|
|
result = mSink->BeginContext(anIndex);
|
|
|
|
NS_ENSURE_SUCCESS(result, result);
|
|
|
|
// The body context should contain contents only upto the marked position.
|
|
mBodyContext->MoveEntries(*mTempContext, theTagCount - theTopIndex);
|
|
|
|
// Now flush out all the bad contents.
|
|
while (theBadTokenCount-- > 0){
|
|
theToken = (CToken*)mMisplacedContent.PopFront();
|
|
if (theToken) {
|
|
theTag = (eHTMLTags)theToken->GetTypeID();
|
|
attrCount = theToken->GetAttributeCount();
|
|
// Put back attributes, which once got popped out, into the
|
|
// tokenizer. Make sure we preserve their ordering, however!
|
|
// XXXbz would it be faster to get the tokens out with ObjectAt and
|
|
// put them in the tokenizer and then PopFront them all from
|
|
// mMisplacedContent?
|
|
nsDeque temp;
|
|
for (PRInt32 j = 0; j < attrCount; ++j) {
|
|
CToken* theAttrToken = (CToken*)mMisplacedContent.PopFront();
|
|
if (theAttrToken) {
|
|
temp.Push(theAttrToken);
|
|
}
|
|
theBadTokenCount--;
|
|
}
|
|
mTokenizer->PrependTokens(temp);
|
|
|
|
if (eToken_end == theToken->GetTokenType()) {
|
|
// Ref: Bug 25202
|
|
// Make sure that the BeginContext() is ended only by the call to
|
|
// EndContext(). Ex: <center><table><a></center>.
|
|
// In the Ex. above </center> should not close <center> above table.
|
|
// Doing so will cause the current context to get closed prematurely.
|
|
eHTMLTags closed = FindAutoCloseTargetForEndTag(theTag, *mBodyContext,
|
|
mDTDMode);
|
|
PRInt32 theIndex = closed != eHTMLTag_unknown
|
|
? mBodyContext->LastOf(closed)
|
|
: kNotFound;
|
|
|
|
if (theIndex != kNotFound &&
|
|
theIndex <= mBodyContext->mContextTopIndex) {
|
|
IF_FREE(theToken, mTokenAllocator);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// XXX This should go away, with this call, it becomes extremely
|
|
// difficult to handle misplaced style and link tags, since it's hard
|
|
// to propagate the block return all the way up and then re-enter this
|
|
// method.
|
|
result = HandleToken(theToken);
|
|
}
|
|
}
|
|
|
|
if (theTopIndex != mBodyContext->GetCount()) {
|
|
// CloseContainersTo does not close any forms we might have opened while
|
|
// handling saved tokens, because the parser does not track forms on its
|
|
// mBodyContext stack.
|
|
CloseContainersTo(theTopIndex, mBodyContext->TagAt(theTopIndex),
|
|
PR_TRUE);
|
|
}
|
|
|
|
if (!formWasOnStack && mSink->IsFormOnStack()) {
|
|
// If a form has appeared on the sink context stack since the beginning of
|
|
// HandleSavedTokens, have the sink close it:
|
|
mSink->CloseContainer(eHTMLTag_form);
|
|
}
|
|
|
|
// Bad-contents were successfully processed. Now, itz time to get
|
|
// back to the original body context state.
|
|
mTempContext->MoveEntries(*mBodyContext, theTagCount - theTopIndex);
|
|
|
|
// Terminate the new context and switch back to the main context
|
|
mSink->EndContext(anIndex);
|
|
|
|
mFlags &= ~NS_DTD_FLAG_IN_MISPLACED_CONTENT;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* This method gets called when an entity token has been
|
|
* encountered in the parse process.
|
|
*
|
|
* @update gess 3/25/98
|
|
* @param aToken -- next (start) token to be handled
|
|
* @return PR_TRUE if all went well; PR_FALSE if error occurred
|
|
*/
|
|
nsresult
|
|
CNavDTD::HandleEntityToken(CToken* aToken)
|
|
{
|
|
NS_PRECONDITION(nsnull != aToken, kNullToken);
|
|
|
|
nsresult result = NS_OK;
|
|
|
|
const nsSubstring& theStr = aToken->GetStringValue();
|
|
|
|
if (kHashsign != theStr.First() &&
|
|
-1 == nsHTMLEntities::EntityToUnicode(theStr)) {
|
|
// If you're here we have a bogus entity.
|
|
// Convert it into a text token.
|
|
CToken *theToken;
|
|
|
|
nsAutoString entityName;
|
|
entityName.AssignLiteral("&");
|
|
entityName.Append(theStr);
|
|
theToken = mTokenAllocator->CreateTokenOfType(eToken_text, eHTMLTag_text,
|
|
entityName);
|
|
NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
// theToken should get recycled automagically...
|
|
return HandleToken(theToken);
|
|
}
|
|
|
|
eHTMLTags theParentTag = mBodyContext->Last();
|
|
nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken, mTokenAllocator);
|
|
NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
PRInt32 theParentContains = -1;
|
|
if (CanOmit(theParentTag, eHTMLTag_entity, theParentContains)) {
|
|
eHTMLTags theCurrTag = (eHTMLTags)aToken->GetTypeID();
|
|
HandleOmittedTag(aToken, theCurrTag, theParentTag, theNode);
|
|
} else {
|
|
result = AddLeaf(theNode);
|
|
}
|
|
IF_FREE(theNode, &mNodeAllocator);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method gets called when a comment token has been
|
|
* encountered in the parse process. After making sure
|
|
* we're somewhere in the body, we handle the comment
|
|
* in the same code that we use for text.
|
|
*
|
|
* @update gess 3/25/98
|
|
* @param aToken -- next (start) token to be handled
|
|
* @return PR_TRUE if all went well; PR_FALSE if error occurred
|
|
*/
|
|
nsresult
|
|
CNavDTD::HandleCommentToken(CToken* aToken)
|
|
{
|
|
NS_PRECONDITION(nsnull != aToken, kNullToken);
|
|
|
|
nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken, mTokenAllocator);
|
|
NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
nsresult result = mSink ? mSink->AddComment(*theNode) : NS_OK;
|
|
|
|
IF_FREE(theNode, &mNodeAllocator);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* This method gets called when an attribute token has been
|
|
* encountered in the parse process. This is an error, since
|
|
* all attributes should have been accounted for in the prior
|
|
* start or end tokens
|
|
*
|
|
* @update gess 3/25/98
|
|
* @param aToken -- next (start) token to be handled
|
|
* @return PR_TRUE if all went well; PR_FALSE if error occurred
|
|
*/
|
|
nsresult
|
|
CNavDTD::HandleAttributeToken(CToken* aToken)
|
|
{
|
|
NS_ERROR("attribute encountered -- this shouldn't happen.");
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* This method gets called when an "instruction" token has been
|
|
* encountered in the parse process.
|
|
*
|
|
* @update gess 3/25/98
|
|
* @param aToken -- next (start) token to be handled
|
|
* @return PR_TRUE if all went well; PR_FALSE if error occurred
|
|
*/
|
|
nsresult
|
|
CNavDTD::HandleProcessingInstructionToken(CToken* aToken)
|
|
{
|
|
NS_PRECONDITION(nsnull != aToken, kNullToken);
|
|
|
|
nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken, mTokenAllocator);
|
|
NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
nsresult result = mSink ? mSink->AddProcessingInstruction(*theNode) : NS_OK;
|
|
|
|
IF_FREE(theNode, &mNodeAllocator);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method gets called when a DOCTYPE token has been
|
|
* encountered in the parse process.
|
|
*
|
|
* @update harishd 09/02/99
|
|
* @param aToken -- The very first token to be handled
|
|
* @return PR_TRUE if all went well; PR_FALSE if error occurred
|
|
*/
|
|
nsresult
|
|
CNavDTD::HandleDocTypeDeclToken(CToken* aToken)
|
|
{
|
|
NS_PRECONDITION(nsnull != aToken, kNullToken);
|
|
|
|
CDoctypeDeclToken* theToken = static_cast<CDoctypeDeclToken*>(aToken);
|
|
nsAutoString docTypeStr(theToken->GetStringValue());
|
|
// XXX Doesn't this count the newlines twice?
|
|
if (mCountLines) {
|
|
mLineNumber += docTypeStr.CountChar(kNewLine);
|
|
}
|
|
|
|
PRInt32 len = docTypeStr.Length();
|
|
PRInt32 pos = docTypeStr.RFindChar(kGreaterThan);
|
|
if (pos != kNotFound) {
|
|
// First remove '>' from the end.
|
|
docTypeStr.Cut(pos, len - pos);
|
|
}
|
|
|
|
// Now remove "<!" from the begining
|
|
docTypeStr.Cut(0, 2);
|
|
theToken->SetStringValue(docTypeStr);
|
|
|
|
nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken, mTokenAllocator);
|
|
NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
nsresult result = mSink ? mSink->AddDocTypeDecl(*theNode) : NS_OK;
|
|
|
|
IF_FREE(theNode, &mNodeAllocator);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the attributes for this node, and add then into
|
|
* the node.
|
|
*
|
|
* @update gess4/22/98
|
|
* @param aNode is the node you want to collect attributes for
|
|
* @param aCount is the # of attributes you're expecting
|
|
* @return error code (should be 0)
|
|
*/
|
|
nsresult
|
|
CNavDTD::CollectAttributes(nsIParserNode *aNode, eHTMLTags aTag, PRInt32 aCount)
|
|
{
|
|
int attr = 0;
|
|
nsresult result = NS_OK;
|
|
int theAvailTokenCount = mTokenizer->GetCount();
|
|
|
|
if (aCount <= theAvailTokenCount) {
|
|
CToken* theToken;
|
|
for (attr = 0; attr < aCount; ++attr) {
|
|
theToken = mTokenizer->PopToken();
|
|
if (theToken) {
|
|
eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType());
|
|
if (theType != eToken_attribute) {
|
|
// If you're here then it means that the token does not
|
|
// belong to this node. Put the token back into the tokenizer
|
|
// and let it go thro' the regular path. Bug: 59189.
|
|
mTokenizer->PushTokenFront(theToken);
|
|
break;
|
|
}
|
|
|
|
if (mCountLines) {
|
|
mLineNumber += theToken->GetNewlineCount();
|
|
}
|
|
|
|
if (aNode) {
|
|
// If the key is empty, the attribute is unusable, so we should not
|
|
// add it to the node.
|
|
if (!((CAttributeToken*)theToken)->GetKey().IsEmpty()) {
|
|
aNode->AddAttribute(theToken);
|
|
} else {
|
|
IF_FREE(theToken, mTokenAllocator);
|
|
}
|
|
} else {
|
|
IF_FREE(theToken, mTokenAllocator);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
result = kEOF;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method is called to determine whether or not a tag
|
|
* of one type can contain a tag of another type.
|
|
*
|
|
* @update gess 4/8/98
|
|
* @param aParent -- tag enum of parent container
|
|
* @param aChild -- tag enum of child container
|
|
* @return PR_TRUE if parent can contain child
|
|
*/
|
|
NS_IMETHODIMP_(PRBool)
|
|
CNavDTD::CanContain(PRInt32 aParent, PRInt32 aChild) const
|
|
{
|
|
PRBool result = gHTMLElements[aParent].CanContain((eHTMLTags)aChild, mDTDMode);
|
|
|
|
if (eHTMLTag_nobr == aChild &&
|
|
IsInlineElement(aParent, aParent) &&
|
|
HasOpenContainer(eHTMLTag_nobr)) {
|
|
return PR_FALSE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method is called to determine whether or not
|
|
* the given childtag is a block element.
|
|
*
|
|
* @update gess 6June2000
|
|
* @param aChildID -- tag id of child
|
|
* @param aParentID -- tag id of parent (or eHTMLTag_unknown)
|
|
* @return PR_TRUE if this tag is a block tag
|
|
*/
|
|
PRBool
|
|
CNavDTD::IsBlockElement(PRInt32 aTagID, PRInt32 aParentID) const
|
|
{
|
|
eHTMLTags theTag = (eHTMLTags)aTagID;
|
|
|
|
return (theTag > eHTMLTag_unknown && theTag < eHTMLTag_userdefined) &&
|
|
(gHTMLElements[theTag].IsMemberOf(kBlock) ||
|
|
gHTMLElements[theTag].IsMemberOf(kBlockEntity) ||
|
|
gHTMLElements[theTag].IsMemberOf(kHeading) ||
|
|
gHTMLElements[theTag].IsMemberOf(kPreformatted) ||
|
|
gHTMLElements[theTag].IsMemberOf(kList));
|
|
}
|
|
|
|
/**
|
|
* This method is called to determine whether or not
|
|
* the given childtag is an inline element.
|
|
*
|
|
* @update gess 6June2000
|
|
* @param aChildID -- tag id of child
|
|
* @param aParentID -- tag id of parent (or eHTMLTag_unknown)
|
|
* @return PR_TRUE if this tag is an inline tag
|
|
*/
|
|
PRBool
|
|
CNavDTD::IsInlineElement(PRInt32 aTagID, PRInt32 aParentID) const
|
|
{
|
|
eHTMLTags theTag = (eHTMLTags)aTagID;
|
|
|
|
return (theTag > eHTMLTag_unknown && theTag < eHTMLTag_userdefined) &&
|
|
(gHTMLElements[theTag].IsMemberOf(kInlineEntity) ||
|
|
gHTMLElements[theTag].IsMemberOf(kFontStyle) ||
|
|
gHTMLElements[theTag].IsMemberOf(kPhrase) ||
|
|
gHTMLElements[theTag].IsMemberOf(kSpecial) ||
|
|
gHTMLElements[theTag].IsMemberOf(kFormControl));
|
|
}
|
|
|
|
/**
|
|
* This method is called to determine whether or not
|
|
* the necessary intermediate tags should be propagated
|
|
* between the given parent and given child.
|
|
*
|
|
* @update gess 4/8/98
|
|
* @param aParent -- tag enum of parent container
|
|
* @param aChild -- tag enum of child container
|
|
* @return PR_TRUE if propagation should occur
|
|
*/
|
|
PRBool
|
|
CNavDTD::CanPropagate(eHTMLTags aParent, eHTMLTags aChild,
|
|
PRInt32 aParentContains)
|
|
{
|
|
PRBool result = PR_FALSE;
|
|
if (aParentContains == -1) {
|
|
aParentContains = CanContain(aParent, aChild);
|
|
}
|
|
|
|
if (aParent == aChild) {
|
|
return result;
|
|
}
|
|
|
|
if (nsHTMLElement::IsContainer(aChild)) {
|
|
mScratch.Truncate();
|
|
if (!gHTMLElements[aChild].HasSpecialProperty(kNoPropagate)) {
|
|
if (nsHTMLElement::IsBlockParent(aParent) ||
|
|
gHTMLElements[aParent].GetSpecialChildren()) {
|
|
result = ForwardPropagate(mScratch, aParent, aChild);
|
|
if (!result) {
|
|
if (eHTMLTag_unknown != aParent) {
|
|
result = BackwardPropagate(mScratch, aParent, aChild);
|
|
} else {
|
|
result = BackwardPropagate(mScratch, eHTMLTag_html, aChild);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (mScratch.Length() - 1 > gHTMLElements[aParent].mPropagateRange) {
|
|
result = PR_FALSE;
|
|
}
|
|
} else {
|
|
result = !!aParentContains;
|
|
}
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* This method gets called to determine whether a given
|
|
* tag can be omitted from opening. Most cannot.
|
|
*
|
|
* @param aParent
|
|
* @param aChild
|
|
* @param aParentContains
|
|
* @return PR_TRUE if given tag can contain other tags
|
|
*/
|
|
PRBool
|
|
CNavDTD::CanOmit(eHTMLTags aParent, eHTMLTags aChild, PRInt32& aParentContains)
|
|
{
|
|
eHTMLTags theAncestor = gHTMLElements[aChild].mExcludingAncestor;
|
|
if (eHTMLTag_unknown != theAncestor && HasOpenContainer(theAncestor)) {
|
|
return PR_TRUE;
|
|
}
|
|
|
|
theAncestor = gHTMLElements[aChild].mRequiredAncestor;
|
|
if (eHTMLTag_unknown != theAncestor) {
|
|
// If there's a required ancestor, we only omit if it isn't open and we
|
|
// can't get to it through propagation.
|
|
return !HasOpenContainer(theAncestor) &&
|
|
!CanPropagate(aParent, aChild, aParentContains);
|
|
}
|
|
|
|
if (gHTMLElements[aParent].CanExclude(aChild)) {
|
|
return PR_TRUE;
|
|
}
|
|
|
|
// Now the obvious test: if the parent can contain the child, don't omit.
|
|
if (-1 == aParentContains) {
|
|
aParentContains = CanContain(aParent, aChild);
|
|
}
|
|
|
|
if (aParentContains || aChild == aParent) {
|
|
return PR_FALSE;
|
|
}
|
|
|
|
if (gHTMLElements[aParent].IsBlockEntity() &&
|
|
nsHTMLElement::IsInlineEntity(aChild)) {
|
|
// Feel free to drop inlines that a block doesn't contain.
|
|
return PR_TRUE;
|
|
}
|
|
|
|
if (gHTMLElements[aParent].HasSpecialProperty(kBadContentWatch)) {
|
|
// We can only omit this child if it does not have the kBadContentWatch
|
|
// special property.
|
|
return !gHTMLElements[aChild].HasSpecialProperty(kBadContentWatch);
|
|
}
|
|
|
|
if (gHTMLElements[aParent].HasSpecialProperty(kSaveMisplaced)) {
|
|
return PR_TRUE;
|
|
}
|
|
|
|
if (aParent == eHTMLTag_body) {
|
|
// There are very few tags that the body does not contain. If we get here
|
|
// the best thing to do is just drop them.
|
|
return PR_TRUE;
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
/**
|
|
* This method gets called to determine whether a given
|
|
* tag is itself a container
|
|
*
|
|
* @update gess 4/8/98
|
|
* @param aTag -- tag to test as a container
|
|
* @return PR_TRUE if given tag can contain other tags
|
|
*/
|
|
NS_IMETHODIMP_(PRBool)
|
|
CNavDTD::IsContainer(PRInt32 aTag) const
|
|
{
|
|
return nsHTMLElement::IsContainer((eHTMLTags)aTag);
|
|
}
|
|
|
|
|
|
PRBool
|
|
CNavDTD::ForwardPropagate(nsString& aSequence, eHTMLTags aParent,
|
|
eHTMLTags aChild)
|
|
{
|
|
PRBool result = PR_FALSE;
|
|
|
|
switch(aParent) {
|
|
case eHTMLTag_table:
|
|
if (eHTMLTag_tr == aChild || eHTMLTag_td == aChild) {
|
|
return BackwardPropagate(aSequence, aParent, aChild);
|
|
}
|
|
// Otherwise, intentionally fall through...
|
|
|
|
case eHTMLTag_tr:
|
|
if (CanContain(eHTMLTag_td, aChild)) {
|
|
aSequence.Append((PRUnichar)eHTMLTag_td);
|
|
result = BackwardPropagate(aSequence, aParent, eHTMLTag_td);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
PRBool
|
|
CNavDTD::BackwardPropagate(nsString& aSequence, eHTMLTags aParent,
|
|
eHTMLTags aChild) const
|
|
{
|
|
eHTMLTags theParent = aParent;
|
|
|
|
do {
|
|
const TagList* theRootTags = gHTMLElements[aChild].GetRootTags();
|
|
if (!theRootTags) {
|
|
break;
|
|
}
|
|
|
|
theParent = theRootTags->mTags[0];
|
|
NS_ASSERTION(CanContain(theParent, aChild),
|
|
"Children must be contained by their root tags");
|
|
|
|
aChild = theParent;
|
|
aSequence.Append((PRUnichar)theParent);
|
|
} while (theParent != eHTMLTag_unknown && theParent != aParent);
|
|
|
|
return aParent == theParent;
|
|
}
|
|
|
|
PRBool CNavDTD::HasOpenContainer(eHTMLTags aContainer) const
|
|
{
|
|
switch (aContainer) {
|
|
case eHTMLTag_form:
|
|
return !(~mFlags & NS_DTD_FLAG_HAS_OPEN_FORM);
|
|
case eHTMLTag_map:
|
|
return mOpenMapCount > 0;
|
|
default:
|
|
return mBodyContext->HasOpenContainer(aContainer);
|
|
}
|
|
}
|
|
|
|
PRBool
|
|
CNavDTD::HasOpenContainer(const eHTMLTags aTagSet[], PRInt32 aCount) const
|
|
{
|
|
int theIndex;
|
|
int theTopIndex = mBodyContext->GetCount() - 1;
|
|
|
|
for (theIndex = theTopIndex; theIndex > 0; --theIndex) {
|
|
if (FindTagInSet((*mBodyContext)[theIndex], aTagSet, aCount)) {
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
eHTMLTags
|
|
CNavDTD::GetTopNode() const
|
|
{
|
|
return mBodyContext->Last();
|
|
}
|
|
|
|
/**
|
|
* It is with great trepidation that I offer this method (privately of course).
|
|
* The gets called whenever a container gets opened. This methods job is to
|
|
* take a look at the (transient) style stack, and open any style containers that
|
|
* are there. Of course, we shouldn't bother to open styles that are incompatible
|
|
* with our parent container.
|
|
*
|
|
* @update gess6/4/98
|
|
* @param tag of the container just opened
|
|
* @return 0 (for now)
|
|
*/
|
|
nsresult
|
|
CNavDTD::OpenTransientStyles(eHTMLTags aChildTag, PRBool aCloseInvalid)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
// No need to open transient styles in head context - Fix for 41427
|
|
if ((mFlags & NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE) &&
|
|
eHTMLTag_newline != aChildTag &&
|
|
!(mFlags & NS_DTD_FLAG_HAS_OPEN_HEAD)) {
|
|
if (CanContain(eHTMLTag_font, aChildTag)) {
|
|
PRUint32 theCount = mBodyContext->GetCount();
|
|
PRUint32 theLevel = theCount;
|
|
|
|
// This first loop is used to determine how far up the containment
|
|
// hierarchy we go looking for residual styles.
|
|
while (1 < theLevel) {
|
|
eHTMLTags theParentTag = mBodyContext->TagAt(--theLevel);
|
|
if (gHTMLElements[theParentTag].HasSpecialProperty(kNoStyleLeaksIn)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
mFlags &= ~NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE;
|
|
for (; theLevel < theCount; ++theLevel) {
|
|
nsEntryStack* theStack = mBodyContext->GetStylesAt(theLevel);
|
|
if (theStack) {
|
|
// Don't open transient styles if it makes the stack deep, bug 58917.
|
|
if (theCount + theStack->mCount >= FONTSTYLE_IGNORE_DEPTH) {
|
|
break;
|
|
}
|
|
|
|
PRInt32 sindex = 0;
|
|
|
|
nsTagEntry *theEntry = theStack->mEntries;
|
|
PRBool isHeadingOpen = HasOpenTagOfType(kHeading, *mBodyContext);
|
|
for (sindex = 0; sindex < theStack->mCount; ++sindex) {
|
|
nsCParserNode* theNode = (nsCParserNode*)theEntry->mNode;
|
|
if (1 == theNode->mUseCount) {
|
|
eHTMLTags theNodeTag = (eHTMLTags)theNode->GetNodeType();
|
|
if (gHTMLElements[theNodeTag].CanContain(aChildTag, mDTDMode)) {
|
|
// XXX The following comment is entirely incoherent.
|
|
// We do this too, because this entry differs from the new one
|
|
// we're pushing.
|
|
theEntry->mParent = theStack;
|
|
if (isHeadingOpen) {
|
|
// Bug 77352
|
|
// The style system needs to identify residual style tags
|
|
// within heading tags so that heading tags' size can take
|
|
// precedence over the residual style tags' size info..
|
|
// *Note: Make sure that this attribute is transient since it
|
|
// should not get carried over to cases other than heading.
|
|
CAttributeToken theAttrToken(NS_LITERAL_STRING("_moz-rs-heading"),
|
|
EmptyString());
|
|
theNode->AddAttribute(&theAttrToken);
|
|
result = OpenContainer(theNode, theNodeTag, theStack);
|
|
theNode->PopAttributeToken();
|
|
} else {
|
|
result = OpenContainer(theNode, theNodeTag, theStack);
|
|
}
|
|
} else if (aCloseInvalid) {
|
|
// If the node tag can't contain the child tag, then remove the
|
|
// child tag from the style stack
|
|
nsCParserNode* node = theStack->Remove(sindex, theNodeTag);
|
|
IF_FREE(node, &mNodeAllocator);
|
|
--theEntry;
|
|
}
|
|
}
|
|
++theEntry;
|
|
}
|
|
}
|
|
}
|
|
mFlags |= NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method gets called when an explicit style close-tag is encountered.
|
|
* It results in the style tag id being popped from our internal style stack.
|
|
*
|
|
* @update gess6/4/98
|
|
* @param
|
|
* @return 0 if all went well (which it always does)
|
|
*/
|
|
void
|
|
CNavDTD::PopStyle(eHTMLTags aTag)
|
|
{
|
|
if ((mFlags & NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE) &&
|
|
nsHTMLElement::IsResidualStyleTag(aTag)) {
|
|
nsCParserNode* node = mBodyContext->PopStyle(aTag);
|
|
IF_FREE(node, &mNodeAllocator);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This method does two things: 1st, help construct
|
|
* our own internal model of the content-stack; and
|
|
* 2nd, pass this message on to the sink.
|
|
*
|
|
* @update gess4/22/98
|
|
* @param aNode -- next node to be added to model
|
|
*/
|
|
nsresult
|
|
CNavDTD::OpenHTML(const nsCParserNode *aNode)
|
|
{
|
|
NS_PRECONDITION(mBodyContext->GetCount() >= 0, kInvalidTagStackPos);
|
|
|
|
nsresult result = mSink ? mSink->OpenContainer(*aNode) : NS_OK;
|
|
|
|
// Don't push more than one HTML tag into the stack.
|
|
if (mBodyContext->GetCount() == 0) {
|
|
mBodyContext->Push(const_cast<nsCParserNode*>(aNode), 0, PR_FALSE);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method does two things: 1st, help construct
|
|
* our own internal model of the content-stack; and
|
|
* 2nd, pass this message on to the sink.
|
|
* @update gess4/6/98
|
|
* @param aNode -- next node to be added to model
|
|
* @return TRUE if ok, FALSE if error
|
|
*/
|
|
nsresult
|
|
CNavDTD::OpenBody(const nsCParserNode *aNode)
|
|
{
|
|
NS_PRECONDITION(mBodyContext->GetCount() >= 0, kInvalidTagStackPos);
|
|
|
|
nsresult result = NS_OK;
|
|
|
|
if (!(mFlags & NS_DTD_FLAG_HAD_FRAMESET)) {
|
|
mFlags |= NS_DTD_FLAG_HAD_BODY;
|
|
|
|
// Make sure the head is closed by the time the body is opened.
|
|
CloseContainer(eHTMLTag_head, PR_FALSE);
|
|
|
|
// Now we can open the body.
|
|
result = mSink ? mSink->OpenContainer(*aNode) : NS_OK;
|
|
|
|
if (!HasOpenContainer(eHTMLTag_body)) {
|
|
mBodyContext->Push(const_cast<nsCParserNode*>(aNode), 0, PR_FALSE);
|
|
mTokenizer->PrependTokens(mMisplacedContent);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method does two things: 1st, help construct
|
|
* our own internal model of the content-stack; and
|
|
* 2nd, pass this message on to the sink.
|
|
* @update gess4/6/98
|
|
* @param aNode -- next node to be added to model
|
|
* @param aClosedByStartTag -- ONLY TRUE if the container is being closed by opening of another container.
|
|
* @return TRUE if ok, FALSE if error
|
|
*/
|
|
nsresult
|
|
CNavDTD::OpenContainer(const nsCParserNode *aNode,
|
|
eHTMLTags aTag,
|
|
nsEntryStack* aStyleStack)
|
|
{
|
|
NS_PRECONDITION(mBodyContext->GetCount() >= 0, kInvalidTagStackPos);
|
|
|
|
nsresult result = NS_OK;
|
|
PRBool done = PR_TRUE;
|
|
PRBool rs_tag = nsHTMLElement::IsResidualStyleTag(aTag);
|
|
// We need to open transient styles to encompass the <li> so that the bullets
|
|
// inherit the proper colors.
|
|
PRBool li_tag = aTag == eHTMLTag_li;
|
|
|
|
if (rs_tag || li_tag) {
|
|
/*
|
|
* Here's an interesting problem:
|
|
*
|
|
* If there's an <a> on the RS-stack, and you're trying to open
|
|
* another <a>, the one on the RS-stack should be discarded.
|
|
*
|
|
* I'm updating OpenTransientStyles to throw old <a>'s away.
|
|
*/
|
|
OpenTransientStyles(aTag, !li_tag);
|
|
}
|
|
|
|
switch (aTag) {
|
|
case eHTMLTag_html:
|
|
result = OpenHTML(aNode);
|
|
break;
|
|
|
|
case eHTMLTag_head:
|
|
if (!(mFlags & NS_DTD_FLAG_HAS_OPEN_HEAD)) {
|
|
mFlags |= NS_DTD_FLAG_HAS_OPEN_HEAD;
|
|
done = PR_FALSE;
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_body:
|
|
{
|
|
eHTMLTags theParent = mBodyContext->Last();
|
|
if (!gHTMLElements[aTag].IsSpecialParent(theParent)) {
|
|
mFlags |= NS_DTD_FLAG_HAS_OPEN_BODY;
|
|
result = OpenBody(aNode);
|
|
} else {
|
|
done = PR_FALSE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_map:
|
|
++mOpenMapCount;
|
|
done = PR_FALSE;
|
|
break;
|
|
|
|
case eHTMLTag_form:
|
|
// Discard nested forms - bug 72639
|
|
if (!(mFlags & NS_DTD_FLAG_HAS_OPEN_FORM)) {
|
|
mFlags |= NS_DTD_FLAG_HAS_OPEN_FORM;
|
|
result = mSink ? mSink->OpenContainer(*aNode) : NS_OK;
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_frameset:
|
|
// Make sure that the head is closed before we try to open this frameset.
|
|
CloseContainer(eHTMLTag_head, PR_FALSE);
|
|
|
|
// Now that the head is closed, continue on with opening this frameset.
|
|
mFlags |= NS_DTD_FLAG_HAD_FRAMESET;
|
|
done = PR_FALSE;
|
|
break;
|
|
|
|
case eHTMLTag_noembed:
|
|
// <noembed> is unconditionally alternate content.
|
|
done = PR_FALSE;
|
|
mFlags |= NS_DTD_FLAG_ALTERNATE_CONTENT;
|
|
break;
|
|
|
|
case eHTMLTag_noscript:
|
|
// We want to make sure that OpenContainer gets called below since we're
|
|
// not doing it here
|
|
done = PR_FALSE;
|
|
|
|
if (mFlags & NS_IPARSER_FLAG_SCRIPT_ENABLED) {
|
|
// XXX This flag doesn't currently do anything (and would be
|
|
// insufficient if it did).
|
|
mFlags |= NS_DTD_FLAG_ALTERNATE_CONTENT;
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_iframe: // Bug 84491
|
|
case eHTMLTag_noframes:
|
|
done = PR_FALSE;
|
|
if (mFlags & NS_IPARSER_FLAG_FRAMES_ENABLED) {
|
|
mFlags |= NS_DTD_FLAG_ALTERNATE_CONTENT;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
done = PR_FALSE;
|
|
break;
|
|
}
|
|
|
|
if (!done) {
|
|
|
|
result = mSink ? mSink->OpenContainer(*aNode) : NS_OK;
|
|
|
|
// For residual style tags rs_tag will be true and hence
|
|
// the body context will hold an extra reference to the node.
|
|
mBodyContext->Push(const_cast<nsCParserNode*>(aNode), aStyleStack, rs_tag);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
nsresult
|
|
CNavDTD::CloseResidualStyleTags(const eHTMLTags aTag,
|
|
PRBool aClosedByStartTag)
|
|
{
|
|
const PRInt32 count = mBodyContext->GetCount();
|
|
PRInt32 pos = count;
|
|
while (nsHTMLElement::IsResidualStyleTag(mBodyContext->TagAt(pos - 1)))
|
|
--pos;
|
|
if (pos < count)
|
|
return CloseContainersTo(pos, aTag, aClosedByStartTag);
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* This method does two things: 1st, help construct
|
|
* our own internal model of the content-stack; and
|
|
* 2nd, pass this message on to the sink.
|
|
* @update gess4/6/98
|
|
* @param aTag -- id of tag to be closed
|
|
* @return TRUE if ok, FALSE if error
|
|
*/
|
|
nsresult
|
|
CNavDTD::CloseContainer(const eHTMLTags aTag, PRBool aMalformed)
|
|
{
|
|
nsresult result = NS_OK;
|
|
PRBool done = PR_TRUE;
|
|
|
|
switch (aTag) {
|
|
case eHTMLTag_head:
|
|
if (mFlags & NS_DTD_FLAG_HAS_OPEN_HEAD) {
|
|
mFlags &= ~NS_DTD_FLAG_HAS_OPEN_HEAD;
|
|
if (mBodyContext->Last() == eHTMLTag_head) {
|
|
mBodyContext->Pop();
|
|
} else {
|
|
// This else can happen because CloseContainer is called both directly
|
|
// and from CloseContainersTo. CloseContainersTo pops the current tag
|
|
// off of the stack before calling CloseContainer.
|
|
NS_ASSERTION(mBodyContext->LastOf(eHTMLTag_head) == kNotFound,
|
|
"Closing the wrong tag");
|
|
}
|
|
done = PR_FALSE;
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_map:
|
|
if (mOpenMapCount) {
|
|
mOpenMapCount--;
|
|
done = PR_FALSE;
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_form:
|
|
if (mFlags & NS_DTD_FLAG_HAS_OPEN_FORM) {
|
|
mFlags &= ~NS_DTD_FLAG_HAS_OPEN_FORM;
|
|
done = PR_FALSE;
|
|
// If we neglect to close these tags, the sink will refuse to close the
|
|
// form because the form will not be on the top of the SinkContext stack.
|
|
// See HTMLContentSink::CloseForm. (XXX do this in other cases?)
|
|
CloseResidualStyleTags(eHTMLTag_form, PR_FALSE);
|
|
}
|
|
break;
|
|
|
|
case eHTMLTag_iframe:
|
|
case eHTMLTag_noembed:
|
|
case eHTMLTag_noscript:
|
|
case eHTMLTag_noframes:
|
|
// Switch from alternate content state to regular state.
|
|
mFlags &= ~NS_DTD_FLAG_ALTERNATE_CONTENT;
|
|
|
|
// falling thro' intentionally....
|
|
default:
|
|
done = PR_FALSE;
|
|
}
|
|
|
|
if (!done) {
|
|
|
|
if (mSink) {
|
|
result = !aMalformed
|
|
? mSink->CloseContainer(aTag)
|
|
: mSink->CloseMalformedContainer(aTag);
|
|
}
|
|
|
|
// If we were dealing with a head container in the body, make sure to
|
|
// close the head context now, so that body content doesn't get sucked
|
|
// into the head.
|
|
if (mBodyContext->GetCount() == mHeadContainerPosition) {
|
|
mHeadContainerPosition = -1;
|
|
nsresult headresult = CloseContainer(eHTMLTag_head, PR_FALSE);
|
|
|
|
// Note: we could be assigning NS_OK into NS_OK here, but that's ok.
|
|
// This test is to avoid a successful CloseHead result stomping over a
|
|
// request to block the parser.
|
|
if (NS_SUCCEEDED(result)) {
|
|
result = headresult;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method does two things: 1st, help construct
|
|
* our own internal model of the content-stack; and
|
|
* 2nd, pass this message on to the sink.
|
|
* @update gess4/6/98
|
|
* @param anIndex
|
|
* @param aTag
|
|
* @param aClosedByStartTag -- if TRUE, then we're closing something because a start tag caused it
|
|
* @return TRUE if ok, FALSE if error
|
|
*/
|
|
nsresult
|
|
CNavDTD::CloseContainersTo(PRInt32 anIndex, eHTMLTags aTarget,
|
|
PRBool aClosedByStartTag)
|
|
{
|
|
NS_PRECONDITION(mBodyContext->GetCount() > 0, kInvalidTagStackPos);
|
|
nsresult result = NS_OK;
|
|
|
|
if (anIndex < mBodyContext->GetCount() && anIndex >= 0) {
|
|
PRInt32 count = 0;
|
|
while ((count = mBodyContext->GetCount()) > anIndex) {
|
|
nsEntryStack* theChildStyleStack = 0;
|
|
eHTMLTags theTag = mBodyContext->Last();
|
|
nsCParserNode* theNode = mBodyContext->Pop(theChildStyleStack);
|
|
result = CloseContainer(theTag, PR_FALSE);
|
|
|
|
PRBool theTagIsStyle = nsHTMLElement::IsResidualStyleTag(theTag);
|
|
// If the current tag cannot leak out then we shouldn't leak out of the target - Fix 40713
|
|
PRBool theStyleDoesntLeakOut = gHTMLElements[theTag].HasSpecialProperty(kNoStyleLeaksOut);
|
|
if (!theStyleDoesntLeakOut) {
|
|
theStyleDoesntLeakOut = gHTMLElements[aTarget].HasSpecialProperty(kNoStyleLeaksOut);
|
|
}
|
|
|
|
// Do not invoke residual style handling when dealing with
|
|
// alternate content. This fixed bug 25214.
|
|
if (theTagIsStyle && !(mFlags & NS_DTD_FLAG_ALTERNATE_CONTENT)) {
|
|
NS_ASSERTION(theNode, "residual style node should not be null");
|
|
if (!theNode) {
|
|
if (theChildStyleStack) {
|
|
mBodyContext->PushStyles(theChildStyleStack);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
PRBool theTargetTagIsStyle = nsHTMLElement::IsResidualStyleTag(aTarget);
|
|
if (aClosedByStartTag) {
|
|
// Handle closure due to new start tag.
|
|
// The cases we're handing here:
|
|
// 1. <body><b><DIV> //<b> gets pushed onto <body>.mStyles.
|
|
// 2. <body><a>text<a> //in this case, the target matches, so don't push style
|
|
if (theNode->mUseCount == 0) {
|
|
if (theTag != aTarget) {
|
|
if (theChildStyleStack) {
|
|
theChildStyleStack->PushFront(theNode);
|
|
} else {
|
|
mBodyContext->PushStyle(theNode);
|
|
}
|
|
}
|
|
} else if (theTag == aTarget && !gHTMLElements[aTarget].CanContainSelf()) {
|
|
//here's a case we missed: <a><div>text<a>text</a></div>
|
|
//The <div> pushes the 1st <a> onto the rs-stack, then the 2nd <a>
|
|
//pops the 1st <a> from the rs-stack altogether.
|
|
nsCParserNode* node = mBodyContext->PopStyle(theTag);
|
|
IF_FREE(node, &mNodeAllocator);
|
|
}
|
|
|
|
if (theChildStyleStack) {
|
|
mBodyContext->PushStyles(theChildStyleStack);
|
|
}
|
|
} else {
|
|
/*
|
|
* if you're here, then we're dealing with the closure of tags
|
|
* caused by a close tag (as opposed to an open tag).
|
|
* At a minimum, we should consider pushing residual styles up
|
|
* up the stack or popping and recycling displaced nodes.
|
|
*
|
|
* Known cases:
|
|
* 1. <body><b><div>text</DIV>
|
|
* Here the <b> will leak into <div> (see case given above), and
|
|
* when <div> closes the <b> is dropped since it's already residual.
|
|
*
|
|
* 2. <body><div><b>text</div>
|
|
* Here the <b> will leak out of the <div> and get pushed onto
|
|
* the RS stack for the <body>, since it originated in the <div>.
|
|
*
|
|
* 3. <body><span><b>text</span>
|
|
* In this case, the the <b> get's pushed onto the style stack.
|
|
* Later we deal with RS styles stored on the <span>
|
|
*
|
|
* 4. <body><span><b>text</i>
|
|
* Here we the <b>is closed by a (synonymous) style tag.
|
|
* In this case, the <b> is simply closed.
|
|
*/
|
|
if (theChildStyleStack) {
|
|
if (!theStyleDoesntLeakOut) {
|
|
if (theTag != aTarget) {
|
|
if (theNode->mUseCount == 0) {
|
|
theChildStyleStack->PushFront(theNode);
|
|
}
|
|
} else if (theNode->mUseCount == 1) {
|
|
// This fixes bug 30885,29626.
|
|
// Make sure that the node, which is about to
|
|
// get released does not stay on the style stack...
|
|
// Also be sure to remove the correct style off the
|
|
// style stack. - Ref. bug 94208.
|
|
// Ex <FONT><B><I></FONT><FONT></B></I></FONT>
|
|
// Make sure that </B> removes B off the style stack.
|
|
mBodyContext->RemoveStyle(theTag);
|
|
}
|
|
mBodyContext->PushStyles(theChildStyleStack);
|
|
} else{
|
|
IF_DELETE(theChildStyleStack, &mNodeAllocator);
|
|
}
|
|
} else if (theNode->mUseCount == 0) {
|
|
// The old version of this only pushed if the targettag wasn't
|
|
// style. But that misses this case: <font><b>text</font>, where
|
|
// the b should leak.
|
|
if (aTarget != theTag) {
|
|
mBodyContext->PushStyle(theNode);
|
|
}
|
|
} else {
|
|
// Ah, at last, the final case. If you're here, then we just popped
|
|
// a style tag that got onto that tag stack from a stylestack
|
|
// somewhere. Pop it from the stylestack if the target is also a
|
|
// style tag. Make sure to remove the matching style. In the
|
|
// following example:
|
|
// <FONT><B><I></FONT><FONT color=red></B></I></FONT>
|
|
// make sure that </I> does not remove
|
|
// <FONT color=red> off the style stack. - bug 94208
|
|
if (theTargetTagIsStyle && theTag == aTarget) {
|
|
mBodyContext->RemoveStyle(theTag);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// The tag is not a style tag.
|
|
if (theChildStyleStack) {
|
|
if (theStyleDoesntLeakOut) {
|
|
IF_DELETE(theChildStyleStack, &mNodeAllocator);
|
|
} else {
|
|
mBodyContext->PushStyles(theChildStyleStack);
|
|
}
|
|
}
|
|
}
|
|
IF_FREE(theNode, &mNodeAllocator);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method does two things: 1st, help construct
|
|
* our own internal model of the content-stack; and
|
|
* 2nd, pass this message on to the sink.
|
|
* @update gess4/6/98
|
|
* @param aTag --
|
|
* @param aClosedByStartTag -- ONLY TRUE if the container is being closed by opening of another container.
|
|
* @return TRUE if ok, FALSE if error
|
|
*/
|
|
nsresult
|
|
CNavDTD::CloseContainersTo(eHTMLTags aTag, PRBool aClosedByStartTag)
|
|
{
|
|
NS_PRECONDITION(mBodyContext->GetCount() > 0, kInvalidTagStackPos);
|
|
|
|
PRInt32 pos = mBodyContext->LastOf(aTag);
|
|
|
|
if (kNotFound != pos) {
|
|
// The tag is indeed open, so close it.
|
|
return CloseContainersTo(pos, aTag, aClosedByStartTag);
|
|
}
|
|
|
|
eHTMLTags theTopTag = mBodyContext->Last();
|
|
|
|
PRBool theTagIsSynonymous = (nsHTMLElement::IsResidualStyleTag(aTag) &&
|
|
nsHTMLElement::IsResidualStyleTag(theTopTag)) ||
|
|
(gHTMLElements[aTag].IsMemberOf(kHeading) &&
|
|
gHTMLElements[theTopTag].IsMemberOf(kHeading));
|
|
|
|
if (theTagIsSynonymous) {
|
|
// If you're here, it's because we're trying to close one tag,
|
|
// but a different (synonymous) one is actually open. Because this is NAV4x
|
|
// compatibililty mode, we must close the one that's really open.
|
|
aTag = theTopTag;
|
|
pos = mBodyContext->LastOf(aTag);
|
|
if (kNotFound != pos) {
|
|
// The tag is indeed open, so close it.
|
|
return CloseContainersTo(pos, aTag, aClosedByStartTag);
|
|
}
|
|
}
|
|
|
|
nsresult result = NS_OK;
|
|
const TagList* theRootTags = gHTMLElements[aTag].GetRootTags();
|
|
// XXX Can we just bail if !theRootTags? Does that ever happen?
|
|
eHTMLTags theParentTag = theRootTags ? theRootTags->mTags[0] : eHTMLTag_unknown;
|
|
pos = mBodyContext->LastOf(theParentTag);
|
|
if (kNotFound != pos) {
|
|
// The parent container is open, so close it instead
|
|
result = CloseContainersTo(pos + 1, aTag, aClosedByStartTag);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method does two things: 1st, help construct
|
|
* our own internal model of the content-stack; and
|
|
* 2nd, pass this message on to the sink.
|
|
* @update gess4/6/98
|
|
* @param aNode -- next node to be added to model
|
|
* @return error code; 0 means OK
|
|
*/
|
|
nsresult
|
|
CNavDTD::AddLeaf(const nsIParserNode *aNode)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
if (mSink) {
|
|
eHTMLTags theTag = (eHTMLTags)aNode->GetNodeType();
|
|
OpenTransientStyles(theTag);
|
|
|
|
result = mSink->AddLeaf(*aNode);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Call this method ONLY when you want to write a leaf
|
|
* into the head container.
|
|
*
|
|
* @update gess 03/14/99
|
|
* @param aNode -- next node to be added to model
|
|
* @return error code; 0 means OK
|
|
*/
|
|
nsresult
|
|
CNavDTD::AddHeadContent(nsIParserNode *aNode)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
static eHTMLTags gNoXTags[] = { eHTMLTag_noembed, eHTMLTag_noframes };
|
|
|
|
eHTMLTags theTag = (eHTMLTags)aNode->GetNodeType();
|
|
|
|
// XXX - SCRIPT inside NOTAGS should not get executed unless the pref.
|
|
// says so. Since we don't have this support yet..lets ignore the
|
|
// SCRIPT inside NOTAGS. Ref Bug 25880.
|
|
if (eHTMLTag_meta == theTag || eHTMLTag_script == theTag) {
|
|
if (HasOpenContainer(gNoXTags, NS_ARRAY_LENGTH(gNoXTags))) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (mSink) {
|
|
// Make sure the head is opened.
|
|
if (!(mFlags & NS_DTD_FLAG_HAS_OPEN_HEAD)) {
|
|
result = mSink->OpenHead();
|
|
mBodyContext->PushTag(eHTMLTag_head);
|
|
mFlags |= NS_DTD_FLAG_HAS_OPEN_HEAD;
|
|
}
|
|
|
|
// Note: userdefined tags in the head are treated as leaves.
|
|
if (!nsHTMLElement::IsContainer(theTag) || theTag == eHTMLTag_userdefined) {
|
|
result = mSink->AddLeaf(*aNode);
|
|
|
|
if (mFlags & NS_DTD_FLAG_HAS_MAIN_CONTAINER) {
|
|
// Close the head now so that body content doesn't get sucked into it.
|
|
CloseContainer(eHTMLTag_head, PR_FALSE);
|
|
}
|
|
} else {
|
|
if ((mFlags & NS_DTD_FLAG_HAS_MAIN_CONTAINER) &&
|
|
mHeadContainerPosition == -1) {
|
|
// Keep track of this so that we know when to close the head, when
|
|
// this tag is done with.
|
|
mHeadContainerPosition = mBodyContext->GetCount();
|
|
}
|
|
|
|
// Note: The head context is already opened.
|
|
result = mSink->OpenContainer(*aNode);
|
|
|
|
mBodyContext->Push(static_cast<nsCParserNode*>(aNode), nsnull,
|
|
PR_FALSE);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
CNavDTD::CreateContextStackFor(eHTMLTags aParent, eHTMLTags aChild)
|
|
{
|
|
mScratch.Truncate();
|
|
|
|
PRBool result = ForwardPropagate(mScratch, aParent, aChild);
|
|
|
|
if (!result) {
|
|
if (eHTMLTag_unknown == aParent) {
|
|
result = BackwardPropagate(mScratch, eHTMLTag_html, aChild);
|
|
} else if (aParent != aChild) {
|
|
// Don't even bother if we're already inside a similar element...
|
|
result = BackwardPropagate(mScratch, aParent, aChild);
|
|
}
|
|
}
|
|
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
PRInt32 theLen = mScratch.Length();
|
|
eHTMLTags theTag = (eHTMLTags)mScratch[--theLen];
|
|
|
|
// Now, build up the stack according to the tags.
|
|
while (theLen) {
|
|
theTag = (eHTMLTags)mScratch[--theLen];
|
|
|
|
// Note: These tokens should all wind up on contextstack, so don't recycle
|
|
// them.
|
|
CToken *theToken = mTokenAllocator->CreateTokenOfType(eToken_start, theTag);
|
|
HandleToken(theToken);
|
|
}
|
|
}
|