Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 31 Jul 2015 15:03:32 -0400
changeset 287284 afa67b6957bb2ce63bd5eb3a4125c111335d4a2f
parent 287263 ba34dfa90df788753f5cc7ec64195ffd16023ad1 (current diff)
parent 287283 9bc14f82bac8d239b67cf3f939629d933a3cba14 (diff)
child 287341 aeb85029c3b3b594d96a9c72d04b8e971e1bdf5b
child 287344 5f4839ec1475be192508f3df6af815acd21ddae3
child 287371 49f553e28a4c277b33ceef6cd41c2bcc88dd61af
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.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 fx-team to m-c. a=merge
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -234,20 +234,23 @@ let gFxAccounts = {
     } catch (e) { }
 
     // Bail out if FxA is disabled.
     if (!this.weave.fxAccountsEnabled) {
       // When migration transitions from needs-verification to the null state,
       // fxAccountsEnabled is false because migration has not yet finished.  In
       // that case, hide the button.  We'll get another notification with a null
       // state once migration is complete.
+      this.panelUIFooter.hidden = true;
       this.panelUIFooter.removeAttribute("fxastatus");
       return;
     }
 
+    this.panelUIFooter.hidden = false;
+
     // Make sure the button is disabled in customization mode.
     if (this._inCustomizationMode) {
       this.panelUIStatus.setAttribute("disabled", "true");
       this.panelUILabel.setAttribute("disabled", "true");
       this.panelUIAvatar.setAttribute("disabled", "true");
       this.panelUIIcon.setAttribute("disabled", "true");
     } else {
       this.panelUIStatus.removeAttribute("disabled");
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -1,11 +1,15 @@
 /* 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/. */
+html {
+  font-size: 10px;
+  font-family: menu;
+}
 
 body {
   background: none;
 }
 
 /* Panel styles */
 
 .panel {
@@ -172,17 +176,17 @@ body {
   /* The header shouldn't be indented if the tabs are present. */
   -moz-padding-start: 0;
 }
 
 .content-area label {
   display: block;
   width: 100%;
   margin-top: 10px;
-  font-size: 10px;
+  font-size: 1rem;
   color: #777;
 }
 
 .content-area input {
   display: block;
   width: 100%;
   outline: none;
   border-radius: 2px;
@@ -206,59 +210,69 @@ body {
   min-height: 100px;
   padding: 0 1rem;
 }
 
 .rooms > h1 {
   font-weight: bold;
   color: #999;
   padding: .5rem 0;
+  height: 3rem;
+  line-height: 3rem;
+}
+
+.new-room-view > .context-checkbox-checked {
+  background-color: #dbf7ff;
 }
 
 .new-room-view > .context {
-  margin: .5rem 0 0;
-  background-color: #dbf7ff;
+  margin: .5rem 0 .5rem;
   border-radius: 3px 3px 0 0;
-  padding: .5rem;
+  padding: .5rem 1rem ;
+  margin-left: -1rem;
+  margin-right: -1rem;
 }
 
 .new-room-view > .context > .context-enabled {
   margin-bottom: .5rem;
   display: block;
 }
 
 .new-room-view > .context > .context-enabled > input {
   -moz-margin-start: 0;
 }
 
 .new-room-view > .context > .checkbox-wrapper {
+  height: 2rem;
   margin-bottom: .5em;
+  line-height: 2rem;
 }
 
 .new-room-view > .context > .checkbox-wrapper > .checkbox {
-  border-color: #0096dd;
+  border-color: #d8d8d8;
   background-color: #fff;
 }
 
 .new-room-view > .context > .checkbox-wrapper > .checkbox.checked {
   background-image: url("../shared/img/check.svg#check-blue");
 }
 
 .new-room-view > .context > .checkbox-wrapper > label {
   color: #333;
-  font-weight: 700;
+  font-size: 1.1rem;
 }
 
 .new-room-view > .btn {
+  height: 3rem;
   display: block;
-  font-size: 1rem;
-  margin: 0 auto .5rem;
+  font-size: 1.2rem;
+  margin: 0 auto 1rem;
   width: 100%;
   padding: .5rem 1rem;
-  border-radius: 0 0 3px 3px;
+  border-radius: 4px;
 }
 
 /* Remove when bug 1142671 is backed out. */
 .new-room-view > .context.hide + .new-room-button {
   border-radius: 3px;
   margin-top: 0.5rem;
 }
 
@@ -272,24 +286,24 @@ body {
   margin-right: -1rem;
 }
 
 .room-list:empty {
   border-bottom-width: 0;
 }
 
 .room-list > .room-entry {
-  padding: .5rem 1rem;
+  padding: .2rem 1rem;
   /* Always show the default pointer, even over the text part of the entry. */
   cursor: default;
 }
 
 .room-list > .room-entry > h2 {
   display: inline-block;
-  font-size: .85rem;
+  font-size: 1rem;
   color: #777;
   /* See .room-entry-context-item for the margin/size reductions. */
   width: calc(100% - 1rem - 16px);
 }
 
 .room-list > .room-entry.room-active > h2 {
   font-weight: bold;
   color: #000;
@@ -304,21 +318,17 @@ body {
   -moz-margin-end: .3rem;
 }
 
 .room-list > .room-entry.room-active > h2 > .room-notification {
   display: inline-block;
 }
 
 .room-list > .room-entry:hover {
-  background: #f1f1f1;
-}
-
-.room-list > .room-entry:not(:last-child) {
-  border-bottom: 1px solid #ccc;
+  background: #dbf7ff;
 }
 
 .room-list > .room-entry > p {
   margin: 0;
   padding: .2rem 0;
 }
 
 .room-list > .room-entry > p > a {
@@ -419,17 +429,17 @@ body {
 
 .button {
   padding: 2px 5px;
   background-color: #fbfbfb;
   color: #333;
   border: 1px solid #c1c1c1;
   border-radius: 2px;
   min-height: 26px;
-  font-size: 12px;
+  font-size: 1.2rem;
 }
 
 .button > .button-caption {
   vertical-align: middle;
 }
 
 .button:hover {
   background-color: #ebebeb;
@@ -614,17 +624,17 @@ html[dir="rtl"] .generate-url-spinner {
 
 .terms-service {
   padding-left: 1rem;
   padding-right: 1rem;
   padding-bottom: .5rem;
 }
 
 .terms-service > a {
-  color: #00caee;
+  color: #00a9dc;
 }
 
 /* DnD menu */
 
 .dnd-status {
   border: 1px solid transparent;
   padding: 2px 4px;
   margin: 0;
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -769,16 +769,17 @@ loop.panel = (function(_, mozL10n) {
       try {
         hostname = new URL(this.state.url).hostname;
       } catch (ex) {
         // Empty catch - if there's an error, then we won't show the context.
       }
 
       var contextClasses = React.addons.classSet({
         context: true,
+        "context-checkbox-checked": this.state.checked,
         hide: !hostname ||
           !this.props.mozLoop.getLoopPref("contextInConversations.enabled")
       });
 
       return (
         React.createElement("div", {className: "new-room-view"}, 
           React.createElement("div", {className: contextClasses}, 
             React.createElement(Checkbox, {checked: this.state.checked, 
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -769,16 +769,17 @@ loop.panel = (function(_, mozL10n) {
       try {
         hostname = new URL(this.state.url).hostname;
       } catch (ex) {
         // Empty catch - if there's an error, then we won't show the context.
       }
 
       var contextClasses = React.addons.classSet({
         context: true,
+        "context-checkbox-checked": this.state.checked,
         hide: !hostname ||
           !this.props.mozLoop.getLoopPref("contextInConversations.enabled")
       });
 
       return (
         <div className="new-room-view">
           <div className={contextClasses}>
             <Checkbox checked={this.state.checked}
--- a/browser/components/loop/content/shared/css/common.css
+++ b/browser/components/loop/content/shared/css/common.css
@@ -95,29 +95,29 @@ p {
 
 .btn.btn-constrained {
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
 
 .btn-info {
-  background-color: #0096dd;
-  border: 1px solid #0095dd;
+  background-color: #00a9dc;
+  border: 1px solid #00a9dc;
   color: #fff;
 }
 
   .btn-info:hover {
-    background-color: #008acb;
-    border: 1px solid #008acb;
+    background-color: #5cccee;
+    border: 1px solid #5cccee;
   }
 
   .btn-info:active {
-    background-color: #006b9d;
-    border: 1px solid #006b9d;
+    background-color: #5cccee;
+    border: 1px solid #5cccee;
   }
 
 .btn-accept,
 .btn-success,
 .btn-accept + .btn-chevron {
   background-color: #5bc0a4;
   border: 1px solid #5bc0a4;
 }
@@ -463,18 +463,18 @@ html[dir="rtl"] .dropdown-menu {
 
 .checkbox-wrapper {
   -moz-user-select: none;
   user-select: none;
 }
 
 .checkbox {
   float: left;
-  width: 1em;
-  height: 1em;
+  width: 2rem;
+  height: 2rem;
   -moz-margin-end: .5em;
   margin-top: .1em;
   border: 1px solid #999;
   border-radius: 3px;
   cursor: pointer;
   background-color: transparent;
   background-position: center center;
   background-repeat: no-repeat;
@@ -516,25 +516,29 @@ html[dir="rtl"] .context-content {
 .context-content > p {
   font-weight: bold;
   margin-bottom: .8em;
   margin-top: 0;
 }
 
 .context-wrapper {
   border: 1px solid #5cccee;
-  border-radius: 3px;
+  border-radius: 4px;
   background: #fff;
   padding: .8em;
   /* Use the flex row mode to position the elements next to each other. */
   display: flex;
   flex-flow: row nowrap;
   line-height: 1.1em;
 }
 
+.context-wrapper:hover {
+  background-color: #dbf7ff;
+}
+
 .context-wrapper > .context-preview {
   float: left;
   /* 16px is standard height/width for a favicon */
   width: 16px;
   max-height: 16px;
   margin-right: .8em;
   flex: 0 0 auto;
 }
@@ -551,12 +555,12 @@ html[dir="rtl"] .context-wrapper > .cont
   color: black;
   /* 16px for the preview, plus its .8em margin */
   max-width: calc(100% - 16px - .8em);
   word-wrap: break-word;
 }
 
 .context-wrapper > .context-description > .context-url {
   display: block;
-  color: #59A1D7;
+  color: #00a9dc;
   font-weight: 700;
   clear: both;
 }
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -195,18 +195,18 @@
   </hbox>
 
     <vbox id="dialogOverlay" align="center" pack="center">
       <groupbox id="dialogBox"
                 orient="vertical"
                 pack="end"
                 role="dialog"
                 aria-labelledby="dialogTitle">
-        <caption class="titlebar" flex="1" align="center">
-          <label id="dialogTitle" class="title" flex="1"></label>
+        <caption flex="1" align="center">
+          <label id="dialogTitle" flex="1"></label>
           <button id="dialogClose"
                   class="close-icon"
                   aria-label="&preferencesCloseButton.label;"/>
         </caption>
         <browser id="dialogFrame"
                  name="dialogFrame"
                  autoscroll="false"
                  disablehistory="true"/>
--- a/browser/devtools/framework/sidebar.js
+++ b/browser/devtools/framework/sidebar.js
@@ -324,45 +324,32 @@ ToolSidebar.prototype = {
       panel.remove();
     }
 
     this._tabs.delete(tabId);
     this.emit("tab-unregistered", tabId);
   }),
 
   /**
-   * Show or hide a specific tab and tabpanel.
+   * Show or hide a specific tab.
    * @param {Boolean} isVisible True to show the tab/tabpanel, False to hide it.
    * @param {String} id The ID of the tab to be hidden.
-   * @param {String} tabPanelId Optionally pass the ID for the tabPanel if it
-   * can't be retrieved using the tab ID. This is useful when tabs and tabpanels
-   * existed before the widget was created.
    */
-  toggleTab: function(isVisible, id, tabPanelId) {
+  toggleTab: function(isVisible, id) {
     // Toggle the tab.
     let tab = this.getTab(id);
     if (!tab) {
       return;
     }
     tab.hidden = !isVisible;
 
     // Toggle the item in the allTabs menu.
     if (this._allTabsBtn) {
       this._allTabsBtn.querySelector("#sidebar-alltabs-item-" + id).hidden = !isVisible;
     }
-
-    // Toggle the corresponding tabPanel, if one can be found either with the id
-    // or the provided tabPanelId.
-    let tabPanel = this.getTabPanel(id);
-    if (!tabPanel && tabPanelId) {
-      tabPanel = this.getTabPanel(tabPanelId);
-    }
-    if (tabPanel) {
-      tabPanel.hidden = !isVisible;
-    }
   },
 
   /**
    * Select a specific tab.
    */
   select: function(id) {
     let tab = this.getTab(id);
     if (tab) {
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -2470,24 +2470,24 @@ NetworkDetailsView.prototype = {
     $("#response-content-json-box").hidden = true;
     $("#response-content-textarea-box").hidden = true;
     $("#raw-headers").hidden = true;
     $("#response-content-image-box").hidden = true;
 
     let isHtml = RequestsMenuView.prototype.isHtml({ attachment: aData });
 
     // Show the "Preview" tabpanel only for plain HTML responses.
-    this.sidebar.toggleTab(isHtml, "preview-tab", "preview-tabpanel");
+    this.sidebar.toggleTab(isHtml, "preview-tab");
 
     // Show the "Security" tab only for requests that
     //   1) are https (state != insecure)
     //   2) come from a target that provides security information.
     let hasSecurityInfo = aData.securityState &&
                           aData.securityState !== "insecure";
-    this.sidebar.toggleTab(hasSecurityInfo, "security-tab", "security-tabpanel");
+    this.sidebar.toggleTab(hasSecurityInfo, "security-tab");
 
     // Switch to the "Headers" tabpanel if the "Preview" previously selected
     // and this is not an HTML response or "Security" was selected but this
     // request has no security information.
 
     if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
         !hasSecurityInfo && this.widget.selectedPanel === $("#security-tabpanel")) {
       this.widget.selectedIndex = 0;
--- a/browser/devtools/netmonitor/test/browser_net_html-preview.js
+++ b/browser/devtools/netmonitor/test/browser_net_html-preview.js
@@ -17,46 +17,44 @@ function test() {
     waitForNetworkEvents(aMonitor, 6).then(() => {
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
 
       is($("#event-details-pane").selectedIndex, 0,
         "The first tab in the details pane should be selected.");
       is($("#preview-tab").hidden, true,
         "The preview tab should be hidden for non html responses.");
-      is($("#preview-tabpanel").hidden, true,
-        "The preview tabpanel should be hidden for non html responses.");
+      is($("#preview-tabpanel").hidden, false,
+        "The preview tabpanel is not hidden for non html responses.");
 
       RequestsMenu.selectedIndex = 4;
       NetMonitorView.toggleDetailsPane({ visible: true, animated: false }, 6);
 
       is($("#event-details-pane").selectedIndex, 6,
         "The sixth tab in the details pane should be selected.");
       is($("#preview-tab").hidden, false,
         "The preview tab should be visible now.");
-      is($("#preview-tabpanel").hidden, false,
-        "The preview tabpanel should be visible now.");
 
       waitFor(aMonitor.panelWin, EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED).then(() => {
         let iframe = $("#response-preview");
         ok(iframe,
           "There should be a response preview iframe available.");
         ok(iframe.contentDocument,
           "The iframe's content document should be available.");
         is(iframe.contentDocument.querySelector("blink").textContent, "Not Found",
           "The iframe's content document should be loaded and correct.");
 
         RequestsMenu.selectedIndex = 5;
 
         is($("#event-details-pane").selectedIndex, 0,
           "The first tab in the details pane should be selected again.");
         is($("#preview-tab").hidden, true,
           "The preview tab should be hidden again for non html responses.");
-        is($("#preview-tabpanel").hidden, true,
-          "The preview tabpanel should be hidden again for non html responses.");
+        is($("#preview-tabpanel").hidden, false,
+          "The preview tabpanel is not hidden again for non html responses.");
 
         teardown(aMonitor).then(finish);
       });
     });
 
     aDebuggee.performRequests();
   });
 }
--- a/browser/devtools/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/browser/devtools/netmonitor/test/browser_net_security-tab-visibility.js
@@ -40,49 +40,56 @@ add_task(function* () {
     info("Testing Security tab visibility for " + testcase.desc);
     let onNewItem = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
     let onSecurityInfo = monitor.panelWin.once(EVENTS.RECEIVED_SECURITY_INFO);
     let onComplete = testcase.isBroken ?
                        waitForSecurityBrokenNetworkEvent() :
                        waitForNetworkEvents(monitor, 1);
 
     let tab = $("#security-tab");
+    let tabpanel = $("#security-tabpanel");
 
     info("Performing a request to " + testcase.uri);
     debuggee.performRequests(1, testcase.uri);
 
     info("Waiting for new network event.");
     yield onNewItem;
 
     info("Selecting the request.");
     RequestsMenu.selectedIndex = 0;
 
     is(RequestsMenu.selectedItem.attachment.securityState, undefined,
        "Security state has not yet arrived.");
     is(tab.hidden, !testcase.visibleOnNewEvent,
        "Security tab is " +
         (testcase.visibleOnNewEvent ? "visible" : "hidden") +
        " after new request was added to the menu.");
+    is(tabpanel.hidden, false,
+      "#security-tabpanel is visible after new request was added to the menu.");
 
     info("Waiting for security information to arrive.");
     yield onSecurityInfo;
 
     ok(RequestsMenu.selectedItem.attachment.securityState,
        "Security state arrived.");
     is(tab.hidden, !testcase.visibleOnSecurityInfo,
        "Security tab is " +
         (testcase.visibleOnSecurityInfo ? "visible" : "hidden") +
        " after security information arrived.");
+    is(tabpanel.hidden, false,
+      "#security-tabpanel is visible after security information arrived.");
 
     info("Waiting for request to complete.");
     yield onComplete;
     is(tab.hidden, !testcase.visibleOnceComplete,
        "Security tab is " +
         (testcase.visibleOnceComplete ? "visible" : "hidden") +
        " after request has been completed.");
+    is(tabpanel.hidden, false,
+      "#security-tabpanel is visible after request is complete.");
 
     info("Clearing requests.");
     RequestsMenu.clear();
   }
 
   yield teardown(monitor);
 
   /**
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -180,16 +180,17 @@ browser.jar:
   skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
   skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
   skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
   skin/classic/browser/feeds/feedIcon.png             (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png           (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css         (feeds/subscribe-ui.css)
 * skin/classic/browser/newtab/newTab.css              (newtab/newTab.css)
+  skin/classic/browser/newtab/close.png               (../shared/newtab/close.png)
   skin/classic/browser/newtab/controls.svg            (../shared/newtab/controls.svg)
   skin/classic/browser/newtab/whimsycorn.png          (../shared/newtab/whimsycorn.png)
   skin/classic/browser/panic-panel/header.png         (../shared/panic-panel/header.png)
   skin/classic/browser/panic-panel/header@2x.png      (../shared/panic-panel/header@2x.png)
   skin/classic/browser/panic-panel/header-small.png   (../shared/panic-panel/header-small.png)
   skin/classic/browser/panic-panel/header-small@2x.png (../shared/panic-panel/header-small@2x.png)
   skin/classic/browser/panic-panel/icons.png          (../shared/panic-panel/icons.png)
   skin/classic/browser/panic-panel/icons@2x.png       (../shared/panic-panel/icons@2x.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -237,16 +237,17 @@ browser.jar:
   skin/classic/browser/downloads/download-summary.png       (downloads/download-summary.png)
   skin/classic/browser/downloads/download-summary@2x.png    (downloads/download-summary@2x.png)
   skin/classic/browser/downloads/downloads.css              (downloads/downloads.css)
   skin/classic/browser/feeds/subscribe.css                  (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css               (feeds/subscribe-ui.css)
   skin/classic/browser/feeds/feedIcon.png                   (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png                 (feeds/feedIcon16.png)
 * skin/classic/browser/newtab/newTab.css                    (newtab/newTab.css)
+  skin/classic/browser/newtab/close.png                     (../shared/newtab/close.png)
   skin/classic/browser/newtab/controls.svg                  (../shared/newtab/controls.svg)
   skin/classic/browser/newtab/whimsycorn.png                (../shared/newtab/whimsycorn.png)
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
   skin/classic/browser/panic-panel/header.png               (../shared/panic-panel/header.png)
   skin/classic/browser/panic-panel/header@2x.png            (../shared/panic-panel/header@2x.png)
   skin/classic/browser/panic-panel/header-small.png         (../shared/panic-panel/header-small.png)
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -515,17 +515,17 @@ toolbarpaletteitem[place="palette"] > to
 
 #PanelUI-fxa-status {
   display: flex;
   flex: 1 1 0%;
   width: 1px;
 }
 
 #PanelUI-footer-inner,
-#PanelUI-footer-fxa {
+#PanelUI-footer-fxa:not([hidden]) {
   display: flex;
   border-top: 1px solid hsla(210,4%,10%,.14);
 }
 
 #PanelUI-multiView[viewtype="subview"] #PanelUI-footer-inner,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-footer-fxa {
   position: relative;
 }
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -44,49 +44,40 @@
   color: var(--identity-box-chrome-color);
 }
 
 #identity-icon-labels {
   padding-inline-start: 2px;
 }
 
 #notification-popup-box:not([hidden]) + #identity-box {
-  -moz-padding-start: 10px;
+  padding-inline-start: 10px;
   border-radius: 0;
 }
 
 @conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
   border-radius: 0;
 }
 
 @conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
   transition: padding-left, padding-right;
 }
 
-@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
-  padding-left: calc(var(--backbutton-urlbar-overlap) + 4px);
-}
-
-@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
-  padding-right: calc(var(--backbutton-urlbar-overlap) + 4px);
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
+  padding-inline-start: calc(var(--backbutton-urlbar-overlap) + 4px);
 }
 
 @conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
   /* forward button hiding is delayed when hovered */
   transition-delay: 100s;
 }
 
-@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
-  padding-left: calc(var(--backbutton-urlbar-overlap) + 4.01px);
-}
-
-@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
-  /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
-  padding-right: calc(var(--backbutton-urlbar-overlap) + 4.01px);
+  padding-inline-start: calc(var(--backbutton-urlbar-overlap) + 4.01px);
 }
 
 /* TRACKING PROTECTION ICON */
 
 #tracking-protection-icon {
   width: 16px;
   height: 16px;
   margin-inline-end: 2px;
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -328,19 +328,20 @@ description > html|a {
   text-align: center;
   -moz-user-select: none;
 }
 
 .close-icon {
   background-color: transparent !important;
   border: none;
   box-shadow: none;
-  height: 18px;
   padding: 0;
-  min-width: 18px;
+  height: auto;
+  min-height: 16px;
+  min-width: 0;
 }
 
 #dialogBox > .groupbox-body {
   -moz-appearance: none;
   padding: 20px;
 }
 
 #dialogFrame {
copy from toolkit/themes/windows/global/icons/close-XPVista7.png
copy to browser/themes/shared/newtab/close.png
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -50,26 +50,26 @@
 #newtab-undo-close-button {
   -moz-appearance: none;
   padding: 0;
   border: none;
   height: 16px;
   width: 16px;
   float: right;
   right: 0;
-  background-image: -moz-image-rect(url(chrome://global/skin/icons/close.png), 0, 16, 16, 0);
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 16, 16, 0);
   background-color: transparent;
 }
 
 #newtab-undo-close-button:hover {
-  background-image: -moz-image-rect(url(chrome://global/skin/icons/close.png), 0, 32, 16, 16);
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 32, 16, 16);
 }
 
-#newtab-undo-close-button:active {
-  background-image: -moz-image-rect(url(chrome://global/skin/icons/close.png), 0, 48, 16, 32);
+#newtab-undo-close-button:hover:active {
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 48, 16, 32);
 }
 
 /* CUSTOMIZE */
 #newtab-customize-button,
 .newtab-customize {
   background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 32, 32, 0);
   background-size: 28px;
   height: 38px;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -243,16 +243,17 @@ browser.jar:
         skin/classic/browser/downloads/downloads.css                 (downloads/downloads.css)
         skin/classic/browser/feeds/feedIcon.png                      (feeds/feedIcon.png)
         skin/classic/browser/feeds/feedIcon16.png                    (feeds/feedIcon16.png)
         skin/classic/browser/feeds/feedIcon-XP.png                   (feeds/feedIcon-XP.png)
         skin/classic/browser/feeds/feedIcon16-XP.png                 (feeds/feedIcon16-XP.png)
         skin/classic/browser/feeds/subscribe.css                     (feeds/subscribe.css)
         skin/classic/browser/feeds/subscribe-ui.css                  (feeds/subscribe-ui.css)
 *       skin/classic/browser/newtab/newTab.css                       (newtab/newTab.css)
+        skin/classic/browser/newtab/close.png                        (../shared/newtab/close.png)
         skin/classic/browser/newtab/controls.svg                     (../shared/newtab/controls.svg)
         skin/classic/browser/newtab/whimsycorn.png                   (../shared/newtab/whimsycorn.png)
         skin/classic/browser/panic-panel/header.png                  (../shared/panic-panel/header.png)
         skin/classic/browser/panic-panel/header@2x.png               (../shared/panic-panel/header@2x.png)
         skin/classic/browser/panic-panel/header-small.png            (../shared/panic-panel/header-small.png)
         skin/classic/browser/panic-panel/header-small@2x.png         (../shared/panic-panel/header-small@2x.png)
         skin/classic/browser/panic-panel/icons.png                   (../shared/panic-panel/icons.png)
         skin/classic/browser/panic-panel/icons@2x.png                (../shared/panic-panel/icons@2x.png)
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -410,17 +410,19 @@ pref("devtools.remote.usb.enabled", fals
 pref("devtools.remote.wifi.enabled", false);
 
 pref("font.size.inflation.minTwips", 0);
 
 // When true, zooming will be enabled on all sites, even ones that declare user-scalable=no.
 pref("browser.ui.zoom.force-user-scalable", false);
 
 pref("ui.zoomedview.disabled", false);
-pref("ui.zoomedview.limitReadableSize", 8);  // value in layer pixels
+pref("ui.zoomedview.limitReadableSize", 8); // value in layer pixels
+pref("ui.zoomedview.defaultZoomFactor", 2);
+pref("ui.zoomedview.simplified", true); // Do not display all the zoomed view controls
 
 pref("ui.touch.radius.enabled", false);
 pref("ui.touch.radius.leftmm", 3);
 pref("ui.touch.radius.topmm", 5);
 pref("ui.touch.radius.rightmm", 3);
 pref("ui.touch.radius.bottommm", 2);
 pref("ui.touch.radius.visitedWeight", 120);
 
--- a/mobile/android/base/ZoomedView.java
+++ b/mobile/android/base/ZoomedView.java
@@ -5,16 +5,17 @@
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.gfx.PointUtils;
 import org.mozilla.gecko.mozglue.DirectBufferAllocator;
+import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -46,27 +47,32 @@ import android.widget.TextView;
 
 import java.nio.ByteBuffer;
 import java.text.DecimalFormat;
 
 public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChangedListener,
         LayerView.ZoomedViewListener, GeckoEventListener {
     private static final String LOGTAG = "Gecko" + ZoomedView.class.getSimpleName();
 
-    private static final float[] ZOOM_FACTORS_LIST = {2.0f, 3.0f, 1.5f};
+    private static final float[] ZOOM_FACTORS_LIST = {2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 1.5f};
     private static final int W_CAPTURED_VIEW_IN_PERCENT = 50;
     private static final int H_CAPTURED_VIEW_IN_PERCENT = 50;
     private static final int MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS = 1000000;
     private static final int DELAY_BEFORE_NEXT_RENDER_REQUEST_MS = 2000;
     private static final int OPENING_ANIMATION_DURATION_MS = 250;
     private static final int CLOSING_ANIMATION_DURATION_MS = 150;
     private static final float OVERSHOOT_INTERPOLATOR_TENSION = 1.5f;
 
     private float zoomFactor;
     private int currentZoomFactorIndex;
+    private boolean isSimplifiedUI;
+    private int defaultZoomFactor;
+    private int prefDefaultZoomFactorObserverId;
+    private int prefSimplifiedUIObserverId;
+
     private ImageView zoomedImageView;
     private LayerView layerView;
     private int viewWidth;
     private int viewHeight; // Only the zoomed view height, no toolbar, no shadow ...
     private int viewContainerWidth;
     private int viewContainerHeight; // Zoomed view height with toolbar and other elements like shadow, ...
     private int containterSize; // shadow, margin, ...
     private Point lastPosition;
@@ -208,78 +214,89 @@ public class ZoomedView extends FrameLay
     }
 
     public ZoomedView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
     public ZoomedView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        isSimplifiedUI = true;
+        getPrefs();
+        currentZoomFactorIndex = 0;
         returnValue = new PointF();
         animationStart = new PointF();
-        currentZoomFactorIndex = 0;
-        zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
         requestRenderRunnable = new Runnable() {
             @Override
             public void run() {
                 requestZoomedViewRender();
             }
         };
         touchListener = new ZoomedViewTouchListener();
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
                 "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
                 "Gesture:CloseZoomedView");
     }
 
     void destroy() {
+        PrefsHelper.removeObserver(prefDefaultZoomFactorObserverId);
+        PrefsHelper.removeObserver(prefSimplifiedUIObserverId);
         ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
                 "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
                 "Gesture:CloseZoomedView");
     }
 
     // This method (onFinishInflate) is called only when the zoomed view class is used inside
     // an xml structure <org.mozilla.gecko.ZoomedView ...
     // It won't be called if the class is used from java code like "new  ZoomedView(context);"
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         closeButton = (ImageView) findViewById(R.id.dialog_close);
         changeZoomFactorButton = (TextView) findViewById(R.id.change_zoom_factor);
         zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
 
-        setTextInZoomFactorButton(ZOOM_FACTORS_LIST[0]);
+        setTextInZoomFactorButton(zoomFactor);
 
         toolbarHeight = getResources().getDimensionPixelSize(R.dimen.zoomed_view_toolbar_height);
         containterSize = getResources().getDimensionPixelSize(R.dimen.drawable_dropshadow_size);
         cornerRadius = getResources().getDimensionPixelSize(R.dimen.button_corner_radius);
 
         moveToolbar(true);
     }
 
     private void setListeners() {
         closeButton.setOnClickListener(new View.OnClickListener() {
             public void onClick(View view) {
                 stopZoomDisplay(true);
             }
         });
 
-        changeZoomFactorButton.setOnClickListener(new View.OnClickListener() {
-            public void onClick(View view) {
-                changeZoomFactor();
+        changeZoomFactorButton.setOnTouchListener(new  OnTouchListener() {
+            public boolean onTouch(View v, MotionEvent event) {
+
+                if (event.getAction() == MotionEvent.ACTION_UP) {
+                    if (event.getX() >= (changeZoomFactorButton.getLeft() + changeZoomFactorButton.getWidth() / 2)) {
+                        changeZoomFactor(true);
+                    } else {
+                        changeZoomFactor(false);
+                    }
+                }
+                return true;
             }
         });
 
         setOnTouchListener(touchListener);
     }
 
     private void removeListeners() {
         closeButton.setOnClickListener(null);
 
-        changeZoomFactorButton.setOnClickListener(null);
+        changeZoomFactorButton.setOnTouchListener(null);
 
         setOnTouchListener(null);
     }
     /*
      * Convert a click from ZoomedView. Return the position of the click in the
      * LayerView
      */
     private PointF getUnzoomedPositionFromPointInZoomedView(float x, float y) {
@@ -474,17 +491,57 @@ public class ZoomedView extends FrameLay
     private void shouldBlockUpdate(boolean shouldBlockUpdate) {
         stopUpdateView = shouldBlockUpdate;
     }
 
     private Bitmap.Config getBitmapConfig() {
         return (GeckoAppShell.getScreenDepth() == 24) ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
     }
 
-    private void startZoomDisplay(LayerView aLayerView, final int leftFromGecko, final int topFromGecko) {
+    private void getPrefs() {
+        prefSimplifiedUIObserverId = PrefsHelper.getPref("ui.zoomedview.simplified", new PrefsHelper.PrefHandlerBase() {
+            @Override
+            public void prefValue(String pref, boolean simplified) {
+                isSimplifiedUI = simplified;
+                if (simplified) {
+                    changeZoomFactorButton.setVisibility(View.INVISIBLE);
+                    zoomFactor = (float) defaultZoomFactor;
+                } else {
+                    zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
+                    setTextInZoomFactorButton(zoomFactor);
+                    changeZoomFactorButton.setVisibility(View.VISIBLE);
+                }
+            }
+
+            @Override
+            public boolean isObserver() {
+                return true;
+            }
+        });
+
+        prefDefaultZoomFactorObserverId = PrefsHelper.getPref("ui.zoomedview.defaultZoomFactor", new PrefsHelper.PrefHandlerBase() {
+            @Override
+            public void prefValue(String pref, int defaultZoomFactorFromSettings) {
+                defaultZoomFactor = defaultZoomFactorFromSettings;
+                if (isSimplifiedUI) {
+                    zoomFactor = (float) defaultZoomFactor;
+                } else {
+                    zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
+                    setTextInZoomFactorButton(zoomFactor);
+                }
+            }
+
+            @Override
+            public boolean isObserver() {
+                return true;
+            }
+        });
+    }
+
+private void startZoomDisplay(LayerView aLayerView, final int leftFromGecko, final int topFromGecko) {
         if (layerView == null) {
             layerView = aLayerView;
             layerView.addZoomedViewListener(this);
             layerView.setOnMetricsChangedZoomedViewportListener(this);
             ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
             setCapturedSize(metrics);
         }
         startTimeReRender = 0;
@@ -509,32 +566,36 @@ public class ZoomedView extends FrameLay
             if (layerView != null) {
                 layerView.setOnMetricsChangedZoomedViewportListener(null);
                 layerView.removeZoomedViewListener(this);
                 layerView = null;
             }
         }
     }
 
-    private void changeZoomFactor() {
-        if (currentZoomFactorIndex < ZOOM_FACTORS_LIST.length - 1) {
+    private void changeZoomFactor(boolean zoomIn) {
+        if (zoomIn && currentZoomFactorIndex < ZOOM_FACTORS_LIST.length - 1) {
             currentZoomFactorIndex++;
+        } else if (zoomIn && currentZoomFactorIndex >= ZOOM_FACTORS_LIST.length - 1) {
+            currentZoomFactorIndex = 0;
+        } else if (!zoomIn && currentZoomFactorIndex > 0) {
+            currentZoomFactorIndex--;
         } else {
-            currentZoomFactorIndex = 0;
+            currentZoomFactorIndex = ZOOM_FACTORS_LIST.length - 1;
         }
         zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
 
         ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
         refreshZoomedViewSize(metrics);
         setTextInZoomFactorButton(zoomFactor);
     }
 
     private void setTextInZoomFactorButton(float zoom) {
         final String percentageValue = Integer.toString((int) (100*zoom));
-        changeZoomFactorButton.setText(getResources().getString(R.string.percent, percentageValue));
+        changeZoomFactorButton.setText("- " + getResources().getString(R.string.percent, percentageValue) + " +");
     }
 
     @Override
     public void handleMessage(final String event, final JSONObject message) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 try {
--- a/mobile/android/base/home/HistoryPanel.java
+++ b/mobile/android/base/home/HistoryPanel.java
@@ -5,28 +5,31 @@
 
 package org.mozilla.gecko.home;
 
 import java.util.Date;
 import java.util.EnumSet;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.RestrictedProfiles;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
+import org.mozilla.gecko.restrictions.Restriction;
 
 import android.app.AlertDialog;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.database.Cursor;
 import android.graphics.Typeface;
 import android.os.Bundle;
@@ -34,16 +37,17 @@ import android.support.v4.content.Loader
 import android.text.SpannableStringBuilder;
 import android.text.TextPaint;
 import android.text.method.LinkMovementMethod;
 import android.text.style.ClickableSpan;
 import android.text.style.StyleSpan;
 import android.text.style.UnderlineSpan;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.ContextMenu;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.AdapterView;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -157,16 +161,25 @@ public class HistoryPanel extends HomeFr
                 });
 
                 dialogBuilder.show();
             }
         });
     }
 
     @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, view, menuInfo);
+
+        if (!RestrictedProfiles.isAllowed(getActivity(), Restriction.DISALLOW_CLEAR_HISTORY)) {
+            menu.findItem(R.id.home_remove).setVisible(false);
+        }
+    }
+
+    @Override
     public void onDestroyView() {
         super.onDestroyView();
         mList = null;
         mEmptyView = null;
         mClearHistoryButton = null;
     }
 
     @Override
@@ -201,17 +214,19 @@ public class HistoryPanel extends HomeFr
         public Cursor loadCursor() {
             final ContentResolver cr = getContext().getContentResolver();
             return mDB.getRecentHistory(cr, HISTORY_LIMIT);
         }
     }
 
     private void updateUiFromCursor(Cursor c) {
         if (c != null && c.getCount() > 0) {
-            mClearHistoryButton.setVisibility(View.VISIBLE);
+            if (RestrictedProfiles.isAllowed(getActivity(), Restriction.DISALLOW_CLEAR_HISTORY)) {
+                mClearHistoryButton.setVisibility(View.VISIBLE);
+            }
             return;
         }
 
         // Cursor is empty, so hide the "Clear browsing history" button,
         // and set the empty view if it hasn't been set already.
         mClearHistoryButton.setVisibility(View.GONE);
 
         if (mEmptyView == null) {
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -696,8 +696,14 @@ just addresses the organization to follo
 <!ENTITY restriction_disallow_devtools_title "Disallow developer tools">
 <!ENTITY restriction_disallow_devtools_description "Disallow usage of developer tools.">
 <!ENTITY restriction_disallow_customize_home_title "Disallow customizing home">
 <!ENTITY restriction_disallow_customize_home_description "Disallow customizing home panels.">
 <!ENTITY restriction_disallow_private_browsing_title "Disallow private browsing">
 <!ENTITY restriction_disallow_private_browsing_description "Disallow private browsing mode.">
 <!ENTITY restriction_disallow_location_services_title "Disallow location services">
 <!ENTITY restriction_disallow_location_services_description "Disallow sharing of location data to improve geolocation service.">
+<!ENTITY restriction_disallow_display_settings_title "Disallow display settings">
+<!ENTITY restriction_disallow_display_settings_description "Disallow changing of advanced display settings.">
+<!ENTITY restriction_disallow_clear_history_title "Disallow clearing history">
+<!ENTITY restriction_disallow_clear_history_description "Disallow clearing of browser history.">
+<!ENTITY restriction_disallow_master_password_title "Disallow master password">
+<!ENTITY restriction_disallow_master_password_description "Disallow setting a master password for logins.">
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -130,16 +130,17 @@ OnSharedPreferenceChangeListener
     private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
     public static final String PREFS_OPEN_URLS_IN_PRIVATE = NON_PREF_PREFIX + "openExternalURLsPrivately";
     public static final String PREFS_VOICE_INPUT_ENABLED = NON_PREF_PREFIX + "voice_input_enabled";
     public static final String PREFS_QRCODE_ENABLED = NON_PREF_PREFIX + "qrcode_enabled";
     private static final String PREFS_DEVTOOLS = NON_PREF_PREFIX + "devtools.enabled";
     private static final String PREFS_CUSTOMIZE_HOME = NON_PREF_PREFIX + "customize_home";
     private static final String PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING = "privacy.trackingprotection.pbmode.enabled";
     private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
+    private static final String PREFS_CATEGORY_PRIVATE_DATA = NON_PREF_PREFIX + "category_private_data";
 
     private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
 
 
     // This isn't a Gecko pref, even if it looks like one.
     private static final String PREFS_BROWSER_LOCALE = "locale";
 
     public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
@@ -486,16 +487,18 @@ OnSharedPreferenceChangeListener
 
                 if (header.id == R.id.pref_header_language && !localeSwitchingIsEnabled) {
                     // If locale switching is disabled, remove the section
                     // entirely. This logic will need to be extended when
                     // content language selection (Bug 881510) is implemented.
                     iterator.remove();
                 } else if (header.id == R.id.pref_header_devtools && !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_DEVELOPER_TOOLS)) {
                     iterator.remove();
+                } else if (header.id == R.id.pref_header_display && !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_DISPLAY_SETTINGS)) {
+                    iterator.remove();
                 }
             }
         }
     }
 
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         if (!hasFocus || mInitialized)
@@ -710,16 +713,23 @@ OnSharedPreferenceChangeListener
 
                 if (PREFS_CUSTOMIZE_HOME.equals(key)) {
                     if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_CUSTOMIZE_HOME)) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 }
+                if (PREFS_CATEGORY_PRIVATE_DATA.equals(key)) {
+                    if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_CLEAR_HISTORY)) {
+                        preferences.removePreference(pref);
+                        i--;
+                        continue;
+                    }
+                }
 
                 setupPreferences((PreferenceGroup) pref, prefs);
             } else {
                 pref.setOnPreferenceChangeListener(this);
                 if (PREFS_UPDATER_AUTODOWNLOAD.equals(key)) {
                     if (!AppConstants.MOZ_UPDATER) {
                         preferences.removePreference(pref);
                         i--;
@@ -857,16 +867,22 @@ OnSharedPreferenceChangeListener
                         continue;
                     }
                 } else if (PREFS_TRACKING_PROTECTION_LEARN_MORE.equals(key)) {
                     if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING)) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
+                } else if (PREFS_MP_ENABLED.equals(key)) {
+                    if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_MASTER_PASSWORD)) {
+                        preferences.removePreference(pref);
+                        i--;
+                        continue;
+                    }
                 }
 
                 // Some Preference UI elements are not actually preferences,
                 // but they require a key to work correctly. For example,
                 // "Clear private data" requires a key for its state to be
                 // saved when the orientation changes. It uses the
                 // "android.not_a_preference.privacy.clear" key - which doesn't
                 // exist in Gecko - to satisfy this requirement.
--- a/mobile/android/base/resources/layout/zoomed_view.xml
+++ b/mobile/android/base/resources/layout/zoomed_view.xml
@@ -15,25 +15,30 @@
     android:visibility="gone">
 
     <RelativeLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true"
         android:background="@drawable/toolbar_grey_round">
+        <!--
+           Zoom factor button is invisible by default. Set ui.zoomedview.simplified to false
+           in order to display the button in the zoomed view tool bar
+        -->
         <TextView
             android:id="@+id/change_zoom_factor"
             android:layout_width="wrap_content"
             android:layout_height="@dimen/zoomed_view_toolbar_height"
             android:background="@android:color/transparent"
             android:padding="12dip"
             android:layout_alignLeft="@+id/zoomed_image_view"
             android:textSize="16sp"
-            android:textColor="@color/text_and_tabs_tray_grey"/>
+            android:textColor="@color/text_and_tabs_tray_grey"
+            android:visibility="invisible"/>
         <ImageView
             android:id="@+id/dialog_close"
             android:scaleType="center"
             android:layout_width="@dimen/zoomed_view_toolbar_height"
             android:layout_height="@dimen/zoomed_view_toolbar_height"
             android:layout_alignRight="@id/zoomed_image_view"
             android:src="@drawable/close_edit_mode_selector"/>
         <ImageView
--- a/mobile/android/base/resources/xml-v11/preference_headers.xml
+++ b/mobile/android/base/resources/xml-v11/preference_headers.xml
@@ -11,17 +11,18 @@
 
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
             android:title="@string/pref_header_customize">
         <extra android:name="resource"
                android:value="preferences_customize_tablet"/>
     </header>
 
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
-            android:title="@string/pref_header_display">
+            android:title="@string/pref_header_display"
+            android:id="@+id/pref_header_display">
         <extra android:name="resource"
                android:value="preferences_display"/>
     </header>
 
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
             android:title="@string/pref_header_privacy_short">
         <extra android:name="resource"
                android:value="preferences_privacy"/>
--- a/mobile/android/base/resources/xml/preference_headers.xml
+++ b/mobile/android/base/resources/xml/preference_headers.xml
@@ -8,9 +8,12 @@
 
 <preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
             android:id="@+id/pref_header_language">
     </header>
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
             android:id="@+id/pref_header_devtools">
     </header>
+    <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
+            android:id="@+id/pref_header_display">
+    </header>
 </preference-headers>
--- a/mobile/android/base/resources/xml/preferences_privacy.xml
+++ b/mobile/android/base/resources/xml/preferences_privacy.xml
@@ -48,17 +48,17 @@
 
         <CheckBoxPreference android:key="privacy.masterpassword.enabled"
                             android:title="@string/pref_use_master_password"
                             android:persistent="false" />
 
     </PreferenceCategory>
 
     <!-- keys prefixed with "android.not_a_preference." are not synced with Gecko -->
-    <PreferenceCategory android:title="@string/pref_clear_private_data_category">
+    <PreferenceCategory android:key="android.not_a_preference.category_private_data" android:title="@string/pref_clear_private_data_category">
 
         <org.mozilla.gecko.preferences.PrivateDataPreference
                             android:key="android.not_a_preference.privacy.clear"
                             android:title="@string/pref_clear_private_data"
                             android:persistent="true"
                             android:positiveButtonText="@string/button_clear_data"
                             gecko:entries="@array/pref_private_data_entries"
                             gecko:entryValues="@array/pref_private_data_values"
--- a/mobile/android/base/restrictions/RestrictedProfileConfiguration.java
+++ b/mobile/android/base/restrictions/RestrictedProfileConfiguration.java
@@ -19,21 +19,25 @@ public class RestrictedProfileConfigurat
             Restriction.DISALLOW_INSTALL_EXTENSION,
             Restriction.DISALLOW_INSTALL_APPS,
             Restriction.DISALLOW_TOOLS_MENU,
             Restriction.DISALLOW_REPORT_SITE_ISSUE,
             Restriction.DISALLOW_IMPORT_SETTINGS,
             Restriction.DISALLOW_DEVELOPER_TOOLS,
             Restriction.DISALLOW_CUSTOMIZE_HOME,
             Restriction.DISALLOW_PRIVATE_BROWSING,
-            Restriction.DISALLOW_LOCATION_SERVICE
+            Restriction.DISALLOW_LOCATION_SERVICE,
+            Restriction.DISALLOW_DISPLAY_SETTINGS,
+            Restriction.DISALLOW_CLEAR_HISTORY,
+            Restriction.DISALLOW_MASTER_PASSWORD
     );
 
     private static final String ABOUT_ADDONS = "about:addons";
     private static final String ABOUT_PRIVATE_BROWSING = "about:privatebrowsing";
+    private static final String ABOUT_CONFIG = "about:config";
 
     private Context context;
 
     public RestrictedProfileConfiguration(Context context) {
         this.context = context.getApplicationContext();
     }
 
     @Override
@@ -54,16 +58,21 @@ public class RestrictedProfileConfigurat
         if (!isAllowed(Restriction.DISALLOW_INSTALL_EXTENSION) && url.toLowerCase().startsWith(ABOUT_ADDONS)) {
             return false;
         }
 
         if (!isAllowed(Restriction.DISALLOW_PRIVATE_BROWSING) && url.toLowerCase().startsWith(ABOUT_PRIVATE_BROWSING)) {
             return false;
         }
 
+        if (url.toLowerCase().startsWith(ABOUT_CONFIG)) {
+            // Always block access to about:config to prevent circumventing restrictions (Bug 1189233)
+            return false;
+        }
+
         return true;
     }
 
     @Override
     public boolean isRestricted() {
         return true;
     }
 
--- a/mobile/android/base/restrictions/Restriction.java
+++ b/mobile/android/base/restrictions/Restriction.java
@@ -83,16 +83,34 @@ public enum Restriction {
             R.string.restriction_disallow_private_browsing_title,
             R.string.restriction_disallow_private_browsing_description
     ),
 
     DISALLOW_LOCATION_SERVICE(
             17, "no_location_service",
             R.string.restriction_disallow_location_services_title,
             R.string.restriction_disallow_location_services_description
+    ),
+
+    DISALLOW_DISPLAY_SETTINGS(
+            18, "no_display_settings",
+            R.string.restriction_disallow_display_settings_title,
+            R.string.restriction_disallow_display_settings_description
+    ),
+
+    DISALLOW_CLEAR_HISTORY(
+            19, "no_clear_history",
+            R.string.restriction_disallow_clear_history_title,
+            R.string.restriction_disallow_clear_history_description
+    ),
+
+    DISALLOW_MASTER_PASSWORD(
+            20, "no_master_password",
+            R.string.restriction_disallow_master_password_title,
+            R.string.restriction_disallow_master_password_description
     );
 
     public final int id;
     public final String name;
     public final int titleResource;
     public final int descriptionResource;
 
     Restriction(final int id, final String name, int titleResource, int descriptionResource) {
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -552,16 +552,22 @@
   <string name="restriction_disallow_devtools_title">&restriction_disallow_devtools_title;</string>
   <string name="restriction_disallow_devtools_description">&restriction_disallow_devtools_description;</string>
   <string name="restriction_disallow_customize_home_title">&restriction_disallow_customize_home_title;</string>
   <string name="restriction_disallow_customize_home_description">&restriction_disallow_customize_home_description;</string>
   <string name="restriction_disallow_private_browsing_title">&restriction_disallow_private_browsing_title;</string>
   <string name="restriction_disallow_private_browsing_description">&restriction_disallow_private_browsing_description;</string>
   <string name="restriction_disallow_location_services_title">&restriction_disallow_location_services_title;</string>
   <string name="restriction_disallow_location_services_description">&restriction_disallow_location_services_description;</string>
+  <string name="restriction_disallow_display_settings_title">&restriction_disallow_display_settings_title;</string>
+  <string name="restriction_disallow_display_settings_description">&restriction_disallow_display_settings_description;</string>
+  <string name="restriction_disallow_clear_history_title">&restriction_disallow_clear_history_title;</string>
+  <string name="restriction_disallow_clear_history_description">&restriction_disallow_clear_history_description;</string>
+  <string name="restriction_disallow_master_password_title">&restriction_disallow_master_password_title;</string>
+  <string name="restriction_disallow_master_password_description">&restriction_disallow_master_password_description;</string>
 
   <!-- Miscellaneous -->
   <string name="ellipsis">&ellipsis;</string>
 
   <string name="colon">&colon;</string>
 
   <string name="percent">&percent;</string>
 
--- a/mobile/android/base/toolbar/ToolbarEditLayout.java
+++ b/mobile/android/base/toolbar/ToolbarEditLayout.java
@@ -12,16 +12,18 @@ import android.content.Intent;
 import android.speech.RecognizerIntent;
 import android.widget.Button;
 import android.widget.ImageButton;
 import org.mozilla.gecko.ActivityHandlerHelper;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.util.ActivityResultHandler;
@@ -222,26 +224,28 @@ public class ToolbarEditLayout extends T
         if (!voiceIsSupported) {
             return false;
         }
         return GeckoSharedPrefs.forApp(context)
                 .getBoolean(GeckoPreferences.PREFS_VOICE_INPUT_ENABLED, true);
     }
 
     private void launchVoiceRecognizer() {
+        Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "voice_input_launch");
         final Intent intent = InputOptionsUtils.createVoiceRecognizerIntent(getResources().getString(R.string.voicesearch_prompt));
 
         Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ActivityResultHandler() {
             @Override
             public void onActivityResult(int resultCode, Intent data) {
                 if (resultCode != Activity.RESULT_OK) {
                     return;
                 }
 
+                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "voice_input_success");
                 // We have RESULT_OK, not RESULT_NO_MATCH so it should be safe to assume that
                 // we have at least one match. We only need one: this will be
                 // used for showing the user search engines with this search term in it.
                 List<String> voiceStrings = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                 String text = voiceStrings.get(0);
                 mEditText.setText(text);
                 mEditText.setSelection(0, text.length());
 
@@ -257,25 +261,27 @@ public class ToolbarEditLayout extends T
         if (!qrCodeIsSupported) {
             return false;
         }
         return GeckoSharedPrefs.forApp(context)
                 .getBoolean(GeckoPreferences.PREFS_QRCODE_ENABLED, true);
     }
 
     private void launchQRCodeReader() {
+        Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "qrcode_input_launch");
         final Intent intent = InputOptionsUtils.createQRCodeReaderIntent();
 
         Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ActivityResultHandler() {
             @Override
             public void onActivityResult(int resultCode, Intent intent) {
                 if (resultCode == Activity.RESULT_OK) {
                     String text = intent.getStringExtra("SCAN_RESULT");
                     if (!StringUtils.isSearchQuery(text, false)) {
+                        Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "qrcode_input_success");
                         mEditText.setText(text);
                         mEditText.selectAll();
 
                         // Queuing up the keyboard show action.
                         // At this point the app has not resumed yet, and trying to show
                         // the keyboard will fail.
                         showKeyboardOnFocus = true;
                     }
--- a/mobile/android/tests/browser/robocop/robocop.ini
+++ b/mobile/android/tests/browser/robocop/robocop.ini
@@ -133,16 +133,17 @@ skip-if = android_version == "18"
 [testOrderedBroadcast.java]
 [testOSLocale.java]
 # disabled on 2.3 and 4.3: Bug 1124494
 skip-if = android_version == "10" || android_version == "18"
 [testReaderView.java]
 [testReadingListCache.java]
 [testResourceSubstitutions.java]
 [testRestrictedProfiles.java]
+[testRestrictions.java]
 [testSessionFormData.java]
 [testSharedPreferences.java]
 [testSimpleDiscovery.java]
 [testTrackingProtection.java]
 # disabled on 4.3, bug 1158363
 skip-if = android_version == "18"
 [testUITelemetry.java]
 [testVideoControls.java]
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/robocop/testRestrictions.java
@@ -0,0 +1,24 @@
+/* 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/. */
+
+package org.mozilla.gecko.tests;
+
+import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
+
+import org.mozilla.gecko.RestrictedProfiles;
+import org.mozilla.gecko.restrictions.Restriction;
+import org.mozilla.gecko.tests.helpers.GeckoHelper;
+
+public class testRestrictions extends UITest {
+    public void testRestrictions() {
+        GeckoHelper.blockForReady();
+
+        // No restrictions should be enforced when using a normal profile
+        for (Restriction restriction : Restriction.values()) {
+            fAssertTrue(String.format("Restriction %s is not enforced", restriction.name),
+                RestrictedProfiles.isAllowed(getActivity(), restriction)
+            );
+        }
+    }
+}
--- a/toolkit/components/parentalcontrols/nsIParentalControlsService.idl
+++ b/toolkit/components/parentalcontrols/nsIParentalControlsService.idl
@@ -6,17 +6,17 @@
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIFile;
 interface nsIInterfaceRequestor;
 interface nsIArray;
 
-[scriptable, uuid(4ee714a7-e9a8-43ed-a061-60155b63e290)]
+[scriptable, uuid(30ff7af7-ae52-4bd6-88c0-4a8ce4f37bdc)]
 interface nsIParentalControlsService : nsISupports
 {
   /**
    * Action types that can be blocked for users.
    */
   const short DOWNLOAD = 1; // Downloading files
   const short INSTALL_EXTENSION = 2; // Installing extensions
   const short INSTALL_APP = 3; // Installing webapps
@@ -27,16 +27,19 @@ interface nsIParentalControlsService : n
   const short SET_IMAGE = 8; // Setting images as wall paper
   const short MODIFY_ACCOUNTS = 9; // Modifying system accounts
   const short REMOTE_DEBUGGING = 10; // Remote debugging
   const short IMPORT_SETTINGS = 11; // Importing settings from other apps
   const short TOOLS_MENU = 12; // Hide tools menu entry
   const short REPORT_SITE_ISSUE = 13; // Hide "Report Site Issue" menu entry
   const short PRIVATE_BROWSING = 16; // Disallow usage of private browsing
   const short LOCATION_SERVICE = 17; // Sharing of location data to location service
+  const short DISPLAY_SETTINGS = 18; // Website display settings
+  const short CLEAR_HISTORY = 19; // Clear browsing history
+  const short MASTER_PASSWORD = 20; // Setting master password for logins
 
   /**
    * @returns true if the current user account has parental controls
    * restrictions enabled.
    */
   readonly attribute boolean parentalControlsEnabled;
 
   /**
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -255,16 +255,53 @@ this.LoginHelper = {
 
     // Throws if there are bogus values.
     this.checkLoginValues(newLogin);
 
     return newLogin;
   },
 
   /**
+   * Removes duplicates from a list of logins.
+   *
+   * @param {nsILoginInfo[]} logins
+   *        A list of logins we want to deduplicate.
+   *
+   * @param {string[] = ["username", "password"]} uniqueKeys
+   *        A list of login attributes to use as unique keys for the deduplication.
+   *
+   * @returns {nsILoginInfo[]} list of unique logins.
+   */
+  dedupeLogins(logins, uniqueKeys = ["username", "password"]) {
+    const KEY_DELIMITER = ":";
+
+    // Generate a unique key string from a login.
+    function getKey(login, uniqueKeys) {
+      return uniqueKeys.reduce((prev, key) => prev + KEY_DELIMITER + login[key], "");
+    }
+
+    // We use a Map to easily lookup logins by their unique keys.
+    let loginsByKeys = new Map();
+    for (let login of logins) {
+      let key = getKey(login, uniqueKeys);
+      // If we find a more recently used login for the same key, replace the existing one.
+      if (loginsByKeys.has(key)) {
+        let loginDate = login.QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+        let storedLoginDate = loginsByKeys.get(key).QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+        if (loginDate < storedLoginDate) {
+          continue;
+        }
+      }
+      loginsByKeys.set(key, login);
+    }
+    // Return the map values in the form of an array.
+    return [...loginsByKeys.values()];
+  },
+
+  /**
    * Open the password manager window.
    *
    * @param {Window} window
    *                 the window from where we want to open the dialog
    *
    * @param {string} [filterString=""]
    *                 the filterString parameter to pass to the login manager dialog
    */
--- a/toolkit/components/passwordmgr/test/unit/head.js
+++ b/toolkit/components/passwordmgr/test/unit/head.js
@@ -12,16 +12,17 @@
 ////////////////////////////////////////////////////////////////////////////////
 //// Globals
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/LoginRecipes.jsm");
+Cu.import("resource://gre/modules/LoginHelper.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                   "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
--- a/toolkit/components/passwordmgr/test/unit/test_logins_change.js
+++ b/toolkit/components/passwordmgr/test/unit/test_logins_change.js
@@ -47,16 +47,39 @@ function checkLoginInvalid(aLoginInfo, a
     passwordField: aLoginInfo.passwordField,
   })), aExpectedError);
 
   // Verify that no data was stored by the previous calls.
   LoginTestUtils.checkLogins([testLogin]);
   Services.logins.removeLogin(testLogin);
 }
 
+/**
+ * Verifies that two objects are not the same instance
+ * but have equal attributes.
+ *
+ * @param {Object} objectA
+ *        An object to compare.
+ *
+ * @param {Object} objectB
+ *        Another object to compare.
+ *
+ * @param {string[]} attributes
+ *        Attributes to compare.
+ *
+ * @return true if all passed attributes are equal for both objects, false otherwise.
+ */
+function compareAttributes(objectA, objectB, attributes) {
+  // If it's the same object, we want to return false.
+  if (objectA == objectB) {
+    return false;
+  }
+  return attributes.every(attr => objectA[attr] == objectB[attr]);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests
 
 /**
  * Tests that adding logins to the database works.
  */
 add_task(function test_addLogin_removeLogin()
 {
@@ -290,8 +313,74 @@ add_task(function test_modifyLogin_nsIPr
   // Modifying a login to match an existing one should not be possible.
   Assert.throws(
          () => Services.logins.modifyLogin(loginInfo, differentLoginProperties),
          /already exists/);
   LoginTestUtils.checkLogins([loginInfo, differentLoginInfo]);
 
   LoginTestUtils.clearData();
 });
+
+/**
+ * Tests the login deduplication function.
+ */
+add_task(function test_deduplicate_logins() {
+  // Different key attributes combinations and the amount of unique
+  // results expected for the TestData login list.
+  let keyCombinations = [
+    {
+      keyset: ["username", "password"],
+      results: 13,
+    },
+    {
+      keyset: ["hostname", "username"],
+      results: 17,
+    },
+    {
+      keyset: ["hostname", "username", "password"],
+      results: 18,
+    },
+    {
+      keyset: ["hostname", "username", "password", "formSubmitURL"],
+      results: 22,
+    },
+  ];
+
+  let logins = TestData.loginList();
+
+  for (let testCase of keyCombinations) {
+    // Deduplicate the logins using the current testcase keyset.
+    let deduped = LoginHelper.dedupeLogins(logins, testCase.keyset);
+    Assert.equal(deduped.length, testCase.results, "Correct amount of results.");
+
+    // Checks that every login after deduping is unique.
+    Assert.ok(deduped.every(loginA =>
+      deduped.every(loginB => !compareAttributes(loginA, loginB, testCase.keyset))
+    ), "Every login is unique.");
+  }
+});
+
+/**
+ * Ensure that the login deduplication function keeps the most recent login.
+ */
+add_task(function test_deduplicate_keeps_most_recent() {
+  // Logins to deduplicate.
+  let logins = [
+    TestData.formLogin({timeLastUsed: Date.UTC(2004, 11, 4, 0, 0, 0)}),
+    TestData.formLogin({formSubmitURL: "http://example.com", timeLastUsed: Date.UTC(2015, 11, 4, 0, 0, 0)}),
+  ];
+
+  // Deduplicate the logins.
+  let deduped = LoginHelper.dedupeLogins(logins);
+  Assert.equal(deduped.length, 1, "Deduplicated the logins array.");
+
+  // Verify that the remaining login have the most recent date.
+  let loginTimeLastUsed = deduped[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+  Assert.equal(loginTimeLastUsed, Date.UTC(2015, 11, 4, 0, 0, 0), "Most recent login was kept.");
+
+  // Deduplicate the reverse logins array.
+  deduped = LoginHelper.dedupeLogins(logins.reverse());
+  Assert.equal(deduped.length, 1, "Deduplicated the reversed logins array.");
+
+  // Verify that the remaining login have the most recent date.
+  loginTimeLastUsed = deduped[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
+  Assert.equal(loginTimeLastUsed, Date.UTC(2015, 11, 4, 0, 0, 0), "Most recent login was kept.");
+});
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4650,16 +4650,28 @@
   "TELEMETRY_ARCHIVE_EVICTING_OVER_QUOTA_MS": {
     "alert_emails": ["telemetry-client-dev@mozilla.com"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "300000",
     "n_buckets": 20,
     "description": "Time (ms) it takes for evicting over-quota pings"
   },
+  "TELEMETRY_PENDING_LOAD_FAILURE_READ": {
+    "alert_emails": ["telemetry-client-dev@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "count",
+    "description": "Number of pending Telemetry pings that failed to load from the disk"
+  },
+  "TELEMETRY_PENDING_LOAD_FAILURE_PARSE": {
+    "alert_emails": ["telemetry-client-dev@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "count",
+    "description": "Number of pending Telemetry pings that failed to parse once loaded from the disk"
+  },
   "TELEMETRY_PENDING_PINGS_SIZE_MB": {
     "alert_emails": ["telemetry-client-dev@mozilla.com"],
     "expires_in_version": "never",
     "kind": "linear",
     "high": "17",
     "n_buckets": 16,
     "description": "The size of the Telemetry pending pings directory (MB). The special value 17 is used to indicate over quota pings."
   },
@@ -4740,16 +4752,22 @@
   },
   "TELEMETRY_INVALID_PING_TYPE_SUBMITTED": {
     "alert_emails": ["telemetry-client-dev@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "keyed": true,
     "description": "Count of individual invalid ping types that were submitted to Telemetry."
   },
+  "TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS": {
+    "alert_emails": ["telemetry-client-dev@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "count",
+    "description": "Number of Telemetry ping files evicted due to server errors (4XX HTTP code received)"
+  },
   "TELEMETRY_TEST_FLAG": {
     "expires_in_version": "never",
     "kind": "flag",
     "description": "a testing histogram; not meant to be touched"
   },
   "TELEMETRY_TEST_COUNT": {
     "expires_in_version": "never",
     "kind": "count",
--- a/toolkit/components/telemetry/TelemetrySend.jsm
+++ b/toolkit/components/telemetry/TelemetrySend.jsm
@@ -896,16 +896,17 @@ let TelemetrySendImpl = {
       if (statusClass === 200) {
         // We can treat all 2XX as success.
         this._log.info("_doPing - successfully loaded, status: " + status);
         success = true;
       } else if (statusClass === 400) {
         // 4XX means that something with the request was broken.
         this._log.error("_doPing - error submitting to " + url + ", status: " + status
                         + " - ping request broken?");
+        Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").add();
         // TODO: we should handle this better, but for now we should avoid resubmitting
         // broken requests by pretending success.
         success = true;
       } else if (statusClass === 500) {
         // 5XX means there was a server-side error and we should try again later.
         this._log.error("_doPing - error submitting to " + url + ", status: " + status
                         + " - server error, should retry later");
       } else {
--- a/toolkit/components/telemetry/TelemetryStorage.jsm
+++ b/toolkit/components/telemetry/TelemetryStorage.jsm
@@ -64,16 +64,44 @@ const ARCHIVE_SIZE_PROBE_SPECIAL_VALUE =
 
 // This special value is submitted when the pending pings is outside of the quota, as
 // we don't know the size of the pings above the quota.
 const PENDING_PINGS_SIZE_PROBE_SPECIAL_VALUE = 17;
 
 const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
 
 /**
+ * This is thrown by |TelemetryStorage.loadPingFile| when reading the ping
+ * from the disk fails.
+ */
+function PingReadError(message="Error reading the ping file") {
+  Error.call(this, message);
+  let error = new Error();
+  this.name = "PingReadError";
+  this.message = message;
+  this.stack = error.stack;
+}
+PingReadError.prototype = Object.create(Error.prototype);
+PingReadError.prototype.constructor = PingReadError;
+
+/**
+ * This is thrown by |TelemetryStorage.loadPingFile| when parsing the ping JSON
+ * content fails.
+ */
+function PingParseError(message="Error parsing ping content") {
+  Error.call(this, message);
+  let error = new Error();
+  this.name = "PingParseError";
+  this.message = message;
+  this.stack = error.stack;
+}
+PingParseError.prototype = Object.create(Error.prototype);
+PingParseError.prototype.constructor = PingParseError;
+
+/**
  * This is a policy object used to override behavior for testing.
  */
 let Policy = {
   now: () => new Date(),
   getArchiveQuota: () => ARCHIVE_QUOTA_BYTES,
   getPendingPingsQuota: () => (AppConstants.platform in ["android", "gonk"])
                                 ? PENDING_PINGS_QUOTA_BYTES_MOBILE
                                 : PENDING_PINGS_QUOTA_BYTES_DESKTOP,
@@ -1166,17 +1194,27 @@ let TelemetryStorageImpl = {
   loadPendingPing: function(id) {
     this._log.trace("loadPendingPing - id: " + id);
     let info = this._pendingPings.get(id);
     if (!info) {
       this._log.trace("loadPendingPing - unknown id " + id);
       return Promise.reject(new Error("TelemetryStorage.loadPendingPing - no ping with id " + id));
     }
 
-    return this.loadPingFile(info.path, false);
+    return this.loadPingFile(info.path, false).catch(e => {
+      // If we failed to load the ping, check what happened and update the histogram.
+      // Then propagate the rejection.
+      if (e instanceof PingReadError) {
+        Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").add();
+      } else if (e instanceof PingParseError) {
+        Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").add();
+      }
+
+      return Promise.reject(e);
+    });
   },
 
   removePendingPing: function(id) {
     let info = this._pendingPings.get(id);
     if (!info) {
       this._log.trace("removePendingPing - unknown id " + id);
       return Promise.resolve();
     }
@@ -1314,31 +1352,45 @@ let TelemetryStorageImpl = {
   },
 
   /**
    * Loads a ping file.
    * @param {String} aFilePath The path of the ping file.
    * @param {Boolean} [aCompressed=false] If |true|, expects the file to be compressed using lz4.
    * @return {Promise<Object>} A promise resolved with the ping content or rejected if the
    *                           ping contains invalid data.
+   * @throws {PingReadError} There was an error while reading the ping file from the disk.
+   * @throws {PingParseError} There was an error while parsing the JSON content of the ping file.
    */
   loadPingFile: Task.async(function* (aFilePath, aCompressed = false) {
     let options = {};
     if (aCompressed) {
       options.compression = "lz4";
     }
-    let array = yield OS.File.read(aFilePath, options);
+
+    let array;
+    try {
+      array = yield OS.File.read(aFilePath, options);
+    } catch(e) {
+      throw new PingReadError(e.message);
+    };
+
     let decoder = new TextDecoder();
     let string = decoder.decode(array);
+    let ping;
+    try {
+      ping = JSON.parse(string);
+      // The ping's payload used to be stringified JSON.  Deal with that.
+      if (typeof(ping.payload) == "string") {
+        ping.payload = JSON.parse(ping.payload);
+      }
+    } catch (e) {
+      throw new PingParseError(e.message);
+    }
 
-    let ping = JSON.parse(string);
-    // The ping's payload used to be stringified JSON.  Deal with that.
-    if (typeof(ping.payload) == "string") {
-      ping.payload = JSON.parse(ping.payload);
-    }
     return ping;
   }),
 
   /**
    * Archived pings are saved with file names of the form:
    * "<timestamp>.<uuid>.<type>.[json|jsonlz4]"
    * This helper extracts that data from a given filename.
    *
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js
@@ -9,16 +9,18 @@
 Cu.import("resource://gre/modules/TelemetryController.jsm", this);
 Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
 Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
 Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/Preferences.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 
+const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+
 const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
 const PREF_TELEMETRY_SERVER = "toolkit.telemetry.server";
 
 const MS_IN_A_MINUTE = 60 * 1000;
 
 function countPingTypes(pings) {
   let countByType = new Map();
   for (let p of pings) {
@@ -231,16 +233,52 @@ add_task(function* test_backoffTimeout()
   Assert.equal(countByType.get(TEST_TYPE_C), 1, "Should have received the correct amount of type C pings");
   Assert.equal(countByType.get(TEST_TYPE_D), 1, "Should have received the correct amount of type D pings");
   Assert.equal(countByType.get(TEST_TYPE_E), 1, "Should have received the correct amount of type E pings");
 
   yield TelemetrySend.testWaitOnOutgoingPings();
   Assert.equal(TelemetrySend.pendingPingCount, 0, "Should have no pending pings left");
 });
 
+add_task(function* test_evictedOnServerErrors() {
+  const TEST_TYPE = "test-evicted";
+
+  yield TelemetrySend.reset();
+
+  // Write a custom ping handler which will return 403. This will trigger ping eviction
+  // on client side.
+  PingServer.registerPingHandler((req, res) => {
+    res.setStatusLine(null, 403, "Forbidden");
+    res.processAsync();
+    res.finish();
+  });
+
+  // Clear the histogram and submit a ping.
+  Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").clear();
+  let pingId = yield TelemetryController.submitExternalPing(TEST_TYPE, {});
+  yield TelemetrySend.testWaitOnOutgoingPings();
+
+  let h = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").snapshot();
+  Assert.equal(h.sum, 1, "Telemetry must report a ping evicted due to server errors");
+
+  // The ping should not be persisted.
+  yield Assert.rejects(TelemetryStorage.loadPendingPing(pingId), "The ping must not be persisted.");
+
+  // Reset the ping handler and submit a new ping.
+  PingServer.resetPingHandler();
+  pingId = yield TelemetryController.submitExternalPing(TEST_TYPE, {});
+
+  let ping = yield PingServer.promiseNextPings(1);
+  Assert.equal(ping[0].id, pingId, "The correct ping must be received");
+
+  // We should not have updated the error histogram.
+  h = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").snapshot();
+  Assert.equal(h.sum, 1, "Telemetry must report a ping evicted due to server errors");
+});
+
 // Test that the current, non-persisted pending pings are properly saved on shutdown.
 add_task(function* test_persistCurrentPingsOnShutdown() {
   const TEST_TYPE = "test-persistCurrentPingsOnShutdown";
   const PING_COUNT = 5;
   yield TelemetrySend.reset();
   PingServer.stop();
   Assert.equal(TelemetrySend.pendingPingCount, 0, "Should have no pending pings yet");
 
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
@@ -268,16 +268,64 @@ add_task(function* test_overdue_old_form
 
   // |TelemetryStorage.cleanup| doesn't know how to remove a ping with no slug or id,
   // so remove it manually so that the next test doesn't fail.
   yield OS.File.remove(PING_FILES_PATHS[3]);
 
   yield clearPendingPings();
 });
 
+add_task(function* test_corrupted_pending_pings() {
+  const TEST_TYPE = "test_corrupted";
+
+  Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").clear();
+  Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").clear();
+
+  // Save a pending ping and get its id.
+  let pendingPingId = yield TelemetryController.addPendingPing(TEST_TYPE, {}, {});
+
+  // Try to load it: there should be no error.
+  yield TelemetryStorage.loadPendingPing(pendingPingId);
+
+  let h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").snapshot();
+  Assert.equal(h.sum, 0, "Telemetry must not report a pending ping load failure");
+  h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").snapshot();
+  Assert.equal(h.sum, 0, "Telemetry must not report a pending ping parse failure");
+
+  // Delete it from the disk, so that its id will be kept in the cache but it will
+  // fail loading the file.
+  yield OS.File.remove(getSavePathForPingId(pendingPingId));
+
+  // Try to load a pending ping which isn't there anymore.
+  yield Assert.rejects(TelemetryStorage.loadPendingPing(pendingPingId),
+                       "Telemetry must fail loading a ping which isn't there");
+
+  h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").snapshot();
+  Assert.equal(h.sum, 1, "Telemetry must report a pending ping load failure");
+  h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").snapshot();
+  Assert.equal(h.sum, 0, "Telemetry must not report a pending ping parse failure");
+
+  // Save a new ping, so that it gets in the pending pings cache.
+  pendingPingId = yield TelemetryController.addPendingPing(TEST_TYPE, {}, {});
+  // Overwrite it with a corrupted JSON file and then try to load it.
+  const INVALID_JSON = "{ invalid,JSON { {1}";
+  yield OS.File.writeAtomic(getSavePathForPingId(pendingPingId), INVALID_JSON, { encoding: "utf-8" });
+
+  // Try to load the ping with the corrupted JSON content.
+  yield Assert.rejects(TelemetryStorage.loadPendingPing(pendingPingId),
+                       "Telemetry must fail loading a corrupted ping");
+
+  h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").snapshot();
+  Assert.equal(h.sum, 1, "Telemetry must report a pending ping load failure");
+  h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").snapshot();
+  Assert.equal(h.sum, 1, "Telemetry must report a pending ping parse failure");
+
+  yield clearPendingPings();
+});
+
 /**
  * Create some recent and overdue pings and verify that they get sent.
  */
 add_task(function* test_overdue_pings_trigger_send() {
   let pingTypes = [
     { num: RECENT_PINGS },
     { num: OVERDUE_PINGS, age: OVERDUE_PING_FILE_AGE },
   ];
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -13,17 +13,17 @@ const { ObjectActor, createValueGrip, lo
 const { DebuggerServer } = require("devtools/server/main");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dbg_assert, dumpn, update, fetch } = DevToolsUtils;
 const { dirname, joinURI } = require("devtools/toolkit/path");
 const promise = require("promise");
 const PromiseDebugging = require("PromiseDebugging");
 const xpcInspector = require("xpcInspector");
 const ScriptStore = require("./utils/ScriptStore");
-const {DevToolsWorker} = require("devtools/toolkit/shared/worker.js");
+const { DevToolsWorker } = require("devtools/toolkit/shared/worker.js");
 
 const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
 
 loader.lazyGetter(this, "Debugger", () => {
   let Debugger = require("Debugger");
   hackDebugger(Debugger);
   return Debugger;
 });
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -176,17 +176,16 @@ html|button {
 /* xul buttons and menulists */
 
 *|button,
 xul|colorpicker[type="button"],
 xul|menulist {
   -moz-appearance: none;
   height: 30px;
   color: var(--in-content-text-color);
-  line-height: 20px;
   border: 1px solid var(--in-content-box-border-color);
   -moz-border-top-colors: none !important;
   -moz-border-right-colors: none !important;
   -moz-border-bottom-colors: none !important;
   -moz-border-left-colors: none !important;
   border-radius: 2px;
   background-color: var(--in-content-page-background);
 }