Bug 1168829 - Part 1. Remove Loop's standalone UI old dynamic layout and context views. r=mikedeboer
authorMark Banner <standard8@mozilla.com>
Thu, 18 Jun 2015 14:18:02 +0100
changeset 249533 ec9c9d0b9d753547870ccca2c845d700ea40ee97
parent 249532 5d40b50b36ef874e01d3bada9d95c65e7ca450a9
child 249534 f160bb5abec102a8b0272a6bb77c73c5c3c39f15
push id28929
push userryanvm@gmail.com
push dateThu, 18 Jun 2015 19:51:41 +0000
treeherdermozilla-central@d56a1257088e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1168829
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1168829 - Part 1. Remove Loop's standalone UI old dynamic layout and context views. r=mikedeboer
browser/components/loop/content/shared/js/mixins.js
browser/components/loop/standalone/content/js/standaloneRoomViews.js
browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
browser/components/loop/standalone/content/js/webapp.js
browser/components/loop/standalone/content/js/webapp.jsx
browser/components/loop/test/standalone/standaloneRoomViews_test.js
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/shared/js/mixins.js
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -282,26 +282,18 @@ loop.shared.mixins = (function() {
     }
   };
 
   /**
    * Media setup mixin. Provides a common location for settings for the media
    * elements and handling updates of the media containers.
    */
   var MediaSetupMixin = {
-
     componentDidMount: function() {
       this.resetDimensionsCache();
-      rootObject.addEventListener("orientationchange", this.updateVideoContainer);
-      rootObject.addEventListener("resize", this.updateVideoContainer);
-    },
-
-    componentWillUnmount: function() {
-      rootObject.removeEventListener("orientationchange", this.updateVideoContainer);
-      rootObject.removeEventListener("resize", this.updateVideoContainer);
     },
 
     /**
      * Resets the dimensions cache, e.g. for when the session is ended, and
      * before a new session, so that we always ensure we see an update when a
      * new session is started.
      */
     resetDimensionsCache: function() {
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -224,116 +224,22 @@ loop.standaloneRoomViews = (function(moz
           React.createElement("div", {className: "footer-logo"}), 
           React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()}, 
              onClick: this.recordClick})
         )
       );
     }
   });
 
-  var StandaloneRoomContextItem = React.createClass({displayName: "StandaloneRoomContextItem",
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      receivingScreenShare: React.PropTypes.bool,
-      roomContextUrl: React.PropTypes.object
-    },
-
-    recordClick: function() {
-      this.props.dispatcher.dispatch(new sharedActions.RecordClick({
-        linkInfo: "Shared URL"
-      }));
-    },
-
-    render: function() {
-      if (!this.props.roomContextUrl ||
-          !this.props.roomContextUrl.location) {
-        return null;
-      }
-
-      var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location);
-      if (!locationInfo) {
-        return null;
-      }
-
-      var cx = React.addons.classSet;
-
-      var classes = cx({
-        "standalone-context-url": true,
-        "screen-share-active": this.props.receivingScreenShare
-      });
-
-      return (
-        React.createElement("div", {className: classes}, 
-          React.createElement("img", {src: this.props.roomContextUrl.thumbnail || "shared/img/icons-16x16.svg#globe"}), 
-          React.createElement("div", {className: "standalone-context-url-description-wrapper"}, 
-            this.props.roomContextUrl.description, 
-            React.createElement("br", null), React.createElement("a", {href: locationInfo.location, 
-                     onClick: this.recordClick, 
-                     target: "_blank", 
-                     title: locationInfo.location}, locationInfo.hostname)
-          )
-        )
-      );
-    }
-  });
-
-  var StandaloneRoomContextView = React.createClass({displayName: "StandaloneRoomContextView",
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      receivingScreenShare: React.PropTypes.bool.isRequired,
-      roomContextUrls: React.PropTypes.array,
-      roomName: React.PropTypes.string,
-      roomInfoFailure: React.PropTypes.string
-    },
-
-    getInitialState: function() {
-      return {
-        failureLogged: false
-      };
-    },
-
-    _logFailure: function(message) {
-      if (!this.state.failureLogged) {
-        console.error(mozL10n.get(message));
-        this.state.failureLogged = true;
-      }
-    },
-
-    render: function() {
-      // For failures, we currently just log the messages - UX doesn't want them
-      // displayed on primary UI at the moment.
-      if (this.props.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
-        this._logFailure("room_information_failure_unsupported_browser");
-        return null;
-      } else if (this.props.roomInfoFailure) {
-        this._logFailure("room_information_failure_not_available");
-        return null;
-      }
-
-      // We only support one item in the context Urls array for now.
-      var roomContextUrl = (this.props.roomContextUrls &&
-                            this.props.roomContextUrls.length > 0) ?
-                            this.props.roomContextUrls[0] : null;
-      return (
-        React.createElement("div", {className: "standalone-room-info"}, 
-          React.createElement("h2", {className: "room-name"}, this.props.roomName), 
-          React.createElement(StandaloneRoomContextItem, {
-            dispatcher: this.props.dispatcher, 
-            receivingScreenShare: this.props.receivingScreenShare, 
-            roomContextUrl: roomContextUrl})
-        )
-      );
-    }
-  });
-
   var StandaloneRoomView = React.createClass({displayName: "StandaloneRoomView",
     mixins: [
       Backbone.Events,
       sharedMixins.MediaSetupMixin,
-      sharedMixins.RoomsAudioMixin
+      sharedMixins.RoomsAudioMixin,
+      loop.store.StoreMixin("activeRoomStore")
     ],
 
     propTypes: {
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@@ -347,257 +253,59 @@ loop.standaloneRoomViews = (function(moz
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
       });
     },
 
-    componentWillMount: function() {
-      this.listenTo(this.props.activeRoomStore, "change",
-                    this._onActiveRoomStateChanged);
-    },
-
-    /**
-     * Handles a "change" event on the roomStore, and updates this.state
-     * to match the store.
-     *
-     * @private
-     */
-    _onActiveRoomStateChanged: function() {
-      var state = this.props.activeRoomStore.getStoreState();
-      this.updateVideoDimensions(state.localVideoDimensions, state.remoteVideoDimensions);
-      this.setState(state);
-    },
-
     componentDidMount: function() {
       // Adding a class to the document body element from here to ease styling it.
       document.body.classList.add("is-standalone-room");
     },
 
-    componentWillUnmount: function() {
-      this.stopListening(this.props.activeRoomStore);
-    },
-
     /**
      * Watches for when we transition to MEDIA_WAIT room state, so we can request
      * user media access.
      *
      * @param  {Object} nextProps (Unused)
      * @param  {Object} nextState Next state object.
      */
     componentWillUpdate: function(nextProps, nextState) {
       if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
           nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this.getDefaultPublisherConfig({publishVideo: true})
         }));
       }
