Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2)

Side by Side Diff: third_party/WebKit/Source/core/html/HTMLMetaElement-in.cpp

Issue 2312303002: Improve warnings for non-standard meta tag separators (Closed)
Patch Set: Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org) 4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2003, 2010 Apple Inc. All rights reserved. 5 * Copyright (C) 2003, 2010 Apple Inc. All rights reserved.
6 * 6 *
7 * This library is free software; you can redistribute it and/or 7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public 8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either 9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version. 10 * version 2 of the License, or (at your option) any later version.
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
66 } 66 }
67 67
68 // Though isspace() considers \t and \v to be whitespace, Win IE doesn't. 68 // Though isspace() considers \t and \v to be whitespace, Win IE doesn't.
69 static bool isSeparator(UChar c) 69 static bool isSeparator(UChar c)
70 { 70 {
71 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ' ,' || c == '\0'; 71 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ' ,' || c == '\0';
72 } 72 }
73 73
74 void HTMLMetaElement::parseContentAttribute(const String& content, void* data, D ocument* document, bool viewportMetaZeroValuesQuirk) 74 void HTMLMetaElement::parseContentAttribute(const String& content, void* data, D ocument* document, bool viewportMetaZeroValuesQuirk)
75 { 75 {
76 bool error = false; 76 bool hasInvalidSeparator = false;
77 77
78 // Tread lightly in this code -- it was specifically designed to mimic Win I E's parsing behavior. 78 // Tread lightly in this code -- it was specifically designed to mimic Win I E's parsing behavior.
79 unsigned keyBegin, keyEnd; 79 unsigned keyBegin, keyEnd;
80 unsigned valueBegin, valueEnd; 80 unsigned valueBegin, valueEnd;
81 81
82 String buffer = content.lower(); 82 String buffer = content.lower();
83 unsigned length = buffer.length(); 83 unsigned length = buffer.length();
84 for (unsigned i = 0; i < length; /* no increment here */) { 84 for (unsigned i = 0; i < length; /* no increment here */) {
85 // skip to first non-separator, but don't skip past the end of the strin g 85 // skip to first non-separator, but don't skip past the end of the strin g
86 while (isSeparator(buffer[i])) { 86 while (isSeparator(buffer[i])) {
87 if (i >= length) 87 if (i >= length)
88 break; 88 break;
89 i++; 89 i++;
90 } 90 }
91 keyBegin = i; 91 keyBegin = i;
92 92
93 // skip to first separator 93 // skip to first separator
94 while (!isSeparator(buffer[i])) { 94 while (!isSeparator(buffer[i])) {
95 error |= isInvalidSeparator(buffer[i]); 95 hasInvalidSeparator |= isInvalidSeparator(buffer[i]);
96 if (i >= length) 96 if (i >= length)
97 break; 97 break;
98 i++; 98 i++;
99 } 99 }
100 keyEnd = i; 100 keyEnd = i;
101 101
102 // skip to first '=', but don't skip past a ',' or the end of the string 102 // skip to first '=', but don't skip past a ',' or the end of the string
103 while (buffer[i] != '=') { 103 while (buffer[i] != '=') {
104 error |= isInvalidSeparator(buffer[i]); 104 hasInvalidSeparator |= isInvalidSeparator(buffer[i]);
105 if (buffer[i] == ',' || i >= length) 105 if (buffer[i] == ',' || i >= length)
106 break; 106 break;
107 i++; 107 i++;
108 } 108 }
109 109
110 // skip to first non-separator, but don't skip past a ',' or the end of the string 110 // skip to first non-separator, but don't skip past a ',' or the end of the string
111 while (isSeparator(buffer[i])) { 111 while (isSeparator(buffer[i])) {
112 if (buffer[i] == ',' || i >= length) 112 if (buffer[i] == ',' || i >= length)
113 break; 113 break;
114 i++; 114 i++;
115 } 115 }
116 valueBegin = i; 116 valueBegin = i;
117 117
118 // skip to first separator 118 // skip to first separator
119 while (!isSeparator(buffer[i])) { 119 while (!isSeparator(buffer[i])) {
120 error |= isInvalidSeparator(buffer[i]); 120 hasInvalidSeparator |= isInvalidSeparator(buffer[i]);
121 if (i >= length) 121 if (i >= length)
122 break; 122 break;
123 i++; 123 i++;
124 } 124 }
125 valueEnd = i; 125 valueEnd = i;
126 126
127 SECURITY_DCHECK(i <= length); 127 SECURITY_DCHECK(i <= length);
128 128
129 String keyString = buffer.substring(keyBegin, keyEnd - keyBegin); 129 String keyString = buffer.substring(keyBegin, keyEnd - keyBegin);
130 String valueString = buffer.substring(valueBegin, valueEnd - valueBegin) ; 130 String valueString = buffer.substring(valueBegin, valueEnd - valueBegin) ;
131 processViewportKeyValuePair(document, keyString, valueString, viewportMe taZeroValuesQuirk, data); 131 processViewportKeyValuePair(document, !hasInvalidSeparator, keyString, v alueString, viewportMetaZeroValuesQuirk, data);
132 } 132 }
133 if (error && document) { 133 if (hasInvalidSeparator && document) {
134 String message = "Error parsing a meta element's content: ';' is not a v alid key-value pair separator. Please use ',' instead."; 134 String message = "Error parsing a meta element's content: ';' is not a v alid key-value pair separator. Please use ',' instead.";
135 document->addConsoleMessage(ConsoleMessage::create(RenderingMessageSourc e, WarningMessageLevel, message)); 135 document->addConsoleMessage(ConsoleMessage::create(RenderingMessageSourc e, WarningMessageLevel, message));
136 } 136 }
137 } 137 }
138 138
139 static inline float clampLengthValue(float value) 139 static inline float clampLengthValue(float value)
140 { 140 {
141 // Limits as defined in the css-device-adapt spec. 141 // Limits as defined in the css-device-adapt spec.
142 if (value != ViewportDescription::ValueAuto) 142 if (value != ViewportDescription::ValueAuto)
143 return std::min(float(10000), std::max(value, float(1))); 143 return std::min(float(10000), std::max(value, float(1)));
144 return value; 144 return value;
145 } 145 }
146 146
147 static inline float clampScaleValue(float value) 147 static inline float clampScaleValue(float value)
148 { 148 {
149 // Limits as defined in the css-device-adapt spec. 149 // Limits as defined in the css-device-adapt spec.
150 if (value != ViewportDescription::ValueAuto) 150 if (value != ViewportDescription::ValueAuto)
151 return std::min(float(10), std::max(value, float(0.1))); 151 return std::min(float(10), std::max(value, float(0.1)));
152 return value; 152 return value;
153 } 153 }
154 154
155 float HTMLMetaElement::parsePositiveNumber(Document* document, const String& key String, const String& valueString, bool* ok) 155 float HTMLMetaElement::parsePositiveNumber(Document* document, bool reportWarnin gs, const String& keyString, const String& valueString, bool* ok)
156 { 156 {
157 size_t parsedLength; 157 size_t parsedLength;
158 float value; 158 float value;
159 if (valueString.is8Bit()) 159 if (valueString.is8Bit())
160 value = charactersToFloat(valueString.characters8(), valueString.length( ), parsedLength); 160 value = charactersToFloat(valueString.characters8(), valueString.length( ), parsedLength);
161 else 161 else
162 value = charactersToFloat(valueString.characters16(), valueString.length (), parsedLength); 162 value = charactersToFloat(valueString.characters16(), valueString.length (), parsedLength);
163 if (!parsedLength) { 163 if (!parsedLength) {
164 reportViewportWarning(document, UnrecognizedViewportArgumentValueError, valueString, keyString); 164 if (reportWarnings)
165 reportViewportWarning(document, UnrecognizedViewportArgumentValueErr or, valueString, keyString);
165 if (ok) 166 if (ok)
166 *ok = false; 167 *ok = false;
167 return 0; 168 return 0;
168 } 169 }
169 if (parsedLength < valueString.length()) 170 if (parsedLength < valueString.length() && reportWarnings)
170 reportViewportWarning(document, TruncatedViewportArgumentValueError, val ueString, keyString); 171 reportViewportWarning(document, TruncatedViewportArgumentValueError, val ueString, keyString);
171 if (ok) 172 if (ok)
172 *ok = true; 173 *ok = true;
173 return value; 174 return value;
174 } 175 }
175 176
176 Length HTMLMetaElement::parseViewportValueAsLength(Document* document, const Str ing& keyString, const String& valueString) 177 Length HTMLMetaElement::parseViewportValueAsLength(Document* document, bool repo rtWarnings, const String& keyString, const String& valueString)
177 { 178 {
178 // 1) Non-negative number values are translated to px lengths. 179 // 1) Non-negative number values are translated to px lengths.
179 // 2) Negative number values are translated to auto. 180 // 2) Negative number values are translated to auto.
180 // 3) device-width and device-height are used as keywords. 181 // 3) device-width and device-height are used as keywords.
181 // 4) Other keywords and unknown values translate to 0.0. 182 // 4) Other keywords and unknown values translate to 0.0.
182 183
183 unsigned length = valueString.length(); 184 unsigned length = valueString.length();
184 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13); 185 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
185 SWITCH(characters, length) { 186 SWITCH(characters, length) {
186 CASE("device-width") { 187 CASE("device-width") {
187 return Length(DeviceWidth); 188 return Length(DeviceWidth);
188 } 189 }
189 CASE("device-height") { 190 CASE("device-height") {
190 return Length(DeviceHeight); 191 return Length(DeviceHeight);
191 } 192 }
192 } 193 }
193 194
194 float value = parsePositiveNumber(document, keyString, valueString); 195 float value = parsePositiveNumber(document, reportWarnings, keyString, value String);
195 196
196 if (value < 0) 197 if (value < 0)
197 return Length(); // auto 198 return Length(); // auto
198 199
199 return Length(clampLengthValue(value), Fixed); 200 return Length(clampLengthValue(value), Fixed);
200 } 201 }
201 202
202 float HTMLMetaElement::parseViewportValueAsZoom(Document* document, const String & keyString, const String& valueString, bool& computedValueMatchesParsedValue, b ool viewportMetaZeroValuesQuirk) 203 float HTMLMetaElement::parseViewportValueAsZoom(Document* document, bool reportW arnings, const String& keyString, const String& valueString, bool& computedValue MatchesParsedValue, bool viewportMetaZeroValuesQuirk)
203 { 204 {
204 // 1) Non-negative number values are translated to <number> values. 205 // 1) Non-negative number values are translated to <number> values.
205 // 2) Negative number values are translated to auto. 206 // 2) Negative number values are translated to auto.
206 // 3) yes is translated to 1.0. 207 // 3) yes is translated to 1.0.
207 // 4) device-width and device-height are translated to 10.0. 208 // 4) device-width and device-height are translated to 10.0.
208 // 5) no and unknown values are translated to 0.0 209 // 5) no and unknown values are translated to 0.0
209 210
210 computedValueMatchesParsedValue = false; 211 computedValueMatchesParsedValue = false;
211 unsigned length = valueString.length(); 212 unsigned length = valueString.length();
212 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13); 213 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
213 SWITCH(characters, length) { 214 SWITCH(characters, length) {
214 CASE("yes") { 215 CASE("yes") {
215 return 1; 216 return 1;
216 } 217 }
217 CASE("no") { 218 CASE("no") {
218 return 0; 219 return 0;
219 } 220 }
220 CASE("device-width") { 221 CASE("device-width") {
221 return 10; 222 return 10;
222 } 223 }
223 CASE("device-height") { 224 CASE("device-height") {
224 return 10; 225 return 10;
225 } 226 }
226 } 227 }
227 228
228 float value = parsePositiveNumber(document, keyString, valueString); 229 float value = parsePositiveNumber(document, reportWarnings, keyString, value String);
229 230
230 if (value < 0) 231 if (value < 0)
231 return ViewportDescription::ValueAuto; 232 return ViewportDescription::ValueAuto;
232 233
233 if (value > 10.0) 234 if (value > 10.0 && reportWarnings)
234 reportViewportWarning(document, MaximumScaleTooLargeError, String(), Str ing()); 235 reportViewportWarning(document, MaximumScaleTooLargeError, String(), Str ing());
235 236
236 if (!value && viewportMetaZeroValuesQuirk) 237 if (!value && viewportMetaZeroValuesQuirk)
237 return ViewportDescription::ValueAuto; 238 return ViewportDescription::ValueAuto;
238 239
239 float clampedValue = clampScaleValue(value); 240 float clampedValue = clampScaleValue(value);
240 if (clampedValue == value) 241 if (clampedValue == value)
241 computedValueMatchesParsedValue = true; 242 computedValueMatchesParsedValue = true;
242 243
243 return clampedValue; 244 return clampedValue;
244 } 245 }
245 246
246 bool HTMLMetaElement::parseViewportValueAsUserZoom(Document* document, const Str ing& keyString, const String& valueString, bool& computedValueMatchesParsedValue ) 247 bool HTMLMetaElement::parseViewportValueAsUserZoom(Document* document, bool repo rtWarnings, const String& keyString, const String& valueString, bool& computedVa lueMatchesParsedValue)
247 { 248 {
248 // yes and no are used as keywords. 249 // yes and no are used as keywords.
249 // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes. 250 // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
250 // Numbers in the range <-1, 1>, and unknown values, are mapped to no. 251 // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
251 252
252 computedValueMatchesParsedValue = false; 253 computedValueMatchesParsedValue = false;
253 unsigned length = valueString.length(); 254 unsigned length = valueString.length();
254 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13); 255 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
255 SWITCH(characters, length) { 256 SWITCH(characters, length) {
256 CASE("yes") { 257 CASE("yes") {
257 computedValueMatchesParsedValue = true; 258 computedValueMatchesParsedValue = true;
258 return true; 259 return true;
259 } 260 }
260 CASE("no") { 261 CASE("no") {
261 computedValueMatchesParsedValue = true; 262 computedValueMatchesParsedValue = true;
262 return false; 263 return false;
263 } 264 }
264 CASE("device-width") { 265 CASE("device-width") {
265 return true; 266 return true;
266 } 267 }
267 CASE("device-height") { 268 CASE("device-height") {
268 return true; 269 return true;
269 } 270 }
270 } 271 }
271 272
272 float value = parsePositiveNumber(document, keyString, valueString); 273 float value = parsePositiveNumber(document, reportWarnings, keyString, value String);
273 if (fabs(value) < 1) 274 if (fabs(value) < 1)
274 return false; 275 return false;
275 276
276 return true; 277 return true;
277 } 278 }
278 279
279 float HTMLMetaElement::parseViewportValueAsDPI(Document* document, const String& keyString, const String& valueString) 280 float HTMLMetaElement::parseViewportValueAsDPI(Document* document, bool reportWa rnings, const String& keyString, const String& valueString)
280 { 281 {
281 unsigned length = valueString.length(); 282 unsigned length = valueString.length();
282 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 10); 283 DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 10);
283 SWITCH(characters, length) { 284 SWITCH(characters, length) {
284 CASE("device-dpi") { 285 CASE("device-dpi") {
285 return ViewportDescription::ValueDeviceDPI; 286 return ViewportDescription::ValueDeviceDPI;
286 } 287 }
287 CASE("low-dpi") { 288 CASE("low-dpi") {
288 return ViewportDescription::ValueLowDPI; 289 return ViewportDescription::ValueLowDPI;
289 } 290 }
290 CASE("medium-dpi") { 291 CASE("medium-dpi") {
291 return ViewportDescription::ValueMediumDPI; 292 return ViewportDescription::ValueMediumDPI;
292 } 293 }
293 CASE("high-dpi") { 294 CASE("high-dpi") {
294 return ViewportDescription::ValueHighDPI; 295 return ViewportDescription::ValueHighDPI;
295 } 296 }
296 } 297 }
297 298
298 bool ok; 299 bool ok;
299 float value = parsePositiveNumber(document, keyString, valueString, &ok); 300 float value = parsePositiveNumber(document, reportWarnings, keyString, value String, &ok);
300 if (!ok || value < 70 || value > 400) 301 if (!ok || value < 70 || value > 400)
301 return ViewportDescription::ValueAuto; 302 return ViewportDescription::ValueAuto;
302 303
303 return value; 304 return value;
304 } 305 }
305 306
306 void HTMLMetaElement::processViewportKeyValuePair(Document* document, const Stri ng& keyString, const String& valueString, bool viewportMetaZeroValuesQuirk, void * data) 307 void HTMLMetaElement::processViewportKeyValuePair(Document* document, bool repor tWarnings, const String& keyString, const String& valueString, bool viewportMeta ZeroValuesQuirk, void* data)
307 { 308 {
308 ViewportDescription* description = static_cast<ViewportDescription*>(data); 309 ViewportDescription* description = static_cast<ViewportDescription*>(data);
309 310
310 unsigned length = keyString.length(); 311 unsigned length = keyString.length();
311 312
312 DEFINE_ARRAY_FOR_MATCHING(characters, keyString, 17); 313 DEFINE_ARRAY_FOR_MATCHING(characters, keyString, 17);
313 SWITCH(characters, length) { 314 SWITCH(characters, length) {
314 CASE("width") { 315 CASE("width") {
315 const Length& width = parseViewportValueAsLength(document, keyString , valueString); 316 const Length& width = parseViewportValueAsLength(document, reportWar nings, keyString, valueString);
316 if (width.isAuto()) 317 if (width.isAuto())
317 return; 318 return;
318 description->minWidth = Length(ExtendToZoom); 319 description->minWidth = Length(ExtendToZoom);
319 description->maxWidth = width; 320 description->maxWidth = width;
320 return; 321 return;
321 } 322 }
322 CASE("height") { 323 CASE("height") {
323 const Length& height = parseViewportValueAsLength(document, keyStrin g, valueString); 324 const Length& height = parseViewportValueAsLength(document, reportWa rnings, keyString, valueString);
324 if (height.isAuto()) 325 if (height.isAuto())
325 return; 326 return;
326 description->minHeight = Length(ExtendToZoom); 327 description->minHeight = Length(ExtendToZoom);
327 description->maxHeight = height; 328 description->maxHeight = height;
328 return; 329 return;
329 } 330 }
330 CASE("initial-scale") { 331 CASE("initial-scale") {
331 description->zoom = parseViewportValueAsZoom(document, keyString, va lueString, description->zoomIsExplicit, viewportMetaZeroValuesQuirk); 332 description->zoom = parseViewportValueAsZoom(document, reportWarning s, keyString, valueString, description->zoomIsExplicit, viewportMetaZeroValuesQu irk);
332 return; 333 return;
333 } 334 }
334 CASE("minimum-scale") { 335 CASE("minimum-scale") {
335 description->minZoom = parseViewportValueAsZoom(document, keyString, valueString, description->minZoomIsExplicit, viewportMetaZeroValuesQuirk); 336 description->minZoom = parseViewportValueAsZoom(document, reportWarn ings, keyString, valueString, description->minZoomIsExplicit, viewportMetaZeroVa luesQuirk);
336 return; 337 return;
337 } 338 }
338 CASE("maximum-scale") { 339 CASE("maximum-scale") {
339 description->maxZoom = parseViewportValueAsZoom(document, keyString, valueString, description->maxZoomIsExplicit, viewportMetaZeroValuesQuirk); 340 description->maxZoom = parseViewportValueAsZoom(document, reportWarn ings, keyString, valueString, description->maxZoomIsExplicit, viewportMetaZeroVa luesQuirk);
340 return; 341 return;
341 } 342 }
342 CASE("user-scalable") { 343 CASE("user-scalable") {
343 description->userZoom = parseViewportValueAsUserZoom(document, keySt ring, valueString, description->userZoomIsExplicit); 344 description->userZoom = parseViewportValueAsUserZoom(document, repor tWarnings, keyString, valueString, description->userZoomIsExplicit);
344 return; 345 return;
345 } 346 }
346 CASE("target-densitydpi") { 347 CASE("target-densitydpi") {
347 description->deprecatedTargetDensityDPI = parseViewportValueAsDPI(do cument, keyString, valueString); 348 description->deprecatedTargetDensityDPI = parseViewportValueAsDPI(do cument, reportWarnings, keyString, valueString);
348 reportViewportWarning(document, TargetDensityDpiUnsupported, String( ), String()); 349 if (reportWarnings)
350 reportViewportWarning(document, TargetDensityDpiUnsupported, Str ing(), String());
349 return; 351 return;
350 } 352 }
351 CASE("minimal-ui") { 353 CASE("minimal-ui") {
352 // Ignore vendor-specific argument. 354 // Ignore vendor-specific argument.
353 return; 355 return;
354 } 356 }
355 CASE("shrink-to-fit") { 357 CASE("shrink-to-fit") {
356 // Ignore vendor-specific argument. 358 // Ignore vendor-specific argument.
357 return; 359 return;
358 } 360 }
359 } 361 }
360 reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, keyStr ing, String()); 362 if (reportWarnings)
363 reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, ke yString, String());
361 } 364 }
362 365
363 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode) 366 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
364 { 367 {
365 static const char* const errors[] = { 368 static const char* const errors[] = {
366 "The key \"%replacement1\" is not recognized and ignored.", 369 "The key \"%replacement1\" is not recognized and ignored.",
367 "The value \"%replacement1\" for key \"%replacement2\" is invalid, and h as been ignored.", 370 "The value \"%replacement1\" for key \"%replacement2\" is invalid, and h as been ignored.",
368 "The value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.", 371 "The value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
369 "The value for key \"maximum-scale\" is out of bounds and the value has been clamped.", 372 "The value for key \"maximum-scale\" is out of bounds and the value has been clamped.",
370 "The key \"target-densitydpi\" is not supported.", 373 "The key \"target-densitydpi\" is not supported.",
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after
514 { 517 {
515 return getAttribute(http_equivAttr); 518 return getAttribute(http_equivAttr);
516 } 519 }
517 520
518 const AtomicString& HTMLMetaElement::name() const 521 const AtomicString& HTMLMetaElement::name() const
519 { 522 {
520 return getNameAttribute(); 523 return getNameAttribute();
521 } 524 }
522 525
523 } 526 }
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/core/html/HTMLMetaElement.h ('k') | third_party/WebKit/Source/web/tests/ViewportTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698