Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / WebCore / inspector / InspectorOverlayPage.js
1 const boundsColor = "rgba(0,0,0,0.4)";
2 const lightGridColor = "rgba(0,0,0,0.2)";
3 const darkGridColor = "rgba(0,0,0,0.5)";
4 const transparentColor = "rgba(0, 0, 0, 0)";
5 const gridBackgroundColor = "rgba(255, 255, 255, 0.6)";
6
7 // CSS Shapes highlight colors
8 const shapeHighlightColor = "rgba(96, 82, 127, 0.8)";
9 const shapeMarginHighlightColor = "rgba(96, 82, 127, 0.6)";
10
11 const paintRectFillColor = "rgba(255, 0, 0, 0.5)";
12
13 const elementTitleFillColor = "rgb(255, 255, 194)";
14 const elementTitleStrokeColor = "rgb(128, 128, 128)";
15
16 let DATA = {};
17
18 class Bounds {
19     constructor()
20     {
21         this._minX = Number.MAX_VALUE;
22         this._minY = Number.MAX_VALUE;
23         this._maxX = Number.MIN_VALUE;
24         this._maxY = Number.MIN_VALUE;
25     }
26
27     // Public
28
29     get minX() { return this._minX; }
30     get minY() { return this._minY; }
31     get maxX() { return this._maxX; }
32     get maxY() { return this._maxY; }
33
34     update(x, y)
35     {
36         this._minX = Math.min(this._minX, x);
37         this._minY = Math.min(this._minY, y);
38         this._maxX = Math.max(this._maxX, x);
39         this._maxY = Math.max(this._maxY, y);
40     }
41 }
42
43 function drawPausedInDebuggerMessage(message)
44 {
45     var pausedInDebugger = document.getElementById("paused-in-debugger");
46     pausedInDebugger.textContent = message;
47     pausedInDebugger.style.visibility = "visible";
48     document.body.classList.add("dimmed");
49 }
50
51 function drawNodeHighlight(allHighlights)
52 {
53     let bounds = new Bounds;
54
55     for (let highlight of allHighlights) {
56         _isolateActions(() => {
57             context.translate(-highlight.scrollOffset.x, -highlight.scrollOffset.y);
58
59             for (let fragment of highlight.fragments)
60                 _drawFragmentHighlight(fragment, bounds);
61
62             if (highlight.elementData && highlight.elementData.shapeOutsideData)
63                 _drawShapeHighlight(highlight.elementData.shapeOutsideData, bounds);
64         });
65     }
66
67     if (DATA.showRulers)
68         _drawBounds(bounds);
69
70     if (allHighlights.length === 1) {
71         for (let fragment of allHighlights[0].fragments)
72             _drawElementTitle(allHighlights[0].elementData, fragment, allHighlights[0].scrollOffset);
73     }
74 }
75
76 function drawQuadHighlight(highlight)
77 {
78     let bounds = new Bounds;
79
80     _isolateActions(() => {
81         _drawOutlinedQuad(highlight.quads[0], highlight.contentColor, highlight.contentOutlineColor, bounds);
82     });
83
84     if (DATA.showRulers)
85         _drawBounds(bounds);
86 }
87
88 function quadEquals(quad1, quad2)
89 {
90     return quad1[0].x === quad2[0].x && quad1[0].y === quad2[0].y &&
91         quad1[1].x === quad2[1].x && quad1[1].y === quad2[1].y &&
92         quad1[2].x === quad2[2].x && quad1[2].y === quad2[2].y &&
93         quad1[3].x === quad2[3].x && quad1[3].y === quad2[3].y;
94 }
95
96 function updatePaintRects(paintRectList)
97 {
98     var context = paintRectsCanvas.getContext("2d");
99     context.save();
100     context.scale(window.devicePixelRatio, window.devicePixelRatio);
101
102     context.clearRect(0, 0, paintRectsCanvas.width, paintRectsCanvas.height);
103
104     context.fillStyle = paintRectFillColor;
105
106     for (var rectObject of paintRectList)
107         context.fillRect(rectObject.x, rectObject.y, rectObject.width, rectObject.height);
108
109     context.restore();
110 }
111
112 function drawRulers()
113 {
114     const gridLabelSize = 13;
115     const gridSize = 15;
116     const gridStepIncrement = 50;
117     const gridStepLength = 8;
118     const gridSubStepIncrement = 5;
119     const gridSubStepLength = 5;
120
121
122     let pageFactor = DATA.pageZoomFactor * DATA.pageScaleFactor;
123     let scrollX = DATA.scrollOffset.x * DATA.pageScaleFactor;
124     let scrollY = DATA.scrollOffset.y * DATA.pageScaleFactor;
125
126     function zoom(value) {
127         return value * pageFactor;
128     }
129
130     function unzoom(value) {
131         return value / pageFactor;
132     }
133
134     function multipleBelow(value, step) {
135         return value - (value % step);
136     }
137
138     let width = DATA.viewportSize.width / pageFactor;
139     let height = DATA.viewportSize.height / pageFactor;
140     let minX = unzoom(scrollX);
141     let minY = unzoom(scrollY);
142     let maxX = minX + width;
143     let maxY = minY + height;
144
145     // Draw backgrounds
146     _isolateActions(() => {
147         let offsetX = DATA.contentInset.width + gridSize;
148         let offsetY = DATA.contentInset.height + gridSize;
149
150         context.fillStyle = gridBackgroundColor;
151         context.fillRect(DATA.contentInset.width, DATA.contentInset.height, gridSize, gridSize);
152         context.fillRect(offsetX, DATA.contentInset.height, zoom(width) - offsetX, gridSize);
153         context.fillRect(DATA.contentInset.width, offsetY, gridSize, zoom(height) - offsetY);
154     });
155
156     // Ruler styles
157     _isolateActions(() => {
158         context.lineWidth = 1;
159         context.fillStyle = darkGridColor;
160
161         // Draw labels
162         _isolateActions(() => {
163             context.translate(DATA.contentInset.width - scrollX, DATA.contentInset.height - scrollY);
164
165             for (let x = multipleBelow(minX, gridStepIncrement * 2); x < maxX; x += gridStepIncrement * 2) {
166                 if (!x && !scrollX)
167                     continue;
168
169                 _isolateActions(() => {
170                     context.translate(zoom(x) + 0.5, scrollY);
171                     context.fillText(x, 2, gridLabelSize);
172                 });
173             }
174
175             for (let y = multipleBelow(minY, gridStepIncrement * 2); y < maxY; y += gridStepIncrement * 2) {
176                 if (!y && !scrollY)
177                     continue;
178
179                 _isolateActions(() => {
180                     context.translate(scrollX, zoom(y) + 0.5);
181                     context.rotate(-Math.PI / 2);
182                     context.fillText(y, 2, gridLabelSize);
183                 });
184             }
185         });
186
187         // Draw horizontal grid
188         _isolateActions(() => {
189             context.translate(DATA.contentInset.width - scrollX + 0.5, DATA.contentInset.height - scrollY);
190
191             for (let x = multipleBelow(minX, gridSubStepIncrement); x < maxX; x += gridSubStepIncrement) {
192                 if (!x && !scrollX)
193                     continue;
194
195                 context.beginPath();
196                 context.moveTo(zoom(x), scrollY);
197
198                 if (x % gridStepIncrement) {
199                     context.strokeStyle = lightGridColor;
200                     context.lineTo(zoom(x), scrollY + gridSubStepLength);
201                 } else {
202                     context.strokeStyle = darkGridColor;
203                     context.lineTo(zoom(x), scrollY + ((x % (gridStepIncrement * 2)) ? gridSubStepLength : gridStepLength));
204                 }
205
206                 context.stroke();
207             }
208         });
209
210         // Draw vertical grid
211         _isolateActions(() => {
212             context.translate(DATA.contentInset.width - scrollX, DATA.contentInset.height - scrollY + 0.5);
213
214             for (let y = multipleBelow(minY, gridSubStepIncrement); y < maxY; y += gridSubStepIncrement) {
215                 if (!y && !scrollY)
216                     continue;
217
218                 context.beginPath();
219                 context.moveTo(scrollX, zoom(y));
220
221                 if (y % gridStepIncrement) {
222                     context.strokeStyle = lightGridColor;
223                     context.lineTo(scrollX + gridSubStepLength, zoom(y));
224                 } else {
225                     context.strokeStyle = darkGridColor;
226                     context.lineTo(scrollX + ((y % (gridStepIncrement * 2)) ? gridSubStepLength : gridStepLength), zoom(y));
227                 }
228
229                 context.stroke();
230             }
231         });
232     });
233 }
234
235 function reset(payload)
236 {
237     DATA.viewportSize = payload.viewportSize;
238     DATA.deviceScaleFactor = payload.deviceScaleFactor;
239     DATA.pageScaleFactor = payload.pageScaleFactor;
240     DATA.pageZoomFactor = payload.pageZoomFactor;
241     DATA.scrollOffset = payload.scrollOffset;
242     DATA.contentInset = payload.contentInset;
243     DATA.showRulers = payload.showRulers;
244
245     window.canvas = document.getElementById("canvas");
246     window.paintRectsCanvas = document.getElementById("paintrects-canvas");
247
248     window.context = canvas.getContext("2d");
249
250     canvas.width = DATA.deviceScaleFactor * DATA.viewportSize.width;
251     canvas.height = DATA.deviceScaleFactor * DATA.viewportSize.height;
252     canvas.style.width = DATA.viewportSize.width + "px";
253     canvas.style.height = DATA.viewportSize.height + "px";
254     context.scale(DATA.deviceScaleFactor, DATA.deviceScaleFactor);
255     context.clearRect(0, 0, canvas.width, canvas.height);
256
257     // We avoid getting the context for the paint rects canvas until we need to paint, to avoid backing store allocation.
258     paintRectsCanvas.width = DATA.deviceScaleFactor * DATA.viewportSize.width;
259     paintRectsCanvas.height = DATA.deviceScaleFactor * DATA.viewportSize.height;
260     paintRectsCanvas.style.width = DATA.viewportSize.width + "px";
261     paintRectsCanvas.style.height = DATA.viewportSize.height + "px";
262
263     document.getElementById("paused-in-debugger").style.visibility = "hidden";
264     document.getElementById("element-title-container").textContent = "";
265     document.body.classList.remove("dimmed");
266
267     document.getElementById("log").style.setProperty("top", DATA.contentInset.height + "px");
268 }
269
270 function DOMBuilder(tagName, className)
271 {
272     this.element = document.createElement(tagName);
273     this.element.className = className;
274 }
275
276 DOMBuilder.prototype.appendTextNode = function(content)
277 {
278     let node = document.createTextNode(content);
279     this.element.appendChild(node);
280     return node;
281 }
282
283 DOMBuilder.prototype.appendSpan = function(className, value)
284 {
285     var span = document.createElement("span");
286     span.className = className;
287     span.textContent = value;
288     this.element.appendChild(span);
289     return span;
290 }
291
292 DOMBuilder.prototype.appendSpanIfNotNull = function(className, value, prefix)
293 {
294     return value ? this.appendSpan(className, (prefix ? prefix : "") + value) : null;
295 }
296
297 DOMBuilder.prototype.appendProperty = function(className, propertyName, value)
298 {
299     var builder = new DOMBuilder("div", className);
300     builder.appendSpan("css-property", propertyName);
301     builder.appendTextNode(" ");
302     builder.appendSpan("value", value);
303     this.element.appendChild(builder.element);
304     return builder.element;
305 }
306
307 DOMBuilder.prototype.appendPropertyIfNotNull = function(className, propertyName, value)
308 {
309     return value ? this.appendProperty(className, propertyName, value) : null;
310 }
311
312 function _truncateString(value, maxLength)
313 {
314     return value && value.length > maxLength ? value.substring(0, 50) + "\u2026" : value;
315 }
316
317 function _isolateActions(func) {
318     context.save();
319     func();
320     context.restore();
321 }
322
323 function _quadToPath(quad, bounds)
324 {
325     function parseQuadPoint(point) {
326         bounds.update(point.x, point.y);
327         return [point.x, point.y];
328     }
329
330     context.beginPath();
331     context.moveTo(...parseQuadPoint(quad[0]));
332     context.lineTo(...parseQuadPoint(quad[1]));
333     context.lineTo(...parseQuadPoint(quad[2]));
334     context.lineTo(...parseQuadPoint(quad[3]));
335     context.closePath();
336     return context;
337 }
338
339 function _drawOutlinedQuad(quad, fillColor, outlineColor, bounds)
340 {
341     _isolateActions(() => {
342         context.lineWidth = 2;
343         _quadToPath(quad, bounds);
344         context.clip();
345         context.fillStyle = fillColor;
346         context.fill();
347         if (outlineColor) {
348             context.strokeStyle = outlineColor;
349             context.stroke();
350         }
351     });
352 }
353
354 function _drawPath(context, commands, fillColor, bounds)
355 {
356     let commandsIndex = 0;
357
358     function parsePoints(count) {
359         let parsed = [];
360         for (let i = 0; i < count; ++i) {
361             let x = commands[commandsIndex++];
362             parsed.push(x);
363
364             let y = commands[commandsIndex++];
365             parsed.push(y);
366
367             bounds.update(x, y);
368         }
369         return parsed;
370     }
371
372     _isolateActions(() => {
373         context.beginPath();
374
375         while (commandsIndex < commands.length) {
376             switch (commands[commandsIndex++]) {
377             case 'M':
378                 context.moveTo(...parsePoints(1));
379                 break;
380             case 'L':
381                 context.lineTo(...parsePoints(1));
382                 break;
383             case 'C':
384                 context.bezierCurveTo(...parsePoints(3));
385                 break;
386             case 'Q':
387                 context.quadraticCurveTo(...parsePoints(2));
388                 break;
389             case 'Z':
390                 context.closePath();
391                 break;
392             }
393         }
394
395         context.closePath();
396         context.fillStyle = fillColor;
397         context.fill();
398     });
399 }
400
401 function _drawOutlinedQuadWithClip(quad, clipQuad, fillColor, bounds)
402 {
403     _isolateActions(() => {
404         context.fillStyle = fillColor;
405         context.lineWidth = 0;
406         _quadToPath(quad, bounds);
407         context.fill();
408
409         context.globalCompositeOperation = "destination-out";
410         context.fillStyle = "red";
411         _quadToPath(clipQuad, bounds);
412         context.fill();
413     });
414 }
415
416 function _drawBounds(bounds)
417 {
418     _isolateActions(() => {
419         let startX = DATA.contentInset.width;
420         let startY = DATA.contentInset.height;
421
422         context.beginPath();
423
424         if (bounds.minY - startY > 0) {
425             context.moveTo(bounds.minX, bounds.minY);
426             context.lineTo(bounds.minX, startY);
427
428             context.moveTo(bounds.maxX, bounds.minY);
429             context.lineTo(bounds.maxX, startY);
430         }
431
432         if (bounds.minX - startX > 0) {
433             context.moveTo(bounds.minX, bounds.minY);
434             context.lineTo(startX, bounds.minY);
435
436             context.moveTo(bounds.minX, bounds.maxY);
437             context.lineTo(startX, bounds.maxY);
438         }
439
440         context.lineWidth = 1;
441         context.strokeStyle = boundsColor;
442         context.stroke();
443     });
444 }
445
446 function _createElementTitle(elementData)
447 {
448     let builder = new DOMBuilder("div", "element-title");
449
450     builder.appendSpanIfNotNull("tag-name", elementData.tagName);
451     builder.appendSpanIfNotNull("node-id", CSS.escape(elementData.idValue), "#");
452
453     let classes = elementData.classes;
454     if (classes && classes.length)
455         builder.appendSpan("class-name", _truncateString(classes.map((className) => "." + CSS.escape(className)).join(""), 50));
456
457     builder.appendSpanIfNotNull("pseudo-type", elementData.pseudoElement, "::");
458
459     builder.appendTextNode(" ");
460     builder.appendSpan("node-width", elementData.size.width);
461     // \xd7 is the code for the &times; HTML entity.
462     builder.appendSpan("px", "px \xd7 ");
463     builder.appendSpan("node-height", elementData.size.height);
464     builder.appendSpan("px", "px");
465
466     builder.appendPropertyIfNotNull("role-name", "Role", elementData.role);
467
468     document.getElementById("element-title-container").appendChild(builder.element);
469
470     return builder.element;
471 }
472
473 function _drawElementTitle(elementData, fragmentHighlight, scroll)
474 {
475     if (!elementData || !fragmentHighlight.quads.length)
476         return;
477
478     var elementTitle = _createElementTitle(elementData);
479
480     var marginQuad = fragmentHighlight.quads[0];
481
482     var titleWidth = elementTitle.offsetWidth + 6;
483     var titleHeight = elementTitle.offsetHeight + 4;
484
485     var anchorTop = marginQuad[0].y;
486     var anchorBottom = marginQuad[3].y;
487
488     const arrowHeight = 7;
489     var renderArrowUp = false;
490     var renderArrowDown = false;
491
492     var boxX = marginQuad[0].x;
493
494     boxX = Math.max(2, boxX - scroll.x);
495     anchorTop -= scroll.y;
496     anchorBottom -= scroll.y;
497
498     var viewportWidth = DATA.viewportSize.width;
499     if (boxX + titleWidth > viewportWidth)
500         boxX = viewportWidth - titleWidth - 2;
501
502     var viewportHeight = DATA.viewportSize.height;
503     var viewportTop = DATA.contentInset.height;
504
505     var boxY;
506     if (anchorTop > viewportHeight) {
507         boxY = canvas.height - titleHeight - arrowHeight;
508         renderArrowDown = true;
509     } else if (anchorBottom < viewportTop) {
510         boxY = arrowHeight;
511         renderArrowUp = true;
512     } else if (anchorBottom + titleHeight + arrowHeight < viewportHeight) {
513         boxY = anchorBottom + arrowHeight - 4;
514         renderArrowUp = true;
515     } else if (anchorTop - titleHeight - arrowHeight > viewportTop) {
516         boxY = anchorTop - titleHeight - arrowHeight + 3;
517         renderArrowDown = true;
518     } else
519         boxY = arrowHeight;
520
521     _isolateActions(() => {
522         context.translate(0.5, 0.5);
523         context.beginPath();
524         context.moveTo(boxX, boxY);
525         if (renderArrowUp) {
526             context.lineTo(boxX + (2 * arrowHeight), boxY);
527             context.lineTo(boxX + (3 * arrowHeight), boxY - arrowHeight);
528             context.lineTo(boxX + (4 * arrowHeight), boxY);
529         }
530         context.lineTo(boxX + titleWidth, boxY);
531         context.lineTo(boxX + titleWidth, boxY + titleHeight);
532         if (renderArrowDown) {
533             context.lineTo(boxX + (4 * arrowHeight), boxY + titleHeight);
534             context.lineTo(boxX + (3 * arrowHeight), boxY + titleHeight + arrowHeight);
535             context.lineTo(boxX + (2 * arrowHeight), boxY + titleHeight);
536         }
537         context.lineTo(boxX, boxY + titleHeight);
538         context.closePath();
539         context.fillStyle = elementTitleFillColor;
540         context.fill();
541         context.strokeStyle = elementTitleStrokeColor;
542         context.stroke();
543     });
544
545     elementTitle.style.top = (boxY + 3) + "px";
546     elementTitle.style.left = (boxX + 3) + "px";
547 }
548
549 function _drawShapeHighlight(shapeInfo, bounds)
550 {
551     if (shapeInfo.marginShape)
552         _drawPath(context, shapeInfo.marginShape, shapeMarginHighlightColor, bounds);
553
554     if (shapeInfo.shape)
555         _drawPath(context, shapeInfo.shape, shapeHighlightColor, bounds);
556
557     if (!(shapeInfo.shape || shapeInfo.marginShape))
558         _drawOutlinedQuad(shapeInfo.bounds, shapeHighlightColor, shapeHighlightColor, bounds);
559 }
560
561 function _drawFragmentHighlight(highlight, bounds)
562 {
563     if (!highlight.quads.length)
564         return;
565
566     _isolateActions(() => {
567         let quads = highlight.quads.slice();
568         let contentQuad = quads.pop();
569         let paddingQuad = quads.pop();
570         let borderQuad = quads.pop();
571         let marginQuad = quads.pop();
572
573         let hasContent = contentQuad && highlight.contentColor !== transparentColor || highlight.contentOutlineColor !== transparentColor;
574         let hasPadding = paddingQuad && highlight.paddingColor !== transparentColor;
575         let hasBorder = borderQuad && highlight.borderColor !== transparentColor;
576         let hasMargin = marginQuad && highlight.marginColor !== transparentColor;
577
578         if (hasMargin && (!hasBorder || !quadEquals(marginQuad, borderQuad)))
579             _drawOutlinedQuadWithClip(marginQuad, borderQuad, highlight.marginColor, bounds);
580
581         if (hasBorder && (!hasPadding || !quadEquals(borderQuad, paddingQuad)))
582             _drawOutlinedQuadWithClip(borderQuad, paddingQuad, highlight.borderColor, bounds);
583
584         if (hasPadding && (!hasContent || !quadEquals(paddingQuad, contentQuad)))
585             _drawOutlinedQuadWithClip(paddingQuad, contentQuad, highlight.paddingColor, bounds);
586
587         if (hasContent)
588             _drawOutlinedQuad(contentQuad, highlight.contentColor, highlight.contentOutlineColor, bounds);
589     });
590 }
591
592 function showPageIndication()
593 {
594     document.body.classList.add("indicate");
595 }
596
597 function hidePageIndication()
598 {
599     document.body.classList.remove("indicate");
600 }
601
602 function setPlatform(platform)
603 {
604     document.body.classList.add("platform-" + platform);
605 }
606
607 function dispatch(message)
608 {
609     var functionName = message.shift();
610     window[functionName].apply(null, message);
611 }
612
613 function log(text)
614 {
615     var logEntry = document.createElement("div");
616     logEntry.textContent = text;
617     document.getElementById("log").appendChild(logEntry);
618 }