Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / extensions / renderer / resources / media_router_bindings.js
blobb0a468e384765c7cae51650746489ca0e924c534
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       'is_launching': sink.isLaunching_,
34     });
35   }
37   /**
38    * Returns a Mojo MediaRoute object given a MediaRoute and a
39    * media sink name.
40    * @param {!MediaRoute} route
41    * @param {!string} sinkName
42    * @return {!mojo.MediaRoute}
43    */
44   function routeToMojo_(route, sinkName) {
45     return new mediaRouterMojom.MediaRoute({
46       'media_route_id': route.id,
47       'media_source': route.mediaSource,
48       'media_sink': new mediaRouterMojom.MediaSink({
49         'sink_id': route.sinkId,
50         'name': sinkName,
51       }),
52       'description': route.description,
53       'icon_url': route.iconUrl,
54       'is_local': route.isLocal,
55       'custom_controller_path': route.customControllerPath,
56     });
57   }
59   /**
60    * Converts a route message to a RouteMessage Mojo object.
61    * @param {!RouteMessage} message
62    * @return {!mediaRouterMojom.RouteMessage} A Mojo RouteMessage object.
63    */
64   function messageToMojo_(message) {
65     if ("string" == typeof message.message) {
66       return new mediaRouterMojom.RouteMessage({
67         'type': mediaRouterMojom.RouteMessage.Type.TEXT,
68         'message': message.message,
69       });
70     } else {
71       return new mediaRouterMojom.RouteMessage({
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(string, Uint8Array): Promise}
320      */
321     this.sendRouteBinaryMessage = null;
323     /**
324      * @type {function(string):
325      *     Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
326      */
327     this.listenForRouteMessages = null;
329     /**
330      * @type {function(string)}
331      */
332     this.stopListeningForRouteMessages = null;
334     /**
335      * @type {function(string)}
336      */
337     this.onPresentationSessionDetached = null;
339     /**
340      * @type {function()}
341      */
342     this.startObservingMediaRoutes = null;
344     /**
345      * @type {function()}
346      */
347     this.stopObservingMediaRoutes = null;
348   };
350   /**
351    * Routes calls from Media Router to the provider manager extension.
352    * Registered with the MediaRouter stub.
353    * @param {!MediaRouter} MediaRouter proxy to call into the
354    * Media Router mojo interface.
355    * @constructor
356    */
357   function MediaRouteProvider(mediaRouter) {
358     mediaRouterMojom.MediaRouteProvider.stubClass.call(this);
360     /**
361      * Object containing JS callbacks into Provider Manager code.
362      * @type {!MediaRouterHandlers}
363      */
364     this.handlers_ = new MediaRouterHandlers();
366     /**
367      * Proxy class to the browser's Media Router Mojo service.
368      * @type {!MediaRouter}
369      */
370     this.mediaRouter_ = mediaRouter;
371   }
372   MediaRouteProvider.prototype = Object.create(
373       mediaRouterMojom.MediaRouteProvider.stubClass.prototype);
375   /*
376    * Sets the callback handler used to invoke methods in the provider manager.
377    *
378    * TODO(mfoltz): Rename to something more explicit?
379    * @param {!MediaRouterHandlers} handlers
380    */
381   MediaRouteProvider.prototype.setHandlers = function(handlers) {
382     this.handlers_ = handlers;
383     var requiredHandlers = [
384       'stopObservingMediaRoutes',
385       'startObservingMediaRoutes',
386       'sendRouteMessage',
387       'sendRouteBinaryMessage',
388       'listenForRouteMessages',
389       'stopListeningForRouteMessages',
390       'onPresentationSessionDetached',
391       'closeRoute',
392       'joinRoute',
393       'createRoute',
394       'stopObservingMediaSinks',
395       'startObservingMediaRoutes'
396     ];
397     requiredHandlers.forEach(function(nextHandler) {
398       if (handlers[nextHandler] === undefined) {
399         console.error(nextHandler + ' handler not registered.');
400       }
401     });
402   }
404   /**
405    * Starts querying for sinks capable of displaying the media source
406    * designated by |sourceUrn|.  Results are returned by calling
407    * OnSinksReceived.
408    * @param {!string} sourceUrn
409    */
410   MediaRouteProvider.prototype.startObservingMediaSinks =
411       function(sourceUrn) {
412     this.handlers_.startObservingMediaSinks(sourceUrn);
413   };
415   /**
416    * Stops querying for sinks capable of displaying |sourceUrn|.
417    * @param {!string} sourceUrn
418    */
419   MediaRouteProvider.prototype.stopObservingMediaSinks =
420       function(sourceUrn) {
421     this.handlers_.stopObservingMediaSinks(sourceUrn);
422   };
424   /**
425    * Requests that |sinkId| render the media referenced by |sourceUrn|. If the
426    * request is from the Presentation API, then origin and tabId will
427    * be populated.
428    * @param {!string} sourceUrn Media source to render.
429    * @param {!string} sinkId Media sink ID.
430    * @param {!string} presentationId Presentation ID from the site
431    *     requesting presentation. TODO(mfoltz): Remove.
432    * @param {!string} origin Origin of site requesting presentation.
433    * @param {!number} tabId ID of tab requesting presentation.
434    * @return {!Promise.<!Object>} A Promise resolving to an object describing
435    *     the newly created media route, or rejecting with an error message on
436    *     failure.
437    */
438   MediaRouteProvider.prototype.createRoute =
439       function(sourceUrn, sinkId, presentationId, origin, tabId) {
440     return this.handlers_.createRoute(
441         sourceUrn, sinkId, presentationId, origin, tabId)
442         .then(function(route) {
443           // Sink name is not used, so it is omitted here.
444           return {route: routeToMojo_(route, "")};
445         }.bind(this))
446         .catch(function(err) {
447           return {error_text: 'Error creating route: ' + err.message};
448         });
449   };
451   /**
452    * Handles a request via the Presentation API to join an existing route given
453    * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
454    * validating same-origin/tab scope.
455    * @param {!string} sourceUrn Media source to render.
456    * @param {!string} presentationId Presentation ID to join.
457    * @param {!string} origin Origin of site requesting join.
458    * @param {!number} tabId ID of tab requesting join.
459    * @return {!Promise.<!Object>} A Promise resolving to an object describing
460    *     the newly created media route, or rejecting with an error message on
461    *     failure.
462    */
463   MediaRouteProvider.prototype.joinRoute =
464       function(sourceUrn, presentationId, origin, tabId) {
465     return this.handlers_.joinRoute(sourceUrn, presentationId, origin, tabId)
466         .then(function(newRoute) {
467           // Sink name is not used, so it is omitted here.
468           return {route: routeToMojo_(newRoute, "")};
469         },
470         function(err) {
471           return {error_text: 'Error joining route: ' + err.message};
472         });
473   };
475   /**
476    * Closes the route specified by |routeId|.
477    * @param {!string} routeId
478    */
479   MediaRouteProvider.prototype.closeRoute = function(routeId) {
480     this.handlers_.closeRoute(routeId);
481   };
483   /**
484    * Posts a message to the route designated by |routeId|.
485    * @param {!string} routeId
486    * @param {!string} message
487    * @return {!Promise.<boolean>} Resolved with true if the message was sent,
488    *    or false on failure.
489    */
490   MediaRouteProvider.prototype.sendRouteMessage = function(
491       routeId, message) {
492     return this.handlers_.sendRouteMessage(routeId, message)
493         .then(function() {
494           return {'sent': true};
495         }, function() {
496           return {'sent': false};
497         });
498   };
500   /**
501    * Sends a binary message to the route designated by |routeId|.
502    * @param {!string} routeId
503    * @param {!Uint8Array} data
504    * @return {!Promise.<boolean>} Resolved with true if the data was sent,
505    *    or false on failure.
506    */
507   MediaRouteProvider.prototype.sendRouteBinaryMessage = function(
508       routeId, data) {
509     return this.handlers_.sendRouteBinaryMessage(routeId, data)
510         .then(function() {
511           return {'sent': true};
512         }, function() {
513           return {'sent': false};
514         });
515   };
517   /**
518    * Listen for next batch of messages from one of the routeIds.
519    * @param {!string} routeId
520    * @return {!Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
521    *     Resolved with a list of messages, and a boolean indicating if an error
522    *     occurred.
523    */
524   MediaRouteProvider.prototype.listenForRouteMessages = function(routeId) {
525     return this.handlers_.listenForRouteMessages(routeId)
526         .then(function(messages) {
527           return {'messages': messages.map(messageToMojo_), 'error': false};
528         }, function() {
529           return {'messages': [], 'error': true};
530         });
531   };
533   /**
534    * If there is an outstanding |listenForRouteMessages| promise for
535    * |routeId|, resolve that promise with an empty array.
536    * @param {!string} routeId
537    */
538   MediaRouteProvider.prototype.stopListeningForRouteMessages = function(
539       routeId) {
540     return this.handlers_.stopListeningForRouteMessages(routeId);
541   };
543   /**
544    * Indicates that the presentation session that was connected to |routeId| is
545    * no longer connected to it.
546    * @param {!string} routeId
547    */
548   MediaRouteProvider.prototype.onPresentationSessionDetached = function(
549       routeId) {
550     this.handlers_.onPresentationSessionDetached(routeId);
551   };
553   /**
554    * Requests that the provider manager start sending information about active
555    * media routes to the Media Router.
556    */
557   MediaRouteProvider.prototype.startObservingMediaRoutes = function() {
558     this.handlers_.startObservingMediaRoutes();
559   };
561   /**
562    * Requests that the provider manager stop sending information about active
563    * media routes to the Media Router.
564    */
565   MediaRouteProvider.prototype.stopObservingMediaRoutes = function() {
566     this.handlers_.stopObservingMediaRoutes();
567   };
569   mediaRouter = new MediaRouter(connector.bindHandleToProxy(
570       serviceProvider.connectToService(
571           mediaRouterMojom.MediaRouter.name),
572       mediaRouterMojom.MediaRouter));
574   return mediaRouter;