/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * An implementation for a Gecko-style content sink that knows how
 * to build a content model (the "prototype" document) from XUL.
 *
 * For more information on XUL,
 * see http://developer.mozilla.org/en/docs/XUL
 */

#include "nsXULContentSink.h"

#include "jsfriendapi.h"

#include "nsCOMPtr.h"
#include "nsIContentSink.h"
#include "mozilla/dom/Document.h"
#include "nsIFormControl.h"
#include "mozilla/dom/NodeInfo.h"
#include "nsIScriptContext.h"
#include "nsIScriptGlobalObject.h"
#include "nsNameSpaceManager.h"
#include "nsParserBase.h"
#include "nsViewManager.h"
#include "nsIScriptSecurityManager.h"
#include "nsLayoutCID.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsXULElement.h"
#include "mozilla/Logging.h"
#include "nsCRT.h"

#include "nsXULPrototypeDocument.h"  // XXXbe temporary
#include "mozilla/css/Loader.h"

#include "nsUnicharUtils.h"
#include "nsGkAtoms.h"
#include "nsContentUtils.h"
#include "nsAttrName.h"
#include "nsXMLContentSink.h"
#include "nsIScriptError.h"
#include "nsContentTypeParser.h"

static mozilla::LazyLogModule gContentSinkLog("nsXULContentSink");

using namespace mozilla;
using namespace mozilla::dom;
//----------------------------------------------------------------------

XULContentSinkImpl::ContextStack::ContextStack() : mTop(nullptr), mDepth(0) {}

XULContentSinkImpl::ContextStack::~ContextStack() {
  while (mTop) {
    Entry* doomed = mTop;
    mTop = mTop->mNext;
    delete doomed;
  }
}

void XULContentSinkImpl::ContextStack::Push(RefPtr<nsXULPrototypeNode>&& aNode,
                                            State aState) {
  mTop = new Entry(std::move(aNode), aState, mTop);
  ++mDepth;
}

nsresult XULContentSinkImpl::ContextStack::Pop(State* aState) {
  if (mDepth == 0) return NS_ERROR_UNEXPECTED;

  Entry* entry = mTop;
  mTop = mTop->mNext;
  --mDepth;

  *aState = entry->mState;
  delete entry;

  return NS_OK;
}

nsresult XULContentSinkImpl::ContextStack::GetTopNode(
    RefPtr<nsXULPrototypeNode>& aNode) {
  if (mDepth == 0) return NS_ERROR_UNEXPECTED;

  aNode = mTop->mNode;
  return NS_OK;
}

nsresult XULContentSinkImpl::ContextStack::GetTopChildren(
    nsPrototypeArray** aChildren) {
  if (mDepth == 0) return NS_ERROR_UNEXPECTED;

  *aChildren = &(mTop->mChildren);
  return NS_OK;
}

void XULContentSinkImpl::ContextStack::Clear() {
  Entry* cur = mTop;
  while (cur) {
    // Release the root element (and its descendants).
    Entry* next = cur->mNext;
    delete cur;
    cur = next;
  }

  mTop = nullptr;
  mDepth = 0;
}

void XULContentSinkImpl::ContextStack::Traverse(
    nsCycleCollectionTraversalCallback& aCb) {
  nsCycleCollectionTraversalCallback& cb = aCb;
  for (ContextStack::Entry* tmp = mTop; tmp; tmp = tmp->mNext) {
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNode)
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren)
  }
}

//----------------------------------------------------------------------

XULContentSinkImpl::XULContentSinkImpl()
    : mText(nullptr),
      mTextLength(0),
      mTextSize(0),
      mConstrainSize(true),
      mState(eInProlog) {}

XULContentSinkImpl::~XULContentSinkImpl() {
  // The context stack _should_ be empty, unless something has gone wrong.
  NS_ASSERTION(mContextStack.Depth() == 0, "Context stack not empty?");
  mContextStack.Clear();

  free(mText);
}

//----------------------------------------------------------------------
// nsISupports interface

