|
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 "mozilla/DebugOnly.h" |
|
8 #include "mozilla/Likely.h" |
|
9 |
|
10 #include "nsError.h" |
|
11 #include "nsHtml5TreeOpExecutor.h" |
|
12 #include "nsScriptLoader.h" |
|
13 #include "nsIMarkupDocumentViewer.h" |
|
14 #include "nsIContentViewer.h" |
|
15 #include "nsIDocShellTreeItem.h" |
|
16 #include "nsIDocShell.h" |
|
17 #include "nsIScriptGlobalObject.h" |
|
18 #include "nsIScriptSecurityManager.h" |
|
19 #include "nsIWebShellServices.h" |
|
20 #include "nsContentUtils.h" |
|
21 #include "mozAutoDocUpdate.h" |
|
22 #include "nsNetUtil.h" |
|
23 #include "nsHtml5Parser.h" |
|
24 #include "nsHtml5Tokenizer.h" |
|
25 #include "nsHtml5TreeBuilder.h" |
|
26 #include "nsHtml5StreamParser.h" |
|
27 #include "mozilla/css/Loader.h" |
|
28 #include "GeckoProfiler.h" |
|
29 #include "nsIScriptError.h" |
|
30 #include "nsIScriptContext.h" |
|
31 #include "mozilla/Preferences.h" |
|
32 #include "nsIHTMLDocument.h" |
|
33 |
|
34 using namespace mozilla; |
|
35 |
|
36 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor) |
|
37 NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor, |
|
38 nsIContentSink) |
|
39 NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder) |
|
40 |
|
41 NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) |
|
42 |
|
43 NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) |
|
44 |
|
45 class nsHtml5ExecutorReflusher : public nsRunnable |
|
46 { |
|
47 private: |
|
48 nsRefPtr<nsHtml5TreeOpExecutor> mExecutor; |
|
49 public: |
|
50 nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor) |
|
51 : mExecutor(aExecutor) |
|
52 {} |
|
53 NS_IMETHODIMP Run() |
|
54 { |
|
55 mExecutor->RunFlushLoop(); |
|
56 return NS_OK; |
|
57 } |
|
58 }; |
|
59 |
|
60 static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr; |
|
61 static nsITimer* gFlushTimer = nullptr; |
|
62 |
|
63 nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor() |
|
64 : nsHtml5DocumentBuilder(false) |
|
65 , mPreloadedURLs(23) // Mean # of preloadable resources per page on dmoz |
|
66 { |
|
67 // zeroing operator new for everything else |
|
68 } |
|
69 |
|
70 nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() |
|
71 { |
|
72 if (gBackgroundFlushList && isInList()) { |
|
73 mOpQueue.Clear(); |
|
74 removeFrom(*gBackgroundFlushList); |
|
75 if (gBackgroundFlushList->isEmpty()) { |
|
76 delete gBackgroundFlushList; |
|
77 gBackgroundFlushList = nullptr; |
|
78 if (gFlushTimer) { |
|
79 gFlushTimer->Cancel(); |
|
80 NS_RELEASE(gFlushTimer); |
|
81 } |
|
82 } |
|
83 } |
|
84 NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue."); |
|
85 } |
|
86 |
|
87 // nsIContentSink |
|
88 NS_IMETHODIMP |
|
89 nsHtml5TreeOpExecutor::WillParse() |
|
90 { |
|
91 NS_NOTREACHED("No one should call this"); |
|
92 return NS_ERROR_NOT_IMPLEMENTED; |
|
93 } |
|
94 |
|
95 NS_IMETHODIMP |
|
96 nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode) |
|
97 { |
|
98 mDocument->AddObserver(this); |
|
99 WillBuildModelImpl(); |
|
100 GetDocument()->BeginLoad(); |
|
101 if (mDocShell && !GetDocument()->GetWindow() && |
|
102 !IsExternalViewSource()) { |
|
103 // Not loading as data but script global object not ready |
|
104 return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
105 } |
|
106 return NS_OK; |
|
107 } |
|
108 |
|
109 |
|
110 // This is called when the tree construction has ended |
|
111 NS_IMETHODIMP |
|
112 nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated) |
|
113 { |
|
114 if (!aTerminated) { |
|
115 // This is needed to avoid unblocking loads too many times on one hand |
|
116 // and on the other hand to avoid destroying the frame constructor from |
|
117 // within an update batch. See bug 537683. |
|
118 EndDocUpdate(); |
|
119 |
|
120 // If the above caused a call to nsIParser::Terminate(), let that call |
|
121 // win. |
|
122 if (!mParser) { |
|
123 return NS_OK; |
|
124 } |
|
125 } |
|
126 |
|
127 if (mRunsToCompletion) { |
|
128 return NS_OK; |
|
129 } |
|
130 |
|
131 GetParser()->DropStreamParser(); |
|
132 |
|
133 // This comes from nsXMLContentSink and nsHTMLContentSink |
|
134 // If this parser has been marked as broken, treat the end of parse as |
|
135 // forced termination. |
|
136 DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken())); |
|
137 |
|
138 if (!mLayoutStarted) { |
|
139 // We never saw the body, and layout never got started. Force |
|
140 // layout *now*, to get an initial reflow. |
|
141 |
|
142 // NOTE: only force the layout if we are NOT destroying the |
|
143 // docshell. If we are destroying it, then starting layout will |
|
144 // likely cause us to crash, or at best waste a lot of time as we |
|
145 // are just going to tear it down anyway. |
|
146 bool destroying = true; |
|
147 if (mDocShell) { |
|
148 mDocShell->IsBeingDestroyed(&destroying); |
|
149 } |
|
150 |
|
151 if (!destroying) { |
|
152 nsContentSink::StartLayout(false); |
|
153 } |
|
154 } |
|
155 |
|
156 ScrollToRef(); |
|
157 mDocument->RemoveObserver(this); |
|
158 if (!mParser) { |
|
159 // DidBuildModelImpl may cause mParser to be nulled out |
|
160 // Return early to avoid unblocking the onload event too many times. |
|
161 return NS_OK; |
|
162 } |
|
163 |
|
164 // We may not have called BeginLoad() if loading is terminated before |
|
165 // OnStartRequest call. |
|
166 if (mStarted) { |
|
167 mDocument->EndLoad(); |
|
168 } |
|
169 DropParserAndPerfHint(); |
|
170 #ifdef GATHER_DOCWRITE_STATISTICS |
|
171 printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites); |
|
172 printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites); |
|
173 printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites); |
|
174 #endif |
|
175 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH |
|
176 printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize); |
|
177 if (sAppendBatchExaminations != 0) { |
|
178 printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations); |
|
179 } |
|
180 #endif |
|
181 return NS_OK; |
|
182 } |
|
183 |
|
184 NS_IMETHODIMP |
|
185 nsHtml5TreeOpExecutor::WillInterrupt() |
|
186 { |
|
187 NS_NOTREACHED("Don't call. For interface compat only."); |
|
188 return NS_ERROR_NOT_IMPLEMENTED; |
|
189 } |
|
190 |
|
191 NS_IMETHODIMP |
|
192 nsHtml5TreeOpExecutor::WillResume() |
|
193 { |
|
194 NS_NOTREACHED("Don't call. For interface compat only."); |
|
195 return NS_ERROR_NOT_IMPLEMENTED; |
|
196 } |
|
197 |
|
198 NS_IMETHODIMP |
|
199 nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser) |
|
200 { |
|
201 mParser = aParser; |
|
202 return NS_OK; |
|
203 } |
|
204 |
|
205 void |
|
206 nsHtml5TreeOpExecutor::FlushPendingNotifications(mozFlushType aType) |
|
207 { |
|
208 if (aType >= Flush_InterruptibleLayout) { |
|
209 // Bug 577508 / 253951 |
|
210 nsContentSink::StartLayout(true); |
|
211 } |
|
212 } |
|
213 |
|
214 nsISupports* |
|
215 nsHtml5TreeOpExecutor::GetTarget() |
|
216 { |
|
217 return mDocument; |
|
218 } |
|
219 |
|
220 nsresult |
|
221 nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) |
|
222 { |
|
223 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
224 mBroken = aReason; |
|
225 if (mStreamParser) { |
|
226 mStreamParser->Terminate(); |
|
227 } |
|
228 // We are under memory pressure, but let's hope the following allocation |
|
229 // works out so that we get to terminate and clean up the parser from |
|
230 // a safer point. |
|
231 if (mParser) { // can mParser ever be null here? |
|
232 nsCOMPtr<nsIRunnable> terminator = |
|
233 NS_NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate); |
|
234 if (NS_FAILED(NS_DispatchToMainThread(terminator))) { |
|
235 NS_WARNING("failed to dispatch executor flush event"); |
|
236 } |
|
237 } |
|
238 return aReason; |
|
239 } |
|
240 |
|
241 void |
|
242 FlushTimerCallback(nsITimer* aTimer, void* aClosure) |
|
243 { |
|
244 nsRefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst(); |
|
245 if (ex) { |
|
246 ex->RunFlushLoop(); |
|
247 } |
|
248 if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) { |
|
249 delete gBackgroundFlushList; |
|
250 gBackgroundFlushList = nullptr; |
|
251 gFlushTimer->Cancel(); |
|
252 NS_RELEASE(gFlushTimer); |
|
253 } |
|
254 } |
|
255 |
|
256 void |
|
257 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() |
|
258 { |
|
259 if (!mDocument || !mDocument->IsInBackgroundWindow()) { |
|
260 nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this); |
|
261 if (NS_FAILED(NS_DispatchToMainThread(flusher))) { |
|
262 NS_WARNING("failed to dispatch executor flush event"); |
|
263 } |
|
264 } else { |
|
265 if (!gBackgroundFlushList) { |
|
266 gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>(); |
|
267 } |
|
268 if (!isInList()) { |
|
269 gBackgroundFlushList->insertBack(this); |
|
270 } |
|
271 if (!gFlushTimer) { |
|
272 nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1"); |
|
273 t.swap(gFlushTimer); |
|
274 // The timer value 50 should not hopefully slow down background pages too |
|
275 // much, yet lets event loop to process enough between ticks. |
|
276 // See bug 734015. |
|
277 gFlushTimer->InitWithFuncCallback(FlushTimerCallback, nullptr, |
|
278 50, nsITimer::TYPE_REPEATING_SLACK); |
|
279 } |
|
280 } |
|
281 } |
|
282 |
|
283 void |
|
284 nsHtml5TreeOpExecutor::FlushSpeculativeLoads() |
|
285 { |
|
286 nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue; |
|
287 mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue); |
|
288 const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); |
|
289 const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); |
|
290 for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start); |
|
291 iter < end; |
|
292 ++iter) { |
|
293 if (MOZ_UNLIKELY(!mParser)) { |
|
294 // An extension terminated the parser from a HTTP observer. |
|
295 return; |
|
296 } |
|
297 iter->Perform(this); |
|
298 } |
|
299 } |
|
300 |
|
301 class nsHtml5FlushLoopGuard |
|
302 { |
|
303 private: |
|
304 nsRefPtr<nsHtml5TreeOpExecutor> mExecutor; |
|
305 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH |
|
306 uint32_t mStartTime; |
|
307 #endif |
|
308 public: |
|
309 nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor) |
|
310 : mExecutor(aExecutor) |
|
311 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH |
|
312 , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow())) |
|
313 #endif |
|
314 { |
|
315 mExecutor->mRunFlushLoopOnStack = true; |
|
316 } |
|
317 ~nsHtml5FlushLoopGuard() |
|
318 { |
|
319 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH |
|
320 uint32_t timeOffTheEventLoop = |
|
321 PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime; |
|
322 if (timeOffTheEventLoop > |
|
323 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) { |
|
324 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = |
|
325 timeOffTheEventLoop; |
|
326 } |
|
327 printf("Longest time off the event loop: %d\n", |
|
328 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop); |
|
329 #endif |
|
330 |
|
331 mExecutor->mRunFlushLoopOnStack = false; |
|
332 } |
|
333 }; |
|
334 |
|
335 /** |
|
336 * The purpose of the loop here is to avoid returning to the main event loop |
|
337 */ |
|
338 void |
|
339 nsHtml5TreeOpExecutor::RunFlushLoop() |
|
340 { |
|
341 PROFILER_LABEL("html5", "RunFlushLoop"); |
|
342 if (mRunFlushLoopOnStack) { |
|
343 // There's already a RunFlushLoop() on the call stack. |
|
344 return; |
|
345 } |
|
346 |
|
347 nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu! |
|
348 |
|
349 nsCOMPtr<nsISupports> parserKungFuDeathGrip(mParser); |
|
350 |
|
351 // Remember the entry time |
|
352 (void) nsContentSink::WillParseImpl(); |
|
353 |
|
354 for (;;) { |
|
355 if (!mParser) { |
|
356 // Parse has terminated. |
|
357 mOpQueue.Clear(); // clear in order to be able to assert in destructor |
|
358 return; |
|
359 } |
|
360 |
|
361 if (NS_FAILED(IsBroken())) { |
|
362 return; |
|
363 } |
|
364 |
|
365 if (!mParser->IsParserEnabled()) { |
|
366 // The parser is blocked. |
|
367 return; |
|
368 } |
|
369 |
|
370 if (mFlushState != eNotFlushing) { |
|
371 // XXX Can this happen? In case it can, let's avoid crashing. |
|
372 return; |
|
373 } |
|
374 |
|
375 // If there are scripts executing, then the content sink is jumping the gun |
|
376 // (probably due to a synchronous XMLHttpRequest) and will re-enable us |
|
377 // later, see bug 460706. |
|
378 if (IsScriptExecuting()) { |
|
379 return; |
|
380 } |
|
381 |
|
382 if (mReadingFromStage) { |
|
383 nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue; |
|
384 mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue); |
|
385 // Make sure speculative loads never start after the corresponding |
|
386 // normal loads for the same URLs. |
|
387 const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); |
|
388 const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); |
|
389 for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start; |
|
390 iter < end; |
|
391 ++iter) { |
|
392 iter->Perform(this); |
|
393 if (MOZ_UNLIKELY(!mParser)) { |
|
394 // An extension terminated the parser from a HTTP observer. |
|
395 mOpQueue.Clear(); // clear in order to be able to assert in destructor |
|
396 return; |
|
397 } |
|
398 } |
|
399 } else { |
|
400 FlushSpeculativeLoads(); // Make sure speculative loads never start after |
|
401 // the corresponding normal loads for the same |
|
402 // URLs. |
|
403 if (MOZ_UNLIKELY(!mParser)) { |
|
404 // An extension terminated the parser from a HTTP observer. |
|
405 mOpQueue.Clear(); // clear in order to be able to assert in destructor |
|
406 return; |
|
407 } |
|
408 // Not sure if this grip is still needed, but previously, the code |
|
409 // gripped before calling ParseUntilBlocked(); |
|
410 nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip = |
|
411 GetParser()->GetStreamParser(); |
|
412 // Now parse content left in the document.write() buffer queue if any. |
|
413 // This may generate tree ops on its own or dequeue a speculation. |
|
414 nsresult rv = GetParser()->ParseUntilBlocked(); |
|
415 if (NS_FAILED(rv)) { |
|
416 MarkAsBroken(rv); |
|
417 return; |
|
418 } |
|
419 } |
|
420 |
|
421 if (mOpQueue.IsEmpty()) { |
|
422 // Avoid bothering the rest of the engine with a doc update if there's |
|
423 // nothing to do. |
|
424 return; |
|
425 } |
|
426 |
|
427 mFlushState = eInFlush; |
|
428 |
|
429 nsIContent* scriptElement = nullptr; |
|
430 |
|
431 BeginDocUpdate(); |
|
432 |
|
433 uint32_t numberOfOpsToFlush = mOpQueue.Length(); |
|
434 |
|
435 SetAppendBatchCapacity(numberOfOpsToFlush * 2); |
|
436 |
|
437 const nsHtml5TreeOperation* first = mOpQueue.Elements(); |
|
438 const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1; |
|
439 for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) { |
|
440 if (MOZ_UNLIKELY(!mParser)) { |
|
441 // The previous tree op caused a call to nsIParser::Terminate(). |
|
442 break; |
|
443 } |
|
444 NS_ASSERTION(mFlushState == eInDocUpdate, |
|
445 "Tried to perform tree op outside update batch."); |
|
446 nsresult rv = iter->Perform(this, &scriptElement); |
|
447 if (NS_FAILED(rv)) { |
|
448 MarkAsBroken(rv); |
|
449 break; |
|
450 } |
|
451 |
|
452 // Be sure not to check the deadline if the last op was just performed. |
|
453 if (MOZ_UNLIKELY(iter == last)) { |
|
454 break; |
|
455 } else if (MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() == |
|
456 NS_ERROR_HTMLPARSER_INTERRUPTED)) { |
|
457 mOpQueue.RemoveElementsAt(0, (iter - first) + 1); |
|
458 |
|
459 EndDocUpdate(); |
|
460 |
|
461 mFlushState = eNotFlushing; |
|
462 |
|
463 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH |
|
464 printf("REFLUSH SCHEDULED (executing ops): %d\n", |
|
465 ++sTimesFlushLoopInterrupted); |
|
466 #endif |
|
467 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); |
|
468 return; |
|
469 } |
|
470 ++iter; |
|
471 } |
|
472 |
|
473 mOpQueue.Clear(); |
|
474 |
|
475 EndDocUpdate(); |
|
476 |
|
477 mFlushState = eNotFlushing; |
|
478 |
|
479 if (MOZ_UNLIKELY(!mParser)) { |
|
480 // The parse ended already. |
|
481 return; |
|
482 } |
|
483 |
|
484 if (scriptElement) { |
|
485 // must be tail call when mFlushState is eNotFlushing |
|
486 RunScript(scriptElement); |
|
487 |
|
488 // Always check the clock in nsContentSink right after a script |
|
489 StopDeflecting(); |
|
490 if (nsContentSink::DidProcessATokenImpl() == |
|
491 NS_ERROR_HTMLPARSER_INTERRUPTED) { |
|
492 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH |
|
493 printf("REFLUSH SCHEDULED (after script): %d\n", |
|
494 ++sTimesFlushLoopInterrupted); |
|
495 #endif |
|
496 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); |
|
497 return; |
|
498 } |
|
499 } |
|
500 } |
|
501 } |
|
502 |
|
503 nsresult |
|
504 nsHtml5TreeOpExecutor::FlushDocumentWrite() |
|
505 { |
|
506 nsresult rv = IsBroken(); |
|
507 NS_ENSURE_SUCCESS(rv, rv); |
|
508 |
|
509 FlushSpeculativeLoads(); // Make sure speculative loads never start after the |
|
510 // corresponding normal loads for the same URLs. |
|
511 |
|
512 if (MOZ_UNLIKELY(!mParser)) { |
|
513 // The parse has ended. |
|
514 mOpQueue.Clear(); // clear in order to be able to assert in destructor |
|
515 return rv; |
|
516 } |
|
517 |
|
518 if (mFlushState != eNotFlushing) { |
|
519 // XXX Can this happen? In case it can, let's avoid crashing. |
|
520 return rv; |
|
521 } |
|
522 |
|
523 mFlushState = eInFlush; |
|
524 |
|
525 // avoid crashing near EOF |
|
526 nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); |
|
527 nsRefPtr<nsParserBase> parserKungFuDeathGrip(mParser); |
|
528 |
|
529 NS_ASSERTION(!mReadingFromStage, |
|
530 "Got doc write flush when reading from stage"); |
|
531 |
|
532 #ifdef DEBUG |
|
533 mStage.AssertEmpty(); |
|
534 #endif |
|
535 |
|
536 nsIContent* scriptElement = nullptr; |
|
537 |
|
538 BeginDocUpdate(); |
|
539 |
|
540 uint32_t numberOfOpsToFlush = mOpQueue.Length(); |
|
541 |
|
542 SetAppendBatchCapacity(numberOfOpsToFlush * 2); |
|
543 |
|
544 const nsHtml5TreeOperation* start = mOpQueue.Elements(); |
|
545 const nsHtml5TreeOperation* end = start + numberOfOpsToFlush; |
|
546 for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start); |
|
547 iter < end; |
|
548 ++iter) { |
|
549 if (MOZ_UNLIKELY(!mParser)) { |
|
550 // The previous tree op caused a call to nsIParser::Terminate(). |
|
551 break; |
|
552 } |
|
553 NS_ASSERTION(mFlushState == eInDocUpdate, |
|
554 "Tried to perform tree op outside update batch."); |
|
555 rv = iter->Perform(this, &scriptElement); |
|
556 if (NS_FAILED(rv)) { |
|
557 MarkAsBroken(rv); |
|
558 break; |
|
559 } |
|
560 } |
|
561 |
|
562 mOpQueue.Clear(); |
|
563 |
|
564 EndDocUpdate(); |
|
565 |
|
566 mFlushState = eNotFlushing; |
|
567 |
|
568 if (MOZ_UNLIKELY(!mParser)) { |
|
569 // Ending the doc update caused a call to nsIParser::Terminate(). |
|
570 return rv; |
|
571 } |
|
572 |
|
573 if (scriptElement) { |
|
574 // must be tail call when mFlushState is eNotFlushing |
|
575 RunScript(scriptElement); |
|
576 } |
|
577 return rv; |
|
578 } |
|
579 |
|
580 // copied from HTML content sink |
|
581 bool |
|
582 nsHtml5TreeOpExecutor::IsScriptEnabled() |
|
583 { |
|
584 if (!mDocument || !mDocShell) |
|
585 return true; |
|
586 nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(mDocument->GetInnerWindow()); |
|
587 // Getting context is tricky if the document hasn't had its |
|
588 // GlobalObject set yet |
|
589 if (!globalObject) { |
|
590 globalObject = mDocShell->GetScriptGlobalObject(); |
|
591 NS_ENSURE_TRUE(globalObject, true); |
|
592 } |
|
593 NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true); |
|
594 return nsContentUtils::GetSecurityManager()-> |
|
595 ScriptAllowed(globalObject->GetGlobalJSObject()); |
|
596 } |
|
597 |
|
598 void |
|
599 nsHtml5TreeOpExecutor::StartLayout() { |
|
600 if (mLayoutStarted || !mDocument) { |
|
601 return; |
|
602 } |
|
603 |
|
604 EndDocUpdate(); |
|
605 |
|
606 if (MOZ_UNLIKELY(!mParser)) { |
|
607 // got terminate |
|
608 return; |
|
609 } |
|
610 |
|
611 nsContentSink::StartLayout(false); |
|
612 |
|
613 BeginDocUpdate(); |
|
614 } |
|
615 |
|
616 /** |
|
617 * The reason why this code is here and not in the tree builder even in the |
|
618 * main-thread case is to allow the control to return from the tokenizer |
|
619 * before scripts run. This way, the tokenizer is not invoked re-entrantly |
|
620 * although the parser is. |
|
621 * |
|
622 * The reason why this is called as a tail call when mFlushState is set to |
|
623 * eNotFlushing is to allow re-entry to Flush() but only after the current |
|
624 * Flush() has cleared the op queue and is otherwise done cleaning up after |
|
625 * itself. |
|
626 */ |
|
627 void |
|
628 nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement) |
|
629 { |
|
630 if (mRunsToCompletion) { |
|
631 // We are in createContextualFragment() or in the upcoming document.parse(). |
|
632 // Do nothing. Let's not even mark scripts malformed here, because that |
|
633 // could cause serialization weirdness later. |
|
634 return; |
|
635 } |
|
636 |
|
637 NS_ASSERTION(aScriptElement, "No script to run"); |
|
638 nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement); |
|
639 |
|
640 if (!mParser) { |
|
641 NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed."); |
|
642 // We got here not because of an end tag but because the tree builder |
|
643 // popped an incomplete script element on EOF. Returning here to avoid |
|
644 // calling back into mParser anymore. |
|
645 return; |
|
646 } |
|
647 |
|
648 if (sele->GetScriptDeferred() || sele->GetScriptAsync()) { |
|
649 DebugOnly<bool> block = sele->AttemptToExecute(); |
|
650 NS_ASSERTION(!block, "Defer or async script tried to block."); |
|
651 return; |
|
652 } |
|
653 |
|
654 NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing."); |
|
655 |
|
656 mReadingFromStage = false; |
|
657 |
|
658 sele->SetCreatorParser(GetParser()); |
|
659 |
|
660 // Copied from nsXMLContentSink |
|
661 // Now tell the script that it's ready to go. This may execute the script |
|
662 // or return true, or neither if the script doesn't need executing. |
|
663 bool block = sele->AttemptToExecute(); |
|
664 |
|
665 // If the act of insertion evaluated the script, we're fine. |
|
666 // Else, block the parser till the script has loaded. |
|
667 if (block) { |
|
668 if (mParser) { |
|
669 GetParser()->BlockParser(); |
|
670 } |
|
671 } else { |
|
672 // mParser may have been nulled out by now, but the flusher deals |
|
673 |
|
674 // If this event isn't needed, it doesn't do anything. It is sometimes |
|
675 // necessary for the parse to continue after complex situations. |
|
676 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); |
|
677 } |
|
678 } |
|
679 |
|
680 void |
|
681 nsHtml5TreeOpExecutor::Start() |
|
682 { |
|
683 NS_PRECONDITION(!mStarted, "Tried to start when already started."); |
|
684 mStarted = true; |
|
685 } |
|
686 |
|
687 void |
|
688 nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding, |
|
689 int32_t aSource, |
|
690 uint32_t aLineNumber) |
|
691 { |
|
692 EndDocUpdate(); |
|
693 |
|
694 if (MOZ_UNLIKELY(!mParser)) { |
|
695 // got terminate |
|
696 return; |
|
697 } |
|
698 |
|
699 nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell); |
|
700 if (!wss) { |
|
701 return; |
|
702 } |
|
703 |
|
704 // ask the webshellservice to load the URL |
|
705 if (NS_SUCCEEDED(wss->StopDocumentLoad())) { |
|
706 wss->ReloadDocument(aEncoding, aSource); |
|
707 } |
|
708 // if the charset switch was accepted, wss has called Terminate() on the |
|
709 // parser by now |
|
710 |
|
711 if (!mParser) { |
|
712 // success |
|
713 if (aSource == kCharsetFromMetaTag) { |
|
714 MaybeComplainAboutCharset("EncLateMetaReload", false, aLineNumber); |
|
715 } |
|
716 return; |
|
717 } |
|
718 |
|
719 if (aSource == kCharsetFromMetaTag) { |
|
720 MaybeComplainAboutCharset("EncLateMetaTooLate", true, aLineNumber); |
|
721 } |
|
722 |
|
723 GetParser()->ContinueAfterFailedCharsetSwitch(); |
|
724 |
|
725 BeginDocUpdate(); |
|
726 } |
|
727 |
|
728 void |
|
729 nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId, |
|
730 bool aError, |
|
731 uint32_t aLineNumber) |
|
732 { |
|
733 if (mAlreadyComplainedAboutCharset) { |
|
734 return; |
|
735 } |
|
736 // The EncNoDeclaration case for advertising iframes is so common that it |
|
737 // would result is way too many errors. The iframe case doesn't matter |
|
738 // when the ad is an image or a Flash animation anyway. When the ad is |
|
739 // textual, a misrendered ad probably isn't a huge loss for users. |
|
740 // Let's suppress the message in this case. |
|
741 // This means that errors about other different-origin iframes in mashups |
|
742 // are lost as well, but generally, the site author isn't in control of |
|
743 // the embedded different-origin pages anyway and can't fix problems even |
|
744 // if alerted about them. |
|
745 if (!strcmp(aMsgId, "EncNoDeclaration") && mDocShell) { |
|
746 nsCOMPtr<nsIDocShellTreeItem> parent; |
|
747 mDocShell->GetSameTypeParent(getter_AddRefs(parent)); |
|
748 if (parent) { |
|
749 return; |
|
750 } |
|
751 } |
|
752 mAlreadyComplainedAboutCharset = true; |
|
753 nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag |
|
754 : nsIScriptError::warningFlag, |
|
755 NS_LITERAL_CSTRING("HTML parser"), |
|
756 mDocument, |
|
757 nsContentUtils::eHTMLPARSER_PROPERTIES, |
|
758 aMsgId, |
|
759 nullptr, |
|
760 0, |
|
761 nullptr, |
|
762 EmptyString(), |
|
763 aLineNumber); |
|
764 } |
|
765 |
|
766 void |
|
767 nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(nsIDocument* aDoc) |
|
768 { |
|
769 NS_ASSERTION(!mAlreadyComplainedAboutCharset, |
|
770 "How come we already managed to complain?"); |
|
771 mAlreadyComplainedAboutCharset = true; |
|
772 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, |
|
773 NS_LITERAL_CSTRING("HTML parser"), |
|
774 aDoc, |
|
775 nsContentUtils::eHTMLPARSER_PROPERTIES, |
|
776 "EncProtocolUnsupported"); |
|
777 } |
|
778 |
|
779 nsHtml5Parser* |
|
780 nsHtml5TreeOpExecutor::GetParser() |
|
781 { |
|
782 MOZ_ASSERT(!mRunsToCompletion); |
|
783 return static_cast<nsHtml5Parser*>(mParser.get()); |
|
784 } |
|
785 |
|
786 void |
|
787 nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue) |
|
788 { |
|
789 NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution."); |
|
790 if (mOpQueue.IsEmpty()) { |
|
791 mOpQueue.SwapElements(aOpQueue); |
|
792 return; |
|
793 } |
|
794 mOpQueue.MoveElementsFrom(aOpQueue); |
|
795 } |
|
796 |
|
797 void |
|
798 nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine) |
|
799 { |
|
800 GetParser()->InitializeDocWriteParserState(aState, aLine); |
|
801 } |
|
802 |
|
803 nsIURI* |
|
804 nsHtml5TreeOpExecutor::GetViewSourceBaseURI() |
|
805 { |
|
806 if (!mViewSourceBaseURI) { |
|
807 |
|
808 // We query the channel for the baseURI because in certain situations it |
|
809 // cannot otherwise be determined. If this process fails, fall back to the |
|
810 // standard method. |
|
811 nsCOMPtr<nsIViewSourceChannel> vsc; |
|
812 vsc = do_QueryInterface(mDocument->GetChannel()); |
|
813 if (vsc) { |
|
814 nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI)); |
|
815 if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) { |
|
816 return mViewSourceBaseURI; |
|
817 } |
|
818 } |
|
819 |
|
820 nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI(); |
|
821 bool isViewSource; |
|
822 orig->SchemeIs("view-source", &isViewSource); |
|
823 if (isViewSource) { |
|
824 nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig); |
|
825 NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!"); |
|
826 nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI)); |
|
827 } else { |
|
828 // Fail gracefully if the base URL isn't a view-source: URL. |
|
829 // Not sure if this can ever happen. |
|
830 mViewSourceBaseURI = orig; |
|
831 } |
|
832 } |
|
833 return mViewSourceBaseURI; |
|
834 } |
|
835 |
|
836 //static |
|
837 void |
|
838 nsHtml5TreeOpExecutor::InitializeStatics() |
|
839 { |
|
840 mozilla::Preferences::AddBoolVarCache(&sExternalViewSource, |
|
841 "view_source.editor.external"); |
|
842 } |
|
843 |
|
844 bool |
|
845 nsHtml5TreeOpExecutor::IsExternalViewSource() |
|
846 { |
|
847 if (!sExternalViewSource) { |
|
848 return false; |
|
849 } |
|
850 bool isViewSource = false; |
|
851 if (mDocumentURI) { |
|
852 mDocumentURI->SchemeIs("view-source", &isViewSource); |
|
853 } |
|
854 return isViewSource; |
|
855 } |
|
856 |
|
857 // Speculative loading |
|
858 |
|
859 already_AddRefed<nsIURI> |
|
860 nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL) |
|
861 { |
|
862 if (aURL.IsEmpty()) { |
|
863 return nullptr; |
|
864 } |
|
865 // The URL of the document without <base> |
|
866 nsIURI* documentURI = mDocument->GetDocumentURI(); |
|
867 // The URL of the document with non-speculative <base> |
|
868 nsIURI* documentBaseURI = mDocument->GetDocBaseURI(); |
|
869 |
|
870 // If the two above are different, use documentBaseURI. If they are the |
|
871 // same, the document object isn't aware of a <base>, so attempt to use the |
|
872 // mSpeculationBaseURI or, failing, that, documentURI. |
|
873 nsIURI* base = (documentURI == documentBaseURI) ? |
|
874 (mSpeculationBaseURI ? |
|
875 mSpeculationBaseURI.get() : documentURI) |
|
876 : documentBaseURI; |
|
877 const nsCString& charset = mDocument->GetDocumentCharacterSet(); |
|
878 nsCOMPtr<nsIURI> uri; |
|
879 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base); |
|
880 if (NS_FAILED(rv)) { |
|
881 NS_WARNING("Failed to create a URI"); |
|
882 return nullptr; |
|
883 } |
|
884 nsAutoCString spec; |
|
885 uri->GetSpec(spec); |
|
886 if (mPreloadedURLs.Contains(spec)) { |
|
887 return nullptr; |
|
888 } |
|
889 mPreloadedURLs.PutEntry(spec); |
|
890 return uri.forget(); |
|
891 } |
|
892 |
|
893 void |
|
894 nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL, |
|
895 const nsAString& aCharset, |
|
896 const nsAString& aType, |
|
897 const nsAString& aCrossOrigin, |
|
898 bool aScriptFromHead) |
|
899 { |
|
900 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); |
|
901 if (!uri) { |
|
902 return; |
|
903 } |
|
904 mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin, |
|
905 aScriptFromHead); |
|
906 } |
|
907 |
|
908 void |
|
909 nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL, |
|
910 const nsAString& aCharset, |
|
911 const nsAString& aCrossOrigin) |
|
912 { |
|
913 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); |
|
914 if (!uri) { |
|
915 return; |
|
916 } |
|
917 mDocument->PreloadStyle(uri, aCharset, aCrossOrigin); |
|
918 } |
|
919 |
|
920 void |
|
921 nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL, |
|
922 const nsAString& aCrossOrigin) |
|
923 { |
|
924 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); |
|
925 if (!uri) { |
|
926 return; |
|
927 } |
|
928 mDocument->MaybePreLoadImage(uri, aCrossOrigin); |
|
929 } |
|
930 |
|
931 void |
|
932 nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL) |
|
933 { |
|
934 if (mSpeculationBaseURI) { |
|
935 // the first one wins |
|
936 return; |
|
937 } |
|
938 const nsCString& charset = mDocument->GetDocumentCharacterSet(); |
|
939 DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL, |
|
940 charset.get(), mDocument->GetDocumentURI()); |
|
941 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to create a URI"); |
|
942 } |
|
943 |
|
944 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH |
|
945 uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0; |
|
946 uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0; |
|
947 uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0; |
|
948 uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0; |
|
949 uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0; |
|
950 #endif |
|
951 bool nsHtml5TreeOpExecutor::sExternalViewSource = false; |