blob: 0784920e2de91be326fb719a445b8085706a073c [file] [log] [blame]
<!--
Copyright (C) 2012 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
padding: 0;
font-size: 13px;
color: #222;
--arrow-width: 15px;
--arrow-height: 8px;
--shadow-up: 5px;
--shadow-down: -5px;
--shadow-direction: var(--shadow-up);
--arrow-up: polygon(0 0, 100% 0, 50% 100%);
--arrow-down: polygon(50% 0, 0 100%, 100% 100%);
--arrow: var(--arrow-up);
}
body.platform-linux {
font-family: Roboto, Ubuntu, Arial, sans-serif;
}
body.platform-mac {
color: rgb(48, 57, 66);
font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;
}
body.platform-windows {
font-family: 'Segoe UI', Tahoma, sans-serif;
}
.fill {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.dimmed {
background-color: rgba(0, 0, 0, 0.31);
}
#canvas {
pointer-events: none;
}
.controls-line {
display: flex;
justify-content: center;
margin: 10px 0;
}
.message-box {
padding: 2px 4px;
display: flex;
align-items: center;
cursor: default;
overflow: hidden;
}
#paused-in-debugger {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.controls-line > * {
background-color: rgb(255, 255, 194);
border: 1px solid rgb(202, 202, 202);
height: 22px;
box-sizing: border-box;
}
.controls-line .button {
width: 26px;
margin-left: -1px;
margin-right: 0;
padding: 0;
flex-shrink: 0;
flex-grow: 0;
cursor: pointer;
}
.controls-line .button .glyph {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.75);
opacity: 0.8;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
position: relative;
}
.controls-line .button:active .glyph {
top: 1px;
left: 1px;
}
#resume-button .glyph {
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAKCAYAAABv7tTEAAAAAXNSR0IArs4c6QAAAFJJREFUKM+10bEJgGAMBeEPbR3BLRzEVdzEVRzELRzBVohVwEJ+iODBlQfhBeJhsmHU4C0KnFjQV6J0x1SNAhdWDJUoPTB3PvLLeaUhypM3n3sD/qc7lDrdpIEAAAAASUVORK5CYII=);
-webkit-mask-size: 13px 10px;
background-color: rgb(66, 129, 235);
}
#step-over-button .glyph {
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAKCAYAAAC5Sw6hAAAAAXNSR0IArs4c6QAAAOFJREFUKM+N0j8rhXEUB/DPcxW35CqhvIBrtqibkklhV8qkTHe4ZbdblcXgPVhuMdqUTUl5A2KRRCF5LGc4PT1P7qnfcr5/zu/8KdTHLFaxjHnc4RZXKI0QYxjgLQTVd42l/0wmg5iFX3iq5H6w22RS4DyRH7CB8cAXcBTGJT6xUmd0mEwuMdFQcA3fwXvGTAan8BrgPabTL9fRRyfx91PRMwyjGwcJ2EyCfsrfpPw2Pipz24NT/MZciiQYVshzOKnZ5Hturxt3k2MnCpS4SPkeHpPR8Sh3tYgttBoW9II2/AHiaEqvD2Fc0wAAAABJRU5ErkJggg==);
-webkit-mask-size: 18px 10px;
}
.px {
color: rgb(128, 128, 128);
}
#element-title {
position: absolute;
z-index: 10;
}
/* Material */
.hidden {
display: none !important;
}
.tooltip-content {
position: absolute;
z-index: 10;
-webkit-user-select: none;
}
.tooltip-content {
background-color: white;
padding: 5px 8px;
border-radius: 3px;
box-sizing: border-box;
max-width: calc(100% - 4px);
z-index: 1;
background-clip: padding-box;
will-change: transform;
text-rendering: optimizeLegibility;
pointer-events: none;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.35));
}
.tooltip-content::after {
content: "";
background: white;
width: var(--arrow-width);
height: var(--arrow-height);
clip-path: var(--arrow);
position: absolute;
top: var(--arrow-top);
left: var(--arrow-left);
visibility: var(--arrow-visibility);
}
.element-info {
display: flex;
flex-direction: column;
line-height: 16px;
}
.element-info-header {
display: flex;
align-content: stretch;
line-height: 16px;
}
.element-info-body {
display: flex;
flex-direction: column;
border-top: 1px solid #999;
padding-top: 2px;
margin-top: 2px;
}
.element-info-row {
display: flex;
line-height: 19px;
}
.element-info-row > * {
flex: none;
}
.element-info-name {
color: #666;
}
.element-info-gap {
flex: auto;
}
.element-info-value {
display: flex;
text-align: right;
color: rgb(48, 57, 66);
margin-left: 10px;
align-items: baseline;
}
.color-swatch {
display: flex;
margin-right: 2px;
width: 10px;
height: 10px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
line-height: 10px;
}
.color-swatch-inner {
flex: auto;
border: 1px solid rgba(128, 128, 128, 0.6);
}
.element-description {
flex: 1 1;
font-weight: bold;
word-wrap: break-word;
word-break: break-all;
}
.dimensions {
color: hsl(0, 0%, 45%);
text-align: right;
margin-left: 10px;
}
.material-node-width {
margin-right: 2px;
}
.material-node-height {
margin-left: 2px;
}
.material-tag-name {
/* Keep this in sync with inspectorSyntaxHighlight.css (--dom-tag-name-color) */
color: rgb(136, 18, 128);
}
.material-class-name, .material-node-id {
/* Keep this in sync with inspectorSyntaxHighlight.css (.webkit-html-attribute-value) */
color: rgb(26, 26, 166);
}
.contrast-text {
border-radius: 3px;
width: 20px;
height: 16px;
text-align: center;
line-height: 16px;
margin-right: 9px;
border: 1px solid #ccc;
}
</style>
<script>
const lightGridColor = "rgba(0,0,0,0.2)";
const darkGridColor = "rgba(0,0,0,0.7)";
const transparentColor = "rgba(0, 0, 0, 0)";
const gridBackgroundColor = "rgba(255, 255, 255, 0.8)";
function drawPausedInDebuggerMessage(message)
{
window._controlsVisible = true;
document.querySelector(".controls-line").style.visibility = "visible";
document.getElementById("paused-in-debugger").textContent = message;
document.body.classList.add("dimmed");
}
function _drawGrid(context, rulerAtRight, rulerAtBottom)
{
if (window._gridPainted)
return;
window._gridPainted = true;
context.save();
var pageFactor = pageZoomFactor * pageScaleFactor;
var scrollX = window.scrollX * pageScaleFactor;
var scrollY = window.scrollY * pageScaleFactor;
function zoom(x)
{
return Math.round(x * pageFactor);
}
function unzoom(x)
{
return Math.round(x / pageFactor);
}
var width = canvasWidth / pageFactor;
var height = canvasHeight / pageFactor;
const gridSubStep = 5;
const gridStep = 50;
{
// Draw X grid background
context.save();
context.fillStyle = gridBackgroundColor;
if (rulerAtBottom)
context.fillRect(0, zoom(height) - 15, zoom(width), zoom(height));
else
context.fillRect(0, 0, zoom(width), 15);
// Clip out backgrounds intersection
context.globalCompositeOperation = "destination-out";
context.fillStyle = "red";
if (rulerAtRight)
context.fillRect(zoom(width) - 15, 0, zoom(width), zoom(height));
else
context.fillRect(0, 0, 15, zoom(height));
context.restore();
// Draw Y grid background
context.fillStyle = gridBackgroundColor;
if (rulerAtRight)
context.fillRect(zoom(width) - 15, 0, zoom(width), zoom(height));
else
context.fillRect(0, 0, 15, zoom(height));
}
context.lineWidth = 1;
context.strokeStyle = darkGridColor;
context.fillStyle = darkGridColor;
{
// Draw labels.
context.save();
context.translate(-scrollX, 0.5 - scrollY);
var maxY = height + unzoom(scrollY);
for (var y = 2 * gridStep; y < maxY; y += 2 * gridStep) {
context.save();
context.translate(scrollX, zoom(y));
context.rotate(-Math.PI / 2);
context.fillText(y, 2, rulerAtRight ? zoom(width) - 7 : 13);
context.restore();
}
context.translate(0.5, -0.5);
var maxX = width + unzoom(scrollX);
for (var x = 2 * gridStep; x < maxX; x += 2 * gridStep) {
context.save();
context.fillText(x, zoom(x) + 2, rulerAtBottom ? scrollY + zoom(height) - 7 : scrollY + 13);
context.restore();
}
context.restore();
}
{
// Draw vertical grid
context.save();
if (rulerAtRight) {
context.translate(zoom(width), 0);
context.scale(-1, 1);
}
context.translate(-scrollX, 0.5 - scrollY);
var maxY = height + unzoom(scrollY);
for (var y = gridStep; y < maxY; y += gridStep) {
context.beginPath();
context.moveTo(scrollX, zoom(y));
var markLength = (y % (gridStep * 2)) ? 5 : 8;
context.lineTo(scrollX + markLength, zoom(y));
context.stroke();
}
context.strokeStyle = lightGridColor;
for (var y = gridSubStep; y < maxY; y += gridSubStep) {
if (!(y % gridStep))
continue;
context.beginPath();
context.moveTo(scrollX, zoom(y));
context.lineTo(scrollX + gridSubStep, zoom(y));
context.stroke();
}
context.restore();
}
{
// Draw horizontal grid
context.save();
if (rulerAtBottom) {
context.translate(0, zoom(height));
context.scale(1, -1);
}
context.translate(0.5 - scrollX, -scrollY);
var maxX = width + unzoom(scrollX);
for (var x = gridStep; x < maxX; x += gridStep) {
context.beginPath();
context.moveTo(zoom(x), scrollY);
var markLength = (x % (gridStep * 2)) ? 5 : 8;
context.lineTo(zoom(x), scrollY + markLength);
context.stroke();
}
context.strokeStyle = lightGridColor;
for (var x = gridSubStep; x < maxX; x += gridSubStep) {
if (!(x % gridStep))
continue;
context.beginPath();
context.moveTo(zoom(x), scrollY);
context.lineTo(zoom(x), scrollY + gridSubStep);
context.stroke();
}
context.restore();
}
context.restore();
}
function drawViewSize()
{
var text = viewportSize.width + "px \u00D7 " + viewportSize.height + "px";
context.save();
context.font = "20px ";
switch (platform) {
case "windows": context.font = "14px Consolas, Lucida Console"; break;
case "mac": context.font = "14px Menlo, Monaco"; break;
case "linux": context.font = "14px dejavu sans mono"; break;
}
var frameWidth = canvasWidth;
var textWidth = context.measureText(text).width;
context.fillStyle = gridBackgroundColor;
context.fillRect(frameWidth - textWidth - 12, 0, frameWidth, 25);
context.fillStyle = darkGridColor;
context.fillText(text, frameWidth - textWidth - 6, 18);
context.restore();
}
function resetCanvas(canvasElement)
{
canvasElement.width = deviceScaleFactor * viewportSize.width;
canvasElement.height = deviceScaleFactor * viewportSize.height;
canvasElement.style.width = viewportSize.width + "px";
canvasElement.style.height = viewportSize.height + "px";
var context = canvasElement.getContext("2d");
context.scale(deviceScaleFactor, deviceScaleFactor);
}
function reset(resetData)
{
window.viewportSize = resetData.viewportSize;
window.deviceScaleFactor = resetData.deviceScaleFactor;
window.pageScaleFactor = resetData.pageScaleFactor;
window.pageZoomFactor = resetData.pageZoomFactor;
window.scrollX = Math.round(resetData.scrollX);
window.scrollY = Math.round(resetData.scrollY);
window.canvas = document.getElementById("canvas");
window.context = canvas.getContext("2d");
resetCanvas(canvas);
window.canvasWidth = viewportSize.width;
window.canvasHeight = viewportSize.height;
window._controlsVisible = false;
document.querySelector(".controls-line").style.visibility = "hidden";
document.getElementById("tooltip-container").removeChildren();
document.body.classList.remove("dimmed");
window._gridPainted = false;
}
/**
* @param {!String} hexa
* @return {!Array<number>}
*/
function parseHexa(hexa) {
return hexa.match(/#(\w\w)(\w\w)(\w\w)(\w\w)/).slice(1).map(c => parseInt(c, 16) / 255);
}
/**
* @param {!String} hexa
* @return {!Array<number>}
*/
function normalizeColorString(hexa) {
if (hexa.endsWith("FF"))
return hexa.substr(0, 7);
const [r, g, b, a] = parseHexa(hexa);
return `rgba(${(r * 255).toFixed()}, ${(g * 255).toFixed()}, ${(b * 255).toFixed()}, ${Math.round(a * 100) / 100})`;
}
/**
* Calculate the contrast ratio between a foreground and a background color.
* Returns the ratio to 1, for example for two colors with a contrast ratio of 21:1,
* this function will return 21.
* See http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
* @param {!Array<number>} fgRGBA
* @param {!Array<number>} bgRGBA
* @return {number}
*/
function contrastRatio(fgRGBA, bgRGBA) {
// If we have a semi-transparent background color over an unknown
// background, draw the line for the "worst case" scenario: where
// the unknown background is the same color as the text.
bgRGBA = blendColors(bgRGBA, fgRGBA);
const fgLuminance = luminance(blendColors(fgRGBA, bgRGBA));
const bgLuminance = luminance(bgRGBA);
const result = (Math.max(fgLuminance, bgLuminance) + 0.05) / (Math.min(fgLuminance, bgLuminance) + 0.05);
return result.toFixed(2);
/**
* Calculate the luminance of this color using the WCAG algorithm.
* See http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
* @param {!Array<number>} rgba
* @return {number}
*/
function luminance(rgba) {
const rSRGB = rgba[0];
const gSRGB = rgba[1];
const bSRGB = rgba[2];
const r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow(((rSRGB + 0.055) / 1.055), 2.4);
const g = gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow(((gSRGB + 0.055) / 1.055), 2.4);
const b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow(((bSRGB + 0.055) / 1.055), 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
/**
* Combine the two given color according to alpha blending.
* @param {!Array<number>} fgRGBA
* @param {!Array<number>} bgRGBA
* @return {!Array<number>}
*/
function blendColors(fgRGBA, bgRGBA) {
const alpha = fgRGBA[3];
return [
((1 - alpha) * bgRGBA[0]) + (alpha * fgRGBA[0]),
((1 - alpha) * bgRGBA[1]) + (alpha * fgRGBA[1]),
((1 - alpha) * bgRGBA[2]) + (alpha * fgRGBA[2]),
alpha + (bgRGBA[3] * (1 - alpha))
];
}
}
function computeIsLargeFont(contrast) {
const boldWeights = new Set(['bold', 'bolder', '600', '700', '800', '900']);
const fontSizePx = parseFloat(contrast.fontSize.replace('px', ''));
const isBold = boldWeights.has(contrast.fontWeight);
const fontSizePt = fontSizePx * 72 / 96;
if (isBold)
return fontSizePt >= 14;
else
return fontSizePt >= 18;
}
function _createElementDescription(elementInfo)
{
const elementInfoElement = createElement("div", "element-info");
const elementInfoHeaderElement = elementInfoElement.createChild("div", "element-info-header");
const descriptionElement = elementInfoHeaderElement.createChild("div", "element-description monospace");
const tagNameElement = descriptionElement.createChild("span", "material-tag-name");
tagNameElement.textContent = elementInfo.tagName;
const nodeIdElement = descriptionElement.createChild("span", "material-node-id");
nodeIdElement.textContent = elementInfo.idValue ? "#" + elementInfo.idValue : "";
nodeIdElement.classList.toggle("hidden", !elementInfo.idValue);
const classNameElement = descriptionElement.createChild("span", "material-class-name");
classNameElement.textContent = (elementInfo.className || "").trimEnd(50);
classNameElement.classList.toggle("hidden", !elementInfo.className);
const dimensionsElement = elementInfoHeaderElement.createChild("div", "dimensions");
dimensionsElement.createChild("span", "material-node-width").textContent = Math.round(elementInfo.nodeWidth * 100) / 100;
dimensionsElement.createTextChild("\u00d7");
dimensionsElement.createChild("span", "material-node-height").textContent = Math.round(elementInfo.nodeHeight * 100) / 100;
const style = elementInfo.style || {};
let elementInfoBodyElement;
const color = style["color"];
if (color && color !== "#00000000")
addColorRow("Color", color);
const fontFamily = style["font-family"];
const fontSize = style["font-size"];
if (fontFamily && fontSize !== "0px")
addTextRow("Font", `${fontSize} ${fontFamily}`.trimEnd(20));
const bgcolor = style["background-color"];
if (bgcolor && bgcolor !== "#00000000")
addColorRow("Background", bgcolor);
const margin = style["margin"];
if (margin && margin !== "0px")
addTextRow("Margin", margin);
const padding = style["padding"];
if (padding && padding !== "0px")
addTextRow("Padding", padding);
const cbgColor = elementInfo.contrast ? elementInfo.contrast.backgroundColor : null;
if (color && color !== "#00000000" && cbgColor && cbgColor !== "#00000000")
addContrastRow(style["color"], elementInfo.contrast);
function addRow(name) {
if (!elementInfoBodyElement)
elementInfoBodyElement = elementInfoElement.createChild("div", "element-info-body")
const rowElement = elementInfoBodyElement.createChild("div", "element-info-row");
const nameElement = rowElement.createChild("div", "element-info-name");
nameElement.textContent = name;
rowElement.createChild("div", "element-info-gap");
return rowElement.createChild("div", "element-info-value");
}
function addTextRow(name, value) {
addRow(name).createTextChild(value);
}
function addColorRow(name, color) {
const valueElement = addRow(name);
const swatch = valueElement.createChild("div", "color-swatch");
const inner = swatch.createChild("div", "color-swatch-inner");
inner.style.backgroundColor = color;
valueElement.createTextChild(normalizeColorString(color));
}
function addContrastRow(fgColor, contrast) {
const ratio = contrastRatio(parseHexa(fgColor), parseHexa(contrast.backgroundColor));
const threshold = computeIsLargeFont(contrast) ? 3.0 : 4.5;
const valueElement = addRow("Contrast");
const sampleText = valueElement.createChild("div", "contrast-text");
sampleText.style.color = fgColor;
sampleText.style.backgroundColor = contrast.backgroundColor;
sampleText.textContent = "Aa";
const valueSpan = valueElement.createChild("span");
valueSpan.textContent = Math.round(ratio * 100) / 100;
if (ratio < threshold) {
valueSpan.style.color = "red";
valueSpan.style.paddingRight = "5px";
valueElement.createTextChild(` < ${threshold.toFixed(1)}`);
}
}
return elementInfoElement;
}
function _drawElementTitle(elementInfo, bounds)
{
const tooltipContainer = document.getElementById("tooltip-container");
tooltipContainer.removeChildren();
_createMaterialTooltip(tooltipContainer, bounds, _createElementDescription(elementInfo), true);
}
function _createMaterialTooltip(parentElement, bounds, contentElement, withArrow)
{
const tooltipContainer = parentElement.createChild("div");
const tooltipContent = tooltipContainer.createChild("div", "tooltip-content");
tooltipContent.appendChild(contentElement);
const titleWidth = tooltipContent.offsetWidth;
const titleHeight = tooltipContent.offsetHeight;
const arrowHalfWidth = 8;
const pageMargin = 2;
const arrowWidth = arrowHalfWidth * 2;
const arrowInset = arrowHalfWidth + 2;
const containerMinX = pageMargin + arrowInset;
const containerMaxX = canvasWidth - pageMargin - arrowInset - arrowWidth;
// Left align arrow to the tooltip but ensure it is pointing to the element.
// Center align arrow if the inspected element bounds are too narrow.
const boundsAreTooNarrow = bounds.maxX - bounds.minX < arrowWidth + 2 * arrowInset;
let arrowX;
if (boundsAreTooNarrow) {
arrowX = (bounds.minX + bounds.maxX) * 0.5 - arrowHalfWidth;
} else {
const xFromLeftBound = bounds.minX + arrowInset;
const xFromRightBound = bounds.maxX - arrowInset - arrowWidth;
if (xFromLeftBound > containerMinX && xFromLeftBound < containerMaxX)
arrowX = xFromLeftBound;
else
arrowX = Number.constrain(containerMinX, xFromLeftBound, xFromRightBound);
}
// Hide arrow if element is completely off the sides of the page.
const arrowHidden = !withArrow || arrowX < containerMinX || arrowX > containerMaxX;
let boxX = arrowX - arrowInset;
boxX = Number.constrain(boxX, pageMargin, canvasWidth - titleWidth - pageMargin);
let boxY = bounds.minY - arrowHalfWidth - titleHeight;
let onTop = true;
if (boxY < 0) {
boxY = Math.min(canvasHeight - titleHeight, bounds.maxY + arrowHalfWidth);
onTop = false;
} else if (bounds.minY > canvasHeight) {
boxY = canvasHeight - arrowHalfWidth - titleHeight;
}
tooltipContent.style.top = boxY + "px";
tooltipContent.style.left = boxX + "px";
tooltipContent.style.setProperty('--arrow-visibility', arrowHidden ? 'hidden' : 'visible');
if (arrowHidden)
return;
tooltipContent.style.setProperty('--arrow', onTop ? 'var(--arrow-up)' : 'var(--arrow-down)');
tooltipContent.style.setProperty('--shadow-direction', onTop ? 'var(--shadow-up)' : 'var(--shadow-down)');
tooltipContent.style.setProperty('--arrow-top', (onTop ? titleHeight : -arrowHalfWidth) + 'px');
tooltipContent.style.setProperty('--arrow-left', (arrowX - boxX) + 'px');
}
function _drawRulers(context, bounds, rulerAtRight, rulerAtBottom)
{
context.save();
var width = canvasWidth;
var height = canvasHeight;
context.strokeStyle = "rgba(128, 128, 128, 0.3)";
context.lineWidth = 1;
context.translate(0.5, 0.5);
if (rulerAtRight) {
for (var y in bounds.rightmostXForY) {
context.beginPath();
context.moveTo(width, y);
context.lineTo(bounds.rightmostXForY[y], y);
context.stroke();
}
} else {
for (var y in bounds.leftmostXForY) {
context.beginPath();
context.moveTo(0, y);
context.lineTo(bounds.leftmostXForY[y], y);
context.stroke();
}
}
if (rulerAtBottom) {
for (var x in bounds.bottommostYForX) {
context.beginPath();
context.moveTo(x, height);
context.lineTo(x, bounds.topmostYForX[x]);
context.stroke();
}
} else {
for (var x in bounds.topmostYForX) {
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x, bounds.topmostYForX[x]);
context.stroke();
}
}
context.restore();
}
function buildPath(commands, bounds) {
var commandsIndex = 0;
function extractPoints(count)
{
var points = [];
for (var i = 0; i < count; ++i) {
var x = Math.round(commands[commandsIndex++]);
bounds.maxX = Math.max(bounds.maxX, x);
bounds.minX = Math.min(bounds.minX, x);
var y = Math.round(commands[commandsIndex++]);
bounds.maxY = Math.max(bounds.maxY, y);
bounds.minY = Math.min(bounds.minY, y);
bounds.leftmostXForY[y] = Math.min(bounds.leftmostXForY[y] || Number.MAX_VALUE, x);
bounds.rightmostXForY[y] = Math.max(bounds.rightmostXForY[y] || Number.MIN_VALUE, x);
bounds.topmostYForX[x] = Math.min(bounds.topmostYForX[x] || Number.MAX_VALUE, y);
bounds.bottommostYForX[x] = Math.max(bounds.bottommostYForX[x] || Number.MIN_VALUE, y);
points.push(x, y);
}
return points;
}
var commandsLength = commands.length;
var path = new Path2D();
while (commandsIndex < commandsLength) {
switch (commands[commandsIndex++]) {
case "M":
path.moveTo.apply(path, extractPoints(1));
break;
case "L":
path.lineTo.apply(path, extractPoints(1));
break;
case "C":
path.bezierCurveTo.apply(path, extractPoints(3));
break;
case "Q":
path.quadraticCurveTo.apply(path, extractPoints(2));
break;
case "Z":
path.closePath();
break;
}
}
path.closePath();
return path;
}
function drawPath(context, commands, fillColor, outlineColor, bounds)
{
context.save();
const path = buildPath(commands, bounds);
if (fillColor) {
context.fillStyle = fillColor;
context.fill(path);
}
if (outlineColor) {
context.lineWidth = 2;
context.strokeStyle = outlineColor;
context.stroke(path);
}
context.restore();
return path;
}
function emptyBounds()
{
var bounds = {
minX: Number.MAX_VALUE,
minY: Number.MAX_VALUE,
maxX: Number.MIN_VALUE,
maxY: Number.MIN_VALUE,
leftmostXForY: {},
rightmostXForY: {},
topmostYForX: {},
bottommostYForX: {}
};
return bounds;
}
function _drawLayoutGridHighlight(highlight, context)
{
context.save();
context.translate(0.5, 0.5);
context.setLineDash([3, 3]);
context.lineWidth = 0;
context.strokeStyle = highlight.color;
context.stroke(buildPath(highlight.cells, emptyBounds()));
context.restore();
}
function clipLayoutGridCells(highlight) {
if (highlight.gridInfo) {
for (const grid of highlight.gridInfo) {
if (!grid.isPrimaryGrid)
continue;
const path = buildPath(grid.cells, emptyBounds());
context.clip(path);
}
}
}
function drawHighlight(highlight, context)
{
context = context || window.context;
context.save();
var bounds = emptyBounds();
for (var paths = highlight.paths.slice(); paths.length;) {
var path = paths.pop();
// Clip content quad using the data grid cells info to create white stripes.
context.save();
if (path.name === "content")
clipLayoutGridCells(highlight);
drawPath(context, path.path, path.fillColor, path.outlineColor, bounds);
if (paths.length) {
context.globalCompositeOperation = "destination-out";
drawPath(context, paths[paths.length - 1].path, "red", null, bounds);
}
context.restore();
}
context.restore();
context.save();
var rulerAtRight = highlight.paths.length && highlight.showRulers && bounds.minX < 20 && bounds.maxX + 20 < canvasWidth;
var rulerAtBottom = highlight.paths.length && highlight.showRulers && bounds.minY < 20 && bounds.maxY + 20 < canvasHeight;
if (highlight.showRulers)
_drawGrid(context, rulerAtRight, rulerAtBottom);
if (highlight.paths.length) {
if (highlight.showExtensionLines)
_drawRulers(context, bounds, rulerAtRight, rulerAtBottom);
if (highlight.elementInfo)
_drawElementTitle(highlight.elementInfo, bounds);
}
if (highlight.gridInfo) {
for (var grid of highlight.gridInfo)
_drawLayoutGridHighlight(grid, context);
}
context.restore();
return { bounds: bounds };
}
function drawScreenshotBorder(rect)
{
var context = window.context;
context.save();
context.fillStyle = '#0003';
context.strokeStyle = '#fffd';
context.lineWidth = 1;
context.rect(rect.x1 - 0.5, rect.y1 - 0.5, rect.x2 - rect.x1 + 1, rect.y2 - rect.y1 + 1);
context.fill();
context.stroke();
context.restore();
}
function setPlatform(platform)
{
window.platform = platform;
document.body.classList.add("platform-" + platform);
}
function dispatch(message)
{
var functionName = message.shift();
window[functionName].apply(null, message);
}
function log(text)
{
document.getElementById("log").createChild("div").textContent = text;
}
function onResumeClick()
{
InspectorOverlayHost.resume();
}
function onStepOverClick()
{
InspectorOverlayHost.stepOver();
}
function onLoaded()
{
document.getElementById("resume-button").addEventListener("click", onResumeClick);
document.getElementById("step-over-button").addEventListener("click", onStepOverClick);
}
function eventHasCtrlOrMeta(event)
{
return window.platform == "mac" ? (event.metaKey && !event.ctrlKey) : (event.ctrlKey && !event.metaKey);
}
function onDocumentKeyDown(event)
{
if (!window._controlsVisible)
return;
if (event.key == "F8" || eventHasCtrlOrMeta(event) && event.keyCode == 220 /* backslash */)
InspectorOverlayHost.resume();
else if (event.key == "F10" || eventHasCtrlOrMeta(event) && event.keyCode == 222 /* single quote */)
InspectorOverlayHost.stepOver();
}
Element.prototype.createChild = function(tagName, className)
{
var element = createElement(tagName, className);
element.addEventListener("click", function(e) { e.stopPropagation(); }, false);
this.appendChild(element);
return element;
}
Element.prototype.createTextChild = function(text)
{
var element = document.createTextNode(text);
this.appendChild(element);
return element;
}
Element.prototype.removeChildren = function()
{
if (this.firstChild)
this.textContent = "";
}
function createElement(tagName, className)
{
var element = document.createElement(tagName);
if (className)
element.className = className;
return element;
}
String.prototype.trimEnd = function(maxLength)
{
if (this.length <= maxLength)
return String(this);
return this.substr(0, maxLength - 1) + "\u2026";
}
/**
* @param {number} num
* @param {number} min
* @param {number} max
* @return {number}
*/
Number.constrain = function(num, min, max) {
if (num < min)
num = min;
else if (num > max)
num = max;
return num;
};
function test() {
document.body.classList.add("platform-mac");
reset({
viewportSize: {width: 800, height: 600},
deviceScaleFactor: 1,
pageScaleFactor: 1,
pageZoomFactor: 1,
scrollX: 0,
scrollY: 0
});
drawHighlight(
{"paths":[{"path":["M",122,133.796875,"L",822,133.796875,"L",822,208.796875,"L",122,208.796875,"Z"], "fillColor":"rgba(111, 168, 220, 0.658823529411765)","name":"content"},
{"path":["M",122,113.796875,"L",822,113.796875,"L",822,228.796875,"L",122,228.796875,"Z"],"fillColor":"rgba(246, 178, 107, 0.66)","name":"margin"}],"showRulers":false,"showExtensionLines":false,
"elementInfo":{"tagName":"p","idValue":"","nodeWidth":"700","nodeHeight":"75","style":{"color":"#FFFFFFFF","font-family":"\"Product Sans\", \"Open Sans\", Roboto, Arial","font-size":"20px","line-height":"25px","padding":"0px","margin":"20px 0px","background-color":"#00000000"},"contrast":{"fontSize":"20px","fontWeight":"400","backgroundColor":"#F9B826BF"}}});
}
window.addEventListener("DOMContentLoaded", onLoaded);
document.addEventListener("keydown", onDocumentKeyDown);
</script>
</head>
<body class="fill">
</body>
<canvas id="canvas" class="fill"></canvas>
<div id="tooltip-container"></div>
<div class="controls-line">
<div class="message-box"><div id="paused-in-debugger"></div></div>
<div class="button" id="resume-button" title="Resume script execution (F8)."><div class="glyph"></div></div>
<div class="button" id="step-over-button" title="Step over next function call (F10)."><div class="glyph"></div></div>
</div>
<div id="log"></div>
</html>