parser/html/nsHtml5TreeOpExecutor.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

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

mercurial