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 }