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
37 namespace WebCore {
38
CSSPreloadScanner(Document * document)39 CSSPreloadScanner::CSSPreloadScanner(Document* document)
40 : m_state(Initial)
41 , m_document(document)
42 {
43 }
44
reset()45 void CSSPreloadScanner::reset()
46 {
47 m_state = Initial;
48 m_rule.clear();
49 m_ruleValue.clear();
50 }
51
scan(const HTMLToken & token,bool scanningBody)52 void 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
tokenize(UChar c)61 inline 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
parseCSSStringOrURL(const UChar * characters,size_t length)149 static 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
emitRule()193 void 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