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 GuestViewInternal =
11 require('binding').Binding.create('guestViewInternal').generate();
12 var IdGenerator = requireNative('id_generator');
13 // TODO(lazyboy): Rename this to WebViewInternal and call WebViewInternal
15 var WebView = require('webViewInternal').WebView;
16 var WebViewEvents = require('webViewEvents').WebViewEvents;
18 var WEB_VIEW_ATTRIBUTE_AUTOSIZE = 'autosize';
19 var WEB_VIEW_ATTRIBUTE_MAXHEIGHT = 'maxheight';
20 var WEB_VIEW_ATTRIBUTE_MAXWIDTH = 'maxwidth';
21 var WEB_VIEW_ATTRIBUTE_MINHEIGHT = 'minheight';
22 var WEB_VIEW_ATTRIBUTE_MINWIDTH = 'minwidth';
23 var AUTO_SIZE_ATTRIBUTES = [
24 WEB_VIEW_ATTRIBUTE_AUTOSIZE,
25 WEB_VIEW_ATTRIBUTE_MAXHEIGHT,
26 WEB_VIEW_ATTRIBUTE_MAXWIDTH,
27 WEB_VIEW_ATTRIBUTE_MINHEIGHT,
28 WEB_VIEW_ATTRIBUTE_MINWIDTH
31 var WEB_VIEW_ATTRIBUTE_PARTITION = 'partition';
33 var PLUGIN_METHOD_ATTACH = '-internal-attach';
35 var ERROR_MSG_ALREADY_NAVIGATED =
36 'The object has already navigated, so its partition cannot be changed.';
37 var ERROR_MSG_INVALID_PARTITION_ATTRIBUTE = 'Invalid partition attribute.';
39 /** @type {Array.<string>} */
40 var WEB_VIEW_ATTRIBUTES = [
44 /** @class representing state of storage partition. */
45 function Partition() {
46 this.validPartitionId = true;
47 this.persistStorage = false;
48 this.storagePartitionId = '';
51 Partition.prototype.toAttribute = function() {
52 if (!this.validPartitionId) {
55 return (this.persistStorage ? 'persist:' : '') + this.storagePartitionId;
58 Partition.prototype.fromAttribute = function(value, hasNavigated) {
61 result.error = ERROR_MSG_ALREADY_NAVIGATED;
68 var LEN = 'persist:'.length;
69 if (value.substr(0, LEN) == 'persist:') {
70 value = value.substr(LEN);
72 this.validPartitionId = false;
73 result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE;
76 this.persistStorage = true;
78 this.persistStorage = false;
81 this.storagePartitionId = value;
85 // Implemented when the experimental API is available.
86 WebViewInternal.maybeRegisterExperimentalAPIs = function(proto) {}
91 function WebViewInternal(webviewNode) {
92 privates(webviewNode).internal = this;
93 this.webviewNode = webviewNode;
94 this.attached = false;
95 this.elementAttached = false;
97 this.beforeFirstNavigation = true;
98 this.validPartitionId = true;
99 // Used to save some state upon deferred attachment.
100 // If <object> bindings is not available, we defer attachment.
101 // This state contains whether or not the attachment request was for
103 this.deferredAttachState = null;
105 // on* Event handlers.
108 this.browserPluginNode = this.createBrowserPluginNode();
109 var shadowRoot = this.webviewNode.createShadowRoot();
110 shadowRoot.appendChild(this.browserPluginNode);
112 this.setupWebviewNodeAttributes();
113 this.setupFocusPropagation();
114 this.setupWebviewNodeProperties();
116 this.viewInstanceId = IdGenerator.GetNextId();
118 this.partition = new Partition();
119 this.parseAttributes();
121 new WebViewEvents(this, this.viewInstanceId);
127 WebViewInternal.prototype.createBrowserPluginNode = function() {
128 // We create BrowserPlugin as a custom element in order to observe changes
129 // to attributes synchronously.
130 var browserPluginNode = new WebViewInternal.BrowserPlugin();
131 privates(browserPluginNode).internal = this;
133 $Array.forEach(WEB_VIEW_ATTRIBUTES, function(attributeName) {
134 // Only copy attributes that have been assigned values, rather than copying
135 // a series of undefined attributes to BrowserPlugin.
136 if (this.webviewNode.hasAttribute(attributeName)) {
137 browserPluginNode.setAttribute(
138 attributeName, this.webviewNode.getAttribute(attributeName));
139 } else if (this.webviewNode[attributeName]){
140 // Reading property using has/getAttribute does not work on
141 // document.DOMContentLoaded event (but works on
142 // window.DOMContentLoaded event).
143 // So copy from property if copying from attribute fails.
144 browserPluginNode.setAttribute(
145 attributeName, this.webviewNode[attributeName]);
149 return browserPluginNode;
152 WebViewInternal.prototype.getInstanceId = function() {
153 return this.instanceId;
157 * Resets some state upon reattaching <webview> element to the DOM.
159 WebViewInternal.prototype.resetUponReattachment = function() {
160 this.instanceId = undefined;
161 this.beforeFirstNavigation = true;
162 this.validPartitionId = true;
163 this.partition.validPartitionId = true;
166 // Sets <webview>.request property.
167 WebViewInternal.prototype.setRequestPropertyOnWebViewNode = function(request) {
168 Object.defineProperty(
178 WebViewInternal.prototype.setupFocusPropagation = function() {
179 if (!this.webviewNode.hasAttribute('tabIndex')) {
180 // <webview> needs a tabIndex in order to be focusable.
181 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
182 // to allow <webview> to be focusable.
183 // See http://crbug.com/231664.
184 this.webviewNode.setAttribute('tabIndex', -1);
187 this.webviewNode.addEventListener('focus', function(e) {
188 // Focus the BrowserPlugin when the <webview> takes focus.
189 self.browserPluginNode.focus();
191 this.webviewNode.addEventListener('blur', function(e) {
192 // Blur the BrowserPlugin when the <webview> loses focus.
193 self.browserPluginNode.blur();
200 WebViewInternal.prototype.back = function() {
207 WebViewInternal.prototype.forward = function() {
214 WebViewInternal.prototype.canGoBack = function() {
215 return this.entryCount > 1 && this.currentEntryIndex > 0;
221 WebViewInternal.prototype.canGoForward = function() {
222 return this.currentEntryIndex >= 0 &&
223 this.currentEntryIndex < (this.entryCount - 1);
229 WebViewInternal.prototype.clearData = function() {
230 if (!this.instanceId) {
233 var args = $Array.concat([this.instanceId], $Array.slice(arguments));
234 $Function.apply(WebView.clearData, null, args);
240 WebViewInternal.prototype.getProcessId = function() {
241 return this.processId;
247 WebViewInternal.prototype.go = function(relativeIndex) {
248 if (!this.instanceId) {
251 WebView.go(this.instanceId, relativeIndex);
257 WebViewInternal.prototype.print = function() {
258 this.executeScript({code: 'window.print();'});
264 WebViewInternal.prototype.reload = function() {
265 if (!this.instanceId) {
268 WebView.reload(this.instanceId);
274 WebViewInternal.prototype.stop = function() {
275 if (!this.instanceId) {
278 WebView.stop(this.instanceId);
284 WebViewInternal.prototype.terminate = function() {
285 if (!this.instanceId) {
288 WebView.terminate(this.instanceId);
294 WebViewInternal.prototype.validateExecuteCodeCall = function() {
295 var ERROR_MSG_CANNOT_INJECT_SCRIPT = '<webview>: ' +
296 'Script cannot be injected into content until the page has loaded.';
297 if (!this.instanceId) {
298 throw new Error(ERROR_MSG_CANNOT_INJECT_SCRIPT);
305 WebViewInternal.prototype.executeScript = function(var_args) {
306 this.validateExecuteCodeCall();
307 var args = $Array.concat([this.instanceId, this.src],
308 $Array.slice(arguments));
309 $Function.apply(WebView.executeScript, null, args);
315 WebViewInternal.prototype.insertCSS = function(var_args) {
316 this.validateExecuteCodeCall();
317 var args = $Array.concat([this.instanceId, this.src],
318 $Array.slice(arguments));
319 $Function.apply(WebView.insertCSS, null, args);
322 WebViewInternal.prototype.setupAutoSizeProperties = function() {
324 $Array.forEach(AUTO_SIZE_ATTRIBUTES, function(attributeName) {
325 this[attributeName] = this.webviewNode.getAttribute(attributeName);
326 Object.defineProperty(this.webviewNode, attributeName, {
328 return self[attributeName];
330 set: function(value) {
331 self.webviewNode.setAttribute(attributeName, value);
341 WebViewInternal.prototype.setupWebviewNodeProperties = function() {
342 var ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE = '<webview>: ' +
343 'contentWindow is not available at this time. It will become available ' +
344 'when the page has finished loading.';
346 this.setupAutoSizeProperties();
348 var browserPluginNode = this.browserPluginNode;
349 // Expose getters and setters for the attributes.
350 $Array.forEach(WEB_VIEW_ATTRIBUTES, function(attributeName) {
351 Object.defineProperty(this.webviewNode, attributeName, {
353 if (browserPluginNode.hasOwnProperty(attributeName)) {
354 return browserPluginNode[attributeName];
356 return browserPluginNode.getAttribute(attributeName);
359 set: function(value) {
360 if (browserPluginNode.hasOwnProperty(attributeName)) {
361 // Give the BrowserPlugin first stab at the attribute so that it can
362 // throw an exception if there is a problem. This attribute will then
363 // be propagated back to the <webview>.
364 browserPluginNode[attributeName] = value;
366 browserPluginNode.setAttribute(attributeName, value);
373 // <webview> src does not quite behave the same as BrowserPlugin src, and so
374 // we don't simply keep the two in sync.
375 this.src = this.webviewNode.getAttribute('src');
376 Object.defineProperty(this.webviewNode, 'src', {
380 set: function(value) {
381 self.webviewNode.setAttribute('src', value);
387 Object.defineProperty(this.webviewNode, 'name', {
391 set: function(value) {
392 self.webviewNode.setAttribute('name', value);
397 Object.defineProperty(this.webviewNode, 'partition', {
399 return self.partition.toAttribute();
401 set: function(value) {
402 var result = self.partition.fromAttribute(value, self.hasNavigated());
406 self.webviewNode.setAttribute('partition', value);
411 // We cannot use {writable: true} property descriptor because we want a
412 // dynamic getter value.
413 Object.defineProperty(this.webviewNode, 'contentWindow', {
415 if (browserPluginNode.contentWindow)
416 return browserPluginNode.contentWindow;
417 window.console.error(ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE);
427 WebViewInternal.prototype.setupWebviewNodeAttributes = function() {
428 this.setupWebViewSrcAttributeMutationObserver();
434 WebViewInternal.prototype.setupWebViewSrcAttributeMutationObserver =
436 // The purpose of this mutation observer is to catch assignment to the src
437 // attribute without any changes to its value. This is useful in the case
438 // where the webview guest has crashed and navigating to the same address
439 // spawns off a new process.
441 this.srcAndPartitionObserver = new MutationObserver(function(mutations) {
442 $Array.forEach(mutations, function(mutation) {
443 var oldValue = mutation.oldValue;
444 var newValue = self.webviewNode.getAttribute(mutation.attributeName);
445 if (oldValue != newValue) {
448 self.handleWebviewAttributeMutation(
449 mutation.attributeName, oldValue, newValue);
454 attributeOldValue: true,
455 attributeFilter: ['src', 'partition']
457 this.srcAndPartitionObserver.observe(this.webviewNode, params);
463 WebViewInternal.prototype.handleWebviewAttributeMutation =
464 function(name, oldValue, newValue) {
465 // This observer monitors mutations to attributes of the <webview> and
466 // updates the BrowserPlugin properties accordingly. In turn, updating
467 // a BrowserPlugin property will update the corresponding BrowserPlugin
468 // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
470 if (AUTO_SIZE_ATTRIBUTES.indexOf(name) > -1) {
471 this[name] = newValue;
472 if (!this.instanceId) {
475 // Convert autosize attribute to boolean.
476 var autosize = this.webviewNode.hasAttribute(WEB_VIEW_ATTRIBUTE_AUTOSIZE);
477 GuestViewInternal.setAutoSize(this.instanceId, {
478 'enableAutoSize': autosize,
480 'width': parseInt(this.minwidth || 0),
481 'height': parseInt(this.minheight || 0)
484 'width': parseInt(this.maxwidth || 0),
485 'height': parseInt(this.maxheight || 0)
489 } else if (name == 'name') {
490 // We treat null attribute (attribute removed) and the empty string as
492 oldValue = oldValue || '';
493 newValue = newValue || '';
495 if (oldValue === newValue) {
498 this.name = newValue;
499 if (!this.instanceId) {
502 WebView.setName(this.instanceId, newValue);
504 } else if (name == 'src') {
505 // We treat null attribute (attribute removed) and the empty string as
507 oldValue = oldValue || '';
508 newValue = newValue || '';
509 // Once we have navigated, we don't allow clearing the src attribute.
510 // Once <webview> enters a navigated state, it cannot be return back to a
511 // placeholder state.
512 if (newValue == '' && oldValue != '') {
513 // src attribute changes normally initiate a navigation. We suppress
514 // the next src attribute handler call to avoid reloading the page
515 // on every guest-initiated navigation.
516 this.ignoreNextSrcAttributeChange = true;
517 this.webviewNode.setAttribute('src', oldValue);
521 if (this.ignoreNextSrcAttributeChange) {
522 // Don't allow the src mutation observer to see this change.
523 this.srcAndPartitionObserver.takeRecords();
524 this.ignoreNextSrcAttributeChange = false;
528 this.parseSrcAttribute(result);
533 } else if (name == 'partition') {
534 // Note that throwing error here won't synchronously propagate.
535 this.partition.fromAttribute(newValue, this.hasNavigated());
538 // No <webview> -> <object> mutation propagation for these attributes.
539 if (name == 'src' || name == 'partition') {
543 if (this.browserPluginNode.hasOwnProperty(name)) {
544 this.browserPluginNode[name] = newValue;
546 this.browserPluginNode.setAttribute(name, newValue);
553 WebViewInternal.prototype.handleBrowserPluginAttributeMutation =
554 function(name, oldValue, newValue) {
555 if (name == 'internalbindings' && !oldValue && newValue) {
556 this.browserPluginNode.removeAttribute('internalbindings');
558 if (this.deferredAttachState) {
560 // A setTimeout is necessary for the binding to be initialized properly.
561 window.setTimeout(function() {
562 if (self.hasBindings()) {
563 var params = self.buildAttachParams(
564 self.deferredAttachState.isNewWindow);
565 self.browserPluginNode[PLUGIN_METHOD_ATTACH](self.instanceId, params);
566 self.deferredAttachState = null;
573 // This observer monitors mutations to attributes of the BrowserPlugin and
574 // updates the <webview> attributes accordingly.
575 // |newValue| is null if the attribute |name| has been removed.
576 if (newValue != null) {
577 // Update the <webview> attribute to match the BrowserPlugin attribute.
578 // Note: Calling setAttribute on <webview> will trigger its mutation
579 // observer which will then propagate that attribute to BrowserPlugin. In
580 // cases where we permit assigning a BrowserPlugin attribute the same value
581 // again (such as navigation when crashed), this could end up in an infinite
582 // loop. Thus, we avoid this loop by only updating the <webview> attribute
583 // if the BrowserPlugin attributes differs from it.
584 if (newValue != this.webviewNode.getAttribute(name)) {
585 this.webviewNode.setAttribute(name, newValue);
588 // If an attribute is removed from the BrowserPlugin, then remove it
589 // from the <webview> as well.
590 this.webviewNode.removeAttribute(name);
594 WebViewInternal.prototype.onSizeChanged = function(webViewEvent) {
595 var newWidth = webViewEvent.newWidth;
596 var newHeight = webViewEvent.newHeight;
598 var node = this.webviewNode;
600 var width = node.offsetWidth;
601 var height = node.offsetHeight;
603 // Check the current bounds to make sure we do not resize <webview>
604 // outside of current constraints.
606 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH) &&
607 node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]) {
608 maxWidth = node[WEB_VIEW_ATTRIBUTE_MAXWIDTH];
614 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINWIDTH) &&
615 node[WEB_VIEW_ATTRIBUTE_MINWIDTH]) {
616 minWidth = node[WEB_VIEW_ATTRIBUTE_MINWIDTH];
620 if (minWidth > maxWidth) {
625 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXHEIGHT) &&
626 node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT]) {
627 maxHeight = node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT];
632 if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT) &&
633 node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]) {
634 minHeight = node[WEB_VIEW_ATTRIBUTE_MINHEIGHT];
638 if (minHeight > maxHeight) {
639 minHeight = maxHeight;
642 if (!this.webviewNode.hasAttribute(WEB_VIEW_ATTRIBUTE_AUTOSIZE) ||
643 (newWidth >= minWidth &&
644 newWidth <= maxWidth &&
645 newHeight >= minHeight &&
646 newHeight <= maxHeight)) {
647 node.style.width = newWidth + 'px';
648 node.style.height = newHeight + 'px';
649 // Only fire the DOM event if the size of the <webview> has actually
651 this.dispatchEvent(webViewEvent);
655 // Returns true if Browser Plugin bindings is available.
656 // Bindings are unavailable if <object> is not in the render tree.
657 WebViewInternal.prototype.hasBindings = function() {
658 return 'function' == typeof this.browserPluginNode[PLUGIN_METHOD_ATTACH];
661 WebViewInternal.prototype.hasNavigated = function() {
662 return !this.beforeFirstNavigation;
665 /** @return {boolean} */
666 WebViewInternal.prototype.parseSrcAttribute = function(result) {
667 if (!this.partition.validPartitionId) {
668 result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE;
671 this.src = this.webviewNode.getAttribute('src');
677 if (!this.elementAttached) {
681 if (!this.hasGuestInstanceID()) {
682 if (this.beforeFirstNavigation) {
683 this.beforeFirstNavigation = false;
684 this.allocateInstanceId();
689 // Navigate to this.src.
690 WebView.navigate(this.instanceId, this.src);
694 /** @return {boolean} */
695 WebViewInternal.prototype.parseAttributes = function() {
696 var hasNavigated = this.hasNavigated();
697 var attributeValue = this.webviewNode.getAttribute('partition');
698 var result = this.partition.fromAttribute(attributeValue, hasNavigated);
699 return this.parseSrcAttribute(result);
702 WebViewInternal.prototype.hasGuestInstanceID = function() {
703 return this.instanceId != undefined;
706 WebViewInternal.prototype.allocateInstanceId = function() {
707 var storagePartitionId =
708 this.webviewNode.getAttribute(WEB_VIEW_ATTRIBUTE_PARTITION) ||
709 this.webviewNode[WEB_VIEW_ATTRIBUTE_PARTITION];
711 'storagePartitionId': storagePartitionId,
714 GuestViewInternal.createGuest(
717 function(instanceId) {
718 // TODO(lazyboy): Make sure this.autoNavigate_ stuff correctly updated
719 // |self.src| at this point.
720 self.attachWindow(instanceId, false);
724 WebViewInternal.prototype.onFrameNameChanged = function(name) {
725 this.name = name || '';
726 if (this.name === '') {
727 this.webviewNode.removeAttribute('name');
729 this.webviewNode.setAttribute('name', this.name);
733 WebViewInternal.prototype.dispatchEvent = function(webViewEvent) {
734 return this.webviewNode.dispatchEvent(webViewEvent);
738 * Adds an 'on<event>' property on the webview, which can be used to set/unset
741 WebViewInternal.prototype.setupEventProperty = function(eventName) {
742 var propertyName = 'on' + eventName.toLowerCase();
744 var webviewNode = this.webviewNode;
745 Object.defineProperty(webviewNode, propertyName, {
747 return self.on[propertyName];
749 set: function(value) {
750 if (self.on[propertyName])
751 webviewNode.removeEventListener(eventName, self.on[propertyName]);
752 self.on[propertyName] = value;
754 webviewNode.addEventListener(eventName, value);
760 // Updates state upon loadcommit.
761 WebViewInternal.prototype.onLoadCommit = function(
762 currentEntryIndex, entryCount, processId, url, isTopLevel) {
763 this.currentEntryIndex = currentEntryIndex;
764 this.entryCount = entryCount;
765 this.processId = processId;
766 var oldValue = this.webviewNode.getAttribute('src');
768 if (isTopLevel && (oldValue != newValue)) {
769 // Touching the src attribute triggers a navigation. To avoid
770 // triggering a page reload on every guest-initiated navigation,
771 // we use the flag ignoreNextSrcAttributeChange here.
772 this.ignoreNextSrcAttributeChange = true;
773 this.webviewNode.setAttribute('src', newValue);
777 WebViewInternal.prototype.onAttach = function(storagePartitionId) {
778 this.webviewNode.setAttribute('partition', storagePartitionId);
779 this.partition.fromAttribute(storagePartitionId, this.hasNavigated());
784 WebViewInternal.prototype.getUserAgent = function() {
785 return this.userAgentOverride || navigator.userAgent;
789 WebViewInternal.prototype.isUserAgentOverridden = function() {
790 return !!this.userAgentOverride &&
791 this.userAgentOverride != navigator.userAgent;
795 WebViewInternal.prototype.setUserAgentOverride = function(userAgentOverride) {
796 this.userAgentOverride = userAgentOverride;
797 if (!this.instanceId) {
798 // If we are not attached yet, then we will pick up the user agent on
802 WebView.overrideUserAgent(this.instanceId, userAgentOverride);
806 WebViewInternal.prototype.find = function(search_text, options, callback) {
807 if (!this.instanceId) {
810 WebView.find(this.instanceId, search_text, options, callback);
814 WebViewInternal.prototype.stopFinding = function(action) {
815 if (!this.instanceId) {
818 WebView.stopFinding(this.instanceId, action);
822 WebViewInternal.prototype.setZoom = function(zoomFactor, callback) {
823 if (!this.instanceId) {
826 WebView.setZoom(this.instanceId, zoomFactor, callback);
829 WebViewInternal.prototype.getZoom = function(callback) {
830 if (!this.instanceId) {
833 WebView.getZoom(this.instanceId, callback);
836 WebViewInternal.prototype.buildAttachParams = function(isNewWindow) {
838 'autosize': this.webviewNode.hasAttribute(WEB_VIEW_ATTRIBUTE_AUTOSIZE),
839 'instanceId': this.viewInstanceId,
840 'maxheight': parseInt(this.maxheight || 0),
841 'maxwidth': parseInt(this.maxwidth || 0),
842 'minheight': parseInt(this.minheight || 0),
843 'minwidth': parseInt(this.minwidth || 0),
845 // We don't need to navigate new window from here.
846 'src': isNewWindow ? undefined : this.src,
847 // If we have a partition from the opener, that will also be already
848 // set via this.onAttach().
849 'storagePartitionId': this.partition.toAttribute(),
850 'userAgentOverride': this.userAgentOverride
855 WebViewInternal.prototype.attachWindow = function(instanceId, isNewWindow) {
856 this.instanceId = instanceId;
857 var params = this.buildAttachParams(isNewWindow);
859 if (!this.hasBindings()) {
860 // No bindings means that the plugin isn't there (display: none), we defer
861 // attachWindow in this case.
862 this.deferredAttachState = {isNewWindow: isNewWindow};
866 this.deferredAttachState = null;
867 return this.browserPluginNode[PLUGIN_METHOD_ATTACH](this.instanceId, params);
870 // Registers browser plugin <object> custom element.
871 function registerBrowserPluginElement() {
872 var proto = Object.create(HTMLObjectElement.prototype);
874 proto.createdCallback = function() {
875 this.setAttribute('type', 'application/browser-plugin');
876 // The <object> node fills in the <webview> container.
877 this.style.width = '100%';
878 this.style.height = '100%';
881 proto.attributeChangedCallback = function(name, oldValue, newValue) {
882 var internal = privates(this).internal;
886 internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue);
889 proto.attachedCallback = function() {
890 // Load the plugin immediately.
891 var unused = this.nonExistentAttribute;
894 WebViewInternal.BrowserPlugin =
895 DocumentNatives.RegisterElement('browserplugin', {extends: 'object',
898 delete proto.createdCallback;
899 delete proto.attachedCallback;
900 delete proto.detachedCallback;
901 delete proto.attributeChangedCallback;
904 // Registers <webview> custom element.
905 function registerWebViewElement() {
906 var proto = Object.create(HTMLElement.prototype);
908 proto.createdCallback = function() {
909 new WebViewInternal(this);
912 proto.attributeChangedCallback = function(name, oldValue, newValue) {
913 var internal = privates(this).internal;
917 internal.handleWebviewAttributeMutation(name, oldValue, newValue);
920 proto.detachedCallback = function() {
921 var internal = privates(this).internal;
925 internal.elementAttached = false;
928 proto.attachedCallback = function() {
929 var internal = privates(this).internal;
933 if (!internal.elementAttached) {
934 internal.elementAttached = true;
935 internal.resetUponReattachment();
936 internal.parseAttributes();
959 'isUserAgentOverridden',
960 'setUserAgentOverride'
963 // Forward proto.foo* method calls to WebViewInternal.foo*.
964 for (var i = 0; methods[i]; ++i) {
965 var createHandler = function(m) {
966 return function(var_args) {
967 var internal = privates(this).internal;
968 return $Function.apply(internal[m], internal, arguments);
971 proto[methods[i]] = createHandler(methods[i]);
974 WebViewInternal.maybeRegisterExperimentalAPIs(proto);
977 DocumentNatives.RegisterElement('webview', {prototype: proto});
979 // Delete the callbacks so developers cannot call them and produce unexpected
981 delete proto.createdCallback;
982 delete proto.attachedCallback;
983 delete proto.detachedCallback;
984 delete proto.attributeChangedCallback;
987 var useCapture = true;
988 window.addEventListener('readystatechange', function listener(event) {
989 if (document.readyState == 'loading')
992 registerBrowserPluginElement();
993 registerWebViewElement();
994 window.removeEventListener(event.type, listener, useCapture);
998 * Implemented when the experimental API is available.
1001 WebViewInternal.prototype.maybeGetExperimentalEvents = function() {};
1004 * Implemented when the experimental API is available.
1007 WebViewInternal.prototype.maybeGetExperimentalPermissions = function() {
1012 * Calls to show contextmenu right away instead of dispatching a 'contextmenu'
1014 * This will be overridden in web_view_experimental.js to implement contextmenu
1017 WebViewInternal.prototype.maybeHandleContextMenu = function(e, webViewEvent) {
1018 var requestId = e.requestId;
1019 // Setting |params| = undefined will show the context menu unmodified, hence
1020 // the 'contextmenu' API is disabled for stable channel.
1021 var params = undefined;
1022 WebView.showContextMenu(this.instanceId, requestId, params);
1026 * Implemented when the experimental API is available.
1029 WebViewInternal.prototype.setupExperimentalContextMenus = function() {};
1031 exports.WebView = WebView;
1032 exports.WebViewInternal = WebViewInternal;