-
-      if (this.state.roomState !== ROOM_STATES.JOINED &&
-          nextState.roomState === ROOM_STATES.JOINED) {
-        // This forces the video size to update - creating the publisher
-        // first, and then connecting to the session doesn't seem to set the
-        // initial size correctly.
-        this.updateVideoContainer();
-      }
-
-      if (nextState.roomState === ROOM_STATES.INIT ||
-          nextState.roomState === ROOM_STATES.GATHER ||
-          nextState.roomState === ROOM_STATES.READY) {
-        this.resetDimensionsCache();
-      }
-
-      // When screen sharing stops.
-      if (this.state.receivingScreenShare && !nextState.receivingScreenShare) {
-        // Remove the custom screenshare styles on the remote camera.
-        var node = this._getElement(".remote");
-        node.removeAttribute("style");
-      }
-
-      if (this.state.receivingScreenShare != nextState.receivingScreenShare ||
-          this.state.remoteVideoEnabled != nextState.remoteVideoEnabled) {
-        this.updateVideoContainer();
-      }
     },
 
     joinRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
     },
 
     leaveRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
     },
 
     /**
-     * Wrapper for window.matchMedia so that we use an appropriate version
-     * for the ui-showcase, which puts views inside of their own iframes.
-     *
-     * Currently, we use an icky hack, and the showcase conspires with
-     * react-frame-component to set iframe.contentWindow.matchMedia onto
-     * activeRoomStore.  Once React context matures a bit (somewhere between
-     * 0.14 and 1.0, apparently):
-     *
-     * https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#solution-make-context-parent-based-instead-of-owner-based
-     *
-     * we should be able to use those to clean this up.
-     *
-     * @param queryString
-     * @returns {MediaQueryList|null}
-     * @private
-     */
-    _matchMedia: function(queryString) {
-      if ("matchMedia" in this.state) {
-        return this.state.matchMedia(queryString);
-      } else if ("matchMedia" in window) {
-        return window.matchMedia(queryString);
-      }
-      return null;
-    },
-
-    /**
      * Toggles streaming status for a given stream type.
      *
      * @param  {String}  type     Stream type ("audio" or "video").
      * @param  {Boolean} enabled  Enabled stream flag.
      */
     publishStream: function(type, enabled) {
       this.props.dispatcher.dispatch(new sharedActions.SetMute({
         type: type,
         enabled: enabled
       }));
     },
 
     /**
-     * Specifically updates the local camera stream size and position, depending
-     * on the size and position of the remote video stream.
-     * This method gets called from `updateVideoContainer`, which is defined in
-     * the `MediaSetupMixin`.
-     *
-     * @param  {Object} ratio Aspect ratio of the local camera stream
-     */
-    updateLocalCameraPosition: function(ratio) {
-      // The local stream is a quarter of the remote stream.
-      var LOCAL_STREAM_SIZE = 0.25;
-      // The local stream overlaps the remote stream by a quarter of the local stream.
-      var LOCAL_STREAM_OVERLAP = 0.25;
-      // The minimum size of video height/width allowed by the sdk css.
-      var SDK_MIN_SIZE = 48;
-
-      var node = this._getElement(".local");
-      var targetWidth;
-
-      node.style.right = "auto";
-      if (this._matchMedia("screen and (max-width:640px)").matches) {
-        // For reduced screen widths, we just go for a fixed size and no overlap.
-        targetWidth = 180;
-        node.style.width = (targetWidth * ratio.width) + "px";
-        node.style.height = (targetWidth * ratio.height) + "px";
-        node.style.left = "auto";
-      } else {
-        // The local camera view should be a quarter of the size of the remote stream
-        // and positioned to overlap with the remote stream at a quarter of its width.
-
-        // Now position the local camera view correctly with respect to the remote
-        // video stream or the screen share stream.
-        var remoteVideoDimensions;
-        var isScreenShare = this.state.receivingScreenShare;
-        var videoDisplayed = isScreenShare ?
-          this.state.screenShareVideoObject || this.props.screenSharePosterUrl :
-          this.state.remoteSrcVideoObject || this.props.remotePosterUrl;
-
-        if ((isScreenShare || this.shouldRenderRemoteVideo()) && videoDisplayed) {
-          remoteVideoDimensions = this.getRemoteVideoDimensions(
-            isScreenShare ? "screen" : "camera");
-        } else {
-          var remoteElement = this.getDOMNode().querySelector(".remote.focus-stream");
-          if (!remoteElement) {
-            return;
-          }
-          remoteVideoDimensions = {
-            streamWidth: remoteElement.offsetWidth,
-            offsetX: remoteElement.offsetLeft
-          };
-        }
-
-        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
-
-        var realWidth = targetWidth * ratio.width;
-        var realHeight = targetWidth * ratio.height;
-
-        // If we've hit the min size limits, then limit at the minimum.
-        if (realWidth < SDK_MIN_SIZE) {
-          realWidth = SDK_MIN_SIZE;
-          realHeight = realWidth / ratio.width * ratio.height;
-        }
-        if (realHeight < SDK_MIN_SIZE) {
-          realHeight = SDK_MIN_SIZE;
-          realWidth = realHeight / ratio.height * ratio.width;
-        }
-
-        var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
-        // The horizontal offset of the stream, and the width of the resulting
-        // pillarbox, is determined by the height exponent of the aspect ratio.
-        // Therefore we multiply the width of the local camera view by the height
-        // ratio.
-        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
-        node.style.width = realWidth + "px";
-        node.style.height = realHeight + "px";
-      }
-    },
-
-    /**
-     * Specifically updates the remote camera stream size and position, if
-     * a screen share is being received. It is slaved from the position of the
-     * local stream.
-     * This method gets called from `updateVideoContainer`, which is defined in
-     * the `MediaSetupMixin`.
-     *
-     * @param  {Object} ratio Aspect ratio of the remote camera stream
-     */
-    updateRemoteCameraPosition: function(ratio) {
-      // Nothing to do for screenshare
-      if (!this.state.receivingScreenShare) {
-        return;
-      }
-      // XXX For the time being, if we're a narrow screen, aka mobile, we don't display
-      // the remote media (bug 1133534).
-      if (this._matchMedia("screen and (max-width:640px)").matches) {
-        return;
-      }
-
-      // 10px separation between the two streams.
-      var LOCAL_REMOTE_SEPARATION = 10;
-
-      var node = this._getElement(".remote");
-      var localNode = this._getElement(".local");
-
-      // Match the width to the local video.
-      node.style.width = localNode.offsetWidth + "px";
-
-      // The height is then determined from the aspect ratio
-      var height = ((localNode.offsetWidth / ratio.width) * ratio.height);
-      node.style.height = height + "px";
-
-      node.style.right = "auto";
-      node.style.bottom = "auto";
-
-      // Now position the local camera view correctly with respect to the remote
-      // video stream.
-
-      // The top is measured from the top of the element down the screen,
-      // so subtract the height of the video and the separation distance.
-      node.style.top = (localNode.offsetTop - height - LOCAL_REMOTE_SEPARATION) + "px";
-
-      // Match the left-hand sides.
-      node.style.left = localNode.offsetLeft + "px";
-    },
-
-    /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
       return this.state.roomState === ROOM_STATES.JOINED            ||
              this.state.roomState === ROOM_STATES.SESSION_CONNECTED ||
              this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS;
