| OLD | NEW |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // This file adheres to closure-compiler conventions in order to enable | 5 // This file adheres to closure-compiler conventions in order to enable |
| 6 // compilation with ADVANCED_OPTIMIZATIONS. In particular, members that are to | 6 // compilation with ADVANCED_OPTIMIZATIONS. In particular, members that are to |
| 7 // be accessed externally should be specified in this['style'] as opposed to | 7 // be accessed externally should be specified in this['style'] as opposed to |
| 8 // this.style because member identifiers are minified by default. | 8 // this.style because member identifiers are minified by default. |
| 9 // See http://goo.gl/FwOgy | 9 // See http://goo.gl/FwOgy |
| 10 | 10 |
| (...skipping 22 matching lines...) Expand all Loading... |
| 33 // JavaScript errors are logged on the main application side. The handler is | 33 // JavaScript errors are logged on the main application side. The handler is |
| 34 // added ASAP to catch any errors in startup. Note this does not appear to | 34 // added ASAP to catch any errors in startup. Note this does not appear to |
| 35 // work in iOS < 5. | 35 // work in iOS < 5. |
| 36 window.addEventListener('error', function(event) { | 36 window.addEventListener('error', function(event) { |
| 37 // Sadly, event.filename and event.lineno are always 'undefined' and '0' | 37 // Sadly, event.filename and event.lineno are always 'undefined' and '0' |
| 38 // with UIWebView. | 38 // with UIWebView. |
| 39 invokeOnHost_({'command': 'window.error', | 39 invokeOnHost_({'command': 'window.error', |
| 40 'message': event.message.toString()}); | 40 'message': event.message.toString()}); |
| 41 }); | 41 }); |
| 42 | 42 |
| 43 /** | |
| 44 * Margin in points around touchable elements (e.g. links for custom context | |
| 45 * menu). | |
| 46 * @type {number} | |
| 47 */ | |
| 48 var touchMargin_ = 25; | |
| 49 | |
| 50 __gCrWeb['getPageWidth'] = function() { | |
| 51 var documentElement = document.documentElement; | |
| 52 var documentBody = document.body; | |
| 53 return Math.max(documentElement.clientWidth, | |
| 54 documentElement.scrollWidth, | |
| 55 documentElement.offsetWidth, | |
| 56 documentBody.scrollWidth, | |
| 57 documentBody.offsetWidth); | |
| 58 }; | |
| 59 | |
| 60 // Implementation of document.elementFromPoint that is working for iOS4 and | |
| 61 // iOS5 and that also goes into frames and iframes. | |
| 62 var elementFromPoint_ = function(x, y) { | |
| 63 var elementFromPointIsUsingViewPortCoordinates = function(win) { | |
| 64 if (win.pageYOffset > 0) { // Page scrolled down. | |
| 65 return (win.document.elementFromPoint( | |
| 66 0, win.pageYOffset + win.innerHeight - 1) === null); | |
| 67 } | |
| 68 if (win.pageXOffset > 0) { // Page scrolled to the right. | |
| 69 return (win.document.elementFromPoint( | |
| 70 win.pageXOffset + win.innerWidth - 1, 0) === null); | |
| 71 } | |
| 72 return false; // No scrolling, don't care. | |
| 73 }; | |
| 74 | |
| 75 var newCoordinate = function(x, y) { | |
| 76 var coordinates = { | |
| 77 x: x, y: y, | |
| 78 viewPortX: x - window.pageXOffset, viewPortY: y - window.pageYOffset, | |
| 79 useViewPortCoordinates: false, | |
| 80 window: window | |
| 81 }; | |
| 82 return coordinates; | |
| 83 }; | |
| 84 | |
| 85 // Returns the coordinates of the upper left corner of |obj| in the | |
| 86 // coordinates of the window that |obj| is in. | |
| 87 var getPositionInWindow = function(obj) { | |
| 88 var coord = { x: 0, y: 0 }; | |
| 89 while (obj.offsetParent) { | |
| 90 coord.x += obj.offsetLeft; | |
| 91 coord.y += obj.offsetTop; | |
| 92 obj = obj.offsetParent; | |
| 93 } | |
| 94 return coord; | |
| 95 }; | |
| 96 | |
| 97 var elementsFromCoordinates = function(coordinates) { | |
| 98 coordinates.useViewPortCoordinates = coordinates.useViewPortCoordinates || | |
| 99 elementFromPointIsUsingViewPortCoordinates(coordinates.window); | |
| 100 | |
| 101 var currentElement = null; | |
| 102 if (coordinates.useViewPortCoordinates) { | |
| 103 currentElement = coordinates.window.document.elementFromPoint( | |
| 104 coordinates.viewPortX, coordinates.viewPortY); | |
| 105 } else { | |
| 106 currentElement = coordinates.window.document.elementFromPoint( | |
| 107 coordinates.x, coordinates.y); | |
| 108 } | |
| 109 // We have to check for tagName, because if a selection is made by the | |
| 110 // UIWebView, the element we will get won't have one. | |
| 111 if (!currentElement || !currentElement.tagName) { | |
| 112 return null; | |
| 113 } | |
| 114 if (currentElement.tagName.toLowerCase() === 'iframe' || | |
| 115 currentElement.tagName.toLowerCase() === 'frame') { | |
| 116 // The following condition is true if the iframe is in a different | |
| 117 // domain; no further information is accessible. | |
| 118 if (typeof(currentElement.contentWindow.document) == 'undefined') { | |
| 119 return currentElement; | |
| 120 } | |
| 121 var framePosition = getPositionInWindow(currentElement); | |
| 122 coordinates.viewPortX -= | |
| 123 framePosition.x - coordinates.window.pageXOffset; | |
| 124 coordinates.viewPortY -= | |
| 125 framePosition.y - coordinates.window.pageYOffset; | |
| 126 coordinates.window = currentElement.contentWindow; | |
| 127 coordinates.x -= framePosition.x + coordinates.window.pageXOffset; | |
| 128 coordinates.y -= framePosition.y + coordinates.window.pageYOffset; | |
| 129 return elementsFromCoordinates(coordinates); | |
| 130 } | |
| 131 return currentElement; | |
| 132 }; | |
| 133 | |
| 134 return elementsFromCoordinates(newCoordinate(x, y)); | |
| 135 }; | |
| 136 | |
| 137 var spiralCoordinates = function(x, y) { | |
| 138 var coordinates = []; | |
| 139 | |
| 140 var maxAngle = Math.PI * 2.0 * 3.0; | |
| 141 var pointCount = 30; | |
| 142 var angleStep = maxAngle / pointCount; | |
| 143 var speed = touchMargin_ / maxAngle; | |
| 144 | |
| 145 for (var index = 0; index < pointCount; index++) { | |
| 146 var angle = angleStep * index; | |
| 147 var radius = angle * speed; | |
| 148 | |
| 149 coordinates.push({x: x + Math.round(Math.cos(angle) * radius), | |
| 150 y: y + Math.round(Math.sin(angle) * radius)}); | |
| 151 } | |
| 152 | |
| 153 return coordinates; | |
| 154 }; | |
| 155 | |
| 156 // Returns the url of the image or link under the selected point. Returns an | |
| 157 // empty string if no links or images are found. | |
| 158 __gCrWeb['getElementFromPoint'] = function(x, y) { | |
| 159 var hitCoordinates = spiralCoordinates(x, y); | |
| 160 for (var index = 0; index < hitCoordinates.length; index++) { | |
| 161 var coordinates = hitCoordinates[index]; | |
| 162 | |
| 163 var element = elementFromPoint_(coordinates.x, coordinates.y); | |
| 164 if (!element || !element.tagName) { | |
| 165 // Nothing under the hit point. Try the next hit point. | |
| 166 continue; | |
| 167 } | |
| 168 | |
| 169 if (getComputedWebkitTouchCallout_(element) === 'none') | |
| 170 continue; | |
| 171 // Also check element's ancestors. A bound on the level is used here to | |
| 172 // avoid large overhead when no links or images are found. | |
| 173 var level = 0; | |
| 174 while (++level < 8 && element && element != document) { | |
| 175 var tagName = element.tagName; | |
| 176 if (!tagName) | |
| 177 continue; | |
| 178 tagName = tagName.toLowerCase(); | |
| 179 | |
| 180 if (tagName === 'input' || tagName === 'textarea' || | |
| 181 tagName === 'select' || tagName === 'option') { | |
| 182 // If the element is a known input element, stop the spiral search and | |
| 183 // return empty results. | |
| 184 return {}; | |
| 185 } | |
| 186 | |
| 187 if (tagName === 'a' && element.href) { | |
| 188 // Found a link. | |
| 189 return { | |
| 190 href: element.href, | |
| 191 referrerPolicy: getReferrerPolicy_(element), | |
| 192 innerText: element.innerText | |
| 193 }; | |
| 194 } | |
| 195 | |
| 196 if (tagName === 'img' && element.src) { | |
| 197 // Found an image. | |
| 198 var result = { | |
| 199 src: element.src, | |
| 200 referrerPolicy: getReferrerPolicy_() | |
| 201 }; | |
| 202 // Copy the title, if any. | |
| 203 if (element.title) { | |
| 204 result.title = element.title; | |
| 205 } | |
| 206 // Check if the image is also a link. | |
| 207 var parent = element.parentNode; | |
| 208 while (parent) { | |
| 209 if (parent.tagName && | |
| 210 parent.tagName.toLowerCase() === 'a' && | |
| 211 parent.href) { | |
| 212 // This regex identifies strings like void(0), | |
| 213 // void(0) ;void(0);, ;;;; | |
| 214 // which result in a NOP when executed as JavaScript. | |
| 215 var regex = RegExp("^javascript:(?:(?:void\\(0\\)|;)\\s*)+$"); | |
| 216 if (parent.href.match(regex)) { | |
| 217 parent = parent.parentNode; | |
| 218 continue; | |
| 219 } | |
| 220 result.href = parent.href; | |
| 221 result.referrerPolicy = getReferrerPolicy_(parent); | |
| 222 break; | |
| 223 } | |
| 224 parent = parent.parentNode; | |
| 225 } | |
| 226 return result; | |
| 227 } | |
| 228 element = element.parentNode; | |
| 229 } | |
| 230 } | |
| 231 return {}; | |
| 232 }; | |
| 233 | |
| 234 // Suppresses the next click such that they are not handled by JS click | |
| 235 // event handlers. | |
| 236 __gCrWeb['suppressNextClick'] = function() { | |
| 237 var suppressNextClick = function(evt) { | |
| 238 evt.preventDefault(); | |
| 239 document.removeEventListener('click', suppressNextClick, false); | |
| 240 }; | |
| 241 document.addEventListener('click', suppressNextClick); | |
| 242 }; | |
| 243 | 43 |
| 244 // Returns true if the top window or any frames inside contain an input | 44 // Returns true if the top window or any frames inside contain an input |
| 245 // field of type 'password'. | 45 // field of type 'password'. |
| 246 __gCrWeb['hasPasswordField'] = function() { | 46 __gCrWeb['hasPasswordField'] = function() { |
| 247 return hasPasswordField_(window); | 47 return hasPasswordField_(window); |
| 248 }; | 48 }; |
| 249 | 49 |
| 250 // Returns a string that is formatted according to the JSON syntax rules. | 50 // Returns a string that is formatted according to the JSON syntax rules. |
| 251 // This is equivalent to the built-in JSON.stringify() function, but is | 51 // This is equivalent to the built-in JSON.stringify() function, but is |
| 252 // less likely to be overridden by the website itself. This public function | 52 // less likely to be overridden by the website itself. This public function |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 331 } | 131 } |
| 332 } | 132 } |
| 333 | 133 |
| 334 return false; | 134 return false; |
| 335 }; | 135 }; |
| 336 | 136 |
| 337 function invokeOnHost_(command) { | 137 function invokeOnHost_(command) { |
| 338 __gCrWeb.message.invokeOnHost(command); | 138 __gCrWeb.message.invokeOnHost(command); |
| 339 }; | 139 }; |
| 340 | 140 |
| 341 /** | |
| 342 * Gets the referrer policy to use for navigations away from the current page. | |
| 343 * If a link element is passed, and it includes a rel=noreferrer tag, that | |
| 344 * will override the page setting. | |
| 345 * @param {HTMLElement=} opt_linkElement The link triggering the navigation. | |
| 346 * @return {string} The policy string. | |
| 347 * @private | |
| 348 */ | |
| 349 var getReferrerPolicy_ = function(opt_linkElement) { | |
| 350 if (opt_linkElement) { | |
| 351 var rel = opt_linkElement.getAttribute('rel'); | |
| 352 if (rel && rel.toLowerCase() == 'noreferrer') { | |
| 353 return 'never'; | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 var metaTags = document.getElementsByTagName('meta'); | |
| 358 for (var i = 0; i < metaTags.length; ++i) { | |
| 359 if (metaTags[i].name.toLowerCase() == 'referrer') { | |
| 360 return metaTags[i].content.toLowerCase(); | |
| 361 } | |
| 362 } | |
| 363 return 'default'; | |
| 364 }; | |
| 365 | |
| 366 // Various aspects of global DOM behavior are overridden here. | 141 // Various aspects of global DOM behavior are overridden here. |
| 367 | 142 |
| 368 // A popstate event needs to be fired anytime the active history entry | 143 // A popstate event needs to be fired anytime the active history entry |
| 369 // changes without an associated document change. Either via back, forward, go | 144 // changes without an associated document change. Either via back, forward, go |
| 370 // navigation or by loading the URL, clicking on a link, etc. | 145 // navigation or by loading the URL, clicking on a link, etc. |
| 371 __gCrWeb['dispatchPopstateEvent'] = function(stateObject) { | 146 __gCrWeb['dispatchPopstateEvent'] = function(stateObject) { |
| 372 var popstateEvent = window.document.createEvent('HTMLEvents'); | 147 var popstateEvent = window.document.createEvent('HTMLEvents'); |
| 373 popstateEvent.initEvent('popstate', true, false); | 148 popstateEvent.initEvent('popstate', true, false); |
| 374 if (stateObject) | 149 if (stateObject) |
| 375 popstateEvent.state = JSON.parse(stateObject); | 150 popstateEvent.state = JSON.parse(stateObject); |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 474 window.scrollTo = function(x, y) { | 249 window.scrollTo = function(x, y) { |
| 475 if (webViewScrollViewIsDragging_) | 250 if (webViewScrollViewIsDragging_) |
| 476 return; | 251 return; |
| 477 originalWindowScrollTo(x, y); | 252 originalWindowScrollTo(x, y); |
| 478 }; | 253 }; |
| 479 | 254 |
| 480 window.addEventListener('hashchange', function(evt) { | 255 window.addEventListener('hashchange', function(evt) { |
| 481 invokeOnHost_({'command': 'window.hashchange'}); | 256 invokeOnHost_({'command': 'window.hashchange'}); |
| 482 }); | 257 }); |
| 483 | 258 |
| 484 var getComputedWebkitTouchCallout_ = function(element) { | |
| 485 return window.getComputedStyle(element, null)['webkitTouchCallout']; | |
| 486 }; | |
| 487 | |
| 488 // Flush the message queue. | 259 // Flush the message queue. |
| 489 if (__gCrWeb.message) { | 260 if (__gCrWeb.message) { |
| 490 __gCrWeb.message.invokeQueues(); | 261 __gCrWeb.message.invokeQueues(); |
| 491 } | 262 } |
| 492 | 263 |
| 493 // Capture form submit actions. | 264 // Capture form submit actions. |
| 494 document.addEventListener('submit', function(evt) { | 265 document.addEventListener('submit', function(evt) { |
| 495 var action; | 266 var action; |
| 496 if (evt['defaultPrevented']) | 267 if (evt['defaultPrevented']) |
| 497 return; | 268 return; |
| 498 action = evt.target.getAttribute('action'); | 269 action = evt.target.getAttribute('action'); |
| 499 // Default action is to re-submit to same page. | 270 // Default action is to re-submit to same page. |
| 500 if (!action) | 271 if (!action) |
| 501 action = document.location.href; | 272 action = document.location.href; |
| 502 invokeOnHost_({ | 273 invokeOnHost_({ |
| 503 'command': 'document.submit', | 274 'command': 'document.submit', |
| 504 'formName': __gCrWeb.common.getFormIdentifier(evt.srcElement), | 275 'formName': __gCrWeb.common.getFormIdentifier(evt.srcElement), |
| 505 'href': __gCrWeb['getFullyQualifiedURL'](action) | 276 'href': __gCrWeb['getFullyQualifiedURL'](action) |
| 506 }); | 277 }); |
| 507 }, false); | 278 }, false); |
| 508 | 279 |
| 509 addFormEventListeners_(); | 280 addFormEventListeners_(); |
| 510 | 281 |
| 511 }()); // End of anonymous object | 282 }()); // End of anonymous object |
| OLD | NEW |