Web Inspector: replace HTMLCanvasElement with CanvasRenderingContext for instrumentat...
[WebKit-https.git] / Source / WebCore / inspector / InspectorOverlayPage.js
1 const lightGridColor = "rgba(0,0,0,0.2)";
2 const darkGridColor = "rgba(0,0,0,0.5)";
3 const transparentColor = "rgba(0, 0, 0, 0)";
4 const gridBackgroundColor = "rgba(255, 255, 255, 0.6)";
5
6 // CSS Shapes highlight colors
7 const shapeHighlightColor = "rgba(96, 82, 127, 0.8)";
8 const shapeMarginHighlightColor = "rgba(96, 82, 127, 0.6)";
9
10 const paintRectFillColor = "rgba(255, 0, 0, 0.5)";
11
12 function drawPausedInDebuggerMessage(message)
13 {
14     var pausedInDebugger = document.getElementById("paused-in-debugger");
15     pausedInDebugger.textContent = message;
16     pausedInDebugger.style.visibility = "visible";
17     document.body.classList.add("dimmed");
18 }
19
20 function quadToPath(quad)
21 {
22     context.beginPath();
23     context.moveTo(quad[0].x, quad[0].y);
24     context.lineTo(quad[1].x, quad[1].y);
25     context.lineTo(quad[2].x, quad[2].y);
26     context.lineTo(quad[3].x, quad[3].y);
27     context.closePath();
28     return context;
29 }
30
31 function drawOutlinedQuad(quad, fillColor, outlineColor)
32 {
33     context.save();
34     context.lineWidth = 2;
35     quadToPath(quad).clip();
36     context.fillStyle = fillColor;
37     context.fill();
38     if (outlineColor) {
39         context.strokeStyle = outlineColor;
40         context.stroke();
41     }
42     context.restore();
43 }
44
45 function pathCommand(context, commands, name, index, length) {
46     context[name].apply(context, commands.slice(index + 1, index + length + 1));
47     return index + length + 1;
48 }
49
50 function drawPath(context, commands, fillColor, outlineColor)
51 {
52     context.save();
53     context.beginPath();
54
55     var commandsIndex = 0;
56     var commandsLength = commands.length;
57     while(commandsIndex < commandsLength) {
58         switch(commands[commandsIndex]) {
59         // 1 point
60         case 'M':
61             commandsIndex = pathCommand(context, commands, "moveTo", commandsIndex, 2);
62             break;
63         // 1 point
64         case 'L':
65             commandsIndex = pathCommand(context, commands, "lineTo", commandsIndex, 2);
66             break;
67         // 3 points
68         case 'C':
69             commandsIndex = pathCommand(context, commands, "bezierCurveTo", commandsIndex, 6);
70             break;
71         // 2 points
72         case 'Q':
73             commandsIndex = pathCommand(context, commands, "quadraticCurveTo", commandsIndex, 2);
74             break;
75         // 0 points
76         case 'Z':
77             commandsIndex = pathCommand(context, commands, "closePath", commandsIndex, 0);
78             break;
79         default:
80             commandsIndex++;
81         }
82     }
83
84     context.closePath();
85     context.fillStyle = fillColor;
86     context.fill();
87
88     if (outlineColor) {
89         context.lineWidth = 2;
90         context.strokeStyle = outlineColor;
91         context.stroke();
92     }
93
94     context.restore();
95 }
96
97 function drawOutlinedQuadWithClip(quad, clipQuad, fillColor)
98 {
99     var canvas = document.getElementById("canvas");
100     context.fillStyle = fillColor;
101     context.save();
102     context.lineWidth = 0;
103     quadToPath(quad).fill();
104     context.globalCompositeOperation = "destination-out";
105     context.fillStyle = "red";
106     quadToPath(clipQuad).fill();
107     context.restore();
108 }
109
110 function quadEquals(quad1, quad2)
111 {
112     return quad1[0].x === quad2[0].x && quad1[0].y === quad2[0].y &&
113         quad1[1].x === quad2[1].x && quad1[1].y === quad2[1].y &&
114         quad1[2].x === quad2[2].x && quad1[2].y === quad2[2].y &&
115         quad1[3].x === quad2[3].x && quad1[3].y === quad2[3].y;
116 }
117
118 function drawGutter()
119 {
120     var frameWidth = frameViewFullSize.width;
121     var frameHeight = frameViewFullSize.height;
122
123     if (!frameWidth || document.body.offsetWidth <= frameWidth)
124         rightGutter.style.removeProperty("display");
125     else {
126         rightGutter.style.display = "block";
127         rightGutter.style.left = frameWidth + "px";
128     }
129
130     if (!frameHeight || document.body.offsetHeight <= frameHeight)
131         bottomGutter.style.removeProperty("display");
132     else {
133         bottomGutter.style.display = "block";
134         bottomGutter.style.top = frameHeight + "px";
135     }
136 }
137
138 var updatePaintRectsIntervalID;
139
140 function updatePaintRects(paintRectList)
141 {
142     var context = paintRectsCanvas.getContext("2d");
143     context.save();
144     context.scale(window.devicePixelRatio, window.devicePixelRatio);
145
146     context.clearRect(0, 0, paintRectsCanvas.width, paintRectsCanvas.height);
147
148     context.fillStyle = paintRectFillColor;
149
150     for (var rectObject of paintRectList)
151         context.fillRect(rectObject.x, rectObject.y, rectObject.width, rectObject.height);
152
153     context.restore();
154 }
155
156 function reset(resetData)
157 {
158     var deviceScaleFactor = resetData.deviceScaleFactor;
159     var viewportSize = resetData.viewportSize;
160     window.frameViewFullSize = resetData.frameViewFullSize;
161
162     window.canvas = document.getElementById("canvas");
163     window.paintRectsCanvas = document.getElementById("paintrects-canvas");
164
165     window.context = canvas.getContext("2d");
166     window.rightGutter = document.getElementById("right-gutter");
167     window.bottomGutter = document.getElementById("bottom-gutter");
168
169     canvas.width = deviceScaleFactor * viewportSize.width;
170     canvas.height = deviceScaleFactor * viewportSize.height;
171     canvas.style.width = viewportSize.width + "px";
172     canvas.style.height = viewportSize.height + "px";
173     context.scale(deviceScaleFactor, deviceScaleFactor);
174
175     // We avoid getting the context for the paint rects canvas until we need to paint, to avoid backing store allocation.
176     paintRectsCanvas.width = deviceScaleFactor * viewportSize.width;
177     paintRectsCanvas.height = deviceScaleFactor * viewportSize.height;
178     paintRectsCanvas.style.width = viewportSize.width + "px";
179     paintRectsCanvas.style.height = viewportSize.height + "px";
180
181     document.getElementById("paused-in-debugger").style.visibility = "hidden";
182     document.getElementById("element-title-container").innerHTML = "";
183     document.body.classList.remove("dimmed");
184 }
185
186 function DOMBuilder(tagName, className)
187 {
188     this.element = document.createElement(tagName);
189     this.element.className = className;
190 }
191
192 DOMBuilder.prototype.appendTextNode = function(content)
193 {
194     let node = document.createTextNode(content);
195     this.element.appendChild(node);
196     return node;
197 }
198
199 DOMBuilder.prototype.appendSpan = function(className, value)
200 {
201     var span = document.createElement("span");
202     span.className = className;
203     span.textContent = value;
204     this.element.appendChild(span);
205     return span;
206 }
207
208 DOMBuilder.prototype.appendSpanIfNotNull = function(className, value, prefix)
209 {
210     return value ? this.appendSpan(className, (prefix ? prefix : "") + value) : null;
211 }
212
213 DOMBuilder.prototype.appendProperty = function(className, propertyName, value)
214 {
215     var builder = new DOMBuilder("div", className);
216     builder.appendSpan("css-property", propertyName);
217     builder.appendTextNode(" ");
218     builder.appendSpan("value", value);
219     this.element.appendChild(builder.element);
220     return builder.element;
221 }
222
223 DOMBuilder.prototype.appendPropertyIfNotNull = function(className, propertyName, value)
224 {
225     return value ? this.appendProperty(className, propertyName, value) : null;
226 }
227
228 function _truncateString(value, maxLength)
229 {
230     return value && value.length > maxLength ? value.substring(0, 50) + "\u2026" : value;
231 }
232
233 function _createElementTitle(elementData)
234 {
235     let builder = new DOMBuilder("div", "element-title");
236
237     builder.appendSpanIfNotNull("tag-name", elementData.tagName);
238     builder.appendSpanIfNotNull("node-id", CSS.escape(elementData.idValue), "#");
239
240     let classes = elementData.classes;
241     if (classes && classes.length)
242         builder.appendSpan("class-name", _truncateString(classes.map((className) => "." + CSS.escape(className)).join(""), 50));
243
244     builder.appendSpanIfNotNull("pseudo-type", elementData.pseudoElement, "::");
245
246     builder.appendTextNode(" ");
247     builder.appendSpan("node-width", elementData.size.width);
248     // \xd7 is the code for the &times; HTML entity.
249     builder.appendSpan("px", "px \xd7 ");
250     builder.appendSpan("node-height", elementData.size.height);
251     builder.appendSpan("px", "px");
252
253     builder.appendPropertyIfNotNull("role-name", "Role", elementData.role);
254
255     document.getElementById("element-title-container").appendChild(builder.element);
256
257     return builder.element;
258 }
259
260 function _drawElementTitle(elementData, fragmentHighlight, scroll)
261 {
262     if (!elementData || !fragmentHighlight.quads.length)
263         return;
264
265     var elementTitle = _createElementTitle(elementData);
266
267     var marginQuad = fragmentHighlight.quads[0];
268
269     var titleWidth = elementTitle.offsetWidth + 6;
270     var titleHeight = elementTitle.offsetHeight + 4;
271
272     var anchorTop = marginQuad[0].y;
273     var anchorBottom = marginQuad[3].y;
274
275     const arrowHeight = 7;
276     var renderArrowUp = false;
277     var renderArrowDown = false;
278
279     var boxX = marginQuad[0].x;
280
281     boxX = Math.max(2, boxX - scroll.x);
282     anchorTop -= scroll.y;
283     anchorBottom -= scroll.y;
284
285     if (boxX + titleWidth > canvas.width)
286         boxX = canvas.width - titleWidth - 2;
287
288     var boxY;
289     if (anchorTop > canvas.height) {
290         boxY = canvas.height - titleHeight - arrowHeight;
291         renderArrowDown = true;
292     } else if (anchorBottom < 0) {
293         boxY = arrowHeight;
294         renderArrowUp = true;
295     } else if (anchorBottom + titleHeight + arrowHeight < canvas.height) {
296         boxY = anchorBottom + arrowHeight - 4;
297         renderArrowUp = true;
298     } else if (anchorTop - titleHeight - arrowHeight > 0) {
299         boxY = anchorTop - titleHeight - arrowHeight + 3;
300         renderArrowDown = true;
301     } else
302         boxY = arrowHeight;
303
304     context.save();
305     context.translate(0.5, 0.5);
306     context.beginPath();
307     context.moveTo(boxX, boxY);
308     if (renderArrowUp) {
309         context.lineTo(boxX + 2 * arrowHeight, boxY);
310         context.lineTo(boxX + 3 * arrowHeight, boxY - arrowHeight);
311         context.lineTo(boxX + 4 * arrowHeight, boxY);
312     }
313     context.lineTo(boxX + titleWidth, boxY);
314     context.lineTo(boxX + titleWidth, boxY + titleHeight);
315     if (renderArrowDown) {
316         context.lineTo(boxX + 4 * arrowHeight, boxY + titleHeight);
317         context.lineTo(boxX + 3 * arrowHeight, boxY + titleHeight + arrowHeight);
318         context.lineTo(boxX + 2 * arrowHeight, boxY + titleHeight);
319     }
320     context.lineTo(boxX, boxY + titleHeight);
321     context.closePath();
322     context.fillStyle = "rgb(255, 255, 194)";
323     context.fill();
324     context.strokeStyle = "rgb(128, 128, 128)";
325     context.stroke();
326
327     context.restore();
328
329     elementTitle.style.top = (boxY + 3) + "px";
330     elementTitle.style.left = (boxX + 3) + "px";
331 }
332
333 function _drawShapeHighlight(shapeInfo)
334 {
335     if (shapeInfo.marginShape)
336         drawPath(context, shapeInfo.marginShape, shapeMarginHighlightColor);
337
338     if (shapeInfo.shape)
339         drawPath(context, shapeInfo.shape, shapeHighlightColor);
340
341     if (!(shapeInfo.shape || shapeInfo.marginShape))
342         drawOutlinedQuad(shapeInfo.bounds, shapeHighlightColor, shapeHighlightColor);
343 }
344
345 function _drawFragmentHighlight(highlight)
346 {
347     if (!highlight.quads.length)
348         return;
349
350     context.save();
351
352     var quads = highlight.quads.slice();
353     var contentQuad = quads.pop();
354     var paddingQuad = quads.pop();
355     var borderQuad = quads.pop();
356     var marginQuad = quads.pop();
357
358     var hasContent = contentQuad && highlight.contentColor !== transparentColor || highlight.contentOutlineColor !== transparentColor;
359     var hasPadding = paddingQuad && highlight.paddingColor !== transparentColor;
360     var hasBorder = borderQuad && highlight.borderColor !== transparentColor;
361     var hasMargin = marginQuad && highlight.marginColor !== transparentColor;
362
363     var clipQuad;
364     if (hasMargin && (!hasBorder || !quadEquals(marginQuad, borderQuad))) {
365         drawOutlinedQuadWithClip(marginQuad, borderQuad, highlight.marginColor);
366         clipQuad = borderQuad;
367     }
368     if (hasBorder && (!hasPadding || !quadEquals(borderQuad, paddingQuad))) {
369         drawOutlinedQuadWithClip(borderQuad, paddingQuad, highlight.borderColor);
370         clipQuad = paddingQuad;
371     }
372     if (hasPadding && (!hasContent || !quadEquals(paddingQuad, contentQuad))) {
373         drawOutlinedQuadWithClip(paddingQuad, contentQuad, highlight.paddingColor);
374         clipQuad = contentQuad;
375     }
376     if (hasContent)
377         drawOutlinedQuad(contentQuad, highlight.contentColor, highlight.contentOutlineColor);
378
379     var width = canvas.width;
380     var height = canvas.height;
381     var minX = Number.MAX_VALUE, minY = Number.MAX_VALUE, maxX = Number.MIN_VALUE; maxY = Number.MIN_VALUE;
382     for (var i = 0; i < highlight.quads.length; ++i) {
383         var quad = highlight.quads[i];
384         for (var j = 0; j < quad.length; ++j) {
385             minX = Math.min(minX, quad[j].x);
386             maxX = Math.max(maxX, quad[j].x);
387             minY = Math.min(minY, quad[j].y);
388             maxY = Math.max(maxY, quad[j].y);
389         }
390     }
391
392     context.restore();
393 }
394
395 function showPageIndication()
396 {
397     document.body.classList.add("indicate");
398 }
399
400 function hidePageIndication()
401 {
402     document.body.classList.remove("indicate");
403 }
404
405 function drawNodeHighlight(allHighlights)
406 {
407     var elementTitleContainer = document.getElementById("element-title-container");
408     while (elementTitleContainer.hasChildNodes())
409         elementTitleContainer.removeChild(elementTitleContainer.lastChild);
410
411     for (var highlight of allHighlights) {
412         context.save();
413         context.translate(-highlight.scrollOffset.x, -highlight.scrollOffset.y);
414
415         for (var fragment of highlight.fragments)
416             _drawFragmentHighlight(fragment);
417
418         if (highlight.elementData && highlight.elementData.shapeOutsideData)
419             _drawShapeHighlight(highlight.elementData.shapeOutsideData);
420
421         context.restore();
422
423         if (allHighlights.length === 1) {
424             for (var fragment of highlight.fragments)
425                 _drawElementTitle(highlight.elementData, fragment, highlight.scrollOffset);
426         }
427     }
428 }
429
430 function drawQuadHighlight(highlight)
431 {
432     context.save();
433     drawOutlinedQuad(highlight.quads[0], highlight.contentColor, highlight.contentOutlineColor);
434     context.restore();
435 }
436
437 function setPlatform(platform)
438 {
439     document.body.classList.add("platform-" + platform);
440 }
441
442 function dispatch(message)
443 {
444     var functionName = message.shift();
445     window[functionName].apply(null, message);
446 }
447
448 function log(text)
449 {
450     var logEntry = document.createElement("div");
451     logEntry.textContent = text;
452     document.getElementById("log").appendChild(logEntry);
453 }