OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // This file adheres to closure-compiler conventions in order to enable |
| 6 // compilation with ADVANCED_OPTIMIZATIONS. In particular, members that are to |
| 7 // be accessed externally should be specified in this['style'] as opposed to |
| 8 // this.style because member identifiers are minified by default. |
| 9 // See http://goo.gl/FwOgy |
| 10 |
| 11 goog.provide('__crweb.core'); |
| 12 |
| 13 /** |
| 14 * The Chrome object is populated in an anonymous object defined at |
| 15 * initialization to prevent polluting the global namespace. |
| 16 */ |
| 17 |
| 18 /* Beginning of anonymous object. */ |
| 19 new function() { |
| 20 // TODO(jimblackler): use this namespace as a wrapper for all externally- |
| 21 // visible functions, to be consistent with other JS scripts. crbug.com/380390 |
| 22 __gCrWeb['core'] = {}; |
| 23 |
| 24 // JavaScript errors are logged on the main application side. The handler is |
| 25 // added ASAP to catch any errors in startup. Note this does not appear to |
| 26 // work in iOS < 5. |
| 27 window.addEventListener('error', function(event) { |
| 28 // Sadly, event.filename and event.lineno are always 'undefined' and '0' |
| 29 // with UIWebView. |
| 30 invokeOnHost_({'command': 'window.error', |
| 31 'message': event.message.toString()}); |
| 32 }); |
| 33 |
| 34 /** |
| 35 * Margin in points around touchable elements (e.g. links for custom context |
| 36 * menu). |
| 37 * @type {number} |
| 38 */ |
| 39 var touchMargin_ = 25; |
| 40 |
| 41 __gCrWeb['innerSizeAsString'] = function() { |
| 42 return window.innerWidth + '/' + window.innerHeight; |
| 43 }; |
| 44 |
| 45 // Implementation of document.elementFromPoint that is working for iOS4 and |
| 46 // iOS5 and that also goes into frames and iframes. |
| 47 var elementFromPoint_ = function(x, y) { |
| 48 var elementFromPointIsUsingViewPortCoordinates = function(win) { |
| 49 if (win.pageYOffset > 0) { // Page scrolled down. |
| 50 return (win.document.elementFromPoint( |
| 51 0, win.pageYOffset + win.innerHeight - 1) === null); |
| 52 } |
| 53 if (win.pageXOffset > 0) { // Page scrolled to the right. |
| 54 return (win.document.elementFromPoint( |
| 55 win.pageXOffset + win.innerWidth - 1, 0) === null); |
| 56 } |
| 57 return false; // No scrolling, don't care. |
| 58 }; |
| 59 |
| 60 var newCoordinate = function(x, y) { |
| 61 var coordinates = { |
| 62 x: x, y: y, |
| 63 viewPortX: x - window.pageXOffset, viewPortY: y - window.pageYOffset, |
| 64 useViewPortCoordinates: false, |
| 65 window: window |
| 66 }; |
| 67 return coordinates; |
| 68 }; |
| 69 |
| 70 // Returns the coordinates of the upper left corner of |obj| in the |
| 71 // coordinates of the window that |obj| is in. |
| 72 var getPositionInWindow = function(obj) { |
| 73 var coord = { x: 0, y: 0 }; |
| 74 while (obj.offsetParent) { |
| 75 coord.x += obj.offsetLeft; |
| 76 coord.y += obj.offsetTop; |
| 77 obj = obj.offsetParent; |
| 78 } |
| 79 return coord; |
| 80 }; |
| 81 |
| 82 var elementsFromCoordinates = function(coordinates) { |
| 83 coordinates.useViewPortCoordinates = coordinates.useViewPortCoordinates || |
| 84 elementFromPointIsUsingViewPortCoordinates(coordinates.window); |
| 85 |
| 86 var currentElement = null; |
| 87 if (coordinates.useViewPortCoordinates) { |
| 88 currentElement = coordinates.window.document.elementFromPoint( |
| 89 coordinates.viewPortX, coordinates.viewPortY); |
| 90 } else { |
| 91 currentElement = coordinates.window.document.elementFromPoint( |
| 92 coordinates.x, coordinates.y); |
| 93 } |
| 94 // We have to check for tagName, because if a selection is made by the |
| 95 // UIWebView, the element we will get won't have one. |
| 96 if (!currentElement || !currentElement.tagName) { |
| 97 return null; |
| 98 } |
| 99 if (currentElement.tagName.toLowerCase() === 'iframe' || |
| 100 currentElement.tagName.toLowerCase() === 'frame') { |
| 101 // The following condition is true if the iframe is in a different |
| 102 // domain; no further information is accessible. |
| 103 if (typeof(currentElement.contentWindow.document) == 'undefined') { |
| 104 invokeOnHost_({ |
| 105 'command': 'window.error', |
| 106 'message': 'iframe contentWindow.document is not accessible.'}); |
| 107 return currentElement; |
| 108 } |
| 109 var framePosition = getPositionInWindow(currentElement); |
| 110 coordinates.viewPortX -= |
| 111 framePosition.x - coordinates.window.pageXOffset; |
| 112 coordinates.viewPortY -= |
| 113 framePosition.y - coordinates.window.pageYOffset; |
| 114 coordinates.window = currentElement.contentWindow; |
| 115 coordinates.x -= framePosition.x + coordinates.window.pageXOffset; |
| 116 coordinates.y -= framePosition.y + coordinates.window.pageYOffset; |
| 117 return elementsFromCoordinates(coordinates); |
| 118 } |
| 119 return currentElement; |
| 120 }; |
| 121 |
| 122 return elementsFromCoordinates(newCoordinate(x, y)); |
| 123 }; |
| 124 |
| 125 var spiralCoordinates = function(x, y) { |
| 126 var coordinates = []; |
| 127 |
| 128 var maxAngle = Math.PI * 2.0 * 3.0; |
| 129 var pointCount = 30; |
| 130 var angleStep = maxAngle / pointCount; |
| 131 var speed = touchMargin_ / maxAngle; |
| 132 |
| 133 for (var index = 0; index < pointCount; index++) { |
| 134 var angle = angleStep * index; |
| 135 var radius = angle * speed; |
| 136 |
| 137 coordinates.push({x: x + Math.round(Math.cos(angle) * radius), |
| 138 y: y + Math.round(Math.sin(angle) * radius)}); |
| 139 } |
| 140 |
| 141 return coordinates; |
| 142 }; |
| 143 |
| 144 // Returns the url of the image or link under the selected point. Returns an |
| 145 // empty string if no links or images are found. |
| 146 __gCrWeb['getElementFromPoint'] = function(x, y) { |
| 147 var hitCoordinates = spiralCoordinates(x, y); |
| 148 for (var index = 0; index < hitCoordinates.length; index++) { |
| 149 var coordinates = hitCoordinates[index]; |
| 150 |
| 151 var element = elementFromPoint_(coordinates.x, coordinates.y); |
| 152 if (!element || !element.tagName) { |
| 153 // Nothing under the hit point. Try the next hit point. |
| 154 continue; |
| 155 } |
| 156 |
| 157 if (getComputedWebkitTouchCallout_(element) === 'none') |
| 158 continue; |
| 159 // Also check element's ancestors. A bound on the level is used here to |
| 160 // avoid large overhead when no links or images are found. |
| 161 var level = 0; |
| 162 while (++level < 8 && element && element != document) { |
| 163 var tagName = element.tagName; |
| 164 if (!tagName) |
| 165 continue; |
| 166 tagName = tagName.toLowerCase(); |
| 167 |
| 168 if (tagName === 'input' || tagName === 'textarea' || |
| 169 tagName === 'select' || tagName === 'option') { |
| 170 // If the element is a known input element, stop the spiral search and |
| 171 // return empty results. |
| 172 return '{}'; |
| 173 } |
| 174 |
| 175 if (tagName === 'a' && element.href) { |
| 176 // Found a link. |
| 177 return __gCrWeb.common.JSONStringify( |
| 178 {href: element.href, |
| 179 referrerPolicy: getReferrerPolicy_(element)}); |
| 180 } |
| 181 |
| 182 if (tagName === 'img' && element.src) { |
| 183 // Found an image. |
| 184 var result = {src: element.src, |
| 185 referrerPolicy: getReferrerPolicy_()}; |
| 186 // Copy the title, if any. |
| 187 if (element.title) { |
| 188 result.title = element.title; |
| 189 } |
| 190 // Check if the image is also a link. |
| 191 var parent = element.parentNode; |
| 192 while (parent) { |
| 193 if (parent.tagName && |
| 194 parent.tagName.toLowerCase() === 'a' && |
| 195 parent.href) { |
| 196 // This regex identifies strings like void(0), |
| 197 // void(0) ;void(0);, ;;;; |
| 198 // which result in a NOP when executed as JavaScript. |
| 199 var regex = RegExp("^javascript:(?:(?:void\\(0\\)|;)\\s*)+$"); |
| 200 if (parent.href.match(regex)) { |
| 201 parent = parent.parentNode; |
| 202 continue; |
| 203 } |
| 204 result.href = parent.href; |
| 205 result.referrerPolicy = getReferrerPolicy_(parent); |
| 206 break; |
| 207 } |
| 208 parent = parent.parentNode; |
| 209 } |
| 210 return __gCrWeb.common.JSONStringify(result); |
| 211 } |
| 212 element = element.parentNode; |
| 213 } |
| 214 } |
| 215 return '{}'; |
| 216 }; |
| 217 |
| 218 // Returns true if the top window or any frames inside contain an input |
| 219 // field of type 'password'. |
| 220 __gCrWeb['hasPasswordField'] = function() { |
| 221 return hasPasswordField_(window); |
| 222 }; |
| 223 |
| 224 // Returns a string that is formatted according to the JSON syntax rules. |
| 225 // This is equivalent to the built-in JSON.stringify() function, but is |
| 226 // less likely to be overridden by the website itself. This public function |
| 227 // should not be used if spoofing it would create a security vulnerability. |
| 228 // The |__gCrWeb| object itself does not use it; it uses its private |
| 229 // counterpart instead. |
| 230 // Prevents websites from changing stringify's behavior by adding the |
| 231 // method toJSON() by temporarily removing it. |
| 232 __gCrWeb['stringify'] = function(value) { |
| 233 if (value === null) |
| 234 return 'null'; |
| 235 if (value === undefined) |
| 236 return undefined; |
| 237 if (typeof(value.toJSON) == 'function') { |
| 238 var originalToJSON = value.toJSON; |
| 239 value.toJSON = undefined; |
| 240 var stringifiedValue = __gCrWeb.common.JSONStringify(value); |
| 241 value.toJSON = originalToJSON; |
| 242 return stringifiedValue; |
| 243 } |
| 244 return __gCrWeb.common.JSONStringify(value); |
| 245 }; |
| 246 |
| 247 /* |
| 248 * Adds the listeners that are used to handle forms, enabling autofill and |
| 249 * the replacement method to dismiss the keyboard needed because of the |
| 250 * Autofill keyboard accessory. |
| 251 */ |
| 252 function addFormEventListeners_() { |
| 253 // Focus and input events for form elements are messaged to the main |
| 254 // application for broadcast to CRWWebControllerObservers. |
| 255 // This is done with a single event handler for each type being added to the |
| 256 // main document element which checks the source element of the event; this |
| 257 // is much easier to manage than adding handlers to individual elements. |
| 258 var formActivity = function(evt) { |
| 259 var srcElement = evt.srcElement; |
| 260 var fieldName = srcElement.name || ''; |
| 261 var value = srcElement.value || ''; |
| 262 |
| 263 var msg = { |
| 264 'command': 'form.activity', |
| 265 'formName': __gCrWeb.common.getFormIdentifier(evt.srcElement.form), |
| 266 'fieldName': fieldName, |
| 267 'type': evt.type, |
| 268 'value': value |
| 269 }; |
| 270 if (evt.keyCode) |
| 271 msg.keyCode = evt.keyCode; |
| 272 invokeOnHost_(msg); |
| 273 }; |
| 274 |
| 275 // Focus events performed on the 'capture' phase otherwise they are often |
| 276 // not received. |
| 277 document.addEventListener('focus', formActivity, true); |
| 278 document.addEventListener('blur', formActivity, true); |
| 279 document.addEventListener('change', formActivity, true); |
| 280 |
| 281 // Text input is watched at the bubbling phase as this seems adequate in |
| 282 // practice and it is less obtrusive to page scripts than capture phase. |
| 283 document.addEventListener('input', formActivity, false); |
| 284 document.addEventListener('keyup', formActivity, false); |
| 285 }; |
| 286 |
| 287 // Returns true if the supplied window or any frames inside contain an input |
| 288 // field of type 'password'. |
| 289 // @private |
| 290 var hasPasswordField_ = function(win) { |
| 291 var doc = win.document; |
| 292 |
| 293 // We may will not be allowed to read the 'document' property from a frame |
| 294 // that is in a different domain. |
| 295 if (!doc) { |
| 296 return false; |
| 297 } |
| 298 |
| 299 if (doc.querySelector('input[type=password]')) { |
| 300 return true; |
| 301 } |
| 302 |
| 303 var frames = win.frames; |
| 304 for (var i = 0; i < frames.length; i++) { |
| 305 if (hasPasswordField_(frames[i])) { |
| 306 return true; |
| 307 } |
| 308 } |
| 309 |
| 310 return false; |
| 311 }; |
| 312 |
| 313 function invokeOnHost_(command) { |
| 314 __gCrWeb.message.invokeOnHost(command); |
| 315 }; |
| 316 |
| 317 function invokeOnHostImmediate_(command) { |
| 318 __gCrWeb.message.invokeOnHostImmediate(command); |
| 319 }; |
| 320 |
| 321 /** |
| 322 * Gets the referrer policy to use for navigations away from the current page. |
| 323 * If a link element is passed, and it includes a rel=noreferrer tag, that |
| 324 * will override the page setting. |
| 325 * @param {HTMLElement} linkElement The link triggering the navigation. |
| 326 * @return {String} The policy string. |
| 327 * @private |
| 328 */ |
| 329 var getReferrerPolicy_ = function(linkElement) { |
| 330 if (linkElement) { |
| 331 var rel = linkElement.getAttribute('rel'); |
| 332 if (rel && rel.toLowerCase() == 'noreferrer') { |
| 333 return 'never'; |
| 334 } |
| 335 } |
| 336 |
| 337 var metaTags = document.getElementsByTagName('meta'); |
| 338 for (var i = 0; i < metaTags.length; ++i) { |
| 339 if (metaTags[i].name.toLowerCase() == 'referrer') { |
| 340 return metaTags[i].content.toLowerCase(); |
| 341 } |
| 342 } |
| 343 return 'default'; |
| 344 }; |
| 345 |
| 346 // Provides a way for other injected javascript to access the page's referrer |
| 347 // policy. |
| 348 __gCrWeb['getPageReferrerPolicy'] = function() { |
| 349 return getReferrerPolicy_(); |
| 350 }; |
| 351 |
| 352 // Various aspects of global DOM behavior are overridden here. |
| 353 |
| 354 // A popstate event needs to be fired anytime the active history entry |
| 355 // changes. Either via back, forward, go navigation or by loading the URL, |
| 356 // clicking on a link, etc. |
| 357 __gCrWeb['dispatchPopstateEvent'] = function(stateObject) { |
| 358 var popstateEvent = window.document.createEvent('HTMLEvents'); |
| 359 popstateEvent.initEvent('popstate', true, false); |
| 360 if (stateObject) |
| 361 popstateEvent.state = JSON.parse(stateObject); |
| 362 |
| 363 // setTimeout() is used in order to return immediately. Otherwise the |
| 364 // dispatchEvent call waits for all event handlers to return, which could |
| 365 // cause a ReentryGuard failure. |
| 366 window.setTimeout(function() { |
| 367 window.dispatchEvent(popstateEvent); |
| 368 }, 0); |
| 369 }; |
| 370 |
| 371 // Keep the original replaceState() method. It's needed to update UIWebView's |
| 372 // URL and window.history.state property during history navigations that don't |
| 373 // cause a page load. |
| 374 var originalWindowHistoryReplaceState = window.history.replaceState; |
| 375 __gCrWeb['replaceWebViewURL'] = function(url, stateObject) { |
| 376 originalWindowHistoryReplaceState.call(history, stateObject, null, url); |
| 377 }; |
| 378 |
| 379 // Intercept window.history methods to call back/forward natively. |
| 380 window.history.back = function() { |
| 381 invokeOnHost_({'command': 'window.history.back'}); |
| 382 }; |
| 383 window.history.forward = function() { |
| 384 invokeOnHost_({'command': 'window.history.forward'}); |
| 385 }; |
| 386 window.history.go = function(delta) { |
| 387 invokeOnHost_({'command': 'window.history.go', 'value': delta}); |
| 388 }; |
| 389 window.history.pushState = function(stateObject, pageTitle, pageUrl) { |
| 390 __gCrWeb.core_dynamic.historyWillChangeState(); |
| 391 // Calling stringify() on undefined causes a JSON parse error. |
| 392 var serializedState = |
| 393 typeof(stateObject) == 'undefined' ? '' : |
| 394 __gCrWeb.common.JSONStringify(stateObject); |
| 395 pageUrl = pageUrl || window.location.href; |
| 396 originalWindowHistoryReplaceState.call(history, stateObject, null, pageUrl); |
| 397 invokeOnHost_({'command': 'window.history.didPushState', |
| 398 'stateObject': serializedState, |
| 399 'baseUrl': document.baseURI, |
| 400 'pageUrl': pageUrl.toString()}); |
| 401 }; |
| 402 window.history.replaceState = function(stateObject, pageTitle, pageUrl) { |
| 403 __gCrWeb.core_dynamic.historyWillChangeState(); |
| 404 // Calling stringify() on undefined causes a JSON parse error. |
| 405 var serializedState = |
| 406 typeof(stateObject) == 'undefined' ? '' : |
| 407 __gCrWeb.common.JSONStringify(stateObject); |
| 408 pageUrl = pageUrl || window.location.href; |
| 409 originalWindowHistoryReplaceState.call(history, stateObject, null, pageUrl); |
| 410 invokeOnHost_({'command': 'window.history.didReplaceState', |
| 411 'stateObject': serializedState, |
| 412 'baseUrl': document.baseURI, |
| 413 'pageUrl': pageUrl.toString()}); |
| 414 }; |
| 415 |
| 416 __gCrWeb['getFullyQualifiedURL'] = function(originalURL) { |
| 417 // A dummy anchor (never added to the document) is used to obtain the |
| 418 // fully-qualified URL of |originalURL|. |
| 419 var anchor = document.createElement('a'); |
| 420 anchor.href = originalURL; |
| 421 return anchor.href; |
| 422 }; |
| 423 |
| 424 // Intercept window.close calls. |
| 425 window.close = function() { |
| 426 invokeOnHost_({'command': 'window.close.self'}); |
| 427 }; |
| 428 |
| 429 window.addEventListener('hashchange', function(evt) { |
| 430 invokeOnHost_({'command': 'window.hashchange'}); |
| 431 }); |
| 432 |
| 433 __gCrWeb.core_dynamic.addEventListeners(); |
| 434 |
| 435 // Returns if a frame with |name| is found in |currentWindow|. |
| 436 // Note frame.name is undefined for cross domain frames. |
| 437 var hasFrame_ = function(currentWindow, name) { |
| 438 if (currentWindow.name === name) |
| 439 return true; |
| 440 |
| 441 var frames = currentWindow.frames; |
| 442 for (var index = 0; index < frames.length; ++index) { |
| 443 var frame = frames[index]; |
| 444 if (frame === undefined) |
| 445 continue; |
| 446 if (hasFrame_(frame, name)) |
| 447 return true; |
| 448 } |
| 449 return false; |
| 450 }; |
| 451 |
| 452 // Checks if |node| is an anchor to be opened in the current tab. |
| 453 var isInternaLink_ = function(node) { |
| 454 if (!node instanceof HTMLAnchorElement) |
| 455 return false; |
| 456 |
| 457 // Anchor with href='javascript://.....' will be opened in the current tab |
| 458 // for simplicity. |
| 459 if (node.href.indexOf('javascript:') == 0) |
| 460 return true; |
| 461 |
| 462 // UIWebView will take care of the following cases. |
| 463 // |
| 464 // - If the given browsing context name is the empty string or '_self', then |
| 465 // the chosen browsing context must be the current one. |
| 466 // |
| 467 // - If the given browsing context name is '_parent', then the chosen |
| 468 // browsing context must be the parent browsing context of the current |
| 469 // one, unless there is no one, in which case the chosen browsing context |
| 470 // must be the current browsing context. |
| 471 // |
| 472 // - If the given browsing context name is '_top', then the chosen browsing |
| 473 // context must be the top-level browsing context of the current one, if |
| 474 // there is one, or else the current browsing context. |
| 475 // |
| 476 // Here an undefined target is considered in the same way as an empty |
| 477 // target. |
| 478 if (node.target === undefined || node.target === '' || |
| 479 node.target === '_self' || node.target === '_parent' || |
| 480 node.target === '_top') { |
| 481 return true; |
| 482 } |
| 483 |
| 484 // A new browsing context is being requested for an '_blank' target. |
| 485 if (node.target === '_blank') |
| 486 return false; |
| 487 |
| 488 // Otherwise UIWebView will take care of the case where there exists a |
| 489 // browsing context whose name is the same as the given browsing context |
| 490 // name. If there is no such a browsing context, a new browsing context is |
| 491 // being requested. |
| 492 return hasFrame_(window, node.target); |
| 493 }; |
| 494 |
| 495 var getTargetLink_ = function(target) { |
| 496 var node = target; |
| 497 // Find the closest ancester that is a link. |
| 498 while (node) { |
| 499 if (node instanceof HTMLAnchorElement) |
| 500 break; |
| 501 node = node.parentNode; |
| 502 } |
| 503 return node; |
| 504 }; |
| 505 |
| 506 var setExternalRequest_ = function(href, target) { |
| 507 if (typeof(target) == 'undefined' || target == '_blank' || target == '') { |
| 508 target = '' + Date.now() + '-' + Math.random(); |
| 509 } |
| 510 if (typeof(href) == 'undefined') { |
| 511 // W3C recommended behavior. |
| 512 href = 'about:blank'; |
| 513 } |
| 514 // ExternalRequest messages need to be handled before the expected |
| 515 // shouldStartLoadWithRequest, as such we cannot wait for the regular |
| 516 // message queue invoke which delays to avoid illegal recursion into |
| 517 // UIWebView. This immediate class of messages is handled ASAP by |
| 518 // CRWWebController. |
| 519 invokeOnHostImmediate_({'command': 'externalRequest', |
| 520 'href': href, |
| 521 'target': target, |
| 522 'referrerPolicy': getReferrerPolicy_()}); |
| 523 }; |
| 524 |
| 525 var resetExternalRequest_ = function() { |
| 526 invokeOnHost_({'command': 'resetExternalRequest'}); |
| 527 }; |
| 528 |
| 529 var clickBubbleListener_ = function(evt) { |
| 530 if (evt['defaultPrevented']) { |
| 531 resetExternalRequest_(); |
| 532 } |
| 533 // Remove the listener. |
| 534 evt.currentTarget.removeEventListener( |
| 535 'click', clickBubbleListener_, false); |
| 536 }; |
| 537 |
| 538 var getComputedWebkitTouchCallout_ = function(element) { |
| 539 return window.getComputedStyle(element, null)['webkitTouchCallout']; |
| 540 }; |
| 541 |
| 542 /** |
| 543 * This method applies the various document-level overrides. Sometimes the |
| 544 * document object gets reset in the early stages of the page lifecycle, so |
| 545 * this is exposed as a method for the application to invoke later. That way |
| 546 * the window-level overrides can be applied as soon as possible. |
| 547 */ |
| 548 __gCrWeb.core.documentInject = function() { |
| 549 // Perform web view specific operations requiring document.body presence. |
| 550 // If necessary returns and waits for document to be present. |
| 551 if (!__gCrWeb.core_dynamic.documentInject()) |
| 552 return; |
| 553 |
| 554 document.addEventListener('click', function(evt) { |
| 555 var node = getTargetLink_(evt.target); |
| 556 |
| 557 if (!node) |
| 558 return; |
| 559 |
| 560 if (isInternaLink_(node)) { |
| 561 return; |
| 562 } |
| 563 setExternalRequest_(node.href, node.target); |
| 564 // Add listener to the target and its immediate ancesters. These event |
| 565 // listeners will be removed if they get called. The listeners for some |
| 566 // elements might never be removed, but if multiple identical event |
| 567 // listeners are registered on the same event target with the same |
| 568 // parameters the duplicate instances are discarded. |
| 569 for (var level = 0; level < 5; ++level) { |
| 570 if (node && node != document) { |
| 571 node.addEventListener('click', clickBubbleListener_, false); |
| 572 node = node.parentNode; |
| 573 } else { |
| 574 break; |
| 575 } |
| 576 } |
| 577 }, true); |
| 578 |
| 579 // Intercept clicks on anchors (links) during bubbling phase so that the |
| 580 // browser can handle target type appropriately. |
| 581 document.addEventListener('click', function(evt) { |
| 582 var node = getTargetLink_(evt.target); |
| 583 |
| 584 if (!node) |
| 585 return; |
| 586 |
| 587 if (isInternaLink_(node)) { |
| 588 if (evt['defaultPrevented']) |
| 589 return; |
| 590 // Internal link. The web view will handle navigation, but register |
| 591 // the anchor for UIWebView to start the progress indicator ASAP and |
| 592 // notify web controller as soon as possible of impending navigation. |
| 593 if (__gCrWeb.core_dynamic.handleInternalClickEvent) { |
| 594 __gCrWeb.core_dynamic.handleInternalClickEvent(node); |
| 595 } |
| 596 return; |
| 597 } else { |
| 598 // Resets the external request if it has been canceled, otherwise |
| 599 // updates the href in case it has been changed. |
| 600 if (evt['defaultPrevented']) |
| 601 resetExternalRequest_(); |
| 602 else |
| 603 setExternalRequest_(node.href, node.target); |
| 604 } |
| 605 }, false); |
| 606 |
| 607 // Capture form submit actions. |
| 608 document.addEventListener('submit', function(evt) { |
| 609 if (evt['defaultPrevented']) |
| 610 return; |
| 611 |
| 612 var form = evt.target; |
| 613 var targetsFrame = form.target && hasFrame_(window, form.target); |
| 614 // TODO(stuartmorgan): Handle external targets. crbug.com/233543 |
| 615 |
| 616 var action = form.getAttribute('action'); |
| 617 // Default action is to re-submit to same page. |
| 618 if (!action) |
| 619 action = document.location.href; |
| 620 invokeOnHost_({ |
| 621 'command': 'document.submit', |
| 622 'formName': __gCrWeb.common.getFormIdentifier(evt.srcElement), |
| 623 'href': __gCrWeb['getFullyQualifiedURL'](action), |
| 624 'targetsFrame': targetsFrame |
| 625 }); |
| 626 }, false); |
| 627 |
| 628 addFormEventListeners_(); |
| 629 |
| 630 // Handle or wait for and handle document load completion, if applicable. |
| 631 if (__gCrWeb.core_dynamic.handleDocumentLoaded) |
| 632 __gCrWeb.core_dynamic.handleDocumentLoaded(); |
| 633 |
| 634 return true; |
| 635 }; |
| 636 |
| 637 __gCrWeb.core.documentInject(); |
| 638 |
| 639 // Form prototype loaded with event to supply Autocomplete API |
| 640 // functionality. |
| 641 HTMLFormElement.prototype.requestAutocomplete = function() { |
| 642 invokeOnHost_( |
| 643 {'command': 'form.requestAutocomplete', |
| 644 'formName': __gCrWeb.common.getFormIdentifier(this)}); |
| 645 }; |
| 646 } // End of anonymous object |
OLD | NEW |