Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsXULPrototypeDocument.h"
#include "mozilla/Encoding.h"
#include "nsCharsetSource.h"
#include "nsParser.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/URL.h"
#include "mozilla/dom/PrototypeDocumentContentSink.h"
using namespace mozilla::dom;
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) {
// 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;
RefPtr<PrototypeDocumentContentSink> sink = mOriginalSink;
RefPtr<nsXULPrototypeDocument> prototype = mCurrentPrototype;
return sink->OnPrototypeLoadDone(prototype);
}
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 = new nsParser();
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