NS_IMPL_CYCLE_COLLECTION_CLASS(XULContentSinkImpl)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XULContentSinkImpl)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager)
  tmp->mContextStack.Clear();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototype)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XULContentSinkImpl)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)
  tmp->mContextStack.Traverse(cb);
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototype)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULContentSinkImpl)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXMLContentSink)
  NS_INTERFACE_MAP_ENTRY(nsIXMLContentSink)
  NS_INTERFACE_MAP_ENTRY(nsIExpatSink)
  NS_INTERFACE_MAP_ENTRY(nsIContentSink)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(XULContentSinkImpl)
NS_IMPL_CYCLE_COLLECTING_RELEASE(XULContentSinkImpl)

//----------------------------------------------------------------------
// nsIContentSink interface

NS_IMETHODIMP
XULContentSinkImpl::DidBuildModel(bool aTerminated) {
  nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
  if (doc) {
    mPrototype->NotifyLoadDone();
    mDocument = nullptr;
  }

  // Drop our reference to the parser to get rid of a circular
  // reference.
  mParser = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
XULContentSinkImpl::WillInterrupt(void) {
  // XXX Notify the docshell, if necessary
  return NS_OK;
}

void XULContentSinkImpl::WillResume() {
  // XXX Notify the docshell, if necessary
}

NS_IMETHODIMP
XULContentSinkImpl::SetParser(nsParserBase* aParser) {
  mParser = aParser;
  return NS_OK;
}

void XULContentSinkImpl::SetDocumentCharset(
    NotNull<const Encoding*> aEncoding) {
  nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
  if (doc) {
    doc->SetDocumentCharacterSet(aEncoding);
  }
}

nsISupports* XULContentSinkImpl::GetTarget() {
  nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
  return ToSupports(doc);
}

//----------------------------------------------------------------------

nsresult XULContentSinkImpl::Init(Document* aDocument,
                                  nsXULPrototypeDocument* aPrototype) {
  MOZ_ASSERT(aDocument != nullptr, "null ptr");
  if (!aDocument) return NS_ERROR_NULL_POINTER;

  mDocument = do_GetWeakReference(aDocument);
  mPrototype = aPrototype;

  mDocumentURL = mPrototype->GetURI();
  mNodeInfoManager = aPrototype->GetNodeInfoManager();
  if (!mNodeInfoManager) return NS_ERROR_UNEXPECTED;

  mState = eInProlog;
  return NS_OK;
}

//----------------------------------------------------------------------
//
// Text buffering
//

bool XULContentSinkImpl::IsDataInBuffer(char16_t* buffer, int32_t length) {
  for (int32_t i = 0; i < length; ++i) {
    if (buffer[i] == ' ' || buffer[i] == '\t' || buffer[i] == '\n' ||
        buffer[i] == '\r')
      continue;

    return true;
  }
  return false;
}

nsresult XULContentSinkImpl::FlushText(bool aCreateTextNode) {
  nsresult rv;

  do {
    // Don't do anything if there's no text to create a node from, or
    // if they've told us not to create a text node
    if (!mTextLength) break;

    if (!aCreateTextNode) break;

    RefPtr<nsXULPrototypeNode> node;
    rv = mContextStack.GetTopNode(node);
    if (NS_FAILED(rv)) return rv;

    bool stripWhitespace = false;
    if (node->mType == nsXULPrototypeNode::eType_Element) {
      mozilla::dom::NodeInfo* nodeInfo =
          static_cast<nsXULPrototypeElement*>(node.get())->mNodeInfo;

      if (nodeInfo->NamespaceEquals(kNameSpaceID_XUL))
        stripWhitespace = !nodeInfo->Equals(nsGkAtoms::label) &&
                          !nodeInfo->Equals(nsGkAtoms::description);
    }

    // Don't bother if there's nothing but whitespace.
    if (stripWhitespace && !IsDataInBuffer(mText, mTextLength)) break;

    // Don't bother if we're not in XUL document body
    if (mState != eInDocumentElement || mContextStack.Depth() == 0) break;

    RefPtr<nsXULPrototypeText> text = new nsXULPrototypeText();
    text->mValue.Assign(mText, mTextLength);
    if (stripWhitespace) text->mValue.Trim(" \t\n\r");

    // hook it up
    nsPrototypeArray* children = nullptr;
    rv = mContextStack.GetTopChildren(&children);
    if (NS_FAILED(rv)) return rv;

    children->AppendElement(text.forget());
  } while (0);

  // Reset our text buffer
  mTextLength = 0;
  return NS_OK;
}

//----------------------------------------------------------------------

nsresult XULContentSinkImpl::NormalizeAttributeString(
    const char16_t* aExpatName, nsAttrName& aName) {
  int32_t nameSpaceID;
  RefPtr<nsAtom> prefix, localName;
  nsContentUtils::SplitExpatName(aExpatName, getter_AddRefs(prefix),
                                 getter_AddRefs(localName), &nameSpaceID);

  if (nameSpaceID == kNameSpaceID_None) {
    aName.SetTo(localName);

    return NS_OK;
  }

  RefPtr<mozilla::dom::NodeInfo> ni;
  ni = mNodeInfoManager->GetNodeInfo(localName, prefix, nameSpaceID,
                                     nsINode::ATTRIBUTE_NODE);
  aName.SetTo(ni);

  return NS_OK;
}

/**** BEGIN NEW APIs ****/

NS_IMETHODIMP
XULContentSinkImpl::HandleStartElement(const char16_t* aName,
                                       const char16_t** aAtts,
                                       uint32_t aAttsCount,
                                       uint32_t aLineNumber,
                                       uint32_t aColumnNumber) {
  // XXX Hopefully the parser will flag this before we get here. If
  // we're in the epilog, there should be no new elements
  MOZ_ASSERT(mState != eInEpilog, "tag in XUL doc epilog");
  MOZ_ASSERT(aAttsCount % 2 == 0, "incorrect aAttsCount");

  // Adjust aAttsCount so it's the actual number of attributes
  aAttsCount /= 2;

  if (mState == eInEpilog) return NS_ERROR_UNEXPECTED;

  if (mState != eInScript) {
    FlushText();
  }

  int32_t nameSpaceID;
  RefPtr<nsAtom> prefix, localName;
  nsContentUtils::SplitExpatName(aName, getter_AddRefs(prefix),
                                 getter_AddRefs(localName), &nameSpaceID);

  RefPtr<mozilla::dom::NodeInfo> nodeInfo;
  nodeInfo = mNodeInfoManager->GetNodeInfo(localName, prefix, nameSpaceID,
                                           nsINode::ELEMENT_NODE);

  nsresult rv = NS_OK;
  switch (mState) {
    case eInProlog:
      // We're the root document element
      rv = OpenRoot(aAtts, aAttsCount, nodeInfo);
      break;

    case eInDocumentElement:
      rv = OpenTag(aAtts, aAttsCount, aLineNumber, nodeInfo);
      break;

    case eInEpilog:
    case eInScript:
      MOZ_LOG(
          gContentSinkLog, LogLevel::Warning,
          ("xul: warning: unexpected tags in epilog at line %d", aLineNumber));
      rv = NS_ERROR_UNEXPECTED;  // XXX
      break;
  }

  return rv;
}

NS_IMETHODIMP
XULContentSinkImpl::HandleEndElement(const char16_t* aName) {
  // Never EVER return anything but NS_OK or
  // NS_ERROR_HTMLPARSER_BLOCK from this method. Doing so will blow
  // the parser's little mind all over the planet.
  nsresult rv;

  RefPtr<nsXULPrototypeNode> node;
  rv = mContextStack.GetTopNode(node);

  if (NS_FAILED(rv)) {
    return NS_OK;
  }

  switch (node->mType) {
    case nsXULPrototypeNode::eType_Element: {
      // Flush any text _now_, so that we'll get text nodes created
      // before popping the stack.
      FlushText();

      // Pop the context stack and do prototype hookup.
      nsPrototypeArray* children = nullptr;
      rv = mContextStack.GetTopChildren(&children);
      if (NS_FAILED(rv)) return rv;

      nsXULPrototypeElement* element =
          static_cast<nsXULPrototypeElement*>(node.get());

      int32_t count = children->Length();
      if (count) {
        element->mChildren.SetCapacity(count);

        for (int32_t i = 0; i < count; ++i)
          element->mChildren.AppendElement(children->ElementAt(i));
      }
    } break;

    case nsXULPrototypeNode::eType_Script: {
      nsXULPrototypeScript* script =
          static_cast<nsXULPrototypeScript*>(node.get());

      // If given a src= attribute, we must ignore script tag content.
      if (!script->mSrcURI && !script->HasStencil()) {
        nsCOMPtr<Document> doc = do_QueryReferent(mDocument);

        script->mOutOfLine = false;
        if (doc) {
          script->Compile(mText, mTextLength, mDocumentURL, script->mLineNo,
                          doc);
        }
      }

      FlushText(false);
    } break;

    default:
      NS_ERROR("didn't expect that");
      break;
  }

  rv = mContextStack.Pop(&mState);
  NS_ASSERTION(NS_SUCCEEDED(rv), "context stack corrupted");
  if (NS_FAILED(rv)) return rv;

  if (mContextStack.Depth() == 0) {
    // The root element should -always- be an element, because
    // it'll have been created via XULContentSinkImpl::OpenRoot().
    NS_ASSERTION(node->mType == nsXULPrototypeNode::eType_Element,
                 "root is not an element");
    if (node->mType != nsXULPrototypeNode::eType_Element)
      return NS_ERROR_UNEXPECTED;

    // Now that we're done parsing, set the prototype document's
    // root element. This transfers ownership of the prototype
    // element tree to the prototype document.
    nsXULPrototypeElement* element =
        static_cast<nsXULPrototypeElement*>(node.get());

    mPrototype->SetRootElement(element);
    mState = eInEpilog;
  }

  return NS_OK;
}

NS_IMETHODIMP
XULContentSinkImpl::HandleComment(const char16_t* aName) {
  FlushText();
  return NS_OK;
}

NS_IMETHODIMP
XULContentSinkImpl::HandleCDataSection(const char16_t* aData,
                                       uint32_t aLength) {
  FlushText();
  return AddText(aData, aLength);
}

NS_IMETHODIMP
XULContentSinkImpl::HandleDoctypeDecl(const nsAString& aSubset,
                                      const nsAString& aName,
                                      const nsAString& aSystemId,
                                      const nsAString& aPublicId,
                                      nsISupports* aCatalogData) {
  return NS_OK;
}

NS_IMETHODIMP
XULContentSinkImpl::HandleCharacterData(const char16_t* aData,
                                        uint32_t aLength) {
  if (aData && mState != eInProlog && mState != eInEpilog) {
    return AddText(aData, aLength);
  }
  return NS_OK;
}

NS_IMETHODIMP
XULContentSinkImpl::HandleProcessingInstruction(const char16_t* aTarget,
                                                const char16_t* aData) {
  FlushText();

  const nsDependentString target(aTarget);
  const nsDependentString data(aData);

  // Note: the created nsXULPrototypePI has mRefCnt == 1
  RefPtr<nsXULPrototypePI> pi = new nsXULPrototypePI();
  pi->mTarget = target;
  pi->mData = data;

  if (mState == eInProlog) {
    // Note: passing in already addrefed pi
    return mPrototype->AddProcessingInstruction(pi);
  }

  nsresult rv;
  nsPrototypeArray* children = nullptr;
  rv = mContextStack.GetTopChildren(&children);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // XXX(Bug 1631371) Check if this should use a fallible operation as it
  // pretended earlier.
  children->AppendElement(pi);

  return NS_OK;
}

NS_IMETHODIMP
XULContentSinkImpl::HandleXMLDeclaration(const char16_t* aVersion,
                                         const char16_t* aEncoding,
                                         int32_t aStandalone) {
  return NS_OK;
}

NS_IMETHODIMP
XULContentSinkImpl::ReportError(const char16_t* aErrorText,
                                const char16_t* aSourceText,
                                nsIScriptError* aError, bool* _retval) {
  MOZ_ASSERT(aError && aSourceText && aErrorText, "Check arguments!!!");

  // The expat driver should report the error.
  *_retval = true;

  nsresult rv = NS_OK;

  // make sure to empty the context stack so that
  // <parsererror> could become the root element.
  mContextStack.Clear();

  mState = eInProlog;

  // Clear any buffered-up text we have.  It's enough to set the length to 0.
  // The buffer itself is allocated when we're created and deleted in our
  // destructor, so don't mess with it.
  mTextLength = 0;

  // return leaving the document empty if we're asked to not add a <parsererror>
  // root node
  nsCOMPtr<Document> idoc = do_QueryReferent(mDocument);
  if (idoc && idoc->SuppressParserErrorElement()) {
    return NS_OK;
  };

  const char16_t* noAtts[] = {0, 0};

  constexpr auto errorNs =
      u"http://www.mozilla.org/newlayout/xml/parsererror.xml"_ns;

  nsAutoString parsererror(errorNs);
  parsererror.Append((char16_t)0xFFFF);
  parsererror.AppendLiteral("parsererror");

  rv = HandleStartElement(parsererror.get(), noAtts, 0, 0, 0);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = HandleCharacterData(aErrorText, NS_strlen(aErrorText));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString sourcetext(errorNs);
  sourcetext.Append((char16_t)0xFFFF);
  sourcetext.AppendLiteral("sourcetext");

  rv = HandleStartElement(sourcetext.get(), noAtts, 0, 0, 0);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = HandleCharacterData(aSourceText, NS_strlen(aSourceText));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = HandleEndElement(sourcetext.get());
  NS_ENSURE_SUCCESS(rv, rv);

  rv = HandleEndElement(parsererror.get());
  NS_ENSURE_SUCCESS(rv, rv);

  return rv;
}

nsresult XULContentSinkImpl::OpenRoot(const char16_t** aAttributes,
                                      const uint32_t aAttrLen,
                                      mozilla::dom::NodeInfo* aNodeInfo) {
  NS_ASSERTION(mState == eInProlog, "how'd we get here?");
  if (mState != eInProlog) return NS_ERROR_UNEXPECTED;

  if (aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) ||
      aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XUL)) {
    MOZ_LOG(gContentSinkLog, LogLevel::Error,
            ("xul: script tag not allowed as root content element"));

    return NS_ERROR_UNEXPECTED;
  }

  // Create the element
  RefPtr<nsXULPrototypeElement> element = new nsXULPrototypeElement(aNodeInfo);

  // Add the attributes
  nsresult rv = AddAttributes(aAttributes, aAttrLen, element);
  if (NS_FAILED(rv)) return rv;

  // Push the element onto the context stack, so that child
  // containers will hook up to us as their parent.
  mContextStack.Push(std::move(element), mState);

  mState = eInDocumentElement;
  return NS_OK;
}

