Bug 168411 Move bookmarks transactions into a JS service (adding a bookmark leaks the Add Bookmark dialog) r=sspitzer, sr+a=mconnor

This commit is contained in:
jminta%gmail.com 2006-06-15 21:58:43 +00:00
parent 0db7c90051
commit dca4d1ab35
10 changed files with 645 additions and 374 deletions

View File

@ -21,6 +21,7 @@
#
# Contributor(s):
# Ben Goodger <ben@netscape.com> (Original Author)
# Joey Minta <jminta@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@ -42,6 +43,8 @@ const ADD_BM_DIALOG_FEATURES = "centerscreen,chrome,dialog,resizable,modal";
const ADD_BM_DIALOG_FEATURES = "centerscreen,chrome,dialog,resizable,dependent";
#endif
const kBATCH_LIMIT = 4;
var gNC_NS, gWEB_NS, gRDF_NS, gXUL_NS, gNC_NS_CMD;
// definition of the services frequently used for bookmarks
@ -84,6 +87,7 @@ var kIOIID;
var IOSVC;
var gBmProperties;
var gBkmkTxnSvc;
// should be moved in a separate file
function initServices()
@ -135,6 +139,8 @@ function initServices()
RDF.GetResource(gNC_NS+"Description"),
RDF.GetResource(gNC_NS+"WebPanel"),
RDF.GetResource(gNC_NS+"FeedURL")];
gBkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
.getService(Components.interfaces.nsIBookmarkTransactionManager);
}
function initBMService()
@ -142,8 +148,6 @@ function initBMService()
kBMSVCIID = Components.interfaces.nsIBookmarksService;
BMDS = RDF.GetDataSource("rdf:bookmarks");
BMSVC = BMDS.QueryInterface(kBMSVCIID);
BookmarkTransaction.prototype.RDFC = RDFC;
BookmarkTransaction.prototype.BMDS = BMDS;
}
/**
@ -415,14 +419,14 @@ var BookmarksCommand = {
undoBookmarkTransaction: function ()
{
BMSVC.transactionManager.undoTransaction();
gBkmkTxnSvc.undo();
BookmarksUtils.refreshSearch();
BookmarksUtils.flushDataSource();
},
redoBookmarkTransaction: function ()
{
BMSVC.transactionManager.redoTransaction();
gBkmkTxnSvc.redo();
BookmarksUtils.refreshSearch();
BookmarksUtils.flushDataSource();
},
@ -456,7 +460,7 @@ var BookmarksCommand = {
sBookmarkItem += aSelection.item[i].Value + "\n";
// save the selection property into text string that we will use later in paste function
// and in BookmarkInsertTransaction
// and in INSERT tranasactions
// (if the selection is folder or livemark save all childs property)
var aType = BookmarksUtils.resolveType(aSelection.item[i]);
if (aType == "Livemark") {
@ -900,15 +904,17 @@ var BookmarksCommand = {
this.doBookmarksCommand(rTarget, gNC_NS_CMD+"import", args);
var countAfter = parseInt(BookmarksUtils.getProperty(rTarget, gRDF_NS+"nextVal"));
var transaction = new BookmarkImportTransaction("import");
if (countAfter - countBefore > 1)
gBkmkTxnSvc.startBatch();
for (var index = countBefore; index < countAfter; index++) {
var nChildArc = RDFCU.IndexToOrdinalResource(index);
var rChild = BMDS.GetTarget(rTarget, nChildArc, true);
transaction.item .push(rChild);
transaction.parent .push(rTarget);
transaction.index .push(index);
gBkmkTxnSvc.createAndCommitTxn(gBkmkTxnSvc.IMPORT, "IMPORT", rChild, index,
rTarget, 0, null);
}
BMSVC.transactionManager.doTransaction(transaction);
if (countAfter - countBefore > 1)
gBkmkTxnSvc.endBatch();
BookmarksUtils.flushDataSource();
},
@ -1111,10 +1117,10 @@ var BookmarksController = {
switch(aCommand) {
case "cmd_undo":
case "cmd_bm_undo":
return BMSVC.transactionManager.numberOfUndoItems > 0;
return gBkmkTxnSvc.canUndo();
case "cmd_redo":
case "cmd_bm_redo":
return BMSVC.transactionManager.numberOfRedoItems > 0;
return gBkmkTxnSvc.canRedo();
case "cmd_paste":
if (ptype0 == "Livemark" || (aTarget && !BookmarksUtils.isValidTargetContainer(aTarget.parent)))
return false;
@ -1548,11 +1554,11 @@ var BookmarksUtils = {
removeSelection: function (aAction, aSelection)
{
var transaction = new BookmarkRemoveTransaction(aAction);
transaction.item = [];
transaction.parent = [];
transaction.index = [];
transaction.removedProp = [];
if (aSelection.length > 1)
gBkmkTxnSvc.startBatch();
if (aSelection.length > this.BATCH_LIMIT && aAction != "move")
BMDS.beginUpdateBatch();
for (var i = 0; i < aSelection.length; ++i) {
// try to put back aSelection.parent[i] if it's null, so we can delete after searching
if (aSelection.parent[i] == null)
@ -1560,32 +1566,37 @@ var BookmarksUtils = {
if (aSelection.parent[i]) {
RDFC.Init(BMDS, aSelection.parent[i]);
transaction.item .push(aSelection.item[i]);
transaction.parent.push(aSelection.parent[i]);
transaction.index .push(RDFC.IndexOf(aSelection.item[i]));
// save the selection property into array that uses later in BookmarkRemoveTransaction
// save the selection property into array that is used later in
// when performing the REMOVE transaction
// (if the selection is folder save all childs property)
var propArray;
if (aAction != "move") {
var propArray = [];
propArray.push([aSelection.item[i], null, null, null, null, null, null]);
propArray = [aSelection.item[i], null, null, null, null, null, null];
var aType = BookmarksUtils.resolveType(aSelection.item[i]);
if (aType != "Livemark") {// don't change livemark properties
for (var j = 0; j < gBmProperties.length; ++j) {
var oldValue = BMDS.GetTarget(aSelection.item[i], gBmProperties[j], true);
if (oldValue)
propArray[0][j+1] = oldValue.QueryInterface(kRDFLITIID);
propArray[j+1] = oldValue.QueryInterface(kRDFLITIID);
}
}
if (aType == "Folder" || aType == "Livemark")
BookmarksUtils.getAllChildren(aSelection.item[i], propArray);
transaction.removedProp.push(propArray);
} else {
transaction.removedProp.push(null);
}
var proplength = propArray ? propArray.length : 0;
gBkmkTxnSvc.createAndCommitTxn(gBkmkTxnSvc.REMOVE, aAction,
aSelection.item[i],
RDFC.IndexOf(aSelection.item[i]),
aSelection.parent[i],
proplength, propArray);
}
}
BMSVC.transactionManager.doTransaction(transaction);
if (aSelection.length > 1)
gBkmkTxnSvc.endBatch();
if (aSelection.length > this.BATCH_LIMIT && aAction != "move")
BMDS.beginUpdateBatch();
return true;
},
@ -1645,20 +1656,22 @@ var BookmarksUtils = {
insertSelection: function (aAction, aSelection, aTarget, aTargetIndex)
{
var transaction = new BookmarkInsertTransaction(aAction);
transaction.item = new Array(aSelection.length);
transaction.parent = new Array(aSelection.length);
transaction.index = new Array(aSelection.length);
transaction.removedProp = new Array(aSelection.length);
var item, removedProps;
var index = aTarget.index;
var brokenIndex = aTarget.index;
if (aSelection.length > 1)
gBkmkTxnSvc.startBatch();
if (aSelection.length > this.BATCH_LIMIT && aAction != "move")
BMDS.beginUpdateBatch();
for (var i=0; i<aSelection.length; ++i) {
var rSource = aSelection.item[i];
if (BMSVC.isBookmarkedResource(rSource))
rSource = BMSVC.cloneResource(rSource);
transaction.item [i] = rSource;
transaction.parent[i] = aTarget.parent;
item = rSource;
// we only have aSelection.prop if insertSelection call by paste action we don't use it for move
transaction.removedProp[i] = aSelection.prop ? aSelection.prop[i] : null;
removedProps = aSelection.prop ? aSelection.prop[i] : null;
// Broken Insert Code attempts to always insert items in the
// right place (i.e. after the selected item). However, because
// of RDF Container suckyness, this code gets very confused, due
@ -1667,19 +1680,25 @@ var BookmarksUtils = {
// The -1 is there to handle inserting into the persontal toolbar
// folder via right-click on the PTF.
if (aTarget.index == -1) {
transaction.index[i] = -1;
index = -1;
} else {
#ifdef BROKEN_INSERT_CODE
if (aTargetIndex == -1)
transaction.index [i] = (++index);
index = (++brokenIndex);
else
transaction.index [i] = (index++);
index = (brokenIndex++);
#else
transaction.index [i] = index++;
index = brokenIndex++;
#endif
}
var proplength = removedProps ? removedProps.length : 0;
gBkmkTxnSvc.createAndCommitTxn(gBkmkTxnSvc.INSERT, aAction, item, index,
aTarget.parent, proplength, removedProps);
}
BMSVC.transactionManager.doTransaction(transaction);
if (aSelection.length > 1)
gBkmkTxnSvc.endBatch();
if (aSelection.length > this.BATCH_LIMIT && aAction != "move")
BMDS.endUpdateBatch();
},
moveAndCheckSelection: function (aAction, aSelection, aTarget)
@ -1697,8 +1716,15 @@ var BookmarksUtils = {
moveSelection: function (aAction, aSelection, aTarget)
{
var txn = new BookmarkMoveTransaction(aAction, aSelection, aTarget);
BMSVC.transactionManager.doTransaction(txn);
if (aSelection.length > kBATCH_LIMIT)
BMDS.beginUpdateBatch();
gBkmkTxnSvc.startBatch();
BookmarksUtils.removeSelection("move", aSelection);
BookmarksUtils.insertSelection("move", aSelection, aTarget);
gBkmkTxnSvc.endBatch();
if (aSelection.length > kBATCH_LIMIT)
BMDS.endUpdateBatch();
},
// returns true if this selection should be copied instead of moved,
@ -1918,297 +1944,8 @@ var BookmarksUtils = {
}
}
function BookmarkTransaction()
{
}
BookmarkTransaction.prototype = {
BATCH_LIMIT : 4,
RDFC : null,
BMDS : null,
QueryInterface: function (iid)
{
if (!iid.equals(Components.interfaces.nsITransaction) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
beginUpdateBatch: function()
{
if (this.item.length > this.BATCH_LIMIT) {
this.BMDS.beginUpdateBatch();
}
},
endUpdateBatch: function()
{
if (this.item.length > this.BATCH_LIMIT) {
this.BMDS.endUpdateBatch();
}
},
merge : function (aTxn) {return false},
getHelperForLanguage: function (aCount) {return null},
getInterfaces : function (aCount) {return null},
canCreateWrapper : function (aIID) {return "AllAccess"}
}
function BookmarkInsertTransaction (aAction)
{
this.wrappedJSObject = this;
this.type = "insert";
this.action = aAction;
this.item = null;
this.parent = null;
this.index = null;
this.removedProp = null;
this.Properties = gBmProperties;
// move container declaration to her so it can be recognize if
// undoTransaction is call after the BM manager is close and reopen.
this.container = Components.classes[kRDFCContractID].createInstance(kRDFCIID);
}
BookmarkInsertTransaction.prototype =
{
__proto__: BookmarkTransaction.prototype,
isTransient: false,
doTransaction: function ()
{
this.beginUpdateBatch();
for (var i=0; i<this.item.length; ++i) {
this.RDFC.Init(this.BMDS, this.parent[i]);
// if the index is -1, we use appendElement, and then update the
// index so that undoTransaction can still function
if (this.index[i] == -1) {
this.RDFC.AppendElement(this.item[i]);
this.index[i] = this.RDFC.GetCount();
} else {
#ifdef BROKEN_INSERT_CODE
try {
this.RDFC.InsertElementAt(this.item[i], this.index[i], true);
} catch (e if e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) {
// if this failed, then we assume that we really want to append,
// because things are out of whack until we renumber.
this.RDFC.AppendElement(this.item[i]);
// and then fix up the index so undo works
this.index[i] = this.RDFC.GetCount();
}
#else
this.RDFC.InsertElementAt(this.item[i], this.index[i], true);
#endif
}
// insert back all the properties
var props = this.removedProp[i];
if (props) {
for (var k = 0; k < props.length; ++k) {
for (var j = 0; j < this.Properties.length; ++j) {
var oldValue = this.BMDS.GetTarget(props[k][0], this.Properties[j], true);
// must check, if paste call after copy the oldvalue didn't remove.
if (!oldValue) {
var newValue = props[k][j+1];
if (newValue)
this.BMDS.Assert(props[k][0], this.Properties[j], newValue, true);
}
}
}
}
}
this.endUpdateBatch();
},
undoTransaction: function ()
{
this.beginUpdateBatch();
// XXXvarga Can't use |RDFC| here because it's being "reused" elsewhere.
for (var i = this.item.length-1; i >= 0; i--) {
this.container.Init(this.BMDS, this.parent[i]);
// remove all properties befor we remove the element so nsLocalSearchService
// don't return deleted element in Search
var props = this.removedProp[i];
if (props){
for (var k = 0; k < props.length; ++k) {
for (var j = 0; j < this.Properties.length; ++j) {
var oldValue = props[k][j+1];
if (oldValue)
this.BMDS.Unassert(props[k][0], this.Properties[j], oldValue);
}
}
}
this.container.RemoveElementAt(this.index[i], true);
}
this.endUpdateBatch();
},
redoTransaction: function ()
{
this.doTransaction();
}
}
function BookmarkRemoveTransaction (aAction)
{
this.wrappedJSObject = this;
this.type = "remove";
this.action = aAction;
this.item = null;
this.parent = null;
this.index = null;
this.removedProp = null;
this.Properties = gBmProperties;
}
BookmarkRemoveTransaction.prototype =
{
__proto__: BookmarkTransaction.prototype,
isTransient: false,
doTransaction: function ()
{
this.beginUpdateBatch();
for (var i=0; i<this.item.length; ++i) {
this.RDFC.Init(this.BMDS, this.parent[i]);
// remove all properties befor we remove the element so nsLocalSearchService
// don't return deleted element in Search
var props = this.removedProp[i];
if (props) {
for (var k = 0; k < props.length; ++k) {
for (var j = 0; j < this.Properties.length; ++j) {
var oldValue = props[k][j+1];
if (oldValue)
this.BMDS.Unassert(props[k][0], this.Properties[j], oldValue);
}
}
}
this.RDFC.RemoveElementAt(this.index[i], false);
}
this.endUpdateBatch();
},
undoTransaction: function ()
{
this.beginUpdateBatch();
for (var i=this.item.length-1; i>=0; i--) {
this.RDFC.Init(this.BMDS, this.parent[i]);
this.RDFC.InsertElementAt(this.item[i], this.index[i], false);
// insert back all the properties
var props = this.removedProp[i];
if (props) {
for (var k = 0; k < props.length; ++k) {
for (var j = 0; j < this.Properties.length; ++j) {
var newValue = props[k][j+1];
if (newValue)
this.BMDS.Assert(props[k][0], this.Properties[j], newValue, true);
}
}
}
}
this.endUpdateBatch();
},
redoTransaction: function ()
{
this.doTransaction();
}
}
function BookmarkMoveTransaction (aAction, aSelection, aTarget)
{
this.wrappedJSObject = this;
this.type = "move";
this.action = aAction;
this.selection = aSelection;
this.target = aTarget;
}
BookmarkMoveTransaction.prototype =
{
__proto__: BookmarkTransaction.prototype,
isTransient: false,
beginUpdateBatch: function()
{
if (this.selection.length > this.BATCH_LIMIT) {
this.BMDS.beginUpdateBatch();
}
},
endUpdateBatch: function()
{
if (this.selection.length > this.BATCH_LIMIT) {
this.BMDS.endUpdateBatch();
}
},
doTransaction: function ()
{
this.beginUpdateBatch();
BookmarksUtils.removeSelection("move", this.selection);
BookmarksUtils.insertSelection("move", this.selection, this.target);
this.endUpdateBatch();
},
undoTransaction: function () {},
redoTransaction: function () {}
}
function BookmarkImportTransaction (aAction)
{
this.wrappedJSObject = this;
this.type = "import";
this.action = aAction;
this.item = [];
this.parent = [];
this.index = [];
}
BookmarkImportTransaction.prototype =
{
__proto__: BookmarkTransaction.prototype,
isTransient: false,
doTransaction: function ()
{
},
undoTransaction: function ()
{
this.beginUpdateBatch();
for (var i=this.item.length-1; i>=0; i--) {
this.RDFC.Init(this.BMDS, this.parent[i]);
this.RDFC.RemoveElementAt(this.index[i], true);
}
this.endUpdateBatch();
},
redoTransaction: function ()
{
this.beginUpdateBatch();
for (var i=0; i<this.item.length; ++i) {
this.RDFC.Init(this.BMDS, this.parent[i]);
this.RDFC.InsertElementAt(this.item[i], this.index[i], true);
}
this.endUpdateBatch();
}
}
var BookmarkEditMenuTxnListener =
{
didDo: function (aTxmgr, aTxn)
{
this.updateMenuItem(aTxmgr, aTxn);
@ -2226,7 +1963,9 @@ var BookmarkEditMenuTxnListener =
didMerge : function (aTxmgr, aTxn) {},
didBeginBatch : function (aTxmgr, aTxn) {},
didEndBatch : function (aTxmgr, aTxn) {},
didEndBatch : function (aTxmgr, aTxn) {
this.updateMenuItem(aTxmgr, aTxn);
},
willDo : function (aTxmgr, aTxn) {},
willUndo : function (aTxmgr, aTxn) {},
willRedo : function (aTxmgr, aTxn) {},
@ -2234,34 +1973,43 @@ var BookmarkEditMenuTxnListener =
willBeginBatch : function (aTxmgr, aTxn) {},
willEndBatch : function (aTxmgr, aTxn) {},
updateMenuItem: function (aTxmgr, aTxn) {
if (aTxn) {
aTxn = aTxn.wrappedJSObject;
if ((aTxn.type == "remove" || aTxn.type == "insert") && aTxn.action == "move")
return;
}
var node, transactionNumber, transactionList, transactionLabel, action;
updateMenuItem: function bkmkMenuListenerUpdate(aTxmgr, aTxn) {
var node, transactionNumber, transactionList, transactionLabel, action, item;
node = document.getElementById("cmd_undo");
transactionNumber = aTxmgr.numberOfUndoItems;
dump("N UNDO: "+transactionNumber+"\n")
dump("N UNDO: "+transactionNumber+"\n");
if (transactionNumber == 0) {
transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_undo");
} else {
transactionList = aTxmgr.getUndoList();
action = transactionList.getItem(transactionNumber-1).wrappedJSObject.action;
transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_"+action+"_undo")
if (!transactionList.itemIsBatch(transactionNumber-1)) {
item = transactionList.getItem(transactionNumber-1);
action = item.wrappedJSObject.action;
} else {
var childList = transactionList.getChildListForItem(transactionNumber-1);
item = childList.getItem(0);
action = item.wrappedJSObject.action;
}
transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_"+action+"_undo");
}
node.setAttribute("label", transactionLabel);
node = document.getElementById("cmd_redo");
transactionNumber = aTxmgr.numberOfRedoItems;
dump("N REDO: "+transactionNumber+"\n")
dump("N REDO: "+transactionNumber+"\n");
if (transactionNumber == 0) {
transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_redo");
} else {
transactionList = aTxmgr.getRedoList();
action = transactionList.getItem(transactionNumber-1).wrappedJSObject.action;
transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_"+action+"_redo")
if (!transactionList.itemIsBatch(transactionNumber-1)) {
item = transactionList.getItem(transactionNumber-1);
action = item.wrappedJSObject.action;
} else {
var childList = transactionList.getChildListForItem(transactionNumber-1);
item = childList.getItem(0);
action = item.wrappedJSObject.action;
}
transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_"+action+"_redo");
}
node.setAttribute("label", transactionLabel);
}

View File

@ -85,13 +85,17 @@ function Startup()
document.getElementById("CommandUpdate_Bookmarks").setAttribute("commandupdater","true");
bookmarksView.focus();
BMSVC.transactionManager.AddListener(BookmarkEditMenuTxnListener);
var bkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
.getService(Components.interfaces.nsIBookmarkTransactionManager);
bkmkTxnSvc.transactionManager.AddListener(BookmarkEditMenuTxnListener);
}
function Shutdown()
{
BMSVC.transactionManager.RemoveListener(BookmarkEditMenuTxnListener);
var bkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
.getService(Components.interfaces.nsIBookmarkTransactionManager);
bkmkTxnSvc.transactionManager.RemoveListener(BookmarkEditMenuTxnListener);
// Store current window position and size in window attributes (for persistence).
var win = document.getElementById("bookmark-window");
win.setAttribute("x", screenX);

View File

@ -25,6 +25,7 @@
# Ben Goodger <ben@netscape.com> (Original Author)
# Blake Ross <blaker@nemtscape.com>
# Pierre Chanial <chanial@noos.fr> (v 2.0)
# Joey Minta <jminta@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@ -504,11 +505,30 @@
<parameter name="aTxn"/>
<parameter name="aDo"/>
<body><![CDATA[
aTxn = aTxn.wrappedJSObject;
var type = aTxn.type;
// Skip transactions that aggregates nested "insert" or "remove" transactions.
if ((type == "insert") && aDo || (type == "remove") && !aDo)
this._itemToBeToggled = aTxn.item;
if (aTxn) {
aTxn = aTxn.wrappedJSObject;
var type = aTxn.type;
// Skip transactions that aggregates nested "insert" or "remove" transactions.
if ((type == "insert") && aDo || (type == "remove") && !aDo)
this._itemToBeToggled = [aTxn.item];
} else {
var txnList;
var bkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
.getService(Components.interfaces.nsIBookmarkTransactionManager);
var txmgr = bkmkTxnSvc.transactionManager;
if (this.bookmarkTreeTransactionListener.mLastTxnWasDo) {
txnList = txmgr.getUndoList();
} else {
txnList = txmgr.getRedoList();
}
var items =[];
var childList = txnList.getChildListForItem(txnList.numItems-1);
for (var i=0; i<childList.numItems; i++) {
items.push(childList.getItem(i).wrappedJSObject.item);
}
this._itemToBeToggled = items;
}
},
]]></body>
</method>
@ -813,21 +833,28 @@
mOuter: this,
mLastTxnWasDo: null,
willDo: function (aTxmgr, aTxn) {},
didDo : function (aTxmgr, aTxn) {
this.mLastTxnWasDo = true;
this.mOuter.preUpdateTreeSelection(aTxn, true);
},
willUndo: function (aTxmgr, aTxn) {},
didUndo : function (aTxmgr, aTxn) {
this.mLastTxnWasDo = false;
this.mOuter.preUpdateTreeSelection(aTxn, false);
},
willRedo: function (aTxmgr, aTxn) {},
didRedo : function (aTxmgr, aTxn) {
this.mLastTxnWasDo = true;
this.mOuter.preUpdateTreeSelection(aTxn, true);
},
didMerge : function (aTxmgr, aTxn) {},
didBeginBatch : function (aTxmgr, aTxn) {},
didEndBatch : function (aTxmgr, aTxn) {},
didEndBatch : function (aTxmgr, aTxn) {
this.mOuter.preUpdateTreeSelection(aTxn, this.mLastTxnWasDo);
},
willMerge : function (aTxmgr, aTxn) {},
willBeginBatch : function (aTxmgr, aTxn) {},
willEndBatch : function (aTxmgr, aTxn) {}
@ -925,10 +952,14 @@
<implementation>
<constructor>
// Adding the transaction listener
BMSVC.transactionManager.AddListener(this.bookmarkTreeTransactionListener);
var bkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
.getService(Components.interfaces.nsIBookmarkTransactionManager);
bkmkTxnSvc.transactionManager.AddListener(this.bookmarkTreeTransactionListener);
</constructor>
<destructor>
BMSVC.transactionManager.RemoveListener(this.bookmarkTreeTransactionListener);
var bkmkTxnSvc = Components.classes["@mozilla.org/bookmarks/transactionmanager;1"]
.getService(Components.interfaces.nsIBookmarkTransactionManager);
bkmkTxnSvc.transactionManager.RemoveListener(this.bookmarkTreeTransactionListener);
</destructor>
<field name="clickCount">2</field>
</implementation>

View File

@ -45,7 +45,8 @@ include $(DEPTH)/config/autoconf.mk
MODULE = bookmarks
XPIDL_MODULE = bookmarks
XPIDLSRCS = nsIBookmarksService.idl
XPIDLSRCS = nsIBookmarksService.idl \
nsIBookmarkTransactionManager.idl
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,130 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Bookmarks transaction code.
*
* The Initial Developer of the Original Code is
* Joey Minta <jminta@gmail.com>
*
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsIRDFResource;
interface nsIRDFNode;
interface nsITransactionManager;
/**
* nsIBookmarkTransactionService is a service designed to handle
* nsITransactions that correspond to changes in bookmarks. It is here as a
* service so that we can keep the transactions around without holding onto
* the whole global js scope+window.
*/
[scriptable, uuid(f6305e79-1760-4991-ab4d-a42db60f0e67)]
interface nsIBookmarkTransactionManager : nsISupports
{
/**
* Performs a new transaction according to the specified parameters
*
* @param aType the type of transaction being performed. Must be one
* of the three constants defined below
* @param aAction the action to be performed. Expected values are:
* import, insert, move, and remove (see bookmarks.js)
* @param aItem the rdf node the transaction is being performed on
* @param aIndex the index of the item in its RDF Container
* @param aParent the rdf-parent of aItem, that is, the folder it
* should be inserted into.
* @param aRemovedProps properties removed from the item in question
*
*/
void createAndCommitTxn(in unsigned long aType,
in aString aAction,
in nsIRDFNode aItem,
in long aIndex,
in nsIRDFResource aParent,
in unsigned long aPropCount,
[array, size_is(aPropCount)] in nsIRDFNode aRemovedProps);
/**
* Constants corresponding to the 3 different types of transactions possible
* Note that moving bookmarks is a combination of REMOVE+INSERT
*/
const unsigned long IMPORT = 0;
const unsigned long INSERT = 1;
const unsigned long REMOVE = 2;
/**
* Signals the transaction manager that a series of transactions are going to
* be performed, but that, for the purposes of undo and redo, they should all
* be regarded as a single transaction. That is, a single undo() call will
* undo all of the transactions created and committed between startBatch() and
* endBatch(). See also nsITransactionManager::beginBatch
*
* @note if startBatch() is called multiple times. The batch will not end
* endBatch() has been called the same number of times.
*/
void startBatch();
/**
* Ends the batch transaction in process, subject to the note above about
* multiple, successive calls of startBatch(). See also
* nsITransactionManager::endBatch
*/
void endBatch();
/**
* Undo the last transaction in the transaction manager's stack
*/
void undo();
/**
* Returns true if it is possible to undo a transaction at this time
*/
boolean canUndo();
/**
* Redo the last transaction
*/
void redo();
/**
* Returns true if it is possible to redo a transaction at this time
*/
boolean canRedo();
/**
* A reference to the transaction manager for bookmarks
*/
readonly attribute nsITransactionManager transactionManager;
};

