gecko/toolkit/components/viewconfig/content/config.js
Ehsan Akhgari a95b6edeea Bug 722872 - Part 1: Add nsITransferable::Init(nsILoadContext*), enforce that it's called in debug builds, and add nsIDOMDocument* arguments to nsIClipboardHelper methods; r=roc
This patch does the following:

* It adds nsITransferable::Init(nsILoadContext*).  The load context
  might be null, which means that the transferable is non-private, but
  if it's non-null, we extract the boolean value for the privacy mode
  and store it in the transferable.
* It adds checks in debug builds to make sure that Init is always
  called, in form of fatal assertions.
* It adds nsIDOMDocument* agruments to nsIClipboardHelper methods which
  represent the document that the string is coming from.
  nsIClipboardHelper implementation internally gets the nsILoadContext
  from that and passes it on to the transferable upon creation.  The
  reason that I did this was that nsIClipboardHelper is supposed to be a
  high-level helper, and in most of its call sites, we have easy access
  to a document object.
* It modifies all of the call sites of the above interfaces according to
  this change.
* It adds a GetLoadContext helper to nsIDocument to help with changing
  the call sites.
2012-04-16 22:14:01 -04:00

646 lines
20 KiB
JavaScript

// -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/Services.jsm");
const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
const nsISupportsString = Components.interfaces.nsISupportsString;
const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
const nsIClipboardHelper = Components.interfaces.nsIClipboardHelper;
const nsIAtomService = Components.interfaces.nsIAtomService;
const nsSupportsString_CONTRACTID = "@mozilla.org/supports-string;1";
const nsPrompt_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1";
const nsPrefService_CONTRACTID = "@mozilla.org/preferences-service;1";
const nsClipboardHelper_CONTRACTID = "@mozilla.org/widget/clipboardhelper;1";
const nsAtomService_CONTRACTID = "@mozilla.org/atom-service;1";
const gPrefBranch = Services.prefs;
const gClipboardHelper = Components.classes[nsClipboardHelper_CONTRACTID].getService(nsIClipboardHelper);
const gAtomService = Components.classes[nsAtomService_CONTRACTID].getService(nsIAtomService);
var gLockAtoms = [gAtomService.getAtom("default"), gAtomService.getAtom("user"), gAtomService.getAtom("locked")];
// we get these from a string bundle
var gLockStrs = [];
var gTypeStrs = [];
const PREF_IS_DEFAULT_VALUE = 0;
const PREF_IS_USER_SET = 1;
const PREF_IS_LOCKED = 2;
var gPrefHash = {};
var gPrefArray = [];
var gPrefView = gPrefArray; // share the JS array
var gSortedColumn = "prefCol";
var gSortFunction = null;
var gSortDirection = 1; // 1 is ascending; -1 is descending
var gConfigBundle = null;
var gFilter = null;
var view = {
get rowCount() { return gPrefView.length; },
getCellText : function(index, col) {
if (!(index in gPrefView))
return "";
var value = gPrefView[index][col.id];
switch (col.id) {
case "lockCol":
return gLockStrs[value];
case "typeCol":
return gTypeStrs[value];
default:
return value;
}
},
getRowProperties : function(index, prop) {},
getCellProperties : function(index, col, prop) {
if (index in gPrefView)
prop.AppendElement(gLockAtoms[gPrefView[index].lockCol]);
},
getColumnProperties : function(col, prop) {},
treebox : null,
selection : null,
isContainer : function(index) { return false; },
isContainerOpen : function(index) { return false; },
isContainerEmpty : function(index) { return false; },
isSorted : function() { return true; },
canDrop : function(index, orientation) { return false; },
drop : function(row, orientation) {},
setTree : function(out) { this.treebox = out; },
getParentIndex: function(rowIndex) { return -1; },
hasNextSibling: function(rowIndex, afterIndex) { return false; },
getLevel: function(index) { return 1; },
getImageSrc: function(row, col) { return ""; },
toggleOpenState : function(index) {},
cycleHeader: function(col) {
var index = this.selection.currentIndex;
if (col.id == gSortedColumn) {
gSortDirection = -gSortDirection;
gPrefArray.reverse();
if (gPrefView != gPrefArray)
gPrefView.reverse();
if (index >= 0)
index = gPrefView.length - index - 1;
}
else {
var pref = null;
if (index >= 0)
pref = gPrefView[index];
var old = document.getElementById(gSortedColumn);
old.setAttribute("sortDirection", "");
gPrefArray.sort(gSortFunction = gSortFunctions[col.id]);
if (gPrefView != gPrefArray)
gPrefView.sort(gSortFunction);
gSortedColumn = col.id;
if (pref)
index = getViewIndexOfPref(pref);
}
col.element.setAttribute("sortDirection", gSortDirection > 0 ? "ascending" : "descending");
this.treebox.invalidate();
if (index >= 0) {
this.selection.select(index);
this.treebox.ensureRowIsVisible(index);
}
},
selectionChanged : function() {},
cycleCell: function(row, col) {},
isEditable: function(row, col) {return false; },
isSelectable: function(row, col) {return false; },
setCellValue: function(row, col, value) {},
setCellText: function(row, col, value) {},
performAction: function(action) {},
performActionOnRow: function(action, row) {},
performActionOnCell: function(action, row, col) {},
isSeparator: function(index) {return false; }
};
// find the index in gPrefView of a pref object
// or -1 if it does not exist in the filtered view
function getViewIndexOfPref(pref)
{
var low = -1, high = gPrefView.length;
var index = (low + high) >> 1;
while (index > low) {
var mid = gPrefView[index];
if (mid == pref)
return index;
if (gSortFunction(mid, pref) < 0)
low = index;
else
high = index;
index = (low + high) >> 1;
}
return -1;
}
// find the index in gPrefView where a pref object belongs
function getNearestViewIndexOfPref(pref)
{
var low = -1, high = gPrefView.length;
var index = (low + high) >> 1;
while (index > low) {
if (gSortFunction(gPrefView[index], pref) < 0)
low = index;
else
high = index;
index = (low + high) >> 1;
}
return high;
}
// find the index in gPrefArray of a pref object
function getIndexOfPref(pref)
{
var low = -1, high = gPrefArray.length;
var index = (low + high) >> 1;
while (index > low) {
var mid = gPrefArray[index];
if (mid == pref)
return index;
if (gSortFunction(mid, pref) < 0)
low = index;
else
high = index;
index = (low + high) >> 1;
}
return index;
}
function getNearestIndexOfPref(pref)
{
var low = -1, high = gPrefArray.length;
var index = (low + high) >> 1;
while (index > low) {
if (gSortFunction(gPrefArray[index], pref) < 0)
low = index;
else
high = index;
index = (low + high) >> 1;
}
return high;
}
var gPrefListener =
{
observe: function(subject, topic, prefName)
{
if (topic != "nsPref:changed")
return;
if (/^capability\./.test(prefName)) // avoid displaying "private" preferences
return;
var arrayIndex = gPrefArray.length;
var viewIndex = arrayIndex;
var selectedIndex = view.selection.currentIndex;
var pref;
var updateView = false;
var updateArray = false;
var addedRow = false;
if (prefName in gPrefHash) {
pref = gPrefHash[prefName];
viewIndex = getViewIndexOfPref(pref);
arrayIndex = getIndexOfPref(pref);
fetchPref(prefName, arrayIndex);
// fetchPref replaces the existing pref object
pref = gPrefHash[prefName];
if (viewIndex >= 0) {
// Might need to update the filtered view
gPrefView[viewIndex] = gPrefHash[prefName];
view.treebox.invalidateRow(viewIndex);
}
if (gSortedColumn == "lockCol" || gSortedColumn == "valueCol") {
updateArray = true;
gPrefArray.splice(arrayIndex, 1);
if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
updateView = true;
gPrefView.splice(viewIndex, 1);
}
}
}
else {
fetchPref(prefName, arrayIndex);
pref = gPrefArray.pop();
updateArray = true;
addedRow = true;
if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
updateView = true;
}
}
if (updateArray) {
// Reinsert in the data array
var newIndex = getNearestIndexOfPref(pref);
gPrefArray.splice(newIndex, 0, pref);
if (updateView) {
// View is filtered, reinsert in the view separately
newIndex = getNearestViewIndexOfPref(pref);
gPrefView.splice(newIndex, 0, pref);
}
else if (gFilter) {
// View is filtered, but nothing to update
return;
}
if (addedRow)
view.treebox.rowCountChanged(newIndex, 1);
// Invalidate the changed range in the view
var low = Math.min(viewIndex, newIndex);
var high = Math.max(viewIndex, newIndex);
view.treebox.invalidateRange(low, high);
if (selectedIndex == viewIndex) {
selectedIndex = newIndex;
}
else if (selectedIndex >= low && selectedIndex <= high) {
selectedIndex += (newIndex > viewIndex) ? -1 : 1;
}
if (selectedIndex >= 0) {
view.selection.select(selectedIndex);
if (selectedIndex == newIndex)
view.treebox.ensureRowIsVisible(selectedIndex);
}
}
}
};
function prefObject(prefName, prefIndex)
{
this.prefCol = prefName;
}
prefObject.prototype =
{
lockCol: PREF_IS_DEFAULT_VALUE,
typeCol: nsIPrefBranch.PREF_STRING,
valueCol: ""
};
function fetchPref(prefName, prefIndex)
{
var pref = new prefObject(prefName);
gPrefHash[prefName] = pref;
gPrefArray[prefIndex] = pref;
if (gPrefBranch.prefIsLocked(prefName))
pref.lockCol = PREF_IS_LOCKED;
else if (gPrefBranch.prefHasUserValue(prefName))
pref.lockCol = PREF_IS_USER_SET;
try {
switch (gPrefBranch.getPrefType(prefName)) {
case gPrefBranch.PREF_BOOL:
pref.typeCol = gPrefBranch.PREF_BOOL;
// convert to a string
pref.valueCol = gPrefBranch.getBoolPref(prefName).toString();
break;
case gPrefBranch.PREF_INT:
pref.typeCol = gPrefBranch.PREF_INT;
// convert to a string
pref.valueCol = gPrefBranch.getIntPref(prefName).toString();
break;
default:
case gPrefBranch.PREF_STRING:
pref.valueCol = gPrefBranch.getComplexValue(prefName, nsISupportsString).data;
// Try in case it's a localized string (will throw an exception if not)
if (pref.lockCol == PREF_IS_DEFAULT_VALUE &&
/^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.valueCol))
pref.valueCol = gPrefBranch.getComplexValue(prefName, nsIPrefLocalizedString).data;
break;
}
} catch (e) {
// Also catch obscure cases in which you can't tell in advance
// that the pref exists but has no user or default value...
}
}
function onConfigLoad()
{
// Load strings
gConfigBundle = document.getElementById("configBundle");
gLockStrs[PREF_IS_DEFAULT_VALUE] = gConfigBundle.getString("default");
gLockStrs[PREF_IS_USER_SET] = gConfigBundle.getString("user");
gLockStrs[PREF_IS_LOCKED] = gConfigBundle.getString("locked");
gTypeStrs[nsIPrefBranch.PREF_STRING] = gConfigBundle.getString("string");
gTypeStrs[nsIPrefBranch.PREF_INT] = gConfigBundle.getString("int");
gTypeStrs[nsIPrefBranch.PREF_BOOL] = gConfigBundle.getString("bool");
var showWarning = gPrefBranch.getBoolPref("general.warnOnAboutConfig");
if (showWarning)
document.getElementById("warningButton").focus();
else
ShowPrefs();
}
// Unhide the warning message
function ShowPrefs()
{
var prefArray = gPrefBranch.getChildList("");
prefArray.forEach(function (prefName) {
if (/^capability\./.test(prefName)) // avoid displaying "private" preferences
return;
fetchPref(prefName, gPrefArray.length);
});
var descending = document.getElementsByAttribute("sortDirection", "descending");
if (descending.item(0)) {
gSortedColumn = descending[0].id;
gSortDirection = -1;
}
else {
var ascending = document.getElementsByAttribute("sortDirection", "ascending");
if (ascending.item(0))
gSortedColumn = ascending[0].id;
else
document.getElementById(gSortedColumn).setAttribute("sortDirection", "ascending");
}
gSortFunction = gSortFunctions[gSortedColumn];
gPrefArray.sort(gSortFunction);
gPrefBranch.addObserver("", gPrefListener, false);
var configTree = document.getElementById("configTree");
configTree.view = view;
configTree.controllers.insertControllerAt(0, configController);
document.getElementById("configDeck").setAttribute("selectedIndex", 1);
document.getElementById("configTreeKeyset").removeAttribute("disabled");
if (!document.getElementById("showWarningNextTime").checked)
gPrefBranch.setBoolPref("general.warnOnAboutConfig", false);
// Process about:config?filter=<string>
var textbox = document.getElementById("textbox");
// About URIs don't support query params, so do this manually
var loc = document.location.href;
var matches = /[?&]filter\=([^&]+)/i.exec(loc);
if (matches)
textbox.value = decodeURIComponent(matches[1]);
// Even if we did not set the filter string via the URL query,
// textbox might have been set via some other mechanism
if (textbox.value)
FilterPrefs();
textbox.focus();
}
function onConfigUnload()
{
if (document.getElementById("configDeck").getAttribute("selectedIndex") == 1) {
gPrefBranch.removeObserver("", gPrefListener);
var configTree = document.getElementById("configTree");
configTree.view = null;
configTree.controllers.removeController(configController);
}
}
function FilterPrefs()
{
if (document.getElementById("configDeck").getAttribute("selectedIndex") != 1) {
return false;
}
var substring = document.getElementById("textbox").value;
// Check for "/regex/[i]"
if (substring.charAt(0) == '/') {
var r = substring.match(/^\/(.*)\/(i?)$/);
try {
gFilter = RegExp(r[1], r[2]);
}
catch (e) {
return; // Do nothing on incomplete or bad RegExp
}
}
else if (substring) {
gFilter = RegExp(substring.replace(/([^* \w])/g, "\\$1")
.replace(/^\*+/, "").replace(/\*+/g, ".*"), "i");
} else {
gFilter = null;
}
var prefCol = (view.selection && view.selection.currentIndex < 0) ?
null : gPrefView[view.selection.currentIndex].prefCol;
var oldlen = gPrefView.length;
gPrefView = gPrefArray;
if (gFilter) {
gPrefView = [];
for (var i = 0; i < gPrefArray.length; ++i)
if (gFilter.test(gPrefArray[i].prefCol + ";" + gPrefArray[i].valueCol))
gPrefView.push(gPrefArray[i]);
}
view.treebox.invalidate();
view.treebox.rowCountChanged(oldlen, gPrefView.length - oldlen);
gotoPref(prefCol);
}
function prefColSortFunction(x, y)
{
if (x.prefCol > y.prefCol)
return gSortDirection;
if (x.prefCol < y.prefCol)
return -gSortDirection;
return 0;
}
function lockColSortFunction(x, y)
{
if (x.lockCol != y.lockCol)
return gSortDirection * (y.lockCol - x.lockCol);
return prefColSortFunction(x, y);
}
function typeColSortFunction(x, y)
{
if (x.typeCol != y.typeCol)
return gSortDirection * (y.typeCol - x.typeCol);
return prefColSortFunction(x, y);
}
function valueColSortFunction(x, y)
{
if (x.valueCol > y.valueCol)
return gSortDirection;
if (x.valueCol < y.valueCol)
return -gSortDirection;
return prefColSortFunction(x, y);
}
const gSortFunctions =
{
prefCol: prefColSortFunction,
lockCol: lockColSortFunction,
typeCol: typeColSortFunction,
valueCol: valueColSortFunction
};
const configController = {
supportsCommand: function supportsCommand(command) {
return command == "cmd_copy";
},
isCommandEnabled: function isCommandEnabled(command) {
return view.selection && view.selection.currentIndex >= 0;
},
doCommand: function doCommand(command) {
copyPref();
},
onEvent: function onEvent(event) {
}
}
function updateContextMenu()
{
var lockCol = PREF_IS_LOCKED;
var typeCol = nsIPrefBranch.PREF_STRING;
var valueCol = "";
var copyDisabled = true;
var prefSelected = view.selection.currentIndex >= 0;
if (prefSelected) {
var prefRow = gPrefView[view.selection.currentIndex];
lockCol = prefRow.lockCol;
typeCol = prefRow.typeCol;
valueCol = prefRow.valueCol;
copyDisabled = false;
}
var copyPref = document.getElementById("copyPref");
copyPref.setAttribute("disabled", copyDisabled);
var copyName = document.getElementById("copyName");
copyName.setAttribute("disabled", copyDisabled);
var copyValue = document.getElementById("copyValue");
copyValue.setAttribute("disabled", copyDisabled);
var resetSelected = document.getElementById("resetSelected");
resetSelected.setAttribute("disabled", lockCol != PREF_IS_USER_SET);
var canToggle = typeCol == nsIPrefBranch.PREF_BOOL && valueCol != "";
// indicates that a pref is locked or no pref is selected at all
var isLocked = lockCol == PREF_IS_LOCKED;
var modifySelected = document.getElementById("modifySelected");
modifySelected.setAttribute("disabled", isLocked);
modifySelected.hidden = canToggle;
var toggleSelected = document.getElementById("toggleSelected");
toggleSelected.setAttribute("disabled", isLocked);
toggleSelected.hidden = !canToggle;
}
function copyPref()
{
var pref = gPrefView[view.selection.currentIndex];
gClipboardHelper.copyString(pref.prefCol + ';' + pref.valueCol, document);
}
function copyName()
{
gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].prefCol, document);
}
function copyValue()
{
gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].valueCol, document);
}
function ModifySelected()
{
if (view.selection.currentIndex >= 0)
ModifyPref(gPrefView[view.selection.currentIndex]);
}
function ResetSelected()
{
var entry = gPrefView[view.selection.currentIndex];
gPrefBranch.clearUserPref(entry.prefCol);
}
function NewPref(type)
{
var result = { value: "" };
var dummy = { value: 0 };
if (Services.prompt.prompt(window,
gConfigBundle.getFormattedString("new_title",
[gTypeStrs[type]]),
gConfigBundle.getString("new_prompt"),
result,
null,
dummy)) {
result.value = result.value.trim();
if (!result.value) {
return;
}
var pref;
if (result.value in gPrefHash)
pref = gPrefHash[result.value];
else
pref = { prefCol: result.value, lockCol: PREF_IS_DEFAULT_VALUE, typeCol: type, valueCol: "" };
if (ModifyPref(pref))
setTimeout(gotoPref, 0, result.value);
}
}
function gotoPref(pref)
{
// make sure the pref exists and is displayed in the current view
var index = pref in gPrefHash ? getViewIndexOfPref(gPrefHash[pref]) : -1;
if (index >= 0) {
view.selection.select(index);
view.treebox.ensureRowIsVisible(index);
} else {
view.selection.clearSelection();
view.selection.currentIndex = -1;
}
}
function ModifyPref(entry)
{
if (entry.lockCol == PREF_IS_LOCKED)
return false;
var title = gConfigBundle.getFormattedString("modify_title", [gTypeStrs[entry.typeCol]]);
if (entry.typeCol == nsIPrefBranch.PREF_BOOL) {
var check = { value: entry.valueCol == "false" };
if (!entry.valueCol && !Services.prompt.select(window, title, entry.prefCol, 2, [false, true], check))
return false;
gPrefBranch.setBoolPref(entry.prefCol, check.value);
} else {
var result = { value: entry.valueCol };
var dummy = { value: 0 };
if (!Services.prompt.prompt(window, title, entry.prefCol, result, null, dummy))
return false;
if (entry.typeCol == nsIPrefBranch.PREF_INT) {
// | 0 converts to integer or 0; - 0 to float or NaN.
// Thus, this check should catch all cases.
var val = result.value | 0;
if (val != result.value - 0) {
var err_title = gConfigBundle.getString("nan_title");
var err_text = gConfigBundle.getString("nan_text");
Services.prompt.alert(window, err_title, err_text);
return false;
}
gPrefBranch.setIntPref(entry.prefCol, val);
} else {
var supportsString = Components.classes[nsSupportsString_CONTRACTID].createInstance(nsISupportsString);
supportsString.data = result.value;
gPrefBranch.setComplexValue(entry.prefCol, nsISupportsString, supportsString);
}
}
Services.prefs.savePrefFile(null);
return true;
}