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