/* -*- 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);
  }
}
