fix for bug #378558: Organize Bookmarks - paste doesn't work after cut
includes fixes for: bug #341953: unable to paste links into the organize bookmarks dialog bug #383044: Drag-and-drop bookmark creation should support meta-data bug #385381: Paste doesn't work in the bookmarks Manager during copy if item is modified between copy and paste r=sspitzer
This commit is contained in:
Родитель
df2d51d512
Коммит
ef9e63b3df
|
@ -114,8 +114,7 @@ PlacesController.prototype = {
|
|||
case "cmd_copy":
|
||||
return this._view.hasSelection;
|
||||
case "cmd_paste":
|
||||
return this._canInsert() &&
|
||||
this._hasClipboardData() && this._canPaste();
|
||||
return this._canInsert() && this._canPaste();
|
||||
case "cmd_selectAll":
|
||||
if (this._view.selType != "single") {
|
||||
var result = this._view.getResult();
|
||||
|
@ -375,31 +374,6 @@ PlacesController.prototype = {
|
|||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether or not the clipboard contains data that the active
|
||||
* view can support in a paste operation.
|
||||
* @returns true if the clipboard contains data compatible with the active
|
||||
* view, false otherwise.
|
||||
*/
|
||||
_hasClipboardData: function PC__hasClipboardData() {
|
||||
var types = this._view.peerDropTypes;
|
||||
var flavors =
|
||||
Cc["@mozilla.org/supports-array;1"].
|
||||
createInstance(Ci.nsISupportsArray);
|
||||
for (var i = 0; i < types.length; ++i) {
|
||||
var cstring =
|
||||
Cc["@mozilla.org/supports-cstring;1"].
|
||||
createInstance(Ci.nsISupportsCString);
|
||||
cstring.data = types[i];
|
||||
flavors.AppendElement(cstring);
|
||||
}
|
||||
|
||||
var clipboard =
|
||||
Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
||||
return clipboard.hasDataMatchingFlavors(flavors,
|
||||
Ci.nsIClipboard.kGlobalClipboard);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether or not nodes can be inserted relative to the selection.
|
||||
*/
|
||||
|
@ -426,22 +400,41 @@ PlacesController.prototype = {
|
|||
* Looks at the data on the clipboard to see if it is paste-able.
|
||||
* Paste-able data is:
|
||||
* - in a format that the view can receive
|
||||
* - not a set of URIs that is entirely already present in the view,
|
||||
* since we can only have one instance of a URI per container.
|
||||
* @returns true if the data is paste-able, false if the clipboard data
|
||||
* cannot be pasted
|
||||
*/
|
||||
_canPaste: function PC__canPaste() {
|
||||
var types = this._view.peerDropTypes;
|
||||
var flavors =
|
||||
Cc["@mozilla.org/supports-array;1"].
|
||||
createInstance(Ci.nsISupportsArray);
|
||||
for (var i = 0; i < types.length; ++i) {
|
||||
var cstring =
|
||||
Cc["@mozilla.org/supports-cstring;1"].
|
||||
createInstance(Ci.nsISupportsCString);
|
||||
cstring.data = types[i];
|
||||
flavors.AppendElement(cstring);
|
||||
}
|
||||
|
||||
var clipboard =
|
||||
Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
||||
var hasClipboardData = clipboard.hasDataMatchingFlavors(flavors,
|
||||
Ci.nsIClipboard.kGlobalClipboard);
|
||||
if (!hasClipboardData)
|
||||
return false;
|
||||
|
||||
// XXX todo
|
||||
// see bug #387007 for an idea on how to make this more efficient
|
||||
// right now, we are pulling data off the clipboard and using
|
||||
// unwrapNodes() to verify it.
|
||||
|
||||
var xferable =
|
||||
Cc["@mozilla.org/widget/transferable;1"].
|
||||
createInstance(Ci.nsITransferable);
|
||||
xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
|
||||
xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR);
|
||||
xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_PLACE);
|
||||
xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_URL);
|
||||
|
||||
var clipboard = Cc["@mozilla.org/widget/clipboard;1"].
|
||||
getService(Ci.nsIClipboard);
|
||||
for (var j = 0; j < types.length; ++j) {
|
||||
xferable.addDataFlavor(types[j]);
|
||||
}
|
||||
clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
|
||||
|
||||
try {
|
||||
|
@ -452,14 +445,12 @@ PlacesController.prototype = {
|
|||
if (this._view.peerDropTypes.indexOf(type.value) == -1)
|
||||
return false;
|
||||
|
||||
// unwrapNodes will throw if the data blob is malformed.
|
||||
var nodes = PlacesUtils.unwrapNodes(data, type.value);
|
||||
|
||||
var ip = this._view.insertionPoint;
|
||||
return ip != null;
|
||||
// unwrapNodes() will throw if the data blob is malformed.
|
||||
var unwrappedNodes = PlacesUtils.unwrapNodes(data, type.value);
|
||||
return this._view.insertionPoint != null;
|
||||
}
|
||||
catch (e) {
|
||||
// Unwrap nodes failed, possibly because a field that should have
|
||||
// unwrapeNodes() failed, possibly because a field that should have
|
||||
// contained a URI did not actually contain something that is
|
||||
// parse-able as a URI.
|
||||
return false;
|
||||
|
@ -1039,19 +1030,16 @@ PlacesController.prototype = {
|
|||
|
||||
|
||||
/**
|
||||
* Creates a set of transactions for the removal of a range of items. A range is
|
||||
* an array of adjacent nodes in a view.
|
||||
* @param range
|
||||
* An array of nodes to remove. Should all be adjacent.
|
||||
* @param transactions
|
||||
* An array of transactions.
|
||||
* Walk the list of folders we're removing in this delete operation, and
|
||||
* see if the selected node specified is already implicitly being removed
|
||||
* because it is a child of that folder.
|
||||
* @param node
|
||||
* Node to check for containment.
|
||||
* @param pastFolders
|
||||
* List of folders the calling function has already traversed
|
||||
* @returns true if the node should be skipped, false otherwise.
|
||||
*/
|
||||
_removeRange: function PC__removeRange(range, transactions) {
|
||||
NS_ASSERT(transactions instanceof Array, "Must pass a transactions array");
|
||||
var index = PlacesUtils.getIndexOfNode(range[0]);
|
||||
|
||||
var removedFolders = [];
|
||||
|
||||
_shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) {
|
||||
/**
|
||||
* Determines if a node is contained by another node within a resultset.
|
||||
* @param node
|
||||
|
@ -1069,26 +1057,31 @@ PlacesController.prototype = {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the list of folders we're removing in this delete operation, and
|
||||
* see if the selected node specified is already implicitly being removed
|
||||
* because it is a child of that folder.
|
||||
* @param node
|
||||
* Node to check for containment.
|
||||
* @returns true if the node should be skipped, false otherwise.
|
||||
*/
|
||||
function shouldSkipNode(node) {
|
||||
for (var j = 0; j < removedFolders.length; ++j) {
|
||||
if (isContainedBy(node, removedFolders[j]))
|
||||
|
||||
for (var j = 0; j < pastFolders.length; ++j) {
|
||||
if (isContainedBy(node, pastFolders[j]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a set of transactions for the removal of a range of items.
|
||||
* A range is an array of adjacent nodes in a view.
|
||||
* @param range
|
||||
* An array of nodes to remove. Should all be adjacent.
|
||||
* @param transactions
|
||||
* An array of transactions.
|
||||
*/
|
||||
_removeRange: function PC__removeRange(range, transactions) {
|
||||
NS_ASSERT(transactions instanceof Array, "Must pass a transactions array");
|
||||
var index = PlacesUtils.getIndexOfNode(range[0]);
|
||||
|
||||
var removedFolders = [];
|
||||
|
||||
for (var i = 0; i < range.length; ++i) {
|
||||
var node = range[i];
|
||||
if (shouldSkipNode(node))
|
||||
if (this._shouldSkipNode(node, removedFolders))
|
||||
continue;
|
||||
|
||||
if (PlacesUtils.nodeIsFolder(node)) {
|
||||
|
@ -1247,35 +1240,36 @@ PlacesController.prototype = {
|
|||
Cc["@mozilla.org/widget/transferable;1"].
|
||||
createInstance(Ci.nsITransferable);
|
||||
var foundFolder = false, foundLink = false;
|
||||
var pcString = psString = placeString = mozURLString = htmlString = unicodeString = "";
|
||||
var copiedFolders = [];
|
||||
var placeString = mozURLString = htmlString = unicodeString = "";
|
||||
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
var node = nodes[i];
|
||||
if (this._shouldSkipNode(node, copiedFolders))
|
||||
continue;
|
||||
if (PlacesUtils.nodeIsFolder(node))
|
||||
copiedFolders.push(node);
|
||||
|
||||
function generateChunk(type, overrideURI) {
|
||||
var suffix = i < (nodes.length - 1) ? NEWLINE : "";
|
||||
return PlacesUtils.wrapNode(node, type, overrideURI) + suffix;
|
||||
var suffix = i < (nodes.length - 1) ? "\n" : "";
|
||||
var uri = overrideURI;
|
||||
|
||||
if (PlacesUtils.nodeIsLivemarkContainer(node))
|
||||
uri = PlacesUtils.livemarks.getFeedURI(node.itemId).spec
|
||||
|
||||
mozURLString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_X_MOZ_URL,
|
||||
uri) + suffix);
|
||||
unicodeString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_UNICODE,
|
||||
uri) + suffix);
|
||||
htmlString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_HTML,
|
||||
uri) + suffix);
|
||||
|
||||
var placeSuffix = i < (nodes.length - 1) ? "," : "";
|
||||
return PlacesUtils.wrapNode(node, type, overrideURI) + placeSuffix;
|
||||
}
|
||||
|
||||
function generateURIChunks(overrideURI) {
|
||||
mozURLString += generateChunk(PlacesUtils.TYPE_X_MOZ_URL, overrideURI);
|
||||
htmlString += generateChunk(PlacesUtils.TYPE_HTML, overrideURI);
|
||||
unicodeString += generateChunk(PlacesUtils.TYPE_UNICODE, overrideURI);
|
||||
}
|
||||
|
||||
if (PlacesUtils.nodeIsFolder(node) || PlacesUtils.nodeIsQuery(node)) {
|
||||
pcString += generateChunk(PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
|
||||
|
||||
// Also copy the feed URI for live-bookmark folders
|
||||
if (PlacesUtils.nodeIsLivemarkContainer(node)) {
|
||||
var uri = PlacesUtils.livemarks.getFeedURI(node.itemId);
|
||||
generateURIChunks(uri.spec);
|
||||
}
|
||||
}
|
||||
else if (PlacesUtils.nodeIsSeparator(node))
|
||||
psString += generateChunk(PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR);
|
||||
else {
|
||||
// all items wrapped as TYPE_X_MOZ_PLACE
|
||||
placeString += generateChunk(PlacesUtils.TYPE_X_MOZ_PLACE);
|
||||
generateURIChunks();
|
||||
}
|
||||
}
|
||||
|
||||
function addData(type, data) {
|
||||
|
@ -1283,11 +1277,7 @@ PlacesController.prototype = {
|
|||
xferable.setTransferData(type, PlacesUtils._wrapString(data), data.length * 2);
|
||||
}
|
||||
// This order is _important_! It controls how this and other applications
|
||||
// select data to be inserted based on type.
|
||||
if (pcString)
|
||||
addData(PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, pcString);
|
||||
if (psString)
|
||||
addData(PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, psString);
|
||||
// select data to be inserted based on type.
|
||||
if (placeString)
|
||||
addData(PlacesUtils.TYPE_X_MOZ_PLACE, placeString);
|
||||
if (mozURLString)
|
||||
|
@ -1297,8 +1287,7 @@ PlacesController.prototype = {
|
|||
if (htmlString)
|
||||
addData(PlacesUtils.TYPE_HTML, htmlString);
|
||||
|
||||
if (pcString || psString || placeString || unicodeString || htmlString ||
|
||||
mozURLString) {
|
||||
if (placeString || unicodeString || htmlString || mozURLString) {
|
||||
var clipboard =
|
||||
Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
|
||||
clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
|
||||
|
@ -1363,9 +1352,14 @@ PlacesController.prototype = {
|
|||
data = data.value.QueryInterface(Ci.nsISupportsString).data;
|
||||
var items = PlacesUtils.unwrapNodes(data, type.value);
|
||||
var transactions = [];
|
||||
var index = ip.index;
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
// adjusted to make sure that items are given the correct index -
|
||||
// transactions insert differently if index == -1
|
||||
if (ip.index > -1)
|
||||
index = ip.index + i;
|
||||
transactions.push(PlacesUtils.makeTransaction(items[i], type.value,
|
||||
ip.itemId, ip.index,
|
||||
ip.itemId, index,
|
||||
true));
|
||||
}
|
||||
return transactions;
|
||||
|
@ -1382,12 +1376,9 @@ PlacesController.prototype = {
|
|||
|
||||
// Get transactions to paste any folders, separators or links that might
|
||||
// be on the clipboard, aggregate them and execute them.
|
||||
var transactions =
|
||||
[].concat(getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER]),
|
||||
getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR]),
|
||||
getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE,
|
||||
PlacesUtils.TYPE_X_MOZ_URL,
|
||||
PlacesUtils.TYPE_UNICODE]));
|
||||
var transactions = getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE,
|
||||
PlacesUtils.TYPE_X_MOZ_URL,
|
||||
PlacesUtils.TYPE_UNICODE]);
|
||||
var txn = new PlacesAggregateTransaction("Paste", transactions);
|
||||
PlacesUtils.tm.doTransaction(txn);
|
||||
}
|
||||
|
@ -1612,7 +1603,7 @@ PlacesAggregateTransaction.prototype = {
|
|||
},
|
||||
|
||||
commit: function PAT_commit(aUndo) {
|
||||
for (var i = this._transactions.length - 1; i >= 0; --i) {
|
||||
for (var i = 0; i < this._transactions.length; ++i) {
|
||||
var txn = this._transactions[i];
|
||||
if (this.container > -1)
|
||||
txn.container = this.container;
|
||||
|
|
|
@ -453,41 +453,167 @@ var PlacesUtils = {
|
|||
* @returns A string serialization of the node
|
||||
*/
|
||||
wrapNode: function PU_wrapNode(aNode, aType, aOverrideURI) {
|
||||
var self = this;
|
||||
|
||||
// when wrapping a node, we want all the items, even if the original
|
||||
// query options are excluding them.
|
||||
// this can happen when copying from the left hand pane of the bookmarks
|
||||
// organizer
|
||||
function convertNode(cNode) {
|
||||
try {
|
||||
if (self.nodeIsFolder(cNode) && cNode.queryOptions.excludeItems)
|
||||
return self.getFolderContents(cNode.itemId, false, true).root;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
return cNode;
|
||||
}
|
||||
|
||||
switch (aType) {
|
||||
case this.TYPE_X_MOZ_PLACE_CONTAINER:
|
||||
case this.TYPE_X_MOZ_PLACE:
|
||||
case this.TYPE_X_MOZ_PLACE_SEPARATOR:
|
||||
// Data is encoded like this:
|
||||
// bookmarks folder: <itemId>\n<>\n<parentId>\n<indexInParent>
|
||||
// uri: 0\n<uri>\n<parentId>\n<indexInParent>
|
||||
// bookmark: <itemId>\n<uri>\n<parentId>\n<indexInParent>
|
||||
// separator: <itemId>\n<>\n<parentId>\n<indexInParent>
|
||||
var wrapped = "";
|
||||
if (aNode.itemId != -1) //
|
||||
wrapped += aNode.itemId + NEWLINE;
|
||||
else
|
||||
wrapped += "0" + NEWLINE;
|
||||
|
||||
if (this.nodeIsURI(aNode) || this.nodeIsQuery(aNode))
|
||||
wrapped += aNode.uri + NEWLINE;
|
||||
else
|
||||
wrapped += NEWLINE;
|
||||
|
||||
if (this.nodeIsFolder(aNode.parent))
|
||||
wrapped += aNode.parent.itemId + NEWLINE;
|
||||
else
|
||||
wrapped += "0" + NEWLINE;
|
||||
|
||||
wrapped += this.getIndexOfNode(aNode);
|
||||
return wrapped;
|
||||
case this.TYPE_X_MOZ_URL:
|
||||
return (aOverrideURI || aNode.uri) + NEWLINE + aNode.title;
|
||||
case this.TYPE_HTML:
|
||||
return "<A HREF=\"" + (aOverrideURI || aNode.uri) + "\">" +
|
||||
aNode.title + "</A>";
|
||||
case this.TYPE_X_MOZ_PLACE:
|
||||
case this.TYPE_X_MOZ_PLACE_SEPARATOR:
|
||||
case this.TYPE_X_MOZ_PLACE_CONTAINER:
|
||||
function gatherDataPlace(bNode) {
|
||||
var nodeId = 0;
|
||||
if (bNode.itemId != -1)
|
||||
nodeId = bNode.itemId;
|
||||
var nodeUri = bNode.uri
|
||||
var nodeTitle = bNode.title;
|
||||
var nodeParentId = 0;
|
||||
if (bNode.parent && self.nodeIsFolder(bNode.parent))
|
||||
nodeParentId = bNode.parent.itemId;
|
||||
var nodeIndex = self.getIndexOfNode(bNode);
|
||||
var nodeKeyword = self.bookmarks.getKeywordForBookmark(bNode.itemId);
|
||||
var nodeAnnos = self.getAnnotationsForItem(bNode.itemId);
|
||||
var nodeType = "";
|
||||
if (self.nodeIsContainer(bNode))
|
||||
nodeType = self.TYPE_X_MOZ_PLACE_CONTAINER;
|
||||
else if (self.nodeIsURI(bNode)) // a bookmark or a history visit
|
||||
nodeType = self.TYPE_X_MOZ_PLACE;
|
||||
else if (self.nodeIsSeparator(bNode))
|
||||
nodeType = self.TYPE_X_MOZ_PLACE_SEPARATOR;
|
||||
|
||||
var node = { id: nodeId,
|
||||
uri: nodeUri,
|
||||
title: nodeTitle,
|
||||
parent: nodeParentId,
|
||||
index: nodeIndex,
|
||||
keyword: nodeKeyword,
|
||||
annos: nodeAnnos,
|
||||
type: nodeType };
|
||||
|
||||
// Recurse down children if the node is a folder
|
||||
if (self.nodeIsContainer(bNode)) {
|
||||
if (self.nodeIsLivemarkContainer(bNode)) {
|
||||
// just save the livemark info, reinstantiate on other end
|
||||
var feedURI = self.livemarks.getFeedURI(bNode.itemId).spec;
|
||||
var siteURI = self.livemarks.getSiteURI(bNode.itemId).spec;
|
||||
node.uri = { feed: feedURI,
|
||||
site: siteURI };
|
||||
}
|
||||
else { // bookmark folders + history containers
|
||||
var wasOpen = bNode.containerOpen;
|
||||
if (!wasOpen)
|
||||
bNode.containerOpen = true;
|
||||
var childNodes = [];
|
||||
var cc = bNode.childCount;
|
||||
for (var i = 0; i < cc; ++i) {
|
||||
var childObj = gatherDataPlace(bNode.getChild(i));
|
||||
if (childObj != null)
|
||||
childNodes.push(childObj);
|
||||
}
|
||||
var parent = node;
|
||||
node = { folder: parent,
|
||||
children: childNodes,
|
||||
type: self.TYPE_X_MOZ_PLACE_CONTAINER };
|
||||
bNode.containerOpen = wasOpen;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
return this.toJSONString(gatherDataPlace(convertNode(aNode)));
|
||||
|
||||
case this.TYPE_X_MOZ_URL:
|
||||
function gatherDataUrl(bNode) {
|
||||
if (self.nodeIsLivemarkContainer(bNode)) {
|
||||
var siteURI = self.livemarks.getSiteURI(bNode.itemId).spec;
|
||||
return siteURI + "\n" + bNode.title;
|
||||
}
|
||||
else if (self.nodeIsURI(bNode))
|
||||
return (aOverrideURI || bNode.uri) + "\n" + bNode.title;
|
||||
else // ignore containers and separators - items without valid URIs
|
||||
return "";
|
||||
}
|
||||
return gatherDataUrl(convertNode(aNode));
|
||||
|
||||
case this.TYPE_HTML:
|
||||
function gatherDataHtml(bNode) {
|
||||
function htmlEscape(s) {
|
||||
s = s.replace(/&/g, "&");
|
||||
s = s.replace(/>/g, ">");
|
||||
s = s.replace(/</g, "<");
|
||||
s = s.replace(/"/g, """);
|
||||
s = s.replace(/'/g, "'");
|
||||
return s;
|
||||
}
|
||||
// escape out potential HTML in the title
|
||||
var escapedTitle = htmlEscape(bNode.title);
|
||||
if (self.nodeIsLivemarkContainer(bNode)) {
|
||||
var siteURI = self.livemarks.getSiteURI(bNode.itemId).spec;
|
||||
return "<A HREF=\"" + siteURI + "\">" + escapedTitle + "</A>\n";
|
||||
}
|
||||
else if (self.nodeIsContainer(bNode)) {
|
||||
var wasOpen = bNode.containerOpen;
|
||||
if (!wasOpen)
|
||||
bNode.containerOpen = true;
|
||||
|
||||
var childString = "<DL><DT>" + escapedTitle + "</DT>\n";
|
||||
var cc = bNode.childCount;
|
||||
for (var i = 0; i < cc; ++i)
|
||||
childString += "<DD>\n"
|
||||
+ gatherDataHtml(bNode.getChild(i))
|
||||
+ "</DD>\n";
|
||||
bNode.containerOpen = wasOpen;
|
||||
return childString + "</DL>\n";
|
||||
}
|
||||
else if (self.nodeIsURI(bNode))
|
||||
return "<A HREF=\"" + bNode.uri + "\">" + escapedTitle + "</A>\n";
|
||||
else if (self.nodeIsSeparator(bNode))
|
||||
return "<HR>\n";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
return gatherDataHtml(convertNode(aNode));
|
||||
}
|
||||
// case this.TYPE_UNICODE:
|
||||
return (aOverrideURI || aNode.uri);
|
||||
function gatherDataText(bNode) {
|
||||
if (self.nodeIsLivemarkContainer(bNode)) {
|
||||
return self.livemarks.getSiteURI(bNode.itemId).spec;
|
||||
}
|
||||
else if (self.nodeIsContainer(bNode)) {
|
||||
var wasOpen = bNode.containerOpen;
|
||||
if (!wasOpen)
|
||||
bNode.containerOpen = true;
|
||||
|
||||
var childString = bNode.title + "\n";
|
||||
var cc = bNode.childCount;
|
||||
for (var i = 0; i < cc; ++i) {
|
||||
var child = bNode.getChild(i);
|
||||
var suffix = i < (cc - 1) ? "\n" : "";
|
||||
childString += gatherDataText(child) + suffix;
|
||||
}
|
||||
bNode.containerOpen = wasOpen;
|
||||
return childString;
|
||||
}
|
||||
else if (self.nodeIsURI(bNode))
|
||||
return (aOverrideURI || bNode.uri);
|
||||
else if (self.nodeIsSeparator(bNode))
|
||||
return "--------------------";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
return gatherDataText(convertNode(aNode));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -501,9 +627,10 @@ var PlacesUtils = {
|
|||
* The index within the container the item is copied to
|
||||
* @returns A nsITransaction object that performs the copy.
|
||||
*/
|
||||
_getURIItemCopyTransaction: function (aURI, aContainer, aIndex) {
|
||||
var title = this.history.getPageTitle(aURI);
|
||||
return new PlacesCreateItemTransaction(aURI, aContainer, aIndex, title);
|
||||
_getURIItemCopyTransaction: function (aData, aContainer, aIndex) {
|
||||
var itemURL = this._uri(aData.uri);
|
||||
return new PlacesCreateItemTransaction(itemURL, aContainer,
|
||||
aIndex, aData.title);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -521,19 +648,19 @@ var PlacesUtils = {
|
|||
* @returns A nsITransaction object that performs the copy.
|
||||
*/
|
||||
_getBookmarkItemCopyTransaction:
|
||||
function PU__getBookmarkItemCopyTransaction(aId, aContainer, aIndex,
|
||||
function PU__getBookmarkItemCopyTransaction(aData, aContainer, aIndex,
|
||||
aExcludeAnnotations) {
|
||||
var bookmarks = this.bookmarks;
|
||||
var itemURL = bookmarks.getBookmarkURI(aId);
|
||||
var itemTitle = bookmarks.getItemTitle(aId);
|
||||
var keyword = bookmarks.getKeywordForBookmark(aId);
|
||||
var annos = this.getAnnotationsForItem(aId);
|
||||
var itemURL = this._uri(aData.uri);
|
||||
var itemTitle = aData.title;
|
||||
var keyword = aData.keyword;
|
||||
var annos = aData.annos;
|
||||
if (aExcludeAnnotations) {
|
||||
annos =
|
||||
annos.filter(function(aValue, aIndex, aArray) {
|
||||
return aExcludeAnnotations.indexOf(aValue.name) == -1;
|
||||
});
|
||||
}
|
||||
|
||||
var createTxn =
|
||||
new PlacesCreateItemTransaction(itemURL, aContainer, aIndex, itemTitle,
|
||||
keyword, annos);
|
||||
|
@ -542,9 +669,10 @@ var PlacesUtils = {
|
|||
|
||||
/**
|
||||
* Gets a transaction for copying (recursively nesting to include children)
|
||||
* a folder and its contents from one folder to another.
|
||||
* a folder (or container) and its contents from one folder to another.
|
||||
*
|
||||
* @param aData
|
||||
* Unwrapped dropped folder data
|
||||
* Unwrapped dropped folder data - Obj containing folder and children
|
||||
* @param aContainer
|
||||
* The container we are copying into
|
||||
* @param aIndex
|
||||
|
@ -554,33 +682,39 @@ var PlacesUtils = {
|
|||
_getFolderCopyTransaction:
|
||||
function PU__getFolderCopyTransaction(aData, aContainer, aIndex) {
|
||||
var self = this;
|
||||
function getChildItemsTransactions(aFolderId) {
|
||||
function getChildItemsTransactions(aChildren) {
|
||||
var childItemsTransactions = [];
|
||||
var children = self.getFolderContents(aFolderId, false, false);
|
||||
var cc = children.childCount;
|
||||
var cc = aChildren.length;
|
||||
var index = aIndex;
|
||||
for (var i = 0; i < cc; ++i) {
|
||||
var txn = null;
|
||||
var node = children.getChild(i);
|
||||
if (self.nodeIsFolder(node)) {
|
||||
var nodeFolderId = node.itemId;
|
||||
var title = self.bookmarks.getItemTitle(nodeFolderId);
|
||||
var annos = self.getAnnotationsForItem(nodeFolderId);
|
||||
var folderItemsTransactions =
|
||||
getChildItemsTransactions(nodeFolderId);
|
||||
txn = new PlacesCreateFolderTransaction(title, -1, aIndex, annos,
|
||||
folderItemsTransactions);
|
||||
var node = aChildren[i];
|
||||
|
||||
// adjusted to make sure that items are given the correct index -
|
||||
// transactions insert differently if index == -1
|
||||
if (aIndex > -1)
|
||||
index = aIndex + i;
|
||||
|
||||
if (node.type == self.TYPE_X_MOZ_PLACE_CONTAINER) {
|
||||
if (node.folder) {
|
||||
var title = node.folder.title;
|
||||
var annos = node.folder.annos;
|
||||
var folderItemsTransactions =
|
||||
getChildItemsTransactions(node.children);
|
||||
txn = new PlacesCreateFolderTransaction(title, -1, index, annos,
|
||||
folderItemsTransactions);
|
||||
}
|
||||
else { // node is a livemark
|
||||
var feedURI = self._uri(node.uri.feed);
|
||||
var siteURI = self._uri(node.uri.site);
|
||||
txn = new PlacesCreateLivemarkTransaction(feedURI, siteURI,
|
||||
node.title, aContainer, index, node.annos);
|
||||
}
|
||||
}
|
||||
else if (self.nodeIsBookmark(node)) {
|
||||
txn = self._getBookmarkItemCopyTransaction(node.itemId, -1,
|
||||
aIndex);
|
||||
}
|
||||
else if (self.nodeIsURI(node) || self.nodeIsQuery(node)) {
|
||||
// XXXmano: can this ^ ever happen?
|
||||
txn = self._getURIItemCopyTransaction(self._uri(node.uri), -1,
|
||||
aIndex);
|
||||
}
|
||||
else if (self.nodeIsSeparator(node))
|
||||
txn = new PlacesCreateSeparatorTransaction(-1, aIndex);
|
||||
else if (node.type == self.TYPE_X_MOZ_PLACE_SEPARATOR)
|
||||
txn = new PlacesCreateSeparatorTransaction(-1, index);
|
||||
else if (node.type == self.TYPE_X_MOZ_PLACE)
|
||||
txn = self._getBookmarkItemCopyTransaction(node, -1, index);
|
||||
|
||||
NS_ASSERT(txn, "Unexpected item under a bookmarks folder");
|
||||
if (txn)
|
||||
|
@ -588,12 +722,13 @@ var PlacesUtils = {
|
|||
}
|
||||
return childItemsTransactions;
|
||||
}
|
||||
|
||||
var title = aData.folder.title;
|
||||
var annos = aData.folder.annos;
|
||||
|
||||
var title = this.bookmarks.getItemTitle(aData.id);
|
||||
var annos = this.getAnnotationsForItem(aData.id);
|
||||
var createTxn =
|
||||
new PlacesCreateFolderTransaction(title, aContainer, aIndex, annos,
|
||||
getChildItemsTransactions(aData.id));
|
||||
getChildItemsTransactions(aData.children));
|
||||
return createTxn;
|
||||
},
|
||||
|
||||
|
@ -607,41 +742,42 @@ var PlacesUtils = {
|
|||
* @returns An array of objects representing each item contained by the source.
|
||||
*/
|
||||
unwrapNodes: function PU_unwrapNodes(blob, type) {
|
||||
// We use \n here because the transferable system converts \r\n to \n
|
||||
var parts = blob.split("\n");
|
||||
// We split on "\n" because the transferable system converts "\r\n" to "\n"
|
||||
var nodes = [];
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
var data = { };
|
||||
switch (type) {
|
||||
case this.TYPE_X_MOZ_PLACE_CONTAINER:
|
||||
switch(type) {
|
||||
case this.TYPE_X_MOZ_PLACE:
|
||||
case this.TYPE_X_MOZ_PLACE_SEPARATOR:
|
||||
// Data in these types has 4 parts, so if there are less than 4 parts
|
||||
// remaining, the data blob is malformed and we should stop.
|
||||
if (i > (parts.length - 4))
|
||||
break;
|
||||
nodes.push({ id: parseInt(parts[i++]),
|
||||
uri: parts[i] ? this._uri(parts[i]) : null,
|
||||
parent: parseInt(parts[++i]),
|
||||
index: parseInt(parts[++i]) });
|
||||
case this.TYPE_X_MOZ_PLACE_CONTAINER:
|
||||
nodes = this.parseJSON("[" + blob + "]");
|
||||
break;
|
||||
case this.TYPE_X_MOZ_URL:
|
||||
// See above.
|
||||
if (i > (parts.length - 2))
|
||||
var parts = blob.split("\n");
|
||||
// data in this type has 2 parts per entry, so if there are fewer
|
||||
// than 2 parts left, the blob is malformed and we should stop
|
||||
if (parts.length % 2)
|
||||
break;
|
||||
nodes.push({ uri: this._uri(parts[i++]),
|
||||
title: parts[i] ? parts[i] : parts[i-1] });
|
||||
for (var i = 0; i < parts.length; i=i+2) {
|
||||
var uriString = parts[i];
|
||||
var titleString = parts[i+1];
|
||||
// note: this._uri() will throw if uriString is not a valid URI
|
||||
if (this._uri(uriString)) {
|
||||
nodes.push({ uri: uriString,
|
||||
title: titleString ? titleString : uriString });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case this.TYPE_UNICODE:
|
||||
// See above.
|
||||
if (i > (parts.length - 1))
|
||||
break;
|
||||
nodes.push({ uri: this._uri(parts[i]) });
|
||||
var parts = blob.split("\n");
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var uriString = parts[i];
|
||||
// note: this._uri() will throw if uriString is not a valid URI
|
||||
if (uriString != "" && this._uri(uriString))
|
||||
nodes.push({ uri: uriString, title: uriString });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG("Cannot unwrap data of type " + type);
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
},
|
||||
|
@ -664,23 +800,30 @@ var PlacesUtils = {
|
|||
*/
|
||||
makeTransaction: function PU_makeTransaction(data, type, container,
|
||||
index, copy) {
|
||||
switch (type) {
|
||||
switch (data.type) {
|
||||
case this.TYPE_X_MOZ_PLACE_CONTAINER:
|
||||
if (data.id > 0) {
|
||||
if (data.folder) {
|
||||
// Place is a folder.
|
||||
if (copy)
|
||||
return this._getFolderCopyTransaction(data, container, index);
|
||||
}
|
||||
else if (copy) {
|
||||
// Place is a Livemark Container, should be reinstantiated
|
||||
var feedURI = this._uri(data.uri.feed);
|
||||
var siteURI = this._uri(data.uri.site);
|
||||
return new PlacesCreateLivemarkTransaction(feedURI, siteURI,
|
||||
data.title, container, index, data.annos);
|
||||
}
|
||||
break;
|
||||
case this.TYPE_X_MOZ_PLACE:
|
||||
if (data.id <= 0)
|
||||
return this._getURIItemCopyTransaction(data.uri, container, index);
|
||||
return this._getURIItemCopyTransaction(data, container, index);
|
||||
|
||||
if (copy) {
|
||||
// Copying a child of a live-bookmark by itself should result
|
||||
// as a new normal bookmark item (bug 376731)
|
||||
var copyBookmarkAnno =
|
||||
this._getBookmarkItemCopyTransaction(data.id, container, index,
|
||||
this._getBookmarkItemCopyTransaction(data, container, index,
|
||||
["livemark/bookmarkFeedURI"]);
|
||||
return copyBookmarkAnno;
|
||||
}
|
||||
|
@ -692,20 +835,24 @@ var PlacesUtils = {
|
|||
return new PlacesCreateSeparatorTransaction(container, index);
|
||||
}
|
||||
break;
|
||||
case this.TYPE_X_MOZ_URL:
|
||||
case this.TYPE_UNICODE:
|
||||
var title = type == this.TYPE_X_MOZ_URL ? data.title : data.uri.spec;
|
||||
var createTxn =
|
||||
new PlacesCreateItemTransaction(data.uri, container, index, title);
|
||||
return createTxn;
|
||||
default:
|
||||
return null;
|
||||
if (type == this.TYPE_X_MOZ_URL || type == this.TYPE_UNICODE) {
|
||||
var title = (type == this.TYPE_X_MOZ_URL) ? data.title : data.uri;
|
||||
var url = this._uri(data.uri);
|
||||
var createTxn =
|
||||
new PlacesCreateItemTransaction(url, container, index, title);
|
||||
return createTxn;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (data.id <= 0)
|
||||
return null;
|
||||
|
||||
// Move the item otherwise
|
||||
return new PlacesMoveItemTransaction(data.id, container, index);
|
||||
var id = data.folder ? data.folder.id : data.id;
|
||||
return new PlacesMoveItemTransaction(id, container, index);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1323,6 +1470,113 @@ var PlacesUtils = {
|
|||
return annos.getPageAnnotation(aURI, POST_DATA_ANNO);
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a JavaScript object into a JSON string
|
||||
* (see http://www.json.org/ for the full grammar).
|
||||
*
|
||||
* The inverse operation consists of eval("(" + JSON_string + ")");
|
||||
* and should be provably safe.
|
||||
*
|
||||
* @param aJSObject is the object to be converted
|
||||
* @return the object's JSON representation
|
||||
*/
|
||||
toJSONString: function PU_toJSONString(aJSObject) {
|
||||
// these characters have a special escape notation
|
||||
const charMap = { "\b": "\\b", "\t": "\\t", "\n": "\\n", "\f": "\\f",
|
||||
"\r": "\\r", '"': '\\"', "\\": "\\\\" };
|
||||
// we use a single string builder for efficiency reasons
|
||||
var parts = [];
|
||||
|
||||
// this recursive function walks through all objects and appends their
|
||||
// JSON representation to the string builder
|
||||
function jsonIfy(aObj) {
|
||||
if (typeof aObj == "boolean") {
|
||||
parts.push(aObj ? "true" : "false");
|
||||
}
|
||||
else if (typeof aObj == "number" && isFinite(aObj)) {
|
||||
// there is no representation for infinite numbers or for NaN!
|
||||
parts.push(aObj.toString());
|
||||
}
|
||||
else if (typeof aObj == "string") {
|
||||
aObj = aObj.replace(/[\\"\x00-\x1F\u0080-\uFFFF]/g, function($0) {
|
||||
// use the special escape notation if one exists, otherwise
|
||||
// produce a general unicode escape sequence
|
||||
return charMap[$0] ||
|
||||
"\\u" + ("0000" + $0.charCodeAt(0).toString(16)).slice(-4);
|
||||
});
|
||||
parts.push('"' + aObj + '"');
|
||||
}
|
||||
else if (aObj == null) {
|
||||
parts.push("null");
|
||||
}
|
||||
else if (aObj instanceof Array) {
|
||||
parts.push("[");
|
||||
for (var i = 0; i < aObj.length; i++) {
|
||||
jsonIfy(aObj[i]);
|
||||
parts.push(",");
|
||||
}
|
||||
if (parts[parts.length - 1] == ",")
|
||||
parts.pop(); // drop the trailing colon
|
||||
parts.push("]");
|
||||
}
|
||||
else if (typeof aObj == "object") {
|
||||
parts.push("{");
|
||||
for (var key in aObj) {
|
||||
jsonIfy(key.toString());
|
||||
parts.push(":");
|
||||
jsonIfy(aObj[key]);
|
||||
parts.push(",");
|
||||
}
|
||||
if (parts[parts.length - 1] == ",")
|
||||
parts.pop(); // drop the trailing colon
|
||||
parts.push("}");
|
||||
}
|
||||
else {
|
||||
throw new Error("No JSON representation for this object!");
|
||||
}
|
||||
} // end of jsonIfy definition
|
||||
jsonIfy(aJSObject);
|
||||
|
||||
var newJSONString = parts.join(" ");
|
||||
// sanity check - so that API consumers can just eval this string
|
||||
if (/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
|
||||
newJSONString.replace(/"(\\.|[^"\\])*"/g, "")
|
||||
))
|
||||
throw new Error("JSON conversion failed unexpectedly!");
|
||||
|
||||
return newJSONString;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a JSON string into a JavaScript object
|
||||
* (see http://www.json.org/ for the full grammar).
|
||||
*
|
||||
* @param jsonText is the object to be converted
|
||||
* @return a JS Object
|
||||
*/
|
||||
parseJSON: function parseJSON(jsonText) {
|
||||
var m = {
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
};
|
||||
|
||||
var EVAL_SANDBOX = new Components.utils.Sandbox("about:blank");
|
||||
|
||||
if (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/.test(jsonText.
|
||||
replace(/\\./g, '@').
|
||||
replace(/"[^"\\\n\r]*"/g, ''))) {
|
||||
var j = Components.utils.evalInSandbox(jsonText, EVAL_SANDBOX);
|
||||
return j;
|
||||
}
|
||||
else
|
||||
throw new SyntaxError('parseJSON');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче