merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 15 Sep 2014 14:29:01 +0200
changeset 205307 56cba2986c61ce1a8314f46fd55655fb16fce9ff
parent 205301 77127f875559cc1e91753090d07fb3053c2d3ea2 (current diff)
parent 205306 454e5c070bc14f40a26b079c85e89e5c3d76e20f (diff)
child 205308 d08c31df0c1a19146d84bd627acb87ca8caf942b
child 205334 ff1d0d05957461b85a8e931a281cb9e37d1048b4
child 205364 cb23604875d1d9192a3df9b834b53d7903daf59e
child 205383 a404daa686f8b719a040c6281280e5b6bd6697c0
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmerge
milestone35.0a1
merge fx-team to mozilla-central a=merge
browser/app/profile/firefox.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1402,17 +1402,16 @@ pref("devtools.tilt.outro_transition", t
 //                  stored. Setting this preference to 0 will not
 //                  clear any recent files, but rather hide the
 //                  'Open Recent'-menu.
 // - showTrailingSpace: Whether to highlight trailing space or not.
 // - enableCodeFolding: Whether to enable code folding or not.
 // - enableAutocompletion: Whether to enable JavaScript autocompletion.
 pref("devtools.scratchpad.recentFilesMax", 10);
 pref("devtools.scratchpad.showTrailingSpace", false);
-pref("devtools.scratchpad.enableCodeFolding", true);
 pref("devtools.scratchpad.enableAutocompletion", true);
 
 // Enable the Storage Inspector
 pref("devtools.storage.enabled", false);
 
 // Enable the Style Editor.
 pref("devtools.styleeditor.enabled", true);
 pref("devtools.styleeditor.source-maps-enabled", false);
@@ -1508,16 +1507,17 @@ pref("devtools.eyedropper.zoom", 6);
 // - keymap: which keymap to use (can be 'default', 'emacs' or 'vim')
 // - autoclosebrackets: whether to permit automatic bracket/quote closing.
 // - detectindentation: whether to detect the indentation from the file
 pref("devtools.editor.tabsize", 2);
 pref("devtools.editor.expandtab", true);
 pref("devtools.editor.keymap", "default");
 pref("devtools.editor.autoclosebrackets", true);
 pref("devtools.editor.detectindentation", true);
+pref("devtools.editor.enableCodeFolding", true);
 pref("devtools.editor.autocomplete", true);
 
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Pref to store the browser version at the time of a telemetry ping for an
 // opened developer tool. This allows us to ping telemetry just once per browser
 // version for each user.
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -228,17 +228,18 @@ let DebuggerView = {
 
     this.editor = new Editor({
       mode: Editor.modes.text,
       readOnly: true,
       lineNumbers: true,
       showAnnotationRuler: true,
       gutters: gutters,
       extraKeys: extraKeys,
-      contextMenu: "sourceEditorContextMenu"
+      contextMenu: "sourceEditorContextMenu",
+      enableCodeFolding: false
     });
 
     this.editor.appendTo(document.getElementById("editor")).then(() => {
       this.editor.extend(DebuggerEditor);
       this._loadingText = L10N.getStr("loadingText");
       this._onEditorLoad(aCallback);
     });
 
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -29,18 +29,18 @@ const EVAL_FUNCTION_TIMEOUT      = 1000;
 const MAXIMUM_FONT_SIZE = 96;
 const MINIMUM_FONT_SIZE = 6;
 const NORMAL_FONT_SIZE = 12;
 
 const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
 const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
 const SHOW_TRAILING_SPACE = "devtools.scratchpad.showTrailingSpace";
-const ENABLE_CODE_FOLDING = "devtools.scratchpad.enableCodeFolding";
 const ENABLE_AUTOCOMPLETION = "devtools.scratchpad.enableAutocompletion";
+const TAB_SIZE = "devtools.editor.tabsize";
 
 const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
 
 const require   = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 
 const Telemetry = require("devtools/shared/telemetry");
 const Editor    = require("devtools/sourceeditor/editor");
 const TargetFactory = require("devtools/framework/target").TargetFactory;
@@ -650,17 +650,17 @@ var Scratchpad = {
    * Pretty print the source text inside the scratchpad.
    *
    * @return Promise
    *         A promise resolved with the pretty printed code, or rejected with
    *         an error.
    */
   prettyPrint: function SP_prettyPrint() {
     const uglyText = this.getText();
-    const tabsize = Services.prefs.getIntPref("devtools.editor.tabsize");
+    const tabsize = Services.prefs.getIntPref(TAB_SIZE);
     const id = Math.random();
     const deferred = promise.defer();
 
     const onReply = ({ data }) => {
       if (data.id !== id) {
         return;
       }
       this.prettyPrintWorker.removeEventListener("message", onReply, false);
@@ -1599,17 +1599,16 @@ var Scratchpad = {
     }
 
     let config = {
       mode: Editor.modes.js,
       value: initialText,
       lineNumbers: true,
       contextMenu: "scratchpad-text-popup",
       showTrailingSpace: Services.prefs.getBoolPref(SHOW_TRAILING_SPACE),
-      enableCodeFolding: Services.prefs.getBoolPref(ENABLE_CODE_FOLDING),
       autocomplete: Services.prefs.getBoolPref(ENABLE_AUTOCOMPLETION),
     };
 
     this.editor = new Editor(config);
     let editorElement = document.querySelector("#scratchpad-editor");
     this.editor.appendTo(editorElement).then(() => {
       var lines = initialText.split("\n");
 
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -4,16 +4,17 @@
  * 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";
 
 const { Cu, Cc, Ci, components } = require("chrome");
 
 const TAB_SIZE    = "devtools.editor.tabsize";
+const ENABLE_CODE_FOLDING = "devtools.editor.enableCodeFolding";
 const EXPAND_TAB  = "devtools.editor.expandtab";
 const KEYMAP      = "devtools.editor.keymap";
 const AUTO_CLOSE  = "devtools.editor.autoclosebrackets";
 const AUTOCOMPLETE  = "devtools.editor.autocomplete";
 const DETECT_INDENT = "devtools.editor.detectindentation";
 const DETECT_INDENT_MAX_LINES = 500;
 const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
 const XUL_NS      = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@@ -183,24 +184,22 @@ function Editor(config) {
     if (!config.extraKeys)
       return;
 
     Object.keys(config.extraKeys).forEach((key) => {
       this.config.extraKeys[key] = config.extraKeys[key];
     });
   });
 
-  // Set the code folding gutter, if needed.
-  if (this.config.enableCodeFolding) {
-    this.config.foldGutter = true;
-
-    if (!this.config.gutters) {
-      this.config.gutters = this.config.lineNumbers ? ["CodeMirror-linenumbers"] : [];
-      this.config.gutters.push("CodeMirror-foldgutter");
-    }
+  if (!this.config.gutters) {
+    this.config.gutters = [];
+  }
+  if (this.config.lineNumbers
+      && this.config.gutters.indexOf("CodeMirror-linenumbers") === -1) {
+    this.config.gutters.push("CodeMirror-linenumbers");
   }
 
   // Remember the initial value of autoCloseBrackets.
   this.config.autoCloseBracketsSaved = this.config.autoCloseBrackets;
 
   // Overwrite default tab behavior. If something is selected,
   // indent those lines. If nothing is selected and we're
   // indenting with tabs, insert one tab. Otherwise insert N
@@ -328,16 +327,17 @@ Editor.prototype = {
       this.reloadPreferences = this.reloadPreferences.bind(this);
       this._prefObserver = new PrefObserver("devtools.editor.");
       this._prefObserver.on(TAB_SIZE, this.reloadPreferences);
       this._prefObserver.on(EXPAND_TAB, this.reloadPreferences);
       this._prefObserver.on(KEYMAP, this.reloadPreferences);
       this._prefObserver.on(AUTO_CLOSE, this.reloadPreferences);
       this._prefObserver.on(AUTOCOMPLETE, this.reloadPreferences);
       this._prefObserver.on(DETECT_INDENT, this.reloadPreferences);
+      this._prefObserver.on(ENABLE_CODE_FOLDING, this.reloadPreferences);
 
       this.reloadPreferences();
       def.resolve();
     };
 
     env.addEventListener("load", onLoad, true);
     env.setAttribute("src", CM_IFRAME);
     el.appendChild(env);
@@ -425,16 +425,17 @@ Editor.prototype = {
       useAutoClose ? this.config.autoCloseBracketsSaved : false);
 
     // If alternative keymap is provided, use it.
     const keyMap = Services.prefs.getCharPref(KEYMAP);
     if (VALID_KEYMAPS.has(keyMap))
       this.setOption("keyMap", keyMap)
     else
       this.setOption("keyMap", "default");
+    this.updateCodeFoldingGutter();
 
     this.resetIndentUnit();
     this.setupAutoCompletion();
   },
 
   /**
    * Set the editor's indentation based on the current prefs and
    * re-detect indentation if we should.
@@ -950,16 +951,22 @@ Editor.prototype = {
     }
 
     if (o === "autocomplete") {
       this.config.autocomplete = v;
       this.setupAutoCompletion();
     } else {
       cm.setOption(o, v);
     }
+
+    if (o === "enableCodeFolding") {
+      // The new value maybe explicitly force foldGUtter on or off, ignoring
+      // the prefs service.
+      this.updateCodeFoldingGutter();
+    }
   },
 
   /**
    * Gets an option for the editor.  For most options it just defers to
    * CodeMirror.getOption, but certain ones are maintained within the editor
    * instance.
    */
   getOption: function(o) {
@@ -1031,20 +1038,56 @@ Editor.prototype = {
 
     if (this._prefObserver) {
       this._prefObserver.off(TAB_SIZE, this.reloadPreferences);
       this._prefObserver.off(EXPAND_TAB, this.reloadPreferences);
       this._prefObserver.off(KEYMAP, this.reloadPreferences);
       this._prefObserver.off(AUTO_CLOSE, this.reloadPreferences);
       this._prefObserver.off(AUTOCOMPLETE, this.reloadPreferences);
       this._prefObserver.off(DETECT_INDENT, this.reloadPreferences);
+      this._prefObserver.off(ENABLE_CODE_FOLDING, this.reloadPreferences);
       this._prefObserver.destroy();
     }
 
     this.emit("destroy");
+  },
+
+  updateCodeFoldingGutter: function () {
+    let shouldFoldGutter = this.config.enableCodeFolding,
+        foldGutterIndex = this.config.gutters.indexOf("CodeMirror-foldgutter"),
+        cm = editors.get(this);
+
+    if (shouldFoldGutter === undefined) {
+      shouldFoldGutter = Services.prefs.getBoolPref(ENABLE_CODE_FOLDING);
+    }
+
+    if (shouldFoldGutter) {
+      // Add the gutter before enabling foldGutter
+      if (foldGutterIndex === -1) {
+        let gutters = this.config.gutters.slice();
+        gutters.push("CodeMirror-foldgutter");
+        this.setOption("gutters", gutters);
+      }
+
+      this.setOption("foldGutter", true);
+    } else {
+      // No code should remain folded when folding is off.
+      if (cm) {
+        cm.execCommand("unfoldAll");
+      }
+
+      // Remove the gutter so it doesn't take up space
+      if (foldGutterIndex !== -1) {
+        let gutters = this.config.gutters.slice();
+        gutters.splice(foldGutterIndex, 1);
+        this.setOption("gutters", gutters);
+      }
+
+      this.setOption("foldGutter", false);
+    }
   }
 };
 
 // Since Editor is a thin layer over CodeMirror some methods
 // are mapped directly—without any changes.
 
 CM_MAPPING.forEach(function (name) {
   Editor.prototype[name] = function (...args) {
--- a/browser/devtools/sourceeditor/test/browser_editor_prefs.js
+++ b/browser/devtools/sourceeditor/test/browser_editor_prefs.js
@@ -2,63 +2,103 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test to make sure that the editor reacts to preference changes
 
 const TAB_SIZE    = "devtools.editor.tabsize";
+const ENABLE_CODE_FOLDING = "devtools.editor.enableCodeFolding";
 const EXPAND_TAB  = "devtools.editor.expandtab";
 const KEYMAP      = "devtools.editor.keymap";
 const AUTO_CLOSE  = "devtools.editor.autoclosebrackets";
 const AUTOCOMPLETE  = "devtools.editor.autocomplete";
 const DETECT_INDENT = "devtools.editor.detectindentation";
 
 function test() {
   waitForExplicitFinish();
   setup((ed, win) => {
 
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints",
+      "CodeMirror-foldgutter"], "gutters is correct");
+
     ed.setText("Checking preferences.");
 
     info ("Turning prefs off");
 
     ed.setOption("autocomplete", true);
 
     Services.prefs.setIntPref(TAB_SIZE, 2);
+    Services.prefs.setBoolPref(ENABLE_CODE_FOLDING, false);
     Services.prefs.setBoolPref(EXPAND_TAB, false);
     Services.prefs.setCharPref(KEYMAP, "default");
     Services.prefs.setBoolPref(AUTO_CLOSE, false);
     Services.prefs.setBoolPref(AUTOCOMPLETE, false);
     Services.prefs.setBoolPref(DETECT_INDENT, false);
 
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints"], "gutters is correct");
+
     is(ed.getOption("tabSize"), 2, "tabSize is correct");
     is(ed.getOption("indentUnit"), 2, "indentUnit is correct");
+    is(ed.getOption("foldGutter"), false, "foldGutter is correct");
+    is(ed.getOption("enableCodeFolding"), undefined, "enableCodeFolding is correct");
     is(ed.getOption("indentWithTabs"), true, "indentWithTabs is correct");
     is(ed.getOption("keyMap"), "default", "keyMap is correct");
     is(ed.getOption("autoCloseBrackets"), "", "autoCloseBrackets is correct");
     is(ed.getOption("autocomplete"), true, "autocomplete is correct");
     ok(!ed.isAutocompletionEnabled(), "Autocompletion is not enabled");
 
     info ("Turning prefs on");
 
     Services.prefs.setIntPref(TAB_SIZE, 4);
+    Services.prefs.setBoolPref(ENABLE_CODE_FOLDING, true);
     Services.prefs.setBoolPref(EXPAND_TAB, true);
     Services.prefs.setCharPref(KEYMAP, "sublime");
     Services.prefs.setBoolPref(AUTO_CLOSE, true);
     Services.prefs.setBoolPref(AUTOCOMPLETE, true);
 
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints",
+      "CodeMirror-foldgutter"], "gutters is correct");
+
     is(ed.getOption("tabSize"), 4, "tabSize is correct");
     is(ed.getOption("indentUnit"), 4, "indentUnit is correct");
+    is(ed.getOption("foldGutter"), true, "foldGutter is correct");
+    is(ed.getOption("enableCodeFolding"), undefined, "enableCodeFolding is correct");
     is(ed.getOption("indentWithTabs"), false, "indentWithTabs is correct");
     is(ed.getOption("keyMap"), "sublime", "keyMap is correct");
     is(ed.getOption("autoCloseBrackets"), "()[]{}''\"\"", "autoCloseBrackets is correct");
     is(ed.getOption("autocomplete"), true, "autocomplete is correct");
     ok(ed.isAutocompletionEnabled(), "Autocompletion is enabled");
 
+    info ("Forcing foldGutter off using enableCodeFolding");
+    ed.setOption("enableCodeFolding", false);
+
+    is(ed.getOption("foldGutter"), false, "foldGutter is correct");
+    is(ed.getOption("enableCodeFolding"), false, "enableCodeFolding is correct");
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints"], "gutters is correct");
+
+    info ("Forcing foldGutter on using enableCodeFolding");
+    ed.setOption("enableCodeFolding", true);
+
+    is(ed.getOption("foldGutter"), true, "foldGutter is correct");
+    is(ed.getOption("enableCodeFolding"), true, "enableCodeFolding is correct");
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints",
+      "CodeMirror-foldgutter"], "gutters is correct");
+
     info ("Checking indentation detection");
 
     Services.prefs.setBoolPref(DETECT_INDENT, true);
 
     ed.setText("Detecting\n\tTabs");
     is(ed.getOption("indentWithTabs"), true, "indentWithTabs is correct");
     is(ed.getOption("indentUnit"), 4, "indentUnit is correct");
 
--- a/browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js
@@ -30,19 +30,19 @@ let test = asyncTest(function*() {
       this.isShown = false;
     }
   };
 
   info("Expecting a node-highlighted event");
   let onHighlighted = editor.once("node-highlighted");
 
   info("Simulate a mousemove event on the div selector");
-  editor._onMouseMove({clientX: 40, clientY: 10});
+  editor._onMouseMove({clientX: 56, clientY: 10});
   yield onHighlighted;
 
   ok(editor.highlighter.isShown, "The highlighter is now shown");
   is(editor.highlighter.options.selector, "div", "The selector is correct");
 
   info("Simulate a mousemove event elsewhere in the editor");
-  editor._onMouseMove({clientX: 0, clientY: 0});
+  editor._onMouseMove({clientX: 16, clientY: 0});
 
   ok(!editor.highlighter.isShown, "The highlighter is now hidden");
 });
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -491,17 +491,17 @@ let UI = {
     if (!(project.type == "runtimeApp" ||
           project.type == "mainProcess" ||
           project.type == "tab")) {
       return; // For something that is not an editable app, we're done.
     }
 
     Task.spawn(function() {
       if (project.type == "runtimeApp") {
-        yield UI.busyUntil(AppManager.runRuntimeApp(), "running app");
+        yield UI.busyUntil(AppManager.launchRuntimeApp(), "running app");
       }
       yield UI.createToolbox();
     });
   },
 
   /********** DECK **********/
 
   setupDeck: function() {
@@ -1009,17 +1009,17 @@ let Cmds = {
 
   play: function() {
     switch(AppManager.selectedProject.type) {
       case "packaged":
         return UI.busyWithProgressUntil(AppManager.installAndRunProject(), "installing and running app");
       case "hosted":
         return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
       case "runtimeApp":
-        return UI.busyUntil(AppManager.runRuntimeApp(), "running app");
+        return UI.busyUntil(AppManager.launchOrReloadRuntimeApp(), "launching / reloading app");
       case "tab":
         return UI.busyUntil(AppManager.reloadTab(), "reloading tab");
     }
     return promise.reject();
   },
 
   stop: function() {
     return UI.busyUntil(AppManager.stopRunningApp(), "stopping app");
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -321,17 +321,19 @@ exports.AppManager = AppManager = {
       manifest = "app://" + project.packagedAppOrigin + "/manifest.webapp";
     }
 
     return manifest;
   },
 
   _selectedProject: null,
   set selectedProject(value) {
-    if (value != this.selectedProject) {
+    // A regular comparison still sees a difference when equal in some cases
+    if (JSON.stringify(this._selectedProject) !==
+        JSON.stringify(value)) {
       this._selectedProject = value;
 
       // Clear out tab store's selected state, if any
       this.tabStore.selectedTab = null;
 
       if (this.selectedProject) {
         if (this.selectedProject.type == "packaged" ||
             this.selectedProject.type == "hosted") {
@@ -434,19 +436,29 @@ exports.AppManager = AppManager = {
       return promise.resolve();
     }
     let deferred = promise.defer();
     this.connection.once(Connection.Events.DISCONNECTED, () => deferred.resolve());
     this.connection.disconnect();
     return deferred.promise;
   },
 
-  runRuntimeApp: function() {
+  launchRuntimeApp: function() {
     if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
-      return promise.reject("attempting to run a non-runtime app");
+      return promise.reject("attempting to launch a non-runtime app");
+    }
+    let client = this.connection.client;
+    let actor = this._listTabsResponse.webappsActor;
+    let manifest = this.getProjectManifestURL(this.selectedProject);
+    return AppActorFront.launchApp(client, actor, manifest);
+  },
+
+  launchOrReloadRuntimeApp: function() {
+    if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
+      return promise.reject("attempting to launch / reload a non-runtime app");
     }
     let client = this.connection.client;
     let actor = this._listTabsResponse.webappsActor;
     let manifest = this.getProjectManifestURL(this.selectedProject);
     if (!this.isProjectRunning()) {
       return AppActorFront.launchApp(client, actor, manifest);
     } else {
       return AppActorFront.reloadApp(client, actor, manifest);
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -298,33 +298,34 @@ class Automation(object):
   def setupPermissionsDatabase(self, profileDir, permissions):
     # Included for reftest compatibility;
     # see https://bugzilla.mozilla.org/show_bug.cgi?id=688667
 
     # Open database and create table
     permDB = sqlite3.connect(os.path.join(profileDir, "permissions.sqlite"))
     cursor = permDB.cursor();
 
-    cursor.execute("PRAGMA user_version=3");
+    cursor.execute("PRAGMA user_version=4");
 
     # SQL copied from nsPermissionManager.cpp
     cursor.execute("""CREATE TABLE IF NOT EXISTS moz_hosts (
       id INTEGER PRIMARY KEY,
       host TEXT,
       type TEXT,
       permission INTEGER,
       expireType INTEGER,
       expireTime INTEGER,
+      modificationTime INTEGER,
       appId INTEGER,
       isInBrowserElement INTEGER)""")
 
     # Insert desired permissions
     for perm in permissions.keys():
       for host,allow in permissions[perm]:
-        cursor.execute("INSERT INTO moz_hosts values(NULL, ?, ?, ?, 0, 0, 0, 0)",
+        cursor.execute("INSERT INTO moz_hosts values(NULL, ?, ?, ?, 0, 0, 0, 0, 0)",
                        (host, perm, 1 if allow else 2))
 
     # Commit and close
     permDB.commit()
     cursor.close()
 
   def initializeProfile(self, profileDir,
                               extraPrefs=None,
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1709,22 +1709,26 @@ ContentChild::RecvAddPermission(const IP
     MOZ_ASSERT(secMan);
 
     nsCOMPtr<nsIPrincipal> principal;
     nsresult rv = secMan->GetAppCodebasePrincipal(uri, permission.appId,
                                                 permission.isInBrowserElement,
                                                 getter_AddRefs(principal));
     NS_ENSURE_SUCCESS(rv, true);
 
+    // child processes don't care about modification time.
+    int64_t modificationTime = 0;
+
     permissionManager->AddInternal(principal,
                                    nsCString(permission.type),
                                    permission.capability,
                                    0,
                                    permission.expireType,
                                    permission.expireTime,
+                                   modificationTime,
                                    nsPermissionManager::eNotify,
                                    nsPermissionManager::eNoDBOperation);
 #endif
 
     return true;
 }
 
 bool
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -352,17 +352,17 @@ nsPermissionManager::AppClearDataObserve
   nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   observerService->AddObserver(new AppClearDataObserver(), "webapps-clear-data", /* holdsWeak= */ false);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsPermissionManager Implementation
 
 static const char kPermissionsFileName[] = "permissions.sqlite";
-#define HOSTS_SCHEMA_VERSION 3
+#define HOSTS_SCHEMA_VERSION 4
 
 static const char kHostpermFileName[] = "hostperm.1";
 
 // Default permissions are read from a URL - this is the preference we read
 // to find that URL.
 static const char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
 // If the pref above doesn't exist, the URL we use by default.
 static const char kDefaultsUrl[] = "resource://app/chrome/browser/default_permissions";
@@ -427,18 +427,22 @@ nsPermissionManager::Init()
 
     for (uint32_t i = 0; i < perms.Length(); i++) {
       const IPC::Permission &perm = perms[i];
 
       nsCOMPtr<nsIPrincipal> principal;
       rv = GetPrincipal(perm.host, perm.appId, perm.isInBrowserElement, getter_AddRefs(principal));
       NS_ENSURE_SUCCESS(rv, rv);
 
+      // The child process doesn't care about modification times - it neither
+      // reads nor writes, nor removes them based on the date - so 0 (which
+      // will end up as now()) is fine.
+      uint64_t modificationTime = 0;
       AddInternal(principal, perm.type, perm.capability, 0, perm.expireType,
-                  perm.expireTime, eNotify, eNoDBOperation);
+                  perm.expireTime, modificationTime, eNotify, eNoDBOperation);
     }
 
     // Stop here; we don't need the DB in the child process
     return NS_OK;
   }
 
   // ignore failure here, since it's non-fatal (we can run fine without
   // persistent storage - e.g. if there's no profile).
@@ -541,32 +545,49 @@ nsPermissionManager::InitDB(bool aRemove
         NS_ENSURE_SUCCESS(rv, rv);
 
         rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       // fall through to the next upgrade
 
+    // Version 3->4 is the creation of the modificationTime field.
+    case 3:
+      {
+        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+              "ALTER TABLE moz_hosts ADD modificationTime INTEGER"));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // We leave the modificationTime at zero for all existing records; using
+        // now() would mean, eg, that doing "remove all from the last hour"
+        // within the first hour after migration would remove all permissions.
+
+        rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // fall through to the next upgrade
+
     // current version.
     case HOSTS_SCHEMA_VERSION:
       break;
 
     // downgrading.
     // if columns have been added to the table, we can still use the ones we
     // understand safely. if columns have been deleted or altered, just
     // blow away the table and start from scratch! if you change the way
     // a column is interpreted, make sure you also change its name so this
     // check will catch it.
     default:
       {
         // check if all the expected columns exist
         nsCOMPtr<mozIStorageStatement> stmt;
         rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-          "SELECT host, type, permission, expireType, expireTime, appId, isInBrowserElement FROM moz_hosts"),
+          "SELECT host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement FROM moz_hosts"),
           getter_AddRefs(stmt));
         if (NS_SUCCEEDED(rv))
           break;
 
         // our columns aren't there - drop the table!
         rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
         NS_ENSURE_SUCCESS(rv, rv);
 
@@ -578,28 +599,28 @@ nsPermissionManager::InitDB(bool aRemove
   }
 
   // make operations on the table asynchronous, for performance
   mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
 
   // cache frequently used statements (for insertion, deletion, and updating)
   rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "INSERT INTO moz_hosts "
-    "(id, host, type, permission, expireType, expireTime, appId, isInBrowserElement) "
-    "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)"), getter_AddRefs(mStmtInsert));
+    "(id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) "
+    "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)"), getter_AddRefs(mStmtInsert));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "DELETE FROM moz_hosts "
     "WHERE id = ?1"), getter_AddRefs(mStmtDelete));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "UPDATE moz_hosts "
-    "SET permission = ?2, expireType= ?3, expireTime = ?4 WHERE id = ?1"),
+    "SET permission = ?2, expireType= ?3, expireTime = ?4, modificationTime = ?5 WHERE id = ?1"),
     getter_AddRefs(mStmtUpdate));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Always import default permissions.
   ImportDefaults();
   // check whether to import or just read in the db
   if (tableExists)
     return Read();
@@ -621,16 +642,17 @@ nsPermissionManager::CreateTable()
   return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE moz_hosts ("
       " id INTEGER PRIMARY KEY"
       ",host TEXT"
       ",type TEXT"
       ",permission INTEGER"
       ",expireType INTEGER"
       ",expireTime INTEGER"
+      ",modificationTime INTEGER"
       ",appId INTEGER"
       ",isInBrowserElement INTEGER"
     ")"));
 }
 
 NS_IMETHODIMP
 nsPermissionManager::Add(nsIURI     *aURI,
                          const char *aType,
@@ -674,27 +696,31 @@ nsPermissionManager::AddFromPrincipal(ns
     return NS_OK;
   }
 
   // Permissions may not be added to expanded principals.
   if (IsExpandedPrincipal(aPrincipal)) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  // A modificationTime of zero will cause AddInternal to use now().
+  int64_t modificationTime = 0;
+
   return AddInternal(aPrincipal, nsDependentCString(aType), aPermission, 0,
-                     aExpireType, aExpireTime, eNotify, eWriteToDB);
+                     aExpireType, aExpireTime, modificationTime, eNotify, eWriteToDB);
 }
 
 nsresult
 nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal,
                                  const nsAFlatCString &aType,
                                  uint32_t              aPermission,
                                  int64_t               aID,
                                  uint32_t              aExpireType,
                                  int64_t               aExpireTime,
