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

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