| 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 18 matching lines...) Expand all Loading... |
| 29 if (__gCrWeb.common.updatePluginPlaceholders()) | 29 if (__gCrWeb.common.updatePluginPlaceholders()) |
| 30 __gCrWeb.message.invokeOnHost({'command': 'addPluginPlaceholders'}); | 30 __gCrWeb.message.invokeOnHost({'command': 'addPluginPlaceholders'}); |
| 31 } | 31 } |
| 32 | 32 |
| 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 __gCrWeb.message.invokeOnHost( |
| 40 'message': event.message.toString()}); | 40 {'command': 'window.error', 'message': event.message.toString()}); |
| 41 }); | 41 }); |
| 42 | 42 |
| 43 | 43 |
| 44 // 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 |
| 45 // field of type 'password'. | 45 // field of type 'password'. |
| 46 __gCrWeb['hasPasswordField'] = function() { | 46 __gCrWeb['hasPasswordField'] = function() { |
| 47 return hasPasswordField_(window); | 47 return hasPasswordField_(window); |
| 48 }; | 48 }; |
| 49 | 49 |
| 50 // Returns a string that is formatted according to the JSON syntax rules. | |
| 51 // This is equivalent to the built-in JSON.stringify() function, but is | |
| 52 // less likely to be overridden by the website itself. This public function | |
| 53 // should not be used if spoofing it would create a security vulnerability. | |
| 54 // The |__gCrWeb| object itself does not use it; it uses its private | |
| 55 // counterpart instead. | |
| 56 // Prevents websites from changing stringify's behavior by adding the | |
| 57 // method toJSON() by temporarily removing it. | |
| 58 __gCrWeb['stringify'] = function(value) { | |
| 59 if (value === null) | |
| 60 return 'null'; | |
| 61 if (value === undefined) | |
| 62 return undefined; | |
| 63 if (typeof(value.toJSON) == 'function') { | |
| 64 var originalToJSON = value.toJSON; | |
| 65 value.toJSON = undefined; | |
| 66 var stringifiedValue = __gCrWeb.common.JSONStringify(value); | |
| 67 value.toJSON = originalToJSON; | |
| 68 return stringifiedValue; | |
| 69 } | |
| 70 return __gCrWeb.common.JSONStringify(value); | |
| 71 }; | |
| 72 | |
| 73 /* | |
| 74 * Adds the listeners that are used to handle forms, enabling autofill and | |
| 75 * the replacement method to dismiss the keyboard needed because of the | |
| 76 * Autofill keyboard accessory. | |
| 77 */ | |
| 78 function addFormEventListeners_() { | |
| 79 // Focus and input events for form elements are messaged to the main | |
| 80 // application for broadcast to CRWWebControllerObservers. | |
| 81 // This is done with a single event handler for each type being added to the | |
| 82 // main document element which checks the source element of the event; this | |
| 83 // is much easier to manage than adding handlers to individual elements. | |
| 84 var formActivity = function(evt) { | |
| 85 var srcElement = evt.srcElement; | |
| 86 var fieldName = srcElement.name || ''; | |
| 87 var value = srcElement.value || ''; | |
| 88 | |
| 89 var msg = { | |
| 90 'command': 'form.activity', | |
| 91 'formName': __gCrWeb.common.getFormIdentifier(evt.srcElement.form), | |
| 92 'fieldName': fieldName, | |
| 93 'type': evt.type, | |
| 94 'value': value | |
| 95 }; | |
| 96 invokeOnHost_(msg); | |
| 97 }; | |
| 98 | |
| 99 // Focus events performed on the 'capture' phase otherwise they are often | |
| 100 // not received. | |
| 101 document.addEventListener('focus', formActivity, true); | |
| 102 document.addEventListener('blur', formActivity, true); | |
| 103 document.addEventListener('change', formActivity, true); | |
| 104 | |
| 105 // Text input is watched at the bubbling phase as this seems adequate in | |
| 106 // practice and it is less obtrusive to page scripts than capture phase. | |
| 107 document.addEventListener('input', formActivity, false); | |
| 108 document.addEventListener('keyup', formActivity, false); | |
| 109 }; | |
| 110 | 50 |
| 111 // Returns true if the supplied window or any frames inside contain an input | 51 // Returns true if the supplied window or any frames inside contain an input |
| 112 // field of type 'password'. | 52 // field of type 'password'. |
| 113 // @private | 53 // @private |
| 114 var hasPasswordField_ = function(win) { | 54 var hasPasswordField_ = function(win) { |
| 115 var doc = win.document; | 55 var doc = win.document; |
| 116 | 56 |
| 117 // We may will not be allowed to read the 'document' property from a frame | 57 // We may will not be allowed to read the 'document' property from a frame |
| 118 // that is in a different domain. | 58 // that is in a different domain. |
| 119 if (!doc) { | 59 if (!doc) { |
| 120 return false; | 60 return false; |
| 121 } | 61 } |
| 122 | 62 |
| 123 if (doc.querySelector('input[type=password]')) { | 63 if (doc.querySelector('input[type=password]')) { |
| 124 return true; | 64 return true; |
| 125 } | 65 } |
| 126 | 66 |
| 127 var frames = win.frames; | 67 var frames = win.frames; |
| 128 for (var i = 0; i < frames.length; i++) { | 68 for (var i = 0; i < frames.length; i++) { |
| 129 if (hasPasswordField_(frames[i])) { | 69 if (hasPasswordField_(frames[i])) { |
| 130 return true; | 70 return true; |
| 131 } | 71 } |
| 132 } | 72 } |
| 133 | 73 |
| 134 return false; | 74 return false; |
| 135 }; | 75 }; |
| 136 | 76 |
| 137 function invokeOnHost_(command) { | |
| 138 __gCrWeb.message.invokeOnHost(command); | |
| 139 }; | |
| 140 | |
| 141 // Various aspects of global DOM behavior are overridden here. | |
| 142 | |
| 143 // A popstate event needs to be fired anytime the active history entry | |
| 144 // changes without an associated document change. Either via back, forward, go | |
| 145 // navigation or by loading the URL, clicking on a link, etc. | |
| 146 __gCrWeb['dispatchPopstateEvent'] = function(stateObject) { | |
| 147 var popstateEvent = window.document.createEvent('HTMLEvents'); | |
| 148 popstateEvent.initEvent('popstate', true, false); | |
| 149 if (stateObject) | |
| 150 popstateEvent.state = JSON.parse(stateObject); | |
| 151 | |
| 152 // setTimeout() is used in order to return immediately. Otherwise the | |
| 153 // dispatchEvent call waits for all event handlers to return, which could | |
| 154 // cause a ReentryGuard failure. | |
| 155 window.setTimeout(function() { | |
| 156 window.dispatchEvent(popstateEvent); | |
| 157 }, 0); | |
| 158 }; | |
| 159 | |
| 160 // A hashchange event needs to be fired after a same-document history | |
| 161 // navigation between two URLs that are equivalent except for their fragments. | |
| 162 __gCrWeb['dispatchHashchangeEvent'] = function(oldURL, newURL) { | |
| 163 var hashchangeEvent = window.document.createEvent('HTMLEvents'); | |
| 164 hashchangeEvent.initEvent('hashchange', true, false); | |
| 165 if (oldURL) | |
| 166 hashchangeEvent.oldURL = oldURL; | |
| 167 if (newURL) | |
| 168 hashchangeEvent.newURL = newURL | |
| 169 | |
| 170 // setTimeout() is used in order to return immediately. Otherwise the | |
| 171 // dispatchEvent call waits for all event handlers to return, which could | |
| 172 // cause a ReentryGuard failure. | |
| 173 window.setTimeout(function() { | |
| 174 window.dispatchEvent(hashchangeEvent); | |
| 175 }, 0); | |
| 176 }; | |
| 177 | |
| 178 // Keep the original pushState() and replaceState() methods. It's needed to | |
| 179 // update the web view's URL and window.history.state property during history | |
| 180 // navigations that don't cause a page load. | |
| 181 var originalWindowHistoryPushState = window.history.pushState; | |
| 182 var originalWindowHistoryReplaceState = window.history.replaceState; | |
| 183 __gCrWeb['replaceWebViewURL'] = function(url, stateObject) { | |
| 184 originalWindowHistoryReplaceState.call(history, stateObject, '', url); | |
| 185 }; | |
| 186 | |
| 187 // Intercept window.history methods to call back/forward natively. | |
| 188 window.history.back = function() { | |
| 189 invokeOnHost_({'command': 'window.history.back'}); | |
| 190 }; | |
| 191 window.history.forward = function() { | |
| 192 invokeOnHost_({'command': 'window.history.forward'}); | |
| 193 }; | |
| 194 window.history.go = function(delta) { | |
| 195 invokeOnHost_({'command': 'window.history.go', 'value': delta | 0}); | |
| 196 }; | |
| 197 window.history.pushState = function(stateObject, pageTitle, pageUrl) { | |
| 198 __gCrWeb.message.invokeOnHost( | |
| 199 {'command': 'window.history.willChangeState'}); | |
| 200 // Calling stringify() on undefined causes a JSON parse error. | |
| 201 var serializedState = | |
| 202 typeof(stateObject) == 'undefined' ? '' : | |
| 203 __gCrWeb.common.JSONStringify(stateObject); | |
| 204 pageUrl = pageUrl || window.location.href; | |
| 205 originalWindowHistoryPushState.call(history, stateObject, | |
| 206 pageTitle, pageUrl); | |
| 207 invokeOnHost_({'command': 'window.history.didPushState', | |
| 208 'stateObject': serializedState, | |
| 209 'baseUrl': document.baseURI, | |
| 210 'pageUrl': pageUrl.toString()}); | |
| 211 }; | |
| 212 window.history.replaceState = function(stateObject, pageTitle, pageUrl) { | |
| 213 __gCrWeb.message.invokeOnHost( | |
| 214 {'command': 'window.history.willChangeState'}); | |
| 215 | |
| 216 // Calling stringify() on undefined causes a JSON parse error. | |
| 217 var serializedState = | |
| 218 typeof(stateObject) == 'undefined' ? '' : | |
| 219 __gCrWeb.common.JSONStringify(stateObject); | |
| 220 pageUrl = pageUrl || window.location.href; | |
| 221 originalWindowHistoryReplaceState.call(history, stateObject, | |
| 222 pageTitle, pageUrl); | |
| 223 invokeOnHost_({'command': 'window.history.didReplaceState', | |
| 224 'stateObject': serializedState, | |
| 225 'baseUrl': document.baseURI, | |
| 226 'pageUrl': pageUrl.toString()}); | |
| 227 }; | |
| 228 | |
| 229 __gCrWeb['getFullyQualifiedURL'] = function(originalURL) { | |
| 230 // A dummy anchor (never added to the document) is used to obtain the | |
| 231 // fully-qualified URL of |originalURL|. | |
| 232 var anchor = document.createElement('a'); | |
| 233 anchor.href = originalURL; | |
| 234 return anchor.href; | |
| 235 }; | |
| 236 | |
| 237 __gCrWeb['sendFaviconsToHost'] = function() { | 77 __gCrWeb['sendFaviconsToHost'] = function() { |
| 238 __gCrWeb.message.invokeOnHost({'command': 'document.favicons', | 78 __gCrWeb.message.invokeOnHost({'command': 'document.favicons', |
| 239 'favicons': __gCrWeb.common.getFavicons()}); | 79 'favicons': __gCrWeb.common.getFavicons()}); |
| 240 } | 80 } |
| 241 | 81 |
| 242 // Tracks whether user is in the middle of scrolling/dragging. If user is | |
| 243 // scrolling, ignore window.scrollTo() until user stops scrolling. | |
| 244 var webViewScrollViewIsDragging_ = false; | |
| 245 __gCrWeb['setWebViewScrollViewIsDragging'] = function(state) { | |
| 246 webViewScrollViewIsDragging_ = state; | |
| 247 }; | |
| 248 var originalWindowScrollTo = window.scrollTo; | |
| 249 window.scrollTo = function(x, y) { | |
| 250 if (webViewScrollViewIsDragging_) | |
| 251 return; | |
| 252 originalWindowScrollTo(x, y); | |
| 253 }; | |
| 254 | |
| 255 window.addEventListener('hashchange', function(evt) { | |
| 256 invokeOnHost_({'command': 'window.hashchange'}); | |
| 257 }); | |
| 258 | |
| 259 // Flush the message queue. | 82 // Flush the message queue. |
| 260 if (__gCrWeb.message) { | 83 if (__gCrWeb.message) { |
| 261 __gCrWeb.message.invokeQueues(); | 84 __gCrWeb.message.invokeQueues(); |
| 262 } | 85 } |
| 263 | 86 |
| 264 // Capture form submit actions. | |
| 265 document.addEventListener('submit', function(evt) { | |
| 266 var action; | |
| 267 if (evt['defaultPrevented']) | |
| 268 return; | |
| 269 action = evt.target.getAttribute('action'); | |
| 270 // Default action is to re-submit to same page. | |
| 271 if (!action) | |
| 272 action = document.location.href; | |
| 273 invokeOnHost_({ | |
| 274 'command': 'document.submit', | |
| 275 'formName': __gCrWeb.common.getFormIdentifier(evt.srcElement), | |
| 276 'href': __gCrWeb['getFullyQualifiedURL'](action) | |
| 277 }); | |
| 278 }, false); | |
| 279 | |
| 280 addFormEventListeners_(); | |
| 281 | |
| 282 }()); // End of anonymous object | 87 }()); // End of anonymous object |
| OLD | NEW |