|
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 "nsHtml5Parser.h" |
|
8 |
|
9 #include "mozilla/AutoRestore.h" |
|
10 #include "nsContentUtils.h" // for kLoadAsData |
|
11 #include "nsHtml5Tokenizer.h" |
|
12 #include "nsHtml5TreeBuilder.h" |
|
13 #include "nsHtml5AtomTable.h" |
|
14 #include "nsHtml5DependentUTF16Buffer.h" |
|
15 #include "nsNetUtil.h" |
|
16 |
|
17 NS_INTERFACE_TABLE_HEAD(nsHtml5Parser) |
|
18 NS_INTERFACE_TABLE(nsHtml5Parser, nsIParser, nsISupportsWeakReference) |
|
19 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5Parser) |
|
20 NS_INTERFACE_MAP_END |
|
21 |
|
22 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5Parser) |
|
23 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5Parser) |
|
24 |
|
25 NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5Parser) |
|
26 |
|
27 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5Parser) |
|
28 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExecutor) |
|
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(GetStreamParser()) |
|
30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
31 |
|
32 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5Parser) |
|
33 NS_IMPL_CYCLE_COLLECTION_UNLINK(mExecutor) |
|
34 tmp->DropStreamParser(); |
|
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
36 |
|
37 nsHtml5Parser::nsHtml5Parser() |
|
38 : mFirstBuffer(new nsHtml5OwningUTF16Buffer((void*)nullptr)) |
|
39 , mLastBuffer(mFirstBuffer) |
|
40 , mExecutor(new nsHtml5TreeOpExecutor()) |
|
41 , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor, nullptr)) |
|
42 , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder, false)) |
|
43 , mRootContextLineNumber(1) |
|
44 { |
|
45 mTokenizer->setInterner(&mAtomTable); |
|
46 // There's a zeroing operator new for everything else |
|
47 } |
|
48 |
|
49 nsHtml5Parser::~nsHtml5Parser() |
|
50 { |
|
51 mTokenizer->end(); |
|
52 if (mDocWriteSpeculativeTokenizer) { |
|
53 mDocWriteSpeculativeTokenizer->end(); |
|
54 } |
|
55 } |
|
56 |
|
57 NS_IMETHODIMP_(void) |
|
58 nsHtml5Parser::SetContentSink(nsIContentSink* aSink) |
|
59 { |
|
60 NS_ASSERTION(aSink == static_cast<nsIContentSink*> (mExecutor), |
|
61 "Attempt to set a foreign sink."); |
|
62 } |
|
63 |
|
64 NS_IMETHODIMP_(nsIContentSink*) |
|
65 nsHtml5Parser::GetContentSink() |
|
66 { |
|
67 return static_cast<nsIContentSink*> (mExecutor); |
|
68 } |
|
69 |
|
70 NS_IMETHODIMP_(void) |
|
71 nsHtml5Parser::GetCommand(nsCString& aCommand) |
|
72 { |
|
73 aCommand.Assign("view"); |
|
74 } |
|
75 |
|
76 NS_IMETHODIMP_(void) |
|
77 nsHtml5Parser::SetCommand(const char* aCommand) |
|
78 { |
|
79 NS_ASSERTION(!strcmp(aCommand, "view") || |
|
80 !strcmp(aCommand, "view-source") || |
|
81 !strcmp(aCommand, "external-resource") || |
|
82 !strcmp(aCommand, kLoadAsData), |
|
83 "Unsupported parser command"); |
|
84 } |
|
85 |
|
86 NS_IMETHODIMP_(void) |
|
87 nsHtml5Parser::SetCommand(eParserCommands aParserCommand) |
|
88 { |
|
89 NS_ASSERTION(aParserCommand == eViewNormal, |
|
90 "Parser command was not eViewNormal."); |
|
91 } |
|
92 |
|
93 NS_IMETHODIMP_(void) |
|
94 nsHtml5Parser::SetDocumentCharset(const nsACString& aCharset, |
|
95 int32_t aCharsetSource) |
|
96 { |
|
97 NS_PRECONDITION(!mExecutor->HasStarted(), |
|
98 "Document charset set too late."); |
|
99 NS_PRECONDITION(GetStreamParser(), "Setting charset on a script-only parser."); |
|
100 nsAutoCString trimmed; |
|
101 trimmed.Assign(aCharset); |
|
102 trimmed.Trim(" \t\r\n\f"); |
|
103 GetStreamParser()->SetDocumentCharset(trimmed, aCharsetSource); |
|
104 mExecutor->SetDocumentCharsetAndSource(trimmed, |
|
105 aCharsetSource); |
|
106 } |
|
107 |
|
108 NS_IMETHODIMP |
|
109 nsHtml5Parser::GetChannel(nsIChannel** aChannel) |
|
110 { |
|
111 if (GetStreamParser()) { |
|
112 return GetStreamParser()->GetChannel(aChannel); |
|
113 } else { |
|
114 return NS_ERROR_NOT_AVAILABLE; |
|
115 } |
|
116 } |
|
117 |
|
118 NS_IMETHODIMP |
|
119 nsHtml5Parser::GetDTD(nsIDTD** aDTD) |
|
120 { |
|
121 *aDTD = nullptr; |
|
122 return NS_OK; |
|
123 } |
|
124 |
|
125 nsIStreamListener* |
|
126 nsHtml5Parser::GetStreamListener() |
|
127 { |
|
128 return mStreamListener; |
|
129 } |
|
130 |
|
131 NS_IMETHODIMP |
|
132 nsHtml5Parser::ContinueInterruptedParsing() |
|
133 { |
|
134 NS_NOTREACHED("Don't call. For interface compat only."); |
|
135 return NS_ERROR_NOT_IMPLEMENTED; |
|
136 } |
|
137 |
|
138 NS_IMETHODIMP_(void) |
|
139 nsHtml5Parser::BlockParser() |
|
140 { |
|
141 mBlocked = true; |
|
142 } |
|
143 |
|
144 NS_IMETHODIMP_(void) |
|
145 nsHtml5Parser::UnblockParser() |
|
146 { |
|
147 mBlocked = false; |
|
148 mExecutor->ContinueInterruptedParsingAsync(); |
|
149 } |
|
150 |
|
151 NS_IMETHODIMP_(void) |
|
152 nsHtml5Parser::ContinueInterruptedParsingAsync() |
|
153 { |
|
154 mExecutor->ContinueInterruptedParsingAsync(); |
|
155 } |
|
156 |
|
157 NS_IMETHODIMP_(bool) |
|
158 nsHtml5Parser::IsParserEnabled() |
|
159 { |
|
160 return !mBlocked; |
|
161 } |
|
162 |
|
163 NS_IMETHODIMP_(bool) |
|
164 nsHtml5Parser::IsComplete() |
|
165 { |
|
166 return mExecutor->IsComplete(); |
|
167 } |
|
168 |
|
169 NS_IMETHODIMP |
|
170 nsHtml5Parser::Parse(nsIURI* aURL, |
|
171 nsIRequestObserver* aObserver, |
|
172 void* aKey, // legacy; ignored |
|
173 nsDTDMode aMode) // legacy; ignored |
|
174 { |
|
175 /* |
|
176 * Do NOT cause WillBuildModel to be called synchronously from here! |
|
177 * The document won't be ready for it until OnStartRequest! |
|
178 */ |
|
179 NS_PRECONDITION(!mExecutor->HasStarted(), |
|
180 "Tried to start parse without initializing the parser."); |
|
181 NS_PRECONDITION(GetStreamParser(), |
|
182 "Can't call this Parse() variant on script-created parser"); |
|
183 GetStreamParser()->SetObserver(aObserver); |
|
184 GetStreamParser()->SetViewSourceTitle(aURL); // In case we're viewing source |
|
185 mExecutor->SetStreamParser(GetStreamParser()); |
|
186 mExecutor->SetParser(this); |
|
187 return NS_OK; |
|
188 } |
|
189 |
|
190 NS_IMETHODIMP |
|
191 nsHtml5Parser::Parse(const nsAString& aSourceBuffer, |
|
192 void* aKey, |
|
193 const nsACString& aContentType, |
|
194 bool aLastCall, |
|
195 nsDTDMode aMode) // ignored |
|
196 { |
|
197 nsresult rv; |
|
198 if (NS_FAILED(rv = mExecutor->IsBroken())) { |
|
199 return rv; |
|
200 } |
|
201 if (aSourceBuffer.Length() > INT32_MAX) { |
|
202 return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); |
|
203 } |
|
204 |
|
205 // Maintain a reference to ourselves so we don't go away |
|
206 // till we're completely done. The old parser grips itself in this method. |
|
207 nsCOMPtr<nsIParser> kungFuDeathGrip(this); |
|
208 |
|
209 // Gripping the other objects just in case, since the other old grip |
|
210 // required grips to these, too. |
|
211 nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(GetStreamParser()); |
|
212 nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor); |
|
213 |
|
214 if (!mExecutor->HasStarted()) { |
|
215 NS_ASSERTION(!GetStreamParser(), |
|
216 "Had stream parser but document.write started life cycle."); |
|
217 // This is the first document.write() on a document.open()ed document |
|
218 mExecutor->SetParser(this); |
|
219 mTreeBuilder->setScriptingEnabled(mExecutor->IsScriptEnabled()); |
|
220 |
|
221 bool isSrcdoc = false; |
|
222 nsCOMPtr<nsIChannel> channel; |
|
223 rv = GetChannel(getter_AddRefs(channel)); |
|
224 if (NS_SUCCEEDED(rv)) { |
|
225 isSrcdoc = NS_IsSrcdocChannel(channel); |
|
226 } |
|
227 mTreeBuilder->setIsSrcdocDocument(isSrcdoc); |
|
228 |
|
229 mTokenizer->start(); |
|
230 mExecutor->Start(); |
|
231 if (!aContentType.EqualsLiteral("text/html")) { |
|
232 mTreeBuilder->StartPlainText(); |
|
233 mTokenizer->StartPlainText(); |
|
234 } |
|
235 /* |
|
236 * If you move the following line, be very careful not to cause |
|
237 * WillBuildModel to be called before the document has had its |
|
238 * script global object set. |
|
239 */ |
|
240 rv = mExecutor->WillBuildModel(eDTDMode_unknown); |
|
241 NS_ENSURE_SUCCESS(rv, rv); |
|
242 } |
|
243 |
|
244 // Return early if the parser has processed EOF |
|
245 if (mExecutor->IsComplete()) { |
|
246 return NS_OK; |
|
247 } |
|
248 |
|
249 if (aLastCall && aSourceBuffer.IsEmpty() && !aKey) { |
|
250 // document.close() |
|
251 NS_ASSERTION(!GetStreamParser(), |
|
252 "Had stream parser but got document.close()."); |
|
253 if (mDocumentClosed) { |
|
254 // already closed |
|
255 return NS_OK; |
|
256 } |
|
257 mDocumentClosed = true; |
|
258 if (!mBlocked && !mInDocumentWrite) { |
|
259 return ParseUntilBlocked(); |
|
260 } |
|
261 return NS_OK; |
|
262 } |
|
263 |
|
264 // If we got this far, we are dealing with a document.write or |
|
265 // document.writeln call--not document.close(). |
|
266 |
|
267 NS_ASSERTION(IsInsertionPointDefined(), |
|
268 "Doc.write reached parser with undefined insertion point."); |
|
269 |
|
270 NS_ASSERTION(!(GetStreamParser() && !aKey), |
|
271 "Got a null key in a non-script-created parser"); |
|
272 |
|
273 // XXX is this optimization bogus? |
|
274 if (aSourceBuffer.IsEmpty()) { |
|
275 return NS_OK; |
|
276 } |
|
277 |
|
278 // This guard is here to prevent document.close from tokenizing synchronously |
|
279 // while a document.write (that wrote the script that called document.close!) |
|
280 // is still on the call stack. |
|
281 mozilla::AutoRestore<bool> guard(mInDocumentWrite); |
|
282 mInDocumentWrite = true; |
|
283 |
|
284 // The script is identified by aKey. If there's nothing in the buffer |
|
285 // chain for that key, we'll insert at the head of the queue. |
|
286 // When the script leaves something in the queue, a zero-length |
|
287 // key-holder "buffer" is inserted in the queue. If the same script |
|
288 // leaves something in the chain again, it will be inserted immediately |
|
289 // before the old key holder belonging to the same script. |
|
290 // |
|
291 // We don't do the actual data insertion yet in the hope that the data gets |
|
292 // tokenized and there no data or less data to copy to the heap after |
|
293 // tokenization. Also, this way, we avoid inserting one empty data buffer |
|
294 // per document.write, which matters for performance when the parser isn't |
|
295 // blocked and a badly-authored script calls document.write() once per |
|
296 // input character. (As seen in a benchmark!) |
|
297 // |
|
298 // The insertion into the input stream happens conceptually before anything |
|
299 // gets tokenized. To make sure multi-level document.write works right, |
|
300 // it's necessary to establish the location of our parser key up front |
|
301 // in case this is the first write with this key. |
|
302 // |
|
303 // In a document.open() case, the first write level has a null key, so that |
|
304 // case is handled separately, because normal buffers containing data |
|
305 // have null keys. |
|
306 |
|
307 // These don't need to be owning references, because they always point to |
|
308 // the buffer queue and buffers can't be removed from the buffer queue |
|
309 // before document.write() returns. The buffer queue clean-up happens the |
|
310 // next time ParseUntilBlocked() is called. |
|
311 // However, they are made owning just in case the reasoning above is flawed |
|
312 // and a flaw would lead to worse problems with plain pointers. If this |
|
313 // turns out to be a perf problem, it's worthwhile to consider making |
|
314 // prevSearchbuf a plain pointer again. |
|
315 nsRefPtr<nsHtml5OwningUTF16Buffer> prevSearchBuf; |
|
316 nsRefPtr<nsHtml5OwningUTF16Buffer> firstLevelMarker; |
|
317 |
|
318 if (aKey) { |
|
319 if (mFirstBuffer == mLastBuffer) { |
|
320 nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey); |
|
321 keyHolder->next = mLastBuffer; |
|
322 mFirstBuffer = keyHolder; |
|
323 } else if (mFirstBuffer->key != aKey) { |
|
324 prevSearchBuf = mFirstBuffer; |
|
325 for (;;) { |
|
326 if (prevSearchBuf->next == mLastBuffer) { |
|
327 // key was not found |
|
328 nsHtml5OwningUTF16Buffer* keyHolder = |
|
329 new nsHtml5OwningUTF16Buffer(aKey); |
|
330 keyHolder->next = mFirstBuffer; |
|
331 mFirstBuffer = keyHolder; |
|
332 prevSearchBuf = nullptr; |
|
333 break; |
|
334 } |
|
335 if (prevSearchBuf->next->key == aKey) { |
|
336 // found a key holder |
|
337 break; |
|
338 } |
|
339 prevSearchBuf = prevSearchBuf->next; |
|
340 } |
|
341 } // else mFirstBuffer is the keyholder |
|
342 |
|
343 // prevSearchBuf is the previous buffer before the keyholder or null if |
|
344 // there isn't one. |
|
345 } else { |
|
346 // We have a first-level write in the document.open() case. We insert before |
|
347 // mLastBuffer, effectively, by making mLastBuffer be a new sentinel object |
|
348 // and redesignating the previous mLastBuffer as our firstLevelMarker. We |
|
349 // need to put a marker there, because otherwise additional document.writes |
|
350 // from nested event loops would insert in the wrong place. Sigh. |
|
351 mLastBuffer->next = new nsHtml5OwningUTF16Buffer((void*)nullptr); |
|
352 firstLevelMarker = mLastBuffer; |
|
353 mLastBuffer = mLastBuffer->next; |
|
354 } |
|
355 |
|
356 nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer); |
|
357 |
|
358 while (!mBlocked && stackBuffer.hasMore()) { |
|
359 stackBuffer.adjust(mLastWasCR); |
|
360 mLastWasCR = false; |
|
361 if (stackBuffer.hasMore()) { |
|
362 int32_t lineNumberSave; |
|
363 bool inRootContext = (!GetStreamParser() && !aKey); |
|
364 if (inRootContext) { |
|
365 mTokenizer->setLineNumber(mRootContextLineNumber); |
|
366 } else { |
|
367 // we aren't the root context, so save the line number on the |
|
368 // *stack* so that we can restore it. |
|
369 lineNumberSave = mTokenizer->getLineNumber(); |
|
370 } |
|
371 |
|
372 mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer); |
|
373 |
|
374 if (inRootContext) { |
|
375 mRootContextLineNumber = mTokenizer->getLineNumber(); |
|
376 } else { |
|
377 mTokenizer->setLineNumber(lineNumberSave); |
|
378 } |
|
379 |
|
380 if (mTreeBuilder->HasScript()) { |
|
381 mTreeBuilder->Flush(); // Move ops to the executor |
|
382 rv = mExecutor->FlushDocumentWrite(); // run the ops |
|
383 NS_ENSURE_SUCCESS(rv, rv); |
|
384 // Flushing tree ops can cause all sorts of things. |
|
385 // Return early if the parser got terminated. |
|
386 if (mExecutor->IsComplete()) { |
|
387 return NS_OK; |
|
388 } |
|
389 } |
|
390 // Ignore suspension requests |
|
391 } |
|
392 } |
|
393 |
|
394 nsRefPtr<nsHtml5OwningUTF16Buffer> heapBuffer; |
|
395 if (stackBuffer.hasMore()) { |
|
396 // The buffer wasn't tokenized to completion. Create a copy of the tail |
|
397 // on the heap. |
|
398 heapBuffer = stackBuffer.FalliblyCopyAsOwningBuffer(); |
|
399 if (!heapBuffer) { |
|
400 // Allocation failed. The parser is now broken. |
|
401 return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); |
|
402 } |
|
403 } |
|
404 |
|
405 if (heapBuffer) { |
|
406 // We have something to insert before the keyholder holding in the non-null |
|
407 // aKey case and we have something to swap into firstLevelMarker in the |
|
408 // null aKey case. |
|
409 if (aKey) { |
|
410 NS_ASSERTION(mFirstBuffer != mLastBuffer, |
|
411 "Where's the keyholder?"); |
|
412 // the key holder is still somewhere further down the list from |
|
413 // prevSearchBuf (which may be null) |
|
414 if (mFirstBuffer->key == aKey) { |
|
415 NS_ASSERTION(!prevSearchBuf, |
|
416 "Non-null prevSearchBuf when mFirstBuffer is the key holder?"); |
|
417 heapBuffer->next = mFirstBuffer; |
|
418 mFirstBuffer = heapBuffer; |
|
419 } else { |
|
420 if (!prevSearchBuf) { |
|
421 prevSearchBuf = mFirstBuffer; |
|
422 } |
|
423 // We created a key holder earlier, so we will find it without walking |
|
424 // past the end of the list. |
|
425 while (prevSearchBuf->next->key != aKey) { |
|
426 prevSearchBuf = prevSearchBuf->next; |
|
427 } |
|
428 heapBuffer->next = prevSearchBuf->next; |
|
429 prevSearchBuf->next = heapBuffer; |
|
430 } |
|
431 } else { |
|
432 NS_ASSERTION(firstLevelMarker, "How come we don't have a marker."); |
|
433 firstLevelMarker->Swap(heapBuffer); |
|
434 } |
|
435 } |
|
436 |
|
437 if (!mBlocked) { // buffer was tokenized to completion |
|
438 NS_ASSERTION(!stackBuffer.hasMore(), |
|
439 "Buffer wasn't tokenized to completion?"); |
|
440 // Scripting semantics require a forced tree builder flush here |
|
441 mTreeBuilder->Flush(); // Move ops to the executor |
|
442 rv = mExecutor->FlushDocumentWrite(); // run the ops |
|
443 NS_ENSURE_SUCCESS(rv, rv); |
|
444 } else if (stackBuffer.hasMore()) { |
|
445 // The buffer wasn't tokenized to completion. Tokenize the untokenized |
|
446 // content in order to preload stuff. This content will be retokenized |
|
447 // later for normal parsing. |
|
448 if (!mDocWriteSpeculatorActive) { |
|
449 mDocWriteSpeculatorActive = true; |
|
450 if (!mDocWriteSpeculativeTreeBuilder) { |
|
451 // Lazily initialize if uninitialized |
|
452 mDocWriteSpeculativeTreeBuilder = |
|
453 new nsHtml5TreeBuilder(nullptr, mExecutor->GetStage()); |
|
454 mDocWriteSpeculativeTreeBuilder->setScriptingEnabled( |
|
455 mTreeBuilder->isScriptingEnabled()); |
|
456 mDocWriteSpeculativeTokenizer = |
|
457 new nsHtml5Tokenizer(mDocWriteSpeculativeTreeBuilder, false); |
|
458 mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable); |
|
459 mDocWriteSpeculativeTokenizer->start(); |
|
460 } |
|
461 mDocWriteSpeculativeTokenizer->resetToDataState(); |
|
462 mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder, &mAtomTable); |
|
463 mDocWriteSpeculativeLastWasCR = false; |
|
464 } |
|
465 |
|
466 // Note that with multilevel document.write if we didn't just activate the |
|
467 // speculator, it's possible that the speculator is now in the wrong state. |
|
468 // That's OK for the sake of simplicity. The worst that can happen is |
|
469 // that the speculative loads aren't exactly right. The content will be |
|
470 // reparsed anyway for non-preload purposes. |
|
471 |
|
472 // The buffer position for subsequent non-speculative parsing now lives |
|
473 // in heapBuffer, so it's ok to let the buffer position of stackBuffer |
|
474 // to be overwritten and not restored below. |
|
475 while (stackBuffer.hasMore()) { |
|
476 stackBuffer.adjust(mDocWriteSpeculativeLastWasCR); |
|
477 if (stackBuffer.hasMore()) { |
|
478 mDocWriteSpeculativeLastWasCR = |
|
479 mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer); |
|
480 } |
|
481 } |
|
482 |
|
483 mDocWriteSpeculativeTreeBuilder->Flush(); |
|
484 mDocWriteSpeculativeTreeBuilder->DropHandles(); |
|
485 mExecutor->FlushSpeculativeLoads(); |
|
486 } |
|
487 |
|
488 return NS_OK; |
|
489 } |
|
490 |
|
491 NS_IMETHODIMP |
|
492 nsHtml5Parser::Terminate() |
|
493 { |
|
494 // We should only call DidBuildModel once, so don't do anything if this is |
|
495 // the second time that Terminate has been called. |
|
496 if (mExecutor->IsComplete()) { |
|
497 return NS_OK; |
|
498 } |
|
499 // XXX - [ until we figure out a way to break parser-sink circularity ] |
|
500 // Hack - Hold a reference until we are completely done... |
|
501 nsCOMPtr<nsIParser> kungFuDeathGrip(this); |
|
502 nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(GetStreamParser()); |
|
503 nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor); |
|
504 if (GetStreamParser()) { |
|
505 GetStreamParser()->Terminate(); |
|
506 } |
|
507 return mExecutor->DidBuildModel(true); |
|
508 } |
|
509 |
|
510 NS_IMETHODIMP |
|
511 nsHtml5Parser::ParseFragment(const nsAString& aSourceBuffer, |
|
512 nsTArray<nsString>& aTagStack) |
|
513 { |
|
514 return NS_ERROR_NOT_IMPLEMENTED; |
|
515 } |
|
516 |
|
517 NS_IMETHODIMP |
|
518 nsHtml5Parser::BuildModel() |
|
519 { |
|
520 NS_NOTREACHED("Don't call this!"); |
|
521 return NS_ERROR_NOT_IMPLEMENTED; |
|
522 } |
|
523 |
|
524 NS_IMETHODIMP |
|
525 nsHtml5Parser::CancelParsingEvents() |
|
526 { |
|
527 NS_NOTREACHED("Don't call this!"); |
|
528 return NS_ERROR_NOT_IMPLEMENTED; |
|
529 } |
|
530 |
|
531 void |
|
532 nsHtml5Parser::Reset() |
|
533 { |
|
534 NS_NOTREACHED("Don't call this!"); |
|
535 } |
|
536 |
|
537 bool |
|
538 nsHtml5Parser::CanInterrupt() |
|
539 { |
|
540 // nsContentSink needs this to let nsContentSink::DidProcessATokenImpl |
|
541 // interrupt. |
|
542 return true; |
|
543 } |
|
544 |
|
545 bool |
|
546 nsHtml5Parser::IsInsertionPointDefined() |
|
547 { |
|
548 return !mExecutor->IsFlushing() && |
|
549 (!GetStreamParser() || mParserInsertedScriptsBeingEvaluated); |
|
550 } |
|
551 |
|
552 void |
|
553 nsHtml5Parser::BeginEvaluatingParserInsertedScript() |
|
554 { |
|
555 ++mParserInsertedScriptsBeingEvaluated; |
|
556 } |
|
557 |
|
558 void |
|
559 nsHtml5Parser::EndEvaluatingParserInsertedScript() |
|
560 { |
|
561 --mParserInsertedScriptsBeingEvaluated; |
|
562 } |
|
563 |
|
564 void |
|
565 nsHtml5Parser::MarkAsNotScriptCreated(const char* aCommand) |
|
566 { |
|
567 NS_PRECONDITION(!mStreamListener, "Must not call this twice."); |
|
568 eParserMode mode = NORMAL; |
|
569 if (!nsCRT::strcmp(aCommand, "view-source")) { |
|
570 mode = VIEW_SOURCE_HTML; |
|
571 } else if (!nsCRT::strcmp(aCommand, "view-source-xml")) { |
|
572 mode = VIEW_SOURCE_XML; |
|
573 } else if (!nsCRT::strcmp(aCommand, "view-source-plain")) { |
|
574 mode = VIEW_SOURCE_PLAIN; |
|
575 } else if (!nsCRT::strcmp(aCommand, "plain-text")) { |
|
576 mode = PLAIN_TEXT; |
|
577 } else if (!nsCRT::strcmp(aCommand, kLoadAsData)) { |
|
578 mode = LOAD_AS_DATA; |
|
579 } |
|
580 #ifdef DEBUG |
|
581 else { |
|
582 NS_ASSERTION(!nsCRT::strcmp(aCommand, "view") || |
|
583 !nsCRT::strcmp(aCommand, "external-resource"), |
|
584 "Unsupported parser command!"); |
|
585 } |
|
586 #endif |
|
587 mStreamListener = |
|
588 new nsHtml5StreamListener(new nsHtml5StreamParser(mExecutor, this, mode)); |
|
589 } |
|
590 |
|
591 bool |
|
592 nsHtml5Parser::IsScriptCreated() |
|
593 { |
|
594 return !GetStreamParser(); |
|
595 } |
|
596 |
|
597 /* End nsIParser */ |
|
598 |
|
599 // not from interface |
|
600 nsresult |
|
601 nsHtml5Parser::ParseUntilBlocked() |
|
602 { |
|
603 nsresult rv = mExecutor->IsBroken(); |
|
604 NS_ENSURE_SUCCESS(rv, rv); |
|
605 if (mBlocked || mExecutor->IsComplete()) { |
|
606 return NS_OK; |
|
607 } |
|
608 NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle."); |
|
609 NS_ASSERTION(!mInDocumentWrite, |
|
610 "ParseUntilBlocked entered while in doc.write!"); |
|
611 |
|
612 mDocWriteSpeculatorActive = false; |
|
613 |
|
614 for (;;) { |
|
615 if (!mFirstBuffer->hasMore()) { |
|
616 if (mFirstBuffer == mLastBuffer) { |
|
617 if (mExecutor->IsComplete()) { |
|
618 // something like cache manisfests stopped the parse in mid-flight |
|
619 return NS_OK; |
|
620 } |
|
621 if (mDocumentClosed) { |
|
622 NS_ASSERTION(!GetStreamParser(), |
|
623 "This should only happen with script-created parser."); |
|
624 mTokenizer->eof(); |
|
625 mTreeBuilder->StreamEnded(); |
|
626 mTreeBuilder->Flush(); |
|
627 mExecutor->FlushDocumentWrite(); |
|
628 // The below call does memory cleanup, so call it even if the |
|
629 // parser has been marked as broken. |
|
630 mTokenizer->end(); |
|
631 return NS_OK; |
|
632 } |
|
633 // never release the last buffer. |
|
634 NS_ASSERTION(!mLastBuffer->getStart() && !mLastBuffer->getEnd(), |
|
635 "Sentinel buffer had its indeces changed."); |
|
636 if (GetStreamParser()) { |
|
637 if (mReturnToStreamParserPermitted && |
|
638 !mExecutor->IsScriptExecuting()) { |
|
639 mTreeBuilder->Flush(); |
|
640 mReturnToStreamParserPermitted = false; |
|
641 GetStreamParser()->ContinueAfterScripts(mTokenizer, |
|
642 mTreeBuilder, |
|
643 mLastWasCR); |
|
644 } |
|
645 } else { |
|
646 // Script-created parser |
|
647 mTreeBuilder->Flush(); |
|
648 // No need to flush the executor, because the executor is already |
|
649 // in a flush |
|
650 NS_ASSERTION(mExecutor->IsInFlushLoop(), |
|
651 "How did we come here without being in the flush loop?"); |
|
652 } |
|
653 return NS_OK; // no more data for now but expecting more |
|
654 } |
|
655 mFirstBuffer = mFirstBuffer->next; |
|
656 continue; |
|
657 } |
|
658 |
|
659 if (mBlocked || mExecutor->IsComplete()) { |
|
660 return NS_OK; |
|
661 } |
|
662 |
|
663 // now we have a non-empty buffer |
|
664 mFirstBuffer->adjust(mLastWasCR); |
|
665 mLastWasCR = false; |
|
666 if (mFirstBuffer->hasMore()) { |
|
667 bool inRootContext = (!GetStreamParser() && !mFirstBuffer->key); |
|
668 if (inRootContext) { |
|
669 mTokenizer->setLineNumber(mRootContextLineNumber); |
|
670 } |
|
671 mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer); |
|
672 if (inRootContext) { |
|
673 mRootContextLineNumber = mTokenizer->getLineNumber(); |
|
674 } |
|
675 if (mTreeBuilder->HasScript()) { |
|
676 mTreeBuilder->Flush(); |
|
677 nsresult rv = mExecutor->FlushDocumentWrite(); |
|
678 NS_ENSURE_SUCCESS(rv, rv); |
|
679 } |
|
680 if (mBlocked) { |
|
681 return NS_OK; |
|
682 } |
|
683 } |
|
684 continue; |
|
685 } |
|
686 } |
|
687 |
|
688 nsresult |
|
689 nsHtml5Parser::Initialize(nsIDocument* aDoc, |
|
690 nsIURI* aURI, |
|
691 nsISupports* aContainer, |
|
692 nsIChannel* aChannel) |
|
693 { |
|
694 return mExecutor->Init(aDoc, aURI, aContainer, aChannel); |
|
695 } |
|
696 |
|
697 void |
|
698 nsHtml5Parser::StartTokenizer(bool aScriptingEnabled) { |
|
699 |
|
700 bool isSrcdoc = false; |
|
701 nsCOMPtr<nsIChannel> channel; |
|
702 nsresult rv = GetChannel(getter_AddRefs(channel)); |
|
703 if (NS_SUCCEEDED(rv)) { |
|
704 isSrcdoc = NS_IsSrcdocChannel(channel); |
|
705 } |
|
706 mTreeBuilder->setIsSrcdocDocument(isSrcdoc); |
|
707 |
|
708 mTreeBuilder->SetPreventScriptExecution(!aScriptingEnabled); |
|
709 mTreeBuilder->setScriptingEnabled(aScriptingEnabled); |
|
710 mTokenizer->start(); |
|
711 } |
|
712 |
|
713 void |
|
714 nsHtml5Parser::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, |
|
715 int32_t aLine) |
|
716 { |
|
717 mTokenizer->resetToDataState(); |
|
718 mTokenizer->setLineNumber(aLine); |
|
719 mTreeBuilder->loadState(aState, &mAtomTable); |
|
720 mLastWasCR = false; |
|
721 mReturnToStreamParserPermitted = true; |
|
722 } |
|
723 |
|
724 void |
|
725 nsHtml5Parser::ContinueAfterFailedCharsetSwitch() |
|
726 { |
|
727 NS_PRECONDITION(GetStreamParser(), |
|
728 "Tried to continue after failed charset switch without a stream parser"); |
|
729 GetStreamParser()->ContinueAfterFailedCharsetSwitch(); |
|
730 } |
|
731 |