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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 1998
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Pierre Phaneuf <pp@ludusdesign.com>
25 : * Henri Sivonen <hsivonen@iki.fi>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "nsHtml5TreeOpExecutor.h"
42 : #include "nsScriptLoader.h"
43 : #include "nsIMarkupDocumentViewer.h"
44 : #include "nsIContentViewer.h"
45 : #include "nsIDocShellTreeItem.h"
46 : #include "nsIStyleSheetLinkingElement.h"
47 : #include "nsStyleLinkElement.h"
48 : #include "nsIDocShell.h"
49 : #include "nsIScriptGlobalObject.h"
50 : #include "nsIScriptGlobalObjectOwner.h"
51 : #include "nsIScriptSecurityManager.h"
52 : #include "nsIWebShellServices.h"
53 : #include "nsContentUtils.h"
54 : #include "mozAutoDocUpdate.h"
55 : #include "nsNetUtil.h"
56 : #include "nsHtml5Parser.h"
57 : #include "nsHtml5Tokenizer.h"
58 : #include "nsHtml5TreeBuilder.h"
59 : #include "nsHtml5StreamParser.h"
60 : #include "mozilla/css/Loader.h"
61 : #include "mozilla/Util.h" // DebugOnly
62 : #include "sampler.h"
63 :
64 : using namespace mozilla;
65 :
66 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5TreeOpExecutor)
67 :
68 312 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor)
69 41 : NS_INTERFACE_TABLE_INHERITED1(nsHtml5TreeOpExecutor,
70 : nsIContentSink)
71 41 : NS_INTERFACE_TABLE_TAIL_INHERITING(nsContentSink)
72 :
73 246 : NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
74 :
75 246 : NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
76 :
77 2 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
78 2 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_OF_NSCOMPTR(mOwnedElements)
79 2 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
80 :
81 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
82 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mOwnedElements)
83 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
84 :
85 : class nsHtml5ExecutorReflusher : public nsRunnable
86 0 : {
87 : private:
88 : nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
89 : public:
90 0 : nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
91 0 : : mExecutor(aExecutor)
92 0 : {}
93 0 : NS_IMETHODIMP Run()
94 : {
95 0 : mExecutor->RunFlushLoop();
96 0 : return NS_OK;
97 : }
98 : };
99 :
100 12 : nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor(bool aRunsToCompletion)
101 : {
102 12 : mRunsToCompletion = aRunsToCompletion;
103 12 : mPreloadedURLs.Init(23); // Mean # of preloadable resources per page on dmoz
104 : // zeroing operator new for everything else
105 12 : }
106 :
107 36 : nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
108 : {
109 12 : NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
110 24 : }
111 :
112 : // nsIContentSink
113 : NS_IMETHODIMP
114 0 : nsHtml5TreeOpExecutor::WillParse()
115 : {
116 0 : NS_NOTREACHED("No one should call this");
117 0 : return NS_ERROR_NOT_IMPLEMENTED;
118 : }
119 :
120 : // This is called when the tree construction has ended
121 : NS_IMETHODIMP
122 234 : nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated)
123 : {
124 234 : NS_PRECONDITION(mStarted, "Bad life cycle.");
125 :
126 234 : if (!aTerminated) {
127 : // This is needed to avoid unblocking loads too many times on one hand
128 : // and on the other hand to avoid destroying the frame constructor from
129 : // within an update batch. See bug 537683.
130 234 : EndDocUpdate();
131 :
132 : // If the above caused a call to nsIParser::Terminate(), let that call
133 : // win.
134 234 : if (!mParser) {
135 0 : return NS_OK;
136 : }
137 : }
138 :
139 234 : if (mRunsToCompletion) {
140 234 : return NS_OK;
141 : }
142 :
143 0 : GetParser()->DropStreamParser();
144 :
145 : // This comes from nsXMLContentSink and nsHTMLContentSink
146 0 : DidBuildModelImpl(aTerminated);
147 :
148 0 : if (!mLayoutStarted) {
149 : // We never saw the body, and layout never got started. Force
150 : // layout *now*, to get an initial reflow.
151 :
152 : // NOTE: only force the layout if we are NOT destroying the
153 : // docshell. If we are destroying it, then starting layout will
154 : // likely cause us to crash, or at best waste a lot of time as we
155 : // are just going to tear it down anyway.
156 0 : bool destroying = true;
157 0 : if (mDocShell) {
158 0 : mDocShell->IsBeingDestroyed(&destroying);
159 : }
160 :
161 0 : if (!destroying) {
162 0 : nsContentSink::StartLayout(false);
163 : }
164 : }
165 :
166 0 : ScrollToRef();
167 0 : mDocument->RemoveObserver(this);
168 0 : if (!mParser) {
169 : // DidBuildModelImpl may cause mParser to be nulled out
170 : // Return early to avoid unblocking the onload event too many times.
171 0 : return NS_OK;
172 : }
173 0 : mDocument->EndLoad();
174 0 : DropParserAndPerfHint();
175 : #ifdef GATHER_DOCWRITE_STATISTICS
176 : printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
177 : printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
178 : printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
179 : #endif
180 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
181 : printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize);
182 : if (sAppendBatchExaminations != 0) {
183 : printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations);
184 : }
185 : #endif
186 0 : return NS_OK;
187 : }
188 :
189 : NS_IMETHODIMP
190 0 : nsHtml5TreeOpExecutor::WillInterrupt()
191 : {
192 0 : NS_NOTREACHED("Don't call. For interface compat only.");
193 0 : return NS_ERROR_NOT_IMPLEMENTED;
194 : }
195 :
196 : NS_IMETHODIMP
197 0 : nsHtml5TreeOpExecutor::WillResume()
198 : {
199 0 : NS_NOTREACHED("Don't call. For interface compat only.");
200 0 : return NS_ERROR_NOT_IMPLEMENTED;
201 : }
202 :
203 : NS_IMETHODIMP
204 234 : nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser)
205 : {
206 234 : mParser = aParser;
207 234 : return NS_OK;
208 : }
209 :
210 : void
211 0 : nsHtml5TreeOpExecutor::FlushPendingNotifications(mozFlushType aType)
212 : {
213 0 : if (aType >= Flush_InterruptibleLayout) {
214 : // Bug 577508 / 253951
215 0 : nsContentSink::StartLayout(true);
216 : }
217 0 : }
218 :
219 : void
220 0 : nsHtml5TreeOpExecutor::SetDocumentCharsetAndSource(nsACString& aCharset, PRInt32 aCharsetSource)
221 : {
222 0 : if (mDocument) {
223 0 : mDocument->SetDocumentCharacterSetSource(aCharsetSource);
224 0 : mDocument->SetDocumentCharacterSet(aCharset);
225 : }
226 0 : if (mDocShell) {
227 : // the following logic to get muCV is copied from
228 : // nsHTMLDocument::StartDocumentLoad
229 : // We need to call muCV->SetPrevDocCharacterSet here in case
230 : // the charset is detected by parser DetectMetaTag
231 0 : nsCOMPtr<nsIMarkupDocumentViewer> mucv;
232 0 : nsCOMPtr<nsIContentViewer> cv;
233 0 : mDocShell->GetContentViewer(getter_AddRefs(cv));
234 0 : if (cv) {
235 0 : mucv = do_QueryInterface(cv);
236 : } else {
237 : // in this block of code, if we get an error result, we return
238 : // it but if we get a null pointer, that's perfectly legal for
239 : // parent and parentContentViewer
240 : nsCOMPtr<nsIDocShellTreeItem> docShellAsItem =
241 0 : do_QueryInterface(mDocShell);
242 0 : if (!docShellAsItem) {
243 : return;
244 : }
245 0 : nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
246 0 : docShellAsItem->GetSameTypeParent(getter_AddRefs(parentAsItem));
247 0 : nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));
248 0 : if (parent) {
249 0 : nsCOMPtr<nsIContentViewer> parentContentViewer;
250 : nsresult rv =
251 0 : parent->GetContentViewer(getter_AddRefs(parentContentViewer));
252 0 : if (NS_SUCCEEDED(rv) && parentContentViewer) {
253 0 : mucv = do_QueryInterface(parentContentViewer);
254 : }
255 : }
256 : }
257 0 : if (mucv) {
258 0 : mucv->SetPrevDocCharacterSet(aCharset);
259 : }
260 : }
261 : }
262 :
263 : nsISupports*
264 0 : nsHtml5TreeOpExecutor::GetTarget()
265 : {
266 0 : return mDocument;
267 : }
268 :
269 : // nsContentSink overrides
270 :
271 : void
272 0 : nsHtml5TreeOpExecutor::UpdateChildCounts()
273 : {
274 : // No-op
275 0 : }
276 :
277 : void
278 0 : nsHtml5TreeOpExecutor::MarkAsBroken()
279 : {
280 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
281 0 : NS_ASSERTION(!mRunsToCompletion, "Fragment parsers can't be broken!");
282 0 : mBroken = true;
283 0 : if (mStreamParser) {
284 0 : mStreamParser->Terminate();
285 : }
286 : // We are under memory pressure, but let's hope the following allocation
287 : // works out so that we get to terminate and clean up the parser from
288 : // a safer point.
289 0 : if (mParser) { // can mParser ever be null here?
290 : nsCOMPtr<nsIRunnable> terminator =
291 0 : NS_NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate);
292 0 : if (NS_FAILED(NS_DispatchToMainThread(terminator))) {
293 0 : NS_WARNING("failed to dispatch executor flush event");
294 : }
295 : }
296 0 : }
297 :
298 : nsresult
299 12 : nsHtml5TreeOpExecutor::FlushTags()
300 : {
301 12 : return NS_OK;
302 : }
303 :
304 : void
305 0 : nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
306 : {
307 0 : nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
308 0 : if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
309 0 : NS_WARNING("failed to dispatch executor flush event");
310 : }
311 0 : }
312 :
313 : void
314 0 : nsHtml5TreeOpExecutor::UpdateStyleSheet(nsIContent* aElement)
315 : {
316 : // Break out of the doc update created by Flush() to zap a runnable
317 : // waiting to call UpdateStyleSheet without the right observer
318 0 : EndDocUpdate();
319 :
320 0 : if (NS_UNLIKELY(!mParser)) {
321 : // EndDocUpdate ran stuff that called nsIParser::Terminate()
322 0 : return;
323 : }
324 :
325 0 : nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aElement));
326 0 : NS_ASSERTION(ssle, "Node didn't QI to style.");
327 :
328 0 : ssle->SetEnableUpdates(true);
329 :
330 : bool willNotify;
331 : bool isAlternate;
332 0 : nsresult rv = ssle->UpdateStyleSheet(mRunsToCompletion ? nsnull : this,
333 : &willNotify,
334 0 : &isAlternate);
335 0 : if (NS_SUCCEEDED(rv) && willNotify && !isAlternate && !mRunsToCompletion) {
336 0 : ++mPendingSheetCount;
337 0 : mScriptLoader->AddExecuteBlocker();
338 : }
339 :
340 0 : if (aElement->IsHTML(nsGkAtoms::link)) {
341 : // look for <link rel="next" href="url">
342 0 : nsAutoString relVal;
343 0 : aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relVal);
344 0 : if (!relVal.IsEmpty()) {
345 0 : PRUint32 linkTypes = nsStyleLinkElement::ParseLinkTypes(relVal);
346 0 : bool hasPrefetch = linkTypes & PREFETCH;
347 0 : if (hasPrefetch || (linkTypes & NEXT)) {
348 0 : nsAutoString hrefVal;
349 0 : aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
350 0 : if (!hrefVal.IsEmpty()) {
351 0 : PrefetchHref(hrefVal, aElement, hasPrefetch);
352 : }
353 : }
354 0 : if (linkTypes & DNS_PREFETCH) {
355 0 : nsAutoString hrefVal;
356 0 : aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
357 0 : if (!hrefVal.IsEmpty()) {
358 0 : PrefetchDNS(hrefVal);
359 : }
360 : }
361 : }
362 : }
363 :
364 : // Re-open update
365 0 : BeginDocUpdate();
366 : }
367 :
368 : void
369 234 : nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
370 : {
371 468 : nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
372 234 : mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
373 234 : const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
374 234 : const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
375 234 : for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start);
376 : iter < end;
377 : ++iter) {
378 0 : if (NS_UNLIKELY(!mParser)) {
379 : // An extension terminated the parser from a HTTP observer.
380 : return;
381 : }
382 0 : iter->Perform(this);
383 : }
384 : }
385 :
386 : class nsHtml5FlushLoopGuard
387 : {
388 : private:
389 : nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
390 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
391 : PRUint32 mStartTime;
392 : #endif
393 : public:
394 0 : nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
395 0 : : mExecutor(aExecutor)
396 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
397 : , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
398 : #endif
399 : {
400 0 : mExecutor->mRunFlushLoopOnStack = true;
401 0 : }
402 0 : ~nsHtml5FlushLoopGuard()
403 0 : {
404 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
405 : PRUint32 timeOffTheEventLoop =
406 : PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
407 : if (timeOffTheEventLoop >
408 : nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
409 : nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop =
410 : timeOffTheEventLoop;
411 : }
412 : printf("Longest time off the event loop: %d\n",
413 : nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop);
414 : #endif
415 :
416 0 : mExecutor->mRunFlushLoopOnStack = false;
417 0 : }
418 : };
419 :
420 : /**
421 : * The purpose of the loop here is to avoid returning to the main event loop
422 : */
423 : void
424 0 : nsHtml5TreeOpExecutor::RunFlushLoop()
425 : {
426 0 : SAMPLE_LABEL("html5", "RunFlushLoop");
427 0 : if (mRunFlushLoopOnStack) {
428 : // There's already a RunFlushLoop() on the call stack.
429 : return;
430 : }
431 :
432 0 : nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu!
433 :
434 0 : nsCOMPtr<nsISupports> parserKungFuDeathGrip(mParser);
435 :
436 : // Remember the entry time
437 0 : (void) nsContentSink::WillParseImpl();
438 :
439 0 : for (;;) {
440 0 : if (!mParser) {
441 : // Parse has terminated.
442 0 : mOpQueue.Clear(); // clear in order to be able to assert in destructor
443 : return;
444 : }
445 :
446 0 : if (IsBroken()) {
447 : return;
448 : }
449 :
450 0 : if (!mParser->IsParserEnabled()) {
451 : // The parser is blocked.
452 : return;
453 : }
454 :
455 0 : if (mFlushState != eNotFlushing) {
456 : // XXX Can this happen? In case it can, let's avoid crashing.
457 : return;
458 : }
459 :
460 : // If there are scripts executing, then the content sink is jumping the gun
461 : // (probably due to a synchronous XMLHttpRequest) and will re-enable us
462 : // later, see bug 460706.
463 0 : if (IsScriptExecuting()) {
464 : return;
465 : }
466 :
467 0 : if (mReadingFromStage) {
468 0 : nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
469 0 : mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue);
470 : // Make sure speculative loads never start after the corresponding
471 : // normal loads for the same URLs.
472 0 : const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
473 0 : const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
474 0 : for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
475 : iter < end;
476 : ++iter) {
477 0 : iter->Perform(this);
478 0 : if (NS_UNLIKELY(!mParser)) {
479 : // An extension terminated the parser from a HTTP observer.
480 0 : mOpQueue.Clear(); // clear in order to be able to assert in destructor
481 : return;
482 : }
483 : }
484 : } else {
485 0 : FlushSpeculativeLoads(); // Make sure speculative loads never start after
486 : // the corresponding normal loads for the same
487 : // URLs.
488 0 : if (NS_UNLIKELY(!mParser)) {
489 : // An extension terminated the parser from a HTTP observer.
490 0 : mOpQueue.Clear(); // clear in order to be able to assert in destructor
491 : return;
492 : }
493 : // Not sure if this grip is still needed, but previously, the code
494 : // gripped before calling ParseUntilBlocked();
495 : nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip =
496 0 : GetParser()->GetStreamParser();
497 : // Now parse content left in the document.write() buffer queue if any.
498 : // This may generate tree ops on its own or dequeue a speculation.
499 0 : GetParser()->ParseUntilBlocked();
500 : }
501 :
502 0 : if (mOpQueue.IsEmpty()) {
503 : // Avoid bothering the rest of the engine with a doc update if there's
504 : // nothing to do.
505 : return;
506 : }
507 :
508 0 : mFlushState = eInFlush;
509 :
510 0 : nsIContent* scriptElement = nsnull;
511 :
512 0 : BeginDocUpdate();
513 :
514 0 : PRUint32 numberOfOpsToFlush = mOpQueue.Length();
515 :
516 0 : mElementsSeenInThisAppendBatch.SetCapacity(numberOfOpsToFlush * 2);
517 :
518 0 : const nsHtml5TreeOperation* first = mOpQueue.Elements();
519 0 : const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
520 0 : for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
521 0 : if (NS_UNLIKELY(!mParser)) {
522 : // The previous tree op caused a call to nsIParser::Terminate().
523 0 : break;
524 : }
525 0 : NS_ASSERTION(mFlushState == eInDocUpdate,
526 : "Tried to perform tree op outside update batch.");
527 0 : iter->Perform(this, &scriptElement);
528 :
529 : // Be sure not to check the deadline if the last op was just performed.
530 0 : if (NS_UNLIKELY(iter == last)) {
531 0 : break;
532 0 : } else if (NS_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
533 : NS_ERROR_HTMLPARSER_INTERRUPTED)) {
534 0 : mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
535 :
536 0 : EndDocUpdate();
537 :
538 0 : mFlushState = eNotFlushing;
539 :
540 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
541 : printf("REFLUSH SCHEDULED (executing ops): %d\n",
542 : ++sTimesFlushLoopInterrupted);
543 : #endif
544 0 : nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
545 : return;
546 : }
547 0 : ++iter;
548 : }
549 :
550 0 : mOpQueue.Clear();
551 :
552 0 : EndDocUpdate();
553 :
554 0 : mFlushState = eNotFlushing;
555 :
556 0 : if (NS_UNLIKELY(!mParser)) {
557 : // The parse ended already.
558 : return;
559 : }
560 :
561 0 : if (scriptElement) {
562 : // must be tail call when mFlushState is eNotFlushing
563 0 : RunScript(scriptElement);
564 :
565 : // Always check the clock in nsContentSink right after a script
566 0 : StopDeflecting();
567 0 : if (nsContentSink::DidProcessATokenImpl() ==
568 : NS_ERROR_HTMLPARSER_INTERRUPTED) {
569 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
570 : printf("REFLUSH SCHEDULED (after script): %d\n",
571 : ++sTimesFlushLoopInterrupted);
572 : #endif
573 0 : nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
574 : return;
575 : }
576 : }
577 : }
578 : }
579 :
580 : void
581 234 : nsHtml5TreeOpExecutor::FlushDocumentWrite()
582 : {
583 234 : FlushSpeculativeLoads(); // Make sure speculative loads never start after the
584 : // corresponding normal loads for the same URLs.
585 :
586 234 : if (NS_UNLIKELY(!mParser)) {
587 : // The parse has ended.
588 0 : mOpQueue.Clear(); // clear in order to be able to assert in destructor
589 0 : return;
590 : }
591 :
592 234 : if (mFlushState != eNotFlushing) {
593 : // XXX Can this happen? In case it can, let's avoid crashing.
594 0 : return;
595 : }
596 :
597 234 : mFlushState = eInFlush;
598 :
599 : // avoid crashing near EOF
600 468 : nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
601 468 : nsRefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
602 :
603 234 : NS_ASSERTION(!mReadingFromStage,
604 : "Got doc write flush when reading from stage");
605 :
606 : #ifdef DEBUG
607 234 : mStage.AssertEmpty();
608 : #endif
609 :
610 234 : nsIContent* scriptElement = nsnull;
611 :
612 234 : BeginDocUpdate();
613 :
614 234 : PRUint32 numberOfOpsToFlush = mOpQueue.Length();
615 :
616 234 : mElementsSeenInThisAppendBatch.SetCapacity(numberOfOpsToFlush * 2);
617 :
618 234 : const nsHtml5TreeOperation* start = mOpQueue.Elements();
619 234 : const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
620 3485 : for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
621 : iter < end;
622 : ++iter) {
623 3251 : if (NS_UNLIKELY(!mParser)) {
624 : // The previous tree op caused a call to nsIParser::Terminate().
625 0 : break;
626 : }
627 3251 : NS_ASSERTION(mFlushState == eInDocUpdate,
628 : "Tried to perform tree op outside update batch.");
629 3251 : iter->Perform(this, &scriptElement);
630 : }
631 :
632 234 : mOpQueue.Clear();
633 :
634 234 : EndDocUpdate();
635 :
636 234 : mFlushState = eNotFlushing;
637 :
638 234 : if (NS_UNLIKELY(!mParser)) {
639 : // Ending the doc update caused a call to nsIParser::Terminate().
640 : return;
641 : }
642 :
643 234 : if (scriptElement) {
644 : // must be tail call when mFlushState is eNotFlushing
645 0 : RunScript(scriptElement);
646 : }
647 : }
648 :
649 : // copied from HTML content sink
650 : bool
651 0 : nsHtml5TreeOpExecutor::IsScriptEnabled()
652 : {
653 0 : if (!mDocument || !mDocShell)
654 0 : return true;
655 0 : nsCOMPtr<nsIScriptGlobalObject> globalObject = mDocument->GetScriptGlobalObject();
656 : // Getting context is tricky if the document hasn't had its
657 : // GlobalObject set yet
658 0 : if (!globalObject) {
659 0 : nsCOMPtr<nsIScriptGlobalObjectOwner> owner = do_GetInterface(mDocShell);
660 0 : NS_ENSURE_TRUE(owner, true);
661 0 : globalObject = owner->GetScriptGlobalObject();
662 0 : NS_ENSURE_TRUE(globalObject, true);
663 : }
664 0 : nsIScriptContext *scriptContext = globalObject->GetContext();
665 0 : NS_ENSURE_TRUE(scriptContext, true);
666 0 : JSContext* cx = scriptContext->GetNativeContext();
667 0 : NS_ENSURE_TRUE(cx, true);
668 0 : bool enabled = true;
669 0 : nsContentUtils::GetSecurityManager()->
670 0 : CanExecuteScripts(cx, mDocument->NodePrincipal(), &enabled);
671 0 : return enabled;
672 : }
673 :
674 : void
675 234 : nsHtml5TreeOpExecutor::SetDocumentMode(nsHtml5DocumentMode m)
676 : {
677 234 : nsCompatibility mode = eCompatibility_NavQuirks;
678 234 : switch (m) {
679 : case STANDARDS_MODE:
680 0 : mode = eCompatibility_FullStandards;
681 0 : break;
682 : case ALMOST_STANDARDS_MODE:
683 0 : mode = eCompatibility_AlmostStandards;
684 0 : break;
685 : case QUIRKS_MODE:
686 234 : mode = eCompatibility_NavQuirks;
687 234 : break;
688 : }
689 468 : nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(mDocument);
690 234 : NS_ASSERTION(htmlDocument, "Document didn't QI into HTML document.");
691 234 : htmlDocument->SetCompatibilityMode(mode);
692 234 : }
693 :
694 : void
695 234 : nsHtml5TreeOpExecutor::StartLayout() {
696 234 : if (mLayoutStarted || !mDocument) {
697 222 : return;
698 : }
699 :
700 12 : EndDocUpdate();
701 :
702 12 : if (NS_UNLIKELY(!mParser)) {
703 : // got terminate
704 0 : return;
705 : }
706 :
707 12 : nsContentSink::StartLayout(false);
708 :
709 12 : BeginDocUpdate();
710 : }
711 :
712 : /**
713 : * The reason why this code is here and not in the tree builder even in the
714 : * main-thread case is to allow the control to return from the tokenizer
715 : * before scripts run. This way, the tokenizer is not invoked re-entrantly
716 : * although the parser is.
717 : *
718 : * The reason why this is called as a tail call when mFlushState is set to
719 : * eNotFlushing is to allow re-entry to Flush() but only after the current
720 : * Flush() has cleared the op queue and is otherwise done cleaning up after
721 : * itself.
722 : */
723 : void
724 0 : nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement)
725 : {
726 0 : NS_ASSERTION(aScriptElement, "No script to run");
727 0 : nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
728 :
729 0 : if (!mParser) {
730 0 : NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
731 : // We got here not because of an end tag but because the tree builder
732 : // popped an incomplete script element on EOF. Returning here to avoid
733 : // calling back into mParser anymore.
734 : return;
735 : }
736 :
737 0 : if (mPreventScriptExecution) {
738 0 : sele->PreventExecution();
739 : }
740 0 : if (mRunsToCompletion) {
741 : return;
742 : }
743 :
744 0 : if (sele->GetScriptDeferred() || sele->GetScriptAsync()) {
745 0 : DebugOnly<bool> block = sele->AttemptToExecute();
746 0 : NS_ASSERTION(!block, "Defer or async script tried to block.");
747 : return;
748 : }
749 :
750 0 : NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing.");
751 :
752 0 : mReadingFromStage = false;
753 :
754 0 : sele->SetCreatorParser(GetParser());
755 :
756 : // Copied from nsXMLContentSink
757 : // Now tell the script that it's ready to go. This may execute the script
758 : // or return true, or neither if the script doesn't need executing.
759 0 : bool block = sele->AttemptToExecute();
760 :
761 : // If the act of insertion evaluated the script, we're fine.
762 : // Else, block the parser till the script has loaded.
763 0 : if (block) {
764 0 : if (mParser) {
765 0 : GetParser()->BlockParser();
766 : }
767 : } else {
768 : // mParser may have been nulled out by now, but the flusher deals
769 :
770 : // If this event isn't needed, it doesn't do anything. It is sometimes
771 : // necessary for the parse to continue after complex situations.
772 0 : nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
773 : }
774 : }
775 :
776 : nsresult
777 234 : nsHtml5TreeOpExecutor::Init(nsIDocument* aDoc,
778 : nsIURI* aURI,
779 : nsISupports* aContainer,
780 : nsIChannel* aChannel)
781 : {
782 234 : return nsContentSink::Init(aDoc, aURI, aContainer, aChannel);
783 : }
784 :
785 : void
786 234 : nsHtml5TreeOpExecutor::Start()
787 : {
788 234 : NS_PRECONDITION(!mStarted, "Tried to start when already started.");
789 234 : mStarted = true;
790 234 : }
791 :
792 : void
793 0 : nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding,
794 : PRInt32 aSource)
795 : {
796 0 : EndDocUpdate();
797 :
798 0 : if (NS_UNLIKELY(!mParser)) {
799 : // got terminate
800 0 : return;
801 : }
802 :
803 0 : nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
804 0 : if (!wss) {
805 : return;
806 : }
807 :
808 : // ask the webshellservice to load the URL
809 0 : if (NS_SUCCEEDED(wss->StopDocumentLoad())) {
810 0 : wss->ReloadDocument(aEncoding, aSource);
811 : }
812 : // if the charset switch was accepted, wss has called Terminate() on the
813 : // parser by now
814 :
815 0 : if (!mParser) {
816 : // success
817 : return;
818 : }
819 :
820 0 : GetParser()->ContinueAfterFailedCharsetSwitch();
821 :
822 0 : BeginDocUpdate();
823 : }
824 :
825 : nsHtml5Parser*
826 0 : nsHtml5TreeOpExecutor::GetParser()
827 : {
828 0 : MOZ_ASSERT(!mRunsToCompletion);
829 0 : return static_cast<nsHtml5Parser*>(mParser.get());
830 : }
831 :
832 : void
833 234 : nsHtml5TreeOpExecutor::Reset()
834 : {
835 234 : MOZ_ASSERT(mRunsToCompletion);
836 234 : DropHeldElements();
837 234 : mOpQueue.Clear();
838 234 : mStarted = false;
839 234 : mFlushState = eNotFlushing;
840 234 : mRunFlushLoopOnStack = false;
841 234 : MOZ_ASSERT(!mReadingFromStage);
842 234 : MOZ_ASSERT(!mBroken);
843 234 : }
844 :
845 : void
846 468 : nsHtml5TreeOpExecutor::DropHeldElements()
847 : {
848 468 : mScriptLoader = nsnull;
849 468 : mDocument = nsnull;
850 468 : mNodeInfoManager = nsnull;
851 468 : mCSSLoader = nsnull;
852 468 : mDocumentURI = nsnull;
853 468 : mDocShell = nsnull;
854 468 : mOwnedElements.Clear();
855 468 : }
856 :
857 : void
858 234 : nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
859 : {
860 234 : NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
861 234 : if (mOpQueue.IsEmpty()) {
862 234 : mOpQueue.SwapElements(aOpQueue);
863 234 : return;
864 : }
865 0 : mOpQueue.MoveElementsFrom(aOpQueue);
866 : }
867 :
868 : void
869 0 : nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, PRInt32 aLine)
870 : {
871 0 : GetParser()->InitializeDocWriteParserState(aState, aLine);
872 0 : }
873 :
874 : nsIURI*
875 0 : nsHtml5TreeOpExecutor::GetViewSourceBaseURI()
876 : {
877 0 : if (!mViewSourceBaseURI) {
878 0 : nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI();
879 : bool isViewSource;
880 0 : orig->SchemeIs("view-source", &isViewSource);
881 0 : if (isViewSource) {
882 0 : nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig);
883 0 : NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!");
884 0 : nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI));
885 : } else {
886 : // Fail gracefully if the base URL isn't a view-source: URL.
887 : // Not sure if this can ever happen.
888 0 : mViewSourceBaseURI = orig;
889 : }
890 : }
891 0 : return mViewSourceBaseURI;
892 : }
893 :
894 : // Speculative loading
895 :
896 : already_AddRefed<nsIURI>
897 0 : nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL)
898 : {
899 0 : if (aURL.IsEmpty()) {
900 0 : return nsnull;
901 : }
902 : // The URL of the document without <base>
903 0 : nsIURI* documentURI = mDocument->GetDocumentURI();
904 : // The URL of the document with non-speculative <base>
905 0 : nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
906 :
907 : // If the two above are different, use documentBaseURI. If they are the
908 : // same, the document object isn't aware of a <base>, so attempt to use the
909 : // mSpeculationBaseURI or, failing, that, documentURI.
910 : nsIURI* base = (documentURI == documentBaseURI) ?
911 : (mSpeculationBaseURI ?
912 0 : mSpeculationBaseURI.get() : documentURI)
913 0 : : documentBaseURI;
914 0 : const nsCString& charset = mDocument->GetDocumentCharacterSet();
915 0 : nsCOMPtr<nsIURI> uri;
916 0 : nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base);
917 0 : if (NS_FAILED(rv)) {
918 0 : NS_WARNING("Failed to create a URI");
919 0 : return nsnull;
920 : }
921 0 : nsCAutoString spec;
922 0 : uri->GetSpec(spec);
923 0 : if (mPreloadedURLs.Contains(spec)) {
924 0 : return nsnull;
925 : }
926 0 : mPreloadedURLs.PutEntry(spec);
927 0 : return uri.forget();
928 : }
929 :
930 : void
931 0 : nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
932 : const nsAString& aCharset,
933 : const nsAString& aType,
934 : const nsAString& aCrossOrigin)
935 : {
936 0 : nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
937 0 : if (!uri) {
938 : return;
939 : }
940 0 : mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin);
941 : }
942 :
943 : void
944 0 : nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
945 : const nsAString& aCharset)
946 : {
947 0 : nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
948 0 : if (!uri) {
949 : return;
950 : }
951 0 : mDocument->PreloadStyle(uri, aCharset);
952 : }
953 :
954 : void
955 0 : nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
956 : const nsAString& aCrossOrigin)
957 : {
958 0 : nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
959 0 : if (!uri) {
960 : return;
961 : }
962 0 : mDocument->MaybePreLoadImage(uri, aCrossOrigin);
963 : }
964 :
965 : void
966 0 : nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL)
967 : {
968 0 : if (mSpeculationBaseURI) {
969 : // the first one wins
970 0 : return;
971 : }
972 0 : const nsCString& charset = mDocument->GetDocumentCharacterSet();
973 0 : DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL,
974 0 : charset.get(), mDocument->GetDocumentURI());
975 0 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to create a URI");
976 4392 : }
977 :
978 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
979 : PRUint32 nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
980 : PRUint32 nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
981 : PRUint32 nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
982 : PRUint32 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
983 : PRUint32 nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
984 : #endif
|