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 "core/html/HTMLImageElement.h"
25
26 #include "CSSPropertyNames.h"
27 #include "HTMLNames.h"
28 #include "RuntimeEnabledFeatures.h"
29 #include "bindings/v8/ScriptEventListener.h"
30 #include "core/dom/Attribute.h"
31 #include "core/events/ThreadLocalEventNames.h"
32 #include "core/fetch/ImageResource.h"
33 #include "core/html/HTMLAnchorElement.h"
34 #include "core/html/HTMLFormElement.h"
35 #include "core/html/parser/HTMLParserIdioms.h"
36 #include "core/html/parser/HTMLSrcsetParser.h"
37 #include "core/rendering/RenderImage.h"
38
39 using namespace std;
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
HTMLImageElement(Document & document,HTMLFormElement * form)45 HTMLImageElement::HTMLImageElement(Document& document, HTMLFormElement* form)
46 : HTMLElement(imgTag, document)
47 , m_imageLoader(this)
48 , m_form(form)
49 , m_compositeOperator(CompositeSourceOver)
50 , m_imageDevicePixelRatio(1.0f)
51 {
52 ScriptWrappable::init(this);
53 if (form)
54 form->registerImgElement(this);
55 }
56
create(Document & document)57 PassRefPtr<HTMLImageElement> HTMLImageElement::create(Document& document)
58 {
59 return adoptRef(new HTMLImageElement(document));
60 }
61
create(Document & document,HTMLFormElement * form)62 PassRefPtr<HTMLImageElement> HTMLImageElement::create(Document& document, HTMLFormElement* form)
63 {
64 return adoptRef(new HTMLImageElement(document, form));
65 }
66
~HTMLImageElement()67 HTMLImageElement::~HTMLImageElement()
68 {
69 if (m_form)
70 m_form->removeImgElement(this);
71 }
72
createForJSConstructor(Document & document,int width,int height)73 PassRefPtr<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, int width, int height)
74 {
75 RefPtr<HTMLImageElement> image = adoptRef(new HTMLImageElement(document));
76 if (width)
77 image->setWidth(width);
78 if (height)
79 image->setHeight(height);
80 return image.release();
81 }
82
isPresentationAttribute(const QualifiedName & name) const83 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
84 {
85 if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
86 return true;
87 return HTMLElement::isPresentationAttribute(name);
88 }
89
collectStyleForPresentationAttribute(const QualifiedName & name,const AtomicString & value,MutableStylePropertySet * style)90 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
91 {
92 if (name == widthAttr)
93 addHTMLLengthToStyle(style, CSSPropertyWidth, value);
94 else if (name == heightAttr)
95 addHTMLLengthToStyle(style, CSSPropertyHeight, value);
96 else if (name == borderAttr)
97 applyBorderAttributeToStyle(value, style);
98 else if (name == vspaceAttr) {
99 addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
100 addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
101 } else if (name == hspaceAttr) {
102 addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
103 addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
104 } else if (name == alignAttr)
105 applyAlignmentAttributeToStyle(value, style);
106 else if (name == valignAttr)
107 addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
108 else
109 HTMLElement::collectStyleForPresentationAttribute(name, value, style);
110 }
111
imageSourceURL() const112 const AtomicString HTMLImageElement::imageSourceURL() const
113 {
114 return m_bestFitImageURL.isNull() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
115 }
116
formOwner() const117 HTMLFormElement* HTMLImageElement::formOwner() const
118 {
119 return findFormAncestor();
120 }
121
parseAttribute(const QualifiedName & name,const AtomicString & value)122 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
123 {
124 if (name == altAttr) {
125 if (renderer() && renderer()->isImage())
126 toRenderImage(renderer())->updateAltText();
127 } else if (name == srcAttr || name == srcsetAttr) {
128 if (RuntimeEnabledFeatures::srcsetEnabled()) {
129 ImageCandidate candidate = bestFitSourceForImageAttributes(document().devicePixelRatio(), fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr));
130 m_bestFitImageURL = candidate.toString();
131 float candidateScaleFactor = candidate.scaleFactor();
132 if (candidateScaleFactor > 0)
133 m_imageDevicePixelRatio = 1 / candidateScaleFactor;
134 if (renderer() && renderer()->isImage())
135 toRenderImage(renderer())->setImageDevicePixelRatio(m_imageDevicePixelRatio);
136 }
137 m_imageLoader.updateFromElementIgnoringPreviousError();
138 }
139 else if (name == usemapAttr)
140 setIsLink(!value.isNull());
141 else if (name == onbeforeloadAttr)
142 setAttributeEventListener(EventTypeNames::beforeload, createAttributeEventListener(this, name, value));
143 else if (name == compositeAttr) {
144 // FIXME: images don't support blend modes in their compositing attribute.
145 blink::WebBlendMode blendOp = blink::WebBlendModeNormal;
146 if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
147 m_compositeOperator = CompositeSourceOver;
148 } else
149 HTMLElement::parseAttribute(name, value);
150 }
151
altText() const152 const AtomicString& HTMLImageElement::altText() const
153 {
154 // lets figure out the alt text.. magic stuff
155 // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
156 // also heavily discussed by Hixie on bugzilla
157 if (!getAttribute(altAttr).isNull())
158 return getAttribute(altAttr);
159 // fall back to title attribute
160 return getAttribute(titleAttr);
161 }
162
createRenderer(RenderStyle * style)163 RenderObject* HTMLImageElement::createRenderer(RenderStyle* style)
164 {
165 if (style->hasContent())
166 return RenderObject::createObject(this, style);
167
168 RenderImage* image = new RenderImage(this);
169 image->setImageResource(RenderImageResource::create());
170 image->setImageDevicePixelRatio(m_imageDevicePixelRatio);
171 return image;
172 }
173
canStartSelection() const174 bool HTMLImageElement::canStartSelection() const
175 {
176 if (shadow())
177 return HTMLElement::canStartSelection();
178
179 return false;
180 }
181
attach(const AttachContext & context)182 void HTMLImageElement::attach(const AttachContext& context)
183 {
184 HTMLElement::attach(context);
185
186 if (renderer() && renderer()->isImage() && !m_imageLoader.hasPendingBeforeLoadEvent()) {
187 RenderImage* renderImage = toRenderImage(renderer());
188 RenderImageResource* renderImageResource = renderImage->imageResource();
189 if (renderImageResource->hasImage())
190 return;
191
192 // If we have no image at all because we have no src attribute, set
193 // image height and width for the alt text instead.
194 if (!m_imageLoader.image() && !renderImageResource->cachedImage())
195 renderImage->setImageSizeForAltText();
196 else
197 renderImageResource->setImageResource(m_imageLoader.image());
198
199 }
200 }
201
insertedInto(ContainerNode * insertionPoint)202 Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode* insertionPoint)
203 {
204 // m_form can be non-null if it was set in constructor.
205 if (m_form && insertionPoint->highestAncestor() != m_form->highestAncestor()) {
206 m_form->removeImgElement(this);
207 m_form = 0;
208 }
209
210 if (!m_form) {
211 m_form = findFormAncestor();
212 if (m_form)
213 m_form->registerImgElement(this);
214 }
215
216 // If we have been inserted from a renderer-less document,
217 // our loader may have not fetched the image, so do it now.
218 if (insertionPoint->inDocument() && !m_imageLoader.image())
219 m_imageLoader.updateFromElement();
220
221 return HTMLElement::insertedInto(insertionPoint);
222 }
223
removedFrom(ContainerNode * insertionPoint)224 void HTMLImageElement::removedFrom(ContainerNode* insertionPoint)
225 {
226 if (m_form)
227 m_form->removeImgElement(this);
228 m_form = 0;
229 HTMLElement::removedFrom(insertionPoint);
230 }
231
width(bool ignorePendingStylesheets)232 int HTMLImageElement::width(bool ignorePendingStylesheets)
233 {
234 if (!renderer()) {
235 // check the attribute first for an explicit pixel value
236 bool ok;
237 int width = getAttribute(widthAttr).toInt(&ok);
238 if (ok)
239 return width;
240
241 // if the image is available, use its width
242 if (m_imageLoader.image())
243 return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
244 }
245
246 if (ignorePendingStylesheets)
247 document().updateLayoutIgnorePendingStylesheets();
248 else
249 document().updateLayout();
250
251 RenderBox* box = renderBox();
252 return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), box) : 0;
253 }
254
height(bool ignorePendingStylesheets)255 int HTMLImageElement::height(bool ignorePendingStylesheets)
256 {
257 if (!renderer()) {
258 // check the attribute first for an explicit pixel value
259 bool ok;
260 int height = getAttribute(heightAttr).toInt(&ok);
261 if (ok)
262 return height;
263
264 // if the image is available, use its height
265 if (m_imageLoader.image())
266 return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
267 }
268
269 if (ignorePendingStylesheets)
270 document().updateLayoutIgnorePendingStylesheets();
271 else
272 document().updateLayout();
273
274 RenderBox* box = renderBox();
275 return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), box) : 0;
276 }
277
naturalWidth() const278 int HTMLImageElement::naturalWidth() const
279 {
280 if (!m_imageLoader.image())
281 return 0;
282
283 return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).width();
284 }
285
naturalHeight() const286 int HTMLImageElement::naturalHeight() const
287 {
288 if (!m_imageLoader.image())
289 return 0;
290
291 return m_imageLoader.image()->imageSizeForRenderer(renderer(), 1.0f).height();
292 }
293
isURLAttribute(const Attribute & attribute) const294 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
295 {
296 return attribute.name() == srcAttr
297 || attribute.name() == lowsrcAttr
298 || attribute.name() == longdescAttr
299 || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
300 || HTMLElement::isURLAttribute(attribute);
301 }
302
alt() const303 const AtomicString& HTMLImageElement::alt() const
304 {
305 return getAttribute(altAttr);
306 }
307
draggable() const308 bool HTMLImageElement::draggable() const
309 {
310 // Image elements are draggable by default.
311 return !equalIgnoringCase(getAttribute(draggableAttr), "false");
312 }
313
setHeight(int value)314 void HTMLImageElement::setHeight(int value)
315 {
316 setIntegralAttribute(heightAttr, value);
317 }
318
src() const319 KURL HTMLImageElement::src() const
320 {
321 return document().completeURL(getAttribute(srcAttr));
322 }
323
setSrc(const String & value)324 void HTMLImageElement::setSrc(const String& value)
325 {
326 setAttribute(srcAttr, AtomicString(value));
327 }
328
setWidth(int value)329 void HTMLImageElement::setWidth(int value)
330 {
331 setIntegralAttribute(widthAttr, value);
332 }
333
x() const334 int HTMLImageElement::x() const
335 {
336 RenderObject* r = renderer();
337 if (!r)
338 return 0;
339
340 // FIXME: This doesn't work correctly with transforms.
341 FloatPoint absPos = r->localToAbsolute();
342 return absPos.x();
343 }
344
y() const345 int HTMLImageElement::y() const
346 {
347 RenderObject* r = renderer();
348 if (!r)
349 return 0;
350
351 // FIXME: This doesn't work correctly with transforms.
352 FloatPoint absPos = r->localToAbsolute();
353 return absPos.y();
354 }
355
complete() const356 bool HTMLImageElement::complete() const
357 {
358 return m_imageLoader.imageComplete();
359 }
360
addSubresourceAttributeURLs(ListHashSet<KURL> & urls) const361 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
362 {
363 HTMLElement::addSubresourceAttributeURLs(urls);
364
365 addSubresourceURL(urls, src());
366 // FIXME: What about when the usemap attribute begins with "#"?
367 addSubresourceURL(urls, document().completeURL(getAttribute(usemapAttr)));
368 }
369
didMoveToNewDocument(Document & oldDocument)370 void HTMLImageElement::didMoveToNewDocument(Document& oldDocument)
371 {
372 m_imageLoader.elementDidMoveToNewDocument();
373 HTMLElement::didMoveToNewDocument(oldDocument);
374 }
375
isServerMap() const376 bool HTMLImageElement::isServerMap() const
377 {
378 if (!fastHasAttribute(ismapAttr))
379 return false;
380
381 const AtomicString& usemap = fastGetAttribute(usemapAttr);
382
383 // If the usemap attribute starts with '#', it refers to a map element in the document.
384 if (usemap.string()[0] == '#')
385 return false;
386
387 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
388 }
389
imageContents()390 Image* HTMLImageElement::imageContents()
391 {
392 if (!m_imageLoader.imageComplete())
393 return 0;
394
395 return m_imageLoader.image()->image();
396 }
397
isInteractiveContent() const398 bool HTMLImageElement::isInteractiveContent() const
399 {
400 return fastHasAttribute(usemapAttr);
401 }
402
403 }
404