/* -*- 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(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 parser; // Get the document's principal nsCOMPtr principal = mDocument->NodePrincipal(); rv = PrepareToLoadPrototype(mDocumentURI, principal, getter_AddRefs(parser)); if (NS_FAILED(rv)) return rv; nsCOMPtr 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 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 sink = mOriginalSink; RefPtr 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 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 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