/src/mozilla-central/parser/htmlparser/nsParser.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set sw=2 ts=2 et tw=79: */ |
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 http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsAtom.h" |
8 | | #include "nsParser.h" |
9 | | #include "nsString.h" |
10 | | #include "nsCRT.h" |
11 | | #include "nsScanner.h" |
12 | | #include "plstr.h" |
13 | | #include "nsIStringStream.h" |
14 | | #include "nsIChannel.h" |
15 | | #include "nsICachingChannel.h" |
16 | | #include "nsIInputStream.h" |
17 | | #include "CNavDTD.h" |
18 | | #include "prenv.h" |
19 | | #include "prlock.h" |
20 | | #include "prcvar.h" |
21 | | #include "nsParserCIID.h" |
22 | | #include "nsReadableUtils.h" |
23 | | #include "nsCOMPtr.h" |
24 | | #include "nsExpatDriver.h" |
25 | | #include "nsIServiceManager.h" |
26 | | #include "nsICategoryManager.h" |
27 | | #include "nsISupportsPrimitives.h" |
28 | | #include "nsIFragmentContentSink.h" |
29 | | #include "nsStreamUtils.h" |
30 | | #include "nsHTMLTokenizer.h" |
31 | | #include "nsDataHashtable.h" |
32 | | #include "nsXPCOMCIDInternal.h" |
33 | | #include "nsMimeTypes.h" |
34 | | #include "mozilla/CondVar.h" |
35 | | #include "mozilla/Mutex.h" |
36 | | #include "nsCharsetSource.h" |
37 | | #include "nsThreadUtils.h" |
38 | | #include "nsIHTMLContentSink.h" |
39 | | |
40 | | #include "mozilla/BinarySearch.h" |
41 | | #include "mozilla/dom/ScriptLoader.h" |
42 | | #include "mozilla/Encoding.h" |
43 | | |
44 | | using namespace mozilla; |
45 | | |
46 | 0 | #define NS_PARSER_FLAG_OBSERVERS_ENABLED 0x00000004 |
47 | 0 | #define NS_PARSER_FLAG_PENDING_CONTINUE_EVENT 0x00000008 |
48 | 0 | #define NS_PARSER_FLAG_FLUSH_TOKENS 0x00000020 |
49 | 0 | #define NS_PARSER_FLAG_CAN_TOKENIZE 0x00000040 |
50 | | |
51 | | //-------------- Begin ParseContinue Event Definition ------------------------ |
52 | | /* |
53 | | The parser can be explicitly interrupted by passing a return value of |
54 | | NS_ERROR_HTMLPARSER_INTERRUPTED from BuildModel on the DTD. This will cause |
55 | | the parser to stop processing and allow the application to return to the event |
56 | | loop. The data which was left at the time of interruption will be processed |
57 | | the next time OnDataAvailable is called. If the parser has received its final |
58 | | chunk of data then OnDataAvailable will no longer be called by the networking |
59 | | module, so the parser will schedule a nsParserContinueEvent which will call |
60 | | the parser to process the remaining data after returning to the event loop. |
61 | | If the parser is interrupted while processing the remaining data it will |
62 | | schedule another ParseContinueEvent. The processing of data followed by |
63 | | scheduling of the continue events will proceed until either: |
64 | | |
65 | | 1) All of the remaining data can be processed without interrupting |
66 | | 2) The parser has been cancelled. |
67 | | |
68 | | |
69 | | This capability is currently used in CNavDTD and nsHTMLContentSink. The |
70 | | nsHTMLContentSink is notified by CNavDTD when a chunk of tokens is going to be |
71 | | processed and when each token is processed. The nsHTML content sink records |
72 | | the time when the chunk has started processing and will return |
73 | | NS_ERROR_HTMLPARSER_INTERRUPTED if the token processing time has exceeded a |
74 | | threshold called max tokenizing processing time. This allows the content sink |
75 | | to limit how much data is processed in a single chunk which in turn gates how |
76 | | much time is spent away from the event loop. Processing smaller chunks of data |
77 | | also reduces the time spent in subsequent reflows. |
78 | | |
79 | | This capability is most apparent when loading large documents. If the maximum |
80 | | token processing time is set small enough the application will remain |
81 | | responsive during document load. |
82 | | |
83 | | A side-effect of this capability is that document load is not complete when |
84 | | the last chunk of data is passed to OnDataAvailable since the parser may have |
85 | | been interrupted when the last chunk of data arrived. The document is complete |
86 | | when all of the document has been tokenized and there aren't any pending |
87 | | nsParserContinueEvents. This can cause problems if the application assumes |
88 | | that it can monitor the load requests to determine when the document load has |
89 | | been completed. This is what happens in Mozilla. The document is considered |
90 | | completely loaded when all of the load requests have been satisfied. To delay |
91 | | the document load until all of the parsing has been completed the |
92 | | nsHTMLContentSink adds a dummy parser load request which is not removed until |
93 | | the nsHTMLContentSink's DidBuildModel is called. The CNavDTD will not call |
94 | | DidBuildModel until the final chunk of data has been passed to the parser |
95 | | through the OnDataAvailable and there aren't any pending |
96 | | nsParserContineEvents. |
97 | | |
98 | | Currently the parser is ignores requests to be interrupted during the |
99 | | processing of script. This is because a document.write followed by JavaScript |
100 | | calls to manipulate the DOM may fail if the parser was interrupted during the |
101 | | document.write. |
102 | | |
103 | | For more details @see bugzilla bug 76722 |
104 | | */ |
105 | | |
106 | | |
107 | | class nsParserContinueEvent : public Runnable |
108 | | { |
109 | | public: |
110 | | RefPtr<nsParser> mParser; |
111 | | |
112 | | explicit nsParserContinueEvent(nsParser* aParser) |
113 | | : mozilla::Runnable("nsParserContinueEvent") |
114 | | , mParser(aParser) |
115 | 0 | {} |
116 | | |
117 | | NS_IMETHOD Run() override |
118 | 0 | { |
119 | 0 | mParser->HandleParserContinueEvent(this); |
120 | 0 | return NS_OK; |
121 | 0 | } |
122 | | }; |
123 | | |
124 | | //-------------- End ParseContinue Event Definition ------------------------ |
125 | | |
126 | | /** |
127 | | * default constructor |
128 | | */ |
129 | | nsParser::nsParser() |
130 | | : mParserContext(nullptr) |
131 | | , mCharset(WINDOWS_1252_ENCODING) |
132 | 0 | { |
133 | 0 | Initialize(true); |
134 | 0 | } |
135 | | |
136 | | nsParser::~nsParser() |
137 | 0 | { |
138 | 0 | Cleanup(); |
139 | 0 | } |
140 | | |
141 | | void |
142 | | nsParser::Initialize(bool aConstructor) |
143 | 0 | { |
144 | 0 | if (aConstructor) { |
145 | 0 | // Raw pointer |
146 | 0 | mParserContext = 0; |
147 | 0 | } |
148 | 0 | else { |
149 | 0 | // nsCOMPtrs |
150 | 0 | mObserver = nullptr; |
151 | 0 | mUnusedInput.Truncate(); |
152 | 0 | } |
153 | 0 |
|
154 | 0 | mContinueEvent = nullptr; |
155 | 0 | mCharsetSource = kCharsetUninitialized; |
156 | 0 | mCharset = WINDOWS_1252_ENCODING; |
157 | 0 | mInternalState = NS_OK; |
158 | 0 | mStreamStatus = NS_OK; |
159 | 0 | mCommand = eViewNormal; |
160 | 0 | mBlocked = 0; |
161 | 0 | mFlags = NS_PARSER_FLAG_OBSERVERS_ENABLED | |
162 | 0 | NS_PARSER_FLAG_CAN_TOKENIZE; |
163 | 0 |
|
164 | 0 | mProcessingNetworkData = false; |
165 | 0 | mIsAboutBlank = false; |
166 | 0 | } |
167 | | |
168 | | void |
169 | | nsParser::Cleanup() |
170 | 0 | { |
171 | | #ifdef DEBUG |
172 | | if (mParserContext && mParserContext->mPrevContext) { |
173 | | NS_WARNING("Extra parser contexts still on the parser stack"); |
174 | | } |
175 | | #endif |
176 | |
|
177 | 0 | while (mParserContext) { |
178 | 0 | CParserContext *pc = mParserContext->mPrevContext; |
179 | 0 | delete mParserContext; |
180 | 0 | mParserContext = pc; |
181 | 0 | } |
182 | 0 |
|
183 | 0 | // It should not be possible for this flag to be set when we are getting |
184 | 0 | // destroyed since this flag implies a pending nsParserContinueEvent, which |
185 | 0 | // has an owning reference to |this|. |
186 | 0 | NS_ASSERTION(!(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT), "bad"); |
187 | 0 | } |
188 | | |
189 | | NS_IMPL_CYCLE_COLLECTION_CLASS(nsParser) |
190 | | |
191 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsParser) |
192 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mDTD) |
193 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mSink) |
194 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mObserver) |
195 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
196 | | |
197 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsParser) |
198 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDTD) |
199 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSink) |
200 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObserver) |
201 | 0 | CParserContext *pc = tmp->mParserContext; |
202 | 0 | while (pc) { |
203 | 0 | cb.NoteXPCOMChild(pc->mTokenizer); |
204 | 0 | pc = pc->mPrevContext; |
205 | 0 | } |
206 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
207 | | |
208 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(nsParser) |
209 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(nsParser) |
210 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsParser) |
211 | 0 | NS_INTERFACE_MAP_ENTRY(nsIStreamListener) |
212 | 0 | NS_INTERFACE_MAP_ENTRY(nsIParser) |
213 | 0 | NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) |
214 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
215 | 0 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParser) |
216 | 0 | NS_INTERFACE_MAP_END |
217 | | |
218 | | // The parser continue event is posted only if |
219 | | // all of the data to parse has been passed to ::OnDataAvailable |
220 | | // and the parser has been interrupted by the content sink |
221 | | // because the processing of tokens took too long. |
222 | | |
223 | | nsresult |
224 | | nsParser::PostContinueEvent() |
225 | 0 | { |
226 | 0 | if (!(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT)) { |
227 | 0 | // If this flag isn't set, then there shouldn't be a live continue event! |
228 | 0 | NS_ASSERTION(!mContinueEvent, "bad"); |
229 | 0 |
|
230 | 0 | // This creates a reference cycle between this and the event that is |
231 | 0 | // broken when the event fires. |
232 | 0 | nsCOMPtr<nsIRunnable> event = new nsParserContinueEvent(this); |
233 | 0 | if (NS_FAILED(NS_DispatchToCurrentThread(event))) { |
234 | 0 | NS_WARNING("failed to dispatch parser continuation event"); |
235 | 0 | } else { |
236 | 0 | mFlags |= NS_PARSER_FLAG_PENDING_CONTINUE_EVENT; |
237 | 0 | mContinueEvent = event; |
238 | 0 | } |
239 | 0 | } |
240 | 0 | return NS_OK; |
241 | 0 | } |
242 | | |
243 | | NS_IMETHODIMP_(void) |
244 | | nsParser::GetCommand(nsCString& aCommand) |
245 | 0 | { |
246 | 0 | aCommand = mCommandStr; |
247 | 0 | } |
248 | | |
249 | | /** |
250 | | * Call this method once you've created a parser, and want to instruct it |
251 | | * about the command which caused the parser to be constructed. For example, |
252 | | * this allows us to select a DTD which can do, say, view-source. |
253 | | * |
254 | | * @param aCommand the command string to set |
255 | | */ |
256 | | NS_IMETHODIMP_(void) |
257 | | nsParser::SetCommand(const char* aCommand) |
258 | 0 | { |
259 | 0 | mCommandStr.Assign(aCommand); |
260 | 0 | if (mCommandStr.EqualsLiteral("view-source")) { |
261 | 0 | mCommand = eViewSource; |
262 | 0 | } else if (mCommandStr.EqualsLiteral("view-fragment")) { |
263 | 0 | mCommand = eViewFragment; |
264 | 0 | } else { |
265 | 0 | mCommand = eViewNormal; |
266 | 0 | } |
267 | 0 | } |
268 | | |
269 | | /** |
270 | | * Call this method once you've created a parser, and want to instruct it |
271 | | * about the command which caused the parser to be constructed. For example, |
272 | | * this allows us to select a DTD which can do, say, view-source. |
273 | | * |
274 | | * @param aParserCommand the command to set |
275 | | */ |
276 | | NS_IMETHODIMP_(void) |
277 | | nsParser::SetCommand(eParserCommands aParserCommand) |
278 | 0 | { |
279 | 0 | mCommand = aParserCommand; |
280 | 0 | } |
281 | | |
282 | | /** |
283 | | * Call this method once you've created a parser, and want to instruct it |
284 | | * about what charset to load |
285 | | * |
286 | | * @param aCharset- the charset of a document |
287 | | * @param aCharsetSource- the source of the charset |
288 | | */ |
289 | | void |
290 | | nsParser::SetDocumentCharset(NotNull<const Encoding*> aCharset, |
291 | | int32_t aCharsetSource) |
292 | 0 | { |
293 | 0 | mCharset = aCharset; |
294 | 0 | mCharsetSource = aCharsetSource; |
295 | 0 | if (mParserContext && mParserContext->mScanner) { |
296 | 0 | mParserContext->mScanner->SetDocumentCharset(aCharset, aCharsetSource); |
297 | 0 | } |
298 | 0 | } |
299 | | |
300 | | void |
301 | | nsParser::SetSinkCharset(NotNull<const Encoding*> aCharset) |
302 | 0 | { |
303 | 0 | if (mSink) { |
304 | 0 | mSink->SetDocumentCharset(aCharset); |
305 | 0 | } |
306 | 0 | } |
307 | | |
308 | | /** |
309 | | * This method gets called in order to set the content |
310 | | * sink for this parser to dump nodes to. |
311 | | * |
312 | | * @param nsIContentSink interface for node receiver |
313 | | */ |
314 | | NS_IMETHODIMP_(void) |
315 | | nsParser::SetContentSink(nsIContentSink* aSink) |
316 | 0 | { |
317 | 0 | MOZ_ASSERT(aSink, "sink cannot be null!"); |
318 | 0 | mSink = aSink; |
319 | 0 |
|
320 | 0 | if (mSink) { |
321 | 0 | mSink->SetParser(this); |
322 | 0 | nsCOMPtr<nsIHTMLContentSink> htmlSink = do_QueryInterface(mSink); |
323 | 0 | if (htmlSink) { |
324 | 0 | mIsAboutBlank = true; |
325 | 0 | } |
326 | 0 | } |
327 | 0 | } |
328 | | |
329 | | /** |
330 | | * retrieve the sink set into the parser |
331 | | * @return current sink |
332 | | */ |
333 | | NS_IMETHODIMP_(nsIContentSink*) |
334 | | nsParser::GetContentSink() |
335 | 0 | { |
336 | 0 | return mSink; |
337 | 0 | } |
338 | | |
339 | | static nsIDTD* |
340 | | FindSuitableDTD(CParserContext& aParserContext) |
341 | 0 | { |
342 | 0 | // We always find a DTD. |
343 | 0 | aParserContext.mAutoDetectStatus = ePrimaryDetect; |
344 | 0 |
|
345 | 0 | // Quick check for view source. |
346 | 0 | MOZ_ASSERT(aParserContext.mParserCommand != eViewSource, |
347 | 0 | "The old parser is not supposed to be used for View Source " |
348 | 0 | "anymore."); |
349 | 0 |
|
350 | 0 | // Now see if we're parsing HTML (which, as far as we're concerned, simply |
351 | 0 | // means "not XML"). |
352 | 0 | if (aParserContext.mDocType != eXML) { |
353 | 0 | return new CNavDTD(); |
354 | 0 | } |
355 | 0 | |
356 | 0 | // If we're here, then we'd better be parsing XML. |
357 | 0 | NS_ASSERTION(aParserContext.mDocType == eXML, "What are you trying to send me, here?"); |
358 | 0 | return new nsExpatDriver(); |
359 | 0 | } |
360 | | |
361 | | NS_IMETHODIMP |
362 | | nsParser::CancelParsingEvents() |
363 | 0 | { |
364 | 0 | if (mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT) { |
365 | 0 | NS_ASSERTION(mContinueEvent, "mContinueEvent is null"); |
366 | 0 | // Revoke the pending continue parsing event |
367 | 0 | mContinueEvent = nullptr; |
368 | 0 | mFlags &= ~NS_PARSER_FLAG_PENDING_CONTINUE_EVENT; |
369 | 0 | } |
370 | 0 | return NS_OK; |
371 | 0 | } |
372 | | |
373 | | //////////////////////////////////////////////////////////////////////// |
374 | | |
375 | | /** |
376 | | * Evalutes EXPR1 and EXPR2 exactly once each, in that order. Stores the value |
377 | | * of EXPR2 in RV is EXPR2 fails, otherwise RV contains the result of EXPR1 |
378 | | * (which could be success or failure). |
379 | | * |
380 | | * To understand the motivation for this construct, consider these example |
381 | | * methods: |
382 | | * |
383 | | * nsresult nsSomething::DoThatThing(nsIWhatever* obj) { |
384 | | * nsresult rv = NS_OK; |
385 | | * ... |
386 | | * return obj->DoThatThing(); |
387 | | * NS_ENSURE_SUCCESS(rv, rv); |
388 | | * ... |
389 | | * return rv; |
390 | | * } |
391 | | * |
392 | | * void nsCaller::MakeThingsHappen() { |
393 | | * return mSomething->DoThatThing(mWhatever); |
394 | | * } |
395 | | * |
396 | | * Suppose, for whatever reason*, we want to shift responsibility for calling |
397 | | * mWhatever->DoThatThing() from nsSomething::DoThatThing up to |
398 | | * nsCaller::MakeThingsHappen. We might rewrite the two methods as follows: |
399 | | * |
400 | | * nsresult nsSomething::DoThatThing() { |
401 | | * nsresult rv = NS_OK; |
402 | | * ... |
403 | | * ... |
404 | | * return rv; |
405 | | * } |
406 | | * |
407 | | * void nsCaller::MakeThingsHappen() { |
408 | | * nsresult rv; |
409 | | * PREFER_LATTER_ERROR_CODE(mSomething->DoThatThing(), |
410 | | * mWhatever->DoThatThing(), |
411 | | * rv); |
412 | | * return rv; |
413 | | * } |
414 | | * |
415 | | * *Possible reasons include: nsCaller doesn't want to give mSomething access |
416 | | * to mWhatever, nsCaller wants to guarantee that mWhatever->DoThatThing() will |
417 | | * be called regardless of how nsSomething::DoThatThing behaves, &c. |
418 | | */ |
419 | | #define PREFER_LATTER_ERROR_CODE(EXPR1, EXPR2, RV) { \ |
420 | | nsresult RV##__temp = EXPR1; \ |
421 | | RV = EXPR2; \ |
422 | | if (NS_FAILED(RV)) { \ |
423 | | RV = RV##__temp; \ |
424 | | } \ |
425 | | } |
426 | | |
427 | | /** |
428 | | * This gets called just prior to the model actually |
429 | | * being constructed. It's important to make this the |
430 | | * last thing that happens right before parsing, so we |
431 | | * can delay until the last moment the resolution of |
432 | | * which DTD to use (unless of course we're assigned one). |
433 | | */ |
434 | | nsresult |
435 | | nsParser::WillBuildModel(nsString& aFilename) |
436 | 0 | { |
437 | 0 | if (!mParserContext) |
438 | 0 | return NS_ERROR_HTMLPARSER_INVALIDPARSERCONTEXT; |
439 | 0 | |
440 | 0 | if (eUnknownDetect != mParserContext->mAutoDetectStatus) |
441 | 0 | return NS_OK; |
442 | 0 | |
443 | 0 | if (eDTDMode_unknown == mParserContext->mDTDMode || |
444 | 0 | eDTDMode_autodetect == mParserContext->mDTDMode) { |
445 | 0 | if (mIsAboutBlank) { |
446 | 0 | mParserContext->mDTDMode = eDTDMode_quirks; |
447 | 0 | mParserContext->mDocType = eHTML_Quirks; |
448 | 0 | } else { |
449 | 0 | mParserContext->mDTDMode = eDTDMode_full_standards; |
450 | 0 | mParserContext->mDocType = eXML; |
451 | 0 | } |
452 | 0 | } // else XML fragment with nested parser context |
453 | 0 |
|
454 | 0 | NS_ASSERTION(!mDTD || !mParserContext->mPrevContext, |
455 | 0 | "Clobbering DTD for non-root parser context!"); |
456 | 0 | mDTD = FindSuitableDTD(*mParserContext); |
457 | 0 | NS_ENSURE_TRUE(mDTD, NS_ERROR_OUT_OF_MEMORY); |
458 | 0 |
|
459 | 0 | nsITokenizer* tokenizer; |
460 | 0 | nsresult rv = mParserContext->GetTokenizer(mDTD, mSink, tokenizer); |
461 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
462 | 0 |
|
463 | 0 | rv = mDTD->WillBuildModel(*mParserContext, tokenizer, mSink); |
464 | 0 | nsresult sinkResult = mSink->WillBuildModel(mDTD->GetMode()); |
465 | 0 | // nsIDTD::WillBuildModel used to be responsible for calling |
466 | 0 | // nsIContentSink::WillBuildModel, but that obligation isn't expressible |
467 | 0 | // in the nsIDTD interface itself, so it's sounder and simpler to give that |
468 | 0 | // responsibility back to the parser. The former behavior of the DTD was to |
469 | 0 | // NS_ENSURE_SUCCESS the sink WillBuildModel call, so if the sink returns |
470 | 0 | // failure we should use sinkResult instead of rv, to preserve the old error |
471 | 0 | // handling behavior of the DTD: |
472 | 0 | return NS_FAILED(sinkResult) ? sinkResult : rv; |
473 | 0 | } |
474 | | |
475 | | /** |
476 | | * This gets called when the parser is done with its input. |
477 | | * Note that the parser may have been called recursively, so we |
478 | | * have to check for a prev. context before closing out the DTD/sink. |
479 | | */ |
480 | | nsresult |
481 | | nsParser::DidBuildModel(nsresult anErrorCode) |
482 | 0 | { |
483 | 0 | nsresult result = anErrorCode; |
484 | 0 |
|
485 | 0 | if (IsComplete()) { |
486 | 0 | if (mParserContext && !mParserContext->mPrevContext) { |
487 | 0 | // Let sink know if we're about to end load because we've been terminated. |
488 | 0 | // In that case we don't want it to run deferred scripts. |
489 | 0 | bool terminated = mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING; |
490 | 0 | if (mDTD && mSink) { |
491 | 0 | nsresult dtdResult = mDTD->DidBuildModel(anErrorCode), |
492 | 0 | sinkResult = mSink->DidBuildModel(terminated); |
493 | 0 | // nsIDTD::DidBuildModel used to be responsible for calling |
494 | 0 | // nsIContentSink::DidBuildModel, but that obligation isn't expressible |
495 | 0 | // in the nsIDTD interface itself, so it's sounder and simpler to give |
496 | 0 | // that responsibility back to the parser. The former behavior of the |
497 | 0 | // DTD was to NS_ENSURE_SUCCESS the sink DidBuildModel call, so if the |
498 | 0 | // sink returns failure we should use sinkResult instead of dtdResult, |
499 | 0 | // to preserve the old error handling behavior of the DTD: |
500 | 0 | result = NS_FAILED(sinkResult) ? sinkResult : dtdResult; |
501 | 0 | } |
502 | 0 |
|
503 | 0 | //Ref. to bug 61462. |
504 | 0 | mParserContext->mRequest = nullptr; |
505 | 0 | } |
506 | 0 | } |
507 | 0 |
|
508 | 0 | return result; |
509 | 0 | } |
510 | | |
511 | | /** |
512 | | * This method adds a new parser context to the list, |
513 | | * pushing the current one to the next position. |
514 | | * |
515 | | * @param ptr to new context |
516 | | */ |
517 | | void |
518 | | nsParser::PushContext(CParserContext& aContext) |
519 | 0 | { |
520 | 0 | NS_ASSERTION(aContext.mPrevContext == mParserContext, |
521 | 0 | "Trying to push a context whose previous context differs from " |
522 | 0 | "the current parser context."); |
523 | 0 | mParserContext = &aContext; |
524 | 0 | } |
525 | | |
526 | | /** |
527 | | * This method pops the topmost context off the stack, |
528 | | * returning it to the user. The next context (if any) |
529 | | * becomes the current context. |
530 | | * @update gess7/22/98 |
531 | | * @return prev. context |
532 | | */ |
533 | | CParserContext* |
534 | | nsParser::PopContext() |
535 | 0 | { |
536 | 0 | CParserContext* oldContext = mParserContext; |
537 | 0 | if (oldContext) { |
538 | 0 | mParserContext = oldContext->mPrevContext; |
539 | 0 | if (mParserContext) { |
540 | 0 | // If the old context was blocked, propagate the blocked state |
541 | 0 | // back to the new one. Also, propagate the stream listener state |
542 | 0 | // but don't override onStop state to guarantee the call to DidBuildModel(). |
543 | 0 | if (mParserContext->mStreamListenerState != eOnStop) { |
544 | 0 | mParserContext->mStreamListenerState = oldContext->mStreamListenerState; |
545 | 0 | } |
546 | 0 | } |
547 | 0 | } |
548 | 0 | return oldContext; |
549 | 0 | } |
550 | | |
551 | | /** |
552 | | * Call this when you want control whether or not the parser will parse |
553 | | * and tokenize input (TRUE), or whether it just caches input to be |
554 | | * parsed later (FALSE). |
555 | | * |
556 | | * @param aState determines whether we parse/tokenize or just cache. |
557 | | * @return current state |
558 | | */ |
559 | | void |
560 | | nsParser::SetUnusedInput(nsString& aBuffer) |
561 | 0 | { |
562 | 0 | mUnusedInput = aBuffer; |
563 | 0 | } |
564 | | |
565 | | /** |
566 | | * Call this when you want to *force* the parser to terminate the |
567 | | * parsing process altogether. This is binary -- so once you terminate |
568 | | * you can't resume without restarting altogether. |
569 | | */ |
570 | | NS_IMETHODIMP |
571 | | nsParser::Terminate(void) |
572 | 0 | { |
573 | 0 | // We should only call DidBuildModel once, so don't do anything if this is |
574 | 0 | // the second time that Terminate has been called. |
575 | 0 | if (mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING) { |
576 | 0 | return NS_OK; |
577 | 0 | } |
578 | 0 | |
579 | 0 | nsresult result = NS_OK; |
580 | 0 | // XXX - [ until we figure out a way to break parser-sink circularity ] |
581 | 0 | // Hack - Hold a reference until we are completely done... |
582 | 0 | nsCOMPtr<nsIParser> kungFuDeathGrip(this); |
583 | 0 | mInternalState = result = NS_ERROR_HTMLPARSER_STOPPARSING; |
584 | 0 |
|
585 | 0 | // CancelParsingEvents must be called to avoid leaking the nsParser object |
586 | 0 | // @see bug 108049 |
587 | 0 | // If NS_PARSER_FLAG_PENDING_CONTINUE_EVENT is set then CancelParsingEvents |
588 | 0 | // will reset it so DidBuildModel will call DidBuildModel on the DTD. Note: |
589 | 0 | // The IsComplete() call inside of DidBuildModel looks at the pendingContinueEvents flag. |
590 | 0 | CancelParsingEvents(); |
591 | 0 |
|
592 | 0 | // If we got interrupted in the middle of a document.write, then we might |
593 | 0 | // have more than one parser context on our parsercontext stack. This has |
594 | 0 | // the effect of making DidBuildModel a no-op, meaning that we never call |
595 | 0 | // our sink's DidBuildModel and break the reference cycle, causing a leak. |
596 | 0 | // Since we're getting terminated, we manually clean up our context stack. |
597 | 0 | while (mParserContext && mParserContext->mPrevContext) { |
598 | 0 | CParserContext *prev = mParserContext->mPrevContext; |
599 | 0 | delete mParserContext; |
600 | 0 | mParserContext = prev; |
601 | 0 | } |
602 | 0 |
|
603 | 0 | if (mDTD) { |
604 | 0 | mDTD->Terminate(); |
605 | 0 | DidBuildModel(result); |
606 | 0 | } else if (mSink) { |
607 | 0 | // We have no parser context or no DTD yet (so we got terminated before we |
608 | 0 | // got any data). Manually break the reference cycle with the sink. |
609 | 0 | result = mSink->DidBuildModel(true); |
610 | 0 | NS_ENSURE_SUCCESS(result, result); |
611 | 0 | } |
612 | 0 |
|
613 | 0 | return NS_OK; |
614 | 0 | } |
615 | | |
616 | | NS_IMETHODIMP |
617 | | nsParser::ContinueInterruptedParsing() |
618 | 0 | { |
619 | 0 | // If there are scripts executing, then the content sink is jumping the gun |
620 | 0 | // (probably due to a synchronous XMLHttpRequest) and will re-enable us |
621 | 0 | // later, see bug 460706. |
622 | 0 | if (!IsOkToProcessNetworkData()) { |
623 | 0 | return NS_OK; |
624 | 0 | } |
625 | 0 | |
626 | 0 | // If the stream has already finished, there's a good chance |
627 | 0 | // that we might start closing things down when the parser |
628 | 0 | // is reenabled. To make sure that we're not deleted across |
629 | 0 | // the reenabling process, hold a reference to ourselves. |
630 | 0 | nsresult result=NS_OK; |
631 | 0 | nsCOMPtr<nsIParser> kungFuDeathGrip(this); |
632 | 0 | nsCOMPtr<nsIContentSink> sinkDeathGrip(mSink); |
633 | 0 |
|
634 | | #ifdef DEBUG |
635 | | if (mBlocked) { |
636 | | NS_WARNING("Don't call ContinueInterruptedParsing on a blocked parser."); |
637 | | } |
638 | | #endif |
639 | |
|
640 | 0 | bool isFinalChunk = mParserContext && |
641 | 0 | mParserContext->mStreamListenerState == eOnStop; |
642 | 0 |
|
643 | 0 | mProcessingNetworkData = true; |
644 | 0 | if (sinkDeathGrip) { |
645 | 0 | sinkDeathGrip->WillParse(); |
646 | 0 | } |
647 | 0 | result = ResumeParse(true, isFinalChunk); // Ref. bug 57999 |
648 | 0 | mProcessingNetworkData = false; |
649 | 0 |
|
650 | 0 | if (result != NS_OK) { |
651 | 0 | result=mInternalState; |
652 | 0 | } |
653 | 0 |
|
654 | 0 | return result; |
655 | 0 | } |
656 | | |
657 | | /** |
658 | | * Stops parsing temporarily. That is, it will prevent the |
659 | | * parser from building up content model while scripts |
660 | | * are being loaded (either an external script from a web |
661 | | * page, or any number of extension content scripts). |
662 | | */ |
663 | | NS_IMETHODIMP_(void) |
664 | | nsParser::BlockParser() |
665 | 0 | { |
666 | 0 | mBlocked++; |
667 | 0 | } |
668 | | |
669 | | /** |
670 | | * Open up the parser for tokenization, building up content |
671 | | * model..etc. However, this method does not resume parsing |
672 | | * automatically. It's the callers' responsibility to restart |
673 | | * the parsing engine. |
674 | | */ |
675 | | NS_IMETHODIMP_(void) |
676 | | nsParser::UnblockParser() |
677 | 0 | { |
678 | 0 | MOZ_DIAGNOSTIC_ASSERT(mBlocked > 0); |
679 | 0 | if (MOZ_LIKELY(mBlocked > 0)) { |
680 | 0 | mBlocked--; |
681 | 0 | } |
682 | 0 | } |
683 | | |
684 | | NS_IMETHODIMP_(void) |
685 | | nsParser::ContinueInterruptedParsingAsync() |
686 | 0 | { |
687 | 0 | MOZ_ASSERT(mSink); |
688 | 0 | if (MOZ_LIKELY(mSink)) { |
689 | 0 | mSink->ContinueInterruptedParsingAsync(); |
690 | 0 | } |
691 | 0 | } |
692 | | |
693 | | /** |
694 | | * Call this to query whether the parser is enabled or not. |
695 | | */ |
696 | | NS_IMETHODIMP_(bool) |
697 | | nsParser::IsParserEnabled() |
698 | 0 | { |
699 | 0 | return !mBlocked; |
700 | 0 | } |
701 | | |
702 | | /** |
703 | | * Call this to query whether the parser thinks it's done with parsing. |
704 | | */ |
705 | | NS_IMETHODIMP_(bool) |
706 | | nsParser::IsComplete() |
707 | 0 | { |
708 | 0 | return !(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT); |
709 | 0 | } |
710 | | |
711 | | |
712 | | void nsParser::HandleParserContinueEvent(nsParserContinueEvent *ev) |
713 | 0 | { |
714 | 0 | // Ignore any revoked continue events... |
715 | 0 | if (mContinueEvent != ev) |
716 | 0 | return; |
717 | 0 | |
718 | 0 | mFlags &= ~NS_PARSER_FLAG_PENDING_CONTINUE_EVENT; |
719 | 0 | mContinueEvent = nullptr; |
720 | 0 |
|
721 | 0 | NS_ASSERTION(IsOkToProcessNetworkData(), |
722 | 0 | "Interrupted in the middle of a script?"); |
723 | 0 | ContinueInterruptedParsing(); |
724 | 0 | } |
725 | | |
726 | | bool |
727 | | nsParser::IsInsertionPointDefined() |
728 | 0 | { |
729 | 0 | return false; |
730 | 0 | } |
731 | | |
732 | | void |
733 | | nsParser::PushDefinedInsertionPoint() |
734 | 0 | { |
735 | 0 | } |
736 | | |
737 | | void |
738 | | nsParser::PopDefinedInsertionPoint() |
739 | 0 | { |
740 | 0 | } |
741 | | |
742 | | void |
743 | | nsParser::MarkAsNotScriptCreated(const char* aCommand) |
744 | 0 | { |
745 | 0 | } |
746 | | |
747 | | bool |
748 | | nsParser::IsScriptCreated() |
749 | 0 | { |
750 | 0 | return false; |
751 | 0 | } |
752 | | |
753 | | /** |
754 | | * This is the main controlling routine in the parsing process. |
755 | | * Note that it may get called multiple times for the same scanner, |
756 | | * since this is a pushed based system, and all the tokens may |
757 | | * not have been consumed by the scanner during a given invocation |
758 | | * of this method. |
759 | | */ |
760 | | NS_IMETHODIMP |
761 | | nsParser::Parse(nsIURI* aURL, |
762 | | nsIRequestObserver* aListener, |
763 | | void* aKey, |
764 | | nsDTDMode aMode) |
765 | 0 | { |
766 | 0 |
|
767 | 0 | MOZ_ASSERT(aURL, "Error: Null URL given"); |
768 | 0 |
|
769 | 0 | nsresult result = NS_ERROR_HTMLPARSER_BADURL; |
770 | 0 | mObserver = aListener; |
771 | 0 |
|
772 | 0 | if (aURL) { |
773 | 0 | nsAutoCString spec; |
774 | 0 | nsresult rv = aURL->GetSpec(spec); |
775 | 0 | if (rv != NS_OK) { |
776 | 0 | return rv; |
777 | 0 | } |
778 | 0 | NS_ConvertUTF8toUTF16 theName(spec); |
779 | 0 |
|
780 | 0 | nsScanner* theScanner = new nsScanner(theName, false); |
781 | 0 | CParserContext* pc = new CParserContext(mParserContext, theScanner, aKey, |
782 | 0 | mCommand, aListener); |
783 | 0 | if (pc && theScanner) { |
784 | 0 | pc->mMultipart = true; |
785 | 0 | pc->mContextType = CParserContext::eCTURL; |
786 | 0 | pc->mDTDMode = aMode; |
787 | 0 | PushContext(*pc); |
788 | 0 |
|
789 | 0 | result = NS_OK; |
790 | 0 | } else { |
791 | 0 | result = mInternalState = NS_ERROR_HTMLPARSER_BADCONTEXT; |
792 | 0 | } |
793 | 0 | } |
794 | 0 | return result; |
795 | 0 | } |
796 | | |
797 | | /** |
798 | | * Used by XML fragment parsing below. |
799 | | * |
800 | | * @param aSourceBuffer contains a string-full of real content |
801 | | */ |
802 | | nsresult |
803 | | nsParser::Parse(const nsAString& aSourceBuffer, |
804 | | void* aKey, |
805 | | bool aLastCall) |
806 | 0 | { |
807 | 0 | nsresult result = NS_OK; |
808 | 0 |
|
809 | 0 | // Don't bother if we're never going to parse this. |
810 | 0 | if (mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING) { |
811 | 0 | return result; |
812 | 0 | } |
813 | 0 | |
814 | 0 | if (!aLastCall && aSourceBuffer.IsEmpty()) { |
815 | 0 | // Nothing is being passed to the parser so return |
816 | 0 | // immediately. mUnusedInput will get processed when |
817 | 0 | // some data is actually passed in. |
818 | 0 | // But if this is the last call, make sure to finish up |
819 | 0 | // stuff correctly. |
820 | 0 | return result; |
821 | 0 | } |
822 | 0 | |
823 | 0 | // Maintain a reference to ourselves so we don't go away |
824 | 0 | // till we're completely done. |
825 | 0 | nsCOMPtr<nsIParser> kungFuDeathGrip(this); |
826 | 0 |
|
827 | 0 | if (aLastCall || !aSourceBuffer.IsEmpty() || !mUnusedInput.IsEmpty()) { |
828 | 0 | // Note: The following code will always find the parser context associated |
829 | 0 | // with the given key, even if that context has been suspended (e.g., for |
830 | 0 | // another document.write call). This doesn't appear to be exactly what IE |
831 | 0 | // does in the case where this happens, but this makes more sense. |
832 | 0 | CParserContext* pc = mParserContext; |
833 | 0 | while (pc && pc->mKey != aKey) { |
834 | 0 | pc = pc->mPrevContext; |
835 | 0 | } |
836 | 0 |
|
837 | 0 | if (!pc) { |
838 | 0 | // Only make a new context if we don't have one, OR if we do, but has a |
839 | 0 | // different context key. |
840 | 0 | nsScanner* theScanner = new nsScanner(mUnusedInput); |
841 | 0 | NS_ENSURE_TRUE(theScanner, NS_ERROR_OUT_OF_MEMORY); |
842 | 0 |
|
843 | 0 | eAutoDetectResult theStatus = eUnknownDetect; |
844 | 0 |
|
845 | 0 | if (mParserContext && |
846 | 0 | mParserContext->mMimeType.EqualsLiteral("application/xml")) { |
847 | 0 | // Ref. Bug 90379 |
848 | 0 | NS_ASSERTION(mDTD, "How come the DTD is null?"); |
849 | 0 |
|
850 | 0 | if (mParserContext) { |
851 | 0 | theStatus = mParserContext->mAutoDetectStatus; |
852 | 0 | // Added this to fix bug 32022. |
853 | 0 | } |
854 | 0 | } |
855 | 0 |
|
856 | 0 | pc = new CParserContext(mParserContext, theScanner, aKey, mCommand, |
857 | 0 | 0, theStatus, aLastCall); |
858 | 0 | NS_ENSURE_TRUE(pc, NS_ERROR_OUT_OF_MEMORY); |
859 | 0 |
|
860 | 0 | PushContext(*pc); |
861 | 0 |
|
862 | 0 | pc->mMultipart = !aLastCall; // By default |
863 | 0 | if (pc->mPrevContext) { |
864 | 0 | pc->mMultipart |= pc->mPrevContext->mMultipart; |
865 | 0 | } |
866 | 0 |
|
867 | 0 | // Start fix bug 40143 |
868 | 0 | if (pc->mMultipart) { |
869 | 0 | pc->mStreamListenerState = eOnDataAvail; |
870 | 0 | if (pc->mScanner) { |
871 | 0 | pc->mScanner->SetIncremental(true); |
872 | 0 | } |
873 | 0 | } else { |
874 | 0 | pc->mStreamListenerState = eOnStop; |
875 | 0 | if (pc->mScanner) { |
876 | 0 | pc->mScanner->SetIncremental(false); |
877 | 0 | } |
878 | 0 | } |
879 | 0 | // end fix for 40143 |
880 | 0 |
|
881 | 0 | pc->mContextType=CParserContext::eCTString; |
882 | 0 | pc->SetMimeType(NS_LITERAL_CSTRING("application/xml")); |
883 | 0 | pc->mDTDMode = eDTDMode_full_standards; |
884 | 0 |
|
885 | 0 | mUnusedInput.Truncate(); |
886 | 0 |
|
887 | 0 | pc->mScanner->Append(aSourceBuffer); |
888 | 0 | // Do not interrupt document.write() - bug 95487 |
889 | 0 | result = ResumeParse(false, false, false); |
890 | 0 | } else { |
891 | 0 | pc->mScanner->Append(aSourceBuffer); |
892 | 0 | if (!pc->mPrevContext) { |
893 | 0 | // Set stream listener state to eOnStop, on the final context - Fix 68160, |
894 | 0 | // to guarantee DidBuildModel() call - Fix 36148 |
895 | 0 | if (aLastCall) { |
896 | 0 | pc->mStreamListenerState = eOnStop; |
897 | 0 | pc->mScanner->SetIncremental(false); |
898 | 0 | } |
899 | 0 |
|
900 | 0 | if (pc == mParserContext) { |
901 | 0 | // If pc is not mParserContext, then this call to ResumeParse would |
902 | 0 | // do the wrong thing and try to continue parsing using |
903 | 0 | // mParserContext. We need to wait to actually resume parsing on pc. |
904 | 0 | ResumeParse(false, false, false); |
905 | 0 | } |
906 | 0 | } |
907 | 0 | } |
908 | 0 | } |
909 | 0 |
|
910 | 0 | return result; |
911 | 0 | } |
912 | | |
913 | | NS_IMETHODIMP |
914 | | nsParser::ParseFragment(const nsAString& aSourceBuffer, |
915 | | nsTArray<nsString>& aTagStack) |
916 | 0 | { |
917 | 0 | nsresult result = NS_OK; |
918 | 0 | nsAutoString theContext; |
919 | 0 | uint32_t theCount = aTagStack.Length(); |
920 | 0 | uint32_t theIndex = 0; |
921 | 0 |
|
922 | 0 | // Disable observers for fragments |
923 | 0 | mFlags &= ~NS_PARSER_FLAG_OBSERVERS_ENABLED; |
924 | 0 |
|
925 | 0 | for (theIndex = 0; theIndex < theCount; theIndex++) { |
926 | 0 | theContext.Append('<'); |
927 | 0 | theContext.Append(aTagStack[theCount - theIndex - 1]); |
928 | 0 | theContext.Append('>'); |
929 | 0 | } |
930 | 0 |
|
931 | 0 | if (theCount == 0) { |
932 | 0 | // Ensure that the buffer is not empty. Because none of the DTDs care |
933 | 0 | // about leading whitespace, this doesn't change the result. |
934 | 0 | theContext.Assign(' '); |
935 | 0 | } |
936 | 0 |
|
937 | 0 | // First, parse the context to build up the DTD's tag stack. Note that we |
938 | 0 | // pass false for the aLastCall parameter. |
939 | 0 | result = Parse(theContext, |
940 | 0 | (void*)&theContext, |
941 | 0 | false); |
942 | 0 | if (NS_FAILED(result)) { |
943 | 0 | mFlags |= NS_PARSER_FLAG_OBSERVERS_ENABLED; |
944 | 0 | return result; |
945 | 0 | } |
946 | 0 |
|
947 | 0 | if (!mSink) { |
948 | 0 | // Parse must have failed in the XML case and so the sink was killed. |
949 | 0 | return NS_ERROR_HTMLPARSER_STOPPARSING; |
950 | 0 | } |
951 | 0 | |
952 | 0 | nsCOMPtr<nsIFragmentContentSink> fragSink = do_QueryInterface(mSink); |
953 | 0 | NS_ASSERTION(fragSink, "ParseFragment requires a fragment content sink"); |
954 | 0 |
|
955 | 0 | fragSink->WillBuildContent(); |
956 | 0 | // Now, parse the actual content. Note that this is the last call |
957 | 0 | // for HTML content, but for XML, we will want to build and parse |
958 | 0 | // the end tags. However, if tagStack is empty, it's the last call |
959 | 0 | // for XML as well. |
960 | 0 | if (theCount == 0) { |
961 | 0 | result = Parse(aSourceBuffer, |
962 | 0 | &theContext, |
963 | 0 | true); |
964 | 0 | fragSink->DidBuildContent(); |
965 | 0 | } else { |
966 | 0 | // Add an end tag chunk, so expat will read the whole source buffer, |
967 | 0 | // and not worry about ']]' etc. |
968 | 0 | result = Parse(aSourceBuffer + NS_LITERAL_STRING("</"), |
969 | 0 | &theContext, |
970 | 0 | false); |
971 | 0 | fragSink->DidBuildContent(); |
972 | 0 |
|
973 | 0 | if (NS_SUCCEEDED(result)) { |
974 | 0 | nsAutoString endContext; |
975 | 0 | for (theIndex = 0; theIndex < theCount; theIndex++) { |
976 | 0 | // we already added an end tag chunk above |
977 | 0 | if (theIndex > 0) { |
978 | 0 | endContext.AppendLiteral("</"); |
979 | 0 | } |
980 | 0 |
|
981 | 0 | nsString& thisTag = aTagStack[theIndex]; |
982 | 0 | // was there an xmlns=? |
983 | 0 | int32_t endOfTag = thisTag.FindChar(char16_t(' ')); |
984 | 0 | if (endOfTag == -1) { |
985 | 0 | endContext.Append(thisTag); |
986 | 0 | } else { |
987 | 0 | endContext.Append(Substring(thisTag,0,endOfTag)); |
988 | 0 | } |
989 | 0 |
|
990 | 0 | endContext.Append('>'); |
991 | 0 | } |
992 | 0 |
|
993 | 0 | result = Parse(endContext, |
994 | 0 | &theContext, |
995 | 0 | true); |
996 | 0 | } |
997 | 0 | } |
998 | 0 |
|
999 | 0 | mFlags |= NS_PARSER_FLAG_OBSERVERS_ENABLED; |
1000 | 0 |
|
1001 | 0 | return result; |
1002 | 0 | } |
1003 | | |
1004 | | /** |
1005 | | * This routine is called to cause the parser to continue parsing its |
1006 | | * underlying stream. This call allows the parse process to happen in |
1007 | | * chunks, such as when the content is push based, and we need to parse in |
1008 | | * pieces. |
1009 | | * |
1010 | | * An interesting change in how the parser gets used has led us to add extra |
1011 | | * processing to this method. The case occurs when the parser is blocked in |
1012 | | * one context, and gets a parse(string) call in another context. In this |
1013 | | * case, the parserContexts are linked. No problem. |
1014 | | * |
1015 | | * The problem is that Parse(string) assumes that it can proceed unabated, |
1016 | | * but if the parser is already blocked that assumption is false. So we |
1017 | | * needed to add a mechanism here to allow the parser to continue to process |
1018 | | * (the pop and free) contexts until 1) it get's blocked again; 2) it runs |
1019 | | * out of contexts. |
1020 | | * |
1021 | | * |
1022 | | * @param allowItertion : set to true if non-script resumption is requested |
1023 | | * @param aIsFinalChunk : tells us when the last chunk of data is provided. |
1024 | | * @return error code -- 0 if ok, non-zero if error. |
1025 | | */ |
1026 | | nsresult |
1027 | | nsParser::ResumeParse(bool allowIteration, bool aIsFinalChunk, |
1028 | | bool aCanInterrupt) |
1029 | 0 | { |
1030 | 0 | nsresult result = NS_OK; |
1031 | 0 |
|
1032 | 0 | if (!mBlocked && mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) { |
1033 | 0 |
|
1034 | 0 | result = WillBuildModel(mParserContext->mScanner->GetFilename()); |
1035 | 0 | if (NS_FAILED(result)) { |
1036 | 0 | mFlags &= ~NS_PARSER_FLAG_CAN_TOKENIZE; |
1037 | 0 | return result; |
1038 | 0 | } |
1039 | 0 |
|
1040 | 0 | if (mDTD) { |
1041 | 0 | mSink->WillResume(); |
1042 | 0 | bool theIterationIsOk = true; |
1043 | 0 |
|
1044 | 0 | while (result == NS_OK && theIterationIsOk) { |
1045 | 0 | if (!mUnusedInput.IsEmpty() && mParserContext->mScanner) { |
1046 | 0 | // -- Ref: Bug# 22485 -- |
1047 | 0 | // Insert the unused input into the source buffer |
1048 | 0 | // as if it was read from the input stream. |
1049 | 0 | // Adding UngetReadable() per vidur!! |
1050 | 0 | mParserContext->mScanner->UngetReadable(mUnusedInput); |
1051 | 0 | mUnusedInput.Truncate(0); |
1052 | 0 | } |
1053 | 0 |
|
1054 | 0 | // Only allow parsing to be interrupted in the subsequent call to |
1055 | 0 | // build model. |
1056 | 0 | nsresult theTokenizerResult = (mFlags & NS_PARSER_FLAG_CAN_TOKENIZE) |
1057 | 0 | ? Tokenize(aIsFinalChunk) |
1058 | 0 | : NS_OK; |
1059 | 0 | result = BuildModel(); |
1060 | 0 |
|
1061 | 0 | if (result == NS_ERROR_HTMLPARSER_INTERRUPTED && aIsFinalChunk) { |
1062 | 0 | PostContinueEvent(); |
1063 | 0 | } |
1064 | 0 |
|
1065 | 0 | theIterationIsOk = theTokenizerResult != NS_ERROR_HTMLPARSER_EOF && |
1066 | 0 | result != NS_ERROR_HTMLPARSER_INTERRUPTED; |
1067 | 0 |
|
1068 | 0 | // Make sure not to stop parsing too early. Therefore, before shutting |
1069 | 0 | // down the parser, it's important to check whether the input buffer |
1070 | 0 | // has been scanned to completion (theTokenizerResult should be kEOF). |
1071 | 0 | // kEOF -> End of buffer. |
1072 | 0 |
|
1073 | 0 | // If we're told to block the parser, we disable all further parsing |
1074 | 0 | // (and cache any data coming in) until the parser is re-enabled. |
1075 | 0 | if (NS_ERROR_HTMLPARSER_BLOCK == result) { |
1076 | 0 | mSink->WillInterrupt(); |
1077 | 0 | if (!mBlocked) { |
1078 | 0 | // If we were blocked by a recursive invocation, don't re-block. |
1079 | 0 | BlockParser(); |
1080 | 0 | } |
1081 | 0 | return NS_OK; |
1082 | 0 | } |
1083 | 0 | if (NS_ERROR_HTMLPARSER_STOPPARSING == result) { |
1084 | 0 | // Note: Parser Terminate() calls DidBuildModel. |
1085 | 0 | if (mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) { |
1086 | 0 | DidBuildModel(mStreamStatus); |
1087 | 0 | mInternalState = result; |
1088 | 0 | } |
1089 | 0 |
|
1090 | 0 | return NS_OK; |
1091 | 0 | } |
1092 | 0 | if ((NS_OK == result && |
1093 | 0 | theTokenizerResult == NS_ERROR_HTMLPARSER_EOF) || |
1094 | 0 | result == NS_ERROR_HTMLPARSER_INTERRUPTED) { |
1095 | 0 | bool theContextIsStringBased = |
1096 | 0 | CParserContext::eCTString == mParserContext->mContextType; |
1097 | 0 |
|
1098 | 0 | if (mParserContext->mStreamListenerState == eOnStop || |
1099 | 0 | !mParserContext->mMultipart || theContextIsStringBased) { |
1100 | 0 | if (!mParserContext->mPrevContext) { |
1101 | 0 | if (mParserContext->mStreamListenerState == eOnStop) { |
1102 | 0 | DidBuildModel(mStreamStatus); |
1103 | 0 | return NS_OK; |
1104 | 0 | } |
1105 | 0 | } else { |
1106 | 0 | CParserContext* theContext = PopContext(); |
1107 | 0 | if (theContext) { |
1108 | 0 | theIterationIsOk = allowIteration && theContextIsStringBased; |
1109 | 0 | if (theContext->mCopyUnused) { |
1110 | 0 | if (!theContext->mScanner->CopyUnusedData(mUnusedInput)) { |
1111 | 0 | mInternalState = NS_ERROR_OUT_OF_MEMORY; |
1112 | 0 | } |
1113 | 0 | } |
1114 | 0 |
|
1115 | 0 | delete theContext; |
1116 | 0 | } |
1117 | 0 |
|
1118 | 0 | result = mInternalState; |
1119 | 0 | aIsFinalChunk = mParserContext && |
1120 | 0 | mParserContext->mStreamListenerState == eOnStop; |
1121 | 0 | // ...then intentionally fall through to mSink->WillInterrupt()... |
1122 | 0 | } |
1123 | 0 | } |
1124 | 0 | } |
1125 | 0 |
|
1126 | 0 | if (theTokenizerResult == NS_ERROR_HTMLPARSER_EOF || |
1127 | 0 | result == NS_ERROR_HTMLPARSER_INTERRUPTED) { |
1128 | 0 | result = (result == NS_ERROR_HTMLPARSER_INTERRUPTED) ? NS_OK : result; |
1129 | 0 | mSink->WillInterrupt(); |
1130 | 0 | } |
1131 | 0 | } |
1132 | 0 | } else { |
1133 | 0 | mInternalState = result = NS_ERROR_HTMLPARSER_UNRESOLVEDDTD; |
1134 | 0 | } |
1135 | 0 | } |
1136 | 0 |
|
1137 | 0 | return (result == NS_ERROR_HTMLPARSER_INTERRUPTED) ? NS_OK : result; |
1138 | 0 | } |
1139 | | |
1140 | | /** |
1141 | | * This is where we loop over the tokens created in the |
1142 | | * tokenization phase, and try to make sense out of them. |
1143 | | */ |
1144 | | nsresult |
1145 | | nsParser::BuildModel() |
1146 | 0 | { |
1147 | 0 | nsITokenizer* theTokenizer = nullptr; |
1148 | 0 |
|
1149 | 0 | nsresult result = NS_OK; |
1150 | 0 | if (mParserContext) { |
1151 | 0 | result = mParserContext->GetTokenizer(mDTD, mSink, theTokenizer); |
1152 | 0 | } |
1153 | 0 |
|
1154 | 0 | if (NS_SUCCEEDED(result)) { |
1155 | 0 | if (mDTD) { |
1156 | 0 | result = mDTD->BuildModel(theTokenizer, mSink); |
1157 | 0 | } |
1158 | 0 | } else { |
1159 | 0 | mInternalState = result = NS_ERROR_HTMLPARSER_BADTOKENIZER; |
1160 | 0 | } |
1161 | 0 | return result; |
1162 | 0 | } |
1163 | | |
1164 | | /******************************************************************* |
1165 | | These methods are used to talk to the netlib system... |
1166 | | *******************************************************************/ |
1167 | | |
1168 | | nsresult |
1169 | | nsParser::OnStartRequest(nsIRequest *request, nsISupports* aContext) |
1170 | 0 | { |
1171 | 0 | MOZ_ASSERT(eNone == mParserContext->mStreamListenerState, |
1172 | 0 | "Parser's nsIStreamListener API was not setup " |
1173 | 0 | "correctly in constructor."); |
1174 | 0 |
|
1175 | 0 | if (mObserver) { |
1176 | 0 | mObserver->OnStartRequest(request, aContext); |
1177 | 0 | } |
1178 | 0 | mParserContext->mStreamListenerState = eOnStart; |
1179 | 0 | mParserContext->mAutoDetectStatus = eUnknownDetect; |
1180 | 0 | mParserContext->mRequest = request; |
1181 | 0 |
|
1182 | 0 | NS_ASSERTION(!mParserContext->mPrevContext, |
1183 | 0 | "Clobbering DTD for non-root parser context!"); |
1184 | 0 | mDTD = nullptr; |
1185 | 0 |
|
1186 | 0 | nsresult rv; |
1187 | 0 | nsAutoCString contentType; |
1188 | 0 | nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); |
1189 | 0 | if (channel) { |
1190 | 0 | rv = channel->GetContentType(contentType); |
1191 | 0 | if (NS_SUCCEEDED(rv)) { |
1192 | 0 | mParserContext->SetMimeType(contentType); |
1193 | 0 | } |
1194 | 0 | } |
1195 | 0 |
|
1196 | 0 | rv = NS_OK; |
1197 | 0 |
|
1198 | 0 | return rv; |
1199 | 0 | } |
1200 | | |
1201 | | static bool |
1202 | | ExtractCharsetFromXmlDeclaration(const unsigned char* aBytes, int32_t aLen, |
1203 | | nsCString& oCharset) |
1204 | 0 | { |
1205 | 0 | // This code is rather pointless to have. Might as well reuse expat as |
1206 | 0 | // seen in nsHtml5StreamParser. -- hsivonen |
1207 | 0 | oCharset.Truncate(); |
1208 | 0 | if ((aLen >= 5) && |
1209 | 0 | ('<' == aBytes[0]) && |
1210 | 0 | ('?' == aBytes[1]) && |
1211 | 0 | ('x' == aBytes[2]) && |
1212 | 0 | ('m' == aBytes[3]) && |
1213 | 0 | ('l' == aBytes[4])) { |
1214 | 0 | int32_t i; |
1215 | 0 | bool versionFound = false, encodingFound = false; |
1216 | 0 | for (i = 6; i < aLen && !encodingFound; ++i) { |
1217 | 0 | // end of XML declaration? |
1218 | 0 | if ((((char*) aBytes)[i] == '?') && |
1219 | 0 | ((i + 1) < aLen) && |
1220 | 0 | (((char*) aBytes)[i + 1] == '>')) { |
1221 | 0 | break; |
1222 | 0 | } |
1223 | 0 | // Version is required. |
1224 | 0 | if (!versionFound) { |
1225 | 0 | // Want to avoid string comparisons, hence looking for 'n' |
1226 | 0 | // and only if found check the string leading to it. Not |
1227 | 0 | // foolproof, but fast. |
1228 | 0 | // The shortest string allowed before this is (strlen==13): |
1229 | 0 | // <?xml version |
1230 | 0 | if ((((char*) aBytes)[i] == 'n') && |
1231 | 0 | (i >= 12) && |
1232 | 0 | (0 == PL_strncmp("versio", (char*) (aBytes + i - 6), 6))) { |
1233 | 0 | // Fast forward through version |
1234 | 0 | char q = 0; |
1235 | 0 | for (++i; i < aLen; ++i) { |
1236 | 0 | char qi = ((char*) aBytes)[i]; |
1237 | 0 | if (qi == '\'' || qi == '"') { |
1238 | 0 | if (q && q == qi) { |
1239 | 0 | // ending quote |
1240 | 0 | versionFound = true; |
1241 | 0 | break; |
1242 | 0 | } else { |
1243 | 0 | // Starting quote |
1244 | 0 | q = qi; |
1245 | 0 | } |
1246 | 0 | } |
1247 | 0 | } |
1248 | 0 | } |
1249 | 0 | } else { |
1250 | 0 | // encoding must follow version |
1251 | 0 | // Want to avoid string comparisons, hence looking for 'g' |
1252 | 0 | // and only if found check the string leading to it. Not |
1253 | 0 | // foolproof, but fast. |
1254 | 0 | // The shortest allowed string before this (strlen==26): |
1255 | 0 | // <?xml version="1" encoding |
1256 | 0 | if ((((char*) aBytes)[i] == 'g') && (i >= 25) && (0 == PL_strncmp( |
1257 | 0 | "encodin", (char*) (aBytes + i - 7), 7))) { |
1258 | 0 | int32_t encStart = 0; |
1259 | 0 | char q = 0; |
1260 | 0 | for (++i; i < aLen; ++i) { |
1261 | 0 | char qi = ((char*) aBytes)[i]; |
1262 | 0 | if (qi == '\'' || qi == '"') { |
1263 | 0 | if (q && q == qi) { |
1264 | 0 | int32_t count = i - encStart; |
1265 | 0 | // encoding value is invalid if it is UTF-16 |
1266 | 0 | if (count > 0 && PL_strncasecmp("UTF-16", |
1267 | 0 | (char*) (aBytes + encStart), count)) { |
1268 | 0 | oCharset.Assign((char*) (aBytes + encStart), count); |
1269 | 0 | } |
1270 | 0 | encodingFound = true; |
1271 | 0 | break; |
1272 | 0 | } else { |
1273 | 0 | encStart = i + 1; |
1274 | 0 | q = qi; |
1275 | 0 | } |
1276 | 0 | } |
1277 | 0 | } |
1278 | 0 | } |
1279 | 0 | } // if (!versionFound) |
1280 | 0 | } // for |
1281 | 0 | } |
1282 | 0 | return !oCharset.IsEmpty(); |
1283 | 0 | } |
1284 | | |
1285 | | inline char |
1286 | | GetNextChar(nsACString::const_iterator& aStart, |
1287 | | nsACString::const_iterator& aEnd) |
1288 | 0 | { |
1289 | 0 | NS_ASSERTION(aStart != aEnd, "end of buffer"); |
1290 | 0 | return (++aStart != aEnd) ? *aStart : '\0'; |
1291 | 0 | } |
1292 | | |
1293 | | static nsresult |
1294 | | NoOpParserWriteFunc(nsIInputStream* in, |
1295 | | void* closure, |
1296 | | const char* fromRawSegment, |
1297 | | uint32_t toOffset, |
1298 | | uint32_t count, |
1299 | | uint32_t *writeCount) |
1300 | 0 | { |
1301 | 0 | *writeCount = count; |
1302 | 0 | return NS_OK; |
1303 | 0 | } |
1304 | | |
1305 | | typedef struct { |
1306 | | bool mNeedCharsetCheck; |
1307 | | nsParser* mParser; |
1308 | | nsScanner* mScanner; |
1309 | | nsIRequest* mRequest; |
1310 | | } ParserWriteStruct; |
1311 | | |
1312 | | /* |
1313 | | * This function is invoked as a result of a call to a stream's |
1314 | | * ReadSegments() method. It is called for each contiguous buffer |
1315 | | * of data in the underlying stream or pipe. Using ReadSegments |
1316 | | * allows us to avoid copying data to read out of the stream. |
1317 | | */ |
1318 | | static nsresult |
1319 | | ParserWriteFunc(nsIInputStream* in, |
1320 | | void* closure, |
1321 | | const char* fromRawSegment, |
1322 | | uint32_t toOffset, |
1323 | | uint32_t count, |
1324 | | uint32_t *writeCount) |
1325 | 0 | { |
1326 | 0 | nsresult result; |
1327 | 0 | ParserWriteStruct* pws = static_cast<ParserWriteStruct*>(closure); |
1328 | 0 | const unsigned char* buf = |
1329 | 0 | reinterpret_cast<const unsigned char*> (fromRawSegment); |
1330 | 0 | uint32_t theNumRead = count; |
1331 | 0 |
|
1332 | 0 | if (!pws) { |
1333 | 0 | return NS_ERROR_FAILURE; |
1334 | 0 | } |
1335 | 0 | |
1336 | 0 | if (pws->mNeedCharsetCheck) { |
1337 | 0 | pws->mNeedCharsetCheck = false; |
1338 | 0 | int32_t source; |
1339 | 0 | auto preferred = pws->mParser->GetDocumentCharset(source); |
1340 | 0 |
|
1341 | 0 | // This code was bogus when I found it. It expects the BOM or the XML |
1342 | 0 | // declaration to be entirely in the first network buffer. -- hsivonen |
1343 | 0 | const Encoding* encoding; |
1344 | 0 | size_t bomLength; |
1345 | 0 | Tie(encoding, bomLength) = Encoding::ForBOM(MakeSpan(buf, count)); |
1346 | 0 | Unused << bomLength; |
1347 | 0 | if (encoding) { |
1348 | 0 | // The decoder will swallow the BOM. The UTF-16 will re-sniff for |
1349 | 0 | // endianness. The value of preferred is now "UTF-8", "UTF-16LE" |
1350 | 0 | // or "UTF-16BE". |
1351 | 0 | preferred = WrapNotNull(encoding); |
1352 | 0 | source = kCharsetFromByteOrderMark; |
1353 | 0 | } else if (source < kCharsetFromChannel) { |
1354 | 0 | nsAutoCString declCharset; |
1355 | 0 |
|
1356 | 0 | if (ExtractCharsetFromXmlDeclaration(buf, count, declCharset)) { |
1357 | 0 | encoding = Encoding::ForLabel(declCharset); |
1358 | 0 | if (encoding) { |
1359 | 0 | preferred = WrapNotNull(encoding); |
1360 | 0 | source = kCharsetFromMetaTag; |
1361 | 0 | } |
1362 | 0 | } |
1363 | 0 | } |
1364 | 0 |
|
1365 | 0 | pws->mParser->SetDocumentCharset(preferred, source); |
1366 | 0 | pws->mParser->SetSinkCharset(preferred); |
1367 | 0 |
|
1368 | 0 | } |
1369 | 0 |
|
1370 | 0 | result = pws->mScanner->Append(fromRawSegment, theNumRead); |
1371 | 0 | if (NS_SUCCEEDED(result)) { |
1372 | 0 | *writeCount = count; |
1373 | 0 | } |
1374 | 0 |
|
1375 | 0 | return result; |
1376 | 0 | } |
1377 | | |
1378 | | nsresult |
1379 | | nsParser::OnDataAvailable(nsIRequest *request, nsISupports* aContext, |
1380 | | nsIInputStream *pIStream, uint64_t sourceOffset, |
1381 | | uint32_t aLength) |
1382 | 0 | { |
1383 | 0 | MOZ_ASSERT((eOnStart == mParserContext->mStreamListenerState || |
1384 | 0 | eOnDataAvail == mParserContext->mStreamListenerState), |
1385 | 0 | "Error: OnStartRequest() must be called before OnDataAvailable()"); |
1386 | 0 | MOZ_ASSERT(NS_InputStreamIsBuffered(pIStream), |
1387 | 0 | "Must have a buffered input stream"); |
1388 | 0 |
|
1389 | 0 | nsresult rv = NS_OK; |
1390 | 0 |
|
1391 | 0 | if (mIsAboutBlank) { |
1392 | 0 | MOZ_ASSERT(false, "Must not get OnDataAvailable for about:blank"); |
1393 | 0 | // ... but if an extension tries to feed us data for about:blank in a |
1394 | 0 | // release build, silently ignore the data. |
1395 | 0 | uint32_t totalRead; |
1396 | 0 | rv = pIStream->ReadSegments(NoOpParserWriteFunc, |
1397 | 0 | nullptr, |
1398 | 0 | aLength, |
1399 | 0 | &totalRead); |
1400 | 0 | return rv; |
1401 | 0 | } |
1402 | 0 |
|
1403 | 0 | CParserContext *theContext = mParserContext; |
1404 | 0 |
|
1405 | 0 | while (theContext && theContext->mRequest != request) { |
1406 | 0 | theContext = theContext->mPrevContext; |
1407 | 0 | } |
1408 | 0 |
|
1409 | 0 | if (theContext) { |
1410 | 0 | theContext->mStreamListenerState = eOnDataAvail; |
1411 | 0 |
|
1412 | 0 | if (eInvalidDetect == theContext->mAutoDetectStatus) { |
1413 | 0 | if (theContext->mScanner) { |
1414 | 0 | nsScannerIterator iter; |
1415 | 0 | theContext->mScanner->EndReading(iter); |
1416 | 0 | theContext->mScanner->SetPosition(iter, true); |
1417 | 0 | } |
1418 | 0 | } |
1419 | 0 |
|
1420 | 0 | uint32_t totalRead; |
1421 | 0 | ParserWriteStruct pws; |
1422 | 0 | pws.mNeedCharsetCheck = true; |
1423 | 0 | pws.mParser = this; |
1424 | 0 | pws.mScanner = theContext->mScanner; |
1425 | 0 | pws.mRequest = request; |
1426 | 0 |
|
1427 | 0 | rv = pIStream->ReadSegments(ParserWriteFunc, &pws, aLength, &totalRead); |
1428 | 0 | if (NS_FAILED(rv)) { |
1429 | 0 | return rv; |
1430 | 0 | } |
1431 | 0 | |
1432 | 0 | if (IsOkToProcessNetworkData()) { |
1433 | 0 | nsCOMPtr<nsIParser> kungFuDeathGrip(this); |
1434 | 0 | nsCOMPtr<nsIContentSink> sinkDeathGrip(mSink); |
1435 | 0 | mProcessingNetworkData = true; |
1436 | 0 | if (sinkDeathGrip) { |
1437 | 0 | sinkDeathGrip->WillParse(); |
1438 | 0 | } |
1439 | 0 | rv = ResumeParse(); |
1440 | 0 | mProcessingNetworkData = false; |
1441 | 0 | } |
1442 | 0 | } else { |
1443 | 0 | rv = NS_ERROR_UNEXPECTED; |
1444 | 0 | } |
1445 | 0 |
|
1446 | 0 | return rv; |
1447 | 0 | } |
1448 | | |
1449 | | /** |
1450 | | * This is called by the networking library once the last block of data |
1451 | | * has been collected from the net. |
1452 | | */ |
1453 | | nsresult |
1454 | | nsParser::OnStopRequest(nsIRequest *request, nsISupports* aContext, |
1455 | | nsresult status) |
1456 | 0 | { |
1457 | 0 | nsresult rv = NS_OK; |
1458 | 0 |
|
1459 | 0 | CParserContext *pc = mParserContext; |
1460 | 0 | while (pc) { |
1461 | 0 | if (pc->mRequest == request) { |
1462 | 0 | pc->mStreamListenerState = eOnStop; |
1463 | 0 | pc->mScanner->SetIncremental(false); |
1464 | 0 | break; |
1465 | 0 | } |
1466 | 0 | |
1467 | 0 | pc = pc->mPrevContext; |
1468 | 0 | } |
1469 | 0 |
|
1470 | 0 | mStreamStatus = status; |
1471 | 0 |
|
1472 | 0 | if (IsOkToProcessNetworkData() && NS_SUCCEEDED(rv)) { |
1473 | 0 | mProcessingNetworkData = true; |
1474 | 0 | if (mSink) { |
1475 | 0 | mSink->WillParse(); |
1476 | 0 | } |
1477 | 0 | rv = ResumeParse(true, true); |
1478 | 0 | mProcessingNetworkData = false; |
1479 | 0 | } |
1480 | 0 |
|
1481 | 0 | // If the parser isn't enabled, we don't finish parsing till |
1482 | 0 | // it is reenabled. |
1483 | 0 |
|
1484 | 0 |
|
1485 | 0 | // XXX Should we wait to notify our observers as well if the |
1486 | 0 | // parser isn't yet enabled? |
1487 | 0 | if (mObserver) { |
1488 | 0 | mObserver->OnStopRequest(request, aContext, status); |
1489 | 0 | } |
1490 | 0 |
|
1491 | 0 | return rv; |
1492 | 0 | } |
1493 | | |
1494 | | |
1495 | | /******************************************************************* |
1496 | | Here come the tokenization methods... |
1497 | | *******************************************************************/ |
1498 | | |
1499 | | |
1500 | | /** |
1501 | | * Part of the code sandwich, this gets called right before |
1502 | | * the tokenization process begins. The main reason for |
1503 | | * this call is to allow the delegate to do initialization. |
1504 | | */ |
1505 | | bool |
1506 | | nsParser::WillTokenize(bool aIsFinalChunk) |
1507 | 0 | { |
1508 | 0 | if (!mParserContext) { |
1509 | 0 | return true; |
1510 | 0 | } |
1511 | 0 | |
1512 | 0 | nsITokenizer* theTokenizer; |
1513 | 0 | nsresult result = mParserContext->GetTokenizer(mDTD, mSink, theTokenizer); |
1514 | 0 | NS_ENSURE_SUCCESS(result, false); |
1515 | 0 | return NS_SUCCEEDED(theTokenizer->WillTokenize(aIsFinalChunk)); |
1516 | 0 | } |
1517 | | |
1518 | | |
1519 | | /** |
1520 | | * This is the primary control routine to consume tokens. |
1521 | | * It iteratively consumes tokens until an error occurs or |
1522 | | * you run out of data. |
1523 | | */ |
1524 | | nsresult nsParser::Tokenize(bool aIsFinalChunk) |
1525 | 0 | { |
1526 | 0 | nsITokenizer* theTokenizer; |
1527 | 0 |
|
1528 | 0 | nsresult result = NS_ERROR_NOT_AVAILABLE; |
1529 | 0 | if (mParserContext) { |
1530 | 0 | result = mParserContext->GetTokenizer(mDTD, mSink, theTokenizer); |
1531 | 0 | } |
1532 | 0 |
|
1533 | 0 | if (NS_SUCCEEDED(result)) { |
1534 | 0 | bool flushTokens = false; |
1535 | 0 |
|
1536 | 0 | bool killSink = false; |
1537 | 0 |
|
1538 | 0 | WillTokenize(aIsFinalChunk); |
1539 | 0 | while (NS_SUCCEEDED(result)) { |
1540 | 0 | mParserContext->mScanner->Mark(); |
1541 | 0 | result = theTokenizer->ConsumeToken(*mParserContext->mScanner, |
1542 | 0 | flushTokens); |
1543 | 0 | if (NS_FAILED(result)) { |
1544 | 0 | mParserContext->mScanner->RewindToMark(); |
1545 | 0 | if (NS_ERROR_HTMLPARSER_EOF == result) { |
1546 | 0 | break; |
1547 | 0 | } |
1548 | 0 | if (NS_ERROR_HTMLPARSER_STOPPARSING == result) { |
1549 | 0 | killSink = true; |
1550 | 0 | result = Terminate(); |
1551 | 0 | break; |
1552 | 0 | } |
1553 | 0 | } else if (flushTokens && (mFlags & NS_PARSER_FLAG_OBSERVERS_ENABLED)) { |
1554 | 0 | // I added the extra test of NS_PARSER_FLAG_OBSERVERS_ENABLED to fix Bug# 23931. |
1555 | 0 | // Flush tokens on seeing </SCRIPT> -- Ref: Bug# 22485 -- |
1556 | 0 | // Also remember to update the marked position. |
1557 | 0 | mFlags |= NS_PARSER_FLAG_FLUSH_TOKENS; |
1558 | 0 | mParserContext->mScanner->Mark(); |
1559 | 0 | break; |
1560 | 0 | } |
1561 | 0 | } |
1562 | 0 |
|
1563 | 0 | if (killSink) { |
1564 | 0 | mSink = nullptr; |
1565 | 0 | } |
1566 | 0 | } else { |
1567 | 0 | result = mInternalState = NS_ERROR_HTMLPARSER_BADTOKENIZER; |
1568 | 0 | } |
1569 | 0 |
|
1570 | 0 | return result; |
1571 | 0 | } |
1572 | | |
1573 | | /** |
1574 | | * Get the channel associated with this parser |
1575 | | * |
1576 | | * @param aChannel out param that will contain the result |
1577 | | * @return NS_OK if successful |
1578 | | */ |
1579 | | NS_IMETHODIMP |
1580 | | nsParser::GetChannel(nsIChannel** aChannel) |
1581 | 0 | { |
1582 | 0 | nsresult result = NS_ERROR_NOT_AVAILABLE; |
1583 | 0 | if (mParserContext && mParserContext->mRequest) { |
1584 | 0 | result = CallQueryInterface(mParserContext->mRequest, aChannel); |
1585 | 0 | } |
1586 | 0 | return result; |
1587 | 0 | } |
1588 | | |
1589 | | /** |
1590 | | * Get the DTD associated with this parser |
1591 | | */ |
1592 | | NS_IMETHODIMP |
1593 | | nsParser::GetDTD(nsIDTD** aDTD) |
1594 | 0 | { |
1595 | 0 | if (mParserContext) { |
1596 | 0 | NS_IF_ADDREF(*aDTD = mDTD); |
1597 | 0 | } |
1598 | 0 |
|
1599 | 0 | return NS_OK; |
1600 | 0 | } |
1601 | | |
1602 | | /** |
1603 | | * Get this as nsIStreamListener |
1604 | | */ |
1605 | | nsIStreamListener* |
1606 | | nsParser::GetStreamListener() |
1607 | 0 | { |
1608 | 0 | return this; |
1609 | 0 | } |