Bug 1432355 - Add a policy to disable private browsing. r?Gijs draft
authorMichael Kaply <mozilla@kaply.com>
Thu, 01 Mar 2018 10:20:38 -0600
changeset 761883 3769c5ff55c22328a656e0021c25fbcdfaf6b828
parent 761787 e33efdb3e1517d521deb949de3fcd6d9946ea440
push id101027
push usermozilla@kaply.com
push dateThu, 01 Mar 2018 16:20:55 +0000
reviewersGijs
bugs1432355
milestone60.0a1
Bug 1432355 - Add a policy to disable private browsing. r?Gijs MozReview-Commit-ID: AprbL6usEv5
browser/base/content/browser.js
browser/base/content/macBrowserOverlay.xul
browser/base/content/nsContextMenu.js
browser/components/customizableui/CustomizableWidgets.jsm
browser/components/enterprisepolicies/Policies.jsm
browser/components/enterprisepolicies/schemas/policies-schema.json
browser/components/enterprisepolicies/tests/browser/browser.ini
browser/components/enterprisepolicies/tests/browser/browser_policy_disable_privatebrowsing.js
browser/components/extensions/ext-windows.js
browser/components/nsBrowserContentHandler.js
browser/components/places/content/controller.js
browser/components/preferences/in-content/privacy.js
browser/components/syncedtabs/TabListView.js
browser/modules/WindowsJumpLists.jsm
toolkit/modules/PrivateBrowsingUtils.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1365,16 +1365,23 @@ var gBrowserInit = {
         Cu.reportError(e);
       }
     }
 
     // Wait until chrome is painted before executing code not critical to making the window visible
     this._boundDelayedStartup = this._delayedStartup.bind(this);
     window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
 
+    if (!PrivateBrowsingUtils.enabled) {
+      document.getElementById("Tools:PrivateBrowsing").hidden = true;
+      // Setting disabled doesn't disable the shortcut, so we just remove
+      // the keybinding.
+      document.getElementById("key_privatebrowsing").remove();
+    }
+
     this._loadHandled = true;
   },
 
   _cancelDelayedStartup() {
     window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
     this._boundDelayedStartup = null;
   },
 
@@ -1966,16 +1973,19 @@ if (AppConstants.platform == "macosx") {
         } catch (e) {
         }
       }
     }
 
     if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
       document.getElementById("macDockMenuNewWindow").hidden = true;
     }
+    if (!PrivateBrowsingUtils.enabled) {
+      document.getElementById("macDockMenuNewPrivateWindow").hidden = true;
+    }
 
     this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0);
   };
 
   gBrowserInit.nonBrowserWindowDelayedStartup = function() {
     this._delayedStartupTimeoutId = null;
 
     // initialise the offline listener
@@ -4134,17 +4144,17 @@ function OpenBrowserWindow(options) {
   TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj);
 
   var handler = Cc["@mozilla.org/browser/clh;1"]
                   .getService(Ci.nsIBrowserHandler);
   var defaultArgs = handler.defaultArgs;
   var wintype = document.documentElement.getAttribute("windowtype");
 
   var extraFeatures = "";
-  if (options && options.private) {
+  if (options && options.private && PrivateBrowsingUtils.enabled) {
     extraFeatures = ",private";
     if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
       // Force the new window to load about:privatebrowsing instead of the default home page
       defaultArgs = "about:privatebrowsing";
     }
   } else {
     extraFeatures = ",non-private";
   }
--- a/browser/base/content/macBrowserOverlay.xul
+++ b/browser/base/content/macBrowserOverlay.xul
@@ -53,13 +53,14 @@
 
 <!-- Dock menu -->
 <popupset>
   <menupopup id="menu_mac_dockmenu">
     <!-- The command cannot be cmd_newNavigator because we need to activate
          the application. -->
     <menuitem label="&newNavigatorCmd.label;" oncommand="OpenBrowserWindowFromDockMenu();"
               id="macDockMenuNewWindow" />
-    <menuitem label="&newPrivateWindow.label;" oncommand="OpenBrowserWindowFromDockMenu({private: true});" />
+    <menuitem label="&newPrivateWindow.label;" oncommand="OpenBrowserWindowFromDockMenu({private: true});"
+              id="macDockMenuNewPrivateWindow" />
   </menupopup>
 </popupset>
 
 </overlay>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -353,17 +353,17 @@ nsContextMenu.prototype = {
          gBrowserBundle.formatStringFromName("userContextOpenLink.label",
                                              [label], 1));
     }
 
     var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
     var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
     var showContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
     this.showItem("context-openlink", shouldShow && !isWindowPrivate);
