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
= [
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'),
70 evt
: CreateEvent('webview.onExit'),
71 fields
: ['processId', 'reason']
75 customHandler: function(webViewInternal
, event
, webViewEvent
) {
76 webViewInternal
.handleLoadAbortEvent(event
, webViewEvent
);
78 evt
: CreateEvent('webview.onLoadAbort'),
79 fields
: ['url', 'isTopLevel', 'reason']
82 customHandler: function(webViewInternal
, event
, webViewEvent
) {
83 webViewInternal
.handleLoadCommitEvent(event
, webViewEvent
);
85 evt
: CreateEvent('webview.onLoadCommit'),
86 fields
: ['url', 'isTopLevel']
89 evt
: CreateEvent('webview.onLoadProgress'),
90 fields
: ['url', 'progress']
93 evt
: CreateEvent('webview.onLoadRedirect'),
94 fields
: ['isTopLevel', 'oldUrl', 'newUrl']
97 evt
: CreateEvent('webview.onLoadStart'),
98 fields
: ['url', 'isTopLevel']
101 evt
: CreateEvent('webview.onLoadStop'),
106 customHandler: function(webViewInternal
, event
, webViewEvent
) {
107 webViewInternal
.handleNewWindowEvent(event
, webViewEvent
);
109 evt
: CreateEvent('webview.onNewWindow'),
114 'windowOpenDisposition',
118 'permissionrequest': {
120 customHandler: function(webViewInternal
, event
, webViewEvent
) {
121 webViewInternal
.handlePermissionEvent(event
, webViewEvent
);
123 evt
: CreateEvent('webview.onPermissionRequest'),
126 'lastUnlockedBySelf',
135 evt
: CreateEvent('webview.onResponsive'),
136 fields
: ['processId']
139 evt
: CreateEvent('webview.onSizeChanged'),
140 customHandler: function(webViewInternal
, event
, webViewEvent
) {
141 webViewInternal
.handleSizeChangedEvent(event
, webViewEvent
);
143 fields
: ['oldHeight', 'oldWidth', 'newHeight', 'newWidth']
146 evt
: CreateEvent('webview.onUnresponsive'),
147 fields
: ['processId']
151 // Implemented when the experimental API is available.
152 WebViewInternal
.maybeRegisterExperimentalAPIs = function(proto
) {}
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();
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
]);
196 return browserPluginNode
;
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);
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();
224 WebViewInternal
.prototype.canGoBack = function() {
225 return this.entryCount
> 1 && this.currentEntryIndex
> 0;
231 WebViewInternal
.prototype.canGoForward = function() {
232 return this.currentEntryIndex
>= 0 &&
233 this.currentEntryIndex
< (this.entryCount
- 1);
239 WebViewInternal
.prototype.getProcessId = function() {
240 return this.processId
;
246 WebViewInternal
.prototype.go = function(relativeIndex
) {
247 if (!this.instanceId
) {
250 WebView
.go(this.instanceId
, relativeIndex
);
256 WebViewInternal
.prototype.reload = function() {
257 if (!this.instanceId
) {
260 WebView
.reload(this.instanceId
);
266 WebViewInternal
.prototype.stop = function() {
267 if (!this.instanceId
) {
270 WebView
.stop(this.instanceId
);
276 WebViewInternal
.prototype.terminate = function() {
277 if (!this.instanceId
) {
280 WebView
.terminate(this.instanceId
);
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
);
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
);
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
);
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.';
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
, {
326 if (browserPluginNode
.hasOwnProperty(attributeName
)) {
327 return browserPluginNode
[attributeName
];
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
;
339 browserPluginNode
.setAttribute(attributeName
, value
);
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', {
353 set: function(value
) {
354 self
.webviewNode
.setAttribute('src', value
);
360 // We cannot use {writable: true} property descriptor because we want a
361 // dynamic getter value.
362 Object
.defineProperty(this.webviewNode
, 'contentWindow', {
364 if (browserPluginNode
.contentWindow
)
365 return browserPluginNode
.contentWindow
;
366 window
.console
.error(ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE
);
376 WebViewInternal
.prototype.setupWebviewNodeAttributes = function() {
377 this.setupWebViewSrcAttributeMutationObserver();
383 WebViewInternal
.prototype.setupWebViewSrcAttributeMutationObserver
=
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.
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
) {
397 self
.handleWebviewAttributeMutation(
398 mutation
.attributeName
, oldValue
, newValue
);
403 attributeOldValue
: true,
404 attributeFilter
: ['src']
406 this.srcObserver
.observe(this.webviewNode
, params
);
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
420 // We treat null attribute (attribute removed) and the empty string as
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
);
436 if (this.ignoreNextSrcAttributeChange
) {
437 // Don't allow the src mutation observer to see this change.
438 this.srcObserver
.takeRecords();
439 this.ignoreNextSrcAttributeChange
= false;
443 if (this.browserPluginNode
.hasOwnProperty(name
)) {
444 this.browserPluginNode
[name
] = newValue
;
446 this.browserPluginNode
.setAttribute(name
, newValue
);
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
);
470 // If an attribute is removed from the BrowserPlugin, then remove it
471 // from the <webview> as well.
472 this.webviewNode
.removeAttribute(name
);
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.
497 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH
) &&
498 node
[WEB_VIEW_ATTRIBUTE_MAXWIDTH
]) {
499 maxWidth
= node
[WEB_VIEW_ATTRIBUTE_MAXWIDTH
];
505 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MINWIDTH
) &&
506 node
[WEB_VIEW_ATTRIBUTE_MINWIDTH
]) {
507 minWidth
= node
[WEB_VIEW_ATTRIBUTE_MINWIDTH
];
511 if (minWidth
> maxWidth
) {
516 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXHEIGHT
) &&
517 node
[WEB_VIEW_ATTRIBUTE_MAXHEIGHT
]) {
518 maxHeight
= node
[WEB_VIEW_ATTRIBUTE_MAXHEIGHT
];
523 if (node
.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT
) &&
524 node
[WEB_VIEW_ATTRIBUTE_MINHEIGHT
]) {
525 minHeight
= node
[WEB_VIEW_ATTRIBUTE_MINHEIGHT
];
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
);
546 WebViewInternal
.prototype.setupWebviewNodeEvents = function() {
548 this.viewInstanceId
= IdGenerator
.GetNextId();
549 var onInstanceIdAllocated = function(e
) {
550 var detail
= e
.detail
? JSON
.parse(e
.detail
) : {};
551 self
.instanceId
= detail
.windowId
;
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();
571 var events
= self
.getEvents();
572 for (var eventName
in events
) {
573 this.setupEventProperty(eventName
);
580 WebViewInternal
.prototype.setupEvent = function(eventName
, eventInfo
) {
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
);
597 webviewNode
.dispatchEvent(webViewEvent
);
598 }, {instanceId
: self
.instanceId
});
602 * Adds an 'on<event>' property on the webview, which can be used to set/unset
606 WebViewInternal
.prototype.setupEventProperty = function(eventName
) {
607 var propertyName
= 'on' + eventName
.toLowerCase();
609 var webviewNode
= this.webviewNode
;
610 Object
.defineProperty(webviewNode
, propertyName
, {
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
;
619 webviewNode
.addEventListener(eventName
, value
);
628 WebViewInternal
.prototype.getPermissionTypes = function() {
630 ['media', 'geolocation', 'pointerLock', 'download', 'loadplugin'];
631 return permissions
.concat(this.maybeGetExperimentalPermissions());
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
);
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
);
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
);
688 var browserPluginNode
= this.browserPluginNode
;
689 var webviewNode
= this.webviewNode
;
691 var requestId
= event
.requestId
;
692 var actionTaken
= false;
694 var validateCall = function () {
696 throw new Error(ERROR_MSG_NEWWINDOW_ACTION_ALREADY_TAKEN
);
702 attach: function(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
711 setTimeout(function() {
713 browserPluginNode
['-internal-attachWindowTo'](webview
,
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');
726 discard: function() {
728 WebView
.setPermission(self
.instanceId
, requestId
, 'deny');
731 webViewEvent
.window
= windowObj
;
733 var defaultPrevented
= !webviewNode
.dispatchEvent(webViewEvent
);
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.
745 WebView
.setPermission(
746 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
750 showWarningMessage();
755 // The default action is to discard the window.
756 WebView
.setPermission(
757 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
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.';
775 WARNING_MSG_PERMISSION_DENIED
.replace('%1', permission
));
778 var requestId
= event
.requestId
;
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
) {
788 showWarningMessage(event
.permission
);
793 var browserPluginNode
= this.browserPluginNode
;
794 var webviewNode
= this.webviewNode
;
796 var decisionMade
= false;
798 var validateCall = function() {
800 throw new Error(ERROR_MSG_PERMISSION_ALREADY_DECIDED
);
805 // Construct the event.request object.
809 WebView
.setPermission(self
.instanceId
, requestId
, 'allow');
813 WebView
.setPermission(self
.instanceId
, requestId
, 'deny');
816 webViewEvent
.request
= request
;
818 var defaultPrevented
= !webviewNode
.dispatchEvent(webViewEvent
);
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.
830 WebView
.setPermission(
831 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
835 showWarningMessage(event
.permission
);
840 WebView
.setPermission(
841 self
.instanceId
, requestId
, 'default', '', function(allowed
) {
845 showWarningMessage(event
.permission
);
853 WebViewInternal
.prototype.setupWebRequestEvents = function() {
856 var createWebRequestEvent = function(webRequestEvent
) {
858 if (!self
[webRequestEvent
.name
]) {
859 self
[webRequestEvent
.name
] =
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
,
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(
883 WebRequestSchema
.events
[i
].name
,
885 get: webRequestEvent
,
889 this.maybeAttachWebRequestEventToObject(this.webviewNode
,
890 WebRequestSchema
.events
[i
].name
,
893 Object
.defineProperty(
905 WebViewInternal
.prototype.getUserAgent = function() {
906 return this.userAgentOverride
|| navigator
.userAgent
;
910 WebViewInternal
.prototype.isUserAgentOverridden = function() {
911 return !!this.userAgentOverride
&&
912 this.userAgentOverride
!= navigator
.userAgent
;
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
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;
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',
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;
973 internal.handleWebviewAttributeMutation(name
, oldValue
, newValue
);
976 proto
.back = function() {
980 proto
.forward = function() {
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
);
1036 DocumentNatives
.RegisterElement('webview', {prototype: proto
});
1038 // Delete the callbacks so developers cannot call them and produce unexpected
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')
1051 registerBrowserPluginElement();
1052 registerWebViewElement();
1053 window
.removeEventListener(event
.type
, listener
, useCapture
);
1057 * Implemented when the experimental API is available.
1060 WebViewInternal
.prototype.maybeGetExperimentalEvents = function() {};
1063 * Implemented when the experimental API is available.
1066 WebViewInternal
.prototype.maybeAttachWebRequestEventToObject = function() {};
1069 * Implemented when the experimental API is available.
1072 WebViewInternal
.prototype.maybeGetExperimentalPermissions = function() {
1076 exports
.WebView
= WebView
;
1077 exports
.WebViewInternal
= WebViewInternal
;
1078 exports
.CreateEvent
= CreateEvent
;