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 "bindings/core/v8/ScriptEventListener.h"
27#include "core/CSSPropertyNames.h"
28#include "core/HTMLNames.h"
29#include "core/MediaTypeNames.h"
30#include "core/css/MediaQueryMatcher.h"
31#include "core/css/MediaValuesDynamic.h"
32#include "core/css/parser/SizesAttributeParser.h"
33#include "core/dom/Attribute.h"
34#include "core/dom/NodeTraversal.h"
35#include "core/fetch/ImageResource.h"
36#include "core/frame/UseCounter.h"
37#include "core/html/HTMLAnchorElement.h"
38#include "core/html/HTMLCanvasElement.h"
39#include "core/html/HTMLFormElement.h"
40#include "core/html/HTMLSourceElement.h"
41#include "core/html/canvas/CanvasRenderingContext.h"
42#include "core/html/parser/HTMLParserIdioms.h"
43#include "core/html/parser/HTMLSrcsetParser.h"
44#include "core/inspector/ConsoleMessage.h"
45#include "core/rendering/RenderImage.h"
46#include "platform/MIMETypeRegistry.h"
47#include "platform/RuntimeEnabledFeatures.h"
48
49namespace blink {
50
51using namespace HTMLNames;
52
53class HTMLImageElement::ViewportChangeListener FINAL : public MediaQueryListListener {
54public:
55    static RefPtrWillBeRawPtr<ViewportChangeListener> create(HTMLImageElement* element)
56    {
57        return adoptRefWillBeNoop(new ViewportChangeListener(element));
58    }
59
60    virtual void notifyMediaQueryChanged() OVERRIDE
61    {
62        if (m_element)
63            m_element->notifyViewportChanged();
64    }
65
66#if !ENABLE(OILPAN)
67    void clearElement() { m_element = nullptr; }
68#endif
69    virtual void trace(Visitor* visitor) OVERRIDE
70    {
71        visitor->trace(m_element);
72        MediaQueryListListener::trace(visitor);
73    }
74private:
75    explicit ViewportChangeListener(HTMLImageElement* element) : m_element(element) { }
76    RawPtrWillBeMember<HTMLImageElement> m_element;
77};
78
79HTMLImageElement::HTMLImageElement(Document& document, HTMLFormElement* form, bool createdByParser)
80    : HTMLElement(imgTag, document)
81    , m_imageLoader(HTMLImageLoader::create(this))
82    , m_compositeOperator(CompositeSourceOver)
83    , m_imageDevicePixelRatio(1.0f)
84    , m_formWasSetByParser(false)
85    , m_elementCreatedByParser(createdByParser)
86    , m_intrinsicSizingViewportDependant(false)
87    , m_effectiveSizeViewportDependant(false)
88{
89    if (form && form->inDocument()) {
90#if ENABLE(OILPAN)
91        m_form = form;
92#else
93        m_form = form->createWeakPtr();
94#endif
95        m_formWasSetByParser = true;
96        m_form->associate(*this);
97        m_form->didAssociateByParser();
98    }
99}
100
101PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::create(Document& document)
102{
103    return adoptRefWillBeNoop(new HTMLImageElement(document));
104}
105
106PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::create(Document& document, HTMLFormElement* form, bool createdByParser)
107{
108    return adoptRefWillBeNoop(new HTMLImageElement(document, form, createdByParser));
109}
110
111HTMLImageElement::~HTMLImageElement()
112{
113#if !ENABLE(OILPAN)
114    if (m_listener) {
115        document().mediaQueryMatcher().removeViewportListener(m_listener.get());
116        m_listener->clearElement();
117    }
118    if (m_form)
119        m_form->disassociate(*this);
120#endif
121}
122
123void HTMLImageElement::trace(Visitor* visitor)
124{
125    visitor->trace(m_imageLoader);
126    visitor->trace(m_listener);
127    visitor->trace(m_form);
128    HTMLElement::trace(visitor);
129}
130
131void HTMLImageElement::notifyViewportChanged()
132{
133    // Re-selecting the source URL in order to pick a more fitting resource
134    // And update the image's intrinsic dimensions when the viewport changes.
135    // Picking of a better fitting resource is UA dependant, not spec required.
136    selectSourceURL(ImageLoader::UpdateSizeChanged);
137}
138
139PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, int width, int height)
140{
141    RefPtrWillBeRawPtr<HTMLImageElement> image = adoptRefWillBeNoop(new HTMLImageElement(document));
142    if (width)
143        image->setWidth(width);
144    if (height)
145        image->setHeight(height);
146    image->m_elementCreatedByParser = false;
147    return image.release();
148}
149
150bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
151{
152    if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
153        return true;
154    return HTMLElement::isPresentationAttribute(name);
155}
156
157void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
158{
159    if (name == widthAttr)
160        addHTMLLengthToStyle(style, CSSPropertyWidth, value);
161    else if (name == heightAttr)
162        addHTMLLengthToStyle(style, CSSPropertyHeight, value);
163    else if (name == borderAttr)
164        applyBorderAttributeToStyle(value, style);
165    else if (name == vspaceAttr) {
166        addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
167        addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
168    } else if (name == hspaceAttr) {
169        addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
170        addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
171    } else if (name == alignAttr)
172        applyAlignmentAttributeToStyle(value, style);
173    else if (name == valignAttr)
174        addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
175    else
176        HTMLElement::collectStyleForPresentationAttribute(name, value, style);
177}
178
179const AtomicString HTMLImageElement::imageSourceURL() const
180{
181    return m_bestFitImageURL.isNull() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
182}
183
184HTMLFormElement* HTMLImageElement::formOwner() const
185{
186    return m_form.get();
187}
188
189void HTMLImageElement::formRemovedFromTree(const Node& formRoot)
190{
191    ASSERT(m_form);
192    if (NodeTraversal::highestAncestorOrSelf(*this) != formRoot)
193        resetFormOwner();
194}
195
196void HTMLImageElement::resetFormOwner()
197{
198    m_formWasSetByParser = false;
199    HTMLFormElement* nearestForm = findFormAncestor();
200    if (m_form) {
201        if (nearestForm == m_form.get())
202            return;
203        m_form->disassociate(*this);
204    }
205    if (nearestForm) {
206#if ENABLE(OILPAN)
207        m_form = nearestForm;
208#else
209        m_form = nearestForm->createWeakPtr();
210#endif
211        m_form->associate(*this);
212    } else {
213#if ENABLE(OILPAN)
214        m_form = nullptr;
215#else
216        m_form = WeakPtr<HTMLFormElement>();
217#endif
218    }
219}
220
221void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate)
222{
223    m_bestFitImageURL = candidate.url();
224    float candidateDensity = candidate.density();
225    if (candidateDensity >= 0)
226        m_imageDevicePixelRatio = 1.0 / candidateDensity;
227    if (candidate.resourceWidth() > 0) {
228        m_intrinsicSizingViewportDependant = true;
229        UseCounter::count(document(), UseCounter::SrcsetWDescriptor);
230    } else if (!candidate.srcOrigin()) {
231        UseCounter::count(document(), UseCounter::SrcsetXDescriptor);
232    }
233    if (renderer() && renderer()->isImage())
234        toRenderImage(renderer())->setImageDevicePixelRatio(m_imageDevicePixelRatio);
235}
236
237void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
238{
239    if (name == altAttr) {
240        if (renderer() && renderer()->isImage())
241            toRenderImage(renderer())->updateAltText();
242    } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr) {
243        selectSourceURL(ImageLoader::UpdateIgnorePreviousError);
244    } else if (name == usemapAttr) {
245        setIsLink(!value.isNull());
246    } else if (name == compositeAttr) {
247        blink::WebBlendMode blendOp = blink::WebBlendModeNormal;
248        if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
249            m_compositeOperator = CompositeSourceOver;
250        else if (m_compositeOperator != CompositeSourceOver)
251            UseCounter::count(document(), UseCounter::HTMLImageElementComposite);
252    } else {
253        HTMLElement::parseAttribute(name, value);
254    }
255}
256
257const AtomicString& HTMLImageElement::altText() const
258{
259    // lets figure out the alt text.. magic stuff
260    // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
261    // also heavily discussed by Hixie on bugzilla
262    const AtomicString& alt = fastGetAttribute(altAttr);
263    if (!alt.isNull())
264        return alt;
265    // fall back to title attribute
266    return fastGetAttribute(titleAttr);
267}
268
269static bool supportedImageType(const String& type)
270{
271    return MIMETypeRegistry::isSupportedImagePrefixedMIMEType(type);
272}
273
274// http://picture.responsiveimages.org/#update-source-set
275ImageCandidate HTMLImageElement::findBestFitImageFromPictureParent()
276{
277    ASSERT(isMainThread());
278    Node* parent = parentNode();
279    if (!parent || !isHTMLPictureElement(*parent))
280        return ImageCandidate();
281    for (Node* child = parent->firstChild(); child; child = child->nextSibling()) {
282        if (child == this)
283            return ImageCandidate();
284
285        if (!isHTMLSourceElement(*child))
286            continue;
287
288        HTMLSourceElement* source = toHTMLSourceElement(child);
289        if (!source->fastGetAttribute(srcAttr).isNull())
290            UseCounter::countDeprecation(document(), UseCounter::PictureSourceSrc);
291        String srcset = source->fastGetAttribute(srcsetAttr);
292        if (srcset.isEmpty())
293            continue;
294        String type = source->fastGetAttribute(typeAttr);
295        if (!type.isEmpty() && !supportedImageType(type))
296            continue;
297
298        if (!source->mediaQueryMatches())
299            continue;
300
301        String sizes = source->fastGetAttribute(sizesAttr);
302        if (!sizes.isNull())
303            UseCounter::count(document(), UseCounter::Sizes);
304        SizesAttributeParser parser = SizesAttributeParser(MediaValuesDynamic::create(document()), sizes);
305        unsigned effectiveSize = parser.length();
306        m_effectiveSizeViewportDependant = parser.viewportDependant();
307        ImageCandidate candidate = bestFitSourceForSrcsetAttribute(document().devicePixelRatio(), effectiveSize, source->fastGetAttribute(srcsetAttr));
308        if (candidate.isEmpty())
309            continue;
310        return candidate;
311    }
312    return ImageCandidate();
313}
314
315RenderObject* HTMLImageElement::createRenderer(RenderStyle* style)
316{
317    if (style->hasContent())
318        return RenderObject::createObject(this, style);
319
320    RenderImage* image = new RenderImage(this);
321    image->setImageResource(RenderImageResource::create());
322    image->setImageDevicePixelRatio(m_imageDevicePixelRatio);
323    return image;
324}
325
326bool HTMLImageElement::canStartSelection() const
327{
328    if (shadow())
329        return HTMLElement::canStartSelection();
330
331    return false;
332}
333
334void HTMLImageElement::attach(const AttachContext& context)
335{
336    HTMLElement::attach(context);
337
338    if (renderer() && renderer()->isImage()) {
339        RenderImage* renderImage = toRenderImage(renderer());
340        RenderImageResource* renderImageResource = renderImage->imageResource();
341        if (renderImageResource->hasImage())
342            return;
343
344        // If we have no image at all because we have no src attribute, set
345        // image height and width for the alt text instead.
346        if (!imageLoader().image() && !renderImageResource->cachedImage())
347            renderImage->setImageSizeForAltText();
348        else
349            renderImageResource->setImageResource(imageLoader().image());
350
351    }
352}
353
354Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode* insertionPoint)
355{
356    if (!m_formWasSetByParser || NodeTraversal::highestAncestorOrSelf(*insertionPoint) != NodeTraversal::highestAncestorOrSelf(*m_form.get()))
357        resetFormOwner();
358    if (m_listener)
359        document().mediaQueryMatcher().addViewportListener(m_listener);
360
361    bool imageWasModified = false;
362    if (RuntimeEnabledFeatures::pictureEnabled()) {
363        ImageCandidate candidate = findBestFitImageFromPictureParent();
364        if (!candidate.isEmpty()) {
365            setBestFitURLAndDPRFromImageCandidate(candidate);
366            imageWasModified = true;
367        }
368    }
369
370    // If we have been inserted from a renderer-less document,
371    // our loader may have not fetched the image, so do it now.
372    if ((insertionPoint->inDocument() && !imageLoader().image()) || imageWasModified)
373        imageLoader().updateFromElement(ImageLoader::UpdateNormal, m_elementCreatedByParser ? ImageLoader::ForceLoadImmediately : ImageLoader::LoadNormally);
374
375    return HTMLElement::insertedInto(insertionPoint);
376}
377
378void HTMLImageElement::removedFrom(ContainerNode* insertionPoint)
379{
380    if (!m_form || NodeTraversal::highestAncestorOrSelf(*m_form.get()) != NodeTraversal::highestAncestorOrSelf(*this))
381        resetFormOwner();
382    if (m_listener)
383        document().mediaQueryMatcher().removeViewportListener(m_listener);
384    HTMLElement::removedFrom(insertionPoint);
385}
386
387int HTMLImageElement::width(bool ignorePendingStylesheets)
388{
389    if (!renderer()) {
390        // check the attribute first for an explicit pixel value
391        bool ok;
392        int width = getAttribute(widthAttr).toInt(&ok);
393        if (ok)
394            return width;
395
396        // if the image is available, use its width
397        if (imageLoader().image())
398            return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f).width();
399    }
400
401    if (ignorePendingStylesheets)
402        document().updateLayoutIgnorePendingStylesheets();
403    else
404        document().updateLayout();
405
406    RenderBox* box = renderBox();
407    return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), box) : 0;
408}
409
410int HTMLImageElement::height(bool ignorePendingStylesheets)
411{
412    if (!renderer()) {
413        // check the attribute first for an explicit pixel value
414        bool ok;
415        int height = getAttribute(heightAttr).toInt(&ok);
416        if (ok)
417            return height;
418
419        // if the image is available, use its height
420        if (imageLoader().image())
421            return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f).height();
422    }
423
424    if (ignorePendingStylesheets)
425        document().updateLayoutIgnorePendingStylesheets();
426    else
427        document().updateLayout();
428
429    RenderBox* box = renderBox();
430    return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), box) : 0;
431}
432
433int HTMLImageElement::naturalWidth() const
434{
435    if (!imageLoader().image())
436        return 0;
437
438    return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f, ImageResource::IntrinsicSize).width();
439}
440
441int HTMLImageElement::naturalHeight() const
442{
443    if (!imageLoader().image())
444        return 0;
445
446    return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f, ImageResource::IntrinsicSize).height();
447}
448
449const String& HTMLImageElement::currentSrc() const
450{
451    // http://www.whatwg.org/specs/web-apps/current-work/multipage/edits.html#dom-img-currentsrc
452    // The currentSrc IDL attribute must return the img element's current request's current URL.
453    // Initially, the pending request turns into current request when it is either available or broken.
454    // We use the image's dimensions as a proxy to it being in any of these states.
455    if (!imageLoader().image() || !imageLoader().image()->image() || !imageLoader().image()->image()->width())
456        return emptyAtom;
457
458    return imageLoader().image()->url().string();
459}
460
461bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
462{
463    return attribute.name() == srcAttr
464        || attribute.name() == lowsrcAttr
465        || attribute.name() == longdescAttr
466        || (attribute.name() == usemapAttr && attribute.value()[0] != '#')
467        || HTMLElement::isURLAttribute(attribute);
468}
469
470bool HTMLImageElement::hasLegalLinkAttribute(const QualifiedName& name) const
471{
472    return name == srcAttr || HTMLElement::hasLegalLinkAttribute(name);
473}
474
475const QualifiedName& HTMLImageElement::subResourceAttributeName() const
476{
477    return srcAttr;
478}
479
480bool HTMLImageElement::draggable() const
481{
482    // Image elements are draggable by default.
483    return !equalIgnoringCase(getAttribute(draggableAttr), "false");
484}
485
486void HTMLImageElement::setHeight(int value)
487{
488    setIntegralAttribute(heightAttr, value);
489}
490
491KURL HTMLImageElement::src() const
492{
493    return document().completeURL(getAttribute(srcAttr));
494}
495
496void HTMLImageElement::setSrc(const String& value)
497{
498    setAttribute(srcAttr, AtomicString(value));
499}
500
501void HTMLImageElement::setWidth(int value)
502{
503    setIntegralAttribute(widthAttr, value);
504}
505
506int HTMLImageElement::x() const
507{
508    document().updateLayoutIgnorePendingStylesheets();
509    RenderObject* r = renderer();
510    if (!r)
511        return 0;
512
513    // FIXME: This doesn't work correctly with transforms.
514    FloatPoint absPos = r->localToAbsolute();
515    return absPos.x();
516}
517
518int HTMLImageElement::y() const
519{
520    document().updateLayoutIgnorePendingStylesheets();
521    RenderObject* r = renderer();
522    if (!r)
523        return 0;
524
525    // FIXME: This doesn't work correctly with transforms.
526    FloatPoint absPos = r->localToAbsolute();
527    return absPos.y();
528}
529
530bool HTMLImageElement::complete() const
531{
532    return imageLoader().imageComplete();
533}
534
535void HTMLImageElement::didMoveToNewDocument(Document& oldDocument)
536{
537    imageLoader().elementDidMoveToNewDocument();
538    HTMLElement::didMoveToNewDocument(oldDocument);
539}
540
541bool HTMLImageElement::isServerMap() const
542{
543    if (!fastHasAttribute(ismapAttr))
544        return false;
545
546    const AtomicString& usemap = fastGetAttribute(usemapAttr);
547
548    // If the usemap attribute starts with '#', it refers to a map element in the document.
549    if (usemap[0] == '#')
550        return false;
551
552    return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
553}
554
555Image* HTMLImageElement::imageContents()
556{
557    if (!imageLoader().imageComplete())
558        return 0;
559
560    return imageLoader().image()->image();
561}
562
563bool HTMLImageElement::isInteractiveContent() const
564{
565    return fastHasAttribute(usemapAttr);
566}
567
568PassRefPtr<Image> HTMLImageElement::getSourceImageForCanvas(SourceImageMode, SourceImageStatus* status) const
569{
570    if (!complete() || !cachedImage()) {
571        *status = IncompleteSourceImageStatus;
572        return nullptr;
573    }
574
575    if (cachedImage()->errorOccurred()) {
576        *status = UndecodableSourceImageStatus;
577        return nullptr;
578    }
579
580    RefPtr<Image> sourceImage = cachedImage()->imageForRenderer(renderer());
581
582    // We need to synthesize a container size if a renderer is not available to provide one.
583    if (!renderer() && sourceImage->usesContainerSize())
584        sourceImage->setContainerSize(sourceImage->size());
585
586    *status = NormalSourceImageStatus;
587    return sourceImage->imageForDefaultFrame();
588}
589
590bool HTMLImageElement::wouldTaintOrigin(SecurityOrigin* destinationSecurityOrigin) const
591{
592    ImageResource* image = cachedImage();
593    if (!image)
594        return false;
595    return !image->isAccessAllowed(destinationSecurityOrigin);
596}
597
598FloatSize HTMLImageElement::sourceSize() const
599{
600    ImageResource* image = cachedImage();
601    if (!image)
602        return FloatSize();
603    LayoutSize size;
604    size = image->imageSizeForRenderer(renderer(), 1.0f); // FIXME: Not sure about this.
605
606    return size;
607}
608
609FloatSize HTMLImageElement::defaultDestinationSize() const
610{
611    ImageResource* image = cachedImage();
612    if (!image)
613        return FloatSize();
614    LayoutSize size;
615    size = image->imageSizeForRenderer(renderer(), 1.0f); // FIXME: Not sure about this.
616    if (renderer() && renderer()->isRenderImage() && image->image() && !image->image()->hasRelativeWidth())
617        size.scale(toRenderImage(renderer())->imageDevicePixelRatio());
618    return size;
619}
620
621void HTMLImageElement::selectSourceURL(ImageLoader::UpdateFromElementBehavior behavior)
622{
623    bool foundURL = false;
624    if (RuntimeEnabledFeatures::pictureEnabled()) {
625        ImageCandidate candidate = findBestFitImageFromPictureParent();
626        if (!candidate.isEmpty()) {
627            setBestFitURLAndDPRFromImageCandidate(candidate);
628            foundURL = true;
629        }
630    }
631
632    if (!foundURL) {
633        unsigned effectiveSize = 0;
634        if (RuntimeEnabledFeatures::pictureSizesEnabled()) {
635            String sizes = fastGetAttribute(sizesAttr);
636            if (!sizes.isNull())
637                UseCounter::count(document(), UseCounter::Sizes);
638            SizesAttributeParser parser = SizesAttributeParser(MediaValuesDynamic::create(document()), sizes);
639            effectiveSize = parser.length();
640            m_effectiveSizeViewportDependant = parser.viewportDependant();
641        }
642        ImageCandidate candidate = bestFitSourceForImageAttributes(document().devicePixelRatio(), effectiveSize, fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr));
643        setBestFitURLAndDPRFromImageCandidate(candidate);
644    }
645    if (m_intrinsicSizingViewportDependant && m_effectiveSizeViewportDependant && !m_listener) {
646        m_listener = ViewportChangeListener::create(this);
647        document().mediaQueryMatcher().addViewportListener(m_listener);
648    }
649    imageLoader().updateFromElement(behavior);
650}
651
652const KURL& HTMLImageElement::sourceURL() const
653{
654    return cachedImage()->response().url();
655}
656
657}
658