Commit 5581dc00 authored by Mike Dougherty's avatar Mike Dougherty Committed by Commit Bot

[iOS] Add context menu support for shadow DOM

The context menu currently does not find elements inside the shadow DOM
of another which are attached with the |attachShadow| API. Add support
by looking inside an element's shadowRoot.

Bug: 1017257
Change-Id: I720e54f1ef7157c3ae5200c8dd097302dcee7429
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1877602
Commit-Queue: Mike Dougherty <michaeldo@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#709155}
parent fbb6a5d4
...@@ -69,6 +69,13 @@ const CGPoint kPointOnSvgLink = {50.0, 75.0}; ...@@ -69,6 +69,13 @@ const CGPoint kPointOnSvgLink = {50.0, 75.0};
// on the svg link returned by |GetHtmlForSvgLink()| and |GetHtmlForSvgXlink()|. // on the svg link returned by |GetHtmlForSvgLink()| and |GetHtmlForSvgXlink()|.
const CGPoint kPointOutsideSvgLink = {50.0, 10.0}; const CGPoint kPointOutsideSvgLink = {50.0, 10.0};
// A point in the web view's coordinate space on the shadow DOM link returned by
// |GetHtmlForShadowDomLink()|.
const CGPoint kPointOnShadowDomLink = {5.0, 2.0};
// A point in the web view's coordinate space within the shadow DOM returned by
// |GetHtmlForShadowDomLink()| but not on the link.
const CGPoint kPointOutsideShadowDomLink = {50.0, 75.0};
// A point in the web view's coordinate space outside of the document bounds. // A point in the web view's coordinate space outside of the document bounds.
const CGPoint kPointOutsideDocument = {150.0, 150.0}; const CGPoint kPointOutsideDocument = {150.0, 150.0};
...@@ -127,6 +134,21 @@ NSString* GetHtmlForLink(NSString* href, NSString* text) { ...@@ -127,6 +134,21 @@ NSString* GetHtmlForLink(NSString* href, NSString* text) {
return GetHtmlForLink(href, text, /*style=*/nil); return GetHtmlForLink(href, text, /*style=*/nil);
} }
// Returns HTML for a shadow DOM link to |href| and display text |text|.
NSString* GetHtmlForShadowDomLink(NSString* href, NSString* text) {
NSString* shadow_html = [NSString
stringWithFormat:@"<div style=\"height:100px;font-size:20px\">%@</div>",
GetHtmlForLink(href, text)];
return [NSString
stringWithFormat:
@"<div id='largeDiv' style='height:100px'></div>"
@"<script>var shadow = "
@"document.getElementById('largeDiv').attachShadow({mode: 'open'});"
@"shadow.innerHTML = '%@';"
@"</script>",
shadow_html];
}
// Returns html for an image styled to fill the width and top 25% of its // Returns html for an image styled to fill the width and top 25% of its
// container. |source| must be provided, but specifying an image |title| and // container. |source| must be provided, but specifying an image |title| and
// inline |style| are optional. // inline |style| are optional.
...@@ -579,6 +601,38 @@ TEST_F(ContextMenuJsFindElementAtPointTest, LinkOfTextFromTallPage) { ...@@ -579,6 +601,38 @@ TEST_F(ContextMenuJsFindElementAtPointTest, LinkOfTextFromTallPage) {
EXPECT_NSEQ(expected_result, result); EXPECT_NSEQ(expected_result, result);
} }
// Tests that __gCrWeb.findElementAtPoint finds a link inside shadow DOM
// content.
TEST_F(ContextMenuJsFindElementAtPointTest, ShadowDomLink) {
NSString* const link = @"http://destination/";
ASSERT_TRUE(web::test::LoadHtml(
web_view_,
GetHtmlForPage(/*head=*/nil, GetHtmlForShadowDomLink(link, @"link")),
GetTestURL()));
id result = FindElementAtPoint(kPointOnShadowDomLink);
NSDictionary* expected_result = @{
kContextMenuElementRequestId : kRequestId,
kContextMenuElementInnerText : @"link",
kContextMenuElementReferrerPolicy : @"default",
kContextMenuElementHyperlink : link,
};
EXPECT_NSEQ(expected_result, result);
}
// Tests that a point within shadow DOM content but not on a link does not
// return details for the link.
TEST_F(ContextMenuJsFindElementAtPointTest, PointOutsideShadowDomLink) {
NSString* const link = @"http://destination/";
ASSERT_TRUE(web::test::LoadHtml(
web_view_,
GetHtmlForPage(/*head=*/nil, GetHtmlForShadowDomLink(link, @"link")),
GetTestURL()));
id result = FindElementAtPoint(kPointOutsideShadowDomLink);
EXPECT_NSEQ(@{kContextMenuElementRequestId : kRequestId}, result);
}
// Tests that a callout information about a link is displayed when // Tests that a callout information about a link is displayed when
// -webkit-touch-callout property is not specified. Please see: // -webkit-touch-callout property is not specified. Please see:
// https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-touch-callout // https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-touch-callout
......
...@@ -92,7 +92,7 @@ __gCrWeb['findElementAtPointInPageCoordinates'] = function(requestId, x, y) { ...@@ -92,7 +92,7 @@ __gCrWeb['findElementAtPointInPageCoordinates'] = function(requestId, x, y) {
var coordinates = hitCoordinates[index]; var coordinates = hitCoordinates[index];
var coordinateDetails = newCoordinate(coordinates.x, coordinates.y); var coordinateDetails = newCoordinate(coordinates.x, coordinates.y);
var element = elementsFromCoordinates(coordinateDetails); var element = elementsFromCoordinates(window.document, coordinateDetails);
// if element is a frame, tell it to respond to this element request // if element is a frame, tell it to respond to this element request
if (element && if (element &&
(element.tagName.toLowerCase() === 'iframe' || (element.tagName.toLowerCase() === 'iframe' ||
...@@ -219,26 +219,28 @@ var newCoordinate = function(x, y) { ...@@ -219,26 +219,28 @@ var newCoordinate = function(x, y) {
/** /**
* Returns the element at the given coordinates. * Returns the element at the given coordinates.
* @param {Object} root The Document or ShadowRoot object to search within.
* @param {Object} coordinates Page coordinates in the same format as the result * @param {Object} coordinates Page coordinates in the same format as the result
* from {@code newCoordinate}. * from {@code newCoordinate}.
*/ */
var elementsFromCoordinates = function(coordinates) { var elementsFromCoordinates = function(root, coordinates) {
coordinates.useViewPortCoordinates = coordinates.useViewPortCoordinates || coordinates.useViewPortCoordinates = coordinates.useViewPortCoordinates ||
elementFromPointIsUsingViewPortCoordinates(coordinates.window); elementFromPointIsUsingViewPortCoordinates(coordinates.window);
var currentElement = null; var currentElement = null;
if (coordinates.useViewPortCoordinates) { if (coordinates.useViewPortCoordinates) {
currentElement = coordinates.window.document.elementFromPoint( currentElement = root.elementFromPoint(
coordinates.viewPortX, coordinates.viewPortY); coordinates.viewPortX, coordinates.viewPortY);
} else { } else {
currentElement = coordinates.window.document.elementFromPoint( currentElement = root.elementFromPoint(coordinates.x, coordinates.y);
coordinates.x, coordinates.y);
} }
// We have to check for tagName, because if a selection is made by the
// UIWebView, the element we will get won't have one. // Check for tagName, because if a selection is made by the WebView, the
// element we will get won't have one.
if (!currentElement || !currentElement.tagName) { if (!currentElement || !currentElement.tagName) {
return null; return null;
} }
if (currentElement.tagName.toLowerCase() === 'iframe' || if (currentElement.tagName.toLowerCase() === 'iframe' ||
currentElement.tagName.toLowerCase() === 'frame') { currentElement.tagName.toLowerCase() === 'frame') {
// Check if the frame is in a different domain using only information // Check if the frame is in a different domain using only information
...@@ -254,7 +256,16 @@ var elementsFromCoordinates = function(coordinates) { ...@@ -254,7 +256,16 @@ var elementsFromCoordinates = function(coordinates) {
coordinates.window = currentElement.contentWindow; coordinates.window = currentElement.contentWindow;
coordinates.x -= framePosition.x + coordinates.window.pageXOffset; coordinates.x -= framePosition.x + coordinates.window.pageXOffset;
coordinates.y -= framePosition.y + coordinates.window.pageYOffset; coordinates.y -= framePosition.y + coordinates.window.pageYOffset;
return elementsFromCoordinates(coordinates); return elementsFromCoordinates(coordinates.window.document, coordinates);
}
if (currentElement.shadowRoot) {
// The element's shadowRoot can be the same as |root| if the point is not
// on any child element. Break the recursion and return no found element.
if (currentElement.shadowRoot == root) {
return null;
}
return elementsFromCoordinates(currentElement.shadowRoot, coordinates);
} }
return currentElement; return currentElement;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment