WebCore:
[WebKit-https.git] / WebCore / html / html_imageimpl.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include "config.h"
25 #include "html_imageimpl.h"
26
27 #include "DocLoader.h"
28 #include "EventNames.h"
29 #include "FloatRect.h"
30 #include "HTMLFormElement.h"
31 #include "csshelper.h"
32 #include "CSSPropertyNames.h"
33 #include "CSSValueKeywords.h"
34 #include "HTMLDocument.h"
35 #include "RenderImage.h"
36 #include "HTMLNames.h"
37
38 using namespace std;
39
40 namespace WebCore {
41
42 using namespace EventNames;
43 using namespace HTMLNames;
44
45 HTMLImageLoader::HTMLImageLoader(Element* elt)
46     : m_element(elt), m_image(0), m_firedLoad(true), m_imageComplete(true)
47 {
48 }
49
50 HTMLImageLoader::~HTMLImageLoader()
51 {
52     if (m_image)
53         m_image->deref(this);
54     m_element->document()->removeImage(this);
55 }
56
57 void HTMLImageLoader::setLoadingImage(CachedImage *loadingImage)
58 {
59     m_firedLoad = false;
60     m_imageComplete = false;
61     m_image = loadingImage;
62 }
63
64 void HTMLImageLoader::updateFromElement()
65 {
66     // If we're not making renderers for the page, then don't load images.  We don't want to slow
67     // down the raw HTML parsing case by loading images we don't intend to display.
68     Element* elem = element();
69     Document* doc = elem->document();
70     if (!doc->renderer())
71         return;
72
73     AtomicString attr = elem->getAttribute(elem->hasLocalName(objectTag) ? dataAttr : srcAttr);
74     
75     // Treat a lack of src or empty string for src as no image at all.
76     CachedImage *newImage = 0;
77     if (!attr.isEmpty())
78         newImage = doc->docLoader()->requestImage(parseURL(attr));
79
80     CachedImage *oldImage = m_image;
81     if (newImage != oldImage) {
82 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
83         if (!doc->ownerElement() && newImage)
84             printf("Image requested at %d\n", doc->elapsedTime());
85 #endif
86         setLoadingImage(newImage);
87         if (newImage)
88             newImage->ref(this);
89         if (oldImage)
90             oldImage->deref(this);
91     }
92
93     if (RenderImage* renderer = static_cast<RenderImage*>(elem->renderer()))
94         renderer->resetAnimation();
95 }
96
97 void HTMLImageLoader::dispatchLoadEvent()
98 {
99     if (!m_firedLoad && m_image) {
100         m_firedLoad = true;
101         element()->dispatchHTMLEvent(m_image->isErrorImage() ? errorEvent : loadEvent, false, false);
102     }
103 }
104
105 void HTMLImageLoader::notifyFinished(CachedObject *image)
106 {
107     m_imageComplete = true;
108     Element* elem = element();
109     Document* doc = elem->document();
110     doc->dispatchImageLoadEventSoon(this);
111 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
112         if (!doc->ownerElement())
113             printf("Image loaded at %d\n", doc->elapsedTime());
114 #endif
115     if (RenderImage* renderer = static_cast<RenderImage*>(elem->renderer()))
116         renderer->setCachedImage(m_image);
117 }
118
119 // -------------------------------------------------------------------------
120
121 HTMLImageElement::HTMLImageElement(Document *doc, HTMLFormElement *f)
122     : HTMLElement(imgTag, doc), m_imageLoader(this), ismap(false), m_form(f)
123     , m_compositeOperator(CompositeSourceOver)
124 {
125     if (f)
126         f->registerImgElement(this);
127 }
128
129 HTMLImageElement::HTMLImageElement(const QualifiedName& tagName, Document *doc)
130     : HTMLElement(tagName, doc), m_imageLoader(this), ismap(false), m_form(0)
131     , m_compositeOperator(CompositeSourceOver)
132 {
133 }
134
135 HTMLImageElement::~HTMLImageElement()
136 {
137     if (m_form)
138         m_form->removeImgElement(this);
139 }
140
141 bool HTMLImageElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
142 {
143     if (attrName == widthAttr ||
144         attrName == heightAttr ||
145         attrName == vspaceAttr ||
146         attrName == hspaceAttr ||
147         attrName == valignAttr) {
148         result = eUniversal;
149         return false;
150     }
151     
152     if (attrName == borderAttr || attrName == alignAttr) {
153         result = eReplaced; // Shared with embed and iframe elements.
154         return false;
155     }
156
157     return HTMLElement::mapToEntry(attrName, result);
158 }
159
160 void HTMLImageElement::parseMappedAttribute(MappedAttribute *attr)
161 {
162     const QualifiedName& attrName = attr->name();
163     if (attrName == altAttr) {
164         if (renderer())
165             static_cast<RenderImage*>(renderer())->updateAltText();
166     } else if (attrName == srcAttr)
167         m_imageLoader.updateFromElement();
168     else if (attrName == widthAttr)
169         addCSSLength(attr, CSS_PROP_WIDTH, attr->value());
170     else if (attrName == heightAttr)
171         addCSSLength(attr, CSS_PROP_HEIGHT, attr->value());
172     else if (attrName == borderAttr) {
173         // border="noborder" -> border="0"
174         if(attr->value().toInt()) {
175             addCSSLength(attr, CSS_PROP_BORDER_WIDTH, attr->value());
176             addCSSProperty(attr, CSS_PROP_BORDER_TOP_STYLE, CSS_VAL_SOLID);
177             addCSSProperty(attr, CSS_PROP_BORDER_RIGHT_STYLE, CSS_VAL_SOLID);
178             addCSSProperty(attr, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_VAL_SOLID);
179             addCSSProperty(attr, CSS_PROP_BORDER_LEFT_STYLE, CSS_VAL_SOLID);
180         }
181     } else if (attrName == vspaceAttr) {
182         addCSSLength(attr, CSS_PROP_MARGIN_TOP, attr->value());
183         addCSSLength(attr, CSS_PROP_MARGIN_BOTTOM, attr->value());
184     } else if (attrName == hspaceAttr) {
185         addCSSLength(attr, CSS_PROP_MARGIN_LEFT, attr->value());
186         addCSSLength(attr, CSS_PROP_MARGIN_RIGHT, attr->value());
187     } else if (attrName == alignAttr)
188         addHTMLAlignment(attr);
189     else if (attrName == valignAttr)
190         addCSSProperty(attr, CSS_PROP_VERTICAL_ALIGN, attr->value());
191     else if (attrName == usemapAttr) {
192         if (attr->value().domString()[0] == '#')
193             usemap = attr->value();
194         else
195             usemap = document()->completeURL(parseURL(attr->value()));
196         m_isLink = !attr->isNull();
197     } else if (attrName == ismapAttr)
198         ismap = true;
199     else if (attrName == onabortAttr)
200         setHTMLEventListener(abortEvent, attr);
201     else if (attrName == onloadAttr)
202         setHTMLEventListener(loadEvent, attr);
203     else if (attrName == compositeAttr) {
204         if (!parseCompositeOperator(attr->value(), m_compositeOperator))
205             m_compositeOperator = CompositeSourceOver;
206     } else if (attrName == nameAttr) {
207         String newNameAttr = attr->value();
208         if (inDocument() && document()->isHTMLDocument()) {
209             HTMLDocument* doc = static_cast<HTMLDocument*>(document());
210             doc->removeNamedItem(oldNameAttr);
211             doc->addNamedItem(newNameAttr);
212         }
213         oldNameAttr = newNameAttr;
214     } else
215         HTMLElement::parseMappedAttribute(attr);
216 }
217
218 String HTMLImageElement::altText() const
219 {
220     // lets figure out the alt text.. magic stuff
221     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
222     // also heavily discussed by Hixie on bugzilla
223     String alt = getAttribute(altAttr);
224     // fall back to title attribute
225     if (alt.isNull())
226         alt = getAttribute(titleAttr);
227     return alt;
228 }
229
230 RenderObject *HTMLImageElement::createRenderer(RenderArena *arena, RenderStyle *style)
231 {
232      return new (arena) RenderImage(this);
233 }
234
235 void HTMLImageElement::attach()
236 {
237     HTMLElement::attach();
238
239     if (RenderImage* imageObj = static_cast<RenderImage*>(renderer()))
240         imageObj->setCachedImage(m_imageLoader.image());
241 }
242
243 void HTMLImageElement::insertedIntoDocument()
244 {
245     Document* doc = document();
246     if (doc->isHTMLDocument())
247         static_cast<HTMLDocument*>(doc)->addNamedItem(oldNameAttr);
248
249     HTMLElement::insertedIntoDocument();
250 }
251
252 void HTMLImageElement::removedFromDocument()
253 {
254     Document* doc = document();
255     if (doc->isHTMLDocument())
256         static_cast<HTMLDocument*>(doc)->removeNamedItem(oldNameAttr);
257
258     HTMLElement::removedFromDocument();
259 }
260
261 int HTMLImageElement::width(bool ignorePendingStylesheets) const
262 {
263     if (!renderer()) {
264         // check the attribute first for an explicit pixel value
265         bool ok;
266         int width = getAttribute(widthAttr).toInt(&ok);
267         if (ok)
268             return width;
269         
270         // if the image is available, use its width
271         if (m_imageLoader.image())
272             return m_imageLoader.image()->imageSize().width();
273     }
274
275     Document* doc = document();
276     if (ignorePendingStylesheets)
277         doc->updateLayoutIgnorePendingStylesheets();
278     else
279         doc->updateLayout();
280
281     return renderer() ? renderer()->contentWidth() : 0;
282 }
283
284 int HTMLImageElement::height(bool ignorePendingStylesheets) const
285 {
286     if (!renderer()) {
287         // check the attribute first for an explicit pixel value
288         bool ok;
289         int height = getAttribute(heightAttr).toInt(&ok);
290         if (ok)
291             return height;
292         
293         // if the image is available, use its height
294         if (m_imageLoader.image())
295             return m_imageLoader.image()->imageSize().height();        
296     }
297
298     Document* doc = document();
299     if (ignorePendingStylesheets)
300         doc->updateLayoutIgnorePendingStylesheets();
301     else
302         doc->updateLayout();
303
304     return renderer() ? renderer()->contentHeight() : 0;
305 }
306
307 bool HTMLImageElement::isURLAttribute(Attribute *attr) const
308 {
309     return attr->name() == srcAttr || (attr->name() == usemapAttr && attr->value().domString()[0] != '#');
310 }
311
312 String HTMLImageElement::name() const
313 {
314     return getAttribute(nameAttr);
315 }
316
317 void HTMLImageElement::setName(const String& value)
318 {
319     setAttribute(nameAttr, value);
320 }
321
322 String HTMLImageElement::align() const
323 {
324     return getAttribute(alignAttr);
325 }
326
327 void HTMLImageElement::setAlign(const String& value)
328 {
329     setAttribute(alignAttr, value);
330 }
331
332 String HTMLImageElement::alt() const
333 {
334     return getAttribute(altAttr);
335 }
336
337 void HTMLImageElement::setAlt(const String& value)
338 {
339     setAttribute(altAttr, value);
340 }
341
342 int HTMLImageElement::border() const
343 {
344     // ### return value in pixels
345     return getAttribute(borderAttr).toInt();
346 }
347
348 void HTMLImageElement::setBorder(int value)
349 {
350     setAttribute(borderAttr, String::number(value));
351 }
352
353 void HTMLImageElement::setHeight(int value)
354 {
355     setAttribute(heightAttr, String::number(value));
356 }
357
358 int HTMLImageElement::hspace() const
359 {
360     // ### return actual value
361     return getAttribute(hspaceAttr).toInt();
362 }
363
364 void HTMLImageElement::setHspace(int value)
365 {
366     setAttribute(hspaceAttr, String::number(value));
367 }
368
369 bool HTMLImageElement::isMap() const
370 {
371     return !getAttribute(ismapAttr).isNull();
372 }
373
374 void HTMLImageElement::setIsMap(bool isMap)
375 {
376     setAttribute(ismapAttr, isMap ? "" : 0);
377 }
378
379 String HTMLImageElement::longDesc() const
380 {
381     return getAttribute(longdescAttr);
382 }
383
384 void HTMLImageElement::setLongDesc(const String& value)
385 {
386     setAttribute(longdescAttr, value);
387 }
388
389 String HTMLImageElement::src() const
390 {
391     return document()->completeURL(getAttribute(srcAttr));
392 }
393
394 void HTMLImageElement::setSrc(const String& value)
395 {
396     setAttribute(srcAttr, value);
397 }
398
399 String HTMLImageElement::useMap() const
400 {
401     return getAttribute(usemapAttr);
402 }
403
404 void HTMLImageElement::setUseMap(const String& value)
405 {
406     setAttribute(usemapAttr, value);
407 }
408
409 int HTMLImageElement::vspace() const
410 {
411     // ### return actual vspace
412     return getAttribute(vspaceAttr).toInt();
413 }
414
415 void HTMLImageElement::setVspace(int value)
416 {
417     setAttribute(vspaceAttr, String::number(value));
418 }
419
420 void HTMLImageElement::setWidth(int value)
421 {
422     setAttribute(widthAttr, String::number(value));
423 }
424
425 int HTMLImageElement::x() const
426 {
427     RenderObject *r = renderer();
428     if (!r)
429         return 0;
430     int x, y;
431     r->absolutePosition(x, y);
432     return x;
433 }
434
435 int HTMLImageElement::y() const
436 {
437     RenderObject *r = renderer();
438     if (!r)
439         return 0;
440     int x, y;
441     r->absolutePosition(x, y);
442     return y;
443 }
444
445 bool HTMLImageElement::complete() const
446 {
447     return m_imageLoader.imageComplete();
448 }
449
450 // -------------------------------------------------------------------------
451
452 HTMLMapElement::HTMLMapElement(Document *doc)
453     : HTMLElement(mapTag, doc)
454 {
455 }
456
457 HTMLMapElement::~HTMLMapElement()
458 {
459     document()->removeImageMap(this);
460 }
461
462 bool HTMLMapElement::checkDTD(const Node* newChild)
463 {
464     // FIXME: This seems really odd, allowing only blocks inside map elements.
465     return newChild->hasTagName(areaTag) || newChild->hasTagName(scriptTag) || inBlockTagList(newChild);
466 }
467
468 bool HTMLMapElement::mapMouseEvent(int x, int y, int width, int height, RenderObject::NodeInfo& info)
469 {
470     Node *node = this;
471     while ((node = node->traverseNextNode(this)))
472         if (node->hasTagName(areaTag))
473             if (static_cast<HTMLAreaElement *>(node)->mapMouseEvent(x, y, width, height, info))
474                 return true;
475     return false;
476 }
477
478 void HTMLMapElement::parseMappedAttribute(MappedAttribute *attr)
479 {
480     const QualifiedName& attrName = attr->name();
481     if (attrName == idAttr || attrName == nameAttr) {
482         Document* doc = document();
483         if (attrName == idAttr) {
484             // Call base class so that hasID bit gets set.
485             HTMLElement::parseMappedAttribute(attr);
486             if (doc->htmlMode() != Document::XHtml)
487                 return;
488         }
489         doc->removeImageMap(this);
490         m_name = attr->value();
491         if (m_name[0] == '#') {
492             String mapName(m_name.domString().copy());
493             mapName.remove(0, 1);
494             m_name = mapName;
495         }
496         doc->addImageMap(this);
497     } else
498         HTMLElement::parseMappedAttribute(attr);
499 }
500
501 PassRefPtr<HTMLCollection> HTMLMapElement::areas()
502 {
503     return new HTMLCollection(this, HTMLCollection::MAP_AREAS);
504 }
505
506 String HTMLMapElement::name() const
507 {
508     return getAttribute(nameAttr);
509 }
510
511 void HTMLMapElement::setName(const String& value)
512 {
513     setAttribute(nameAttr, value);
514 }
515
516 // -------------------------------------------------------------------------
517
518 HTMLAreaElement::HTMLAreaElement(Document *doc)
519     : HTMLAnchorElement(areaTag, doc)
520 {
521     m_coords = 0;
522     m_coordsLen = 0;
523     m_shape = Unknown;
524     lasth = -1;
525     lastw = -1;
526 }
527
528 HTMLAreaElement::~HTMLAreaElement()
529 {
530     delete [] m_coords;
531 }
532
533 void HTMLAreaElement::parseMappedAttribute(MappedAttribute *attr)
534 {
535     if (attr->name() == shapeAttr) {
536         if (equalIgnoringCase(attr->value(), "default"))
537             m_shape = Default;
538         else if (equalIgnoringCase(attr->value(), "circle"))
539             m_shape = Circle;
540         else if (equalIgnoringCase(attr->value(), "poly"))
541             m_shape = Poly;
542         else if (equalIgnoringCase(attr->value(), "rect"))
543             m_shape = Rect;
544     } else if (attr->name() == coordsAttr) {
545         delete [] m_coords;
546         m_coords = attr->value().toCoordsArray(m_coordsLen);
547     } else if (attr->name() == targetAttr) {
548         m_hasTarget = !attr->isNull();
549     } else if (attr->name() == altAttr || attr->name() == accesskeyAttr) {
550         // Do nothing.
551     } else
552         HTMLAnchorElement::parseMappedAttribute(attr);
553 }
554
555 bool HTMLAreaElement::mapMouseEvent(int x, int y, int width, int height, RenderObject::NodeInfo& info)
556 {
557     if (width != lastw || height != lasth) {
558         region = getRegion(width, height);
559         lastw = width;
560         lasth = height;
561     }
562
563     if (!region.contains(IntPoint(x, y)))
564         return false;
565     
566     info.setInnerNode(this);
567     info.setURLElement(this);
568     return true;
569 }
570
571 IntRect HTMLAreaElement::getRect(RenderObject* obj) const
572 {
573     int dx, dy;
574     obj->absolutePosition(dx, dy);
575     Path p = getRegion(lastw, lasth);
576     p.translate(IntSize(dx, dy));
577     return enclosingIntRect(p.boundingRect());
578 }
579
580 Path HTMLAreaElement::getRegion(int width, int height) const
581 {
582     if (!m_coords)
583         return Path();
584
585     // If element omits the shape attribute, select shape based on number of coordinates.
586     Shape shape = m_shape;
587     if (shape == Unknown) {
588         if (m_coordsLen == 3)
589             shape = Circle;
590         else if (m_coordsLen == 4)
591             shape = Rect;
592         else if (m_coordsLen >= 6)
593             shape = Poly;
594     }
595
596     Path path;
597     switch (shape) {
598         case Poly:
599             if (m_coordsLen >= 6) {
600                 Path path;
601                 int numPoints = m_coordsLen / 2;
602                 path.moveTo(FloatPoint(m_coords[0].calcMinValue(width), m_coords[1].calcMinValue(height)));
603                 for (int i = 1; i < numPoints; ++i)
604                     path.addLineTo(FloatPoint(m_coords[i * 2].calcMinValue(width), m_coords[i * 2 + 1].calcMinValue(height)));
605                 path.closeSubpath();
606             }
607             break;
608         case Circle:
609             if (m_coordsLen >= 3) {
610                 Length radius = m_coords[2];
611                 int r = min(radius.calcMinValue(width), radius.calcMinValue(height));
612                 path.addEllipse(FloatRect(m_coords[0].calcMinValue(width) - r, m_coords[1].calcMinValue(height) - r,
613                     2 * r, 2 * r));
614             }
615             break;
616         case Rect:
617             if (m_coordsLen >= 4) {
618                 int x0 = m_coords[0].calcMinValue(width);
619                 int y0 = m_coords[1].calcMinValue(height);
620                 int x1 = m_coords[2].calcMinValue(width);
621                 int y1 = m_coords[3].calcMinValue(height);
622                 path.addRect(FloatRect(x0, y0, x1 - x0, y1 - y0));
623             }
624             break;
625         case Default:
626             path.addRect(FloatRect(0, 0, width, height));
627             break;
628         case Unknown:
629             break;
630     }
631
632     return path;
633 }
634
635 String HTMLAreaElement::accessKey() const
636 {
637     return getAttribute(accesskeyAttr);
638 }
639
640 void HTMLAreaElement::setAccessKey(const String& value)
641 {
642     setAttribute(accesskeyAttr, value);
643 }
644
645 String HTMLAreaElement::alt() const
646 {
647     return getAttribute(altAttr);
648 }
649
650 void HTMLAreaElement::setAlt(const String& value)
651 {
652     setAttribute(altAttr, value);
653 }
654
655 String HTMLAreaElement::coords() const
656 {
657     return getAttribute(coordsAttr);
658 }
659
660 void HTMLAreaElement::setCoords(const String& value)
661 {
662     setAttribute(coordsAttr, value);
663 }
664
665 String HTMLAreaElement::href() const
666 {
667     return document()->completeURL(getAttribute(hrefAttr));
668 }
669
670 void HTMLAreaElement::setHref(const String& value)
671 {
672     setAttribute(hrefAttr, value);
673 }
674
675 bool HTMLAreaElement::noHref() const
676 {
677     return !getAttribute(nohrefAttr).isNull();
678 }
679
680 void HTMLAreaElement::setNoHref(bool noHref)
681 {
682     setAttribute(nohrefAttr, noHref ? "" : 0);
683 }
684
685 String HTMLAreaElement::shape() const
686 {
687     return getAttribute(shapeAttr);
688 }
689
690 void HTMLAreaElement::setShape(const String& value)
691 {
692     setAttribute(shapeAttr, value);
693 }
694
695 int HTMLAreaElement::tabIndex() const
696 {
697     return getAttribute(tabindexAttr).toInt();
698 }
699
700 void HTMLAreaElement::setTabIndex(int tabIndex)
701 {
702     setAttribute(tabindexAttr, String::number(tabIndex));
703 }
704
705 String HTMLAreaElement::target() const
706 {
707     return getAttribute(targetAttr);
708 }
709
710 void HTMLAreaElement::setTarget(const String& value)
711 {
712     setAttribute(targetAttr, value);
713 }
714
715 }