+                                 int64_t               aModificationTime,
                                  NotifyOperationType   aNotifyOperation,
                                  DBOperationType       aDBOperation)
 {
   nsAutoCString host;
   nsresult rv = GetHostForPrincipal(aPrincipal, host);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!IsChildProcess()) {
@@ -755,25 +781,37 @@ nsPermissionManager::AddInternal(nsIPrin
          aExpireTime == oldPermissionEntry.mExpireTime))
       op = eOperationNone;
     else if (oldPermissionEntry.mID == cIDPermissionIsDefault)
       // The existing permission is one added as a default and the new permission
       // doesn't exactly match so we are replacing the default.  This is true
       // even if the new permission is UNKNOWN_ACTION (which means a "logical
       // remove" of the default)
       op = eOperationReplacingDefault;
+    else if (aID == cIDPermissionIsDefault)
+      // We are adding a default permission but a "real" permission already
+      // exists.  This almost-certainly means we just did a removeAllSince and
+      // are re-importing defaults - so we can ignore this.
+      op = eOperationNone;
     else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
       op = eOperationRemoving;
     else
       op = eOperationChanging;
   }
 
+  // child processes should *always* be passed a modificationTime of zero.
+  MOZ_ASSERT(!IsChildProcess() || aModificationTime == 0);
+
   // do the work for adding, deleting, or changing a permission:
   // update the in-memory list, write to the db, and notify consumers.
   int64_t id;
