1 // Copyright 2014 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 the shared functionality for different guestview
6 // containers, such as web_view, app_view, etc.
8 var DocumentNatives = requireNative('document_natives');
9 var GuestView = require('guestView').GuestView;
10 var GuestViewInternalNatives = requireNative('guest_view_internal');
11 var IdGenerator = requireNative('id_generator');
13 function GuestViewContainer(element, viewType) {
14 privates(element).internal = this;
16 this.element = element;
17 this.elementAttached = false;
18 this.viewInstanceId = IdGenerator.GetNextId();
19 this.viewType = viewType;
21 this.setupGuestProperty();
22 this.guest = new GuestView(viewType);
23 this.setupAttributes();
25 privates(this).browserPluginElement = this.createBrowserPluginElement();
26 this.setupFocusPropagation();
27 var shadowRoot = this.element.createShadowRoot();
28 shadowRoot.appendChild(privates(this).browserPluginElement);
31 // Forward public API methods from |proto| to their internal implementations.
32 GuestViewContainer.forwardApiMethods = function(proto, apiMethods) {
33 var createProtoHandler = function(m) {
34 return function(var_args) {
35 var internal = privates(this).internal;
36 return $Function.apply(internal[m], internal, arguments);
39 for (var i = 0; apiMethods[i]; ++i) {
40 proto[apiMethods[i]] = createProtoHandler(apiMethods[i]);
44 // Registers the browserplugin and guestview as custom elements once the
45 // document has loaded.
46 GuestViewContainer.registerElement = function(guestViewContainerType) {
47 var useCapture = true;
48 window.addEventListener('readystatechange', function listener(event) {
49 if (document.readyState == 'loading')
52 registerBrowserPluginElement(
53 guestViewContainerType.VIEW_TYPE.toLowerCase());
54 registerGuestViewElement(guestViewContainerType);
55 window.removeEventListener(event.type, listener, useCapture);
59 // Create the 'guest' property to track new GuestViews and always listen for
61 GuestViewContainer.prototype.setupGuestProperty = function() {
62 Object.defineProperty(this, 'guest', {
64 return privates(this).guest;
66 set: function(value) {
67 privates(this).guest = value;
71 privates(this).guest.onresize = function(e) {
72 // Dispatch the 'contentresize' event.
73 var contentResizeEvent = new Event('contentresize', { bubbles: true });
74 contentResizeEvent.oldWidth = e.oldWidth;
75 contentResizeEvent.oldHeight = e.oldHeight;
76 contentResizeEvent.newWidth = e.newWidth;
77 contentResizeEvent.newHeight = e.newHeight;
78 this.dispatchEvent(contentResizeEvent);
85 GuestViewContainer.prototype.createBrowserPluginElement = function() {
86 // We create BrowserPlugin as a custom element in order to observe changes
87 // to attributes synchronously.
88 var browserPluginElement =
89 new GuestViewContainer[this.viewType + 'BrowserPlugin']();
90 privates(browserPluginElement).internal = this;
91 return browserPluginElement;
94 GuestViewContainer.prototype.setupFocusPropagation = function() {
95 if (!this.element.hasAttribute('tabIndex')) {
96 // GuestViewContainer needs a tabIndex in order to be focusable.
97 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
98 // to allow GuestViewContainer to be focusable.
99 // See http://crbug.com/231664.
100 this.element.setAttribute('tabIndex', -1);
102 this.element.addEventListener('focus', function(e) {
103 // Focus the BrowserPlugin when the GuestViewContainer takes focus.
104 privates(this).browserPluginElement.focus();
106 this.element.addEventListener('blur', function(e) {
107 // Blur the BrowserPlugin when the GuestViewContainer loses focus.
108 privates(this).browserPluginElement.blur();
112 GuestViewContainer.prototype.attachWindow = function() {
113 if (!this.internalInstanceId) {
117 this.guest.attach(this.internalInstanceId,
123 GuestViewContainer.prototype.handleBrowserPluginAttributeMutation =
124 function(name, oldValue, newValue) {
125 if (name == 'internalinstanceid' && !oldValue && !!newValue) {
126 privates(this).browserPluginElement.removeAttribute('internalinstanceid');
127 this.internalInstanceId = parseInt(newValue);
129 // Track when the element resizes using the element resize callback.
130 GuestViewInternalNatives.RegisterElementResizeCallback(
131 this.internalInstanceId, this.onElementResize.bind(this));
133 if (!this.guest.getId()) {
136 this.guest.attach(this.internalInstanceId,
142 GuestViewContainer.prototype.onElementResize = function(oldWidth, oldHeight,
143 newWidth, newHeight) {
144 // Dispatch the 'resize' event.
145 var resizeEvent = new Event('resize', { bubbles: true });
146 resizeEvent.oldWidth = oldWidth;
147 resizeEvent.oldHeight = oldHeight;
148 resizeEvent.newWidth = newWidth;
149 resizeEvent.newHeight = newHeight;
150 this.dispatchEvent(resizeEvent);
152 if (!this.guest.getId())
154 this.guest.setSize({normal: {width: newWidth, height: newHeight}});
157 GuestViewContainer.prototype.buildParams = function() {
158 var params = this.buildContainerParams();
159 params['instanceId'] = this.viewInstanceId;
160 // When the GuestViewContainer is not participating in layout (display:none)
161 // then getBoundingClientRect() would report a width and height of 0.
162 // However, in the case where the GuestViewContainer has a fixed size we can
163 // use that value to initially size the guest so as to avoid a relayout of the
165 var css = window.getComputedStyle(this.element, null);
166 var elementRect = this.element.getBoundingClientRect();
167 params['elementWidth'] = parseInt(elementRect.width) ||
168 parseInt(css.getPropertyValue('width'));
169 params['elementHeight'] = parseInt(elementRect.height) ||
170 parseInt(css.getPropertyValue('height'));
174 GuestViewContainer.prototype.dispatchEvent = function(event) {
175 return this.element.dispatchEvent(event);
178 // Implemented by the specific view type, if needed.
179 GuestViewContainer.prototype.buildContainerParams = function() { return {}; };
180 GuestViewContainer.prototype.onElementAttached = function() {};
181 GuestViewContainer.prototype.onElementDetached = function() {};
182 GuestViewContainer.prototype.setupAttributes = function() {};
184 // Registers the browser plugin <object> custom element. |viewType| is the
185 // name of the specific guestview container (e.g. 'webview').
186 function registerBrowserPluginElement(viewType) {
187 var proto = Object.create(HTMLObjectElement.prototype);
189 proto.createdCallback = function() {
190 this.setAttribute('type', 'application/browser-plugin');
191 this.setAttribute('id', 'browser-plugin-' + IdGenerator.GetNextId());
192 this.style.width = '100%';
193 this.style.height = '100%';
196 proto.attachedCallback = function() {
197 // Load the plugin immediately.
198 var unused = this.nonExistentAttribute;
201 proto.attributeChangedCallback = function(name, oldValue, newValue) {
202 var internal = privates(this).internal;
206 internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue);
209 GuestViewContainer[viewType + 'BrowserPlugin'] =
210 DocumentNatives.RegisterElement(viewType + 'browserplugin',
211 {extends: 'object', prototype: proto});
213 delete proto.createdCallback;
214 delete proto.attachedCallback;
215 delete proto.detachedCallback;
216 delete proto.attributeChangedCallback;
219 // Registers the guestview container as a custom element.
220 // |guestViewContainerType| is the type of guestview container
221 // (e.g.WebViewImpl).
222 function registerGuestViewElement(guestViewContainerType) {
223 var proto = Object.create(HTMLElement.prototype);
225 proto.createdCallback = function() {
226 new guestViewContainerType(this);
229 proto.attachedCallback = function() {
230 var internal = privates(this).internal;
234 internal.elementAttached = true;
235 internal.onElementAttached();
238 proto.attributeChangedCallback = function(name, oldValue, newValue) {
239 var internal = privates(this).internal;
240 if (!internal || !internal.attributes[name]) {
244 // Let the changed attribute handle its own mutation.
245 internal.attributes[name].maybeHandleMutation(oldValue, newValue);
248 proto.detachedCallback = function() {
249 var internal = privates(this).internal;
253 internal.elementAttached = false;
254 internal.internalInstanceId = 0;
255 internal.guest.destroy();
256 internal.onElementDetached();
259 // Let the specific view type add extra functionality to its custom element
261 if (guestViewContainerType.setupElement) {
262 guestViewContainerType.setupElement(proto);
265 window[guestViewContainerType.VIEW_TYPE] =
266 DocumentNatives.RegisterElement(
267 guestViewContainerType.VIEW_TYPE.toLowerCase(),
270 // Delete the callbacks so developers cannot call them and produce unexpected
272 delete proto.createdCallback;
273 delete proto.attachedCallback;
274 delete proto.detachedCallback;
275 delete proto.attributeChangedCallback;
279 exports.GuestViewContainer = GuestViewContainer;