Add the ability to code generated prepopulated static nested structs
[chromium-blink-merge.git] / extensions / renderer / resources / media_router_bindings.js
blobeb0d35539f2c3ff87f0e6dedceabe2576bd595e5
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     });
34   }
36   /**
37    * Returns a Mojo MediaRoute object given a MediaRoute and a
38    * media sink name.
39    * @param {!MediaRoute} route
40    * @param {!string} sinkName
41    * @return {!mojo.MediaRoute}
42    */
43   function routeToMojo_(route, sinkName) {
44     return new mediaRouterMojom.MediaRoute({
45       'media_route_id': route.id,
46       'media_source': route.mediaSource,
47       'media_sink': new mediaRouterMojom.MediaSink({
48         'sink_id': route.sinkId,
49         'name': sinkName,
50       }),
51       'description': route.description,
52       'icon_url': route.iconUrl,
53       'is_local': route.isLocal
54     });
55   }
57   /**
58    * Converts a route message to a RouteMessage Mojo object.
59    * @param {!RouteMessage} message
60    * @return {!mediaRouterMojom.RouteMessage} A Mojo RouteMessage object.
61    */
62   function messageToMojo_(message) {
63     if ("string" == typeof message.message) {
64       return new mediaRouterMojom.RouteMessage({
65         'route_id': message.routeId,
66         'type': mediaRouterMojom.RouteMessage.Type.TEXT,
67         'message': message.message,
68       });
69     } else {
70       return new mediaRouterMojom.RouteMessage({
71         'route_id': message.routeId,
72         'type': mediaRouterMojom.RouteMessage.Type.BINARY,
73         'data': message.message,
74       });
75     }
76   }
78   /**
79    * Creates a new MediaRouter.
80    * Converts a route struct to its Mojo form.
81    * @param {!MediaRouterService} service
82    * @constructor
83    */
84   function MediaRouter(service) {
85     /**
86      * The Mojo service proxy. Allows extension code to call methods that reside
87      * in the browser.
88      * @type {!MediaRouterService}
89      */
90     this.service_ = service;
92     /**
93      * The provider manager service delegate. Its methods are called by the
94      * browser-resident Mojo service.
95      * @type {!MediaRouter}
96      */
97     this.mrpm_ = new MediaRouteProvider(this);
99     /**
100      * The message pipe that connects the Media Router to mrpm_ across
101      * browser/renderer IPC boundaries. Object must remain in scope for the
102      * lifetime of the connection to prevent the connection from closing
103      * automatically.
104      * @type {!mojo.MessagePipe}
105      */
106     this.pipe_ = core.createMessagePipe();
108     /**
109      * Handle to a KeepAlive service object, which prevents the extension from
110      * being suspended as long as it remains in scope.
111      * @type {boolean}
112      */
113     this.keepAlive_ = null;
115     /**
116      * The stub used to bind the service delegate to the Mojo interface.
117      * Object must remain in scope for the lifetime of the connection to
118      * prevent the connection from closing automatically.
119      * @type {!mojom.MediaRouter}
120      */
121     this.mediaRouteProviderStub_ = connector.bindHandleToStub(
122         this.pipe_.handle0, mediaRouterMojom.MediaRouteProvider);
124     // Link mediaRouteProviderStub_ to the provider manager delegate.
125     bindings.StubBindings(this.mediaRouteProviderStub_).delegate = this.mrpm_;
126   }
128   /**
129    * Registers the Media Router Provider Manager with the Media Router.
130    * @return {!Promise<string>} Instance ID for the Media Router.
131    */
132   MediaRouter.prototype.start = function() {
133     return this.service_.registerMediaRouteProvider(this.pipe_.handle1).then(
134         function(result) {
135           return result.instance_id;
136         }.bind(this));
137   }
139   /**
140    * Sets the service delegate methods.
141    * @param {Object} handlers
142    */
143   MediaRouter.prototype.setHandlers = function(handlers) {
144     this.mrpm_.setHandlers(handlers);
145   }
147   /**
148    * The keep alive status.
149    * @return {boolean}
150    */
151   MediaRouter.prototype.getKeepAlive = function() {
152     return this.keepAlive_ != null;
153   };
155   /**
156    * Called by the provider manager when a sink list for a given source is
157    * updated.
158    * @param {!string} sourceUrn
159    * @param {!Array<!MediaSink>} sinks
160    */
161   MediaRouter.prototype.onSinksReceived = function(sourceUrn, sinks) {
162     this.service_.onSinksReceived(sourceUrn, sinks.map(sinkToMojo_));
163   };
165   /**
166    * Called by the provider manager to keep the extension from suspending
167    * if it enters a state where suspension is undesirable (e.g. there is an
168    * active MediaRoute.)
169    * If keepAlive is true, the extension is kept alive.
170    * If keepAlive is false, the extension is allowed to suspend.
171    * @param {boolean} keepAlive
172    */
173   MediaRouter.prototype.setKeepAlive = function(keepAlive) {
174     if (keepAlive === false && this.keepAlive_) {
175       this.keepAlive_.close();
176       this.keepAlive_ = null;
177     } else if (keepAlive === true && !this.keepAlive_) {
178       this.keepAlive_ = new routerModule.Router(
179           serviceProvider.connectToService(
180               keepAliveMojom.KeepAlive.name));
181     }
182   };
184   /**
185    * Called by the provider manager to send an issue from a media route
186    * provider to the Media Router, to show the user.
187    * @param {!Object} issue The issue object.
188    */
189   MediaRouter.prototype.onIssue = function(issue) {
190     function issueSeverityToMojo_(severity) {
191       switch (severity) {
192         case 'fatal':
193           return mediaRouterMojom.Issue.Severity.FATAL;
194         case 'warning':
195           return mediaRouterMojom.Issue.Severity.WARNING;
196         case 'notification':
197           return mediaRouterMojom.Issue.Severity.NOTIFICATION;
198         default:
199           console.error('Unknown issue severity: ' + severity);
200           return mediaRouterMojom.Issue.Severity.NOTIFICATION;
201       }
202     }
204     function issueActionToMojo_(action) {
205       switch (action) {
206         case 'ok':
207           return mediaRouterMojom.Issue.ActionType.OK;
208         case 'cancel':
209           return mediaRouterMojom.Issue.ActionType.CANCEL;
210         case 'dismiss':
211           return mediaRouterMojom.Issue.ActionType.DISMISS;
212         case 'learn_more':
213           return mediaRouterMojom.Issue.ActionType.LEARN_MORE;
214         default:
215           console.error('Unknown issue action type : ' + action);
216           return mediaRouterMojom.Issue.ActionType.OK;
217       }
218     }
220     var secondaryActions = (issue.secondaryActions || []).map(function(e) {
221       return issueActionToMojo_(e);
222     });
223     this.service_.onIssue(new mediaRouterMojom.Issue({
224       'route_id': issue.routeId,
225       'severity': issueSeverityToMojo_(issue.severity),
226       'title': issue.title,
227       'message': issue.message,
228       'default_action': issueActionToMojo_(issue.defaultAction),
229       'secondary_actions': secondaryActions,
230       'help_url': issue.helpUrl,
231       'is_blocking': issue.isBlocking
232     }));
233   };
235   /**
236    * Called by the provider manager when the set of active routes
237    * has been updated.
238    * @param {!Array<MediaRoute>} routes The active set of media routes.
239    * @param {!Array<MediaSink>} sinks The active set of media sinks.
240    */
241   MediaRouter.prototype.onRoutesUpdated = function(routes, sinks) {
242     // Create an inverted index relating sink IDs to their names.
243     var sinkNameMap = {};
244     for (var i = 0; i < sinks.length; i++) {
245       sinkNameMap[sinks[i].id] = sinks[i].friendlyName;
246     }
248     // Convert MediaRoutes to Mojo objects and add their sink names
249     // via sinkNameMap.
250     var mojoRoutes = routes.map(function(nextRoute) {
251       return routeToMojo_(nextRoute, sinkNameMap[nextRoute.sinkId]);
252     });
254     this.service_.onRoutesUpdated(mojoRoutes, sinks.map(sinkToMojo_));
255   };
257   /**
258    * Called by the Provider Manager when an error was encountered in response
259    * to a media route creation request.
260    * @param {!string} requestId The request id.
261    * @param {!string} error The error.
262    */
263   MediaRouter.prototype.onRouteResponseError =
264       function(requestId, error) {
265     this.service_.onRouteResponseError(requestId, error);
266   };
268   /**
269    * Called by the provider manager when a route was able to be created by a
270    * media route provider.
271    *
272    * @param {string} requestId The media route request id.
273    * @param {string} routeId The id of the media route that was created.
274    */
275   MediaRouter.prototype.onRouteResponseReceived =
276       function(requestId, routeId) {
277     this.service_.onRouteResponseReceived(requestId, routeId);
278   };
280   /**
281    * Object containing callbacks set by the provider manager.
282    * TODO(mfoltz): Better named ProviderManagerDelegate?
283    *
284    * @constructor
285    * @struct
286    */
287   function MediaRouterHandlers() {
288     /**
289      * @type {function(!string, !string, !string, !string, !number}
290      */
291     this.createRoute = null;
293     /**
294      * @type {function(!string, !string, !string, !number)}
295      */
296     this.joinRoute = null;
298     /**
299      * @type {function(string)}
300      */
301     this.closeRoute = null;
303     /**
304      * @type {function(string)}
305      */
306     this.startObservingMediaSinks = null;
308     /**
309      * @type {function(string)}
310      */
311     this.stopObservingMediaSinks = null;
313     /**
314      * @type {function(string, string): Promise}
315      */
316     this.sendRouteMessage = null;
318     /**
319      * @type {function(Array.<string>): Promise.<Array.<RouteMessage>>}
320      */
321     this.listenForRouteMessages = null;
323     /**
324      * @type {function()}
325      */
326     this.startObservingMediaRoutes = null;
328     /**
329      * @type {function()}
330      */
331     this.stopObservingMediaRoutes = null;
332   };
334   /**
335    * Routes calls from Media Router to the provider manager extension.
336    * Registered with the MediaRouter stub.
337    * @param {!MediaRouter} MediaRouter proxy to call into the
338    * Media Router mojo interface.
339    * @constructor
340    */
341   function MediaRouteProvider(mediaRouter) {
342     mediaRouterMojom.MediaRouteProvider.stubClass.call(this);
344     /**
345      * Object containing JS callbacks into Provider Manager code.
346      * @type {!MediaRouterHandlers}
347      */
348     this.handlers_ = new MediaRouterHandlers();
350     /**
351      * Proxy class to the browser's Media Router Mojo service.
352      * @type {!MediaRouter}
353      */
354     this.mediaRouter_ = mediaRouter;
355   }
356   MediaRouteProvider.prototype = Object.create(
357       mediaRouterMojom.MediaRouteProvider.stubClass.prototype);
359   /*
360    * Sets the callback handler used to invoke methods in the provider manager.
361    *
362    * TODO(mfoltz): Rename to something more explicit?
363    * @param {!MediaRouterHandlers} handlers
364    */
365   MediaRouteProvider.prototype.setHandlers = function(handlers) {
366     this.handlers_ = handlers;
367     var requiredHandlers = [
368       'stopObservingMediaRoutes',
369       'startObservingMediaRoutes',
370       'sendRouteMessage',
371       'listenForRouteMessages',
372       'closeRoute',
373       'joinRoute',
374       'createRoute',
375       'stopObservingMediaSinks',
376       'startObservingMediaRoutes'
377     ];
378     requiredHandlers.forEach(function(nextHandler) {
379       if (handlers[nextHandler] === undefined) {
380         console.error(nextHandler + ' handler not registered.');
381       }
382     });
383   }
385   /**
386    * Starts querying for sinks capable of displaying the media source
387    * designated by |sourceUrn|.  Results are returned by calling
388    * OnSinksReceived.
389    * @param {!string} sourceUrn
390    */
391   MediaRouteProvider.prototype.startObservingMediaSinks =
392       function(sourceUrn) {
393     this.handlers_.startObservingMediaSinks(sourceUrn);
394   };
396   /**
397    * Stops querying for sinks capable of displaying |sourceUrn|.
398    * @param {!string} sourceUrn
399    */
400   MediaRouteProvider.prototype.stopObservingMediaSinks =
401       function(sourceUrn) {
402     this.handlers_.stopObservingMediaSinks(sourceUrn);
403   };
405   /**
406    * Requests that |sinkId| render the media referenced by |sourceUrn|. If the
407    * request is from the Presentation API, then origin and tabId will
408    * be populated.
409    * @param {!string} sourceUrn Media source to render.
410    * @param {!string} sinkId Media sink ID.
411    * @param {!string} presentationId Presentation ID from the site
412    *     requesting presentation. TODO(mfoltz): Remove.
413    * @param {!string} origin Origin of site requesting presentation.
414    * @param {!number} tabId ID of tab requesting presentation.
415    * @return {!Promise.<!Object>} A Promise resolving to an object describing
416    *     the newly created media route, or rejecting with an error message on
417    *     failure.
418    */
419   MediaRouteProvider.prototype.createRoute =
420       function(sourceUrn, sinkId, presentationId, origin, tabId) {
421     return this.handlers_.createRoute(
422         sourceUrn, sinkId, presentationId, origin, tabId)
423         .then(function(route) {
424           // Sink name is not used, so it is omitted here.
425           return {route: routeToMojo_(route, "")};
426         }.bind(this))
427         .catch(function(err) {
428           return {error_text: 'Error creating route: ' + err.message};
429         });
430   };
432   /**
433    * Handles a request via the Presentation API to join an existing route given
434    * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
435    * validating same-origin/tab scope.
436    * @param {!string} sourceUrn Media source to render.
437    * @param {!string} presentationId Presentation ID to join.
438    * @param {!string} origin Origin of site requesting join.
439    * @param {!number} tabId ID of tab requesting join.
440    * @return {!Promise.<!Object>} A Promise resolving to an object describing
441    *     the newly created media route, or rejecting with an error message on
442    *     failure.
443    */
444   MediaRouteProvider.prototype.joinRoute =
445       function(sourceUrn, presentationId, origin, tabId) {
446     return this.handlers_.joinRoute(sourceUrn, presentationId, origin, tabId)
447         .then(function(newRoute) {
448           // Sink name is not used, so it is omitted here.
449           return {route: routeToMojo_(newRoute, "")};
450         },
451         function(err) {
452           return {error_text: 'Error joining route: ' + err.message};
453         });
454   };
456   /**
457    * Closes the route specified by |routeId|.
458    * @param {!string} routeId
459    */
460   MediaRouteProvider.prototype.closeRoute = function(routeId) {
461     this.handlers_.closeRoute(routeId);
462   };
464   /**
465    * Posts a message to the route designated by |routeId|.
466    * @param {!string} routeId
467    * @param {!string} message
468    * @return {!Promise.<boolean>} Resolved with true if the message was sent,
469    *    or false on failure.
470    */
471   MediaRouteProvider.prototype.sendRouteMessage = function(
472       routeId, message) {
473     return this.handlers_.sendRouteMessage(routeId, message)
474         .then(function() {
475           return true;
476         }, function() {
477           return false;
478         });
479   };
481   /**
482    * Listen for next batch of messages from one of the routeIds.
483    * @param {!Array.<string>} routeIds
484    * @return {!Promise.<Array.<RouteMessage>>} Resolved with a list of messages,
485    *    an empty list if an error occurred.
486    */
487   MediaRouteProvider.prototype.listenForRouteMessages = function(routeIds) {
488     return this.handlers_.listenForRouteMessages(routeIds)
489         .then(function(messages) {
490           return {'messages': messages.map(messageToMojo_)};
491         }, function() {
492           return {'messages': []};
493         });
494   };
496   /**
497    * Requests that the provider manager start sending information about active
498    * media routes to the Media Router.
499    */
500   MediaRouteProvider.prototype.startObservingMediaRoutes = function() {
501     this.handlers_.startObservingMediaRoutes();
502   };
504   /**
505    * Requests that the provider manager stop sending information about active
506    * media routes to the Media Router.
507    */
508   MediaRouteProvider.prototype.stopObservingMediaRoutes = function() {
509     this.handlers_.stopObservingMediaRoutes();
510   };
512   mediaRouter = new MediaRouter(connector.bindHandleToProxy(
513       serviceProvider.connectToService(
514           mediaRouterMojom.MediaRouter.name),
515       mediaRouterMojom.MediaRouter));
517   return mediaRouter;