OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 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 module implements Webview (<webview>) as a custom element that wraps a | 5 // This module implements Webview (<webview>) as a custom element that wraps a |
6 // BrowserPlugin object element. The object element is hidden within | 6 // BrowserPlugin object element. The object element is hidden within |
7 // the shadow DOM of the Webview element. | 7 // the shadow DOM of the Webview element. |
8 | 8 |
9 var DocumentNatives = requireNative('document_natives'); | 9 var DocumentNatives = requireNative('document_natives'); |
10 var EventBindings = require('event_bindings'); | |
11 var GuestViewInternal = | 10 var GuestViewInternal = |
12 require('binding').Binding.create('guestViewInternal').generate(); | 11 require('binding').Binding.create('guestViewInternal').generate(); |
13 var IdGenerator = requireNative('id_generator'); | 12 var IdGenerator = requireNative('id_generator'); |
14 var MessagingNatives = requireNative('messaging_natives'); | |
15 var WebRequestEvent = require('webRequestInternal').WebRequestEvent; | |
16 var WebRequestSchema = | |
17 requireNative('schema_registry').GetSchema('webRequest'); | |
18 var DeclarativeWebRequestSchema = | |
19 requireNative('schema_registry').GetSchema('declarativeWebRequest'); | |
20 var WebView = require('webview').WebView; | 13 var WebView = require('webview').WebView; |
| 14 var WebViewEvents = require('webViewEvents').WebViewEvents; |
21 | 15 |
22 var WEB_VIEW_ATTRIBUTE_MAXHEIGHT = 'maxheight'; | 16 var WEB_VIEW_ATTRIBUTE_MAXHEIGHT = 'maxheight'; |
23 var WEB_VIEW_ATTRIBUTE_MAXWIDTH = 'maxwidth'; | 17 var WEB_VIEW_ATTRIBUTE_MAXWIDTH = 'maxwidth'; |
24 var WEB_VIEW_ATTRIBUTE_MINHEIGHT = 'minheight'; | 18 var WEB_VIEW_ATTRIBUTE_MINHEIGHT = 'minheight'; |
25 var WEB_VIEW_ATTRIBUTE_MINWIDTH = 'minwidth'; | 19 var WEB_VIEW_ATTRIBUTE_MINWIDTH = 'minwidth'; |
26 var WEB_VIEW_ATTRIBUTE_PARTITION = 'partition'; | 20 var WEB_VIEW_ATTRIBUTE_PARTITION = 'partition'; |
27 | 21 |
28 var ERROR_MSG_ALREADY_NAVIGATED = | 22 var ERROR_MSG_ALREADY_NAVIGATED = |
29 'The object has already navigated, so its partition cannot be changed.'; | 23 'The object has already navigated, so its partition cannot be changed.'; |
30 var ERROR_MSG_INVALID_PARTITION_ATTRIBUTE = 'Invalid partition attribute.'; | 24 var ERROR_MSG_INVALID_PARTITION_ATTRIBUTE = 'Invalid partition attribute.'; |
31 | 25 |
32 /** @type {Array.<string>} */ | 26 /** @type {Array.<string>} */ |
33 var WEB_VIEW_ATTRIBUTES = [ | 27 var WEB_VIEW_ATTRIBUTES = [ |
34 'allowtransparency', | 28 'allowtransparency', |
35 'autosize', | 29 'autosize', |
36 WEB_VIEW_ATTRIBUTE_MINHEIGHT, | 30 WEB_VIEW_ATTRIBUTE_MINHEIGHT, |
37 WEB_VIEW_ATTRIBUTE_MINWIDTH, | 31 WEB_VIEW_ATTRIBUTE_MINWIDTH, |
38 WEB_VIEW_ATTRIBUTE_MAXHEIGHT, | 32 WEB_VIEW_ATTRIBUTE_MAXHEIGHT, |
39 WEB_VIEW_ATTRIBUTE_MAXWIDTH | 33 WEB_VIEW_ATTRIBUTE_MAXWIDTH |
40 ]; | 34 ]; |
41 | 35 |
42 /** @class representing state of storage partition. */ | 36 /** @class representing state of storage partition. */ |
43 function Partition() { | 37 function Partition() { |
44 this.validPartitionId = true; | 38 this.validPartitionId = true; |
45 this.persist_storage_ = false; | 39 this.persistStorage = false; |
46 this.storage_partition_id = ''; | 40 this.storagePartitionId = ''; |
47 }; | 41 }; |
48 | 42 |
49 Partition.prototype.toAttribute = function() { | 43 Partition.prototype.toAttribute = function() { |
50 if (!this.validPartitionId) { | 44 if (!this.validPartitionId) { |
51 return ''; | 45 return ''; |
52 } | 46 } |
53 return (this.persist_storage_ ? 'persist:' : '') + this.storage_partition_id; | 47 return (this.persistStorage ? 'persist:' : '') + this.storagePartitionId; |
54 }; | 48 }; |
55 | 49 |
56 Partition.prototype.fromAttribute = function(value, hasNavigated) { | 50 Partition.prototype.fromAttribute = function(value, hasNavigated) { |
57 var result = {}; | 51 var result = {}; |
58 if (hasNavigated) { | 52 if (hasNavigated) { |
59 result.error = ERROR_MSG_ALREADY_NAVIGATED; | 53 result.error = ERROR_MSG_ALREADY_NAVIGATED; |
60 return result; | 54 return result; |
61 } | 55 } |
62 if (!value) { | 56 if (!value) { |
63 value = ''; | 57 value = ''; |
64 } | 58 } |
65 | 59 |
66 var LEN = 'persist:'.length; | 60 var LEN = 'persist:'.length; |
67 if (value.substr(0, LEN) == 'persist:') { | 61 if (value.substr(0, LEN) == 'persist:') { |
68 value = value.substr(LEN); | 62 value = value.substr(LEN); |
69 if (!value) { | 63 if (!value) { |
70 this.validPartitionId = false; | 64 this.validPartitionId = false; |
71 result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE; | 65 result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE; |
72 return result; | 66 return result; |
73 } | 67 } |
74 this.persist_storage_ = true; | 68 this.persistStorage = true; |
75 } else { | 69 } else { |
76 this.persist_storage_ = false; | 70 this.persistStorage = false; |
77 } | 71 } |
78 | 72 |
79 this.storage_partition_id = value; | 73 this.storagePartitionId = value; |
80 return result; | 74 return result; |
81 }; | 75 }; |
82 | 76 |
83 var CreateEvent = function(name) { | |
84 var eventOpts = {supportsListeners: true, supportsFilters: true}; | |
85 return new EventBindings.Event(name, undefined, eventOpts); | |
86 }; | |
87 | |
88 // WEB_VIEW_EVENTS is a map of stable <webview> DOM event names to their | |
89 // associated extension event descriptor objects. | |
90 // An event listener will be attached to the extension event |evt| specified in | |
91 // the descriptor. | |
92 // |fields| specifies the public-facing fields in the DOM event that are | |
93 // accessible to <webview> developers. | |
94 // |customHandler| allows a handler function to be called each time an extension | |
95 // event is caught by its event listener. The DOM event should be dispatched | |
96 // within this handler function. With no handler function, the DOM event | |
97 // will be dispatched by default each time the extension event is caught. | |
98 // |cancelable| (default: false) specifies whether the event's default | |
99 // behavior can be canceled. If the default action associated with the event | |
100 // is prevented, then its dispatch function will return false in its event | |
101 // handler. The event must have a custom handler for this to be meaningful. | |
102 | |
103 var FrameNameChangedEvent = CreateEvent('webview.onFrameNameChanged'); | |
104 | |
105 var WEB_VIEW_EVENTS = { | |
106 'close': { | |
107 evt: CreateEvent('webview.onClose'), | |
108 fields: [] | |
109 }, | |
110 'consolemessage': { | |
111 evt: CreateEvent('webview.onConsoleMessage'), | |
112 fields: ['level', 'message', 'line', 'sourceId'] | |
113 }, | |
114 'contentload': { | |
115 evt: CreateEvent('webview.onContentLoad'), | |
116 fields: [] | |
117 }, | |
118 'contextmenu': { | |
119 evt: CreateEvent('webview.contextmenu'), | |
120 cancelable: true, | |
121 customHandler: function(webViewInternal, event, webViewEvent) { | |
122 webViewInternal.maybeHandleContextMenu(event, webViewEvent); | |
123 }, | |
124 fields: ['items'] | |
125 }, | |
126 'dialog': { | |
127 cancelable: true, | |
128 customHandler: function(webViewInternal, event, webViewEvent) { | |
129 webViewInternal.handleDialogEvent(event, webViewEvent); | |
130 }, | |
131 evt: CreateEvent('webview.onDialog'), | |
132 fields: ['defaultPromptText', 'messageText', 'messageType', 'url'] | |
133 }, | |
134 'exit': { | |
135 evt: CreateEvent('webview.onExit'), | |
136 fields: ['processId', 'reason'] | |
137 }, | |
138 'loadabort': { | |
139 cancelable: true, | |
140 customHandler: function(webViewInternal, event, webViewEvent) { | |
141 webViewInternal.handleLoadAbortEvent(event, webViewEvent); | |
142 }, | |
143 evt: CreateEvent('webview.onLoadAbort'), | |
144 fields: ['url', 'isTopLevel', 'reason'] | |
145 }, | |
146 'loadcommit': { | |
147 customHandler: function(webViewInternal, event, webViewEvent) { | |
148 webViewInternal.handleLoadCommitEvent(event, webViewEvent); | |
149 }, | |
150 evt: CreateEvent('webview.onLoadCommit'), | |
151 fields: ['url', 'isTopLevel'] | |
152 }, | |
153 'loadprogress': { | |
154 evt: CreateEvent('webview.onLoadProgress'), | |
155 fields: ['url', 'progress'] | |
156 }, | |
157 'loadredirect': { | |
158 evt: CreateEvent('webview.onLoadRedirect'), | |
159 fields: ['isTopLevel', 'oldUrl', 'newUrl'] | |
160 }, | |
161 'loadstart': { | |
162 evt: CreateEvent('webview.onLoadStart'), | |
163 fields: ['url', 'isTopLevel'] | |
164 }, | |
165 'loadstop': { | |
166 evt: CreateEvent('webview.onLoadStop'), | |
167 fields: [] | |
168 }, | |
169 'newwindow': { | |
170 cancelable: true, | |
171 customHandler: function(webViewInternal, event, webViewEvent) { | |
172 webViewInternal.handleNewWindowEvent(event, webViewEvent); | |
173 }, | |
174 evt: CreateEvent('webview.onNewWindow'), | |
175 fields: [ | |
176 'initialHeight', | |
177 'initialWidth', | |
178 'targetUrl', | |
179 'windowOpenDisposition', | |
180 'name' | |
181 ] | |
182 }, | |
183 'permissionrequest': { | |
184 cancelable: true, | |
185 customHandler: function(webViewInternal, event, webViewEvent) { | |
186 webViewInternal.handlePermissionEvent(event, webViewEvent); | |
187 }, | |
188 evt: CreateEvent('webview.onPermissionRequest'), | |
189 fields: [ | |
190 'identifier', | |
191 'lastUnlockedBySelf', | |
192 'name', | |
193 'permission', | |
194 'requestMethod', | |
195 'url', | |
196 'userGesture' | |
197 ] | |
198 }, | |
199 'responsive': { | |
200 evt: CreateEvent('webview.onResponsive'), | |
201 fields: ['processId'] | |
202 }, | |
203 'sizechanged': { | |
204 evt: CreateEvent('webview.onSizeChanged'), | |
205 customHandler: function(webViewInternal, event, webViewEvent) { | |
206 webViewInternal.handleSizeChangedEvent(event, webViewEvent); | |
207 }, | |
208 fields: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth'] | |
209 }, | |
210 'unresponsive': { | |
211 evt: CreateEvent('webview.onUnresponsive'), | |
212 fields: ['processId'] | |
213 } | |
214 }; | |
215 | |
216 // Implemented when the experimental API is available. | 77 // Implemented when the experimental API is available. |
217 WebViewInternal.maybeRegisterExperimentalAPIs = function(proto) {} | 78 WebViewInternal.maybeRegisterExperimentalAPIs = function(proto) {} |
218 | 79 |
219 /** | 80 /** |
220 * @constructor | 81 * @constructor |
221 */ | 82 */ |
222 function WebViewInternal(webviewNode) { | 83 function WebViewInternal(webviewNode) { |
223 privates(webviewNode).internal = this; | 84 privates(webviewNode).internal = this; |
224 this.webviewNode = webviewNode; | 85 this.webviewNode = webviewNode; |
225 this.attached = false; | 86 this.attached = false; |
226 | 87 |
227 this.beforeFirstNavigation = true; | 88 this.beforeFirstNavigation = true; |
228 this.validPartitionId = true; | 89 this.validPartitionId = true; |
229 | 90 |
| 91 // on* Event handlers. |
| 92 this.on = {}; |
| 93 |
230 this.browserPluginNode = this.createBrowserPluginNode(); | 94 this.browserPluginNode = this.createBrowserPluginNode(); |
231 var shadowRoot = this.webviewNode.createShadowRoot(); | 95 var shadowRoot = this.webviewNode.createShadowRoot(); |
232 shadowRoot.appendChild(this.browserPluginNode); | 96 shadowRoot.appendChild(this.browserPluginNode); |
233 | 97 |
234 this.setupWebviewNodeAttributes(); | 98 this.setupWebviewNodeAttributes(); |
235 this.setupFocusPropagation(); | 99 this.setupFocusPropagation(); |
236 this.setupWebviewNodeProperties(); | 100 this.setupWebviewNodeProperties(); |
237 | 101 |
238 this.viewInstanceId = IdGenerator.GetNextId(); | 102 this.viewInstanceId = IdGenerator.GetNextId(); |
239 | 103 |
240 this.partition = new Partition(); | 104 this.partition = new Partition(); |
241 this.parseAttributes(); | 105 this.parseAttributes(); |
242 | 106 |
243 this.setupWebviewNodeEvents(); | 107 new WebViewEvents(this, this.viewInstanceId); |
244 } | 108 } |
245 | 109 |
246 /** | 110 /** |
247 * @private | 111 * @private |
248 */ | 112 */ |
249 WebViewInternal.prototype.createBrowserPluginNode = function() { | 113 WebViewInternal.prototype.createBrowserPluginNode = function() { |
250 // We create BrowserPlugin as a custom element in order to observe changes | 114 // We create BrowserPlugin as a custom element in order to observe changes |
251 // to attributes synchronously. | 115 // to attributes synchronously. |
252 var browserPluginNode = new WebViewInternal.BrowserPlugin(); | 116 var browserPluginNode = new WebViewInternal.BrowserPlugin(); |
253 privates(browserPluginNode).internal = this; | 117 privates(browserPluginNode).internal = this; |
(...skipping 10 matching lines...) Expand all Loading... |
264 // window.DOMContentLoaded event). | 128 // window.DOMContentLoaded event). |
265 // So copy from property if copying from attribute fails. | 129 // So copy from property if copying from attribute fails. |
266 browserPluginNode.setAttribute( | 130 browserPluginNode.setAttribute( |
267 attributeName, this.webviewNode[attributeName]); | 131 attributeName, this.webviewNode[attributeName]); |
268 } | 132 } |
269 }, this); | 133 }, this); |
270 | 134 |
271 return browserPluginNode; | 135 return browserPluginNode; |
272 }; | 136 }; |
273 | 137 |
| 138 WebViewInternal.prototype.getInstanceId = function() { |
| 139 return this.instanceId; |
| 140 }; |
| 141 |
274 /** | 142 /** |
275 * @private | |
276 * Resets some state upon reattaching <webview> element to the DOM. | 143 * Resets some state upon reattaching <webview> element to the DOM. |
277 */ | 144 */ |
278 WebViewInternal.prototype.resetUponReattachment = function() { | 145 WebViewInternal.prototype.resetUponReattachment = function() { |
279 this.instanceId = undefined; | 146 this.instanceId = undefined; |
280 this.beforeFirstNavigation = true; | 147 this.beforeFirstNavigation = true; |
281 this.validPartitionId = true; | 148 this.validPartitionId = true; |
282 this.partition.validPartitionId = true; | 149 this.partition.validPartitionId = true; |
283 }; | 150 }; |
284 | 151 |
285 /** | 152 // Sets <webview>.request property. |
286 * @private | 153 WebViewInternal.prototype.setRequestPropertyOnWebViewNode = function(request) { |
287 */ | 154 Object.defineProperty( |
| 155 this.webviewNode, |
| 156 'request', |
| 157 { |
| 158 value: request, |
| 159 enumerable: true |
| 160 } |
| 161 ); |
| 162 }; |
| 163 |
288 WebViewInternal.prototype.setupFocusPropagation = function() { | 164 WebViewInternal.prototype.setupFocusPropagation = function() { |
289 if (!this.webviewNode.hasAttribute('tabIndex')) { | 165 if (!this.webviewNode.hasAttribute('tabIndex')) { |
290 // <webview> needs a tabIndex in order to be focusable. | 166 // <webview> needs a tabIndex in order to be focusable. |
291 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute | 167 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute |
292 // to allow <webview> to be focusable. | 168 // to allow <webview> to be focusable. |
293 // See http://crbug.com/231664. | 169 // See http://crbug.com/231664. |
294 this.webviewNode.setAttribute('tabIndex', -1); | 170 this.webviewNode.setAttribute('tabIndex', -1); |
295 } | 171 } |
296 var self = this; | 172 var self = this; |
297 this.webviewNode.addEventListener('focus', function(e) { | 173 this.webviewNode.addEventListener('focus', function(e) { |
(...skipping 335 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
633 if (newValue != this.webviewNode.getAttribute(name)) { | 509 if (newValue != this.webviewNode.getAttribute(name)) { |
634 this.webviewNode.setAttribute(name, newValue); | 510 this.webviewNode.setAttribute(name, newValue); |
635 } | 511 } |
636 } else { | 512 } else { |
637 // If an attribute is removed from the BrowserPlugin, then remove it | 513 // If an attribute is removed from the BrowserPlugin, then remove it |
638 // from the <webview> as well. | 514 // from the <webview> as well. |
639 this.webviewNode.removeAttribute(name); | 515 this.webviewNode.removeAttribute(name); |
640 } | 516 } |
641 }; | 517 }; |
642 | 518 |
643 /** | 519 WebViewInternal.prototype.onSizeChanged = function(newWidth, newHeight) { |
644 * @private | |
645 */ | |
646 WebViewInternal.prototype.getEvents = function() { | |
647 var experimentalEvents = this.maybeGetExperimentalEvents(); | |
648 for (var eventName in experimentalEvents) { | |
649 WEB_VIEW_EVENTS[eventName] = experimentalEvents[eventName]; | |
650 } | |
651 return WEB_VIEW_EVENTS; | |
652 }; | |
653 | |
654 WebViewInternal.prototype.handleSizeChangedEvent = | |
655 function(event, webViewEvent) { | |
656 var node = this.webviewNode; | 520 var node = this.webviewNode; |
657 | 521 |
658 var width = node.offsetWidth; | 522 var width = node.offsetWidth; |
659 var height = node.offsetHeight; | 523 var height = node.offsetHeight; |
660 | 524 |
661 // Check the current bounds to make sure we do not resize <webview> | 525 // Check the current bounds to make sure we do not resize <webview> |
662 // outside of current constraints. | 526 // outside of current constraints. |
663 var maxWidth; | 527 var maxWidth; |
664 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH) && | 528 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH) && |
665 node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]) { | 529 node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]) { |
(...skipping 24 matching lines...) Expand all Loading... |
690 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT) && | 554 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT) && |
691 node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]) { | 555 node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]) { |
692 minHeight = node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]; | 556 minHeight = node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]; |
693 } else { | 557 } else { |
694 minHeight = height; | 558 minHeight = height; |
695 } | 559 } |
696 if (minHeight > maxHeight) { | 560 if (minHeight > maxHeight) { |
697 minHeight = maxHeight; | 561 minHeight = maxHeight; |
698 } | 562 } |
699 | 563 |
700 if (webViewEvent.newWidth >= minWidth && | 564 if (newWidth >= minWidth && |
701 webViewEvent.newWidth <= maxWidth && | 565 newWidth <= maxWidth && |
702 webViewEvent.newHeight >= minHeight && | 566 newHeight >= minHeight && |
703 webViewEvent.newHeight <= maxHeight) { | 567 newHeight <= maxHeight) { |
704 node.style.width = webViewEvent.newWidth + 'px'; | 568 node.style.width = newWidth + 'px'; |
705 node.style.height = webViewEvent.newHeight + 'px'; | 569 node.style.height = newHeight + 'px'; |
706 } | 570 } |
707 node.dispatchEvent(webViewEvent); | |
708 }; | 571 }; |
709 | 572 |
710 WebViewInternal.prototype.hasNavigated = function() { | 573 WebViewInternal.prototype.hasNavigated = function() { |
711 return !this.beforeFirstNavigation; | 574 return !this.beforeFirstNavigation; |
712 }; | 575 }; |
713 | 576 |
714 /** @return {boolean} */ | 577 /** @return {boolean} */ |
715 WebViewInternal.prototype.parseSrcAttribute = function(result) { | 578 WebViewInternal.prototype.parseSrcAttribute = function(result) { |
716 if (!this.partition.validPartitionId) { | 579 if (!this.partition.validPartitionId) { |
717 result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE; | 580 result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE; |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
753 var self = this; | 616 var self = this; |
754 GuestViewInternal.allocateInstanceId( | 617 GuestViewInternal.allocateInstanceId( |
755 function(instanceId) { | 618 function(instanceId) { |
756 self.instanceId = instanceId; | 619 self.instanceId = instanceId; |
757 // TODO(lazyboy): Make sure this.autoNavigate_ stuff correctly updated | 620 // TODO(lazyboy): Make sure this.autoNavigate_ stuff correctly updated |
758 // |self.src| at this point. | 621 // |self.src| at this point. |
759 self.attachWindowAndSetUpEvents(self.instanceId, self.src); | 622 self.attachWindowAndSetUpEvents(self.instanceId, self.src); |
760 }); | 623 }); |
761 }; | 624 }; |
762 | 625 |
763 /** | 626 WebViewInternal.prototype.onFrameNameChanged = function(name) { |
764 * @private | 627 this.name = name || ''; |
765 */ | 628 if (this.name === '') { |
766 WebViewInternal.prototype.setupWebviewNodeEvents = function() { | 629 this.webviewNode.removeAttribute('name'); |
767 this.setupWebRequestEvents(); | 630 } else { |
768 this.setupExperimentalContextMenus_(); | 631 this.webviewNode.setAttribute('name', this.name); |
769 | |
770 this.on = {}; | |
771 var events = this.getEvents(); | |
772 for (var eventName in events) { | |
773 this.setupEventProperty(eventName); | |
774 } | 632 } |
775 }; | 633 }; |
776 | 634 |
777 /** | 635 WebViewInternal.prototype.dispatchEvent = function(webViewEvent) { |
778 * @private | 636 return this.webviewNode.dispatchEvent(webViewEvent); |
779 */ | |
780 WebViewInternal.prototype.setupNameAttribute = function() { | |
781 var self = this; | |
782 FrameNameChangedEvent.addListener(function(event) { | |
783 self.name = event.name || ''; | |
784 if (self.name === '') { | |
785 self.webviewNode.removeAttribute('name'); | |
786 } else { | |
787 self.webviewNode.setAttribute('name', self.name); | |
788 } | |
789 }, {instanceId: self.instanceId}); | |
790 }; | |
791 | |
792 /** | |
793 * @private | |
794 */ | |
795 WebViewInternal.prototype.setupEvent = function(eventName, eventInfo) { | |
796 var self = this; | |
797 var webviewNode = this.webviewNode; | |
798 eventInfo.evt.addListener(function(event) { | |
799 var details = {bubbles:true}; | |
800 if (eventInfo.cancelable) | |
801 details.cancelable = true; | |
802 var webViewEvent = new Event(eventName, details); | |
803 $Array.forEach(eventInfo.fields, function(field) { | |
804 if (event[field] !== undefined) { | |
805 webViewEvent[field] = event[field]; | |
806 } | |
807 }); | |
808 if (eventInfo.customHandler) { | |
809 eventInfo.customHandler(self, event, webViewEvent); | |
810 return; | |
811 } | |
812 webviewNode.dispatchEvent(webViewEvent); | |
813 }, {instanceId: self.instanceId}); | |
814 }; | 637 }; |
815 | 638 |
816 /** | 639 /** |
817 * Adds an 'on<event>' property on the webview, which can be used to set/unset | 640 * Adds an 'on<event>' property on the webview, which can be used to set/unset |
818 * an event handler. | 641 * an event handler. |
819 * @private | |
820 */ | 642 */ |
821 WebViewInternal.prototype.setupEventProperty = function(eventName) { | 643 WebViewInternal.prototype.setupEventProperty = function(eventName) { |
822 var propertyName = 'on' + eventName.toLowerCase(); | 644 var propertyName = 'on' + eventName.toLowerCase(); |
823 var self = this; | 645 var self = this; |
824 var webviewNode = this.webviewNode; | 646 var webviewNode = this.webviewNode; |
825 Object.defineProperty(webviewNode, propertyName, { | 647 Object.defineProperty(webviewNode, propertyName, { |
826 get: function() { | 648 get: function() { |
827 return self.on[propertyName]; | 649 return self.on[propertyName]; |
828 }, | 650 }, |
829 set: function(value) { | 651 set: function(value) { |
830 if (self.on[propertyName]) | 652 if (self.on[propertyName]) |
831 webviewNode.removeEventListener(eventName, self.on[propertyName]); | 653 webviewNode.removeEventListener(eventName, self.on[propertyName]); |
832 self.on[propertyName] = value; | 654 self.on[propertyName] = value; |
833 if (value) | 655 if (value) |
834 webviewNode.addEventListener(eventName, value); | 656 webviewNode.addEventListener(eventName, value); |
835 }, | 657 }, |
836 enumerable: true | 658 enumerable: true |
837 }); | 659 }); |
838 }; | 660 }; |
839 | 661 |
840 /** | 662 // Updates state upon loadcommit. |
841 * @private | 663 WebViewInternal.prototype.onLoadCommit = function( |
842 */ | 664 currentEntryIndex, entryCount, processId, url, isTopLevel) { |
843 WebViewInternal.prototype.getPermissionTypes = function() { | 665 this.currentEntryIndex = currentEntryIndex; |
844 var permissions = | 666 this.entryCount = entryCount; |
845 ['media', | 667 this.processId = processId; |
846 'geolocation', | |
847 'pointerLock', | |
848 'download', | |
849 'loadplugin', | |
850 'filesystem']; | |
851 return permissions.concat(this.maybeGetExperimentalPermissions()); | |
852 }; | |
853 | |
854 /** | |
855 * @private | |
856 */ | |
857 WebViewInternal.prototype.handleDialogEvent = | |
858 function(event, webViewEvent) { | |
859 var showWarningMessage = function(dialogType) { | |
860 var VOWELS = ['a', 'e', 'i', 'o', 'u']; | |
861 var WARNING_MSG_DIALOG_BLOCKED = '<webview>: %1 %2 dialog was blocked.'; | |
862 var article = (VOWELS.indexOf(dialogType.charAt(0)) >= 0) ? 'An' : 'A'; | |
863 var output = WARNING_MSG_DIALOG_BLOCKED.replace('%1', article); | |
864 output = output.replace('%2', dialogType); | |
865 window.console.warn(output); | |
866 }; | |
867 | |
868 var self = this; | |
869 var webviewNode = this.webviewNode; | |
870 var requestId = event.requestId; | |
871 var actionTaken = false; | |
872 | |
873 var validateCall = function() { | |
874 var ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN = '<webview>: ' + | |
875 'An action has already been taken for this "dialog" event.'; | |
876 | |
877 if (actionTaken) { | |
878 throw new Error(ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN); | |
879 } | |
880 actionTaken = true; | |
881 }; | |
882 | |
883 var dialog = { | |
884 ok: function(user_input) { | |
885 validateCall(); | |
886 user_input = user_input || ''; | |
887 WebView.setPermission(self.instanceId, requestId, 'allow', user_input); | |
888 }, | |
889 cancel: function() { | |
890 validateCall(); | |
891 WebView.setPermission(self.instanceId, requestId, 'deny'); | |
892 } | |
893 }; | |
894 webViewEvent.dialog = dialog; | |
895 | |
896 var defaultPrevented = !webviewNode.dispatchEvent(webViewEvent); | |
897 if (actionTaken) { | |
898 return; | |
899 } | |
900 | |
901 if (defaultPrevented) { | |
902 // Tell the JavaScript garbage collector to track lifetime of |dialog| and | |
903 // call back when the dialog object has been collected. | |
904 MessagingNatives.BindToGC(dialog, function() { | |
905 // Avoid showing a warning message if the decision has already been made. | |
906 if (actionTaken) { | |
907 return; | |
908 } | |
909 WebView.setPermission( | |
910 self.instanceId, requestId, 'default', '', function(allowed) { | |
911 if (allowed) { | |
912 return; | |
913 } | |
914 showWarningMessage(event.messageType); | |
915 }); | |
916 }); | |
917 } else { | |
918 actionTaken = true; | |
919 // The default action is equivalent to canceling the dialog. | |
920 WebView.setPermission( | |
921 self.instanceId, requestId, 'default', '', function(allowed) { | |
922 if (allowed) { | |
923 return; | |
924 } | |
925 showWarningMessage(event.messageType); | |
926 }); | |
927 } | |
928 }; | |
929 | |
930 /** | |
931 * @private | |
932 */ | |
933 WebViewInternal.prototype.handleLoadAbortEvent = | |
934 function(event, webViewEvent) { | |
935 var showWarningMessage = function(reason) { | |
936 var WARNING_MSG_LOAD_ABORTED = '<webview>: ' + | |
937 'The load has aborted with reason "%1".'; | |
938 window.console.warn(WARNING_MSG_LOAD_ABORTED.replace('%1', reason)); | |
939 }; | |
940 if (this.webviewNode.dispatchEvent(webViewEvent)) { | |
941 showWarningMessage(event.reason); | |
942 } | |
943 }; | |
944 | |
945 /** | |
946 * @private | |
947 */ | |
948 WebViewInternal.prototype.handleLoadCommitEvent = | |
949 function(event, webViewEvent) { | |
950 this.currentEntryIndex = event.currentEntryIndex; | |
951 this.entryCount = event.entryCount; | |
952 this.processId = event.processId; | |
953 var oldValue = this.webviewNode.getAttribute('src'); | 668 var oldValue = this.webviewNode.getAttribute('src'); |
954 var newValue = event.url; | 669 var newValue = url; |
955 if (event.isTopLevel && (oldValue != newValue)) { | 670 if (isTopLevel && (oldValue != newValue)) { |
956 // Touching the src attribute triggers a navigation. To avoid | 671 // Touching the src attribute triggers a navigation. To avoid |
957 // triggering a page reload on every guest-initiated navigation, | 672 // triggering a page reload on every guest-initiated navigation, |
958 // we use the flag ignoreNextSrcAttributeChange here. | 673 // we use the flag ignoreNextSrcAttributeChange here. |
959 this.ignoreNextSrcAttributeChange = true; | 674 this.ignoreNextSrcAttributeChange = true; |
960 this.webviewNode.setAttribute('src', newValue); | 675 this.webviewNode.setAttribute('src', newValue); |
961 } | 676 } |
962 this.webviewNode.dispatchEvent(webViewEvent); | |
963 } | |
964 | |
965 /** | |
966 * @private | |
967 */ | |
968 WebViewInternal.prototype.handleNewWindowEvent = | |
969 function(event, webViewEvent) { | |
970 var ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN = '<webview>: ' + | |
971 'An action has already been taken for this "newwindow" event.'; | |
972 | |
973 var ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH = '<webview>: ' + | |
974 'Unable to attach the new window to the provided webview.'; | |
975 | |
976 var ERROR_MSG_WEBVIEW_EXPECTED = '<webview> element expected.'; | |
977 | |
978 var showWarningMessage = function() { | |
979 var WARNING_MSG_NEWWINDOW_BLOCKED = '<webview>: A new window was blocked.'; | |
980 window.console.warn(WARNING_MSG_NEWWINDOW_BLOCKED); | |
981 }; | |
982 | |
983 var self = this; | |
984 var webviewNode = this.webviewNode; | |
985 | |
986 var requestId = event.requestId; | |
987 var actionTaken = false; | |
988 | |
989 var validateCall = function () { | |
990 if (actionTaken) { | |
991 throw new Error(ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN); | |
992 } | |
993 actionTaken = true; | |
994 }; | |
995 | |
996 var windowObj = { | |
997 attach: function(webview) { | |
998 validateCall(); | |
999 if (!webview || !webview.tagName || webview.tagName != 'WEBVIEW') | |
1000 throw new Error(ERROR_MSG_WEBVIEW_EXPECTED); | |
1001 // Attach happens asynchronously to give the tagWatcher an opportunity | |
1002 // to pick up the new webview before attach operates on it, if it hasn't | |
1003 // been attached to the DOM already. | |
1004 // Note: Any subsequent errors cannot be exceptions because they happen | |
1005 // asynchronously. | |
1006 setTimeout(function() { | |
1007 var webViewInternal = privates(webview).internal; | |
1008 if (event.storagePartitionId) { | |
1009 webViewInternal.webviewNode.setAttribute('partition', | |
1010 event.storagePartitionId); | |
1011 var partition = new Partition(); | |
1012 partition.fromAttribute(event.storagePartitionId, | |
1013 webViewInternal.hasNavigated()); | |
1014 webViewInternal.partition = partition; | |
1015 } | |
1016 | |
1017 var attached = | |
1018 webViewInternal.attachWindowAndSetUpEvents( | |
1019 event.windowId, undefined, event.storagePartitionId); | |
1020 | |
1021 if (!attached) { | |
1022 window.console.error(ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH); | |
1023 } | |
1024 // If the object being passed into attach is not a valid <webview> | |
1025 // then we will fail and it will be treated as if the new window | |
1026 // was rejected. The permission API plumbing is used here to clean | |
1027 // up the state created for the new window if attaching fails. | |
1028 WebView.setPermission( | |
1029 self.instanceId, requestId, attached ? 'allow' : 'deny'); | |
1030 }, 0); | |
1031 }, | |
1032 discard: function() { | |
1033 validateCall(); | |
1034 WebView.setPermission(self.instanceId, requestId, 'deny'); | |
1035 } | |
1036 }; | |
1037 webViewEvent.window = windowObj; | |
1038 | |
1039 var defaultPrevented = !webviewNode.dispatchEvent(webViewEvent); | |
1040 if (actionTaken) { | |
1041 return; | |
1042 } | |
1043 | |
1044 if (defaultPrevented) { | |
1045 // Make browser plugin track lifetime of |windowObj|. | |
1046 MessagingNatives.BindToGC(windowObj, function() { | |
1047 // Avoid showing a warning message if the decision has already been made. | |
1048 if (actionTaken) { | |
1049 return; | |
1050 } | |
1051 WebView.setPermission( | |
1052 self.instanceId, requestId, 'default', '', function(allowed) { | |
1053 if (allowed) { | |
1054 return; | |
1055 } | |
1056 showWarningMessage(); | |
1057 }); | |
1058 }); | |
1059 } else { | |
1060 actionTaken = true; | |
1061 // The default action is to discard the window. | |
1062 WebView.setPermission( | |
1063 self.instanceId, requestId, 'default', '', function(allowed) { | |
1064 if (allowed) { | |
1065 return; | |
1066 } | |
1067 showWarningMessage(); | |
1068 }); | |
1069 } | |
1070 }; | 677 }; |
1071 | 678 |
1072 WebViewInternal.prototype.handlePermissionEvent = | 679 WebViewInternal.prototype.onAttach = function(storagePartitionId) { |
1073 function(event, webViewEvent) { | 680 this.webviewNode.setAttribute('partition', storagePartitionId); |
1074 var ERROR_MSG_PERMISSION_ALREADY_DECIDED = '<webview>: ' + | 681 this.partition.fromAttribute(storagePartitionId, this.hasNavigated()); |
1075 'Permission has already been decided for this "permissionrequest" event.'; | |
1076 | |
1077 var showWarningMessage = function(permission) { | |
1078 var WARNING_MSG_PERMISSION_DENIED = '<webview>: ' + | |
1079 'The permission request for "%1" has been denied.'; | |
1080 window.console.warn( | |
1081 WARNING_MSG_PERMISSION_DENIED.replace('%1', permission)); | |
1082 }; | |
1083 | |
1084 var requestId = event.requestId; | |
1085 var self = this; | |
1086 | |
1087 if (this.getPermissionTypes().indexOf(event.permission) < 0) { | |
1088 // The permission type is not allowed. Trigger the default response. | |
1089 WebView.setPermission( | |
1090 self.instanceId, requestId, 'default', '', function(allowed) { | |
1091 if (allowed) { | |
1092 return; | |
1093 } | |
1094 showWarningMessage(event.permission); | |
1095 }); | |
1096 return; | |
1097 } | |
1098 | |
1099 var webviewNode = this.webviewNode; | |
1100 var decisionMade = false; | |
1101 | |
1102 var validateCall = function() { | |
1103 if (decisionMade) { | |
1104 throw new Error(ERROR_MSG_PERMISSION_ALREADY_DECIDED); | |
1105 } | |
1106 decisionMade = true; | |
1107 }; | |
1108 | |
1109 // Construct the event.request object. | |
1110 var request = { | |
1111 allow: function() { | |
1112 validateCall(); | |
1113 WebView.setPermission(self.instanceId, requestId, 'allow'); | |
1114 }, | |
1115 deny: function() { | |
1116 validateCall(); | |
1117 WebView.setPermission(self.instanceId, requestId, 'deny'); | |
1118 } | |
1119 }; | |
1120 webViewEvent.request = request; | |
1121 | |
1122 var defaultPrevented = !webviewNode.dispatchEvent(webViewEvent); | |
1123 if (decisionMade) { | |
1124 return; | |
1125 } | |
1126 | |
1127 if (defaultPrevented) { | |
1128 // Make browser plugin track lifetime of |request|. | |
1129 MessagingNatives.BindToGC(request, function() { | |
1130 // Avoid showing a warning message if the decision has already been made. | |
1131 if (decisionMade) { | |
1132 return; | |
1133 } | |
1134 WebView.setPermission( | |
1135 self.instanceId, requestId, 'default', '', function(allowed) { | |
1136 if (allowed) { | |
1137 return; | |
1138 } | |
1139 showWarningMessage(event.permission); | |
1140 }); | |
1141 }); | |
1142 } else { | |
1143 decisionMade = true; | |
1144 WebView.setPermission( | |
1145 self.instanceId, requestId, 'default', '', function(allowed) { | |
1146 if (allowed) { | |
1147 return; | |
1148 } | |
1149 showWarningMessage(event.permission); | |
1150 }); | |
1151 } | |
1152 }; | 682 }; |
1153 | 683 |
1154 var WebRequestMessageEvent = CreateEvent('webview.onMessage'); | |
1155 | |
1156 function DeclarativeWebRequestEvent(opt_eventName, | |
1157 opt_argSchemas, | |
1158 opt_eventOptions, | |
1159 opt_webViewInstanceId) { | |
1160 var subEventName = opt_eventName + '/' + IdGenerator.GetNextId(); | |
1161 EventBindings.Event.call(this, subEventName, opt_argSchemas, opt_eventOptions, | |
1162 opt_webViewInstanceId); | |
1163 | |
1164 var self = this; | |
1165 // TODO(lazyboy): When do we dispose this listener? | |
1166 WebRequestMessageEvent.addListener(function() { | |
1167 // Re-dispatch to subEvent's listeners. | |
1168 $Function.apply(self.dispatch, self, $Array.slice(arguments)); | |
1169 }, {instanceId: opt_webViewInstanceId || 0}); | |
1170 } | |
1171 | |
1172 DeclarativeWebRequestEvent.prototype = { | |
1173 __proto__: EventBindings.Event.prototype | |
1174 }; | |
1175 | |
1176 /** | |
1177 * @private | |
1178 */ | |
1179 WebViewInternal.prototype.setupWebRequestEvents = function() { | |
1180 var self = this; | |
1181 var request = {}; | |
1182 var createWebRequestEvent = function(webRequestEvent) { | |
1183 return function() { | |
1184 if (!self[webRequestEvent.name]) { | |
1185 self[webRequestEvent.name] = | |
1186 new WebRequestEvent( | |
1187 'webview.' + webRequestEvent.name, | |
1188 webRequestEvent.parameters, | |
1189 webRequestEvent.extraParameters, webRequestEvent.options, | |
1190 self.viewInstanceId); | |
1191 } | |
1192 return self[webRequestEvent.name]; | |
1193 }; | |
1194 }; | |
1195 | |
1196 var createDeclarativeWebRequestEvent = function(webRequestEvent) { | |
1197 return function() { | |
1198 if (!self[webRequestEvent.name]) { | |
1199 // The onMessage event gets a special event type because we want | |
1200 // the listener to fire only for messages targeted for this particular | |
1201 // <webview>. | |
1202 var EventClass = webRequestEvent.name === 'onMessage' ? | |
1203 DeclarativeWebRequestEvent : EventBindings.Event; | |
1204 self[webRequestEvent.name] = | |
1205 new EventClass( | |
1206 'webview.' + webRequestEvent.name, | |
1207 webRequestEvent.parameters, | |
1208 webRequestEvent.options, | |
1209 self.viewInstanceId); | |
1210 } | |
1211 return self[webRequestEvent.name]; | |
1212 }; | |
1213 }; | |
1214 | |
1215 for (var i = 0; i < DeclarativeWebRequestSchema.events.length; ++i) { | |
1216 var eventSchema = DeclarativeWebRequestSchema.events[i]; | |
1217 var webRequestEvent = createDeclarativeWebRequestEvent(eventSchema); | |
1218 Object.defineProperty( | |
1219 request, | |
1220 eventSchema.name, | |
1221 { | |
1222 get: webRequestEvent, | |
1223 enumerable: true | |
1224 } | |
1225 ); | |
1226 } | |
1227 | |
1228 // Populate the WebRequest events from the API definition. | |
1229 for (var i = 0; i < WebRequestSchema.events.length; ++i) { | |
1230 var webRequestEvent = createWebRequestEvent(WebRequestSchema.events[i]); | |
1231 Object.defineProperty( | |
1232 request, | |
1233 WebRequestSchema.events[i].name, | |
1234 { | |
1235 get: webRequestEvent, | |
1236 enumerable: true | |
1237 } | |
1238 ); | |
1239 } | |
1240 Object.defineProperty( | |
1241 this.webviewNode, | |
1242 'request', | |
1243 { | |
1244 value: request, | |
1245 enumerable: true | |
1246 } | |
1247 ); | |
1248 }; | |
1249 | 684 |
1250 /** @private */ | 685 /** @private */ |
1251 WebViewInternal.prototype.getUserAgent = function() { | 686 WebViewInternal.prototype.getUserAgent = function() { |
1252 return this.userAgentOverride || navigator.userAgent; | 687 return this.userAgentOverride || navigator.userAgent; |
1253 }; | 688 }; |
1254 | 689 |
1255 /** @private */ | 690 /** @private */ |
1256 WebViewInternal.prototype.isUserAgentOverridden = function() { | 691 WebViewInternal.prototype.isUserAgentOverridden = function() { |
1257 return !!this.userAgentOverride && | 692 return !!this.userAgentOverride && |
1258 this.userAgentOverride != navigator.userAgent; | 693 this.userAgentOverride != navigator.userAgent; |
(...skipping 20 matching lines...) Expand all Loading... |
1279 this.webviewNode.getAttribute(WEB_VIEW_ATTRIBUTE_PARTITION) || | 714 this.webviewNode.getAttribute(WEB_VIEW_ATTRIBUTE_PARTITION) || |
1280 this.webviewNode[WEB_VIEW_ATTRIBUTE_PARTITION]; | 715 this.webviewNode[WEB_VIEW_ATTRIBUTE_PARTITION]; |
1281 var params = { | 716 var params = { |
1282 'api': 'webview', | 717 'api': 'webview', |
1283 'instanceId': this.viewInstanceId, | 718 'instanceId': this.viewInstanceId, |
1284 'name': this.name, | 719 'name': this.name, |
1285 'src': opt_src, | 720 'src': opt_src, |
1286 'storagePartitionId': storagePartitionId, | 721 'storagePartitionId': storagePartitionId, |
1287 'userAgentOverride': this.userAgentOverride | 722 'userAgentOverride': this.userAgentOverride |
1288 }; | 723 }; |
1289 this.setupNameAttribute(); | |
1290 var events = this.getEvents(); | |
1291 for (var eventName in events) { | |
1292 this.setupEvent(eventName, events[eventName]); | |
1293 } | |
1294 | 724 |
1295 return this.browserPluginNode['-internal-attach'](this.instanceId, params); | 725 return this.browserPluginNode['-internal-attach'](this.instanceId, params); |
1296 }; | 726 }; |
1297 | 727 |
1298 // Registers browser plugin <object> custom element. | 728 // Registers browser plugin <object> custom element. |
1299 function registerBrowserPluginElement() { | 729 function registerBrowserPluginElement() { |
1300 var proto = Object.create(HTMLObjectElement.prototype); | 730 var proto = Object.create(HTMLObjectElement.prototype); |
1301 | 731 |
1302 proto.createdCallback = function() { | 732 proto.createdCallback = function() { |
1303 this.setAttribute('type', 'application/browser-plugin'); | 733 this.setAttribute('type', 'application/browser-plugin'); |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1424 */ | 854 */ |
1425 WebViewInternal.prototype.maybeGetExperimentalPermissions = function() { | 855 WebViewInternal.prototype.maybeGetExperimentalPermissions = function() { |
1426 return []; | 856 return []; |
1427 }; | 857 }; |
1428 | 858 |
1429 /** | 859 /** |
1430 * Calls to show contextmenu right away instead of dispatching a 'contextmenu' | 860 * Calls to show contextmenu right away instead of dispatching a 'contextmenu' |
1431 * event. | 861 * event. |
1432 * This will be overridden in web_view_experimental.js to implement contextmenu | 862 * This will be overridden in web_view_experimental.js to implement contextmenu |
1433 * API. | 863 * API. |
1434 * @private | |
1435 */ | 864 */ |
1436 WebViewInternal.prototype.maybeHandleContextMenu = function(e, webViewEvent) { | 865 WebViewInternal.prototype.maybeHandleContextMenu = function(e, webViewEvent) { |
1437 var requestId = e.requestId; | 866 var requestId = e.requestId; |
1438 // Setting |params| = undefined will show the context menu unmodified, hence | 867 // Setting |params| = undefined will show the context menu unmodified, hence |
1439 // the 'contextmenu' API is disabled for stable channel. | 868 // the 'contextmenu' API is disabled for stable channel. |
1440 var params = undefined; | 869 var params = undefined; |
1441 WebView.showContextMenu(this.instanceId, requestId, params); | 870 WebView.showContextMenu(this.instanceId, requestId, params); |
1442 }; | 871 }; |
1443 | 872 |
1444 /** | 873 /** |
1445 * Implemented when the experimental API is available. | 874 * Implemented when the experimental API is available. |
1446 * @private | 875 * @private |
1447 */ | 876 */ |
1448 WebViewInternal.prototype.setupExperimentalContextMenus_ = function() {}; | 877 WebViewInternal.prototype.setupExperimentalContextMenus = function() {}; |
1449 | 878 |
1450 exports.WebView = WebView; | 879 exports.WebView = WebView; |
1451 exports.WebViewInternal = WebViewInternal; | 880 exports.WebViewInternal = WebViewInternal; |
1452 exports.CreateEvent = CreateEvent; | |
OLD | NEW |