form.elements should reflect the element ordering after the HTML tree builder algorithm
[WebKit-https.git] / Source / WebCore / html / HTMLImageElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
5  * Copyright (C) 2010 Google 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 "HTMLImageElement.h"
25
26 #include "CSSPropertyNames.h"
27 #include "CSSValueKeywords.h"
28 #include "CachedImage.h"
29 #include "EventNames.h"
30 #include "FrameView.h"
31 #include "HTMLAnchorElement.h"
32 #include "HTMLDocument.h"
33 #include "HTMLFormElement.h"
34 #include "HTMLParserIdioms.h"
35 #include "HTMLSourceElement.h"
36 #include "HTMLSrcsetParser.h"
37 #include "MIMETypeRegistry.h"
38 #include "MediaList.h"
39 #include "MediaQueryEvaluator.h"
40 #include "Page.h"
41 #include "RenderImage.h"
42 #include "Settings.h"
43 #include "ShadowRoot.h"
44 #include "SourceSizeList.h"
45 #include <wtf/text/StringBuilder.h>
46
47 #if ENABLE(SERVICE_CONTROLS)
48 #include "ImageControlsRootElement.h"
49 #endif
50
51 namespace WebCore {
52
53 using namespace HTMLNames;
54
55 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
56     : HTMLElement(tagName, document)
57     , m_imageLoader(*this)
58     , m_form(nullptr)
59     , m_formSetByParser(form)
60     , m_compositeOperator(CompositeSourceOver)
61     , m_imageDevicePixelRatio(1.0f)
62 #if ENABLE(SERVICE_CONTROLS)
63     , m_experimentalImageMenuEnabled(false)
64 #endif
65 {
66     ASSERT(hasTagName(imgTag));
67     setHasCustomStyleResolveCallbacks();
68 }
69
70 Ref<HTMLImageElement> HTMLImageElement::create(Document& document)
71 {
72     return adoptRef(*new HTMLImageElement(imgTag, document));
73 }
74
75 Ref<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
76 {
77     return adoptRef(*new HTMLImageElement(tagName, document, form));
78 }
79
80 HTMLImageElement::~HTMLImageElement()
81 {
82     if (m_form)
83         m_form->removeImgElement(this);
84 }
85
86 Ref<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, const int* optionalWidth, const int* optionalHeight)
87 {
88     Ref<HTMLImageElement> image = adoptRef(*new HTMLImageElement(imgTag, document));
89     if (optionalWidth)
90         image->setWidth(*optionalWidth);
91     if (optionalHeight)
92         image->setHeight(*optionalHeight);
93     return image;
94 }
95
96 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
97 {
98     if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
99         return true;
100     return HTMLElement::isPresentationAttribute(name);
101 }
102
103 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
104 {
105     if (name == widthAttr)
106         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
107     else if (name == heightAttr)
108         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
109     else if (name == borderAttr)
110         applyBorderAttributeToStyle(value, style);
111     else if (name == vspaceAttr) {
112         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
113         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
114     } else if (name == hspaceAttr) {
115         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
116         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
117     } else if (name == alignAttr)
118         applyAlignmentAttributeToStyle(value, style);
119     else if (name == valignAttr)
120         addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
121     else
122         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
123 }
124
125 const AtomicString& HTMLImageElement::imageSourceURL() const
126 {
127     return m_bestFitImageURL.isEmpty() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
128 }
129
130 void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate)
131 {
132     m_bestFitImageURL = candidate.string.toString();
133     m_currentSrc = AtomicString(document().completeURL(imageSourceURL()).string());
134     if (candidate.density >= 0)
135         m_imageDevicePixelRatio = 1 / candidate.density;
136     if (is<RenderImage>(renderer()))
137         downcast<RenderImage>(*renderer()).setImageDevicePixelRatio(m_imageDevicePixelRatio);
138 }
139
140 ImageCandidate HTMLImageElement::bestFitSourceFromPictureElement()
141 {
142     auto* parent = parentNode();
143     if (!is<HTMLPictureElement>(parent))
144         return { };
145     for (Node* child = parent->firstChild(); child && child != this; child = child->nextSibling()) {
146         if (!is<HTMLSourceElement>(*child))
147             continue;
148         auto& source = downcast<HTMLSourceElement>(*child);
149         auto& srcset = source.fastGetAttribute(srcsetAttr);
150         if (srcset.isEmpty())
151             continue;
152         if (source.hasAttribute(typeAttr)) {
153             String type = source.fastGetAttribute(typeAttr).string();
154             int indexOfSemicolon = type.find(';');
155             if (indexOfSemicolon >= 0)
156                 type.truncate(indexOfSemicolon);
157             type = stripLeadingAndTrailingHTMLSpaces(type);
158             type = type.lower();
159             if (!type.isEmpty() && !MIMETypeRegistry::isSupportedImageMIMEType(type) && type != "image/svg+xml")
160                 continue;
161         }
162         MediaQueryEvaluator evaluator(document().printing() ? "print" : "screen", document().frame(), computedStyle());
163         if (!evaluator.eval(MediaQuerySet::createAllowingDescriptionSyntax(source.media()).ptr()))
164             continue;
165
166         float sourceSize = parseSizesAttribute(source.fastGetAttribute(sizesAttr).string(), document().renderView(), document().frame());
167         ImageCandidate candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), nullAtom, source.fastGetAttribute(srcsetAttr), sourceSize);
168         if (!candidate.isEmpty())
169             return candidate;
170     }
171     return { };
172 }
173
174 void HTMLImageElement::selectImageSource()
175 {
176     // First look for the best fit source from our <picture> parent if we have one.
177     ImageCandidate candidate = bestFitSourceFromPictureElement();
178     if (candidate.isEmpty()) {
179         // If we don't have a <picture> or didn't find a source, then we use our own attributes.
180         float sourceSize = parseSizesAttribute(fastGetAttribute(sizesAttr).string(), document().renderView(), document().frame());
181         candidate = bestFitSourceForImageAttributes(document().deviceScaleFactor(), fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr), sourceSize);
182     }
183     setBestFitURLAndDPRFromImageCandidate(candidate);
184     m_imageLoader.updateFromElementIgnoringPreviousError();
185 }
186
187 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
188 {
189     if (name == altAttr) {
190         if (is<RenderImage>(renderer()))
191             downcast<RenderImage>(*renderer()).updateAltText();
192     } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr)
193         selectImageSource();
194     else if (name == usemapAttr) {
195         if (inDocument() && !m_lowercasedUsemap.isNull())
196             document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
197
198         // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning, which has to be stripped off.
199         // FIXME: We should check that the first character is '#'.
200         // FIXME: HTML5 specification says we should strip any leading string before '#'.
201         // FIXME: HTML5 specification says we should ignore usemap attributes without #.
202         if (value.length() > 1)
203             m_lowercasedUsemap = value.string().substring(1).lower();
204         else
205             m_lowercasedUsemap = nullAtom;
206
207         if (inDocument() && !m_lowercasedUsemap.isNull())
208             document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
209     } else if (name == compositeAttr) {
210         // FIXME: images don't support blend modes in their compositing attribute.
211         BlendMode blendOp = BlendModeNormal;
212         if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
213             m_compositeOperator = CompositeSourceOver;
214 #if ENABLE(SERVICE_CONTROLS)
215     } else if (name == webkitimagemenuAttr) {
216         m_experimentalImageMenuEnabled = !value.isNull();
217         updateImageControls();
218 #endif
219     } else {
220         if (name == nameAttr) {
221             bool willHaveName = !value.isNull();
222             if (m_hadNameBeforeAttributeChanged != willHaveName && inDocument() && is<HTMLDocument>(document())) {
223                 HTMLDocument& document = downcast<HTMLDocument>(this->document());
224                 const AtomicString& id = getIdAttribute();
225                 if (!id.isEmpty() && id != getNameAttribute()) {
226                     if (willHaveName)
227                         document.addDocumentNamedItem(*id.impl(), *this);
228                     else
229                         document.removeDocumentNamedItem(*id.impl(), *this);
230                 }
231             }
232             m_hadNameBeforeAttributeChanged = willHaveName;
233         }
234         HTMLElement::parseAttribute(name, value);
235     }
236 }
237
238 const AtomicString& HTMLImageElement::altText() const
239 {
240     // lets figure out the alt text.. magic stuff
241     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
242     // also heavily discussed by Hixie on bugzilla
243     const AtomicString& alt = fastGetAttribute(altAttr);
244     if (!alt.isNull())
245         return alt;
246     // fall back to title attribute
247     return fastGetAttribute(titleAttr);
248 }
249
250 RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(Ref<RenderStyle>&& style, const RenderTreePosition&)
251 {
252     if (style.get().hasContent())
253         return RenderElement::createFor(*this, WTF::move(style));
254
255     return createRenderer<RenderImage>(*this, WTF::move(style), nullptr, m_imageDevicePixelRatio);
256 }
257
258 bool HTMLImageElement::canStartSelection() const
259 {
260     if (shadowRoot())
261         return HTMLElement::canStartSelection();
262
263     return false;
264 }
265
266 void HTMLImageElement::didAttachRenderers()
267 {
268     if (!is<RenderImage>(renderer()))
269         return;
270     if (m_imageLoader.hasPendingBeforeLoadEvent())
271         return;
272
273 #if ENABLE(SERVICE_CONTROLS)
274     updateImageControls();
275 #endif
276
277     auto& renderImage = downcast<RenderImage>(*renderer());
278     RenderImageResource& renderImageResource = renderImage.imageResource();
279     if (renderImageResource.hasImage())
280         return;
281     renderImageResource.setCachedImage(m_imageLoader.image());
282
283     // If we have no image at all because we have no src attribute, set
284     // image height and width for the alt text instead.
285     if (!m_imageLoader.image() && !renderImageResource.cachedImage())
286         renderImage.setImageSizeForAltText();
287 }
288
289 Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode& insertionPoint)
290 {
291     if (m_formSetByParser) {
292         m_form = m_formSetByParser;
293         m_formSetByParser = nullptr;
294     }
295
296     if (!m_form)
297         m_form = HTMLFormElement::findClosestFormAncestor(*this);
298
299     if (m_form)
300         m_form->registerImgElement(this);
301
302     // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result
303     // in callbacks back to this node.
304     Node::InsertionNotificationRequest insertNotificationRequest = HTMLElement::insertedInto(insertionPoint);
305
306     if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
307         document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
308
309     if (is<HTMLPictureElement>(parentNode()))
310         selectImageSource();
311
312     // If we have been inserted from a renderer-less document,
313     // our loader may have not fetched the image, so do it now.
314     if (insertionPoint.inDocument() && !m_imageLoader.image())
315         m_imageLoader.updateFromElement();
316
317     return insertNotificationRequest;
318 }
319
320 void HTMLImageElement::removedFrom(ContainerNode& insertionPoint)
321 {
322     if (m_form)
323         m_form->removeImgElement(this);
324
325     if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
326         document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
327
328     m_form = nullptr;
329     HTMLElement::removedFrom(insertionPoint);
330 }
331
332 int HTMLImageElement::width(bool ignorePendingStylesheets)
333 {
334     if (!renderer()) {
335         // check the attribute first for an explicit pixel value
336         bool ok;
337         int width = getAttribute(widthAttr).toInt(&ok);
338         if (ok)
339             return width;
340
341         // if the image is available, use its width
342         if (m_imageLoader.image())
343             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
344     }
345
346     if (ignorePendingStylesheets)
347         document().updateLayoutIgnorePendingStylesheets();
348     else
349         document().updateLayout();
350
351     RenderBox* box = renderBox();
352     if (!box)
353         return 0;
354     LayoutRect contentRect = box->contentBoxRect();
355     return adjustForAbsoluteZoom(snappedIntRect(contentRect).width(), *box);
356 }
357
358 int HTMLImageElement::height(bool ignorePendingStylesheets)
359 {
360     if (!renderer()) {
361         // check the attribute first for an explicit pixel value
362         bool ok;
363         int height = getAttribute(heightAttr).toInt(&ok);
364         if (ok)
365             return height;
366
367         // if the image is available, use its height
368         if (m_imageLoader.image())
369             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
370     }
371
372     if (ignorePendingStylesheets)
373         document().updateLayoutIgnorePendingStylesheets();
374     else
375         document().updateLayout();
376
377     RenderBox* box = renderBox();
378     if (!box)
379         return 0;
380     LayoutRect contentRect = box->contentBoxRect();
381     return adjustForAbsoluteZoom(snappedIntRect(contentRect).height(), *box);
382 }
383
384 int HTMLImageElement::naturalWidth() const
385 {
386     if (!m_imageLoader.image())
387         return 0;
388
389     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
390 }
391
392 int HTMLImageElement::naturalHeight() const
393 {
394     if (!m_imageLoader.image())
395         return 0;
396
397     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
398 }
399
400 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
401 {
402     return attribute.name() == srcAttr
403         || attribute.name() == lowsrcAttr
404         || attribute.name() == longdescAttr
405         || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
406         || HTMLElement::isURLAttribute(attribute);
407 }
408
409 bool HTMLImageElement::attributeContainsURL(const Attribute& attribute) const
410 {
411     return attribute.name() == srcsetAttr
412         || HTMLElement::attributeContainsURL(attribute);
413 }
414
415 String HTMLImageElement::completeURLsInAttributeValue(const URL& base, const Attribute& attribute) const
416 {
417     if (attribute.name() == srcsetAttr) {
418         Vector<ImageCandidate> imageCandidates = parseImageCandidatesFromSrcsetAttribute(StringView(attribute.value()));
419         StringBuilder result;
420         for (const auto& candidate : imageCandidates) {
421             if (&candidate != &imageCandidates[0])
422                 result.appendLiteral(", ");
423             result.append(URL(base, candidate.string.toString()).string());
424             if (candidate.density != UninitializedDescriptor) {
425                 result.append(' ');
426                 result.appendNumber(candidate.density);
427                 result.append('x');
428             }
429             if (candidate.resourceWidth != UninitializedDescriptor) {
430                 result.append(' ');
431                 result.appendNumber(candidate.resourceWidth);
432                 result.append('x');
433             }
434         }
435         return result.toString();
436     }
437     return HTMLElement::completeURLsInAttributeValue(base, attribute);
438 }
439
440 bool HTMLImageElement::matchesLowercasedUsemap(const AtomicStringImpl& name) const
441 {
442     ASSERT(String(&const_cast<AtomicStringImpl&>(name)).lower().impl() == &name);
443     return m_lowercasedUsemap.impl() == &name;
444 }
445
446 const AtomicString& HTMLImageElement::alt() const
447 {
448     return fastGetAttribute(altAttr);
449 }
450
451 bool HTMLImageElement::draggable() const
452 {
453     // Image elements are draggable by default.
454     return !equalIgnoringCase(fastGetAttribute(draggableAttr), "false");
455 }
456
457 void HTMLImageElement::setHeight(int value)
458 {
459     setIntegralAttribute(heightAttr, value);
460 }
461
462 URL HTMLImageElement::src() const
463 {
464     return document().completeURL(fastGetAttribute(srcAttr));
465 }
466
467 void HTMLImageElement::setSrc(const String& value)
468 {
469     setAttribute(srcAttr, value);
470 }
471
472 void HTMLImageElement::setWidth(int value)
473 {
474     setIntegralAttribute(widthAttr, value);
475 }
476
477 int HTMLImageElement::x() const
478 {
479     document().updateLayoutIgnorePendingStylesheets();
480     auto renderer = this->renderer();
481     if (!renderer)
482         return 0;
483
484     // FIXME: This doesn't work correctly with transforms.
485     return renderer->localToAbsolute().x();
486 }
487
488 int HTMLImageElement::y() const
489 {
490     document().updateLayoutIgnorePendingStylesheets();
491     auto renderer = this->renderer();
492     if (!renderer)
493         return 0;
494
495     // FIXME: This doesn't work correctly with transforms.
496     return renderer->localToAbsolute().y();
497 }
498
499 bool HTMLImageElement::complete() const
500 {
501     return m_imageLoader.imageComplete();
502 }
503
504 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
505 {
506     HTMLElement::addSubresourceAttributeURLs(urls);
507
508     addSubresourceURL(urls, src());
509     // FIXME: What about when the usemap attribute begins with "#"?
510     addSubresourceURL(urls, document().completeURL(fastGetAttribute(usemapAttr)));
511 }
512
513 void HTMLImageElement::didMoveToNewDocument(Document* oldDocument)
514 {
515     m_imageLoader.elementDidMoveToNewDocument();
516     HTMLElement::didMoveToNewDocument(oldDocument);
517 }
518
519 bool HTMLImageElement::isServerMap() const
520 {
521     if (!fastHasAttribute(ismapAttr))
522         return false;
523
524     const AtomicString& usemap = fastGetAttribute(usemapAttr);
525
526     // If the usemap attribute starts with '#', it refers to a map element in the document.
527     if (usemap.string()[0] == '#')
528         return false;
529
530     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
531 }
532
533 #if ENABLE(SERVICE_CONTROLS)
534 void HTMLImageElement::updateImageControls()
535 {
536     // If this image element is inside a shadow tree then it is part of an image control.
537     if (isInShadowTree())
538         return;
539
540     Settings* settings = document().settings();
541     if (!settings || !settings->imageControlsEnabled())
542         return;
543
544     bool hasControls = hasImageControls();
545     if (!m_experimentalImageMenuEnabled && hasControls)
546         destroyImageControls();
547     else if (m_experimentalImageMenuEnabled && !hasControls)
548         createImageControls();
549 }
550
551 void HTMLImageElement::createImageControls()
552 {
553     ASSERT(m_experimentalImageMenuEnabled);
554     ASSERT(!hasImageControls());
555
556     RefPtr<ImageControlsRootElement> imageControls = ImageControlsRootElement::maybeCreate(document());
557     if (!imageControls)
558         return;
559
560     ensureUserAgentShadowRoot().appendChild(imageControls.releaseNonNull());
561
562     auto* renderObject = renderer();
563     if (!renderObject)
564         return;
565
566     downcast<RenderImage>(*renderObject).setHasShadowControls(true);
567 }
568
569 void HTMLImageElement::destroyImageControls()
570 {
571     ShadowRoot* shadowRoot = userAgentShadowRoot();
572     if (!shadowRoot)
573         return;
574
575     if (Node* node = shadowRoot->firstChild()) {
576         ASSERT_WITH_SECURITY_IMPLICATION(node->isImageControlsRootElement());
577         shadowRoot->removeChild(*node);
578     }
579
580     auto* renderObject = renderer();
581     if (!renderObject)
582         return;
583
584     downcast<RenderImage>(*renderObject).setHasShadowControls(false);
585 }
586
587 bool HTMLImageElement::hasImageControls() const
588 {
589     if (ShadowRoot* shadowRoot = userAgentShadowRoot()) {
590         Node* node = shadowRoot->firstChild();
591         ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isImageControlsRootElement());
592         return node;
593     }
594
595     return false;
596 }
597
598 bool HTMLImageElement::childShouldCreateRenderer(const Node& child) const
599 {
600     return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child);
601 }
602 #endif // ENABLE(SERVICE_CONTROLS)
603
604 #if PLATFORM(IOS)
605 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
606 bool HTMLImageElement::willRespondToMouseClickEvents()
607 {
608     auto renderer = this->renderer();
609     if (!renderer || renderer->style().touchCalloutEnabled())
610         return true;
611     return HTMLElement::willRespondToMouseClickEvents();
612 }
613 #endif
614
615 }