}
+ */
+MediaRouterContainerInterface.prototype.allSinks;
+
+/**
+ * The list of CastModes to show.
+ * @type {!Array|undefined}
+ */
+MediaRouterContainerInterface.prototype.castModeList;
+
+/**
+ * The URL to open when the device missing link is clicked.
+ * @type {string|undefined}
+ */
+MediaRouterContainerInterface.prototype.deviceMissingUrl;
+
+/**
+ * The URL to open when the cloud services pref learn more link is clicked.
+ * @type {string|undefined}
+ */
+MediaRouterContainerInterface.prototype.firstRunFlowCloudPrefLearnMoreUrl;
+
+/**
+ * The URL to open when the first run flow learn more link is clicked.
+ * @type {string|undefined}
+ */
+MediaRouterContainerInterface.prototype.firstRunFlowLearnMoreUrl;
+
+/**
+ * The header text for the sink list.
+ * @type {string|undefined}
+ */
+MediaRouterContainerInterface.prototype.headerText;
+
+/**
+ * The header text tooltip. This would be descriptive of the
+ * source origin, whether a host name, tab URL, etc.
+ * @type {string|undefined}
+ */
+MediaRouterContainerInterface.prototype.headerTextTooltip;
+
+/**
+ * The issue to show.
+ * @type {?media_router.Issue}
+ */
+MediaRouterContainerInterface.prototype.issue;
+
+/**
+ * The list of current routes.
+ * @type {!Array|undefined}
+ */
+MediaRouterContainerInterface.prototype.routeList;
+
+/**
+ * Whether the search input should be padded as if it were at the bottom of
+ * the dialog.
+ * @type {boolean}
+ */
+MediaRouterContainerInterface.prototype.searchUseBottomPadding;
+
+/**
+ * Whether to show the user domain of sinks associated with identity.
+ * @type {boolean|undefined}
+ */
+MediaRouterContainerInterface.prototype.showDomain;
+
+/**
+ * Whether to show the first run flow.
+ * @type {boolean|undefined}
+ */
+MediaRouterContainerInterface.prototype.showFirstRunFlow;
+
+/**
+ * Whether to show the cloud preference setting in the first run flow.
+ * @type {boolean|undefined}
+ */
+MediaRouterContainerInterface.prototype.showFirstRunFlowCloudPref;
+
+/**
+ * Whether the WebUI route controls should be shown instead of the
+ * extensionview in the route details view.
+ * @type {boolean}
+ */
+MediaRouterContainerInterface.prototype.useWebUiRouteControls;
+
+/**
+ * Fires a 'report-initial-action' event when the user takes their first
+ * action after the dialog opens. Also fires a 'report-initial-action-close'
+ * event if that initial action is to close the dialog.
+ * @param {!media_router.MediaRouterUserAction} initialAction
+ */
+MediaRouterContainerInterface.prototype.maybeReportUserFirstAction = function(
+ initialAction) {};
+
+/**
+ * Updates |currentView_| if the dialog had just opened and there's
+ * only one local route.
+ */
+MediaRouterContainerInterface.prototype.maybeShowRouteDetailsOnOpen =
+ function() {};
+
+/**
+ * Handles response of previous create route attempt.
+ * @param {string} sinkId The ID of the sink to which the Media Route was
+ * creating a route.
+ * @param {?media_router.Route} route The newly created route that
+ * corresponds to the sink if route creation succeeded; null otherwise.
+ * @param {boolean} isForDisplay Whether or not |route| is for display.
+ */
+MediaRouterContainerInterface.prototype.onCreateRouteResponseReceived =
+ function(sinkId, route, isForDisplay) {};
+
+/**
+ * Called when a search has completed up to route creation. |sinkId|
+ * identifies the sink that should be in |allSinks|, if a sink was found.
+ * @param {string} sinkId The ID of the sink that is the result of the
+ * currently pending search.
+ */
+MediaRouterContainerInterface.prototype.onReceiveSearchResult = function(
+ sinkId) {};
+
+/**
+ * Called when the connection to the route controller is invalidated. Switches
+ * from route details view to the sink list view.
+ */
+MediaRouterContainerInterface.prototype.onRouteControllerInvalidated =
+ function() {};
+
+/**
+ * Sets the selected cast mode to the one associated with |castModeType|,
+ * and rebuilds sinks to reflect the change.
+ * @param {number} castModeType The type of the selected cast mode.
+ */
+MediaRouterContainerInterface.prototype.selectCastMode = function(
+ castModeType) {};
+
+/**
+ * Update the max dialog height and update the positioning of the elements.
+ * @param {number} height The max height of the Media Router dialog.
+ */
+MediaRouterContainerInterface.prototype.updateMaxDialogHeight = function(
+ height) {};
diff --git a/chrome/browser/resources/media_router/elements/route_controls/compiled_resources2.gyp b/chrome/browser/resources/media_router/elements/route_controls/compiled_resources2.gyp
new file mode 100644
index 00000000000000..cb312aec6609c3
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_controls/compiled_resources2.gyp
@@ -0,0 +1,25 @@
+# Copyright 2017 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.
+{
+ 'targets': [
+ {
+ 'target_name': 'route_controls',
+ 'dependencies': [
+ '../../compiled_resources2.gyp:media_router_browser_api',
+ '../../compiled_resources2.gyp:media_router_data',
+ '../../compiled_resources2.gyp:media_router_ui_interface',
+ '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior',
+ '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
+ ],
+ 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'],
+ },
+ {
+ 'target_name': 'route_controls_interface',
+ 'dependencies': [
+ '../../compiled_resources2.gyp:media_router_data',
+ ],
+ 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'],
+ },
+ ],
+}
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.css b/chrome/browser/resources/media_router/elements/route_controls/route_controls.css
new file mode 100644
index 00000000000000..be9d9edebc2faa
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.css
@@ -0,0 +1,76 @@
+/* Copyright 2017 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. */
+
+#button-holder {
+ float: left;
+}
+
+#current-time {
+ left: 20px;
+ position: absolute;
+}
+
+#duration {
+ position: absolute;
+ right: 20px;
+}
+
+.ellipsis {
+ padding: 0 1%;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 90%;
+}
+
+#media-controls {
+ font-size: 1.25em;
+ margin: 0 8px;
+}
+
+#play-pause-volume-controls {
+ display: block;
+ margin-top: 13px;
+ overflow: hidden;
+}
+
+#route-description {
+ margin: 15px 8px 3px 8px;
+ overflow: hidden;
+}
+
+#route-time-controls {
+ display: block;
+ margin-top: 3px;
+ overflow: hidden;
+}
+
+#route-time-slider {
+ --paper-slider-knob-color: rgb(16, 16, 16);
+ --paper-slider-active-color: rgb(16, 16, 16);
+ --paper-slider-pin-color: rgb(16, 16, 16);
+ width: 100%;
+}
+
+#route-title {
+ color: rgb(125, 125, 125);
+ margin: 3px 8px;
+ overflow: hidden;
+}
+
+#route-volume-slider {
+ --paper-slider-knob-color: rgb(16, 16, 16);
+ --paper-slider-active-color: rgb(33, 150, 243);
+ --paper-slider-pin-color: rgb(16, 16, 16);
+ width: 100%;
+}
+
+#timeline {
+ font-size: 0.75em;
+}
+
+#volume-holder {
+ display: block;
+ overflow: hidden;
+ padding: 0.3em 0;
+}
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.html b/chrome/browser/resources/media_router/elements/route_controls/route_controls.html
new file mode 100644
index 00000000000000..b6ca6c4ae017c9
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.js b/chrome/browser/resources/media_router/elements/route_controls/route_controls.js
new file mode 100644
index 00000000000000..4d3a65bc61b8b8
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.js
@@ -0,0 +1,261 @@
+// Copyright 2017 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.
+
+/**
+ * This Polymer element shows media controls for a route that is currently cast
+ * to a device.
+ * @implements {RouteControlsInterface}
+ */
+Polymer({
+ is: 'route-controls',
+
+ properties: {
+ /**
+ * The current time displayed in seconds, before formatting.
+ * @private {number}
+ */
+ displayedCurrentTime_: {
+ type: Number,
+ value: 0,
+ },
+
+ /**
+ * The media description to display. Uses route description if none is
+ * provided by the route status object.
+ * @private {string}
+ */
+ displayedDescription_: {
+ type: String,
+ value: '',
+ },
+
+ /**
+ * The volume shown in the volume control, between 0 and 1.
+ * @private {number}
+ */
+ displayedVolume_: {
+ type: Number,
+ value: 0,
+ },
+
+ /**
+ * Set to true when the user is dragging the seek bar. Updates for the
+ * current time from the browser will be ignored when set to true.
+ * @private {boolean}
+ */
+ isSeeking_: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * Set to true when the user is dragging the volume bar. Volume updates from
+ * the browser will be ignored when set to true.
+ * @private {boolean}
+ */
+ isVolumeChanging_: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * The status of the media route shown.
+ * @type {!media_router.RouteStatus}
+ */
+ routeStatus: {
+ type: Object,
+ observer: 'onRouteStatusChange_',
+ value: new media_router.RouteStatus(
+ '', '', false, false, false, false, false, false, 0, 0, 0),
+ },
+ },
+
+ behaviors: [
+ I18nBehavior,
+ ],
+
+ /**
+ * Called by Polymer when the element loads. Registers the element to be
+ * notified of route status updates.
+ */
+ ready: function() {
+ media_router.ui.setRouteControls(
+ /** @type {RouteControlsInterface} */ (this));
+ },
+
+ /**
+ * Converts a number representing an interval of seconds to a string with
+ * HH:MM:SS format.
+ * @param {number} timeInSec Must be non-negative. Intervals longer than 100
+ * hours get truncated silently.
+ * @return {string}
+ *
+ * @private
+ */
+ getFormattedTime_: function(timeInSec) {
+ if (timeInSec < 0) {
+ return '';
+ }
+ var hours = Math.floor(timeInSec / 3600);
+ var minutes = Math.floor(timeInSec / 60) % 60;
+ var seconds = Math.floor(timeInSec) % 60;
+ return ('0' + hours).substr(-2) + ':' + ('0' + minutes).substr(-2) + ':' +
+ ('0' + seconds).substr(-2);
+ },
+
+ /**
+ * @param {!media_router.RouteStatus} routeStatus
+ * @return {string} The value for the icon attribute of the mute/unmute
+ * button.
+ *
+ * @private
+ */
+ getMuteUnmuteIcon_: function(routeStatus) {
+ return routeStatus.isMuted ? 'av:volume-off' : 'av:volume-up';
+ },
+
+ /**
+ * @param {!media_router.RouteStatus} routeStatus
+ * @return {string} Localized title for the mute/unmute button.
+ *
+ * @private
+ */
+ getMuteUnmuteTitle_: function(routeStatus) {
+ return routeStatus.isMuted ? this.i18n('unmuteTitle') :
+ this.i18n('muteTitle');
+ },
+
+ /**
+ * @param {!media_router.RouteStatus} routeStatus
+ * @return {string}The value for the icon attribute of the play/pause button.
+ *
+ * @private
+ */
+ getPlayPauseIcon_: function(routeStatus) {
+ return routeStatus.isPaused ? 'av:play-arrow' : 'av:pause';
+ },
+
+ /**
+ * @param {!media_router.RouteStatus} routeStatus
+ * @return {string} Localized title for the play/pause button.
+ *
+ * @private
+ */
+ getPlayPauseTitle_: function(routeStatus) {
+ return routeStatus.isPaused ? this.i18n('playTitle') :
+ this.i18n('pauseTitle');
+ },
+
+ /**
+ * Called when the user toggles the mute status of the media. Sends a mute or
+ * unmute command to the browser.
+ *
+ * @private
+ */
+ onMuteUnmute_: function() {
+ media_router.browserApi.setCurrentMediaMute(!this.routeStatus.isMuted);
+ },
+
+ /**
+ * Called when the user toggles between playing and pausing the media. Sends a
+ * play or pause command to the browser.
+ *
+ * @private
+ */
+ onPlayPause_: function() {
+ if (this.routeStatus.isPaused) {
+ media_router.browserApi.playCurrentMedia();
+ } else {
+ media_router.browserApi.pauseCurrentMedia();
+ }
+ },
+
+ /**
+ * Resets the route controls. Called when the route details view is closed.
+ */
+ reset: function() {
+ this.routeStatus = new media_router.RouteStatus(
+ '', '', false, false, false, false, false, false, 0, 0, 0);
+ media_router.ui.setRouteControls(null);
+ },
+
+ /**
+ * Updates seek and volume bars if the user is not currently dragging on
+ * them.
+ * @param {!media_router.RouteStatus} newRouteStatus
+ *
+ * @private
+ */
+ onRouteStatusChange_: function(newRouteStatus) {
+ if (!this.isSeeking_) {
+ this.displayedCurrentTime_ = newRouteStatus.currentTime;
+ }
+ if (!this.isVolumeChanging_) {
+ this.displayedVolume_ = newRouteStatus.volume;
+ }
+ if (newRouteStatus.description !== '') {
+ this.displayedDescription_ = newRouteStatus.description;
+ }
+ },
+
+ /**
+ * Called when the route is updated. Updates the description shown if it has
+ * not been provided by status updates.
+ * @param {!media_router.Route} route
+ */
+ onRouteUpdated: function(route) {
+ if (this.routeStatus.description === '') {
+ this.displayedDescription_ =
+ loadTimeData.getStringF('castingActivityStatus', route.description);
+ }
+ },
+
+ /**
+ * Called when the user clicks on or stops dragging the seek bar.
+ * @param {!Event} e
+ *
+ * @private
+ */
+ onSeekComplete_: function(e) {
+ this.isSeeking_ = false;
+ this.displayedCurrentTime_ = e.target.value;
+ media_router.browserApi.seekCurrentMedia(this.displayedCurrentTime_);
+ },
+
+ /**
+ * Called when the user starts dragging the seek bar.
+ * @param {!Event} e
+ *
+ * @private
+ */
+ onSeekStart_: function(e) {
+ this.isSeeking_ = true;
+ var target = /** @type {{immediateValue: number}} */ (e.target);
+ this.displayedCurrentTime_ = target.immediateValue;
+ },
+
+ /**
+ * Called when the user clicks on or stops dragging the volume bar.
+ * @param {!Event} e
+ *
+ * @private
+ */
+ onVolumeChangeComplete_: function(e) {
+ this.isVolumeChanging_ = false;
+ this.volumeSliderValue_ = e.target.value;
+ media_router.browserApi.setCurrentMediaVolume(this.volumeSliderValue_);
+ },
+
+ /**
+ * Called when the user starts dragging the volume bar.
+ * @param {!Event} e
+ *
+ * @private
+ */
+ onVolumeChangeStart_: function(e) {
+ this.isVolumeChanging_ = true;
+ var target = /** @type {{immediateValue: number}} */ (e.target);
+ this.volumeSliderValue_ = target.immediateValue;
+ },
+});
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls_interface.js b/chrome/browser/resources/media_router/elements/route_controls/route_controls_interface.js
new file mode 100644
index 00000000000000..93aefe5db756bc
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls_interface.js
@@ -0,0 +1,27 @@
+// Copyright 2017 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.
+
+/**
+ * Interface for the Polymer element that shows media controls for a route that
+ * is currently cast to a device.
+ * @record
+ */
+function RouteControlsInterface() {}
+
+/**
+ * @type {!media_router.RouteStatus}
+ */
+RouteControlsInterface.prototype.routeStatus;
+
+/**
+ * Resets the route controls. Called when the route details view is closed.
+ */
+RouteControlsInterface.prototype.reset = function() {};
+
+/**
+ * Called when the route is updated. Updates the description shown if it has
+ * not been provided by status updates.
+ * @param {!media_router.Route} route
+ */
+RouteControlsInterface.prototype.onRouteUpdated = function(route) {};
diff --git a/chrome/browser/resources/media_router/elements/route_details/compiled_resources2.gyp b/chrome/browser/resources/media_router/elements/route_details/compiled_resources2.gyp
index 92f14fd00ccb72..ea2470c0b281fa 100644
--- a/chrome/browser/resources/media_router/elements/route_details/compiled_resources2.gyp
+++ b/chrome/browser/resources/media_router/elements/route_details/compiled_resources2.gyp
@@ -6,7 +6,10 @@
{
'target_name': 'route_details',
'dependencies': [
+ 'extension_view_wrapper/compiled_resources2.gyp:extension_view_wrapper',
'../../compiled_resources2.gyp:media_router_data',
+ '../../compiled_resources2.gyp:media_router_ui_interface',
+ '../route_controls/compiled_resources2.gyp:route_controls',
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior',
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
],
diff --git a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/compiled_resources2.gyp b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/compiled_resources2.gyp
new file mode 100644
index 00000000000000..b7d8fe1d144892
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/compiled_resources2.gyp
@@ -0,0 +1,14 @@
+# Copyright 2017 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.
+{
+ 'targets': [
+ {
+ 'target_name': 'extension_view_wrapper',
+ 'dependencies': [
+ '../../../compiled_resources2.gyp:media_router_data',
+ ],
+ 'includes': ['../../../../../../../third_party/closure_compiler/compile_js2.gypi'],
+ },
+ ],
+}
diff --git a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.css b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.css
new file mode 100644
index 00000000000000..bd41df11035f08
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.css
@@ -0,0 +1,9 @@
+/* Copyright 2017 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. */
+
+#custom-controller {
+ display: inline-block;
+ height: 142px;
+ width: 100%;
+}
diff --git a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.html b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.html
new file mode 100644
index 00000000000000..9b33d90aedd3be
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.js b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.js
new file mode 100644
index 00000000000000..997e7e081efd4c
--- /dev/null
+++ b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.js
@@ -0,0 +1,64 @@
+// Copyright 2017 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.
+
+// This Polymer element shows the custom controller for a route using
+// extensionview.
+Polymer({
+ is: 'extension-view-wrapper',
+
+ properties: {
+ /**
+ * The route to show the custom controller for.
+ * @type {?media_router.Route|undefined}
+ */
+ route: {
+ type: Object,
+ observer: 'maybeLoadExtensionView_',
+ },
+
+ /**
+ * Whether the extension view is ready to be shown.
+ * @type {boolean}
+ */
+ isExtensionViewReady: {
+ type: Boolean,
+ value: false,
+ notify: true,
+ },
+ },
+
+ /**
+ * Called by Polymer when this module has loaded.
+ */
+ ready: function() {
+ this.maybeLoadExtensionView_();
+ },
+
+ /**
+ * Loads the custom controller if the controller path for the current route is
+ * valid.
+ */
+ maybeLoadExtensionView_: function() {
+ var extensionview = this.$['custom-controller'];
+
+ // Do nothing if the controller path doesn't exist or is already shown in
+ // the extension view.
+ if (!this.route || !this.route.customControllerPath ||
+ this.route.customControllerPath == extensionview.src) {
+ return;
+ }
+
+ var that = this;
+ extensionview.load(this.route.customControllerPath)
+ .then(
+ function() {
+ // Load was successful; show the custom controller.
+ that.isExtensionViewReady = true;
+ },
+ function() {
+ // Load was unsuccessful; fall back to default view.
+ that.isExtensionViewReady = false;
+ });
+ },
+});
diff --git a/chrome/browser/resources/media_router/elements/route_details/route_details.css b/chrome/browser/resources/media_router/elements/route_details/route_details.css
index 9f1f1dbf6ddf3c..742647da622fd9 100644
--- a/chrome/browser/resources/media_router/elements/route_details/route_details.css
+++ b/chrome/browser/resources/media_router/elements/route_details/route_details.css
@@ -2,12 +2,6 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
-#custom-controller {
- display: inline-block;
- height: 142px;
- width: 100%;
-}
-
#route-action-buttons {
@apply(--layout-horizontal);
@apply(--layout-end-justified);
diff --git a/chrome/browser/resources/media_router/elements/route_details/route_details.html b/chrome/browser/resources/media_router/elements/route_details/route_details.html
index 61043b440754fe..1313fb20762219 100644
--- a/chrome/browser/resources/media_router/elements/route_details/route_details.html
+++ b/chrome/browser/resources/media_router/elements/route_details/route_details.html
@@ -1,16 +1,25 @@
+
+
-
+
[[activityStatus_]]
-
-
+
+
+
+
+
+
+
+
diff --git a/chrome/browser/resources/media_router/media_router.js b/chrome/browser/resources/media_router/media_router.js
index d3bc326a7711b4..206f1f4494e464 100644
--- a/chrome/browser/resources/media_router/media_router.js
+++ b/chrome/browser/resources/media_router/media_router.js
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
//
//
@@ -216,7 +217,7 @@ cr.define('media_router', function() {
* selectedCastModeValue - cast mode selected by the user.
*/
function onCreateRoute(event) {
- /** @type {{sinkId: string, selectedCastModeValue, number}} */
+ /** @type {{sinkId: string, selectedCastModeValue: number}} */
var detail = event.detail;
media_router.browserApi.requestRoute(detail.sinkId,
detail.selectedCastModeValue);
diff --git a/chrome/browser/resources/media_router/media_router_browser_api.js b/chrome/browser/resources/media_router/media_router_browser_api.js
new file mode 100644
index 00000000000000..482decffa92a71
--- /dev/null
+++ b/chrome/browser/resources/media_router/media_router_browser_api.js
@@ -0,0 +1,319 @@
+// Copyright 2017 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.
+
+// API invoked by this UI to communicate with the browser WebUI message handler.
+cr.define('media_router.browserApi', function() {
+ 'use strict';
+
+ /**
+ * Indicates that the user has acknowledged the first run flow.
+ *
+ * @param {boolean} optedIntoCloudServices Whether or not the user opted into
+ * cloud services.
+ */
+ function acknowledgeFirstRunFlow(optedIntoCloudServices) {
+ chrome.send('acknowledgeFirstRunFlow', [optedIntoCloudServices]);
+ }
+
+ /**
+ * Acts on the given issue.
+ *
+ * @param {number} issueId
+ * @param {number} actionType Type of action that the user clicked.
+ * @param {?number} helpPageId The numeric help center ID.
+ */
+ function actOnIssue(issueId, actionType, helpPageId) {
+ chrome.send(
+ 'actOnIssue',
+ [{issueId: issueId, actionType: actionType, helpPageId: helpPageId}]);
+ }
+
+ /**
+ * Modifies |route| by changing its source to the one identified by
+ * |selectedCastMode|.
+ *
+ * @param {!media_router.Route} route The route being modified.
+ * @param {number} selectedCastMode The value of the cast mode the user
+ * selected.
+ */
+ function changeRouteSource(route, selectedCastMode) {
+ chrome.send(
+ 'requestRoute',
+ [{sinkId: route.sinkId, selectedCastMode: selectedCastMode}]);
+ }
+
+ /**
+ * Closes the dialog.
+ *
+ * @param {boolean} pressEscToClose Whether the user pressed ESC to close the
+ * dialog.
+ */
+ function closeDialog(pressEscToClose) {
+ chrome.send('closeDialog', [pressEscToClose]);
+ }
+
+ /**
+ * Closes the given route.
+ *
+ * @param {!media_router.Route} route
+ */
+ function closeRoute(route) {
+ chrome.send('closeRoute', [{routeId: route.id, isLocal: route.isLocal}]);
+ }
+
+ /**
+ * Joins the given route.
+ *
+ * @param {!media_router.Route} route
+ */
+ function joinRoute(route) {
+ chrome.send('joinRoute', [{sinkId: route.sinkId, routeId: route.id}]);
+ }
+
+ /**
+ * Indicates that the initial data has been received.
+ */
+ function onInitialDataReceived() {
+ chrome.send('onInitialDataReceived');
+ }
+
+ /**
+ * Reports that the route details view was closed.
+ */
+ function onMediaControllerClosed() {
+ chrome.send('onMediaControllerClosed');
+ }
+
+ /**
+ * Reports that the route details view was opened for |routeId|.
+ *
+ * @param {string} routeId
+ */
+ function onMediaControllerAvailable(routeId) {
+ chrome.send('onMediaControllerAvailable', [{routeId: routeId}]);
+ }
+
+ /**
+ * Sends a command to pause the route shown in the route details view.
+ */
+ function pauseCurrentMedia() {
+ chrome.send('pauseCurrentMedia');
+ }
+
+ /**
+ * Sends a command to play the route shown in the route details view.
+ */
+ function playCurrentMedia() {
+ chrome.send('playCurrentMedia');
+ }
+
+ /**
+ * Reports when the user clicks outside the dialog.
+ */
+ function reportBlur() {
+ chrome.send('reportBlur');
+ }
+
+ /**
+ * Reports the index of the selected sink.
+ *
+ * @param {number} sinkIndex
+ */
+ function reportClickedSinkIndex(sinkIndex) {
+ chrome.send('reportClickedSinkIndex', [sinkIndex]);
+ }
+
+ /**
+ * Reports that the user used the filter input.
+ */
+ function reportFilter() {
+ chrome.send('reportFilter');
+ }
+
+ /**
+ * Reports the initial dialog view.
+ *
+ * @param {string} view
+ */
+ function reportInitialState(view) {
+ chrome.send('reportInitialState', [view]);
+ }
+
+ /**
+ * Reports the initial action the user took.
+ *
+ * @param {number} action
+ */
+ function reportInitialAction(action) {
+ chrome.send('reportInitialAction', [action]);
+ }
+
+ /**
+ * Reports the navigation to the specified view.
+ *
+ * @param {string} view
+ */
+ function reportNavigateToView(view) {
+ chrome.send('reportNavigateToView', [view]);
+ }
+
+ /**
+ * Reports whether or not a route was created successfully.
+ *
+ * @param {boolean} success
+ */
+ function reportRouteCreation(success) {
+ chrome.send('reportRouteCreation', [success]);
+ }
+
+ /**
+ * Reports the outcome of a create route response.
+ *
+ * @param {number} outcome
+ */
+ function reportRouteCreationOutcome(outcome) {
+ chrome.send('reportRouteCreationOutcome', [outcome]);
+ }
+
+ /**
+ * Reports the cast mode that the user selected.
+ *
+ * @param {number} castModeType
+ */
+ function reportSelectedCastMode(castModeType) {
+ chrome.send('reportSelectedCastMode', [castModeType]);
+ }
+
+ /**
+ * Reports the current number of sinks.
+ *
+ * @param {number} sinkCount
+ */
+ function reportSinkCount(sinkCount) {
+ chrome.send('reportSinkCount', [sinkCount]);
+ }
+
+ /**
+ * Reports the time it took for the user to select a sink after the sink list
+ * is populated and shown.
+ *
+ * @param {number} timeMs
+ */
+ function reportTimeToClickSink(timeMs) {
+ chrome.send('reportTimeToClickSink', [timeMs]);
+ }
+
+ /**
+ * Reports the time, in ms, it took for the user to close the dialog without
+ * taking any other action.
+ *
+ * @param {number} timeMs
+ */
+ function reportTimeToInitialActionClose(timeMs) {
+ chrome.send('reportTimeToInitialActionClose', [timeMs]);
+ }
+
+ /**
+ * Requests data to initialize the WebUI with.
+ * The data will be returned via media_router.ui.setInitialData.
+ */
+ function requestInitialData() {
+ chrome.send('requestInitialData');
+ }
+
+ /**
+ * Requests that a media route be started with the given sink.
+ *
+ * @param {string} sinkId The sink ID.
+ * @param {number} selectedCastMode The value of the cast mode the user
+ * selected.
+ */
+ function requestRoute(sinkId, selectedCastMode) {
+ chrome.send(
+ 'requestRoute', [{sinkId: sinkId, selectedCastMode: selectedCastMode}]);
+ }
+
+ /**
+ * Requests that the media router search all providers for a sink matching
+ * |searchCriteria| that can be used with the media source associated with the
+ * cast mode |selectedCastMode|. If such a sink is found, a route is also
+ * created between the sink and the media source.
+ *
+ * @param {string} sinkId Sink ID of the pseudo sink generating the request.
+ * @param {string} searchCriteria Search criteria for the route providers.
+ * @param {string} domain User's current hosted domain.
+ * @param {number} selectedCastMode The value of the cast mode to be used with
+ * the sink.
+ */
+ function searchSinksAndCreateRoute(
+ sinkId, searchCriteria, domain, selectedCastMode) {
+ chrome.send('searchSinksAndCreateRoute', [{
+ sinkId: sinkId,
+ searchCriteria: searchCriteria,
+ domain: domain,
+ selectedCastMode: selectedCastMode
+ }]);
+ }
+
+ /**
+ * Sends a command to seek the route shown in the route details view.
+ *
+ * @param {number} time The new current time in seconds.
+ */
+ function seekCurrentMedia(time) {
+ chrome.send('seekCurrentMedia', [{time: time}]);
+ }
+
+ /**
+ * Sends a command to mute or unmute the route shown in the route details
+ * view.
+ *
+ * @param {boolean} mute Mute the route if true, unmute it if false.
+ */
+ function setCurrentMediaMute(mute) {
+ chrome.send('setCurrentMediaMute', [{mute: mute}]);
+ }
+
+ /**
+ * Sends a command to change the volume of the route shown in the route
+ * details view.
+ *
+ * @param {number} volume The volume between 0 and 1.
+ */
+ function setCurrentMediaVolume(volume) {
+ chrome.send('setCurrentMediaVolume', [{volume: volume}]);
+ }
+
+ return {
+ acknowledgeFirstRunFlow: acknowledgeFirstRunFlow,
+ actOnIssue: actOnIssue,
+ changeRouteSource: changeRouteSource,
+ closeDialog: closeDialog,
+ closeRoute: closeRoute,
+ joinRoute: joinRoute,
+ onInitialDataReceived: onInitialDataReceived,
+ onMediaControllerClosed: onMediaControllerClosed,
+ onMediaControllerAvailable: onMediaControllerAvailable,
+ pauseCurrentMedia: pauseCurrentMedia,
+ playCurrentMedia: playCurrentMedia,
+ reportBlur: reportBlur,
+ reportClickedSinkIndex: reportClickedSinkIndex,
+ reportFilter: reportFilter,
+ reportInitialAction: reportInitialAction,
+ reportInitialState: reportInitialState,
+ reportNavigateToView: reportNavigateToView,
+ reportRouteCreation: reportRouteCreation,
+ reportRouteCreationOutcome: reportRouteCreationOutcome,
+ reportSelectedCastMode: reportSelectedCastMode,
+ reportSinkCount: reportSinkCount,
+ reportTimeToClickSink: reportTimeToClickSink,
+ reportTimeToInitialActionClose: reportTimeToInitialActionClose,
+ requestInitialData: requestInitialData,
+ requestRoute: requestRoute,
+ searchSinksAndCreateRoute: searchSinksAndCreateRoute,
+ seekCurrentMedia: seekCurrentMedia,
+ setCurrentMediaMute: setCurrentMediaMute,
+ setCurrentMediaVolume: setCurrentMediaVolume,
+ };
+});
diff --git a/chrome/browser/resources/media_router/media_router_common.css b/chrome/browser/resources/media_router/media_router_common.css
index 767fd1ed896107..dd22f1cc1058ac 100644
--- a/chrome/browser/resources/media_router/media_router_common.css
+++ b/chrome/browser/resources/media_router/media_router_common.css
@@ -5,6 +5,7 @@
:root {
--dialog-padding-end: 26px;
--dialog-padding-start: 16px;
+ --dialog-width: 340px;
--navigation-icon-button-size: 36px;
--non-navigation-icon-size: 16px;
}
diff --git a/chrome/browser/resources/media_router/media_router_data.js b/chrome/browser/resources/media_router/media_router_data.js
index 674fd2bab86045..441ea8e86ea796 100644
--- a/chrome/browser/resources/media_router/media_router_data.js
+++ b/chrome/browser/resources/media_router/media_router_data.js
@@ -23,6 +23,16 @@ media_router.CastModeType = {
DESKTOP_MIRROR: 0x4,
};
+/**
+ * Route controller types that can be shown in the route details view.
+ * @enum {number}
+ */
+media_router.ControllerType = {
+ NONE: 0,
+ WEBUI: 1,
+ EXTENSION: 2,
+};
+
/**
* The ESC key maps to KeyboardEvent.key value 'Escape'.
* @const {string}
@@ -212,6 +222,61 @@ cr.define('media_router', function() {
this.customControllerPath = customControllerPath;
};
+ /**
+ * @param {string} title The title of the route.
+ * @param {string} description A description for the route.
+ * @param {boolean} canPlayPause Whether the route can be played/paused.
+ * @param {boolean} canMute Whether the route can be muted/unmuted.
+ * @param {boolean} canSetVolume Whether the route volume can be changed.
+ * @param {boolean} canSeek Whether the route's playback position can be
+ * changed.
+ * @param {boolean} isPaused Whether the route is paused.
+ * @param {boolean} isMuted Whether the route is muted.
+ * @param {number} volume The route's volume, between 0 and 1.
+ * @param {number} duration The route's duration in seconds.
+ * @param {number} currentTime The route's current position in seconds.
+ * Must not be greater than |duration|.
+ * @constructor
+ * @struct
+ */
+ var RouteStatus = function(
+ title, description, canPlayPause, canMute, canSetVolume, canSeek,
+ isPaused, isMuted, volume, duration, currentTime) {
+
+ /** @type {string} */
+ this.title = title;
+
+ /** @type {string} */
+ this.description = description;
+
+ /** @type {boolean} */
+ this.canPlayPause = canPlayPause;
+
+ /** @type {boolean} */
+ this.canMute = canMute;
+
+ /** @type {boolean} */
+ this.canSetVolume = canSetVolume;
+
+ /** @type {boolean} */
+ this.canSeek = canSeek;
+
+ /** @type {boolean} */
+ this.isPaused = isPaused;
+
+ /** @type {boolean} */
+ this.isMuted = isMuted;
+
+ /** @type {number} */
+ this.volume = volume;
+
+ /** @type {number} */
+ this.duration = duration;
+
+ /** @type {number} */
+ this.currentTime = currentTime;
+ };
+
/**
* @param {string} id The ID of the media sink.
@@ -271,6 +336,7 @@ cr.define('media_router', function() {
CastMode: CastMode,
Issue: Issue,
Route: Route,
+ RouteStatus: RouteStatus,
Sink: Sink,
TabInfo: TabInfo,
};
diff --git a/chrome/browser/resources/media_router/media_router_ui_interface.js b/chrome/browser/resources/media_router/media_router_ui_interface.js
index a8ec54461a248a..abe9a27f3ce225 100644
--- a/chrome/browser/resources/media_router/media_router_ui_interface.js
+++ b/chrome/browser/resources/media_router/media_router_ui_interface.js
@@ -13,6 +13,9 @@ cr.define('media_router.ui', function() {
// The media-router-header element.
var header = null;
+ // The route-controls element. Is null if the route details view isn't open.
+ var routeControls = null;
+
/**
* Handles response of previous create route attempt.
*
@@ -26,6 +29,14 @@ cr.define('media_router.ui', function() {
container.onCreateRouteResponseReceived(sinkId, route, isForDisplay);
}
+ /**
+ * Called when the route controller for the route that is currently selected
+ * is invalidated.
+ */
+ function onRouteControllerInvalidated() {
+ container.onRouteControllerInvalidated();
+ }
+
/**
* Handles the search response by forwarding |sinkId| to the container.
*
@@ -47,7 +58,7 @@ cr.define('media_router.ui', function() {
/**
* Sets |container| and |header|.
*
- * @param {!MediaRouterContainerElement} mediaRouterContainer
+ * @param {!MediaRouterContainerInterface} mediaRouterContainer
* @param {!MediaRouterHeaderElement} mediaRouterHeader
*/
function setElements(mediaRouterContainer, mediaRouterHeader) {
@@ -75,10 +86,8 @@ cr.define('media_router.ui', function() {
function setFirstRunFlowData(data) {
container.firstRunFlowCloudPrefLearnMoreUrl =
data['firstRunFlowCloudPrefLearnMoreUrl'];
- container.firstRunFlowLearnMoreUrl =
- data['firstRunFlowLearnMoreUrl'];
- container.showFirstRunFlowCloudPref =
- data['showFirstRunFlowCloudPref'];
+ container.firstRunFlowLearnMoreUrl = data['firstRunFlowLearnMoreUrl'];
+ container.showFirstRunFlowCloudPref = data['showFirstRunFlowCloudPref'];
// Some users acknowledged the first run flow before the cloud prefs
// setting was implemented. These users will see the first run flow
// again.
@@ -102,6 +111,7 @@ cr.define('media_router.ui', function() {
* Parameters in data:
* deviceMissingUrl - url to be opened on "Device missing?" clicked.
* sinksAndIdentity - list of sinks to be displayed and user identity.
+ * useWebUiRouteControls - whether new WebUI route controls should be used.
* routes - list of routes that are associated with the sinks.
* castModes - list of available cast modes.
* useTabMirroring - whether the cast mode should be set to TAB_MIRROR.
@@ -110,6 +120,7 @@ cr.define('media_router.ui', function() {
container.deviceMissingUrl = data['deviceMissingUrl'];
container.castModeList = data['castModes'];
this.setSinkListAndIdentity(data['sinksAndIdentity']);
+ container.useWebUiRouteControls = !!data['useWebUiRouteControls'];
container.routeList = data['routes'];
container.maybeShowRouteDetailsOnOpen();
if (data['useTabMirroring'])
@@ -127,6 +138,16 @@ cr.define('media_router.ui', function() {
container.issue = issue;
}
+ /**
+ * Sets |routeControls|. The argument may be null if the route details view is
+ * getting closed.
+ *
+ * @param {?RouteControlsInterface} mediaRouterRouteControls
+ */
+ function setRouteControls(mediaRouterRouteControls) {
+ routeControls = mediaRouterRouteControls;
+ }
+
/**
* Sets the list of currently active routes.
*
@@ -166,263 +187,30 @@ cr.define('media_router.ui', function() {
container.updateMaxDialogHeight(height);
}
+ /**
+ * Updates the route status shown in the route controls.
+ *
+ * @param {!media_router.RouteStatus} status
+ */
+ function updateRouteStatus(status) {
+ if (routeControls) {
+ routeControls.routeStatus = status;
+ }
+ }
+
return {
onCreateRouteResponseReceived: onCreateRouteResponseReceived,
+ onRouteControllerInvalidated: onRouteControllerInvalidated,
receiveSearchResult: receiveSearchResult,
setCastModeList: setCastModeList,
setElements: setElements,
setFirstRunFlowData: setFirstRunFlowData,
setInitialData: setInitialData,
setIssue: setIssue,
+ setRouteControls: setRouteControls,
setRouteList: setRouteList,
setSinkListAndIdentity: setSinkListAndIdentity,
updateMaxHeight: updateMaxHeight,
- };
-});
-
-// API invoked by this UI to communicate with the browser WebUI message handler.
-cr.define('media_router.browserApi', function() {
- 'use strict';
-
- /**
- * Indicates that the user has acknowledged the first run flow.
- *
- * @param {boolean} optedIntoCloudServices Whether or not the user opted into
- * cloud services.
- */
- function acknowledgeFirstRunFlow(optedIntoCloudServices) {
- chrome.send('acknowledgeFirstRunFlow', [optedIntoCloudServices]);
- }
-
- /**
- * Acts on the given issue.
- *
- * @param {number} issueId
- * @param {number} actionType Type of action that the user clicked.
- * @param {?number} helpPageId The numeric help center ID.
- */
- function actOnIssue(issueId, actionType, helpPageId) {
- chrome.send('actOnIssue', [{issueId: issueId, actionType: actionType,
- helpPageId: helpPageId}]);
- }
-
- /**
- * Modifies |route| by changing its source to the one identified by
- * |selectedCastMode|.
- *
- * @param {!media_router.Route} route The route being modified.
- * @param {number} selectedCastMode The value of the cast mode the user
- * selected.
- */
- function changeRouteSource(route, selectedCastMode) {
- chrome.send('requestRoute',
- [{sinkId: route.sinkId, selectedCastMode: selectedCastMode}]);
- }
-
- /**
- * Closes the dialog.
- *
- * @param {boolean} pressEscToClose Whether the user pressed ESC to close the
- * dialog.
- */
- function closeDialog(pressEscToClose) {
- chrome.send('closeDialog', [pressEscToClose]);
- }
-
- /**
- * Closes the given route.
- *
- * @param {!media_router.Route} route
- */
- function closeRoute(route) {
- chrome.send('closeRoute', [{routeId: route.id, isLocal: route.isLocal}]);
- }
-
- /**
- * Joins the given route.
- *
- * @param {!media_router.Route} route
- */
- function joinRoute(route) {
- chrome.send('joinRoute', [{sinkId: route.sinkId, routeId: route.id}]);
- }
-
- /**
- * Indicates that the initial data has been received.
- */
- function onInitialDataReceived() {
- chrome.send('onInitialDataReceived');
- }
-
- /**
- * Reports when the user clicks outside the dialog.
- */
- function reportBlur() {
- chrome.send('reportBlur');
- }
-
- /**
- * Reports the index of the selected sink.
- *
- * @param {number} sinkIndex
- */
- function reportClickedSinkIndex(sinkIndex) {
- chrome.send('reportClickedSinkIndex', [sinkIndex]);
- }
-
- /**
- * Reports that the user used the filter input.
- */
- function reportFilter() {
- chrome.send('reportFilter');
- }
-
- /**
- * Reports the initial dialog view.
- *
- * @param {string} view
- */
- function reportInitialState(view) {
- chrome.send('reportInitialState', [view]);
- }
-
- /**
- * Reports the initial action the user took.
- *
- * @param {number} action
- */
- function reportInitialAction(action) {
- chrome.send('reportInitialAction', [action]);
- }
-
- /**
- * Reports the navigation to the specified view.
- *
- * @param {string} view
- */
- function reportNavigateToView(view) {
- chrome.send('reportNavigateToView', [view]);
- }
-
- /**
- * Reports whether or not a route was created successfully.
- *
- * @param {boolean} success
- */
- function reportRouteCreation(success) {
- chrome.send('reportRouteCreation', [success]);
- }
-
- /**
- * Reports the outcome of a create route response.
- *
- * @param {number} outcome
- */
- function reportRouteCreationOutcome(outcome) {
- chrome.send('reportRouteCreationOutcome', [outcome]);
- }
-
- /**
- * Reports the cast mode that the user selected.
- *
- * @param {number} castModeType
- */
- function reportSelectedCastMode(castModeType) {
- chrome.send('reportSelectedCastMode', [castModeType]);
- }
-
- /**
- * Reports the current number of sinks.
- *
- * @param {number} sinkCount
- */
- function reportSinkCount(sinkCount) {
- chrome.send('reportSinkCount', [sinkCount]);
- }
-
- /**
- * Reports the time it took for the user to select a sink after the sink list
- * is populated and shown.
- *
- * @param {number} timeMs
- */
- function reportTimeToClickSink(timeMs) {
- chrome.send('reportTimeToClickSink', [timeMs]);
- }
-
- /**
- * Reports the time, in ms, it took for the user to close the dialog without
- * taking any other action.
- *
- * @param {number} timeMs
- */
- function reportTimeToInitialActionClose(timeMs) {
- chrome.send('reportTimeToInitialActionClose', [timeMs]);
- }
-
- /**
- * Requests data to initialize the WebUI with.
- * The data will be returned via media_router.ui.setInitialData.
- */
- function requestInitialData() {
- chrome.send('requestInitialData');
- }
-
- /**
- * Requests that a media route be started with the given sink.
- *
- * @param {string} sinkId The sink ID.
- * @param {number} selectedCastMode The value of the cast mode the user
- * selected.
- */
- function requestRoute(sinkId, selectedCastMode) {
- chrome.send('requestRoute',
- [{sinkId: sinkId, selectedCastMode: selectedCastMode}]);
- }
-
- /**
- * Requests that the media router search all providers for a sink matching
- * |searchCriteria| that can be used with the media source associated with the
- * cast mode |selectedCastMode|. If such a sink is found, a route is also
- * created between the sink and the media source.
- *
- * @param {string} sinkId Sink ID of the pseudo sink generating the request.
- * @param {string} searchCriteria Search criteria for the route providers.
- * @param {string} domain User's current hosted domain.
- * @param {number} selectedCastMode The value of the cast mode to be used with
- * the sink.
- */
- function searchSinksAndCreateRoute(
- sinkId, searchCriteria, domain, selectedCastMode) {
- chrome.send('searchSinksAndCreateRoute',
- [{sinkId: sinkId,
- searchCriteria: searchCriteria,
- domain: domain,
- selectedCastMode: selectedCastMode}]);
- }
-
- return {
- acknowledgeFirstRunFlow: acknowledgeFirstRunFlow,
- actOnIssue: actOnIssue,
- changeRouteSource: changeRouteSource,
- closeDialog: closeDialog,
- closeRoute: closeRoute,
- joinRoute: joinRoute,
- onInitialDataReceived: onInitialDataReceived,
- reportBlur: reportBlur,
- reportClickedSinkIndex: reportClickedSinkIndex,
- reportFilter: reportFilter,
- reportInitialAction: reportInitialAction,
- reportInitialState: reportInitialState,
- reportNavigateToView: reportNavigateToView,
- reportRouteCreation: reportRouteCreation,
- reportRouteCreationOutcome: reportRouteCreationOutcome,
- reportSelectedCastMode: reportSelectedCastMode,
- reportSinkCount: reportSinkCount,
- reportTimeToClickSink: reportTimeToClickSink,
- reportTimeToInitialActionClose: reportTimeToInitialActionClose,
- requestInitialData: requestInitialData,
- requestRoute: requestRoute,
- searchSinksAndCreateRoute: searchSinksAndCreateRoute,
+ updateRouteStatus: updateRouteStatus,
};
});
diff --git a/chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.cc b/chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.cc
index 6345cbec2b1434..b28aadd200928d 100644
--- a/chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.cc
@@ -36,6 +36,18 @@ void AddRouteDetailsStrings(content::WebUIDataSource* html_source) {
IDS_MEDIA_ROUTER_STOP_CASTING_BUTTON);
html_source->AddLocalizedString("startCastingButtonText",
IDS_MEDIA_ROUTER_START_CASTING_BUTTON);
+ html_source->AddLocalizedString("playTitle",
+ IDS_MEDIA_ROUTER_ROUTE_DETAILS_PLAY_TITLE);
+ html_source->AddLocalizedString("pauseTitle",
+ IDS_MEDIA_ROUTER_ROUTE_DETAILS_PAUSE_TITLE);
+ html_source->AddLocalizedString("muteTitle",
+ IDS_MEDIA_ROUTER_ROUTE_DETAILS_MUTE_TITLE);
+ html_source->AddLocalizedString("unmuteTitle",
+ IDS_MEDIA_ROUTER_ROUTE_DETAILS_UNMUTE_TITLE);
+ html_source->AddLocalizedString("seekTitle",
+ IDS_MEDIA_ROUTER_ROUTE_DETAILS_SEEK_TITLE);
+ html_source->AddLocalizedString("volumeTitle",
+ IDS_MEDIA_ROUTER_ROUTE_DETAILS_VOLUME_TITLE);
}
void AddIssuesStrings(content::WebUIDataSource* html_source) {
diff --git a/chrome/browser/ui/webui/media_router/media_router_resources_provider.cc b/chrome/browser/ui/webui/media_router/media_router_resources_provider.cc
index 6450288a4c6737..79861a064a9d0a 100644
--- a/chrome/browser/ui/webui/media_router/media_router_resources_provider.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_resources_provider.cc
@@ -64,9 +64,14 @@ void AddPolymerElements(content::WebUIDataSource* html_source) {
"elements/media_router_search_highlighter/"
"media_router_search_highlighter.js",
IDR_MEDIA_ROUTER_SEARCH_HIGHLIGHTER_JS);
- html_source->AddResourcePath(
- "elements/route_details/route_details.css",
- IDR_ROUTE_DETAILS_CSS);
+ html_source->AddResourcePath("elements/route_controls/route_controls.css",
+ IDR_ROUTE_CONTROLS_CSS);
+ html_source->AddResourcePath("elements/route_controls/route_controls.html",
+ IDR_ROUTE_CONTROLS_HTML);
+ html_source->AddResourcePath("elements/route_controls/route_controls.js",
+ IDR_ROUTE_CONTROLS_JS);
+ html_source->AddResourcePath("elements/route_details/route_details.css",
+ IDR_ROUTE_DETAILS_CSS);
html_source->AddResourcePath(
"elements/route_details/route_details.html",
IDR_ROUTE_DETAILS_HTML);
@@ -76,6 +81,17 @@ void AddPolymerElements(content::WebUIDataSource* html_source) {
html_source->AddResourcePath(
"elements/media_router_container/pseudo_sink_search_state.js",
IDR_PSEUDO_SINK_SEARCH_STATE_JS);
+ html_source->AddResourcePath(
+ "elements/route_details/extension_view_wrapper/"
+ "extension_view_wrapper.html",
+ IDR_EXTENSION_VIEW_WRAPPER_HTML);
+ html_source->AddResourcePath(
+ "elements/route_details/extension_view_wrapper/extension_view_wrapper.js",
+ IDR_EXTENSION_VIEW_WRAPPER_JS);
+ html_source->AddResourcePath(
+ "elements/route_details/extension_view_wrapper/"
+ "extension_view_wrapper.css",
+ IDR_EXTENSION_VIEW_WRAPPER_CSS);
}
} // namespace
diff --git a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
index b4da44a3602594..dda4615c5e5320 100644
--- a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
@@ -502,7 +502,7 @@ void MediaRouterWebUIMessageHandler::OnRequestInitialData(
initial_data.SetBoolean("useTabMirroring", use_tab_mirroring);
initial_data.SetBoolean(
- "useNewRouteControls",
+ "useWebUiRouteControls",
base::FeatureList::IsEnabled(features::kMediaRouterUIRouteController));
web_ui()->CallJavascriptFunctionUnsafe(kSetInitialData, initial_data);
diff --git a/chrome/test/data/webui/media_router/media_router_container_route_tests.js b/chrome/test/data/webui/media_router/media_router_container_route_tests.js
index 71a41e8814cab2..110302cb1ade98 100644
--- a/chrome/test/data/webui/media_router/media_router_container_route_tests.js
+++ b/chrome/test/data/webui/media_router/media_router_container_route_tests.js
@@ -93,21 +93,21 @@ cr.define('media_router_container_route', function() {
document.body.appendChild(container);
// Get common functions and variables.
- var test_base = media_router_container_test_base.init(container);
-
- checkCurrentView = test_base.checkCurrentView;
- checkElementsVisibleWithId = test_base.checkElementsVisibleWithId;
- checkElementVisible = test_base.checkElementVisible;
- checkElementText = test_base.checkElementText;
- fakeBlockingIssue = test_base.fakeBlockingIssue;
- fakeCastModeList = test_base.fakeCastModeList;
- fakeNonBlockingIssue = test_base.fakeNonBlockingIssue;
- fakeRouteList = test_base.fakeRouteList;
+ var testBase = media_router_container_test_base.init(container);
+
+ checkCurrentView = testBase.checkCurrentView;
+ checkElementsVisibleWithId = testBase.checkElementsVisibleWithId;
+ checkElementVisible = testBase.checkElementVisible;
+ checkElementText = testBase.checkElementText;
+ fakeBlockingIssue = testBase.fakeBlockingIssue;
+ fakeCastModeList = testBase.fakeCastModeList;
+ fakeNonBlockingIssue = testBase.fakeNonBlockingIssue;
+ fakeRouteList = testBase.fakeRouteList;
fakeRouteListWithLocalRoutesOnly =
- test_base.fakeRouteListWithLocalRoutesOnly;
- fakeSinkList = test_base.fakeSinkList;
+ testBase.fakeRouteListWithLocalRoutesOnly;
+ fakeSinkList = testBase.fakeSinkList;
- container.castModeList = test_base.fakeCastModeList;
+ container.castModeList = testBase.fakeCastModeList;
// Allow for the media router container to be created, attached, and
// listeners registered in an afterNextRender() call.
@@ -254,7 +254,8 @@ cr.define('media_router_container_route', function() {
// Tests for expected visible UI when the view is ROUTE_DETAILS.
test('route details visibility', function(done) {
- container.showRouteDetails_();
+ container.showRouteDetails_(
+ new media_router.Route('id 3', 'sink id 3', 'Title 3', 0, true));
setTimeout(function() {
checkElementsVisibleWithId(['container-header',
'device-missing',
@@ -294,7 +295,8 @@ cr.define('media_router_container_route', function() {
// Tests for expected visible UI when the view is ROUTE_DETAILS, and there
// is a non-blocking issue.
test('route details visibility non blocking issue', function(done) {
- container.showRouteDetails_();
+ container.showRouteDetails_(
+ new media_router.Route('id 3', 'sink id 3', 'Title 3', 0, true));
// Set a non-blocking issue. The issue should be shown.
container.issue = fakeNonBlockingIssue;
@@ -309,7 +311,8 @@ cr.define('media_router_container_route', function() {
// Tests for expected visible UI when the view is ROUTE_DETAILS, and there
// is a blocking issue.
test('route details visibility with blocking issue', function(done) {
- container.showRouteDetails_();
+ container.showRouteDetails_(
+ new media_router.Route('id 3', 'sink id 3', 'Title 3', 0, true));
// Set a blocking issue. The issue should be shown, and everything
// else, hidden.
@@ -324,8 +327,8 @@ cr.define('media_router_container_route', function() {
test('creating route with selected cast mode', function(done) {
container.allSinks = fakeSinkList;
- MockInteractions.tap(container.$['container-header'].
- $['arrow-drop-icon']);
+ MockInteractions.tap(
+ container.$$('#container-header').$['arrow-drop-icon']);
setTimeout(function() {
// Select cast mode 2.
var castModeList =
@@ -416,7 +419,7 @@ cr.define('media_router_container_route', function() {
fakeRouteList[i].currentCastMode = 2;
}
MockInteractions.tap(
- container.$['container-header'].$['arrow-drop-icon']);
+ container.$$('#container-header').$$('#arrow-drop-icon'));
setTimeout(function() {
MockInteractions.tap(container.$$('#cast-mode-list')
.querySelectorAll('paper-item')[2]);
@@ -445,7 +448,7 @@ cr.define('media_router_container_route', function() {
fakeRouteList[i].currentCastMode = 2;
}
MockInteractions.tap(
- container.$['container-header'].$['arrow-drop-icon']);
+ container.$$('#container-header').$$('#arrow-drop-icon'));
setTimeout(function() {
MockInteractions.tap(container.$$('#cast-mode-list')
.querySelectorAll('paper-item')[1]);
diff --git a/chrome/test/data/webui/media_router/media_router_elements_browsertest.js b/chrome/test/data/webui/media_router/media_router_elements_browsertest.js
index 6be1d6ee431cab..e82cfbadfa3763 100644
--- a/chrome/test/data/webui/media_router/media_router_elements_browsertest.js
+++ b/chrome/test/data/webui/media_router/media_router_elements_browsertest.js
@@ -30,9 +30,7 @@ MediaRouterElementsBrowserTest.prototype = {
/** @override */
accessibilityIssuesAreErrors: true,
- commandLineSwitches: [{
- switchName: 'media-router', switchValue: '1'
- }],
+ commandLineSwitches: [{switchName: 'media-router', switchValue: '1'}],
// List tests for individual elements. The media-router-container tests are
// split between several files and use common functionality from
@@ -48,12 +46,57 @@ MediaRouterElementsBrowserTest.prototype = {
'media_router_container_test_base.js',
'media_router_header_tests.js',
'media_router_search_highlighter_tests.js',
+ 'route_controls_tests.js',
'route_details_tests.js',
]),
+ /**
+ * Mocks the browser API methods to make them fire events instead.
+ */
+ installMockBrowserApi: function() {
+ cr.define('media_router.browserApi', function() {
+ 'use strict';
+
+ function pauseCurrentMedia() {
+ document.dispatchEvent(new Event('mock-pause-current-media'));
+ }
+
+ function playCurrentMedia() {
+ document.dispatchEvent(new Event('mock-play-current-media'));
+ }
+
+ function seekCurrentMedia(time) {
+ var event =
+ new CustomEvent('mock-seek-current-media', {detail: {time: time}})
+ document.dispatchEvent(event);
+ }
+
+ function setCurrentMediaMute(mute) {
+ var event = new CustomEvent(
+ 'mock-set-current-media-mute', {detail: {mute: mute}});
+ document.dispatchEvent(event);
+ }
+
+ function setCurrentMediaVolume(volume) {
+ var event = new CustomEvent(
+ 'mock-set-current-media-volume', {detail: {volume: volume}});
+ document.dispatchEvent(event);
+ }
+
+ return {
+ pauseCurrentMedia: pauseCurrentMedia,
+ playCurrentMedia: playCurrentMedia,
+ seekCurrentMedia: seekCurrentMedia,
+ setCurrentMediaMute: setCurrentMediaMute,
+ setCurrentMediaVolume: setCurrentMediaVolume,
+ };
+ });
+ },
+
/** @override */
setUp: function() {
PolymerTest.prototype.setUp.call(this);
+ this.installMockBrowserApi();
// Enable when failure is resolved.
// AX_ARIA_02: http://crbug.com/591547
@@ -134,6 +177,12 @@ TEST_F('MediaRouterElementsBrowserTest',
mocha.run();
});
+TEST_F(
+ 'MediaRouterElementsBrowserTest', 'MediaRouterRouteControls', function() {
+ route_controls.registerTests();
+ mocha.run();
+ });
+
TEST_F('MediaRouterElementsBrowserTest', 'MediaRouterRouteDetails', function() {
route_details.registerTests();
mocha.run();
diff --git a/chrome/test/data/webui/media_router/route_controls_tests.js b/chrome/test/data/webui/media_router/route_controls_tests.js
new file mode 100644
index 00000000000000..910c7a95b99506
--- /dev/null
+++ b/chrome/test/data/webui/media_router/route_controls_tests.js
@@ -0,0 +1,247 @@
+// Copyright 2017 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.
+
+/** @fileoverview Suite of tests for route-controls. */
+cr.define('route_controls', function() {
+ function registerTests() {
+ suite('RouteControls', function() {
+ /**
+ * Route Controls created before each test.
+ * @type {RouteControls}
+ */
+ var controls;
+
+ /**
+ * First fake route created before each test.
+ * @type {media_router.Route}
+ */
+ var fakeRouteOne;
+
+ /**
+ * Second fake route created before each test.
+ * @type {media_router.Route}
+ */
+ var fakeRouteTwo;
+
+ var assertElementText = function(expected, elementId) {
+ assertEquals(expected, controls.$$('#' + elementId).innerText);
+ };
+
+ var isElementShown = function(elementId) {
+ return !controls.$$('#' + elementId).hasAttribute('hidden');
+ };
+
+ var assertElementShown = function(elementId) {
+ assertTrue(isElementShown(elementId));
+ };
+
+ var assertElementHidden = function(elementId) {
+ assertFalse(isElementShown(elementId));
+ };
+
+ // Creates an instance of RouteStatus with the given parameters. If a
+ // parameter is not set, it defaults to an empty string, zero, or false.
+ var createRouteStatus = function(params = {}) {
+ return new media_router.RouteStatus(
+ params.title ? params.title : '',
+ params.status ? params.status : '', !!params.canPlayPause,
+ !!params.canMute, !!params.canSetVolume, !!params.canSeek,
+ !!params.isPaused, !!params.isMuted,
+ params.volume ? params.volume : 0,
+ params.duration ? params.duration : 0,
+ params.currentTime ? params.currentTime : 0);
+ };
+
+ // Import route_controls.html before running suite.
+ suiteSetup(function() {
+ return PolymerTest.importHtml(
+ 'chrome://media-router/elements/route_controls/' +
+ 'route_controls.html');
+ });
+
+ // Initialize a route-controls before each test.
+ setup(function(done) {
+ PolymerTest.clearBody();
+ controls = document.createElement('route-controls');
+ document.body.appendChild(controls);
+
+ // Initialize routes and sinks.
+ fakeRouteOne = new media_router.Route(
+ 'route id 1', 'sink id 1', 'Video 1', 1, true, false);
+ fakeRouteTwo = new media_router.Route(
+ 'route id 2', 'sink id 2', 'Video 2', 2, false, true);
+
+ // Allow for the route controls to be created and attached.
+ setTimeout(done);
+ });
+
+ // Tests the initial expected text.
+ test('initial text setting', function() {
+ // Set |route|.
+ controls.onRouteUpdated(fakeRouteOne);
+ assertElementText(
+ loadTimeData.getStringF(
+ 'castingActivityStatus', fakeRouteOne.description),
+ 'route-description');
+
+ // Set |route| to a different route.
+ controls.onRouteUpdated(fakeRouteTwo);
+ assertElementText(
+ loadTimeData.getStringF(
+ 'castingActivityStatus', fakeRouteTwo.description),
+ 'route-description');
+ });
+
+ // Tests that the route title and status are shown when RouteStatus is
+ // updated.
+ test('update route text', function() {
+ var title = 'test title';
+ var status = 'test status';
+ controls.routeStatus =
+ createRouteStatus({title: title, status: status});
+
+ assertElementText(title, 'route-title');
+ assertElementText(status, 'route-description');
+ });
+
+ // Tests that media controls are shown and hidden when RouteStatus is
+ // updated.
+ test('media controls visibility', function() {
+ // Create a RouteStatus with no controls.
+ controls.routeStatus = createRouteStatus();
+ assertElementHidden('route-play-pause-button');
+ assertElementHidden('route-time-controls');
+ assertElementHidden('route-volume-button');
+ assertElementHidden('route-volume-slider');
+
+ controls.routeStatus =
+ createRouteStatus({canPlayPause: true, canSeek: true});
+
+ assertElementShown('route-play-pause-button');
+ assertElementShown('route-time-controls');
+ assertElementHidden('route-volume-button');
+ assertElementHidden('route-volume-slider');
+
+ controls.routeStatus =
+ createRouteStatus({canMute: true, canSetVolume: true});
+
+ assertElementHidden('route-play-pause-button');
+ assertElementHidden('route-time-controls');
+ assertElementShown('route-volume-button');
+ assertElementShown('route-volume-slider');
+ });
+
+ // Tests that the play button sends a command to the browser API.
+ test('send play command', function(done) {
+ document.addEventListener('mock-play-current-media', function(data) {
+ done();
+ });
+
+ controls.routeStatus =
+ createRouteStatus({canPlayPause: true, isPaused: true});
+ MockInteractions.tap(controls.$$('#route-play-pause-button'));
+ });
+
+ // Tests that the pause button sends a command to the browser API.
+ test('send pause command', function(done) {
+ document.addEventListener('mock-pause-current-media', function(data) {
+ done();
+ });
+
+ controls.routeStatus =
+ createRouteStatus({canPlayPause: true, isPaused: false});
+ MockInteractions.tap(controls.$$('#route-play-pause-button'));
+ });
+
+ // Tests that the mute button sends a command to the browser API.
+ test('send mute command', function(done) {
+ function waitForMuteEvent(data) {
+ // Remove the event listener to avoid interfering with other tests.
+ document.removeEventListener(
+ 'mock-set-current-media-mute', waitForMuteEvent);
+ if (data.detail.mute) {
+ done();
+ } else {
+ done('Expected the "Mute" command but received "Unmute".');
+ }
+ }
+ document.addEventListener(
+ 'mock-set-current-media-mute', waitForMuteEvent);
+
+ controls.routeStatus =
+ createRouteStatus({canMute: true, isMuted: false});
+ MockInteractions.tap(controls.$$('#route-volume-button'));
+ });
+
+ // Tests that the unmute button sends a command to the browser API.
+ test('send unmute command', function(done) {
+ function waitForUnmuteEvent(data) {
+ // Remove the event listener to avoid interfering with other tests.
+ document.removeEventListener(
+ 'mock-set-current-media-mute', waitForUnmuteEvent);
+ if (data.detail.mute) {
+ done('Expected the "Unmute" command but received "Mute".');
+ } else {
+ done();
+ }
+ }
+ document.addEventListener(
+ 'mock-set-current-media-mute', waitForUnmuteEvent);
+
+ controls.routeStatus =
+ createRouteStatus({canMute: true, isMuted: true});
+ MockInteractions.tap(controls.$$('#route-volume-button'));
+ });
+
+ // // Tests that the seek slider sends a command to the browser API.
+ test('send seek command', function(done) {
+ var currentTime = 500;
+ var duration = 1200;
+ document.addEventListener('mock-seek-current-media', function(data) {
+ if (data.detail.time == currentTime) {
+ done();
+ } else {
+ done(
+ 'Expected the time to be ' + currentTime + ' but instead got ' +
+ data.detail.time);
+ }
+ });
+
+ controls.routeStatus =
+ createRouteStatus({canSeek: true, duration: duration});
+
+ // In actual usage, the change event gets fired when the user interacts
+ // with the slider.
+ controls.$$('#route-time-slider').value = currentTime;
+ controls.$$('#route-time-slider').fire('change');
+ });
+
+ // Tests that the volume slider sends a command to the browser API.
+ test('send set volume command', function(done) {
+ var volume = 0.45;
+ document.addEventListener(
+ 'mock-set-current-media-volume', function(data) {
+ if (data.detail.volume == volume) {
+ done();
+ } else {
+ done(
+ 'Expected the volume to be ' + volume +
+ ' but instead got ' + data.detail.volume);
+ }
+ });
+
+ controls.routeStatus = createRouteStatus({canSetVolume: true});
+
+ // In actual usage, the change event gets fired when the user interacts
+ // with the slider.
+ controls.$$('#route-volume-slider').value = volume;
+ controls.$$('#route-volume-slider').fire('change');
+ });
+ });
+ }
+
+ return {
+ registerTests: registerTests,
+ };
+});
diff --git a/chrome/test/data/webui/media_router/route_details_tests.js b/chrome/test/data/webui/media_router/route_details_tests.js
index dcab054364afd5..f47a046d14a031 100644
--- a/chrome/test/data/webui/media_router/route_details_tests.js
+++ b/chrome/test/data/webui/media_router/route_details_tests.js
@@ -24,49 +24,51 @@ cr.define('route_details', function() {
*/
var fakeRouteTwo;
+ /**
+ * Fake sink that corresponds to |fakeRouteOne|.
+ * @type {media_router.Sink}
+ */
+ var fakeSinkOne;
+
// Checks whether |expected| and the text in the span element in
// the |elementId| element are equal.
var checkSpanText = function(expected, elementId) {
- assertEquals(expected,
- details.$[elementId].querySelector('span').innerText);
+ assertEquals(
+ expected,
+ details.$$('#' + elementId).querySelector('span').innerText);
};
// Checks the default route view is shown.
var checkDefaultViewIsShown = function() {
- assertFalse(details.$['route-information'].hasAttribute('hidden'));
- assertTrue(details.$['custom-controller'].hasAttribute('hidden'));
+ assertFalse(details.$$('#route-information').hasAttribute('hidden'));
+ assertTrue(details.$$('extension-view-wrapper').hasAttribute('hidden'));
};
- // Checks the default route view is shown.
+ // Checks the start button is shown.
var checkStartCastButtonIsShown = function() {
- assertFalse(
- details.$['start-casting-to-route-button'].hasAttribute('hidden'));
+ assertFalse(details.$$('#start-casting-to-route-button')
+ .hasAttribute('hidden'));
};
- // Checks the default route view is not shown.
+ // Checks the start button is not shown.
var checkStartCastButtonIsNotShown = function() {
- assertTrue(
- details.$['start-casting-to-route-button'].hasAttribute('hidden'));
+ assertTrue(details.$$('#start-casting-to-route-button')
+ .hasAttribute('hidden'));
};
// Checks the custom controller is shown.
var checkCustomControllerIsShown = function() {
- assertTrue(details.$['route-information'].hasAttribute('hidden'));
- assertFalse(details.$['custom-controller'].hasAttribute('hidden'));
+ assertTrue(details.$$('#route-information').hasAttribute('hidden'));
+ assertFalse(
+ details.$$('extension-view-wrapper').hasAttribute('hidden'));
};
// Checks whether |expected| and the text in the |elementId| element
// are equal given an id.
var checkElementTextWithId = function(expected, elementId) {
- assertEquals(expected, details.$[elementId].innerText);
+ assertEquals(expected, details.$$('#' + elementId).innerText);
};
- /**
- * Fake sink that corresponds to |fakeRouteOne|.
- * @type {media_router.Sink}
- */
- var fakeSinkOne;
-
// Import route_details.html before running suite.
suiteSetup(function() {
return PolymerTest.importHtml(
@@ -78,6 +80,7 @@ cr.define('route_details', function() {
setup(function(done) {
PolymerTest.clearBody();
details = document.createElement('route-details');
+ details.useWebUiRouteControls = false;
document.body.appendChild(details);
// Initialize routes and sinks.
@@ -140,7 +143,7 @@ cr.define('route_details', function() {
details.addEventListener('close-route', function() {
done();
});
- MockInteractions.tap(details.$['close-route-button']);
+ MockInteractions.tap(details.$$('#close-route-button'));
});
// Tests for 'join-route-click' event firing when the
@@ -149,7 +152,7 @@ cr.define('route_details', function() {
test('start casting to route button click', function(done) {
details.addEventListener('join-route-click', function() { done(); });
details.route = fakeRouteTwo;
- MockInteractions.tap(details.$['start-casting-to-route-button']);
+ MockInteractions.tap(details.$$('#start-casting-to-route-button'));
});
// Tests for 'replace-route-click' event firing when the
@@ -160,7 +163,7 @@ cr.define('route_details', function() {
'change-route-source-click', function() { done(); });
details.route = fakeRouteOne;
details.availableCastModes = 1;
- MockInteractions.tap(details.$['start-casting-to-route-button']);
+ MockInteractions.tap(details.$$('#start-casting-to-route-button'));
});
// Tests the initial expected text.
@@ -199,35 +202,31 @@ cr.define('route_details', function() {
// Tests when |route| exists, has a custom controller, and it loads.
test('route has custom controller and loading succeeds', function(done) {
- var loadInvoked = false;
- details.$['custom-controller'].load = function(url) {
- loadInvoked = true;
- assertEquals('chrome-extension://123/custom_view.html', url);
+ details.$$('extension-view-wrapper').$$('#custom-controller').load =
+ function(url) {
+ setTimeout(function() {
+ assertEquals('chrome-extension://123/custom_view.html', url);
+ checkCustomControllerIsShown();
+ done();
+ });
return Promise.resolve();
};
details.route = fakeRouteOne;
- setTimeout(function() {
- assertTrue(loadInvoked);
- checkCustomControllerIsShown();
- done();
- });
});
// Tests when |route| exists, has a custom controller, but fails to load.
test('route has custom controller but loading fails', function(done) {
- var loadInvoked = false;
- details.$['custom-controller'].load = function(url) {
- loadInvoked = true;
+ details.$$('extension-view-wrapper').$$('#custom-controller').load =
+ function(url) {
+ setTimeout(function() {
+ checkDefaultViewIsShown();
+ done();
+ });
return Promise.reject();
};
details.route = fakeRouteOne;
- setTimeout(function() {
- assertTrue(loadInvoked);
- checkDefaultViewIsShown();
- done();
- });
});
});
}