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.
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.
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.
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.
24 #include "HTMLImageElement.h"
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"
37 #include "RenderImage.h"
41 using namespace HTMLNames;
43 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
44 : HTMLElement(tagName, document)
47 , m_compositeOperator(CompositeSourceOver)
49 ASSERT(hasTagName(imgTag));
50 setHasCustomStyleResolveCallbacks();
52 form->registerImgElement(this);
55 PassRefPtr<HTMLImageElement> HTMLImageElement::create(Document& document)
57 return adoptRef(new HTMLImageElement(imgTag, document));
60 PassRefPtr<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
62 return adoptRef(new HTMLImageElement(tagName, document, form));
65 HTMLImageElement::~HTMLImageElement()
68 m_form->removeImgElement(this);
71 PassRefPtr<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, const int* optionalWidth, const int* optionalHeight)
73 RefPtr<HTMLImageElement> image = adoptRef(new HTMLImageElement(imgTag, document));
75 image->setWidth(*optionalWidth);
77 image->setHeight(*optionalHeight);
78 return image.release();
81 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
83 if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
85 return HTMLElement::isPresentationAttribute(name);
88 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
90 if (name == widthAttr)
91 addHTMLLengthToStyle(style, CSSPropertyWidth, value);
92 else if (name == heightAttr)
93 addHTMLLengthToStyle(style, CSSPropertyHeight, value);
94 else if (name == borderAttr)
95 applyBorderAttributeToStyle(value, style);
96 else if (name == vspaceAttr) {
97 addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
98 addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
99 } else if (name == hspaceAttr) {
100 addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
101 addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
102 } else if (name == alignAttr)
103 applyAlignmentAttributeToStyle(value, style);
104 else if (name == valignAttr)
105 addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
107 HTMLElement::collectStyleForPresentationAttribute(name, value, style);
110 const AtomicString& HTMLImageElement::imageSourceURL() const
112 return m_bestFitImageURL.isEmpty() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
115 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
117 if (name == altAttr) {
118 if (renderer() && renderer()->isImage())
119 toRenderImage(renderer())->updateAltText();
120 } else if (name == srcAttr || name == srcsetAttr) {
121 m_bestFitImageURL = bestFitSourceForImageAttributes(document().deviceScaleFactor(), fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr));
122 m_imageLoader.updateFromElementIgnoringPreviousError();
123 } else if (name == usemapAttr) {
124 setIsLink(!value.isNull() && !shouldProhibitLinks(this));
126 if (inDocument() && !m_lowercasedUsemap.isNull())
127 document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
129 // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning, which has to be stripped off.
130 // FIXME: We should check that the first character is '#'.
131 // FIXME: HTML5 specification says we should strip any leading string before '#'.
132 // FIXME: HTML5 specification says we should ignore usemap attributes without #.
133 if (value.length() > 1)
134 m_lowercasedUsemap = value.string().substring(1).lower();
136 m_lowercasedUsemap = nullAtom;
138 if (inDocument() && !m_lowercasedUsemap.isNull())
139 document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
140 } else if (name == onbeforeloadAttr)
141 setAttributeEventListener(eventNames().beforeloadEvent, name, value);
142 else if (name == compositeAttr) {
143 // FIXME: images don't support blend modes in their compositing attribute.
144 BlendMode blendOp = BlendModeNormal;
145 if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
146 m_compositeOperator = CompositeSourceOver;
148 if (name == nameAttr) {
149 bool willHaveName = !value.isNull();
150 if (hasName() != willHaveName && inDocument() && document().isHTMLDocument()) {
151 HTMLDocument* document = toHTMLDocument(&this->document());
152 const AtomicString& id = getIdAttribute();
153 if (!id.isEmpty() && id != getNameAttribute()) {
155 document->addDocumentNamedItem(*id.impl(), *this);
157 document->removeDocumentNamedItem(*id.impl(), *this);
161 HTMLElement::parseAttribute(name, value);
165 String HTMLImageElement::altText() const
167 // lets figure out the alt text.. magic stuff
168 // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
169 // also heavily discussed by Hixie on bugzilla
170 String alt = getAttribute(altAttr);
171 // fall back to title attribute
173 alt = getAttribute(titleAttr);
177 RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(PassRef<RenderStyle> style)
179 if (style.get().hasContent())
180 return RenderElement::createFor(*this, std::move(style));
182 return createRenderer<RenderImage>(*this, std::move(style));
185 bool HTMLImageElement::canStartSelection() const
188 return HTMLElement::canStartSelection();
193 void HTMLImageElement::didAttachRenderers()
195 if (!renderer() || !renderer()->isImage())
197 if (m_imageLoader.hasPendingBeforeLoadEvent())
199 RenderImage* renderImage = toRenderImage(renderer());
200 RenderImageResource& renderImageResource = renderImage->imageResource();
201 if (renderImageResource.hasImage())
203 renderImageResource.setCachedImage(m_imageLoader.image());
205 // If we have no image at all because we have no src attribute, set
206 // image height and width for the alt text instead.
207 if (!m_imageLoader.image() && !renderImageResource.cachedImage())
208 renderImage->setImageSizeForAltText();
211 Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode& insertionPoint)
213 if (!m_form) { // m_form can be non-null if it was set in constructor.
214 m_form = HTMLFormElement::findClosestFormAncestor(*this);
216 m_form->registerImgElement(this);
219 // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result
220 // in callbacks back to this node.
221 Node::InsertionNotificationRequest insertNotificationRequest = HTMLElement::insertedInto(insertionPoint);
223 if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
224 document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
226 // If we have been inserted from a renderer-less document,
227 // our loader may have not fetched the image, so do it now.
228 if (insertionPoint.inDocument() && !m_imageLoader.image())
229 m_imageLoader.updateFromElement();
231 return insertNotificationRequest;
234 void HTMLImageElement::removedFrom(ContainerNode& insertionPoint)
237 m_form->removeImgElement(this);
239 if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
240 document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
243 HTMLElement::removedFrom(insertionPoint);
246 int HTMLImageElement::width(bool ignorePendingStylesheets)
249 // check the attribute first for an explicit pixel value
251 int width = getAttribute(widthAttr).toInt(&ok);
255 // if the image is available, use its width
256 if (m_imageLoader.image())
257 return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
260 if (ignorePendingStylesheets)
261 document().updateLayoutIgnorePendingStylesheets();
263 document().updateLayout();
265 RenderBox* box = renderBox();
266 return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), *box) : 0;
269 int HTMLImageElement::height(bool ignorePendingStylesheets)
272 // check the attribute first for an explicit pixel value
274 int height = getAttribute(heightAttr).toInt(&ok);
278 // if the image is available, use its height
279 if (m_imageLoader.image())
280 return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
283 if (ignorePendingStylesheets)
284 document().updateLayoutIgnorePendingStylesheets();
286 document().updateLayout();
288 RenderBox* box = renderBox();
289 return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), *box) : 0;
292 int HTMLImageElement::naturalWidth() const
294 if (!m_imageLoader.image())
297 return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
300 int HTMLImageElement::naturalHeight() const
302 if (!m_imageLoader.image())
305 return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
308 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
310 return attribute.name() == srcAttr
311 || attribute.name() == lowsrcAttr
312 || attribute.name() == longdescAttr
313 || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
314 || HTMLElement::isURLAttribute(attribute);
317 bool HTMLImageElement::matchesLowercasedUsemap(const AtomicStringImpl& name) const
319 ASSERT(String(&const_cast<AtomicStringImpl&>(name)).lower().impl() == &name);
320 return m_lowercasedUsemap.impl() == &name;
323 const AtomicString& HTMLImageElement::alt() const
325 return getAttribute(altAttr);
328 bool HTMLImageElement::draggable() const
330 // Image elements are draggable by default.
331 return !equalIgnoringCase(getAttribute(draggableAttr), "false");
334 void HTMLImageElement::setHeight(int value)
336 setIntegralAttribute(heightAttr, value);
339 URL HTMLImageElement::src() const
341 return document().completeURL(getAttribute(srcAttr));
344 void HTMLImageElement::setSrc(const String& value)
346 setAttribute(srcAttr, value);
349 void HTMLImageElement::setWidth(int value)
351 setIntegralAttribute(widthAttr, value);
354 int HTMLImageElement::x() const
356 auto renderer = this->renderer();
360 // FIXME: This doesn't work correctly with transforms.
361 return renderer->localToAbsolute().x();
364 int HTMLImageElement::y() const
366 auto renderer = this->renderer();
370 // FIXME: This doesn't work correctly with transforms.
371 return renderer->localToAbsolute().y();
374 bool HTMLImageElement::complete() const
376 return m_imageLoader.imageComplete();
379 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
381 HTMLElement::addSubresourceAttributeURLs(urls);
383 addSubresourceURL(urls, src());
384 // FIXME: What about when the usemap attribute begins with "#"?
385 addSubresourceURL(urls, document().completeURL(getAttribute(usemapAttr)));
388 void HTMLImageElement::didMoveToNewDocument(Document* oldDocument)
390 m_imageLoader.elementDidMoveToNewDocument();
391 HTMLElement::didMoveToNewDocument(oldDocument);
394 bool HTMLImageElement::isServerMap() const
396 if (!fastHasAttribute(ismapAttr))
399 const AtomicString& usemap = fastGetAttribute(usemapAttr);
401 // If the usemap attribute starts with '#', it refers to a map element in the document.
402 if (usemap.string()[0] == '#')
405 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
409 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
410 bool HTMLImageElement::willRespondToMouseClickEvents()
412 auto renderer = this->renderer();
413 if (!renderer || renderer->style().touchCalloutEnabled())
415 return HTMLElement::willRespondToMouseClickEvents();