View File

@ -46,7 +46,6 @@
interface nsIArray;
interface nsIRDFResource;
interface nsITransactionManager;
[scriptable, uuid(ccc48346-429d-4931-94dc-beb1afcea292)]
interface nsIBookmarksService : nsISupports
@ -108,8 +107,6 @@ interface nsIBookmarksService : nsISupports
string resolveKeyword(in wstring aName, out wstring aPostData);
readonly attribute nsITransactionManager transactionManager;
//XXXpch: to be removed.
void addBookmarkImmediately(in wstring aURI, in wstring aTitle, in long bmType, in wstring docCharset);
boolean isBookmarked(in string aURL);

View File

@ -73,6 +73,7 @@ CPPSRCS = nsBookmarksService.cpp \
$(NULL)
#EXTRA_COMPONENTS = nsBookmarkProtocolHandler.js
EXTRA_COMPONENTS = nsBookmarkTransactionManager.js
# we don't want the shared lib, but we want to force the creation of a
# static lib.

View File

@ -0,0 +1,373 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is bookmark transaction code.
*
* The Initial Developer of the Original Code is
* Joey Minta <jminta@gmail.com>
*
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
function bookmarkTransactionManager() {
this.wrappedJSObject = this;
this.mTransactionManager = Components.classes["@mozilla.org/transactionmanager;1"]
.createInstance(Components.interfaces.nsITransactionManager);
this.mBatchCount = 0;
this.classInfo = {
getInterfaces: function (count) {
var ifaces = [
Components.interfaces.nsISupports,
Components.interfaces.nsIClassInfo
];
count.value = ifaces.length;
return ifaces;
},
getHelperForLanguage: function (language) {
return null;
},
contractID: "@mozilla.org/bookmarks/transactionManager;1",
classDescription: "Booksmarks Transaction Manager",
classID: Components.ID("{62d2f7fb-acd2-4876-aa2d-b607de9329ff}"),
implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
flags: 0
};
// Define our transactions
function bkmkTxn() {
this.item = null;
this.parent = null;
this.index = null;
this.removedProp = null;
};
bkmkTxn.prototype = {
BMDS: null,
QueryInterface: function bkmkTxnQI(iid) {
if (!iid.equals(Components.interfaces.nsITransaction) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
merge : function (aTxn) {return false},
getHelperForLanguage: function (aCount) {return null},
getInterfaces : function (aCount) {return null},
canCreateWrapper : function (aIID) {return "AllAccess"},
mAssertProperties: function bkmkTxnAssertProps(aProps) {
if (!aProps) {
return;
}
for each (var prop in aProps) {
for (var i = 0; i < this.Properties.length; i++) {
var oldValue = this.BMDS.GetTarget(this.item, this.Properties[i], true);
// must check, if paste call after copy the oldvalue didn't remove.
if (!oldValue) {
var newValue = aProps[i+1];
if (newValue) {
this.BMDS.Assert(this.item,
this.Properties[i],
newValue, true);
}
} else {
this.removedProp[i+1] = oldValue;
}
}
}
},
mUnassertProperties: function bkmkTxnUnassertProps(aProps) {
if (!aProps) {
return;
}
for each (var prop in aProps) {
for (var i = 0; i < this.Properties.length; i++) {
var oldValue = aProps[i+1];
if (oldValue) {
this.BMDS.Unassert(this.item, this.Properties[i], oldValue);
}
}
}
}
};
bkmkTxn.prototype.RDFC =
Components.classes["@mozilla.org/rdf/container;1"]
.createInstance(Components.interfaces.nsIRDFContainer);
var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"]
.getService(Components.interfaces.nsIRDFService);
bkmkTxn.prototype.BMDS = rdfService.GetDataSource("rdf:bookmarks");
bkmkTxn.prototype.Properties =
[rdfService.GetResource("http://home.netscape.com/NC-rdf#Name"),
rdfService.GetResource("http://home.netscape.com/NC-rdf#URL"),
rdfService.GetResource("http://home.netscape.com/NC-rdf#ShortcutURL"),
rdfService.GetResource("http://home.netscape.com/NC-rdf#Description"),
rdfService.GetResource("http://home.netscape.com/NC-rdf#WebPanel"),
rdfService.GetResource("http://home.netscape.com/NC-rdf#FeedURL")];
function bkmkInsertTxn(aAction) {
this.type = "insert";
// move container declaration to here so it can be recognized if
// undoTransaction is call after the BM manager is close and reopen.
this.container = Components.classes["@mozilla.org/rdf/container;1"]
.createInstance(Components.interfaces.nsIRDFContainer);
}
bkmkInsertTxn.prototype = {
__proto__: bkmkTxn.prototype,
isTransient: false,
doTransaction: function bkmkInsertDoTxn() {
this.RDFC.Init(this.BMDS, this.parent);
// if the index is -1, we use appendElement, and then update the
// index so that undoTransaction can still function
if (this.index == -1) {
this.RDFC.AppendElement(this.item);
this.index = this.RDFC.GetCount();
} else {
/*XXX- broken insert code, see bug 264571
try {
this.RDFC.InsertElementAt(this.item, this.index, true);
} catch (e if e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) {
// if this failed, then we assume that we really want to append,
// because things are out of whack until we renumber.
this.RDFC.AppendElement(this.item);
// and then fix up the index so undo works
this.index = this.RDFC.GetCount();
}
*/
this.RDFC.InsertElementAt(this.item, this.index, true);
}
// insert back all the properties
this.mAssertProperties(this.removedProp);
},
undoTransaction: function bkmkInsertUndoTxn() {
// XXXvarga Can't use |RDFC| here because it's being "reused" elsewhere.
this.container.Init(this.BMDS, this.parent);
// remove all properties befor we remove the element so
// nsLocalSearchService doesn't return deleted element in Search
this.mUnassertProperties(this.removedProp);
this.container.RemoveElementAt(this.index, true);
},
redoTransaction: function bkmkInsertRedoTxn() {
this.doTransaction();
}
};
function bkmkRemoveTxn() {
this.type = "remove";
}
bkmkRemoveTxn.prototype = {
__proto__: bkmkTxn.prototype,
isTransient: false,
doTransaction: function bkmkRemoveDoTxn() {
this.RDFC.Init(this.BMDS, this.parent);
// remove all properties befor we remove the element so
// nsLocalSearchService doesn't return deleted element in Search
this.mUnassertProperties(this.removedProp);
this.RDFC.RemoveElementAt(this.index, false);
},
undoTransaction: function bkmkRemoveUndoTxn() {
this.RDFC.Init(this.BMDS, this.parent);
this.RDFC.InsertElementAt(this.item, this.index, false);
// insert back all the properties
this.mAssertProperties(this.removedProp);
},
redoTransaction: function () {
this.doTransaction();
}
}
function bkmkImportTxn(aAction) {
this.type = "import";
this.action = aAction;
}
bkmkImportTxn.prototype = {
__proto__: bkmkTxn.prototype,
isTransient: false,
doTransaction: function bkmkImportDoTxn() {},
undoTransaction: function mkmkImportUndoTxn() {
this.RDFC.Init(this.BMDS, this.parent);
this.RDFC.RemoveElementAt(this.index, true);
},
redoTransaction: function bkmkImportredoTxn() {
this.RDFC.Init(this.BMDS, this.parent);
this.RDFC.InsertElementAt(this.item, this.index, true);
}
};
this.BookmarkRemoveTransaction = bkmkRemoveTxn;
this.BookmarkInsertTransaction = bkmkInsertTxn;
this.BookmarkImportTransaction = bkmkImportTxn;
}
bookmarkTransactionManager.prototype.QueryInterface = function bkTxnMgrQI(aIID) {
if (aIID.equals(Components.interfaces.nsISupports) ||
aIID.equals(Components.interfaces.nsIBookmarkTransactionManager)) {
return this;
}
if (aIID.equals(Components.interfaces.nsIClassInfo)) {
return this.classInfo;
}
throw NS_ERROR_NO_INTERFACE;
}
bookmarkTransactionManager.prototype.createAndCommitTxn =
function bkmkTxnMgrCandC(aType, aAction, aItem, aIndex, aParent, aPropCount, aRemovedProps) {
var txn;
var nsIBookmarkTransactionManager = Components.interfaces.nsIBookmarkTransactionManager;
switch (aType) {
case nsIBookmarkTransactionManager.IMPORT:
txn = new this.BookmarkImportTransaction(aAction);
break;
case nsIBookmarkTransactionManager.INSERT:
txn = new this.BookmarkInsertTransaction(aAction);
break;
case nsIBookmarkTransactionManager.REMOVE:
txn = new this.BookmarkRemoveTransaction(aAction);
break;
default:
Components.utils.reportError("Unknown bookmark transaction type:"+aType);
throw NS_ERROR_FAILURE;
}
txn.item = aItem;
txn.parent = aParent;
txn.index = aIndex;
txn.removedProp = aRemovedProps;
txn.action = aAction;
txn.wrappedJSObject = txn;
this.mTransactionManager.doTransaction(txn);
}
bookmarkTransactionManager.prototype.startBatch = function bkmkTxnMgrUndo() {
if (this.mBatchCount == 0) {
this.mTransactionManager.beginBatch();
}
this.mBatchCount++;
}
bookmarkTransactionManager.prototype.endBatch = function bkmkTxnMgrUndo() {
this.mBatchCount--;
if (this.mBatchCount == 0) {
this.mTransactionManager.endBatch();
}
}
bookmarkTransactionManager.prototype.undo = function bkmkTxnMgrUndo() {
this.mTransactionManager.undoTransaction();
}
bookmarkTransactionManager.prototype.redo = function bkmkTxnMgrRedo() {
this.mTransactionManager.redoTransaction();
}
bookmarkTransactionManager.prototype.canUndo = function bkmkTxnMgrCanUndo() {
return this.mTransactionManager.numberOfUndoItems > 0;
}
bookmarkTransactionManager.prototype.canRedo = function bkmkTxnMgrCanRedo() {
return this.mTransactionManager.numberOfRedoItems > 0;
}
bookmarkTransactionManager.prototype.__defineGetter__("transactionManager",
function bkmkTxnMgrGetter() { return this.mTransactionManager; });
/****
**** module registration
****/
const kFactory = {
createInstance: function (outer, iid) {
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return (new bookmarkTransactionManager()).QueryInterface(iid);
}
};
var bkmkTxnMgrModule = {
mCID: Components.ID("{8be133d0-681d-4f0b-972b-6a68e41afb62}"),
mContractID: "@mozilla.org/bookmarks/transactionmanager;1",
registerSelf: function (compMgr, fileSpec, location, type) {
compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
compMgr.registerFactoryLocation(this.mCID,
"Bookmark Transaction Manager",
this.mContractID,
fileSpec,
location,
type);
},
getClassObject: function (compMgr, cid, iid) {
if (!cid.equals(this.mCID))
throw Components.results.NS_ERROR_NO_INTERFACE;
if (!iid.equals(Components.interfaces.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
return kFactory;
},
canUnload: function(compMgr) {
return true;
}
};
function NSGetModule(compMgr, fileSpec) {
return bkmkTxnMgrModule;
}

View File

@ -1698,9 +1698,6 @@ nsBookmarksService::Init()
nsICache::STREAM_BASED, getter_AddRefs(mCacheSession));
}
mTransactionManager = do_CreateInstance(NS_TRANSACTIONMANAGER_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
/* create a URL for the string resource file */
nsCOMPtr<nsIURI> uri;
if (NS_SUCCEEDED(rv = mNetService->NewURI(bookmark_properties, nsnull, nsnull,
@ -3531,15 +3528,6 @@ nsBookmarksService::ResolveKeyword(const PRUnichar *aUserInput, PRUnichar** aPos
return NS_RDF_NO_VALUE;
}
NS_IMETHODIMP
nsBookmarksService::GetTransactionManager(nsITransactionManager** aTransactionManager)
{
NS_ENSURE_ARG_POINTER(aTransactionManager);
NS_ADDREF(*aTransactionManager = mTransactionManager);
return NS_OK;
}
NS_IMETHODIMP
nsBookmarksService::GetBookmarksToolbarFolder(nsIRDFResource** aResult)
{

View File

@ -56,7 +56,6 @@
#include "nsIIOService.h"
#include "nsICacheService.h"
#include "nsICacheSession.h"
#include "nsITransactionManager.h"
#include "nsIPrefBranch.h"
class nsIOutputStream;
@ -84,7 +83,6 @@ protected:
nsCOMPtr<nsIIOService> mNetService;
nsCOMPtr<nsICacheService> mCacheService;
nsCOMPtr<nsICacheSession> mCacheSession;
nsCOMPtr<nsITransactionManager> mTransactionManager;
PRUint32 htmlSize;
PRInt32 mUpdateBatchNest;