diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 0edbf931bdef..3dc2e7ccd3bd 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -320,9 +320,10 @@ pref("browser.tabs.selectOwnerOnClose", true); pref("browser.bookmarks.sort.direction", "descending"); pref("browser.bookmarks.sort.resource", "rdf:http://home.netscape.com/NC-rdf#Name"); -// By default, do not overwrite bookmarks.html in the profile directory -// See bug #381216 for details -pref("browser.bookmarks.overwrite", false); +// By default, do not export HTML at shutdown. +// If true, at shutdown the bookmarks in your menu and toolbar will +// be exported as HTML to the bookmarks.html file. +pref("browser.bookmarks.autoExportHTML", false); // Scripts & Windows prefs pref("dom.disable_open_during_load", true); diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc index 7bb5910f1cd1..c158f8ef0049 100644 --- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -329,7 +329,7 @@ diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index 304c2e8a3fb1..05751eb840c2 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -105,7 +105,7 @@ var StarUI = { this._itemId = -1; this._uri = null; if (this._batching) { - PlacesUtils.ptm.endBatch(); + PlacesUIUtils.ptm.endBatch(); this._batching = false; } } @@ -188,11 +188,11 @@ var StarUI = { // Otherwise, if no changes were done in the edit-item panel, the last // transaction on the undo stack may be the initial createItem transaction, // or worse, the batched editing of some other item. - PlacesUtils.ptm.doTransaction({ doTransaction: function() { }, - undoTransaction: function() { }, - redoTransaction: function() { }, - isTransient: false, - merge: function() { return false; } }); + PlacesUIUtils.ptm.doTransaction({ doTransaction: function() { }, + undoTransaction: function() { }, + redoTransaction: function() { }, + isTransient: false, + merge: function() { return false; } }); if (this.panel.state == "closed") { // Consume dismiss clicks, see bug 400924 @@ -271,7 +271,7 @@ var StarUI = { cancelButtonOnCommand: function SU_cancelButtonOnCommand() { this.endBatch(); - PlacesUtils.ptm.undoTransaction(); + PlacesUIUtils.ptm.undoTransaction(); this.panel.hidePopup(); }, @@ -282,8 +282,8 @@ var StarUI = { // a "Bookmark Removed" notification along with an Undo button is // shown if (this._batching) { - PlacesUtils.ptm.endBatch(); - PlacesUtils.ptm.beginBatch(); // allow undo from within the notification + PlacesUIUtils.ptm.endBatch(); + PlacesUIUtils.ptm.beginBatch(); // allow undo from within the notification var bundle = this._element("bundle_browser"); // "Bookmark Removed" title (the description field is already empty in @@ -308,8 +308,8 @@ var StarUI = { // the tags for the url var itemIds = PlacesUtils.getBookmarksForURI(this._uri); for (var i=0; i < itemIds.length; i++) { - var txn = PlacesUtils.ptm.removeItem(itemIds[i]); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.removeItem(itemIds[i]); + PlacesUIUtils.ptm.doTransaction(txn); } #ifdef ADVANCED_STARRING_UI @@ -324,21 +324,21 @@ var StarUI = { // restore the bookmark by undoing the last transaction and go back // to the edit state this.endBatch(); - PlacesUtils.ptm.undoTransaction(); + PlacesUIUtils.ptm.undoTransaction(); this._itemId = PlacesUtils.getMostRecentBookmarkForURI(this._uri); this.showEditBookmarkPopup(); }, beginBatch: function SU_beginBatch() { if (!this._batching) { - PlacesUtils.ptm.beginBatch(); + PlacesUIUtils.ptm.beginBatch(); this._batching = true; } }, endBatch: function SU_endBatch() { if (this._batching) { - PlacesUtils.ptm.endBatch(); + PlacesUIUtils.ptm.endBatch(); this._batching = false; } } @@ -372,7 +372,7 @@ var PlacesCommandHook = { var description; try { title = webNav.document.title || url.spec; - description = PlacesUtils.getDescriptionFromDocument(webNav.document); + description = PlacesUIUtils.getDescriptionFromDocument(webNav.document); } catch (e) { } @@ -386,9 +386,9 @@ var PlacesCommandHook = { var parent = aParent != undefined ? aParent : PlacesUtils.unfiledBookmarksFolderId; var descAnno = { name: DESCRIPTION_ANNO, value: description }; - var txn = PlacesUtils.ptm.createItem(uri, parent, -1, - title, null, [descAnno]); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.createItem(uri, parent, -1, + title, null, [descAnno]); + PlacesUIUtils.ptm.doTransaction(txn); itemId = PlacesUtils.getMostRecentBookmarkForURI(uri); } @@ -432,8 +432,8 @@ var PlacesCommandHook = { var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI); if (itemId == -1) { StarUI.beginBatch(); - var txn = PlacesUtils.ptm.createItem(linkURI, aParent, -1, aTitle); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.createItem(linkURI, aParent, -1, aTitle); + PlacesUIUtils.ptm.doTransaction(txn); itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI); } @@ -474,7 +474,7 @@ var PlacesCommandHook = { */ bookmarkCurrentPages: function PCH_bookmarkCurrentPages() { var tabURIs = this._getUniqueTabInfo(); - PlacesUtils.showMinimalAddMultiBookmarkUI(tabURIs); + PlacesUIUtils.showMinimalAddMultiBookmarkUI(tabURIs); }, @@ -500,12 +500,12 @@ var PlacesCommandHook = { if (arguments.length > 2) description = feedSubtitle; else - description = PlacesUtils.getDescriptionFromDocument(doc); + description = PlacesUIUtils.getDescriptionFromDocument(doc); var toolbarIP = new InsertionPoint(PlacesUtils.bookmarks.toolbarFolder, -1); - PlacesUtils.showMinimalAddLivemarkUI(feedURI, gBrowser.currentURI, - title, description, toolbarIP, true); + PlacesUIUtils.showMinimalAddLivemarkUI(feedURI, gBrowser.currentURI, + title, description, toolbarIP, true); }, /** @@ -581,7 +581,7 @@ var BookmarksEventHandler = { return; var target = aEvent.originalTarget; - var view = PlacesUtils.getViewForNode(target); + var view = PlacesUIUtils.getViewForNode(target); if (target.node && PlacesUtils.nodeIsFolder(target.node)) { // Don't open the root folder in tabs when the empty area on the toolbar // is middle-clicked or when a non-bookmark item except for Open in Tabs) @@ -625,7 +625,7 @@ var BookmarksEventHandler = { onCommand: function BM_onCommand(aEvent) { var target = aEvent.originalTarget; if (target.node) - PlacesUtils.openNodeWithEvent(target.node, aEvent); + PlacesUIUtils.openNodeWithEvent(target.node, aEvent); }, /** @@ -685,7 +685,7 @@ var BookmarksEventHandler = { openHomePage.setAttribute("onclick", "checkForMiddleClick(this, event); event.stopPropagation();"); openHomePage.setAttribute("label", - PlacesUtils.getFormattedString("menuOpenLivemarkOrigin.label", + PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label", [target.parentNode.getAttribute("label")])); target.appendChild(openHomePage); } @@ -694,7 +694,7 @@ var BookmarksEventHandler = { var openInTabs = document.createElement("menuitem"); openInTabs.setAttribute("openInTabs", "true"); openInTabs.setAttribute("oncommand", - "PlacesUtils.openContainerNodeInTabs(this.parentNode._resultNode, event);"); + "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._resultNode, event);"); openInTabs.setAttribute("label", gNavigatorBundle.getString("menuOpenAllInTabs.label")); target.appendChild(openInTabs); @@ -755,7 +755,7 @@ var BookmarksMenuDropHandler = { getSupportedFlavours: function BMDH_getSupportedFlavours() { var flavorSet = new FlavourSet(); var view = document.getElementById("bookmarksMenuPopup"); - var types = PlacesUtils.GENERIC_VIEW_DROP_TYPES + var types = PlacesUIUtils.GENERIC_VIEW_DROP_TYPES for (var i = 0; i < types.length; ++i) flavorSet.appendFlavour(types[i]); return flavorSet; diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index b528038459e7..68a6b8dcf11f 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -2543,7 +2543,7 @@ var bookmarksButtonObserver = { var split = aXferData.data.split("\n"); var url = split[0]; if (url != aXferData.data) // do nothing if it's not a valid URL - PlacesUtils.showMinimalAddBookmarkUI(makeURI(url), split[1]); + PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(url), split[1]); }, onDragOver: function (aEvent, aFlavour, aDragSession) @@ -3013,7 +3013,7 @@ function addToUrlbarHistory(aUrlToAdd) try { if (aUrlToAdd.indexOf(" ") == -1) { - PlacesUtils.markPageAsTyped(aUrlToAdd); + PlacesUIUtils.markPageAsTyped(aUrlToAdd); } } catch(ex) { @@ -4424,9 +4424,9 @@ function asyncOpenWebPanel(event) // This is the Opera convention for a special link that - when clicked - allows // you to add a sidebar panel. We support the Opera convention here. The link's // title attribute contains the title that should be used for the sidebar panel. - PlacesUtils.showMinimalAddBookmarkUI(makeURI(wrapper.href), - wrapper.getAttribute("title"), - null, null, true, true); + PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(wrapper.href), + wrapper.getAttribute("title"), + null, null, true, true); event.preventDefault(); return false; } @@ -5390,9 +5390,9 @@ function AddKeywordForSearchField() else spec += "?" + formData.join("&"); - var description = PlacesUtils.getDescriptionFromDocument(node.ownerDocument); - PlacesUtils.showMinimalAddBookmarkUI(makeURI(spec), "", description, null, - null, null, "", postData); + var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument); + PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(spec), "", description, null, + null, null, "", postData); } function SwitchDocumentDirection(aWindow) { diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index bd82882c613a..b08b46fdc584 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -1191,13 +1191,13 @@ nsContextMenu.prototype = { var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri); if (itemId == -1) { var title = doc.title; - var description = PlacesUtils.getDescriptionFromDocument(doc); + var description = PlacesUIUtils.getDescriptionFromDocument(doc); var descAnno = { name: DESCRIPTION_ANNO, value: description }; - var txn = PlacesUtils.ptm.createItem(uri, + var txn = PlacesUIUtils.ptm.createItem(uri, PlacesUtils.bookmarksMenuFolderId, -1, title, null, [descAnno]); - PlacesUtils.ptm.doTransaction(txn); + PlacesUIUtils.ptm.doTransaction(txn); itemId = PlacesUtils.getMostRecentBookmarkForURI(uri); StarUI.beginBatch(); } diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index de1e922c469a..a5ce77a8f641 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -45,6 +45,13 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource:///modules/distribution.js"); +// Check to see if bookmarks need backing up once per +// day on 1 hour idle. +const BOOKMARKS_ARCHIVE_IDLE_TIME = 60 * 60; + +// Backup bookmarks once every 24 hours. +const BOOKMARKS_ARCHIVE_INTERVAL = 86400 * 1000; + // Factory object const BrowserGlueServiceFactory = { _instance: null, @@ -103,15 +110,22 @@ BrowserGlue.prototype = { if (this._saveSession) { this._setPrefToSaveSession(); } + this._shutdownPlaces(); break; case "session-save": this._setPrefToSaveSession(); subject.QueryInterface(Ci.nsISupportsPRBool); subject.data = true; break; + case "idle": + if (this.idleService.idleTime > BOOKMARKS_ARCHIVE_IDLE_TIME * 1000) { + // Back up bookmarks. + this._archiveBookmarks(); + } + break; } - } -, + }, + // initialization (called on application startup) _init: function() { @@ -324,6 +338,14 @@ BrowserGlue.prototype = { return Sanitizer; }, + _idleService: null, + get idleService() { + if (!this._idleService) + this._idleService = Cc["@mozilla.org/widget/idleservice;1"]. + getService(Ci.nsIIdleService); + return this._idleService; + }, + /** * Initialize Places * - imports the bookmarks html file if bookmarks datastore is empty @@ -336,10 +358,11 @@ BrowserGlue.prototype = { var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"]. getService(Ci.nsINavHistoryService); + var prefBranch = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + var importBookmarks = false; try { - var prefBranch = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefBranch); importBookmarks = prefBranch.getBoolPref("browser.places.importBookmarksHTML"); } catch(ex) {} @@ -347,55 +370,95 @@ BrowserGlue.prototype = { // Call it here for Fx3 profiles created before the Places folder // has been added, otherwise it's called during import. this.ensurePlacesDefaultQueriesInitialized(); - return; } + else { + // get latest backup + Cu.import("resource://gre/modules/utils.js"); + var bookmarksFile = PlacesUtils.getMostRecentBackup(); - var dirService = Cc["@mozilla.org/file/directory_service;1"]. - getService(Ci.nsIProperties); - - var bookmarksFile = dirService.get("BMarks", Ci.nsILocalFile); - - if (bookmarksFile.exists()) { - // import the file - try { - var importer = - Cc["@mozilla.org/browser/places/import-export-service;1"]. - getService(Ci.nsIPlacesImportExportService); - importer.importHTMLFromFile(bookmarksFile, true); - } catch(ex) { - } finally { - prefBranch.setBoolPref("browser.places.importBookmarksHTML", false); + if (bookmarksFile && bookmarksFile.leafName.match("\.json$")) { + // restore a JSON backup + PlacesUtils.restoreBookmarksFromJSONFile(bookmarksFile); } + else { + // if there's no json backup... + // - use bookmarks.postplaces.html if it exists + // - otherwise use bookmarks.html - // only back up pre-places bookmarks.html if we plan on overwriting it - if (prefBranch.getBoolPref("browser.bookmarks.overwrite")) { - // backup pre-places bookmarks.html - // XXXtodo remove this before betas, after import/export is solid - var profDir = dirService.get("ProfD", Ci.nsILocalFile); - var bookmarksBackup = profDir.clone(); - bookmarksBackup.append("bookmarks.preplaces.html"); - if (!bookmarksBackup.exists()) { - // save old bookmarks.html file as bookmarks.preplaces.html - try { - bookmarksFile.copyTo(profDir, "bookmarks.preplaces.html"); - } catch(ex) { - dump("nsBrowserGlue::_initPlaces(): copy of bookmarks.html to bookmarks.preplaces.html failed: " + ex + "\n"); - } + var dirService = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + var bookmarksFile = dirService.get("BMarks", Ci.nsILocalFile); + + // bookmarks.postplaces.html + const POSTPLACES_BOOKMARKS_FILE = "bookmarks.postplaces.html"; + var bookmarksPostPlacesFile = bookmarksFile.clone(); + bookmarksPostPlacesFile.leafName = POSTPLACES_BOOKMARKS_FILE; + if (bookmarksPostPlacesFile.exists()) + bookmarksFile = bookmarksPostPlacesFile; + + // import the file + try { + var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. + getService(Ci.nsIPlacesImportExportService); + importer.importHTMLFromFile(bookmarksFile, true /* overwrite existing */); + } finally { + prefBranch.setBoolPref("browser.places.importBookmarksHTML", false); } } } + + // Initialize bookmark archiving on idle. + // Once a day, either on idle or shutdown, bookmarks are backed up. + this.idleService.addIdleObserver(this, BOOKMARKS_ARCHIVE_IDLE_TIME); }, /** * Places shut-down tasks * - back up and archive bookmarks + * - export bookmarks as HTML, if so configured + * + * Note: quit-application-granted notification is received twice + * so replace this method with a no-op when first called. */ _shutdownPlaces: function bg__shutdownPlaces() { - // backup bookmarks to bookmarks.html - var importer = + // Backup and archive Places bookmarks. + this._archiveBookmarks(); + + // Backup bookmarks to bookmarks.html to support apps that depend + // on the legacy format. + var autoExportHTML = false; + try { + autoExportHTML = prefs.getIntPref("browser.bookmarks.autoExportHTML"); + } catch(ex) {} + + if (autoExportHTML) { Cc["@mozilla.org/browser/places/import-export-service;1"]. - getService(Ci.nsIPlacesImportExportService); - importer.backupBookmarksFile(); + getService(Ci.nsIPlacesImportExportService). + backupBookmarksFile(); + } + }, + + /** + * Back up and archive bookmarks + */ + _archiveBookmarks: function nsBrowserGlue__archiveBookmarks() { + Cu.import("resource://gre/modules/utils.js"); + + var lastBackup = PlacesUtils.getMostRecentBackup(); + + // Backup bookmarks if there aren't any backups or + // they haven't been backed up in the last 24 hrs. + if (!lastBackup || + Date.now() - lastBackup.lastModifiedTime > BOOKMARKS_ARCHIVE_INTERVAL) { + var maxBackups = 5; + var prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + try { + maxBackups = prefs.getIntPref("browser.bookmarks.max_backups"); + } catch(ex) {} + + PlacesUtils.archiveBookmarksFile(maxBackups, false /* don't force */); + } }, _migrateUI: function bg__migrateUI() { diff --git a/browser/components/places/content/bookmarkProperties.js b/browser/components/places/content/bookmarkProperties.js index a1dd76f4f4c8..540d67e4c68b 100755 --- a/browser/components/places/content/bookmarkProperties.js +++ b/browser/components/places/content/bookmarkProperties.js @@ -567,8 +567,8 @@ var BookmarkPropertiesPanel = { var itemToSelect = userEnteredNameField; try { this._microsummaries = - PlacesUtils.microsummaries.getMicrosummaries(this._bookmarkURI, - this._bookmarkId); + PlacesUIUtils.microsummaries.getMicrosummaries(this._bookmarkURI, + this._bookmarkId); } catch(ex) { // getMicrosummaries will throw an exception if the page to which the URI @@ -590,8 +590,8 @@ var BookmarkPropertiesPanel = { var menuItem = this._createMicrosummaryMenuItem(microsummary); if (this._action == ACTION_EDIT && - PlacesUtils.microsummaries - .isMicrosummary(this._bookmarkId, microsummary)) + PlacesUIUtils.microsummaries + .isMicrosummary(this._bookmarkId, microsummary)) itemToSelect = menuItem; menupopup.appendChild(menuItem); @@ -713,7 +713,7 @@ var BookmarkPropertiesPanel = { try { var value = this._element(aTextboxID).value; if (value) { - var uri = PlacesUtils.createFixedURI(value); + var uri = PlacesUIUtils.createFixedURI(value); return true; } } catch (e) { } @@ -725,7 +725,7 @@ var BookmarkPropertiesPanel = { */ _getEditTitleTransaction: function BPP__getEditTitleTransaction(aItemId, aNewTitle) { - return PlacesUtils.ptm.editItemTitle(aItemId, aNewTitle); + return PlacesUIUtils.ptm.editItemTitle(aItemId, aNewTitle); }, /** @@ -813,21 +813,21 @@ var BookmarkPropertiesPanel = { // description var description = this._element("descriptionTextfield").value; if (description != this._itemDescription) { - transactions.push(PlacesUtils.ptm. + transactions.push(PlacesUIUtils.ptm. editItemDescription(itemId, description, this._itemType != BOOKMARK_ITEM)); } if (this._itemType == BOOKMARK_ITEM) { // location - var url = PlacesUtils.createFixedURI(this._element("editURLBar").value); + var url = PlacesUIUtils.createFixedURI(this._element("editURLBar").value); if (!this._bookmarkURI.equals(url)) - transactions.push(PlacesUtils.ptm.editBookmarkURI(itemId, url)); + transactions.push(PlacesUIUtils.ptm.editBookmarkURI(itemId, url)); // keyword transactions var newKeyword = this._element("keywordTextfield").value; if (newKeyword != this._bookmarkKeyword) { - transactions.push(PlacesUtils.ptm. + transactions.push(PlacesUIUtils.ptm. editBookmarkKeyword(itemId, newKeyword)); } @@ -841,39 +841,39 @@ var BookmarkPropertiesPanel = { // selected a microsummary which is not the one the bookmark previously // had. if ((newMicrosummary == null && - PlacesUtils.microsummaries.hasMicrosummary(itemId)) || + PlacesUIUtils.microsummaries.hasMicrosummary(itemId)) || (newMicrosummary != null && - !PlacesUtils.microsummaries - .isMicrosummary(itemId, newMicrosummary))) { + !PlacesUIUtils.microsummaries + .isMicrosummary(itemId, newMicrosummary))) { transactions.push( - PlacesUtils.ptm.editBookmarkMicrosummary(itemId, newMicrosummary)); + PlacesUIUtils.ptm.editBookmarkMicrosummary(itemId, newMicrosummary)); } // load in sidebar var loadInSidebarChecked = this._element("loadInSidebarCheckbox").checked; if (loadInSidebarChecked != this._loadBookmarkInSidebar) { transactions.push( - PlacesUtils.ptm.setLoadInSidebar(itemId, loadInSidebarChecked)); + PlacesUIUtils.ptm.setLoadInSidebar(itemId, loadInSidebarChecked)); } } else if (this._itemType == LIVEMARK_CONTAINER) { var feedURIString = this._element("feedLocationTextfield").value; - var feedURI = PlacesUtils.createFixedURI(feedURIString); + var feedURI = PlacesUIUtils.createFixedURI(feedURIString); if (!this._feedURI.equals(feedURI)) { transactions.push( - PlacesUtils.ptm.editLivemarkFeedURI(this._folderId, feedURI)); + PlacesUIUtils.ptm.editLivemarkFeedURI(this._folderId, feedURI)); } // Site Location is empty, we can set its URI to null var newSiteURIString = this._element("feedSiteLocationTextfield").value; var newSiteURI = null; if (newSiteURIString) - newSiteURI = PlacesUtils.createFixedURI(newSiteURIString); + newSiteURI = PlacesUIUtils.createFixedURI(newSiteURIString); if ((!newSiteURI && this._siteURI) || (newSiteURI && (!this._siteURI || !this._siteURI.equals(newSiteURI)))) { transactions.push( - PlacesUtils.ptm.editLivemarkSiteURI(this._folderId, newSiteURI)); + PlacesUIUtils.ptm.editLivemarkSiteURI(this._folderId, newSiteURI)); } } @@ -882,8 +882,8 @@ var BookmarkPropertiesPanel = { if (transactions.length > 0) { window.arguments[0].performed = true; var aggregate = - PlacesUtils.ptm.aggregateTransactions(this._getDialogTitle(), transactions); - PlacesUtils.ptm.doTransaction(aggregate); + PlacesUIUtils.ptm.aggregateTransactions(this._getDialogTitle(), transactions); + PlacesUIUtils.ptm.doTransaction(aggregate); } }, @@ -912,7 +912,7 @@ var BookmarkPropertiesPanel = { */ _getCreateNewBookmarkTransaction: function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) { - var uri = PlacesUtils.createFixedURI(this._element("editURLBar").value); + var uri = PlacesUIUtils.createFixedURI(this._element("editURLBar").value); var title = this._element("userEnteredName").label; var keyword = this._element("keywordTextfield").value; var annotations = []; @@ -928,20 +928,20 @@ var BookmarkPropertiesPanel = { var microsummary = this._element("namePicker").selectedItem.microsummary; if (microsummary) { childTransactions.push( - PlacesUtils.ptm.editBookmarkMicrosummary(-1, microsummary)); + PlacesUIUtils.ptm.editBookmarkMicrosummary(-1, microsummary)); } if (this._postData) { childTransactions.push( - PlacesUtils.ptm.editBookmarkPostData(-1, this._postData)); + PlacesUIUtils.ptm.editBookmarkPostData(-1, this._postData)); } - var transactions = [PlacesUtils.ptm.createItem(uri, aContainer, aIndex, - title, keyword, - annotations, - childTransactions)]; + var transactions = [PlacesUIUtils.ptm.createItem(uri, aContainer, aIndex, + title, keyword, + annotations, + childTransactions)]; - return PlacesUtils.ptm.aggregateTransactions(this._getDialogTitle(), transactions); + return PlacesUIUtils.ptm.aggregateTransactions(this._getDialogTitle(), transactions); }, /** @@ -953,7 +953,7 @@ var BookmarkPropertiesPanel = { for (var i = 0; i < this._URIList.length; ++i) { var uri = this._URIList[i]; var title = this._getURITitleFromHistory(uri); - transactions.push(PlacesUtils.ptm.createItem(uri, -1, -1, title)); + transactions.push(PlacesUIUtils.ptm.createItem(uri, -1, -1, title)); } return transactions; }, @@ -973,8 +973,8 @@ var BookmarkPropertiesPanel = { if (description) annotations.push(this._getDescriptionAnnotation(description)); - return PlacesUtils.ptm.createFolder(folderName, aContainer, aIndex, - annotations, childItemsTransactions); + return PlacesUIUtils.ptm.createFolder(folderName, aContainer, aIndex, + annotations, childItemsTransactions); }, /** @@ -984,16 +984,16 @@ var BookmarkPropertiesPanel = { _getCreateNewLivemarkTransaction: function BPP__getCreateNewLivemarkTransaction(aContainer, aIndex) { var feedURIString = this._element("feedLocationTextfield").value; - var feedURI = PlacesUtils.createFixedURI(feedURIString); + var feedURI = PlacesUIUtils.createFixedURI(feedURIString); var siteURIString = this._element("feedSiteLocationTextfield").value; var siteURI = null; if (siteURIString) - siteURI = PlacesUtils.createFixedURI(siteURIString); + siteURI = PlacesUIUtils.createFixedURI(siteURIString); var name = this._element("namePicker").value; - return PlacesUtils.ptm.createLivemark(feedURI, siteURI, name, - aContainer, aIndex); + return PlacesUIUtils.ptm.createLivemark(feedURI, siteURI, name, + aContainer, aIndex); }, /** @@ -1018,7 +1018,7 @@ var BookmarkPropertiesPanel = { // perfrom our transaction do via the transaction manager passed by the // opener so it can be undone. window.arguments[0].performed = true; - PlacesUtils.ptm.doTransaction(createTxn); + PlacesUIUtils.ptm.doTransaction(createTxn); }, onNamePickerInput: function BPP_onNamePickerInput() { @@ -1049,7 +1049,7 @@ var BookmarkPropertiesPanel = { if (!this._folderTree.place) { const FOLDER_TREE_PLACE_URI = "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" + - PlacesUtils.allBookmarksFolderId; + PlacesUIUtils.allBookmarksFolderId; this._folderTree.place = FOLDER_TREE_PLACE_URI; } diff --git a/browser/components/places/content/bookmarksPanel.js b/browser/components/places/content/bookmarksPanel.js index 5884a4d22a38..4f0b7de924db 100644 --- a/browser/components/places/content/bookmarksPanel.js +++ b/browser/components/places/content/bookmarksPanel.js @@ -37,7 +37,7 @@ function init() { document.getElementById("bookmarks-view").place = - "place:queryType=1&folder=" + window.top.PlacesUtils.allBookmarksFolderId; + "place:queryType=1&folder=" + window.top.PlacesUIUtils.allBookmarksFolderId; document.getElementById("search-box").focus(); } diff --git a/browser/components/places/content/controller.js b/browser/components/places/content/controller.js index ff470ae38a71..3ff5a0d1ebb9 100755 --- a/browser/components/places/content/controller.js +++ b/browser/components/places/content/controller.js @@ -103,9 +103,9 @@ PlacesController.prototype = { isCommandEnabled: function PC_isCommandEnabled(aCommand) { switch (aCommand) { case "cmd_undo": - return PlacesUtils.ptm.numberOfUndoItems > 0; + return PlacesUIUtils.ptm.numberOfUndoItems > 0; case "cmd_redo": - return PlacesUtils.ptm.numberOfRedoItems > 0; + return PlacesUIUtils.ptm.numberOfRedoItems > 0; case "cmd_cut": case "cmd_delete": return this._hasRemovableSelection(false); @@ -152,7 +152,7 @@ PlacesController.prototype = { case "placesCmd_reloadMicrosummary": var selectedNode = this._view.selectedNode; return selectedNode && PlacesUtils.nodeIsBookmark(selectedNode) && - PlacesUtils.microsummaries.hasMicrosummary(selectedNode.itemId); + PlacesUIUtils.microsummaries.hasMicrosummary(selectedNode.itemId); case "placesCmd_reload": // Livemark containers var selectedNode = this._view.selectedNode; @@ -192,10 +192,10 @@ PlacesController.prototype = { doCommand: function PC_doCommand(aCommand) { switch (aCommand) { case "cmd_undo": - PlacesUtils.ptm.undoTransaction(); + PlacesUIUtils.ptm.undoTransaction(); break; case "cmd_redo": - PlacesUtils.ptm.redoTransaction(); + PlacesUIUtils.ptm.redoTransaction(); break; case "cmd_cut": this.cut(); @@ -213,13 +213,13 @@ PlacesController.prototype = { this.selectAll(); break; case "placesCmd_open": - PlacesUtils.openNodeIn(this._view.selectedNode, "current"); + PlacesUIUtils.openNodeIn(this._view.selectedNode, "current"); break; case "placesCmd_open:window": - PlacesUtils.openNodeIn(this._view.selectedNode, "window"); + PlacesUIUtils.openNodeIn(this._view.selectedNode, "window"); break; case "placesCmd_open:tab": - PlacesUtils.openNodeIn(this._view.selectedNode, "tab"); + PlacesUIUtils.openNodeIn(this._view.selectedNode, "tab"); break; case "placesCmd_new:folder": this.newItem("folder"); @@ -337,8 +337,8 @@ PlacesController.prototype = { // if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely // pasteable, with no need to unwrap all the nodes. - var flavors = PlacesUtils.placesFlavors; - var clipboard = PlacesUtils.clipboard; + var flavors = PlacesUIUtils.placesFlavors; + var clipboard = PlacesUIUtils.clipboard; var hasPlacesData = clipboard.hasDataMatchingFlavors(flavors, flavors.length, Ci.nsIClipboard.kGlobalClipboard); @@ -442,7 +442,7 @@ PlacesController.prototype = { uri = PlacesUtils._uri(node.uri); if (PlacesUtils.nodeIsBookmark(node)) { nodeData["bookmark"] = true; - var mss = PlacesUtils.microsummaries; + var mss = PlacesUIUtils.microsummaries; if (mss.hasMicrosummary(node.itemId)) nodeData["microsummary"] = true; else if (node.parent && @@ -628,9 +628,9 @@ PlacesController.prototype = { return; if (PlacesUtils.nodeIsFolder(node)) - PlacesUtils.showFolderProperties(node.itemId); + PlacesUIUtils.showFolderProperties(node.itemId); else if (PlacesUtils.nodeIsBookmark(node)) - PlacesUtils.showBookmarkProperties(node.itemId); + PlacesUIUtils.showBookmarkProperties(node.itemId); }, /** @@ -656,7 +656,7 @@ PlacesController.prototype = { */ reloadSelectedMicrosummary: function PC_reloadSelectedMicrosummary() { var selectedNode = this._view.selectedNode; - var mss = PlacesUtils.microsummaries; + var mss = PlacesUIUtils.microsummaries; if (mss.hasMicrosummary(selectedNode.itemId)) mss.refreshMicrosummary(selectedNode.itemId); }, @@ -688,14 +688,14 @@ PlacesController.prototype = { GetStringFromName("brandShortName"); var buttonPressed = promptService.confirmEx(window, - PlacesUtils.getString("tabs.openWarningTitle"), - PlacesUtils.getFormattedString(messageKey, + PlacesUIUtils.getString("tabs.openWarningTitle"), + PlacesUIUtils.getFormattedString(messageKey, [numTabsToOpen, brandShortName]), (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0) + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1), - PlacesUtils.getString(openKey), + PlacesUIUtils.getString(openKey), null, null, - PlacesUtils.getFormattedString("tabs.openWarningPromptMeBranded", + PlacesUIUtils.getFormattedString("tabs.openWarningPromptMeBranded", [brandShortName]), warnOnOpen); @@ -714,9 +714,9 @@ PlacesController.prototype = { openSelectionInTabs: function PC_openLinksInTabs(aEvent) { var node = this._view.selectedNode; if (node && PlacesUtils.nodeIsContainer(node)) - PlacesUtils.openContainerNodeInTabs(this._view.selectedNode, aEvent); + PlacesUIUtils.openContainerNodeInTabs(this._view.selectedNode, aEvent); else - PlacesUtils.openURINodesInTabs(this._view.getSelectionNodes(), aEvent); + PlacesUIUtils.openURINodesInTabs(this._view.getSelectionNodes(), aEvent); }, /** @@ -732,11 +732,11 @@ PlacesController.prototype = { var performed = false; if (aType == "bookmark") - performed = PlacesUtils.showAddBookmarkUI(null, null, null, ip); + performed = PlacesUIUtils.showAddBookmarkUI(null, null, null, ip); else if (aType == "livemark") - performed = PlacesUtils.showAddLivemarkUI(null, null, null, null, ip); + performed = PlacesUIUtils.showAddLivemarkUI(null, null, null, null, ip); else // folder - performed = PlacesUtils.showAddFolderUI(null, ip); + performed = PlacesUIUtils.showAddFolderUI(null, ip); if (performed) { // select the new item @@ -757,7 +757,7 @@ PlacesController.prototype = { throw Cr.NS_ERROR_NOT_AVAILABLE; var performed = false; - performed = PlacesUtils.showAddFolderUI(null, ip); + performed = PlacesUIUtils.showAddFolderUI(null, ip); if (performed) { // select the new item var insertedNodeId = PlacesUtils.bookmarks @@ -773,8 +773,8 @@ PlacesController.prototype = { var ip = this._view.insertionPoint; if (!ip) throw Cr.NS_ERROR_NOT_AVAILABLE; - var txn = PlacesUtils.ptm.createSeparator(ip.itemId, ip.index); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.createSeparator(ip.itemId, ip.index); + PlacesUIUtils.ptm.doTransaction(txn); // select the new item var insertedNodeId = PlacesUtils.bookmarks .getIdForItemAt(ip.itemId, ip.index); @@ -795,8 +795,8 @@ PlacesController.prototype = { */ sortFolderByName: function PC_sortFolderByName() { var itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode); - var txn = PlacesUtils.ptm.sortFolderByName(itemId); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.sortFolderByName(itemId); + PlacesUIUtils.ptm.doTransaction(txn); }, /** @@ -856,7 +856,7 @@ PlacesController.prototype = { if (PlacesUtils.nodeIsFolder(node)) removedFolders.push(node); - transactions.push(PlacesUtils.ptm.removeItem(node.itemId)); + transactions.push(PlacesUIUtils.ptm.removeItem(node.itemId)); } }, @@ -873,8 +873,8 @@ PlacesController.prototype = { for (var i = ranges.length - 1; i >= 0 ; --i) this._removeRange(ranges[i], transactions); if (transactions.length > 0) { - var txn = PlacesUtils.ptm.aggregateTransactions(txnName, transactions); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.aggregateTransactions(txnName, transactions); + PlacesUIUtils.ptm.doTransaction(txn); } }, @@ -997,7 +997,7 @@ PlacesController.prototype = { var data = new TransferData(); function addData(type, overrideURI) { - data.addDataForFlavour(type, PlacesUtils._wrapString( + data.addDataForFlavour(type, PlacesUIUtils._wrapString( PlacesUtils.wrapNode(node, type, overrideURI))); } @@ -1075,7 +1075,7 @@ PlacesController.prototype = { function addData(type, data) { xferable.addDataFlavor(type); - xferable.setTransferData(type, PlacesUtils._wrapString(data), data.length * 2); + 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. @@ -1089,7 +1089,7 @@ PlacesController.prototype = { addData(PlacesUtils.TYPE_HTML, htmlString); if (placeString || unicodeString || htmlString || mozURLString) { - PlacesUtils.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard); + PlacesUIUtils.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard); } } finally { @@ -1116,6 +1116,7 @@ PlacesController.prototype = { // 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. + dump("PASTING\n"); /** * Constructs a transferable that can receive data of specific types. @@ -1133,7 +1134,7 @@ PlacesController.prototype = { return xferable; } - var clipboard = PlacesUtils.clipboard; + var clipboard = PlacesUIUtils.clipboard; var ip = this._view.insertionPoint; if (!ip) @@ -1161,9 +1162,9 @@ PlacesController.prototype = { // transactions insert differently if index == -1 if (ip.index > -1) index = ip.index + i; - transactions.push(PlacesUtils.makeTransaction(items[i], type.value, - ip.itemId, index, - true)); + transactions.push(PlacesUIUtils.makeTransaction(items[i], type.value, + ip.itemId, index, + true)); } return transactions; } @@ -1182,8 +1183,8 @@ PlacesController.prototype = { var transactions = getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE, PlacesUtils.TYPE_X_MOZ_URL, PlacesUtils.TYPE_UNICODE]); - var txn = PlacesUtils.ptm.aggregateTransactions("Paste", transactions); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.aggregateTransactions("Paste", transactions); + PlacesUIUtils.ptm.doTransaction(txn); // select the pasted items, they should be consecutive var insertedNodeIds = []; @@ -1243,7 +1244,7 @@ var PlacesControllerDragHelper = { canDrop: function PCDH_canDrop() { var session = this.getSession(); if (session) { - var types = PlacesUtils.GENERIC_VIEW_DROP_TYPES; + var types = PlacesUIUtils.GENERIC_VIEW_DROP_TYPES; for (var i = 0; i < types.length; ++i) { if (session.isDataFlavorSupported(types[i])) return true; @@ -1263,7 +1264,7 @@ var PlacesControllerDragHelper = { _initTransferable: function PCDH__initTransferable(session) { var xferable = Cc["@mozilla.org/widget/transferable;1"]. createInstance(Ci.nsITransferable); - var types = PlacesUtils.GENERIC_VIEW_DROP_TYPES; + var types = PlacesUIUtils.GENERIC_VIEW_DROP_TYPES; for (var i = 0; i < types.length; ++i) { if (session.isDataFlavorSupported(types[i])) xferable.addDataFlavor(types[i]); @@ -1306,13 +1307,13 @@ var PlacesControllerDragHelper = { movedCount++; } - transactions.push(PlacesUtils.makeTransaction(unwrapped, + transactions.push(PlacesUIUtils.makeTransaction(unwrapped, flavor.value, insertionPoint.itemId, index, copy)); } - var txn = PlacesUtils.ptm.aggregateTransactions("DropItems", transactions); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.aggregateTransactions("DropItems", transactions); + PlacesUIUtils.ptm.doTransaction(txn); } }; diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js index d957f9f3a521..586805568c71 100644 --- a/browser/components/places/content/editBookmarkOverlay.js +++ b/browser/components/places/content/editBookmarkOverlay.js @@ -145,7 +145,7 @@ var gEditItemOverlay = { // description field this._initTextField("descriptionField", - PlacesUtils.getItemDescription(this._itemId)); + PlacesUIUtils.getItemDescription(this._itemId)); this._showHideRows(); @@ -324,8 +324,8 @@ var gEditItemOverlay = { try { if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK && !this._readOnly) - this._microsummaries = PlacesUtils.microsummaries - .getMicrosummaries(this._uri, -1); + this._microsummaries = PlacesUIUtils.microsummaries + .getMicrosummaries(this._uri, -1); } catch(ex) { // getMicrosummaries will throw an exception in at least two cases: @@ -346,8 +346,8 @@ var gEditItemOverlay = { var microsummary = enumerator.getNext() .QueryInterface(Ci.nsIMicrosummary); var menuItem = this._createMicrosummaryMenuItem(microsummary); - if (PlacesUtils.microsummaries - .isMicrosummary(this._itemId, microsummary)) + if (PlacesUIUtils.microsummaries + .isMicrosummary(this._itemId, microsummary)) itemToSelect = menuItem; menupopup.appendChild(menuItem); @@ -449,12 +449,12 @@ var gEditItemOverlay = { } if (tagsToAdd.length > 0) { - var tagTxn = PlacesUtils.ptm.tagURI(this._uri, tagsToAdd); - PlacesUtils.ptm.doTransaction(tagTxn); + var tagTxn = PlacesUIUtils.ptm.tagURI(this._uri, tagsToAdd); + PlacesUIUtils.ptm.doTransaction(tagTxn); } if (tagsToRemove.length > 0) { - var untagTxn = PlacesUtils.ptm.untagURI(this._uri, tagsToRemove); - PlacesUtils.ptm.doTransaction(untagTxn); + var untagTxn = PlacesUIUtils.ptm.untagURI(this._uri, tagsToRemove); + PlacesUIUtils.ptm.doTransaction(untagTxn); } } }, @@ -470,12 +470,12 @@ var gEditItemOverlay = { var namePicker = this._element("namePicker") var txns = []; - const ptm = PlacesUtils.ptm; + const ptm = PlacesUIUtils.ptm; // Here we update either the item title or its cached static title var newTitle = this._element("userEnteredName").label; if (this._getItemStaticTitle() != newTitle) { - if (PlacesUtils.microsummaries.hasMicrosummary(this._itemId)) { + if (PlacesUIUtils.microsummaries.hasMicrosummary(this._itemId)) { // Note: this implicitly also takes care of the microsummary->static // title case, the removeMicorosummary method in the service will set // the item-title to the value of this annotation. @@ -496,10 +496,10 @@ var gEditItemOverlay = { // bookmark previously had one, or the user selected a microsummary which // is not the one the bookmark previously had if ((newMicrosummary == null && - PlacesUtils.microsummaries.hasMicrosummary(this._itemId)) || + PlacesUIUtils.microsummaries.hasMicrosummary(this._itemId)) || (newMicrosummary != null && - !PlacesUtils.microsummaries - .isMicrosummary(this._itemId, newMicrosummary))) { + !PlacesUIUtils.microsummaries + .isMicrosummary(this._itemId, newMicrosummary))) { txns.push(ptm.editBookmarkMicrosummary(this._itemId, newMicrosummary)); } @@ -510,67 +510,67 @@ var gEditItemOverlay = { onDescriptionFieldBlur: function EIO_onDescriptionFieldInput() { var description = this._element("descriptionField").value; if (description != PlacesUtils.getItemDescription(this._itemId)) { - var txn = PlacesUtils.ptm - .editItemDescription(this._itemId, description); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm + .editItemDescription(this._itemId, description); + PlacesUIUtils.ptm.doTransaction(txn); } }, onLocationFieldBlur: function EIO_onLocationFieldBlur() { var uri; try { - uri = PlacesUtils.createFixedURI(this._element("locationField").value); + uri = PlacesUIUtils.createFixedURI(this._element("locationField").value); } catch(ex) { return; } if (!this._uri.equals(uri)) { - var txn = PlacesUtils.ptm.editBookmarkURI(this._itemId, uri); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.editBookmarkURI(this._itemId, uri); + PlacesUIUtils.ptm.doTransaction(txn); } }, onKeywordFieldBlur: function EIO_onKeywordFieldBlur() { var keyword = this._element("keywordField").value; if (keyword != PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId)) { - var txn = PlacesUtils.ptm.editBookmarkKeyword(this._itemId, keyword); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.editBookmarkKeyword(this._itemId, keyword); + PlacesUIUtils.ptm.doTransaction(txn); } }, onFeedLocationFieldBlur: function EIO_onFeedLocationFieldBlur() { var uri; try { - uri = PlacesUtils.createFixedURI(this._element("feedLocationField").value); + uri = PlacesUIUtils.createFixedURI(this._element("feedLocationField").value); } catch(ex) { return; } var currentFeedURI = PlacesUtils.livemarks.getFeedURI(this._itemId); if (!currentFeedURI.equals(uri)) { - var txn = PlacesUtils.ptm.editLivemarkFeedURI(this._itemId, uri); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.editLivemarkFeedURI(this._itemId, uri); + PlacesUIUtils.ptm.doTransaction(txn); } }, onSiteLocationFieldBlur: function EIO_onSiteLocationFieldBlur() { var uri = null; try { - uri = PlacesUtils.createFixedURI(this._element("siteLocationField").value); + uri = PlacesUIUtils.createFixedURI(this._element("siteLocationField").value); } catch(ex) { } var currentSiteURI = PlacesUtils.livemarks.getSiteURI(this._itemId); if (!uri || !currentSiteURI.equals(uri)) { - var txn = PlacesUtils.ptm.editLivemarkSiteURI(this._itemId, uri); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.editLivemarkSiteURI(this._itemId, uri); + PlacesUIUtils.ptm.doTransaction(txn); } }, onLoadInSidebarCheckboxCommand: function EIO_onLoadInSidebarCheckboxCommand() { var loadInSidebarChecked = this._element("loadInSidebarCheckbox").checked; - var txn = PlacesUtils.ptm.setLoadInSidebar(this._itemId, - loadInSidebarChecked); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.setLoadInSidebar(this._itemId, + loadInSidebarChecked); + PlacesUIUtils.ptm.doTransaction(txn); }, toggleFolderTreeVisibility: function EIO_toggleFolderTreeVisibility() { @@ -591,7 +591,7 @@ var gEditItemOverlay = { if (!this._folderTree.place) { const FOLDER_TREE_PLACE_URI = "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" + - window.top.PlacesUtils.allBookmarksFolderId; + window.top.PlacesUIUtils.allBookmarksFolderId; this._folderTree.place = FOLDER_TREE_PLACE_URI; } @@ -651,8 +651,8 @@ var gEditItemOverlay = { // Move the item var container = this._getFolderIdFromMenuList(); if (PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) != container) { - var txn = PlacesUtils.ptm.moveItem(this._itemId, container, -1); - PlacesUtils.ptm.doTransaction(txn); + var txn = PlacesUIUtils.ptm.moveItem(this._itemId, container, -1); + PlacesUIUtils.ptm.doTransaction(txn); // Mark the containing folder as recently-used if it isn't in the // static list diff --git a/browser/components/places/content/history-panel.js b/browser/components/places/content/history-panel.js index cd52ec41c101..e83a049924b0 100644 --- a/browser/components/places/content/history-panel.js +++ b/browser/components/places/content/history-panel.js @@ -104,7 +104,7 @@ function historyAddBookmarks() // or if the selected item is not a URI node var node = gHistoryTree.selectedNode; if (node && PlacesUtils.nodeIsURI(node)) - PlacesUtils.showMinimalAddBookmarkUI(PlacesUtils._uri(node.uri), node.title); + PlacesUIUtils.showMinimalAddBookmarkUI(PlacesUtils._uri(node.uri), node.title); } function searchHistory(aInput) diff --git a/browser/components/places/content/menu.xml b/browser/components/places/content/menu.xml index e824d538abeb..29d1477d0e14 100755 --- a/browser/components/places/content/menu.xml +++ b/browser/components/places/content/menu.xml @@ -215,7 +215,7 @@ @@ -601,7 +601,7 @@ + oncommand="PlacesOrganizer.onRestoreBookmarksFromFile();"/> 0) @@ -829,7 +829,7 @@ PlacesTreeView.prototype = { { var uri = aNode.uri; NS_ASSERT(uri, "if there is no uri, we can't persist the open state"); - return uri ? PlacesUtils.RDF.GetResource(uri) : null; + return uri ? PlacesUIUtils.RDF.GetResource(uri) : null; }, // nsITreeView @@ -1117,7 +1117,7 @@ PlacesTreeView.prototype = { // if they go through the "result" API. if (PlacesUtils.nodeIsSeparator(node)) return ""; - return node.title || PlacesUtils.getString("noTitle"); + return node.title || PlacesUIUtils.getString("noTitle"); case this.COLUMN_TYPE_TAGS: return node.tags; case this.COLUMN_TYPE_URI: @@ -1191,13 +1191,13 @@ PlacesTreeView.prototype = { var resource = this._getResourceForNode(node); if (resource) { - const openLiteral = PlacesUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open"); - const trueLiteral = PlacesUtils.RDF.GetLiteral("true"); + const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open"); + const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true"); if (node.containerOpen) - PlacesUtils.localStore.Unassert(resource, openLiteral, trueLiteral); + PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral); else - PlacesUtils.localStore.Assert(resource, openLiteral, trueLiteral, true); + PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true); } node.containerOpen = !node.containerOpen; diff --git a/browser/components/places/content/utils.js b/browser/components/places/content/utils.js index b46d541c721f..5e1c2a5d52a5 100644 --- a/browser/components/places/content/utils.js +++ b/browser/components/places/content/utils.js @@ -23,6 +23,7 @@ * Myk Melez * Asaf Romano * Sungjoon Steve Won + * Dietrich Ayala * * 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 @@ -46,11 +47,11 @@ var Ci = Components.interfaces; var Cc = Components.classes; var Cr = Components.results; -Components.utils.import("resource://gre/modules/JSON.jsm"); +Components.utils.import("resource://gre/modules/utils.js"); +Components.utils.import("resource://gre/modules/debug.js"); const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar"; const DESCRIPTION_ANNO = "bookmarkProperties/description"; -const POST_DATA_ANNO = "bookmarkProperties/POSTData"; const LMANNO_FEEDURI = "livemark/feedURI"; const LMANNO_SITEURI = "livemark/siteURI"; const ORGANIZER_FOLDER_ANNO = "PlacesOrganizer/OrganizerFolder"; @@ -80,71 +81,7 @@ function asFullVisit(aNode){ return QI_node(aNode, Ci.nsINavHistoryFullVisitResu function asContainer(aNode){ return QI_node(aNode, Ci.nsINavHistoryContainerResultNode);} function asQuery(aNode) { return QI_node(aNode, Ci.nsINavHistoryQueryResultNode); } -var PlacesUtils = { - // Place entries that are containers, e.g. bookmark folders or queries. - TYPE_X_MOZ_PLACE_CONTAINER: "text/x-moz-place-container", - // Place entries that are bookmark separators. - TYPE_X_MOZ_PLACE_SEPARATOR: "text/x-moz-place-separator", - // Place entries that are not containers or separators - TYPE_X_MOZ_PLACE: "text/x-moz-place", - // Place entries in shortcut url format (url\ntitle) - TYPE_X_MOZ_URL: "text/x-moz-url", - // Place entries formatted as HTML anchors - TYPE_HTML: "text/html", - // Place entries as raw URL text - TYPE_UNICODE: "text/unicode", - - /** - * The Bookmarks Service. - */ - get bookmarks() { - delete this.bookmarks; - return this.bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); - }, - - /** - * The Nav History Service. - */ - get history() { - delete this.history; - return this.history = Cc["@mozilla.org/browser/nav-history-service;1"]. - getService(Ci.nsINavHistoryService); - }, - - get globalHistory() { - delete this.globalHistory; - return this.globalHistory = Cc["@mozilla.org/browser/global-history;2"]. - getService(Ci.nsIBrowserHistory); - }, - - /** - * The Live Bookmark Service. - */ - get livemarks() { - delete this.livemarks; - return this.livemarks = Cc["@mozilla.org/browser/livemark-service;2"]. - getService(Ci.nsILivemarkService); - }, - - /** - * The Annotations Service. - */ - get annotations() { - delete this.annotations; - return this.annotations = Cc["@mozilla.org/browser/annotation-service;1"]. - getService(Ci.nsIAnnotationService); - }, - - /** - * The Favicons Service - */ - get favicons() { - delete this.favicons; - return this.favicons = Cc["@mozilla.org/browser/favicon-service;1"]. - getService(Ci.nsIFaviconService); - }, - +var PlacesUIUtils = { /** * The Microsummary Service */ @@ -154,15 +91,6 @@ var PlacesUtils = { getService(Ci.nsIMicrosummaryService); }, - /** - * The Places Tagging Service - */ - get tagging() { - delete this.tagging; - return this.tagging = Cc["@mozilla.org/browser/tagging-service;1"]. - getService(Ci.nsITaggingService); - }, - get RDF() { delete this.RDF; return this.RDF = Cc["@mozilla.org/rdf/rdf-service;1"]. @@ -192,19 +120,6 @@ var PlacesUtils = { getService(Ci.nsIURIFixup); }, - /** - * Makes a URI from a spec. - * @param aSpec - * The string spec of the URI - * @returns A URI object for the spec. - */ - _uri: function PU__uri(aSpec) { - NS_ASSERT(aSpec, "empty URL spec"); - return Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService). - newURI(aSpec, null, null); - }, - /** * Makes a URI from a spec, and do fixup * @param aSpec @@ -248,416 +163,6 @@ var PlacesUtils = { return this._bundle.GetStringFromName(key); }, - /** - * Determines whether or not a ResultNode is a Bookmark folder. - * @param aNode - * A result node - * @returns true if the node is a Bookmark folder, false otherwise - */ - nodeIsFolder: function PU_nodeIsFolder(aNode) { - NS_ASSERT(aNode, "null node"); - return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER || - aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT); - }, - - /** - * Determines whether or not a ResultNode represents a bookmarked URI. - * @param aNode - * A result node - * @returns true if the node represents a bookmarked URI, false otherwise - */ - nodeIsBookmark: function PU_nodeIsBookmark(aNode) { - NS_ASSERT(aNode, "null node"); - return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI && - aNode.itemId != -1; - }, - - /** - * Determines whether or not a ResultNode is a Bookmark separator. - * @param aNode - * A result node - * @returns true if the node is a Bookmark separator, false otherwise - */ - nodeIsSeparator: function PU_nodeIsSeparator(aNode) { - NS_ASSERT(aNode, "null node"); - - return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR); - }, - - /** - * Determines whether or not a ResultNode is a visit item. - * @param aNode - * A result node - * @returns true if the node is a visit item, false otherwise - */ - nodeIsVisit: function PU_nodeIsVisit(aNode) { - NS_ASSERT(aNode, "null node"); - - const NHRN = Ci.nsINavHistoryResultNode; - var type = aNode.type; - return type == NHRN.RESULT_TYPE_VISIT || - type == NHRN.RESULT_TYPE_FULL_VISIT; - }, - - /** - * Determines whether or not a ResultNode is a URL item. - * @param aNode - * A result node - * @returns true if the node is a URL item, false otherwise - */ - uriTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_URI, - Ci.nsINavHistoryResultNode.RESULT_TYPE_VISIT, - Ci.nsINavHistoryResultNode.RESULT_TYPE_FULL_VISIT], - nodeIsURI: function PU_nodeIsURI(aNode) { - NS_ASSERT(aNode, "null node"); - return this.uriTypes.indexOf(aNode.type) != -1; - }, - - /** - * Determines whether or not a ResultNode is a Query item. - * @param aNode - * A result node - * @returns true if the node is a Query item, false otherwise - */ - nodeIsQuery: function PU_nodeIsQuery(aNode) { - NS_ASSERT(aNode, "null node"); - return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY; - }, - - /** - * Determines if a node is read only (children cannot be inserted, sometimes - * they cannot be removed depending on the circumstance) - * @param aNode - * A result node - * @returns true if the node is readonly, false otherwise - */ - nodeIsReadOnly: function PU_nodeIsReadOnly(aNode) { - NS_ASSERT(aNode, "null node"); - - if (this.nodeIsFolder(aNode)) - return this.bookmarks.getFolderReadonly(asQuery(aNode).folderItemId); - if (this.nodeIsQuery(aNode)) - return asQuery(aNode).childrenReadOnly; - return false; - }, - - /** - * Determines whether or not a ResultNode is a host container. - * @param aNode - * A result node - * @returns true if the node is a host container, false otherwise - */ - nodeIsHost: function PU_nodeIsHost(aNode) { - NS_ASSERT(aNode, "null node"); - return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY && - aNode.parent && - asQuery(aNode.parent).queryOptions.resultType == - Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY; - }, - - /** - * Determines whether or not a ResultNode is a day container. - * @param node - * A NavHistoryResultNode - * @returns true if the node is a day container, false otherwise - */ - nodeIsDay: function PU_nodeIsDay(aNode) { - NS_ASSERT(aNode, "null node"); - var resultType; - return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY && - aNode.parent && - ((resultType = asQuery(aNode.parent).queryOptions.resultType) == - Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY || - resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY); - }, - - /** - * Determines whether or not a ResultNode is a container. - * @param aNode - * A result node - * @returns true if the node is a container item, false otherwise - */ - containerTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, - Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT, - Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY, - Ci.nsINavHistoryResultNode.RESULT_TYPE_DYNAMIC_CONTAINER], - nodeIsContainer: function PU_nodeIsContainer(aNode) { - NS_ASSERT(aNode, "null node"); - return this.containerTypes.indexOf(aNode.type) != -1; - }, - - /** - * Determines whether or not a result-node is a dynamic-container item. - * The dynamic container result node type is for dynamically created - * containers (e.g. for the file browser service where you get your folders - * in bookmark menus). - * @param aNode - * A result node - * @returns true if the node is a dynamic container item, false otherwise - */ - nodeIsDynamicContainer: function PU_nodeIsDynamicContainer(aNode) { - NS_ASSERT(aNode, "null node"); - if (aNode.type == NHRN.RESULT_TYPE_DYNAMIC_CONTAINER) - return true; - return false; - }, - - /** - * Determines whether a result node is a remote container registered by the - * livemark service. - * @param aNode - * A result Node - * @returns true if the node is a livemark container item - */ - nodeIsLivemarkContainer: function PU_nodeIsLivemarkContainer(aNode) { - // Use the annotations service directly to avoid instantiating - // the Livemark service on startup. (bug 398300) - return this.nodeIsFolder(aNode) && - this.annotations.itemHasAnnotation(aNode.itemId, LMANNO_FEEDURI); - }, - - /** - * Determines whether a result node is a live-bookmark item - * @param aNode - * A result node - * @returns true if the node is a livemark container item - */ - nodeIsLivemarkItem: function PU_nodeIsLivemarkItem(aNode) { - return aNode.parent && this.nodeIsLivemarkContainer(aNode.parent); - }, - - /** - * Determines whether or not a node is a readonly folder. - * @param aNode - * The node to test. - * @returns true if the node is a readonly folder. - */ - isReadonlyFolder: function(aNode) { - NS_ASSERT(aNode, "null node"); - - return this.nodeIsFolder(aNode) && - this.bookmarks.getFolderReadonly(asQuery(aNode).folderItemId); - }, - - /** - * Gets the concrete item-id for the given node. Generally, this is just - * node.itemId, but for folder-shortcuts that's node.folderItemId. - */ - getConcreteItemId: function PU_getConcreteItemId(aNode) { - if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) - return asQuery(aNode).folderItemId; - return aNode.itemId; - }, - - /** - * Gets the index of a node within its parent container - * @param aNode - * The node to look up - * @returns The index of the node within its parent container, or -1 if the - * node was not found or the node specified has no parent. - */ - getIndexOfNode: function PU_getIndexOfNode(aNode) { - NS_ASSERT(aNode, "null node"); - - var parent = aNode.parent; - if (!parent) - return -1; - var wasOpen = parent.containerOpen; - var result, oldViewer; - if (!wasOpen) { - result = parent.parentResult; - oldViewer = result.viewer; - result.viewer = null; - parent.containerOpen = true; - } - var cc = parent.childCount; - for (var i = 0; i < cc && parent.getChild(i) != aNode; ++i); - if (!wasOpen) { - parent.containerOpen = false; - result.viewer = oldViewer; - } - return i < cc ? i : -1; - }, - - /** - * String-wraps a result node according to the rules of the specified - * content type. - * @param aNode - * The Result node to wrap (serialize) - * @param aType - * The content type to serialize as - * @param [optional] aOverrideURI - * Used instead of the node's URI if provided. - * This is useful for wrapping a container as TYPE_X_MOZ_URL, - * TYPE_HTML or TYPE_UNICODE. - * @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: - 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)) { - asContainer(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 JSON.toString(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 + NEWLINE + bNode.title; - } - if (self.nodeIsURI(bNode)) - return (aOverrideURI || bNode.uri) + NEWLINE + bNode.title; - // 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(/" + escapedTitle + "" + NEWLINE; - } - if (self.nodeIsContainer(bNode)) { - asContainer(bNode); - var wasOpen = bNode.containerOpen; - if (!wasOpen) - bNode.containerOpen = true; - - var childString = "
" + escapedTitle + "
" + NEWLINE; - var cc = bNode.childCount; - for (var i = 0; i < cc; ++i) - childString += "
" - + NEWLINE - + gatherDataHtml(bNode.getChild(i)) - + "
" - + NEWLINE; - bNode.containerOpen = wasOpen; - return childString + "
" + NEWLINE; - } - if (self.nodeIsURI(bNode)) - return "" + escapedTitle + "" + NEWLINE; - if (self.nodeIsSeparator(bNode)) - return "
" + NEWLINE; - return ""; - } - return gatherDataHtml(convertNode(aNode)); - } - // case this.TYPE_UNICODE: - function gatherDataText(bNode) { - if (self.nodeIsLivemarkContainer(bNode)) - return self.livemarks.getSiteURI(bNode.itemId).spec; - if (self.nodeIsContainer(bNode)) { - asContainer(bNode); - var wasOpen = bNode.containerOpen; - if (!wasOpen) - bNode.containerOpen = true; - - var childString = bNode.title + NEWLINE; - var cc = bNode.childCount; - for (var i = 0; i < cc; ++i) { - var child = bNode.getChild(i); - var suffix = i < (cc - 1) ? NEWLINE : ""; - childString += gatherDataText(child) + suffix; - } - bNode.containerOpen = wasOpen; - return childString; - } - if (self.nodeIsURI(bNode)) - return (aOverrideURI || bNode.uri); - if (self.nodeIsSeparator(bNode)) - return "--------------------"; - return ""; - } - - return gatherDataText(convertNode(aNode)); - }, - /** * Get a transaction for copying a uri item from one container to another * as a bookmark. @@ -670,7 +175,7 @@ var PlacesUtils = { * @returns A nsITransaction object that performs the copy. */ _getURIItemCopyTransaction: function (aData, aContainer, aIndex) { - return this.ptm.createItem(this._uri(aData.uri), aContainer, aIndex, + return this.ptm.createItem(PlacesUtils._uri(aData.uri), aContainer, aIndex, aData.title, ""); }, @@ -691,15 +196,14 @@ var PlacesUtils = { _getBookmarkItemCopyTransaction: function PU__getBookmarkItemCopyTransaction(aData, aContainer, aIndex, aExcludeAnnotations) { - var itemURL = this._uri(aData.uri); + var itemURL = PlacesUtils._uri(aData.uri); var itemTitle = aData.title; - var keyword = aData.keyword; - var annos = aData.annos; + var keyword = aData.keyword || null; + var annos = aData.annos || []; if (aExcludeAnnotations) { - annos = - annos.filter(function(aValue, aIndex, aArray) { - return aExcludeAnnotations.indexOf(aValue.name) == -1; - }); + annos = annos.filter(function(aValue, aIndex, aArray) { + return aExcludeAnnotations.indexOf(aValue.name) == -1; + }); } var childTxns = []; if (aData.dateAdded) @@ -739,25 +243,23 @@ var PlacesUtils = { 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 = self.ptm.createFolder(title, -1, index, annos, + if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) { + if (node.livemark && node.annos) // node is a livemark + txn = self._getLivemarkCopyTransaction(node, aContainer, index); + else { + var folderItemsTransactions = []; + if (node.dateAdded) + folderItemsTransactions.push(self.ptm.editItemDateAdded(null, node.dateAdded)); + if (node.lastModified) + folderItemsTransactions.push(self.ptm.editItemLastModified(null, node.lastModified)); + var annos = node.annos || []; + txn = self.ptm.createFolder(node.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 = self.ptm.createLivemark(feedURI, siteURI, node.title, - aContainer, index, node.annos); - } } - else if (node.type == self.TYPE_X_MOZ_PLACE_SEPARATOR) + else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) txn = self.ptm.createSeparator(-1, index); - else if (node.type == self.TYPE_X_MOZ_PLACE) + else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) txn = self._getBookmarkItemCopyTransaction(node, -1, index); NS_ASSERT(txn, "Unexpected item under a bookmarks folder"); @@ -767,75 +269,51 @@ var PlacesUtils = { return childItemsTransactions; } - var title = aData.folder.title; - var annos = aData.folder.annos; - var childItems = getChildItemsTransactions(aData.children); - if (aData.folder.dateAdded) - childItems.push(this.ptm.editItemDateAdded(null, aData.folder.dateAdded)); - if (aData.folder.lastModified) - childItems.push(this.ptm.editItemLastModified(null, aData.folder.lastModified)); - return this.ptm.createFolder(title, aContainer, aIndex, annos, childItems); + // tag folders use tag transactions + if (aContainer == PlacesUtils.bookmarks.tagsFolder) { + var txns = []; + if (aData.children) { + aData.children.forEach(function(aChild) { + txns.push(this.ptm.tagURI(PlacesUtils._uri(aChild.uri), [aData.title])); + }, this); + } + return this.ptm.aggregateTransactions("addTags", txns); + } + else if (aData.livemark && aData.annos) { + // Place is a Livemark Container + return this._getLivemarkCopyTransaction(aData, aContainer, aIndex); + } + else { + var childItems = getChildItemsTransactions(aData.children); + if (aData.dateAdded) + childItems.push(this.ptm.editItemDateAdded(null, aData.dateAdded)); + if (aData.lastModified) + childItems.push(this.ptm.editItemLastModified(null, aData.lastModified)); + + var annos = aData.annos || []; + return this.ptm.createFolder(aData.title, aContainer, aIndex, annos, childItems); + } }, - /** - * Unwraps data from the Clipboard or the current Drag Session. - * @param blob - * A blob (string) of data, in some format we potentially know how - * to parse. - * @param type - * The content type of the blob. - * @returns An array of objects representing each item contained by the source. - */ - unwrapNodes: function PU_unwrapNodes(blob, type) { - // We split on "\n" because the transferable system converts "\r\n" to "\n" - var nodes = []; - switch(type) { - case this.TYPE_X_MOZ_PLACE: - case this.TYPE_X_MOZ_PLACE_SEPARATOR: - case this.TYPE_X_MOZ_PLACE_CONTAINER: - nodes = JSON.fromString("[" + blob + "]"); - break; - case this.TYPE_X_MOZ_URL: - 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 - // but drag and drop of files from the shell has parts.length = 1 - if (parts.length != 1 && parts.length % 2) - break; - for (var i = 0; i < parts.length; i=i+2) { - var uriString = parts[i]; - var titleString = ""; - if (parts.length > i+1) - titleString = parts[i+1]; - else { - // for drag and drop of files, try to use the leafName as title - try { - titleString = this._uri(uriString).QueryInterface(Ci.nsIURL) - .fileName; - } - catch (e) {} - } - // 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: - 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; + _getLivemarkCopyTransaction: + function PU__getLivemarkCopyTransaction(aData, aContainer, aIndex) { + NS_ASSERT(aData.livemark && aData.annos, "node is not a livemark"); + // Place is a Livemark Container + var feedURI = null; + var siteURI = null; + aData.annos = aData.annos.filter(function(aAnno) { + if (aAnno.name == LMANNO_FEEDURI) { + feedURI = this._uri(aAnno.value); + return false; + } + else if (aAnno.name == LMANNO_SITEURI) { + siteURI = this._uri(aAnno.value); + return false; + } + return true; + }, this); + return this.ptm.createLivemark(feedURI, siteURI, aData.title, aContainer, + aIndex, aData.annos); }, /** @@ -857,81 +335,42 @@ var PlacesUtils = { makeTransaction: function PU_makeTransaction(data, type, container, index, copy) { switch (data.type) { - case this.TYPE_X_MOZ_PLACE_CONTAINER: - if (data.folder) { - // Place is a folder. + case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER: 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 this.ptm.createLivemark(feedURI, siteURI, data.title, container, - index, data.annos); - } - break; - case this.TYPE_X_MOZ_PLACE: - if (data.id <= 0) - 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, container, index, - ["livemark/bookmarkFeedURI"]); - return copyBookmarkAnno; - } - break; - case this.TYPE_X_MOZ_PLACE_SEPARATOR: - if (copy) { + else { // Move the item + var id = data.folder ? data.folder.id : data.id; + return this.ptm.moveItem(id, container, index); + } + break; + case PlacesUtils.TYPE_X_MOZ_PLACE: + if (data.id <= 0) // non-bookmark item + 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, container, index, + ["livemark/bookmarkFeedURI"]); + return copyBookmarkAnno; + } + else + return this.ptm.moveItem(data.id, container, index); + break; + case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR: // There is no data in a separator, so copying it just amounts to // inserting a new separator. return this.ptm.createSeparator(container, index); - } - break; - default: - if (type == this.TYPE_X_MOZ_URL || type == this.TYPE_UNICODE) { - var title = (type == this.TYPE_X_MOZ_URL) ? data.title : data.uri; - return this.ptm.createItem(this._uri(data.uri), container, index, - title); - } - return null; + break; + default: + if (type == PlacesUtils.TYPE_X_MOZ_URL || type == PlacesUtils.TYPE_UNICODE) { + var title = (type == PlacesUtils.TYPE_X_MOZ_URL) ? data.title : data.uri; + return this.ptm.createItem(PlacesUtils._uri(data.uri), container, index, + title); + } } - if (data.id <= 0) - return null; - - // Move the item otherwise - var id = data.folder ? data.folder.id : data.id; - return this.ptm.moveItem(id, container, index); - }, - - /** - * Generates a nsINavHistoryResult for the contents of a folder. - * @param folderId - * The folder to open - * @param [optional] excludeItems - * True to hide all items (individual bookmarks). This is used on - * the left places pane so you just get a folder hierarchy. - * @param [optional] expandQueries - * True to make query items expand as new containers. For managing, - * you want this to be false, for menus and such, you want this to - * be true. - * @returns A nsINavHistoryResult containing the contents of the - * folder. The result.root is guaranteed to be open. - */ - getFolderContents: - function PU_getFolderContents(aFolderId, aExcludeItems, aExpandQueries) { - var query = this.history.getNewQuery(); - query.setFolders([aFolderId], 1); - var options = this.history.getNewQueryOptions(); - options.excludeItems = aExcludeItems; - options.expandQueries = aExpandQueries; - - var result = this.history.executeQuery(query, options); - result.root.containerOpen = true; - return result; + return null; }, /** @@ -1307,7 +746,8 @@ var PlacesUtils = { * TRANSITION_LINK. */ markPageAsTyped: function PU_markPageAsTyped(aURL) { - this.globalHistory.markPageAsTyped(this.createFixedURI(aURL)); + PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory) + .markPageAsTyped(this.createFixedURI(aURL)); }, /** @@ -1318,7 +758,7 @@ var PlacesUtils = { * If we don't call this, we'll treat those visits as TRANSITION_LINK. */ markPageAsFollowedBookmark: function PU_markPageAsFollowedBookmark(aURL) { - this.history.markPageAsFollowedBookmark(this.createFixedURI(aURL)); + PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL)); }, /** @@ -1330,8 +770,8 @@ var PlacesUtils = { * */ checkURLSecurity: function PU_checkURLSecurity(aURINode) { - if (!this.nodeIsBookmark(aURINode)) { - var uri = this._uri(aURINode.uri); + if (!PlacesUtils.nodeIsBookmark(aURINode)) { + var uri = PlacesUtils._uri(aURINode.uri); if (uri.schemeIs("javascript") || uri.schemeIs("data")) { const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties"; var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"]. @@ -1349,137 +789,6 @@ var PlacesUtils = { return true; }, - /** - * Fetch all annotations for a URI, including all properties of each - * annotation which would be required to recreate it. - * @param aURI - * The URI for which annotations are to be retrieved. - * @return Array of objects, each containing the following properties: - * name, flags, expires, mimeType, type, value - */ - getAnnotationsForURI: function PU_getAnnotationsForURI(aURI) { - var annosvc = this.annotations; - var annos = [], val = null; - var annoNames = annosvc.getPageAnnotationNames(aURI, {}); - for (var i = 0; i < annoNames.length; i++) { - var flags = {}, exp = {}, mimeType = {}, storageType = {}; - annosvc.getPageAnnotationInfo(aURI, annoNames[i], flags, exp, mimeType, storageType); - if (storageType.value == annosvc.TYPE_BINARY) { - var data = {}, length = {}, mimeType = {}; - annosvc.getPageAnnotationBinary(aURI, annoNames[i], data, length, mimeType); - val = data.value; - } - else - val = annosvc.getPageAnnotation(aURI, annoNames[i]); - - annos.push({name: annoNames[i], - flags: flags.value, - expires: exp.value, - mimeType: mimeType.value, - type: storageType.value, - value: val}); - } - return annos; - }, - - /** - * Fetch all annotations for an item, including all properties of each - * annotation which would be required to recreate it. - * @param aItemId - * The identifier of the itme for which annotations are to be - * retrieved. - * @return Array of objects, each containing the following properties: - * name, flags, expires, mimeType, type, value - */ - getAnnotationsForItem: function PU_getAnnotationsForItem(aItemId) { - var annosvc = this.annotations; - var annos = [], val = null; - var annoNames = annosvc.getItemAnnotationNames(aItemId, {}); - for (var i = 0; i < annoNames.length; i++) { - var flags = {}, exp = {}, mimeType = {}, storageType = {}; - annosvc.getItemAnnotationInfo(aItemId, annoNames[i], flags, exp, mimeType, storageType); - if (storageType.value == annosvc.TYPE_BINARY) { - var data = {}, length = {}, mimeType = {}; - annosvc.geItemAnnotationBinary(aItemId, annoNames[i], data, length, mimeType); - val = data.value; - } - else - val = annosvc.getItemAnnotation(aItemId, annoNames[i]); - - annos.push({name: annoNames[i], - flags: flags.value, - expires: exp.value, - mimeType: mimeType.value, - type: storageType.value, - value: val}); - } - return annos; - }, - - /** - * Annotate a URI with a batch of annotations. - * @param aURI - * The URI for which annotations are to be set. - * @param aAnnotations - * Array of objects, each containing the following properties: - * name, flags, expires, type, mimeType (only used for binary - * annotations) value. - */ - setAnnotationsForURI: function PU_setAnnotationsForURI(aURI, aAnnos) { - var annosvc = this.annotations; - aAnnos.forEach(function(anno) { - var flags = ("flags" in anno) ? anno.flags : 0; - var expires = ("expires" in anno) ? - anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER; - if (anno.type == annosvc.TYPE_BINARY) { - annosvc.setPageAnnotationBinary(aURI, anno.name, anno.value, - anno.value.length, anno.mimeType, - flags, expires); - } - else - annosvc.setPageAnnotation(aURI, anno.name, anno.value, flags, expires); - }); - }, - - /** - * Annotate an item with a batch of annotations. - * @param aItemId - * The identifier of the item for which annotations are to be set - * @param aAnnotations - * Array of objects, each containing the following properties: - * name, flags, expires, type, mimeType (only used for binary - * annotations) value. - */ - setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos) { - var annosvc = this.annotations; - aAnnos.forEach(function(anno) { - var flags = ("flags" in anno) ? anno.flags : 0; - var expires = ("expires" in anno) ? - anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER; - if (anno.type == annosvc.TYPE_BINARY) { - annosvc.setItemAnnotationBinary(aItemId, anno.name, anno.value, - anno.value.length, anno.mimeType, - flags, expires); - } - else { - annosvc.setItemAnnotation(aItemId, anno.name, anno.value, flags, - expires); - } - }); - }, - - /** - * Helper for getting a serialized Places query for a particular folder. - * @param aFolderId The folder id to get a query for. - * @return string serialized place URI - */ - getQueryStringForFolder: function PU_getQueryStringForFolder(aFolderId) { - var options = this.history.getNewQueryOptions(); - var query = this.history.getNewQuery(); - query.setFolders([aFolderId], 1); - return this.history.queriesToQueryString([query], 1, options); - }, - /** * Get the description associated with a document, as specified in a * element. @@ -1499,85 +808,6 @@ var PlacesUtils = { return ""; }, - // identifier getters for special folders - get placesRootId() { - delete this.placesRootId; - return this.placesRootId = this.bookmarks.placesRoot; - }, - - get bookmarksMenuFolderId() { - delete this.bookmarksMenuFolderId; - return this.bookmarksMenuFolderId = this.bookmarks.bookmarksMenuFolder; - }, - - get toolbarFolderId() { - delete this.toolbarFolderId; - return this.toolbarFolderId = this.bookmarks.toolbarFolder; - }, - - get tagsFolderId() { - delete this.tagsFolderId; - return this.tagsFolderId = this.bookmarks.tagsFolder; - }, - - get unfiledBookmarksFolderId() { - delete this.unfiledBookmarksFolderId; - return this.unfiledBookmarksFolderId = this.bookmarks.unfiledBookmarksFolder; - }, - - /** - * Set the POST data associated with a bookmark, if any. - * Used by POST keywords. - * @param aBookmarkId - * @returns string of POST data - */ - setPostDataForBookmark: function PU_setPostDataForBookmark(aBookmarkId, aPostData) { - const annos = this.annotations; - if (aPostData) - annos.setItemAnnotation(aBookmarkId, POST_DATA_ANNO, aPostData, - 0, Ci.nsIAnnotationService.EXPIRE_NEVER); - else if (annos.itemHasAnnotation(aBookmarkId, POST_DATA_ANNO)) - annos.removeItemAnnotation(aBookmarkId, POST_DATA_ANNO); - }, - - /** - * Get the POST data associated with a bookmark, if any. - * @param aBookmarkId - * @returns string of POST data if set for aBookmarkId. null otherwise. - */ - getPostDataForBookmark: function PU_getPostDataForBookmark(aBookmarkId) { - const annos = this.annotations; - if (annos.itemHasAnnotation(aBookmarkId, POST_DATA_ANNO)) - return annos.getItemAnnotation(aBookmarkId, POST_DATA_ANNO); - - return null; - }, - - /** - * Get the URI (and any associated POST data) for a given keyword. - * @param aKeyword string keyword - * @returns an array containing a string URL and a string of POST data - */ - getURLAndPostDataForKeyword: function PU_getURLAndPostDataForKeyword(aKeyword) { - var url = null, postdata = null; - try { - var uri = this.bookmarks.getURIForKeyword(aKeyword); - if (uri) { - url = uri.spec; - var bookmarks = this.bookmarks.getBookmarkIdsForURI(uri, {}); - for (let i = 0; i < bookmarks.length; i++) { - var bookmark = bookmarks[i]; - var kw = this.bookmarks.getKeywordForBookmark(bookmark); - if (kw == aKeyword) { - postdata = this.getPostDataForBookmark(bookmark); - break; - } - } - } - } catch(ex) {} - return [url, postdata]; - }, - /** * Retrieve the description of an item * @param aItemId @@ -1586,113 +816,11 @@ var PlacesUtils = { * not set. */ getItemDescription: function PU_getItemDescription(aItemId) { - if (this.annotations.itemHasAnnotation(aItemId, DESCRIPTION_ANNO)) - return this.annotations.getItemAnnotation(aItemId, DESCRIPTION_ANNO); + if (PlacesUtils.annotations.itemHasAnnotation(aItemId, DESCRIPTION_ANNO)) + return PlacesUtils.annotations.getItemAnnotation(aItemId, DESCRIPTION_ANNO); return ""; }, - - /** - * Get all bookmarks for a URL, excluding items under tag or livemark - * containers. - */ - getBookmarksForURI: - function PU_getBookmarksForURI(aURI) { - var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI, {}); - - // filter the ids list - return bmkIds.filter(function(aID) { - var parent = this.bookmarks.getFolderIdForItem(aID); - // Livemark child - if (this.annotations.itemHasAnnotation(parent, LMANNO_FEEDURI)) - return false; - var grandparent = this.bookmarks.getFolderIdForItem(parent); - // item under a tag container - if (grandparent == this.tagsFolderId) - return false; - return true; - }, this); - }, - - /** - * Get the most recently added/modified bookmark for a URL, excluding items - * under tag or livemark containers. -1 is returned if no item is found. - */ - getMostRecentBookmarkForURI: - function PU_getMostRecentBookmarkForURI(aURI) { - var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI, {}); - for (var i = 0; i < bmkIds.length; i++) { - // Find the first folder which isn't a tag container - var bk = bmkIds[i]; - var parent = this.bookmarks.getFolderIdForItem(bk); - if (parent == this.unfiledBookmarksFolderId) - return bk; - - var grandparent = this.bookmarks.getFolderIdForItem(parent); - if (grandparent != this.tagsFolderId && - !this.annotations.itemHasAnnotation(parent, LMANNO_FEEDURI)) - return bk; - } - return -1; - }, - - getMostRecentFolderForFeedURI: - function PU_getMostRecentFolderForFeedURI(aURI) { - var feedSpec = aURI.spec - var annosvc = this.annotations; - var livemarks = annosvc.getItemsWithAnnotation(LMANNO_FEEDURI, {}); - for (var i = 0; i < livemarks.length; i++) { - if (annosvc.getItemAnnotation(livemarks[i], LMANNO_FEEDURI) == feedSpec) - return livemarks[i]; - } - return -1; - }, - - getURLsForContainerNode: function PU_getURLsForContainerNode(aNode) { - let urls = []; - if (this.nodeIsFolder(aNode) && asQuery(aNode).queryOptions.excludeItems) { - // grab manually - let contents = this.getFolderContents(aNode.itemId, false, false).root; - for (let i = 0; i < contents.childCount; ++i) { - let child = contents.getChild(i); - if (this.nodeIsURI(child)) - urls.push({uri: child.uri, isBookmark: this.nodeIsBookmark(child)}); - } - } - else { - let result, oldViewer, wasOpen; - try { - let wasOpen = aNode.containerOpen; - result = aNode.parentResult; - oldViewer = result.viewer; - if (!wasOpen) { - result.viewer = null; - aNode.containerOpen = true; - } - for (let i = 0; i < aNode.childCount; ++i) { - // Include visible url nodes only - let child = aNode.getChild(i); - if (this.nodeIsURI(child)) { - // If the node contents is visible, add the uri - if ((wasOpen && oldViewer && child.viewIndex != -1) || - urls.indexOf(child.uri) == -1) { - urls.push({ uri: child.uri, - isBookmark: this.nodeIsBookmark(child) }); - } - } - } - if (!wasOpen) - aNode.containerOpen = false; - } - finally { - if (!wasOpen) - result.viewer = oldViewer; - } - } - - return urls; - }, - /** * Gives the user a chance to cancel loading lots of tabs at once */ @@ -1767,7 +895,7 @@ var PlacesUtils = { }, openContainerNodeInTabs: function PU_openContainerInTabs(aNode, aEvent) { - var urlsToOpen = this.getURLsForContainerNode(aNode); + var urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode); if (!this._confirmOpenInTabs(urlsToOpen.length)) return; @@ -1778,8 +906,8 @@ var PlacesUtils = { var urlsToOpen = []; for (var i=0; i < aNodes.length; i++) { // skip over separators and folders - if (this.nodeIsURI(aNodes[i])) - urlsToOpen.push({uri: aNodes[i].uri, isBookmark: this.nodeIsBookmark(aNodes[i])}); + if (PlacesUtils.nodeIsURI(aNodes[i])) + urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])}); } this._openTabset(urlsToOpen, aEvent); }, @@ -1805,13 +933,13 @@ var PlacesUtils = { */ openNodeIn: function PU_openNodeIn(aNode, aWhere) { if (aNode && PlacesUtils.nodeIsURI(aNode) && - PlacesUtils.checkURLSecurity(aNode)) { + this.checkURLSecurity(aNode)) { var isBookmark = PlacesUtils.nodeIsBookmark(aNode); if (isBookmark) - PlacesUtils.markPageAsFollowedBookmark(aNode.uri); + this.markPageAsFollowedBookmark(aNode.uri); else - PlacesUtils.markPageAsTyped(aNode.uri); + this.markPageAsTyped(aNode.uri); // Check whether the node is a bookmark which should be opened as // a web panel @@ -1843,22 +971,22 @@ var PlacesUtils = { if (iconURI) iconURISpec = iconURI.spec; - if (this.uriTypes.indexOf(type) != -1) { + if (PlacesUtils.uriTypes.indexOf(type) != -1) { element = document.createElement("menuitem"); element.setAttribute("statustext", aNode.uri); element.className = "menuitem-iconic bookmark-item"; } - else if (this.containerTypes.indexOf(type) != -1) { + else if (PlacesUtils.containerTypes.indexOf(type) != -1) { element = document.createElement("menu"); element.setAttribute("container", "true"); if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) element.setAttribute("query", "true"); else if (aNode.itemId != -1) { - if (this.nodeIsLivemarkContainer(aNode)) + if (PlacesUtils.nodeIsLivemarkContainer(aNode)) element.setAttribute("livemark", "true"); - else if (this.bookmarks - .getFolderIdForItem(aNode.itemId) == this.tagsFolderId) + else if (PlacesUtils.bookmarks. + getFolderIdForItem(aNode.itemId) == PlacesUtils.tagsFolderId) element.setAttribute("tagContainer", "true"); } @@ -1897,17 +1025,18 @@ var PlacesUtils = { get leftPaneFolderId() { var leftPaneRoot = -1; var allBookmarksId; - var items = this.annotations.getItemsWithAnnotation(ORGANIZER_FOLDER_ANNO, {}); + var items = PlacesUtils.annotations.getItemsWithAnnotation(ORGANIZER_FOLDER_ANNO, {}); if (items.length != 0 && items[0] != -1) leftPaneRoot = items[0]; if (leftPaneRoot != -1) { // Build the leftPaneQueries Map delete this.leftPaneQueries; this.leftPaneQueries = {}; - var items = this.annotations.getItemsWithAnnotation(ORGANIZER_QUERY_ANNO, { }); + var items = PlacesUtils.annotations. + getItemsWithAnnotation(ORGANIZER_QUERY_ANNO, { }); for (var i=0; i < items.length; i++) { - var queryName = this.annotations - .getItemAnnotation(items[i], ORGANIZER_QUERY_ANNO); + var queryName = PlacesUtils.annotations. + getItemAnnotation(items[i], ORGANIZER_QUERY_ANNO); this.leftPaneQueries[queryName] = items[i]; } delete this.leftPaneFolderId; @@ -1915,69 +1044,70 @@ var PlacesUtils = { } var self = this; - const EXPIRE_NEVER = this.annotations.EXPIRE_NEVER; + const EXPIRE_NEVER = PlacesUtils.annotations.EXPIRE_NEVER; var callback = { runBatched: function(aUserData) { delete self.leftPaneQueries; self.leftPaneQueries = { }; // Left Pane Root Folder - leftPaneRoot = self.bookmarks.createFolder(self.placesRootId, "", -1); + leftPaneRoot = + PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId, "", -1); // History Query - let uri = self._uri("place:sort=4&"); + let uri = PlacesUtils._uri("place:sort=4&"); let title = self.getString("OrganizerQueryHistory"); - let itemId = self.bookmarks.insertBookmark(leftPaneRoot, uri, -1, title); - self.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, - "History", 0, EXPIRE_NEVER); + let itemId = PlacesUtils.bookmarks.insertBookmark(leftPaneRoot, uri, -1, title); + PlacesUtils.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, + "History", 0, EXPIRE_NEVER); self.leftPaneQueries["History"] = itemId; // XXX: Downloads // Tags Query - uri = self._uri("place:folder=" + self.tagsFolderId); - itemId = self.bookmarks.insertBookmark(leftPaneRoot, uri, -1, null); - self.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, - "Tags", 0, EXPIRE_NEVER); + uri = PlacesUtils._uri("place:folder=" + PlacesUtils.tagsFolderId); + itemId = PlacesUtils.bookmarks.insertBookmark(leftPaneRoot, uri, -1, null); + PlacesUtils.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, + "Tags", 0, EXPIRE_NEVER); self.leftPaneQueries["Tags"] = itemId; // All Bookmarks Folder title = self.getString("OrganizerQueryAllBookmarks"); - itemId = self.bookmarks.createFolder(leftPaneRoot, title, -1); + itemId = PlacesUtils.bookmarks.createFolder(leftPaneRoot, title, -1); allBookmarksId = itemId; - self.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, - "AllBookmarks", 0, EXPIRE_NEVER); + PlacesUtils.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, + "AllBookmarks", 0, EXPIRE_NEVER); self.leftPaneQueries["AllBookmarks"] = itemId; // All Bookmarks->Bookmarks Toolbar Query - uri = self._uri("place:folder=" + self.toolbarFolderId); - itemId = self.bookmarks.insertBookmark(allBookmarksId, uri, -1, null); - self.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, - "BookmarksToolbar", 0, EXPIRE_NEVER); + uri = PlacesUtils._uri("place:folder=" + PlacesUtils.toolbarFolderId); + itemId = PlacesUtils.bookmarks.insertBookmark(allBookmarksId, uri, -1, null); + PlacesUtils.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, + "BookmarksToolbar", 0, EXPIRE_NEVER); self.leftPaneQueries["BookmarksToolbar"] = itemId; // All Bookmarks->Bookmarks Menu Query - uri = self._uri("place:folder=" + self.bookmarksMenuFolderId); - itemId = self.bookmarks.insertBookmark(allBookmarksId, uri, -1, null); - self.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, - "BookmarksMenu", 0, EXPIRE_NEVER); + uri = PlacesUtils._uri("place:folder=" + PlacesUtils.bookmarksMenuFolderId); + itemId = PlacesUtils.bookmarks.insertBookmark(allBookmarksId, uri, -1, null); + PlacesUtils.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, + "BookmarksMenu", 0, EXPIRE_NEVER); self.leftPaneQueries["BookmarksMenu"] = itemId; // All Bookmarks->Unfiled bookmarks - uri = self._uri("place:folder=" + self.unfiledBookmarksFolderId); - itemId = self.bookmarks.insertBookmark(allBookmarksId, uri, -1, null); - self.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, - "UnfiledBookmarks", 0, - EXPIRE_NEVER); + uri = PlacesUtils._uri("place:folder=" + PlacesUtils.unfiledBookmarksFolderId); + itemId = PlacesUtils.bookmarks.insertBookmark(allBookmarksId, uri, -1, null); + PlacesUtils.annotations.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, + "UnfiledBookmarks", 0, + EXPIRE_NEVER); self.leftPaneQueries["UnfiledBookmarks"] = itemId; // disallow manipulating this folder within the organizer UI - self.bookmarks.setFolderReadonly(leftPaneRoot, true); + PlacesUtils.bookmarks.setFolderReadonly(leftPaneRoot, true); } }; - this.bookmarks.runInBatchMode(callback, null); - this.annotations.setItemAnnotation(leftPaneRoot, ORGANIZER_FOLDER_ANNO, - true, 0, EXPIRE_NEVER); + PlacesUtils.bookmarks.runInBatchMode(callback, null); + PlacesUtils.annotations.setItemAnnotation(leftPaneRoot, ORGANIZER_FOLDER_ANNO, + true, 0, EXPIRE_NEVER); delete this.leftPaneFolderId; return this.leftPaneFolderId = leftPaneRoot; }, @@ -1990,12 +1120,12 @@ var PlacesUtils = { } }; -PlacesUtils.placesFlavors = [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, - PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, - PlacesUtils.TYPE_X_MOZ_PLACE]; +PlacesUIUtils.placesFlavors = [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, + PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, + PlacesUtils.TYPE_X_MOZ_PLACE]; -PlacesUtils.GENERIC_VIEW_DROP_TYPES = [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, - PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, - PlacesUtils.TYPE_X_MOZ_PLACE, - PlacesUtils.TYPE_X_MOZ_URL, - PlacesUtils.TYPE_UNICODE]; +PlacesUIUtils.GENERIC_VIEW_DROP_TYPES = [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, + PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, + PlacesUtils.TYPE_X_MOZ_PLACE, + PlacesUtils.TYPE_X_MOZ_URL, + PlacesUtils.TYPE_UNICODE]; diff --git a/browser/components/places/src/Makefile.in b/browser/components/places/src/Makefile.in index a30e6ffecec8..e1b30a90ce78 100644 --- a/browser/components/places/src/Makefile.in +++ b/browser/components/places/src/Makefile.in @@ -67,6 +67,8 @@ CPPSRCS = nsPlacesImportExportService.cpp EXTRA_COMPONENTS = nsPlacesTransactionsService.js +EXTRA_PP_COMPONENTS = nsPlacesTransactionsService.js + include $(topsrcdir)/config/rules.mk XPIDL_FLAGS += -I$(topsrcdir)/browser/components diff --git a/browser/components/places/src/nsPlacesImportExportService.cpp b/browser/components/places/src/nsPlacesImportExportService.cpp index 9025bf5d9db4..8b7508ffe33f 100644 --- a/browser/components/places/src/nsPlacesImportExportService.cpp +++ b/browser/components/places/src/nsPlacesImportExportService.cpp @@ -381,10 +381,10 @@ protected: // importing bookmarks.html files. PRBool mAllowRootChanges; - // if set, this is an import of initial bookmarks.html content, + // If set, this is an import of initial bookmarks.html content, // so we don't want to kick off HTTP traffic // and we want the imported personal toolbar folder - // to be set as the personal toolbar folder. (if not set + // to be set as the personal toolbar folder. (If not set // we will treat it as a normal folder.) PRBool mIsImportDefaults; @@ -2440,9 +2440,7 @@ nsPlacesImportExportService::ExportHTMLToFile(nsILocalFile* aBookmarksFile) return rv; } -#define BROWSER_BOOKMARKS_OVERWRITE_PREF "browser.bookmarks.overwrite" #define BROWSER_BOOKMARKS_MAX_BACKUPS_PREF "browser.bookmarks.max_backups" -#define POSTPLACES_BOOKMARKS_FILE "bookmarks.postplaces.html" NS_IMETHODIMP nsPlacesImportExportService::BackupBookmarksFile() @@ -2458,15 +2456,6 @@ nsPlacesImportExportService::BackupBookmarksFile() rv = NS_GetSpecialDirectory(NS_APP_BOOKMARKS_50_FILE, getter_AddRefs(bookmarksFileDir)); - PRBool overwriteBookmarks; - rv = prefs->GetBoolPref(BROWSER_BOOKMARKS_OVERWRITE_PREF, &overwriteBookmarks); - NS_ENSURE_SUCCESS(rv, rv); - - if (!overwriteBookmarks) { - rv = bookmarksFileDir->SetLeafName(NS_LITERAL_STRING(POSTPLACES_BOOKMARKS_FILE)); - NS_ENSURE_SUCCESS(rv, rv); - } - NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr bookmarksFile(do_QueryInterface(bookmarksFileDir)); @@ -2483,17 +2472,6 @@ nsPlacesImportExportService::BackupBookmarksFile() rv = ExportHTMLToFile(bookmarksFile); NS_ENSURE_SUCCESS(rv, rv); - // archive if needed - PRInt32 numberOfBackups; - rv = prefs->GetIntPref(BROWSER_BOOKMARKS_MAX_BACKUPS_PREF, &numberOfBackups); - if (NS_FAILED(rv)) - numberOfBackups = 5; - - if (numberOfBackups > 0) { - rv = ArchiveBookmarksFile(numberOfBackups, PR_FALSE); - NS_ENSURE_SUCCESS(rv, rv); - } - return NS_OK; } @@ -2509,7 +2487,7 @@ nsPlacesImportExportService::BackupBookmarksFile() */ nsresult nsPlacesImportExportService::ArchiveBookmarksFile(PRInt32 numberOfBackups, - PRBool forceArchive) + PRBool forceArchive) { nsCOMPtr bookmarksBackupDir; nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, @@ -2530,7 +2508,7 @@ nsPlacesImportExportService::ArchiveBookmarksFile(PRInt32 numberOfBackups, } // construct the new leafname - PRTime now64 = PR_Now(); + PRTime now64 = PR_Now(); PRExplodedTime nowInfo; PR_ExplodeTime(now64, PR_LocalTimeParameters, &nowInfo); PR_NormalizeTime(&nowInfo, PR_LocalTimeParameters); @@ -2541,8 +2519,6 @@ nsPlacesImportExportService::ArchiveBookmarksFile(PRInt32 numberOfBackups, // and makes the alphabetical order of multiple backup files more useful. PR_FormatTime(timeString, 128, "bookmarks-%Y-%m-%d.html", &nowInfo); - //nsCAutoString backupFilenameCString(timeString); - //nsAutoString backupFilenameString = NS_ConvertUTF8toUTF16(backupFilenameCString); nsAutoString backupFilenameString = NS_ConvertUTF8toUTF16((timeString)); nsCOMPtr backupFile; @@ -2609,18 +2585,6 @@ nsPlacesImportExportService::ArchiveBookmarksFile(PRInt32 numberOfBackups, getter_AddRefs(bookmarksFile)); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); - NS_ENSURE_SUCCESS(rv, rv); - - PRBool overwriteBookmarks; - rv = prefs->GetBoolPref(BROWSER_BOOKMARKS_OVERWRITE_PREF, &overwriteBookmarks); - NS_ENSURE_SUCCESS(rv, rv); - - if (!overwriteBookmarks) { - rv = bookmarksFile->SetLeafName(NS_LITERAL_STRING(POSTPLACES_BOOKMARKS_FILE)); - NS_ENSURE_SUCCESS(rv, rv); - } - rv = bookmarksFile->CopyTo(bookmarksBackupDir, backupFilenameString); // at least dump something out in case this fails in a debug build NS_ENSURE_SUCCESS(rv, rv); diff --git a/browser/components/places/src/nsPlacesTransactionsService.js b/browser/components/places/src/nsPlacesTransactionsService.js index 5601e2db2aa8..e27a4d045023 100644 --- a/browser/components/places/src/nsPlacesTransactionsService.js +++ b/browser/components/places/src/nsPlacesTransactionsService.js @@ -37,17 +37,18 @@ * * ***** END LICENSE BLOCK ***** */ +let Ci = Components.interfaces; +let Cc = Components.classes; +let Cr = Components.results; + const loadInSidebarAnno = "bookmarkProperties/loadInSidebar"; const descriptionAnno = "bookmarkProperties/description"; const CLASS_ID = Components.ID("c0844a84-5a12-4808-80a8-809cb002bb4f"); const CONTRACT_ID = "@mozilla.org/browser/placesTransactionsService;1"; -var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]. - getService(Components.interfaces.mozIJSSubScriptLoader); -loader.loadSubScript("chrome://global/content/debug.js"); -loader.loadSubScript("chrome://browser/content/places/utils.js"); - Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/utils.js"); +Components.utils.import("resource://gre/modules/debug.js"); // The minimum amount of transactions we should tell our observers to begin // batching (rather than letting them do incremental drawing). diff --git a/browser/components/places/tests/unit/head_bookmarks.js b/browser/components/places/tests/unit/head_bookmarks.js index df5fcd809064..e087eac6e9cb 100644 --- a/browser/components/places/tests/unit/head_bookmarks.js +++ b/browser/components/places/tests/unit/head_bookmarks.js @@ -37,6 +37,8 @@ * * ***** END LICENSE BLOCK ***** */ +version(170); + const NS_APP_USER_PROFILE_50_DIR = "ProfD"; var Ci = Components.interfaces; var Cc = Components.classes; diff --git a/browser/components/places/tests/unit/test_384370.js b/browser/components/places/tests/unit/test_384370.js new file mode 100644 index 000000000000..7c8cddd38d22 --- /dev/null +++ b/browser/components/places/tests/unit/test_384370.js @@ -0,0 +1,321 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** 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 Bug 384370 code. + * + * The Initial Developer of the Original Code is Mozilla Corp. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dietrich Ayala + * + * 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 ***** */ + +const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar"; +const DESCRIPTION_ANNO = "bookmarkProperties/description"; +const POST_DATA_ANNO = "bookmarkProperties/POSTData"; +const LAST_CHARSET_ANNO = "URIProperties/characterSet"; + +Components.utils.import("resource://gre/modules/utils.js"); +do_check_eq(typeof PlacesUtils, "object"); + +// main +function run_test() { + /* + HTML+FEATURES SUMMARY: + - import legacy bookmarks + - export as json, import, test (tests integrity of html > json) + - export as html, import, test (tests integrity of json > html) + + BACKUP/RESTORE SUMMARY: + - create a bookmark in each root + - tag multiple URIs with multiple tags + - export as json, import, test + */ + + // get places import/export service + var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].getService(Ci.nsIPlacesImportExportService); + + // avoid creating the places smart folder during tests + Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch). + setBoolPref("browser.places.createdSmartBookmarks", true); + + // file pointer to legacy bookmarks file + //var bookmarksFileOld = do_get_file("browser/components/places/tests/unit/bookmarks.large.html"); + var bookmarksFileOld = do_get_file("browser/components/places/tests/unit/bookmarks.preplaces.html"); + // file pointer to a new places-exported json file + var jsonFile = dirSvc.get("ProfD", Ci.nsILocalFile); + jsonFile.append("bookmarks.exported.json"); + + // create bookmarks.exported.json + if (jsonFile.exists()) + jsonFile.remove(false); + jsonFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600); + if (!jsonFile.exists()) + do_throw("couldn't create file: bookmarks.exported.json"); + + // Test importing a pre-Places canonical bookmarks file. + // 1. import bookmarks.preplaces.html + // 2. run the test-suite + // Note: we do not empty the db before this import to catch bugs like 380999 + try { + importer.importHTMLFromFile(bookmarksFileOld, true); + } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); } + populate(); + validate(); + + // Test exporting a Places canonical json file. + // 1. export to bookmarks.exported.json + // 2. empty bookmarks db + // 3. import bookmarks.exported.json + // 4. run the test-suite + try { + PlacesUtils.backupBookmarksToFile(jsonFile); + } catch(ex) { do_throw("couldn't export to file: " + ex); } + LOG("exported json"); + try { + PlacesUtils.restoreBookmarksFromFile(jsonFile); + } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + LOG("imported json"); + validate(); + LOG("validated import"); +} + +var tagData = [ + { uri: uri("http://slint.us"), tags: ["indie", "kentucky", "music"] }, + { uri: uri("http://en.wikipedia.org/wiki/Diplodocus"), tags: ["dinosaur", "dj", "rad word"] } +]; + +var recentTagsQueryURI = uri("place:folder=" + PlacesUtils.bookmarks.tagsFolder + + "&group=" + Ci.nsINavHistoryQueryOptions.GROUP_BY_FOLDER + + "&applyOptionsToContainers=1" + + "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS); + +var bookmarkData = [ + //{ uri: uri("http://www.saveur.com/"), title: "Saveur Magazine" }, + //{ uri: uri("http://twitter.com/"), title: "Twitter" }, + { uri: uri("http://slint.us"), title: "indie, kentucky, music" }, + { uri: uri("http://en.wikipedia.org/wiki/Diplodocus"), title: "dinosaur, dj, rad word" } + //{ uri: recentTagsQueryURI, title: "Recent Tags" } +]; + +/* +populate data in each folder +(menu is populated via the html import) +*/ +function populate() { + // add tags + for each(let {uri: u, tags: t} in tagData) + PlacesUtils.tagging.tagURI(u, t); + + // add unfiled bookmarks + for each(let {uri: u, title: t} in bookmarkData) { + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.unfiledBookmarksFolder, + u, PlacesUtils.bookmarks.DEFAULT_INDEX, t); + } + + // add to the toolbar + for each(let {uri: u, title: t} in bookmarkData) { + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.toolbarFolder, + u, PlacesUtils.bookmarks.DEFAULT_INDEX, t); + } +} + +function validate() { + testCanonicalBookmarks(PlacesUtils.bookmarks.bookmarksMenuFolder); + testToolbarFolder(); + testUnfiledBookmarks(); + testTags(); +} + +// Tests a bookmarks datastore that has a set of bookmarks, etc +// that flex each supported field and feature. +function testCanonicalBookmarks() { + // query to see if the deleted folder and items have been imported + var query = PlacesUtils.history.getNewQuery(); + query.setFolders([PlacesUtils.bookmarks.bookmarksMenuFolder], 1); + var result = PlacesUtils.history.executeQuery(query, PlacesUtils.history.getNewQueryOptions()); + var rootNode = result.root; + rootNode.containerOpen = true; + + // 6-2: the toolbar contents are imported to the places-toolbar folder, + // the separator above it is removed. + do_check_eq(rootNode.childCount, 4); + + // get test folder + var testFolder = rootNode.getChild(3); + do_check_eq(testFolder.type, testFolder.RESULT_TYPE_FOLDER); + do_check_eq(testFolder.title, "test"); + + /* + // add date + do_check_eq(PlacesUtils.bookmarks.getItemDateAdded(testFolder.itemId)/1000000, 1177541020); + // last modified + do_check_eq(PlacesUtils.bookmarks.getItemLastModified(testFolder.itemId)/1000000, 1177541050); + */ + + testFolder = testFolder.QueryInterface(Ci.nsINavHistoryQueryResultNode); + do_check_eq(testFolder.hasChildren, true); + // folder description + do_check_true(PlacesUtils.annotations.itemHasAnnotation(testFolder.itemId, + DESCRIPTION_ANNO)); + do_check_eq("folder test comment", + PlacesUtils.annotations.getItemAnnotation(testFolder.itemId, DESCRIPTION_ANNO)); + // open test folder, and test the children + testFolder.containerOpen = true; + var cc = testFolder.childCount; + // XXX Bug 380468 + // do_check_eq(cc, 2); + do_check_eq(cc, 1); + + // test bookmark 1 + var testBookmark1 = testFolder.getChild(0); + // url + do_check_eq("http://test/post", testBookmark1.uri); + // title + do_check_eq("test post keyword", testBookmark1.title); + // keyword + do_check_eq("test", PlacesUtils.bookmarks.getKeywordForBookmark(testBookmark1.itemId)); + // sidebar + do_check_true(PlacesUtils.annotations.itemHasAnnotation(testBookmark1.itemId, + LOAD_IN_SIDEBAR_ANNO)); + /* + // add date + do_check_eq(testBookmark1.dateAdded/1000000, 1177375336); + + // last modified + do_check_eq(testBookmark1.lastModified/1000000, 1177375423); + */ + + // post data + do_check_true(PlacesUtils.annotations.itemHasAnnotation(testBookmark1.itemId, POST_DATA_ANNO)); + do_check_eq("hidden1%3Dbar&text1%3D%25s", + PlacesUtils.annotations.getItemAnnotation(testBookmark1.itemId, POST_DATA_ANNO)); + + // last charset + do_check_true(PlacesUtils.annotations.itemHasAnnotation(testBookmark1.itemId, LAST_CHARSET_ANNO)); + do_check_eq("ISO-8859-1", PlacesUtils.annotations.getItemAnnotation(testBookmark1.itemId, + LAST_CHARSET_ANNO)); + // description + do_check_true(PlacesUtils.annotations.itemHasAnnotation(testBookmark1.itemId, + DESCRIPTION_ANNO)); + do_check_eq("item description", + PlacesUtils.annotations.getItemAnnotation(testBookmark1.itemId, + DESCRIPTION_ANNO)); + + /* + // XXX Bug 380468 + // test bookmark 2 + var testBookmark2 = testFolder.getChild(1); + // url + do_check_eq("http://test/micsum", testBookmark2.uri); + // title + do_check_eq("test microsummary", testBookmark2.title); + // check that it's a microsummary + var micsum = mssvc.getMicrosummary(testBookmark2.itemId); + if (!micsum) + do_throw("Could not import microsummary"); + // check generator uri + var generator = micsum.generator; + do_check_eq("urn:source:http://dietrich.ganx4.com/mozilla/test-microsummary.xml", generator.uri.spec); + // expiration and generated title can change, so don't test them + */ + + // clean up + testFolder.containerOpen = false; + rootNode.containerOpen = false; +} + +function testToolbarFolder() { + var query = PlacesUtils.history.getNewQuery(); + query.setFolders([PlacesUtils.bookmarks.toolbarFolder], 1); + var result = PlacesUtils.history.executeQuery(query, PlacesUtils.history.getNewQueryOptions()); + + var toolbar = result.root; + toolbar.containerOpen = true; + + // child count (add 2 for pre-existing items) + do_check_eq(toolbar.childCount, bookmarkData.length + 2); + + // livemark + var livemark = toolbar.getChild(1); + // title + do_check_eq("Latest Headlines", livemark.title); + // livemark check + do_check_true(PlacesUtils.livemarks.isLivemark(livemark.itemId)); + // site url + do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/", + PlacesUtils.livemarks.getSiteURI(livemark.itemId).spec); + // feed url + do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml", + PlacesUtils.livemarks.getFeedURI(livemark.itemId).spec); + + // test added bookmark data + var child = toolbar.getChild(2); + do_check_eq(child.uri, bookmarkData[0].uri.spec); + do_check_eq(child.title, bookmarkData[0].title); + child = toolbar.getChild(3); + do_check_eq(child.uri, bookmarkData[1].uri.spec); + do_check_eq(child.title, bookmarkData[1].title); + + toolbar.containerOpen = false; +} + +function testUnfiledBookmarks() { + var query = PlacesUtils.history.getNewQuery(); + query.setFolders([PlacesUtils.bookmarks.unfiledBookmarksFolder], 1); + var result = PlacesUtils.history.executeQuery(query, PlacesUtils.history.getNewQueryOptions()); + var rootNode = result.root; + rootNode.containerOpen = true; + do_check_eq(rootNode.childCount, bookmarkData.length); + for (var i = 0; i < rootNode.childCount; i++) { + var child = rootNode.getChild(i); + dump(bookmarkData[i].uri.spec + " == " + child.uri + "?\n"); + do_check_true(bookmarkData[i].uri.equals(uri(child.uri))); + do_check_eq(child.title, bookmarkData[i].title); + /* WTF + if (child.tags) + do_check_eq(child.tags, bookmarkData[i].title); + */ + } + rootNode.containerOpen = false; +} + +function testTags() { + for each(let {uri: u, tags: t} in tagData) { + var i = 0; + dump("test tags for " + u.spec + ": " + t + "\n"); + var tt = PlacesUtils.tagging.getTagsForURI(u, {}); + dump("true tags for " + u.spec + ": " + tt + "\n"); + do_check_true(t.every(function(el) { + i++; + return tt.indexOf(el) > -1; + })); + do_check_eq(i, t.length); + } +} diff --git a/browser/components/places/tests/unit/test_398914.js b/browser/components/places/tests/unit/test_398914.js index 37187e1d3397..5c8b39e084cb 100644 --- a/browser/components/places/tests/unit/test_398914.js +++ b/browser/components/places/tests/unit/test_398914.js @@ -36,12 +36,7 @@ * * ***** END LICENSE BLOCK ***** */ -version(170); - -var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. - getService(Ci.mozIJSSubScriptLoader); -loader.loadSubScript("chrome://global/content/debug.js"); -loader.loadSubScript("chrome://browser/content/places/utils.js"); +Components.utils.import("resource://gre/modules/utils.js"); const bmsvc = PlacesUtils.bookmarks; const testFolderId = PlacesUtils.bookmarksMenuFolderId; diff --git a/browser/components/places/tests/unit/test_bookmarks_html.js b/browser/components/places/tests/unit/test_bookmarks_html.js index 04fcfbf7aa8d..f7ef1f1e9b1e 100644 --- a/browser/components/places/tests/unit/test_bookmarks_html.js +++ b/browser/components/places/tests/unit/test_bookmarks_html.js @@ -78,7 +78,6 @@ try { do_throw("Could not get io service\n"); } - const DESCRIPTION_ANNO = "bookmarkProperties/description"; const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar"; const POST_DATA_ANNO = "bookmarkProperties/POSTData"; diff --git a/browser/components/sidebar/src/nsSidebar.js b/browser/components/sidebar/src/nsSidebar.js index 991960514ef3..159b4d94e398 100644 --- a/browser/components/sidebar/src/nsSidebar.js +++ b/browser/components/sidebar/src/nsSidebar.js @@ -129,7 +129,7 @@ function (aTitle, aContentURL, aCustomizeURL, aPersist) } catch(ex) { return; } - win.PlacesUtils.showMinimalAddBookmarkUI(uri, aTitle, null, null, true, true); + win.PlacesUIUtils.showMinimalAddBookmarkUI(uri, aTitle, null, null, true, true); } nsSidebar.prototype.validateSearchEngine = diff --git a/toolkit/components/places/src/Makefile.in b/toolkit/components/places/src/Makefile.in index 2dda865e5189..1fa4d7429762 100644 --- a/toolkit/components/places/src/Makefile.in +++ b/toolkit/components/places/src/Makefile.in @@ -102,4 +102,8 @@ EXTRA_PP_COMPONENTS = nsLivemarkService.js \ nsTaggingService.js \ $(NULL) +EXTRA_JS_MODULES = utils.js + +EXTRA_PP_JS_MODULES = utils.js + include $(topsrcdir)/config/rules.mk diff --git a/toolkit/components/places/src/nsTaggingService.js b/toolkit/components/places/src/nsTaggingService.js index 0426a933d6f2..d1deaa4016d7 100644 --- a/toolkit/components/places/src/nsTaggingService.js +++ b/toolkit/components/places/src/nsTaggingService.js @@ -194,13 +194,14 @@ TaggingService.prototype = { * the item-id of the tag element under the tags root */ _removeTagIfEmpty: function TS__removeTagIfEmpty(aTagId) { - var options = this._history.getNewQueryOptions(); - var query = this._history.getNewQuery(); - query.setFolders([aTagId], 1); - var result = this._history.executeQuery(query, options); - var rootNode = result.root; - rootNode.containerOpen = true; - if (rootNode.childCount == 0) + var node = this._getTagNode(aTagId).QueryInterface(Ci.nsINavHistoryContainerResultNode); + var wasOpen = node.containerOpen; + if (!wasOpen) + node.containerOpen = true; + var cc = node.childCount; + if (wasOpen) + node.containerOpen = false; + if (cc == 0) this._bms.removeFolder(aTagId); }, diff --git a/toolkit/components/places/src/utils.js b/toolkit/components/places/src/utils.js new file mode 100644 index 000000000000..1891816a5fb1 --- /dev/null +++ b/toolkit/components/places/src/utils.js @@ -0,0 +1,1447 @@ +/* -*- Mode: C++; tab-width: 8; 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 the Places Command Controller. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ben Goodger + * Myk Melez + * Asaf Romano + * Sungjoon Steve Won + * Dietrich Ayala + * + * 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 LOG(str) { + dump("*** " + str + "\n"); +} + +var EXPORTED_SYMBOLS = ["PlacesUtils"]; + +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cr = Components.results; + +Components.utils.import("resource://gre/modules/debug.js"); + +const POST_DATA_ANNO = "bookmarkProperties/POSTData"; +const LMANNO_FEEDURI = "livemark/feedURI"; +const LMANNO_SITEURI = "livemark/siteURI"; + +#ifdef XP_MACOSX +// On Mac OSX, the transferable system converts "\r\n" to "\n\n", where we +// really just want "\n". +const NEWLINE= "\n"; +#else +// On other platforms, the transferable system converts "\r\n" to "\n". +const NEWLINE = "\r\n"; +#endif + +function QI_node(aNode, aIID) { + var result = null; + try { + result = aNode.QueryInterface(aIID); + } + catch (e) { + } + NS_ASSERT(result, "Node QI Failed"); + return result; +} +function asVisit(aNode) { return QI_node(aNode, Ci.nsINavHistoryVisitResultNode); } +function asFullVisit(aNode){ return QI_node(aNode, Ci.nsINavHistoryFullVisitResultNode);} +function asContainer(aNode){ return QI_node(aNode, Ci.nsINavHistoryContainerResultNode);} +function asQuery(aNode) { return QI_node(aNode, Ci.nsINavHistoryQueryResultNode); } + +var PlacesUtils = { + // Place entries that are containers, e.g. bookmark folders or queries. + TYPE_X_MOZ_PLACE_CONTAINER: "text/x-moz-place-container", + // Place entries that are bookmark separators. + TYPE_X_MOZ_PLACE_SEPARATOR: "text/x-moz-place-separator", + // Place entries that are not containers or separators + TYPE_X_MOZ_PLACE: "text/x-moz-place", + // Place entries in shortcut url format (url\ntitle) + TYPE_X_MOZ_URL: "text/x-moz-url", + // Place entries formatted as HTML anchors + TYPE_HTML: "text/html", + // Place entries as raw URL text + TYPE_UNICODE: "text/unicode", + + /** + * The Bookmarks Service. + */ + get bookmarks() { + delete this.bookmarks; + return this.bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. + getService(Ci.nsINavBookmarksService); + }, + + /** + * The Nav History Service. + */ + get history() { + delete this.history; + return this.history = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); + }, + + /** + * The Live Bookmark Service. + */ + get livemarks() { + delete this.livemarks; + return this.livemarks = Cc["@mozilla.org/browser/livemark-service;2"]. + getService(Ci.nsILivemarkService); + }, + + /** + * The Annotations Service. + */ + get annotations() { + delete this.annotations; + return this.annotations = Cc["@mozilla.org/browser/annotation-service;1"]. + getService(Ci.nsIAnnotationService); + }, + + /** + * The Favicons Service + */ + get favicons() { + delete this.favicons; + return this.favicons = Cc["@mozilla.org/browser/favicon-service;1"]. + getService(Ci.nsIFaviconService); + }, + + /** + * The Places Tagging Service + */ + get tagging() { + delete this.tagging; + return this.tagging = Cc["@mozilla.org/browser/tagging-service;1"]. + getService(Ci.nsITaggingService); + }, + + /** + * Makes a URI from a spec. + * @param aSpec + * The string spec of the URI + * @returns A URI object for the spec. + */ + _uri: function PU__uri(aSpec) { + NS_ASSERT(aSpec, "empty URL spec"); + return Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService). + newURI(aSpec, null, null); + }, + + /** + * String bundle helpers + */ + get _bundle() { + const PLACES_STRING_BUNDLE_URI = + "chrome://places/locale/places.properties"; + delete this._bundle; + return this._bundle = Cc["@mozilla.org/intl/stringbundle;1"]. + getService(Ci.nsIStringBundleService). + createBundle(PLACES_STRING_BUNDLE_URI); + }, + + getFormattedString: function PU_getFormattedString(key, params) { + return this._bundle.formatStringFromName(key, params, params.length); + }, + + getString: function PU_getString(key) { + return this._bundle.GetStringFromName(key); + }, + + /** + * Determines whether or not a ResultNode is a Bookmark folder. + * @param aNode + * A result node + * @returns true if the node is a Bookmark folder, false otherwise + */ + nodeIsFolder: function PU_nodeIsFolder(aNode) { + NS_ASSERT(aNode, "null node"); + return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER || + aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT); + }, + + /** + * Determines whether or not a ResultNode represents a bookmarked URI. + * @param aNode + * A result node + * @returns true if the node represents a bookmarked URI, false otherwise + */ + nodeIsBookmark: function PU_nodeIsBookmark(aNode) { + NS_ASSERT(aNode, "null node"); + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI && + aNode.itemId != -1; + }, + + /** + * Determines whether or not a ResultNode is a Bookmark separator. + * @param aNode + * A result node + * @returns true if the node is a Bookmark separator, false otherwise + */ + nodeIsSeparator: function PU_nodeIsSeparator(aNode) { + NS_ASSERT(aNode, "null node"); + + return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR); + }, + + /** + * Determines whether or not a ResultNode is a visit item. + * @param aNode + * A result node + * @returns true if the node is a visit item, false otherwise + */ + nodeIsVisit: function PU_nodeIsVisit(aNode) { + NS_ASSERT(aNode, "null node"); + + const NHRN = Ci.nsINavHistoryResultNode; + var type = aNode.type; + return type == NHRN.RESULT_TYPE_VISIT || + type == NHRN.RESULT_TYPE_FULL_VISIT; + }, + + /** + * Determines whether or not a ResultNode is a URL item. + * @param aNode + * A result node + * @returns true if the node is a URL item, false otherwise + */ + uriTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_URI, + Ci.nsINavHistoryResultNode.RESULT_TYPE_VISIT, + Ci.nsINavHistoryResultNode.RESULT_TYPE_FULL_VISIT], + nodeIsURI: function PU_nodeIsURI(aNode) { + NS_ASSERT(aNode, "null node"); + return this.uriTypes.indexOf(aNode.type) != -1; + }, + + /** + * Determines whether or not a ResultNode is a Query item. + * @param aNode + * A result node + * @returns true if the node is a Query item, false otherwise + */ + nodeIsQuery: function PU_nodeIsQuery(aNode) { + NS_ASSERT(aNode, "null node"); + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY; + }, + + /** + * Determines if a node is read only (children cannot be inserted, sometimes + * they cannot be removed depending on the circumstance) + * @param aNode + * A result node + * @returns true if the node is readonly, false otherwise + */ + nodeIsReadOnly: function PU_nodeIsReadOnly(aNode) { + NS_ASSERT(aNode, "null node"); + + if (this.nodeIsFolder(aNode)) + return this.bookmarks.getFolderReadonly(asQuery(aNode).folderItemId); + if (this.nodeIsQuery(aNode)) + return asQuery(aNode).childrenReadOnly; + return false; + }, + + /** + * Determines whether or not a ResultNode is a host container. + * @param aNode + * A result node + * @returns true if the node is a host container, false otherwise + */ + nodeIsHost: function PU_nodeIsHost(aNode) { + NS_ASSERT(aNode, "null node"); + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY && + aNode.parent && + asQuery(aNode.parent).queryOptions.resultType == + Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY; + }, + + /** + * Determines whether or not a ResultNode is a day container. + * @param node + * A NavHistoryResultNode + * @returns true if the node is a day container, false otherwise + */ + nodeIsDay: function PU_nodeIsDay(aNode) { + NS_ASSERT(aNode, "null node"); + var resultType; + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY && + aNode.parent && + ((resultType = asQuery(aNode.parent).queryOptions.resultType) == + Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY || + resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY); + }, + + /** + * Determines whether or not a ResultNode is a container. + * @param aNode + * A result node + * @returns true if the node is a container item, false otherwise + */ + containerTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, + Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT, + Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY, + Ci.nsINavHistoryResultNode.RESULT_TYPE_DYNAMIC_CONTAINER], + nodeIsContainer: function PU_nodeIsContainer(aNode) { + NS_ASSERT(aNode, "null node"); + return this.containerTypes.indexOf(aNode.type) != -1; + }, + + /** + * Determines whether or not a result-node is a dynamic-container item. + * The dynamic container result node type is for dynamically created + * containers (e.g. for the file browser service where you get your folders + * in bookmark menus). + * @param aNode + * A result node + * @returns true if the node is a dynamic container item, false otherwise + */ + nodeIsDynamicContainer: function PU_nodeIsDynamicContainer(aNode) { + NS_ASSERT(aNode, "null node"); + if (aNode.type == NHRN.RESULT_TYPE_DYNAMIC_CONTAINER) + return true; + return false; + }, + + /** + * Determines whether a result node is a remote container registered by the + * livemark service. + * @param aNode + * A result Node + * @returns true if the node is a livemark container item + */ + nodeIsLivemarkContainer: function PU_nodeIsLivemarkContainer(aNode) { + // Use the annotations service directly to avoid instantiating + // the Livemark service on startup. (bug 398300) + return this.nodeIsFolder(aNode) && + this.annotations.itemHasAnnotation(aNode.itemId, LMANNO_FEEDURI); + }, + + /** + * Determines whether a result node is a live-bookmark item + * @param aNode + * A result node + * @returns true if the node is a livemark container item + */ + nodeIsLivemarkItem: function PU_nodeIsLivemarkItem(aNode) { + return aNode.parent && this.nodeIsLivemarkContainer(aNode.parent); + }, + + /** + * Determines whether or not a node is a readonly folder. + * @param aNode + * The node to test. + * @returns true if the node is a readonly folder. + */ + isReadonlyFolder: function(aNode) { + NS_ASSERT(aNode, "null node"); + + return this.nodeIsFolder(aNode) && + this.bookmarks.getFolderReadonly(asQuery(aNode).folderItemId); + }, + + /** + * Gets the concrete item-id for the given node. Generally, this is just + * node.itemId, but for folder-shortcuts that's node.folderItemId. + */ + getConcreteItemId: function PU_getConcreteItemId(aNode) { + if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) + return asQuery(aNode).folderItemId; + return aNode.itemId; + }, + + /** + * Gets the index of a node within its parent container + * @param aNode + * The node to look up + * @returns The index of the node within its parent container, or -1 if the + * node was not found or the node specified has no parent. + */ + getIndexOfNode: function PU_getIndexOfNode(aNode) { + NS_ASSERT(aNode, "null node"); + + var parent = aNode.parent; + if (!parent) + return -1; + var wasOpen = parent.containerOpen; + var result, oldViewer; + if (!wasOpen) { + result = parent.parentResult; + oldViewer = result.viewer; + result.viewer = null; + parent.containerOpen = true; + } + var cc = parent.childCount; + for (var i = 0; i < cc && parent.getChild(i) != aNode; ++i); + if (!wasOpen) { + parent.containerOpen = false; + result.viewer = oldViewer; + } + return i < cc ? i : -1; + }, + + /** + * String-wraps a result node according to the rules of the specified + * content type. + * @param aNode + * The Result node to wrap (serialize) + * @param aType + * The content type to serialize as + * @param [optional] aOverrideURI + * Used instead of the node's URI if provided. + * This is useful for wrapping a container as TYPE_X_MOZ_URL, + * TYPE_HTML or TYPE_UNICODE. + * @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) { + if (self.nodeIsFolder(cNode) && asQuery(cNode).queryOptions.excludeItems) + return self.getFolderContents(cNode.itemId, false, true).root; + return cNode; + } + + switch (aType) { + case this.TYPE_X_MOZ_PLACE: + case this.TYPE_X_MOZ_PLACE_SEPARATOR: + case this.TYPE_X_MOZ_PLACE_CONTAINER: + var writer = { + value: "", + write: function PU_wrapNode__write(aStr, aLen) { + this.value += aStr; + } + }; + self.serializeNodeAsJSONToOutputStream(aNode, writer); + return writer.value; + case this.TYPE_X_MOZ_URL: + function gatherDataUrl(bNode) { + if (self.nodeIsLivemarkContainer(bNode)) { + var siteURI = self.livemarks.getSiteURI(bNode.itemId).spec; + return siteURI + NEWLINE + bNode.title; + } + if (self.nodeIsURI(bNode)) + return (aOverrideURI || bNode.uri) + NEWLINE + bNode.title; + // 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(/" + escapedTitle + "" + NEWLINE; + } + if (self.nodeIsContainer(bNode)) { + asContainer(bNode); + var wasOpen = bNode.containerOpen; + if (!wasOpen) + bNode.containerOpen = true; + + var childString = "
" + escapedTitle + "
" + NEWLINE; + var cc = bNode.childCount; + for (var i = 0; i < cc; ++i) + childString += "
" + + NEWLINE + + gatherDataHtml(bNode.getChild(i)) + + "
" + + NEWLINE; + bNode.containerOpen = wasOpen; + return childString + "
" + NEWLINE; + } + if (self.nodeIsURI(bNode)) + return "" + escapedTitle + "" + NEWLINE; + if (self.nodeIsSeparator(bNode)) + return "
" + NEWLINE; + return ""; + } + return gatherDataHtml(convertNode(aNode)); + } + // case this.TYPE_UNICODE: + function gatherDataText(bNode) { + if (self.nodeIsLivemarkContainer(bNode)) + return self.livemarks.getSiteURI(bNode.itemId).spec; + if (self.nodeIsContainer(bNode)) { + asContainer(bNode); + var wasOpen = bNode.containerOpen; + if (!wasOpen) + bNode.containerOpen = true; + + var childString = bNode.title + NEWLINE; + var cc = bNode.childCount; + for (var i = 0; i < cc; ++i) { + var child = bNode.getChild(i); + var suffix = i < (cc - 1) ? NEWLINE : ""; + childString += gatherDataText(child) + suffix; + } + bNode.containerOpen = wasOpen; + return childString; + } + if (self.nodeIsURI(bNode)) + return (aOverrideURI || bNode.uri); + if (self.nodeIsSeparator(bNode)) + return "--------------------"; + return ""; + } + + return gatherDataText(convertNode(aNode)); + }, + + /** + * Unwraps data from the Clipboard or the current Drag Session. + * @param blob + * A blob (string) of data, in some format we potentially know how + * to parse. + * @param type + * The content type of the blob. + * @returns An array of objects representing each item contained by the source. + */ + unwrapNodes: function PU_unwrapNodes(blob, type) { + // We split on "\n" because the transferable system converts "\r\n" to "\n" + var nodes = []; + switch(type) { + case this.TYPE_X_MOZ_PLACE: + case this.TYPE_X_MOZ_PLACE_SEPARATOR: + case this.TYPE_X_MOZ_PLACE_CONTAINER: + try { + var JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON); + nodes = JSON.decode("[" + blob + "]"); + } catch(ex) { + LOG("unwrapNodes(): JSON.decode(): " + ex); + } + break; + case this.TYPE_X_MOZ_URL: + 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 + // but drag and drop of files from the shell has parts.length = 1 + if (parts.length != 1 && parts.length % 2) + break; + for (var i = 0; i < parts.length; i=i+2) { + var uriString = parts[i]; + var titleString = ""; + if (parts.length > i+1) + titleString = parts[i+1]; + else { + // for drag and drop of files, try to use the leafName as title + try { + titleString = this._uri(uriString).QueryInterface(Ci.nsIURL) + .fileName; + } + catch (e) {} + } + // 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: + 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; + }, + + /** + * Generates a nsINavHistoryResult for the contents of a folder. + * @param folderId + * The folder to open + * @param [optional] excludeItems + * True to hide all items (individual bookmarks). This is used on + * the left places pane so you just get a folder hierarchy. + * @param [optional] expandQueries + * True to make query items expand as new containers. For managing, + * you want this to be false, for menus and such, you want this to + * be true. + * @returns A nsINavHistoryResult containing the contents of the + * folder. The result.root is guaranteed to be open. + */ + getFolderContents: + function PU_getFolderContents(aFolderId, aExcludeItems, aExpandQueries) { + var query = this.history.getNewQuery(); + query.setFolders([aFolderId], 1); + var options = this.history.getNewQueryOptions(); + options.excludeItems = aExcludeItems; + options.expandQueries = aExpandQueries; + + var result = this.history.executeQuery(query, options); + result.root.containerOpen = true; + return result; + }, + + /** + * Fetch all annotations for a URI, including all properties of each + * annotation which would be required to recreate it. + * @param aURI + * The URI for which annotations are to be retrieved. + * @return Array of objects, each containing the following properties: + * name, flags, expires, mimeType, type, value + */ + getAnnotationsForURI: function PU_getAnnotationsForURI(aURI) { + var annosvc = this.annotations; + var annos = [], val = null; + var annoNames = annosvc.getPageAnnotationNames(aURI, {}); + for (var i = 0; i < annoNames.length; i++) { + var flags = {}, exp = {}, mimeType = {}, storageType = {}; + annosvc.getPageAnnotationInfo(aURI, annoNames[i], flags, exp, mimeType, storageType); + if (storageType.value == annosvc.TYPE_BINARY) { + var data = {}, length = {}, mimeType = {}; + annosvc.getPageAnnotationBinary(aURI, annoNames[i], data, length, mimeType); + val = data.value; + } + else + val = annosvc.getPageAnnotation(aURI, annoNames[i]); + + annos.push({name: annoNames[i], + flags: flags.value, + expires: exp.value, + mimeType: mimeType.value, + type: storageType.value, + value: val}); + } + return annos; + }, + + /** + * Fetch all annotations for an item, including all properties of each + * annotation which would be required to recreate it. + * @param aItemId + * The identifier of the itme for which annotations are to be + * retrieved. + * @return Array of objects, each containing the following properties: + * name, flags, expires, mimeType, type, value + */ + getAnnotationsForItem: function PU_getAnnotationsForItem(aItemId) { + var annosvc = this.annotations; + var annos = [], val = null; + var annoNames = annosvc.getItemAnnotationNames(aItemId, {}); + for (var i = 0; i < annoNames.length; i++) { + var flags = {}, exp = {}, mimeType = {}, storageType = {}; + annosvc.getItemAnnotationInfo(aItemId, annoNames[i], flags, exp, mimeType, storageType); + if (storageType.value == annosvc.TYPE_BINARY) { + var data = {}, length = {}, mimeType = {}; + annosvc.geItemAnnotationBinary(aItemId, annoNames[i], data, length, mimeType); + val = data.value; + } + else + val = annosvc.getItemAnnotation(aItemId, annoNames[i]); + + annos.push({name: annoNames[i], + flags: flags.value, + expires: exp.value, + mimeType: mimeType.value, + type: storageType.value, + value: val}); + } + return annos; + }, + + /** + * Annotate a URI with a batch of annotations. + * @param aURI + * The URI for which annotations are to be set. + * @param aAnnotations + * Array of objects, each containing the following properties: + * name, flags, expires, type, mimeType (only used for binary + * annotations) value. + */ + setAnnotationsForURI: function PU_setAnnotationsForURI(aURI, aAnnos) { + var annosvc = this.annotations; + aAnnos.forEach(function(anno) { + var flags = ("flags" in anno) ? anno.flags : 0; + var expires = ("expires" in anno) ? + anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER; + if (anno.type == annosvc.TYPE_BINARY) { + annosvc.setPageAnnotationBinary(aURI, anno.name, anno.value, + anno.value.length, anno.mimeType, + flags, expires); + } + else + annosvc.setPageAnnotation(aURI, anno.name, anno.value, flags, expires); + }); + }, + + /** + * Annotate an item with a batch of annotations. + * @param aItemId + * The identifier of the item for which annotations are to be set + * @param aAnnotations + * Array of objects, each containing the following properties: + * name, flags, expires, type, mimeType (only used for binary + * annotations) value. + */ + setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos) { + var annosvc = this.annotations; + aAnnos.forEach(function(anno) { + var flags = ("flags" in anno) ? anno.flags : 0; + var expires = ("expires" in anno) ? + anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER; + if (anno.type == annosvc.TYPE_BINARY) { + annosvc.setItemAnnotationBinary(aItemId, anno.name, anno.value, + anno.value.length, anno.mimeType, + flags, expires); + } + else { + annosvc.setItemAnnotation(aItemId, anno.name, anno.value, flags, + expires); + } + }); + }, + + /** + * Helper for getting a serialized Places query for a particular folder. + * @param aFolderId The folder id to get a query for. + * @return string serialized place URI + */ + getQueryStringForFolder: function PU_getQueryStringForFolder(aFolderId) { + var options = this.history.getNewQueryOptions(); + var query = this.history.getNewQuery(); + query.setFolders([aFolderId], 1); + return this.history.queriesToQueryString([query], 1, options); + }, + + // identifier getters for special folders + get placesRootId() { + delete this.placesRootId; + return this.placesRootId = this.bookmarks.placesRoot; + }, + + get bookmarksMenuFolderId() { + delete this.bookmarksMenuFolderId; + return this.bookmarksMenuFolderId = this.bookmarks.bookmarksMenuFolder; + }, + + get toolbarFolderId() { + delete this.toolbarFolderId; + return this.toolbarFolderId = this.bookmarks.toolbarFolder; + }, + + get tagsFolderId() { + delete this.tagsFolderId; + return this.tagsFolderId = this.bookmarks.tagsFolder; + }, + + get unfiledBookmarksFolderId() { + delete this.unfiledBookmarksFolderId; + return this.unfiledBookmarksFolderId = this.bookmarks.unfiledBookmarksFolder; + }, + + /** + * Set the POST data associated with a bookmark, if any. + * Used by POST keywords. + * @param aBookmarkId + * @returns string of POST data + */ + setPostDataForBookmark: function PU_setPostDataForBookmark(aBookmarkId, aPostData) { + const annos = this.annotations; + if (aPostData) + annos.setItemAnnotation(aBookmarkId, POST_DATA_ANNO, aPostData, + 0, Ci.nsIAnnotationService.EXPIRE_NEVER); + else if (annos.itemHasAnnotation(aBookmarkId, POST_DATA_ANNO)) + annos.removeItemAnnotation(aBookmarkId, POST_DATA_ANNO); + }, + + /** + * Get the POST data associated with a bookmark, if any. + * @param aBookmarkId + * @returns string of POST data if set for aBookmarkId. null otherwise. + */ + getPostDataForBookmark: function PU_getPostDataForBookmark(aBookmarkId) { + const annos = this.annotations; + if (annos.itemHasAnnotation(aBookmarkId, POST_DATA_ANNO)) + return annos.getItemAnnotation(aBookmarkId, POST_DATA_ANNO); + + return null; + }, + + /** + * Get the URI (and any associated POST data) for a given keyword. + * @param aKeyword string keyword + * @returns an array containing a string URL and a string of POST data + */ + getURLAndPostDataForKeyword: function PU_getURLAndPostDataForKeyword(aKeyword) { + var url = null, postdata = null; + try { + var uri = this.bookmarks.getURIForKeyword(aKeyword); + if (uri) { + url = uri.spec; + var bookmarks = this.bookmarks.getBookmarkIdsForURI(uri, {}); + for (let i = 0; i < bookmarks.length; i++) { + var bookmark = bookmarks[i]; + var kw = this.bookmarks.getKeywordForBookmark(bookmark); + if (kw == aKeyword) { + postdata = this.getPostDataForBookmark(bookmark); + break; + } + } + } + } catch(ex) {} + return [url, postdata]; + }, + + /** + * Get all bookmarks for a URL, excluding items under tag or livemark + * containers. + */ + getBookmarksForURI: + function PU_getBookmarksForURI(aURI) { + var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI, {}); + + // filter the ids list + return bmkIds.filter(function(aID) { + var parent = this.bookmarks.getFolderIdForItem(aID); + // Livemark child + if (this.annotations.itemHasAnnotation(parent, LMANNO_FEEDURI)) + return false; + var grandparent = this.bookmarks.getFolderIdForItem(parent); + // item under a tag container + if (grandparent == this.tagsFolderId) + return false; + return true; + }, this); + }, + + /** + * Get the most recently added/modified bookmark for a URL, excluding items + * under tag or livemark containers. -1 is returned if no item is found. + */ + getMostRecentBookmarkForURI: + function PU_getMostRecentBookmarkForURI(aURI) { + var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI, {}); + for (var i = 0; i < bmkIds.length; i++) { + // Find the first folder which isn't a tag container + var bk = bmkIds[i]; + var parent = this.bookmarks.getFolderIdForItem(bk); + if (parent == this.unfiledBookmarksFolderId) + return bk; + + var grandparent = this.bookmarks.getFolderIdForItem(parent); + if (grandparent != this.tagsFolderId && + !this.annotations.itemHasAnnotation(parent, LMANNO_FEEDURI)) + return bk; + } + return -1; + }, + + getMostRecentFolderForFeedURI: + function PU_getMostRecentFolderForFeedURI(aURI) { + var feedSpec = aURI.spec + var annosvc = this.annotations; + var livemarks = annosvc.getItemsWithAnnotation(LMANNO_FEEDURI, {}); + for (var i = 0; i < livemarks.length; i++) { + if (annosvc.getItemAnnotation(livemarks[i], LMANNO_FEEDURI) == feedSpec) + return livemarks[i]; + } + return -1; + }, + + getURLsForContainerNode: function PU_getURLsForContainerNode(aNode) { + let urls = []; + if (this.nodeIsFolder(aNode) && asQuery(aNode).queryOptions.excludeItems) { + // grab manually + let contents = this.getFolderContents(aNode.itemId, false, false).root; + for (let i = 0; i < contents.childCount; ++i) { + let child = contents.getChild(i); + if (this.nodeIsURI(child)) + urls.push({uri: child.uri, isBookmark: this.nodeIsBookmark(child)}); + } + } + else { + let result, oldViewer, wasOpen; + try { + let wasOpen = aNode.containerOpen; + result = aNode.parentResult; + oldViewer = result.viewer; + if (!wasOpen) { + result.viewer = null; + aNode.containerOpen = true; + } + for (let i = 0; i < aNode.childCount; ++i) { + // Include visible url nodes only + let child = aNode.getChild(i); + if (this.nodeIsURI(child)) { + // If the node contents is visible, add the uri only if its node is + // visible. Otherwise follow viewer's collapseDuplicates property, + // default to true + if ((wasOpen && oldViewer && child.viewIndex != -1) || + (oldViewer && !oldViewer.collapseDuplicates) || + urls.indexOf(child.uri) == -1) { + urls.push({ uri: child.uri, + isBookmark: this.nodeIsBookmark(child) }); + } + } + } + if (!wasOpen) + aNode.containerOpen = false; + } + finally { + if (!wasOpen) + result.viewer = oldViewer; + } + } + + return urls; + }, + + /** + * Restores bookmarks/tags from a JSON file. + * WARNING: This method *removes* any bookmarks in the collection before + * restoring from the file. + */ + restoreBookmarksFromFile: function PU_restoreBookmarksFromFile(aFile) { + var ioSvc = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + var fileURL = ioSvc.newFileURI(aFile).QueryInterface(Ci.nsIURL); + var fileExtension = fileURL.fileExtension.toLowerCase(); + if (fileExtension == "json") + this.restoreBookmarksFromJSONFile(aFile); + else { + const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties"; + var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"]. + getService(Ci.nsIStringBundleService). + createBundle(BRANDING_BUNDLE_URI). + GetStringFromName("brandShortName"); + var errorStr = this.getString("restoreFormatError"); + Cc["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Ci.nsIPromptService). + alert(window, brandShortName, errorStr); + } + }, + + /** + * Restores bookmarks/tags from a JSON file. + * WARNING: This method *removes* any bookmarks in the collection before + * restoring from the file. + */ + restoreBookmarksFromJSONFile: + function PU_restoreBookmarksFromJSONFile(aFile) { + // open file stream + var stream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + stream.init(aFile, 0x01, 0, 0); + var converted = Cc["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Ci.nsIConverterInputStream); + converted.init(stream, "UTF-8", 1024, + Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + // read in contents + var str = {}; + var jsonStr = ""; + while (converted.readString(4096, str) != 0) + jsonStr += str.value; + converted.close(); + + if (jsonStr.length == 0) + return; // empty file + + this.restoreBookmarksFromJSONString(jsonStr, true); + }, + + /** + * Import bookmarks from a JSON string. + * + * @param aString + * JSON string of serialized bookmark data. + * @param aReplace + * Boolean if true, replace existing bookmarks, else merge. + */ + restoreBookmarksFromJSONString: + function PU_restoreBookmarksFromJSONString(aString, aReplace) { + // convert string to JSON + var nodes = null; + try { + nodes = this.unwrapNodes(aString, this.TYPE_X_MOZ_PLACE_CONTAINER); + } catch (ex) { + LOG("restoreBookmarksFromJSONString(): " + ex); + return; + } + + if (nodes.length == 0 || !nodes[0].children || + nodes[0].children.length == 0) + return; // nothing to restore + + // ensure tag folder gets processed last + nodes[0].children.sort(function sortRoots(aNode, bNode) { + return (aNode.root && aNode.root == "tagsFolder") ? 1 : + (bNode.root && bNode.root == "tagsFolder") ? -1 : 0; + }); + + var batch = { + nodes: nodes[0].children, + runBatched: function restore_runBatched() { + this.nodes.forEach(function(node) { + var root = node.root; + // FIXME support folders other than known roots + // restoring the library left pane, for example, breaks the library + if (!root) + return; + + if (!node.children || node.children.length == 0) + return; // nothing to restore for this root + + var container = this.placesRootId; // default to places root + switch (root) { + case "bookmarksMenuFolder": + container = this.bookmarksMenuFolderId; + break; + case "tagsFolder": + container = this.tagsFolderId; + break; + case "unfiledBookmarksFolder": + container = this.unfiledBookmarksFolderId; + break; + case "toolbarFolder": + container = this.toolbarFolderId; + break; + } + + if (aReplace) { + if (container != this.tagsFolderId) + this.bookmarks.removeFolderChildren(container); + else { + // remove tags via the tagging service + var tags = this.tagging.allTags; + var uris = []; + for (let i in tags) { + var tagURIs = this.tagging.getURIsForTag(tags[i]); + for (let i in tagURIs) + this.tagging.untagURI(tagURIs[i], [tags[i]]); + } + } + } + + // insert the data into the db + node.children.forEach(function(child) { + var index = child.index; + this.importJSONNode(child, container, index); + }, this); + }, PlacesUtils); + } + }; + + this.bookmarks.runInBatchMode(batch, null); + }, + + /** + * Takes a JSON-serialized node and inserts it into the db. + * + * @param aData + * The unwrapped data blob of dropped or pasted data. + * @param aContainer + * The container the data was dropped or pasted into + * @param aIndex + * The index within the container the item was dropped or pasted at + */ + importJSONNode: function PU_importJSONNode(aData, aContainer, aIndex) { + // create item + var id = -1; + switch (aData.type) { + case this.TYPE_X_MOZ_PLACE_CONTAINER: + if (aContainer == PlacesUtils.bookmarks.tagsFolder) { + if (aData.children) { + aData.children.forEach(function(aChild) { + this.tagging.tagURI(this._uri(aChild.uri), [aData.title]); + }, this); + return; + } + } + else if (aData.livemark && aData.annos) { + // node is a livemark + var feedURI = null; + var siteURI = null; + aData.annos = aData.annos.filter(function(aAnno) { + if (aAnno.name == LMANNO_FEEDURI) { + feedURI = this._uri(aAnno.value); + return false; + } + else if (aAnno.name == LMANNO_SITEURI) { + siteURI = this._uri(aAnno.value); + return false; + } + return true; + }, this); + + if (feedURI) + id = this.livemarks.createLivemark(aContainer, aData.title, siteURI, feedURI, aIndex); + } + else { + id = this.bookmarks.createFolder(aContainer, aData.title, aIndex); + // process children + if (aData.children) { + aData.children.every(function(aChild, aIndex) { + this.importJSONNode(aChild, id, aIndex); + return true; + }, this); + } + } + break; + case this.TYPE_X_MOZ_PLACE: + id = this.bookmarks.insertBookmark(aContainer, this._uri(aData.uri), aIndex, aData.title); + if (aData.keyword) + this.bookmarks.setKeywordForBookmark(id, aData.keyword); + break; + case this.TYPE_X_MOZ_PLACE_SEPARATOR: + id = this.bookmarks.insertSeparator(aContainer, aIndex); + break; + default: + } + + // set generic properties + if (id != -1) { + this.bookmarks.setItemDateAdded(id, aData.dateAdded); + this.bookmarks.setItemLastModified(id, aData.lastModified); + if (aData.annos) + this.setAnnotationsForItem(id, aData.annos); + } + }, + + /** + * Serializes the given node (and all it's descendents) as JSON + * and writes the serialization to the given output stream. + * + * @param aNode - a nsINavHistoryResultNode + * @param aStream - a nsIOutputStream. NOTE: it only uses the write(str, len) + * method of nsIOutputStream. The caller is responsible for + * closing the stream. + */ + serializeNodeAsJSONToOutputStream: + function PU_serializeNodeAsJSONToOutputStream(aNode, aStream) { + var self = this; + + function addGenericProperties(aPlacesNode, aJSNode) { + aJSNode.title = unescape(encodeURIComponent(aPlacesNode.title)); + var id = aPlacesNode.itemId; + if (id != -1) { + aJSNode.id = id; + + var parent = aPlacesNode.parent; + if (parent) + aJSNode.parent = parent.itemId; + var dateAdded = aPlacesNode.dateAdded; + if (dateAdded) + aJSNode.dateAdded = dateAdded; + var lastModified = aPlacesNode.lastModified; + if (lastModified) + aJSNode.lastModified = lastModified; + + // XXX need a hasAnnos api + var annos = []; + try { + annos = self.getAnnotationsForItem(id).filter(function(anno) { + // XXX should whitelist this instead, w/ a pref for + // backup/restore of non-whitelisted annos + // XXX causes JSON encoding errors, so utf-8 encode + //anno.value = unescape(encodeURIComponent(anno.value)); + if (anno.name == "livemark/feedURI") + aJSNode.livemark = 1; + return anno.name != "placesInternal/GUID"; + }); + } catch(ex) { + LOG(ex); + } + if (annos.length != 0) + aJSNode.annos = annos; + } + // XXXdietrich - store annos for non-bookmark items + } + + function addURIProperties(aPlacesNode, aJSNode) { + aJSNode.type = self.TYPE_X_MOZ_PLACE; + aJSNode.uri = aPlacesNode.uri; + if (aJSNode.id && aJSNode.id != -1) { + // harvest bookmark-specific properties + var keyword = self.bookmarks.getKeywordForBookmark(aJSNode.id); + if (keyword) + aJSNode.keyword = keyword; + } + } + + function addSeparatorProperties(aPlacesNode, aJSNode) { + aJSNode.type = self.TYPE_X_MOZ_PLACE_SEPARATOR; + } + + function addContainerProperties(aPlacesNode, aJSNode) { + // saved queries + if (aJSNode.id != -1 && + self.bookmarks.getItemType(aJSNode.id) == self.bookmarks.TYPE_BOOKMARK) { + aJSNode.type = self.TYPE_X_MOZ_PLACE; + aJSNode.uri = aPlacesNode.uri; + return; + } + else if (aJSNode.id != -1) { // bookmark folder + aJSNode.type = self.TYPE_X_MOZ_PLACE_CONTAINER; + // mark special folders + if (aJSNode.id == self.bookmarks.placesRoot) + aJSNode.root = "placesRoot"; + else if (aJSNode.id == self.bookmarks.bookmarksMenuFolder) + aJSNode.root = "bookmarksMenuFolder"; + else if (aJSNode.id == self.bookmarks.tagsFolder) + aJSNode.root = "tagsFolder"; + else if (aJSNode.id == self.bookmarks.unfiledBookmarksFolder) + aJSNode.root = "unfiledBookmarksFolder"; + else if (aJSNode.id == self.bookmarks.toolbarFolder) + aJSNode.root = "toolbarFolder"; + } + } + + function writeScalarNode(aStream, aNode) { + // serialize to json + var jstr = self.toJSONString(aNode); + // write to stream + aStream.write(jstr, jstr.length); + } + + function writeComplexNode(aStream, aNode, aSourceNode) { + // write prefix + var properties = []; + for (let [name, value] in Iterator(aNode)) { + if (name == "annos") + value = self.toJSONString(value); + else if (typeof value == "string") + value = "\"" + value + "\""; + properties.push("\"" + name + "\":" + value); + } + var jStr = "{" + properties.join(",") + ",\"children\":["; + aStream.write(jStr, jStr.length); + + // write child nodes + if (!aNode.livemark) { + asContainer(aSourceNode); + aSourceNode.containerOpen = true; + var cc = aSourceNode.childCount; + for (var i = 0; i < cc; ++i) { + if (i != 0) + aStream.write(",", 1); + serializeNodeToJSONStream(aSourceNode.getChild(i), i); + } + aSourceNode.containerOpen = false; + } + + // write suffix + aStream.write("]}", 2); + } + + function serializeNodeToJSONStream(bNode, aIndex) { + var node = {}; + + // set index in order received + // XXX handy shortcut, but are there cases where we don't want + // to export using the sorting provided by the query? + if (aIndex) + node.index = aIndex; + + addGenericProperties(bNode, node); + + if (self.nodeIsURI(bNode)) + addURIProperties(bNode, node); + else if (self.nodeIsContainer(bNode)) + addContainerProperties(bNode, node); + else if (self.nodeIsSeparator(bNode)) + addSeparatorProperties(bNode, node); + + if (!node.feedURI && node.type == self.TYPE_X_MOZ_PLACE_CONTAINER) + writeComplexNode(aStream, node, bNode); + else + writeScalarNode(aStream, node); + } + + // serialize to stream + serializeNodeToJSONStream(aNode, null); + }, + + // XXX testing serializers + toJSONString: function PIO_toJSONString(aObj) { + var JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON); + return JSON.encode(aObj); + }, + + /** + * Serializes bookmarks using JSON, and writes to the supplied file. + */ + backupBookmarksToFile: function PIO_backupBookmarksToFile(aFile) { + if (aFile.exists() && !aFile.isWritable()) + return; // XXX + + // init stream + var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0); + + // query places root + var options = this.history.getNewQueryOptions(); + options.expandQueries = false; + var query = this.history.getNewQuery(); + query.setFolders([this.bookmarks.placesRoot], 1); + var result = this.history.executeQuery(query, options); + result.root.containerOpen = true; + // serialize as JSON, write to stream + this.serializeNodeAsJSONToOutputStream(result.root, stream); + result.root.containerOpen = false; + + // close stream + if (stream instanceof Ci.nsISafeOutputStream) + stream.finish(); + else + stream.close(); + }, + + /** + * ArchiveBookmarksFile() + * + * Creates a dated backup once a day in /bookmarkbackups. + * Stores the bookmarks using JSON. + * + * @param int aNumberOfBackups - the maximum number of backups to keep + * + * @param bool aForceArchive - forces creating an archive even if one was + * already created that day (overwrites) + */ + archiveBookmarksFile: + function PU_archiveBookmarksFile(aNumberOfBackups, aForceArchive) { + // get/create backups directory + var dirService = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + var bookmarksBackupDir = dirService.get("ProfD", Ci.nsILocalFile); + bookmarksBackupDir.append("bookmarkbackups"); + if (!bookmarksBackupDir.exists()) { + bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700); + if (!bookmarksBackupDir.exists()) + return; // unable to create directory! + } + + // construct the new leafname + // Use YYYY-MM-DD (ISO 8601) as it doesn't contain illegal characters + // and makes the alphabetical order of multiple backup files more useful. + var date = new Date().toLocaleFormat("%Y-%m-%d"); + var backupFilename = this.getFormattedString("bookmarksArchiveFilename", [date]); + + var backupFile = null; + if (!aForceArchive) { + var backupFileNames = []; + var backupFilenamePrefix = backupFilename.substr(0, backupFilename.indexOf("-")); + var entries = bookmarksBackupDir.directoryEntries; + while (entries.hasMoreElements()) { + var entry = entries.getNext().QueryInterface(Ci.nsIFile); + var backupName = entry.leafName; + if (backupName.substr(0, backupFilenamePrefix.length) == backupFilenamePrefix) { + if (backupName == backupFilename) + backupFile = entry; + backupFileNames.push(backupName); + } + } + + if (aNumberOfBackups > 0 && backupFileNames.length >= aNumberOfBackups) { + var numberOfBackupsToDelete = backupFileNames.length - aNumberOfBackups; + backupFileNames.sort(); + while (numberOfBackupsToDelete--) { + backupFile = bookmarksBackupDir.clone(); + backupFile.append(backupFileNames[0]); + backupFile.remove(false); + backupFileNames.shift(); + } + } + + if (backupFile) + return; // already have today's backup, job done + } + + backupFile = bookmarksBackupDir.clone(); + backupFile.append(backupFilename); + + if (aForceArchive && backupFile.exists()) + backupFile.remove(false); + + if (!backupFile.exists()) + backupFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); + + this.backupBookmarksToFile(backupFile); + }, + + /** + * Get the most recent backup file. + * @returns nsIFile backup file + */ + getMostRecentBackup: function PU_getMostRecentBackup() { + var dirService = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + var bookmarksBackupDir = dirService.get("ProfD", Ci.nsILocalFile); + bookmarksBackupDir.append("bookmarkbackups"); + if (!bookmarksBackupDir.exists()) + return null; + + var backups = []; + var entries = bookmarksBackupDir.directoryEntries; + while (entries.hasMoreElements()) { + var entry = entries.getNext().QueryInterface(Ci.nsIFile); + if (!entry.isHidden() && entry.leafName.match(/^bookmarks-.+(html|json)?$/)) + backups.push(entry.leafName); + } + + if (backups.length == 0) + return null; + + backups.sort(); + var filename = backups.pop(); + + var backupFile = bookmarksBackupDir.clone(); + backupFile.append(filename); + return backupFile; + } +}; diff --git a/toolkit/content/Makefile.in b/toolkit/content/Makefile.in index fb08abf0ac4d..45881f489e2b 100644 --- a/toolkit/content/Makefile.in +++ b/toolkit/content/Makefile.in @@ -51,6 +51,9 @@ ifdef ENABLE_TESTS DIRS += tests endif +EXTRA_JS_MODULES = debug.js +EXTRA_PP_JS_MODULES = debug.js + include $(topsrcdir)/config/rules.mk distclean:: diff --git a/toolkit/content/debug.js b/toolkit/content/debug.js index 7287c49514dd..a8552b465304 100644 --- a/toolkit/content/debug.js +++ b/toolkit/content/debug.js @@ -42,6 +42,8 @@ # This file contains functions that are useful for debugging purposes from # within JavaScript code. +var EXPORTED_SYMBOLS = ["NS_ASSERT"]; + var gTraceOnAssert = true; /** diff --git a/toolkit/content/globalOverlay.js b/toolkit/content/globalOverlay.js index 6efa76500a3f..196b18e09c53 100644 --- a/toolkit/content/globalOverlay.js +++ b/toolkit/content/globalOverlay.js @@ -196,4 +196,4 @@ function FillInTooltip ( tipElement ) return retVal; } -#include debug.js +Components.utils.import("resource://gre/modules/debug.js"); diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index d51c9c2ca8b4..a04cae42be5d 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -11,7 +11,6 @@ toolkit.jar: * content/global/about.xhtml (about.xhtml) content/global/plugins.html content/global/plugins.css -* content/global/debug.js (debug.js) + content/global/buildconfig.html (buildconfig.html) + content/global/charsetOverlay.js (charsetOverlay.js) + content/global/charsetOverlay.xul (charsetOverlay.xul)