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(newWidth
, newHeight
) {
143 if (!this.guest
.getId())
145 this.guest
.setSize({normal
: {width
: newWidth
, height
: newHeight
}});
148 GuestViewContainer
.prototype.buildParams = function() {
149 var params
= this.buildContainerParams();
150 params
['instanceId'] = this.viewInstanceId
;
151 // When the GuestViewContainer is not participating in layout (display:none)
152 // then getBoundingClientRect() would report a width and height of 0.
153 // However, in the case where the GuestViewContainer has a fixed size we can
154 // use that value to initially size the guest so as to avoid a relayout of the
156 var css
= window
.getComputedStyle(this.element
, null);
157 var elementRect
= this.element
.getBoundingClientRect();
158 params
['elementWidth'] = parseInt(elementRect
.width
) ||
159 parseInt(css
.getPropertyValue('width'));
160 params
['elementHeight'] = parseInt(elementRect
.height
) ||
161 parseInt(css
.getPropertyValue('height'));
165 GuestViewContainer
.prototype.dispatchEvent = function(event
) {
166 return this.element
.dispatchEvent(event
);
169 // Implemented by the specific view type, if needed.
170 GuestViewContainer
.prototype.buildContainerParams = function() { return {}; };
171 GuestViewContainer
.prototype.onElementAttached = function() {};
172 GuestViewContainer
.prototype.onElementDetached = function() {};
173 GuestViewContainer
.prototype.setupAttributes = function() {};
175 // Registers the browser plugin <object> custom element. |viewType| is the
176 // name of the specific guestview container (e.g. 'webview').
177 function registerBrowserPluginElement(viewType
) {
178 var proto
= $Object
.create(HTMLElement
.prototype);
180 proto
.createdCallback = function() {
181 this.setAttribute('type', 'application/browser-plugin');
182 this.setAttribute('id', 'browser-plugin-' + IdGenerator
.GetNextId());
183 this.style
.width
= '100%';
184 this.style
.height
= '100%';
187 proto
.attachedCallback = function() {
188 // Load the plugin immediately.
189 var unused
= this.nonExistentAttribute
;
192 proto
.attributeChangedCallback = function(name
, oldValue
, newValue
) {
193 var internal = privates(this).internal;
197 internal.handleBrowserPluginAttributeMutation(name
, oldValue
, newValue
);
200 GuestViewContainer
[viewType
+ 'BrowserPlugin'] =
201 DocumentNatives
.RegisterElement(viewType
+ 'browserplugin',
202 {extends: 'object', prototype: proto
});
204 delete proto
.createdCallback
;
205 delete proto
.attachedCallback
;
206 delete proto
.detachedCallback
;
207 delete proto
.attributeChangedCallback
;
210 // Registers the guestview container as a custom element.
211 // |guestViewContainerType| is the type of guestview container
212 // (e.g.WebViewImpl).
213 function registerGuestViewElement(guestViewContainerType
) {
214 var proto
= $Object
.create(HTMLElement
.prototype);
216 proto
.createdCallback = function() {
217 new guestViewContainerType(this);
220 proto
.attachedCallback = function() {
221 var internal = privates(this).internal;
225 internal.elementAttached
= true;
226 internal.onElementAttached();
229 proto
.attributeChangedCallback = function(name
, oldValue
, newValue
) {
230 var internal = privates(this).internal;
231 if (!internal || !internal.attributes
[name
]) {
235 // Let the changed attribute handle its own mutation.
236 internal.attributes
[name
].maybeHandleMutation(oldValue
, newValue
);
239 proto
.detachedCallback = function() {
240 var internal = privates(this).internal;
244 internal.elementAttached
= false;
245 internal.internalInstanceId
= 0;
246 internal.guest
.destroy();
247 internal.onElementDetached();
250 // Let the specific view type add extra functionality to its custom element
252 if (guestViewContainerType
.setupElement
) {
253 guestViewContainerType
.setupElement(proto
);
256 window
[guestViewContainerType
.VIEW_TYPE
] =
257 DocumentNatives
.RegisterElement(
258 guestViewContainerType
.VIEW_TYPE
.toLowerCase(),
261 // Delete the callbacks so developers cannot call them and produce unexpected
263 delete proto
.createdCallback
;
264 delete proto
.attachedCallback
;
265 delete proto
.detachedCallback
;
266 delete proto
.attributeChangedCallback
;
270 exports
.GuestViewContainer
= GuestViewContainer
;