-    this.showItem("context-openlinkprivate", shouldShow);
+    this.showItem("context-openlinkprivate", shouldShow && PrivateBrowsingUtils.enabled);
     this.showItem("context-openlinkintab", shouldShow && !inContainer);
     this.showItem("context-openlinkincontainertab", shouldShow && inContainer);
     this.showItem("context-openlinkinusercontext-menu", shouldShow && !isWindowPrivate && showContainers);
     this.showItem("context-openlinkincurrent", this.onPlainTextLink);
     this.showItem("context-sep-open", shouldShow);
   },
 
   initNavigationItems: function CM_initNavigationItems() {
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -234,23 +234,16 @@ const CustomizableWidgets = [
         } else {
           element.classList.add("subviewbutton-iconic", "bookmark-item");
         }
       }
       panelview.appendChild(body);
       panelview.appendChild(footer);
     }
   }, {
-    id: "privatebrowsing-button",
-    shortcutId: "key_privatebrowsing",
-    onCommand(e) {
-      let win = e.target.ownerGlobal;
-      win.OpenBrowserWindow({private: true});
-    }
-  }, {
     id: "save-page-button",
     shortcutId: "key_savePage",
     tooltiptext: "save-page-button.tooltiptext3",
     onCommand(aEvent) {
       let win = aEvent.target.ownerGlobal;
       win.saveBrowser(win.gBrowser.selectedBrowser);
     }
   }, {
@@ -943,8 +936,19 @@ if (Services.prefs.getBoolPref("privacy.
       forgetButton.addEventListener("command", this);
     },
     onViewHiding(aEvent) {
       let forgetButton = aEvent.target.querySelector("#PanelUI-panic-view-button");
       forgetButton.removeEventListener("command", this);
     },
   });
 }
