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 // Shim that simulates a <webview> tag via Mutation Observers.
7 // The actual tag is implemented via the browser plugin. The internals of this
8 // are hidden via Shadow DOM.
12 var DocumentNatives
= requireNative('document_natives');
13 var EventBindings
= require('event_bindings');
14 var IdGenerator
= requireNative('id_generator');
15 var MessagingNatives
= requireNative('messaging_natives');
16 var WebRequestEvent
= require('webRequestInternal').WebRequestEvent
;
17 var WebRequestSchema
=
18 requireNative('schema_registry').GetSchema('webRequest');
19 var DeclarativeWebRequestSchema
=
20 requireNative('schema_registry').GetSchema('declarativeWebRequest');
21 var WebView
= require('binding').Binding
.create('webview').generate();
23 // This secret enables hiding <webview> private members from the outside scope.
24 // Outside of this file, |secret| is inaccessible. The only way to access the
25 // <webview> element's internal members is via the |secret|. Since it's only
26 // accessible by code here (and in web_view_experimental), only <webview>'s
27 // API can access it and not external developers.
30 var WEB_VIEW_ATTRIBUTE_MAXHEIGHT
= 'maxheight';
31 var WEB_VIEW_ATTRIBUTE_MAXWIDTH
= 'maxwidth';
32 var WEB_VIEW_ATTRIBUTE_MINHEIGHT
= 'minheight';
33 var WEB_VIEW_ATTRIBUTE_MINWIDTH
= 'minwidth';
35 /** @type {Array.<string>} */
36 var WEB_VIEW_ATTRIBUTES
= [
41 WEB_VIEW_ATTRIBUTE_MINHEIGHT
,
42 WEB_VIEW_ATTRIBUTE_MINWIDTH
,
43 WEB_VIEW_ATTRIBUTE_MAXHEIGHT
,
44 WEB_VIEW_ATTRIBUTE_MAXWIDTH
47 var CreateEvent = function(name
) {
48 var eventOpts
= {supportsListeners
: true, supportsFilters
: true};
49 return new EventBindings
.Event(name
, undefined, eventOpts
);
52 // WEB_VIEW_EVENTS is a map of stable <webview> DOM event names to their
53 // associated extension event descriptor objects.
54 // An event listener will be attached to the extension event |evt| specified in
56 // |fields| specifies the public-facing fields in the DOM event that are
57 // accessible to <webview> developers.
58 // |customHandler| allows a handler function to be called each time an extension
59 // event is caught by its event listener. The DOM event should be dispatched
60 // within this handler function. With no handler function, the DOM event
61 // will be dispatched by default each time the extension event is caught.
62 // |cancelable| (default: false) specifies whether the event's default
63 // behavior can be canceled. If the default action associated with the event
64 // is prevented, then its dispatch function will return false in its event
65 // handler. The event must have a custom handler for this to be meaningful.
66 var WEB_VIEW_EVENTS
= {
68 evt
: CreateEvent('webview.onClose'),
72 evt
: CreateEvent('webview.onConsoleMessage'),
73 fields
: ['level', 'message', 'line', 'sourceId']
76 evt
: CreateEvent('webview.onContentLoad'),
80 evt
: CreateEvent('webview.onExit'),
81 fields
: ['processId', 'reason']
85 customHandler: function(webViewInternal
, event
, webViewEvent
) {
86 webViewInternal
.handleLoadAbortEvent_(event
, webViewEvent
);
88 evt
: CreateEvent('webview.onLoadAbort'),
89 fields
: ['url', 'isTopLevel', 'reason']
92 customHandler: function(webViewInternal
, event
, webViewEvent
) {
93 webViewInternal
.handleLoadCommitEvent_(event
, webViewEvent
);
95 evt
: CreateEvent('webview.onLoadCommit'),
96 fields
: ['url', 'isTopLevel']
99 evt
: CreateEvent('webview.onLoadProgress'),
100 fields
: ['url', 'progress']
103 evt
: CreateEvent('webview.onLoadRedirect'),
104 fields
: ['isTopLevel', 'oldUrl', 'newUrl']
107 evt
: CreateEvent('webview.onLoadStart'),
108 fields
: ['url', 'isTopLevel']
111 evt
: CreateEvent('webview.onLoadStop'),
116 customHandler: function(webViewInternal
, event
, webViewEvent
) {
117 webViewInternal
.handleNewWindowEvent_(event
, webViewEvent
);
119 evt
: CreateEvent('webview.onNewWindow'),
124 'windowOpenDisposition',
128 'permissionrequest': {
130 customHandler: function(webViewInternal
, event
, webViewEvent
) {
131 webViewInternal
.handlePermissionEvent_(event
, webViewEvent
);
133 evt
: CreateEvent('webview.onPermissionRequest'),
136 'lastUnlockedBySelf',
145 evt
: CreateEvent('webview.onResponsive'),
146 fields
: ['processId']
149 evt
: CreateEvent('webview.onSizeChanged'),
150 customHandler: function(webViewInternal
, event
, webViewEvent
) {
151 webViewInternal
.handleSizeChangedEvent_(event
, webViewEvent
);
153 fields
: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth']
156 evt
: CreateEvent('webview.onUnresponsive'),
157 fields
: ['processId']
161 // Implemented when the experimental API is available.
162 WebViewInternal
.maybeRegisterExperimentalAPIs = function(proto
) {}
167 function WebViewInternal(webviewNode
) {
168 this.webviewNode_
= webviewNode
;
169 this.browserPluginNode_
= this.createBrowserPluginNode_();
170 var shadowRoot
= this.webviewNode_
.webkitCreateShadowRoot();
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 Object
.defineProperty(browserPluginNode
, 'internal_', {
189 value: function(key
) {
190 if (key
!== secret
) {
197 var ALL_ATTRIBUTES
= WEB_VIEW_ATTRIBUTES
.concat(['src']);
198 $Array
.forEach(ALL_ATTRIBUTES
, function(attributeName
) {
199 // Only copy attributes that have been assigned values, rather than copying
200 // a series of undefined attributes to BrowserPlugin.
201 if (this.webviewNode_
.hasAttribute(attributeName
)) {
202 browserPluginNode
.setAttribute(
203 attributeName
, this.webviewNode_
.getAttribute(attributeName
));
204 } else if (this.webviewNode_
[attributeName
]){
205 // Reading property using has/getAttribute does not work on
206 // document.DOMContentLoaded event (but works on
207 // window.DOMContentLoaded event).
208 // So copy from property if copying from attribute fails.
209 browserPluginNode
.setAttribute(
210 attributeName
, this.webviewNode_
[attributeName
]);
214 return browserPluginNode
;
220 WebViewInternal
.prototype.setupFocusPropagation_ = function() {
221 if (!this.webviewNode_
.hasAttribute('tabIndex')) {
222 // <webview> needs a tabIndex in order to be focusable.
223 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
224 // to allow <webview> to be focusable.
225 // See http://crbug.com/231664.
226 this.webviewNode_
.setAttribute('tabIndex', -1);
229 this.webviewNode_
.addEventListener('focus', function(e
) {
230 // Focus the BrowserPlugin when the <webview> takes focus.
231 self
.browserPluginNode_
.focus();
233 this.webviewNode_
.addEventListener('blur', function(e
) {
234 // Blur the BrowserPlugin when the <webview> loses focus.
235 self
.browserPluginNode_
.blur();
242 WebViewInternal
.prototype.canGoBack_ = function() {
243 return this.entryCount_
> 1 && this.currentEntryIndex_
> 0;
249 WebViewInternal
.prototype.canGoForward_ = function() {
250 return this.currentEntryIndex_
>= 0 &&
251 this.currentEntryIndex_
< (this.entryCount_
- 1);
257 WebViewInternal
.prototype.getProcessId_ = function() {
258 return this.processId_
;
264 WebViewInternal
.prototype.go_ = function(relativeIndex
) {
265 if (!this.instanceId_
) {
268 WebView
.go(this.instanceId_
, relativeIndex
);
274 WebViewInternal
.prototype.reload_ = function() {
275 if (!this.instanceId_
) {
278 WebView
.reload(this.instanceId_
);
284 WebViewInternal
.prototype.stop_ = function() {
285 if (!this.instanceId_
) {
288 WebView
.stop(this.instanceId_
);
294 WebViewInternal
.prototype.terminate_ = function() {
295 if (!this.instanceId_
) {
298 WebView
.terminate(this.instanceId_
);
304 WebViewInternal
.prototype.validateExecuteCodeCall_ = function() {
305 var ERROR_MSG_CANNOT_INJECT_SCRIPT
= '<webview>: ' +
306 'Script cannot be injected into content until the page has loaded.';
307 if (!this.instanceId_
) {
308 throw new Error(ERROR_MSG_CANNOT_INJECT_SCRIPT
);
315 WebViewInternal
.prototype.executeScript_ = function(var_args
) {
316 this.validateExecuteCodeCall_();
317 var args
= $Array
.concat([this.instanceId_
], $Array
.slice(arguments
));
318 $Function
.apply(WebView
.executeScript
, null, args
);
324 WebViewInternal
.prototype.insertCSS_ = function(var_args
) {
325 this.validateExecuteCodeCall_();
326 var args
= $Array
.concat([this.instanceId_
], $Array
.slice(arguments
));
327 $Function
.apply(WebView
.insertCSS
, null, args
);
333 WebViewInternal
.prototype.setupWebviewNodeProperties_ = function() {
334 var ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE
= '<webview>: ' +
335 'contentWindow is not available at this time. It will become available ' +
336 'when the page has finished loading.';
339 var browserPluginNode
= this.browserPluginNode_
;
340 // Expose getters and setters for the attributes.
341 $Array
.forEach(WEB_VIEW_ATTRIBUTES
, function(attributeName
) {
342 Object
.defineProperty(this.webviewNode_
, attributeName
, {
344 if (browserPluginNode
.hasOwnProperty(attributeName
)) {
345 return browserPluginNode
[attributeName
];
347 return browserPluginNode
.getAttribute(attributeName
);
350 set: function(value
) {
351 if (browserPluginNode
.hasOwnProperty(attributeName
)) {
352 // Give the BrowserPlugin first stab at the attribute so that it can
353 // throw an exception if there is a problem. This attribute will then
354 // be propagated back to the <webview>.
355 browserPluginNode
[attributeName
] = value
;
357 browserPluginNode
.setAttribute(attributeName
, value
);
364 // <webview> src does not quite behave the same as BrowserPlugin src, and so
365 // we don't simply keep the two in sync.
366 this.src_
= this.webviewNode_
.getAttribute('src');
367 Object
.defineProperty(this.webviewNode_
, 'src', {
371 set: function(value
) {
372 self
.webviewNode_
.setAttribute('src', value
);
378 // We cannot use {writable: true} property descriptor because we want a
379 // dynamic getter value.
380 Object
.defineProperty(this.webviewNode_
, 'contentWindow', {
382 if (browserPluginNode
.contentWindow
)
383 return browserPluginNode
.contentWindow
;
384 window
.console
.error(ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE
);
394 WebViewInternal
.prototype.setupWebviewNodeAttributes_ = function() {
395 Object
.defineProperty(this.webviewNode_
, 'internal_', {
398 value: function(key
) {
399 if (key
!== secret
) {
405 this.setupWebViewSrcAttributeMutationObserver_();
411 WebViewInternal
.prototype.setupWebViewSrcAttributeMutationObserver_
=
413 // The purpose of this mutation observer is to catch assignment to the src
414 // attribute without any changes to its value. This is useful in the case
415 // where the webview guest has crashed and navigating to the same address
416 // spawns off a new process.
418 this.srcObserver_
= new MutationObserver(function(mutations
) {
419 $Array
.forEach(mutations
, function(mutation
) {
420 var oldValue
= mutation
.oldValue
;
421 var newValue
= self
.webviewNode_
.getAttribute(mutation
.attributeName
);
422 if (oldValue
!= newValue
) {
425 self
.handleWebviewAttributeMutation_(
426 mutation
.attributeName
, oldValue
, newValue
);
431 attributeOldValue
: true,
432 attributeFilter
: ['src']
434 this.srcObserver_
.observe(this.webviewNode_
, params
);
440 WebViewInternal
.prototype.handleWebviewAttributeMutation_
=
441 function(name
, oldValue
, newValue
) {
442 // This observer monitors mutations to attributes of the <webview> and
443 // updates the BrowserPlugin properties accordingly. In turn, updating
444 // a BrowserPlugin property will update the corresponding BrowserPlugin
445 // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
448 // We treat null attribute (attribute removed) and the empty string as
450 oldValue
= oldValue
|| '';
451 newValue
= newValue
|| '';
452 // Once we have navigated, we don't allow clearing the src attribute.
453 // Once <webview> enters a navigated state, it cannot be return back to a
454 // placeholder state.
455 if (newValue
== '' && oldValue
!= '') {
456 // src attribute changes normally initiate a navigation. We suppress
457 // the next src attribute handler call to avoid reloading the page
458 // on every guest-initiated navigation.
459 this.ignoreNextSrcAttributeChange_
= true;
460 this.webviewNode_
.setAttribute('src', oldValue
);
463 this.src_
= newValue
;
464 if (this.ignoreNextSrcAttributeChange_
) {
465 // Don't allow the src mutation observer to see this change.
466 this.srcObserver_
.takeRecords();
467 this.ignoreNextSrcAttributeChange_
= false;
471 if (this.browserPluginNode_
.hasOwnProperty(name
)) {
472 this.browserPluginNode_
[name
] = newValue
;
474 this.browserPluginNode_
.setAttribute(name
, newValue
);
481 WebViewInternal
.prototype.handleBrowserPluginAttributeMutation_
=
482 function(name
, newValue
) {
483 // This observer monitors mutations to attributes of the BrowserPlugin and
484 // updates the <webview> attributes accordingly.
485 // |newValue| is null if the attribute |name| has been removed.
486 if (newValue
!= null) {
487 // Update the <webview> attribute to match the BrowserPlugin attribute.
488 // Note: Calling setAttribute on <webview> will trigger its mutation
489 // observer which will then propagate that attribute to BrowserPlugin. In
490 // cases where we permit assigning a BrowserPlugin attribute the same value
491 // again (such as navigation when crashed), this could end up in an infinite
492 // loop. Thus, we avoid this loop by only updating the <webview> attribute
493 // if the BrowserPlugin attributes differs from it.
494 if (newValue
!= this.webviewNode_
.getAttribute(name
)) {
495 this.webviewNode_
.setAttribute(name
, newValue
);
498 // If an attribute is removed from the BrowserPlugin, then remove it
499 // from the <webview> as well.
500 this.webviewNode_
.removeAttribute(name
);
507 WebViewInternal
.prototype.getEvents_ = function() {
508 var experimentalEvents
= this.maybeGetExperimentalEvents_();
509 for (var eventName
in experimentalEvents
) {
510 WEB_VIEW_EVENTS
[eventName
] = experimentalEvents
[eventName
];
512 return WEB_VIEW_EVENTS
;
515 WebViewInternal
.prototype.handleSizeChangedEvent_
=
516 function(event
, webViewEvent
) {
517 var node
= this.webviewNode_
;
519 var width
= node
.offsetWidth
;
520 var height
= node
.offsetHeight
;
522 // Check the current bounds to make sure we do not resize <webview>
523 // outside of current constraints.
525 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH
) &&
526 node
[WEB_VIEW_ATTRIBUTE_MAXWIDTH
]) {
527 maxWidth
= node
[WEB_VIEW_ATTRIBUTE_MAXWIDTH
];
533 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MINWIDTH
) &&
534 node
[WEB_VIEW_ATTRIBUTE_MINWIDTH
]) {
535 minWidth
= node
[WEB_VIEW_ATTRIBUTE_MINWIDTH
];
539 if (minWidth
> maxWidth
) {
544 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXHEIGHT
) &&
545 node
[WEB_VIEW_ATTRIBUTE_MAXHEIGHT
]) {
546 maxHeight
= node
[WEB_VIEW_ATTRIBUTE_MAXHEIGHT
];
551 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT
) &&
552 node
[WEB_VIEW_ATTRIBUTE_MINHEIGHT
]) {
553 minHeight
= node
[WEB_VIEW_ATTRIBUTE_MINHEIGHT
];
557 if (minHeight
> maxHeight
) {
558 minHeight
= maxHeight
;
561 if (webViewEvent
.newWidth
>= minWidth
&&
562 webViewEvent
.newWidth
<= maxWidth
&&
563 webViewEvent
.newHeight
>= minHeight
&&
564 webViewEvent
.newHeight
<= maxHeight
) {
565 node
.style
.width
= webViewEvent
.newWidth
+ 'px';
566 node
.style
.height
= webViewEvent
.newHeight
+ 'px';
568 node
.dispatchEvent(webViewEvent
);
574 WebViewInternal
.prototype.setupWebviewNodeEvents_ = function() {
576 this.viewInstanceId_
= IdGenerator
.GetNextId();
577 var onInstanceIdAllocated = function(e
) {
578 var detail
= e
.detail
? JSON
.parse(e
.detail
) : {};
579 self
.instanceId_
= detail
.windowId
;
582 'instanceId': self
.viewInstanceId_
584 if (self
.userAgentOverride_
) {
585 params
['userAgentOverride'] = self
.userAgentOverride_
;
587 self
.browserPluginNode_
['-internal-attach'](params
);
589 var events
= self
.getEvents_();
590 for (var eventName
in events
) {
591 self
.setupEvent_(eventName
, events
[eventName
]);
594 this.browserPluginNode_
.addEventListener('-internal-instanceid-allocated',
595 onInstanceIdAllocated
);
596 this.setupWebRequestEvents_();
599 var events
= self
.getEvents_();
600 for (var eventName
in events
) {
601 this.setupEventProperty_(eventName
);
608 WebViewInternal
.prototype.setupEvent_ = function(eventName
, eventInfo
) {
610 var webviewNode
= this.webviewNode_
;
611 eventInfo
.evt
.addListener(function(event
) {
612 var details
= {bubbles
:true};
613 if (eventInfo
.cancelable
)
614 details
.cancelable
= true;
615 var webViewEvent
= new Event(eventName
, details
);
616 $Array
.forEach(eventInfo
.fields
, function(field
) {
617 if (event
[field
] !== undefined) {
618 webViewEvent
[field
] = event
[field
];
621 if (eventInfo
.customHandler
) {
622 eventInfo
.customHandler(self
, event
, webViewEvent
);
625 webviewNode
.dispatchEvent(webViewEvent
);
626 }, {instanceId
: self
.instanceId_
});
630 * Adds an 'on<event>' property on the webview, which can be used to set/unset
634 WebViewInternal
.prototype.setupEventProperty_ = function(eventName
) {
635 var propertyName
= 'on' + eventName
.toLowerCase();
637 var webviewNode
= this.webviewNode_
;
638 Object
.defineProperty(webviewNode
, propertyName
, {
640 return self
.on_
[propertyName
];
642 set: function(value
) {
643 if (self
.on_
[propertyName
])
644 webviewNode
.removeEventListener(eventName
, self
.on_
[propertyName
]);
645 self
.on_
[propertyName
] = value
;
647 webviewNode
.addEventListener(eventName
, value
);
656 WebViewInternal
.prototype.getPermissionTypes_ = function() {
657 return ['media', 'geolocation', 'pointerLock', 'download', 'loadplugin'];
663 WebViewInternal
.prototype.handleLoadAbortEvent_
=
664 function(event
, webViewEvent
) {
665 var showWarningMessage = function(reason
) {
666 var WARNING_MSG_LOAD_ABORTED
= '<webview>: ' +
667 'The load has aborted with reason "%1".';
668 window
.console
.warn(WARNING_MSG_LOAD_ABORTED
.replace('%1', reason
));
670 if (this.webviewNode_
.dispatchEvent(webViewEvent
)) {
671 showWarningMessage(event
.reason
);
678 WebViewInternal
.prototype.handleLoadCommitEvent_
=
679 function(event
, webViewEvent
) {
680 this.currentEntryIndex_
= event
.currentEntryIndex
;
681 this.entryCount_
= event
.entryCount
;
682 this.processId_
= event
.processId
;
683 var oldValue
= this.webviewNode_
.getAttribute('src');
684 var newValue
= event
.url
;
685 if (event
.isTopLevel
&& (oldValue
!= newValue
)) {
686 // Touching the src attribute triggers a navigation. To avoid
687 // triggering a page reload on every guest-initiated navigation,
688 // we use the flag ignoreNextSrcAttributeChange_ here.
689 this.ignoreNextSrcAttributeChange_
= true;
690 this.webviewNode_
.setAttribute('src', newValue
);
692 this.webviewNode_
.dispatchEvent(webViewEvent
);
698 WebViewInternal
.prototype.handleNewWindowEvent_
=
699 function(event
, webViewEvent
) {
700 var ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN
= '<webview>: ' +
701 'An action has already been taken for this "newwindow" event.';
703 var ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH
= '<webview>: ' +
704 'Unable to attach the new window to the provided webview.';
706 var ERROR_MSG_WEBVIEW_EXPECTED
= '<webview> element expected.';
708 var showWarningMessage = function() {
709 var WARNING_MSG_NEWWINDOW_BLOCKED
= '<webview>: A new window was blocked.';
710 window
.console
.warn(WARNING_MSG_NEWWINDOW_BLOCKED
);
714 var browserPluginNode
= this.browserPluginNode_
;
715 var webviewNode
= this.webviewNode_
;
717 var requestId
= event
.requestId
;
718 var actionTaken
= false;
720 var validateCall = function () {
722 throw new Error(ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN
);
728 attach: function(webview
) {
731 throw new Error(ERROR_MSG_WEBVIEW_EXPECTED
);
732 // Attach happens asynchronously to give the tagWatcher an opportunity
733 // to pick up the new webview before attach operates on it, if it hasn't
734 // been attached to the DOM already.
735 // Note: Any subsequent errors cannot be exceptions because they happen
737 setTimeout(function() {
739 browserPluginNode
['-internal-attachWindowTo'](webview
,
742 window
.console
.error(ERROR_MSG_NEWWINDOW_UNABLE_TO_ATTACH
);
744 // If the object being passed into attach is not a valid <webview>
745 // then we will fail and it will be treated as if the new window
746 // was rejected. The permission API plumbing is used here to clean
747 // up the state created for the new window if attaching fails.
748 WebView
.setPermission(
749 self
.instanceId_
, requestId
, attached
? 'allow' : 'deny');
752 discard: function() {
754 WebView
.setPermission(self
.instanceId_
, requestId
, 'deny');
757 webViewEvent
.window
= windowObj
;
759 var defaultPrevented
= !webviewNode
.dispatchEvent(webViewEvent
);
764 if (defaultPrevented
) {
765 // Make browser plugin track lifetime of |windowObj|.
766 MessagingNatives
.BindToGC(windowObj
, function() {
767 // Avoid showing a warning message if the decision has already been made.
771 WebView
.setPermission(
772 self
.instanceId_
, requestId
, 'default', '', function(allowed
) {
776 showWarningMessage();
781 // The default action is to discard the window.
782 WebView
.setPermission(
783 self
.instanceId_
, requestId
, 'default', '', function(allowed
) {
787 showWarningMessage();
792 WebViewInternal
.prototype.handlePermissionEvent_
=
793 function(event
, webViewEvent
) {
794 var ERROR_MSG_PERMISSION_ALREADY_DECIDED
= '<webview>: ' +
795 'Permission has already been decided for this "permissionrequest" event.';
797 var showWarningMessage = function(permission
) {
798 var WARNING_MSG_PERMISSION_DENIED
= '<webview>: ' +
799 'The permission request for "%1" has been denied.';
801 WARNING_MSG_PERMISSION_DENIED
.replace('%1', permission
));
804 var requestId
= event
.requestId
;
807 var PERMISSION_TYPES
= this.getPermissionTypes_().concat(
808 this.maybeGetExperimentalPermissions_());
809 if (PERMISSION_TYPES
.indexOf(event
.permission
) < 0) {
810 // The permission type is not allowed. Trigger the default response.
811 WebView
.setPermission(
812 self
.instanceId_
, requestId
, 'default', '', function(allowed
) {
816 showWarningMessage(event
.permission
);
821 var browserPluginNode
= this.browserPluginNode_
;
822 var webviewNode
= this.webviewNode_
;
824 var decisionMade
= false;
826 var validateCall = function() {
828 throw new Error(ERROR_MSG_PERMISSION_ALREADY_DECIDED
);
833 // Construct the event.request object.
837 WebView
.setPermission(self
.instanceId_
, requestId
, 'allow');
841 WebView
.setPermission(self
.instanceId_
, requestId
, 'deny');
844 webViewEvent
.request
= request
;
846 var defaultPrevented
= !webviewNode
.dispatchEvent(webViewEvent
);
851 if (defaultPrevented
) {
852 // Make browser plugin track lifetime of |request|.
853 MessagingNatives
.BindToGC(request
, function() {
854 // Avoid showing a warning message if the decision has already been made.
858 WebView
.setPermission(
859 self
.instanceId_
, requestId
, 'default', '', function(allowed
) {
863 showWarningMessage(event
.permission
);
868 WebView
.setPermission(
869 self
.instanceId_
, requestId
, 'default', '', function(allowed
) {
873 showWarningMessage(event
.permission
);
881 WebViewInternal
.prototype.setupWebRequestEvents_ = function() {
884 var createWebRequestEvent = function(webRequestEvent
) {
886 if (!self
[webRequestEvent
.name
+ '_']) {
887 self
[webRequestEvent
.name
+ '_'] =
889 'webview.' + webRequestEvent
.name
,
890 webRequestEvent
.parameters
,
891 webRequestEvent
.extraParameters
, webRequestEvent
.options
,
892 self
.viewInstanceId_
);
894 return self
[webRequestEvent
.name
+ '_'];
898 for (var i
= 0; i
< DeclarativeWebRequestSchema
.events
.length
; ++i
) {
899 var eventSchema
= DeclarativeWebRequestSchema
.events
[i
];
900 var webRequestEvent
= createWebRequestEvent(eventSchema
);
901 this.maybeAttachWebRequestEventToObject_(request
,
906 // Populate the WebRequest events from the API definition.
907 for (var i
= 0; i
< WebRequestSchema
.events
.length
; ++i
) {
908 var webRequestEvent
= createWebRequestEvent(WebRequestSchema
.events
[i
]);
909 Object
.defineProperty(
911 WebRequestSchema
.events
[i
].name
,
913 get: webRequestEvent
,
917 this.maybeAttachWebRequestEventToObject_(this.webviewNode_
,
918 WebRequestSchema
.events
[i
].name
,
921 Object
.defineProperty(
933 WebViewInternal
.prototype.getUserAgent_ = function() {
934 return this.userAgentOverride_
|| navigator
.userAgent
;
938 WebViewInternal
.prototype.isUserAgentOverridden_ = function() {
939 return !!this.userAgentOverride_
&&
940 this.userAgentOverride_
!= navigator
.userAgent
;
944 WebViewInternal
.prototype.setUserAgentOverride_ = function(userAgentOverride
) {
945 this.userAgentOverride_
= userAgentOverride
;
946 if (!this.instanceId_
) {
947 // If we are not attached yet, then we will pick up the user agent on
951 WebView
.overrideUserAgent(this.instanceId_
, userAgentOverride
);
954 // Registers browser plugin <object> custom element.
955 function registerBrowserPluginElement() {
956 var proto
= Object
.create(HTMLObjectElement
.prototype);
958 proto
.createdCallback = function() {
959 this.setAttribute('type', 'application/browser-plugin');
960 // The <object> node fills in the <webview> container.
961 this.style
.width
= '100%';
962 this.style
.height
= '100%';
965 proto
.attributeChangedCallback = function(name
, oldValue
, newValue
) {
966 if (!this.internal_
) {
969 var internal = this.internal_(secret
);
970 internal.handleBrowserPluginAttributeMutation_(name
, newValue
);
973 proto
.attachedCallback = function() {
974 // Load the plugin immediately.
975 var unused
= this.nonExistentAttribute
;
978 WebViewInternal
.BrowserPlugin
=
979 DocumentNatives
.RegisterElement('browser-plugin', {extends: 'object',
982 delete proto
.createdCallback
;
983 delete proto
.attachedCallback
;
984 delete proto
.detachedCallback
;
985 delete proto
.attributeChangedCallback
;
988 // Registers <webview> custom element.
989 function registerWebViewElement() {
990 var proto
= Object
.create(HTMLElement
.prototype);
992 proto
.createdCallback = function() {
993 new WebViewInternal(this);
996 proto
.attributeChangedCallback = function(name
, oldValue
, newValue
) {
997 if (!this.internal_
) {
1000 var internal = this.internal_(secret
);
1001 internal.handleWebviewAttributeMutation_(name
, oldValue
, newValue
);
1004 proto
.back = function() {
1008 proto
.forward = function() {
1012 proto
.canGoBack = function() {
1013 return this.internal_(secret
).canGoBack_();
1016 proto
.canGoForward = function() {
1017 return this.internal_(secret
).canGoForward_();
1020 proto
.getProcessId = function() {
1021 return this.internal_(secret
).getProcessId_();
1024 proto
.go = function(relativeIndex
) {
1025 this.internal_(secret
).go_(relativeIndex
);
1028 proto
.reload = function() {
1029 this.internal_(secret
).reload_();
1032 proto
.stop = function() {
1033 this.internal_(secret
).stop_();
1036 proto
.terminate = function() {
1037 this.internal_(secret
).terminate_();
1040 proto
.executeScript = function(var_args
) {
1041 var internal = this.internal_(secret
);
1042 $Function
.apply(internal.executeScript_
, internal, arguments
);
1045 proto
.insertCSS = function(var_args
) {
1046 var internal = this.internal_(secret
);
1047 $Function
.apply(internal.insertCSS_
, internal, arguments
);
1050 proto
.getUserAgent = function() {
1051 return this.internal_(secret
).getUserAgent_();
1054 proto
.isUserAgentOverridden = function() {
1055 return this.internal_(secret
).isUserAgentOverridden_();
1058 proto
.setUserAgentOverride = function(userAgentOverride
) {
1059 this.internal_(secret
).setUserAgentOverride_(userAgentOverride
);
1061 WebViewInternal
.maybeRegisterExperimentalAPIs(proto
, secret
);
1064 DocumentNatives
.RegisterElement('webview', {prototype: proto
});
1066 // Delete the callbacks so developers cannot call them and produce unexpected
1068 delete proto
.createdCallback
;
1069 delete proto
.attachedCallback
;
1070 delete proto
.detachedCallback
;
1071 delete proto
.attributeChangedCallback
;
1074 var useCapture
= true;
1075 window
.addEventListener('readystatechange', function listener(event
) {
1076 if (document
.readyState
== 'loading')
1079 registerBrowserPluginElement();
1080 registerWebViewElement();
1081 window
.removeEventListener(event
.type
, listener
, useCapture
);
1085 * Implemented when the experimental API is available.
1088 WebViewInternal
.prototype.maybeGetExperimentalEvents_ = function() {};
1091 * Implemented when the experimental API is available.
1094 WebViewInternal
.prototype.maybeAttachWebRequestEventToObject_ = function() {};
1097 * Implemented when the experimental API is available.
1100 WebViewInternal
.prototype.maybeGetExperimentalPermissions_ = function() {
1104 exports
.WebView
= WebView
;
1105 exports
.WebViewInternal
= WebViewInternal
;
1106 exports
.CreateEvent
= CreateEvent
;