Reviewed by Ken.
[WebKit-https.git] / WebCore / kwq / KWQRenderTreeDebug.cpp
1 /*
2  * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "KWQRenderTreeDebug.h"
27
28 #include "dom_docimpl.h"
29 #include "dom_position.h"
30 #include "htmltags.h"
31 #include "jsediting.h"
32 #include "khtmlview.h"
33 #include "render_canvas.h"
34 #include "render_replaced.h"
35 #include "render_table.h"
36 #include "render_text.h"
37 #include "render_br.h"
38 #include "selection.h"
39
40 #include "KWQKHTMLPart.h"
41 #include "KWQTextStream.h"
42
43 using DOM::DocumentImpl;
44 using DOM::JSEditor;
45 using DOM::NodeImpl;
46 using DOM::Position;
47
48 using khtml::BorderValue;
49 using khtml::EBorderStyle;
50 using khtml::InlineTextBox;
51 using khtml::RenderLayer;
52 using khtml::RenderObject;
53 using khtml::RenderTableCell;
54 using khtml::RenderWidget;
55 using khtml::RenderText;
56 using khtml::RenderCanvas;
57 using khtml::RenderBR;
58 using khtml::Selection;
59 using khtml::transparentColor;
60
61 static void writeLayers(QTextStream &ts, const RenderLayer* rootLayer, RenderLayer* l,
62                         const QRect& paintDirtyRect, int indent=0);
63
64 static QTextStream &operator<<(QTextStream &ts, const QRect &r)
65 {
66     return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height();
67 }
68
69 static void writeIndent(QTextStream &ts, int indent)
70 {
71     for (int i = 0; i != indent; ++i) {
72         ts << "  ";
73     }
74 }
75
76 static void printBorderStyle(QTextStream &ts, const RenderObject &o, const EBorderStyle borderStyle)
77 {
78     switch (borderStyle) {
79         case khtml::BNONE:
80             ts << "none";
81             break;
82         case khtml::BHIDDEN:
83             ts << "hidden";
84             break;
85         case khtml::INSET:
86             ts << "inset";
87             break;
88         case khtml::GROOVE:
89             ts << "groove";
90             break;
91         case khtml::RIDGE:
92             ts << "ridge";
93             break;
94         case khtml::OUTSET:
95             ts << "outset";
96             break;
97         case khtml::DOTTED:
98             ts << "dotted";
99             break;
100         case khtml::DASHED:
101             ts << "dashed";
102             break;
103         case khtml::SOLID:
104             ts << "solid";
105             break;
106         case khtml::DOUBLE:
107             ts << "double";
108             break;
109     }
110     
111     ts << " ";
112 }
113
114 static QString getTagName(NodeImpl *n)
115 {
116     if (n->isDocumentNode())
117         return "";
118     if (n->id() <= ID_LAST_TAG)
119         return getTagName(n->id()).string();
120     return n->nodeName().string();
121 }
122
123 static QTextStream &operator<<(QTextStream &ts, const RenderObject &o)
124 {
125     ts << o.renderName();
126     
127     if (o.style() && o.style()->zIndex()) {
128         ts << " zI: " << o.style()->zIndex();
129     }
130     
131     if (o.element()) {
132         QString tagName = getTagName(o.element());
133         if (!tagName.isEmpty()) {
134             ts << " {" << tagName << "}";
135         }
136     }
137     
138     // FIXME: Will remove this <br> code once all layout tests pass.  Until then, we can't really change
139     // all the results easily.
140     bool usePositions = true;
141     if (o.isBR()) {
142         const RenderBR* br = static_cast<const RenderBR*>(&o);
143         usePositions = (br->firstTextBox() && br->firstTextBox()->isText());
144     }
145     QRect r(usePositions ? o.xPos() : 0, usePositions ? o.yPos() : 0, o.width(), o.height());
146     ts << " " << r;
147     
148     if (!o.isText()) {
149         if (o.parent() && (o.parent()->style()->color() != o.style()->color()))
150             ts << " [color=" << o.style()->color().name() << "]";
151         if (o.parent() && (o.parent()->style()->backgroundColor() != o.style()->backgroundColor()) &&
152             o.style()->backgroundColor().isValid() && 
153             o.style()->backgroundColor().rgb() != khtml::transparentColor)
154             // Do not dump invalid or transparent backgrounds, since that is the default.
155             ts << " [bgcolor=" << o.style()->backgroundColor().name() << "]";
156     
157         if (o.borderTop() || o.borderRight() || o.borderBottom() || o.borderLeft()) {
158             ts << " [border:";
159             
160             BorderValue prevBorder;
161             if (o.style()->borderTop() != prevBorder) {
162                 prevBorder = o.style()->borderTop();
163                 if (!o.borderTop())
164                     ts << " none";
165                 else {
166                     ts << " (" << o.borderTop() << "px ";
167                     printBorderStyle(ts, o, o.style()->borderTopStyle());
168                     QColor col = o.style()->borderTopColor();
169                     if (!col.isValid()) col = o.style()->color();
170                     ts << col.name() << ")";
171                 }
172             }
173             
174             if (o.style()->borderRight() != prevBorder) {
175                 prevBorder = o.style()->borderRight();
176                 if (!o.borderRight())
177                     ts << " none";
178                 else {
179                     ts << " (" << o.borderRight() << "px ";
180                     printBorderStyle(ts, o, o.style()->borderRightStyle());
181                     QColor col = o.style()->borderRightColor();
182                     if (!col.isValid()) col = o.style()->color();
183                     ts << col.name() << ")";
184                 }
185             }
186             
187             if (o.style()->borderBottom() != prevBorder) {
188                 prevBorder = o.style()->borderBottom();
189                 if (!o.borderBottom())
190                     ts << " none";
191                 else {
192                     ts << " (" << o.borderBottom() << "px ";
193                     printBorderStyle(ts, o, o.style()->borderBottomStyle());
194                     QColor col = o.style()->borderBottomColor();
195                     if (!col.isValid()) col = o.style()->color();
196                     ts << col.name() << ")";
197                 }
198             }
199             
200             if (o.style()->borderLeft() != prevBorder) {
201                 prevBorder = o.style()->borderLeft();
202                 if (!o.borderLeft())
203                     ts << " none";
204                 else {                    
205                     ts << " (" << o.borderLeft() << "px ";
206                     printBorderStyle(ts, o, o.style()->borderLeftStyle());
207                     QColor col = o.style()->borderLeftColor();
208                     if (!col.isValid()) col = o.style()->color();
209                     ts << col.name() << ")";
210                 }
211             }
212             
213             ts << "]";
214         }
215     }
216     
217     if (o.isTableCell()) {
218         const RenderTableCell &c = static_cast<const RenderTableCell &>(o);
219         ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
220     }
221
222     return ts;
223 }
224
225 static QString quoteAndEscapeNonPrintables(const QString &s)
226 {
227     QString result;
228     result += '"';
229     for (uint i = 0; i != s.length(); ++i) {
230         QChar c = s.at(i);
231         if (c == '\\') {
232             result += "\\\\";
233         } else if (c == '"') {
234             result += "\\\"";
235         } else if (c == '\n' || c.unicode() == 0x00A0) {
236             result += ' ';
237         } else {
238             ushort u = c.unicode();
239             if (u >= 0x20 && u < 0x7F) {
240                 result += c;
241             } else {
242                 QString hex;
243                 hex.sprintf("\\x{%X}", u);
244                 result += hex;
245             }
246         }
247     }
248     result += '"';
249     return result;
250 }
251
252 static void writeTextRun(QTextStream &ts, const RenderText &o, const InlineTextBox &run)
253 {
254     ts << "text run at (" << run.m_x << "," << run.m_y << ") width " << run.m_width << ": "
255         << quoteAndEscapeNonPrintables(o.data().string().mid(run.m_start, run.m_len))
256         << "\n"; 
257 }
258
259 static void write(QTextStream &ts, const RenderObject &o, int indent = 0)
260 {
261     writeIndent(ts, indent);
262     
263     ts << o << "\n";
264     
265     if (o.isText() && !o.isBR()) {
266         const RenderText &text = static_cast<const RenderText &>(o);
267         for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
268             writeIndent(ts, indent+1);
269             writeTextRun(ts, text, *box);
270         }
271     }
272
273     for (RenderObject *child = o.firstChild(); child; child = child->nextSibling()) {
274         if (child->layer()) {
275             continue;
276         }
277         write(ts, *child, indent + 1);
278     }
279     
280     if (o.isWidget()) {
281         QWidget *widget = static_cast<const RenderWidget &>(o).widget();
282         if (widget && widget->inherits("KHTMLView")) {
283             KHTMLView *view = static_cast<KHTMLView *>(widget);
284             RenderObject *root = KWQ(view->part())->renderer();
285             if (root) {
286                 view->layout();
287                 RenderLayer* l = root->layer();
288                 if (l)
289                     writeLayers(ts, l, l, QRect(l->xPos(), l->yPos(), l->width(), l->height()), indent+1);
290             }
291         }
292     }
293 }
294
295 static void write(QTextStream &ts, RenderLayer &l,
296                   const QRect& layerBounds, const QRect& backgroundClipRect, const QRect& clipRect,
297                   int layerType = 0, int indent = 0)
298 {
299     writeIndent(ts, indent);
300     
301     ts << "layer";
302     ts << " " << layerBounds;
303
304     if (layerBounds != layerBounds.intersect(backgroundClipRect))
305         ts << " backgroundClip " << backgroundClipRect;
306     if (layerBounds != layerBounds.intersect(clipRect))
307         ts << " clip " << clipRect;
308
309     if (l.renderer()->hasOverflowClip()) {
310         if (l.scrollXOffset())
311             ts << " scrollX " << l.scrollXOffset();
312         if (l.scrollYOffset())
313             ts << " scrollY " << l.scrollYOffset();
314         if (l.renderer()->clientWidth() != l.scrollWidth())
315             ts << " scrollWidth " << l.scrollWidth();
316         if (l.renderer()->clientHeight() != l.scrollHeight())
317             ts << " scrollHeight " << l.scrollHeight();
318     }
319
320     if (layerType == -1)
321         ts << " layerType: background only";
322     else if (layerType == 1)
323         ts << " layerType: foreground only";
324     
325     ts << "\n";
326
327     if (layerType != -1)
328         write(ts, *l.renderer(), indent + 1);
329 }
330     
331 static void writeLayers(QTextStream &ts, const RenderLayer* rootLayer, RenderLayer* l,
332                         const QRect& paintDirtyRect, int indent)
333 {
334     // Calculate the clip rects we should use.
335     QRect layerBounds, damageRect, clipRectToApply;
336     l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply);
337     
338     // Ensure our z-order lists are up-to-date.
339     l->updateZOrderLists();
340
341     bool shouldPaint = l->intersectsDamageRect(layerBounds, damageRect);
342     QPtrVector<RenderLayer>* negList = l->negZOrderList();
343     if (shouldPaint && negList && negList->count() > 0)
344         write(ts, *l, layerBounds, damageRect, clipRectToApply, -1, indent);
345
346     if (negList) {
347         for (unsigned i = 0; i != negList->count(); ++i)
348             writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, indent);
349     }
350
351     if (shouldPaint)
352         write(ts, *l, layerBounds, damageRect, clipRectToApply, negList && negList->count() > 0, indent);
353
354     QPtrVector<RenderLayer>* posList = l->posZOrderList();
355     if (posList) {
356         for (unsigned i = 0; i != posList->count(); ++i)
357             writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, indent);
358     }
359 }
360
361 static QString nodePositionRelativeToRoot(NodeImpl *node, NodeImpl *root)
362 {
363     QString result;
364
365     NodeImpl *n = node;
366     while (1) {
367         NodeImpl *p = n->parentNode();
368         if (!p || n == root) {
369             result += " of root {" + getTagName(n) + "}";
370             break;
371         }
372         if (n != node)
373             result +=  " of ";
374         int count = 1;
375         for (NodeImpl *search = p->firstChild(); search != n; search = search->nextSibling())
376             count++;
377         result +=  "child " + QString::number(count) + " {" + getTagName(n) + "}";
378         n = p;
379     }
380     
381     return result;
382 }
383
384 static void writeSelection(QTextStream &ts, const RenderObject *o)
385 {
386     NodeImpl *n = o->element();
387     if (!n || !n->isDocumentNode())
388         return;
389
390     DocumentImpl *doc = static_cast<DocumentImpl *>(n);
391     if (!doc->part())
392         return;
393     
394     Selection selection = doc->part()->selection();
395     if (selection.isNone())
396         return;
397
398     if (!selection.start().node()->isContentEditable() || !selection.end().node()->isContentEditable())
399         return;
400
401     Position startPosition = selection.start();
402     Position endPosition = selection.end();
403
404     QString startNodeTagName(getTagName(startPosition.node()));
405     QString endNodeTagName(getTagName(endPosition.node()));
406     
407     NodeImpl *rootNode = doc->getElementById("root");
408     
409     if (selection.isCaret()) {
410         Position upstream = startPosition.upstream(DOM::StayInBlock);
411         Position downstream = startPosition.downstream(DOM::StayInBlock);
412         QString positionString = nodePositionRelativeToRoot(startPosition.node(), rootNode);
413         QString upstreamString = nodePositionRelativeToRoot(upstream.node(), rootNode);
414         QString downstreamString = nodePositionRelativeToRoot(downstream.node(), rootNode);
415         ts << "selection is CARET:\n" << 
416             "start:      position " << startPosition.offset() << " of " << positionString << "\n"
417             "upstream:   position " << upstream.offset() << " of " << upstreamString << "\n"
418             "downstream: position " << downstream.offset() << " of " << downstreamString << "\n"; 
419     }
420     else if (selection.isRange()) {
421         QString startString = nodePositionRelativeToRoot(startPosition.node(), rootNode);
422         Position upstreamStart = startPosition.upstream(DOM::StayInBlock);
423         QString upstreamStartString = nodePositionRelativeToRoot(upstreamStart.node(), rootNode);
424         Position downstreamStart = startPosition.downstream(DOM::StayInBlock);
425         QString downstreamStartString = nodePositionRelativeToRoot(downstreamStart.node(), rootNode);
426         QString endString = nodePositionRelativeToRoot(endPosition.node(), rootNode);
427         Position upstreamEnd = endPosition.upstream(DOM::StayInBlock);
428         QString upstreamEndString = nodePositionRelativeToRoot(upstreamEnd.node(), rootNode);
429         Position downstreamEnd = endPosition.downstream(DOM::StayInBlock);
430         QString downstreamEndString = nodePositionRelativeToRoot(downstreamEnd.node(), rootNode);
431         ts << "selection is RANGE:\n" <<
432             "start:      position " << startPosition.offset() << " of " << startString << "\n" <<
433             "upstream:   position " << upstreamStart.offset() << " of " << upstreamStartString << "\n"
434             "downstream: position " << downstreamStart.offset() << " of " << downstreamStartString << "\n"
435             "end:        position " << endPosition.offset() << " of " << endString << "\n"
436             "upstream:   position " << upstreamEnd.offset() << " of " << upstreamEndString << "\n"
437             "downstream: position " << downstreamEnd.offset() << " of " << downstreamEndString << "\n"; 
438     }
439 }
440
441 static bool debuggingRenderTreeFlag = false;
442
443 bool debuggingRenderTree()
444 {
445     return debuggingRenderTreeFlag;
446 }
447
448 QString externalRepresentation(RenderObject *o)
449 {
450     debuggingRenderTreeFlag = true;
451     JSEditor::setSupportsPasteCommand(true);
452
453     QString s;
454     {
455         QTextStream ts(&s);
456         if (o) {
457             // FIXME: Hiding the vertical scrollbar is a total hack to preserve the
458             // layout test results until I can figure out what the heck is going on. -dwh
459             o->canvas()->view()->setVScrollBarMode(QScrollView::AlwaysOff);
460             o->canvas()->view()->layout();
461             RenderLayer* l = o->layer();
462             if (l) {
463                 writeLayers(ts, l, l, QRect(l->xPos(), l->yPos(), l->width(), l->height()));
464                 writeSelection(ts, o);
465             }
466         }
467     }
468     return s;
469 }