nsresult XULContentSinkImpl::OpenTag(const char16_t** aAttributes,
                                     const uint32_t aAttrLen,
                                     const uint32_t aLineNumber,
                                     mozilla::dom::NodeInfo* aNodeInfo) {
  // Create the element
  RefPtr<nsXULPrototypeElement> element = new nsXULPrototypeElement(aNodeInfo);

  // Link this element to its parent.
  nsPrototypeArray* children = nullptr;
  nsresult rv = mContextStack.GetTopChildren(&children);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // Add the attributes
  rv = AddAttributes(aAttributes, aAttrLen, element);
  if (NS_FAILED(rv)) return rv;

  children->AppendElement(element);

  if (aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) ||
      aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XUL)) {
    // Do scripty things now
    rv = OpenScript(aAttributes, aLineNumber);
    NS_ENSURE_SUCCESS(rv, rv);

    NS_ASSERTION(mState == eInScript || mState == eInDocumentElement,
                 "Unexpected state");
    if (mState == eInScript) {
      // OpenScript has pushed the nsPrototypeScriptElement onto the
      // stack, so we're done.
      return NS_OK;
    }
  }

  // Push the element onto the context stack, so that child
  // containers will hook up to us as their parent.
  mContextStack.Push(std::move(element), mState);

  mState = eInDocumentElement;
  return NS_OK;
}

