diff --git a/lib/ElementHandle.js b/lib/ElementHandle.js index ef356ba1..07972fd1 100644 --- a/lib/ElementHandle.js +++ b/lib/ElementHandle.js @@ -15,7 +15,7 @@ */ const path = require('path'); const {JSHandle} = require('./ExecutionContext'); -const {helper, debugError} = require('./helper'); +const {helper, assert, debugError} = require('./helper'); class ElementHandle extends JSHandle { /** @@ -76,13 +76,29 @@ class ElementHandle extends JSHandle { } /** - * @return {!Promise<{x: number, y: number}>} + * @return {!Promise} */ - async _boundingBoxCenter() { - const box = await this._assertBoundingBox(); + async _clickablePoint() { + const result = await this._client.send('DOM.getContentQuads', { + objectId: this._remoteObject.objectId + }).catch(debugError); + if (!result || !result.quads.length) + throw new Error('Node is either not visible or not an HTMLElement'); + // Filter out quads that have too small area to click into. + const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).filter(quad => computeQuadArea(quad) > 1); + if (!quads.length) + throw new Error('Node is either not visible or not an HTMLElement'); + // Return the middle point of the first quad. + const quad = quads[0]; + let x = 0; + let y = 0; + for (const point of quad) { + x += point.x; + y += point.y; + } return { - x: box.x + box.width / 2, - y: box.y + box.height / 2 + x: x / 4, + y: y / 4 }; } @@ -110,7 +126,7 @@ class ElementHandle extends JSHandle { async hover() { await this._scrollIntoViewIfNeeded(); - const {x, y} = await this._boundingBoxCenter(); + const {x, y} = await this._clickablePoint(); await this._page.mouse.move(x, y); } @@ -119,7 +135,7 @@ class ElementHandle extends JSHandle { */ async click(options = {}) { await this._scrollIntoViewIfNeeded(); - const {x, y} = await this._boundingBoxCenter(); + const {x, y} = await this._clickablePoint(); await this._page.mouse.click(x, y, options); } @@ -135,7 +151,7 @@ class ElementHandle extends JSHandle { async tap() { await this._scrollIntoViewIfNeeded(); - const {x, y} = await this._boundingBoxCenter(); + const {x, y} = await this._clickablePoint(); await this._page.touchscreen.tap(x, y); } @@ -199,17 +215,6 @@ class ElementHandle extends JSHandle { }; } - /** - * @return {!Promise{x: number, y: number, width: number, height: number}>} - */ - async _assertBoundingBox() { - const boundingBox = await this.boundingBox(); - if (boundingBox) - return boundingBox; - - throw new Error('Node is either not visible or not an HTMLElement'); - } - /** * * @param {!Object=} options @@ -218,7 +223,8 @@ class ElementHandle extends JSHandle { async screenshot(options = {}) { let needsViewportReset = false; - let boundingBox = await this._assertBoundingBox(); + let boundingBox = await this.boundingBox(); + assert(boundingBox, 'Node is either not visible or not an HTMLElement'); const viewport = this._page.viewport(); @@ -234,7 +240,8 @@ class ElementHandle extends JSHandle { await this._scrollIntoViewIfNeeded(); - boundingBox = await this._assertBoundingBox(); + boundingBox = await this.boundingBox(); + assert(boundingBox, 'Node is either not visible or not an HTMLElement'); const { layoutViewport: { pageX, pageY } } = await this._client.send('Page.getLayoutMetrics'); @@ -349,5 +356,17 @@ class ElementHandle extends JSHandle { } } +function computeQuadArea(quad) { + // Compute sum of all directed areas of adjacent triangles + // https://en.wikipedia.org/wiki/Polygon#Simple_polygons + let area = 0; + for (let i = 0; i < quad.length; ++i) { + const p1 = quad[i]; + const p2 = quad[(i + 1) % quad.length]; + area += (p1.x * p2.y - p2.x * p1.y) / 2; + } + return area; +} + module.exports = ElementHandle; helper.tracePublicAPI(ElementHandle); diff --git a/test/assets/wrappedlink.html b/test/assets/wrappedlink.html new file mode 100644 index 00000000..55e9c3f9 --- /dev/null +++ b/test/assets/wrappedlink.html @@ -0,0 +1,29 @@ + +