Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 18 Jun 2015 16:06:54 -0400
changeset 280407 740f1dc6307949ff99166375348caa4ba7a0d704
parent 280406 5088462e483356018031755766e1752768baab7f (current diff)
parent 280399 4b0fb77f77a43835b83f07afee72574d06b6998a (diff)
child 280408 2694ff2ace6a1b5f6d283c5eafafac730487b189
child 280409 3bb9ad48ec026d36185b83b3ee1f4297acf9d48a
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
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
Merge m-c to b2g-inbound. a=merge
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1202,16 +1202,17 @@ pref("security.sandbox.windows.log", fal
 // On windows these levels are:
 // 0 - no sandbox
 // 1 - sandbox with USER_NON_ADMIN access token level
 // 2 - a more strict sandbox, which might cause functionality issues. This now
 //     includes running at low integrity.
 // 3 - the strongest settings we seem to be able to use without breaking
 //     everything, but will probably cause some functionality restrictions
 pref("dom.ipc.plugins.sandbox-level.default", 0);
+pref("dom.ipc.plugins.sandbox-level.flash", 0);
 
 #if defined(MOZ_CONTENT_SANDBOX)
 // This controls the strength of the Windows content process sandbox for testing
 // purposes. This will require a restart.
 // On windows these levels are:
 // 0 - sandbox with USER_NON_ADMIN access token level
 // 1 - level 0 plus low integrity
 // 2 - a policy that we can reasonably call an effective sandbox
@@ -1404,18 +1405,18 @@ pref("devtools.command-button-rulers.ena
 // Inspector preferences
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 // What was the last active sidebar in the inspector
 pref("devtools.inspector.activeSidebar", "ruleview");
 // Enable the markup preview
 pref("devtools.inspector.markupPreview", false);
 pref("devtools.inspector.remote", false);
-// Expand pseudo-elements by default in the rule-view
-pref("devtools.inspector.show_pseudo_elements", true);
+// Collapse pseudo-elements by default in the rule-view
+pref("devtools.inspector.show_pseudo_elements", false);
 // The default size for image preview tooltips in the rule-view/computed-view/markup-view
 pref("devtools.inspector.imagePreviewTooltipSize", 300);
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content (like controls in <video> tags)
 pref("devtools.inspector.showAllAnonymousContent", false);
 // Enable the MDN docs tooltip
 pref("devtools.inspector.mdnDocsTooltip.enabled", true);
@@ -1831,16 +1832,19 @@ pref("identity.fxaccounts.remote.webchan
 pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings");
 
 // The remote URL of the FxA Profile Server
 pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
 
 // The remote URL of the FxA OAuth Server
 pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
 
+// Whether we display profile images in the UI or not.
+pref("identity.fxaccounts.profile_image.enabled", true);
+
 // Migrate any existing Firefox Account data from the default profile to the
 // Developer Edition profile.
 #ifdef MOZ_DEV_EDITION
 pref("identity.fxaccounts.migrateToDevEdition", true);
 #else
 pref("identity.fxaccounts.migrateToDevEdition", false);
 #endif
 
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -85,16 +85,22 @@ var FullScreen = {
       // mozfullscreenchange event fired, which could confuse content script.
       this.hideNavToolbox(document.mozFullScreen);
     }
     else {
       this.showNavToolbox(false);
       // This is needed if they use the context menu to quit fullscreen
       this._isPopupOpen = false;
       this.cleanup();
+      // In TabsInTitlebar._update(), we cancel the appearance update on
+      // resize event for exiting fullscreen, since that happens before we
+      // change the UI here in the "fullscreen" event. Hence we need to
+      // call it here to ensure the appearance is properly updated. See
+      // TabsInTitlebar._update() and bug 1173768.
+      TabsInTitlebar.updateAppearance(true);
     }
   },
 
   exitDomFullScreen : function() {
     document.mozCancelFullScreen();
   },
 
   handleEvent: function (event) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5061,17 +5061,25 @@ var TabsInTitlebar = {
       // _update is called on resize events, because the window is not ready
       // after sizemode events. However, we only care about the event when the
       // sizemode is different from the last time we updated the appearance of
       // the tabs in the titlebar.
       let sizemode = document.documentElement.getAttribute("sizemode");
       if (this._lastSizeMode == sizemode) {
         return;
       }
+      let oldSizeMode = this._lastSizeMode;
       this._lastSizeMode = sizemode;
+      // Don't update right now if we are leaving fullscreen, since the UI is
+      // still changing in the consequent "fullscreen" event. Code there will
+      // call this function again when everything is ready.
+      // See browser-fullScreen.js: FullScreen.toggle and bug 1173768.
+      if (oldSizeMode == "fullscreen") {
+        return;
+      }
     }
 
     for (let something in this._disallowed) {
       allowed = false;
       break;
     }
 
     let titlebar = $("titlebar");
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -496,16 +496,26 @@ input[type=button] {
   background-color: white;
   border-radius: 6px;
   filter: drop-shadow(0 0 1px rgba(0,0,0,0.4)) drop-shadow(0 3px 4px rgba(0,0,0,0.4));
   transition: all 200ms ease-in-out;
   transform-origin: top right;
   transform: translate(-30px, -20px) scale(0) translate(30px, 20px);
 }
 
+#newtab-customize-panel:-moz-locale-dir(rtl) {
+  transform-origin: 40px top 20px;
+}
+
+#newtab-customize-panel:-moz-locale-dir(rtl),
+#newtab-customize-panel-anchor:-moz-locale-dir(rtl) {
+  left: 15px;
+  right: auto;
+}
+
 #newtab-customize-panel[open="true"] {
   transform: translate(-30px, -20px) scale(1) translate(30px, 20px);
 }
 
 #newtab-customize-panel-anchor {
   width: 18px;
   height: 18px;
   background-color: white;
--- a/browser/base/content/searchSuggestionUI.js
+++ b/browser/base/content/searchSuggestionUI.js
@@ -140,29 +140,28 @@ SearchSuggestionUIController.prototype =
   addInputValueToFormHistory: function () {
     this._sendMsg("AddFormHistoryEntry", this.input.value);
   },
 
   handleEvent: function (event) {
     this["_on" + event.type[0].toUpperCase() + event.type.substr(1)](event);
   },
 
-  _onInput: function () {
+  _onInput: function (event) {
     if (this._ignoreInputEvent) {
-      this._ignoreInputEvent = false;
       return;
     }
     if (this.input.value) {
       this._getSuggestions();
     }
     else {
       this._stickyInputValue = "";
       this._hideSuggestions();
     }
-    this.selectAndUpdateInput(-1);
+    this.selectedIndex = -1;
   },
 
   _onKeypress: function (event) {
     let selectedIndexDelta = 0;
     switch (event.keyCode) {
     case event.DOM_VK_UP:
       if (this.numSuggestions) {
         selectedIndexDelta = -1;
@@ -237,25 +236,22 @@ SearchSuggestionUIController.prototype =
   _onMousedown: function (event) {
     if (event.button == 2) {
       return;
     }
     let idx = this._indexOfTableRowOrDescendent(event.target);
     let suggestion = this.suggestionAtIndex(idx);
     this._stickyInputValue = suggestion;
 
-    // Commit composition string forcibly, because setting input value does not
-    // work if input has composition string (see bug 1115616 and bug 632744).
-    // Ignore input event for composition end to avoid getting suggestion again.
+    // Setting value commits composition string forcibly.  While IME commits
+    // composition, this needs to ignore input event at committed composition
+    // string which will be overwritten by the suggestion.
     this._ignoreInputEvent = true;
-    this.input.blur();
-    this.input.focus();
+    this.input.value = suggestion;
     this._ignoreInputEvent = false;
-
-    this.input.value = suggestion;
     this.input.setAttribute("selection-index", idx);
     this.input.setAttribute("selection-kind", "mouse");
     this._hideSuggestions();
     if (this.onClick) {
       this.onClick.call(null, event);
     }
   },
 
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -332,17 +332,17 @@ skip-if = buildapp == 'mulet' || e10s # 
 [browser_overflowScroll.js]
 [browser_pageInfo.js]
 skip-if = buildapp == 'mulet'
 [browser_page_style_menu.js]
 
 [browser_parsable_css.js]
 skip-if = e10s
 [browser_parsable_script.js]
-skip-if = asan # Disabled because it takes a long time (see test for more information)
+skip-if = asan || (os == 'linux' && !debug && (bits == 32)) # disabled on asan because of timeouts, and bug 1172468 for the linux 32-bit pgo issue.
 
 [browser_pinnedTabs.js]
 [browser_plainTextLinks.js]
 [browser_popupUI.js]
 skip-if = buildapp == 'mulet'
 [browser_popup_blocker.js]
 skip-if = (os == 'linux') || (e10s && debug) # Frequent bug 1081925 and bug 1125520 failures
 [browser_printpreview.js]
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -3,17 +3,17 @@
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 let suggestedLink = {
   url: "http://example1.com/2",
   imageURI: "data:image/png;base64,helloWORLD3",
   title: "title2",
   type: "affiliate",
-  frecent_sites: ["classroom.google.com", "codecademy.com", "elearning.ut.ac.id", "khanacademy.org", "learn.jquery.com", "teamtreehouse.com", "tutorialspoint.com", "udacity.com", "w3cschool.cc", "w3schools.com"]
+  frecent_sites: ["classroom.google.com", "codeacademy.org", "codecademy.com", "codeschool.com", "codeyear.com", "elearning.ut.ac.id", "how-to-build-websites.com", "htmlcodetutorial.com", "htmldog.com", "htmlplayground.com", "learn.jquery.com", "quackit.com", "roseindia.net", "teamtreehouse.com", "tizag.com", "tutorialspoint.com", "udacity.com", "w3schools.com", "webdevelopersnotes.com"]
 };
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "enhanced": [{
     url: "http://example.com/",
     enhancedImageURI: "data:image/png;base64,helloWORLD",
     title: "title",
     type: "organic",
@@ -134,17 +134,17 @@ function runTests() {
   yield addNewTabPageTab();
   yield customizeNewTabPage("enhanced");
 
   // Suggested link was not enhanced by directory link with same domain
   ({type, enhanced, title, suggested} = getData(0));
   is(type, "affiliate", "suggested link is affiliate");
   is(enhanced, "", "suggested link has no enhanced image");
   is(title, "title2");
-  ok(suggested.indexOf("Suggested for <strong> Web Education </strong> visitors") > -1, "Suggested for 'Web Education'");
+  ok(suggested.indexOf("Suggested for <strong> webdev education </strong> visitors") > -1, "Suggested for 'webdev education'");
 
   // Enhanced history link shows up second
   ({type, enhanced, title, suggested} = getData(1));
   is(type, "enhanced", "pinned history link is enhanced");
   isnot(enhanced, "", "pinned history link has enhanced image");
   is(title, "title");
   is(suggested, "", "There is no suggested explanation");
 
@@ -171,19 +171,45 @@ function runTests() {
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts");
 
 
-    // Test server provided explanation string without category override.
+  // Test server provided explanation string without category override.
   delete suggestedLink.adgroup_name;
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
     "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
-  ok(suggested.indexOf("Suggested for <strong> Web Education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Web Education' enthusiasts");
+  ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts");
+
+
+
+  // Test with xml entities in category name
+  suggestedLink.url = "http://example1.com/3";
+  suggestedLink.adgroup_name = ">angles< & \"quotes\'";
+  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
+    "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
+  yield watchLinksChangeOnce().then(TestRunner.next);
+
+  yield addNewTabPageTab();
+  ({type, enhanced, title, suggested} = getData(0));
+  Cu.reportError("SUGGEST " + suggested);
+  ok(suggested.indexOf("Suggested for <strong> &gt;angles&lt; &amp; \"quotes\' </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'xml entities' enthusiasts");
+
+
+  // Test with xml entities in explanation.
+  suggestedLink.explanation = "Testing junk explanation &<>\"'";
+  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
+    "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
+  yield watchLinksChangeOnce().then(TestRunner.next);
+
+  yield addNewTabPageTab();
+  ({type, enhanced, title, suggested} = getData(0));
+  Cu.reportError("SUGGEST " + suggested);
+  ok(suggested.indexOf("Testing junk explanation &amp;&lt;&gt;\"'") > -1, "Junk test");
 }
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -725,16 +725,17 @@ loop.panel = (function(_, mozL10n) {
         if (!this.isMounted()) {
           return;
         }
 
         var previewImage = metadata.favicon || "";
         var description = metadata.title || metadata.description;
         var url = metadata.url;
         this.setState({
+          checked: false,
           previewImage: previewImage,
           description: description,
           url: url
         });
       }.bind(this));
     },
 
     onCheckboxChange: function(newState) {
@@ -770,17 +771,18 @@ loop.panel = (function(_, mozL10n) {
         context: true,
         hide: !hostname ||
           !this.props.mozLoop.getLoopPref("contextInConversations.enabled")
       });
 
       return (
         React.createElement("div", {className: "new-room-view"}, 
           React.createElement("div", {className: contextClasses}, 
-            React.createElement(Checkbox, {label: mozL10n.get("context_inroom_label"), 
+            React.createElement(Checkbox, {checked: this.state.checked, 
+                      label: mozL10n.get("context_inroom_label"), 
                       onChange: this.onCheckboxChange}), 
             React.createElement(sharedViews.ContextUrlView, {
               allowClick: false, 
               description: this.state.description, 
               showContextTitle: false, 
               thumbnail: this.state.previewImage, 
               url: this.state.url, 
               useDesktopPaths: true})
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -725,16 +725,17 @@ loop.panel = (function(_, mozL10n) {
         if (!this.isMounted()) {
           return;
         }
 
         var previewImage = metadata.favicon || "";
         var description = metadata.title || metadata.description;
         var url = metadata.url;
         this.setState({
+          checked: false,
           previewImage: previewImage,
           description: description,
           url: url
         });
       }.bind(this));
     },
 
     onCheckboxChange: function(newState) {
@@ -770,17 +771,18 @@ loop.panel = (function(_, mozL10n) {
         context: true,
         hide: !hostname ||
           !this.props.mozLoop.getLoopPref("contextInConversations.enabled")
       });
 
       return (
         <div className="new-room-view">
           <div className={contextClasses}>
-            <Checkbox label={mozL10n.get("context_inroom_label")}
+            <Checkbox checked={this.state.checked}
+                      label={mozL10n.get("context_inroom_label")}
                       onChange={this.onCheckboxChange} />
             <sharedViews.ContextUrlView
               allowClick={false}
               description={this.state.description}
               showContextTitle={false}
               thumbnail={this.state.previewImage}
               url={this.state.url}
               useDesktopPaths={true} />
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -659,24 +659,31 @@ loop.roomViews = (function(mozL10n) {
           if (this.state.mediaConnected) {
             // since the remoteVideo hasn't yet been enabled, if the
             // media is connected, then we should be displaying an avatar.
             return false;
           }
 
           return true;
 
+        case ROOM_STATES.READY:
+        case ROOM_STATES.INIT:
+        case ROOM_STATES.JOINING:
         case ROOM_STATES.SESSION_CONNECTED:
         case ROOM_STATES.JOINED:
+        case ROOM_STATES.MEDIA_WAIT:
           // this case is so that we don't show an avatar while waiting for
           // the other party to connect
           return true;
 
+        case ROOM_STATES.CLOSING:
+          return true;
+
         default:
-          console.warn("StandaloneRoomView.shouldRenderRemoteVideo:" +
+          console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" +
             " unexpected roomState: ", this.state.roomState);
           return true;
       }
     },
 
     render: function() {
       if (this.state.roomName) {
         this.setTitle(this.state.roomName);
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -659,24 +659,31 @@ loop.roomViews = (function(mozL10n) {
           if (this.state.mediaConnected) {
             // since the remoteVideo hasn't yet been enabled, if the
             // media is connected, then we should be displaying an avatar.
             return false;
           }
 
           return true;
 
+        case ROOM_STATES.READY:
+        case ROOM_STATES.INIT:
+        case ROOM_STATES.JOINING:
         case ROOM_STATES.SESSION_CONNECTED:
         case ROOM_STATES.JOINED:
+        case ROOM_STATES.MEDIA_WAIT:
           // this case is so that we don't show an avatar while waiting for
           // the other party to connect
           return true;
 
+        case ROOM_STATES.CLOSING:
+          return true;
+
         default:
-          console.warn("StandaloneRoomView.shouldRenderRemoteVideo:" +
+          console.warn("DesktopRoomConversationView.shouldRenderRemoteVideo:" +
             " unexpected roomState: ", this.state.roomState);
           return true;
       }
     },
 
     render: function() {
       if (this.state.roomName) {
         this.setTitle(this.state.roomName);
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -255,18 +255,17 @@
 }
 
 .fx-embedded .no-video {
   background: black none repeat scroll 0% 0%;
   height: 100%;
   width: 100%;
 }
 
-.standalone .local-stream,
-.standalone .remote-inset-stream {
+.standalone .local-stream {
   /* required to have it superimposed to the control toolbar */
   z-index: 1001;
 }
 
 /* Side by side video elements */
 
 .conversation .media.side-by-side .focus-stream {
   width: 50%;
@@ -549,17 +548,17 @@
   /*
    * Expand to fill the available space, since there is no video any
    * intrinsic width. XXX should really change to an <img> for clarity
    */
   height: 100%;
   width: 100%;
 }
 
-.local .avatar {
+.conversation .local .avatar {
   position: absolute;
   z-index: 1;
 }
 
 .remote .avatar {
   /* make visually distinct from local avatar */
   opacity: 0.25;
 }
@@ -642,26 +641,25 @@
  * */
 html, .fx-embedded, #main,
 .video-layout-wrapper,
 .conversation {
   height: 100%;
 }
 
 /* We use 641px rather than 640, as min-width and max-width are inclusive */
-@media screen and (min-width:641px) {
-  .standalone .conversation-toolbar {
+@media screen and (min-width: 641px) {
+  .standalone .conversation .conversation-toolbar {
     position: absolute;
     bottom: 0;
     left: 0;
     right: 0;
   }
 
-  .standalone .local-stream,
-  .standalone .remote-inset-stream {
+  .standalone .conversation .local-stream {
     position: absolute;
     right: 15px;
     bottom: 15px;
     width: 20%;
     height: 20%;
     max-width: 400px;
     max-height: 300px;
   }
@@ -696,21 +694,17 @@ html, .fx-embedded, #main,
   .standalone .media {
     height: 90%;
   }
 
   .standalone .media.nested {
     min-height: 500px;
   }
 
-  .standalone .remote-inset-stream {
-    display: none;
-  }
-
-  .standalone .local-stream {
+  .standalone .conversation .local-stream {
     flex: 1;
     min-width: 120px;
     min-height: 150px;
     width: 100%;
   }
 
   /* Nested video elements */
   .standalone .conversation .media.nested {
@@ -748,39 +742,52 @@ html, .fx-embedded, #main,
   position: relative;
   height: 100%;
 }
 
 .room-conversation-wrapper header {
   background: #000;
   height: 50px;
   text-align: left;
-  width: 75%;
+  margin: 0 10px;
+}
+
+html[dir="rtl"] .room-conversation-wrapper header {
+  text-align: right;
 }
 
 .room-conversation-wrapper header h1 {
   font-size: 1.5em;
   color: #fff;
   line-height: 50px;
-  text-indent: 60px;
+  text-indent: 40px;
   background-image: url("../img/firefox-logo.png");
   background-size: 30px;
-  background-position: 20px;
+  background-position: 0 center;
   background-repeat: no-repeat;
   display: inline-block;
+  margin: 0 10px;
+}
+
+html[dir="rtl"] .room-conversation-wrapper header h1 {
+  background-position: 100% center;
 }
 
 .room-conversation-wrapper header a {
   float: right;
 }
 
+html[dir="rtl"] .room-conversation-wrapper header a {
+  float: left;
+}
+
 .room-conversation-wrapper header .icon-help {
   display: inline-block;
   background-size: contain;
-  margin-top: 20px;
+  margin-top: 15px;
   width: 20px;
   height: 20px;
   background: transparent url("../img/svg/glyph-help-16x16.svg") no-repeat;
 }
 
 .fx-embedded .room-conversation .conversation-toolbar .btn-hangup {
   background-image: url("../img/icons-16x16.svg#leave");
 }
@@ -880,16 +887,17 @@ body[platform="win"] .share-service-drop
 .dropdown-menu-item:hover:active > .icon-add-share-service {
   background-image: url("../img/icons-16x16.svg#add-active");
 }
 
 .context-url-view-wrapper {
   padding-left: 1em;
   padding-right: 1em;
   padding-bottom: 0.5em;
+  margin-bottom: 0.5em;
   background-color: #E8F6FE;
 }
 
 .room-context {
   background: rgba(0,0,0,.6);
   border-top: 2px solid #444;
   border-bottom: 2px solid #444;
   padding: .5rem;
@@ -1075,16 +1083,208 @@ html[dir="rtl"] .room-context-btn-edit {
   right: auto;
   left: 8px;
 }
 
 html[dir="rtl"] .room-context-btn-edit {
   left: 20px;
 }
 
+.media-layout {
+  /* 50px is the header, 3em is the footer. */
+  height: calc(100% - 50px - 3em);
+}
+
+.media-layout > .media-wrapper {
+  display: flex;
+  flex-flow: column wrap;
+  /* 64px for .conversation-toolbar */
+  height: calc(100% - 64px);
+  margin: 0 10px;
+}
+
+.media-wrapper > .focus-stream {
+  /* We want this to be the width, minus 200px which is for the right-side text
+     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: #4E4E4E;
+}
+
+.media-wrapper > .remote {
+  /* Works around an issue with object-fit: cover in Google Chrome - it doesn't
+     currently crop but overlaps the surrounding elements.
+     https://code.google.com/p/chromium/issues/detail?id=400829 */
+  overflow: hidden;
+}
+
+.media-wrapper > .remote > .remote-video {
+  object-fit: cover;
+}
+
+/* Note: we can't use flex for the text-chat-view as this lets it overflow
+   the expected column heights, and we ca't fix its height. */
+.media-wrapper > .text-chat-view {
+  flex: 0 0 auto;
+  /* Text chat is a fixed 200px width for normal displays. */
+  width: 200px;
+  height: 100%;
+}
+
+.media-wrapper.showing-local-streams > .text-chat-view {
+  /* When we're displaying the local streams, then we need to make the text
+     chat view a bit shorter to give room. */
+  height: calc(100% - 150px);
+}
+
+.media-wrapper.showing-local-streams.receiving-screen-share > .text-chat-view {
+  /* When we're displaying the local streams, then we need to make the text
+     chat view a bit shorter to give room. */
+  height: calc(100% - 300px);
+}
+
+.media-wrapper > .text-chat-view > .text-chat-entries {
+  /* 40px is the height of .text-chat-box. */
+  height: calc(100% - 40px);
+}
+
+.media-wrapper > .local {
+  flex: 0 1 auto;
+  width: 200px;
+  height: 150px;
+}
+
+.media-wrapper.receiving-screen-share > .screen {
+  order: 1;
+}
+
+.media-wrapper.receiving-screen-share > .text-chat-view {
+  order: 2;
+}
+
+.media-wrapper.receiving-screen-share > .remote {
+  order: 3;
+  flex: 0 1 auto;
+  width: 200px;
+  height: 150px;
+}
+
+.media-wrapper.receiving-screen-share > .local {
+  order: 4;
+}
+
+@media screen and (max-width:640px) {
+  .media-layout {
+    /* 50px is height of header, 25px is height of footer. */
+    height: calc(100% - 50px - 25px);
+  }
+
+  .media-layout > .media-wrapper {
+    flex-direction: row;
+    margin: 0;
+    width: 100%;
+    /* conversation toolbar is 38px in narrow mode */
+    height: calc(100% - 38px);
+  }
+
+  .media-wrapper > .focus-stream {
+    width: 100%;
+    /* A reasonable height */
+    height: 70%;
+  }
+
+  .media-wrapper.receiving-screen-share > .focus-stream {
+    height: 50%;
+  }
+
+  .media-wrapper > .text-chat-view > .text-chat-entries {
+    /* 40px is the height of .text-chat-box. */
+    height: calc(100% - 40px);
+    width: 100%;
+  }
+
+  .media-wrapper > .local {
+    /* Position over the remote video */
+    position: absolute;
+    /* Make sure its on top */
+    z-index: 1001;
+    margin: 3px;
+    right: 0;
+    /* 29px is (30% of 50px high header) + (height toolbar (38px) +
+       height footer (25px) - height header (50px)) */
+    bottom: calc(30% + 29px);
+    width: 120px;
+    height: 120px;
+  }
+
+  html[dir="rtl"] .media-wrapper > .local {
+    right: auto;
+    left: 0;
+  }
+
+  .media-wrapper > .text-chat-view {
+    order: 3;
+    flex: 1 1 auto;
+    width: 100%;
+  }
+
+  .media-wrapper > .text-chat-view,
+  .media-wrapper.showing-local-streams > .text-chat-view,
+  .media-wrapper.showing-local-streams.receiving-screen-share > .text-chat-view {
+    /* The remaining 30% that the .focus-stream doesn't use. */
+    height: 30%;
+  }
+
+  .media-wrapper.receiving-screen-share > .screen {
+    order: 1;
+  }
+
+  .media-wrapper.receiving-screen-share > .remote {
+    /* Screen shares have remote & local video side-by-side on narrow screens */
+    order: 2;
+    flex: 1 1 auto;
+    height: 20%;
+    /* Ensure no previously specified widths take effect, and we take up no more
+       than half the width. */
+    width: auto;
+    max-width: 50%;
+  }
+
+  .media-wrapper.receiving-screen-share > .remote > .remote-video {
+      /* Reset the object-fit for this. */
+    object-fit: contain;
+  }
+
+  .media-wrapper.receiving-screen-share > .local {
+    /* Screen shares have remote & local video side-by-side on narrow screens */
+    order: 3;
+    flex: 1 1 auto;
+    height: 20%;
+    /* Ensure no previously specified widths take effect, and we take up no more
+       than half the width. */
+    width: auto;
+    max-width: 50%;
+    /* This cancels out the absolute positioning when it's just remote video. */
+    position: relative;
+    bottom: auto;
+    right: auto;
+    margin: 0;
+  }
+
+  .media-wrapper.receiving-screen-share > .text-chat-view {
+    order: 4;
+  }
+}
+
+.standalone > #main > .room-conversation-wrapper > .media-layout > .conversation-toolbar {
+  border: none;
+}
+
 /* Standalone rooms */
 
 .standalone .room-conversation-wrapper {
   position: relative;
   height: 100%;
   background: #000;
 }
 
@@ -1103,16 +1303,21 @@ html[dir="rtl"] .room-context-btn-edit {
   left: 25%;
   z-index: 1000;
   /* `width` here is specified by the design spec. */
   width: 250px;
   color: #fff;
   box-sizing: content-box;
 }
 
+html[dir="rtl"] .standalone .room-conversation-wrapper .room-inner-info-area {
+  right: 25%;
+  left: auto;
+}
+
 .standalone .prompt-media-message {
   padding-top: 136px; /* Fallback for browsers that don't support calc() */
   /* 122px is 2x the intrinsic height of the background-image, and
      1rem puts one line of margin between the background-image and
      supporting text. */
   padding-top: calc(122px + 1rem);
   color: #000;
   background-color: #fff;
@@ -1148,33 +1353,16 @@ html[dir="rtl"] .room-context-btn-edit {
 
 .standalone .room-conversation-wrapper .room-inner-info-area a.btn {
   padding: .5em 3em .3em 3em;
   border-radius: 3px;
   font-weight: normal;
   max-width: 400px;
 }
 
-.standalone-room-info {
-  position: absolute;
-  display: block;
-  top: 0;
-  right: 10px;
-  /* 20px is 10px for left and right margins. */
-  width: calc(25% - 20px);
-  z-index: 2000000;
-  font-size: 1.2em;
-  padding: .4em;
-  height: 100%;
-}
-
-.standalone-room-info > h2 {
-  color: #fff;
-}
-
 .standalone-context-url {
   color: #fff;
   /* Try and keep clear of local video */
   height: 40%;
 }
 
 .standalone-context-url.screen-share-active {
   /* Try and keep clear of remote video when screensharing */
@@ -1238,17 +1426,17 @@ html[dir="rtl"] .room-context-btn-edit {
 
 .fx-embedded .text-chat-entries {
   flex: 1 1 auto;
   max-height: 120px;
   min-height: 60px;
   padding: .7em .5em 0;
 }
 
-.fx-embedded .text-chat-box {
+.text-chat-box {
   flex: 0 0 auto;
   max-height: 40px;
   min-height: 40px;
   width: 100%;
 }
 
 .text-chat-entries {
   overflow: scroll;
@@ -1304,43 +1492,27 @@ html[dir="rtl"] .room-context-btn-edit {
 }
 
 .fx-embedded .text-chat-box > form > input {
   border: 0;
   border-top: 1px solid #999;
 }
 
 @media screen and (max-width:640px) {
-  .standalone-room-info {
-    /* This isn't perfect, we just center the heading for now. Bug 1141493
-       should fix this. */
-    position: absolute;
-    width: 100%;
-    right: 0px;
-
-    /* Override the 100% specified in the .standalone-room-info selector
-       block so that this div doesn't take over the _whole_ screen and
-       transparently occlude UI widgetry (like the Join button), making
-       it unusable. */
-    height: auto;
-  }
-
   .standalone-context-url {
     /* XXX We haven't got UX for standalone yet, so temporarily not displaying
        on narrow window widths. See bug 1153827. */
     display: none;
   }
 
   /* Rooms specific responsive styling */
   .standalone .room-conversation {
     background: #000;
   }
-  .room-conversation-wrapper header {
-    width: 100%;
-  }
+
   .standalone .room-conversation-wrapper .room-inner-info-area {
     right: 0;
     margin: auto;
     width: 100%;
     left: 0;
   }
   .standalone .room-conversation-wrapper .video-layout-wrapper {
     /* 50px: header's height; 25px: footer's height */
@@ -1352,17 +1524,17 @@ html[dir="rtl"] .room-context-btn-edit {
   .standalone .room-conversation .video_wrapper.remote_wrapper.not-joined {
     width: 100%;
   }
 
   .standalone .conversation-toolbar {
     height: 38px;
     padding: 8px;
   }
-  .standalone .focus-stream {
+  .standalone .conversation .focus-stream {
     /* Set at maximum height, minus height of conversation toolbar */
     height: 100%;
   }
 
   .standalone .media.nested {
     /* This forces the remote video stream to fit within wrapper's height */
     min-height: 0px;
   }
@@ -1413,16 +1585,14 @@ html[dir="rtl"] .room-context-btn-edit {
      convention in video conferencing systems. */
   transform: scale(-1, 1);
   transform-origin: 50% 50% 0;
 }
 
 .remote-video {
   width: 100%;
   height: 100%;
-  display: block;
-  position: absolute;
 }
 
 .screen-share-video {
   width: 100%;
   height: 100%;
 }
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -164,16 +164,17 @@ loop.shared.actions = (function() {
      */
     RemotePeerConnected: Action.define("remotePeerConnected", {
     }),
 
     /**
      * Used to notify that the session has a data channel available.
      */
     DataChannelsAvailable: Action.define("dataChannelsAvailable", {
+      available: Boolean
     }),
 
     /**
      * Used to send a message to the other peer.
      */
     SendTextChatMessage: Action.define("sendTextChatMessage", {
       contentType: String,
       message: String
--- 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/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -269,16 +269,20 @@ loop.OTSdkDriver = (function() {
     },
 
     /**
      * Disconnects the sdk session.
      */
     disconnectSession: function() {
       this.endScreenShare();
 
+      this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable({
+        available: false
+      }));
+
       if (this.session) {
         this.session.off("sessionDisconnected streamCreated streamDestroyed connectionCreated connectionDestroyed streamPropertyChanged");
         this.session.disconnect();
         delete this.session;
 
         this._notifyMetricsEvent("Session.connectionDestroyed", "local");
       }
       if (this.publisher) {
@@ -290,16 +294,18 @@ loop.OTSdkDriver = (function() {
       this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now());
 
       // Also, tidy these variables ready for next time.
       delete this._sessionConnected;
       delete this._publisherReady;
       delete this._publishedLocalStream;
       delete this._subscribedRemoteStream;
       delete this._mockPublisherEl;
+      delete this._publisherChannel;
+      delete this._subscriberChannel;
       this.connections = {};
       this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_UNINITIALIZED);
     },
 
     /**
      * Oust all users from an ongoing session. This is typically done when a room
      * owner deletes the room.
      *
@@ -718,17 +724,19 @@ loop.OTSdkDriver = (function() {
     },
 
     /**
      * Checks to see if all channels have been obtained, and if so it dispatches
      * a notification to the stores to inform them.
      */
     _checkDataChannelsAvailable: function() {
       if (this._publisherChannel && this._subscriberChannel) {
-        this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable());
+        this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable({
+          available: true
+        }));
       }
     },
 
     /**
      * Sends a text chat message on the data channel.
      *
      * @param {String} message The message to send.
      */
@@ -816,16 +824,20 @@ loop.OTSdkDriver = (function() {
      *
      * @param {StreamEvent} event The event details:
      * https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
      */
     _onRemoteStreamDestroyed: function(event) {
       this._notifyMetricsEvent("Session.streamDestroyed");
 
       if (event.stream.videoType !== "screen") {
+        this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable({
+          available: false
+        }));
+        delete this._subscriberChannel;
         delete this._mockSubscribeEl;
         return;
       }
 
       // All we need to do is notify the store we're no longer receiving,
       // the sdk should do the rest.
       this.dispatcher.dispatch(new sharedActions.ReceivingScreenShare({
         receiving: false
@@ -834,16 +846,20 @@ loop.OTSdkDriver = (function() {
       delete this._mockScreenShareEl;
     },
 
     /**
      * Handles the event when the remote stream is destroyed.
      */
     _onLocalStreamDestroyed: function() {
       this._notifyMetricsEvent("Publisher.streamDestroyed");
+      this.dispatcher.dispatch(new sharedActions.DataChannelsAvailable({
+        available: false
+      }));
+      delete this._publisherChannel;
       delete this._mockPublisherEl;
     },
 
     /**
      * Called from the sdk when the media access dialog is opened.
      * Prevents the default action, to prevent the SDK's "allow access"
      * dialog from being shown.
      *
--- a/browser/components/loop/content/shared/js/textChatStore.js
+++ b/browser/components/loop/content/shared/js/textChatStore.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var loop = loop || {};
 loop.store = loop.store || {};
 
-loop.store.TextChatStore = (function(mozL10n) {
+loop.store.TextChatStore = (function() {
   "use strict";
 
   var sharedActions = loop.shared.actions;
 
   var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES = {
     RECEIVED: "recv",
     SENT: "sent",
     SPECIAL: "special"
@@ -64,20 +64,25 @@ loop.store.TextChatStore = (function(moz
         messageList: [],
         length: 0
       };
     },
 
     /**
      * Handles information for when data channels are available - enables
      * text chat.
+     *
+     * @param {sharedActions.DataChannelsAvailable} actionData
      */
-    dataChannelsAvailable: function() {
-      this.setStoreState({ textChatEnabled: true });
-      window.dispatchEvent(new CustomEvent("LoopChatEnabled"));
+    dataChannelsAvailable: function(actionData) {
+      this.setStoreState({ textChatEnabled: actionData.available });
+
+      if (actionData.available) {
+        window.dispatchEvent(new CustomEvent("LoopChatEnabled"));
+      }
     },
 
     /**
      * Appends a message to the store, which may be of type 'sent' or 'received'.
      *
      * @param {CHAT_MESSAGE_TYPES} type
      * @param {Object} messageData Data for this message. Options are:
      * - {CHAT_CONTENT_TYPES} contentType
@@ -132,32 +137,34 @@ loop.store.TextChatStore = (function(moz
      * Handles receiving information about the room - specifically the room name
      * so it can be added to the list.
      *
      * @param  {sharedActions.UpdateRoomInfo} actionData
      */
     updateRoomInfo: function(actionData) {
       // XXX When we add special messages to desktop, we'll need to not post
       // multiple changes of room name, only the first. Bug 1171940 should fix this.
-      this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
-        contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
-        message: mozL10n.get("rooms_welcome_title", {conversationName: actionData.roomName})
-      });
+      if (actionData.roomName) {
+        this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
+          contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
+          message: actionData.roomName
+        });
+      }
 
       // Append the context if we have any.
-      if ("urls" in actionData && actionData.urls.length) {
+      if (("urls" in actionData) && actionData.urls && actionData.urls.length) {
         // We only support the first url at the moment.
         var urlData = actionData.urls[0];
 
         this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
           contentType: CHAT_CONTENT_TYPES.CONTEXT,
           message: urlData.description,
           extraData: {
             location: urlData.location,
             thumbnail: urlData.thumbnail
           }
         });
       }
     }
   });
 
   return TextChatStore;
-})(navigator.mozL10n || window.mozL10n);
+})();
--- a/browser/components/loop/content/shared/js/textChatView.js
+++ b/browser/components/loop/content/shared/js/textChatView.js
@@ -34,16 +34,32 @@ loop.shared.views.TextChatView = (functi
       return (
         React.createElement("div", {className: classes}, 
           React.createElement("p", null, this.props.message)
         )
       );
     }
   });
 
+  var TextChatRoomName = React.createClass({displayName: "TextChatRoomName",
+    mixins: [React.addons.PureRenderMixin],
+
+    propTypes: {
+      message: React.PropTypes.string.isRequired
+    },
+
+    render: function() {
+      return (
+        React.createElement("div", {className: "text-chat-entry special room-name"}, 
+          React.createElement("p", null, mozL10n.get("rooms_welcome_title", {conversationName: this.props.message}))
+        )
+      );
+    }
+  });
+
   /**
    * Manages the text entries in the chat entries view. This is split out from
    * TextChatView so that scrolling can be managed more efficiently - this
    * component only updates when the message list is changed.
    */
   var TextChatEntriesView = React.createClass({displayName: "TextChatEntriesView",
     mixins: [React.addons.PureRenderMixin],
 
@@ -76,31 +92,38 @@ loop.shared.views.TextChatView = (functi
         return null;
       }
 
       return (
         React.createElement("div", {className: "text-chat-entries"}, 
           React.createElement("div", {className: "text-chat-scroller"}, 
             
               this.props.messageList.map(function(entry, i) {
-                if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL &&
-                    entry.contentType === CHAT_CONTENT_TYPES.CONTEXT) {
-                  return (
-                    React.createElement("div", {className: "context-url-view-wrapper"}, 
-                      React.createElement(sharedViews.ContextUrlView, {
-                        allowClick: true, 
-                        description: entry.message, 
-                        dispatcher: this.props.dispatcher, 
-                        key: i, 
-                        showContextTitle: true, 
-                        thumbnail: entry.extraData.thumbnail, 
-                        url: entry.extraData.location, 
-                        useDesktopPaths: false})
-                    )
-                  );
+                if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
+                  switch (entry.contentType) {
+                    case CHAT_CONTENT_TYPES.ROOM_NAME:
+                      return React.createElement(TextChatRoomName, {message: entry.message});
+                    case CHAT_CONTENT_TYPES.CONTEXT:
+                      return (
+                        React.createElement("div", {className: "context-url-view-wrapper"}, 
+                          React.createElement(sharedViews.ContextUrlView, {
+                            allowClick: true, 
+                            description: entry.message, 
+                            dispatcher: this.props.dispatcher, 
+                            key: i, 
+                            showContextTitle: true, 
+                            thumbnail: entry.extraData.thumbnail, 
+                            url: entry.extraData.location, 
+                            useDesktopPaths: false})
+                        )
+                      );
+                    default:
+                      console.error("Unsupported contentType", entry.contentType);
+                      return null;
+                  }
                 }
 
                 return (
                   React.createElement(TextChatEntry, {key: i, 
                                  contentType: entry.contentType, 
                                  message: entry.message, 
                                  type: entry.type})
                 );
@@ -153,16 +176,21 @@ loop.shared.views.TextChatView = (functi
     /**
      * Handles submitting of the form - dispatches a send text chat message.
      *
      * @param {Object} event The DOM event.
      */
     handleFormSubmit: function(event) {
       event.preventDefault();
 
+      // Don't send empty messages.
+      if (!this.state.messageDetail) {
+        return;
+      }
+
       this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: this.state.messageDetail
       }));
 
       // Reset the form to empty, ready for the next message.
       this.setState({ messageDetail: "" });
     },
--- a/browser/components/loop/content/shared/js/textChatView.jsx
+++ b/browser/components/loop/content/shared/js/textChatView.jsx
@@ -34,16 +34,32 @@ loop.shared.views.TextChatView = (functi
       return (
         <div className={classes}>
           <p>{this.props.message}</p>
         </div>
       );
     }
   });
 
+  var TextChatRoomName = React.createClass({
+    mixins: [React.addons.PureRenderMixin],
+
+    propTypes: {
+      message: React.PropTypes.string.isRequired
+    },
+
+    render: function() {
+      return (
+        <div className="text-chat-entry special room-name">
+          <p>{mozL10n.get("rooms_welcome_title", {conversationName: this.props.message})}</p>
+        </div>
+      );
+    }
+  });
+
   /**
    * Manages the text entries in the chat entries view. This is split out from
    * TextChatView so that scrolling can be managed more efficiently - this
    * component only updates when the message list is changed.
    */
   var TextChatEntriesView = React.createClass({
     mixins: [React.addons.PureRenderMixin],
 
@@ -76,31 +92,38 @@ loop.shared.views.TextChatView = (functi
         return null;
       }
 
       return (
         <div className="text-chat-entries">
           <div className="text-chat-scroller">
             {
               this.props.messageList.map(function(entry, i) {
-                if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL &&
-                    entry.contentType === CHAT_CONTENT_TYPES.CONTEXT) {
-                  return (
-                    <div className="context-url-view-wrapper">
-                      <sharedViews.ContextUrlView
-                        allowClick={true}
-                        description={entry.message}
-                        dispatcher={this.props.dispatcher}
-                        key={i}
-                        showContextTitle={true}
-                        thumbnail={entry.extraData.thumbnail}
-                        url={entry.extraData.location}
-                        useDesktopPaths={false} />
-                    </div>
-                  );
+                if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
+                  switch (entry.contentType) {
+                    case CHAT_CONTENT_TYPES.ROOM_NAME:
+                      return <TextChatRoomName message={entry.message}/>;
+                    case CHAT_CONTENT_TYPES.CONTEXT:
+                      return (
+                        <div className="context-url-view-wrapper">
+                          <sharedViews.ContextUrlView
+                            allowClick={true}
+                            description={entry.message}
+                            dispatcher={this.props.dispatcher}
+                            key={i}
+                            showContextTitle={true}
+                            thumbnail={entry.extraData.thumbnail}
+                            url={entry.extraData.location}
+                            useDesktopPaths={false} />
+                        </div>
+                      );
+                    default:
+                      console.error("Unsupported contentType", entry.contentType);
+                      return null;
+                  }
                 }
 
                 return (
                   <TextChatEntry key={i}
                                  contentType={entry.contentType}
                                  message={entry.message}
                                  type={entry.type} />
                 );
@@ -153,16 +176,21 @@ loop.shared.views.TextChatView = (functi
     /**
      * Handles submitting of the form - dispatches a send text chat message.
      *
      * @param {Object} event The DOM event.
      */
     handleFormSubmit: function(event) {
       event.preventDefault();
 
+      // Don't send empty messages.
+      if (!this.state.messageDetail) {
+        return;
+      }
+
       this.props.dispatcher.dispatch(new sharedActions.SendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: this.state.messageDetail
       }));
 
       // Reset the form to empty, ready for the next message.
       this.setState({ messageDetail: "" });
     },
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -628,16 +628,25 @@ loop.shared.views = (function(_, l10n) {
         additionalClass: "",
         checked: false,
         disabled: false,
         label: null,
         value: ""
       };
     },
 
+    componentWillReceiveProps: function(nextProps) {
+      // Only change the state if the prop has changed, and if it is also
+      // different from the state.
+      if (this.props.checked !== nextProps.checked &&
+          this.state.checked !== nextProps.checked) {
+        this.setState({ checked: nextProps.checked });
+      }
+    },
+
     getInitialState: function() {
       return {
         checked: this.props.checked,
         value: this.props.checked ? this.props.value : ""
       };
     },
 
     _handleClick: function(event) {
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -628,16 +628,25 @@ loop.shared.views = (function(_, l10n) {
         additionalClass: "",
         checked: false,
         disabled: false,
         label: null,
         value: ""
       };
     },
 
+    componentWillReceiveProps: function(nextProps) {
+      // Only change the state if the prop has changed, and if it is also
+      // different from the state.
+      if (this.props.checked !== nextProps.checked &&
+          this.state.checked !== nextProps.checked) {
+        this.setState({ checked: nextProps.checked });
+      }
+    },
+
     getInitialState: function() {
       return {
         checked: this.props.checked,
         value: this.props.checked ? this.props.value : ""
       };
     },
 
     _handleClick: function(event) {
--- a/browser/components/loop/standalone/content/css/webapp.css
+++ b/browser/components/loop/standalone/content/css/webapp.css
@@ -124,17 +124,17 @@ body,
   background-image: url("../shared/img/mozilla-logo.png");
   background-repeat: no-repeat;
 }
 
 /* Rooms Footer */
 
 .rooms-footer {
   background: #000;
-  margin: 0 20px;
+  margin: 0 10px;
   text-align: left;
   height: 3em;
   position: relative;
 }
 
 html[dir="rtl"] .rooms-footer {
   text-align: right;
 }
@@ -349,44 +349,26 @@ p.standalone-btn-label {
   box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.4);
   border-radius: 3px;
   z-index: 1002; /* ensures the form is always on top of the control bar */
 }
 .standalone .room-conversation-wrapper .ended-conversation .feedback {
   right: 35%;
 }
 
+html[dir="rtl"] .standalone .room-conversation-wrapper .ended-conversation .feedback {
+  right: auto;
+  left: 35%;
+}
+
 .standalone .ended-conversation .local-stream {
   /* Hide  local media stream when feedback form is shown. */
   display: none;
 }
 
-/**
- * The .text-chat-* styles are very temporarily whilst we work on text chat
- * (bug 1108892 and dependencies).
- */
-.text-chat-view {
-  height: 60px;
-  color: black;
-}
-
-.text-chat-entries {
-  /* XXX Should use flex, this is just for the initial implementation. */
-  height: calc(100% - 2em);
-}
-
-.text-chat-box {
-  width: 30%;
-  margin: auto;
-}
-
-.text-chat-box > form > input {
-  width: 100%;
-}
-
 @media screen and (max-width:640px) {
   .standalone .ended-conversation .feedback {
     width: 92%;
     top: 10%;
     left: 5px;
     right: 5px;
   }
 }
--- 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,71 @@ 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();
+      // UX don't want to surface these errors (as they would imply the user
+      // needs to do something to fix them, when if they're having a conversation
+      // they just need to connect). However, we do want there to be somewhere to
+      // find reasonably easily, in case there's issues raised.
+      if (!this.state.roomInfoFailure && nextState.roomInfoFailure) {
+        if (nextState.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
+          console.error(mozL10n.get("room_information_failure_unsupported_browser"));
+        } else {
+          console.error(mozL10n.get("room_information_failure_not_available"));
+        }
       }
     },
 
     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;
@@ -642,104 +362,92 @@ loop.standaloneRoomViews = (function(moz
           console.warn("StandaloneRoomView.shouldRenderRemoteVideo:" +
             " unexpected roomState: ", this.state.roomState);
           return true;
 
       }
     },
 
     render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": this.state.videoMuted
-      });
+      var displayScreenShare = this.state.receivingScreenShare ||
+        this.props.screenSharePosterUrl;
 
       var remoteStreamClasses = React.addons.classSet({
-        "video_inner": true,
         "remote": true,
-        "focus-stream": !this.state.receivingScreenShare,
-        "remote-inset-stream": this.state.receivingScreenShare
+        "focus-stream": !displayScreenShare
       });
 
       var screenShareStreamClasses = React.addons.classSet({
         "screen": true,
-        "focus-stream": this.state.receivingScreenShare,
-        hide: !this.state.receivingScreenShare
+        "focus-stream": displayScreenShare
       });
 
-      // XXX Temporarily showAlways = showRoomName = false for TextChatView
-      // until bug 1168829 is completed.
+      var mediaWrapperClasses = React.addons.classSet({
+        "media-wrapper": true,
+        "receiving-screen-share": displayScreenShare,
+        "showing-local-streams": this.state.localSrcVideoObject ||
+          this.props.localPosterUrl
+      });
+
       return (
         React.createElement("div", {className: "room-conversation-wrapper"}, 
           React.createElement("div", {className: "beta-logo"}), 
-          React.createElement(sharedViews.TextChatView, {
-            dispatcher: this.props.dispatcher, 
-            showAlways: false, 
-            showRoomName: false}), 
           React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}), 
           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, {
+          React.createElement("div", {className: "media-layout"}, 
+            React.createElement("div", {className: mediaWrapperClasses}, 
+              React.createElement("span", {className: "self-view-hidden-message"}, 
+                mozL10n.get("self_view_hidden_message")
+              ), 
+              React.createElement("div", {className: remoteStreamClasses}, 
+                React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
+                  posterUrl: this.props.remotePosterUrl, 
+                  mediaType: "remote", 
+                  srcVideoObject: this.state.remoteSrcVideoObject})
+              ), 
+              React.createElement("div", {className: screenShareStreamClasses}, 
+                React.createElement(sharedViews.MediaView, {displayAvatar: false, 
+                  posterUrl: this.props.screenSharePosterUrl, 
+                  mediaType: "screen-share", 
+                  srcVideoObject: this.state.screenShareVideoObject})
+              ), 
+              React.createElement(sharedViews.TextChatView, {
                 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, 
-                      mediaType: "remote", 
-                      srcVideoObject: this.state.remoteSrcVideoObject})
-                  ), 
-                  React.createElement("div", {className: screenShareStreamClasses}, 
-                    React.createElement(sharedViews.MediaView, {displayAvatar: false, 
-                      posterUrl: this.props.screenSharePosterUrl, 
-                      mediaType: "screen-share", 
-                      srcVideoObject: this.state.screenShareVideoObject})
-                  )
-                ), 
-                React.createElement("div", {className: localStreamClasses}, 
-                  React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
-                    posterUrl: this.props.localPosterUrl, 
-                    mediaType: "local", 
-                    srcVideoObject: this.state.localSrcVideoObject})
-                )
-              ), 
-              React.createElement(sharedViews.ConversationToolbar, {
-                dispatcher: this.props.dispatcher, 
-                video: {enabled: !this.state.videoMuted,
-                        visible: this._roomIsActive()}, 
-                audio: {enabled: !this.state.audioMuted,
-                        visible: this._roomIsActive()}, 
-                publishStream: this.publishStream, 
-                hangup: this.leaveRoom, 
-                hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), 
-                enableHangup: this._roomIsActive()})
-            )
+                showAlways: true, 
+                showRoomName: true}), 
+              React.createElement("div", {className: "local"}, 
+                React.createElement(sharedViews.MediaView, {displayAvatar: this.state.videoMuted, 
+                  posterUrl: this.props.localPosterUrl, 
+                  mediaType: "local", 
+                  srcVideoObject: this.state.localSrcVideoObject})
+              )
+            ), 
+            React.createElement(sharedViews.ConversationToolbar, {
+              dispatcher: this.props.dispatcher, 
+              video: {enabled: !this.state.videoMuted,
+                      visible: this._roomIsActive()}, 
+              audio: {enabled: !this.state.audioMuted,
+                      visible: this._roomIsActive()}, 
+              publishStream: this.publishStream, 
+              hangup: this.leaveRoom, 
+              hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), 
+              enableHangup: this._roomIsActive()})
           ), 
           React.createElement(loop.fxOSMarketplaceViews.FxOSHiddenMarketplaceView, {
             marketplaceSrc: this.state.marketplaceSrc, 
             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,71 @@ 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();
+      // UX don't want to surface these errors (as they would imply the user
+      // needs to do something to fix them, when if they're having a conversation
+      // they just need to connect). However, we do want there to be somewhere to
+      // find reasonably easily, in case there's issues raised.
+      if (!this.state.roomInfoFailure && nextState.roomInfoFailure) {
+        if (nextState.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
+          console.error(mozL10n.get("room_information_failure_unsupported_browser"));
+        } else {
+          console.error(mozL10n.get("room_information_failure_not_available"));
+        }
       }
     },
 
     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;
@@ -642,104 +362,92 @@ loop.standaloneRoomViews = (function(moz
           console.warn("StandaloneRoomView.shouldRenderRemoteVideo:" +
             " unexpected roomState: ", this.state.roomState);
           return true;
 
       }
     },
 
     render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": this.state.videoMuted
-      });
+      var displayScreenShare = this.state.receivingScreenShare ||
+        this.props.screenSharePosterUrl;
 
       var remoteStreamClasses = React.addons.classSet({
-        "video_inner": true,
         "remote": true,
-        "focus-stream": !this.state.receivingScreenShare,
-        "remote-inset-stream": this.state.receivingScreenShare
+        "focus-stream": !displayScreenShare
       });
 
       var screenShareStreamClasses = React.addons.classSet({
         "screen": true,
-        "focus-stream": this.state.receivingScreenShare,
-        hide: !this.state.receivingScreenShare
+        "focus-stream": displayScreenShare
       });
 
-      // XXX Temporarily showAlways = showRoomName = false for TextChatView
-      // until bug 1168829 is completed.
+      var mediaWrapperClasses = React.addons.classSet({
+        "media-wrapper": true,
+        "receiving-screen-share": displayScreenShare,
+        "showing-local-streams": this.state.localSrcVideoObject ||
+          this.props.localPosterUrl
+      });
+
       return (
         <div className="room-conversation-wrapper">
           <div className="beta-logo" />
-          <sharedViews.TextChatView
-            dispatcher={this.props.dispatcher}
-            showAlways={false}
-            showRoomName={false} />
           <StandaloneRoomHeader dispatcher={this.props.dispatcher} />
           <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
+          <div className="media-layout">
+            <div className={mediaWrapperClasses}>
+              <span className="self-view-hidden-message">
+                {mozL10n.get("self_view_hidden_message")}
+              </span>
+              <div className={remoteStreamClasses}>
+                <sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
+                  posterUrl={this.props.remotePosterUrl}
+                  mediaType="remote"
+                  srcVideoObject={this.state.remoteSrcVideoObject} />
+              </div>
+              <div className={screenShareStreamClasses}>
+                <sharedViews.MediaView displayAvatar={false}
+                  posterUrl={this.props.screenSharePosterUrl}
+                  mediaType="screen-share"
+                  srcVideoObject={this.state.screenShareVideoObject} />
+              </div>
+              <sharedViews.TextChatView
                 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}
-                      mediaType="remote"
-                      srcVideoObject={this.state.remoteSrcVideoObject} />
-                  </div>
-                  <div className={screenShareStreamClasses}>
-                    <sharedViews.MediaView displayAvatar={false}
-                      posterUrl={this.props.screenSharePosterUrl}
-                      mediaType="screen-share"
-                      srcVideoObject={this.state.screenShareVideoObject} />
-                  </div>
-                </div>
-                <div className={localStreamClasses}>
-                  <sharedViews.MediaView displayAvatar={this.state.videoMuted}
-                    posterUrl={this.props.localPosterUrl}
-                    mediaType="local"
-                    srcVideoObject={this.state.localSrcVideoObject} />
-                </div>
+                showAlways={true}
+                showRoomName={true} />
+              <div className="local">
+                <sharedViews.MediaView displayAvatar={this.state.videoMuted}
+                  posterUrl={this.props.localPosterUrl}
+                  mediaType="local"
+                  srcVideoObject={this.state.localSrcVideoObject} />
               </div>
-              <sharedViews.ConversationToolbar
-                dispatcher={this.props.dispatcher}
-                video={{enabled: !this.state.videoMuted,
-                        visible: this._roomIsActive()}}
-                audio={{enabled: !this.state.audioMuted,
-                        visible: this._roomIsActive()}}
-                publishStream={this.publishStream}
-                hangup={this.leaveRoom}
-                hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}
-                enableHangup={this._roomIsActive()} />
             </div>
+            <sharedViews.ConversationToolbar
+              dispatcher={this.props.dispatcher}
+              video={{enabled: !this.state.videoMuted,
+                      visible: this._roomIsActive()}}
+              audio={{enabled: !this.state.audioMuted,
+                      visible: this._roomIsActive()}}
+              publishStream={this.publishStream}
+              hangup={this.leaveRoom}
+              hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}
+              enableHangup={this._roomIsActive()} />
           </div>
           <loop.fxOSMarketplaceViews.FxOSHiddenMarketplaceView
             marketplaceSrc={this.state.marketplaceSrc}
             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/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -845,16 +845,35 @@ describe("loop.panel", function() {
 
       // Simulate being visible
       view.onDocumentVisible();
 
       var contextContent = view.getDOMNode().querySelector(".context-content");
       expect(contextContent).to.not.equal(null);
     });
 
+    it("should cancel the checkbox when a new URL is available", function() {
+      fakeMozLoop.getSelectedTabMetadata = function (callback) {
+        callback({
+          url: "https://www.example.com",
+          description: "fake description",
+          previews: [""]
+        });
+      };
+
+      var view = createTestComponent();
+
+      view.setState({ checked: true });
+
+      // Simulate being visible
+      view.onDocumentVisible();
+
+      expect(view.state.checked).eql(false);
+    });
+
     it("should show a default favicon when none is available", function() {
       fakeMozLoop.getSelectedTabMetadata = function (callback) {
         callback({
           url: "https://www.example.com",
           description: "fake description",
           previews: [""]
         });
       };
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -406,16 +406,26 @@ describe("loop.OTSdkDriver", function ()
     it("should disconnect the session", function() {
       driver.session = session;
 
       driver.disconnectSession();
 
       sinon.assert.calledOnce(session.disconnect);
     });
 
+    it("should dispatch a DataChannelsAvailable action with available = false", function() {
+      driver.disconnectSession();
+
+      sinon.assert.calledOnce(dispatcher.dispatch);
+      sinon.assert.calledWithExactly(dispatcher.dispatch,
+        new sharedActions.DataChannelsAvailable({
+          available: false
+        }));
+    });
+
     it("should destroy the publisher", function() {
       driver.publisher = publisher;
 
       driver.disconnectSession();
 
       sinon.assert.calledOnce(publisher.destroy);
     });
 
@@ -1000,26 +1010,36 @@ describe("loop.OTSdkDriver", function ()
     describe("streamDestroyed: publisher/local", function() {
       it("should dispatch a ConnectionStatus action", function() {
         driver._metrics.sendStreams = 1;
         driver._metrics.recvStreams = 1;
         driver._metrics.connections = 2;
 
         publisher.trigger("streamDestroyed");
 
-        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledTwice(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.ConnectionStatus({
             event: "Publisher.streamDestroyed",
             state: "receiving",
             connections: 2,
             recvStreams: 1,
             sendStreams: 0
           }));
       });
+
+      it("should dispatch a DataChannelsAvailable action", function() {
+        publisher.trigger("streamDestroyed");
+
+        sinon.assert.calledTwice(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.DataChannelsAvailable({
+            available: false
+          }));
+      });
     });
 
     describe("streamDestroyed: session/remote", function() {
       var stream;
 
       beforeEach(function() {
         stream = {
           videoType: "screen"
@@ -1049,24 +1069,43 @@ describe("loop.OTSdkDriver", function ()
             event: "Session.streamDestroyed",
             state: "sending",
             connections: 2,
             recvStreams: 0,
             sendStreams: 1
           }));
       });
 
-      it("should not dispatch an action if the videoType is camera", function() {
+      it("should not dispatch a ConnectionStatus action if the videoType is camera", function() {
         stream.videoType = "camera";
 
         session.trigger("streamDestroyed", { stream: stream });
 
         sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
           sinon.match.hasOwn("name", "receivingScreenShare"));
       });
+
+      it("should dispatch a DataChannelsAvailable action for videoType = camera", function() {
+        stream.videoType = "camera";
+
+        session.trigger("streamDestroyed", { stream: stream });
+
+        sinon.assert.calledTwice(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.DataChannelsAvailable({
+            available: false
+          }));
+      });
+
+      it("should not dispatch a DataChannelsAvailable action for videoType = screen", function() {
+        session.trigger("streamDestroyed", { stream: stream });
+
+        sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "dataChannelsAvailable"));
+      });
     });
 
     describe("streamPropertyChanged", function() {
       var stream = {
         connection: { id: "fake" },
         videoType: "screen",
         videoDimensions: {
           width: 320,
@@ -1292,17 +1331,19 @@ describe("loop.OTSdkDriver", function ()
 
         subscriber._.getDataChannel.callsArgWith(2, null, fakeChannel);
         publisher._.getDataChannel.callsArgWith(2, null, fakeChannel);
 
         session.trigger("signal:readyForDataChannel");
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
-          new sharedActions.DataChannelsAvailable());
+          new sharedActions.DataChannelsAvailable({
+            available: true
+          }));
       });
 
       it("should dispatch `ReceivedTextChatMessage` when a text message is received", function() {
         var fakeChannel = _.extend({}, Backbone.Events);
 
         subscriber._.getDataChannel.callsArgWith(2, null, fakeChannel);
 
         session.trigger("signal:readyForDataChannel");
--- a/browser/components/loop/test/shared/textChatStore_test.js
+++ b/browser/components/loop/test/shared/textChatStore_test.js
@@ -32,29 +32,41 @@ describe("loop.store.TextChatStore", fun
     });
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
   describe("#dataChannelsAvailable", function() {
-    it("should set textChatEnabled to true", function() {
-      store.dataChannelsAvailable();
+    it("should set textChatEnabled to the supplied state", function() {
+      store.dataChannelsAvailable(new sharedActions.DataChannelsAvailable({
+        available: true
+      }));
 
       expect(store.getStoreState("textChatEnabled")).eql(true);
     });
 
     it("should dispatch a LoopChatEnabled event", function() {
-      store.dataChannelsAvailable();
+      store.dataChannelsAvailable(new sharedActions.DataChannelsAvailable({
+        available: true
+      }));
 
       sinon.assert.calledOnce(window.dispatchEvent);
       sinon.assert.calledWithExactly(window.dispatchEvent,
         new CustomEvent("LoopChatEnabled"));
     });
+
+    it("should not dispatch a LoopChatEnabled event if available is false", function() {
+      store.dataChannelsAvailable(new sharedActions.DataChannelsAvailable({
+        available: false
+      }));
+
+      sinon.assert.notCalled(window.dispatchEvent);
+    });
   });
 
   describe("#receivedTextChatMessage", function() {
     it("should add the message to the list", function() {
       var message = "Hello!";
 
       store.receivedTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
@@ -128,67 +140,61 @@ describe("loop.store.TextChatStore", fun
       sinon.assert.calledOnce(window.dispatchEvent);
       sinon.assert.calledWithExactly(window.dispatchEvent,
         new CustomEvent("LoopChatMessageAppended"));
     });
   });
 
   describe("#updateRoomInfo", function() {
     it("should add the room name to the list", function() {
-      sandbox.stub(navigator.mozL10n, "get").returns("Let's really share!");
-
       store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
         roomName: "Let's share!",
         roomOwner: "Mark",
         roomUrl: "fake"
       }));
 
       expect(store.getStoreState("messageList")).eql([{
         type: CHAT_MESSAGE_TYPES.SPECIAL,
         contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
-        message: "Let's really share!",
+        message: "Let's share!",
         extraData: undefined
       }]);
     });
 
     it("should add the context to the list", function() {
-      sandbox.stub(navigator.mozL10n, "get").returns("Let's really share!");
-
       store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
         roomName: "Let's share!",
         roomOwner: "Mark",
         roomUrl: "fake",
         urls: [{
           description: "A wonderful event",
           location: "http://wonderful.invalid",
           thumbnail: "fake"
         }]
       }));
 
       expect(store.getStoreState("messageList")).eql([
         {
           type: CHAT_MESSAGE_TYPES.SPECIAL,
           contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
-          message: "Let's really share!",
+          message: "Let's share!",
           extraData: undefined
         }, {
           type: CHAT_MESSAGE_TYPES.SPECIAL,
           contentType: CHAT_CONTENT_TYPES.CONTEXT,
           message: "A wonderful event",
           extraData: {
             location: "http://wonderful.invalid",
             thumbnail: "fake"
           }
         }
       ]);
     });
 
     it("should not dispatch a LoopChatMessageAppended event", function() {
-      sandbox.stub(navigator.mozL10n, "get").returns("Let's really share!");
-
       store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
         roomName: "Let's share!",
         roomOwner: "Mark",
         roomUrl: "fake"
       }));
 
       sinon.assert.notCalled(window.dispatchEvent);
     });
--- a/browser/components/loop/test/shared/textChatView_test.js
+++ b/browser/components/loop/test/shared/textChatView_test.js
@@ -169,10 +169,23 @@ describe("loop.shared.views.TextChatView
 
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWithExactly(dispatcher.dispatch,
         new sharedActions.SendTextChatMessage({
           contentType: CHAT_CONTENT_TYPES.TEXT,
           message: "Hello!"
         }));
     });
+
+    it("should not dispatch SendTextChatMessage when the message is empty", function() {
+      view = mountTestComponent();
+
+      var entryNode = view.getDOMNode().querySelector(".text-chat-box > form > input");
+
+      TestUtils.Simulate.keyDown(entryNode, {
+        key: "Enter",
+        which: 13
+      });
+
+      sinon.assert.notCalled(dispatcher.dispatch);
+    });
   });
 });
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -760,16 +760,36 @@ describe("loop.shared.views", function()
         view = mountTestComponent({
           disabled: true
         });
 
         var node = view.getDOMNode();
         expect(node.classList.contains("disabled")).to.eql(true);
         expect(node.hasAttribute("disabled")).to.eql(true);
       });
+
+      it("should render the checkbox as checked when the prop is set", function() {
+        view = mountTestComponent({
+          checked: true
+        });
+
+        var checkbox = view.getDOMNode().querySelector(".checkbox");
+        expect(checkbox.classList.contains("checked")).eql(true);
+      });
+
+      it("should alter the render state when the props are changed", function() {
+        view = mountTestComponent({
+          checked: true
+        });
+
+        view.setProps({checked: false});
+
+        var checkbox = view.getDOMNode().querySelector(".checkbox");
+        expect(checkbox.classList.contains("checked")).eql(false);
+      });
     });
 
     describe("#_handleClick", function() {
       var onChange;
 
       beforeEach(function() {
         onChange = sinon.stub();
       });
--- 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() {
@@ -822,24 +420,24 @@ describe("loop.standaloneRoomViews", fun
           function() {
             activeRoomStore.setStoreState({used: false});
             expect(view.getDOMNode().querySelector(".faces")).eql(null);
           });
 
       });
 
       describe("Mute", function() {
-        it("should render local media as audio-only if video is muted",
+        it("should render a local avatar if video is muted",
           function() {
             activeRoomStore.setStoreState({
               roomState: ROOM_STATES.SESSION_CONNECTED,
               videoMuted: true
             });
 
-            expect(view.getDOMNode().querySelector(".local-stream-audio"))
+            expect(view.getDOMNode().querySelector(".local .avatar"))
               .not.eql(null);
           });
 
         it("should render a local avatar if the room HAS_PARTICIPANTS and" +
           " .videoMuted is true",
           function() {
             activeRoomStore.setStoreState({
               roomState: ROOM_STATES.HAS_PARTICIPANTS,
--- 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);
@@ -966,16 +967,31 @@
                   React.createElement(StandaloneRoomView, {
                     dispatcher: dispatcher, 
                     activeRoomStore: updatingActiveRoomStore, 
                     roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                     isFirefox: true, 
                     localPosterUrl: "sample-img/video-screen-local.png", 
                     remotePosterUrl: "sample-img/video-screen-remote.png"})
                 )
+            ), 
+
+            React.createElement(FramedExample, {width: 600, height: 480, 
+                           onContentsRendered: updatingSharingRoomStore.forcedUpdate, 
+              summary: "Standalone room convo (has-participants, receivingScreenShare, 600x480)"}, 
+                React.createElement("div", {className: "standalone", cssClass: "standalone"}, 
+                  React.createElement(StandaloneRoomView, {
+                    dispatcher: dispatcher, 
+                    activeRoomStore: updatingSharingRoomStore, 
+                    roomState: ROOM_STATES.HAS_PARTICIPANTS, 
+                    isFirefox: true, 
+                    localPosterUrl: "sample-img/video-screen-local.png", 
+                    remotePosterUrl: "sample-img/video-screen-remote.png", 
+                    screenSharePosterUrl: "sample-img/video-screen-terminal.png"})
+                )
             )
           ), 
 
           React.createElement(Section, {name: "TextChatView (standalone)"}, 
             React.createElement(FramedExample, {width: 200, height: 400, cssClass: "standalone", 
                           summary: "Standalone Text Chat conversation (200 x 400)"}, 
               React.createElement("div", {className: "standalone text-chat-example"}, 
                 React.createElement(TextChatView, {
--- 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);
@@ -967,16 +968,31 @@
                     dispatcher={dispatcher}
                     activeRoomStore={updatingActiveRoomStore}
                     roomState={ROOM_STATES.HAS_PARTICIPANTS}
                     isFirefox={true}
                     localPosterUrl="sample-img/video-screen-local.png"
                     remotePosterUrl="sample-img/video-screen-remote.png" />
                 </div>
             </FramedExample>
+
+            <FramedExample width={600} height={480}
+                           onContentsRendered={updatingSharingRoomStore.forcedUpdate}
+              summary="Standalone room convo (has-participants, receivingScreenShare, 600x480)">
+                <div className="standalone" cssClass="standalone">
+                  <StandaloneRoomView
+                    dispatcher={dispatcher}
+                    activeRoomStore={updatingSharingRoomStore}
+                    roomState={ROOM_STATES.HAS_PARTICIPANTS}
+                    isFirefox={true}
+                    localPosterUrl="sample-img/video-screen-local.png"
+                    remotePosterUrl="sample-img/video-screen-remote.png"
+                    screenSharePosterUrl="sample-img/video-screen-terminal.png" />
+                </div>
+            </FramedExample>
           </Section>
 
           <Section name="TextChatView (standalone)">
             <FramedExample width={200} height={400} cssClass="standalone"
                           summary="Standalone Text Chat conversation (200 x 400)">
               <div className="standalone text-chat-example">
                 <TextChatView
                   dispatcher={dispatcher}
--- a/browser/components/sessionstore/test/browser_595601-restore_hidden.js
+++ b/browser/components/sessionstore/test/browser_595601-restore_hidden.js
@@ -9,16 +9,17 @@ let state = {windows:[{tabs:[
   {entries:[{url:"http://example.com#5"}], hidden: true},
   {entries:[{url:"http://example.com#6"}], hidden: true},
   {entries:[{url:"http://example.com#7"}], hidden: true},
   {entries:[{url:"http://example.com#8"}], hidden: true}
 ]}]};
 
 function test() {
   waitForExplicitFinish();
+  requestLongerTimeout(2);
 
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs");
   });
 
   // First stage: restoreHiddenTabs = true
   // Second stage: restoreHiddenTabs = false
   test_loadTabs(true, function () {
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -66,16 +66,17 @@ support-files =
 [browser_perf-front-01.js]
 [browser_perf-front-02.js]
 [browser_perf-highlighted.js]
 [browser_perf-jit-view-01.js]
 [browser_perf-jit-view-02.js]
 [browser_perf-loading-01.js]
 [browser_perf-loading-02.js]
 [browser_perf-marker-details-01.js]
+skip-if = os == 'linux' # Bug 1172120
 [browser_perf-options-01.js]
 [browser_perf-options-02.js]
 [browser_perf-options-03.js]
 [browser_perf-options-invert-call-tree-01.js]
 [browser_perf-options-invert-call-tree-02.js]
 [browser_perf-options-invert-flame-graph-01.js]
 [browser_perf-options-invert-flame-graph-02.js]
 [browser_perf-options-flatten-tree-recursion-01.js]
--- a/browser/devtools/styleinspector/ruleview.css
+++ b/browser/devtools/styleinspector/ruleview.css
@@ -26,31 +26,32 @@ body {
 
 .devtools-toolbar {
   width: 100%;
   display: flex;
 }
 
 #pseudo-class-panel {
   position: relative;
-  top: -1px;
+  margin-top: -1px;
+  margin-bottom: -1px;
   overflow-y: hidden;
   max-height: 24px;
-  justify-content: space-around;
   transition-property: max-height;
   transition-duration: 150ms;
   transition-timing-function: ease;
 }
 
 #pseudo-class-panel[hidden] {
   max-height: 0px;
 }
 
 #pseudo-class-panel > label {
   -moz-user-select: none;
+  flex-grow: 1;
 }
 
 .ruleview {
   overflow: auto;
   -moz-user-select: text;
 }
 
 .ruleview-code {
--- a/browser/devtools/styleinspector/test/browser_ruleview_add-rule_01.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_add-rule_01.js
@@ -11,25 +11,27 @@ let PAGE_CONTENT = [
   '<style type="text/css">',
   '  .testclass {',
   '    text-align: center;',
   '  }',
   '</style>',
   '<div id="testid" class="testclass">Styled Node</div>',
   '<span class="testclass2">This is a span</span>',
   '<span class="class1 class2">Multiple classes</span>',
+  '<span class="class3      class4">Multiple classes</span>',
   '<p>Empty<p>',
   '<h1 class="asd@@@@a!!!!:::@asd">Invalid characters in class</h1>',
   '<h2 id="asd@@@a!!2a">Invalid characters in id</h2>'
 ].join("\n");
 
 const TEST_DATA = [
   { node: "#testid", expected: "#testid" },
   { node: ".testclass2", expected: ".testclass2" },
   { node: ".class1.class2", expected: ".class1.class2" },
+  { node: ".class3.class4", expected: ".class3.class4" },
   { node: "p", expected: "p" },
   { node: "h1", expected: ".asd\\@\\@\\@\\@a\\!\\!\\!\\!\\:\\:\\:\\@asd" },
   { node: "h2", expected: "#asd\\@\\@\\@a\\!\\!2a" }
 ];
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(PAGE_CONTENT));
 
--- a/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_02.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_02.js
@@ -17,17 +17,22 @@ let PAGE_CONTENT = [
   '  }',
   '</style>',
   '<div id="testid">Styled Node</div>',
   '<span class="testclass">This is a span</span>',
   '<div class="testclass2">A</div>',
   '<div id="testid3">B</div>'
 ].join("\n");
 
+const PSEUDO_PREF = "devtools.inspector.show_pseudo_elements";
+
 add_task(function*() {
+  // Expand the pseudo-elements section by default.
+  Services.prefs.setBoolPref(PSEUDO_PREF, true);
+
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(PAGE_CONTENT));
   let {inspector, view} = yield openRuleView();
 
   info("Selecting the test element");
   yield selectNode(".testclass", inspector);
   yield testEditSelector(view, "div:nth-child(1)");
 
   info("Selecting the modified element");
@@ -36,16 +41,19 @@ add_task(function*() {
 
   info("Selecting the test element");
   yield selectNode("#testid3", inspector);
   yield testEditSelector(view, ".testclass2::first-letter");
 
   info("Selecting the modified element");
   yield selectNode(".testclass2", inspector);
   yield checkModifiedElement(view, ".testclass2::first-letter");
+
+  // Reset the pseudo-elements section pref to its default value.
+  Services.prefs.clearUserPref(PSEUDO_PREF);
 });
 
 function* testEditSelector(view, name) {
   info("Test editing existing selector fields");
 
   let idRuleEditor = getRuleViewRuleEditor(view, 1) ||
     getRuleViewRuleEditor(view, 1, 0);
 
--- a/browser/devtools/styleinspector/test/browser_ruleview_pseudo-element_01.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_pseudo-element_01.js
@@ -2,134 +2,139 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that pseudoelements are displayed correctly in the rule view
 
 const TEST_URI = TEST_URL_ROOT + "doc_pseudoelement.html";
+const PSEUDO_PREF = "devtools.inspector.show_pseudo_elements";
 
 add_task(function*() {
+  Services.prefs.setBoolPref(PSEUDO_PREF, true);
+
   yield addTab(TEST_URI);
-  let {toolbox, inspector, view} = yield openRuleView();
+  let {inspector, view} = yield openRuleView();
 
   yield testTopLeft(inspector, view);
   yield testTopRight(inspector, view);
   yield testBottomRight(inspector, view);
   yield testBottomLeft(inspector, view);
   yield testParagraph(inspector, view);
   yield testBody(inspector, view);
+
+  Services.prefs.clearUserPref(PSEUDO_PREF);
 });
 
 function* testTopLeft(inspector, view) {
   let selector = "#topleft";
-  let {
-    rules,
-    element,
-    elementStyle
-  } = yield assertPseudoElementRulesNumbers(selector, inspector, view, {
+  let {rules} = yield assertPseudoElementRulesNumbers(selector, inspector, view, {
     elementRulesNb: 4,
     firstLineRulesNb: 2,
     firstLetterRulesNb: 1,
     selectionRulesNb: 0
   });
 
   let gutters = assertGutters(view);
 
-  // Make sure that clicking on the twisty hides pseudo elements
+  info("Make sure that clicking on the twisty hides pseudo elements");
   let expander = gutters[0].querySelector(".ruleview-expander");
-  ok (view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are expanded");
+  ok(view.element.firstChild.classList.contains("show-expandable-container"),
+     "Pseudo Elements are expanded");
+
   expander.click();
-  ok (!view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are collapsed by twisty");
+  ok(!view.element.firstChild.classList.contains("show-expandable-container"),
+     "Pseudo Elements are collapsed by twisty");
+
   expander.click();
-  ok (view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are expanded again");
+  ok(view.element.firstChild.classList.contains("show-expandable-container"),
+     "Pseudo Elements are expanded again");
 
-  // Make sure that dblclicking on the header container also toggles the pseudo elements
-  EventUtils.synthesizeMouseAtCenter(gutters[0], {clickCount: 2}, inspector.sidebar.getWindowForTab("ruleview"));
-  ok (!view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are collapsed by dblclicking");
-
-  let defaultView = element.ownerDocument.defaultView;
+  info("Make sure that dblclicking on the header container also toggles " +
+       "the pseudo elements");
+  EventUtils.synthesizeMouseAtCenter(gutters[0], {clickCount: 2},
+                                     view.doc.defaultView);
+  ok(!view.element.firstChild.classList.contains("show-expandable-container"),
+     "Pseudo Elements are collapsed by dblclicking");
 
   let elementRule = rules.elementRules[0];
   let elementRuleView = getRuleViewRuleEditor(view, 3);
 
   let elementFirstLineRule = rules.firstLineRules[0];
-  let elementFirstLineRuleView = [].filter.call(view.element.children[1].children, (e) => {
+  let elementFirstLineRuleView = [...view.element.children[1].children].filter(e => {
     return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule;
   })[0]._ruleEditor;
 
-  is
-  (
-    convertTextPropsToString(elementFirstLineRule.textProps),
-    "color: orange",
-    "TopLeft firstLine properties are correct"
-  );
+  is(convertTextPropsToString(elementFirstLineRule.textProps),
+     "color: orange",
+     "TopLeft firstLine properties are correct");
 
   let firstProp = elementFirstLineRuleView.addProperty("background-color", "rgb(0, 255, 0)", "");
   let secondProp = elementFirstLineRuleView.addProperty("font-style", "italic", "");
 
-  is (firstProp, elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 2],
-      "First added property is on back of array");
-  is (secondProp, elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 1],
-      "Second added property is on back of array");
+  is(firstProp,
+     elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 2],
+     "First added property is on back of array");
+  is(secondProp,
+     elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 1],
+     "Second added property is on back of array");
 
   yield elementFirstLineRule._applyingModifications;
 
   is((yield getComputedStyleProperty(selector, ":first-line", "background-color")),
-    "rgb(0, 255, 0)", "Added property should have been used.");
+     "rgb(0, 255, 0)", "Added property should have been used.");
   is((yield getComputedStyleProperty(selector, ":first-line", "font-style")),
-    "italic", "Added property should have been used.");
+     "italic", "Added property should have been used.");
   is((yield getComputedStyleProperty(selector, null, "text-decoration")),
-    "none", "Added property should not apply to element");
+     "none", "Added property should not apply to element");
 
   firstProp.setEnabled(false);
   yield elementFirstLineRule._applyingModifications;
 
   is((yield getComputedStyleProperty(selector, ":first-line", "background-color")),
-    "rgb(255, 0, 0)", "Disabled property should now have been used.");
+     "rgb(255, 0, 0)", "Disabled property should now have been used.");
   is((yield getComputedStyleProperty(selector, null, "background-color")),
-    "rgb(221, 221, 221)", "Added property should not apply to element");
+     "rgb(221, 221, 221)", "Added property should not apply to element");
 
   firstProp.setEnabled(true);
   yield elementFirstLineRule._applyingModifications;
 
   is((yield getComputedStyleProperty(selector, ":first-line", "background-color")),
-    "rgb(0, 255, 0)", "Added property should have been used.");
+     "rgb(0, 255, 0)", "Added property should have been used.");
   is((yield getComputedStyleProperty(selector, null, "text-decoration")),
-    "none", "Added property should not apply to element");
+     "none", "Added property should not apply to element");
 
   firstProp = elementRuleView.addProperty("background-color", "rgb(0, 0, 255)", "");
   yield elementRule._applyingModifications;
 
   is((yield getComputedStyleProperty(selector, null, "background-color")),
-    "rgb(0, 0, 255)", "Added property should have been used.");
+     "rgb(0, 0, 255)", "Added property should have been used.");
   is((yield getComputedStyleProperty(selector, ":first-line", "background-color")),
-    "rgb(0, 255, 0)", "Added prop does not apply to pseudo");
+     "rgb(0, 255, 0)", "Added prop does not apply to pseudo");
 }
 
 function* testTopRight(inspector, view) {
-  let {
-    rules,
-    element,
-    elementStyle
-  } = yield assertPseudoElementRulesNumbers("#topright", inspector, view, {
+  yield assertPseudoElementRulesNumbers("#topright", inspector, view, {
     elementRulesNb: 4,
     firstLineRulesNb: 1,
     firstLetterRulesNb: 1,
     selectionRulesNb: 0
   });
 
   let gutters = assertGutters(view);
 
   let expander = gutters[0].querySelector(".ruleview-expander");
-  ok (!view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements remain collapsed after switching element");
+  ok(!view.element.firstChild.classList.contains("show-expandable-container"),
+     "Pseudo Elements remain collapsed after switching element");
+
   expander.scrollIntoView();
   expander.click();
-  ok (view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are shown again after clicking twisty");
+  ok(view.element.firstChild.classList.contains("show-expandable-container"),
+     "Pseudo Elements are shown again after clicking twisty");
 }
 
 function* testBottomRight(inspector, view) {
   yield assertPseudoElementRulesNumbers("#bottomright", inspector, view, {
     elementRulesNb: 4,
     firstLineRulesNb: 1,
     firstLetterRulesNb: 1,
     selectionRulesNb: 0
@@ -141,71 +146,46 @@ function* testBottomLeft(inspector, view
     elementRulesNb: 4,
     firstLineRulesNb: 1,
     firstLetterRulesNb: 1,
     selectionRulesNb: 0
   });
 }
 
 function* testParagraph(inspector, view) {
-  let {
-    rules,
-    element,
-    elementStyle
-  } = yield assertPseudoElementRulesNumbers("#bottomleft p", inspector, view, {
+  let {rules} = yield assertPseudoElementRulesNumbers("#bottomleft p", inspector, view, {
     elementRulesNb: 3,
     firstLineRulesNb: 1,
     firstLetterRulesNb: 1,
     selectionRulesNb: 1
   });
 
-  let gutters = assertGutters(view);
+  assertGutters(view);
 
   let elementFirstLineRule = rules.firstLineRules[0];
-  let elementFirstLineRuleView = [].filter.call(view.element.children[1].children, (e) => {
-    return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule;
-  })[0]._ruleEditor;
-
-  is
-  (
-    convertTextPropsToString(elementFirstLineRule.textProps),
-    "background: blue none repeat scroll 0% 0%",
-    "Paragraph first-line properties are correct"
-  );
+  is(convertTextPropsToString(elementFirstLineRule.textProps),
+     "background: blue none repeat scroll 0% 0%",
+     "Paragraph first-line properties are correct");
 
   let elementFirstLetterRule = rules.firstLetterRules[0];
-  let elementFirstLetterRuleView = [].filter.call(view.element.children[1].children, (e) => {
-    return e._ruleEditor && e._ruleEditor.rule === elementFirstLetterRule;
-  })[0]._ruleEditor;
-
-  is
-  (
-    convertTextPropsToString(elementFirstLetterRule.textProps),
-    "color: red; font-size: 130%",
-    "Paragraph first-letter properties are correct"
-  );
+  is(convertTextPropsToString(elementFirstLetterRule.textProps),
+     "color: red; font-size: 130%",
+     "Paragraph first-letter properties are correct");
 
   let elementSelectionRule = rules.selectionRules[0];
-  let elementSelectionRuleView = [].filter.call(view.element.children[1].children, (e) => {
-    return e._ruleEditor && e._ruleEditor.rule === elementSelectionRule;
-  })[0]._ruleEditor;
-
-  is
-  (
-    convertTextPropsToString(elementSelectionRule.textProps),
-    "color: white; background: black none repeat scroll 0% 0%",
-    "Paragraph first-letter properties are correct"
-  );
+  is(convertTextPropsToString(elementSelectionRule.textProps),
+     "color: white; background: black none repeat scroll 0% 0%",
+     "Paragraph first-letter properties are correct");
 }
 
 function* testBody(inspector, view) {
-  let {element, elementStyle} = yield testNode("body", inspector, view);
+  yield testNode("body", inspector, view);
 
-  let gutters = view.element.querySelectorAll(".theme-gutter");
-  is (gutters.length, 0, "There are no gutter headings");
+  let gutters = getGutters(view);
+  is(gutters.length, 0, "There are no gutter headings");
 }
 
 function convertTextPropsToString(textProps) {
   return textProps.map(t => t.name + ": " + t.value).join("; ");
 }
 
 function* testNode(selector, inspector, view) {
   let element = getNode(selector);
@@ -219,29 +199,38 @@ function* assertPseudoElementRulesNumber
 
   let rules = {
     elementRules: elementStyle.rules.filter(rule => !rule.pseudoElement),
     firstLineRules: elementStyle.rules.filter(rule => rule.pseudoElement === ":first-line"),
     firstLetterRules: elementStyle.rules.filter(rule => rule.pseudoElement === ":first-letter"),
     selectionRules: elementStyle.rules.filter(rule => rule.pseudoElement === ":-moz-selection")
   };
 
-  is(rules.elementRules.length, ruleNbs.elementRulesNb, selector +
-    " has the correct number of non pseudo element rules");
-  is(rules.firstLineRules.length, ruleNbs.firstLineRulesNb, selector +
-    " has the correct number of :first-line rules");
-  is(rules.firstLetterRules.length, ruleNbs.firstLetterRulesNb, selector +
-    " has the correct number of :first-letter rules");
-  is(rules.selectionRules.length, ruleNbs.selectionRulesNb, selector +
-    " has the correct number of :selection rules");
+  is(rules.elementRules.length, ruleNbs.elementRulesNb,
+     selector + " has the correct number of non pseudo element rules");
+  is(rules.firstLineRules.length, ruleNbs.firstLineRulesNb,
+     selector + " has the correct number of :first-line rules");
+  is(rules.firstLetterRules.length, ruleNbs.firstLetterRulesNb,
+     selector + " has the correct number of :first-letter rules");
+  is(rules.selectionRules.length, ruleNbs.selectionRulesNb,
+     selector + " has the correct number of :selection rules");
 
-  return {rules: rules, element: element, elementStyle: elementStyle};
+  return {rules, element, elementStyle};
+}
+
+function getGutters(view) {
+  return view.element.querySelectorAll(".theme-gutter");
 }
 
 function assertGutters(view) {
-  let gutters = view.element.querySelectorAll(".theme-gutter");
-  is (gutters.length, 3, "There are 3 gutter headings");
-  is (gutters[0].textContent, "Pseudo-elements", "Gutter heading is correct");
-  is (gutters[1].textContent, "This Element", "Gutter heading is correct");
-  is (gutters[2].textContent, "Inherited from body", "Gutter heading is correct");
+  let gutters = getGutters(view);
+
+  is(gutters.length, 3,
+     "There are 3 gutter headings");
+  is(gutters[0].textContent, "Pseudo-elements",
+     "Gutter heading is correct");
+  is(gutters[1].textContent, "This Element",
+     "Gutter heading is correct");
+  is(gutters[2].textContent, "Inherited from body",
+     "Gutter heading is correct");
 
   return gutters;
 }
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.1.165
+Current extension version is: 1.1.215
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -156,17 +156,18 @@ function makeContentReadable(obj, window
   return Cu.cloneInto(obj, window);
 }
 
 function createNewChannel(uri, node, principal) {
   return NetUtil.newChannel({
     uri: uri,
     loadingNode: node,
     loadingPrincipal: principal,
-    contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER});
+    contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+  });
 }
 
 function asyncFetchChannel(channel, callback) {
   return NetUtil.asyncFetch(channel, callback);
 }
 
 // PDF data storage
 function PdfDataListener(length) {
--- a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -178,17 +178,18 @@ let PdfjsChromeUtils = {
 
   /*
    * Internal
    */
 
   _findbarFromMessage: function(aMsg) {
     let browser = aMsg.target;
     let tabbrowser = browser.getTabBrowser();
-    let tab = tabbrowser.getTabForBrowser(browser);
+    let tab;
+    tab = tabbrowser.getTabForBrowser(browser);
     return tabbrowser.getFindBar(tab);
   },
 
   _updateControlState: function (aMsg) {
     let data = aMsg.data;
     this._findbarFromMessage(aMsg)
         .updateControlState(data.result, data.findPrevious);
   },
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.1.165';
-PDFJS.build = '39d2103';
+PDFJS.version = '1.1.215';
+PDFJS.build = 'c9a7498';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -4203,17 +4203,17 @@ var CanvasGraphics = (function CanvasGra
       this.current.font = fontObj;
       this.current.fontSize = size;
 
       if (fontObj.isType3Font) {
         return; // we don't need ctx.font for Type3 fonts
       }
 
       var name = fontObj.loadedName || 'sans-serif';
-      var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
+      var bold = fontObj.black ? (fontObj.bold ? '900' : 'bold') :
                                  (fontObj.bold ? 'bold' : 'normal');
 
       var italic = fontObj.italic ? 'italic' : 'normal';
       var typeface = '"' + name + '", ' + fontObj.fallbackName;
 
       // Some font backends cannot handle fonts below certain size.
       // Keeping the font at minimal size and using the fontSizeScale to change
       // the current transformation matrix before the fillText/strokeText.
@@ -4463,16 +4463,17 @@ var CanvasGraphics = (function CanvasGra
       var glyphsLength = glyphs.length;
       var isTextInvisible =
         current.textRenderingMode === TextRenderingMode.INVISIBLE;
       var i, glyph, width;
 
       if (isTextInvisible || fontSize === 0) {
         return;
       }
+      this.cachedGetSinglePixelWidth = null;
 
       ctx.save();
       ctx.transform.apply(ctx, current.textMatrix);
       ctx.translate(current.x, current.y);
 
       ctx.scale(textHScale, fontDirection);
 
       for (i = 0; i < glyphsLength; ++i) {
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.1.165';
-PDFJS.build = '39d2103';
+PDFJS.version = '1.1.215';
+PDFJS.build = 'c9a7498';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -2057,42 +2057,54 @@ var Page = (function PageClosure() {
     this.resourcesPromise = null;
   }
 
   Page.prototype = {
     getPageProp: function Page_getPageProp(key) {
       return this.pageDict.get(key);
     },
 
-    getInheritedPageProp: function Page_inheritPageProp(key) {
-      var dict = this.pageDict;
-      var value = dict.get(key);
-      while (value === undefined) {
+    getInheritedPageProp: function Page_getInheritedPageProp(key) {
+      var dict = this.pageDict, valueArray = null, loopCount = 0;
+      var MAX_LOOP_COUNT = 100;
+      // Always walk up the entire parent chain, to be able to find
+      // e.g. \Resources placed on multiple levels of the tree.
+      while (dict) {
+        var value = dict.get(key);
+        if (value) {
+          if (!valueArray) {
+            valueArray = [];
+          }
+          valueArray.push(value);
+        }
+        if (++loopCount > MAX_LOOP_COUNT) {
+          warn('Page_getInheritedPageProp: maximum loop count exceeded.');
+          break;
+        }
         dict = dict.get('Parent');
-        if (!dict) {
-          break;
-        }
-        value = dict.get(key);
-      }
-      return value;
+      }
+      if (!valueArray) {
+        return Dict.empty;
+      }
+      if (valueArray.length === 1 || !isDict(valueArray[0]) ||
+          loopCount > MAX_LOOP_COUNT) {
+        return valueArray[0];
+      }
+      return Dict.merge(this.xref, valueArray);
     },
 
     get content() {
       return this.getPageProp('Contents');
     },
 
     get resources() {
-      var value = this.getInheritedPageProp('Resources');
       // For robustness: The spec states that a \Resources entry has to be
-      // present, but can be empty. Some document omit it still. In this case
-      // return an empty dictionary:
-      if (value === undefined) {
-        value = Dict.empty;
-      }
-      return shadow(this, 'resources', value);
+      // present, but can be empty. Some document omit it still, in this case
+      // we return an empty dictionary.
+      return shadow(this, 'resources', this.getInheritedPageProp('Resources'));
     },
 
     get mediaBox() {
       var obj = this.getInheritedPageProp('MediaBox');
       // Reset invalid media box to letter size.
       if (!isArray(obj) || obj.length !== 4) {
         obj = LETTER_SIZE_MEDIABOX;
       }
@@ -2355,16 +2367,20 @@ var PDFDocument = (function PDFDocumentC
         Trapped: isName
       });
     }
   };
 
   PDFDocument.prototype = {
     parse: function PDFDocument_parse(recoveryMode) {
       this.setup(recoveryMode);
+      var version = this.catalog.catDict.get('Version');
+      if (isName(version)) {
+        this.pdfFormatVersion = version.name;
+      }
       try {
         // checking if AcroForm is present
         this.acroForm = this.catalog.catDict.get('AcroForm');
         if (this.acroForm) {
           this.xfa = this.acroForm.get('XFA');
           var fields = this.acroForm.get('Fields');
           if ((!fields || !isArray(fields) || fields.length === 0) &&
               !this.xfa) {
@@ -2456,18 +2472,20 @@ var PDFDocument = (function PDFDocumentC
         var MAX_VERSION_LENGTH = 12;
         var version = '', ch;
         while ((ch = stream.getByte()) > 0x20) { // SPACE
           if (version.length >= MAX_VERSION_LENGTH) {
             break;
           }
           version += String.fromCharCode(ch);
         }
-        // removing "%PDF-"-prefix
-        this.pdfFormatVersion = version.substring(5);
+        if (!this.pdfFormatVersion) {
+          // removing "%PDF-"-prefix
+          this.pdfFormatVersion = version.substring(5);
+        }
         return;
       }
       // May not be a PDF file, continue anyway.
     },
     parseStartXRef: function PDFDocument_parseStartXRef() {
       var startXRef = this.startXRef;
       this.xref.setStartXRef(startXRef);
     },
@@ -2734,16 +2752,34 @@ var Dict = (function DictClosure() {
       for (var key in this.map) {
         callback(key, this.get(key));
       }
     }
   };
 
   Dict.empty = new Dict(null);
 
+  Dict.merge = function Dict_merge(xref, dictArray) {
+    var mergedDict = new Dict(xref);
+
+    for (var i = 0, ii = dictArray.length; i < ii; i++) {
+      var dict = dictArray[i];
+      if (!isDict(dict)) {
+        continue;
+      }
+      for (var keyName in dict.map) {
+        if (mergedDict.map[keyName]) {
+          continue;
+        }
+        mergedDict.map[keyName] = dict.map[keyName];
+      }
+    }
+    return mergedDict;
+  };
+
   return Dict;
 })();
 
 var Ref = (function RefClosure() {
   function Ref(num, gen) {
     this.num = num;
     this.gen = gen;
   }
@@ -5208,17 +5244,20 @@ var PDFFunction = (function PDFFunctionC
         var dmax = domain[1];
         if (i < bounds.length) {
           dmax = bounds[i];
         }
 
         var rmin = encode[2 * i];
         var rmax = encode[2 * i + 1];
 
-        tmpBuf[0] = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
+        // Prevent the value from becoming NaN as a result
+        // of division by zero (fixes issue6113.pdf).
+        tmpBuf[0] = dmin === dmax ? rmin :
+                    rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
 
         // call the appropriate function
         fns[i](tmpBuf, 0, dest, destOffset);
       };
     },
 
     constructPostScript: function PDFFunction_constructPostScript(fn, dict,
                                                                   xref) {
@@ -6217,19 +6256,19 @@ var ColorSpace = (function ColorSpaceClo
         case 'CMYK':
           return 'DeviceCmykCS';
         case 'Pattern':
           return ['PatternCS', null];
         default:
           error('unrecognized colorspace ' + mode);
       }
     } else if (isArray(cs)) {
-      mode = cs[0].name;
+      mode = xref.fetchIfRef(cs[0]).name;
       this.mode = mode;
-      var numComps, params;
+      var numComps, params, alt;
 
       switch (mode) {
         case 'DeviceGray':
         case 'G':
           return 'DeviceGrayCS';
         case 'DeviceRGB':
         case 'RGB':
           return 'DeviceRgbCS';
@@ -6241,26 +6280,37 @@ var ColorSpace = (function ColorSpaceClo
           return ['CalGrayCS', params];
         case 'CalRGB':
           params = xref.fetchIfRef(cs[1]).getAll();
           return ['CalRGBCS', params];
         case 'ICCBased':
           var stream = xref.fetchIfRef(cs[1]);
           var dict = stream.dict;
           numComps = dict.get('N');
+          alt = dict.get('Alternate');
+          if (alt) {
+            var altIR = ColorSpace.parseToIR(alt, xref, res);
+            // Parse the /Alternate CS to ensure that the number of components
+            // are correct, and also (indirectly) that it is not a PatternCS.
+            var altCS = ColorSpace.fromIR(altIR);
+            if (altCS.numComps === numComps) {
+              return altIR;
+            }
+            warn('ICCBased color space: Ignoring incorrect /Alternate entry.');
+          }
           if (numComps === 1) {
             return 'DeviceGrayCS';
           } else if (numComps === 3) {
             return 'DeviceRgbCS';
           } else if (numComps === 4) {
             return 'DeviceCmykCS';
           }
           break;
         case 'Pattern':
-          var basePatternCS = cs[1];
+          var basePatternCS = xref.fetchIfRef(cs[1]) || null;
           if (basePatternCS) {
             basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
           }
           return ['PatternCS', basePatternCS];
         case 'Indexed':
         case 'I':
           var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
           var hiVal = cs[2] + 1;
@@ -6273,21 +6323,21 @@ var ColorSpace = (function ColorSpaceClo
         case 'DeviceN':
           var name = cs[1];
           numComps = 1;
           if (isName(name)) {
             numComps = 1;
           } else if (isArray(name)) {
             numComps = name.length;
           }
-          var alt = ColorSpace.parseToIR(cs[2], xref, res);
+          alt = ColorSpace.parseToIR(cs[2], xref, res);
           var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
           return ['AlternateCS', numComps, alt, tintFnIR];
         case 'Lab':
-          params = cs[1].getAll();
+          params = xref.fetchIfRef(cs[1]).getAll();
           return ['LabCS', params];
         default:
           error('unimplemented color space object "' + mode + '"');
       }
     } else {
       error('unrecognized color space object: "' + cs + '"');
     }
     return null;
@@ -16328,22 +16378,24 @@ var Font = (function FontClosure() {
     }
     if (code >= 0xFFF0 && code <= 0xFFFF) { // Specials Unicode block
       return true;
     }
     switch (code) {
       case 0x7F: // Control char
       case 0xA0: // Non breaking space
       case 0xAD: // Soft hyphen
-      case 0x0E33: // Thai character SARA AM
       case 0x2011: // Non breaking hyphen
       case 0x205F: // Medium mathematical space
       case 0x25CC: // Dotted circle (combining mark)
         return true;
     }
+    if ((code & ~0xFF) === 0x0E00) { // Thai/Lao chars (with combining mark)
+      return true;
+    }
     return false;
   }
 
   /**
    * Rebuilds the char code to glyph ID map by trying to replace the char codes
    * with their unicode value. It also moves char codes that are in known
    * problematic locations.
    * @return {Object} Two properties:
@@ -17757,23 +17809,28 @@ var Font = (function FontClosure() {
       // The 'post' table has glyphs names.
       if (tables.post) {
         var valid = readPostScriptTable(tables.post, properties, numGlyphs);
         if (!valid) {
           tables.post = null;
         }
       }
 
-      var charCodeToGlyphId = [], charCode, toUnicode = properties.toUnicode;
-
-      function hasGlyph(glyphId, charCode) {
+      var charCodeToGlyphId = [], charCode;
+      var toUnicode = properties.toUnicode, widths = properties.widths;
+      var isIdentityUnicode = toUnicode instanceof IdentityToUnicodeMap;
+
+      function hasGlyph(glyphId, charCode, widthCode) {
         if (!missingGlyphs[glyphId]) {
           return true;
         }
-        if (charCode >= 0 && toUnicode.has(charCode)) {
+        if (!isIdentityUnicode && charCode >= 0 && toUnicode.has(charCode)) {
+          return true;
+        }
+        if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
           return true;
         }
         return false;
       }
 
       if (properties.type === 'CIDFontType2') {
         var cidToGidMap = properties.cidToGidMap || [];
         var isCidToGidMapEmpty = cidToGidMap.length === 0;
@@ -17783,17 +17840,17 @@ var Font = (function FontClosure() {
           var glyphId = -1;
           if (isCidToGidMapEmpty) {
             glyphId = charCode;
           } else if (cidToGidMap[cid] !== undefined) {
             glyphId = cidToGidMap[cid];
           }
 
           if (glyphId >= 0 && glyphId < numGlyphs &&
-              hasGlyph(glyphId, charCode)) {
+              hasGlyph(glyphId, charCode, cid)) {
             charCodeToGlyphId[charCode] = glyphId;
           }
         });
         if (dupFirstEntry) {
           charCodeToGlyphId[0] = numGlyphs - 1;
         }
       } else {
         // Most of the following logic in this code branch is based on the
@@ -17844,28 +17901,29 @@ var Font = (function FontClosure() {
             } else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
               // TODO: the encoding needs to be updated with mac os table.
               unicodeOrCharCode = Encodings.MacRomanEncoding.indexOf(glyphName);
             }
 
             var found = false;
             for (i = 0; i < cmapMappingsLength; ++i) {
               if (cmapMappings[i].charCode === unicodeOrCharCode &&
-                  hasGlyph(cmapMappings[i].glyphId, unicodeOrCharCode)) {
+                  hasGlyph(cmapMappings[i].glyphId, unicodeOrCharCode, -1)) {
                 charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
                 found = true;
                 break;
               }
             }
             if (!found && properties.glyphNames) {
-              // Try to map using the post table. There are currently no known
-              // pdfs that this fixes.
+              // Try to map using the post table.
               var glyphId = properties.glyphNames.indexOf(glyphName);
-              if (glyphId > 0 && hasGlyph(glyphId, -1)) {
+              if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
                 charCodeToGlyphId[charCode] = glyphId;
+              } else {
+                charCodeToGlyphId[charCode] = 0; // notdef
               }
             }
           }
         } else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
           // Default Unicode semantics, use the charcodes as is.
           for (i = 0; i < cmapMappingsLength; ++i) {
             charCodeToGlyphId[cmapMappings[i].charCode] =
               cmapMappings[i].glyphId;
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -798,33 +798,33 @@ html[dir='rtl'] .dropdownToolbarButton {
   box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset,
               0 0 1px hsla(0,0%,0%,.3) inset,
               0 1px 0 hsla(0,0%,100%,.05);
 }
 
 .dropdownToolbarButton {
   width: 120px;
   max-width: 120px;
-  padding: 3px 2px 2px;
+  padding: 0;
   overflow: hidden;
   background: url(images/toolbarButton-menuArrows.png) no-repeat;
 }
 html[dir='ltr'] .dropdownToolbarButton {
   background-position: 95%;
 }
 html[dir='rtl'] .dropdownToolbarButton {
   background-position: 5%;
 }
 
 .dropdownToolbarButton > select {
   min-width: 140px;
   font-size: 12px;
   color: hsl(0,0%,95%);
   margin: 0;
-  padding: 0;
+  padding: 3px 2px 2px;
   border: none;
   background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */
 }
 
 .dropdownToolbarButton > select > option {
   background: hsl(0,0%,24%);
 }
 
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -13,21 +13,21 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar,
            DownloadManager, getFileName, getPDFFileNameFromURL,
            PDFHistory, Preferences, SidebarView, ViewHistory, Stats,
            PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
-           PasswordPrompt, PDFPresentationMode, HandTool, Promise,
-           PDFDocumentProperties, PDFOutlineView, PDFAttachmentView,
+           PasswordPrompt, PDFPresentationMode, PDFDocumentProperties, HandTool,
+           Promise, PDFLinkService, PDFOutlineView, PDFAttachmentView,
            OverlayManager, PDFFindController, PDFFindBar, getVisibleElements,
            watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState,
-           RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE,
+           parseQueryString, RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE,
            IGNORE_CURRENT_POSITION_ON_ZOOM: true */
 
 'use strict';
 
 var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
 var DEFAULT_SCALE_DELTA = 1.1;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 10.0;
@@ -204,16 +204,31 @@ function watchScroll(viewAreaElement, ca
   };
 
   var rAF = null;
   viewAreaElement.addEventListener('scroll', debounceScroll, true);
   return state;
 }
 
 /**
+ * Helper function to parse query string (e.g. ?param1=value&parm2=...).
+ */
+function parseQueryString(query) {
+  var parts = query.split('&');
+  var params = {};
+  for (var i = 0, ii = parts.length; i < ii; ++i) {
+    var param = parts[i].split('=');
+    var key = param[0].toLowerCase();
+    var value = param.length > 1 ? param[1] : null;
+    params[decodeURIComponent(key)] = decodeURIComponent(value);
+  }
+  return params;
+}
+
+/**
  * Use binary search to find the index of the first item in a given array which
  * passes a given condition. The items are expected to be sorted in the sense
  * that if the condition is true for one item in the array, then it is also true
  * for all following items.
  *
  * @returns {Number} Index of the first array element to pass the test,
  *                   or |items.length| if no such element exists.
  */
@@ -1325,366 +1340,669 @@ var PDFFindController = (function PDFFin
       }
       this.findBar.updateUIState(state, previous);
     }
   };
   return PDFFindController;
 })();
 
 
-var PDFHistory = {
-  initialized: false,
-  initialDestination: null,
-
+/**
+ * Performs navigation functions inside PDF, such as opening specified page,
+ * or destination.
+ * @class
+ * @implements {IPDFLinkService}
+ */
+var PDFLinkService = (function () {
   /**
-   * @param {string} fingerprint
-   * @param {IPDFLinkService} linkService
+   * @constructs PDFLinkService
    */
-  initialize: function pdfHistoryInitialize(fingerprint, linkService) {
-    this.initialized = true;
-    this.reInitialized = false;
-    this.allowHashChange = true;
-    this.historyUnlocked = true;
-    this.isViewerInPresentationMode = false;
-
-    this.previousHash = window.location.hash.substring(1);
-    this.currentBookmark = '';
-    this.currentPage = 0;
-    this.updatePreviousBookmark = false;
-    this.previousBookmark = '';
-    this.previousPage = 0;
-    this.nextHashParam = '';
-
-    this.fingerprint = fingerprint;
-    this.linkService = linkService;
-    this.currentUid = this.uid = 0;
-    this.current = {};
-
-    var state = window.history.state;
-    if (this._isStateObjectDefined(state)) {
-      // This corresponds to navigating back to the document
-      // from another page in the browser history.
+  function PDFLinkService() {
+    this.baseUrl = null;
+    this.pdfDocument = null;
+    this.pdfViewer = null;
+    this.pdfHistory = null;
+
+    this._pagesRefCache = null;
+  }
+
+  PDFLinkService.prototype = {
+    setDocument: function PDFLinkService_setDocument(pdfDocument, baseUrl) {
+      this.baseUrl = baseUrl;
+      this.pdfDocument = pdfDocument;
+      this._pagesRefCache = Object.create(null);
+    },
+
+    setViewer: function PDFLinkService_setViewer(pdfViewer) {
+      this.pdfViewer = pdfViewer;
+    },
+
+    setHistory: function PDFLinkService_setHistory(pdfHistory) {
+      this.pdfHistory = pdfHistory;
+    },
+
+    /**
+     * @returns {number}
+     */
+    get pagesCount() {
+      return this.pdfDocument.numPages;
+    },
+
+    /**
+     * @returns {number}
+     */
+    get page() {
+      return this.pdfViewer.currentPageNumber;
+    },
+
+    /**
+     * @param {number} value
+     */
+    set page(value) {
+      this.pdfViewer.currentPageNumber = value;
+    },
+
+    /**
+     * @param dest - The PDF destination object.
+     */
+    navigateTo: function PDFLinkService_navigateTo(dest) {
+      var destString = '';
+      var self = this;
+
+      var goToDestination = function(destRef) {
+        // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
+        var pageNumber = destRef instanceof Object ?
+          self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
+          (destRef + 1);
+        if (pageNumber) {
+          if (pageNumber > self.pagesCount) {
+            pageNumber = self.pagesCount;
+          }
+          self.pdfViewer.scrollPageIntoView(pageNumber, dest);
+
+          if (self.pdfHistory) {
+            // Update the browsing history.
+            self.pdfHistory.push({
+              dest: dest,
+              hash: destString,
+              page: pageNumber
+            });
+          }
+        } else {
+          self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
+            var pageNum = pageIndex + 1;
+            var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
+            self._pagesRefCache[cacheKey] = pageNum;
+            goToDestination(destRef);
+          });
+        }
+      };
+
+      var destinationPromise;
+      if (typeof dest === 'string') {
+        destString = dest;
+        destinationPromise = this.pdfDocument.getDestination(dest);
+      } else {
+        destinationPromise = Promise.resolve(dest);
+      }
+      destinationPromise.then(function(destination) {
+        dest = destination;
+        if (!(destination instanceof Array)) {
+          return; // invalid destination
+        }
+        goToDestination(destination[0]);
+      });
+    },
+
+    /**
+     * @param dest - The PDF destination object.
+     * @returns {string} The hyperlink to the PDF object.
+     */
+    getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
+      if (typeof dest === 'string') {
+        return this.getAnchorUrl('#' + escape(dest));
+      }
+      if (dest instanceof Array) {
+        var destRef = dest[0]; // see navigateTo method for dest format
+        var pageNumber = destRef instanceof Object ?
+          this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
+          (destRef + 1);
+        if (pageNumber) {
+          var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
+          var destKind = dest[1];
+          if (typeof destKind === 'object' && 'name' in destKind &&
+              destKind.name === 'XYZ') {
+            var scale = (dest[4] || this.pdfViewer.currentScaleValue);
+            var scaleNumber = parseFloat(scale);
+            if (scaleNumber) {
+              scale = scaleNumber * 100;
+            }
+            pdfOpenParams += '&zoom=' + scale;
+            if (dest[2] || dest[3]) {
+              pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
+            }
+          }
+          return pdfOpenParams;
+        }
+      }
+      return '';
+    },
+
+    /**
+     * Prefix the full url on anchor links to make sure that links are resolved
+     * relative to the current URL instead of the one defined in <base href>.
+     * @param {String} anchor The anchor hash, including the #.
+     * @returns {string} The hyperlink to the PDF object.
+     */
+    getAnchorUrl: function PDFLinkService_getAnchorUrl(anchor) {
+      return (this.baseUrl || '') + anchor;
+    },
+
+    /**
+     * @param {string} hash
+     */
+    setHash: function PDFLinkService_setHash(hash) {
+      if (hash.indexOf('=') >= 0) {
+        var params = parseQueryString(hash);
+        // borrowing syntax from "Parameters for Opening PDF Files"
+        if ('nameddest' in params) {
+          if (this.pdfHistory) {
+            this.pdfHistory.updateNextHashParam(params.nameddest);
+          }
+          this.navigateTo(params.nameddest);
+          return;
+        }
+        var pageNumber, dest;
+        if ('page' in params) {
+          pageNumber = (params.page | 0) || 1;
+        }
+        if ('zoom' in params) {
+          // Build the destination array.
+          var zoomArgs = params.zoom.split(','); // scale,left,top
+          var zoomArg = zoomArgs[0];
+          var zoomArgNumber = parseFloat(zoomArg);
+
+          if (zoomArg.indexOf('Fit') === -1) {
+            // If the zoomArg is a number, it has to get divided by 100. If it's
+            // a string, it should stay as it is.
+            dest = [null, { name: 'XYZ' },
+                    zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
+                    zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
+                    (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
+          } else {
+            if (zoomArg === 'Fit' || zoomArg === 'FitB') {
+              dest = [null, { name: zoomArg }];
+            } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
+                       (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
+              dest = [null, { name: zoomArg },
+                      zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
+            } else if (zoomArg === 'FitR') {
+              if (zoomArgs.length !== 5) {
+                console.error('PDFLinkService_setHash: ' +
+                              'Not enough parameters for \'FitR\'.');
+              } else {
+                dest = [null, { name: zoomArg },
+                        (zoomArgs[1] | 0), (zoomArgs[2] | 0),
+                        (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
+              }
+            } else {
+              console.error('PDFLinkService_setHash: \'' + zoomArg +
+                            '\' is not a valid zoom value.');
+            }
+          }
+        }
+        if (dest) {
+          this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
+        } else if (pageNumber) {
+          this.page = pageNumber; // simple page
+        }
+        if ('pagemode' in params) {
+          if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
+              params.pagemode === 'attachments') {
+            this.switchSidebarView((params.pagemode === 'bookmarks' ?
+                                   'outline' : params.pagemode), true);
+          } else if (params.pagemode === 'none' && this.sidebarOpen) {
+            document.getElementById('sidebarToggle').click();
+          }
+        }
+      } else if (/^\d+$/.test(hash)) { // page number
+        this.page = hash;
+      } else { // named destination
+        if (this.pdfHistory) {
+          this.pdfHistory.updateNextHashParam(unescape(hash));
+        }
+        this.navigateTo(unescape(hash));
+      }
+    },
+
+    /**
+     * @param {string} action
+     */
+    executeNamedAction: function PDFLinkService_executeNamedAction(action) {
+      // See PDF reference, table 8.45 - Named action
+      switch (action) {
+        case 'GoBack':
+          if (this.pdfHistory) {
+            this.pdfHistory.back();
+          }
+          break;
+
+        case 'GoForward':
+          if (this.pdfHistory) {
+            this.pdfHistory.forward();
+          }
+          break;
+
+        case 'NextPage':
+          this.page++;
+          break;
+
+        case 'PrevPage':
+          this.page--;
+          break;
+
+        case 'LastPage':
+          this.page = this.pagesCount;
+          break;
+
+        case 'FirstPage':
+          this.page = 1;
+          break;
+
+        default:
+          break; // No action according to spec
+      }
+
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('namedaction', true, true, {
+        action: action
+      });
+      this.pdfViewer.container.dispatchEvent(event);
+    },
+
+    /**
+     * @param {number} pageNum - page number.
+     * @param {Object} pageRef - reference to the page.
+     */
+    cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
+      var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
+      this._pagesRefCache[refStr] = pageNum;
+    }
+  };
+
+  return PDFLinkService;
+})();
+
+
+var PDFHistory = (function () {
+  function PDFHistory(options) {
+    this.linkService = options.linkService;
+
+    this.initialized = false;
+    this.initialDestination = null;
+    this.initialBookmark = null;
+  }
+
+  PDFHistory.prototype = {
+    /**
+     * @param {string} fingerprint
+     * @param {IPDFLinkService} linkService
+     */
+    initialize: function pdfHistoryInitialize(fingerprint) {
+      this.initialized = true;
+      this.reInitialized = false;
+      this.allowHashChange = true;
+      this.historyUnlocked = true;
+      this.isViewerInPresentationMode = false;
+
+      this.previousHash = window.location.hash.substring(1);
+      this.currentBookmark = '';
+      this.currentPage = 0;
+      this.updatePreviousBookmark = false;
+      this.previousBookmark = '';
+      this.previousPage = 0;
+      this.nextHashParam = '';
+
+      this.fingerprint = fingerprint;
+      this.currentUid = this.uid = 0;
+      this.current = {};
+
+      var state = window.history.state;
+      if (this._isStateObjectDefined(state)) {
+        // This corresponds to navigating back to the document
+        // from another page in the browser history.
+        if (state.target.dest) {
+          this.initialDestination = state.target.dest;
+        } else {
+          this.initialBookmark = state.target.hash;
+        }
+        this.currentUid = state.uid;
+        this.uid = state.uid + 1;
+        this.current = state.target;
+      } else {
+        // This corresponds to the loading of a new document.
+        if (state && state.fingerprint &&
+          this.fingerprint !== state.fingerprint) {
+          // Reinitialize the browsing history when a new document
+          // is opened in the web viewer.
+          this.reInitialized = true;
+        }
+        this._pushOrReplaceState({fingerprint: this.fingerprint}, true);
+      }
+
+      var self = this;
+      window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
+        evt.preventDefault();
+        evt.stopPropagation();
+
+        if (!self.historyUnlocked) {
+          return;
+        }
+        if (evt.state) {
+          // Move back/forward in the history.
+          self._goTo(evt.state);
+        } else {
+          // Handle the user modifying the hash of a loaded document.
+          self.previousHash = window.location.hash.substring(1);
+
+          // If the history is empty when the hash changes,
+          // update the previous entry in the browser history.
+          if (self.uid === 0) {
+            var previousParams = (self.previousHash && self.currentBookmark &&
+            self.previousHash !== self.currentBookmark) ?
+            {hash: self.currentBookmark, page: self.currentPage} :
+            {page: 1};
+            self.historyUnlocked = false;
+            self.allowHashChange = false;
+            window.history.back();
+            self._pushToHistory(previousParams, false, true);
+            window.history.forward();
+            self.historyUnlocked = true;
+          }
+          self._pushToHistory({hash: self.previousHash}, false, true);
+          self._updatePreviousBookmark();
+        }
+      }, false);
+
+      function pdfHistoryBeforeUnload() {
+        var previousParams = self._getPreviousParams(null, true);
+        if (previousParams) {
+          var replacePrevious = (!self.current.dest &&
+          self.current.hash !== self.previousHash);
+          self._pushToHistory(previousParams, false, replacePrevious);
+          self._updatePreviousBookmark();
+        }
+        // Remove the event listener when navigating away from the document,
+        // since 'beforeunload' prevents Firefox from caching the document.
+        window.removeEventListener('beforeunload', pdfHistoryBeforeUnload,
+                                   false);
+      }
+
+      window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+
+      window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
+        // If the entire viewer (including the PDF file) is cached in
+        // the browser, we need to reattach the 'beforeunload' event listener
+        // since the 'DOMContentLoaded' event is not fired on 'pageshow'.
+        window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+      }, false);
+
+      window.addEventListener('presentationmodechanged', function(e) {
+        self.isViewerInPresentationMode = !!e.detail.active;
+      });
+    },
+
+    clearHistoryState: function pdfHistory_clearHistoryState() {
+      this._pushOrReplaceState(null, true);
+    },
+
+    _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
+      return (state && state.uid >= 0 &&
+      state.fingerprint && this.fingerprint === state.fingerprint &&
+      state.target && state.target.hash) ? true : false;
+    },
+
+    _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
+                                                                replace) {
+      if (replace) {
+      window.history.replaceState(stateObj, '');
+      } else {
+      window.history.pushState(stateObj, '');
+      }
+    },
+
+    get isHashChangeUnlocked() {
+      if (!this.initialized) {
+        return true;
+      }
+      // If the current hash changes when moving back/forward in the history,
+      // this will trigger a 'popstate' event *as well* as a 'hashchange' event.
+      // Since the hash generally won't correspond to the exact the position
+      // stored in the history's state object, triggering the 'hashchange' event
+      // can thus corrupt the browser history.
+      //
+      // When the hash changes during a 'popstate' event, we *only* prevent the
+      // first 'hashchange' event and immediately reset allowHashChange.
+      // If it is not reset, the user would not be able to change the hash.
+
+      var temp = this.allowHashChange;
+      this.allowHashChange = true;
+      return temp;
+    },
+
+    _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
+      if (this.updatePreviousBookmark &&
+        this.currentBookmark && this.currentPage) {
+        this.previousBookmark = this.currentBookmark;
+        this.previousPage = this.currentPage;
+        this.updatePreviousBookmark = false;
+      }
+    },
+
+    updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
+                                                                    pageNum) {
+      if (this.initialized) {
+        this.currentBookmark = bookmark.substring(1);
+        this.currentPage = pageNum | 0;
+        this._updatePreviousBookmark();
+      }
+    },
+
+    updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
+      if (this.initialized) {
+        this.nextHashParam = param;
+      }
+    },
+
+    push: function pdfHistoryPush(params, isInitialBookmark) {
+      if (!(this.initialized && this.historyUnlocked)) {
+        return;
+      }
+      if (params.dest && !params.hash) {
+        params.hash = (this.current.hash && this.current.dest &&
+        this.current.dest === params.dest) ?
+          this.current.hash :
+          this.linkService.getDestinationHash(params.dest).split('#')[1];
+      }
+      if (params.page) {
+        params.page |= 0;
+      }
+      if (isInitialBookmark) {
+        var target = window.history.state.target;
+        if (!target) {
+          // Invoked when the user specifies an initial bookmark,
+          // thus setting initialBookmark, when the document is loaded.
+          this._pushToHistory(params, false);
+          this.previousHash = window.location.hash.substring(1);
+        }
+        this.updatePreviousBookmark = this.nextHashParam ? false : true;
+        if (target) {
+          // If the current document is reloaded,
+          // avoid creating duplicate entries in the history.
+          this._updatePreviousBookmark();
+        }
+        return;
+      }
+      if (this.nextHashParam) {
+        if (this.nextHashParam === params.hash) {
+          this.nextHashParam = null;
+          this.updatePreviousBookmark = true;
+          return;
+        } else {
+          this.nextHashParam = null;
+        }
+      }
+
+      if (params.hash) {
+        if (this.current.hash) {
+          if (this.current.hash !== params.hash) {
+            this._pushToHistory(params, true);
+          } else {
+            if (!this.current.page && params.page) {
+              this._pushToHistory(params, false, true);
+            }
+            this.updatePreviousBookmark = true;
+          }
+        } else {
+          this._pushToHistory(params, true);
+        }
+      } else if (this.current.page && params.page &&
+        this.current.page !== params.page) {
+        this._pushToHistory(params, true);
+      }
+    },
+
+    _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
+                                                              beforeUnload) {
+      if (!(this.currentBookmark && this.currentPage)) {
+        return null;
+      } else if (this.updatePreviousBookmark) {
+        this.updatePreviousBookmark = false;
+      }
+      if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
+        // Prevent the history from getting stuck in the current state,
+        // effectively preventing the user from going back/forward in
+        // the history.
+        //
+        // This happens if the current position in the document didn't change
+        // when the history was previously updated. The reasons for this are
+        // either:
+        // 1. The current zoom value is such that the document does not need to,
+        //    or cannot, be scrolled to display the destination.
+        // 2. The previous destination is broken, and doesn't actally point to a
+        //    position within the document.
+        //    (This is either due to a bad PDF generator, or the user making a
+        //     mistake when entering a destination in the hash parameters.)
+        return null;
+      }
+      if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
+        if (this.previousBookmark === this.currentBookmark) {
+          return null;
+        }
+      } else if (this.current.page || onlyCheckPage) {
+        if (this.previousPage === this.currentPage) {
+          return null;
+        }
+      } else {
+        return null;
+      }
+      var params = {hash: this.currentBookmark, page: this.currentPage};
+      if (this.isViewerInPresentationMode) {
+        params.hash = null;
+      }
+      return params;
+    },
+
+    _stateObj: function pdfHistory_stateObj(params) {
+      return {fingerprint: this.fingerprint, uid: this.uid, target: params};
+    },
+
+    _pushToHistory: function pdfHistory_pushToHistory(params,
+                                                      addPrevious, overwrite) {
+      if (!this.initialized) {
+        return;
+      }
+      if (!params.hash && params.page) {
+        params.hash = ('page=' + params.page);
+      }
+      if (addPrevious && !overwrite) {
+        var previousParams = this._getPreviousParams();
+        if (previousParams) {
+          var replacePrevious = (!this.current.dest &&
+          this.current.hash !== this.previousHash);
+          this._pushToHistory(previousParams, false, replacePrevious);
+        }
+      }
+      this._pushOrReplaceState(this._stateObj(params),
+        (overwrite || this.uid === 0));
+      this.currentUid = this.uid++;
+      this.current = params;
+      this.updatePreviousBookmark = true;
+    },
+
+    _goTo: function pdfHistory_goTo(state) {
+      if (!(this.initialized && this.historyUnlocked &&
+        this._isStateObjectDefined(state))) {
+        return;
+      }
+      if (!this.reInitialized && state.uid < this.currentUid) {
+        var previousParams = this._getPreviousParams(true);
+        if (previousParams) {
+          this._pushToHistory(this.current, false);
+          this._pushToHistory(previousParams, false);
+          this.currentUid = state.uid;
+          window.history.back();
+          return;
+        }
+      }
+      this.historyUnlocked = false;
+
       if (state.target.dest) {
-        this.initialDestination = state.target.dest;
+        this.linkService.navigateTo(state.target.dest);
       } else {
-        linkService.setHash(state.target.hash);
+        this.linkService.setHash(state.target.hash);
       }
       this.currentUid = state.uid;
-      this.uid = state.uid + 1;
+      if (state.uid > this.uid) {
+        this.uid = state.uid;
+      }
       this.current = state.target;
-    } else {
-      // This corresponds to the loading of a new document.
-      if (state && state.fingerprint &&
-          this.fingerprint !== state.fingerprint) {
-        // Reinitialize the browsing history when a new document
-        // is opened in the web viewer.
-        this.reInitialized = true;
-      }
-      this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
-    }
-
-    var self = this;
-    window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
-      evt.preventDefault();
-      evt.stopPropagation();
-
-      if (!self.historyUnlocked) {
-        return;
-      }
-      if (evt.state) {
-        // Move back/forward in the history.
-        self._goTo(evt.state);
-      } else {
-        // Handle the user modifying the hash of a loaded document.
-        self.previousHash = window.location.hash.substring(1);
-
-        // If the history is empty when the hash changes,
-        // update the previous entry in the browser history.
-        if (self.uid === 0) {
-          var previousParams = (self.previousHash && self.currentBookmark &&
-                                self.previousHash !== self.currentBookmark) ?
-            { hash: self.currentBookmark, page: self.currentPage } :
-            { page: 1 };
-          self.historyUnlocked = false;
-          self.allowHashChange = false;
+      this.updatePreviousBookmark = true;
+
+      var currentHash = window.location.hash.substring(1);
+      if (this.previousHash !== currentHash) {
+        this.allowHashChange = false;
+      }
+      this.previousHash = currentHash;
+
+      this.historyUnlocked = true;
+    },
+
+    back: function pdfHistoryBack() {
+      this.go(-1);
+    },
+
+    forward: function pdfHistoryForward() {
+      this.go(1);
+    },
+
+    go: function pdfHistoryGo(direction) {
+      if (this.initialized && this.historyUnlocked) {
+        var state = window.history.state;
+        if (direction === -1 && state && state.uid > 0) {
           window.history.back();
-          self._pushToHistory(previousParams, false, true);
+        } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
           window.history.forward();
-          self.historyUnlocked = true;
-        }
-        self._pushToHistory({ hash: self.previousHash }, false, true);
-        self._updatePreviousBookmark();
-      }
-    }, false);
-
-    function pdfHistoryBeforeUnload() {
-      var previousParams = self._getPreviousParams(null, true);
-      if (previousParams) {
-        var replacePrevious = (!self.current.dest &&
-                               self.current.hash !== self.previousHash);
-        self._pushToHistory(previousParams, false, replacePrevious);
-        self._updatePreviousBookmark();
-      }
-      // Remove the event listener when navigating away from the document,
-      // since 'beforeunload' prevents Firefox from caching the document.
-      window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false);
-    }
-    window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
-
-    window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
-      // If the entire viewer (including the PDF file) is cached in the browser,
-      // we need to reattach the 'beforeunload' event listener since
-      // the 'DOMContentLoaded' event is not fired on 'pageshow'.
-      window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
-    }, false);
-
-    window.addEventListener('presentationmodechanged', function(e) {
-      self.isViewerInPresentationMode = !!e.detail.active;
-    });
-  },
-
-  _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
-    return (state && state.uid >= 0 &&
-            state.fingerprint && this.fingerprint === state.fingerprint &&
-            state.target && state.target.hash) ? true : false;
-  },
-
-  _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
-                                                              replace) {
-    if (replace) {
-      window.history.replaceState(stateObj, '');
-    } else {
-      window.history.pushState(stateObj, '');
-    }
-  },
-
-  get isHashChangeUnlocked() {
-    if (!this.initialized) {
-      return true;
-    }
-    // If the current hash changes when moving back/forward in the history,
-    // this will trigger a 'popstate' event *as well* as a 'hashchange' event.
-    // Since the hash generally won't correspond to the exact the position
-    // stored in the history's state object, triggering the 'hashchange' event
-    // can thus corrupt the browser history.
-    //
-    // When the hash changes during a 'popstate' event, we *only* prevent the
-    // first 'hashchange' event and immediately reset allowHashChange.
-    // If it is not reset, the user would not be able to change the hash.
-
-    var temp = this.allowHashChange;
-    this.allowHashChange = true;
-    return temp;
-  },
-
-  _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
-    if (this.updatePreviousBookmark &&
-        this.currentBookmark && this.currentPage) {
-      this.previousBookmark = this.currentBookmark;
-      this.previousPage = this.currentPage;
-      this.updatePreviousBookmark = false;
-    }
-  },
-
-  updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
-                                                                  pageNum) {
-    if (this.initialized) {
-      this.currentBookmark = bookmark.substring(1);
-      this.currentPage = pageNum | 0;
-      this._updatePreviousBookmark();
-    }
-  },
-
-  updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
-    if (this.initialized) {
-      this.nextHashParam = param;
-    }
-  },
-
-  push: function pdfHistoryPush(params, isInitialBookmark) {
-    if (!(this.initialized && this.historyUnlocked)) {
-      return;
-    }
-    if (params.dest && !params.hash) {
-      params.hash = (this.current.hash && this.current.dest &&
-                     this.current.dest === params.dest) ?
-        this.current.hash :
-        this.linkService.getDestinationHash(params.dest).split('#')[1];
-    }
-    if (params.page) {
-      params.page |= 0;
-    }
-    if (isInitialBookmark) {
-      var target = window.history.state.target;
-      if (!target) {
-        // Invoked when the user specifies an initial bookmark,
-        // thus setting initialBookmark, when the document is loaded.
-        this._pushToHistory(params, false);
-        this.previousHash = window.location.hash.substring(1);
-      }
-      this.updatePreviousBookmark = this.nextHashParam ? false : true;
-      if (target) {
-        // If the current document is reloaded,
-        // avoid creating duplicate entries in the history.
-        this._updatePreviousBookmark();
-      }
-      return;
-    }
-    if (this.nextHashParam) {
-      if (this.nextHashParam === params.hash) {
-        this.nextHashParam = null;
-        this.updatePreviousBookmark = true;
-        return;
-      } else {
-        this.nextHashParam = null;
+        }
       }
     }
-
-    if (params.hash) {
-      if (this.current.hash) {
-        if (this.current.hash !== params.hash) {
-          this._pushToHistory(params, true);
-        } else {
-          if (!this.current.page && params.page) {
-            this._pushToHistory(params, false, true);
-          }
-          this.updatePreviousBookmark = true;
-        }
-      } else {
-        this._pushToHistory(params, true);
-      }
-    } else if (this.current.page && params.page &&
-               this.current.page !== params.page) {
-      this._pushToHistory(params, true);
-    }
-  },
-
-  _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
-                                                            beforeUnload) {
-    if (!(this.currentBookmark && this.currentPage)) {
-      return null;
-    } else if (this.updatePreviousBookmark) {
-      this.updatePreviousBookmark = false;
-    }
-    if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
-      // Prevent the history from getting stuck in the current state,
-      // effectively preventing the user from going back/forward in the history.
-      //
-      // This happens if the current position in the document didn't change when
-      // the history was previously updated. The reasons for this are either:
-      // 1. The current zoom value is such that the document does not need to,
-      //    or cannot, be scrolled to display the destination.
-      // 2. The previous destination is broken, and doesn't actally point to a
-      //    position within the document.
-      //    (This is either due to a bad PDF generator, or the user making a
-      //     mistake when entering a destination in the hash parameters.)
-      return null;
-    }
-    if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
-      if (this.previousBookmark === this.currentBookmark) {
-        return null;
-      }
-    } else if (this.current.page || onlyCheckPage) {
-      if (this.previousPage === this.currentPage) {
-        return null;
-      }
-    } else {
-      return null;
-    }
-    var params = { hash: this.currentBookmark, page: this.currentPage };
-    if (this.isViewerInPresentationMode) {
-      params.hash = null;
-    }
-    return params;
-  },
-
-  _stateObj: function pdfHistory_stateObj(params) {
-    return { fingerprint: this.fingerprint, uid: this.uid, target: params };
-  },
-
-  _pushToHistory: function pdfHistory_pushToHistory(params,
-                                                    addPrevious, overwrite) {
-    if (!this.initialized) {
-      return;
-    }
-    if (!params.hash && params.page) {
-      params.hash = ('page=' + params.page);
-    }
-    if (addPrevious && !overwrite) {
-      var previousParams = this._getPreviousParams();
-      if (previousParams) {
-        var replacePrevious = (!this.current.dest &&
-                               this.current.hash !== this.previousHash);
-        this._pushToHistory(previousParams, false, replacePrevious);
-      }
-    }
-    this._pushOrReplaceState(this._stateObj(params),
-                             (overwrite || this.uid === 0));
-    this.currentUid = this.uid++;
-    this.current = params;
-    this.updatePreviousBookmark = true;
-  },
-
-  _goTo: function pdfHistory_goTo(state) {
-    if (!(this.initialized && this.historyUnlocked &&
-          this._isStateObjectDefined(state))) {
-      return;
-    }
-    if (!this.reInitialized && state.uid < this.currentUid) {
-      var previousParams = this._getPreviousParams(true);
-      if (previousParams) {
-        this._pushToHistory(this.current, false);
-        this._pushToHistory(previousParams, false);
-        this.currentUid = state.uid;
-        window.history.back();
-        return;
-      }
-    }
-    this.historyUnlocked = false;
-
-    if (state.target.dest) {
-      this.linkService.navigateTo(state.target.dest);
-    } else {
-      this.linkService.setHash(state.target.hash);
-    }
-    this.currentUid = state.uid;
-    if (state.uid > this.uid) {
-      this.uid = state.uid;
-    }
-    this.current = state.target;
-    this.updatePreviousBookmark = true;
-
-    var currentHash = window.location.hash.substring(1);
-    if (this.previousHash !== currentHash) {
-      this.allowHashChange = false;
-    }
-    this.previousHash = currentHash;
-
-    this.historyUnlocked = true;
-  },
-
-  back: function pdfHistoryBack() {
-    this.go(-1);
-  },
-
-  forward: function pdfHistoryForward() {
-    this.go(1);
-  },
-
-  go: function pdfHistoryGo(direction) {
-    if (this.initialized && this.historyUnlocked) {
-      var state = window.history.state;
-      if (direction === -1 && state && state.uid > 0) {
-        window.history.back();
-      } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
-        window.history.forward();
-      }
-    }
-  }
-};
+  };
+
+  return PDFHistory;
+})();
 
 
 var SecondaryToolbar = {
   opened: false,
   previousContainerHeight: null,
   newContainerHeight: null,
 
   initialize: function secondaryToolbarInitialize(options) {
@@ -4311,17 +4629,16 @@ var PDFViewer = (function pdfViewer() {
       }
 
       this.pdfDocument = pdfDocument;
       if (!pdfDocument) {
         return;
       }
 
       var pagesCount = pdfDocument.numPages;
-      var pagesRefMap = this.pagesRefMap = {};
       var self = this;
 
       var resolvePagesPromise;
       var pagesPromise = new Promise(function (resolve) {
         resolvePagesPromise = resolve;
       });
       this.pagesPromise = pagesPromise;
       pagesPromise.then(function () {
@@ -4376,30 +4693,31 @@ var PDFViewer = (function pdfViewer() {
             renderingQueue: this.renderingQueue,
             textLayerFactory: textLayerFactory,
             annotationsLayerFactory: this
           });
           bindOnAfterAndBeforeDraw(pageView);
           this._pages.push(pageView);
         }
 
+        var linkService = this.linkService;
+
         // Fetch all the pages since the viewport is needed before printing
         // starts to create the correct size canvas. Wait until one page is
         // rendered so we don't tie up too many resources early on.
         onePageRendered.then(function () {
           if (!PDFJS.disableAutoFetch) {
             var getPagesLeft = pagesCount;
             for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
               pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
                 var pageView = self._pages[pageNum - 1];
                 if (!pageView.pdfPage) {
                   pageView.setPdfPage(pdfPage);
                 }
-                var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
-                pagesRefMap[refStr] = pageNum;
+                linkService.cachePageRef(pageNum, pdfPage.ref);
                 getPagesLeft--;
                 if (!getPagesLeft) {
                   resolvePagesPromise();
                 }
               }.bind(null, pageNum));
             }
           } else {
             // XXX: Printing is semi-broken with auto fetch disabled.
@@ -4876,16 +5194,21 @@ var SimpleLinkService = (function Simple
     /**
      * @param {string} hash
      */
     setHash: function (hash) {},
     /**
      * @param {string} action
      */
     executeNamedAction: function (action) {},
+    /**
+     * @param {number} pageNum - page number.
+     * @param {Object} pageRef - reference to the page.
+     */
+    cachePageRef: function (pageNum, pageRef) {}
   };
   return SimpleLinkService;
 })();
 
 
 var THUMBNAIL_SCROLL_MARGIN = -19;
 
 
@@ -5553,31 +5876,36 @@ var PDFAttachmentView = (function PDFAtt
   };
 
   return PDFAttachmentView;
 })();
 
 
 var PDFViewerApplication = {
   initialBookmark: document.location.hash.substring(1),
+  initialDestination: null,
   initialized: false,
   fellback: false,
   pdfDocument: null,
   sidebarOpen: false,
   printing: false,
   /** @type {PDFViewer} */
   pdfViewer: null,
   /** @type {PDFThumbnailViewer} */
   pdfThumbnailViewer: null,
   /** @type {PDFRenderingQueue} */
   pdfRenderingQueue: null,
   /** @type {PDFPresentationMode} */
   pdfPresentationMode: null,
   /** @type {PDFDocumentProperties} */
   pdfDocumentProperties: null,
+  /** @type {PDFLinkService} */
+  pdfLinkService: null,
+  /** @type {PDFHistory} */
+  pdfHistory: null,
   pageRotation: 0,
   updateScaleControls: true,
   isInitialViewSet: false,
   animationStartedPromise: null,
   preferenceSidebarViewOnLoad: SidebarView.NONE,
   preferencePdfBugEnabled: false,
   preferenceShowPreviousViewOnLoad: true,
   preferenceDefaultZoomValue: '',
@@ -5585,36 +5913,45 @@ var PDFViewerApplication = {
   url: '',
 
   // called once when the document is loaded
   initialize: function pdfViewInitialize() {
     var pdfRenderingQueue = new PDFRenderingQueue();
     pdfRenderingQueue.onIdle = this.cleanup.bind(this);
     this.pdfRenderingQueue = pdfRenderingQueue;
 
+    var pdfLinkService = new PDFLinkService();
+    this.pdfLinkService = pdfLinkService;
+
     var container = document.getElementById('viewerContainer');
     var viewer = document.getElementById('viewer');
     this.pdfViewer = new PDFViewer({
       container: container,
       viewer: viewer,
       renderingQueue: pdfRenderingQueue,
-      linkService: this
+      linkService: pdfLinkService
     });
     pdfRenderingQueue.setViewer(this.pdfViewer);
+    pdfLinkService.setViewer(this.pdfViewer);
 
     var thumbnailContainer = document.getElementById('thumbnailView');
     this.pdfThumbnailViewer = new PDFThumbnailViewer({
       container: thumbnailContainer,
       renderingQueue: pdfRenderingQueue,
-      linkService: this
+      linkService: pdfLinkService
     });
     pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
 
     Preferences.initialize();
 
+    this.pdfHistory = new PDFHistory({
+      linkService: pdfLinkService
+    });
+    pdfLinkService.setHistory(this.pdfHistory);
+
     this.findController = new PDFFindController({
       pdfViewer: this.pdfViewer,
       integratedFind: this.supportsIntegratedFind
     });
     this.pdfViewer.setFindController(this.findController);
 
     this.findBar = new PDFFindBar({
       bar: document.getElementById('findbar'),
@@ -5771,21 +6108,21 @@ var PDFViewerApplication = {
     return this.pdfViewer.currentScaleValue;
   },
 
   get pagesCount() {
     return this.pdfDocument.numPages;
   },
 
   set page(val) {
-    this.pdfViewer.currentPageNumber = val;
+    this.pdfLinkService.page = val;
   },
 
-  get page() {
-    return this.pdfViewer.currentPageNumber;
+  get page() { // TODO remove
+    return this.pdfLinkService.page;
   },
 
   get supportsPrinting() {
     var canvas = document.createElement('canvas');
     var value = 'mozPrintCallback' in canvas;
 
     return PDFJS.shadow(this, 'supportsPrinting', value);
   },
@@ -5924,16 +6261,17 @@ var PDFViewerApplication = {
       return;
     }
 
     this.pdfDocument.destroy();
     this.pdfDocument = null;
 
     this.pdfThumbnailViewer.setDocument(null);
     this.pdfViewer.setDocument(null);
+    this.pdfLinkService.setDocument(null, null);
 
     if (typeof PDFBug !== 'undefined') {
       PDFBug.cleanup();
     }
   },
 
   // TODO(mack): This function signature should really be pdfViewOpen(url, args)
   open: function pdfViewOpen(file, scale, password,
@@ -6056,140 +6394,16 @@ var PDFViewerApplication = {
       function response(download) {
         if (!download) {
           return;
         }
         PDFViewerApplication.download();
       });
   },
 
-  navigateTo: function pdfViewNavigateTo(dest) {
-    var destString = '';
-    var self = this;
-
-    var goToDestination = function(destRef) {
-      self.pendingRefStr = null;
-      // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
-      var pageNumber = destRef instanceof Object ?
-        self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
-        (destRef + 1);
-      if (pageNumber) {
-        if (pageNumber > self.pagesCount) {
-          pageNumber = self.pagesCount;
-        }
-        self.pdfViewer.scrollPageIntoView(pageNumber, dest);
-
-        // Update the browsing history.
-        PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
-      } else {
-        self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
-          var pageNum = pageIndex + 1;
-          self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
-          goToDestination(destRef);
-        });
-      }
-    };
-
-    var destinationPromise;
-    if (typeof dest === 'string') {
-      destString = dest;
-      destinationPromise = this.pdfDocument.getDestination(dest);
-    } else {
-      destinationPromise = Promise.resolve(dest);
-    }
-    destinationPromise.then(function(destination) {
-      dest = destination;
-      if (!(destination instanceof Array)) {
-        return; // invalid destination
-      }
-      goToDestination(destination[0]);
-    });
-  },
-
-  executeNamedAction: function pdfViewExecuteNamedAction(action) {
-    // See PDF reference, table 8.45 - Named action
-    switch (action) {
-      case 'GoToPage':
-        document.getElementById('pageNumber').focus();
-        break;
-
-      case 'GoBack':
-        PDFHistory.back();
-        break;
-
-      case 'GoForward':
-        PDFHistory.forward();
-        break;
-
-      case 'Find':
-        if (!this.supportsIntegratedFind) {
-          this.findBar.toggle();
-        }
-        break;
-
-      case 'NextPage':
-        this.page++;
-        break;
-
-      case 'PrevPage':
-        this.page--;
-        break;
-
-      case 'LastPage':
-        this.page = this.pagesCount;
-        break;
-
-      case 'FirstPage':
-        this.page = 1;
-        break;
-
-      default:
-        break; // No action according to spec
-    }
-  },
-
-  getDestinationHash: function pdfViewGetDestinationHash(dest) {
-    if (typeof dest === 'string') {
-      return this.getAnchorUrl('#' + escape(dest));
-    }
-    if (dest instanceof Array) {
-      var destRef = dest[0]; // see navigateTo method for dest format
-      var pageNumber = destRef instanceof Object ?
-        this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
-        (destRef + 1);
-      if (pageNumber) {
-        var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
-        var destKind = dest[1];
-        if (typeof destKind === 'object' && 'name' in destKind &&
-            destKind.name === 'XYZ') {
-          var scale = (dest[4] || this.currentScaleValue);
-          var scaleNumber = parseFloat(scale);
-          if (scaleNumber) {
-            scale = scaleNumber * 100;
-          }
-          pdfOpenParams += '&zoom=' + scale;
-          if (dest[2] || dest[3]) {
-            pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
-          }
-        }
-        return pdfOpenParams;
-      }
-    }
-    return '';
-  },
-
-  /**
-   * Prefix the full url on anchor links to make sure that links are resolved
-   * relative to the current URL instead of the one defined in <base href>.
-   * @param {String} anchor The anchor hash, including the #.
-   */
-  getAnchorUrl: function getAnchorUrl(anchor) {
-    return this.url.split('#')[0] + anchor;
-  },
-
   /**
    * Show the error box.
    * @param {String} message A message that is human readable.
    * @param {Object} moreInfo (optional) Further information about the error
    *                            that is more technical.  Should have a 'message'
    *                            and optionally a 'stack' property.
    */
   error: function pdfViewError(message, moreInfo) {
@@ -6269,45 +6483,53 @@ var PDFViewerApplication = {
     var pagesCount = pdfDocument.numPages;
     document.getElementById('numPages').textContent =
       mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
     document.getElementById('pageNumber').max = pagesCount;
 
     var id = this.documentFingerprint = pdfDocument.fingerprint;
     var store = this.store = new ViewHistory(id);
 
+    var baseDocumentUrl = this.url.split('#')[0];
+    this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
+
     var pdfViewer = this.pdfViewer;
     pdfViewer.currentScale = scale;
     pdfViewer.setDocument(pdfDocument);
     var firstPagePromise = pdfViewer.firstPagePromise;
     var pagesPromise = pdfViewer.pagesPromise;
     var onePageRendered = pdfViewer.onePageRendered;
 
     this.pageRotation = 0;
     this.isInitialViewSet = false;
-    this.pagesRefMap = pdfViewer.pagesRefMap;
 
     this.pdfThumbnailViewer.setDocument(pdfDocument);
 
     firstPagePromise.then(function(pdfPage) {
       downloadedPromise.then(function () {
         var event = document.createEvent('CustomEvent');
         event.initCustomEvent('documentload', true, true, {});
         window.dispatchEvent(event);
       });
 
       self.loadingBar.setWidth(document.getElementById('viewer'));
 
       if (!PDFJS.disableHistory && !self.isViewerEmbedded) {
         // The browsing history is only enabled when the viewer is standalone,
         // i.e. not when it is embedded in a web page.
-        if (!self.preferenceShowPreviousViewOnLoad && window.history.state) {
-          window.history.replaceState(null, '');
-        }
-        PDFHistory.initialize(self.documentFingerprint, self);
+        if (!self.preferenceShowPreviousViewOnLoad) {
+          self.pdfHistory.clearHistoryState();
+        }
+        self.pdfHistory.initialize(self.documentFingerprint);
+
+        if (self.pdfHistory.initialDestination) {
+          self.initialDestination = self.pdfHistory.initialDestination;
+        } else if (self.pdfHistory.initialBookmark) {
+          self.initialBookmark = self.pdfHistory.initialBookmark;
+        }
       }
 
       store.initializedPromise.then(function resolved() {
         var storedHash = null;
         if (self.preferenceShowPreviousViewOnLoad &&
             store.get('exists', false)) {
           var pageNum = store.get('page', '1');
           var zoom = self.preferenceDefaultZoomValue ||
@@ -6358,17 +6580,17 @@ var PDFViewerApplication = {
     // outline depends on pagesRefMap
     var promises = [pagesPromise, this.animationStartedPromise];
     Promise.all(promises).then(function() {
       pdfDocument.getOutline().then(function(outline) {
         var container = document.getElementById('outlineView');
         self.outline = new PDFOutlineView({
           container: container,
           outline: outline,
-          linkService: self
+          linkService: self.pdfLinkService
         });
         self.outline.render();
         document.getElementById('viewOutline').disabled = !outline;
 
         if (!outline && !container.classList.contains('hidden')) {
           self.switchSidebarView('thumbs');
         }
         if (outline &&
@@ -6469,25 +6691,25 @@ var PDFViewerApplication = {
     this.isInitialViewSet = true;
 
     // When opening a new file (when one is already loaded in the viewer):
     // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
     // if 'currentPageNumber' is larger than the number of pages in the file.
     document.getElementById('pageNumber').value =
       this.pdfViewer.currentPageNumber = 1;
 
-    if (PDFHistory.initialDestination) {
-      this.navigateTo(PDFHistory.initialDestination);
-      PDFHistory.initialDestination = null;
+    if (this.initialDestination) {
+      this.pdfLinkService.navigateTo(this.initialDestination);
+      this.initialDestination = null;
     } else if (this.initialBookmark) {
-      this.setHash(this.initialBookmark);
-      PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
+      this.pdfLinkService.setHash(this.initialBookmark);
+      this.pdfHistory.push({ hash: this.initialBookmark }, true);
       this.initialBookmark = null;
     } else if (storedHash) {
-      this.setHash(storedHash);
+      this.pdfLinkService.setHash(storedHash);
     } else if (scale) {
       this.setScale(scale, true);
       this.page = 1;
     }
 
     if (this.pdfViewer.currentScale === UNKNOWN_SCALE) {
       // Scale was not initialized: invalid bookmark or scale was not specified.
       // Setting the default one.
@@ -6502,94 +6724,16 @@ var PDFViewerApplication = {
   },
 
   forceRendering: function pdfViewForceRendering() {
     this.pdfRenderingQueue.printing = this.printing;
     this.pdfRenderingQueue.isThumbnailViewEnabled = this.sidebarOpen;
     this.pdfRenderingQueue.renderHighestPriority();
   },
 
-  setHash: function pdfViewSetHash(hash) {
-    if (!this.isInitialViewSet) {
-      this.initialBookmark = hash;
-      return;
-    }
-    if (!hash) {
-      return;
-    }
-
-    if (hash.indexOf('=') >= 0) {
-      var params = this.parseQueryString(hash);
-      // borrowing syntax from "Parameters for Opening PDF Files"
-      if ('nameddest' in params) {
-        PDFHistory.updateNextHashParam(params.nameddest);
-        this.navigateTo(params.nameddest);
-        return;
-      }
-      var pageNumber, dest;
-      if ('page' in params) {
-        pageNumber = (params.page | 0) || 1;
-      }
-      if ('zoom' in params) {
-        // Build the destination array.
-        var zoomArgs = params.zoom.split(','); // scale,left,top
-        var zoomArg = zoomArgs[0];
-        var zoomArgNumber = parseFloat(zoomArg);
-
-        if (zoomArg.indexOf('Fit') === -1) {
-          // If the zoomArg is a number, it has to get divided by 100. If it's
-          // a string, it should stay as it is.
-          dest = [null, { name: 'XYZ' },
-                  zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
-                  zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
-                  (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
-        } else {
-          if (zoomArg === 'Fit' || zoomArg === 'FitB') {
-            dest = [null, { name: zoomArg }];
-          } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
-                     (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
-            dest = [null, { name: zoomArg },
-                    zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
-          } else if (zoomArg === 'FitR') {
-            if (zoomArgs.length !== 5) {
-              console.error('pdfViewSetHash: ' +
-                            'Not enough parameters for \'FitR\'.');
-            } else {
-              dest = [null, { name: zoomArg },
-                      (zoomArgs[1] | 0), (zoomArgs[2] | 0),
-                      (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
-            }
-          } else {
-            console.error('pdfViewSetHash: \'' + zoomArg +
-                          '\' is not a valid zoom value.');
-          }
-        }
-      }
-      if (dest) {
-        this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
-      } else if (pageNumber) {
-        this.page = pageNumber; // simple page
-      }
-      if ('pagemode' in params) {
-        if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
-            params.pagemode === 'attachments') {
-          this.switchSidebarView((params.pagemode === 'bookmarks' ?
-                                  'outline' : params.pagemode), true);
-        } else if (params.pagemode === 'none' && this.sidebarOpen) {
-          document.getElementById('sidebarToggle').click();
-        }
-      }
-    } else if (/^\d+$/.test(hash)) { // page number
-      this.page = hash;
-    } else { // named destination
-      PDFHistory.updateNextHashParam(unescape(hash));
-      this.navigateTo(unescape(hash));
-    }
-  },
-
   refreshThumbnailViewer: function pdfViewRefreshThumbnailViewer() {
     var pdfViewer = this.pdfViewer;
     var thumbnailViewer = this.pdfThumbnailViewer;
 
     // set thumbnail images of rendered pages
     var pagesCount = pdfViewer.pagesCount;
     for (var pageIndex = 0; pageIndex < pagesCount; pageIndex++) {
       var pageView = pdfViewer.getPageView(pageIndex);
@@ -6655,40 +6799,27 @@ var PDFViewerApplication = {
 
         if (attachmentsButton.getAttribute('disabled')) {
           return;
         }
         break;
     }
   },
 
-  // Helper function to parse query string (e.g. ?param1=value&parm2=...).
-  parseQueryString: function pdfViewParseQueryString(query) {
-    var parts = query.split('&');
-    var params = {};
-    for (var i = 0, ii = parts.length; i < ii; ++i) {
-      var param = parts[i].split('=');
-      var key = param[0].toLowerCase();
-      var value = param.length > 1 ? param[1] : null;
-      params[decodeURIComponent(key)] = decodeURIComponent(value);
-    }
-    return params;
-  },
-
   beforePrint: function pdfViewSetupBeforePrint() {
     if (!this.supportsPrinting) {
       var printMessage = mozL10n.get('printing_not_supported', null,
           'Warning: Printing is not fully supported by this browser.');
       this.error(printMessage);
       return;
     }
 
     var alertNotReady = false;
     var i, ii;
-    if (!this.pagesCount) {
+    if (!this.pdfDocument || !this.pagesCount) {
       alertNotReady = true;
     } else {
       for (i = 0, ii = this.pagesCount; i < ii; ++i) {
         if (!this.pdfViewer.getPageView(i).pdfPage) {
           alertNotReady = true;
           break;
         }
       }
@@ -6816,17 +6947,17 @@ function webViewerInitialized() {
 
   document.getElementById('openFile').setAttribute('hidden', 'true');
   document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true');
 
 
   if (PDFViewerApplication.preferencePdfBugEnabled) {
     // Special debugging flags in the hash section of the URL.
     var hash = document.location.hash.substring(1);
-    var hashParams = PDFViewerApplication.parseQueryString(hash);
+    var hashParams = parseQueryString(hash);
 
     if ('disableworker' in hashParams) {
       PDFJS.disableWorker = (hashParams['disableworker'] === 'true');
     }
     if ('disablerange' in hashParams) {
       PDFJS.disableRange = (hashParams['disablerange'] === 'true');
     }
     if ('disablestream' in hashParams) {
@@ -7050,16 +7181,33 @@ document.addEventListener('textlayerrend
     console.error(mozL10n.get('document_colors_disabled', null,
       'PDF documents are not allowed to use their own colors: ' +
       '\'Allow pages to choose their own colors\' ' +
       'is deactivated in the browser.'));
     PDFViewerApplication.fallback();
   }
 }, true);
 
+document.addEventListener('namedaction', function (e) {
+  // Processing couple of named actions that might be useful.
+  // See also PDFLinkService.executeNamedAction
+  var action = e.action;
+  switch (action) {
+    case 'GoToPage':
+      document.getElementById('pageNumber').focus();
+      break;
+
+    case 'Find':
+      if (!this.supportsIntegratedFind) {
+        this.findBar.toggle();
+      }
+      break;
+  }
+}, true);
+
 window.addEventListener('presentationmodechanged', function (e) {
   var active = e.detail.active;
   var switchInProgress = e.detail.switchInProgress;
   PDFViewerApplication.pdfViewer.presentationModeState =
     switchInProgress ? PresentationModeState.CHANGING :
     active ? PresentationModeState.FULLSCREEN : PresentationModeState.NORMAL;
 });
 
@@ -7082,22 +7230,24 @@ window.addEventListener('updateviewarea'
       'page': location.pageNumber,
       'zoom': location.scale,
       'scrollLeft': location.left,
       'scrollTop': location.top
     }).catch(function() {
       // unable to write to storage
     });
   });
-  var href = PDFViewerApplication.getAnchorUrl(location.pdfOpenParams);
+  var href =
+    PDFViewerApplication.pdfLinkService.getAnchorUrl(location.pdfOpenParams);
   document.getElementById('viewBookmark').href = href;
   document.getElementById('secondaryViewBookmark').href = href;
 
   // Update the current bookmark in the browsing history.
-  PDFHistory.updateCurrentBookmark(location.pdfOpenParams, location.pageNumber);
+  PDFViewerApplication.pdfHistory.updateCurrentBookmark(location.pdfOpenParams,
+                                                        location.pageNumber);
 
   // Show/hide the loading indicator in the page number input element.
   var pageNumberInput = document.getElementById('pageNumber');
   var currentPage =
     PDFViewerApplication.pdfViewer.getPageView(PDFViewerApplication.page - 1);
 
   if (currentPage.renderingState === RenderingStates.FINISHED) {
     pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
@@ -7117,18 +7267,26 @@ window.addEventListener('resize', functi
   }
   updateViewarea();
 
   // Set the 'max-height' CSS property of the secondary toolbar.
   SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
 });
 
 window.addEventListener('hashchange', function webViewerHashchange(evt) {
-  if (PDFHistory.isHashChangeUnlocked) {
-    PDFViewerApplication.setHash(document.location.hash.substring(1));
+  if (PDFViewerApplication.pdfHistory.isHashChangeUnlocked) {
+    var hash = document.location.hash.substring(1);
+    if (!hash) {
+      return;
+    }
+    if (!PDFViewerApplication.isInitialViewSet) {
+      PDFViewerApplication.initialBookmark = hash;
+    } else {
+      PDFViewerApplication.pdfLinkService.setHash(hash);
+    }
   }
 });
 
 
 function selectScaleOption(value) {
   var options = document.getElementById('scaleSelect').options;
   var predefinedValueFound = false;
   for (var i = 0; i < options.length; i++) {
@@ -7471,23 +7629,23 @@ window.addEventListener('keydown', funct
       pdfViewer.focus();
     }
   }
 
   if (cmd === 2) { // alt-key
     switch (evt.keyCode) {
       case 37: // left arrow
         if (isViewerInPresentationMode) {
-          PDFHistory.back();
+          PDFViewerApplication.pdfHistory.back();
           handled = true;
         }
         break;
       case 39: // right arrow
         if (isViewerInPresentationMode) {
-          PDFHistory.forward();
+          PDFViewerApplication.pdfHistory.forward();
           handled = true;
         }
         break;
     }
   }
 
   if (handled) {
     evt.preventDefault();
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -61,40 +61,184 @@ const PREF_DIRECTORY_SOURCE = "browser.n
 // The preference that tells where to send click/view pings
 const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
 
 // The preference that tells if newtab is enhanced
 const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
 
 // Only allow explicitly approved frecent sites with display name
 const ALLOWED_FRECENT_SITES = new Map([
-  [ 'airdroid.com,android-developers.blogspot.com,android.com,androidandme.com,androidapplications.com,androidapps.com,androidauthority.com,androidcentral.com,androidcommunity.com,androidfilehost.com,androidforums.com,androidguys.com,androidheadlines.com,androidpit.com,androidpolice.com,androidspin.com,androidtapp.com,androinica.com,droid-life.com,droidforums.net,droidviews.com,droidxforums.com,forum.xda-developers.com,phandroid.com,play.google.com,shopandroid.com,talkandroid.com,theandroidsoul.com,thedroidguy.com,videodroid.org',
-    'Technology' ],
-  [ 'assurancewireless.com,att.com,attsavings.com,boostmobile.com,budgetmobile.com,consumercellular.com,credomobile.com,gosmartmobile.com,h2owirelessnow.com,lycamobile.com,lycamobile.us,metropcs.com,myfamilymobile.com,polarmobile.com,qlinkwireless.com,republicwireless.com,sprint.com,straighttalk.com,t-mobile.com,tracfonewireless.com,verizonwireless.com,virginmobile.com,virginmobile.com.au,virginmobileusa.com,vodafone.co.uk,vodafone.com,vzwshop.com',
-    'Mobile Phone' ],
+  [ '1800petmeds.com,800petmeds.com,adopt.dogtime.com,adoptapet.com,akc.org,americanhumane.org,animal.discovery.com,animalconcerns.org,animalshelter.org,arcatapet.com,aspca.org,avma.org,bestfriends.org,blog.petmeds.com,buddydoghs.com,carealotpets.com,dailypuppy.com,dog.com,dogbar.com,dogbreedinfo.com,drsfostersmith.com,entirelypets.com,farmsanctuary.org,farmusa.org,freekibble.com,freekibblekat.com,healthypets.com,hsus.org,humanesociety.org,liveaquaria.com,marinedepot.com,medi-vet.com,nationalpetpharmacy.com,nsalamerica.org,nycacc.org,ohmydogsupplies.com,pet-dog-cat-supply-store.com,petcarerx.com,petco.com,petdiscounters.com,petedge.com,peteducation.com,petfinder.com,petfooddirect.com,petguys.com,petharbor.com,petmountain.com,petplanet.co.uk,pets911.com,petsmart.com,petsuppliesplus.com,puppyfind.com,revivalanimal.com,terrificpets.com,thatpetplace.com,theanimalrescuesite.com,theanimalrescuesite.greatergood.com,thefluffingtonpost.com,therainforestsite.com,vetdepot.com',
+    'pet' ],
+  [ '1aauto.com,autoblog.com,autoguide.com,autosite.com,autoweek.com,bimmerpost.com,bmwblog.com,boldride.com,caranddriver.com,carcomplaints.com,carspoon.com,cherokeeforum.com,classiccars.com,commercialtrucktrader.com,corvetteforum.com,dealerrater.com,ebizautos.com,ford-trucks.com,hemmings.com,jalopnik.com,jeepforum.com,jeepsunlimited.com,jk-forum.com,legendaryspeed.com,motorauthority.com,motortrend.com,motorwings.com,odometer.com,pirate4x4.com,purecars.com,roadandtrack.com,teslamotorsclub.com,topgear.com,topspeed.com,totalmini.com,truckpaper.com,wranglerforum.com',
+    'auto' ],
+  [ 'autobytel.com,autocheck.com,automotive.com,autonation.com,autos.aol.com,autos.msn.com,autos.yahoo.com,autotrader.autos.msn.com,autotrader.com,autotraderclassics.com,autoweb.com,car.com,carbuyingtips.com,carfax.com,cargurus.com,carmax.com,carprices.com,cars.com,cars.oodle.com,carsdirect.com,carsforsale.com,edmunds.com,hertzcarsales.com,imotors.com,intellichoice.com,internetautoguide.com,kbb.com,lemonfree.com,nada.com,nadaguides.com,thecarconnection.com,thetruthaboutcars.com,truecar.com,usedcars.com,usnews.rankingsandreviews.com',
+    'auto' ],
+  [ 'acura.com,audi.ca,audi.com,audiusa.com,automobiles.honda.com,bentleymotors.com,bmw.com,bmwusa.com,buick.com,buyatoyota.com,cadillac.com,cars.mclaren.com,chevrolet.com,choosenissan.com,chrysler.com,daimler.com,dodge.com,ferrari.com/en_us,fiskerautomotive.com,ford.com,gm.com,gmc.com,hummer.com,hyundai.com,hyundaiusa.com,infiniti.com,infinitiusa.com,jaguarusa.com,jeep.com,kia.com,kiamotors.com,lamborghini.com/en/home,landrover.com,landroverusa.com,lexus.com,lincoln.com,maserati.us,mazda.com,mazdausa.com,mbusa.com,mbusi.com,mercedes-amg.com,mercedes-benz.com,mercuryvehicles.com,miniusa.com,nissanusa.com,pontiac.com,porsche.com/usa,ramtrucks.com,rolls-roycemotorcars.com,saturn.com,scion.com,subaru.com,teslamotors.com,toyota.com,volkswagen.co.uk,volkswagen.com,volvocars.com/us,vw.com',
+    'auto' ],
+  [ '1010tires.com,4wheelparts.com,advanceautoparts.com,andysautosport.com,autoanything.com,autogeek.net,autopartsgiant.com,autopartswarehouse.com,autotrucktoys.com,autozone.com,autozoneinc.com,bavauto.com,bigotires.com,bilsteinus.com,brembo.com,car-part.com,carid.com,carparts.com,carquest.com,dinancars.com,discounttire.com,discounttiredirect.com,firestonecompleteautocare.com,goodyear.com,hrewheels,jcwhitney.com,kw-suspensions.com,momousa.com,napaonline.com,onlinetires.com,oreillyauto.com,oriellysautoparts.com,pepboys.com,repairpal.com,rockauto.com,shop.advanceautoparts.com,slickcar.com,stoptech.com,streetbeatcustoms.com,summitracing.com,tirebuyer.com,tirerack.com,tiresplus.com,tsw.com,velocitymotoring.com,wheelmax.com',
+    'auto parts' ],
+  [ 'abebooks.co.uk,abebooks.com,addall.com,alibris.com,allaboutcircuits.com,allbookstores.com,allyoucanbooks.com,answersingenesis.org,artnet.com,audiobooks.com,barnesandnoble.com,barnesandnobleinc.com,bartleby.com,betterworldbooks.com,biblio.com,biggerbooks.com,bncollege.com,bookbyte.com,bookdepository.com,bookfinder.com,bookrenter.com,booksamillion.com,booksite.com,boundless.com,brookstone.com,btol.com,calibre-ebook.com,campusbookrentals.com,casadellibro.com,cbomc.com,cengagebrain.com,chapters.indigo.ca,christianbook.com,ciscopress.com,coursesmart.com,cqpress.com,crafterschoice.com,crossings.com,cshlp.org,deseretbook.com,directtextbook.com,discountmags.com,doubledaybookclub.com,doubledaylargeprint.com,doverpublications.com,ebooks.com,ecampus.com,fellabooks.net,fictionwise.com,flatworldknowledge.com,goodreads.com,grolier.com,harpercollins.com,hayhouse.com,historybookclub.com,hpb.com,hpbmarketplace.com,interweave.com,iseeme.com,katiekazoo.com,knetbooks.com,learnoutloud.com,librarything.com,literaryguild.com,lulu.com,lww.com,macmillan.com,magazines.com,mbsdirect.net,militarybookclub.com,mypearsonstore.com,mysteryguild.com,netplaces.com,noble.com,novelguide.com,onespirit.com,oxfordjournals.org,paperbackswap.com,papy.co.jp,peachpit.com,penguin.com,penguingroup.com,pimsleur.com,powells.com,qpb.com,quepublishing.com,reviews.com,rhapsodybookclub.com,rodalestore.com,royalsocietypublishing.org,sagepub.com,scrubsmag.com,sfbc.com,simonandschuster.com,simonandschuster.net,simpletruths.com,teach12.net,textbooks.com,textbookx.com,thegoodcook.com,thriftbooks.com,tlsbooks.com,toshibabookplace.com,tumblebooks.com,urbookdownload.com,usedbooksearch.co.uk,valorebooks.com,valuemags.com,vialibri.net,wwnorton.com,zoobooks.com',
+    'literature' ],
+  [ '53.com,ally.com,bankofamerica.com,bbt.com,bnymellon.com,capitalone.com/bank/,chase.com,citi.com,citibank.com,citizensbank.com,citizensbankonline.com,creditonebank.com,everbank.com,hsbc.com,key.com,pnc.com,pncbank.com,rbs.co.uk,regions.com,sovereignbank.com,suntrust.com,tdbank.com,usaa.com,usbank.com,wachovia.com,wamu.com,wellsfargo.com,wsecu.org',
+    'banking' ],
+  [ '247wallst.com,bizjournals.com,bloomberg.com,businessweek.com,cnbc.com,cnnmoney.com,dowjones.com,easyhomesite.com,economist.com,entrepreneur.com,fastcompany.com,finance.yahoo.com,forbes.com,fortune.com,foxbusiness.com,ft.com,hbr.org,ibtimes.com,inc.com,manta.com,marketwatch.com,newsweek.com,online.wsj.com,qz.com,reuters.com,smartmoney.com,wsj.com',
+    'business news' ],
+  [ 'achievecard.com,americanexpress.com,barclaycardus.com,card.com,citicards.com,comparecards.com,creditcards.citi.com,discover.com,discovercard.com,experian.com,skylightpaycard.com,squareup.com,visa.com,visabuxx.com,visaextras.com',
+    'finance' ],
+  [ 'alliantcreditunion.org,connexuscu.org,lmcu.org,nasafcu.com,navyfcu.org,navyfederal.org,penfed.org,sccu.com,suncoastcreditunion.com,tinkerfcu.org,veridiancu.org',
+    'finance' ],
+  [ 'allbusiness.com,bankrate.com,buyersellertips.com,cboe.com,cnbcprime.com,coindesk.com,dailyfinance.com,dailyfx.com,dealbreaker.com,easierstreetdaily.com,economywatch.com,etfdailynews.com,etfdb.com,financeformulas.net,finviz.com,fool.com,forexpros.com,forexthreads.com,ftpress.com,fx-exchange.com,insidermonkey.com,investmentu.com,investopedia.com,investorjunkie.com,investors.com,kiplinger.com,minyanville.com,moneymorning.com,moneyning.com,moneysavingexpert.com,morningstar.com,nakedcapitalism.com,ncsoft.net,oilprice.com,realclearmarkets.com,rttnews.com,seekingalpha.com,silverdoctors.com,stockcharts.com,stockpickr.com,thefinancials.com,thestreet.com,wallstreetinsanity.com,wikinvest.com,xe.com,youngmoney.com',
+    'investing' ],
+  [ 'edwardjones.com,fidelity.com,goldmansachs.com,jpmorgan.com,ml.com,morganstanley.com,mymerrill.com,personal.vanguard.com,principal.com,schwab.com,schwabplan.com,scottrade.com,tdameritrade.com,troweprice.com,vanguard.com',
+    'investing' ],
+  [ '247lendinggroup.com,americanoneunsecured.com,avant.com,bestegg.com,chasestudentloans.com,eloan.com,gofundme.com,guidetolenders.com,kiva.org,lendacademy.com,lendingclub.com,lendingtree.com,lightstream.com,loanio.com,manageyourloans.com,meetearnest.com,microplace.com,netcredit.com,peer-lend.com,personalloans.com,prosper.com,salliemae.com,sofi.com,springleaf.com,uk.zopa.com,upstart.com',
+    'finance' ],
+  [ 'betterment.com,blooom.com,futureadvisor.com,kapitall.com,motifinvesting.com,personalcapital.com,wealthfront.com,wisebanyan.com',
+    'investing' ],
+  [ 'bancdebinary.com,cherrytrade.com,empireoption.net,etrade.com,firstrade.com,forex.com,interactivebrokers.com,ishares.com,optionsxpress.com,sharebuilder.com,thinkorswim.com,tradeking.com,trademonster.com,us.etrade.com,zecco.com',
+    'finance' ],
+  [ 'annualcreditreport.com,bluebird.com,credio.com,creditkarma.com,creditreport.com,cybersource.com,equifax.com,freecreditreport.com,freecreditscore.com,freedomdebtrelief.com,freescoreonline.com,mint.com,moneymappress.com,myfico.com,nationaldebtrelief.com,onesmartpenny.com,paypal.com,transunion.com,truecredit.com,upromise.com,vuebill.com,xpressbillpay.com,youneedabudget.com',
+    'personal finance' ],
+  [ 'angieslist.com,bloomberg.com,businessinsider.com,buydomains.com,domain.com,entrepreneur.com,fastcompany.com,forbes.com,fortune.com,godaddy.com,inc.com,manta.com,nytimes.com,openforum.com,register.com,salesforce.com,sba.gov,sbomag.com,shopsmall.americanexpress.com,smallbusiness.yahoo.com,squarespace.com,startupjournal.com,startupnation.com,weebly.com,wordpress.com,youngentrepreneur.com',
+    'business news' ],
+  [ '1040now.net,24hourtax.com,acttax.com,comparetaxsoftware.org,e-file.com,etax.com,free1040taxreturn.com,hrblock.com,intuit.com,irstaxdoctors.com,libertytax.com,octaxcol.com,pay1040.com,priortax.com,quickbooks.com,quickrefunds.com,rapidtax.com,refundschedule.com,taxact.com,taxactonline.com,taxefile.com,taxhead.com,taxhelptoday.me,taxsimple.org,turbotax.com',
+    'tax' ],
+  [ 'adeccousa.com,americasjobexchange.com,aoljobs.com,applicantpro.com,applicantstack.com,apply-4-jobs.com,apply2jobs.com,att.jobs,beyond.com,careerboutique.com,careerbuilder.com,careerflash.net,careerslocal.net,climber.com,coverlettersandresume.com,dice.com,diversityonecareers.com,employmentguide.com,everyjobforme.com,experteer.com,find.ly,findtherightjob.com,freelancer.com,gigats.com,glassdoor.com,governmentjobs.com,hrapply.com,hrdepartment.com,hrsmart.com,ihire.com,indeed.com,internships.com,itsmycareer.com,job-applications.com,job-hunt.org,job-interview-site.com,job.com,jobcentral.com,jobdiagnosis.com,jobhat.com,jobing.com,jobrapido.com,jobs.aol.com,jobs.net,jobsbucket.com,jobsflag.com,jobsgalore.com,jobsonline.com,jobsradar.com,jobster.com,jobtorch.com,jobungo.com,jobvite.com,juju.com,linkedin.com,livecareer.com,localjobster.com,mindtools.com,monster.com,myjobhelper.com,myperfectresume.com,payscale.com,pryor.com,quintcareers.com,randstad.com,recruitingcenter.net,resume-library.com,resume-now.com,roberthalf.com,salary.com,salaryexpert.com,simplyhired.com,smartrecruiters.com,snagajob.com,startwire.com,theladders.com,themuse.com,theresumator.com,thingamajob.com,usajobs.gov,ziprecruiter.com',
+    'career services' ],
+  [ 'americanheart.org,americanredcross.com,americares.org,catholiccharitiesusa.org,charitybuzz.com,charitynavigator.org,charitywater.org,directrelief.org,fao.org,habitat.org,hrw.org,imf.org,mskcc.org,ohchr.org,redcross.org,reliefweb.int,salvationarmyusa.org,savethechildren.org,un.org,undp.org,unep.org,unesco.org,unfpa.org,unhcr.org,unicef.org,unicefusa.org,unops.org,volunteermatch.org,wfp.org,who.int,worldbank.org',
+    'philanthropic' ],
+  [ 'academia.edu,albany.edu,american.edu,amity.edu,annauniv.edu,apus.edu,arizona.edu,ashford.edu,asu.edu,auburn.edu,austincc.edu,baylor.edu,bc.edu,berkeley.edu,brandeis.edu,brookings.edu,brown.edu,bu.edu,buffalo.edu,byu.edu,calpoly.edu,calstate.edu,caltech.edu,cam.ac.uk,cambridge.org,capella.edu,case.edu,clemson.edu,cmu.edu,colorado.edu,colostate-pueblo.edu,colostate.edu,columbia.edu,commnet.edu,cornell.edu,cpp.edu,csulb.edu,csun.edu,csus.edu,cuny.edu,cwru.edu,dartmouth.edu,depaul.edu,devry.edu,drexel.edu,du.edu,duke.edu,emory.edu,fau.edu,fcps.edu,fiu.edu,fordham.edu,fsu.edu,fullerton.edu,fullsail.edu,gatech.edu,gcu.edu,georgetown.edu,gmu.edu,gsu.edu,gwu.edu,harvard.edu,hawaii.edu,hbs.edu,iastate.edu,iit.edu,illinois.edu,indiana.edu,iu.edu,jhu.edu,k-state.edu,kent.edu,ku.edu,lamar.edu,liberty.edu,losrios.edu,lsu.edu,luc.edu,maine.edu,maricopa.edu,mass.edu,miami.edu,miamioh.edu,missouri.edu,mit.edu,mnscu.edu,monash.edu,msu.edu,mtu.edu,nau.edu,ncsu.edu,nd.edu,neu.edu,njit.edu,northeastern.edu,northwestern.edu,nova.edu,nyu.edu,odu.edu,ohio-state.edu,ohio.edu,okstate.edu,oregonstate.edu,osu.edu,ou.edu,ox.ac.uk,pdx.edu,pearson.com,phoenix.edu,pitt.edu,princeton.edu,psu.edu,purdue.edu,regis.edu,rice.edu,rit.edu,rochester.edu,rpi.edu,rutgers.edu,sc.edu,scu.edu,sdsu.edu,seattleu.edu,sfsu.edu,si.edu,sjsu.edu,snhu.edu,stanford.edu,stonybrook.edu,suny.edu,syr.edu,tamu.edu,temple.edu,towson.edu,ttu.edu,tufts.edu,ua.edu,uark.edu,ub.edu,uc.edu,uccs.edu,ucdavis.edu,ucf.edu,uchicago.edu,uci.edu,ucla.edu,uconn.edu,ucr.edu,ucsb.edu,ucsc.edu,ucsd.edu,ucsf.edu,udel.edu,udemy.com,ufl.edu,uga.edu,uh.edu,uic.edu,uillinois.edu,uiowa.edu,uiuc.edu,uky.edu,umass.edu,umb.edu,umbc.edu,umd.edu,umich.edu,umn.edu,umuc.edu,unc.edu,uncc.edu,unf.edu,uniminuto.edu,universityofcalifornia.edu,unl.edu,unlv.edu,unm.edu,unt.edu,uoc.edu,uoregon.edu,upc.edu,upenn.edu,upi.edu,uri.edu,usc.edu,usf.edu,usg.edu,usu.edu,uta.edu,utah.edu,utdallas.edu,utexas.edu,utk.edu,uvm.edu,uw.edu,uwm.edu,vanderbilt.edu,vccs.edu,vcu.edu,virginia.edu,vt.edu,waldenu.edu,washington.edu,wayne.edu,wednet.edu,wgu.edu,wisc.edu,wisconsin.edu,wm.edu,wmich.edu,wsu.edu,wustl.edu,wvu.edu,yale.edu',
+    'college' ],
+  [ 'collegeboard.com,collegeconfidential.com,collegeview.com,ecollege.com,finaid.org,find-colleges-now.com,ratemyprofessors.com,ratemyteachers.com,studentsreview.com',
+    'college' ],
+  [ 'actstudent.org,adaptedmind.com,aesoponline.com,archives.com,bibme.org,blackboard.com,bookrags.com,cengage.com,chegg.com,classdojo.com,classzone.com,cliffsnotes.com,coursecompass.com,educationconnection.com,educationdynamics.com,ets.org,familysearch.org,fastweb.com,genealogy.com,gradesaver.com,instructure.com,khanacademy.org,learn4good.com,mathway.com,mathxl.com,mcgraw-hill.com,merriam-webster.com,mheducation.com,niche.com,openstudy.com,pearsoned.com,pearsonmylabandmastering.com,pearsonsuccessnet.com,poptropica.com,powerschool.com,proprofs.com,purplemath.com,quizlet.com,readwritethink.org,renlearn.com,rhymezone.com,schoolloop.com,schoology.com,smithsonianmag.com,sparknotes.com,study.com,studyisland.com,studymode.com,synonym.com,teacherprobs.com,teacherspayteachers.com,tutorvista.com,vocabulary.com,yourschoolmatch.com',
+    'education' ],
+  [ 'browardschools.com,k12.ca.us,k12.fl.us,k12.ga.us,k12.in.us,k12.mn.us,k12.mo.us,k12.nc.us,k12.nj.us,k12.oh.us,k12.va.us,k12.wi.us',
+    'education' ],
+  [ 'coolmath-games.com,coolmath.com,coolmath4kids.com,coolquiz.com,funbrain.com,funtrivia.com,gamesforthebrain.com,girlsgogames.com,hoodamath.com,lumosity.com,math.com,mathsisfun.com,trivia.com,wizard101.com',
+    'learning games' ],
+  [ 'askmen.com,boredomtherapy.com,buzzfeed.com,complex.com,dailymotion.com,elitedaily.com,gawker.com,howstuffworks.com,instagram.com,madamenoire.com,polygon.com,ranker.com,rollingstone.com,ted.com,theblaze.com,thechive.com,thecrux.com,thedailybeast.com,thoughtcatalog.com,uproxx.com,upworthy.com,zergnet.com',
+    'entertainment' ],
+  [ '11points.com,7gid.com,adultswim.com,break.com,cheezburger.com,collegehumor.com,cracked.com,dailydawdle.com,damnlol.com,dumb.com,dumblaws.com,ebaumsworld.com,explosm.net,failblog.org,fun-gallery.com,funnygig.com,funnyjunk.com,funnymama.com,funnyordie.com,funnytear.com,funplus.com,glassgiant.com,goingviralposts.com,gorillamask.net,i-am-bored.com,icanhascheezburger.com,ifunny.com,imjussayin.co,inherentlyfunny.com,izismile.com,jokes.com,keenspot.com,knowyourmeme.com,laughstub.com,memebase.com,mememaker.net,metacafe.com,mylol.com,picslist.com,punoftheday.com,queendom.com,rajnikantvscidjokes.in,regretfulmorning.com,shareonfb.com,somethingawful.com,stupidvideos.com,superfunnyimages.com,thedailywh.at,theonion.com,tosh.comedycentral.com,uberhumor.com,welltimedphotos.com',
+    'humor' ],
+  [ 'air.tv,amctheatres.com,boxofficemojo.com,cinapalace.com,cinaplay.com,cinemablend.com,cinemark.com,cinematical.com,collider.com,comicbookmovie.com,comingsoon.net,crackle.com,denofgeek.us,dreamworks.com,empireonline.com,enstarz.com,fandango.com,filmschoolrejects.com,flickeringmyth.com,flixster.com,fullmovie2k.com,g2g.fm,galleryhip.com,hollywood.com,hollywoodreporter.com,iglomovies.com,imdb.com,indiewire.com,instantwatcher.com,joblo.com,kickass.to,kissdrama.net,marcustheatres.com,megashare9.com,moviefone.com,movieinsider.com,moviemistakes.com,moviepilot.com,movierulz.com,movies.com,movies.yahoo.com,movieseum.com,movietickets.com,movieweb.com,mrmovietimes.com,mymovieshub.com,netflix.com,onlinemovies.pro,pelis24.com,projectfreetv.ch,redbox.com,regmovies.com,repelis.tv,rogerebert.suntimes.com,ropeofsilicon.com,rottentomatoes.com,sidereel.com,slashfilm.com,solarmovie.is,starwars.com,superherohype.com,tcm.com,twomovies.us,variety.com,vimeo.com,viooz.ac,warnerbros.com,watchfree.to,wbredirect.com,youtubeonfire.com,zmovie.tw,zumvo.com',
+    'movie' ],
+  [ '1079ishot.com,2dopeboyz.com,8tracks.com,acdc.com,allaccess.com,allhiphop.com,allmusic.com,audiofanzine.com,audiomack.com,azlyrics.com,baeblemusic.com,bandsintown.com,billboard.com,brooklynvegan.com,brunomars.com,buzznet.com,cmt.com,coachella.com,consequenceofsound.net,contactmusic.com,countryweekly.com,dangerousminds.net,datpiff.com,ddotomen.com,diffuser.fm,directlyrics.com,djbooth.net,eventful.com,fireflyfestival.com,genius.com,guitartricks.com,harmony-central.com,hiphopdx.com,hiphopearly.com,hypem.com,idolator.com,iheart.com,jambase.com,kanyetothe.com,knue.com,lamusica.com,last.fm,livemixtapes.com,loudwire.com,lyricinterpretations.com,lyrics.net,lyricsbox.com,lyricsmania.com,lyricsmode.com,metal-archives.com,metrolyrics.com,mp3.com,mtv.co.uk,myspace.com,newnownext.com,noisetrade.com,okayplayer.com,pandora.com,phish.com,pigeonsandplanes.com,pitchfork.com,popcrush.com,radio.com,rap-up.com,rdio.com,reverbnation.com,revolvermag.com,rockhall.com,saavn.com,songlyrics.com,soundcloud.com,spin.com,spinrilla.com,spotify.com,stereogum.com,stereotude.com,talkbass.com,tasteofcountry.com,thebacklot.com,theboombox.com,theboot.com,thissongissick.com,tunesbaby.com,ultimate-guitar.com,ultimateclassicrock.com,vevo.com,vibe.com,vladtv.com,whosampled.com,wikibit.me,worldstarhiphop.com,wyrk.com,xxlmag.com',
+    'music' ],
+  [ 'aceshowbiz.com,aintitcoolnews.com,allkpop.com,askkissy.com,atraf.co.il,audioboom.com,beamly.com,beyondhollywood.com,blastr.com,blippitt.com,bollywoodlife.com,bossip.com,buzzlamp.com,buzzsugar.com,cambio.com,celebdirtylaundry.com,celebrity-gossip.net,celebuzz.com,chisms.net,comicsalliance.com,concertboom.com,crushable.com,cultbox.co.uk,dailyentertainmentnews.com,dayscafe.com,deadline.com,deathandtaxesmag.com,diaryofahollywoodstreetking.com,digitalspy.com,dlisted.com,egotastic.com,empirenews.net,enelbrasero.com,eonline.com,etonline.com,ew.com,extratv.com,facade.com,famousfix.com,fanaru.com,fanpop.com,fansshare.com,fhm.com,geektyrant.com,glamourpage.com,gossipcenter.com,gossipcop.com,heatworld.com,hlntv.com,hollyscoop.com,hollywoodlife.com,hollywoodtuna.com,hypable.com,infotransfer.net,insideedition.com,interaksyon.com,jezebel.com,justjared.buzznet.com,justjared.com,justjaredjr.com,komando.com,koreaboo.com,laineygossip.com,maxgo.com,maxim.com,maxviral.com,mediatakeout.com,mosthappy.com,moviestalk.com,my.ology.com,nationalenquirer.com,necolebitchie.com,ngoisao.net,nofilmschool.com,nolocreo.com,octane.tv,okmagazine.com,ouchpress.com,people.com,peopleenespanol.com,perezhilton.com,pinkisthenewblog.com,platotv.tv,playbill.com,playbillvault.com,playgroundmag.net,popsugar.com,purepeople.com,radaronline.com,rantchic.com,realitytea.com,reshareworthy.com,rinkworks.com,ripbird.com,sara-freder.com,screencrush.com,screenjunkies.com,soapcentral.com,soapoperadigest.com,sobadsogood.com,socialitelife.com,sourcefednews.com,splitsider.com,starcasm.net,starmagazine.com,starpulse.com,straightfromthea.com,stupiddope.com,tbn.org,theawesomedaily.com,theawl.com,thefrisky.com,thefw.com,thehollywoodgossip.com,theresacaputo.com,thesuperficial.com,thezooom.com,tmz.com,tvnotas.com.mx,twanatells.com,usmagazine.com,vanityfair.com,vanswarpedtour.com,vietgiaitri.com,viral.buzz,vulture.com,wakavision.com,worthytales.net,wwtdd.com',
+    'entertainment news' ],
+  [ 'abc.go.com,abcfamily.go.com,abclocal.go.com,accesshollywood.com,aetv.com,amctv.com,animalplanet.com,bbcamerica.com,bet.com,biography.com,bravotv.com,cartoonnetwork.com,cbn.com,cbs.com,cc.com,centrictv.com,cinemax.com,comedycentral.com,ctv.ca,cwtv.com,daytondailynews.com,drphil.com,dsc.discovery.com,fox.com,fox23.com,fox4news.com,fxnetworks.com,hbo.com,history.com,hulu.com,ifc.com,iqiyi.com,jeopardy.com,kfor.com,logotv.com,mtv.com,myfoxchicago.com,myfoxdc.com,myfoxmemphis.com,myfoxphilly.com,nbc.com,nbcchicago.com,oxygen.com,pbs.org,pbskids.org,rachaelrayshow.com,rtve.es,scifi.com,sho.com,showtimeanytime.com,spike.com,sundance.tv,syfy.com,tbs.com,teamcoco.com,telemundo.com,thedoctorstv.com,titantv.com,tlc.com,tlc.discovery.com,tnt.tv,tntdrama.com,tv.com,tvguide.com,tvseriesfinale.com,usanetwork.com,uvidi.com,vh1.com,viki.com,watchcartoononline.com,watchseries-online.ch,wetv.com,wheeloffortune.com,whio.com,wnep.com,wral.com,wtvr.com,xfinitytv.com,yidio.com',
+    'TV show' ],
+  [ 'americanhiking.org,appalachiantrail.org,canadiangeographic.ca,defenders.org,discovermagazine.com,discoveroutdoors.com,dsc.discovery.com,earthtouchnews.com,edf.org,epa.gov,ewg.org,fishngame.org,foe.org,fs.fed.us,geography.about.com,landtrustalliance.org,nationalgeographic.com,nature.com,nrdc.org,nwf.org,outdoorchannel.com,outdoors.org,seedmagazine.com,trcp.org,usda.gov,worldwildlife.org',
+    'environment' ],
+  [ 'abbreviations.com,abcmouse.com,abcya.com,achieve3000.com,ancestry.com,animaljam.com,babble.com,babycenter.com,babynamespedia.com,behindthename.com,bestmomstv.com,brainyquote.com,cafemom.com,citationmachine.net,clubpenguin.com,cutemunchkins.com,discoveryeducation.com,disney.com,easybib.com,education.com,enotes.com,everydayfamily.com,familyeducation.com,gamefaqs.com,greatschools.org,hrw.com,imvu.com,infoplease.com,itsybitsysteps.com,justmommies.com,k12.com,kidsactivitiesblog.com,mathwarehouse.com,mom.me,mom365.com,mommyshorts.com,momswhothink.com,momtastic.com,monsterhigh.com,myheritage.com,nameberry.com,nickmom.com,pampers.com,parenthood.com,parenting.com,parenting.ivillage.com,parents.com,parentsociety.com,raz-kids.com,regentsprep.org,scarymommy.com,scholastic.com,shmoop.com,softschools.com,spanishdict.com,starfall.com,thebump.com,thefreedictionary.com,thenest.com,thinkbabynames.com,todaysparent.com,webkinz.com,whattoexpect.com',
+    'family' ],
+  [ 'americangirl.com,barbie.com,barbiecollectibles.com,cartoonnetworkshop.com,chuckecheese.com,coloring.ws,disney.co.uk,disney.com.au,disney.go.com,disney.in,disneychannel-asia.com,disneyinternational.com,disneyjunior.com,disneylatino.com,disneyme.com,dltk-kids.com,dressupone.com,fantage.com,funbrainjr.com,hotwheels.com,icarly.com,kiwicrate.com,marvel.com,marvelkids.com,mattelgames.com,maxsteel.com,monkeyquest.com,nick-asia.com,nick.co.uk,nick.com,nick.tv,nickelodeon.com.au,nickjr.co.uk,nickjr.com,ninjakiwi.com,notdoppler.com,powerrangers.com,sciencekids.co.nz,search.disney.com,seventeen.com,teennick.com,theslap.com,yepi.com',
+    'family' ],
+  [ 'alabama.gov,archives.gov,bls.gov,ca.gov,cancer.gov,cdc.gov,census.gov,cia.gov,cms.gov,commerce.gov,ct.gov,delaware.gov,dhs.gov,doi.gov,dol.gov,dot.gov,ed.gov,eftps.gov,epa.gov,fbi.gov,fda.gov,fema.gov,flhsmv.gov,ftc.gov,ga.gov,georgia.gov,gpo.gov,hhs.gov,house.gov,hud.gov,illinois.gov,in.gov,irs.gov,justice.gov,ky.gov,loc.gov,louisiana.gov,maryland.gov,mass.gov,michigan.gov,mo.gov,nih.gov,nj.gov,nps.gov,ny.gov,nyc.gov,ohio.gov,ok.gov,opm.gov,oregon.gov,pa.gov,recreation.gov,sba.gov,sc.gov,sec.gov,senate.gov,state.fl.us,state.gov,state.il.us,state.ma.us,state.mi.us,state.mn.us,state.nc.us,state.ny.us,state.oh.us,state.pa.us,studentloans.gov,telldc.com,texas.gov,tn.gov,travel.state.gov,tsa.gov,usa.gov,uscis.gov,uscourts.gov,usda.gov,usdoj.gov,usembassy.gov,usgs.gov,utah.gov,va.gov,virginia.gov,wa.gov,whitehouse.gov,wi.gov,wisconsin.gov',
+    'government' ],
+  [ 'beachbody.com,bodybuilding.com,caloriecount.com,extremefitness.com,fitbit.com,fitday.com,fitnessmagazine.com,fitnessonline.com,fitwatch.com,livestrong.com,maxworkouts.com,mensfitness.com,menshealth.com,muscleandfitness.com,muscleandfitnesshers.com,myfitnesspal.com,shape.com,womenshealthmag.com',
+    'health & fitness' ],
+  [ 'activebeat.com,alliancehealth.com,beyonddiet.com,caring.com,complete-health-and-happiness.com,diabeticconnect.com,doctoroz.com,everydayhealth.com,followmyhealth.com,greatist.com,health.com,healthboards.com,healthcaresource.com,healthgrades.com,healthguru.com,healthination.com,healthtap.com,helpguide.org,iherb.com,kidshealth.org,lifescript.com,lovelivehealth.com,medicaldaily.com,mercola.com,perfectorigins.com,prevention.com,qualityhealth.com,questdiagnostics.com,realself.com,sharecare.com,sparkpeople.com,spryliving.com,steadyhealth.com,symptomfind.com,ucomparehealthcare.com,vitals.com,webmd.com,weightwatchers.com,wellness.com,zocdoc.com',
+    'health & wellness' ],
+  [ 'aetna.com,anthem.com,athenahealth.com,bcbs.com,bluecrossca.com,cigna.benefitnation.net,cigna.com,cigna.healthplan.com,ehealthcare.com,ehealthinsurance.com,empireblue.com,goldenrule.com,healthcare.gov,healthnet.com,humana-medicare.com,humana.com,kaiserpermanente.org,metlife.com,my.cigna.com,mybenefits.metlife.com,myuhc.com,uhc.com,unitedhealthcareonline.com,walterrayholt.com',
+    'health insurance' ],
+  [ 'aafp.org,americanheart.org,apa.org,cancer.org,cancercenter.com,caremark.com,clevelandclinic.org,diabetesfree.org,drugs.com,emedicinehealth.com,express-scripts.com,familydoctor.org,goodrx.com,healthcaremagic.com,healthfinder.gov,healthline.com,ieee.org,intelihealth.com,labcorp.com,livecellresearch.com,mayoclinic.com,mayoclinic.org,md.com,medcohealth.com,medhelp.org,medicalnewstoday.com,medicare.gov,medicaresupplement.com,medicinenet.com,medscape.com,memorialhermann.org,merckmanuals.com,patient.co.uk,psychcentral.com,psychology.org,psychologytoday.com,rightdiagnosis.com,rxlist.com,socialpsychology.org,spine-health.com,who.int',
+    'health & wellness' ],
+  [ 'aaa.com,aig.com,allianz-assistance.com,allstate.com,allstateagencies.com,amfam.com,amica.com,autoquotesdirect.com,esurance.com,farmers.com,farmersagent.com,geico.com,general-car-insurance-quotes.net,insurance.com,libertymutual.com,libertymutualgroup.com,mercuryinsurance.com,nationwide.com,progressive.com,progressiveagent.com,progressiveinsurance.com,provide-insurance.com,safeco.com,statefarm.com,thehartford.com,travelers.com,usaa.com',
+    'insurance' ],
+  [ '101cookbooks.com,allrecipes.com,bettycrocker.com,bonappetit.com,chocolateandzucchini.com,chow.com,chowhound.chow.com,cookinglight.com,cooks.com,cooksillustrated.com,cooksrecipes.com,delish.com,eater.com,eatingwell.com,epicurious.com,food.com,foodandwine.com,foodgawker.com,foodnetwork.com,gourmet.com,grouprecipes.com,homemaderecipes.co,iheartnaptime.net,kraftfoods.com,kraftrecipes.com,myrecipes.com,opentable.com,pillsbury.com,recipe.com,recipesource.com,recipezaar.com,saveur.com,seriouseats.com,simplyrecipes.com,smittenkitchen.com,southernliving.com,supercook.com,tasteofhome.com,tastespotting.com,technicpack.net,thekitchn.com,urbanspoon.com,wonderhowto.com,yelp.com,yummly.com,zagat.com',
+    'food & lifestyle' ],
+  [ 'aarp.org,allure.com,bustle.com,cosmopolitan.com,diply.com,eharmony.com,elle.com,glamour.com,grandascent.com,harpersbazaar.com,hellogiggles.com,instructables.com,instyle.com,marieclaire.com,match.com,mindbodygreen.com,nymag.com,okcupid.com,petco.com,photobucket.com,pof.com,rantlifestyle.com,redbookmag.com,reddit.com,sheknows.com,style.com,stylebistro.com,theilovedogssite.com,theknot.com,thescene.com,thrillist.com,vogue.com,womansday.com,youngcons.com,yourdictionary.com',
+    'lifestyle' ],
+  [ 'apartmentratings.com,apartmenttherapy.com,architectmagazine.com,architecturaldigest.com,askthebuilder.com,bhg.com,bobvila.com,countryhome.com,countryliving.com,davesgarden.com,decor8blog.com,decorpad.com,diycozyhome.com,diyideas.com,diynetwork.com,doityourself.com,domainehome.com,dwell.com,elledecor.com,familyhandyman.com,frontdoor.com,gardenguides.com,gardenweb.com,getdecorating.com,goodhousekeeping.com,hgtv.com,hgtvgardens.com,hobbylobby.com,homeadvisor.com,homerepair.about.com,hometalk.com,hometime.com,hometips.com,housebeautiful.com,houzz.com,inhabitat.com,lonny.com,makingitlovely.com,marthastewart.com,michaels.com,myhomeideas.com,realsimple.com,remodelista.com,shanty-2-chic.com,styleathome.com,thehandmadehome.net,thehealthyhomeeconomist.com,thisoldhouse.com,traditionalhome.com,trulia.com,younghouselove.com',
+    'home & lifestyle' ],
+  [ '10best.com,10tv.com,11alive.com,19actionnews.com,9news.com,abcnews.com,abcnews.go.com,adweek.com,ajc.com,anchorfree.us,arcamax.com,austin360.com,azcentral.com,bbc.co.uk,boston.com,bostonglobe.com,capecodonline.com,cbsnews.com,cheatsheet.com,chicagotribune.com,chron.com,citylab.com,cnn.com,csmonitor.com,dailyitem.com,dailymail.co.uk,dallasnews.com,eleconomista.es,examiner.com,fastcolabs.com,fivethirtyeight.com,foursquare.com,foxcarolina.com,foxnews.com,globalnews.ca,greatergood.com,guardian.co.uk,historynet.com,huffingtonpost.co.uk,huffingtonpost.com,ijreview.com,independent.co.uk,journal-news.com,kare11.com,kcra.com,kctv5.com,kgw.com,khou.com,king5.com,kirotv.com,kitv.com,kmbc.com,knoxnews.com,kpho.com,kptv.com,kron4.com,ksdk.com,ksl.com,ktvb.com,kvue.com,kxan.com,latimes.com,lifehack.org,littlethings.com,mailtribune.com,mic.com,mirror.co.uk,msn.com,msnbc.com,msnbc.msn.com,myfoxboston.com,nbcnews.com,nbcnewyork.com,newburyportnews.com,news.bbc.co.uk,news.yahoo.com,news12.com,newschannel10.com,newsday.com,newser.com,newsmax.com,newyorker.com,nj.com,nj1015.com,npr.org,nydailynews.com,nypost.com,nytimes.com,palmbeachpost.com,patch.com,philly.com,phys.org,poconorecord.com,prnewswire.com,rare.us,realclearworld.com,record-eagle.com,richmond.com,rt.com,salemnews.com,salon.com,sfgate.com,slate.com,statesman.com,suntimes.com,takepart.com,telegraph.co.uk,theatlantic.com,thedailystar.com,theguardian.com,theroot.com,theverge.com,time.com,timesonline.co.uk,topix.com,usatoday.com,usatoday30.usatoday.com,usnews.com,vice.com,vox.com,wane.com,washingtonpost.com,washingtontimes.com,wave3.com,wavy.com,wbaltv.com,wbir.com,wcnc.com,wdbj7.com,westernjournalism.com,wfaa.com,wfsb.com,wftv.com,wgal.com,wishtv.com,wisn.com,wistv.com,wivb.com,wkyc.com,wlwt.com,wmur.com,woodtv.com,wpxi.com,wsbtv.com,wsfa.com,wsmv.com,wsoctv.com,wthr.com,wtnh.com,wtsp.com,wwltv.com,wyff4.com,wzzm13.com',
+    'news' ],
+  [ 'aei.org,breitbart.com,conservativetalknow.com,conservativetribune.com,dailykos.com,ddo.com,drudgereport.com,dscc.org,foreignpolicy.com,franklinprosperityreport.com,freedomworks.org,macleans.ca,mediamatters.org,militarytimes.com,nationaljournal.com,nationalreview.com,politicalwire.com,politico.com,pressrepublican.com,qpolitical.com,realclearpolitics.com,talkingpointsmemo.com,thehill.com,thenation.com,thinkprogress.org,tnr.com,worldoftanks.eu',
+    'news' ],
+  [ 'americanscientist.org,discovermagazine.com,iflscience.com,livescience.com,nasa.gov,nationalgeographic.com,nature.com,newscientist.com,popsci.com,sciencedaily.com,sciencemag.org,sciencenews.org,scientificamerican.com,space.com,zmescience.com',
+    'science' ],
+  [ 'accuweather.com,intellicast.com,noaa.gov,ssa.gov,theweathernetwork.com,weather.com,weather.gov,weather.yahoo.com,weatherbug.com,weatherunderground.com,weatherzone.com.au,wunderground.com,www.weather.com',
+    'weather' ],
+  [ 'bhphotovideo.com,bigfolio.com,bigstockphoto.com,cameralabs.com,canonrumors.com,canstockphoto.com,digitalcamerareview.com,dpreview.com,expertphotography.com,gettyimages.com,icp.org,imaging-resource.com,intothedarkroom.com,istockphoto.com,nikonusa.com,photos.com,shutterstock.com,slrgear.com,the-digital-picture.com,thephotoargus.com,usa.canon.com,whatdigitalcamera.com,zenfolio.com',
+    'photography' ],
+  [ 'abercrombie.com,ae.com,aeropostale.com,anthropologie.com,bananarepublic.com,buycostumes.com,chadwicks.com,express.com,forever21.com,freepeople.com,hm.com,hollisterco.com,jcrew.com,jessicalondon.com,kingsizemen.com,lordandtaylor.com,lulus.com,metrostyle.com,nomorerack.com,oldnavy.com,oldnavy.gap.com,polyvore.com,rackroomshoes.com,ralphlauren.com,refinery29.com,roamans.com,sammydress.com,shop.nordstrom.com,shopbop.com,topshop.com,urbanoutfitters.com,victoriassecret.com,wetseal.com,womanwithin.com',
+    'shopping' ],
+  [ 'bizrate.com,compare99.com,coupons.com,dealtime.com,epinions.com,junglee.com,kijiji.ca,pricegrabber.com,pronto.com,redplum.com,retailmenot.com,shopping.com,shopzilla.com,smarter.com,valpak.com',
+    'shopping' ],
+  [ '123greetings.com,1800baskets.com,1800flowers.com,americangreetings.com,birthdayexpress.com,bluemountain.com,e-cards.com,egreetings.com,florists.com,ftd.com,gifts.com,groupcard.com,harryanddavid.com,hipstercards.com,kabloom.com,personalcreations.com,proflowers.com,redenvelope.com,someecards.com',
+    'flowers & gifts' ],
+  [ '6pm.com,alibaba.com,aliexpress.com,amazon.co.uk,amazon.com,asos.com,bathandbodyworks.com,bloomingdales.com,bradsdeals.com,buy.com,cafepress.com,circuitcity.com,clarkhoward.com,consumeraffairs.com,costco.com,cvs.com,dhgate.com,diapers.com,dillards.com,ebates.com,ebay.com,ebaystores.com,etsy.com,fingerhut.com,groupon.com,hsn.com,jcpenney.com,kmart.com,kohls.com,kroger.com,lowes.com,macys.com,menards.com,nextag.com,nordstrom.com,orientaltrading.com,overstock.com,qvc.com,racked.com,rewardsnetwork.com,samsclub.com,sears.com,sephora.com,shopathome.com,shopify.com,shopstyle.com,slickdeals.net,soap.com,staples.com,target.com,toptenreviews.com,vistaprint.com,walgreens.com,walmart.ca,walmart.com,wayfair.com,zappos.com,zazzle.com,zulily.com',
+    'shopping' ],
+  [ 'acehardware.com,ashleyfurniture.com,bedbathandbeyond.com,brylanehome.com,casa.com,cb2.com,crateandbarrel.com,dwr.com,ethanallen.com,furniture.com,harborfreight.com,hayneedle.com,homedecorators.com,homedepot.com,ikea.com,info.ikea-usa.com,landofnod.com,pier1.com,plowhearth.com,potterybarn.com,restorationhardware.com,roomandboard.com,westelm.com,williams-sonoma.com',
+    'home shopping' ],
+  [ 'alexandermcqueen.com,bergdorfgoodman.com,bottegaveneta.com,burberry-bluelabel.com,burberry.com,chanel.com,christianlouboutin.com,coach.com,diesel.com,dior.com,dolcegabbana.com,dolcegabbana.it,fendi.com,ferragamo.com,giorgioarmani.com,givenchy.com,gucci.com,guess.com,hermes.com,jeanpaulgaultier.com,jimmychoo.com,juicycouture.com,katespade.com,louisvuitton.com,manoloblahnik.com,marcjacobs.com,neimanmarcus.com,net-a-porter.com,paulsmith.co.uk,prada.com,robertocavalli.com,saksfifthavenue.com,toryburch.com,valentino.com,versace.com,vuitton.com,ysl.com,yslbeautyus.com',
+    'luxury shopping' ],
+  [ 'bargainseatsonline.com,livenation.com,stubhub.com,ticketfly.com,ticketliquidator.com,ticketmaster.com,tickets.com,ticketsnow.com,ticketweb.com,vividseats.com',
+    'events & tickets' ],
+  [ 'babiesrus.com,brothers-brick.com,etoys.com,fao.com,fisher-price.com,hasbro.com,hasbrotoyshop.com,lego.com,legoland.com,mattel.com,toys.com,toysrus.com,toystogrowon.com,toywiz.com',
+    'toys & games' ],
+  [ 'challengegames.nfl.com,fantasy.nfl.com,fantasyfootballblog.net,fantasyfootballcafe.com,fantasyfootballnerd.com,fantasysmarts.com,fftoday.com,fftoolbox.com,football.fantasysports.yahoo.com,footballsfuture.com,mrfootball.com,officefootballpool.com,thehuddle.com',
+    'fantasy football' ],
+  [ 'dailyjoust.com,draftday.com,draftking.com,draftkings.com,draftstreet.com,fanduel.com,realmoneyfantasyleagues.com,thedailyaudible.com',
+    'fantasy sports' ],
+  [ 'cdmsports.com,fanball.com,fantasyguru.com,fantasynews.cbssports.com,fantasyquestions.com,fantasyrundown.com,fantasysharks.com,fantasysports.yahoo.com,fantazzle.com,fantrax.com,fleaflicker.com,junkyardjake.com,kffl.com,mockdraftcentral.com,myfantasyleague.com,rototimes.com,rotowire.com,rotoworld.com,rtsports.com,whatifsports.com',
+    'fantasy sports' ],
+  [ 'football.about.com,football.com,footballoutsiders.com,nationalfootballpost.com,nflalumni.org,nflpa.com,nfltraderumors.co,profootballhof.com,profootballtalk.com,profootballtalk.nbcsports.com,profootballweekly.com',
+    'football' ],
+  [ '49ers.com,atlantafalcons.com,azcardinals.com,baltimoreravens.com,bengals.com,buccaneers.com,buffalobills.com,chargers.com,chicagobears.com,clevelandbrowns.com,colts.com,dallascowboys.com,denverbroncos.com,detroitlions.com,giants.com,houstontexans.com,jaguars.com,kcchiefs.com,miamidolphins.com,neworleanssaints.com,newyorkjets.com,packers.com,panthers.com,patriots.com,philadelphiaeagles.com,raiders.com,redskins.com,seahawks.com,steelers.com,stlouisrams.com,titansonline.com,vikings.com',
+    'football' ],
+  [ 'baseball-reference.com,baseballamerica.com,europeantour.com,golf.com,golfdigest.com,lpga.com,milb.com,minorleagueball.com,mlb.com,mlb.mlb.com,nascar.com,nba.com,ncaa.com,nhl.com,pga.com,pgatour.com,prowrestling.com,surfermag.com,surfline.com,surfshot.com,thehockeynews.com,tsn.com,ufc.com,worldgolfchampionships.com,wwe.com',
+    'sports' ],
+  [ '247sports.com,active.com,armslist.com,basketball-reference.com,bigten.org,bleacherreport.com,bleedinggreennation.com,bloodyelbow.com,cagesideseats.com,cbssports.com,cinesport.com,collegespun.com,cricbuzz.com,crictime.com,csnphilly.com,csnwashington.com,cstv.com,eastbay.com,espn.com,espn.go.com,espncricinfo.com,espnfc.com,espnfc.us,espnradio.com,eteamz.com,fanatics.com,fansided.com,fbschedules.com,fieldandstream.com,flightclub.com,foxsports.com,givemesport.com,goduke.com,goheels.com,golfchannel.com,golfnow.com,grantland.com,grindtv.com,hoopshype.com,icc-cricket.com,imleagues.com,kentuckysportsradio.com,larrybrownsports.com,leaguelineup.com,maxpreps.com,mlbtraderumors.com,mmafighting.com,mmajunkie.com,mmamania.com,msn.foxsports.com,myscore.com,nbcsports.com,nbcsports.msnbc.com,nesn.com,rantsports.com,realclearsports.com,reserveamerica.com,rivals.com,runnersworld.com,sbnation.com,scout.com,sherdog.com,si.com,speedsociety.com,sportingnews.com,sports.yahoo.com,sportsillustrated.cnn.com,sportsmanias.com,sportsmonster.us,sportsonearth.com,stack.com,teamworkonline.com,thebiglead.com,thescore.com,trails.com,triblive.com,upickem.net,usatodayhss.com,watchcric.net,yardbarker.com',
+    'sports news' ],
+  [ 'adidas.com,backcountry.com,backcountrygear.com,cabelas.com,champssports.com,competitivecyclist.com,dickssportinggoods.com,finishline.com,footlocker.com,ladyfootlocker.com,modells.com,motosport.com,mountaingear.com,newbalance.com,nike.com,patagonia.com,puma.com,reebok.com,sportsmansguide.com,steepandcheap.com,tgw.com,thenorthface.com',
+    'sports & outdoor goods' ],
+  [ 'airdroid.com,android-developers.blogspot.com,android.com,androidandme.com,androidapplications.com,androidapps.com,androidauthority.com,androidcommunity.com,androidfilehost.com,androidforums.com,androidguys.com,androidheadlines.com,androidpit.com,androidspin.com,androidtapp.com,androinica.com,droid-life.com,droidforums.net,droidviews.com,droidxforums.com,forum.xda-developers.com,phandroid.com,play.google.com,shopandroid.com,talkandroid.com,theandroidsoul.com,thedroidguy.com,videodroid.org',
+    'technology' ],
+  [ '9to5mac.com,appadvice.com,apple.com,appleinsider.com,appleturns.com,appsafari.com,cultofmac.com,everymac.com,insanelymac.com,iphoneunlockspot.com,isource.com,itunes.apple.com,lowendmac.com,mac-forums.com,macdailynews.com,macenstein.com,macgasm.net,macintouch.com,maclife.com,macnews.com,macnn.com,macobserver.com,macosx.com,macpaw.com,macrumors.com,macsales.com,macstories.net,macupdate.com,macuser.co.uk,macworld.co.uk,macworld.com,maxiapple.com,spymac.com,theapplelounge.com',
+    'technology' ],
+  [ 'adobe.com,asus.com,avast.com,data.com,formstack.com,gboxapp.com,gotomeeting.com,hp.com,htc.com,ibm.com,intel.com,java.com,logme.in,mcafee.com,mcafeesecure.com,microsoftstore.com,norton.com,office.com,office365.com,opera.com,oracle.com,proboards.com,samsung.com,sourceforge.net,squarespace.com,techtarget.com,ultipro.com,uniblue.com,web.com,winzip.com',
+    'technology' ],
+  [ '3dprint.com,4sysops.com,access-programmers.co.uk,accountingweb.com,afterdawn.com,akihabaranews.com,appsrumors.com,avg.com,belkin.com,besttechinfo.com,betanews.com,botcrawl.com,breakingmuscle.com,cheap-phones.com,chip.de,chip.eu,citeworld.com,cleanpcremove.com,commentcamarche.net,computer.org,computerhope.com,computershopper.com,computerweekly.com,contextures.com,coolest-gadgets.com,csoonline.com,daniweb.com,datacenterknowledge.com,ddj.com,devicemag.com,digitaltrends.com,dottech.org,dslreports.com,edugeek.net,eetimes.com,epic.com,eurekalert.org,eweek.com,experts-exchange.com,fosshub.com,freesoftwaremagazine.com,funkyspacemonkey.com,futuremark.com,gadgetreview.com,gizmodo.co.uk,globalsecurity.org,gunup.com,guru3d.com,head-fi.org,hexus.net,hothardware.com,howtoforge.com,idg.com.au,idownloadblog.com,ihackmyi.com,ilounge.com,infomine.com,intellireview.com,intomobile.com,iphonehacks.com,ismashphone.com,it168.com,itechpost.com,itpro.co.uk,jailbreaknation.com,laptoping.com,lightreading.com,malwaretips.com,mediaroom.com,mobilemag.com,modmyi.com,modmymobile.com,mophie.com,mozillazine.org,neoseeker.com,neowin.net,newsoxy.com,nextadvisor.com,notebookcheck.com,notebookreview.com,nvidia.com,orafaq.com,osdir.com,osxdaily.com,our-hometown.com,pchome.net,pconline.com.cn,pcpop.com,pcpro.co.uk,pcreview.co.uk,pcrisk.com,pcwelt.de,phonerebel.com,phonescoop.com,physorg.com,pocket-lint.com,post-theory.com,prnewswire.co.uk,programming4.us,quickpwn.com,redmondpie.com,redorbit.com,safer-networking.org,scientificblogging.com,sciverse.com,servicerow.com,sinfuliphone.com,singularityhub.com,slashgear.com,softonic.com,softonic.com.br,softonic.fr,sophos.com,sparkfun.com,speedguide.net,stuff.tv,symantec.com,taplikahome.com,techdailynews.net,techeblog.com,techie-buzz.com,techniqueworld.com,technobuffalo.com,technologyreview.com,technologytell.com,techpowerup.com,techpp.com,techradar.com,techshout.com,techworld.com,techworld.com.au,techworldtweets.com,telecomfile.com,tgdaily.com,theinquirer.net,thenextweb.com,theregister.co.uk,thermofisher.com,thewindowsclub.com,tomsitpro.com,trustedreviews.com,tuaw.com,tweaktown.com,unwiredview.com,wccftech.com,webmonkey.com,webpronews.com,windows7codecs.com,windowscentral.com,windowsitpro.com,windowstechies.com,winsupersite.com,wired.co.uk,wp-themes.com,xml.com,zol.com.cn',
+    'technology' ],
   [ 'addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org',
     'Mozilla' ],
-  [ '3dprint.com,4sysops.com,access-programmers.co.uk,accountingweb.com,addictivetips.com,adweek.com,afterdawn.com,akihabaranews.com,anandtech.com,appsrumors.com,arstechnica.com,belkin.com,besttechinfo.com,betanews.com,bgr.com,botcrawl.com,breakingmuscle.com,canonrumors.com,cheap-phones.com,chip.de,chip.eu,cio.com,citeworld.com,cleanpcremove.com,cnet.com,commentcamarche.net,computer.org,computerhope.com,computershopper.com,computerweekly.com,contextures.com,coolest-gadgets.com,crn.com,csoonline.com,daniweb.com,data.com,datacenterknowledge.com,ddj.com,devicemag.com,digitaltrends.com,dottech.org,dpreview.com,dslreports.com,edugeek.net,eetimes.com,engadget.com,epic.com,eurekalert.org,eweek.com,experts-exchange.com,extremetech.com,fosshub.com,freesoftwaremagazine.com,funkyspacemonkey.com,futuremark.com,gadgetreview.com,ghacks.net,gizmodo.co.uk,gizmodo.com,globalsecurity.org,greenbot.com,gunup.com,guru3d.com,head-fi.org,hexus.net,hothardware.com,howtoforge.com,idg.com.au,idigitaltimes.com,idownloadblog.com,ihackmyi.com,ilounge.com,infomine.com,informationweek.com,intellireview.com,intomobile.com,iphonehacks.com,ismashphone.com,isource.com,it168.com,itechpost.com,itpro.co.uk,itworld.com,jailbreaknation.com,kioskea.net,laptoping.com,laptopmag.com,lightreading.com,livescience.com,malwaretips.com,mediaroom.com,mobilemag.com,modmyi.com,modmymobile.com,mophie.com,mozillazine.org,neoseeker.com,neowin.net,newscientist.com,newsoxy.com,nextadvisor.com,notebookcheck.com,notebookreview.com,nvidia.com,nwc.com,orafaq.com,osdir.com,osxdaily.com,our-hometown.com,pcadvisor.co.uk,pchome.net,pcmag.com,pconline.com.cn,pcpop.com,pcpro.co.uk,pcreview.co.uk,pcrisk.com,pcwelt.de,phonerebel.com,phonescoop.com,physorg.com,pocket-lint.com,post-theory.com,prnewswire.co.uk,prnewswire.com,programming4.us,quickpwn.com,readwrite.com,redmondpie.com,redorbit.com,reviewed.com,safer-networking.org,sciencedaily.com,sciencenews.org,scientificamerican.com,scientificblogging.com,sciverse.com,servicerow.com,sinfuliphone.com,singularityhub.com,slashdot.org,slashgear.com,softonic.com,softonic.com.br,softonic.fr,sophos.com,space.com,sparkfun.com,speedguide.net,stuff.tv,techdailynews.net,techdirt.com,techeblog.com,techhive.com,techie-buzz.com,technewsworld.com,techniqueworld.com,technobuffalo.com,technologyreview.com,technologytell.com,techpowerup.com,techpp.com,techrepublic.com,techshout.com,techweb.com,techworld.com,techworld.com.au,techworldtweets.com,telecomfile.com,tgdaily.com,theinquirer.net,thenextweb.com,theregister.co.uk,thermofisher.com,theverge.com,thewindowsclub.com,tomsguide.com,tomshardware.com,tomsitpro.com,toptenreviews.com,trustedreviews.com,tuaw.com,tweaktown.com,ubergizmo.com,unwiredview.com,venturebeat.com,wccftech.com,webmonkey.com,webpronews.com,windows7codecs.com,windowscentral.com,windowsitpro.com,windowstechies.com,winsupersite.com,wired.co.uk,wired.com,wp-themes.com,xda-developers.com,xml.com,zdnet.com,zmescience.com,zol.com.cn',
-    'Technology' ],
-  [ '9to5mac.com,appadvice.com,apple.com,appleinsider.com,appleturns.com,appsafari.com,cultofmac.com,everymac.com,insanelymac.com,iphoneunlockspot.com,isource.com,itunes.apple.com,lowendmac.com,mac-forums.com,macdailynews.com,macenstein.com,macgasm.net,macintouch.com,maclife.com,macnews.com,macnn.com,macobserver.com,macosx.com,macpaw.com,macrumors.com,macsales.com,macstories.net,macupdate.com,macuser.co.uk,macworld.co.uk,macworld.com,maxiapple.com,spymac.com,theapplelounge.com',
-    'Technology' ],
-  [ 'alistapart.com,answers.microsoft.com,backpack.openbadges.org,blog.chromium.org,caniuse.com,codefirefox.com,codepen.io,css-tricks.com,css3generator.com,cssdeck.com,csswizardry.com,devdocs.io,docs.angularjs.org,ghacks.net,github.com,html5demos.com,html5rocks.com,html5test.com,iojs.org,khanacademy.org,l10n.mozilla.org,learn.jquery.com,marketplace.firefox.com,mozilla-hispano.org,mozillians.org,news.ycombinator.com,npmjs.com,packagecontrol.io,quirksmode.org,readwrite.com,reps.mozilla.org,smashingmagazine.com,stackoverflow.com,status.modern.ie,teamtreehouse.com,tutorialspoint.com,udacity.com,validator.w3.org,w3.org,w3cschool.cc,w3schools.com,whatcanidoformozilla.org',
-    'Web Development' ],
-  [ 'classroom.google.com,codecademy.com,elearning.ut.ac.id,khanacademy.org,learn.jquery.com,teamtreehouse.com,tutorialspoint.com,udacity.com,w3cschool.cc,w3schools.com',
-    'Web Education' ],
-  [ 'abebooks.co.uk,abebooks.com,alibris.com,allaboutcircuits.com,allyoucanbooks.com,answersingenesis.org,artnet.com,audiobooks.com,barnesandnoble.com,barnesandnobleinc.com,bartleby.com,betterworldbooks.com,biggerbooks.com,bncollege.com,bookbyte.com,bookdepository.com,bookfinder.com,bookrenter.com,booksamillion.com,booksite.com,boundless.com,brookstone.com,btol.com,calibre-ebook.com,campusbookrentals.com,casadellibro.com,cbomc.com,cengagebrain.com,chapters.indigo.ca,christianbook.com,ciscopress.com,coursesmart.com,cqpress.com,crafterschoice.com,crossings.com,cshlp.org,deseretbook.com,directtextbook.com,discountmags.com,doubledaybookclub.com,doubledaylargeprint.com,doverpublications.com,ebooks.com,ecampus.com,fellabooks.net,fictionwise.com,flatworldknowledge.com,grolier.com,harpercollins.com,hayhouse.com,historybookclub.com,hpb.com,hpbmarketplace.com,interweave.com,iseeme.com,katiekazoo.com,knetbooks.com,learnoutloud.com,librarything.com,literaryguild.com,lulu.com,lww.com,macmillan.com,magazines.com,mbsdirect.net,militarybookclub.com,mypearsonstore.com,mysteryguild.com,netplaces.com,noble.com,novelguide.com,onespirit.com,oxfordjournals.org,paperbackswap.com,papy.co.jp,peachpit.com,penguin.com,penguingroup.com,pimsleur.com,powells.com,qpb.com,quepublishing.com,reviews.com,rhapsodybookclub.com,rodalestore.com,royalsocietypublishing.org,sagepub.com,scrubsmag.com,sfbc.com,simonandschuster.com,simonandschuster.net,simpletruths.com,teach12.net,textbooks.com,textbookx.com,thegoodcook.com,thriftbooks.com,tlsbooks.com,toshibabookplace.com,tumblebooks.com,urbookdownload.com,valorebooks.com,valuemags.com,wwnorton.com,zoobooks.com',
-    'Literature' ],
-  [ 'aceshowbiz.com,aintitcoolnews.com,askkissy.com,askmen.com,atraf.co.il,audioboom.com,beamly.com,blippitt.com,bollywoodlife.com,bossip.com,buzzlamp.com,celebdirtylaundry.com,celebfocus.com,celebitchy.com,celebrity-gossip.net,celebrityabout.com,celebwild.com,chisms.net,concertboom.com,crushable.com,cultbox.co.uk,dailyentertainmentnews.com,dayscafe.com,deadline.com,deathandtaxesmag.com,diaryofahollywoodstreetking.com,digitalspy.com,egotastic.com,empirenews.net,enelbrasero.com,everydaycelebs.com,ew.com,extratv.com,facade.com,fanaru.com,fhm.com,geektyrant.com,glamourpage.com,heatworld.com,hlntv.com,hollyscoop.com,hollywoodreporter.com,hollywoodtuna.com,hypable.com,infotransfer.net,insideedition.com,interaksyon.com,jezebel.com,justjared.com,justjaredjr.com,komando.com,koreaboo.com,maxgo.com,maxim.com,maxviral.com,mediatakeout.com,mosthappy.com,moviestalk.com,my.ology.com,ngoisao.net,nofilmschool.com,nolocreo.com,octane.tv,ouchpress.com,people.com,peopleenespanol.com,perezhilton.com,pinkisthenewblog.com,platotv.tv,playbill.com,playbillvault.com,playgroundmag.net,popeater.com,popnhop.com,popsugar.co.uk,popsugar.com,purepeople.com,radaronline.com,rantchic.com,reshareworthy.com,rinkworks.com,ripbird.com,sara-freder.com,screenjunkies.com,soapcentral.com,soapoperadigest.com,sobadsogood.com,splitsider.com,starcasm.net,starpulse.com,straightfromthea.com,stupidcelebrities.net,stupiddope.com,tbn.org,theawesomedaily.com,theawl.com,thefrisky.com,thefw.com,theresacaputo.com,thezooom.com,tvnotas.com.mx,twanatells.com,vanswarpedtour.com,vietgiaitri.com,viral.buzz,vulture.com,wakavision.com,worthytales.net,wwtdd.com,younghollywood.com',
-    'Entertainment News' ],
-  [ '247wallst.com,4-traders.com,advfn.com,agweb.com,allbusiness.com,barchart.com,barrons.com,beckershospitalreview.com,benzinga.com,bizjournals.com,bizsugar.com,bloomberg.com,bloomberglaw.com,business-standard.com,businessinsider.com,businessinsider.com.au,businesspundit.com,businessweek.com,businesswire.com,cboe.com,cheatsheet.com,chicagobusiness.com,cjonline.com,cnbc.com,cnnmoney.com,cqrcengage.com,dailyfinance.com,dailyfx.com,dealbreaker.com,djindexes.com,dowjones.com,easierstreetdaily.com,economist.com,economyandmarkets.com,economywatch.com,edweek.org,eleconomista.es,entrepreneur.com,etfdailynews.com,etfdb.com,ewallstreeter.com,fastcolabs.com,fastcompany.com,financeformulas.net,financialpost.com,flife.de,forbes.com,forexpros.com,fortune.com,foxbusiness.com,ft.com,ftpress.com,fx-exchange.com,hbr.org,howdofinance.com,ibtimes.com,inc.com,investopedia.com,investors.com,investorwords.com,journalofaccountancy.com,kiplinger.com,lendingandcredit.net,lfb.org,mainstreet.com,markettraders.com,marketwatch.com,maxkeiser.com,minyanville.com,ml.com,moneycontrol.com,moneymappress.com,moneynews.com,moneysavingexpert.com,morningstar.com,mortgagenewsdaily.com,motleyfool.com,mt.co.kr,nber.org,nyse.com,oilprice.com,pewsocialtrends.org,principal.com,qz.com,rantfinance.com,realclearmarkets.com,recode.net,reuters.ca,reuters.co.in,reuters.co.uk,reuters.com,rttnews.com,seekingalpha.com,smallbiztrends.com,streetinsider.com,thecheapinvestor.com,theeconomiccollapseblog.com,themoneyconverter.com,thestreet.com,tickertech.com,tradingeconomics.com,updown.com,valuewalk.com,wikinvest.com,wsj.com,zacks.com',
-    'Financial News' ],
-  [ '10tv.com,8newsnow.com,9news.com,abc.net.au,abc7.com,abc7chicago.com,abcnews.go.com,aclu.org,activistpost.com,ajc.com,al.com,alan.com,alarab.net,aljazeera.com,americanthinker.com,app.com,aristeguinoticias.com,azcentral.com,baltimoresun.com,becomingminimalist.com,beforeitsnews.com,bigstory.ap.org,blackamericaweb.com,bloomberg.com,bloombergview.com,boston.com,bostonherald.com,breitbart.com,buffalonews.com,c-span.org,canada.com,cbs46.com,cbsnews.com,chicagotribune.com,chron.com,citizensvoice.com,citylab.com,cleveland.com,cnn.com,coed.com,countercurrentnews.com,courant.com,ctvnews.ca,dailyherald.com,dailynews.com,dallasnews.com,delawareonline.com,democratandchronicle.com,democraticunderground.com,democrats.org,denverpost.com,desmoinesregister.com,dispatch.com,elcomercio.pe,english.aljazeera.net,examiner.com,farsnews.com,firstcoastnews.com,firstpost.com,firsttoknow.com,foreignpolicy.com,foxnews.com,freebeacon.com,freep.com,fresnobee.com,gazette.com,global.nytimes.com,heraldtribune.com,hindustantimes.com,hngn.com,humanevents.com,huzlers.com,indiatimes.com,indystar.com,irishtimes.com,jacksonville.com,jpost.com,jsonline.com,kansascity.com,kctv5.com,kentucky.com,kickerdaily.com,king5.com,kmov.com,knoxnews.com,kpho.com,kvue.com,kwqc.com,kxan.com,lainformacion.com,latimes.com,ldnews.com,lex18.com,linternaute.com,livemint.com,lostateminor.com,m24.ru,macleans.ca,manchestereveningnews.co.uk,marinecorpstimes.com,masslive.com,mavikocaeli.com.tr,mcall.com,medium.com,mentalfloss.com,mercurynews.com,metro.us,miamiherald.com,militarytimes.com,mk.ru,mlive.com,mondotimes.com,montrealgazette.com,msnbc.com,msnewsnow.com,mynews13.com,mysanantonio.com,mysuncoast.com,nbclosangeles.com,nbcnewyork.com,nbcphiladelphia.com,ndtv.com,newindianexpress.com,news.cincinnati.com,news.google.com,news.msn.com,news.yahoo.com,news10.net,news8000.com,newsday.com,newsdaymarketing.net,newsen.com,newsmax.com,newsobserver.com,newsok.com,newsru.ua,newstatesman.com,newszoom.com,nj.com,nola.com,northjersey.com,nouvelobs.com,npr.org,nwfdailynews.com,nwitimes.com,nydailynews.com,nytimes.com,observer.com,ocregister.com,okcfox.com,omaha.com,onenewspage.com,ontheissues.org,oregonlive.com,orlandosentinel.com,palmbeachpost.com,pe.com,pennlive.com,philly.com,pilotonline.com,polar.com,post-gazette.com,postandcourier.com,presstelegram.com,presstv.ir,propublica.org,providencejournal.com,realclearpolitics.com,recorderonline.com,reporterdock.com,reporterherald.com,respublica.al,reuters.com,rg.ru,roanoke.com,sacbee.com,scmp.com,scnow.com,sdpnoticias.com,seattletimes.com,semana.com,sfgate.com,sharepowered.com,sinembargo.mx,slate.com,sltrib.com,sotomayortv.com,sourcewatch.org,spectator.co.uk,squaremirror.com,star-telegram.com,staradvertiser.com,startribune.com,statesman.com,stltoday.com,streetwise.co,stuff.co.nz,success.com,suffolknewsherald.com,sun-sentinel.com,sunnewsnetwork.ca,suntimes.com,supernewschannel.com,surenews.com,svoboda.org,syracuse.com,tampabay.com,tbd.com,telegram.com,telegraph.co.uk,tennessean.com,the-open-mind.com,theadvocate.com,theage.com.au,theatlantic.com,thebarefootwriter.com,theblaze.com,thecalifornian.com,thedailysheeple.com,thefix.com,theintelligencer.net,thelocal.com,thenational.ae,thenewstribune.com,theparisreview.org,thereporter.com,therepublic.com,thestar.com,thetelegram.com,thetimes.co.uk,theuspatriot.com,time.com,timescall.com,timesdispatch.com,timesleaderonline.com,timesofisrael.com,toledoblade.com,toprightnews.com,townhall.com,tpnn.com,trendolizer.com,triblive.com,tribune.com.pk,tricities.com,troymessenger.com,trueactivist.com,truthandaction.org,tsn.ua,tulsaworld.com,twincities.com,upi.com,usatoday.com,utsandiego.com,vagazette.com,viralwomen.com,vitalworldnews.com,voasomali.com,vox.com,washingtonexaminer.com,washingtonpost.com,watchdog.org,wave3.com,wavy.com,wbay.com,wbtw.com,wcpo.com,wctrib.com,wdtn.com,weeklystandard.com,westernjournalism.com,wfsb.com,wgrz.com,whas11.com,winonadailynews.com,wishtv.com,wistv.com,wkbn.com,wkow.com,wlfi.com,wmtw.com,wmur.com,wopular.com,world-top-news.com,worldnews.com,wplol.us,wpsdlocal6.com,wptz.com,wric.com,wsmv.com,wthitv.com,wthr.com,wtnh.com,wtol.com,wtsp.com,wvec.com,wwlp.com,wwltv.com,wyff4.com,yonhapnews.co.kr,yourbreakingnews.com',
-    'News' ],
-  [ '2k.com,360game.vn,4399.com,a10.com,activision.com,addictinggames.com,alawar.com,alienwarearena.com,anagrammer.com,andkon.com,aq.com,arcadeprehacks.com,arcadeyum.com,arcgames.com,archeagegame.com,armorgames.com,askmrrobot.com,battle.net,battlefieldheroes.com,bigfishgames.com,bigpoint.com,bioware.com,bluesnews.com,boardgamegeek.com,bollyheaven.com,bubblebox.com,bukkit.org,bungie.net,buycraft.net,callofduty.com,candystand.com,cda.pl,challonge.com,championselect.net,cheapassgamer.com,cheatcc.com,cheatengine.org,cheathappens.com,chess.com,civfanatics.com,clashofclans-tools.com,clashofclansbuilder.com,comdotgame.com,commonsensemedia.org,coolrom.com,crazygames.com,csgolounge.com,curse.com,d20pfsrd.com,destructoid.com,diablofans.com,diablowiki.net,didigames.com,dota2.com,dota2lounge.com,dressupgames.com,dulfy.net,ebog.com,elderscrollsonline.com,elitedangerous.com,elitepvpers.com,emuparadise.me,enjoydressup.com,escapegames24.com,escapistmagazine.com,eventhubs.com,eveonline.com,farming-simulator.com,feed-the-beast.com,flashgames247.com,flightrising.com,flipline.com,flonga.com,freegames.ws,freeonlinegames.com,fresh-hotel.org,friv.com,friv.today,fullypcgames.net,funny-games.biz,funtrivia.com,futhead.com,g2a.com,gamasutra.com,game-debate.com,game-oldies.com,game321.com,gamebaby.com,gamebaby.net,gamebanana.com,gamefaqs.com,gamefly.com,gamefront.com,gamegape.com,gamehouse.com,gameinformer.com,gamejolt.com,gamemazing.com,gamemeteor.com,gamerankings.com,gamersgate.com,games-msn.com,games-workshop.com,games.com,games2girls.com,gamesbox.com,gamesfreak.net,gametop.com,gametracker.com,gametrailers.com,gamezhero.com,gbatemp.net,geforce.com,gematsu.com,giantbomb.com,girl.me,girlsgames123.com,girlsplay.com,gog.com,gogames.me,gonintendo.com,goodgamestudios.com,gosugamers.net,greenmangaming.com,gtaforums.com,gtainside.com,guildwars2.com,hackedarcadegames.com,hearthpwn.com,hirezstudios.com,hitbox.tv,hltv.org,howrse.com,icy-veins.com,indiedb.com,jayisgames.com,jigzone.com,joystiq.com,juegosdechicas.com,kabam.com,kbhgames.com,kerbalspaceprogram.com,king.com,kixeye.com,kizi.com,kogama.com,kongregate.com,kotaku.com,lolcounter.com,lolking.net,lolnexus.com,lolpro.com,lolskill.net,lootcrate.com,lumosity.com,mafa.com,mangafox.me,mangapark.com,mariowiki.com,maxgames.com,megagames.com,metacritic.com,mindjolt.com,minecraft.net,minecraftforum.net,minecraftservers.org,minecraftskins.com,mineplex.com,miniclip.com,mmo-champion.com,mmobomb.com,mmohuts.com,mmorpg.com,mmosite.com,mobafire.com,moddb.com,modxvm.com,mojang.com,moshimonsters.com,mousebreaker.com,moviestarplanet.com,mtgsalvation.com,muchgames.com,myonlinearcade.com,myplaycity.com,myrealgames.com,mythicspoiler.com,n4g.com,newgrounds.com,nexon.net,nexusmods.com,ninjakiwi.com,nintendo.com,nintendoeverything.com,nintendolife.com,nitrome.com,nosteam.ro,notdoppler.com,noxxic.com,operationsports.com,origin.com,ownedcore.com,pacogames.com,pathofexile.com,pcgamer.com,pch.com,pcsx2.net,penny-arcade.com,planetminecraft.com,plarium.com,playdota.com,playpink.com,playsides.com,playstationlifestyle.net,playstationtrophies.org,pog.com,pokemon.com,polygon.com,popcap.com,primarygames.com,probuilds.net,ps3hax.net,psnprofiles.com,psu.com,qq.com,r2games.com,resourcepack.net,retrogamer.com,rewardtv.com,riotgames.com,robertsspaceindustries.com,roblox.com,robocraftgame.com,rockpapershotgun.com,rockstargames.com,roosterteeth.com,runescape.com,schoolofdragons.com,screwattack.com,scufgaming.com,segmentnext.com,shacknews.com,shockwave.com,shoryuken.com,siliconera.com,silvergames.com,skydaz.com,smashbros.com,solomid.net,starcitygames.com,starsue.net,steamcommunity.com,steamgifts.com,strategywiki.org,supercheats.com,surrenderat20.net,swtor.com,tankionline.com,tcgplayer.com,teamfortress.com,teamliquid.net,tetrisfriends.com,thesims3.com,thesimsresource.com,thetechgame.com,topg.org,totaljerkface.com,toucharcade.com,transformice.com,trueachievements.com,twcenter.net,twitch.tv,twoplayergames.org,unity3d.com,vg247.com,vgchartz.com,videogamesblogger.com,warframe.com,warlight.net,warthunder.com,watchcartoononline.com,websudoku.com,wildstar-online.com,wildtangent.com,wineverygame.com,wizards.com,worldofsolitaire.com,worldoftanks.com,wowhead.com,wowprogress.com,wowwiki.com,xbox.com,xbox360iso.com,xboxachievements.com,xfire.com,xtremetop100.com,y8.com,yoyogames.com,zybez.net,zynga.com',
-    'Video Game' ],
+  [ 'addictivetips.com,allthingsd.com,anandtech.com,androidcentral.com,androidpolice.com,arstechnica.com,bgr.com,boygeniusreport.com,cio.com,cnet.com,computerworld.com,crn.com,electronista.com,engadget.com,extremetech.com,fastcocreate.com,fastcodesign.com,fastcoexist.com,frontlinek12.com,gigaom.com,gizmag.com,gizmodo.com,greenbot.com,howtogeek.com,idigitaltimes.com,imore.com,informationweek.com,infoworld.com,itworld.com,kioskea.net,laptopmag.com,leadpages.net,lifehacker.com,mashable.com,networkworld.com,news.cnet.com,nwc.com,pastebin.com,pcadvisor.co.uk,pcmag.com,pcworld.com,phonearena.com,reviewed.com,serverfault.com,siteadvisor.com,slashdot.org,techcrunch.com,techdirt.com,techhive.com,technewsworld.com,techrepublic.com,techweb.com,tomsguide.com,tomshardware.com,ubergizmo.com,venturebeat.com,wired.com,xda-developers.com,zdnet.com',
+    'technology news' ],
+  [ 'bestbuy.ca,bestbuy.com,cdw.com,compusa.com,computerlivehelp.co,cyberguys.com,dell.com,digitalinsight.com,directron.com,ebuyer.com,frontierpc.com,frys-electronics-ads.com,frys.com,geeks.com,gyazo.com,homestead.com,lenovo.com,macmall.com,microcenter.com,miniinthebox.com,mwave.com,newegg.com,officedepot.com,outletpc.com,outpost.com,radioshack.com,rakuten.com,tigerdirect.com',
+    'tech retail' ],
+  [ 'chat.com,fring.com,hello.firefox.com,oovoo.com,viber.com',
+    'video chat' ],
+  [ 'alistapart.com,answers.microsoft.com,backpack.openbadges.org,blog.chromium.org,caniuse.com,codefirefox.com,codepen.io,css-tricks.com,css3generator.com,cssdeck.com,csswizardry.com,devdocs.io,docs.angularjs.org,ghacks.net,github.com,html5demos.com,html5rocks.com,html5test.com,iojs.org,l10n.mozilla.org,marketplace.firefox.com,mozilla-hispano.org,mozillians.org,news.ycombinator.com,npmjs.com,packagecontrol.io,quirksmode.org,readwrite.com,reps.mozilla.org,smashingmagazine.com,speckyboy.com,stackoverflow.com,status.modern.ie,validator.w3.org,w3.org,webreference.com,whatcanidoformozilla.org',
+    'web development' ],
+  [ 'classroom.google.com,codeacademy.org,codecademy.com,codeschool.com,codeyear.com,elearning.ut.ac.id,how-to-build-websites.com,htmlcodetutorial.com,htmldog.com,htmlplayground.com,learn.jquery.com,quackit.com,roseindia.net,teamtreehouse.com,tizag.com,tutorialspoint.com,udacity.com,w3schools.com,webdevelopersnotes.com',
+    'webdev education' ],
+  [ 'att.com,att.net,attonlineoffers.com,bell.ca,bellsouth.com,cableone.net,cablevision.com,centurylink.com,centurylink.net,centurylinkquote.com,charter-business.com,charter.com,charter.net,chartercabledeals.com,chartermedia.com,comcast.com,comcast.net,cox.com,cox.net,coxnewsweb.com,directv.com,dish.com,dishnetwork.com,freeconferencecall.com,frontier.com,hughesnet.com,liveitwithcharter.com,mycenturylink.com,mydish.com,net10.com,officialtvstream.com.es,optimum.com,optimum.net,paygonline.com,paytm.com,qwest.com,rcn.com,rebtel.com,ringcentral.com,straighttalkbyop.com,swappa.com,textem.net,timewarner.com,timewarnercable.com,tracfone.com,verizon.com,verizon.net,voipo.com,vonagebusiness.com,wayport.net,whistleout.com,wildblue.net,windstream.net,windstreambusiness.net,wowway.com,ww2.cox.com,xfinity.com',
+    'telecommunication' ],
+  [ 'alltel.com,assurancewireless.com,attsavings.com,boostmobile.com,boostmobilestore.com,budgetmobile.com,consumercellular.com,credomobile.com,gosmartmobile.com,h2owirelessnow.com,lycamobile.com,lycamobile.us,metropcs.com,motorola.com,mycricket.com,myfamilymobile.com,nextel.com,nokia.com,nokiausa.com,polarmobile.com,qlinkwireless.com,republicwireless.com,sprint.com,sprintpcs.com,straighttalk.com,t-mobile.co.uk,t-mobile.com,tmobile.com,tracfonewireless.com,uscellular.com,verizonwireless.com,virginmobile.com,virginmobile.com.au,virginmobileusa.com,vodafone.co.uk,vodafone.com,vodaphone.co.uk,vonange.com,vzwshop.com,wireless.att.com',
+    'mobile carrier' ],
+  [ 'aa.com,aerlingus.com,airasia.com,aircanada.com,airfrance.com,airindia.com,alaskaair.com,alaskaairlines.com,allegiantair.com,britishairways.com,cathaypacific.com,china-airlines.com,continental.com,delta.com,deltavacations.com,dragonair.com,easyjet.com,elal.co.il,emirates.com,flightaware.com,flyfrontier.com,frontierairlines.com,hawaiianair.com,iberia.com,jetairways.com,jetblue.com,klm.com,koreanair.com,kuwait-airways.com,lan.com,lufthansa.com,malaysiaairlines.com,mihinlanka.com,nwa.com,qantas.com.au,qatarairways.com,ryanair.com,singaporeair.com,smartfares.com,southwest.com,southwestvacations.com,spiritair.com,spiritairlines.com,thaiair.com,united.com,usairways.com,virgin-atlantic.com,virginamerica.com,virginblue.com.au',
+    'travel & airline' ],
+  [ 'carnival.com,celebrity-cruises.com,celebritycruises.com,costacruise.com,cruise.com,cruiseamerica.com,cruisecritic.com,cruisedirect.com,cruisemates.com,cruises.com,cruisesonly.com,crystalcruises.com,cunard.com,disneycruise.disney.go.com,hollandamerica.com,ncl.com,pocruises.com,princess.com,royalcaribbean.com,royalcaribbean.cruiselines.com,rssc.com,seabourn.com,silversea.com,starcruises.com,vikingrivercruises.com,windstarcruises.com',
+    'travel & cruise' ],
+  [ 'agoda.com,airbnb.com,beaches.com,bedandbreakfast.com,bestwestern.com,booking.com,caesars.com,choicehotels.com,comfortinn.com,daysinn.com,dealbase.com,doubletree3.hilton.com,embassysuites.com,fairmont.com,flipkey.com,fourseasons.com,greatwolf.com,hamptoninn.hilton.com,hamptoninn3.hilton.com,hhonors3.hilton.com,hilton.com,hiltongardeninn3.hilton.com,hiltonworldwide.com,holidayinn.com,homeaway.com,hotelclub.com,hotelopia.com,hotels.com,hotelscombined.com,hyatt.com,ihg.com,laterooms.com,lhw.com,lq.com,mandarinoriental.com,marriott.com,motel6.com,omnihotels.com,radisson.com,ramada.com,rci.com,reservationcounter.com,resortvacationstogo.com,ritzcarlton.com,roomkey.com,sheraton.com,starwoodhotels.com,starwoodhotelshawaii.com,super8.com,thetrain.com,vacationhomerentals.com,vacationrentals.com,vrbo.com,wyndhamrewards.com',
+    'hotel & resort' ],
+  [ 'airfarewatchdog.com,airliners.net,atlanta-airport.com,budgettravel.com,cntraveler.com,cntraveller.com,destination360.com,flightstats.com,flyertalk.com,fodors.com,frommers.com,letsgo.com,lonelyplanet.com,matadornetwork.com,perfectvacation.co,ricksteves.com,roughguides.com,timeout.com,travelalberta.us,travelandleisure.com,travelchannel.com,traveler.nationalgeographic.com,travelmath.com,traveltune.com,tripadvisor.com,vegas.com,viator.com,virtualtourist.com,wikitravel.org,worldtravelguide.net',
+    'travel' ],
+  [ 'aavacations.com,applevacations.com,avianca.com,bookingbuddy.com,bookit.com,cheapair.com,cheapcaribbean.com,cheapflights.com,cheapoair.com,cheaptickets.com,chinahighlights.com,costcotravel.com,ctrip.com,despegar.com,edreams.net,expedia.ca,expedia.com,fareboom.com,farebuzz.com,farecast.live.com,farecompare.com,faregeek.com,flightnetwork.com,funjet.com,golastminute.com,hipmunk.com,hotwire.com,ifly.com,justairticket.com,kayak.com,lastminute.com,lastminutetravel.com,lowestfare.com,lowfares.com,momondo.com,onetime.com,onetravel.com,orbitz.com,otel.com,priceline.com,pricelinevisa.com,sidestep.com,skyscanner.com,smartertravel.com,statravel.com,tigerair.com,travelocity.com,travelonbids.com,travelzoo.com,tripsta.com,trivago.com,universalorlando.com,universalstudioshollywood.com,vacationexpress.com,venere.com,webjet.com,yatra.com',
+    'travel' ],
+  [ 'airportrentalcars.com,alamo.com,amtrak.com,anytransitguide.com,avis.com,boltbus.com,budget.com,carrentalexpress.com,carrentals.com,coachusa.com,dollar.com,e-zrentacar.com,enterprise.com,europcar.com,foxrentacar.com,gotobus.com,greyhound.com,hertz.com,hertzondemand.com,indianrail.gov.in,irctc.co.in,megabus.com,mta.info,nationalcar.com,nationalrail.co.uk,njtransit.com,paylesscar.com,paylesscarrental.com,peterpanbus.com,raileurope.com,rentalcars.com,rideuta.com,stagecoachbus.com,thrifty.com,uber.com,wanderu.com,zipcar.com',
+    'travel & transit' ],
+  [ 'bulbagarden.net,cheatcc.com,cheatmasters.com,cheats.ign.com,comicvine.com,computerandvideogames.com,counter-strike.net,escapistmagazine.com,gamedaily.com,gamefront.com,gameinformer.com,gamerankings.com,gamespot.com,gamesradar.com,gamestop.com,gametrailers.com,gamezone.com,giantbomb.com,ign.com,kotaku.com,metacritic.com,minecraft-server-list.com,minecraftforge.net,minecraftforum.net,minecraftservers.org,minecraftskins.com,mmo-champion.com,mojang.com,pcgamer.com,planetminecraft.com,supercheats.com,thesims.com,totaljerkface.com,unity3d.com,vg247.com,wowhead.com',
+    'gaming' ],
+  [ 'a10.com,absolutist.com,addictinggames.com,aeriagames.com,agame.com,alpha-wars.com,arcadeyum.com,armorgames.com,ballerarcade.com,battle.net,battlefield.com,bigfishgames.com,bioware.com,bitrhymes.com,candystand.com,conjurorthegame.com,crazymonkeygames.com,crusharcade.com,curse.com,cuttherope.net,dreammining.com,dressupgames.com,ea.com,easports.com,fps-pb.com,freearcade.com,freeonlinegames.com,friv.com,funplusgame.com,gamefly.com,gameforge.com,gamehouse.com,gamejolt.com,gameloft.com,gameoapp.com,gamepedia.com,gamersfirst.com,games.com,games.yahoo.com,gamesgames.com,gamezhero.com,gamingwonderland.com,ganymede.eu,goodgamestudios.com,gpotato.com,gsn.com,guildwars2.com,hirezstudios.com,igg.com,iwin.com,kahoot.it,king.com,kizi.com,kongregate.com,leagueoflegends.com,lolking.net,maxgames.com,minecraft-mp.com,minecraft.net,miniclip.com,mmo-play.com,mmorpg.com,mobafire.com,moviestarplanet.com,myonlinearcade.com,needforspeed.com,newgrounds.com,nexusmods.com,nintendo.com,noxxic.com,onrpg.com,origin.com,pch.com,peakgames.net,playstation.com,pogo.com,pokemon.com,popcap.com,primarygames.com,r2games.com,railnation.us,riotgames.com,roblox.com,rockstargames.com,runescape.com,shockwave.com,silvergames.com,spore.com,steamcommunity.com,steampowered.com,stickpage.com,swtor.com,tetrisfriends.com,thegamerstop.com,thesims3.com,twitch.tv,warthunder.com,wildtangent.com,worldoftanks.com,worldofwarcraft.com,worldofwarplanes.com,worldofwarships.com,xbox.com,y8.com,zone.msn.com,zynga.com,zyngawithfriends.com',
+    'online gaming' ],
 ]);
 
 // Only allow link urls that are http(s)
 const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]);
 
 // Only allow link image urls that are https or data
 const ALLOWED_IMAGE_SCHEMES = new Set(["https", "data"]);
 
@@ -641,16 +785,28 @@ let DirectoryLinksProvider = {
       // URIs without base domains will be allowed
       base = Services.eTLD.getBaseDomain(uri);
     }
     catch(ex) {}
     // Require a scheme match and the base only if desired
     return allowed.has(scheme) && (!checkBase || ALLOWED_URL_BASE.has(base));
   },
 
+  _escapeChars(text) {
+    let charMap = {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#039;'
+    };
+
+    return text.replace(/[&<>"']/g, (character) => charMap[character]);
+  },
+
   /**
    * Gets the current set of directory links.
    * @param aCallback The function that the array of links is passed to.
    */
   getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
     this._readDirectoryLinksFile().then(rawLinks => {
       // Reset the cache of suggested tiles and enhanced images for this new set of links
       this._enhancedLinks.clear();
@@ -673,18 +829,18 @@ let DirectoryLinksProvider = {
         if (name == undefined) {
           return;
         }
 
         let sanitizeFlags = ParserUtils.SanitizerCidEmbedsOnly |
           ParserUtils.SanitizerDropForms |
           ParserUtils.SanitizerDropNonCSSPresentation;
 
-        link.explanation = link.explanation ? ParserUtils.convertToPlainText(link.explanation, sanitizeFlags, 0) : "";
-        link.targetedName = ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0) || name;
+        link.explanation = this._escapeChars(link.explanation ? ParserUtils.convertToPlainText(link.explanation, sanitizeFlags, 0) : "");
+        link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0) || name);
         link.lastVisitDate = rawLinks.suggested.length - position;
         // check if link wants to avoid inadjacent sites
         if (link.check_inadjacency) {
           this._avoidInadjacentSites = true;
         }
 
         // We cache suggested tiles here but do not push any of them in the links list yet.
         // The decision for which suggested tile to include will be made separately.
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -255,16 +255,26 @@ if test -n "$MOZ_NATIVE_DEVICES" ; then
     AC_MSG_CHECKING([for v7 appcompat library])
     if ! test -e $ANDROID_APPCOMPAT_LIB ; then
         AC_MSG_ERROR([You must download the v7 app compat Android support library when targeting Android with native video casting support enabled.  Run the Android SDK tool and install Android Support Library under Extras.  See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_APPCOMPAT_LIB)])
     fi
     AC_MSG_RESULT([$ANDROID_APPCOMPAT_LIB])
     AC_SUBST(ANDROID_APPCOMPAT_LIB)
     AC_SUBST(ANDROID_APPCOMPAT_RES)
 
+    ANDROID_RECYCLERVIEW_LIB="$ANDROID_COMPAT_DIR_BASE/v7/recyclerview/libs/android-support-v7-recyclerview.jar"
+    ANDROID_RECYCLERVIEW_RES="$ANDROID_COMPAT_DIR_BASE/v7/recyclerview/res"
+    AC_MSG_CHECKING([for v7 recyclerview library])
+    if ! test -e $ANDROID_RECYCLERVIEW_LIB ; then
+        AC_MSG_ERROR([You must download the v7 recyclerview Android support library.  Run the Android SDK tool and install Android Support Library under Extras.  See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_RECYCLERVIEW_LIB)])
+    fi
+    AC_MSG_RESULT([$ANDROID_RECYCLERVIEW_LIB])
+    AC_SUBST(ANDROID_RECYCLERVIEW_LIB)
+    AC_SUBST(ANDROID_RECYCLERVIEW_RES)
+
     ANDROID_MEDIAROUTER_LIB="$ANDROID_COMPAT_DIR_BASE/v7/mediarouter/libs/android-support-v7-mediarouter.jar"
     ANDROID_MEDIAROUTER_RES="$ANDROID_COMPAT_DIR_BASE/v7/mediarouter/res"
     AC_MSG_CHECKING([for v7 mediarouter library])
     if ! test -e $ANDROID_MEDIAROUTER_LIB ; then
         AC_MSG_ERROR([You must download the v7 media router Android support library when targeting Android with native video casting support enabled.  Run the Android SDK tool and install Android Support Library under Extras.  See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_MEDIAROUTER_LIB)])
     fi
     AC_MSG_RESULT([$ANDROID_MEDIAROUTER_LIB])
     AC_SUBST(ANDROID_MEDIAROUTER_LIB)
--- a/build/unix/stdc++compat/stdc++compat.cpp
+++ b/build/unix/stdc++compat/stdc++compat.cpp
@@ -17,38 +17,27 @@
    GLIBCXX_3.4.13 is from gcc 4.4.2 (151127)
    GLIBCXX_3.4.14 is from gcc 4.5.0 (151126)
    GLIBCXX_3.4.15 is from gcc 4.6.0 (160071)
    GLIBCXX_3.4.16 is from gcc 4.6.1 (172240)
    GLIBCXX_3.4.17 is from gcc 4.7.0 (174383)
    GLIBCXX_3.4.18 is from gcc 4.8.0 (190787)
    GLIBCXX_3.4.19 is from gcc 4.8.1 (199309)
    GLIBCXX_3.4.20 is from gcc 4.9.0 (199307)
-   GLIBCXX_3.4.21 is from gcc 5.0 (210290) */
+   GLIBCXX_3.4.21 is from gcc 5.0 (210290)
+
+This file adds the necessary compatibility tricks to avoid symbols with
+version GLIBCXX_3.4.11 and bigger, keeping binary compatibility with
+libstdc++ 4.3.
+
+*/
 
 #define GLIBCXX_VERSION(a, b, c) (((a) << 16) | ((b) << 8) | (c))
 
 namespace std {
-#if MOZ_LIBSTDCXX_VERSION >= GLIBCXX_VERSION(3, 4, 9)
-    /* Instantiate these templates to avoid GLIBCXX_3.4.9 symbol versions */
-    template ostream& ostream::_M_insert(double);
-    template ostream& ostream::_M_insert(long);
-    template ostream& ostream::_M_insert(unsigned long);
-    template ostream& ostream::_M_insert(long long);
-    template ostream& ostream::_M_insert(unsigned long long);
-    template ostream& ostream::_M_insert(bool);
-    template ostream& ostream::_M_insert(const void*);
-    template ostream& __ostream_insert(ostream&, const char*, streamsize);
-    template istream& istream::_M_extract(double&);
-    template istream& istream::_M_extract(float&);
-    template istream& istream::_M_extract(unsigned int&);
-    template istream& istream::_M_extract(unsigned long&);
-    template istream& istream::_M_extract(unsigned short&);
-    template istream& istream::_M_extract(unsigned long long&);
-#endif
 #if MOZ_LIBSTDCXX_VERSION >= GLIBCXX_VERSION(3, 4, 14)
     /* Instantiate these templates to avoid GLIBCXX_3.4.14 symbol versions
      * depending on optimization level */
     template char *string::_S_construct_aux_2(size_type, char, allocator<char> const&);
 #ifdef _GLIBCXX_USE_WCHAR_T
     template wchar_t *wstring::_S_construct_aux_2(size_type, wchar_t, allocator<wchar_t> const&);
 #endif /* _GLIBCXX_USE_WCHAR_T */
 #ifdef __GXX_EXPERIMENTAL_CXX0X__
--- a/config/config.mk
+++ b/config/config.mk
@@ -626,17 +626,17 @@ EXPAND_LD = $(EXPAND_LIBS_EXEC) --uselis
 EXPAND_MKSHLIB_ARGS = --uselist
 ifdef SYMBOL_ORDER
 EXPAND_MKSHLIB_ARGS += --symbol-order $(SYMBOL_ORDER)
 endif
 EXPAND_MKSHLIB = $(EXPAND_LIBS_EXEC) $(EXPAND_MKSHLIB_ARGS) -- $(MKSHLIB)
 
 ifneq (,$(MOZ_LIBSTDCXX_TARGET_VERSION)$(MOZ_LIBSTDCXX_HOST_VERSION))
 ifneq ($(OS_ARCH),Darwin)
-CHECK_STDCXX = @$(TOOLCHAIN_PREFIX)objdump -p $(1) | grep -e 'GLIBCXX_3\.4\.\(9\|[1-9][0-9]\)' > /dev/null && echo 'TEST-UNEXPECTED-FAIL | check_stdcxx | We do not want these libstdc++ symbols to be used:' && $(TOOLCHAIN_PREFIX)objdump -T $(1) | grep -e 'GLIBCXX_3\.4\.\(9\|[1-9][0-9]\)' && exit 1 || true
+CHECK_STDCXX = @$(TOOLCHAIN_PREFIX)objdump -p $(1) | grep -e 'GLIBCXX_3\.4\.\(1[1-9]\|[2-9][0-9]\)' > /dev/null && echo 'TEST-UNEXPECTED-FAIL | check_stdcxx | We do not want these libstdc++ symbols to be used:' && $(TOOLCHAIN_PREFIX)objdump -T $(1) | grep -e 'GLIBCXX_3\.4\.\(1[1-9]\|[2-9][0-9]\)' && exit 1 || true
 endif
 endif
 
 ifeq (,$(filter $(OS_TARGET),WINNT Darwin))
 CHECK_TEXTREL = @$(TOOLCHAIN_PREFIX)readelf -d $(1) | grep TEXTREL > /dev/null && echo 'TEST-UNEXPECTED-FAIL | check_textrel | We do not want text relocations in libraries and programs' || true
 endif
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),android)
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10089,16 +10089,21 @@ nsDocShell::InternalLoad(nsIURI* aURI,
 
       /* we need to assign mLSHE to aSHEntry right here, so that on History
        * loads, SetCurrentURI() called from OnNewURI() will send proper
        * onLocationChange() notifications to the browser to update
        * back/forward buttons.
        */
       SetHistoryEntry(&mLSHE, aSHEntry);
 
+      // Set the doc's URI according to the new history entry's URI.
+      nsCOMPtr<nsIDocument> doc = GetDocument();
+      NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+      doc->SetDocumentURI(aURI);
+
       /* This is a anchor traversal with in the same page.
        * call OnNewURI() so that, this traversal will be
        * recorded in session and global history.
        */
       nsCOMPtr<nsISupports> owner;
       if (mOSHE) {
         mOSHE->GetOwner(getter_AddRefs(owner));
       }
@@ -10179,21 +10184,16 @@ nsDocShell::InternalLoad(nsIURI* aURI,
         nsCOMPtr<IHistory> history = services::GetHistoryService();
         if (history) {
           history->SetURITitle(aURI, mTitle);
         } else if (mGlobalHistory) {
           mGlobalHistory->SetPageTitle(aURI, mTitle);
         }
       }
 
-      // Set the doc's URI according to the new history entry's URI.
-      nsCOMPtr<nsIDocument> doc = GetDocument();
-      NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
-      doc->SetDocumentURI(aURI);
-
       SetDocCurrentStateObj(mOSHE);
 
       // Inform the favicon service that the favicon for oldURI also
       // applies to aURI.
       CopyFavicon(currentURI, aURI, mInPrivateBrowsing);
 
       nsRefPtr<nsGlobalWindow> win = mScriptGlobal ?
         mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
@@ -11711,18 +11711,18 @@ nsDocShell::AddState(JS::Handle<JS::Valu
   // since SetCurrentURI will call FireOnLocationChange for us.
   //
   // Both SetCurrentURI(...) and FireDummyOnLocationChange() pass
   // nullptr for aRequest param to FireOnLocationChange(...). Such an update
   // notification is allowed only when we know docshell is not loading a new
   // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise,
   // FireOnLocationChange(...) breaks security UI.
   if (!equalURIs) {
+    document->SetDocumentURI(newURI);
     SetCurrentURI(newURI, nullptr, true, LOCATION_CHANGE_SAME_DOCUMENT);
-    document->SetDocumentURI(newURI);
 
     AddURIVisit(newURI, oldURI, oldURI, 0);
 
     // AddURIVisit doesn't set the title for the new URI in global history,
     // so do that here.
     if (mUseGlobalHistory && !mInPrivateBrowsing) {
       nsCOMPtr<IHistory> history = services::GetHistoryService();
       if (history) {
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -28,16 +28,17 @@ support-files =
   file_bug234628-9.html
   file_bug420605.html
   file_bug503832.html
   file_bug655270.html
   file_bug670318.html
   file_bug852909.pdf
   file_bug852909.png
   file_bug1046022.html
+  file_multiple_pushState.html
   print_postdata.sjs
   test-form_sjis.html
   timelineMarkers-04.html
   browser_timelineMarkers-frame-02.js
   browser_timelineMarkers-frame-03.js
   browser_timelineMarkers-frame-04.js
   browser_timelineMarkers-frame-05.js
   head.js
@@ -93,16 +94,17 @@ skip-if = e10s # Bug ?????? - test touch
 skip-if = e10s # Bug ?????? - event handler checks event.target is the content document and test e10s-utils doesn't do that.
 [browser_bug92473.js]
 skip-if = e10s # Bug ?????? - event handler checks event.target is the content document and test e10s-utils doesn't do that.
 [browser_uriFixupIntegration.js]
 [browser_loadDisallowInherit.js]
 skip-if = e10s
 [browser_loadURI.js]
 skip-if = e10s # Bug ?????? - event handler checks event.target is the content document and test e10s-utils doesn't do that.
+[browser_multiple_pushState.js]
 [browser_onbeforeunload_navigation.js]
 skip-if = e10s
 [browser_search_notification.js]
 [browser_timelineMarkers-01.js]
 [browser_timelineMarkers-02.js]
 [browser_timelineMarkers-03.js]
 [browser_timelineMarkers-04.js]
 [browser_timelineMarkers-05.js]
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/browser_multiple_pushState.js
@@ -0,0 +1,15 @@
+add_task(function* test_multiple_pushState() {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: "http://example.org/browser/docshell/test/browser/file_multiple_pushState.html",
+  }, function* (browser) {
+    const kExpected = "http://example.org/bar/ABC/DEF?key=baz";
+
+    let contentLocation = yield ContentTask.spawn(browser, null, function* () {
+      return content.document.location.href;
+    });
+
+    is(contentLocation, kExpected);
+    is(browser.documentURI.spec, kExpected);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/file_multiple_pushState.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Test multiple calls to history.pushState</title>
+  </head>
+  <body>
+    <h1>Ohai</h1>
+  </body>
+  <script type="text/javascript">
+    window.history.pushState({}, "", "/bar/ABC?key=baz");
+    window.history.pushState({}, "", "/bar/ABC/DEF?key=baz");
+  </script>
+</html>
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -1780,17 +1780,18 @@ nsMessageManagerScriptExecutor::TryCache
     JS::Rooted<JSScript*> script(cx);
 
     if (aRunInGlobalScope) {
       if (!JS::Compile(cx, options, srcBuf, &script)) {
         return;
       }
     } else {
       // We're going to run these against some non-global scope.
-      if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
+      options.setHasPollutedScope(true);
+      if (!JS::Compile(cx, options, srcBuf, &script)) {
         return;
       }
     }
 
     aScriptp.set(script);
 
     nsAutoCString scheme;
     uri->GetScheme(scheme);
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -800,16 +800,19 @@ UploadLastDir::StoreLastUsedDirectory(ns
   aDir->GetPath(unicodePath);
   if (unicodePath.IsEmpty()) // nothing to do
     return NS_OK;
   nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
   if (!prefValue)
     return NS_ERROR_OUT_OF_MEMORY;
   prefValue->SetAsAString(unicodePath);
 
+  // Use the document's current load context to ensure that the content pref
+  // service doesn't persistently store this directory for this domain if the
+  // user is using private browsing:
   nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
   return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext, nullptr);
 }
 
 NS_IMETHODIMP
 UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic, char16_t const* aData)
 {
   if (strcmp(aTopic, "browser:purge-session-history") == 0) {
@@ -977,17 +980,17 @@ HTMLInputElement::Clone(mozilla::dom::No
   switch (GetValueMode()) {
     case VALUE_MODE_VALUE:
       if (mValueChanged) {
         // We don't have our default value anymore.  Set our value on
         // the clone.
         nsAutoString value;
         GetValueInternal(value);
         // SetValueInternal handles setting the VALUE_CHANGED bit for us
-        rv = it->SetValueInternal(value, false, true);
+        rv = it->SetValueInternal(value, nsTextEditorState::eSetValue_Notify);
         NS_ENSURE_SUCCESS(rv, rv);
       }
       break;
     case VALUE_MODE_FILENAME:
       if (it->OwnerDoc()->IsStaticDocument()) {
         // We're going to be used in print preview.  Since the doc is static
         // we can just grab the pretty string and use it as wallpaper
         GetDisplayFileName(it->mStaticDocFileList);
@@ -1162,41 +1165,44 @@ HTMLInputElement::AfterSetAttr(int32_t a
         // prevented there being a valid step in range). Changing @max to/from
         // 1 and a number greater than on equal to 3 should change whether we
         // have a step mismatch or not.
         // The value may also need to change between a value that results in
         // a step mismatch and a value that results in overflow. For example,
         // if @max in the example above were to change from 1 to -1.
         nsAutoString value;
         GetValue(value);
-        nsresult rv = SetValueInternal(value, false, false);
+        nsresult rv =
+          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
         NS_ENSURE_SUCCESS(rv, rv);
         MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                    "HTML5 spec does not allow this");
       }
     } else if (aName == nsGkAtoms::min) {
       UpdateHasRange();
       UpdateRangeUnderflowValidityState();
       UpdateStepMismatchValidityState();
       if (mType == NS_FORM_INPUT_RANGE) {
         // See @max comment
         nsAutoString value;
         GetValue(value);
-        nsresult rv = SetValueInternal(value, false, false);
+        nsresult rv =
+          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
         NS_ENSURE_SUCCESS(rv, rv);
         MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                    "HTML5 spec does not allow this");
       }
     } else if (aName == nsGkAtoms::step) {
       UpdateStepMismatchValidityState();
       if (mType == NS_FORM_INPUT_RANGE) {
         // See @max comment
         nsAutoString value;
         GetValue(value);
-        nsresult rv = SetValueInternal(value, false, false);
+        nsresult rv =
+          SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
         NS_ENSURE_SUCCESS(rv, rv);
         MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                    "HTML5 spec does not allow this");
       }
     } else if (aName == nsGkAtoms::dir &&
                aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
       SetDirectionIfAuto(true, aNotify);
     } else if (aName == nsGkAtoms::lang) {
@@ -1574,27 +1580,31 @@ HTMLInputElement::SetValue(const nsAStri
       // event, we should keep it that way. Otherwise, we should make sure the
       // element will not fire any event because of the script interaction.
       //
       // NOTE: this is currently quite expensive work (too much string
       // manipulation). We should probably optimize that.
       nsAutoString currentValue;
       GetValue(currentValue);
 
-      nsresult rv = SetValueInternal(aValue, false, true);
+      nsresult rv =
+        SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
+                                 nsTextEditorState::eSetValue_Notify);
       if (NS_FAILED(rv)) {
         aRv.Throw(rv);
         return;
       }
 
       if (mFocusedValue.Equals(currentValue)) {
         GetValue(mFocusedValue);
       }
     } else {
-      nsresult rv = SetValueInternal(aValue, false, true);
+      nsresult rv =
+        SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
+                                 nsTextEditorState::eSetValue_Notify);
       if (NS_FAILED(rv)) {
         aRv.Throw(rv);
         return;
       }
     }
   }
 }
 
@@ -2196,17 +2206,19 @@ HTMLInputElement::SetUserInput(const nsA
     if (!list.AppendElement(aValue, fallible)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     ErrorResult rv;
     MozSetFileNameArray(list, rv);
     return rv.StealNSResult();
   } else {
-    nsresult rv = SetValueInternal(aValue, true, true);
+    nsresult rv =
+      SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput |
+                               nsTextEditorState::eSetValue_Notify);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
                                        static_cast<nsIDOMHTMLInputElement*>(this),
                                        NS_LITERAL_STRING("input"), true,
                                        true);
 
@@ -2504,19 +2516,17 @@ HTMLInputElement::UpdateFileList()
       }
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-HTMLInputElement::SetValueInternal(const nsAString& aValue,
-                                   bool aUserInput,
-                                   bool aSetValueChanged)
+HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
 {
   NS_PRECONDITION(GetValueMode() != VALUE_MODE_FILENAME,
                   "Don't call SetValueInternal for file inputs");
 
   switch (GetValueMode()) {
     case VALUE_MODE_VALUE:
     {
       // At the moment, only single line text control have to sanitize their value
@@ -2524,31 +2534,32 @@ HTMLInputElement::SetValueInternal(const
       // it if it's useless.
       nsAutoString value(aValue);
 
       if (!mParserCreating) {
         SanitizeValue(value);
       }
       // else DoneCreatingElement calls us again once mParserCreating is false
 
-      if (aSetValueChanged) {
+      bool setValueChanged = !!(aFlags & nsTextEditorState::eSetValue_Notify);
+      if (setValueChanged) {
         SetValueChanged(true);
       }
 
       if (IsSingleLineTextControl(false)) {
-        if (!mInputData.mState->SetValue(value, aUserInput, aSetValueChanged)) {
+        if (!mInputData.mState->SetValue(value, aFlags)) {
           return NS_ERROR_OUT_OF_MEMORY;
         }
         if (mType == NS_FORM_INPUT_EMAIL) {
           UpdateAllValidityStates(mParserCreating);
         }
       } else {
         free(mInputData.mValue);
         mInputData.mValue = ToNewUnicode(value);
-        if (aSetValueChanged) {
+        if (setValueChanged) {
           SetValueChanged(true);
         }
         if (mType == NS_FORM_INPUT_NUMBER) {
           // This has to happen before OnValueChanged is called because that
           // method needs the new value of our frame's anon text control.
           nsNumberControlFrame* numberControlFrame =
             do_QueryFrame(GetPrimaryFrame());
           if (numberControlFrame) {
@@ -3146,17 +3157,18 @@ HTMLInputElement::PreHandleEvent(EventCh
   // Fire onchange (if necessary), before we do the blur, bug 357684.
   if (aVisitor.mEvent->message == NS_BLUR_CONTENT) {
     // Experimental mobile types rely on the system UI to prevent users to not
     // set invalid values but we have to be extra-careful. Especially if the
     // option has been enabled on desktop.
     if (IsExperimentalMobileType(mType)) {
       nsAutoString aValue;
       GetValueInternal(aValue);
-      nsresult rv = SetValueInternal(aValue, false, false);
+      nsresult rv =
+        SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     FireChangeEventIfNeeded();
   }
 
   if (mType == NS_FORM_INPUT_RANGE &&
       (aVisitor.mEvent->message == NS_FOCUS_CONTENT ||
        aVisitor.mEvent->message == NS_BLUR_CONTENT)) {
@@ -3267,17 +3279,19 @@ HTMLInputElement::PreHandleEvent(EventCh
     }
     if (textControl && aVisitor.mEvent->originalTarget == textControl) {
       if (aVisitor.mEvent->message == NS_EDITOR_INPUT) {
         // Propogate the anon text control's new value to our HTMLInputElement:
         nsAutoString value;
         numberControlFrame->GetValueOfAnonTextControl(value);
         numberControlFrame->HandlingInputEvent(true);
         nsWeakFrame weakNumberControlFrame(numberControlFrame);
-        rv = SetValueInternal(value, true, true);
+        rv = SetValueInternal(value,
+                              nsTextEditorState::eSetValue_BySetUserInput |
+                              nsTextEditorState::eSetValue_Notify);
         NS_ENSURE_SUCCESS(rv, rv);
         if (weakNumberControlFrame.IsAlive()) {
           numberControlFrame->HandlingInputEvent(false);
         }
       }
       else if (aVisitor.mEvent->message == NS_FORM_CHANGE) {
         // We cancel the DOM 'change' event that is fired for any change to our
         // anonymous text control since we fire our own 'change' events and
@@ -3344,17 +3358,18 @@ HTMLInputElement::CancelRangeThumbDrag(b
   } else {
     // Don't dispatch an 'input' event - at least not using
     // DispatchTrustedEvent.
     // TODO: decide what we should do here - bug 851782.
     nsAutoString val;
     ConvertNumberToString(mRangeThumbDragStartValue, val);
     // TODO: What should we do if SetValueInternal fails?  (The allocation
     // is small, so we should be fine here.)
-    SetValueInternal(val, true, true);
+    SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
+                          nsTextEditorState::eSetValue_Notify);
     nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
     if (frame) {
       frame->UpdateForValueChange();
     }
     nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
       new AsyncEventDispatcher(this, NS_LITERAL_STRING("input"), true, false);
     asyncDispatcher->RunDOMEventWhenSafe();
   }
@@ -3364,17 +3379,18 @@ void
 HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
 {
   MOZ_ASSERT(aValue.isFinite());
 
   nsAutoString val;
   ConvertNumberToString(aValue, val);
   // TODO: What should we do if SetValueInternal fails?  (The allocation
   // is small, so we should be fine here.)
-  SetValueInternal(val, true, true);
+  SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
+                        nsTextEditorState::eSetValue_Notify);
   nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
   if (frame) {
     frame->UpdateForValueChange();
   }
   nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
                                        static_cast<nsIDOMHTMLInputElement*>(this),
                                        NS_LITERAL_STRING("input"), true,
                                        false);
@@ -3457,17 +3473,18 @@ HTMLInputElement::StepNumberControlForUs
   if (NS_FAILED(rv) || !newValue.isFinite()) {
     return; // value should not or will not change
   }
 
   nsAutoString newVal;
   ConvertNumberToString(newValue, newVal);
   // TODO: What should we do if SetValueInternal fails?  (The allocation
   // is small, so we should be fine here.)
-  SetValueInternal(newVal, true, true);
+  SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
+                           nsTextEditorState::eSetValue_Notify);
 
   nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
                                        static_cast<nsIDOMHTMLInputElement*>(this),
                                        NS_LITERAL_STRING("input"), true,
                                        false);
 }
 
 static bool
@@ -4271,17 +4288,17 @@ HTMLInputElement::HandleTypeChange(uint8
         if (aOldValueMode != VALUE_MODE_VALUE) {
           GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
         } else {
           value = aOldValue;
         }
         // TODO: What should we do if SetValueInternal fails?  (The allocation
         // may potentially be big, but most likely we've failed to allocate
         // before the type change.)
-        SetValueInternal(value, false, false);
+        SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
       }
       break;
     case VALUE_MODE_FILENAME:
     default:
       // We don't care about the value.
       // There is no value sanitizing algorithm for elements in this mode.
       break;
   }
@@ -4954,17 +4971,18 @@ HTMLInputElement::SetRangeText(const nsA
         aSelectionEnd = state->GetSelectionProperties().mEnd;
         aRv = NS_OK;
       }
     }
   }
 
   if (aStart <= aEnd) {
     value.Replace(aStart, aEnd - aStart, aReplacement);
-    nsresult rv = SetValueInternal(value, false, false);
+    nsresult rv =
+      SetValueInternal(value, nsTextEditorState::eSetValue_ByContent);
     if (NS_FAILED(rv)) {
       aRv.Throw(rv);
       return;
     }
   }
 
   uint32_t newEnd = aStart + aReplacement.Length();
   int32_t delta =  aReplacement.Length() - (aEnd - aStart);
@@ -5279,17 +5297,17 @@ HTMLInputElement::SetDefaultValueAsValue
                "GetValueMode() should return VALUE_MODE_VALUE!");
 
   // The element has a content attribute value different from it's value when
   // it's in the value mode value.
   nsAutoString resetVal;
   GetDefaultValue(resetVal);
 
   // SetValueInternal is going to sanitize the value.
-  return SetValueInternal(resetVal, false, false);
+  return SetValueInternal(resetVal, nsTextEditorState::eSetValue_Internal);
 }
 
 void
 HTMLInputElement::SetDirectionIfAuto(bool aAuto, bool aNotify)
 {
   if (aAuto) {
     SetHasDirAuto();
     if (IsSingleLineTextControl(true)) {
@@ -5520,17 +5538,17 @@ HTMLInputElement::DoneCreatingElement()
 
   // Sanitize the value.
   if (GetValueMode() == VALUE_MODE_VALUE) {
     nsAutoString aValue;
     GetValue(aValue);
     // TODO: What should we do if SetValueInternal fails?  (The allocation
     // may potentially be big, but most likely we've failed to allocate
     // before the type change.)
-    SetValueInternal(aValue, false, false);
+    SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
   }
 
   mShouldInitChecked = false;
 }
 
 EventStates
 HTMLInputElement::IntrinsicState() const
 {
@@ -5679,17 +5697,18 @@ HTMLInputElement::RestoreState(nsPresSta
         if (GetValueMode() == VALUE_MODE_DEFAULT &&
             mType != NS_FORM_INPUT_HIDDEN) {
           break;
         }
 
         // TODO: What should we do if SetValueInternal fails?  (The allocation
         // may potentially be big, but most likely we've failed to allocate
         // before the type change.)
-        SetValueInternal(inputState->GetValue(), false, true);
+        SetValueInternal(inputState->GetValue(),
+                         nsTextEditorState::eSetValue_Notify);
         break;
     }
   }
 
   if (aState->IsDisabledSet()) {
     SetDisabled(aState->GetDisabled());
   }
 
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -34,18 +34,27 @@ class EventChainPostVisitor;
 class EventChainPreVisitor;
 
 namespace dom {
 
 class Date;
 class File;
 class FileList;
 
-class UploadLastDir final : public nsIObserver, public nsSupportsWeakReference {
-
+/**
+ * A class we use to create a singleton object that is used to keep track of
+ * the last directory from which the user has picked files (via
+ * <input type=file>) on a per-domain basis. The implementation uses
+ * nsIContentPrefService2/NS_CONTENT_PREF_SERVICE_CONTRACTID to store the last
+ * directory per-domain, and to ensure that whether the directories are
+ * persistently saved (saved across sessions) or not honors whether or not the
+ * page is being viewed in private browsing.
+ */
+class UploadLastDir final : public nsIObserver, public nsSupportsWeakReference
+{
   ~UploadLastDir() {}
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   /**
    * Fetch the last used directory for this location from the content
@@ -805,19 +814,24 @@ protected:
    * @param aLen    the length of the sub-string.
    * @param aResult the parsed number.
    * @return whether the sub-string has been parsed successfully.
    */
   static bool DigitSubStringToNumber(const nsAString& aValue, uint32_t aStart,
                                      uint32_t aLen, uint32_t* aResult);
 
   // Helper method
-  nsresult SetValueInternal(const nsAString& aValue,
-                            bool aUserInput,
-                            bool aSetValueChanged);
+
+  /**
+   * Setting the value.
+   *
+   * @param aValue      String to set.
+   * @param aFlags      See nsTextEditorState::SetValueFlags.
+   */
+  nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
 
   nsresult GetValueInternal(nsAString& aValue) const;
 
   /**
    * Returns whether the current value is the empty string.
    *
    * @return whether the current value is the empty string.
    */
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3105,17 +3105,27 @@ void HTMLMediaElement::SetupSrcMediaStre
     GetSrcMediaStream()->ChangeExplicitBlockerCount(1);
   }
 
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
 
   ChangeDelayLoadStatus(false);
   GetSrcMediaStream()->AddAudioOutput(this);
   SetVolumeInternal();
-  VideoFrameContainer* container = GetVideoFrameContainer();
+
+  bool bUseOverlayImage = mSrcStream->AsDOMHwMediaStream() != nullptr;
+  VideoFrameContainer* container;
+
+  if (bUseOverlayImage) {
+    container = GetOverlayImageVideoFrameContainer();
+  }
+  else {
+    container = GetVideoFrameContainer();
+  }
+
   if (container) {
     GetSrcMediaStream()->AddVideoOutput(container);
   }
 
   CheckAutoplayDataReady();
 
   // Note: we must call DisconnectTrackListListeners(...)  before dropping
   // mSrcStream
@@ -3773,17 +3783,33 @@ VideoFrameContainer* HTMLMediaElement::G
     return mVideoFrameContainer;
 
   // Only video frames need an image container.
   if (!IsVideo()) {
     return nullptr;
   }
 
   mVideoFrameContainer =
-    new VideoFrameContainer(this, LayerManager::CreateAsynchronousImageContainer());
+    new VideoFrameContainer(this, LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS));
+
+  return mVideoFrameContainer;
+}
+
+VideoFrameContainer* HTMLMediaElement::GetOverlayImageVideoFrameContainer()
+{
+  if (mVideoFrameContainer)
+    return mVideoFrameContainer;
+
+  // Only video frames need an image container.
+  if (!IsVideo()) {
+    return nullptr;
+  }
+
+  mVideoFrameContainer =
+    new VideoFrameContainer(this, LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS_OVERLAY));
 
   return mVideoFrameContainer;
 }
 
 nsresult HTMLMediaElement::DispatchEvent(const nsAString& aName)
 {
   LOG_EVENT(LogLevel::Debug, ("%p Dispatching event %s", this,
                           NS_ConvertUTF16toUTF8(aName).get()));
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -207,16 +207,19 @@ public:
   // Called by the media decoder to indicate whether the media cache has
   // suspended the channel.
   virtual void NotifySuspendedByCache(bool aIsSuspended) final override;
 
   virtual bool IsActive() final override;
 
   virtual bool IsHidden() final override;
 
+  // In order to create overlayImageContainer to support DOMHwMediaStream.
+  VideoFrameContainer* GetOverlayImageVideoFrameContainer();
+
   // Called by the media decoder and the video frame to get the
   // ImageContainer containing the video data.
   virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   layers::ImageContainer* GetImageContainer();
 
   // Dispatch events
   virtual nsresult DispatchAsyncEvent(const nsAString& aName) final override;
 
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -292,23 +292,24 @@ HTMLTextAreaElement::UpdatePlaceholderVi
 NS_IMETHODIMP_(bool)
 HTMLTextAreaElement::GetPlaceholderVisibility()
 {
   return mState.GetPlaceholderVisibility();
 }
 
 nsresult
 HTMLTextAreaElement::SetValueInternal(const nsAString& aValue,
-                                      bool aUserInput)
+                                      uint32_t aFlags)
 {
   // Need to set the value changed flag here, so that
   // nsTextControlFrame::UpdateValueDisplay retrieves the correct value
   // if needed.
   SetValueChanged(true);
-  if (!mState.SetValue(aValue, aUserInput, true)) {
+  aFlags |= nsTextEditorState::eSetValue_Notify;
+  if (!mState.SetValue(aValue, aFlags)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 HTMLTextAreaElement::SetValue(const nsAString& aValue)
@@ -318,33 +319,34 @@ HTMLTextAreaElement::SetValue(const nsAS
   // event, we should keep it that way. Otherwise, we should make sure the
   // element will not fire any event because of the script interaction.
   //
   // NOTE: this is currently quite expensive work (too much string
   // manipulation). We should probably optimize that.
   nsAutoString currentValue;
   GetValueInternal(currentValue, true);
 
-  nsresult rv = SetValueInternal(aValue, false);
+  nsresult rv =
+    SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mFocusedValue.Equals(currentValue)) {
     GetValueInternal(mFocusedValue, true);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 HTMLTextAreaElement::SetUserInput(const nsAString& aValue)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
-  return SetValueInternal(aValue, true);
+  return SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput);
 }
 
 NS_IMETHODIMP
 HTMLTextAreaElement::SetValueChanged(bool aValueChanged)
 {
   bool previousValue = mValueChanged;
 
   mValueChanged = aValueChanged;
@@ -963,17 +965,18 @@ HTMLTextAreaElement::SetRangeText(const 
         aSelectionEnd = mState.GetSelectionProperties().mEnd;
         aRv = NS_OK;
       }
     }
   }
 
   if (aStart <= aEnd) {
     value.Replace(aStart, aEnd - aStart, aReplacement);
-    nsresult rv = SetValueInternal(value, false);
+    nsresult rv =
+      SetValueInternal(value, nsTextEditorState::eSetValue_ByContent);
     if (NS_FAILED(rv)) {
       aRv.Throw(rv);
       return;
     }
   }
 
   uint32_t newEnd = aStart + aReplacement.Length();
   int32_t delta =  aReplacement.Length() - (aEnd - aStart);
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -317,18 +317,24 @@ protected:
    * Get the value, whether it is from the content or the frame.
    * @param aValue the value [out]
    * @param aIgnoreWrap whether to ignore the wrap attribute when getting the
    *        value.  If this is true, linebreaks will not be inserted even if
    *        wrap=hard.
    */
   void GetValueInternal(nsAString& aValue, bool aIgnoreWrap) const;
 
-  nsresult SetValueInternal(const nsAString& aValue,
-                            bool aUserInput);
+  /**
+   * Setting the value.
+   *
+   * @param aValue      String to set.
+   * @param aFlags      See nsTextEditorState::SetValueFlags.
+   */
+  nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
+
   nsresult GetSelectionRange(int32_t* aSelectionStart, int32_t* aSelectionEnd);
 
   /**
    * Common method to call from the various mutation observer methods.
    * aContent is a content node that's either the one that changed or its
    * parent; we should only respond to the change if aContent is non-anonymous.
    */
   void ContentChanged(nsIContent* aContent);
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -22,16 +22,17 @@
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIDOMHTMLTextAreaElement.h"
 #include "nsITransactionManager.h"
 #include "nsIControllerContext.h"
 #include "nsAttrValue.h"
 #include "nsAttrValueInlines.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMEventListener.h"
+#include "nsIEditorIMESupport.h"
 #include "nsIEditorObserver.h"
 #include "nsIWidget.h"
 #include "nsIDocumentEncoder.h"
 #include "nsISelectionPrivate.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIEditor.h"
 #include "nsTextEditRules.h"
@@ -1010,25 +1011,26 @@ nsTextInputListener::UpdateTextInputComm
   return domWindow->UpdateCommands(commandsToUpdate, sel, reason);
 }
 
 // END nsTextInputListener
 
 // nsTextEditorState
 
 nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement)
-  : mTextCtrlElement(aOwningElement),
-    mBoundFrame(nullptr),
-    mEverInited(false),
-    mEditorInitialized(false),
-    mInitializing(false),
-    mValueTransferInProgress(false),
-    mSelectionCached(true),
-    mSelectionRestoreEagerInit(false),
-    mPlaceholderVisibility(false)
+  : mTextCtrlElement(aOwningElement)
+  , mBoundFrame(nullptr)
+  , mEverInited(false)
+  , mEditorInitialized(false)
+  , mInitializing(false)
+  , mValueTransferInProgress(false)
+  , mSelectionCached(true)
+  , mSelectionRestoreEagerInit(false)
+  , mPlaceholderVisibility(false)
+  , mIsCommittingComposition(false)
 {
   MOZ_COUNT_CTOR(nsTextEditorState);
 }
 
 nsTextEditorState::~nsTextEditorState()
 {
   MOZ_COUNT_DTOR(nsTextEditorState);
   Clear();
@@ -1436,17 +1438,17 @@ nsTextEditorState::PrepareEditor(const n
     // Now call SetValue() which will make the necessary editor calls to set
     // the default value.  Make sure to turn off undo before setting the default
     // value, and turn it back on afterwards. This will make sure we can't undo
     // past the default value.
 
     rv = newEditor->EnableUndo(false);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    bool success = SetValue(defaultValue, false, false);
+    bool success = SetValue(defaultValue, eSetValue_Internal);
     NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
 
     rv = newEditor->EnableUndo(true);
     NS_ASSERTION(NS_SUCCEEDED(rv),"Transaction Manager must have failed");
 
     // Now restore the original editor flags.
     rv = newEditor->SetFlags(editorFlags);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -1700,17 +1702,17 @@ nsTextEditorState::UnbindFromFrame(nsTex
     mTextListener = nullptr;
   }
 
   mBoundFrame = nullptr;
 
   // Now that we don't have a frame any more, store the value in the text buffer.
   // The only case where we don't do this is if a value transfer is in progress.
   if (!mValueTransferInProgress) {
-    bool success = SetValue(value, false, false);
+    bool success = SetValue(value, eSetValue_Internal);
     // TODO Find something better to do if this fails...
     NS_ENSURE_TRUE_VOID(success);
   }
 
   if (mRootNode && mMutationObserver) {
     mRootNode->RemoveMutationObserver(mMutationObserver);
     mMutationObserver = nullptr;
   }
@@ -1855,16 +1857,25 @@ nsTextEditorState::GetMaxLength(int32_t*
   }
 
   return false;
 }
 
 void
 nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const
 {
+  // While SetValue() is being called and requesting to commit composition to
+  // IME, GetValue() may be called for appending text or something.  Then, we
+  // need to return the latest aValue of SetValue() since the value hasn't
+  // been set to the editor yet.
+  if (mIsCommittingComposition) {
+    aValue = mValueBeingSet;
+    return;
+  }
+
   if (mEditor && mBoundFrame && (mEditorInitialized || !IsSingleLineTextControl())) {
     bool canCache = aIgnoreWrap && !IsSingleLineTextControl();
     if (canCache && !mCachedValue.IsEmpty()) {
       aValue = mCachedValue;
       return;
     }
 
     aValue.Truncate(); // initialize out param
@@ -1916,19 +1927,83 @@ nsTextEditorState::GetValue(nsAString& a
       mTextCtrlElement->GetDefaultValueFromContent(aValue);
     } else {
       aValue = NS_ConvertUTF8toUTF16(*mValue);
     }
   }
 }
 
 bool
-nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput,
-                            bool aSetValueChanged)
+nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags)
 {
+  nsAutoString newValue(aValue);
+
+  // While mIsCommittingComposition is true (that means that some event
+  // handlers which are fired during committing composition are the caller of
+  // this method), GetValue() uses mValueBeingSet for its result because the
+  // first calls of this methods hasn't set the value yet.  So, when it's true,
+  // we need to modify mValueBeingSet.  In this case, we will back to the first
+  // call of this method, then, mValueBeingSet will be truncated when
+  // mIsCommittingComposition is set false.  See below.
+  if (mIsCommittingComposition) {
+    mValueBeingSet = aValue;
+  }
+
+  // Note that if this may be called during reframe of the editor.  In such
+  // case, we shouldn't commit composition.  Therefore, when this is called
+  // for internal processing, we shouldn't commit the composition.
+  if (aFlags & (eSetValue_BySetUserInput | eSetValue_ByContent)) {
+    if (EditorHasComposition()) {
+      // When this is called recursively, there shouldn't be composition.
+      if (NS_WARN_IF(mIsCommittingComposition)) {
+        // Don't request to commit composition again.  But if it occurs,
+        // we should skip to set the new value to the editor here.  It should
+        // be set later with the updated mValueBeingSet.
+        return true;
+      }
+      // If there is composition, need to commit composition first because
+      // other browsers do that.
+      // NOTE: We don't need to block nested calls of this because input nor
+      //       other events won't be fired by setting values and script blocker
+      //       is used during setting the value to the editor.  IE also allows
+      //       to set the editor value on the input event which is caused by
+      //       forcibly committing composition.
+      if (nsContentUtils::IsSafeToRunScript()) {
+        WeakPtr<nsTextEditorState> self(this);
+        // WARNING: During this call, compositionupdate, compositionend, input
+        // events will be fired.  Therefore, everything can occur.  E.g., the
+        // document may be unloaded.
+        mValueBeingSet = aValue;
+        mIsCommittingComposition = true;
+        nsCOMPtr<nsIEditorIMESupport> editorIMESupport =
+                                        do_QueryInterface(mEditor);
+        MOZ_RELEASE_ASSERT(editorIMESupport);
+        nsresult rv = editorIMESupport->ForceCompositionEnd();
+        if (!self.get()) {
+          return true;
+        }
+        mIsCommittingComposition = false;
+        // If this is called recursively during committing composition and
+        // some of them may be skipped above.  Therefore, we need to set
+        // value to the editor with the aValue of the latest call.
+        newValue = mValueBeingSet;
+        // When mIsCommittingComposition is false, mValueBeingSet won't be
+        // used.  Therefore, let's clear it.
+        mValueBeingSet.Truncate();
+        if (NS_FAILED(rv)) {
+          NS_WARNING("nsTextEditorState failed to commit composition");
+          return true;
+        }
+      } else {
+        NS_WARNING("SetValue() is called when there is composition but "
+                   "it's not safe to request to commit the composition");
+      }
+    }
+  }
+
   if (mEditor && mBoundFrame) {
     // The InsertText call below might flush pending notifications, which
     // could lead into a scheduled PrepareEditor to be called.  That will
     // lead to crashes (or worse) because we'd be initializing the editor
     // before InsertText returns.  This script blocker makes sure that
     // PrepareEditor cannot be called prematurely.
     nsAutoScriptBlocker scriptBlocker;
 
@@ -1940,29 +2015,23 @@ nsTextEditorState::SetValue(const nsAStr
 #endif
 
     nsAutoString currentValue;
     mBoundFrame->GetText(currentValue);
 
     nsWeakFrame weakFrame(mBoundFrame);
 
     // this is necessary to avoid infinite recursion
-    if (!currentValue.Equals(aValue))
+    if (!currentValue.Equals(newValue))
     {
       ValueSetter valueSetter(mEditor);
 
       // \r is an illegal character in the dom, but people use them,
       // so convert windows and mac platform linebreaks to \n:
-      // Unfortunately aValue is declared const, so we have to copy
-      // in order to do this substitution.
-      nsString newValue;
-      if (!newValue.Assign(aValue, fallible)) {
-        return false;
-      }
-      if (aValue.FindChar(char16_t('\r')) != -1) {
+      if (newValue.FindChar(char16_t('\r')) != -1) {
         if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) {
           return false;
         }
       }
 
       nsCOMPtr<nsIDOMDocument> domDoc;
       mEditor->GetDocument(getter_AddRefs(domDoc));
       if (!domDoc) {
@@ -2015,17 +2084,18 @@ nsTextEditorState::SetValue(const nsAStr
         mEditor->GetFlags(&savedFlags);
         flags = savedFlags;
         flags &= ~(nsIPlaintextEditor::eEditorDisabledMask);
         flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask);
         flags |= nsIPlaintextEditor::eEditorDontEchoPassword;
         mEditor->SetFlags(flags);
 
         mTextListener->SettingValue(true);
-        mTextListener->SetValueChanged(aSetValueChanged);
+        bool notifyValueChanged = !!(aFlags & eSetValue_Notify);
+        mTextListener->SetValueChanged(notifyValueChanged);
 
         // Also don't enforce max-length here
         int32_t savedMaxLength;
         plaintextEditor->GetMaxTextLength(&savedMaxLength);
         plaintextEditor->SetMaxTextLength(-1);
 
         if (insertValue.IsEmpty()) {
           mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
@@ -2038,17 +2108,17 @@ nsTextEditorState::SetValue(const nsAStr
 
         if (!weakFrame.IsAlive()) {
           // If the frame was destroyed because of a flush somewhere inside
           // InsertText, mBoundFrame here will be false.  But it's also possible
           // for the frame to go away because of another reason (such as deleting
           // the existing selection -- see bug 574558), in which case we don't
           // need to reset the value here.
           if (!mBoundFrame) {
-            return SetValue(newValue, false, aSetValueChanged);
+            return SetValue(newValue, aFlags & eSetValue_Notify);
           }
           return true;
         }
 
         if (!IsSingleLineTextControl()) {
           if (!mCachedValue.Assign(newValue, fallible)) {
             return false;
           }
@@ -2060,17 +2130,17 @@ nsTextEditorState::SetValue(const nsAStr
           selPriv->EndBatchChanges();
       }
     }
   } else {
     if (!mValue) {
       mValue = new nsCString;
     }
     nsString value;
-    if (!value.Assign(aValue, fallible)) {
+    if (!value.Assign(newValue, fallible)) {
       return false;
     }
     if (!nsContentUtils::PlatformToDOMLineBreaks(value, fallible)) {
       return false;
     }
     if (!CopyUTF16toUTF8(value, *mValue, fallible)) {
       return false;
     }
@@ -2160,16 +2230,26 @@ nsTextEditorState::HideSelectionIfBlurre
 {
   MOZ_ASSERT(mSelCon, "Should have a selection controller if we have a frame!");
   nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
   if (!nsContentUtils::IsFocusedContent(content)) {
     mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
   }
 }
 
+bool
+nsTextEditorState::EditorHasComposition()
+{
+  bool isComposing = false;
+  nsCOMPtr<nsIEditorIMESupport> editorIMESupport = do_QueryInterface(mEditor);
+  return editorIMESupport &&
+         NS_SUCCEEDED(editorIMESupport->GetComposing(&isComposing)) &&
+         isComposing;
+}
+
 NS_IMPL_ISUPPORTS(nsAnonDivObserver, nsIMutationObserver)
 
 void
 nsAnonDivObserver::CharacterDataChanged(nsIDocument*             aDocument,
                                         nsIContent*              aContent,
                                         CharacterDataChangeInfo* aInfo)
 {
   mTextEditorState->ClearValueCache();
--- a/dom/html/nsTextEditorState.h
+++ b/dom/html/nsTextEditorState.h
@@ -138,19 +138,30 @@ public:
   nsIEditor* GetEditor();
   nsISelectionController* GetSelectionController() const;
   nsFrameSelection* GetConstFrameSelection();
   nsresult BindToFrame(nsTextControlFrame* aFrame);
   void UnbindFromFrame(nsTextControlFrame* aFrame);
   nsresult PrepareEditor(const nsAString *aValue = nullptr);
   void InitializeKeyboardEventListeners();
 
+  enum SetValueFlags
+  {
+    // The call is for internal processing.
+    eSetValue_Internal              = 0,
+    // The value is changed by a call of setUserInput() from chrome.
+    eSetValue_BySetUserInput        = 1 << 0,
+    // The value is changed by changing value attribute of the element or
+    // something like setRangeText().
+    eSetValue_ByContent             = 1 << 1,
+    // Whether the value change should be notified to the frame/contet nor not.
+    eSetValue_Notify                = 1 << 2
+  };
   MOZ_WARN_UNUSED_RESULT bool SetValue(const nsAString& aValue,
-                                       bool aUserInput,
-                                       bool aSetValueAsChanged);
+                                       uint32_t aFlags);
   void GetValue(nsAString& aValue, bool aIgnoreWrap) const;
   void EmptyValue() { if (mValue) mValue->Truncate(); }
   bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; }
 
   nsresult CreatePlaceholderNode();
 
   mozilla::dom::Element* GetRootNode() {
     if (!mRootNode)
@@ -239,16 +250,18 @@ private:
   void Clear();
 
   nsresult InitializeRootNode();
 
   void FinishedRestoringSelection();
 
   mozilla::dom::HTMLInputElement* GetParentNumberControl(nsFrame* aFrame) const;
 
+  bool EditorHasComposition();
+
   class InitializationGuard {
   public:
     explicit InitializationGuard(nsTextEditorState& aState) :
       mState(aState),
       mGuardSet(false)
     {
       if (!mState.mInitializing) {
         mGuardSet = true;
@@ -278,24 +291,30 @@ private:
   nsCOMPtr<nsIEditor> mEditor;
   nsCOMPtr<mozilla::dom::Element> mRootNode;
   nsCOMPtr<mozilla::dom::Element> mPlaceholderDiv;
   nsTextControlFrame* mBoundFrame;
   nsRefPtr<nsTextInputListener> mTextListener;
   nsAutoPtr<nsCString> mValue;
   nsRefPtr<nsAnonDivObserver> mMutationObserver;
   mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control.
+  // mValueBeingSet is available only while SetValue() is requesting to commit
+  // composition.  I.e., this is valid only while mIsCommittingComposition is
+  // true.  While active composition is being committed, GetValue() needs
+  // the latest value which is set by SetValue().  So, this is cache for that.
+  nsString mValueBeingSet;
+  SelectionProperties mSelectionProperties;
   bool mEverInited; // Have we ever been initialized?
   bool mEditorInitialized;
   bool mInitializing; // Whether we're in the process of initialization
   bool mValueTransferInProgress; // Whether a value is being transferred to the frame
   bool mSelectionCached; // Whether mSelectionProperties is valid
   mutable bool mSelectionRestoreEagerInit; // Whether we're eager initing because of selection restore
-  SelectionProperties mSelectionProperties;
   bool mPlaceholderVisibility;
+  bool mIsCommittingComposition;
 };
 
 inline void
 ImplCycleCollectionUnlink(nsTextEditorState& aField)
 {
   aField.Unlink();
 }
 
--- a/dom/ipc/PPluginWidget.ipdl
+++ b/dom/ipc/PPluginWidget.ipdl
@@ -36,12 +36,18 @@ parent:
 
   /**
    * Returns NS_NATIVE_PLUGIN_PORT and its variants: a sharable native
    * window for plugins. On Linux, this returns an XID for a socket widget
    * embedded in the chrome side native window. On Windows this returns the
    * native HWND of the plugin widget.
    */
   sync GetNativePluginPort() returns (uintptr_t value);
+
+  /**
+   * Sends an NS_NATIVE_CHILD_WINDOW to be adopted by the widget's native window
+   * on the chrome side. This is only currently used on Windows.
+   */
+  sync SetNativeChildWindow(uintptr_t childWindow);
 };
 
 }
 }
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2640,40 +2640,29 @@ void
 TabParent::TryCacheDPIAndScale()
 {
   if (mDPI > 0) {
     return;
   }
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
 
-  if (!widget && mFrameElement) {
-    // Even if we don't have a widget (e.g. because we're display:none), there's
-    // probably a widget somewhere in the hierarchy our frame element lives in.
-    widget = nsContentUtils::WidgetForDocument(mFrameElement->OwnerDoc());
-  }
-
   if (widget) {
     mDPI = widget->GetDPI();
     mDefaultScale = widget->GetDefaultScale();
   }
 }
 
 already_AddRefed<nsIWidget>
 TabParent::GetWidget() const
 {
-  nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
-  if (!content)
+  if (!mFrameElement) {
     return nullptr;
-
-  nsIFrame *frame = content->GetPrimaryFrame();
-  if (!frame)
-    return nullptr;
-
-  nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
+  }
+  nsCOMPtr<nsIWidget> widget = nsContentUtils::WidgetForDocument(mFrameElement->OwnerDoc());
   return widget.forget();
 }
 
 void
 TabParent::ApzAwareEventRoutingToChild(ScrollableLayerGuid* aOutTargetGuid,
                                        uint64_t* aOutInputBlockId,
                                        nsEventStatus* aOutApzResponse)
 {
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -13,19 +13,23 @@
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "MediaStreamGraph.h"
 #include "AudioStreamTrack.h"
 #include "VideoStreamTrack.h"
+#include "Layers.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::layers;
+
+const TrackID TRACK_VIDEO_PRIMARY = 1;
 
 class DOMMediaStream::StreamListener : public MediaStreamListener {
 public:
   explicit StreamListener(DOMMediaStream* aStream)
     : mStream(aStream)
   {}
 
   // Main thread only
@@ -663,8 +667,117 @@ DOMAudioNodeMediaStream::CreateTrackUnio
                                                 AudioNode* aNode,
                                                 MediaStreamGraph* aGraph)
 {
   nsRefPtr<DOMAudioNodeMediaStream> stream = new DOMAudioNodeMediaStream(aNode);
   stream->InitTrackUnionStream(aWindow, aGraph);
   return stream.forget();
 }
 
+DOMHwMediaStream::DOMHwMediaStream()
+{
+#ifdef MOZ_WIDGET_GONK
+  mImageContainer = LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS_OVERLAY);
+  nsRefPtr<Image> img = mImageContainer->CreateImage(ImageFormat::OVERLAY_IMAGE);
+  mOverlayImage = static_cast<layers::OverlayImage*>(img.get());
+  mImageContainer->SetCurrentImage(mOverlayImage);
+#endif
+}
+
+DOMHwMediaStream::~DOMHwMediaStream()
+{
+}
+
+already_AddRefed<DOMHwMediaStream>
+DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow)
+{
+  nsRefPtr<DOMHwMediaStream> stream = new DOMHwMediaStream();
+  stream->InitSourceStream(aWindow);
+  stream->Init(stream->GetStream());
+
+  return stream.forget();
+}
+
+void
+DOMHwMediaStream::Init(MediaStream* stream)
+{
+  SourceMediaStream* srcStream = stream->AsSourceStream();
+
+  if (srcStream) {
+    VideoSegment segment;
+#ifdef MOZ_WIDGET_GONK
+    const StreamTime delta = STREAM_TIME_MAX; // Because MediaStreamGraph will run out frames in non-autoplay mode,
+                                              // we must give it bigger frame length to cover this situation.
+    mImageData.mOverlayId = DEFAULT_IMAGE_ID;
+    mImageData.mSize.width = DEFAULT_IMAGE_WIDTH;
+    mImageData.mSize.height = DEFAULT_IMAGE_HEIGHT;
+    mOverlayImage->SetData(mImageData);
+
+    nsRefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
+    mozilla::gfx::IntSize size = image->GetSize();
+
+    segment.AppendFrame(image.forget(), delta, size);
+#endif
+    srcStream->AddTrack(TRACK_VIDEO_PRIMARY, 0, new VideoSegment());
+    srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
+    srcStream->FinishAddTracks();
+    srcStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+  }
+}
+
+int32_t
+DOMHwMediaStream::RequestOverlayId()
+{
+#ifdef MOZ_WIDGET_GONK
+  return mOverlayImage->GetOverlayId();
+#else
+  return -1;
+#endif
+}
+
+void
+DOMHwMediaStream::SetImageSize(uint32_t width, uint32_t height)
+{
+#ifdef MOZ_WIDGET_GONK
+  OverlayImage::Data imgData;
+
+  imgData.mOverlayId = mOverlayImage->GetOverlayId();
+  imgData.mSize = IntSize(width, height);
+  mOverlayImage->SetData(imgData);
+#endif
+
+  SourceMediaStream* srcStream = GetStream()->AsSourceStream();
+  StreamBuffer::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY);
+
+  if (!track || !track->GetSegment()) {
+    return;
+  }
+
+#ifdef MOZ_WIDGET_GONK
+  // Clear the old segment.
+  // Changing the existing content of segment is a Very BAD thing, and this way will
+  // confuse consumers of MediaStreams.
+  // It is only acceptable for DOMHwMediaStream
+  // because DOMHwMediaStream doesn't have consumers of TV streams currently.
+  track->GetSegment()->Clear();
+
+  // Change the image size.
+  const StreamTime delta = STREAM_TIME_MAX;
+  nsRefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
+  mozilla::gfx::IntSize size = image->GetSize();
+  VideoSegment segment;
+
+  segment.AppendFrame(image.forget(), delta, size);
+  srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
+#endif
+}
+void
+DOMHwMediaStream::SetOverlayId(int32_t aOverlayId)
+{
+#ifdef MOZ_WIDGET_GONK
+  OverlayImage::Data imgData;
+
+  imgData.mOverlayId = aOverlayId;
+  imgData.mSize = mOverlayImage->GetSize();
+
+  mOverlayImage->SetData(imgData);
+#endif
+}
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef NSDOMMEDIASTREAM_H_
 #define NSDOMMEDIASTREAM_H_
 
+#include "ImageContainer.h"
+
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "StreamBuffer.h"
 #include "nsIDOMWindow.h"
 #include "nsIPrincipal.h"
 #include "mozilla/PeerIdentity.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/CORSMode.h"
@@ -24,16 +26,17 @@
 // X11 has a #define for CurrentTime. Unbelievable :-(.
 // See dom/media/webaudio/AudioContext.h for more fun!
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
 
 namespace mozilla {
 
+class DOMHwMediaStream;
 class DOMLocalMediaStream;
 class MediaStream;
 class MediaEngineSource;
 class MediaStreamGraph;
 
 namespace dom {
 class AudioNode;
 class HTMLCanvasElement;
@@ -42,16 +45,21 @@ class AudioStreamTrack;
 class VideoStreamTrack;
 class AudioTrack;
 class VideoTrack;
 class AudioTrackList;
 class VideoTrackList;
 class MediaTrackListListener;
 }
 
+namespace layers {
+class ImageContainer;
+class OverlayImage;
+}
+
 class MediaStreamDirectListener;
 
 #define NS_DOMMEDIASTREAM_IID \
 { 0x8cb65468, 0x66c0, 0x444e, \
   { 0x89, 0x9f, 0x89, 0x1d, 0x9e, 0xd2, 0xbe, 0x7c } }
 
 /**
  * DOM wrapper for MediaStreams.
@@ -109,16 +117,17 @@ public:
    * Overridden in DOMLocalMediaStreams to allow getUserMedia to disable
    * media at the SourceMediaStream.
    */
   virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled);
 
   virtual void StopTrack(TrackID aTrackID);
 
   virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; }
+  virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; }
 
   bool IsFinished();
   /**
    * Returns a principal indicating who may access this stream. The stream contents
    * can only be accessed by principals subsuming this principal.
    */
   nsIPrincipal* GetPrincipal() { return mPrincipal; }
   mozilla::CORSMode GetCORSMode();
@@ -370,11 +379,45 @@ protected:
   ~DOMAudioNodeMediaStream();
 
 private:
   // If this object wraps a stream owned by an AudioNode, we need to ensure that
   // the node isn't cycle-collected too early.
   nsRefPtr<AudioNode> mStreamNode;
 };
 
+class DOMHwMediaStream : public DOMLocalMediaStream
+{
+  typedef mozilla::gfx::IntSize IntSize;
+  typedef layers::ImageContainer ImageContainer;
+#ifdef MOZ_WIDGET_GONK
+  typedef layers::OverlayImage OverlayImage;
+  typedef layers::OverlayImage::Data Data;
+#endif
+
+public:
+  DOMHwMediaStream();
+
+  static already_AddRefed<DOMHwMediaStream> CreateHwStream(nsIDOMWindow* aWindow);
+  virtual DOMHwMediaStream* AsDOMHwMediaStream() override { return this; }
+  int32_t RequestOverlayId();
+  void SetOverlayId(int32_t aOverlayId);
+  void SetImageSize(uint32_t width, uint32_t height);
+
+protected:
+  ~DOMHwMediaStream();
+
+private:
+  void Init(MediaStream* aStream);
+
+#ifdef MOZ_WIDGET_GONK
+  nsRefPtr<ImageContainer> mImageContainer;
+  const int DEFAULT_IMAGE_ID = 0x01;
+  const int DEFAULT_IMAGE_WIDTH = 400;
+  const int DEFAULT_IMAGE_HEIGHT = 300;
+  nsRefPtr<OverlayImage> mOverlayImage;
+  Data mImageData;
+#endif
+};
+
 }
 
 #endif /* NSDOMMEDIASTREAM_H_ */
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -2491,16 +2491,22 @@ SourceMediaStream::FinishAddTracks()
 {
   MutexAutoLock lock(mMutex);
   mUpdateTracks.MoveElementsFrom(mPendingTracks);
   if (GraphImpl()) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
+StreamBuffer::Track*
+SourceMediaStream::FindTrack(TrackID aID)
+{
+  return mBuffer.FindTrack(aID);
+}
+
 void
 SourceMediaStream::ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment)
 {
   if (aSegment->GetType() != MediaSegment::AUDIO ||
       aTrackData->mInputRate == GraphImpl()->GraphRate()) {
     return;
   }
   AudioSegment* segment = static_cast<AudioSegment*>(aSegment);
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -797,16 +797,21 @@ public:
 
   /**
    * Call after a series of AddTrack or AddAudioTrack calls to implement
    * any pending track adds.
    */
   void FinishAddTracks();
 
   /**
+   * Find track by track id.
+   */
+  StreamBuffer::Track* FindTrack(TrackID aID);
+
+  /**
    * Append media data to a track. Ownership of aSegment remains with the caller,
    * but aSegment is emptied.
    * Returns false if the data was not appended because no such track exists
    * or the stream was already finished.
    */
   bool AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment = nullptr);
   /**
    * Returns true if the buffer currently has enough data.
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -8,17 +8,17 @@ UNIFIED_SOURCES += [
     'MockMediaResource.cpp',
     'TestAudioCompactor.cpp',
     'TestGMPCrossOrigin.cpp',
     'TestGMPRemoveAndDelete.cpp',
     'TestIntervalSet.cpp',
     'TestMediaPromise.cpp',
     'TestMP3Demuxer.cpp',
     'TestMP4Demuxer.cpp',
-    'TestMP4Reader.cpp',
+    # 'TestMP4Reader.cpp', disabled so we can turn check tests back on (bug 1175752)
     'TestTrackEncoder.cpp',
     'TestVideoSegment.cpp',
     'TestWebMBuffered.cpp',
 ]
 
 if CONFIG['MOZ_EME']:
     UNIFIED_SOURCES += [
         'TestEME.cpp',
--- a/dom/media/webspeech/synth/SpeechSynthesisUtterance.cpp
+++ b/dom/media/webspeech/synth/SpeechSynthesisUtterance.cpp
@@ -50,17 +50,17 @@ SpeechSynthesisUtterance::GetParentObjec
 {
   return GetOwner();
 }
 
 already_AddRefed<SpeechSynthesisUtterance>
 SpeechSynthesisUtterance::Constructor(GlobalObject& aGlobal,
                                       ErrorResult& aRv)
 {
-  return Constructor(aGlobal, NS_LITERAL_STRING(""), aRv);
+  return Constructor(aGlobal, EmptyString(), aRv);
 }
 
 already_AddRefed<SpeechSynthesisUtterance>
 SpeechSynthesisUtterance::Constructor(GlobalObject& aGlobal,
                                       const nsAString& aText,
                                       ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aGlobal.GetAsSupports());
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -294,17 +294,17 @@ nsSpeechTask::DispatchStartImpl(const ns
 
   MOZ_ASSERT(mUtterance);
   NS_ENSURE_TRUE(mUtterance->mState == SpeechSynthesisUtterance::STATE_PENDING,
                  NS_ERROR_NOT_AVAILABLE);
 
   mUtterance->mState = SpeechSynthesisUtterance::STATE_SPEAKING;
   mUtterance->mChosenVoiceURI = aUri;
   mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("start"), 0, 0,
-                                           NS_LITERAL_STRING(""));
+                                           EmptyString());
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchEnd(float aElapsedTime, uint32_t aCharIndex)
 {
   if (!mIndirectAudio) {
@@ -365,17 +365,17 @@ nsSpeechTask::DispatchPauseImpl(float aE
   MOZ_ASSERT(mUtterance);
   NS_ENSURE_FALSE(mUtterance->mPaused, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_FALSE(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED,
                   NS_ERROR_NOT_AVAILABLE);
 
   mUtterance->mPaused = true;
   mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("pause"),
                                            aCharIndex, aElapsedTime,
-                                           NS_LITERAL_STRING(""));
+                                           EmptyString());
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchResume(float aElapsedTime, uint32_t aCharIndex)
 {
   if (!mIndirectAudio) {
     NS_WARNING("Can't call DispatchResume() from a direct audio speech service");
@@ -392,17 +392,17 @@ nsSpeechTask::DispatchResumeImpl(float a
   MOZ_ASSERT(mUtterance);
   NS_ENSURE_TRUE(mUtterance->mPaused, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_FALSE(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED,
                   NS_ERROR_NOT_AVAILABLE);
 
   mUtterance->mPaused = false;
   mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("resume"),
                                            aCharIndex, aElapsedTime,
-                                           NS_LITERAL_STRING(""));
+                                           EmptyString());
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchError(float aElapsedTime, uint32_t aCharIndex)
 {
   if (!mIndirectAudio) {
     NS_WARNING("Can't call DispatchError() from a direct audio speech service");
@@ -417,17 +417,17 @@ nsSpeechTask::DispatchErrorImpl(float aE
 {
   MOZ_ASSERT(mUtterance);
   NS_ENSURE_FALSE(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED,
                   NS_ERROR_NOT_AVAILABLE);
 
   mUtterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
   mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("error"),
                                            aCharIndex, aElapsedTime,
-                                           NS_LITERAL_STRING(""));
+                                           EmptyString());
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchBoundary(const nsAString& aName,
                                float aElapsedTime, uint32_t aCharIndex)
 {
   if (!mIndirectAudio) {
--- a/dom/plugins/base/nsPluginNativeWindow.h
+++ b/dom/plugins/base/nsPluginNativeWindow.h
@@ -44,17 +44,17 @@ public:
     return NS_OK;
   }
   nsresult SetPluginInstance(nsNPAPIPluginInstance *aPluginInstance) { 
     if (mPluginInstance != aPluginInstance)
       mPluginInstance = aPluginInstance;
     return NS_OK;
   }
 
-  nsresult GetPluginWidget(nsIWidget **aWidget) {
+  nsresult GetPluginWidget(nsIWidget **aWidget) const {
     NS_IF_ADDREF(*aWidget = mWidget);
     return NS_OK;
   }
   nsresult SetPluginWidget(nsIWidget *aWidget) { 
     mWidget = aWidget;
     return NS_OK;
   }
 
--- a/dom/plugins/ipc/PPluginInstance.ipdl
+++ b/dom/plugins/ipc/PPluginInstance.ipdl
@@ -63,17 +63,20 @@ intr protocol PPluginInstance
   manages PBrowserStream;
   manages PPluginStream;
   manages PStreamNotify;
   manages PPluginSurface;
 
 child:
   intr __delete__();
 
-  intr NPP_SetWindow(NPRemoteWindow window);
+  // Return value is only used on Windows and only when the window needs its
+  // parent set to the chrome widget native window.
+  intr NPP_SetWindow(NPRemoteWindow window)
+    returns (NPRemoteWindow childWindowToBeAdopted);
 
   intr NPP_GetValue_NPPVpluginWantsAllNetworkStreams()
     returns (bool value, NPError result);
 
   // this message is not used on non-X platforms
   intr NPP_GetValue_NPPVpluginNeedsXEmbed()
     returns (bool value, NPError result);
 
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -1119,17 +1119,18 @@ void PluginInstanceChild::DeleteWindow()
 #endif
 
   // We don't have to keep the plug-in window ID any longer.
   mWindow.window = nullptr;
 }
 #endif
 
 bool
-PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow)
+PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow,
+                                         NPRemoteWindow* aChildWindowToBeAdopted)
 {
     PLUGIN_LOG_DEBUG(("%s (aWindow=<window: 0x%lx, x: %d, y: %d, width: %d, height: %d>)",
                       FULLFUNCTION,
                       aWindow.window,
                       aWindow.x, aWindow.y,
                       aWindow.width, aWindow.height));
     NS_ASSERTION(!mLayersRendering && !mPendingPluginCall,
                  "Shouldn't be receiving NPP_SetWindow with layer rendering");
@@ -1210,19 +1211,42 @@ PluginInstanceChild::AnswerNPP_SetWindow
               aWindow.height == 0) {
             // Skip SetWindow call for hidden QuickTime plugins
             return true;
           }
 
           if (!CreatePluginWindow())
               return false;
 
-          ReparentPluginWindow(reinterpret_cast<HWND>(aWindow.window));
           SizePluginWindow(aWindow.width, aWindow.height);
 
+          // If the window is not our parent set the return child window so that
+          // it can be re-parented in the chrome process. Re-parenting now
+          // happens there as we might not have sufficient permission.
+          // Also, this needs to be after SizePluginWindow because SetWindowPos
+          // relies on things that it sets.
+          HWND parentWindow = reinterpret_cast<HWND>(aWindow.window);
+          if (mPluginParentHWND != parentWindow  && IsWindow(parentWindow)) {
+              mPluginParentHWND = parentWindow;
+              aChildWindowToBeAdopted->window =
+                  reinterpret_cast<uint64_t>(mPluginWindowHWND);
+          } else {
+              // Now we know that the window has the correct parent we can show
+              // it. The actual visibility is controlled by its parent.
+              // First time round, these calls are made by our caller after the
+              // parent is set.
+              ShowWindow(mPluginWindowHWND, SW_SHOWNA);
+
+              // This used to be called in SizePluginWindow, but we need to make
+              // sure that mPluginWindowHWND has had it's parent set correctly,
+              // otherwise it can cause a focus issue.
+              SetWindowPos(mPluginWindowHWND, nullptr, 0, 0, aWindow.width,
+                           aWindow.height, SWP_NOZORDER | SWP_NOREPOSITION);
+          }
+
           mWindow.window = (void*)mPluginWindowHWND;
           mWindow.x = aWindow.x;
           mWindow.y = aWindow.y;
           mWindow.width = aWindow.width;
           mWindow.height = aWindow.height;
           mWindow.type = aWindow.type;
 
           if (mPluginIface->setwindow) {
@@ -1401,43 +1425,22 @@ PluginInstanceChild::DestroyPluginWindow
             mPluginWndProc = 0;
         }
         DestroyWindow(mPluginWindowHWND);
         mPluginWindowHWND = 0;
     }
 }
 
 void
-PluginInstanceChild::ReparentPluginWindow(HWND hWndParent)
-{
-    if (hWndParent != mPluginParentHWND && IsWindow(hWndParent)) {
-        // Fix the child window's style to be a child window.
-        LONG_PTR style = GetWindowLongPtr(mPluginWindowHWND, GWL_STYLE);
-        style |= WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
-        style &= ~WS_POPUP;
-        SetWindowLongPtr(mPluginWindowHWND, GWL_STYLE, style);
-
-        // Do the reparenting.
-        SetParent(mPluginWindowHWND, hWndParent);
-
-        // Make sure we're visible.
-        ShowWindow(mPluginWindowHWND, SW_SHOWNA);
-    }
-    mPluginParentHWND = hWndParent;
-}
-
-void
 PluginInstanceChild::SizePluginWindow(int width,
                                       int height)
 {
     if (mPluginWindowHWND) {
         mPluginSize.x = width;
         mPluginSize.y = height;
-        SetWindowPos(mPluginWindowHWND, nullptr, 0, 0, width, height,
-                     SWP_NOZORDER | SWP_NOREPOSITION);
     }
 }
 
 // See chromium's webplugin_delegate_impl.cc for explanation of this function.
 // static
 LRESULT CALLBACK
 PluginInstanceChild::DummyWindowProc(HWND hWnd,
                                      UINT message,
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -60,17 +60,18 @@ class PluginInstanceChild : public PPlug
                                              LPARAM lParam);
     static LRESULT CALLBACK PluginWindowProcInternal(HWND hWnd,
                                                      UINT message,
                                                      WPARAM wParam,
                                                      LPARAM lParam);
 #endif
 
 protected:
-    virtual bool AnswerNPP_SetWindow(const NPRemoteWindow& window) override;
+    bool AnswerNPP_SetWindow(const NPRemoteWindow& window,
+                             NPRemoteWindow* aChildWindowToBeAdopted) override;
 
     virtual bool
     AnswerNPP_GetValue_NPPVpluginWantsAllNetworkStreams(bool* wantsAllStreams, NPError* rv) override;
     virtual bool
     AnswerNPP_GetValue_NPPVpluginNeedsXEmbed(bool* needs, NPError* rv) override;
     virtual bool
     AnswerNPP_GetValue_NPPVpluginScriptableNPObject(PPluginScriptableObjectChild** value,
                                                     NPError* result) override;
@@ -273,17 +274,16 @@ private:
 
     virtual bool
     DeallocPPluginBackgroundDestroyerChild(PPluginBackgroundDestroyerChild* aActor) override;
 
 #if defined(OS_WIN)
     static bool RegisterWindowClass();
     bool CreatePluginWindow();
     void DestroyPluginWindow();
-    void ReparentPluginWindow(HWND hWndParent);
     void SizePluginWindow(int width, int height);
     int16_t WinlessHandleEvent(NPEvent& event);
     void CreateWinlessPopupSurrogate();
     void DestroyWinlessPopupSurrogate();
     void InitPopupMenuHook();
     void SetupFlashMsgThrottle();
     void UnhookWinlessFlashThrottle();
     void HookSetWindowLongPtr();
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -44,16 +44,18 @@
 #endif
 
 #if defined(OS_WIN)
 #include <windowsx.h>
 #include "gfxWindowsPlatform.h"
 #include "mozilla/plugins/PluginSurfaceParent.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
+#include "nsIWidget.h"
+#include "nsPluginNativeWindow.h"
 extern const wchar_t* kFlashFullscreenClass;
 #elif defined(MOZ_WIDGET_GTK)
 #include <gdk/gdk.h>
 #elif defined(XP_MACOSX)
 #include <ApplicationServices/ApplicationServices.h>
 #endif // defined(XP_MACOSX)
 
 // This is the pref used to determine whether to use Shumway on a Flash object
@@ -1020,18 +1022,40 @@ PluginInstanceParent::NPP_SetWindow(cons
 
 #if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
     const NPSetWindowCallbackStruct* ws_info =
       static_cast<NPSetWindowCallbackStruct*>(aWindow->ws_info);
     window.visualID = ws_info->visual ? ws_info->visual->visualid : None;
     window.colormap = ws_info->colormap;
 #endif
 
-    if (!CallNPP_SetWindow(window))
+    NPRemoteWindow childWindow;
+    if (!CallNPP_SetWindow(window, &childWindow)) {
         return NPERR_GENERIC_ERROR;
+    }
+
+#if defined(XP_WIN)
+    // If a child window is returned it means that we need to re-parent it.
+    if (childWindow.window) {
+        nsCOMPtr<nsIWidget> widget;
+        static_cast<const nsPluginNativeWindow*>(aWindow)->
+            GetPluginWidget(getter_AddRefs(widget));
+        if (widget) {
+            widget->SetNativeData(NS_NATIVE_CHILD_WINDOW,
+                                  static_cast<uintptr_t>(childWindow.window));
+        }
+
+        // Now it has got the correct parent, make sure it is visible.
+        // In subsequent calls to SetWindow these calls happen in the Child.
+        HWND childHWND = reinterpret_cast<HWND>(childWindow.window);
+        ShowWindow(childHWND, SW_SHOWNA);
+        SetWindowPos(childHWND, nullptr, 0, 0, window.width, window.height,
+                     SWP_NOZORDER | SWP_NOREPOSITION);
+    }
+#endif
 
     return NPERR_NO_ERROR;
 }
 
 NPError
 PluginInstanceParent::NPP_GetValue(NPPVariable aVariable,
                                    void* _retval)
 {
--- a/dom/plugins/ipc/PluginProcessParent.cpp
+++ b/dom/plugins/ipc/PluginProcessParent.cpp
@@ -91,27 +91,27 @@ AddSandboxAllowedFiles(int32_t aSandboxL
     if (aSandboxLevel > 2) {
         AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR);
         AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR,
                               NS_LITERAL_STRING("\\*"));
     }
 
     // Level 2 and above is now using low integrity, so we need to give write
     // access to the Flash directories.
+    // This should be made Flash specific (Bug 1171396).
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_APPDATA_DIR,
                           NS_LITERAL_STRING("\\Macromedia\\Flash Player\\*"));
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_APPDATA_DIR,
                           NS_LITERAL_STRING("\\Adobe\\Flash Player\\*"));
 
-#if defined(_X86_)
-    // Write access to the Temp directory should only be needed for 32-bit as
-    // it is used to turn off protected mode, which only applies to x86.
+    // Write access to the Temp directory is used to turn off protected mode
+    // and is needed in some mochitest crash tests.
+    // Bug 1171393 tracks removing this requirement.
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_OS_TEMP_DIR,
                           NS_LITERAL_STRING("\\*"));
-#endif
 }
 #endif
 
 bool
 PluginProcessParent::Launch(mozilla::UniquePtr<LaunchCompleteTask> aLaunchCompleteTask,
                             int32_t aSandboxLevel)
 {
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
--- a/dom/plugins/ipc/PluginWidgetParent.cpp
+++ b/dom/plugins/ipc/PluginWidgetParent.cpp
@@ -205,10 +205,25 @@ PluginWidgetParent::RecvGetNativePluginP
 #else
   *value = (uintptr_t)mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT);
   NS_ASSERTION(*value, "no native port??");
 #endif
   PWLOG("PluginWidgetParent::RecvGetNativeData() %p\n", (void*)*value);
   return true;
 }
 
+bool
+PluginWidgetParent::RecvSetNativeChildWindow(const uintptr_t& aChildWindow)
+{
+#if defined(XP_WIN)
+  ENSURE_CHANNEL;
+  PWLOG("PluginWidgetParent::RecvSetNativeChildWindow(%p)\n",
+        static_cast<void*>(aChildWindow));
+  mWidget->SetNativeData(NS_NATIVE_CHILD_WINDOW, aChildWindow);
+  return true;
+#else
+  NS_NOTREACHED("PluginWidgetParent::RecvSetNativeChildWindow not implemented!");
+  return false;
+#endif
+}
+
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/ipc/PluginWidgetParent.h
+++ b/dom/plugins/ipc/PluginWidgetParent.h
@@ -26,16 +26,17 @@ class PluginWidgetParent : public PPlugi
 public:
   PluginWidgetParent();
   virtual ~PluginWidgetParent();
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
   virtual bool RecvCreate(nsresult* aResult) override;
   virtual bool RecvSetFocus(const bool& aRaise) override;
   virtual bool RecvGetNativePluginPort(uintptr_t* value) override;
+  bool RecvSetNativeChildWindow(const uintptr_t& aChildWindow) override;
 
   // Helper for compositor checks on the channel
   bool ActorDestroyed() { return !mWidget; }
 
   // Called by PBrowser when it receives a Destroy() call from the child.
   void ParentDestroy();
 
   // Sets mWidget's parent
--- a/dom/tv/TVTuner.cpp
+++ b/dom/tv/TVTuner.cpp
@@ -192,18 +192,20 @@ TVTuner::GetStream() const
 {
   nsRefPtr<DOMMediaStream> stream = mStream;
   return stream.forget();
 }
 
 nsresult
 TVTuner::InitMediaStream()
 {
-  // TODO Instantiate |mStream| when bug 987498 is done.
+  nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(GetOwner());
+  nsRefPtr<DOMHwMediaStream> stream = DOMHwMediaStream::CreateHwStream(window);
 
+  mStream = stream.forget();
   return NS_OK;
 }
 
 nsresult
 TVTuner::DispatchCurrentSourceChangedEvent(TVSource* aSource)
 {
   TVCurrentSourceChangedEventInit init;
   init.mSource = aSource;
@@ -213,10 +215,22 @@ TVTuner::DispatchCurrentSourceChangedEve
                                              init);
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethodWithArg<nsCOMPtr<nsIDOMEvent>>(this,
                                                        &TVTuner::DispatchTVEvent,
                                                        event);
   return NS_DispatchToCurrentThread(runnable);
 }
 
+nsresult
+TVTuner::NotifyImageSizeChanged(uint32_t aWidth, uint32_t aHeight)
+{
+  DOMHwMediaStream* hwMediaStream = mStream->AsDOMHwMediaStream();
+
+  if (hwMediaStream) {
+    hwMediaStream->SetImageSize(aWidth, aHeight);
+  }
+
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/tv/TVTuner.h
+++ b/dom/tv/TVTuner.h
@@ -26,16 +26,17 @@ class TVSource;
 class TVTuner final : public DOMEventTargetHelper
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TVTuner, DOMEventTargetHelper)
 
   static already_AddRefed<TVTuner> Create(nsPIDOMWindow* aWindow,
                                           nsITVTunerData* aData);
+  nsresult NotifyImageSizeChanged(uint32_t aWidth, uint32_t aHeight);
 
   // WebIDL (internal functions)
 
   virtual JSObject* WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   nsresult SetCurrentSource(TVSourceType aSourceType);
 
   nsresult DispatchTVEvent(nsIDOMEvent* aEvent);
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -133,30 +133,43 @@ BufferRecycleBin::GetBuffer(uint32_t aSi
     return new uint8_t[aSize];
 
   uint32_t last = mRecycledBuffers.Length() - 1;
   uint8_t* result = mRecycledBuffers[last].forget();
   mRecycledBuffers.RemoveElementAt(last);
   return result;
 }
 
-ImageContainer::ImageContainer(int flag)
+ImageContainer::ImageContainer(ImageContainer::Mode flag)
 : mReentrantMonitor("ImageContainer.mReentrantMonitor"),
   mPaintCount(0),
   mPreviousImagePainted(false),
   mImageFactory(new ImageFactory()),
   mRecycleBin(new BufferRecycleBin()),
   mCompositionNotifySink(nullptr),
   mImageClient(nullptr)
 {
-  if (flag == ENABLE_ASYNC && ImageBridgeChild::IsCreated()) {
+  if (ImageBridgeChild::IsCreated()) {
     // the refcount of this ImageClient is 1. we don't use a RefPtr here because the refcount
     // of this class must be done on the ImageBridge thread.
-    mImageClient = ImageBridgeChild::GetSingleton()->CreateImageClient(CompositableType::IMAGE).take();
-    MOZ_ASSERT(mImageClient);
+    switch(flag) {
+      case SYNCHRONOUS:
+        break;
+      case ASYNCHRONOUS:
+        mImageClient = ImageBridgeChild::GetSingleton()->CreateImageClient(CompositableType::IMAGE).take();
+        MOZ_ASSERT(mImageClient);
+        break;
+      case ASYNCHRONOUS_OVERLAY:
+        mImageClient = ImageBridgeChild::GetSingleton()->CreateImageClient(CompositableType::IMAGE_OVERLAY).take();
+        MOZ_ASSERT(mImageClient);
+        break;
+      default:
+        MOZ_ASSERT(false, "This flag is invalid.");
+        break;
+    }
   }
 }
 
 ImageContainer::~ImageContainer()
 {
   if (IsAsync()) {
     ImageBridgeChild::DispatchReleaseImageClient(mImageClient);
   }
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -286,19 +286,19 @@ protected:
  * updates the shared state to point to the new image and the old image
  * is immediately released (not true in Normal or Asynchronous modes).
  */
 class ImageContainer final : public SupportsWeakPtr<ImageContainer> {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageContainer)
 public:
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ImageContainer)
 
-  enum { DISABLE_ASYNC = 0x0, ENABLE_ASYNC = 0x01 };
+  enum Mode { SYNCHRONOUS = 0x0, ASYNCHRONOUS = 0x01, ASYNCHRONOUS_OVERLAY = 0x02 };
 
-  explicit ImageContainer(int flag = 0);
+  explicit ImageContainer(ImageContainer::Mode flag = SYNCHRONOUS);
 
   /**
    * Create an Image in one of the given formats.
    * Picks the "best" format from the list and creates an Image of that
    * format.
    * Returns null if this backend does not support any of the formats.
    * Can be called on any thread. This method takes mReentrantMonitor
    * when accessing thread-shared state.
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -162,26 +162,19 @@ LayerManager::CreateDrawTarget(const Int
 #ifdef DEBUG
 void
 LayerManager::Mutated(Layer* aLayer)
 {
 }
 #endif  // DEBUG
 
 already_AddRefed<ImageContainer>
-LayerManager::CreateImageContainer()
+LayerManager::CreateImageContainer(ImageContainer::Mode flag)
 {
-  nsRefPtr<ImageContainer> container = new ImageContainer(ImageContainer::DISABLE_ASYNC);
-  return container.forget();
-}
-
-already_AddRefed<ImageContainer>
-LayerManager::CreateAsynchronousImageContainer()
-{
-  nsRefPtr<ImageContainer> container = new ImageContainer(ImageContainer::ENABLE_ASYNC);
+  nsRefPtr<ImageContainer> container = new ImageContainer(flag);
   return container.forget();
 }
 
 bool
 LayerManager::AreComponentAlphaLayersEnabled()
 {
   return gfxPrefs::ComponentAlphaEnabled();
 }
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -41,16 +41,17 @@
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsString.h"                   // for nsCString
 #include "nsTArray.h"                   // for nsTArray
 #include "nsTArrayForwardDeclare.h"     // for InfallibleTArray
 #include "nscore.h"                     // for nsACString, nsAString
 #include "mozilla/Logging.h"                      // for PRLogModuleInfo
 #include "nsIWidget.h"                  // For plugin window configuration information structs
 #include "gfxVR.h"
+#include "ImageContainer.h"
 
 class gfxContext;
 
 extern uint8_t gLayerManagerLayerBuilder;
 
 namespace mozilla {
 
 class ComputedTimingFunction;
@@ -76,17 +77,16 @@ class AnimationData;
 class AsyncPanZoomController;
 class ClientLayerManager;
 class Layer;
 class LayerMetricsWrapper;
 class PaintedLayer;
 class ContainerLayer;
 class ImageLayer;
 class ColorLayer;
-class ImageContainer;
 class CanvasLayer;
 class ReadbackLayer;
 class ReadbackProcessor;
 class RefLayer;
 class LayerComposite;
 class ShadowableLayer;
 class ShadowLayerForwarder;
 class LayerManagerComposite;
@@ -430,29 +430,22 @@ public:
    */
   virtual already_AddRefed<RefLayer> CreateRefLayer() { return nullptr; }
 
 
   /**
    * Can be called anytime, from any thread.
    *
    * Creates an Image container which forwards its images to the compositor within
-   * layer transactions on the main thread.
+   * layer transactions on the main thread or asynchronously using the ImageBridge IPDL protocol.
+   * In the case of asynchronous, If the protocol is not available, the returned ImageContainer
+   * will forward images within layer transactions.
    */
-  static already_AddRefed<ImageContainer> CreateImageContainer();
-
-  /**
-   * Can be called anytime, from any thread.
-   *
-   * Tries to create an Image container which forwards its images to the compositor
-   * asynchronously using the ImageBridge IPDL protocol. If the protocol is not
-   * available, the returned ImageContainer will forward images within layer
-   * transactions, just like if it was created with CreateImageContainer().
-   */
-  static already_AddRefed<ImageContainer> CreateAsynchronousImageContainer();
+  static already_AddRefed<ImageContainer> CreateImageContainer(ImageContainer::Mode flag
+                                                                = ImageContainer::SYNCHRONOUS);
 
   /**
    * Type of layer manager his is. This is to be used sparsely in order to
    * avoid a lot of Layers backend specific code. It should be used only when
    * Layers backend specific functionality is necessary.
    */
   virtual LayersBackend GetBackendType() = 0;
 
--- a/gfx/layers/composite/ImageHost.cpp
+++ b/gfx/layers/composite/ImageHost.cpp
@@ -358,16 +358,26 @@ ImageHostOverlay::GetRenderState()
 }
 
 void
 ImageHostOverlay::UseOverlaySource(OverlaySource aOverlay)
 {
   mOverlay = aOverlay;
 }
 
+IntSize
+ImageHostOverlay::GetImageSize() const
+{
+  if (mHasPictureRect) {
+    return IntSize(mPictureRect.width, mPictureRect.height);
+  }
+
+  return IntSize();
+}
+
 void
 ImageHostOverlay::PrintInfo(std::stringstream& aStream, const char* aPrefix)
 {
   aStream << aPrefix;
   aStream << nsPrintfCString("ImageHost (0x%p)", this).get();
 
   AppendToString(aStream, mPictureRect, " [picture-rect=", "]");
 
--- a/gfx/layers/composite/ImageHost.h
+++ b/gfx/layers/composite/ImageHost.h
@@ -107,17 +107,18 @@ public:
   virtual void Composite(EffectChain& aEffectChain,
                          float aOpacity,
                          const gfx::Matrix4x4& aTransform,
                          const gfx::Filter& aFilter,
                          const gfx::Rect& aClipRect,
                          const nsIntRegion* aVisibleRegion = nullptr) override;
   virtual LayerRenderState GetRenderState() override;
   virtual void UseOverlaySource(OverlaySource aOverlay) override;
-  virtual void SetPictureRect(const gfx::IntRect& aPictureRect) override
+  virtual gfx::IntSize GetImageSize() const override;
+  virtual void SetPictureRect(const nsIntRect& aPictureRect) override
   {
     mPictureRect = aPictureRect;
     mHasPictureRect = true;
   }
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix);
 protected:
   gfx::IntRect mPictureRect;
   bool mHasPictureRect;
--- a/gfx/layers/ipc/CompositorChild.cpp
+++ b/gfx/layers/ipc/CompositorChild.cpp
@@ -257,24 +257,27 @@ CompositorChild::RecvUpdatePluginConfigu
   // Now that we are on the main thread, update plugin widget config.
   // This should happen a little before we paint to the screen assuming
   // the main thread is running freely.
   DebugOnly<nsresult> rv;
   MOZ_ASSERT(NS_IsMainThread());
 
   // Tracks visible plugins we update, so we can hide any plugins we don't.
   nsTArray<uintptr_t> visiblePluginIds;
-
+  nsIWidget* parent = nullptr;
   for (uint32_t pluginsIdx = 0; pluginsIdx < aPlugins.Length(); pluginsIdx++) {
     nsIWidget* widget =
       nsIWidget::LookupRegisteredPluginWindow(aPlugins[pluginsIdx].windowId());
     if (!widget) {
       NS_WARNING("Unexpected, plugin id not found!");
       continue;
     }
+    if (!parent) {
+      parent = widget->GetParent();
+    }
     bool isVisible = aPlugins[pluginsIdx].visible();
     if (widget && !widget->Destroyed()) {
       gfx::IntRect bounds;
       gfx::IntRect visibleBounds;
       // If the plugin is visible update it's geometry.
       if (isVisible) {
         // bounds (content origin) - don't pass true to Resize, it triggers a
         // sync paint update to the plugin process on Windows, which happens
@@ -316,31 +319,32 @@ CompositorChild::RecvUpdatePluginConfigu
         NS_ASSERTION(NS_SUCCEEDED(rv), "widget call failure");
 #endif
         visiblePluginIds.AppendElement(aPlugins[pluginsIdx].windowId());
       }
     }
   }
   // Any plugins we didn't update need to be hidden, as they are
   // not associated with visible content.
-  nsIWidget::UpdateRegisteredPluginWindowVisibility(visiblePluginIds);
+  nsIWidget::UpdateRegisteredPluginWindowVisibility((uintptr_t)parent, visiblePluginIds);
   return true;
 #endif // !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
 }
 
 bool
-CompositorChild::RecvUpdatePluginVisibility(nsTArray<uintptr_t>&& aVisibleIdList)
+CompositorChild::RecvUpdatePluginVisibility(const uintptr_t& aOwnerWidget,
+                                            nsTArray<uintptr_t>&& aVisibleIdList)
 {
 #if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
   NS_NOTREACHED("CompositorChild::RecvUpdatePluginVisibility calls "
                 "unexpected on this platform.");
   return false;
 #else
   MOZ_ASSERT(NS_IsMainThread());
-  nsIWidget::UpdateRegisteredPluginWindowVisibility(aVisibleIdList);
+  nsIWidget::UpdateRegisteredPluginWindowVisibility(aOwnerWidget, aVisibleIdList);
   return true;
 #endif // !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
 }
 
 bool
 CompositorChild::RecvDidComposite(const uint64_t& aId, const uint64_t& aTransactionId)
 {
   if (mLayerManager) {
--- a/gfx/layers/ipc/CompositorChild.h
+++ b/gfx/layers/ipc/CompositorChild.h
@@ -83,17 +83,18 @@ public:
   RecvOverfill(const uint32_t &aOverfill) override;
 
   virtual bool
   RecvUpdatePluginConfigurations(const nsIntPoint& aContentOffset,
                                  const nsIntRegion& aVisibleRegion,
                                  nsTArray<PluginWindowData>&& aPlugins) override;
 
   virtual bool
-  RecvUpdatePluginVisibility(nsTArray<uintptr_t>&& aWindowList) override;
+  RecvUpdatePluginVisibility(const uintptr_t& aOwnerWidget,
+                             nsTArray<uintptr_t>&& aWindowList) override;
 
   /**
    * Request that the parent tell us when graphics are ready on GPU.
    * When we get that message, we bounce it to the TabParent via
    * the TabChild
    * @param tabChild The object to bounce the note to.  Non-NULL.
    */
   void RequestNotifyAfterRemotePaint(TabChild* aTabChild);
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -1435,22 +1435,19 @@ CompositorParent::InitializeLayerManager
 PLayerTransactionParent*
 CompositorParent::AllocPLayerTransactionParent(const nsTArray<LayersBackend>& aBackendHints,
                                                const uint64_t& aId,
                                                TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                                bool *aSuccess)
 {
   MOZ_ASSERT(aId == 0);
 
-  // mWidget doesn't belong to the compositor thread, so it should be set to
-  // nullptr before returning from this method, to avoid accessing it elsewhere.
   gfx::IntRect rect;
   mWidget->GetClientBounds(rect);
   InitializeLayerManager(aBackendHints);
-  mWidget = nullptr;
 
   if (!mLayerManager) {
     NS_WARNING("Failed to initialise Compositor");
     *aSuccess = false;
     LayerTransactionParent* p = new LayerTransactionParent(nullptr, this, 0);
     p->AddIPDLReference();
     return p;
   }
@@ -2032,17 +2029,19 @@ UpdatePluginWindowState(uint64_t aId)
                           !lts.mUpdatedPluginDataAvailable;
   if (shouldComposePlugin) {
     if (!lts.mPluginData.Length()) {
       // We will pass through here in cases where the previous shadow layer
       // tree contained visible plugins and the new tree does not. All we need
       // to do here is hide the plugins for the old tree, so don't waste time
       // calculating clipping.
       nsTArray<uintptr_t> aVisibleIdList;
-      unused << lts.mParent->SendUpdatePluginVisibility(aVisibleIdList);
+      uintptr_t parentWidget = (uintptr_t)lts.mParent->GetWidget();
+      unused << lts.mParent->SendUpdatePluginVisibility(parentWidget,
+                                                        aVisibleIdList);
       lts.mUpdatedPluginDataAvailable = false;
       return;
     }
 
     // Retrieve the offset and visible region of the layer that hosts
     // the plugins, CompositorChild needs these in calculating proper
     // plugin clipping.
     LayerTransactionParent* layerTree = lts.mLayerTree;
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -419,16 +419,18 @@ public:
 
   float ComputeRenderIntegrity();
 
   /**
    * Returns true if the calling thread is the compositor thread.
    */
   static bool IsInCompositorThread();
 
+  nsIWidget* GetWidget() { return mWidget; }
+
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~CompositorParent();
 
   void DeferredDestroy();
 
   virtual PLayerTransactionParent*
     AllocPLayerTransactionParent(const nsTArray<LayersBackend>& aBackendHints,
--- a/gfx/layers/ipc/PCompositor.ipdl
+++ b/gfx/layers/ipc/PCompositor.ipdl
@@ -64,17 +64,18 @@ child:
   async UpdatePluginConfigurations(IntPoint aContentOffset,
                                    nsIntRegion aVisibleRegion,
                                    PluginWindowData[] aPlugins);
 
   /**
    * Sets the list of currently visible plugin windows based on a
    * list of plugin window ids.
    */
-  async UpdatePluginVisibility(uintptr_t[] aVisibleIdList);
+  async UpdatePluginVisibility(uintptr_t aOwnerWidget,
+                               uintptr_t[] aVisibleIdList);
 
   /**
    * Drop any buffers that might be retained on the child compositor
    * side.
    */
   async ClearCachedResources(uint64_t id);
 
 parent:
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -665,20 +665,16 @@ ShadowLayerForwarder::EndTransaction(Inf
                                           mIsFirstPaint, aScheduleComposite,
                                           aPaintSequenceNumber, aIsRepeatTransaction,
                                           aTransactionStart)) {
       MOZ_LAYERS_LOG(("[LayersForwarder] WARNING: sending transaction failed!"));
       return false;
     }
   }
 
-  // Clear any cached plugin data we might have, now that the
-  // transaction is complete.
-  mPluginWindowData.Clear();
-
   *aSent = true;
   mIsFirstPaint = false;
   MOZ_LAYERS_LOG(("[LayersForwarder] ... done"));
   return true;
 }
 
 bool
 ShadowLayerForwarder::AllocShmem(size_t aSize,
--- a/gfx/thebes/gfxPlatformMac.cpp
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -509,17 +509,16 @@ public:
       if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) {
         NS_WARNING("Could not set displaylink output callback");
         CVDisplayLinkRelease(mDisplayLink);
         mDisplayLink = nullptr;
         return;
       }
 
       mPreviousTimestamp = TimeStamp::Now();
-      mStartingVsync = true;
       if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) {
         NS_WARNING("Could not activate the display link");
         CVDisplayLinkRelease(mDisplayLink);
         mDisplayLink = nullptr;
       }
     }
 
     virtual void DisableVsync() override
@@ -543,17 +542,16 @@ public:
     }
 
     // The vsync timestamps given by the CVDisplayLinkCallback are
     // in the future for the NEXT frame. Large parts of Gecko, such
     // as animations assume a timestamp at either now or in the past.
     // Normalize the timestamps given to the VsyncDispatchers to the vsync
     // that just occured, not the vsync that is upcoming.
     TimeStamp mPreviousTimestamp;
-    bool mStartingVsync;
 
   private:
     // Manages the display link render thread
     CVDisplayLinkRef   mDisplayLink;
     nsRefPtr<nsITimer> mTimer;
   }; // OSXDisplay
 
 private:
@@ -569,39 +567,35 @@ static CVReturn VsyncCallback(CVDisplayL
                               const CVTimeStamp* aOutputTime,
                               CVOptionFlags aFlagsIn,
                               CVOptionFlags* aFlagsOut,
                               void* aDisplayLinkContext)
 {
   // Executed on OS X hardware vsync thread
   OSXVsyncSource::OSXDisplay* display = (OSXVsyncSource::OSXDisplay*) aDisplayLinkContext;
   int64_t nextVsyncTimestamp = aOutputTime->hostTime;
+
   mozilla::TimeStamp nextVsync = mozilla::TimeStamp::FromSystemTime(nextVsyncTimestamp);
-
   mozilla::TimeStamp previousVsync = display->mPreviousTimestamp;
-  bool firstVsync = display->mStartingVsync;
+  mozilla::TimeStamp now = TimeStamp::Now();
 
-  display->mStartingVsync = false;
-  display->mPreviousTimestamp = nextVsync;
-  mozilla::TimeStamp now = TimeStamp::Now();
+  // Snow leopard sometimes sends vsync timestamps very far in the past.
+  // Normalize the vsync timestamps to now.
   if (nextVsync <= previousVsync) {
-    TimeDuration next = nextVsync - now;
-    TimeDuration prev = now - previousVsync;
-    printf_stderr("Next from now: %f, prev from now: %f, first vsync %d\n",
-      next.ToMilliseconds(), prev.ToMilliseconds(), firstVsync);
-    MOZ_ASSERT(false, "Next vsync less than previous vsync\n");
+    nextVsync = now;
+    previousVsync = now;
+  } else if (now < previousVsync) {
+    // Bug 1158321 - The VsyncCallback can sometimes execute before the reported
+    // vsync time. In those cases, normalize the timestamp to Now() as sending
+    // timestamps in the future has undefined behavior. See the comment above
+    // OSXDisplay::mPreviousTimestamp
+    previousVsync = now;
   }
 
-  // Bug 1158321 - The VsyncCallback can sometimes execute before the reported
-  // vsync time. In those cases, normalize the timestamp to Now() as sending
-  // timestamps in the future has undefined behavior. See the comment above
-  // OSXDisplay::mPreviousTimestamp
-  if (now < previousVsync) {
-    previousVsync = now;
-  }
+  display->mPreviousTimestamp = nextVsync;
 
   display->NotifyVsync(previousVsync);
   return kCVReturnSuccess;
 }
 
 already_AddRefed<mozilla::gfx::VsyncSource>
 gfxPlatformMac::CreateHardwareVsyncSource()
 {
--- a/intl/uconv/nsReplacementToUnicode.h
+++ b/intl/uconv/nsReplacementToUnicode.h
@@ -17,21 +17,21 @@
 class nsReplacementToUnicode : public nsBasicDecoderSupport
 {
 public:
   nsReplacementToUnicode();
 
   NS_IMETHOD Convert(const char* aSrc,
                      int32_t* aSrcLength,
                      char16_t* aDest,
-                     int32_t* aDestLength);
+                     int32_t* aDestLength) override;
 
   MOZ_WARN_UNUSED_RESULT NS_IMETHOD GetMaxLength(const char* aSrc,
                                                  int32_t aSrcLength,
                                                  int32_t* aDestLength) override;
 
-  NS_IMETHOD Reset();
+  NS_IMETHOD Reset() override;
 
 private:
   bool mSeenByte;
 };
 
 #endif // nsReplacementToUnicode_h_
--- a/intl/uconv/nsUTF8ToUnicode.h
+++ b/intl/uconv/nsUTF8ToUnicode.h
@@ -52,19 +52,19 @@ protected:
   MOZ_WARN_UNUSED_RESULT NS_IMETHOD GetMaxLength(const char* aSrc,
                                                  int32_t aSrcLength,
                                                  int32_t* aDestLength) override;
 
   //--------------------------------------------------------------------
   // Subclassing of nsBasicDecoderSupport class [declaration]
 
   NS_IMETHOD Convert(const char * aSrc, int32_t * aSrcLength, 
-      char16_t * aDest, int32_t * aDestLength);
+      char16_t * aDest, int32_t * aDestLength) override;
 
   //--------------------------------------------------------------------
   // Subclassing of nsBasicDecoderSupport class [declaration]
 
-  NS_IMETHOD Reset();
+  NS_IMETHOD Reset() override;
 
 };
 
 #endif /* nsUTF8ToUnicode_h___ */
 
--- a/intl/uconv/ucvlatin/nsUTF16ToUnicode.h
+++ b/intl/uconv/ucvlatin/nsUTF16ToUnicode.h
@@ -22,17 +22,17 @@ protected:
 
 public:
   //--------------------------------------------------------------------
   // Subclassing of nsDecoderSupport class [declaration]
 
   MOZ_WARN_UNUSED_RESULT NS_IMETHOD GetMaxLength(const char* aSrc,
                                                  int32_t aSrcLength,
                                                  int32_t* aDestLength) override;
-  NS_IMETHOD Reset();
+  NS_IMETHOD Reset() override;
 
 protected:
   uint8_t mState;
   // to store an odd byte left over between runs
   uint8_t mOddByte;
   // to store an odd high surrogate left over between runs
   char16_t mOddHighSurrogate;
   // to store an odd low surrogate left over between runs
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -280,18 +280,18 @@ class GarbageCollectionEvent
     // Reference to a nullable, non-owned, statically allocated C string. If the
     // collection was forced to be non-incremental, this is a short reason of
     // why the GC could not perform an incremental collection.
     const char* nonincrementalReason;
 
     // Represents a single slice of a possibly multi-slice incremental garbage
     // collection.
     struct Collection {
-        int64_t startTimestamp;
-        int64_t endTimestamp;
+        double startTimestamp;
+        double endTimestamp;
     };
 
     // The set of garbage collection slices that made up this GC cycle.
     mozilla::Vector<Collection> collections;
 
     GarbageCollectionEvent(const GarbageCollectionEvent& rhs) = delete;
     GarbageCollectionEvent& operator=(const GarbageCollectionEvent& rhs) = delete;
 
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -207,16 +207,28 @@ TryEvalJSON(JSContext* cx, JSLinearStrin
     if (!linearChars.init(cx, str))
         return EvalJSON_Failure;
 
     return linearChars.isLatin1()
            ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
            : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
 }
 
+static bool
+HasPollutedScopeChain(JSObject* scopeChain)
+{
+    while (scopeChain) {
+        if (scopeChain->is<DynamicWithObject>())
+            return true;
+        scopeChain = scopeChain->enclosingScope();
+    }
+
+    return false;
+}
+
 // Define subset of ExecuteType so that casting performs the injection.
 enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL };
 
 // Common code implementing direct and indirect eval.
 //
 // Evaluate call.argv[2], if it is a string, in the context of the given calling
 // frame, with the provided scope chain, with the semantics of either a direct
 // or indirect eval (see ES5 10.4.2).  If this is an indirect eval, scopeobj
@@ -233,16 +245,22 @@ EvalKernel(JSContext* cx, const CallArgs
     AssertInnerizedScopeChain(cx, *scopeobj);
 
     Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global());
     if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
 
+    if (evalType == DIRECT_EVAL && caller.script()->isDerivedClassConstructor()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DISABLED_DERIVED_CLASS,
+                             "direct eval");
+        return false;
+    }
+
     // ES5 15.1.2.1 step 1.
     if (args.length() < 1) {
         args.rval().setUndefined();
         return true;
     }
     if (!args[0].isString()) {
         args.rval().set(args[0]);
         return true;
@@ -309,18 +327,23 @@ EvalKernel(JSContext* cx, const CallArgs
 
         RootedObject enclosing(cx);
         if (evalType == DIRECT_EVAL)
             enclosing = callerScript->innermostStaticScope(pc);
         Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosing));
         if (!staticScope)
             return false;
 
+        bool hasPollutedGlobalScope =
+            HasPollutedScopeChain(scopeobj) ||
+            (evalType == DIRECT_EVAL && callerScript->hasPollutedGlobalScope());
+
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
+               .setHasPollutedScope(hasPollutedGlobalScope)
                .setIsRunOnce(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
                .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
 
         AutoStableStringChars linearChars(cx);
@@ -328,17 +351,17 @@ EvalKernel(JSContext* cx, const CallArgs
             return false;
 
         const char16_t* chars = linearChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
         JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, staticScope, callerScript,
+                                                     scopeobj, callerScript, staticScope,
                                                      options, srcBuf, linearStr, staticLevel);
         if (!compiled)
             return false;
 
         if (compiled->strict())
             staticScope->setStrict();
 
         esg.setNewScript(compiled);
@@ -382,29 +405,31 @@ js::DirectEvalStringFromIon(JSContext* c
 
     if (!esg.foundScript()) {
         RootedScript maybeScript(cx);
         const char* filename;
         unsigned lineno;
         bool mutedErrors;
         uint32_t pcOffset;
         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
-                                             &mutedErrors, CALLED_FROM_JSOP_EVAL);
+                                              &mutedErrors, CALLED_FROM_JSOP_EVAL);
 
         const char* introducerFilename = filename;
         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
             introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
         RootedObject enclosing(cx, callerScript->innermostStaticScope(pc));
         Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosing));
         if (!staticScope)
             return false;
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
+               .setHasPollutedScope(HasPollutedScopeChain(scopeobj) ||
+                                    callerScript->hasPollutedGlobalScope())
                .setIsRunOnce(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
                .maybeMakeStrictMode(IsStrictEvalPC(pc));
 
         AutoStableStringChars linearChars(cx);
@@ -412,17 +437,17 @@ js::DirectEvalStringFromIon(JSContext* c
             return false;
 
         const char16_t* chars = linearChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
         JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, staticScope, callerScript,
+                                                     scopeobj, callerScript, staticScope,
                                                      options, srcBuf, linearStr, staticLevel);
         if (!compiled)
             return false;
 
         if (compiled->strict())
             staticScope->setStrict();
 
         esg.setNewScript(compiled);
@@ -483,41 +508,45 @@ js::IsAnyBuiltinEval(JSFunction* fun)
 
 JS_FRIEND_API(bool)
 js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
                                   MutableHandleObject scopeArg)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, global);
     MOZ_ASSERT(global->is<GlobalObject>());
-    MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
+    MOZ_RELEASE_ASSERT(scriptArg->hasPollutedGlobalScope());
 
     RootedScript script(cx, scriptArg);
     if (script->compartment() != cx->compartment()) {
-        Rooted<ScopeObject*> staticScope(cx, StaticNonSyntacticScopeObjects::create(cx, nullptr));
-        if (!staticScope)
-            return false;
-        script = CloneGlobalScript(cx, staticScope, script);
+        script = CloneScript(cx, nullptr, nullptr, script);
         if (!script)
             return false;
 
         Debugger::onNewScript(cx, script);
     }
 
-    Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
-    Rooted<ScopeObject*> scope(cx, NonSyntacticVariablesObject::create(cx, globalRoot));
+    RootedObject scope(cx, JS_NewPlainObject(cx));
     if (!scope)
         return false;
 
+    if (!scope->setQualifiedVarObj(cx))
+        return false;
+
+    if (!scope->setUnqualifiedVarObj(cx))
+        return false;
+
     JSObject* thisobj = GetThisObject(cx, global);
     if (!thisobj)
         return false;
 
     RootedValue thisv(cx, ObjectValue(*thisobj));
     RootedValue rval(cx);
+    // XXXbz when this is fixed to pass in an actual ScopeObject, fix
+    // up the assert in js::CloneFunctionObject accordingly.
     if (!ExecuteKernel(cx, script, *scope, thisv, UndefinedValue(), EXECUTE_GLOBAL,
                        NullFramePtr() /* evalInFrame */, rval.address()))
     {
         return false;
     }
 
     scopeArg.set(scope);
     return true;
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2267,20 +2267,21 @@ EvalReturningScope(JSContext* cx, unsign
     JS::AutoFilename filename;
     unsigned lineno;
 
     DescribeScriptedCaller(cx, &filename, &lineno);
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(filename.get(), lineno);
     options.setNoScriptRval(true);
+    options.setHasPollutedScope(true);
 
     JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
     RootedScript script(cx);
-    if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script))
+    if (!JS::Compile(cx, options, srcBuf, &script))
         return false;
 
     if (global) {
         global = CheckedUnwrap(global);
         if (!global) {
             JS_ReportError(cx, "Permission denied to access global");
             return false;
         }
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -139,21 +139,20 @@ MaybeCheckEvalFreeVariables(ExclusiveCon
             scope = scope->enclosingScope();
         }
     }
 
     return true;
 }
 
 static inline bool
-CanLazilyParse(ExclusiveContext* cx, HandleObject staticScope,
-               const ReadOnlyCompileOptions& options)
+CanLazilyParse(ExclusiveContext* cx, const ReadOnlyCompileOptions& options)
 {
     return options.canLazilyParse &&
-           !HasNonSyntacticStaticScopeChain(staticScope) &&
+           !options.hasPollutedGlobalScope &&
            !cx->compartment()->options().disableLazyParsing() &&
            !cx->compartment()->options().discardSource() &&
            !options.sourceIsLazy;
 }
 
 static void
 MarkFunctionsWithinEvalScript(JSScript* script)
 {
@@ -206,18 +205,18 @@ frontend::CreateScriptSourceObject(Exclu
             return nullptr;
     }
 
     return sso;
 }
 
 JSScript*
 frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject scopeChain,
-                        Handle<ScopeObject*> enclosingStaticScope,
                         HandleScript evalCaller,
+                        Handle<StaticEvalObject*> evalStaticScope,
                         const ReadOnlyCompileOptions& options,
                         SourceBufferHolder& srcBuf,
                         JSString* source_ /* = nullptr */,
                         unsigned staticLevel /* = 0 */,
                         SourceCompressionTask* extraSct /* = nullptr */)
 {
     MOZ_ASSERT(srcBuf.get());
 
@@ -256,17 +255,17 @@ frontend::CompileScript(ExclusiveContext
 
     if (!cx->compartment()->options().discardSource()) {
         if (options.sourceIsLazy)
             ss->setSourceRetrievable();
         else if (!ss->setSourceCopy(cx, srcBuf, false, sct))
             return nullptr;
     }
 
-    bool canLazilyParse = CanLazilyParse(cx, enclosingStaticScope, options);
+    bool canLazilyParse = CanLazilyParse(cx, options);
 
     Maybe<Parser<SyntaxParseHandler> > syntaxParser;
     if (canLazilyParse) {
         syntaxParser.emplace(cx, alloc, options, srcBuf.get(), srcBuf.length(),
                              /* foldConstants = */ false,
                              (Parser<SyntaxParseHandler>*) nullptr,
                              (LazyScript*) nullptr);
 
@@ -280,32 +279,32 @@ frontend::CompileScript(ExclusiveContext
     parser.sct = sct;
     parser.ss = ss;
 
     if (!parser.checkOptions())
         return nullptr;
 
     bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction();
     Directives directives(options.strictOption);
-    GlobalSharedContext globalsc(cx, directives, enclosingStaticScope, options.extraWarningsOption);
+    GlobalSharedContext globalsc(cx, directives, evalStaticScope, options.extraWarningsOption);
 
-    Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingStatic