| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview APIs used by CRWContextMenuController. |
| */ |
| |
| goog.provide('__crWeb.contextMenu'); |
| |
| /** Beginning of anonymous object */ |
| (function() { |
| |
| /** |
| * Returns the url of the image or link under the selected point. Returns an |
| * empty object if no links or images are found. |
| * @param {number} x Horizontal center of the selected point in web view |
| * coordinates. |
| * @param {number} y Vertical center of the selected point in web view |
| * coordinates. |
| * @param {number} webViewWidth the width of web view. |
| * @param {number} webViewHeight the height of web view. |
| * @return {!Object} An object of the form { |
| * href, // URL of the link under the point |
| * innerText, // innerText of the link, if the selected element is a link |
| * src, // src of the image, if the selected element is an image |
| * title, // title of the image, if the selected |
| * referrerPolicy |
| * } |
| * where: |
| * <ul> |
| * <li>href, innerText are set if the selected element is a link. |
| * <li>src, title are set if the selected element is an image. |
| * <li>href is also set if the selected element is an image with a link. |
| * <li>referrerPolicy is the referrer policy to use for navigations away |
| * from the current page. |
| * </ul> |
| */ |
| __gCrWeb['getElementFromPoint'] = |
| function(x, y, webViewWidth, webViewHeight) { |
| var scale = getPageWidth() / webViewWidth; |
| return getElementFromPointInPageCoordinates(x * scale, y * scale) |
| }; |
| |
| /** |
| * Suppresses the next click such that they are not handled by JS click |
| * event handlers. |
| * @type {void} |
| */ |
| __gCrWeb['suppressNextClick'] = function() { |
| var suppressNextClick = function(evt) { |
| evt.preventDefault(); |
| document.removeEventListener('click', suppressNextClick, false); |
| }; |
| document.addEventListener('click', suppressNextClick); |
| }; |
| |
| /** |
| * Returns the url of the image or link under the selected point in page |
| * coordinates. Returns an empty object if no links or images are found. |
| * @param {number} x Horizontal center of the selected point in page |
| * coordinates. |
| * @param {number} y Vertical center of the selected point in page |
| * coordinates. |
| * @return {!Object} An object in the same form as |
| * {@code getElementFromPoint} result. |
| */ |
| var getElementFromPointInPageCoordinates = function(x, y) { |
| var hitCoordinates = spiralCoordinates_(x, y); |
| for (var index = 0; index < hitCoordinates.length; index++) { |
| var coordinates = hitCoordinates[index]; |
| |
| var element = elementFromPoint_(coordinates.x, coordinates.y); |
| if (!element || !element.tagName) { |
| // Nothing under the hit point. Try the next hit point. |
| continue; |
| } |
| |
| // Also check element's ancestors. A bound on the level is used here to |
| // avoid large overhead when no links or images are found. |
| var level = 0; |
| while (++level < 8 && element && element != document) { |
| var tagName = element.tagName; |
| if (!tagName) |
| continue; |
| tagName = tagName.toLowerCase(); |
| |
| if (tagName === 'input' || tagName === 'textarea' || |
| tagName === 'select' || tagName === 'option') { |
| // If the element is a known input element, stop the spiral search and |
| // return empty results. |
| return {}; |
| } |
| |
| if (getComputedWebkitTouchCallout_(element) !== 'none') { |
| if (tagName === 'a' && element.href) { |
| // Found a link. |
| return { |
| href: element.href, |
| referrerPolicy: getReferrerPolicy_(element), |
| innerText: element.innerText |
| }; |
| } |
| |
| if (tagName === 'img' && element.src) { |
| // Found an image. |
| var result = { |
| src: element.src, |
| referrerPolicy: getReferrerPolicy_() |
| }; |
| // Copy the title, if any. |
| if (element.title) { |
| result.title = element.title; |
| } |
| // Check if the image is also a link. |
| var parent = element.parentNode; |
| while (parent) { |
| if (parent.tagName && |
| parent.tagName.toLowerCase() === 'a' && |
| parent.href) { |
| // This regex identifies strings like void(0), |
| // void(0) ;void(0);, ;;;; |
| // which result in a NOP when executed as JavaScript. |
| var regex = RegExp("^javascript:(?:(?:void\\(0\\)|;)\\s*)+$"); |
| if (parent.href.match(regex)) { |
| parent = parent.parentNode; |
| continue; |
| } |
| result.href = parent.href; |
| result.referrerPolicy = getReferrerPolicy_(parent); |
| break; |
| } |
| parent = parent.parentNode; |
| } |
| return result; |
| } |
| } |
| element = element.parentNode; |
| } |
| } |
| return {}; |
| }; |
| |
| /** |
| * Returns the margin in points around touchable elements (e.g. links for |
| * custom context menu). |
| * @type {number} |
| */ |
| var getPageWidth = function() { |
| var documentElement = document.documentElement; |
| var documentBody = document.body; |
| return Math.max(documentElement.clientWidth, |
| documentElement.scrollWidth, |
| documentElement.offsetWidth, |
| documentBody.scrollWidth, |
| documentBody.offsetWidth); |
| }; |
| |
| /** |
| * Implementation of document.elementFromPoint that is working for iOS4 and |
| * iOS5 and that also goes into frames and iframes. |
| * @private |
| */ |
| var elementFromPoint_ = function(x, y) { |
| var elementFromPointIsUsingViewPortCoordinates = function(win) { |
| if (win.pageYOffset > 0) { // Page scrolled down. |
| return (win.document.elementFromPoint( |
| 0, win.pageYOffset + win.innerHeight - 1) === null); |
| } |
| if (win.pageXOffset > 0) { // Page scrolled to the right. |
| return (win.document.elementFromPoint( |
| win.pageXOffset + win.innerWidth - 1, 0) === null); |
| } |
| return false; // No scrolling, don't care. |
| }; |
| |
| var newCoordinate = function(x, y) { |
| var coordinates = { |
| x: x, y: y, |
| viewPortX: x - window.pageXOffset, viewPortY: y - window.pageYOffset, |
| useViewPortCoordinates: false, |
| window: window |
| }; |
| return coordinates; |
| }; |
| |
| // Returns the coordinates of the upper left corner of |obj| in the |
| // coordinates of the window that |obj| is in. |
| var getPositionInWindow = function(obj) { |
| var coord = { x: 0, y: 0 }; |
| while (obj.offsetParent) { |
| coord.x += obj.offsetLeft; |
| coord.y += obj.offsetTop; |
| obj = obj.offsetParent; |
| } |
| return coord; |
| }; |
| |
| var elementsFromCoordinates = function(coordinates) { |
| coordinates.useViewPortCoordinates = coordinates.useViewPortCoordinates || |
| elementFromPointIsUsingViewPortCoordinates(coordinates.window); |
| |
| var currentElement = null; |
| if (coordinates.useViewPortCoordinates) { |
| currentElement = coordinates.window.document.elementFromPoint( |
| coordinates.viewPortX, coordinates.viewPortY); |
| } else { |
| currentElement = coordinates.window.document.elementFromPoint( |
| 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. |
| if (!currentElement || !currentElement.tagName) { |
| return null; |
| } |
| if (currentElement.tagName.toLowerCase() === 'iframe' || |
| currentElement.tagName.toLowerCase() === 'frame') { |
| // Check if the frame is in a different domain using only information |
| // visible to the current frame (i.e. currentElement.src) to avoid |
| // triggering a SecurityError in the console. |
| if (!__gCrWeb.common.isSameOrigin( |
| window.location.href, currentElement.src)) { |
| return currentElement; |
| } |
| var framePosition = getPositionInWindow(currentElement); |
| coordinates.viewPortX -= |
| framePosition.x - coordinates.window.pageXOffset; |
| coordinates.viewPortY -= |
| framePosition.y - coordinates.window.pageYOffset; |
| coordinates.window = currentElement.contentWindow; |
| coordinates.x -= framePosition.x + coordinates.window.pageXOffset; |
| coordinates.y -= framePosition.y + coordinates.window.pageYOffset; |
| return elementsFromCoordinates(coordinates); |
| } |
| return currentElement; |
| }; |
| |
| return elementsFromCoordinates(newCoordinate(x, y)); |
| }; |
| |
| /** @private */ |
| var spiralCoordinates_ = function(x, y) { |
| var MAX_ANGLE = Math.PI * 2.0 * 3.0; |
| var POINT_COUNT = 30; |
| var ANGLE_STEP = MAX_ANGLE / POINT_COUNT; |
| var TOUCH_MARGIN = 25; |
| var SPEED = TOUCH_MARGIN / MAX_ANGLE; |
| |
| var coordinates = []; |
| for (var index = 0; index < POINT_COUNT; index++) { |
| var angle = ANGLE_STEP * index; |
| var radius = angle * SPEED; |
| |
| coordinates.push({x: x + Math.round(Math.cos(angle) * radius), |
| y: y + Math.round(Math.sin(angle) * radius)}); |
| } |
| |
| return coordinates; |
| }; |
| |
| /** @private */ |
| var getComputedWebkitTouchCallout_ = function(element) { |
| return window.getComputedStyle(element, null)['webkitTouchCallout']; |
| }; |
| |
| /** |
| * Gets the referrer policy to use for navigations away from the current page. |
| * If a link element is passed, and it includes a rel=noreferrer tag, that |
| * will override the page setting. |
| * @param {HTMLElement=} opt_linkElement The link triggering the navigation. |
| * @return {string} The policy string. |
| * @private |
| */ |
| var getReferrerPolicy_ = function(opt_linkElement) { |
| if (opt_linkElement) { |
| var rel = opt_linkElement.getAttribute('rel'); |
| if (rel && rel.toLowerCase() == 'noreferrer') { |
| return 'never'; |
| } |
| } |
| |
| // Search for referrer meta tag. WKWebView only supports a subset of values |
| // for referrer meta tags. If it parses a referrer meta tag with an |
| // unsupported value, it will default to 'never'. |
| var metaTags = document.getElementsByTagName('meta'); |
| for (var i = 0; i < metaTags.length; ++i) { |
| if (metaTags[i].name.toLowerCase() == 'referrer') { |
| var referrerPolicy = metaTags[i].content.toLowerCase(); |
| if (referrerPolicy == 'default' || |
| referrerPolicy == 'always' || |
| referrerPolicy == 'no-referrer' || |
| referrerPolicy == 'origin' || |
| referrerPolicy == 'no-referrer-when-downgrade' || |
| referrerPolicy == 'unsafe-url') { |
| return referrerPolicy; |
| } else { |
| return 'never'; |
| } |
| } |
| } |
| return 'default'; |
| }; |
| |
| }()); // End of anonymouse object |