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 "core/html/parser/CSSPreloadScanner.h"
30
31 #include "core/FetchInitiatorTypeNames.h"
32 #include "core/html/parser/HTMLParserIdioms.h"
33 #include "platform/text/SegmentedString.h"
34
35 namespace blink {
36
CSSPreloadScanner()37 CSSPreloadScanner::CSSPreloadScanner()
38 : m_state(Initial)
39 , m_requests(0)
40 {
41 }
42
~CSSPreloadScanner()43 CSSPreloadScanner::~CSSPreloadScanner()
44 {
45 }
46
reset()47 void CSSPreloadScanner::reset()
48 {
49 m_state = Initial;
50 m_rule.clear();
51 m_ruleValue.clear();
52 }
53
54 template<typename Char>
scanCommon(const Char * begin,const Char * end,const SegmentedString & source,PreloadRequestStream & requests)55 void CSSPreloadScanner::scanCommon(const Char* begin, const Char* end, const SegmentedString& source, PreloadRequestStream& requests)
56 {
57 m_requests = &requests;
58 for (const Char* it = begin; it != end && m_state != DoneParsingImportRules; ++it)
59 tokenize(*it, source);
60 m_requests = 0;
61 }
62
scan(const HTMLToken::DataVector & data,const SegmentedString & source,PreloadRequestStream & requests)63 void CSSPreloadScanner::scan(const HTMLToken::DataVector& data, const SegmentedString& source, PreloadRequestStream& requests)
64 {
65 scanCommon(data.data(), data.data() + data.size(), source, requests);
66 }
67
scan(const String & tagName,const SegmentedString & source,PreloadRequestStream & requests)68 void CSSPreloadScanner::scan(const String& tagName, const SegmentedString& source, PreloadRequestStream& requests)
69 {
70 if (tagName.is8Bit()) {
71 const LChar* begin = tagName.characters8();
72 scanCommon(begin, begin + tagName.length(), source, requests);
73 return;
74 }
75 const UChar* begin = tagName.characters16();
76 scanCommon(begin, begin + tagName.length(), source, requests);
77 }
78
tokenize(UChar c,const SegmentedString & source)79 inline void CSSPreloadScanner::tokenize(UChar c, const SegmentedString& source)
80 {
81 // We are just interested in @import rules, no need for real tokenization here
82 // Searching for other types of resources is probably low payoff.
83 switch (m_state) {
84 case Initial:
85 if (isHTMLSpace<UChar>(c))
86 break;
87 if (c == '@')
88 m_state = RuleStart;
89 else if (c == '/')
90 m_state = MaybeComment;
91 else
92 m_state = DoneParsingImportRules;
93 break;
94 case MaybeComment:
95 if (c == '*')
96 m_state = Comment;
97 else
98 m_state = Initial;
99 break;
100 case Comment:
101 if (c == '*')
102 m_state = MaybeCommentEnd;
103 break;
104 case MaybeCommentEnd:
105 if (c == '*')
106 break;
107 if (c == '/')
108 m_state = Initial;
109 else
110 m_state = Comment;
111 break;
112 case RuleStart:
113 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
114 m_rule.clear();
115 m_ruleValue.clear();
116 m_rule.append(c);
117 m_state = Rule;
118 } else
119 m_state = Initial;
120 break;
121 case Rule:
122 if (isHTMLSpace<UChar>(c))
123 m_state = AfterRule;
124 else if (c == ';')
125 m_state = Initial;
126 else
127 m_rule.append(c);
128 break;
129 case AfterRule:
130 if (isHTMLSpace<UChar>(c))
131 break;
132 if (c == ';')
133 m_state = Initial;
134 else if (c == '{')
135 m_state = DoneParsingImportRules;
136 else {
137 m_state = RuleValue;
138 m_ruleValue.append(c);
139 }
140 break;
141 case RuleValue:
142 if (isHTMLSpace<UChar>(c))
143 m_state = AfterRuleValue;
144 else if (c == ';')
145 emitRule(source);
146 else
147 m_ruleValue.append(c);
148 break;
149 case AfterRuleValue:
150 if (isHTMLSpace<UChar>(c))
151 break;
152 if (c == ';')
153 emitRule(source);
154 else if (c == '{')
155 m_state = DoneParsingImportRules;
156 else {
157 // FIXME: media rules
158 m_state = Initial;
159 }
160 break;
161 case DoneParsingImportRules:
162 ASSERT_NOT_REACHED();
163 break;
164 }
165 }
166
parseCSSStringOrURL(const String & string)167 static String parseCSSStringOrURL(const String& string)
168 {
169 size_t offset = 0;
170 size_t reducedLength = string.length();
171
172 while (reducedLength && isHTMLSpace<UChar>(string[offset])) {
173 ++offset;
174 --reducedLength;
175 }
176 while (reducedLength && isHTMLSpace<UChar>(string[offset + reducedLength - 1]))
177 --reducedLength;
178
179 if (reducedLength >= 5
180 && (string[offset] == 'u' || string[offset] == 'U')
181 && (string[offset + 1] == 'r' || string[offset + 1] == 'R')
182 && (string[offset + 2] == 'l' || string[offset + 2] == 'L')
183 && string[offset + 3] == '('
184 && string[offset + reducedLength - 1] == ')') {
185 offset += 4;
186 reducedLength -= 5;
187 }
188
189 while (reducedLength && isHTMLSpace<UChar>(string[offset])) {
190 ++offset;
191 --reducedLength;
192 }
193 while (reducedLength && isHTMLSpace<UChar>(string[offset + reducedLength - 1]))
194 --reducedLength;
195
196 if (reducedLength < 2 || string[offset] != string[offset + reducedLength - 1] || !(string[offset] == '\'' || string[offset] == '"'))
197 return String();
198 offset++;
199 reducedLength -= 2;
200
201 while (reducedLength && isHTMLSpace<UChar>(string[offset])) {
202 ++offset;
203 --reducedLength;
204 }
205 while (reducedLength && isHTMLSpace<UChar>(string[offset + reducedLength - 1]))
206 --reducedLength;
207
208 return string.substring(offset, reducedLength);
209 }
210
emitRule(const SegmentedString & source)211 void CSSPreloadScanner::emitRule(const SegmentedString& source)
212 {
213 if (equalIgnoringCase(m_rule, "import")) {
214 String url = parseCSSStringOrURL(m_ruleValue.toString());
215 if (!url.isEmpty()) {
216 KURL baseElementURL; // FIXME: This should be passed in from the HTMLPreloadScaner via scan()!
217 TextPosition position = TextPosition(source.currentLine(), source.currentColumn());
218 OwnPtr<PreloadRequest> request = PreloadRequest::create(FetchInitiatorTypeNames::css, position, url, baseElementURL, Resource::CSSStyleSheet);
219 // FIXME: Should this be including the charset in the preload request?
220 m_requests->append(request.release());
221 }
222 m_state = Initial;
223 } else if (equalIgnoringCase(m_rule, "charset"))
224 m_state = Initial;
225 else
226 m_state = DoneParsingImportRules;
227 m_rule.clear();
228 m_ruleValue.clear();
229 }
230
231 }
232