Bug 416459 - Implement proper cut action for bookmarks.

r=dietrich
This commit is contained in:
Marco Bonardo 2011-06-30 15:34:02 +02:00
Родитель 986b94d168
Коммит 309f2be51a
16 изменённых файлов: 379 добавлений и 181 удалений

Просмотреть файл

@ -431,6 +431,19 @@ PlacesViewBase.prototype = {
}
},
toggleCutNode: function PVB_toggleCutNode(aNode, aValue) {
let elt = aNode._DOMElement;
if (elt) {
// We may get the popup for menus, but we need the menu itself.
if (elt.localName == "menupopup")
elt = elt.parentNode;
if (aValue)
elt.setAttribute("cutting", "true");
else
elt.removeAttribute("cutting");
}
},
nodeURIChanged: function PVB_nodeURIChanged(aPlacesNode, aURIString) {
let elt = aPlacesNode._DOMElement;
if (!elt)
@ -658,6 +671,7 @@ PlacesViewBase.prototype = {
}
if (this._controller) {
this._controller.terminate();
this._viewElt.controllers.removeController(this._controller);
this._controller = null;
}

Просмотреть файл

@ -117,6 +117,9 @@ function PlacesController(aView) {
XPCOMUtils.defineLazyServiceGetter(this, "clipboard",
"@mozilla.org/widget/clipboard;1",
"nsIClipboard");
XPCOMUtils.defineLazyGetter(this, "profileName", function () {
return Services.dirsvc.get("ProfD", Ci.nsIFile).leafName;
});
}
PlacesController.prototype = {
@ -125,6 +128,20 @@ PlacesController.prototype = {
*/
_view: null,
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIClipboardOwner
]),
// nsIClipboardOwner
LosingOwnership: function PC_LosingOwnership (aXferable) {
this.cutNodes = [];
},
terminate: function PC_terminate() {
if (this._cutNodes.length > 0)
this._clearClipboard();
},
supportsCommand: function PC_supportsCommand(aCommand) {
//LOG("supportsCommand: " + command);
// Non-Places specific commands that we also support
@ -1086,74 +1103,127 @@ PlacesController.prototype = {
}
},
get clipboardAction () {
let action = {};
let actionOwner;
try {
let xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION)
this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
xferable.getTransferData(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION, action, {});
[action, actionOwner] =
action.value.QueryInterface(Ci.nsISupportsString).data.split(",");
} catch(ex) {
// Paste from external sources don't have any associated action, just
// fallback to a copy action.
return "copy";
}
// For cuts also check who inited the action, since cuts across different
// instances should instead be handled as copies (The sources are not
// available for this instance).
if (action == "cut" && actionOwner != this.profileName)
action = "copy";
return action;
},
_clearClipboard: function PC__clearClipboard() {
this.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
// Unfortunately just invoking emptyClipboard is not enough, since it
// does not act on the native clipboard.
let xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
// GTK doesn't like empty transferables, so just add an unknown type.
xferable.addDataFlavor("text/x-moz-place-empty");
this.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
},
_populateClipboard: function PC__populateClipboard(aNodes, aAction) {
// This order is _important_! It controls how this and other applications
// select data to be inserted based on type.
let contents = [
{ type: PlacesUtils.TYPE_X_MOZ_PLACE, entries: [] },
{ type: PlacesUtils.TYPE_X_MOZ_URL, entries: [] },
{ type: PlacesUtils.TYPE_HTML, entries: [] },
{ type: PlacesUtils.TYPE_UNICODE, entries: [] },
];
// Avoid handling descendants of a copied node, the transactions take care
// of them automatically.
let copiedFolders = [];
aNodes.forEach(function (node) {
if (this._shouldSkipNode(node, copiedFolders))
return;
if (PlacesUtils.nodeIsFolder(node))
copiedFolders.push(node);
let overrideURI = PlacesUtils.nodeIsLivemarkContainer(node) ?
PlacesUtils.livemarks.getFeedURI(node.itemId).spec : null;
let resolveShortcuts = !PlacesControllerDragHelper.canMoveNode(node);
contents.forEach(function (content) {
content.entries.push(
PlacesUtils.wrapNode(node, content.type, overrideURI, resolveShortcuts)
);
});
}, this);
function addData(type, data) {
xferable.addDataFlavor(type);
xferable.setTransferData(type, PlacesUtils.toISupportsString(data),
data.length * 2);
}
let xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
let hasData = false;
// This order matters here! It controls how this and other applications
// select data to be inserted based on type.
contents.forEach(function (content) {
if (content.entries.length > 0) {
hasData = true;
let glue =
content.type == PlacesUtils.TYPE_X_MOZ_PLACE ? "," : PlacesUtils.endl;
addData(content.type, content.entries.join(glue));
}
});
// Track the exected action in the xferable. This must be the last flavor
// since it's the least preferred one.
// Enqueue a unique instance identifier to distinguish operations across
// concurrent instances of the application.
addData(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION, aAction + "," + this.profileName);
if (hasData)
this.clipboard.setData(xferable, this, Ci.nsIClipboard.kGlobalClipboard);
},
_cutNodes: [],
set cutNodes(aNodes) {
let self = this;
function updateCutNodes(aValue) {
self._cutNodes.forEach(function (aNode) {
self._view.toggleCutNode(aNode, aValue);
});
}
updateCutNodes(false);
this._cutNodes = aNodes;
updateCutNodes(true);
return aNodes;
},
/**
* Copy Bookmarks and Folders to the clipboard
*/
copy: function PC_copy() {
let result = this._view.result;
let didSuppressNotifications = result.suppressNotifications;
if (!didSuppressNotifications)
result.suppressNotifications = true;
try {
let nodes = this._view.selectedNodes;
let xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
let foundFolder = false, foundLink = false;
let copiedFolders = [];
let placeString, mozURLString, htmlString, unicodeString;
placeString = mozURLString = htmlString = unicodeString = "";
for (let i = 0; i < nodes.length; ++i) {
let node = nodes[i];
if (this._shouldSkipNode(node, copiedFolders))
continue;
if (PlacesUtils.nodeIsFolder(node))
copiedFolders.push(node);
function generateChunk(type, overrideURI) {
let suffix = i < (nodes.length - 1) ? PlacesUtils.endl : "";
let 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);
let placeSuffix = i < (nodes.length - 1) ? "," : "";
let resolveShortcuts = !PlacesControllerDragHelper.canMoveNode(node);
return PlacesUtils.wrapNode(node, type, overrideURI, resolveShortcuts) + placeSuffix;
}
// all items wrapped as TYPE_X_MOZ_PLACE
placeString += generateChunk(PlacesUtils.TYPE_X_MOZ_PLACE);
}
function addData(type, data) {
xferable.addDataFlavor(type);
xferable.setTransferData(type, PlacesUIUtils._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 (placeString)
addData(PlacesUtils.TYPE_X_MOZ_PLACE, placeString);
if (mozURLString)
addData(PlacesUtils.TYPE_X_MOZ_URL, mozURLString);
if (unicodeString)
addData(PlacesUtils.TYPE_UNICODE, unicodeString);
if (htmlString)
addData(PlacesUtils.TYPE_HTML, htmlString);
if (placeString || unicodeString || htmlString || mozURLString) {
this.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
}
this._populateClipboard(this._view.selectedNodes, "copy");
}
finally {
if (!didSuppressNotifications)
@ -1165,101 +1235,94 @@ PlacesController.prototype = {
* Cut Bookmarks and Folders to the clipboard
*/
cut: function PC_cut() {
this.copy();
this.remove("Cut Selection");
let result = this._view.result;
let didSuppressNotifications = result.suppressNotifications;
if (!didSuppressNotifications)
result.suppressNotifications = true;
try {
this._populateClipboard(this._view.selectedNodes, "cut");
this.cutNodes = this._view.selectedNodes;
}
finally {
if (!didSuppressNotifications)
result.suppressNotifications = false;
}
},
/**
* Paste Bookmarks and Folders from the clipboard
*/
paste: function PC_paste() {
// Strategy:
//
// There can be data of various types (folder, separator, link) on the
// clipboard. We need to get all of that data and build edit transactions
// for them. This means asking the clipboard once for each type and
// aggregating the results.
/**
* Constructs a transferable that can receive data of specific types.
* @param types
* The types of data the transferable can hold, in order of
* preference.
* @returns The transferable.
*/
function makeXferable(types) {
var xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
for (var i = 0; i < types.length; ++i)
xferable.addDataFlavor(types[i]);
return xferable;
}
var clipboard = this.clipboard;
var ip = this._view.insertionPoint;
// No reason to proceed if there isn't a valid insertion point.
let ip = this._view.insertionPoint;
if (!ip)
throw Cr.NS_ERROR_NOT_AVAILABLE;
/**
* Gets a list of transactions to perform the paste of specific types.
* @param types
* The types of data to form paste transactions for
* @returns An array of transactions that perform the paste.
*/
function getTransactions(types) {
var xferable = makeXferable(types);
clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
let action = this.clipboardAction;
var data = { }, type = { };
try {
xferable.getAnyTransferData(type, data, { });
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) {
var txn;
if (ip.isTag) {
var uri = PlacesUtils._uri(items[i].uri);
txn = PlacesUIUtils.ptm.tagURI(uri, [ip.itemId]);
}
else {
// adjusted to make sure that items are given the correct index
// transactions insert differently if index == -1
// transaction will enqueue the item.
if (ip.index > -1)
index = ip.index + i;
txn = PlacesUIUtils.makeTransaction(items[i], type.value,
ip.itemId, index, true);
}
transactions.push(txn);
}
return transactions;
}
catch (e) {
// getAnyTransferData will throw if there is no data of the specified
// type on the clipboard.
// unwrapNodes will throw if the data that is present is malformed in
// some way.
// In either case, don't fail horribly, just return no data.
}
return [];
let xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
// This order matters here! It controls the preferred flavors for this
// paste operation.
[ PlacesUtils.TYPE_X_MOZ_PLACE,
PlacesUtils.TYPE_X_MOZ_URL,
PlacesUtils.TYPE_UNICODE,
].forEach(function (type) xferable.addDataFlavor(type));
this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
// Now get the clipboard contents, in the best available flavor.
let data = {}, type = {}, items = [];
try {
xferable.getAnyTransferData(type, data, {});
data = data.value.QueryInterface(Ci.nsISupportsString).data;
type = type.value;
items = PlacesUtils.unwrapNodes(data, type);
} catch(ex) {
// No supported data exists or nodes unwrap failed, just bail out.
return;
}
// Get transactions to paste any folders, separators or links that might
// be on the clipboard, aggregate them and execute them.
var transactions = getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE,
PlacesUtils.TYPE_X_MOZ_URL,
PlacesUtils.TYPE_UNICODE]);
var txn = PlacesUIUtils.ptm.aggregateTransactions("Paste", transactions);
PlacesUIUtils.ptm.doTransaction(txn);
let transactions = [];
let insertionIndex = ip.index;
for (let i = 0; i < items.length; ++i) {
if (ip.isTag) {
// Pasting into a tag container means tagging the item, regardless of
// the requested action.
transactions.push(
new PlacesTagURITransaction(PlacesUtils._uri(items[i].uri),
[ip.itemId])
);
continue;
}
// select the pasted items, they should be consecutive
var insertedNodeIds = [];
for (var i = 0; i < transactions.length; ++i)
insertedNodeIds.push(PlacesUtils.bookmarks
.getIdForItemAt(ip.itemId, ip.index + i));
// Adjust index to make sure items are pasted in the correct position.
// If index is DEFAULT_INDEX, items are just appended.
if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
insertionIndex = ip.index + i;
transactions.push(
PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
insertionIndex, action == "copy")
);
}
PlacesUtils.transactionManager.doTransaction(
new PlacesAggregatedTransaction("Paste", transactions)
);
// Cut/past operations are not repeatable, so clear the clipboard.
if (action == "cut") {
this._clearClipboard();
}
// Select the pasted items, they should be consecutive.
let insertedNodeIds = [];
for (let i = 0; i < transactions.length; ++i) {
insertedNodeIds.push(
PlacesUtils.bookmarks.getIdForItemAt(ip.itemId, ip.index + i)
);
}
if (insertedNodeIds.length > 0)
this._view.selectItems(insertedNodeIds, false);
}

Просмотреть файл

@ -60,6 +60,14 @@
if (result) {
result.root.containerOpen = false;
}
// Unregister the controllber before unlinking the view, otherwise it
// may still try to update commands on a view with a null result.
if (this._controller) {
this._controller.terminate();
this.controllers.removeController(this._controller);
}
this.view = null;
]]></destructor>
@ -333,6 +341,17 @@
]]></getter>
</property>
<method name="toggleCutNode">
<parameter name="aNode"/>
<parameter name="aValue"/>
<body><![CDATA[
aNode._cutting = aValue;
try {
this.view.nodeIconChanged(aNode);
} catch(ex) { /* ignore no more valid nodes */ }
]]></body>
</method>
<!-- nsIPlacesView -->
<property name="removableSelectionRanges">
<getter><![CDATA[

Просмотреть файл

@ -1093,6 +1093,11 @@ PlacesTreeView.prototype = {
return;
let node = this._getNodeForRow(aRow);
if (node._cutting) {
aProperties.AppendElement(this._getAtomFor("cutting"));
}
if (!node._cellProperties) {
let properties = new Array();
let itemId = node.itemId;

Просмотреть файл

@ -75,19 +75,6 @@ var PlacesUIUtils = {
return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
},
/**
* Wraps a string in a nsISupportsString wrapper
* @param aString
* The string to wrap
* @returns A nsISupportsString object containing a string.
*/
_wrapString: function PUIU__wrapString(aString) {
var s = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
s.data = aString;
return s;
},
getFormattedString: function PUIU_getFormattedString(key, params) {
return bundle.formatStringFromName(key, params, params.length);
},
@ -814,9 +801,7 @@ var PlacesUIUtils = {
browserWindow.whereToOpenLink(aEvent, false, true) : "window";
if (where == "window") {
// There is no browser window open, thus open a new one.
var uriList = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
uriList.data = urls.join("|");
var uriList = PlacesUtils.toISupportsString(urls.join("|"));
var args = Cc["@mozilla.org/supports-array;1"].
createInstance(Ci.nsISupportsArray);
args.AppendElement(uriList);

Просмотреть файл

@ -73,6 +73,7 @@ _BROWSER_TEST_FILES = \
browser_toolbar_migration.js \
browser_library_batch_delete.js \
browser_555547.js \
browser_416459_cut.js \
$(NULL)
libs:: $(_BROWSER_TEST_FILES)

Просмотреть файл

@ -0,0 +1,68 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
const TEST_URL = "http://example.com/";
let gLibrary;
let gItemId;
function test() {
waitForExplicitFinish();
gLibrary = openLibrary(onLibraryReady);
}
function onLibraryReady(library) {
PlacesOrganizer = gLibrary.PlacesOrganizer;
// Sanity checks.
ok(PlacesUtils, "PlacesUtils in scope");
ok(PlacesUIUtils, "PlacesUIUtils in scope");
ok(PlacesOrganizer, "PlacesOrganizer in scope");
gItemId = PlacesUtils.bookmarks.insertBookmark(
PlacesUtils.toolbarFolderId, NetUtil.newURI(TEST_URL),
PlacesUtils.bookmarks.DEFAULT_INDEX, "test"
);
selectBookmarkIn("BookmarksToolbar");
waitForClipboard(function(aData) !!aData,
cutSelection,
onClipboardReady,
PlacesUtils.TYPE_X_MOZ_PLACE);
}
function selectBookmarkIn(aLeftPaneQuery) {
info("Selecting " + aLeftPaneQuery + " in the left pane");
PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
let rootId = PlacesUtils.getConcreteItemId(PlacesOrganizer._places.selectedNode);
is(PlacesUtils.bookmarks.getFolderIdForItem(gItemId), rootId,
"Bookmark has the right parent");
info("Selecting the bookmark in the right pane");
PlacesOrganizer._content.selectItems([gItemId]);
let bookmarkNode = PlacesOrganizer._content.selectedNode;
is(bookmarkNode.uri, TEST_URL, "Found the expected bookmark");
}
function cutSelection() {
info("Cutting selection");
PlacesOrganizer._content.controller.cut();
}
function pasteClipboard(aLeftPaneQuery) {
info("Selecting " + aLeftPaneQuery + " in the left pane");
PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
info("Pasting clipboard");
PlacesOrganizer._content.controller.paste();
}
function onClipboardReady() {
pasteClipboard("UnfiledBookmarks");
selectBookmarkIn("UnfiledBookmarks");
// Cleanup.
gLibrary.close();
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
finish();
}

Просмотреть файл

@ -16,9 +16,13 @@ registerCleanupFunction(function(){
function openLibrary(callback) {
var library = window.openDialog("chrome://browser/content/places/places.xul",
let library = window.openDialog("chrome://browser/content/places/places.xul",
"", "chrome,toolbar=yes,dialog=no,resizable");
waitForFocus(function () {
callback(library);
}, library);
return library;
}
Components.utils.import("resource://gre/modules/NetUtil.jsm");

Просмотреть файл

@ -230,6 +230,16 @@ menuitem.bookmark-item {
list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
}
.bookmark-item[cutting] > .toolbarbutton-icon,
.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
opacity: 0.5;
}
.bookmark-item[cutting] > .toolbarbutton-text,
.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
opacity: 0.7;
}
/* Stock icons for the menu bar items */
menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
-moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");

Просмотреть файл

@ -99,13 +99,12 @@ treechildren::-moz-tree-image(title, query, folder) {
list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
}
treechildren::-moz-tree-row(session-start) {
border-top:1px dotted ThreeDShadow;
font-weight: bold;
treechildren::-moz-tree-image(cutting) {
opacity: 0.5;
}
treechildren::-moz-tree-cell-text(date, session-continue) {
color: -moz-Field;
treechildren::-moz-tree-cell-text(cutting) {
opacity: 0.7;
}
/**** menuitem stock icons ****/

Просмотреть файл

@ -263,6 +263,16 @@ toolbarbutton.bookmark-item > menupopup {
height: 16px;
}
.bookmark-item[cutting] > .toolbarbutton-icon,
.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
opacity: 0.5;
}
.bookmark-item[cutting] > .toolbarbutton-text,
.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
opacity: 0.7;
}
#bookmarksToolbarFolderMenu,
#BMB_bookmarksToolbar {
list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");

Просмотреть файл

@ -119,10 +119,6 @@ treechildren::-moz-tree-image(title, separator) {
margin: 0;
}
treechildren::-moz-tree-row(session-start) {
border-top:1px dotted ThreeDShadow;
}
treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
}
@ -178,12 +174,6 @@ treechildren::-moz-tree-image(title, query, folder, open) {
-moz-image-region: rect(0, 16px, 16px, 0);
}
/* FIXME this should make the date field invisible, but only does it for
unselected items and maybe won't work for different color schemes. */
treechildren::-moz-tree-cell-text(date, session-continue) {
color:white;
}
treechildren::-moz-tree-cell-text(title, separator) {
color: ThreeDShadow;
margin: 0px 5px;
@ -197,3 +187,11 @@ treechildren::-moz-tree-twisty(title, separator) {
-moz-appearance: none;
padding: 0px;
}
treechildren::-moz-tree-image(cutting) {
opacity: 0.5;
}
treechildren::-moz-tree-cell-text(cutting) {
opacity: 0.7;
}

Просмотреть файл

@ -566,6 +566,16 @@ menuitem.bookmark-item {
-moz-image-region: rect(16px, 32px, 32px, 16px);
}
.bookmark-item[cutting] > .toolbarbutton-icon,
.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
opacity: 0.5;
}
.bookmark-item[cutting] > .toolbarbutton-text,
.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
opacity: 0.7;
}
/* ::::: primary toolbar buttons ::::: */
.toolbarbutton-1 {

Просмотреть файл

@ -116,13 +116,12 @@ treechildren::-moz-tree-image(title, query, folder, open) {
-moz-image-region: rect(16px, 32px, 32px, 16px);
}
treechildren::-moz-tree-row(session-start) {
border-top:1px dotted ThreeDShadow;
font-weight: bold;
treechildren::-moz-tree-image(cutting) {
opacity: 0.5;
}
treechildren::-moz-tree-cell-text(date, session-continue) {
color: -moz-Field;
treechildren::-moz-tree-cell-text(cutting) {
opacity: 0.7;
}
/* Browser Sidebars */

Просмотреть файл

@ -129,6 +129,8 @@ var PlacesUtils = {
TYPE_HTML: "text/html",
// Place entries as raw URL text
TYPE_UNICODE: "text/unicode",
// Used to track the action that populated the clipboard.
TYPE_X_MOZ_PLACE_ACTION: "text/x-moz-place-action",
EXCLUDE_FROM_BACKUP_ANNO: "places/excludeFromBackup",
GUID_ANNO: "placesInternal/GUID",
@ -165,6 +167,19 @@ var PlacesUtils = {
return NetUtil.newURI(aSpec);
},
/**
* Wraps a string in a nsISupportsString wrapper.
* @param aString
* The string to wrap.
* @returns A nsISupportsString object containing a string.
*/
toISupportsString: function PU_toISupportsString(aString) {
let s = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
s.data = aString;
return s;
},
getFormattedString: function PU_getFormattedString(key, params) {
return bundle.formatStringFromName(key, params, params.length);
},

Просмотреть файл

@ -123,9 +123,7 @@
catch (exc) {}
try {
var str = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
str.data = "foo";
var str = PlacesUtils.toISupportsString("foo");
query = PlacesUtils.history.getNewQuery();
query.tags = str;
do_throw("Passing nsISupportsString to SetTags should fail");