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.
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.
16 /** @suppress {duplicate} */
17 var remoting
= remoting
|| {};
20 remoting
.ClientPluginMessage = function() {
24 /** @type {Object<*>} */
29 * @param {Element} container The container for the embed element.
30 * @param {Array<string>} requiredCapabilities The set of capabilties that the
31 * session must support for this application.
33 * @implements {remoting.ClientPlugin}
35 remoting
.ClientPluginImpl = function(container
,
36 requiredCapabilities
) {
37 // TODO(kelvinp): Hack to remove all plugin elements as our current code does
38 // not handle connection cancellation properly.
39 container
.innerText
= '';
40 this.plugin_
= remoting
.ClientPluginImpl
.createPluginElement_();
41 this.plugin_
.id
= 'session-client-plugin';
42 container
.appendChild(this.plugin_
);
44 /** @private {Array<string>} */
45 this.requiredCapabilities_
= requiredCapabilities
;
47 /** @private {remoting.ClientPlugin.ConnectionEventHandler} */
48 this.connectionEventHandler_
= null;
50 /** @private {?function(string, number, number)} */
51 this.updateMouseCursorImage_
= base
.doNothing
;
52 /** @private {?function(string, string)} */
53 this.updateClipboardData_
= base
.doNothing
;
54 /** @private {?function(string)} */
55 this.onCastExtensionHandler_
= base
.doNothing
;
56 /** @private {?function({rects:Array<Array<number>>}):void} */
57 this.debugRegionHandler_
= null;
59 /** @private {number} */
60 this.pluginApiVersion_
= -1;
61 /** @private {Array<string>} */
62 this.pluginApiFeatures_
= [];
63 /** @private {number} */
64 this.pluginApiMinVersion_
= -1;
66 * Capabilities to be used for the next connect request.
67 * @private {!Array<string>}
69 this.capabilities_
= [];
71 * Capabilities that are negotiated between the client and the host.
72 * @private {Array<remoting.ClientSession.Capability>}
74 this.hostCapabilities_
= null;
75 /** @private {boolean} */
76 this.helloReceived_
= false;
77 /** @private {function(boolean)|null} */
78 this.onInitializedCallback_
= null;
79 /** @private {function(string, string):void} */
80 this.onPairingComplete_ = function(clientId
, sharedSecret
) {};
81 /** @private {remoting.ClientSession.PerfStats} */
82 this.perfStats_
= new remoting
.ClientSession
.PerfStats();
84 /** @type {remoting.ClientPluginImpl} */
86 this.plugin_
.addEventListener('message',
87 /** @param {Event} event Message event from the plugin. */
90 /** @type {remoting.ClientPluginMessage} */ (event
.data
));
93 if (remoting
.settings
.CLIENT_PLUGIN_TYPE
== 'native') {
94 window
.setTimeout(this.showPluginForClickToPlay_
.bind(this), 500);
98 this.hostDesktop_
= new remoting
.ClientPlugin
.HostDesktopImpl(
99 this, this.postMessage_
.bind(this));
102 this.extensions_
= new remoting
.ProtocolExtensionManager(
103 this.sendClientMessage_
.bind(this));
105 /** @private {remoting.CredentialsProvider} */
106 this.credentials_
= null;
108 /** @private {!Object} */
109 this.keyRemappings_
= {};
113 * Creates plugin element without adding it to a container.
115 * @return {HTMLEmbedElement} Plugin element
117 remoting
.ClientPluginImpl
.createPluginElement_ = function() {
119 /** @type {HTMLEmbedElement} */ (document
.createElement('embed'));
120 if (remoting
.settings
.CLIENT_PLUGIN_TYPE
== 'pnacl') {
121 plugin
.src
= 'remoting_client_pnacl.nmf';
122 plugin
.type
= 'application/x-pnacl';
123 } else if (remoting
.settings
.CLIENT_PLUGIN_TYPE
== 'nacl') {
124 plugin
.src
= 'remoting_client_nacl.nmf';
125 plugin
.type
= 'application/x-nacl';
127 plugin
.src
= 'about://none';
128 plugin
.type
= 'application/vnd.chromium.remoting-viewer';
132 plugin
.tabIndex
= 0; // Required, otherwise focus() doesn't work.
137 * Chromoting session API version (for this javascript).
138 * This is compared with the plugin API version to verify that they are
144 remoting
.ClientPluginImpl
.prototype.API_VERSION_
= 6;
147 * The oldest API version that we support.
148 * This will differ from the |API_VERSION_| if we maintain backward
149 * compatibility with older API versions.
154 remoting
.ClientPluginImpl
.prototype.API_MIN_VERSION_
= 5;
157 * @param {remoting.ClientPlugin.ConnectionEventHandler} handler
159 remoting
.ClientPluginImpl
.prototype.setConnectionEventHandler
=
161 this.connectionEventHandler_
= handler
;
165 * @param {function(string, number, number):void} handler
167 remoting
.ClientPluginImpl
.prototype.setMouseCursorHandler = function(handler
) {
168 this.updateMouseCursorImage_
= handler
;
172 * @param {function(string, string):void} handler
174 remoting
.ClientPluginImpl
.prototype.setClipboardHandler = function(handler
) {
175 this.updateClipboardData_
= handler
;
179 * @param {?function({rects:Array<Array<number>>}):void} handler
181 remoting
.ClientPluginImpl
.prototype.setDebugDirtyRegionHandler
=
183 this.debugRegionHandler_
= handler
;
184 this.plugin_
.postMessage(JSON
.stringify(
185 { method
: 'enableDebugRegion', data
: { enable
: handler
!= null } }));
189 * @param {string|remoting.ClientPluginMessage}
190 * rawMessage Message from the plugin.
193 remoting
.ClientPluginImpl
.prototype.handleMessage_ = function(rawMessage
) {
195 /** @type {remoting.ClientPluginMessage} */
196 ((typeof(rawMessage
) == 'string') ? base
.jsonParseSafe(rawMessage
)
198 if (!message
|| !('method' in message
) || !('data' in message
)) {
199 console
.error('Received invalid message from the plugin:', rawMessage
);
204 this.handleMessageMethod_(message
);
205 } catch(/** @type {*} */ e
) {
211 * @param {remoting.ClientPluginMessage}
212 * message Parsed message from the plugin.
215 remoting
.ClientPluginImpl
.prototype.handleMessageMethod_ = function(message
) {
217 * Splits a string into a list of words delimited by spaces.
218 * @param {string} str String that should be split.
219 * @return {!Array<string>} List of words.
221 var tokenize = function(str
) {
222 /** @type {Array<string>} */
223 var tokens
= str
.match(/\S+/g);
224 return tokens
? tokens
: [];
227 if (this.connectionEventHandler_
) {
228 var handler
= this.connectionEventHandler_
;
230 if (message
.method
== 'sendOutgoingIq') {
231 handler
.onOutgoingIq(base
.getStringAttr(message
.data
, 'iq'));
233 } else if (message
.method
== 'logDebugMessage') {
234 handler
.onDebugMessage(base
.getStringAttr(message
.data
, 'message'));
236 } else if (message
.method
== 'onConnectionStatus') {
237 var stateString
= base
.getStringAttr(message
.data
, 'state');
238 var state
= remoting
.ClientSession
.State
.fromString(stateString
);
239 var error
= remoting
.ClientSession
.ConnectionError
.fromString(
240 base
.getStringAttr(message
.data
, 'error'));
242 // Delay firing the CONNECTED event until the capabilities are negotiated,
243 // TODO(kelvinp): Fix the client plugin to fire capabilities and the
244 // connected event in the same message.
245 if (state
=== remoting
.ClientSession
.State
.CONNECTED
) {
246 console
.assert(this.hostCapabilities_
=== null,
247 'Capabilities should only be set after the session is connected');
250 handler
.onConnectionStatusUpdate(state
, error
);
252 } else if (message
.method
== 'onRouteChanged') {
253 var channel
= base
.getStringAttr(message
.data
, 'channel');
254 var connectionType
= base
.getStringAttr(message
.data
, 'connectionType');
255 handler
.onRouteChanged(channel
, connectionType
);
257 } else if (message
.method
== 'onConnectionReady') {
258 var ready
= base
.getBooleanAttr(message
.data
, 'ready');
259 handler
.onConnectionReady(ready
);
261 } else if (message
.method
== 'setCapabilities') {
262 var capabilityString
= base
.getStringAttr(message
.data
, 'capabilities');
263 console
.log('plugin: setCapabilities: [' + capabilityString
+ ']');
265 console
.assert(this.hostCapabilities_
=== null,
266 'setCapabilities() should only be called once.');
267 this.hostCapabilities_
= tokenize(capabilityString
);
269 handler
.onConnectionStatusUpdate(
270 remoting
.ClientSession
.State
.CONNECTED
,
271 remoting
.ClientSession
.ConnectionError
.NONE
);
272 this.extensions_
.start();
274 } else if (message
.method
== 'onFirstFrameReceived') {
275 handler
.onFirstFrameReceived();
280 if (message
.method
== 'hello') {
281 // Resize in case we had to enlarge it to support click-to-play.
282 this.hidePluginForClickToPlay_();
283 this.pluginApiVersion_
= base
.getNumberAttr(message
.data
, 'apiVersion');
284 this.pluginApiMinVersion_
=
285 base
.getNumberAttr(message
.data
, 'apiMinVersion');
287 if (this.pluginApiVersion_
>= 7) {
288 this.pluginApiFeatures_
=
289 tokenize(base
.getStringAttr(message
.data
, 'apiFeatures'));
291 // Negotiate capabilities.
292 /** @type {!Array<string>} */
293 var supportedCapabilities
= [];
294 if ('supportedCapabilities' in message
.data
) {
295 supportedCapabilities
=
296 tokenize(base
.getStringAttr(message
.data
, 'supportedCapabilities'));
298 // At the moment the webapp does not recognize any of
299 // 'requestedCapabilities' capabilities (so they all should be disabled)
300 // and do not care about any of 'supportedCapabilities' capabilities (so
301 // they all can be enabled).
302 // All the required capabilities (specified by the app) are added to this.
303 this.capabilities_
= supportedCapabilities
.concat(
304 this.requiredCapabilities_
);
305 } else if (this.pluginApiVersion_
>= 6) {
306 this.pluginApiFeatures_
= ['highQualityScaling', 'injectKeyEvent'];
308 this.pluginApiFeatures_
= ['highQualityScaling'];
310 this.helloReceived_
= true;
311 if (this.onInitializedCallback_
!= null) {
312 this.onInitializedCallback_(true);
313 this.onInitializedCallback_
= null;
316 } else if (message
.method
== 'onDesktopSize') {
317 this.hostDesktop_
.onSizeUpdated(message
);
318 } else if (message
.method
== 'onDesktopShape') {
319 this.hostDesktop_
.onShapeUpdated(message
);
320 } else if (message
.method
== 'onPerfStats') {
321 // Return value is ignored. These calls will throw an error if the value
323 base
.getNumberAttr(message
.data
, 'videoBandwidth');
324 base
.getNumberAttr(message
.data
, 'videoFrameRate');
325 base
.getNumberAttr(message
.data
, 'captureLatency');
326 base
.getNumberAttr(message
.data
, 'encodeLatency');
327 base
.getNumberAttr(message
.data
, 'decodeLatency');
328 base
.getNumberAttr(message
.data
, 'renderLatency');
329 base
.getNumberAttr(message
.data
, 'roundtripLatency');
331 /** @type {remoting.ClientSession.PerfStats} */ (message
.data
);
333 } else if (message
.method
== 'injectClipboardItem') {
334 var mimetype
= base
.getStringAttr(message
.data
, 'mimeType');
335 var item
= base
.getStringAttr(message
.data
, 'item');
336 this.updateClipboardData_(mimetype
, item
);
338 } else if (message
.method
== 'fetchPin') {
339 // The pairingSupported value in the dictionary indicates whether both
340 // client and host support pairing. If the client doesn't support pairing,
341 // then the value won't be there at all, so give it a default of false.
342 var pairingSupported
= base
.getBooleanAttr(message
.data
, 'pairingSupported',
344 this.credentials_
.getPIN(pairingSupported
).then(
345 this.onPinFetched_
.bind(this)
348 } else if (message
.method
== 'fetchThirdPartyToken') {
349 var tokenUrl
= base
.getStringAttr(message
.data
, 'tokenUrl');
350 var hostPublicKey
= base
.getStringAttr(message
.data
, 'hostPublicKey');
351 var scope
= base
.getStringAttr(message
.data
, 'scope');
352 this.credentials_
.getThirdPartyToken(tokenUrl
, hostPublicKey
, scope
).then(
353 this.onThirdPartyTokenFetched_
.bind(this)
355 } else if (message
.method
== 'pairingResponse') {
356 var clientId
= base
.getStringAttr(message
.data
, 'clientId');
357 var sharedSecret
= base
.getStringAttr(message
.data
, 'sharedSecret');
358 this.onPairingComplete_(clientId
, sharedSecret
);
360 } else if (message
.method
== 'unsetCursorShape') {
361 this.updateMouseCursorImage_('', 0, 0);
363 } else if (message
.method
== 'setCursorShape') {
364 var width
= base
.getNumberAttr(message
.data
, 'width');
365 var height
= base
.getNumberAttr(message
.data
, 'height');
366 var hotspotX
= base
.getNumberAttr(message
.data
, 'hotspotX');
367 var hotspotY
= base
.getNumberAttr(message
.data
, 'hotspotY');
368 var srcArrayBuffer
= base
.getObjectAttr(message
.data
, 'data');
371 /** @type {HTMLCanvasElement} */ (document
.createElement('canvas'));
372 canvas
.width
= width
;
373 canvas
.height
= height
;
376 /** @type {CanvasRenderingContext2D} */ (canvas
.getContext('2d'));
377 var imageData
= context
.getImageData(0, 0, width
, height
);
378 console
.assert(srcArrayBuffer
instanceof ArrayBuffer
,
379 '|srcArrayBuffer| is not an ArrayBuffer.');
380 var src
= new Uint8Array(/** @type {ArrayBuffer} */(srcArrayBuffer
));
381 var dest
= imageData
.data
;
382 for (var i
= 0; i
< /** @type {number} */(dest
.length
); i
+= 4) {
383 dest
[i
] = src
[i
+ 2];
384 dest
[i
+ 1] = src
[i
+ 1];
385 dest
[i
+ 2] = src
[i
];
386 dest
[i
+ 3] = src
[i
+ 3];
389 context
.putImageData(imageData
, 0, 0);
390 this.updateMouseCursorImage_(canvas
.toDataURL(), hotspotX
, hotspotY
);
392 } else if (message
.method
== 'onDebugRegion') {
393 if (this.debugRegionHandler_
) {
394 this.debugRegionHandler_(
395 /** @type {{rects: Array<(Array<number>)>}} **/(message
.data
));
397 } else if (message
.method
== 'extensionMessage') {
398 var extMsgType
= base
.getStringAttr(message
.data
, 'type');
399 var extMsgData
= base
.getStringAttr(message
.data
, 'data');
400 this.extensions_
.onProtocolExtensionMessage(extMsgType
, extMsgData
);
406 * Deletes the plugin.
408 remoting
.ClientPluginImpl
.prototype.dispose = function() {
410 this.plugin_
.parentNode
.removeChild(this.plugin_
);
414 base
.dispose(this.extensions_
);
415 this.extensions_
= null;
419 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin.
421 remoting
.ClientPluginImpl
.prototype.element = function() {
426 * @param {function(boolean): void} onDone
428 remoting
.ClientPluginImpl
.prototype.initialize = function(onDone
) {
429 if (this.helloReceived_
) {
432 this.onInitializedCallback_
= onDone
;
437 * @return {boolean} True if the plugin and web-app versions are compatible.
439 remoting
.ClientPluginImpl
.prototype.isSupportedVersion = function() {
440 if (!this.helloReceived_
) {
442 "isSupportedVersion() is called before the plugin is initialized.");
445 return this.API_VERSION_
>= this.pluginApiMinVersion_
&&
446 this.pluginApiVersion_
>= this.API_MIN_VERSION_
;
450 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
451 * @return {boolean} True if the plugin supports the named feature.
453 remoting
.ClientPluginImpl
.prototype.hasFeature = function(feature
) {
454 if (!this.helloReceived_
) {
456 "hasFeature() is called before the plugin is initialized.");
459 return this.pluginApiFeatures_
.indexOf(feature
) > -1;
464 * @param {remoting.ClientSession.Capability} capability The capability to test
466 * @return {boolean} True if the capability has been negotiated between
467 * the client and host.
469 remoting
.ClientPluginImpl
.prototype.hasCapability = function(capability
) {
470 return this.hostCapabilities_
!== null &&
471 this.hostCapabilities_
.indexOf(capability
) > -1;
475 * @return {boolean} True if the plugin supports the injectKeyEvent API.
477 remoting
.ClientPluginImpl
.prototype.isInjectKeyEventSupported = function() {
478 return this.pluginApiVersion_
>= 6;
482 * @param {string} iq Incoming IQ stanza.
484 remoting
.ClientPluginImpl
.prototype.onIncomingIq = function(iq
) {
485 if (this.plugin_
&& this.plugin_
.postMessage
) {
486 this.plugin_
.postMessage(JSON
.stringify(
487 { method
: 'incomingIq', data
: { iq
: iq
} }));
489 // plugin.onIq may not be set after the plugin has been shut
490 // down. Particularly this happens when we receive response to
491 // session-terminate stanza.
492 console
.warn('plugin.onIq is not set so dropping incoming message.');
497 * @param {remoting.Host} host The host to connect to.
498 * @param {string} localJid Local jid.
499 * @param {remoting.CredentialsProvider} credentialsProvider
501 remoting
.ClientPluginImpl
.prototype.connect
=
502 function(host
, localJid
, credentialsProvider
) {
504 if (remoting
.platformIsMac()) {
506 } else if (remoting
.platformIsChromeOS()) {
510 // Use PPB_VideoDecoder API only in Chrome 43 and above. It is broken in
511 // previous versions of Chrome, see crbug.com/459103 and crbug.com/463577 .
512 var enableVideoDecodeRenderer
=
513 parseInt((remoting
.getChromeVersion() || '0').split('.')[0], 10) >= 43;
514 this.plugin_
.postMessage(JSON
.stringify(
515 { method
: 'delegateLargeCursors', data
: {} }));
516 var methods
= 'third_party,spake2_pair,spake2_hmac,spake2_plain';
517 this.credentials_
= credentialsProvider
;
518 this.useAsyncPinDialog_();
519 this.plugin_
.postMessage(JSON
.stringify(
520 { method
: 'connect', data
: {
521 hostJid
: host
.jabberId
,
522 hostPublicKey
: host
.publicKey
,
525 authenticationMethods
: methods
,
526 authenticationTag
: host
.hostId
,
527 capabilities
: this.capabilities_
.join(" "),
528 clientPairingId
: credentialsProvider
.getPairingInfo().clientId
,
529 clientPairedSecret
: credentialsProvider
.getPairingInfo().sharedSecret
,
530 keyFilter
: keyFilter
,
531 enableVideoDecodeRenderer
: enableVideoDecodeRenderer
537 * Release all currently pressed keys.
539 remoting
.ClientPluginImpl
.prototype.releaseAllKeys = function() {
540 this.plugin_
.postMessage(JSON
.stringify(
541 { method
: 'releaseAllKeys', data
: {} }));
545 * Sets and stores the key remapping setting for the current host.
547 * @param {!Object} remappings
549 remoting
.ClientPluginImpl
.prototype.setRemapKeys
=
550 function(remappings
) {
551 // Cancel any existing remappings and apply the new ones.
552 this.applyRemapKeys_(this.keyRemappings_
, false);
553 this.applyRemapKeys_(remappings
, true);
554 this.keyRemappings_
= /** @type {!Object} */ (base
.deepCopy(remappings
));
558 * Applies the configured key remappings to the session, or resets them.
560 * @param {!Object} remappings
561 * @param {boolean} apply True to apply remappings, false to cancel them.
564 remoting
.ClientPluginImpl
.prototype.applyRemapKeys_
=
565 function(remappings
, apply
) {
566 for (var i
in remappings
) {
567 var from = parseInt(i
, 10);
568 var to
= parseInt(remappings
[i
], 10);
570 console
.log('remapKey 0x' + from.toString(16) + '>0x' + to
.toString(16));
571 this.remapKey(from, to
);
573 console
.log('cancel remapKey 0x' + from.toString(16));
574 this.remapKey(from, from);
580 * Sends a key combination to the remoting host, by sending down events for
581 * the given keys, followed by up events in reverse order.
583 * @param {Array<number>} keys Key codes to be sent.
584 * @return {void} Nothing.
586 remoting
.ClientPluginImpl
.prototype.injectKeyCombination
=
588 for (var i
= 0; i
< keys
.length
; i
++) {
589 this.injectKeyEvent(keys
[i
], true);
591 for (var i
= 0; i
< keys
.length
; i
++) {
592 this.injectKeyEvent(keys
[i
], false);
597 * Send a key event to the host.
599 * @param {number} usbKeycode The USB-style code of the key to inject.
600 * @param {boolean} pressed True to inject a key press, False for a release.
602 remoting
.ClientPluginImpl
.prototype.injectKeyEvent
=
603 function(usbKeycode
, pressed
) {
604 this.plugin_
.postMessage(JSON
.stringify(
605 { method
: 'injectKeyEvent', data
: {
606 'usbKeycode': usbKeycode
,
612 * Remap one USB keycode to another in all subsequent key events.
614 * @param {number} fromKeycode The USB-style code of the key to remap.
615 * @param {number} toKeycode The USB-style code to remap the key to.
617 remoting
.ClientPluginImpl
.prototype.remapKey
=
618 function(fromKeycode
, toKeycode
) {
619 this.plugin_
.postMessage(JSON
.stringify(
620 { method
: 'remapKey', data
: {
621 'fromKeycode': fromKeycode
,
622 'toKeycode': toKeycode
}
627 * Enable/disable redirection of the specified key to the web-app.
629 * @param {number} keycode The USB-style code of the key.
630 * @param {Boolean} trap True to enable trapping, False to disable.
632 remoting
.ClientPluginImpl
.prototype.trapKey = function(keycode
, trap
) {
633 this.plugin_
.postMessage(JSON
.stringify(
634 { method
: 'trapKey', data
: {
641 * Returns an associative array with a set of stats for this connecton.
643 * @return {remoting.ClientSession.PerfStats} The connection statistics.
645 remoting
.ClientPluginImpl
.prototype.getPerfStats = function() {
646 return this.perfStats_
;
650 * Sends a clipboard item to the host.
652 * @param {string} mimeType The MIME type of the clipboard item.
653 * @param {string} item The clipboard item.
655 remoting
.ClientPluginImpl
.prototype.sendClipboardItem
=
656 function(mimeType
, item
) {
657 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.SEND_CLIPBOARD_ITEM
))
659 this.plugin_
.postMessage(JSON
.stringify(
660 { method
: 'sendClipboardItem',
661 data
: { mimeType
: mimeType
, item
: item
}}));
665 * Notifies the plugin whether to send touch events to the host.
667 * @param {boolean} enable True if touch events should be sent.
669 remoting
.ClientPluginImpl
.prototype.enableTouchEvents = function(enable
) {
670 this.plugin_
.postMessage(
671 JSON
.stringify({method
: 'enableTouchEvents', data
: {'enable': enable
}}));
675 * Notifies the host that the client has the specified size and pixel density.
677 * @param {number} width The available client width in DIPs.
678 * @param {number} height The available client height in DIPs.
679 * @param {number} device_scale The number of device pixels per DIP.
681 remoting
.ClientPluginImpl
.prototype.notifyClientResolution
=
682 function(width
, height
, device_scale
) {
683 this.hostDesktop_
.resize(width
, height
, device_scale
);
687 * Requests that the host pause or resume sending video updates.
689 * @param {boolean} pause True to suspend video updates, false otherwise.
691 remoting
.ClientPluginImpl
.prototype.pauseVideo
=
693 if (this.hasFeature(remoting
.ClientPlugin
.Feature
.VIDEO_CONTROL
)) {
694 this.plugin_
.postMessage(JSON
.stringify(
695 { method
: 'videoControl', data
: { pause
: pause
}}));
696 } else if (this.hasFeature(remoting
.ClientPlugin
.Feature
.PAUSE_VIDEO
)) {
697 this.plugin_
.postMessage(JSON
.stringify(
698 { method
: 'pauseVideo', data
: { pause
: pause
}}));
703 * Requests that the host pause or resume sending audio updates.
705 * @param {boolean} pause True to suspend audio updates, false otherwise.
707 remoting
.ClientPluginImpl
.prototype.pauseAudio
=
709 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.PAUSE_AUDIO
)) {
712 this.plugin_
.postMessage(JSON
.stringify(
713 { method
: 'pauseAudio', data
: { pause
: pause
}}));
717 * Requests that the host configure the video codec for lossless encode.
719 * @param {boolean} wantLossless True to request lossless encoding.
721 remoting
.ClientPluginImpl
.prototype.setLosslessEncode
=
722 function(wantLossless
) {
723 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.VIDEO_CONTROL
)) {
726 this.plugin_
.postMessage(JSON
.stringify(
727 { method
: 'videoControl', data
: { losslessEncode
: wantLossless
}}));
731 * Requests that the host configure the video codec for lossless color.
733 * @param {boolean} wantLossless True to request lossless color.
735 remoting
.ClientPluginImpl
.prototype.setLosslessColor
=
736 function(wantLossless
) {
737 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.VIDEO_CONTROL
)) {
740 this.plugin_
.postMessage(JSON
.stringify(
741 { method
: 'videoControl', data
: { losslessColor
: wantLossless
}}));
745 * Called when a PIN is obtained from the user.
747 * @param {string} pin The PIN.
750 remoting
.ClientPluginImpl
.prototype.onPinFetched_
=
752 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.ASYNC_PIN
)) {
755 this.plugin_
.postMessage(JSON
.stringify(
756 { method
: 'onPinFetched', data
: { pin
: pin
}}));
760 * Tells the plugin to ask for the PIN asynchronously.
763 remoting
.ClientPluginImpl
.prototype.useAsyncPinDialog_
=
765 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.ASYNC_PIN
)) {
768 this.plugin_
.postMessage(JSON
.stringify(
769 { method
: 'useAsyncPinDialog', data
: {} }));
773 * Allows automatic mouse-lock.
775 remoting
.ClientPluginImpl
.prototype.allowMouseLock = function() {
776 this.plugin_
.postMessage(JSON
.stringify(
777 { method
: 'allowMouseLock', data
: {} }));
781 * Sets the third party authentication token and shared secret.
783 * @param {remoting.ThirdPartyToken} token
786 remoting
.ClientPluginImpl
.prototype.onThirdPartyTokenFetched_ = function(
788 this.plugin_
.postMessage(JSON
.stringify(
789 { method
: 'onThirdPartyTokenFetched',
790 data
: { token
: token
.token
, sharedSecret
: token
.secret
}}));
794 * Request pairing with the host for PIN-less authentication.
796 * @param {string} clientName The human-readable name of the client.
797 * @param {function(string, string):void} onDone, Callback to receive the
798 * client id and shared secret when they are available.
800 remoting
.ClientPluginImpl
.prototype.requestPairing
=
801 function(clientName
, onDone
) {
802 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.PINLESS_AUTH
)) {
805 this.onPairingComplete_
= onDone
;
806 this.plugin_
.postMessage(JSON
.stringify(
807 { method
: 'requestPairing', data
: { clientName
: clientName
} }));
811 * Send an extension message to the host.
813 * @param {string} type The message type.
814 * @param {string} message The message payload.
817 remoting
.ClientPluginImpl
.prototype.sendClientMessage_
=
818 function(type
, message
) {
819 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.EXTENSION_MESSAGE
)) {
822 this.plugin_
.postMessage(JSON
.stringify(
823 { method
: 'extensionMessage',
824 data
: { type
: type
, data
: message
} }));
828 remoting
.ClientPluginImpl
.prototype.hostDesktop = function() {
829 return this.hostDesktop_
;
832 remoting
.ClientPluginImpl
.prototype.extensions = function() {
833 return this.extensions_
;
837 * If we haven't yet received a "hello" message from the plugin, change its
838 * size so that the user can confirm it if click-to-play is enabled, or can
839 * see the "this plugin is disabled" message if it is actually disabled.
842 remoting
.ClientPluginImpl
.prototype.showPluginForClickToPlay_ = function() {
843 if (!this.helloReceived_
) {
846 this.plugin_
.style
.width
= width
+ 'px';
847 this.plugin_
.style
.height
= height
+ 'px';
848 // Center the plugin just underneath the "Connnecting..." dialog.
849 var dialog
= document
.getElementById('client-dialog');
850 var dialogRect
= dialog
.getBoundingClientRect();
851 this.plugin_
.style
.top
= (dialogRect
.bottom
+ 16) + 'px';
852 this.plugin_
.style
.left
= (window
.innerWidth
- width
) / 2 + 'px';
853 this.plugin_
.style
.position
= 'fixed';
858 * Undo the CSS rules needed to make the plugin clickable for click-to-play.
861 remoting
.ClientPluginImpl
.prototype.hidePluginForClickToPlay_ = function() {
862 this.plugin_
.style
.width
= '';
863 this.plugin_
.style
.height
= '';
864 this.plugin_
.style
.top
= '';
865 this.plugin_
.style
.left
= '';
866 this.plugin_
.style
.position
= '';
870 * Callback passed to submodules to post a message to the plugin.
872 * @param {Object} message
875 remoting
.ClientPluginImpl
.prototype.postMessage_ = function(message
) {
876 if (this.plugin_
&& this.plugin_
.postMessage
) {
877 this.plugin_
.postMessage(JSON
.stringify(message
));
883 * @implements {remoting.ClientPluginFactory}
885 remoting
.DefaultClientPluginFactory = function() {};
888 * @param {Element} container
889 * @param {Array<string>} requiredCapabilities
890 * @return {remoting.ClientPlugin}
892 remoting
.DefaultClientPluginFactory
.prototype.createPlugin
=
893 function(container
, requiredCapabilities
) {
894 return new remoting
.ClientPluginImpl(container
,
895 requiredCapabilities
);
898 remoting
.DefaultClientPluginFactory
.prototype.preloadPlugin = function() {
899 if (remoting
.settings
.CLIENT_PLUGIN_TYPE
!= 'pnacl') {
903 var plugin
= remoting
.ClientPluginImpl
.createPluginElement_();
904 plugin
.addEventListener(
905 'loadend', function() { document
.body
.removeChild(plugin
); }, false);
906 document
.body
.appendChild(plugin
);