[iOS] Upstream -webkit-touch-callout
[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     else if (name == onbeforeloadAttr)
129         setAttributeEventListener(eventNames().beforeloadEvent, name, value);
130     else if (name == compositeAttr) {
131         // FIXME: images don't support blend modes in their compositing attribute.
132         BlendMode blendOp = BlendModeNormal;
133         if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
134             m_compositeOperator = CompositeSourceOver;
135     } else {
136         if (name == nameAttr) {
137             bool willHaveName = !value.isNull();
138             if (hasName() != willHaveName && inDocument() && document().isHTMLDocument()) {
139                 HTMLDocument* document = toHTMLDocument(&this->document());
140                 const AtomicString& id = getIdAttribute();
141                 if (!id.isEmpty() && id != getNameAttribute()) {
142                     if (willHaveName)
143                         document->addDocumentNamedItem(id, this);
144                     else
145                         document->removeDocumentNamedItem(id, this);
146                 }
147             }
148         }
149         HTMLElement::parseAttribute(name, value);
150     }
151 }
152
153 String HTMLImageElement::altText() const
154 {
155     // lets figure out the alt text.. magic stuff
156     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
157     // also heavily discussed by Hixie on bugzilla
158     String alt = getAttribute(altAttr);
159     // fall back to title attribute
160     if (alt.isNull())
161         alt = getAttribute(titleAttr);
162     return alt;
163 }
164
165 RenderElement* HTMLImageElement::createRenderer(RenderArena& arena, RenderStyle& style)
166 {
167     if (style.hasContent())
168         return RenderElement::createFor(*this, style);
169
170     RenderImage* image = new (arena) RenderImage(this);
171     image->setImageResource(RenderImageResource::create());
172     return image;
173 }
174
175 bool HTMLImageElement::canStartSelection() const
176 {
177     if (shadowRoot())
178         return HTMLElement::canStartSelection();
179
180     return false;
181 }
182
183 void HTMLImageElement::didAttachRenderers()
184 {
185     if (!renderer() || !renderer()->isImage())
186         return;
187     if (m_imageLoader.hasPendingBeforeLoadEvent())
188         return;
189     RenderImage* renderImage = toRenderImage(renderer());
190     RenderImageResource* renderImageResource = renderImage->imageResource();
191     if (renderImageResource->hasImage())
192         return;
193     renderImageResource->setCachedImage(m_imageLoader.image());
194
195     // If we have no image at all because we have no src attribute, set
196     // image height and width for the alt text instead.
197     if (!m_imageLoader.image() && !renderImageResource->cachedImage())
198         renderImage->setImageSizeForAltText();
199 }
200
201 Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode* insertionPoint)
202 {
203     if (!m_form) { // m_form can be non-null if it was set in constructor.
204         m_form = HTMLFormElement::findClosestFormAncestor(*this);
205         if (m_form)
206             m_form->registerImgElement(this);
207     }
208
209     // If we have been inserted from a renderer-less document,
210     // our loader may have not fetched the image, so do it now.
211     if (insertionPoint->inDocument() && !m_imageLoader.image())
212         m_imageLoader.updateFromElement();
213
214     return HTMLElement::insertedInto(insertionPoint);
215 }
216
217 void HTMLImageElement::removedFrom(ContainerNode* insertionPoint)
218 {
219     if (m_form)
220         m_form->removeImgElement(this);
221     m_form = 0;
222     HTMLElement::removedFrom(insertionPoint);
223 }
224
225 int HTMLImageElement::width(bool ignorePendingStylesheets)
226 {
227     if (!renderer()) {
228         // check the attribute first for an explicit pixel value
229         bool ok;
230         int width = getAttribute(widthAttr).toInt(&ok);
231         if (ok)
232             return width;
233
234         // if the image is available, use its width
235         if (m_imageLoader.image())
236             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
237     }
238
239     if (ignorePendingStylesheets)
240         document().updateLayoutIgnorePendingStylesheets();
241     else
242         document().updateLayout();
243
244     RenderBox* box = renderBox();
245     return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), box) : 0;
246 }
247
248 int HTMLImageElement::height(bool ignorePendingStylesheets)
249 {
250     if (!renderer()) {
251         // check the attribute first for an explicit pixel value
252         bool ok;
253         int height = getAttribute(heightAttr).toInt(&ok);
254         if (ok)
255             return height;
256
257         // if the image is available, use its height
258         if (m_imageLoader.image())
259             return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
260     }
261
262     if (ignorePendingStylesheets)
263         document().updateLayoutIgnorePendingStylesheets();
264     else
265         document().updateLayout();
266
267     RenderBox* box = renderBox();
268     return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), box) : 0;
269 }
270
271 int HTMLImageElement::naturalWidth() const
272 {
273     if (!m_imageLoader.image())
274         return 0;
275
276     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
277 }
278
279 int HTMLImageElement::naturalHeight() const
280 {
281     if (!m_imageLoader.image())
282         return 0;
283
284     return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
285 }
286
287 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
288 {
289     return attribute.name() == srcAttr
290         || attribute.name() == lowsrcAttr
291         || attribute.name() == longdescAttr
292         || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
293         || HTMLElement::isURLAttribute(attribute);
294 }
295
296 const AtomicString& HTMLImageElement::alt() const
297 {
298     return getAttribute(altAttr);
299 }
300
301 bool HTMLImageElement::draggable() const
302 {
303     // Image elements are draggable by default.
304     return !equalIgnoringCase(getAttribute(draggableAttr), "false");
305 }
306
307 void HTMLImageElement::setHeight(int value)
308 {
309     setAttribute(heightAttr, String::number(value));
310 }
311
312 KURL HTMLImageElement::src() const
313 {
314     return document().completeURL(getAttribute(srcAttr));
315 }
316
317 void HTMLImageElement::setSrc(const String& value)
318 {
319     setAttribute(srcAttr, value);
320 }
321
322 void HTMLImageElement::setWidth(int value)
323 {
324     setAttribute(widthAttr, String::number(value));
325 }
326
327 int HTMLImageElement::x() const
328 {
329     RenderObject* r = renderer();
330     if (!r)
331         return 0;
332
333     // FIXME: This doesn't work correctly with transforms.
334     FloatPoint absPos = r->localToAbsolute();
335     return absPos.x();
336 }
337
338 int HTMLImageElement::y() const
339 {
340     RenderObject* r = renderer();
341     if (!r)
342         return 0;
343
344     // FIXME: This doesn't work correctly with transforms.
345     FloatPoint absPos = r->localToAbsolute();
346     return absPos.y();
347 }
348
349 bool HTMLImageElement::complete() const
350 {
351     return m_imageLoader.imageComplete();
352 }
353
354 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
355 {
356     HTMLElement::addSubresourceAttributeURLs(urls);
357
358     addSubresourceURL(urls, src());
359     // FIXME: What about when the usemap attribute begins with "#"?
360     addSubresourceURL(urls, document().completeURL(getAttribute(usemapAttr)));
361 }
362
363 void HTMLImageElement::didMoveToNewDocument(Document* oldDocument)
364 {
365     m_imageLoader.elementDidMoveToNewDocument();
366     HTMLElement::didMoveToNewDocument(oldDocument);
367 }
368
369 bool HTMLImageElement::isServerMap() const
370 {
371     if (!fastHasAttribute(ismapAttr))
372         return false;
373
374     const AtomicString& usemap = fastGetAttribute(usemapAttr);
375     
376     // If the usemap attribute starts with '#', it refers to a map element in the document.
377     if (usemap.string()[0] == '#')
378         return false;
379
380     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
381 }
382
383 #if PLATFORM(IOS)
384 // FIXME: This is a workaround for <rdar://problem/7725158>. We should find a better place for the touchCalloutEnabled() logic.
385 bool HTMLImageElement::willRespondToMouseClickEvents()
386 {
387     RenderObject* renderer = this->renderer();
388     RenderStyle* style = renderer ? renderer->style() : nullptr;
389     if (!style || style->touchCalloutEnabled())
390         return true;
391     return HTMLElement::willRespondToMouseClickEvents();
392 }
393 #endif
394
395 }