nsresult XULContentSinkImpl::OpenScript(const char16_t** aAttributes,
                                        const uint32_t aLineNumber) {
  bool isJavaScript = true;
  nsresult rv;

  // Look for SRC attribute and look for a LANGUAGE attribute
  nsAutoString src;
  while (*aAttributes) {
    const nsDependentString key(aAttributes[0]);
    if (key.EqualsLiteral("src")) {
      src.Assign(aAttributes[1]);
    } else if (key.EqualsLiteral("type")) {
      nsDependentString str(aAttributes[1]);
      nsContentTypeParser parser(str);
      nsAutoString mimeType;
      rv = parser.GetType(mimeType);
      if (NS_FAILED(rv)) {
        if (rv == NS_ERROR_INVALID_ARG) {
          // Fail immediately rather than checking if later things
          // are okay.
          return NS_OK;
        }
        // We do want the warning here
        NS_ENSURE_SUCCESS(rv, rv);
      }

      // NOTE(emilio): Module scripts don't pass this test, aren't cached yet.
      // If they become cached, then we need to tweak
      // PrototypeDocumentContentSink and remove the special cases there.
      if (nsContentUtils::IsJavascriptMIMEType(mimeType)) {
        isJavaScript = true;

        // Get the version string, and ensure that JavaScript supports it.
        nsAutoString versionName;
        rv = parser.GetParameter("version", versionName);

        if (NS_SUCCEEDED(rv)) {
          nsContentUtils::ReportToConsoleNonLocalized(
              u"Versioned JavaScripts are no longer supported. "
              "Please remove the version parameter."_ns,
              nsIScriptError::errorFlag, "XUL Document"_ns, nullptr,
              mDocumentURL, u""_ns, aLineNumber);
          isJavaScript = false;
        } else if (rv != NS_ERROR_INVALID_ARG) {
          return rv;
        }
      } else {
        isJavaScript = false;
      }
    } else if (key.EqualsLiteral("language")) {
      // Language is deprecated, and the impl in ScriptLoader ignores the
      // various version strings anyway.  So we make no attempt to support
      // languages other than JS for language=
      nsAutoString lang(aAttributes[1]);
      if (nsContentUtils::IsJavaScriptLanguage(lang)) {
        isJavaScript = true;
      }
    }
    aAttributes += 2;
  }

  // Don't process scripts that aren't JavaScript.
  if (!isJavaScript) {
    return NS_OK;
  }

  nsCOMPtr<Document> doc(do_QueryReferent(mDocument));
  nsCOMPtr<nsIScriptGlobalObject> globalObject;
  if (doc) globalObject = do_QueryInterface(doc->GetWindow());
  RefPtr<nsXULPrototypeScript> script = new nsXULPrototypeScript(aLineNumber);

  // If there is a SRC attribute...
  if (!src.IsEmpty()) {
    // Use the SRC attribute value to load the URL
    rv = NS_NewURI(getter_AddRefs(script->mSrcURI), src, nullptr, mDocumentURL);

    // Check if this document is allowed to load a script from this source
    // NOTE: if we ever allow scripts added via the DOM to run, we need to
    // add a CheckLoadURI call for that as well.
    if (NS_SUCCEEDED(rv)) {
      if (!mSecMan)
        mSecMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
      if (NS_SUCCEEDED(rv)) {
        nsCOMPtr<Document> doc = do_QueryReferent(mDocument, &rv);

        if (NS_SUCCEEDED(rv)) {
          rv = mSecMan->CheckLoadURIWithPrincipal(
              doc->NodePrincipal(), script->mSrcURI,
              nsIScriptSecurityManager::ALLOW_CHROME, doc->InnerWindowID());
        }
      }
    }

    if (NS_FAILED(rv)) {
      return rv;
    }

    // Attempt to deserialize an out-of-line script from the FastLoad
    // file right away.  Otherwise we'll end up reloading the script and
    // corrupting the FastLoad file trying to serialize it, in the case
    // where it's already there.
    script->DeserializeOutOfLine(nullptr, mPrototype);
  }

  nsPrototypeArray* children = nullptr;
  rv = mContextStack.GetTopChildren(&children);
  if (NS_FAILED(rv)) {
    return rv;
  }

  children->AppendElement(script);

  mConstrainSize = false;

  mContextStack.Push(script, mState);
  mState = eInScript;

  return NS_OK;
}

