| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2010 Google, Inc. All Rights Reserved. | |
| 3 * | |
| 4 * Redistribution and use in source and binary forms, with or without | |
| 5 * modification, are permitted provided that the following conditions | |
| 6 * are met: | |
| 7 * 1. Redistributions of source code must retain the above copyright | |
| 8 * notice, this list of conditions and the following disclaimer. | |
| 9 * 2. Redistributions in binary form must reproduce the above copyright | |
| 10 * notice, this list of conditions and the following disclaimer in the | |
| 11 * documentation and/or other materials provided with the distribution. | |
| 12 * | |
| 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | |
| 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR | |
| 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 24 */ | |
| 25 | |
| 26 #include "sky/engine/core/html/parser/HTMLDocumentParser.h" | |
| 27 | |
| 28 #include "base/bind.h" | |
| 29 #include "gen/sky/core/HTMLNames.h" | |
| 30 #include "sky/engine/core/css/MediaValuesCached.h" | |
| 31 #include "sky/engine/core/dom/Element.h" | |
| 32 #include "sky/engine/core/frame/LocalFrame.h" | |
| 33 #include "sky/engine/core/html/HTMLScriptElement.h" | |
| 34 #include "sky/engine/core/html/parser/AtomicHTMLToken.h" | |
| 35 #include "sky/engine/core/html/parser/BackgroundHTMLParser.h" | |
| 36 #include "sky/engine/core/html/parser/HTMLParserScheduler.h" | |
| 37 #include "sky/engine/core/html/parser/HTMLParserThread.h" | |
| 38 #include "sky/engine/core/html/parser/HTMLTreeBuilder.h" | |
| 39 #include "sky/engine/platform/SharedBuffer.h" | |
| 40 #include "sky/engine/platform/TraceEvent.h" | |
| 41 | |
| 42 namespace blink { | |
| 43 | |
| 44 HTMLDocumentParser::HTMLDocumentParser(Document& document, bool reportErrors) | |
| 45 : DocumentParser(&document) | |
| 46 , m_treeBuilder(HTMLTreeBuilder::create(this, &document, reportErrors)) | |
| 47 , m_parserScheduler(HTMLParserScheduler::create(this)) | |
| 48 , m_weakFactory(this) | |
| 49 , m_isFragment(false) | |
| 50 , m_endWasDelayed(false) | |
| 51 , m_haveBackgroundParser(false) | |
| 52 , m_pumpSessionNestingLevel(0) | |
| 53 { | |
| 54 } | |
| 55 | |
| 56 HTMLDocumentParser::~HTMLDocumentParser() | |
| 57 { | |
| 58 ASSERT(!m_parserScheduler); | |
| 59 ASSERT(!m_pumpSessionNestingLevel); | |
| 60 ASSERT(!m_haveBackgroundParser); | |
| 61 // FIXME: We should be able to ASSERT(m_pendingChunks.isEmpty()), | |
| 62 // but there are cases where that's not true currently. For example, | |
| 63 // we we're told to stop parsing before we've consumed all the input. | |
| 64 } | |
| 65 | |
| 66 void HTMLDocumentParser::parse(mojo::ScopedDataPipeConsumerHandle source, | |
| 67 const base::Closure& completionCallback) | |
| 68 { | |
| 69 ASSERT(!isStopped()); | |
| 70 ASSERT(!m_haveBackgroundParser); | |
| 71 m_haveBackgroundParser = true; | |
| 72 | |
| 73 m_completionCallback = completionCallback; | |
| 74 | |
| 75 OwnPtr<BackgroundHTMLParser::Configuration> config = adoptPtr(new Background
HTMLParser::Configuration); | |
| 76 config->source = source.Pass(); | |
| 77 config->parser = m_weakFactory.GetWeakPtr(); | |
| 78 | |
| 79 m_backgroundParser = BackgroundHTMLParser::create(config.release()); | |
| 80 HTMLParserThread::taskRunner()->PostTask(FROM_HERE, | |
| 81 base::Bind(&BackgroundHTMLParser::start, m_backgroundParser)); | |
| 82 } | |
| 83 | |
| 84 void HTMLDocumentParser::detach() | |
| 85 { | |
| 86 if (m_haveBackgroundParser) | |
| 87 stopBackgroundParser(); | |
| 88 DocumentParser::detach(); | |
| 89 m_treeBuilder->detach(); | |
| 90 // FIXME: It seems wrong that we would have a preload scanner here. | |
| 91 // Yet during fast/dom/HTMLScriptElement/script-load-events.html we do. | |
| 92 m_parserScheduler.clear(); // Deleting the scheduler will clear any timers. | |
| 93 } | |
| 94 | |
| 95 void HTMLDocumentParser::stopParsing() | |
| 96 { | |
| 97 DocumentParser::stopParsing(); | |
| 98 m_parserScheduler.clear(); // Deleting the scheduler will clear any timers. | |
| 99 if (m_haveBackgroundParser) | |
| 100 stopBackgroundParser(); | |
| 101 } | |
| 102 | |
| 103 // This kicks off "Once the user agent stops parsing" as described by: | |
| 104 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-
end | |
| 105 void HTMLDocumentParser::prepareToStopParsing() | |
| 106 { | |
| 107 // FIXME: It may not be correct to disable this for the background parser. | |
| 108 // That means hasInsertionPoint() may not be correct in some cases. | |
| 109 ASSERT(!hasInsertionPoint() || m_haveBackgroundParser); | |
| 110 | |
| 111 // pumpTokenizer can cause this parser to be detached from the Document, | |
| 112 // but we need to ensure it isn't deleted yet. | |
| 113 RefPtr<HTMLDocumentParser> protect(this); | |
| 114 | |
| 115 if (isStopped()) | |
| 116 return; | |
| 117 | |
| 118 DocumentParser::prepareToStopParsing(); | |
| 119 | |
| 120 // We will not have a scriptRunner when parsing a DocumentFragment. | |
| 121 if (!m_isFragment) | |
| 122 document()->setReadyState(Document::Interactive); | |
| 123 | |
| 124 // Setting the ready state above can fire mutation event and detach us | |
| 125 // from underneath. In that case, just bail out. | |
| 126 if (isDetached()) | |
| 127 return; | |
| 128 | |
| 129 ASSERT(isStopping()); | |
| 130 ASSERT(!hasInsertionPoint() || m_haveBackgroundParser); | |
| 131 end(); | |
| 132 } | |
| 133 | |
| 134 bool HTMLDocumentParser::isParsingFragment() const | |
| 135 { | |
| 136 return m_treeBuilder->isParsingFragment(); | |
| 137 } | |
| 138 | |
| 139 bool HTMLDocumentParser::isScheduledForResume() const | |
| 140 { | |
| 141 return m_parserScheduler && m_parserScheduler->isScheduledForResume(); | |
| 142 } | |
| 143 | |
| 144 // Used by HTMLParserScheduler | |
| 145 void HTMLDocumentParser::resumeParsingAfterYield() | |
| 146 { | |
| 147 // pumpTokenizer can cause this parser to be detached from the Document, | |
| 148 // but we need to ensure it isn't deleted yet. | |
| 149 RefPtr<HTMLDocumentParser> protect(this); | |
| 150 | |
| 151 ASSERT(m_haveBackgroundParser); | |
| 152 pumpPendingChunks(); | |
| 153 } | |
| 154 | |
| 155 void HTMLDocumentParser::runScriptsForPausedTreeBuilder() | |
| 156 { | |
| 157 if (m_isFragment) | |
| 158 return; | |
| 159 TextPosition scriptStartPosition = TextPosition::belowRangePosition(); | |
| 160 RefPtr<Element> scriptToProcess = m_treeBuilder->takeScriptToProcess(scriptS
tartPosition); | |
| 161 | |
| 162 // Sending the script to dart may find additional 'import' declarations | |
| 163 // which need to load before the script can execute. HTMLScriptRunner | |
| 164 // always calls scriptExecutionCompleted regardless of success/failure. | |
| 165 m_scriptRunner = HTMLScriptRunner::createForScript( | |
| 166 toHTMLScriptElement(scriptToProcess.get()), scriptStartPosition, this); | |
| 167 m_scriptRunner->start(); | |
| 168 } | |
| 169 | |
| 170 void HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser(PassOwnPtr<Pa
rsedChunk> chunk) | |
| 171 { | |
| 172 TRACE_EVENT0("blink", "HTMLDocumentParser::didReceiveParsedChunkFromBackgrou
ndParser"); | |
| 173 // Sky should not need nested parsers. | |
| 174 ASSERT(document()->activeParserCount() == 0); | |
| 175 | |
| 176 if (isWaitingForScripts() || !m_pendingChunks.isEmpty() || | |
| 177 document()->activeParserCount() > 0) { | |
| 178 m_pendingChunks.append(chunk); | |
| 179 return; | |
| 180 } | |
| 181 | |
| 182 // processParsedChunkFromBackgroundParser can cause this parser to be detach
ed from the Document, | |
| 183 // but we need to ensure it isn't deleted yet. | |
| 184 RefPtr<HTMLDocumentParser> protect(this); | |
| 185 | |
| 186 ASSERT(m_pendingChunks.isEmpty()); | |
| 187 m_pendingChunks.append(chunk); | |
| 188 pumpPendingChunks(); | |
| 189 } | |
| 190 | |
| 191 void HTMLDocumentParser::processParsedChunkFromBackgroundParser(PassOwnPtr<Parse
dChunk> popChunk) | |
| 192 { | |
| 193 // TODO(eseidel): Include the token count in the trace event. | |
| 194 TRACE_EVENT0("blink", "HTMLDocumentParser::processParsedChunkFromBackgroundP
arser"); | |
| 195 | |
| 196 ASSERT_WITH_SECURITY_IMPLICATION(!document()->activeParserCount()); | |
| 197 ASSERT(!isParsingFragment()); | |
| 198 ASSERT(!isWaitingForScripts()); | |
| 199 ASSERT(!isStopped()); | |
| 200 | |
| 201 // ASSERT that this object is both attached to the Document and protected. | |
| 202 ASSERT(refCount() >= 2); | |
| 203 | |
| 204 ActiveParserSession session(contextForParsingSession()); | |
| 205 | |
| 206 OwnPtr<ParsedChunk> chunk(popChunk); | |
| 207 OwnPtr<CompactHTMLTokenStream> tokens = chunk->tokens.release(); | |
| 208 | |
| 209 Vector<CompactHTMLToken>::const_iterator it; | |
| 210 for (it = tokens->begin(); it != tokens->end(); ++it) { | |
| 211 // A chunk can issue import loads causing us to be isWaitingForScripts | |
| 212 // but we don't stop processing in that case. | |
| 213 ASSERT(!m_treeBuilder->hasParserBlockingScript()); | |
| 214 ASSERT(!m_scriptRunner); | |
| 215 | |
| 216 m_textPosition = it->textPosition(); | |
| 217 | |
| 218 constructTreeFromCompactHTMLToken(*it); | |
| 219 | |
| 220 if (isStopped()) | |
| 221 break; | |
| 222 | |
| 223 if (m_treeBuilder->hasParserBlockingScript()) { | |
| 224 ++it; // Make it == end() during non-stopped termination. | |
| 225 ASSERT(it == tokens->end()); // The </script> is assumed to be the | |
| 226 // last token of this bunch. | |
| 227 runScriptsForPausedTreeBuilder(); | |
| 228 break; | |
| 229 } | |
| 230 | |
| 231 if (it->type() == HTMLToken::EndOfFile) { | |
| 232 ++it; // Make it == end() during non-stopped termination. | |
| 233 ASSERT(it == tokens->end()); // The EOF is assumed to be the | |
| 234 // last token of this bunch. | |
| 235 ASSERT(m_pendingChunks.isEmpty()); // There should never be any chun
ks after the EOF. | |
| 236 prepareToStopParsing(); | |
| 237 break; | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 // Either we aborted due to stopping or we processed all tokens. | |
| 242 ASSERT(isStopped() || it == tokens->end()); | |
| 243 | |
| 244 // Make sure any pending text nodes are emitted before returning. | |
| 245 if (!isStopped()) | |
| 246 m_treeBuilder->flush(); | |
| 247 } | |
| 248 | |
| 249 void HTMLDocumentParser::pumpPendingChunks() | |
| 250 { | |
| 251 // FIXME: Share this constant with the parser scheduler. | |
| 252 const double parserTimeLimit = 0.500; | |
| 253 | |
| 254 // ASSERT that this object is both attached to the Document and protected. | |
| 255 ASSERT(refCount() >= 2); | |
| 256 ASSERT(!isWaitingForScripts()); | |
| 257 ASSERT(!isStopped()); | |
| 258 | |
| 259 double startTime = currentTime(); | |
| 260 | |
| 261 while (!m_pendingChunks.isEmpty()) { | |
| 262 processParsedChunkFromBackgroundParser(m_pendingChunks.takeFirst()); | |
| 263 | |
| 264 // Always check isStopped first as m_document may be null. | |
| 265 if (isStopped() || isWaitingForScripts()) | |
| 266 break; | |
| 267 | |
| 268 if (currentTime() - startTime > parserTimeLimit && !m_pendingChunks.isEm
pty()) { | |
| 269 m_parserScheduler->scheduleForResume(); | |
| 270 break; | |
| 271 } | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 Document* HTMLDocumentParser::contextForParsingSession() | |
| 276 { | |
| 277 // The parsing session should interact with the document only when parsing | |
| 278 // non-fragments. Otherwise, we might delay the load event mistakenly. | |
| 279 if (isParsingFragment()) | |
| 280 return 0; | |
| 281 return document(); | |
| 282 } | |
| 283 | |
| 284 void HTMLDocumentParser::constructTreeFromHTMLToken(HTMLToken& rawToken) | |
| 285 { | |
| 286 AtomicHTMLToken token(rawToken); | |
| 287 | |
| 288 // We clear the rawToken in case constructTreeFromAtomicToken | |
| 289 // synchronously re-enters the parser. We don't clear the token immedately | |
| 290 // for Character tokens because the AtomicHTMLToken avoids copying the | |
| 291 // characters by keeping a pointer to the underlying buffer in the | |
| 292 // HTMLToken. Fortunately, Character tokens can't cause us to re-enter | |
| 293 // the parser. | |
| 294 // | |
| 295 // FIXME: Stop clearing the rawToken once we start running the parser off | |
| 296 // the main thread or once we stop allowing synchronous JavaScript | |
| 297 // execution from parseAttribute. | |
| 298 if (rawToken.type() != HTMLToken::Character) | |
| 299 rawToken.clear(); | |
| 300 | |
| 301 m_treeBuilder->constructTree(&token); | |
| 302 | |
| 303 if (!rawToken.isUninitialized()) { | |
| 304 ASSERT(rawToken.type() == HTMLToken::Character); | |
| 305 rawToken.clear(); | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 void HTMLDocumentParser::constructTreeFromCompactHTMLToken(const CompactHTMLToke
n& compactToken) | |
| 310 { | |
| 311 AtomicHTMLToken token(compactToken); | |
| 312 m_treeBuilder->constructTree(&token); | |
| 313 } | |
| 314 | |
| 315 bool HTMLDocumentParser::hasInsertionPoint() | |
| 316 { | |
| 317 return false; | |
| 318 } | |
| 319 | |
| 320 void HTMLDocumentParser::stopBackgroundParser() | |
| 321 { | |
| 322 ASSERT(m_haveBackgroundParser); | |
| 323 m_haveBackgroundParser = false; | |
| 324 | |
| 325 HTMLParserThread::taskRunner()->PostTask(FROM_HERE, | |
| 326 base::Bind(&BackgroundHTMLParser::stop, m_backgroundParser)); | |
| 327 m_weakFactory.InvalidateWeakPtrs(); | |
| 328 } | |
| 329 | |
| 330 void HTMLDocumentParser::end() | |
| 331 { | |
| 332 ASSERT(!isDetached()); | |
| 333 ASSERT(!isScheduledForResume()); | |
| 334 | |
| 335 if (m_haveBackgroundParser) | |
| 336 stopBackgroundParser(); | |
| 337 | |
| 338 // Notice that we copy the compleition callback into a local variable | |
| 339 // because we might be deleted after we call ffinish() below. | |
| 340 base::Closure completionCallback = m_completionCallback; | |
| 341 | |
| 342 // Informs the the rest of WebCore that parsing is really finished (and dele
tes this). | |
| 343 m_treeBuilder->finished(); | |
| 344 | |
| 345 completionCallback.Run(); | |
| 346 } | |
| 347 | |
| 348 void HTMLDocumentParser::attemptToEnd() | |
| 349 { | |
| 350 if (shouldDelayEnd()) { | |
| 351 m_endWasDelayed = true; | |
| 352 return; | |
| 353 } | |
| 354 prepareToStopParsing(); | |
| 355 } | |
| 356 | |
| 357 void HTMLDocumentParser::endIfDelayed() | |
| 358 { | |
| 359 // If we've already been detached, don't bother ending. | |
| 360 if (isDetached()) | |
| 361 return; | |
| 362 | |
| 363 if (!m_endWasDelayed || shouldDelayEnd()) | |
| 364 return; | |
| 365 | |
| 366 m_endWasDelayed = false; | |
| 367 prepareToStopParsing(); | |
| 368 } | |
| 369 | |
| 370 bool HTMLDocumentParser::isExecutingScript() const | |
| 371 { | |
| 372 // TODO(eseidel): Callers may need updates now that scripts can be async. | |
| 373 return m_scriptRunner && m_scriptRunner->isExecutingScript(); | |
| 374 } | |
| 375 | |
| 376 OrdinalNumber HTMLDocumentParser::lineNumber() const | |
| 377 { | |
| 378 ASSERT(m_haveBackgroundParser); | |
| 379 return m_textPosition.m_line; | |
| 380 } | |
| 381 | |
| 382 TextPosition HTMLDocumentParser::textPosition() const | |
| 383 { | |
| 384 ASSERT(m_haveBackgroundParser); | |
| 385 return m_textPosition; | |
| 386 } | |
| 387 | |
| 388 bool HTMLDocumentParser::isWaitingForScripts() const | |
| 389 { | |
| 390 return m_treeBuilder->hasParserBlockingScript() || m_scriptRunner || | |
| 391 !document()->haveImportsLoaded(); | |
| 392 } | |
| 393 | |
| 394 void HTMLDocumentParser::resumeAfterWaitingForImports() | |
| 395 { | |
| 396 if (isWaitingForScripts()) | |
| 397 return; | |
| 398 if (m_pendingChunks.isEmpty()) | |
| 399 return; | |
| 400 ASSERT(m_haveBackgroundParser); | |
| 401 RefPtr<HTMLDocumentParser> protect(this); | |
| 402 pumpPendingChunks(); | |
| 403 } | |
| 404 | |
| 405 // HTMLScriptRunner has finished executing a script. | |
| 406 // Just call resumeAfterWaitingForImports since it happens to do what we need. | |
| 407 void HTMLDocumentParser::scriptExecutionCompleted() { | |
| 408 ASSERT(m_scriptRunner); | |
| 409 m_scriptRunner.clear(); | |
| 410 // To avoid re-entering the parser for synchronous scripts, we use a postTask. | |
| 411 base::MessageLoop::current()->PostTask( | |
| 412 FROM_HERE, base::Bind(&HTMLDocumentParser::resumeAfterWaitingForImports, | |
| 413 m_weakFactory.GetWeakPtr())); | |
| 414 } | |
| 415 } | |
| OLD | NEW |