Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / extensions / renderer / resources / media_router_bindings.js
blob172b91b094130d58d88528847efe5edc2c3d31c5
1 // Copyright 2015 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 var mediaRouter;
7 define('media_router_bindings', [
8     'mojo/public/js/bindings',
9     'mojo/public/js/core',
10     'content/public/renderer/service_provider',
11     'chrome/browser/media/router/media_router.mojom',
12     'extensions/common/mojo/keep_alive.mojom',
13     'mojo/public/js/connection',
14     'mojo/public/js/router',
15 ], function(bindings,
16             core,
17             serviceProvider,
18             mediaRouterMojom,
19             keepAliveMojom,
20             connector,
21             routerModule) {
22   'use strict';
24   /**
25    * Converts a media sink to a MediaSink Mojo object.
26    * @param {!MediaSink} sink A media sink.
27    * @return {!mediaRouterMojom.MediaSink} A Mojo MediaSink object.
28    */
29   function sinkToMojo_(sink) {
30     return new mediaRouterMojom.MediaSink({
31       'name': sink.friendlyName,
32       'sink_id': sink.id,
33       'icon_type': sinkIconTypeToMojo(sink.iconType),
34       'is_launching': sink.isLaunching_,
35     });
36   }
38   /**
39    * Converts a media sink's icon type to a MediaSink.IconType Mojo object.
40    * @param {!MediaSink.IconType} type A media sink's icon type.
41    * @return {!mediaRouterMojom.MediaSink.IconType} A Mojo MediaSink.IconType
42    *     object.
43    */
44   function sinkIconTypeToMojo(type) {
45     switch (type) {
46       case 'cast':
47         return mediaRouterMojom.MediaSink.IconType.CAST;
48       case 'cast_audio':
49         return mediaRouterMojom.MediaSink.IconType.CAST_AUDIO;
50       case 'generic':
51         return mediaRouterMojom.MediaSink.IconType.GENERIC;
52       case 'hangout':
53         return mediaRouterMojom.MediaSink.IconType.HANGOUT;
54       default:
55         console.error('Unknown sink icon type : ' + type);
56         return mediaRouterMojom.MediaSink.IconType.GENERIC;
57     }
58   }
60   /**
61    * Returns a Mojo MediaRoute object given a MediaRoute and a
62    * media sink name.
63    * @param {!MediaRoute} route
64    * @return {!mojo.MediaRoute}
65    */
66   function routeToMojo_(route) {
67     return new mediaRouterMojom.MediaRoute({
68       'media_route_id': route.id,
69       'media_source': route.mediaSource,
70       'media_sink_id': route.sinkId,
71       'description': route.description,
72       'icon_url': route.iconUrl,
73       'is_local': route.isLocal,
74       'custom_controller_path': route.customControllerPath,
75       // TODO(imcheng): Remove logic when extension always sets the field.
76       'for_display': route.forDisplay == undefined ? true : route.forDisplay
77     });
78   }
80   /**
81    * Converts a route message to a RouteMessage Mojo object.
82    * @param {!RouteMessage} message
83    * @return {!mediaRouterMojom.RouteMessage} A Mojo RouteMessage object.
84    */
85   function messageToMojo_(message) {
86     if ("string" == typeof message.message) {
87       return new mediaRouterMojom.RouteMessage({
88         'type': mediaRouterMojom.RouteMessage.Type.TEXT,
89         'message': message.message,
90       });
91     } else {
92       return new mediaRouterMojom.RouteMessage({
93         'type': mediaRouterMojom.RouteMessage.Type.BINARY,
94         'data': message.message,
95       });
96     }
97   }
99   /**
100    * Creates a new MediaRouter.
101    * Converts a route struct to its Mojo form.
102    * @param {!MediaRouterService} service
103    * @constructor
104    */
105   function MediaRouter(service) {
106     /**
107      * The Mojo service proxy. Allows extension code to call methods that reside
108      * in the browser.
109      * @type {!MediaRouterService}
110      */
111     this.service_ = service;
113     /**
114      * The provider manager service delegate. Its methods are called by the
115      * browser-resident Mojo service.
116      * @type {!MediaRouter}
117      */
118     this.mrpm_ = new MediaRouteProvider(this);
120     /**
121      * The message pipe that connects the Media Router to mrpm_ across
122      * browser/renderer IPC boundaries. Object must remain in scope for the
123      * lifetime of the connection to prevent the connection from closing
124      * automatically.
125      * @type {!mojo.MessagePipe}
126      */
127     this.pipe_ = core.createMessagePipe();
129     /**
130      * Handle to a KeepAlive service object, which prevents the extension from
131      * being suspended as long as it remains in scope.
132      * @type {boolean}
133      */
134     this.keepAlive_ = null;
136     /**
137      * The stub used to bind the service delegate to the Mojo interface.
138      * Object must remain in scope for the lifetime of the connection to
139      * prevent the connection from closing automatically.
140      * @type {!mojom.MediaRouter}
141      */
142     this.mediaRouteProviderStub_ = connector.bindHandleToStub(
143         this.pipe_.handle0, mediaRouterMojom.MediaRouteProvider);
145     // Link mediaRouteProviderStub_ to the provider manager delegate.
146     bindings.StubBindings(this.mediaRouteProviderStub_).delegate = this.mrpm_;
147   }
149   /**
150    * Registers the Media Router Provider Manager with the Media Router.
151    * @return {!Promise<string>} Instance ID for the Media Router.
152    */
153   MediaRouter.prototype.start = function() {
154     return this.service_.registerMediaRouteProvider(this.pipe_.handle1).then(
155         function(result) {
156           return result.instance_id;
157         }.bind(this));
158   }
160   /**
161    * Sets the service delegate methods.
162    * @param {Object} handlers
163    */
164   MediaRouter.prototype.setHandlers = function(handlers) {
165     this.mrpm_.setHandlers(handlers);
166   }
168   /**
169    * The keep alive status.
170    * @return {boolean}
171    */
172   MediaRouter.prototype.getKeepAlive = function() {
173     return this.keepAlive_ != null;
174   };
176   /**
177    * Called by the provider manager when a sink list for a given source is
178    * updated.
179    * @param {!string} sourceUrn
180    * @param {!Array<!MediaSink>} sinks
181    */
182   MediaRouter.prototype.onSinksReceived = function(sourceUrn, sinks) {
183     this.service_.onSinksReceived(sourceUrn, sinks.map(sinkToMojo_));
184   };
186   /**
187    * Called by the provider manager to keep the extension from suspending
188    * if it enters a state where suspension is undesirable (e.g. there is an
189    * active MediaRoute.)
190    * If keepAlive is true, the extension is kept alive.
191    * If keepAlive is false, the extension is allowed to suspend.
192    * @param {boolean} keepAlive
193    */
194   MediaRouter.prototype.setKeepAlive = function(keepAlive) {
195     if (keepAlive === false && this.keepAlive_) {
196       this.keepAlive_.close();
197       this.keepAlive_ = null;
198     } else if (keepAlive === true && !this.keepAlive_) {
199       this.keepAlive_ = new routerModule.Router(
200           serviceProvider.connectToService(
201               keepAliveMojom.KeepAlive.name));
202     }
203   };
205   /**
206    * Called by the provider manager to send an issue from a media route
207    * provider to the Media Router, to show the user.
208    * @param {!Object} issue The issue object.
209    */
210   MediaRouter.prototype.onIssue = function(issue) {
211     function issueSeverityToMojo_(severity) {
212       switch (severity) {
213         case 'fatal':
214           return mediaRouterMojom.Issue.Severity.FATAL;
215         case 'warning':
216           return mediaRouterMojom.Issue.Severity.WARNING;
217         case 'notification':
218           return mediaRouterMojom.Issue.Severity.NOTIFICATION;
219         default:
220           console.error('Unknown issue severity: ' + severity);
221           return mediaRouterMojom.Issue.Severity.NOTIFICATION;
222       }
223     }
225     function issueActionToMojo_(action) {
226       switch (action) {
227         case 'ok':
228           return mediaRouterMojom.Issue.ActionType.OK;
229         case 'cancel':
230           return mediaRouterMojom.Issue.ActionType.CANCEL;
231         case 'dismiss':
232           return mediaRouterMojom.Issue.ActionType.DISMISS;
233         case 'learn_more':
234           return mediaRouterMojom.Issue.ActionType.LEARN_MORE;
235         default:
236           console.error('Unknown issue action type : ' + action);
237           return mediaRouterMojom.Issue.ActionType.OK;
238       }
239     }
241     var secondaryActions = (issue.secondaryActions || []).map(function(e) {
242       return issueActionToMojo_(e);
243     });
244     this.service_.onIssue(new mediaRouterMojom.Issue({
245       'route_id': issue.routeId,
246       'severity': issueSeverityToMojo_(issue.severity),
247       'title': issue.title,
248       'message': issue.message,
249       'default_action': issueActionToMojo_(issue.defaultAction),
250       'secondary_actions': secondaryActions,
251       'help_url': issue.helpUrl,
252       'is_blocking': issue.isBlocking
253     }));
254   };
256   /**
257    * Called by the provider manager when the set of active routes
258    * has been updated.
259    * @param {!Array<MediaRoute>} routes The active set of media routes.
260    */
261   MediaRouter.prototype.onRoutesUpdated = function(routes) {
262     this.service_.onRoutesUpdated(routes.map(routeToMojo_));
263   };
265   /**
266    * Object containing callbacks set by the provider manager.
267    * TODO(mfoltz): Better named ProviderManagerDelegate?
268    *
269    * @constructor
270    * @struct
271    */
272   function MediaRouterHandlers() {
273     /**
274      * @type {function(!string, !string, !string, !string, !number}
275      */
276     this.createRoute = null;
278     /**
279      * @type {function(!string, !string, !string, !number)}
280      */
281     this.joinRoute = null;
283     /**
284      * @type {function(string)}
285      */
286     this.closeRoute = null;
288     /**
289      * @type {function(string)}
290      */
291     this.startObservingMediaSinks = null;
293     /**
294      * @type {function(string)}
295      */
296     this.stopObservingMediaSinks = null;
298     /**
299      * @type {function(string, string): Promise}
300      */
301     this.sendRouteMessage = null;
303     /**
304      * @type {function(string, Uint8Array): Promise}
305      */
306     this.sendRouteBinaryMessage = null;
308     /**
309      * @type {function(string):
310      *     Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
311      */
312     this.listenForRouteMessages = null;
314     /**
315      * @type {function(string)}
316      */
317     this.stopListeningForRouteMessages = null;
319     /**
320      * @type {function(string)}
321      */
322     this.onPresentationSessionDetached = null;
324     /**
325      * @type {function()}
326      */
327     this.startObservingMediaRoutes = null;
329     /**
330      * @type {function()}
331      */
332     this.stopObservingMediaRoutes = null;
333   };
335   /**
336    * Routes calls from Media Router to the provider manager extension.
337    * Registered with the MediaRouter stub.
338    * @param {!MediaRouter} MediaRouter proxy to call into the
339    * Media Router mojo interface.
340    * @constructor
341    */
342   function MediaRouteProvider(mediaRouter) {
343     mediaRouterMojom.MediaRouteProvider.stubClass.call(this);
345     /**
346      * Object containing JS callbacks into Provider Manager code.
347      * @type {!MediaRouterHandlers}
348      */
349     this.handlers_ = new MediaRouterHandlers();
351     /**
352      * Proxy class to the browser's Media Router Mojo service.
353      * @type {!MediaRouter}
354      */
355     this.mediaRouter_ = mediaRouter;
356   }
357   MediaRouteProvider.prototype = Object.create(
358       mediaRouterMojom.MediaRouteProvider.stubClass.prototype);
360   /*
361    * Sets the callback handler used to invoke methods in the provider manager.
362    *
363    * TODO(mfoltz): Rename to something more explicit?
364    * @param {!MediaRouterHandlers} handlers
365    */
366   MediaRouteProvider.prototype.setHandlers = function(handlers) {
367     this.handlers_ = handlers;
368     var requiredHandlers = [
369       'stopObservingMediaRoutes',
370       'startObservingMediaRoutes',
371       'sendRouteMessage',
372       'sendRouteBinaryMessage',
373       'listenForRouteMessages',
374       'stopListeningForRouteMessages',
375       'onPresentationSessionDetached',
376       'closeRoute',
377       'joinRoute',
378       'createRoute',
379       'stopObservingMediaSinks',
380       'startObservingMediaRoutes'
381     ];
382     requiredHandlers.forEach(function(nextHandler) {
383       if (handlers[nextHandler] === undefined) {
384         console.error(nextHandler + ' handler not registered.');
385       }
386     });
387   }
389   /**
390    * Starts querying for sinks capable of displaying the media source
391    * designated by |sourceUrn|.  Results are returned by calling
392    * OnSinksReceived.
393    * @param {!string} sourceUrn
394    */
395   MediaRouteProvider.prototype.startObservingMediaSinks =
396       function(sourceUrn) {
397     this.handlers_.startObservingMediaSinks(sourceUrn);
398   };
400   /**
401    * Stops querying for sinks capable of displaying |sourceUrn|.
402    * @param {!string} sourceUrn
403    */
404   MediaRouteProvider.prototype.stopObservingMediaSinks =
405       function(sourceUrn) {
406     this.handlers_.stopObservingMediaSinks(sourceUrn);
407   };
409   /**
410    * Requests that |sinkId| render the media referenced by |sourceUrn|. If the
411    * request is from the Presentation API, then origin and tabId will
412    * be populated.
413    * @param {!string} sourceUrn Media source to render.
414    * @param {!string} sinkId Media sink ID.
415    * @param {!string} presentationId Presentation ID from the site
416    *     requesting presentation. TODO(mfoltz): Remove.
417    * @param {!string} origin Origin of site requesting presentation.
418    * @param {!number} tabId ID of tab requesting presentation.
419    * @return {!Promise.<!Object>} A Promise resolving to an object describing
420    *     the newly created media route, or rejecting with an error message on
421    *     failure.
422    */
423   MediaRouteProvider.prototype.createRoute =
424       function(sourceUrn, sinkId, presentationId, origin, tabId) {
425     return this.handlers_.createRoute(
426         sourceUrn, sinkId, presentationId, origin, tabId)
427         .then(function(route) {
428           return {route: routeToMojo_(route)};
429         }.bind(this))
430         .catch(function(err) {
431           return {error_text: 'Error creating route: ' + err.message};
432         });
433   };
435   /**
436    * Handles a request via the Presentation API to join an existing route given
437    * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
438    * validating same-origin/tab scope.
439    * @param {!string} sourceUrn Media source to render.
440    * @param {!string} presentationId Presentation ID to join.
441    * @param {!string} origin Origin of site requesting join.
442    * @param {!number} tabId ID of tab requesting join.
443    * @return {!Promise.<!Object>} A Promise resolving to an object describing
444    *     the newly created media route, or rejecting with an error message on
445    *     failure.
446    */
447   MediaRouteProvider.prototype.joinRoute =
448       function(sourceUrn, presentationId, origin, tabId) {
449     return this.handlers_.joinRoute(sourceUrn, presentationId, origin, tabId)
450         .then(function(newRoute) {
451           return {route: routeToMojo_(newRoute)};
452         },
453         function(err) {
454           return {error_text: 'Error joining route: ' + err.message};
455         });
456   };
458   /**
459    * Closes the route specified by |routeId|.
460    * @param {!string} routeId
461    */
462   MediaRouteProvider.prototype.closeRoute = function(routeId) {
463     this.handlers_.closeRoute(routeId);
464   };
466   /**
467    * Posts a message to the route designated by |routeId|.
468    * @param {!string} routeId
469    * @param {!string} message
470    * @return {!Promise.<boolean>} Resolved with true if the message was sent,
471    *    or false on failure.
472    */
473   MediaRouteProvider.prototype.sendRouteMessage = function(
474       routeId, message) {
475     return this.handlers_.sendRouteMessage(routeId, message)
476         .then(function() {
477           return {'sent': true};
478         }, function() {
479           return {'sent': false};
480         });
481   };
483   /**
484    * Sends a binary message to the route designated by |routeId|.
485    * @param {!string} routeId
486    * @param {!Uint8Array} data
487    * @return {!Promise.<boolean>} Resolved with true if the data was sent,
488    *    or false on failure.
489    */
490   MediaRouteProvider.prototype.sendRouteBinaryMessage = function(
491       routeId, data) {
492     return this.handlers_.sendRouteBinaryMessage(routeId, data)
493         .then(function() {
494           return {'sent': true};
495         }, function() {
496           return {'sent': false};
497         });
498   };
500   /**
501    * Listen for next batch of messages from one of the routeIds.
502    * @param {!string} routeId
503    * @return {!Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
504    *     Resolved with a list of messages, and a boolean indicating if an error
505    *     occurred.
506    */
507   MediaRouteProvider.prototype.listenForRouteMessages = function(routeId) {
508     return this.handlers_.listenForRouteMessages(routeId)
509         .then(function(messages) {
510           return {'messages': messages.map(messageToMojo_), 'error': false};
511         }, function() {
512           return {'messages': [], 'error': true};
513         });
514   };
516   /**
517    * If there is an outstanding |listenForRouteMessages| promise for
518    * |routeId|, resolve that promise with an empty array.
519    * @param {!string} routeId
520    */
521   MediaRouteProvider.prototype.stopListeningForRouteMessages = function(
522       routeId) {
523     return this.handlers_.stopListeningForRouteMessages(routeId);
524   };
526   /**
527    * Indicates that the presentation session that was connected to |routeId| is
528    * no longer connected to it.
529    * @param {!string} routeId
530    */
531   MediaRouteProvider.prototype.onPresentationSessionDetached = function(
532       routeId) {
533     this.handlers_.onPresentationSessionDetached(routeId);
534   };
536   /**
537    * Requests that the provider manager start sending information about active
538    * media routes to the Media Router.
539    */
540   MediaRouteProvider.prototype.startObservingMediaRoutes = function() {
541     this.handlers_.startObservingMediaRoutes();
542   };
544   /**
545    * Requests that the provider manager stop sending information about active
546    * media routes to the Media Router.
547    */
548   MediaRouteProvider.prototype.stopObservingMediaRoutes = function() {
549     this.handlers_.stopObservingMediaRoutes();
550   };
552   mediaRouter = new MediaRouter(connector.bindHandleToProxy(
553       serviceProvider.connectToService(
554           mediaRouterMojom.MediaRouter.name),
555       mediaRouterMojom.MediaRouter));
557   return mediaRouter;