1/*
2 * Copyright (C) 2008, 2010 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2009 Torch Mobile, Inc. http://www.torchmobile.com/
4 * Copyright (C) 2010 Google Inc. All Rights Reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "CSSPreloadScanner.h"
30
31#include "CachedCSSStyleSheet.h"
32#include "CachedResourceLoader.h"
33#include "Document.h"
34#include "HTMLParserIdioms.h"
35#include "HTMLToken.h"
36
37namespace WebCore {
38
39CSSPreloadScanner::CSSPreloadScanner(Document* document)
40    : m_state(Initial)
41    , m_document(document)
42{
43}
44
45void CSSPreloadScanner::reset()
46{
47    m_state = Initial;
48    m_rule.clear();
49    m_ruleValue.clear();
50}
51
52void CSSPreloadScanner::scan(const HTMLToken& token, bool scanningBody)
53{
54    m_scanningBody = scanningBody;
55
56    const HTMLToken::DataVector& characters = token.characters();
57    for (HTMLToken::DataVector::const_iterator iter = characters.begin(); iter != characters.end() && m_state != DoneParsingImportRules; ++iter)
58        tokenize(*iter);
59}
60
61inline void CSSPreloadScanner::tokenize(UChar c)
62{
63    // We are just interested in @import rules, no need for real tokenization here
64    // Searching for other types of resources is probably low payoff.
65    switch (m_state) {
66    case Initial:
67        if (isHTMLSpace(c))
68            break;
69        if (c == '@')
70            m_state = RuleStart;
71        else if (c == '/')
72            m_state = MaybeComment;
73        else
74            m_state = DoneParsingImportRules;
75        break;
76    case MaybeComment:
77        if (c == '*')
78            m_state = Comment;
79        else
80            m_state = Initial;
81        break;
82    case Comment:
83        if (c == '*')
84            m_state = MaybeCommentEnd;
85        break;
86    case MaybeCommentEnd:
87        if (c == '*')
88            break;
89        if (c == '/')
90            m_state = Initial;
91        else
92            m_state = Comment;
93        break;
94    case RuleStart:
95        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
96            m_rule.clear();
97            m_ruleValue.clear();
98            m_rule.append(c);
99            m_state = Rule;
100        } else
101            m_state = Initial;
102        break;
103    case Rule:
104        if (isHTMLSpace(c))
105            m_state = AfterRule;
106        else if (c == ';')
107            m_state = Initial;
108        else
109            m_rule.append(c);
110        break;
111    case AfterRule:
112        if (isHTMLSpace(c))
113            break;
114        if (c == ';')
115            m_state = Initial;
116        else if (c == '{')
117            m_state = DoneParsingImportRules;
118        else {
119            m_state = RuleValue;
120            m_ruleValue.append(c);
121        }
122        break;
123    case RuleValue:
124        if (isHTMLSpace(c))
125            m_state = AfterRuleValue;
126        else if (c == ';')
127            emitRule();
128        else
129            m_ruleValue.append(c);
130        break;
131    case AfterRuleValue:
132        if (isHTMLSpace(c))
133            break;
134        if (c == ';')
135            emitRule();
136        else if (c == '{')
137            m_state = DoneParsingImportRules;
138        else {
139            // FIXME: media rules
140            m_state = Initial;
141        }
142        break;
143    case DoneParsingImportRules:
144        ASSERT_NOT_REACHED();
145        break;
146    }
147}
148
149static String parseCSSStringOrURL(const UChar* characters, size_t length)
150{
151    size_t offset = 0;
152    size_t reducedLength = length;
153
154    while (reducedLength && isHTMLSpace(characters[offset])) {
155        ++offset;
156        --reducedLength;
157    }
158    while (reducedLength && isHTMLSpace(characters[offset + reducedLength - 1]))
159        --reducedLength;
160
161    if (reducedLength >= 5
162            && (characters[offset] == 'u' || characters[offset] == 'U')
163            && (characters[offset + 1] == 'r' || characters[offset + 1] == 'R')
164            && (characters[offset + 2] == 'l' || characters[offset + 2] == 'L')
165            && characters[offset + 3] == '('
166            && characters[offset + reducedLength - 1] == ')') {
167        offset += 4;
168        reducedLength -= 5;
169    }
170
171    while (reducedLength && isHTMLSpace(characters[offset])) {
172        ++offset;
173        --reducedLength;
174    }
175    while (reducedLength && isHTMLSpace(characters[offset + reducedLength - 1]))
176        --reducedLength;
177
178    if (reducedLength < 2 || characters[offset] != characters[offset + reducedLength - 1] || !(characters[offset] == '\'' || characters[offset] == '"'))
179        return String();
180    offset++;
181    reducedLength -= 2;
182
183    while (reducedLength && isHTMLSpace(characters[offset])) {
184        ++offset;
185        --reducedLength;
186    }
187    while (reducedLength && isHTMLSpace(characters[offset + reducedLength - 1]))
188        --reducedLength;
189
190    return String(characters + offset, reducedLength);
191}
192
193void CSSPreloadScanner::emitRule()
194{
195    if (equalIgnoringCase("import", m_rule.data(), m_rule.size())) {
196        String value = parseCSSStringOrURL(m_ruleValue.data(), m_ruleValue.size());
197        if (!value.isEmpty())
198            m_document->cachedResourceLoader()->preload(CachedResource::CSSStyleSheet, value, String(), m_scanningBody);
199        m_state = Initial;
200    } else if (equalIgnoringCase("charset", m_rule.data(), m_rule.size()))
201        m_state = Initial;
202    else
203        m_state = DoneParsingImportRules;
204    m_rule.clear();
205    m_ruleValue.clear();
206}
207
208}
209