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