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('webview').WebView
;
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
= [
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
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
= {
58 evt
: CreateEvent('webview.onClose'),
62 evt
: CreateEvent('webview.onConsoleMessage'),
63 fields
: ['level', 'message', 'line', 'sourceId']
66 evt
: CreateEvent('webview.onContentLoad'),
71 customHandler: function(webViewInternal
, event
, webViewEvent
) {
72 webViewInternal
.handleDialogEvent(event
, webViewEvent
);
74 evt
: CreateEvent('webview.onDialog'),
75 fields
: ['defaultPromptText', 'messageText', 'messageType', 'url']
78 evt
: CreateEvent('webview.onExit'),
79 fields
: ['processId', 'reason']
83 customHandler: function(webViewInternal
, event
, webViewEvent
) {
84 webViewInternal
.handleLoadAbortEvent(event
, webViewEvent
);
86 evt
: CreateEvent('webview.onLoadAbort'),
87 fields
: ['url', 'isTopLevel', 'reason']
90 customHandler: function(webViewInternal
, event
, webViewEvent
) {
91 webViewInternal
.handleLoadCommitEvent(event
, webViewEvent
);
93 evt
: CreateEvent('webview.onLoadCommit'),
94 fields
: ['url', 'isTopLevel']
97 evt
: CreateEvent('webview.onLoadProgress'),
98 fields
: ['url', 'progress']
101 evt
: CreateEvent('webview.onLoadRedirect'),
102 fields
: ['isTopLevel', 'oldUrl', 'newUrl']
105 evt
: CreateEvent('webview.onLoadStart'),
106 fields
: ['url', 'isTopLevel']
109 evt
: CreateEvent('webview.onLoadStop'),
114 customHandler: function(webViewInternal
, event
, webViewEvent
) {
115 webViewInternal
.handleNewWindowEvent(event
, webViewEvent
);
117 evt
: CreateEvent('webview.onNewWindow'),
122 'windowOpenDisposition',
126 'permissionrequest': {
128 customHandler: function(webViewInternal
, event
, webViewEvent
) {
129 webViewInternal
.handlePermissionEvent(event
, webViewEvent
);
131 evt
: CreateEvent('webview.onPermissionRequest'),
134 'lastUnlockedBySelf',
143 evt
: CreateEvent('webview.onResponsive'),
144 fields
: ['processId']
147 evt
: CreateEvent('webview.onSizeChanged'),
148 customHandler: function(webViewInternal
, event
, webViewEvent
) {
149 webViewInternal
.handleSizeChangedEvent(event
, webViewEvent
);
151 fields
: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth']
154 evt
: CreateEvent('webview.onUnresponsive'),
155 fields
: ['processId']
159 // Implemented when the experimental API is available.
160 WebViewInternal
.maybeRegisterExperimentalAPIs = function(proto
) {}
165 function WebViewInternal(webviewNode
) {
166 privates(webviewNode
).internal = this;
167 this.webviewNode
= webviewNode
;
168 this.attached
= false;
169 this.browserPluginNode
= this.createBrowserPluginNode();
170 var shadowRoot
= this.webviewNode
.createShadowRoot();
171 shadowRoot
.appendChild(this.browserPluginNode
);
173 this.setupWebviewNodeAttributes();
174 this.setupFocusPropagation();
175 this.setupWebviewNodeProperties();
176 this.setupWebviewNodeEvents();
182 WebViewInternal
.prototype.createBrowserPluginNode = function() {
183 // We create BrowserPlugin as a custom element in order to observe changes
184 // to attributes synchronously.
185 var browserPluginNode
= new WebViewInternal
.BrowserPlugin();
186 privates(browserPluginNode
).internal = this;
188 var ALL_ATTRIBUTES
= WEB_VIEW_ATTRIBUTES
.concat(['src']);
189 $Array
.forEach(ALL_ATTRIBUTES
, function(attributeName
) {
190 // Only copy attributes that have been assigned values, rather than copying
191 // a series of undefined attributes to BrowserPlugin.
192 if (this.webviewNode
.hasAttribute(attributeName
)) {
193 browserPluginNode
.setAttribute(
194 attributeName
, this.webviewNode
.getAttribute(attributeName
));
195 } else if (this.webviewNode
[attributeName
]){
196 // Reading property using has/getAttribute does not work on
197 // document.DOMContentLoaded event (but works on
198 // window.DOMContentLoaded event).
199 // So copy from property if copying from attribute fails.
200 browserPluginNode
.setAttribute(
201 attributeName
, this.webviewNode
[attributeName
]);
205 return browserPluginNode
;
211 WebViewInternal
.prototype.setupFocusPropagation = function() {
212 if (!this.webviewNode
.hasAttribute('tabIndex')) {
213 // <webview> needs a tabIndex in order to be focusable.
214 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
215 // to allow <webview> to be focusable.
216 // See http://crbug.com/231664.
217 this.webviewNode
.setAttribute('tabIndex', -1);
220 this.webviewNode
.addEventListener('focus', function(e
) {
221 // Focus the BrowserPlugin when the <webview> takes focus.
222 self
.browserPluginNode
.focus();
224 this.webviewNode
.addEventListener('blur', function(e
) {
225 // Blur the BrowserPlugin when the <webview> loses focus.
226 self
.browserPluginNode
.blur();
233 WebViewInternal
.prototype.canGoBack = function() {
234 return this.entryCount
> 1 && this.currentEntryIndex
> 0;
240 WebViewInternal
.prototype.canGoForward = function() {
241 return this.currentEntryIndex
>= 0 &&
242 this.currentEntryIndex
< (this.entryCount
- 1);
248 WebViewInternal
.prototype.clearData = function() {
249 if (!this.instanceId
) {
252 var args
= $Array
.concat([this.instanceId
], $Array
.slice(arguments
));
253 $Function
.apply(WebView
.clearData
, null, args
);
259 WebViewInternal
.prototype.getProcessId = function() {
260 return this.processId
;
266 WebViewInternal
.prototype.go = function(relativeIndex
) {
267 if (!this.instanceId
) {
270 WebView
.go(this.instanceId
, relativeIndex
);
276 WebViewInternal
.prototype.reload = function() {
277 if (!this.instanceId
) {
280 WebView
.reload(this.instanceId
);
286 WebViewInternal
.prototype.stop = function() {
287 if (!this.instanceId
) {
290 WebView
.stop(this.instanceId
);
296 WebViewInternal
.prototype.terminate = function() {
297 if (!this.instanceId
) {
300 WebView
.terminate(this.instanceId
);
306 WebViewInternal
.prototype.validateExecuteCodeCall = function() {
307 var ERROR_MSG_CANNOT_INJECT_SCRIPT
= '<webview>: ' +
308 'Script cannot be injected into content until the page has loaded.';
309 if (!this.instanceId
) {
310 throw new Error(ERROR_MSG_CANNOT_INJECT_SCRIPT
);
317 WebViewInternal
.prototype.executeScript = function(var_args
) {
318 this.validateExecuteCodeCall();
319 var args
= $Array
.concat([this.instanceId
, this.src
],
320 $Array
.slice(arguments
));
321 $Function
.apply(WebView
.executeScript
, null, args
);
327 WebViewInternal
.prototype.insertCSS = function(var_args
) {
328 this.validateExecuteCodeCall();
329 var args
= $Array
.concat([this.instanceId
, this.src
],
330 $Array
.slice(arguments
));
331 $Function
.apply(WebView
.insertCSS
, null, args
);
337 WebViewInternal
.prototype.setupWebviewNodeProperties = function() {
338 var ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE
= '<webview>: ' +
339 'contentWindow is not available at this time. It will become available ' +
340 'when the page has finished loading.';
343 var browserPluginNode
= this.browserPluginNode
;
344 // Expose getters and setters for the attributes.
345 $Array
.forEach(WEB_VIEW_ATTRIBUTES
, function(attributeName
) {
346 Object
.defineProperty(this.webviewNode
, attributeName
, {
348 if (browserPluginNode
.hasOwnProperty(attributeName
)) {
349 return browserPluginNode
[attributeName
];
351 return browserPluginNode
.getAttribute(attributeName
);
354 set: function(value
) {
355 if (browserPluginNode
.hasOwnProperty(attributeName
)) {
356 // Give the BrowserPlugin first stab at the attribute so that it can
357 // throw an exception if there is a problem. This attribute will then
358 // be propagated back to the <webview>.
359 browserPluginNode
[attributeName
] = value
;
361 browserPluginNode
.setAttribute(attributeName
, value
);
368 // <webview> src does not quite behave the same as BrowserPlugin src, and so
369 // we don't simply keep the two in sync.
370 this.src
= this.webviewNode
.getAttribute('src');
371 Object
.defineProperty(this.webviewNode
, 'src', {
375 set: function(value
) {
376 self
.webviewNode
.setAttribute('src', value
);
382 // We cannot use {writable: true} property descriptor because we want a
383 // dynamic getter value.
384 Object
.defineProperty(this.webviewNode
, 'contentWindow', {
386 if (browserPluginNode
.contentWindow
)
387 return browserPluginNode
.contentWindow
;
388 window
.console
.error(ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE
);
398 WebViewInternal
.prototype.setupWebviewNodeAttributes = function() {
399 this.setupWebViewSrcAttributeMutationObserver();
405 WebViewInternal
.prototype.setupWebViewSrcAttributeMutationObserver
=
407 // The purpose of this mutation observer is to catch assignment to the src
408 // attribute without any changes to its value. This is useful in the case
409 // where the webview guest has crashed and navigating to the same address
410 // spawns off a new process.
412 this.srcObserver
= new MutationObserver(function(mutations
) {
413 $Array
.forEach(mutations
, function(mutation
) {
414 var oldValue
= mutation
.oldValue
;
415 var newValue
= self
.webviewNode
.getAttribute(mutation
.attributeName
);
416 if (oldValue
!= newValue
) {
419 self
.handleWebviewAttributeMutation(
420 mutation
.attributeName
, oldValue
, newValue
);
425 attributeOldValue
: true,
426 attributeFilter
: ['src']
428 this.srcObserver
.observe(this.webviewNode
, params
);
434 WebViewInternal
.prototype.handleWebviewAttributeMutation
=
435 function(name
, oldValue
, newValue
) {
436 // This observer monitors mutations to attributes of the <webview> and
437 // updates the BrowserPlugin properties accordingly. In turn, updating
438 // a BrowserPlugin property will update the corresponding BrowserPlugin
439 // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
442 // We treat null attribute (attribute removed) and the empty string as
444 oldValue
= oldValue
|| '';
445 newValue
= newValue
|| '';
446 // Once we have navigated, we don't allow clearing the src attribute.
447 // Once <webview> enters a navigated state, it cannot be return back to a
448 // placeholder state.
449 if (newValue
== '' && oldValue
!= '') {
450 // src attribute changes normally initiate a navigation. We suppress
451 // the next src attribute handler call to avoid reloading the page
452 // on every guest-initiated navigation.
453 this.ignoreNextSrcAttributeChange
= true;
454 this.webviewNode
.setAttribute('src', oldValue
);
458 if (this.ignoreNextSrcAttributeChange
) {
459 // Don't allow the src mutation observer to see this change.
460 this.srcObserver
.takeRecords();
461 this.ignoreNextSrcAttributeChange
= false;
465 if (this.browserPluginNode
.hasOwnProperty(name
)) {
466 this.browserPluginNode
[name
] = newValue
;
468 this.browserPluginNode
.setAttribute(name
, newValue
);
475 WebViewInternal
.prototype.handleBrowserPluginAttributeMutation
=
476 function(name
, newValue
) {
477 // This observer monitors mutations to attributes of the BrowserPlugin and
478 // updates the <webview> attributes accordingly.
479 // |newValue| is null if the attribute |name| has been removed.
480 if (newValue
!= null) {
481 // Update the <webview> attribute to match the BrowserPlugin attribute.
482 // Note: Calling setAttribute on <webview> will trigger its mutation
483 // observer which will then propagate that attribute to BrowserPlugin. In
484 // cases where we permit assigning a BrowserPlugin attribute the same value
485 // again (such as navigation when crashed), this could end up in an infinite
486 // loop. Thus, we avoid this loop by only updating the <webview> attribute
487 // if the BrowserPlugin attributes differs from it.
488 if (newValue
!= this.webviewNode
.getAttribute(name
)) {
489 this.webviewNode
.setAttribute(name
, newValue
);
492 // If an attribute is removed from the BrowserPlugin, then remove it
493 // from the <webview> as well.
494 this.webviewNode
.removeAttribute(name
);
501 WebViewInternal
.prototype.getEvents = function() {
502 var experimentalEvents
= this.maybeGetExperimentalEvents();
503 for (var eventName
in experimentalEvents
) {
504 WEB_VIEW_EVENTS
[eventName
] = experimentalEvents
[eventName
];
506 return WEB_VIEW_EVENTS
;
509 WebViewInternal
.prototype.handleSizeChangedEvent
=
510 function(event
, webViewEvent
) {
511 var node
= this.webviewNode
;
513 var width
= node
.offsetWidth
;
514 var height
= node
.offsetHeight
;
516 // Check the current bounds to make sure we do not resize <webview>
517 // outside of current constraints.
519 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH
) &&
520 node
[WEB_VIEW_ATTRIBUTE_MAXWIDTH
]) {
521 maxWidth
= node
[WEB_VIEW_ATTRIBUTE_MAXWIDTH
];
527 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MINWIDTH
) &&
528 node
[WEB_VIEW_ATTRIBUTE_MINWIDTH
]) {
529 minWidth
= node
[WEB_VIEW_ATTRIBUTE_MINWIDTH
];
533 if (minWidth
> maxWidth
) {
538 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXHEIGHT
) &&
539 node
[WEB_VIEW_ATTRIBUTE_MAXHEIGHT
]) {
540 maxHeight
= node
[WEB_VIEW_ATTRIBUTE_MAXHEIGHT
];
545 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT
) &&
546 node
[WEB_VIEW_ATTRIBUTE_MINHEIGHT
]) {
547 minHeight
= node
[WEB_VIEW_ATTRIBUTE_MINHEIGHT
];
551 if (minHeight
> maxHeight
) {
552 minHeight
= maxHeight
;
555 if (webViewEvent
.newWidth
>= minWidth
&&
556 webViewEvent
.newWidth
<= maxWidth
&&
557 webViewEvent
.newHeight
>= minHeight
&&
558 webViewEvent
.newHeight
<= maxHeight
) {
559 node
.style
.width
= webViewEvent
.newWidth
+ 'px';
560 node
.style
.height
= webViewEvent
.newHeight
+ 'px';
562 node
.dispatchEvent(webViewEvent
);
568 WebViewInternal
.prototype.setupWebviewNodeEvents = function() {
570 this.viewInstanceId
= IdGenerator
.GetNextId();
571 var onInstanceIdAllocated = function(e
) {
572 var detail
= e
.detail
? JSON
.parse(e
.detail
) : {};
573 self
.attachWindowAndSetUpEvents(detail
.windowId
);
575 this.browserPluginNode
.addEventListener('-internal-instanceid-allocated',
576 onInstanceIdAllocated
);
577 this.setupWebRequestEvents();
578 this.setupExperimentalContextMenus_();
581 var events
= self
.getEvents();
582 for (var eventName
in events
) {
583 this.setupEventProperty(eventName
);
590 WebViewInternal
.prototype.setupEvent = function(eventName
, eventInfo
) {
592 var webviewNode
= this.webviewNode
;
593 eventInfo
.evt
.addListener(function(event
) {
594 var details
= {bubbles
:true};
595 if (eventInfo
.cancelable
)
596 details
.cancelable
= true;
597 var webViewEvent
= new Event(eventName
, details
);
598 $Array
.forEach(eventInfo
.fields
, function(field
) {
599 if (event
[field
] !== undefined) {
600 webViewEvent
[field
] = event
[field
];
603 if (eventInfo
.customHandler
) {
604 eventInfo
.customHandler(self
, event
, webViewEvent
);
607 webviewNode
.dispatchEvent(webViewEvent
);
608 }, {instanceId
: self
.instanceId
});
612 * Adds an 'on<event>' property on the webview, which can be used to set/unset
616 WebViewInternal
.prototype.setupEventProperty = function(eventName
) {
617 var propertyName
= 'on' + eventName
.toLowerCase();
619 var webviewNode
= this.webviewNode
;
620 Object
.defineProperty(webviewNode
, propertyName
, {
622 return self
.on
[propertyName
];
624 set: function(value
) {
625 if (self
.on
[propertyName
])
626 webviewNode
.removeEventListener(eventName
, self
.on
[propertyName
]);
627 self
.on
[propertyName
] = value
;
629 webviewNode
.addEventListener(eventName
, value
);
638 WebViewInternal
.prototype.getPermissionTypes = function() {
640 ['media', 'geolocation', 'pointerLock', 'download', 'loadplugin'];
641 return permissions
.concat(this.maybeGetExperimentalPermissions());
647 WebViewInternal
.prototype.handleDialogEvent
=
648 function(event
, webViewEvent
) {
649 var showWarningMessage = function(dialogType
) {
650 var VOWELS
= ['a', 'e', 'i', 'o', 'u'];
651 var WARNING_MSG_DIALOG_BLOCKED
= '<webview>: %1 %2 dialog was blocked.';
652 var article
= (VOWELS
.indexOf(dialogType
.charAt(0)) >= 0) ? 'An' : 'A';
653 var output
= WARNING_MSG_DIALOG_BLOCKED
.replace('%1', article
);
654 output
= output
.replace('%2', dialogType
);
655 window
.console
.warn(output
);
659 var browserPluginNode
= this.browserPluginNode
;
660 var webviewNode
= this.webviewNode
;
662 var requestId
= event
.requestId
;
663 var actionTaken
= false;
665 var validateCall = function() {
666 var ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN
= '<webview>: ' +
667 'An action has already been taken for this "dialog" event.';
670 throw new Error(ERROR_MSG_DIALOG_ACTION_ALREADY_TAKEN
);
676 ok: function(user_input
) {
678 user_input
= user_input
|| '';
679 WebView
.setPermission(self
.instanceId
, requestId
, 'allow', user_input
);
683 WebView
.setPermission(self
.instanceId
, requestId
, 'deny');
686 webViewEvent
.dialog
= dialog
;
688 var defaultPrevented
= !webviewNode
.dispatchEvent(webViewEvent
);
693 if (defaultPrevented
) {
694 // Tell the JavaScript garbage collector to track lifetime of |dialog| and
695 // call back when the dialog object has been collected.
696 MessagingNatives
.BindToGC(dialog
, function() {
697 // Avoid showing a warning message if the decision has already been made.
701 WebView
.setPermission(
702 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
706 showWarningMessage(event
.messageType
);
711 // The default action is equivalent to canceling the dialog.
712 WebView
.setPermission(
713 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
717 showWarningMessage(event
.messageType
);
725 WebViewInternal
.prototype.handleLoadAbortEvent
=
726 function(event
, webViewEvent
) {
727 var showWarningMessage = function(reason
) {
728 var WARNING_MSG_LOAD_ABORTED
= '<webview>: ' +
729 'The load has aborted with reason "%1".';
730 window
.console
.warn(WARNING_MSG_LOAD_ABORTED
.replace('%1', reason
));
732 if (this.webviewNode
.dispatchEvent(webViewEvent
)) {
733 showWarningMessage(event
.reason
);
740 WebViewInternal
.prototype.handleLoadCommitEvent
=
741 function(event
, webViewEvent
) {
742 this.currentEntryIndex
= event
.currentEntryIndex
;
743 this.entryCount
= event
.entryCount
;
744 this.processId
= event
.processId
;
745 var oldValue
= this.webviewNode
.getAttribute('src');
746 var newValue
= event
.url
;
747 if (event
.isTopLevel
&& (oldValue
!= newValue
)) {
748 // Touching the src attribute triggers a navigation. To avoid
749 // triggering a page reload on every guest-initiated navigation,
750 // we use the flag ignoreNextSrcAttributeChange here.
751 this.ignoreNextSrcAttributeChange
= true;
752 this.webviewNode
.setAttribute('src', newValue
);
754 this.webviewNode
.dispatchEvent(webViewEvent
);
760 WebViewInternal
.prototype.handleNewWindowEvent
=
761 function(event
, webViewEvent
) {
762 var ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN
= '<webview>: ' +
763 'An action has already been taken for this "newwindow" event.';
765 var ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH
= '<webview>: ' +
766 'Unable to attach the new window to the provided webview.';
768 var ERROR_MSG_WEBVIEW_EXPECTED
= '<webview> element expected.';
770 var showWarningMessage = function() {
771 var WARNING_MSG_NEWWINDOW_BLOCKED
= '<webview>: A new window was blocked.';
772 window
.console
.warn(WARNING_MSG_NEWWINDOW_BLOCKED
);
776 var browserPluginNode
= this.browserPluginNode
;
777 var webviewNode
= this.webviewNode
;
779 var requestId
= event
.requestId
;
780 var actionTaken
= false;
782 var validateCall = function () {
784 throw new Error(ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN
);
790 attach: function(webview
) {
792 if (!webview
|| !webview
.tagName
|| webview
.tagName
!= 'WEBVIEW')
793 throw new Error(ERROR_MSG_WEBVIEW_EXPECTED
);
794 // Attach happens asynchronously to give the tagWatcher an opportunity
795 // to pick up the new webview before attach operates on it, if it hasn't
796 // been attached to the DOM already.
797 // Note: Any subsequent errors cannot be exceptions because they happen
799 setTimeout(function() {
800 var webViewInternal
= privates(webview
).internal;
802 webViewInternal
.attachWindowAndSetUpEvents(event
.windowId
);
805 window
.console
.error(ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH
);
807 // If the object being passed into attach is not a valid <webview>
808 // then we will fail and it will be treated as if the new window
809 // was rejected. The permission API plumbing is used here to clean
810 // up the state created for the new window if attaching fails.
811 WebView
.setPermission(
812 self
.instanceId
, requestId
, attached
? 'allow' : 'deny');
815 discard: function() {
817 WebView
.setPermission(self
.instanceId
, requestId
, 'deny');
820 webViewEvent
.window
= windowObj
;
822 var defaultPrevented
= !webviewNode
.dispatchEvent(webViewEvent
);
827 if (defaultPrevented
) {
828 // Make browser plugin track lifetime of |windowObj|.
829 MessagingNatives
.BindToGC(windowObj
, function() {
830 // Avoid showing a warning message if the decision has already been made.
834 WebView
.setPermission(
835 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
839 showWarningMessage();
844 // The default action is to discard the window.
845 WebView
.setPermission(
846 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
850 showWarningMessage();
855 WebViewInternal
.prototype.handlePermissionEvent
=
856 function(event
, webViewEvent
) {
857 var ERROR_MSG_PERMISSION_ALREADY_DECIDED
= '<webview>: ' +
858 'Permission has already been decided for this "permissionrequest" event.';
860 var showWarningMessage = function(permission
) {
861 var WARNING_MSG_PERMISSION_DENIED
= '<webview>: ' +
862 'The permission request for "%1" has been denied.';
864 WARNING_MSG_PERMISSION_DENIED
.replace('%1', permission
));
867 var requestId
= event
.requestId
;
870 if (this.getPermissionTypes().indexOf(event
.permission
) < 0) {
871 // The permission type is not allowed. Trigger the default response.
872 WebView
.setPermission(
873 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
877 showWarningMessage(event
.permission
);
882 var browserPluginNode
= this.browserPluginNode
;
883 var webviewNode
= this.webviewNode
;
885 var decisionMade
= false;
887 var validateCall = function() {
889 throw new Error(ERROR_MSG_PERMISSION_ALREADY_DECIDED
);
894 // Construct the event.request object.
898 WebView
.setPermission(self
.instanceId
, requestId
, 'allow');
902 WebView
.setPermission(self
.instanceId
, requestId
, 'deny');
905 webViewEvent
.request
= request
;
907 var defaultPrevented
= !webviewNode
.dispatchEvent(webViewEvent
);
912 if (defaultPrevented
) {
913 // Make browser plugin track lifetime of |request|.
914 MessagingNatives
.BindToGC(request
, function() {
915 // Avoid showing a warning message if the decision has already been made.
919 WebView
.setPermission(
920 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
924 showWarningMessage(event
.permission
);
929 WebView
.setPermission(
930 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
934 showWarningMessage(event
.permission
);
942 WebViewInternal
.prototype.setupWebRequestEvents = function() {
945 var createWebRequestEvent = function(webRequestEvent
) {
947 if (!self
[webRequestEvent
.name
]) {
948 self
[webRequestEvent
.name
] =
950 'webview.' + webRequestEvent
.name
,
951 webRequestEvent
.parameters
,
952 webRequestEvent
.extraParameters
, webRequestEvent
.options
,
953 self
.viewInstanceId
);
955 return self
[webRequestEvent
.name
];
959 for (var i
= 0; i
< DeclarativeWebRequestSchema
.events
.length
; ++i
) {
960 var eventSchema
= DeclarativeWebRequestSchema
.events
[i
];
961 var webRequestEvent
= createWebRequestEvent(eventSchema
);
962 this.maybeAttachWebRequestEventToObject(request
,
967 // Populate the WebRequest events from the API definition.
968 for (var i
= 0; i
< WebRequestSchema
.events
.length
; ++i
) {
969 var webRequestEvent
= createWebRequestEvent(WebRequestSchema
.events
[i
]);
970 Object
.defineProperty(
972 WebRequestSchema
.events
[i
].name
,
974 get: webRequestEvent
,
978 this.maybeAttachWebRequestEventToObject(this.webviewNode
,
979 WebRequestSchema
.events
[i
].name
,
982 Object
.defineProperty(
993 WebViewInternal
.prototype.getUserAgent = function() {
994 return this.userAgentOverride
|| navigator
.userAgent
;
998 WebViewInternal
.prototype.isUserAgentOverridden = function() {
999 return !!this.userAgentOverride
&&
1000 this.userAgentOverride
!= navigator
.userAgent
;
1004 WebViewInternal
.prototype.setUserAgentOverride = function(userAgentOverride
) {
1005 this.userAgentOverride
= userAgentOverride
;
1006 if (!this.instanceId
) {
1007 // If we are not attached yet, then we will pick up the user agent on
1011 WebView
.overrideUserAgent(this.instanceId
, userAgentOverride
);
1015 WebViewInternal
.prototype.attachWindowAndSetUpEvents = function(instanceId
) {
1016 this.instanceId
= instanceId
;
1019 'instanceId': this.viewInstanceId
1021 if (this.userAgentOverride
) {
1022 params
['userAgentOverride'] = this.userAgentOverride
;
1024 this.browserPluginNode
['-internal-attach'](this.instanceId
, params
);
1026 var events
= this.getEvents();
1027 for (var eventName
in events
) {
1028 this.setupEvent(eventName
, events
[eventName
]);
1033 // Registers browser plugin <object> custom element.
1034 function registerBrowserPluginElement() {
1035 var proto
= Object
.create(HTMLObjectElement
.prototype);
1037 proto
.createdCallback = function() {
1038 this.setAttribute('type', 'application/browser-plugin');
1039 // The <object> node fills in the <webview> container.
1040 this.style
.width
= '100%';
1041 this.style
.height
= '100%';
1044 proto
.attributeChangedCallback = function(name
, oldValue
, newValue
) {
1045 var internal = privates(this).internal;
1049 internal.handleBrowserPluginAttributeMutation(name
, newValue
);
1052 proto
.attachedCallback = function() {
1053 // Load the plugin immediately.
1054 var unused
= this.nonExistentAttribute
;
1057 WebViewInternal
.BrowserPlugin
=
1058 DocumentNatives
.RegisterElement('browser-plugin', {extends: 'object',
1061 delete proto
.createdCallback
;
1062 delete proto
.attachedCallback
;
1063 delete proto
.detachedCallback
;
1064 delete proto
.attributeChangedCallback
;
1067 // Registers <webview> custom element.
1068 function registerWebViewElement() {
1069 var proto
= Object
.create(HTMLElement
.prototype);
1071 proto
.createdCallback = function() {
1072 new WebViewInternal(this);
1075 proto
.attributeChangedCallback = function(name
, oldValue
, newValue
) {
1076 var internal = privates(this).internal;
1080 internal.handleWebviewAttributeMutation(name
, oldValue
, newValue
);
1083 proto
.back = function() {
1087 proto
.forward = function() {
1091 proto
.canGoBack = function() {
1092 return privates(this).internal.canGoBack();
1095 proto
.canGoForward = function() {
1096 return privates(this).internal.canGoForward();
1099 proto
.clearData = function() {
1100 var internal = privates(this).internal;
1101 $Function
.apply(internal.clearData
, internal, arguments
);
1104 proto
.getProcessId = function() {
1105 return privates(this).internal.getProcessId();
1108 proto
.go = function(relativeIndex
) {
1109 privates(this).internal.go(relativeIndex
);
1112 proto
.reload = function() {
1113 privates(this).internal.reload();
1116 proto
.stop = function() {
1117 privates(this).internal.stop();
1120 proto
.terminate = function() {
1121 privates(this).internal.terminate();
1124 proto
.executeScript = function(var_args
) {
1125 var internal = privates(this).internal;
1126 $Function
.apply(internal.executeScript
, internal, arguments
);
1129 proto
.insertCSS = function(var_args
) {
1130 var internal = privates(this).internal;
1131 $Function
.apply(internal.insertCSS
, internal, arguments
);
1134 proto
.getUserAgent = function() {
1135 return privates(this).internal.getUserAgent();
1138 proto
.isUserAgentOverridden = function() {
1139 return privates(this).internal.isUserAgentOverridden();
1142 proto
.setUserAgentOverride = function(userAgentOverride
) {
1143 privates(this).internal.setUserAgentOverride(userAgentOverride
);
1145 WebViewInternal
.maybeRegisterExperimentalAPIs(proto
);
1148 DocumentNatives
.RegisterElement('webview', {prototype: proto
});
1150 // Delete the callbacks so developers cannot call them and produce unexpected
1152 delete proto
.createdCallback
;
1153 delete proto
.attachedCallback
;
1154 delete proto
.detachedCallback
;
1155 delete proto
.attributeChangedCallback
;
1158 var useCapture
= true;
1159 window
.addEventListener('readystatechange', function listener(event
) {
1160 if (document
.readyState
== 'loading')
1163 registerBrowserPluginElement();
1164 registerWebViewElement();
1165 window
.removeEventListener(event
.type
, listener
, useCapture
);
1169 * Implemented when the experimental API is available.
1172 WebViewInternal
.prototype.maybeGetExperimentalEvents = function() {};
1175 * Implemented when the experimental API is available.
1178 WebViewInternal
.prototype.maybeAttachWebRequestEventToObject = function() {};
1181 * Implemented when the experimental API is available.
1184 WebViewInternal
.prototype.maybeGetExperimentalPermissions = function() {
1189 * Implemented when the experimental API is available.
1192 WebViewInternal
.prototype.setupExperimentalContextMenus_ = function() {};
1194 exports
.WebView
= WebView
;
1195 exports
.WebViewInternal
= WebViewInternal
;
1196 exports
.CreateEvent
= CreateEvent
;