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
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 = {
42 evt: CreateEvent('webViewInternal.onClose'),
46 evt: CreateEvent('webViewInternal.onConsoleMessage'),
47 fields: ['level', 'message', 'line', 'sourceId']
50 evt: CreateEvent('webViewInternal.onContentLoad'),
55 customHandler: function(handler, event, webViewEvent) {
56 handler.handleDialogEvent(event, webViewEvent);
58 evt: CreateEvent('webViewInternal.onDialog'),
59 fields: ['defaultPromptText', 'messageText', 'messageType', 'url']
62 evt: CreateEvent('webViewInternal.onDropLink'),
66 evt: CreateEvent('webViewInternal.onExit'),
67 fields: ['processId', 'reason']
70 evt: CreateEvent('webViewInternal.onFindReply'),
82 customHandler: function(handler, event, webViewEvent) {
83 handler.handleLoadAbortEvent(event, webViewEvent);
85 evt: CreateEvent('webViewInternal.onLoadAbort'),
86 fields: ['url', 'isTopLevel', 'reason']
89 customHandler: function(handler, event, webViewEvent) {
90 handler.handleLoadCommitEvent(event, webViewEvent);
92 evt: CreateEvent('webViewInternal.onLoadCommit'),
93 fields: ['url', 'isTopLevel']
96 evt: CreateEvent('webViewInternal.onLoadProgress'),
97 fields: ['url', 'progress']
100 evt: CreateEvent('webViewInternal.onLoadRedirect'),
101 fields: ['isTopLevel', 'oldUrl', 'newUrl']
104 evt: CreateEvent('webViewInternal.onLoadStart'),
105 fields: ['url', 'isTopLevel']
108 evt: CreateEvent('webViewInternal.onLoadStop'),
113 customHandler: function(handler, event, webViewEvent) {
114 handler.handleNewWindowEvent(event, webViewEvent);
116 evt: CreateEvent('webViewInternal.onNewWindow'),
121 'windowOpenDisposition',
125 'permissionrequest': {
127 customHandler: function(handler, event, webViewEvent) {
128 handler.handlePermissionEvent(event, webViewEvent);
130 evt: CreateEvent('webViewInternal.onPermissionRequest'),
133 'lastUnlockedBySelf',
142 evt: CreateEvent('webViewInternal.onResponsive'),
143 fields: ['processId']
146 evt: CreateEvent('webViewInternal.onSizeChanged'),
147 customHandler: function(handler, event, webViewEvent) {
148 handler.handleSizeChangedEvent(event, webViewEvent);
150 fields: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth']
153 evt: CreateEvent('webViewInternal.onUnresponsive'),
154 fields: ['processId']
157 evt: CreateEvent('webViewInternal.onZoomChange'),
158 fields: ['oldZoomFactor', 'newZoomFactor']
162 function DeclarativeWebRequestEvent(opt_eventName,
165 opt_webViewInstanceId) {
166 var subEventName = opt_eventName + '/' + IdGenerator.GetNextId();
167 EventBindings.Event.call(this,
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
185 function WebViewEvents(webViewImpl, viewInstanceId) {
186 this.webViewImpl = webViewImpl;
187 this.viewInstanceId = viewInstanceId;
189 // on* Event handlers.
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]);
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() {
210 var createWebRequestEvent = function(webRequestEvent) {
212 if (!this[webRequestEvent.name]) {
213 this[webRequestEvent.name] =
215 'webViewInternal.' + webRequestEvent.name,
216 webRequestEvent.parameters,
217 webRequestEvent.extraParameters, webRequestEvent.options,
218 this.viewInstanceId);
220 return this[webRequestEvent.name];
224 var createDeclarativeWebRequestEvent = function(webRequestEvent) {
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
230 var EventClass = webRequestEvent.name === 'onMessage' ?
231 DeclarativeWebRequestEvent : EventBindings.Event;
232 this[webRequestEvent.name] =
234 'webViewInternal.declarativeWebRequest.' + webRequestEvent.name,
235 webRequestEvent.parameters,
236 webRequestEvent.options,
237 this.viewInstanceId);
239 return this[webRequestEvent.name];
243 for (var i = 0; i < DeclarativeWebRequestSchema.events.length; ++i) {
244 var eventSchema = DeclarativeWebRequestSchema.events[i];
245 var webRequestEvent = createDeclarativeWebRequestEvent(eventSchema);
246 Object.defineProperty(
250 get: webRequestEvent,
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(
261 WebRequestSchema.events[i].name,
263 get: webRequestEvent,
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];
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;
287 var webViewEvent = new Event(name, details);
288 $Array.forEach(info.fields, function(field) {
289 if (e[field] !== undefined) {
290 webViewEvent[field] = e[field];
293 if (info.customHandler) {
294 info.customHandler(this, e, webViewEvent);
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, {
307 return this.on[propertyName];
309 set: function(value) {
310 if (this.on[propertyName]) {
311 this.webViewImpl.element.removeEventListener(
312 eventName, this.on[propertyName]);
314 this.on[propertyName] = value;
316 this.webViewImpl.element.addEventListener(eventName, value);
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));
332 if (this.webViewImpl.dispatchEvent(webViewEvent)) {
333 showWarningMessage(event.reason);
337 WebViewEvents.prototype.handleLoadCommitEvent = function(event, webViewEvent) {
338 this.webViewImpl.onLoadCommit(event.baseUrlForDataUrl,
339 event.currentEntryIndex,
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;