1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Return the portion of the element that should be made visible.
// Based on the WebDriver spec, this function only considers the first rectangle
// returned by element.getClientRects function.
// * When the rectangle is already partially visible in the enclosing viewport,
// return the portion that is currently visible. According to WebDriver spec,
// no scrolling should be done to bring more of the element into view.
// * When the rectangle is completely outside of the enclosing viewport,
// return the entire rectangle, as WebDriver spec requires us to scroll the
// entire rectangle into view. (However, scrolling is NOT the responsibility
// of this function.)
//
// The returned value is an object with the following properties about the
// region mentioned above: left, top, height, width. Note that left and top are
// relative to the upper-left corner of the element's bounding client rect (as
// returned by element.getBoundingClientRect).
function getElementRegion(element) {
// Check that node type is element.
if (element.nodeType != 1)
throw new Error(element + ' is not an element');
// We try 2 methods to determine element region. Try the first client rect,
// and then the bounding client rect.
// SVG is one case that doesn't have a first client rect.
const clientRects = element.getClientRects();
// Determines if region is partially in viewport, returning visible region
// if so. If not, returns null. If fully visible, returns original region.
function getVisibleSubregion(region) {
// Given two regions, determines if any intersection occurs.
// Overlapping edges are not considered intersections.
function getIntersectingSubregion(region1, region2) {
if (!(Math.round(region2.right) <= Math.round(region1.left) ||
Math.round(region2.left) >= Math.round(region1.right) ||
Math.round(region2.top) >= Math.round(region1.bottom) ||
Math.round(region2.bottom) <= Math.round(region1.top))) {
// Determines region of intersection.
// If region2 contains region1, returns region1.
// If region1 contains region2, returns region2.
return {
'left': Math.max(region1.left, region2.left),
'right': Math.min(region1.right, region2.right),
'bottom': Math.min(region1.bottom, region2.bottom),
'top': Math.max(region1.top, region2.top)
};
}
return null;
}
const visualViewport = window.visualViewport;
// We need to disregard any scrollbars therefore instead of innerSize
// of the window we should use the viewport size.
// This size can be affected (scaled) by user's pinch.
// We need to undo this scaling because client rects are calculated
// relatively to the original unscaled viewport.
const viewport = new DOMRect(0, 0,
visualViewport.width * visualViewport.scale,
visualViewport.height * visualViewport.scale
);
return getIntersectingSubregion(viewport, region);
}
let boundingRect = null;
let clientRect = null;
// Element area of a map has same first ClientRect and BoundingClientRect
// after blink roll at chromium commit position 290738 which includes blink
// revision 180610. Thus handle area as a special case.
if (clientRects.length == 0 || element.tagName.toLowerCase() == 'area') {
// Area clicking is technically not supported by W3C standard but is a
// desired feature. Returns region containing the area instead of subregion
// so that whole area is visible and always clicked correctly.
if (element.tagName.toLowerCase() == 'area') {
const coords = element.coords.split(',');
if (element.shape.toLowerCase() == 'rect') {
if (coords.length != 4)
throw new Error('failed to detect the region of the area');
const leftX = Number(coords[0]);
const topY = Number(coords[1]);
const rightX = Number(coords[2]);
const bottomY = Number(coords[3]);
return {
'left': leftX,
'top': topY,
'width': rightX - leftX,
'height': bottomY - topY
};
} else if (element.shape.toLowerCase() == 'circle') {
if (coords.length != 3)
throw new Error('failed to detect the region of the area');
const centerX = Number(coords[0]);
const centerY = Number(coords[1]);
const radius = Number(coords[2]);
return {
'left': Math.max(0, centerX - radius),
'top': Math.max(0, centerY - radius),
'width': radius * 2,
'height': radius * 2
};
} else if (element.shape.toLowerCase() == 'poly') {
if (coords.length < 2)
throw new Error('failed to detect the region of the area');
let minX = Number(coords[0]);
let minY = Number(coords[1]);
let maxX = minX;
let maxY = minY;
for (i = 2; i < coords.length; i += 2) {
const x = Number(coords[i]);
const y = Number(coords[i + 1]);
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
return {
'left': minX,
'top': minY,
'width': maxX - minX,
'height': maxY - minY
};
} else {
throw new Error('shape=' + element.shape + ' is not supported');
}
} else {
clientRect = boundingRect = element.getBoundingClientRect();
}
} else {
boundingRect = element.getBoundingClientRect();
clientRect = clientRects[0];
for (let i = 0; i < clientRects.length; i++) {
if (clientRects[i].height != 0 && clientRects[i].width != 0) {
clientRect = clientRects[i];
break;
}
}
}
const visiblePortion = getVisibleSubregion(clientRect) || clientRect;
// Returned region is relative to boundingRect's left,top.
return {
'left': visiblePortion.left - boundingRect.left,
'top': visiblePortion.top - boundingRect.top,
'height': visiblePortion.bottom - visiblePortion.top,
'width': visiblePortion.right - visiblePortion.left
};
}
|