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