diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index e9fc039b2ca..d4802edda4a 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1407,7 +1407,6 @@ pref("devtools.tilt.outro_transition", true); // - 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 @@ -1513,6 +1512,7 @@ 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 diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js index fe6d3a133ec..8141469a092 100644 --- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -233,7 +233,8 @@ let DebuggerView = { showAnnotationRuler: true, gutters: gutters, extraKeys: extraKeys, - contextMenu: "sourceEditorContextMenu" + contextMenu: "sourceEditorContextMenu", + enableCodeFolding: false }); this.editor.appendTo(document.getElementById("editor")).then(() => { diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js index 59f21dead8c..a297821e37d 100644 --- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -34,8 +34,8 @@ 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"; @@ -655,7 +655,7 @@ var Scratchpad = { */ 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(); @@ -1604,7 +1604,6 @@ var Scratchpad = { 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), }; diff --git a/browser/devtools/sourceeditor/editor.js b/browser/devtools/sourceeditor/editor.js index 23225b28099..7b26288553b 100644 --- a/browser/devtools/sourceeditor/editor.js +++ b/browser/devtools/sourceeditor/editor.js @@ -9,6 +9,7 @@ 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"; @@ -188,14 +189,12 @@ function Editor(config) { }); }); - // 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. @@ -333,6 +332,7 @@ Editor.prototype = { 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(); @@ -430,6 +430,7 @@ Editor.prototype = { this.setOption("keyMap", keyMap) else this.setOption("keyMap", "default"); + this.updateCodeFoldingGutter(); this.resetIndentUnit(); this.setupAutoCompletion(); @@ -955,6 +956,12 @@ Editor.prototype = { } else { cm.setOption(o, v); } + + if (o === "enableCodeFolding") { + // The new value maybe explicitly force foldGUtter on or off, ignoring + // the prefs service. + this.updateCodeFoldingGutter(); + } }, /** @@ -1036,10 +1043,46 @@ Editor.prototype = { 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); + } } }; diff --git a/browser/devtools/sourceeditor/test/browser_editor_prefs.js b/browser/devtools/sourceeditor/test/browser_editor_prefs.js index 04e55b0e6eb..381a7fc4870 100644 --- a/browser/devtools/sourceeditor/test/browser_editor_prefs.js +++ b/browser/devtools/sourceeditor/test/browser_editor_prefs.js @@ -7,6 +7,7 @@ // 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"; @@ -17,6 +18,11 @@ 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"); @@ -24,14 +30,21 @@ function test() { 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"); @@ -41,19 +54,46 @@ function test() { 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); diff --git a/browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js b/browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js index c397980988c..b2c5c4798a3 100644 --- a/browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js +++ b/browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js @@ -35,14 +35,14 @@ let test = asyncTest(function*() { 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"); }); diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js index 303207b96ff..0b59b64894f 100644 --- a/browser/devtools/webide/content/webide.js +++ b/browser/devtools/webide/content/webide.js @@ -496,7 +496,7 @@ let UI = { Task.spawn(function() { if (project.type == "runtimeApp") { - yield UI.busyUntil(AppManager.runRuntimeApp(), "running app"); + yield UI.busyUntil(AppManager.launchRuntimeApp(), "running app"); } yield UI.createToolbox(); }); @@ -1014,7 +1014,7 @@ let Cmds = { 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"); } diff --git a/browser/devtools/webide/modules/app-manager.js b/browser/devtools/webide/modules/app-manager.js index 24938dcd98c..585d444ea75 100644 --- a/browser/devtools/webide/modules/app-manager.js +++ b/browser/devtools/webide/modules/app-manager.js @@ -326,7 +326,9 @@ exports.AppManager = AppManager = { _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 @@ -439,9 +441,19 @@ exports.AppManager = AppManager = { 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; diff --git a/build/automation.py.in b/build/automation.py.in index ac4fa28a239..bccf2d253cc 100644 --- a/build/automation.py.in +++ b/build/automation.py.in @@ -303,7 +303,7 @@ class Automation(object): 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 ( @@ -313,13 +313,14 @@ class Automation(object): 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 diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 8546eb95e22..27e16b88214 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -1714,12 +1714,16 @@ ContentChild::RecvAddPermission(const IPC::Permission& permission) 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 diff --git a/extensions/cookie/nsPermissionManager.cpp b/extensions/cookie/nsPermissionManager.cpp index 2382b9a56b2..066374dc2a9 100644 --- a/extensions/cookie/nsPermissionManager.cpp +++ b/extensions/cookie/nsPermissionManager.cpp @@ -357,7 +357,7 @@ nsPermissionManager::AppClearDataObserverInit() // nsPermissionManager Implementation static const char kPermissionsFileName[] = "permissions.sqlite"; -#define HOSTS_SCHEMA_VERSION 3 +#define HOSTS_SCHEMA_VERSION 4 static const char kHostpermFileName[] = "hostperm.1"; @@ -432,8 +432,12 @@ nsPermissionManager::Init() 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 @@ -546,6 +550,23 @@ nsPermissionManager::InitDB(bool aRemoveFile) // 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; @@ -561,7 +582,7 @@ nsPermissionManager::InitDB(bool aRemoveFile) // check if all the expected columns exist nsCOMPtr 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; @@ -583,8 +604,8 @@ nsPermissionManager::InitDB(bool aRemoveFile) // 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( @@ -594,7 +615,7 @@ nsPermissionManager::InitDB(bool aRemoveFile) 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); @@ -626,6 +647,7 @@ nsPermissionManager::CreateTable() ",permission INTEGER" ",expireType INTEGER" ",expireTime INTEGER" + ",modificationTime INTEGER" ",appId INTEGER" ",isInBrowserElement INTEGER" ")")); @@ -679,8 +701,11 @@ nsPermissionManager::AddFromPrincipal(nsIPrincipal* 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 @@ -690,6 +715,7 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal, int64_t aID, uint32_t aExpireType, int64_t aExpireTime, + int64_t aModificationTime, NotifyOperationType aNotifyOperation, DBOperationType aDBOperation) { @@ -760,15 +786,27 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal, // 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: { @@ -786,7 +824,9 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal, 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; @@ -797,7 +837,7 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal, 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) { @@ -824,7 +864,7 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal, // 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, @@ -866,12 +906,13 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal, 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, @@ -915,6 +956,7 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal, 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) { @@ -926,7 +968,8 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal, 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) { @@ -983,6 +1026,7 @@ nsPermissionManager::RemoveFromPrincipal(nsIPrincipal* aPrincipal, 0, nsIPermissionManager::EXPIRE_NEVER, 0, + 0, eNotify, eWriteToDB); } @@ -994,6 +1038,13 @@ nsPermissionManager::RemoveAll() return RemoveAllInternal(true); } +NS_IMETHODIMP +nsPermissionManager::RemoveAllSince(int64_t aSince) +{ + ENSURE_NOT_CHILD_PROCESS; + return RemoveAllModifiedSince(aSince); +} + void nsPermissionManager::CloseDB(bool aRebuildOnSuccess) { @@ -1323,12 +1374,16 @@ nsPermissionManager::GetPermissionHashKey(const nsACString& aHost, // helper struct for passing arguments into hash enumeration callback. struct nsGetEnumeratorData { - nsGetEnumeratorData(nsCOMArray *aArray, const nsTArray *aTypes) + nsGetEnumeratorData(nsCOMArray *aArray, + const nsTArray *aTypes, + int64_t aSince = 0) : array(aArray) - , types(aTypes) {} + , types(aTypes) + , since(aSince) {} nsCOMArray *array; const nsTArray *types; + int64_t since; }; static PLDHashOperator @@ -1395,6 +1450,76 @@ NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aT return NS_OK; } +static PLDHashOperator +AddPermissionsModifiedSinceToList( + nsPermissionManager::PermissionHashKey* entry, void* arg) +{ + nsGetEnumeratorData* data = static_cast(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 array; + nsGetEnumeratorData data(&array, &mTypeArray, aModificationTime); + + mPermissionTable.EnumerateEntries(AddPermissionsModifiedSinceToList, &data); + + for (int32_t i = 0; iGetHost(host); + array[i]->GetIsInBrowserElement(&isInBrowserElement); + array[i]->GetType(type); + array[i]->GetAppId(&appId); + + nsCOMPtr 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) { @@ -1475,6 +1600,7 @@ nsPermissionManager::RemovePermissionsForApp(uint32_t aAppId, bool aBrowserOnly) 0, nsIPermissionManager::EXPIRE_NEVER, 0, + 0, nsPermissionManager::eNotify, nsPermissionManager::eNoDBOperation); } @@ -1646,7 +1772,7 @@ nsPermissionManager::Read() nsCOMPtr 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); @@ -1655,6 +1781,7 @@ nsPermissionManager::Read() uint32_t permission; uint32_t expireType; int64_t expireTime; + int64_t modificationTime; uint32_t appId; bool isInBrowserElement; bool hasResult; @@ -1682,15 +1809,17 @@ nsPermissionManager::Read() 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(stmt->AsInt64(6)); - isInBrowserElement = static_cast(stmt->AsInt32(7)); + + appId = static_cast(stmt->AsInt64(7)); + isInBrowserElement = static_cast(stmt->AsInt32(8)); nsCOMPtr principal; nsresult rv = GetPrincipal(host, appId, isInBrowserElement, getter_AddRefs(principal)); @@ -1700,7 +1829,7 @@ nsPermissionManager::Read() } rv = AddInternal(principal, type, permission, id, expireType, expireTime, - eDontNotify, eNoDBOperation); + modificationTime, eDontNotify, eNoDBOperation); if (NS_FAILED(rv)) { readError = true; continue; @@ -1848,8 +1977,14 @@ nsPermissionManager::_DoImport(nsIInputStream *inputStream, mozIStorageConnectio 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); } @@ -1880,6 +2015,7 @@ nsPermissionManager::UpdateDB(OperationType aOp, uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime, + int64_t aModificationTime, uint32_t aAppId, bool aIsInBrowserElement) { @@ -1912,10 +2048,13 @@ nsPermissionManager::UpdateDB(OperationType aOp, 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; } @@ -1937,6 +2076,9 @@ nsPermissionManager::UpdateDB(OperationType aOp, if (NS_FAILED(rv)) break; rv = aStmt->BindInt64ByIndex(3, aExpireTime); + if (NS_FAILED(rv)) break; + + rv = aStmt->BindInt64ByIndex(4, aModificationTime); break; } diff --git a/extensions/cookie/nsPermissionManager.h b/extensions/cookie/nsPermissionManager.h index 1755d891bc2..4bbc0db94bf 100644 --- a/extensions/cookie/nsPermissionManager.h +++ b/extensions/cookie/nsPermissionManager.h @@ -37,12 +37,14 @@ public: { 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) @@ -53,6 +55,7 @@ public: uint32_t mPermission; uint32_t mExpireType; int64_t mExpireTime; + int64_t mModificationTime; uint32_t mNonSessionPermission; uint32_t mNonSessionExpireType; uint32_t mNonSessionExpireTime; @@ -154,7 +157,7 @@ public: // unknown permission... return relevant data return PermissionEntry(-1, aType, nsIPermissionManager::UNKNOWN_ACTION, - nsIPermissionManager::EXPIRE_NEVER, 0); + nsIPermissionManager::EXPIRE_NEVER, 0, 0); } private: @@ -200,6 +203,7 @@ public: int64_t aID, uint32_t aExpireType, int64_t aExpireTime, + int64_t aModificationTime, NotifyOperationType aNotifyOperation, DBOperationType aDBOperation); @@ -260,6 +264,7 @@ private: uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime, + int64_t aModificationTime, uint32_t aAppId, bool aIsInBrowserElement); @@ -299,6 +304,13 @@ private: RemoveExpiredPermissionsForAppEnumerator(PermissionHashKey* entry, void* nonused); + + /** + * This method removes all permissions modified after the specified time. + */ + nsresult + RemoveAllModifiedSince(int64_t aModificationTime); + nsCOMPtr mObserverService; nsCOMPtr mIDNService; diff --git a/extensions/cookie/test/unit/test_permmanager_defaults.js b/extensions/cookie/test/unit/test_permmanager_defaults.js index ff0c454262a..2137f5b8b7a 100644 --- a/extensions/cookie/test/unit/test_permmanager_defaults.js +++ b/extensions/cookie/test/unit/test_permmanager_defaults.js @@ -3,8 +3,15 @@ // 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(); } @@ -28,6 +35,7 @@ add_task(function* do_test() { 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. @@ -92,6 +100,47 @@ add_task(function* do_test() { 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); }); diff --git a/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js b/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js index a8066653de3..b96813b98aa 100644 --- a/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js +++ b/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js @@ -113,13 +113,25 @@ function run_test() { ); } + 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"] diff --git a/extensions/cookie/test/unit/test_permmanager_removesince.js b/extensions/cookie/test/unit/test_permmanager_removesince.js new file mode 100644 index 00000000000..e7d4dadd546 --- /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); +} diff --git a/extensions/cookie/test/unit/xpcshell.ini b/extensions/cookie/test/unit/xpcshell.ini index 070183a2554..26d6d42f42d 100644 --- a/extensions/cookie/test/unit/xpcshell.ini +++ b/extensions/cookie/test/unit/xpcshell.ini @@ -23,6 +23,7 @@ support-files = [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] diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index cbdbd8a872d..ab897d572aa 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -84,8 +84,10 @@ 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) diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index c1b21012799..2641fdb56bc 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -948,8 +948,10 @@ const KTableValue nsCSSProps::kColorKTable[] = { 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, diff --git a/mobile/android/base/db/BrowserDB.java b/mobile/android/base/db/BrowserDB.java index b269734a97e..34b9eea76c7 100644 --- a/mobile/android/base/db/BrowserDB.java +++ b/mobile/android/base/db/BrowserDB.java @@ -200,8 +200,11 @@ public class BrowserDB { 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) { diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 6c88b907685..5272341e4e4 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -58,6 +58,8 @@ 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) { @@ -998,19 +1000,38 @@ public class LocalBrowserDB { 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(); } @@ -1027,10 +1048,66 @@ public class LocalBrowserDB { 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, diff --git a/mobile/android/base/favicons/Favicons.java b/mobile/android/base/favicons/Favicons.java index b2cecca4022..e2d6b857f03 100644 --- a/mobile/android/base/favicons/Favicons.java +++ b/mobile/android/base/favicons/Favicons.java @@ -17,6 +17,7 @@ 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; @@ -53,8 +54,6 @@ public class Favicons { 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; @@ -260,12 +259,15 @@ public class Favicons { } } - 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); } /** diff --git a/mobile/android/base/favicons/LoadFaviconTask.java b/mobile/android/base/favicons/LoadFaviconTask.java index f2470f397ff..1b4b23e4a8b 100644 --- a/mobile/android/base/favicons/LoadFaviconTask.java +++ b/mobile/android/base/favicons/LoadFaviconTask.java @@ -46,7 +46,6 @@ public class LoadFaviconTask { private static final HashMap 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. diff --git a/netwerk/base/public/nsIPermissionManager.idl b/netwerk/base/public/nsIPermissionManager.idl index 62fb0b8eeec..dc0523255a0 100644 --- a/netwerk/base/public/nsIPermissionManager.idl +++ b/netwerk/base/public/nsIPermissionManager.idl @@ -37,7 +37,7 @@ interface nsIDOMWindow; interface nsIPermission; interface nsISimpleEnumerator; -[scriptable, uuid(c9fec678-f194-43c9-96b0-7bd9dbdd6bb0)] +[scriptable, uuid(620d9b61-8997-4d13-aa64-ec03341dd75b)] interface nsIPermissionManager : nsISupports { /** @@ -132,6 +132,11 @@ interface nsIPermissionManager : nsISupports */ 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 diff --git a/testing/mozbase/mozprofile/mozprofile/permissions.py b/testing/mozbase/mozprofile/mozprofile/permissions.py index aaecd5dc59e..126d0f91f99 100644 --- a/testing/mozbase/mozprofile/mozprofile/permissions.py +++ b/testing/mozbase/mozprofile/mozprofile/permissions.py @@ -241,8 +241,12 @@ class Permissions(object): 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: diff --git a/testing/mozbase/mozprofile/tests/permissions.py b/testing/mozbase/mozprofile/tests/permissions.py index 801570aad69..b402991d929 100755 --- a/testing/mozbase/mozprofile/tests/permissions.py +++ b/testing/mozbase/mozprofile/tests/permissions.py @@ -40,7 +40,18 @@ http://127.0.0.1:8888 privileged 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, @@ -59,7 +70,7 @@ http://127.0.0.1:8888 privileged 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() @@ -149,7 +160,7 @@ http://127.0.0.1:8888 privileged 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) @@ -160,6 +171,8 @@ http://127.0.0.1:8888 privileged 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() diff --git a/toolkit/themes/osx/global/button.css b/toolkit/themes/osx/global/button.css index 901aa11181e..a853213aca2 100644 --- a/toolkit/themes/osx/global/button.css +++ b/toolkit/themes/osx/global/button.css @@ -14,6 +14,22 @@ button { 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; diff --git a/widget/LookAndFeel.h b/widget/LookAndFeel.h index 41722561b3e..e0283eaa4b7 100644 --- a/widget/LookAndFeel.h +++ b/widget/LookAndFeel.h @@ -126,10 +126,14 @@ public: // 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 diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm index 58d38869977..f0733b1f378 100644 --- a/widget/cocoa/nsLookAndFeel.mm +++ b/widget/cocoa/nsLookAndFeel.mm @@ -137,6 +137,13 @@ nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor) // 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: diff --git a/widget/tests/test_platform_colors.xul b/widget/tests/test_platform_colors.xul index eeef6a241c4..3e936032891 100644 --- a/widget/tests/test_platform_colors.xul +++ b/widget/tests/test_platform_colors.xul @@ -66,8 +66,10 @@ var colors = { "-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)"], diff --git a/widget/xpwidgets/nsXPLookAndFeel.cpp b/widget/xpwidgets/nsXPLookAndFeel.cpp index c1f146feab4..b804143b113 100644 --- a/widget/xpwidgets/nsXPLookAndFeel.cpp +++ b/widget/xpwidgets/nsXPLookAndFeel.cpp @@ -215,8 +215,10 @@ const char nsXPLookAndFeel::sColorPrefs[][38] = "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",