Use more references instead of pointers in DocumentOrderedMap
[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 "HTMLNames.h"
36 #include "HTMLParserIdioms.h"
37 #include "Page.h"
38 #include "RenderImage.h"
39
40 using namespace std;
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
47     : HTMLElement(tagName, document)
48     , m_imageLoader(this)
49     , m_form(form)
50     , m_compositeOperator(CompositeSourceOver)
51 {
52     ASSERT(hasTagName(imgTag));
53     setHasCustomStyleResolveCallbacks();
54     if (form)
55         form->registerImgElement(this);
56 }
57
58 PassRefPtr<HTMLImageElement> HTMLImageElement::create(Document& document)
59 {
60     return adoptRef(new HTMLImageElement(imgTag, document));
61 }
62
63 PassRefPtr<HTMLImageElement> HTMLImageElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
64 {
65     return adoptRef(new HTMLImageElement(tagName, document, form));
66 }
67
68 HTMLImageElement::~HTMLImageElement()
69 {
70     if (m_form)
71         m_form->removeImgElement(this);
72 }
73
74 PassRefPtr<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, const int* optionalWidth, const int* optionalHeight)
75 {
76     RefPtr<HTMLImageElement> image = adoptRef(new HTMLImageElement(imgTag, document));
77     if (optionalWidth)
78         image->setWidth(*optionalWidth);
79     if (optionalHeight)
80         image->setHeight(*optionalHeight);
81     return image.release();
82 }
83
84 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
85 {
86     if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
87         return true;
88     return HTMLElement::isPresentationAttribute(name);
89 }
90
91 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
92 {
93     if (name == widthAttr)
94         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
95     else if (name == heightAttr)
96         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
97     else if (name == borderAttr)
98         applyBorderAttributeToStyle(value, style);
99     else if (name == vspaceAttr) {
100         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
101         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
102     } else if (name == hspaceAttr) {
103         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
104         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
105     } else if (name == alignAttr)
106         applyAlignmentAttributeToStyle(value, style);
107     else if (name == valignAttr)
108         addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
109     else
110         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
111 }
112
113 const AtomicString& HTMLImageElement::imageSourceURL() const
114 {
115     return m_bestFitImageURL.isEmpty() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
116 }
117
118 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
119 {
120     if (name == altAttr) {
121         if (renderer() && renderer()->isImage())
122             toRenderImage(renderer())->updateAltText();
123     } else if (name == srcAttr || name == srcsetAttr) {
124         m_bestFitImageURL = bestFitSourceForImageAttributes(document().deviceScaleFactor(), fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr));
125         m_imageLoader.updateFromElementIgnoringPreviousError();
126     } else if (name == usemapAttr) {
127         setIsLink(!value.isNull() && !shouldProhibitLinks(this));
128
129         if (m_lowercasedUsemap == value)
130             return;
131
132         if (!m_lowercasedUsemap.isNull())
133             document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
134
135         // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning, which has to be stripped off.
136         // FIXME: We should check that the first character is '#'.
137         // FIXME: HTML5 specification says we should strip any leading string before '#'.
138         // FIXME: HTML5 specification says we should ignore usemap attributes without #.
139         if (value.length() > 1)
140             m_lowercasedUsemap = value.string().substring(1).lower();
141         else
142             m_lowercasedUsemap = nullAtom;
143
144         if (!m_lowercasedUsemap.isNull())
145             document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
146     } else if (name == onbeforeloadAttr)
147         setAttributeEventListener(eventNames().beforeloadEvent, name, value);
148     else if (name == compositeAttr) {
149         // FIXME: images don't support blend modes in their compositing attribute.
150         BlendMode blendOp = BlendModeNormal;
151         if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
152             m_compositeOperator = CompositeSourceOver;
153     } else {
154         if (name == nameAttr) {
155             bool willHaveName = !value.isNull();
156             if (hasName() != willHaveName && inDocument() && document().isHTMLDocument()) {
157                 HTMLDocument* document = toHTMLDocument(&this->document());
158                 const AtomicString& id = getIdAttribute();
159                 if (!id.isEmpty() && id != getNameAttribute()) {
160                     if (willHaveName)
161                         document->addDocumentNamedItem(*id.impl(), *this);
162                     else
163                         document->removeDocumentNamedItem(*id.impl(), *this);
164                 }
165             }
166         }
167         HTMLElement::parseAttribute(name, value);
168     }
169 }
170
171 String HTMLImageElement::altText() const
172 {
173     // lets figure out the alt text.. magic stuff
174     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
175     // also heavily discussed by Hixie on bugzilla
176     String alt = getAttribute(altAttr);
177     // fall back to title attribute
178     if (alt.isNull())
179         alt = getAttribute(titleAttr);
180     return alt;
181 }
182
183 RenderElement* HTMLImageElement::createRenderer(RenderArena& arena, RenderStyle& style)
184 {
185     if (style.hasContent())
186         return RenderElement::createFor(*this, style);
187
188     RenderImage* image = new (arena) RenderImage(this);
189     image->setImageResource(RenderImageResource::create());
190     return image;
191 }
192
193 bool HTMLImageElement::canStartSelection() const
194 {
195     if (shadowRoot())
196         return HTMLElement::canStartSelection();
197
198     return false;
199 }
200
201 void HTMLImageElement::didAttachRenderers()
202 {
203     if (!renderer() || !renderer()->isImage())
204         return;
205     if (m_imageLoader.hasPendingBeforeLoadEvent())
206         return;
207     RenderImage* renderImage = toRenderImage(renderer());
208     RenderImageResource* renderImageResource = renderImage->imageResource();
209     if (renderImageResource->hasImage())
210         return;
211     renderImageResource->setCachedImage(m_imageLoader.image());
212
213     // If we have no image at all because we have no src attribute, set
214     // image height and width for the alt text instead.
215     if (!m_imageLoader.image() && !renderImageResource->cachedImage())
216         renderImage->setImageSizeForAltText();
217 }
218
219 Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode& insertionPoint)
220 {
221     if (!m_form) { // m_form can be non-null if it was set in constructor.
222         m_form = HTMLFormElement::findClosestFormAncestor(*this);
223         if (m_form)
224             m_form->registerImgElement(this);
225     }
226
227     if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
228         document().addImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
229
230     // If we have been inserted from a renderer-less document,
231     // our loader may have not fetched the image, so do it now.
232     if (insertionPoint.inDocument() && !m_imageLoader.image())
233         m_imageLoader.updateFromElement();
234
235     return HTMLElement::insertedInto(insertionPoint);
236 }
237
238 void HTMLImageElement::removedFrom(ContainerNode& insertionPoint)
239 {
240     if (m_form)
241         m_form->removeImgElement(this);
242
243     if (insertionPoint.inDocument() && !m_lowercasedUsemap.isNull())
244         document().removeImageElementByLowercasedUsemap(*m_lowercasedUsemap.impl(), *this);
245
246     m_form = 0;
247     HTMLElement::removedFrom(insertionPoint);
248 }
249
250 int HTMLImageElement::width(bool ignorePendingStylesheets)
251 {
252     if (!renderer()) {
253         // check the attribute first for an explicit pixel value
254         bool ok;
255         int width = getAttribute(widthAttr).toInt(&ok);
256         if (ok)
257             return width;
258
259         // if the image is available, use its width
260         if (m_imageLoader.image())
261             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
262     }
263
264     if (ignorePendingStylesheets)
265         document().updateLayoutIgnorePendingStylesheets();
266     else
267         document().updateLayout();
268
269     RenderBox* box = renderBox();
270     return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), box) : 0;
271 }
272
273 int HTMLImageElement::height(bool ignorePendingStylesheets)
274 {
275     if (!renderer()) {
276         // check the attribute first for an explicit pixel value
277         bool ok;
278         int height = getAttribute(heightAttr).toInt(&ok);
279         if (ok)
280             return height;
281
282         // if the image is available, use its height
283         if (m_imageLoader.image())
284             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
285     }
286
287     if (ignorePendingStylesheets)
288         document().updateLayoutIgnorePendingStylesheets();
289     else
290         document().updateLayout();
291
292     RenderBox* box = renderBox();
293     return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), box) : 0;
294 }
295
296 int HTMLImageElement::naturalWidth() const
297 {
298     if (!m_imageLoader.image())
299         return 0;
300
301     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
302 }
303
304 int HTMLImageElement::naturalHeight() const
305 {
306     if (!m_imageLoader.image())
307         return 0;
308
309     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
310 }
311
312 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
313 {
314     return attribute.name() == srcAttr
315         || attribute.name() == lowsrcAttr
316         || attribute.name() == longdescAttr
317         || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
318         || HTMLElement::isURLAttribute(attribute);
319 }
320
321 bool HTMLImageElement::matchesLowercasedUsemap(const AtomicStringImpl& name) const
322 {
323     ASSERT(const_cast<AtomicStringImpl&>(name).lower() == &name);
324     return m_lowercasedUsemap.impl() == &name;
325 }
326
327 const AtomicString& HTMLImageElement::alt() const
328 {
329     return getAttribute(altAttr);
330 }
331
332 bool HTMLImageElement::draggable() const
333 {
334     // Image elements are draggable by default.
335     return !equalIgnoringCase(getAttribute(draggableAttr), "false");
336 }
337
338 void HTMLImageElement::setHeight(int value)
339 {
340     setAttribute(heightAttr, String::number(value));
341 }
342
343 URL HTMLImageElement::src() const
344 {
345     return document().completeURL(getAttribute(srcAttr));
346 }
347
348 void HTMLImageElement::setSrc(const String& value)
349 {
350     setAttribute(srcAttr, value);
351 }
352
353 void HTMLImageElement::setWidth(int value)
354 {
355     setAttribute(widthAttr, String::number(value));
356 }
357
358 int HTMLImageElement::x() const
359 {
360     auto renderer = this->renderer();
361     if (!renderer)
362         return 0;
363
364     // FIXME: This doesn't work correctly with transforms.
365     return renderer->localToAbsolute().x();
366 }
367
368 int HTMLImageElement::y() const
369 {
370     auto renderer = this->renderer();
371     if (!renderer)
372         return 0;
373
374     // FIXME: This doesn't work correctly with transforms.
375     return renderer->localToAbsolute().y();
376 }
377
378 bool HTMLImageElement::complete() const
379 {
380     return m_imageLoader.imageComplete();
381 }
382
383 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
384 {
385     HTMLElement::addSubresourceAttributeURLs(urls);
386
387     addSubresourceURL(urls, src());
388     // FIXME: What about when the usemap attribute begins with "#"?
389     addSubresourceURL(urls, document().completeURL(getAttribute(usemapAttr)));
390 }
391
392 void HTMLImageElement::didMoveToNewDocument(Document* oldDocument)
393 {
394     m_imageLoader.elementDidMoveToNewDocument();
395     HTMLElement::didMoveToNewDocument(oldDocument);
396 }
397
398 bool HTMLImageElement::isServerMap() const
399 {
400     if (!fastHasAttribute(ismapAttr))
401         return false;
402
403     const AtomicString& usemap = fastGetAttribute(usemapAttr);
404     
405     // If the usemap attribute starts with '#', it refers to a map element in the document.
406     if (usemap.string()[0] == '#')
407         return false;
408
409     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
410 }
411
412 #if PLATFORM(IOS)
413 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
414 bool HTMLImageElement::willRespondToMouseClickEvents()
415 {
416     auto renderer = this->renderer();
417     RenderStyle* style = renderer ? renderer->style() : nullptr;
418     if (!style || style->touchCalloutEnabled())
419         return true;
420     return HTMLElement::willRespondToMouseClickEvents();
421 }
422 #endif
423
424 }