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');
12 var MessagingNatives
= requireNative('messaging_natives');
14 function GuestViewContainer(element
, viewType
) {
15 privates(element
).internal = this;
17 this.element
= element
;
18 this.elementAttached
= false;
19 this.viewInstanceId
= IdGenerator
.GetNextId();
20 this.viewType
= viewType
;
22 this.setupGuestProperty();
23 this.guest
= new GuestView(viewType
);
24 this.setupAttributes();
26 privates(this).internalElement
= this.createInternalElement
$();
27 this.setupFocusPropagation();
28 var shadowRoot
= this.element
.createShadowRoot();
29 shadowRoot
.appendChild(privates(this).internalElement
);
31 GuestViewInternalNatives
.RegisterView(this.viewInstanceId
, this, viewType
);
34 // Forward public API methods from |proto| to their internal implementations.
35 GuestViewContainer
.forwardApiMethods = function(proto
, apiMethods
) {
36 var createProtoHandler = function(m
) {
37 return function(var_args
) {
38 var internal = privates(this).internal;
39 return $Function
.apply(internal[m
], internal, arguments
);
42 for (var i
= 0; apiMethods
[i
]; ++i
) {
43 proto
[apiMethods
[i
]] = createProtoHandler(apiMethods
[i
]);
47 // Registers the browserplugin and guestview as custom elements once the
48 // document has loaded.
49 GuestViewContainer
.registerElement = function(guestViewContainerType
) {
50 var useCapture
= true;
51 window
.addEventListener('readystatechange', function listener(event
) {
52 if (document
.readyState
== 'loading')
55 registerInternalElement(guestViewContainerType
.VIEW_TYPE
.toLowerCase());
56 registerGuestViewElement(guestViewContainerType
);
57 window
.removeEventListener(event
.type
, listener
, useCapture
);
61 // Create the 'guest' property to track new GuestViews and always listen for
63 GuestViewContainer
.prototype.setupGuestProperty = function() {
64 $Object
.defineProperty(this, 'guest', {
66 return privates(this).guest
;
68 set: function(value
) {
69 privates(this).guest
= value
;
73 privates(this).guest
.onresize = function(e
) {
74 // Dispatch the 'contentresize' event.
75 var contentResizeEvent
= new Event('contentresize', { bubbles
: true });
76 contentResizeEvent
.oldWidth
= e
.oldWidth
;
77 contentResizeEvent
.oldHeight
= e
.oldHeight
;
78 contentResizeEvent
.newWidth
= e
.newWidth
;
79 contentResizeEvent
.newHeight
= e
.newHeight
;
80 this.dispatchEvent(contentResizeEvent
);
87 GuestViewContainer
.prototype.createInternalElement
$ = function() {
88 // We create BrowserPlugin as a custom element in order to observe changes
89 // to attributes synchronously.
90 var browserPluginElement
=
91 new GuestViewContainer
[this.viewType
+ 'BrowserPlugin']();
92 privates(browserPluginElement
).internal = this;
93 return browserPluginElement
;
96 GuestViewContainer
.prototype.setupFocusPropagation = function() {
97 if (!this.element
.hasAttribute('tabIndex')) {
98 // GuestViewContainer needs a tabIndex in order to be focusable.
99 // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
100 // to allow GuestViewContainer to be focusable.
101 // See http://crbug.com/231664.
102 this.element
.setAttribute('tabIndex', -1);
104 this.element
.addEventListener('focus', this.weakWrapper(function(e
) {
105 // Focus the BrowserPlugin when the GuestViewContainer takes focus.
106 privates(this).internalElement
.focus();
108 this.element
.addEventListener('blur', this.weakWrapper(function(e
) {
109 // Blur the BrowserPlugin when the GuestViewContainer loses focus.
110 privates(this).internalElement
.blur();
114 GuestViewContainer
.prototype.focus = function() {
115 // Focus the internal element when focus() is called on the GuestView element.
116 privates(this).internalElement
.focus();
119 GuestViewContainer
.prototype.attachWindow
$ = function() {
120 if (!this.internalInstanceId
) {
124 this.guest
.attach(this.internalInstanceId
,
130 GuestViewContainer
.prototype.makeGCOwnContainer = function(internalInstanceId
) {
131 MessagingNatives
.BindToGC(this, function() {
132 GuestViewInternalNatives
.DestroyContainer(internalInstanceId
);
136 GuestViewContainer
.prototype.onInternalInstanceId = function(
137 internalInstanceId
) {
138 this.internalInstanceId
= internalInstanceId
;
139 this.makeGCOwnContainer(this.internalInstanceId
);
141 // Track when the element resizes using the element resize callback.
142 GuestViewInternalNatives
.RegisterElementResizeCallback(
143 this.internalInstanceId
, this.weakWrapper(this.onElementResize
));
145 if (!this.guest
.getId()) {
148 this.guest
.attach(this.internalInstanceId
,
153 GuestViewContainer
.prototype.handleInternalElementAttributeMutation
=
154 function(name
, oldValue
, newValue
) {
155 if (name
== 'internalinstanceid' && !oldValue
&& !!newValue
) {
156 privates(this).internalElement
.removeAttribute('internalinstanceid');
157 this.onInternalInstanceId(parseInt(newValue
));
161 GuestViewContainer
.prototype.onElementResize = function(newWidth
, newHeight
) {
162 if (!this.guest
.getId())
164 this.guest
.setSize({normal
: {width
: newWidth
, height
: newHeight
}});
167 GuestViewContainer
.prototype.buildParams = function() {
168 var params
= this.buildContainerParams();
169 params
['instanceId'] = this.viewInstanceId
;
170 // When the GuestViewContainer is not participating in layout (display:none)
171 // then getBoundingClientRect() would report a width and height of 0.
172 // However, in the case where the GuestViewContainer has a fixed size we can
173 // use that value to initially size the guest so as to avoid a relayout of the
175 var css
= window
.getComputedStyle(this.element
, null);
176 var elementRect
= this.element
.getBoundingClientRect();
177 params
['elementWidth'] = parseInt(elementRect
.width
) ||
178 parseInt(css
.getPropertyValue('width'));
179 params
['elementHeight'] = parseInt(elementRect
.height
) ||
180 parseInt(css
.getPropertyValue('height'));
184 GuestViewContainer
.prototype.dispatchEvent = function(event
) {
185 return this.element
.dispatchEvent(event
);
188 // Returns a wrapper function for |func| with a weak reference to |this|.
189 GuestViewContainer
.prototype.weakWrapper = function(func
) {
190 var viewInstanceId
= this.viewInstanceId
;
192 var view
= GuestViewInternalNatives
.GetViewFromID(viewInstanceId
);
194 return $Function
.apply(func
, view
, $Array
.slice(arguments
));
199 // Implemented by the specific view type, if needed.
200 GuestViewContainer
.prototype.buildContainerParams = function() { return {}; };
201 GuestViewContainer
.prototype.onElementAttached = function() {};
202 GuestViewContainer
.prototype.onElementDetached = function() {};
203 GuestViewContainer
.prototype.setupAttributes = function() {};
205 // Registers the browser plugin <object> custom element. |viewType| is the
206 // name of the specific guestview container (e.g. 'webview').
207 function registerInternalElement(viewType
) {
208 var proto
= $Object
.create(HTMLElement
.prototype);
210 proto
.createdCallback = function() {
211 this.setAttribute('type', 'application/browser-plugin');
212 this.setAttribute('id', 'browser-plugin-' + IdGenerator
.GetNextId());
213 this.style
.width
= '100%';
214 this.style
.height
= '100%';
217 proto
.attachedCallback = function() {
218 // Load the plugin immediately.
219 var unused
= this.nonExistentAttribute
;
222 proto
.attributeChangedCallback = function(name
, oldValue
, newValue
) {
223 var internal = privates(this).internal;
227 internal.handleInternalElementAttributeMutation(name
, oldValue
, newValue
);
230 GuestViewContainer
[viewType
+ 'BrowserPlugin'] =
231 DocumentNatives
.RegisterElement(viewType
+ 'browserplugin',
232 {extends: 'object', prototype: proto
});
234 delete proto
.createdCallback
;
235 delete proto
.attachedCallback
;
236 delete proto
.detachedCallback
;
237 delete proto
.attributeChangedCallback
;
240 // Registers the guestview container as a custom element.
241 // |guestViewContainerType| is the type of guestview container
242 // (e.g. WebViewImpl).
243 function registerGuestViewElement(guestViewContainerType
) {
244 var proto
= $Object
.create(HTMLElement
.prototype);
246 proto
.createdCallback = function() {
247 new guestViewContainerType(this);
250 proto
.attachedCallback = function() {
251 var internal = privates(this).internal;
255 internal.elementAttached
= true;
256 internal.onElementAttached();
259 proto
.attributeChangedCallback = function(name
, oldValue
, newValue
) {
260 var internal = privates(this).internal;
261 if (!internal || !internal.attributes
[name
]) {
265 // Let the changed attribute handle its own mutation.
266 internal.attributes
[name
].maybeHandleMutation(oldValue
, newValue
);
269 proto
.detachedCallback = function() {
270 var internal = privates(this).internal;
274 internal.elementAttached
= false;
275 internal.internalInstanceId
= 0;
276 internal.guest
.destroy();
277 internal.onElementDetached();
280 // Override |focus| to let |internal| handle it.
281 proto
.focus = function() {
282 var internal = privates(this).internal;
289 // Let the specific view type add extra functionality to its custom element
291 if (guestViewContainerType
.setupElement
) {
292 guestViewContainerType
.setupElement(proto
);
295 window
[guestViewContainerType
.VIEW_TYPE
] =
296 DocumentNatives
.RegisterElement(
297 guestViewContainerType
.VIEW_TYPE
.toLowerCase(),
300 // Delete the callbacks so developers cannot call them and produce unexpected
302 delete proto
.createdCallback
;
303 delete proto
.attachedCallback
;
304 delete proto
.detachedCallback
;
305 delete proto
.attributeChangedCallback
;
309 exports
.GuestViewContainer
= GuestViewContainer
;