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

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

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

Powered by Google App Engine
This is Rietveld 408576698