summaryrefslogtreecommitdiff
path: root/browser/components/places/PlacesUIUtils.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/places/PlacesUIUtils.jsm')
-rw-r--r--browser/components/places/PlacesUIUtils.jsm1375
1 files changed, 1375 insertions, 0 deletions
diff --git a/browser/components/places/PlacesUIUtils.jsm b/browser/components/places/PlacesUIUtils.jsm
new file mode 100644
index 000000000..8a7d4a00f
--- /dev/null
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -0,0 +1,1375 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["PlacesUIUtils"];
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
+ Cu.import("resource://gre/modules/PlacesUtils.jsm");
+ return PlacesUtils;
+});
+
+this.PlacesUIUtils = {
+ ORGANIZER_LEFTPANE_VERSION: 7,
+ ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
+ ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
+
+ LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
+ DESCRIPTION_ANNO: "bookmarkProperties/description",
+
+ TYPE_TAB_DROP: "application/x-moz-tabbrowser-tab",
+
+ /**
+ * Makes a URI from a spec, and do fixup
+ * @param aSpec
+ * The string spec of the URI
+ * @returns A URI object for the spec.
+ */
+ createFixedURI: function(aSpec) {
+ return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
+ },
+
+ getFormattedString: function(key, params) {
+ return bundle.formatStringFromName(key, params, params.length);
+ },
+
+ /**
+ * Get a localized plural string for the specified key name and numeric value
+ * substituting parameters.
+ *
+ * @param aKey
+ * String, key for looking up the localized string in the bundle
+ * @param aNumber
+ * Number based on which the final localized form is looked up
+ * @param aParams
+ * Array whose items will substitute #1, #2,... #n parameters
+ * in the string.
+ *
+ * @see https://developer.mozilla.org/en/Localization_and_Plurals
+ * @return The localized plural string.
+ */
+ getPluralString: function(aKey, aNumber, aParams) {
+ let str = PluralForm.get(aNumber, bundle.GetStringFromName(aKey));
+
+ // Replace #1 with aParams[0], #2 with aParams[1], and so on.
+ return str.replace(/\#(\d+)/g, function(matchedId, matchedNumber) {
+ let param = aParams[parseInt(matchedNumber, 10) - 1];
+ return param !== undefined ? param : matchedId;
+ });
+ },
+
+ getString: function(key) {
+ return bundle.GetStringFromName(key);
+ },
+
+ get _copyableAnnotations() [
+ this.DESCRIPTION_ANNO,
+ this.LOAD_IN_SIDEBAR_ANNO,
+ PlacesUtils.POST_DATA_ANNO,
+ PlacesUtils.READ_ONLY_ANNO,
+ ],
+
+ /**
+ * Get a transaction for copying a uri item (either a bookmark or a history
+ * entry) from one container to another.
+ *
+ * @param aData
+ * JSON object of dropped or pasted item properties
+ * @param aContainer
+ * The container being copied into
+ * @param aIndex
+ * The index within the container the item is copied to
+ * @return A nsITransaction object that performs the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getURIItemCopyTransaction:
+ function(aData, aContainer, aIndex)
+ {
+ let transactions = [];
+ if (aData.dateAdded) {
+ transactions.push(
+ new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
+ );
+ }
+ if (aData.lastModified) {
+ transactions.push(
+ new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
+ );
+ }
+
+ let keyword = aData.keyword || null;
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function(aAnno) {
+ return this._copyableAnnotations.indexOf(aAnno.name) != -1;
+ }, this);
+ }
+
+ return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(aData.uri),
+ aContainer, aIndex, aData.title,
+ keyword, annos, transactions);
+ },
+
+ /**
+ * Gets a transaction for copying (recursively nesting to include children)
+ * a folder (or container) and its contents from one folder to another.
+ *
+ * @param aData
+ * Unwrapped dropped folder data - Obj containing folder and children
+ * @param aContainer
+ * The container we are copying into
+ * @param aIndex
+ * The index in the destination container to insert the new items
+ * @return A nsITransaction object that will perform the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getFolderCopyTransaction(aData, aContainer, aIndex) {
+ function getChildItemsTransactions(aRoot) {
+ let transactions = [];
+ let index = aIndex;
+ for (let i = 0; i < aRoot.childCount; ++i) {
+ let child = aRoot.getChild(i);
+ // Temporary hacks until we switch to PlacesTransactions.jsm.
+ let isLivemark =
+ PlacesUtils.annotations.itemHasAnnotation(child.itemId,
+ PlacesUtils.LMANNO_FEEDURI);
+ let [node] = PlacesUtils.unwrapNodes(
+ PlacesUtils.wrapNode(child, PlacesUtils.TYPE_X_MOZ_PLACE, isLivemark),
+ PlacesUtils.TYPE_X_MOZ_PLACE
+ );
+
+ // Make sure that items are given the correct index, this will be
+ // passed by the transaction manager to the backend for the insertion.
+ // Insertion behaves differently for DEFAULT_INDEX (append).
+ if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX) {
+ index = i;
+ }
+
+ if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
+ if (node.livemark && node.annos) {
+ transactions.push(
+ PlacesUIUtils._getLivemarkCopyTransaction(node, aContainer, index)
+ );
+ }
+ else {
+ transactions.push(
+ PlacesUIUtils._getFolderCopyTransaction(node, aContainer, index)
+ );
+ }
+ }
+ else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
+ transactions.push(new PlacesCreateSeparatorTransaction(-1, index));
+ }
+ else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) {
+ transactions.push(
+ PlacesUIUtils._getURIItemCopyTransaction(node, -1, index)
+ );
+ }
+ else {
+ throw new Error("Unexpected item under a bookmarks folder");
+ }
+ }
+ return transactions;
+ }
+
+ if (aContainer == PlacesUtils.tagsFolderId) { // Copying into a tag folder.
+ let transactions = [];
+ if (!aData.livemark && aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
+ let {root} = PlacesUtils.getFolderContents(aData.id, false, false);
+ let urls = PlacesUtils.getURLsForContainerNode(root);
+ root.containerOpen = false;
+ for (let { uri } of urls) {
+ transactions.push(
+ new PlacesTagURITransaction(NetUtil.newURI(uri), [aData.title])
+ );
+ }
+ }
+ return new PlacesAggregatedTransaction("addTags", transactions);
+ }
+
+ if (aData.livemark && aData.annos) { // Copying a livemark.
+ return this._getLivemarkCopyTransaction(aData, aContainer, aIndex);
+ }
+
+ let {root} = PlacesUtils.getFolderContents(aData.id, false, false);
+ let transactions = getChildItemsTransactions(root);
+ root.containerOpen = false;
+
+ if (aData.dateAdded) {
+ transactions.push(
+ new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
+ );
+ }
+ if (aData.lastModified) {
+ transactions.push(
+ new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
+ );
+ }
+
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function(aAnno) {
+ return this._copyableAnnotations.indexOf(aAnno.name) != -1;
+ }, this);
+ }
+
+ return new PlacesCreateFolderTransaction(aData.title, aContainer, aIndex,
+ annos, transactions);
+ },
+
+ /**
+ * Gets a transaction for copying a live bookmark item from one container to
+ * another.
+ *
+ * @param aData
+ * Unwrapped live bookmarkmark data
+ * @param aContainer
+ * The container we are copying into
+ * @param aIndex
+ * The index in the destination container to insert the new items
+ * @return A nsITransaction object that will perform the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getLivemarkCopyTransaction:
+ function(aData, aContainer, aIndex)
+ {
+ if (!aData.livemark || !aData.annos) {
+ throw new Error("node is not a livemark");
+ }
+
+ let feedURI, siteURI;
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function(aAnno) {
+ if (aAnno.name == PlacesUtils.LMANNO_FEEDURI) {
+ feedURI = PlacesUtils._uri(aAnno.value);
+ }
+ else if (aAnno.name == PlacesUtils.LMANNO_SITEURI) {
+ siteURI = PlacesUtils._uri(aAnno.value);
+ }
+ return this._copyableAnnotations.indexOf(aAnno.name) != -1
+ }, this);
+ }
+
+ return new PlacesCreateLivemarkTransaction(feedURI, siteURI, aData.title,
+ aContainer, aIndex, annos);
+ },
+
+ /**
+ * Test if a bookmark item = a live bookmark item.
+ *
+ * @param aItemId
+ * item identifier
+ * @return true if a live bookmark item, false otherwise.
+ *
+ * @note Maybe this should be removed later, see bug 1072833.
+ */
+ _isLivemark:
+ function(aItemId)
+ {
+ // Since this check may be done on each dragover event, it's worth maintaining
+ // a cache.
+ let self = this._isLivemark;
+ if (!("ids" in self)) {
+ const LIVEMARK_ANNO = PlacesUtils.LMANNO_FEEDURI;
+
+ let idsVec = PlacesUtils.annotations.getItemsWithAnnotation(LIVEMARK_ANNO);
+ self.ids = new Set(idsVec);
+
+ let obs = Object.freeze({
+ QueryInterface: XPCOMUtils.generateQI(Ci.nsIAnnotationObserver),
+
+ onItemAnnotationSet(itemId, annoName) {
+ if (annoName == LIVEMARK_ANNO)
+ self.ids.add(itemId);
+ },
+
+ onItemAnnotationRemoved(itemId, annoName) {
+ // If annoName is set to an empty string, the item is gone.
+ if (annoName == LIVEMARK_ANNO || annoName == "")
+ self.ids.delete(itemId);
+ },
+
+ onPageAnnotationSet() { },
+ onPageAnnotationRemoved() { },
+ });
+ PlacesUtils.annotations.addObserver(obs);
+ PlacesUtils.registerShutdownFunction(() => {
+ PlacesUtils.annotations.removeObserver(obs);
+ });
+ }
+ return self.ids.has(aItemId);
+ },
+
+ /**
+ * Constructs a Transaction for the drop or paste of a blob of data into
+ * a container.
+ * @param data
+ * The unwrapped data blob of dropped or pasted data.
+ * @param type
+ * The content type of the data
+ * @param container
+ * The container the data was dropped or pasted into
+ * @param index
+ * The index within the container the item was dropped or pasted at
+ * @param copy
+ * The drag action was copy, so don't move folders or links.
+ * @returns An object implementing nsITransaction that can perform
+ * the move/insert.
+ */
+ makeTransaction:
+ function(data, type, container, index, copy)
+ {
+ switch (data.type) {
+ case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
+ if (copy) {
+ return this._getFolderCopyTransaction(data, container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ break;
+ case PlacesUtils.TYPE_X_MOZ_PLACE:
+ if (copy || data.id == -1) { // Id is -1 if the place is not bookmarked.
+ return this._getURIItemCopyTransaction(data, container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ break;
+ case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
+ if (copy) {
+ // There is no data in a separator, so copying it just amounts to
+ // inserting a new separator.
+ return new PlacesCreateSeparatorTransaction(container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ break;
+ default:
+ if (type == PlacesUtils.TYPE_X_MOZ_URL ||
+ type == PlacesUtils.TYPE_UNICODE ||
+ type == this.TYPE_TAB_DROP) {
+ let title = type != PlacesUtils.TYPE_UNICODE ? data.title
+ : data.uri;
+ return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri),
+ container, index, title);
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Shows the bookmark dialog corresponding to the specified info.
+ *
+ * @param aInfo
+ * Describes the item to be edited/added in the dialog.
+ * See documentation at the top of bookmarkProperties.js
+ * @param aWindow
+ * Owner window for the new dialog.
+ *
+ * @see documentation at the top of bookmarkProperties.js
+ * @return true if any transaction has been performed, false otherwise.
+ */
+ showBookmarkDialog:
+ function(aInfo, aParentWindow) {
+ // Preserve size attributes differently based on the fact the dialog has
+ // a folder picker or not, since it needs more horizontal space than the
+ // other controls.
+ let hasFolderPicker = !("hiddenRows" in aInfo) ||
+ aInfo.hiddenRows.indexOf("folderPicker") == -1;
+ // Use a different chrome url to persist different sizes.
+ let dialogURL = hasFolderPicker ?
+ "chrome://browser/content/places/bookmarkProperties2.xul" :
+ "chrome://browser/content/places/bookmarkProperties.xul";
+
+ let features = "centerscreen,chrome,modal,resizable=yes";
+ aParentWindow.openDialog(dialogURL, "", features, aInfo);
+ return ("performed" in aInfo && aInfo.performed);
+ },
+
+ _getTopBrowserWin: function() {
+ return RecentWindow.getMostRecentBrowserWindow();
+ },
+
+ /**
+ * Returns the closet ancestor places view for the given DOM node
+ * @param aNode
+ * a DOM node
+ * @return the closet ancestor places view if exists, null otherwsie.
+ */
+ getViewForNode: function(aNode) {
+ let node = aNode;
+
+ // The view for a <menu> of which its associated menupopup is a places
+ // view, is the menupopup.
+ if (node.localName == "menu" && !node._placesNode &&
+ node.lastChild._placesView)
+ return node.lastChild._placesView;
+
+ while (node instanceof Ci.nsIDOMElement) {
+ if (node._placesView)
+ return node._placesView;
+ if (node.localName == "tree" && node.getAttribute("type") == "places")
+ return node;
+
+ node = node.parentNode;
+ }
+
+ return null;
+ },
+
+ /**
+ * By calling this before visiting an URL, the visit will be associated to a
+ * TRANSITION_TYPED transition (if there is no a referrer).
+ * This is used when visiting pages from the history menu, history sidebar,
+ * url bar, url autocomplete results, and history searches from the places
+ * organizer. If this is not called visits will be marked as
+ * TRANSITION_LINK.
+ */
+ markPageAsTyped: function(aURL) {
+ PlacesUtils.history.markPageAsTyped(this.createFixedURI(aURL));
+ },
+
+ /**
+ * By calling this before visiting an URL, the visit will be associated to a
+ * TRANSITION_BOOKMARK transition.
+ * This is used when visiting pages from the bookmarks menu,
+ * personal toolbar, and bookmarks from within the places organizer.
+ * If this is not called visits will be marked as TRANSITION_LINK.
+ */
+ markPageAsFollowedBookmark: function(aURL) {
+ PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL));
+ },
+
+ /**
+ * By calling this before visiting an URL, any visit in frames will be
+ * associated to a TRANSITION_FRAMED_LINK transition.
+ * This is actually used to distinguish user-initiated visits in frames
+ * so automatic visits can be correctly ignored.
+ */
+ markPageAsFollowedLink: function(aURL) {
+ PlacesUtils.history.markPageAsFollowedLink(this.createFixedURI(aURL));
+ },
+
+ /**
+ * Allows opening of javascript/data URI only if the given node is
+ * bookmarked (see bug 224521).
+ * @param aURINode
+ * a URI node
+ * @param aWindow
+ * a window on which a potential error alert is shown on.
+ * @return true if it's safe to open the node in the browser, false otherwise.
+ *
+ */
+ checkURLSecurity: function(aURINode, aWindow) {
+ if (PlacesUtils.nodeIsBookmark(aURINode))
+ return true;
+
+ 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"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(BRANDING_BUNDLE_URI).
+ GetStringFromName("brandShortName");
+
+ var errorStr = this.getString("load-js-data-url-error");
+ Services.prompt.alert(aWindow, brandShortName, errorStr);
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Get the description associated with a document, as specified in a <META>
+ * element.
+ * @param doc
+ * A DOM Document to get a description for
+ * @returns A description string if a META element was discovered with a
+ * "description" or "httpequiv" attribute, empty string otherwise.
+ */
+ getDescriptionFromDocument: function(doc) {
+ var metaElements = doc.getElementsByTagName("META");
+ for (var i = 0; i < metaElements.length; ++i) {
+ if (metaElements[i].name.toLowerCase() == "description" ||
+ metaElements[i].httpEquiv.toLowerCase() == "description") {
+ return metaElements[i].content;
+ }
+ }
+ return "";
+ },
+
+ /**
+ * Retrieve the description of an item
+ * @param aItemId
+ * item identifier
+ * @returns the description of the given item, or an empty string if it is
+ * not set.
+ */
+ getItemDescription: function(aItemId) {
+ if (PlacesUtils.annotations.itemHasAnnotation(aItemId, this.DESCRIPTION_ANNO))
+ return PlacesUtils.annotations.getItemAnnotation(aItemId, this.DESCRIPTION_ANNO);
+ return "";
+ },
+
+ /**
+ * Check whether or not the given node represents a removable entry (either in
+ * history or in bookmarks).
+ *
+ * @param aNode
+ * a node, except the root node of a query.
+ * @return true if the aNode represents a removable entry, false otherwise.
+ */
+ canUserRemove: function(aNode) {
+ let parentNode = aNode.parent;
+ if (!parentNode)
+ throw new Error("canUserRemove doesn't accept root nodes");
+
+ // If it's not a bookmark, we can remove it unless it's a child of a
+ // livemark.
+ if (aNode.itemId == -1) {
+ // Rather than executing a db query, checking the existence of the feedURI
+ // annotation, detect livemark children by the fact that they are the only
+ // direct non-bookmark children of bookmark folders.
+ return !PlacesUtils.nodeIsFolder(parentNode);
+ }
+
+ // Generally it's always possible to remove children of a query.
+ if (PlacesUtils.nodeIsQuery(parentNode))
+ return true;
+
+ // Otherwise it has to be a child of an editable folder.
+ return !this.isContentsReadOnly(parentNode);
+ },
+
+ /**
+ * DO NOT USE THIS API IN ADDONS. IT IS VERY LIKELY TO CHANGE WHEN THE SWITCH
+ * TO GUIDS IS COMPLETE (BUG 1071511).
+ *
+ * Check whether or not the given node or item-id points to a folder which
+ * should not be modified by the user (i.e. its children should be unremovable
+ * and unmovable, new children should be disallowed, etc).
+ * These semantics are not inherited, meaning that read-only folder may
+ * contain editable items (for instance, the places root is read-only, but all
+ * of its direct children aren't).
+ *
+ * You should only pass folder item ids or folder nodes for aNodeOrItemId.
+ * While this is only enforced for the node case (if an item id of a separator
+ * or a bookmark is passed, false is returned), it's considered the caller's
+ * job to ensure that it checks a folder.
+ * Also note that folder-shortcuts should only be passed as result nodes.
+ * Otherwise they are just treated as bookmarks (i.e. false is returned).
+ *
+ * @param aNodeOrItemId
+ * any item id or result node.
+ * @throws if aNodeOrItemId is neither an item id nor a folder result node.
+ * @note livemark "folders" are considered read-only (but see bug 1072833).
+ * @return true if aItemId points to a read-only folder, false otherwise.
+ */
+ isContentsReadOnly: function(aNodeOrItemId) {
+ let itemId;
+ if (typeof(aNodeOrItemId) == "number") {
+ itemId = aNodeOrItemId;
+ }
+ else if (PlacesUtils.nodeIsFolder(aNodeOrItemId)) {
+ itemId = PlacesUtils.getConcreteItemId(aNodeOrItemId);
+ }
+ else {
+ throw new Error("invalid value for aNodeOrItemId");
+ }
+
+ if (itemId == PlacesUtils.placesRootId || this._isLivemark(itemId))
+ return true;
+
+ // leftPaneFolderId, and as a result, allBookmarksFolderId, is a lazy getter
+ // performing at least a synchronous DB query (and on its very first call
+ // in a fresh profile, it also creates the entire structure).
+ // Therefore we don't want to this function, which is called very often by
+ // isCommandEnabled, to ever be the one that invokes it first, especially
+ // because isCommandEnabled may be called way before the left pane folder is
+ // even created (for example, if the user only uses the bookmarks menu or
+ // toolbar for managing bookmarks). To do so, we avoid comparing to those
+ // special folder if the lazy getter is still in place. This is safe merely
+ // because the only way to access the left pane contents goes through
+ // "resolving" the leftPaneFolderId getter.
+ if ("get" in Object.getOwnPropertyDescriptor(this, "leftPaneFolderId"))
+ return false;
+
+ return itemId == this.leftPaneFolderId ||
+ itemId == this.allBookmarksFolderId;
+ },
+
+ /**
+ * Gives the user a chance to cancel loading lots of tabs at once
+ */
+ _confirmOpenInTabs:
+ function(numTabsToOpen, aWindow) {
+ const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen";
+ var reallyOpen = true;
+
+ if (Services.prefs.getBoolPref(WARN_ON_OPEN_PREF)) {
+ if (numTabsToOpen >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
+ // default to true: if it were false, we wouldn't get this far
+ var warnOnOpen = { value: true };
+
+ var messageKey = "tabs.openWarningMultipleBranded";
+ var openKey = "tabs.openButtonMultiple";
+ 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 buttonPressed = Services.prompt.confirmEx(
+ aWindow,
+ this.getString("tabs.openWarningTitle"),
+ this.getFormattedString(messageKey, [numTabsToOpen, brandShortName]),
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1),
+ this.getString(openKey), null, null,
+ this.getFormattedString("tabs.openWarningPromptMeBranded",
+ [brandShortName]),
+ warnOnOpen
+ );
+
+ reallyOpen = (buttonPressed == 0);
+ // don't set the pref unless they press OK and it's false
+ if (reallyOpen && !warnOnOpen.value)
+ Services.prefs.setBoolPref(WARN_ON_OPEN_PREF, false);
+ }
+ }
+
+ return reallyOpen;
+ },
+
+ /** aItemsToOpen needs to be an array of objects of the form:
+ * {uri: string, isBookmark: boolean}
+ */
+ _openTabset: function(aItemsToOpen, aEvent, aWindow) {
+ if (!aItemsToOpen.length)
+ return;
+
+ // Prefer the caller window if it's a browser window, otherwise use
+ // the top browser window.
+ var browserWindow = null;
+ browserWindow =
+ aWindow && aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser" ?
+ aWindow : this._getTopBrowserWin();
+
+ var urls = [];
+ let skipMarking = browserWindow && PrivateBrowsingUtils.isWindowPrivate(browserWindow);
+ for (let item of aItemsToOpen) {
+ urls.push(item.uri);
+ if (skipMarking) {
+ continue;
+ }
+
+ if (item.isBookmark)
+ this.markPageAsFollowedBookmark(item.uri);
+ else
+ this.markPageAsTyped(item.uri);
+ }
+
+ // whereToOpenLink doesn't return "window" when there's no browser window
+ // open (Bug 630255).
+ var where = browserWindow ?
+ browserWindow.whereToOpenLink(aEvent, false, true) : "window";
+ if (where == "window") {
+ // There is no browser window open, thus open a new one.
+ var uriList = PlacesUtils.toISupportsString(urls.join("|"));
+ var args = Cc["@mozilla.org/supports-array;1"].
+ createInstance(Ci.nsISupportsArray);
+ args.AppendElement(uriList);
+ browserWindow = Services.ww.openWindow(aWindow,
+ "chrome://browser/content/browser.xul",
+ null, "chrome,dialog=no,all", args);
+ return;
+ }
+
+ var loadInBackground = where == "tabshifted" ? true : false;
+ // For consistency, we want all the bookmarks to open in new tabs, instead
+ // of having one of them replace the currently focused tab. Hence we call
+ // loadTabs with aReplace set to false.
+ browserWindow.gBrowser.loadTabs(urls, loadInBackground, false);
+ },
+
+ openLiveMarkNodesInTabs:
+ function(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ PlacesUtils.livemarks.getLivemark({id: aNode.itemId})
+ .then(aLivemark => {
+ urlsToOpen = [];
+
+ let nodes = aLivemark.getNodesForContainer(aNode);
+ for (let node of nodes) {
+ urlsToOpen.push({uri: node.uri, isBookmark: false});
+ }
+
+ if (this._confirmOpenInTabs(urlsToOpen.length, window)) {
+ this._openTabset(urlsToOpen, aEvent, window);
+ }
+ }, Cu.reportError);
+ },
+
+ openContainerNodeInTabs:
+ function(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
+ if (this._confirmOpenInTabs(urlsToOpen.length, window)) {
+ this._openTabset(urlsToOpen, aEvent, window);
+ }
+ },
+
+ openURINodesInTabs: function(aNodes, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ let urlsToOpen = [];
+ for (var i=0; i < aNodes.length; i++) {
+ // Skip over separators and folders.
+ if (PlacesUtils.nodeIsURI(aNodes[i]))
+ urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])});
+ }
+ if (this._confirmOpenInTabs(urlsToOpen.length, window)) {
+ this._openTabset(urlsToOpen, aEvent, window);
+ }
+ },
+
+ /**
+ * Loads the node's URL in the appropriate tab or window or as a web
+ * panel given the user's preference specified by modifier keys tracked by a
+ * DOM mouse/key event.
+ * @param aNode
+ * An uri result node.
+ * @param aEvent
+ * The DOM mouse/key event with modifier keys set that track the
+ * user's preferred destination window or tab.
+ * @param aView
+ * The controller associated with aNode.
+ */
+ openNodeWithEvent:
+ function(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+ this._openNodeIn(aNode, window.whereToOpenLink(aEvent, false, true), window);
+ },
+
+ /**
+ * Loads the node's URL in the appropriate tab or window or as a
+ * web panel.
+ * see also openUILinkIn
+ */
+ openNodeIn: function(aNode, aWhere, aView, aPrivate) {
+ let window = aView.ownerWindow;
+ this._openNodeIn(aNode, aWhere, window, aPrivate);
+ },
+
+ _openNodeIn: function(aNode, aWhere, aWindow, aPrivate=false) {
+ if (aNode && PlacesUtils.nodeIsURI(aNode) &&
+ this.checkURLSecurity(aNode, aWindow)) {
+ let isBookmark = PlacesUtils.nodeIsBookmark(aNode);
+
+ if (!PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
+ if (isBookmark)
+ this.markPageAsFollowedBookmark(aNode.uri);
+ else
+ this.markPageAsTyped(aNode.uri);
+ }
+
+ // Check whether the node is a bookmark which should be opened as
+ // a web panel
+ if (aWhere == "current" && isBookmark) {
+ if (PlacesUtils.annotations
+ .itemHasAnnotation(aNode.itemId, this.LOAD_IN_SIDEBAR_ANNO)) {
+ let browserWin = this._getTopBrowserWin();
+ if (browserWin) {
+ browserWin.openWebPanel(aNode.title, aNode.uri);
+ return;
+ }
+ }
+ }
+ aWindow.openUILinkIn(aNode.uri, aWhere, {
+ inBackground: Services.prefs.getBoolPref("browser.tabs.loadBookmarksInBackground"),
+ private: aPrivate,
+ });
+ }
+ },
+
+ /**
+ * Helper for guessing scheme from an url string.
+ * Used to avoid nsIURI overhead in frequently called UI functions.
+ *
+ * @param aUrlString the url to guess the scheme from.
+ *
+ * @return guessed scheme for this url string.
+ *
+ * @note this is not supposed be perfect, so use it only for UI purposes.
+ */
+ guessUrlSchemeForUI: function(aUrlString) {
+ return aUrlString.substr(0, aUrlString.indexOf(":"));
+ },
+
+ getBestTitle: function(aNode, aDoNotCutTitle) {
+ var title;
+ if (!aNode.title && PlacesUtils.nodeIsURI(aNode)) {
+ // if node title is empty, try to set the label using host and filename
+ // PlacesUtils._uri() will throw if aNode.uri is not a valid URI
+ try {
+ var uri = PlacesUtils._uri(aNode.uri);
+ var host = uri.host;
+ var fileName = uri.QueryInterface(Ci.nsIURL).fileName;
+ // if fileName is empty, use path to distinguish labels
+ if (aDoNotCutTitle) {
+ title = host + uri.path;
+ } else {
+ title = host + (fileName ?
+ (host ? "/" + this.ellipsis + "/" : "") + fileName :
+ uri.path);
+ }
+ }
+ catch (e) {
+ // Use (no title) for non-standard URIs (data:, javascript:, ...)
+ title = "";
+ }
+ }
+ else
+ title = aNode.title;
+
+ return title || this.getString("noTitle");
+ },
+
+ get leftPaneQueries() {
+ // build the map
+ this.leftPaneFolderId;
+ return this.leftPaneQueries;
+ },
+
+ // Get the folder id for the organizer left-pane folder.
+ get leftPaneFolderId() {
+ let leftPaneRoot = -1;
+ let allBookmarksId;
+
+ // Shortcuts to services.
+ let bs = PlacesUtils.bookmarks;
+ let as = PlacesUtils.annotations;
+
+ // This is the list of the left pane queries.
+ let queries = {
+ "PlacesRoot": { title: "" },
+ "History": { title: this.getString("OrganizerQueryHistory") },
+ "Downloads": { title: this.getString("OrganizerQueryDownloads") },
+ "Tags": { title: this.getString("OrganizerQueryTags") },
+ "AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") },
+ "BookmarksToolbar":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"),
+ concreteId: PlacesUtils.toolbarFolderId },
+ "BookmarksMenu":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"),
+ concreteId: PlacesUtils.bookmarksMenuFolderId },
+ "UnfiledBookmarks":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("UnsortedBookmarksFolderTitle"),
+ concreteId: PlacesUtils.unfiledBookmarksFolderId },
+ };
+ // All queries but PlacesRoot.
+ const EXPECTED_QUERY_COUNT = 7;
+
+ // Removes an item and associated annotations, ignoring eventual errors.
+ function safeRemoveItem(aItemId) {
+ try {
+ if (as.itemHasAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) &&
+ !(as.getItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) in queries)) {
+ // Some extension annotated their roots with our query annotation,
+ // so we should not delete them.
+ return;
+ }
+ // removeItemAnnotation does not check if item exists, nor the anno,
+ // so this is safe to do.
+ as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO);
+ // This will throw if the annotation is an orphan.
+ bs.removeItem(aItemId);
+ }
+ catch(e) { /* orphan anno */ }
+ }
+
+ // Returns true if item really exists, false otherwise.
+ function itemExists(aItemId) {
+ try {
+ bs.getItemIndex(aItemId);
+ return true;
+ }
+ catch(e) {
+ return false;
+ }
+ }
+
+ // Get all items marked as being the left pane folder.
+ let items = as.getItemsWithAnnotation(this.ORGANIZER_FOLDER_ANNO);
+ if (items.length > 1) {
+ // Something went wrong, we cannot have more than one left pane folder,
+ // remove all left pane folders and continue. We will create a new one.
+ items.forEach(safeRemoveItem);
+ }
+ else if (items.length == 1 && items[0] != -1) {
+ leftPaneRoot = items[0];
+ // Check that organizer left pane root is valid.
+ let version = as.getItemAnnotation(leftPaneRoot, this.ORGANIZER_FOLDER_ANNO);
+ if (version != this.ORGANIZER_LEFTPANE_VERSION ||
+ !itemExists(leftPaneRoot)) {
+ // Invalid root, we must rebuild the left pane.
+ safeRemoveItem(leftPaneRoot);
+ leftPaneRoot = -1;
+ }
+ }
+
+ if (leftPaneRoot != -1) {
+ // A valid left pane folder has been found.
+ // Build the leftPaneQueries Map. This is used to quickly access them,
+ // associating a mnemonic name to the real item ids.
+ delete this.leftPaneQueries;
+ this.leftPaneQueries = {};
+
+ let items = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO);
+ // While looping through queries we will also check for their validity.
+ let queriesCount = 0;
+ for (let i = 0; i < items.length; i++) {
+ let queryName = as.getItemAnnotation(items[i], this.ORGANIZER_QUERY_ANNO);
+
+ // Some extension did use our annotation to decorate their items
+ // with icons, so we should check only our elements, to avoid dataloss.
+ if (!(queryName in queries))
+ continue;
+
+ let query = queries[queryName];
+ query.itemId = items[i];
+
+ if (!itemExists(query.itemId)) {
+ // Orphan annotation, bail out and create a new left pane root.
+ break;
+ }
+
+ // Check that all queries have valid parents.
+ let parentId = bs.getFolderIdForItem(query.itemId);
+ if (items.indexOf(parentId) == -1 && parentId != leftPaneRoot) {
+ // The parent is not part of the left pane, bail out and create a new
+ // left pane root.
+ break;
+ }
+
+ // Titles could have been corrupted or the user could have changed his
+ // locale. Check title and eventually fix it.
+ if (bs.getItemTitle(query.itemId) != query.title)
+ bs.setItemTitle(query.itemId, query.title);
+ if ("concreteId" in query) {
+ if (bs.getItemTitle(query.concreteId) != query.concreteTitle)
+ bs.setItemTitle(query.concreteId, query.concreteTitle);
+ }
+
+ // Add the query to our cache.
+ this.leftPaneQueries[queryName] = query.itemId;
+ queriesCount++;
+ }
+
+ if (queriesCount != EXPECTED_QUERY_COUNT) {
+ // Queries number is wrong, so the left pane must be corrupt.
+ // Note: we can't just remove the leftPaneRoot, because some query could
+ // have a bad parent, so we have to remove all items one by one.
+ items.forEach(safeRemoveItem);
+ safeRemoveItem(leftPaneRoot);
+ }
+ else {
+ // Everything is fine, return the current left pane folder.
+ delete this.leftPaneFolderId;
+ return this.leftPaneFolderId = leftPaneRoot;
+ }
+ }
+
+ // Create a new left pane folder.
+ var callback = {
+ // Helper to create an organizer special query.
+ create_query: function(aQueryName, aParentId, aQueryUrl) {
+ let itemId = bs.insertBookmark(aParentId,
+ PlacesUtils._uri(aQueryUrl),
+ bs.DEFAULT_INDEX,
+ queries[aQueryName].title);
+ // Mark as special organizer query.
+ as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName,
+ 0, as.EXPIRE_NEVER);
+ // We should never backup this, since it changes between profiles.
+ as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
+ 0, as.EXPIRE_NEVER);
+ // Add to the queries map.
+ PlacesUIUtils.leftPaneQueries[aQueryName] = itemId;
+ return itemId;
+ },
+
+ // Helper to create an organizer special folder.
+ create_folder: function(aFolderName, aParentId, aIsRoot) {
+ // Left Pane Root Folder.
+ let folderId = bs.createFolder(aParentId,
+ queries[aFolderName].title,
+ bs.DEFAULT_INDEX);
+ // We should never backup this, since it changes between profiles.
+ as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
+ 0, as.EXPIRE_NEVER);
+
+ if (aIsRoot) {
+ // Mark as special left pane root.
+ as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
+ PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
+ 0, as.EXPIRE_NEVER);
+ }
+ else {
+ // Mark as special organizer folder.
+ as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aFolderName,
+ 0, as.EXPIRE_NEVER);
+ PlacesUIUtils.leftPaneQueries[aFolderName] = folderId;
+ }
+ return folderId;
+ },
+
+ runBatched: function(aUserData) {
+ delete PlacesUIUtils.leftPaneQueries;
+ PlacesUIUtils.leftPaneQueries = { };
+
+ // Left Pane Root Folder.
+ leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true);
+
+ // History Query.
+ this.create_query("History", leftPaneRoot,
+ "place:type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
+
+ // Downloads.
+ this.create_query("Downloads", leftPaneRoot,
+ "place:transition=" +
+ Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
+
+ // Tags Query.
+ this.create_query("Tags", leftPaneRoot,
+ "place:type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING);
+
+ // All Bookmarks Folder.
+ allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false);
+
+ // All Bookmarks->Bookmarks Toolbar Query.
+ this.create_query("BookmarksToolbar", allBookmarksId,
+ "place:folder=TOOLBAR");
+
+ // All Bookmarks->Bookmarks Menu Query.
+ this.create_query("BookmarksMenu", allBookmarksId,
+ "place:folder=BOOKMARKS_MENU");
+
+ // All Bookmarks->Unfiled Bookmarks Query.
+ this.create_query("UnfiledBookmarks", allBookmarksId,
+ "place:folder=UNFILED_BOOKMARKS");
+ }
+ };
+ bs.runInBatchMode(callback, null);
+ // Maybe: PlacesUtils.bookmarks.runInBatchMode(callback, null); ?
+
+ delete this.leftPaneFolderId;
+ return this.leftPaneFolderId = leftPaneRoot;
+ },
+
+ /**
+ * Get the folder id for the organizer left-pane folder.
+ */
+ get allBookmarksFolderId() {
+ // ensure the left-pane root is initialized;
+ this.leftPaneFolderId;
+ delete this.allBookmarksFolderId;
+ return this.allBookmarksFolderId = this.leftPaneQueries["AllBookmarks"];
+ },
+
+ /**
+ * If an item is a left-pane query, returns the name of the query
+ * or an empty string if not.
+ *
+ * @param aItemId id of a container
+ * @returns the name of the query, or empty string if not a left-pane query
+ */
+ getLeftPaneQueryNameFromId: function(aItemId) {
+ var queryName = "";
+ // If the let pane hasn't been built, use the annotation service
+ // directly, to avoid building the left pane too early.
+ if (Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").value === undefined) {
+ try {
+ queryName = PlacesUtils.annotations.
+ getItemAnnotation(aItemId, this.ORGANIZER_QUERY_ANNO);
+ }
+ catch (ex) {
+ // doesn't have the annotation
+ queryName = "";
+ }
+ }
+ else {
+ // If the left pane has already been built, use the name->id map
+ // cached in PlacesUIUtils.
+ for (let [name, id] in Iterator(this.leftPaneQueries)) {
+ if (aItemId == id)
+ queryName = name;
+ }
+ }
+ return queryName;
+ },
+
+ /**
+ * Returns the passed URL with a #moz-resolution fragment
+ * for the specified dimensions and devicePixelRatio.
+ *
+ * @param aWindow
+ * A window from where we want to get the device
+ * pixel Ratio
+ *
+ * @param aURL
+ * The URL where we should add the fragment
+ *
+ * @param aWidth
+ * The target image width
+ *
+ * @param aHeight
+ * The target image height
+ *
+ * @return The URL with the fragment at the end
+ */
+ getImageURLForResolution:
+ function(aWindow, aURL, aWidth, aHeight) {
+ return aURL;
+ }
+};
+
+XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
+ "@mozilla.org/rdf/rdf-service;1",
+ "nsIRDFService");
+
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "localStore", function() {
+ return PlacesUIUtils.RDF.GetDataSource("rdf:local-store");
+});
+
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
+ return Services.prefs.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "URIFixup",
+ "@mozilla.org/docshell/urifixup;1",
+ "nsIURIFixup");
+
+XPCOMUtils.defineLazyGetter(this, "bundle", function() {
+ const PLACES_STRING_BUNDLE_URI =
+ "chrome://browser/locale/places/places.properties";
+ return Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(PLACES_STRING_BUNDLE_URI);
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "focusManager",
+ "@mozilla.org/focus-manager;1",
+ "nsIFocusManager");
+
+/**
+ * This is a compatibility shim for old PUIU.ptm users.
+ *
+ * If you're looking for transactions and writing new code using them, directly
+ * use the transactions objects exported by the PlacesUtils.jsm module.
+ *
+ * This object will be removed once enough users are converted to the new API.
+ */
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ptm", function() {
+ // Ensure PlacesUtils is imported in scope.
+ PlacesUtils;
+
+ return {
+ aggregateTransactions: function(aName, aTransactions)
+ new PlacesAggregatedTransaction(aName, aTransactions),
+
+ createFolder: function(aName, aContainer, aIndex, aAnnotations,
+ aChildItemsTransactions)
+ new PlacesCreateFolderTransaction(aName, aContainer, aIndex, aAnnotations,
+ aChildItemsTransactions),
+
+ createItem: function(aURI, aContainer, aIndex, aTitle, aKeyword,
+ aAnnotations, aChildTransactions)
+ new PlacesCreateBookmarkTransaction(aURI, aContainer, aIndex, aTitle,
+ aKeyword, aAnnotations,
+ aChildTransactions),
+
+ createSeparator: function(aContainer, aIndex)
+ new PlacesCreateSeparatorTransaction(aContainer, aIndex),
+
+ createLivemark: function(aFeedURI, aSiteURI, aName, aContainer, aIndex,
+ aAnnotations)
+ new PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aName, aContainer,
+ aIndex, aAnnotations),
+
+ moveItem: function(aItemId, aNewContainer, aNewIndex)
+ new PlacesMoveItemTransaction(aItemId, aNewContainer, aNewIndex),
+
+ removeItem: function(aItemId)
+ new PlacesRemoveItemTransaction(aItemId),
+
+ editItemTitle: function(aItemId, aNewTitle)
+ new PlacesEditItemTitleTransaction(aItemId, aNewTitle),
+
+ editBookmarkURI: function(aItemId, aNewURI)
+ new PlacesEditBookmarkURITransaction(aItemId, aNewURI),
+
+ setItemAnnotation: function(aItemId, aAnnotationObject)
+ new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject),
+
+ setPageAnnotation: function(aURI, aAnnotationObject)
+ new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject),
+
+ editBookmarkKeyword: function(aItemId, aNewKeyword)
+ new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword),
+
+ editBookmarkPostData: function(aItemId, aPostData)
+ new PlacesEditBookmarkPostDataTransaction(aItemId, aPostData),
+
+ editLivemarkSiteURI: function(aLivemarkId, aSiteURI)
+ new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI),
+
+ editLivemarkFeedURI: function(aLivemarkId, aFeedURI)
+ new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI),
+
+ editItemDateAdded: function(aItemId, aNewDateAdded)
+ new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded),
+
+ editItemLastModified: function(aItemId, aNewLastModified)
+ new PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified),
+
+ sortFolderByName: function(aFolderId)
+ new PlacesSortFolderByNameTransaction(aFolderId),
+
+ tagURI: function(aURI, aTags)
+ new PlacesTagURITransaction(aURI, aTags),
+
+ untagURI: function(aURI, aTags)
+ new PlacesUntagURITransaction(aURI, aTags),
+
+ /**
+ * Transaction for setting/unsetting Load-in-sidebar annotation.
+ *
+ * @param aBookmarkId
+ * id of the bookmark where to set Load-in-sidebar annotation.
+ * @param aLoadInSidebar
+ * boolean value.
+ * @returns nsITransaction object.
+ */
+ setLoadInSidebar: function(aItemId, aLoadInSidebar)
+ {
+ let annoObj = { name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+ type: Ci.nsIAnnotationService.TYPE_INT32,
+ flags: 0,
+ value: aLoadInSidebar,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
+ },
+
+ /**
+ * Transaction for editing the description of a bookmark or a folder.
+ *
+ * @param aItemId
+ * id of the item to edit.
+ * @param aDescription
+ * new description.
+ * @returns nsITransaction object.
+ */
+ editItemDescription: function(aItemId, aDescription)
+ {
+ let annoObj = { name: PlacesUIUtils.DESCRIPTION_ANNO,
+ type: Ci.nsIAnnotationService.TYPE_STRING,
+ flags: 0,
+ value: aDescription,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
+ },
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// nsITransactionManager forwarders.
+
+ beginBatch: function()
+ PlacesUtils.transactionManager.beginBatch(null),
+
+ endBatch: function()
+ PlacesUtils.transactionManager.endBatch(false),
+
+ doTransaction: function(txn)
+ PlacesUtils.transactionManager.doTransaction(txn),
+
+ undoTransaction: function()
+ PlacesUtils.transactionManager.undoTransaction(),
+
+ redoTransaction: function()
+ PlacesUtils.transactionManager.redoTransaction(),
+
+ get numberOfUndoItems()
+ PlacesUtils.transactionManager.numberOfUndoItems,
+ get numberOfRedoItems()
+ PlacesUtils.transactionManager.numberOfRedoItems,
+ get maxTransactionCount()
+ PlacesUtils.transactionManager.maxTransactionCount,
+ set maxTransactionCount(val)
+ PlacesUtils.transactionManager.maxTransactionCount = val,
+
+ clear: function()
+ PlacesUtils.transactionManager.clear(),
+
+ peekUndoStack: function()
+ PlacesUtils.transactionManager.peekUndoStack(),
+
+ peekRedoStack: function()
+ PlacesUtils.transactionManager.peekRedoStack(),
+
+ getUndoStack: function()
+ PlacesUtils.transactionManager.getUndoStack(),
+
+ getRedoStack: function()
+ PlacesUtils.transactionManager.getRedoStack(),
+
+ AddListener: function(aListener)
+ PlacesUtils.transactionManager.AddListener(aListener),
+
+ RemoveListener: function(aListener)
+ PlacesUtils.transactionManager.RemoveListener(aListener)
+ }
+});