PrototypeDocumentParser.cpp (7584B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 6 7 #include "PrototypeDocumentParser.h" 8 9 #include "nsXULPrototypeCache.h" 10 #include "nsXULContentSink.h" 11 #include "nsXULPrototypeDocument.h" 12 #include "mozilla/Encoding.h" 13 #include "nsCharsetSource.h" 14 #include "nsParser.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/PrototypeDocumentContentSink.h" 17 18 using namespace mozilla::dom; 19 20 namespace mozilla { 21 namespace parser { 22 23 PrototypeDocumentParser::PrototypeDocumentParser(nsIURI* aDocumentURI, 24 dom::Document* aDocument) 25 : mDocumentURI(aDocumentURI), 26 mDocument(aDocument), 27 mPrototypeAlreadyLoaded(false), 28 mIsComplete(false) {} 29 30 PrototypeDocumentParser::~PrototypeDocumentParser() {} 31 32 NS_INTERFACE_TABLE_HEAD(PrototypeDocumentParser) 33 NS_INTERFACE_TABLE(PrototypeDocumentParser, nsIParser, nsIStreamListener, 34 nsIRequestObserver) 35 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(PrototypeDocumentParser) 36 NS_INTERFACE_MAP_END 37 38 NS_IMPL_CYCLE_COLLECTING_ADDREF(PrototypeDocumentParser) 39 NS_IMPL_CYCLE_COLLECTING_RELEASE(PrototypeDocumentParser) 40 41 NS_IMPL_CYCLE_COLLECTION(PrototypeDocumentParser, mDocumentURI, mOriginalSink, 42 mDocument, mStreamListener, mCurrentPrototype) 43 44 NS_IMETHODIMP_(void) 45 PrototypeDocumentParser::SetContentSink(nsIContentSink* aSink) { 46 MOZ_ASSERT(aSink, "sink cannot be null!"); 47 mOriginalSink = static_cast<PrototypeDocumentContentSink*>(aSink); 48 MOZ_ASSERT(mOriginalSink); 49 50 aSink->SetParser(this); 51 } 52 53 NS_IMETHODIMP_(nsIContentSink*) 54 PrototypeDocumentParser::GetContentSink() { return mOriginalSink; } 55 56 nsIStreamListener* PrototypeDocumentParser::GetStreamListener() { return this; } 57 58 NS_IMETHODIMP_(bool) 59 PrototypeDocumentParser::IsComplete() { return mIsComplete; } 60 61 NS_IMETHODIMP 62 PrototypeDocumentParser::Parse(nsIURI* aURL) { 63 // Look in the chrome cache: we've got this puppy loaded 64 // already. 65 nsXULPrototypeDocument* proto = 66 mDocumentURI->SchemeIs("chrome") 67 ? nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) 68 : nullptr; 69 70 // We don't abort on failure here because there are too many valid 71 // cases that can return failure, and the null-ness of |proto| is enough 72 // to trigger the fail-safe parse-from-disk solution. Example failure cases 73 // (for reference) include: 74 // 75 // NS_ERROR_NOT_AVAILABLE: the URI cannot be found in the startup cache, 76 // parse from disk 77 // other: the startup cache file could not be found, probably 78 // due to being accessed before a profile has been selected (e.g. 79 // loading chrome for the profile manager itself). This must be 80 // parsed from disk. 81 nsresult rv; 82 if (proto) { 83 mCurrentPrototype = proto; 84 85 // Set up the right principal on the document. 86 mDocument->SetPrincipals(proto->DocumentPrincipal(), 87 proto->DocumentPrincipal()); 88 } else { 89 // It's just a vanilla document load. Create a parser to deal 90 // with the stream n' stuff. 91 92 nsCOMPtr<nsIParser> parser; 93 // Get the document's principal 94 nsCOMPtr<nsIPrincipal> principal = mDocument->NodePrincipal(); 95 rv = 96 PrepareToLoadPrototype(mDocumentURI, principal, getter_AddRefs(parser)); 97 if (NS_FAILED(rv)) return rv; 98 99 nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv); 100 NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener"); 101 if (NS_FAILED(rv)) return rv; 102 103 mStreamListener = listener; 104 105 parser->Parse(mDocumentURI); 106 } 107 108 // If we're racing with another document to load proto, wait till the 109 // load has finished loading before trying build the document. 110 // Either the nsXULContentSink finishing to load the XML or 111 // the nsXULPrototypeDocument completing deserialization will trigger the 112 // OnPrototypeLoadDone callback. 113 // If the prototype is already loaded, OnPrototypeLoadDone will be called 114 // in OnStopRequest. 115 RefPtr<PrototypeDocumentParser> self = this; 116 rv = mCurrentPrototype->AwaitLoadDone( 117 [self]() { self->OnPrototypeLoadDone(); }, &mPrototypeAlreadyLoaded); 118 if (NS_FAILED(rv)) return rv; 119 120 return NS_OK; 121 } 122 123 NS_IMETHODIMP 124 PrototypeDocumentParser::OnStartRequest(nsIRequest* request) { 125 if (mStreamListener) { 126 return mStreamListener->OnStartRequest(request); 127 } 128 // There's already a prototype cached, so return cached here so the original 129 // request will be aborted. Either OnStopRequest or the prototype load 130 // finishing will notify the content sink that we're done loading the 131 // prototype. 132 return NS_ERROR_PARSED_DATA_CACHED; 133 } 134 135 NS_IMETHODIMP 136 PrototypeDocumentParser::OnStopRequest(nsIRequest* request, nsresult aStatus) { 137 if (mStreamListener) { 138 return mStreamListener->OnStopRequest(request, aStatus); 139 } 140 if (mPrototypeAlreadyLoaded) { 141 return this->OnPrototypeLoadDone(); 142 } 143 // The prototype will handle calling OnPrototypeLoadDone when it is ready. 144 return NS_OK; 145 } 146 147 NS_IMETHODIMP 148 PrototypeDocumentParser::OnDataAvailable(nsIRequest* request, 149 nsIInputStream* aInStr, 150 uint64_t aSourceOffset, 151 uint32_t aCount) { 152 if (mStreamListener) { 153 return mStreamListener->OnDataAvailable(request, aInStr, aSourceOffset, 154 aCount); 155 } 156 MOZ_ASSERT_UNREACHABLE("Cached prototype doesn't receive data"); 157 return NS_ERROR_UNEXPECTED; 158 } 159 160 nsresult PrototypeDocumentParser::OnPrototypeLoadDone() { 161 MOZ_ASSERT(!mIsComplete, "Should not be called more than once."); 162 mIsComplete = true; 163 164 RefPtr<PrototypeDocumentContentSink> sink = mOriginalSink; 165 RefPtr<nsXULPrototypeDocument> prototype = mCurrentPrototype; 166 return sink->OnPrototypeLoadDone(prototype); 167 } 168 169 nsresult PrototypeDocumentParser::PrepareToLoadPrototype( 170 nsIURI* aURI, nsIPrincipal* aDocumentPrincipal, nsIParser** aResult) { 171 nsresult rv; 172 173 // Create a new prototype document. 174 rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype)); 175 if (NS_FAILED(rv)) return rv; 176 177 rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal); 178 if (NS_FAILED(rv)) { 179 mCurrentPrototype = nullptr; 180 return rv; 181 } 182 183 // Store the new prototype right away so if there are multiple requests 184 // for the same document they all get the same prototype. 185 if (mDocumentURI->SchemeIs("chrome") && 186 nsXULPrototypeCache::GetInstance()->IsEnabled()) { 187 nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype); 188 } 189 190 mDocument->SetPrincipals(aDocumentPrincipal, aDocumentPrincipal); 191 192 // Create a XUL content sink, a parser, and kick off a load for 193 // the document. 194 RefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl(); 195 196 rv = sink->Init(mDocument, mCurrentPrototype); 197 NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink"); 198 if (NS_FAILED(rv)) return rv; 199 200 nsCOMPtr<nsIParser> parser = new nsParser(); 201 202 parser->SetCommand(eViewNormal); 203 204 parser->SetDocumentCharset(UTF_8_ENCODING, kCharsetFromDocTypeDefault); 205 parser->SetContentSink(sink); // grabs a reference to the parser 206 207 parser.forget(aResult); 208 return NS_OK; 209 } 210 211 } // namespace parser 212 } // namespace mozilla