nsresult XULContentSinkImpl::AddAttributes(const char16_t** aAttributes,
                                           const uint32_t aAttrLen,
                                           nsXULPrototypeElement* aElement) {
  // Add tag attributes to the element
  nsresult rv;

  // Create storage for the attributes
  nsXULPrototypeAttribute* attrs = nullptr;
  if (aAttrLen > 0) {
    attrs = aElement->mAttributes.AppendElements(aAttrLen);
  }

  // Copy the attributes into the prototype
  uint32_t i;
  for (i = 0; i < aAttrLen; ++i) {
    rv = NormalizeAttributeString(aAttributes[i * 2], attrs[i].mName);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = aElement->SetAttrAt(i, nsDependentString(aAttributes[i * 2 + 1]),
                             mDocumentURL);
    NS_ENSURE_SUCCESS(rv, rv);

    if (MOZ_LOG_TEST(gContentSinkLog, LogLevel::Debug)) {
      nsAutoString extraWhiteSpace;
      int32_t cnt = mContextStack.Depth();
      while (--cnt >= 0) extraWhiteSpace.AppendLiteral("  ");
      nsAutoString qnameC, valueC;
      qnameC.Assign(aAttributes[0]);
      valueC.Assign(aAttributes[1]);
      MOZ_LOG(gContentSinkLog, LogLevel::Debug,
              ("xul: %.5d. %s    %s=%s",
               -1,  // XXX pass in line number
               NS_ConvertUTF16toUTF8(extraWhiteSpace).get(),
               NS_ConvertUTF16toUTF8(qnameC).get(),
               NS_ConvertUTF16toUTF8(valueC).get()));
    }
  }

  return NS_OK;
}

