DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (fce0b326cd31)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
/* -*- 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 https://mozilla.org/MPL/2.0/. */

#include "PrototypeDocumentParser.h"

#include "nsXULPrototypeCache.h"
#include "nsXULContentSink.h"
#include "nsParserCIID.h"
#include "mozilla/Encoding.h"
#include "nsCharsetSource.h"
#include "mozilla/dom/PrototypeDocumentContentSink.h"

using namespace mozilla::dom;

static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);

namespace mozilla {
namespace parser {

PrototypeDocumentParser::PrototypeDocumentParser(nsIURI* aDocumentURI,
                                                 dom::Document* aDocument)
    : mDocumentURI(aDocumentURI),
      mDocument(aDocument),
      mPrototypeAlreadyLoaded(false),
      mIsComplete(false) {}

PrototypeDocumentParser::~PrototypeDocumentParser() {}

NS_INTERFACE_TABLE_HEAD(PrototypeDocumentParser)
  NS_INTERFACE_TABLE(PrototypeDocumentParser, nsIParser, nsIStreamListener,
                     nsIRequestObserver)
  NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(PrototypeDocumentParser)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(PrototypeDocumentParser)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PrototypeDocumentParser)

NS_IMPL_CYCLE_COLLECTION(PrototypeDocumentParser, mDocumentURI, mOriginalSink,
                         mDocument, mStreamListener, mCurrentPrototype)

NS_IMETHODIMP_(void)
PrototypeDocumentParser::SetContentSink(nsIContentSink* aSink) {
  MOZ_ASSERT(aSink, "sink cannot be null!");
  mOriginalSink = static_cast<PrototypeDocumentContentSink*>(aSink);
  MOZ_ASSERT(mOriginalSink);

  aSink->SetParser(this);
}

NS_IMETHODIMP_(nsIContentSink*)
PrototypeDocumentParser::GetContentSink() { return mOriginalSink; }

nsIStreamListener* PrototypeDocumentParser::GetStreamListener() { return this; }

NS_IMETHODIMP_(bool)
PrototypeDocumentParser::IsComplete() { return mIsComplete; }

NS_IMETHODIMP
PrototypeDocumentParser::Parse(nsIURI* aURL, nsIRequestObserver* aListener,
                               void* aKey, nsDTDMode aMode) {
  // Look in the chrome cache: we've got this puppy loaded
  // already.
  nsXULPrototypeDocument* proto =
      IsChromeURI(mDocumentURI)
          ? nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI)
          : nullptr;

  // We don't abort on failure here because there are too many valid
  // cases that can return failure, and the null-ness of |proto| is enough
  // to trigger the fail-safe parse-from-disk solution. Example failure cases
  // (for reference) include:
  //
  // NS_ERROR_NOT_AVAILABLE: the URI cannot be found in the startup cache,
  //                         parse from disk
  // other: the startup cache file could not be found, probably
  //        due to being accessed before a profile has been selected (e.g.
  //        loading chrome for the profile manager itself). This must be
  //        parsed from disk.
  nsresult rv;
  if (proto) {
    mCurrentPrototype = proto;

    // Set up the right principal on the document.
    mDocument->SetPrincipals(proto->DocumentPrincipal(),
                             proto->DocumentPrincipal());
  } else {
    // It's just a vanilla document load. Create a parser to deal
    // with the stream n' stuff.

    nsCOMPtr<nsIParser> parser;
    // Get the document's principal
    nsCOMPtr<nsIPrincipal> principal = mDocument->NodePrincipal();
    rv =
        PrepareToLoadPrototype(mDocumentURI, principal, getter_AddRefs(parser));
    if (NS_FAILED(rv)) return rv;

    nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv);
    NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener");
    if (NS_FAILED(rv)) return rv;

    mStreamListener = listener;

    parser->Parse(mDocumentURI);
  }

  // If we're racing with another document to load proto, wait till the
  // load has finished loading before trying build the document.
  // Either the nsXULContentSink finishing to load the XML or
  // the nsXULPrototypeDocument completing deserialization will trigger the
  // OnPrototypeLoadDone callback.
  // If the prototype is already loaded, OnPrototypeLoadDone will be called
  // in OnStopRequest.
  RefPtr<PrototypeDocumentParser> self = this;
  rv = mCurrentPrototype->AwaitLoadDone(
      [self]() { self->OnPrototypeLoadDone(); }, &mPrototypeAlreadyLoaded);
  if (NS_FAILED(rv)) return rv;

  return NS_OK;
}

NS_IMETHODIMP
PrototypeDocumentParser::OnStartRequest(nsIRequest* request) {
  if (mStreamListener) {
    return mStreamListener->OnStartRequest(request);
  }
  // There's already a prototype cached, so return cached here so the original
  // request will be aborted. Either OnStopRequest or the prototype load
  // finishing will notify the content sink that we're done loading the
  // prototype.
  return NS_ERROR_PARSED_DATA_CACHED;
}

NS_IMETHODIMP
PrototypeDocumentParser::OnStopRequest(nsIRequest* request, nsresult aStatus) {
  if (mStreamListener) {
    return mStreamListener->OnStopRequest(request, aStatus);
  }
  if (mPrototypeAlreadyLoaded) {
    return this->OnPrototypeLoadDone();
  }
  // The prototype will handle calling OnPrototypeLoadDone when it is ready.
  return NS_OK;
}

NS_IMETHODIMP
PrototypeDocumentParser::OnDataAvailable(nsIRequest* request,
                                         nsIInputStream* aInStr,
                                         uint64_t aSourceOffset,
                                         uint32_t aCount) {
  if (mStreamListener) {
    return mStreamListener->OnDataAvailable(request, aInStr, aSourceOffset,
                                            aCount);
  }
  MOZ_ASSERT_UNREACHABLE("Cached prototype doesn't receive data");
  return NS_ERROR_UNEXPECTED;
}

nsresult PrototypeDocumentParser::OnPrototypeLoadDone() {
  MOZ_ASSERT(!mIsComplete, "Should not be called more than once.");
  mIsComplete = true;

  return mOriginalSink->OnPrototypeLoadDone(mCurrentPrototype);
}

nsresult PrototypeDocumentParser::PrepareToLoadPrototype(
    nsIURI* aURI, nsIPrincipal* aDocumentPrincipal, nsIParser** aResult) {
  nsresult rv;

  // Create a new prototype document.
  rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype));
  if (NS_FAILED(rv)) return rv;

  rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal);
  if (NS_FAILED(rv)) {
    mCurrentPrototype = nullptr;
    return rv;
  }

  // Store the new prototype right away so if there are multiple requests
  // for the same document they all get the same prototype.
  if (IsChromeURI(mDocumentURI) &&
      nsXULPrototypeCache::GetInstance()->IsEnabled()) {
    nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
  }

  mDocument->SetPrincipals(aDocumentPrincipal, aDocumentPrincipal);

  // Create a XUL content sink, a parser, and kick off a load for
  // the document.
  RefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl();

  rv = sink->Init(mDocument, mCurrentPrototype);
  NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
  NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser");
  if (NS_FAILED(rv)) return rv;

  parser->SetCommand(eViewNormal);

  parser->SetDocumentCharset(UTF_8_ENCODING, kCharsetFromDocTypeDefault);
  parser->SetContentSink(sink);  // grabs a reference to the parser

  parser.forget(aResult);
  return NS_OK;
}

}  // namespace parser
}  // namespace mozilla