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  *
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 "html/html_imageimpl.h"
24 #include "html/html_formimpl.h"
25 #include "html/html_documentimpl.h"
26 
27 #include "khtmlview.h"
28 #include "khtml_part.h"
29 
30 #include <kstringhandler.h>
31 
32 #include "rendering/render_image.h"
33 #include "rendering/render_flow.h"
34 #include "css/cssstyleselector.h"
35 #include "css/cssproperties.h"
36 #include "css/cssvalues.h"
37 #include "xml/dom2_eventsimpl.h"
38 
39 #include <QCharRef>
40 #include <QPoint>
41 #include <QStack>
42 #include <QImage>
43 
44 using namespace DOM;
45 using namespace khtml;
46 
47 // -------------------------------------------------------------------------
48 
HTMLImageElementImpl(DocumentImpl * doc,HTMLFormElementImpl * f)49 HTMLImageElementImpl::HTMLImageElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f)
50     : HTMLElementImpl(doc), ismap(false), loadEventSent(true), unsafe(false), m_image(nullptr), m_form(f)
51 {
52     if (m_form) {
53         m_form->registerImgElement(this);
54     }
55 }
56 
~HTMLImageElementImpl()57 HTMLImageElementImpl::~HTMLImageElementImpl()
58 {
59     if (document()) {
60         document()->removeImage(this);
61     }
62 
63     if (m_image) {
64         m_image->deref(this);
65     }
66 
67     if (m_form) {
68         m_form->removeImgElement(this);
69     }
70 }
71 
id() const72 NodeImpl::Id HTMLImageElementImpl::id() const
73 {
74     return ID_IMG;
75 }
76 
parseAttribute(AttributeImpl * attr)77 void HTMLImageElementImpl::parseAttribute(AttributeImpl *attr)
78 {
79     switch (attr->id()) {
80     case ATTR_ALT:
81         setChanged();
82         break;
83     case ATTR_SRC: {
84         setChanged();
85 
86         //Start loading the image already, to generate events
87         const DOMString imgSrcUrl = attr->value().trimSpaces();
88         if (!imgSrcUrl.isEmpty()) { //### why do we not hide or something when setting this?
89             CachedImage *newImage = document()->docLoader()->requestImage(imgSrcUrl);
90             if (newImage && newImage != m_image) {
91                 CachedImage *oldImage = m_image;
92                 loadEventSent = false;
93                 m_image = newImage;
94                 m_image->ref(this);
95                 if (oldImage) {
96                     oldImage->deref(this);
97                 }
98             }
99 
100             if (m_image) {
101                 const QUrl fullURL = QUrl(m_image->url().string());
102                 if (document()->origin()->taintsCanvas(fullURL)) {
103                     unsafe = true;
104                 }
105             }
106         }
107     }
108     break;
109     case ATTR_WIDTH:
110         if (!attr->value().isEmpty()) {
111             addCSSLength(CSS_PROP_WIDTH, attr->value());
112         } else {
113             removeCSSProperty(CSS_PROP_WIDTH);
114         }
115         break;
116     case ATTR_HEIGHT:
117         if (!attr->value().isEmpty()) {
118             addCSSLength(CSS_PROP_HEIGHT, attr->value());
119         } else {
120             removeCSSProperty(CSS_PROP_HEIGHT);
121         }
122         break;
123     case ATTR_BORDER:
124         // border="noborder" -> border="0"
125         if (attr->value().toInt()) {
126             addCSSLength(CSS_PROP_BORDER_WIDTH, attr->value());
127             addCSSProperty(CSS_PROP_BORDER_TOP_STYLE, CSS_VAL_SOLID);
128             addCSSProperty(CSS_PROP_BORDER_RIGHT_STYLE, CSS_VAL_SOLID);
129             addCSSProperty(CSS_PROP_BORDER_BOTTOM_STYLE, CSS_VAL_SOLID);
130             addCSSProperty(CSS_PROP_BORDER_LEFT_STYLE, CSS_VAL_SOLID);
131         } else {
132             removeCSSProperty(CSS_PROP_BORDER_WIDTH);
133             removeCSSProperty(CSS_PROP_BORDER_TOP_STYLE);
134             removeCSSProperty(CSS_PROP_BORDER_RIGHT_STYLE);
135             removeCSSProperty(CSS_PROP_BORDER_BOTTOM_STYLE);
136             removeCSSProperty(CSS_PROP_BORDER_LEFT_STYLE);
137         }
138         break;
139     case ATTR_VSPACE:
140         addCSSLength(CSS_PROP_MARGIN_TOP, attr->value());
141         addCSSLength(CSS_PROP_MARGIN_BOTTOM, attr->value());
142         break;
143     case ATTR_HSPACE:
144         addCSSLength(CSS_PROP_MARGIN_LEFT, attr->value());
145         addCSSLength(CSS_PROP_MARGIN_RIGHT, attr->value());
146         break;
147     case ATTR_ALIGN:
148         addHTMLAlignment(attr->value());
149         break;
150     case ATTR_VALIGN:
151         addCSSProperty(CSS_PROP_VERTICAL_ALIGN, attr->value().lower());
152         break;
153     case ATTR_USEMAP:
154         if (attr->value()[0] == '#') {
155             usemap = attr->value().lower();
156         } else {
157             QString url = document()->completeURL(attr->value().trimSpaces().string());
158             // ### we remove the part before the anchor and hope
159             // the map is on the same html page....
160             usemap = url;
161         }
162         m_hasAnchor = attr->val() != nullptr;
163         break;
164     case ATTR_ISMAP:
165         ismap = true;
166         break;
167     case ATTR_ONABORT: // ### add support for this
168         setHTMLEventListener(EventImpl::ABORT_EVENT,
169                              document()->createHTMLEventListener(attr->value().string(), "onabort", this));
170         break;
171     case ATTR_ONERROR:
172         setHTMLEventListener(EventImpl::ERROR_EVENT,
173                              document()->createHTMLEventListener(attr->value().string(), "onerror", this));
174         break;
175     case ATTR_ONLOAD:
176         setHTMLEventListener(EventImpl::LOAD_EVENT,
177                              document()->createHTMLEventListener(attr->value().string(), "onload", this));
178         break;
179     case ATTR_NOSAVE:
180         break;
181     case ATTR_NAME:
182         if (inDocument() && m_name != attr->value()) {
183             document()->underDocNamedCache().remove(m_name,        this);
184             document()->underDocNamedCache().add(attr->value(), this);
185         }
186         m_name = attr->value();
187     //fallthrough
188     default:
189         HTMLElementImpl::parseAttribute(attr);
190     }
191 }
192 
notifyFinished(CachedObject * finishedObj)193 void HTMLImageElementImpl::notifyFinished(CachedObject *finishedObj)
194 {
195     if (m_image == finishedObj) {
196         document()->dispatchImageLoadEventSoon(this);
197     }
198 }
199 
dispatchLoadEvent()200 void HTMLImageElementImpl::dispatchLoadEvent()
201 {
202     if (!loadEventSent) {
203         loadEventSent = true;
204         if (m_image->isErrorImage()) {
205             dispatchHTMLEvent(EventImpl::ERROR_EVENT, false, false);
206         } else {
207             dispatchHTMLEvent(EventImpl::LOAD_EVENT, false, false);
208         }
209     }
210 }
211 
altText() const212 DOMString HTMLImageElementImpl::altText() const
213 {
214     // lets figure out the alt text.. magic stuff
215     // https://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
216     // also heavily discussed by Hixie on bugzilla
217     DOMString alt(getAttribute(ATTR_ALT));
218     // fall back to title attribute
219     if (alt.isNull()) {
220         alt = getAttribute(ATTR_TITLE);
221     }
222 #if 0
223     if (alt.isNull()) {
224         QString p = QUrl(document()->completeURL(getAttribute(ATTR_SRC).string())).toDisplayString();
225         int pos;
226         if ((pos = p.lastIndexOf('.')) > 0) {
227             p.truncate(pos);
228         }
229         alt = DOMString(KStringHandler::csqueeze(p));
230     }
231 #endif
232 
233     return alt;
234 }
235 
attach()236 void HTMLImageElementImpl::attach()
237 {
238     assert(!attached());
239     assert(!m_render);
240     assert(parentNode());
241 
242     RenderStyle *_style = document()->styleSelector()->styleForElement(this);
243     _style->ref();
244     if (parentNode()->renderer() && parentNode()->renderer()->childAllowed() &&
245             _style->display() != NONE) {
246         m_render = new(document()->renderArena()) RenderImage(this);
247         m_render->setStyle(_style);
248         parentNode()->renderer()->addChild(m_render, nextRenderer());
249     }
250     _style->deref();
251 
252     NodeBaseImpl::attach();
253     if (m_render) {
254         m_render->updateFromElement();
255     }
256 }
257 
removedFromDocument()258 void HTMLImageElementImpl::removedFromDocument()
259 {
260     document()->underDocNamedCache().remove(m_name, this);
261     HTMLElementImpl::removedFromDocument();
262 }
263 
insertedIntoDocument()264 void HTMLImageElementImpl::insertedIntoDocument()
265 {
266     document()->underDocNamedCache().add(m_name, this);
267     HTMLElementImpl::insertedIntoDocument();
268 }
269 
removeId(const DOMString & id)270 void HTMLImageElementImpl::removeId(const DOMString &id)
271 {
272     document()->underDocNamedCache().remove(id, this);
273     HTMLElementImpl::removeId(id);
274 }
275 
addId(const DOMString & id)276 void HTMLImageElementImpl::addId(const DOMString &id)
277 {
278     document()->underDocNamedCache().add(id, this);
279     HTMLElementImpl::addId(id);
280 }
281 
width() const282 long HTMLImageElementImpl::width() const
283 {
284     if (!m_render) {
285         DOMString widthAttr = getAttribute(ATTR_WIDTH);
286         if (!widthAttr.isNull()) {
287             return widthAttr.toInt();
288         } else if (m_image && m_image->pixmap_size().isValid()) {
289             return m_image->pixmap_size().width();
290         } else {
291             return 0;
292         }
293     }
294 
295     document()->updateLayout();
296 
297     return m_render ? m_render->contentWidth() :
298            getAttribute(ATTR_WIDTH).toInt();
299 }
300 
height() const301 long HTMLImageElementImpl::height() const
302 {
303     if (!m_render) {
304         DOMString heightAttr = getAttribute(ATTR_HEIGHT);
305         if (!heightAttr.isNull()) {
306             return heightAttr.toInt();
307         } else if (m_image && m_image->pixmap_size().isValid()) {
308             return m_image->pixmap_size().height();
309         } else {
310             return 0;
311         }
312     }
313 
314     document()->updateLayout();
315 
316     return m_render ? m_render->contentHeight() :
317            getAttribute(ATTR_HEIGHT).toInt();
318 }
319 
setWidth(long width)320 void HTMLImageElementImpl::setWidth(long width)
321 {
322     setAttribute(ATTR_WIDTH, QString::number(width));
323 }
324 
setHeight(long height)325 void HTMLImageElementImpl::setHeight(long height)
326 {
327     setAttribute(ATTR_HEIGHT, QString::number(height));
328 }
329 
currentImage() const330 QImage HTMLImageElementImpl::currentImage() const
331 {
332     if (!complete() || !m_image || !m_image->image()) {
333         return QImage();
334     }
335 
336     QImage *im = m_image->image()->qimage();
337     if (im) {
338         return *im;
339     } else {
340         return QImage();
341     }
342 }
343 
x() const344 long HTMLImageElementImpl::x() const
345 {
346     if (renderer()) {
347         int x = 0;
348         int y = 0;
349         renderer()->absolutePosition(x, y);
350         return x;
351     }
352     return 0;
353 }
354 
y() const355 long HTMLImageElementImpl::y() const
356 {
357     if (renderer()) {
358         int x = 0;
359         int y = 0;
360         renderer()->absolutePosition(x, y);
361         return y;
362     }
363     return 0;
364 }
365 
currentPixmap() const366 QPixmap HTMLImageElementImpl::currentPixmap() const
367 {
368     if (m_image) {
369         return m_image->pixmap();
370     }
371 
372     return QPixmap();
373 }
374 
complete() const375 bool HTMLImageElementImpl::complete() const
376 {
377     return m_image && m_image->isComplete();
378 }
379 
380 // -------------------------------------------------------------------------
381 
HTMLMapElementImpl(DocumentImpl * doc)382 HTMLMapElementImpl::HTMLMapElementImpl(DocumentImpl *doc)
383     : HTMLElementImpl(doc)
384 {
385 }
386 
~HTMLMapElementImpl()387 HTMLMapElementImpl::~HTMLMapElementImpl()
388 {
389     if (document() && document()->isHTMLDocument()) {
390         static_cast<HTMLDocumentImpl *>(document())->mapMap.remove(name);
391     }
392 }
393 
id() const394 NodeImpl::Id HTMLMapElementImpl::id() const
395 {
396     return ID_MAP;
397 }
398 
399 bool
mapMouseEvent(int x_,int y_,int width_,int height_,RenderObject::NodeInfo & info)400 HTMLMapElementImpl::mapMouseEvent(int x_, int y_, int width_, int height_,
401                                   RenderObject::NodeInfo &info)
402 {
403     //cout << "map:mapMouseEvent " << endl;
404     //cout << x_ << " " << y_ <<" "<< width_ <<" "<< height_ << endl;
405     QStack<NodeImpl *> nodeStack;
406 
407     NodeImpl *current = firstChild();
408     while (1) {
409         if (!current) {
410             if (nodeStack.isEmpty()) {
411                 break;
412             }
413             current = nodeStack.pop();
414             current = current->nextSibling();
415             continue;
416         }
417         if (current->id() == ID_AREA) {
418             //cout << "area found " << endl;
419             HTMLAreaElementImpl *area = static_cast<HTMLAreaElementImpl *>(current);
420             if (area->mapMouseEvent(x_, y_, width_, height_, info)) {
421                 return true;
422             }
423         }
424         NodeImpl *child = current->firstChild();
425         if (child) {
426             nodeStack.push(current);
427             current = child;
428         } else {
429             current = current->nextSibling();
430         }
431     }
432 
433     return false;
434 }
435 
parseAttribute(AttributeImpl * attr)436 void HTMLMapElementImpl::parseAttribute(AttributeImpl *attr)
437 {
438     switch (attr->id()) {
439     case ATTR_ID:
440         if (document()->htmlMode() != DocumentImpl::XHtml) {
441             HTMLElementImpl::parseAttribute(attr);
442             break;
443         } else {
444             // add name with full url:
445             QString url = document()->completeURL(attr->value().trimSpaces().string());
446             if (document()->isHTMLDocument()) {
447                 static_cast<HTMLDocumentImpl *>(document())->mapMap[url] = this;
448             }
449         }
450     // fall through
451     case ATTR_NAME: {
452         DOMString s = attr->value();
453         if (*s.unicode() == '#') {
454             name = QString(s.unicode() + 1, s.length() - 1).toLower();
455         } else {
456             name = s.string().toLower();
457         }
458         // ### make this work for XML documents, e.g. in case of <html:map...>
459         if (document()->isHTMLDocument()) {
460             static_cast<HTMLDocumentImpl *>(document())->mapMap[name] = this;
461         }
462 
463         //fallthrough
464     }
465     default:
466         HTMLElementImpl::parseAttribute(attr);
467     }
468 }
469 
areas()470 HTMLCollectionImpl *HTMLMapElementImpl::areas()
471 {
472     return new HTMLCollectionImpl(this, HTMLCollectionImpl::MAP_AREAS);
473 }
474 
475 // -------------------------------------------------------------------------
476 
HTMLAreaElementImpl(DocumentImpl * doc)477 HTMLAreaElementImpl::HTMLAreaElementImpl(DocumentImpl *doc)
478     : HTMLAnchorElementImpl(doc)
479 {
480     m_coords = nullptr;
481     m_coordsLen = 0;
482     nohref = false;
483     shape = Unknown;
484     lasth = lastw = -1;
485 }
486 
~HTMLAreaElementImpl()487 HTMLAreaElementImpl::~HTMLAreaElementImpl()
488 {
489     delete [] m_coords;
490 }
491 
id() const492 NodeImpl::Id HTMLAreaElementImpl::id() const
493 {
494     return ID_AREA;
495 }
496 
parseAttribute(AttributeImpl * attr)497 void HTMLAreaElementImpl::parseAttribute(AttributeImpl *attr)
498 {
499     switch (attr->id()) {
500     case ATTR_SHAPE:
501         if (strcasecmp(attr->value(), "default") == 0) {
502             shape = Default;
503         } else if (strcasecmp(attr->value(), "circle") == 0) {
504             shape = Circle;
505         } else if (strcasecmp(attr->value(), "poly") == 0 || strcasecmp(attr->value(),  "polygon") == 0) {
506             shape = Poly;
507         } else if (strcasecmp(attr->value(), "rect") == 0) {
508             shape = Rect;
509         }
510         break;
511     case ATTR_COORDS:
512         delete [] m_coords;
513         m_coords = attr->val()->toCoordsArray(m_coordsLen);
514         break;
515     case ATTR_NOHREF:
516         nohref = attr->val() != nullptr;
517         break;
518     case ATTR_TARGET:
519         m_hasTarget = attr->val() != nullptr;
520         break;
521     case ATTR_ALT:
522         break;
523     case ATTR_ACCESSKEY:
524         break;
525     default:
526         HTMLAnchorElementImpl::parseAttribute(attr);
527     }
528 }
529 
mapMouseEvent(int x_,int y_,int width_,int height_,RenderObject::NodeInfo & info)530 bool HTMLAreaElementImpl::mapMouseEvent(int x_, int y_, int width_, int height_,
531                                         RenderObject::NodeInfo &info)
532 {
533     bool inside = false;
534     if (width_ != lastw || height_ != lasth) {
535         region = getRegion(width_, height_);
536         lastw = width_; lasth = height_;
537     }
538     if (region.contains(QPoint(x_, y_))) {
539         inside = true;
540         info.setInnerNode(this);
541         info.setURLElement(this);
542     }
543 
544     return inside;
545 }
546 
getRect() const547 QRect HTMLAreaElementImpl::getRect() const
548 {
549     if (parentNode()->renderer() == nullptr) {
550         return QRect();
551     }
552     int dx, dy;
553     if (!parentNode()->renderer()->absolutePosition(dx, dy)) {
554         return QRect();
555     }
556     QRegion region = getRegion(lastw, lasth);
557     region.translate(dx, dy);
558     return region.boundingRect();
559 }
560 
getRegion(int width_,int height_) const561 QRegion HTMLAreaElementImpl::getRegion(int width_, int height_) const
562 {
563     QRegion region;
564     if (!m_coords) {
565         return region;
566     }
567 
568     // added broken HTML support (Dirk): some pages omit the SHAPE
569     // attribute, so we try to guess by looking at the coords count
570     // what the HTML author tried to tell us.
571 
572     // a Poly needs at least 3 points (6 coords), so this is correct
573     if ((shape == Poly || shape == Unknown) && m_coordsLen > 5) {
574         // make sure it is even
575         int len = m_coordsLen >> 1;
576         QPolygon points(len);
577         for (int i = 0; i < len; ++i)
578             points.setPoint(i, m_coords[(i << 1)].minWidth(width_),
579                             m_coords[(i << 1) + 1].minWidth(height_));
580         region = QRegion(points);
581     } else if ((shape == Circle && m_coordsLen >= 3) || (shape == Unknown && m_coordsLen == 3)) {
582         int r = qMin(m_coords[2].minWidth(width_), m_coords[2].minWidth(height_));
583         region = QRegion(m_coords[0].minWidth(width_) - r,
584                          m_coords[1].minWidth(height_) - r, 2 * r, 2 * r, QRegion::Ellipse);
585     } else if ((shape == Rect && m_coordsLen >= 4) || (shape == Unknown && m_coordsLen == 4)) {
586         int x0 = m_coords[0].minWidth(width_);
587         int y0 = m_coords[1].minWidth(height_);
588         int x1 = m_coords[2].minWidth(width_);
589         int y1 = m_coords[3].minWidth(height_);
590         // use qMin () and qAbs () to make sure that this works for any pair
591         // of opposite corners (x0,y0) and (x1,y1)
592         region = QRegion(qMin(x0, x1), qMin(y0, y1), qAbs(x1 - x0), qAbs(y1 - y0));
593     } else if (shape == Default) {
594         region = QRegion(0, 0, width_, height_);
595     }
596     // else
597     // return null region
598 
599     return region;
600 }
601