+  if (aModificationTime == 0) {
+    aModificationTime = PR_Now() / 1000;
+  }
+
   switch (op) {
   case eOperationNone:
     {
       // nothing to do
       return NS_OK;
     }
 
   case eOperationAdding:
@@ -781,28 +819,30 @@ nsPermissionManager::AddInternal(nsIPrin
       if (aDBOperation == eWriteToDB) {
         // we'll be writing to the database - generate a known unique id
         id = ++mLargestID;
       } else {
         // we're reading from the database - use the id already assigned
         id = aID;
       }
 
-      entry->GetPermissions().AppendElement(PermissionEntry(id, typeIndex, aPermission, aExpireType, aExpireTime));
+      entry->GetPermissions().AppendElement(PermissionEntry(id, typeIndex, aPermission,
+                                                            aExpireType, aExpireTime,
+                                                            aModificationTime));
 
       if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
         uint32_t appId;
         rv = aPrincipal->GetAppId(&appId);
         NS_ENSURE_SUCCESS(rv, rv);
 
         bool isInBrowserElement;
         rv = aPrincipal->GetIsInBrowserElement(&isInBrowserElement);
         NS_ENSURE_SUCCESS(rv, rv);
 
-        UpdateDB(op, mStmtInsert, id, host, aType, aPermission, aExpireType, aExpireTime, appId, isInBrowserElement);
+        UpdateDB(op, mStmtInsert, id, host, aType, aPermission, aExpireType, aExpireTime, aModificationTime, appId, isInBrowserElement);
       }
 
       if (aNotifyOperation == eNotify) {
         NotifyObserversWithPermission(host,
                                       entry->GetKey()->mAppId,
                                       entry->GetKey()->mIsInBrowserElement,
                                       mTypeArray[typeIndex],
                                       aPermission,
@@ -819,17 +859,17 @@ nsPermissionManager::AddInternal(nsIPrin
       PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
       id = oldPermissionEntry.mID;
       entry->GetPermissions().RemoveElementAt(index);
 
       if (aDBOperation == eWriteToDB)
         // We care only about the id here so we pass dummy values for all other
         // parameters.
         UpdateDB(op, mStmtDelete, id, EmptyCString(), EmptyCString(), 0,
-                 nsIPermissionManager::EXPIRE_NEVER, 0, 0, false);
+                 nsIPermissionManager::EXPIRE_NEVER, 0, 0, 0, false);
 
       if (aNotifyOperation == eNotify) {
         NotifyObserversWithPermission(host,
                                       entry->GetKey()->mAppId,
                                       entry->GetKey()->mIsInBrowserElement,
                                       mTypeArray[typeIndex],
                                       oldPermissionEntry.mPermission,
                                       oldPermissionEntry.mExpireType,
@@ -861,22 +901,23 @@ nsPermissionManager::AddInternal(nsIPrin
         entry->GetPermissions()[index].mNonSessionPermission = aPermission;
         entry->GetPermissions()[index].mNonSessionExpireType = aExpireType;
         entry->GetPermissions()[index].mNonSessionExpireTime = aExpireTime;
       }
 
       entry->GetPermissions()[index].mPermission = aPermission;
       entry->GetPermissions()[index].mExpireType = aExpireType;
       entry->GetPermissions()[index].mExpireTime = aExpireTime;
+      entry->GetPermissions()[index].mModificationTime = aModificationTime;
 
       if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
-        // We care only about the id, the permission and expireType/expireTime here.
+        // We care only about the id, the permission and expireType/expireTime/modificationTime here.
         // We pass dummy values for all other parameters.
         UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(),
-                 aPermission, aExpireType, aExpireTime, 0, false);
+                 aPermission, aExpireType, aExpireTime, aModificationTime, 0, false);
 
       if (aNotifyOperation == eNotify) {
         NotifyObserversWithPermission(host,
                                       entry->GetKey()->mAppId,
                                       entry->GetKey()->mIsInBrowserElement,
                                       mTypeArray[typeIndex],
                                       aPermission,
                                       aExpireType,
@@ -910,28 +951,30 @@ nsPermissionManager::AddInternal(nsIPrin
       // default support that.
       NS_ENSURE_TRUE(aExpireType == EXPIRE_NEVER, NS_ERROR_UNEXPECTED);
 
       // update the existing entry in memory.
       entry->GetPermissions()[index].mID = id;
       entry->GetPermissions()[index].mPermission = aPermission;
       entry->GetPermissions()[index].mExpireType = aExpireType;
       entry->GetPermissions()[index].mExpireTime = aExpireTime;
+      entry->GetPermissions()[index].mModificationTime = aModificationTime;
 
       // If requested, create the entry in the DB.
       if (aDBOperation == eWriteToDB) {
         uint32_t appId;
         rv = aPrincipal->GetAppId(&appId);
         NS_ENSURE_SUCCESS(rv, rv);
 
         bool isInBrowserElement;
         rv = aPrincipal->GetIsInBrowserElement(&isInBrowserElement);
         NS_ENSURE_SUCCESS(rv, rv);
 
-        UpdateDB(eOperationAdding, mStmtInsert, id, host, aType, aPermission, aExpireType, aExpireTime, appId, isInBrowserElement);
+        UpdateDB(eOperationAdding, mStmtInsert, id, host, aType, aPermission,
+                 aExpireType, aExpireTime, aModificationTime, appId, isInBrowserElement);
       }
 
       if (aNotifyOperation == eNotify) {
         NotifyObserversWithPermission(host,
                                       entry->GetKey()->mAppId,
                                       entry->GetKey()->mIsInBrowserElement,
                                       mTypeArray[typeIndex],
                                       aPermission,
@@ -978,27 +1021,35 @@ nsPermissionManager::RemoveFromPrincipal
 
   // AddInternal() handles removal, just let it do the work
   return AddInternal(aPrincipal,
                      nsDependentCString(aType),
                      nsIPermissionManager::UNKNOWN_ACTION,
                      0,
                      nsIPermissionManager::EXPIRE_NEVER,
                      0,
+                     0,
                      eNotify,
                      eWriteToDB);
 }
 
 NS_IMETHODIMP
 nsPermissionManager::RemoveAll()
 {
   ENSURE_NOT_CHILD_PROCESS;
   return RemoveAllInternal(true);
 }
 
+NS_IMETHODIMP
+nsPermissionManager::RemoveAllSince(int64_t aSince)
+{
+  ENSURE_NOT_CHILD_PROCESS;
+  return RemoveAllModifiedSince(aSince);
+}
+
 void
 nsPermissionManager::CloseDB(bool aRebuildOnSuccess)
 {
   // Null the statements, this will finalize them.
   mStmtInsert = nullptr;
   mStmtDelete = nullptr;
   mStmtUpdate = nullptr;
   if (mDBConn) {
@@ -1318,22 +1369,26 @@ nsPermissionManager::GetPermissionHashKe
 
   // No entry, really...
   return nullptr;
 }
 
 // helper struct for passing arguments into hash enumeration callback.
 struct nsGetEnumeratorData
 {
-  nsGetEnumeratorData(nsCOMArray<nsIPermission> *aArray, const nsTArray<nsCString> *aTypes)
+  nsGetEnumeratorData(nsCOMArray<nsIPermission> *aArray,
+                      const nsTArray<nsCString> *aTypes,
+                      int64_t aSince = 0)
    : array(aArray)
-   , types(aTypes) {}
+   , types(aTypes)
+   , since(aSince) {}
 
   nsCOMArray<nsIPermission> *array;
   const nsTArray<nsCString> *types;
+  int64_t since;
 };
 
 static PLDHashOperator
 AddPermissionsToList(nsPermissionManager::PermissionHashKey* entry, void *arg)
 {
   nsGetEnumeratorData *data = static_cast<nsGetEnumeratorData *>(arg);
 
   for (uint32_t i = 0; i < entry->GetPermissions().Length(); ++i) {
@@ -1390,16 +1445,86 @@ NS_IMETHODIMP nsPermissionManager::Obser
   else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
     // the profile has already changed; init the db from the new location
     InitDB(false);
   }
 
   return NS_OK;
 }
 
+static PLDHashOperator
+AddPermissionsModifiedSinceToList(
+  nsPermissionManager::PermissionHashKey* entry, void* arg)
+{
+  nsGetEnumeratorData* data = static_cast<nsGetEnumeratorData *>(arg);
+
+  for (size_t i = 0; i < entry->GetPermissions().Length(); ++i) {
+    const nsPermissionManager::PermissionEntry& permEntry = entry->GetPermissions()[i];
+
+    if (data->since > permEntry.mModificationTime) {
+      continue;
+    }
+
+    nsPermission* perm = new nsPermission(entry->GetKey()->mHost,
+                                          entry->GetKey()->mAppId,
+                                          entry->GetKey()->mIsInBrowserElement,
+                                          data->types->ElementAt(permEntry.mType),
+                                          permEntry.mPermission,
+                                          permEntry.mExpireType,
+                                          permEntry.mExpireTime);
+
+    data->array->AppendObject(perm);
+  }
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+nsPermissionManager::RemoveAllModifiedSince(int64_t aModificationTime)
+{
+  ENSURE_NOT_CHILD_PROCESS;
+
+  // roll an nsCOMArray of all our permissions, then hand out an enumerator
+  nsCOMArray<nsIPermission> array;
+  nsGetEnumeratorData data(&array, &mTypeArray, aModificationTime);
+
+  mPermissionTable.EnumerateEntries(AddPermissionsModifiedSinceToList, &data);
+
+  for (int32_t i = 0; i<array.Count(); ++i) {
+    nsAutoCString host;
+    bool isInBrowserElement = false;
+    nsAutoCString type;
+    uint32_t appId = 0;
+
+    array[i]->GetHost(host);
+    array[i]->GetIsInBrowserElement(&isInBrowserElement);
+    array[i]->GetType(type);
+    array[i]->GetAppId(&appId);
+
+    nsCOMPtr<nsIPrincipal> principal;
+    if (NS_FAILED(GetPrincipal(host, appId, isInBrowserElement,
+                               getter_AddRefs(principal)))) {
+      NS_ERROR("GetPrincipal() failed!");
+      continue;
+    }
+    // AddInternal handles removal, so let it do the work...
+    AddInternal(
+      principal,
+      type,
+      nsIPermissionManager::UNKNOWN_ACTION,
+      0,
+      nsIPermissionManager::EXPIRE_NEVER, 0, 0,
+      nsPermissionManager::eNotify,
+      nsPermissionManager::eWriteToDB);
+  }
+  // now re-import any defaults as they may now be required if we just deleted
+  // an override.
+  ImportDefaults();
+  return NS_OK;
+}
+
 PLDHashOperator
 nsPermissionManager::GetPermissionsForApp(nsPermissionManager::PermissionHashKey* entry, void* arg)
 {
   GetPermissionsForAppStruct* data = static_cast<GetPermissionsForAppStruct*>(arg);
 
   for (uint32_t i = 0; i < entry->GetPermissions().Length(); ++i) {
     nsPermissionManager::PermissionEntry& permEntry = entry->GetPermissions()[i];
 
@@ -1470,16 +1595,17 @@ nsPermissionManager::RemovePermissionsFo
     }
 
     AddInternal(principal,
                 type,
                 nsIPermissionManager::UNKNOWN_ACTION,
                 0,
                 nsIPermissionManager::EXPIRE_NEVER,
                 0,
+                0,
                 nsPermissionManager::eNotify,
                 nsPermissionManager::eNoDBOperation);
   }
 
   return NS_OK;
 }
 
 PLDHashOperator
@@ -1641,25 +1767,26 @@ nsPermissionManager::Read()
 
     bool hasResult;
     rv = stmtDeleteExpired->ExecuteStep(&hasResult);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<mozIStorageStatement> stmt;
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT id, host, type, permission, expireType, expireTime, appId, isInBrowserElement "
+    "SELECT id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement "
     "FROM moz_hosts"), getter_AddRefs(stmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   int64_t id;
   nsAutoCString host, type;
   uint32_t permission;
   uint32_t expireType;
   int64_t expireTime;
+  int64_t modificationTime;
   uint32_t appId;
   bool isInBrowserElement;
   bool hasResult;
   bool readError = false;
 
   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
     // explicitly set our entry id counter for use in AddInternal(),
     // and keep track of the largest id so we know where to pick up.
@@ -1677,35 +1804,37 @@ nsPermissionManager::Read()
     if (NS_FAILED(rv)) {
       readError = true;
       continue;
     }
 
     permission = stmt->AsInt32(3);
     expireType = stmt->AsInt32(4);
 
-    // convert into int64_t value (milliseconds)
+    // convert into int64_t values (milliseconds)
     expireTime = stmt->AsInt64(5);
+    modificationTime = stmt->AsInt64(6);
 
-    if (stmt->AsInt64(6) < 0) {
+    if (stmt->AsInt64(7) < 0) {
       readError = true;
       continue;
     }
-    appId = static_cast<uint32_t>(stmt->AsInt64(6));
-    isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
+
+    appId = static_cast<uint32_t>(stmt->AsInt64(7));
+    isInBrowserElement = static_cast<bool>(stmt->AsInt32(8));
 
     nsCOMPtr<nsIPrincipal> principal;
     nsresult rv = GetPrincipal(host, appId, isInBrowserElement, getter_AddRefs(principal));
     if (NS_FAILED(rv)) {
       readError = true;
       continue;
     }
 
     rv = AddInternal(principal, type, permission, id, expireType, expireTime,
-                     eDontNotify, eNoDBOperation);
+                     modificationTime, eDontNotify, eNoDBOperation);
     if (NS_FAILED(rv)) {
       readError = true;
       continue;
     }
   }
 
   if (readError) {
     NS_ERROR("Error occured while reading the permissions database!");
@@ -1843,18 +1972,24 @@ nsPermissionManager::_DoImport(nsIInputS
         if (NS_FAILED(rv))
           continue;
       }
 
       nsCOMPtr<nsIPrincipal> principal;
       nsresult rv = GetPrincipal(lineArray[3], getter_AddRefs(principal));
       NS_ENSURE_SUCCESS(rv, rv);
 
+      // the import file format doesn't handle modification times, so we use
+      // 0, which AddInternal will convert to now()
+      int64_t modificationTime = 0;
+
       rv = AddInternal(principal, lineArray[1], permission, id,
-                       nsIPermissionManager::EXPIRE_NEVER, 0, eDontNotify, operation);
+                       nsIPermissionManager::EXPIRE_NEVER, 0,
+                       modificationTime,
+                       eDontNotify, operation);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
   } while (isMore);
 
   return NS_OK;
 }
 
@@ -1875,16 +2010,17 @@ void
 nsPermissionManager::UpdateDB(OperationType aOp,
                               mozIStorageAsyncStatement* aStmt,
                               int64_t aID,
                               const nsACString &aHost,
                               const nsACString &aType,
                               uint32_t aPermission,
                               uint32_t aExpireType,
                               int64_t aExpireTime,
+                              int64_t aModificationTime,
                               uint32_t aAppId,
                               bool aIsInBrowserElement)
 {
   ENSURE_NOT_CHILD_PROCESS_NORET;
 
   nsresult rv;
 
   // no statement is ok - just means we don't have a profile
@@ -1907,20 +2043,23 @@ nsPermissionManager::UpdateDB(OperationT
       if (NS_FAILED(rv)) break;
 
       rv = aStmt->BindInt32ByIndex(4, aExpireType);
       if (NS_FAILED(rv)) break;
 
       rv = aStmt->BindInt64ByIndex(5, aExpireTime);
       if (NS_FAILED(rv)) break;
 
-      rv = aStmt->BindInt64ByIndex(6, aAppId);
+      rv = aStmt->BindInt64ByIndex(6, aModificationTime);
       if (NS_FAILED(rv)) break;
 
-      rv = aStmt->BindInt64ByIndex(7, aIsInBrowserElement);
+      rv = aStmt->BindInt64ByIndex(7, aAppId);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindInt64ByIndex(8, aIsInBrowserElement);
       break;
     }
 
   case eOperationRemoving:
     {
       rv = aStmt->BindInt64ByIndex(0, aID);
       break;
     }
@@ -1932,16 +2071,19 @@ nsPermissionManager::UpdateDB(OperationT
 
       rv = aStmt->BindInt32ByIndex(1, aPermission);
       if (NS_FAILED(rv)) break;
 
       rv = aStmt->BindInt32ByIndex(2, aExpireType);
       if (NS_FAILED(rv)) break;
 
       rv = aStmt->BindInt64ByIndex(3, aExpireTime);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindInt64ByIndex(4, aModificationTime);
       break;
     }
 
   default:
     {
       NS_NOTREACHED("need a valid operation in UpdateDB()!");
       rv = NS_ERROR_UNEXPECTED;
       break;
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -32,32 +32,35 @@ class nsPermissionManager MOZ_FINAL : pu
                                       public nsIObserver,
                                       public nsSupportsWeakReference
 {
 public:
   class PermissionEntry
   {
   public:
     PermissionEntry(int64_t aID, uint32_t aType, uint32_t aPermission,
-                    uint32_t aExpireType, int64_t aExpireTime)
+                    uint32_t aExpireType, int64_t aExpireTime,
+                    int64_t aModificationTime)
      : mID(aID)
      , mType(aType)
      , mPermission(aPermission)
      , mExpireType(aExpireType)
      , mExpireTime(aExpireTime)
+     , mModificationTime(aModificationTime)
      , mNonSessionPermission(aPermission)
      , mNonSessionExpireType(aExpireType)
      , mNonSessionExpireTime(aExpireTime)
     {}
 
     int64_t  mID;
     uint32_t mType;
     uint32_t mPermission;
     uint32_t mExpireType;
     int64_t  mExpireTime;
+    int64_t  mModificationTime;
     uint32_t mNonSessionPermission;
     uint32_t mNonSessionExpireType;
     uint32_t mNonSessionExpireTime;
   };
 
   /**
    * PermissionKey is the key used by PermissionHashKey hash table.
    *
@@ -149,17 +152,17 @@ public:
     inline PermissionEntry GetPermission(uint32_t aType) const
     {
       for (uint32_t i = 0; i < mPermissions.Length(); ++i)
         if (mPermissions[i].mType == aType)
           return mPermissions[i];
 
       // unknown permission... return relevant data 
       return PermissionEntry(-1, aType, nsIPermissionManager::UNKNOWN_ACTION,
-                             nsIPermissionManager::EXPIRE_NEVER, 0);
+                             nsIPermissionManager::EXPIRE_NEVER, 0, 0);
     }
 
   private:
     nsAutoTArray<PermissionEntry, 1> mPermissions;
   };
 
   // nsISupports
   NS_DECL_ISUPPORTS
@@ -195,16 +198,17 @@ public:
   static const int64_t cIDPermissionIsDefault = -1;
 
   nsresult AddInternal(nsIPrincipal* aPrincipal,
                        const nsAFlatCString &aType,
                        uint32_t aPermission,
                        int64_t aID,
                        uint32_t aExpireType,
                        int64_t  aExpireTime,
+                       int64_t aModificationTime,
                        NotifyOperationType aNotifyOperation,
                        DBOperationType aDBOperation);
 
   /**
    * Initialize the "webapp-uninstall" observing.
    * Will create a nsPermissionManager instance if needed.
    * That way, we can prevent have nsPermissionManager created at startup just
    * to be able to clear data when an application is uninstalled.
@@ -255,16 +259,17 @@ private:
   static void UpdateDB(OperationType aOp,
                        mozIStorageAsyncStatement* aStmt,
                        int64_t aID,
                        const nsACString& aHost,
                        const nsACString& aType,
                        uint32_t aPermission,
                        uint32_t aExpireType,
                        int64_t aExpireTime,
+                       int64_t aModificationTime,
                        uint32_t aAppId,
                        bool aIsInBrowserElement);
 
   nsresult RemoveExpiredPermissionsForApp(uint32_t aAppId);
 
   /**
    * This struct has to be passed as an argument to GetPermissionsForApp.
    * |appId| and |browserOnly| have to be defined.
@@ -294,16 +299,23 @@ private:
 
   /**
    * This method restores an app's permissions when its session ends.
    */
   static PLDHashOperator
   RemoveExpiredPermissionsForAppEnumerator(PermissionHashKey* entry,
                                            void* nonused);
 
+
+  /**
+   * This method removes all permissions modified after the specified time.
+   */
+  nsresult
+  RemoveAllModifiedSince(int64_t aModificationTime);
+
   nsCOMPtr<nsIObserverService> mObserverService;
   nsCOMPtr<nsIIDNService>      mIDNService;
 
   nsCOMPtr<mozIStorageConnection> mDBConn;
   nsCOMPtr<mozIStorageAsyncStatement> mStmtInsert;
   nsCOMPtr<mozIStorageAsyncStatement> mStmtDelete;
   nsCOMPtr<mozIStorageAsyncStatement> mStmtUpdate;
 
--- a/extensions/cookie/test/unit/test_permmanager_defaults.js
+++ b/extensions/cookie/test/unit/test_permmanager_defaults.js
@@ -1,15 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // The origin we use in most of the tests.
 const TEST_ORIGIN = "example.org";
+const TEST_ORIGIN_2 = "example.com";
 const TEST_PERMISSION = "test-permission";
 
+function promiseTimeout(delay) {
+  let deferred = Promise.defer();
+  do_timeout(delay, deferred.resolve);
+  return deferred.promise;
+}
+
 function run_test() {
   run_next_test();
 }
 
 add_task(function* do_test() {
   // setup a profile.
   do_get_profile();
 
@@ -23,16 +30,17 @@ add_task(function* do_test() {
   ostream.init(file, -1, 0666, 0);
   let conv = Cc["@mozilla.org/intl/converter-output-stream;1"].
              createInstance(Ci.nsIConverterOutputStream);
   conv.init(ostream, "UTF-8", 0, 0);
 
   conv.writeString("# this is a comment\n");
   conv.writeString("\n"); // a blank line!
   conv.writeString("host\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN + "\n");
+  conv.writeString("host\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN_2 + "\n");
   ostream.close();
 
   // Set the preference used by the permission manager so the file is read.
   Services.prefs.setCharPref("permissions.manager.defaultsUrl", "file://" + file.path);
 
   // initialize the permission manager service - it will read that default.
   let pm = Cc["@mozilla.org/permissionmanager;1"].
            getService(Ci.nsIPermissionManager);
@@ -87,16 +95,57 @@ add_task(function* do_test() {
   pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.PROMPT_ACTION);
 
   // it should be reflected in a permission check, in the enumerator and the DB
   do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION,
               pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
   do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION, findCapabilityViaEnum());
   yield checkCapabilityViaDB(Ci.nsIPermissionManager.PROMPT_ACTION);
 
+  // --------------------------------------------------------------
+  // check default permissions and removeAllSince work as expected.
+  pm.removeAll(); // ensure only defaults are there.
+
+  let permURI2 = NetUtil.newURI("http://" + TEST_ORIGIN_2);
+  let principal2 = Services.scriptSecurityManager.getNoAppCodebasePrincipal(permURI2);
+
+  // default for both principals is allow.
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal2, TEST_PERMISSION));
+
+  // Add a default override for TEST_ORIGIN_2 - this one should *not* be
+  // restored in removeAllSince()
+  pm.addFromPrincipal(principal2, TEST_PERMISSION, Ci.nsIPermissionManager.DENY_ACTION);
+  do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+              pm.testPermissionFromPrincipal(principal2, TEST_PERMISSION));
+  yield promiseTimeout(20);
+
+  let since = Number(Date.now());
+  yield promiseTimeout(20);
+
+  // explicitly add a permission which overrides the default for the first
+  // principal - this one *should* be removed by removeAllSince.
+  pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.DENY_ACTION);
+  do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+
+  // do a removeAllSince.
+  pm.removeAllSince(since);
+
+  // the default for the first principal should re-appear as we modified it
+  // later then |since|
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+
+  // but the permission for principal2 should remain as we added that before |since|.
+  do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+              pm.testPermissionFromPrincipal(principal2, TEST_PERMISSION));
+
   // remove the temp file we created.
   file.remove(false);
 });
 
 // use an enumerator to find the requested permission.  Returns the permission
 // value (ie, the "capability" in nsIPermission parlance) or null if it can't
 // be found.
 function findCapabilityViaEnum(host = TEST_ORIGIN, type = TEST_PERMISSION) {
--- a/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
+++ b/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
@@ -108,22 +108,34 @@ function run_test() {
       " (id, host, type, permission, expireType, expireTime, appId, isInBrowserElement) " +
       "VALUES (" + i + ", '" + data.host + "', '" + data.type + "', "
                  + data.permission + ", " + data.expireType + ", "
                  + data.expireTime + ", " + data.appId + ", "
                  + data.isInBrowserElement + ")"
     );
   }
 
+  let earliestNow = Number(Date.now());
   // Initialize the permission manager service
   var pm = Cc["@mozilla.org/permissionmanager;1"]
              .getService(Ci.nsIPermissionManager);
+  let latestNow = Number(Date.now());
 
-  // The schema should still be 3. We want this test to be updated for each
-  // schema update.
-  do_check_eq(connection.schemaVersion, 3);
+  // The schema should be upgraded to 4, and a 'modificationTime' column should
+  // exist with all records having a value of 0.
+  do_check_eq(connection.schemaVersion, 4);
+
+  let select = connection.createStatement("SELECT modificationTime FROM moz_hosts")
+  let numMigrated = 0;
+  while (select.executeStep()) {
+    let thisModTime = select.getInt64(0);
+    do_check_true(thisModTime == 0, "new modifiedTime field is correct");
+    numMigrated += 1;
+  }
+  // check we found at least 1 record that was migrated.
+  do_check_true(numMigrated > 0, "we found at least 1 record that was migrated");
 
   // This permission should always be there.
   let principal = Cc["@mozilla.org/scriptsecuritymanager;1"]
                     .getService(Ci.nsIScriptSecurityManager)
                     .getNoAppCodebasePrincipal(NetUtil.newURI("http://example.org"));
   do_check_eq(pm.testPermissionFromPrincipal(principal, 'test-load-invalid-entries'), Ci.nsIPermissionManager.ALLOW_ACTION);
 }
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_removesince.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that removing permissions since a specified time behaves as expected.
+
+let test_generator = do_run_test();
+
+function run_test() {
+  do_test_pending();
+  test_generator.next();
+}
+
+function continue_test()
+{
+  do_run_generator(test_generator);
+}
+
+function do_run_test() {
+  // Set up a profile.
+  let profile = do_get_profile();
+
+  let pm = Services.perms;
+
+  // to help with testing edge-cases, we will arrange for .removeAllSince to
+  // remove *all* permissions from one principal and one permission from another.
+  let permURI1 = NetUtil.newURI("http://example.com");
+  let principal1 = Services.scriptSecurityManager.getNoAppCodebasePrincipal(permURI1);
+
+  let permURI2 = NetUtil.newURI("http://example.org");
+  let principal2 = Services.scriptSecurityManager.getNoAppCodebasePrincipal(permURI2);
+
+  // add a permission now - this isn't going to be removed.
+  pm.addFromPrincipal(principal1, "test/remove-since", 1);
+
+  // sleep briefly, then record the time - we'll remove all since then.
+  do_timeout(20, continue_test);
+  yield;
+
+  let since = Number(Date.now());
+
+  // *sob* - on Windows at least, the now recorded by nsPermissionManager.cpp
+  // might be a couple of ms *earlier* than what JS sees.  So another sleep
+  // to ensure our |since| is greater than the time of the permissions we
+  // are now adding.  Sadly this means we'll never be able to test when since
+  // exactly equals the modTime, but there you go...
+  do_timeout(20, continue_test);
+  yield;
+
+  // add another item - this second one should get nuked.
+  pm.addFromPrincipal(principal1, "test/remove-since-2", 1);
+
+  // add 2 items for the second principal - both will be removed.
+  pm.addFromPrincipal(principal2, "test/remove-since", 1);
+  pm.addFromPrincipal(principal2, "test/remove-since-2", 1);
+
+  // do the removal.
+  pm.removeAllSince(since);
+
+  // principal1 - the first one should remain.
+  do_check_eq(1, pm.testPermissionFromPrincipal(principal1, "test/remove-since"));
+  // but the second should have been removed.
+  do_check_eq(0, pm.testPermissionFromPrincipal(principal1, "test/remove-since-2"));
+
+  // principal2 - both should have been removed.
+  do_check_eq(0, pm.testPermissionFromPrincipal(principal2, "test/remove-since"));
+  do_check_eq(0, pm.testPermissionFromPrincipal(principal2, "test/remove-since-2"));
+
+  do_finish_generator_test(test_generator);
+}
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -18,16 +18,17 @@ support-files =
 [test_cookies_thirdparty_session.js]
 [test_domain_eviction.js]
 [test_eviction.js]
 [test_permmanager_defaults.js]
 [test_permmanager_expiration.js]
 [test_permmanager_getPermissionObject.js]
 [test_permmanager_notifications.js]
 [test_permmanager_removeall.js]
+[test_permmanager_removesince.js]
 [test_permmanager_load_invalid_entries.js]
 skip-if = debug == true
 [test_permmanager_idn.js]
 [test_permmanager_subdomains.js]
 [test_permmanager_local_files.js]
 [test_permmanager_mailto.js]
 [test_permmanager_cleardata.js]
 [test_schema_2_migration.js]
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -79,18 +79,20 @@ CSS_KEY(-moz-image-rect, _moz_image_rect
 CSS_KEY(-moz-info, _moz_info)
 CSS_KEY(-moz-inline-box, _moz_inline_box)
 CSS_KEY(-moz-inline-grid, _moz_inline_grid)
 CSS_KEY(-moz-inline-stack, _moz_inline_stack)
 CSS_KEY(-moz-isolate, _moz_isolate)
 CSS_KEY(-moz-isolate-override, _moz_isolate_override)
 CSS_KEY(-moz-left, _moz_left)
 CSS_KEY(-moz-list, _moz_list)
+CSS_KEY(-moz-mac-buttonactivetext, _moz_mac_buttonactivetext)
 CSS_KEY(-moz-mac-chrome-active, _moz_mac_chrome_active)
 CSS_KEY(-moz-mac-chrome-inactive, _moz_mac_chrome_inactive)
+CSS_KEY(-moz-mac-defaultbuttontext, _moz_mac_defaultbuttontext)
 CSS_KEY(-moz-mac-focusring, _moz_mac_focusring)
 CSS_KEY(-moz-mac-fullscreen-button, _moz_mac_fullscreen_button)
 CSS_KEY(-moz-mac-menuselect, _moz_mac_menuselect)
 CSS_KEY(-moz-mac-menushadow, _moz_mac_menushadow)
 CSS_KEY(-moz-mac-menutextdisable, _moz_mac_menutextdisable)
 CSS_KEY(-moz-mac-menutextselect, _moz_mac_menutextselect)
 CSS_KEY(-moz-mac-disabledtoolbartext, _moz_mac_disabledtoolbartext)
 CSS_KEY(-moz-mac-secondaryhighlight, _moz_mac_secondaryhighlight)
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -943,18 +943,20 @@ const KTableValue nsCSSProps::kColorKTab
   eCSSKeyword__moz_default_background_color, NS_COLOR_MOZ_DEFAULT_BACKGROUND_COLOR,
   eCSSKeyword__moz_default_color, NS_COLOR_MOZ_DEFAULT_COLOR,
   eCSSKeyword__moz_dialog, LookAndFeel::eColorID__moz_dialog,
   eCSSKeyword__moz_dialogtext, LookAndFeel::eColorID__moz_dialogtext,
   eCSSKeyword__moz_dragtargetzone, LookAndFeel::eColorID__moz_dragtargetzone,
   eCSSKeyword__moz_hyperlinktext, NS_COLOR_MOZ_HYPERLINKTEXT,
   eCSSKeyword__moz_html_cellhighlight, LookAndFeel::eColorID__moz_html_cellhighlight,
   eCSSKeyword__moz_html_cellhighlighttext, LookAndFeel::eColorID__moz_html_cellhighlighttext,
+  eCSSKeyword__moz_mac_buttonactivetext, LookAndFeel::eColorID__moz_mac_buttonactivetext,
   eCSSKeyword__moz_mac_chrome_active, LookAndFeel::eColorID__moz_mac_chrome_active,
   eCSSKeyword__moz_mac_chrome_inactive, LookAndFeel::eColorID__moz_mac_chrome_inactive,
+  eCSSKeyword__moz_mac_defaultbuttontext, LookAndFeel::eColorID__moz_mac_defaultbuttontext,
   eCSSKeyword__moz_mac_focusring, LookAndFeel::eColorID__moz_mac_focusring,
   eCSSKeyword__moz_mac_menuselect, LookAndFeel::eColorID__moz_mac_menuselect,
   eCSSKeyword__moz_mac_menushadow, LookAndFeel::eColorID__moz_mac_menushadow,
   eCSSKeyword__moz_mac_menutextdisable, LookAndFeel::eColorID__moz_mac_menutextdisable,
   eCSSKeyword__moz_mac_menutextselect, LookAndFeel::eColorID__moz_mac_menutextselect,
   eCSSKeyword__moz_mac_disabledtoolbartext, LookAndFeel::eColorID__moz_mac_disabledtoolbartext,
   eCSSKeyword__moz_mac_secondaryhighlight, LookAndFeel::eColorID__moz_mac_secondaryhighlight,
   eCSSKeyword__moz_menuhover, LookAndFeel::eColorID__moz_menuhover,
--- a/mobile/android/base/db/BrowserDB.java
+++ b/mobile/android/base/db/BrowserDB.java
@@ -195,18 +195,21 @@ public class BrowserDB {
     public static void removeReadingListItemWithURL(ContentResolver cr, String uri) {
         sDb.removeReadingListItemWithURL(cr, uri);
     }
 
     public static LoadFaviconResult getFaviconForFaviconUrl(ContentResolver cr, String faviconURL) {
         return sDb.getFaviconForUrl(cr, faviconURL);
     }
 
-    public static String getFaviconUrlForHistoryUrl(ContentResolver cr, String url) {
-        return sDb.getFaviconUrlForHistoryUrl(cr, url);
+    /**
+     * Try to find a usable favicon URL in the history or bookmarks table.
+     */
+    public static String getFaviconURLFromPageURL(ContentResolver cr, String url) {
+        return sDb.getFaviconURLFromPageURL(cr, url);
     }
 
     public static void updateFaviconForUrl(ContentResolver cr, String pageUri, byte[] encodedFavicon, String faviconUri) {
         sDb.updateFaviconForUrl(cr, pageUri, encodedFavicon, faviconUri);
     }
 
     public static void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail) {
         sDb.updateThumbnailForUrl(cr, uri, thumbnail);
--- a/mobile/android/base/db/LocalBrowserDB.java
+++ b/mobile/android/base/db/LocalBrowserDB.java
@@ -53,16 +53,18 @@ import android.net.Uri;
 import android.provider.Browser;
 import android.text.TextUtils;
 import android.util.Log;
 
 public class LocalBrowserDB {
     // Calculate these once, at initialization. isLoggable is too expensive to
     // have in-line in each log call.
     private static final String LOGTAG = "GeckoLocalBrowserDB";
+    private static final Integer FAVICON_ID_NOT_FOUND = Integer.MIN_VALUE;
+
     private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
     protected static void debug(String message) {
         if (logDebug) {
             Log.d(LOGTAG, message);
         }
     }
 
     private final String mProfile;
@@ -993,49 +995,124 @@ public class LocalBrowserDB {
 
         if (b == null) {
             return null;
         }
 
         return FaviconDecoder.decodeFavicon(b);
     }
 
-    public String getFaviconUrlForHistoryUrl(ContentResolver cr, String uri) {
-        final Cursor c = cr.query(mHistoryUriWithProfile,
-                                  new String[] { History.FAVICON_URL },
-                                  Combined.URL + " = ?",
-                                  new String[] { uri },
-                                  null);
+    /**
+     * Try to find a usable favicon URL in the history or bookmarks table.
+     */
+    public String getFaviconURLFromPageURL(ContentResolver cr, String uri) {
+        // Check first in the history table.
+        Cursor c = cr.query(mHistoryUriWithProfile,
+                            new String[] { History.FAVICON_URL },
+                            Combined.URL + " = ?",
+                            new String[] { uri },
+                            null);
 
         try {
-            if (!c.moveToFirst()) {
-                return null;
+            if (c.moveToFirst()) {
+                return c.getString(c.getColumnIndexOrThrow(History.FAVICON_URL));
+            }
+        } finally {
+            c.close();
+        }
+
+        // If that fails, check in the bookmarks table.
+        c = cr.query(mBookmarksUriWithProfile,
+                     new String[] { Bookmarks.FAVICON_URL },
+                     Bookmarks.URL + " = ?",
+                     new String[] { uri },
+                     null);
+
+        try {
+            if (c.moveToFirst()) {
+                return c.getString(c.getColumnIndexOrThrow(Bookmarks.FAVICON_URL));
             }
 
-            return c.getString(c.getColumnIndexOrThrow(History.FAVICON_URL));
+            return null;
         } finally {
             c.close();
         }
     }
 
     public void updateFaviconForUrl(ContentResolver cr, String pageUri,
             byte[] encodedFavicon, String faviconUri) {
         ContentValues values = new ContentValues();
         values.put(Favicons.URL, faviconUri);
         values.put(Favicons.PAGE_URL, pageUri);
         values.put(Favicons.DATA, encodedFavicon);
 
         // Update or insert
         Uri faviconsUri = getAllFaviconsUri().buildUpon().
                 appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
 
-        cr.update(faviconsUri,
-                  values,
-                  Favicons.URL + " = ?",
-                  new String[] { faviconUri });
+        final int updated = cr.update(faviconsUri,
+                                      values,
+                                      Favicons.URL + " = ?",
+                                      new String[] { faviconUri });
+
+        if (updated == 0) {
+            return;
+        }
+
+        // After writing the encodedFavicon, ensure that the favicon_id in both the bookmark and
+        // history tables are also up-to-date.
+        final Integer id = getIDForFaviconURL(cr, faviconUri);
+        if (id == FAVICON_ID_NOT_FOUND) {
+            return;
+        }
+
+        updateHistoryAndBookmarksFaviconID(cr, pageUri, id);
+    }
+
+    /**
+     * Locates and returns the favicon ID of a target URL as an Integer.
+     */
+    private Integer getIDForFaviconURL(ContentResolver cr, String faviconURL) {
+        final Cursor c = cr.query(mFaviconsUriWithProfile,
+                                  new String[] { Favicons._ID },
+                                  Favicons.URL + " = ? AND " + Favicons.DATA + " IS NOT NULL",
+                                  new String[] { faviconURL },
+                                  null);
+
+        try {
+            final int col = c.getColumnIndexOrThrow(Favicons._ID);
+            if (c.moveToFirst() && !c.isNull(col)) {
+                return c.getInt(col);
+            }
+
+            // IDs can be negative, so we return a sentinel value indicating "not found".
+            return FAVICON_ID_NOT_FOUND;
+        } finally {
+            c.close();
+        }
+    }
+
+    /**
+     * Update the favicon ID in the history and bookmark tables after a new
+     * favicon table entry is added.
+     */
+    private void updateHistoryAndBookmarksFaviconID(ContentResolver cr, String pageURL, int id) {
+        final ContentValues bookmarkValues = new ContentValues();
+        bookmarkValues.put(Bookmarks.FAVICON_ID, id);
+        cr.update(mBookmarksUriWithProfile,
+                  bookmarkValues,
+                  Bookmarks.URL + " = ?",
+                  new String[] { pageURL });
+
+        final ContentValues historyValues = new ContentValues();
+        historyValues.put(History.FAVICON_ID, id);
+        cr.update(mHistoryUriWithProfile,
+                  historyValues,
+                  History.URL + " = ?",
+                  new String[] { pageURL });
     }
 
     public void updateThumbnailForUrl(ContentResolver cr, String uri,
             BitmapDrawable thumbnail) {
 
         // If a null thumbnail was passed in, delete the stored thumbnail for this url.
         if (thumbnail == null) {
             cr.delete(mThumbnailsUriWithProfile, Thumbnails.URL + " == ?", new String[] { uri });
--- a/mobile/android/base/favicons/Favicons.java
+++ b/mobile/android/base/favicons/Favicons.java
@@ -12,16 +12,17 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.cache.FaviconCache;
 import org.mozilla.gecko.util.GeckoJarReader;
 import org.mozilla.gecko.util.NonEvictingLruCache;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -48,18 +49,16 @@ public class Favicons {
     // Size of the favicon bitmap cache, in bytes (Counting payload only).
     public static final int FAVICON_CACHE_SIZE_BYTES = 512 * 1024;
 
     // Number of URL mappings from page URL to Favicon URL to cache in memory.
     public static final int NUM_PAGE_URL_MAPPINGS_TO_STORE = 128;
 
     public static final int NOT_LOADING  = 0;
     public static final int LOADED       = 1;
-    public static final int FLAG_PERSIST = 2;
-    public static final int FLAG_SCALE   = 4;
 
     // The default Favicon to show if no other can be found.
     public static Bitmap defaultFavicon;
 
     // The density-adjusted default Favicon dimensions.
     public static int defaultFaviconSize;
 
     // The density-adjusted maximum Favicon dimensions.
@@ -255,22 +254,25 @@ public class Favicons {
         Tab theTab = Tabs.getInstance().getFirstTabForUrl(pageURL);
         if (theTab != null) {
             targetURL = theTab.getFaviconURL();
             if (targetURL != null) {
                 return targetURL;
             }
         }
 
-        targetURL = BrowserDB.getFaviconUrlForHistoryUrl(context.getContentResolver(), pageURL);
-        if (targetURL == null) {
-            // Nothing in the history database. Fall back to the default URL and hope for the best.
-            targetURL = guessDefaultFaviconURL(pageURL);
+        // Try to find the faviconURL in the history and/or bookmarks table.
+        final ContentResolver resolver = context.getContentResolver();
+        targetURL = BrowserDB.getFaviconURLFromPageURL(resolver, pageURL);
+        if (targetURL != null) {
+            return targetURL;
         }
-        return targetURL;
+
+        // If we still can't find it, fall back to the default URL and hope for the best.
+        return guessDefaultFaviconURL(pageURL);
     }
 
     /**
      * Helper function to create an async job to load a Favicon which does not exist in the memcache.
      * Contains logic to prevent the repeated loading of Favicons which have previously failed.
      * There is no support for recovery from transient failures.
      *
      * @param pageURL URL of the page for which to load a Favicon. If null, no job is created.
--- a/mobile/android/base/favicons/LoadFaviconTask.java
+++ b/mobile/android/base/favicons/LoadFaviconTask.java
@@ -41,17 +41,16 @@ import java.util.concurrent.atomic.Atomi
 public class LoadFaviconTask {
     private static final String LOGTAG = "LoadFaviconTask";
 
     // Access to this map needs to be synchronized prevent multiple jobs loading the same favicon
     // from executing concurrently.
     private static final HashMap<String, LoadFaviconTask> loadsInFlight = new HashMap<>();
 
     public static final int FLAG_PERSIST = 1;
-    public static final int FLAG_SCALE = 2;
     private static final int MAX_REDIRECTS_TO_FOLLOW = 5;
     // The default size of the buffer to use for downloading Favicons in the event no size is given
     // by the server.
     private static final int DEFAULT_FAVICON_BUFFER_SIZE = 25000;
 
     private static final AtomicInteger nextFaviconLoadId = new AtomicInteger(0);
     private final Context context;
     private final int id;
--- a/netwerk/base/public/nsIPermissionManager.idl
+++ b/netwerk/base/public/nsIPermissionManager.idl
@@ -32,17 +32,17 @@
 
 interface nsIURI;
 interface nsIObserver;
 interface nsIPrincipal;
 interface nsIDOMWindow;
 interface nsIPermission;
 interface nsISimpleEnumerator;
 
-[scriptable, uuid(c9fec678-f194-43c9-96b0-7bd9dbdd6bb0)]
+[scriptable, uuid(620d9b61-8997-4d13-aa64-ec03341dd75b)]
 interface nsIPermissionManager : nsISupports
 {
   /**
    * Predefined return values for the testPermission method and for
    * the permission param of the add method
    * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
    * default permission when no entry is found for a host, and
    * should not be used by consumers to indicate otherwise.
@@ -128,16 +128,21 @@ interface nsIPermissionManager : nsISupp
   void removeFromPrincipal(in nsIPrincipal principal, in string type);
 
   /**
    * Clear permission information for all websites.
    */
   void removeAll();
 
   /**
+   * Clear all permission information added since the specified time.
+   */
+  void removeAllSince(in int64_t since);
+
+  /**
    * Test whether a website has permission to perform the given action.
    * @param uri     the uri to be tested
    * @param type    a case-sensitive ASCII string, identifying the consumer
    * @param return  see add(), param permission. returns UNKNOWN_ACTION when
    *                there is no stored permission for this uri and / or type.
    */
   uint32_t testPermission(in nsIURI uri,
                           in string type);
--- a/testing/mozbase/mozprofile/mozprofile/permissions.py
+++ b/testing/mozbase/mozprofile/mozprofile/permissions.py
@@ -236,18 +236,22 @@ class Permissions(object):
            type TEXT,
            permission INTEGER,
            expireType INTEGER,
            expireTime INTEGER)""")
 
         rows = cursor.execute("PRAGMA table_info(moz_hosts)")
         count = len(rows.fetchall())
 
+        # if the db contains 9 columns, we're using user_version 4
+        if count == 9:
+            statement = "INSERT INTO moz_hosts values(NULL, ?, ?, ?, 0, 0, 0, 0, 0)"
+            cursor.execute("PRAGMA user_version=4;")
         # if the db contains 8 columns, we're using user_version 3
-        if count == 8:
+        elif count == 8:
             statement = "INSERT INTO moz_hosts values(NULL, ?, ?, ?, 0, 0, 0, 0)"
             cursor.execute("PRAGMA user_version=3;")
         else:
             statement = "INSERT INTO moz_hosts values(NULL, ?, ?, ?, 0, 0)"
             cursor.execute("PRAGMA user_version=2;")
 
         for location in locations:
             # set the permissions
--- a/testing/mozbase/mozprofile/tests/permissions.py
+++ b/testing/mozbase/mozprofile/tests/permissions.py
@@ -35,17 +35,28 @@ http://127.0.0.1:8888           privileg
             self.locations_file.close()
 
     def write_perm_db(self, version=3):
         permDB = sqlite3.connect(os.path.join(self.profile_dir, "permissions.sqlite"))
         cursor = permDB.cursor()
 
         cursor.execute("PRAGMA user_version=%d;" % version)
 
-        if version == 3:
+        if version == 4:
+            cursor.execute("""CREATE TABLE IF NOT EXISTS moz_hosts (
+               id INTEGER PRIMARY KEY,
+               host TEXT,
+               type TEXT,
+               permission INTEGER,
+               expireType INTEGER,
+               expireTime INTEGER,
+               modificationTime INTEGER,
+               appId INTEGER,
+               isInBrowserElement INTEGER)""")
+        elif version == 3:
             cursor.execute("""CREATE TABLE IF NOT EXISTS moz_hosts (
                id INTEGER PRIMARY KEY,
                host TEXT,
                type TEXT,
                permission INTEGER,
                expireType INTEGER,
                expireTime INTEGER,
                appId INTEGER,
@@ -54,17 +65,17 @@ http://127.0.0.1:8888           privileg
             cursor.execute("""CREATE TABLE IF NOT EXISTS moz_hosts (
                id INTEGER PRIMARY KEY,
                host TEXT,
                type TEXT,
                permission INTEGER,
                expireType INTEGER,
                expireTime INTEGER)""")
         else:
-            raise Exception("version must be 2 or 3")
+            raise Exception("version must be 2, 3 or 4")
 
         permDB.commit()
         cursor.close()
 
     def test_create_permissions_db(self):
         perms = Permissions(self.profile_dir, self.locations_file.name)
         perms_db_filename = os.path.join(self.profile_dir, 'permissions.sqlite')
 
@@ -144,22 +155,24 @@ http://127.0.0.1:8888           privileg
 
         con = sqlite3.connect(perms_db_filename)
         cur = con.cursor()
         cur.execute(select_stmt)
         entries = cur.fetchall()
 
         self.assertEqual(len(entries), 3)
 
-        columns = 8 if version == 3 else 6
+        columns = 9 if version == 4 else (8 if version == 3 else 6)
         self.assertEqual(len(entries[0]), columns)
         for x in range(4, columns):
             self.assertEqual(entries[0][x], 0)
 
     def test_existing_permissions_db_v2(self):
         self.verify_user_version(2)
 
     def test_existing_permissions_db_v3(self):
         self.verify_user_version(3)
 
+    def test_existing_permissions_db_v4(self):
+        self.verify_user_version(4)
 
 if __name__ == '__main__':
     unittest.main()
--- a/toolkit/themes/osx/global/button.css
+++ b/toolkit/themes/osx/global/button.css
@@ -9,16 +9,32 @@ button {
   /* The horizontal margin used here come from the Aqua Human Interface
      Guidelines, there should be 12 pixels between two buttons. */
   margin: 5px 6px 3px;
   min-width: 79px;
   color: ButtonText;
   text-shadow: none;
 }
 
+button:hover:active {
+  color: -moz-mac-buttonactivetext;
+}
+
+/* When the window isn't focused, the default button background isn't drawn,
+ * so don't change the text color then: */
+button[default="true"]:not(:-moz-window-inactive) {
+  color: -moz-mac-defaultbuttontext;
+}
+
+/* Likewise, when active (mousedown) but not hovering, the default button
+ * background isn't drawn, override the previous selector for that case: */
+button[default="true"]:not(:hover):active {
+  color: ButtonText;
+}
+
 .button-text {
   margin: 1px 0 !important;
   -moz-margin-start: 3px !important;
   -moz-margin-end: 2px !important;
   text-align: center;
 }
 
 .button-icon {
--- a/widget/LookAndFeel.h
+++ b/widget/LookAndFeel.h
@@ -121,20 +121,24 @@ public:
     eColorID__moz_menubarhovertext,
     // On platforms where these colors are the same as
     // -moz-field, use -moz-fieldtext as foreground color
     eColorID__moz_eventreerow,
     eColorID__moz_oddtreerow,
 
     // colors needed by the Mac OS X theme
 
+    // foreground color of :hover:active buttons
+    eColorID__moz_mac_buttonactivetext,
     // background color of chrome toolbars in active windows
     eColorID__moz_mac_chrome_active,
     // background color of chrome toolbars in inactive windows
     eColorID__moz_mac_chrome_inactive,
+    // foreground color of default buttons
+    eColorID__moz_mac_defaultbuttontext,
     //ring around text fields and lists
     eColorID__moz_mac_focusring,
     //colour used when mouse is over a menu item
     eColorID__moz_mac_menuselect,
     //colour used to do shadows on menu items
     eColorID__moz_mac_menushadow,
     // color used to display text for disabled menu items
     eColorID__moz_mac_menutextdisable,
--- a/widget/cocoa/nsLookAndFeel.mm
+++ b/widget/cocoa/nsLookAndFeel.mm
@@ -132,16 +132,23 @@ nsLookAndFeel::NativeGetColor(ColorID aI
       // It's really hard to effectively map these to the Appearance Manager properly,
       // since they are modeled word for word after the win32 system colors and don't have any 
       // real counterparts in the Mac world. I'm sure we'll be tweaking these for 
       // years to come. 
       //
       // Thanks to mpt26@student.canterbury.ac.nz for the hardcoded values that form the defaults
       //  if querying the Appearance Manager fails ;)
       //
+    case eColorID__moz_mac_buttonactivetext:
+    case eColorID__moz_mac_defaultbuttontext:
+      if (nsCocoaFeatures::OnYosemiteOrLater()) {
+        aColor = NS_RGB(0xFF,0xFF,0xFF);
+        break;
+      }
+      // Otherwise fall through and return the regular button text:
       
     case eColorID_buttontext:
     case eColorID__moz_buttonhovertext:
       aColor = GetColorFromNSColor([NSColor controlTextColor]);
       break;
     case eColorID_captiontext:
     case eColorID_menutext:
     case eColorID_infotext:
--- a/widget/tests/test_platform_colors.xul
+++ b/widget/tests/test_platform_colors.xul
@@ -61,18 +61,20 @@ var colors = {
   "-moz-field": ["rgb(255, 255, 255)"],
   "-moz-fieldtext": ["rgb(0, 0, 0)"],
   "-moz-dialog": ["rgb(232, 232, 232)"],
   "-moz-dialogtext": ["rgb(0, 0, 0)"],
   "-moz-dragtargetzone": ["rgb(199, 208, 218)", "rgb(198, 198, 198)", "rgb(180, 213, 255)", "rgb(250, 236, 115)", "rgb(255, 176, 139)", "rgb(255, 209, 129)", "rgb(194, 249, 144)", "rgb(232, 184, 255)"],
   "-moz-hyperlinktext": ["rgb(0, 0, 238)"],
   "-moz-html-cellhighlight": ["rgb(212, 212, 212)"],
   "-moz-html-cellhighlighttext": ["rgb(0, 0, 0)"],
+  "-moz-mac-buttonactivetext": ["rgb(0, 0, 0)", "rgb(255, 255, 255)"],
   "-moz-mac-chrome-active": ["rgb(150, 150, 150)", "rgb(167, 167, 167)", "rgb(178, 178, 178)"],
   "-moz-mac-chrome-inactive": ["rgb(202, 202, 202)", "rgb(216, 216, 216)", "rgb(225, 225, 225)"],
+  "-moz-mac-defaultbuttontext": ["rgb(0, 0, 0)", "rgb(255, 255, 255)"],
   //"-moz-mac-focusring": ["rgb(83, 144, 210)", "rgb(95, 112, 130)", "rgb(63, 152, 221)", "rgb(108, 126, 141)"],
   "-moz-mac-menuselect": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
   "-moz-mac-menushadow": ["rgb(163, 163, 163)"],
   "-moz-mac-menutextdisable": ["rgb(152, 152, 152)"],
   "-moz-mac-menutextselect": ["rgb(255, 255, 255)"],
   "-moz-mac-disabledtoolbartext": ["rgb(127, 127, 127)"],
   "-moz-mac-secondaryhighlight": ["rgb(212, 212, 212)"],
   "-moz-menuhover": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
--- a/widget/xpwidgets/nsXPLookAndFeel.cpp
+++ b/widget/xpwidgets/nsXPLookAndFeel.cpp
@@ -210,18 +210,20 @@ const char nsXPLookAndFeel::sColorPrefs[
   "ui.-moz-buttonhoverface",
   "ui.-moz_buttonhovertext",
   "ui.-moz_menuhover",
   "ui.-moz_menuhovertext",
   "ui.-moz_menubartext",
   "ui.-moz_menubarhovertext",
   "ui.-moz_eventreerow",
   "ui.-moz_oddtreerow",
+  "ui.-moz-mac-buttonactivetext",
   "ui.-moz_mac_chrome_active",
   "ui.-moz_mac_chrome_inactive",
+  "ui.-moz-mac-defaultbuttontext",
   "ui.-moz-mac-focusring",
   "ui.-moz-mac-menuselect",
   "ui.-moz-mac-menushadow",
   "ui.-moz-mac-menutextdisable",
   "ui.-moz-mac-menutextselect",
   "ui.-moz_mac_disabledtoolbartext",
   "ui.-moz-mac-secondaryhighlight",
   "ui.-moz-win-mediatext",