1// Copyright 2013 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
5'use strict';
6
7/**
8 * @param {WebView} webView Web View tag.
9 * @param {?string} ext File extension.
10 * @param {?string} mime File mime type.
11 * @param {?string} searchQuery Search query.
12 * @param {number} width Width of the CWS widget.
13 * @param {number} height Height of the CWS widget.
14 * @param {string} url Share Url for an entry.
15 * @param {string} target Target (scheme + host + port) of the widget.
16 * @constructor
17 */
18function CWSContainerClient(
19    webView, ext, mime, searchQuery, width, height, url, target) {
20  this.webView_ = webView;
21  this.ext_ = (ext && ext[0] == '.') ? ext.substr(1) : ext;
22  this.mime_ = mime;
23  this.searchQuery_ = searchQuery;
24  this.width_ = width;
25  this.height_ = height;
26  this.url_ = url;
27  this.target_ = target;
28
29  this.loaded_ = false;
30  this.loading_ = false;
31
32  this.onMessageBound_ = this.onMessage_.bind(this);
33  this.onLoadStopBound_ = this.onLoadStop_.bind(this);
34  this.onLoadAbortBound_ = this.onLoadAbort_.bind(this);
35}
36
37CWSContainerClient.prototype = {
38  __proto__: cr.EventTarget.prototype
39};
40
41/**
42 * Events CWSContainerClient fires
43 *
44 * @enum {string}
45 * @const
46 */
47CWSContainerClient.Events = {
48  LOADED: 'CWSContainerClient.Events.LOADED',
49  LOAD_FAILED: 'CWSContainerClient.Events.LOAD_FAILED',
50  REQUEST_INSTALL: 'CWSContainerClient.Events.REQUEST_INSTALL'
51};
52Object.freeze(CWSContainerClient.Events);
53
54/**
55 * Handles messages from the widget
56 * @param {Event} event Message event.
57 * @private
58 */
59CWSContainerClient.prototype.onMessage_ = function(event) {
60  if (event.origin != this.target_)
61    return;
62
63  var data = event.data;
64  switch (data['message']) {
65    case 'widget_loaded':
66      this.onWidgetLoaded_();
67      break;
68    case 'widget_load_failed':
69      this.onWidgetLoadFailed_();
70      break;
71    case 'before_install':
72      this.sendInstallRequest_(data['item_id']);
73      break;
74    default:
75      console.error('Unexpected message: ' + data['message'], data);
76  }
77};
78
79/**
80 * Called when receiving 'loadstop' event from the <webview>.
81 * @param {Event} event Message event.
82 * @private
83 */
84CWSContainerClient.prototype.onLoadStop_ = function(event) {
85  if (this.url_ == this.webView_.src && !this.loaded_) {
86    this.loaded_ = true;
87    this.postInitializeMessage_();
88  }
89};
90
91/**
92 * Called when the widget is loaded successfully.
93 * @private
94 */
95CWSContainerClient.prototype.onWidgetLoaded_ = function() {
96  cr.dispatchSimpleEvent(this, CWSContainerClient.Events.LOADED);
97};
98
99/**
100 * Called when the widget is failed to load.
101 * @private
102 */
103CWSContainerClient.prototype.onWidgetLoadFailed_ = function() {
104  this.sendWidgetLoadFailed_();
105};
106
107/**
108 * Called when receiving the 'loadabort' event from <webview>.
109 * @param {Event} event Message event.
110 * @private
111 */
112CWSContainerClient.prototype.onLoadAbort_ = function(event) {
113  this.sendWidgetLoadFailed_();
114};
115
116/**
117 * Called when the installation is completed from the suggest-app dialog.
118 *
119 * @param {boolean} result True if the installation is success, false if failed.
120 * @param {string} itemId Item id to be installed.
121 */
122CWSContainerClient.prototype.onInstallCompleted = function(result, itemId) {
123  if (result)
124    this.postInstallSuccessMessage_(itemId);
125  else
126    this.postInstallFailureMessage_(itemId);
127};
128
129/**
130 * Send the fail message to the suggest-app dialog.
131 * @private
132 */
133CWSContainerClient.prototype.sendWidgetLoadFailed_ = function() {
134  cr.dispatchSimpleEvent(this, CWSContainerClient.Events.LOAD_FAILED);
135};
136
137/**
138 * Send the install request to the suggest-app dialog.
139 *
140 * @param {string} itemId Item id to be installed.
141 * @private
142 */
143CWSContainerClient.prototype.sendInstallRequest_ = function(itemId) {
144  var event = new Event(CWSContainerClient.Events.REQUEST_INSTALL);
145  event.itemId = itemId;
146  this.dispatchEvent(event);
147};
148
149/**
150 * Send the 'install_failure' message to the widget.
151 *
152 * @param {string} itemId Item id to be installed.
153 * @private
154 */
155CWSContainerClient.prototype.postInstallFailureMessage_ = function(itemId) {
156  var message = {
157    message: 'install_failure',
158    item_id: itemId,
159    v: 1
160  };
161
162  this.postMessage_(message);
163};
164
165/**
166 * Send the 'install_success' message to the widget.
167 *
168 * @param {string} itemId Item id to be installed.
169 * @private
170 */
171CWSContainerClient.prototype.postInstallSuccessMessage_ = function(itemId) {
172  var message = {
173    message: 'install_success',
174    item_id: itemId,
175    v: 1
176  };
177
178  this.postMessage_(message);
179};
180
181/**
182 * Send the 'initialize' message to the widget.
183 * @private
184 */
185CWSContainerClient.prototype.postInitializeMessage_ = function() {
186  var message = {
187    message: 'initialize',
188    hl: util.getCurrentLocaleOrDefault(),
189    width: this.width_,
190    height: this.height_,
191    v: 1
192  };
193
194  if (this.searchQuery_) {
195    message['search_query'] = this.searchQuery_;
196  } else {
197    message['file_extension'] = this.ext_;
198    message['mime_type'] = this.mime_;
199  }
200
201  this.postMessage_(message);
202};
203
204/**
205 * Send a message to the widget. This method shouldn't be called directly,
206 * should from more specified posting function (eg. postXyzMessage_()).
207 *
208 * @param {object} message Message object to be posted.
209 * @private
210 */
211CWSContainerClient.prototype.postMessage_ = function(message) {
212  if (!this.webView_.contentWindow)
213    return;
214
215  this.webView_.contentWindow.postMessage(message, this.target_);
216};
217
218/**
219 * Loads the page to <webview>. Can be called only once.
220 */
221CWSContainerClient.prototype.load = function() {
222  if (this.loading_ || this.loaded_)
223    throw new Error('Already loaded.');
224  this.loading_ = true;
225  this.loaded_ = false;
226
227  window.addEventListener('message', this.onMessageBound_);
228  this.webView_.addEventListener('loadstop', this.onLoadStopBound_);
229  this.webView_.addEventListener('loadabort', this.onLoadAbortBound_);
230  this.webView_.setAttribute('src', this.url_);
231};
232
233/**
234 * Aborts loading of the embedded dialog and performs cleanup.
235 */
236CWSContainerClient.prototype.abort = function() {
237  window.removeEventListener('message', this.onMessageBound_);
238  this.webView_.removeEventListener('loadstop', this.onLoadStopBound_);
239  this.webView_.removeEventListener(
240      'loadabort', this.onLoadAbortBound_);
241  this.webView_.stop();
242};
243
244/**
245 * Cleans the dialog by removing all handlers.
246 */
247CWSContainerClient.prototype.dispose = function() {
248  this.abort();
249};
250