blob: ef0f82c4e7112729c2ec8f063b70b69e985a7447 [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(debugBackend) {
// Attach methods to handle commands in the 'Overlay' devtools domain.
// https://chromedevtools.github.io/devtools-protocol/tot/Overlay
let commands = debugBackend.Overlay = {};
// Returns non-overlapping rectangles to highlight for the box model of a node.
// This pseudo-command in the Overlay domain is a helper for the C++
// |OverlayAgent::HighlightNode|.
commands._highlightNodeRects = function(params) {
let node = debugBackend.DOM._findNode(params);
let config = params.highlightConfig || {};
let highlights = [];
if (node && node.getBoundingClientRect) {
let styles = window.getComputedStyle(node);
let content = node.getBoundingClientRect();
let color;
let box;
let transformed = isNodeTransformed(node);
if (!transformed) {
// Margin
color = config.marginColor;
box = styleBox(styles, 'margin');
if (color) {
boxRects(content, box).forEach(
rect => highlights.push(highlightParams(rect, color)));
}
// Border
color = config.borderColor;
box = styleBox(styles, 'border');
if (color) {
boxRects(content, box).forEach(
rect => highlights.push(highlightParams(rect, color)));
}
content = insetRect(content, box);
// Padding
color = config.paddingColor;
box = styleBox(styles, 'padding');
if (color) {
boxRects(content, box).forEach(
rect => highlights.push(highlightParams(rect, color)));
}
content = insetRect(content, box);
}
// Content
color = config.contentColor;
if (color) {
let highlight = highlightParams(content, color);
if (transformed) {
highlight.outlineColor = {r: 255, g: 0, b: 255, a: 1.0};
}
highlights.push(highlight);
}
}
return JSON.stringify({highlightRects: highlights});
}
// Returns the inset width of the 4 sides of a box in the computed style, with
// margin being negative to place it outside the rect.
function styleBox(styles, boxName) {
let suffix = boxName == 'border' ? '-width' : '';
let sign = boxName == 'margin' ? -1 : 1;
let box = {};
['top', 'right', 'bottom', 'left'].forEach(side => {
let width = styles.getPropertyValue(`${boxName}-${side}${suffix}`);
box[side] = sign * parseFloat(width) || 0;
});
return box;
}
function isNodeTransformed(node) {
while (node) {
if (window.getComputedStyle(node).transform !== 'none') return true;
node = node.offsetParent;
}
return false;
}
// Returns an array of non-overlapping rectangles for the box around the inside
// of |rect|, but the rectangle for any box side with negative width will be
// on the outside of |rect|.
function boxRects(rect, box) {
// Start out assuming box widths are all positive.
let outerT = rect.y;
let outerB = rect.y + rect.height;
let outerL = rect.x;
let outerR = rect.x + rect.width;
let innerT = outerT + box.top;
let innerB = outerB - box.bottom;
let innerL = outerL + box.left;
let innerR = outerR - box.right;
// Swap any inner/outer "inverted" by a negative box side.
if (outerT > innerT) [outerT, innerT] = [innerT, outerT];
if (outerB < innerB) [outerB, innerB] = [innerB, outerB];
if (outerL > innerL) [outerL, innerL] = [innerL, outerL];
if (outerR < innerR) [outerR, innerR] = [innerR, outerR];
// +--------------+
// | |
// +--+--------+--+
// | | | |
// | | | |
// +--+--------+--+
// | |
// +--------------+
return [
// top
{ x: outerL,
y: outerT,
width: outerR - outerL,
height: innerT - outerT },
// bottom
{ x: outerL,
y: innerB,
width: outerR - outerL,
height: outerB - innerB },
// left
{ x: outerL,
y: innerT,
width: innerL - outerL,
height: innerB - innerT },
// right
{ x: innerR,
y: innerT,
width: outerR - innerR,
height: innerB - innerT },
];
}
// Returns the rectangle with the box around the insides removed.
function insetRect(rect, box) {
return {
x: rect.x + box.left,
y: rect.y + box.top,
width: rect.width - box.left - box.right,
height: rect.height - box.top - box.bottom };
}
// Returns parameters matching the DevTools protocol "Overlay.highlightRect"
// parameters, as expected by the native overlay agent.
function highlightParams(rect, color) {
// Copy each property rather than whole objects to ensure this can be
// converted with JSON.serialize().
return {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
color: {
r: color.r,
g: color.g,
b: color.b,
a: color.a,
},
};
}
// TODO: Pass debugBackend from C++ instead of getting it from the window.
})(window.debugBackend);