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.attachWindow$ = function() {
115 if (!this.internalInstanceId) {
119 this.guest.attach(this.internalInstanceId,
125 GuestViewContainer.prototype.makeGCOwnContainer = function(internalInstanceId) {
126 MessagingNatives.BindToGC(this, function() {
127 GuestViewInternalNatives.DestroyContainer(internalInstanceId);
131 GuestViewContainer.prototype.onInternalInstanceId = function(
132 internalInstanceId) {
133 this.internalInstanceId = internalInstanceId;
134 this.makeGCOwnContainer(this.internalInstanceId);
136 // Track when the element resizes using the element resize callback.
137 GuestViewInternalNatives.RegisterElementResizeCallback(
138 this.internalInstanceId, this.weakWrapper(this.onElementResize));
140 if (!this.guest.getId()) {
143 this.guest.attach(this.internalInstanceId,
148 GuestViewContainer.prototype.handleInternalElementAttributeMutation =
149 function(name, oldValue, newValue) {
150 if (name == 'internalinstanceid' && !oldValue && !!newValue) {
151 privates(this).internalElement.removeAttribute('internalinstanceid');
152 this.onInternalInstanceId(parseInt(newValue));
156 GuestViewContainer.prototype.onElementResize = function(newWidth, newHeight) {
157 if (!this.guest.getId())
159 this.guest.setSize({normal: {width: newWidth, height: newHeight}});
162 GuestViewContainer.prototype.buildParams = function() {
163 var params = this.buildContainerParams();
164 params['instanceId'] = this.viewInstanceId;
165 // When the GuestViewContainer is not participating in layout (display:none)
166 // then getBoundingClientRect() would report a width and height of 0.
167 // However, in the case where the GuestViewContainer has a fixed size we can
168 // use that value to initially size the guest so as to avoid a relayout of the
170 var css = window.getComputedStyle(this.element, null);
171 var elementRect = this.element.getBoundingClientRect();
172 params['elementWidth'] = parseInt(elementRect.width) ||
173 parseInt(css.getPropertyValue('width'));
174 params['elementHeight'] = parseInt(elementRect.height) ||
175 parseInt(css.getPropertyValue('height'));
179 GuestViewContainer.prototype.dispatchEvent = function(event) {
180 return this.element.dispatchEvent(event);
183 // Returns a wrapper function for |func| with a weak reference to |this|.
184 GuestViewContainer.prototype.weakWrapper = function(func) {
185 var viewInstanceId = this.viewInstanceId;
187 var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
189 return $Function.apply(func, view, $Array.slice(arguments));
194 // Implemented by the specific view type, if needed.
195 GuestViewContainer.prototype.buildContainerParams = function() { return {}; };
196 GuestViewContainer.prototype.onElementAttached = function() {};
197 GuestViewContainer.prototype.onElementDetached = function() {};
198 GuestViewContainer.prototype.setupAttributes = function() {};
200 // Registers the browser plugin <object> custom element. |viewType| is the
201 // name of the specific guestview container (e.g. 'webview').
202 function registerInternalElement(viewType) {
203 var proto = $Object.create(HTMLElement.prototype);
205 proto.createdCallback = function() {
206 this.setAttribute('type', 'application/browser-plugin');
207 this.setAttribute('id', 'browser-plugin-' + IdGenerator.GetNextId());
208 this.style.width = '100%';
209 this.style.height = '100%';
212 proto.attachedCallback = function() {
213 // Load the plugin immediately.
214 var unused = this.nonExistentAttribute;
217 proto.attributeChangedCallback = function(name, oldValue, newValue) {
218 var internal = privates(this).internal;
222 internal.handleInternalElementAttributeMutation(name, oldValue, newValue);
225 GuestViewContainer[viewType + 'BrowserPlugin'] =
226 DocumentNatives.RegisterElement(viewType + 'browserplugin',
227 {extends: 'object', prototype: proto});
229 delete proto.createdCallback;
230 delete proto.attachedCallback;
231 delete proto.detachedCallback;
232 delete proto.attributeChangedCallback;
235 // Registers the guestview container as a custom element.
236 // |guestViewContainerType| is the type of guestview container
237 // (e.g.WebViewImpl).
238 function registerGuestViewElement(guestViewContainerType) {
239 var proto = $Object.create(HTMLElement.prototype);
241 proto.createdCallback = function() {
242 new guestViewContainerType(this);
245 proto.attachedCallback = function() {
246 var internal = privates(this).internal;
250 internal.elementAttached = true;
251 internal.onElementAttached();
254 proto.attributeChangedCallback = function(name, oldValue, newValue) {
255 var internal = privates(this).internal;
256 if (!internal || !internal.attributes[name]) {
260 // Let the changed attribute handle its own mutation.
261 internal.attributes[name].maybeHandleMutation(oldValue, newValue);
264 proto.detachedCallback = function() {
265 var internal = privates(this).internal;
269 internal.elementAttached = false;
270 internal.internalInstanceId = 0;
271 internal.guest.destroy();
272 internal.onElementDetached();
275 // Let the specific view type add extra functionality to its custom element
277 if (guestViewContainerType.setupElement) {
278 guestViewContainerType.setupElement(proto);
281 window[guestViewContainerType.VIEW_TYPE] =
282 DocumentNatives.RegisterElement(
283 guestViewContainerType.VIEW_TYPE.toLowerCase(),
286 // Delete the callbacks so developers cannot call them and produce unexpected
288 delete proto.createdCallback;
289 delete proto.attachedCallback;
290 delete proto.detachedCallback;
291 delete proto.attributeChangedCallback;
295 exports.GuestViewContainer = GuestViewContainer;