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