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.
4
5var DocumentNatives = requireNative('document_natives');
6var GuestViewInternal =
7    require('binding').Binding.create('guestViewInternal').generate();
8var IdGenerator = requireNative('id_generator');
9var guestViewInternalNatives = requireNative('guest_view_internal');
10
11function AppViewInternal(appviewNode) {
12  privates(appviewNode).internal = this;
13  this.appviewNode = appviewNode;
14
15  this.browserPluginNode = this.createBrowserPluginNode();
16  var shadowRoot = this.appviewNode.createShadowRoot();
17  shadowRoot.appendChild(this.browserPluginNode);
18  this.viewInstanceId = IdGenerator.GetNextId();
19}
20
21AppViewInternal.prototype.getErrorNode = function() {
22  if (!this.errorNode) {
23    this.errorNode = document.createElement('div');
24    this.errorNode.innerText = 'Unable to connect to app.';
25    this.errorNode.style.position = 'absolute';
26    this.errorNode.style.left = '0px';
27    this.errorNode.style.top = '0px';
28    this.errorNode.style.width = '100%';
29    this.errorNode.style.height = '100%';
30    this.appviewNode.shadowRoot.appendChild(this.errorNode);
31  }
32  return this.errorNode;
33};
34
35AppViewInternal.prototype.createBrowserPluginNode = function() {
36  // We create BrowserPlugin as a custom element in order to observe changes
37  // to attributes synchronously.
38  var browserPluginNode = new AppViewInternal.BrowserPlugin();
39  privates(browserPluginNode).internal = this;
40  return browserPluginNode;
41};
42
43AppViewInternal.prototype.connect = function(app, data, callback) {
44  var createParams = {
45    'appId': app,
46    'data': data || {}
47  };
48  var self = this;
49  GuestViewInternal.createGuest(
50    'appview',
51    createParams,
52    function(guestInstanceId) {
53      if (!guestInstanceId) {
54        this.browserPluginNode.style.visibility = 'hidden';
55        var errorMsg = 'Unable to connect to app "' + app + '".';
56        window.console.warn(errorMsg);
57        this.getErrorNode().innerText = errorMsg;
58        if (callback) {
59          callback(false);
60        }
61        return;
62      }
63      this.attachWindow(guestInstanceId);
64      if (callback) {
65        callback(true);
66      }
67    }.bind(this)
68  );
69};
70
71AppViewInternal.prototype.attachWindow = function(guestInstanceId) {
72  this.guestInstanceId = guestInstanceId;
73  var params = {
74    'instanceId': this.viewInstanceId,
75  };
76  this.browserPluginNode.style.visibility = 'visible';
77  return guestViewInternalNatives.AttachGuest(
78      parseInt(this.browserPluginNode.getAttribute('internalinstanceid')),
79      guestInstanceId,
80      params);
81};
82
83function registerBrowserPluginElement() {
84  var proto = Object.create(HTMLObjectElement.prototype);
85
86  proto.createdCallback = function() {
87    this.setAttribute('type', 'application/browser-plugin');
88    this.style.width = '100%';
89    this.style.height = '100%';
90  };
91
92  proto.attachedCallback = function() {
93    // Load the plugin immediately.
94    var unused = this.nonExistentAttribute;
95  };
96
97  AppViewInternal.BrowserPlugin =
98      DocumentNatives.RegisterElement('appplugin', {extends: 'object',
99                                                    prototype: proto});
100
101  delete proto.createdCallback;
102  delete proto.attachedCallback;
103  delete proto.detachedCallback;
104  delete proto.attributeChangedCallback;
105}
106
107function registerAppViewElement() {
108  var proto = Object.create(HTMLElement.prototype);
109
110  proto.createdCallback = function() {
111    new AppViewInternal(this);
112  };
113
114  proto.connect = function() {
115    var internal = privates(this).internal;
116    $Function.apply(internal.connect, internal, arguments);
117  }
118  window.AppView =
119      DocumentNatives.RegisterElement('appview', {prototype: proto});
120
121  // Delete the callbacks so developers cannot call them and produce unexpected
122  // behavior.
123  delete proto.createdCallback;
124  delete proto.attachedCallback;
125  delete proto.detachedCallback;
126  delete proto.attributeChangedCallback;
127}
128
129var useCapture = true;
130window.addEventListener('readystatechange', function listener(event) {
131  if (document.readyState == 'loading')
132    return;
133
134  registerBrowserPluginElement();
135  registerAppViewElement();
136  window.removeEventListener(event.type, listener, useCapture);
137}, useCapture);
138