1/*
2 * Copyright (C) 2010 Google, Inc. All Rights Reserved.
3 * Copyright (C) 2013 Apple, Inc. All Rights Reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "HTMLParserScheduler.h"
29
30#include "Document.h"
31#include "FrameView.h"
32#include "HTMLDocumentParser.h"
33#include "Page.h"
34
35// defaultParserChunkSize is used to define how many tokens the parser will
36// process before checking against parserTimeLimit and possibly yielding.
37// This is a performance optimization to prevent checking after every token.
38static const int defaultParserChunkSize = 4096;
39
40// defaultParserTimeLimit is the seconds the parser will run in one write() call
41// before yielding. Inline <script> execution can cause it to exceed the limit.
42// FIXME: We would like this value to be 0.2.
43static const double defaultParserTimeLimit = 0.500;
44
45namespace WebCore {
46
47static double parserTimeLimit(Page* page)
48{
49    // We're using the poorly named customHTMLTokenizerTimeDelay setting.
50    if (page && page->hasCustomHTMLTokenizerTimeDelay())
51        return page->customHTMLTokenizerTimeDelay();
52    return defaultParserTimeLimit;
53}
54
55ActiveParserSession::ActiveParserSession(Document* document)
56    : m_document(document)
57{
58    if (!m_document)
59        return;
60    m_document->incrementActiveParserCount();
61}
62
63ActiveParserSession::~ActiveParserSession()
64{
65    if (!m_document)
66        return;
67    m_document->decrementActiveParserCount();
68}
69
70PumpSession::PumpSession(unsigned& nestingLevel, Document* document)
71    : NestingLevelIncrementer(nestingLevel)
72    , ActiveParserSession(document)
73    // Setting processedTokens to INT_MAX causes us to check for yields
74    // after any token during any parse where yielding is allowed.
75    // At that time we'll initialize startTime.
76    , processedTokens(INT_MAX)
77    , startTime(0)
78    , needsYield(false)
79    , didSeeScript(false)
80{
81}
82
83PumpSession::~PumpSession()
84{
85}
86
87HTMLParserScheduler::HTMLParserScheduler(HTMLDocumentParser& parser)
88    : m_parser(parser)
89    , m_parserTimeLimit(parserTimeLimit(m_parser.document()->page()))
90    , m_parserChunkSize(defaultParserChunkSize)
91    , m_continueNextChunkTimer(this, &HTMLParserScheduler::continueNextChunkTimerFired)
92    , m_isSuspendedWithActiveTimer(false)
93#if !ASSERT_DISABLED
94    , m_suspended(false)
95#endif
96{
97}
98
99HTMLParserScheduler::~HTMLParserScheduler()
100{
101    m_continueNextChunkTimer.stop();
102}
103
104void HTMLParserScheduler::continueNextChunkTimerFired(Timer<HTMLParserScheduler>& timer)
105{
106    ASSERT(!m_suspended);
107    ASSERT_UNUSED(timer, &timer == &m_continueNextChunkTimer);
108
109    // FIXME: The timer class should handle timer priorities instead of this code.
110    // If a layout is scheduled, wait again to let the layout timer run first.
111    if (m_parser.document()->isLayoutTimerActive()) {
112        m_continueNextChunkTimer.startOneShot(0);
113        return;
114    }
115    m_parser.resumeParsingAfterYield();
116}
117
118void HTMLParserScheduler::checkForYieldBeforeScript(PumpSession& session)
119{
120    // If we've never painted before and a layout is pending, yield prior to running
121    // scripts to give the page a chance to paint earlier.
122    Document* document = m_parser.document();
123    bool needsFirstPaint = document->view() && !document->view()->hasEverPainted();
124    if (needsFirstPaint && document->isLayoutTimerActive())
125        session.needsYield = true;
126    session.didSeeScript = true;
127}
128
129void HTMLParserScheduler::scheduleForResume()
130{
131    ASSERT(!m_suspended);
132    m_continueNextChunkTimer.startOneShot(0);
133}
134
135void HTMLParserScheduler::suspend()
136{
137    ASSERT(!m_suspended);
138    ASSERT(!m_isSuspendedWithActiveTimer);
139#if !ASSERT_DISABLED
140    m_suspended = true;
141#endif
142
143    if (!m_continueNextChunkTimer.isActive())
144        return;
145    m_isSuspendedWithActiveTimer = true;
146    m_continueNextChunkTimer.stop();
147}
148
149void HTMLParserScheduler::resume()
150{
151    ASSERT(m_suspended);
152    ASSERT(!m_continueNextChunkTimer.isActive());
153#if !ASSERT_DISABLED
154    m_suspended = false;
155#endif
156
157    if (!m_isSuspendedWithActiveTimer)
158        return;
159    m_isSuspendedWithActiveTimer = false;
160    m_continueNextChunkTimer.startOneShot(0);
161}
162
163}
164