1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/parser/html/nsHtml5TreeOpExecutor.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,951 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set sw=2 ts=2 et tw=79: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/DebugOnly.h" 1.11 +#include "mozilla/Likely.h" 1.12 + 1.13 +#include "nsError.h" 1.14 +#include "nsHtml5TreeOpExecutor.h" 1.15 +#include "nsScriptLoader.h" 1.16 +#include "nsIMarkupDocumentViewer.h" 1.17 +#include "nsIContentViewer.h" 1.18 +#include "nsIDocShellTreeItem.h" 1.19 +#include "nsIDocShell.h" 1.20 +#include "nsIScriptGlobalObject.h" 1.21 +#include "nsIScriptSecurityManager.h" 1.22 +#include "nsIWebShellServices.h" 1.23 +#include "nsContentUtils.h" 1.24 +#include "mozAutoDocUpdate.h" 1.25 +#include "nsNetUtil.h" 1.26 +#include "nsHtml5Parser.h" 1.27 +#include "nsHtml5Tokenizer.h" 1.28 +#include "nsHtml5TreeBuilder.h" 1.29 +#include "nsHtml5StreamParser.h" 1.30 +#include "mozilla/css/Loader.h" 1.31 +#include "GeckoProfiler.h" 1.32 +#include "nsIScriptError.h" 1.33 +#include "nsIScriptContext.h" 1.34 +#include "mozilla/Preferences.h" 1.35 +#include "nsIHTMLDocument.h" 1.36 + 1.37 +using namespace mozilla; 1.38 + 1.39 +NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor) 1.40 + NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor, 1.41 + nsIContentSink) 1.42 +NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder) 1.43 + 1.44 +NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) 1.45 + 1.46 +NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink) 1.47 + 1.48 +class nsHtml5ExecutorReflusher : public nsRunnable 1.49 +{ 1.50 + private: 1.51 + nsRefPtr<nsHtml5TreeOpExecutor> mExecutor; 1.52 + public: 1.53 + nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor) 1.54 + : mExecutor(aExecutor) 1.55 + {} 1.56 + NS_IMETHODIMP Run() 1.57 + { 1.58 + mExecutor->RunFlushLoop(); 1.59 + return NS_OK; 1.60 + } 1.61 +}; 1.62 + 1.63 +static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr; 1.64 +static nsITimer* gFlushTimer = nullptr; 1.65 + 1.66 +nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor() 1.67 + : nsHtml5DocumentBuilder(false) 1.68 + , mPreloadedURLs(23) // Mean # of preloadable resources per page on dmoz 1.69 +{ 1.70 + // zeroing operator new for everything else 1.71 +} 1.72 + 1.73 +nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() 1.74 +{ 1.75 + if (gBackgroundFlushList && isInList()) { 1.76 + mOpQueue.Clear(); 1.77 + removeFrom(*gBackgroundFlushList); 1.78 + if (gBackgroundFlushList->isEmpty()) { 1.79 + delete gBackgroundFlushList; 1.80 + gBackgroundFlushList = nullptr; 1.81 + if (gFlushTimer) { 1.82 + gFlushTimer->Cancel(); 1.83 + NS_RELEASE(gFlushTimer); 1.84 + } 1.85 + } 1.86 + } 1.87 + NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue."); 1.88 +} 1.89 + 1.90 +// nsIContentSink 1.91 +NS_IMETHODIMP 1.92 +nsHtml5TreeOpExecutor::WillParse() 1.93 +{ 1.94 + NS_NOTREACHED("No one should call this"); 1.95 + return NS_ERROR_NOT_IMPLEMENTED; 1.96 +} 1.97 + 1.98 +NS_IMETHODIMP 1.99 +nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode) 1.100 +{ 1.101 + mDocument->AddObserver(this); 1.102 + WillBuildModelImpl(); 1.103 + GetDocument()->BeginLoad(); 1.104 + if (mDocShell && !GetDocument()->GetWindow() && 1.105 + !IsExternalViewSource()) { 1.106 + // Not loading as data but script global object not ready 1.107 + return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR); 1.108 + } 1.109 + return NS_OK; 1.110 +} 1.111 + 1.112 + 1.113 +// This is called when the tree construction has ended 1.114 +NS_IMETHODIMP 1.115 +nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated) 1.116 +{ 1.117 + if (!aTerminated) { 1.118 + // This is needed to avoid unblocking loads too many times on one hand 1.119 + // and on the other hand to avoid destroying the frame constructor from 1.120 + // within an update batch. See bug 537683. 1.121 + EndDocUpdate(); 1.122 + 1.123 + // If the above caused a call to nsIParser::Terminate(), let that call 1.124 + // win. 1.125 + if (!mParser) { 1.126 + return NS_OK; 1.127 + } 1.128 + } 1.129 + 1.130 + if (mRunsToCompletion) { 1.131 + return NS_OK; 1.132 + } 1.133 + 1.134 + GetParser()->DropStreamParser(); 1.135 + 1.136 + // This comes from nsXMLContentSink and nsHTMLContentSink 1.137 + // If this parser has been marked as broken, treat the end of parse as 1.138 + // forced termination. 1.139 + DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken())); 1.140 + 1.141 + if (!mLayoutStarted) { 1.142 + // We never saw the body, and layout never got started. Force 1.143 + // layout *now*, to get an initial reflow. 1.144 + 1.145 + // NOTE: only force the layout if we are NOT destroying the 1.146 + // docshell. If we are destroying it, then starting layout will 1.147 + // likely cause us to crash, or at best waste a lot of time as we 1.148 + // are just going to tear it down anyway. 1.149 + bool destroying = true; 1.150 + if (mDocShell) { 1.151 + mDocShell->IsBeingDestroyed(&destroying); 1.152 + } 1.153 + 1.154 + if (!destroying) { 1.155 + nsContentSink::StartLayout(false); 1.156 + } 1.157 + } 1.158 + 1.159 + ScrollToRef(); 1.160 + mDocument->RemoveObserver(this); 1.161 + if (!mParser) { 1.162 + // DidBuildModelImpl may cause mParser to be nulled out 1.163 + // Return early to avoid unblocking the onload event too many times. 1.164 + return NS_OK; 1.165 + } 1.166 + 1.167 + // We may not have called BeginLoad() if loading is terminated before 1.168 + // OnStartRequest call. 1.169 + if (mStarted) { 1.170 + mDocument->EndLoad(); 1.171 + } 1.172 + DropParserAndPerfHint(); 1.173 +#ifdef GATHER_DOCWRITE_STATISTICS 1.174 + printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites); 1.175 + printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites); 1.176 + printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites); 1.177 +#endif 1.178 +#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH 1.179 + printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize); 1.180 + if (sAppendBatchExaminations != 0) { 1.181 + printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations); 1.182 + } 1.183 +#endif 1.184 + return NS_OK; 1.185 +} 1.186 + 1.187 +NS_IMETHODIMP 1.188 +nsHtml5TreeOpExecutor::WillInterrupt() 1.189 +{ 1.190 + NS_NOTREACHED("Don't call. For interface compat only."); 1.191 + return NS_ERROR_NOT_IMPLEMENTED; 1.192 +} 1.193 + 1.194 +NS_IMETHODIMP 1.195 +nsHtml5TreeOpExecutor::WillResume() 1.196 +{ 1.197 + NS_NOTREACHED("Don't call. For interface compat only."); 1.198 + return NS_ERROR_NOT_IMPLEMENTED; 1.199 +} 1.200 + 1.201 +NS_IMETHODIMP 1.202 +nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser) 1.203 +{ 1.204 + mParser = aParser; 1.205 + return NS_OK; 1.206 +} 1.207 + 1.208 +void 1.209 +nsHtml5TreeOpExecutor::FlushPendingNotifications(mozFlushType aType) 1.210 +{ 1.211 + if (aType >= Flush_InterruptibleLayout) { 1.212 + // Bug 577508 / 253951 1.213 + nsContentSink::StartLayout(true); 1.214 + } 1.215 +} 1.216 + 1.217 +nsISupports* 1.218 +nsHtml5TreeOpExecutor::GetTarget() 1.219 +{ 1.220 + return mDocument; 1.221 +} 1.222 + 1.223 +nsresult 1.224 +nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) 1.225 +{ 1.226 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.227 + mBroken = aReason; 1.228 + if (mStreamParser) { 1.229 + mStreamParser->Terminate(); 1.230 + } 1.231 + // We are under memory pressure, but let's hope the following allocation 1.232 + // works out so that we get to terminate and clean up the parser from 1.233 + // a safer point. 1.234 + if (mParser) { // can mParser ever be null here? 1.235 + nsCOMPtr<nsIRunnable> terminator = 1.236 + NS_NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate); 1.237 + if (NS_FAILED(NS_DispatchToMainThread(terminator))) { 1.238 + NS_WARNING("failed to dispatch executor flush event"); 1.239 + } 1.240 + } 1.241 + return aReason; 1.242 +} 1.243 + 1.244 +void 1.245 +FlushTimerCallback(nsITimer* aTimer, void* aClosure) 1.246 +{ 1.247 + nsRefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst(); 1.248 + if (ex) { 1.249 + ex->RunFlushLoop(); 1.250 + } 1.251 + if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) { 1.252 + delete gBackgroundFlushList; 1.253 + gBackgroundFlushList = nullptr; 1.254 + gFlushTimer->Cancel(); 1.255 + NS_RELEASE(gFlushTimer); 1.256 + } 1.257 +} 1.258 + 1.259 +void 1.260 +nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() 1.261 +{ 1.262 + if (!mDocument || !mDocument->IsInBackgroundWindow()) { 1.263 + nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this); 1.264 + if (NS_FAILED(NS_DispatchToMainThread(flusher))) { 1.265 + NS_WARNING("failed to dispatch executor flush event"); 1.266 + } 1.267 + } else { 1.268 + if (!gBackgroundFlushList) { 1.269 + gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>(); 1.270 + } 1.271 + if (!isInList()) { 1.272 + gBackgroundFlushList->insertBack(this); 1.273 + } 1.274 + if (!gFlushTimer) { 1.275 + nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1"); 1.276 + t.swap(gFlushTimer); 1.277 + // The timer value 50 should not hopefully slow down background pages too 1.278 + // much, yet lets event loop to process enough between ticks. 1.279 + // See bug 734015. 1.280 + gFlushTimer->InitWithFuncCallback(FlushTimerCallback, nullptr, 1.281 + 50, nsITimer::TYPE_REPEATING_SLACK); 1.282 + } 1.283 + } 1.284 +} 1.285 + 1.286 +void 1.287 +nsHtml5TreeOpExecutor::FlushSpeculativeLoads() 1.288 +{ 1.289 + nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue; 1.290 + mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue); 1.291 + const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); 1.292 + const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); 1.293 + for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start); 1.294 + iter < end; 1.295 + ++iter) { 1.296 + if (MOZ_UNLIKELY(!mParser)) { 1.297 + // An extension terminated the parser from a HTTP observer. 1.298 + return; 1.299 + } 1.300 + iter->Perform(this); 1.301 + } 1.302 +} 1.303 + 1.304 +class nsHtml5FlushLoopGuard 1.305 +{ 1.306 + private: 1.307 + nsRefPtr<nsHtml5TreeOpExecutor> mExecutor; 1.308 + #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH 1.309 + uint32_t mStartTime; 1.310 + #endif 1.311 + public: 1.312 + nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor) 1.313 + : mExecutor(aExecutor) 1.314 + #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH 1.315 + , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow())) 1.316 + #endif 1.317 + { 1.318 + mExecutor->mRunFlushLoopOnStack = true; 1.319 + } 1.320 + ~nsHtml5FlushLoopGuard() 1.321 + { 1.322 + #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH 1.323 + uint32_t timeOffTheEventLoop = 1.324 + PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime; 1.325 + if (timeOffTheEventLoop > 1.326 + nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) { 1.327 + nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 1.328 + timeOffTheEventLoop; 1.329 + } 1.330 + printf("Longest time off the event loop: %d\n", 1.331 + nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop); 1.332 + #endif 1.333 + 1.334 + mExecutor->mRunFlushLoopOnStack = false; 1.335 + } 1.336 +}; 1.337 + 1.338 +/** 1.339 + * The purpose of the loop here is to avoid returning to the main event loop 1.340 + */ 1.341 +void 1.342 +nsHtml5TreeOpExecutor::RunFlushLoop() 1.343 +{ 1.344 + PROFILER_LABEL("html5", "RunFlushLoop"); 1.345 + if (mRunFlushLoopOnStack) { 1.346 + // There's already a RunFlushLoop() on the call stack. 1.347 + return; 1.348 + } 1.349 + 1.350 + nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu! 1.351 + 1.352 + nsCOMPtr<nsISupports> parserKungFuDeathGrip(mParser); 1.353 + 1.354 + // Remember the entry time 1.355 + (void) nsContentSink::WillParseImpl(); 1.356 + 1.357 + for (;;) { 1.358 + if (!mParser) { 1.359 + // Parse has terminated. 1.360 + mOpQueue.Clear(); // clear in order to be able to assert in destructor 1.361 + return; 1.362 + } 1.363 + 1.364 + if (NS_FAILED(IsBroken())) { 1.365 + return; 1.366 + } 1.367 + 1.368 + if (!mParser->IsParserEnabled()) { 1.369 + // The parser is blocked. 1.370 + return; 1.371 + } 1.372 + 1.373 + if (mFlushState != eNotFlushing) { 1.374 + // XXX Can this happen? In case it can, let's avoid crashing. 1.375 + return; 1.376 + } 1.377 + 1.378 + // If there are scripts executing, then the content sink is jumping the gun 1.379 + // (probably due to a synchronous XMLHttpRequest) and will re-enable us 1.380 + // later, see bug 460706. 1.381 + if (IsScriptExecuting()) { 1.382 + return; 1.383 + } 1.384 + 1.385 + if (mReadingFromStage) { 1.386 + nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue; 1.387 + mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue); 1.388 + // Make sure speculative loads never start after the corresponding 1.389 + // normal loads for the same URLs. 1.390 + const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); 1.391 + const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); 1.392 + for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start; 1.393 + iter < end; 1.394 + ++iter) { 1.395 + iter->Perform(this); 1.396 + if (MOZ_UNLIKELY(!mParser)) { 1.397 + // An extension terminated the parser from a HTTP observer. 1.398 + mOpQueue.Clear(); // clear in order to be able to assert in destructor 1.399 + return; 1.400 + } 1.401 + } 1.402 + } else { 1.403 + FlushSpeculativeLoads(); // Make sure speculative loads never start after 1.404 + // the corresponding normal loads for the same 1.405 + // URLs. 1.406 + if (MOZ_UNLIKELY(!mParser)) { 1.407 + // An extension terminated the parser from a HTTP observer. 1.408 + mOpQueue.Clear(); // clear in order to be able to assert in destructor 1.409 + return; 1.410 + } 1.411 + // Not sure if this grip is still needed, but previously, the code 1.412 + // gripped before calling ParseUntilBlocked(); 1.413 + nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip = 1.414 + GetParser()->GetStreamParser(); 1.415 + // Now parse content left in the document.write() buffer queue if any. 1.416 + // This may generate tree ops on its own or dequeue a speculation. 1.417 + nsresult rv = GetParser()->ParseUntilBlocked(); 1.418 + if (NS_FAILED(rv)) { 1.419 + MarkAsBroken(rv); 1.420 + return; 1.421 + } 1.422 + } 1.423 + 1.424 + if (mOpQueue.IsEmpty()) { 1.425 + // Avoid bothering the rest of the engine with a doc update if there's 1.426 + // nothing to do. 1.427 + return; 1.428 + } 1.429 + 1.430 + mFlushState = eInFlush; 1.431 + 1.432 + nsIContent* scriptElement = nullptr; 1.433 + 1.434 + BeginDocUpdate(); 1.435 + 1.436 + uint32_t numberOfOpsToFlush = mOpQueue.Length(); 1.437 + 1.438 + SetAppendBatchCapacity(numberOfOpsToFlush * 2); 1.439 + 1.440 + const nsHtml5TreeOperation* first = mOpQueue.Elements(); 1.441 + const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1; 1.442 + for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) { 1.443 + if (MOZ_UNLIKELY(!mParser)) { 1.444 + // The previous tree op caused a call to nsIParser::Terminate(). 1.445 + break; 1.446 + } 1.447 + NS_ASSERTION(mFlushState == eInDocUpdate, 1.448 + "Tried to perform tree op outside update batch."); 1.449 + nsresult rv = iter->Perform(this, &scriptElement); 1.450 + if (NS_FAILED(rv)) { 1.451 + MarkAsBroken(rv); 1.452 + break; 1.453 + } 1.454 + 1.455 + // Be sure not to check the deadline if the last op was just performed. 1.456 + if (MOZ_UNLIKELY(iter == last)) { 1.457 + break; 1.458 + } else if (MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() == 1.459 + NS_ERROR_HTMLPARSER_INTERRUPTED)) { 1.460 + mOpQueue.RemoveElementsAt(0, (iter - first) + 1); 1.461 + 1.462 + EndDocUpdate(); 1.463 + 1.464 + mFlushState = eNotFlushing; 1.465 + 1.466 + #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH 1.467 + printf("REFLUSH SCHEDULED (executing ops): %d\n", 1.468 + ++sTimesFlushLoopInterrupted); 1.469 + #endif 1.470 + nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); 1.471 + return; 1.472 + } 1.473 + ++iter; 1.474 + } 1.475 + 1.476 + mOpQueue.Clear(); 1.477 + 1.478 + EndDocUpdate(); 1.479 + 1.480 + mFlushState = eNotFlushing; 1.481 + 1.482 + if (MOZ_UNLIKELY(!mParser)) { 1.483 + // The parse ended already. 1.484 + return; 1.485 + } 1.486 + 1.487 + if (scriptElement) { 1.488 + // must be tail call when mFlushState is eNotFlushing 1.489 + RunScript(scriptElement); 1.490 + 1.491 + // Always check the clock in nsContentSink right after a script 1.492 + StopDeflecting(); 1.493 + if (nsContentSink::DidProcessATokenImpl() == 1.494 + NS_ERROR_HTMLPARSER_INTERRUPTED) { 1.495 + #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH 1.496 + printf("REFLUSH SCHEDULED (after script): %d\n", 1.497 + ++sTimesFlushLoopInterrupted); 1.498 + #endif 1.499 + nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); 1.500 + return; 1.501 + } 1.502 + } 1.503 + } 1.504 +} 1.505 + 1.506 +nsresult 1.507 +nsHtml5TreeOpExecutor::FlushDocumentWrite() 1.508 +{ 1.509 + nsresult rv = IsBroken(); 1.510 + NS_ENSURE_SUCCESS(rv, rv); 1.511 + 1.512 + FlushSpeculativeLoads(); // Make sure speculative loads never start after the 1.513 + // corresponding normal loads for the same URLs. 1.514 + 1.515 + if (MOZ_UNLIKELY(!mParser)) { 1.516 + // The parse has ended. 1.517 + mOpQueue.Clear(); // clear in order to be able to assert in destructor 1.518 + return rv; 1.519 + } 1.520 + 1.521 + if (mFlushState != eNotFlushing) { 1.522 + // XXX Can this happen? In case it can, let's avoid crashing. 1.523 + return rv; 1.524 + } 1.525 + 1.526 + mFlushState = eInFlush; 1.527 + 1.528 + // avoid crashing near EOF 1.529 + nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); 1.530 + nsRefPtr<nsParserBase> parserKungFuDeathGrip(mParser); 1.531 + 1.532 + NS_ASSERTION(!mReadingFromStage, 1.533 + "Got doc write flush when reading from stage"); 1.534 + 1.535 +#ifdef DEBUG 1.536 + mStage.AssertEmpty(); 1.537 +#endif 1.538 + 1.539 + nsIContent* scriptElement = nullptr; 1.540 + 1.541 + BeginDocUpdate(); 1.542 + 1.543 + uint32_t numberOfOpsToFlush = mOpQueue.Length(); 1.544 + 1.545 + SetAppendBatchCapacity(numberOfOpsToFlush * 2); 1.546 + 1.547 + const nsHtml5TreeOperation* start = mOpQueue.Elements(); 1.548 + const nsHtml5TreeOperation* end = start + numberOfOpsToFlush; 1.549 + for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start); 1.550 + iter < end; 1.551 + ++iter) { 1.552 + if (MOZ_UNLIKELY(!mParser)) { 1.553 + // The previous tree op caused a call to nsIParser::Terminate(). 1.554 + break; 1.555 + } 1.556 + NS_ASSERTION(mFlushState == eInDocUpdate, 1.557 + "Tried to perform tree op outside update batch."); 1.558 + rv = iter->Perform(this, &scriptElement); 1.559 + if (NS_FAILED(rv)) { 1.560 + MarkAsBroken(rv); 1.561 + break; 1.562 + } 1.563 + } 1.564 + 1.565 + mOpQueue.Clear(); 1.566 + 1.567 + EndDocUpdate(); 1.568 + 1.569 + mFlushState = eNotFlushing; 1.570 + 1.571 + if (MOZ_UNLIKELY(!mParser)) { 1.572 + // Ending the doc update caused a call to nsIParser::Terminate(). 1.573 + return rv; 1.574 + } 1.575 + 1.576 + if (scriptElement) { 1.577 + // must be tail call when mFlushState is eNotFlushing 1.578 + RunScript(scriptElement); 1.579 + } 1.580 + return rv; 1.581 +} 1.582 + 1.583 +// copied from HTML content sink 1.584 +bool 1.585 +nsHtml5TreeOpExecutor::IsScriptEnabled() 1.586 +{ 1.587 + if (!mDocument || !mDocShell) 1.588 + return true; 1.589 + nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(mDocument->GetInnerWindow()); 1.590 + // Getting context is tricky if the document hasn't had its 1.591 + // GlobalObject set yet 1.592 + if (!globalObject) { 1.593 + globalObject = mDocShell->GetScriptGlobalObject(); 1.594 + NS_ENSURE_TRUE(globalObject, true); 1.595 + } 1.596 + NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true); 1.597 + return nsContentUtils::GetSecurityManager()-> 1.598 + ScriptAllowed(globalObject->GetGlobalJSObject()); 1.599 +} 1.600 + 1.601 +void 1.602 +nsHtml5TreeOpExecutor::StartLayout() { 1.603 + if (mLayoutStarted || !mDocument) { 1.604 + return; 1.605 + } 1.606 + 1.607 + EndDocUpdate(); 1.608 + 1.609 + if (MOZ_UNLIKELY(!mParser)) { 1.610 + // got terminate 1.611 + return; 1.612 + } 1.613 + 1.614 + nsContentSink::StartLayout(false); 1.615 + 1.616 + BeginDocUpdate(); 1.617 +} 1.618 + 1.619 +/** 1.620 + * The reason why this code is here and not in the tree builder even in the 1.621 + * main-thread case is to allow the control to return from the tokenizer 1.622 + * before scripts run. This way, the tokenizer is not invoked re-entrantly 1.623 + * although the parser is. 1.624 + * 1.625 + * The reason why this is called as a tail call when mFlushState is set to 1.626 + * eNotFlushing is to allow re-entry to Flush() but only after the current 1.627 + * Flush() has cleared the op queue and is otherwise done cleaning up after 1.628 + * itself. 1.629 + */ 1.630 +void 1.631 +nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement) 1.632 +{ 1.633 + if (mRunsToCompletion) { 1.634 + // We are in createContextualFragment() or in the upcoming document.parse(). 1.635 + // Do nothing. Let's not even mark scripts malformed here, because that 1.636 + // could cause serialization weirdness later. 1.637 + return; 1.638 + } 1.639 + 1.640 + NS_ASSERTION(aScriptElement, "No script to run"); 1.641 + nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement); 1.642 + 1.643 + if (!mParser) { 1.644 + NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed."); 1.645 + // We got here not because of an end tag but because the tree builder 1.646 + // popped an incomplete script element on EOF. Returning here to avoid 1.647 + // calling back into mParser anymore. 1.648 + return; 1.649 + } 1.650 + 1.651 + if (sele->GetScriptDeferred() || sele->GetScriptAsync()) { 1.652 + DebugOnly<bool> block = sele->AttemptToExecute(); 1.653 + NS_ASSERTION(!block, "Defer or async script tried to block."); 1.654 + return; 1.655 + } 1.656 + 1.657 + NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing."); 1.658 + 1.659 + mReadingFromStage = false; 1.660 + 1.661 + sele->SetCreatorParser(GetParser()); 1.662 + 1.663 + // Copied from nsXMLContentSink 1.664 + // Now tell the script that it's ready to go. This may execute the script 1.665 + // or return true, or neither if the script doesn't need executing. 1.666 + bool block = sele->AttemptToExecute(); 1.667 + 1.668 + // If the act of insertion evaluated the script, we're fine. 1.669 + // Else, block the parser till the script has loaded. 1.670 + if (block) { 1.671 + if (mParser) { 1.672 + GetParser()->BlockParser(); 1.673 + } 1.674 + } else { 1.675 + // mParser may have been nulled out by now, but the flusher deals 1.676 + 1.677 + // If this event isn't needed, it doesn't do anything. It is sometimes 1.678 + // necessary for the parse to continue after complex situations. 1.679 + nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); 1.680 + } 1.681 +} 1.682 + 1.683 +void 1.684 +nsHtml5TreeOpExecutor::Start() 1.685 +{ 1.686 + NS_PRECONDITION(!mStarted, "Tried to start when already started."); 1.687 + mStarted = true; 1.688 +} 1.689 + 1.690 +void 1.691 +nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding, 1.692 + int32_t aSource, 1.693 + uint32_t aLineNumber) 1.694 +{ 1.695 + EndDocUpdate(); 1.696 + 1.697 + if (MOZ_UNLIKELY(!mParser)) { 1.698 + // got terminate 1.699 + return; 1.700 + } 1.701 + 1.702 + nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell); 1.703 + if (!wss) { 1.704 + return; 1.705 + } 1.706 + 1.707 + // ask the webshellservice to load the URL 1.708 + if (NS_SUCCEEDED(wss->StopDocumentLoad())) { 1.709 + wss->ReloadDocument(aEncoding, aSource); 1.710 + } 1.711 + // if the charset switch was accepted, wss has called Terminate() on the 1.712 + // parser by now 1.713 + 1.714 + if (!mParser) { 1.715 + // success 1.716 + if (aSource == kCharsetFromMetaTag) { 1.717 + MaybeComplainAboutCharset("EncLateMetaReload", false, aLineNumber); 1.718 + } 1.719 + return; 1.720 + } 1.721 + 1.722 + if (aSource == kCharsetFromMetaTag) { 1.723 + MaybeComplainAboutCharset("EncLateMetaTooLate", true, aLineNumber); 1.724 + } 1.725 + 1.726 + GetParser()->ContinueAfterFailedCharsetSwitch(); 1.727 + 1.728 + BeginDocUpdate(); 1.729 +} 1.730 + 1.731 +void 1.732 +nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId, 1.733 + bool aError, 1.734 + uint32_t aLineNumber) 1.735 +{ 1.736 + if (mAlreadyComplainedAboutCharset) { 1.737 + return; 1.738 + } 1.739 + // The EncNoDeclaration case for advertising iframes is so common that it 1.740 + // would result is way too many errors. The iframe case doesn't matter 1.741 + // when the ad is an image or a Flash animation anyway. When the ad is 1.742 + // textual, a misrendered ad probably isn't a huge loss for users. 1.743 + // Let's suppress the message in this case. 1.744 + // This means that errors about other different-origin iframes in mashups 1.745 + // are lost as well, but generally, the site author isn't in control of 1.746 + // the embedded different-origin pages anyway and can't fix problems even 1.747 + // if alerted about them. 1.748 + if (!strcmp(aMsgId, "EncNoDeclaration") && mDocShell) { 1.749 + nsCOMPtr<nsIDocShellTreeItem> parent; 1.750 + mDocShell->GetSameTypeParent(getter_AddRefs(parent)); 1.751 + if (parent) { 1.752 + return; 1.753 + } 1.754 + } 1.755 + mAlreadyComplainedAboutCharset = true; 1.756 + nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag 1.757 + : nsIScriptError::warningFlag, 1.758 + NS_LITERAL_CSTRING("HTML parser"), 1.759 + mDocument, 1.760 + nsContentUtils::eHTMLPARSER_PROPERTIES, 1.761 + aMsgId, 1.762 + nullptr, 1.763 + 0, 1.764 + nullptr, 1.765 + EmptyString(), 1.766 + aLineNumber); 1.767 +} 1.768 + 1.769 +void 1.770 +nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(nsIDocument* aDoc) 1.771 +{ 1.772 + NS_ASSERTION(!mAlreadyComplainedAboutCharset, 1.773 + "How come we already managed to complain?"); 1.774 + mAlreadyComplainedAboutCharset = true; 1.775 + nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, 1.776 + NS_LITERAL_CSTRING("HTML parser"), 1.777 + aDoc, 1.778 + nsContentUtils::eHTMLPARSER_PROPERTIES, 1.779 + "EncProtocolUnsupported"); 1.780 +} 1.781 + 1.782 +nsHtml5Parser* 1.783 +nsHtml5TreeOpExecutor::GetParser() 1.784 +{ 1.785 + MOZ_ASSERT(!mRunsToCompletion); 1.786 + return static_cast<nsHtml5Parser*>(mParser.get()); 1.787 +} 1.788 + 1.789 +void 1.790 +nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue) 1.791 +{ 1.792 + NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution."); 1.793 + if (mOpQueue.IsEmpty()) { 1.794 + mOpQueue.SwapElements(aOpQueue); 1.795 + return; 1.796 + } 1.797 + mOpQueue.MoveElementsFrom(aOpQueue); 1.798 +} 1.799 + 1.800 +void 1.801 +nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine) 1.802 +{ 1.803 + GetParser()->InitializeDocWriteParserState(aState, aLine); 1.804 +} 1.805 + 1.806 +nsIURI* 1.807 +nsHtml5TreeOpExecutor::GetViewSourceBaseURI() 1.808 +{ 1.809 + if (!mViewSourceBaseURI) { 1.810 + 1.811 + // We query the channel for the baseURI because in certain situations it 1.812 + // cannot otherwise be determined. If this process fails, fall back to the 1.813 + // standard method. 1.814 + nsCOMPtr<nsIViewSourceChannel> vsc; 1.815 + vsc = do_QueryInterface(mDocument->GetChannel()); 1.816 + if (vsc) { 1.817 + nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI)); 1.818 + if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) { 1.819 + return mViewSourceBaseURI; 1.820 + } 1.821 + } 1.822 + 1.823 + nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI(); 1.824 + bool isViewSource; 1.825 + orig->SchemeIs("view-source", &isViewSource); 1.826 + if (isViewSource) { 1.827 + nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig); 1.828 + NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!"); 1.829 + nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI)); 1.830 + } else { 1.831 + // Fail gracefully if the base URL isn't a view-source: URL. 1.832 + // Not sure if this can ever happen. 1.833 + mViewSourceBaseURI = orig; 1.834 + } 1.835 + } 1.836 + return mViewSourceBaseURI; 1.837 +} 1.838 + 1.839 +//static 1.840 +void 1.841 +nsHtml5TreeOpExecutor::InitializeStatics() 1.842 +{ 1.843 + mozilla::Preferences::AddBoolVarCache(&sExternalViewSource, 1.844 + "view_source.editor.external"); 1.845 +} 1.846 + 1.847 +bool 1.848 +nsHtml5TreeOpExecutor::IsExternalViewSource() 1.849 +{ 1.850 + if (!sExternalViewSource) { 1.851 + return false; 1.852 + } 1.853 + bool isViewSource = false; 1.854 + if (mDocumentURI) { 1.855 + mDocumentURI->SchemeIs("view-source", &isViewSource); 1.856 + } 1.857 + return isViewSource; 1.858 +} 1.859 + 1.860 +// Speculative loading 1.861 + 1.862 +already_AddRefed<nsIURI> 1.863 +nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL) 1.864 +{ 1.865 + if (aURL.IsEmpty()) { 1.866 + return nullptr; 1.867 + } 1.868 + // The URL of the document without <base> 1.869 + nsIURI* documentURI = mDocument->GetDocumentURI(); 1.870 + // The URL of the document with non-speculative <base> 1.871 + nsIURI* documentBaseURI = mDocument->GetDocBaseURI(); 1.872 + 1.873 + // If the two above are different, use documentBaseURI. If they are the 1.874 + // same, the document object isn't aware of a <base>, so attempt to use the 1.875 + // mSpeculationBaseURI or, failing, that, documentURI. 1.876 + nsIURI* base = (documentURI == documentBaseURI) ? 1.877 + (mSpeculationBaseURI ? 1.878 + mSpeculationBaseURI.get() : documentURI) 1.879 + : documentBaseURI; 1.880 + const nsCString& charset = mDocument->GetDocumentCharacterSet(); 1.881 + nsCOMPtr<nsIURI> uri; 1.882 + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base); 1.883 + if (NS_FAILED(rv)) { 1.884 + NS_WARNING("Failed to create a URI"); 1.885 + return nullptr; 1.886 + } 1.887 + nsAutoCString spec; 1.888 + uri->GetSpec(spec); 1.889 + if (mPreloadedURLs.Contains(spec)) { 1.890 + return nullptr; 1.891 + } 1.892 + mPreloadedURLs.PutEntry(spec); 1.893 + return uri.forget(); 1.894 +} 1.895 + 1.896 +void 1.897 +nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL, 1.898 + const nsAString& aCharset, 1.899 + const nsAString& aType, 1.900 + const nsAString& aCrossOrigin, 1.901 + bool aScriptFromHead) 1.902 +{ 1.903 + nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); 1.904 + if (!uri) { 1.905 + return; 1.906 + } 1.907 + mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin, 1.908 + aScriptFromHead); 1.909 +} 1.910 + 1.911 +void 1.912 +nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL, 1.913 + const nsAString& aCharset, 1.914 + const nsAString& aCrossOrigin) 1.915 +{ 1.916 + nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); 1.917 + if (!uri) { 1.918 + return; 1.919 + } 1.920 + mDocument->PreloadStyle(uri, aCharset, aCrossOrigin); 1.921 +} 1.922 + 1.923 +void 1.924 +nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL, 1.925 + const nsAString& aCrossOrigin) 1.926 +{ 1.927 + nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); 1.928 + if (!uri) { 1.929 + return; 1.930 + } 1.931 + mDocument->MaybePreLoadImage(uri, aCrossOrigin); 1.932 +} 1.933 + 1.934 +void 1.935 +nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL) 1.936 +{ 1.937 + if (mSpeculationBaseURI) { 1.938 + // the first one wins 1.939 + return; 1.940 + } 1.941 + const nsCString& charset = mDocument->GetDocumentCharacterSet(); 1.942 + DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL, 1.943 + charset.get(), mDocument->GetDocumentURI()); 1.944 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to create a URI"); 1.945 +} 1.946 + 1.947 +#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH 1.948 +uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0; 1.949 +uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0; 1.950 +uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0; 1.951 +uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0; 1.952 +uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0; 1.953 +#endif 1.954 +bool nsHtml5TreeOpExecutor::sExternalViewSource = false;