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..f28c354d2df7241a7ed0571a243be1b7e2a13884 |
--- /dev/null |
+++ b/extensions/renderer/resources/media_router_bindings.js |
@@ -0,0 +1,482 @@ |
+// 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 media sink to a MediaSink Mojo object. |
+ * @param {!MediaSink} sink A media sink. |
+ * @return {!mediaRouterMojom.MediaSink} A Mojo MediaSink object. |
+ */ |
+ function sinkToMojo_(sink) { |
+ return new mediaRouterMojom.MediaSink({ |
+ 'name': sink.friendlyName, |
+ 'sink_id': sink.id, |
+ }); |
+ } |
+ |
+ /** |
+ * Returns a Mojo MediaRoute object given a MediaRoute and a |
+ * media sink name. |
+ * @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 |
+ }); |
+ } |
+ |
+ /** |
+ * Creates a new MediaRouterObserver. |
+ * Converts a route struct to its Mojo form. |
+ * @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.service_ = service; |
+ |
+ /** |
+ * The provider manager service delegate. Its methods are called by the |
+ * browser-resident Mojo service. |
+ * @type {!MediaRouter} |
+ */ |
+ this.mrpm_ = new MediaRouter(this); |
+ |
+ /** |
+ * 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; |
+ |
+ /** |
+ * The stub used to bind the service delegate to the Mojo interface. |
+ * 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 mediaRouterStub_ to the provider manager delegate. |
+ bindings.StubBindings(this.mediaRouterStub_).delegate = this.mrpm_; |
+ } |
+ |
+ /** |
+ * Registers the Media Router Provider Manager with the Media Router. |
+ * @return {!Promise<string>} Instance ID for the Media Router. |
+ */ |
+ MediaRouterObserver.prototype.start = function() { |
+ return this.service_.provideMediaRouter(this.pipe_.handle1).then( |
+ function(result) { |
+ return result.instance_id; |
+ }.bind(this)); |
+ } |
+ |
+ /** |
+ * Sets the service delegate methods. |
+ * @param {Object} handlers |
+ */ |
+ MediaRouterObserver.prototype.setHandlers = function(handlers) { |
+ this.mrpm_.setHandlers(handlers); |
+ } |
+ |
+ /** |
+ * 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<!MediaSink>} sinks |
+ */ |
+ MediaRouterObserver.prototype.onSinksReceived = function(sourceUrn, sinks) { |
+ this.service_.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)); |
+ } |
+ }; |
+ |
+ /** |
+ * Sends a message to an active media route. |
+ * @param {!string} routeId |
+ * @param {!Object|string} message A message that can be converted to a JSON |
+ * string. |
+ */ |
+ MediaRouterObserver.prototype.onMessage = function(routeId, message) { |
+ // TODO(mfoltz): Handle binary messages (ArrayBuffer, Blob). |
+ this.service_.onMessage(routeId, JSON.stringify(message)); |
+ }; |
+ |
+ /** |
+ * Called by the provider manager to send an issue from a media route |
+ * provider to the Media Router, to show the user. |
+ * @param {!Object} issue The issue object. |
+ */ |
+ 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.service_.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 set of active routes |
+ * has been updated. |
+ * @param {!Array<MediaRoute>} routes The active set of media routes. |
+ * @param {!Array<MediaSink>} sinks The active set of media sinks. |
+ */ |
+ 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++) { |
+ sinkNameMap[sinks[i].id] = sinks[i].friendlyName; |
+ } |
+ |
+ // Convert MediaRoutes to Mojo objects and add their sink names |
+ // via sinkNameMap. |
+ var mojoRoutes = routes.map(function(route) { |
+ return routeToMojo_(routes[j], sinkNameMap[routes[j].sinkId]); |
+ }); |
+ |
+ this.service_.onRoutesUpdated( |
+ mojoRoutes, |
+ sinks.map(MediaRouterObserver.sinkToMojo_)); |
+ }; |
+ |
+ /** |
+ * Called by the Provider Manager when an error was encountered in response |
+ * to a media route creation request. |
+ * @param {!string} requestId The request id. |
+ * @param {!string} error The error. |
+ */ |
+ MediaRouterObserver.prototype.onRouteResponseError = |
+ function(requestId, error) { |
+ this.service_.onRouteResponseError(requestId, error); |
+ }; |
+ |
+ /** |
+ * Called by the provider manager when a route was able to be created by a |
+ * media route provider. |
+ * |
+ * @param {string} requestId The media route request id. |
+ * @param {string} routeId The id of the media route that was created. |
+ */ |
+ MediaRouterObserver.prototype.onRouteResponseReceived = |
+ function(requestId, routeId) { |
+ this.service_.onRouteResponseReceived(requestId, routeId); |
+ }; |
+ |
+ /** |
+ * Object containing callbacks set by the provider manager. |
+ * TODO(mfoltz): Better named ProviderManagerDelegate? |
+ * |
+ * @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. |
+ * @param {!MediaRouterObserver} mediaRouterObserver API to call into the |
+ * Media Router mojo interface. |
+ * @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; |
+ } |
+ MediaRouter.prototype = Object.create( |
+ mediaRouterMojom.MediaRouter.stubClass.prototype); |
+ |
+ /* |
+ * Sets the callback handler used to invoke methods in the provider manager. |
+ * |
+ * TODO(mfoltz): Rename to something more explicit? |
+ * @param {!MediaRouterHandlers} handlers |
+ */ |
+ MediaRouter.prototype.setHandlers = function(handlers) { |
+ this.handlers_ = handlers; |
+ var requiredHandlers = [ |
+ 'stopObservingMediaRoutes', |
+ 'startObservingMediaRoutes', |
+ 'postMessage', |
+ 'closeRoute', |
+ 'joinRoute', |
+ 'createRoute', |
+ 'stopObservingMediaSinks', |
+ 'startObservingMediaRoutes' |
+ ]; |
+ requiredHandlers.forEach(function(nextHandler) { |
+ if (!handlers.hasOwnProperty(nextHandler)) { |
+ console.error(nextHandler + ' handler not registered.'); |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Starts querying for sinks capable of displaying the media source |
+ * designated by |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 |sourceUrn|. |
+ * @param {!string} sourceUrn |
+ */ |
+ MediaRouter.prototype.stopObservingMediaSinks = |
+ function(sourceUrn) { |
+ this.handlers_.stopObservingMediaSinks(sourceUrn); |
+ }; |
+ |
+ /** |
+ * Requests that |sinkId| render the media referenced by |sourceUrn|. If the |
+ * request is from the Presentation API, then opt_origin and opt_tabId will |
+ * be populated. |
+ * @param {!string} sourceUrn The media source to render. |
+ * @param {!string} sinkId The media sink ID. |
+ * @param {!string=} opt_presentationId Presentation ID from the site |
+ * requesting presentation. TODO(mfoltz): Remove. |
+ * @param {!string=} opt_origin The origin of the site requesting |
+ * presentation. |
+ * @param {!number=} opt_tabId ID of the tab that requested presentation. |
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing |
+ * the newly created media route. |
+ */ |
+ 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}; |
+ }); |
+ }; |
+ |
+ /** |
+ * Handles a request via the Presentation API to join an existing route given |
+ * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used so the |
+ * media route provider can limit the scope by origin or tab. |
+ * @param {!string} sourceUrn The media source to render. |
+ * @param {!string} presentationId The presentation ID to join. |
+ * @param {!string} origin The origin of the site requesting join. |
+ * @param {!number} tabId The ID of the tab requesting join. |
+ * @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 the route specified by |routeId|. |
+ * @param {!string} routeId |
+ */ |
+ MediaRouter.prototype.closeRoute = function(routeId) { |
+ this.handlers_.closeRoute(routeId); |
+ }; |
+ |
+ /** |
+ * Posts a message to the route designated by |routeId|. |
+ * @param {!string} routeId |
+ * @param {!string} message |
+ * @param {string} extraInfoJson |
+ */ |
+ MediaRouter.prototype.postMessage = function( |
+ routeId, message, extraInfoJson) { |
+ // TODO(mfoltz): Remove extraInfoJson if no longer needed. |
+ 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; |
+}); |
+ |