Chromium Code Reviews| Index: extensions/renderer/resources/media_router_bindings.js | 
| diff --git a/extensions/renderer/resources/media_router_bindings.js b/extensions/renderer/resources/media_router_bindings.js | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..8cbf0b0c0c83ad33762efad6cfa66095ea7a9419 | 
| --- /dev/null | 
| +++ b/extensions/renderer/resources/media_router_bindings.js | 
| @@ -0,0 +1,503 @@ | 
| +// Copyright 2015 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +var mediaRouterObserver; | 
| + | 
| +define('media_router_bindings', [ | 
| + 'mojo/public/js/bindings', | 
| + 'mojo/public/js/core', | 
| + 'content/public/renderer/service_provider', | 
| + 'chrome/browser/media/router/media_router.mojom', | 
| + 'extensions/common/mojo/keep_alive.mojom', | 
| + 'mojo/public/js/connection', | 
| + 'mojo/public/js/router', | 
| +], function(bindings, | 
| + core, | 
| + serviceProvider, | 
| + mediaRouterMojom, | 
| + keepAliveMojom, | 
| + connector, | 
| + routerModule) { | 
| + 'use strict'; | 
| + | 
| + | 
| + /** | 
| + * Converts a sink object to a MediaSink Mojo object. | 
| + * @param {!Object} sink | 
| + * @return {!mediaRouterMojom.MediaSink} | 
| + */ | 
| + function sinkToMojo_(function(sink) { | 
| + return new mediaRouterMojom.MediaSink({ | 
| + 'name': sink.friendlyName, | 
| + 'sink_id': sink.id, | 
| + }); | 
| + } | 
| + | 
| + | 
| + /** | 
| + * Converts a route struct to its Mojo form. | 
| + * | 
| + * @param {!MediaRoute} route | 
| + * @param {!string=} opt_sinkName | 
| + * @return {!mojo.MediaRoute} | 
| + */ | 
| + function routeToMojo_(route, opt_sinkName) { | 
| + return new mediaRouterMojom.MediaRoute({ | 
| + 'media_route_id': route.id, | 
| + 'media_source': route.mediaSource, | 
| + 'media_sink': new mediaRouterMojom.MediaSink({ | 
| + 'sink_id': route.sinkId, | 
| + 'name': opt_sinkName, | 
| + }), | 
| + 'description': route.description, | 
| + 'icon_url': route.iconUrl, | 
| + 'is_local': route.isLocal | 
| + }); | 
| + } | 
| + | 
| + /** | 
| + * @param {!MediaRouterService} service | 
| + * @constructor | 
| + */ | 
| + function MediaRouterObserver(service) { | 
| + /** | 
| + * The Mojo service proxy. Allows extension code to call methods that reside | 
| + * in the browser. | 
| + * @type {!MediaRouterService} | 
| + */ | 
| + this.observer_ = service; | 
| 
 
Devlin
2015/06/04 23:01:19
Wait, why does a MediaRouterObserver have an obser
 
Kevin M
2015/06/08 17:32:59
Done.
 
 | 
| + | 
| + /** | 
| + * The MRPM service delegate. Its methods are called by the browser-resident | 
| + * Mojo service. | 
| + * @type {!MediaRouter} | 
| + */ | 
| + this.mrpm_ = new MediaRouter(this); | 
| 
 
Devlin
2015/06/04 23:01:19
Also quite odd for the MediaRouterObserver to own
 
Kevin M
2015/06/08 17:32:59
Agreed with the awkward names. We'll do a quality
 
 | 
| + | 
| + /** | 
| + * The message pipe that connects the Media Router to mrpm_ across | 
| + * browser/renderer IPC boundaries. | 
| + * Object must remain in scope for the lifetime of the connection to | 
| + * prevent the connection from closing automatically. | 
| + * @type {!mojo.MessagePipe} | 
| + */ | 
| + this.pipe_ = core.createMessagePipe(); | 
| + | 
| + /** | 
| + * Handle to a KeepAlive service object, which prevents the extension from | 
| + * being suspended as long as it remains in scope | 
| + * @type {boolean} | 
| + */ | 
| + this.keepAlive_ = null; | 
| + | 
| + // Define the stub used to bind this.mrpm_ to the Mojo interface. | 
| 
 
Devlin
2015/06/04 22:42:25
jsdoc-style comments
 
 | 
| + // Object must remain in scope for the lifetime of the connection to | 
| + // prevent the connection from closing automatically. | 
| + // @type {!mojom.MediaRouter} | 
| + this.mediaRouterStub_ = connector.bindHandleToStub( | 
| + this.pipe_.handle0, mediaRouterMojom.MediaRouter); | 
| + | 
| + // Link the stub to impl code. | 
| + bindings.StubBindings(this.mediaRouterStub_).delegate = this.mrpm_; | 
| + } | 
| + | 
| + | 
| + /** | 
| + * Register the Media Router Provider Manager with the Media Router. | 
| 
 
Devlin
2015/06/04 22:42:25
nit: function comments should be descriptive, i.e.
 
Kevin M
2015/06/08 17:32:59
Thanks, done.
 
 | 
| + * @return {!Promise<string>} A unique, string-based ID for this instance | 
| + * of the Media Router. | 
| + */ | 
| + MediaRouterObserver.prototype.start = function() { | 
| + return this.observer_.provideMediaRouter(this.pipe_.handle1).then( | 
| + function(result) { | 
| + return result.instance_id; | 
| + }.bind(this)); | 
| + } | 
| + | 
| + | 
| + /** | 
| + * Sets the service delegate methods. | 
| + * @param {Object} | 
| 
 
Devlin
2015/06/04 22:42:26
handlers
 
Kevin M
2015/06/08 17:32:59
Done.
 
 | 
| + */ | 
| + MediaRouterObserver.prototype.setHandlers = function(handlers) { | 
| + this.mrpm_.setHandlers(handlers); | 
| + } | 
| + | 
| + | 
| + /** | 
| + * Gets the keep alive status. | 
| + * @return {boolean} | 
| + */ | 
| + MediaRouterObserver.prototype.getKeepAlive = function() { | 
| + return this.keepAlive_ != null; | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Called by the Provider Manager when a sink list for a given source is | 
| + * updated. | 
| + * @param {!string} sourceUrn | 
| + * @param {!Array<!Object>} sinks | 
| + */ | 
| + MediaRouterObserver.prototype.onSinksReceived = function(sourceUrn, sinks) { | 
| + this.observer_.onSinksReceived(sourceUrn, | 
| + sinks.map(sinkToMojo_)); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Called by the Provider Manager to keep the extension from suspending | 
| + * if it enters a state where suspension is undesirable (e.g. there is an | 
| + * active MediaRoute.) | 
| + * If keepAlive is true, the extension is kept alive. | 
| + * If keepAlive is false, the extension is allowed to suspend. | 
| + * | 
| + * @param {boolean} keepAlive | 
| + */ | 
| + MediaRouterObserver.prototype.setKeepAlive = function(keepAlive) { | 
| + if (keepAlive === false && this.keepAlive_) { | 
| + this.keepAlive_.close(); | 
| + this.keepAlive_ = null; | 
| + } else if (keepAlive === true && !this.keepAlive_) { | 
| + this.keepAlive_ = new routerModule.Router( | 
| + serviceProvider.connectToService( | 
| + keepAliveMojom.KeepAlive.name)); | 
| + } | 
| + }; | 
| + | 
| 
 
Devlin
2015/06/04 22:42:25
I don't think we have the double-newline style.
 
Kevin M
2015/06/08 17:32:59
Done.
 
 | 
| + | 
| + /** | 
| + * Sends a message to an active media route. | 
| + * | 
| 
 
Devlin
2015/06/04 23:01:19
remove (and below)
 
Kevin M
2015/06/08 17:32:58
Done.
 
 | 
| + * @param {!string} routeId | 
| + * @param {!Object|string} message A message that can be converted to a JSON | 
| + * string. | 
| + */ | 
| + MediaRouterObserver.prototype.onMessage = function(routeId, message) { | 
| + this.observer_.onMessage(routeId, JSON.stringify(message)); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Reports an issue to the Media Router. | 
| + * | 
| + * @param {!Object} issue | 
| + */ | 
| + MediaRouterObserver.prototype.onIssue = function(issue) { | 
| + function issueSeverityToMojo_(severity) { | 
| + switch (severity) { | 
| + case 'fatal': | 
| + return mediaRouterMojom.Issue.Severity.FATAL; | 
| + case 'warning': | 
| + return mediaRouterMojom.Issue.Severity.WARNING; | 
| + case 'notification': | 
| + return mediaRouterMojom.Issue.Severity.NOTIFICATION; | 
| + default: | 
| + console.error('Unknown issue severity: ' + severity); | 
| + return mediaRouterMojom.Issue.Severity.NOTIFICATION; | 
| + } | 
| + } | 
| + | 
| + function issueActionToMojo_(action) { | 
| + switch (action) { | 
| + case 'ok': | 
| + return mediaRouterMojom.Issue.ActionType.OK; | 
| + case 'cancel': | 
| + return mediaRouterMojom.Issue.ActionType.CANCEL; | 
| + case 'dismiss': | 
| + return mediaRouterMojom.Issue.ActionType.DISMISS; | 
| + case 'learn_more': | 
| + return mediaRouterMojom.Issue.ActionType.LEARN_MORE; | 
| + default: | 
| + console.error('Unknown issue action type : ' + action); | 
| + return mediaRouterMojom.Issue.ActionType.OK; | 
| + } | 
| + } | 
| + | 
| + var secondaryActions = (issue.secondaryActions || []).map(function(e) { | 
| + return issueActionToMojo_(e); | 
| + }); | 
| + this.observer_.onIssue(new mediaRouterMojom.Issue({ | 
| + 'route_id': issue.routeId, | 
| + 'severity': issueSeverityToMojo_(issue.severity), | 
| + 'title': issue.title, | 
| + 'message': issue.message, | 
| + 'default_action': issueActionToMojo_(issue.defaultAction), | 
| + 'secondary_actions': secondaryActions, | 
| + 'help_url': issue.helpUrl, | 
| + 'is_blocking': issue.isBlocking | 
| + })); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Called by the Provider Manager when the list of active routes | 
| + * has changed. | 
| + * @param {!Array<MediaRoute>} routes | 
| + * @param {!Array<MediaSink>} routes | 
| 
 
Devlin
2015/06/04 23:01:19
sinks?
 
Kevin M
2015/06/08 17:32:58
Done.
 
 | 
| + */ | 
| + MediaRouterObserver.prototype.onRoutesUpdated = function(routes, sinks) { | 
| + // Create an inverted index relating sink IDs to their names. | 
| + var sinkNameMap = {}; | 
| + for (var i = 0; i < sinks.length; i++) { | 
| 
 
Devlin
2015/06/04 23:01:19
nit: forEach() is surprisingly faster than for (va
 
Kevin M
2015/06/08 17:32:58
Is this still the case? I ran this benchmark suite
 
Devlin
2015/06/08 19:51:41
Huh.  Used to be the inverse.  Also, it's importan
 
 | 
| + sinkNameMap[sinks[i].id] = sinks[i].friendlyName; | 
| + } | 
| + | 
| + // Convert MediaRoutes to Mojo objects and add their sink names | 
| + // via sinkNameMap. | 
| + var mojoRoutes = []; | 
| + for (var j = 0; j < routes.length; j++) { | 
| + mojoRoutes.push(routeToMojo_(routes[j], sinkNameMap[routes[j].sinkId])); | 
| + } | 
| + | 
| + this.observer_.onRoutesUpdated( | 
| + mojoRoutes, | 
| + sinks.map(MediaRouterObserver.sinkToMojo_)); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Called by the Provider Manager when an error was encountered for a media | 
| + * route. | 
| + * | 
| + * @param {!string} requestId The ID of the route request which experienced an | 
| + * error. | 
| + * @param {!string} error | 
| + */ | 
| + MediaRouterObserver.prototype.onRouteResponseError = | 
| + function(requestId, error) { | 
| + this.observer_.onRouteResponseError(requestId, error); | 
| + }; | 
| + | 
| + | 
| + MediaRouterObserver.prototype.onRouteResponseReceived = | 
| + function(requestId, routeId) { | 
| + this.observer_.onRouteResponseReceived(requestId, routeId); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Object containing JS callbacks into Provider Manager code. | 
| + * @constructor | 
| + * @struct | 
| + */ | 
| + function MediaRouterHandlers() { | 
| + /** | 
| + * @type {function(!string, !string, !string=, !string=, !number=} | 
| + */ | 
| + this.createRoute = null; | 
| + | 
| + /** | 
| + * @type {function(!string, !string, !string, !number)} | 
| + */ | 
| + this.joinRoute = null; | 
| + | 
| + /** | 
| + * @type {function(string)} | 
| + */ | 
| + this.closeRoute = null; | 
| + | 
| + /** | 
| + * @type {function(string)} | 
| + */ | 
| + this.startObservingMediaSinks = null; | 
| + | 
| + /** | 
| + * @type {function(string)} | 
| + */ | 
| + this.stopObservingMediaSinks = null; | 
| + | 
| + /** | 
| + * @type {function(string, string, string)} | 
| + */ | 
| + this.postMessage = null; | 
| + | 
| + /** | 
| + * @type {function()} | 
| + */ | 
| + this.startObservingMediaRoutes = null; | 
| + | 
| + /** | 
| + * @type {function()} | 
| + */ | 
| + this.stopObservingMediaRoutes = null; | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Routes calls from Media Router to the Provider Manager extension. | 
| + * Registered with the MediaRouter stub. | 
| + * | 
| + * @constructor | 
| + */ | 
| + function MediaRouter(mediaRouterObserver) { | 
| + /** | 
| + * Object containing JS callbacks into Provider Manager code. | 
| + * @type {!MediaRouterHandlers} | 
| + */ | 
| + this.handlers_ = new MediaRouterHandlers(); | 
| + | 
| + /** | 
| + * Proxy class to the browser's Media Router Mojo service. | 
| + * @type {!MediaRouterObserver} | 
| + */ | 
| + this.mediaRouter_ = mediaRouterObserver; | 
| 
 
Devlin
2015/06/04 23:01:19
Why is |mediaRouter_| a mediaRouterObserver?  And
 
Kevin M
2015/06/08 17:32:58
A previous code reviewer thought it strange to hav
 
 | 
| + } | 
| + MediaRouter.prototype = Object.create( | 
| + mediaRouterMojom.MediaRouter.stubClass.prototype); | 
| + | 
| + | 
| + /* | 
| + * Sets the callback handler used to invoke methods in the Provider Manager. | 
| + * @param {!MediaRouterHandlers} handlers | 
| + */ | 
| + MediaRouter.prototype.setHandlers = function(handlers) { | 
| + this.handlers_ = handlers; | 
| + if (!this.handlers_.stopObservingMediaRoutes) { | 
| 
 
Devlin
2015/06/04 23:01:19
var requiredHandlers = ['stopObservingMediaRouters
 
Kevin M
2015/06/08 17:32:59
Done.
 
 | 
| + console.error('stopObservingMediaRoutes handler not registered.'); | 
| + return; | 
| + } | 
| + if (!this.handlers_.startObservingMediaRoutes) { | 
| + console.error('startObservingMediaRoutes handler not registered.'); | 
| + return; | 
| + } | 
| + if (!this.handlers_.postMessage) { | 
| + console.error('postMessage handler not registered.'); | 
| + return; | 
| + } | 
| + if (!this.handlers_.closeRoute) { | 
| + console.error('closeRoute handler not registered.'); | 
| + return; | 
| + } | 
| + if (!this.handlers_.joinRoute) { | 
| + console.error('joinRoute handler not registered.'); | 
| + return; | 
| + } | 
| + if (!this.handlers_.createRoute) { | 
| + console.error('createRoute handler not registered.'); | 
| + return; | 
| + } | 
| + if (!this.handlers_.stopObservingMediaSinks) { | 
| + console.error('stopObservingMediaSinks handler not registered.'); | 
| + return; | 
| + } | 
| + if ( !this.handlers_.startObservingMediaSinks) { | 
| + console.error('startObservingMediaSinks handler not registered.'); | 
| + return; | 
| + } | 
| + } | 
| + | 
| + | 
| + /** | 
| + * Starts querying for sinks capable of displaying the media |sourceUrn|. | 
| + * Results are returned by calling OnSinksReceived. | 
| + * @param {!string} sourceUrn | 
| + */ | 
| + MediaRouter.prototype.startObservingMediaSinks = | 
| + function(sourceUrn) { | 
| + this.handlers_.startObservingMediaSinks(sourceUrn); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Stops querying for sinks capable of displaying the media |sourceUrn|. | 
| + * @param {!string} sourceUrn | 
| + */ | 
| + MediaRouter.prototype.stopObservingMediaSinks = | 
| + function(sourceUrn) { | 
| + this.handlers_.stopObservingMediaSinks(sourceUrn); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Requests that |sinkId| render the media referenced by |sourceUrn|. | 
| + * @param {!string} sourceUrn | 
| + * @param {!string} sinkId | 
| + * @param {!string=} opt_presentationId | 
| + * @param {!string=} opt_origin | 
| + * @param {!number=} opt_tabId | 
| + * @return {!Promise.<!Object>} | 
| 
 
Devlin
2015/06/04 23:01:19
Kind of a shame that this can't be a Promise<mojo.
 
Kevin M
2015/06/08 17:32:59
I agree, but Mojo's JS binding library doesn't hav
 
Devlin
2015/06/08 19:51:41
Oh, bummer.
 
 | 
| + */ | 
| + MediaRouter.prototype.createRoute = | 
| + function(sourceUrn, sinkId, opt_presentationId, opt_origin, opt_tabId) { | 
| + return this.handlers_.createRoute( | 
| + sourceUrn, sinkId, opt_presentationId, opt_origin, opt_tabId) | 
| + .then(function(route) { | 
| + // Sink name is not used, so it is omitted here. | 
| + return {route: routeToMojo_(route, "")}; | 
| + }.bind(this)) | 
| + .catch(function(err) { | 
| + return {error_text: err.message}; | 
| + }); | 
| + }; | 
| + | 
| + /** | 
| + * Join an existing route given by |presentationId| and render media | 
| + * referenced by |sourceUrn|. |origin| and |tabId| are used for checking | 
| + * origin/tab scope. | 
| + * @param {!string} sourceUrn | 
| + * @param {!string} presentationId | 
| + * @param {!string} origin | 
| + * @param {!number} tabId | 
| + * @return {!Promise.<!Object>} Resolved with the route on success, | 
| + * or with an error message on failure. | 
| + */ | 
| + MediaRouter.prototype.joinRoute = | 
| + function(sourceUrn, presentationId, origin, tabId) { | 
| + return this.handlers_.joinRoute(sourceUrn, presentationId, origin, tabId) | 
| + .then(function(newRoute) { | 
| + return {route: routeToMojo_(newRoute)}; | 
| + }, | 
| + function(err) { | 
| + return {error_text: 'Error joining route: ' + err.message}; | 
| + }); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Closes route specified by |routeId| | 
| + * @param {!string} routeId | 
| + */ | 
| + MediaRouter.prototype.closeRoute = function(routeId) { | 
| + this.handlers_.closeRoute(routeId); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Posts message to a sink connected by route with |routeId|. | 
| + * @param {!string} routeId | 
| + * @param {!string} message | 
| + * @param {string} extraInfoJson | 
| + */ | 
| + MediaRouter.prototype.postMessage = function( | 
| + routeId, message, extraInfoJson) { | 
| + this.handlers_.postMessage(routeId, message, JSON.parse(extraInfoJson)); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Requests that the Provider Manager start sending information about active | 
| + * media routes to the Media Router. | 
| + */ | 
| + MediaRouter.prototype.startObservingMediaRoutes = function() { | 
| + this.handlers_.startObservingMediaRoutes(); | 
| + }; | 
| + | 
| + | 
| + /** | 
| + * Requests that the Provider Manager stop sending information about active | 
| + * media routes to the Media Router. | 
| + */ | 
| + MediaRouter.prototype.stopObservingMediaRoutes = function() { | 
| + this.handlers_.stopObservingMediaRoutes(); | 
| + }; | 
| + | 
| + mediaRouterObserver = new MediaRouterObserver(connector.bindHandleToProxy( | 
| + serviceProvider.connectToService( | 
| + mediaRouterMojom.MediaRouterObserver.name), | 
| + mediaRouterMojom.MediaRouterObserver)); | 
| + | 
| + return mediaRouterObserver; | 
| +}); | 
| + |