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<string,*>} */
29 * @param {Element} container The container for the embed element.
30 * @param {function(string, string):boolean} onExtensionMessage The handler for
31 * protocol extension messages. Returns true if a message is recognized;
33 * @param {Array<string>} requiredCapabilities The set of capabilties that the
34 * session must support for this application.
36 * @implements {remoting.ClientPlugin}
38 remoting.ClientPluginImpl = function(container, onExtensionMessage,
39 requiredCapabilities) {
40 this.plugin_ = remoting.ClientPluginImpl.createPluginElement_();
41 this.plugin_.id = 'session-client-plugin';
42 container.appendChild(this.plugin_);
44 this.onExtensionMessage_ = onExtensionMessage;
46 * @type {Array<string>}
49 this.requiredCapabilities_ = requiredCapabilities;
52 * @param {string} iq The Iq stanza received from the host.
55 this.onOutgoingIqHandler_ = function (iq) {};
57 * @param {string} message Log message.
60 this.onDebugMessageHandler_ = function (message) {};
62 * @param {number} state The connection state.
63 * @param {number} error The error code, if any.
66 this.onConnectionStatusUpdateHandler_ = function(state, error) {};
69 * @param {string} channel The channel name.
70 * @param {string} connectionType The connection type.
73 this.onRouteChangedHandler_ = function(channel, connectionType) {};
76 * @param {boolean} ready Connection ready state.
79 this.onConnectionReadyHandler_ = function(ready) {};
82 * @param {string} tokenUrl Token-request URL, received from the host.
83 * @param {string} hostPublicKey Public key for the host.
84 * @param {string} scope OAuth scope to request the token for.
87 this.fetchThirdPartyTokenHandler_ = function(
88 tokenUrl, hostPublicKey, scope) {};
90 * @param {!Array<string>} capabilities The negotiated capabilities.
93 this.onSetCapabilitiesHandler_ = function (capabilities) {};
95 this.fetchPinHandler_ = function (supportsPairing) {};
97 * @param {string} data Remote gnubbyd data.
100 this.onGnubbyAuthHandler_ = function(data) {};
102 * @param {string} url
103 * @param {number} hotspotX
104 * @param {number} hotspotY
107 this.updateMouseCursorImage_ = function(url, hotspotX, hotspotY) {};
109 * @param {string} data Remote cast extension message.
112 this.onCastExtensionHandler_ = function(data) {};
113 /** @private {?function({rects:Array<Array<number>>}):void} */
114 this.debugRegionHandler_ = null;
120 this.pluginApiVersion_ = -1;
122 * @type {Array<string>}
125 this.pluginApiFeatures_ = [];
130 this.pluginApiMinVersion_ = -1;
132 * @type {!Array<string>}
135 this.capabilities_ = [];
140 this.helloReceived_ = false;
142 * @type {function(boolean)|null}
145 this.onInitializedCallback_ = null;
147 * @type {function(string, string):void}
150 this.onPairingComplete_ = function(clientId, sharedSecret) {};
152 * @type {remoting.ClientSession.PerfStats}
155 this.perfStats_ = new remoting.ClientSession.PerfStats();
157 /** @type {remoting.ClientPluginImpl} */
159 /** @param {Event} event Message event from the plugin. */
160 this.plugin_.addEventListener('message', function(event) {
162 /** @type {remoting.ClientPluginMessage} */ (event.data));
165 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'native') {
166 window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500);
169 this.hostDesktop_ = new remoting.ClientPlugin.HostDesktopImpl(
170 this, this.postMessage_.bind(this));
174 * Creates plugin element without adding it to a container.
176 * @return {HTMLEmbedElement} Plugin element
178 remoting.ClientPluginImpl.createPluginElement_ = function() {
180 /** @type {HTMLEmbedElement} */ (document.createElement('embed'));
181 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'pnacl') {
182 plugin.src = 'remoting_client_pnacl.nmf';
183 plugin.type = 'application/x-pnacl';
184 } else if (remoting.settings.CLIENT_PLUGIN_TYPE == 'nacl') {
185 plugin.src = 'remoting_client_nacl.nmf';
186 plugin.type = 'application/x-nacl';
188 plugin.src = 'about://none';
189 plugin.type = 'application/vnd.chromium.remoting-viewer';
193 plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
198 * Chromoting session API version (for this javascript).
199 * This is compared with the plugin API version to verify that they are
205 remoting.ClientPluginImpl.prototype.API_VERSION_ = 6;
208 * The oldest API version that we support.
209 * This will differ from the |API_VERSION_| if we maintain backward
210 * compatibility with older API versions.
215 remoting.ClientPluginImpl.prototype.API_MIN_VERSION_ = 5;
218 * @param {function(string):void} handler
220 remoting.ClientPluginImpl.prototype.setOnOutgoingIqHandler = function(handler) {
221 this.onOutgoingIqHandler_ = handler;
225 * @param {function(string):void} handler
227 remoting.ClientPluginImpl.prototype.setOnDebugMessageHandler =
229 this.onDebugMessageHandler_ = handler;
233 * @param {function(number, number):void} handler
235 remoting.ClientPluginImpl.prototype.setConnectionStatusUpdateHandler =
237 this.onConnectionStatusUpdateHandler_ = handler;
241 * @param {function(string, string):void} handler
243 remoting.ClientPluginImpl.prototype.setRouteChangedHandler = function(handler) {
244 this.onRouteChangedHandler_ = handler;
248 * @param {function(boolean):void} handler
250 remoting.ClientPluginImpl.prototype.setConnectionReadyHandler =
252 this.onConnectionReadyHandler_ = handler;
256 * @param {function(!Array<string>):void} handler
258 remoting.ClientPluginImpl.prototype.setCapabilitiesHandler = function(handler) {
259 this.onSetCapabilitiesHandler_ = handler;
263 * @param {function(string):void} handler
265 remoting.ClientPluginImpl.prototype.setGnubbyAuthHandler = function(handler) {
266 this.onGnubbyAuthHandler_ = handler;
270 * @param {function(string):void} handler
272 remoting.ClientPluginImpl.prototype.setCastExtensionHandler =
274 this.onCastExtensionHandler_ = handler;
278 * @param {function(string, number, number):void} handler
280 remoting.ClientPluginImpl.prototype.setMouseCursorHandler = function(handler) {
281 this.updateMouseCursorImage_ = handler;
285 * @param {function(string, string, string):void} handler
287 remoting.ClientPluginImpl.prototype.setFetchThirdPartyTokenHandler =
289 this.fetchThirdPartyTokenHandler_ = handler;
293 * @param {function(boolean):void} handler
295 remoting.ClientPluginImpl.prototype.setFetchPinHandler = function(handler) {
296 this.fetchPinHandler_ = handler;
300 * @param {?function({rects:Array<Array<number>>}):void} handler
302 remoting.ClientPluginImpl.prototype.setDebugDirtyRegionHandler =
304 this.debugRegionHandler_ = handler;
305 this.plugin_.postMessage(JSON.stringify(
306 { method: 'enableDebugRegion', data: { enable: handler != null } }));
310 * @param {string|remoting.ClientPluginMessage}
311 * rawMessage Message from the plugin.
314 remoting.ClientPluginImpl.prototype.handleMessage_ = function(rawMessage) {
316 /** @type {remoting.ClientPluginMessage} */
317 ((typeof(rawMessage) == 'string') ? base.jsonParseSafe(rawMessage)
319 if (!message || !('method' in message) || !('data' in message)) {
320 console.error('Received invalid message from the plugin:', rawMessage);
325 this.handleMessageMethod_(message);
326 } catch(/** @type {*} */ e) {
332 * @param {remoting.ClientPluginMessage}
333 * message Parsed message from the plugin.
336 remoting.ClientPluginImpl.prototype.handleMessageMethod_ = function(message) {
338 * Splits a string into a list of words delimited by spaces.
339 * @param {string} str String that should be split.
340 * @return {!Array<string>} List of words.
342 var tokenize = function(str) {
343 /** @type {Array<string>} */
344 var tokens = str.match(/\S+/g);
345 return tokens ? tokens : [];
348 if (message.method == 'hello') {
349 // Resize in case we had to enlarge it to support click-to-play.
350 this.hidePluginForClickToPlay_();
351 this.pluginApiVersion_ = getNumberAttr(message.data, 'apiVersion');
352 this.pluginApiMinVersion_ = getNumberAttr(message.data, 'apiMinVersion');
354 if (this.pluginApiVersion_ >= 7) {
355 this.pluginApiFeatures_ =
356 tokenize(getStringAttr(message.data, 'apiFeatures'));
358 // Negotiate capabilities.
359 /** @type {!Array<string>} */
360 var supportedCapabilities = [];
361 if ('supportedCapabilities' in message.data) {
362 supportedCapabilities =
363 tokenize(getStringAttr(message.data, 'supportedCapabilities'));
365 // At the moment the webapp does not recognize any of
366 // 'requestedCapabilities' capabilities (so they all should be disabled)
367 // and do not care about any of 'supportedCapabilities' capabilities (so
368 // they all can be enabled).
369 // All the required capabilities (specified by the app) are added to this.
370 this.capabilities_ = supportedCapabilities.concat(
371 this.requiredCapabilities_);
372 } else if (this.pluginApiVersion_ >= 6) {
373 this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
375 this.pluginApiFeatures_ = ['highQualityScaling'];
377 this.helloReceived_ = true;
378 if (this.onInitializedCallback_ != null) {
379 this.onInitializedCallback_(true);
380 this.onInitializedCallback_ = null;
383 } else if (message.method == 'sendOutgoingIq') {
384 this.onOutgoingIqHandler_(getStringAttr(message.data, 'iq'));
386 } else if (message.method == 'logDebugMessage') {
387 this.onDebugMessageHandler_(getStringAttr(message.data, 'message'));
389 } else if (message.method == 'onConnectionStatus') {
390 var state = remoting.ClientSession.State.fromString(
391 getStringAttr(message.data, 'state'));
392 var error = remoting.ClientSession.ConnectionError.fromString(
393 getStringAttr(message.data, 'error'));
394 this.onConnectionStatusUpdateHandler_(state, error);
396 } else if (message.method == 'onRouteChanged') {
397 var channel = getStringAttr(message.data, 'channel');
398 var connectionType = getStringAttr(message.data, 'connectionType');
399 this.onRouteChangedHandler_(channel, connectionType);
401 } else if (message.method == 'onDesktopSize') {
402 this.hostDesktop_.onSizeUpdated(message);
403 } else if (message.method == 'onDesktopShape') {
404 this.hostDesktop_.onShapeUpdated(message);
405 } else if (message.method == 'onPerfStats') {
406 // Return value is ignored. These calls will throw an error if the value
408 getNumberAttr(message.data, 'videoBandwidth');
409 getNumberAttr(message.data, 'videoFrameRate');
410 getNumberAttr(message.data, 'captureLatency');
411 getNumberAttr(message.data, 'encodeLatency');
412 getNumberAttr(message.data, 'decodeLatency');
413 getNumberAttr(message.data, 'renderLatency');
414 getNumberAttr(message.data, 'roundtripLatency');
416 /** @type {remoting.ClientSession.PerfStats} */ (message.data);
418 } else if (message.method == 'injectClipboardItem') {
419 var mimetype = getStringAttr(message.data, 'mimeType');
420 var item = getStringAttr(message.data, 'item');
421 if (remoting.clipboard) {
422 remoting.clipboard.fromHost(mimetype, item);
425 } else if (message.method == 'onFirstFrameReceived') {
426 if (remoting.clientSession) {
427 remoting.clientSession.onFirstFrameReceived();
430 } else if (message.method == 'onConnectionReady') {
431 var ready = getBooleanAttr(message.data, 'ready');
432 this.onConnectionReadyHandler_(ready);
434 } else if (message.method == 'fetchPin') {
435 // The pairingSupported value in the dictionary indicates whether both
436 // client and host support pairing. If the client doesn't support pairing,
437 // then the value won't be there at all, so give it a default of false.
438 var pairingSupported = getBooleanAttr(message.data, 'pairingSupported',
440 this.fetchPinHandler_(pairingSupported);
442 } else if (message.method == 'setCapabilities') {
443 /** @type {!Array<string>} */
444 var capabilities = tokenize(getStringAttr(message.data, 'capabilities'));
445 this.onSetCapabilitiesHandler_(capabilities);
447 } else if (message.method == 'fetchThirdPartyToken') {
448 var tokenUrl = getStringAttr(message.data, 'tokenUrl');
449 var hostPublicKey = getStringAttr(message.data, 'hostPublicKey');
450 var scope = getStringAttr(message.data, 'scope');
451 this.fetchThirdPartyTokenHandler_(tokenUrl, hostPublicKey, scope);
453 } else if (message.method == 'pairingResponse') {
454 var clientId = getStringAttr(message.data, 'clientId');
455 var sharedSecret = getStringAttr(message.data, 'sharedSecret');
456 this.onPairingComplete_(clientId, sharedSecret);
458 } else if (message.method == 'extensionMessage') {
459 var extMsgType = getStringAttr(message.data, 'type');
460 var extMsgData = getStringAttr(message.data, 'data');
461 switch (extMsgType) {
463 this.onGnubbyAuthHandler_(extMsgData);
465 case 'test-echo-reply':
466 console.log('Got echo reply: ' + extMsgData);
469 this.onCastExtensionHandler_(extMsgData);
472 this.onExtensionMessage_(extMsgType, extMsgData);
476 } else if (message.method == 'unsetCursorShape') {
477 this.updateMouseCursorImage_('', 0, 0);
479 } else if (message.method == 'setCursorShape') {
480 var width = getNumberAttr(message.data, 'width');
481 var height = getNumberAttr(message.data, 'height');
482 var hotspotX = getNumberAttr(message.data, 'hotspotX');
483 var hotspotY = getNumberAttr(message.data, 'hotspotY');
484 var srcArrayBuffer = getObjectAttr(message.data, 'data');
487 /** @type {HTMLCanvasElement} */ (document.createElement('canvas'));
488 canvas.width = width;
489 canvas.height = height;
492 /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
493 var imageData = context.getImageData(0, 0, width, height);
494 base.debug.assert(srcArrayBuffer instanceof ArrayBuffer);
495 var src = new Uint8Array(/** @type {ArrayBuffer} */(srcArrayBuffer));
496 var dest = imageData.data;
497 for (var i = 0; i < /** @type {number} */(dest.length); i += 4) {
498 dest[i] = src[i + 2];
499 dest[i + 1] = src[i + 1];
500 dest[i + 2] = src[i];
501 dest[i + 3] = src[i + 3];
504 context.putImageData(imageData, 0, 0);
505 this.updateMouseCursorImage_(canvas.toDataURL(), hotspotX, hotspotY);
507 } else if (message.method == 'onDebugRegion') {
508 if (this.debugRegionHandler_) {
509 this.debugRegionHandler_(
510 /** @type {{rects: Array<(Array<number>)>}} **/(message.data));
517 * Deletes the plugin.
519 remoting.ClientPluginImpl.prototype.dispose = function() {
521 this.plugin_.parentNode.removeChild(this.plugin_);
527 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin.
529 remoting.ClientPluginImpl.prototype.element = function() {
534 * @param {function(boolean): void} onDone
536 remoting.ClientPluginImpl.prototype.initialize = function(onDone) {
537 if (this.helloReceived_) {
540 this.onInitializedCallback_ = onDone;
545 * @return {boolean} True if the plugin and web-app versions are compatible.
547 remoting.ClientPluginImpl.prototype.isSupportedVersion = function() {
548 if (!this.helloReceived_) {
550 "isSupportedVersion() is called before the plugin is initialized.");
553 return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
554 this.pluginApiVersion_ >= this.API_MIN_VERSION_;
558 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
559 * @return {boolean} True if the plugin supports the named feature.
561 remoting.ClientPluginImpl.prototype.hasFeature = function(feature) {
562 if (!this.helloReceived_) {
564 "hasFeature() is called before the plugin is initialized.");
567 return this.pluginApiFeatures_.indexOf(feature) > -1;
571 * @return {boolean} True if the plugin supports the injectKeyEvent API.
573 remoting.ClientPluginImpl.prototype.isInjectKeyEventSupported = function() {
574 return this.pluginApiVersion_ >= 6;
578 * @param {string} iq Incoming IQ stanza.
580 remoting.ClientPluginImpl.prototype.onIncomingIq = function(iq) {
581 if (this.plugin_ && this.plugin_.postMessage) {
582 this.plugin_.postMessage(JSON.stringify(
583 { method: 'incomingIq', data: { iq: iq } }));
585 // plugin.onIq may not be set after the plugin has been shut
586 // down. Particularly this happens when we receive response to
587 // session-terminate stanza.
588 console.warn('plugin.onIq is not set so dropping incoming message.');
593 * @param {string} hostJid The jid of the host to connect to.
594 * @param {string} hostPublicKey The base64 encoded version of the host's
596 * @param {string} localJid Local jid.
597 * @param {string} sharedSecret The access code for IT2Me or the PIN
599 * @param {string} authenticationMethods Comma-separated list of
600 * authentication methods the client should attempt to use.
601 * @param {string} authenticationTag A host-specific tag to mix into
602 * authentication hashes.
603 * @param {string} clientPairingId For paired Me2Me connections, the
604 * pairing id for this client, as issued by the host.
605 * @param {string} clientPairedSecret For paired Me2Me connections, the
606 * paired secret for this client, as issued by the host.
608 remoting.ClientPluginImpl.prototype.connect = function(
609 hostJid, hostPublicKey, localJid, sharedSecret,
610 authenticationMethods, authenticationTag,
611 clientPairingId, clientPairedSecret) {
613 if (remoting.platformIsMac()) {
615 } else if (remoting.platformIsChromeOS()) {
618 // Use PPB_VideoDecoder API only in Chrome 42 and above. It is broken in
619 // previous versions of Chrome, see http://crbug.com/447403 .
620 // Currently PPAPI doesn't provide a way for plugins to check the Chrome
621 // version, so this check needs to be in the webapp.
622 var enableVideoDecodeRenderer =
623 parseInt((remoting.getChromeVersion() || '0').split('.')[0], 10) >= 42;
624 this.plugin_.postMessage(JSON.stringify(
625 { method: 'delegateLargeCursors', data: {} }));
626 this.plugin_.postMessage(JSON.stringify(
627 { method: 'connect', data: {
629 hostPublicKey: hostPublicKey,
631 sharedSecret: sharedSecret,
632 authenticationMethods: authenticationMethods,
633 authenticationTag: authenticationTag,
634 capabilities: this.capabilities_.join(" "),
635 clientPairingId: clientPairingId,
636 clientPairedSecret: clientPairedSecret,
637 keyFilter: keyFilter,
638 enableVideoDecodeRenderer: enableVideoDecodeRenderer
644 * Release all currently pressed keys.
646 remoting.ClientPluginImpl.prototype.releaseAllKeys = function() {
647 this.plugin_.postMessage(JSON.stringify(
648 { method: 'releaseAllKeys', data: {} }));
652 * Send a key event to the host.
654 * @param {number} usbKeycode The USB-style code of the key to inject.
655 * @param {boolean} pressed True to inject a key press, False for a release.
657 remoting.ClientPluginImpl.prototype.injectKeyEvent =
658 function(usbKeycode, pressed) {
659 this.plugin_.postMessage(JSON.stringify(
660 { method: 'injectKeyEvent', data: {
661 'usbKeycode': usbKeycode,
667 * Remap one USB keycode to another in all subsequent key events.
669 * @param {number} fromKeycode The USB-style code of the key to remap.
670 * @param {number} toKeycode The USB-style code to remap the key to.
672 remoting.ClientPluginImpl.prototype.remapKey =
673 function(fromKeycode, toKeycode) {
674 this.plugin_.postMessage(JSON.stringify(
675 { method: 'remapKey', data: {
676 'fromKeycode': fromKeycode,
677 'toKeycode': toKeycode}
682 * Enable/disable redirection of the specified key to the web-app.
684 * @param {number} keycode The USB-style code of the key.
685 * @param {Boolean} trap True to enable trapping, False to disable.
687 remoting.ClientPluginImpl.prototype.trapKey = function(keycode, trap) {
688 this.plugin_.postMessage(JSON.stringify(
689 { method: 'trapKey', data: {
696 * Returns an associative array with a set of stats for this connecton.
698 * @return {remoting.ClientSession.PerfStats} The connection statistics.
700 remoting.ClientPluginImpl.prototype.getPerfStats = function() {
701 return this.perfStats_;
705 * Sends a clipboard item to the host.
707 * @param {string} mimeType The MIME type of the clipboard item.
708 * @param {string} item The clipboard item.
710 remoting.ClientPluginImpl.prototype.sendClipboardItem =
711 function(mimeType, item) {
712 if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
714 this.plugin_.postMessage(JSON.stringify(
715 { method: 'sendClipboardItem',
716 data: { mimeType: mimeType, item: item }}));
720 * Notifies the host that the client has the specified size and pixel density.
722 * @param {number} width The available client width in DIPs.
723 * @param {number} height The available client height in DIPs.
724 * @param {number} device_scale The number of device pixels per DIP.
726 remoting.ClientPluginImpl.prototype.notifyClientResolution =
727 function(width, height, device_scale) {
728 this.hostDesktop_.resize(width, height, device_scale);
732 * Requests that the host pause or resume sending video updates.
734 * @param {boolean} pause True to suspend video updates, false otherwise.
736 remoting.ClientPluginImpl.prototype.pauseVideo =
738 if (this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
739 this.plugin_.postMessage(JSON.stringify(
740 { method: 'videoControl', data: { pause: pause }}));
741 } else if (this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) {
742 this.plugin_.postMessage(JSON.stringify(
743 { method: 'pauseVideo', data: { pause: pause }}));
748 * Requests that the host pause or resume sending audio updates.
750 * @param {boolean} pause True to suspend audio updates, false otherwise.
752 remoting.ClientPluginImpl.prototype.pauseAudio =
754 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) {
757 this.plugin_.postMessage(JSON.stringify(
758 { method: 'pauseAudio', data: { pause: pause }}));
762 * Requests that the host configure the video codec for lossless encode.
764 * @param {boolean} wantLossless True to request lossless encoding.
766 remoting.ClientPluginImpl.prototype.setLosslessEncode =
767 function(wantLossless) {
768 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
771 this.plugin_.postMessage(JSON.stringify(
772 { method: 'videoControl', data: { losslessEncode: wantLossless }}));
776 * Requests that the host configure the video codec for lossless color.
778 * @param {boolean} wantLossless True to request lossless color.
780 remoting.ClientPluginImpl.prototype.setLosslessColor =
781 function(wantLossless) {
782 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
785 this.plugin_.postMessage(JSON.stringify(
786 { method: 'videoControl', data: { losslessColor: wantLossless }}));
790 * Called when a PIN is obtained from the user.
792 * @param {string} pin The PIN.
794 remoting.ClientPluginImpl.prototype.onPinFetched =
796 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
799 this.plugin_.postMessage(JSON.stringify(
800 { method: 'onPinFetched', data: { pin: pin }}));
804 * Tells the plugin to ask for the PIN asynchronously.
806 remoting.ClientPluginImpl.prototype.useAsyncPinDialog =
808 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
811 this.plugin_.postMessage(JSON.stringify(
812 { method: 'useAsyncPinDialog', data: {} }));
816 * Allows automatic mouse-lock.
818 remoting.ClientPluginImpl.prototype.allowMouseLock = function() {
819 this.plugin_.postMessage(JSON.stringify(
820 { method: 'allowMouseLock', data: {} }));
824 * Sets the third party authentication token and shared secret.
826 * @param {string} token The token received from the token URL.
827 * @param {string} sharedSecret Shared secret received from the token URL.
829 remoting.ClientPluginImpl.prototype.onThirdPartyTokenFetched = function(
830 token, sharedSecret) {
831 this.plugin_.postMessage(JSON.stringify(
832 { method: 'onThirdPartyTokenFetched',
833 data: { token: token, sharedSecret: sharedSecret}}));
837 * Request pairing with the host for PIN-less authentication.
839 * @param {string} clientName The human-readable name of the client.
840 * @param {function(string, string):void} onDone, Callback to receive the
841 * client id and shared secret when they are available.
843 remoting.ClientPluginImpl.prototype.requestPairing =
844 function(clientName, onDone) {
845 if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) {
848 this.onPairingComplete_ = onDone;
849 this.plugin_.postMessage(JSON.stringify(
850 { method: 'requestPairing', data: { clientName: clientName } }));
854 * Send an extension message to the host.
856 * @param {string} type The message type.
857 * @param {string} message The message payload.
859 remoting.ClientPluginImpl.prototype.sendClientMessage =
860 function(type, message) {
861 if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) {
864 this.plugin_.postMessage(JSON.stringify(
865 { method: 'extensionMessage',
866 data: { type: type, data: message } }));
870 remoting.ClientPluginImpl.prototype.hostDesktop = function() {
871 return this.hostDesktop_;
875 * If we haven't yet received a "hello" message from the plugin, change its
876 * size so that the user can confirm it if click-to-play is enabled, or can
877 * see the "this plugin is disabled" message if it is actually disabled.
880 remoting.ClientPluginImpl.prototype.showPluginForClickToPlay_ = function() {
881 if (!this.helloReceived_) {
884 this.plugin_.style.width = width + 'px';
885 this.plugin_.style.height = height + 'px';
886 // Center the plugin just underneath the "Connnecting..." dialog.
887 var dialog = document.getElementById('client-dialog');
888 var dialogRect = dialog.getBoundingClientRect();
889 this.plugin_.style.top = (dialogRect.bottom + 16) + 'px';
890 this.plugin_.style.left = (window.innerWidth - width) / 2 + 'px';
891 this.plugin_.style.position = 'fixed';
896 * Undo the CSS rules needed to make the plugin clickable for click-to-play.
899 remoting.ClientPluginImpl.prototype.hidePluginForClickToPlay_ = function() {
900 this.plugin_.style.width = '';
901 this.plugin_.style.height = '';
902 this.plugin_.style.top = '';
903 this.plugin_.style.left = '';
904 this.plugin_.style.position = '';
908 * Callback passed to submodules to post a message to the plugin.
910 * @param {Object} message
913 remoting.ClientPluginImpl.prototype.postMessage_ = function(message) {
914 if (this.plugin_ && this.plugin_.postMessage) {
915 this.plugin_.postMessage(JSON.stringify(message));
921 * @implements {remoting.ClientPluginFactory}
923 remoting.DefaultClientPluginFactory = function() {};
926 * @param {Element} container
927 * @param {function(string, string):boolean} onExtensionMessage
928 * @param {Array<string>} requiredCapabilities
929 * @return {remoting.ClientPlugin}
931 remoting.DefaultClientPluginFactory.prototype.createPlugin =
932 function(container, onExtensionMessage, requiredCapabilities) {
933 return new remoting.ClientPluginImpl(container, onExtensionMessage,
934 requiredCapabilities);
937 remoting.DefaultClientPluginFactory.prototype.preloadPlugin = function() {
938 if (remoting.settings.CLIENT_PLUGIN_TYPE != 'pnacl') {
942 var plugin = remoting.ClientPluginImpl.createPluginElement_();
943 plugin.addEventListener(
944 'loadend', function() { document.body.removeChild(plugin); }, false);
945 document.body.appendChild(plugin);