@@ -679,22 +387,16 @@ loop.standaloneRoomViews = (function(moz
           React.createElement(StandaloneRoomInfoArea, {roomState: this.state.roomState, 
                                   failureReason: this.state.failureReason, 
                                   joinRoom: this.joinRoom, 
                                   isFirefox: this.props.isFirefox, 
                                   activeRoomStore: this.props.activeRoomStore, 
                                   roomUsed: this.state.used}), 
           React.createElement("div", {className: "video-layout-wrapper"}, 
             React.createElement("div", {className: "conversation room-conversation"}, 
-              React.createElement(StandaloneRoomContextView, {
-                dispatcher: this.props.dispatcher, 
-                receivingScreenShare: this.state.receivingScreenShare, 
-                roomContextUrls: this.state.roomContextUrls, 
-                roomName: this.state.roomName, 
-                roomInfoFailure: this.state.roomInfoFailure}), 
               React.createElement("div", {className: "media nested"}, 
                 React.createElement("span", {className: "self-view-hidden-message"}, 
                   mozL10n.get("self_view_hidden_message")
                 ), 
                 React.createElement("div", {className: "video_wrapper remote_wrapper"}, 
                   React.createElement("div", {className: remoteStreamClasses}, 
                     React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
                       posterUrl: this.props.remotePosterUrl, 
@@ -732,14 +434,13 @@ loop.standaloneRoomViews = (function(moz
             onMarketplaceMessage: this.state.onMarketplaceMessage}), 
           React.createElement(StandaloneRoomFooter, {dispatcher: this.props.dispatcher})
         )
       );
     }
   });
 
   return {
-    StandaloneRoomContextView: StandaloneRoomContextView,
     StandaloneRoomFooter: StandaloneRoomFooter,
     StandaloneRoomHeader: StandaloneRoomHeader,
     StandaloneRoomView: StandaloneRoomView
   };
 })(navigator.mozL10n);
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -224,116 +224,22 @@ loop.standaloneRoomViews = (function(moz
           <div className="footer-logo" />
           <p dangerouslySetInnerHTML={{__html: this._getContent()}}
              onClick={this.recordClick}></p>
         </footer>
       );
     }
   });
 
