Bug 1253731 - Land version 1.1.11 of the Loop system add-on in mozilla-central - code updates. rs=Standard8 for already reviewed code,a=li\
authorMark Banner <standard8@mozilla.com>
Fri, 04 Mar 2016 23:49:29 +0000
changeset 304447 c3aaa6f957ad50accaf84d5818fbc17627e213cf
parent 304446 9259d8ecdf34c88e63fa205b77c4952c3fd446e2
child 304448 3ac92b42152fc8e895fe8e62d1b86471a6966dc3
push id9210
push usermbanner@mozilla.com
push dateMon, 07 Mar 2016 08:55:10 +0000
treeherdermozilla-aurora@3ac92b42152f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, li
bugs1253731
milestone46.0a2
Bug 1253731 - Land version 1.1.11 of the Loop system add-on in mozilla-central - code updates. rs=Standard8 for already reviewed code,a=li\ zzard
browser/extensions/loop/bootstrap.js
browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
browser/extensions/loop/chrome/content/panels/vendor/simpleSlideshow.js
browser/extensions/loop/chrome/content/shared/css/conversation.css
browser/extensions/loop/chrome/content/shared/img/firefox-hello_logo.svg
browser/extensions/loop/chrome/content/shared/img/paused-hello.svg
browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
browser/extensions/loop/chrome/content/shared/js/loopapi-client.js
browser/extensions/loop/chrome/content/shared/js/utils.js
browser/extensions/loop/chrome/content/shared/js/views.js
browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
browser/extensions/loop/chrome/content/shared/test/loopapi-client_test.js
browser/extensions/loop/chrome/content/shared/test/utils_test.js
browser/extensions/loop/chrome/content/shared/test/views_test.js
browser/extensions/loop/chrome/skin/shared/loop.css
browser/extensions/loop/install.rdf.in
browser/extensions/loop/test/functional/test_1_browser_call.py
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -596,28 +596,35 @@ var WindowListener = {
         if (this._browserSharePaused || !this._listeningToTabSelect) {
           return;
         }
 
         let browser = gBrowser.selectedBrowser;
 
         let cursor = document.getElementById("loop-remote-cursor");
         if (!cursor) {
-          cursor = document.createElement("image");
+          // Create a container to keep the pointer inside.
+          // This allows us to hide the overflow when out of bounds.
+          let cursorContainer = document.createElement("div");
+          cursorContainer.setAttribute("id", "loop-remote-cursor-container");
+
+          cursor = document.createElement("img");
           cursor.setAttribute("id", "loop-remote-cursor");
+
+          cursorContainer.appendChild(cursor);
+          // Note that browser.parent is a xul:stack so container will use
+          // 100% of space if no other constrains added.
+          browser.parentNode.appendChild(cursorContainer);
         }
 
-        // Update the cursor's position.
-        cursor.setAttribute("left",
-                            cursorData.ratioX * browser.boxObject.width);
-        cursor.setAttribute("top",
-                            cursorData.ratioY * browser.boxObject.height);
-
-        // browser's parent is a xul:stack, so positioning with left/top works.
-        browser.parentNode.appendChild(cursor);
+        // Update the cursor's position with CSS.
+        cursor.style.left =
+          Math.abs(cursorData.ratioX * browser.boxObject.width) + "px";
+        cursor.style.top =
+          Math.abs(cursorData.ratioY * browser.boxObject.height) + "px";
       },
 
       /**
        *  Removes the remote cursor from the screen
        *
        *  @param browser OPT browser where the cursor should be removed from.
        */
       removeRemoteCursor: function() {
--- a/browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
@@ -139,16 +139,24 @@ const kPushMessageName = "Loop:Message:P
 const kPushSubscription = "pushSubscription";
 const kRoomsPushPrefix = "Rooms:";
 const kMauPrefMap = new Map(
   Object.getOwnPropertyNames(LOOP_MAU_TYPE).map(name => {
     let parts = name.toLowerCase().split("_");
     return [LOOP_MAU_TYPE[name], parts[0] + parts[1].charAt(0).toUpperCase() + parts[1].substr(1)];
   })
 );
+
+/**
+ * WARNING: Every function in kMessageHandlers must call the reply() function,
+ * as otherwise the content requesters can be left hanging.
+ *
+ * Ideally, we should rewrite them to handle failure/long times better, at which
+ * point this could be relaxed slightly.
+ */
 const kMessageHandlers = {
   /**
    * Start browser sharing, which basically means to start listening for tab
    * switches and passing the new window ID to the sender whenever that happens.
    *
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
    *                           [
--- a/browser/extensions/loop/chrome/content/panels/vendor/simpleSlideshow.js
+++ b/browser/extensions/loop/chrome/content/panels/vendor/simpleSlideshow.js
@@ -94,20 +94,18 @@ loop.SimpleSlideshow = function () {
 
     propTypes: {
       data: React.PropTypes.array.isRequired
     },
     render: function () {
       var slidesNodes = this.props.data.map(function (slideNode, index) {
         var isActive = state.currentSlide === index;
         return React.createElement(Slide, { active: isActive,
-          imageAlt: slideNode.imageAlt,
           imageClass: slideNode.imageClass,
           indexClass: slideNode.id,
-          key: slideNode.id,
           text: slideNode.text,
           title: slideNode.title });
       });
       return React.createElement(
         "div",
         { className: "slides" },
         slidesNodes
       );
--- a/browser/extensions/loop/chrome/content/shared/css/conversation.css
+++ b/browser/extensions/loop/chrome/content/shared/css/conversation.css
@@ -371,16 +371,54 @@ html[dir="rtl"] .settings-menu.dropdown-
 }
 
 @keyframes rotate-spinner {
   to {
     transform: rotate(360deg);
   }
 }
 
+/* Stream paused */
+.focus-stream > .room-inner-info-area > .remote-stream-paused {
+  background-color: #fff;
+  border: solid 1px #a6a6a6;
+  box-shadow: 0 2px 4px rgba(0,0,0,.25);
+  display: inline-block;
+  margin: auto;
+  padding: 10px 15px;
+}
+
+.focus-stream > .room-inner-info-area > .remote-stream-paused > h1 {
+  font-size: 1.6rem;
+  line-height: 3rem;
+  padding-left: 42px;
+  position: relative;
+  text-align: left;
+}
+
+.focus-stream > .room-inner-info-area > .remote-stream-paused > h1:before {
+  background: url("../img/paused-hello.svg") center center no-repeat;
+  content: "";
+  display: block;
+  height: 30px;
+  left: 0;
+  position: absolute;
+  width: 32px;
+}
+
+html[dir="rtl"] .focus-stream > .room-inner-info-area > .remote-stream-paused > h1 {
+  padding-left: 0;
+  padding-right: 42px;
+}
+
+html[dir="rtl"] .focus-stream > .room-inner-info-area > .remote-stream-paused > h1:before {
+  right: 0;
+  transform: scaleX(-1);
+}
+
 .remote > .avatar {
   /* make visually distinct from local avatar */
   opacity: 0.25;
 }
 
 /* Force full height on all parents up to the video elements
  * this way we can ensure the aspect ratio and use height 100%
  * on the video element
@@ -590,16 +628,20 @@ body[platform="win"] .share-service-drop
      chat and video displays. */
   width: calc(100% - 200px);
   /* 100% height to fill up media-layout, thus forcing other elements into the
      second column that's 200px wide */
   height: 100%;
   background-color: #d8d8d8;
 }
 
+.media-wrapper > .focus-stream.screen-sharing-paused > .remote-video-box {
+  display: none;
+}
+
 .media-wrapper > .local {
   flex: 0 1 auto;
   width: 200px;
   height: 150px;
 }
 
 .media-wrapper > .local .local-video,
 .media-wrapper > .focus-stream > .local .local-video {
--- a/browser/extensions/loop/chrome/content/shared/img/firefox-hello_logo.svg
+++ b/browser/extensions/loop/chrome/content/shared/img/firefox-hello_logo.svg
@@ -1,1 +1,1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 225 50"><path fill="#1E92CE" d="M146.3,20.2c-0.1-0.1-0.2-0.2-0.4-0.3c-0.2-0.1-0.3-0.1-0.5-0.1c-0.2,0-0.3,0-0.5,0.1 c-0.2,0.1-0.3,0.2-0.4,0.3c-0.1,0.1-0.2,0.3-0.3,0.4c-0.1,0.2-0.1,0.3-0.1,0.5c0,0.2,0,0.4,0.1,0.5c0.1,0.2,0.2,0.3,0.3,0.4 c0.1,0.1,0.2,0.2,0.4,0.3c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.3,0,0.5-0.1c0.2-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4 c0.1-0.2,0.1-0.3,0.1-0.5c0-0.2,0-0.3-0.1-0.5C146.5,20.4,146.4,20.3,146.3,20.2z M146.3,21.5c-0.1,0.1-0.1,0.2-0.2,0.3 c-0.1,0.1-0.2,0.2-0.3,0.2c-0.1,0.1-0.3,0.1-0.4,0.1c-0.1,0-0.3,0-0.4-0.1c-0.1-0.1-0.2-0.1-0.3-0.2c-0.1-0.1-0.2-0.2-0.2-0.3 c-0.1-0.1-0.1-0.3-0.1-0.4c0-0.2,0-0.3,0.1-0.4c0.1-0.1,0.1-0.2,0.2-0.3c0.1-0.1,0.2-0.2,0.3-0.2c0.1-0.1,0.3-0.1,0.4-0.1 c0.1,0,0.3,0,0.4,0.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.2,0.2,0.2,0.3c0.1,0.1,0.1,0.3,0.1,0.4C146.4,21.2,146.4,21.4,146.3,21.5 z M145.7,21.4C145.7,21.3,145.6,21.3,145.7,21.4c-0.1-0.1-0.1-0.1-0.1-0.2c0,0,0,0,0,0c0.1,0,0.2,0,0.3-0.1 c0.1-0.1,0.1-0.2,0.1-0.3c0-0.1,0-0.1,0-0.2c0,0,0-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c-0.1,0-0.1,0-0.2,0H145v1.4h0.2v-0.6 c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0.1,0.1,0.1,0.1,0.2l0.1,0.2h0.3L145.7,21.4C145.7,21.4,145.7,21.4,145.7,21.4z M145.3,21h-0.1v-0.5h0.1c0.1,0,0.2,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0-0.1,0.1 C145.4,21,145.4,21,145.3,21z M54.4,37.8h4.1V26.5h6.8v-3.3h-6.8v-6.7h8.4l0.5-3.3h-13V37.8z M71.6,11.9c-1.5,0-2.6,1.2-2.6,2.6 c0,1.4,1.1,2.6,2.5,2.6c1.5,0,2.6-1.2,2.6-2.6C74.1,13.1,73,11.9,71.6,11.9z M69.5,37.8h3.9V19.4l-3.9,0.7V37.8z M85.4,19.3 c-1.7,0-3.2,0.9-4.6,2.9c0-1-0.2-2-0.7-2.8l-3.6,0.9c0.4,1.1,0.6,2.6,0.6,4.8v12.7h3.9V25.6c0.4-1.5,1.7-2.7,3.4-2.7 c0.4,0,0.7,0.1,1.1,0.2l1.2-3.6C86.3,19.4,86,19.3,85.4,19.3z M94,19.4c-2.3,0-4,0.7-5.5,2.4c-1.6,1.8-2.2,3.9-2.2,7 c0,5.7,3.2,9.4,8.3,9.4c2.4,0,4.6-0.8,6.4-2.4l-1.5-2.4c-1.3,1.2-2.8,1.8-4.5,1.8c-3.5,0-4.4-2.6-4.4-5.1v-0.3h10.7V29 c0-4.2-0.8-6.4-2.3-7.8C97.4,19.9,95.7,19.4,94,19.4z M90.6,27c0-2.9,1.2-4.6,3.4-4.6c1.9,0,3.2,1.7,3.2,4.6H90.6z M108,19.8v-2.7 c0-1.6,0.9-2.5,2.2-2.5c0.7,0,1.3,0.2,2.2,0.6l1.3-2.5c-1.2-0.7-2.5-1-4-1c-3.2,0-5.5,1.7-5.5,5.5c0,1.7,0.1,2.7,0.1,2.7h-1.7v2.7 h1.7v15.3h3.9V22.5h3.7l1-2.7H108z M119.9,19.4c-4.7,0-7.8,3.7-7.8,9.4c0,5.7,3,9.4,7.9,9.4c4.9,0,7.9-3.6,7.9-9.4 C127.9,23.2,125,19.4,119.9,19.4z M120.1,35.3c-2.3,0-3.6-1.5-3.6-6.7c0-4.4,1.1-6.2,3.5-6.2c2.3,0,3.7,1.5,3.7,6.6 C123.6,33.4,122.4,35.3,120.1,35.3z M143.2,19.8h-4.5c-0.5,0.7-2.3,4.3-2.8,5.5c-0.9-1.7-2.4-4.5-3.3-5.8l-4.2,0.9l5.2,7.7 l-6.7,9.7h4.9c0.7-1,3.2-5.4,3.9-6.7c0.4,0.6,3.3,5.7,3.9,6.7h4.9L138,28L143.2,19.8z M165.8,23.6h-10.3V13.3h-2.9v24.6h2.9V26 h10.3v11.9h2.9V13.3h-2.9V23.6z M179,19.5c-2.1,0-3.9,0.8-5.3,2.5c-1.5,1.8-2.1,3.7-2.1,6.7c0,5.9,3,9.5,7.9,9.5 c2.3,0,4.4-0.8,6-2.2l-1.1-1.8c-1.3,1.1-2.6,1.7-4.4,1.7c-1.8,0-3.4-0.6-4.4-2.2c-0.6-0.9-0.8-2.2-0.8-3.9v-0.4h11.1V29 c-0.1-4.3-0.5-5.9-2-7.5C182.6,20.2,181,19.5,179,19.5z M174.8,27.3c0.1-3.7,1.6-5.6,4.1-5.6c1.4,0,2.6,0.6,3.2,1.6 c0.5,0.9,0.8,2,0.8,4H174.8z M193.1,36.1c-0.8,0-1-0.4-1-1.8V16c0-2.7-0.5-4.2-0.5-4.2l-2.8,0.5c0,0,0.4,1.3,0.4,3.7v18.9 c0,1.3,0.3,2,0.9,2.5c0.5,0.5,1.3,0.8,2.1,0.8c0.8,0,1.1-0.1,1.8-0.4l-0.6-1.8C193.4,36,193.2,36.1,193.1,36.1z M200.3,36.1 c-0.8,0-1-0.4-1-1.8V16c0-2.7-0.5-4.2-0.5-4.2l-2.8,0.5c0,0,0.4,1.3,0.4,3.7v18.9c0,1.3,0.3,2,0.9,2.5c0.5,0.5,1.2,0.8,2.1,0.8 c0.7,0,1.1-0.1,1.8-0.4l-0.6-1.8C200.6,36,200.4,36.1,200.3,36.1z M216.3,22.6c-1.2-1.8-3.1-3.1-6.1-3.1c-4.7,0-7.6,3.5-7.6,9.4 c0,5.9,2.8,9.5,7.7,9.5c4.5,0,7.7-3.2,7.7-9.1C218,26.3,217.5,24.2,216.3,22.6z M214.3,33.3c-0.6,1.7-2,2.7-3.9,2.7 c-1.5,0-3-0.7-3.6-1.8c-0.7-1.1-1.1-3.3-1.1-5.8c0-2.1,0.3-3.5,0.9-4.7c0.6-1.2,2.1-1.9,3.6-1.9c1.5,0,3,0.7,3.8,2.3 c0.6,1.2,0.8,2.9,0.8,5.4C214.8,31.2,214.7,32.2,214.3,33.3z M26.1,7.7C16.1,7.7,8,14.9,8,23.6c0,4.4,2,8.3,5.2,11.2 c-0.6,2-1.7,4.7-3.9,7.3c0.4,0.7,6.6-1.7,11-3.4c1.8,0.5,3.7,0.8,5.7,0.8c10,0,18.1-7.1,18.1-15.9C44.1,14.9,36,7.7,26.1,7.7z M31.5,17.3c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4C29.1,18.3,30.2,17.3,31.5,17.3z M20.6,17.3 c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4C18.2,18.3,19.3,17.3,20.6,17.3z M26.1,34.7 C26.1,34.7,26,34.7,26.1,34.7c-0.1,0-0.1,0-0.1,0c-4.8,0-10.2-3.1-11.5-8.4c3.3,1.5,7.8,2.2,11.5,2.2c3.7,0,8.3-0.7,11.5-2.2 C36.3,31.6,30.9,34.7,26.1,34.7z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 225 50"><path fill="#06A6E0" d="M146.3,20.2c-0.1-0.1-0.2-0.2-0.4-0.3c-0.2-0.1-0.3-0.1-0.5-0.1c-0.2,0-0.3,0-0.5,0.1 c-0.2,0.1-0.3,0.2-0.4,0.3c-0.1,0.1-0.2,0.3-0.3,0.4c-0.1,0.2-0.1,0.3-0.1,0.5c0,0.2,0,0.4,0.1,0.5c0.1,0.2,0.2,0.3,0.3,0.4 c0.1,0.1,0.2,0.2,0.4,0.3c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.3,0,0.5-0.1c0.2-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4 c0.1-0.2,0.1-0.3,0.1-0.5c0-0.2,0-0.3-0.1-0.5C146.5,20.4,146.4,20.3,146.3,20.2z M146.3,21.5c-0.1,0.1-0.1,0.2-0.2,0.3 c-0.1,0.1-0.2,0.2-0.3,0.2c-0.1,0.1-0.3,0.1-0.4,0.1c-0.1,0-0.3,0-0.4-0.1c-0.1-0.1-0.2-0.1-0.3-0.2c-0.1-0.1-0.2-0.2-0.2-0.3 c-0.1-0.1-0.1-0.3-0.1-0.4c0-0.2,0-0.3,0.1-0.4c0.1-0.1,0.1-0.2,0.2-0.3c0.1-0.1,0.2-0.2,0.3-0.2c0.1-0.1,0.3-0.1,0.4-0.1 c0.1,0,0.3,0,0.4,0.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.2,0.2,0.2,0.3c0.1,0.1,0.1,0.3,0.1,0.4C146.4,21.2,146.4,21.4,146.3,21.5 z M145.7,21.4C145.7,21.3,145.6,21.3,145.7,21.4c-0.1-0.1-0.1-0.1-0.1-0.2c0,0,0,0,0,0c0.1,0,0.2,0,0.3-0.1 c0.1-0.1,0.1-0.2,0.1-0.3c0-0.1,0-0.1,0-0.2c0,0,0-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c-0.1,0-0.1,0-0.2,0H145v1.4h0.2v-0.6 c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0.1,0.1,0.1,0.1,0.2l0.1,0.2h0.3L145.7,21.4C145.7,21.4,145.7,21.4,145.7,21.4z M145.3,21h-0.1v-0.5h0.1c0.1,0,0.2,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0-0.1,0.1 C145.4,21,145.4,21,145.3,21z M54.4,37.8h4.1V26.5h6.8v-3.3h-6.8v-6.7h8.4l0.5-3.3h-13V37.8z M71.6,11.9c-1.5,0-2.6,1.2-2.6,2.6 c0,1.4,1.1,2.6,2.5,2.6c1.5,0,2.6-1.2,2.6-2.6C74.1,13.1,73,11.9,71.6,11.9z M69.5,37.8h3.9V19.4l-3.9,0.7V37.8z M85.4,19.3 c-1.7,0-3.2,0.9-4.6,2.9c0-1-0.2-2-0.7-2.8l-3.6,0.9c0.4,1.1,0.6,2.6,0.6,4.8v12.7h3.9V25.6c0.4-1.5,1.7-2.7,3.4-2.7 c0.4,0,0.7,0.1,1.1,0.2l1.2-3.6C86.3,19.4,86,19.3,85.4,19.3z M94,19.4c-2.3,0-4,0.7-5.5,2.4c-1.6,1.8-2.2,3.9-2.2,7 c0,5.7,3.2,9.4,8.3,9.4c2.4,0,4.6-0.8,6.4-2.4l-1.5-2.4c-1.3,1.2-2.8,1.8-4.5,1.8c-3.5,0-4.4-2.6-4.4-5.1v-0.3h10.7V29 c0-4.2-0.8-6.4-2.3-7.8C97.4,19.9,95.7,19.4,94,19.4z M90.6,27c0-2.9,1.2-4.6,3.4-4.6c1.9,0,3.2,1.7,3.2,4.6H90.6z M108,19.8v-2.7 c0-1.6,0.9-2.5,2.2-2.5c0.7,0,1.3,0.2,2.2,0.6l1.3-2.5c-1.2-0.7-2.5-1-4-1c-3.2,0-5.5,1.7-5.5,5.5c0,1.7,0.1,2.7,0.1,2.7h-1.7v2.7 h1.7v15.3h3.9V22.5h3.7l1-2.7H108z M119.9,19.4c-4.7,0-7.8,3.7-7.8,9.4c0,5.7,3,9.4,7.9,9.4c4.9,0,7.9-3.6,7.9-9.4 C127.9,23.2,125,19.4,119.9,19.4z M120.1,35.3c-2.3,0-3.6-1.5-3.6-6.7c0-4.4,1.1-6.2,3.5-6.2c2.3,0,3.7,1.5,3.7,6.6 C123.6,33.4,122.4,35.3,120.1,35.3z M143.2,19.8h-4.5c-0.5,0.7-2.3,4.3-2.8,5.5c-0.9-1.7-2.4-4.5-3.3-5.8l-4.2,0.9l5.2,7.7 l-6.7,9.7h4.9c0.7-1,3.2-5.4,3.9-6.7c0.4,0.6,3.3,5.7,3.9,6.7h4.9L138,28L143.2,19.8z M165.8,23.6h-10.3V13.3h-2.9v24.6h2.9V26 h10.3v11.9h2.9V13.3h-2.9V23.6z M179,19.5c-2.1,0-3.9,0.8-5.3,2.5c-1.5,1.8-2.1,3.7-2.1,6.7c0,5.9,3,9.5,7.9,9.5 c2.3,0,4.4-0.8,6-2.2l-1.1-1.8c-1.3,1.1-2.6,1.7-4.4,1.7c-1.8,0-3.4-0.6-4.4-2.2c-0.6-0.9-0.8-2.2-0.8-3.9v-0.4h11.1V29 c-0.1-4.3-0.5-5.9-2-7.5C182.6,20.2,181,19.5,179,19.5z M174.8,27.3c0.1-3.7,1.6-5.6,4.1-5.6c1.4,0,2.6,0.6,3.2,1.6 c0.5,0.9,0.8,2,0.8,4H174.8z M193.1,36.1c-0.8,0-1-0.4-1-1.8V16c0-2.7-0.5-4.2-0.5-4.2l-2.8,0.5c0,0,0.4,1.3,0.4,3.7v18.9 c0,1.3,0.3,2,0.9,2.5c0.5,0.5,1.3,0.8,2.1,0.8c0.8,0,1.1-0.1,1.8-0.4l-0.6-1.8C193.4,36,193.2,36.1,193.1,36.1z M200.3,36.1 c-0.8,0-1-0.4-1-1.8V16c0-2.7-0.5-4.2-0.5-4.2l-2.8,0.5c0,0,0.4,1.3,0.4,3.7v18.9c0,1.3,0.3,2,0.9,2.5c0.5,0.5,1.2,0.8,2.1,0.8 c0.7,0,1.1-0.1,1.8-0.4l-0.6-1.8C200.6,36,200.4,36.1,200.3,36.1z M216.3,22.6c-1.2-1.8-3.1-3.1-6.1-3.1c-4.7,0-7.6,3.5-7.6,9.4 c0,5.9,2.8,9.5,7.7,9.5c4.5,0,7.7-3.2,7.7-9.1C218,26.3,217.5,24.2,216.3,22.6z M214.3,33.3c-0.6,1.7-2,2.7-3.9,2.7 c-1.5,0-3-0.7-3.6-1.8c-0.7-1.1-1.1-3.3-1.1-5.8c0-2.1,0.3-3.5,0.9-4.7c0.6-1.2,2.1-1.9,3.6-1.9c1.5,0,3,0.7,3.8,2.3 c0.6,1.2,0.8,2.9,0.8,5.4C214.8,31.2,214.7,32.2,214.3,33.3z M26.1,7.7C16.1,7.7,8,14.9,8,23.6c0,4.4,2,8.3,5.2,11.2 c-0.6,2-1.7,4.7-3.9,7.3c0.4,0.7,6.6-1.7,11-3.4c1.8,0.5,3.7,0.8,5.7,0.8c10,0,18.1-7.1,18.1-15.9C44.1,14.9,36,7.7,26.1,7.7z M31.5,17.3c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4C29.1,18.3,30.2,17.3,31.5,17.3z M20.6,17.3 c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4C18.2,18.3,19.3,17.3,20.6,17.3z M26.1,34.7 C26.1,34.7,26,34.7,26.1,34.7c-0.1,0-0.1,0-0.1,0c-4.8,0-10.2-3.1-11.5-8.4c3.3,1.5,7.8,2.2,11.5,2.2c3.7,0,8.3-0.7,11.5-2.2 C36.3,31.6,30.9,34.7,26.1,34.7z"/></svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/loop/chrome/content/shared/img/paused-hello.svg
@@ -0,0 +1,1 @@
+<svg width="32" height="30" viewBox="0 0 32 30" xmlns="http://www.w3.org/2000/svg"><path d="M15.999 0c-8.834 0-15.999 6.135-15.999 13.7 0 3.767 1.776 7.179 4.648 9.656-.499 1.714-1.487 4.04-3.438 6.311.334.575 5.83-1.454 9.704-2.973 1.599.456 3.306.706 5.084.706 8.837 0 16.001-6.134 16.001-13.701 0-7.565-7.164-13.7-16.001-13.7zm4.742 8.186v9.823c0 .121-.035.226-.106.314-.07.088-.154.133-.25.133h-2.844c-.096 0-.18-.044-.25-.133-.07-.088-.106-.193-.106-.314v-9.823c0-.121.035-.226.106-.314.07-.088.154-.133.25-.133h2.844c.096 0 .18.044.25.133.07.088.106.193.106.314zm-5.333 0v9.823c0 .121-.035.226-.106.314-.07.088-.154.133-.25.133h-2.844c-.096 0-.18-.044-.25-.133-.07-.088-.106-.193-.106-.314v-9.823c0-.121.035-.226.106-.314.07-.088.154-.133.25-.133h2.844c.096 0 .18.044.25.133.07.088.106.193.106.314z" fill="#757575"/></svg>
--- a/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
+++ b/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
@@ -107,16 +107,17 @@ loop.store.ActiveRoomStore = (function(m
       "chatMessageExchanged",
       "localSrcMediaElement",
       "localVideoDimensions",
       "mediaConnected",
       "receivingScreenShare",
       "remoteSrcMediaElement",
       "remoteVideoDimensions",
       "remoteVideoEnabled",
+      "streamPaused",
       "screenSharingState",
       "screenShareMediaElement",
       "videoMuted"
     ],
 
     /**
      * Returns initial state data for this active room.
      *
@@ -146,16 +147,18 @@ loop.store.ActiveRoomStore = (function(m
         // Any urls (aka context) associated with the room.
         roomContextUrls: null,
         // The description for a room as stored in the context data.
         roomDescription: null,
         // Room information failed to be obtained for a reason. See ROOM_INFO_FAILURES.
         roomInfoFailure: null,
         // The name of the room.
         roomName: null,
+        // True when sharing screen has been paused.
+        streamPaused: false,
         // Social API state.
         socialShareProviders: null,
         // True if media has been connected both-ways.
         mediaConnected: false,
         // True if a chat message was sent or received during a session.
         // Read more at https://wiki.mozilla.org/Loop/Session.
         chatMessageExchanged: false
       };
@@ -263,17 +266,18 @@ loop.store.ActiveRoomStore = (function(m
         "mediaStreamDestroyed",
         "remoteVideoStatus",
         "videoDimensionsChanged",
         "startBrowserShare",
         "endScreenShare",
         "toggleBrowserSharing",
         "updateSocialShareInfo",
         "connectionStatus",
-        "mediaConnected"
+        "mediaConnected",
+        "videoScreenStreamChanged"
       ];
       // Register actions that are only used on Desktop.
       if (this._isDesktop) {
         // 'receivedTextChatMessage' and  'sendTextChatMessage' actions are only
         // registered for Telemetry. Once measured, they're unregistered.
         actions.push("receivedTextChatMessage", "sendTextChatMessage");
       }
       this.dispatcher.register(this, actions);
@@ -1203,16 +1207,28 @@ loop.store.ActiveRoomStore = (function(m
       var storeProp = (actionData.isLocal ? "local" : "remote") + "VideoDimensions";
       var nextState = {};
       nextState[storeProp] = this.getStoreState()[storeProp];
       nextState[storeProp][actionData.videoType] = actionData.dimensions;
       this.setStoreState(nextState);
     },
 
     /**
+     * Listen to screen stream changes in order to check if sharing screen
+     * has been paused.
+     *
+     * @param {sharedActions.VideoScreenStreamChanged} actionData
+     */
+    videoScreenStreamChanged: function(actionData) {
+      this.setStoreState({
+        streamPaused: !actionData.hasVideo
+      });
+    },
+
+    /**
      * Handles chat messages received and/ or about to send. If this is the first
      * chat message for the current session, register a count with telemetry.
      * It will unhook the listeners when the telemetry criteria have been
      * fulfilled to make sure we remain lean.
      * Note: the 'receivedTextChatMessage' and 'sendTextChatMessage' actions are
      *       only registered on Desktop.
      *
      * @param  {sharedActions.ReceivedTextChatMessage|SendTextChatMessage} actionData
--- a/browser/extensions/loop/chrome/content/shared/js/loopapi-client.js
+++ b/browser/extensions/loop/chrome/content/shared/js/loopapi-client.js
@@ -6,17 +6,16 @@ var loop = loop || {};
 (function() {
   "use strict";
 
   var _slice = Array.prototype.slice;
 
   var kMessageName = "Loop:Message";
   var kPushMessageName = "Loop:Message:Push";
   var kBatchMessage = "Batch";
-  var kReplyTimeoutMs = 5000;
   var gListeningForMessages = false;
   var gListenersMap = {};
   var gListeningForPushMessages = false;
   var gSubscriptionsMap = {};
   var gRootObj = window;
 
   loop._lastMessageID = 0;
 
@@ -70,30 +69,20 @@ var loop = loop || {};
         };
 
         gRootObj.addMessageListener(kMessageName, gListeningForMessages);
       }
 
       gListenersMap[seq] = resolve;
 
       gRootObj.sendAsyncMessage(kMessageName, payload);
-
-      gRootObj.setTimeout(function() {
-        // Check if the promise was already resolved before by the message handler.
-        if (!gListenersMap[seq]) {
-          return;
-        }
-        resolve();
-        delete gListenersMap[seq];
-      }, kReplyTimeoutMs);
     });
   };
 
   // These functions should only be used in unit tests.
-  loop.request.getReplyTimeoutMs = function() { return kReplyTimeoutMs; };
   loop.request.inspect = function() { return _.extend({}, gListenersMap); };
   loop.request.reset = function() {
     gListeningForMessages = false;
     gListenersMap = {};
   };
 
   loop.storedRequests = {};
 
--- a/browser/extensions/loop/chrome/content/shared/js/utils.js
+++ b/browser/extensions/loop/chrome/content/shared/js/utils.js
@@ -391,33 +391,35 @@ if (inChrome) {
    * Formats a url for display purposes. This includes converting the
    * domain to punycode, and then decoding the url.
    * Intended to be used for both display and (uglier in confusing cases) clickthrough,
    * as described by dveditz in comment 12 of the bug 1196143,
    * as well as testing the behavior case in the browser.
    *
    * @param {String}  url                   The url to format.
    * @param {String}  suppressConsoleError  For testing, call with a boolean which is true to squash the default console error.
-   * @return {Object}                       An object containing the hostname and full location.
+   * @return {Object}                       An object containing the hostname,
+   *                                        full location and protocol.
    */
   function formatURL(url, suppressConsoleError) {
     // We're using new URL to pass this through the browser's ACE/punycode
     // processing system. If the browser considers a url to need to be
     // punycode encoded for it to be displayed, then new URL will do that for
     // us. This saves us needing our own punycode library.
     // Note that URL does canonicalize hostname-only URLs,
     // adding a slash to them, but this is ok for at least HTTP(S)
     // because GET always has to specify a path, which will (by default) be
     var urlObject;
     try {
       urlObject = new URL(url);
       // Finally, ensure we look good.
       return {
         hostname: urlObject.hostname,
-        location: decodeURI(urlObject.href)
+        location: decodeURI(urlObject.href),
+        protocol: urlObject.protocol
       };
     } catch (ex) {
       if (suppressConsoleError ? !suppressConsoleError : true) {
         console.log("Error occurred whilst parsing URL: ", ex);
         console.trace();
       }
       return null;
     }
--- a/browser/extensions/loop/chrome/content/shared/js/views.js
+++ b/browser/extensions/loop/chrome/content/shared/js/views.js
@@ -601,21 +601,26 @@ loop.shared.views = function (_, mozL10n
         linkInfo: "Shared URL"
       }));
     },
 
     render: function () {
       // Bug 1196143 - formatURL sanitizes(decodes) the URL from IDN homographic attacks.
       // Try catch to not produce output if invalid url
       try {
-        var sanitizeURL = loop.shared.utils.formatURL(this.props.url, true).hostname;
+        var sanitizedURL = loop.shared.utils.formatURL(this.props.url, true);
       } catch (ex) {
         return null;
       }
 
+      // Only allow specific types of URLs.
+      if (!sanitizedURL || sanitizedURL.protocol !== "http:" && sanitizedURL.protocol !== "https:" && sanitizedURL.protocol !== "ftp:") {
+        return null;
+      }
+
       var thumbnail = this.props.thumbnail;
 
       if (!thumbnail) {
         thumbnail = this.props.useDesktopPaths ? "shared/img/icons-16x16.svg#globe" : "shared/img/icons-16x16.svg#globe";
       }
 
       var wrapperClasses = classNames({
         "context-wrapper": true,
@@ -635,17 +640,17 @@ loop.shared.views = function (_, mozL10n
           React.createElement("img", { className: "context-preview", src: thumbnail }),
           React.createElement(
             "span",
             { className: "context-info" },
             this.props.description,
             React.createElement(
               "span",
               { className: "context-url" },
-              sanitizeURL
+              sanitizedURL.hostname
             )
           )
         )
       );
     }
   });
 
   /**
@@ -661,16 +666,17 @@ loop.shared.views = function (_, mozL10n
 
     propTypes: {
       cursorStore: React.PropTypes.instanceOf(loop.store.RemoteCursorStore),
       dispatcher: React.PropTypes.object,
       displayAvatar: React.PropTypes.bool.isRequired,
       isLoading: React.PropTypes.bool.isRequired,
       mediaType: React.PropTypes.string.isRequired,
       posterUrl: React.PropTypes.string,
+      screenSharingPaused: React.PropTypes.bool,
       shareCursor: React.PropTypes.bool,
       // Expecting "local" or "remote".
       srcMediaElement: React.PropTypes.object
     },
 
     getInitialState: function () {
       return {
         videoElementSize: null
@@ -776,17 +782,17 @@ loop.shared.views = function (_, mozL10n
       }
 
       var videoElement = this.getDOMNode().querySelector("video");
       if (!videoElement || videoElement.tagName.toLowerCase() !== "video") {
         // Must be displaying the avatar view, so don't try and attach video.
         return;
       }
 
-      if (this.props.shareCursor) {
+      if (this.props.shareCursor && !this.props.screenSharingPaused) {
         videoElement.addEventListener("loadeddata", this.handleVideoDimensions);
         videoElement.addEventListener("mousemove", this.handleMouseMove);
       }
 
       // Set the src of our video element
       var attrName = "";
       if ("srcObject" in videoElement) {
         // srcObject is according to the standard.
@@ -867,16 +873,17 @@ loop.shared.views = function (_, mozL10n
       // Passing in matchMedia, allows it to be overriden for ui-showcase's
       // benefit. We expect either the override or window.matchMedia.
       matchMedia: React.PropTypes.func.isRequired,
       remotePosterUrl: React.PropTypes.string,
       remoteSrcMediaElement: React.PropTypes.object,
       renderRemoteVideo: React.PropTypes.bool.isRequired,
       screenShareMediaElement: React.PropTypes.object,
       screenSharePosterUrl: React.PropTypes.string,
+      screenSharingPaused: React.PropTypes.bool,
       showInitialContext: React.PropTypes.bool.isRequired,
       useDesktopPaths: React.PropTypes.bool.isRequired
     },
 
     isLocalMediaAbsolutelyPositioned: function (matchMedia) {
       if (!matchMedia) {
         matchMedia = this.props.matchMedia;
       }
@@ -933,17 +940,18 @@ loop.shared.views = function (_, mozL10n
     render: function () {
       var remoteStreamClasses = classNames({
         "remote": true,
         "focus-stream": !this.props.displayScreenShare
       });
 
       var screenShareStreamClasses = classNames({
         "screen": true,
-        "focus-stream": this.props.displayScreenShare
+        "focus-stream": this.props.displayScreenShare,
+        "screen-sharing-paused": this.props.screenSharingPaused
       });
 
       var mediaWrapperClasses = classNames({
         "media-wrapper": true,
         "receiving-screen-share": this.props.displayScreenShare,
         "showing-local-streams": this.props.localSrcMediaElement || this.props.localPosterUrl,
         "showing-remote-streams": this.props.remoteSrcMediaElement || this.props.remotePosterUrl || this.props.isRemoteLoading
       });
@@ -976,16 +984,17 @@ loop.shared.views = function (_, mozL10n
             { className: screenShareStreamClasses },
             React.createElement(MediaView, {
               cursorStore: this.props.cursorStore,
               dispatcher: this.props.dispatcher,
               displayAvatar: false,
               isLoading: this.props.isScreenShareLoading,
               mediaType: "screen-share",
               posterUrl: this.props.screenSharePosterUrl,
+              screenSharingPaused: this.props.screenSharingPaused,
               shareCursor: true,
               srcMediaElement: this.props.screenShareMediaElement }),
             this.props.displayScreenShare ? this.props.children : null
           ),
           React.createElement(loop.shared.views.chat.TextChatView, {
             dispatcher: this.props.dispatcher,
             showInitialContext: this.props.showInitialContext,
             useDesktopPaths: this.props.useDesktopPaths }),
--- a/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
@@ -743,16 +743,27 @@ describe("loop.store.ActiveRoomStore", f
       actionData.isLocal = false;
       store.videoDimensionsChanged(new sharedActions.VideoDimensionsChanged(actionData));
 
       expect(store.getStoreState().remoteVideoDimensions)
         .to.have.property(actionData.videoType, actionData.dimensions);
     });
   });
 
+  describe("#videoScreenStreamChanged", function() {
+    it("should set streamPaused if screen stream has no video", function() {
+      var actionData = {
+        hasVideo: false
+      };
+
+      store.videoScreenStreamChanged(new sharedActions.VideoScreenStreamChanged(actionData));
+      expect(store.getStoreState().streamPaused).eql(true);
+    });
+  });
+
   describe("#updateRoomInfo", function() {
     var fakeRoomInfo;
 
     beforeEach(function() {
       fakeRoomInfo = {
         roomContextUrls: [{
           description: "fake site",
           location: "http://invalid.com",
--- a/browser/extensions/loop/chrome/content/shared/test/loopapi-client_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/loopapi-client_test.js
@@ -1,24 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 describe("loopapi-client", function() {
   "use strict";
 
   var expect = chai.expect;
-  var sandbox, clock, replyTimeoutMs;
+  var sandbox;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     window.addMessageListener = sinon.stub();
     window.removeMessageListener = sinon.stub();
     window.sendAsyncMessage = sinon.stub();
-    clock = sandbox.useFakeTimers();
-    replyTimeoutMs = loop.request.getReplyTimeoutMs();
   });
 
   afterEach(function() {
     loop.request.reset();
     loop.subscribe.reset();
     sandbox.restore();
 
     delete window.addMessageListener;
@@ -31,37 +29,52 @@ describe("loopapi-client", function() {
       var promise = loop.request("GetLoopPref", "enabled");
 
       expect(promise).to.be.an.instanceof(Promise);
       sinon.assert.calledOnce(window.sendAsyncMessage);
       sinon.assert.calledWithExactly(window.sendAsyncMessage, "Loop:Message",
         [loop._lastMessageID, "GetLoopPref", "enabled"]);
       sinon.assert.calledOnce(window.addMessageListener);
 
-      clock.tick(replyTimeoutMs);
+      // Call the added listener, so that the promise resolves.
+      window.addMessageListener.args[0][1]({
+        name: "Loop:Message",
+        data: [loop._lastMessageID, "true"]
+      });
+
       return promise;
     });
 
     it("should correct the command name", function() {
       var promise = loop.request("getLoopPref", "enabled");
 
       sinon.assert.calledWithExactly(window.sendAsyncMessage, "Loop:Message",
         [loop._lastMessageID, "GetLoopPref", "enabled"]);
 
-      clock.tick(replyTimeoutMs);
+      // Call the added listener, so that the promise resolves.
+      window.addMessageListener.args[0][1]({
+        name: "Loop:Message",
+        data: [loop._lastMessageID, "true"]
+      });
+
       return promise;
     });
 
     it("should pass all arguments in-order", function() {
       var promise = loop.request("SetLoopPref", "enabled", false, 1, 2, 3);
 
       sinon.assert.calledWithExactly(window.sendAsyncMessage, "Loop:Message",
         [loop._lastMessageID, "SetLoopPref", "enabled", false, 1, 2, 3]);
 
-      clock.tick(replyTimeoutMs);
+      // Call the added listener, so that the promise resolves.
+      window.addMessageListener.args[0][1]({
+        name: "Loop:Message",
+        data: [loop._lastMessageID, "true"]
+      });
+
       return promise;
     });
 
     it("should resolve the promise when a response is received", function() {
       var listener;
       window.addMessageListener = function(name, callback) {
         listener = callback;
       };
@@ -72,42 +85,39 @@ describe("loopapi-client", function() {
 
       listener({
         data: [loop._lastMessageID, "result"]
       });
 
       return promise;
     });
 
-    it("should cancel the message listener when no reply is received in time", function() {
-      var promise = loop.request("GetLoopPref", "enabled");
-
-      promise.then(function(result) {
-        expect(result).to.eql(undefined);
-      });
-
-      clock.tick(replyTimeoutMs);
-      return promise;
-    });
-
     it("should not start listening for messages more than once", function() {
       return new Promise(function(resolve) {
         loop.request("GetLoopPref", "enabled").then(function() {
           sinon.assert.calledOnce(window.addMessageListener);
 
           loop.request("GetLoopPref", "enabled").then(function() {
             sinon.assert.calledOnce(window.addMessageListener);
 
             resolve();
           });
 
-          clock.tick(replyTimeoutMs);
+          // Call the added listener, so that the promise resolves.
+          window.addMessageListener.args[0][1]({
+            name: "Loop:Message",
+            data: [loop._lastMessageID, "true"]
+          });
         });
 
-        clock.tick(replyTimeoutMs);
+        // Call the added listener, so that the promise resolves.
+        window.addMessageListener.args[0][1]({
+          name: "Loop:Message",
+          data: [loop._lastMessageID, "true"]
+        });
       });
     });
   });
 
   describe("loop.storeRequest", function() {
     afterEach(function() {
       loop.storedRequests = {};
     });
@@ -161,34 +171,44 @@ describe("loopapi-client", function() {
       expect(promise).to.be.an.instanceof(Promise);
       sinon.assert.calledOnce(window.sendAsyncMessage);
       sinon.assert.calledWithExactly(window.sendAsyncMessage, "Loop:Message",
         [loop._lastMessageID, "Batch", [
           [loop._lastMessageID - 2, "GetLoopPref", "enabled"],
           [loop._lastMessageID - 1, "GetLoopPref", "e10s.enabled"]]
         ]);
 
-      clock.tick(replyTimeoutMs);
+      // Call the added listener, so that the promise resolves.
+      window.addMessageListener.args[0][1]({
+        name: "Loop:Message",
+        data: [loop._lastMessageID, "true"]
+      });
+
       return promise;
     });
 
     it("should correct command names", function() {
       var promise = loop.requestMulti(
         ["GetLoopPref", "enabled"],
         // Use lowercase 'g' on purpose, it should get corrected:
         ["getLoopPref", "e10s.enabled"]
       );
 
       sinon.assert.calledWithExactly(window.sendAsyncMessage, "Loop:Message",
         [loop._lastMessageID, "Batch", [
           [loop._lastMessageID - 2, "GetLoopPref", "enabled"],
           [loop._lastMessageID - 1, "GetLoopPref", "e10s.enabled"]]
         ]);
 
-      clock.tick(replyTimeoutMs);
+      // Call the added listener, so that the promise resolves.
+      window.addMessageListener.args[0][1]({
+        name: "Loop:Message",
+        data: [loop._lastMessageID, "true"]
+      });
+
       return promise;
     });
 
     it("should resolve the promise when a response is received", function() {
       var listener;
       window.addMessageListener = function(name, callback) {
         listener = callback;
       };
--- a/browser/extensions/loop/chrome/content/shared/test/utils_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/utils_test.js
@@ -298,28 +298,30 @@ describe("loop.shared.utils", function()
       // Stub to prevent console messages.
       sandbox.stub(window.console, "log");
     });
 
     it("should decode encoded URIs", function() {
       expect(sharedUtils.formatURL("http://invalid.com/?a=Foo%20Bar"))
         .eql({
           location: "http://invalid.com/?a=Foo Bar",
-          hostname: "invalid.com"
+          hostname: "invalid.com",
+          protocol: "http:"
         });
     });
 
     it("should change some idn urls to ascii encoded", function() {
       // Note, this is based on the browser's list of what does/doesn't get
       // altered for punycode, so if the list changes this could change in the
       // future.
       expect(sharedUtils.formatURL("http://\u0261oogle.com/"))
         .eql({
           location: "http://xn--oogle-qmc.com/",
-          hostname: "xn--oogle-qmc.com"
+          hostname: "xn--oogle-qmc.com",
+          protocol: "http:"
         });
     });
 
     it("should return null if suppressConsoleError is true and the url is not valid", function() {
       expect(sharedUtils.formatURL("hinvalid//url", true)).eql(null);
     });
 
     it("should return null if suppressConsoleError is true and is a malformed URI sequence", function() {
--- a/browser/extensions/loop/chrome/content/shared/test/views_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/views_test.js
@@ -827,16 +827,26 @@ describe("VideoMuteButton", function() {
       });
 
       var node = view.getDOMNode();
 
       expect(node.querySelector(".remote").classList.contains("focus-stream")).eql(false);
       expect(node.querySelector(".screen").classList.contains("focus-stream")).eql(true);
     });
 
+    it("should mark the screen share stream as paused when screen shared has been paused", function() {
+      view = mountTestComponent({
+        screenSharingPaused: true
+      });
+
+      var node = view.getDOMNode();
+
+      expect(node.querySelector(".screen").classList.contains("screen-sharing-paused")).eql(true);
+    });
+
     it("should not mark the wrapper as receiving screen share when not displaying a screen share", function() {
       view = mountTestComponent({
         displayScreenShare: false
       });
 
       expect(view.getDOMNode().querySelector(".media-wrapper")
         .classList.contains("receiving-screen-share")).eql(false);
     });
--- a/browser/extensions/loop/chrome/skin/shared/loop.css
+++ b/browser/extensions/loop/chrome/skin/shared/loop.css
@@ -314,9 +314,18 @@
   #loop-slideshow-browser {
     width: 620px;
     height:450px;
     margin-top: 10%;
 
     /* XXX derived from width, so should be 50% - (620px / 2)? */
     -moz-margin-start: calc(50% - 310px);
   }
