Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(107)

Side by Side Diff: chrome/renderer/resources/extensions/web_view_events.js

Issue 333713005: Move event stuff of web_view.js to its own class/file. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: move rest of the events Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 // Event management for WebViewInternal.
6
7 var DeclarativeWebRequestSchema =
8 requireNative('schema_registry').GetSchema('declarativeWebRequest');
9 var EventBindings = require('event_bindings');
10 var IdGenerator = requireNative('id_generator');
11 var MessagingNatives = requireNative('messaging_natives');
12 var WebRequestEvent = require('webRequestInternal').WebRequestEvent;
13 var WebRequestSchema =
14 requireNative('schema_registry').GetSchema('webRequest');
15 var WebView = require('webview').WebView;
16
17 var CreateEvent = function(name) {
18 var eventOpts = {supportsListeners: true, supportsFilters: true};
19 return new EventBindings.Event(name, undefined, eventOpts);
20 };
21
22 // WEB_VIEW_EVENTS is a map of stable <webview> DOM event names to their
Fady Samuel 2014/06/19 14:25:26 Move this above WEB_VIEW_EVENTS directly.
23 // associated extension event descriptor objects.
24 // An event listener will be attached to the extension event |evt| specified in
25 // the descriptor.
26 // |fields| specifies the public-facing fields in the DOM event that are
27 // accessible to <webview> developers.
28 // |customHandler| allows a handler function to be called each time an extension
29 // event is caught by its event listener. The DOM event should be dispatched
30 // within this handler function. With no handler function, the DOM event
31 // will be dispatched by default each time the extension event is caught.
32 // |cancelable| (default: false) specifies whether the event's default
33 // behavior can be canceled. If the default action associated with the event
34 // is prevented, then its dispatch function will return false in its event
35 // handler. The event must have a custom handler for this to be meaningful.
36
37 var FrameNameChangedEvent = CreateEvent('webview.onFrameNameChanged');
38 var WebRequestMessageEvent = CreateEvent('webview.onMessage');
39
40 var WEB_VIEW_EVENTS = {
41 'close': {
42 evt: CreateEvent('webview.onClose'),
43 fields: []
44 },
45 'consolemessage': {
46 evt: CreateEvent('webview.onConsoleMessage'),
47 fields: ['level', 'message', 'line', 'sourceId']
48 },
49 'contentload': {
50 evt: CreateEvent('webview.onContentLoad'),
51 fields: []
52 },
53 'contextmenu': {
54 evt: CreateEvent('webview.contextmenu'),
55 cancelable: true,
56 customHandler: function(handler, event, webViewEvent) {
57 handler.handleContextMenu(event, webViewEvent);
58 },
59 fields: ['items']
60 },
61 'dialog': {
62 cancelable: true,
63 customHandler: function(handler, event, webViewEvent) {
64 handler.handleDialogEvent(event, webViewEvent);
65 },
66 evt: CreateEvent('webview.onDialog'),
67 fields: ['defaultPromptText', 'messageText', 'messageType', 'url']
68 },
69 'exit': {
70 evt: CreateEvent('webview.onExit'),
71 fields: ['processId', 'reason']
72 },
73 'loadabort': {
74 cancelable: true,
75 customHandler: function(handler, event, webViewEvent) {
76 handler.handleLoadAbortEvent(event, webViewEvent);
77 },
78 evt: CreateEvent('webview.onLoadAbort'),
79 fields: ['url', 'isTopLevel', 'reason']
80 },
81 'loadcommit': {
82 customHandler: function(handler, event, webViewEvent) {
83 handler.handleLoadCommitEvent(event, webViewEvent);
84 },
85 evt: CreateEvent('webview.onLoadCommit'),
86 fields: ['url', 'isTopLevel']
87 },
88 'loadprogress': {
89 evt: CreateEvent('webview.onLoadProgress'),
90 fields: ['url', 'progress']
91 },
92 'loadredirect': {
93 evt: CreateEvent('webview.onLoadRedirect'),
94 fields: ['isTopLevel', 'oldUrl', 'newUrl']
95 },
96 'loadstart': {
97 evt: CreateEvent('webview.onLoadStart'),
98 fields: ['url', 'isTopLevel']
99 },
100 'loadstop': {
101 evt: CreateEvent('webview.onLoadStop'),
102 fields: []
103 },
104 'newwindow': {
105 cancelable: true,
106 customHandler: function(handler, event, webViewEvent) {
107 handler.handleNewWindowEvent(event, webViewEvent);
108 },
109 evt: CreateEvent('webview.onNewWindow'),
110 fields: [
111 'initialHeight',
112 'initialWidth',
113 'targetUrl',
114 'windowOpenDisposition',
115 'name'
116 ]
117 },
118 'permissionrequest': {
119 cancelable: true,
120 customHandler: function(handler, event, webViewEvent) {
121 handler.handlePermissionEvent(event, webViewEvent);
122 },
123 evt: CreateEvent('webview.onPermissionRequest'),
124 fields: [
125 'identifier',
126 'lastUnlockedBySelf',
127 'name',
128 'permission',
129 'requestMethod',
130 'url',
131 'userGesture'
132 ]
133 },
134 'responsive': {
135 evt: CreateEvent('webview.onResponsive'),
136 fields: ['processId']
137 },
138 'sizechanged': {
139 evt: CreateEvent('webview.onSizeChanged'),
140 customHandler: function(handler, event, webViewEvent) {
141 handler.handleSizeChangedEvent(event, webViewEvent);
142 },
143 fields: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth']
144 },
145 'unresponsive': {
146 evt: CreateEvent('webview.onUnresponsive'),
147 fields: ['processId']
148 }
149 };
150
151 function DeclarativeWebRequestEvent(opt_eventName,
152 opt_argSchemas,
153 opt_eventOptions,
154 opt_webViewInstanceId) {
155 var subEventName = opt_eventName + '/' + IdGenerator.GetNextId();
156 EventBindings.Event.call(this, subEventName, opt_argSchemas, opt_eventOptions,
157 opt_webViewInstanceId);
158
159 var self = this;
160 // TODO(lazyboy): When do we dispose this listener?
161 WebRequestMessageEvent.addListener(function() {
162 // Re-dispatch to subEvent's listeners.
163 $Function.apply(self.dispatch, self, $Array.slice(arguments));
164 }, {instanceId: opt_webViewInstanceId || 0});
165 }
166
167 DeclarativeWebRequestEvent.prototype = {
168 __proto__: EventBindings.Event.prototype
169 };
170
171 // Constructor.
172 function WebViewEvents(webViewInternal, viewInstanceId) {
173 this.webViewInternal = webViewInternal;
174 this.viewInstanceId = viewInstanceId;
175 this.setup();
176 }
177
178 // Sets up events.
179 WebViewEvents.prototype.setup = function() {
180 this.setupFrameNameChangedEvent();
181 this.setupWebRequestEvents();
182 this.webViewInternal.setupExperimentalContextMenus();
183
184 var events = this.getEvents();
185 for (var eventName in events) {
186 this.setupEvent(eventName, events[eventName]);
187 }
188 };
189
190 WebViewEvents.prototype.setupFrameNameChangedEvent = function() {
191 var self = this;
192 FrameNameChangedEvent.addListener(function(e) {
193 self.webViewInternal.onFrameNameChanged(e.name);
194 }, {instanceId: self.viewInstanceId});
195 };
196
197 WebViewEvents.prototype.setupWebRequestEvents = function() {
198 var self = this;
199 var request = {};
200 var createWebRequestEvent = function(webRequestEvent) {
201 return function() {
202 if (!self[webRequestEvent.name]) {
203 self[webRequestEvent.name] =
204 new WebRequestEvent(
205 'webview.' + webRequestEvent.name,
206 webRequestEvent.parameters,
207 webRequestEvent.extraParameters, webRequestEvent.options,
208 self.viewInstanceId);
209 }
210 return self[webRequestEvent.name];
211 };
212 };
213
214 var createDeclarativeWebRequestEvent = function(webRequestEvent) {
215 return function() {
216 if (!self[webRequestEvent.name]) {
217 // The onMessage event gets a special event type because we want
218 // the listener to fire only for messages targeted for this particular
219 // <webview>.
220 var EventClass = webRequestEvent.name === 'onMessage' ?
221 DeclarativeWebRequestEvent : EventBindings.Event;
222 self[webRequestEvent.name] =
223 new EventClass(
224 'webview.' + webRequestEvent.name,
225 webRequestEvent.parameters,
226 webRequestEvent.options,
227 self.viewInstanceId);
228 }
229 return self[webRequestEvent.name];
230 };
231 };
232
233 for (var i = 0; i < DeclarativeWebRequestSchema.events.length; ++i) {
234 var eventSchema = DeclarativeWebRequestSchema.events[i];
235 var webRequestEvent = createDeclarativeWebRequestEvent(eventSchema);
236 Object.defineProperty(
237 request,
238 eventSchema.name,
239 {
240 get: webRequestEvent,
241 enumerable: true
242 }
243 );
244 }
245
246 // Populate the WebRequest events from the API definition.
247 for (var i = 0; i < WebRequestSchema.events.length; ++i) {
248 var webRequestEvent = createWebRequestEvent(WebRequestSchema.events[i]);
249 Object.defineProperty(
250 request,
251 WebRequestSchema.events[i].name,
252 {
253 get: webRequestEvent,
254 enumerable: true
255 }
256 );
257 }
258
259 this.webViewInternal.setRequestPropertyOnWebViewNode(request);
260 };
261
262 WebViewEvents.prototype.getEvents = function() {
263 var experimentalEvents = this.webViewInternal.maybeGetExperimentalEvents();
264 for (var eventName in experimentalEvents) {
265 WEB_VIEW_EVENTS[eventName] = experimentalEvents[eventName];
266 }
267 return WEB_VIEW_EVENTS;
268 };
269
270 WebViewEvents.prototype.setupEvent = function(name, info) {
271 var self = this;
272 info.evt.addListener(function(e) {
273 var details = {bubbles:true};
274 if (info.cancelable)
275 details.cancelable = true;
276 var webViewEvent = new Event(name, details);
277 $Array.forEach(info.fields, function(field) {
278 if (e[field] !== undefined) {
279 webViewEvent[field] = e[field];
280 }
281 });
282 if (info.customHandler) {
283 info.customHandler(self, e, webViewEvent);
284 return;
285 }
286 self.webViewInternal.dispatchEvent(webViewEvent);
287 }, {instanceId: self.viewInstanceId});
288
289 this.webViewInternal.setupEventProperty(name);
290 };
291
292
293 // Event handlers.
294 WebViewEvents.prototype.handleContextMenu = function(e, webViewEvent) {
295 this.webViewInternal.maybeHandleContextMenu();
296 };
297
298 WebViewEvents.prototype.handleDialogEvent = function(event, webViewEvent) {
299 var showWarningMessage = function(dialogType) {
300 var VOWELS = ['a', 'e', 'i', 'o', 'u'];
301 var WARNING_MSG_DIALOG_BLOCKED = '<webview>: %1 %2 dialog was blocked.';
302 var article = (VOWELS.indexOf(dialogType.charAt(0)) >= 0) ? 'An' : 'A';
303 var output = WARNING_MSG_DIALOG_BLOCKED.replace('%1', article);
304 output = output.replace('%2', dialogType);
305 window.console.warn(output);
306 };
307
308 var self = this;
309 var requestId = event.requestId;
310 var actionTaken = false;
311
312 var validateCall = function() {
313 var ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN = '<webview>: ' +
314 'An action has already been taken for this "dialog" event.';
315
316 if (actionTaken) {
317 throw new Error(ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN);
318 }
319 actionTaken = true;
320 };
321
322 var getInstanceId = function() {
323 return self.webViewInternal.getInstanceId();
324 };
325
326 var dialog = {
327 ok: function(user_input) {
328 validateCall();
329 user_input = user_input || '';
330 WebView.setPermission(getInstanceId(), requestId, 'allow', user_input);
331 },
332 cancel: function() {
333 validateCall();
334 WebView.setPermission(getInstanceId(), requestId, 'deny');
335 }
336 };
337 webViewEvent.dialog = dialog;
338
339 var defaultPrevented = !self.webViewInternal.dispatchEvent(webViewEvent);
340 if (actionTaken) {
341 return;
342 }
343
344 if (defaultPrevented) {
345 // Tell the JavaScript garbage collector to track lifetime of |dialog| and
346 // call back when the dialog object has been collected.
347 MessagingNatives.BindToGC(dialog, function() {
348 // Avoid showing a warning message if the decision has already been made.
349 if (actionTaken) {
350 return;
351 }
352 WebView.setPermission(
353 getInstanceId(), requestId, 'default', '', function(allowed) {
354 if (allowed) {
355 return;
356 }
357 showWarningMessage(event.messageType);
358 });
359 });
360 } else {
361 actionTaken = true;
362 // The default action is equivalent to canceling the dialog.
363 WebView.setPermission(
364 getInstanceId(), requestId, 'default', '', function(allowed) {
365 if (allowed) {
366 return;
367 }
368 showWarningMessage(event.messageType);
369 });
370 }
371 };
372
373 WebViewEvents.prototype.handleLoadAbortEvent = function(event, webViewEvent) {
374 var showWarningMessage = function(reason) {
375 var WARNING_MSG_LOAD_ABORTED = '<webview>: ' +
376 'The load has aborted with reason "%1".';
377 window.console.warn(WARNING_MSG_LOAD_ABORTED.replace('%1', reason));
378 };
379 if (this.webViewInternal.dispatchEvent(webViewEvent)) {
380 showWarningMessage(event.reason);
381 }
382 };
383
384 WebViewEvents.prototype.handleLoadCommitEvent = function(event, webViewEvent) {
385 this.webViewInternal.onLoadCommit(event.currentEntryIndex, event.entryCount,
386 event.processId, event.url,
387 event.isTopLevel);
388 this.webViewInternal.dispatchEvent(webViewEvent);
389 };
390
391 WebViewEvents.prototype.handleNewWindowEvent = function(event, webViewEvent) {
392 var ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN = '<webview>: ' +
393 'An action has already been taken for this "newwindow" event.';
394
395 var ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH = '<webview>: ' +
396 'Unable to attach the new window to the provided webview.';
397
398 var ERROR_MSG_WEBVIEW_EXPECTED = '<webview> element expected.';
399
400 var showWarningMessage = function() {
401 var WARNING_MSG_NEWWINDOW_BLOCKED = '<webview>: A new window was blocked.';
402 window.console.warn(WARNING_MSG_NEWWINDOW_BLOCKED);
403 };
404
405 var requestId = event.requestId;
406 var actionTaken = false;
407 var self = this;
408 var getInstanceId = function() {
409 return self.webViewInternal.getInstanceId();
410 };
411
412 var validateCall = function () {
413 if (actionTaken) {
414 throw new Error(ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN);
415 }
416 actionTaken = true;
417 };
418
419 var windowObj = {
420 attach: function(webview) {
421 validateCall();
422 if (!webview || !webview.tagName || webview.tagName != 'WEBVIEW')
423 throw new Error(ERROR_MSG_WEBVIEW_EXPECTED);
424 // Attach happens asynchronously to give the tagWatcher an opportunity
425 // to pick up the new webview before attach operates on it, if it hasn't
426 // been attached to the DOM already.
427 // Note: Any subsequent errors cannot be exceptions because they happen
428 // asynchronously.
429 setTimeout(function() {
430 var webViewInternal = privates(webview).internal;
431 if (event.storagePartitionId) {
432 webViewInternal.onAttach(event.storagePartitionId);
433 }
434
435 var attached =
436 webViewInternal.attachWindowAndSetUpEvents(
437 event.windowId, undefined, event.storagePartitionId);
438
439 if (!attached) {
440 window.console.error(ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH);
441 }
442 // If the object being passed into attach is not a valid <webview>
443 // then we will fail and it will be treated as if the new window
444 // was rejected. The permission API plumbing is used here to clean
445 // up the state created for the new window if attaching fails.
446 WebView.setPermission(
447 getInstanceId(), requestId, attached ? 'allow' : 'deny');
448 }, 0);
449 },
450 discard: function() {
451 validateCall();
452 WebView.setPermission(getInstanceId(), requestId, 'deny');
453 }
454 };
455 webViewEvent.window = windowObj;
456
457 var defaultPrevented = !self.webViewInternal.dispatchEvent(webViewEvent);
458 if (actionTaken) {
459 return;
460 }
461
462 if (defaultPrevented) {
463 // Make browser plugin track lifetime of |windowObj|.
464 MessagingNatives.BindToGC(windowObj, function() {
465 // Avoid showing a warning message if the decision has already been made.
466 if (actionTaken) {
467 return;
468 }
469 WebView.setPermission(
470 getInstanceId(), requestId, 'default', '', function(allowed) {
471 if (allowed) {
472 return;
473 }
474 showWarningMessage();
475 });
476 });
477 } else {
478 actionTaken = true;
479 // The default action is to discard the window.
480 WebView.setPermission(
481 getInstanceId(), requestId, 'default', '', function(allowed) {
482 if (allowed) {
483 return;
484 }
485 showWarningMessage();
486 });
487 }
488 };
489
490 WebViewEvents.prototype.getPermissionTypes = function() {
491 var permissions =
492 ['media',
493 'geolocation',
494 'pointerLock',
495 'download',
496 'loadplugin',
497 'filesystem'];
498 return permissions.concat(
499 this.webViewInternal.maybeGetExperimentalPermissions());
500 };
501
502 WebViewEvents.prototype.handlePermissionEvent =
503 function(event, webViewEvent) {
504 var ERROR_MSG_PERMISSION_ALREADY_DECIDED = '<webview>: ' +
505 'Permission has already been decided for this "permissionrequest" event.';
506
507 var showWarningMessage = function(permission) {
508 var WARNING_MSG_PERMISSION_DENIED = '<webview>: ' +
509 'The permission request for "%1" has been denied.';
510 window.console.warn(
511 WARNING_MSG_PERMISSION_DENIED.replace('%1', permission));
512 };
513
514 var requestId = event.requestId;
515 var self = this;
516 var getInstanceId = function() {
517 return self.webViewInternal.getInstanceId();
518 };
519
520 if (this.getPermissionTypes().indexOf(event.permission) < 0) {
521 // The permission type is not allowed. Trigger the default response.
522 WebView.setPermission(
523 getInstanceId(), requestId, 'default', '', function(allowed) {
524 if (allowed) {
525 return;
526 }
527 showWarningMessage(event.permission);
528 });
529 return;
530 }
531
532 var decisionMade = false;
533 var validateCall = function() {
534 if (decisionMade) {
535 throw new Error(ERROR_MSG_PERMISSION_ALREADY_DECIDED);
536 }
537 decisionMade = true;
538 };
539
540 // Construct the event.request object.
541 var request = {
542 allow: function() {
543 validateCall();
544 WebView.setPermission(getInstanceId(), requestId, 'allow');
545 },
546 deny: function() {
547 validateCall();
548 WebView.setPermission(getInstanceId(), requestId, 'deny');
549 }
550 };
551 webViewEvent.request = request;
552
553 var defaultPrevented = !self.webViewInternal.dispatchEvent(webViewEvent);
554 if (decisionMade) {
555 return;
556 }
557
558 if (defaultPrevented) {
559 // Make browser plugin track lifetime of |request|.
560 MessagingNatives.BindToGC(request, function() {
561 // Avoid showing a warning message if the decision has already been made.
562 if (decisionMade) {
563 return;
564 }
565 WebView.setPermission(
566 getInstanceId(), requestId, 'default', '', function(allowed) {
567 if (allowed) {
568 return;
569 }
570 showWarningMessage(event.permission);
571 });
572 });
573 } else {
574 decisionMade = true;
575 WebView.setPermission(
576 getInstanceId(), requestId, 'default', '', function(allowed) {
577 if (allowed) {
578 return;
579 }
580 showWarningMessage(event.permission);
581 });
582 }
583 };
584
585 WebViewEvents.prototype.handleSizeChangedEvent = function(
586 event, webViewEvent) {
587 this.webViewInternal.onSizeChanged(webViewEvent.newWidth,
588 webViewEvent.newHeight);
589 this.webViewInternal.dispatchEvent(webViewEvent);
590 };
591
592 exports.WebViewEvents = WebViewEvents;
593 exports.CreateEvent = CreateEvent;
OLDNEW
« no previous file with comments | « chrome/renderer/resources/extensions/web_view.js ('k') | chrome/renderer/resources/extensions/web_view_experimental.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698