Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(742)

Side by Side Diff: remoting/webapp/client_plugin.js

Issue 552403004: Interfaceify ClientPlugin in preparation for mocking it. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Renamed interfaces. Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
Sergey Ulanov 2014/09/20 00:35:24 this is not a new file, so keep 2012 here?
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview 6 * @fileoverview
7 * Class that wraps low-level details of interacting with the client plugin. 7 * Interface abstracting the ClientPlugin functionality.
Sergey Ulanov 2014/09/20 00:35:24 Please update this comment - it's confusing after
Jamie 2014/09/20 00:54:22 Done.
8 *
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.
12 */ 8 */
13 9
14 'use strict'; 10 'use strict';
15 11
16 /** @suppress {duplicate} */ 12 /** @suppress {duplicate} */
17 var remoting = remoting || {}; 13 var remoting = remoting || {};
18 14
19 /** 15 /**
20 * @param {Element} container The container for the embed element. 16 * @interface
21 * @param {function(string, string):boolean} onExtensionMessage The handler for 17 * @extends {base.Disposable}
22 * protocol extension messages. Returns true if a message is recognized;
23 * false otherwise.
24 * @constructor
25 */ 18 */
26 remoting.ClientPlugin = function(container, onExtensionMessage) { 19 remoting.ClientPlugin = function() {};
27 this.plugin_ = remoting.ClientPlugin.createPluginElement_();
28 this.plugin_.id = 'session-client-plugin';
29 container.appendChild(this.plugin_);
30
31 this.onExtensionMessage_ = onExtensionMessage;
32
33 this.desktopWidth = 0;
34 this.desktopHeight = 0;
35 this.desktopXDpi = 96;
36 this.desktopYDpi = 96;
37
38 /** @param {string} iq The Iq stanza received from the host. */
39 this.onOutgoingIqHandler = function (iq) {};
40 /** @param {string} message Log message. */
41 this.onDebugMessageHandler = function (message) {};
42 /**
43 * @param {number} state The connection state.
44 * @param {number} error The error code, if any.
45 */
46 this.onConnectionStatusUpdateHandler = function(state, error) {};
47 /** @param {boolean} ready Connection ready state. */
48 this.onConnectionReadyHandler = function(ready) {};
49
50 /**
51 * @param {string} tokenUrl Token-request URL, received from the host.
52 * @param {string} hostPublicKey Public key for the host.
53 * @param {string} scope OAuth scope to request the token for.
54 */
55 this.fetchThirdPartyTokenHandler = function(
56 tokenUrl, hostPublicKey, scope) {};
57 this.onDesktopSizeUpdateHandler = function () {};
58 /** @param {!Array.<string>} capabilities The negotiated capabilities. */
59 this.onSetCapabilitiesHandler = function (capabilities) {};
60 this.fetchPinHandler = function (supportsPairing) {};
61 /** @param {string} data Remote gnubbyd data. */
62 this.onGnubbyAuthHandler = function(data) {};
63 /**
64 * @param {string} url
65 * @param {number} hotspotX
66 * @param {number} hotspotY
67 */
68 this.updateMouseCursorImage = function(url, hotspotX, hotspotY) {};
69
70 /** @param {string} data Remote cast extension message. */
71 this.onCastExtensionHandler = function(data) {};
72
73 /** @type {remoting.MediaSourceRenderer} */
74 this.mediaSourceRenderer_ = null;
75
76 /** @type {number} */
77 this.pluginApiVersion_ = -1;
78 /** @type {Array.<string>} */
79 this.pluginApiFeatures_ = [];
80 /** @type {number} */
81 this.pluginApiMinVersion_ = -1;
82 /** @type {!Array.<string>} */
83 this.capabilities_ = [];
84 /** @type {boolean} */
85 this.helloReceived_ = false;
86 /** @type {function(boolean)|null} */
87 this.onInitializedCallback_ = null;
88 /** @type {function(string, string):void} */
89 this.onPairingComplete_ = function(clientId, sharedSecret) {};
90 /** @type {remoting.ClientSession.PerfStats} */
91 this.perfStats_ = new remoting.ClientSession.PerfStats();
92
93 /** @type {remoting.ClientPlugin} */
94 var that = this;
95 /** @param {Event} event Message event from the plugin. */
96 this.plugin_.addEventListener('message', function(event) {
97 that.handleMessage_(event.data);
98 }, false);
99
100 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'native') {
101 window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500);
102 }
103 };
104 20
105 /** 21 /**
106 * Creates plugin element without adding it to a container. 22 * @return {number} The width of the remote desktop, in pixels.
107 *
108 * @return {remoting.ViewerPlugin} Plugin element
109 */ 23 */
110 remoting.ClientPlugin.createPluginElement_ = function() { 24 remoting.ClientPlugin.prototype.getDesktopWidth = function() {};
111 var plugin = /** @type {remoting.ViewerPlugin} */
112 document.createElement('embed');
113 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'pnacl') {
114 plugin.src = 'remoting_client_pnacl.nmf';
115 plugin.type = 'application/x-pnacl';
116 } else if (remoting.settings.CLIENT_PLUGIN_TYPE == 'nacl') {
117 plugin.src = 'remoting_client_nacl.nmf';
118 plugin.type = 'application/x-nacl';
119 } else {
120 plugin.src = 'about://none';
121 plugin.type = 'application/vnd.chromium.remoting-viewer';
122 }
123 plugin.width = 0;
124 plugin.height = 0;
125 plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
126 return plugin;
127 }
128 25
129 /** 26 /**
130 * Preloads the plugin to make instantiation faster when the user tries 27 * @return {number} The height of the remote desktop, in pixels.
131 * to connect.
132 */ 28 */
133 remoting.ClientPlugin.preload = function() { 29 remoting.ClientPlugin.prototype.getDesktopHeight = function() {};
134 if (remoting.settings.CLIENT_PLUGIN_TYPE != 'pnacl') {
135 return;
136 }
137
138 var plugin = remoting.ClientPlugin.createPluginElement_();
139 plugin.addEventListener(
140 'loadend', function() { document.body.removeChild(plugin); }, false);
141 document.body.appendChild(plugin);
142 }
143 30
144 /** 31 /**
145 * Set of features for which hasFeature() can be used to test. 32 * @return {number} The x-DPI of the remote desktop.
146 *
147 * @enum {string}
148 */ 33 */
149 remoting.ClientPlugin.Feature = { 34 remoting.ClientPlugin.prototype.getDesktopXDpi = function() {};
150 INJECT_KEY_EVENT: 'injectKeyEvent',
151 NOTIFY_CLIENT_RESOLUTION: 'notifyClientResolution',
152 ASYNC_PIN: 'asyncPin',
153 PAUSE_VIDEO: 'pauseVideo',
154 PAUSE_AUDIO: 'pauseAudio',
155 REMAP_KEY: 'remapKey',
156 SEND_CLIPBOARD_ITEM: 'sendClipboardItem',
157 THIRD_PARTY_AUTH: 'thirdPartyAuth',
158 TRAP_KEY: 'trapKey',
159 PINLESS_AUTH: 'pinlessAuth',
160 EXTENSION_MESSAGE: 'extensionMessage',
161 MEDIA_SOURCE_RENDERING: 'mediaSourceRendering',
162 VIDEO_CONTROL: 'videoControl'
163 };
164 35
165 /** 36 /**
166 * Chromoting session API version (for this javascript). 37 * @return {number} The y-DPI of the remote desktop.
167 * This is compared with the plugin API version to verify that they are
168 * compatible.
169 *
170 * @const
171 * @private
172 */ 38 */
173 remoting.ClientPlugin.prototype.API_VERSION_ = 6; 39 remoting.ClientPlugin.prototype.getDesktopYDpi = function() {};
174 40
175 /** 41 /**
176 * The oldest API version that we support. 42 * @return {HTMLElement} The DOM element representing the remote session.
177 * This will differ from the |API_VERSION_| if we maintain backward
178 * compatibility with older API versions.
179 *
180 * @const
181 * @private
182 */ 43 */
183 remoting.ClientPlugin.prototype.API_MIN_VERSION_ = 5; 44 remoting.ClientPlugin.prototype.element = function() {};
184 45
185 /** 46 /**
186 * @param {string|{method:string, data:Object.<string,*>}} 47 * @param {function():void} onDone Completion callback.
187 * rawMessage Message from the plugin.
188 * @private
189 */ 48 */
190 remoting.ClientPlugin.prototype.handleMessage_ = function(rawMessage) { 49 remoting.ClientPlugin.prototype.initialize = function(onDone) {};
191 var message =
192 /** @type {{method:string, data:Object.<string,*>}} */
193 ((typeof(rawMessage) == 'string') ? jsonParseSafe(rawMessage)
194 : rawMessage);
195 if (!message || !('method' in message) || !('data' in message)) {
196 console.error('Received invalid message from the plugin:', rawMessage);
197 return;
198 }
199
200 try {
201 this.handleMessageMethod_(message);
202 } catch(e) {
203 console.error(/** @type {*} */ (e));
204 }
205 }
206
207 /**
208 * @param {{method:string, data:Object.<string,*>}}
209 * message Parsed message from the plugin.
210 * @private
211 */
212 remoting.ClientPlugin.prototype.handleMessageMethod_ = function(message) {
213 /**
214 * Splits a string into a list of words delimited by spaces.
215 * @param {string} str String that should be split.
216 * @return {!Array.<string>} List of words.
217 */
218 var tokenize = function(str) {
219 /** @type {Array.<string>} */
220 var tokens = str.match(/\S+/g);
221 return tokens ? tokens : [];
222 };
223
224 if (message.method == 'hello') {
225 // Resize in case we had to enlarge it to support click-to-play.
226 this.hidePluginForClickToPlay_();
227 this.pluginApiVersion_ = getNumberAttr(message.data, 'apiVersion');
228 this.pluginApiMinVersion_ = getNumberAttr(message.data, 'apiMinVersion');
229
230 if (this.pluginApiVersion_ >= 7) {
231 this.pluginApiFeatures_ =
232 tokenize(getStringAttr(message.data, 'apiFeatures'));
233
234 // Negotiate capabilities.
235
236 /** @type {!Array.<string>} */
237 var requestedCapabilities = [];
238 if ('requestedCapabilities' in message.data) {
239 requestedCapabilities =
240 tokenize(getStringAttr(message.data, 'requestedCapabilities'));
241 }
242
243 /** @type {!Array.<string>} */
244 var supportedCapabilities = [];
245 if ('supportedCapabilities' in message.data) {
246 supportedCapabilities =
247 tokenize(getStringAttr(message.data, 'supportedCapabilities'));
248 }
249
250 // At the moment the webapp does not recognize any of
251 // 'requestedCapabilities' capabilities (so they all should be disabled)
252 // and do not care about any of 'supportedCapabilities' capabilities (so
253 // they all can be enabled).
254 this.capabilities_ = supportedCapabilities;
255
256 // Let the host know that the webapp can be requested to always send
257 // the client's dimensions.
258 this.capabilities_.push(
259 remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION);
260
261 // Let the host know that we're interested in knowing whether or not
262 // it rate-limits desktop-resize requests.
263 this.capabilities_.push(
264 remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS);
265
266 // Let the host know that we can use the video framerecording extension.
267 this.capabilities_.push(
268 remoting.ClientSession.Capability.VIDEO_RECORDER);
269
270 // Let the host know that we can support casting of the screen.
271 // TODO(aiguha): Add this capability based on a gyp/command-line flag,
272 // rather than by default.
273 this.capabilities_.push(
274 remoting.ClientSession.Capability.CAST);
275
276 } else if (this.pluginApiVersion_ >= 6) {
277 this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
278 } else {
279 this.pluginApiFeatures_ = ['highQualityScaling'];
280 }
281 this.helloReceived_ = true;
282 if (this.onInitializedCallback_ != null) {
283 this.onInitializedCallback_(true);
284 this.onInitializedCallback_ = null;
285 }
286
287 } else if (message.method == 'sendOutgoingIq') {
288 this.onOutgoingIqHandler(getStringAttr(message.data, 'iq'));
289
290 } else if (message.method == 'logDebugMessage') {
291 this.onDebugMessageHandler(getStringAttr(message.data, 'message'));
292
293 } else if (message.method == 'onConnectionStatus') {
294 var state = remoting.ClientSession.State.fromString(
295 getStringAttr(message.data, 'state'))
296 var error = remoting.ClientSession.ConnectionError.fromString(
297 getStringAttr(message.data, 'error'));
298 this.onConnectionStatusUpdateHandler(state, error);
299
300 } else if (message.method == 'onDesktopSize') {
301 this.desktopWidth = getNumberAttr(message.data, 'width');
302 this.desktopHeight = getNumberAttr(message.data, 'height');
303 this.desktopXDpi = getNumberAttr(message.data, 'x_dpi', 96);
304 this.desktopYDpi = getNumberAttr(message.data, 'y_dpi', 96);
305 this.onDesktopSizeUpdateHandler();
306
307 } else if (message.method == 'onPerfStats') {
308 // Return value is ignored. These calls will throw an error if the value
309 // is not a number.
310 getNumberAttr(message.data, 'videoBandwidth');
311 getNumberAttr(message.data, 'videoFrameRate');
312 getNumberAttr(message.data, 'captureLatency');
313 getNumberAttr(message.data, 'encodeLatency');
314 getNumberAttr(message.data, 'decodeLatency');
315 getNumberAttr(message.data, 'renderLatency');
316 getNumberAttr(message.data, 'roundtripLatency');
317 this.perfStats_ =
318 /** @type {remoting.ClientSession.PerfStats} */ message.data;
319
320 } else if (message.method == 'injectClipboardItem') {
321 var mimetype = getStringAttr(message.data, 'mimeType');
322 var item = getStringAttr(message.data, 'item');
323 if (remoting.clipboard) {
324 remoting.clipboard.fromHost(mimetype, item);
325 }
326
327 } else if (message.method == 'onFirstFrameReceived') {
328 if (remoting.clientSession) {
329 remoting.clientSession.onFirstFrameReceived();
330 }
331
332 } else if (message.method == 'onConnectionReady') {
333 var ready = getBooleanAttr(message.data, 'ready');
334 this.onConnectionReadyHandler(ready);
335
336 } else if (message.method == 'fetchPin') {
337 // The pairingSupported value in the dictionary indicates whether both
338 // client and host support pairing. If the client doesn't support pairing,
339 // then the value won't be there at all, so give it a default of false.
340 var pairingSupported = getBooleanAttr(message.data, 'pairingSupported',
341 false)
342 this.fetchPinHandler(pairingSupported);
343
344 } else if (message.method == 'setCapabilities') {
345 /** @type {!Array.<string>} */
346 var capabilities = tokenize(getStringAttr(message.data, 'capabilities'));
347 this.onSetCapabilitiesHandler(capabilities);
348
349 } else if (message.method == 'fetchThirdPartyToken') {
350 var tokenUrl = getStringAttr(message.data, 'tokenUrl');
351 var hostPublicKey = getStringAttr(message.data, 'hostPublicKey');
352 var scope = getStringAttr(message.data, 'scope');
353 this.fetchThirdPartyTokenHandler(tokenUrl, hostPublicKey, scope);
354
355 } else if (message.method == 'pairingResponse') {
356 var clientId = getStringAttr(message.data, 'clientId');
357 var sharedSecret = getStringAttr(message.data, 'sharedSecret');
358 this.onPairingComplete_(clientId, sharedSecret);
359
360 } else if (message.method == 'extensionMessage') {
361 var extMsgType = getStringAttr(message.data, 'type');
362 var extMsgData = getStringAttr(message.data, 'data');
363 switch (extMsgType) {
364 case 'gnubby-auth':
365 this.onGnubbyAuthHandler(extMsgData);
366 break;
367 case 'test-echo-reply':
368 console.log('Got echo reply: ' + extMsgData);
369 break;
370 case 'cast_message':
371 this.onCastExtensionHandler(extMsgData);
372 break;
373 default:
374 this.onExtensionMessage_(extMsgType, extMsgData);
375 break;
376 }
377
378 } else if (message.method == 'mediaSourceReset') {
379 if (!this.mediaSourceRenderer_) {
380 console.error('Unexpected mediaSourceReset.');
381 return;
382 }
383 this.mediaSourceRenderer_.reset(getStringAttr(message.data, 'format'))
384
385 } else if (message.method == 'mediaSourceData') {
386 if (!(message.data['buffer'] instanceof ArrayBuffer)) {
387 console.error('Invalid mediaSourceData message:', message.data);
388 return;
389 }
390 if (!this.mediaSourceRenderer_) {
391 console.error('Unexpected mediaSourceData.');
392 return;
393 }
394 // keyframe flag may be absent from the message.
395 var keyframe = !!message.data['keyframe'];
396 this.mediaSourceRenderer_.onIncomingData(
397 (/** @type {ArrayBuffer} */ message.data['buffer']), keyframe);
398
399 } else if (message.method == 'unsetCursorShape') {
400 this.updateMouseCursorImage('', 0, 0);
401
402 } else if (message.method == 'setCursorShape') {
403 var width = getNumberAttr(message.data, 'width');
404 var height = getNumberAttr(message.data, 'height');
405 var hotspotX = getNumberAttr(message.data, 'hotspotX');
406 var hotspotY = getNumberAttr(message.data, 'hotspotY');
407 var srcArrayBuffer = getObjectAttr(message.data, 'data');
408
409 var canvas =
410 /** @type {HTMLCanvasElement} */ (document.createElement('canvas'));
411 canvas.width = width;
412 canvas.height = height;
413
414 var context =
415 /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
416 var imageData = context.getImageData(0, 0, width, height);
417 base.debug.assert(srcArrayBuffer instanceof ArrayBuffer);
418 var src = new Uint8Array(/** @type {ArrayBuffer} */(srcArrayBuffer));
419 var dest = imageData.data;
420 for (var i = 0; i < /** @type {number} */(dest.length); i += 4) {
421 dest[i] = src[i + 2];
422 dest[i + 1] = src[i + 1];
423 dest[i + 2] = src[i];
424 dest[i + 3] = src[i + 3];
425 }
426
427 context.putImageData(imageData, 0, 0);
428 this.updateMouseCursorImage(canvas.toDataURL(), hotspotX, hotspotY);
429 }
430 };
431
432 /**
433 * Deletes the plugin.
434 */
435 remoting.ClientPlugin.prototype.cleanup = function() {
436 if (this.plugin_) {
437 this.plugin_.parentNode.removeChild(this.plugin_);
438 this.plugin_ = null;
439 }
440 };
441
442 /**
443 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin.
444 */
445 remoting.ClientPlugin.prototype.element = function() {
446 return this.plugin_;
447 };
448
449 /**
450 * @param {function(boolean): void} onDone
451 */
452 remoting.ClientPlugin.prototype.initialize = function(onDone) {
453 if (this.helloReceived_) {
454 onDone(true);
455 } else {
456 this.onInitializedCallback_ = onDone;
457 }
458 };
459
460 /**
461 * @return {boolean} True if the plugin and web-app versions are compatible.
462 */
463 remoting.ClientPlugin.prototype.isSupportedVersion = function() {
464 if (!this.helloReceived_) {
465 console.error(
466 "isSupportedVersion() is called before the plugin is initialized.");
467 return false;
468 }
469 return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
470 this.pluginApiVersion_ >= this.API_MIN_VERSION_;
471 };
472
473 /**
474 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
475 * @return {boolean} True if the plugin supports the named feature.
476 */
477 remoting.ClientPlugin.prototype.hasFeature = function(feature) {
478 if (!this.helloReceived_) {
479 console.error(
480 "hasFeature() is called before the plugin is initialized.");
481 return false;
482 }
483 return this.pluginApiFeatures_.indexOf(feature) > -1;
484 };
485
486 /**
487 * @return {boolean} True if the plugin supports the injectKeyEvent API.
488 */
489 remoting.ClientPlugin.prototype.isInjectKeyEventSupported = function() {
490 return this.pluginApiVersion_ >= 6;
491 };
492
493 /**
494 * @param {string} iq Incoming IQ stanza.
495 */
496 remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {
497 if (this.plugin_ && this.plugin_.postMessage) {
498 this.plugin_.postMessage(JSON.stringify(
499 { method: 'incomingIq', data: { iq: iq } }));
500 } else {
501 // plugin.onIq may not be set after the plugin has been shut
502 // down. Particularly this happens when we receive response to
503 // session-terminate stanza.
504 console.warn('plugin.onIq is not set so dropping incoming message.');
505 }
506 };
507 50
508 /** 51 /**
509 * @param {string} hostJid The jid of the host to connect to. 52 * @param {string} hostJid The jid of the host to connect to.
510 * @param {string} hostPublicKey The base64 encoded version of the host's 53 * @param {string} hostPublicKey The base64 encoded version of the host's
511 * public key. 54 * public key.
512 * @param {string} localJid Local jid. 55 * @param {string} localJid Local jid.
513 * @param {string} sharedSecret The access code for IT2Me or the PIN 56 * @param {string} sharedSecret The access code for IT2Me or the PIN
514 * for Me2Me. 57 * for Me2Me.
515 * @param {string} authenticationMethods Comma-separated list of 58 * @param {string} authenticationMethods Comma-separated list of
516 * authentication methods the client should attempt to use. 59 * authentication methods the client should attempt to use.
517 * @param {string} authenticationTag A host-specific tag to mix into 60 * @param {string} authenticationTag A host-specific tag to mix into
518 * authentication hashes. 61 * authentication hashes.
519 * @param {string} clientPairingId For paired Me2Me connections, the 62 * @param {string} clientPairingId For paired Me2Me connections, the
520 * pairing id for this client, as issued by the host. 63 * pairing id for this client, as issued by the host.
521 * @param {string} clientPairedSecret For paired Me2Me connections, the 64 * @param {string} clientPairedSecret For paired Me2Me connections, the
522 * paired secret for this client, as issued by the host. 65 * paired secret for this client, as issued by the host.
523 */ 66 */
524 remoting.ClientPlugin.prototype.connect = function( 67 remoting.ClientPlugin.prototype.connect = function(
525 hostJid, hostPublicKey, localJid, sharedSecret, 68 hostJid, hostPublicKey, localJid, sharedSecret,
526 authenticationMethods, authenticationTag, 69 authenticationMethods, authenticationTag,
527 clientPairingId, clientPairedSecret) { 70 clientPairingId, clientPairedSecret) {};
528 var keyFilter = '';
529 if (remoting.platformIsMac()) {
530 keyFilter = 'mac';
531 } else if (remoting.platformIsChromeOS()) {
532 keyFilter = 'cros';
533 }
534 this.plugin_.postMessage(JSON.stringify(
535 { method: 'delegateLargeCursors', data: {} }));
536 this.plugin_.postMessage(JSON.stringify(
537 { method: 'connect', data: {
538 hostJid: hostJid,
539 hostPublicKey: hostPublicKey,
540 localJid: localJid,
541 sharedSecret: sharedSecret,
542 authenticationMethods: authenticationMethods,
543 authenticationTag: authenticationTag,
544 capabilities: this.capabilities_.join(" "),
545 clientPairingId: clientPairingId,
546 clientPairedSecret: clientPairedSecret,
547 keyFilter: keyFilter
548 }
549 }));
550 };
551 71
552 /** 72 /**
553 * Release all currently pressed keys. 73 * @param {number} key The keycode to inject.
74 * @param {boolean} down True for press; false for a release.
554 */ 75 */
555 remoting.ClientPlugin.prototype.releaseAllKeys = function() { 76 remoting.ClientPlugin.prototype.injectKeyEvent =
556 this.plugin_.postMessage(JSON.stringify( 77 function(key, down) {};
557 { method: 'releaseAllKeys', data: {} }));
558 };
559 78
560 /** 79 /**
561 * Send a key event to the host. 80 * @param {number} from
562 * 81 * @param {number} to
563 * @param {number} usbKeycode The USB-style code of the key to inject.
564 * @param {boolean} pressed True to inject a key press, False for a release.
565 */ 82 */
566 remoting.ClientPlugin.prototype.injectKeyEvent = 83 remoting.ClientPlugin.prototype.remapKey = function(from, to) {};
567 function(usbKeycode, pressed) {
568 this.plugin_.postMessage(JSON.stringify(
569 { method: 'injectKeyEvent', data: {
570 'usbKeycode': usbKeycode,
571 'pressed': pressed}
572 }));
573 };
574 84
575 /** 85 /**
576 * Remap one USB keycode to another in all subsequent key events. 86 * Release all keys currently being pressed.
577 *
578 * @param {number} fromKeycode The USB-style code of the key to remap.
579 * @param {number} toKeycode The USB-style code to remap the key to.
580 */ 87 */
581 remoting.ClientPlugin.prototype.remapKey = 88 remoting.ClientPlugin.prototype.releaseAllKeys = function() {};
582 function(fromKeycode, toKeycode) {
583 this.plugin_.postMessage(JSON.stringify(
584 { method: 'remapKey', data: {
585 'fromKeycode': fromKeycode,
586 'toKeycode': toKeycode}
587 }));
588 };
589 89
590 /** 90 /**
591 * Enable/disable redirection of the specified key to the web-app. 91 * @param {number} width
592 * 92 * @param {number} height
593 * @param {number} keycode The USB-style code of the key. 93 * @param {number} dpi
594 * @param {Boolean} trap True to enable trapping, False to disable.
595 */ 94 */
596 remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) { 95 remoting.ClientPlugin.prototype.notifyClientResolution =
597 this.plugin_.postMessage(JSON.stringify( 96 function(width, height, dpi) {};
598 { method: 'trapKey', data: {
599 'keycode': keycode,
600 'trap': trap}
601 }));
602 };
603 97
604 /** 98 /**
605 * Returns an associative array with a set of stats for this connecton. 99 * @param {string} iq
100 */
101 remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {};
102
103 /**
104 * @return {boolean} True if the web-app and plugin are compatible.
105 */
106 remoting.ClientPlugin.prototype.isSupportedVersion = function() {};
107
108 /**
109 * @param {remoting.ClientPlugin.Feature} feature
110 * @return {boolean} True if the plugin support the specified feature.
111 */
112 remoting.ClientPlugin.prototype.hasFeature = function(feature) {};
113
114 /**
115 * Enable MediaSource rendering via the specified renderer.
606 * 116 *
607 * @return {remoting.ClientSession.PerfStats} The connection statistics. 117 * @param {remoting.MediaSourceRenderer} mediaSourceRenderer
608 */ 118 */
609 remoting.ClientPlugin.prototype.getPerfStats = function() { 119 remoting.ClientPlugin.prototype.enableMediaSourceRendering =
610 return this.perfStats_; 120 function(mediaSourceRenderer) {};
611 };
612 121
613 /** 122 /**
614 * Sends a clipboard item to the host. 123 * Sends a clipboard item to the host.
615 * 124 *
616 * @param {string} mimeType The MIME type of the clipboard item. 125 * @param {string} mimeType The MIME type of the clipboard item.
617 * @param {string} item The clipboard item. 126 * @param {string} item The clipboard item.
618 */ 127 */
619 remoting.ClientPlugin.prototype.sendClipboardItem = 128 remoting.ClientPlugin.prototype.sendClipboardItem =
620 function(mimeType, item) { 129 function(mimeType, item) {};
621 if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
622 return;
623 this.plugin_.postMessage(JSON.stringify(
624 { method: 'sendClipboardItem',
625 data: { mimeType: mimeType, item: item }}));
626 };
627 130
628 /** 131 /**
629 * Notifies the host that the client has the specified size and pixel density. 132 * Tell the plugin to request a PIN asynchronously.
630 *
631 * @param {number} width The available client width in DIPs.
632 * @param {number} height The available client height in DIPs.
633 * @param {number} device_scale The number of device pixels per DIP.
634 */ 133 */
635 remoting.ClientPlugin.prototype.notifyClientResolution = 134 remoting.ClientPlugin.prototype.useAsyncPinDialog = function() {};
636 function(width, height, device_scale) {
637 if (this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_RESOLUTION)) {
638 var dpi = Math.floor(device_scale * 96);
639 this.plugin_.postMessage(JSON.stringify(
640 { method: 'notifyClientResolution',
641 data: { width: Math.floor(width * device_scale),
642 height: Math.floor(height * device_scale),
643 x_dpi: dpi, y_dpi: dpi }}));
644 }
645 };
646 135
647 /** 136 /**
648 * Requests that the host pause or resume sending video updates. 137 * Request that this client be paired with the current host.
649 * 138 *
650 * @param {boolean} pause True to suspend video updates, false otherwise. 139 * @param {string} clientName The human-readable name of the client.
140 * @param {function(string, string):void} onDone Callback to receive the
141 * client id and shared secret when they are available.
651 */ 142 */
652 remoting.ClientPlugin.prototype.pauseVideo = 143 remoting.ClientPlugin.prototype.requestPairing =
653 function(pause) { 144 function(clientName, onDone) {};
654 if (this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
655 this.plugin_.postMessage(JSON.stringify(
656 { method: 'videoControl', data: { pause: pause }}));
657 } else if (this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) {
658 this.plugin_.postMessage(JSON.stringify(
659 { method: 'pauseVideo', data: { pause: pause }}));
660 }
661 };
662
663 /**
664 * Requests that the host pause or resume sending audio updates.
665 *
666 * @param {boolean} pause True to suspend audio updates, false otherwise.
667 */
668 remoting.ClientPlugin.prototype.pauseAudio =
669 function(pause) {
670 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) {
671 return;
672 }
673 this.plugin_.postMessage(JSON.stringify(
674 { method: 'pauseAudio', data: { pause: pause }}));
675 };
676
677 /**
678 * Requests that the host configure the video codec for lossless encode.
679 *
680 * @param {boolean} wantLossless True to request lossless encoding.
681 */
682 remoting.ClientPlugin.prototype.setLosslessEncode =
683 function(wantLossless) {
684 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
685 return;
686 }
687 this.plugin_.postMessage(JSON.stringify(
688 { method: 'videoControl', data: { losslessEncode: wantLossless }}));
689 };
690
691 /**
692 * Requests that the host configure the video codec for lossless color.
693 *
694 * @param {boolean} wantLossless True to request lossless color.
695 */
696 remoting.ClientPlugin.prototype.setLosslessColor =
697 function(wantLossless) {
698 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
699 return;
700 }
701 this.plugin_.postMessage(JSON.stringify(
702 { method: 'videoControl', data: { losslessColor: wantLossless }}));
703 };
704 145
705 /** 146 /**
706 * Called when a PIN is obtained from the user. 147 * Called when a PIN is obtained from the user.
707 * 148 *
708 * @param {string} pin The PIN. 149 * @param {string} pin The PIN.
709 */ 150 */
710 remoting.ClientPlugin.prototype.onPinFetched = 151 remoting.ClientPlugin.prototype.onPinFetched = function(pin) {};
711 function(pin) {
712 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
713 return;
714 }
715 this.plugin_.postMessage(JSON.stringify(
716 { method: 'onPinFetched', data: { pin: pin }}));
717 };
718
719 /**
720 * Tells the plugin to ask for the PIN asynchronously.
721 */
722 remoting.ClientPlugin.prototype.useAsyncPinDialog =
723 function() {
724 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
725 return;
726 }
727 this.plugin_.postMessage(JSON.stringify(
728 { method: 'useAsyncPinDialog', data: {} }));
729 };
730 152
731 /** 153 /**
732 * Sets the third party authentication token and shared secret. 154 * Sets the third party authentication token and shared secret.
733 * 155 *
734 * @param {string} token The token received from the token URL. 156 * @param {string} token The token received from the token URL.
735 * @param {string} sharedSecret Shared secret received from the token URL. 157 * @param {string} sharedSecret Shared secret received from the token URL.
736 */ 158 */
737 remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = function( 159 remoting.ClientPlugin.prototype.onThirdPartyTokenFetched =
738 token, sharedSecret) { 160 function(token, sharedSecret) {};
739 this.plugin_.postMessage(JSON.stringify(
740 { method: 'onThirdPartyTokenFetched',
741 data: { token: token, sharedSecret: sharedSecret}}));
742 };
743 161
744 /** 162 /**
745 * Request pairing with the host for PIN-less authentication. 163 * @param {boolean} pause True to pause the audio stream; false to resume it.
746 *
747 * @param {string} clientName The human-readable name of the client.
748 * @param {function(string, string):void} onDone, Callback to receive the
749 * client id and shared secret when they are available.
750 */ 164 */
751 remoting.ClientPlugin.prototype.requestPairing = 165 remoting.ClientPlugin.prototype.pauseAudio = function(pause) {};
752 function(clientName, onDone) { 166
753 if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) { 167 /**
754 return; 168 * @param {boolean} pause True to pause the video stream; false to resume it.
755 } 169 */
756 this.onPairingComplete_ = onDone; 170 remoting.ClientPlugin.prototype.pauseVideo = function(pause) {};
757 this.plugin_.postMessage(JSON.stringify( 171
758 { method: 'requestPairing', data: { clientName: clientName } })); 172 /**
759 }; 173 * @return {remoting.ClientSession.PerfStats} A summary of the connection
174 * performance.
175 */
176 remoting.ClientPlugin.prototype.getPerfStats = function() {};
760 177
761 /** 178 /**
762 * Send an extension message to the host. 179 * Send an extension message to the host.
763 * 180 *
764 * @param {string} type The message type. 181 * @param {string} name
765 * @param {string} message The message payload. 182 * @param {string} data
766 */ 183 */
767 remoting.ClientPlugin.prototype.sendClientMessage = 184 remoting.ClientPlugin.prototype.sendClientMessage =
768 function(type, message) { 185 function(name, data) {};
769 if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) {
770 return;
771 }
772 this.plugin_.postMessage(JSON.stringify(
773 { method: 'extensionMessage',
774 data: { type: type, data: message } }));
775 186
187 /**
188 * @param {function(string):void} handler Callback for sending an IQ stanza.
189 */
190 remoting.ClientPlugin.prototype.setOnOutgoingIqHandler =
191 function(handler) {};
192
193 /**
194 * @param {function(string):void} handler Callback for logging debug messages.
195 */
196 remoting.ClientPlugin.prototype.setOnDebugMessageHandler =
197 function(handler) {};
198
199 /**
200 * @param {function(number, number):void} handler Callback for connection status
201 * update notifications. The first parameter is the connection state; the
202 * second is the error code, if any.
203 */
204 remoting.ClientPlugin.prototype.setConnectionStatusUpdateHandler =
205 function(handler) {};
206
207 /**
208 * @param {function(boolean):void} handler Callback for connection readiness
209 * notifications.
210 */
211 remoting.ClientPlugin.prototype.setConnectionReadyHandler =
212 function(handler) {};
213
214 /**
215 * @param {function():void} handler Callback for desktop size change
216 * notifications.
217 */
218 remoting.ClientPlugin.prototype.setDesktopSizeUpdateHandler =
219 function(handler) {};
220
221 /**
222 * @param {function(!Array.<string>):void} handler Callback to inform of
223 * capabilities negotiated between host and client.
224 */
225 remoting.ClientPlugin.prototype.setCapabilitiesHandler =
226 function(handler) {};
227
228 /**
229 * @param {function(string):void} handler Callback for processing security key
230 * (Gnubby) protocol messages.
231 */
232 remoting.ClientPlugin.prototype.setGnubbyAuthHandler =
233 function(handler) {};
234
235 /**
236 * @param {function(string):void} handler Callback for processing Cast protocol
237 * messages.
238 */
239 remoting.ClientPlugin.prototype.setCastExtensionHandler =
240 function(handler) {};
241
242 /**
243 * @param {function(string, number, number):void} handler Callback for
244 * processing large mouse cursor images. The first parameter is a data:
245 * URL encoding the mouse cursor; the second and third parameters are
246 * the cursor hotspot's x- and y-coordinates, respectively.
247 */
248 remoting.ClientPlugin.prototype.setMouseCursorHandler =
249 function(handler) {};
250
251 /**
252 * @param {function(string, string, string):void} handler Callback for
253 * fetching third-party tokens. The first parameter is the token URL; the
254 * second is the public key of the host; the third is the OAuth2 scope
255 * being requested.
256 */
257 remoting.ClientPlugin.prototype.setFetchThirdPartyTokenHandler =
258 function(handler) {};
259
260 /**
261 * @param {function(boolean):void} handler Callback for fetching a PIN from
262 * the user. The parameter is true if PIN pairing is supported by the
263 * host, or false otherwise.
264 */
265 remoting.ClientPlugin.prototype.setFetchPinHandler =
266 function(handler) {};
267
268
269 /**
270 * Set of features for which hasFeature() can be used to test.
271 *
272 * @enum {string}
273 */
274 remoting.ClientPlugin.Feature = {
Jamie 2014/09/19 23:55:44 This enum was defined on the implementation in the
275 INJECT_KEY_EVENT: 'injectKeyEvent',
276 NOTIFY_CLIENT_RESOLUTION: 'notifyClientResolution',
277 ASYNC_PIN: 'asyncPin',
278 PAUSE_VIDEO: 'pauseVideo',
279 PAUSE_AUDIO: 'pauseAudio',
280 REMAP_KEY: 'remapKey',
281 SEND_CLIPBOARD_ITEM: 'sendClipboardItem',
282 THIRD_PARTY_AUTH: 'thirdPartyAuth',
283 TRAP_KEY: 'trapKey',
284 PINLESS_AUTH: 'pinlessAuth',
285 EXTENSION_MESSAGE: 'extensionMessage',
286 MEDIA_SOURCE_RENDERING: 'mediaSourceRendering',
287 VIDEO_CONTROL: 'videoControl'
776 }; 288 };
777 289
778 /**
779 * Request MediaStream-based rendering.
780 *
781 * @param {remoting.MediaSourceRenderer} mediaSourceRenderer
782 */
783 remoting.ClientPlugin.prototype.enableMediaSourceRendering =
784 function(mediaSourceRenderer) {
785 if (!this.hasFeature(remoting.ClientPlugin.Feature.MEDIA_SOURCE_RENDERING)) {
786 return;
787 }
788 this.mediaSourceRenderer_ = mediaSourceRenderer;
789 this.plugin_.postMessage(JSON.stringify(
790 { method: 'enableMediaSourceRendering', data: {} }));
791 };
792 290
793 /** 291 /**
794 * If we haven't yet received a "hello" message from the plugin, change its 292 * @interface
795 * size so that the user can confirm it if click-to-play is enabled, or can
796 * see the "this plugin is disabled" message if it is actually disabled.
797 * @private
798 */ 293 */
799 remoting.ClientPlugin.prototype.showPluginForClickToPlay_ = function() { 294 remoting.ClientPluginFactory = function() {};
800 if (!this.helloReceived_) {
801 var width = 200;
802 var height = 200;
803 this.plugin_.style.width = width + 'px';
804 this.plugin_.style.height = height + 'px';
805 // Center the plugin just underneath the "Connnecting..." dialog.
806 var dialog = document.getElementById('client-dialog');
807 var dialogRect = dialog.getBoundingClientRect();
808 this.plugin_.style.top = (dialogRect.bottom + 16) + 'px';
809 this.plugin_.style.left = (window.innerWidth - width) / 2 + 'px';
810 this.plugin_.style.position = 'fixed';
811 }
812 };
813 295
814 /** 296 /**
815 * Undo the CSS rules needed to make the plugin clickable for click-to-play. 297 * @param {Element} container The container for the embed element.
816 * @private 298 * @param {function(string, string):boolean} onExtensionMessage The handler for
299 * protocol extension messages. Returns true if a message is recognized;
300 * false otherwise.
301 * @return {remoting.ClientPlugin} A new client plugin instance.
817 */ 302 */
818 remoting.ClientPlugin.prototype.hidePluginForClickToPlay_ = function() { 303 remoting.ClientPluginFactory.prototype.createPlugin =
819 this.plugin_.style.width = ''; 304 function(container, onExtensionMessage) {};
820 this.plugin_.style.height = ''; 305
821 this.plugin_.style.top = ''; 306 /**
822 this.plugin_.style.left = ''; 307 * Preload the plugin to make instantiation faster when the user tries
823 this.plugin_.style.position = ''; 308 * to connect.
824 }; 309 */
310 remoting.ClientPluginFactory.prototype.preloadPlugin = function() {};
Jamie 2014/09/19 23:55:44 This was previously a static member of the impleme
311
312 /**
313 * @type {remoting.ClientPluginFactory}
314 */
315 remoting.ClientPlugin.factory = null;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698