+
+  #loop-remote-cursor-container {
+    position: absolute;
+    pointer-events: none;
+    /* Hide overflow so that when the tail of the pointer gets to the edge
+     * of shared area, it doesn't widen the viewport by adding a scrollbar.
+     */
+    overflow: hidden;
+  }
 }
--- a/browser/extensions/loop/install.rdf.in
+++ b/browser/extensions/loop/install.rdf.in
@@ -4,17 +4,17 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest">
     <em:id>loop@mozilla.org</em:id>
     <em:bootstrap>true</em:bootstrap>
-    <em:version>1.1.9</em:version>
+    <em:version>1.1.11</em:version>
     <em:type>2</em:type>
 
     <!-- Target Application this extension can install into,
          with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>46.0a1</em:minVersion>
--- a/browser/extensions/loop/test/functional/test_1_browser_call.py
+++ b/browser/extensions/loop/test/functional/test_1_browser_call.py
@@ -117,20 +117,21 @@ class Test1BrowserCall(MarionetteTestCas
                       "room URL returned by server: '" + room_url +
                       "' has invalid scheme")
         return room_url
 
     def standalone_load_and_join_room(self, url):
         self.switch_to_standalone()
         self.marionette.navigate(url)
 
-        # Join the room
-        join_button = self.wait_for_element_displayed(By.CLASS_NAME,
-                                                      "btn-join")
-        join_button.click()
+        # Join the room - the first time around, the tour will be displayed
+        # so we look for its close button.
+        tour_close_button = self.wait_for_element_displayed(By.CLASS_NAME,
+                                                            "button-close")
+        tour_close_button.click()
 
     # Assumes the standalone or the conversation window is selected first.
     def check_video(self, selector):
         video = self.wait_for_element_displayed(By.CSS_SELECTOR,
                                                 selector, 20)
         self.wait_for_element_attribute_to_be_false(video, "paused")
         self.assertEqual(video.get_attribute("ended"), "false")