Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1409)

Unified Diff: ios/web/web_state/js/resources/core.js

Issue 1029983002: Upstream ios/web/ JS files (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ios/web/web_state/js/resources/console.js ('k') | ios/web/web_state/js/resources/core_dynamic_ui.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/web/web_state/js/resources/core.js
diff --git a/ios/web/web_state/js/resources/core.js b/ios/web/web_state/js/resources/core.js
new file mode 100644
index 0000000000000000000000000000000000000000..5e5d0dd3376909a4e903206653c0511c5a1fc125
--- /dev/null
+++ b/ios/web/web_state/js/resources/core.js
@@ -0,0 +1,646 @@
+// Copyright 2012 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.
+
+// This file adheres to closure-compiler conventions in order to enable
+// compilation with ADVANCED_OPTIMIZATIONS. In particular, members that are to
+// be accessed externally should be specified in this['style'] as opposed to
+// this.style because member identifiers are minified by default.
+// See http://goo.gl/FwOgy
+
+goog.provide('__crweb.core');
+
+/**
+ * The Chrome object is populated in an anonymous object defined at
+ * initialization to prevent polluting the global namespace.
+ */
+
+/* Beginning of anonymous object. */
+new function() {
+ // TODO(jimblackler): use this namespace as a wrapper for all externally-
+ // visible functions, to be consistent with other JS scripts. crbug.com/380390
+ __gCrWeb['core'] = {};
+
+ // JavaScript errors are logged on the main application side. The handler is
+ // added ASAP to catch any errors in startup. Note this does not appear to
+ // work in iOS < 5.
+ window.addEventListener('error', function(event) {
+ // Sadly, event.filename and event.lineno are always 'undefined' and '0'
+ // with UIWebView.
+ invokeOnHost_({'command': 'window.error',
+ 'message': event.message.toString()});
+ });
+
+ /**
+ * Margin in points around touchable elements (e.g. links for custom context
+ * menu).
+ * @type {number}
+ */
+ var touchMargin_ = 25;
+
+ __gCrWeb['innerSizeAsString'] = function() {
+ return window.innerWidth + '/' + window.innerHeight;
+ };
+
+ // Implementation of document.elementFromPoint that is working for iOS4 and
+ // iOS5 and that also goes into frames and iframes.
+ 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') {
+ // The following condition is true if the iframe is in a different
+ // domain; no further information is accessible.
+ if (typeof(currentElement.contentWindow.document) == 'undefined') {
+ invokeOnHost_({
+ 'command': 'window.error',
+ 'message': 'iframe contentWindow.document is not accessible.'});
+ 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));
+ };
+
+ var spiralCoordinates = function(x, y) {
+ var coordinates = [];
+
+ var maxAngle = Math.PI * 2.0 * 3.0;
+ var pointCount = 30;
+ var angleStep = maxAngle / pointCount;
+ var speed = touchMargin_ / maxAngle;
+
+ for (var index = 0; index < pointCount; index++) {
+ var angle = angleStep * 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;
+ };
+
+ // Returns the url of the image or link under the selected point. Returns an
+ // empty string if no links or images are found.
+ __gCrWeb['getElementFromPoint'] = 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;
+ }
+
+ if (getComputedWebkitTouchCallout_(element) === 'none')
+ 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 (tagName === 'a' && element.href) {
+ // Found a link.
+ return __gCrWeb.common.JSONStringify(
+ {href: element.href,
+ referrerPolicy: getReferrerPolicy_(element)});
+ }
+
+ 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 __gCrWeb.common.JSONStringify(result);
+ }
+ element = element.parentNode;
+ }
+ }
+ return '{}';
+ };
+
+ // Returns true if the top window or any frames inside contain an input
+ // field of type 'password'.
+ __gCrWeb['hasPasswordField'] = function() {
+ return hasPasswordField_(window);
+ };
+
+ // Returns a string that is formatted according to the JSON syntax rules.
+ // This is equivalent to the built-in JSON.stringify() function, but is
+ // less likely to be overridden by the website itself. This public function
+ // should not be used if spoofing it would create a security vulnerability.
+ // The |__gCrWeb| object itself does not use it; it uses its private
+ // counterpart instead.
+ // Prevents websites from changing stringify's behavior by adding the
+ // method toJSON() by temporarily removing it.
+ __gCrWeb['stringify'] = function(value) {
+ if (value === null)
+ return 'null';
+ if (value === undefined)
+ return undefined;
+ if (typeof(value.toJSON) == 'function') {
+ var originalToJSON = value.toJSON;
+ value.toJSON = undefined;
+ var stringifiedValue = __gCrWeb.common.JSONStringify(value);
+ value.toJSON = originalToJSON;
+ return stringifiedValue;
+ }
+ return __gCrWeb.common.JSONStringify(value);
+ };
+
+ /*
+ * Adds the listeners that are used to handle forms, enabling autofill and
+ * the replacement method to dismiss the keyboard needed because of the
+ * Autofill keyboard accessory.
+ */
+ function addFormEventListeners_() {
+ // Focus and input events for form elements are messaged to the main
+ // application for broadcast to CRWWebControllerObservers.
+ // This is done with a single event handler for each type being added to the
+ // main document element which checks the source element of the event; this
+ // is much easier to manage than adding handlers to individual elements.
+ var formActivity = function(evt) {
+ var srcElement = evt.srcElement;
+ var fieldName = srcElement.name || '';
+ var value = srcElement.value || '';
+
+ var msg = {
+ 'command': 'form.activity',
+ 'formName': __gCrWeb.common.getFormIdentifier(evt.srcElement.form),
+ 'fieldName': fieldName,
+ 'type': evt.type,
+ 'value': value
+ };
+ if (evt.keyCode)
+ msg.keyCode = evt.keyCode;
+ invokeOnHost_(msg);
+ };
+
+ // Focus events performed on the 'capture' phase otherwise they are often
+ // not received.
+ document.addEventListener('focus', formActivity, true);
+ document.addEventListener('blur', formActivity, true);
+ document.addEventListener('change', formActivity, true);
+
+ // Text input is watched at the bubbling phase as this seems adequate in
+ // practice and it is less obtrusive to page scripts than capture phase.
+ document.addEventListener('input', formActivity, false);
+ document.addEventListener('keyup', formActivity, false);
+ };
+
+ // Returns true if the supplied window or any frames inside contain an input
+ // field of type 'password'.
+ // @private
+ var hasPasswordField_ = function(win) {
+ var doc = win.document;
+
+ // We may will not be allowed to read the 'document' property from a frame
+ // that is in a different domain.
+ if (!doc) {
+ return false;
+ }
+
+ if (doc.querySelector('input[type=password]')) {
+ return true;
+ }
+
+ var frames = win.frames;
+ for (var i = 0; i < frames.length; i++) {
+ if (hasPasswordField_(frames[i])) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ function invokeOnHost_(command) {
+ __gCrWeb.message.invokeOnHost(command);
+ };
+
+ function invokeOnHostImmediate_(command) {
+ __gCrWeb.message.invokeOnHostImmediate(command);
+ };
+
+ /**
+ * 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} linkElement The link triggering the navigation.
+ * @return {String} The policy string.
+ * @private
+ */
+ var getReferrerPolicy_ = function(linkElement) {
+ if (linkElement) {
+ var rel = linkElement.getAttribute('rel');
+ if (rel && rel.toLowerCase() == 'noreferrer') {
+ return 'never';
+ }
+ }
+
+ var metaTags = document.getElementsByTagName('meta');
+ for (var i = 0; i < metaTags.length; ++i) {
+ if (metaTags[i].name.toLowerCase() == 'referrer') {
+ return metaTags[i].content.toLowerCase();
+ }
+ }
+ return 'default';
+ };
+
+ // Provides a way for other injected javascript to access the page's referrer
+ // policy.
+ __gCrWeb['getPageReferrerPolicy'] = function() {
+ return getReferrerPolicy_();
+ };
+
+ // Various aspects of global DOM behavior are overridden here.
+
+ // A popstate event needs to be fired anytime the active history entry
+ // changes. Either via back, forward, go navigation or by loading the URL,
+ // clicking on a link, etc.
+ __gCrWeb['dispatchPopstateEvent'] = function(stateObject) {
+ var popstateEvent = window.document.createEvent('HTMLEvents');
+ popstateEvent.initEvent('popstate', true, false);
+ if (stateObject)
+ popstateEvent.state = JSON.parse(stateObject);
+
+ // setTimeout() is used in order to return immediately. Otherwise the
+ // dispatchEvent call waits for all event handlers to return, which could
+ // cause a ReentryGuard failure.
+ window.setTimeout(function() {
+ window.dispatchEvent(popstateEvent);
+ }, 0);
+ };
+
+ // Keep the original replaceState() method. It's needed to update UIWebView's
+ // URL and window.history.state property during history navigations that don't
+ // cause a page load.
+ var originalWindowHistoryReplaceState = window.history.replaceState;
+ __gCrWeb['replaceWebViewURL'] = function(url, stateObject) {
+ originalWindowHistoryReplaceState.call(history, stateObject, null, url);
+ };
+
+ // Intercept window.history methods to call back/forward natively.
+ window.history.back = function() {
+ invokeOnHost_({'command': 'window.history.back'});
+ };
+ window.history.forward = function() {
+ invokeOnHost_({'command': 'window.history.forward'});
+ };
+ window.history.go = function(delta) {
+ invokeOnHost_({'command': 'window.history.go', 'value': delta});
+ };
+ window.history.pushState = function(stateObject, pageTitle, pageUrl) {
+ __gCrWeb.core_dynamic.historyWillChangeState();
+ // Calling stringify() on undefined causes a JSON parse error.
+ var serializedState =
+ typeof(stateObject) == 'undefined' ? '' :
+ __gCrWeb.common.JSONStringify(stateObject);
+ pageUrl = pageUrl || window.location.href;
+ originalWindowHistoryReplaceState.call(history, stateObject, null, pageUrl);
+ invokeOnHost_({'command': 'window.history.didPushState',
+ 'stateObject': serializedState,
+ 'baseUrl': document.baseURI,
+ 'pageUrl': pageUrl.toString()});
+ };
+ window.history.replaceState = function(stateObject, pageTitle, pageUrl) {
+ __gCrWeb.core_dynamic.historyWillChangeState();
+ // Calling stringify() on undefined causes a JSON parse error.
+ var serializedState =
+ typeof(stateObject) == 'undefined' ? '' :
+ __gCrWeb.common.JSONStringify(stateObject);
+ pageUrl = pageUrl || window.location.href;
+ originalWindowHistoryReplaceState.call(history, stateObject, null, pageUrl);
+ invokeOnHost_({'command': 'window.history.didReplaceState',
+ 'stateObject': serializedState,
+ 'baseUrl': document.baseURI,
+ 'pageUrl': pageUrl.toString()});
+ };
+
+ __gCrWeb['getFullyQualifiedURL'] = function(originalURL) {
+ // A dummy anchor (never added to the document) is used to obtain the
+ // fully-qualified URL of |originalURL|.
+ var anchor = document.createElement('a');
+ anchor.href = originalURL;
+ return anchor.href;
+ };
+
+ // Intercept window.close calls.
+ window.close = function() {
+ invokeOnHost_({'command': 'window.close.self'});
+ };
+
+ window.addEventListener('hashchange', function(evt) {
+ invokeOnHost_({'command': 'window.hashchange'});
+ });
+
+ __gCrWeb.core_dynamic.addEventListeners();
+
+ // Returns if a frame with |name| is found in |currentWindow|.
+ // Note frame.name is undefined for cross domain frames.
+ var hasFrame_ = function(currentWindow, name) {
+ if (currentWindow.name === name)
+ return true;
+
+ var frames = currentWindow.frames;
+ for (var index = 0; index < frames.length; ++index) {
+ var frame = frames[index];
+ if (frame === undefined)
+ continue;
+ if (hasFrame_(frame, name))
+ return true;
+ }
+ return false;
+ };
+
+ // Checks if |node| is an anchor to be opened in the current tab.
+ var isInternaLink_ = function(node) {
+ if (!node instanceof HTMLAnchorElement)
+ return false;
+
+ // Anchor with href='javascript://.....' will be opened in the current tab
+ // for simplicity.
+ if (node.href.indexOf('javascript:') == 0)
+ return true;
+
+ // UIWebView will take care of the following cases.
+ //
+ // - If the given browsing context name is the empty string or '_self', then
+ // the chosen browsing context must be the current one.
+ //
+ // - If the given browsing context name is '_parent', then the chosen
+ // browsing context must be the parent browsing context of the current
+ // one, unless there is no one, in which case the chosen browsing context
+ // must be the current browsing context.
+ //
+ // - If the given browsing context name is '_top', then the chosen browsing
+ // context must be the top-level browsing context of the current one, if
+ // there is one, or else the current browsing context.
+ //
+ // Here an undefined target is considered in the same way as an empty
+ // target.
+ if (node.target === undefined || node.target === '' ||
+ node.target === '_self' || node.target === '_parent' ||
+ node.target === '_top') {
+ return true;
+ }
+
+ // A new browsing context is being requested for an '_blank' target.
+ if (node.target === '_blank')
+ return false;
+
+ // Otherwise UIWebView will take care of the case where there exists a
+ // browsing context whose name is the same as the given browsing context
+ // name. If there is no such a browsing context, a new browsing context is
+ // being requested.
+ return hasFrame_(window, node.target);
+ };
+
+ var getTargetLink_ = function(target) {
+ var node = target;
+ // Find the closest ancester that is a link.
+ while (node) {
+ if (node instanceof HTMLAnchorElement)
+ break;
+ node = node.parentNode;
+ }
+ return node;
+ };
+
+ var setExternalRequest_ = function(href, target) {
+ if (typeof(target) == 'undefined' || target == '_blank' || target == '') {
+ target = '' + Date.now() + '-' + Math.random();
+ }
+ if (typeof(href) == 'undefined') {
+ // W3C recommended behavior.
+ href = 'about:blank';
+ }
+ // ExternalRequest messages need to be handled before the expected
+ // shouldStartLoadWithRequest, as such we cannot wait for the regular
+ // message queue invoke which delays to avoid illegal recursion into
+ // UIWebView. This immediate class of messages is handled ASAP by
+ // CRWWebController.
+ invokeOnHostImmediate_({'command': 'externalRequest',
+ 'href': href,
+ 'target': target,
+ 'referrerPolicy': getReferrerPolicy_()});
+ };
+
+ var resetExternalRequest_ = function() {
+ invokeOnHost_({'command': 'resetExternalRequest'});
+ };
+
+ var clickBubbleListener_ = function(evt) {
+ if (evt['defaultPrevented']) {
+ resetExternalRequest_();
+ }
+ // Remove the listener.
+ evt.currentTarget.removeEventListener(
+ 'click', clickBubbleListener_, false);
+ };
+
+ var getComputedWebkitTouchCallout_ = function(element) {
+ return window.getComputedStyle(element, null)['webkitTouchCallout'];
+ };
+
+ /**
+ * This method applies the various document-level overrides. Sometimes the
+ * document object gets reset in the early stages of the page lifecycle, so
+ * this is exposed as a method for the application to invoke later. That way
+ * the window-level overrides can be applied as soon as possible.
+ */
+ __gCrWeb.core.documentInject = function() {
+ // Perform web view specific operations requiring document.body presence.
+ // If necessary returns and waits for document to be present.
+ if (!__gCrWeb.core_dynamic.documentInject())
+ return;
+
+ document.addEventListener('click', function(evt) {
+ var node = getTargetLink_(evt.target);
+
+ if (!node)
+ return;
+
+ if (isInternaLink_(node)) {
+ return;
+ }
+ setExternalRequest_(node.href, node.target);
+ // Add listener to the target and its immediate ancesters. These event
+ // listeners will be removed if they get called. The listeners for some
+ // elements might never be removed, but if multiple identical event
+ // listeners are registered on the same event target with the same
+ // parameters the duplicate instances are discarded.
+ for (var level = 0; level < 5; ++level) {
+ if (node && node != document) {
+ node.addEventListener('click', clickBubbleListener_, false);
+ node = node.parentNode;
+ } else {
+ break;
+ }
+ }
+ }, true);
+
+ // Intercept clicks on anchors (links) during bubbling phase so that the
+ // browser can handle target type appropriately.
+ document.addEventListener('click', function(evt) {
+ var node = getTargetLink_(evt.target);
+
+ if (!node)
+ return;
+
+ if (isInternaLink_(node)) {
+ if (evt['defaultPrevented'])
+ return;
+ // Internal link. The web view will handle navigation, but register
+ // the anchor for UIWebView to start the progress indicator ASAP and
+ // notify web controller as soon as possible of impending navigation.
+ if (__gCrWeb.core_dynamic.handleInternalClickEvent) {
+ __gCrWeb.core_dynamic.handleInternalClickEvent(node);
+ }
+ return;
+ } else {
+ // Resets the external request if it has been canceled, otherwise
+ // updates the href in case it has been changed.
+ if (evt['defaultPrevented'])
+ resetExternalRequest_();
+ else
+ setExternalRequest_(node.href, node.target);
+ }
+ }, false);
+
+ // Capture form submit actions.
+ document.addEventListener('submit', function(evt) {
+ if (evt['defaultPrevented'])
+ return;
+
+ var form = evt.target;
+ var targetsFrame = form.target && hasFrame_(window, form.target);
+ // TODO(stuartmorgan): Handle external targets. crbug.com/233543
+
+ var action = form.getAttribute('action');
+ // Default action is to re-submit to same page.
+ if (!action)
+ action = document.location.href;
+ invokeOnHost_({
+ 'command': 'document.submit',
+ 'formName': __gCrWeb.common.getFormIdentifier(evt.srcElement),
+ 'href': __gCrWeb['getFullyQualifiedURL'](action),
+ 'targetsFrame': targetsFrame
+ });
+ }, false);
+
+ addFormEventListeners_();
+
+ // Handle or wait for and handle document load completion, if applicable.
+ if (__gCrWeb.core_dynamic.handleDocumentLoaded)
+ __gCrWeb.core_dynamic.handleDocumentLoaded();
+
+ return true;
+ };
+
+ __gCrWeb.core.documentInject();
+
+ // Form prototype loaded with event to supply Autocomplete API
+ // functionality.
+ HTMLFormElement.prototype.requestAutocomplete = function() {
+ invokeOnHost_(
+ {'command': 'form.requestAutocomplete',
+ 'formName': __gCrWeb.common.getFormIdentifier(this)});
+ };
+} // End of anonymous object
« no previous file with comments | « ios/web/web_state/js/resources/console.js ('k') | ios/web/web_state/js/resources/core_dynamic_ui.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698