Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / HTMLMetaElement-in.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  * Copyright (C) 2003, 2010 Apple Inc. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include "config.h"
24 #include "core/html/HTMLMetaElement.h"
25
26 #include "core/HTMLNames.h"
27 #include "core/dom/Document.h"
28 #include "core/frame/LocalFrame.h"
29 #include "core/frame/Settings.h"
30 #include "core/loader/FrameLoaderClient.h"
31 #include "platform/RuntimeEnabledFeatures.h"
32
33 namespace WebCore {
34
35 #define DEFINE_ARRAY_FOR_MATCHING(name, source, maxMatchLength) \
36 const UChar* name; \
37 const unsigned uMaxMatchLength = maxMatchLength; \
38 UChar characterBuffer[uMaxMatchLength]; \
39 if (!source.is8Bit()) { \
40     name = source.characters16(); \
41 } else { \
42     unsigned bufferLength = std::min(uMaxMatchLength, source.length()); \
43     const LChar* characters8 = source.characters8(); \
44     for (unsigned i = 0; i < bufferLength; ++i) \
45         characterBuffer[i] = characters8[i]; \
46     name = characterBuffer; \
47 }
48
49 using namespace HTMLNames;
50
51 inline HTMLMetaElement::HTMLMetaElement(Document& document)
52     : HTMLElement(metaTag, document)
53 {
54     ScriptWrappable::init(this);
55 }
56
57 DEFINE_NODE_FACTORY(HTMLMetaElement)
58
59 static bool isInvalidSeparator(UChar c)
60 {
61     return c == ';';
62 }
63
64 // Though isspace() considers \t and \v to be whitespace, Win IE doesn't.
65 static bool isSeparator(UChar c)
66 {
67     return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0';
68 }
69
70 void HTMLMetaElement::parseContentAttribute(const String& content, KeyValuePairCallback callback, void* data)
71 {
72     bool error = false;
73
74     // Tread lightly in this code -- it was specifically designed to mimic Win IE's parsing behavior.
75     unsigned keyBegin, keyEnd;
76     unsigned valueBegin, valueEnd;
77
78     String buffer = content.lower();
79     unsigned length = buffer.length();
80     for (unsigned i = 0; i < length; /* no increment here */) {
81         // skip to first non-separator, but don't skip past the end of the string
82         while (isSeparator(buffer[i])) {
83             if (i >= length)
84                 break;
85             i++;
86         }
87         keyBegin = i;
88
89         // skip to first separator
90         while (!isSeparator(buffer[i])) {
91             error |= isInvalidSeparator(buffer[i]);
92             if (i >= length)
93                 break;
94             i++;
95         }
96         keyEnd = i;
97
98         // skip to first '=', but don't skip past a ',' or the end of the string
99         while (buffer[i] != '=') {
100             error |= isInvalidSeparator(buffer[i]);
101             if (buffer[i] == ',' || i >= length)
102                 break;
103             i++;
104         }
105
106         // skip to first non-separator, but don't skip past a ',' or the end of the string
107         while (isSeparator(buffer[i])) {
108             if (buffer[i] == ',' || i >= length)
109                 break;
110             i++;
111         }
112         valueBegin = i;
113
114         // skip to first separator
115         while (!isSeparator(buffer[i])) {
116             error |= isInvalidSeparator(buffer[i]);
117             if (i >= length)
118                 break;
119             i++;
120         }
121         valueEnd = i;
122
123         ASSERT_WITH_SECURITY_IMPLICATION(i <= length);
124
125         String keyString = buffer.substring(keyBegin, keyEnd - keyBegin);
126         String valueString = buffer.substring(valueBegin, valueEnd - valueBegin);
127         (this->*callback)(keyString, valueString, data);
128     }
129     if (error) {
130         String message = "Error parsing a meta element's content: ';' is not a valid key-value pair separator. Please use ',' instead.";
131         document().addConsoleMessage(RenderingMessageSource, WarningMessageLevel, message);
132     }
133 }
134
135 static inline float clampLengthValue(float value)
136 {
137     // Limits as defined in the css-device-adapt spec.
138     if (value != ViewportDescription::ValueAuto)
139         return std::min(float(10000), std::max(value, float(1)));
140     return value;
141 }
142
143 static inline float clampScaleValue(float value)
144 {
145     // Limits as defined in the css-device-adapt spec.
146     if (value != ViewportDescription::ValueAuto)
147         return std::min(float(10), std::max(value, float(0.1)));
148     return value;
149 }
150
151 float HTMLMetaElement::parsePositiveNumber(const String& keyString, const String& valueString, bool* ok)
152 {
153     size_t parsedLength;
154     float value;
155     if (valueString.is8Bit())
156         value = charactersToFloat(valueString.characters8(), valueString.length(), parsedLength);
157     else
158         value = charactersToFloat(valueString.characters16(), valueString.length(), parsedLength);
159     if (!parsedLength) {
160         reportViewportWarning(UnrecognizedViewportArgumentValueError, valueString, keyString);
161         if (ok)
162             *ok = false;
163         return 0;
164     }
165     if (parsedLength < valueString.length())
166         reportViewportWarning(TruncatedViewportArgumentValueError, valueString, keyString);
167     if (ok)
168         *ok = true;
169     return value;
170 }
171
172 Length HTMLMetaElement::parseViewportValueAsLength(const String& keyString, const String& valueString)
173 {
174     // 1) Non-negative number values are translated to px lengths.
175     // 2) Negative number values are translated to auto.
176     // 3) device-width and device-height are used as keywords.
177     // 4) Other keywords and unknown values translate to 0.0.
178
179     unsigned length = valueString.length();
180     DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
181     SWITCH(characters, length) {
182         CASE("device-width") {
183             return Length(DeviceWidth);
184         }
185         CASE("device-height") {
186             return Length(DeviceHeight);
187         }
188     }
189
190     float value = parsePositiveNumber(keyString, valueString);
191
192     if (value < 0)
193         return Length(); // auto
194
195     return Length(clampLengthValue(value), Fixed);
196 }
197
198 float HTMLMetaElement::parseViewportValueAsZoom(const String& keyString, const String& valueString, bool& computedValueMatchesParsedValue)
199 {
200     // 1) Non-negative number values are translated to <number> values.
201     // 2) Negative number values are translated to auto.
202     // 3) yes is translated to 1.0.
203     // 4) device-width and device-height are translated to 10.0.
204     // 5) no and unknown values are translated to 0.0
205
206     computedValueMatchesParsedValue = false;
207     unsigned length = valueString.length();
208     DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
209     SWITCH(characters, length) {
210         CASE("yes") {
211             return 1;
212         }
213         CASE("no") {
214             return 0;
215         }
216         CASE("device-width") {
217             return 10;
218         }
219         CASE("device-height") {
220             return 10;
221         }
222     }
223
224     float value = parsePositiveNumber(keyString, valueString);
225
226     if (value < 0)
227         return ViewportDescription::ValueAuto;
228
229     if (value > 10.0)
230         reportViewportWarning(MaximumScaleTooLargeError, String(), String());
231
232     if (!value && document().settings() && document().settings()->viewportMetaZeroValuesQuirk())
233         return ViewportDescription::ValueAuto;
234
235     float clampedValue = clampScaleValue(value);
236     if (clampedValue == value)
237         computedValueMatchesParsedValue = true;
238
239     return clampedValue;
240 }
241
242 bool HTMLMetaElement::parseViewportValueAsUserZoom(const String& keyString, const String& valueString, bool& computedValueMatchesParsedValue)
243 {
244     // yes and no are used as keywords.
245     // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
246     // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
247
248     computedValueMatchesParsedValue = false;
249     unsigned length = valueString.length();
250     DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 13);
251     SWITCH(characters, length) {
252         CASE("yes") {
253             computedValueMatchesParsedValue = true;
254             return true;
255         }
256         CASE("no") {
257             computedValueMatchesParsedValue = true;
258             return false;
259         }
260         CASE("device-width") {
261             return true;
262         }
263         CASE("device-height") {
264             return true;
265         }
266     }
267
268     float value = parsePositiveNumber(keyString, valueString);
269     if (fabs(value) < 1)
270         return false;
271
272     return true;
273 }
274
275 float HTMLMetaElement::parseViewportValueAsDPI(const String& keyString, const String& valueString)
276 {
277     unsigned length = valueString.length();
278     DEFINE_ARRAY_FOR_MATCHING(characters, valueString, 10);
279     SWITCH(characters, length) {
280         CASE("device-dpi") {
281             return ViewportDescription::ValueDeviceDPI;
282         }
283         CASE("low-dpi") {
284             return ViewportDescription::ValueLowDPI;
285         }
286         CASE("medium-dpi") {
287             return ViewportDescription::ValueMediumDPI;
288         }
289         CASE("high-dpi") {
290             return ViewportDescription::ValueHighDPI;
291         }
292     }
293
294     bool ok;
295     float value = parsePositiveNumber(keyString, valueString, &ok);
296     if (!ok || value < 70 || value > 400)
297         return ViewportDescription::ValueAuto;
298
299     return value;
300 }
301
302 void HTMLMetaElement::processViewportKeyValuePair(const String& keyString, const String& valueString, void* data)
303 {
304     ViewportDescription* description = static_cast<ViewportDescription*>(data);
305
306     unsigned length = keyString.length();
307
308     DEFINE_ARRAY_FOR_MATCHING(characters, keyString, 17);
309     SWITCH(characters, length) {
310         CASE("width") {
311             const Length& width = parseViewportValueAsLength(keyString, valueString);
312             if (width.isAuto())
313                 return;
314             description->minWidth = Length(ExtendToZoom);
315             description->maxWidth = width;
316             return;
317         }
318         CASE("height") {
319             const Length& height = parseViewportValueAsLength(keyString, valueString);
320             if (height.isAuto())
321                 return;
322             description->minHeight = Length(ExtendToZoom);
323             description->maxHeight = height;
324             return;
325         }
326         CASE("initial-scale") {
327             description->zoom = parseViewportValueAsZoom(keyString, valueString, description->zoomIsExplicit);
328             return;
329         }
330         CASE("minimum-scale") {
331             description->minZoom = parseViewportValueAsZoom(keyString, valueString, description->minZoomIsExplicit);
332             return;
333         }
334         CASE("maximum-scale") {
335             description->maxZoom = parseViewportValueAsZoom(keyString, valueString, description->maxZoomIsExplicit);
336             return;
337         }
338         CASE("user-scalable") {
339             description->userZoom = parseViewportValueAsUserZoom(keyString, valueString, description->userZoomIsExplicit);
340             return;
341         }
342         CASE("target-densitydpi") {
343             description->deprecatedTargetDensityDPI = parseViewportValueAsDPI(keyString, valueString);
344             reportViewportWarning(TargetDensityDpiUnsupported, String(), String());
345             return;
346         }
347         CASE("minimal-ui") {
348             // Ignore vendor-specific argument.
349             return;
350         }
351     }
352     reportViewportWarning(UnrecognizedViewportArgumentKeyError, keyString, String());
353 }
354
355 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
356 {
357     static const char* const errors[] = {
358         "The key \"%replacement1\" is not recognized and ignored.",
359         "The value \"%replacement1\" for key \"%replacement2\" is invalid, and has been ignored.",
360         "The value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
361         "The value for key \"maximum-scale\" is out of bounds and the value has been clamped.",
362         "The key \"target-densitydpi\" is not supported.",
363     };
364
365     return errors[errorCode];
366 }
367
368 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
369 {
370     switch (errorCode) {
371     case TruncatedViewportArgumentValueError:
372     case TargetDensityDpiUnsupported:
373     case UnrecognizedViewportArgumentKeyError:
374     case UnrecognizedViewportArgumentValueError:
375     case MaximumScaleTooLargeError:
376         return WarningMessageLevel;
377     }
378
379     ASSERT_NOT_REACHED();
380     return ErrorMessageLevel;
381 }
382
383 void HTMLMetaElement::reportViewportWarning(ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
384 {
385     if (!document().frame())
386         return;
387
388     String message = viewportErrorMessageTemplate(errorCode);
389     if (!replacement1.isNull())
390         message.replace("%replacement1", replacement1);
391     if (!replacement2.isNull())
392         message.replace("%replacement2", replacement2);
393
394     // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
395     document().addConsoleMessage(RenderingMessageSource, viewportErrorMessageLevel(errorCode), message);
396 }
397
398 void HTMLMetaElement::processViewportContentAttribute(const String& content, ViewportDescription::Type origin)
399 {
400     ASSERT(!content.isNull());
401
402     if (!document().shouldOverrideLegacyDescription(origin))
403         return;
404
405     ViewportDescription descriptionFromLegacyTag(origin);
406     if (document().shouldMergeWithLegacyDescription(origin))
407         descriptionFromLegacyTag = document().viewportDescription();
408
409     parseContentAttribute(content, &HTMLMetaElement::processViewportKeyValuePair, (void*)&descriptionFromLegacyTag);
410
411     if (descriptionFromLegacyTag.minZoom == ViewportDescription::ValueAuto)
412         descriptionFromLegacyTag.minZoom = 0.25;
413
414     if (descriptionFromLegacyTag.maxZoom == ViewportDescription::ValueAuto) {
415         descriptionFromLegacyTag.maxZoom = 5;
416         descriptionFromLegacyTag.minZoom = std::min(descriptionFromLegacyTag.minZoom, float(5));
417     }
418
419     document().setViewportDescription(descriptionFromLegacyTag);
420 }
421
422
423 void HTMLMetaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
424 {
425     if (name == http_equivAttr || name == contentAttr) {
426         process();
427         return;
428     }
429
430     if (name != nameAttr)
431         HTMLElement::parseAttribute(name, value);
432 }
433
434 Node::InsertionNotificationRequest HTMLMetaElement::insertedInto(ContainerNode* insertionPoint)
435 {
436     HTMLElement::insertedInto(insertionPoint);
437     return InsertionShouldCallDidNotifySubtreeInsertions;
438 }
439
440 void HTMLMetaElement::didNotifySubtreeInsertionsToDocument()
441 {
442     process();
443 }
444
445 static bool inDocumentHead(HTMLMetaElement* element)
446 {
447     if (!element->inDocument())
448         return false;
449
450     for (Element* current = element; current; current = current->parentElement()) {
451         if (isHTMLHeadElement(*current))
452             return true;
453     }
454     return false;
455 }
456
457 void HTMLMetaElement::process()
458 {
459     if (!inDocument())
460         return;
461
462     // All below situations require a content attribute (which can be the empty string).
463     const AtomicString& contentValue = fastGetAttribute(contentAttr);
464     if (contentValue.isNull())
465         return;
466
467     const AtomicString& nameValue = fastGetAttribute(nameAttr);
468     if (!nameValue.isEmpty()) {
469         if (equalIgnoringCase(nameValue, "viewport"))
470             processViewportContentAttribute(contentValue, ViewportDescription::ViewportMeta);
471         else if (equalIgnoringCase(nameValue, "referrer"))
472             document().processReferrerPolicy(contentValue);
473         else if (equalIgnoringCase(nameValue, "handheldfriendly") && equalIgnoringCase(contentValue, "true"))
474             processViewportContentAttribute("width=device-width", ViewportDescription::HandheldFriendlyMeta);
475         else if (equalIgnoringCase(nameValue, "mobileoptimized"))
476             processViewportContentAttribute("width=device-width, initial-scale=1", ViewportDescription::MobileOptimizedMeta);
477         else if (RuntimeEnabledFeatures::themeColorEnabled() && equalIgnoringCase(nameValue, "theme-color") && document().frame())
478             document().frame()->loader().client()->dispatchDidChangeThemeColor();
479     }
480
481     // Get the document to process the tag, but only if we're actually part of DOM
482     // tree (changing a meta tag while it's not in the tree shouldn't have any effect
483     // on the document).
484
485     const AtomicString& httpEquivValue = fastGetAttribute(http_equivAttr);
486     if (!httpEquivValue.isEmpty())
487         document().processHttpEquiv(httpEquivValue, contentValue, inDocumentHead(this));
488 }
489
490 const AtomicString& HTMLMetaElement::content() const
491 {
492     return getAttribute(contentAttr);
493 }
494
495 const AtomicString& HTMLMetaElement::httpEquiv() const
496 {
497     return getAttribute(http_equivAttr);
498 }
499
500 const AtomicString& HTMLMetaElement::name() const
501 {
502     return getNameAttribute();
503 }
504
505 }