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

Side by Side Diff: remoting/webapp/crd/js/client_plugin_impl.js

Issue 1133913002: [Chromoting] Move shared webapp JS files from crd/js -> base/js (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 /**
6 * @fileoverview
7 * Class that wraps low-level details of interacting with the client plugin.
8 *
9 * This abstracts a <embed> element and controls the plugin which does
10 * the actual remoting work. It also handles differences between
11 * client plugins versions when it is necessary.
12 */
13
14 'use strict';
15
16 /** @suppress {duplicate} */
17 var remoting = remoting || {};
18
19 /** @constructor */
20 remoting.ClientPluginMessage = function() {
21 /** @type {string} */
22 this.method = '';
23
24 /** @type {Object<string,*>} */
25 this.data = {};
26 };
27
28 /**
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.
32 * @constructor
33 * @implements {remoting.ClientPlugin}
34 */
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_);
43
44 /** @private {Array<string>} */
45 this.requiredCapabilities_ = requiredCapabilities;
46
47 /** @private {remoting.ClientPlugin.ConnectionEventHandler} */
48 this.connectionEventHandler_ = null;
49
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;
58
59 /** @private {number} */
60 this.pluginApiVersion_ = -1;
61 /** @private {Array<string>} */
62 this.pluginApiFeatures_ = [];
63 /** @private {number} */
64 this.pluginApiMinVersion_ = -1;
65 /**
66 * Capabilities to be used for the next connect request.
67 * @private {!Array<string>}
68 */
69 this.capabilities_ = [];
70 /**
71 * Capabilities that are negotiated between the client and the host.
72 * @private {Array<remoting.ClientSession.Capability>}
73 */
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();
83
84 /** @type {remoting.ClientPluginImpl} */
85 var that = this;
86 this.plugin_.addEventListener('message',
87 /** @param {Event} event Message event from the plugin. */
88 function(event) {
89 that.handleMessage_(
90 /** @type {remoting.ClientPluginMessage} */ (event.data));
91 }, false);
92
93 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'native') {
94 window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500);
95 }
96
97 /** @private */
98 this.hostDesktop_ = new remoting.ClientPlugin.HostDesktopImpl(
99 this, this.postMessage_.bind(this));
100
101 /** @private */
102 this.extensions_ = new remoting.ProtocolExtensionManager(
103 this.sendClientMessage_.bind(this));
104
105 /** @private {remoting.CredentialsProvider} */
106 this.credentials_ = null;
107
108 /** @private {string} */
109 this.keyRemappings_ = '';
110 };
111
112 /**
113 * Creates plugin element without adding it to a container.
114 *
115 * @return {HTMLEmbedElement} Plugin element
116 */
117 remoting.ClientPluginImpl.createPluginElement_ = function() {
118 var plugin =
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';
126 } else {
127 plugin.src = 'about://none';
128 plugin.type = 'application/vnd.chromium.remoting-viewer';
129 }
130 plugin.width = '0';
131 plugin.height = '0';
132 plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
133 return plugin;
134 }
135
136 /**
137 * Chromoting session API version (for this javascript).
138 * This is compared with the plugin API version to verify that they are
139 * compatible.
140 *
141 * @const
142 * @private
143 */
144 remoting.ClientPluginImpl.prototype.API_VERSION_ = 6;
145
146 /**
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.
150 *
151 * @const
152 * @private
153 */
154 remoting.ClientPluginImpl.prototype.API_MIN_VERSION_ = 5;
155
156 /**
157 * @param {remoting.ClientPlugin.ConnectionEventHandler} handler
158 */
159 remoting.ClientPluginImpl.prototype.setConnectionEventHandler =
160 function(handler) {
161 this.connectionEventHandler_ = handler;
162 };
163
164 /**
165 * @param {function(string, number, number):void} handler
166 */
167 remoting.ClientPluginImpl.prototype.setMouseCursorHandler = function(handler) {
168 this.updateMouseCursorImage_ = handler;
169 };
170
171 /**
172 * @param {function(string, string):void} handler
173 */
174 remoting.ClientPluginImpl.prototype.setClipboardHandler = function(handler) {
175 this.updateClipboardData_ = handler;
176 };
177
178 /**
179 * @param {?function({rects:Array<Array<number>>}):void} handler
180 */
181 remoting.ClientPluginImpl.prototype.setDebugDirtyRegionHandler =
182 function(handler) {
183 this.debugRegionHandler_ = handler;
184 this.plugin_.postMessage(JSON.stringify(
185 { method: 'enableDebugRegion', data: { enable: handler != null } }));
186 };
187
188 /**
189 * @param {string|remoting.ClientPluginMessage}
190 * rawMessage Message from the plugin.
191 * @private
192 */
193 remoting.ClientPluginImpl.prototype.handleMessage_ = function(rawMessage) {
194 var message =
195 /** @type {remoting.ClientPluginMessage} */
196 ((typeof(rawMessage) == 'string') ? base.jsonParseSafe(rawMessage)
197 : rawMessage);
198 if (!message || !('method' in message) || !('data' in message)) {
199 console.error('Received invalid message from the plugin:', rawMessage);
200 return;
201 }
202
203 try {
204 this.handleMessageMethod_(message);
205 } catch(/** @type {*} */ e) {
206 console.error(e);
207 }
208 }
209
210 /**
211 * @param {remoting.ClientPluginMessage}
212 * message Parsed message from the plugin.
213 * @private
214 */
215 remoting.ClientPluginImpl.prototype.handleMessageMethod_ = function(message) {
216 /**
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.
220 */
221 var tokenize = function(str) {
222 /** @type {Array<string>} */
223 var tokens = str.match(/\S+/g);
224 return tokens ? tokens : [];
225 };
226
227 if (this.connectionEventHandler_) {
228 var handler = this.connectionEventHandler_;
229
230 if (message.method == 'sendOutgoingIq') {
231 handler.onOutgoingIq(base.getStringAttr(message.data, 'iq'));
232
233 } else if (message.method == 'logDebugMessage') {
234 handler.onDebugMessage(base.getStringAttr(message.data, 'message'));
235
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'));
241
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 base.debug.assert(this.hostCapabilities_ === null,
247 'Capabilities should only be set after the session is connected');
248 return;
249 }
250 handler.onConnectionStatusUpdate(state, error);
251
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);
256
257 } else if (message.method == 'onConnectionReady') {
258 var ready = base.getBooleanAttr(message.data, 'ready');
259 handler.onConnectionReady(ready);
260
261 } else if (message.method == 'setCapabilities') {
262 var capabilityString = base.getStringAttr(message.data, 'capabilities');
263 console.log('plugin: setCapabilities: [' + capabilityString + ']');
264
265 base.debug.assert(this.hostCapabilities_ === null,
266 'setCapabilities() should only be called once');
267 this.hostCapabilities_ = tokenize(capabilityString);
268
269 handler.onConnectionStatusUpdate(
270 remoting.ClientSession.State.CONNECTED,
271 remoting.ClientSession.ConnectionError.NONE);
272 this.extensions_.start();
273
274 } else if (message.method == 'onFirstFrameReceived') {
275 handler.onFirstFrameReceived();
276
277 }
278 }
279
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');
286
287 if (this.pluginApiVersion_ >= 7) {
288 this.pluginApiFeatures_ =
289 tokenize(base.getStringAttr(message.data, 'apiFeatures'));
290
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'));
297 }
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'];
307 } else {
308 this.pluginApiFeatures_ = ['highQualityScaling'];
309 }
310 this.helloReceived_ = true;
311 if (this.onInitializedCallback_ != null) {
312 this.onInitializedCallback_(true);
313 this.onInitializedCallback_ = null;
314 }
315
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
322 // is not a number.
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');
330 this.perfStats_ =
331 /** @type {remoting.ClientSession.PerfStats} */ (message.data);
332
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);
337
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',
343 false);
344 this.credentials_.getPIN(pairingSupported).then(
345 this.onPinFetched_.bind(this)
346 );
347
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)
354 );
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);
359
360 } else if (message.method == 'unsetCursorShape') {
361 this.updateMouseCursorImage_('', 0, 0);
362
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');
369
370 var canvas =
371 /** @type {HTMLCanvasElement} */ (document.createElement('canvas'));
372 canvas.width = width;
373 canvas.height = height;
374
375 var context =
376 /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
377 var imageData = context.getImageData(0, 0, width, height);
378 base.debug.assert(srcArrayBuffer instanceof ArrayBuffer);
379 var src = new Uint8Array(/** @type {ArrayBuffer} */(srcArrayBuffer));
380 var dest = imageData.data;
381 for (var i = 0; i < /** @type {number} */(dest.length); i += 4) {
382 dest[i] = src[i + 2];
383 dest[i + 1] = src[i + 1];
384 dest[i + 2] = src[i];
385 dest[i + 3] = src[i + 3];
386 }
387
388 context.putImageData(imageData, 0, 0);
389 this.updateMouseCursorImage_(canvas.toDataURL(), hotspotX, hotspotY);
390
391 } else if (message.method == 'onDebugRegion') {
392 if (this.debugRegionHandler_) {
393 this.debugRegionHandler_(
394 /** @type {{rects: Array<(Array<number>)>}} **/(message.data));
395 }
396 } else if (message.method == 'extensionMessage') {
397 var extMsgType = base.getStringAttr(message.data, 'type');
398 var extMsgData = base.getStringAttr(message.data, 'data');
399 this.extensions_.onProtocolExtensionMessage(extMsgType, extMsgData);
400
401 }
402 };
403
404 /**
405 * Deletes the plugin.
406 */
407 remoting.ClientPluginImpl.prototype.dispose = function() {
408 if (this.plugin_) {
409 this.plugin_.parentNode.removeChild(this.plugin_);
410 this.plugin_ = null;
411 }
412
413 base.dispose(this.extensions_);
414 this.extensions_ = null;
415 };
416
417 /**
418 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin.
419 */
420 remoting.ClientPluginImpl.prototype.element = function() {
421 return this.plugin_;
422 };
423
424 /**
425 * @param {function(boolean): void} onDone
426 */
427 remoting.ClientPluginImpl.prototype.initialize = function(onDone) {
428 if (this.helloReceived_) {
429 onDone(true);
430 } else {
431 this.onInitializedCallback_ = onDone;
432 }
433 };
434
435 /**
436 * @return {boolean} True if the plugin and web-app versions are compatible.
437 */
438 remoting.ClientPluginImpl.prototype.isSupportedVersion = function() {
439 if (!this.helloReceived_) {
440 console.error(
441 "isSupportedVersion() is called before the plugin is initialized.");
442 return false;
443 }
444 return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
445 this.pluginApiVersion_ >= this.API_MIN_VERSION_;
446 };
447
448 /**
449 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
450 * @return {boolean} True if the plugin supports the named feature.
451 */
452 remoting.ClientPluginImpl.prototype.hasFeature = function(feature) {
453 if (!this.helloReceived_) {
454 console.error(
455 "hasFeature() is called before the plugin is initialized.");
456 return false;
457 }
458 return this.pluginApiFeatures_.indexOf(feature) > -1;
459 };
460
461
462 /**
463 * @param {remoting.ClientSession.Capability} capability The capability to test
464 * for.
465 * @return {boolean} True if the capability has been negotiated between
466 * the client and host.
467 */
468 remoting.ClientPluginImpl.prototype.hasCapability = function(capability) {
469 return this.hostCapabilities_ !== null &&
470 this.hostCapabilities_.indexOf(capability) > -1;
471 };
472
473 /**
474 * @return {boolean} True if the plugin supports the injectKeyEvent API.
475 */
476 remoting.ClientPluginImpl.prototype.isInjectKeyEventSupported = function() {
477 return this.pluginApiVersion_ >= 6;
478 };
479
480 /**
481 * @param {string} iq Incoming IQ stanza.
482 */
483 remoting.ClientPluginImpl.prototype.onIncomingIq = function(iq) {
484 if (this.plugin_ && this.plugin_.postMessage) {
485 this.plugin_.postMessage(JSON.stringify(
486 { method: 'incomingIq', data: { iq: iq } }));
487 } else {
488 // plugin.onIq may not be set after the plugin has been shut
489 // down. Particularly this happens when we receive response to
490 // session-terminate stanza.
491 console.warn('plugin.onIq is not set so dropping incoming message.');
492 }
493 };
494
495 /**
496 * @param {remoting.Host} host The host to connect to.
497 * @param {string} localJid Local jid.
498 * @param {remoting.CredentialsProvider} credentialsProvider
499 */
500 remoting.ClientPluginImpl.prototype.connect =
501 function(host, localJid, credentialsProvider) {
502 var keyFilter = '';
503 if (remoting.platformIsMac()) {
504 keyFilter = 'mac';
505 } else if (remoting.platformIsChromeOS()) {
506 keyFilter = 'cros';
507 }
508
509 // Use PPB_VideoDecoder API only in Chrome 43 and above. It is broken in
510 // previous versions of Chrome, see crbug.com/459103 and crbug.com/463577 .
511 var enableVideoDecodeRenderer =
512 parseInt((remoting.getChromeVersion() || '0').split('.')[0], 10) >= 43;
513 this.plugin_.postMessage(JSON.stringify(
514 { method: 'delegateLargeCursors', data: {} }));
515 var methods = 'third_party,spake2_pair,spake2_hmac,spake2_plain';
516 this.credentials_ = credentialsProvider;
517 this.useAsyncPinDialog_();
518 this.plugin_.postMessage(JSON.stringify(
519 { method: 'connect', data: {
520 hostJid: host.jabberId,
521 hostPublicKey: host.publicKey,
522 localJid: localJid,
523 sharedSecret: '',
524 authenticationMethods: methods,
525 authenticationTag: host.hostId,
526 capabilities: this.capabilities_.join(" "),
527 clientPairingId: credentialsProvider.getPairingInfo().clientId,
528 clientPairedSecret: credentialsProvider.getPairingInfo().sharedSecret,
529 keyFilter: keyFilter,
530 enableVideoDecodeRenderer: enableVideoDecodeRenderer
531 }
532 }));
533 };
534
535 /**
536 * Release all currently pressed keys.
537 */
538 remoting.ClientPluginImpl.prototype.releaseAllKeys = function() {
539 this.plugin_.postMessage(JSON.stringify(
540 { method: 'releaseAllKeys', data: {} }));
541 };
542
543 /**
544 * Sets and stores the key remapping setting for the current host.
545 *
546 * @param {string} remappings Comma separated list of key remappings.
547 */
548 remoting.ClientPluginImpl.prototype.setRemapKeys =
549 function(remappings) {
550 // Cancel any existing remappings and apply the new ones.
551 this.applyRemapKeys_(this.keyRemappings_, false);
552 this.applyRemapKeys_(remappings, true);
553 this.keyRemappings_ = remappings;
554 };
555
556 /**
557 * Applies the configured key remappings to the session, or resets them.
558 *
559 * @param {string} remapKeys
560 * @param {boolean} apply True to apply remappings, false to cancel them.
561 * @private
562 */
563 remoting.ClientPluginImpl.prototype.applyRemapKeys_ =
564 function(remapKeys, apply) {
565 if (remapKeys == '') {
566 return;
567 }
568
569 var remappings = remapKeys.split(',');
570 for (var i = 0; i < remappings.length; ++i) {
571 var keyCodes = remappings[i].split('>');
572 if (keyCodes.length != 2) {
573 console.log('bad remapKey: ' + remappings[i]);
574 continue;
575 }
576 var fromKey = parseInt(keyCodes[0], 0);
577 var toKey = parseInt(keyCodes[1], 0);
578 if (!fromKey || !toKey) {
579 console.log('bad remapKey code: ' + remappings[i]);
580 continue;
581 }
582 if (apply) {
583 console.log('remapKey 0x' + fromKey.toString(16) +
584 '>0x' + toKey.toString(16));
585 this.remapKey(fromKey, toKey);
586 } else {
587 console.log('cancel remapKey 0x' + fromKey.toString(16));
588 this.remapKey(fromKey, fromKey);
589 }
590 }
591 };
592
593 /**
594 * Sends a key combination to the remoting host, by sending down events for
595 * the given keys, followed by up events in reverse order.
596 *
597 * @param {Array<number>} keys Key codes to be sent.
598 * @return {void} Nothing.
599 */
600 remoting.ClientPluginImpl.prototype.injectKeyCombination =
601 function(keys) {
602 for (var i = 0; i < keys.length; i++) {
603 this.injectKeyEvent(keys[i], true);
604 }
605 for (var i = 0; i < keys.length; i++) {
606 this.injectKeyEvent(keys[i], false);
607 }
608 };
609
610 /**
611 * Send a key event to the host.
612 *
613 * @param {number} usbKeycode The USB-style code of the key to inject.
614 * @param {boolean} pressed True to inject a key press, False for a release.
615 */
616 remoting.ClientPluginImpl.prototype.injectKeyEvent =
617 function(usbKeycode, pressed) {
618 this.plugin_.postMessage(JSON.stringify(
619 { method: 'injectKeyEvent', data: {
620 'usbKeycode': usbKeycode,
621 'pressed': pressed}
622 }));
623 };
624
625 /**
626 * Remap one USB keycode to another in all subsequent key events.
627 *
628 * @param {number} fromKeycode The USB-style code of the key to remap.
629 * @param {number} toKeycode The USB-style code to remap the key to.
630 */
631 remoting.ClientPluginImpl.prototype.remapKey =
632 function(fromKeycode, toKeycode) {
633 this.plugin_.postMessage(JSON.stringify(
634 { method: 'remapKey', data: {
635 'fromKeycode': fromKeycode,
636 'toKeycode': toKeycode}
637 }));
638 };
639
640 /**
641 * Enable/disable redirection of the specified key to the web-app.
642 *
643 * @param {number} keycode The USB-style code of the key.
644 * @param {Boolean} trap True to enable trapping, False to disable.
645 */
646 remoting.ClientPluginImpl.prototype.trapKey = function(keycode, trap) {
647 this.plugin_.postMessage(JSON.stringify(
648 { method: 'trapKey', data: {
649 'keycode': keycode,
650 'trap': trap}
651 }));
652 };
653
654 /**
655 * Returns an associative array with a set of stats for this connecton.
656 *
657 * @return {remoting.ClientSession.PerfStats} The connection statistics.
658 */
659 remoting.ClientPluginImpl.prototype.getPerfStats = function() {
660 return this.perfStats_;
661 };
662
663 /**
664 * Sends a clipboard item to the host.
665 *
666 * @param {string} mimeType The MIME type of the clipboard item.
667 * @param {string} item The clipboard item.
668 */
669 remoting.ClientPluginImpl.prototype.sendClipboardItem =
670 function(mimeType, item) {
671 if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
672 return;
673 this.plugin_.postMessage(JSON.stringify(
674 { method: 'sendClipboardItem',
675 data: { mimeType: mimeType, item: item }}));
676 };
677
678 /**
679 * Notifies the host that the client has the specified size and pixel density.
680 *
681 * @param {number} width The available client width in DIPs.
682 * @param {number} height The available client height in DIPs.
683 * @param {number} device_scale The number of device pixels per DIP.
684 */
685 remoting.ClientPluginImpl.prototype.notifyClientResolution =
686 function(width, height, device_scale) {
687 this.hostDesktop_.resize(width, height, device_scale);
688 };
689
690 /**
691 * Requests that the host pause or resume sending video updates.
692 *
693 * @param {boolean} pause True to suspend video updates, false otherwise.
694 */
695 remoting.ClientPluginImpl.prototype.pauseVideo =
696 function(pause) {
697 if (this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
698 this.plugin_.postMessage(JSON.stringify(
699 { method: 'videoControl', data: { pause: pause }}));
700 } else if (this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) {
701 this.plugin_.postMessage(JSON.stringify(
702 { method: 'pauseVideo', data: { pause: pause }}));
703 }
704 };
705
706 /**
707 * Requests that the host pause or resume sending audio updates.
708 *
709 * @param {boolean} pause True to suspend audio updates, false otherwise.
710 */
711 remoting.ClientPluginImpl.prototype.pauseAudio =
712 function(pause) {
713 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) {
714 return;
715 }
716 this.plugin_.postMessage(JSON.stringify(
717 { method: 'pauseAudio', data: { pause: pause }}));
718 };
719
720 /**
721 * Requests that the host configure the video codec for lossless encode.
722 *
723 * @param {boolean} wantLossless True to request lossless encoding.
724 */
725 remoting.ClientPluginImpl.prototype.setLosslessEncode =
726 function(wantLossless) {
727 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
728 return;
729 }
730 this.plugin_.postMessage(JSON.stringify(
731 { method: 'videoControl', data: { losslessEncode: wantLossless }}));
732 };
733
734 /**
735 * Requests that the host configure the video codec for lossless color.
736 *
737 * @param {boolean} wantLossless True to request lossless color.
738 */
739 remoting.ClientPluginImpl.prototype.setLosslessColor =
740 function(wantLossless) {
741 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
742 return;
743 }
744 this.plugin_.postMessage(JSON.stringify(
745 { method: 'videoControl', data: { losslessColor: wantLossless }}));
746 };
747
748 /**
749 * Called when a PIN is obtained from the user.
750 *
751 * @param {string} pin The PIN.
752 * @private
753 */
754 remoting.ClientPluginImpl.prototype.onPinFetched_ =
755 function(pin) {
756 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
757 return;
758 }
759 this.plugin_.postMessage(JSON.stringify(
760 { method: 'onPinFetched', data: { pin: pin }}));
761 };
762
763 /**
764 * Tells the plugin to ask for the PIN asynchronously.
765 * @private
766 */
767 remoting.ClientPluginImpl.prototype.useAsyncPinDialog_ =
768 function() {
769 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
770 return;
771 }
772 this.plugin_.postMessage(JSON.stringify(
773 { method: 'useAsyncPinDialog', data: {} }));
774 };
775
776 /**
777 * Allows automatic mouse-lock.
778 */
779 remoting.ClientPluginImpl.prototype.allowMouseLock = function() {
780 this.plugin_.postMessage(JSON.stringify(
781 { method: 'allowMouseLock', data: {} }));
782 };
783
784 /**
785 * Sets the third party authentication token and shared secret.
786 *
787 * @param {remoting.ThirdPartyToken} token
788 * @private
789 */
790 remoting.ClientPluginImpl.prototype.onThirdPartyTokenFetched_ = function(
791 token) {
792 this.plugin_.postMessage(JSON.stringify(
793 { method: 'onThirdPartyTokenFetched',
794 data: { token: token.token, sharedSecret: token.secret}}));
795 };
796
797 /**
798 * Request pairing with the host for PIN-less authentication.
799 *
800 * @param {string} clientName The human-readable name of the client.
801 * @param {function(string, string):void} onDone, Callback to receive the
802 * client id and shared secret when they are available.
803 */
804 remoting.ClientPluginImpl.prototype.requestPairing =
805 function(clientName, onDone) {
806 if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) {
807 return;
808 }
809 this.onPairingComplete_ = onDone;
810 this.plugin_.postMessage(JSON.stringify(
811 { method: 'requestPairing', data: { clientName: clientName } }));
812 };
813
814 /**
815 * Send an extension message to the host.
816 *
817 * @param {string} type The message type.
818 * @param {string} message The message payload.
819 * @private
820 */
821 remoting.ClientPluginImpl.prototype.sendClientMessage_ =
822 function(type, message) {
823 if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) {
824 return;
825 }
826 this.plugin_.postMessage(JSON.stringify(
827 { method: 'extensionMessage',
828 data: { type: type, data: message } }));
829
830 };
831
832 remoting.ClientPluginImpl.prototype.hostDesktop = function() {
833 return this.hostDesktop_;
834 };
835
836 remoting.ClientPluginImpl.prototype.extensions = function() {
837 return this.extensions_;
838 };
839
840 /**
841 * If we haven't yet received a "hello" message from the plugin, change its
842 * size so that the user can confirm it if click-to-play is enabled, or can
843 * see the "this plugin is disabled" message if it is actually disabled.
844 * @private
845 */
846 remoting.ClientPluginImpl.prototype.showPluginForClickToPlay_ = function() {
847 if (!this.helloReceived_) {
848 var width = 200;
849 var height = 200;
850 this.plugin_.style.width = width + 'px';
851 this.plugin_.style.height = height + 'px';
852 // Center the plugin just underneath the "Connnecting..." dialog.
853 var dialog = document.getElementById('client-dialog');
854 var dialogRect = dialog.getBoundingClientRect();
855 this.plugin_.style.top = (dialogRect.bottom + 16) + 'px';
856 this.plugin_.style.left = (window.innerWidth - width) / 2 + 'px';
857 this.plugin_.style.position = 'fixed';
858 }
859 };
860
861 /**
862 * Undo the CSS rules needed to make the plugin clickable for click-to-play.
863 * @private
864 */
865 remoting.ClientPluginImpl.prototype.hidePluginForClickToPlay_ = function() {
866 this.plugin_.style.width = '';
867 this.plugin_.style.height = '';
868 this.plugin_.style.top = '';
869 this.plugin_.style.left = '';
870 this.plugin_.style.position = '';
871 };
872
873 /**
874 * Callback passed to submodules to post a message to the plugin.
875 *
876 * @param {Object} message
877 * @private
878 */
879 remoting.ClientPluginImpl.prototype.postMessage_ = function(message) {
880 if (this.plugin_ && this.plugin_.postMessage) {
881 this.plugin_.postMessage(JSON.stringify(message));
882 }
883 };
884
885 /**
886 * @constructor
887 * @implements {remoting.ClientPluginFactory}
888 */
889 remoting.DefaultClientPluginFactory = function() {};
890
891 /**
892 * @param {Element} container
893 * @param {Array<string>} requiredCapabilities
894 * @return {remoting.ClientPlugin}
895 */
896 remoting.DefaultClientPluginFactory.prototype.createPlugin =
897 function(container, requiredCapabilities) {
898 return new remoting.ClientPluginImpl(container,
899 requiredCapabilities);
900 };
901
902 remoting.DefaultClientPluginFactory.prototype.preloadPlugin = function() {
903 if (remoting.settings.CLIENT_PLUGIN_TYPE != 'pnacl') {
904 return;
905 }
906
907 var plugin = remoting.ClientPluginImpl.createPluginElement_();
908 plugin.addEventListener(
909 'loadend', function() { document.body.removeChild(plugin); }, false);
910 document.body.appendChild(plugin);
911 };
OLDNEW
« no previous file with comments | « remoting/webapp/crd/js/client_plugin_host_desktop_impl.js ('k') | remoting/webapp/crd/js/client_session.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698