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 |