+
+if (PrivateBrowsingUtils.enabled) {
+  CustomizableWidgets.push({
+    id: "privatebrowsing-button",
+    shortcutId: "key_privatebrowsing",
+    onCommand(e) {
+      let win = e.target.ownerGlobal;
+      win.OpenBrowserWindow({private: true});
+    }
+  });
+}
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -167,16 +167,27 @@ var Policies = {
   "DisablePocket": {
     onBeforeAddons(manager, param) {
       if (param) {
         setAndLockPref("extensions.pocket.enabled", false);
       }
     }
   },
 
+  "DisablePrivateBrowsing": {
+    onBeforeAddons(manager, param) {
+      if (param) {
+        manager.disallowFeature("privatebrowsing");
+        manager.disallowFeature("about:privatebrowsing", true);
+        setAndLockPref("browser.privatebrowsing.autostart", false);
+      }
+    }
+  },
+
+
   "DisplayBookmarksToolbar": {
     onBeforeUIStartup(manager, param) {
       if (param) {
         // This policy is meant to change the default behavior, not to force it.
         // If this policy was alreay applied and the user chose to re-hide the
         // bookmarks toolbar, do not show it again.
         runOnce("displayBookmarksToolbar", () => {
           gXulStore.setValue(BROWSER_DOCUMENT_URL, "PersonalToolbar", "collapsed", "false");
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -148,16 +148,24 @@
     "DisablePocket": {
       "description": "Prevents ability to save webpages to Pocket.",
       "first_available": "60.0",
 
       "type": "boolean",
       "enum": [true]
     },
 
+    "DisablePrivateBrowsing": {
+      "description": "Disables private browsing.",
+      "first_available": "60.0",
+
+      "type": "boolean",
+      "enum": [true]
+    },
+
     "DisplayBookmarksToolbar": {
       "description": "Causes the bookmarks toolbar to be displayed by default.",
       "first_available": "60.0",
 
       "type": "boolean",
       "enum": [true]
     },
 
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -20,12 +20,13 @@ support-files =
 [browser_policy_block_about_support.js]
 [browser_policy_block_set_desktop_background.js]
 [browser_policy_bookmarks.js]
 [browser_policy_default_browser_check.js]
 [browser_policy_disable_formhistory.js]
 [browser_policy_disable_fxscreenshots.js]
 [browser_policy_disable_masterpassword.js]
 [browser_policy_disable_pocket.js]
+[browser_policy_disable_privatebrowsing.js]
 [browser_policy_disable_shield.js]
 [browser_policy_display_bookmarks.js]
 [browser_policy_display_menu.js]
 [browser_policy_remember_passwords.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_privatebrowsing.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+add_task(async function setup() {
+  await setupPolicyEngineWithJson({
+    "policies": {
+      "DisablePrivateBrowsing": true
+    }
+  });
+});
+
+add_task(async function test_menu_shown() {
+  is(PrivateBrowsingUtils.enabled, false, "Private browsing should be disabled");
+  let newWin = await BrowserTestUtils.openNewBrowserWindow();
+  let privateBrowsingCommand = newWin.document.getElementById("Tools:PrivateBrowsing");
+  is(privateBrowsingCommand.hidden, true, "The private browsing command should be hidden");
+  await BrowserTestUtils.closeWindow(newWin);
+});
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -123,16 +123,19 @@ this.windows = class extends ExtensionAP
             let tab = tabTracker.getTab(createData.tabId);
 
             // Private browsing tabs can only be moved to private browsing
             // windows.
             let incognito = PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser);
             if (createData.incognito !== null && createData.incognito != incognito) {
               return Promise.reject({message: "`incognito` property must match the incognito state of tab"});
             }
+            if (createData.incognito && !PrivateBrowsingUtils.enabled) {
+              return Promise.reject({message: "`incognito` cannot be used if incognito mode is disabled"});
+            }
             createData.incognito = incognito;
 
             args.appendElement(tab);
           } else if (createData.url !== null) {
             if (Array.isArray(createData.url)) {
               let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
               for (let url of createData.url) {
                 array.appendElement(mkstr(url));
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -376,17 +376,17 @@ nsBrowserContentHandler.prototype = {
           let localSchemes = new Set(["chrome", "file", "resource"]);
           if (uri instanceof Ci.nsINestedURI) {
             uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
           }
           return localSchemes.has(uri.scheme);
         };
         if (isLocal(resolvedURI)) {
           // If the URI is local, we are sure it won't wrongly inherit chrome privs
-          var features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
+          let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
           openWindow(null, resolvedURI.spec, "_blank", features);
           cmdLine.preventDefault = true;
         } else {
           dump("*** Preventing load of web URI as chrome\n");
           dump("    If you're trying to load a webpage, do not pass --chrome.\n");
         }
       } catch (e) {
         Cu.reportError(e);
@@ -397,43 +397,55 @@ nsBrowserContentHandler.prototype = {
       cmdLine.preventDefault = true;
     }
     if (cmdLine.handleFlag("silent", false))
       cmdLine.preventDefault = true;
 
     try {
       var privateWindowParam = cmdLine.handleFlagWithParam("private-window", false);
       if (privateWindowParam) {
-        let resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
-        handURIToExistingBrowser(resolvedURI, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, true,
+        let forcePrivate = true;
+        let resolvedURI;
+        if (!PrivateBrowsingUtils.enabled) {
+          // Load about:privatebrowsing in a normal tab, which will display an error indicating
+          // access to private browsing has been disabled.
+          forcePrivate = false;
+          resolvedURI = Services.io.newURI("about:privatebrowsing");
+        } else {
+          resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
+        }
+        handURIToExistingBrowser(resolvedURI, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, forcePrivate,
                                  Services.scriptSecurityManager.getSystemPrincipal());
-        cmdLine.preventDefault = true;
       }
     } catch (e) {
       if (e.result != Cr.NS_ERROR_INVALID_ARG) {
         throw e;
       }
       // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
       if (cmdLine.handleFlag("private-window", false)) {
+        let features = "chrome,dialog=no,all";
+        if (PrivateBrowsingUtils.enabled) {
+          features += ",private";
+        }
         openWindow(null, this.chromeURL, "_blank",
-          "chrome,dialog=no,private,all" + this.getFeatures(cmdLine),
+          features + this.getFeatures(cmdLine),
           "about:privatebrowsing");
         cmdLine.preventDefault = true;
       }
     }
 
     var searchParam = cmdLine.handleFlagWithParam("search", false);
     if (searchParam) {
       doSearch(searchParam, cmdLine);
       cmdLine.preventDefault = true;
     }
 
     // The global PB Service consumes this flag, so only eat it in per-window
     // PB builds.
-    if (cmdLine.handleFlag("private", false)) {
+    if (cmdLine.handleFlag("private", false) && PrivateBrowsingUtils.enabled) {
       PrivateBrowsingUtils.enterTemporaryAutoStartMode();
     }
 
     var fileParam = cmdLine.handleFlagWithParam("file", false);
     if (fileParam) {
       var file = cmdLine.resolveFile(fileParam);
       var fileURI = Services.io.newFileURI(file);
       openWindow(null, this.chromeURL, "_blank",
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -467,16 +467,20 @@ PlacesController.prototype = {
    * @param   aMenuItem
    *          the context menu item
    * @param   aMetaData
    *          meta data about the selection
    * @return true if the conditions (see buildContextMenu) are satisfied
    *         and the item can be displayed, false otherwise.
    */
   _shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
+    if (aMenuItem.hasAttribute("hideifprivatebrowsing") && !PrivateBrowsingUtils.enabled) {
+      return false;
+    }
+
     var selectiontype = aMenuItem.getAttribute("selectiontype");
     if (!selectiontype) {
       selectiontype = "single|multiple";
     }
     var selectionTypes = selectiontype.split("|");
     if (selectionTypes.includes("any")) {
       return true;
     }
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -454,16 +454,21 @@ var gPrivacyPane = {
       pkiBundle.getString("enable_fips"),
     ]);
     appendSearchKeywords("siteDataSettings", [
       bundlePrefs.getString("siteDataSettings2.description"),
       bundlePrefs.getString("removeAllCookies.label"),
       bundlePrefs.getString("removeSelectedCookies.label"),
     ]);
 
+    if (!PrivateBrowsingUtils.enabled) {
+      document.getElementById("privateBrowsingAutoStart").hidden = true;
+      document.querySelector("menuitem[value='dontremember']").hidden = true;
+    }
+
     // Notify observers that the UI is now ready
     Services.obs.notifyObservers(window, "privacy-pane-loaded");
   },
 
   // TRACKING PROTECTION MODE
 
   /**
    * Selects the right item of the Tracking Protection radiogroup.
--- a/browser/components/syncedtabs/TabListView.js
+++ b/browser/components/syncedtabs/TabListView.js
@@ -1,16 +1,19 @@
 /* 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/. */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
+ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
+                               "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
 let { getChromeWindow } = ChromeUtils.import("resource:///modules/syncedtabs/util.js", {});
 
 let log = ChromeUtils.import("resource://gre/modules/Log.jsm", {})
             .Log.repository.getLogger("Sync.RemoteTabs");
 
 var EXPORTED_SYMBOLS = [
   "TabListView"
 ];
@@ -515,18 +518,20 @@ TabListView.prototype = {
     let item = this.container.querySelector(".item.selected");
     let showTabOptions = this._isTab(item);
 
     let el = menu.firstChild;
 
     while (el) {
       let show = false;
       if (showTabOptions) {
-        if (el.getAttribute("id") != "syncedTabsOpenAllInTabs" &&
-            el.getAttribute("id") != "syncedTabsManageDevices") {
+        if (el.getAttribute("id") == "syncedTabsOpenSelectedInPrivateWindow") {
+          show = PrivateBrowsingUtils.enabled;
+        } else if (el.getAttribute("id") != "syncedTabsOpenAllInTabs" &&
+                   el.getAttribute("id") != "syncedTabsManageDevices") {
           show = true;
         }
       } else if (el.getAttribute("id") == "syncedTabsOpenAllInTabs") {
         const tabs = item.querySelectorAll(".item-tabs-list > .item.tab");
         show = tabs.length > 0;
       } else if (el.getAttribute("id") == "syncedTabsRefresh") {
         show = true;
       } else if (el.getAttribute("id") == "syncedTabsManageDevices") {
--- a/browser/modules/WindowsJumpLists.jsm
+++ b/browser/modules/WindowsJumpLists.jsm
@@ -96,28 +96,28 @@ var tasksCfg = [
     get title() { return _getString("taskbar.tasks.newWindow.label"); },
     get description() { return _getString("taskbar.tasks.newWindow.description"); },
     args:             "-browser",
     iconIndex:        2, // New tab icon
     open:             true,
     close:            true, // No point, but we don't always update the list on
                             // shutdown. Thus true for consistency.
   },
+];
 
-  // Open new private window
-  {
-    get title() { return _getString("taskbar.tasks.newPrivateWindow.label"); },
-    get description() { return _getString("taskbar.tasks.newPrivateWindow.description"); },
-    args:             "-private-window",
-    iconIndex:        4, // Private browsing mode icon
-    open:             true,
-    close:            true, // No point, but we don't always update the list on
-                            // shutdown. Thus true for consistency.
-  },
-];
+// Open new private window
+let privateWindowTask = {
+  get title() { return _getString("taskbar.tasks.newPrivateWindow.label"); },
+  get description() { return _getString("taskbar.tasks.newPrivateWindow.description"); },
+  args:             "-private-window",
+  iconIndex:        4, // Private browsing mode icon
+  open:             true,
+  close:            true, // No point, but we don't always update the list on
+                          // shutdown. Thus true for consistency.
+};
 
 // Implementation
 
 var WinTaskbarJumpList =
 {
   _builder: null,
   _tasks: null,
   _shuttingDown: false,
@@ -129,16 +129,20 @@ var WinTaskbarJumpList =
   startup: function WTBJL_startup() {
     // exit if this isn't win7 or higher.
     if (!this._initTaskbar())
       return;
 
     // Store our task list config data
     this._tasks = tasksCfg;
 
+    if (PrivateBrowsingUtils.enabled) {
+      tasksCfg.push(privateWindowTask);
+    }
+
     // retrieve taskbar related prefs.
     this._refreshPrefs();
 
     // observer for private browsing and our prefs branch
     this._initObs();
 
     // jump list refresh timer
     this._updateTimer();
--- a/toolkit/modules/PrivateBrowsingUtils.jsm
+++ b/toolkit/modules/PrivateBrowsingUtils.jsm
@@ -8,16 +8,20 @@ ChromeUtils.import("resource://gre/modul
 
 const kAutoStartPref = "browser.privatebrowsing.autostart";
 
 // This will be set to true when the PB mode is autostarted from the command
 // line for the current session.
 var gTemporaryAutoStartMode = false;
 
 var PrivateBrowsingUtils = {
+  get enabled() {
+    return Services.policies.isAllowed("privatebrowsing");
+  },
+
   // Rather than passing content windows to this function, please use
   // isBrowserPrivate since it works with e10s.
   isWindowPrivate: function pbu_isWindowPrivate(aWindow) {
     if (!aWindow.isChromeWindow) {
       dump("WARNING: content window passed to PrivateBrowsingUtils.isWindowPrivate. " +
            "Use isContentWindowPrivate instead (but only for frame scripts).\n"
            + new Error().stack);
     }