Reland the ULONG -> SIZE_T change from 317177
[chromium-blink-merge.git] / extensions / renderer / resources / guest_view / web_view_events.js
blob7d42d26123957a6bc5104af35b416a51198219cb
1 // Copyright 2014 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.
5 // Event management for WebView.
7 var DeclarativeWebRequestSchema =
8     requireNative('schema_registry').GetSchema('declarativeWebRequest');
9 var EventBindings = require('event_bindings');
10 var IdGenerator = requireNative('id_generator');
11 var WebRequestEvent = require('webRequestInternal').WebRequestEvent;
12 var WebRequestSchema =
13     requireNative('schema_registry').GetSchema('webRequest');
14 var WebViewActionRequests =
15     require('webViewActionRequests').WebViewActionRequests;
17 var CreateEvent = function(name) {
18   var eventOpts = {supportsListeners: true, supportsFilters: true};
19   return new EventBindings.Event(name, undefined, eventOpts);
22 var FrameNameChangedEvent = CreateEvent('webViewInternal.onFrameNameChanged');
23 var PluginDestroyedEvent = CreateEvent('webViewInternal.onPluginDestroyed');
24 var WebRequestMessageEvent = CreateEvent('webViewInternal.onMessage');
26 // WEB_VIEW_EVENTS is a map of stable <webview> DOM event names to their
27 //     associated extension event descriptor objects.
28 // An event listener will be attached to the extension event |evt| specified in
29 //     the descriptor.
30 // |fields| specifies the public-facing fields in the DOM event that are
31 //     accessible to <webview> developers.
32 // |customHandler| allows a handler function to be called each time an extension
33 //     event is caught by its event listener. The DOM event should be dispatched
34 //     within this handler function. With no handler function, the DOM event
35 //     will be dispatched by default each time the extension event is caught.
36 // |cancelable| (default: false) specifies whether the event's default
37 //     behavior can be canceled. If the default action associated with the event
38 //     is prevented, then its dispatch function will return false in its event
39 //     handler. The event must have a custom handler for this to be meaningful.
40 var WEB_VIEW_EVENTS = {
41   'close': {
42     evt: CreateEvent('webViewInternal.onClose'),
43     fields: []
44   },
45   'consolemessage': {
46     evt: CreateEvent('webViewInternal.onConsoleMessage'),
47     fields: ['level', 'message', 'line', 'sourceId']
48   },
49   'contentload': {
50     evt: CreateEvent('webViewInternal.onContentLoad'),
51     fields: []
52   },
53   'dialog': {
54     cancelable: true,
55     customHandler: function(handler, event, webViewEvent) {
56       handler.handleDialogEvent(event, webViewEvent);
57     },
58     evt: CreateEvent('webViewInternal.onDialog'),
59     fields: ['defaultPromptText', 'messageText', 'messageType', 'url']
60   },
61   'droplink': {
62     evt: CreateEvent('webViewInternal.onDropLink'),
63     fields: ['url']
64   },
65   'exit': {
66     evt: CreateEvent('webViewInternal.onExit'),
67     fields: ['processId', 'reason']
68   },
69   'findupdate': {
70     evt: CreateEvent('webViewInternal.onFindReply'),
71     fields: [
72       'searchText',
73       'numberOfMatches',
74       'activeMatchOrdinal',
75       'selectionRect',
76       'canceled',
77       'finalUpdate'
78     ]
79   },
80   'loadabort': {
81     cancelable: true,
82     customHandler: function(handler, event, webViewEvent) {
83       handler.handleLoadAbortEvent(event, webViewEvent);
84     },
85     evt: CreateEvent('webViewInternal.onLoadAbort'),
86     fields: ['url', 'isTopLevel', 'reason']
87   },
88   'loadcommit': {
89     customHandler: function(handler, event, webViewEvent) {
90       handler.handleLoadCommitEvent(event, webViewEvent);
91     },
92     evt: CreateEvent('webViewInternal.onLoadCommit'),
93     fields: ['url', 'isTopLevel']
94   },
95   'loadprogress': {
96     evt: CreateEvent('webViewInternal.onLoadProgress'),
97     fields: ['url', 'progress']
98   },
99   'loadredirect': {
100     evt: CreateEvent('webViewInternal.onLoadRedirect'),
101     fields: ['isTopLevel', 'oldUrl', 'newUrl']
102   },
103   'loadstart': {
104     evt: CreateEvent('webViewInternal.onLoadStart'),
105     fields: ['url', 'isTopLevel']
106   },
107   'loadstop': {
108     evt: CreateEvent('webViewInternal.onLoadStop'),
109     fields: []
110   },
111   'newwindow': {
112     cancelable: true,
113     customHandler: function(handler, event, webViewEvent) {
114       handler.handleNewWindowEvent(event, webViewEvent);
115     },
116     evt: CreateEvent('webViewInternal.onNewWindow'),
117     fields: [
118       'initialHeight',
119       'initialWidth',
120       'targetUrl',
121       'windowOpenDisposition',
122       'name'
123     ]
124   },
125   'permissionrequest': {
126     cancelable: true,
127     customHandler: function(handler, event, webViewEvent) {
128       handler.handlePermissionEvent(event, webViewEvent);
129     },
130     evt: CreateEvent('webViewInternal.onPermissionRequest'),
131     fields: [
132       'identifier',
133       'lastUnlockedBySelf',
134       'name',
135       'permission',
136       'requestMethod',
137       'url',
138       'userGesture'
139     ]
140   },
141   'responsive': {
142     evt: CreateEvent('webViewInternal.onResponsive'),
143     fields: ['processId']
144   },
145   'sizechanged': {
146     evt: CreateEvent('webViewInternal.onSizeChanged'),
147     customHandler: function(handler, event, webViewEvent) {
148       handler.handleSizeChangedEvent(event, webViewEvent);
149     },
150     fields: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth']
151   },
152   'unresponsive': {
153     evt: CreateEvent('webViewInternal.onUnresponsive'),
154     fields: ['processId']
155   },
156   'zoomchange': {
157     evt: CreateEvent('webViewInternal.onZoomChange'),
158     fields: ['oldZoomFactor', 'newZoomFactor']
159   }
162 function DeclarativeWebRequestEvent(opt_eventName,
163                                     opt_argSchemas,
164                                     opt_eventOptions,
165                                     opt_webViewInstanceId) {
166   var subEventName = opt_eventName + '/' + IdGenerator.GetNextId();
167   EventBindings.Event.call(this,
168                            subEventName,
169                            opt_argSchemas,
170                            opt_eventOptions,
171                            opt_webViewInstanceId);
173   // TODO(lazyboy): When do we dispose this listener?
174   WebRequestMessageEvent.addListener(function() {
175     // Re-dispatch to subEvent's listeners.
176     $Function.apply(this.dispatch, this, $Array.slice(arguments));
177   }.bind(this), {instanceId: opt_webViewInstanceId || 0});
180 DeclarativeWebRequestEvent.prototype = {
181   __proto__: EventBindings.Event.prototype
184 // Constructor.
185 function WebViewEvents(webViewImpl, viewInstanceId) {
186   this.webViewImpl = webViewImpl;
187   this.viewInstanceId = viewInstanceId;
189   // on* Event handlers.
190   this.on = {};
192   // Set up the events.
193   this.setupFrameNameChangedEvent();
194   this.setupWebRequestEvents();
195   this.webViewImpl.setupExperimentalContextMenus();
196   var events = this.getEvents();
197   for (var eventName in events) {
198     this.setupEvent(eventName, events[eventName]);
199   }
202 WebViewEvents.prototype.setupFrameNameChangedEvent = function() {
203   FrameNameChangedEvent.addListener(function(e) {
204     this.webViewImpl.onFrameNameChanged(e.name);
205   }.bind(this), {instanceId: this.viewInstanceId});
208 WebViewEvents.prototype.setupWebRequestEvents = function() {
209   var request = {};
210   var createWebRequestEvent = function(webRequestEvent) {
211     return function() {
212       if (!this[webRequestEvent.name]) {
213         this[webRequestEvent.name] =
214             new WebRequestEvent(
215                 'webViewInternal.' + webRequestEvent.name,
216                 webRequestEvent.parameters,
217                 webRequestEvent.extraParameters, webRequestEvent.options,
218                 this.viewInstanceId);
219       }
220       return this[webRequestEvent.name];
221     }.bind(this);
222   }.bind(this);
224   var createDeclarativeWebRequestEvent = function(webRequestEvent) {
225     return function() {
226       if (!this[webRequestEvent.name]) {
227         // The onMessage event gets a special event type because we want
228         // the listener to fire only for messages targeted for this particular
229         // <webview>.
230         var EventClass = webRequestEvent.name === 'onMessage' ?
231             DeclarativeWebRequestEvent : EventBindings.Event;
232         this[webRequestEvent.name] =
233             new EventClass(
234                 'webViewInternal.declarativeWebRequest.' + webRequestEvent.name,
235                 webRequestEvent.parameters,
236                 webRequestEvent.options,
237                 this.viewInstanceId);
238       }
239       return this[webRequestEvent.name];
240     }.bind(this);
241   }.bind(this);
243   for (var i = 0; i < DeclarativeWebRequestSchema.events.length; ++i) {
244     var eventSchema = DeclarativeWebRequestSchema.events[i];
245     var webRequestEvent = createDeclarativeWebRequestEvent(eventSchema);
246     Object.defineProperty(
247         request,
248         eventSchema.name,
249         {
250           get: webRequestEvent,
251           enumerable: true
252         }
253     );
254   }
256   // Populate the WebRequest events from the API definition.
257   for (var i = 0; i < WebRequestSchema.events.length; ++i) {
258     var webRequestEvent = createWebRequestEvent(WebRequestSchema.events[i]);
259     Object.defineProperty(
260         request,
261         WebRequestSchema.events[i].name,
262         {
263           get: webRequestEvent,
264           enumerable: true
265         }
266     );
267   }
269   request = this.webViewImpl.maybeSetupExperimentalChromeWebViewEvents(request);
270   this.webViewImpl.setRequestPropertyOnWebViewElement(request);
273 WebViewEvents.prototype.getEvents = function() {
274   var chromeEvents = this.webViewImpl.maybeGetChromeWebViewEvents();
275   for (var eventName in chromeEvents) {
276     WEB_VIEW_EVENTS[eventName] = chromeEvents[eventName];
277   }
278   return WEB_VIEW_EVENTS;
281 WebViewEvents.prototype.setupEvent = function(name, info) {
282   info.evt.addListener(function(e) {
283     var details = {bubbles: true};
284     if (info.cancelable) {
285       details.cancelable = true;
286     }
287     var webViewEvent = new Event(name, details);
288     $Array.forEach(info.fields, function(field) {
289       if (e[field] !== undefined) {
290         webViewEvent[field] = e[field];
291       }
292     }.bind(this));
293     if (info.customHandler) {
294       info.customHandler(this, e, webViewEvent);
295       return;
296     }
297     this.webViewImpl.dispatchEvent(webViewEvent);
298   }.bind(this), {instanceId: this.viewInstanceId});
300   this.setupEventProperty(name);
303 WebViewEvents.prototype.setupEventProperty = function(eventName) {
304   var propertyName = 'on' + eventName.toLowerCase();
305   Object.defineProperty(this.webViewImpl.element, propertyName, {
306     get: function() {
307       return this.on[propertyName];
308     }.bind(this),
309     set: function(value) {
310       if (this.on[propertyName]) {
311         this.webViewImpl.element.removeEventListener(
312             eventName, this.on[propertyName]);
313       }
314       this.on[propertyName] = value;
315       if (value)
316         this.webViewImpl.element.addEventListener(eventName, value);
317     }.bind(this),
318     enumerable: true
319   });
322 WebViewEvents.prototype.handleDialogEvent = function(event, webViewEvent) {
323   new WebViewActionRequests.Dialog(this.webViewImpl, event, webViewEvent);
326 WebViewEvents.prototype.handleLoadAbortEvent = function(event, webViewEvent) {
327   var showWarningMessage = function(reason) {
328     var WARNING_MSG_LOAD_ABORTED = '<webview>: ' +
329         'The load has aborted with reason "%1".';
330     window.console.warn(WARNING_MSG_LOAD_ABORTED.replace('%1', reason));
331   };
332   if (this.webViewImpl.dispatchEvent(webViewEvent)) {
333     showWarningMessage(event.reason);
334   }
337 WebViewEvents.prototype.handleLoadCommitEvent = function(event, webViewEvent) {
338   this.webViewImpl.onLoadCommit(event.baseUrlForDataUrl,
339                                 event.currentEntryIndex,
340                                 event.entryCount,
341                                 event.processId,
342                                 event.url,
343                                 event.isTopLevel);
344   this.webViewImpl.dispatchEvent(webViewEvent);
347 WebViewEvents.prototype.handleNewWindowEvent = function(event, webViewEvent) {
348   new WebViewActionRequests.NewWindow(this.webViewImpl, event, webViewEvent);
351 WebViewEvents.prototype.handlePermissionEvent = function(event, webViewEvent) {
352   new WebViewActionRequests.PermissionRequest(
353       this.webViewImpl, event, webViewEvent);
356 WebViewEvents.prototype.handleSizeChangedEvent = function(
357     event, webViewEvent) {
358   this.webViewImpl.onSizeChanged(webViewEvent);
361 exports.WebViewEvents = WebViewEvents;
362 exports.CreateEvent = CreateEvent;