-  var StandaloneRoomContextItem = React.createClass({
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      receivingScreenShare: React.PropTypes.bool,
-      roomContextUrl: React.PropTypes.object
-    },
-
-    recordClick: function() {
-      this.props.dispatcher.dispatch(new sharedActions.RecordClick({
-        linkInfo: "Shared URL"
-      }));
-    },
-
-    render: function() {
-      if (!this.props.roomContextUrl ||
-          !this.props.roomContextUrl.location) {
-        return null;
-      }
-
-      var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location);
-      if (!locationInfo) {
-        return null;
-      }
-
-      var cx = React.addons.classSet;
-
-      var classes = cx({
-        "standalone-context-url": true,
-        "screen-share-active": this.props.receivingScreenShare
-      });
-
-      return (
-        <div className={classes}>
-          <img src={this.props.roomContextUrl.thumbnail || "shared/img/icons-16x16.svg#globe"} />
-          <div className="standalone-context-url-description-wrapper">
-            {this.props.roomContextUrl.description}
-            <br /><a href={locationInfo.location}
-                     onClick={this.recordClick}
-                     target="_blank"
-                     title={locationInfo.location}>{locationInfo.hostname}</a>
-          </div>
-        </div>
-      );
-    }
-  });
-
-  var StandaloneRoomContextView = React.createClass({
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      receivingScreenShare: React.PropTypes.bool.isRequired,
-      roomContextUrls: React.PropTypes.array,
-      roomName: React.PropTypes.string,
-      roomInfoFailure: React.PropTypes.string
-    },
-
-    getInitialState: function() {
-      return {
-        failureLogged: false
-      };
-    },
-
-    _logFailure: function(message) {
-      if (!this.state.failureLogged) {
-        console.error(mozL10n.get(message));
-        this.state.failureLogged = true;
-      }
-    },
-
-    render: function() {
-      // For failures, we currently just log the messages - UX doesn't want them
-      // displayed on primary UI at the moment.
-      if (this.props.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
-        this._logFailure("room_information_failure_unsupported_browser");
-        return null;
-      } else if (this.props.roomInfoFailure) {
-        this._logFailure("room_information_failure_not_available");
-        return null;
-      }
-
-      // We only support one item in the context Urls array for now.
-      var roomContextUrl = (this.props.roomContextUrls &&
-                            this.props.roomContextUrls.length > 0) ?
-                            this.props.roomContextUrls[0] : null;
-      return (
-        <div className="standalone-room-info">
-          <h2 className="room-name">{this.props.roomName}</h2>
-          <StandaloneRoomContextItem
-            dispatcher={this.props.dispatcher}
-            receivingScreenShare={this.props.receivingScreenShare}
-            roomContextUrl={roomContextUrl} />
-        </div>
-      );
-    }
-  });
-
   var StandaloneRoomView = React.createClass({
     mixins: [
       Backbone.Events,
       sharedMixins.MediaSetupMixin,
-      sharedMixins.RoomsAudioMixin
+      sharedMixins.RoomsAudioMixin,
+      loop.store.StoreMixin("activeRoomStore")
     ],
 
     propTypes: {
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
       ]).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
@@ -347,257 +253,59 @@ loop.standaloneRoomViews = (function(moz
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
       });
     },
 
-    componentWillMount: function() {
-      this.listenTo(this.props.activeRoomStore, "change",
-                    this._onActiveRoomStateChanged);
-    },
-
-    /**
-     * Handles a "change" event on the roomStore, and updates this.state
-     * to match the store.
-     *
-     * @private
-     */
-    _onActiveRoomStateChanged: function() {
-      var state = this.props.activeRoomStore.getStoreState();
-      this.updateVideoDimensions(state.localVideoDimensions, state.remoteVideoDimensions);
-      this.setState(state);
-    },
-
     componentDidMount: function() {
       // Adding a class to the document body element from here to ease styling it.
       document.body.classList.add("is-standalone-room");
     },
 
-    componentWillUnmount: function() {
-      this.stopListening(this.props.activeRoomStore);
-    },
-
     /**
      * Watches for when we transition to MEDIA_WAIT room state, so we can request
      * user media access.
      *
      * @param  {Object} nextProps (Unused)
      * @param  {Object} nextState Next state object.
      */
     componentWillUpdate: function(nextProps, nextState) {
       if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
           nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this.getDefaultPublisherConfig({publishVideo: true})
         }));
       }
