From ac635aae45a440c98f8177867828a55031ca2368 Mon Sep 17 00:00:00 2001 From: Marco Bonardo Date: Mon, 22 Nov 2010 20:34:57 +0100 Subject: [PATCH] Bug 613477 - Make the primary Star UI async. r=sdwilsh ui-r=limi a=blocking --- browser/base/content/browser-places.js | 169 +++++--- browser/base/content/browser.xul | 1 + .../base/content/test/browser_bug432599.js | 40 +- .../base/content/test/browser_bug581253.js | 30 +- .../browser/browser_410196_paste_into_tags.js | 386 ++++++++---------- toolkit/components/places/src/PlacesUtils.jsm | 121 ++++-- .../components/places/src/nsTaggingService.js | 2 +- 7 files changed, 429 insertions(+), 320 deletions(-) diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index ea04d4e87a4b..de4f90f981fd 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -23,6 +23,7 @@ # Joe Hughes # Asaf Romano # Ehsan Akhgari +# Marco Bonardo # # 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 @@ -920,7 +921,8 @@ var PlacesMenuDNDHandler = { var PlacesStarButton = { - init: function PSB_init() { + init: function PSB_init() + { try { PlacesUtils.bookmarks.addObserver(this, false); } catch(ex) { @@ -928,74 +930,139 @@ var PlacesStarButton = { } }, - uninit: function PSB_uninit() { - PlacesUtils.bookmarks.removeObserver(this); + uninit: function PSB_uninit() + { + try { + PlacesUtils.bookmarks.removeObserver(this); + } catch(ex) {} }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]), + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsINavBookmarkObserver + ]), - _starred: false, - _batching: false, + get _starredTooltip() + { + delete this._starredTooltip; + return this._starredTooltip = + gNavigatorBundle.getString("starButtonOn.tooltip"); + }, + get _unstarredTooltip() + { + delete this._unstarredTooltip; + return this._unstarredTooltip = + gNavigatorBundle.getString("starButtonOff.tooltip"); + }, - updateState: function PSB_updateState() { - var starIcon = document.getElementById("star-button"); - if (!starIcon) + updateState: function PSB_updateState() + { + this._starIcon = document.getElementById("star-button"); + if (!this._starIcon || gBrowser.currentURI.equals(this._uri)) { return; - - var uri = gBrowser.currentURI; - this._starred = uri && (PlacesUtils.getMostRecentBookmarkForURI(uri) != -1 || - PlacesUtils.getMostRecentFolderForFeedURI(uri) != -1); - if (this._starred) { - starIcon.setAttribute("starred", "true"); - starIcon.setAttribute("tooltiptext", gNavigatorBundle.getString("starButtonOn.tooltip")); } - else { - starIcon.removeAttribute("starred"); - starIcon.setAttribute("tooltiptext", gNavigatorBundle.getString("starButtonOff.tooltip")); + + // Reset tracked values. + this._uri = gBrowser.currentURI; + this._itemIds = []; + + // Hide the star while we update its state. + this._starIcon.hidden = true; + + PlacesUtils.asyncGetBookmarkIds(this._uri, function (aItemIds) { + this._itemIds = aItemIds; + this._updateStateInternal(); + // Finally show the star. + this._starIcon.hidden = false; + }, this); + }, + + _updateStateInternal: function PSB__updateStateInternal() + { + if (!this._starIcon) { + return; + } + + let starred = this._starIcon.hasAttribute("starred"); + if (this._itemIds.length > 0 && !starred) { + this._starIcon.setAttribute("starred", "true"); + this._starIcon.setAttribute("tooltiptext", this._starredTooltip); + } + else if (this._itemIds.length == 0 && starred) { + this._starIcon.removeAttribute("starred"); + this._starIcon.setAttribute("tooltiptext", this._unstarredTooltip); } }, - onClick: function PSB_onClick(aEvent) { - if (aEvent.button == 0) - PlacesCommandHook.bookmarkCurrentPage(this._starred); - - // don't bubble to the textbox so that the address won't be selected + onClick: function PSB_onClick(aEvent) + { + if (aEvent.button == 0) { + PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0); + } + // Don't bubble to the textbox, to avoid unwanted selection of the address. aEvent.stopPropagation(); }, - // nsINavBookmarkObserver - onBeginUpdateBatch: function PSB_onBeginUpdateBatch() { - this._batching = true; + // nsINavBookmarkObserver + onItemAdded: + function PSB_onItemAdded(aItemId, aFolder, aIndex, aItemType, aURI) + { + if (!this._starIcon) { + return; + } + + if (aURI.equals(this._uri)) { + // If a new bookmark has been added to the tracked uri, register it. + if (this._itemIds.indexOf(aItemId) == -1) { + this._itemIds.push(aItemId); + this._updateStateInternal(); + } + } }, - onEndUpdateBatch: function PSB_onEndUpdateBatch() { - this.updateState(); - this._batching = false; + onItemRemoved: + function PSB_onItemRemoved(aItemId, aFolder, aIndex, aItemType) + { + if (!this._starIcon) { + return; + } + + let index = this._itemIds.indexOf(aItemId); + // If one of the tracked bookmarks has been removed, unregister it. + if (index != -1) { + this._itemIds.splice(index, 1); + this._updateStateInternal(); + } }, - onItemAdded: function PSB_onItemAdded(aItemId, aFolder, aIndex, aItemType, - aURI) { - if (!this._batching && !this._starred) - this.updateState(); + onItemChanged: + function PSB_onItemChanged(aItemId, aProperty, aIsAnnotationProperty, + aNewValue, aLastModified, aItemType) + { + if (!this._starIcon) { + return; + } + + if (aProperty == "uri") { + let index = this._itemIds.indexOf(aItemId); + // If the changed bookmark was tracked, check if it is now pointing to + // a different uri and unregister it. + if (index != -1 && aNewValue != this._uri.spec) { + this._itemIds.splice(index, 1); + this._updateStateInternal(); + } + // If another bookmark is now pointing to the tracked uri, register it. + else if (index == -1 && aNewValue == this._uri.spec) { + this._itemIds.push(aItemId); + this._updateStateInternal(); + } + } }, - onBeforeItemRemoved: function() {}, - - onItemRemoved: function PSB_onItemRemoved(aItemId, aFolder, aIndex, - aItemType) { - if (!this._batching) - this.updateState(); - }, - - onItemChanged: function PSB_onItemChanged(aItemId, aProperty, - aIsAnnotationProperty, aNewValue, - aLastModified, aItemType) { - if (!this._batching && aProperty == "uri") - this.updateState(); - }, - - onItemVisited: function() {}, - onItemMoved: function() {} + onBeginUpdateBatch: function () {}, + onEndUpdateBatch: function () {}, + onBeforeItemRemoved: function () {}, + onItemVisited: function () {}, + onItemMoved: function () {} }; diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 910cbf89c3d4..3ee87c368c80 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -572,6 +572,7 @@ - * - * 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 ***** */ - -// Get history services -var hs = Cc["@mozilla.org/browser/nav-history-service;1"]. - getService(Ci.nsINavHistoryService); -var gh = hs.QueryInterface(Ci.nsIGlobalHistory2); -var bh = hs.QueryInterface(Ci.nsIBrowserHistory); -var ts = Cc["@mozilla.org/browser/tagging-service;1"]. - getService(Components.interfaces.nsITaggingService); -var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ function add_visit(aURI, aReferrer) { - var visitId = hs.addVisit(aURI, - Date.now() * 1000, - aReferrer, - hs.TRANSITION_TYPED, // user typed in URL bar - false, // not redirect - 0); - return visitId; + return PlacesUtils.history.addVisit(aURI, Date.now() * 1000, aReferrer, + PlacesUtils.history.TRANSITION_TYPED, + false, 0); } function add_bookmark(aURI) { - var bId = bs.insertBookmark(bs.unfiledBookmarksFolder, aURI, - bs.DEFAULT_INDEX, "bookmark/" + aURI.spec); - return bId; + return PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, + "bookmark/" + aURI.spec); } +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + const TEST_URL = "http://example.com/"; const MOZURISPEC = "http://mozilla.com/"; +let gLibrary; +let PlacesOrganizer; + function test() { waitForExplicitFinish(); - var win = window.openDialog("chrome://browser/content/places/places.xul", - "", - "chrome,toolbar=yes,dialog=no,resizable"); - - win.addEventListener("load", function onload() { - win.removeEventListener("load", onload, false); - executeSoon(function () { - var PU = win.PlacesUtils; - var PO = win.PlacesOrganizer; - var PUIU = win.PlacesUIUtils; - - // individual tests for each step of tagging a history item - var tests = { - - sanity: function(){ - // sanity check - ok(PU, "PlacesUtils in scope"); - ok(PUIU, "PlacesUIUtils in scope"); - ok(PO, "Places organizer in scope"); - }, - - makeHistVisit: function() { - // need to add a history object - var testURI1 = PU._uri(MOZURISPEC); - isnot(testURI1, null, "testURI is not null"); - var visitId = add_visit(testURI1); - ok(visitId > 0, "A visit was added to the history"); - ok(gh.isVisited(testURI1), MOZURISPEC + " is a visited url."); - }, - - makeTag: function() { - // create an initial tag to work with - var bmId = add_bookmark(PlacesUtils._uri(TEST_URL)); - ok(bmId > 0, "A bookmark was added"); - ts.tagURI(PlacesUtils._uri(TEST_URL), ["foo"]); - var tags = ts.getTagsForURI(PU._uri(TEST_URL)); - is(tags[0], 'foo', "tag is foo"); - }, - - focusTag: function (paste){ - // focus the new tag - PO.selectLeftPaneQuery("Tags"); - var tags = PO._places.selectedNode; - tags.containerOpen = true; - var fooTag = tags.getChild(0); - this.tagNode = fooTag; - PO._places.selectNode(fooTag); - is(this.tagNode.title, 'foo', "tagNode title is foo"); - var ip = PO._places.insertionPoint; - ok(ip.isTag, "IP is a tag"); - if (paste) { - ok(true, "About to paste"); - PO._places.controller.paste(); - } - }, - - histNode: null, - - copyHistNode: function (){ - // focus the history object - PO.selectLeftPaneQuery("History"); - var histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode); - histContainer.containerOpen = true; - PO._places.selectNode(histContainer.getChild(0)); - this.histNode = PO._content.view.nodeForTreeIndex(0); - PO._content.selectNode(this.histNode); - is(this.histNode.uri, MOZURISPEC, - "historyNode exists: " + this.histNode.uri); - // copy the history node - PO._content.controller.copy(); - }, - - waitForPaste: function (){ - try { - var xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - xferable.addDataFlavor(PU.TYPE_X_MOZ_PLACE); - var clipboard = Cc["@mozilla.org/widget/clipboard;1"]. - getService(Ci.nsIClipboard); - clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard); - var data = { }, type = { }; - xferable.getAnyTransferData(type, data, { }); - // Data is in the clipboard - continue_test(); - } catch (ex) { - // check again after 100ms. - setTimeout(tests.waitForPaste, 100); - } - }, - - pasteToTag: function (){ - // paste history node into tag - this.focusTag(true); - }, - - historyNode: function (){ - // re-focus the history again - PO.selectLeftPaneQuery("History"); - var histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode); - histContainer.containerOpen = true; - PO._places.selectNode(histContainer.getChild(0)); - var histNode = PO._content.view.nodeForTreeIndex(0); - ok(histNode, "histNode exists: " + histNode.title); - // check to see if the history node is tagged! - var tags = PU.tagging.getTagsForURI(PU._uri(MOZURISPEC)); - ok(tags.length == 1, "history node is tagged: " + tags.length); - // check if a bookmark was created - var isBookmarked = PU.bookmarks.isBookmarked(PU._uri(MOZURISPEC)); - is(isBookmarked, true, MOZURISPEC + " is bookmarked"); - var bookmarkIds = PU.bookmarks.getBookmarkIdsForURI( - PU._uri(histNode.uri)); - ok(bookmarkIds.length > 0, "bookmark exists for the tagged history item: " + bookmarkIds); - }, - - checkForBookmarkInUI: function(){ - // is the bookmark visible in the UI? - // get the Unsorted Bookmarks node - PO.selectLeftPaneQuery("UnfiledBookmarks"); - // now we can see what is in the _content tree - var unsortedNode = PO._content.view.nodeForTreeIndex(1); - ok(unsortedNode, "unsortedNode is not null: " + unsortedNode.uri); - is(unsortedNode.uri, MOZURISPEC, "node uri's are the same"); - }, - - tagNode: null, - - cleanUp: function(){ - ts.untagURI(PU._uri(MOZURISPEC), ["foo"]); - ts.untagURI(PU._uri(TEST_URL), ["foo"]); - hs.removeAllPages(); - var tags = ts.getTagsForURI(PU._uri(TEST_URL)); - is(tags.length, 0, "tags are gone"); - bs.removeFolderChildren(bs.unfiledBookmarksFolder); - } - }; - - tests.sanity(); - tests.makeHistVisit(); - tests.makeTag(); - tests.focusTag(); - tests.copyHistNode(); - tests.waitForPaste(); - - function continue_test() { - tests.pasteToTag(); - tests.historyNode(); - tests.checkForBookmarkInUI(); - - // remove new places data we created - tests.cleanUp(); - - win.close(); - finish(); - } - - }); - },false); + gLibrary = window.openDialog("chrome://browser/content/places/places.xul", + "", "chrome,toolbar=yes,dialog=no,resizable"); + waitForFocus(onLibraryReady, gLibrary); +} + +function onLibraryReady() { + ok(PlacesUtils, "PlacesUtils in scope"); + ok(PlacesUIUtils, "PlacesUIUtils in scope"); + + PlacesOrganizer = gLibrary.PlacesOrganizer; + ok(PlacesOrganizer, "Places organizer in scope"); + + tests.makeHistVisit(); + tests.makeTag(); + tests.focusTag(); + tests.copyHistNode(); + tests.waitForClipboard(); +} + +function onClipboardReady() { + tests.pasteToTag(); + tests.historyNode(); + tests.checkForBookmarkInUI(); + + gLibrary.close(); + + // Remove new Places data we created. + PlacesUtils.tagging.untagURI(NetUtil.newURI(MOZURISPEC), ["foo"]); + PlacesUtils.tagging.untagURI(NetUtil.newURI(TEST_URL), ["foo"]); + let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL)); + is(tags.length, 0, "tags are gone"); + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + + waitForClearHistory(finish); +} + +let tests = { + + makeHistVisit: function() { + // need to add a history object + let testURI1 = NetUtil.newURI(MOZURISPEC); + isnot(testURI1, null, "testURI is not null"); + let visitId = add_visit(testURI1); + ok(visitId > 0, "A visit was added to the history"); + ok(PlacesUtils.ghistory2.isVisited(testURI1), MOZURISPEC + " is a visited url."); + }, + + makeTag: function() { + // create an initial tag to work with + let bmId = add_bookmark(NetUtil.newURI(TEST_URL)); + ok(bmId > 0, "A bookmark was added"); + PlacesUtils.tagging.tagURI(NetUtil.newURI(TEST_URL), ["foo"]); + let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL)); + is(tags[0], 'foo', "tag is foo"); + }, + + focusTag: function (paste){ + // focus the new tag + PlacesOrganizer.selectLeftPaneQuery("Tags"); + let tags = PlacesOrganizer._places.selectedNode; + tags.containerOpen = true; + let fooTag = tags.getChild(0); + this.tagNode = fooTag; + PlacesOrganizer._places.selectNode(fooTag); + is(this.tagNode.title, 'foo', "tagNode title is foo"); + let ip = PlacesOrganizer._places.insertionPoint; + ok(ip.isTag, "IP is a tag"); + if (paste) { + ok(true, "About to paste"); + PlacesOrganizer._places.controller.paste(); + } + }, + + histNode: null, + + copyHistNode: function (){ + // focus the history object + PlacesOrganizer.selectLeftPaneQuery("History"); + let histContainer = PlacesOrganizer._places.selectedNode; + PlacesUtils.asContainer(histContainer); + histContainer.containerOpen = true; + PlacesOrganizer._places.selectNode(histContainer.getChild(0)); + this.histNode = PlacesOrganizer._content.view.nodeForTreeIndex(0); + PlacesOrganizer._content.selectNode(this.histNode); + is(this.histNode.uri, MOZURISPEC, + "historyNode exists: " + this.histNode.uri); + // copy the history node + PlacesOrganizer._content.controller.copy(); + }, + + waitForClipboard: function (){ + try { + let xferable = Cc["@mozilla.org/widget/transferable;1"]. + createInstance(Ci.nsITransferable); + xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_PLACE); + let clipboard = Cc["@mozilla.org/widget/clipboard;1"]. + getService(Ci.nsIClipboard); + clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard); + let data = { }, type = { }; + xferable.getAnyTransferData(type, data, { }); + // Data is in the clipboard + onClipboardReady(); + } catch (ex) { + // check again after 100ms. + setTimeout(arguments.callee, 100); + } + }, + + pasteToTag: function (){ + // paste history node into tag + this.focusTag(true); + }, + + historyNode: function (){ + // re-focus the history again + PlacesOrganizer.selectLeftPaneQuery("History"); + let histContainer = PlacesOrganizer._places.selectedNode; + PlacesUtils.asContainer(histContainer); + histContainer.containerOpen = true; + PlacesOrganizer._places.selectNode(histContainer.getChild(0)); + let histNode = PlacesOrganizer._content.view.nodeForTreeIndex(0); + ok(histNode, "histNode exists: " + histNode.title); + // check to see if the history node is tagged! + let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(MOZURISPEC)); + ok(tags.length == 1, "history node is tagged: " + tags.length); + // check if a bookmark was created + let isBookmarked = PlacesUtils.bookmarks.isBookmarked(NetUtil.newURI(MOZURISPEC)); + is(isBookmarked, true, MOZURISPEC + " is bookmarked"); + let bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI( + NetUtil.newURI(histNode.uri)); + ok(bookmarkIds.length > 0, "bookmark exists for the tagged history item: " + bookmarkIds); + }, + + checkForBookmarkInUI: function(){ + // is the bookmark visible in the UI? + // get the Unsorted Bookmarks node + PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + // now we can see what is in the _content tree + let unsortedNode = PlacesOrganizer._content.view.nodeForTreeIndex(1); + ok(unsortedNode, "unsortedNode is not null: " + unsortedNode.uri); + is(unsortedNode.uri, MOZURISPEC, "node uri's are the same"); + }, + + tagNode: null, +}; + +/** + * Clears history invoking callback when done. + */ +function waitForClearHistory(aCallback) { + const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished"; + let observer = { + observe: function(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, TOPIC_EXPIRATION_FINISHED); + aCallback(); + } + }; + Services.obs.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false); + PlacesUtils.bhistory.removeAllPages(); } diff --git a/toolkit/components/places/src/PlacesUtils.jsm b/toolkit/components/places/src/PlacesUtils.jsm index b0d65ecb9c08..aa2227d2b69b 100644 --- a/toolkit/components/places/src/PlacesUtils.jsm +++ b/toolkit/components/places/src/PlacesUtils.jsm @@ -82,10 +82,6 @@ XPCOMUtils.defineLazyGetter(this, "NetUtil", function() { return NetUtil; }); -// Global observers flags. -let gHasAnnotationsObserver = false; -let gHasShutdownObserver = false; - // The minimum amount of transactions before starting a batch. Usually we do // do incremental updates, a batch will cause views to completely // refresh instead. @@ -275,15 +271,11 @@ var PlacesUtils = { */ get _readOnly() { // Add annotations observer. - if (!gHasAnnotationsObserver) { - this.annotations.addObserver(this, false); - gHasAnnotationsObserver = true; - } - // Observe shutdown, so we can remove the anno observer. - if (!gHasShutdownObserver) { - Services.obs.addObserver(this, this.TOPIC_SHUTDOWN, false); - gHasShutdownObserver = true; - } + this.annotations.addObserver(this, false); + this.registerShutdownFunction(function () { + this.annotations.removeObserver(this); + }); + var readOnly = this.annotations.getItemsWithAnnotation(this.READ_ONLY_ANNO); this.__defineGetter__("_readOnly", function() readOnly); return this._readOnly; @@ -295,24 +287,25 @@ var PlacesUtils = { , Ci.nsITransactionListener ]), - // nsIObserver - observe: function PU_observe(aSubject, aTopic, aData) { - if (aTopic == this.TOPIC_SHUTDOWN) { - if (gHasAnnotationsObserver) - this.annotations.removeObserver(this); - - if (Object.getOwnPropertyDescriptor(this, "transactionManager").value !== undefined) { - // Clear all references to local transactions in the transaction manager, - // this prevents from leaking it. - this.transactionManager.RemoveListener(this); - this.transactionManager.clear(); - } - - Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN); - gHasShutdownObserver = false; + _shutdownFunctions: [], + registerShutdownFunction: function PU_registerShutdownFunction(aFunc) + { + // If this is the first registered function, add the shutdown observer. + if (this._shutdownFunctions.length == 0) { + Services.obs.addObserver(this, this.TOPIC_SHUTDOWN, false); } + this._shutdownFunctions.push(aFunc); }, + ////////////////////////////////////////////////////////////////////////////// + //// nsIObserver + observe: function PU_observe(aSubject, aTopic, aData) + { + if (aTopic == this.TOPIC_SHUTDOWN) { + Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN); + this._shutdownFunctions.forEach(function (aFunc) aFunc.apply(this), this); + } + }, ////////////////////////////////////////////////////////////////////////////// //// nsIAnnotationObserver @@ -2048,6 +2041,63 @@ var PlacesUtils = { } }, + + /** + * Given a uri returns list of itemIds associated to it. + * + * @param aURI + * nsIURI or spec of the page. + * @param aCallback + * Function to be called when done. + * The function will receive an array of itemIds associated to aURI. + * @param aScope + * Scope for the callback. + * + * @note Children of live bookmarks folders are excluded. + */ + asyncGetBookmarkIds: function PU_asyncGetBookmarkIds(aURI, aCallback, aScope) + { + if (!this._asyncGetBookmarksStmt) { + let db = this.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; + this._asyncGetBookmarksStmt = db.createAsyncStatement( + "SELECT b.id " + + "FROM moz_bookmarks b " + + "JOIN moz_places h on h.id = b.fk " + + "WHERE h.url = :url " + + "AND NOT EXISTS( " + + "SELECT 1 FROM moz_items_annos a " + + "JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id " + + "WHERE a.item_id = b.parent AND n.name = :name " + + ") " + ); + this.registerShutdownFunction(function () { + this._asyncGetBookmarksStmt.finalize(); + }); + } + + let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI; + this._asyncGetBookmarksStmt.params.url = url; + this._asyncGetBookmarksStmt.params.name = this.LMANNO_FEEDURI; + this._asyncGetBookmarksStmt.executeAsync({ + _itemIds: [], + handleResult: function(aResultSet) { + let row, haveMatches = false; + for (let row; (row = aResultSet.getNextRow());) { + this._itemIds.push(row.getResultByIndex(0)); + } + }, + handleError: function(aError) { + Cu.reportError("Async statement execution returned (" + aError.result + + "): " + aError.message); + }, + handleCompletion: function(aReason) + { + if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { + aCallback.apply(aScope, [this._itemIds]); + } + } + }); + } }; XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "history", @@ -2091,18 +2141,15 @@ XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "microsummaries", "nsIMicrosummaryService"); XPCOMUtils.defineLazyGetter(PlacesUtils, "transactionManager", function() { - Services.obs.addObserver(PlacesUtils, - PlacesUtils.TOPIC_SHUTDOWN, - false); - // Observe shutdown, so we can remove the anno observer. - if (!gHasShutdownObserver) { - Services.obs.addObserver(PlacesUtils, PlacesUtils.TOPIC_SHUTDOWN, false); - gHasShutdownObserver = true; - } - let tm = Cc["@mozilla.org/transactionmanager;1"]. getService(Ci.nsITransactionManager); tm.AddListener(PlacesUtils); + this.registerShutdownFunction(function () { + // Clear all references to local transactions in the transaction manager, + // this prevents from leaking it. + this.transactionManager.RemoveListener(this); + this.transactionManager.clear(); + }); return tm; }); diff --git a/toolkit/components/places/src/nsTaggingService.js b/toolkit/components/places/src/nsTaggingService.js index 6759a494995f..dfecf64db530 100644 --- a/toolkit/components/places/src/nsTaggingService.js +++ b/toolkit/components/places/src/nsTaggingService.js @@ -193,7 +193,7 @@ TaggingService.prototype = { { if (tag.id == -1) { // Tag does not exist yet, create it. - tag.id = this._createTag(tag.name); + this._createTag(tag.name); } if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) {