File indexing completed on 2024-05-05 16:10:13

0001 /**
0002  * This file is part of the DOM implementation for KDE.
0003  *
0004  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
0005  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
0006  *
0007  * This library is free software; you can redistribute it and/or
0008  * modify it under the terms of the GNU Library General Public
0009  * License as published by the Free Software Foundation; either
0010  * version 2 of the License, or (at your option) any later version.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Library General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Library General Public License
0018  * along with this library; see the file COPYING.LIB.  If not, write to
0019  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020  * Boston, MA 02110-1301, USA.
0021  */
0022 
0023 #include "html/html_imageimpl.h"
0024 #include "html/html_formimpl.h"
0025 #include "html/html_documentimpl.h"
0026 
0027 #include "khtmlview.h"
0028 #include "khtml_part.h"
0029 
0030 #include <kstringhandler.h>
0031 
0032 #include "rendering/render_image.h"
0033 #include "rendering/render_flow.h"
0034 #include "css/cssstyleselector.h"
0035 #include "css/cssproperties.h"
0036 #include "css/cssvalues.h"
0037 #include "xml/dom2_eventsimpl.h"
0038 
0039 #include <QCharRef>
0040 #include <QPoint>
0041 #include <QStack>
0042 #include <QImage>
0043 
0044 using namespace DOM;
0045 using namespace khtml;
0046 
0047 // -------------------------------------------------------------------------
0048 
0049 HTMLImageElementImpl::HTMLImageElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f)
0050     : HTMLElementImpl(doc), ismap(false), loadEventSent(true), unsafe(false), m_image(nullptr), m_form(f)
0051 {
0052     if (m_form) {
0053         m_form->registerImgElement(this);
0054     }
0055 }
0056 
0057 HTMLImageElementImpl::~HTMLImageElementImpl()
0058 {
0059     if (document()) {
0060         document()->removeImage(this);
0061     }
0062 
0063     if (m_image) {
0064         m_image->deref(this);
0065     }
0066 
0067     if (m_form) {
0068         m_form->removeImgElement(this);
0069     }
0070 }
0071 
0072 NodeImpl::Id HTMLImageElementImpl::id() const
0073 {
0074     return ID_IMG;
0075 }
0076 
0077 void HTMLImageElementImpl::parseAttribute(AttributeImpl *attr)
0078 {
0079     switch (attr->id()) {
0080     case ATTR_ALT:
0081         setChanged();
0082         break;
0083     case ATTR_SRC: {
0084         setChanged();
0085 
0086         //Start loading the image already, to generate events
0087         const DOMString imgSrcUrl = attr->value().trimSpaces();
0088         if (!imgSrcUrl.isEmpty()) { //### why do we not hide or something when setting this?
0089             CachedImage *newImage = document()->docLoader()->requestImage(imgSrcUrl);
0090             if (newImage && newImage != m_image) {
0091                 CachedImage *oldImage = m_image;
0092                 loadEventSent = false;
0093                 m_image = newImage;
0094                 m_image->ref(this);
0095                 if (oldImage) {
0096                     oldImage->deref(this);
0097                 }
0098             }
0099 
0100             if (m_image) {
0101                 const QUrl fullURL = QUrl(m_image->url().string());
0102                 if (document()->origin()->taintsCanvas(fullURL)) {
0103                     unsafe = true;
0104                 }
0105             }
0106         }
0107     }
0108     break;
0109     case ATTR_WIDTH:
0110         if (!attr->value().isEmpty()) {
0111             addCSSLength(CSS_PROP_WIDTH, attr->value());
0112         } else {
0113             removeCSSProperty(CSS_PROP_WIDTH);
0114         }
0115         break;
0116     case ATTR_HEIGHT:
0117         if (!attr->value().isEmpty()) {
0118             addCSSLength(CSS_PROP_HEIGHT, attr->value());
0119         } else {
0120             removeCSSProperty(CSS_PROP_HEIGHT);
0121         }
0122         break;
0123     case ATTR_BORDER:
0124         // border="noborder" -> border="0"
0125         if (attr->value().toInt()) {
0126             addCSSLength(CSS_PROP_BORDER_WIDTH, attr->value());
0127             addCSSProperty(CSS_PROP_BORDER_TOP_STYLE, CSS_VAL_SOLID);
0128             addCSSProperty(CSS_PROP_BORDER_RIGHT_STYLE, CSS_VAL_SOLID);
0129             addCSSProperty(CSS_PROP_BORDER_BOTTOM_STYLE, CSS_VAL_SOLID);
0130             addCSSProperty(CSS_PROP_BORDER_LEFT_STYLE, CSS_VAL_SOLID);
0131         } else {
0132             removeCSSProperty(CSS_PROP_BORDER_WIDTH);
0133             removeCSSProperty(CSS_PROP_BORDER_TOP_STYLE);
0134             removeCSSProperty(CSS_PROP_BORDER_RIGHT_STYLE);
0135             removeCSSProperty(CSS_PROP_BORDER_BOTTOM_STYLE);
0136             removeCSSProperty(CSS_PROP_BORDER_LEFT_STYLE);
0137         }
0138         break;
0139     case ATTR_VSPACE:
0140         addCSSLength(CSS_PROP_MARGIN_TOP, attr->value());
0141         addCSSLength(CSS_PROP_MARGIN_BOTTOM, attr->value());
0142         break;
0143     case ATTR_HSPACE:
0144         addCSSLength(CSS_PROP_MARGIN_LEFT, attr->value());
0145         addCSSLength(CSS_PROP_MARGIN_RIGHT, attr->value());
0146         break;
0147     case ATTR_ALIGN:
0148         addHTMLAlignment(attr->value());
0149         break;
0150     case ATTR_VALIGN:
0151         addCSSProperty(CSS_PROP_VERTICAL_ALIGN, attr->value().lower());
0152         break;
0153     case ATTR_USEMAP:
0154         if (attr->value()[0] == '#') {
0155             usemap = attr->value().lower();
0156         } else {
0157             QString url = document()->completeURL(attr->value().trimSpaces().string());
0158             // ### we remove the part before the anchor and hope
0159             // the map is on the same html page....
0160             usemap = url;
0161         }
0162         m_hasAnchor = attr->val() != nullptr;
0163         break;
0164     case ATTR_ISMAP:
0165         ismap = true;
0166         break;
0167     case ATTR_ONABORT: // ### add support for this
0168         setHTMLEventListener(EventImpl::ABORT_EVENT,
0169                              document()->createHTMLEventListener(attr->value().string(), "onabort", this));
0170         break;
0171     case ATTR_ONERROR:
0172         setHTMLEventListener(EventImpl::ERROR_EVENT,
0173                              document()->createHTMLEventListener(attr->value().string(), "onerror", this));
0174         break;
0175     case ATTR_ONLOAD:
0176         setHTMLEventListener(EventImpl::LOAD_EVENT,
0177                              document()->createHTMLEventListener(attr->value().string(), "onload", this));
0178         break;
0179     case ATTR_NOSAVE:
0180         break;
0181     case ATTR_NAME:
0182         if (inDocument() && m_name != attr->value()) {
0183             document()->underDocNamedCache().remove(m_name,        this);
0184             document()->underDocNamedCache().add(attr->value(), this);
0185         }
0186         m_name = attr->value();
0187     //fallthrough
0188     default:
0189         HTMLElementImpl::parseAttribute(attr);
0190     }
0191 }
0192 
0193 void HTMLImageElementImpl::notifyFinished(CachedObject *finishedObj)
0194 {
0195     if (m_image == finishedObj) {
0196         document()->dispatchImageLoadEventSoon(this);
0197     }
0198 }
0199 
0200 void HTMLImageElementImpl::dispatchLoadEvent()
0201 {
0202     if (!loadEventSent) {
0203         loadEventSent = true;
0204         if (m_image->isErrorImage()) {
0205             dispatchHTMLEvent(EventImpl::ERROR_EVENT, false, false);
0206         } else {
0207             dispatchHTMLEvent(EventImpl::LOAD_EVENT, false, false);
0208         }
0209     }
0210 }
0211 
0212 DOMString HTMLImageElementImpl::altText() const
0213 {
0214     // lets figure out the alt text.. magic stuff
0215     // https://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
0216     // also heavily discussed by Hixie on bugzilla
0217     DOMString alt(getAttribute(ATTR_ALT));
0218     // fall back to title attribute
0219     if (alt.isNull()) {
0220         alt = getAttribute(ATTR_TITLE);
0221     }
0222 #if 0
0223     if (alt.isNull()) {
0224         QString p = QUrl(document()->completeURL(getAttribute(ATTR_SRC).string())).toDisplayString();
0225         int pos;
0226         if ((pos = p.lastIndexOf('.')) > 0) {
0227             p.truncate(pos);
0228         }
0229         alt = DOMString(KStringHandler::csqueeze(p));
0230     }
0231 #endif
0232 
0233     return alt;
0234 }
0235 
0236 void HTMLImageElementImpl::attach()
0237 {
0238     assert(!attached());
0239     assert(!m_render);
0240     assert(parentNode());
0241 
0242     RenderStyle *_style = document()->styleSelector()->styleForElement(this);
0243     _style->ref();
0244     if (parentNode()->renderer() && parentNode()->renderer()->childAllowed() &&
0245             _style->display() != NONE) {
0246         m_render = new(document()->renderArena()) RenderImage(this);
0247         m_render->setStyle(_style);
0248         parentNode()->renderer()->addChild(m_render, nextRenderer());
0249     }
0250     _style->deref();
0251 
0252     NodeBaseImpl::attach();
0253     if (m_render) {
0254         m_render->updateFromElement();
0255     }
0256 }
0257 
0258 void HTMLImageElementImpl::removedFromDocument()
0259 {
0260     document()->underDocNamedCache().remove(m_name, this);
0261     HTMLElementImpl::removedFromDocument();
0262 }
0263 
0264 void HTMLImageElementImpl::insertedIntoDocument()
0265 {
0266     document()->underDocNamedCache().add(m_name, this);
0267     HTMLElementImpl::insertedIntoDocument();
0268 }
0269 
0270 void HTMLImageElementImpl::removeId(const DOMString &id)
0271 {
0272     document()->underDocNamedCache().remove(id, this);
0273     HTMLElementImpl::removeId(id);
0274 }
0275 
0276 void HTMLImageElementImpl::addId(const DOMString &id)
0277 {
0278     document()->underDocNamedCache().add(id, this);
0279     HTMLElementImpl::addId(id);
0280 }
0281 
0282 long HTMLImageElementImpl::width() const
0283 {
0284     if (!m_render) {
0285         DOMString widthAttr = getAttribute(ATTR_WIDTH);
0286         if (!widthAttr.isNull()) {
0287             return widthAttr.toInt();
0288         } else if (m_image && m_image->pixmap_size().isValid()) {
0289             return m_image->pixmap_size().width();
0290         } else {
0291             return 0;
0292         }
0293     }
0294 
0295     document()->updateLayout();
0296 
0297     return m_render ? m_render->contentWidth() :
0298            getAttribute(ATTR_WIDTH).toInt();
0299 }
0300 
0301 long HTMLImageElementImpl::height() const
0302 {
0303     if (!m_render) {
0304         DOMString heightAttr = getAttribute(ATTR_HEIGHT);
0305         if (!heightAttr.isNull()) {
0306             return heightAttr.toInt();
0307         } else if (m_image && m_image->pixmap_size().isValid()) {
0308             return m_image->pixmap_size().height();
0309         } else {
0310             return 0;
0311         }
0312     }
0313 
0314     document()->updateLayout();
0315 
0316     return m_render ? m_render->contentHeight() :
0317            getAttribute(ATTR_HEIGHT).toInt();
0318 }
0319 
0320 void HTMLImageElementImpl::setWidth(long width)
0321 {
0322     setAttribute(ATTR_WIDTH, QString::number(width));
0323 }
0324 
0325 void HTMLImageElementImpl::setHeight(long height)
0326 {
0327     setAttribute(ATTR_HEIGHT, QString::number(height));
0328 }
0329 
0330 QImage HTMLImageElementImpl::currentImage() const
0331 {
0332     if (!complete() || !m_image || !m_image->image()) {
0333         return QImage();
0334     }
0335 
0336     QImage *im = m_image->image()->qimage();
0337     if (im) {
0338         return *im;
0339     } else {
0340         return QImage();
0341     }
0342 }
0343 
0344 long HTMLImageElementImpl::x() const
0345 {
0346     if (renderer()) {
0347         int x = 0;
0348         int y = 0;
0349         renderer()->absolutePosition(x, y);
0350         return x;
0351     }
0352     return 0;
0353 }
0354 
0355 long HTMLImageElementImpl::y() const
0356 {
0357     if (renderer()) {
0358         int x = 0;
0359         int y = 0;
0360         renderer()->absolutePosition(x, y);
0361         return y;
0362     }
0363     return 0;
0364 }
0365 
0366 QPixmap HTMLImageElementImpl::currentPixmap() const
0367 {
0368     if (m_image) {
0369         return m_image->pixmap();
0370     }
0371 
0372     return QPixmap();
0373 }
0374 
0375 bool HTMLImageElementImpl::complete() const
0376 {
0377     return m_image && m_image->isComplete();
0378 }
0379 
0380 // -------------------------------------------------------------------------
0381 
0382 HTMLMapElementImpl::HTMLMapElementImpl(DocumentImpl *doc)
0383     : HTMLElementImpl(doc)
0384 {
0385 }
0386 
0387 HTMLMapElementImpl::~HTMLMapElementImpl()
0388 {
0389     if (document() && document()->isHTMLDocument()) {
0390         static_cast<HTMLDocumentImpl *>(document())->mapMap.remove(name);
0391     }
0392 }
0393 
0394 NodeImpl::Id HTMLMapElementImpl::id() const
0395 {
0396     return ID_MAP;
0397 }
0398 
0399 bool
0400 HTMLMapElementImpl::mapMouseEvent(int x_, int y_, int width_, int height_,
0401                                   RenderObject::NodeInfo &info)
0402 {
0403     //cout << "map:mapMouseEvent " << endl;
0404     //cout << x_ << " " << y_ <<" "<< width_ <<" "<< height_ << endl;
0405     QStack<NodeImpl *> nodeStack;
0406 
0407     NodeImpl *current = firstChild();
0408     while (1) {
0409         if (!current) {
0410             if (nodeStack.isEmpty()) {
0411                 break;
0412             }
0413             current = nodeStack.pop();
0414             current = current->nextSibling();
0415             continue;
0416         }
0417         if (current->id() == ID_AREA) {
0418             //cout << "area found " << endl;
0419             HTMLAreaElementImpl *area = static_cast<HTMLAreaElementImpl *>(current);
0420             if (area->mapMouseEvent(x_, y_, width_, height_, info)) {
0421                 return true;
0422             }
0423         }
0424         NodeImpl *child = current->firstChild();
0425         if (child) {
0426             nodeStack.push(current);
0427             current = child;
0428         } else {
0429             current = current->nextSibling();
0430         }
0431     }
0432 
0433     return false;
0434 }
0435 
0436 void HTMLMapElementImpl::parseAttribute(AttributeImpl *attr)
0437 {
0438     switch (attr->id()) {
0439     case ATTR_ID:
0440         if (document()->htmlMode() != DocumentImpl::XHtml) {
0441             HTMLElementImpl::parseAttribute(attr);
0442             break;
0443         } else {
0444             // add name with full url:
0445             QString url = document()->completeURL(attr->value().trimSpaces().string());
0446             if (document()->isHTMLDocument()) {
0447                 static_cast<HTMLDocumentImpl *>(document())->mapMap[url] = this;
0448             }
0449         }
0450     // fall through
0451     case ATTR_NAME: {
0452         DOMString s = attr->value();
0453         if (*s.unicode() == '#') {
0454             name = QString(s.unicode() + 1, s.length() - 1).toLower();
0455         } else {
0456             name = s.string().toLower();
0457         }
0458         // ### make this work for XML documents, e.g. in case of <html:map...>
0459         if (document()->isHTMLDocument()) {
0460             static_cast<HTMLDocumentImpl *>(document())->mapMap[name] = this;
0461         }
0462 
0463         //fallthrough
0464     }
0465     default:
0466         HTMLElementImpl::parseAttribute(attr);
0467     }
0468 }
0469 
0470 HTMLCollectionImpl *HTMLMapElementImpl::areas()
0471 {
0472     return new HTMLCollectionImpl(this, HTMLCollectionImpl::MAP_AREAS);
0473 }
0474 
0475 // -------------------------------------------------------------------------
0476 
0477 HTMLAreaElementImpl::HTMLAreaElementImpl(DocumentImpl *doc)
0478     : HTMLAnchorElementImpl(doc)
0479 {
0480     m_coords = nullptr;
0481     m_coordsLen = 0;
0482     nohref = false;
0483     shape = Unknown;
0484     lasth = lastw = -1;
0485 }
0486 
0487 HTMLAreaElementImpl::~HTMLAreaElementImpl()
0488 {
0489     delete [] m_coords;
0490 }
0491 
0492 NodeImpl::Id HTMLAreaElementImpl::id() const
0493 {
0494     return ID_AREA;
0495 }
0496 
0497 void HTMLAreaElementImpl::parseAttribute(AttributeImpl *attr)
0498 {
0499     switch (attr->id()) {
0500     case ATTR_SHAPE:
0501         if (strcasecmp(attr->value(), "default") == 0) {
0502             shape = Default;
0503         } else if (strcasecmp(attr->value(), "circle") == 0) {
0504             shape = Circle;
0505         } else if (strcasecmp(attr->value(), "poly") == 0 || strcasecmp(attr->value(),  "polygon") == 0) {
0506             shape = Poly;
0507         } else if (strcasecmp(attr->value(), "rect") == 0) {
0508             shape = Rect;
0509         }
0510         break;
0511     case ATTR_COORDS:
0512         delete [] m_coords;
0513         m_coords = attr->val()->toCoordsArray(m_coordsLen);
0514         break;
0515     case ATTR_NOHREF:
0516         nohref = attr->val() != nullptr;
0517         break;
0518     case ATTR_TARGET:
0519         m_hasTarget = attr->val() != nullptr;
0520         break;
0521     case ATTR_ALT:
0522         break;
0523     case ATTR_ACCESSKEY:
0524         break;
0525     default:
0526         HTMLAnchorElementImpl::parseAttribute(attr);
0527     }
0528 }
0529 
0530 bool HTMLAreaElementImpl::mapMouseEvent(int x_, int y_, int width_, int height_,
0531                                         RenderObject::NodeInfo &info)
0532 {
0533     bool inside = false;
0534     if (width_ != lastw || height_ != lasth) {
0535         region = getRegion(width_, height_);
0536         lastw = width_; lasth = height_;
0537     }
0538     if (region.contains(QPoint(x_, y_))) {
0539         inside = true;
0540         info.setInnerNode(this);
0541         info.setURLElement(this);
0542     }
0543 
0544     return inside;
0545 }
0546 
0547 QRect HTMLAreaElementImpl::getRect() const
0548 {
0549     if (parentNode()->renderer() == nullptr) {
0550         return QRect();
0551     }
0552     int dx, dy;
0553     if (!parentNode()->renderer()->absolutePosition(dx, dy)) {
0554         return QRect();
0555     }
0556     QRegion region = getRegion(lastw, lasth);
0557     region.translate(dx, dy);
0558     return region.boundingRect();
0559 }
0560 
0561 QRegion HTMLAreaElementImpl::getRegion(int width_, int height_) const
0562 {
0563     QRegion region;
0564     if (!m_coords) {
0565         return region;
0566     }
0567 
0568     // added broken HTML support (Dirk): some pages omit the SHAPE
0569     // attribute, so we try to guess by looking at the coords count
0570     // what the HTML author tried to tell us.
0571 
0572     // a Poly needs at least 3 points (6 coords), so this is correct
0573     if ((shape == Poly || shape == Unknown) && m_coordsLen > 5) {
0574         // make sure it is even
0575         int len = m_coordsLen >> 1;
0576         QPolygon points(len);
0577         for (int i = 0; i < len; ++i)
0578             points.setPoint(i, m_coords[(i << 1)].minWidth(width_),
0579                             m_coords[(i << 1) + 1].minWidth(height_));
0580         region = QRegion(points);
0581     } else if ((shape == Circle && m_coordsLen >= 3) || (shape == Unknown && m_coordsLen == 3)) {
0582         int r = qMin(m_coords[2].minWidth(width_), m_coords[2].minWidth(height_));
0583         region = QRegion(m_coords[0].minWidth(width_) - r,
0584                          m_coords[1].minWidth(height_) - r, 2 * r, 2 * r, QRegion::Ellipse);
0585     } else if ((shape == Rect && m_coordsLen >= 4) || (shape == Unknown && m_coordsLen == 4)) {
0586         int x0 = m_coords[0].minWidth(width_);
0587         int y0 = m_coords[1].minWidth(height_);
0588         int x1 = m_coords[2].minWidth(width_);
0589         int y1 = m_coords[3].minWidth(height_);
0590         // use qMin () and qAbs () to make sure that this works for any pair
0591         // of opposite corners (x0,y0) and (x1,y1)
0592         region = QRegion(qMin(x0, x1), qMin(y0, y1), qAbs(x1 - x0), qAbs(y1 - y0));
0593     } else if (shape == Default) {
0594         region = QRegion(0, 0, width_, height_);
0595     }
0596     // else
0597     // return null region
0598 
0599     return region;
0600 }