nsresult XULContentSinkImpl::AddText(const char16_t* aText, int32_t aLength) {
  // Create buffer when we first need it
  if (0 == mTextSize) {
    mText = (char16_t*)malloc(sizeof(char16_t) * 4096);
    if (nullptr == mText) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
    mTextSize = 4096;
  }

  // Copy data from string into our buffer; flush buffer when it fills up
  int32_t offset = 0;
  while (0 != aLength) {
    int32_t amount = mTextSize - mTextLength;
    if (amount > aLength) {
      amount = aLength;
    }
    if (0 == amount) {
      if (mConstrainSize) {
        nsresult rv = FlushText();
        if (NS_OK != rv) {
          return rv;
        }
      } else {
        CheckedInt32 size = mTextSize;
        size += aLength;
        if (!size.isValid()) {
          return NS_ERROR_OUT_OF_MEMORY;
        }
        mTextSize = size.value();

        mText = (char16_t*)realloc(mText, sizeof(char16_t) * mTextSize);
        if (nullptr == mText) {
          return NS_ERROR_OUT_OF_MEMORY;
        }
      }
    }
    memcpy(&mText[mTextLength], aText + offset, sizeof(char16_t) * amount);

    mTextLength += amount;
    offset += amount;
    aLength -= amount;
  }

  return NS_OK;
}
