Assertion failure in WebCore::PseudoElement::didRecalcStyle()
[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 "Page.h"
37 #include "RenderImage.h"
38
39 namespace WebCore {
40
41 using namespace HTMLNames;
42
43 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
44     : HTMLElement(tagName, document)
45     , m_imageLoader(this)
46     , m_form(form)
47     , m_compositeOperator(CompositeSourceOver)
48 {
49     ASSERT(hasTagName(imgTag));
50     setHasCustomStyleResolveCallbacks();
51     if (form)
52         form->registerImgElement(this);
53 }
54
55 PassRefPtr<HTMLImageElement> HTMLImageElement::create(Document& document)
56 {
57     return adoptRef(new HTMLImageElement(imgTag, document));
58 }
59
60 PassRefPtr<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
61 {
62     return adoptRef(new HTMLImageElement(tagName, document, form));
63 }
64
65 HTMLImageElement::~HTMLImageElement()
66 {
67     if (m_form)
68         m_form->removeImgElement(this);
69 }
70
71 PassRefPtr<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, const int* optionalWidth, const int* optionalHeight)
72 {
73     RefPtr<HTMLImageElement> image = adoptRef(new HTMLImageElement(imgTag, document));
74     if (optionalWidth)
75         image->setWidth(*optionalWidth);
76     if (optionalHeight)
77         image->setHeight(*optionalHeight);
78     return image.release();
79 }
80
81 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
82 {
83     if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
84         return true;
85     return HTMLElement::isPresentationAttribute(name);
86 }
87
88 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
89 {
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);
106     else
107         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
108 }
109
110 const AtomicString& HTMLImageElement::imageSourceURL() const
111 {
112     return m_bestFitImageURL.isEmpty() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
113 }
114
115 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
116 {
117     if (name == altAttr) {
118         if (renderer() && renderer()->isRenderImage())
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));
125
126         if (inDocument() && !m_lowercasedUsemap.isNull())
127             document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
128
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();
135         else
136             m_lowercasedUsemap = nullAtom;
137
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;
147     } else {
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()) {
154                     if (willHaveName)
155                         document->addDocumentNamedItem(*id.impl(), *this);
156                     else
157                         document->removeDocumentNamedItem(*id.impl(), *this);
158                 }
159             }
160         }
161         HTMLElement::parseAttribute(name, value);
162     }
163 }
164
165 String HTMLImageElement::altText() const
166 {
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
172     if (alt.isNull())
173         alt = getAttribute(titleAttr);
174     return alt;
175 }
176
177 RenderPtr<RenderElement> HTMLImageElement::createElementRenderer(PassRef<RenderStyle> style)
178 {
179     if (style.get().hasContent())
180         return RenderElement::createFor(*this, std::move(style));
181
182     return createRenderer<RenderImage>(*this, std::move(style));
183 }
184
185 bool HTMLImageElement::canStartSelection() const
186 {
187     if (shadowRoot())
188         return HTMLElement::canStartSelection();
189
190     return false;
191 }
192
193 void HTMLImageElement::didAttachRenderers()
194 {
195     if (!renderer() || !renderer()->isRenderImage())
196         return;
197     if (m_imageLoader.hasPendingBeforeLoadEvent())
198         return;
199     RenderImage* renderImage = toRenderImage(renderer());
200     RenderImageResource& renderImageResource = renderImage->imageResource();
201     if (renderImageResource.hasImage())
202         return;
203     renderImageResource.setCachedImage(m_imageLoader.image());
204
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();
209 }
210
211 Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode& insertionPoint)
212 {
213     if (!m_form) { // m_form can be non-null if it was set in constructor.
214         m_form = HTMLFormElement::findClosestFormAncestor(*this);
215         if (m_form)
216             m_form->registerImgElement(this);
217     }
218
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);
222
223     if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
224         document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
225
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();
230
231     return insertNotificationRequest;
232 }
233
234 void HTMLImageElement::removedFrom(ContainerNode& insertionPoint)
235 {
236     if (m_form)
237         m_form->removeImgElement(this);
238
239     if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
240         document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
241
242     m_form = 0;
243     HTMLElement::removedFrom(insertionPoint);
244 }
245
246 int HTMLImageElement::width(bool ignorePendingStylesheets)
247 {
248     if (!renderer()) {
249         // check the attribute first for an explicit pixel value
250         bool ok;
251         int width = getAttribute(widthAttr).toInt(&ok);
252         if (ok)
253             return width;
254
255         // if the image is available, use its width
256         if (m_imageLoader.image())
257             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
258     }
259
260     if (ignorePendingStylesheets)
261         document().updateLayoutIgnorePendingStylesheets();
262     else
263         document().updateLayout();
264
265     RenderBox* box = renderBox();
266     return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), *box) : 0;
267 }
268
269 int HTMLImageElement::height(bool ignorePendingStylesheets)
270 {
271     if (!renderer()) {
272         // check the attribute first for an explicit pixel value
273         bool ok;
274         int height = getAttribute(heightAttr).toInt(&ok);
275         if (ok)
276             return height;
277
278         // if the image is available, use its height
279         if (m_imageLoader.image())
280             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
281     }
282
283     if (ignorePendingStylesheets)
284         document().updateLayoutIgnorePendingStylesheets();
285     else
286         document().updateLayout();
287
288     RenderBox* box = renderBox();
289     return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), *box) : 0;
290 }
291
292 int HTMLImageElement::naturalWidth() const
293 {
294     if (!m_imageLoader.image())
295         return 0;
296
297     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
298 }
299
300 int HTMLImageElement::naturalHeight() const
301 {
302     if (!m_imageLoader.image())
303         return 0;
304
305     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
306 }
307
308 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
309 {
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);
315 }
316
317 bool HTMLImageElement::matchesLowercasedUsemap(const AtomicStringImpl& name) const
318 {
319     ASSERT(String(&const_cast<AtomicStringImpl&>(name)).lower().impl() == &name);
320     return m_lowercasedUsemap.impl() == &name;
321 }
322
323 const AtomicString& HTMLImageElement::alt() const
324 {
325     return getAttribute(altAttr);
326 }
327
328 bool HTMLImageElement::draggable() const
329 {
330     // Image elements are draggable by default.
331     return !equalIgnoringCase(getAttribute(draggableAttr), "false");
332 }
333
334 void HTMLImageElement::setHeight(int value)
335 {
336     setIntegralAttribute(heightAttr, value);
337 }
338
339 URL HTMLImageElement::src() const
340 {
341     return document().completeURL(getAttribute(srcAttr));
342 }
343
344 void HTMLImageElement::setSrc(const String& value)
345 {
346     setAttribute(srcAttr, value);
347 }
348
349 void HTMLImageElement::setWidth(int value)
350 {
351     setIntegralAttribute(widthAttr, value);
352 }
353
354 int HTMLImageElement::x() const
355 {
356     auto renderer = this->renderer();
357     if (!renderer)
358         return 0;
359
360     // FIXME: This doesn't work correctly with transforms.
361     return renderer->localToAbsolute().x();
362 }
363
364 int HTMLImageElement::y() const
365 {
366     auto renderer = this->renderer();
367     if (!renderer)
368         return 0;
369
370     // FIXME: This doesn't work correctly with transforms.
371     return renderer->localToAbsolute().y();
372 }
373
374 bool HTMLImageElement::complete() const
375 {
376     return m_imageLoader.imageComplete();
377 }
378
379 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
380 {
381     HTMLElement::addSubresourceAttributeURLs(urls);
382
383     addSubresourceURL(urls, src());
384     // FIXME: What about when the usemap attribute begins with "#"?
385     addSubresourceURL(urls, document().completeURL(getAttribute(usemapAttr)));
386 }
387
388 void HTMLImageElement::didMoveToNewDocument(Document* oldDocument)
389 {
390     m_imageLoader.elementDidMoveToNewDocument();
391     HTMLElement::didMoveToNewDocument(oldDocument);
392 }
393
394 bool HTMLImageElement::isServerMap() const
395 {
396     if (!fastHasAttribute(ismapAttr))
397         return false;
398
399     const AtomicString& usemap = fastGetAttribute(usemapAttr);
400     
401     // If the usemap attribute starts with '#', it refers to a map element in the document.
402     if (usemap.string()[0] == '#')
403         return false;
404
405     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
406 }
407
408 #if PLATFORM(IOS)
409 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
410 bool HTMLImageElement::willRespondToMouseClickEvents()
411 {
412     auto renderer = this->renderer();
413     if (!renderer || renderer->style().touchCalloutEnabled())
414         return true;
415     return HTMLElement::willRespondToMouseClickEvents();
416 }
417 #endif
418
419 }