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