Update V8 to version 3.30.4 (based on bleeding_edge revision r24443)
[chromium-blink-merge.git] / remoting / webapp / client_plugin_impl.js
blob28a973d1edae21ec69f6d864e0e4497172742a36
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * @fileoverview
7 * Class that wraps low-level details of interacting with the client plugin.
9 * This abstracts a <embed> element and controls the plugin which does
10 * the actual remoting work. It also handles differences between
11 * client plugins versions when it is necessary.
14 'use strict';
16 /** @suppress {duplicate} */
17 var remoting = remoting || {};
19 /**
20 * @param {Element} container The container for the embed element.
21 * @param {function(string, string):boolean} onExtensionMessage The handler for
22 * protocol extension messages. Returns true if a message is recognized;
23 * false otherwise.
24 * @constructor
25 * @implements {remoting.ClientPlugin}
27 remoting.ClientPluginImpl = function(container, onExtensionMessage) {
28 this.plugin_ = remoting.ClientPluginImpl.createPluginElement_();
29 this.plugin_.id = 'session-client-plugin';
30 container.appendChild(this.plugin_);
32 this.onExtensionMessage_ = onExtensionMessage;
34 /** @private */
35 this.desktopWidth_ = 0;
36 /** @private */
37 this.desktopHeight_ = 0;
38 /** @private */
39 this.desktopXDpi_ = 96;
40 /** @private */
41 this.desktopYDpi_ = 96;
43 /**
44 * @param {string} iq The Iq stanza received from the host.
45 * @private
47 this.onOutgoingIqHandler_ = function (iq) {};
48 /**
49 * @param {string} message Log message.
50 * @private
52 this.onDebugMessageHandler_ = function (message) {};
53 /**
54 * @param {number} state The connection state.
55 * @param {number} error The error code, if any.
56 * @private
58 this.onConnectionStatusUpdateHandler_ = function(state, error) {};
59 /**
60 * @param {boolean} ready Connection ready state.
61 * @private
63 this.onConnectionReadyHandler_ = function(ready) {};
65 /**
66 * @param {string} tokenUrl Token-request URL, received from the host.
67 * @param {string} hostPublicKey Public key for the host.
68 * @param {string} scope OAuth scope to request the token for.
69 * @private
71 this.fetchThirdPartyTokenHandler_ = function(
72 tokenUrl, hostPublicKey, scope) {};
73 /** @private */
74 this.onDesktopSizeUpdateHandler_ = function () {};
75 /**
76 * @param {!Array.<string>} capabilities The negotiated capabilities.
77 * @private
79 this.onSetCapabilitiesHandler_ = function (capabilities) {};
80 /** @private */
81 this.fetchPinHandler_ = function (supportsPairing) {};
82 /**
83 * @param {string} data Remote gnubbyd data.
84 * @private
86 this.onGnubbyAuthHandler_ = function(data) {};
87 /**
88 * @param {string} url
89 * @param {number} hotspotX
90 * @param {number} hotspotY
91 * @private
93 this.updateMouseCursorImage_ = function(url, hotspotX, hotspotY) {};
95 /**
96 * @param {string} data Remote cast extension message.
97 * @private
99 this.onCastExtensionHandler_ = function(data) {};
102 * @type {remoting.MediaSourceRenderer}
103 * @private
105 this.mediaSourceRenderer_ = null;
108 * @type {number}
109 * @private
111 this.pluginApiVersion_ = -1;
113 * @type {Array.<string>}
114 * @private
116 this.pluginApiFeatures_ = [];
118 * @type {number}
119 * @private
121 this.pluginApiMinVersion_ = -1;
123 * @type {!Array.<string>}
124 * @private
126 this.capabilities_ = [];
128 * @type {boolean}
129 * @private
131 this.helloReceived_ = false;
133 * @type {function(boolean)|null}
134 * @private
136 this.onInitializedCallback_ = null;
138 * @type {function(string, string):void}
139 * @private
141 this.onPairingComplete_ = function(clientId, sharedSecret) {};
143 * @type {remoting.ClientSession.PerfStats}
144 * @private
146 this.perfStats_ = new remoting.ClientSession.PerfStats();
148 /** @type {remoting.ClientPluginImpl} */
149 var that = this;
150 /** @param {Event} event Message event from the plugin. */
151 this.plugin_.addEventListener('message', function(event) {
152 that.handleMessage_(event.data);
153 }, false);
155 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'native') {
156 window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500);
161 * Creates plugin element without adding it to a container.
163 * @return {remoting.ViewerPlugin} Plugin element
165 remoting.ClientPluginImpl.createPluginElement_ = function() {
166 var plugin = /** @type {remoting.ViewerPlugin} */
167 document.createElement('embed');
168 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'pnacl') {
169 plugin.src = 'remoting_client_pnacl.nmf';
170 plugin.type = 'application/x-pnacl';
171 } else if (remoting.settings.CLIENT_PLUGIN_TYPE == 'nacl') {
172 plugin.src = 'remoting_client_nacl.nmf';
173 plugin.type = 'application/x-nacl';
174 } else {
175 plugin.src = 'about://none';
176 plugin.type = 'application/vnd.chromium.remoting-viewer';
178 plugin.width = 0;
179 plugin.height = 0;
180 plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
181 return plugin;
185 * Chromoting session API version (for this javascript).
186 * This is compared with the plugin API version to verify that they are
187 * compatible.
189 * @const
190 * @private
192 remoting.ClientPluginImpl.prototype.API_VERSION_ = 6;
195 * The oldest API version that we support.
196 * This will differ from the |API_VERSION_| if we maintain backward
197 * compatibility with older API versions.
199 * @const
200 * @private
202 remoting.ClientPluginImpl.prototype.API_MIN_VERSION_ = 5;
205 * @param {function(string):void} handler
207 remoting.ClientPluginImpl.prototype.setOnOutgoingIqHandler = function(handler) {
208 this.onOutgoingIqHandler_ = handler;
212 * @param {function(string):void} handler
214 remoting.ClientPluginImpl.prototype.setOnDebugMessageHandler =
215 function(handler) {
216 this.onDebugMessageHandler_ = handler;
220 * @param {function(number, number):void} handler
222 remoting.ClientPluginImpl.prototype.setConnectionStatusUpdateHandler =
223 function(handler) {
224 this.onConnectionStatusUpdateHandler_ = handler;
228 * @param {function(boolean):void} handler
230 remoting.ClientPluginImpl.prototype.setConnectionReadyHandler =
231 function(handler) {
232 this.onConnectionReadyHandler_ = handler;
236 * @param {function():void} handler
238 remoting.ClientPluginImpl.prototype.setDesktopSizeUpdateHandler =
239 function(handler) {
240 this.onDesktopSizeUpdateHandler_ = handler;
244 * @param {function(!Array.<string>):void} handler
246 remoting.ClientPluginImpl.prototype.setCapabilitiesHandler = function(handler) {
247 this.onSetCapabilitiesHandler_ = handler;
251 * @param {function(string):void} handler
253 remoting.ClientPluginImpl.prototype.setGnubbyAuthHandler = function(handler) {
254 this.onGnubbyAuthHandler_ = handler;
258 * @param {function(string):void} handler
260 remoting.ClientPluginImpl.prototype.setCastExtensionHandler =
261 function(handler) {
262 this.onCastExtensionHandler_ = handler;
266 * @param {function(string, number, number):void} handler
268 remoting.ClientPluginImpl.prototype.setMouseCursorHandler = function(handler) {
269 this.updateMouseCursorImage_ = handler;
273 * @param {function(string, string, string):void} handler
275 remoting.ClientPluginImpl.prototype.setFetchThirdPartyTokenHandler =
276 function(handler) {
277 this.fetchThirdPartyTokenHandler_ = handler;
281 * @param {function(boolean):void} handler
283 remoting.ClientPluginImpl.prototype.setFetchPinHandler = function(handler) {
284 this.fetchPinHandler_ = handler;
288 * @param {string|{method:string, data:Object.<string,*>}}
289 * rawMessage Message from the plugin.
290 * @private
292 remoting.ClientPluginImpl.prototype.handleMessage_ = function(rawMessage) {
293 var message =
294 /** @type {{method:string, data:Object.<string,*>}} */
295 ((typeof(rawMessage) == 'string') ? jsonParseSafe(rawMessage)
296 : rawMessage);
297 if (!message || !('method' in message) || !('data' in message)) {
298 console.error('Received invalid message from the plugin:', rawMessage);
299 return;
302 try {
303 this.handleMessageMethod_(message);
304 } catch(e) {
305 console.error(/** @type {*} */ (e));
310 * @param {{method:string, data:Object.<string,*>}}
311 * message Parsed message from the plugin.
312 * @private
314 remoting.ClientPluginImpl.prototype.handleMessageMethod_ = function(message) {
316 * Splits a string into a list of words delimited by spaces.
317 * @param {string} str String that should be split.
318 * @return {!Array.<string>} List of words.
320 var tokenize = function(str) {
321 /** @type {Array.<string>} */
322 var tokens = str.match(/\S+/g);
323 return tokens ? tokens : [];
326 if (message.method == 'hello') {
327 // Resize in case we had to enlarge it to support click-to-play.
328 this.hidePluginForClickToPlay_();
329 this.pluginApiVersion_ = getNumberAttr(message.data, 'apiVersion');
330 this.pluginApiMinVersion_ = getNumberAttr(message.data, 'apiMinVersion');
332 if (this.pluginApiVersion_ >= 7) {
333 this.pluginApiFeatures_ =
334 tokenize(getStringAttr(message.data, 'apiFeatures'));
336 // Negotiate capabilities.
338 /** @type {!Array.<string>} */
339 var requestedCapabilities = [];
340 if ('requestedCapabilities' in message.data) {
341 requestedCapabilities =
342 tokenize(getStringAttr(message.data, 'requestedCapabilities'));
345 /** @type {!Array.<string>} */
346 var supportedCapabilities = [];
347 if ('supportedCapabilities' in message.data) {
348 supportedCapabilities =
349 tokenize(getStringAttr(message.data, 'supportedCapabilities'));
352 // At the moment the webapp does not recognize any of
353 // 'requestedCapabilities' capabilities (so they all should be disabled)
354 // and do not care about any of 'supportedCapabilities' capabilities (so
355 // they all can be enabled).
356 this.capabilities_ = supportedCapabilities;
358 // Let the host know that the webapp can be requested to always send
359 // the client's dimensions.
360 this.capabilities_.push(
361 remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION);
363 // Let the host know that we're interested in knowing whether or not
364 // it rate-limits desktop-resize requests.
365 this.capabilities_.push(
366 remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS);
368 // Let the host know that we can use the video framerecording extension.
369 this.capabilities_.push(
370 remoting.ClientSession.Capability.VIDEO_RECORDER);
372 // Let the host know that we can support casting of the screen.
373 // TODO(aiguha): Add this capability based on a gyp/command-line flag,
374 // rather than by default.
375 this.capabilities_.push(
376 remoting.ClientSession.Capability.CAST);
378 } else if (this.pluginApiVersion_ >= 6) {
379 this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
380 } else {
381 this.pluginApiFeatures_ = ['highQualityScaling'];
383 this.helloReceived_ = true;
384 if (this.onInitializedCallback_ != null) {
385 this.onInitializedCallback_(true);
386 this.onInitializedCallback_ = null;
389 } else if (message.method == 'sendOutgoingIq') {
390 this.onOutgoingIqHandler_(getStringAttr(message.data, 'iq'));
392 } else if (message.method == 'logDebugMessage') {
393 this.onDebugMessageHandler_(getStringAttr(message.data, 'message'));
395 } else if (message.method == 'onConnectionStatus') {
396 var state = remoting.ClientSession.State.fromString(
397 getStringAttr(message.data, 'state'))
398 var error = remoting.ClientSession.ConnectionError.fromString(
399 getStringAttr(message.data, 'error'));
400 this.onConnectionStatusUpdateHandler_(state, error);
402 } else if (message.method == 'onDesktopSize') {
403 this.desktopWidth_ = getNumberAttr(message.data, 'width');
404 this.desktopHeight_ = getNumberAttr(message.data, 'height');
405 this.desktopXDpi_ = getNumberAttr(message.data, 'x_dpi', 96);
406 this.desktopYDpi_ = getNumberAttr(message.data, 'y_dpi', 96);
407 this.onDesktopSizeUpdateHandler_();
409 } else if (message.method == 'onPerfStats') {
410 // Return value is ignored. These calls will throw an error if the value
411 // is not a number.
412 getNumberAttr(message.data, 'videoBandwidth');
413 getNumberAttr(message.data, 'videoFrameRate');
414 getNumberAttr(message.data, 'captureLatency');
415 getNumberAttr(message.data, 'encodeLatency');
416 getNumberAttr(message.data, 'decodeLatency');
417 getNumberAttr(message.data, 'renderLatency');
418 getNumberAttr(message.data, 'roundtripLatency');
419 this.perfStats_ =
420 /** @type {remoting.ClientSession.PerfStats} */ message.data;
422 } else if (message.method == 'injectClipboardItem') {
423 var mimetype = getStringAttr(message.data, 'mimeType');
424 var item = getStringAttr(message.data, 'item');
425 if (remoting.clipboard) {
426 remoting.clipboard.fromHost(mimetype, item);
429 } else if (message.method == 'onFirstFrameReceived') {
430 if (remoting.clientSession) {
431 remoting.clientSession.onFirstFrameReceived();
434 } else if (message.method == 'onConnectionReady') {
435 var ready = getBooleanAttr(message.data, 'ready');
436 this.onConnectionReadyHandler_(ready);
438 } else if (message.method == 'fetchPin') {
439 // The pairingSupported value in the dictionary indicates whether both
440 // client and host support pairing. If the client doesn't support pairing,
441 // then the value won't be there at all, so give it a default of false.
442 var pairingSupported = getBooleanAttr(message.data, 'pairingSupported',
443 false)
444 this.fetchPinHandler_(pairingSupported);
446 } else if (message.method == 'setCapabilities') {
447 /** @type {!Array.<string>} */
448 var capabilities = tokenize(getStringAttr(message.data, 'capabilities'));
449 this.onSetCapabilitiesHandler_(capabilities);
451 } else if (message.method == 'fetchThirdPartyToken') {
452 var tokenUrl = getStringAttr(message.data, 'tokenUrl');
453 var hostPublicKey = getStringAttr(message.data, 'hostPublicKey');
454 var scope = getStringAttr(message.data, 'scope');
455 this.fetchThirdPartyTokenHandler_(tokenUrl, hostPublicKey, scope);
457 } else if (message.method == 'pairingResponse') {
458 var clientId = getStringAttr(message.data, 'clientId');
459 var sharedSecret = getStringAttr(message.data, 'sharedSecret');
460 this.onPairingComplete_(clientId, sharedSecret);
462 } else if (message.method == 'extensionMessage') {
463 var extMsgType = getStringAttr(message.data, 'type');
464 var extMsgData = getStringAttr(message.data, 'data');
465 switch (extMsgType) {
466 case 'gnubby-auth':
467 this.onGnubbyAuthHandler_(extMsgData);
468 break;
469 case 'test-echo-reply':
470 console.log('Got echo reply: ' + extMsgData);
471 break;
472 case 'cast_message':
473 this.onCastExtensionHandler_(extMsgData);
474 break;
475 default:
476 this.onExtensionMessage_(extMsgType, extMsgData);
477 break;
480 } else if (message.method == 'mediaSourceReset') {
481 if (!this.mediaSourceRenderer_) {
482 console.error('Unexpected mediaSourceReset.');
483 return;
485 this.mediaSourceRenderer_.reset(getStringAttr(message.data, 'format'))
487 } else if (message.method == 'mediaSourceData') {
488 if (!(message.data['buffer'] instanceof ArrayBuffer)) {
489 console.error('Invalid mediaSourceData message:', message.data);
490 return;
492 if (!this.mediaSourceRenderer_) {
493 console.error('Unexpected mediaSourceData.');
494 return;
496 // keyframe flag may be absent from the message.
497 var keyframe = !!message.data['keyframe'];
498 this.mediaSourceRenderer_.onIncomingData(
499 (/** @type {ArrayBuffer} */ message.data['buffer']), keyframe);
501 } else if (message.method == 'unsetCursorShape') {
502 this.updateMouseCursorImage_('', 0, 0);
504 } else if (message.method == 'setCursorShape') {
505 var width = getNumberAttr(message.data, 'width');
506 var height = getNumberAttr(message.data, 'height');
507 var hotspotX = getNumberAttr(message.data, 'hotspotX');
508 var hotspotY = getNumberAttr(message.data, 'hotspotY');
509 var srcArrayBuffer = getObjectAttr(message.data, 'data');
511 var canvas =
512 /** @type {HTMLCanvasElement} */ (document.createElement('canvas'));
513 canvas.width = width;
514 canvas.height = height;
516 var context =
517 /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
518 var imageData = context.getImageData(0, 0, width, height);
519 base.debug.assert(srcArrayBuffer instanceof ArrayBuffer);
520 var src = new Uint8Array(/** @type {ArrayBuffer} */(srcArrayBuffer));
521 var dest = imageData.data;
522 for (var i = 0; i < /** @type {number} */(dest.length); i += 4) {
523 dest[i] = src[i + 2];
524 dest[i + 1] = src[i + 1];
525 dest[i + 2] = src[i];
526 dest[i + 3] = src[i + 3];
529 context.putImageData(imageData, 0, 0);
530 this.updateMouseCursorImage_(canvas.toDataURL(), hotspotX, hotspotY);
535 * Deletes the plugin.
537 remoting.ClientPluginImpl.prototype.dispose = function() {
538 if (this.plugin_) {
539 this.plugin_.parentNode.removeChild(this.plugin_);
540 this.plugin_ = null;
545 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin.
547 remoting.ClientPluginImpl.prototype.element = function() {
548 return this.plugin_;
552 * @param {function(boolean): void} onDone
554 remoting.ClientPluginImpl.prototype.initialize = function(onDone) {
555 if (this.helloReceived_) {
556 onDone(true);
557 } else {
558 this.onInitializedCallback_ = onDone;
563 * @return {boolean} True if the plugin and web-app versions are compatible.
565 remoting.ClientPluginImpl.prototype.isSupportedVersion = function() {
566 if (!this.helloReceived_) {
567 console.error(
568 "isSupportedVersion() is called before the plugin is initialized.");
569 return false;
571 return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
572 this.pluginApiVersion_ >= this.API_MIN_VERSION_;
576 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
577 * @return {boolean} True if the plugin supports the named feature.
579 remoting.ClientPluginImpl.prototype.hasFeature = function(feature) {
580 if (!this.helloReceived_) {
581 console.error(
582 "hasFeature() is called before the plugin is initialized.");
583 return false;
585 return this.pluginApiFeatures_.indexOf(feature) > -1;
589 * @return {boolean} True if the plugin supports the injectKeyEvent API.
591 remoting.ClientPluginImpl.prototype.isInjectKeyEventSupported = function() {
592 return this.pluginApiVersion_ >= 6;
596 * @param {string} iq Incoming IQ stanza.
598 remoting.ClientPluginImpl.prototype.onIncomingIq = function(iq) {
599 if (this.plugin_ && this.plugin_.postMessage) {
600 this.plugin_.postMessage(JSON.stringify(
601 { method: 'incomingIq', data: { iq: iq } }));
602 } else {
603 // plugin.onIq may not be set after the plugin has been shut
604 // down. Particularly this happens when we receive response to
605 // session-terminate stanza.
606 console.warn('plugin.onIq is not set so dropping incoming message.');
611 * @param {string} hostJid The jid of the host to connect to.
612 * @param {string} hostPublicKey The base64 encoded version of the host's
613 * public key.
614 * @param {string} localJid Local jid.
615 * @param {string} sharedSecret The access code for IT2Me or the PIN
616 * for Me2Me.
617 * @param {string} authenticationMethods Comma-separated list of
618 * authentication methods the client should attempt to use.
619 * @param {string} authenticationTag A host-specific tag to mix into
620 * authentication hashes.
621 * @param {string} clientPairingId For paired Me2Me connections, the
622 * pairing id for this client, as issued by the host.
623 * @param {string} clientPairedSecret For paired Me2Me connections, the
624 * paired secret for this client, as issued by the host.
626 remoting.ClientPluginImpl.prototype.connect = function(
627 hostJid, hostPublicKey, localJid, sharedSecret,
628 authenticationMethods, authenticationTag,
629 clientPairingId, clientPairedSecret) {
630 var keyFilter = '';
631 if (remoting.platformIsMac()) {
632 keyFilter = 'mac';
633 } else if (remoting.platformIsChromeOS()) {
634 keyFilter = 'cros';
636 this.plugin_.postMessage(JSON.stringify(
637 { method: 'delegateLargeCursors', data: {} }));
638 this.plugin_.postMessage(JSON.stringify(
639 { method: 'connect', data: {
640 hostJid: hostJid,
641 hostPublicKey: hostPublicKey,
642 localJid: localJid,
643 sharedSecret: sharedSecret,
644 authenticationMethods: authenticationMethods,
645 authenticationTag: authenticationTag,
646 capabilities: this.capabilities_.join(" "),
647 clientPairingId: clientPairingId,
648 clientPairedSecret: clientPairedSecret,
649 keyFilter: keyFilter
651 }));
655 * Release all currently pressed keys.
657 remoting.ClientPluginImpl.prototype.releaseAllKeys = function() {
658 this.plugin_.postMessage(JSON.stringify(
659 { method: 'releaseAllKeys', data: {} }));
663 * Send a key event to the host.
665 * @param {number} usbKeycode The USB-style code of the key to inject.
666 * @param {boolean} pressed True to inject a key press, False for a release.
668 remoting.ClientPluginImpl.prototype.injectKeyEvent =
669 function(usbKeycode, pressed) {
670 this.plugin_.postMessage(JSON.stringify(
671 { method: 'injectKeyEvent', data: {
672 'usbKeycode': usbKeycode,
673 'pressed': pressed}
674 }));
678 * Remap one USB keycode to another in all subsequent key events.
680 * @param {number} fromKeycode The USB-style code of the key to remap.
681 * @param {number} toKeycode The USB-style code to remap the key to.
683 remoting.ClientPluginImpl.prototype.remapKey =
684 function(fromKeycode, toKeycode) {
685 this.plugin_.postMessage(JSON.stringify(
686 { method: 'remapKey', data: {
687 'fromKeycode': fromKeycode,
688 'toKeycode': toKeycode}
689 }));
693 * Enable/disable redirection of the specified key to the web-app.
695 * @param {number} keycode The USB-style code of the key.
696 * @param {Boolean} trap True to enable trapping, False to disable.
698 remoting.ClientPluginImpl.prototype.trapKey = function(keycode, trap) {
699 this.plugin_.postMessage(JSON.stringify(
700 { method: 'trapKey', data: {
701 'keycode': keycode,
702 'trap': trap}
703 }));
707 * Returns an associative array with a set of stats for this connecton.
709 * @return {remoting.ClientSession.PerfStats} The connection statistics.
711 remoting.ClientPluginImpl.prototype.getPerfStats = function() {
712 return this.perfStats_;
716 * Sends a clipboard item to the host.
718 * @param {string} mimeType The MIME type of the clipboard item.
719 * @param {string} item The clipboard item.
721 remoting.ClientPluginImpl.prototype.sendClipboardItem =
722 function(mimeType, item) {
723 if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
724 return;
725 this.plugin_.postMessage(JSON.stringify(
726 { method: 'sendClipboardItem',
727 data: { mimeType: mimeType, item: item }}));
731 * Notifies the host that the client has the specified size and pixel density.
733 * @param {number} width The available client width in DIPs.
734 * @param {number} height The available client height in DIPs.
735 * @param {number} device_scale The number of device pixels per DIP.
737 remoting.ClientPluginImpl.prototype.notifyClientResolution =
738 function(width, height, device_scale) {
739 if (this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_RESOLUTION)) {
740 var dpi = Math.floor(device_scale * 96);
741 this.plugin_.postMessage(JSON.stringify(
742 { method: 'notifyClientResolution',
743 data: { width: Math.floor(width * device_scale),
744 height: Math.floor(height * device_scale),
745 x_dpi: dpi, y_dpi: dpi }}));
750 * Requests that the host pause or resume sending video updates.
752 * @param {boolean} pause True to suspend video updates, false otherwise.
754 remoting.ClientPluginImpl.prototype.pauseVideo =
755 function(pause) {
756 if (this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
757 this.plugin_.postMessage(JSON.stringify(
758 { method: 'videoControl', data: { pause: pause }}));
759 } else if (this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) {
760 this.plugin_.postMessage(JSON.stringify(
761 { method: 'pauseVideo', data: { pause: pause }}));
766 * Requests that the host pause or resume sending audio updates.
768 * @param {boolean} pause True to suspend audio updates, false otherwise.
770 remoting.ClientPluginImpl.prototype.pauseAudio =
771 function(pause) {
772 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) {
773 return;
775 this.plugin_.postMessage(JSON.stringify(
776 { method: 'pauseAudio', data: { pause: pause }}));
780 * Requests that the host configure the video codec for lossless encode.
782 * @param {boolean} wantLossless True to request lossless encoding.
784 remoting.ClientPluginImpl.prototype.setLosslessEncode =
785 function(wantLossless) {
786 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
787 return;
789 this.plugin_.postMessage(JSON.stringify(
790 { method: 'videoControl', data: { losslessEncode: wantLossless }}));
794 * Requests that the host configure the video codec for lossless color.
796 * @param {boolean} wantLossless True to request lossless color.
798 remoting.ClientPluginImpl.prototype.setLosslessColor =
799 function(wantLossless) {
800 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
801 return;
803 this.plugin_.postMessage(JSON.stringify(
804 { method: 'videoControl', data: { losslessColor: wantLossless }}));
808 * Called when a PIN is obtained from the user.
810 * @param {string} pin The PIN.
812 remoting.ClientPluginImpl.prototype.onPinFetched =
813 function(pin) {
814 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
815 return;
817 this.plugin_.postMessage(JSON.stringify(
818 { method: 'onPinFetched', data: { pin: pin }}));
822 * Tells the plugin to ask for the PIN asynchronously.
824 remoting.ClientPluginImpl.prototype.useAsyncPinDialog =
825 function() {
826 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
827 return;
829 this.plugin_.postMessage(JSON.stringify(
830 { method: 'useAsyncPinDialog', data: {} }));
834 * Sets the third party authentication token and shared secret.
836 * @param {string} token The token received from the token URL.
837 * @param {string} sharedSecret Shared secret received from the token URL.
839 remoting.ClientPluginImpl.prototype.onThirdPartyTokenFetched = function(
840 token, sharedSecret) {
841 this.plugin_.postMessage(JSON.stringify(
842 { method: 'onThirdPartyTokenFetched',
843 data: { token: token, sharedSecret: sharedSecret}}));
847 * Request pairing with the host for PIN-less authentication.
849 * @param {string} clientName The human-readable name of the client.
850 * @param {function(string, string):void} onDone, Callback to receive the
851 * client id and shared secret when they are available.
853 remoting.ClientPluginImpl.prototype.requestPairing =
854 function(clientName, onDone) {
855 if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) {
856 return;
858 this.onPairingComplete_ = onDone;
859 this.plugin_.postMessage(JSON.stringify(
860 { method: 'requestPairing', data: { clientName: clientName } }));
864 * Send an extension message to the host.
866 * @param {string} type The message type.
867 * @param {string} message The message payload.
869 remoting.ClientPluginImpl.prototype.sendClientMessage =
870 function(type, message) {
871 if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) {
872 return;
874 this.plugin_.postMessage(JSON.stringify(
875 { method: 'extensionMessage',
876 data: { type: type, data: message } }));
881 * Request MediaStream-based rendering.
883 * @param {remoting.MediaSourceRenderer} mediaSourceRenderer
885 remoting.ClientPluginImpl.prototype.enableMediaSourceRendering =
886 function(mediaSourceRenderer) {
887 if (!this.hasFeature(remoting.ClientPlugin.Feature.MEDIA_SOURCE_RENDERING)) {
888 return;
890 this.mediaSourceRenderer_ = mediaSourceRenderer;
891 this.plugin_.postMessage(JSON.stringify(
892 { method: 'enableMediaSourceRendering', data: {} }));
895 remoting.ClientPluginImpl.prototype.getDesktopWidth = function() {
896 return this.desktopWidth_;
899 remoting.ClientPluginImpl.prototype.getDesktopHeight = function() {
900 return this.desktopHeight_;
903 remoting.ClientPluginImpl.prototype.getDesktopXDpi = function() {
904 return this.desktopXDpi_;
907 remoting.ClientPluginImpl.prototype.getDesktopYDpi = function() {
908 return this.desktopYDpi_;
912 * If we haven't yet received a "hello" message from the plugin, change its
913 * size so that the user can confirm it if click-to-play is enabled, or can
914 * see the "this plugin is disabled" message if it is actually disabled.
915 * @private
917 remoting.ClientPluginImpl.prototype.showPluginForClickToPlay_ = function() {
918 if (!this.helloReceived_) {
919 var width = 200;
920 var height = 200;
921 this.plugin_.style.width = width + 'px';
922 this.plugin_.style.height = height + 'px';
923 // Center the plugin just underneath the "Connnecting..." dialog.
924 var dialog = document.getElementById('client-dialog');
925 var dialogRect = dialog.getBoundingClientRect();
926 this.plugin_.style.top = (dialogRect.bottom + 16) + 'px';
927 this.plugin_.style.left = (window.innerWidth - width) / 2 + 'px';
928 this.plugin_.style.position = 'fixed';
933 * Undo the CSS rules needed to make the plugin clickable for click-to-play.
934 * @private
936 remoting.ClientPluginImpl.prototype.hidePluginForClickToPlay_ = function() {
937 this.plugin_.style.width = '';
938 this.plugin_.style.height = '';
939 this.plugin_.style.top = '';
940 this.plugin_.style.left = '';
941 this.plugin_.style.position = '';
946 * @constructor
947 * @implements {remoting.ClientPluginFactory}
949 remoting.DefaultClientPluginFactory = function() {};
952 * @param {Element} container
953 * @param {function(string, string):boolean} onExtensionMessage
954 * @return {remoting.ClientPlugin}
956 remoting.DefaultClientPluginFactory.prototype.createPlugin =
957 function(container, onExtensionMessage) {
958 return new remoting.ClientPluginImpl(container, onExtensionMessage);
961 remoting.DefaultClientPluginFactory.prototype.preloadPlugin = function() {
962 if (remoting.settings.CLIENT_PLUGIN_TYPE != 'pnacl') {
963 return;
966 var plugin = remoting.ClientPluginImpl.createPluginElement_();
967 plugin.addEventListener(
968 'loadend', function() { document.body.removeChild(plugin); }, false);
969 document.body.appendChild(plugin);