Revert 168224 - Update V8 to version 3.15.4.
[chromium-blink-merge.git] / remoting / webapp / client_plugin_async.js
blobe27983ccaaf58e490155d48bb0bc794ba5050c5c
1 // Copyright (c) 2012 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 /**
6 * @fileoverview
7 * Class that wraps low-level details of interacting with the client plugin.
9 * This abstracts a <embed> element and controls the plugin which does
10 * the actual remoting work. It also handles differences between
11 * client plugins versions when it is necessary.
14 'use strict';
16 /** @suppress {duplicate} */
17 var remoting = remoting || {};
19 /**
20 * @param {remoting.ViewerPlugin} plugin The plugin embed element.
21 * @constructor
22 * @implements {remoting.ClientPlugin}
24 remoting.ClientPluginAsync = function(plugin) {
25 this.plugin = plugin;
27 this.desktopWidth = 0;
28 this.desktopHeight = 0;
29 this.desktopXDpi = 96;
30 this.desktopYDpi = 96;
32 /** @param {string} iq The Iq stanza received from the host. */
33 this.onOutgoingIqHandler = function (iq) {};
34 /** @param {string} message Log message. */
35 this.onDebugMessageHandler = function (message) {};
36 /**
37 * @param {number} state The connection state.
38 * @param {number} error The error code, if any.
40 this.onConnectionStatusUpdateHandler = function(state, error) {};
41 /** @param {boolean} ready Connection ready state. */
42 this.onConnectionReadyHandler = function(ready) {};
43 this.onDesktopSizeUpdateHandler = function () {};
45 /** @type {number} */
46 this.pluginApiVersion_ = -1;
47 /** @type {Array.<string>} */
48 this.pluginApiFeatures_ = [];
49 /** @type {number} */
50 this.pluginApiMinVersion_ = -1;
51 /** @type {boolean} */
52 this.helloReceived_ = false;
53 /** @type {function(boolean)|null} */
54 this.onInitializedCallback_ = null;
56 /** @type {remoting.ClientSession.PerfStats} */
57 this.perfStats_ = new remoting.ClientSession.PerfStats();
59 /** @type {remoting.ClientPluginAsync} */
60 var that = this;
61 /** @param {Event} event Message event from the plugin. */
62 this.plugin.addEventListener('message', function(event) {
63 that.handleMessage_(event.data);
64 }, false);
65 window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500);
68 /**
69 * Chromoting session API version (for this javascript).
70 * This is compared with the plugin API version to verify that they are
71 * compatible.
73 * @const
74 * @private
76 remoting.ClientPluginAsync.prototype.API_VERSION_ = 6;
78 /**
79 * The oldest API version that we support.
80 * This will differ from the |API_VERSION_| if we maintain backward
81 * compatibility with older API versions.
83 * @const
84 * @private
86 remoting.ClientPluginAsync.prototype.API_MIN_VERSION_ = 5;
88 /**
89 * @param {string} messageStr Message from the plugin.
91 remoting.ClientPluginAsync.prototype.handleMessage_ = function(messageStr) {
92 var message = /** @type {{method:string, data:Object.<string,string>}} */
93 jsonParseSafe(messageStr);
95 if (!message || !('method' in message) || !('data' in message)) {
96 console.error('Received invalid message from the plugin: ' + messageStr);
97 return;
100 if (message.method == 'hello') {
101 // Reset the size in case we had to enlarge it to support click-to-play.
102 this.plugin.width = 0;
103 this.plugin.height = 0;
104 if (typeof message.data['apiVersion'] != 'number' ||
105 typeof message.data['apiMinVersion'] != 'number') {
106 console.error('Received invalid hello message: ' + messageStr);
107 return;
109 this.pluginApiVersion_ = /** @type {number} */ message.data['apiVersion'];
110 if (this.pluginApiVersion_ >= 7) {
111 if (typeof message.data['apiFeatures'] != 'string') {
112 console.error('Received invalid hello message: ' + messageStr);
113 return;
115 this.pluginApiFeatures_ =
116 /** @type {Array.<string>} */ message.data['apiFeatures'].split(' ');
117 } else if (this.pluginApiVersion_ >= 6) {
118 this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
119 } else {
120 this.pluginApiFeatures_ = ['highQualityScaling'];
122 this.pluginApiMinVersion_ =
123 /** @type {number} */ message.data['apiMinVersion'];
124 this.helloReceived_ = true;
125 if (this.onInitializedCallback_ != null) {
126 this.onInitializedCallback_(true);
127 this.onInitializedCallback_ = null;
129 } else if (message.method == 'sendOutgoingIq') {
130 if (typeof message.data['iq'] != 'string') {
131 console.error('Received invalid sendOutgoingIq message: ' + messageStr);
132 return;
134 this.onOutgoingIqHandler(message.data['iq']);
135 } else if (message.method == 'logDebugMessage') {
136 if (typeof message.data['message'] != 'string') {
137 console.error('Received invalid logDebugMessage message: ' + messageStr);
138 return;
140 this.onDebugMessageHandler(message.data['message']);
141 } else if (message.method == 'onConnectionStatus') {
142 if (typeof message.data['state'] != 'string' ||
143 !(message.data['state'] in remoting.ClientSession.State) ||
144 typeof message.data['error'] != 'string') {
145 console.error('Received invalid onConnectionState message: ' +
146 messageStr);
147 return;
150 /** @type {remoting.ClientSession.State} */
151 var state = remoting.ClientSession.State[message.data['state']];
152 var error;
153 if (message.data['error'] in remoting.ClientSession.ConnectionError) {
154 error = /** @type {remoting.ClientSession.ConnectionError} */
155 remoting.ClientSession.ConnectionError[message.data['error']];
156 } else {
157 error = remoting.ClientSession.ConnectionError.UNKNOWN;
160 this.onConnectionStatusUpdateHandler(state, error);
161 } else if (message.method == 'onDesktopSize') {
162 if (typeof message.data['width'] != 'number' ||
163 typeof message.data['height'] != 'number') {
164 console.error('Received invalid onDesktopSize message: ' + messageStr);
165 return;
167 this.desktopWidth = /** @type {number} */ message.data['width'];
168 this.desktopHeight = /** @type {number} */ message.data['height'];
169 this.desktopXDpi = (typeof message.data['x_dpi'] == 'number') ?
170 /** @type {number} */ (message.data['x_dpi']) : 96;
171 this.desktopYDpi = (typeof message.data['y_dpi'] == 'number') ?
172 /** @type {number} */ (message.data['y_dpi']) : 96;
173 this.onDesktopSizeUpdateHandler();
174 } else if (message.method == 'onPerfStats') {
175 if (typeof message.data['videoBandwidth'] != 'number' ||
176 typeof message.data['videoFrameRate'] != 'number' ||
177 typeof message.data['captureLatency'] != 'number' ||
178 typeof message.data['encodeLatency'] != 'number' ||
179 typeof message.data['decodeLatency'] != 'number' ||
180 typeof message.data['renderLatency'] != 'number' ||
181 typeof message.data['roundtripLatency'] != 'number') {
182 console.error('Received incorrect onPerfStats message: ' + messageStr);
183 return;
185 this.perfStats_ =
186 /** @type {remoting.ClientSession.PerfStats} */ message.data;
187 } else if (message.method == 'injectClipboardItem') {
188 if (typeof message.data['mimeType'] != 'string' ||
189 typeof message.data['item'] != 'string') {
190 console.error('Received incorrect injectClipboardItem message.');
191 return;
193 if (remoting.clipboard) {
194 remoting.clipboard.fromHost(message.data['mimeType'],
195 message.data['item']);
197 } else if (message.method == 'onFirstFrameReceived') {
198 if (remoting.clientSession) {
199 remoting.clientSession.onFirstFrameReceived();
201 } else if (message.method == 'onConnectionReady') {
202 if (typeof message.data['ready'] != 'boolean') {
203 console.error('Received incorrect onConnectionReady message.');
204 return;
206 var ready = /** @type {boolean} */ message.data['ready'];
207 this.onConnectionReadyHandler(ready);
212 * Deletes the plugin.
214 remoting.ClientPluginAsync.prototype.cleanup = function() {
215 this.plugin.parentNode.removeChild(this.plugin);
219 * @return {HTMLEmbedElement} HTML element that correspods to the plugin.
221 remoting.ClientPluginAsync.prototype.element = function() {
222 return this.plugin;
226 * @param {function(boolean): void} onDone
228 remoting.ClientPluginAsync.prototype.initialize = function(onDone) {
229 if (this.helloReceived_) {
230 onDone(true);
231 } else {
232 this.onInitializedCallback_ = onDone;
237 * @return {boolean} True if the plugin and web-app versions are compatible.
239 remoting.ClientPluginAsync.prototype.isSupportedVersion = function() {
240 if (!this.helloReceived_) {
241 console.error(
242 "isSupportedVersion() is called before the plugin is initialized.");
243 return false;
245 return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
246 this.pluginApiVersion_ >= this.API_MIN_VERSION_;
250 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
251 * @return {boolean} True if the plugin supports the named feature.
253 remoting.ClientPluginAsync.prototype.hasFeature = function(feature) {
254 if (!this.helloReceived_) {
255 console.error(
256 "hasFeature() is called before the plugin is initialized.");
257 return false;
259 return this.pluginApiFeatures_.indexOf(feature) > -1;
263 * @return {boolean} True if the plugin supports the injectKeyEvent API.
265 remoting.ClientPluginAsync.prototype.isInjectKeyEventSupported = function() {
266 return this.pluginApiVersion_ >= 6;
270 * @param {string} iq Incoming IQ stanza.
272 remoting.ClientPluginAsync.prototype.onIncomingIq = function(iq) {
273 if (this.plugin && this.plugin.postMessage) {
274 this.plugin.postMessage(JSON.stringify(
275 { method: 'incomingIq', data: { iq: iq } }));
276 } else {
277 // plugin.onIq may not be set after the plugin has been shut
278 // down. Particularly this happens when we receive response to
279 // session-terminate stanza.
280 console.warn('plugin.onIq is not set so dropping incoming message.');
285 * @param {string} hostJid The jid of the host to connect to.
286 * @param {string} hostPublicKey The base64 encoded version of the host's
287 * public key.
288 * @param {string} localJid Local jid.
289 * @param {string} sharedSecret The access code for IT2Me or the PIN
290 * for Me2Me.
291 * @param {string} authenticationMethods Comma-separated list of
292 * authentication methods the client should attempt to use.
293 * @param {string} authenticationTag A host-specific tag to mix into
294 * authentication hashes.
296 remoting.ClientPluginAsync.prototype.connect = function(
297 hostJid, hostPublicKey, localJid, sharedSecret,
298 authenticationMethods, authenticationTag) {
299 this.plugin.postMessage(JSON.stringify(
300 { method: 'connect', data: {
301 hostJid: hostJid,
302 hostPublicKey: hostPublicKey,
303 localJid: localJid,
304 sharedSecret: sharedSecret,
305 authenticationMethods: authenticationMethods,
306 authenticationTag: authenticationTag
308 }));
312 * Release all currently pressed keys.
314 remoting.ClientPluginAsync.prototype.releaseAllKeys = function() {
315 this.plugin.postMessage(JSON.stringify(
316 { method: 'releaseAllKeys', data: {} }));
320 * Send a key event to the host.
322 * @param {number} usbKeycode The USB-style code of the key to inject.
323 * @param {boolean} pressed True to inject a key press, False for a release.
325 remoting.ClientPluginAsync.prototype.injectKeyEvent =
326 function(usbKeycode, pressed) {
327 this.plugin.postMessage(JSON.stringify(
328 { method: 'injectKeyEvent', data: {
329 'usbKeycode': usbKeycode,
330 'pressed': pressed}
331 }));
335 * Remap one USB keycode to another in all subsequent key events.
337 * @param {number} fromKeycode The USB-style code of the key to remap.
338 * @param {number} toKeycode The USB-style code to remap the key to.
340 remoting.ClientPluginAsync.prototype.remapKey =
341 function(fromKeycode, toKeycode) {
342 this.plugin.postMessage(JSON.stringify(
343 { method: 'remapKey', data: {
344 'fromKeycode': fromKeycode,
345 'toKeycode': toKeycode}
346 }));
350 * Returns an associative array with a set of stats for this connecton.
352 * @return {remoting.ClientSession.PerfStats} The connection statistics.
354 remoting.ClientPluginAsync.prototype.getPerfStats = function() {
355 return this.perfStats_;
359 * Sends a clipboard item to the host.
361 * @param {string} mimeType The MIME type of the clipboard item.
362 * @param {string} item The clipboard item.
364 remoting.ClientPluginAsync.prototype.sendClipboardItem =
365 function(mimeType, item) {
366 if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
367 return;
368 this.plugin.postMessage(JSON.stringify(
369 { method: 'sendClipboardItem',
370 data: { mimeType: mimeType, item: item }}));
374 * Notifies the host that the client has the specified dimensions.
376 * @param {number} width The available client width.
377 * @param {number} height The available client height.
379 remoting.ClientPluginAsync.prototype.notifyClientDimensions =
380 function(width, height) {
381 if (!this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_DIMENSIONS))
382 return;
383 this.plugin.postMessage(JSON.stringify(
384 { method: 'notifyClientDimensions',
385 data: { width: width, height: height }}));
389 * Requests that the host pause or resume sending video updates.
391 * @param {boolean} pause True to suspend video updates, false otherwise.
393 remoting.ClientPluginAsync.prototype.pauseVideo =
394 function(pause) {
395 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO))
396 return;
397 this.plugin.postMessage(JSON.stringify(
398 { method: 'pauseVideo', data: { pause: pause }}));
402 * Requests that the host pause or resume sending audio updates.
404 * @param {boolean} pause True to suspend audio updates, false otherwise.
406 remoting.ClientPluginAsync.prototype.pauseAudio =
407 function(pause) {
408 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO))
409 return;
410 this.plugin.postMessage(JSON.stringify(
411 { method: 'pauseAudio', data: { pause: pause }}));
415 * If we haven't yet received a "hello" message from the plugin, change its
416 * size so that the user can confirm it if click-to-play is enabled, or can
417 * see the "this plugin is disabled" message if it is actually disabled.
418 * @private
420 remoting.ClientPluginAsync.prototype.showPluginForClickToPlay_ = function() {
421 if (!this.helloReceived_) {
422 var width = 200;
423 var height = 200;
424 this.plugin.width = width;
425 this.plugin.height = height;
426 // Center the plugin just underneath the "Connnecting..." dialog.
427 var parentNode = this.plugin.parentNode;
428 var dialog = document.getElementById('client-dialog');
429 var dialogRect = dialog.getBoundingClientRect();
430 parentNode.style.top = (dialogRect.bottom + 16) + 'px';
431 parentNode.style.left = (window.innerWidth - width) / 2 + 'px';