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);