-
-      if (this.state.roomState !== ROOM_STATES.JOINED &&
-          nextState.roomState === ROOM_STATES.JOINED) {
-        // This forces the video size to update - creating the publisher
-        // first, and then connecting to the session doesn't seem to set the
-        // initial size correctly.
-        this.updateVideoContainer();
-      }
-
-      if (nextState.roomState === ROOM_STATES.INIT ||
-          nextState.roomState === ROOM_STATES.GATHER ||
-          nextState.roomState === ROOM_STATES.READY) {
-        this.resetDimensionsCache();
-      }
-
-      // When screen sharing stops.
-      if (this.state.receivingScreenShare && !nextState.receivingScreenShare) {
-        // Remove the custom screenshare styles on the remote camera.
-        var node = this._getElement(".remote");
-        node.removeAttribute("style");
-      }
-
-      if (this.state.receivingScreenShare != nextState.receivingScreenShare ||
-          this.state.remoteVideoEnabled != nextState.remoteVideoEnabled) {
-        this.updateVideoContainer();
-      }
     },
 
     joinRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
     },
 
     leaveRoom: function() {
       this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
     },
 
     /**
-     * Wrapper for window.matchMedia so that we use an appropriate version
-     * for the ui-showcase, which puts views inside of their own iframes.
-     *
-     * Currently, we use an icky hack, and the showcase conspires with
-     * react-frame-component to set iframe.contentWindow.matchMedia onto
-     * activeRoomStore.  Once React context matures a bit (somewhere between
-     * 0.14 and 1.0, apparently):
-     *
-     * https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#solution-make-context-parent-based-instead-of-owner-based
-     *
-     * we should be able to use those to clean this up.
-     *
-     * @param queryString
-     * @returns {MediaQueryList|null}
-     * @private
-     */
-    _matchMedia: function(queryString) {
-      if ("matchMedia" in this.state) {
-        return this.state.matchMedia(queryString);
-      } else if ("matchMedia" in window) {
-        return window.matchMedia(queryString);
-      }
-      return null;
-    },
-
-    /**
      * Toggles streaming status for a given stream type.
      *
      * @param  {String}  type     Stream type ("audio" or "video").
      * @param  {Boolean} enabled  Enabled stream flag.
      */
     publishStream: function(type, enabled) {
       this.props.dispatcher.dispatch(new sharedActions.SetMute({
         type: type,
         enabled: enabled
       }));
     },
 
     /**
-     * Specifically updates the local camera stream size and position, depending
-     * on the size and position of the remote video stream.
-     * This method gets called from `updateVideoContainer`, which is defined in
-     * the `MediaSetupMixin`.
-     *
-     * @param  {Object} ratio Aspect ratio of the local camera stream
-     */
-    updateLocalCameraPosition: function(ratio) {
-      // The local stream is a quarter of the remote stream.
-      var LOCAL_STREAM_SIZE = 0.25;
-      // The local stream overlaps the remote stream by a quarter of the local stream.
-      var LOCAL_STREAM_OVERLAP = 0.25;
-      // The minimum size of video height/width allowed by the sdk css.
-      var SDK_MIN_SIZE = 48;
-
-      var node = this._getElement(".local");
-      var targetWidth;
-
-      node.style.right = "auto";
-      if (this._matchMedia("screen and (max-width:640px)").matches) {
-        // For reduced screen widths, we just go for a fixed size and no overlap.
-        targetWidth = 180;
-        node.style.width = (targetWidth * ratio.width) + "px";
-        node.style.height = (targetWidth * ratio.height) + "px";
-        node.style.left = "auto";
-      } else {
-        // The local camera view should be a quarter of the size of the remote stream
-        // and positioned to overlap with the remote stream at a quarter of its width.
-
-        // Now position the local camera view correctly with respect to the remote
-        // video stream or the screen share stream.
-        var remoteVideoDimensions;
-        var isScreenShare = this.state.receivingScreenShare;
-        var videoDisplayed = isScreenShare ?
-          this.state.screenShareVideoObject || this.props.screenSharePosterUrl :
-          this.state.remoteSrcVideoObject || this.props.remotePosterUrl;
-
-        if ((isScreenShare || this.shouldRenderRemoteVideo()) && videoDisplayed) {
-          remoteVideoDimensions = this.getRemoteVideoDimensions(
-            isScreenShare ? "screen" : "camera");
-        } else {
-          var remoteElement = this.getDOMNode().querySelector(".remote.focus-stream");
-          if (!remoteElement) {
-            return;
-          }
-          remoteVideoDimensions = {
-            streamWidth: remoteElement.offsetWidth,
-            offsetX: remoteElement.offsetLeft
-          };
-        }
-
-        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
-
-        var realWidth = targetWidth * ratio.width;
-        var realHeight = targetWidth * ratio.height;
-
-        // If we've hit the min size limits, then limit at the minimum.
-        if (realWidth < SDK_MIN_SIZE) {
-          realWidth = SDK_MIN_SIZE;
-          realHeight = realWidth / ratio.width * ratio.height;
-        }
-        if (realHeight < SDK_MIN_SIZE) {
-          realHeight = SDK_MIN_SIZE;
-          realWidth = realHeight / ratio.height * ratio.width;
-        }
-
-        var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
-        // The horizontal offset of the stream, and the width of the resulting
-        // pillarbox, is determined by the height exponent of the aspect ratio.
-        // Therefore we multiply the width of the local camera view by the height
-        // ratio.
-        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
-        node.style.width = realWidth + "px";
-        node.style.height = realHeight + "px";
-      }
-    },
-
-    /**
-     * Specifically updates the remote camera stream size and position, if
-     * a screen share is being received. It is slaved from the position of the
-     * local stream.
-     * This method gets called from `updateVideoContainer`, which is defined in
-     * the `MediaSetupMixin`.
-     *
-     * @param  {Object} ratio Aspect ratio of the remote camera stream
-     */
-    updateRemoteCameraPosition: function(ratio) {
-      // Nothing to do for screenshare
-      if (!this.state.receivingScreenShare) {
-        return;
-      }
-      // XXX For the time being, if we're a narrow screen, aka mobile, we don't display
-      // the remote media (bug 1133534).
-      if (this._matchMedia("screen and (max-width:640px)").matches) {
-        return;
-      }
-
-      // 10px separation between the two streams.
-      var LOCAL_REMOTE_SEPARATION = 10;
-
-      var node = this._getElement(".remote");
-      var localNode = this._getElement(".local");
-
-      // Match the width to the local video.
-      node.style.width = localNode.offsetWidth + "px";
-
-      // The height is then determined from the aspect ratio
-      var height = ((localNode.offsetWidth / ratio.width) * ratio.height);
-      node.style.height = height + "px";
-
-      node.style.right = "auto";
-      node.style.bottom = "auto";
-
-      // Now position the local camera view correctly with respect to the remote
-      // video stream.
-
-      // The top is measured from the top of the element down the screen,
-      // so subtract the height of the video and the separation distance.
-      node.style.top = (localNode.offsetTop - height - LOCAL_REMOTE_SEPARATION) + "px";
-
-      // Match the left-hand sides.
-      node.style.left = localNode.offsetLeft + "px";
-    },
-
-    /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
       return this.state.roomState === ROOM_STATES.JOINED            ||
              this.state.roomState === ROOM_STATES.SESSION_CONNECTED ||
              this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS;
