Extensions: convert media_router_bindings.js to use new Mojo JS bindings.

This CL also changes the mojoPrivate API to be simply an async wrapper around
require().

Bug: 699569, 718047
Change-Id: I271887d1cc1eb49fd2faefff47467c47be23a3be
Reviewed-on: https://chromium-review.googlesource.com/783645
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Reviewed-by: Derek Cheng <imcheng@chromium.org>
Reviewed-by: Ken Rockot <rockot@chromium.org>
Commit-Queue: Yuzhu Shen <yzshen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#521129}
diff --git a/chrome/common/media_router/mojo/BUILD.gn b/chrome/common/media_router/mojo/BUILD.gn
index 0dfaec4..63f946b 100644
--- a/chrome/common/media_router/mojo/BUILD.gn
+++ b/chrome/common/media_router/mojo/BUILD.gn
@@ -13,9 +13,6 @@
   public_deps = [
     "//mojo/common:common_custom_types",
   ]
-
-  # TODO(crbug.com/699569): Convert to use the new JS bindings.
-  use_new_js_bindings = false
 }
 
 mojom("media_router") {
@@ -31,9 +28,6 @@
     "//url/mojo:url_mojom_gurl",
     "//url/mojo:url_mojom_origin",
   ]
-
-  # TODO(crbug.com/699569): Convert to use the new JS bindings.
-  use_new_js_bindings = false
 }
 
 mojom("media_router_test_interfaces") {
diff --git a/chrome/renderer/resources/extensions/media_router_bindings.js b/chrome/renderer/resources/extensions/media_router_bindings.js
index 9251258..4ce4750 100644
--- a/chrome/renderer/resources/extensions/media_router_bindings.js
+++ b/chrome/renderer/resources/extensions/media_router_bindings.js
@@ -2,935 +2,1391 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-var mediaRouter;
+'use strict';
 
-define('media_router_bindings', [
-    'chrome/common/media_router/mojo/media_controller.mojom',
-    'chrome/common/media_router/mojo/media_router.mojom',
-    'chrome/common/media_router/mojo/media_status.mojom',
-    'content/public/renderer/frame_interfaces',
-    'extensions/common/mojo/keep_alive.mojom',
-    'media/mojo/interfaces/mirror_service_remoting.mojom',
-    'media/mojo/interfaces/remoting_common.mojom',
-    'mojo/common/time.mojom',
-    'mojo/public/js/bindings',
-    'net/interfaces/ip_address.mojom',
-    'net/interfaces/ip_endpoint.mojom',
-    'url/mojo/origin.mojom',
-    'url/mojo/url.mojom',
-], function(mediaControllerMojom,
-            mediaRouterMojom,
-            mediaStatusMojom,
-            frameInterfaces,
-            keepAliveMojom,
-            remotingMojom,
-            remotingCommonMojom,
-            timeMojom,
-            bindings,
-            ipAddressMojom,
-            ipEndpointMojom,
-            originMojom,
-            urlMojom) {
-  'use strict';
+if ((typeof mojo === 'undefined') || !mojo.bindingsLibraryInitialized) {
+  loadScript('mojo_bindings');
+}
+mojo.config.autoLoadMojomDeps = false;
 
-  /**
-   * 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,
-      'description': sink.description,
-      'domain': sink.domain,
-      'sink_id': sink.id,
-      'icon_type': sinkIconTypeToMojo(sink.iconType),
-    });
+loadScript('chrome/common/media_router/mojo/media_controller.mojom');
+loadScript('chrome/common/media_router/mojo/media_router.mojom');
+loadScript('chrome/common/media_router/mojo/media_status.mojom');
+loadScript('extensions/common/mojo/keep_alive.mojom');
+loadScript('media/mojo/interfaces/mirror_service_remoting.mojom');
+loadScript('media/mojo/interfaces/remoting_common.mojom');
+loadScript('mojo/common/time.mojom');
+loadScript('net/interfaces/ip_address.mojom');
+loadScript('net/interfaces/ip_endpoint.mojom');
+loadScript('url/mojo/origin.mojom');
+loadScript('url/mojo/url.mojom');
+
+// The following adapter classes preserve backward compatibility for the media
+// router component extension.
+// TODO(crbug.com/787128): Remove these adapters.
+
+function assignFields(object, fields) {
+  for(var field in fields) {
+    if (object.hasOwnProperty(field))
+      object[field] = fields[field];
+  }
+}
+
+/**
+ * Adapter for mediaRouter.mojom.DialMediaSink.
+ * @constructor
+ */
+function DialMediaSinkAdapter(fields) {
+  this.ip_address = null;
+  this.model_name = null;
+  this.app_url = null;
+
+  assignFields(this, fields);
+}
+
+DialMediaSinkAdapter.fromNewVersion = function(other) {
+  return new DialMediaSinkAdapter({
+    'ip_address': IPAddressAdapter.fromNewVersion(other.ipAddress),
+    'model_name': other.modelName,
+    'app_url': other.appUrl,
+  });
+};
+
+DialMediaSinkAdapter.prototype.toNewVersion = function() {
+  return new mediaRouter.mojom.DialMediaSink({
+    'ipAddress' : this.ip_address.toNewVersion(),
+    'modelName' : this.model_name,
+    'appUrl' : this.app_url,
+  });
+};
+
+/**
+ * Adapter for mediaRouter.mojom.CastMediaSink.
+ * @constructor
+ */
+function CastMediaSinkAdapter(fields) {
+  this.ip_endpoint = null;
+  this.model_name = null;
+  this.capabilities = 0;
+  this.cast_channel_id = 0;
+
+  assignFields(this, fields);
+}
+
+CastMediaSinkAdapter.fromNewVersion = function(other) {
+  return new CastMediaSinkAdapter({
+    'ip_endpoint': IPEndpointAdapter.fromNewVersion(other.ipEndpoint),
+    'model_name': other.modelName,
+    'capabilities': other.capabilities,
+    'cast_channel_id': other.castChannelId,
+  });
+};
+
+CastMediaSinkAdapter.prototype.toNewVersion = function() {
+  return new mediaRouter.mojom.CastMediaSink({
+    'ipEndpoint': this.ip_endpoint.toNewVersion(),
+    'modelName': this.model_name,
+    'capabilities': this.capabilities,
+    'castChannelId': this.cast_channel_id,
+  });
+};
+
+/**
+ * Adapter for mediaRouter.mojom.HangoutsMediaStatusExtraData.
+ * @constructor
+ */
+function HangoutsMediaStatusExtraDataAdapter(fields) {
+  this.local_present = false;
+
+  assignFields(this, fields);
+}
+
+HangoutsMediaStatusExtraDataAdapter.prototype.toNewVersion = function() {
+  return new mediaRouter.mojom.HangoutsMediaStatusExtraData({
+    'localPresent': this.local_present,
+  });
+};
+
+/**
+ * Adapter for net.interfaces.IPAddress.
+ * @constructor
+ */
+function IPAddressAdapter(fields) {
+  this.address_bytes = null;
+
+  assignFields(this, fields);
+}
+
+IPAddressAdapter.fromNewVersion = function(other) {
+  return new IPAddressAdapter({
+    'address_bytes': other.addressBytes,
+  });
+};
+
+IPAddressAdapter.prototype.toNewVersion = function() {
+  return new net.interfaces.IPAddress({
+    'addressBytes': this.address_bytes,
+  });
+};
+
+/**
+ * Adapter for net.interfaces.IPEndpoint.
+ * @constructor
+ */
+function IPEndpointAdapter(fields) {
+  this.address = null;
+  this.port = 0;
+
+  assignFields(this, fields);
+}
+
+IPEndpointAdapter.fromNewVersion = function(other) {
+  return new IPEndpointAdapter({
+    'address': IPAddressAdapter.fromNewVersion(other.address),
+    'port': other.port,
+  });
+};
+
+IPEndpointAdapter.prototype.toNewVersion = function() {
+  return new net.interfaces.IPEndpoint({
+    'address': this.address.toNewVersion(),
+    'port': this.port,
+  });
+};
+
+/**
+ * Adapter for mediaRouter.mojom.MediaStatus.
+ * @constructor
+ */
+function MediaStatusAdapter(fields) {
+  this.title = null;
+  this.description = null;
+  this.can_play_pause = false;
+  this.can_mute = false;
+  this.can_set_volume = false;
+  this.can_seek = false;
+  this.is_muted = false;
+  this.play_state = 0;
+  this.volume = 0;
+  this.duration = null;
+  this.current_time = null;
+  this.hangouts_extra_data = null;
+
+  assignFields(this, fields);
+}
+
+MediaStatusAdapter.PlayState = mediaRouter.mojom.MediaStatus.PlayState;
+
+MediaStatusAdapter.prototype.toNewVersion = function() {
+  return new mediaRouter.mojom.MediaStatus({
+    'title': this.title,
+    'description': this.description,
+    'canPlayPause': this.can_play_pause,
+    'canMute': this.can_mute,
+    'canSetVolume': this.can_set_volume,
+    'canSeek': this.can_seek,
+    'isMuted': this.is_muted,
+    'playState': this.play_state,
+    'volume': this.volume,
+    'duration': this.duration,
+    'currentTime': this.current_time,
+    'hangoutsExtraData':
+        this.hangouts_extra_data && this.hangouts_extra_data.toNewVersion(),
+  });
+};
+
+/**
+ * Adapter for media.mojom.RemotingSinkMetadata.
+ * @constructor
+ */
+function RemotingSinkMetadataAdapter(fields) {
+  this.features = null;
+  this.audio_capabilities = null;
+  this.video_capabilities = null;
+  this.friendly_name = null;
+
+  assignFields(this, fields);
+}
+
+RemotingSinkMetadataAdapter.fromNewVersion = function(other) {
+  return new RemotingSinkMetadataAdapter({
+    'features': other.features,
+    'audio_capabilities': other.audioCapabilities,
+    'video_capabilities': other.videoCapabilities,
+    'friendly_name': other.friendlyName,
+  });
+};
+
+RemotingSinkMetadataAdapter.prototype.toNewVersion = function() {
+  return new media.mojom.RemotingSinkMetadata({
+    'features': this.features,
+    'audioCapabilities': this.audio_capabilities,
+    'videoCapabilities': this.video_capabilities,
+    'friendlyName': this.friendly_name,
+  });
+};
+
+/**
+ * Adapter for mediaRouter.mojom.MediaSink.
+ * @constructor
+ */
+function MediaSinkAdapter(fields) {
+  this.sink_id = null;
+  this.name = null;
+  this.description = null;
+  this.domain = null;
+  this.icon_type = 0;
+  this.extra_data = null;
+
+  assignFields(this, fields);
+}
+
+MediaSinkAdapter.fromNewVersion = function(other) {
+  return new MediaSinkAdapter({
+    'sink_id': other.sinkId,
+    'name': other.name,
+    'description': other.description,
+    'domain': other.domain,
+    'icon_type': other.iconType,
+    'extra_data': other.extraData &&
+        MediaSinkExtraDataAdapter.fromNewVersion(other.extraData),
+  });
+};
+
+MediaSinkAdapter.prototype.toNewVersion = function() {
+  return new mediaRouter.mojom.MediaSink({
+    'sinkId': this.sink_id,
+    'name': this.name,
+    'description': this.description,
+    'domain': this.domain,
+    'iconType': this.icon_type,
+    'extraData': this.extra_data && this.extra_data.toNewVersion(),
+  });
+};
+
+/**
+ * Adapter for mediaRouter.mojom.MediaSinkExtraData.
+ * @constructor
+ */
+function MediaSinkExtraDataAdapter(value) {
+  this.$data = null;
+  this.$tag = undefined;
+
+  if (value == undefined) {
+    return;
   }
 
-  /**
-   * Converts a media sink's icon type to a MediaSink.IconType Mojo object.
-   * @param {!MediaSink.IconType} type A media sink's icon type.
-   * @return {!mediaRouterMojom.MediaSink.IconType} A Mojo MediaSink.IconType
-   *     object.
-   */
-  function sinkIconTypeToMojo(type) {
-    switch (type) {
-      case 'cast':
-        return mediaRouterMojom.SinkIconType.CAST;
-      case 'cast_audio_group':
-        return mediaRouterMojom.SinkIconType.CAST_AUDIO_GROUP;
-      case 'cast_audio':
-        return mediaRouterMojom.SinkIconType.CAST_AUDIO;
-      case 'meeting':
-        return mediaRouterMojom.SinkIconType.MEETING;
-      case 'hangout':
-        return mediaRouterMojom.SinkIconType.HANGOUT;
-      case 'education':
-        return mediaRouterMojom.SinkIconType.EDUCATION;
-      case 'generic':
-        return mediaRouterMojom.SinkIconType.GENERIC;
-      default:
-        console.error('Unknown sink icon type : ' + type);
-        return mediaRouterMojom.SinkIconType.GENERIC;
+  var keys = Object.keys(value);
+  if (keys.length == 0) {
+    return;
+  }
+
+  if (keys.length > 1) {
+    throw new TypeError('You may set only one member on a union.');
+  }
+
+  var fields = [
+    'dial_media_sink',
+    'cast_media_sink',
+  ];
+
+  if (fields.indexOf(keys[0]) < 0) {
+    throw new ReferenceError(keys[0] +
+        ' is not a MediaSinkExtraDataAdapter member.');
+  }
+
+  this[keys[0]] = value[keys[0]];
+}
+
+MediaSinkExtraDataAdapter.Tags = {
+  dial_media_sink: 0,
+  cast_media_sink: 1,
+};
+
+Object.defineProperty(MediaSinkExtraDataAdapter.prototype, 'dial_media_sink', {
+  get: function() {
+    if (this.$tag != MediaSinkExtraDataAdapter.Tags.dial_media_sink) {
+      throw new ReferenceError(
+          'MediaSinkExtraDataAdapter.dial_media_sink is not currently set.');
     }
+    return this.$data;
+  },
+
+  set: function(value) {
+    this.$tag = MediaSinkExtraDataAdapter.Tags.dial_media_sink;
+    this.$data = value;
   }
-
-  /**
-   * Returns a Mojo MediaRoute object given a MediaRoute and a
-   * media sink name.
-   * @param {!MediaRoute} route
-   * @return {!mojo.MediaRoute}
-   */
-  function routeToMojo_(route) {
-    return new mediaRouterMojom.MediaRoute({
-      'media_route_id': route.id,
-      'media_source': route.mediaSource,
-      'media_sink_id': route.sinkId,
-      'description': route.description,
-      'icon_url': route.iconUrl,
-      'is_local': route.isLocal,
-      'custom_controller_path': route.customControllerPath || '',
-      'for_display': route.forDisplay,
-      'is_incognito': route.offTheRecord,
-      'is_local_presentation': route.isOffscreenPresentation,
-      'supports_media_route_controller': route.supportsMediaRouteController,
-      'controller_type': route.controllerType,
-      // Begin newly added properties, followed by the milestone they were
-      // added.  The guard should be safe to remove N+2 milestones later.
-      'presentation_id': route.presentationId || ''  // M64
-    });
-  }
-
-  /**
-   * Converts a route message to a RouteMessage Mojo object.
-   * @param {!RouteMessage} message
-   * @return {!mediaRouterMojom.RouteMessage} A Mojo RouteMessage object.
-   */
-  function messageToMojo_(message) {
-    if ("string" == typeof message.message) {
-      return new mediaRouterMojom.RouteMessage({
-        'type': mediaRouterMojom.RouteMessage.Type.TEXT,
-        'message': message.message,
-      });
-    } else {
-      return new mediaRouterMojom.RouteMessage({
-        'type': mediaRouterMojom.RouteMessage.Type.BINARY,
-        'data': message.message,
-      });
-    }
-  }
-
-  /**
-   * Converts presentation connection state to Mojo enum value.
-   * @param {!string} state
-   * @return {!mediaRouterMojom.MediaRouter.PresentationConnectionState}
-   */
-  function presentationConnectionStateToMojo_(state) {
-    var PresentationConnectionState =
-        mediaRouterMojom.MediaRouter.PresentationConnectionState;
-    switch (state) {
-      case 'connecting':
-        return PresentationConnectionState.CONNECTING;
-      case 'connected':
-        return PresentationConnectionState.CONNECTED;
-      case 'closed':
-        return PresentationConnectionState.CLOSED;
-      case 'terminated':
-        return PresentationConnectionState.TERMINATED;
-      default:
-        console.error('Unknown presentation connection state: ' + state);
-        return PresentationConnectionState.TERMINATED;
-    }
-  }
-
-  /**
-   * Converts presentation connection close reason to Mojo enum value.
-   * @param {!string} reason
-   * @return {!mediaRouterMojom.MediaRouter.PresentationConnectionCloseReason}
-   */
-  function presentationConnectionCloseReasonToMojo_(reason) {
-    var PresentationConnectionCloseReason =
-        mediaRouterMojom.MediaRouter.PresentationConnectionCloseReason;
-    switch (reason) {
-      case 'error':
-        return PresentationConnectionCloseReason.CONNECTION_ERROR;
-      case 'closed':
-        return PresentationConnectionCloseReason.CLOSED;
-      case 'went_away':
-        return PresentationConnectionCloseReason.WENT_AWAY;
-      default:
-        console.error('Unknown presentation connection close reason : ' +
-            reason);
-        return PresentationConnectionCloseReason.CONNECTION_ERROR;
-    }
-  }
-
-  /**
-   * Parses the given route request Error object and converts it to the
-   * corresponding result code.
-   * @param {!Error} error
-   * @return {!mediaRouterMojom.RouteRequestResultCode}
-   */
-  function getRouteRequestResultCode_(error) {
-    return error.errorCode ? error.errorCode :
-      mediaRouterMojom.RouteRequestResultCode.UNKNOWN_ERROR;
-  }
-
-  /**
-   * Creates and returns a successful route response from given route.
-   * @param {!MediaRoute} route
-   * @return {!Object}
-   */
-  function toSuccessRouteResponse_(route) {
-    return {
-        route: routeToMojo_(route),
-        result_code: mediaRouterMojom.RouteRequestResultCode.OK
-    };
-  }
-
-  /**
-   * Creates and returns a error route response from given Error object.
-   * @param {!Error} error
-   * @return {!Object}
-   */
-  function toErrorRouteResponse_(error) {
-    return {
-        error_text: error.message,
-        result_code: getRouteRequestResultCode_(error)
-    };
-  }
-
-  /**
-   * Creates a new MediaRouter.
-   * Converts a route struct to its Mojo form.
-   * @param {!mediaRouterMojom.MediaRouterPtr} service
-   * @constructor
-   */
-  function MediaRouter(service) {
-    /**
-     * The Mojo service proxy. Allows extension code to call methods that reside
-     * in the browser.
-     * @type {!mediaRouterMojom.MediaRouterPtr}
-     */
-    this.service_ = service;
-
-    /**
-     * The provider manager service delegate. Its methods are called by the
-     * browser-resident Mojo service.
-     * @type {!MediaRouter}
-     */
-    this.mrpm_ = new MediaRouteProvider(this);
-
-    /**
-     * 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 bindings 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 {!bindings.Binding}
-     */
-    this.mediaRouteProviderBinding_ = new bindings.Binding(
-        mediaRouterMojom.MediaRouteProvider, this.mrpm_);
-  }
-
-  /**
-   * Returns definitions of Mojo core and generated Mojom classes that can be
-   * used directly by the component.
-   * @return {!Object}
-   * TODO(imcheng): We should export these along with MediaRouter. This requires
-   * us to modify the component to handle multiple exports. When that logic is
-   * baked in for a couple of milestones, we should be able to remove this
-   * method.
-   */
-  MediaRouter.prototype.getMojoExports = function() {
-    return {
-      Binding: bindings.Binding,
-      DialMediaSink: mediaRouterMojom.DialMediaSink,
-      CastMediaSink: mediaRouterMojom.CastMediaSink,
-      HangoutsMediaRouteController:
-          mediaControllerMojom.HangoutsMediaRouteController,
-      HangoutsMediaStatusExtraData:
-          mediaStatusMojom.HangoutsMediaStatusExtraData,
-      IPAddress: ipAddressMojom.IPAddress,
-      IPEndpoint: ipEndpointMojom.IPEndpoint,
-      InterfacePtrController: bindings.InterfacePtrController,
-      InterfaceRequest: bindings.InterfaceRequest,
-      MediaController: mediaControllerMojom.MediaController,
-      MediaStatus: mediaStatusMojom.MediaStatus,
-      MediaStatusObserverPtr: mediaStatusMojom.MediaStatusObserverPtr,
-      MirrorServiceRemoter: remotingMojom.MirrorServiceRemoter,
-      MirrorServiceRemoterPtr: remotingMojom.MirrorServiceRemoterPtr,
-      MirrorServiceRemotingSourcePtr:
-          remotingMojom.MirrorServiceRemotingSourcePtr,
-      RemotingStopReason: remotingCommonMojom.RemotingStopReason,
-      RemotingStartFailReason: remotingCommonMojom.RemotingStartFailReason,
-      RemotingSinkFeature: remotingCommonMojom.RemotingSinkFeature,
-      RemotingSinkAudioCapability:
-          remotingCommonMojom.RemotingSinkAudioCapability,
-      RemotingSinkVideoCapability:
-          remotingCommonMojom.RemotingSinkVideoCapability,
-      RemotingSinkMetadata: remotingCommonMojom.RemotingSinkMetadata,
-      RouteControllerType: mediaRouterMojom.RouteControllerType,
-      Origin: originMojom.Origin,
-      Sink: mediaRouterMojom.MediaSink,
-      SinkExtraData: mediaRouterMojom.MediaSinkExtraData,
-      TimeDelta: timeMojom.TimeDelta,
-      Url: urlMojom.Url,
-      makeRequest: bindings.makeRequest,
-    };
-  };
-
-  /**
-   * Registers the Media Router Provider Manager with the Media Router.
-   * @return {!Promise<Object>} Instance ID and config for the Media Router.
-   */
-  MediaRouter.prototype.start = function() {
-    return this.service_.registerMediaRouteProvider(
-        mediaRouterMojom.MediaRouteProvider.Id.EXTENSION,
-        this.mediaRouteProviderBinding_.createInterfacePtrAndBind());
-  }
-
-  /**
-   * Sets the service delegate methods.
-   * @param {Object} handlers
-   */
-  MediaRouter.prototype.setHandlers = function(handlers) {
-    this.mrpm_.setHandlers(handlers);
-  }
-
-  /**
-   * The keep alive status.
-   * @return {boolean}
-   */
-  MediaRouter.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
-   * @param {!Array<!originMojom.Origin>} origins
-   */
-  MediaRouter.prototype.onSinksReceived = function(sourceUrn, sinks, origins) {
-    this.service_.onSinksReceived(
-        mediaRouterMojom.MediaRouteProvider.Id.EXTENSION, sourceUrn,
-        sinks.map(sinkToMojo_), origins);
-  };
-
-  /**
-   * Called by the provider manager when a sink is found to notify the MR of the
-   * sink's ID. The actual sink will be returned through the normal sink list
-   * update process, so this helps the MR identify the search result in the
-   * list.
-   * @param {string} pseudoSinkId  ID of the pseudo sink that started the
-   *     search.
-   * @param {string} sinkId ID of the newly-found sink.
-   */
-  MediaRouter.prototype.onSearchSinkIdReceived = function(
-      pseudoSinkId, sinkId) {
-    this.service_.onSearchSinkIdReceived(pseudoSinkId, sinkId);
-  };
-
-  /**
-   * 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
-   */
-  MediaRouter.prototype.setKeepAlive = function(keepAlive) {
-    if (keepAlive === false && this.keepAlive_) {
-      this.keepAlive_.ptr.reset();
-      this.keepAlive_ = null;
-    } else if (keepAlive === true && !this.keepAlive_) {
-      this.keepAlive_ = new keepAliveMojom.KeepAlivePtr(
-          frameInterfaces.getInterface(keepAliveMojom.KeepAlive.name));
-    }
-  };
-
-  /**
-   * 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.
-   */
-  MediaRouter.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 '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_page_id': issue.helpPageId,
-      '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 {string=} sourceUrn The sourceUrn associated with this route
-   *     query.
-   * @param {Array<string>=} joinableRouteIds The active set of joinable
-   *     media routes.
-   */
-  MediaRouter.prototype.onRoutesUpdated = function(
-      routes, sourceUrn = '', joinableRouteIds = []) {
-    this.service_.onRoutesUpdated(
-        mediaRouterMojom.MediaRouteProvider.Id.EXTENSION,
-        routes.map(routeToMojo_), sourceUrn, joinableRouteIds);
-  };
-
-  /**
-   * Called by the provider manager when sink availability has been updated.
-   * @param {!mediaRouterMojom.MediaRouter.SinkAvailability} availability
-   *     The new sink availability.
-   */
-  MediaRouter.prototype.onSinkAvailabilityUpdated = function(availability) {
-    this.service_.onSinkAvailabilityUpdated(
-        mediaRouterMojom.MediaRouteProvider.Id.EXTENSION, availability);
-  };
-
-  /**
-   * Called by the provider manager when the state of a presentation connected
-   * to a route has changed.
-   * @param {string} routeId
-   * @param {string} state
-   */
-  MediaRouter.prototype.onPresentationConnectionStateChanged =
-      function(routeId, state) {
-    this.service_.onPresentationConnectionStateChanged(
-        routeId, presentationConnectionStateToMojo_(state));
-  };
-
-  /**
-   * Called by the provider manager when the state of a presentation connected
-   * to a route has closed.
-   * @param {string} routeId
-   * @param {string} reason
-   * @param {string} message
-   */
-  MediaRouter.prototype.onPresentationConnectionClosed =
-      function(routeId, reason, message) {
-    this.service_.onPresentationConnectionClosed(
-        routeId, presentationConnectionCloseReasonToMojo_(reason), message);
-  };
-
-  /**
-   * @param {string} routeId
-   * @param {!Array<!RouteMessage>} mesages
-   */
-  MediaRouter.prototype.onRouteMessagesReceived = function(routeId, messages) {
-    this.service_.onRouteMessagesReceived(
-        routeId, messages.map(messageToMojo_));
-  };
-
-  /**
-   * @param {number} tabId
-   * @param {!remotingMojom.MirrorServiceRemoterPtr} remoter
-   * @param {!remotingMojom.MirrorServiceRemotingSourcePtr} remotingSource
-   */
-  MediaRouter.prototype.onMediaRemoterCreated = function(tabId, remoter,
-      remotingSource) {
-    this.service_.onMediaRemoterCreated(tabId, remoter, remotingSource);
-  }
-
-  /**
-   * Object containing callbacks set by the provider manager.
-   *
-   * @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): Promise}
-     */
-    this.terminateRoute = null;
-
-    /**
-     * @type {function(string)}
-     */
-    this.startObservingMediaSinks = null;
-
-    /**
-     * @type {function(string)}
-     */
-    this.stopObservingMediaSinks = null;
-
-    /**
-     * @type {function(string, string): Promise}
-     */
-    this.sendRouteMessage = null;
-
-    /**
-     * @type {function(string, Uint8Array): Promise}
-     */
-    this.sendRouteBinaryMessage = null;
-
-    /**
-     * @type {function(string)}
-     */
-    this.startListeningForRouteMessages = null;
-
-    /**
-     * @type {function(string)}
-     */
-    this.stopListeningForRouteMessages = null;
-
-    /**
-     * @type {function(string)}
-     */
-    this.detachRoute = null;
-
-    /**
-     * @type {function()}
-     */
-    this.startObservingMediaRoutes = null;
-
-    /**
-     * @type {function()}
-     */
-    this.stopObservingMediaRoutes = null;
-
-    /**
-     * @type {function()}
-     */
-    this.connectRouteByRouteId = null;
-
-    /**
-     * @type {function()}
-     */
-    this.enableMdnsDiscovery = null;
-
-    /**
-     * @type {function()}
-     */
-    this.updateMediaSinks = null;
-
-    /**
-     * @type {function(string, string, !SinkSearchCriteria): string}
-     */
-    this.searchSinks = null;
-
-    /**
-     * @type {function()}
-     */
-    this.provideSinks = null;
-
-    /**
-     * @type {function(string, !bindings.InterfaceRequest,
-     *            !mediaStatusMojom.MediaStatusObserverPtr): !Promise<void>}
-     */
-    this.createMediaRouteController = null;
-  };
-
-  /**
-   * Routes calls from Media Router to the provider manager extension.
-   * Registered with the MediaRouter stub.
-   * @param {!MediaRouter} MediaRouter proxy to call into the
-   * Media Router mojo interface.
-   * @constructor
-   */
-  function MediaRouteProvider(mediaRouter) {
-    /**
-     * 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 {!MediaRouter}
-     */
-    this.mediaRouter_ = mediaRouter;
-  }
-
-  /*
-   * Sets the callback handler used to invoke methods in the provider manager.
-   *
-   * @param {!MediaRouterHandlers} handlers
-   */
-  MediaRouteProvider.prototype.setHandlers = function(handlers) {
-    this.handlers_ = handlers;
-    var requiredHandlers = [
-      'stopObservingMediaRoutes',
-      'startObservingMediaRoutes',
-      'sendRouteMessage',
-      'sendRouteBinaryMessage',
-      'startListeningForRouteMessages',
-      'stopListeningForRouteMessages',
-      'detachRoute',
-      'terminateRoute',
-      'joinRoute',
-      'createRoute',
-      'stopObservingMediaSinks',
-      'startObservingMediaRoutes',
-      'connectRouteByRouteId',
-      'enableMdnsDiscovery',
-      'updateMediaSinks',
-      'searchSinks',
-      'provideSinks',
-      'createMediaRouteController',
-      'onBeforeInvokeHandler'
-    ];
-    requiredHandlers.forEach(function(nextHandler) {
-      if (handlers[nextHandler] === undefined) {
-        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
-   */
-  MediaRouteProvider.prototype.startObservingMediaSinks =
-      function(sourceUrn) {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.startObservingMediaSinks(sourceUrn);
-  };
-
-  /**
-   * Stops querying for sinks capable of displaying |sourceUrn|.
-   * @param {!string} sourceUrn
-   */
-  MediaRouteProvider.prototype.stopObservingMediaSinks =
-      function(sourceUrn) {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.stopObservingMediaSinks(sourceUrn);
-  };
-
-  /**
-   * Requests that |sinkId| render the media referenced by |sourceUrn|. If the
-   * request is from the Presentation API, then origin and tabId will
-   * be populated.
-   * @param {!string} sourceUrn Media source to render.
-   * @param {!string} sinkId Media sink ID.
-   * @param {!string} presentationId Presentation ID from the site
-   *     requesting presentation. TODO(mfoltz): Remove.
-   * @param {!originMojom.Origin} origin Origin of site requesting presentation.
-   * @param {!number} tabId ID of tab requesting presentation.
-   * @param {!timeMojom.TimeDelta} timeout If positive, the timeout duration for
-   *     the request. Otherwise, the default duration will be used.
-   * @param {!boolean} incognito If true, the route is being requested by
-   *     an incognito profile.
-   * @return {!Promise.<!Object>} A Promise resolving to an object describing
-   *     the newly created media route, or rejecting with an error message on
-   *     failure.
-   */
-  MediaRouteProvider.prototype.createRoute =
-      function(sourceUrn, sinkId, presentationId, origin, tabId,
-               timeout, incognito) {
-    this.handlers_.onBeforeInvokeHandler();
-    return this.handlers_.createRoute(
-        sourceUrn, sinkId, presentationId, origin, tabId,
-        Math.floor(timeout.microseconds / 1000), incognito)
-        .then(function(route) {
-          return toSuccessRouteResponse_(route);
-        },
-        function(err) {
-          return toErrorRouteResponse_(err);
-        });
-  };
-
-  /**
-   * Handles a request via the Presentation API to join an existing route given
-   * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
-   * validating same-origin/tab scope.
-   * @param {!string} sourceUrn Media source to render.
-   * @param {!string} presentationId Presentation ID to join.
-   * @param {!originMojom.Origin} origin Origin of site requesting join.
-   * @param {!number} tabId ID of tab requesting join.
-   * @param {!timeMojom.TimeDelta} timeout If positive, the timeout duration for
-   *     the request. Otherwise, the default duration will be used.
-   * @param {!boolean} incognito If true, the route is being requested by
-   *     an incognito profile.
-   * @return {!Promise.<!Object>} A Promise resolving to an object describing
-   *     the newly created media route, or rejecting with an error message on
-   *     failure.
-   */
-  MediaRouteProvider.prototype.joinRoute =
-      function(sourceUrn, presentationId, origin, tabId, timeout,
-               incognito) {
-    this.handlers_.onBeforeInvokeHandler();
-    return this.handlers_.joinRoute(
-        sourceUrn, presentationId, origin, tabId,
-        Math.floor(timeout.microseconds / 1000), incognito)
-        .then(function(route) {
-          return toSuccessRouteResponse_(route);
-        },
-        function(err) {
-          return toErrorRouteResponse_(err);
-        });
-  };
-
-  /**
-   * Handles a request via the Presentation API to join an existing route given
-   * by |sourceUrn| and |routeId|. |origin| and |tabId| are used for
-   * validating same-origin/tab scope.
-   * @param {!string} sourceUrn Media source to render.
-   * @param {!string} routeId Route ID to join.
-   * @param {!string} presentationId Presentation ID to join.
-   * @param {!originMojom.Origin} origin Origin of site requesting join.
-   * @param {!number} tabId ID of tab requesting join.
-   * @param {!timeMojom.TimeDelta} timeout If positive, the timeout duration for
-   *     the request. Otherwise, the default duration will be used.
-   * @param {!boolean} incognito If true, the route is being requested by
-   *     an incognito profile.
-   * @return {!Promise.<!Object>} A Promise resolving to an object describing
-   *     the newly created media route, or rejecting with an error message on
-   *     failure.
-   */
-  MediaRouteProvider.prototype.connectRouteByRouteId =
-      function(sourceUrn, routeId, presentationId, origin, tabId,
-               timeout, incognito) {
-    this.handlers_.onBeforeInvokeHandler();
-    return this.handlers_.connectRouteByRouteId(
-        sourceUrn, routeId, presentationId, origin, tabId,
-        Math.floor(timeout.microseconds / 1000), incognito)
-        .then(function(route) {
-          return toSuccessRouteResponse_(route);
-        },
-        function(err) {
-          return toErrorRouteResponse_(err);
-        });
-  };
-
-  /**
-   * Terminates the route specified by |routeId|.
-   * @param {!string} routeId
-   * @return {!Promise<!Object>} A Promise resolving to an object describing
-   *    the result of the terminate operation, or rejecting with an error
-   *    message and code if the operation failed.
-   */
-  MediaRouteProvider.prototype.terminateRoute = function(routeId) {
-    this.handlers_.onBeforeInvokeHandler();
-    return this.handlers_.terminateRoute(routeId).then(
-        () => ({result_code: mediaRouterMojom.RouteRequestResultCode.OK}),
-        (err) => toErrorRouteResponse_(err));
-  };
-
-  /**
-   * Posts a message to the route designated by |routeId|.
-   * @param {!string} routeId
-   * @param {!string} message
-   * @return {!Promise.<boolean>} Resolved with true if the message was sent,
-   *    or false on failure.
-   */
-  MediaRouteProvider.prototype.sendRouteMessage = function(
-    routeId, message) {
-    this.handlers_.onBeforeInvokeHandler();
-    return this.handlers_.sendRouteMessage(routeId, message)
-        .then(function() {
-          return {'sent': true};
-        }, function() {
-          return {'sent': false};
-        });
-  };
-
-  /**
-   * Sends a binary message to the route designated by |routeId|.
-   * @param {!string} routeId
-   * @param {!Array<number>} data
-   * @return {!Promise.<boolean>} Resolved with true if the data was sent,
-   *    or false on failure.
-   */
-  MediaRouteProvider.prototype.sendRouteBinaryMessage = function(
-    routeId, data) {
-    this.handlers_.onBeforeInvokeHandler();
-    return this.handlers_.sendRouteBinaryMessage(routeId, new Uint8Array(data))
-        .then(function() {
-          return {'sent': true};
-        }, function() {
-          return {'sent': false};
-        });
-  };
-
-  /**
-   * Listen for messages from a route.
-   * @param {!string} routeId
-   */
-  MediaRouteProvider.prototype.startListeningForRouteMessages = function(
-      routeId) {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.startListeningForRouteMessages(routeId);
-  };
-
-  /**
-   * @param {!string} routeId
-   */
-  MediaRouteProvider.prototype.stopListeningForRouteMessages = function(
-      routeId) {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.stopListeningForRouteMessages(routeId);
-  };
-
-  /**
-   * Indicates that the presentation connection that was connected to |routeId|
-   * is no longer connected to it.
-   * @param {!string} routeId
-   */
-  MediaRouteProvider.prototype.detachRoute = function(
-      routeId) {
-    this.handlers_.detachRoute(routeId);
-  };
-
-  /**
-   * Requests that the provider manager start sending information about active
-   * media routes to the Media Router.
-   * @param {!string} sourceUrn
-   */
-  MediaRouteProvider.prototype.startObservingMediaRoutes = function(sourceUrn) {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.startObservingMediaRoutes(sourceUrn);
-  };
-
-  /**
-   * Requests that the provider manager stop sending information about active
-   * media routes to the Media Router.
-   * @param {!string} sourceUrn
-   */
-  MediaRouteProvider.prototype.stopObservingMediaRoutes = function(sourceUrn) {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.stopObservingMediaRoutes(sourceUrn);
-  };
-
-  /**
-   * Enables mDNS device discovery.
-   */
-  MediaRouteProvider.prototype.enableMdnsDiscovery = function() {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.enableMdnsDiscovery();
-  };
-
-  /**
-   * Requests that the provider manager update media sinks.
-   * @param {!string} sourceUrn
-   */
-  MediaRouteProvider.prototype.updateMediaSinks = function(sourceUrn) {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.updateMediaSinks(sourceUrn);
-  };
-
-  /**
-   * Requests that the provider manager search its providers for a sink matching
-   * |searchCriteria| that is compatible with |sourceUrn|. If a sink is found
-   * that can be used immediately for route creation, its ID is returned.
-   * Otherwise the empty string is returned.
-   *
-   * @param {string} sinkId Sink ID of the pseudo sink generating the request.
-   * @param {string} sourceUrn Media source to be used with the sink.
-   * @param {!SinkSearchCriteria} searchCriteria Search criteria for the route
-   *     providers.
-   * @return {!Promise.<!{sink_id: !string}>} A Promise resolving to either the
-   *     sink ID of the sink found by the search that can be used for route
-   *     creation, or the empty string if no route can be immediately created.
-   */
-  MediaRouteProvider.prototype.searchSinks = function(
-      sinkId, sourceUrn, searchCriteria) {
-    this.handlers_.onBeforeInvokeHandler();
-    const searchSinksResponse =
-        this.handlers_.searchSinks(sinkId, sourceUrn, searchCriteria);
-
-    if ('string' == typeof searchSinksResponse) {
-        // TODO (zijiang): Remove this check when M59 is stable and the
-        // extension is always returning a promise.
-        return Promise.resolve({
-          'sink_id': sink_id
-        });
-    }
-    return searchSinksResponse.then(
-        sink_id => {
-          return { 'sink_id': sink_id };
-        },
-        () => {
-          return { 'sink_id': '' };
-        });
-  };
-
-  /**
-   * Notifies the provider manager that MediaRouter has discovered a list of
-   * sinks.
-   * @param {string} providerName
-   * @param {!Array<!mediaRouterMojom.MediaSink>} sinks
-   */
-  MediaRouteProvider.prototype.provideSinks = function(providerName, sinks) {
-    this.handlers_.onBeforeInvokeHandler();
-    this.handlers_.provideSinks(providerName, sinks);
-  };
-
-  /**
-   * Creates a controller for the given route and binds the given
-   * InterfaceRequest to it, and registers an observer for media status updates
-   * for the route.
-   * @param {string} routeId
-   * @param {!bindings.InterfaceRequest} controllerRequest
-   * @param {!mediaStatusMojom.MediaStatusObserverPtr} observer
-   * @return {!Promise<!{success: boolean}>} Resolves to true if a controller
-   *     is created. Resolves to false if a controller cannot be created, or if
-   *     the controller is already bound.
-   */
-  MediaRouteProvider.prototype.createMediaRouteController = function(
-      routeId, controllerRequest, observer) {
-    this.handlers_.onBeforeInvokeHandler();
-    return this.handlers_
-        .createMediaRouteController(routeId, controllerRequest, observer)
-        .then(() => ({success: true}), e => ({success: false}));
-  };
-
-  mediaRouter = new MediaRouter(new mediaRouterMojom.MediaRouterPtr(
-      frameInterfaces.getInterface(mediaRouterMojom.MediaRouter.name)));
-
-  return mediaRouter;
 });
+
+Object.defineProperty(MediaSinkExtraDataAdapter.prototype, 'cast_media_sink', {
+  get: function() {
+    if (this.$tag != MediaSinkExtraDataAdapter.Tags.cast_media_sink) {
+      throw new ReferenceError(
+          'MediaSinkExtraDataAdapter.cast_media_sink is not currently set.');
+    }
+    return this.$data;
+  },
+
+  set: function(value) {
+    this.$tag = MediaSinkExtraDataAdapter.Tags.cast_media_sink;
+    this.$data = value;
+  }
+});
+
+MediaSinkExtraDataAdapter.fromNewVersion = function(other) {
+  if (other.$tag == mediaRouter.mojom.MediaSinkExtraData.Tags.dialMediaSink) {
+    return new MediaSinkExtraDataAdapter({
+      'dial_media_sink':
+          DialMediaSinkAdapter.fromNewVersion(other.dialMediaSink),
+    });
+  } else {
+    return new MediaSinkExtraDataAdapter({
+      'cast_media_sink':
+          CastMediaSinkAdapter.fromNewVersion(other.castMediaSink),
+    });
+  }
+};
+
+MediaSinkExtraDataAdapter.prototype.toNewVersion = function() {
+  if (this.$tag == MediaSinkExtraDataAdapter.Tags.dial_media_sink) {
+    return new mediaRouter.mojom.MediaSinkExtraData({
+      'dialMediaSink': this.dial_media_sink.toNewVersion(),
+    });
+  } else {
+    return new mediaRouter.mojom.MediaSinkExtraData({
+      'castMediaSink': this.cast_media_sink.toNewVersion(),
+    });
+  }
+};
+
+/**
+ * Adapter for media.mojom.MirrorServiceRemoterPtr.
+ * @constructor
+ */
+function MirrorServiceRemoterPtrAdapter(handleOrPtrInfo) {
+  this.ptr = new mojo.InterfacePtrController(MirrorServiceRemoterAdapter,
+                                             handleOrPtrInfo);
+}
+
+MirrorServiceRemoterPtrAdapter.prototype =
+    Object.create(media.mojom.MirrorServiceRemoterPtr.prototype);
+MirrorServiceRemoterPtrAdapter.prototype.constructor =
+    MirrorServiceRemoterPtrAdapter;
+
+MirrorServiceRemoterPtrAdapter.prototype.startDataStreams = function() {
+  return MirrorServiceRemoterProxy.prototype.startDataStreams
+      .apply(this.ptr.getProxy(), arguments).then(function(response) {
+    return Promise.resolve({
+      'audio_stream_id': response.audioStreamId,
+      'video_stream_id': response.videoStreamId,
+    });
+  });
+};
+
+/**
+ * Adapter for media.mojom.MirrorServiceRemoter.stubclass.
+ * @constructor
+ */
+function MirrorServiceRemoterStubAdapter(delegate) {
+  this.delegate_ = delegate;
+}
+
+MirrorServiceRemoterStubAdapter.prototype = Object.create(
+    media.mojom.MirrorServiceRemoter.stubClass.prototype);
+MirrorServiceRemoterStubAdapter.prototype.constructor =
+    MirrorServiceRemoterStubAdapter;
+
+MirrorServiceRemoterStubAdapter.prototype.startDataStreams =
+    function(hasAudio, hasVideo) {
+  return this.delegate_ && this.delegate_.startDataStreams &&
+      this.delegate_.startDataStreams(hasAudio, hasVideo).then(
+          function(response) {
+            return {
+              'audioStreamId': response.audio_stream_id,
+              'videoStreamId': response.video_stream_id,
+            };
+          });
+};
+
+/**
+ * Adapter for media.mojom.MirrorServiceRemoter.
+ */
+var MirrorServiceRemoterAdapter = {
+    name: 'media::mojom::MirrorServiceRemoter',
+    kVersion: 0,
+    ptrClass: MirrorServiceRemoterPtrAdapter,
+    proxyClass: media.mojom.MirrorServiceRemoter.proxyClass,
+    stubClass: MirrorServiceRemoterStubAdapter,
+    validateRequest: media.mojom.MirrorServiceRemoter.validateRequest,
+    validateResponse: media.mojom.MirrorServiceRemoter.validateResponse,
+};
+
+/**
+ * Adapter for media.mojom.MirrorServiceRemotingSourcePtr.
+ * @constructor
+ */
+function MirrorServiceRemotingSourcePtrAdapter(handleOrPtrInfo) {
+  this.ptr = new mojo.InterfacePtrController(MirrorServiceRemotingSourceAdapter,
+                                             handleOrPtrInfo);
+}
+
+MirrorServiceRemotingSourcePtrAdapter.prototype =
+    Object.create(media.mojom.MirrorServiceRemotingSourcePtr.prototype);
+MirrorServiceRemotingSourcePtrAdapter.prototype.constructor =
+    MirrorServiceRemotingSourcePtrAdapter;
+
+MirrorServiceRemotingSourcePtrAdapter.prototype.onSinkAvailable =
+    function(metadata) {
+  return this.ptr.getProxy().onSinkAvailable(metadata.toNewVersion());
+};
+
+/**
+ * Adapter for media.mojom.MirrorServiceRemotingSource.
+ */
+var MirrorServiceRemotingSourceAdapter = {
+    name: 'media::mojom::MirrorServiceRemotingSource',
+    kVersion: 0,
+    ptrClass: MirrorServiceRemotingSourcePtrAdapter,
+    proxyClass: media.mojom.MirrorServiceRemotingSource.proxyClass,
+    stubClass: null,
+    validateRequest: media.mojom.MirrorServiceRemotingSource.validateRequest,
+    validateResponse: null,
+};
+
+/**
+ * Adapter for mediaRouter.mojom.MediaStatusObserver.
+ * @constructor
+ */
+function MediaStatusObserverPtrAdapter(handleOrPtrInfo) {
+  this.ptr = new mojo.InterfacePtrController(MediaStatusObserverAdapter,
+                                             handleOrPtrInfo);
+}
+
+MediaStatusObserverPtrAdapter.prototype =
+    Object.create(mediaRouter.mojom.MediaStatusObserverPtr.prototype);
+MediaStatusObserverPtrAdapter.prototype.constructor =
+    MediaStatusObserverPtrAdapter;
+
+MediaStatusObserverPtrAdapter.prototype.onMediaStatusUpdated =
+    function(status) {
+  return this.ptr.getProxy().onMediaStatusUpdated(status.toNewVersion());
+};
+
+/**
+ * Adapter for mediaRouter.mojom.MediaStatusObserver.
+ */
+var MediaStatusObserverAdapter = {
+  name: 'mediaRouter::mojom::MediaStatusObserver',
+  kVersion: 0,
+  ptrClass: MediaStatusObserverPtrAdapter,
+  proxyClass: mediaRouter.mojom.MediaStatusObserver.proxyClass,
+  stubClass: null,
+  validateRequest: mediaRouter.mojom.MediaStatusObserver.validateRequest,
+  validateResponse: null,
+};
+
+/**
+ * Converts a media sink to a MediaSink Mojo object.
+ * @param {!MediaSink} sink A media sink.
+ * @return {!mediaRouter.mojom.MediaSink} A Mojo MediaSink object.
+ */
+function sinkToMojo_(sink) {
+  return new mediaRouter.mojom.MediaSink({
+    'name': sink.friendlyName,
+    'description': sink.description,
+    'domain': sink.domain,
+    'sinkId': sink.id,
+    'iconType': sinkIconTypeToMojo(sink.iconType),
+  });
+}
+
+/**
+ * Converts a media sink's icon type to a MediaSink.IconType Mojo object.
+ * @param {!MediaSink.IconType} type A media sink's icon type.
+ * @return {!mediaRouter.mojom.MediaSink.IconType} A Mojo MediaSink.IconType
+ *     object.
+ */
+function sinkIconTypeToMojo(type) {
+  switch (type) {
+    case 'cast':
+      return mediaRouter.mojom.SinkIconType.CAST;
+    case 'cast_audio_group':
+      return mediaRouter.mojom.SinkIconType.CAST_AUDIO_GROUP;
+    case 'cast_audio':
+      return mediaRouter.mojom.SinkIconType.CAST_AUDIO;
+    case 'meeting':
+      return mediaRouter.mojom.SinkIconType.MEETING;
+    case 'hangout':
+      return mediaRouter.mojom.SinkIconType.HANGOUT;
+    case 'education':
+      return mediaRouter.mojom.SinkIconType.EDUCATION;
+    case 'generic':
+      return mediaRouter.mojom.SinkIconType.GENERIC;
+    default:
+      console.error('Unknown sink icon type : ' + type);
+      return mediaRouter.mojom.SinkIconType.GENERIC;
+  }
+}
+
+/**
+ * Returns a Mojo MediaRoute object given a MediaRoute and a
+ * media sink name.
+ * @param {!MediaRoute} route
+ * @return {!mediaRouter.mojom.MediaRoute}
+ */
+function routeToMojo_(route) {
+  return new mediaRouter.mojom.MediaRoute({
+    'mediaRouteId': route.id,
+    'mediaSource': route.mediaSource,
+    'mediaSinkId': route.sinkId,
+    'description': route.description,
+    'iconUrl': route.iconUrl,
+    'isLocal': route.isLocal,
+    'customControllerPath': route.customControllerPath || '',
+    'forDisplay': route.forDisplay,
+    'isIncognito': route.offTheRecord,
+    'isLocalPresentation': route.isOffscreenPresentation,
+    'supportsMediaRouteController': route.supportsMediaRouteController,
+    'controllerType': route.controllerType,
+    // Begin newly added properties, followed by the milestone they were
+    // added.  The guard should be safe to remove N+2 milestones later.
+    'presentationId': route.presentationId || ''  // M64
+  });
+}
+
+/**
+ * Converts a route message to a RouteMessage Mojo object.
+ * @param {!RouteMessage} message
+ * @return {!mediaRouter.mojom.RouteMessage} A Mojo RouteMessage object.
+ */
+function messageToMojo_(message) {
+  if ("string" == typeof message.message) {
+    return new mediaRouter.mojom.RouteMessage({
+      'type': mediaRouter.mojom.RouteMessage.Type.TEXT,
+      'message': message.message,
+    });
+  } else {
+    return new mediaRouter.mojom.RouteMessage({
+      'type': mediaRouter.mojom.RouteMessage.Type.BINARY,
+      'data': message.message,
+    });
+  }
+}
+
+/**
+ * Converts presentation connection state to Mojo enum value.
+ * @param {!string} state
+ * @return {!mediaRouter.mojom.MediaRouter.PresentationConnectionState}
+ */
+function presentationConnectionStateToMojo_(state) {
+  var PresentationConnectionState =
+      mediaRouter.mojom.MediaRouter.PresentationConnectionState;
+  switch (state) {
+    case 'connecting':
+      return PresentationConnectionState.CONNECTING;
+    case 'connected':
+      return PresentationConnectionState.CONNECTED;
+    case 'closed':
+      return PresentationConnectionState.CLOSED;
+    case 'terminated':
+      return PresentationConnectionState.TERMINATED;
+    default:
+      console.error('Unknown presentation connection state: ' + state);
+      return PresentationConnectionState.TERMINATED;
+  }
+}
+
+/**
+ * Converts presentation connection close reason to Mojo enum value.
+ * @param {!string} reason
+ * @return {!mediaRouter.mojom.MediaRouter.PresentationConnectionCloseReason}
+ */
+function presentationConnectionCloseReasonToMojo_(reason) {
+  var PresentationConnectionCloseReason =
+      mediaRouter.mojom.MediaRouter.PresentationConnectionCloseReason;
+  switch (reason) {
+    case 'error':
+      return PresentationConnectionCloseReason.CONNECTION_ERROR;
+    case 'closed':
+      return PresentationConnectionCloseReason.CLOSED;
+    case 'went_away':
+      return PresentationConnectionCloseReason.WENT_AWAY;
+    default:
+      console.error('Unknown presentation connection close reason : ' +
+          reason);
+      return PresentationConnectionCloseReason.CONNECTION_ERROR;
+  }
+}
+
+/**
+ * Parses the given route request Error object and converts it to the
+ * corresponding result code.
+ * @param {!Error} error
+ * @return {!mediaRouter.mojom.RouteRequestResultCode}
+ */
+function getRouteRequestResultCode_(error) {
+  return error.errorCode ? error.errorCode :
+    mediaRouter.mojom.RouteRequestResultCode.UNKNOWN_ERROR;
+}
+
+/**
+ * Creates and returns a successful route response from given route.
+ * @param {!MediaRoute} route
+ * @return {!Object}
+ */
+function toSuccessRouteResponse_(route) {
+  return {
+      route: routeToMojo_(route),
+      resultCode: mediaRouter.mojom.RouteRequestResultCode.OK
+  };
+}
+
+/**
+ * Creates and returns a error route response from given Error object.
+ * @param {!Error} error
+ * @return {!Object}
+ */
+function toErrorRouteResponse_(error) {
+  return {
+      errorText: error.message,
+      resultCode: getRouteRequestResultCode_(error)
+  };
+}
+
+/**
+ * Creates a new MediaRouter.
+ * Converts a route struct to its Mojo form.
+ * @param {!mediaRouter.mojom.MediaRouterPtr} service
+ * @constructor
+ */
+function MediaRouter(service) {
+  /**
+   * The Mojo service proxy. Allows extension code to call methods that reside
+   * in the browser.
+   * @type {!mediaRouter.mojom.MediaRouterPtr}
+   */
+  this.service_ = service;
+
+  /**
+   * The provider manager service delegate. Its methods are called by the
+   * browser-resident Mojo service.
+   * @type {!MediaRouter}
+   */
+  this.mrpm_ = new MediaRouteProvider(this);
+
+  /**
+   * 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 bindings 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 {!mojo.Binding}
+   */
+  this.mediaRouteProviderBinding_ = new mojo.Binding(
+      mediaRouter.mojom.MediaRouteProvider, this.mrpm_);
+}
+
+/**
+ * Returns definitions of Mojo core and generated Mojom classes that can be
+ * used directly by the component.
+ * @return {!Object}
+ * TODO(imcheng): We should export these along with MediaRouter. This requires
+ * us to modify the component to handle multiple exports. When that logic is
+ * baked in for a couple of milestones, we should be able to remove this
+ * method.
+ * TODO(imcheng): We should stop exporting mojo bindings classes that the
+ * Media Router extension doesn't directly use, such as
+ * mojo.AssociatedInterfacePtrInfo, mojo.InterfacePtrController and
+ * mojo.interfaceControl2.
+ */
+MediaRouter.prototype.getMojoExports = function() {
+  return {
+    AssociatedInterfacePtrInfo: mojo.AssociatedInterfacePtrInfo,
+    Binding: mojo.Binding,
+    DialMediaSink: DialMediaSinkAdapter,
+    CastMediaSink: CastMediaSinkAdapter,
+    HangoutsMediaRouteController:
+        mediaRouter.mojom.HangoutsMediaRouteController,
+    HangoutsMediaStatusExtraData: HangoutsMediaStatusExtraDataAdapter,
+    IPAddress: IPAddressAdapter,
+    IPEndpoint: IPEndpointAdapter,
+    InterfacePtrController: mojo.InterfacePtrController,
+    InterfacePtrInfo: mojo.InterfacePtrInfo,
+    InterfaceRequest: mojo.InterfaceRequest,
+    MediaController: mediaRouter.mojom.MediaController,
+    MediaStatus: MediaStatusAdapter,
+    MediaStatusObserverPtr: mediaRouter.mojom.MediaStatusObserverPtr,
+    MirrorServiceRemoter: MirrorServiceRemoterAdapter,
+    MirrorServiceRemoterPtr: MirrorServiceRemoterPtrAdapter,
+    MirrorServiceRemotingSourcePtr: MirrorServiceRemotingSourcePtrAdapter,
+    RemotingStopReason: media.mojom.RemotingStopReason,
+    RemotingStartFailReason: media.mojom.RemotingStartFailReason,
+    RemotingSinkFeature: media.mojom.RemotingSinkFeature,
+    RemotingSinkAudioCapability:
+        media.mojom.RemotingSinkAudioCapability,
+    RemotingSinkVideoCapability:
+        media.mojom.RemotingSinkVideoCapability,
+    RemotingSinkMetadata: RemotingSinkMetadataAdapter,
+    RouteControllerType: mediaRouter.mojom.RouteControllerType,
+    Origin: url.mojom.Origin,
+    Sink: MediaSinkAdapter,
+    SinkExtraData: MediaSinkExtraDataAdapter,
+    TimeDelta: mojo.common.mojom.TimeDelta,
+    Url: url.mojom.Url,
+    interfaceControl2: mojo.interfaceControl2,
+    makeRequest: mojo.makeRequest,
+  };
+};
+
+/**
+ * Registers the Media Router Provider Manager with the Media Router.
+ * @return {!Promise<Object>} Instance ID and config for the Media Router.
+ */
+MediaRouter.prototype.start = function() {
+  return this.service_.registerMediaRouteProvider(
+      mediaRouter.mojom.MediaRouteProvider.Id.EXTENSION,
+      this.mediaRouteProviderBinding_.createInterfacePtrAndBind()).then(
+          function(response) {
+            return {
+              'enable_dial_discovery': response.enableDialDiscovery,
+              'enable_cast_discovery': response.enableCastDiscovery,
+            };
+          });
+}
+
+/**
+ * Sets the service delegate methods.
+ * @param {Object} handlers
+ */
+MediaRouter.prototype.setHandlers = function(handlers) {
+  this.mrpm_.setHandlers(handlers);
+}
+
+/**
+ * The keep alive status.
+ * @return {boolean}
+ */
+MediaRouter.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
+ * @param {!Array<!url.mojom.Origin>} origins
+ */
+MediaRouter.prototype.onSinksReceived = function(sourceUrn, sinks, origins) {
+  this.service_.onSinksReceived(
+      mediaRouter.mojom.MediaRouteProvider.Id.EXTENSION, sourceUrn,
+      sinks.map(sinkToMojo_), origins);
+};
+
+/**
+ * Called by the provider manager when a sink is found to notify the MR of the
+ * sink's ID. The actual sink will be returned through the normal sink list
+ * update process, so this helps the MR identify the search result in the
+ * list.
+ * @param {string} pseudoSinkId  ID of the pseudo sink that started the
+ *     search.
+ * @param {string} sinkId ID of the newly-found sink.
+ */
+MediaRouter.prototype.onSearchSinkIdReceived = function(
+    pseudoSinkId, sinkId) {
+  this.service_.onSearchSinkIdReceived(pseudoSinkId, sinkId);
+};
+
+/**
+ * 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
+ */
+MediaRouter.prototype.setKeepAlive = function(keepAlive) {
+  if (keepAlive === false && this.keepAlive_) {
+    this.keepAlive_.ptr.reset();
+    this.keepAlive_ = null;
+  } else if (keepAlive === true && !this.keepAlive_) {
+    this.keepAlive_ = new extensions.KeepAlivePtr;
+    Mojo.bindInterface(extensions.KeepAlive.name,
+                       mojo.makeRequest(this.keepAlive_).handle);
+  }
+};
+
+/**
+ * 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.
+ */
+MediaRouter.prototype.onIssue = function(issue) {
+  function issueSeverityToMojo_(severity) {
+    switch (severity) {
+      case 'fatal':
+        return mediaRouter.mojom.Issue.Severity.FATAL;
+      case 'warning':
+        return mediaRouter.mojom.Issue.Severity.WARNING;
+      case 'notification':
+        return mediaRouter.mojom.Issue.Severity.NOTIFICATION;
+      default:
+        console.error('Unknown issue severity: ' + severity);
+        return mediaRouter.mojom.Issue.Severity.NOTIFICATION;
+    }
+  }
+
+  function issueActionToMojo_(action) {
+    switch (action) {
+      case 'dismiss':
+        return mediaRouter.mojom.Issue.ActionType.DISMISS;
+      case 'learn_more':
+        return mediaRouter.mojom.Issue.ActionType.LEARN_MORE;
+      default:
+        console.error('Unknown issue action type : ' + action);
+        return mediaRouter.mojom.Issue.ActionType.OK;
+    }
+  }
+
+  var secondaryActions = (issue.secondaryActions || []).map(issueActionToMojo_);
+  this.service_.onIssue(new mediaRouter.mojom.Issue({
+    'routeId': issue.routeId || '',
+    'severity': issueSeverityToMojo_(issue.severity),
+    'title': issue.title,
+    'message': issue.message || '',
+    'defaultAction': issueActionToMojo_(issue.defaultAction),
+    'secondaryActions': secondaryActions,
+    'helpPageId': issue.helpPageId,
+    'isBlocking': 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 {string=} sourceUrn The sourceUrn associated with this route
+ *     query.
+ * @param {Array<string>=} joinableRouteIds The active set of joinable
+ *     media routes.
+ */
+MediaRouter.prototype.onRoutesUpdated = function(
+    routes, sourceUrn = '', joinableRouteIds = []) {
+  this.service_.onRoutesUpdated(
+      mediaRouter.mojom.MediaRouteProvider.Id.EXTENSION,
+      routes.map(routeToMojo_), sourceUrn, joinableRouteIds);
+};
+
+/**
+ * Called by the provider manager when sink availability has been updated.
+ * @param {!mediaRouter.mojom.MediaRouter.SinkAvailability} availability
+ *     The new sink availability.
+ */
+MediaRouter.prototype.onSinkAvailabilityUpdated = function(availability) {
+  this.service_.onSinkAvailabilityUpdated(
+      mediaRouter.mojom.MediaRouteProvider.Id.EXTENSION, availability);
+};
+
+/**
+ * Called by the provider manager when the state of a presentation connected
+ * to a route has changed.
+ * @param {string} routeId
+ * @param {string} state
+ */
+MediaRouter.prototype.onPresentationConnectionStateChanged =
+    function(routeId, state) {
+  this.service_.onPresentationConnectionStateChanged(
+      routeId, presentationConnectionStateToMojo_(state));
+};
+
+/**
+ * Called by the provider manager when the state of a presentation connected
+ * to a route has closed.
+ * @param {string} routeId
+ * @param {string} reason
+ * @param {string} message
+ */
+MediaRouter.prototype.onPresentationConnectionClosed =
+    function(routeId, reason, message) {
+  this.service_.onPresentationConnectionClosed(
+      routeId, presentationConnectionCloseReasonToMojo_(reason), message);
+};
+
+/**
+ * @param {string} routeId
+ * @param {!Array<!RouteMessage>} mesages
+ */
+MediaRouter.prototype.onRouteMessagesReceived = function(routeId, messages) {
+  this.service_.onRouteMessagesReceived(
+      routeId, messages.map(messageToMojo_));
+};
+
+/**
+ * @param {number} tabId
+ * @param {!media.mojom.MirrorServiceRemoterPtr} remoter
+ * @param {!mojo.InterfaceRequest} remotingSource
+ */
+MediaRouter.prototype.onMediaRemoterCreated = function(tabId, remoter,
+    remotingSource) {
+  this.service_.onMediaRemoterCreated(
+      tabId,
+      new media.mojom.MirrorServiceRemoterPtr(remoter.ptr.passInterface()),
+      remotingSource);
+}
+
+/**
+ * Object containing callbacks set by the provider manager.
+ *
+ * @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): Promise}
+   */
+  this.terminateRoute = null;
+
+  /**
+   * @type {function(string)}
+   */
+  this.startObservingMediaSinks = null;
+
+  /**
+   * @type {function(string)}
+   */
+  this.stopObservingMediaSinks = null;
+
+  /**
+   * @type {function(string, string): Promise}
+   */
+  this.sendRouteMessage = null;
+
+  /**
+   * @type {function(string, Uint8Array): Promise}
+   */
+  this.sendRouteBinaryMessage = null;
+
+  /**
+   * @type {function(string)}
+   */
+  this.startListeningForRouteMessages = null;
+
+  /**
+   * @type {function(string)}
+   */
+  this.stopListeningForRouteMessages = null;
+
+  /**
+   * @type {function(string)}
+   */
+  this.detachRoute = null;
+
+  /**
+   * @type {function()}
+   */
+  this.startObservingMediaRoutes = null;
+
+  /**
+   * @type {function()}
+   */
+  this.stopObservingMediaRoutes = null;
+
+  /**
+   * @type {function()}
+   */
+  this.connectRouteByRouteId = null;
+
+  /**
+   * @type {function()}
+   */
+  this.enableMdnsDiscovery = null;
+
+  /**
+   * @type {function()}
+   */
+  this.updateMediaSinks = null;
+
+  /**
+   * @type {function(string, string, !SinkSearchCriteria): string}
+   */
+  this.searchSinks = null;
+
+  /**
+   * @type {function()}
+   */
+  this.provideSinks = null;
+
+  /**
+   * @type {function(string, !mojo.InterfaceRequest,
+   *            !mediaRouter.mojom.MediaStatusObserverPtr): !Promise<void>}
+   */
+  this.createMediaRouteController = null;
+};
+
+/**
+ * Routes calls from Media Router to the provider manager extension.
+ * Registered with the MediaRouter stub.
+ * @param {!MediaRouter} MediaRouter proxy to call into the
+ * Media Router mojo interface.
+ * @constructor
+ */
+function MediaRouteProvider(mediaRouter) {
+  /**
+   * 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 {!MediaRouter}
+   */
+  this.mediaRouter_ = mediaRouter;
+}
+
+/*
+ * Sets the callback handler used to invoke methods in the provider manager.
+ *
+ * @param {!MediaRouterHandlers} handlers
+ */
+MediaRouteProvider.prototype.setHandlers = function(handlers) {
+  this.handlers_ = handlers;
+  var requiredHandlers = [
+    'stopObservingMediaRoutes',
+    'startObservingMediaRoutes',
+    'sendRouteMessage',
+    'sendRouteBinaryMessage',
+    'startListeningForRouteMessages',
+    'stopListeningForRouteMessages',
+    'detachRoute',
+    'terminateRoute',
+    'joinRoute',
+    'createRoute',
+    'stopObservingMediaSinks',
+    'startObservingMediaRoutes',
+    'connectRouteByRouteId',
+    'enableMdnsDiscovery',
+    'updateMediaSinks',
+    'searchSinks',
+    'provideSinks',
+    'createMediaRouteController',
+    'onBeforeInvokeHandler'
+  ];
+  requiredHandlers.forEach(function(nextHandler) {
+    if (handlers[nextHandler] === undefined) {
+      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
+ */
+MediaRouteProvider.prototype.startObservingMediaSinks =
+    function(sourceUrn) {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.startObservingMediaSinks(sourceUrn);
+};
+
+/**
+ * Stops querying for sinks capable of displaying |sourceUrn|.
+ * @param {!string} sourceUrn
+ */
+MediaRouteProvider.prototype.stopObservingMediaSinks =
+    function(sourceUrn) {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.stopObservingMediaSinks(sourceUrn);
+};
+
+/**
+ * Requests that |sinkId| render the media referenced by |sourceUrn|. If the
+ * request is from the Presentation API, then origin and tabId will
+ * be populated.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} sinkId Media sink ID.
+ * @param {!string} presentationId Presentation ID from the site
+ *     requesting presentation. TODO(mfoltz): Remove.
+ * @param {!url.mojom.Origin} origin Origin of site requesting presentation.
+ * @param {!number} tabId ID of tab requesting presentation.
+ * @param {!mojo.common.mojom.TimeDelta} timeout If positive, the timeout
+ *     duration for the request. Otherwise, the default duration will be used.
+ * @param {!boolean} incognito If true, the route is being requested by
+ *     an incognito profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ *     the newly created media route, or rejecting with an error message on
+ *     failure.
+ */
+MediaRouteProvider.prototype.createRoute =
+    function(sourceUrn, sinkId, presentationId, origin, tabId,
+             timeout, incognito) {
+  this.handlers_.onBeforeInvokeHandler();
+  return this.handlers_.createRoute(
+      sourceUrn, sinkId, presentationId, origin, tabId,
+      Math.floor(timeout.microseconds / 1000), incognito)
+      .then(function(route) {
+        return toSuccessRouteResponse_(route);
+      },
+      function(err) {
+        return toErrorRouteResponse_(err);
+      });
+};
+
+/**
+ * Handles a request via the Presentation API to join an existing route given
+ * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
+ * validating same-origin/tab scope.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} presentationId Presentation ID to join.
+ * @param {!url.mojom.Origin} origin Origin of site requesting join.
+ * @param {!number} tabId ID of tab requesting join.
+ * @param {!mojo.common.mojom.TimeDelta} timeout If positive, the timeout
+ *     duration for the request. Otherwise, the default duration will be used.
+ * @param {!boolean} incognito If true, the route is being requested by
+ *     an incognito profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ *     the newly created media route, or rejecting with an error message on
+ *     failure.
+ */
+MediaRouteProvider.prototype.joinRoute =
+    function(sourceUrn, presentationId, origin, tabId, timeout,
+             incognito) {
+  this.handlers_.onBeforeInvokeHandler();
+  return this.handlers_.joinRoute(
+      sourceUrn, presentationId, origin, tabId,
+      Math.floor(timeout.microseconds / 1000), incognito)
+      .then(function(route) {
+        return toSuccessRouteResponse_(route);
+      },
+      function(err) {
+        return toErrorRouteResponse_(err);
+      });
+};
+
+/**
+ * Handles a request via the Presentation API to join an existing route given
+ * by |sourceUrn| and |routeId|. |origin| and |tabId| are used for
+ * validating same-origin/tab scope.
+ * @param {!string} sourceUrn Media source to render.
+ * @param {!string} routeId Route ID to join.
+ * @param {!string} presentationId Presentation ID to join.
+ * @param {!url.mojom.Origin} origin Origin of site requesting join.
+ * @param {!number} tabId ID of tab requesting join.
+ * @param {!mojo.common.mojom.TimeDelta} timeout If positive, the timeout
+ *     duration for the request. Otherwise, the default duration will be used.
+ * @param {!boolean} incognito If true, the route is being requested by
+ *     an incognito profile.
+ * @return {!Promise.<!Object>} A Promise resolving to an object describing
+ *     the newly created media route, or rejecting with an error message on
+ *     failure.
+ */
+MediaRouteProvider.prototype.connectRouteByRouteId =
+    function(sourceUrn, routeId, presentationId, origin, tabId,
+             timeout, incognito) {
+  this.handlers_.onBeforeInvokeHandler();
+  return this.handlers_.connectRouteByRouteId(
+      sourceUrn, routeId, presentationId, origin, tabId,
+      Math.floor(timeout.microseconds / 1000), incognito)
+      .then(function(route) {
+        return toSuccessRouteResponse_(route);
+      },
+      function(err) {
+        return toErrorRouteResponse_(err);
+      });
+};
+
+/**
+ * Terminates the route specified by |routeId|.
+ * @param {!string} routeId
+ * @return {!Promise<!Object>} A Promise resolving to an object describing
+ *    the result of the terminate operation, or rejecting with an error
+ *    message and code if the operation failed.
+ */
+MediaRouteProvider.prototype.terminateRoute = function(routeId) {
+  this.handlers_.onBeforeInvokeHandler();
+  return this.handlers_.terminateRoute(routeId).then(
+      () => ({resultCode: mediaRouter.mojom.RouteRequestResultCode.OK}),
+      (err) => toErrorRouteResponse_(err));
+};
+
+/**
+ * Posts a message to the route designated by |routeId|.
+ * @param {!string} routeId
+ * @param {!string} message
+ * @return {!Promise.<boolean>} Resolved with true if the message was sent,
+ *    or false on failure.
+ */
+MediaRouteProvider.prototype.sendRouteMessage = function(
+  routeId, message) {
+  this.handlers_.onBeforeInvokeHandler();
+  return this.handlers_.sendRouteMessage(routeId, message)
+      .then(function() {
+        return {'sent': true};
+      }, function() {
+        return {'sent': false};
+      });
+};
+
+/**
+ * Sends a binary message to the route designated by |routeId|.
+ * @param {!string} routeId
+ * @param {!Array<number>} data
+ * @return {!Promise.<boolean>} Resolved with true if the data was sent,
+ *    or false on failure.
+ */
+MediaRouteProvider.prototype.sendRouteBinaryMessage = function(
+  routeId, data) {
+  this.handlers_.onBeforeInvokeHandler();
+  return this.handlers_.sendRouteBinaryMessage(routeId, new Uint8Array(data))
+      .then(function() {
+        return {'sent': true};
+      }, function() {
+        return {'sent': false};
+      });
+};
+
+/**
+ * Listen for messages from a route.
+ * @param {!string} routeId
+ */
+MediaRouteProvider.prototype.startListeningForRouteMessages = function(
+    routeId) {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.startListeningForRouteMessages(routeId);
+};
+
+/**
+ * @param {!string} routeId
+ */
+MediaRouteProvider.prototype.stopListeningForRouteMessages = function(
+    routeId) {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.stopListeningForRouteMessages(routeId);
+};
+
+/**
+ * Indicates that the presentation connection that was connected to |routeId|
+ * is no longer connected to it.
+ * @param {!string} routeId
+ */
+MediaRouteProvider.prototype.detachRoute = function(
+    routeId) {
+  this.handlers_.detachRoute(routeId);
+};
+
+/**
+ * Requests that the provider manager start sending information about active
+ * media routes to the Media Router.
+ * @param {!string} sourceUrn
+ */
+MediaRouteProvider.prototype.startObservingMediaRoutes = function(sourceUrn) {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.startObservingMediaRoutes(sourceUrn);
+};
+
+/**
+ * Requests that the provider manager stop sending information about active
+ * media routes to the Media Router.
+ * @param {!string} sourceUrn
+ */
+MediaRouteProvider.prototype.stopObservingMediaRoutes = function(sourceUrn) {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.stopObservingMediaRoutes(sourceUrn);
+};
+
+/**
+ * Enables mDNS device discovery.
+ */
+MediaRouteProvider.prototype.enableMdnsDiscovery = function() {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.enableMdnsDiscovery();
+};
+
+/**
+ * Requests that the provider manager update media sinks.
+ * @param {!string} sourceUrn
+ */
+MediaRouteProvider.prototype.updateMediaSinks = function(sourceUrn) {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.updateMediaSinks(sourceUrn);
+};
+
+/**
+ * Requests that the provider manager search its providers for a sink matching
+ * |searchCriteria| that is compatible with |sourceUrn|. If a sink is found
+ * that can be used immediately for route creation, its ID is returned.
+ * Otherwise the empty string is returned.
+ *
+ * @param {string} sinkId Sink ID of the pseudo sink generating the request.
+ * @param {string} sourceUrn Media source to be used with the sink.
+ * @param {!SinkSearchCriteria} searchCriteria Search criteria for the route
+ *     providers.
+ * @return {!Promise.<!{sink_id: !string}>} A Promise resolving to either the
+ *     sink ID of the sink found by the search that can be used for route
+ *     creation, or the empty string if no route can be immediately created.
+ */
+MediaRouteProvider.prototype.searchSinks = function(
+    sinkId, sourceUrn, searchCriteria) {
+  this.handlers_.onBeforeInvokeHandler();
+ return this.handlers_.searchSinks(sinkId, sourceUrn, searchCriteria).then(
+      sinkId => {
+        return { 'sinkId': sinkId };
+      },
+      () => {
+        return { 'sinkId': '' };
+      });
+};
+
+/**
+ * Notifies the provider manager that MediaRouter has discovered a list of
+ * sinks.
+ * @param {string} providerName
+ * @param {!Array<!mediaRouter.mojom.MediaSink>} sinks
+ */
+MediaRouteProvider.prototype.provideSinks = function(providerName, sinks) {
+  this.handlers_.onBeforeInvokeHandler();
+  this.handlers_.provideSinks(providerName,
+                              sinks.map(MediaSinkAdapter.fromNewVersion));
+};
+
+/**
+ * Creates a controller for the given route and binds the given
+ * InterfaceRequest to it, and registers an observer for media status updates
+ * for the route.
+ * @param {string} routeId
+ * @param {!mojo.InterfaceRequest} controllerRequest
+ * @param {!mediaRouter.mojom.MediaStatusObserverPtr} observer
+ * @return {!Promise<!{success: boolean}>} Resolves to true if a controller
+ *     is created. Resolves to false if a controller cannot be created, or if
+ *     the controller is already bound.
+ */
+MediaRouteProvider.prototype.createMediaRouteController = function(
+    routeId, controllerRequest, observer) {
+  this.handlers_.onBeforeInvokeHandler();
+  return this.handlers_.createMediaRouteController(
+      routeId, controllerRequest,
+      new MediaStatusObserverPtrAdapter(observer.ptr.passInterface())).then(
+          () => ({success: true}), e => ({success: false}));
+};
+
+var ptr = new mediaRouter.mojom.MediaRouterPtr;
+Mojo.bindInterface(mediaRouter.mojom.MediaRouter.name,
+                   mojo.makeRequest(ptr).handle);
+exports.$set('returnValue', new MediaRouter(ptr));
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 521f0f8..ce1e9fd 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -36,9 +36,6 @@
       "//mojo/common:common_custom_types",
       "//url/mojo:url_mojom_gurl",
     ]
-
-    # TODO(crbug.com/699569): Convert to use the new JS bindings.
-    js_bindings_mode = "both"
   }
 
   # This must be a static library because extensions common depends on
diff --git a/extensions/renderer/api/mojo_private/mojo_private_unittest.cc b/extensions/renderer/api/mojo_private/mojo_private_unittest.cc
index e73457e..9693bf28 100644
--- a/extensions/renderer/api/mojo_private/mojo_private_unittest.cc
+++ b/extensions/renderer/api/mojo_private/mojo_private_unittest.cc
@@ -36,10 +36,8 @@
 };
 
 TEST_F(MojoPrivateApiTest, RequireAsync) {
-  env()->RegisterModule("add",
-                        "define('add', [], function() {"
-                        "  return { Add: function(x, y) { return x + y; } };"
-                        "});");
+  env()->RegisterModule(
+      "add", "exports.$set('returnValue', function(x, y) { return x + y; });");
   ASSERT_NO_FATAL_FAILURE(
       RunTest("mojo_private_unittest.js", "testRequireAsync"));
 }
diff --git a/extensions/renderer/resources/mime_handler_private_custom_bindings.js b/extensions/renderer/resources/mime_handler_private_custom_bindings.js
index 262c549..6d09de8b 100644
--- a/extensions/renderer/resources/mime_handler_private_custom_bindings.js
+++ b/extensions/renderer/resources/mime_handler_private_custom_bindings.js
@@ -14,7 +14,9 @@
     'Streams are only available from a mime handler view guest.';
 var STREAM_ABORTED_ERROR = 'Stream has been aborted.';
 
-loadScript('mojo_bindings');
+if ((typeof mojo === 'undefined') || !mojo.bindingsLibraryInitialized) {
+  loadScript('mojo_bindings');
+}
 loadScript('extensions/common/api/mime_handler.mojom');
 
 var servicePtr = new extensions.mimeHandler.MimeHandlerServicePtr;
diff --git a/extensions/renderer/resources/mojo_private_custom_bindings.js b/extensions/renderer/resources/mojo_private_custom_bindings.js
index 548e3a3a..794471b 100644
--- a/extensions/renderer/resources/mojo_private_custom_bindings.js
+++ b/extensions/renderer/resources/mojo_private_custom_bindings.js
@@ -12,7 +12,7 @@
   let apiFunctions = bindingsAPI.apiFunctions;
 
   apiFunctions.setHandleRequest('requireAsync', function(moduleName) {
-    return requireAsync(moduleName);
+    return Promise.resolve(require(moduleName).returnValue);
   });
 });
 
diff --git a/extensions/test/data/mojo_private_unittest.js b/extensions/test/data/mojo_private_unittest.js
index 85a48da..cfdc9db 100644
--- a/extensions/test/data/mojo_private_unittest.js
+++ b/extensions/test/data/mojo_private_unittest.js
@@ -13,7 +13,7 @@
   function testRequireAsync() {
     mojoPrivate.requireAsync('add').then(
         test.callbackPass(function(add) {
-          test.assertEq('function', typeof add.Add);
+          test.assertEq('function', typeof add);
         }));
   },
 ], test.runTests, exports);
diff --git a/media/mojo/interfaces/BUILD.gn b/media/mojo/interfaces/BUILD.gn
index d49ed181..d7bacb7 100644
--- a/media/mojo/interfaces/BUILD.gn
+++ b/media/mojo/interfaces/BUILD.gn
@@ -69,9 +69,6 @@
   sources = [
     "remoting_common.mojom",
   ]
-
-  # TODO(crbug.com/699569): Convert to use the new JS bindings.
-  use_new_js_bindings = false
 }
 
 mojom("mirror_service_remoting") {
@@ -82,9 +79,6 @@
   public_deps = [
     ":remoting_common",
   ]
-
-  # TODO(crbug.com/699569): Convert to use the new JS bindings.
-  use_new_js_bindings = false
 }
 
 mojom("remoting") {
diff --git a/mojo/common/BUILD.gn b/mojo/common/BUILD.gn
index b43da6b..d9c8672 100644
--- a/mojo/common/BUILD.gn
+++ b/mojo/common/BUILD.gn
@@ -31,9 +31,6 @@
   if (is_win) {
     sources += [ "logfont_win.mojom" ]
   }
-
-  # TODO(crbug.com/699569): Convert to use the new JS bindings.
-  js_bindings_mode = "both"
 }
 
 mojom("read_only_buffer") {
diff --git a/net/interfaces/BUILD.gn b/net/interfaces/BUILD.gn
index cbccdbc..0778246 100644
--- a/net/interfaces/BUILD.gn
+++ b/net/interfaces/BUILD.gn
@@ -13,7 +13,4 @@
   public_deps = [
     "//url/mojo:url_mojom_gurl",
   ]
-
-  # TODO(crbug.com/699569): Convert to use the new JS bindings.
-  use_new_js_bindings = false
 }
diff --git a/url/mojo/BUILD.gn b/url/mojo/BUILD.gn
index 970ed7a..7d5e5d6 100644
--- a/url/mojo/BUILD.gn
+++ b/url/mojo/BUILD.gn
@@ -8,9 +8,6 @@
   sources = [
     "url.mojom",
   ]
-
-  # TODO(crbug.com/699569): Convert to use the new JS bindings.
-  js_bindings_mode = "both"
 }
 
 mojom("url_mojom_origin") {
@@ -21,9 +18,6 @@
   public_deps = [
     ":url_mojom_gurl",
   ]
-
-  # TODO(crbug.com/699569): Convert to use the new JS bindings.
-  js_bindings_mode = "both"
 }
 
 mojom("test_url_mojom_gurl") {