summaryrefslogtreecommitdiffstats
path: root/ios
diff options
context:
space:
mode:
authorstuartmorgan <stuartmorgan@chromium.org>2015-03-24 15:22:28 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-24 22:23:15 +0000
commit1ab833b209a2e2fc1e6bf2001d0bfcc5181e7bf2 (patch)
tree886435b5ff0cf5b98fea89ba7a59f0be77ce1b3c /ios
parent187d3948887386d83b37e1b7101562022ad26d1b (diff)
downloadchromium_src-1ab833b209a2e2fc1e6bf2001d0bfcc5181e7bf2.zip
chromium_src-1ab833b209a2e2fc1e6bf2001d0bfcc5181e7bf2.tar.gz
chromium_src-1ab833b209a2e2fc1e6bf2001d0bfcc5181e7bf2.tar.bz2
Upstream ios/web/ JS files
This upstreams all the injected JS for the web layer (but not any of the packaging bits, which will come later) BUG=464810 Review URL: https://codereview.chromium.org/1029983002 Cr-Commit-Position: refs/heads/master@{#322080}
Diffstat (limited to 'ios')
-rw-r--r--ios/web/web_state/js/resources/base.js2
-rw-r--r--ios/web/web_state/js/resources/common.js113
-rw-r--r--ios/web/web_state/js/resources/console.js44
-rw-r--r--ios/web/web_state/js/resources/core.js646
-rw-r--r--ios/web/web_state/js/resources/core_dynamic_ui.js157
-rw-r--r--ios/web/web_state/js/resources/core_dynamic_wk.js59
-rw-r--r--ios/web/web_state/js/resources/dialog_overrides.js114
-rw-r--r--ios/web/web_state/js/resources/message.js2
-rw-r--r--ios/web/web_state/js/resources/message_dynamic_ui.js2
-rw-r--r--ios/web/web_state/js/resources/message_dynamic_wk.js2
-rw-r--r--ios/web/web_state/js/resources/plugin_placeholder.js199
-rw-r--r--ios/web/web_state/js/resources/web_bundle_ui.js16
-rw-r--r--ios/web/web_state/js/resources/web_bundle_wk.js15
-rw-r--r--ios/web/web_state/js/resources/window_id.js31
-rw-r--r--ios/web/web_state/js/resources/window_open_ui.js245
-rw-r--r--ios/web/web_state/js/resources/window_open_wk.js15
16 files changed, 1662 insertions, 0 deletions
diff --git a/ios/web/web_state/js/resources/base.js b/ios/web/web_state/js/resources/base.js
index 97b9622..216ddaf 100644
--- a/ios/web/web_state/js/resources/base.js
+++ b/ios/web/web_state/js/resources/base.js
@@ -8,6 +8,8 @@
// this.style because member identifiers are minified by default.
// See http://goo.gl/FwOgy
+goog.provide('__crweb.base');
+
// This object is checked on the main app to know when to inject (or not).
window['__gCrWeb'] = {};
diff --git a/ios/web/web_state/js/resources/common.js b/ios/web/web_state/js/resources/common.js
index 1cea022..7e84350 100644
--- a/ios/web/web_state/js/resources/common.js
+++ b/ios/web/web_state/js/resources/common.js
@@ -4,6 +4,8 @@
// This file provides common methods that can be shared by other JavaScripts.
+goog.provide('__crweb.common');
+
/**
* Namespace for this file. It depends on |__gCrWeb| having already been
* injected. String 'common' is used in |__gCrWeb['common']| as it needs to be
@@ -576,4 +578,115 @@ new function() {
element.dispatchEvent(changeEvent);
}, 0);
};
+
+ /**
+ * Retrieves favicon information.
+ *
+ * @return {Object} Object containing favicon data.
+ */
+ __gCrWeb.common.getFavicons = function() {
+ var favicons = [];
+ var hasFavicon = false;
+ favicons.toJSON = null; // Never inherit Array.prototype.toJSON.
+ var links = document.getElementsByTagName('link');
+ var linkCount = links.length;
+ for (var i = 0; i < linkCount; ++i) {
+ if (links[i].rel) {
+ var rel = links[i].rel.toLowerCase();
+ if (rel == 'shortcut icon' ||
+ rel == 'icon' ||
+ rel == 'apple-touch-icon' ||
+ rel == 'apple-touch-icon-precomposed') {
+ var favicon = {
+ rel: links[i].rel.toLowerCase(),
+ href: links[i].href
+ };
+ favicons.push(favicon);
+ if (rel == 'icon' || rel == 'shortcut icon') {
+ hasFavicon = true;
+ }
+ }
+ }
+ }
+ if (!hasFavicon) {
+ // If an HTTP(S)? webpage does not reference a "favicon" then search
+ // for a file named "favicon.ico" at the root of the website (legacy).
+ // http://en.wikipedia.org/wiki/Favicon
+ var location = document.location;
+ if (location.protocol == 'http:' || location.protocol == 'https:') {
+ var favicon = {
+ rel: 'icon',
+ href: location.origin + '/favicon.ico'
+ };
+ favicons.push(favicon);
+ }
+ }
+ return favicons;
+ };
+
+ /**
+ * Checks whether an <object> node is plugin content (as <object> can also be
+ * used to embed images).
+ * @param {HTMLElement} node The <object> node to check.
+ * @return {Boolean} Whether the node appears to be a plugin.
+ * @private
+ */
+ var objectNodeIsPlugin_ = function(node) {
+ return node.hasAttribute('classid') ||
+ (node.hasAttribute('type') && node.type.indexOf('image/') != 0);
+ };
+
+ /**
+ * Checks whether plugin a node has fallback content.
+ * @param {HTMLElement} node The node to check.
+ * @return {Boolean} Whether the node has fallback.
+ * @private
+ */
+ var pluginHasFallbackContent_ = function(node) {
+ return node.textContent.trim().length > 0 ||
+ node.getElementsByTagName('img').length > 0;
+ };
+
+ /**
+ * Returns a list of plugin elements in the document that have no fallback
+ * content. For nested plugins, only the innermost plugin element is returned.
+ * @return {Array} A list of plugin elements.
+ * @private
+ */
+ var findPluginNodesWithoutFallback_ = function() {
+ var pluginNodes = [];
+ var objects = document.getElementsByTagName('object');
+ var objectCount = objects.length;
+ for (var i = 0; i < objectCount; i++) {
+ var object = objects[i];
+ if (objectNodeIsPlugin_(object) &&
+ !pluginHasFallbackContent_(object)) {
+ pluginNodes.push(object);
+ }
+ }
+ var applets = document.getElementsByTagName('applet');
+ var appletsCount = applets.length;
+ for (var i = 0; i < appletsCount; i++) {
+ var applet = applets[i];
+ if (!pluginHasFallbackContent_(applet)) {
+ pluginNodes.push(applet);
+ }
+ }
+ return pluginNodes;
+ };
+
+ /**
+ * Finds and stores any plugins that don't have placeholders.
+ * Returns true if any plugins without placeholders are found.
+ */
+ __gCrWeb.common.updatePluginPlaceholders = function() {
+ var plugins = findPluginNodesWithoutFallback_();
+ if (plugins.length > 0) {
+ // Store the list of plugins in a known place for the replacement script
+ // to use, then trigger it.
+ __gCrWeb['placeholderTargetPlugins'] = plugins;
+ return true;
+ }
+ return false;
+ };
} // End of anonymous object
diff --git a/ios/web/web_state/js/resources/console.js b/ios/web/web_state/js/resources/console.js
new file mode 100644
index 0000000..0e67644
--- /dev/null
+++ b/ios/web/web_state/js/resources/console.js
@@ -0,0 +1,44 @@
+// Copyright 2015 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.
+
+// Scripts to allow page console.log() etc. output to be seen on the console
+// of the host application.
+
+goog.provide('__crweb.console');
+
+/**
+ * Namespace for this module.
+ */
+__gCrWeb.console = {};
+
+/* Beginning of anonymous object. */
+new function() {
+ function sendConsoleMessage(method, originalArguments) {
+ message = Array.prototype.slice.call(originalArguments).join(' ');
+ __gCrWeb.message.invokeOnHost({'command': 'console',
+ 'method': method,
+ 'message': message,
+ 'origin': document.location.origin});
+ }
+
+ console.log = function() {
+ sendConsoleMessage('log', arguments);
+ };
+
+ console.debug = function() {
+ sendConsoleMessage('debug', arguments);
+ };
+
+ console.info = function() {
+ sendConsoleMessage('info', arguments);
+ };
+
+ console.warn = function() {
+ sendConsoleMessage('warn', arguments);
+ };
+
+ console.error = function() {
+ sendConsoleMessage('error', arguments);
+ };
+}
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 0000000..5e5d0dd
--- /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
diff --git a/ios/web/web_state/js/resources/core_dynamic_ui.js b/ios/web/web_state/js/resources/core_dynamic_ui.js
new file mode 100644
index 0000000..e8988b7
--- /dev/null
+++ b/ios/web/web_state/js/resources/core_dynamic_ui.js
@@ -0,0 +1,157 @@
+// Copyright 2014 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.
+
+// Scripts that are conceptually part of core.js, but have UIWebView-specific
+// details/behaviors.
+
+goog.provide('__crweb.core_dynamic_ui');
+
+/**
+ * Namespace for this module.
+ */
+__gCrWeb.core_dynamic = {};
+
+/* Beginning of anonymous object. */
+new function() {
+ /**
+ * Resets common.JSONStringify to a clean copy. This can be called to ensure
+ * that its copy is not of an override injected earlier by the page. This
+ * must be called after document.body is present.
+ */
+ var resetJsonStringify_ = function() {
+ var frame = document.createElement('iframe');
+ // crwebnull protocol returns NO immediately to reject the load attempt.
+ // A new frame is still created with an independent window object.
+ frame.src = 'crwebnull://';
+ document.body.appendChild(frame);
+ // One some occasions the contentWindow is not available, or the JSON object
+ // is not available. It is not clear why, but this defense still has value
+ // when it can be applied.
+ if (frame.contentWindow && frame.contentWindow.JSON) {
+ // Refresh original stringify object from new window reference (if
+ // available) in case the originally retained version was not the native
+ // version.
+ __gCrWeb.common.JSONStringify = frame.contentWindow.JSON.stringify;
+ }
+ document.body.removeChild(frame);
+ };
+
+ /**
+ * Adds UIWebView specific event listeners.
+ */
+ __gCrWeb.core_dynamic.addEventListeners = function() {
+ window.addEventListener('unload', function(evt) {
+ // In the case of a newly-created UIWebView, the URL starts as
+ // about:blank, the window.unload event fires, and then the URL changes.
+ // However, at this point the window object is *not* reset. After this,
+ // when the page changes for any reason the window object *is* reset.
+ // For this reason, we do not report the window.unload event from the
+ // default page to the first URL.
+ // This is sent as an immediate command because if the message arrives
+ // after the page change has taken effect, false positive security errors
+ // can occur.
+ if (!document._defaultPage)
+ __gCrWeb.message.invokeOnHostImmediate({'command': 'window.unload'});
+ });
+ };
+
+ /**
+ * Applies UIWebView specific document-level overrides. These overrides
+ * require the document body to be present; therefore the method sets event
+ * listeners and timers to retry upon document body load if document body is
+ * not yet present. Returns false if on default page or document is not
+ * present.
+ */
+ __gCrWeb.core_dynamic.documentInject = function() {
+ // The default page gets the injections to the window object, but not the
+ // document object. On the first occasion the page changes (from the default
+ // about:blank) the window.unload event fires but the window object does not
+ // actually reset. However by the time the DOMContentLoaded event fires the
+ // document object will have been reset (to a non-default page).
+ if (document && document['_defaultPage']) {
+ window.addEventListener('DOMContentLoaded', __gCrWeb.core.documentInject);
+ return false;
+ }
+ if (!document || !document.body) {
+ // Either the document or document body is not yet available... retest in
+ // 1 / 4 of a second.
+ window.setTimeout(__gCrWeb.core.documentInject, 250);
+ return false;
+ }
+
+ // Try to guarantee a clean copy of common.JSONStringify.
+ resetJsonStringify_();
+
+ // Flush the message queue and send document.present message, if load has
+ // not been aborted.
+ if (!document._cancelled) {
+ if (__gCrWeb.message) {
+ __gCrWeb.message.invokeQueues();
+ }
+ __gCrWeb.message.invokeOnHost({'command': 'document.present'});
+ }
+
+ // Add event listener for title changes.
+ var lastSeenTitle = document.title;
+ document.addEventListener('DOMSubtreeModified', function(evt) {
+ if (document.title !== lastSeenTitle) {
+ lastSeenTitle = document.title;
+ __gCrWeb.message.invokeOnHost({'command': 'document.retitled'});
+ }
+ });
+
+ return true;
+ };
+
+ /**
+ * Notifies client and handles post-document load tasks when document has
+ * finished loading.
+ */
+ __gCrWeb.core_dynamic.handleDocumentLoaded = function() {
+ var invokeOnHost_ = __gCrWeb.message.invokeOnHost;
+ var loaded_ = function() {
+ invokeOnHost_({'command': 'document.loaded'});
+ // Send the favicons to the browser.
+ invokeOnHost_({'command': 'document.favicons',
+ 'favicons': __gCrWeb.common.getFavicons()});
+ // Add placeholders for plugin content.
+ if (__gCrWeb.common.updatePluginPlaceholders())
+ __gCrWeb.message.invokeOnHost({'command': 'addPluginPlaceholders'});
+ };
+
+ if (document.readyState === 'loaded' || document.readyState === 'complete')
+ loaded_();
+ else
+ window.addEventListener('load', loaded_);
+ }
+
+
+ /**
+ * Sends anchor.click message.
+ */
+ __gCrWeb.core_dynamic.handleInternalClickEvent = function(node) {
+ __gCrWeb.message.invokeOnHost({'command': 'anchor.click',
+ 'href': node.href});
+ }
+
+ /**
+ * Called when history.pushState and history.replaceState are invoked.
+ */
+ __gCrWeb.core_dynamic.historyWillChangeState = function () {
+ // UIWebViewWebController does not need to be notified prior to
+ // history.pushState or history.replaceState calls.
+ };
+
+ /**
+ * Exits Fullscreen video by calling webkitExitFullScreen on every video
+ * element.
+ */
+ __gCrWeb['exitFullscreenVideo'] = function() {
+ var videos = document.getElementsByTagName('video');
+ var videosLength = videos.length;
+ for (var i = 0; i < videosLength; ++i) {
+ videos[i].webkitExitFullScreen();
+ }
+ };
+}
diff --git a/ios/web/web_state/js/resources/core_dynamic_wk.js b/ios/web/web_state/js/resources/core_dynamic_wk.js
new file mode 100644
index 0000000..dd91478
--- /dev/null
+++ b/ios/web/web_state/js/resources/core_dynamic_wk.js
@@ -0,0 +1,59 @@
+// Copyright 2014 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.
+
+// Scripts that are conceptually part of core.js, but have WKWebView-specific
+// details/behaviors.
+
+goog.provide('__crweb.core_dynamic_wk');
+
+/**
+ * Namespace for this module.
+ */
+__gCrWeb.core_dynamic = {};
+
+/* Beginning of anonymous object. */
+new function() {
+ /**
+ * Adds WKWebView specific event listeners.
+ */
+ __gCrWeb.core_dynamic.addEventListeners = function() {
+ // So far there are no WKWebView specific event listeners.
+ };
+
+ /**
+ * Applies WKWebView specific document-level overrides. Script injection for
+ * WKWebView always happens after the document is presented; therefore, there
+ * is no need to provide for invoking documentInject at a later time.
+ */
+ __gCrWeb.core_dynamic.documentInject = function() {
+ // Flush the message queue.
+ if (__gCrWeb.message) {
+ __gCrWeb.message.invokeQueues();
+ }
+ return true;
+ };
+
+ /**
+ * Handles document load completion tasks. Invoked from
+ * [WKNavigationDelegate webView:didFinishNavigation:], when document load is
+ * complete.
+ */
+ __gCrWeb.didFinishNavigation = function() {
+ // Send the favicons to the browser.
+ __gCrWeb.message.invokeOnHost({'command': 'document.favicons',
+ 'favicons': __gCrWeb.common.getFavicons()});
+ // Add placeholders for plugin content.
+ if (__gCrWeb.common.updatePluginPlaceholders())
+ __gCrWeb.message.invokeOnHost({'command': 'addPluginPlaceholders'});
+ }
+
+ /**
+ * Sends window.history.willChangeState message. Called when
+ * history.pushState and history.replaceState are invoked.
+ */
+ __gCrWeb.core_dynamic.historyWillChangeState = function() {
+ __gCrWeb.message.invokeOnHost(
+ {'command': 'window.history.willChangeState'});
+ };
+}
diff --git a/ios/web/web_state/js/resources/dialog_overrides.js b/ios/web/web_state/js/resources/dialog_overrides.js
new file mode 100644
index 0000000..d5c582e
--- /dev/null
+++ b/ios/web/web_state/js/resources/dialog_overrides.js
@@ -0,0 +1,114 @@
+// Copyright 2014 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.
+
+goog.provide('__crweb.dialog_overrides');
+
+// Namespace for this module.
+__gCrWeb.dialogOverrides = {};
+
+// Beginning of anonymous object.
+new function() {
+ /*
+ * Install a wrapper around functions displaying dialogs in order to catch
+ * code displaying dialog.
+ *
+ * Since the Javascript on the page may cache the value of those functions
+ * and invoke them later, we must only install the wrapper once and change
+ * their behaviour when required.
+ *
+ * Returns a function that allows changing the value of the two booleans
+ * |suppressDialogs| and |notifyAboutDialogs| that are tested by the wrappers.
+ */
+ var installDialogOverridesMethods = function() {
+ var suppressDialogs = false;
+ var notifyAboutDialogs = false;
+
+ // Returns a wrapper function around |originalDialog|. The wrapper may
+ // suppress the dialog and notify host about show/suppress.
+ var makeDialogWrapper = function(originalDialogGetter) {
+ return function() {
+ if (!suppressDialogs) {
+ if (notifyAboutDialogs) {
+ __gCrWeb.message.invokeOnHost({'command': 'dialog.willShow'});
+ }
+ return originalDialogGetter().apply(null, arguments);
+ } else if (notifyAboutDialogs) {
+ __gCrWeb.message.invokeOnHost({'command': 'dialog.suppressed'});
+ }
+ };
+ };
+
+ // Install wrapper around the following properties of |window|.
+ var wrappedFunctionNames = ['alert', 'confirm', 'prompt', 'open'];
+ var len = wrappedFunctionNames.length;
+ for (var i = 0; i < len; i++) {
+ (function(wrappedFunctionName) {
+ var wrappedDialogMethod = window[wrappedFunctionName];
+ window[wrappedFunctionName] = makeDialogWrapper(
+ function() { return wrappedDialogMethod; });
+ })(wrappedFunctionNames[i]);
+ }
+
+ // Reading or writing to the property 'geolocation' too early breaks
+ // the API. Make a copy of navigator and stub in the required methods
+ // without touching the property. See crbug.com/280818 for more
+ // details.
+ var stubNavigator = {};
+
+ // Copy all properties and functions without touching 'geolocation'.
+ var oldNavigator = navigator;
+ for (var keyName in navigator) {
+ if (keyName !== 'geolocation') {
+ var value = navigator[keyName];
+ if (typeof(value) == 'function') {
+ // Forward functions calls to real navigator.
+ stubNavigator[keyName] = function() {
+ return value.apply(oldNavigator, arguments);
+ }
+ } else {
+ Object['defineProperty'](stubNavigator, keyName, {
+ value: value,
+ configurable: false,
+ writable: false,
+ enumerable: true
+ });
+ }
+ }
+ }
+
+ // Stub in 'geolocation' if necessary, using delayed accessor for the
+ // 'geolocation' property of the original |navigator|.
+ if ('geolocation' in navigator) {
+ var geolocation = {};
+ var geoPropNames = ['getCurrentPosition', 'watchPosition', 'clearWatch'];
+ var len = geoPropNames.length;
+ for (var i = 0; i < len; i++) {
+ (function(geoPropName) {
+ geolocation[geoPropName] = makeDialogWrapper(function() {
+ return function() {
+ return oldNavigator.geolocation[geoPropName].apply(
+ oldNavigator.geolocation, arguments);
+ };
+ });
+ })(geoPropNames[i]);
+ }
+ stubNavigator.geolocation = geolocation;
+ }
+
+ // Install |stubNavigator| as |navigator|.
+ navigator = stubNavigator;
+
+ // Returns the closure allowing to change |suppressDialogs| and
+ // |notifyAboutDialogs| variables.
+ return function(setEnabled, setNotify) {
+ suppressDialogs = setEnabled;
+ notifyAboutDialogs = setNotify;
+ };
+ };
+
+ // Override certain methods that produce dialogs. This needs to be installed
+ // after other window methods overrides.
+ __gCrWeb['setSuppressDialogs'] = installDialogOverridesMethods();
+
+} // End of anonymous object
diff --git a/ios/web/web_state/js/resources/message.js b/ios/web/web_state/js/resources/message.js
index 40ebda4..30e4a6b 100644
--- a/ios/web/web_state/js/resources/message.js
+++ b/ios/web/web_state/js/resources/message.js
@@ -4,6 +4,8 @@
// Scripts for the message handler.
+goog.provide('__crweb.message');
+
/**
* Namespace for this module.
*/
diff --git a/ios/web/web_state/js/resources/message_dynamic_ui.js b/ios/web/web_state/js/resources/message_dynamic_ui.js
index 5d17954..9ec5cdc 100644
--- a/ios/web/web_state/js/resources/message_dynamic_ui.js
+++ b/ios/web/web_state/js/resources/message_dynamic_ui.js
@@ -4,6 +4,8 @@
// Scripts for the message handler for use with UIWebView.
+goog.provide('__crweb.message_dynamic_ui');
+
/**
* Namespace for this module.
*/
diff --git a/ios/web/web_state/js/resources/message_dynamic_wk.js b/ios/web/web_state/js/resources/message_dynamic_wk.js
index 644f2fa..d8b0c24 100644
--- a/ios/web/web_state/js/resources/message_dynamic_wk.js
+++ b/ios/web/web_state/js/resources/message_dynamic_wk.js
@@ -4,6 +4,8 @@
// Scripts for the message handler for use with WKWebView.
+goog.provide('__crweb.message_dynamic_wk');
+
/**
* Namespace for this module.
*/
diff --git a/ios/web/web_state/js/resources/plugin_placeholder.js b/ios/web/web_state/js/resources/plugin_placeholder.js
new file mode 100644
index 0000000..4a588fd
--- /dev/null
+++ b/ios/web/web_state/js/resources/plugin_placeholder.js
@@ -0,0 +1,199 @@
+// Copyright 2013 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. See http://goo.gl/FwOgy
+//
+// Installs and runs the plugin placeholder function on the |__gCrWeb| object.
+
+/**
+ * Namespace for this file. It depends on |__gCrWeb| having already been
+ * injected.
+ */
+__gCrWeb['plugin'] = {};
+
+/* Beginning of anonymous object. */
+new function() {
+
+ /* Data-URL version of plugin_blocked_android.png. Served this way rather
+ * than with an intercepted URL to avoid messing up https pages.
+ */
+ __gCrWeb['plugin'].imageData_ =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAAD' +
+ 'aklEQVR4Xn2Wz2tcVRTHP/e+O28mMxONJKlF4kIkP4luXFgQuuxCBaG41IWrLupOXLur+A' +
+ 'e4cmV3LiS6qujSLgq2CIKQUqS2YnWsRkzGSTIz7zyHw+EdchnkcOd+7+OeT84578tMwmet' +
+ 'O1fkar1RRNAgUJuqbeEn/0RUcdS6UX7w0X54/93qw4V+m0IReBiizhAYpG52kfrO86+F9/' +
+ 'YXNnukHOTpc5SHgpiOu1cT623FBELeGvgTXfppOAjN3dCKm7GIkWiY4LsBnqBPpGqAgN/z' +
+ 'CDMMBsCWX+pwibd5hzdZZmLNOsxDm8VAzIkt1hX5NLucqgrZm3RlIC/XscKTNlAQpvncMi' +
+ 'tAnEM33D4nqgbcosBSPT3DRTJ3+Cx+4UfV3/CQniMQQ5g2WMJkoGKHNodUCBDpsYEQ2KGm' +
+ 'JBKIFPT4nYckB9ueaPxRscamWczco3qXLcR9wx4ndBsziqFSjaOCAWLm4kj0xhhSMVFli4' +
+ 'opyYuLlJ7s+/xTE6IgcVBthUuW6goHZDiA5IeCAnFEhkKVxxQh+pnoqSeMCEw4Uvt5kEHP' +
+ 'c8IyF3iJ5De1NYSAMOYvOtxgwBqv0wcE5rR4gcQGq9Sc5wt7bq2JtfYtI0Ys8mCmLhFg7q' +
+ 'w6XKRStUHJiMJmpC8vglqypAOU/MwRiw7KYGKqxZSKqE/iTKrQAwGxv5oU4ZbzGHCTf1QN' +
+ 'OTXbQhJ/gbxKjy85IPECHQSQ3EFUfM0+93iZgluM6LuzDUTJOXpc5jcWeDb3DjQrsMhj9t' +
+ 'TdPcAq8mtjjunyFEtN8ohfOWaVZR88Qd2WKK15a5zoRY8ZmRaNIZ/yCZ/P1u0zY+9TASjc' +
+ 'q04YMzBhqAAUBXf5iWcITGdql3aTtpIZVnxGYvSxj1VPXUB0EtHnxBoT6iwgeXEwQfwC69' +
+ 'xmROAcr5DwESxa3XLGW9G9AgPGVKahzzb/UvEcq81PwCl/MyDMrUgxQeMH7tNniQW6nPKA' +
+ 'e5TU3KUFjPmTRxyofUsFeFVQqyENBHDAYyodJhR0CFrnfaYECgvAjdogEwZCVySQaJ8Zeq' +
+ 'AL874rsy+2ofT1ev5fkSdmihwF0jpOra/kskTHkGMckkG9Gg7Xvw9XtifXOy/GEgCr7H/r' +
+ 'yepFOFy5fu1agI9XH71RbRWRrDmHOhrfLYrx9ndv3Wz98R+P7LgG2uyMvgAAAABJRU5Erk' +
+ 'Jggg==';
+
+ /**
+ * Returns the first <embed> child of the given node, if any.
+ * @param {HTMLElement} node The node to check.
+ * @return {HTMLElement} The first <embed> child, or null.
+ * @private
+ */
+ __gCrWeb['plugin'].getEmbedChild_ = function(node) {
+ if (node.hasChildNodes()) {
+ for (var i = 0; i < node.childNodes.length; i++) {
+ if (node.childNodes[i].nodeName === 'EMBED') {
+ return node.childNodes[i];
+ }
+ }
+ }
+ return null;
+ };
+
+ /**
+ * Returns the size for the given plugin element. For the common
+ * pattern of an IE-specific <object> wrapping an all-other-browsers <embed>,
+ * the object doesn't have real style info (most notably size), so this uses
+ * the embed in that case.
+ * @param {HTMLElement} plugin The <object> node to check.
+ * @return {Object} The size (width and height) for the plugin element.
+ * @private
+ */
+ __gCrWeb['plugin'].getPluginSize_ = function(plugin) {
+ var style;
+ // For the common pattern of an IE-specific <object> wrapping an
+ // all-other-browsers <embed>, the object doesn't have real style info
+ // (most notably size), so this uses the embed in that case.
+ var embedChild = __gCrWeb['plugin'].getEmbedChild_(plugin);
+ if (embedChild) {
+ style = window.getComputedStyle(embedChild);
+ } else {
+ style = window.getComputedStyle(plugin);
+ }
+
+ var width = parseFloat(style.width);
+ var height = parseFloat(style.height);
+ if (plugin.tagName === 'APPLET') {
+ // Size computation doesn't always work correctly with applets in
+ // UIWebView, so use the attributes as fallbacks.
+ if (isNaN(width)) {
+ width = parseFloat(plugin.width);
+ }
+ if (isNaN(height)) {
+ height = parseFloat(plugin.height);
+ }
+ }
+
+ return {
+ 'width': width,
+ 'height': height
+ };
+ };
+
+ /**
+ * Checks whether an element is "significant". Whether a plugin is
+ * "significant" is a heuristic that attempts to determine if it's a critical
+ * visual element for the page (i.e., not invisible, or an incidental ad).
+ * @param {HTMLElement} plugin The <object> node to check.
+ * @return {Boolean} Whether the node is significant.
+ * @private
+ */
+ __gCrWeb['plugin'].isSignificantPlugin_ = function(plugin) {
+ var windowWidth = window.innerWidth;
+ var windowHeight = window.innerHeight;
+ var pluginSize = __gCrWeb['plugin'].getPluginSize_(plugin);
+ var pluginWidth = parseFloat(pluginSize.width);
+ var pluginHeight = parseFloat(pluginSize.height);
+ // A plugin must be at least |significantFraction| of one dimension of the
+ // page, and a minimum size in the other dimension (to weed out banners and
+ // tall side ads).
+ var minSize = Math.min(200, windowWidth / 2, windowHeight / 2);
+ var significantFraction = 0.5;
+ return (pluginWidth > windowWidth * significantFraction &&
+ pluginHeight > minSize) ||
+ (pluginHeight > windowHeight * significantFraction &&
+ pluginWidth > minSize);
+ };
+
+ /**
+ * Walks the list of detected plugin elements, adding a placeholder to any
+ * that are "significant" (see above).
+ * @param {string} message The message to show in the placeholder.
+ */
+ __gCrWeb['plugin']['addPluginPlaceholders'] = function(message) {
+ var plugins = __gCrWeb['placeholderTargetPlugins'];
+ for (i = 0; i < plugins.length; i++) {
+ var plugin = plugins[i];
+ if (!__gCrWeb['plugin'].isSignificantPlugin_(plugin)) {
+ continue;
+ }
+
+ var pluginSize = __gCrWeb['plugin'].getPluginSize_(plugin);
+ var widthStyle = pluginSize.width + 'px';
+ var heightStyle = pluginSize.height + 'px';
+
+ // The outer wrapper is a div with relative positioning, as an anchor for
+ // an inner absolute-position element, whose height is based on whether or
+ // not there's an embed. If there is, then it's zero height, to avoid
+ // affecting the layout of the (presumably-full-size) <embed> fallback. If
+ // not, it's full-height to ensure the placeholder takes up the right
+ // amount of space in the page layout. Width is full-width either way, to
+ // avoid being affected by container alignment.
+ var placeholder = document.createElement('div');
+ placeholder.style.width = widthStyle;
+ if (__gCrWeb['plugin'].getEmbedChild_(plugin)) {
+ placeholder.style.height = '0';
+ } else {
+ placeholder.style.height = heightStyle;
+ }
+ placeholder.style.position = 'relative';
+
+ // Inside is a full-plugin-size solid box.
+ var placeholderBox = document.createElement('div');
+ placeholderBox.style.position = 'absolute';
+ placeholderBox.style.boxSizing = 'border-box';
+ placeholderBox.style.width = widthStyle;
+ placeholderBox.style.height = heightStyle;
+ placeholderBox.style.border = '1px solid black';
+ placeholderBox.style.backgroundColor = '#808080';
+ placeholder.appendChild(placeholderBox);
+
+ // Inside that is the plugin placeholder image, centered.
+ var pluginImg = document.createElement('img');
+ var imageSize = 36;
+ pluginImg.width = imageSize;
+ pluginImg.height = imageSize;
+ pluginImg.style.position = 'absolute';
+ // Center vertically and horizontally.
+ var halfSize = imageSize / 2;
+ pluginImg.style.top = '50%';
+ pluginImg.style.marginTop = '-' + halfSize + 'px';
+ pluginImg.style.left = '50%';
+ pluginImg.style.marginLeft = '-' + halfSize + 'px';
+ pluginImg.src = __gCrWeb['plugin'].imageData_;
+ placeholderBox.appendChild(pluginImg);
+
+ // And below that, the message.
+ var label = document.createElement('p');
+ label.style.width = widthStyle;
+ label.style.height = '1.5em';
+ label.style.position = 'absolute';
+ // Position below the image.
+ label.style.top = '50%';
+ label.style.marginTop = imageSize + 'px';
+ // Center horizontally.
+ label.style.textAlign = 'center';
+ label.textContent = message;
+ placeholderBox.appendChild(label);
+
+ plugin.insertBefore(placeholder, plugin.firstChild);
+ }
+ };
+} // End of anonymous object
diff --git a/ios/web/web_state/js/resources/web_bundle_ui.js b/ios/web/web_state/js/resources/web_bundle_ui.js
new file mode 100644
index 0000000..db29cb7
--- /dev/null
+++ b/ios/web/web_state/js/resources/web_bundle_ui.js
@@ -0,0 +1,16 @@
+// Copyright 2015 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.
+
+// Set of scripts required by web layer backed up by UIWebView.
+goog.provide('__crweb.web_bundle_ui');
+
+goog.require('__crweb.base');
+goog.require('__crweb.common');
+goog.require('__crweb.core');
+goog.require('__crweb.core_dynamic_ui');
+goog.require('__crweb.console');
+goog.require('__crweb.dialog_overrides');
+goog.require('__crweb.message');
+goog.require('__crweb.message_dynamic_ui');
+goog.require('__crweb.window_open_ui');
diff --git a/ios/web/web_state/js/resources/web_bundle_wk.js b/ios/web/web_state/js/resources/web_bundle_wk.js
new file mode 100644
index 0000000..ff26a6c
--- /dev/null
+++ b/ios/web/web_state/js/resources/web_bundle_wk.js
@@ -0,0 +1,15 @@
+// Copyright 2015 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.
+
+// Set of scripts required by web layer backed up by WKWebView.
+goog.provide('__crweb.web_bundle_wk');
+
+goog.require('__crweb.base');
+goog.require('__crweb.common');
+goog.require('__crweb.core');
+goog.require('__crweb.core_dynamic_wk');
+goog.require('__crweb.console');
+goog.require('__crweb.dialog_overrides');
+goog.require('__crweb.message');
+goog.require('__crweb.message_dynamic_wk');
diff --git a/ios/web/web_state/js/resources/window_id.js b/ios/web/web_state/js/resources/window_id.js
new file mode 100644
index 0000000..73ef670
--- /dev/null
+++ b/ios/web/web_state/js/resources/window_id.js
@@ -0,0 +1,31 @@
+// Copyright 2014 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. See http://goo.gl/FwOgy
+
+// Script to set windowId.
+
+
+// Namespace for module, used as presence beacon for injection checks.
+__gCrWeb['windowIdObject'] = {};
+
+new function() {
+ // CRWJSWindowIdManager replaces $(WINDOW_ID) with appropriate string upon
+ // injection.
+ __gCrWeb['windowId'] = '$(WINDOW_ID)';
+
+ // Wrap queues flushing in setTimeout to avoid reentrant calls.
+ // In some circumstances setTimeout does not work on iOS8 if set from
+ // injected script. There is an assumption that it's happen when the script
+ // has been injected too early. Do not place anything important to delayed
+ // function body, since there is no guarantee that it will ever be executed.
+ // TODO(eugenebut): Find out why setTimeout does not work (crbug.com/402682).
+ window.setTimeout(function() {
+ // Send messages queued since message.js injection.
+ if (__gCrWeb.message) {
+ __gCrWeb.message.invokeQueues();
+ }
+ }, 0);
+}
diff --git a/ios/web/web_state/js/resources/window_open_ui.js b/ios/web/web_state/js/resources/window_open_ui.js
new file mode 100644
index 0000000..fb418f1
--- /dev/null
+++ b/ios/web/web_state/js/resources/window_open_ui.js
@@ -0,0 +1,245 @@
+// Copyright 2014 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.
+
+// Scripts that are conceptually part of core.js, but have UIWebView-specific
+// details/behaviors.
+
+goog.provide('__crweb.window_open_ui');
+
+// Namespace for this module.
+__gCrWeb.windowOpen = {};
+
+// Beginning of anonymous object.
+new function() {
+ // Preserve a reference to the original window.open method.
+ __gCrWeb['originalWindowOpen'] = window.open;
+
+ // Object used to keep track of all windows opened from this window.
+ var openedWindows = {};
+
+ /**
+ * Checks if a child window exists with the given name and if so, sets its
+ * closed property to true and removes it from |openedWindows|.
+ * @param {String} windowName The name of the window to mark as closed.
+ */
+ __gCrWeb['windowClosed'] = function(windowName) {
+ if (openedWindows.hasOwnProperty(windowName)) {
+ openedWindows[windowName].closed = true;
+ delete openedWindows[windowName];
+ }
+ };
+
+ function invokeOnHost_(command) {
+ __gCrWeb.message.invokeOnHost(command);
+ };
+
+ var invokeNotImplementedOnHost_ = function(methodName) {
+ invokeOnHost_({'command': 'window.error',
+ 'message': methodName + ' is not implemented'});
+ };
+
+ // Define Object watch/unwatch functions to detect assignments to
+ // certain object properties. Handles defineProperty case only because
+ // this code runs in UIWebView (i.e. Safari).
+ var objectWatch = function(obj, prop, handler) {
+ var val = obj[prop];
+ if (delete obj[prop]) {
+ Object['defineProperty'](obj, prop, {
+ 'get': function() {
+ return val;
+ },
+ 'set': function(newVal) {
+ return val = handler.call(obj, prop, val, newVal);
+ }
+ });
+ }
+ };
+
+ /**
+ * Creates and returns a window proxy used to represent the window object and
+ * intercept calls made on it.
+ * @param {String} target The name of the window.
+ * @return {Object} A window proxy object for intercepting window methods.
+ * @private
+ */
+ var createWindowProxy_ = function(target) {
+ // Create return window object.
+ // 'name' is always the original supplied name.
+ var windowProxy = {name: target};
+
+ // Define window object methods.
+ windowProxy.alert = function() {
+ invokeNotImplementedOnHost_('windowProxy.alert');
+ };
+
+ windowProxy.blur = function() {
+ invokeNotImplementedOnHost_('windowProxy.blur');
+ };
+
+ windowProxy.clearInterval = function() {
+ invokeNotImplementedOnHost_('windowProxy.clearInterval');
+ };
+
+ windowProxy.clearTimeout = function() {
+ invokeNotImplementedOnHost_('windowProxy.clearTimeout');
+ };
+
+ windowProxy.close = function() {
+ invokeOnHost_({'command': 'window.close',
+ 'target': target});
+ };
+
+ windowProxy.confirm = function() {
+ invokeNotImplementedOnHost_('windowProxy.confirm');
+ };
+
+ windowProxy.createPopup = function() {
+ invokeNotImplementedOnHost_('windowProxy.createPopup');
+ };
+
+ windowProxy.focus = function() {
+ // Noop as the opened window always gets focus.
+ };
+
+ windowProxy.moveBy = function() {
+ invokeNotImplementedOnHost_('windowProxy.moveBy');
+ };
+
+ windowProxy.moveTo = function() {
+ invokeNotImplementedOnHost_('windowProxy.moveTo');
+ };
+
+ windowProxy.stop = function() {
+ invokeOnHost_({'command': 'window.stop',
+ 'target': target});
+ };
+
+ windowProxy.open = function() {
+ invokeNotImplementedOnHost_('windowProxy.open');
+ };
+
+ windowProxy.print = function() {
+ invokeNotImplementedOnHost_('windowProxy.print');
+ };
+
+ windowProxy.prompt = function() {
+ invokeNotImplementedOnHost_('windowProxy.prompt');
+ };
+
+ windowProxy.resizeBy = function() {
+ invokeNotImplementedOnHost_('windowProxy.resizeBy');
+ };
+
+ windowProxy.resizeTo = function() {
+ invokeNotImplementedOnHost_('windowProxy.resizeTo');
+ };
+
+ windowProxy.scroll = function() {
+ invokeNotImplementedOnHost_('windowProxy.scroll');
+ };
+
+ windowProxy.scrollBy = function() {
+ invokeNotImplementedOnHost_('windowProxy.scrollBy');
+ };
+
+ windowProxy.scrollTo = function() {
+ invokeNotImplementedOnHost_('windowProxy.scrollTo');
+ };
+
+ windowProxy.setInterval = function() {
+ invokeNotImplementedOnHost_('windowProxy.setInterval');
+ };
+
+ windowProxy.setTimeout = function() {
+ invokeNotImplementedOnHost_('windowProxy.setTimeout');
+ };
+
+ // Define window object properties.
+ // The current window.
+ windowProxy.self = windowProxy;
+ // The topmost browser window.
+ windowProxy.top = windowProxy;
+
+ // Provide proxy document which supplies one method, document.write().
+ windowProxy.document = {};
+ windowProxy.document.title = '';
+ windowProxy.document.write = function(html) {
+ invokeOnHost_({'command': 'window.document.write',
+ 'html': html,
+ 'target': target});
+ };
+
+ windowProxy.document.open = function() {
+ // The open() method should open an output stream to collect the output
+ // from any document.write() or document.writeln() methods.
+ invokeNotImplementedOnHost_('windowProxy.document.open');
+ };
+
+ windowProxy.document.close = function() {
+ // The close() method should close the output stream previously opened
+ // with the document.open() method, and displays the collected data in
+ // this process.
+ invokeNotImplementedOnHost_('windowProxy.document.close');
+ };
+
+ windowProxy.location = {};
+ windowProxy.location.assign = function(url) {
+ windowProxy.location = url;
+ };
+ // Watch assignments to window.location and window.location.href.
+ // Invoke equivalent method in ObjC code.
+ var onWindowProxyLocationChange = function(prop, oldVal, newVal) {
+ invokeOnHost_({'command': 'window.location',
+ 'value': __gCrWeb['getFullyQualifiedURL'](newVal),
+ 'target': target});
+ return newVal;
+ };
+ objectWatch(windowProxy, 'location', onWindowProxyLocationChange);
+ objectWatch(windowProxy.location, 'href', onWindowProxyLocationChange);
+ windowProxy.closed = false;
+
+ return windowProxy;
+ };
+
+ // Intercept window.open calls.
+ window.open = function(url, target, features) {
+ if (target == '_parent' || target == '_self' || target == '_top') {
+ return __gCrWeb['originalWindowOpen'].call(window, url, target, features);
+ }
+
+ // Because of the difficulty of returning data from JS->ObjC calls, in the
+ // event of a blank window name the JS side chooses a pseudo-GUID to
+ // use as the window name which is passed to ObjC and mapped to the real
+ // Tab there.
+ var isTargetBlank = (typeof target == 'undefined' || target == '_blank' ||
+ target == '' || target == null);
+ if (isTargetBlank) {
+ target = '' + Date.now() + '-' + Math.random();
+ }
+
+ if (typeof(url) == 'undefined') {
+ // W3C recommended behavior.
+ url = 'about:blank';
+ }
+
+ invokeOnHost_({
+ 'command': 'window.open',
+ 'target': target,
+ 'url': url,
+ 'referrerPolicy': __gCrWeb.getPageReferrerPolicy()
+ });
+
+ // Create a new |windowProxy| if none already exists with |target| as its
+ // name.
+ var windowProxy;
+ if (openedWindows.hasOwnProperty(target)) {
+ windowProxy = openedWindows[target];
+ } else {
+ windowProxy = createWindowProxy_(target);
+ openedWindows[target] = windowProxy;
+ }
+ return windowProxy;
+ };
+
+} // End of anonymous object
diff --git a/ios/web/web_state/js/resources/window_open_wk.js b/ios/web/web_state/js/resources/window_open_wk.js
new file mode 100644
index 0000000..209628c
--- /dev/null
+++ b/ios/web/web_state/js/resources/window_open_wk.js
@@ -0,0 +1,15 @@
+// Copyright 2014 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.
+
+goog.provide('__crweb.window_open_wk');
+
+// WKWebView natively supports window.open so there is no need to install
+// JavaScript-based fix. CRWJSWindowOpenManager always injected as multiple
+// JS managers depend on it. The script does not have any content except
+// presenceBeacon.
+
+
+// Namespace for this module.
+__gCrWeb.windowOpen = {};
+