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