NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / renderer / resources / extensions / web_view.js
blob3bf84b451c50f8c9781dc96f3a8ef8155743b4ac
1 // Copyright (c) 2012 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 // This module implements Webview (<webview>) as a custom element that wraps a
6 // BrowserPlugin object element. The object element is hidden within
7 // the shadow DOM of the Webview element.
9 var DocumentNatives = requireNative('document_natives');
10 var EventBindings = require('event_bindings');
11 var IdGenerator = requireNative('id_generator');
12 var MessagingNatives = requireNative('messaging_natives');
13 var WebRequestEvent = require('webRequestInternal').WebRequestEvent;
14 var WebRequestSchema =
15 requireNative('schema_registry').GetSchema('webRequest');
16 var DeclarativeWebRequestSchema =
17 requireNative('schema_registry').GetSchema('declarativeWebRequest');
18 var WebView = require('binding').Binding.create('webview').generate();
20 var WEB_VIEW_ATTRIBUTE_MAXHEIGHT = 'maxheight';
21 var WEB_VIEW_ATTRIBUTE_MAXWIDTH = 'maxwidth';
22 var WEB_VIEW_ATTRIBUTE_MINHEIGHT = 'minheight';
23 var WEB_VIEW_ATTRIBUTE_MINWIDTH = 'minwidth';
25 /** @type {Array.<string>} */
26 var WEB_VIEW_ATTRIBUTES = [
27 'allowtransparency',
28 'autosize',
29 'name',
30 'partition',
31 WEB_VIEW_ATTRIBUTE_MINHEIGHT,
32 WEB_VIEW_ATTRIBUTE_MINWIDTH,
33 WEB_VIEW_ATTRIBUTE_MAXHEIGHT,
34 WEB_VIEW_ATTRIBUTE_MAXWIDTH
37 var CreateEvent = function(name) {
38 var eventOpts = {supportsListeners: true, supportsFilters: true};
39 return new EventBindings.Event(name, undefined, eventOpts);
42 // WEB_VIEW_EVENTS is a map of stable <webview> DOM event names to their
43 // associated extension event descriptor objects.
44 // An event listener will be attached to the extension event |evt| specified in
45 // the descriptor.
46 // |fields| specifies the public-facing fields in the DOM event that are
47 // accessible to <webview> developers.
48 // |customHandler| allows a handler function to be called each time an extension
49 // event is caught by its event listener. The DOM event should be dispatched
50 // within this handler function. With no handler function, the DOM event
51 // will be dispatched by default each time the extension event is caught.
52 // |cancelable| (default: false) specifies whether the event's default
53 // behavior can be canceled. If the default action associated with the event
54 // is prevented, then its dispatch function will return false in its event
55 // handler. The event must have a custom handler for this to be meaningful.
56 var WEB_VIEW_EVENTS = {
57 'close': {
58 evt: CreateEvent('webview.onClose'),
59 fields: []
61 'consolemessage': {
62 evt: CreateEvent('webview.onConsoleMessage'),
63 fields: ['level', 'message', 'line', 'sourceId']
65 'contentload': {
66 evt: CreateEvent('webview.onContentLoad'),
67 fields: []
69 'exit': {
70 evt: CreateEvent('webview.onExit'),
71 fields: ['processId', 'reason']
73 'loadabort': {
74 cancelable: true,
75 customHandler: function(webViewInternal, event, webViewEvent) {
76 webViewInternal.handleLoadAbortEvent(event, webViewEvent);
78 evt: CreateEvent('webview.onLoadAbort'),
79 fields: ['url', 'isTopLevel', 'reason']
81 'loadcommit': {
82 customHandler: function(webViewInternal, event, webViewEvent) {
83 webViewInternal.handleLoadCommitEvent(event, webViewEvent);
85 evt: CreateEvent('webview.onLoadCommit'),
86 fields: ['url', 'isTopLevel']
88 'loadprogress': {
89 evt: CreateEvent('webview.onLoadProgress'),
90 fields: ['url', 'progress']
92 'loadredirect': {
93 evt: CreateEvent('webview.onLoadRedirect'),
94 fields: ['isTopLevel', 'oldUrl', 'newUrl']
96 'loadstart': {
97 evt: CreateEvent('webview.onLoadStart'),
98 fields: ['url', 'isTopLevel']
100 'loadstop': {
101 evt: CreateEvent('webview.onLoadStop'),
102 fields: []
104 'newwindow': {
105 cancelable: true,
106 customHandler: function(webViewInternal, event, webViewEvent) {
107 webViewInternal.handleNewWindowEvent(event, webViewEvent);
109 evt: CreateEvent('webview.onNewWindow'),
110 fields: [
111 'initialHeight',
112 'initialWidth',
113 'targetUrl',
114 'windowOpenDisposition',
115 'name'
118 'permissionrequest': {
119 cancelable: true,
120 customHandler: function(webViewInternal, event, webViewEvent) {
121 webViewInternal.handlePermissionEvent(event, webViewEvent);
123 evt: CreateEvent('webview.onPermissionRequest'),
124 fields: [
125 'identifier',
126 'lastUnlockedBySelf',
127 'name',
128 'permission',
129 'requestMethod',
130 'url',
131 'userGesture'
134 'responsive': {
135 evt: CreateEvent('webview.onResponsive'),
136 fields: ['processId']
138 'sizechanged': {
139 evt: CreateEvent('webview.onSizeChanged'),
140 customHandler: function(webViewInternal, event, webViewEvent) {
141 webViewInternal.handleSizeChangedEvent(event, webViewEvent);
143 fields: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth']
145 'unresponsive': {
146 evt: CreateEvent('webview.onUnresponsive'),
147 fields: ['processId']
151 // Implemented when the experimental API is available.
152 WebViewInternal.maybeRegisterExperimentalAPIs = function(proto) {}
155 * @constructor
157 function WebViewInternal(webviewNode) {
158 privates(webviewNode).internal = this;
159 this.webviewNode = webviewNode;
160 this.browserPluginNode = this.createBrowserPluginNode();
161 var shadowRoot = this.webviewNode.webkitCreateShadowRoot();
162 shadowRoot.appendChild(this.browserPluginNode);
164 this.setupWebviewNodeAttributes();
165 this.setupFocusPropagation();
166 this.setupWebviewNodeProperties();
167 this.setupWebviewNodeEvents();
171 * @private
173 WebViewInternal.prototype.createBrowserPluginNode = function() {
174 // We create BrowserPlugin as a custom element in order to observe changes
175 // to attributes synchronously.
176 var browserPluginNode = new WebViewInternal.BrowserPlugin();
177 privates(browserPluginNode).internal = this;
179 var ALL_ATTRIBUTES = WEB_VIEW_ATTRIBUTES.concat(['src']);
180 $Array.forEach(ALL_ATTRIBUTES, function(attributeName) {
181 // Only copy attributes that have been assigned values, rather than copying
182 // a series of undefined attributes to BrowserPlugin.
183 if (this.webviewNode.hasAttribute(attributeName)) {
184 browserPluginNode.setAttribute(
185 attributeName, this.webviewNode.getAttribute(attributeName));
186 } else if (this.webviewNode[attributeName]){
187 // Reading property using has/getAttribute does not work on
188 // document.DOMContentLoaded event (but works on
189 // window.DOMContentLoaded event).
190 // So copy from property if copying from attribute fails.
191 browserPluginNode.setAttribute(
192 attributeName, this.webviewNode[attributeName]);
194 }, this);
196 return browserPluginNode;
200 * @private
202 WebViewInternal.prototype.setupFocusPropagation = function() {
203 if (!this.webviewNode.hasAttribute('tabIndex')) {
204 // <webview> needs a tabIndex in order to be focusable.
205 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
206 // to allow <webview> to be focusable.
207 // See http://crbug.com/231664.
208 this.webviewNode.setAttribute('tabIndex', -1);
210 var self = this;
211 this.webviewNode.addEventListener('focus', function(e) {
212 // Focus the BrowserPlugin when the <webview> takes focus.
213 self.browserPluginNode.focus();
215 this.webviewNode.addEventListener('blur', function(e) {
216 // Blur the BrowserPlugin when the <webview> loses focus.
217 self.browserPluginNode.blur();
222 * @private
224 WebViewInternal.prototype.canGoBack = function() {
225 return this.entryCount > 1 && this.currentEntryIndex > 0;
229 * @private
231 WebViewInternal.prototype.canGoForward = function() {
232 return this.currentEntryIndex >= 0 &&
233 this.currentEntryIndex < (this.entryCount - 1);
237 * @private
239 WebViewInternal.prototype.getProcessId = function() {
240 return this.processId;
244 * @private
246 WebViewInternal.prototype.go = function(relativeIndex) {
247 if (!this.instanceId) {
248 return;
250 WebView.go(this.instanceId, relativeIndex);
254 * @private
256 WebViewInternal.prototype.reload = function() {
257 if (!this.instanceId) {
258 return;
260 WebView.reload(this.instanceId);
264 * @private
266 WebViewInternal.prototype.stop = function() {
267 if (!this.instanceId) {
268 return;
270 WebView.stop(this.instanceId);
274 * @private
276 WebViewInternal.prototype.terminate = function() {
277 if (!this.instanceId) {
278 return;
280 WebView.terminate(this.instanceId);
284 * @private
286 WebViewInternal.prototype.validateExecuteCodeCall = function() {
287 var ERROR_MSG_CANNOT_INJECT_SCRIPT = '<webview>: ' +
288 'Script cannot be injected into content until the page has loaded.';
289 if (!this.instanceId) {
290 throw new Error(ERROR_MSG_CANNOT_INJECT_SCRIPT);
295 * @private
297 WebViewInternal.prototype.executeScript = function(var_args) {
298 this.validateExecuteCodeCall();
299 var args = $Array.concat([this.instanceId], $Array.slice(arguments));
300 $Function.apply(WebView.executeScript, null, args);
304 * @private
306 WebViewInternal.prototype.insertCSS = function(var_args) {
307 this.validateExecuteCodeCall();
308 var args = $Array.concat([this.instanceId], $Array.slice(arguments));
309 $Function.apply(WebView.insertCSS, null, args);
313 * @private
315 WebViewInternal.prototype.setupWebviewNodeProperties = function() {
316 var ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE = '<webview>: ' +
317 'contentWindow is not available at this time. It will become available ' +
318 'when the page has finished loading.';
320 var self = this;
321 var browserPluginNode = this.browserPluginNode;
322 // Expose getters and setters for the attributes.
323 $Array.forEach(WEB_VIEW_ATTRIBUTES, function(attributeName) {
324 Object.defineProperty(this.webviewNode, attributeName, {
325 get: function() {
326 if (browserPluginNode.hasOwnProperty(attributeName)) {
327 return browserPluginNode[attributeName];
328 } else {
329 return browserPluginNode.getAttribute(attributeName);
332 set: function(value) {
333 if (browserPluginNode.hasOwnProperty(attributeName)) {
334 // Give the BrowserPlugin first stab at the attribute so that it can
335 // throw an exception if there is a problem. This attribute will then
336 // be propagated back to the <webview>.
337 browserPluginNode[attributeName] = value;
338 } else {
339 browserPluginNode.setAttribute(attributeName, value);
342 enumerable: true
344 }, this);
346 // <webview> src does not quite behave the same as BrowserPlugin src, and so
347 // we don't simply keep the two in sync.
348 this.src = this.webviewNode.getAttribute('src');
349 Object.defineProperty(this.webviewNode, 'src', {
350 get: function() {
351 return self.src;
353 set: function(value) {
354 self.webviewNode.setAttribute('src', value);
356 // No setter.
357 enumerable: true
360 // We cannot use {writable: true} property descriptor because we want a
361 // dynamic getter value.
362 Object.defineProperty(this.webviewNode, 'contentWindow', {
363 get: function() {
364 if (browserPluginNode.contentWindow)
365 return browserPluginNode.contentWindow;
366 window.console.error(ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE);
368 // No setter.
369 enumerable: true
374 * @private
376 WebViewInternal.prototype.setupWebviewNodeAttributes = function() {
377 this.setupWebViewSrcAttributeMutationObserver();
381 * @private
383 WebViewInternal.prototype.setupWebViewSrcAttributeMutationObserver =
384 function() {
385 // The purpose of this mutation observer is to catch assignment to the src
386 // attribute without any changes to its value. This is useful in the case
387 // where the webview guest has crashed and navigating to the same address
388 // spawns off a new process.
389 var self = this;
390 this.srcObserver = new MutationObserver(function(mutations) {
391 $Array.forEach(mutations, function(mutation) {
392 var oldValue = mutation.oldValue;
393 var newValue = self.webviewNode.getAttribute(mutation.attributeName);
394 if (oldValue != newValue) {
395 return;
397 self.handleWebviewAttributeMutation(
398 mutation.attributeName, oldValue, newValue);
401 var params = {
402 attributes: true,
403 attributeOldValue: true,
404 attributeFilter: ['src']
406 this.srcObserver.observe(this.webviewNode, params);
410 * @private
412 WebViewInternal.prototype.handleWebviewAttributeMutation =
413 function(name, oldValue, newValue) {
414 // This observer monitors mutations to attributes of the <webview> and
415 // updates the BrowserPlugin properties accordingly. In turn, updating
416 // a BrowserPlugin property will update the corresponding BrowserPlugin
417 // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
418 // details.
419 if (name == 'src') {
420 // We treat null attribute (attribute removed) and the empty string as
421 // one case.
422 oldValue = oldValue || '';
423 newValue = newValue || '';
424 // Once we have navigated, we don't allow clearing the src attribute.
425 // Once <webview> enters a navigated state, it cannot be return back to a
426 // placeholder state.
427 if (newValue == '' && oldValue != '') {
428 // src attribute changes normally initiate a navigation. We suppress
429 // the next src attribute handler call to avoid reloading the page
430 // on every guest-initiated navigation.
431 this.ignoreNextSrcAttributeChange = true;
432 this.webviewNode.setAttribute('src', oldValue);
433 return;
435 this.src = newValue;
436 if (this.ignoreNextSrcAttributeChange) {
437 // Don't allow the src mutation observer to see this change.
438 this.srcObserver.takeRecords();
439 this.ignoreNextSrcAttributeChange = false;
440 return;
443 if (this.browserPluginNode.hasOwnProperty(name)) {
444 this.browserPluginNode[name] = newValue;
445 } else {
446 this.browserPluginNode.setAttribute(name, newValue);
451 * @private
453 WebViewInternal.prototype.handleBrowserPluginAttributeMutation =
454 function(name, newValue) {
455 // This observer monitors mutations to attributes of the BrowserPlugin and
456 // updates the <webview> attributes accordingly.
457 // |newValue| is null if the attribute |name| has been removed.
458 if (newValue != null) {
459 // Update the <webview> attribute to match the BrowserPlugin attribute.
460 // Note: Calling setAttribute on <webview> will trigger its mutation
461 // observer which will then propagate that attribute to BrowserPlugin. In
462 // cases where we permit assigning a BrowserPlugin attribute the same value
463 // again (such as navigation when crashed), this could end up in an infinite
464 // loop. Thus, we avoid this loop by only updating the <webview> attribute
465 // if the BrowserPlugin attributes differs from it.
466 if (newValue != this.webviewNode.getAttribute(name)) {
467 this.webviewNode.setAttribute(name, newValue);
469 } else {
470 // If an attribute is removed from the BrowserPlugin, then remove it
471 // from the <webview> as well.
472 this.webviewNode.removeAttribute(name);
477 * @private
479 WebViewInternal.prototype.getEvents = function() {
480 var experimentalEvents = this.maybeGetExperimentalEvents();
481 for (var eventName in experimentalEvents) {
482 WEB_VIEW_EVENTS[eventName] = experimentalEvents[eventName];
484 return WEB_VIEW_EVENTS;
487 WebViewInternal.prototype.handleSizeChangedEvent =
488 function(event, webViewEvent) {
489 var node = this.webviewNode;
491 var width = node.offsetWidth;
492 var height = node.offsetHeight;
494 // Check the current bounds to make sure we do not resize <webview>
495 // outside of current constraints.
496 var maxWidth;
497 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH) &&
498 node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]) {
499 maxWidth = node[WEB_VIEW_ATTRIBUTE_MAXWIDTH];
500 } else {
501 maxWidth = width;
504 var minWidth;
505 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINWIDTH) &&
506 node[WEB_VIEW_ATTRIBUTE_MINWIDTH]) {
507 minWidth = node[WEB_VIEW_ATTRIBUTE_MINWIDTH];
508 } else {
509 minWidth = width;
511 if (minWidth > maxWidth) {
512 minWidth = maxWidth;
515 var maxHeight;
516 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXHEIGHT) &&
517 node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT]) {
518 maxHeight = node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT];
519 } else {
520 maxHeight = height;
522 var minHeight;
523 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT) &&
524 node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]) {
525 minHeight = node[WEB_VIEW_ATTRIBUTE_MINHEIGHT];
526 } else {
527 minHeight = height;
529 if (minHeight > maxHeight) {
530 minHeight = maxHeight;
533 if (webViewEvent.newWidth >= minWidth &&
534 webViewEvent.newWidth <= maxWidth &&
535 webViewEvent.newHeight >= minHeight &&
536 webViewEvent.newHeight <= maxHeight) {
537 node.style.width = webViewEvent.newWidth + 'px';
538 node.style.height = webViewEvent.newHeight + 'px';
540 node.dispatchEvent(webViewEvent);
544 * @private
546 WebViewInternal.prototype.setupWebviewNodeEvents = function() {
547 var self = this;
548 this.viewInstanceId = IdGenerator.GetNextId();
549 var onInstanceIdAllocated = function(e) {
550 var detail = e.detail ? JSON.parse(e.detail) : {};
551 self.instanceId = detail.windowId;
552 var params = {
553 'api': 'webview',
554 'instanceId': self.viewInstanceId
556 if (self.userAgentOverride) {
557 params['userAgentOverride'] = self.userAgentOverride;
559 self.browserPluginNode['-internal-attach'](params);
561 var events = self.getEvents();
562 for (var eventName in events) {
563 self.setupEvent(eventName, events[eventName]);
566 this.browserPluginNode.addEventListener('-internal-instanceid-allocated',
567 onInstanceIdAllocated);
568 this.setupWebRequestEvents();
570 this.on = {};
571 var events = self.getEvents();
572 for (var eventName in events) {
573 this.setupEventProperty(eventName);
578 * @private
580 WebViewInternal.prototype.setupEvent = function(eventName, eventInfo) {
581 var self = this;
582 var webviewNode = this.webviewNode;
583 eventInfo.evt.addListener(function(event) {
584 var details = {bubbles:true};
585 if (eventInfo.cancelable)
586 details.cancelable = true;
587 var webViewEvent = new Event(eventName, details);
588 $Array.forEach(eventInfo.fields, function(field) {
589 if (event[field] !== undefined) {
590 webViewEvent[field] = event[field];
593 if (eventInfo.customHandler) {
594 eventInfo.customHandler(self, event, webViewEvent);
595 return;
597 webviewNode.dispatchEvent(webViewEvent);
598 }, {instanceId: self.instanceId});
602 * Adds an 'on<event>' property on the webview, which can be used to set/unset
603 * an event handler.
604 * @private
606 WebViewInternal.prototype.setupEventProperty = function(eventName) {
607 var propertyName = 'on' + eventName.toLowerCase();
608 var self = this;
609 var webviewNode = this.webviewNode;
610 Object.defineProperty(webviewNode, propertyName, {
611 get: function() {
612 return self.on[propertyName];
614 set: function(value) {
615 if (self.on[propertyName])
616 webviewNode.removeEventListener(eventName, self.on[propertyName]);
617 self.on[propertyName] = value;
618 if (value)
619 webviewNode.addEventListener(eventName, value);
621 enumerable: true
626 * @private
628 WebViewInternal.prototype.getPermissionTypes = function() {
629 var permissions =
630 ['media', 'geolocation', 'pointerLock', 'download', 'loadplugin'];
631 return permissions.concat(this.maybeGetExperimentalPermissions());
635 * @private
637 WebViewInternal.prototype.handleLoadAbortEvent =
638 function(event, webViewEvent) {
639 var showWarningMessage = function(reason) {
640 var WARNING_MSG_LOAD_ABORTED = '<webview>: ' +
641 'The load has aborted with reason "%1".';
642 window.console.warn(WARNING_MSG_LOAD_ABORTED.replace('%1', reason));
644 if (this.webviewNode.dispatchEvent(webViewEvent)) {
645 showWarningMessage(event.reason);
650 * @private
652 WebViewInternal.prototype.handleLoadCommitEvent =
653 function(event, webViewEvent) {
654 this.currentEntryIndex = event.currentEntryIndex;
655 this.entryCount = event.entryCount;
656 this.processId = event.processId;
657 var oldValue = this.webviewNode.getAttribute('src');
658 var newValue = event.url;
659 if (event.isTopLevel && (oldValue != newValue)) {
660 // Touching the src attribute triggers a navigation. To avoid
661 // triggering a page reload on every guest-initiated navigation,
662 // we use the flag ignoreNextSrcAttributeChange here.
663 this.ignoreNextSrcAttributeChange = true;
664 this.webviewNode.setAttribute('src', newValue);
666 this.webviewNode.dispatchEvent(webViewEvent);
670 * @private
672 WebViewInternal.prototype.handleNewWindowEvent =
673 function(event, webViewEvent) {
674 var ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN = '<webview>: ' +
675 'An action has already been taken for this "newwindow" event.';
677 var ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH = '<webview>: ' +
678 'Unable to attach the new window to the provided webview.';
680 var ERROR_MSG_WEBVIEW_EXPECTED = '<webview> element expected.';
682 var showWarningMessage = function() {
683 var WARNING_MSG_NEWWINDOW_BLOCKED = '<webview>: A new window was blocked.';
684 window.console.warn(WARNING_MSG_NEWWINDOW_BLOCKED);
687 var self = this;
688 var browserPluginNode = this.browserPluginNode;
689 var webviewNode = this.webviewNode;
691 var requestId = event.requestId;
692 var actionTaken = false;
694 var validateCall = function () {
695 if (actionTaken) {
696 throw new Error(ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN);
698 actionTaken = true;
701 var windowObj = {
702 attach: function(webview) {
703 validateCall();
704 if (!webview)
705 throw new Error(ERROR_MSG_WEBVIEW_EXPECTED);
706 // Attach happens asynchronously to give the tagWatcher an opportunity
707 // to pick up the new webview before attach operates on it, if it hasn't
708 // been attached to the DOM already.
709 // Note: Any subsequent errors cannot be exceptions because they happen
710 // asynchronously.
711 setTimeout(function() {
712 var attached =
713 browserPluginNode['-internal-attachWindowTo'](webview,
714 event.windowId);
715 if (!attached) {
716 window.console.error(ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH);
718 // If the object being passed into attach is not a valid <webview>
719 // then we will fail and it will be treated as if the new window
720 // was rejected. The permission API plumbing is used here to clean
721 // up the state created for the new window if attaching fails.
722 WebView.setPermission(
723 self.instanceId, requestId, attached ? 'allow' : 'deny');
724 }, 0);
726 discard: function() {
727 validateCall();
728 WebView.setPermission(self.instanceId, requestId, 'deny');
731 webViewEvent.window = windowObj;
733 var defaultPrevented = !webviewNode.dispatchEvent(webViewEvent);
734 if (actionTaken) {
735 return;
738 if (defaultPrevented) {
739 // Make browser plugin track lifetime of |windowObj|.
740 MessagingNatives.BindToGC(windowObj, function() {
741 // Avoid showing a warning message if the decision has already been made.
742 if (actionTaken) {
743 return;
745 WebView.setPermission(
746 self.instanceId, requestId, 'default', '', function(allowed) {
747 if (allowed) {
748 return;
750 showWarningMessage();
753 } else {
754 actionTaken = true;
755 // The default action is to discard the window.
756 WebView.setPermission(
757 self.instanceId, requestId, 'default', '', function(allowed) {
758 if (allowed) {
759 return;
761 showWarningMessage();
766 WebViewInternal.prototype.handlePermissionEvent =
767 function(event, webViewEvent) {
768 var ERROR_MSG_PERMISSION_ALREADY_DECIDED = '<webview>: ' +
769 'Permission has already been decided for this "permissionrequest" event.';
771 var showWarningMessage = function(permission) {
772 var WARNING_MSG_PERMISSION_DENIED = '<webview>: ' +
773 'The permission request for "%1" has been denied.';
774 window.console.warn(
775 WARNING_MSG_PERMISSION_DENIED.replace('%1', permission));
778 var requestId = event.requestId;
779 var self = this;
781 if (this.getPermissionTypes().indexOf(event.permission) < 0) {
782 // The permission type is not allowed. Trigger the default response.
783 WebView.setPermission(
784 self.instanceId, requestId, 'default', '', function(allowed) {
785 if (allowed) {
786 return;
788 showWarningMessage(event.permission);
790 return;
793 var browserPluginNode = this.browserPluginNode;
794 var webviewNode = this.webviewNode;
796 var decisionMade = false;
798 var validateCall = function() {
799 if (decisionMade) {
800 throw new Error(ERROR_MSG_PERMISSION_ALREADY_DECIDED);
802 decisionMade = true;
805 // Construct the event.request object.
806 var request = {
807 allow: function() {
808 validateCall();
809 WebView.setPermission(self.instanceId, requestId, 'allow');
811 deny: function() {
812 validateCall();
813 WebView.setPermission(self.instanceId, requestId, 'deny');
816 webViewEvent.request = request;
818 var defaultPrevented = !webviewNode.dispatchEvent(webViewEvent);
819 if (decisionMade) {
820 return;
823 if (defaultPrevented) {
824 // Make browser plugin track lifetime of |request|.
825 MessagingNatives.BindToGC(request, function() {
826 // Avoid showing a warning message if the decision has already been made.
827 if (decisionMade) {
828 return;
830 WebView.setPermission(
831 self.instanceId, requestId, 'default', '', function(allowed) {
832 if (allowed) {
833 return;
835 showWarningMessage(event.permission);
838 } else {
839 decisionMade = true;
840 WebView.setPermission(
841 self.instanceId, requestId, 'default', '', function(allowed) {
842 if (allowed) {
843 return;
845 showWarningMessage(event.permission);
851 * @private
853 WebViewInternal.prototype.setupWebRequestEvents = function() {
854 var self = this;
855 var request = {};
856 var createWebRequestEvent = function(webRequestEvent) {
857 return function() {
858 if (!self[webRequestEvent.name]) {
859 self[webRequestEvent.name] =
860 new WebRequestEvent(
861 'webview.' + webRequestEvent.name,
862 webRequestEvent.parameters,
863 webRequestEvent.extraParameters, webRequestEvent.options,
864 self.viewInstanceId);
866 return self[webRequestEvent.name];
870 for (var i = 0; i < DeclarativeWebRequestSchema.events.length; ++i) {
871 var eventSchema = DeclarativeWebRequestSchema.events[i];
872 var webRequestEvent = createWebRequestEvent(eventSchema);
873 this.maybeAttachWebRequestEventToObject(request,
874 eventSchema.name,
875 webRequestEvent);
878 // Populate the WebRequest events from the API definition.
879 for (var i = 0; i < WebRequestSchema.events.length; ++i) {
880 var webRequestEvent = createWebRequestEvent(WebRequestSchema.events[i]);
881 Object.defineProperty(
882 request,
883 WebRequestSchema.events[i].name,
885 get: webRequestEvent,
886 enumerable: true
889 this.maybeAttachWebRequestEventToObject(this.webviewNode,
890 WebRequestSchema.events[i].name,
891 webRequestEvent);
893 Object.defineProperty(
894 this.webviewNode,
895 'request',
897 value: request,
898 enumerable: true,
899 writable: false
904 /** @private */
905 WebViewInternal.prototype.getUserAgent = function() {
906 return this.userAgentOverride || navigator.userAgent;
909 /** @private */
910 WebViewInternal.prototype.isUserAgentOverridden = function() {
911 return !!this.userAgentOverride &&
912 this.userAgentOverride != navigator.userAgent;
915 /** @private */
916 WebViewInternal.prototype.setUserAgentOverride = function(userAgentOverride) {
917 this.userAgentOverride = userAgentOverride;
918 if (!this.instanceId) {
919 // If we are not attached yet, then we will pick up the user agent on
920 // attachment.
921 return;
923 WebView.overrideUserAgent(this.instanceId, userAgentOverride);
926 // Registers browser plugin <object> custom element.
927 function registerBrowserPluginElement() {
928 var proto = Object.create(HTMLObjectElement.prototype);
930 proto.createdCallback = function() {
931 this.setAttribute('type', 'application/browser-plugin');
932 // The <object> node fills in the <webview> container.
933 this.style.width = '100%';
934 this.style.height = '100%';
937 proto.attributeChangedCallback = function(name, oldValue, newValue) {
938 var internal = privates(this).internal;
939 if (!internal) {
940 return;
942 internal.handleBrowserPluginAttributeMutation(name, newValue);
945 proto.attachedCallback = function() {
946 // Load the plugin immediately.
947 var unused = this.nonExistentAttribute;
950 WebViewInternal.BrowserPlugin =
951 DocumentNatives.RegisterElement('browser-plugin', {extends: 'object',
952 prototype: proto});
954 delete proto.createdCallback;
955 delete proto.attachedCallback;
956 delete proto.detachedCallback;
957 delete proto.attributeChangedCallback;
960 // Registers <webview> custom element.
961 function registerWebViewElement() {
962 var proto = Object.create(HTMLElement.prototype);
964 proto.createdCallback = function() {
965 new WebViewInternal(this);
968 proto.attributeChangedCallback = function(name, oldValue, newValue) {
969 var internal = privates(this).internal;
970 if (!internal) {
971 return;
973 internal.handleWebviewAttributeMutation(name, oldValue, newValue);
976 proto.back = function() {
977 this.go(-1);
980 proto.forward = function() {
981 this.go(1);
984 proto.canGoBack = function() {
985 return privates(this).internal.canGoBack();
988 proto.canGoForward = function() {
989 return privates(this).internal.canGoForward();
992 proto.getProcessId = function() {
993 return privates(this).internal.getProcessId();
996 proto.go = function(relativeIndex) {
997 privates(this).internal.go(relativeIndex);
1000 proto.reload = function() {
1001 privates(this).internal.reload();
1004 proto.stop = function() {
1005 privates(this).internal.stop();
1008 proto.terminate = function() {
1009 privates(this).internal.terminate();
1012 proto.executeScript = function(var_args) {
1013 var internal = privates(this).internal;
1014 $Function.apply(internal.executeScript, internal, arguments);
1017 proto.insertCSS = function(var_args) {
1018 var internal = privates(this).internal;
1019 $Function.apply(internal.insertCSS, internal, arguments);
1022 proto.getUserAgent = function() {
1023 return privates(this).internal.getUserAgent();
1026 proto.isUserAgentOverridden = function() {
1027 return privates(this).internal.isUserAgentOverridden();
1030 proto.setUserAgentOverride = function(userAgentOverride) {
1031 privates(this).internal.setUserAgentOverride(userAgentOverride);
1033 WebViewInternal.maybeRegisterExperimentalAPIs(proto);
1035 window.WebView =
1036 DocumentNatives.RegisterElement('webview', {prototype: proto});
1038 // Delete the callbacks so developers cannot call them and produce unexpected
1039 // behavior.
1040 delete proto.createdCallback;
1041 delete proto.attachedCallback;
1042 delete proto.detachedCallback;
1043 delete proto.attributeChangedCallback;
1046 var useCapture = true;
1047 window.addEventListener('readystatechange', function listener(event) {
1048 if (document.readyState == 'loading')
1049 return;
1051 registerBrowserPluginElement();
1052 registerWebViewElement();
1053 window.removeEventListener(event.type, listener, useCapture);
1054 }, useCapture);
1057 * Implemented when the experimental API is available.
1058 * @private
1060 WebViewInternal.prototype.maybeGetExperimentalEvents = function() {};
1063 * Implemented when the experimental API is available.
1064 * @private
1066 WebViewInternal.prototype.maybeAttachWebRequestEventToObject = function() {};
1069 * Implemented when the experimental API is available.
1070 * @private
1072 WebViewInternal.prototype.maybeGetExperimentalPermissions = function() {
1073 return [];
1076 exports.WebView = WebView;
1077 exports.WebViewInternal = WebViewInternal;
1078 exports.CreateEvent = CreateEvent;