blob: ad90bfcb095dc98ad857ef9b2129ecba8425a0cc [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This module implements Webview (<webview>) as a custom element that wraps a
// BrowserPlugin object element. The object element is hidden within
// the shadow DOM of the Webview element.
var DocumentNatives = requireNative('document_natives');
var GuestViewInternal =
require('binding').Binding.create('guestViewInternal').generate();
var IdGenerator = requireNative('id_generator');
var WebView = require('webview').WebView;
var WebViewEvents = require('webViewEvents').WebViewEvents;
var WEB_VIEW_ATTRIBUTE_MAXHEIGHT = 'maxheight';
var WEB_VIEW_ATTRIBUTE_MAXWIDTH = 'maxwidth';
var WEB_VIEW_ATTRIBUTE_MINHEIGHT = 'minheight';
var WEB_VIEW_ATTRIBUTE_MINWIDTH = 'minwidth';
var WEB_VIEW_ATTRIBUTE_PARTITION = 'partition';
var PLUGIN_METHOD_ATTACH = '-internal-attach';
var ERROR_MSG_ALREADY_NAVIGATED =
'The object has already navigated, so its partition cannot be changed.';
var ERROR_MSG_INVALID_PARTITION_ATTRIBUTE = 'Invalid partition attribute.';
/** @type {Array.<string>} */
var WEB_VIEW_ATTRIBUTES = [
'allowtransparency',
'autosize',
WEB_VIEW_ATTRIBUTE_MINHEIGHT,
WEB_VIEW_ATTRIBUTE_MINWIDTH,
WEB_VIEW_ATTRIBUTE_MAXHEIGHT,
WEB_VIEW_ATTRIBUTE_MAXWIDTH
];
/** @class representing state of storage partition. */
function Partition() {
this.validPartitionId = true;
this.persistStorage = false;
this.storagePartitionId = '';
};
Partition.prototype.toAttribute = function() {
if (!this.validPartitionId) {
return '';
}
return (this.persistStorage ? 'persist:' : '') + this.storagePartitionId;
};
Partition.prototype.fromAttribute = function(value, hasNavigated) {
var result = {};
if (hasNavigated) {
result.error = ERROR_MSG_ALREADY_NAVIGATED;
return result;
}
if (!value) {
value = '';
}
var LEN = 'persist:'.length;
if (value.substr(0, LEN) == 'persist:') {
value = value.substr(LEN);
if (!value) {
this.validPartitionId = false;
result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE;
return result;
}
this.persistStorage = true;
} else {
this.persistStorage = false;
}
this.storagePartitionId = value;
return result;
};
// Implemented when the experimental API is available.
WebViewInternal.maybeRegisterExperimentalAPIs = function(proto) {}
/**
* @constructor
*/
function WebViewInternal(webviewNode) {
privates(webviewNode).internal = this;
this.webviewNode = webviewNode;
this.attached = false;
this.beforeFirstNavigation = true;
this.validPartitionId = true;
// Used to save some state upon deferred attachment.
// If <object> bindings is not available, we defer attachment.
// This state contains whether or not the attachment request was for
// newwindow.
this.deferredAttachState = null;
// on* Event handlers.
this.on = {};
this.browserPluginNode = this.createBrowserPluginNode();
var shadowRoot = this.webviewNode.createShadowRoot();
shadowRoot.appendChild(this.browserPluginNode);
this.setupWebviewNodeAttributes();
this.setupFocusPropagation();
this.setupWebviewNodeProperties();
this.viewInstanceId = IdGenerator.GetNextId();
this.partition = new Partition();
this.parseAttributes();
new WebViewEvents(this, this.viewInstanceId);
}
/**
* @private
*/
WebViewInternal.prototype.createBrowserPluginNode = function() {
// We create BrowserPlugin as a custom element in order to observe changes
// to attributes synchronously.
var browserPluginNode = new WebViewInternal.BrowserPlugin();
privates(browserPluginNode).internal = this;
$Array.forEach(WEB_VIEW_ATTRIBUTES, function(attributeName) {
// Only copy attributes that have been assigned values, rather than copying
// a series of undefined attributes to BrowserPlugin.
if (this.webviewNode.hasAttribute(attributeName)) {
browserPluginNode.setAttribute(
attributeName, this.webviewNode.getAttribute(attributeName));
} else if (this.webviewNode[attributeName]){
// Reading property using has/getAttribute does not work on
// document.DOMContentLoaded event (but works on
// window.DOMContentLoaded event).
// So copy from property if copying from attribute fails.
browserPluginNode.setAttribute(
attributeName, this.webviewNode[attributeName]);
}
}, this);
return browserPluginNode;
};
WebViewInternal.prototype.getInstanceId = function() {
return this.instanceId;
};
/**
* Resets some state upon reattaching <webview> element to the DOM.
*/
WebViewInternal.prototype.resetUponReattachment = function() {
this.instanceId = undefined;
this.beforeFirstNavigation = true;
this.validPartitionId = true;
this.partition.validPartitionId = true;
};
// Sets <webview>.request property.
WebViewInternal.prototype.setRequestPropertyOnWebViewNode = function(request) {
Object.defineProperty(
this.webviewNode,
'request',
{
value: request,
enumerable: true
}
);
};
WebViewInternal.prototype.setupFocusPropagation = function() {
if (!this.webviewNode.hasAttribute('tabIndex')) {
// <webview> needs a tabIndex in order to be focusable.
// TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
// to allow <webview> to be focusable.
// See http://crbug.com/231664.
this.webviewNode.setAttribute('tabIndex', -1);
}
var self = this;
this.webviewNode.addEventListener('focus', function(e) {
// Focus the BrowserPlugin when the <webview> takes focus.
self.browserPluginNode.focus();
});
this.webviewNode.addEventListener('blur', function(e) {
// Blur the BrowserPlugin when the <webview> loses focus.
self.browserPluginNode.blur();
});
};
/**
* @private
*/
WebViewInternal.prototype.back = function() {
return this.go(-1);
};
/**
* @private
*/
WebViewInternal.prototype.forward = function() {
return this.go(1);
};
/**
* @private
*/
WebViewInternal.prototype.canGoBack = function() {
return this.entryCount > 1 && this.currentEntryIndex > 0;
};
/**
* @private
*/
WebViewInternal.prototype.canGoForward = function() {
return this.currentEntryIndex >= 0 &&
this.currentEntryIndex < (this.entryCount - 1);
};
/**
* @private
*/
WebViewInternal.prototype.clearData = function() {
if (!this.instanceId) {
return;
}
var args = $Array.concat([this.instanceId], $Array.slice(arguments));
$Function.apply(WebView.clearData, null, args);
};
/**
* @private
*/
WebViewInternal.prototype.getProcessId = function() {
return this.processId;
};
/**
* @private
*/
WebViewInternal.prototype.go = function(relativeIndex) {
if (!this.instanceId) {
return;
}
WebView.go(this.instanceId, relativeIndex);
};
/**
* @private
*/
WebViewInternal.prototype.reload = function() {
if (!this.instanceId) {
return;
}
WebView.reload(this.instanceId);
};
/**
* @private
*/
WebViewInternal.prototype.stop = function() {
if (!this.instanceId) {
return;
}
WebView.stop(this.instanceId);
};
/**
* @private
*/
WebViewInternal.prototype.terminate = function() {
if (!this.instanceId) {
return;
}
WebView.terminate(this.instanceId);
};
/**
* @private
*/
WebViewInternal.prototype.validateExecuteCodeCall = function() {
var ERROR_MSG_CANNOT_INJECT_SCRIPT = '<webview>: ' +
'Script cannot be injected into content until the page has loaded.';
if (!this.instanceId) {
throw new Error(ERROR_MSG_CANNOT_INJECT_SCRIPT);
}
};
/**
* @private
*/
WebViewInternal.prototype.executeScript = function(var_args) {
this.validateExecuteCodeCall();
var args = $Array.concat([this.instanceId, this.src],
$Array.slice(arguments));
$Function.apply(WebView.executeScript, null, args);
};
/**
* @private
*/
WebViewInternal.prototype.insertCSS = function(var_args) {
this.validateExecuteCodeCall();
var args = $Array.concat([this.instanceId, this.src],
$Array.slice(arguments));
$Function.apply(WebView.insertCSS, null, args);
};
/**
* @private
*/
WebViewInternal.prototype.setupWebviewNodeProperties = function() {
var ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE = '<webview>: ' +
'contentWindow is not available at this time. It will become available ' +
'when the page has finished loading.';
var self = this;
var browserPluginNode = this.browserPluginNode;
// Expose getters and setters for the attributes.
$Array.forEach(WEB_VIEW_ATTRIBUTES, function(attributeName) {
Object.defineProperty(this.webviewNode, attributeName, {
get: function() {
if (browserPluginNode.hasOwnProperty(attributeName)) {
return browserPluginNode[attributeName];
} else {
return browserPluginNode.getAttribute(attributeName);
}
},
set: function(value) {
if (browserPluginNode.hasOwnProperty(attributeName)) {
// Give the BrowserPlugin first stab at the attribute so that it can
// throw an exception if there is a problem. This attribute will then
// be propagated back to the <webview>.
browserPluginNode[attributeName] = value;
} else {
browserPluginNode.setAttribute(attributeName, value);
}
},
enumerable: true
});
}, this);
// <webview> src does not quite behave the same as BrowserPlugin src, and so
// we don't simply keep the two in sync.
this.src = this.webviewNode.getAttribute('src');
Object.defineProperty(this.webviewNode, 'src', {
get: function() {
return self.src;
},
set: function(value) {
self.webviewNode.setAttribute('src', value);
},
// No setter.
enumerable: true
});
Object.defineProperty(this.webviewNode, 'name', {
get: function() {
return self.name;
},
set: function(value) {
self.webviewNode.setAttribute('name', value);
},
enumerable: true
});
Object.defineProperty(this.webviewNode, 'partition', {
get: function() {
return self.partition.toAttribute();
},
set: function(value) {
var result = self.partition.fromAttribute(value, self.hasNavigated());
if (result.error) {
throw result.error;
}
self.webviewNode.setAttribute('partition', value);
},
enumerable: true
});
// We cannot use {writable: true} property descriptor because we want a
// dynamic getter value.
Object.defineProperty(this.webviewNode, 'contentWindow', {
get: function() {
if (browserPluginNode.contentWindow)
return browserPluginNode.contentWindow;
window.console.error(ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE);
},
// No setter.
enumerable: true
});
};
/**
* @private
*/
WebViewInternal.prototype.setupWebviewNodeAttributes = function() {
this.setupWebViewSrcAttributeMutationObserver();
};
/**
* @private
*/
WebViewInternal.prototype.setupWebViewSrcAttributeMutationObserver =
function() {
// The purpose of this mutation observer is to catch assignment to the src
// attribute without any changes to its value. This is useful in the case
// where the webview guest has crashed and navigating to the same address
// spawns off a new process.
var self = this;
this.srcAndPartitionObserver = new MutationObserver(function(mutations) {
$Array.forEach(mutations, function(mutation) {
var oldValue = mutation.oldValue;
var newValue = self.webviewNode.getAttribute(mutation.attributeName);
if (oldValue != newValue) {
return;
}
self.handleWebviewAttributeMutation(
mutation.attributeName, oldValue, newValue);
});
});
var params = {
attributes: true,
attributeOldValue: true,
attributeFilter: ['src', 'partition']
};
this.srcAndPartitionObserver.observe(this.webviewNode, params);
};
/**
* @private
*/
WebViewInternal.prototype.handleWebviewAttributeMutation =
function(name, oldValue, newValue) {
// This observer monitors mutations to attributes of the <webview> and
// updates the BrowserPlugin properties accordingly. In turn, updating
// a BrowserPlugin property will update the corresponding BrowserPlugin
// attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
// details.
if (name == 'name') {
// We treat null attribute (attribute removed) and the empty string as
// one case.
oldValue = oldValue || '';
newValue = newValue || '';
if (oldValue === newValue) {
return;
}
this.name = newValue;
if (!this.instanceId) {
return;
}
WebView.setName(this.instanceId, newValue);
return;
} else if (name == 'src') {
// We treat null attribute (attribute removed) and the empty string as
// one case.
oldValue = oldValue || '';
newValue = newValue || '';
// Once we have navigated, we don't allow clearing the src attribute.
// Once <webview> enters a navigated state, it cannot be return back to a
// placeholder state.
if (newValue == '' && oldValue != '') {
// src attribute changes normally initiate a navigation. We suppress
// the next src attribute handler call to avoid reloading the page
// on every guest-initiated navigation.
this.ignoreNextSrcAttributeChange = true;
this.webviewNode.setAttribute('src', oldValue);
return;
}
this.src = newValue;
if (this.ignoreNextSrcAttributeChange) {
// Don't allow the src mutation observer to see this change.
this.srcAndPartitionObserver.takeRecords();
this.ignoreNextSrcAttributeChange = false;
return;
}
var result = {};
this.parseSrcAttribute(result);
if (result.error) {
throw result.error;
}
} else if (name == 'partition') {
// Note that throwing error here won't synchronously propagate.
this.partition.fromAttribute(newValue, this.hasNavigated());
}
// No <webview> -> <object> mutation propagation for these attributes.
if (name == 'src' || name == 'partition') {
return;
}
if (this.browserPluginNode.hasOwnProperty(name)) {
this.browserPluginNode[name] = newValue;
} else {
this.browserPluginNode.setAttribute(name, newValue);
}
};
/**
* @private
*/
WebViewInternal.prototype.handleBrowserPluginAttributeMutation =
function(name, oldValue, newValue) {
if (name == 'internalbindings' && !oldValue && newValue) {
this.browserPluginNode.removeAttribute('internalbindings');
if (this.deferredAttachState) {
var self = this;
// A setTimeout is necessary for the binding to be initialized properly.
window.setTimeout(function() {
if (self.hasBindings()) {
var params = self.buildAttachParams(
self.deferredAttachState.isNewWindow);
self.browserPluginNode[PLUGIN_METHOD_ATTACH](self.instanceId, params);
self.deferredAttachState = null;
}
}, 0);
}
return;
}
// This observer monitors mutations to attributes of the BrowserPlugin and
// updates the <webview> attributes accordingly.
// |newValue| is null if the attribute |name| has been removed.
if (newValue != null) {
// Update the <webview> attribute to match the BrowserPlugin attribute.
// Note: Calling setAttribute on <webview> will trigger its mutation
// observer which will then propagate that attribute to BrowserPlugin. In
// cases where we permit assigning a BrowserPlugin attribute the same value
// again (such as navigation when crashed), this could end up in an infinite
// loop. Thus, we avoid this loop by only updating the <webview> attribute
// if the BrowserPlugin attributes differs from it.
if (newValue != this.webviewNode.getAttribute(name)) {
this.webviewNode.setAttribute(name, newValue);
}
} else {
// If an attribute is removed from the BrowserPlugin, then remove it
// from the <webview> as well.
this.webviewNode.removeAttribute(name);
}
};
WebViewInternal.prototype.onSizeChanged = function(newWidth, newHeight) {
var node = this.webviewNode;
var width = node.offsetWidth;
var height = node.offsetHeight;
// Check the current bounds to make sure we do not resize <webview>
// outside of current constraints.
var maxWidth;
if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH) &&
node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]) {
maxWidth = node[WEB_VIEW_ATTRIBUTE_MAXWIDTH];
} else {
maxWidth = width;
}
var minWidth;
if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINWIDTH) &&
node[WEB_VIEW_ATTRIBUTE_MINWIDTH]) {
minWidth = node[WEB_VIEW_ATTRIBUTE_MINWIDTH];
} else {
minWidth = width;
}
if (minWidth > maxWidth) {
minWidth = maxWidth;
}
var maxHeight;
if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXHEIGHT) &&
node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT]) {
maxHeight = node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT];
} else {
maxHeight = height;
}
var minHeight;
if (node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT) &&
node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]) {
minHeight = node[WEB_VIEW_ATTRIBUTE_MINHEIGHT];
} else {
minHeight = height;
}
if (minHeight > maxHeight) {
minHeight = maxHeight;
}
if (newWidth >= minWidth &&
newWidth <= maxWidth &&
newHeight >= minHeight &&
newHeight <= maxHeight) {
node.style.width = newWidth + 'px';
node.style.height = newHeight + 'px';
}
};
// Returns true if Browser Plugin bindings is available.
// Bindings are unavailable if <object> is not in the render tree.
WebViewInternal.prototype.hasBindings = function() {
return 'function' == typeof this.browserPluginNode[PLUGIN_METHOD_ATTACH];
};
WebViewInternal.prototype.hasNavigated = function() {
return !this.beforeFirstNavigation;
};
/** @return {boolean} */
WebViewInternal.prototype.parseSrcAttribute = function(result) {
if (!this.partition.validPartitionId) {
result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE;
return false;
}
this.src = this.webviewNode.getAttribute('src');
if (!this.src) {
return true;
}
if (!this.hasGuestInstanceID()) {
if (this.beforeFirstNavigation) {
this.beforeFirstNavigation = false;
this.allocateInstanceId();
}
return true;
}
// Navigate to this.src.
WebView.navigate(this.instanceId, this.src);
return true;
};
/** @return {boolean} */
WebViewInternal.prototype.parseAttributes = function() {
var hasNavigated = this.hasNavigated();
var attributeValue = this.webviewNode.getAttribute('partition');
var result = this.partition.fromAttribute(attributeValue, hasNavigated);
return this.parseSrcAttribute(result);
};
WebViewInternal.prototype.hasGuestInstanceID = function() {
return this.instanceId != undefined;
};
WebViewInternal.prototype.allocateInstanceId = function() {
// Parse .src and .partition.
var self = this;
GuestViewInternal.allocateInstanceId(
function(instanceId) {
// TODO(lazyboy): Make sure this.autoNavigate_ stuff correctly updated
// |self.src| at this point.
self.attachWindow(instanceId, false);
});
};
WebViewInternal.prototype.onFrameNameChanged = function(name) {
this.name = name || '';
if (this.name === '') {
this.webviewNode.removeAttribute('name');
} else {
this.webviewNode.setAttribute('name', this.name);
}
};
WebViewInternal.prototype.dispatchEvent = function(webViewEvent) {
return this.webviewNode.dispatchEvent(webViewEvent);
};
/**
* Adds an 'on<event>' property on the webview, which can be used to set/unset
* an event handler.
*/
WebViewInternal.prototype.setupEventProperty = function(eventName) {
var propertyName = 'on' + eventName.toLowerCase();
var self = this;
var webviewNode = this.webviewNode;
Object.defineProperty(webviewNode, propertyName, {
get: function() {
return self.on[propertyName];
},
set: function(value) {
if (self.on[propertyName])
webviewNode.removeEventListener(eventName, self.on[propertyName]);
self.on[propertyName] = value;
if (value)
webviewNode.addEventListener(eventName, value);
},
enumerable: true
});
};
// Updates state upon loadcommit.
WebViewInternal.prototype.onLoadCommit = function(
currentEntryIndex, entryCount, processId, url, isTopLevel) {
this.currentEntryIndex = currentEntryIndex;
this.entryCount = entryCount;
this.processId = processId;
var oldValue = this.webviewNode.getAttribute('src');
var newValue = url;
if (isTopLevel && (oldValue != newValue)) {
// Touching the src attribute triggers a navigation. To avoid
// triggering a page reload on every guest-initiated navigation,
// we use the flag ignoreNextSrcAttributeChange here.
this.ignoreNextSrcAttributeChange = true;
this.webviewNode.setAttribute('src', newValue);
}
};
WebViewInternal.prototype.onAttach = function(storagePartitionId) {
this.webviewNode.setAttribute('partition', storagePartitionId);
this.partition.fromAttribute(storagePartitionId, this.hasNavigated());
};
/** @private */
WebViewInternal.prototype.getUserAgent = function() {
return this.userAgentOverride || navigator.userAgent;
};
/** @private */
WebViewInternal.prototype.isUserAgentOverridden = function() {
return !!this.userAgentOverride &&
this.userAgentOverride != navigator.userAgent;
};
/** @private */
WebViewInternal.prototype.setUserAgentOverride = function(userAgentOverride) {
this.userAgentOverride = userAgentOverride;
if (!this.instanceId) {
// If we are not attached yet, then we will pick up the user agent on
// attachment.
return;
}
WebView.overrideUserAgent(this.instanceId, userAgentOverride);
};
WebViewInternal.prototype.buildAttachParams = function(isNewWindow) {
var params = {
'api': 'webview',
'instanceId': this.viewInstanceId,
'name': this.name,
// We don't need to navigate new window from here.
'src': isNewWindow ? undefined : this.src,
// If we have a partition from the opener, that will also be already
// set via this.onAttach().
'storagePartitionId': this.partition.toAttribute(),
'userAgentOverride': this.userAgentOverride
};
return params;
};
WebViewInternal.prototype.attachWindow = function(instanceId, isNewWindow) {
this.instanceId = instanceId;
var params = this.buildAttachParams(isNewWindow);
if (!this.hasBindings()) {
// No bindings means that the plugin isn't there (display: none), we defer
// attachWindow in this case.
this.deferredAttachState = {isNewWindow: isNewWindow};
return false;
}
this.deferredAttachState = null;
return this.browserPluginNode[PLUGIN_METHOD_ATTACH](this.instanceId, params);
};
// Registers browser plugin <object> custom element.
function registerBrowserPluginElement() {
var proto = Object.create(HTMLObjectElement.prototype);
proto.createdCallback = function() {
this.setAttribute('type', 'application/browser-plugin');
// The <object> node fills in the <webview> container.
this.style.width = '100%';
this.style.height = '100%';
};
proto.attributeChangedCallback = function(name, oldValue, newValue) {
var internal = privates(this).internal;
if (!internal) {
return;
}
internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue);
};
proto.attachedCallback = function() {
// Load the plugin immediately.
var unused = this.nonExistentAttribute;
};
WebViewInternal.BrowserPlugin =
DocumentNatives.RegisterElement('browser-plugin', {extends: 'object',
prototype: proto});
delete proto.createdCallback;
delete proto.attachedCallback;
delete proto.detachedCallback;
delete proto.attributeChangedCallback;
}
// Registers <webview> custom element.
function registerWebViewElement() {
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
new WebViewInternal(this);
};
proto.customElementDetached = false;
proto.attributeChangedCallback = function(name, oldValue, newValue) {
var internal = privates(this).internal;
if (!internal) {
return;
}
internal.handleWebviewAttributeMutation(name, oldValue, newValue);
};
proto.detachedCallback = function() {
this.customElementDetached = true;
};
proto.attachedCallback = function() {
if (this.customElementDetached) {
var webViewInternal = privates(this).internal;
webViewInternal.resetUponReattachment();
webViewInternal.allocateInstanceId();
}
this.customElementDetached = false;
};
var methods = [
'back',
'forward',
'canGoBack',
'canGoForward',
'clearData',
'getProcessId',
'go',
'reload',
'stop',
'terminate',
'executeScript',
'insertCSS',
'getUserAgent',
'isUserAgentOverridden',
'setUserAgentOverride'
];
// Forward proto.foo* method calls to WebViewInternal.foo*.
for (var i = 0; methods[i]; ++i) {
var createHandler = function(m) {
return function(var_args) {
var internal = privates(this).internal;
return $Function.apply(internal[m], internal, arguments);
};
};
proto[methods[i]] = createHandler(methods[i]);
}
WebViewInternal.maybeRegisterExperimentalAPIs(proto);
window.WebView =
DocumentNatives.RegisterElement('webview', {prototype: proto});
// Delete the callbacks so developers cannot call them and produce unexpected
// behavior.
delete proto.createdCallback;
delete proto.attachedCallback;
delete proto.detachedCallback;
delete proto.attributeChangedCallback;
}
var useCapture = true;
window.addEventListener('readystatechange', function listener(event) {
if (document.readyState == 'loading')
return;
registerBrowserPluginElement();
registerWebViewElement();
window.removeEventListener(event.type, listener, useCapture);
}, useCapture);
/**
* Implemented when the experimental API is available.
* @private
*/
WebViewInternal.prototype.maybeGetExperimentalEvents = function() {};
/**
* Implemented when the experimental API is available.
* @private
*/
WebViewInternal.prototype.maybeGetExperimentalPermissions = function() {
return [];
};
/**
* Calls to show contextmenu right away instead of dispatching a 'contextmenu'
* event.
* This will be overridden in web_view_experimental.js to implement contextmenu
* API.
*/
WebViewInternal.prototype.maybeHandleContextMenu = function(e, webViewEvent) {
var requestId = e.requestId;
// Setting |params| = undefined will show the context menu unmodified, hence
// the 'contextmenu' API is disabled for stable channel.
var params = undefined;
WebView.showContextMenu(this.instanceId, requestId, params);
};
/**
* Implemented when the experimental API is available.
* @private
*/
WebViewInternal.prototype.setupExperimentalContextMenus = function() {};
exports.WebView = WebView;
exports.WebViewInternal = WebViewInternal;