@@ -679,22 +387,16 @@ loop.standaloneRoomViews = (function(moz
           <StandaloneRoomInfoArea roomState={this.state.roomState}
                                   failureReason={this.state.failureReason}
                                   joinRoom={this.joinRoom}
                                   isFirefox={this.props.isFirefox}
                                   activeRoomStore={this.props.activeRoomStore}
                                   roomUsed={this.state.used} />
           <div className="video-layout-wrapper">
             <div className="conversation room-conversation">
-              <StandaloneRoomContextView
-                dispatcher={this.props.dispatcher}
-                receivingScreenShare={this.state.receivingScreenShare}
-                roomContextUrls={this.state.roomContextUrls}
-                roomName={this.state.roomName}
-                roomInfoFailure={this.state.roomInfoFailure} />
               <div className="media nested">
                 <span className="self-view-hidden-message">
                   {mozL10n.get("self_view_hidden_message")}
                 </span>
                 <div className="video_wrapper remote_wrapper">
                   <div className={remoteStreamClasses}>
                     <sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
                       posterUrl={this.props.remotePosterUrl}
@@ -732,14 +434,13 @@ loop.standaloneRoomViews = (function(moz
             onMarketplaceMessage={this.state.onMarketplaceMessage} />
           <StandaloneRoomFooter dispatcher={this.props.dispatcher} />
         </div>
       );
     }
   });
 
   return {
-    StandaloneRoomContextView: StandaloneRoomContextView,
     StandaloneRoomFooter: StandaloneRoomFooter,
     StandaloneRoomHeader: StandaloneRoomHeader,
     StandaloneRoomView: StandaloneRoomView
   };
 })(navigator.mozL10n);
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -1089,16 +1089,17 @@ loop.webapp = (function($, _, OT, mozL10
     var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
       activeRoomStore: activeRoomStore
     });
     var textChatStore = new loop.store.TextChatStore(dispatcher, {
       sdkDriver: sdkDriver
     });
 
     loop.store.StoreMixin.register({
+      activeRoomStore: activeRoomStore,
       feedbackStore: feedbackStore,
       // This isn't used in any views, but is saved here to ensure it
       // is kept alive.
       standaloneMetricsStore: standaloneMetricsStore,
       textChatStore: textChatStore
     });
 
     window.addEventListener("unload", function() {
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -1089,16 +1089,17 @@ loop.webapp = (function($, _, OT, mozL10
     var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
       activeRoomStore: activeRoomStore
     });
     var textChatStore = new loop.store.TextChatStore(dispatcher, {
       sdkDriver: sdkDriver
     });
 
     loop.store.StoreMixin.register({
+      activeRoomStore: activeRoomStore,
       feedbackStore: feedbackStore,
       // This isn't used in any views, but is saved here to ensure it
       // is kept alive.
       standaloneMetricsStore: standaloneMetricsStore,
       textChatStore: textChatStore
     });
 
     window.addEventListener("unload", function() {
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -26,150 +26,31 @@ describe("loop.standaloneRoomViews", fun
     });
     var textChatStore = new loop.store.TextChatStore(dispatcher, {
       sdkDriver: {}
     });
     feedbackStore = new loop.store.FeedbackStore(dispatcher, {
       feedbackClient: {}
     });
     loop.store.StoreMixin.register({
+      activeRoomStore: activeRoomStore,
       feedbackStore: feedbackStore,
       textChatStore: textChatStore
     });
 
     sandbox.useFakeTimers();
 
     // Prevents audio request errors in the test console.
     sandbox.useFakeXMLHttpRequest();
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
-  describe("StandaloneRoomContextView", function() {
-    beforeEach(function() {
-      sandbox.stub(navigator.mozL10n, "get").returnsArg(0);
-    });
-
-    function mountTestComponent(extraProps) {
-      var props = _.extend({
-        dispatcher: dispatcher,
-        receivingScreenShare: false
-      }, extraProps);
-      return TestUtils.renderIntoDocument(
-        React.createElement(
-          loop.standaloneRoomViews.StandaloneRoomContextView, props));
-    }
-
-    it("should display the room name if no failures are known", function() {
-      var view = mountTestComponent({
-        roomName: "Mike's room",
-        receivingScreenShare: false
-      });
-
-      expect(view.getDOMNode().textContent).eql("Mike's room");
-    });
-
-    it("should log an unsupported browser message if crypto is unsupported", function() {
-      var view = mountTestComponent({
-        roomName: "Mark's room",
-        roomInfoFailure: ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED
-      });
-
-      sinon.assert.called(console.error);
-      sinon.assert.calledWithMatch(console.error, sinon.match("unsupported"));
-    });
-
-    it("should display a general error message for any other failure", function() {
-      var view = mountTestComponent({
-        roomName: "Mark's room",
-        roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA
-      });
-
-      sinon.assert.called(console.error);
-      sinon.assert.calledWithMatch(console.error, sinon.match("not_available"));
-    });
-
-    it("should display context information if a url is supplied", function() {
-      var view = mountTestComponent({
-        roomName: "Mike's room",
-        roomContextUrls: [{
-          description: "Mark's super page",
-          location: "http://invalid.com",
-          thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
-        }]
-      });
-
-      expect(view.getDOMNode().querySelector(".standalone-context-url")).not.eql(null);
-    });
-
-    it("should format the url for display", function() {
-      sandbox.stub(sharedUtils, "formatURL").returns({
-          location: "location",
-          hostname: "hostname"
-        });
-
-      var view = mountTestComponent({
-        roomName: "Mike's room",
-        roomContextUrls: [{
-          description: "Mark's super page",
-          location: "http://invalid.com",
-          thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
-        }]
-      });
-
-      expect(view.getDOMNode()
-        .querySelector(".standalone-context-url-description-wrapper > a").textContent)
-        .eql("hostname");
-    });
-
-    it("should not display context information if no urls are supplied", function() {
-      var view = mountTestComponent({
-        roomName: "Mike's room"
-      });
-
-      expect(view.getDOMNode().querySelector(".standalone-context-url")).eql(null);
-    });
-
-    it("should dispatch a RecordClick action when the link is clicked", function() {
-      var view = mountTestComponent({
-        roomName: "Mark's room",
-        roomContextUrls: [{
-          description: "Mark's super page",
-          location: "http://invalid.com",
-          thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
-        }]
-      });
-
-      TestUtils.Simulate.click(view.getDOMNode()
-        .querySelector(".standalone-context-url-description-wrapper > a"));
-
-      sinon.assert.calledOnce(dispatcher.dispatch);
-      sinon.assert.calledWithExactly(dispatcher.dispatch,
-        new sharedActions.RecordClick({
-          linkInfo: "Shared URL"
-        }));
-    });
-
-    it("should display the default favicon when no thumbnail is available", function() {
-      var view = mountTestComponent({
-        roomName: "Mike's room",
-        roomContextUrls: [{
-          description: "Mark's super page",
-          location: "http://invalid.com",
-          thumbnail: ""
-        }]
-      });
-
-      expect(view.getDOMNode().querySelector(".standalone-context-url > img").src)
-        .to.match(/shared\/img\/icons-16x16.svg#globe$/);
-    });
-  });
-
   describe("StandaloneRoomHeader", function() {
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         React.createElement(
           loop.standaloneRoomViews.StandaloneRoomHeader, {
             dispatcher: dispatcher
           }));
     }
@@ -219,53 +100,16 @@ describe("loop.standaloneRoomViews", fun
         "re-entered", function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
           var view = mountTestComponent();
 
           activeRoomStore.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
 
           expectActionDispatched(view);
         });
-
-      it("should updateVideoContainer when the JOINED state is entered", function() {
-          activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
-
-          var view = mountTestComponent();
-
-          sandbox.stub(view, "updateVideoContainer");
-
-          activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
-
-          sinon.assert.calledOnce(view.updateVideoContainer);
-      });
-
-      it("should updateVideoContainer when the JOINED state is re-entered", function() {
-          activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
-
-          var view = mountTestComponent();
-
-          sandbox.stub(view, "updateVideoContainer");
-
-          activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
-
-          sinon.assert.calledOnce(view.updateVideoContainer);
-      });
-
-      it("should reset the video dimensions cache when the gather state is entered", function() {
-        activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
-
-        var view = mountTestComponent();
-
-        activeRoomStore.setStoreState({roomState: ROOM_STATES.GATHER});
-
-        expect(view._videoDimensionsCache).eql({
-          local: {},
-          remote: {}
-        });
-      });
     });
 
     describe("#publishStream", function() {
       var view;
 
       beforeEach(function() {
         view = mountTestComponent();
         view.setState({
@@ -292,262 +136,16 @@ describe("loop.standaloneRoomViews", fun
         sinon.assert.calledOnce(dispatch);
         sinon.assert.calledWithExactly(dispatch, new sharedActions.SetMute({
           type: "video",
           enabled: true
         }));
       });
     });
 
-    describe("Local Stream Size Position", function() {
-      var view, localElement;
-
-      beforeEach(function() {
-        sandbox.stub(window, "matchMedia").returns({
-          matches: false
-        });
-        activeRoomStore.setStoreState({
-          remoteSrcVideoObject: {},
-          remoteVideoEnabled: true
-        });
-        view = mountTestComponent();
-        localElement = view._getElement(".local");
-      });
-
-      it("should be a quarter of the width of the main stream", function() {
-        sandbox.stub(view, "getRemoteVideoDimensions").returns({
-          streamWidth: 640,
-          offsetX: 0
-        });
-
-        view.updateLocalCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(localElement.style.width).eql("160px");
-        expect(localElement.style.height).eql("120px");
-      });
-
-      it("should be a quarter of the width of the remote view element when there is no stream", function() {
-        activeRoomStore.setStoreState({
-          remoteSrcVideoObject: null,
-          remoteVideoEnabled: false
-        });
-
-        sandbox.stub(view, "getDOMNode").returns({
-          querySelector: function(selector) {
-            if (selector === ".local") {
-              return localElement;
-            }
-
-            return {
-              offsetWidth: 640,
-              offsetLeft: 0
-            };
-          }
-        });
-
-        view.updateLocalCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(localElement.style.width).eql("160px");
-        expect(localElement.style.height).eql("120px");
-      });
-
-      it("should be a quarter of the width reduced for aspect ratio", function() {
-        sandbox.stub(view, "getRemoteVideoDimensions").returns({
-          streamWidth: 640,
-          offsetX: 0
-        });
-
-        view.updateLocalCameraPosition({
-          width: 0.75,
-          height: 1
-        });
-
-        expect(localElement.style.width).eql("120px");
-        expect(localElement.style.height).eql("160px");
-      });
-
-      it("should ensure the height is a minimum of 48px", function() {
-        sandbox.stub(view, "getRemoteVideoDimensions").returns({
-          streamWidth: 180,
-          offsetX: 0
-        });
-
-        view.updateLocalCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(localElement.style.width).eql("64px");
-        expect(localElement.style.height).eql("48px");
-      });
-
-      it("should ensure the width is a minimum of 48px", function() {
-        sandbox.stub(view, "getRemoteVideoDimensions").returns({
-          streamWidth: 180,
-          offsetX: 0
-        });
-
-        view.updateLocalCameraPosition({
-          width: 0.75,
-          height: 1
-        });
-
-        expect(localElement.style.width).eql("48px");
-        expect(localElement.style.height).eql("64px");
-      });
-
-      it("should position the stream to overlap the main stream by a quarter", function() {
-        sandbox.stub(view, "getRemoteVideoDimensions").returns({
-          streamWidth: 640,
-          offsetX: 0
-        });
-
-        view.updateLocalCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(localElement.style.width).eql("160px");
-        expect(localElement.style.left).eql("600px");
-      });
-
-      it("should position the stream to overlap the remote view element when there is no stream", function() {
-        activeRoomStore.setStoreState({
-          remoteSrcVideoObject: null,
-          remoteVideoEnabled: false
-        });
-
-        sandbox.stub(view, "getDOMNode").returns({
-          querySelector: function(selector) {
-            if (selector === ".local") {
-              return localElement;
-            }
-
-            return {
-              offsetWidth: 640,
-              offsetLeft: 0
-            };
-          }
-        });
-
-        view.updateLocalCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(localElement.style.width).eql("160px");
-        expect(localElement.style.left).eql("600px");
-      });
-
-      it("should position the stream to overlap the main stream by a quarter when the aspect ratio is vertical", function() {
-        sandbox.stub(view, "getRemoteVideoDimensions").returns({
-          streamWidth: 640,
-          offsetX: 0
-        });
-
-        view.updateLocalCameraPosition({
-          width: 0.75,
-          height: 1
-        });
-
-        expect(localElement.style.width).eql("120px");
-        expect(localElement.style.left).eql("610px");
-      });
-    });
-
-    describe("Remote Stream Size Position", function() {
-      var view, localElement, remoteElement;
-
-      beforeEach(function() {
-        sandbox.stub(window, "matchMedia").returns({
-          matches: false
-        });
-        view = mountTestComponent();
-
-        localElement = {
-          style: {}
-        };
-        remoteElement = {
-          style: {},
-          removeAttribute: sinon.spy()
-        };
-
-        sandbox.stub(view, "_getElement", function(className) {
-          return className === ".local" ? localElement : remoteElement;
-        });
-
-        view.setState({"receivingScreenShare": true});
-      });
-
-      it("should do nothing if not receiving screenshare", function() {
-        view.setState({"receivingScreenShare": false});
-        remoteElement.style.width = "10px";
-
-        view.updateRemoteCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(remoteElement.style.width).eql("10px");
-      });
-
-      it("should be the same width as the local video", function() {
-        localElement.offsetWidth = 100;
-
-        view.updateRemoteCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(remoteElement.style.width).eql("100px");
-      });
-
-      it("should be the same left edge as the local video", function() {
-        localElement.offsetLeft = 50;
-
-        view.updateRemoteCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(remoteElement.style.left).eql("50px");
-      });
-
-      it("should have a height determined by the aspect ratio", function() {
-        localElement.offsetWidth = 100;
-
-        view.updateRemoteCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        expect(remoteElement.style.height).eql("75px");
-      });
-
-      it("should have the top be set such that the bottom is 10px above the local video", function() {
-        localElement.offsetWidth = 100;
-        localElement.offsetTop = 200;
-
-        view.updateRemoteCameraPosition({
-          width: 1,
-          height: 0.75
-        });
-
-        // 200 (top) - 75 (height) - 10 (spacing) = 115
-        expect(remoteElement.style.top).eql("115px");
-      });
-
-    });
-
     describe("#render", function() {
       var view;
 
       beforeEach(function() {
         view = mountTestComponent();
       });
 
       describe("Empty room message", function() {
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -265,16 +265,17 @@
     urls: [{
       description: "A wonderful page!",
       location: "http://wonderful.invalid"
       // use the fallback thumbnail
     }]
   }));
 
   loop.store.StoreMixin.register({
+    activeRoomStore: activeRoomStore,
     conversationStore: conversationStore,
     feedbackStore: feedbackStore,
     textChatStore: textChatStore
   });
 
   // Local mocks
 
   var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -265,16 +265,17 @@
     urls: [{
       description: "A wonderful page!",
       location: "http://wonderful.invalid"
       // use the fallback thumbnail
     }]
   }));
 
   loop.store.StoreMixin.register({
+    activeRoomStore: activeRoomStore,
     conversationStore: conversationStore,
     feedbackStore: feedbackStore,
     textChatStore: textChatStore
   });
 
   // Local mocks
 
   var mockMozLoopRooms = _.extend({}, navigator.mozLoop);