797cf4c8145eb4fc3cd4e1ab7b5dba5c4902abaa
[WebKit-https.git] / WebCore / html / HTMLTokenizer.cpp
1 /*
2     This file is part of the KDE libraries
3
4     Copyright (C) 1997 Martin Jones (mjones@kde.org)
5               (C) 1997 Torben Weis (weis@kde.org)
6               (C) 1998 Waldo Bastian (bastian@kde.org)
7               (C) 1999 Lars Knoll (knoll@kde.org)
8               (C) 1999 Antti Koivisto (koivisto@kde.org)
9               (C) 2001 Dirk Mueller (mueller@kde.org)
10     Copyright (C) 2004, 2005, 2006, 2007 Apple Inc.
11     Copyright (C) 2005, 2006 Alexey Proskuryakov (ap@nypop.com)
12
13     This library is free software; you can redistribute it and/or
14     modify it under the terms of the GNU Library General Public
15     License as published by the Free Software Foundation; either
16     version 2 of the License, or (at your option) any later version.
17
18     This library is distributed in the hope that it will be useful,
19     but WITHOUT ANY WARRANTY; without even the implied warranty of
20     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21     Library General Public License for more details.
22
23     You should have received a copy of the GNU Library General Public License
24     along with this library; see the file COPYING.LIB.  If not, write to
25     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26     Boston, MA 02111-1307, USA.
27 */
28
29 #include "config.h"
30 #include "HTMLTokenizer.h"
31
32 #include "CSSHelper.h"
33 #include "Cache.h"
34 #include "CachedScript.h"
35 #include "DocLoader.h"
36 #include "DocumentFragment.h"
37 #include "EventNames.h"
38 #include "Frame.h"
39 #include "FrameLoader.h"
40 #include "FrameView.h"
41 #include "HTMLElement.h"
42 #include "HTMLNames.h"
43 #include "HTMLParser.h"
44 #include "HTMLScriptElement.h"
45 #include "HTMLViewSourceDocument.h"
46 #include "Settings.h"
47 #include "SystemTime.h"
48 #include "kjs_proxy.h"
49
50 #include "HTMLEntityNames.c"
51
52 // #define INSTRUMENT_LAYOUT_SCHEDULING 1
53
54 #if MOBILE
55 // The mobile device needs to be responsive, as such the tokenizer chunk size is reduced.
56 // This value is used to define how many characters the tokenizer will process before 
57 // yeilding control.
58 #define TOKENIZER_CHUNK_SIZE  256
59 #else
60 #define TOKENIZER_CHUNK_SIZE  4096
61 #endif
62
63 using namespace std;
64
65 namespace WebCore {
66
67 using namespace HTMLNames;
68 using namespace EventNames;
69
70 #if MOBILE
71 // As the chunks are smaller (above), the tokenizer should not yield for as long a period, otherwise
72 // it will take way to long to load a page.
73 const double tokenizerTimeDelay = 0.300;
74
75 #else
76 // FIXME: We would like this constant to be 200ms.
77 // Yielding more aggressively results in increased responsiveness and better incremental rendering.
78 // It slows down overall page-load on slower machines, though, so for now we set a value of 500.
79 const double tokenizerTimeDelay = 0.500;
80 #endif
81
82 static const char commentStart [] = "<!--";
83 static const char scriptEnd [] = "</script";
84 static const char xmpEnd [] = "</xmp";
85 static const char styleEnd [] =  "</style";
86 static const char textareaEnd [] = "</textarea";
87 static const char titleEnd [] = "</title";
88
89 // Full support for MS Windows extensions to Latin-1.
90 // Technically these extensions should only be activated for pages
91 // marked "windows-1252" or "cp1252", but
92 // in the standard Microsoft way, these extensions infect hundreds of thousands
93 // of web pages.  Note that people with non-latin-1 Microsoft extensions
94 // are SOL.
95 //
96 // See: http://www.microsoft.com/globaldev/reference/WinCP.asp
97 //      http://www.bbsinc.com/iso8859.html
98 //      http://www.obviously.com/
99 //
100 // There may be better equivalents
101
102 // We only need this for entities. For non-entity text, we handle this in the text encoding.
103
104 static const UChar windowsLatin1ExtensionArray[32] = {
105     0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, // 80-87
106     0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, // 88-8F
107     0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, // 90-97
108     0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178  // 98-9F
109 };
110
111 static inline UChar fixUpChar(UChar c)
112 {
113     if ((c & ~0x1F) != 0x0080)
114         return c;
115     return windowsLatin1ExtensionArray[c - 0x80];
116 }
117
118 static inline bool tagMatch(const char* s1, const UChar* s2, unsigned length)
119 {
120     for (unsigned i = 0; i != length; ++i) {
121         unsigned char c1 = s1[i];
122         unsigned char uc1 = toupper(c1);
123         UChar c2 = s2[i];
124         if (c1 != c2 && uc1 != c2)
125             return false;
126     }
127     return true;
128 }
129
130 inline void Token::addAttribute(Document* doc, AtomicString& attrName, const AtomicString& v, bool viewSourceMode)
131 {
132     if (!attrName.isEmpty()) {
133         ASSERT(!attrName.contains('/'));
134         Attribute* a = new MappedAttribute(attrName, v);
135         if (!attrs)
136             attrs = new NamedMappedAttrMap(0);
137         attrs->insertAttribute(a, viewSourceMode);
138     }
139     
140     attrName = emptyAtom;
141 }
142
143 // ----------------------------------------------------------------------------
144
145 HTMLTokenizer::HTMLTokenizer(HTMLDocument* doc, bool reportErrors)
146     : Tokenizer()
147     , buffer(0)
148     , scriptCode(0)
149     , scriptCodeSize(0)
150     , scriptCodeMaxSize(0)
151     , scriptCodeResync(0)
152     , m_executingScript(0)
153     , m_requestingScript(false)
154     , m_hasScriptsWaitingForStylesheets(false)
155     , m_timer(this, &HTMLTokenizer::timerFired)
156     , m_doc(doc)
157     , parser(new HTMLParser(doc, reportErrors))
158     , inWrite(false)
159     , m_fragment(false)
160 {
161     begin();
162 }
163
164 HTMLTokenizer::HTMLTokenizer(HTMLViewSourceDocument* doc)
165     : Tokenizer(true)
166     , buffer(0)
167     , scriptCode(0)
168     , scriptCodeSize(0)
169     , scriptCodeMaxSize(0)
170     , scriptCodeResync(0)
171     , m_executingScript(0)
172     , m_requestingScript(false)
173     , m_hasScriptsWaitingForStylesheets(false)
174     , m_timer(this, &HTMLTokenizer::timerFired)
175     , m_doc(doc)
176     , parser(0)
177     , inWrite(false)
178     , m_fragment(false)
179 {
180     begin();
181 }
182
183 HTMLTokenizer::HTMLTokenizer(DocumentFragment* frag)
184     : buffer(0)
185     , scriptCode(0)
186     , scriptCodeSize(0)
187     , scriptCodeMaxSize(0)
188     , scriptCodeResync(0)
189     , m_executingScript(0)
190     , m_requestingScript(false)
191     , m_hasScriptsWaitingForStylesheets(false)
192     , m_timer(this, &HTMLTokenizer::timerFired)
193     , m_doc(frag->document())
194     , inWrite(false)
195     , m_fragment(true)
196 {
197     parser = new HTMLParser(frag);
198     begin();
199 }
200
201 void HTMLTokenizer::reset()
202 {
203     ASSERT(m_executingScript == 0);
204
205     while (!pendingScripts.isEmpty()) {
206       CachedScript *cs = pendingScripts.dequeue();
207       ASSERT(cache()->disabled() || cs->accessCount() > 0);
208       cs->deref(this);
209     }
210     
211     fastFree(buffer);
212     buffer = dest = 0;
213     size = 0;
214
215     fastFree(scriptCode);
216     scriptCode = 0;
217     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
218
219     m_timer.stop();
220     m_state.setAllowYield(false);
221     m_state.setForceSynchronous(false);
222
223     currToken.reset();
224 }
225
226 void HTMLTokenizer::begin()
227 {
228     m_executingScript = 0;
229     m_requestingScript = false;
230     m_hasScriptsWaitingForStylesheets = false;
231     m_state.setLoadingExtScript(false);
232     reset();
233     size = 254;
234     buffer = static_cast<UChar*>(fastMalloc(sizeof(UChar) * 254));
235     dest = buffer;
236     tquote = NoQuote;
237     searchCount = 0;
238     m_state.setEntityState(NoEntity);
239     scriptSrc = String();
240     pendingSrc.clear();
241     currentPrependingSrc = 0;
242     noMoreData = false;
243     brokenComments = false;
244     brokenServer = false;
245     lineno = 0;
246     scriptStartLineno = 0;
247     tagStartLineno = 0;
248     m_state.setForceSynchronous(false);
249 }
250
251 void HTMLTokenizer::setForceSynchronous(bool force)
252 {
253     m_state.setForceSynchronous(force);
254 }
255
256 HTMLTokenizer::State HTMLTokenizer::processListing(SegmentedString list, State state)
257 {
258     // This function adds the listing 'list' as
259     // preformatted text-tokens to the token-collection
260     while (!list.isEmpty()) {
261         if (state.skipLF()) {
262             state.setSkipLF(false);
263             if (*list == '\n') {
264                 list.advance(0);
265                 continue;
266             }
267         }
268
269         checkBuffer();
270
271         if (*list == '\n' || *list == '\r') {
272             if (state.discardLF())
273                 // Ignore this LF
274                 state.setDiscardLF(false); // We have discarded 1 LF
275             else
276                 *dest++ = '\n';
277
278             /* Check for MS-DOS CRLF sequence */
279             if (*list == '\r')
280                 state.setSkipLF(true);
281
282             list.advance(0);
283         } else {
284             state.setDiscardLF(false);
285             *dest++ = *list;
286             list.advance(0);
287         }
288     }
289
290     return state;
291 }
292
293 HTMLTokenizer::State HTMLTokenizer::parseSpecial(SegmentedString &src, State state)
294 {
295     ASSERT(state.inTextArea() || state.inTitle() || !state.hasEntityState());
296     ASSERT(!state.hasTagState());
297     ASSERT(state.inXmp() + state.inTextArea() + state.inTitle() + state.inStyle() + state.inScript() == 1 );
298     if (state.inScript())
299         scriptStartLineno = lineno;
300
301     if (state.inComment()) 
302         state = parseComment(src, state);
303
304     while ( !src.isEmpty() ) {
305         checkScriptBuffer();
306         UChar ch = *src;
307
308         if (!scriptCodeResync && !brokenComments && !state.inTextArea() && !state.inXmp() && ch == '-' && scriptCodeSize >= 3 && !src.escaped() && scriptCode[scriptCodeSize-3] == '<' && scriptCode[scriptCodeSize-2] == '!' && scriptCode[scriptCodeSize-1] == '-') {
309             state.setInComment(true);
310             state = parseComment(src, state);
311             continue;
312         }
313         if (scriptCodeResync && !tquote && ch == '>') {
314             src.advance(lineNumberPtr());
315             scriptCodeSize = scriptCodeResync-1;
316             scriptCodeResync = 0;
317             scriptCode[ scriptCodeSize ] = scriptCode[ scriptCodeSize + 1 ] = 0;
318             if (state.inScript())
319                 state = scriptHandler(state);
320             else {
321                 state = processListing(SegmentedString(scriptCode, scriptCodeSize), state);
322                 processToken();
323                 if (state.inStyle()) { 
324                     currToken.tagName = styleTag.localName(); 
325                     currToken.beginTag = false; 
326                 } else if (state.inTextArea()) { 
327                     currToken.tagName = textareaTag.localName(); 
328                     currToken.beginTag = false; 
329                 } else if (state.inTitle()) { 
330                     currToken.tagName = titleTag.localName(); 
331                     currToken.beginTag = false; 
332                 } else if (state.inXmp()) {
333                     currToken.tagName = xmpTag.localName(); 
334                     currToken.beginTag = false; 
335                 }
336                 processToken();
337                 state.setInStyle(false);
338                 state.setInScript(false);
339                 state.setInTextArea(false);
340                 state.setInTitle(false);
341                 state.setInXmp(false);
342                 tquote = NoQuote;
343                 scriptCodeSize = scriptCodeResync = 0;
344             }
345             return state;
346         }
347         // possible end of tagname, lets check.
348         if (!scriptCodeResync && !state.escaped() && !src.escaped() && (ch == '>' || ch == '/' || ch <= ' ') && ch &&
349              scriptCodeSize >= searchStopperLen &&
350              tagMatch( searchStopper, scriptCode+scriptCodeSize-searchStopperLen, searchStopperLen )) {
351             scriptCodeResync = scriptCodeSize-searchStopperLen+1;
352             tquote = NoQuote;
353             continue;
354         }
355         if (scriptCodeResync && !state.escaped()) {
356             if (ch == '\"')
357                 tquote = (tquote == NoQuote) ? DoubleQuote : ((tquote == SingleQuote) ? SingleQuote : NoQuote);
358             else if (ch == '\'')
359                 tquote = (tquote == NoQuote) ? SingleQuote : (tquote == DoubleQuote) ? DoubleQuote : NoQuote;
360             else if (tquote != NoQuote && (ch == '\r' || ch == '\n'))
361                 tquote = NoQuote;
362         }
363         state.setEscaped(!state.escaped() && ch == '\\');
364         if (!scriptCodeResync && (state.inTextArea() || state.inTitle()) && !src.escaped() && ch == '&') {
365             UChar* scriptCodeDest = scriptCode+scriptCodeSize;
366             src.advance(lineNumberPtr());
367             state = parseEntity(src, scriptCodeDest, state, m_cBufferPos, true, false);
368             scriptCodeSize = scriptCodeDest-scriptCode;
369         } else {
370             scriptCode[scriptCodeSize++] = *src;
371             src.advance(lineNumberPtr());
372         }
373     }
374
375     return state;
376 }
377
378 HTMLTokenizer::State HTMLTokenizer::scriptHandler(State state)
379 {
380     // We are inside a <script>
381     bool doScriptExec = false;
382
383     // (Bugzilla 3837) Scripts following a frameset element should not execute or, 
384     // in the case of extern scripts, even load.
385     bool followingFrameset = (m_doc->body() && m_doc->body()->hasTagName(framesetTag));
386   
387     CachedScript* cs = 0;
388     // don't load external scripts for standalone documents (for now)
389     if (!inViewSourceMode()) {
390         if (!scriptSrc.isEmpty() && m_doc->frame()) {
391             // forget what we just got; load from src url instead
392             if (!parser->skipMode() && !followingFrameset) {
393 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
394                 if (!m_doc->ownerElement())
395                     printf("Requesting script at time %d\n", m_doc->elapsedTime());
396 #endif
397                 // The parser might have been stopped by for example a window.close call in an earlier script.
398                 // If so, we don't want to load scripts.
399                 if (!m_parserStopped && (cs = m_doc->docLoader()->requestScript(scriptSrc, scriptSrcCharset)))
400                     pendingScripts.enqueue(cs);
401                 else
402                     scriptNode = 0;
403             } else
404                 scriptNode = 0;
405             scriptSrc = String();
406         } else {
407 #ifdef TOKEN_DEBUG
408             kdDebug( 6036 ) << "---START SCRIPT---" << endl;
409             kdDebug( 6036 ) << DeprecatedString(scriptCode, scriptCodeSize) << endl;
410             kdDebug( 6036 ) << "---END SCRIPT---" << endl;
411 #endif
412             // Parse scriptCode containing <script> info
413 #if USE(LOW_BANDWIDTH_DISPLAY)
414             if (m_doc->inLowBandwidthDisplay()) {
415                 // ideal solution is only skipping internal JavaScript if there is external JavaScript.
416                 // but internal JavaScript can use document.write() to create an external JavaScript,
417                 // so we have to skip internal JavaScript all the time.
418                 m_doc->frame()->loader()->needToSwitchOutLowBandwidthDisplay();
419                 doScriptExec = false;
420             } else
421 #endif
422             doScriptExec = static_cast<HTMLScriptElement*>(scriptNode.get())->shouldExecuteAsJavaScript();
423             scriptNode = 0;
424         }
425     }
426
427     state = processListing(SegmentedString(scriptCode, scriptCodeSize), state);
428     DeprecatedString exScript(reinterpret_cast<DeprecatedChar*>(buffer), dest - buffer);
429     processToken();
430     currToken.tagName = scriptTag.localName();
431     currToken.beginTag = false;
432     processToken();
433
434     state.setInScript(false);
435     
436     // FIXME: The script should be syntax highlighted.
437     if (inViewSourceMode())
438         return state;
439
440     SegmentedString *savedPrependingSrc = currentPrependingSrc;
441     SegmentedString prependingSrc;
442     currentPrependingSrc = &prependingSrc;
443     scriptCodeSize = scriptCodeResync = 0;
444
445     if (!parser->skipMode() && !followingFrameset) {
446         if (cs) {
447             if (savedPrependingSrc)
448                 savedPrependingSrc->append(src);
449             else
450                 pendingSrc.prepend(src);
451             setSrc(SegmentedString());
452
453             // the ref() call below may call notifyFinished if the script is already in cache,
454             // and that mucks with the state directly, so we must write it back to the object.
455             m_state = state;
456             bool savedRequestingScript = m_requestingScript;
457             m_requestingScript = true;
458             cs->ref(this);
459             m_requestingScript = savedRequestingScript;
460             state = m_state;
461             // will be 0 if script was already loaded and ref() executed it
462             if (!pendingScripts.isEmpty())
463                 state.setLoadingExtScript(true);
464         } else if (!m_fragment && doScriptExec) {
465             if (!m_executingScript)
466                 pendingSrc.prepend(src);
467             else
468                 prependingSrc = src;
469             setSrc(SegmentedString());
470             state = scriptExecution(exScript, state, DeprecatedString::null, scriptStartLineno);
471         }
472     }
473
474     if (!m_executingScript && !state.loadingExtScript()) {
475         src.append(pendingSrc);
476         pendingSrc.clear();
477     } else if (!prependingSrc.isEmpty()) {
478         // restore first so that the write appends in the right place
479         // (does not hurt to do it again below)
480         currentPrependingSrc = savedPrependingSrc;
481
482         // we need to do this slightly modified bit of one of the write() cases
483         // because we want to prepend to pendingSrc rather than appending
484         // if there's no previous prependingSrc
485         if (state.loadingExtScript()) {
486             if (currentPrependingSrc) {
487                 currentPrependingSrc->append(prependingSrc);
488             } else {
489                 pendingSrc.prepend(prependingSrc);
490             }
491         } else {
492             m_state = state;
493             write(prependingSrc, false);
494             state = m_state;
495         }
496     }
497
498     currentPrependingSrc = savedPrependingSrc;
499
500     return state;
501 }
502
503 HTMLTokenizer::State HTMLTokenizer::scriptExecution(const DeprecatedString& str, State state, DeprecatedString scriptURL, int baseLine)
504 {
505     if (m_fragment || !m_doc->frame())
506         return state;
507     m_executingScript++;
508     DeprecatedString url = scriptURL.isNull() ? m_doc->frame()->document()->URL() : scriptURL;
509
510     SegmentedString *savedPrependingSrc = currentPrependingSrc;
511     SegmentedString prependingSrc;
512     currentPrependingSrc = &prependingSrc;
513
514 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
515     if (!m_doc->ownerElement())
516         printf("beginning script execution at %d\n", m_doc->elapsedTime());
517 #endif
518
519     m_state = state;
520     m_doc->frame()->loader()->executeScript(url, baseLine, str);
521     state = m_state;
522
523     state.setAllowYield(true);
524
525 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
526     if (!m_doc->ownerElement())
527         printf("ending script execution at %d\n", m_doc->elapsedTime());
528 #endif
529     
530     m_executingScript--;
531
532     if (!m_executingScript && !state.loadingExtScript()) {
533         pendingSrc.prepend(prependingSrc);        
534         src.append(pendingSrc);
535         pendingSrc.clear();
536     } else if (!prependingSrc.isEmpty()) {
537         // restore first so that the write appends in the right place
538         // (does not hurt to do it again below)
539         currentPrependingSrc = savedPrependingSrc;
540
541         // we need to do this slightly modified bit of one of the write() cases
542         // because we want to prepend to pendingSrc rather than appending
543         // if there's no previous prependingSrc
544         if (state.loadingExtScript()) {
545             if (currentPrependingSrc)
546                 currentPrependingSrc->append(prependingSrc);
547             else
548                 pendingSrc.prepend(prependingSrc);
549         } else {
550             m_state = state;
551             write(prependingSrc, false);
552             state = m_state;
553         }
554     }
555
556     currentPrependingSrc = savedPrependingSrc;
557
558     return state;
559 }
560
561 HTMLTokenizer::State HTMLTokenizer::parseComment(SegmentedString &src, State state)
562 {
563     // FIXME: Why does this code even run for comments inside <script> and <style>? This seems bogus.
564     checkScriptBuffer(src.length());
565     while ( !src.isEmpty() ) {
566         scriptCode[ scriptCodeSize++ ] = *src;
567
568         if (*src == '>') {
569             bool handleBrokenComments = brokenComments && !(state.inScript() || state.inStyle());
570             int endCharsCount = 1; // start off with one for the '>' character
571             if (scriptCodeSize > 2 && scriptCode[scriptCodeSize-3] == '-' && scriptCode[scriptCodeSize-2] == '-') {
572                 endCharsCount = 3;
573             }
574             else if (scriptCodeSize > 3 && scriptCode[scriptCodeSize-4] == '-' && scriptCode[scriptCodeSize-3] == '-' && 
575                 scriptCode[scriptCodeSize-2] == '!') {
576                 // Other browsers will accept --!> as a close comment, even though it's
577                 // not technically valid.
578                 endCharsCount = 4;
579             }
580             if (handleBrokenComments || endCharsCount > 1) {
581                 src.advance(lineNumberPtr());
582                 if (!(state.inTitle() || state.inScript() || state.inXmp() || state.inTextArea() || state.inStyle())) {
583                     checkScriptBuffer();
584                     scriptCode[scriptCodeSize] = 0;
585                     scriptCode[scriptCodeSize + 1] = 0;
586                     currToken.tagName = commentAtom;
587                     currToken.beginTag = true;
588                     state = processListing(SegmentedString(scriptCode, scriptCodeSize - endCharsCount), state);
589                     processToken();
590                     currToken.tagName = commentAtom;
591                     currToken.beginTag = false;
592                     processToken();
593                     scriptCodeSize = 0;
594                 }
595                 state.setInComment(false);
596                 return state; // Finished parsing comment
597             }
598         }
599         src.advance(lineNumberPtr());
600     }
601
602     return state;
603 }
604
605 HTMLTokenizer::State HTMLTokenizer::parseServer(SegmentedString& src, State state)
606 {
607     checkScriptBuffer(src.length());
608     while (!src.isEmpty()) {
609         scriptCode[scriptCodeSize++] = *src;
610         if (*src == '>' &&
611             scriptCodeSize > 1 && scriptCode[scriptCodeSize-2] == '%') {
612             src.advance(lineNumberPtr());
613             state.setInServer(false);
614             scriptCodeSize = 0;
615             return state; // Finished parsing server include
616         }
617         src.advance(lineNumberPtr());
618     }
619     return state;
620 }
621
622 HTMLTokenizer::State HTMLTokenizer::parseProcessingInstruction(SegmentedString &src, State state)
623 {
624     UChar oldchar = 0;
625     while (!src.isEmpty()) {
626         UChar chbegin = *src;
627         if (chbegin == '\'')
628             tquote = tquote == SingleQuote ? NoQuote : SingleQuote;
629         else if (chbegin == '\"')
630             tquote = tquote == DoubleQuote ? NoQuote : DoubleQuote;
631         // Look for '?>'
632         // Some crappy sites omit the "?" before it, so
633         // we look for an unquoted '>' instead. (IE compatible)
634         else if (chbegin == '>' && (!tquote || oldchar == '?')) {
635             // We got a '?>' sequence
636             state.setInProcessingInstruction(false);
637             src.advance(lineNumberPtr());
638             state.setDiscardLF(true);
639             return state; // Finished parsing comment!
640         }
641         src.advance(lineNumberPtr());
642         oldchar = chbegin;
643     }
644     
645     return state;
646 }
647
648 HTMLTokenizer::State HTMLTokenizer::parseText(SegmentedString &src, State state)
649 {
650     while (!src.isEmpty()) {
651         UChar cc = *src;
652
653         if (state.skipLF()) {
654             state.setSkipLF(false);
655             if (cc == '\n') {
656                 src.advance(lineNumberPtr());
657                 continue;
658             }
659         }
660
661         // do we need to enlarge the buffer?
662         checkBuffer();
663
664         if (cc == '\r') {
665             state.setSkipLF(true);
666             *dest++ = '\n';
667         } else
668             *dest++ = cc;
669         src.advance(lineNumberPtr());
670     }
671
672     return state;
673 }
674
675
676 HTMLTokenizer::State HTMLTokenizer::parseEntity(SegmentedString &src, UChar*& dest, State state, unsigned &cBufferPos, bool start, bool parsingTag)
677 {
678     if (start)
679     {
680         cBufferPos = 0;
681         state.setEntityState(SearchEntity);
682         EntityUnicodeValue = 0;
683     }
684
685     while(!src.isEmpty())
686     {
687         UChar cc = *src;
688         switch(state.entityState()) {
689         case NoEntity:
690             ASSERT(state.entityState() != NoEntity);
691             return state;
692         
693         case SearchEntity:
694             if(cc == '#') {
695                 cBuffer[cBufferPos++] = cc;
696                 src.advance(lineNumberPtr());
697                 state.setEntityState(NumericSearch);
698             }
699             else
700                 state.setEntityState(EntityName);
701
702             break;
703
704         case NumericSearch:
705             if (cc == 'x' || cc == 'X') {
706                 cBuffer[cBufferPos++] = cc;
707                 src.advance(lineNumberPtr());
708                 state.setEntityState(Hexadecimal);
709             } else if (cc >= '0' && cc <= '9')
710                 state.setEntityState(Decimal);
711             else
712                 state.setEntityState(SearchSemicolon);
713             break;
714
715         case Hexadecimal: {
716             int ll = min(src.length(), 10 - cBufferPos);
717             while (ll--) {
718                 cc = *src;
719                 if (!((cc >= '0' && cc <= '9') || (cc >= 'a' && cc <= 'f') || (cc >= 'A' && cc <= 'F'))) {
720                     state.setEntityState(SearchSemicolon);
721                     break;
722                 }
723                 int digit;
724                 if (cc < 'A')
725                     digit = cc - '0';
726                 else
727                     digit = (cc - 'A' + 10) & 0xF; // handle both upper and lower case without a branch
728                 EntityUnicodeValue = EntityUnicodeValue * 16 + digit;
729                 cBuffer[cBufferPos++] = cc;
730                 src.advance(lineNumberPtr());
731             }
732             if (cBufferPos == 10)  
733                 state.setEntityState(SearchSemicolon);
734             break;
735         }
736         case Decimal:
737         {
738             int ll = min(src.length(), 9-cBufferPos);
739             while(ll--) {
740                 cc = *src;
741
742                 if (!(cc >= '0' && cc <= '9')) {
743                     state.setEntityState(SearchSemicolon);
744                     break;
745                 }
746
747                 EntityUnicodeValue = EntityUnicodeValue * 10 + (cc - '0');
748                 cBuffer[cBufferPos++] = cc;
749                 src.advance(lineNumberPtr());
750             }
751             if (cBufferPos == 9)  
752                 state.setEntityState(SearchSemicolon);
753             break;
754         }
755         case EntityName:
756         {
757             int ll = min(src.length(), 9-cBufferPos);
758             while(ll--) {
759                 cc = *src;
760
761                 if (!((cc >= 'a' && cc <= 'z') || (cc >= '0' && cc <= '9') || (cc >= 'A' && cc <= 'Z'))) {
762                     state.setEntityState(SearchSemicolon);
763                     break;
764                 }
765
766                 cBuffer[cBufferPos++] = cc;
767                 src.advance(lineNumberPtr());
768             }
769             if (cBufferPos == 9) 
770                 state.setEntityState(SearchSemicolon);
771             if (state.entityState() == SearchSemicolon) {
772                 if(cBufferPos > 1) {
773                     const Entity *e = findEntity(cBuffer, cBufferPos);
774                     if(e)
775                         EntityUnicodeValue = e->code;
776
777                     // be IE compatible
778                     if(parsingTag && EntityUnicodeValue > 255 && *src != ';')
779                         EntityUnicodeValue = 0;
780                 }
781             }
782             else
783                 break;
784         }
785         case SearchSemicolon:
786             // Don't allow values that are more than 21 bits.
787             if (EntityUnicodeValue > 0 && EntityUnicodeValue <= 0x10FFFF) {
788                 if (!inViewSourceMode()) {
789                     if (*src == ';')
790                         src.advance(lineNumberPtr());
791                     if (EntityUnicodeValue <= 0xFFFF) {
792                         checkBuffer();
793                         src.push(fixUpChar(EntityUnicodeValue));
794                     } else {
795                         // Convert to UTF-16, using surrogate code points.
796                         checkBuffer(2);
797                         src.push(U16_LEAD(EntityUnicodeValue));
798                         src.push(U16_TRAIL(EntityUnicodeValue));
799                     }
800                 } else {
801                     // FIXME: We should eventually colorize entities by sending them as a special token.
802                     checkBuffer(11);
803                     *dest++ = '&';
804                     for (unsigned i = 0; i < cBufferPos; i++)
805                         dest[i] = cBuffer[i];
806                     dest += cBufferPos;
807                     if (*src == ';') {
808                         *dest++ = ';';
809                         src.advance(lineNumberPtr());
810                     }
811                 }
812             } else {
813                 checkBuffer(10);
814                 // ignore the sequence, add it to the buffer as plaintext
815                 *dest++ = '&';
816                 for (unsigned i = 0; i < cBufferPos; i++)
817                     dest[i] = cBuffer[i];
818                 dest += cBufferPos;
819             }
820
821             state.setEntityState(NoEntity);
822             return state;
823         }
824     }
825
826     return state;
827 }
828
829 HTMLTokenizer::State HTMLTokenizer::parseTag(SegmentedString &src, State state)
830 {
831     ASSERT(!state.hasEntityState());
832
833     unsigned cBufferPos = m_cBufferPos;
834
835     int* lineNoPtr = lineNumberPtr();
836     bool lastIsSlash = false;
837
838     while (!src.isEmpty()) {
839         checkBuffer();
840         switch(state.tagState()) {
841         case NoTag:
842         {
843             m_cBufferPos = cBufferPos;
844             return state;
845         }
846         case TagName:
847         {
848 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
849             qDebug("TagName");
850 #endif
851             if (searchCount > 0)
852             {
853                 if (*src == commentStart[searchCount])
854                 {
855                     searchCount++;
856                     if (searchCount == 4)
857                     {
858 #ifdef TOKEN_DEBUG
859                         kdDebug( 6036 ) << "Found comment" << endl;
860 #endif
861                         // Found '<!--' sequence
862                         src.advance(lineNoPtr);
863                         dest = buffer; // ignore the previous part of this tag
864                         state.setInComment(true);
865                         state.setTagState(NoTag);
866
867                         // Fix bug 34302 at kde.bugs.org.  Go ahead and treat
868                         // <!--> as a valid comment, since both mozilla and IE on windows
869                         // can handle this case.  Only do this in quirks mode. -dwh
870                         if (!src.isEmpty() && *src == '>' && m_doc->inCompatMode()) {
871                           state.setInComment(false);
872                           src.advance(lineNoPtr);
873                           if (!src.isEmpty())
874                               // cuts off high bits, which is okay
875                               cBuffer[cBufferPos++] = *src;
876                         }
877                         else
878                           state = parseComment(src, state);
879
880                         m_cBufferPos = cBufferPos;
881                         return state; // Finished parsing tag!
882                     }
883                     // cuts off high bits, which is okay
884                     cBuffer[cBufferPos++] = *src;
885                     src.advance(lineNoPtr);
886                     break;
887                 }
888                 else
889                     searchCount = 0; // Stop looking for '<!--' sequence
890             }
891
892             bool finish = false;
893             unsigned int ll = min(src.length(), CBUFLEN - cBufferPos);
894             while (ll--) {
895                 UChar curchar = *src;
896                 if (curchar <= ' ' || curchar == '>' || curchar == '<') {
897                     finish = true;
898                     break;
899                 }
900                 
901                 // tolower() shows up on profiles. This is faster!
902                 if (curchar >= 'A' && curchar <= 'Z' && !inViewSourceMode())
903                     cBuffer[cBufferPos++] = curchar + ('a' - 'A');
904                 else
905                     cBuffer[cBufferPos++] = curchar;
906                 src.advance(lineNoPtr);
907             }
908
909             // Disadvantage: we add the possible rest of the tag
910             // as attribute names. ### judge if this causes problems
911             if(finish || CBUFLEN == cBufferPos) {
912                 bool beginTag;
913                 char* ptr = cBuffer;
914                 unsigned int len = cBufferPos;
915                 cBuffer[cBufferPos] = '\0';
916                 if ((cBufferPos > 0) && (*ptr == '/')) {
917                     // End Tag
918                     beginTag = false;
919                     ptr++;
920                     len--;
921                 }
922                 else
923                     // Start Tag
924                     beginTag = true;
925
926                 // Ignore the / in fake xml tags like <br/>.  We trim off the "/" so that we'll get "br" as the tag name and not "br/".
927                 if (len > 1 && ptr[len-1] == '/' && !inViewSourceMode())
928                     ptr[--len] = '\0';
929
930                 // Now that we've shaved off any invalid / that might have followed the name), make the tag.
931                 // FIXME: FireFox and WinIE turn !foo nodes into comments, we ignore comments. (fast/parser/tag-with-exclamation-point.html)
932                 if (ptr[0] != '!' || inViewSourceMode()) {
933                     currToken.tagName = AtomicString(ptr);
934                     currToken.beginTag = beginTag;
935                 }
936                 dest = buffer;
937                 state.setTagState(SearchAttribute);
938                 cBufferPos = 0;
939             }
940             break;
941         }
942         case SearchAttribute:
943 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
944             qDebug("SearchAttribute");
945 #endif
946             while(!src.isEmpty()) {
947                 UChar curchar = *src;
948                 // In this mode just ignore any quotes we encounter and treat them like spaces.
949                 if (curchar > ' ' && curchar != '\'' && curchar != '"') {
950                     if (curchar == '<' || curchar == '>')
951                         state.setTagState(SearchEnd);
952                     else
953                         state.setTagState(AttributeName);
954
955                     cBufferPos = 0;
956                     break;
957                 }
958                 if (inViewSourceMode())
959                     currToken.addViewSourceChar(curchar);
960                 src.advance(lineNoPtr);
961             }
962             break;
963         case AttributeName:
964         {
965 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
966             qDebug("AttributeName");
967 #endif
968             int ll = min(src.length(), CBUFLEN-cBufferPos);
969             while(ll--) {
970                 UChar curchar = *src;
971                 // If we encounter a "/" when scanning an attribute name, treat it as a delimiter.  This allows the 
972                 // cases like <input type=checkbox checked/> to work (and accommodates XML-style syntax as per HTML5).
973                 if (curchar <= '>' && (curchar >= '<' || curchar <= ' ' || curchar == '/')) {
974                     cBuffer[cBufferPos] = '\0';
975                     attrName = AtomicString(cBuffer);
976                     dest = buffer;
977                     *dest++ = 0;
978                     state.setTagState(SearchEqual);
979                     if (inViewSourceMode())
980                         currToken.addViewSourceChar('a');
981                     break;
982                 }
983                 
984                 // tolower() shows up on profiles. This is faster!
985                 if (curchar >= 'A' && curchar <= 'Z' && !inViewSourceMode())
986                     cBuffer[cBufferPos++] = curchar + ('a' - 'A');
987                 else
988                     cBuffer[cBufferPos++] = curchar;
989                     
990                 src.advance(lineNoPtr);
991             }
992             if ( cBufferPos == CBUFLEN ) {
993                 cBuffer[cBufferPos] = '\0';
994                 attrName = AtomicString(cBuffer);
995                 dest = buffer;
996                 *dest++ = 0;
997                 state.setTagState(SearchEqual);
998                 if (inViewSourceMode())
999                     currToken.addViewSourceChar('a');
1000             }
1001             break;
1002         }
1003         case SearchEqual:
1004 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1005             qDebug("SearchEqual");
1006 #endif
1007             while(!src.isEmpty()) {
1008                 UChar curchar = *src;
1009
1010                 if (lastIsSlash && curchar == '>') {
1011                     // This is a quirk (with a long sad history).  We have to do this
1012                     // since widgets do <script src="foo.js"/> and expect the tag to close.
1013                     if (currToken.tagName == scriptTag)
1014                         currToken.flat = true;
1015                     currToken.brokenXMLStyle = true;
1016                 }
1017
1018                 // In this mode just ignore any quotes or slashes we encounter and treat them like spaces.
1019                 if (curchar > ' ' && curchar != '\'' && curchar != '"' && curchar != '/') {
1020                     if(curchar == '=') {
1021 #ifdef TOKEN_DEBUG
1022                         kdDebug(6036) << "found equal" << endl;
1023 #endif
1024                         state.setTagState(SearchValue);
1025                         if (inViewSourceMode())
1026                             currToken.addViewSourceChar(curchar);
1027                         src.advance(lineNoPtr);
1028                     }
1029                     else {
1030                         currToken.addAttribute(m_doc, attrName, emptyAtom, inViewSourceMode());
1031                         dest = buffer;
1032                         state.setTagState(SearchAttribute);
1033                         lastIsSlash = false;
1034                     }
1035                     break;
1036                 }
1037                 if (inViewSourceMode())
1038                     currToken.addViewSourceChar(curchar);
1039                     
1040                 lastIsSlash = curchar == '/';
1041
1042                 src.advance(lineNoPtr);
1043             }
1044             break;
1045         case SearchValue:
1046             while(!src.isEmpty()) {
1047                 UChar curchar = *src;
1048                 if(curchar > ' ') {
1049                     if(( curchar == '\'' || curchar == '\"' )) {
1050                         tquote = curchar == '\"' ? DoubleQuote : SingleQuote;
1051                         state.setTagState(QuotedValue);
1052                         if (inViewSourceMode())
1053                             currToken.addViewSourceChar(curchar);
1054                         src.advance(lineNoPtr);
1055                     } else
1056                         state.setTagState(Value);
1057
1058                     break;
1059                 }
1060                 if (inViewSourceMode())
1061                     currToken.addViewSourceChar(curchar);
1062                 src.advance(lineNoPtr);
1063             }
1064             break;
1065         case QuotedValue:
1066 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1067             qDebug("QuotedValue");
1068 #endif
1069             while(!src.isEmpty()) {
1070                 checkBuffer();
1071
1072                 UChar curchar = *src;
1073                 if (curchar == '>' && attrName.isEmpty()) {
1074                     // Handle a case like <img '>.  Just go ahead and be willing
1075                     // to close the whole tag.  Don't consume the character and
1076                     // just go back into SearchEnd while ignoring the whole
1077                     // value.
1078                     // FIXME: Note that this is actually not a very good solution. It's
1079                     // an interim hack and doesn't handle the general case of
1080                     // unmatched quotes among attributes that have names. -dwh
1081                     while(dest > buffer+1 && (*(dest-1) == '\n' || *(dest-1) == '\r'))
1082                         dest--; // remove trailing newlines
1083                     AtomicString v(buffer+1, dest-buffer-1);
1084                     attrName = v; // Just make the name/value match. (FIXME: Is this some WinIE quirk?)
1085                     currToken.addAttribute(m_doc, attrName, v, inViewSourceMode());
1086                     if (inViewSourceMode())
1087                         currToken.addViewSourceChar('x');
1088                     state.setTagState(SearchAttribute);
1089                     dest = buffer;
1090                     tquote = NoQuote;
1091                     break;
1092                 }
1093                 
1094                 if(curchar <= '\'' && !src.escaped()) {
1095                     // ### attributes like '&{blaa....};' are supposed to be treated as jscript.
1096                     if ( curchar == '&' )
1097                     {
1098                         src.advance(lineNoPtr);
1099                         state = parseEntity(src, dest, state, cBufferPos, true, true);
1100                         break;
1101                     }
1102                     else if ( (tquote == SingleQuote && curchar == '\'') ||
1103                               (tquote == DoubleQuote && curchar == '\"') )
1104                     {
1105                         // some <input type=hidden> rely on trailing spaces. argh
1106                         while(dest > buffer+1 && (*(dest-1) == '\n' || *(dest-1) == '\r'))
1107                             dest--; // remove trailing newlines
1108                         AtomicString v(buffer+1, dest-buffer-1);
1109                         if (attrName.isEmpty()) {
1110                             attrName = v; // Make the name match the value. (FIXME: Is this a WinIE quirk?)
1111                             if (inViewSourceMode())
1112                                 currToken.addViewSourceChar('x');
1113                         } else if (inViewSourceMode())
1114                             currToken.addViewSourceChar('v');
1115                         currToken.addAttribute(m_doc, attrName, v, inViewSourceMode());
1116                         dest = buffer;
1117                         state.setTagState(SearchAttribute);
1118                         tquote = NoQuote;
1119                         if (inViewSourceMode())
1120                             currToken.addViewSourceChar(curchar);
1121                         src.advance(lineNoPtr);
1122                         break;
1123                     }
1124                 }
1125                 *dest++ = *src;
1126                 src.advance(lineNoPtr);
1127             }
1128             break;
1129         case Value:
1130 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1131             qDebug("Value");
1132 #endif
1133             while(!src.isEmpty()) {
1134                 checkBuffer();
1135                 UChar curchar = *src;
1136                 if(curchar <= '>' && !src.escaped()) {
1137                     // parse Entities
1138                     if ( curchar == '&' )
1139                     {
1140                         src.advance(lineNoPtr);
1141                         state = parseEntity(src, dest, state, cBufferPos, true, true);
1142                         break;
1143                     }
1144                     // no quotes. Every space means end of value
1145                     // '/' does not delimit in IE!
1146                     if ( curchar <= ' ' || curchar == '>' )
1147                     {
1148                         AtomicString v(buffer+1, dest-buffer-1);
1149                         currToken.addAttribute(m_doc, attrName, v, inViewSourceMode());
1150                         if (inViewSourceMode())
1151                             currToken.addViewSourceChar('v');
1152                         dest = buffer;
1153                         state.setTagState(SearchAttribute);
1154                         break;
1155                     }
1156                 }
1157
1158                 *dest++ = *src;
1159                 src.advance(lineNoPtr);
1160             }
1161             break;
1162         case SearchEnd:
1163         {
1164 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1165                 qDebug("SearchEnd");
1166 #endif
1167             while(!src.isEmpty()) {
1168                 if (*src == '>' || *src == '<')
1169                     break;
1170
1171                 if (*src == '/')
1172                     currToken.flat = true;
1173
1174                 if (inViewSourceMode())
1175                     currToken.addViewSourceChar(*src);
1176                 src.advance(lineNoPtr);
1177             }
1178             if (src.isEmpty()) break;
1179
1180             searchCount = 0; // Stop looking for '<!--' sequence
1181             state.setTagState(NoTag);
1182             tquote = NoQuote;
1183
1184             if (*src != '<')
1185                 src.advance(lineNoPtr);
1186
1187             if (currToken.tagName == nullAtom) { //stop if tag is unknown
1188                 m_cBufferPos = cBufferPos;
1189                 return state;
1190             }
1191
1192             AtomicString tagName = currToken.tagName;
1193 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 0
1194             kdDebug( 6036 ) << "appending Tag: " << tagName.deprecatedString() << endl;
1195 #endif
1196
1197             // Handle <script src="foo"/> like Mozilla/Opera. We have to do this now for Dashboard
1198             // compatibility.
1199             bool isSelfClosingScript = currToken.flat && currToken.beginTag && currToken.tagName == scriptTag;
1200             bool beginTag = !currToken.flat && currToken.beginTag;
1201             if (currToken.beginTag && currToken.tagName == scriptTag && !inViewSourceMode() && !parser->skipMode()) {
1202                 Attribute* a = 0;
1203                 scriptSrc = String();
1204                 scriptSrcCharset = String();
1205                 if (currToken.attrs && !m_fragment) {
1206                     Settings* settings = m_doc->settings();
1207                     if (settings && settings->isJavaScriptEnabled()) {
1208                         if ((a = currToken.attrs->getAttributeItem(srcAttr)))
1209                             scriptSrc = m_doc->completeURL(parseURL(a->value()));
1210                         if ((a = currToken.attrs->getAttributeItem(charsetAttr)))
1211                             scriptSrcCharset = a->value().domString().stripWhiteSpace();
1212                         if (scriptSrcCharset.isEmpty())
1213                             scriptSrcCharset = m_doc->frame()->loader()->encoding();
1214                     }
1215                 }
1216             }
1217
1218             RefPtr<Node> n = processToken();
1219             m_cBufferPos = cBufferPos;
1220             if (n) {
1221                 if ((tagName == preTag || tagName == listingTag) && !inViewSourceMode()) {
1222                     if (beginTag)
1223                         state.setDiscardLF(true); // Discard the first LF after we open a pre.
1224                 } else if (tagName == scriptTag && n) {
1225                     ASSERT(!scriptNode);
1226                     scriptNode = n;
1227                     if (beginTag) {
1228                         searchStopper = scriptEnd;
1229                         searchStopperLen = 8;
1230                         state.setInScript(true);
1231                         state = parseSpecial(src, state);
1232                     } else if (isSelfClosingScript) { // Handle <script src="foo"/>
1233                         state.setInScript(true);
1234                         state = scriptHandler(state);
1235                     }
1236                 } else if (tagName == styleTag) {
1237                     if (beginTag) {
1238                         searchStopper = styleEnd;
1239                         searchStopperLen = 7;
1240                         state.setInStyle(true);
1241                         state = parseSpecial(src, state);
1242                     }
1243                 } else if (tagName == textareaTag) {
1244                     if (beginTag) {
1245                         searchStopper = textareaEnd;
1246                         searchStopperLen = 10;
1247                         state.setInTextArea(true);
1248                         state = parseSpecial(src, state);
1249                     }
1250                 } else if (tagName == titleTag) {
1251                     if (beginTag) {
1252                         searchStopper = titleEnd;
1253                         searchStopperLen = 7;
1254                         State savedState = state;
1255                         SegmentedString savedSrc = src;
1256                         long savedLineno = lineno;
1257                         state.setInTitle(true);
1258                         state = parseSpecial(src, state);
1259                         if (state.inTitle() && src.isEmpty()) {
1260                             // We just ate the rest of the document as the title #text node!
1261                             // Reset the state then retokenize without special title handling.
1262                             // Let the parser clean up the missing </title> tag.
1263                             // FIXME: This is incorrect, because src.isEmpty() doesn't mean we're
1264                             // at the end of the document unless noMoreData is also true. We need
1265                             // to detect this case elsewhere, and save the state somewhere other
1266                             // than a local variable.
1267                             state = savedState;
1268                             src = savedSrc;
1269                             lineno = savedLineno;
1270                             scriptCodeSize = 0;
1271                         }
1272                     }
1273                 } else if (tagName == xmpTag) {
1274                     if (beginTag) {
1275                         searchStopper = xmpEnd;
1276                         searchStopperLen = 5;
1277                         state.setInXmp(true);
1278                         state = parseSpecial(src, state);
1279                     }
1280                 }
1281             }
1282             if (tagName == plaintextTag)
1283                 state.setInPlainText(beginTag);
1284             return state; // Finished parsing tag!
1285         }
1286         } // end switch
1287     }
1288     m_cBufferPos = cBufferPos;
1289     return state;
1290 }
1291
1292 inline bool HTMLTokenizer::continueProcessing(int& processedCount, double startTime, State &state)
1293 {
1294     // We don't want to be checking elapsed time with every character, so we only check after we've
1295     // processed a certain number of characters.
1296     bool allowedYield = state.allowYield();
1297     state.setAllowYield(false);
1298     if (!state.loadingExtScript() && !state.forceSynchronous() && !m_executingScript && (processedCount > TOKENIZER_CHUNK_SIZE || allowedYield)) {
1299         processedCount = 0;
1300         if (currentTime() - startTime > tokenizerTimeDelay) {
1301             /* FIXME: We'd like to yield aggressively to give stylesheets the opportunity to
1302                load, but this hurts overall performance on slower machines.  For now turn this
1303                off.
1304             || (!m_doc->haveStylesheetsLoaded() && 
1305                 (m_doc->documentElement()->id() != ID_HTML || m_doc->body()))) {*/
1306             // Schedule the timer to keep processing as soon as possible.
1307             m_timer.startOneShot(0);
1308 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1309             if (currentTime() - startTime > tokenizerTimeDelay)
1310                 printf("Deferring processing of data because 500ms elapsed away from event loop.\n");
1311 #endif
1312             return false;
1313         }
1314     }
1315     
1316     processedCount++;
1317     return true;
1318 }
1319
1320 bool HTMLTokenizer::write(const SegmentedString& str, bool appendData)
1321 {
1322 #ifdef TOKEN_DEBUG
1323     kdDebug( 6036 ) << this << " Tokenizer::write(\"" << str.toString() << "\"," << appendData << ")" << endl;
1324 #endif
1325
1326     if (!buffer)
1327         return false;
1328     
1329     if (m_parserStopped)
1330         return false;
1331
1332     SegmentedString source(str);
1333     if (m_executingScript)
1334         source.setExcludeLineNumbers();
1335
1336     if ((m_executingScript && appendData) || !pendingScripts.isEmpty()) {
1337         // don't parse; we will do this later
1338         if (currentPrependingSrc)
1339             currentPrependingSrc->append(source);
1340         else
1341             pendingSrc.append(source);
1342         return false;
1343     }
1344
1345     if (!src.isEmpty())
1346         src.append(source);
1347     else
1348         setSrc(source);
1349
1350     // Once a timer is set, it has control of when the tokenizer continues.
1351     if (m_timer.isActive())
1352         return false;
1353
1354     bool wasInWrite = inWrite;
1355     inWrite = true;
1356     
1357 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1358     if (!m_doc->ownerElement())
1359         printf("Beginning write at time %d\n", m_doc->elapsedTime());
1360 #endif
1361     
1362     int processedCount = 0;
1363     double startTime = currentTime();
1364
1365     Frame *frame = m_doc->frame();
1366
1367     State state = m_state;
1368
1369     int* lineNoPtr = lineNumberPtr();
1370
1371     while (!src.isEmpty() && (!frame || !frame->loader()->isScheduledLocationChangePending())) {
1372         if (!continueProcessing(processedCount, startTime, state))
1373             break;
1374
1375         // do we need to enlarge the buffer?
1376         checkBuffer();
1377
1378         UChar cc = *src;
1379
1380         bool wasSkipLF = state.skipLF();
1381         if (wasSkipLF)
1382             state.setSkipLF(false);
1383
1384         if (wasSkipLF && (cc == '\n'))
1385             src.advance(0);
1386         else if (state.needsSpecialWriteHandling()) {
1387             // it's important to keep needsSpecialWriteHandling with the flags this block tests
1388             if (state.hasEntityState())
1389                 state = parseEntity(src, dest, state, m_cBufferPos, false, state.hasTagState());
1390             else if (state.inPlainText())
1391                 state = parseText(src, state);
1392             else if (state.inAnySpecial())
1393                 state = parseSpecial(src, state);
1394             else if (state.inComment())
1395                 state = parseComment(src, state);
1396             else if (state.inServer())
1397                 state = parseServer(src, state);
1398             else if (state.inProcessingInstruction())
1399                 state = parseProcessingInstruction(src, state);
1400             else if (state.hasTagState())
1401                 state = parseTag(src, state);
1402             else if (state.startTag()) {
1403                 state.setStartTag(false);
1404                 
1405                 switch(cc) {
1406                 case '/':
1407                     break;
1408                 case '!': {
1409                     // <!-- comment -->
1410                     searchCount = 1; // Look for '<!--' sequence to start comment
1411                     
1412                     break;
1413                 }
1414                 case '?': {
1415                     // xml processing instruction
1416                     state.setInProcessingInstruction(true);
1417                     tquote = NoQuote;
1418                     state = parseProcessingInstruction(src, state);
1419                     continue;
1420
1421                     break;
1422                 }
1423                 case '%':
1424                     if (!brokenServer) {
1425                         // <% server stuff, handle as comment %>
1426                         state.setInServer(true);
1427                         tquote = NoQuote;
1428                         state = parseServer(src, state);
1429                         continue;
1430                     }
1431                     // else fall through
1432                 default: {
1433                     if( ((cc >= 'a') && (cc <= 'z')) || ((cc >= 'A') && (cc <= 'Z'))) {
1434                         // Start of a Start-Tag
1435                     } else {
1436                         // Invalid tag
1437                         // Add as is
1438                         *dest = '<';
1439                         dest++;
1440                         continue;
1441                     }
1442                 }
1443                 }; // end case
1444
1445                 processToken();
1446
1447                 m_cBufferPos = 0;
1448                 state.setTagState(TagName);
1449                 state = parseTag(src, state);
1450             }
1451         } else if (cc == '&' && !src.escaped()) {
1452             src.advance(lineNoPtr);
1453             state = parseEntity(src, dest, state, m_cBufferPos, true, state.hasTagState());
1454         } else if (cc == '<' && !src.escaped()) {
1455             tagStartLineno = lineno;
1456             src.advance(lineNoPtr);
1457             state.setStartTag(true);
1458         } else if (cc == '\n' || cc == '\r') {
1459             if (state.discardLF())
1460                 // Ignore this LF
1461                 state.setDiscardLF(false); // We have discarded 1 LF
1462             else {
1463                 // Process this LF
1464                 *dest++ = '\n';
1465                 if (cc == '\r' && !src.excludeLineNumbers())
1466                     lineno++;
1467             }
1468
1469             /* Check for MS-DOS CRLF sequence */
1470             if (cc == '\r')
1471                 state.setSkipLF(true);
1472             src.advance(lineNoPtr);
1473         } else {
1474             state.setDiscardLF(false);
1475             *dest++ = cc;
1476             src.advance(lineNoPtr);
1477         }
1478     }
1479     
1480 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1481     if (!m_doc->ownerElement())
1482         printf("Ending write at time %d\n", m_doc->elapsedTime());
1483 #endif
1484     
1485     inWrite = wasInWrite;
1486
1487     m_state = state;
1488
1489     if (noMoreData && !inWrite && !state.loadingExtScript() && !m_executingScript && !m_timer.isActive()) {
1490         end(); // this actually causes us to be deleted
1491         return true;
1492     }
1493     return false;
1494 }
1495
1496 void HTMLTokenizer::stopParsing()
1497 {
1498     Tokenizer::stopParsing();
1499     m_timer.stop();
1500
1501     // The part needs to know that the tokenizer has finished with its data,
1502     // regardless of whether it happened naturally or due to manual intervention.
1503     if (!m_fragment && m_doc->frame())
1504         m_doc->frame()->loader()->tokenizerProcessedData();
1505 }
1506
1507 bool HTMLTokenizer::processingData() const
1508 {
1509     return m_timer.isActive() || inWrite;
1510 }
1511
1512 void HTMLTokenizer::timerFired(Timer<HTMLTokenizer>*)
1513 {
1514 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1515     if (!m_doc->ownerElement())
1516         printf("Beginning timer write at time %d\n", m_doc->elapsedTime());
1517 #endif
1518
1519     if (m_doc->view() && m_doc->view()->layoutPending() && !m_doc->minimumLayoutDelay()) {
1520         // Restart the timer and let layout win.  This is basically a way of ensuring that the layout
1521         // timer has higher priority than our timer.
1522         m_timer.startOneShot(0);
1523         return;
1524     }
1525     
1526     RefPtr<Frame> frame = m_fragment ? 0 : m_doc->frame();
1527
1528     // Invoke write() as though more data came in.
1529     bool didCallEnd = write(SegmentedString(), true);
1530   
1531     // If we called end() during the write,  we need to let WebKit know that we're done processing the data.
1532     if (didCallEnd && frame)
1533         frame->loader()->tokenizerProcessedData();
1534 }
1535
1536 void HTMLTokenizer::end()
1537 {
1538     ASSERT(!m_timer.isActive());
1539     m_timer.stop(); // Only helps if assertion above fires, but do it anyway.
1540
1541     if (buffer) {
1542         // parseTag is using the buffer for different matters
1543         if (!m_state.hasTagState())
1544             processToken();
1545
1546         fastFree(scriptCode);
1547         scriptCode = 0;
1548         scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
1549
1550         fastFree(buffer);
1551         buffer = 0;
1552     }
1553
1554     if (!inViewSourceMode())
1555         parser->finished();
1556     else
1557         m_doc->finishedParsing();
1558 }
1559
1560 void HTMLTokenizer::finish()
1561 {
1562     // do this as long as we don't find matching comment ends
1563     while((m_state.inComment() || m_state.inServer()) && scriptCode && scriptCodeSize) {
1564         // we've found an unmatched comment start
1565         if (m_state.inComment())
1566             brokenComments = true;
1567         else
1568             brokenServer = true;
1569         checkScriptBuffer();
1570         scriptCode[scriptCodeSize] = 0;
1571         scriptCode[scriptCodeSize + 1] = 0;
1572         int pos;
1573         String food;
1574         if (m_state.inScript() || m_state.inStyle())
1575             food = String(scriptCode, scriptCodeSize);
1576         else if (m_state.inServer()) {
1577             food = "<";
1578             food.append(String(scriptCode, scriptCodeSize));
1579         } else {
1580             pos = DeprecatedConstString(reinterpret_cast<DeprecatedChar*>(scriptCode), scriptCodeSize).string().find('>');
1581             food = String(scriptCode + pos + 1, scriptCodeSize - pos - 1);
1582         }
1583         fastFree(scriptCode);
1584         scriptCode = 0;
1585         scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
1586         m_state.setInComment(false);
1587         m_state.setInServer(false);
1588         if (!food.isEmpty())
1589             write(food, true);
1590     }
1591     // this indicates we will not receive any more data... but if we are waiting on
1592     // an external script to load, we can't finish parsing until that is done
1593     noMoreData = true;
1594     if (!inWrite && !m_state.loadingExtScript() && !m_executingScript && !m_timer.isActive())
1595         end(); // this actually causes us to be deleted
1596 }
1597
1598 PassRefPtr<Node> HTMLTokenizer::processToken()
1599 {
1600     KJSProxy* jsProxy = (!m_fragment && m_doc->frame()) ? m_doc->frame()->scriptProxy() : 0;
1601     if (jsProxy)
1602         jsProxy->setEventHandlerLineno(tagStartLineno);
1603     if (dest > buffer) {
1604 #ifdef TOKEN_DEBUG
1605         if(currToken.tagName.length()) {
1606             qDebug( "unexpected token: %s, str: *%s*", currToken.tagName.deprecatedString().latin1(),DeprecatedConstString( buffer,dest-buffer ).deprecatedString().latin1() );
1607             ASSERT(0);
1608         }
1609
1610 #endif
1611         currToken.text = StringImpl::createStrippingNull(buffer, dest - buffer);
1612         if (currToken.tagName != commentAtom)
1613             currToken.tagName = textAtom;
1614     } else if (currToken.tagName == nullAtom) {
1615         currToken.reset();
1616         if (jsProxy)
1617             jsProxy->setEventHandlerLineno(lineno);
1618         return 0;
1619     }
1620
1621     dest = buffer;
1622
1623 #ifdef TOKEN_DEBUG
1624     DeprecatedString name = currToken.tagName.deprecatedString();
1625     DeprecatedString text;
1626     if(currToken.text)
1627         text = DeprecatedConstString(currToken.text->unicode(), currToken.text->length()).deprecatedString();
1628
1629     kdDebug( 6036 ) << "Token --> " << name << endl;
1630     if (currToken.flat)
1631         kdDebug( 6036 ) << "Token is FLAT!" << endl;
1632     if(!text.isNull())
1633         kdDebug( 6036 ) << "text: \"" << text << "\"" << endl;
1634     unsigned l = currToken.attrs ? currToken.attrs->length() : 0;
1635     if(l) {
1636         kdDebug( 6036 ) << "Attributes: " << l << endl;
1637         for (unsigned i = 0; i < l; ++i) {
1638             Attribute* c = currToken.attrs->attributeItem(i);
1639             kdDebug( 6036 ) << "    " << c->localName().deprecatedString()
1640                             << "=\"" << c->value().deprecatedString() << "\"" << endl;
1641         }
1642     }
1643     kdDebug( 6036 ) << endl;
1644 #endif
1645
1646     RefPtr<Node> n;
1647     
1648     if (!m_parserStopped) {
1649         if (inViewSourceMode())
1650             static_cast<HTMLViewSourceDocument*>(m_doc)->addViewSourceToken(&currToken);
1651         else
1652             // pass the token over to the parser, the parser DOES NOT delete the token
1653             n = parser->parseToken(&currToken);
1654     }
1655     currToken.reset();
1656     if (jsProxy)
1657         jsProxy->setEventHandlerLineno(0);
1658
1659     return n.release();
1660 }
1661
1662 HTMLTokenizer::~HTMLTokenizer()
1663 {
1664     ASSERT(!inWrite);
1665     reset();
1666     delete parser;
1667 }
1668
1669
1670 void HTMLTokenizer::enlargeBuffer(int len)
1671 {
1672     int newSize = max(size * 2, size + len);
1673     int oldOffset = dest - buffer;
1674     buffer = static_cast<UChar*>(fastRealloc(buffer, newSize * sizeof(UChar)));
1675     dest = buffer + oldOffset;
1676     size = newSize;
1677 }
1678
1679 void HTMLTokenizer::enlargeScriptBuffer(int len)
1680 {
1681     int newSize = max(scriptCodeMaxSize * 2, scriptCodeMaxSize + len);
1682     scriptCode = static_cast<UChar*>(fastRealloc(scriptCode, newSize * sizeof(UChar)));
1683     scriptCodeMaxSize = newSize;
1684 }
1685     
1686 void HTMLTokenizer::executeScriptsWaitingForStylesheets()
1687 {
1688     ASSERT(m_doc->haveStylesheetsLoaded());
1689
1690     if (m_hasScriptsWaitingForStylesheets)
1691         notifyFinished(0);
1692 }
1693
1694 void HTMLTokenizer::notifyFinished(CachedResource*)
1695 {
1696 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1697     if (!m_doc->ownerElement())
1698         printf("script loaded at %d\n", m_doc->elapsedTime());
1699 #endif
1700
1701     ASSERT(!pendingScripts.isEmpty());
1702
1703     // Make scripts loaded from file URLs wait for stylesheets to match Tiger behavior where
1704     // file loads were serialized in lower level.
1705     // FIXME: this should really be done for all script loads or the same effect should be achieved by other
1706     // means, like javascript suspend/resume
1707     m_hasScriptsWaitingForStylesheets = !m_doc->haveStylesheetsLoaded() && pendingScripts.head()->url().startsWith("file:", false);
1708     if (m_hasScriptsWaitingForStylesheets)
1709         return;
1710
1711     bool finished = false;
1712     while (!finished && pendingScripts.head()->isLoaded()) {
1713 #ifdef TOKEN_DEBUG
1714         kdDebug( 6036 ) << "Finished loading an external script" << endl;
1715 #endif
1716         CachedScript* cs = pendingScripts.dequeue();
1717         ASSERT(cache()->disabled() || cs->accessCount() > 0);
1718
1719         String scriptSource = cs->script();
1720 #ifdef TOKEN_DEBUG
1721         kdDebug( 6036 ) << "External script is:" << endl << scriptSource.deprecatedString() << endl;
1722 #endif
1723         setSrc(SegmentedString());
1724
1725         // make sure we forget about the script before we execute the new one
1726         // infinite recursion might happen otherwise
1727         DeprecatedString cachedScriptUrl( cs->url().deprecatedString() );
1728         bool errorOccurred = cs->errorOccurred();
1729         cs->deref(this);
1730         RefPtr<Node> n = scriptNode.release();
1731
1732 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1733         if (!m_doc->ownerElement())
1734             printf("external script beginning execution at %d\n", m_doc->elapsedTime());
1735 #endif
1736
1737         if (errorOccurred)
1738             EventTargetNodeCast(n.get())->dispatchHTMLEvent(errorEvent, true, false);
1739         else {
1740             if (static_cast<HTMLScriptElement*>(n.get())->shouldExecuteAsJavaScript())
1741                 m_state = scriptExecution(scriptSource.deprecatedString(), m_state, cachedScriptUrl);
1742             EventTargetNodeCast(n.get())->dispatchHTMLEvent(loadEvent, false, false);
1743         }
1744
1745         // The state of pendingScripts.isEmpty() can change inside the scriptExecution()
1746         // call above, so test afterwards.
1747         finished = pendingScripts.isEmpty();
1748         if (finished) {
1749             m_state.setLoadingExtScript(false);
1750 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1751             if (!m_doc->ownerElement())
1752                 printf("external script finished execution at %d\n", m_doc->elapsedTime());
1753 #endif
1754         }
1755
1756         // 'm_requestingScript' is true when we are called synchronously from
1757         // scriptHandler(). In that case scriptHandler() will take care
1758         // of pendingSrc.
1759         if (!m_requestingScript) {
1760             SegmentedString rest = pendingSrc;
1761             pendingSrc.clear();
1762             write(rest, false);
1763             // we might be deleted at this point, do not
1764             // access any members.
1765         }
1766     }
1767 }
1768
1769 bool HTMLTokenizer::isWaitingForScripts() const
1770 {
1771     return m_state.loadingExtScript();
1772 }
1773
1774 void HTMLTokenizer::setSrc(const SegmentedString &source)
1775 {
1776     src = source;
1777 }
1778
1779 void parseHTMLDocumentFragment(const String& source, DocumentFragment* fragment)
1780 {
1781     HTMLTokenizer tok(fragment);
1782     tok.setForceSynchronous(true);
1783     tok.write(source, true);
1784     tok.finish();
1785     ASSERT(!tok.processingData());      // make sure we're done (see 3963151)
1786 }
1787
1788 UChar decodeNamedEntity(const char* name)
1789 {
1790     const Entity* e = findEntity(name, strlen(name));
1791     return e ? e->code : 0;
1792 }
1793
1794 }